View | Details | Raw Unified | Return to bug 195691 | Differences between
and this patch

Collapse All | Expand All

(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/NewAttachmentPage.java (-10 / +4 lines)
Lines 239-245 Link Here
239
239
240
	@Override
240
	@Override
241
	public boolean isPageComplete() {
241
	public boolean isPageComplete() {
242
		return !"".equals(filePath.getText().trim()) && (attachmentDesc == null || !"".equals(attachmentDesc.getText().trim()));
242
		return !"".equals(filePath.getText().trim())
243
				&& (attachmentDesc == null || !"".equals(attachmentDesc.getText().trim()));
243
	}
244
	}
244
245
245
	public void populateAttachment() {
246
	public void populateAttachment() {
Lines 253-265 Link Here
253
		return attachment;
254
		return attachment;
254
	}
255
	}
255
256
256
	@Override
257
	public boolean canFlipToNextPage() {
258
		if (filePath.getText().equals(InputAttachmentSourcePage.SCREENSHOT_LABEL))
259
			return false;
260
		return isPageComplete();
261
	}
262
263
	public void setFilePath(String path) {
257
	public void setFilePath(String path) {
264
		filePath.setText(path);
258
		filePath.setText(path);
265
		if (path.endsWith(".patch")) {
259
		if (path.endsWith(".patch")) {
Lines 298-306 Link Here
298
	public boolean supportsDescription() {
292
	public boolean supportsDescription() {
299
		return supportsDescription;
293
		return supportsDescription;
300
	}
294
	}
301
	
295
302
	public void setSupportsDescription(boolean supportsDescription) {
296
	public void setSupportsDescription(boolean supportsDescription) {
303
		this.supportsDescription = supportsDescription;
297
		this.supportsDescription = supportsDescription;
304
	}
298
	}
305
	
299
306
}
300
}
(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/NewAttachmentWizard.java (-21 / +18 lines)
Lines 42-51 Link Here
42
import org.eclipse.mylyn.tasks.ui.editors.AbstractRepositoryTaskEditorInput;
42
import org.eclipse.mylyn.tasks.ui.editors.AbstractRepositoryTaskEditorInput;
43
import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
43
import org.eclipse.mylyn.tasks.ui.editors.TaskEditor;
44
import org.eclipse.mylyn.tasks.ui.editors.TaskEditorInput;
44
import org.eclipse.mylyn.tasks.ui.editors.TaskEditorInput;
45
import org.eclipse.swt.SWT;
46
import org.eclipse.swt.graphics.Image;
47
import org.eclipse.swt.graphics.ImageData;
48
import org.eclipse.swt.graphics.ImageLoader;
49
import org.eclipse.swt.widgets.Display;
45
import org.eclipse.swt.widgets.Display;
50
import org.eclipse.ui.PlatformUI;
46
import org.eclipse.ui.PlatformUI;
51
47
Lines 81-87 Link Here
81
	private boolean screenshotMode;
77
	private boolean screenshotMode;
82
78
83
	public NewAttachmentWizard(TaskRepository repository, AbstractTask task, boolean screenshotMode) {
79
	public NewAttachmentWizard(TaskRepository repository, AbstractTask task, boolean screenshotMode) {
84
		super();
85
		this.task = task;
80
		this.task = task;
86
		this.repository = repository;
81
		this.repository = repository;
87
		this.screenshotMode = screenshotMode;
82
		this.screenshotMode = screenshotMode;
Lines 95-101 Link Here
95
		}
90
		}
96
91
97
		inputPage = new InputAttachmentSourcePage(this);
92
		inputPage = new InputAttachmentSourcePage(this);
98
		attachment = new LocalAttachment();
93
		if (screenshotMode) {
94
			shotPage = new ScreenshotAttachmentPage();
95
			attachment = new ImageAttachment(shotPage);
96
		} else {
97
			attachment = new LocalAttachment();
98
		}
99
		attachment.setFilePath("");
99
		attachment.setFilePath("");
100
		setNeedsProgressMonitor(true);
100
		setNeedsProgressMonitor(true);
101
		IDialogSettings workbenchSettings = TasksUiPlugin.getDefault().getDialogSettings();
101
		IDialogSettings workbenchSettings = TasksUiPlugin.getDefault().getDialogSettings();
Lines 125-130 Link Here
125
	}
125
	}
126
126
127
	@Override
127
	@Override
128
	public void dispose() {
129
		// Ensures the temporary screenshot image is deleted
130
		if (attachment != null && attachment instanceof ImageAttachment) {
131
			((ImageAttachment) attachment).clearImageFile();
132
		}
133
		super.dispose();
134
	}
135
136
	@Override
128
	public boolean performFinish() {
137
	public boolean performFinish() {
129
		/* TODO jpound - support non-text in clipboard */
138
		/* TODO jpound - support non-text in clipboard */
130
		attachPage.populateAttachment();
139
		attachPage.populateAttachment();
Lines 159-177 Link Here
159
					task.setSynchronizationState(RepositoryTaskSyncState.OUTGOING);
168
					task.setSynchronizationState(RepositoryTaskSyncState.OUTGOING);
160
169
161
					if (screenshotMode || InputAttachmentSourcePage.SCREENSHOT_LABEL.equals(path)) {
170
					if (screenshotMode || InputAttachmentSourcePage.SCREENSHOT_LABEL.equals(path)) {
162
						Image image = shotPage.getScreenshotImage();
171
						((ImageAttachment) attachment).ensureImageFileWasCreated();
163
						if (image == null) {
164
							throw new InvocationTargetException(new CoreException(new RepositoryStatus(IStatus.ERROR,
165
									TasksUiPlugin.ID_PLUGIN, RepositoryStatus.ERROR_INTERNAL, "Screenshot is empty",
166
									null)));
167
						}
168
						String path = TasksUiPlugin.getDefault().getDefaultDataDirectory();
169
						ImageLoader loader = new ImageLoader();
170
						loader.data = new ImageData[] { image.getImageData() };
171
						String fileName = path + "/" + SCREENSHOT_FILENAME;
172
						loader.save(fileName, SWT.IMAGE_JPEG);
173
						attachment.setFile(new File(fileName));
174
						attachment.setFilename(SCREENSHOT_FILENAME);
175
					} else if (InputAttachmentSourcePage.CLIPBOARD_LABEL.equals(path)) {
172
					} else if (InputAttachmentSourcePage.CLIPBOARD_LABEL.equals(path)) {
176
						String contents = inputPage.getClipboardContents();
173
						String contents = inputPage.getClipboardContents();
177
						if (contents == null) {
174
						if (contents == null) {
Lines 293-299 Link Here
293
	@Override
290
	@Override
294
	public boolean canFinish() {
291
	public boolean canFinish() {
295
		if (screenshotMode) {
292
		if (screenshotMode) {
296
			return shotPage.isPageComplete();
293
			return shotPage.isPageComplete() && attachPage.isPageComplete();
297
		} else {
294
		} else {
298
			return attachPage.isPageComplete();
295
			return attachPage.isPageComplete();
299
		}
296
		}
Lines 303-309 Link Here
303
	public void addPages() {
300
	public void addPages() {
304
		super.addPages();
301
		super.addPages();
305
		if (screenshotMode) {
302
		if (screenshotMode) {
306
			addPage((shotPage = new ScreenshotAttachmentPage(attachment)));
303
			addPage(shotPage);
307
			addPage((attachPage = new NewAttachmentPage(attachment)));
304
			addPage((attachPage = new NewAttachmentPage(attachment)));
308
		} else {
305
		} else {
309
			if ("".equals(attachment.getFilePath())) {
306
			if ("".equals(attachment.getFilePath())) {
(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/PreviewAttachmentPage.java (-1 / +23 lines)
Lines 13-22 Link Here
13
import java.io.FileNotFoundException;
13
import java.io.FileNotFoundException;
14
import java.io.FileReader;
14
import java.io.FileReader;
15
import java.io.IOException;
15
import java.io.IOException;
16
import java.lang.reflect.InvocationTargetException;
16
import java.util.HashMap;
17
import java.util.HashMap;
17
18
19
import org.eclipse.core.runtime.IProgressMonitor;
18
import org.eclipse.jface.layout.GridDataFactory;
20
import org.eclipse.jface.layout.GridDataFactory;
19
import org.eclipse.jface.layout.GridLayoutFactory;
21
import org.eclipse.jface.layout.GridLayoutFactory;
22
import org.eclipse.jface.operation.IRunnableWithProgress;
20
import org.eclipse.jface.wizard.WizardPage;
23
import org.eclipse.jface.wizard.WizardPage;
21
import org.eclipse.mylyn.internal.tasks.core.LocalAttachment;
24
import org.eclipse.mylyn.internal.tasks.core.LocalAttachment;
22
import org.eclipse.swt.SWT;
25
import org.eclipse.swt.SWT;
Lines 50-56 Link Here
50
	private static final String TITLE = "Attachment Preview";
53
	private static final String TITLE = "Attachment Preview";
51
54
52
	private static final String DESCRIPTION = "Review the attachment before submitting";
55
	private static final String DESCRIPTION = "Review the attachment before submitting";
53
	
56
54
	private LocalAttachment attachment;
57
	private LocalAttachment attachment;
55
58
56
	private static HashMap<String, String> textTypes;
59
	private static HashMap<String, String> textTypes;
Lines 93-98 Link Here
93
		composite.setLayout(new GridLayout());
96
		composite.setLayout(new GridLayout());
94
		setControl(composite);
97
		setControl(composite);
95
98
99
		if (attachment instanceof ImageAttachment) {
100
			try {
101
				getContainer().run(true, false, new IRunnableWithProgress() {
102
					public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException {
103
						monitor.beginTask("Preparing image for preview...", 1);
104
						((ImageAttachment) attachment).ensureImageFileWasCreated();
105
						monitor.worked(1);
106
						monitor.done();
107
					}
108
				});
109
			} catch (InvocationTargetException e) {
110
				createErrorPreview(composite, "Could not create image for preview");
111
				return;
112
			} catch (InterruptedException e) {
113
				createErrorPreview(composite, "Could not create image for preview");
114
				return;
115
			}
116
		}
117
96
		if (InputAttachmentSourcePage.CLIPBOARD_LABEL.equals(attachment.getFilePath())) {
118
		if (InputAttachmentSourcePage.CLIPBOARD_LABEL.equals(attachment.getFilePath())) {
97
			createTextPreview(composite, ((NewAttachmentWizard) getWizard()).getClipboardContents());
119
			createTextPreview(composite, ((NewAttachmentWizard) getWizard()).getClipboardContents());
98
		} else if (PreviewAttachmentPage.isTextAttachment(attachment.getContentType())) {
120
		} else if (PreviewAttachmentPage.isTextAttachment(attachment.getContentType())) {
(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/ScreenshotAttachmentPage.java (-172 / +348 lines)
Lines 15-28 Link Here
15
import java.util.Map;
15
import java.util.Map;
16
import java.util.Set;
16
import java.util.Set;
17
17
18
import org.eclipse.jface.action.Action;
19
import org.eclipse.jface.action.ActionContributionItem;
20
import org.eclipse.jface.action.IAction;
21
import org.eclipse.jface.action.Separator;
22
import org.eclipse.jface.action.ToolBarManager;
18
import org.eclipse.jface.layout.GridDataFactory;
23
import org.eclipse.jface.layout.GridDataFactory;
24
import org.eclipse.jface.resource.ImageDescriptor;
19
import org.eclipse.jface.wizard.IWizardPage;
25
import org.eclipse.jface.wizard.IWizardPage;
20
import org.eclipse.jface.wizard.WizardPage;
26
import org.eclipse.jface.wizard.WizardPage;
21
import org.eclipse.mylyn.internal.tasks.core.LocalAttachment;
22
import org.eclipse.mylyn.internal.tasks.ui.TaskListColorsAndFonts;
27
import org.eclipse.mylyn.internal.tasks.ui.TaskListColorsAndFonts;
23
import org.eclipse.mylyn.internal.tasks.ui.TasksUiImages;
28
import org.eclipse.mylyn.internal.tasks.ui.TasksUiImages;
24
import org.eclipse.swt.SWT;
29
import org.eclipse.swt.SWT;
25
import org.eclipse.swt.custom.ScrolledComposite;
30
import org.eclipse.swt.custom.ScrolledComposite;
31
import org.eclipse.swt.custom.ViewForm;
26
import org.eclipse.swt.events.ControlAdapter;
32
import org.eclipse.swt.events.ControlAdapter;
27
import org.eclipse.swt.events.ControlEvent;
33
import org.eclipse.swt.events.ControlEvent;
28
import org.eclipse.swt.events.MouseAdapter;
34
import org.eclipse.swt.events.MouseAdapter;
Lines 30-75 Link Here
30
import org.eclipse.swt.events.MouseMoveListener;
36
import org.eclipse.swt.events.MouseMoveListener;
31
import org.eclipse.swt.events.PaintEvent;
37
import org.eclipse.swt.events.PaintEvent;
32
import org.eclipse.swt.events.PaintListener;
38
import org.eclipse.swt.events.PaintListener;
33
import org.eclipse.swt.events.SelectionAdapter;
39
import org.eclipse.swt.graphics.Color;
34
import org.eclipse.swt.events.SelectionEvent;
35
import org.eclipse.swt.graphics.Cursor;
40
import org.eclipse.swt.graphics.Cursor;
36
import org.eclipse.swt.graphics.GC;
41
import org.eclipse.swt.graphics.GC;
37
import org.eclipse.swt.graphics.Image;
42
import org.eclipse.swt.graphics.Image;
38
import org.eclipse.swt.graphics.Point;
43
import org.eclipse.swt.graphics.Point;
44
import org.eclipse.swt.graphics.RGB;
39
import org.eclipse.swt.graphics.Rectangle;
45
import org.eclipse.swt.graphics.Rectangle;
40
import org.eclipse.swt.graphics.Region;
46
import org.eclipse.swt.graphics.Region;
41
import org.eclipse.swt.layout.GridLayout;
42
import org.eclipse.swt.widgets.Button;
43
import org.eclipse.swt.widgets.Canvas;
47
import org.eclipse.swt.widgets.Canvas;
44
import org.eclipse.swt.widgets.Composite;
48
import org.eclipse.swt.widgets.Composite;
45
import org.eclipse.swt.widgets.Display;
49
import org.eclipse.swt.widgets.Display;
50
import org.eclipse.swt.widgets.Event;
46
import org.eclipse.swt.widgets.Shell;
51
import org.eclipse.swt.widgets.Shell;
52
import org.eclipse.swt.widgets.ToolItem;
47
53
48
/**
54
/**
49
 * A wizard page to create a screenshot from the display.
55
 * A wizard page to create a screenshot from the display.
50
 * <p>
51
 * Note: this class uses {@link GC#setXORMode(boolean)} which is deprecated because of lack of Mac implementation
52
 * (bug#50228). The strategy to make it work on platforms XOR is implemented and still have a good looking in platforms
53
 * where it is not implemented, is to draw each line 2 times: first using a WHITE foreground followed by the same draw
54
 * using a BLACK foreground. On platforms where XOR is implemented, the second draw will have no effect, since anything
55
 * XOR black results in noop. On platforms where XOR is NOT implemented, the {@link GC#setXORMode(boolean)} is a noop,
56
 * so the end result will be a black line.
57
 * 
56
 * 
58
 * @author Balazs Brinkus (bug 160572)
57
 * @author Balazs Brinkus (bug 160572)
59
 * @author Mik Kersten
58
 * @author Mik Kersten
60
 * @author Willian Mitsuda
59
 * @author Willian Mitsuda
61
 */
60
 */
62
public class ScreenshotAttachmentPage extends WizardPage {
61
public class ScreenshotAttachmentPage extends WizardPage implements IImageCreator {
63
62
64
	private ScreenshotAttachmentPage page;
63
	private IAction captureAction;
65
64
66
	private LocalAttachment attachment;
65
	private IAction fitAction;
67
66
68
	private Button captureButton;
67
	private IAction cropAction;
69
68
70
	private Button fitButton;
69
	private IAction markAction;
71
70
72
	private Image screenshotImage;
71
	private IAction colorAction;
72
73
	private Image colorIcon;
74
75
	private Color markColor;
76
77
	private IAction clearAction;
78
79
	/**
80
	 * Original screenshot image; used for backup purposes
81
	 */
82
	private Image originalImage;
83
84
	/**
85
	 * Copy of {@link #originalImage original} image; all drawing operations are done here; base for the result image
86
	 */
87
	private Image workImage;
88
89
	/**
90
	 * Used to draw into {@link #workImage}
91
	 */
92
	private GC workImageGC;
73
93
74
	private Canvas canvas;
94
	private Canvas canvas;
75
95
Lines 87-99 Link Here
87
	private Rectangle originalSelection;
107
	private Rectangle originalSelection;
88
108
89
	/**
109
	/**
90
	 * Temporary storage for selection start point or selection resizing initial reference point; this value is
110
	 * Temporary storage for selection start point, selection resizing initial reference point or previous mark point
91
	 * normalized to real image coordinates, no matter the zoom level (see {@link #scaleFactor})
111
	 * (it depends on current tool); this value is normalized to real image coordinates, no matter the zoom level (see
112
	 * {@link #scaleFactor})
92
	 */
113
	 */
93
	private Point startPoint;
114
	private Point startPoint;
94
115
95
	/**
116
	/**
96
	 * What sides I'm resizing when doing an selection {@link Action#RESIZING_SELECTION resize}
117
	 * What sides I'm resizing when doing an selection {@link EditorAction#RESIZING_SELECTION resize}
97
	 */
118
	 */
98
	private Set<SelectionSide> resizableSides = EnumSet.noneOf(SelectionSide.class);
119
	private Set<SelectionSide> resizableSides = EnumSet.noneOf(SelectionSide.class);
99
120
Lines 110-157 Link Here
110
	/**
131
	/**
111
	 * Available actions for the screenshot editor
132
	 * Available actions for the screenshot editor
112
	 */
133
	 */
113
	private static enum Action {
134
	private static enum EditorAction {
114
135
115
		IDLE, SELECTING, RESIZING_SELECTION, MOVING_SELECTION;
136
		CROPPING, SELECTING, RESIZING_SELECTION, MOVING_SELECTION, MARKING;
116
137
117
	};
138
	};
118
139
119
	/**
140
	/**
120
	 * What am I doing now?
141
	 * What am I doing now?
121
	 */
142
	 */
122
	private Action currentAction = Action.IDLE;
143
	private EditorAction currentAction = EditorAction.CROPPING;
123
144
124
	protected ScreenshotAttachmentPage(LocalAttachment attachment) {
145
	protected ScreenshotAttachmentPage() {
125
		super("ScreenShotAttachment");
146
		super("ScreenShotAttachment");
126
		setTitle("Capture Screenshot");
147
		setTitle("Capture Screenshot");
127
		setDescription("After capturing, drag the mouse to crop. This window will not be captured. "
148
		setDescription("After capturing, you can crop the image and make drawings on it. This window will not be captured. "
128
				+ "Note that you can continue to interact with the workbench in order to set up the screenshot.");
149
				+ "Note that you can continue to interact with the workbench in order to set up the screenshot.");
129
		this.attachment = attachment;
130
		this.page = this;
131
	}
150
	}
132
151
133
	public void createControl(Composite parent) {
152
	public void createControl(Composite parent) {
134
153
		ViewForm vf = new ViewForm(parent, SWT.BORDER | SWT.FLAT);
135
		Composite composite = new Composite(parent, SWT.NONE);
154
		vf.horizontalSpacing = 0;
136
		composite.setLayout(new GridLayout());
155
		vf.verticalSpacing = 0;
137
		setControl(composite);
156
		setControl(vf);
157
		vf.setLayoutData(GridDataFactory.fillDefaults().create());
138
158
139
		allocateCursors();
159
		allocateCursors();
140
160
141
		Composite buttonsComposite = new Composite(composite, SWT.NONE);
161
		// TODO: need disabled versions of all toolbar icons
142
		buttonsComposite.setLayout(new GridLayout(3, false));
162
		ToolBarManager tbm = new ToolBarManager(SWT.FLAT | SWT.HORIZONTAL | SWT.RIGHT);
143
		captureButton = new Button(buttonsComposite, SWT.PUSH);
163
		captureAction = new Action("&Capture Desktop", IAction.AS_PUSH_BUTTON) {
144
		captureButton.setText("Capture Desktop");
164
145
		captureButton.setImage(TasksUiImages.getImage(TasksUiImages.IMAGE_CAPTURE));
165
			private boolean isFirstCapture = true;
146
		captureButton.addSelectionListener(new SelectionAdapter() {
147
166
148
			public void widgetSelected(SelectionEvent e) {
167
			@Override
168
			public void run() {
149
				captureScreenshotContent();
169
				captureScreenshotContent();
150
				page.setErrorMessage(null);
170
				setErrorMessage(null);
151
				fitButton.setEnabled(true);
171
				if (isFirstCapture) {
172
					isFirstCapture = false;
173
					fitAction.setEnabled(true);
174
					cropAction.setEnabled(true);
175
					cropAction.setChecked(true);
176
					markAction.setEnabled(true);
177
					clearAction.setEnabled(false);
178
				}
152
			}
179
			}
153
180
154
		});
181
		};
182
		captureAction.setToolTipText("Capture Desktop");
183
		captureAction.setImageDescriptor(ImageDescriptor.createFromImage(TasksUiImages.getImage(TasksUiImages.IMAGE_CAPTURE)));
155
184
156
//		captureDelayedButton = new Button(buttonsComposite, SWT.PUSH);
185
//		captureDelayedButton = new Button(buttonsComposite, SWT.PUSH);
157
//		final String captureIn = "Capture in ";
186
//		final String captureIn = "Capture in ";
Lines 186-239 Link Here
186
//			}
215
//			}
187
//		});
216
//		});
188
217
189
		fitButton = new Button(buttonsComposite, SWT.TOGGLE);
218
		fitAction = new Action("", IAction.AS_CHECK_BOX) {
190
		fitButton.setText("Fit Image");
219
			@Override
191
		fitButton.setImage(TasksUiImages.getImage(TasksUiImages.IMAGE_FIT));
220
			public void run() {
192
		fitButton.addSelectionListener(new SelectionAdapter() {
193
194
			public void widgetSelected(SelectionEvent e) {
195
				refreshCanvasSize();
221
				refreshCanvasSize();
196
			}
222
			}
223
		};
224
		fitAction.setToolTipText("Fit Image");
225
		fitAction.setText("&Fit Image");
226
		fitAction.setImageDescriptor(ImageDescriptor.createFromImage(TasksUiImages.getImage(TasksUiImages.IMAGE_FIT)));
227
		fitAction.setChecked(true);
228
		fitAction.setEnabled(false);
197
229
198
		});
230
		cropAction = new Action("C&rop", IAction.AS_RADIO_BUTTON) {
199
		fitButton.setSelection(true);
231
			@Override
200
		fitButton.setEnabled(false);
232
			public void run() {
233
				currentAction = EditorAction.CROPPING;
234
				cropAction.setChecked(true);
235
				markAction.setChecked(false);
236
				colorAction.setEnabled(false);
237
				canvas.redraw();
238
			}
239
		};
240
		cropAction.setToolTipText("Crop");
241
		cropAction.setImageDescriptor(ImageDescriptor.createFromFile(getClass(), "crop.png"));
242
		cropAction.setEnabled(false);
243
244
		markAction = new Action("&Annotate", IAction.AS_RADIO_BUTTON) {
245
			@Override
246
			public void run() {
247
				currentAction = EditorAction.MARKING;
248
				cropAction.setChecked(false);
249
				markAction.setChecked(true);
250
				colorAction.setEnabled(true);
251
				canvas.redraw();
252
			}
253
		};
254
		markAction.setToolTipText("Draw annotations on screenshot image");
255
		markAction.setImageDescriptor(ImageDescriptor.createFromFile(getClass(), "mark.gif"));
256
		markAction.setDisabledImageDescriptor(ImageDescriptor.createFromFile(getClass(), "mark_disabled.gif"));
257
		markAction.setEnabled(false);
258
259
		colorAction = new Action("", IAction.AS_DROP_DOWN_MENU) {
260
			@Override
261
			public void runWithEvent(final Event e) {
262
				final ColorSelectionWindow colorWindow = new ColorSelectionWindow(getControl().getShell()) {
263
264
					@Override
265
					protected Point getInitialLocation(Point initialSize) {
266
						ToolItem toolItem = (ToolItem) e.widget;
267
						Rectangle itemBounds = toolItem.getBounds();
268
						Point location = toolItem.getParent().toDisplay(itemBounds.x + itemBounds.width,
269
								itemBounds.y + itemBounds.height);
270
						location.x -= initialSize.x;
271
						return location;
272
					}
273
274
				};
275
				colorWindow.setBlockOnOpen(true);
276
				colorWindow.open();
277
				RGB color = colorWindow.getSelectedRGB();
278
				if (color != null) {
279
					setMarkColor(color);
280
				}
281
			}
282
		};
283
		colorAction.setToolTipText("Change pen color");
284
		colorIcon = new Image(getShell().getDisplay(), 16, 16);
285
		setMarkColor(new RGB(255, 85, 85));
286
		colorAction.setEnabled(false);
201
287
202
		scrolledComposite = new ScrolledComposite(composite, SWT.V_SCROLL | SWT.H_SCROLL | SWT.BORDER);
288
		clearAction = new Action("C&lear Annotations", IAction.AS_PUSH_BUTTON) {
203
		scrolledComposite.setLayoutData(GridDataFactory.fillDefaults()
289
			@Override
204
				.align(SWT.FILL, SWT.FILL)
290
			public void run() {
205
				.grab(true, true)
291
				clearAction.setEnabled(false);
206
				.create());
292
				workImageGC.drawImage(originalImage, 0, 0);
293
				canvas.redraw();
294
				markAttachmentDirty();
295
			}
296
		};
297
		clearAction.setToolTipText("Clear all annotations made on screenshot image");
298
		clearAction.setImageDescriptor(ImageDescriptor.createFromFile(getClass(), "erase.png"));
299
		clearAction.setEnabled(false);
300
301
		tbm.add(createAndConfigureCI(captureAction));
302
		tbm.add(createAndConfigureCI(fitAction));
303
		tbm.add(new Separator());
304
		tbm.add(createAndConfigureCI(cropAction));
305
		tbm.add(createAndConfigureCI(markAction));
306
		tbm.add(createAndConfigureCI(colorAction));
307
		tbm.add(new Separator());
308
		tbm.add(createAndConfigureCI(clearAction));
207
309
310
		scrolledComposite = new ScrolledComposite(vf, SWT.V_SCROLL | SWT.H_SCROLL);
208
		canvas = new Canvas(scrolledComposite, SWT.DOUBLE_BUFFERED);
311
		canvas = new Canvas(scrolledComposite, SWT.DOUBLE_BUFFERED);
209
		scrolledComposite.setContent(canvas);
312
		scrolledComposite.setContent(canvas);
210
		canvas.addPaintListener(new PaintListener() {
313
		canvas.addPaintListener(new PaintListener() {
211
314
212
			public void paintControl(PaintEvent e) {
315
			public void paintControl(PaintEvent e) {
213
				if (screenshotImage != null) {
316
				if (workImage != null) {
214
					Rectangle imageBounds = screenshotImage.getBounds();
317
					Rectangle imageBounds = workImage.getBounds();
215
					Rectangle canvasBounds = canvas.getClientArea();
318
					Rectangle canvasBounds = canvas.getClientArea();
216
319
217
					if (fitButton.getSelection()) {
320
					if (fitAction.isChecked()) {
218
						e.gc.drawImage(screenshotImage, 0, 0, imageBounds.width, imageBounds.height, 0, 0,
321
						e.gc.drawImage(workImage, 0, 0, imageBounds.width, imageBounds.height, 0, 0,
219
								canvasBounds.width, canvasBounds.height);
322
								canvasBounds.width, canvasBounds.height);
220
					} else {
323
					} else {
221
						e.gc.drawImage(screenshotImage, 0, 0);
324
						e.gc.drawImage(workImage, 0, 0);
222
					}
223
224
					if (currentAction == Action.IDLE) {
225
						if (currentSelection != null) {
226
							drawSelection(e.gc);
227
						}
228
					} else if (currentAction == Action.SELECTING || currentAction == Action.RESIZING_SELECTION
229
							|| currentAction == Action.MOVING_SELECTION) {
230
						if (currentSelection != null) {
231
							drawSelectionPreview(e.gc);
232
						}
233
					}
325
					}
326
					drawSelection(e.gc);
234
				} else {
327
				} else {
235
//					page.setErrorMessage("Screenshot required");
328
//					page.setErrorMessage("Screenshot required");
236
					fitButton.setEnabled(false);
329
					fitAction.setEnabled(false);
237
				}
330
				}
238
			}
331
			}
239
		});
332
		});
Lines 241-257 Link Here
241
		scrolledComposite.addControlListener(new ControlAdapter() {
334
		scrolledComposite.addControlListener(new ControlAdapter() {
242
			@Override
335
			@Override
243
			public void controlResized(ControlEvent e) {
336
			public void controlResized(ControlEvent e) {
244
				if (fitButton.getSelection()) {
337
				if (fitAction.isChecked()) {
245
					refreshCanvasSize();
338
					refreshCanvasSize();
246
				}
339
				}
247
			}
340
			}
248
		});
341
		});
249
342
343
		vf.setTopLeft(tbm.createControl(vf));
344
		vf.setContent(scrolledComposite);
250
		registerMouseListeners();
345
		registerMouseListeners();
251
	}
346
	}
252
347
348
	private ActionContributionItem createAndConfigureCI(IAction action) {
349
		ActionContributionItem ci = new ActionContributionItem(action);
350
		ci.setMode(ActionContributionItem.MODE_FORCE_TEXT);
351
		return ci;
352
	}
353
354
	private void setMarkColor(RGB color) {
355
		if (markColor != null) {
356
			markColor.dispose();
357
		}
358
		markColor = new Color(getShell().getDisplay(), color);
359
		if (workImageGC != null) {
360
			workImageGC.setForeground(markColor);
361
		}
362
363
		GC colorGC = new GC(colorIcon);
364
		colorGC.setBackground(markColor);
365
		colorGC.fillRectangle(0, 0, 16, 16);
366
		colorGC.drawRectangle(0, 0, 15, 15);
367
		colorGC.dispose();
368
369
		colorAction.setImageDescriptor(ImageDescriptor.createFromImage(colorIcon));
370
	}
371
253
	@Override
372
	@Override
254
	public void dispose() {
373
	public void dispose() {
374
		disposeImageResources();
375
		if (markColor != null) {
376
			markColor.dispose();
377
		}
378
		if (colorIcon != null) {
379
			colorIcon.dispose();
380
		}
381
255
		canvas.setCursor(null);
382
		canvas.setCursor(null);
256
		for (Cursor cursor : cursors.values()) {
383
		for (Cursor cursor : cursors.values()) {
257
			cursor.dispose();
384
			cursor.dispose();
Lines 259-266 Link Here
259
		super.dispose();
386
		super.dispose();
260
	}
387
	}
261
388
389
	private void disposeImageResources() {
390
		if (originalImage != null) {
391
			originalImage.dispose();
392
		}
393
		if (workImageGC != null) {
394
			workImageGC.dispose();
395
		}
396
		if (workImage != null) {
397
			workImage.dispose();
398
		}
399
	}
400
401
	private static final int CURSOR_MARK_TOOL = -1;
402
262
	private void allocateCursors() {
403
	private void allocateCursors() {
263
		Display display = Display.getCurrent();
404
		Display display = getShell().getDisplay();
264
		cursors.put(SWT.CURSOR_ARROW, new Cursor(display, SWT.CURSOR_ARROW));
405
		cursors.put(SWT.CURSOR_ARROW, new Cursor(display, SWT.CURSOR_ARROW));
265
		cursors.put(SWT.CURSOR_SIZEALL, new Cursor(display, SWT.CURSOR_SIZEALL));
406
		cursors.put(SWT.CURSOR_SIZEALL, new Cursor(display, SWT.CURSOR_SIZEALL));
266
		cursors.put(SWT.CURSOR_SIZENWSE, new Cursor(display, SWT.CURSOR_SIZENWSE));
407
		cursors.put(SWT.CURSOR_SIZENWSE, new Cursor(display, SWT.CURSOR_SIZENWSE));
Lines 268-273 Link Here
268
		cursors.put(SWT.CURSOR_SIZENS, new Cursor(display, SWT.CURSOR_SIZENS));
409
		cursors.put(SWT.CURSOR_SIZENS, new Cursor(display, SWT.CURSOR_SIZENS));
269
		cursors.put(SWT.CURSOR_SIZEWE, new Cursor(display, SWT.CURSOR_SIZEWE));
410
		cursors.put(SWT.CURSOR_SIZEWE, new Cursor(display, SWT.CURSOR_SIZEWE));
270
		cursors.put(SWT.CURSOR_CROSS, new Cursor(display, SWT.CURSOR_CROSS));
411
		cursors.put(SWT.CURSOR_CROSS, new Cursor(display, SWT.CURSOR_CROSS));
412
413
		// TODO: allocate custom cursor for "mark" tool
414
		cursors.put(CURSOR_MARK_TOOL, new Cursor(display, SWT.CURSOR_HAND));
271
	}
415
	}
272
416
273
	private Rectangle getScaledSelection() {
417
	private Rectangle getScaledSelection() {
Lines 278-302 Link Here
278
		int y = (int) Math.round(currentSelection.y * scaleFactor);
422
		int y = (int) Math.round(currentSelection.y * scaleFactor);
279
		int right = (int) Math.round((currentSelection.x + currentSelection.width) * scaleFactor);
423
		int right = (int) Math.round((currentSelection.x + currentSelection.width) * scaleFactor);
280
		int bottom = (int) Math.round((currentSelection.y + currentSelection.height) * scaleFactor);
424
		int bottom = (int) Math.round((currentSelection.y + currentSelection.height) * scaleFactor);
281
		int width = Math.min(right, (int) Math.round((screenshotImage.getBounds().width - 1) * scaleFactor)) - x;
425
		int width = Math.min(right, (int) Math.round((workImage.getBounds().width - 1) * scaleFactor)) - x;
282
		int height = Math.min(bottom, (int) Math.round((screenshotImage.getBounds().height - 1) * scaleFactor)) - y;
426
		int height = Math.min(bottom, (int) Math.round((workImage.getBounds().height - 1) * scaleFactor)) - y;
283
		return new Rectangle(x, y, width, height);
427
		return new Rectangle(x, y, width, height);
284
	}
428
	}
285
429
286
	@Override
430
	@Override
287
	public boolean isPageComplete() {
431
	public boolean isPageComplete() {
288
		if (screenshotImage == null)
432
		return workImage != null;
289
			return false;
290
		return true;
291
	}
433
	}
292
434
293
	@Override
435
	@Override
294
	public IWizardPage getNextPage() {
436
	public IWizardPage getNextPage() {
295
		NewAttachmentPage page = (NewAttachmentPage) getWizard().getPage("AttachmentDetails");
437
		NewAttachmentPage page = (NewAttachmentPage) getWizard().getPage("AttachmentDetails");
296
		attachment.setContentType("image/jpeg");
297
		page.setFilePath(InputAttachmentSourcePage.SCREENSHOT_LABEL);
438
		page.setFilePath(InputAttachmentSourcePage.SCREENSHOT_LABEL);
298
		page.setContentType();
439
		page.setContentType();
299
		getCropScreenshot();
300
		return page;
440
		return page;
301
	}
441
	}
302
442
Lines 306-314 Link Here
306
	}
446
	}
307
447
308
	private void captureScreenshotContent() {
448
	private void captureScreenshotContent() {
309
449
		Display display = getShell().getDisplay();
310
		final Display display = Display.getDefault();
450
		Shell wizardShell = getWizard().getContainer().getShell();
311
		final Shell wizardShell = getWizard().getContainer().getShell();
312
		wizardShell.setVisible(false);
451
		wizardShell.setVisible(false);
313
452
314
		// NOTE: need a wait since the shell can take time to disappear (e.g. fade on Vista)
453
		// NOTE: need a wait since the shell can take time to disappear (e.g. fade on Vista)
Lines 318-341 Link Here
318
			// ignore
457
			// ignore
319
		}
458
		}
320
459
321
		display.asyncExec(new Runnable() {
460
		disposeImageResources();
322
			public void run() {
461
		Rectangle displayBounds = display.getBounds();
323
				Rectangle displayBounds = display.getBounds();
462
		originalImage = new Image(display, displayBounds);
324
				screenshotImage = new Image(display, displayBounds);
463
		workImage = new Image(display, displayBounds);
464
465
		GC gc = new GC(display);
466
		gc.copyArea(originalImage, 0, 0);
467
		gc.copyArea(workImage, 0, 0);
468
		gc.dispose();
325
469
326
				GC gc = new GC(display);
470
		workImageGC = new GC(workImage);
327
				gc.copyArea(screenshotImage, 0, 0);
471
		workImageGC.setForeground(markColor);
328
				gc.dispose();
472
		workImageGC.setLineWidth(4);
473
		workImageGC.setLineCap(SWT.CAP_ROUND);
329
474
330
				clearSelection();
475
		clearSelection();
331
				refreshCanvasSize();
476
		refreshCanvasSize();
332
477
333
				wizardShell.setVisible(true);
478
		wizardShell.setVisible(true);
334
				if (screenshotImage != null) {
479
		setPageComplete(true);
335
					setPageComplete(true);
336
				}
337
			}
338
		});
339
	}
480
	}
340
481
341
	/**
482
	/**
Lines 352-358 Link Here
352
		currentSelection = new Rectangle(startX, startY, width, height);
493
		currentSelection = new Rectangle(startX, startY, width, height);
353
494
354
		// Decreases 1 pixel size from original image because Rectangle.intersect() consider them as right-bottom limit
495
		// Decreases 1 pixel size from original image because Rectangle.intersect() consider them as right-bottom limit
355
		Rectangle imageBounds = screenshotImage.getBounds();
496
		Rectangle imageBounds = workImage.getBounds();
356
		imageBounds.width--;
497
		imageBounds.width--;
357
		imageBounds.height--;
498
		imageBounds.height--;
358
		currentSelection.intersect(imageBounds);
499
		currentSelection.intersect(imageBounds);
Lines 363-374 Link Here
363
	 * level is changed
504
	 * level is changed
364
	 */
505
	 */
365
	private void setUpGrabPoints() {
506
	private void setUpGrabPoints() {
507
		grabPoints.clear();
366
		if (currentSelection == null) {
508
		if (currentSelection == null) {
367
			return;
509
			return;
368
		}
510
		}
369
511
370
		canvas.setCursor(null);
371
		grabPoints.clear();
372
		Rectangle scaledSelection = getScaledSelection();
512
		Rectangle scaledSelection = getScaledSelection();
373
		grabPoints.add(GrabPoint.createGrabPoint(scaledSelection.x, scaledSelection.y, SWT.CURSOR_SIZENWSE, EnumSet.of(
513
		grabPoints.add(GrabPoint.createGrabPoint(scaledSelection.x, scaledSelection.y, SWT.CURSOR_SIZENWSE, EnumSet.of(
374
				SelectionSide.LEFT, SelectionSide.TOP)));
514
				SelectionSide.LEFT, SelectionSide.TOP)));
Lines 393-399 Link Here
393
				originalSelection.height);
533
				originalSelection.height);
394
		int deltaX = x - startPoint.x;
534
		int deltaX = x - startPoint.x;
395
		int deltaY = y - startPoint.y;
535
		int deltaY = y - startPoint.y;
396
		Rectangle imageBounds = screenshotImage.getBounds();
536
		Rectangle imageBounds = workImage.getBounds();
397
537
398
		// Check current selection limits
538
		// Check current selection limits
399
		if (resizableSides.contains(SelectionSide.LEFT)) {
539
		if (resizableSides.contains(SelectionSide.LEFT)) {
Lines 436-441 Link Here
436
		if (resizableSides.contains(SelectionSide.BOTTOM)) {
576
		if (resizableSides.contains(SelectionSide.BOTTOM)) {
437
			currentSelection.height += deltaY;
577
			currentSelection.height += deltaY;
438
		}
578
		}
579
580
		setUpGrabPoints();
439
	}
581
	}
440
582
441
	private void refreshSelectionPosition(int x, int y) {
583
	private void refreshSelectionPosition(int x, int y) {
Lines 447-453 Link Here
447
		if (newY < 0) {
589
		if (newY < 0) {
448
			newY = 0;
590
			newY = 0;
449
		}
591
		}
450
		Rectangle imageBounds = screenshotImage.getBounds();
592
		Rectangle imageBounds = workImage.getBounds();
451
		if (newX + originalSelection.width - 1 > imageBounds.width) {
593
		if (newX + originalSelection.width - 1 > imageBounds.width) {
452
			newX = imageBounds.width - originalSelection.width;
594
			newX = imageBounds.width - originalSelection.width;
453
		}
595
		}
Lines 455-460 Link Here
455
			newY = imageBounds.height - originalSelection.height;
597
			newY = imageBounds.height - originalSelection.height;
456
		}
598
		}
457
		currentSelection = new Rectangle(newX, newY, originalSelection.width, originalSelection.height);
599
		currentSelection = new Rectangle(newX, newY, originalSelection.width, originalSelection.height);
600
601
		setUpGrabPoints();
458
	}
602
	}
459
603
460
	private void registerMouseListeners() {
604
	private void registerMouseListeners() {
Lines 464-480 Link Here
464
			 * If a selection is in course, moving the mouse around refreshes the selection rectangle
608
			 * If a selection is in course, moving the mouse around refreshes the selection rectangle
465
			 */
609
			 */
466
			public void mouseMove(MouseEvent e) {
610
			public void mouseMove(MouseEvent e) {
467
				if (currentAction == Action.SELECTING) {
611
				int scaledX = (int) Math.round(e.x / scaleFactor);
468
					// Selection in course
612
				int scaledY = (int) Math.round(e.y / scaleFactor);
469
					refreshCurrentSelection((int) Math.round(e.x / scaleFactor), (int) Math.round(e.y / scaleFactor));
613
614
				if (currentAction == EditorAction.SELECTING) {
615
					refreshCurrentSelection(scaledX, scaledY);
470
					canvas.redraw();
616
					canvas.redraw();
471
				} else if (currentAction == Action.RESIZING_SELECTION) {
617
				} else if (currentAction == EditorAction.RESIZING_SELECTION) {
472
					refreshSelectionResize((int) Math.round(e.x / scaleFactor), (int) Math.round(e.y / scaleFactor));
618
					refreshSelectionResize(scaledX, scaledY);
473
					canvas.redraw();
619
					canvas.redraw();
474
				} else if (currentAction == Action.MOVING_SELECTION) {
620
				} else if (currentAction == EditorAction.MOVING_SELECTION) {
475
					refreshSelectionPosition((int) Math.round(e.x / scaleFactor), (int) Math.round(e.y / scaleFactor));
621
					refreshSelectionPosition(scaledX, scaledY);
476
					canvas.redraw();
622
					canvas.redraw();
477
				} else if (currentAction == Action.IDLE && currentSelection != null) {
623
				} else if (currentAction == EditorAction.CROPPING && currentSelection != null) {
478
					boolean cursorSet = false;
624
					boolean cursorSet = false;
479
625
480
					// No selection in course, but have something selected; first test if I'm hovering some grab point
626
					// No selection in course, but have something selected; first test if I'm hovering some grab point
Lines 491-498 Link Here
491
						canvas.setCursor(cursors.get(SWT.CURSOR_SIZEALL));
637
						canvas.setCursor(cursors.get(SWT.CURSOR_SIZEALL));
492
						cursorSet = true;
638
						cursorSet = true;
493
					}
639
					}
494
					if (!cursorSet && canvas.getCursor() != null) {
640
495
						canvas.setCursor(null);
641
					// If I'm out, the default cursor for cropping mode is cross
642
					Cursor crossCursor = cursors.get(SWT.CURSOR_CROSS);
643
					if (!cursorSet && canvas.getCursor() != crossCursor) {
644
						canvas.setCursor(crossCursor);
645
					}
646
				} else if (currentAction == EditorAction.MARKING) {
647
					drawMarkLine(scaledX, scaledY);
648
649
					Cursor markCursor = cursors.get(CURSOR_MARK_TOOL);
650
					if (canvas.getCursor() != markCursor) {
651
						canvas.setCursor(markCursor);
496
					}
652
					}
497
				}
653
				}
498
			}
654
			}
Lines 502-522 Link Here
502
		canvas.addMouseListener(new MouseAdapter() {
658
		canvas.addMouseListener(new MouseAdapter() {
503
659
504
			/**
660
			/**
505
			 * Releasing the mouse button ends the selection; compute the selection rectangle and redraw the cropped
661
			 * Releasing the mouse button ends the selection or a drawing; compute the selection rectangle and redraw
506
			 * image
662
			 * the cropped image
507
			 */
663
			 */
508
			public void mouseUp(MouseEvent e) {
664
			public void mouseUp(MouseEvent e) {
509
				if (currentAction == Action.SELECTING || currentAction == Action.RESIZING_SELECTION
665
				if (currentAction == EditorAction.SELECTING || currentAction == EditorAction.RESIZING_SELECTION
510
						|| currentAction == Action.MOVING_SELECTION) {
666
						|| currentAction == EditorAction.MOVING_SELECTION) {
511
					canvas.setCursor(cursors.get(SWT.CURSOR_ARROW));
512
513
					int scaledX = (int) Math.round(e.x / scaleFactor);
667
					int scaledX = (int) Math.round(e.x / scaleFactor);
514
					int scaledY = (int) Math.round(e.y / scaleFactor);
668
					int scaledY = (int) Math.round(e.y / scaleFactor);
515
					if (currentAction == Action.SELECTING) {
669
					if (currentAction == EditorAction.SELECTING) {
516
						refreshCurrentSelection(scaledX, scaledY);
670
						refreshCurrentSelection(scaledX, scaledY);
517
					} else if (currentAction == Action.RESIZING_SELECTION) {
671
					} else if (currentAction == EditorAction.RESIZING_SELECTION) {
518
						refreshSelectionResize(scaledX, scaledY);
672
						refreshSelectionResize(scaledX, scaledY);
519
					} else if (currentAction == Action.MOVING_SELECTION) {
673
					} else if (currentAction == EditorAction.MOVING_SELECTION) {
520
						refreshSelectionPosition(scaledX, scaledY);
674
						refreshSelectionPosition(scaledX, scaledY);
521
					}
675
					}
522
					if (currentSelection.width == 0 && currentSelection.height == 0) {
676
					if (currentSelection.width == 0 && currentSelection.height == 0) {
Lines 524-540 Link Here
524
					}
678
					}
525
					setUpGrabPoints();
679
					setUpGrabPoints();
526
					startPoint = null;
680
					startPoint = null;
527
					currentAction = Action.IDLE;
681
					currentAction = EditorAction.CROPPING;
528
682
529
					canvas.redraw();
683
					canvas.redraw();
684
					markAttachmentDirty();
685
				} else if (currentAction == EditorAction.MARKING) {
686
					startPoint = null;
687
					markAttachmentDirty();
530
				}
688
				}
531
			}
689
			}
532
690
533
			/**
691
			/**
534
			 * Pressing mouse button starts a selection; normalizes and marks the start point
692
			 * Pressing mouse button starts a selection or a drawing; normalizes and marks the start point
535
			 */
693
			 */
536
			public void mouseDown(MouseEvent e) {
694
			public void mouseDown(MouseEvent e) {
537
				if (currentAction != Action.IDLE) {
695
				int scaledX = (int) (e.x / scaleFactor);
696
				int scaledY = (int) (e.y / scaleFactor);
697
698
				if (currentAction == EditorAction.MARKING) {
699
					startPoint = new Point(scaledX, scaledY);
700
					drawMarkLine(scaledX, scaledY);
701
					canvas.setCursor(cursors.get(CURSOR_MARK_TOOL));
702
					return;
703
				} else if (currentAction != EditorAction.CROPPING) {
538
					return;
704
					return;
539
				}
705
				}
540
706
Lines 543-551 Link Here
543
					for (GrabPoint point : grabPoints) {
709
					for (GrabPoint point : grabPoints) {
544
						if (point.grabArea.contains(e.x, e.y)) {
710
						if (point.grabArea.contains(e.x, e.y)) {
545
							originalSelection = currentSelection;
711
							originalSelection = currentSelection;
546
							currentAction = Action.RESIZING_SELECTION;
712
							currentAction = EditorAction.RESIZING_SELECTION;
547
							resizableSides = point.resizableSides;
713
							resizableSides = point.resizableSides;
548
							startPoint = new Point((int) (e.x / scaleFactor), (int) (e.y / scaleFactor));
714
							startPoint = new Point(scaledX, scaledY);
549
							canvas.redraw();
715
							canvas.redraw();
550
							return;
716
							return;
551
						}
717
						}
Lines 553-572 Link Here
553
				}
719
				}
554
720
555
				// Check if I could move the selection
721
				// Check if I could move the selection
556
				if (currentSelection != null
722
				if (currentSelection != null && currentSelection.contains(scaledX, scaledY)) {
557
						&& currentSelection.contains((int) (e.x / scaleFactor), (int) (e.y / scaleFactor))) {
558
					originalSelection = currentSelection;
723
					originalSelection = currentSelection;
559
					currentAction = Action.MOVING_SELECTION;
724
					currentAction = EditorAction.MOVING_SELECTION;
560
					startPoint = new Point((int) (e.x / scaleFactor), (int) (e.y / scaleFactor));
725
					startPoint = new Point(scaledX, scaledY);
561
					canvas.redraw();
726
					canvas.redraw();
562
					return;
727
					return;
563
				}
728
				}
564
729
565
				// Do a simple selection
730
				// Do a simple selection
566
				canvas.setCursor(cursors.get(SWT.CURSOR_CROSS));
731
				canvas.setCursor(cursors.get(SWT.CURSOR_CROSS));
567
				currentAction = Action.SELECTING;
732
				currentAction = EditorAction.SELECTING;
568
				currentSelection = null;
733
				currentSelection = null;
569
				startPoint = new Point((int) (e.x / scaleFactor), (int) (e.y / scaleFactor));
734
				startPoint = new Point(scaledX, scaledY);
735
				setUpGrabPoints();
570
				canvas.redraw();
736
				canvas.redraw();
571
			}
737
			}
572
738
Lines 577-589 Link Here
577
	private void clearSelection() {
743
	private void clearSelection() {
578
		currentSelection = null;
744
		currentSelection = null;
579
		startPoint = null;
745
		startPoint = null;
746
		markAttachmentDirty();
580
	}
747
	}
581
748
582
	/**
749
	/**
583
	 * Recalculates image canvas size based on "fit on canvas" setting, set up the grab points, and redraws
750
	 * Recalculates image canvas size based on "fit on canvas" setting, set up the grab points, and redraws
584
	 * <p>
751
	 * <p>
585
	 * This method should be called whenever the {@link #screenshotImage image} <strong>visible</strong> size is
752
	 * This method should be called whenever the {@link #workImage image} <strong>visible</strong> size is changed,
586
	 * changed, which can happen when:
753
	 * which can happen when:
587
	 * <p>
754
	 * <p>
588
	 * <ul>
755
	 * <ul>
589
	 * <li>The "Fit Image" setting is changed, so the image zoom level changes
756
	 * <li>The "Fit Image" setting is changed, so the image zoom level changes
Lines 595-609 Link Here
595
	 * Calling this method under other circumstances may lead to strange behavior in the scrolled composite
762
	 * Calling this method under other circumstances may lead to strange behavior in the scrolled composite
596
	 */
763
	 */
597
	private void refreshCanvasSize() {
764
	private void refreshCanvasSize() {
598
		if (fitButton.getSelection()) {
765
		if (fitAction.isChecked()) {
599
			// This little hack is necessary to get the client area without scrollbars; 
766
			// This little hack is necessary to get the client area without scrollbars; 
600
			// they'll be automatically restored if necessary after Canvas.setBounds()
767
			// they'll be automatically restored if necessary after Canvas.setBounds()
601
			scrolledComposite.getHorizontalBar().setVisible(false);
768
			scrolledComposite.getHorizontalBar().setVisible(false);
602
			scrolledComposite.getVerticalBar().setVisible(false);
769
			scrolledComposite.getVerticalBar().setVisible(false);
603
770
604
			Rectangle bounds = scrolledComposite.getClientArea();
771
			Rectangle bounds = scrolledComposite.getClientArea();
605
			if (screenshotImage != null) {
772
			if (workImage != null) {
606
				Rectangle imageBounds = screenshotImage.getBounds();
773
				Rectangle imageBounds = workImage.getBounds();
607
				if (imageBounds.width > bounds.width || imageBounds.height > bounds.height) {
774
				if (imageBounds.width > bounds.width || imageBounds.height > bounds.height) {
608
					double xRatio = (double) bounds.width / imageBounds.width;
775
					double xRatio = (double) bounds.width / imageBounds.width;
609
					double yRatio = (double) bounds.height / imageBounds.height;
776
					double yRatio = (double) bounds.height / imageBounds.height;
Lines 616-623 Link Here
616
		} else {
783
		} else {
617
			scaleFactor = 1.0;
784
			scaleFactor = 1.0;
618
			Rectangle bounds = scrolledComposite.getClientArea();
785
			Rectangle bounds = scrolledComposite.getClientArea();
619
			if (screenshotImage != null) {
786
			if (workImage != null) {
620
				Rectangle imageBounds = screenshotImage.getBounds();
787
				Rectangle imageBounds = workImage.getBounds();
621
				bounds.width = imageBounds.width;
788
				bounds.width = imageBounds.width;
622
				bounds.height = imageBounds.height;
789
				bounds.height = imageBounds.height;
623
			}
790
			}
Lines 628-652 Link Here
628
	}
795
	}
629
796
630
	/**
797
	/**
631
	 * Decorates the screenshot canvas with the selection rectangle; this is done while still selecting, so it does not
632
	 * have all adornments of a {@link #drawSelection(GC) finished} selection
633
	 */
634
	@SuppressWarnings("deprecation")
635
	private void drawSelectionPreview(GC gc) {
636
		gc.setLineDash(new int[] { 4 });
637
		gc.setXORMode(true);
638
		gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
639
		gc.drawRectangle(getScaledSelection());
640
		gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
641
		gc.drawRectangle(getScaledSelection());
642
		gc.setXORMode(false);
643
		gc.setLineStyle(SWT.LINE_SOLID);
644
	}
645
646
	/**
647
	 * Decorates the screenshot canvas with the selection rectangle, resize grab points and other adornments
798
	 * Decorates the screenshot canvas with the selection rectangle, resize grab points and other adornments
648
	 */
799
	 */
649
	private void drawSelection(GC gc) {
800
	private void drawSelection(GC gc) {
801
		if (currentSelection == null) {
802
			return;
803
		}
650
		Rectangle scaledSelection = getScaledSelection();
804
		Rectangle scaledSelection = getScaledSelection();
651
805
652
		// Draw shadow
806
		// Draw shadow
Lines 666-683 Link Here
666
820
667
		// Draw selection rectangle
821
		// Draw selection rectangle
668
		gc.setLineStyle(SWT.LINE_SOLID);
822
		gc.setLineStyle(SWT.LINE_SOLID);
669
		gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY));
823
		gc.setForeground(getShell().getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY));
670
		gc.drawRectangle(scaledSelection);
824
		gc.drawRectangle(scaledSelection);
671
825
672
		// Draw grab points
826
		// Draw grab points
673
		gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE));
827
		gc.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_WHITE));
674
		gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK));
828
		gc.setForeground(getShell().getDisplay().getSystemColor(SWT.COLOR_BLACK));
675
		for (GrabPoint point : grabPoints) {
829
		for (GrabPoint point : grabPoints) {
676
			gc.fillRectangle(point.grabArea);
830
			gc.fillRectangle(point.grabArea);
677
			gc.drawRectangle(point.grabArea);
831
			gc.drawRectangle(point.grabArea);
678
		}
832
		}
679
	}
833
	}
680
834
835
	/**
836
	 * Connects the previous mark point to the new reference point, by drawing a new line
837
	 */
838
	private void drawMarkLine(int x, int y) {
839
		if (startPoint != null) {
840
			clearAction.setEnabled(true);
841
			workImageGC.drawLine(startPoint.x, startPoint.y, x, y);
842
			startPoint.x = x;
843
			startPoint.y = y;
844
			canvas.redraw();
845
		}
846
	}
847
681
	private static enum SelectionSide {
848
	private static enum SelectionSide {
682
849
683
		LEFT, RIGHT, TOP, BOTTOM;
850
		LEFT, RIGHT, TOP, BOTTOM;
Lines 706-727 Link Here
706
873
707
	private List<GrabPoint> grabPoints = new ArrayList<GrabPoint>(8);
874
	private List<GrabPoint> grabPoints = new ArrayList<GrabPoint>(8);
708
875
709
	private Image getCropScreenshot() {
876
	/**
710
		if (currentSelection == null) {
877
	 * Creates the final screenshot
711
			return screenshotImage;
878
	 * 
879
	 * @return The final screenshot, with all markings, and cropped according to user settings; <strong>The caller is
880
	 *         responsible for disposing the returned image</strong>
881
	 */
882
	public Image createImage() {
883
		Image screenshot = new Image(getShell().getDisplay(), currentSelection != null ? currentSelection
884
				: workImage.getBounds());
885
886
		GC gc = new GC(screenshot);
887
		if (currentSelection != null) {
888
			gc.drawImage(workImage, currentSelection.x, currentSelection.y, currentSelection.width,
889
					currentSelection.height, 0, 0, currentSelection.width, currentSelection.height);
890
		} else {
891
			gc.drawImage(workImage, 0, 0);
712
		}
892
		}
713
714
		Image image = new Image(Display.getDefault(), currentSelection);
715
		GC gc = new GC(image);
716
		gc.drawImage(screenshotImage, currentSelection.x, currentSelection.y, currentSelection.width,
717
				currentSelection.height, 0, 0, currentSelection.width, currentSelection.height);
718
		gc.dispose();
893
		gc.dispose();
719
894
720
		return image;
895
		return screenshot;
721
	}
896
	}
722
897
723
	public Image getScreenshotImage() {
898
	private void markAttachmentDirty() {
724
		return getCropScreenshot();
899
		NewAttachmentWizard wizard = (NewAttachmentWizard) getWizard();
900
		((ImageAttachment) wizard.getAttachment()).markDirty();
725
	}
901
	}
726
902
727
}
903
}
(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/ColorSelectionWindow.java (+116 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2004, 2007 Mylyn project committers and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *******************************************************************************/
8
9
package org.eclipse.mylyn.internal.tasks.ui.wizards;
10
11
import org.eclipse.jface.layout.GridDataFactory;
12
import org.eclipse.jface.layout.GridLayoutFactory;
13
import org.eclipse.jface.window.Window;
14
import org.eclipse.swt.SWT;
15
import org.eclipse.swt.events.MouseAdapter;
16
import org.eclipse.swt.events.MouseEvent;
17
import org.eclipse.swt.events.SelectionAdapter;
18
import org.eclipse.swt.events.SelectionEvent;
19
import org.eclipse.swt.events.ShellAdapter;
20
import org.eclipse.swt.events.ShellEvent;
21
import org.eclipse.swt.graphics.RGB;
22
import org.eclipse.swt.widgets.Button;
23
import org.eclipse.swt.widgets.Composite;
24
import org.eclipse.swt.widgets.Control;
25
import org.eclipse.swt.widgets.Shell;
26
27
/**
28
 * Popup window for color selection
29
 * 
30
 * @author Willian Mitsuda
31
 */
32
public class ColorSelectionWindow extends Window {
33
34
	private ColorCanvas[] colors;
35
36
	public ColorSelectionWindow(Shell shell) {
37
		super(shell);
38
		setShellStyle(SWT.BORDER);
39
	}
40
41
	@Override
42
	protected void configureShell(Shell newShell) {
43
		super.configureShell(newShell);
44
		newShell.addShellListener(new ShellAdapter() {
45
46
			@Override
47
			public void shellDeactivated(ShellEvent e) {
48
				close();
49
			}
50
51
		});
52
	}
53
54
	@Override
55
	protected Control createContents(Composite parent) {
56
		Composite colorComposite = new Composite(parent, SWT.NONE);
57
		colorComposite.setBackground(getShell().getDisplay().getSystemColor(SWT.COLOR_BLACK));
58
		colorComposite.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
59
		colorComposite.setLayout(GridLayoutFactory.fillDefaults().numColumns(8).spacing(1, 1).margins(1, 1).equalWidth(
60
				true).create());
61
62
		// EGA classic palette
63
		colors = new ColorCanvas[16];
64
		colors[0] = createColorCanvas(colorComposite, new RGB(0, 0, 0));
65
		colors[1] = createColorCanvas(colorComposite, new RGB(0, 0, 170));
66
		colors[2] = createColorCanvas(colorComposite, new RGB(0, 170, 0));
67
		colors[3] = createColorCanvas(colorComposite, new RGB(0, 170, 170));
68
		colors[4] = createColorCanvas(colorComposite, new RGB(170, 0, 0));
69
		colors[5] = createColorCanvas(colorComposite, new RGB(170, 0, 170));
70
		colors[6] = createColorCanvas(colorComposite, new RGB(170, 85, 0));
71
		colors[7] = createColorCanvas(colorComposite, new RGB(170, 170, 170));
72
		colors[8] = createColorCanvas(colorComposite, new RGB(85, 85, 85));
73
		colors[9] = createColorCanvas(colorComposite, new RGB(85, 85, 255));
74
		colors[10] = createColorCanvas(colorComposite, new RGB(85, 255, 85));
75
		colors[11] = createColorCanvas(colorComposite, new RGB(85, 255, 255));
76
		colors[12] = createColorCanvas(colorComposite, new RGB(255, 85, 85));
77
		colors[13] = createColorCanvas(colorComposite, new RGB(255, 85, 255));
78
		colors[14] = createColorCanvas(colorComposite, new RGB(255, 255, 85));
79
		colors[15] = createColorCanvas(colorComposite, new RGB(255, 255, 255));
80
81
		Button closeButton = new Button(parent, SWT.PUSH);
82
		closeButton.setText("&Close");
83
		closeButton.setLayoutData(GridDataFactory.fillDefaults().grab(true, true).create());
84
		closeButton.addSelectionListener(new SelectionAdapter() {
85
86
			@Override
87
			public void widgetSelected(SelectionEvent e) {
88
				close();
89
			}
90
91
		});
92
		return parent;
93
	}
94
95
	private ColorCanvas createColorCanvas(Composite parent, RGB rgb) {
96
		final ColorCanvas canvas = new ColorCanvas(parent, SWT.NONE, rgb);
97
		canvas.setLayoutData(GridDataFactory.fillDefaults().hint(16, 16).create());
98
		canvas.addMouseListener(new MouseAdapter() {
99
100
			@Override
101
			public void mouseDown(MouseEvent e) {
102
				selectedRGB = canvas.getRGB();
103
				close();
104
			}
105
106
		});
107
		return canvas;
108
	}
109
110
	private RGB selectedRGB;
111
112
	public RGB getSelectedRGB() {
113
		return selectedRGB;
114
	}
115
116
}
(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/ImageAttachment.java (+90 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2004, 2007 Mylyn project committers and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *******************************************************************************/
8
9
package org.eclipse.mylyn.internal.tasks.ui.wizards;
10
11
import java.io.File;
12
13
import org.eclipse.mylyn.internal.tasks.core.LocalAttachment;
14
import org.eclipse.mylyn.tasks.ui.TasksUiPlugin;
15
import org.eclipse.swt.SWT;
16
import org.eclipse.swt.graphics.Image;
17
import org.eclipse.swt.graphics.ImageData;
18
import org.eclipse.swt.graphics.ImageLoader;
19
20
/**
21
 * Represents a attachment created from a {@link Image}; handles lazy persistence into a {@link File} and image data
22
 * change
23
 * 
24
 * @author Willian Mitsuda
25
 */
26
public class ImageAttachment extends LocalAttachment {
27
28
	private static final long serialVersionUID = 28264291629999181L;
29
30
	/**
31
	 * Provides the {@link Image} object that will be converted to desired file format, and then attached
32
	 */
33
	private IImageCreator imageCreator;
34
35
	public ImageAttachment(IImageCreator imageCreator) {
36
		this.imageCreator = imageCreator;
37
	}
38
39
	@Override
40
	public void setContentType(String contentType) {
41
		// Does not apply; actually always save as JPEG
42
		// Will be implemented on bug#210179
43
	}
44
45
	@Override
46
	public String getContentType() {
47
		return "image/jpeg";
48
	}
49
50
	@Override
51
	public String getFilename() {
52
		return "screenshot.jpg";
53
	}
54
55
	private boolean dirty = true;
56
57
	public void markDirty() {
58
		dirty = true;
59
	}
60
61
	public void ensureImageFileWasCreated() {
62
		if (!dirty) {
63
			return;
64
		}
65
66
		dirty = false;
67
		createContents();
68
	}
69
70
	private void createContents() {
71
		Image image = imageCreator.createImage();
72
		try {
73
			String path = TasksUiPlugin.getDefault().getDefaultDataDirectory();
74
			ImageLoader loader = new ImageLoader();
75
			loader.data = new ImageData[] { image.getImageData() };
76
			String fileName = path + "/" + getFilename();
77
			loader.save(fileName, SWT.IMAGE_JPEG);
78
			setFile(new File(fileName));
79
			setFilePath(fileName);
80
		} finally {
81
			image.dispose();
82
		}
83
	}
84
85
	public void clearImageFile() {
86
		String path = TasksUiPlugin.getDefault().getDefaultDataDirectory();
87
		new File(path + "/" + getFilename()).delete();
88
	}
89
90
}
(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/ColorCanvas.java (+49 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2004, 2007 Mylyn project committers and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *******************************************************************************/
8
9
package org.eclipse.mylyn.internal.tasks.ui.wizards;
10
11
import org.eclipse.swt.events.DisposeEvent;
12
import org.eclipse.swt.events.DisposeListener;
13
import org.eclipse.swt.events.PaintEvent;
14
import org.eclipse.swt.events.PaintListener;
15
import org.eclipse.swt.graphics.Color;
16
import org.eclipse.swt.graphics.RGB;
17
import org.eclipse.swt.widgets.Canvas;
18
import org.eclipse.swt.widgets.Composite;
19
20
/**
21
 * A tiny control just for color display
22
 * 
23
 * @author Willian Mitsuda
24
 */
25
public class ColorCanvas extends Canvas {
26
27
	private Color color;
28
29
	public ColorCanvas(Composite parent, int style, RGB rgb) {
30
		super(parent, style);
31
		color = new Color(parent.getDisplay(), rgb);
32
		addPaintListener(new PaintListener() {
33
			public void paintControl(PaintEvent e) {
34
				e.gc.setBackground(color);
35
				e.gc.fillRectangle(getClientArea());
36
			}
37
		});
38
		addDisposeListener(new DisposeListener() {
39
			public void widgetDisposed(DisposeEvent e) {
40
				color.dispose();
41
			}
42
		});
43
	}
44
45
	public RGB getRGB() {
46
		return color.getRGB();
47
	}
48
49
}
(-)src/org/eclipse/mylyn/internal/tasks/ui/wizards/IImageCreator.java (+25 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2004, 2007 Mylyn project committers and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *******************************************************************************/
8
9
package org.eclipse.mylyn.internal.tasks.ui.wizards;
10
11
import org.eclipse.swt.graphics.Image;
12
13
/**
14
 * Something capable of creating {@link Image images}
15
 * 
16
 * @author Willian Mitsuda
17
 */
18
public interface IImageCreator {
19
20
	/**
21
	 * Creates a {@link Image} object it represents; the caller is responsible for disposing the returned {@link Image}
22
	 */
23
	public Image createImage();
24
25
}

Return to bug 195691