/******************************************************************************* * Copyright (c) 2007 Exadel, Inc. and Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Exadel, Inc. and Red Hat, Inc. - initial API and implementation ******************************************************************************/ package org.jboss.tools.common.model.ui.attribute.adapter; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Properties; import java.util.Set; import org.eclipse.core.runtime.IAdaptable; import org.jboss.tools.common.model.util.XModelTreeListenerSWTASync; import org.jboss.tools.common.model.ui.IStructuredChangeListener; import org.jboss.tools.common.model.ui.StructuredChangedEvent; import org.jboss.tools.common.model.ui.action.sample.XActionWrapper; import org.jboss.tools.common.model.ui.attribute.editor.TableStructuredEditor; import org.jboss.tools.common.model.ui.dnd.DnDUtil; import org.eclipse.jface.action.IAction; import org.eclipse.jface.viewers.ColumnLayoutData; import org.eclipse.jface.viewers.ColumnWeightData; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.ITableLabelProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.TableLayout; import org.eclipse.jface.viewers.Viewer; import org.eclipse.swt.SWTException; import org.eclipse.swt.events.ControlAdapter; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.TableColumn; import org.jboss.tools.common.meta.XAttribute; import org.jboss.tools.common.meta.XModelEntity; import org.jboss.tools.common.meta.action.XActionInvoker; import org.jboss.tools.common.meta.action.XActionItem; import org.jboss.tools.common.meta.action.XActionList; import org.jboss.tools.common.meta.action.XAttributeData; import org.jboss.tools.common.model.XModel; import org.jboss.tools.common.model.XModelObject; import org.jboss.tools.common.model.event.XModelTreeEvent; import org.jboss.tools.common.model.event.XModelTreeListener; import org.jboss.tools.common.model.ui.ModelUIPlugin; import org.jboss.tools.common.model.ui.actions.IActionProvider; public class XChildrenTableStructuredAdapter implements IAdaptable, ITableAdapter, IModelPropertyEditorAdapter, ISelectionChangedListener, ISelectionProvider { public static final String ADD_LABEL = "&Add..."; public static final String REMOVE_LABEL = "&Remove..."; public static final String EDIT_LABEL = "&Edit..."; public static final String UP_LABEL = "&Up"; public static final String DOWN_LABEL = "&Down"; private static final String COPY_XACTION_PATH = "CopyActions.Copy"; //$NON-NLS-1$ private static final String MOVE_XACTION_PATH = "MoveActions.Move"; //$NON-NLS-1$ private XModelTreeListener modelTreeListener; private XModelTreeListenerSWTASync asyncListener; private TableColumn[] tableColumn; private Table table; public TableColumn[] createTableColumn(Table table, int style) { this.table = table; if (tableColumn == null) { if ((shownProperties == null) || (shownProperties.length == 0)) { shownProperties = new String[] {"name"}; // name attribute as default //$NON-NLS-1$ widths = new int[] {100}; } tableColumn = new TableColumn[shownProperties.length]; ColumnLayoutData layoutData; TableLayout layout = new TableLayout(); for (int i = 0; i < tableColumn.length; ++i) { tableColumn[i] = new TableColumn(table, style); //tableColumn[i].setResizable(true); // column labels if ((this.columnLabels!=null)&&(columnLabels.length>i)) { tableColumn[i].setText(columnLabels[i]); } else { tableColumn[i].setText(shownProperties[i]); } // widths layoutData = new ColumnWeightData(widths[i], true); layout.addColumnData(layoutData); //if ((this.widths!=null)&&(widths.length>i)) { // tableColumn[i].setWidth(widths[i]); //} else { // tableColumn[i].setWidth(150); //} } table.setLayout(layout); if(tableColumn.length < 4) { // Cannot guarantee nice work for many columns (glory) table.addControlListener(new Resize(table)); } } return tableColumn; } class Resize extends ControlAdapter { private boolean resizeLock = false; Table table; Resize(Table table) { this.table = table; } public void controlResized(ControlEvent e) { updateColumnWidth(); } private void updateColumnWidth() { if(resizeLock) return; if(table==null || table.getColumnCount() == 0) return; resizeLock = true; int w = table.getClientArea().width - 1; int cw = 0, hs = 0; for (int i = 0; i < table.getColumnCount(); i++) { cw += table.getColumn(i).getWidth(); hs += getWidthHint(i); } for (int i = 0; i < table.getColumnCount(); i++) { TableColumn c = table.getColumn(i); int dw = (w - cw) * getWidthHint(i) / hs; try { c.setWidth(c.getWidth() + dw); } catch (SWTException exc) { //ignore } } resizeLock = false; } protected int getWidthHint(int i) { return (widths == null || widths.length < 2 || widths[i] == 0) ? 10 : widths[i]; } } public Object[] getElements(Object inputElement) { if (value == null) return new Object[0]; return (Object[])value; } public void dispose() { if (model!=null) { model.removeModelTreeListener(asyncListener); } } public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { } public Color getForeground(Object element) { return null; } public Color getBackground(Object element) { return null; } // ***************************** // * IModelPropertyEditorAdapter // ***************************** private XModel model; private XAttribute attribute; private XModelObject xmo; // private XAttributeData attributeData; // private boolean autoStore; private Object value; private long[] timeStamps; public void setModel(XModel model) { this.model = model; } public void setAttribute(XAttribute attribute) { this.attribute = attribute; } public XAttribute getAttribute() { return this.attribute; } public void setModelObject(XModelObject modelObject) { xmo = modelObject; model = modelObject.getModel(); value = applyEntitiesFilter(modelObject.getChildren()); timeStamps = createTimeStamps(); modelTreeListener = new XMTL(); asyncListener = new XModelTreeListenerSWTASync(modelTreeListener); if (model!=null) { model.addModelTreeListener(asyncListener); } } public void setAttributeData(XAttributeData attributeData) { // this.attributeData = attributeData; } private long[] createTimeStamps() { XModelObject[] os = (XModelObject[])value; long[] ts = value == null ? new long[0] : new long[os.length]; for (int i = 0; i < ts.length; i++) ts[i] = os[i].getTimeStamp(); return ts; } public void load() { } public void store() { } public void setAutoStore(boolean autoStore) { // this.autoStore = autoStore; } public void addValueChangeListener(PropertyChangeListener l) { } public void removeValueChangeListener(PropertyChangeListener l) { } public Object getValue() { return this.value; } public void setValue(Object value) { this.value = value; this.value = applyEntitiesFilter((XModelObject[])value); timeStamps = createTimeStamps(); } public String getStringValue(boolean returnNullAsEmptyString) { return (value instanceof String)?(String)value:null; } public void valueChange(PropertyChangeEvent event) { } private String[] shownProperties; private String[] shownEntities; private int[] widths; private String[] columnLabels; private HashMap<String,String> actionMapping = new HashMap<String,String>(); public void setShownProperties(String[] shownProperties) { this.shownProperties = shownProperties; } public void setShownEntities(String[] shownEntities) { this.shownEntities = shownEntities; this.value = applyEntitiesFilter((XModelObject[])value); timeStamps = createTimeStamps(); } private Object[] applyEntitiesFilter(XModelObject[] children) { Object[] result = null; if (this.shownEntities == null) return children; if (xmo!=null) { Set<String> shownEntities = new HashSet<String>(); if (this.shownEntities!=null) { for (int i = 0;i<this.shownEntities.length;++i) shownEntities.add(this.shownEntities[i]); } if (children!=null) { ArrayList<XModelObject> shownChilds = new ArrayList<XModelObject>(); for (int i = 0;i<children.length;++i) if (shownEntities.contains(children[i].getModelEntity().getName())) shownChilds.add(children[i]); result = shownChilds.toArray(new XModelObject[shownChilds.size()]); } } return result; } public void setWidths(int[] is) { widths = is; } public void setColumnLabels(String[] strings) { columnLabels = strings; } // ***************************** // * IAdaptable // ***************************** public Object getAdapter(Class adapter) { if (adapter == IActionProvider.class) { return getActionProvider(); } return this; } // ***************************** // * ISelectionChangedListener // ***************************** // private SelectionChangedEvent selectionChangedEvent; private XModelObject selectedObject; public void selectionChanged(SelectionChangedEvent event) { // this.selectionChangedEvent = event; if (event.getSelection() instanceof StructuredSelection) { StructuredSelection structuredSelection = (StructuredSelection)event.getSelection(); Object object = structuredSelection.getFirstElement(); //if (object instanceof XModelObject) { this.selectedObject = (XModelObject)object; //} //updateButtons(); actionProvider.setXModelObject(selectedObject); } } // ***************************** // * ITableAdapter // ***************************** private ArrayList<IStructuredChangeListener> structureChangeListener = new ArrayList<IStructuredChangeListener>(); public void addStructureChangeListener(IStructuredChangeListener listener) { structureChangeListener.add(listener); } public void removeStructureChangeListener(IStructuredChangeListener listener) { structureChangeListener.remove(listener); } protected void fireStructureChange() { updateValue(); ArrayList<IStructuredChangeListener> copy = new ArrayList<IStructuredChangeListener>(structureChangeListener); Iterator<IStructuredChangeListener> i = copy.iterator(); while(i.hasNext()){ i.next().structureChanged(new StructuredChangedEvent(this)); } copy.clear(); if (actionProvider!=null) { actionProvider.setXModelObject(selectedObject); } } private void updateValue() { this.value = applyEntitiesFilter(xmo.getChildren()); timeStamps = createTimeStamps(); } // ***************************** // * ISelectionProvider // ***************************** private ArrayList<ISelectionChangedListener> selectionChangeListener = new ArrayList<ISelectionChangedListener>(); private ISelection selection; public void addSelectionChangedListener(ISelectionChangedListener listener) { selectionChangeListener.add(listener); } public ISelection getSelection() { selection = new StructuredSelection(new Object[] {this.selectedObject}); return selection; } public void removeSelectionChangedListener(ISelectionChangedListener listener) { selectionChangeListener.remove(listener); } public void setSelection(ISelection selection) { this.selection = selection; } protected void fireSelectionChange() { ArrayList<ISelectionChangedListener> copy = new ArrayList<ISelectionChangedListener>(selectionChangeListener); Iterator<ISelectionChangedListener> i = copy.iterator(); while(i.hasNext()){ i.next().selectionChanged(new SelectionChangedEvent(this, getSelection())); } copy.clear(); } class AddAction extends XActionWrapper { public AddAction(String label) { super(label); } public void run() { super.run(); fireStructureChange(); } protected void initRunningProperties(Properties p) { } } class RemoveAction extends XActionWrapper { public RemoveAction(String label) { super(label); } public void run() { super.run(); fireStructureChange(); } } class EditAction extends XActionWrapper { public EditAction() {} public EditAction(String label) { super(label); } public void setActionPath(String path) { this.path = path; if ("%SelectIt%".equals(path)) { //$NON-NLS-1$ if (xmo != null) { setEnabled(true); } else { setEnabled(false); } return; } if ((xmo == null) || (path == null)) { setEnabled(false); } else { setEnabled(DnDUtil.getEnabledAction(this.xmo, null, path) != null); } } public void setXModelObject(XModelObject xmo) { this.xmo = xmo; if ("%SelectIt%".equals(path)) { //$NON-NLS-1$ if (xmo != null) { setEnabled(true); } else { setEnabled(false); } return; } if ((xmo == null) || (path == null)) { setEnabled(false); } else { setEnabled(xmo.getModelEntity().getActionList().getAction(path)!=null); } } public void run() { if ("%SelectIt%".equals(this.path)) { //$NON-NLS-1$ if (selectionChangedListener!=null) { if(this.xmo == null) return; ISelection selection = new StructuredSelection(this.xmo); SelectionChangedEvent event = new SelectionChangedEvent(new SelectionProvider(selection), selection); selectionChangedListener.selectionChanged(event); } } else { super.run(); } fireStructureChange(); } } class SelectionProvider implements ISelectionProvider { ISelection selection; public SelectionProvider(ISelection selection) { this.selection = selection; } public void addSelectionChangedListener(ISelectionChangedListener listener) { } public ISelection getSelection() { return selection; } public void removeSelectionChangedListener(ISelectionChangedListener listener) { } public void setSelection(ISelection selection) { } } class MoveAction extends XActionWrapper { public MoveAction(String label) { super(label); } public void setActionPath(String path) {} public void setXModelObject(XModelObject xmo) { XModelObject[] list = (XModelObject[])XChildrenTableStructuredAdapter.this.value; this.xmo = xmo; if (xmo!=null) { int index = getIndex(list, xmo); int targetIngex = getTargetIndex(index); boolean actionEnabled = ((xmo.getModelEntity().getActionList().getAction(COPY_XACTION_PATH)!=null)&&(xmo.getModelEntity().getActionList().getAction(MOVE_XACTION_PATH)!=null)); setEnabled(actionEnabled && (targetIngex >= 0 && targetIngex < list.length) && xmo.isObjectEditable()); } else { setEnabled(false); } } private int getIndex(XModelObject[] list, XModelObject child) { for (int i = 0; i < list.length; ++i) { if (child.equals(list[i])) return i; } return -1; } public void run() { XModelObject[] list = (XModelObject[])XChildrenTableStructuredAdapter.this.value; int index = getIndex(list, this.xmo); int targetIndex = getTargetIndex(index); if(index == targetIndex || targetIndex < 0 || targetIndex >= list.length) return; XActionInvoker.invoke(COPY_XACTION_PATH, this.xmo, null, new Properties()); XModelObject prev = list[targetIndex]; XActionInvoker.invoke(MOVE_XACTION_PATH, prev, null, new Properties()); if(table != null && !table.isDisposed()) table.setSelection(targetIndex); fireStructureChange(); } protected int getTargetIndex(int index) { return index; } } class MoveUpAction extends MoveAction { public MoveUpAction() { super(UP_LABEL); } protected int getTargetIndex(int index) { return index - 1; } } class MoveDownAction extends MoveAction { public MoveDownAction() { super(DOWN_LABEL); } protected int getTargetIndex(int index) { return index + 1; } } class ActionProvider implements IActionProvider { private ArrayList<IAction> actions = new ArrayList<IAction>(); private HashMap<String,IAction> hash = new HashMap<String,IAction>(); private boolean showUpDown = true; XActionWrapper addAction = new AddAction(ADD_LABEL); XActionWrapper removeAction = new RemoveAction(REMOVE_LABEL); XActionWrapper editAction = new EditAction(EDIT_LABEL); XActionWrapper upAction = new MoveUpAction(); XActionWrapper downAction = new MoveDownAction(); private ActionProvider() {} public ActionProvider(boolean makeDefaultActions, boolean showUpDown) { this.showUpDown = showUpDown; if (makeDefaultActions) { actions.add(addAction); actions.add(removeAction); actions.add(editAction); if (this.showUpDown) { actions.add(upAction); actions.add(downAction); } hash.put(TableStructuredEditor.ADD_ACTION, addAction); hash.put(TableStructuredEditor.REMOVE_ACTION, removeAction); hash.put(TableStructuredEditor.EDIT_ACTION, editAction); if (this.showUpDown) { hash.put(TableStructuredEditor.UP_ACTION, upAction); hash.put(TableStructuredEditor.DOWN_ACTION, downAction); } hash.put(TableStructuredEditor.DOUBLE_CLICK__ACTION, editAction); } } public void update(ISelection selection) { removeAction.updateState(); editAction.updateState(); } public void setXModelObject(XModelObject xmo) { // add action addAction.setXModelObject(XChildrenTableStructuredAdapter.this.xmo); addAction.setActionPath((String)actionMapping.get(TableStructuredEditor.ADD_ACTION)); removeAction.setXModelObject(xmo); removeAction.setActionPath((String)actionMapping.get(TableStructuredEditor.REMOVE_ACTION)); editAction.setXModelObject(xmo); editAction.setActionPath((String)actionMapping.get(TableStructuredEditor.EDIT_ACTION)); if (this.showUpDown) { upAction.setXModelObject(xmo); upAction.setActionPath((String)actionMapping.get(TableStructuredEditor.UP_ACTION)); downAction.setXModelObject(xmo); downAction.setActionPath((String)actionMapping.get(TableStructuredEditor.DOWN_ACTION)); } } public IAction getAction(String actionName) { return (IAction)hash.get(actionName); } public IAction[] getActions() { return (IAction[])actions.toArray(new IAction[actions.size()]); } } // ***************************** // * IActionProvider // ***************************** private boolean makeDefaultActions = true; private ActionProvider actionProvider; private boolean isCanBeMoveChildren() { if (shownEntities != null && shownEntities.length > 0 && xmo != null) { XModelEntity entity = xmo.getModel().getMetaData().getEntity(shownEntities[0]); Object copyXAction = entity.getActionList().getAction(COPY_XACTION_PATH); Object moveXAction = entity.getActionList().getAction(MOVE_XACTION_PATH); if (copyXAction != null && moveXAction != null) { return true; } } return false; } private void initializeActionProvider() { actionProvider = new ActionProvider(this.makeDefaultActions, this.isCanBeMoveChildren()); actionProvider.setXModelObject(this.selectedObject); } public IActionProvider getActionProvider() { if (this.actionProvider==null) initializeActionProvider(); return actionProvider; } public HashMap<String,String> getActionMapping() { return actionMapping; } // ISelectionChangedListener private ISelectionChangedListener selectionChangedListener; public ISelectionChangedListener getSelectionChangedListener() { return selectionChangedListener; } public void setSelectionChangedListener(ISelectionChangedListener listener) { selectionChangedListener = listener; } private boolean equals(long[] array1, long[] array2) { if (array1 == null && array2 == null) return true; if (array1 != null && array2 != null) { if (array1.length != array2.length) return false; for (int i=0;i<array1.length;++i) { if(array1[i] != array2[i]) return false; } return true; } else { return false; } } private void update() { Object[] newValue = applyEntitiesFilter(xmo.getChildren()); long[] ts = createTimeStamps(); if (!equals(timeStamps, ts) || !areArraysEqual((Object[])value, newValue)) { value = newValue; timeStamps = ts; fireStructureChange(); } } private boolean areArraysEqual(Object[] os1, Object[] os2) { if(os1 == null && os2 == null) return true; if(os1 != null && os2 != null && os1.length == os2.length) { for (int i = 0; i < os1.length; i++) { if(os1[i] != os2[i]) return false; } return true; } return false; } // XModelTreeListener class XMTL implements XModelTreeListener { public void nodeChanged(XModelTreeEvent event){ String opath = (String)event.getInfo(); String npath = event.getModelObject().getPath(); if(!opath.equals(npath)) { // update } if (xmo == null) return; String rootPath = xmo.getPath(); String eventPath = event.getModelObject().getPath(); if ((rootPath == null) || (eventPath == null) || (!eventPath.startsWith(rootPath))) return; update(); } public void structureChanged(XModelTreeEvent event) { if (xmo == null) return; String rootPath = xmo.getPath(); String eventPath = event.getModelObject().getPath(); if ((rootPath==null)||(eventPath==null)||(!eventPath.startsWith(rootPath))) return; if(event.kind()==XModelTreeEvent.STRUCTURE_CHANGED) { update(); } else if(event.kind()==XModelTreeEvent.CHILD_ADDED) { update(); XModelObject addedObject = (XModelObject)event.getInfo(); if (addedObject!=null) selectedObject = addedObject; fireSelectionChange(); } else if(event.kind()==XModelTreeEvent.CHILD_REMOVED) { update(); } } } private ITableLabelProvider tableLabelProvider = new DefaultTableLabelProvider(); public ITableLabelProvider getTableLabelProvider() { return tableLabelProvider; } public void setTableLabelProvider(ITableLabelProvider tableLabelProvider) { this.tableLabelProvider = tableLabelProvider; } class DefaultTableLabelProvider implements ITableLabelProvider { /* (non-Javadoc) * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnImage(java.lang.Object, int) */ public Image getColumnImage(Object element, int columnIndex) { return null; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.ITableLabelProvider#getColumnText(java.lang.Object, int) */ public String getColumnText(Object element, int columnIndex) { XModelObject xmo = ((XModelObject)element); String result = xmo.getAttributeValue(shownProperties[columnIndex]); return (result!=null)?result:MessageFormat.format("no attribute called {0} on entity {1}", shownProperties[columnIndex], xmo.getModelEntity().getName()); } /* (non-Javadoc) * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener) */ public void addListener(ILabelProviderListener listener) { } /* (non-Javadoc) * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose() */ public void dispose() { } /* (non-Javadoc) * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, java.lang.String) */ public boolean isLabelProperty(Object element, String property) { return true; } /* (non-Javadoc) * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener) */ public void removeListener(ILabelProviderListener listener) { } } public boolean hasErrors() { return false; } public boolean isMakeDefaultActions() { return makeDefaultActions; } public void setMakeDefaultActions(boolean makeDefaultActions) { this.makeDefaultActions = makeDefaultActions; } public String getError() { return null; } public void addErrorStateListener(PropertyChangeListener l) { // TODO Auto-generated method stub } }