/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.gef.ui.editor;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.SafeRunner;
import org.eclipse.jface.action.IMenuListener;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.IPageChangedListener;
import org.eclipse.jface.dialogs.PageChangedEvent;
import org.eclipse.jface.util.SafeRunnable;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
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.CTabFolder;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import org.xmind.gef.EditDomain;
import org.xmind.gef.GEF;
import org.xmind.gef.command.CommandStack;
import org.xmind.gef.command.CommandStackEvent;
import org.xmind.gef.command.ICommandStack;
import org.xmind.gef.command.ICommandStackListener;
import org.xmind.gef.ui.actions.ActionRegistry;
import org.xmind.gef.ui.actions.IActionRegistry;
import org.xmind.gef.ui.actions.ICommandStackAction;
import org.xmind.gef.ui.internal.GEFPlugin;
import org.xmind.gef.util.EventListenerSupport;
import org.xmind.gef.util.IEventDispatcher;
import org.xmind.ui.tabfolder.DelegatedSelectionProvider;
import org.xmind.ui.tabfolder.IDelegatedSelectionProvider;
import org.xmind.ui.tabfolder.IPageClosedListener;
/**
* @author Brian Sun
*/
public abstract class GraphicalEditor extends EditorPart
implements IGraphicalEditor, ICommandStackListener {
protected class PageInputSelectionProvider implements ISelectionProvider {
private EventListenerSupport listeners = new EventListenerSupport();
public void addSelectionChangedListener(
ISelectionChangedListener listener) {
listeners.addListener(ISelectionChangedListener.class, listener);
}
public ISelection getSelection() {
IGraphicalEditorPage page = getActivePageInstance();
if (page != null) {
Object pageInput = page.getInput();
if (pageInput != null) {
return new StructuredSelection(pageInput);
}
}
return StructuredSelection.EMPTY;
}
public void removeSelectionChangedListener(
ISelectionChangedListener listener) {
listeners.removeListener(ISelectionChangedListener.class, listener);
}
public void setSelection(ISelection selection) {
if (selection instanceof IStructuredSelection) {
Object pageInput = ((IStructuredSelection) selection)
.getFirstElement();
if (pageInput != null) {
ensurePageVisible(pageInput);
}
}
}
protected void firePageChanged() {
fireSelectionChanged(
new SelectionChangedEvent(this, getSelection()));
}
private void fireSelectionChanged(final SelectionChangedEvent event) {
listeners.fireEvent(ISelectionChangedListener.class,
new IEventDispatcher() {
public void dispatch(Object listener) {
((ISelectionChangedListener) listener)
.selectionChanged(event);
}
});
}
}
protected class MultiPageSelectionProvider
extends DelegatedSelectionProvider {
/*
* (non-Javadoc)
* @see
* org.xmind.ui.tabfolder.DelegatedSelectionProvider#setSelection(org
* .eclipse.jface.viewers.ISelection)
*/
@Override
public void setSelection(ISelection selection) {
Object input = findOwnedInput(selection);
if (input != null) {
ensurePageVisible(input);
}
super.setSelection(selection);
}
}
private Composite container = null;
private IPageContainerPresentation containerPresentation = null;
private IPageChangedListener presentationHooker = new IPageChangedListener() {
public void pageChanged(PageChangedEvent event) {
handlePageChange(getActivePage());
}
};
private List<IGraphicalEditorPage> pages = new ArrayList<IGraphicalEditorPage>();
private EventListenerSupport listeners = new EventListenerSupport();
private ICommandStack commandStack = null;
private IMiniBar miniBar = null;
private IMiniBarContributor miniBarContributor = null;
private IActionRegistry actionRegistry = null;
private List<ICommandStackAction> csActions = null;
private int activePageIndex = -1;
private PageInputSelectionProvider pageInputSelectionProvider = null;
private MenuManager pagePopupMenu = null;
private IGlobalActionHandlerService globalActionHandlerService = null;
/*
* (non-Javadoc)
* @see org.eclipse.ui.part.EditorPart#init(org.eclipse.ui.IEditorSite,
* org.eclipse.ui.IEditorInput)
*/
@Override
public void init(IEditorSite site, IEditorInput input)
throws PartInitException {
setSite(site);
setInput(input);
site.setSelectionProvider(createSelectionProvider());
setCommandStack(createCommandStack());
}
protected ISelectionProvider createSelectionProvider() {
return new MultiPageSelectionProvider();
}
protected Object findOwnedInput(ISelection selection) {
return null;
}
protected Composite getContainer() {
return container;
}
protected IPageContainerPresentation getContainerPresentation() {
return containerPresentation;
}
protected void hookContainerPresentation() {
getContainerPresentation().addPageChangedListener(presentationHooker);
}
public void createPartControl(Composite parent) {
if (containerPresentation == null) {
containerPresentation = createContainerPresentation();
hookContainerPresentation();
}
Composite containerParent = createContainerParent(parent);
this.container = containerPresentation.createContainer(containerParent);
createEditorContents();
}
protected void createEditorContents() {
if (getContainer() instanceof CTabFolder) {
createMiniBarComposite((CTabFolder) getContainer());
createPageContextMenu((CTabFolder) getContainer());
}
}
private void createMiniBarComposite(CTabFolder tabFolder) {
final Composite composite = new Composite(getContainer(), SWT.None);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 0;
layout.marginHeight = 0;
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
composite.setLayout(layout);
createMiniBar(composite);
final ToolBar control = ((ToolBarManager) miniBar.getToolBarManager())
.getControl();
GridData controlData = new GridData(SWT.RIGHT, SWT.FILL, true, true);
control.setLayoutData(controlData);
tabFolder.setTopRight(composite, SWT.RIGHT);
}
protected IPageContainerPresentation createContainerPresentation() {
return new TabFolderContainerPresentation();
}
/**
* Creates the parent control for the container returned by
* {@link #getContainer() }.
* <p>
* Subclasses may extend and must call super implementation first.
* </p>
*
* @param parent
* the parent for all of the editors contents.
* @return the parent for this editor's container. Must not be
* <code>null</code>.
*/
protected Composite createContainerParent(Composite parent) {
parent.setLayout(new FillLayout());
return parent;
}
public void addPage(IGraphicalEditorPage page) {
page.setEditDomain(createEditDomain(page));
createPageControl(page);
pages.add(page);
}
private void createPageControl(IGraphicalEditorPage page) {
page.createPageControl(getContainer());
Assert.isNotNull(page.getControl());
Assert.isNotNull(page.getViewer());
Assert.isNotNull(page.getViewer().getControl());
addPageControl(page.getControl());
}
private void addPageControl(Control pageControl) {
int index = containerPresentation.getPageCount(getContainer());
containerPresentation.addPage(getContainer(), index, pageControl);
}
protected EditDomain createEditDomain(IGraphicalEditorPage page) {
return new EditDomain();
}
protected void disposeEditDomain(IGraphicalEditorPage page,
EditDomain editDomain) {
editDomain.dispose();
}
protected void createPageContextMenu(Composite container) {
if (pagePopupMenu == null) {
pagePopupMenu = createPagePopupMenu();
String menuId = getSite().getId() + ".page"; //$NON-NLS-1$
if (isPagePopupMenuDynamic()) {
setupDynamicPopupMenu(container, pagePopupMenu);
} else {
contributeToPagePopupMenu(pagePopupMenu);
}
registerPagePopupMenu(menuId, pagePopupMenu);
}
container.setMenu(pagePopupMenu.createContextMenu(container));
}
private void setupDynamicPopupMenu(final Composite container,
MenuManager popupMenu) {
final boolean[] showsItems = new boolean[1];
showsItems[0] = false;
popupMenu.setRemoveAllWhenShown(true);
popupMenu.addMenuListener(new IMenuListener() {
public void menuAboutToShow(IMenuManager manager) {
if (showsItems[0]) {
contributeToPagePopupMenu(manager);
}
}
});
container.addMenuDetectListener(new MenuDetectListener() {
public void menuDetected(MenuDetectEvent e) {
CTabFolder folder = (CTabFolder) container;
Point p = folder.toControl(e.x, e.y);
showsItems[0] = !(folder.getClientArea().contains(p)
|| folder.getTopRight().getBounds().contains(p));
}
});
}
protected void registerPagePopupMenu(String menuId, MenuManager menu) {
getSite().registerContextMenu(menuId, menu,
getPageInputSelectionProvider());
}
protected void contributeToPagePopupMenu(IMenuManager menu) {
IEditorActionBarContributor contributor = getEditorSite()
.getActionBarContributor();
if (contributor instanceof GraphicalEditorActionBarContributor) {
((GraphicalEditorActionBarContributor) contributor)
.contributeToPagePopupMenu(menu);
}
}
protected boolean isPagePopupMenuDynamic() {
return true;
}
protected MenuManager createPagePopupMenu() {
return new MenuManager();
}
public void removePage(IGraphicalEditorPage page) {
removePage(findPage(page));
}
protected void removePage(int pageIndex) {
Assert.isTrue(pageIndex >= 0 && pageIndex < getPageCount());
boolean wasActivePage = pageIndex == getActivePage();
IGraphicalEditorPage page = getPage(pageIndex);
containerPresentation.disposePage(getContainer(), pageIndex);
if (page != null) {
page.dispose();
}
pages.remove(page);
if (wasActivePage) {
if (pageIndex == getPageCount())
pageIndex--;
setActivePage(pageIndex);
}
firePageClosed(page);
}
public IGraphicalEditorPage getPage(int pageIndex) {
if (pageIndex < 0 || pageIndex >= getPageCount())
return null;
return pages.get(pageIndex);
}
public int findPage(IGraphicalEditorPage page) {
return pages.indexOf(page);
}
protected void postSave() {
if (getCommandStack() != null) {
getCommandStack().markSaved();
}
firePropertyChange(PROP_DIRTY);
}
public int getPageCount() {
return pages.size();
}
public boolean isDirty() {
return getCommandStack() != null && getCommandStack().isDirty();
}
/**
* Creates the mini bar on the part control.
* <p>
* <b>IMPORTANT:</b> This mini bar contribution relies on the fact that the
* page container is a CTabFolder, so it may do nothing if the
* implementation changes.
* </p>
*
* @see #getContainer()
*/
protected final void createMiniBar(Composite parent) {
if (!(getContainer() instanceof CTabFolder))
return;
miniBar = new MiniBar();
initializeMiniBar(miniBar);
if (!((MiniBar) miniBar).isEmpty()) {
createMiniBarControl(miniBar, (CTabFolder) getContainer(), parent);
}
}
/**
* @param miniBar
*/
private void initializeMiniBar(IMiniBar miniBar) {
if (getMiniBarContributor() != null) {
getMiniBarContributor().init(miniBar, this);
}
}
/**
* Creates the mini bar's control on the specified tab folder.
*
* @param miniBar
* @param tabFolder
*/
private void createMiniBarControl(IMiniBar miniBar, CTabFolder tabFolder,
Composite parent) {
((ToolBarManager) miniBar.getToolBarManager()).createControl(parent);
}
public IMiniBarContributor getMiniBarContributor() {
return miniBarContributor;
}
public void setMiniBarContributor(IMiniBarContributor miniBarContributor) {
this.miniBarContributor = miniBarContributor;
}
/**
* @return commandStack
*/
public ICommandStack getCommandStack() {
return commandStack;
}
protected ICommandStack createCommandStack() {
return new CommandStack();
}
public void handleCommandStackEvent(CommandStackEvent event) {
if ((event.getStatus() & GEF.CS_POST_MASK) != 0
|| event.getStatus() == GEF.CS_UPDATED) {
getSite().getShell().getDisplay().asyncExec(new Runnable() {
public void run() {
firePropertyChange(PROP_DIRTY);
}
});
}
}
@Deprecated
protected void fireDirty() {
firePropertyChange(PROP_DIRTY);
}
/**
* @param commandStack
* the commandStack to set
*/
public void setCommandStack(ICommandStack commandStack) {
ICommandStack oldCS = this.commandStack;
if (commandStack == oldCS)
return;
this.commandStack = commandStack;
commandStackChanged(oldCS, commandStack);
}
protected void commandStackChanged(ICommandStack oldCS,
ICommandStack newCS) {
if (oldCS != null) {
unhookCommandStack(oldCS);
}
if (newCS != null) {
hookCommandStack(newCS);
}
for (IGraphicalEditorPage page : getPages()) {
EditDomain domain = page.getEditDomain();
if (domain != null) {
domain.setCommandStack(newCS);
}
}
if (csActions != null) {
for (ICommandStackAction action : csActions) {
action.setCommandStack(newCS);
}
}
firePropertyChange(PROP_DIRTY);
}
protected void hookCommandStack(ICommandStack cs) {
cs.addCSListener(this);
}
protected void unhookCommandStack(ICommandStack cs) {
cs.removeCSListener(this);
}
@SuppressWarnings("unchecked")
public <T> T getAdapter(Class<T> adapter) {
Object activePage = getSelectedPage();
if (activePage != null) {
T result = GEFPlugin.getAdapter(activePage, adapter);
if (result != null)
return result;
}
T result = getEditorAdapter(adapter);
if (result != null)
return result;
return super.getAdapter(adapter);
}
protected <T> T getEditorAdapter(Class<T> adapter) {
if (adapter.isInstance(this))
return adapter.cast(this);
if (adapter == ICommandStack.class)
return adapter.cast(getCommandStack());
if (adapter == IActionRegistry.class)
return adapter.cast(getActionRegistry());
if (adapter == IMiniBar.class)
return adapter.cast(miniBar);
if (adapter == IMiniBarContributor.class)
return adapter.cast(getMiniBarContributor());
if (adapter == IGlobalActionHandlerService.class) {
if (globalActionHandlerService == null) {
globalActionHandlerService = new GlobalActionHandlerService(
this);
}
return adapter.cast(globalActionHandlerService);
}
if (adapter == IGlobalActionHandlerUpdater.class) {
IEditorActionBarContributor contributor = getEditorSite()
.getActionBarContributor();
if (contributor instanceof IGlobalActionHandlerUpdater) {
return adapter.cast(contributor);
}
return null;
}
return null;
}
public Object getSelectedPage() {
return getActivePageInstance();
}
public IGraphicalEditorPage getActivePageInstance() {
return getPage(getActivePage());
}
public void addPageChangedListener(IPageChangedListener listener) {
listeners.addListener(IPageChangedListener.class, listener);
}
public void removePageChangedListener(IPageChangedListener listener) {
listeners.removeListener(IPageChangedListener.class, listener);
}
protected void firePageChanged(Object page) {
final PageChangedEvent event = new PageChangedEvent(this, page);
listeners.fireEvent(IPageChangedListener.class, new IEventDispatcher() {
public void dispatch(Object listener) {
((IPageChangedListener) listener).pageChanged(event);
}
});
}
protected void firePageClosed(final Object page) {
listeners.fireEvent(IPageChangedListener.class, new IEventDispatcher() {
public void dispatch(Object listener) {
if (listener instanceof IPageClosedListener) {
((IPageClosedListener) listener).pageClosed(page);
}
}
});
}
@SuppressWarnings("deprecation")
protected void handlePageChange(int newPageIndex) {
boolean wasFocused = false;
IGraphicalEditorPage oldActivePage = getPage(activePageIndex);
if (oldActivePage != null && oldActivePage.isActive()) {
wasFocused = oldActivePage.isFocused();
// EditDomain editDomain = oldActivePage.getEditDomain();
// if (editDomain != null) {
// editDomain.setActiveTool(GEF.TOOL_DEFAULT);
// }
oldActivePage.setActive(false);
}
this.activePageIndex = newPageIndex;
IGraphicalEditorPage activePage = getPage(newPageIndex);
if (activePage != null && !activePage.isActive()) {
activePage.setActive(true);
}
if (wasFocused) {
activePage.setFocus();
}
IWorkbenchPage page = getSite().getPage();
if (page != null) {
//Only when this part is current active part, configure editor by use of active editor page.
IWorkbenchPart currentActivePart = page.getActivePart();
if (currentActivePart == this) {
IEditorActionBarContributor contributor = getEditorSite()
.getActionBarContributor();
if (contributor != null
&& contributor instanceof GraphicalEditorActionBarContributor) {
((GraphicalEditorActionBarContributor) contributor)
.setActivePage(activePage);
}
}
}
ISelectionProvider selectionProvider = getSite().getSelectionProvider();
if (selectionProvider instanceof IDelegatedSelectionProvider) {
((IDelegatedSelectionProvider) selectionProvider)
.setDelegate(activePage == null ? null
: activePage.getSelectionProvider());
}
if (getMiniBarContributor() != null) {
getMiniBarContributor().setActivePage(activePage);
}
if (pageInputSelectionProvider != null) {
Object pageInput = activePage == null ? null
: activePage.getInput();
pageInputSelectionProvider
.setSelection(pageInput == null ? StructuredSelection.EMPTY
: new StructuredSelection(pageInput));
}
firePageChanged(activePage);
}
public void setFocus() {
setFocus(getActivePage());
}
protected void setFocus(int pageIndex) {
if (pageIndex < 0 || pageIndex >= getPageCount()) {
container.setFocus();
} else {
IGraphicalEditorPage page = getPage(pageIndex);
if (page != null) {
page.setFocus();
} else {
Control control = containerPresentation
.getPageControl(getContainer(), pageIndex);
if (control != null && !control.isDisposed()) {
control.setFocus();
}
}
}
}
public void movePageTo(int oldIndex, int newIndex) {
IGraphicalEditorPage activePage = getActivePageInstance();
boolean wasActive = oldIndex == getActivePage();
pages.add(newIndex, pages.remove(oldIndex));
for (int i = 0; i < pages.size(); i++) {
IGraphicalEditorPage page = pages.get(i);
boolean wasFocused = page.isFocused();
containerPresentation.setPageControl(getContainer(), i,
page.getControl());
if (wasFocused)
page.setFocus();
page.updatePageTitle();
}
if (wasActive) {
pages.get(newIndex).getControl().setVisible(true);
setActivePage(newIndex);
}
if (activePage != null) {
containerPresentation.setActivePage(getContainer(),
pages.indexOf(activePage));
}
}
public String getPageText(int pageIndex) {
return containerPresentation.getPageText(getContainer(), pageIndex);
}
public void setPageText(int pageIndex, String text) {
if (text == null)
text = ""; //$NON-NLS-1$
containerPresentation.setPageText(getContainer(), pageIndex, text);
}
public int getActivePage() {
return containerPresentation.getActivePage(getContainer());
}
public void setActivePage(int pageIndex) {
// Assert.isTrue(pageIndex < getPageCount());
if (pageIndex < 0 || pageIndex >= getPageCount())
return;
if (pageIndex >= 0) {
containerPresentation.setActivePage(getContainer(), pageIndex);
} else {
containerPresentation.setActivePage(getContainer(),
containerPresentation.getPageCount(getContainer()) - 1);
}
handlePageChange(pageIndex);
}
public IGraphicalEditorPage[] getPages() {
return pages.toArray(new IGraphicalEditorPage[pages.size()]);
}
protected ISelectionProvider getPageInputSelectionProvider() {
if (pageInputSelectionProvider == null) {
pageInputSelectionProvider = new PageInputSelectionProvider();
}
return pageInputSelectionProvider;
}
protected IActionRegistry getActionRegistry() {
if (actionRegistry == null)
actionRegistry = new ActionRegistry();
return actionRegistry;
}
@Override
public void dispose() {
if (csActions != null) {
for (ICommandStackAction action : csActions) {
action.setCommandStack(null);
}
csActions = null;
}
if (actionRegistry != null) {
actionRegistry.dispose();
actionRegistry = null;
}
if (pagePopupMenu != null) {
pagePopupMenu.dispose();
pagePopupMenu = null;
}
if (commandStack != null && !commandStack.isDisposed()) {
disposeCommandStack(commandStack);
commandStack = null;
}
disposePages();
super.dispose();
}
protected void disposeCommandStack(ICommandStack commandStack) {
commandStack.dispose();
}
private void disposePages() {
if (pages.isEmpty())
return;
for (final Object o : pages.toArray()) {
SafeRunner.run(new SafeRunnable() {
public void run() throws Exception {
IGraphicalEditorPage page = (IGraphicalEditorPage) o;
page.dispose();
EditDomain editDomain = page.getEditDomain();
if (editDomain != null) {
disposeEditDomain(page, editDomain);
}
}
});
}
pages.clear();
}
/**
* @param sourceEvent
* @return
*/
public IGraphicalEditorPage findPage(Object input) {
for (IGraphicalEditorPage page : getPages()) {
Object pageInput = page.getInput();
if (pageInput == input
|| (input != null && input.equals(pageInput)))
return page;
}
return null;
}
public IGraphicalEditorPage ensurePageVisible(Object input) {
IGraphicalEditorPage page = findPage(input);
if (page != null) {
if (page != getActivePageInstance()) {
setActivePage(page.getIndex());
page = getActivePageInstance();
}
}
return page;
}
public boolean navigateTo(Object input, Object... elements) {
IGraphicalEditorPage page = ensurePageVisible(input);
if (page != null) {
if (elements == null)
return true;
ISelectionProvider viewer = page.getSelectionProvider();
if (viewer != null) {
viewer.setSelection(new StructuredSelection(elements));
return true;
}
}
return false;
}
protected void addCommandStackAction(ICommandStackAction action) {
if (csActions == null)
csActions = new ArrayList<ICommandStackAction>();
csActions.add(action);
}
}