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