/* * Created on 25-Jan-2007 * Created by Allan Crooks * Copyright (C) 2005, 2006 Aelitis, All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * AELITIS, SAS au capital de 46,603.30 euros * 8 Allee Lenotre, La Grille Royale, 78600 Le Mesnil le Roi, France. * */ package org.gudy.azureus2.ui.swt; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import org.eclipse.swt.SWT; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.events.MenuEvent; import org.eclipse.swt.events.MenuListener; import org.eclipse.swt.widgets.Menu; import org.gudy.azureus2.plugins.ui.Graphic; import org.gudy.azureus2.plugins.ui.menus.MenuItem; import org.gudy.azureus2.pluginsimpl.local.ui.menus.MenuItemImpl; import org.gudy.azureus2.pluginsimpl.local.utils.FormattersImpl; import org.gudy.azureus2.plugins.ui.tables.TableContextMenuItem; import org.gudy.azureus2.ui.swt.plugins.UISWTGraphic; import org.gudy.azureus2.core3.util.AERunnable; import org.gudy.azureus2.core3.util.Constants; /** * A class which helps generate build SWT menus. * * It provides two main functions. The first provides the ability to create * regenerateable menus (it will dispose old items when not displayed and * invoke a callback method to regenerate it). * * The second provides the ability to create SWT menus based on the plugin * API for menu creation - this allows internal code that generates menus * to include plugins to append to their own internal menus. * * @author Allan Crooks */ public class MenuBuildUtils { /** * An interface to be used for addMaintenanceListenerForMenu. */ public static interface MenuBuilder { public void buildMenu(Menu root_menu, MenuEvent menuEvent); } /** * Creates and adds a listener object to implement regeneratable menus. * * The first piece of functionality it offers is the ability to call a * callback method to generate the menu when it is about to be displayed * (the callback method is done by passing an object implementing the * MenuBuilder interface). * * This means that the caller of this method only needs to provide the * logic to construct the menu's contents. This is helpful if you want * to update an existing menu. * * The second piece of functionality is that it automatically does what * is required to dispose of existing menu items when the menu is hidden. */ public static void addMaintenanceListenerForMenu(final Menu menu, final MenuBuilder builder) { if (Constants.isLinux) { // Hack for Ubuntu Unity -- Show not called when no items new org.eclipse.swt.widgets.MenuItem(menu, SWT.SEPARATOR); } // Was taken from TableView.java menu.addMenuListener(new MenuListener() { boolean bShown = false; public void menuHidden(MenuEvent e) { bShown = false; if (Constants.isOSX) return; // Must dispose in an asyncExec, otherwise SWT.Selection doesn't // get fired (async workaround provided by Eclipse Bug #87678) e.widget.getDisplay().asyncExec(new AERunnable() { public void runSupport() { if (bShown || menu.isDisposed()) return; org.eclipse.swt.widgets.MenuItem[] items = menu .getItems(); for (int i = 0; i < items.length; i++) { items[i].dispose(); } if (Constants.isLinux) { // Hack for Ubuntu Unity -- Show not called when no items new org.eclipse.swt.widgets.MenuItem(menu, SWT.SEPARATOR); } } }); }; public void menuShown(MenuEvent e) { try{ org.eclipse.swt.widgets.MenuItem[] items = menu.getItems(); for (int i = 0; i < items.length; i++){ items[i].dispose(); } }catch( Throwable f ){ // getting java.lang.NegativeArraySizeException sometimes on linux. ignore } bShown = true; builder.buildMenu(menu, e); if (Constants.isLinux) { // Hack for Ubuntu Unity -- Show not called when no items if (menu.getItemCount() == 0) { new org.eclipse.swt.widgets.MenuItem(menu, SWT.SEPARATOR); } } } }); } /** * This is an interface used by addPluginMenuItems. */ public static interface PluginMenuController { /** * This method should create a listener object which should be invoked * when the given menu item is selected. */ public Listener makeSelectionListener(MenuItem plugin_menu_item); /** * This method will be invoked just before the given menu item is * displayed in a menu. */ public void notifyFillListeners(MenuItem menu_item); } /** * This is an implementation of the PluginMenuController interface for use with * MenuItemImpl classes - note that this is not intended for use by subclasses of * MenuItemImpl (like TableContextMenuItemImpl). * * The object passed at construction time is the object to be passed when selection * listeners and fill listeners are notified. */ public static class MenuItemPluginMenuControllerImpl implements PluginMenuController { private Object[] objects; public MenuItemPluginMenuControllerImpl(Object[] o) { this.objects = o; } public Listener makeSelectionListener(MenuItem menu_item) { final MenuItemImpl mii = (MenuItemImpl) menu_item; return new Listener() { public void handleEvent(Event e) { mii.invokeListenersMulti(objects); } }; } public void notifyFillListeners(MenuItem menu_item) { ((MenuItemImpl) menu_item).invokeMenuWillBeShownListeners(objects); } } /** * An instance of MenuItemPluginMenuControllerImpl with a default value of * null - this will be the value passed when notifying selection and fill * listeners. */ public static final PluginMenuController BASIC_MENU_ITEM_CONTROLLER = new MenuItemPluginMenuControllerImpl(null); /** * Creates menu items inside the given menu based on the plugin API MenuItem * instances provided. This method is provided mainly as a utility method to * make it easier for menus to incorporate menu components specified by * plugins. * * Usually, the list of array items will be extracted from a class like * MenuItemManager or TableContextMenuManager, where plugins will usually * register menu items they have created. * * @param composite Some composite to get a shell from. * @param items The list of plugin MenuItem to add * @param parent The SWT Menu to add to. * @param prev_was_separator Indicates if the previous item in the menu is * a separator or not * @param enable_items Indicates whether you want generated items to be * enabled or not. If false, all items will be disabled. If true, * then the items *may* be enabled, depending on how each MenuItem * is configured. * @param controller The callback object used by this method when creating the * SWT menus (used for invoking fill and selection listeners). */ public static void addPluginMenuItems(Composite composite, MenuItem[] items, Menu parent, boolean prev_was_separator, boolean enable_items, PluginMenuController controller) { for (int i = 0; i < items.length; i++) { final MenuItemImpl az_menuitem = (MenuItemImpl) items[i]; controller.notifyFillListeners(az_menuitem); if (!az_menuitem.isVisible()) {continue;} final int style = az_menuitem.getStyle(); final int swt_style; boolean this_is_separator = false; // Do we have any children? If so, we override any manually defined // style. boolean is_container = false; if (style == TableContextMenuItem.STYLE_MENU) { swt_style = SWT.CASCADE; is_container = true; } else if (style == TableContextMenuItem.STYLE_PUSH) { swt_style = SWT.PUSH; } else if (style == TableContextMenuItem.STYLE_CHECK) { swt_style = SWT.CHECK; } else if (style == TableContextMenuItem.STYLE_RADIO) { swt_style = SWT.RADIO; } else if (style == TableContextMenuItem.STYLE_SEPARATOR) { this_is_separator = true; swt_style = SWT.SEPARATOR; } else { swt_style = SWT.PUSH; } if (prev_was_separator && this_is_separator) {continue;} // Skip contiguous separators if (this_is_separator && i == items.length - 1) {continue;} // Skip trailing separator prev_was_separator = this_is_separator; final org.eclipse.swt.widgets.MenuItem menuItem = new org.eclipse.swt.widgets.MenuItem( parent, swt_style); if (swt_style == SWT.SEPARATOR) {continue;} if (enable_items) { if (style == TableContextMenuItem.STYLE_CHECK || style == TableContextMenuItem.STYLE_RADIO) { Boolean selection_value = (Boolean) az_menuitem.getData(); if (selection_value == null) { throw new RuntimeException( "MenuItem with resource name \"" + az_menuitem.getResourceKey() + "\" needs to have a boolean value entered via setData before being used!"); } menuItem.setSelection(selection_value.booleanValue()); } } final Listener main_listener = controller.makeSelectionListener(az_menuitem); menuItem.addListener(SWT.Selection, new Listener() { public void handleEvent(Event e) { if (az_menuitem.getStyle() == MenuItem.STYLE_CHECK || az_menuitem.getStyle() == MenuItem.STYLE_RADIO) { if (!menuItem.isDisposed()) { az_menuitem.setData(new Boolean(menuItem.getSelection())); } } main_listener.handleEvent(e); } }); if (is_container) { Menu this_menu = new Menu(composite.getShell(), SWT.DROP_DOWN); menuItem.setMenu(this_menu); addPluginMenuItems(composite, az_menuitem.getItems(), this_menu, false, enable_items, controller); } String custom_title = az_menuitem.getText(); menuItem.setText(custom_title); Graphic g = az_menuitem.getGraphic(); if (g instanceof UISWTGraphic) { Utils.setMenuItemImage(menuItem, ((UISWTGraphic) g).getImage()); } menuItem.setEnabled(enable_items && az_menuitem.isEnabled()); } } /** * * @param flat_entries Overall list of menu entry names * @param split_after Split if more than this * @return Entries are either a String or Object[]{ submeuname, List<String> submenu entries } */ public static List<Object> splitLongMenuListIntoHierarchy( List<String> flat_entries, int split_after ) { List<Object> result = new ArrayList<Object>(); int flat_entry_count = flat_entries.size(); if ( flat_entry_count == 0 ){ return( result ); } Collections.sort( flat_entries, new Comparator<String>() { final Comparator<String> comp = new FormattersImpl().getAlphanumericComparator( true ); public int compare( String o1, String o2) { return( comp.compare( o1, o2 )); } }); int[] buckets = new int[split_after]; for ( int i=0;i<flat_entry_count;i++){ buckets[i%buckets.length]++; } List<char[]> edges = new ArrayList<char[]>(); int pos = 0; for ( int i=0;i<buckets.length;i++){ int entries = buckets[i]; edges.add( flat_entries.get( pos ).toCharArray()); if ( entries > 1 ){ edges.add( flat_entries.get( pos + entries - 1 ).toCharArray()); pos += entries; }else{ break; } } int[] edge_lens = new int[edges.size()]; for ( int i=0;i<edges.size()-1;i++){ char[] c1 = edges.get(i); char[] c2 = edges.get(i+1); int j; for ( j=0;j<Math.min(Math.min(c1.length,c2.length),5); j++ ){ if ( c1[j] != c2[j]){ break; } } j++; edge_lens[i] = Math.min( c1.length,Math.max( edge_lens[i], j )); edge_lens[i+1] = j; } int bucket_pos = 0; int edge_pos = 0; Iterator<String>tag_it = flat_entries.iterator(); while( tag_it.hasNext()){ int bucket_entries = buckets[bucket_pos++]; List<String> bucket_tags = new ArrayList<String>(); for ( int i=0;i<bucket_entries;i++){ bucket_tags.add( tag_it.next()); } if ( bucket_entries == 1 ){ result.add( bucket_tags.get(0)); }else{ String level_name = new String( edges.get( edge_pos ), 0, edge_lens[ edge_pos++ ]) + " - " + new String( edges.get( edge_pos ), 0, edge_lens[ edge_pos++ ]); result.add( new Object[]{ level_name, bucket_tags }); } } return( result ); } }