1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation 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  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.widgets.Canvas;
14 
15 import java.lang.all;
16 import org.eclipse.swt.graphics.Point;
17 import org.eclipse.swt.internal.gtk.OS;
18 import org.eclipse.swt.SWT;
19 import org.eclipse.swt.widgets.Composite;
20 import org.eclipse.swt.widgets.Caret;
21 import org.eclipse.swt.widgets.Control;
22 import org.eclipse.swt.widgets.IME;
23 import org.eclipse.swt.graphics.GC;
24 import org.eclipse.swt.graphics.Rectangle;
25 import org.eclipse.swt.graphics.Font;
26 
27 /**
28  * Instances of this class provide a surface for drawing
29  * arbitrary graphics.
30  * <dl>
31  * <dt><b>Styles:</b></dt>
32  * <dd>(none)</dd>
33  * <dt><b>Events:</b></dt>
34  * <dd>(none)</dd>
35  * </dl>
36  * <p>
37  * This class may be subclassed by custom control implementors
38  * who are building controls that are <em>not</em> constructed
39  * from aggregates of other controls. That is, they are either
40  * painted using SWT graphics calls or are handled by native
41  * methods.
42  * </p>
43  *
44  * @see Composite
45  * @see <a href="http://www.eclipse.org/swt/snippets/#canvas">Canvas snippets</a>
46  * @see <a href="http://www.eclipse.org/swt/examples.php">SWT Example: ControlExample</a>
47  * @see <a href="http://www.eclipse.org/swt/">Sample code and further information</a>
48  */
49 public class Canvas : Composite {
50 
51     alias Composite.setBounds setBounds;
52 
53     Caret caret;
54     IME ime;
55 
56 this () {}
57 
58 /**
59  * Constructs a new instance of this class given its parent
60  * and a style value describing its behavior and appearance.
61  * <p>
62  * The style value is either one of the style constants defined in
63  * class <code>SWT</code> which is applicable to instances of this
64  * class, or must be built by <em>bitwise OR</em>'ing together
65  * (that is, using the <code>int</code> "|" operator) two or more
66  * of those <code>SWT</code> style constants. The class description
67  * lists the style constants that are applicable to the class.
68  * Style bits are also inherited from superclasses.
69  * </p>
70  *
71  * @param parent a composite control which will be the parent of the new instance (cannot be null)
72  * @param style the style of control to construct
73  *
74  * @exception IllegalArgumentException <ul>
75  *    <li>ERROR_NULL_ARGUMENT - if the parent is null</li>
76  * </ul>
77  * @exception SWTException <ul>
78  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the parent</li>
79  * </ul>
80  *
81  * @see SWT
82  * @see Widget#checkSubclass
83  * @see Widget#getStyle
84  */
85 public this (Composite parent, int style) {
86     super (parent, checkStyle (style));
87 }
88 
89 /**
90  * Fills the interior of the rectangle specified by the arguments,
91  * with the receiver's background.
92  *
93  * @param gc the gc where the rectangle is to be filled
94  * @param x the x coordinate of the rectangle to be filled
95  * @param y the y coordinate of the rectangle to be filled
96  * @param width the width of the rectangle to be filled
97  * @param height the height of the rectangle to be filled
98  *
99  * @exception IllegalArgumentException <ul>
100  *    <li>ERROR_NULL_ARGUMENT - if the gc is null</li>
101  *    <li>ERROR_INVALID_ARGUMENT - if the gc has been disposed</li>
102  * </ul>
103  * @exception SWTException <ul>
104  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
105  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
106  * </ul>
107  *
108  * @since 3.2
109  */
110 override public void drawBackground (GC gc, int x, int y, int width, int height) {
111     checkWidget ();
112     if (gc is null) error (SWT.ERROR_NULL_ARGUMENT);
113     if (gc.isDisposed ()) error (SWT.ERROR_INVALID_ARGUMENT);
114     super.drawBackground (gc, x, y, width, height);
115 }
116 
117 /**
118  * Returns the caret.
119  * <p>
120  * The caret for the control is automatically hidden
121  * and shown when the control is painted or resized,
122  * when focus is gained or lost and when an the control
123  * is scrolled.  To avoid drawing on top of the caret,
124  * the programmer must hide and show the caret when
125  * drawing in the window any other time.
126  * </p>
127  *
128  * @return the caret for the receiver, may be null
129  *
130  * @exception SWTException <ul>
131  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
132  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
133  * </ul>
134  */
135 public Caret getCaret () {
136     checkWidget();
137     return caret;
138 }
139 
140 override Point getIMCaretPos () {
141     if (caret is null) return super.getIMCaretPos ();
142     return new Point (caret.x, caret.y);
143 }
144 
145 /**
146  * Returns the IME.
147  *
148  * @return the IME
149  *
150  * @exception SWTException <ul>
151  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
152  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
153  * </ul>
154  * 
155  * @since 3.4
156  */
157 public IME getIME () {
158     checkWidget ();
159     return ime;
160 }
161 
162 override int gtk_button_press_event (GtkWidget* widget, GdkEventButton* event) {
163     if (ime !is null) {
164         auto result = ime.gtk_button_press_event (widget, event);
165         if (result !is 0) return result;
166     }
167     return  super.gtk_button_press_event (widget, event);
168 }
169 
170 override int gtk_commit (GtkIMContext* imcontext, char* text) {
171     if (ime !is null) {
172         auto result = ime.gtk_commit (imcontext, text);
173         if (result !is 0) return result;
174     }
175     return super.gtk_commit (imcontext, text);
176 }
177 
178 override int gtk_expose_event (GtkWidget* widget, GdkEventExpose* event) {
179     if ((state & OBSCURED) !is 0) return 0;
180     bool isFocus = caret !is null && caret.isFocusCaret ();
181     if (isFocus) caret.killFocus ();
182     auto result = super.gtk_expose_event (widget, event);
183     if (isFocus) caret.setFocus ();
184     return result;
185 }
186 
187 override int gtk_focus_in_event (GtkWidget* widget, GdkEventFocus* event) {
188     auto result = super.gtk_focus_in_event (widget, event);
189     if (caret !is null) caret.setFocus ();
190     return result;
191 }
192 
193 override int gtk_focus_out_event (GtkWidget* widget, GdkEventFocus* event) {
194     auto result = super.gtk_focus_out_event (widget, event);
195     if (caret !is null) caret.killFocus ();
196     return result;
197 }
198 
199 override int gtk_preedit_changed (GtkIMContext* imcontext) {
200     if (ime !is null) {
201         auto result = ime.gtk_preedit_changed (imcontext);
202         if (result !is 0) return result;
203     }
204     return super.gtk_preedit_changed (imcontext);
205 }
206 
207 override void redrawWidget (int x, int y, int width, int height, bool redrawAll, bool all, bool trim) {
208     bool isFocus = caret !is null && caret.isFocusCaret ();
209     if (isFocus) caret.killFocus ();
210     super.redrawWidget (x, y, width, height, redrawAll, all, trim);
211     if (isFocus) caret.setFocus ();
212 }
213 
214 override void releaseChildren (bool destroy) {
215     if (caret !is null) {
216         caret.release (false);
217         caret = null;
218     }
219     if (ime !is null) {
220         ime.release (false);
221         ime = null;
222     }
223     super.releaseChildren (destroy);
224 }
225 
226 /**
227  * Scrolls a rectangular area of the receiver by first copying
228  * the source area to the destination and then causing the area
229  * of the source which is not covered by the destination to
230  * be repainted. Children that intersect the rectangle are
231  * optionally moved during the operation. In addition, outstanding
232  * paint events are flushed before the source area is copied to
233  * ensure that the contents of the canvas are drawn correctly.
234  *
235  * @param destX the x coordinate of the destination
236  * @param destY the y coordinate of the destination
237  * @param x the x coordinate of the source
238  * @param y the y coordinate of the source
239  * @param width the width of the area
240  * @param height the height of the area
241  * @param all <code>true</code>if children should be scrolled, and <code>false</code> otherwise
242  *
243  * @exception SWTException <ul>
244  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
245  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
246  * </ul>
247  */
248 public void scroll (int destX, int destY, int x, int y, int width, int height, bool all) {
249     checkWidget();
250     if (width <= 0 || height <= 0) return;
251     if ((style & SWT.MIRRORED) !is 0) {
252         int clientWidth = getClientWidth ();
253         x = clientWidth - width - x;
254         destX = clientWidth - width - destX;
255     }
256     int deltaX = destX - x, deltaY = destY - y;
257     if (deltaX is 0 && deltaY is 0) return;
258     if (!isVisible ()) return;
259     bool isFocus = caret !is null && caret.isFocusCaret ();
260     if (isFocus) caret.killFocus ();
261     auto window = paintWindow ();
262     auto visibleRegion = OS.gdk_drawable_get_visible_region (window);
263     GdkRectangle* srcRect = new GdkRectangle ();
264     srcRect.x = x;
265     srcRect.y = y;
266     srcRect.width = width;
267     srcRect.height = height;
268     auto copyRegion = OS.gdk_region_rectangle (srcRect);
269     OS.gdk_region_intersect(copyRegion, visibleRegion);
270     auto invalidateRegion = OS.gdk_region_rectangle (srcRect);
271     OS.gdk_region_subtract (invalidateRegion, visibleRegion);
272     OS.gdk_region_offset (invalidateRegion, deltaX, deltaY);
273     GdkRectangle* copyRect = new GdkRectangle();
274     OS.gdk_region_get_clipbox (copyRegion, copyRect);
275     if (copyRect.width !is 0 && copyRect.height !is 0) {
276         update ();
277     }
278     Control control = findBackgroundControl ();
279     if (control is null) control = this;
280     if (control.backgroundImage !is null) {
281         redrawWidget (x, y, width, height, false, false, false);
282         redrawWidget (destX, destY, width, height, false, false, false);
283     } else {
284 //      GC gc = new GC (this);
285 //      gc.copyArea (x, y, width, height, destX, destY);
286 //      gc.dispose ();
287         auto gdkGC = OS.gdk_gc_new (window);
288         OS.gdk_gc_set_exposures (gdkGC, true);
289         OS.gdk_draw_drawable (window, gdkGC, window, copyRect.x, copyRect.y, copyRect.x + deltaX, copyRect.y + deltaY, copyRect.width, copyRect.height);
290         OS.g_object_unref (gdkGC);
291         bool disjoint = (destX + width < x) || (x + width < destX) || (destY + height < y) || (y + height < destY);
292         if (disjoint) {
293             GdkRectangle* rect = new GdkRectangle ();
294             rect.x = x;
295             rect.y = y;
296             rect.width = width;
297             rect.height = height;
298             OS.gdk_region_union_with_rect (invalidateRegion, rect);
299         } else {
300             GdkRectangle* rect = new GdkRectangle ();
301             if (deltaX !is 0) {
302                 int newX = destX - deltaX;
303                 if (deltaX < 0) newX = destX + width;
304                 rect.x = newX;
305                 rect.y = y;
306                 rect.width = Math.abs(deltaX);
307                 rect.height = height;
308                 OS.gdk_region_union_with_rect (invalidateRegion, rect);
309             }
310             if (deltaY !is 0) {
311                 int newY = destY - deltaY;
312                 if (deltaY < 0) newY = destY + height;
313                 rect.x = x;
314                 rect.y = newY;
315                 rect.width = width;
316                 rect.height = Math.abs(deltaY);
317                 OS.gdk_region_union_with_rect (invalidateRegion, rect);
318             }
319         }
320         OS.gdk_window_invalidate_region(window, invalidateRegion, all);
321         OS.gdk_region_destroy (visibleRegion);
322         OS.gdk_region_destroy (copyRegion);
323         OS.gdk_region_destroy (invalidateRegion);
324     }
325     if (all) {
326         Control [] children = _getChildren ();
327         for (int i=0; i<children.length; i++) {
328             Control child = children [i];
329             Rectangle rect = child.getBounds ();
330             if (Math.min(x + width, rect.x + rect.width) >= Math.max (x, rect.x) &&
331                 Math.min(y + height, rect.y + rect.height) >= Math.max (y, rect.y)) {
332                     child.setLocation (rect.x + deltaX, rect.y + deltaY);
333             }
334         }
335     }
336     if (isFocus) caret.setFocus ();
337 }
338 
339 override int setBounds (int x, int y, int width, int height, bool move, bool resize) {
340     bool isFocus = caret !is null && caret.isFocusCaret ();
341     if (isFocus) caret.killFocus ();
342     int result = super.setBounds (x, y, width, height, move, resize);
343     if (isFocus) caret.setFocus ();
344     return result;
345 }
346 
347 /**
348  * Sets the receiver's caret.
349  * <p>
350  * The caret for the control is automatically hidden
351  * and shown when the control is painted or resized,
352  * when focus is gained or lost and when an the control
353  * is scrolled.  To avoid drawing on top of the caret,
354  * the programmer must hide and show the caret when
355  * drawing in the window any other time.
356  * </p>
357  * @param caret the new caret for the receiver, may be null
358  *
359  * @exception IllegalArgumentException <ul>
360  *    <li>ERROR_INVALID_ARGUMENT - if the caret has been disposed</li>
361  * </ul>
362  * @exception SWTException <ul>
363  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
364  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
365  * </ul>
366  */
367 public void setCaret (Caret caret) {
368     checkWidget();
369     Caret newCaret = caret;
370     Caret oldCaret = this.caret;
371     this.caret = newCaret;
372     if (hasFocus ()) {
373         if (oldCaret !is null) oldCaret.killFocus ();
374         if (newCaret !is null) {
375             if (newCaret.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
376             newCaret.setFocus ();
377         }
378     }
379 }
380 
381 override public void setFont (Font font) {
382     checkWidget();
383     if (caret !is null) caret.setFont (font);
384     super.setFont (font);
385 }
386 
387 /**
388  * Sets the receiver's IME.
389  * 
390  * @param ime the new IME for the receiver, may be null
391  *
392  * @exception IllegalArgumentException <ul>
393  *    <li>ERROR_INVALID_ARGUMENT - if the IME has been disposed</li>
394  * </ul>
395  * @exception SWTException <ul>
396  *    <li>ERROR_WIDGET_DISPOSED - if the receiver has been disposed</li>
397  *    <li>ERROR_THREAD_INVALID_ACCESS - if not called from the thread that created the receiver</li>
398  * </ul>
399  * 
400  * @since 3.4
401  */
402 public void setIME (IME ime) {
403     checkWidget ();
404     if (ime !is null && ime.isDisposed()) error(SWT.ERROR_INVALID_ARGUMENT);
405     this.ime = ime;
406 }
407 
408 void updateCaret () {
409     auto imHandle = imHandle ();
410     if (imHandle is null) return;
411     GdkRectangle* rect = new GdkRectangle ();
412     rect.x = caret.x;
413     rect.y = caret.y;
414     rect.width = caret.width;
415     rect.height = caret.height;
416     OS.gtk_im_context_set_cursor_location (imHandle, rect);
417 }
418 
419 }