1 /*******************************************************************************
2  * Copyright (c) 2000, 2018 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.swt.widgets;
15 
16 
17 import org.eclipse.swt.*;
18 import org.eclipse.swt.graphics.*;
19 import org.eclipse.swt.internal.*;
20 import org.eclipse.swt.internal.cairo.*;
21 import org.eclipse.swt.internal.gtk.*;
22 
23 /**
24  * Instances of this class provide a surface for drawing
25  * arbitrary graphics.
26  * <dl>
27  * <dt><b>Styles:</b></dt>
28  * <dd>(none)</dd>
29  * <dt><b>Events:</b></dt>
30  * <dd>(none)</dd>
31  * </dl>
32  * <p>
33  * This class may be subclassed by custom control implementors
34  * who are building controls that are <em>not</em> constructed
35  * from aggregates of other controls. That is, they are either
36  * painted using SWT graphics calls or are handled by native
37  * methods.
38  * </p>
39  *
40  * @see Composite
41  * @see <a href="http://www.eclipse.org/swt/snippets/#canvas">Canvas snippets</a>
42  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
43  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
44  */
45 public class Canvas extends Composite {
46 	Caret caret;
47 	IME ime;
48 	boolean blink, drawFlag;
49 
Canvas()50 Canvas () {}
51 
52 /**
53  * Constructs a new instance of this class given its parent
54  * and a style value describing its behavior and appearance.
55  * <p>
56  * The style value is either one of the style constants defined in
57  * class <code>SWT</code> which is applicable to instances of this
58  * class, or must be built by <em>bitwise OR</em>'ing together
59  * (that is, using the <code>int</code> "|" operator) two or more
60  * of those <code>SWT</code> style constants. The class description
61  * lists the style constants that are applicable to the class.
62  * Style bits are also inherited from superclasses.
63  * </p>
64  *
65  * @param parent a composite control which will be the parent of the new instance (cannot be null)
66  * @param style the style of control to construct
67  *
68  * @exception IllegalArgumentException <ul>
69  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
70  * </ul>
71  * @exception SWTException <ul>
72  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
73  * </ul>
74  *
75  * @see SWT
76  * @see Widget#getStyle
77  */
Canvas(Composite parent, int style)78 public Canvas (Composite parent, int style) {
79 	super (parent, checkStyle (style));
80 }
81 
82 /**
83  * Fills the interior of the rectangle specified by the arguments,
84  * with the receiver's background.
85  *
86  * @param gc the gc where the rectangle is to be filled
87  * @param x the x coordinate of the rectangle to be filled
88  * @param y the y coordinate of the rectangle to be filled
89  * @param width the width of the rectangle to be filled
90  * @param height the height of the rectangle to be filled
91  *
92  * @exception IllegalArgumentException <ul>
93  *    <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
94  *    <li>ERROR_INVALID_ARGUMENT - if the gc has been disposed</li>
95  * </ul>
96  * @exception SWTException <ul>
97  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
98  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
99  * </ul>
100  *
101  * @since 3.2
102  */
drawBackground(GC gc, int x, int y, int width, int height)103 public void drawBackground (GC gc, int x, int y, int width, int height) {
104 	drawBackground (gc, x, y, width, height, 0, 0);
105 }
106 
107 /**
108  * Returns the caret.
109  * <p>
110  * The caret for the control is automatically hidden
111  * and shown when the control is painted or resized,
112  * when focus is gained or lost and when an the control
113  * is scrolled.  To avoid drawing on top of the caret,
114  * the programmer must hide and show the caret when
115  * drawing in the window any other time.
116  * </p>
117  *
118  * @return the caret for the receiver, may be null
119  *
120  * @exception SWTException <ul>
121  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
122  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
123  * </ul>
124  */
getCaret()125 public Caret getCaret () {
126 	checkWidget();
127 	return caret;
128 }
129 
130 @Override
getIMCaretPos()131 Point getIMCaretPos () {
132 	if (caret == null) return super.getIMCaretPos ();
133 	return new Point (caret.x, caret.y);
134 }
135 
136 /**
137  * Returns the IME.
138  *
139  * @return the IME
140  *
141  * @exception SWTException <ul>
142  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
143  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
144  * </ul>
145  *
146  * @since 3.4
147  */
getIME()148 public IME getIME () {
149 	checkWidget ();
150 	return ime;
151 }
152 
153 @Override
gtk_button_press_event(long widget, long event)154 long gtk_button_press_event (long widget, long event) {
155 	if (ime != null) {
156 		long result = ime.gtk_button_press_event (widget, event);
157 		if (result != 0) return result;
158 	}
159 	return  super.gtk_button_press_event (widget, event);
160 }
161 
162 @Override
gtk_commit(long imcontext, long text)163 long gtk_commit (long imcontext, long text) {
164 	if (ime != null) {
165 		long result = ime.gtk_commit (imcontext, text);
166 		if (result != 0) return result;
167 	}
168 	return super.gtk_commit (imcontext, text);
169 }
170 
171 @Override
gtk_draw(long widget, long cairo)172 long gtk_draw (long widget, long cairo) {
173 	if ((state & OBSCURED) != 0) return 0;
174 	long result;
175 	if ( GTK.GTK_VERSION < OS.VERSION(3, 22, 0)) {
176 		boolean isFocus = caret != null && caret.isFocusCaret ();
177 		if (isFocus) caret.killFocus ();
178 		result = super.gtk_draw (widget, cairo);
179 		if (isFocus) caret.setFocus ();
180 	} else {
181 		result = super.gtk_draw (widget, cairo);
182 		/*
183 		 *  blink is needed to be checked as gtk_draw() signals sent from other parts of the canvas
184 		 *  can interfere with the blinking state. This will ensure that we are only draw/redrawing the
185 		 *  caret when it is intended to. See Bug 517487.
186 		 *
187 		 *  Additionally, only draw the caret if it has focus. See bug 528819.
188 		 */
189 		if (caret != null && blink == true && caret.isFocusCaret()) {
190 			drawCaret(widget,cairo);
191 			blink = false;
192 		}
193 	}
194 	return result;
195 }
196 
drawCaret(long widget, long cairo)197 private void drawCaret (long widget, long cairo) {
198 	if(this.isDisposed()) return;
199 	if (cairo == 0) error(SWT.ERROR_NO_HANDLES);
200 	if (drawFlag) {
201 		Cairo.cairo_save(cairo);
202 		if (caret.image != null && !caret.image.isDisposed() && caret.image.mask == 0) {
203 			Cairo.cairo_set_source_rgb(cairo, 1, 1, 1);
204 			Cairo.cairo_set_operator(cairo, Cairo.CAIRO_OPERATOR_DIFFERENCE);
205 			long surface = Cairo.cairo_get_target(cairo);
206 			int nWidth = 0;
207 			switch (Cairo.cairo_surface_get_type(surface)) {
208 				case Cairo.CAIRO_SURFACE_TYPE_IMAGE:
209 					nWidth = Cairo.cairo_image_surface_get_width(surface);
210 					break;
211 				case Cairo.CAIRO_SURFACE_TYPE_XLIB:
212 					nWidth = Cairo.cairo_xlib_surface_get_width(surface);
213 					break;
214 			}
215 			int nX = caret.x;
216 			if ((style & SWT.MIRRORED) != 0) nX = getClientWidth () - nWidth - nX;
217 			Cairo.cairo_translate(cairo, nX, caret.y);
218 			Cairo.cairo_set_source_surface(cairo, caret.image.surface, 0, 0);
219 			Cairo.cairo_paint(cairo);
220 		} else {
221 			Cairo.cairo_set_source_rgb(cairo, 1, 1, 1);
222 			Cairo.cairo_set_operator(cairo, Cairo.CAIRO_OPERATOR_DIFFERENCE);
223 			int nWidth = caret.width, nHeight = caret.height;
224 			if (nWidth <= 0) nWidth = Caret.DEFAULT_WIDTH;
225 			int nX = caret.x;
226 			if ((style & SWT.MIRRORED) != 0) nX = getClientWidth () - nWidth - nX;
227 			Cairo.cairo_rectangle(cairo, nX, caret.y, nWidth, nHeight);
228 			}
229 		Cairo.cairo_fill(cairo);
230 		Cairo.cairo_restore(cairo);
231 		drawFlag = false;
232 	} else {
233 		drawFlag = true;
234 		}
235 	return;
236 }
237 
238 @Override
gtk_focus_in_event(long widget, long event)239 long gtk_focus_in_event (long widget, long event) {
240 	long result = super.gtk_focus_in_event (widget, event);
241 	if (caret != null) caret.setFocus ();
242 	return result;
243 }
244 
245 @Override
gtk_focus_out_event(long widget, long event)246 long gtk_focus_out_event (long widget, long event) {
247 	long result = super.gtk_focus_out_event (widget, event);
248 	if (caret != null) caret.killFocus ();
249 	return result;
250 }
251 
252 @Override
gtk_preedit_changed(long imcontext)253 long gtk_preedit_changed (long imcontext) {
254 	if (ime != null) {
255 		long result = ime.gtk_preedit_changed (imcontext);
256 		if (result != 0) return result;
257 	}
258 	return super.gtk_preedit_changed (imcontext);
259 }
260 
261 @Override
redrawWidget(int x, int y, int width, int height, boolean redrawAll, boolean all, boolean trim)262 void redrawWidget (int x, int y, int width, int height, boolean redrawAll, boolean all, boolean trim) {
263 	boolean isFocus = caret != null && caret.isFocusCaret ();
264 	if (isFocus) caret.killFocus ();
265 	super.redrawWidget (x, y, width, height, redrawAll, all, trim);
266 	if (isFocus) caret.setFocus ();
267 }
268 
269 @Override
releaseChildren(boolean destroy)270 void releaseChildren (boolean destroy) {
271 	if (caret != null) {
272 		caret.release (false);
273 		caret = null;
274 	}
275 	if (ime != null) {
276 		ime.release (false);
277 		ime = null;
278 	}
279 	super.releaseChildren (destroy);
280 }
281 
282 @Override
reskinChildren(int flags)283 void reskinChildren (int flags) {
284 	if (caret != null) caret.reskin (flags);
285 	if (ime != null)  ime.reskin (flags);
286 	super.reskinChildren (flags);
287 }
288 
289 /**
290  * Scrolls a rectangular area of the receiver by first copying
291  * the source area to the destination and then causing the area
292  * of the source which is not covered by the destination to
293  * be repainted. Children that intersect the rectangle are
294  * optionally moved during the operation. In addition, all outstanding
295  * paint events are flushed before the source area is copied to
296  * ensure that the contents of the canvas are drawn correctly.
297  *
298  * @param destX the x coordinate of the destination
299  * @param destY the y coordinate of the destination
300  * @param x the x coordinate of the source
301  * @param y the y coordinate of the source
302  * @param width the width of the area
303  * @param height the height of the area
304  * @param all <code>true</code>if children should be scrolled, and <code>false</code> otherwise
305  *
306  * @exception SWTException <ul>
307  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
308  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
309  * </ul>
310  */
scroll(int destX, int destY, int x, int y, int width, int height, boolean all)311 public void scroll (int destX, int destY, int x, int y, int width, int height, boolean all) {
312 	checkWidget();
313 	if (width <= 0 || height <= 0) return;
314 	/*
315 	 * scrollInPixels() doesn't seem to be needed on GTK4, so we can return early.
316 	 * In fact it doesn't seem to be needed on GTK3 either, but it's been left
317 	 * here for stability on older GTK3 versions. The investigation
318 	 * as to why it's unneeded is left as a TODO. See bug 546274.
319 	 */
320 	if (GTK.GTK4) return;
321 	Point destination = DPIUtil.autoScaleUp (new Point (destX, destY));
322 	Rectangle srcRect = DPIUtil.autoScaleUp (new Rectangle (x, y, width, height));
323 	scrollInPixels(destination.x, destination.y, srcRect.x, srcRect.y, srcRect.width, srcRect.height, all);
324 }
325 
scrollInPixels(int destX, int destY, int x, int y, int width, int height, boolean all)326 void scrollInPixels (int destX, int destY, int x, int y, int width, int height, boolean all) {
327 	if ((style & SWT.MIRRORED) != 0) {
328 		int clientWidth = getClientWidth ();
329 		x = clientWidth - width - x;
330 		destX = clientWidth - width - destX;
331 	}
332 	int deltaX = destX - x, deltaY = destY - y;
333 	if (deltaX == 0 && deltaY == 0) return;
334 	if (!isVisible ()) return;
335 	boolean isFocus = caret != null && caret.isFocusCaret ();
336 	if (isFocus) caret.killFocus ();
337 	long window = paintWindow ();
338 	long visibleRegion = GDK.gdk_window_get_visible_region (window);
339 	cairo_rectangle_int_t srcRect = new cairo_rectangle_int_t ();
340 	srcRect.x = x;
341 	srcRect.y = y;
342 	/*
343 	 * Feature in GTK: for 3.16+ the "visible" region in Canvas includes
344 	 * the scrollbar dimensions in its calculations. This means the "previous"
345 	 * location the scrollbars are re-painted when scrolling, causing the
346 	 * hopping effect. See bug 480458.
347 	 */
348 	long hBarHandle = 0;
349 	long vBarHandle = 0;
350 	if (GTK.GTK_IS_SCROLLED_WINDOW(scrolledHandle)) {
351 		hBarHandle = GTK.gtk_scrolled_window_get_hscrollbar (scrolledHandle);
352 		vBarHandle = GTK.gtk_scrolled_window_get_vscrollbar (scrolledHandle);
353 	}
354 	GtkRequisition requisition = new GtkRequisition();
355 	if (hBarHandle != 0) {
356 		gtk_widget_get_preferred_size (hBarHandle, requisition);
357 		if (requisition.height > 0) {
358 			srcRect.y = y - requisition.height;
359 		}
360 	}
361 	if (vBarHandle != 0) {
362 		gtk_widget_get_preferred_size (vBarHandle, requisition);
363 		if (requisition.width > 0) {
364 			srcRect.x = x - requisition.width;
365 		}
366 	}
367 	srcRect.width = width;
368 	srcRect.height = height;
369 	long copyRegion = Cairo.cairo_region_create_rectangle (srcRect);
370 	Cairo.cairo_region_intersect(copyRegion, visibleRegion);
371 	long invalidateRegion = Cairo.cairo_region_create_rectangle (srcRect);
372 	Cairo.cairo_region_subtract (invalidateRegion, visibleRegion);
373 	Cairo.cairo_region_translate (invalidateRegion, deltaX, deltaY);
374 	cairo_rectangle_int_t copyRect = new cairo_rectangle_int_t();
375 	Cairo.cairo_region_get_extents (copyRegion, copyRect);
376 	if (copyRect.width != 0 && copyRect.height != 0) {
377 		update ();
378 	}
379 	Control control = findBackgroundControl ();
380 	if (control == null) control = this;
381 	if (control.backgroundImage != null) {
382 		redrawWidget (x, y, width, height, false, false, false);
383 		redrawWidget (destX, destY, width, height, false, false, false);
384 	} else {
385 		long cairo = GDK.gdk_cairo_create(window);
386 		if (Cairo.cairo_version() < Cairo.CAIRO_VERSION_ENCODE(1, 12, 0)) {
387 			GDK.gdk_cairo_set_source_window(cairo, window, 0, 0);
388 		} else {
389 			Cairo.cairo_push_group(cairo);
390 			GDK.gdk_cairo_set_source_window(cairo, window, 0, 0);
391 			Cairo.cairo_paint(cairo);
392 			Cairo.cairo_pop_group_to_source(cairo);
393 		}
394 		double[] matrix = {1, 0, 0, 1, -deltaX, -deltaY};
395 		Cairo.cairo_pattern_set_matrix(Cairo.cairo_get_source(cairo), matrix);
396 		Cairo.cairo_rectangle(cairo, copyRect.x + deltaX, copyRect.y + deltaY, copyRect.width, copyRect.height);
397 		Cairo.cairo_clip(cairo);
398 		Cairo.cairo_paint(cairo);
399 		Cairo.cairo_destroy(cairo);
400 		boolean disjoint = (destX + width < x) || (x + width < destX) || (destY + height < y) || (y + height < destY);
401 		if (disjoint) {
402 			cairo_rectangle_int_t rect = new cairo_rectangle_int_t();
403 			rect.x = x;
404 			rect.y = y;
405 			rect.width = width;
406 			rect.height = height;
407 			Cairo.cairo_region_union_rectangle (invalidateRegion, rect);
408 		} else {
409 			cairo_rectangle_int_t rect = new cairo_rectangle_int_t();
410 			if (deltaX != 0) {
411 				int newX = destX - deltaX;
412 				if (deltaX < 0) newX = destX + width;
413 				rect.x = newX;
414 				rect.y = y;
415 				rect.width = Math.abs(deltaX);
416 				rect.height = height;
417 				Cairo.cairo_region_union_rectangle (invalidateRegion, rect);
418 			}
419 			if (deltaY != 0) {
420 				int newY = destY - deltaY;
421 				if (deltaY < 0) newY = destY + height;
422 				rect.x = x;
423 				rect.y = newY;
424 				rect.width = width;
425 				rect.height = Math.abs(deltaY);
426 				Cairo.cairo_region_union_rectangle (invalidateRegion, rect);
427 			}
428 		}
429 		GDK.gdk_window_invalidate_region(window, invalidateRegion, all);
430 	}
431 	Cairo.cairo_region_destroy (visibleRegion);
432 	Cairo.cairo_region_destroy (copyRegion);
433 	Cairo.cairo_region_destroy (invalidateRegion);
434 	if (all) {
435 		Control [] children = _getChildren ();
436 		for (int i=0; i<children.length; i++) {
437 			Control child = children [i];
438 			Rectangle rect = child.getBoundsInPixels ();
439 			if (Math.min(x + width, rect.x + rect.width) >= Math.max (x, rect.x) &&
440 				Math.min(y + height, rect.y + rect.height) >= Math.max (y, rect.y)) {
441 					child.setLocationInPixels (rect.x + deltaX, rect.y + deltaY);
442 			}
443 		}
444 	}
445 	if (isFocus) caret.setFocus ();
446 	/*
447 	 * Due to overlay drawing of scrollbars current method of scrolling leaves scrollbar and notifiers for them inside the canvas
448 	 * after scroll. Fix is to redraw once done.
449 	 */
450 	redraw(false);
451 }
452 
453 @Override
setBounds(int x, int y, int width, int height, boolean move, boolean resize)454 int setBounds (int x, int y, int width, int height, boolean move, boolean resize) {
455 	boolean isFocus = caret != null && caret.isFocusCaret ();
456 	if (isFocus) caret.killFocus ();
457 	int result = super.setBounds (x, y, width, height, move, resize);
458 	if (isFocus) caret.setFocus ();
459 	return result;
460 }
461 
462 /**
463  * Sets the receiver's caret.
464  * <p>
465  * The caret for the control is automatically hidden
466  * and shown when the control is painted or resized,
467  * when focus is gained or lost and when an the control
468  * is scrolled.  To avoid drawing on top of the caret,
469  * the programmer must hide and show the caret when
470  * drawing in the window any other time.
471  * </p>
472  * @param caret the new caret for the receiver, may be null
473  *
474  * @exception IllegalArgumentException <ul>
475  *    <li>ERROR_INVALID_ARGUMENT - if the caret has been disposed</li>
476  * </ul>
477  * @exception SWTException <ul>
478  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
479  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
480  * </ul>
481  */
setCaret(Caret caret)482 public void setCaret (Caret caret) {
483 	checkWidget();
484 	Caret newCaret = caret;
485 	Caret oldCaret = this.caret;
486 	this.caret = newCaret;
487 	if (hasFocus ()) {
488 		if (oldCaret != null) oldCaret.killFocus ();
489 		if (newCaret != null) {
490 			if (newCaret.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
491 			newCaret.setFocus ();
492 		}
493 	}
494 }
495 
496 @Override
setFont(Font font)497 public void setFont (Font font) {
498 	checkWidget();
499 	if (caret != null) caret.setFont (font);
500 	super.setFont (font);
501 }
502 
503 /**
504  * Sets the receiver's IME.
505  *
506  * @param ime the new IME for the receiver, may be null
507  *
508  * @exception IllegalArgumentException <ul>
509  *    <li>ERROR_INVALID_ARGUMENT - if the IME has been disposed</li>
510  * </ul>
511  * @exception SWTException <ul>
512  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
513  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
514  * </ul>
515  *
516  * @since 3.4
517  */
setIME(IME ime)518 public void setIME (IME ime) {
519 	checkWidget ();
520 	if (ime != null && ime.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
521 	this.ime = ime;
522 }
523 
updateCaret()524 void updateCaret () {
525 	long imHandle = imHandle ();
526 	if (imHandle == 0) return;
527 	GdkRectangle rect = new GdkRectangle ();
528 	rect.x = caret.x;
529 	rect.y = caret.y;
530 	rect.width = caret.width;
531 	rect.height = caret.height;
532 	GTK.gtk_im_context_set_cursor_location (imHandle, rect);
533 }
534 
535 }
536