Java tutorial
/****************************************************************************** * Copyright (c) 2015 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.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(); if (parent == outline.getRoot()) { treeViewer.refresh(); } else { treeViewer.refresh(parent); } } else { if (node.visible()) { if (event instanceof LabelChangedEvent || event instanceof ImageChangedEvent || event instanceof DecorationEvent) { Display.getCurrent().asyncExec(new TreeViewerUpdateJob(treeViewer, node)); } else if (event instanceof NodeListEvent) { treeViewer.refresh(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); 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 TreeViewerUpdateJob implements Runnable { private final TreeViewer tree; private final Object element; public TreeViewerUpdateJob(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 SelectionChangedEventFilter implements Filter<EventDeliveryJob> { public static SelectionChangedEventFilter INSTANCE = new SelectionChangedEventFilter(); @Override public boolean allows(final EventDeliveryJob job) { return !(job.event() instanceof MasterDetailsContentOutline.SelectionChangedEvent); } } }