/* * JBoss, Home of Professional Open Source. * * See the LEGAL.txt file distributed with this work for information regarding copyright ownership and licensing. * * See the AUTHORS.txt file distributed with this work for a full listing of individual contributors. */ package org.teiid.designer.ui.properties; import java.util.ArrayList; import java.util.Collections; import java.util.EventObject; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.IStatus; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.edit.provider.INotifyChangedListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.SWTException; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Shell; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.views.properties.PropertySheetEntry; import org.eclipse.ui.views.properties.PropertySheetPage; import org.teiid.core.designer.event.EventObjectListener; import org.teiid.core.designer.event.EventSourceException; import org.teiid.designer.core.notification.util.NotificationUtilities; import org.teiid.designer.core.notification.util.SourcedNotificationUtilities; import org.teiid.designer.core.transaction.SourcedNotification; import org.teiid.designer.extension.ExtensionPlugin; import org.teiid.designer.metamodels.core.Annotation; import org.teiid.designer.metamodels.core.AnnotationContainer; import org.teiid.designer.metamodels.core.ModelAnnotation; import org.teiid.designer.ui.UiConstants; import org.teiid.designer.ui.UiPlugin; import org.teiid.designer.ui.common.eventsupport.SelectionUtilities; import org.teiid.designer.ui.event.ModelResourceEvent; import org.teiid.designer.ui.viewsupport.ModelUtilities; /** * ModelObjectPropertySheetPage is a specialization of PropertySheetPage that refreshes the content * of the PropertySheetPage when a notification occurs on the displayed object. It also adds an * Action to the toolbar that can display the metamodel extension properties of a selected * model object. * * @since 8.0 */ public class ModelObjectPropertySheetPage extends PropertySheetPage implements INotifyChangedListener, EventObjectListener { /** the current ISelection state in the PropertySheetPage */ private ISelection currentSelection; private Control control; private boolean handlingNotification = false; /** * * @since 4.2 */ public ModelObjectPropertySheetPage() { super(); // Wire up for event object listener try { UiPlugin.getDefault().getEventBroker().addListener(ModelResourceEvent.class, this); } catch (EventSourceException e) { UiConstants.Util.log(IStatus.ERROR, e, e.getMessage()); } } /** * Overridden from the base class to keep track of the current selection and enable/disable * the extension properties toggle action. * @see org.eclipse.ui.ISelectionListener#selectionChanged(org.eclipse.ui.IWorkbenchPart, org.eclipse.jface.viewers.ISelection) */ @Override public void selectionChanged(IWorkbenchPart part, ISelection selection) { if ( control != null && ! control.isDisposed() ) { if ( selection != null && ! selection.isEmpty() ) { try { currentSelection = selection; super.selectionChanged(part, selection); } catch (Exception e) { UiConstants.Util.log(IStatus.ERROR, e, e.getClass().getName()); } } else if ( selection != null ) { try { super.selectionChanged(part, selection); } catch (Exception e) { UiConstants.Util.log(IStatus.ERROR, e, e.getClass().getName()); } } } } private boolean isModelExtensionDefinitionRelated( Notification notification ) { Object notifier = notification.getNotifier(); // model extension framework uses annotations if ((notifier instanceof AnnotationContainer) || (notifier instanceof Annotation)) { List<Notification> notifications = new ArrayList<Notification>(); if (notification instanceof SourcedNotification) { notifications.addAll(((SourcedNotification)notification).getNotifications()); } else { notifications.add(notification); } for (Notification event : notifications) { Object modelObject = null; if (event.getEventType() == Notification.ADD) { modelObject = event.getNewValue(); } else if (event.getEventType() == Notification.REMOVE) { modelObject = event.getOldValue(); } if (modelObject!=null && ExtensionPlugin.getInstance().isModelExtensionDefinitionRelated(modelObject)) { return true; } } } return false; } /* (non-Javadoc) * Listens to notifications that change the currently displayed object so that the panel can be refreshed. * @see org.eclipse.emf.edit.provider.INotifyChangedListener#notifyChanged(org.eclipse.emf.common.notify.Notification) */ @Override public void notifyChanged(Notification notification) { // to prevent looping, do not process any notifications that occur during refresh. if ( handlingNotification ) { return; } if ( notification instanceof SourcedNotification ) { Object source = ((SourcedNotification) notification).getSource(); if( source == this || source instanceof ModelObjectPropertySource) { //If this is the source of the notification, don't process - just return return; } } if ( currentSelection != null ) { EObject selectedObject = SelectionUtilities.getSelectedEObject(currentSelection); if (selectedObject != null) { // Get the affectedObjects. And the annotated objects, if any affected objects are annotations. Set affectedObjects = SourcedNotificationUtilities.gatherNotifiers(notification, true); Set annotatedObjects = getAnnotatedObjects(affectedObjects); if (affectedObjects.contains(selectedObject) || annotatedObjects.contains(selectedObject) || isModelExtensionDefinitionRelated(notification)) { handlingNotification = true; try { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { refresh(); } }); } catch (SWTException e) { // swallow - a Widget is disposed exception may occur due to deactivateCellEditor call // but this is not even important enough to log. } finally { handlingNotification = false; } } } else if (!SelectionUtilities.getSelectedIResourceObjects(this.currentSelection).isEmpty()) { // if an IResource is selected and the EObject of the notification(s) is a ModelAnnotation // go ahead and refresh the property page. to determine that the ModelAnnotation is from // the model in the current selection is not worth the effort. just refresh. boolean isModelAnnotation = false; if (notification instanceof SourcedNotification) { for (final Iterator it = ((SourcedNotification) notification).getNotifications().iterator(); it.hasNext();) { if (((Notification) it.next()).getNotifier() instanceof ModelAnnotation) { isModelAnnotation = true; break; } } } else { final EObject target = NotificationUtilities.getEObject(notification); isModelAnnotation = target instanceof ModelAnnotation; } // editable model resource properties are found in the ModelAnnotation if (isModelAnnotation) { this.handlingNotification = true; try { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { refresh(); } }); } catch (SWTException e) { // swallow - a Widget is disposed exception may occur due to deactivateCellEditor call // but this is not even important enough to log. } finally { this.handlingNotification = false; } } } } } /* * For any Annotation objects in the supplied set, get its annotated object and add it to the returned set of annotated objects. * @param objects the supplied set of objects * @return the set of annotated objects */ private Set getAnnotatedObjects( Set objects ) { Set annotatedObjects = new HashSet(); if (objects != null) { for (final Iterator it = objects.iterator(); it.hasNext();) { Object obj = it.next(); if (obj instanceof Annotation) { Object annotatedObject = ((Annotation)obj).getAnnotatedObject(); if (annotatedObject != null) annotatedObjects.add(annotatedObject); } } } return annotatedObjects; } /* (non-Javadoc) * Overridden to hook this page up as a notification listener. * @see org.eclipse.ui.part.IPageBookViewPage#init(org.eclipse.ui.part.IPageSite) */ @Override public void init(IPageSite pageSite) { super.init(pageSite); ModelUtilities.addNotifyChangedListener(this); } /* (non-Javadoc) * Overridden to create the extensions action and add it to the toolbar. * @see org.eclipse.ui.part.IPage#createControl(org.eclipse.swt.widgets.Composite) */ @Override public void createControl(Composite parent) { super.createControl(parent); control = super.getControl(); // if ( control instanceof TableTree ) { // new TableSizeAdapter(((TableTree) control).getTable()); // } setupTooltip(); } private void setupTooltip() { final Tree tree = (Tree)getControl(); final Listener listener = new Listener() { Shell tip = null; Label label = null; /** * {@inheritDoc} * * @see org.eclipse.swt.widgets.Listener#handleEvent(org.eclipse.swt.widgets.Event) */ @Override public void handleEvent( Event event ) { switch (event.type) { case SWT.Dispose: case SWT.KeyDown: case SWT.MouseExit: case SWT.MouseDown: case SWT.MouseMove: if (tip != null) { tip.dispose(); tip = null; label = null; } break; case SWT.MouseHover: if (tip != null) { tip.dispose(); tip = null; label = null; } String tooltip = null; TreeItem item = tree.getItem(new Point(event.x, event.y)); if (item != null) { Object data = item.getData(); if (data instanceof PropertySheetEntry) { tooltip = ((PropertySheetEntry)data).getDescription(); } if (tooltip != null) { Shell shell = tree.getShell(); Display display = tree.getDisplay(); tip = new Shell(shell, SWT.ON_TOP | SWT.NO_FOCUS | SWT.TOOL); tip.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); FillLayout layout = new FillLayout(); layout.marginWidth = 2; tip.setLayout(layout); label = new Label(tip, SWT.NONE); label.setForeground(display.getSystemColor(SWT.COLOR_INFO_FOREGROUND)); label.setBackground(display.getSystemColor(SWT.COLOR_INFO_BACKGROUND)); label.setData("_TABLEITEM", item); //$NON-NLS-1$ label.setText(tooltip); label.addListener(SWT.MouseExit, this); label.addListener(SWT.MouseDown, this); Point size = tip.computeSize(SWT.DEFAULT, SWT.DEFAULT); Rectangle rect = item.getBounds(0); // Display the tooltip on the same line as the property, // but offset to the right of wherever the mouse cursor was, // such that it does not obscure the list of properties. Point pt = tree.toDisplay(event.x + 15, rect.y); tip.setBounds(pt.x, pt.y, size.x, size.y); tip.setVisible(true); } } } } }; tree.addListener(SWT.Dispose, listener); tree.addListener(SWT.KeyDown, listener); tree.addListener(SWT.MouseMove, listener); tree.addListener(SWT.MouseHover, listener); } /* (non-Javadoc) * Overridden to unhook this page as a notification listener. * @see org.eclipse.ui.part.IPage#dispose() */ @Override public void dispose() { ModelUtilities.removeNotifyChangedListener(this); // remove event object listener try { UiPlugin.getDefault().getEventBroker().removeListener(ModelResourceEvent.class, this); } catch (EventSourceException e) { UiConstants.Util.log(IStatus.ERROR, e, e.getMessage()); } super.dispose(); } /** * @see org.teiid.core.designer.event.EventObjectListener#processEvent(java.util.EventObject) * @since 4.2 */ @Override public void processEvent(EventObject obj) { ModelResourceEvent event = (ModelResourceEvent) obj; if ( event.getType() == ModelResourceEvent.RELOADED ) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { selectionChanged(null, new StructuredSelection(Collections.EMPTY_LIST)); } }); } } }