/******************************************************************************* * Copyright (c) 2008 * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed below, as Initial Contributors under such license. * The text of such license is available at * http://www.eclipse.org/legal/epl-v10.html. * * Contributors: * Henrik Lindberg *******************************************************************************/ package org.eclipse.equinox.p2.authoring; import java.util.ArrayList; import java.util.EventObject; import java.util.List; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.operations.AbstractOperation; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.equinox.p2.authoring.forms.IMasterDetailsController; import org.eclipse.equinox.p2.authoring.forms.IPageMementoProvider; import org.eclipse.equinox.p2.authoring.forms.TreeMasterDetailsBlock; import org.eclipse.equinox.p2.authoring.internal.IEditEventBusProvider; import org.eclipse.equinox.p2.authoring.internal.IEditorListener; import org.eclipse.equinox.p2.authoring.internal.IUndoOperationSupport; import org.eclipse.equinox.p2.authoring.internal.InstallableUnitBuilder; import org.eclipse.equinox.p2.authoring.internal.ModelChangeEvent; import org.eclipse.equinox.p2.authoring.internal.ModelPart; import org.eclipse.equinox.p2.authoring.internal.P2AuthoringLabelProvider; import org.eclipse.equinox.p2.authoring.internal.P2StyledLabelProvider; import org.eclipse.equinox.p2.authoring.internal.InstallableUnitBuilder.Parameter; import org.eclipse.equinox.p2.authoring.internal.InstallableUnitBuilder.TouchpointActionBuilder; import org.eclipse.equinox.p2.authoring.internal.InstallableUnitBuilder.TouchpointDataBuilder; import org.eclipse.equinox.p2.authoring.internal.InstallableUnitBuilder.TouchpointInstructionBuilder; import org.eclipse.equinox.p2.authoring.spi.ITouchpointActionDescriptor; import org.eclipse.equinox.p2.authoring.spi.ITouchpointActionParameterDescriptor; import org.eclipse.equinox.p2.authoring.spi.ITouchpointTypeDescriptor; import org.eclipse.jface.dialogs.MessageDialog; import org.eclipse.jface.viewers.DelegatingStyledCellLabelProvider; import org.eclipse.jface.viewers.IBaseLabelProvider; import org.eclipse.jface.viewers.IStructuredContentProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWT; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.dialogs.ListDialog; import org.eclipse.ui.forms.AbstractFormPart; import org.eclipse.ui.forms.IDetailsPage; import org.eclipse.ui.forms.IDetailsPageProvider; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.FormPage; import org.eclipse.ui.forms.editor.IFormPage; import org.eclipse.ui.forms.widgets.FormToolkit; /** * A MasterDetails block for TouchpointData that displays the Touchpoint instructions and actions in a tree and allows * for editing of elements in the tree. * * @author Henrik Lindberg * */ public class TouchpointBodyBlock extends TreeMasterDetailsBlock implements IDetailsPageProvider, IMasterDetailsController, IPageMementoProvider { private static final String ACTION_PAGE = "action"; // $NON-NLS1$ private static final String INSTRUCTION_PAGE = "instruction"; // $NON-NLS1$ private static final String TOUCHPOINT_PAGE = "touchpoint"; // $NON-NLS1$ private IDetailsPage m_actionPage; private IDetailsPage m_touchpointDataPage; private IDetailsPage m_instructionPage; private IBaseLabelProvider m_labelProvider; private ITreeContentProvider m_contentProvider; private MasterFormPart m_masterFormPart; public TouchpointBodyBlock(FormPage page, Object layoutData) { super(page, layoutData); } @Override protected void createMasterPart(final IManagedForm managedForm, Composite parent) { super.createMasterPart(managedForm, parent); // create the view m_masterFormPart = new MasterFormPart(); // create the manager of the view data lifecycle managedForm.addPart(m_masterFormPart); // and make it part of the overall lifecycle } @Override public String getName() { return "Instructions"; } @Override public String getDescription() { return "Edit the actions for touchpoint instructions"; } public void add() { // TODO Unused - uses specific methods in a menu to add things... } public void addAction() { // An instruciton or action must be selected to find the correct place to add // the action IStructuredSelection ssel = (IStructuredSelection)m_viewer.getSelection(); Object element = ssel.getFirstElement(); TouchpointInstructionBuilder tib = null; if(element != null) { if(element instanceof TouchpointInstructionBuilder) tib = (TouchpointInstructionBuilder)element; else if(element instanceof TouchpointActionBuilder) tib = (TouchpointInstructionBuilder)((TouchpointActionBuilder)element).getParent(); } InstallableUnitEditor editor = ((InstallableUnitEditor)m_formPage.getEditor()); final ITouchpointTypeDescriptor desc = editor.getTouchpointType(editor.getInstallableUnit().getTouchpointType()); if(desc.isNull()) { MessageDialog.openWarning(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Select Touchpoint Type first", "This unit has no touchpoint type. Please select the Touchpoint Type."); return; } if(desc.isUnknown()) { MessageDialog.openWarning(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Unknown Touchpoint Type", "This unit has an unknown touchpoint type. Actions can not be added."); return; } if(tib == null) { MessageDialog.openWarning(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Select instruction first", "Please select the instruction where the action should be added and try again."); return; } // Let user select an action ListDialog listDialog = new ListDialog(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell()); listDialog.setTitle("Select Action to Add"); listDialog.setContentProvider(new IStructuredContentProvider(){ public Object[] getElements(Object inputElement) { return desc.getActions(); } public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } }); listDialog.setLabelProvider(new P2StyledLabelProvider()); listDialog.setInput(this); listDialog.open(); Object[] result = listDialog.getResult(); // Add the actions to the instruction if(result == null || result.length < 1) return; ITouchpointActionDescriptor instr = (ITouchpointActionDescriptor)result[0]; String actionKey = instr.getKey(); ITouchpointActionParameterDescriptor[] p = instr.getParameters(); List<Parameter> params = new ArrayList<Parameter>(p.length); for(int i = 0; i < p.length;i++) params.add(new Parameter(p[i].getKey(), p[i].getDefaultValue())); TouchpointActionBuilder action = new TouchpointActionBuilder(actionKey, params); // Execute the undoable add action addRemoveTouchpointInstruction(tib, action, true); } public void addRemoveTouchpointInstruction(TouchpointInstructionBuilder instruction, TouchpointActionBuilder action, boolean add) { FormToolkit toolkit = m_formPage.getManagedForm().getToolkit(); if(toolkit instanceof IUndoOperationSupport) { AddRemoveActionOperation op = new AddRemoveActionOperation(instruction, action, add); op.addContext(((IUndoOperationSupport)toolkit).getUndoContext()); try { ((IUndoOperationSupport)toolkit).getOperationHistory().execute(op, null, null); } catch(ExecutionException e) { // TODO Proper logging e.printStackTrace(); } } else { // without undo support - just add it... (should not happen) if(add) instruction.addAction(action); else instruction.removeAction(action); } } public void addTouchpointData() { ITouchpointTypeDescriptor desc = P2AuthoringUIPlugin.getDefault().getTouchpointType(getIU().getTouchpointType()); // No meaningful to add instruction blocks for unknown (don't know the available phases), and // null (should not have any instructions/actions at all anyway). if(desc.isNull() || desc.isUnknown()) { MessageDialog.openWarning(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(), "Can not add instruction block", "Not possible to add an instruction block for missing or unknown touchpoint types. "+ "Select a different touchpoint type."); return; } TouchpointDataBuilder data = new TouchpointDataBuilder(); // give the new block a default name data.setName("Instruction block " + Integer.toString(getIU().getTouchpointData().length + 1)); addRemoveTouchpointData(data, true); } /** * Configures the add button to have a menu with different add types */ @Override protected void configureAddButton(final Button b) { // Create a listener for menu items so that the correct // type of add operation is performed. // SelectionListener listener = new SelectionListener() { public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } public void widgetSelected(SelectionEvent e) { Object data = e.widget.getData(); if("action".equals(data)) addAction(); if("touchpoint".equals(data)) addTouchpointData(); } }; Menu addMenu = new Menu(b.getShell(), SWT.POP_UP); MenuItem mi = new MenuItem(addMenu, SWT.PUSH); mi.setText("Add Action..."); mi.setData("action"); mi.addSelectionListener(listener); mi = new MenuItem(addMenu, SWT.PUSH); mi.setText("Add Instruction Block"); mi.setData("touchpoint"); mi.addSelectionListener(listener); // attach menu to button (pops up on right mouse click) b.setMenu(addMenu); // attach listener to button so menu pops up on button click (left click) b.addSelectionListener(new SelectionListener() { public void widgetDefaultSelected(SelectionEvent e) { widgetSelected(e); } public void widgetSelected(SelectionEvent e) { b.getMenu().setVisible(true); } }); } public void down() { move(1); } @Override public IDetailsPageProvider getDetailsPageProvider() { return this; } public IDetailsPage getActionPage() { if(m_actionPage == null) m_actionPage = new TouchpointActionPage(); return m_actionPage; } public IDetailsPage getInstructionPage() { if(m_instructionPage == null) m_instructionPage = new TouchpointInstructionPage(); return m_instructionPage; } public IDetailsPage getTouchpointDataPage() { if(m_touchpointDataPage == null) m_touchpointDataPage = new TouchpointDataPage(); return m_touchpointDataPage; } @Override public IStructuredContentProvider getMasterContentProvider() { if(m_contentProvider == null) m_contentProvider = new ITreeContentProvider() { public Object[] getChildren(Object parentElement) { if(parentElement instanceof TouchpointDataBuilder) return ((TouchpointDataBuilder)parentElement).getInstructions().values().toArray(); if(parentElement instanceof TouchpointInstructionBuilder) return ((TouchpointInstructionBuilder)parentElement).getActions(); if(parentElement instanceof InstallableUnitBuilder) return ((InstallableUnitBuilder)parentElement).getTouchpointData(); return null; } public Object getParent(Object element) { if(element instanceof ModelPart) return ((ModelPart)element).getParent(); return null; } public boolean hasChildren(Object element) { return (element instanceof TouchpointDataBuilder || element instanceof TouchpointInstructionBuilder); } public Object[] getElements(Object inputElement) { // get the already parsed and handled editor input instead of using the input element // which refers to the input "file". inputElement = getIU(); if(inputElement instanceof InstallableUnitBuilder) return ((InstallableUnitBuilder)inputElement).getTouchpointData(); return null; } public void dispose() { } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { // nothing is needed } }; return m_contentProvider; } private InstallableUnitBuilder getIU() { return ((InstallableUnitEditor)m_formPage.getEditor()).getInstallableUnit(); } /** * Returns 'this' as a handler of add, remove, up, down,... */ @Override public IMasterDetailsController getMasterDetailsController() { return this; } @Override public IBaseLabelProvider getMasterLabelProvider() { if(m_labelProvider == null) m_labelProvider = new DelegatingStyledCellLabelProvider(new P2AuthoringLabelProvider()); return m_labelProvider; } /** * Returns a page for a page key returned by {@link #getPageKey(Object)}. */ public IDetailsPage getPage(Object key) { if(TOUCHPOINT_PAGE.equals(key)) return getTouchpointDataPage(); if(INSTRUCTION_PAGE.equals(key)) return getInstructionPage(); return getActionPage(); } /** * Selects a page key for the selected object. See {@link #getPage(Object)}. */ public Object getPageKey(Object object) { if(object instanceof TouchpointDataBuilder) return TOUCHPOINT_PAGE; if(object instanceof TouchpointInstructionBuilder) return INSTRUCTION_PAGE; if(object instanceof TouchpointActionBuilder) return ACTION_PAGE; return null; } public void remove() { IStructuredSelection ssel = (IStructuredSelection)m_viewer.getSelection(); if(ssel == null) return; // TODO: support removal of more than one at a time Object selected = ssel.getFirstElement(); if(selected instanceof TouchpointDataBuilder) addRemoveTouchpointData((TouchpointDataBuilder)selected, false); if(selected instanceof TouchpointActionBuilder) { TouchpointActionBuilder action = (TouchpointActionBuilder)selected; TouchpointInstructionBuilder instruction = (TouchpointInstructionBuilder)action.getParent(); addRemoveTouchpointInstruction(instruction, action, false); } } public void up() { move(-1); } /** * Common move operation - moves model part up or down in the list. Operation can be undone. */ private void move(int delta) { IStructuredSelection ssel = (IStructuredSelection)m_viewer.getSelection(); if(ssel == null || ssel.size() != 1) return; // nothing to move (or too many) ModelPart selected = (ModelPart)ssel.getFirstElement(); FormToolkit toolkit = m_formPage.getManagedForm().getToolkit(); if(toolkit instanceof IUndoOperationSupport) { MoveOperation op = new MoveOperation(selected, delta); op.addContext(((IUndoOperationSupport)toolkit).getUndoContext()); try { ((IUndoOperationSupport)toolkit).getOperationHistory().execute(op, null, null); } catch(ExecutionException e) { // TODO Proper logging e.printStackTrace(); } } } /** * Method common to both add and remove TouchpointDataBuilder. Operation can be undone. * * @param data * what to add or remove * @param add * true if required should be added, false to remove */ private void addRemoveTouchpointData(TouchpointDataBuilder data, boolean add) { FormToolkit toolkit = m_formPage.getManagedForm().getToolkit(); if(toolkit instanceof IUndoOperationSupport) { AddRemoveOperation op = new AddRemoveOperation(data, add); op.addContext(((IUndoOperationSupport)toolkit).getUndoContext()); try { ((IUndoOperationSupport)toolkit).getOperationHistory().execute(op, null, null); } catch(ExecutionException e) { // TODO Proper logging e.printStackTrace(); } } else { // without undo support - just add it... (should not happen) InstallableUnitBuilder iu = ((InstallableUnitEditor)m_formPage.getEditor()).getInstallableUnit(); iu.addTouchpointData(data); } } /** * Undoable operation for add/remove of TouchpointData * * @author Henrik Lindberg * */ private class AddRemoveOperation extends AbstractOperation { private TouchpointDataBuilder m_data; private boolean m_add; private int m_index; public AddRemoveOperation(TouchpointDataBuilder data, boolean add) { super((add ? "Add" : "Remove") + " Touchpoint Data"); m_data = data; m_add = add; } private void updatePageState(boolean select) { m_masterFormPart.markStale(); m_masterFormPart.markDirty(); switchFocus(select ? m_data : null); // switch focus if on another page } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { return redo(monitor, info); } @Override public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { InstallableUnitBuilder iu = ((InstallableUnitEditor)m_formPage.getEditor()).getInstallableUnit(); if(m_add) m_index = iu.addTouchpointData(m_data); else m_index = iu.removeTouchpointData(m_data); updatePageState(m_add); if(monitor != null) monitor.done(); return Status.OK_STATUS; } @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { InstallableUnitBuilder iu = ((InstallableUnitEditor)m_formPage.getEditor()).getInstallableUnit(); if(m_add) iu.removeTouchpointData(m_data); else iu.addTouchpointData(m_data, m_index); updatePageState(!m_add); if(monitor != null) monitor.done(); return Status.OK_STATUS; } } /** * Undoable operation class for moving touchpoint data, and touchpoint action nodes. * * @author Henrik Lindberg * */ private class MoveOperation extends AbstractOperation { private ModelPart m_moved; private int m_delta; public MoveOperation(ModelPart moved, int delta) { super(moved instanceof TouchpointDataBuilder ? "Move Instruction Block" : "Move Action"); m_moved = moved; m_delta = delta; } private void updatePageState() { m_masterFormPart.markStale(); m_masterFormPart.markDirty(); switchFocus(m_moved); // switch focus if on another page } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { return redo(monitor, info); } @Override public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { return xxdo(monitor, info, m_delta); } public IStatus xxdo(IProgressMonitor monitor, IAdaptable info, int delta) throws ExecutionException { InstallableUnitBuilder iu = ((InstallableUnitEditor)m_formPage.getEditor()).getInstallableUnit(); if(m_moved instanceof TouchpointDataBuilder) iu.moveTouchpointData((TouchpointDataBuilder)m_moved, delta); else ((TouchpointInstructionBuilder)m_moved.getParent()).moveAction((TouchpointActionBuilder)m_moved, delta); updatePageState(); if(monitor != null) monitor.done(); return Status.OK_STATUS; } @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { return xxdo(monitor, info, -m_delta); } } /** * Undoable operation for add/remove of TouchpointData * * @author Henrik Lindberg * */ private class AddRemoveActionOperation extends AbstractOperation { private TouchpointInstructionBuilder m_instruction; private TouchpointActionBuilder m_action; private boolean m_add; private int m_index; public AddRemoveActionOperation(TouchpointInstructionBuilder instruction, TouchpointActionBuilder action, boolean add) { super((add ? "Add" : "Remove") + " Touchpoint Action"); m_instruction = instruction; m_action = action; m_add = add; } private void updatePageState(boolean select) { m_masterFormPart.markStale(); m_masterFormPart.markDirty(); switchFocus(select ? m_action : null); // switch focus if on another page } @Override public IStatus execute(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { return redo(monitor, info); } @Override public IStatus redo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { if(m_add) m_index = m_instruction.addAction(m_action); else m_index = m_instruction.removeAction(m_action); updatePageState(m_add); if(monitor != null) monitor.done(); return Status.OK_STATUS; } @Override public IStatus undo(IProgressMonitor monitor, IAdaptable info) throws ExecutionException { if(m_add) m_instruction.removeAction(m_action); else m_instruction.addAction(m_action, m_index); updatePageState(!m_add); if(monitor != null) monitor.done(); return Status.OK_STATUS; } } private class MasterFormPart extends AbstractFormPart { @Override public void initialize(IManagedForm form) { super.initialize(form); // register a listener to Required Capability change events if(form.getToolkit() instanceof IEditEventBusProvider) { ((IEditEventBusProvider)form.getToolkit()).getEventBus().addListener(new IEditorListener() { public void notify(EventObject o) { if(!(o instanceof ModelChangeEvent)) return; Object source = ((ModelChangeEvent)o).getDetail(); if(source instanceof TouchpointDataBuilder || source instanceof TouchpointInstructionBuilder || source instanceof TouchpointActionBuilder) TouchpointBodyBlock.this.m_viewer.refresh(source, true); else if(source instanceof Parameter) TouchpointBodyBlock.this.m_viewer.refresh(((ModelPart)source).getParent(), true); } }); } } /** * Refreshes the viewer with stale model changes */ @Override public void refresh() { TouchpointBodyBlock.this.m_viewer.refresh(); super.refresh(); } } /** * Returns a memento that restores this page selection. */ public Object getPageMemento() { return ((IStructuredSelection)m_viewer.getSelection()).getFirstElement(); } /** * Restores this page selection from the memento. */ public void setPageMemento(Object memento) { if(memento != null) m_viewer.setSelection(new StructuredSelection(memento), true); } /** * Switches focus in the editor to the page where this required body block is. */ private void switchFocus(ModelPart select) { FormEditor editor = m_formPage.getEditor(); IFormPage currentPage = editor.getActivePageInstance(); if(!m_formPage.getId().equals(currentPage.getId())) editor.setActivePage(m_formPage.getId()); if(select != null) m_viewer.setSelection(new StructuredSelection(select), true); } /** * Overrides default handling of enablement of up/down/remove buttons since it is not possible to remove or move an * instruction node. */ @Override protected void setStandardButtonEnablement(StandardButtons buttons, IStructuredSelection selection) { if(selection != null && selection.size() > 0) { Object selected = selection.getFirstElement(); if(selected instanceof TouchpointInstructionBuilder) { buttons.up.setEnabled(false); buttons.down.setEnabled(false); buttons.remove.setEnabled(false); return; } } super.setStandardButtonEnablement(buttons, selection); } }