/* ******************************************************************************
* 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.viewers;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
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.viewers.Viewer;
import org.eclipse.osgi.util.NLS;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
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.Canvas;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ToolBar;
import org.xmind.ui.internal.ToolkitImages;
public class ImagePreviewViewer {
protected static final int PREF_WIDTH = 300;
protected static final int PREF_HEIGHT = 180;
protected static final double MAX_RATIO = 5.0d;
protected static final double MIN_RATIO = 0.1d;
protected static final double MID_RATIO = 1.0d;
protected static final int BORDER_WIDTH = 1;
protected static final int STEPPING_DISTANCE = 5;
protected static final int TEXT_MARGIN = 3;
private final class PreviewSliderContentProvider implements
ISliderContentProvider {
public double getRatio(Object input, Object value) {
double v = ((Double) value).doubleValue();
if (v < MID_RATIO) {
return (v - getMinRatio()) / (MID_RATIO - getMinRatio()) / 2;
}
return (v - MID_RATIO) / (getMaxRatio() - MID_RATIO) / 2 + 0.5d;
}
public Object getValue(Object input, double ratio) {
double v;
if (ratio > 0.476 && ratio < 0.515) {
v = MID_RATIO;
} else if (ratio < 0.5) {
v = ratio * 2 * (MID_RATIO - getMinRatio()) + getMinRatio();
} else {
v = (ratio - 0.5) * 2 * (getMaxRatio() - MID_RATIO) + MID_RATIO;
}
return Double.valueOf(v);
}
// public Object[] getValues(Object input) {
// return new Double[] { Double.valueOf(getMinRatio()),
// Double.valueOf(MID_RATIO), Double.valueOf(getMaxRatio()) };
// }
public void dispose() {
}
public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
}
}
private static final class PreviewSliderLabelProvider extends LabelProvider {
public String getText(Object element) {
if (element instanceof Double) {
double v = ((Double) element).doubleValue();
return NLS.bind("{0}%", (int) (v * 100)); //$NON-NLS-1$
}
return super.getText(element);
}
}
private class ZoomInAction extends Action {
public ZoomInAction() {
super(null, ToolkitImages.get(ToolkitImages.ZOOM_IN, true));
setDisabledImageDescriptor(ToolkitImages.get(ToolkitImages.ZOOM_IN,
false));
setToolTipText(Messages.ZoomIn_toolTip);
}
public void run() {
zoomIn();
setFocus();
}
}
private class ZoomOutAction extends Action {
public ZoomOutAction() {
super(null, ToolkitImages.get(ToolkitImages.ZOOM_OUT, true));
setDisabledImageDescriptor(ToolkitImages.get(
ToolkitImages.ZOOM_OUT, false));
setToolTipText(Messages.ZoomOut_toolTip);
}
public void run() {
zoomOut();
setFocus();
}
}
private boolean fill;
private int prefWidth = PREF_WIDTH;
private int prefHeight = PREF_HEIGHT;
private double maxRatio = MAX_RATIO;
private double minRatio = MIN_RATIO;
private double x = 0;
private double y = 0;
private double ratio = 1.0d;
private Image image;
private Composite composite;
private Canvas canvas;
private Point startLoc;
private double startX;
private double startY;
private SliderViewer slider;
private IAction zoomInAction;
private IAction zoomOutAction;
private boolean updatingRatioSelection = false;
private boolean disabled = false;
private String title = null;
private int titlePlacement = 0;
private ISelectionChangedListener sliderListener = new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
if (updatingRatioSelection)
return;
ISelection selection = event.getSelection();
Object element = ((IStructuredSelection) selection)
.getFirstElement();
if (element instanceof Double) {
changeRatio(((Double) element).doubleValue());
setFocus();
}
}
};
public ImagePreviewViewer() {
this(false);
}
public ImagePreviewViewer(boolean fill) {
this.fill = fill;
}
public void createControl(Composite parent) {
composite = new Composite(parent, SWT.NONE);
GridLayout layout = new GridLayout();
layout.marginHeight = 0;
layout.marginWidth = 0;
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
composite.setLayout(layout);
Composite composite2 = new Composite(composite, SWT.NONE);
GridLayout layout2 = new GridLayout();
layout2.marginHeight = 0;
layout2.marginWidth = 0;
layout2.horizontalSpacing = 0;
layout2.verticalSpacing = 10;
composite2.setLayout(layout2);
composite2.setLayoutData(new GridData(fill ? GridData.FILL
: GridData.CENTER, fill ? GridData.FILL : GridData.CENTER,
true, true));
createCanvas(composite2);
createRatioControls(composite2);
}
public void setBackgroundColor(Color color) {
if (slider != null && !slider.getControl().isDisposed()) {
slider.getControl().setBackground(color);
}
}
protected void createCanvas(Composite parent) {
canvas = new Canvas(parent, SWT.DOUBLE_BUFFERED);
GridData layoutData = new GridData();
layoutData.horizontalAlignment = GridData.FILL;
layoutData.verticalAlignment = GridData.FILL;
layoutData.grabExcessHorizontalSpace = true;
layoutData.grabExcessVerticalSpace = true;
layoutData.widthHint = getPrefWidth() + BORDER_WIDTH + BORDER_WIDTH;
layoutData.heightHint = getPrefHeight() + BORDER_WIDTH + BORDER_WIDTH;
layoutData.minimumWidth = layoutData.widthHint;
layoutData.minimumHeight = layoutData.heightHint;
canvas.setLayoutData(layoutData);
hookCanvas(canvas, new Listener() {
public void handleEvent(Event event) {
handleCanvasEvent(event);
}
});
canvas.setCursor(parent.getDisplay().getSystemCursor(SWT.CURSOR_HAND));
updateCanvas();
}
protected void createRatioControls(Composite parent) {
Composite bar = new Composite(parent, SWT.NONE);
GridData layoutData = new GridData(SWT.CENTER, SWT.CENTER, false, false);
layoutData.widthHint = getPrefWidth();
bar.setLayoutData(layoutData);
GridLayout layout = new GridLayout(3, false);
layout.marginWidth = 30;
layout.horizontalSpacing = 1;
layout.marginHeight = 0;
bar.setLayout(layout);
ToolBar zoomOutBar = new ToolBar(bar, SWT.FLAT);
zoomOutBar.setLayoutData(new GridData(SWT.RIGHT, GridData.CENTER,
false, false));
if (zoomOutAction == null) {
zoomOutAction = new ZoomOutAction();
}
new ActionContributionItem(zoomOutAction).fill(zoomOutBar, 0);
slider = new SliderViewer(bar, SWT.HORIZONTAL);
GridData sliderLayoutData = new GridData(GridData.CENTER, GridData.CENTER,
false, false);
sliderLayoutData.widthHint = 200;
sliderLayoutData.minimumWidth = 80;
slider.getControl().setLayoutData(sliderLayoutData);
slider.setContentProvider(new PreviewSliderContentProvider());
slider.setLabelProvider(new PreviewSliderLabelProvider());
slider.setInput(this);
updateRatioSelection(ratio);
slider.addSelectionChangedListener(sliderListener);
ToolBar zoomInBar = new ToolBar(bar, SWT.FLAT);
zoomInBar.setLayoutData(new GridData(SWT.LEFT, GridData.CENTER, false,
false));
if (zoomInAction == null) {
zoomInAction = new ZoomInAction();
}
new ActionContributionItem(zoomInAction).fill(zoomInBar, 0);
updateRatioControls();
}
private void updateCanvas() {
if (canvas != null && !canvas.isDisposed()) {
canvas.setEnabled(getImage() != null && !isDisabled());
}
}
protected void updateRatioControls() {
boolean ratioControlEnabled = getImage() != null && !isDisabled();
if (zoomOutAction != null)
zoomOutAction.setEnabled(ratioControlEnabled);
if (zoomInAction != null)
zoomInAction.setEnabled(ratioControlEnabled);
if (slider != null && !slider.getControl().isDisposed()) {
slider.getControl().setEnabled(ratioControlEnabled);
}
}
protected void hookCanvas(Canvas canvas, Listener listener) {
canvas.addListener(SWT.Paint, listener);
canvas.addListener(SWT.MouseDown, listener);
canvas.addListener(SWT.MouseMove, listener);
canvas.addListener(SWT.MouseUp, listener);
canvas.addListener(SWT.KeyDown, listener);
}
protected void handleCanvasEvent(Event event) {
switch (event.type) {
case SWT.Paint:
paintCanvas(event);
break;
case SWT.MouseDown:
if (event.button == 1) {
startDragging(event);
}
break;
case SWT.MouseUp:
if (event.button == 1) {
endDragging(event);
}
break;
case SWT.MouseMove:
if ((event.stateMask & SWT.BUTTON_MASK) == SWT.BUTTON1) {
drag(event);
}
break;
case SWT.KeyDown:
handleKeyDown(event);
break;
}
}
protected void handleKeyDown(Event event) {
int keyCode = event.keyCode;
int stateMask = event.stateMask;
if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.ARROW_UP)) {
moveUp();
} else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.ARROW_DOWN)) {
moveDown();
} else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.ARROW_LEFT)) {
moveLeft();
} else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.ARROW_RIGHT)) {
moveRight();
} else if (SWTUtils.matchKey(stateMask, keyCode, 0, SWT.TAB)) {
((Control) event.widget).traverse(SWT.TRAVERSE_TAB_NEXT);
} else if (SWTUtils.matchKey(stateMask, keyCode, SWT.SHIFT, SWT.TAB)) {
((Control) event.widget).traverse(SWT.TRAVERSE_TAB_PREVIOUS);
}
}
public void moveUp() {
move(x, y, 0, -STEPPING_DISTANCE);
}
public void moveDown() {
move(x, y, 0, STEPPING_DISTANCE);
}
public void moveLeft() {
move(x, y, -STEPPING_DISTANCE, 0);
}
public void moveRight() {
move(x, y, STEPPING_DISTANCE, 0);
}
private void paintCanvas(Event event) {
GC gc = event.gc;
Rectangle area = canvas.getClientArea();
drawImage(gc, area);
drawTitle(gc, area);
gc.setClipping(area);
gc.setForeground(event.display.getSystemColor(SWT.COLOR_GRAY));
gc.drawRectangle(area.x, area.y, area.width - 1, area.height - 1);
}
private void drawTitle(GC gc, Rectangle area) {
if (title == null)
return;
gc.setFont(composite.getFont());
gc.setForeground(composite.getForeground());
Point size = gc.stringExtent(title);
int x, y;
if ((titlePlacement & SWT.LEFT) != 0) {
x = area.x + TEXT_MARGIN;
} else if ((titlePlacement & SWT.RIGHT) != 0) {
x = area.x + area.width - size.x - TEXT_MARGIN;
} else {
x = area.x + (area.width - size.x) / 2;
}
if ((titlePlacement & SWT.TOP) != 0) {
y = area.y + TEXT_MARGIN;
} else if ((titlePlacement & SWT.BOTTOM) != 0) {
y = area.y + area.height - size.y - TEXT_MARGIN;
} else {
y = area.y + (area.height - size.y) / 2;
}
gc.drawString(title, x, y, true);
}
protected void drawImage(GC gc, Rectangle area) {
if (image != null && !image.isDisposed()) {
drawImage(gc, area, image, image.getBounds());
}
}
private void drawImage(GC gc, Rectangle area, Image image, Rectangle imgSize) {
double srcWidth = Math.min(imgSize.width, area.width / ratio);
double srcHeight = Math.min(imgSize.height, area.height / ratio);
double srcX = Math.max(0, Math.min(imgSize.width - srcWidth, x));
double srcY = Math.max(0, Math.min(imgSize.height - srcHeight, y));
double destWidth = srcWidth * ratio;
double destHeight = srcHeight * ratio;
double destX = area.x + BORDER_WIDTH
+ (area.width - BORDER_WIDTH - BORDER_WIDTH - destWidth) / 2;
double destY = area.y + BORDER_WIDTH
+ (area.height - BORDER_WIDTH - BORDER_WIDTH - destHeight) / 2;
gc.setAntialias(SWT.ON);
gc.drawImage(image, (int) srcX, (int) srcY, (int) srcWidth,
(int) srcHeight, (int) destX, (int) destY, (int) destWidth,
(int) destHeight);
}
private void startDragging(Event event) {
if (image == null || image.isDisposed())
return;
startLoc = new Point(event.x, event.y);
startX = x;
startY = y;
}
private void endDragging(Event event) {
startLoc = null;
}
private void drag(Event event) {
if (startLoc == null || image == null || image.isDisposed())
return;
move(startX, startY, event.x - startLoc.x, event.y - startLoc.y);
}
public void move(double startX, double startY, int dx, int dy) {
if (image == null || image.isDisposed())
return;
Rectangle imgSize = image.getBounds();
int refWidth, refHeight;
if (canvas != null && !canvas.isDisposed()) {
Rectangle area = canvas.getClientArea();
refWidth = area.width;
refHeight = area.height;
} else {
refWidth = getPrefWidth();
refHeight = getPrefHeight();
}
double width = Math.min(imgSize.width, refWidth / ratio);
double height = Math.min(imgSize.height, refHeight / ratio);
double newX = Math.max(0, Math.min(imgSize.width - width, startX
- (dx / ratio)));
double newY = Math.max(0, Math.min(imgSize.height - height, startY
- (dy / ratio)));
if (newX != x || newY != y) {
setX(newX);
setY(newY);
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
}
}
public Control getControl() {
return composite;
}
public Canvas getCanvas() {
return canvas;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setRatio(double ratio) {
this.ratio = ratio;
}
public double getRatio() {
return ratio;
}
public int getPrefHeight() {
return prefHeight;
}
public void setPrefHeight(int prefHeight) {
if (prefHeight == this.prefHeight)
return;
int oldPrefHeight = this.prefHeight;
this.prefHeight = prefHeight;
updatePrefSize(getPrefWidth(), oldPrefHeight);
}
public int getPrefWidth() {
return prefWidth;
}
public void setPrefWidth(int prefWidth) {
if (prefWidth == this.prefWidth)
return;
int oldPrefWidth = this.prefWidth;
this.prefWidth = prefWidth;
updatePrefSize(oldPrefWidth, getPrefHeight());
}
private void updatePrefSize(int oldPrefWidth, int oldPrefHeight) {
// if (image == null || image.isDisposed())
// return;
//
// Rectangle imgSize = image.getBounds();
// double width = Math.min(imgSize.width, getPrefHeight() / getRatio());
// double height = Math.min(imgSize.height, getPrefHeight() / getRatio());
// double centerX = getX() + width * 0.5;
// double centerY = getY() + height * 0.5;
// setX(Math.max(0, Math.min(imgSize.width - width, centerX - width / 2)));
// setY(Math.max(0, Math
// .min(imgSize.height - height, centerY - height / 2)));
// if (canvas != null && !canvas.isDisposed()) {
// canvas.redraw();
// }
changeRatio(getRatio(), oldPrefWidth, oldPrefHeight);
}
public double getMaxRatio() {
return maxRatio;
}
public void setMaxRatio(double maxRatio) {
this.maxRatio = maxRatio;
}
public double getMinRatio() {
return minRatio;
}
public void setMinRatio(double minRatio) {
this.minRatio = minRatio;
}
public void zoomIn() {
if (ratio < MID_RATIO) {
changeRatio(Math.min(MID_RATIO, ratio + 0.1));
} else {
changeRatio(ratio + 0.5);
}
}
public void zoomOut() {
if (ratio <= MID_RATIO) {
changeRatio(ratio - 0.1);
} else {
changeRatio(Math.max(MID_RATIO, ratio - 0.3));
}
}
public void setFocus() {
if (canvas != null && !canvas.isDisposed()) {
canvas.setFocus();
}
}
public void changeRatio(double ratio) {
changeRatio(ratio, getPrefWidth(), getPrefHeight());
}
private void changeRatio(double ratio, int oldPrefWidth, int oldPrefHeight) {
ratio = Math.max(getMinRatio(), Math.min(getMaxRatio(), ratio));
double oldRatio = this.ratio;
setRatio(ratio);
double newRatio = this.ratio;
if (image != null) {
Rectangle imgSize = image.getBounds();
double oldWidth = Math.min(imgSize.width, oldPrefWidth / oldRatio);
double oldHeight = Math.min(imgSize.height, oldPrefHeight
/ oldRatio);
double oldCenterX = x + oldWidth / 2;
double oldCenterY = y + oldHeight / 2;
double newWidth = Math
.min(imgSize.width, getPrefWidth() / newRatio);
double newHeight = Math.min(imgSize.height, getPrefHeight()
/ newRatio);
setX(Math.min(imgSize.width - newWidth, Math.max(0, oldCenterX
- newWidth / 2)));
setY(Math.min(imgSize.height - newHeight, Math.max(0, oldCenterY
- newHeight / 2)));
}
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
updateRatioSelection(newRatio);
}
protected void updateRatioSelection(double ratio) {
if (slider != null && !slider.getControl().isDisposed()) {
updatingRatioSelection = true;
slider.setSelection(new StructuredSelection(Double.valueOf(ratio)));
updatingRatioSelection = false;
}
}
public void setImage(Image image) {
if (image != null && !image.isDisposed()) {
Rectangle imgSize = image.getBounds();
setImage(image, imgSize.x + imgSize.width / 2, imgSize.y
+ imgSize.height / 2);
} else {
setImage(null, 0, 0);
}
}
public void setImage(Image image, double centerX, double centerY) {
this.image = image;
if (image != null) {
Rectangle imgSize = image.getBounds();
double horizontalRatio = ((double) getPrefWidth()) / imgSize.width;
double verticalRatio = ((double) getPrefHeight()) / imgSize.height;
setRatio(Math.max(0.6, Math.min(horizontalRatio, verticalRatio)));
double width = Math.min(imgSize.width, getPrefWidth() / getRatio());
double height = Math.min(imgSize.height, getPrefHeight()
/ getRatio());
setX(Math.max(0, Math.min(imgSize.width - width, centerX - width
/ 2)));
setY(Math.max(0, Math.min(imgSize.height - height, centerY - height
/ 2)));
}
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
updateCanvas();
updateRatioControls();
updateRatioSelection(ratio);
}
public Image getImage() {
return image;
}
public boolean isDisabled() {
return disabled;
}
public void setDisabled(boolean disabled) {
this.disabled = disabled;
updateCanvas();
updateRatioControls();
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
if (title == this.title || (title != null && title.equals(this.title)))
return;
this.title = title;
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
}
public int getTitlePlacement() {
return titlePlacement;
}
public void setTitlePlacement(int titlePlacement) {
if (titlePlacement == this.titlePlacement)
return;
this.titlePlacement = titlePlacement;
if (canvas != null && !canvas.isDisposed()) {
canvas.redraw();
}
}
}