/******************************************************************************* * Copyright (c) 2007, 2014 compeople AG and others. * 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: * compeople AG - initial API and implementation *******************************************************************************/ package org.eclipse.riena.navigation.ui.swt.presentation.stack.common; import java.util.HashSet; import java.util.Set; import org.eclipse.riena.navigation.ISubApplicationNode; import org.eclipse.riena.navigation.ISubModuleNode; import org.eclipse.riena.navigation.listener.NavigationTreeObserver; import org.eclipse.riena.navigation.listener.SubModuleNodeListener; import org.eclipse.riena.navigation.ui.controllers.SubApplicationController; import org.eclipse.riena.navigation.ui.swt.ApplicationUtility; import org.eclipse.riena.navigation.ui.swt.component.SubApplicationSwitcherWidget; import org.eclipse.riena.navigation.ui.swt.lnf.renderer.ModuleGroupRenderer; import org.eclipse.riena.navigation.ui.swt.lnf.renderer.SubModuleViewRenderer; import org.eclipse.riena.navigation.ui.swt.presentation.SwtViewProvider; import org.eclipse.riena.navigation.ui.swt.presentation.stack.TitlelessStackPresentation; import org.eclipse.riena.navigation.ui.swt.views.AbstractNavigationCompositeDeligation; import org.eclipse.riena.navigation.ui.swt.views.SubModuleView; import org.eclipse.riena.ui.swt.EmbeddedTitleBar; import org.eclipse.riena.ui.swt.facades.SWTFacade; import org.eclipse.riena.ui.swt.lnf.LnfKeyConstants; import org.eclipse.riena.ui.swt.lnf.LnfManager; import org.eclipse.riena.ui.swt.lnf.rienadefault.RienaDefaultLnf; import org.eclipse.riena.ui.swt.utils.SwtUtilities; import org.eclipse.swt.SWT; import org.eclipse.swt.events.PaintEvent; import org.eclipse.swt.events.PaintListener; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ToolBar; import org.eclipse.swt.widgets.Widget; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.presentations.IPresentablePart; import org.eclipse.ui.presentations.IStackPresentationSite; import org.eclipse.ui.presentations.StackDropResult; import org.eclipse.ui.presentations.StackPresentation; /** * <pre> * +-----------------------------------------------------------------+ * | | * | +---------+---------+ | * | | SubApp1 | SubApp2 | | * +-----------------------------------------------------------------+ * | *1 *2 | * | +---------+ +------------------------------------------------+ | * | | Module1 | | Module1 - Sub1 | | * | +---------+ +------------------------------------------------+ | * | | o Sub1 | | | | * | | o Sub2 | | *3 | | * | +---------+ | | | * | | Module2 | | | | * | +---------+ | | | * | +---------+ | | | * | | Module2 | | | | * | +---------+ | | | * | | | | * | | | | * | +------------------------------------------------+ | * +-----------------------------------------------------------------+ * * legend: *1 - navigation * *2 - sub-module view (with title bar and border) * *3 - content area of the sub-module view (active sub module) * </pre> */ public class TitlelessStackPresentation3xRAP extends StackPresentation { private static boolean navigationVisible; private final Set<IPresentablePart> knownParts = new HashSet<IPresentablePart>(); private IPresentablePart current; private IPresentablePart navigation; private final Composite parent; private Composite placeHolder; private SubModuleViewRenderer renderer; private boolean hasListener; private Rectangle lastMasterBounds; public TitlelessStackPresentation3xRAP(final Composite parent, final IStackPresentationSite stackSite) { super(stackSite); this.parent = new Composite(parent, SWT.DOUBLE_BUFFERED); createPlaceHolder(parent); createSubModuleViewArea(); if (isNavigationFastViewEnabled()) { final CloseNavigationMouseListener closeNavigationMouseListener = new CloseNavigationMouseListener(); parent.getDisplay().addFilter(SWT.MouseDown, closeNavigationMouseListener); } } private void createPlaceHolder(final Composite parent) { placeHolder = new Composite(parent, SWT.NONE); placeHolder.setLayout(new FormLayout()); placeHolder.setBounds(0, 0, 0, 0); placeHolder.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.SUB_MODULE_BACKGROUND)); } @Override public void addPart(final IPresentablePart newPart, final Object cookie) { initializeSubModuleChangeListener(); if (isNavigation(newPart)) { navigation = newPart; } knownParts.add(newPart); } private void observeNavigation() { final NavigationTreeObserver observer = new NavigationTreeObserver(); observer.addListener(new StackPresentationSubModuleListener()); observer.addListenerTo(getSubApplicationNode()); } /** * Pre- and after- hooking of {@link SubModuleView} - layouting */ private class StackPresentationSubModuleListener extends SubModuleNodeListener { @Override public void beforeActivated(final ISubModuleNode source) { if (current != null) { placeHolder.setBounds(calcPlaceHolderBounds()); placeHolder.setVisible(true); placeHolder.moveAbove(null); // move to the top of the drawing order placeHolder.update(); current.getControl().update(); } } @Override public void afterActivated(final ISubModuleNode source) { current.setBounds(calcSubModuleInnerBounds()); current.setVisible(true); if (new ApplicationUtility().isNavigationFastViewEnabled()) { setNavigationVisible(navigationVisible); } hideComposite(placeHolder); redrawSubModuleTitle(); current.getControl().moveAbove(null); // move to the top of the drawing order current.getControl().update(); } @Override public void afterDeactivated(final ISubModuleNode source) { super.afterDeactivated(source); if (new ApplicationUtility().isNavigationFastViewEnabled()) { navigationVisible = isNavigationVisible(); } } } private void hideComposite(final Control control) { if (control == null || control.isDisposed()) { return; } control.setBounds(new Rectangle(0, 0, 0, 0)); control.setVisible(false); } private Rectangle calcPlaceHolderBounds() { final Rectangle tmp = calcSubModuleInnerBounds(); Rectangle placeHolderBounds = null; final EmbeddedTitleBar tb = (EmbeddedTitleBar) locateControl(current.getControl(), StackPresentationControlFilter.TITLE_BAR_FILTER); if (tb != null) { placeHolderBounds = new Rectangle(tmp.x, tmp.y + tb.getBounds().height, tmp.width, tmp.height); } else { placeHolderBounds = new Rectangle(tmp.x, tmp.y + 10, tmp.width, tmp.height); } return placeHolderBounds; } private Control locateControl(final Control ctrl, final StackPresentationControlFilter filter) { if (ctrl == null) { return null; } if (filter.accept(ctrl)) { return ctrl; } if (ctrl instanceof Composite) { final Control[] children = ((Composite) ctrl).getChildren(); for (final Control child : children) { final Control result = locateControl(child, filter); if (result != null) { return result; } } } return null; } /** * @since 4.0 */ public ISubApplicationNode getSubApplicationNode() { if (navigation == null) { return null; } return (ISubApplicationNode) findDataObject(navigation.getControl(), TitlelessStackPresentation.PROPERTY_SUBAPPLICATION_NODE); } private Object findDataObject(final Control control, final String dataKey) { Object data = control.getData(dataKey); if (data != null) { return data; } if (control instanceof Composite) { final Composite composite = (Composite) control; final Control[] children = composite.getChildren(); final int childCount = children.length; for (int i = 0; i < childCount && data == null; i++) { data = findDataObject(children[i], dataKey); } } return data; } @Override public void selectPart(final IPresentablePart toSelect) { if (current == toSelect) { return; } if (isNavigation(toSelect)) { selectNavigation(toSelect); } else if (toSelect != null) { current = toSelect; } } /** * @param toSelect */ private void selectNavigation(final IPresentablePart toSelect) { observeNavigation(); final Rectangle navi = calcNavigationBounds(parent); toSelect.setBounds(navi); redrawSubModuleTitle(); if (!isNavigationFastViewEnabled()) { toSelect.setVisible(true); } } /** * This is called when the window is resized. * <ol> * <li>compute new size for navigation</li> * <li>all non-current views are set to size zero (until they become current and are shown)</li> * <li>the current view is resized</li> * </ol> */ @Override public void setBounds(final Rectangle bounds) { this.lastMasterBounds = bounds; parent.setBounds(bounds); if (navigation != null) { final Rectangle navi = calcNavigationBounds(parent); navigation.setBounds(navi); } final Rectangle innerBounds = calcSubModuleInnerBounds(); if (current != null) { current.setBounds(innerBounds); } for (final IPresentablePart part : knownParts) { if (part != current && !isNavigation(part)) { part.setBounds(new Rectangle(0, 0, 0, 0)); } } parent.setVisible(true); } @Override public void removePart(final IPresentablePart oldPart) { if (isNavigation(oldPart)) { navigation = null; } else if (oldPart == current) { current = null; } knownParts.remove(oldPart); } @Override public void dispose() { if (getRenderer() != null) { getRenderer().dispose(); } } /** * This presentation does not support drag and drop. * * @see org.eclipse.ui.presentations.StackPresentation#dragOver(org.eclipse.swt.widgets.Control, org.eclipse.swt.graphics.Point) */ @Override public StackDropResult dragOver(final Control currentControl, final Point location) { return null; } @Override public Control getControl() { return parent; } /** * Return the control of the active part, so that the correct TAB-ordering can be set. */ @Override public Control[] getTabList(final IPresentablePart part) { if (current != null) { return new Control[] { current.getControl() }; } return new Control[] {}; } @Override public void setActive(final int newState) { /* * Be very care careful what you do here, to avoid causing flicker. This method may be called with AS_INACTIVE and AS_ACTIVE states repeatedly for the * same part. */ } /** * The state (minimized, maximized or restored) is not relevant for the <code>TitlelessStackPresentation</code>. * * @see org.eclipse.ui.presentations.StackPresentation#setState(int) */ @Override public void setState(final int state) { // nothing to do } @Override public void setVisible(final boolean isVisible) { parent.setVisible(isVisible); } @Override public void showPaneMenu() { // nothing to do } @Override public void showSystemMenu() { // nothing to do } // helping methods // //////////////// /** * Calculates the inner (i.e. usable) bounds of the sub-module view. * * @param part * * @return inner bounds sub-module view */ private Rectangle calcSubModuleInnerBounds() { return getSubModuleViewRenderer().computeInnerBounds(calcSubModuleOuterBounds()); } /** * Calculates the bounds of the sub-module view. * * @return outer bounds sub-module view */ private Rectangle calcSubModuleOuterBounds() { final Rectangle naviBounds = calcNavigationBounds(parent); final int x = isNavigationFastViewEnabled() ? getShellSubModuleGap() : naviBounds.x + naviBounds.width + getNavigationSubModuleGap(); final int y = naviBounds.y; final int width = parent.getBounds().width - x - getShellSubModuleGap(); final int height = naviBounds.height; final Rectangle outerBounds = new Rectangle(x, y, width, height); return outerBounds; } /** * Calculates the bounds of the navigation on the left side. * * @return bounds of navigation */ public static Rectangle calcNavigationBounds(final Composite parent) { final GC gc = new GC(parent); try { final Point size = getModuleGroupRenderer().computeSize(gc, SWT.DEFAULT, SWT.DEFAULT); final int x = getShellNavigationGap(); final int width = size.x + (isNavigationFastViewEnabled() ? 2 * AbstractNavigationCompositeDeligation.BORDER_MARGIN : 0); Integer statuslineHeight = LnfManager.getLnf().getIntegerSetting(LnfKeyConstants.STATUSLINE_HEIGHT); statuslineHeight = SwtUtilities.convertYToDpi(statuslineHeight); final int height = parent.getBounds().height - TitlelessStackPresentation.PADDING_BOTTOM - statuslineHeight; return new Rectangle(x, 0, width, height); } finally { gc.dispose(); } } private static boolean isNavigationFastViewEnabled() { return new ApplicationUtility().isNavigationFastViewEnabled(); } /** * Creates the area within witch the view of the current active sub-module is displayed. */ private void createSubModuleViewArea() { parent.setBackground(LnfManager.getLnf().getColor(LnfKeyConstants.SUB_MODULE_BACKGROUND)); SWTFacade.getDefault().addPaintListener(parent, new PaintListener() { /** * Paints the border of the current active sub-module. */ public void paintControl(final PaintEvent e) { if (current != null) { final SubModuleViewRenderer viewRenderer = getRenderer(); if (viewRenderer != null) { final Rectangle bounds = calcSubModuleOuterBounds(); viewRenderer.setBounds(bounds); viewRenderer.paint(e.gc, null); } } } }); } /** * Returns the currently active page. */ private IWorkbenchPage getActivePage() { return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); } /** * Returns the renderer of the sub-module view.<br> * Renderer renders the border of the sub-module view and not the content of the view. * * @return renderer of sub-module view */ private SubModuleViewRenderer getRenderer() { if (renderer == null) { renderer = (SubModuleViewRenderer) LnfManager.getLnf().getRenderer("SubModuleView.renderer"); //$NON-NLS-1$ } return renderer; } /** * Returns the renderer of a module group. */ private static ModuleGroupRenderer getModuleGroupRenderer() { return (ModuleGroupRenderer) LnfManager.getLnf().getRenderer(LnfKeyConstants.MODULE_GROUP_RENDERER); } /** * Returns the renderer of the sub module view */ private SubModuleViewRenderer getSubModuleViewRenderer() { return (SubModuleViewRenderer) LnfManager.getLnf().getRenderer(LnfKeyConstants.SUB_MODULE_VIEW_RENDERER); } /** * Install a sub module change listener with the navigation tree, that will repaint the sub module title when the node has changed. * <p> * This is necessary because when "shared" nodes (i.e. parts) are selected, we do <b>not</b> receive a {@link #selectPart(IPresentablePart)} notification if * the part is <b>already</b> selected. However the shared parts may still have different titles. */ private synchronized void initializeSubModuleChangeListener() { if (hasListener) { return; } final SubApplicationController controller = getSubApplicationController(); if (controller != null) { final NavigationTreeObserver navigationTreeObserver = new NavigationTreeObserver(); navigationTreeObserver.addListener(new SubModuleNodeListener() { @Override public void activated(final ISubModuleNode source) { if(lastMasterBounds != null){ setBounds(lastMasterBounds); } redrawSubModuleTitle(); } }); navigationTreeObserver.addListenerTo(controller.getNavigationNode()); hasListener = true; } } /** * Return the SubApplicationController instance for this presentation. * * @return a SubApplicationController instance or null */ private SubApplicationController getSubApplicationController() { SubApplicationController result = null; final IWorkbenchPage page = getActivePage(); if (page != null) { final String id = page.getPerspective().getId(); final ISubApplicationNode subApplication = SwtViewProvider.getInstance().getNavigationNode(id, ISubApplicationNode.class); result = (SubApplicationController) subApplication.getNavigationNodeController(); } return result; } /** * Returns true if the given part is the navigation tree. */ private boolean isNavigation(final IPresentablePart part) { return part.getPartProperty(TitlelessStackPresentation.PROPERTY_NAVIGATION) != null; } /** * Redraws the custom sub module title contained in the parent */ private void redrawSubModuleTitle() { if (parent != null && !parent.isDisposed()) { parent.redraw(); } } /** * Returns the gap between the right side of the navigation and the left side of the active sub module. * * @return gap */ private int getNavigationSubModuleGap() { final RienaDefaultLnf lnf = LnfManager.getLnf(); return lnf.getIntegerSetting(LnfKeyConstants.NAVIGATION_SUB_MODULE_GAP, TitlelessStackPresentation.DEFAULT_NAVIGATION_SUB_MODULE_GAP); } /** * Returns the gap between the border of the shell and the left side of the navigation.<br> * <i>Note: The shell has also a padding ( {@linkplain LnfKeyConstants.TITLELESS_SHELL_PADDING}).</i> * * @return gap */ private static int getShellNavigationGap() { final RienaDefaultLnf lnf = LnfManager.getLnf(); return lnf.getIntegerSetting(LnfKeyConstants.TITLELESS_SHELL_NAVIGATION_HORIZONTAL_GAP, TitlelessStackPresentation.DEFAULT_PADDING_LEFT); } /** * Returns the gap between right side of the active sub module and the border of the shell.<br> * <i>Note: The shell has also a padding ( {@linkplain LnfKeyConstants.TITLELESS_SHELL_PADDING}).</i> * * @return gap */ private static int getShellSubModuleGap() { final RienaDefaultLnf lnf = LnfManager.getLnf(); return lnf.getIntegerSetting(LnfKeyConstants.TITLELESS_SHELL_SUB_MODULE_HORIZONTAL_GAP, TitlelessStackPresentation.DEFAULT_PADDING_RIGHT); } /** * Sets the visibility of the navigation, if the Property {@link LnfKeyConstants#NAVIGATION_FAST_VIEW} is set to true. * * @since 4.0 */ public void setNavigationVisible(final boolean visible) { if (isNavigationFastViewEnabled()) { navigation.setVisible(visible); } } /** * Returns the visibility of the navigation. * * @return the visibility of the navigation. Always false if the property {@link LnfKeyConstants#NAVIGATION_FAST_VIEW} is set to false. * * @since 4.0 */ public boolean isNavigationVisible() { if (isNavigationFastViewEnabled()) { return navigation.getControl().isVisible(); } return true; } private final class CloseNavigationMouseListener implements Listener { public void handleEvent(final Event event) { if (event.button == 1 && navigation != null && isNavigationVisible() && isSelectedWidgetAllowedToCloseNavigation(event.widget)) { final Point widgetLocationOnDisplay = ((Control) event.widget).toDisplay(new Point(0, 0)); final Rectangle mouseLocation = event.getBounds(); final Control navigationControl = navigation.getControl(); final Rectangle navigationBounds = navigationControl.getBounds(); final Point navigationLocationOnDisplay = navigationControl.toDisplay(0, 0); final Point mousePositionOnDisplay = new Point(mouseLocation.x + widgetLocationOnDisplay.x, mouseLocation.y + widgetLocationOnDisplay.y); // TODO cleanup if (mousePositionOnDisplay.x > navigationLocationOnDisplay.x + navigationBounds.width || mousePositionOnDisplay.y > navigationLocationOnDisplay.y + navigationBounds.height || mousePositionOnDisplay.y < navigationLocationOnDisplay.y) { setNavigationVisible(false); } } } private boolean isSelectedWidgetAllowedToCloseNavigation(final Widget widget) { return widget instanceof Control && !(widget instanceof ToolBar) && !(widget instanceof SubApplicationSwitcherWidget); } } }