package org.simantics.ui.toolbar; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.CommandEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.ICommandListener; import org.eclipse.core.commands.IParameter; import org.eclipse.core.commands.Parameterization; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.commands.State; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExecutableExtension; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.ActionContributionItem; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.ICoolBarManager; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.action.ToolBarContributionItem; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.nebula.widgets.tablecombo.TableCombo; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.TableItem; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IPartListener; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.handlers.HandlerUtil; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.handlers.RadioState; import org.eclipse.ui.handlers.RegistryToggleState; import org.eclipse.ui.menus.WorkbenchWindowControlContribution; import org.eclipse.ui.part.EditorActionBarContributor; import org.simantics.db.common.utils.Logger; import org.simantics.ui.internal.Activator; import org.simantics.ui.toolbar.ToolBarCommandRegistry.Parameter; import org.simantics.ui.toolbar.ToolBarCommandRegistry.ToolbarCommandExtension; import org.simantics.utils.datastructures.MapList; import org.simantics.utils.ui.ExceptionUtils; /** * EditorBarContributor, which tracks toggle states separately for each command. * * @see org.simantics.g3d.toolbarCommand Extension Point * * @author Marko Luukkainen * * * * TODO : configuring the position of buttons. * */ public class ToolbarContributor extends EditorActionBarContributor implements ICommandListener, IPartListener, CommandStateListener, IExecutableExtension { private static boolean DEBUG = false; // Print debug messages to console private boolean REUSE = true; // true: Reuse contribution items (leave toolbar in disabled state when editor closes) // false: delete items on dispose (remove toolbar editor closes) private static final String PLATFORM = "platform:/plugin/"; private String toolbarId; private IEditorPart activePart; private Set parts = new HashSet(); IToolBarManager mgr; ICommandService service; IHandlerService handlerService; List items = new ArrayList(); MapList actions = new MapList(); CommandStateRegistry stateRegistry; IPartListener partListener; private Map menus = new HashMap(); public ToolbarContributor() { service = (ICommandService) PlatformUI.getWorkbench().getService(ICommandService.class); handlerService = (IHandlerService) PlatformUI.getWorkbench().getService(IHandlerService.class); stateRegistry = CommandStateRegistry.getInstance(); // we need part listener to be notified for other editor activations (i.e editors that do not use this contributor). partListener = new IPartListener() { @Override public void partOpened(IWorkbenchPart part) {} @Override public void partDeactivated(IWorkbenchPart part) {} @Override public void partClosed(IWorkbenchPart part) {} @Override public void partBroughtToTop(IWorkbenchPart part) {} @Override public void partActivated(IWorkbenchPart part) { if (part instanceof IEditorPart) setContext2((IEditorPart)part); } }; PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().addPartListener(partListener); } @Override public void setInitializationData(IConfigurationElement config,String propertyName, Object data) throws CoreException { if (data instanceof String) { String[] parameters = ((String) data).split(";"); for (String parameter : parameters) { String[] keyValue = parameter.split("="); if (keyValue.length > 2) { //ErrorLogger.defaultLogWarning("Invalid parameter '" + parameter + ". Complete view argument: " + data, null); continue; } String key = keyValue[0]; String value = keyValue.length > 1 ? keyValue[1] : ""; if ("toolbar".equals(key)) { toolbarId = value; } else if ("hide".equals(key)) { REUSE = !Boolean.parseBoolean(value); } } } } public String getToolbarId() { return toolbarId; } private ICoolBarManager coolBarManager; private IContributionItem toolBar; public void contributeToCoolBar(ICoolBarManager coolBarManager) { this.coolBarManager = coolBarManager; toolBar = coolBarManager.find(getToolbarId()); if (toolBar instanceof ToolBarContributionItem) mgr = ((ToolBarContributionItem) toolBar).getToolBarManager(); if (mgr == null) return; createCommands(); mgr.markDirty(); } private void createCommands() { for (ToolbarCommandExtension ext : ToolBarCommandRegistry.getInstance().getExtensions(getToolbarId())) { addCommand(ext); } } private void addCommand(ToolbarCommandExtension ext) { if (DEBUG) System.out.println("Adding command to toolbar " +getToolbarId() + " " + ext); String commandId = ext.commandId; Command command = service.getCommand(commandId); ICommandWrapper wrapper = new CommandWrapper(command); ParameterizedCommand parameterizedCommand = null; if (ext.parameters.size() > 0) { try { Parameterization parameterizations[] = new Parameterization[ext.parameters.size()]; for (int i = 0; i < ext.parameters.size(); i++) { Parameter param = ext.parameters.get(i); IParameter parameter = command.getParameter(param.name); parameterizations[i] = new Parameterization(parameter, param.value); } parameterizedCommand = new ParameterizedCommand(command, parameterizations); wrapper = new ParameterizedCommandWrapper(parameterizedCommand); } catch (org.eclipse.core.commands.common.NotDefinedException e) { e.printStackTrace(); ExceptionUtils.logError(e); return; } } String type = ext.type; State toggleState = command.getState(RegistryToggleState.STATE_ID); State radioState = command.getState(RadioState.STATE_ID); String name = ext.name; ImageDescriptor image = getImage(ext); CommandAction a = null; if (type.equals("toggle") && toggleState != null) { a = new CommandCheckboxAction(wrapper,name,image); stateRegistry.storeDefaultState(commandId); } else if (radioState != null && ext.value != null) { stateRegistry.storeDefaultState(commandId); if (type.equals("radio")) { a = new CommandRadioAction(wrapper,name,ext.value,image); } else if (type.equals("combo")) { a = new CommandRadioAction(wrapper,name,ext.value,image); ComboContribution combo = menus.get(commandId); if (REUSE && combo == null) { combo = (ComboContribution)mgr.find(commandId); if (combo != null) { menus.put(commandId, combo); items.add(combo); a.getCommand().addCommandListener(this); CommandStateRegistry.getInstance().addListener(commandId, this); } } if (combo == null) { combo = new ComboContribution(); combo.setId(commandId); menus.put(commandId, combo); items.add(combo); mgr.add(combo); a.getCommand().addCommandListener(this); CommandStateRegistry.getInstance().addListener(commandId, this); } actions.add(commandId,a); combo.addAction(a); return; } } else if (type.equals("push")) { a = new CommandPushAction(wrapper,name,image); } else { if (DEBUG) System.out.println(ext + " is not valid."); Logger.defaultLogError(ext + " is not valid."); return; } IContributionItem item = null; if (REUSE) { String id = commandId; if (ext.value != null) id += "."+ext.value; item = mgr.find(id); if (item == null) { item = new ActionContributionItem(a); ((ActionContributionItem)item).setId(id); } else { if (DEBUG) System.out.println("Reusing " + ext); a = (CommandAction)((ActionContributionItem)item).getAction(); } } else { item = new ActionContributionItem(a); } a.getCommand().addCommandListener(this); actions.add(commandId,a); items.add(item); mgr.add(item); CommandStateRegistry.getInstance().addListener(commandId, this); } private ImageDescriptor getImage(ToolbarCommandExtension ext) { ImageDescriptor image = null; if (ext.image != null) { String plugin = null; String file = null; if (ext.image.startsWith(PLATFORM)) { String s = ext.image.substring(PLATFORM.length()); int i = s.indexOf("/"); plugin = s.substring(0,i); file = s.substring(i+1); } else { plugin = ext.contributorId; file = ext.image; } image = Activator.imageDescriptorFromPlugin(plugin, file); } return image; } @Override public void commandChanged(CommandEvent commandEvent) { if (commandEvent.isHandledChanged()||commandEvent.isEnabledChanged()) { Command command = commandEvent.getCommand(); String commandId = command.getId(); for (CommandAction a : actions.getValues(commandId)) { a.setEnabled(command.isHandled() && command.isEnabled()); } } } @Override public void setActiveEditor(IEditorPart targetEditor) { if (targetEditor == activePart) return; setContext(targetEditor); } @Override public void stateChanged(IWorkbenchPart part, String commandId, String state) { // TODO : update only given command if (settingState) return; if (part instanceof IEditorPart) setContext((IEditorPart)part); } private void setContext2(IEditorPart part) { if (REUSE) return; if (this.activePart == part) return; setContext(null); } private void setContext(IEditorPart part) { this.activePart = part; if (activePart != null && !parts.contains(activePart)) { activePart.getSite().getPage().addPartListener(this); } if (part != null) { for (String commandId : actions.getKeys()) { for (CommandAction a : actions.getValues(commandId)) { a.setEnabled(true); } ComboContribution menu = menus.get(commandId); if (menu != null) { menu.setEnabled(true); } } updateActionBars(part); } else { for (String commandId : actions.getKeys()) { for (CommandAction a : actions.getValues(commandId)) { a.setEnabled(false); } ComboContribution menu = menus.get(commandId); if (menu != null) { menu.setEnabled(false); } } } } private void updateActionBars(IEditorPart part) { restoreActionStates(); part.getEditorSite().getActionBars().updateActionBars(); } @Override public void dispose() { if (DEBUG) System.out.println("ToolBarContributor.dispose()"); setActiveEditor(null); if (mgr != null) { if (!REUSE) { for (IContributionItem item : items) { mgr.remove(item); item.dispose(); } } items.clear(); for (String commandId : actions.getKeys()) { for (CommandAction a : actions.getValues(commandId)) { a.getCommand().removeCommandListener(this); } } actions.clear(); // without this the contributed toolbar widgets would continue reserve the space even when they are destroyed. coolBarManager.update(true); mgr.update(true); } CommandStateRegistry.getInstance().removeListener(this); super.dispose(); activePart = null; if (partListener != null) { PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().addPartListener(partListener); partListener = null; } } boolean settingState = false; private void storeRadioActionState(CommandRadioAction action, boolean checked) { if (activePart == null) return; settingState = true; stateRegistry.setEditorState(activePart, action.getCommandId(), action.getValue()); settingState = false; } private void storeToggleActionState(CommandAction action, boolean checked) { if (activePart == null) return; settingState = true; stateRegistry.setEditorState(activePart, action.getCommandId(), checked); settingState = false; } private void restoreActionStates() { if (activePart == null) return; if (DEBUG)System.out.println("Restore " + activePart); // toggles Map defaultToggleStates = stateRegistry.getDefaultToggleStates(); for (String commandId : defaultToggleStates.keySet()) { for (CommandAction a : actions.getValues(commandId)) { if (DEBUG)System.out.println(commandId + " def " + defaultToggleStates.get(commandId)); a.setChecked(defaultToggleStates.get(commandId)); } } Map editorStates = stateRegistry.getEditorToggleStates(activePart);//toggleStates.get(activePart); if (editorStates != null) { for (String commandId : editorStates.keySet()) { for (CommandAction a : actions.getValues(commandId)) { if (DEBUG)System.out.println(commandId + " " + editorStates.get(commandId)); a.setChecked(editorStates.get(commandId)); } } } // radios Map defaultRadioStates = stateRegistry.getDefaultRadioStates(); for (String commandId : defaultRadioStates.keySet()) { String defaultValue = defaultRadioStates.get(commandId); for (CommandAction a : actions.getValues(commandId)) { CommandRadioAction r = (CommandRadioAction)a; if (DEBUG)System.out.println(commandId + " def " + r.getValue().equals(defaultValue) +" " + r.getValue()); r.setChecked(r.getValue().equals(defaultValue)); } } Map editorRadioStates = stateRegistry.getEditorRadioStates(activePart);//radioStates.get(activePart); if (editorRadioStates != null) { for (String commandId : editorRadioStates.keySet()) { String defaultValue = editorRadioStates.get(commandId); for (CommandAction a : actions.getValues(commandId)) { CommandRadioAction r = (CommandRadioAction)a; if (DEBUG)System.out.println(commandId + " " + r.getValue().equals(defaultValue) +" " + r.getValue()); r.setChecked(r.getValue().equals(defaultValue)); } } } for (ComboContribution c : menus.values()) { c.updateSelection(); } } @Override public void partActivated(IWorkbenchPart part) { } @Override public void partBroughtToTop(IWorkbenchPart part) { } @Override public void partClosed(IWorkbenchPart part) { parts.remove(part); stateRegistry.clearStates(part); part.getSite().getPage().removePartListener(this); if (parts.size() == 0) { } if (part instanceof IEditorPart) ((IEditorPart)part).getEditorSite().getActionBars().updateActionBars(); } @Override public void partDeactivated(IWorkbenchPart part) { } @Override public void partOpened(IWorkbenchPart part) { } private boolean getToggleState(Command command) { State toggleState = command.getState(RegistryToggleState.STATE_ID); return (Boolean)toggleState.getValue(); } private interface ICommandWrapper { public Command getCommand(); public String getCommandId(); public void run(); } private class CommandWrapper implements ICommandWrapper{ private Command command; public CommandWrapper(Command command) { this.command = command; } @Override public Command getCommand() { return command; } @Override public String getCommandId() { return command.getId(); } @Override public void run() { try { handlerService.executeCommand(command.getId(), null); } catch (Exception e) { e.printStackTrace(); } } @Override public boolean equals(Object obj) { if (obj.getClass() != getClass()) return false; CommandWrapper other= (CommandWrapper)obj; return other.getCommandId().equals(getCommandId()); } } private class ParameterizedCommandWrapper implements ICommandWrapper{ private ParameterizedCommand command; public ParameterizedCommandWrapper(ParameterizedCommand command) { this.command = command; } @Override public Command getCommand() { return command.getCommand(); } @Override public String getCommandId() { return command.getId(); } @Override public void run() { try { handlerService.executeCommand(command, null); } catch (Exception e) { e.printStackTrace(); } } @Override public boolean equals(Object obj) { if (obj.getClass() != getClass()) return false; ParameterizedCommandWrapper other= (ParameterizedCommandWrapper)obj; return other.command.equals(command); } } private abstract class CommandAction extends Action { private ICommandWrapper command; public CommandAction(ICommandWrapper command, String name, ImageDescriptor image, int style) { super(name,style); this.command = command; if (image != null) setImageDescriptor(image); } @Override public void run() { command.run(); } public Command getCommand() { return command.getCommand(); } public String getCommandId() { return command.getCommandId(); } @Override public boolean equals(Object obj) { if (obj.getClass() != getClass()) return false; CommandAction other= (CommandAction)obj; return command.equals(other.command); } @Override public int hashCode() { return command.getCommandId().hashCode(); } } private class CommandCheckboxAction extends CommandAction { public CommandCheckboxAction(ICommandWrapper command, String name, ImageDescriptor image) { super(command,name,image,Action.AS_CHECK_BOX); } @Override public void run() { boolean checked = isChecked(); storeToggleActionState(this, checked); try { if (checked == getToggleState(getCommand())) HandlerUtil.toggleCommandState(getCommand()); } catch (ExecutionException e) { e.printStackTrace(); } super.run(); } } private class CommandRadioAction extends CommandAction { private String value; public CommandRadioAction(ICommandWrapper command, String name, String value, ImageDescriptor image) { super(command,name,image,Action.AS_RADIO_BUTTON); this.value = value; } @Override public void run() { boolean checked = isChecked(); storeRadioActionState(this, checked); try { HandlerUtil.updateRadioState(getCommand(), value); } catch (ExecutionException e) { e.printStackTrace(); return; } super.run(); } public String getValue() { return value; } @Override public boolean equals(Object obj) { if (obj.getClass() != getClass()) return false; CommandRadioAction other= (CommandRadioAction)obj; if (!other.getCommandId().equals(getCommandId())) return false; if (!other.value.equals(value)) return false; return true; } } private class CommandPushAction extends CommandAction { public CommandPushAction(ICommandWrapper command, String name, ImageDescriptor image) { super(command,name,image,Action.AS_PUSH_BUTTON); } } private class ComboContribution extends WorkbenchWindowControlContribution { private TableCombo combo; private List actions = new ArrayList(); @Override protected Control createControl(Composite parent) { Composite container = new Composite(parent, SWT.NONE); GridLayout glContainer = new GridLayout(1, false); glContainer.marginTop = 0; glContainer.marginHeight = 0; glContainer.marginWidth = 0; container.setLayout(glContainer); GridData glReader = new GridData(SWT.FILL, SWT.FILL, false, false, 1, 1); combo = new TableCombo(container, SWT.BORDER | SWT.READ_ONLY); combo.setLayoutData(glReader); for (Action a : actions) { TableItem item = new TableItem(combo.getTable(), SWT.NONE); item.setText(a.getText()); if (a.getImageDescriptor() != null) item.setImage(a.getImageDescriptor().createImage()); } combo.addSelectionListener(new SelectionListener() { @Override public void widgetSelected(SelectionEvent e) { int index = combo.getSelectionIndex(); if (index == -1) return; actions.get(index).run(); } @Override public void widgetDefaultSelected(SelectionEvent e) { } }); updateSelection(); return container; } public boolean addAction(Action a) { // old action must be replaced. (otherwise reused ComboContributor would use different instances to ToolBarContributor.) actions.remove(a); actions.add(a); return true; } void updateSelection() { if (combo == null) return; for (int i = 0; i < actions.size(); i++) { if (actions.get(i).isChecked()) { combo.select(i); return; } } } public void setEnabled(boolean enabled) { if (combo != null) combo.setEnabled(enabled); } @Override public void dispose() { combo.dispose(); super.dispose(); } } }