/****************************************************************************** * Copyright (c) 2016 Oracle and OnPositive * All rights reserved. This program and the accompanying materials * are 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: * Konstantin Komissarchik - initial implementation and ongoing maintenance * Ling Hao - [329114] rewrite context help binding feature * Dmitry Karpenko - [455493] expand outline nodes with a double-click ******************************************************************************/ package org.eclipse.sapphire.ui.forms.swt; import static org.eclipse.sapphire.modeling.util.MiscUtil.escapeForXml; import static org.eclipse.sapphire.ui.SapphireActionSystem.ACTION_OUTLINE_HIDE; import static org.eclipse.sapphire.ui.SapphireActionSystem.CONTEXT_EDITOR_PAGE; import static org.eclipse.sapphire.ui.SapphireActionSystem.CONTEXT_EDITOR_PAGE_OUTLINE; import static org.eclipse.sapphire.ui.SapphireActionSystem.CONTEXT_EDITOR_PAGE_OUTLINE_HEADER; import static org.eclipse.sapphire.ui.SapphireActionSystem.CONTEXT_EDITOR_PAGE_OUTLINE_NODE; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gd; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdfill; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdhfill; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdhhint; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.gdwhint; import static org.eclipse.sapphire.ui.forms.swt.GridLayoutUtil.glayout; import static org.eclipse.sapphire.ui.forms.swt.SwtUtil.asyncExec; import static org.eclipse.sapphire.ui.forms.swt.SwtUtil.syncExec; import static org.eclipse.sapphire.ui.util.MiscUtil.findSelectionPostDelete; import static org.eclipse.sapphire.util.CollectionsUtil.findPrecedingItem; import static org.eclipse.sapphire.util.CollectionsUtil.findTrailingItem; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IToolBarManager; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.ITreeContentProvider; import org.eclipse.jface.viewers.ITreeViewerListener; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.jface.viewers.StyledCellLabelProvider; import org.eclipse.jface.viewers.StyledString; import org.eclipse.jface.viewers.StyledString.Styler; import org.eclipse.jface.viewers.TreeExpansionEvent; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.jface.viewers.Viewer; import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.sapphire.Disposable; import org.eclipse.sapphire.Element; import org.eclipse.sapphire.ElementData; import org.eclipse.sapphire.ElementList; import org.eclipse.sapphire.ElementType; import org.eclipse.sapphire.EventDeliveryJob; import org.eclipse.sapphire.Filter; import org.eclipse.sapphire.FilteredListener; import org.eclipse.sapphire.ImageData; import org.eclipse.sapphire.ListProperty; import org.eclipse.sapphire.Listener; import org.eclipse.sapphire.LocalizableText; import org.eclipse.sapphire.LoggingService; import org.eclipse.sapphire.PossibleTypesService; import org.eclipse.sapphire.Property; import org.eclipse.sapphire.PropertyDef; import org.eclipse.sapphire.Sapphire; import org.eclipse.sapphire.Text; import org.eclipse.sapphire.modeling.CapitalizationType; import org.eclipse.sapphire.modeling.EditFailedException; import org.eclipse.sapphire.modeling.localization.LabelTransformer; import org.eclipse.sapphire.ui.ISapphireEditorActionContributor; import org.eclipse.sapphire.ui.PartVisibilityEvent; import org.eclipse.sapphire.ui.Presentation; import org.eclipse.sapphire.ui.SapphireAction; import org.eclipse.sapphire.ui.SapphireActionGroup; import org.eclipse.sapphire.ui.SapphireActionHandler; import org.eclipse.sapphire.ui.SapphireEditor; import org.eclipse.sapphire.ui.SapphireEditorPagePart; import org.eclipse.sapphire.ui.SapphirePart; import org.eclipse.sapphire.ui.SapphirePart.ImageChangedEvent; import org.eclipse.sapphire.ui.SapphirePart.LabelChangedEvent; import org.eclipse.sapphire.ui.SapphirePart.PartEvent; import org.eclipse.sapphire.ui.def.DefinitionLoader; import org.eclipse.sapphire.ui.def.EditorPageDef; import org.eclipse.sapphire.ui.def.ISapphireDocumentation; import org.eclipse.sapphire.ui.def.ISapphireDocumentationDef; import org.eclipse.sapphire.ui.def.ISapphireDocumentationRef; import org.eclipse.sapphire.ui.forms.MasterDetailsContentNodeList; import org.eclipse.sapphire.ui.forms.MasterDetailsContentNodePart; import org.eclipse.sapphire.ui.forms.MasterDetailsContentNodePart.DecorationEvent; import org.eclipse.sapphire.ui.forms.MasterDetailsContentNodePart.NodeListEvent; import org.eclipse.sapphire.ui.forms.MasterDetailsContentOutline; import org.eclipse.sapphire.ui.forms.MasterDetailsEditorPageDef; import org.eclipse.sapphire.ui.forms.MasterDetailsEditorPagePart; import org.eclipse.sapphire.ui.forms.MasterDetailsEditorPagePart.OutlineHeaderTextEvent; import org.eclipse.sapphire.ui.forms.SectionPart; import org.eclipse.sapphire.ui.forms.TextDecoration; import org.eclipse.sapphire.ui.forms.swt.internal.ElementsTransfer; import org.eclipse.sapphire.ui.forms.swt.internal.SapphireToolTip; import org.eclipse.sapphire.ui.forms.swt.internal.SectionPresentation; import org.eclipse.sapphire.ui.forms.swt.internal.text.SapphireFormText; import org.eclipse.sapphire.util.ListFactory; import org.eclipse.sapphire.util.MutableReference; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.dnd.DND; import org.eclipse.swt.dnd.DragSource; import org.eclipse.swt.dnd.DragSourceEvent; import org.eclipse.swt.dnd.DragSourceListener; import org.eclipse.swt.dnd.DropTarget; import org.eclipse.swt.dnd.DropTargetAdapter; import org.eclipse.swt.dnd.DropTargetEvent; import org.eclipse.swt.dnd.Transfer; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.TextStyle; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Combo; 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.Link; import org.eclipse.swt.widgets.Menu; import org.eclipse.swt.widgets.MenuItem; import org.eclipse.swt.widgets.Table; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.Tree; import org.eclipse.swt.widgets.TreeItem; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.dialogs.FilteredTree; import org.eclipse.ui.dialogs.PatternFilter; import org.eclipse.ui.forms.DetailsPart; import org.eclipse.ui.forms.FormColors; import org.eclipse.ui.forms.IDetailsPage; import org.eclipse.ui.forms.IFormColors; import org.eclipse.ui.forms.IFormPart; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.MasterDetailsBlock; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.part.IPageSite; import org.eclipse.ui.part.Page; import org.eclipse.ui.progress.WorkbenchJob; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> * @author <a href="mailto:ling.hao@oracle.com">Ling Hao</a> * @author <a href="mailto:dmitry.karpenko@onpositive.com">Dmitry Karpenko</a> */ public final class MasterDetailsEditorPage extends SapphireEditorFormPage implements ISapphireEditorActionContributor { @Text( "Additional {0} problems not shown..." ) private static LocalizableText problemsOverflowMessage; @Text( "two" ) private static LocalizableText two; @Text( "three" ) private static LocalizableText three; @Text( "four" ) private static LocalizableText four; @Text( "five" ) private static LocalizableText five; @Text( "six" ) private static LocalizableText six; @Text( "seven" ) private static LocalizableText seven; @Text( "eight" ) private static LocalizableText eight; @Text( "nine" ) private static LocalizableText nine; static { LocalizableText.init( MasterDetailsEditorPage.class ); } private SwtPresentation presentation; private RootSection mainSection; private ContentOutline contentOutlinePage; private IPartListener2 partListener; public MasterDetailsEditorPage( final SapphireEditor editor, final Element element, final DefinitionLoader.Reference<EditorPageDef> definition ) { this( editor, element, definition, null ); } public MasterDetailsEditorPage( final SapphireEditor editor, final Element element, final DefinitionLoader.Reference<EditorPageDef> definition, final String pageName ) { super( editor, element, definition ); final MasterDetailsEditorPagePart part = getPart(); this.presentation = new SwtPresentation( part, null, editor.getSite().getShell() ) { @Override public void render() { throw new UnsupportedOperationException(); } }; String partName = pageName; if( partName == null ) { partName = part.definition().getPageName().localized( CapitalizationType.TITLE_STYLE, false ); } setPartName( partName ); // Content Outline final SapphireAction outlineHideAction = getPart().getActions( CONTEXT_EDITOR_PAGE ).getAction( ACTION_OUTLINE_HIDE ); final SapphireActionHandler outlineHideActionHandler = new SapphireActionHandler() { @Override protected Object run( final Presentation context ) { setDetailsMaximized( ! isDetailsMaximized() ); return null; } }; outlineHideActionHandler.init( outlineHideAction, null ); outlineHideActionHandler.setChecked( isDetailsMaximized() ); outlineHideAction.addHandler( outlineHideActionHandler ); } @Override public MasterDetailsEditorPagePart getPart() { return (MasterDetailsEditorPagePart) super.getPart(); } public MasterDetailsEditorPageDef getDefinition() { return getPart().definition(); } @Override public String getId() { return getPartName(); } public MasterDetailsContentOutline outline() { return getPart().outline(); } public IDetailsPage getCurrentDetailsPage() { return this.mainSection.getCurrentDetailsSection(); } @Override protected void createFormContent( final IManagedForm managedForm ) { final SapphireEditorPagePart part = getPart(); final ScrolledForm form = managedForm.getForm(); try { FormToolkit toolkit = managedForm.getToolkit(); toolkit.decorateFormHeading(managedForm.getForm().getForm()); this.mainSection = new RootSection(); this.mainSection.createContent( managedForm ); final ISapphireDocumentation doc = getDefinition().getDocumentation().content(); if( doc != null ) { ISapphireDocumentationDef docdef = null; if( doc instanceof ISapphireDocumentationDef ) { docdef = (ISapphireDocumentationDef) doc; } else { docdef = ( (ISapphireDocumentationRef) doc ).resolve(); } if( docdef != null ) { HelpSystem.setHelp( managedForm.getForm().getBody(), docdef ); } } final SapphireActionGroup actions = part.getActions( CONTEXT_EDITOR_PAGE ); final SapphireActionPresentationManager actionPresentationManager = new SapphireActionPresentationManager( this.presentation, actions ); final SapphireToolBarManagerActionPresentation toolbarActionPresentation = new SapphireToolBarManagerActionPresentation( actionPresentationManager ); final IToolBarManager toolbarManager = form.getToolBarManager(); toolbarActionPresentation.setToolBarManager( toolbarManager ); toolbarActionPresentation.render(); final SapphireKeyboardActionPresentation keyboardActionPresentation = new SapphireKeyboardActionPresentation( actionPresentationManager ); keyboardActionPresentation.attach( toolbarActionPresentation.getToolBar() ); keyboardActionPresentation.render(); part.attach ( new FilteredListener<MasterDetailsEditorPagePart.DetailsFocusRequested>() { @Override protected void handleTypedEvent( final MasterDetailsEditorPagePart.DetailsFocusRequested event ) { setFocusOnDetails(); } } ); this.partListener = new IPartListener2() { public void partActivated( final IWorkbenchPartReference partRef ) { } public void partBroughtToTop( final IWorkbenchPartReference partRef ) { } public void partClosed( final IWorkbenchPartReference partRef ) { if( ! isDetailsMaximized() ) { getPart().state().getContentOutlineState().setRatio( MasterDetailsEditorPage.this.mainSection.getOutlineRatio() ); } } public void partDeactivated( final IWorkbenchPartReference partRef ) { } public void partOpened( final IWorkbenchPartReference partRef ) { } public void partHidden( final IWorkbenchPartReference partRef ) { } public void partVisible( final IWorkbenchPartReference partRef ) { } public void partInputChanged( final IWorkbenchPartReference partRef ) { } }; getSite().getPage().addPartListener( this.partListener ); } catch( final Exception e ) { if( this.mainSection != null ) { this.mainSection.dispose(); this.mainSection = null; final Composite body = (Composite) ( (Form) form.getChildren()[ 0 ] ).getChildren()[ 1 ]; for( Control control : body.getChildren() ) { control.dispose(); } final Color bgcolor = body.getDisplay().getSystemColor( SWT.COLOR_WHITE ); final Composite composite = new Composite( body, SWT.NONE ); composite.setLayoutData( gdfill() ); composite.setLayout( glayout( 1, 5, 5, 10, 5 ) ); composite.setBackground( bgcolor ); final Composite msgAndShowStackTraceLinkComposite = new Composite( composite, SWT.NONE ); msgAndShowStackTraceLinkComposite.setLayoutData( gdhfill() ); msgAndShowStackTraceLinkComposite.setLayout( glayout( 2, 0, 0 ) ); msgAndShowStackTraceLinkComposite.setBackground( bgcolor ); String message = e.getMessage(); if( message == null ) { message = e.getClass().getName(); } else { message = message.replace( "&", "&" ); message = message.replace( "<", "<" ); } final SapphireFormText text = new SapphireFormText( msgAndShowStackTraceLinkComposite, SWT.NONE ); text.setLayoutData( gdhfill() ); text.setText( "<form><li style=\"image\" value=\"error\">" + message + "</li></form>", true, false ); text.setImage( "error", ImageData.readFromClassLoader( SwtResourceCache.class, "Error.png" ).required() ); text.setBackground( bgcolor ); final SapphireFormText showStackTraceLink = new SapphireFormText( msgAndShowStackTraceLinkComposite, SWT.NONE ); showStackTraceLink.setLayoutData( gd() ); showStackTraceLink.setText( "<form><p><a href=\"show-stack\">Show stack trace...</a></p></form>", true, false ); showStackTraceLink.setBackground( bgcolor ); showStackTraceLink.addHyperlinkListener ( new HyperlinkAdapter() { @Override public void linkActivated( final HyperlinkEvent event ) { showStackTraceLink.setVisible( false ); final Label separator = new Label( composite, SWT.SEPARATOR | SWT.HORIZONTAL ); separator.setLayoutData( gdhfill() ); separator.setBackground( bgcolor ); final org.eclipse.swt.widgets.Text stack = new org.eclipse.swt.widgets.Text( composite, SWT.MULTI | SWT.READ_ONLY | SWT.BORDER | SWT.V_SCROLL ); stack.setLayoutData( gdfill() ); stack.setBackground( bgcolor ); final StringWriter w = new StringWriter(); e.printStackTrace( new PrintWriter( w ) ); stack.setText( w.getBuffer().toString() ); body.layout( true, true ); } } ); } } } public IContentOutlinePage getContentOutlinePage() { if( this.contentOutlinePage == null ) { this.contentOutlinePage = new ContentOutline(); } return this.contentOutlinePage; } public boolean isDetailsMaximized() { return ! getPart().state().getContentOutlineState().getVisible().content(); } public void setDetailsMaximized( final boolean maximized ) { this.mainSection.setDetailsMaximized( maximized ); getPart().state().getContentOutlineState().setVisible( ! maximized ); } public double getOutlineRatio() { double contentOutlineRatio = getPart().state().getContentOutlineState().getRatio().content(); if( contentOutlineRatio < 0 || contentOutlineRatio > 1 ) { contentOutlineRatio = 0.3d; } return contentOutlineRatio; } public void setOutlineRatio( final Double ratio ) { if( ratio < 0 || ratio > 1 ) { throw new IllegalArgumentException(); } this.mainSection.setOutlineRatio( ratio ); getPart().state().getContentOutlineState().setRatio( ratio ); } @Override public void setActive( final boolean active ) { if( this.mainSection != null ) { super.setActive( active ); } } @Override public boolean isDirty() { return false; } @Override public void setFocus() { if( isDetailsMaximized() ) { setFocusOnDetails(); } else { setFocusOnContentOutline(); } } public void setFocusOnContentOutline() { if( isDetailsMaximized() ) { setDetailsMaximized( false ); } if( this.mainSection != null ) { this.mainSection.masterSection.tree.setFocus(); } } public void setFocusOnDetails() { final Control control = findFirstFocusableControl( this.mainSection.detailsSectionControl ); if( control != null ) { control.setFocus(); } } private Control findFirstFocusableControl( final Control control ) { if( control instanceof Combo || control instanceof Link || control instanceof List<?> || control instanceof Table || control instanceof Tree ) { return control; } else if( control instanceof org.eclipse.swt.widgets.Text ) { if( ( ( (org.eclipse.swt.widgets.Text) control ).getStyle() & SWT.READ_ONLY ) == 0 ) { return control; } } else if( control instanceof Button ) { final Button button = (Button) control; final int style = button.getStyle(); if( ( style & SWT.CHECK ) != 0 || ( ( style & SWT.RADIO ) != 0 && button.getSelection() == true ) ) { return control; } } else if( control instanceof Composite ) { for( Control child : ( (Composite) control ).getChildren() ) { final Control res = findFirstFocusableControl( child ); if( res != null ) { return res; } } } return null; } private FilteredTree createContentOutline( final Composite parent, final MasterDetailsContentOutline outline, final boolean addBorders ) { final int treeStyle = ( addBorders ? SWT.BORDER : SWT.NONE ) | SWT.MULTI; final ContentOutlineFilteredTree filteredTree = new ContentOutlineFilteredTree( parent, treeStyle, outline ); final TreeViewer treeViewer = filteredTree.getViewer(); final Tree tree = treeViewer.getTree(); final ITreeContentProvider contentProvider = new ITreeContentProvider() { private final Listener listener = new Listener() { @Override public void handle( final org.eclipse.sapphire.Event event ) { if( event instanceof PartEvent ) { final SapphirePart part = ( (PartEvent) event ).part(); if( part instanceof MasterDetailsContentNodePart ) { final MasterDetailsContentNodePart node = (MasterDetailsContentNodePart) part; if( event instanceof PartVisibilityEvent ) { final MasterDetailsContentNodePart parent = node.getParentNode(); syncExec( new TreeViewerRefreshOp( treeViewer, ( parent == outline.getRoot() ? null : parent ) ) ); } else { if( node.visible() ) { if( event instanceof LabelChangedEvent || event instanceof ImageChangedEvent || event instanceof DecorationEvent ) { asyncExec( new TreeViewerUpdateOp( treeViewer, node ) ); } else if( event instanceof NodeListEvent ) { syncExec( new TreeViewerRefreshOp( treeViewer, node ) ); } } } } } } }; private void attach( final List<MasterDetailsContentNodePart> nodes ) { for( MasterDetailsContentNodePart node : nodes ) { node.attach( this.listener ); } } private void detach( final List<MasterDetailsContentNodePart> nodes ) { for( MasterDetailsContentNodePart node : nodes ) { node.detach( this.listener ); detach( node.nodes() ); } } public Object[] getElements( final Object inputElement ) { final MasterDetailsContentNodeList nodes = outline.getRoot().nodes(); attach( nodes ); return nodes.visible().toArray(); } public Object[] getChildren( final Object parentElement ) { final MasterDetailsContentNodeList nodes = ( (MasterDetailsContentNodePart) parentElement ).nodes(); attach( nodes ); return nodes.visible().toArray(); } public Object getParent( final Object element ) { return ( (MasterDetailsContentNodePart) element ).getParentNode(); } public boolean hasChildren( final Object parentElement ) { final MasterDetailsContentNodeList nodes = ( (MasterDetailsContentNodePart) parentElement ).nodes(); attach( nodes ); return ! nodes.visible().isEmpty(); } public void inputChanged( final Viewer viewer, final Object oldInput, final Object newInput ) { } public void dispose() { detach( outline.getRoot().nodes() ); } }; final StyledCellLabelProvider labelProvider = new StyledCellLabelProvider () { private final Map<ImageDescriptor,Image> images = new HashMap<ImageDescriptor,Image>(); private final Map<org.eclipse.sapphire.Color,Color> colors = new HashMap<org.eclipse.sapphire.Color,Color>(); public void update( final ViewerCell cell ) { final MasterDetailsContentNodePart node = (MasterDetailsContentNodePart) cell.getElement(); final StyledString styledString = new StyledString( node.getLabel() ); for( final TextDecoration decoration : node.decorations() ) { String text = decoration.text(); if( text != null ) { text = text.trim(); if( text.length() > 0 ) { final Color color = color( decoration.color() ); styledString.append ( " " + text, new Styler() { @Override public void applyStyles( final TextStyle style ) { style.foreground = color; } } ); } } } cell.setText( styledString.toString() ); cell.setStyleRanges( styledString.getStyleRanges() ); cell.setImage( image( node.getImage() ) ); super.update( cell ); } private Color color( final org.eclipse.sapphire.Color c ) { Color color = this.colors.get( c ); if( color == null ) { color = new Color( tree.getDisplay(), c.red(), c.green(), c.blue() ); this.colors.put( c, color ); } return color; } private Image image( final ImageDescriptor imageDescriptor ) { Image image = this.images.get( imageDescriptor ); if( image == null ) { image = imageDescriptor.createImage(); this.images.put( imageDescriptor, image ); } return image; } @Override public void dispose() { for( final Image image : this.images.values() ) { image.dispose(); } } }; new SapphireToolTip( tree ) { protected Object getToolTipArea( final Event event ) { return treeViewer.getCell( new Point( event.x, event.y ) ); } protected boolean shouldCreateToolTip(Event event) { if( ! super.shouldCreateToolTip( event ) ) { return false; } setShift( new Point( 0, 20 ) ); tree.setToolTipText( "" ); boolean res = false; final MasterDetailsContentNodePart node = getNode( event ); if( node != null ) { res = ! node.validation().ok(); } return res; } private MasterDetailsContentNodePart getNode( final Event event ) { final TreeItem item = tree.getItem( new Point(event.x, event.y) ); if( item == null ) { return null; } else { return (MasterDetailsContentNodePart) item.getData(); } } protected void afterHideToolTip(Event event) { super.afterHideToolTip(event); // Clear the restored value else this could be a source of a leak if (event != null && event.widget != treeViewer.getControl()) { treeViewer.getControl().setFocus(); } } @Override protected void createContent( final Event event, final Composite parent ) { final MasterDetailsContentNodePart node = getNode( event ); parent.setLayout( glayout( 1 ) ); SapphireFormText text = new SapphireFormText( parent, SWT.NO_FOCUS ); text.setLayoutData( gdfill() ); final org.eclipse.sapphire.modeling.Status validation = node.validation(); final List<org.eclipse.sapphire.modeling.Status> items = gather( validation ); final StringBuffer buffer = new StringBuffer(); buffer.append( "<form>" ); final int count = items.size(); int i = 0; for( org.eclipse.sapphire.modeling.Status item : items ) { final String imageKey = ( item.severity() == org.eclipse.sapphire.modeling.Status.Severity.ERROR ? "error" : "warning" ); buffer.append( "<li style=\"image\" value=\"" + imageKey + "\">" + escapeForXml( item.message() ) + "</li>" ); i++; if( count > 10 && i == 9 ) { break; } } if( count > 10 ) { final String msg = problemsOverflowMessage.format( numberToString( count - 9 ) ); final String imageKey = ( validation.severity() == org.eclipse.sapphire.modeling.Status.Severity.ERROR ? "error" : "warning" ); buffer.append( "<br/><li style=\"image\" value=\"" + imageKey + "\">" + msg + "</li>" ); } buffer.append( "</form>" ); text.setText( buffer.toString(), true, false ); text.setImage( "error", ImageData.readFromClassLoader( SwtResourceCache.class, "Error.png" ).required() ); text.setImage( "warning", ImageData.readFromClassLoader( SwtResourceCache.class, "Warning.png" ).required() ); } private String numberToString( final int number ) { switch( number ) { case 2 : return two.text(); case 3 : return three.text(); case 4 : return four.text(); case 5 : return five.text(); case 6 : return six.text(); case 7 : return seven.text(); case 8 : return eight.text(); case 9 : return nine.text(); default : return String.valueOf( number ); } } private List<org.eclipse.sapphire.modeling.Status> gather( final org.eclipse.sapphire.modeling.Status status ) { final List<org.eclipse.sapphire.modeling.Status> items = new ArrayList<org.eclipse.sapphire.modeling.Status>(); gather( status, items ); return items; } private void gather( final org.eclipse.sapphire.modeling.Status status, final List<org.eclipse.sapphire.modeling.Status> items ) { if( status.children().isEmpty() ) { items.add( status ); } else { for( org.eclipse.sapphire.modeling.Status child : status.children() ) { gather( child, items ); } } } }; treeViewer.setContentProvider( contentProvider ); treeViewer.setLabelProvider( labelProvider ); treeViewer.setInput( new Object() ); final MutableReference<Boolean> ignoreSelectionChange = new MutableReference<Boolean>( false ); final MutableReference<Boolean> ignoreExpandedStateChange = new MutableReference<Boolean>( false ); final Listener contentTreeListener = new Listener() { @Override public void handle( final org.eclipse.sapphire.Event event ) { if( event instanceof MasterDetailsContentOutline.SelectionChangedEvent ) { if( ignoreSelectionChange.get() == true ) { return; } final MasterDetailsContentOutline.SelectionChangedEvent evt = (MasterDetailsContentOutline.SelectionChangedEvent) event; final List<MasterDetailsContentNodePart> selection = evt.selection(); final IStructuredSelection sel; if( selection.isEmpty() ) { sel = StructuredSelection.EMPTY; } else { sel = new StructuredSelection( selection ); } if( ! treeViewer.getSelection().equals( sel ) ) { for( MasterDetailsContentNodePart node : selection ) { treeViewer.reveal( node ); } treeViewer.setSelection( sel ); } } else if( event instanceof MasterDetailsContentOutline.NodeExpandedStateChangedEvent ) { if( ignoreExpandedStateChange.get() == true ) { return; } final MasterDetailsContentOutline.NodeExpandedStateChangedEvent evt = (MasterDetailsContentOutline.NodeExpandedStateChangedEvent) event; final MasterDetailsContentNodePart node = evt.node(); final boolean expandedState = node.isExpanded(); if( treeViewer.getExpandedState( node ) != expandedState ) { treeViewer.setExpandedState( node, expandedState ); } } else if( event instanceof MasterDetailsContentOutline.FilterChangedEvent ) { final MasterDetailsContentOutline.FilterChangedEvent evt = (MasterDetailsContentOutline.FilterChangedEvent) event; filteredTree.changeFilterText( evt.filter() ); } } }; outline.attach( contentTreeListener ); treeViewer.addSelectionChangedListener ( new ISelectionChangedListener() { public void selectionChanged( final SelectionChangedEvent event ) { ignoreSelectionChange.set( true ); try { final IStructuredSelection selection = (IStructuredSelection) event.getSelection(); final List<MasterDetailsContentNodePart> nodes = new ArrayList<MasterDetailsContentNodePart>(); for( Iterator<?> itr = selection.iterator(); itr.hasNext(); ) { nodes.add( (MasterDetailsContentNodePart) itr.next() ); } outline.setSelectedNodes( nodes ); } finally { ignoreSelectionChange.set( false ); } } } ); treeViewer.addDoubleClickListener(new IDoubleClickListener() { public void doubleClick(DoubleClickEvent event) { IStructuredSelection thisSelection = (IStructuredSelection) event.getSelection(); if (thisSelection.size() == 1) { MasterDetailsContentNodePart selectedNode = (MasterDetailsContentNodePart) thisSelection.getFirstElement(); selectedNode.setExpanded(!selectedNode.isExpanded()); } } }); treeViewer.addTreeListener ( new ITreeViewerListener() { public void treeExpanded( final TreeExpansionEvent event ) { ignoreExpandedStateChange.set( true ); try { final MasterDetailsContentNodePart node = (MasterDetailsContentNodePart) event.getElement(); node.setExpanded( true ); } finally { ignoreExpandedStateChange.set( false ); } } public void treeCollapsed( final TreeExpansionEvent event ) { ignoreExpandedStateChange.set( true ); try { final MasterDetailsContentNodePart node = (MasterDetailsContentNodePart) event.getElement(); node.setExpanded( false ); } finally { ignoreExpandedStateChange.set( false ); } } } ); final ContentOutlineActionSupport actionSupport = new ContentOutlineActionSupport( outline, tree ); treeViewer.setExpandedElements( outline.getExpandedNodes().toArray() ); contentTreeListener.handle( new MasterDetailsContentOutline.SelectionChangedEvent( outline.getSelectedNodes() ) ); filteredTree.changeFilterText( outline.getFilterText() ); final ElementsTransfer transfer = new ElementsTransfer( getModelElement().type().getModelElementClass().getClassLoader() ); final Transfer[] transfers = new Transfer[] { transfer }; final DragSource dragSource = new DragSource( tree, DND.DROP_COPY | DND.DROP_MOVE ); dragSource.setTransfer( transfers ); final List<Element> dragElements = new ArrayList<Element>(); dragSource.addDragListener ( new DragSourceListener() { public void dragStart( final DragSourceEvent event ) { final TreeItem[] selection = tree.getSelection(); final String filter = outline().getFilterText(); if( ( filter == null || filter.length() == 0 ) && draggable( selection ) ) { event.doit = true; for( TreeItem item : selection ) { final MasterDetailsContentNodePart node = (MasterDetailsContentNodePart) item.getData(); dragElements.add( node.getModelElement() ); } } else { event.doit = false; } } protected boolean draggable( final TreeItem[] selection ) { if( selection.length > 0 ) { for( TreeItem item : selection ) { final MasterDetailsContentNodePart node = (MasterDetailsContentNodePart) item.getData(); if( ! draggable( node ) ) { return false; } } return true; } return false; } protected boolean draggable( final MasterDetailsContentNodePart node ) { final Element element = node.getModelElement(); if( element.parent() instanceof ElementList && node.controls( element ) ) { return true; } return false; } public void dragSetData( final DragSourceEvent event ) { event.data = dragElements; } public void dragFinished( final DragSourceEvent event ) { if( event.detail == DND.DROP_MOVE ) { // When drop target is the same editor as drag source, the drop handler takes care of removing // elements from their original location. The following block of code accounts for the case when // dropping into another editor. boolean droppedIntoAnotherEditor = false; for( Element dragElement : dragElements ) { if( ! dragElement.disposed() ) { droppedIntoAnotherEditor = true; break; } } if( droppedIntoAnotherEditor ) { final TreeItem[] selection = tree.getSelection(); final List<MasterDetailsContentNodePart> dragNodes = new ArrayList<MasterDetailsContentNodePart>(); for( TreeItem item : selection ) { dragNodes.add( (MasterDetailsContentNodePart) item.getData() ); } final MasterDetailsContentNodePart parentNode = dragNodes.get( 0 ).getParentNode(); MasterDetailsContentNodePart selectionPostDelete = findSelectionPostDelete( parentNode.nodes().visible(), dragNodes ); if( selectionPostDelete == null ) { selectionPostDelete = parentNode; } final Disposable suspension = outline.listeners().queue().suspend( SelectionChangedEventFilter.INSTANCE ); try { for( Element dragElement : dragElements ) { final ElementList<?> dragElementContainer = (ElementList<?>) dragElement.parent(); dragElementContainer.remove( dragElement ); } } catch( Exception e ) { // Log this exception unless the cause is EditFailedException. These exception // are the result of the user declining a particular action that is necessary // before the edit can happen (such as making a file writable). final EditFailedException editFailedException = EditFailedException.findAsCause( e ); if( editFailedException == null ) { Sapphire.service( LoggingService.class ).log( e ); } } finally { suspension.dispose(); outline.listeners().queue().process(); } parentNode.getContentTree().setSelectedNode( selectionPostDelete ); } } dragElements.clear(); } } ); final DropTarget target = new DropTarget( tree, DND.DROP_COPY | DND.DROP_MOVE ); target.setTransfer( transfers ); target.addDropListener ( new DropTargetAdapter() { public void dragOver( final DropTargetEvent event ) { if( event.item != null ) { final TreeItem dragOverItem = (TreeItem) event.item; final MasterDetailsContentNodePart dragOverNode = (MasterDetailsContentNodePart) dragOverItem.getData(); final MasterDetailsContentNodePart parentNode = dragOverNode.getParentNode(); final List<MasterDetailsContentNodePart> siblingNodes = parentNode.nodes().visible(); final Point pt = dragOverItem.getDisplay().map( null, tree, event.x, event.y ); final Rectangle bounds = dragOverItem.getBounds(); boolean dragOverNodeAcceptedDrop = false; if( pt.y > bounds.y + bounds.height / 3 && pt.y < bounds.y + bounds.height - bounds.height / 3 ) { for( final PropertyDef dragOverTargetChildProperty : dragOverNode.getChildNodeFactoryProperties() ) { if( dragOverTargetChildProperty instanceof ListProperty && ! dragOverTargetChildProperty.isReadOnly() ) { dragOverNodeAcceptedDrop = true; event.feedback = DND.FEEDBACK_SELECT; break; } } } if( ! dragOverNodeAcceptedDrop ) { MasterDetailsContentNodePart precedingNode = null; MasterDetailsContentNodePart trailingNode = null; if( pt.y < bounds.y + bounds.height / 2 ) { precedingNode = findPrecedingItem( siblingNodes, dragOverNode ); trailingNode = dragOverNode; event.feedback = DND.FEEDBACK_INSERT_BEFORE; } else { precedingNode = dragOverNode; trailingNode = findTrailingItem( siblingNodes, dragOverNode ); event.feedback = DND.FEEDBACK_INSERT_AFTER; } boolean ok = false; if( precedingNode != null ) { final Element precedingElement = precedingNode.getModelElement(); if( precedingElement.parent() instanceof ElementList && precedingNode.controls( precedingElement ) ) { ok = true; } } if( ! ok && trailingNode != null ) { final Element trailingElement = trailingNode.getModelElement(); if( trailingElement.parent() instanceof ElementList && trailingNode.controls( trailingElement ) ) { ok = true; } } if( ! ok ) { event.feedback = DND.FEEDBACK_NONE; } } } event.feedback |= DND.FEEDBACK_SCROLL; } @SuppressWarnings( "unchecked" ) public void drop( final DropTargetEvent event ) { if( event.data == null || event.item == null) { event.detail = DND.DROP_NONE; return; } // Determine where something was dropped. final List<ElementData> droppedElements = (List<ElementData>) event.data; final TreeItem dropTargetItem = (TreeItem) event.item; final MasterDetailsContentNodePart dropTargetNode = (MasterDetailsContentNodePart) dropTargetItem.getData(); final MasterDetailsContentNodePart parentNode = dropTargetNode.getParentNode(); final List<MasterDetailsContentNodePart> siblingNodes = parentNode.nodes().visible(); final Point pt = tree.getDisplay().map( null, tree, event.x, event.y ); final Rectangle bounds = dropTargetItem.getBounds(); MasterDetailsContentNodePart precedingNode = null; MasterDetailsContentNodePart trailingNode = null; boolean dropTargetNodeAcceptedDrop = false; if( pt.y > bounds.y + bounds.height / 3 && pt.y < bounds.y + bounds.height - bounds.height / 3 ) { for( final PropertyDef dropTargetChildProperty : dropTargetNode.getChildNodeFactoryProperties() ) { if( dropTargetChildProperty instanceof ListProperty && ! dropTargetChildProperty.isReadOnly() ) { dropTargetNodeAcceptedDrop = true; break; } } } if( ! dropTargetNodeAcceptedDrop ) { if( pt.y < bounds.y + bounds.height / 2 ) { precedingNode = findPrecedingItem( siblingNodes, dropTargetNode ); trailingNode = dropTargetNode; } else { precedingNode = dropTargetNode; trailingNode = findTrailingItem( siblingNodes, dropTargetNode ); } } // Determine whether the drop was valid from model standpoint and figure out // where in the model the dropped elements are to be inserted. ElementList<?> list = null; int position = -1; if( precedingNode != null ) { final Element precedingElement = precedingNode.getModelElement(); if( precedingElement.parent() instanceof ElementList && ! precedingElement.parent().definition().isReadOnly() && precedingNode.controls( precedingElement ) ) { list = (ElementList<?>) precedingElement.parent(); final Set<ElementType> possibleListElementTypes = list.definition().service( PossibleTypesService.class ).types(); for( final ElementData droppedElement : droppedElements ) { if( ! possibleListElementTypes.contains( droppedElement.type() ) ) { list = null; break; } } if( list != null ) { position = list.indexOf( precedingElement ) + 1; } } } if( list == null && trailingNode != null ) { final Element trailingElement = trailingNode.getModelElement(); if( trailingElement.parent() instanceof ElementList && ! trailingElement.parent().definition().isReadOnly() && trailingNode.controls( trailingElement ) ) { list = (ElementList<?>) trailingElement.parent(); final Set<ElementType> possibleListElementTypes = list.definition().service( PossibleTypesService.class ).types(); for( final ElementData droppedElement : droppedElements ) { if( ! possibleListElementTypes.contains( droppedElement.type() ) ) { list = null; break; } } if( list != null ) { position = list.indexOf( trailingElement ); } } } if( list == null ) { for( PropertyDef dropTargetChildProperty : dropTargetNode.getChildNodeFactoryProperties() ) { if( dropTargetChildProperty instanceof ListProperty && ! dropTargetChildProperty.isReadOnly() ) { final ListProperty dropTargetChildListProperty = (ListProperty) dropTargetChildProperty; boolean compatible = true; final Set<ElementType> possibleListElementTypes = dropTargetChildListProperty.service( PossibleTypesService.class ).types(); for( final ElementData droppedElement : droppedElements ) { if( ! possibleListElementTypes.contains( droppedElement.type() ) ) { compatible = false; break; } } if( compatible ) { list = dropTargetNode.getLocalModelElement().property( dropTargetChildListProperty ); position = list.size(); } } } } if( list == null ) { event.detail = DND.DROP_NONE; return; } // Prevent a drop within a drag element. for( Property p = list; p != null; p = p.element().parent() ) { for( final Element dragElement : dragElements ) { if( p.element() == dragElement ) { event.detail = DND.DROP_NONE; return; } } } // Perform the removal and insertion into the new location. final Disposable suspension = outline.listeners().queue().suspend( SelectionChangedEventFilter.INSTANCE ); try { if( event.detail == DND.DROP_MOVE ) { for( Element dragElement : dragElements ) { final ElementList<?> dragElementContainer = (ElementList<?>) dragElement.parent(); if( dragElementContainer == list && dragElementContainer.indexOf( dragElement ) < position ) { position--; } dragElementContainer.remove( dragElement ); } } final List<MasterDetailsContentNodePart> newSelection = new ArrayList<MasterDetailsContentNodePart>(); for( final ElementData droppedElement : droppedElements ) { final Element insertedElement = list.insert( droppedElement.type(), position ); insertedElement.copy( droppedElement ); newSelection.add( parentNode.findNode( insertedElement ) ); position++; } parentNode.getContentTree().setSelectedNodes( newSelection ); } catch( Exception e ) { // Log this exception unless the cause is EditFailedException. These exception // are the result of the user declining a particular action that is necessary // before the edit can happen (such as making a file writable). final EditFailedException editFailedException = EditFailedException.findAsCause( e ); if( editFailedException == null ) { Sapphire.service( LoggingService.class ).log( e ); } event.detail = DND.DROP_NONE; } finally { suspension.dispose(); outline.listeners().queue().process(); } } } ); tree.addDisposeListener ( new DisposeListener() { public void widgetDisposed( final DisposeEvent event ) { outline.detach( contentTreeListener ); actionSupport.dispose(); } } ); return filteredTree; } private static void updateExpandedState( final MasterDetailsContentOutline contentTree, final Tree tree ) { final Set<MasterDetailsContentNodePart> expandedNodes = new HashSet<MasterDetailsContentNodePart>(); gatherExpandedNodes( tree.getItems(), expandedNodes ); contentTree.setExpandedNodes( expandedNodes ); } private static void gatherExpandedNodes( final TreeItem[] items, final Set<MasterDetailsContentNodePart> result ) { for( TreeItem item : items ) { if( item.getExpanded() == true ) { result.add( (MasterDetailsContentNodePart) item.getData() ); gatherExpandedNodes( item.getItems(), result ); } } } public void dispose() { super.dispose(); if( this.mainSection != null ) { this.mainSection.dispose(); } if( this.partListener != null ) { getSite().getPage().removePartListener( this.partListener ); } } private static final class ContentOutlineFilteredTree extends FilteredTree { private final MasterDetailsContentOutline contentTree; private WorkbenchJob refreshJob; public ContentOutlineFilteredTree( final Composite parent, final int treeStyle, final MasterDetailsContentOutline contentTree ) { super ( parent, treeStyle, new PatternFilter() { @Override protected boolean isLeafMatch( final Viewer viewer, final Object element ) { return wordMatches( ( (MasterDetailsContentNodePart) element ).getLabel() ); } }, true ); this.contentTree = contentTree; setBackground( Display.getCurrent().getSystemColor( SWT.COLOR_WHITE ) ); } @Override protected WorkbenchJob doCreateRefreshJob() { final WorkbenchJob base = super.doCreateRefreshJob(); this.refreshJob = new WorkbenchJob( base.getName() ) { public IStatus runInUIThread( final IProgressMonitor monitor ) { IStatus st = base.runInUIThread( new NullProgressMonitor() ); if( st.getSeverity() == IStatus.CANCEL ) { return st; } ContentOutlineFilteredTree.this.contentTree.setFilterText( getFilterString() ); updateExpandedState( ContentOutlineFilteredTree.this.contentTree, getViewer().getTree() ); return Status.OK_STATUS; } }; return this.refreshJob; } public void changeFilterText( final String filterText ) { final String currentFilterText = getFilterString(); if( currentFilterText != null && ! currentFilterText.equals( filterText ) ) { setFilterText( filterText ); textChanged(); //this.refreshJob.cancel(); //this.refreshJob.runInUIThread( null ); } } } private final class ContentOutline extends Page implements IContentOutlinePage { private Composite outerComposite = null; private FilteredTree filteredTree = null; private TreeViewer treeViewer = null; private SapphireActionPresentationManager actionPresentationManager; public void init( final IPageSite pageSite ) { super.init( pageSite ); pageSite.setSelectionProvider( this ); } @Override public void createControl( final Composite parent ) { this.outerComposite = new Composite( parent, SWT.NONE ); this.outerComposite.setLayout( glayout( 1, 5, 5 ) ); this.outerComposite.setBackground( Display.getCurrent().getSystemColor( SWT.COLOR_WHITE ) ); this.filteredTree = createContentOutline( this.outerComposite, outline(), false ); this.filteredTree.setLayoutData( gdfill() ); this.treeViewer = this.filteredTree.getViewer(); final SapphireEditorPagePart part = getPart(); final SapphireActionGroup actions = part.getActions( CONTEXT_EDITOR_PAGE_OUTLINE_HEADER ); this.actionPresentationManager = new SapphireActionPresentationManager( MasterDetailsEditorPage.this.presentation, actions ); final SapphireToolBarManagerActionPresentation toolbarActionsPresentation = new SapphireToolBarManagerActionPresentation( this.actionPresentationManager ); toolbarActionsPresentation.setToolBarManager( getSite().getActionBars().getToolBarManager() ); toolbarActionsPresentation.render(); final SapphireKeyboardActionPresentation keyboardActionsPresentation = new SapphireKeyboardActionPresentation( this.actionPresentationManager ); keyboardActionsPresentation.attach( this.filteredTree.getFilterControl() ); keyboardActionsPresentation.render(); } @Override public Control getControl() { return this.outerComposite; } @Override public void setFocus() { this.treeViewer.getControl().setFocus(); } public ISelection getSelection() { if( this.treeViewer == null ) { return StructuredSelection.EMPTY; } return this.treeViewer.getSelection(); } public void setSelection( final ISelection selection ) { if( this.treeViewer != null ) { this.treeViewer.setSelection( selection ); } } public void addSelectionChangedListener( final ISelectionChangedListener listener ) { } public void removeSelectionChangedListener( final ISelectionChangedListener listener ) { } @Override public void dispose() { super.dispose(); this.actionPresentationManager.dispose(); this.actionPresentationManager = null; } } private final class ContentOutlineActionSupport { private final MasterDetailsContentOutline contentTree; private final Listener contentOutlineListener; private final Tree tree; private final Menu menu; private SapphireActionPresentationManager actionPresentationManager; private SapphireActionGroup tempActions; private ContentOutlineActionSupport( final MasterDetailsContentOutline contentOutline, final Tree tree ) { this.contentTree = contentOutline; this.tree = tree; this.menu = new Menu( tree ); this.tree.setMenu( this.menu ); this.contentOutlineListener = new Listener() { @Override public void handle( final org.eclipse.sapphire.Event event ) { if( event instanceof MasterDetailsContentOutline.SelectionChangedEvent ) { final MasterDetailsContentOutline.SelectionChangedEvent evt = (MasterDetailsContentOutline.SelectionChangedEvent) event; handleSelectionChangedEvent( evt.selection() ); } } }; this.contentTree.attach( this.contentOutlineListener ); handleSelectionChangedEvent( contentOutline.getSelectedNodes() ); } private void handleSelectionChangedEvent( final List<MasterDetailsContentNodePart> selection ) { for( MenuItem item : this.menu.getItems() ) { item.dispose(); } if( this.tempActions != null ) { this.tempActions.dispose(); this.tempActions = null; } if( this.actionPresentationManager != null ) { this.actionPresentationManager.dispose(); this.actionPresentationManager = null; } final SapphireEditorPagePart part = getPart(); final SapphireActionGroup actions; if( selection.size() == 1 ) { final MasterDetailsContentNodePart node = selection.get( 0 ); actions = node.getActions( CONTEXT_EDITOR_PAGE_OUTLINE_NODE ); } else { this.tempActions = new SapphireActionGroup( part, CONTEXT_EDITOR_PAGE_OUTLINE ); actions = this.tempActions; } this.actionPresentationManager = new SapphireActionPresentationManager( MasterDetailsEditorPage.this.presentation, actions ); final SapphireMenuActionPresentation menuActionPresentation = new SapphireMenuActionPresentation( this.actionPresentationManager ); menuActionPresentation.setMenu( this.menu ); menuActionPresentation.render(); final SapphireKeyboardActionPresentation keyboardActionPresentation = new SapphireKeyboardActionPresentation( this.actionPresentationManager ); keyboardActionPresentation.attach( this.tree ); keyboardActionPresentation.render(); } public void dispose() { this.contentTree.detach( this.contentOutlineListener ); if( this.tempActions != null ) { this.tempActions.dispose(); this.tempActions = null; } if( this.actionPresentationManager != null ) { this.actionPresentationManager.dispose(); this.actionPresentationManager = null; } } } public IAction getAction(String actionId) { // TODO return action handlers for the global actions such as Delete, Select All return null; } private final class RootSection extends MasterDetailsBlock { private MasterSection masterSection; private List<IDetailsPage> detailsSections; private Control detailsSectionControl; public RootSection() { this.detailsSections = new ArrayList<IDetailsPage>(); this.detailsSectionControl = null; } @Override public void createContent( final IManagedForm managedForm ) { super.createContent( managedForm ); setOutlineRatio( MasterDetailsEditorPage.this.getOutlineRatio() ); try { final Field field = this.detailsPart.getClass().getDeclaredField( "pageBook" ); field.setAccessible( true ); this.detailsSectionControl = (Control) field.get( this.detailsPart ); // Force focus to contained controls if the page book receives focus due to // keyboard traversal. this.detailsSectionControl.addListener ( SWT.FocusIn, new org.eclipse.swt.widgets.Listener() { @Override public void handleEvent( final Event event ) { RootSection.this.detailsSectionControl.setFocus();; } } ); } catch( Exception e ) { Sapphire.service( LoggingService.class ).log( e ); } this.masterSection.handleSelectionChangedEvent( outline().getSelectedNodes() ); setDetailsMaximized( isDetailsMaximized() ); } @Override protected void createMasterPart( final IManagedForm managedForm, final Composite parent ) { this.masterSection = new MasterSection( managedForm, parent ); final org.eclipse.ui.forms.SectionPart spart = new org.eclipse.ui.forms.SectionPart(this.masterSection); managedForm.addPart(spart); } @Override protected void registerPages( final DetailsPart detailsPart ) { final IDetailsPage detailsPage = new DetailsSection(); detailsPart.registerPage( MasterDetailsContentNodePart.class, detailsPage ); this.detailsSections.add( detailsPage ); } @Override protected void applyLayoutData( final SashForm sashForm ) { sashForm.setLayoutData( gdwhint( gdhhint( gdfill(), 200 ), 400 ) ); } public IDetailsPage getCurrentDetailsSection() { return this.detailsPart.getCurrentPage(); } public void setDetailsMaximized( final boolean maximized ) { this.sashForm.setMaximizedControl( maximized ? this.detailsSectionControl : null ); } public double getOutlineRatio() { final Control[] children = this.sashForm.getChildren(); final int outline = children[ 0 ].getSize().x; final int details = children[ 1 ].getSize().x; final int total = outline + details; final double ratio = ( (double) outline ) / ( (double) total ); return ratio; } public void setOutlineRatio( final double ratio ) { final int total = Integer.MAX_VALUE; final int outline = (int) ( total * ratio ); final int details = total - outline; this.sashForm.setWeights( new int[] { outline, details } ); } public void dispose() { if( this.masterSection != null ) { this.masterSection.dispose(); } for( IDetailsPage section : this.detailsSections ) { section.dispose(); } } @Override protected void createToolBarActions( IManagedForm managedForm ) { } } private final class MasterSection extends Section { private IManagedForm managedForm; private org.eclipse.ui.forms.SectionPart sectionPart; private TreeViewer treeViewer; private Tree tree; private void refreshOutlineHeaderText() { setText( LabelTransformer.transform( getPart().getOutlineHeaderText(), CapitalizationType.TITLE_STYLE, false ) ); } public MasterSection( final IManagedForm managedForm, final Composite parent) { super( parent, Section.TITLE_BAR ); final FormToolkit toolkit = managedForm.getToolkit(); FormColors colors = toolkit.getColors(); this.setMenu(parent.getMenu()); toolkit.adapt(this, true, true); if (this.toggle != null) { this.toggle.setHoverDecorationColor(colors .getColor(IFormColors.TB_TOGGLE_HOVER)); this.toggle.setDecorationColor(colors .getColor(IFormColors.TB_TOGGLE)); } this.setFont(createBoldFont(colors.getDisplay(), this.getFont())); colors.initializeSectionToolBarColors(); this.setTitleBarBackground(colors.getColor(IFormColors.TB_BG)); this.setTitleBarBorderColor(colors .getColor(IFormColors.TB_BORDER)); this.setTitleBarForeground(colors .getColor(IFormColors.TB_TOGGLE)); this.marginWidth = 10; this.marginHeight = 10; setLayoutData( gdfill() ); setLayout( glayout( 1, 0, 0 ) ); final SapphireEditorPagePart part = getPart(); final Listener pagePartListener = new FilteredListener<OutlineHeaderTextEvent>() { @Override protected void handleTypedEvent( final OutlineHeaderTextEvent event ) { refreshOutlineHeaderText(); } }; part.attach( pagePartListener ); refreshOutlineHeaderText(); final Composite client = toolkit.createComposite( this ); client.setLayout( glayout( 1, 0, 0 ) ); this.managedForm = managedForm; final MasterDetailsContentOutline contentTree = outline(); final FilteredTree filteredTree = createContentOutline( client, contentTree, true ); this.treeViewer = filteredTree.getViewer(); this.tree = this.treeViewer.getTree(); this.sectionPart = new org.eclipse.ui.forms.SectionPart( this ); this.managedForm.addPart( this.sectionPart ); contentTree.attach ( new FilteredListener<MasterDetailsContentOutline.SelectionChangedEvent>() { @Override protected void handleTypedEvent( final MasterDetailsContentOutline.SelectionChangedEvent event ) { handleSelectionChangedEvent( event.selection() ); } } ); final ToolBar toolbar = new ToolBar( this, SWT.FLAT | SWT.HORIZONTAL ); setTextClient( toolbar ); final SapphireActionGroup actions = part.getActions( CONTEXT_EDITOR_PAGE_OUTLINE_HEADER ); final SapphireActionPresentationManager actionPresentationManager = new SapphireActionPresentationManager( MasterDetailsEditorPage.this.presentation, actions ); final SapphireToolBarActionPresentation toolbarActionsPresentation = new SapphireToolBarActionPresentation( actionPresentationManager ); toolbarActionsPresentation.setToolBar( toolbar ); toolbarActionsPresentation.render(); final SapphireKeyboardActionPresentation keyboardActionsPresentation = new SapphireKeyboardActionPresentation( actionPresentationManager ); keyboardActionsPresentation.attach( filteredTree.getFilterControl() ); keyboardActionsPresentation.render(); toolkit.paintBordersFor( this ); setClient( client ); this.tree.addDisposeListener ( new DisposeListener() { public void widgetDisposed( final DisposeEvent event ) { part.detach( pagePartListener ); } } ); } private Font createBoldFont(Display display, Font regularFont) { FontData[] fontDatas = regularFont.getFontData(); for (int i = 0; i < fontDatas.length; i++) { fontDatas[i].setStyle(fontDatas[i].getStyle() | SWT.BOLD); } return new Font(display, fontDatas); } private void handleSelectionChangedEvent( final List<MasterDetailsContentNodePart> selection ) { final IStructuredSelection sel = ( selection.isEmpty() ? StructuredSelection.EMPTY : new StructuredSelection( selection.get( 0 ) ) ); this.managedForm.fireSelectionChanged( this.sectionPart, sel ); } } private class DetailsSection implements IDetailsPage { private MasterDetailsContentNodePart node; private Composite composite; private final Listener listener; private List<SectionPart> sections; private List<SectionPresentation> presentations; public DetailsSection() { this.listener = new FilteredListener<PartVisibilityEvent>() { @Override protected void handleTypedEvent( final PartVisibilityEvent event ) { refreshSections(); } }; this.sections = Collections.emptyList(); } public void initialize( final IManagedForm form ) { } public final void createContents( final Composite parent ) { this.composite = parent; this.composite.setLayout( glayout( 2, 0, 0 ) ); this.composite.setBackground( getPart().getSwtResourceCache().color( org.eclipse.sapphire.Color.WHITE ) ); this.composite.setBackgroundMode( SWT.INHERIT_DEFAULT ); this.composite.setData( FormComponentPresentation.DATA_LAYOUT_ROOT, Boolean.TRUE ); refreshSections(); } public void commit(boolean onSave) { } public boolean isDirty() { return false; } public boolean isStale() { return false; } public void refresh() { } public void setFocus() { } public boolean setFormInput(Object input) { return false; } public void selectionChanged( final IFormPart part, final ISelection selection ) { final IStructuredSelection ssel = (IStructuredSelection) selection; if( ssel.size() == 1 ) { this.node = (MasterDetailsContentNodePart) ssel.getFirstElement(); } else { this.node = null; } refreshSections(); } private void refreshSections() { if( this.presentations != null ) { for( final Presentation presentation : this.presentations ) { presentation.dispose(); } } if( this.composite.getChildren().length > 0 ) { throw new IllegalStateException(); } for( final SectionPart section : this.sections ) { section.detach( this.listener ); } if( this.node != null ) { this.sections = this.node.getSections(); } else { this.sections = ListFactory.empty(); } final ListFactory<SectionPresentation> presentationsListFactory = ListFactory.start(); for( final SectionPart section : this.sections ) { section.attach( this.listener ); if( section.visible() ) { final SectionPresentation presentation = (SectionPresentation) section.createPresentation( MasterDetailsEditorPage.this.presentation, this.composite ); presentationsListFactory.add( presentation ); presentation.render(); } } this.presentations = presentationsListFactory.result(); this.composite.getParent().layout( true, true ); } public void dispose() { for( SectionPart section : this.sections ) { section.detach( this.listener ); } } } private static final class TreeViewerUpdateOp implements Runnable { private final TreeViewer tree; private final Object element; public TreeViewerUpdateOp( final TreeViewer tree, final Object element ) { this.tree = tree; this.element = element; } public void run() { this.tree.update( this.element, null ); } } private static final class TreeViewerRefreshOp implements Runnable { private final TreeViewer tree; private final Object element; public TreeViewerRefreshOp( final TreeViewer tree, final Object element ) { this.tree = tree; this.element = element; } public void run() { if( this.element == null ) { this.tree.refresh(); } else { this.tree.refresh( this.element ); } } } private static final class SelectionChangedEventFilter implements Filter<EventDeliveryJob> { public static SelectionChangedEventFilter INSTANCE = new SelectionChangedEventFilter(); @Override public boolean allows( final EventDeliveryJob job ) { return ! ( job.event() instanceof MasterDetailsContentOutline.SelectionChangedEvent ); } } }