/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.viewers; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.IColorProvider; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.IOpenListener; import org.eclipse.jface.viewers.LabelProviderChangedEvent; import org.eclipse.jface.viewers.OpenEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StructuredViewer; import org.eclipse.jface.viewers.ViewerComparator; import org.eclipse.swt.SWT; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.ImageData; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Widget; import org.xmind.ui.dialogs.PopupFilteredList; import org.xmind.ui.dialogs.PopupFilteredList.PatternFilter; import org.xmind.ui.resources.ImageDescriptorProvider; public class MComboViewer extends StructuredViewer { /** * Style bit: Create handle control and drop-down widget with default * behaviours, i.e. showing text, showing image, using menu as drop-down * widget. */ public static final int NORMAL = MButton.NORMAL; /** * Style bit: Don't show text. */ public static final int NO_TEXT = MButton.NO_TEXT; /** * Style bit: Don't show image. */ public static final int NO_IMAGE = MButton.NO_IMAGE; /** * Style bit: Use filtered list as drop-down widget. */ public static final int FILTERED = 1 << 10; private class SelectionAction extends Action { private Object element; public SelectionAction(Object element) { this.element = element; } public void run() { super.run(); setSelection(new StructuredSelection(element)); } } private static final List EMPTY_LIST = Collections.emptyList(); private MButton dropDownHandle; private final boolean filtered; private List<Object> elementList = new ArrayList<Object>(); private Object selection = null; private MenuManager popupMenu = null; private Map<Object, IAction> actionMap = null; private PopupFilteredList popupList = null; private PatternFilter patternFilter = null; private boolean permitsUnprovidedElement = false; private Object emptySelectionImitation = null; private Object separatorImitation = null; private ILabelProvider handleLabelProvider = null; private ILabelProviderListener handleLabelProviderListener = new ILabelProviderListener() { public void labelProviderChanged(LabelProviderChangedEvent event) { if (getControl() == null || getControl().isDisposed()) return; MComboViewer.this.handleLabelProviderChanged(event); } }; /** * Constructs a new instance of this class given its parent and a style * value describing its behavior and appearance. * * @param parent * a composite control which will be the parent of the new * instance (cannot be null) * @param style * the style of control to construct * @see #NORNAL * @see #NO_TEXT * @see #NO_IMAGE * @see #FILTERED */ public MComboViewer(Composite parent, int style) { this.dropDownHandle = createDropDownHandle(parent, style); hookControl(dropDownHandle.getControl()); dropDownHandle.addOpenListener(new IOpenListener() { public void open(OpenEvent event) { openPopup(); } }); this.filtered = (style & FILTERED) != 0; } protected MButton createDropDownHandle(Composite parent, int style) { return new MButton(parent, style); } protected void hookControl(Control control) { super.hookControl(control); Listener listener = new Listener() { public void handleEvent(Event event) { switch (event.type) { case SWT.KeyDown: handleKeyPress(event); break; } } }; control.addListener(SWT.KeyDown, listener); } protected void handleKeyPress(Event e) { if (!dropDownHandle.getControl().isEnabled()) return; int keyCode = e.keyCode; int stateMask = e.stateMask; if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.ARROW_UP)) { selectPrevious(); } else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.ARROW_DOWN)) { selectNext(); } else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.HOME)) { selectFirst(); } else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.END)) { selectLast(); } } protected void selectPrevious() { if (hasNoElement()) return; int index = getSelectionIndex(); if (index <= 0 || index >= getItemCount()) { //selectLast(); } else { setSelection(new StructuredSelection(elementList.get(index - 1))); } } protected void selectNext() { if (hasNoElement()) return; int index = getSelectionIndex(); if (index < 0 || index >= getItemCount() - 1) { //selectFirst(); } else { setSelection(new StructuredSelection(elementList.get(index + 1))); } } protected void selectFirst() { if (hasNoElement()) return; setSelection(new StructuredSelection(elementList.get(0))); } protected void selectLast() { if (hasNoElement()) return; setSelection( new StructuredSelection(elementList.get(getItemCount() - 1))); } protected boolean hasNoElement() { return elementList.isEmpty(); } protected int getItemCount() { return elementList.size(); } protected int getSelectionIndex() { if (getCurrentSelection() == null || hasNoElement()) return -1; return indexForElement(getCurrentSelection()); } protected int indexForElement(Object element) { ViewerComparator comparator = getComparator(); if (comparator == null) { return elementList.indexOf(element); } int count = getItemCount(); int min = 0, max = count - 1; while (min <= max) { int mid = (min + max) / 2; Object data = elementList.get(mid); int compare = comparator.compare(this, data, element); if (compare == 0) { // find first item > element while (compare == 0) { ++mid; if (mid >= count) { break; } data = elementList.get(mid); compare = comparator.compare(this, data, element); } return mid; } if (compare < 0) { min = mid + 1; } else { max = mid - 1; } } return min; } protected void inputChanged(Object input, Object oldInput) { closePopup(); elementList.clear(); Object[] children = getSortedChildren(getRoot()); int size = children.length; Object newSelection = null; for (int i = 0; i < size; i++) { Object el = children[i]; elementList.add(el); mapElement(el, getControl()); // must map it, since findItem only looks in map, if enabled if (selection != null && equals(selection, el)) { newSelection = el; } } selection = newSelection; updateDropDown(); if (popupMenu != null) { refreshPopupMenu(popupMenu); } else if (popupList != null) { refreshPopupList(popupList); } } protected Widget doFindInputItem(Object element) { return dropDownHandle.getControl(); } protected Widget doFindItem(Object element) { return dropDownHandle.getControl(); } protected void doUpdateItem(Widget item, Object element, boolean fullMap) { if (equals(element, getRoot()) || equals(element, getCurrentSelection())) { updateDropDown(); } if (popupMenu != null) { updateAction(element); } } protected void updateAction(Object element) { if (actionMap == null) return; IAction action = actionMap.get(element); if (action == null) return; ILabelProvider labelProvider = (ILabelProvider) getLabelProvider(); updateAction(action, labelProvider, element); } protected void updateAction(IAction action, ILabelProvider labelProvider, Object element) { String text = labelProvider.getText(element); if (text == null) text = ""; //$NON-NLS-1$ action.setText(text); if (labelProvider instanceof ImageDescriptorProvider) { action.setImageDescriptor(((ImageDescriptorProvider) labelProvider) .getImageDescriptor(element)); } else { final Image image = labelProvider.getImage(element); if (image != null) action.setImageDescriptor(new ImageDescriptor() { @Override public ImageData getImageData() { return image.getImageData(); } }); else action.setImageDescriptor(null); } } protected List getSelectionFromWidget() { return getCurrentSelection() == null ? EMPTY_LIST : Collections.singletonList(getCurrentSelection()); } protected void internalRefresh(Object element) { if (equals(element, getRoot())) { updateDropDown(); if (popupMenu != null) { refreshPopupMenu(popupMenu); } else if (popupList != null) { refreshPopupList(popupList); } } else { if (equals(element, getCurrentSelection())) { updateDropDown(); } if (popupMenu != null) { updateAction(element); } } } protected void updateDropDown() { String text = null; Image image = null; Color textFg = null; Color textBg = null; // Point textSize = null; Point imageSize = null; Object currentSelection = getCurrentSelection(); ILabelProvider labelProvider = (ILabelProvider) getWorkingHandleLabelProvider( ILabelProvider.class); if (labelProvider != null) { if (currentSelection == null) currentSelection = emptySelectionImitation; if (currentSelection != null) { text = labelProvider.getText(currentSelection); image = labelProvider.getImage(currentSelection); } if (!hasNoElement()) { for (Object element : elementList) { Image i = labelProvider.getImage(element); if (i != null) { Rectangle b = i.getBounds(); imageSize = union(imageSize, b.width, b.height); } } } } if (currentSelection != null) { IColorProvider colorProvider = (IColorProvider) getWorkingHandleLabelProvider( IColorProvider.class); if (colorProvider != null) { textFg = colorProvider.getForeground(currentSelection); textBg = colorProvider.getBackground(currentSelection); } IToolTipProvider toolTipProvider = (IToolTipProvider) getWorkingHandleLabelProvider( IToolTipProvider.class); if (toolTipProvider != null) { String tooltip = toolTipProvider.getToolTip(currentSelection); dropDownHandle.getControl().setToolTipText(tooltip); } } dropDownHandle.setText(text); dropDownHandle.setImage(image); dropDownHandle.setImageSize(imageSize); dropDownHandle.setTextForeground(textFg); dropDownHandle.setTextBackground(textBg); } private Object getWorkingHandleLabelProvider(Class<?> type) { if (handleLabelProvider != null && type.isInstance(handleLabelProvider)) return handleLabelProvider; if (getLabelProvider() != null && type.isInstance(getLabelProvider())) return getLabelProvider(); return null; } public void reveal(Object element) { } protected void setSelectionToWidget(List l, boolean reveal) { if (l != null && !l.isEmpty()) selection = l.get(0); else selection = null; updateDropDown(); if (popupMenu != null) { setSelectionToMenu(popupMenu); } else if (popupList != null) { popupList.setDefaultSelection(selection); } } public Control getControl() { return dropDownHandle.getControl(); } public Object getCurrentSelection() { return selection; } public PopupFilteredList getPopupList() { return popupList; } public Menu getMenu() { return popupMenu == null ? null : popupMenu.getMenu(); } public void open() { openPopup(); } protected void openPopup() { if (filtered) { if (popupList == null) { popupList = createPopupList(); } if (popupList != null) { openPopupList(popupList); } } else { if (popupMenu == null) { popupMenu = createPopupMenu(); } if (popupMenu != null) { openPopupMenu(popupMenu); } } } protected void openPopupMenu(MenuManager menuManager) { Menu menu = menuManager.getMenu(); if (menu != null && !menu.isDisposed()) { if (menu.isVisible()) { menu.setVisible(false); } else { locatePopupMenu(menu); menu.setVisible(true); } } } protected MenuManager createPopupMenu() { MenuManager popupMenu = new MenuManager(); refreshPopupMenu(popupMenu); popupMenu.createContextMenu(getControl()); return popupMenu; } protected void refreshPopupMenu(MenuManager menuManager) { menuManager.removeAll(); if (actionMap == null) { actionMap = new HashMap<Object, IAction>(); } else { actionMap.clear(); } ILabelProvider labelProvider = (ILabelProvider) getLabelProvider(); for (Object element : elementList) { if (separatorImitation != null && element == separatorImitation) { menuManager.add(new Separator()); } else { IAction action = actionMap.get(element); if (action == null) { action = new SelectionAction(element); action.setChecked(false); actionMap.put(element, action); } updateAction(action, labelProvider, element); menuManager.add(action); } } setSelectionToMenu(menuManager); } protected void locatePopupMenu(Menu menu) { Rectangle r; if (getControl() instanceof Composite) { r = ((Composite) getControl()).getClientArea(); } else { r = getControl().getBounds(); r.x = r.y = 0; } Point loc = getControl().toDisplay(r.x, r.y); loc.y += r.height; menu.setLocation(loc); } protected PopupFilteredList createPopupList() { PopupFilteredList list = new PopupFilteredList(getControl().getShell()); refreshPopupList(list); list.addOpenListener(new IOpenListener() { public void open(OpenEvent event) { setSelection(event.getSelection()); } }); return list; } protected void refreshPopupList(PopupFilteredList list) { list.setPermitsUnprovidedElement(permitsUnprovidedElement()); list.setPatternFilter(getPatternFilter()); list.setContentProvider(getContentProvider()); list.setLabelProvider(getLabelProvider()); list.setFilters(getFilters()); list.setSorter(getSorter()); list.setComparator(getComparator()); list.setComparer(getComparer()); list.setDefaultSelection(getCurrentSelection()); list.setInput(getInput()); } protected void openPopupList(PopupFilteredList list) { locatePopupList(list); list.open(); Shell shell = list.getShell(); if (shell != null && !shell.isDisposed()) { dropDownHandle.setForceFocus(true); shell.addListener(SWT.Dispose, new Listener() { public void handleEvent(Event event) { if (!dropDownHandle.getControl().isDisposed()) { dropDownHandle.setForceFocus(false); } } }); } } protected void locatePopupList(PopupFilteredList list) { Rectangle r; if (getControl() instanceof Composite) { r = ((Composite) getControl()).getClientArea(); } else { r = getControl().getBounds(); r.x = r.y = 0; } Point loc = getControl().toDisplay(r.x, r.y); r.x = loc.x; r.y = loc.y; list.setBoundsReference(r); } protected void closePopup() { if (popupMenu != null) { Menu menu = popupMenu.getMenu(); if (menu != null && !menu.isDisposed()) { menu.setVisible(false); } } if (popupList != null) { popupList.close(); } } protected void setSelectionToMenu(MenuManager menuManager) { int index = getSelectionIndex(); Menu menu = menuManager.getMenu(); if (menu != null && !menu.isDisposed()) { if (index < 0 || index >= menu.getItemCount()) { menu.setDefaultItem(null); } else { menu.setDefaultItem(menu.getItem(index)); } } if (actionMap != null) { for (Object element : elementList) { IAction action = actionMap.get(element); if (action != null) { action.setChecked(equals(element, getCurrentSelection())); } } } } protected void handleDispose(DisposeEvent event) { if (handleLabelProvider != null) { handleLabelProvider.removeListener(handleLabelProviderListener); handleLabelProvider.dispose(); handleLabelProvider = null; } super.handleDispose(event); closePopup(); popupList = null; if (popupMenu != null) { popupMenu.dispose(); popupMenu = null; } if (actionMap != null) { actionMap.clear(); actionMap = null; } } public boolean permitsUnprovidedElement() { return permitsUnprovidedElement; } public void setPermitsUnprovidedElement(boolean permitsUnprovidedElement) { if (permitsUnprovidedElement == this.permitsUnprovidedElement) return; this.permitsUnprovidedElement = permitsUnprovidedElement; if (popupList != null) { popupList.setPermitsUnprovidedElement(permitsUnprovidedElement); } } public PatternFilter getPatternFilter() { return patternFilter; } public void setPatternFilter(PatternFilter patternFilter) { if (patternFilter == this.patternFilter) return; this.patternFilter = patternFilter; if (popupList != null) { popupList.setPatternFilter(patternFilter); } } protected static Point union(Point size, int width, int height) { if (size == null) return new Point(width, height); size.x = Math.max(size.x, width); size.y = Math.max(size.y, height); return size; } public Object getEmptySelectionImitation() { return emptySelectionImitation; } public void setEmptySelectionImitation(Object emptySelectionSubstitution) { this.emptySelectionImitation = emptySelectionSubstitution; } public Object getSeparatorImitation() { return separatorImitation; } public void setSeparatorImitation(Object separatorImitation) { this.separatorImitation = separatorImitation; } public void setEnabled(boolean enabled) { dropDownHandle.setEnabled(enabled); } public boolean isEnabled() { return dropDownHandle.isEnabled(); } public boolean isDropDownVisible() { if (filtered) { return popupList != null && popupList.getShell() != null && !popupList.getShell().isDisposed() && popupList.getShell().isVisible(); } return popupMenu != null && popupMenu.getMenu() != null && !popupMenu.getMenu().isDisposed() && popupMenu.getMenu().isVisible(); } public ILabelProvider getHandleLabelProvider() { return handleLabelProvider; } public void setHandleLabelProvider(ILabelProvider labelProvider) { ILabelProvider oldLabelProvider = this.handleLabelProvider; if (oldLabelProvider == labelProvider) return; if (oldLabelProvider != null) { oldLabelProvider.removeListener(handleLabelProviderListener); } this.handleLabelProvider = labelProvider; if (labelProvider != null) { labelProvider.addListener(handleLabelProviderListener); } refresh(); if (oldLabelProvider != null) { oldLabelProvider.dispose(); } } public void setHandlePaddings(int leftPadding, int topPadding, int rightPadding, int bottomPadding) { if (dropDownHandle != null) { dropDownHandle.setPaddings(leftPadding, topPadding, rightPadding, bottomPadding); } } public void setHandleHorizontalPaddings(int leftPadding, int rightPadding) { if (dropDownHandle != null) { dropDownHandle.setHorizontalPaddings(leftPadding, rightPadding); } } public void setHandleVerticalPaddings(int topPadding, int bottomPadding) { if (dropDownHandle != null) { dropDownHandle.setVerticalPaddings(topPadding, bottomPadding); } } public int getHandleLeftPadding() { int leftPadding = 0; if (dropDownHandle != null) { leftPadding = dropDownHandle.getLeftPadding(); } return leftPadding; } public int getHandleTopPadding() { int topPadding = 0; if (dropDownHandle != null) { topPadding = dropDownHandle.getTopPadding(); } return topPadding; } public int getHandleRightPadding() { int rightPadding = 0; if (dropDownHandle != null) { rightPadding = dropDownHandle.getRightPadding(); } return rightPadding; } public int getHandleBottomPadding() { int bottomPadding = 0; if (dropDownHandle != null) { bottomPadding = dropDownHandle.getBottomPadding(); } return bottomPadding; } }