blob: 53d3a3754136312d230dff53021dbde72d3863eb [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2004, 2011 Tasktop Technologies and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* https://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*
* Balazs Brinkus - initial API and implementation
* Tasktop Technologies - improvements
* Willian Mitsuda - improvements
* Hiroyuki Inaba - improvements
*******************************************************************************/
package org.eclipse.mylyn.commons.ui.screenshots;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.ActionContributionItem;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.action.ToolBarManager;
import org.eclipse.jface.dialogs.Dialog;
import org.eclipse.jface.dialogs.IDialogSettings;
import org.eclipse.jface.dialogs.IInputValidator;
import org.eclipse.jface.dialogs.InputDialog;
import org.eclipse.jface.layout.GridDataFactory;
import org.eclipse.jface.window.Window;
import org.eclipse.mylyn.internal.commons.ui.screenshots.Messages;
import org.eclipse.mylyn.internal.commons.ui.screenshots.ScreenshotImages;
import org.eclipse.mylyn.internal.commons.ui.screenshots.SelectToolAction;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ScrolledComposite;
import org.eclipse.swt.custom.ViewForm;
import org.eclipse.swt.events.ControlAdapter;
import org.eclipse.swt.events.ControlEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.MenuDetectEvent;
import org.eclipse.swt.events.MenuDetectListener;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.events.MouseMoveListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Cursor;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Canvas;
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.Listener;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
/**
* A UI component to capture images of the screen.
*
* @author Balazs Brinkus
* @author Willian Mitsuda
* @author Mik Kersten
* @author Hiroyuki Inaba
* @author Steffen Pingel
* @author Benjamin Muskalla
* @since 3.6
*/
public class ScreenshotViewer {
private SelectToolAction captureAction;
private SelectToolAction fitAction;
private IAction cropAction;
private IAction markAction;
private IAction clearAction;
private IAction undoAction;
private IAction redoAction;
private Composite paletteArea;
private int lastDrawAction;
private IDialogSettings dialogSettings;
private SelectToolAction drawLineToolbar;
private SelectToolAction drawArrowToolbar;
private SelectToolAction drawBoxToolbar;
private SelectToolAction drawTextToolbar;
private SelectToolAction lineTypeToolbar;
private SelectToolAction lineBoldToolbar;
private SelectToolAction drawColorToolbar;
private boolean dirty;
/**
* Original screenshot image; used for backup purposes
*/
private Image originalImage;
/**
* Copy of {@link #originalImage original} image; all drawing operations are done here; base for the result image
*/
private Image workImage;
private Image previousImage;
/**
* Used to draw into {@link #workImage}
*/
private GC workImageGC;
private Canvas canvas;
private ScrolledComposite scrolledComposite;
/**
* Stores the selection rectangle; this value is normalized to real image coordinates, no matter the zoom level (see
* {@link #scaleFactor})
*/
private Rectangle currentSelection;
/**
* Stores the original selection rectangle, before a selection resize/move operation starts
*/
private Rectangle originalSelection;
/**
* Temporary storage for selection start point, selection resizing initial reference point or previous mark point
* (it depends on current tool); this value is normalized to real image coordinates, no matter the zoom level (see
* {@link #scaleFactor})
*/
private Point startPoint;
/**
* What sides I'm resizing when doing an selection {@link EditorAction#RESIZING_SELECTION resize}
*/
private Set<SelectionSide> resizableSides = EnumSet.noneOf(SelectionSide.class);
/**
* Scale factor of displayed image compared to the original image
*/
private double scaleFactor = 1.0;
/**
* Manages allocated cursors
*/
private final Map<Integer, Cursor> cursors = new HashMap<Integer, Cursor>();
/**
* Available actions for the screenshot editor
*/
private static enum EditorAction {
CROPPING, SELECTING, RESIZING_SELECTION, MOVING_SELECTION, MARKING;
};
/**
* What am I doing now?
*/
private EditorAction currentAction = EditorAction.CROPPING;
private boolean isFirstCapture = true;
private Text textArea;
/**
* Mouse event history. Entry is [0] MouseDown/MouseMove/MouseUp, [1] x, [2] y
*/
private List<int[]> historyMouseEvent = new ArrayList<int[]>();
/**
* Draw tool history. Entry is [0] drawHistory index, [1] FREE/LINE/BOX/OVAL, [2] Line type, [3] Bold, [4] R/G/B
*/
private List<int[]> historyDrawTool = new ArrayList<int[]>();
private List<StringBuffer> historyDrawText = new ArrayList<StringBuffer>();
private List<String> historyDrawFont = new ArrayList<String>();
private int historyCheckpoint = 0;
private ViewForm vf;
public ScreenshotViewer(Composite parent, int style) {
createControl(parent, style);
}
public Control getControl() {
return vf;
}
private void doDispose() {
disposeImageResources();
canvas.setCursor(null);
for (Cursor cursor : cursors.values()) {
cursor.dispose();
}
}
private void createControl(Composite parent, int style) {
vf = new ViewForm(parent, style);
vf.horizontalSpacing = 0;
vf.verticalSpacing = 0;
vf.setLayoutData(GridDataFactory.fillDefaults().create());
vf.addDisposeListener(new DisposeListener() {
public void widgetDisposed(DisposeEvent e) {
doDispose();
}
});
allocateCursors();
// TODO: need disabled versions of all toolbar icons
ToolBarManager tbm = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL | SWT.RIGHT);
captureAction = new SelectToolAction(getShell(), Messages.ScreenshotCreationPage_Capture_Desktop_C,
SelectToolAction.CAPTURE_DROP_DOWN_MENU) {
@Override
public void run() {
if (captureAction.getSelect() == SelectToolAction.CAPTURE_DESKTOP) {
captureScreenshotContent(INITIAL_CAPTURE_DELAY);
} else if (captureAction.getSelect() == SelectToolAction.CAPTURE_DESKTOP_DELAYED) {
captureScreenshotContentDelayed();
} else if (captureAction.getSelect() == SelectToolAction.CAPTURE_CLIPBOARD) {
captureScreenshotContent(captureAction.getClipboardImage());
} else if (captureAction.getSelect() == SelectToolAction.CAPTURE_RECTANGLE) {
captureScreenshotContentFromSelection();
} else {
captureScreenshotContent(captureAction.getFileImage());
}
if (isFirstCapture) {
isFirstCapture = false;
fitAction.setEnabled(true);
cropAction.setEnabled(true);
cropAction.setChecked(true);
markAction.setEnabled(true);
drawLineToolbar.setEnabled(true);
drawArrowToolbar.setEnabled(true);
drawBoxToolbar.setEnabled(true);
drawTextToolbar.setEnabled(true);
}
historyMouseEvent = new ArrayList<int[]>();
historyDrawTool = new ArrayList<int[]>();
historyDrawText = new ArrayList<StringBuffer>();
historyDrawFont = new ArrayList<String>();
historyCheckpoint = 0;
undoAction.setEnabled(false);
redoAction.setEnabled(false);
clearAction.setEnabled(false);
}
@Override
protected boolean isEnableRectangle() {
return (currentSelection != null);
}
};
captureAction.setToolTipText(Messages.ScreenshotCreationPage_Capture_Desktop);
captureAction.setImageDescriptor(ScreenshotImages.IMAGE_CAPTURE);
captureAction.setShowMenuAlways(false);
// captureDelayedButton = new Button(buttonsComposite, SWT.PUSH);
// final String captureIn = "Capture in ";
// final int secondsDelay = 1;
// captureDelayedButton.setText(captureIn + secondsDelay +" seconds");
// captureDelayedButton.setImage(TasksUiImages.getImage(TasksUiImages.IMAGE_CAPTURE));
// captureDelayedButton.addSelectionListener(new SelectionListener() {
//
// public void widgetSelected(SelectionEvent e) {
// PlatformUI.getWorkbench().getDisplay().asyncExec(new Runnable() {
// public void run() {
// getShell().setVisible(false);
// for (int i = 1; i <= secondsDelay; i++) {
// try {
// Thread.sleep(1000);
//// captureDelayedButton.setText("Capture in " + (secondsDelay-i) + " seconds");
// } catch (InterruptedException e1) {
// // ignore
// }
// }
// captureScreenshotContent();
// page.setErrorMessage(null);
// fitButton.setEnabled(true);
// captureDelayedButton.setText(captureIn + secondsDelay +" seconds");
// getShell().setVisible(true);
// }
// });
// }
//
// public void widgetDefaultSelected(SelectionEvent e) {
// //ignore
// }
// });
fitAction = new SelectToolAction(Messages.ScreenshotCreationPage_Fit_Image_F,
SelectToolAction.ZOOM_DROP_DOWN_MENU) {
@Override
public void run() {
refreshCanvasSize();
}
};
fitAction.setToolTipText(Messages.ScreenshotCreationPage_Fit_Image);
fitAction.setImageDescriptor(ScreenshotImages.IMAGE_FIT);
//fitAction.setChecked(true);
fitAction.setEnabled(false);
cropAction = new Action(Messages.ScreenshotCreationPage_Crop_R, IAction.AS_RADIO_BUTTON) {
@Override
public void run() {
currentAction = EditorAction.CROPPING;
cropAction.setChecked(true);
markAction.setChecked(false);
// undoAction.setEnabled(false);
// redoAction.setEnabled(false);
canvas.redraw();
}
};
cropAction.setToolTipText(Messages.ScreenshotCreationPage_Crop);
cropAction.setImageDescriptor(ScreenshotImages.CUT);
cropAction.setEnabled(false);
markAction = new Action(Messages.ScreenshotCreationPage_Annotate, IAction.AS_RADIO_BUTTON) {
@Override
public void setChecked(boolean checked) {
super.setChecked(checked);
if (paletteArea != null) {
if (checked) {
if (getSelectDrawToolbar() < 0) {
setSelectDrawToolbar(lastDrawAction);
}
} else {
int select = getSelectDrawToolbar();
if (select >= 0) {
lastDrawAction = select;
unselectDrawToolbar();
}
}
boolean isDrawText = (drawTextToolbar.getSelect() >= 0) ? false : checked;
//drawLineToolbar.setEnabled(checked);
//drawArrowToolbar.setEnabled(checked);
//drawBoxToolbar.setEnabled(checked);
//drawTextToolbar.setEnabled(checked);
drawColorToolbar.setEnabled(isDrawText);
lineTypeToolbar.setEnabled(isDrawText);
lineBoldToolbar.setEnabled(isDrawText);
}
}
@Override
public void run() {
currentAction = EditorAction.MARKING;
cropAction.setChecked(false);
markAction.setChecked(true);
// undoAction.setEnabled(false);
// redoAction.setEnabled(false);
canvas.redraw();
}
};
markAction.setToolTipText(Messages.ScreenshotCreationPage_DRAW_ANNOTATION_ON_SCREENSHOT_IMAGE);
markAction.setImageDescriptor(ScreenshotImages.EDIT);
// markAction.setDisabledImageDescriptor(ImageDescriptor.createFromFile(getClass(), "mark_disabled.gif"));
markAction.setEnabled(false);
clearAction = new Action(Messages.ScreenshotCreationPage_Clear, IAction.AS_PUSH_BUTTON) {
@Override
public void run() {
clearAction.setEnabled(false);
workImageGC.drawImage(originalImage, 0, 0);
canvas.redraw();
setDirty(true);
historyMouseEvent = new ArrayList<int[]>();
historyDrawTool = new ArrayList<int[]>();
historyDrawText = new ArrayList<StringBuffer>();
historyDrawFont = new ArrayList<String>();
historyCheckpoint = 0;
undoAction.setEnabled(false);
redoAction.setEnabled(false);
}
};
clearAction.setToolTipText(Messages.ScreenshotCreationPage_Clear_all_annotations_made_on_screenshot_image);
clearAction.setImageDescriptor(ScreenshotImages.CLEAR);
clearAction.setEnabled(false);
undoAction = new Action(Messages.ScreenshotCreationPage_Undo) {
@Override
public void run() {
if (historyCheckpoint > 0) {
historyCheckpoint--;
drawAnnotationHistory();
}
if (historyCheckpoint == 0) {
undoAction.setEnabled(false);
}
if (historyCheckpoint < historyDrawTool.size()) {
redoAction.setEnabled(true);
}
}
};
undoAction.setToolTipText(Messages.ScreenshotCreationPage_Undo_annotation);
undoAction.setImageDescriptor(ScreenshotImages.UNDO);
undoAction.setEnabled(false);
redoAction = new Action(Messages.ScreenshotCreationPage_Redo) {
@Override
public void run() {
if (historyCheckpoint < historyDrawTool.size()) {
historyCheckpoint++;
drawAnnotationHistory();
}
if (historyCheckpoint > 0) {
undoAction.setEnabled(true);
}
if (historyCheckpoint >= historyDrawTool.size()) {
redoAction.setEnabled(false);
}
}
};
redoAction.setToolTipText(Messages.ScreenshotCreationPage_Redo_annotation);
redoAction.setImageDescriptor(ScreenshotImages.REDO);
redoAction.setEnabled(false);
tbm.add(createAndConfigureCI(captureAction));
tbm.add(new Separator());
tbm.add(createAndConfigureCI(fitAction));
tbm.add(createAndConfigureCI(cropAction));
tbm.add(createAndConfigureCI(markAction));
tbm.add(new Separator());
tbm.add(createAndConfigureCI(clearAction));
tbm.add(createAndConfigureCI(undoAction));
tbm.add(createAndConfigureCI(redoAction));
tbm.add(new Separator());
Composite body = new Composite(vf, SWT.NONE);
GridLayout layout = new GridLayout(2, false);
layout.horizontalSpacing = 0;
layout.verticalSpacing = 0;
layout.marginWidth = 0;
layout.marginHeight = 0;
body.setLayout(layout);
createPaletteBars(body);
lastDrawAction = getSelectDrawToolbar();
unselectDrawToolbar();
scrolledComposite = new ScrolledComposite(body, SWT.V_SCROLL | SWT.H_SCROLL);
scrolledComposite.setLayoutData(new GridData(GridData.FILL_BOTH));
canvas = new Canvas(scrolledComposite, SWT.DOUBLE_BUFFERED);
scrolledComposite.setContent(canvas);
canvas.addPaintListener(new PaintListener() {
public void paintControl(PaintEvent e) {
if (workImage != null) {
Rectangle imageBounds = workImage.getBounds();
Rectangle canvasBounds = canvas.getClientArea();
int zoom = fitAction.getSelect();
switch (zoom) {
case SelectToolAction.ZOOM_FIT:
e.gc.drawImage(workImage, 0, 0, imageBounds.width, imageBounds.height, //
0, 0, canvasBounds.width, canvasBounds.height);
break;
case 50:
e.gc.drawImage(workImage, 0, 0, imageBounds.width, imageBounds.height, //
0, 0, imageBounds.width / 2, imageBounds.height / 2);
break;
case 100:
e.gc.drawImage(workImage, 0, 0);
break;
default:
e.gc.drawImage(workImage, 0, 0, imageBounds.width, imageBounds.height, //
0, 0, imageBounds.width * zoom / 100, imageBounds.height * zoom / 100);
break;
}
drawSelection(e.gc);
} else {
// page.setErrorMessage("Screenshot required");
fitAction.setEnabled(false);
}
}
});
scrolledComposite.addControlListener(new ControlAdapter() {
@Override
public void controlResized(ControlEvent e) {
if (fitAction.getSelect() == SelectToolAction.ZOOM_FIT) {
refreshCanvasSize();
}
}
});
scrolledComposite.setEnabled(false);
ToolBar control = tbm.createControl(vf);
vf.setTopLeft(control);
vf.setContent(body);
registerMouseListeners();
vf.setLayoutData(new GridData(GridData.FILL_BOTH));
Dialog.applyDialogFont(vf);
}
private Shell getShell() {
return getControl().getShell();
}
private void createPaletteBars(Composite body) {
paletteArea = new Composite(body, SWT.NONE);
paletteArea.setLayoutData(new GridData(GridData.FILL_VERTICAL));
RowLayout rowlayout = new RowLayout(SWT.VERTICAL);
rowlayout.marginRight += 1;
paletteArea.setLayout(rowlayout);
paletteArea.addListener(SWT.Paint, new Listener() {
public void handleEvent(Event e) {
Color gcForeground = e.gc.getForeground();
Rectangle bounds = ((Composite) e.widget).getBounds();
Color border = e.widget.getDisplay().getSystemColor(SWT.COLOR_WIDGET_NORMAL_SHADOW);
e.gc.setForeground(border);
e.gc.drawLine(bounds.width - 1, 0, bounds.width - 1, bounds.height);
e.gc.setForeground(gcForeground);
}
});
paletteArea.addMenuDetectListener(new MenuDetectListener() {
public void menuDetected(MenuDetectEvent e) {
Menu rightClickMenu = new Menu(Display.getDefault().getActiveShell(), SWT.POP_UP);
MenuItem menuItem = new MenuItem(rightClickMenu, SWT.CHECK);
menuItem.setText(Messages.ScreenshotCreationPage_Show_Line_Type_Selector);
menuItem.setSelection(lineTypeToolbar.getVisible());
menuItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(final Event event) {
lineTypeToolbar.setVisible(!lineTypeToolbar.getVisible());
paletteArea.layout();
}
});
menuItem = new MenuItem(rightClickMenu, SWT.CHECK);
menuItem.setText(Messages.ScreenshotCreationPage_Show_Line_Bold_Selector);
menuItem.setSelection(lineBoldToolbar.getVisible());
menuItem.addListener(SWT.Selection, new Listener() {
public void handleEvent(final Event event) {
lineBoldToolbar.setVisible(!lineBoldToolbar.getVisible());
paletteArea.layout();
}
});
rightClickMenu.setLocation(e.x, e.y);
rightClickMenu.setVisible(true);
}
});
drawLineToolbar = new SelectToolAction(paletteArea, SelectToolAction.DRAWLINE_TOOLBAR) {
@Override
public void run() {
markAction.run();
drawArrowToolbar.setUnselect();
drawBoxToolbar.setUnselect();
drawTextToolbar.setUnselect();
drawColorToolbar.setEnabled(true);
lineTypeToolbar.setEnabled(true);
lineBoldToolbar.setEnabled(true);
}
};
drawLineToolbar.setEnabled(false);
drawArrowToolbar = new SelectToolAction(paletteArea, SelectToolAction.DRAWARROW_TOOLBAR) {
@Override
public void run() {
markAction.run();
drawLineToolbar.setUnselect();
drawBoxToolbar.setUnselect();
drawTextToolbar.setUnselect();
drawColorToolbar.setEnabled(true);
lineTypeToolbar.setEnabled(true);
lineBoldToolbar.setEnabled(true);
}
};
drawArrowToolbar.setEnabled(false);
drawBoxToolbar = new SelectToolAction(paletteArea, SelectToolAction.DRAWBOX_TOOLBAR) {
@Override
public void run() {
markAction.run();
drawLineToolbar.setUnselect();
drawArrowToolbar.setUnselect();
drawTextToolbar.setUnselect();
drawColorToolbar.setEnabled(true);
lineTypeToolbar.setEnabled(true);
lineBoldToolbar.setEnabled(true);
}
};
drawBoxToolbar.setEnabled(false);
drawTextToolbar = new SelectToolAction(paletteArea, SelectToolAction.DRAWTEXT_TOOLBAR) {
@Override
public void run() {
markAction.run();
drawLineToolbar.setUnselect();
drawArrowToolbar.setUnselect();
drawBoxToolbar.setUnselect();
drawColorToolbar.setEnabled(false);
lineTypeToolbar.setEnabled(false);
lineBoldToolbar.setEnabled(false);
}
};
drawTextToolbar.setEnabled(false);
drawColorToolbar = new SelectToolAction(paletteArea, SelectToolAction.COLOR_TOOLBAR);
drawColorToolbar.setEnabled(false);
lineTypeToolbar = new SelectToolAction(paletteArea, SelectToolAction.LINETYPE_TOOLBAR);
lineTypeToolbar.setEnabled(false);
lineTypeToolbar.setVisible(false);
lineBoldToolbar = new SelectToolAction(paletteArea, SelectToolAction.LINEBOLD_TOOLBAR);
lineBoldToolbar.setEnabled(false);
lineBoldToolbar.setVisible(false);
}
private void setSelectDrawToolbar(int drawTool) {
if (drawLineToolbar.setSelect(drawTool)) {
drawArrowToolbar.setUnselect();
drawBoxToolbar.setUnselect();
drawTextToolbar.setUnselect();
return;
}
if (drawArrowToolbar.setSelect(drawTool)) {
drawLineToolbar.setUnselect();
drawBoxToolbar.setUnselect();
drawTextToolbar.setUnselect();
return;
}
if (drawBoxToolbar.setSelect(drawTool)) {
drawLineToolbar.setUnselect();
drawArrowToolbar.setUnselect();
drawTextToolbar.setUnselect();
return;
}
drawLineToolbar.setUnselect();
drawArrowToolbar.setUnselect();
drawBoxToolbar.setUnselect();
drawTextToolbar.setSelect(drawTool);
}
private void unselectDrawToolbar() {
drawLineToolbar.setUnselect();
drawArrowToolbar.setUnselect();
drawBoxToolbar.setUnselect();
drawTextToolbar.setUnselect();
}
private int getSelectDrawToolbar() {
int drawTool;
if ((drawTool = drawLineToolbar.getSelect()) >= 0) {
return drawTool;
}
if ((drawTool = drawArrowToolbar.getSelect()) >= 0) {
return drawTool;
}
if ((drawTool = drawBoxToolbar.getSelect()) >= 0) {
return drawTool;
}
if ((drawTool = drawTextToolbar.getSelect()) >= 0) {
return drawTool;
}
return -1;
}
private ActionContributionItem createAndConfigureCI(IAction action) {
ActionContributionItem ci = new ActionContributionItem(action);
ci.setMode(ActionContributionItem.MODE_FORCE_TEXT);
return ci;
}
private void disposeImageResources() {
if (originalImage != null) {
originalImage.dispose();
}
if (workImageGC != null) {
workImageGC.dispose();
}
if (workImage != null) {
workImage.dispose();
}
}
private static final int CURSOR_MARK_TOOL = -1;
private static final int INITIAL_CAPTURE_DELAY = 400;
private static final String DEFAULT_DELAY = "2"; //$NON-NLS-1$
private static final String DELAY_DIALOG_KEY = "ScreenshotViewerDelayDialog"; //$NON-NLS-1$
private static final String DELAY_DIALOG_DELAY_VALUE = "DelayValue"; //$NON-NLS-1$
private void allocateCursors() {
Display display = getShell().getDisplay();
cursors.put(SWT.CURSOR_ARROW, new Cursor(display, SWT.CURSOR_ARROW));
cursors.put(SWT.CURSOR_SIZEALL, new Cursor(display, SWT.CURSOR_SIZEALL));
cursors.put(SWT.CURSOR_SIZENWSE, new Cursor(display, SWT.CURSOR_SIZENWSE));
cursors.put(SWT.CURSOR_SIZENESW, new Cursor(display, SWT.CURSOR_SIZENESW));
cursors.put(SWT.CURSOR_SIZENS, new Cursor(display, SWT.CURSOR_SIZENS));
cursors.put(SWT.CURSOR_SIZEWE, new Cursor(display, SWT.CURSOR_SIZEWE));
cursors.put(SWT.CURSOR_CROSS, new Cursor(display, SWT.CURSOR_CROSS));
// TODO: allocate custom cursor for "mark" tool
cursors.put(CURSOR_MARK_TOOL, new Cursor(display, SWT.CURSOR_HAND));
}
private Rectangle getScaledSelection() {
if (currentSelection == null) {
return null;
}
int x = (int) Math.round(currentSelection.x * scaleFactor);
int y = (int) Math.round(currentSelection.y * scaleFactor);
int right = (int) Math.round((currentSelection.x + currentSelection.width) * scaleFactor);
int bottom = (int) Math.round((currentSelection.y + currentSelection.height) * scaleFactor);
int width = Math.min(right, (int) Math.round(workImage.getBounds().width * scaleFactor)) - x;
int height = Math.min(bottom, (int) Math.round(workImage.getBounds().height * scaleFactor)) - y;
return new Rectangle(x, y, width, height);
}
private Rectangle getOutsideSelection(Rectangle rectangle) {
if (rectangle == null) {
return null;
}
return new Rectangle(rectangle.x - SQUARE_SIZE * 2, rectangle.y - SQUARE_SIZE * 2, //
rectangle.width + SQUARE_SIZE * 4 + 1, rectangle.height + SQUARE_SIZE * 4 + 1);
}
private static final int[][] grapGroupPoints = { //
/* */{ 0, 0, 0 }, { 1, 0, 0 }, { 2, 0, 1 }, { 3, 0, 2 }, { 4, 0, 2 }, //
{ 0, 1, 0 }, /* *//* *//* */{ 4, 1, 2 }, //
{ 0, 2, 3 }, /* *//* *//* */{ 4, 2, 4 }, //
{ 0, 3, 5 }, /* *//* *//* */{ 4, 3, 7 }, //
{ 0, 4, 5 }, { 1, 4, 5 }, { 2, 4, 6 }, { 3, 4, 7 }, { 4, 4, 7 } };
private static final int[] grapScanOrder = { 0, 4, 1, 3, 2 };
private int getGrabPoint(int x, int y) {
if (currentSelection == null) {
return -1;
}
Rectangle inside = getScaledSelection();
Rectangle outside = getOutsideSelection(inside);
int[] xGroupPoint = { outside.x, //
inside.x, //
inside.x + SQUARE_SIZE * 4, //
inside.x + inside.width - SQUARE_SIZE * 4, //
inside.x + inside.width, //
outside.x + outside.width };
int[] yGroupPoint = { outside.y, //
inside.y, //
inside.y + SQUARE_SIZE * 4, //
inside.y + inside.height - SQUARE_SIZE * 4, //
inside.y + inside.height, //
outside.y + outside.height };
int xGroup = -1, yGroup = -1;
for (int element : grapScanOrder) {
if (xGroupPoint[element] <= x && x <= xGroupPoint[element + 1]) {
xGroup = element;
break;
}
}
if (xGroup < 0) {
return -1;
}
for (int element : grapScanOrder) {
if (yGroupPoint[element] <= y && y <= yGroupPoint[element + 1]) {
yGroup = element;
break;
}
}
if (yGroup < 0) {
return -1;
}
for (int[] element : grapGroupPoints) {
if (element[0] == xGroup && element[1] == yGroup) {
return element[2];
}
}
return -1;
}
/**
* Returns true, if the viewer has captured an image.
*/
public boolean isComplete() {
return workImage != null;
}
private void captureScreenshotContent(Image image) {
final Display display = getShell().getDisplay();
disposeImageResources();
originalImage = image;
Rectangle displayBounds = originalImage.getBounds();
workImage = new Image(display, displayBounds.width, displayBounds.height);
GC gc = new GC(workImage);
gc.drawImage(originalImage, 0, 0);
gc.dispose();
workImageGC = new GC(workImage);
workImageGC.setLineCap(SWT.CAP_ROUND);
scrolledComposite.setEnabled(true);
clearSelection();
refreshCanvasSize();
stateChanged();
}
/**
* Invoked when the state of the viewer changes, e.g. when the screen is captured. Sub-classes may override.
*
* @see #isComplete()
*/
protected void stateChanged() {
// do nothing
}
private void captureScreenshotContentFromSelection() {
Display display = getShell().getDisplay();
Image image = new Image(display, currentSelection);
GC gc = new GC(image);
gc.drawImage(workImage, currentSelection.x, currentSelection.y, currentSelection.width,
currentSelection.height, 0, 0, currentSelection.width, currentSelection.height);
gc.dispose();
disposeImageResources();
originalImage = image;
Rectangle displayBounds = originalImage.getBounds();
workImage = new Image(display, displayBounds.width, displayBounds.height);
gc = new GC(workImage);
gc.drawImage(originalImage, 0, 0);
gc.dispose();
workImageGC = new GC(workImage);
workImageGC.setLineCap(SWT.CAP_ROUND);
scrolledComposite.setEnabled(true);
clearSelection();
refreshCanvasSize();
stateChanged();
}
private void captureScreenshotContent(int delay) {
final Display display = getShell().getDisplay();
final Shell wizardShell = getShell();
wizardShell.setVisible(false);
// this code needs to run asynchronously to allow the workbench to refresh before the screen is captured
// NOTE: need a wait since the shell can take time to disappear (e.g. fade on Vista)
getShell().getDisplay().timerExec(delay, new Runnable() {
public void run() {
disposeImageResources();
Rectangle displayBounds = display.getBounds();
originalImage = new Image(display, displayBounds.width, displayBounds.height);
workImage = new Image(display, displayBounds.width, displayBounds.height);
GC gc = new GC(display);
gc.copyArea(originalImage, displayBounds.x, displayBounds.y);
gc.copyArea(workImage, displayBounds.x, displayBounds.y);
gc.dispose();
workImageGC = new GC(workImage);
workImageGC.setLineCap(SWT.CAP_ROUND);
scrolledComposite.setEnabled(true);
clearSelection();
refreshCanvasSize();
wizardShell.setVisible(true);
stateChanged();
}
});
}
private void captureScreenshotContentDelayed() {
IInputValidator delayValidator = new IInputValidator() {
public String isValid(String newText) {
try {
int result = Integer.parseInt(newText);
if (result > 0) {
return null;
}
} catch (NumberFormatException e) {
// fall through
}
return Messages.ScreenshotViewer_EnterValidSeconds;
}
};
String delay = getDelayFromSettings(DEFAULT_DELAY);
InputDialog delayDialog = new InputDialog(getShell(), Messages.ScreenshotViewer_Delay,
Messages.ScreenshotViewer_EnterDelayForScreenshot, delay, delayValidator);
int resultCode = delayDialog.open();
if (resultCode == Window.OK) {
storeDelaySetting(delayDialog.getValue());
int newDelay = Integer.parseInt(delayDialog.getValue());
int newDelayInMs = newDelay * 1000;
captureScreenshotContent(INITIAL_CAPTURE_DELAY + newDelayInMs);
}
}
private void storeDelaySetting(String value) {
if (dialogSettings != null) {
IDialogSettings section = dialogSettings.getSection(DELAY_DIALOG_KEY);
if (section == null) {
section = dialogSettings.addNewSection(DELAY_DIALOG_KEY);
}
section.put(DELAY_DIALOG_DELAY_VALUE, value);
}
}
private String getDelayFromSettings(String defaultValue) {
String delay = defaultValue;
if (dialogSettings != null) {
IDialogSettings section = dialogSettings.getSection(DELAY_DIALOG_KEY);
if (section != null) {
int lastDelay = section.getInt(DELAY_DIALOG_DELAY_VALUE);
delay = String.valueOf(lastDelay);
}
}
return delay;
}
/**
* @since 3.6
*/
public void setDialogSettings(IDialogSettings dialogSettings) {
this.dialogSettings = dialogSettings;
}
/**
* @since 3.6
*/
public IDialogSettings getDialogSettings() {
return dialogSettings;
}
/**
* Sets the selection rectangle based on the initial selection start point previously set in {@link #startPoint} and
* the end point passed as parameters to this method
* <p>
* The coordinates are based on the real image coordinates
*/
private void refreshCurrentSelection(int x, int y) {
int startX = Math.min(startPoint.x, x);
int startY = Math.min(startPoint.y, y);
int width = Math.abs(startPoint.x - x);
int height = Math.abs(startPoint.y - y);
currentSelection = new Rectangle(startX, startY, width, height);
// Decreases 1 pixel size from original image because Rectangle.intersect() consider them as right-bottom limit
Rectangle imageBounds = workImage.getBounds();
imageBounds.width--;
imageBounds.height--;
currentSelection.intersect(imageBounds);
}
private static final int grabPointCurosr[] = { SWT.CURSOR_SIZENWSE, SWT.CURSOR_SIZENS, SWT.CURSOR_SIZENESW,
SWT.CURSOR_SIZEWE, SWT.CURSOR_SIZEWE, SWT.CURSOR_SIZENESW, SWT.CURSOR_SIZENS, SWT.CURSOR_SIZENWSE };
private static final SelectionSide[][] grabPointResizableSides = { { SelectionSide.LEFT, SelectionSide.TOP },
{ SelectionSide.TOP }, { SelectionSide.TOP, SelectionSide.RIGHT }, { SelectionSide.LEFT },
{ SelectionSide.RIGHT }, { SelectionSide.LEFT, SelectionSide.BOTTOM }, { SelectionSide.BOTTOM },
{ SelectionSide.BOTTOM, SelectionSide.RIGHT } };
private void refreshSelectionResize(int x, int y) {
currentSelection = new Rectangle(originalSelection.x, originalSelection.y, originalSelection.width,
originalSelection.height);
int deltaX = x - startPoint.x;
int deltaY = y - startPoint.y;
Rectangle imageBounds = workImage.getBounds();
// Check current selection limits
if (resizableSides.contains(SelectionSide.LEFT)) {
deltaX = Math.min(deltaX, originalSelection.width);
if (originalSelection.x + deltaX < 0) {
deltaX = -originalSelection.x;
}
}
if (resizableSides.contains(SelectionSide.RIGHT)) {
deltaX = Math.max(deltaX, -originalSelection.width);
if (originalSelection.x + originalSelection.width + deltaX > imageBounds.width) {
deltaX = imageBounds.width - (originalSelection.x + originalSelection.width);
}
}
if (resizableSides.contains(SelectionSide.TOP)) {
deltaY = Math.min(deltaY, originalSelection.height);
if (originalSelection.y + deltaY < 0) {
deltaY = -originalSelection.y;
}
}
if (resizableSides.contains(SelectionSide.BOTTOM)) {
deltaY = Math.max(deltaY, -originalSelection.height);
if (originalSelection.y + originalSelection.height + deltaY > imageBounds.height) {
deltaY = imageBounds.height - (originalSelection.y + originalSelection.height);
}
}
// Adjust corresponding sides
if (resizableSides.contains(SelectionSide.LEFT)) {
currentSelection.x += deltaX;
currentSelection.width -= deltaX;
}
if (resizableSides.contains(SelectionSide.RIGHT)) {
currentSelection.width += deltaX;
}
if (resizableSides.contains(SelectionSide.TOP)) {
currentSelection.y += deltaY;
currentSelection.height -= deltaY;
}
if (resizableSides.contains(SelectionSide.BOTTOM)) {
currentSelection.height += deltaY;
}
}
private void refreshSelectionPosition(int x, int y) {
int newX = originalSelection.x + (x - startPoint.x);
int newY = originalSelection.y + (y - startPoint.y);
if (newX < 0) {
newX = 0;
}
if (newY < 0) {
newY = 0;
}
Rectangle imageBounds = workImage.getBounds();
if (newX + originalSelection.width > imageBounds.width) {
newX = imageBounds.width - originalSelection.width;
}
if (newY + originalSelection.height > imageBounds.height) {
newY = imageBounds.height - originalSelection.height;
}
currentSelection = new Rectangle(newX, newY, originalSelection.width, originalSelection.height);
}
private void registerMouseListeners() {
canvas.addMouseMoveListener(new MouseMoveListener() {
/**
* If a selection is in course, moving the mouse around refreshes the selection rectangle
*/
public void mouseMove(MouseEvent e) {
int scaledX = (int) Math.round(e.x / scaleFactor);
int scaledY = (int) Math.round(e.y / scaleFactor);
if (currentAction == EditorAction.SELECTING) {
refreshCurrentSelection(scaledX, scaledY);
canvas.redraw();
} else if (currentAction == EditorAction.RESIZING_SELECTION) {
refreshSelectionResize(scaledX, scaledY);
canvas.redraw();
} else if (currentAction == EditorAction.MOVING_SELECTION) {
refreshSelectionPosition(scaledX, scaledY);
canvas.redraw();
} else if (currentAction == EditorAction.CROPPING && currentSelection != null) {
boolean cursorSet = false;
// No selection in course, but have something selected; first test if I'm hovering some grab point
int info = getGrabPoint(e.x, e.y);
if (info >= 0) {
canvas.setCursor(cursors.get(grabPointCurosr[info]));
cursorSet = true;
}
// Test if I'm inside selection, so I can move it
if (!cursorSet && getScaledSelection().contains(e.x, e.y)) {
canvas.setCursor(cursors.get(SWT.CURSOR_SIZEALL));
cursorSet = true;
}
// If I'm out, the default cursor for cropping mode is cross
Cursor crossCursor = cursors.get(SWT.CURSOR_CROSS);
if (!cursorSet && canvas.getCursor() != crossCursor) {
canvas.setCursor(crossCursor);
}
} else if (currentAction == EditorAction.MARKING) {
if (startPoint != null) {
int drawTool = getSelectDrawToolbar();
if (drawTool == SelectToolAction.DRAW_FREE) {
int[] history = new int[3];
history[0] = SWT.MouseMove;
history[1] = scaledX;
history[2] = scaledY;
historyMouseEvent.add(history);
} else {
int[] history = historyMouseEvent.get(historyMouseEvent.size() - 1);
if (history[0] == SWT.MouseMove) {
history[1] = scaledX;
history[2] = scaledY;
} else {
history = new int[3];
history[0] = SWT.MouseMove;
history[1] = scaledX;
history[2] = scaledY;
historyMouseEvent.add(history);
}
}
}
drawMarkLine(scaledX, scaledY);
Cursor markCursor = cursors.get(CURSOR_MARK_TOOL);
if (canvas.getCursor() != markCursor) {
canvas.setCursor(markCursor);
}
}
}
});
canvas.addMouseListener(new MouseAdapter() {
/**
* Releasing the mouse button ends the selection or a drawing; compute the selection rectangle and redraw
* the cropped image
*/
@Override
public void mouseUp(MouseEvent e) {
if (currentAction == EditorAction.SELECTING || currentAction == EditorAction.RESIZING_SELECTION
|| currentAction == EditorAction.MOVING_SELECTION) {
int scaledX = (int) Math.round(e.x / scaleFactor);
int scaledY = (int) Math.round(e.y / scaleFactor);
if (currentAction == EditorAction.SELECTING) {
refreshCurrentSelection(scaledX, scaledY);
} else if (currentAction == EditorAction.RESIZING_SELECTION) {
refreshSelectionResize(scaledX, scaledY);
} else if (currentAction == EditorAction.MOVING_SELECTION) {
refreshSelectionPosition(scaledX, scaledY);
}
if (currentSelection.width == 0 && currentSelection.height == 0) {
currentSelection = null;
}
startPoint = null;
currentAction = EditorAction.CROPPING;
canvas.redraw();
setDirty(true);
} else if (currentAction == EditorAction.MARKING) {
if (startPoint != null) {
int drawTool = getSelectDrawToolbar();
if (drawTool != SelectToolAction.DRAW_FREE) {
if (drawTool == SelectToolAction.DRAW_TEXT) {
drawAnnotationText();
}
previousImage.dispose();
previousImage = null;
}
int[] history = new int[3];
history[0] = SWT.MouseUp;
history[1] = 0;
history[2] = 0;
historyMouseEvent.add(history);
}
startPoint = null;
setDirty(true);
}
}
/**
* Input annotation text and draw text
*/
private void drawAnnotationText() {
workImageGC.drawImage(previousImage, 0, 0);
canvas.redraw();
int[] history = historyMouseEvent.get(historyMouseEvent.size() - 1);
if (history[0] != SWT.MouseMove) {
historyCheckpoint--;
updateAnnotationHistory();
return;
}
int endedPoint_x, endedPoint_y;
if (history[1] < startPoint.x) {
endedPoint_x = startPoint.x;
startPoint.x = history[1];
} else {
endedPoint_x = history[1];
}
if (history[2] < startPoint.y) {
endedPoint_y = startPoint.y;
startPoint.y = history[2];
} else {
endedPoint_y = history[2];
}
final Rectangle bounds = new Rectangle(startPoint.x, startPoint.y, endedPoint_x - startPoint.x,
endedPoint_y - startPoint.y);
textArea = new Text(canvas, SWT.MULTI | SWT.WRAP);
int xs = (int) Math.round(startPoint.x * scaleFactor);
int ys = (int) Math.round(startPoint.y * scaleFactor);
int xe = (int) Math.round(endedPoint_x * scaleFactor);
int ye = (int) Math.round(endedPoint_y * scaleFactor);
textArea.setBounds(new Rectangle(xs, ys, xe - xs, ye - ys));
FontData fontData = new FontData(drawTextToolbar.getStringCustom());
if (scaleFactor != 1.0) {
fontData.setHeight((int) Math.round(fontData.getHeight() * scaleFactor));
}
textArea.setFont(new Font(getShell().getDisplay(), fontData));
textArea.setForeground(new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawTextToolbar.getIntgerCustom())));
textArea.setTabs(1);
Point point = textArea.getCaretLocation();
textArea.setBounds(new Rectangle(xs - point.x, ys, xe - xs + point.x + point.x, ye - ys));
textArea.setFocus();
textArea.addListener(SWT.Deactivate, new Listener() {
public void handleEvent(Event event) {
String text = textArea.getText();
{
String newtext = ""; //$NON-NLS-1$
int currpos = 0;
int charpos = currpos;
textArea.setTopIndex(0);
textArea.setSelection(currpos);
int linepos = textArea.getCaretLineNumber();
boolean remove1st = false;
String line;
while (currpos < text.length()) {
int y = textArea.getCaretLineNumber();
if (linepos != y) {
line = text.substring(charpos, currpos);
if (line.endsWith("\n")) { //$NON-NLS-1$
line = line.substring(0, line.length() - 1);
}
newtext = newtext + "\n" + line; //$NON-NLS-1$
remove1st = true;
charpos = currpos;
linepos = y;
}
currpos++;
textArea.setSelection(currpos);
}
line = text.substring(charpos, currpos);
if (line.endsWith("\n")) { //$NON-NLS-1$
line = line.substring(0, line.length() - 1);
}
if (line.length() > 0) {
newtext = newtext + "\n" + text.substring(charpos, currpos); //$NON-NLS-1$
remove1st = true;
}
currpos = newtext.indexOf("\r"); //$NON-NLS-1$
while (currpos > 0) {
newtext = newtext.substring(0, currpos) + newtext.substring(currpos + 1);
currpos = newtext.indexOf("\r"); //$NON-NLS-1$
}
newtext = newtext.replace("\t", " "); //$NON-NLS-1$//$NON-NLS-2$
if (remove1st) {
newtext = newtext.substring(1);
}
text = newtext;
}
textArea.dispose();
textArea = null;
if (text.length() > 0) {
historyDrawText.get(historyCheckpoint - 1).append(text);
Color color = workImageGC.getForeground();
FontData fontData = new FontData(drawTextToolbar.getStringCustom());
workImageGC.setFont(new Font(getShell().getDisplay(), fontData));
workImageGC.setForeground(new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawTextToolbar.getIntgerCustom())));
workImageGC.setClipping(bounds);
workImageGC.drawText(text, bounds.x, bounds.y, true);
workImageGC.setClipping((Rectangle) null);
workImageGC.setForeground(color);
} else {
historyCheckpoint--;
updateAnnotationHistory();
}
canvas.redraw();
}
});
}
/**
* Pressing mouse button starts a selection or a drawing; normalizes and marks the start point
*/
@Override
public void mouseDown(MouseEvent e) {
int scaledX = (int) (e.x / scaleFactor);
int scaledY = (int) (e.y / scaleFactor);
if (currentAction == EditorAction.MARKING) {
updateAnnotationHistory();
int drawTool = getSelectDrawToolbar();
int[] history = new int[5];
history[0] = historyMouseEvent.size();
history[1] = drawTool;
history[2] = (lineTypeToolbar != null) ? lineTypeToolbar.getSelect() : SWT.LINE_DOT;
history[3] = (lineBoldToolbar != null) ? lineBoldToolbar.getSelect() : 1;
RGB rgb;
if (drawTool == SelectToolAction.DRAW_TEXT) {
rgb = SelectToolAction.int2rgb(drawTextToolbar.getIntgerCustom());
} else {
rgb = SelectToolAction.int2rgb(drawColorToolbar.getSelect());
}
history[4] = (rgb.red << 16) + (rgb.green << 8) + rgb.blue;
historyDrawTool.add(history);
historyDrawText.add(new StringBuffer());
if (drawTool == SelectToolAction.DRAW_TEXT) {
FontData fontData = new FontData(drawTextToolbar.getStringCustom());
historyDrawFont.add(fontData.toString());
} else {
historyDrawFont.add(""); //$NON-NLS-1$
}
historyCheckpoint = historyDrawTool.size();
history = new int[3];
history[0] = SWT.MouseDown;
history[1] = scaledX;
history[2] = scaledY;
historyMouseEvent.add(history);
undoAction.setEnabled(true);
if (drawTool != SelectToolAction.DRAW_FREE) {
Display display = getShell().getDisplay();
previousImage = new Image(display, workImage.getBounds());
GC gc = new GC(previousImage);
gc.drawImage(workImage, 0, 0);
gc.dispose();
}
if (drawTool != SelectToolAction.DRAW_TEXT) {
workImageGC.setLineStyle(lineTypeToolbar.getSelect());
workImageGC.setLineWidth(lineBoldToolbar.getSelect());
workImageGC.setForeground(new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawColorToolbar.getSelect())));
} else {
workImageGC.setLineStyle(SWT.LINE_DOT);
workImageGC.setLineWidth(1);
workImageGC.setForeground(new Color(getShell().getDisplay(), 0, 0, 0));
}
startPoint = new Point(scaledX, scaledY);
drawMarkLine(scaledX, scaledY);
canvas.setCursor(cursors.get(CURSOR_MARK_TOOL));
return;
} else if (currentAction != EditorAction.CROPPING) {
return;
}
// Check the most appropriate action to follow; first check if I'm on some grab point
if (currentSelection != null) {
int info = getGrabPoint(e.x, e.y);
if (info >= 0) {
originalSelection = currentSelection;
currentAction = EditorAction.RESIZING_SELECTION;
resizableSides = new HashSet<SelectionSide>();
for (SelectionSide side : grabPointResizableSides[info]) {
resizableSides.add(side);
}
startPoint = new Point(scaledX, scaledY);
canvas.redraw();
return;
}
}
// Check if I could move the selection
if (currentSelection != null && currentSelection.contains(scaledX, scaledY)) {
originalSelection = currentSelection;
currentAction = EditorAction.MOVING_SELECTION;
startPoint = new Point(scaledX, scaledY);
canvas.redraw();
return;
}
// Do a simple selection
canvas.setCursor(cursors.get(SWT.CURSOR_CROSS));
currentAction = EditorAction.SELECTING;
currentSelection = null;
startPoint = new Point(scaledX, scaledY);
canvas.redraw();
}
});
}
private void clearSelection() {
currentSelection = null;
startPoint = null;
setDirty(true);
}
/**
* Recalculates image canvas size based on "fit on canvas" setting, set up the grab points, and redraws
* <p>
* This method should be called whenever the {@link #workImage image} <strong>visible</strong> size is changed,
* which can happen when:
* <p>
* <ul>
* <li>The "Fit Image" setting is changed, so the image zoom level changes
* <li>The image changes (by recapturing)
* <li>The canvas is resized (indirectly happens by resizing the wizard page) <strong>AND</strong> "Fit Image"
* setting is ON
* </ul>
* <p>
* Calling this method under other circumstances may lead to strange behavior in the scrolled composite
*/
private void refreshCanvasSize() {
if (fitAction.getSelect() == SelectToolAction.ZOOM_FIT) {
// This little hack is necessary to get the client area without scrollbars;
// they'll be automatically restored if necessary after Canvas.setBounds()
scrolledComposite.getHorizontalBar().setVisible(false);
scrolledComposite.getVerticalBar().setVisible(false);
Rectangle bounds = scrolledComposite.getClientArea();
if (workImage != null) {
Rectangle imageBounds = workImage.getBounds();
double xRatio = (double) bounds.width / imageBounds.width;
double yRatio = (double) bounds.height / imageBounds.height;
scaleFactor = Math.min(xRatio, yRatio);
bounds.width = (int) Math.round(imageBounds.width * scaleFactor);
bounds.height = (int) Math.round(imageBounds.height * scaleFactor);
}
canvas.setBounds(bounds);
} else {
scaleFactor = fitAction.getSelect(); // 50, 100, 200, 400 or 800
scaleFactor = scaleFactor / 100;
Rectangle bounds = scrolledComposite.getClientArea();
if (workImage != null) {
Rectangle imageBounds = workImage.getBounds();
bounds.width = (int) Math.round(imageBounds.width * scaleFactor);
bounds.height = (int) Math.round(imageBounds.height * scaleFactor);
}
canvas.setBounds(bounds);
}
canvas.redraw();
}
private void updateAnnotationHistory() {
int[] history;
if (historyCheckpoint < historyDrawTool.size()) {
history = historyDrawTool.get(historyCheckpoint);
while (history[0] < historyMouseEvent.size()) {
historyMouseEvent.remove(historyMouseEvent.size() - 1);
}
while (historyCheckpoint < historyDrawTool.size()) {
historyDrawTool.remove(historyDrawTool.size() - 1);
}
while (historyCheckpoint < historyDrawText.size()) {
historyDrawText.remove(historyDrawText.size() - 1);
}
while (historyCheckpoint < historyDrawFont.size()) {
historyDrawFont.remove(historyDrawFont.size() - 1);
}
redoAction.setEnabled(false);
}
undoAction.setEnabled(historyCheckpoint > 0);
}
/**
* Draw Annotation with history
*/
private void drawAnnotationHistory() {
workImageGC.drawImage(originalImage, 0, 0);
Color backBackground = workImageGC.getBackground();
Color backForeground = workImageGC.getForeground();
int backLineStyle = workImageGC.getLineStyle();
int backLineWidth = workImageGC.getLineWidth();
int[] history;
for (int c = 0; c < historyCheckpoint; c++) {
history = historyDrawTool.get(c);
int toolKind = history[1];
int boldlKind = history[3];
workImageGC.setLineStyle(history[2]);
workImageGC.setLineWidth(boldlKind);
workImageGC.setForeground(new Color(getShell().getDisplay(), //
history[4] >> 16, //
(history[4] >> 8) & 0x00ff, //
history[4] & 0x00ff));
int h = history[0];
history = historyMouseEvent.get(h);
int start_x = history[1];
int start_y = history[2];
for (h++; h < historyMouseEvent.size(); h++) {
history = historyMouseEvent.get(h);
if (history[0] == SWT.MouseUp) {
break;
}
int x = history[1];
int y = history[2];
if (toolKind == SelectToolAction.DRAW_FREE) {
workImageGC.drawLine(start_x, start_y, x, y);
start_x = x;
start_y = y;
} else {
if (start_x == x && start_y == y) {
workImageGC.drawLine(start_x, start_y, x, y);
} else {
int rounded;
int width = x - start_x;
int height = y - start_y;
switch (toolKind) {
case SelectToolAction.DRAW_LINE:
workImageGC.drawLine(start_x, start_y, x, y);
break;
case SelectToolAction.DRAW_ARROW1:
workImageGC.setBackground(workImageGC.getForeground());
drawArrowLine(start_x, start_y, x, y, false);
break;
case SelectToolAction.DRAW_ARROW2:
workImageGC.setBackground(workImageGC.getForeground());
drawArrowLine(start_x, start_y, x, y, true);
break;
case SelectToolAction.DRAW_BOX:
workImageGC.drawRectangle(start_x, start_y, width, height);
break;
case SelectToolAction.DRAW_RBOX:
rounded = boldlKind * 8;
workImageGC.drawRoundRectangle(start_x, start_y, width, height, rounded, rounded);
break;
case SelectToolAction.DRAW_OVAL:
workImageGC.drawOval(start_x, start_y, width, height);
break;
case SelectToolAction.DRAW_FILL_BOX:
workImageGC.setBackground(workImageGC.getForeground());
workImageGC.fillRectangle(start_x, start_y, width, height);
break;
case SelectToolAction.DRAW_FILL_RBOX:
rounded = boldlKind * 8;
workImageGC.setBackground(workImageGC.getForeground());
workImageGC.fillRoundRectangle(start_x, start_y, width, height, rounded, rounded);
break;
case SelectToolAction.DRAW_FILL_OVAL:
workImageGC.setBackground(workImageGC.getForeground());
workImageGC.fillOval(start_x, start_y, width, height);
break;
case SelectToolAction.DRAW_TEXT:
StringBuffer text = historyDrawText.get(c);
{
Font backFont = workImageGC.getFont();
FontData fontData = new FontData(historyDrawFont.get(c));
workImageGC.setFont(new Font(getShell().getDisplay(), fontData));
workImageGC.setClipping(start_x, start_y, width, height);
workImageGC.drawText(text.toString(), start_x, start_y, true);
workImageGC.setClipping((Rectangle) null);
workImageGC.setFont(backFont);
}
break;
}
}
}
}
}
workImageGC.setBackground(backBackground);
workImageGC.setForeground(backForeground);
workImageGC.setLineStyle(backLineStyle);
workImageGC.setLineWidth(backLineWidth);
canvas.redraw();
}
/**
* Decorates the screenshot canvas with the selection rectangle, resize grab points and other adornments
*/
private void drawSelection(GC gc) {
if (currentSelection == null) {
return;
}
Rectangle inside = getScaledSelection();
// Draw shadow
gc.setBackground(gc.getDevice().getSystemColor(SWT.COLOR_GRAY));
gc.setAdvanced(true);
gc.setAlpha(120);
Region invertedSelection = new Region();
invertedSelection.add(canvas.getClientArea());
invertedSelection.subtract(inside);
gc.setClipping(invertedSelection);
gc.fillRectangle(canvas.getClientArea());
gc.setClipping((Region) null);
invertedSelection.dispose();
gc.setAdvanced(false);
// Draw selection rectangle
gc.setLineStyle(SWT.LINE_SOLID);
gc.setForeground(getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
gc.drawRectangle(inside);
// // Draw grab points
// gc.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_WHITE));
// gc.setForeground(getShell().getDisplay().getSystemColor(SWT.COLOR_BLACK));
// for (GrabPoint point : grabPoints) {
// gc.fillRectangle(point.grabArea);
// gc.drawRectangle(point.grabArea);
// }
gc.setForeground(getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
Rectangle outside = getOutsideSelection(inside);
gc.drawRectangle(outside);
gc.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
gc.fillRectangle(outside.x, outside.y, //
SQUARE_SIZE * 6, SQUARE_SIZE * 2);
gc.fillRectangle(outside.x + outside.width - SQUARE_SIZE * 6, outside.y, //
SQUARE_SIZE * 6, SQUARE_SIZE * 2);
gc.fillRectangle(outside.x, outside.y, //
SQUARE_SIZE * 2, SQUARE_SIZE * 6);
gc.fillRectangle(outside.x + outside.width - SQUARE_SIZE * 2, outside.y, //
SQUARE_SIZE * 2, SQUARE_SIZE * 6);
gc.fillRectangle(outside.x, outside.y + outside.height - SQUARE_SIZE * 6, //
SQUARE_SIZE * 2, SQUARE_SIZE * 6);
gc.fillRectangle(outside.x + outside.width - SQUARE_SIZE * 2, outside.y + outside.height - SQUARE_SIZE * 6, //
SQUARE_SIZE * 2, SQUARE_SIZE * 6);
gc.fillRectangle(outside.x, outside.y + outside.height - SQUARE_SIZE * 2, //
SQUARE_SIZE * 6, SQUARE_SIZE * 2);
gc.fillRectangle(outside.x + outside.width - SQUARE_SIZE * 6, outside.y + outside.height - SQUARE_SIZE * 2, //
SQUARE_SIZE * 6, SQUARE_SIZE * 2);
}
/**
* Connects the previous mark point to the new reference point, by drawing a new line, rectangle or oval
*/
private void drawMarkLine(int x, int y) {
if (startPoint != null) {
clearAction.setEnabled(true);
int drawTool = getSelectDrawToolbar();
if (drawTool == SelectToolAction.DRAW_FREE) {
workImageGC.drawLine(startPoint.x, startPoint.y, x, y);
startPoint.x = x;
startPoint.y = y;
} else {
workImageGC.drawImage(previousImage, 0, 0);
if (startPoint.x == x && startPoint.y == y) {
workImageGC.drawLine(startPoint.x, startPoint.y, x, y);
} else {
Color backColor;
Color markColor;
int rounded;
int width = x - startPoint.x;
int height = y - startPoint.y;
switch (drawTool) {
case SelectToolAction.DRAW_LINE:
workImageGC.drawLine(startPoint.x, startPoint.y, x, y);
break;
case SelectToolAction.DRAW_ARROW1:
backColor = workImageGC.getBackground();
markColor = new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawColorToolbar.getSelect()));
workImageGC.setBackground(markColor);
drawArrowLine(startPoint.x, startPoint.y, x, y, false);
workImageGC.setBackground(backColor);
break;
case SelectToolAction.DRAW_ARROW2:
backColor = workImageGC.getBackground();
markColor = new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawColorToolbar.getSelect()));
workImageGC.setBackground(markColor);
drawArrowLine(startPoint.x, startPoint.y, x, y, true);
workImageGC.setBackground(backColor);
break;
case SelectToolAction.DRAW_BOX:
workImageGC.drawRectangle(startPoint.x, startPoint.y, width, height);
break;
case SelectToolAction.DRAW_RBOX:
rounded = lineBoldToolbar.getSelect() * 8;
workImageGC.drawRoundRectangle(startPoint.x, startPoint.y, width, height, rounded, rounded);
break;
case SelectToolAction.DRAW_OVAL:
workImageGC.drawOval(startPoint.x, startPoint.y, width, height);
break;
case SelectToolAction.DRAW_FILL_BOX:
backColor = workImageGC.getBackground();
markColor = new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawColorToolbar.getSelect()));
workImageGC.setBackground(markColor);
workImageGC.fillRectangle(startPoint.x, startPoint.y, width, height);
workImageGC.setBackground(backColor);
break;
case SelectToolAction.DRAW_FILL_RBOX:
rounded = lineBoldToolbar.getSelect() * 8;
backColor = workImageGC.getBackground();
markColor = new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawColorToolbar.getSelect()));
workImageGC.setBackground(markColor);
workImageGC.fillRoundRectangle(startPoint.x, startPoint.y, width, height, rounded, rounded);
workImageGC.setBackground(backColor);
break;
case SelectToolAction.DRAW_FILL_OVAL:
backColor = workImageGC.getBackground();
markColor = new Color(getShell().getDisplay(),
SelectToolAction.int2rgb(drawColorToolbar.getSelect()));
workImageGC.setBackground(markColor);
workImageGC.fillOval(startPoint.x, startPoint.y, width, height);
workImageGC.setBackground(backColor);
break;
case SelectToolAction.DRAW_TEXT:
workImageGC.fillRectangle(startPoint.x, startPoint.y, width, height);
workImageGC.drawRectangle(startPoint.x, startPoint.y, width, height);
break;
}
}
}
canvas.redraw();
}
}
private void drawArrowLine(int xs, int ys, int xe, int ye, boolean bothsides) {
int width = xe - xs, height = ye - ys;
int bold = workImageGC.getLineWidth();
int leng = (bold == 8) ? bold * 4 : (bold == 4) ? bold * 6 : (bold == 2) ? bold * 8 : bold * 10;
double delta = Math.PI / 6.0;
double theta = Math.atan2(height, width);
// Draw line
if (bothsides) {
workImageGC.drawLine( //
xs + (int) (leng / 2 * Math.cos(theta)), //
ys + (int) (leng / 2 * Math.sin(theta)), //
xe - (int) (leng / 2 * Math.cos(theta)), //
ye - (int) (leng / 2 * Math.sin(theta)));
} else {
workImageGC.drawLine( //
xs, //
ys, //
xe - (int) (leng / 2 * Math.cos(theta)), //
ye - (int) (leng / 2 * Math.sin(theta)));
}
// Draw ending side arrow
workImageGC.setLineWidth(1);
int[] point = { xe, ye, //
xe - (int) (leng * Math.cos(theta - delta)), //
ye - (int) (leng * Math.sin(theta - delta)), //
xe - (int) (leng * Math.cos(theta + delta)), //
ye - (int) (leng * Math.sin(theta + delta)) };
workImageGC.fillPolygon(point);
// Draw starting side arrow
if (bothsides) {
int[] point2 = { xs, ys, //
xs + (int) (leng * Math.cos(theta - delta)), //
ys + (int) (leng * Math.sin(theta - delta)), //
xs + (int) (leng * Math.cos(theta + delta)), //
ys + (int) (leng * Math.sin(theta + delta)) };
workImageGC.fillPolygon(point2);
}
workImageGC.setLineWidth(bold);
}
private static enum SelectionSide {
LEFT, RIGHT, TOP, BOTTOM;
};
private static final int SQUARE_SIZE = 3;
/**
* Creates the final screenshot.
*
* @return The final screenshot, with all markings, and cropped according to user settings; <strong>The caller is
* responsible for disposing the returned image</strong>
*/
public Image createImage() {
// use default display to support invocation from non UI thread
Image screenshot = new Image(Display.getDefault(), currentSelection != null
? currentSelection
: workImage.getBounds());
GC gc = new GC(screenshot);
if (currentSelection != null) {
gc.drawImage(workImage, currentSelection.x, currentSelection.y, currentSelection.width,
currentSelection.height, 0, 0, currentSelection.width, currentSelection.height);
} else {
gc.drawImage(workImage, 0, 0);
}
gc.dispose();
setDirty(false);
return screenshot;
}
/**
* Sets the dirty flag to indicate if the image was modified since {@link #createImage()} was invoked last.
*
* @param dirty
* the dirty flag
* @see #isDirty()
*/
public void setDirty(boolean dirty) {
this.dirty = dirty;
}
/**
* Returns true, if the image was modified since {@link #createImage()} was invoked last.
*
* @see #setDirty(boolean)
*/
public boolean isDirty() {
return dirty;
}
}