/******************************************************************************* * 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.intro.config; import org.eclipse.core.runtime.IAdapterFactory; import org.eclipse.core.runtime.IRegistryChangeEvent; import org.eclipse.core.runtime.IRegistryChangeListener; import org.eclipse.core.runtime.PerformanceStats; import org.eclipse.core.runtime.Platform; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IMemento; import org.eclipse.ui.IWorkbenchPreferenceConstants; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; 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.IntroModelRoot; import org.eclipse.ui.internal.intro.impl.model.IntroPartPresentation; import org.eclipse.ui.internal.intro.impl.model.loader.ContentProviderManager; 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.parts.StandbyPart; import org.eclipse.ui.internal.intro.impl.presentations.BrowserIntroPartImplementation; import org.eclipse.ui.internal.intro.impl.util.DialogUtil; import org.eclipse.ui.internal.intro.impl.util.Log; import org.eclipse.ui.internal.intro.impl.util.ReopenUtil; import org.eclipse.ui.intro.IIntroSite; import org.eclipse.ui.part.IntroPart; /** * A re-usable intro part that the Eclipse platform uses for its Out of the Box * Experience. It is a customizable intro part where both its presentation, and * its content can be customized based on a configuration. Both are contributed * using the org.eclipse.ui.intro.config extension point. There are two * presentations: an SWT browser based presentation, and a UI forms * presentation. Based on the configuration, one is chosen on startup. If a * Browser based presentation is selected, and the intro is being loaded on a * platform that does not support the SWT Browser control, the default behavior * is to degrade to UI forms presentation. Content displayed in this intro part * can be static or dynamic. Static is html files, dynamic is markup in content * files. Again, both of which can be specified using the above extension point. * <p> * Memento Support: This intro part tries to restore its previous state when * possible. The state of the intro page is remembered, along with which standby * content content part was opened. IStandbyContent parts are passed the Intro's * memento shortly after construction, and are expected to restore there own * state based on the memento. The customizable intro part handles there initial * creation on load, and leaves restoring state to content part. Same with * saving state. The memento is passed shortly before shutdown to enable storing * of part specific data. * * Note: This class was made public for re-use, as-is, as a valid class for the * <code>org.eclipse.ui.intro</code> extension point. It is not intended to be * subclassed or used otherwise. * </p> * * @since 3.0 */ /* * Internal docs: This class wraps a presentation part and a standby part. The * standby part is only shown when an IntroURL asks to show standby or when we * are restarting an Intro with an old memento. An internal data object * "showStandbyPart" is set on the control by the intro URL to signal standby * part needed. This data is nulled when the close button on the standby part is * clicked, signaling that the standby part is no longer needed. */ public final class CustomizableIntroPart extends IntroPart implements IRegistryChangeListener { private IntroPartPresentation presentation; private StandbyPart standbyPart; private Composite container; private IMemento memento; IntroModelRoot model; // this flag is used to recreate a cached standby part. It is used once and // the set to false when the standby part is first created. private boolean restoreStandby; // Adapter factory to abstract out the StandbyPart implementation from APIs. IAdapterFactory factory = new IAdapterFactory() { @Override public Class<?>[] getAdapterList() { return new Class[] { StandbyPart.class, IntroPartPresentation.class }; } @Override public <T> T getAdapter(Object adaptableObject, Class<T> adapterType) { if (!(adaptableObject instanceof CustomizableIntroPart)) return null; if (adapterType.equals(StandbyPart.class)) { return adapterType.cast(getStandbyPart()); } else if (adapterType.equals(IntroPartPresentation.class)) { return adapterType.cast(getPresentation()); } else return null; } }; public CustomizableIntroPart() { // register adapter to hide standbypart. Platform.getAdapterManager().registerAdapters(factory, CustomizableIntroPart.class); // model can not be loaded here because the configElement of this part // is still not loaded here. // if we are logging performance, start the UI creation start time. // Clock stops at the end of the standbyStateChanged event. if (Log.logPerformance) { if (PerformanceStats.ENABLED) PerformanceStats.getStats( IIntroConstants.PERF_VIEW_CREATION_TIME, IIntroConstants.INTRO).startRun(); else // capture start time to be used when only Intro performance // trace // is turned on. IntroPlugin.getDefault().setUICreationStartTime( System.currentTimeMillis()); } } @Override public void init(IIntroSite site, IMemento memento) throws PartInitException { super.init(site, memento); IntroPlugin.getDefault().closeLaunchBar(); // load the correct model based in the current Intro Part id. Set the // IntroPartId in the manager class. String introId = getConfigurationElement().getAttribute("id"); //$NON-NLS-1$ ExtensionPointManager extensionPointManager = IntroPlugin.getDefault() .getExtensionPointManager(); extensionPointManager.setIntroId(introId); model = extensionPointManager.getCurrentModel(); if (model != null && model.hasValidConfig()) { boolean startAtHomePage = ReopenUtil.isReopenPreference(); if (startAtHomePage) { PlatformUI.getPreferenceStore().setValue( IWorkbenchPreferenceConstants.SHOW_INTRO, true); memento = null; } // we have a valid config contribution, get presentation. Make sure // you pass correct memento. presentation = model.getPresentation(); if (presentation != null) presentation.init(this, getMemento(memento, IIntroConstants.MEMENTO_PRESENTATION_TAG)); // standby part is not created here for performance. // cache memento, and detemine if we have a cached standby part. this.memento = memento; restoreStandby = needToRestoreStandby(memento); // add the registry listerner for dynamic awarness. Platform.getExtensionRegistry().addRegistryChangeListener(this, IIntroConstants.PLUGIN_ID); } if (model == null || !model.hasValidConfig()) DialogUtil.displayErrorMessage(site.getShell(), Messages.CustomizableIntroPart_configNotFound, new Object[] { ModelLoaderUtil.getLogString( getConfigurationElement(), null) }, null); } /** * Creates the UI based on how the InroPart has been configured. * * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite) */ @Override public void createPartControl(Composite parent) { container = new Composite(parent, SWT.NULL); StackLayout layout = new StackLayout(); layout.marginHeight = 0; layout.marginWidth = 0; container.setLayout(layout); if (model != null && model.hasValidConfig()) { presentation.createPartControl(container); // do not create the standby part here for performance. } if (Log.logPerformance) { PerformanceStats stats = PerformanceStats.getStats( IIntroConstants.PERF_UI_ZOOM, IIntroConstants.INTRO); stats.startRun(); } } /** * Determine if we need to recreate a standby part. Return true if we have a * standby part memento that is not for the empty part AND stangby memento * has been tagged for restore, ie: it was open when workbench closed. * * @param memento * @return <code>true</code> if we need to recreate a standby part */ private boolean needToRestoreStandby(IMemento memento) { // If we have a standby memento, it means we closed with standby open, // and so recreate it. IMemento standbyMemento = getMemento(memento, IIntroConstants.MEMENTO_STANDBY_PART_TAG); if (standbyMemento == null) return false; String restore = standbyMemento.getString(IIntroConstants.MEMENTO_RESTORE_ATT); if (restore == null) return false; String cachedStandbyPart = standbyMemento .getString(IIntroConstants.MEMENTO_STANDBY_CONTENT_PART_ID_ATT); if (cachedStandbyPart != null && cachedStandbyPart.equals(IIntroConstants.EMPTY_STANDBY_CONTENT_PART)) return false; return cachedStandbyPart != null ? true : false; } /* * Handled state changes. Recreates the standby part if workbench was shut * down with one. * * @see org.eclipse.ui.IIntroPart#standbyStateChanged(boolean) */ @Override public void standbyStateChanged(boolean standby) { // do this only if there is a valid config. if (model == null || !model.hasValidConfig()) return; if (!standby) // we started of not in standby, no need to restore standby. restoreStandby = false; boolean isStandbyPartNeeded = isStandbyPartNeeded(); isStandbyPartNeeded = isStandbyPartNeeded | restoreStandby; try { if (standbyPart == null && standby && isStandbyPartNeeded) // if standby part is not created yet, create it only if in // standby, and we need to. createStandbyPart(); handleSetFocus(isStandbyPartNeeded); setTopControl(isStandbyPartNeeded ? getStandbyControl() : getPresentationControl()); // triger state change in presentation to enable/disable toobar // actions. For this, we need to disable actions as long as we are in // standby, or we need to show standby part. presentation.standbyStateChanged(standby, isStandbyPartNeeded); } catch (RuntimeException e) { Log.error("Exception thrown in intro", e); //$NON-NLS-1$ } } /** * Returns true if we need to show the standby part. False in all other * cases. This basically overrides the workbench behavior of Standby/normal * states. The design here is that if the showStandbyPart flag is set, then * we always need to show the standby part. * * @return <code>true</code> if we need to show the standby part */ private boolean isStandbyPartNeeded() { return container.getData(IIntroConstants.SHOW_STANDBY_PART) == null ? false : true; } /* * Create standby part. Called only when really needed. We reset the restore * falg, but we need to tag the intro part as needing standby. */ private void createStandbyPart() { standbyPart = new StandbyPart(model); standbyPart.init(this, getMemento(memento, IIntroConstants.MEMENTO_STANDBY_PART_TAG)); standbyPart.createPartControl((Composite) getControl()); restoreStandby = false; container.setData(IIntroConstants.SHOW_STANDBY_PART, "true"); //$NON-NLS-1$ } private void handleSetFocus(boolean standby) { if (standby) { // standby part is null when Intro has not gone into standby state // yet. if (standbyPart != null) standbyPart.setFocus(); } else presentation.setFocus(); } @Override public void setFocus() { handleSetFocus(IntroPlugin.isIntroStandby()); } private void setTopControl(Control c) { // container has stack layout. safe to cast. StackLayout layout = (StackLayout) container.getLayout(); layout.topControl = c; container.layout(); } private Control getPresentationControl() { return container.getChildren()[0]; } private Control getStandbyControl() { // the Container top control may have only one child if the standby // part is not created yet. This happens if the intro never goes into // standby. Doing this for performance. if (standbyPart != null) return container.getChildren()[1]; return null; } IntroPartPresentation getPresentation() { return presentation; } @Override public void dispose() { super.dispose(); // call dispose on both parts. if (presentation != null) presentation.dispose(); if (standbyPart != null) standbyPart.dispose(); // clear all loaded models since we are disposing of the Intro Part. IntroPlugin.getDefault().getExtensionPointManager().clear(); ContentProviderManager.getInst().clear(); // clean platform adapter. Platform.getAdapterManager().unregisterAdapters(factory, CustomizableIntroPart.class); if (model != null && model.hasValidConfig()) Platform.getExtensionRegistry().removeRegistryChangeListener(this); } /** * @return Returns the standbyPart. */ StandbyPart getStandbyPart() { return standbyPart; } /** * Returns the primary control associated with this Intro part. * * @return the SWT control which displays this Intro part's content, or * <code>null</code> if this standby part's controls have not yet * been created. */ public Control getControl() { return container; } @Override public void saveState(IMemento memento) { // give presentation and standby part there own children to create a // name space for each. But save either the presentation or the standby // part as needing to be restored. This way if we close with a standby // mode, we dont get the cached standby part. // Find out if presentation or standby is at the top and restore // them. Container has stack layout. safe to cast. boolean restorePresentation = false; StackLayout layout = (StackLayout) container.getLayout(); if (getPresentationControl().equals(layout.topControl)) restorePresentation = true; IMemento presentationMemento = memento .createChild(IIntroConstants.MEMENTO_PRESENTATION_TAG); IMemento standbyPartMemento = memento .createChild(IIntroConstants.MEMENTO_STANDBY_PART_TAG); if (restorePresentation) presentationMemento.putString(IIntroConstants.MEMENTO_RESTORE_ATT, "true"); //$NON-NLS-1$ else standbyPartMemento.putString(IIntroConstants.MEMENTO_RESTORE_ATT, "true"); //$NON-NLS-1$ if (presentation != null) presentation.saveState(presentationMemento); if (standbyPart != null) standbyPart.saveState(standbyPartMemento); } private IMemento getMemento(IMemento memento, String key) { if (memento == null) return null; return memento.getChild(key); } /** * Support dynamic awarness. * * @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent) */ @Override public void registryChanged(final IRegistryChangeEvent event) { // Clear cached models first, then update UI by delegating to // implementation. wrap in synchExec because notification is // asynchronous. The design here is that the notification is centralized // here, then this event propagates and each receiving class reacts // accordingly. Display.getDefault().syncExec(() -> { String currentPageId = model.getCurrentPageId(); // clear model, including content providers. ExtensionPointManager.getInst().clear(); ContentProviderManager.getInst().clear(); // refresh to new model. model = ExtensionPointManager.getInst().getCurrentModel(); // reuse existing presentation, since we just nulled it. model.setPresentation(getPresentation()); // keep same page on refresh. No need for notification here. model.setCurrentPageId(currentPageId, false); if (getPresentation() != null) getPresentation().registryChanged(event); }); } /* * Internal test hook (Non-API). */ public boolean internal_isFinishedLoading() { BrowserIntroPartImplementation impl = (BrowserIntroPartImplementation)presentation.getIntroPartImplementation(); return impl.isFinishedLoading(); } }