| /******************************************************************************* |
| * 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(); |
| } |
| } |