/* ******************************************************************************
* 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.richtext;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.jface.action.ExternalActionManager;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IContributionManagerOverrides;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.resource.LocalResourceManager;
import org.eclipse.jface.resource.ResourceManager;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.swt.widgets.Widget;
public class AlignmentGroup extends ContributionItem {
private List<IAction> actions = new ArrayList<IAction>();
private IAction currentAction = null;
private IPropertyChangeListener actionListener = null;
/**
* Listener for SWT tool item widget events.
*/
private Listener toolItemListener;
/**
* The widget created for this item; <code>null</code> before creation and
* after disposal.
*/
private ToolItem widget = null;
/**
* Listener for action property change notifications.
*/
private IPropertyChangeListener currentActionListener = null;
private MenuManager dropDownMenuManager = null;
/**
* Remembers all images in use by this contribution item
*/
private LocalResourceManager imageManager;
public void add(IAction action) {
actions.add(action);
action.addPropertyChangeListener(getActionListener());
if (currentAction == null) {
setCurrentAction(action);
}
}
public void remove(IAction action) {
action.removePropertyChangeListener(getActionListener());
actions.remove(action);
if (currentAction == action) {
setCurrentAction(actions.isEmpty() ? null : actions.get(0));
}
}
public IAction getCurrentAction() {
return currentAction;
}
public void setCurrentAction(IAction action) {
if (this.currentAction != null && currentActionListener != null) {
this.currentAction
.removePropertyChangeListener(currentActionListener);
}
this.currentAction = action;
if (action != null) {
action.setChecked(true);
}
for (IAction a : actions) {
if (a != action) {
a.setChecked(false);
}
}
if (action != null) {
if (widget != null && !widget.isDisposed()) {
action.addPropertyChangeListener(getCurrentActionListener());
}
}
update(null);
}
private IPropertyChangeListener getActionListener() {
if (actionListener == null) {
actionListener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
actionPropertyChange(event);
}
};
}
return actionListener;
}
protected void actionPropertyChange(PropertyChangeEvent event) {
IAction triggerAction = (IAction) event.getSource();
String property = event.getProperty();
if (IAction.CHECKED.equals(property)
&& Boolean.TRUE.equals(event.getNewValue())) {
setCurrentAction(triggerAction);
}
}
// public void fill(Menu menu, int index) {
// for (IAction action : actions) {
// new ActionContributionItem(action).fill(menu, index++);
// }
// }
//
public void fill(ToolBar parent, int index) {
if (widget == null && parent != null) {
int flags = SWT.DROP_DOWN;
ToolItem ti = null;
if (index >= 0) {
ti = new ToolItem(parent, flags, index);
} else {
ti = new ToolItem(parent, flags);
}
ti.setData(this);
ti.addListener(SWT.Selection, getToolItemListener());
ti.addListener(SWT.Dispose, getToolItemListener());
widget = ti;
update(null);
// Attach some extra listeners.
if (currentAction != null)
currentAction
.addPropertyChangeListener(getCurrentActionListener());
// }
}
}
private IPropertyChangeListener getCurrentActionListener() {
if (currentActionListener == null) {
currentActionListener = new IPropertyChangeListener() {
public void propertyChange(PropertyChangeEvent event) {
currentActionPropertyChange(event);
}
};
}
return currentActionListener;
}
private void currentActionPropertyChange(final PropertyChangeEvent e) {
// This code should be removed. Avoid using free asyncExec
if (isVisible() && widget != null) {
Display display = widget.getDisplay();
if (display.getThread() == Thread.currentThread()) {
update(e.getProperty());
} else {
display.asyncExec(new Runnable() {
public void run() {
update(e.getProperty());
}
});
}
}
}
public void update() {
update(null);
}
public void update(String propertyName) {
if (widget == null || currentAction == null)
return;
boolean textChanged = propertyName == null
|| propertyName.equals(IAction.TEXT);
boolean imageChanged = propertyName == null
|| propertyName.equals(IAction.IMAGE);
boolean tooltipTextChanged = propertyName == null
|| propertyName.equals(IAction.TOOL_TIP_TEXT);
boolean enableStateChanged = propertyName == null
|| propertyName.equals(IAction.ENABLED)
|| propertyName.equals(IContributionManagerOverrides.P_ENABLED);
boolean checkChanged = (currentAction.getStyle() == IAction.AS_CHECK_BOX || currentAction
.getStyle() == IAction.AS_RADIO_BUTTON)
&& (propertyName == null || propertyName
.equals(IAction.CHECKED));
ToolItem ti = (ToolItem) widget;
String text = currentAction.getText();
// the set text is shown only if there is no image or if forced
// by MODE_FORCE_TEXT
boolean showText = text != null && !hasImages(currentAction);
// only do the trimming if the text will be used
if (showText && text != null) {
text = Action.removeAcceleratorText(text);
text = Action.removeMnemonics(text);
}
if (textChanged) {
String textToSet = showText ? text : ""; //$NON-NLS-1$
boolean rightStyle = (ti.getParent().getStyle() & SWT.RIGHT) != 0;
if (rightStyle || !ti.getText().equals(textToSet)) {
// In addition to being required to update the text if
// it
// gets nulled out in the action, this is also a
// workaround
// for bug 50151: Using SWT.RIGHT on a ToolBar leaves
// blank space
ti.setText(textToSet);
}
}
if (imageChanged) {
// only substitute a missing image if it has no text
updateImages(!showText);
}
if (tooltipTextChanged || textChanged) {
String toolTip = currentAction.getToolTipText();
if ((toolTip == null) || (toolTip.length() == 0)) {
toolTip = text;
}
ExternalActionManager.ICallback callback = ExternalActionManager
.getInstance().getCallback();
String commandId = currentAction.getActionDefinitionId();
if ((callback != null) && (commandId != null) && (toolTip != null)) {
String acceleratorText = callback.getAcceleratorText(commandId);
if (acceleratorText != null && acceleratorText.length() != 0) {
toolTip = JFaceResources.format(
"Toolbar_Tooltip_Accelerator", //$NON-NLS-1$
new Object[] { toolTip, acceleratorText });
}
}
// if the text is showing, then only set the tooltip if
// different
if (!showText || toolTip != null && !toolTip.equals(text)) {
ti.setToolTipText(toolTip);
} else {
ti.setToolTipText(null);
}
}
if (enableStateChanged) {
boolean shouldBeEnabled = currentAction.isEnabled()
&& isEnabledAllowed();
if (ti.getEnabled() != shouldBeEnabled) {
ti.setEnabled(shouldBeEnabled);
}
}
if (checkChanged) {
boolean bv = currentAction.isChecked();
if (ti.getSelection() != bv) {
ti.setSelection(bv);
}
}
}
/**
* Returns whether the given action has any images.
*
* @param actionToCheck
* the action
* @return <code>true</code> if the action has any images,
* <code>false</code> if not
*/
private boolean hasImages(IAction actionToCheck) {
return actionToCheck.getImageDescriptor() != null
|| actionToCheck.getHoverImageDescriptor() != null
|| actionToCheck.getDisabledImageDescriptor() != null;
}
/**
* Updates the images for this action.
*
* @param forceImage
* <code>true</code> if some form of image is compulsory, and
* <code>false</code> if it is acceptable for this item to have
* no image
* @return <code>true</code> if there are images for this action,
* <code>false</code> if not
*/
private boolean updateImages(boolean forceImage) {
ResourceManager parentResourceManager = JFaceResources.getResources();
ImageDescriptor image = currentAction.getHoverImageDescriptor();
if (image == null) {
image = currentAction.getImageDescriptor();
}
ImageDescriptor disabledImage = currentAction
.getDisabledImageDescriptor();
// Make sure there is a valid image.
if (image == null && forceImage) {
image = ImageDescriptor.getMissingImageDescriptor();
}
LocalResourceManager localManager = new LocalResourceManager(
parentResourceManager);
// performance: more efficient in SWT to set disabled and hot
// image before regular image
widget.setDisabledImage(disabledImage == null ? null : localManager
.createImageWithDefault(disabledImage));
widget.setImage(image == null ? null : localManager
.createImageWithDefault(image));
disposeOldImages();
imageManager = localManager;
return image != null;
}
/**
* Returns <code>true</code> if this item is allowed to enable,
* <code>false</code> otherwise.
*
* @return if this item is allowed to be enabled
* @since 2.0
*/
protected boolean isEnabledAllowed() {
if (getParent() == null) {
return true;
}
Boolean value = getParent().getOverrides().getEnabled(this);
return (value == null) ? true : value.booleanValue();
}
/**
* Returns the listener for SWT tool item widget events.
*
* @return a listener for tool item events
*/
private Listener getToolItemListener() {
if (toolItemListener == null) {
toolItemListener = new Listener() {
public void handleEvent(Event event) {
switch (event.type) {
case SWT.Dispose:
handleWidgetDispose(event);
break;
case SWT.Selection:
Widget ew = event.widget;
if (ew != null) {
handleWidgetSelection(event, ((ToolItem) ew)
.getSelection());
}
break;
}
}
};
}
return toolItemListener;
}
/**
* Handles a widget dispose event for the widget corresponding to this item.
*/
private void handleWidgetDispose(Event e) {
// Check if our widget is the one being disposed.
if (e.widget == widget) {
if (dropDownMenuManager != null) {
dropDownMenuManager.dispose();
dropDownMenuManager = null;
}
// Unhook all of the listeners.
if (currentAction != null && currentActionListener != null) {
currentAction
.removePropertyChangeListener(currentActionListener);
}
// Clear the widget field.
widget = null;
disposeOldImages();
}
}
private MenuManager getDropDownMenuManager() {
if (dropDownMenuManager == null) {
dropDownMenuManager = new MenuManager();
dropDownMenuManager.setRemoveAllWhenShown(true);
dropDownMenuManager.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
for (IAction action : actions) {
manager.add(action);
}
}
});
}
return dropDownMenuManager;
}
/**
* Handles a widget selection event.
*/
private void handleWidgetSelection(Event e, boolean selection) {
if (e.widget == widget) {
MenuManager menuMan = getDropDownMenuManager();
Menu menu = menuMan.createContextMenu(widget.getParent());
if (menu != null) {
Rectangle b = widget.getBounds();
Point p = widget.getParent().toDisplay(b.x, b.y + b.height);
menu.setLocation(p.x, p.y);
menu.setVisible(true);
}
}
}
/**
* Dispose any images allocated for this contribution item
*/
private void disposeOldImages() {
if (imageManager != null) {
imageManager.dispose();
imageManager = null;
}
}
}