/******************************************************************************* * 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.views; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.NoSuchElementException; import org.osgi.service.log.LogService; import org.eclipse.equinox.log.Logger; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IPageLayout; import org.eclipse.ui.IPerspectiveDescriptor; import org.eclipse.ui.IPerspectiveFactory; import org.eclipse.ui.ISourceProviderListener; import org.eclipse.ui.ISources; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IViewReference; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.riena.core.Log4r; import org.eclipse.riena.core.util.RienaConfiguration; import org.eclipse.riena.core.util.StringUtils; import org.eclipse.riena.internal.navigation.ui.swt.Activator; import org.eclipse.riena.internal.ui.ridgets.swt.uiprocess.UIProcessRidget; import org.eclipse.riena.internal.ui.swt.facades.WorkbenchFacade; import org.eclipse.riena.navigation.ApplicationModelFailure; import org.eclipse.riena.navigation.IModuleGroupNode; import org.eclipse.riena.navigation.IModuleNode; import org.eclipse.riena.navigation.ISubApplicationNode; import org.eclipse.riena.navigation.ISubModuleNode; import org.eclipse.riena.navigation.NavigationNodeId; import org.eclipse.riena.navigation.listener.NavigationTreeObserver; import org.eclipse.riena.navigation.listener.SubApplicationNodeListener; import org.eclipse.riena.navigation.listener.SubModuleNodeListener; import org.eclipse.riena.navigation.model.SubApplicationNode; import org.eclipse.riena.navigation.ui.controllers.SubApplicationController; import org.eclipse.riena.navigation.ui.swt.binding.DelegatingRidgetMapper; import org.eclipse.riena.navigation.ui.swt.binding.InjectSwtViewBindingDelegate; import org.eclipse.riena.navigation.ui.swt.component.MenuCoolBarComposite; import org.eclipse.riena.navigation.ui.swt.presentation.SwtViewId; import org.eclipse.riena.navigation.ui.swt.presentation.SwtViewProvider; import org.eclipse.riena.ui.ridgets.SubModuleUtils; import org.eclipse.riena.ui.ridgets.controller.IController; import org.eclipse.riena.ui.ridgets.swt.uibinding.AbstractViewBindingDelegate; import org.eclipse.riena.ui.ridgets.swt.uibinding.SwtControlRidgetMapper; import org.eclipse.riena.ui.swt.uiprocess.UIProcessControl; import org.eclipse.riena.ui.swt.utils.SWTBindingPropertyLocator; import org.eclipse.riena.ui.workarea.WorkareaManager; /** * View of a sub-application. * * @since 3.0 */ public class SubApplicationView implements INavigationNodeView<SubApplicationNode>, IPerspectiveFactory { private static final Logger LOGGER = Log4r.getLogger(Activator.getDefault(), SubApplicationView.class); private final LinkedList<SwtViewId> subModuleViewStock; private final AbstractViewBindingDelegate binding; private final RienaMenuHelper menuBindHelper; private final ISourceProviderListener menuSourceProviderListener; private SubApplicationController subApplicationController; private SubApplicationListener subApplicationListener; private SubApplicationNode subApplicationNode; /** * Creates a new instance of {@code SubApplicationView}. */ public SubApplicationView() { subModuleViewStock = new LinkedList<SwtViewId>(); binding = createBinding(); menuBindHelper = new RienaMenuHelper(); menuSourceProviderListener = new MenuSourceProviderListener(); } public void addUpdateListener(final IComponentUpdateListener listener) { throw new UnsupportedOperationException(); } /** * Binds the navigation node to the view. Creates the widgets and the controller if necessary.<br> * Also the menus and the tool bar items are binded. * * @see org.eclipse.riena.navigation.ui.swt.views.INavigationNodeView#bind(org.eclipse.riena.navigation.INavigationNode) */ public void bind(final SubApplicationNode node) { menuBindHelper.addSourceProviderListener(menuSourceProviderListener); if (getNavigationNode().getNavigationNodeController() instanceof IController) { final IController controller = (IController) node.getNavigationNodeController(); binding.injectRidgets(controller); binding.bind(controller); bindMenuAndToolItems(controller); controller.afterBind(); } subApplicationListener = new SubApplicationListener(); getNavigationNode().addListener(subApplicationListener); } public void createInitialLayout(final IPageLayout layout) { addUIControls(); subApplicationNode = (SubApplicationNode) locateSubApplication(layout); subApplicationController = createController(subApplicationNode); initializeListener(subApplicationController); bind(subApplicationNode); subApplicationController.afterBind(); doBaseLayout(layout); } public SubApplicationNode getNavigationNode() { return subApplicationNode; } public void unbind() { if (getNavigationNode() != null) { getNavigationNode().removeListener(subApplicationListener); if (getNavigationNode().getNavigationNodeController() instanceof IController) { final IController controller = (IController) getNavigationNode().getNavigationNodeController(); binding.unbind(controller); menuBindHelper.unbind(controller); } } menuBindHelper.removeSourceProviderListener(menuSourceProviderListener); } protected AbstractViewBindingDelegate createBinding() { final DelegatingRidgetMapper ridgetMapper = new DelegatingRidgetMapper(SwtControlRidgetMapper.getInstance()); addMappings(ridgetMapper); return new InjectSwtViewBindingDelegate(ridgetMapper); } /** * Creates controller of the sub-application view and create and set the some ridgets. * * @param subApplication * sub-application node * @return controller of the sub-application view */ protected SubApplicationController createController(final ISubApplicationNode subApplication) { return new SubApplicationController(subApplication); } protected void doBaseLayout(final IPageLayout layout) { layout.setEditorAreaVisible(false); layout.setFixed(true); } // helping methods ////////////////// private void addMappings(final DelegatingRidgetMapper ridgetMapper) { ridgetMapper.addMapping(UIProcessControl.class, UIProcessRidget.class); } private void addUIControls() { initUIProcessRidget(); } /** * Creates Ridgets for the menu items and the cool bar items and binds the Ridgets of the items with the UI widgets. * * @param controller */ private void bindMenuAndToolItems(final IController controller) { final Shell shell = getShell(); menuBindHelper.bindMenuAndToolItems(controller, shell, shell); } /** * Returns the shell of the application. * * @return application shell */ private Shell getShell() { final SWTBindingPropertyLocator locator = SWTBindingPropertyLocator.getInstance(); final Shell[] shells = Display.getDefault().getShells(); for (final Shell shell : shells) { final String value = locator.locateBindingProperty(shell); if ((value != null) && value.equals(ApplicationViewAdvisor.SHELL_RIDGET_PROPERTY)) { return shell; } } Shell shell = WorkbenchFacade.getInstance().getActiveWindowShell(); if (shell == null) { shell = Display.getDefault().getActiveShell(); } return shell; } /** * Adds a listener for all sub-module nodes of the sub-application. * * @param controller * controller of the sub-application */ private void initializeListener(final SubApplicationController controller) { final NavigationTreeObserver navigationTreeObserver = new NavigationTreeObserver(); navigationTreeObserver.addListener(new MySubModuleNodeListener()); navigationTreeObserver.addListenerTo(controller.getNavigationNode()); } private void initUIProcessRidget() { final UIProcessControl uiControl = new UIProcessControl(getShell()); uiControl.setPropertyName("uiProcessRidget"); //$NON-NLS-1$ binding.addUIControl(uiControl); } /** * @since 3.0 */ protected ISubApplicationNode locateSubApplication(final IPageLayout layout) { return SwtViewProvider.getInstance().getNavigationNode(layout.getDescriptor().getId(), ISubApplicationNode.class); } // helping classes ////////////////// private class SubApplicationListener extends SubApplicationNodeListener { @Override public void block(final ISubApplicationNode source, final boolean block) { super.block(source, block); for (final IModuleGroupNode group : source.getChildren()) { for (final IModuleNode module : group.getChildren()) { module.setBlocked(block); } } } @Override public void disposed(final ISubApplicationNode source) { unbind(); } @Override public void nodeIdChange(final ISubApplicationNode source, final NavigationNodeId oldId, final NavigationNodeId newId) { if (source.equals(getNavigationNode())) { SwtViewProvider.getInstance().replaceNavigationNodeId(source, oldId, newId); } } } /* * Delegates to the SwtViewProvider to locate all View users for the given SetViewId */ /** * @since 3.0 */ protected int getViewUserCount(final SwtViewId id) { return SwtViewProvider.getInstance().getViewUsers(id).size(); } private void updateMenuBar() { final List<MenuCoolBarComposite> menuCoolBarComposites = menuBindHelper.getMenuCoolBarComposites(getShell()); for (final MenuCoolBarComposite menuBarComp : menuCoolBarComposites) { menuBarComp.updateMenuItems(); } final IController controller = (IController) getNavigationNode().getNavigationNodeController(); bindMenuAndToolItems(controller); } /** * After a sub-module node was activated, the corresponding view is shown. */ private class MySubModuleNodeListener extends SubModuleNodeListener { private boolean navigationUp = false; @Override public void prepared(final ISubModuleNode source) { if (SubModuleUtils.isPrepareView() /* && source.isSelectable() */) { checkBaseStructure(); final SwtViewId id = getViewId(source); if (id == null) { return; } final SwtViewProvider viewProvider = SwtViewProvider.getInstance(); viewProvider.setCurrentPrepared(source); prepareView(id, source); viewProvider.setCurrentPrepared(null); } } @Override public void afterActivated(final ISubModuleNode source) { updateMenuBar(); } @Override public void activated(final ISubModuleNode source) { checkBaseStructure(); if (null != source && !source.isSelectable()) { return; } final SwtViewId id = getViewId(source); prepareView(id, null); showView(id); } @Override public void disposed(final ISubModuleNode source) { try { final SwtViewId id = getViewId(source); /* * hideView internally disposes(RCP) a view if ref count is 0. For shared views this behavior is critical as RCP doesn�t know if a View is * reused the Riena way. We just hide it if Riena has no more references. For a list of references we use the SwtViewProvider#getViewUsers API. * We cannot set parts invisible as this collides with the marker concept. */ if (/* Allways hide "unshared" views */id.getSecondary() == null || !id.getSecondary().startsWith(SubModuleView.SHARED_ID) || /* no more instances needed of the given shared view */getViewUserCount(id) < 1) { hideView(id); } final SwtViewProvider viewProvider = SwtViewProvider.getInstance(); viewProvider.unregisterSwtViewId(source); } catch (final ApplicationModelFailure amf) { // not selectable SubModules dont't have an associated view, so if hiding creates an exception we can ignore it TODO : never close a not selectable node if (source.isSelectable()) { LOGGER.log(LogService.LOG_ERROR, "Error disposing node " + source + ": " + amf.getMessage()); //$NON-NLS-1$ //$NON-NLS-2$ } } } protected String createNextId() { return String.valueOf(System.currentTimeMillis()); } /** * At the very first time (a sub-module was activated), the view parts of the sub-application switcher and the navigation tree are shown. */ private void checkBaseStructure() { if (!navigationUp) { createNavigation(); navigationUp = true; } } private void createNavigation() { final String secId = createNextId(); prepareView(NavigationViewPart.ID, secId, null); showView(NavigationViewPart.ID, secId); } } /** * Returns the view ID of the given sub-module node. * * @param source * sub-module node * @return view ID * @since 3.0 */ protected SwtViewId getViewId(final ISubModuleNode node) { return SwtViewProvider.getInstance().getSwtViewId(node); } /** * Returns the currently active page. * * @return active page */ private IWorkbenchPage getActivePage() { return PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); } private IPerspectiveDescriptor getActivePerspective() { final IWorkbenchPage page = getActivePage(); if (page != null) { // get the active perspective return page.getPerspective(); } return null; } /** * Hides the view in the active page. * * @param id * the id of the view extension to use * @param secondaryId * the secondary id to use * @return {@code true} if the view was hidden successfully. * * @since 3.0 */ private boolean hideView(final String id, final String secondary) { boolean successful = false; final IViewReference viewRef = getActivePage().findViewReference(id, secondary); if (viewRef != null) { final IViewPart view = viewRef.getView(false); if (view instanceof INavigationNodeView<?>) { ((INavigationNodeView<?>) view).unbind(); } getActivePage().hideView(view); removeFromStock(new SwtViewId(id, secondary)); successful = true; } return successful; } /** * Hides the view in the active page. * * @param id * ID of the view * * @return {@code true} if the view was hidden successfully. * * @since 5.0 * */ protected boolean hideView(final SwtViewId id) { return hideView(id.getId(), id.getSecondary()); } private void showView(final SwtViewId id) { showView(id.getId(), id.getSecondary()); } /** * Shows a view in the active page. * * @param id * the id of the view extension to use * @param secondaryId * the secondary id to use */ private void showView(final String id, final String secondary) { final IWorkbenchPage page = getActivePage(); final IViewReference viewRef = page.findViewReference(id, secondary); if (viewRef != null) { WorkbenchFacade.getInstance().showView(page, viewRef); if (id != NavigationViewPart.ID) { final SwtViewId swtViewId = new SwtViewId(id, secondary); addToStock(swtViewId); hideUnusedView(); } } } /** * Hides (disposes) the longest unused view, if the maximum number of stocked views is reached. */ private void hideUnusedView() { final SwtViewId firstView = getFirstOfStock(); SwtViewId swtViewId = getLastOfStock(); while ((swtViewId != null) && (!firstView.equals(swtViewId))) { if (!hideView(swtViewId)) { // view wasn't hidden addToStock(swtViewId); } swtViewId = getLastOfStock(); } } private IViewReference prepareView(final SwtViewId id, final ISubModuleNode currentPrepared) { return prepareView(id.getId(), id.getSecondary(), currentPrepared); } /** * Returns the perspective of the given sub-application. * * @param subApp * node of the sub-application * @return perspective of sub-application or {@code null} if no perspective was found */ private IPerspectiveDescriptor getPerspectiveOfSubApplication(final ISubApplicationNode subApp) { final Object subAppIdObject = WorkareaManager.getInstance().getDefinition(getNavigationNode()).getViewId(); final String subAppId = String.valueOf(subAppIdObject); final IPerspectiveDescriptor[] perspectives = PlatformUI.getWorkbench().getPerspectiveRegistry().getPerspectives(); for (final IPerspectiveDescriptor perspective : perspectives) { if (StringUtils.equals(subAppId, perspective.getId())) { return perspective; } } return null; } /** * Prepares a view so that is can be shown. * * @param id * the id of the view extension to use * @param secondary * the secondary id to use * @param currentPrepared * @return the view reference, or <code>null</code> if none is found */ private IViewReference prepareView(final String id, final String secondary, final ISubModuleNode currentPrepared) { try { final IWorkbenchPage page = getActivePage(); IViewPart viewPart = null; if (secondary != null && secondary.startsWith(SubModuleView.SHARED_ID)) { viewPart = SwtViewProvider.getInstance().getRegisteredView(id, secondary); } // if (SubModuleView.SHARED_ID.equals(secondary)) { // viewPart = SwtViewProvider.getInstance().getRegisteredView(id); // } if (viewPart == null) { final IPerspectiveDescriptor activePerspective = getActivePerspective(); final IPerspectiveDescriptor subAppPerspective = getPerspectiveOfSubApplication(getNavigationNode()); if (subAppPerspective != null) { page.setPerspective(subAppPerspective); } else { LOGGER.log(LogService.LOG_WARNING, "No perspective found for sub application: " //$NON-NLS-1$ + getNavigationNode()); } // open view but don't activate it and don't bring it to top viewPart = page.showView(id, secondary, IWorkbenchPage.VIEW_VISIBLE); if (subAppPerspective != null) { page.setPerspective(activePerspective); } } /* * Shared views are only created once. All binding logic is done in createPartControl of the view. Prepared Nodes initially need a controller with * all ridgets injected. If showView did not instantiate a new instance we need to trigger node preparation for controller/ridget binding manually. */ if (currentPrepared != null && currentPrepared.getNavigationNodeController() == null) { if (viewPart instanceof SubModuleView) { ((SubModuleView) viewPart).prepareNode(currentPrepared); } } return page.findViewReference(id, secondary); } catch (final PartInitException exc) { final String msg = String.format("Failed to prepare/show view: %s, %s", id, secondary); //$NON-NLS-1$ LOGGER.log(0, msg, exc); } return null; } /** * After changing a source the menu bar of this sub-application is updated. */ private class MenuSourceProviderListener implements ISourceProviderListener { /** * Updates the menu bar (only if the priority is correct and this sub-application is selected). * * @param sourcePriority * A bit mask of all the source priorities that have changed. */ private void update(final int sourcePriority) { if ((sourcePriority & ISources.ACTIVE_MENU) == ISources.ACTIVE_MENU) { if (getNavigationNode().isSelected()) { updateMenuBar(); } } } public void sourceChanged(final int sourcePriority, final Map sourceValuesByName) { update(sourcePriority); } public void sourceChanged(final int sourcePriority, final String sourceName, final Object sourceValue) { update(sourcePriority); } } /** * Add the given view to the to beginning to the stock. * * @param viewId * ID of the view */ private void addToStock(final SwtViewId viewId) { removeFromStock(viewId); subModuleViewStock.addFirst(viewId); } /** * Returns the first view of the stock. * * @return ID of the first view * * @throws NoSuchElementException * if this list is empty */ private SwtViewId getFirstOfStock() { return subModuleViewStock.getFirst(); } /** * Returns the last view of the stock... * * @return ID of the last view or ... */ private SwtViewId getLastOfStock() { if (getMaxStockedViews() <= 0) { return null; } if (getMaxStockedViews() >= subModuleViewStock.size()) { return null; } return subModuleViewStock.getLast(); } /** * Removes the given view form the stock. * * @param viewId * ID of the view * @return {@code true} if the stock contained the given view */ private boolean removeFromStock(final SwtViewId viewId) { return subModuleViewStock.remove(viewId); } /** * Returns the maximal number of views that will be stocked. * * @return maximal number of stocked views; 0 for indefinitely number of stocked views */ private int getMaxStockedViews() { final String value = RienaConfiguration.getInstance().getProperty(RienaConfiguration.MAX_STOCKED_VIEWS_KEY); try { return Integer.parseInt(value); } catch (final NumberFormatException e) { return 0; } } }