blob: affb2420fd847c567b9527bf7278b6de0434160e [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2013 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() {
public Class<?>[] getAdapterList() {
return new Class[] { StandbyPart.class, IntroPartPresentation.class };
}
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());
}
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.intro.IIntroPart#init(org.eclipse.ui.intro.IIntroSite,
* org.eclipse.ui.IMemento)
*/
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)
*/
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)
*/
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();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IWorkbenchPart#setFocus()
*/
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;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.IWorkbenchPart#dispose()
*/
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;
}
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);
}
/*
* (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);
}
/**
* Support dynamic awarness.
*
* @see org.eclipse.core.runtime.IRegistryChangeListener#registryChanged(org.eclipse.core.runtime.IRegistryChangeEvent)
*/
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(new Runnable() {
public void run() {
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();
}
}