/******************************************************************************* * Copyright (c) 2004, 2011 Tasktop Technologies and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * 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; } }