/******************************************************************************* * Copyright (c) 2004, 2016 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.ui.internal.intro.impl.parts; import java.util.HashMap; import java.util.Map; import org.eclipse.osgi.util.NLS; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Layout; import org.eclipse.ui.IMemento; import org.eclipse.ui.forms.events.HyperlinkAdapter; import org.eclipse.ui.forms.events.HyperlinkEvent; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ImageHyperlink; import org.eclipse.ui.internal.intro.impl.IIntroConstants; import org.eclipse.ui.internal.intro.impl.IntroPlugin; import org.eclipse.ui.internal.intro.impl.Messages; import org.eclipse.ui.internal.intro.impl.model.AbstractIntroPage; import org.eclipse.ui.internal.intro.impl.model.IntroModelRoot; import org.eclipse.ui.internal.intro.impl.model.IntroStandbyContentPart; import org.eclipse.ui.internal.intro.impl.model.loader.ExtensionPointManager; import org.eclipse.ui.internal.intro.impl.model.loader.ModelLoaderUtil; import org.eclipse.ui.internal.intro.impl.util.ImageUtil; import org.eclipse.ui.internal.intro.impl.util.Log; import org.eclipse.ui.internal.intro.impl.util.StringUtil; import org.eclipse.ui.intro.IIntroPart; import org.eclipse.ui.intro.config.CustomizableIntroPart; import org.eclipse.ui.intro.config.IStandbyContentPart; /** * Standby part is responsible for managing and creating IStandbycontent parts. * It knows how to create and cache content parts. It also handles saving and * restoring its own state. It does that by caching the id of the last content * part viewed and recreating that part on startup. It also manages the life * cycle of content parts by creating and initializing them at the right * moments. It also passes the momento at appropriate times to these content * parts to enable storing and retrieving of state by content parts. Content * parts are responsible for recreating there own state, including input, from * the passed momemnto. When the Return to Introduction link is clicked, the * Intro goes out of standby content mode, and the standby content parts are not * shown anymore until the user explicitly asks for a part again. This is * accomplished through a data flag on the CustomizableIntroPart control. * */ public class StandbyPart implements IIntroConstants { private FormToolkit toolkit; private IntroModelRoot model; protected ImageHyperlink returnLink; protected Control separator; private Composite container; protected Composite content; private IIntroPart introPart; private EmptyStandbyContentPart emptyPart; private IMemento memento; private Map<String, ControlKey> cachedContentParts = new HashMap<>(); private ControlKey cachedControlKey; class StandbyLayout extends Layout { private int VGAP = 9; private int VMARGIN = 5; private int HMARGIN = 5; private int SEPARATOR_HEIGHT = 1; @Override protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { Point lsize = returnLink.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache); Point csize = content.computeSize(SWT.DEFAULT, SWT.DEFAULT, flushCache); int width = Math.max(lsize.x + 2 * HMARGIN, csize.x); int height = VMARGIN + lsize.y + VGAP + SEPARATOR_HEIGHT + csize.y; return new Point(width, height); } @Override protected void layout(Composite composite, boolean flushCache) { Rectangle carea = composite.getClientArea(); int lwidth = carea.width - HMARGIN * 2; Point lsize = returnLink.computeSize(lwidth, SWT.DEFAULT, flushCache); int x = HMARGIN; int y = VMARGIN; returnLink.setBounds(x, y, lsize.x, lsize.y); x = 0; y += lsize.y + VGAP; separator.setBounds(x, y, carea.width, SEPARATOR_HEIGHT); y += SEPARATOR_HEIGHT; content.setBounds(x, y, carea.width, carea.height - VMARGIN - lsize.y - VGAP - SEPARATOR_HEIGHT); } } /** * @param parent */ public StandbyPart(IntroModelRoot model) { this.model = model; } public void init(IIntroPart introPart, IMemento memento) { this.introPart = introPart; this.memento = memento; } /* * (non-Javadoc) * * @see org.eclipse.ui.intro.IIntroPart#saveState(org.eclipse.ui.IMemento) */ private IMemento getMemento(IMemento memento, String key) { if (memento == null) return null; return memento.getChild(key); } public void createPartControl(Composite parent) { toolkit = new FormToolkit(parent.getDisplay()); // parent container. Has custom layout. Has return link and content // stack composite. container = toolkit.createComposite(parent); container.setLayout(new StandbyLayout()); // return hyper link. ImageUtil.registerImage(ImageUtil.BACK, "full/elcl16/home_nav.png"); //$NON-NLS-1$ returnLink = toolkit.createImageHyperlink(container, SWT.WRAP | SWT.CENTER); returnLink.setImage(ImageUtil.getImage(ImageUtil.BACK)); returnLink.addHyperlinkListener(new HyperlinkAdapter() { @Override public void linkActivated(HyperlinkEvent e) { doReturn(); } }); // create horizontal separator separator = toolkit.createCompositeSeparator(container); // content stack container content = toolkit.createComposite(container); StackLayout slayout = new StackLayout(); slayout.marginWidth = slayout.marginHeight = 0; content.setLayout(slayout); boolean success = false; if (memento != null) { success = restoreState(memento); if (!success) // add empty standby content. addAndShowEmptyPart(Messages.StandbyPart_canNotRestore); } updateReturnLinkLabel(); } /** * Empty content part used as backup for failures. * */ private void addAndShowEmptyPart(String message) { if (emptyPart == null) emptyPart = new EmptyStandbyContentPart(); addStandbyContentPart(EMPTY_STANDBY_CONTENT_PART, emptyPart); emptyPart.setMessage(message); setTopControl(EMPTY_STANDBY_CONTENT_PART); } /** * Tries to create the last content part viewed, based on content part id.. * * @param memento * @return */ private boolean restoreState(IMemento memento) { String contentPartId = memento .getString(MEMENTO_STANDBY_CONTENT_PART_ID_ATT); if (contentPartId == null) return false; // create the cached content part. Content parts are responsible for // storing and reading their input state. return showContentPart(contentPartId, null); } /** * Sets the into part to standby, and shows the passed standby part, with * the given input. * * @param partId * @param input */ public boolean showContentPart(String partId, String input) { // Get the IntroStandbyContentPart that maps to the given partId. IntroStandbyContentPart standbyPartContent = ExtensionPointManager .getInst().getSharedConfigExtensionsManager() .getStandbyPart(partId); if (standbyPartContent != null) { String standbyContentClassName = standbyPartContent.getClassName(); String pluginId = standbyPartContent.getPluginId(); Object standbyContentObject = ModelLoaderUtil.createClassInstance( pluginId, standbyContentClassName); if (standbyContentObject instanceof IStandbyContentPart) { IStandbyContentPart contentPart = (IStandbyContentPart) standbyContentObject; Control c = addStandbyContentPart(partId, contentPart); if (c != null) { try { setTopControl(partId); setInput(input); return true; } catch (Exception e) { Log.error("Failed to set the input: " + input //$NON-NLS-1$ + " on standby part: " + partId, e); //$NON-NLS-1$ } } // failed to create the standby part, show empty part and signal // failure. String message = NLS.bind(Messages.StandbyPart_failedToCreate, partId); addAndShowEmptyPart(message); return false; } } // no content part defined with the passed partId, show empty part and // signal failure. String message = NLS.bind(Messages.StandbyPart_nonDefined, partId); addAndShowEmptyPart(message); return false; } /** * Creates a standbyContent part in the stack only if one is not already * created. The partId is used as tke key in the cache. The value is an * instance of ControlKey that wraps a control/StandbyPart pair along with * the corresponding part id. This is needed to retrive the control of a * given standby part. The IMemento should be passed to the StandbyPart when * it is initialized. * * @param standbyContent */ public Control addStandbyContentPart(String partId, IStandbyContentPart standbyContent) { ControlKey controlKey = getCachedContent(partId); if (controlKey == null) { try { standbyContent.init(introPart, getMemento(memento, MEMENTO_STANDBY_CONTENT_PART_TAG)); standbyContent.createPartControl(content, toolkit); } catch (Exception e) { // a standby content part throws a PartInitException, log fact. Log.error( "Failed to create part for standby part: " + partId, e); //$NON-NLS-1$ return null; } Control control = standbyContent.getControl(); controlKey = new ControlKey(control, standbyContent, partId); cachedContentParts.put(partId, controlKey); if (partId.equals(EMPTY_STANDBY_CONTENT_PART)) // just in case it was created explicity, reuse it. emptyPart = (EmptyStandbyContentPart) standbyContent; if (controlKey.getControl() == null) { // control is null. This means that interface was not // implemented properly. log fact. String message = StringUtil .concat("Standby Content part: ", partId, //$NON-NLS-1$ " has a null Control defined. This prevents the part from being displayed.") //$NON-NLS-1$ .toString(); Log.error(message, null); return null; } } return controlKey.getControl(); } public void setInput(Object input) { IStandbyContentPart standbyContent = cachedControlKey.getContentPart(); standbyContent.setInput(input); updateReturnLinkLabel(); container.layout(); } public void setTopControl(String key) { cachedControlKey = getCachedContent(key); if (cachedControlKey != null) { setTopControl(cachedControlKey.getControl()); } } private void setTopControl(Control c) { StackLayout layout = (StackLayout) content.getLayout(); layout.topControl = c; if (c instanceof Composite) ((Composite) c).layout(); content.layout(); container.layout(); } private void updateReturnLinkLabel() { String linkText = Messages.StandbyPart_returnToIntro; returnLink.setText(linkText); AbstractIntroPage page = model.getCurrentPage(); if (page == null) // page will be null in static intro. return; String toolTip = Messages.StandbyPart_returnTo; if (page.getTitle() != null) toolTip += " " + page.getTitle(); //$NON-NLS-1$ returnLink.setToolTipText(toolTip); } protected void doReturn() { // remove the flag to indicate that standbypart is no longer needed. ((CustomizableIntroPart) introPart).getControl().setData( IIntroConstants.SHOW_STANDBY_PART, null); IntroPlugin.setIntroStandby(false); } /** * Calls dispose on all cached IStandbyContentParts. * */ public void dispose() { for (ControlKey controlKey : cachedContentParts.values()) { controlKey.getContentPart().dispose(); } toolkit.dispose(); } /** * Save the current state of the standby part. It stores the cached content * part id for later creating it on restart. It also creates another * subclass momento to also give the standby content part its own name * space. This was momentos saved by different content parts will not * conflict. * * @param memento * the memento in which to store state information */ public void saveState(IMemento memento) { // save cached content part id. if (cachedControlKey != null) { String contentPartId = cachedControlKey.getContentPartId(); if (contentPartId == EMPTY_STANDBY_CONTENT_PART) // do not create memento for empty standby. return; memento.putString(MEMENTO_STANDBY_CONTENT_PART_ID_ATT, contentPartId); // give standby part its own child to create a name space for // IStandbyPartContent contribution momentos. IMemento standbyContentPartMemento = memento .createChild(MEMENTO_STANDBY_CONTENT_PART_TAG); // pass new memento to correct standby part. IStandbyContentPart standbyContentpart = cachedControlKey .getContentPart(); if (standbyContentpart != null) standbyContentpart.saveState(standbyContentPartMemento); } } /* * Set focus on the IStandbyContentPart that corresponds to the top control * in the stack. * * @see org.eclipse.ui.internal.intro.impl.parts.IStandbyContentPart#setFocus() */ public void setFocus() { // grab foxus first, then delegate. This way if content part does // nothing on focus, part still works. returnLink.setFocus(); if (cachedControlKey != null) cachedControlKey.getContentPart().setFocus(); } /** * Checks the standby cache stack if we have already created a similar * IStandbyContentPart. If not, returns null. * * @param standbyContent * @return */ private ControlKey getCachedContent(String key) { if (cachedContentParts.containsKey(key)) return (ControlKey) cachedContentParts.get(key); return null; } /* * Model class to wrap Control and IStandbyContentPart pairs, along with the * representing ID.. */ class ControlKey { Control c; IStandbyContentPart part; String contentPartId; ControlKey(Control c, IStandbyContentPart part, String contentPartId) { this.c = c; this.part = part; this.contentPartId = contentPartId; } /** * @return Returns the c. */ public Control getControl() { return c; } /** * @return Returns the content part. */ public IStandbyContentPart getContentPart() { return part; } /** * @return Returns the part id. */ public String getContentPartId() { return contentPartId; } } }