/* ******************************************************************************
* 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.ui.internal.dialogs;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.draw2d.FigureCanvas;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.viewers.ArrayContentProvider;
import org.eclipse.jface.viewers.IFontProvider;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.LabelProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.jface.window.IShellProvider;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
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.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.part.PageBook;
import org.xmind.core.IRevision;
import org.xmind.core.ISheet;
import org.xmind.gef.EditDomain;
import org.xmind.gef.GEF;
import org.xmind.gef.event.KeyEvent;
import org.xmind.gef.service.IRevealService;
import org.xmind.gef.tool.BrowsingTool;
import org.xmind.gef.tool.ITool;
import org.xmind.ui.gallery.NavigationViewer;
import org.xmind.ui.internal.MindMapUIPlugin;
import org.xmind.ui.internal.mindmap.MindMapRevealService;
import org.xmind.ui.internal.mindmap.MindMapViewer;
import org.xmind.ui.mindmap.IMindMapImages;
import org.xmind.ui.mindmap.IMindMapViewer;
import org.xmind.ui.mindmap.MindMap;
import org.xmind.ui.mindmap.MindMapUI;
import org.xmind.ui.resources.FontUtils;
import org.xmind.ui.util.Cancelable;
import org.xmind.ui.util.ICancelable;
import org.xmind.ui.viewers.SWTUtils;
/**
* @author Frank Shaka
*/
public class RevisionPreviewDialog extends Dialog {
private class RevisionMindMapViewer extends MindMapViewer {
protected Control internalCreateControl(Composite parent, int style) {
FigureCanvas canvas = new FigureCanvas(parent, style,
getLightweightSystem());
canvas.setViewport(getViewport());
return canvas;
}
}
private static final String USE_STORED_SIZE = "USE_STORED_SIZE"; //$NON-NLS-1$
private static final IShellProvider NO_PARENT_SHELL = new IShellProvider() {
public Shell getShell() {
return null;
}
};
private class DefaultPreviewTool extends BrowsingTool {
/*
* (non-Javadoc)
* @see
* org.xmind.gef.tool.AbstractTool#handleKeyDown(org.xmind.gef.event
* .KeyEvent)
*/
@Override
protected boolean handleKeyDown(KeyEvent ke) {
if (SWTUtils.matchKey(ke.getState(), ke.keyCode, 0,
SWT.ARROW_LEFT)) {
asyncExec(new Runnable() {
public void run() {
setIndex(index - 1);
}
});
} else if (SWTUtils.matchKey(ke.getState(), ke.keyCode, 0,
SWT.ARROW_RIGHT)) {
asyncExec(new Runnable() {
public void run() {
setIndex(index + 1);
}
});
}
return super.handleKeyDown(ke);
}
}
private class ContainerLayout extends Layout {
/*
* (non-Javadoc)
* @see
* org.eclipse.swt.widgets.Layout#computeSize(org.eclipse.swt.widgets
* .Composite, int, int, boolean)
*/
@Override
protected org.eclipse.swt.graphics.Point computeSize(
Composite composite, int wHint, int hHint, boolean flushCache) {
if (wHint < 0 || hHint < 0) {
Control[] children = composite.getChildren();
int w = Math.max(0, wHint);
int h = Math.max(0, hHint);
for (int i = 0; i < children.length; i++) {
Control child = children[i];
Point childSize = child.getSize();
w = Math.max(w, childSize.x);
h = Math.max(h, childSize.y);
}
return new org.eclipse.swt.graphics.Point(w, h);
}
return new org.eclipse.swt.graphics.Point(wHint, hHint);
}
/*
* (non-Javadoc)
* @see org.eclipse.swt.widgets.Layout#layout(org.eclipse.swt.widgets.
* Composite , boolean)
*/
@Override
protected void layout(Composite composite, boolean flushCache) {
Rectangle area = composite.getClientArea();
int h = NavigationViewer.BIG_HEIGHT + 20;
pageBook.setBounds(area.x, area.y, area.width, area.height - h);
navBar.getControl().setBounds(area.x, area.y + area.height - h,
area.width, h);
}
}
private static class NavigationLabelProvider extends LabelProvider
implements IFontProvider {
private Font font;
private Image image = null;
/**
*
*/
public NavigationLabelProvider() {
this.font = FontUtils.getNewHeight(JFaceResources.DEFAULT_FONT, 32);
}
/*
* (non-Javadoc)
* @see
* org.eclipse.jface.viewers.LabelProvider#getText(java.lang.Object)
*/
@Override
public String getText(Object element) {
if (element instanceof IRevision) {
IRevision revision = (IRevision) element;
return String.valueOf(revision.getRevisionNumber());
}
return ""; //$NON-NLS-1$
}
@Override
public Image getImage(Object element) {
if (element instanceof ISheet) {
return getSheetImage();
}
return super.getImage(element);
}
private Image getSheetImage() {
if (image == null) {
image = MindMapUI.getImages()
.get(IMindMapImages.DEFAULT_THUMBNAIL).createImage();
}
return image;
}
/*
* (non-Javadoc)
* @see
* org.eclipse.jface.viewers.IFontProvider#getFont(java.lang.Object)
*/
public Font getFont(Object element) {
return font;
}
@Override
public void dispose() {
if (image != null) {
image.dispose();
image = null;
}
super.dispose();
}
}
private class NavigationSelectionChangedListener
implements ISelectionChangedListener {
private ICancelable updater = null;
/*
* (non-Javadoc)
* @see
* org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged
* (org.eclipse.jface.viewers.SelectionChangedEvent)
*/
public void selectionChanged(SelectionChangedEvent event) {
if (updater != null) {
updater.cancel();
}
final ISelection selection = event.getSelection();
if (selection.isEmpty())
return;
updater = new Cancelable() {
@Override
protected void doJob() {
updateSelection(selection);
}
};
Display.getCurrent().timerExec(150, updater);
}
private void updateSelection(ISelection selection) {
Object sel = ((IStructuredSelection) selection).getFirstElement();
if (sel == sourceSheet) {
setIndex(revisions.size());
} else {
setIndex(revisions.indexOf(sel));
}
}
}
private Shell parentShell;
private ISheet sourceSheet;
private List<IRevision> revisions;
private int index;
private PageBook pageBook;
private NavigationViewer navBar = null;
private Map<Object, MindMapViewer> viewers = new HashMap<Object, MindMapViewer>();
private MindMapViewer viewer = null;
private Control corruptionWarning = null;
private Rectangle actualBounds = null;
private Listener widgetListener = new Listener() {
public void handleEvent(Event event) {
if ((event.type == SWT.Traverse
&& event.detail == SWT.TRAVERSE_ESCAPE)
|| (event.type == SWT.KeyDown
&& (SWTUtils.matchKey(event.stateMask,
event.keyCode, 0, SWT.ESC)
|| SWTUtils.matchKey(event.stateMask,
event.keyCode, 0, SWT.SPACE)))) {
close();
}
}
};
/**
* @param parentShell
* @param revisions
*/
public RevisionPreviewDialog(Shell parentShell, ISheet sourceSheet,
List<IRevision> revisions, int index) {
super(NO_PARENT_SHELL);
this.parentShell = parentShell;
this.sourceSheet = sourceSheet;
this.revisions = revisions;
this.index = index;
setShellStyle(SWT.DIALOG_TRIM | SWT.MAX | SWT.MIN | SWT.RESIZE
| getDefaultOrientation());
setBlockOnOpen(false);
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.dialogs.Dialog#isResizable()
*/
@Override
protected boolean isResizable() {
return true;
}
public void update() {
Object selection = getSelection();
updateShellTitle(selection);
showPreviewViewer(selection);
updateNavBar(selection);
}
/**
*
*/
private void updateNavBar(Object selection) {
navBar.setSelection(new StructuredSelection(selection));
navBar.getControl().setFocus();
hookWidget(navBar.getControl());
}
private Object getSelection() {
if (index < 0)
return null;
if (index >= revisions.size()) {
return sourceSheet;
}
return revisions.get(index);
}
private void updateShellTitle(Object selection) {
String sheetTitle = String.format("\"%s - %s\"", //$NON-NLS-1$
sourceSheet.getTitleText(),
sourceSheet.getRootTopic().getTitleText());
String title;
if (selection instanceof IRevision) {
title = NLS.bind(
DialogMessages.RevisionPreviewDialog_Revision_titlePattern,
String.valueOf(((IRevision) selection).getRevisionNumber()),
sheetTitle);
} else {
title = NLS.bind(
DialogMessages.RevisionPreviewDialog_CurrentRevision_title,
sheetTitle);
}
getShell().setText(title);
}
/*
* (non-Javadoc)
* @see
* org.eclipse.jface.dialogs.Dialog#createContents(org.eclipse.swt.widgets
* .Composite)
*/
@Override
protected Control createContents(Composite parent) {
Color background = parent.getDisplay()
.getSystemColor(SWT.COLOR_LIST_BACKGROUND);
parent.setBackground(background);
Composite container = new Composite(parent, SWT.NONE);
container.setLayout(new ContainerLayout());
container.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
container.setBackground(background);
pageBook = new PageBook(container, SWT.NONE);
pageBook.setBackground(background);
hookWidget(pageBook);
createNavigationBar(container);
return pageBook;
}
/**
* @param control
*/
private void hookWidget(Control control) {
control.addListener(SWT.Traverse, widgetListener);
control.addListener(SWT.KeyDown, widgetListener);
}
/**
* @param container
* @return
*/
private void createNavigationBar(Composite parent) {
navBar = new NavigationViewer();
navBar.setContentProvider(new ArrayContentProvider());
navBar.setLabelProvider(new NavigationLabelProvider());
navBar.createControl(parent);
Object[] elements = new Object[revisions.size() + 1];
revisions.toArray(elements);
elements[elements.length - 1] = sourceSheet;
navBar.setInput(elements);
navBar.addSelectionChangedListener(
new NavigationSelectionChangedListener());
GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
gridData.heightHint = 0;
gridData.verticalAlignment = 0;
gridData.verticalIndent = 0;
gridData.verticalSpan = 0;
navBar.getControl();
hookWidget(navBar.getControl());
}
private void showPreviewViewer(Object selection) {
if (selection instanceof IRevision) {
IRevision revision = (IRevision) selection;
ISheet sheet = (ISheet) revision.getContent();
if (sheet == null) {
pageBook.showPage(getCorruptionWarning());
} else {
viewer = getRevisionViewer(revision, sheet);
pageBook.showPage(viewer.getControl());
}
} else {
viewer = getRevisionViewer(sourceSheet, sourceSheet);
pageBook.showPage(viewer.getControl());
}
}
private Control getCorruptionWarning() {
if (corruptionWarning == null) {
corruptionWarning = createCorruptionWarning();
}
return corruptionWarning;
}
/**
* @return
*/
private Control createCorruptionWarning() {
Composite composite = new Composite(pageBook, SWT.NONE);
GridLayout layout = new GridLayout(1, false);
layout.marginWidth = 5;
layout.marginHeight = 5;
layout.verticalSpacing = 0;
layout.horizontalSpacing = 0;
composite.setLayout(layout);
Label label = new Label(composite, SWT.NONE);
label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
label.setText(
DialogMessages.RevisionPreviewDialog_CorruptedRevision_message);
return composite;
}
private MindMapViewer getRevisionViewer(Object selection, ISheet sheet) {
MindMapViewer viewer = viewers.get(selection);
if (viewer == null) {
viewer = createViewer(pageBook, sheet);
viewers.put(selection, viewer);
}
return viewer;
}
public MindMapViewer createViewer(Composite parent, ISheet sheet) {
MindMapViewer viewer = new RevisionMindMapViewer();
initViewer(viewer);
viewer.createControl(parent);
viewer.getCanvas().setScrollBarVisibility(FigureCanvas.AUTOMATIC);
hookWidget(viewer.getControl());
viewer.setInput(new MindMap(sheet));
return viewer;
}
public void initViewer(MindMapViewer viewer) {
viewer.getProperties().set(IMindMapViewer.VIEWER_CENTERED, true);
viewer.getProperties().set(IMindMapViewer.VIEWER_MARGIN, 50);
viewer.getProperties().set(IMindMapViewer.VIEWER_CORNERED, true);
IRevealService revealService = new MindMapRevealService(viewer);
viewer.installService(IRevealService.class, revealService);
revealService.setActive(true);
ITool tool = new DefaultPreviewTool();
EditDomain editDomain = new EditDomain();
editDomain.installTool(GEF.TOOL_DEFAULT, tool);
editDomain.setDefaultTool(GEF.TOOL_DEFAULT);
viewer.setEditDomain(editDomain);
}
@Override
protected IDialogSettings getDialogBoundsSettings() {
return MindMapUIPlugin.getDefault()
.getDialogSettings("org.xmind.ui.RevisionsDialog"); //$NON-NLS-1$
}
protected Point getInitialSize() {
IDialogSettings settings = getDialogBoundsSettings();
if (settings.getBoolean(USE_STORED_SIZE)) {
return super.getInitialSize();
}
settings.put(USE_STORED_SIZE, true);
return new Point(720, 620);
}
private void setIndex(int index) {
index = Math.max(0, Math.min(revisions.size(), index));
if (index == this.index)
return;
this.index = index;
update();
}
private void asyncExec(final Runnable job) {
parentShell.getDisplay().asyncExec(new Runnable() {
public void run() {
if (getShell() == null || getShell().isDisposed())
return;
job.run();
}
});
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.window.Window#open()
*/
@Override
public int open() {
if (getShell() == null || getShell().isDisposed()) {
create();
}
constrainShellSize();
update();
// open the window
getShell().open();
return OK;
}
public void open(Rectangle sourceBounds) {
if (getShell() == null || getShell().isDisposed()) {
create();
}
constrainShellSize();
Shell shell = getShell();
actualBounds = shell.getBounds();
shell.setRedraw(false);
shell.setBounds(sourceBounds);
shell.setAlpha(0);
shell.setVisible(true);
shell.setActive();
shell.setFocus();
long start = System.currentTimeMillis();
long end = start + 200;
animateOpening(shell, sourceBounds, start, end);
}
/**
* @param shell
* @param r1
* @param r2
*/
private void animateOpening(final Shell shell, final Rectangle r1,
final long start, final long end) {
if (shell.isDisposed() || actualBounds == null)
return;
long time = System.currentTimeMillis();
if (time > end) {
finishOpening(shell);
} else {
double percent = ((double) (time - start))
/ ((double) (end - start));
int alpha = (int) (255 * percent);
Rectangle r2 = actualBounds;
int x = (int) ((r2.x - r1.x) * percent + r1.x);
int y = (int) ((r2.y - r1.y) * percent + r1.y);
int width = (int) ((r2.width - r1.width) * percent + r1.width);
int height = (int) ((r2.height - r1.height) * percent + r1.height);
shell.setAlpha(alpha);
shell.setBounds(x, y, width, height);
shell.getDisplay().timerExec(5, new Runnable() {
public void run() {
animateOpening(shell, r1, start, end);
}
});
}
}
/**
* @param shell
* @param targetBounds
*/
private void finishOpening(Shell shell) {
if (shell.isDisposed() || actualBounds == null)
return;
shell.setBounds(actualBounds);
shell.setAlpha(255);
shell.setVisible(true);
shell.setActive();
shell.setFocus();
update();
shell.setRedraw(true);
actualBounds = null;
}
/*
* (non-Javadoc)
* @see org.eclipse.jface.dialogs.Dialog#close()
*/
@Override
public boolean close() {
Shell shell = getShell();
if (shell != null && !shell.isDisposed() && actualBounds != null) {
shell.setBounds(actualBounds);
actualBounds = null;
}
return super.close();
}
}