blob: 24a0b475a2e8e4cdb0efa654ebb237ecd0429f8b [file] [log] [blame]
/*******************************************************************************
* 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.gif"); //$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;
}
}
}