| /******************************************************************************* |
| * 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; |
| } |
| |
| } |