/*******************************************************************************
* Copyright (c) 2009 Fraunhofer IWU 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:
* Fraunhofer IWU - initial API and implementation
*******************************************************************************/
package net.enilink.commons.ui.editor;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.forms.widgets.ScrolledPageBook;
/**
* This managed form part handles the 'details' portion of the 'master/details'
* block. It has a page book that manages pages of details registered for the
* current selection.
* <p>
* By default, details part accepts any number of pages. If dynamic page
* provider is registered, this number may be excessive. To avoid running out of
* steam (by creating a large number of pages with widgets on each), maximum
* number of pages can be set to some reasonable value (e.g. 10). When this
* number is reached, old pages (those created first) will be removed and
* disposed as new ones are added. If the disposed pages are needed again after
* that, they will be created again.
*/
public class EditorPartBook extends AbstractEditorPart implements
ISelectionChangedListener {
private Composite pageBook;
private IStructuredSelection lastSelection, currentSelection;
private Map<Object, PartBag> parts;
private IEditorPartProvider partProvider;
private int partLimit = Integer.MAX_VALUE;
private boolean commitIfDirty;
private int style = SWT.NONE;
private static class PartBag {
private static int counter;
private int ticket;
private IEditorPart part;
private boolean fixed;
public PartBag(IEditorPart part, boolean fixed) {
this.part = part;
this.fixed = fixed;
this.ticket = ++counter;
}
public int getTicket() {
return ticket;
}
public IEditorPart getPart() {
return part;
}
public void dispose() {
part.dispose();
part = null;
}
public boolean isDisposed() {
return part == null;
}
public boolean isFixed() {
return fixed;
}
public static int getCurrentTicket() {
return counter;
}
}
/**
* Creates a details part by wrapping the provided page book.
*
* @param mform
* the parent form
* @param scrolledPageBook
* the page book to wrap
*/
public EditorPartBook(boolean commitIfDirty) {
this(commitIfDirty, SWT.V_SCROLL | SWT.H_SCROLL);
}
public EditorPartBook(boolean commitIfDirty, int style) {
this.commitIfDirty = commitIfDirty;
this.style = style;
parts = new HashMap<Object, PartBag>();
}
/**
* Registers the details part to be used for all the objects of the provided
* object class.
*
* @param objectClass
* an object of type 'java.lang.Class' to be used as a key for
* the provided page
* @param page
* the page to show for objects of the provided object class
*/
public void registerPart(Class<?> objectClass, IEditorPart page) {
registerPart(objectClass, page, true);
}
private void registerPart(Object objectClass, IEditorPart page,
boolean fixed) {
parts.put(objectClass, new PartBag(page, fixed));
page.initialize(getForm());
}
/**
* Sets the dynamic page provider. The dynamic provider can return different
* pages for objects of the same class based on their state.
*
* @param provider
* the provider to use
*/
public void setPartProvider(IEditorPartProvider provider) {
this.partProvider = provider;
}
/**
* Commits the part by committing the current page.
*
* @param onSave
* <code>true</code> if commit is requested as a result of the
* 'save' action, <code>false</code> otherwise.
*/
public void commit(boolean onSave) {
IEditorPart page = getCurrentPart();
if (page != null) {
commitPart(currentSelection, page, false);
}
}
/**
* Returns the current page visible in the part.
*
* @return the current page
*/
public IEditorPart getCurrentPart() {
Control control = pageBook instanceof ScrolledPageBook ? ((ScrolledPageBook) pageBook)
.getCurrentPage()
: ((PageBook) pageBook).getCurrentPage();
if (control != null) {
Object data = control.getData();
if (data instanceof IEditorPart)
return (IEditorPart) data;
}
return null;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.ui.forms.IFormPart#dispose()
*/
public void dispose() {
for (PartBag pageBag : parts.values()) {
pageBag.dispose();
}
}
/**
* Tests if the currently visible page is dirty.
*
* @return <code>true</code> if the page is dirty, <code>false</code>
* otherwise.
*/
public boolean isDirty() {
IEditorPart page = getCurrentPart();
if (page != null) {
return page.isDirty();
}
return false;
}
/**
* Tests if the currently visible page is stale and needs refreshing.
*
* @return <code>true</code> if the page is stale, <code>false</code>
* otherwise.
*/
public boolean isStale() {
IEditorPart page = getCurrentPart();
if (page != null) {
return page.isStale();
}
return false;
}
/**
* Refreshes the current page.
*/
public void refresh() {
IEditorPart page = getCurrentPart();
if (page != null) {
page.refresh();
}
}
/**
* Sets the focus to the currently visible page.
*/
public boolean setFocus() {
IEditorPart page = getCurrentPart();
if (page != null) {
page.setFocus();
return true;
}
return false;
}
public Control getControl() {
return pageBook;
}
public boolean setEditorInput(Object input) {
return false;
}
public void selectionChanged(SelectionChangedEvent event) {
lastSelection = currentSelection;
selectionChanged(event.getSelection());
}
public void selectionChanged(ISelection selection) {
if (selection instanceof IStructuredSelection) {
currentSelection = (IStructuredSelection) selection;
} else {
currentSelection = null;
}
update();
}
private void update() {
Object key = null;
if (currentSelection != null && !currentSelection.isEmpty()) {
Object firstElement = currentSelection.getFirstElement();
if (firstElement != null) {
key = getKey(firstElement);
}
}
showPart(key);
}
private Object getKey(Object object) {
if (partProvider != null) {
Object key = partProvider.getPartKey(object);
if (key != null) {
return key;
}
}
return object.getClass();
}
private void showPart(final Object key) {
checkLimit();
final IEditorPart oldPart = getCurrentPart();
if (key != null) {
PartBag pageBag = (PartBag) parts.get(key);
IEditorPart part = pageBag != null ? pageBag.getPart() : null;
if (part == null) {
// try to get the page dynamically from the provider
if (partProvider != null) {
part = partProvider.getPart(key);
if (part != null) {
registerPart(key, part, false);
}
}
}
if (part != null) {
final IEditorPart fpart = part;
BusyIndicator.showWhile(pageBook.getDisplay(), new Runnable() {
public void run() {
if (!hasPage(key)) {
Composite parent = createPage(key);
fpart.createContents(parent);
parent.setData(fpart);
}
// commit the current part
if (commitIfDirty && oldPart != null
&& oldPart.isDirty()) {
commitPart(lastSelection, oldPart, false);
}
fpart.setInput(currentSelection.getFirstElement());
if (oldPart != null) {
oldPart.deactivate();
}
fpart.activate();
// refresh the new part
refreshPart(fpart);
showPage(key);
}
});
return;
}
}
// If we are switching from an old page to nothing,
// don't loose data
if (commitIfDirty && oldPart != null && oldPart.isDirty()) {
commitPart(lastSelection, oldPart, false);
}
if (oldPart != null) {
oldPart.deactivate();
}
showEmptyPage();
}
private Composite createPage(Object key) {
if (pageBook instanceof ScrolledPageBook) {
return ((ScrolledPageBook) pageBook).createPage(key);
}
return ((PageBook) pageBook).createPage(key);
}
private boolean hasPage(Object key) {
if (pageBook instanceof ScrolledPageBook) {
return ((ScrolledPageBook) pageBook).hasPage(key);
}
return ((PageBook) pageBook).hasPage(key);
}
private void showEmptyPage() {
if (pageBook instanceof ScrolledPageBook) {
((ScrolledPageBook) pageBook).showEmptyPage();
} else {
((PageBook) pageBook).showEmptyPage();
}
}
private void showPage(Object key) {
if (pageBook instanceof ScrolledPageBook) {
((ScrolledPageBook) pageBook).showPage(key);
} else {
((PageBook) pageBook).showPage(key);
}
}
private void removePage(Object key, boolean showEmptyPage) {
if (pageBook instanceof ScrolledPageBook) {
((ScrolledPageBook) pageBook).removePage(key, showEmptyPage);
} else {
((PageBook) pageBook).removePage(key, showEmptyPage);
}
}
protected void refreshPart(IEditorPart detailsPart) {
detailsPart.refresh();
}
protected void commitPart(ISelection selection, IEditorPart detailsPart,
boolean onSave) {
detailsPart.commit(onSave);
}
private void checkLimit() {
if (parts.size() <= getPartLimit())
return;
// overflow
int currentTicket = PartBag.getCurrentTicket();
int cutoffTicket = currentTicket - getPartLimit();
for (Map.Entry<Object, PartBag> entry : parts.entrySet()) {
Object key = entry.getKey();
PartBag pageBag = entry.getValue();
if (pageBag.getTicket() <= cutoffTicket) {
// candidate - see if it is active and not fixed
if (!pageBag.isFixed()
&& !pageBag.getPart().equals(getCurrentPart())) {
// drop it
pageBag.dispose();
parts.remove(key);
removePage(key, false);
}
}
}
}
/**
* Returns the maximum number of pages that should be maintained in this
* part. When an attempt is made to add more pages, old pages are removed
* and disposed based on the order of creation (the oldest pages are
* removed). The exception is made for the page that should otherwise be
* disposed but is currently active.
*
* @return maximum number of pages for this part
*/
public int getPartLimit() {
return partLimit;
}
/**
* Sets the page limit for this part.
*
* @see #getPartLimit()
* @param partLimit
* the maximum number of pages that should be maintained in this
* part.
*/
public void setPartLimit(int partLimit) {
this.partLimit = partLimit;
checkLimit();
}
@Override
public void createContents(Composite parent) {
if ((style & (SWT.V_SCROLL | SWT.H_SCROLL)) != 0) {
this.pageBook = getWidgetFactory().createScrolledPageBook(parent,
style);
} else {
this.pageBook = getWidgetFactory().createPageBook(parent, style);
}
}
@Override
public void setInput(Object input) {
if (input != null) {
selectionChanged(new StructuredSelection(input));
} else {
selectionChanged(StructuredSelection.EMPTY);
}
}
}