1 /* GuiDrawingArea.cpp
2  *
3  * Copyright (C) 1993-2018,2020,2021 Paul Boersma,
4  *               2008 Stefan de Konink, 2010 Franz Brausse, 2013 Tom Naughton
5  *
6  * This code is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or (at
9  * your option) any later version.
10  *
11  * This code is distributed in the hope that it will be useful, but
12  * WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14  * See the GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this work. If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include "GuiP.h"
21 #if gtk
22 	#include "gdk/gdkkeysyms.h"
23 	#include <locale.h>
24 #endif
25 #include "GraphicsP.h"
26 
27 Thing_implement (GuiDrawingArea, GuiControl, 0);
28 
29 #if motif
30 	#define iam_drawingarea \
31 		Melder_assert (widget -> widgetClass == xmDrawingAreaWidgetClass); \
32 		GuiDrawingArea me = (GuiDrawingArea) widget -> userData
33 #else
34 	#define iam_drawingarea \
35 		GuiDrawingArea me = (GuiDrawingArea) _GuiObject_getUserData (widget)
36 #endif
37 
38 #if gtk
39 #pragma mark - GTK CALLBACKS (WITH CAIRO)
_guiGtkDrawingArea_destroyCallback(GuiObject widget,gpointer void_me)40 	static void _guiGtkDrawingArea_destroyCallback (GuiObject widget, gpointer void_me) {
41 		(void) widget;
42 		iam (GuiDrawingArea);
43 		forget (me);
44 	}
_guiGtkDrawingArea_drawCallback(GuiObject widget,cairo_t * cairoGraphicsContext,gpointer void_me)45 	static gboolean _guiGtkDrawingArea_drawCallback (GuiObject widget, cairo_t *cairoGraphicsContext, gpointer void_me) {
46 		trace (U"begin");
47 		iam (GuiDrawingArea);
48 		Melder_assert (me);
49 		if (my d_exposeCallback) {
50 			Melder_assert (my numberOfGraphicses > 0);
51 			structGuiDrawingArea_ExposeEvent event { me, 0 };
52 			event. x = 0;
53 			event. y = 0;
54 			event. width = gtk_widget_get_allocated_width (GTK_WIDGET (widget));
55 			event. height = gtk_widget_get_allocated_height (GTK_WIDGET (widget));
56 			try {
57 				for (int igraphics = 1; igraphics <= my numberOfGraphicses; igraphics ++)
58 					((GraphicsScreen) my graphicses [igraphics]) -> d_cairoGraphicsContext = cairoGraphicsContext;
59 				my d_exposeCallback (my d_exposeBoss, & event);
60 				for (int igraphics = 1; igraphics <= my numberOfGraphicses; igraphics ++)
61 					((GraphicsScreen) my graphicses [igraphics]) -> d_cairoGraphicsContext = nullptr;
62 			} catch (MelderError) {
63 				Melder_flushError (U"Redrawing not completed");
64 			}
65 			trace (U"the draw callback handled drawing");
66 			return true;
67 		}
68 		trace (U"GTK will handle redrawing");
69 		return false;
70 	}
71 	static structGuiDrawingArea_MouseEvent::Phase previousPhase = structGuiDrawingArea_MouseEvent::Phase::DROP;
_guiGtkDrawingArea_mouseDownCallback(GuiObject widget,GdkEvent * e,gpointer void_me)72 	static gboolean _guiGtkDrawingArea_mouseDownCallback (GuiObject widget, GdkEvent *e, gpointer void_me) {
73 		iam (GuiDrawingArea);
74 		if (my mouseCallback) {
75 			structGuiDrawingArea_MouseEvent event { me, 0 };
76 			event. x = ((GdkEventButton *) e) -> x;
77 			event. y = ((GdkEventButton *) e) -> y;
78 			event. shiftKeyPressed = (((GdkEventButton *) e) -> state & GDK_SHIFT_MASK) != 0;
79 			event. commandKeyPressed = (((GdkEventButton *) e) -> state & GDK_CONTROL_MASK) != 0;
80 			event. optionKeyPressed = (((GdkEventButton *) e) -> state & GDK_MOD1_MASK) != 0;
81 			if (previousPhase == structGuiDrawingArea_MouseEvent::Phase::CLICK) {
82 				/*
83 					Apparently a double-click.
84 					On other platforms, a mouse-up event is always generated, even within a double-click.
85 					On Linux, we generate it ourselves.
86 				*/
87 				try {
88 					previousPhase = event. phase = structGuiDrawingArea_MouseEvent::Phase::DROP;
89 					my mouseCallback (my mouseBoss, & event);
90 				} catch (MelderError) {
91 					Melder_flushError (U"Mouse drop not completely handled.");
92 				}
93 			}
94 			try {
95 				previousPhase = event. phase = structGuiDrawingArea_MouseEvent::Phase::CLICK;
96 				my mouseCallback (my mouseBoss, & event);
97 			} catch (MelderError) {
98 				Melder_flushError (U"Mouse click not completely handled.");
99 			}
100 			return true;
101 		}
102 		return false;
103 	}
_guiGtkDrawingArea_mouseDraggedCallback(GuiObject widget,GdkEvent * e,gpointer void_me)104 	static gboolean _guiGtkDrawingArea_mouseDraggedCallback (GuiObject widget, GdkEvent *e, gpointer void_me) {
105 		iam (GuiDrawingArea);
106 		if (my mouseCallback) {
107 			structGuiDrawingArea_MouseEvent event { me, 0 };
108 			event. x = ((GdkEventButton *) e) -> x;
109 			event. y = ((GdkEventButton *) e) -> y;
110 			event. shiftKeyPressed = (((GdkEventButton *) e) -> state & GDK_SHIFT_MASK) != 0;
111 			event. commandKeyPressed = (((GdkEventButton *) e) -> state & GDK_CONTROL_MASK) != 0;
112 			event. optionKeyPressed = (((GdkEventButton *) e) -> state & GDK_MOD1_MASK) != 0;
113 			try {
114 				previousPhase = event. phase = structGuiDrawingArea_MouseEvent::Phase::DRAG;
115 				my mouseCallback (my mouseBoss, & event);
116 			} catch (MelderError) {
117 				Melder_flushError (U"Mouse drag not completely handled.");
118 			}
119 			return true;
120 		}
121 		return false;
122 	}
_guiGtkDrawingArea_mouseUpCallback(GuiObject widget,GdkEvent * e,gpointer void_me)123 	static gboolean _guiGtkDrawingArea_mouseUpCallback (GuiObject widget, GdkEvent *e, gpointer void_me) {
124 		iam (GuiDrawingArea);
125 		if (my mouseCallback) {
126 			structGuiDrawingArea_MouseEvent event { me, 0 };
127 			event. x = ((GdkEventButton *) e) -> x;
128 			event. y = ((GdkEventButton *) e) -> y;
129 			event. shiftKeyPressed = (((GdkEventButton *) e) -> state & GDK_SHIFT_MASK) != 0;
130 			event. commandKeyPressed = (((GdkEventButton *) e) -> state & GDK_CONTROL_MASK) != 0;
131 			event. optionKeyPressed = (((GdkEventButton *) e) -> state & GDK_MOD1_MASK) != 0;
132 			try {
133 				previousPhase = event. phase = structGuiDrawingArea_MouseEvent::Phase::DROP;
134 				my mouseCallback (my mouseBoss, & event);
135 			} catch (MelderError) {
136 				Melder_flushError (U"Mouse drop not completely handled.");
137 			}
138 			return true;
139 		}
140 		return false;
141 	}
_guiGtkDrawingArea_keyCallback(GuiObject widget,GdkEvent * gevent,gpointer void_me)142 	static gboolean _guiGtkDrawingArea_keyCallback (GuiObject widget, GdkEvent *gevent, gpointer void_me) {
143 		iam (GuiDrawingArea);
144 		trace (U"begin");
145 		if (my d_keyCallback && gevent -> type == GDK_KEY_PRESS) {
146 			structGuiDrawingArea_KeyEvent event { me, 0 };
147 			GdkEventKey *gkeyEvent = (GdkEventKey *) gevent;
148 			event. key = gkeyEvent -> keyval;
149 			/*
150 			 * Translate with the help of /usr/include/gtk-2.0/gdk/gdkkeysyms.h
151 			 */
152 			if (event. key == GDK_KEY_Escape) event. key = 27;
153 			if (event. key == GDK_KEY_Left)   event. key = 0x2190;
154 			if (event. key == GDK_KEY_Up)     event. key = 0x2191;
155 			if (event. key == GDK_KEY_Right)  event. key = 0x2192;
156 			if (event. key == GDK_KEY_Down)   event. key = 0x2193;
157 			event. shiftKeyPressed = (gkeyEvent -> state & GDK_SHIFT_MASK) != 0;
158 			event. commandKeyPressed = (gkeyEvent -> state & GDK_CONTROL_MASK) != 0;
159 			event. optionKeyPressed = (gkeyEvent -> state & GDK_MOD1_MASK) != 0;
160 			try {
161 				my d_keyCallback (my d_keyBoss, & event);
162 			} catch (MelderError) {
163 				Melder_flushError (U"Key press not completely handled.");
164 			}
165 			/*
166 			 * FIXME: here we should empty the type-ahead buffer
167 			 */
168 			return true;
169 		}
170 		return false;   // if the drawing area has no keyCallback, the system will send the key press to a text field.
171 	}
_guiGtkDrawingArea_resizeCallback(GuiObject widget,GtkAllocation * allocation,gpointer void_me)172 	static gboolean _guiGtkDrawingArea_resizeCallback (GuiObject widget, GtkAllocation *allocation, gpointer void_me) {
173 		iam (GuiDrawingArea);
174 		if (my d_resizeCallback) {
175 			structGuiDrawingArea_ResizeEvent event { me, 0 };
176 			trace (U"drawingArea resized to ", allocation -> width, U" x ", allocation -> height, U".");
177 			event. width = allocation -> width;
178 			event. height = allocation -> height;
179 			//g_debug("%d %d", allocation->width, allocation->height);
180 			try {
181 				my d_resizeCallback (my d_resizeBoss, & event);
182 			} catch (MelderError) {
183 				Melder_flushError (U"Window resizing not completely handled.");
184 			}
185 			return true;
186 		}
187 		return false;
188 	}
_guiGtkDrawingArea_swipeCallback(GuiObject w,GdkEventScroll * event,gpointer void_me)189 	static gboolean _guiGtkDrawingArea_swipeCallback (GuiObject w, GdkEventScroll *event, gpointer void_me) {
190 		iam (GuiDrawingArea);
191 		trace (U"_guiGtkDrawingArea_swipeCallback ", Melder_pointer (my d_horizontalScrollBar), Melder_pointer (my d_verticalScrollBar));
192 		if (my d_horizontalScrollBar) {
193 			double hv = gtk_range_get_value (GTK_RANGE (my d_horizontalScrollBar -> d_widget));
194 			GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (my d_horizontalScrollBar -> d_widget));
195 			gdouble hi;
196 			g_object_get (adjustment, "step_increment", & hi, nullptr);
197 			switch (event -> direction) {
198 				case GDK_SCROLL_LEFT:
199 					gtk_range_set_value (GTK_RANGE (my d_horizontalScrollBar -> d_widget), hv - hi);
200 					break;
201 				case GDK_SCROLL_RIGHT:
202 					gtk_range_set_value (GTK_RANGE (my d_horizontalScrollBar -> d_widget), hv + hi);
203 					break;
204 			}
205 		}
206 		if (my d_verticalScrollBar) {
207 			double vv = gtk_range_get_value (GTK_RANGE (my d_verticalScrollBar -> d_widget));
208 			GtkAdjustment *adjustment = gtk_range_get_adjustment (GTK_RANGE (my d_verticalScrollBar -> d_widget));
209 			gdouble vi;
210 			g_object_get (adjustment, "step_increment", & vi, nullptr);
211 			switch (event -> direction) {
212 				case GDK_SCROLL_UP:
213 					gtk_range_set_value (GTK_RANGE (my d_verticalScrollBar -> d_widget), vv - vi);
214 					break;
215 				case GDK_SCROLL_DOWN:
216 					gtk_range_set_value (GTK_RANGE (my d_verticalScrollBar -> d_widget), vv + vi);
217 					break;
218 			}
219 		}
220 		return true;
221 	}
222 #elif motif
223 #pragma mark - MOTIF CALLBACKS (WITH GDI)
_GuiWinDrawingArea_destroy(GuiObject widget)224 	void _GuiWinDrawingArea_destroy (GuiObject widget) {
225 		iam_drawingarea;
226 		DestroyWindow (widget -> window);
227 		forget (me);   // NOTE: my widget is not destroyed here
228 	}
_GuiWinDrawingArea_update(GuiObject widget)229 	void _GuiWinDrawingArea_update (GuiObject widget) {
230 		iam_drawingarea;
231 		Melder_assert (my numberOfGraphicses > 0);
232 		GraphicsScreen graphics = (GraphicsScreen) my graphicses [1];
233 		Melder_assert (Thing_isa (graphics, classGraphicsScreen));
234 		HDC memoryDC = CreateCompatibleDC (graphics -> d_gdiGraphicsContext);
235 		HBITMAP memoryBitmap = CreateCompatibleBitmap (graphics -> d_gdiGraphicsContext, widget -> width, widget -> height);
236 		SelectObject (memoryDC, memoryBitmap);
237 		SetBkMode (memoryDC, TRANSPARENT);   // not the default!
238 		SelectPen (memoryDC, GetStockPen (BLACK_PEN));
239 		SelectBrush (memoryDC, GetStockBrush (BLACK_BRUSH));
240 		SetTextAlign (memoryDC, TA_LEFT | TA_BASELINE | TA_NOUPDATECP);   // baseline is not the default!
241 		HDC saveContext = graphics -> d_gdiGraphicsContext;
242 		for (int igraphics = 1; igraphics <= my numberOfGraphicses; igraphics ++)
243 			((GraphicsScreen) my graphicses [igraphics]) -> d_gdiGraphicsContext = memoryDC;
244 		if (my d_exposeCallback) {
245 			structGuiDrawingArea_ExposeEvent event { me };
246 			try {
247 				my d_exposeCallback (my d_exposeBoss, & event);
248 			} catch (MelderError) {
249 				Melder_flushError (U"Redrawing not completed");
250 			}
251 		}
252 		for (int igraphics = 1; igraphics <= my numberOfGraphicses; igraphics ++)
253 			((GraphicsScreen) my graphicses [igraphics]) -> d_gdiGraphicsContext = saveContext;
254 		BitBlt (graphics -> d_gdiGraphicsContext, 0, 0, widget -> width, widget -> height, memoryDC, 0, 0, SRCCOPY);
255 		DeleteObject (memoryBitmap);
256 		DeleteDC (memoryDC);
257 		ValidateRect (widget -> window, nullptr);
258 	}
_GuiWinDrawingArea_handleMouse(GuiObject widget,structGuiDrawingArea_MouseEvent::Phase phase,int x,int y)259 	void _GuiWinDrawingArea_handleMouse (GuiObject widget, structGuiDrawingArea_MouseEvent::Phase phase, int x, int y) {
260 		iam_drawingarea;
261 		if (my mouseCallback) {
262 			structGuiDrawingArea_MouseEvent event { me, 0 };
263 			event. x = x;
264 			event. y = y;
265 			event. phase = phase;
266 			//Melder_casual (U": phase ", (int) phase);
267 			event. shiftKeyPressed = GetKeyState (VK_SHIFT) < 0;
268 			event. optionKeyPressed = GetKeyState (VK_MENU) < 0;
269 			event. commandKeyPressed = GetKeyState (VK_CONTROL) < 0;
270 			try {
271 				my mouseCallback (my mouseBoss, & event);
272 			} catch (MelderError) {
273 				switch (phase) {
274 					case structGuiDrawingArea_MouseEvent::Phase::CLICK:
275 						Melder_flushError (U"Mouse click not completely handled.");
276 					break; case structGuiDrawingArea_MouseEvent::Phase::DRAG:
277 						Melder_flushError (U"Mouse drag not completely handled.");
278 					break; case structGuiDrawingArea_MouseEvent::Phase::DROP:
279 						Melder_flushError (U"Mouse drop not completely handled.");
280 					break;
281 				}
282 			}
283 		}
284 	}
_GuiWinDrawingArea_handleKey(GuiObject widget,TCHAR kar)285 	void _GuiWinDrawingArea_handleKey (GuiObject widget, TCHAR kar) {   // TODO: event?
286 		iam_drawingarea;
287 		if (my d_keyCallback) {
288 			structGuiDrawingArea_KeyEvent event { me, 0 };
289 			event. key = kar;
290 			if (event. key == VK_RETURN) event. key = 10;
291 			if (event. key == VK_LEFT)  event. key = 0x2190;
292 			if (event. key == VK_RIGHT) event. key = 0x2192;
293 			if (event. key == VK_UP)    event. key = 0x2191;
294 			if (event. key == VK_DOWN)  event. key = 0x2193;
295 			event. shiftKeyPressed = GetKeyState (VK_SHIFT) < 0;   // TODO: event -> key?
296 			event. optionKeyPressed = GetKeyState (VK_MENU) < 0;
297 			event. commandKeyPressed = GetKeyState (VK_CONTROL) < 0;
298 			try {
299 				my d_keyCallback (my d_keyBoss, & event);
300 			} catch (MelderError) {
301 				Melder_flushError (U"Key press not completely handled.");
302 			}
303 		}
304 	}
_GuiWinDrawingArea_shellResize(GuiObject widget)305 	void _GuiWinDrawingArea_shellResize (GuiObject widget) {
306 		iam_drawingarea;
307 		if (my d_resizeCallback) {
308 			structGuiDrawingArea_ResizeEvent event { me };
309 			event. width = widget -> width;
310 			event. height = widget -> height;
311 			try {
312 				my d_resizeCallback (my d_resizeBoss, & event);
313 			} catch (MelderError) {
314 				Melder_flushError (U"Window resizing not completely handled.");
315 			}
316 		}
317 	}
318 #elif cocoa
319 #pragma mark - COCOA CALLBACKS (WITH QUARTZ)
320 	@interface GuiCocoaDrawingArea ()
321 	@property (nonatomic, assign) BOOL inited;
322 	@property (nonatomic, retain) NSTrackingArea *trackingArea;
323 	@end
324 	@implementation GuiCocoaDrawingArea {
325 		GuiDrawingArea d_userData;
326 	}
327 	- (id) initWithFrame: (NSRect) frame {
328 		self = [super initWithFrame: frame];
329 		if (self) {
330 			_trackingArea = [[[NSTrackingArea alloc]
331 				initWithRect: [self visibleRect]
332 				options: NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingInVisibleRect | NSTrackingActiveAlways
333 				owner: self
334 				userInfo: nil]
335 				autorelease];
336 			[self   addTrackingArea: _trackingArea];
337 		}
338 		return self;
339 	}
340 	- (void) dealloc {   // override
341 		GuiDrawingArea me = d_userData;
342 		if (Melder_debug == 55)
343 			Melder_casual (U"\t\tGuiCocoaDrawingArea-", Melder_pointer (self), U" dealloc for ", Melder_pointer (me));
344 		forget (me);
345 		[self removeTrackingArea: _trackingArea];
346 		trace (U"deleting a drawing area");
347 		[super dealloc];
348 	}
349 	- (GuiThing) getUserData {
350 		return d_userData;
351 	}
352 	- (void) setUserData: (GuiThing) userData {
353 		d_userData = static_cast <GuiDrawingArea> (userData);
354 	}
355 	- (void) resizeCallback: (NSRect) rect {
356 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
357 		if (me && my d_resizeCallback) {
358 			structGuiDrawingArea_ResizeEvent event = { me, 0, 0 };
359 			event. width = rect. size. width;
360 			event. height = rect. size. height;
361 			try {
362 				my d_resizeCallback (my d_resizeBoss, & event);
363 			} catch (MelderError) {
364 				Melder_flushError (U"Window resizing not completely handled.");
365 			}
366 		}
367 	}
368 	- (void) drawRect: (NSRect) dirtyRect {
369 		trace (U"dirtyRect: ", dirtyRect.origin.x, U", ", dirtyRect.origin.y, U", ", dirtyRect.size.width, U", ", dirtyRect.size.height);
370 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
371 		if (Melder_debug == 55)
372 			Melder_casual (U"\t\tGuiCocoaDrawingArea-", Melder_pointer (self), U" draw to ", Melder_pointer (me));
373 		if (! _inited) {
374 			// Last chance to do this. Is there a better place?
375 			[self   resizeCallback: self. frame];
376 			_inited = YES;
377 		}
378 		if (me && my d_exposeCallback) {
379 			structGuiDrawingArea_ExposeEvent event = { me, 0, 0, 0, 0 };
380 			if (Melder_debug == 55)
381 				Melder_casual (U"\t", Thing_messageNameAndAddress (me), U" draw for ", Melder_pointer (my d_exposeBoss));
382 			try {
383 				Melder_assert (my numberOfGraphicses > 0);
384 				for (integer igraphics = 1; igraphics <= my numberOfGraphicses; igraphics ++) {
385 					GraphicsScreen graphics = static_cast <GraphicsScreen> (my graphicses [igraphics]);
386 					if (graphics -> d_macView) {
387 						graphics -> d_macGraphicsContext = Melder_systemVersion < 101400 ?
388 								(CGContextRef) [[NSGraphicsContext currentContext] graphicsPort] :
389 								[[NSGraphicsContext currentContext] CGContext];
390 						Melder_assert (!! graphics -> d_macGraphicsContext);
391 					}
392 				}
393 				my d_exposeCallback (my d_exposeBoss, & event);
394 				for (integer igraphics = 1; igraphics <= my numberOfGraphicses; igraphics ++) {
395 					GraphicsScreen graphics = static_cast <GraphicsScreen> (my graphicses [igraphics]);
396 					if (graphics -> d_macView)
397 						graphics -> d_macGraphicsContext = nullptr;
398 				}
399 			} catch (MelderError) {
400 				Melder_flushError (U"Redrawing not completed");
401 			}
402 		}
403 	}
404 	- (void) setFrame: (NSRect) rect {
405 		[self   resizeCallback: rect];
406 		[super   setFrame: rect];
407 	}
408 	- (BOOL) acceptsFirstResponder {
409 		/*
410 		 * This overridden method tells the event chain whether the drawing area can accept key events.
411 		 * It is important that the Demo window and the RunnerMFC window accept key events.
412 		 * A side effect of accepting key events is that the drawing area obtains the key focus when the user clicks in the drawing area.
413 		 * It is important, however, that the drawing area of the TextGrid window cannot take away the key focus
414 		 * from the text field at the top; therefore, that drawing area should not accept key events.
415 		 * The implementation below is based on the fact that, naturally, the Demo window and the RunnerMFC window
416 		 * have a key callback, and the drawing area of the TextGrid window has not
417 		 * (a side effect of this implementation is that the drawing area of the Manual window does not take away
418 		 * the key focus from the Search field, a situation that cannot hurt).
419 		 */
420 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
421 		return !! my d_keyCallback;
422 	}
423 	- (void) mouseEntered: (NSEvent *) nsEvent {
424 		(void) nsEvent;
425 		[[NSCursor crosshairCursor] push];
426 	}
427 	- (void) mouseExited: (NSEvent *) nsEvent {
428 		(void) nsEvent;
429 		[[NSCursor currentCursor] pop];
430 	}
431 	- (void) mouse: (NSEvent *) nsEvent inPhase: (structGuiDrawingArea_MouseEvent::Phase) phase {
432 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
433 		if (me && my mouseCallback) {
434 			structGuiDrawingArea_MouseEvent event = { me, 0, 0, phase, false, false, false };
435 			NSPoint local_point = [self   convertPoint: [nsEvent locationInWindow]   fromView: nil];
436 			event. x = local_point. x;
437 			event. y = local_point. y;
438 			NSUInteger modifiers = [nsEvent modifierFlags];
439 			event. shiftKeyPressed = modifiers & NSShiftKeyMask;
440 			event. optionKeyPressed = modifiers & NSAlternateKeyMask;
441 			event. commandKeyPressed = modifiers & NSCommandKeyMask;
442 			try {
443 				my mouseCallback (my mouseBoss, & event);
444 			} catch (MelderError) {
445 				switch (phase) {
446 					case structGuiDrawingArea_MouseEvent::Phase::CLICK:
447 						Melder_flushError (U"Mouse click not completely handled.");
448 					break; case structGuiDrawingArea_MouseEvent::Phase::DRAG:
449 						Melder_flushError (U"Mouse drag not completely handled.");
450 					break; case structGuiDrawingArea_MouseEvent::Phase::DROP:
451 						Melder_flushError (U"Mouse drop not completely handled.");
452 					break;
453 				}
454 			}
455 		}
456 	}
457 	- (void) mouseDown: (NSEvent *) nsEvent {
458 	 	[self   mouse: nsEvent   inPhase: structGuiDrawingArea_MouseEvent::Phase::CLICK];
459 	}
460 	- (void) mouseDragged: (NSEvent *) nsEvent {
461 	 	[self   mouse: nsEvent   inPhase: structGuiDrawingArea_MouseEvent::Phase::DRAG];
462 	}
463 	- (void) mouseUp: (NSEvent *) nsEvent {
464 	 	[self   mouse: nsEvent   inPhase: structGuiDrawingArea_MouseEvent::Phase::DROP];
465 	}
466 	- (void) scrollWheel: (NSEvent *) nsEvent {
467 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
468 		if (me && (my d_horizontalScrollBar || my d_verticalScrollBar)) {
469 			if (my d_horizontalScrollBar) {
470 				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_horizontalScrollBar -> d_widget;
471 				[cocoaScrollBar scrollBy: [nsEvent scrollingDeltaX]];
472 			}
473 			if (my d_verticalScrollBar) {
474 				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_verticalScrollBar -> d_widget;
475 				[cocoaScrollBar scrollBy: [nsEvent scrollingDeltaY]];
476 			}
477 		} else {
478 			[super scrollWheel: nsEvent];
479 		}
480 	}
481 	- (void) magnifyWithEvent: (NSEvent *) nsEvent {
482 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
483 		if (me && (my d_horizontalScrollBar || my d_verticalScrollBar)) {
484 			if (my d_horizontalScrollBar) {
485 				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_horizontalScrollBar -> d_widget;
486 				[cocoaScrollBar magnifyBy: [nsEvent magnification]];
487 			}
488 			if (my d_verticalScrollBar) {
489 				GuiCocoaScrollBar *cocoaScrollBar = (GuiCocoaScrollBar *) my d_verticalScrollBar -> d_widget;
490 				[cocoaScrollBar magnifyBy: [nsEvent magnification]];
491 			}
492 		} else {
493 			[super magnifyWithEvent: nsEvent];
494 		}
495 	}
496 	- (BOOL) isFlipped {
497 		return YES;
498 	}
499 	- (void) keyDown: (NSEvent *) nsEvent {
500 		Melder_casual (U"key pressed ");
501 		GuiDrawingArea me = (GuiDrawingArea) d_userData;
502 		if (me && my d_keyCallback) {
503 			structGuiDrawingArea_KeyEvent event = { me, U'\0', false, false, false };
504 			event. key = [[nsEvent charactersIgnoringModifiers]   characterAtIndex: 0];
505 			if (event. key == NSLeftArrowFunctionKey)  event. key = 0x2190;
506 			if (event. key == NSRightArrowFunctionKey) event. key = 0x2192;
507 			if (event. key == NSUpArrowFunctionKey)    event. key = 0x2191;
508 			if (event. key == NSDownArrowFunctionKey)  event. key = 0x2193;
509 			Melder_casual (U"key ", event. key);
510 			NSUInteger modifiers = [nsEvent modifierFlags];
511 			event. shiftKeyPressed = modifiers & NSShiftKeyMask;
512 			event. optionKeyPressed = modifiers & NSAlternateKeyMask;
513 			event. commandKeyPressed = modifiers & NSCommandKeyMask;
514 			try {
515 				my d_keyCallback (my d_keyBoss, & event);
516 			} catch (MelderError) {
517 				Melder_flushError (U"Key press not completely handled.");
518 			}
519 		}
520 	}
521 	- (void) cancelOperation: (id) sender {
522 		Melder_casual (U"escape key pressed");
523 	}
524 	@end
525 #endif
526 
v_destroy()527 void structGuiDrawingArea :: v_destroy () noexcept {
528 	if (Melder_debug == 55)
529 		Melder_casual (U"\t", Thing_messageNameAndAddress (this), U" v_destroy");
530 	#if cocoa
531 		if (our d_widget)
532 			[our d_widget setUserData: nullptr];   // undangle reference to this
533 	#endif
534 	GuiDrawingArea_Parent :: v_destroy ();
535 }
536 
GuiDrawingArea_create(GuiForm parent,int left,int right,int top,int bottom,GuiDrawingArea_ExposeCallback exposeCallback,GuiDrawingArea_MouseCallback mouseCallback,GuiDrawingArea_KeyCallback keyCallback,GuiDrawingArea_ResizeCallback resizeCallback,Thing boss,uint32)537 GuiDrawingArea GuiDrawingArea_create (GuiForm parent, int left, int right, int top, int bottom,
538 	GuiDrawingArea_ExposeCallback exposeCallback,
539 	GuiDrawingArea_MouseCallback mouseCallback,
540 	GuiDrawingArea_KeyCallback keyCallback,
541 	GuiDrawingArea_ResizeCallback resizeCallback, Thing boss,
542 	uint32 /* flags */)
543 {
544 	autoGuiDrawingArea me = Thing_new (GuiDrawingArea);
545 	if (Melder_debug == 55)
546 		Melder_casual (U"\t", Thing_messageNameAndAddress (me.get()), U" init in ", Thing_messageNameAndAddress (parent -> d_shell));
547 	my d_shell = parent -> d_shell;
548 	my d_shell -> drawingArea = me.get();
549 	my d_parent = parent;
550 	my d_exposeCallback = exposeCallback;
551 	my d_exposeBoss = boss;
552 	my mouseCallback = mouseCallback;
553 	my mouseBoss = boss;
554 	my d_keyCallback = keyCallback;
555 	my d_keyBoss = boss;
556 	my d_resizeCallback = resizeCallback;
557 	my d_resizeBoss = boss;
558 	#if gtk
559 		my d_widget = gtk_drawing_area_new ();
560 		GdkEventMask mask = (GdkEventMask) (GDK_EXPOSURE_MASK   // receive exposure events
561 			| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK   // receive click events
562 			| GDK_BUTTON_MOTION_MASK                            // receive motion notifies when a button is pressed
563 			| GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
564 			| GDK_POINTER_MOTION_HINT_MASK);                    // receive fewer motion notify events (the cb might take time)
565 		gtk_widget_set_events (GTK_WIDGET (my d_widget), mask);
566 		g_signal_connect (G_OBJECT (my d_widget), "draw",         G_CALLBACK (_guiGtkDrawingArea_drawCallback),       me.get());
567 		g_signal_connect (G_OBJECT (my d_widget), "destroy",              G_CALLBACK (_guiGtkDrawingArea_destroyCallback),      me.get());
568 		g_signal_connect (G_OBJECT (my d_widget), "button-press-event",   G_CALLBACK (_guiGtkDrawingArea_mouseDownCallback),    me.get());
569 		g_signal_connect (G_OBJECT (my d_widget), "button-release-event", G_CALLBACK (_guiGtkDrawingArea_mouseUpCallback),      me.get());
570 		//g_signal_connect (G_OBJECT (my d_widget), "drag-motion-event",    G_CALLBACK (_guiGtkDrawingArea_mouseUpCallback),   me.get());
571 		g_signal_connect (G_OBJECT (my d_widget), "motion-notify-event",  G_CALLBACK (_guiGtkDrawingArea_mouseDraggedCallback), me.get());
572 		if (parent) {
573 			Melder_assert (parent -> d_widget);
574 			g_signal_connect (G_OBJECT (gtk_widget_get_toplevel (GTK_WIDGET (parent -> d_widget))), "key-press-event",
575 				G_CALLBACK (_guiGtkDrawingArea_keyCallback), me.get());
576 		}
577 		g_signal_connect (G_OBJECT (my d_widget), "size-allocate", G_CALLBACK (_guiGtkDrawingArea_resizeCallback), me.get());
578 
579 		_GuiObject_setUserData (my d_widget, me.get());
580 		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
581     #elif motif
582 		my d_widget = _Gui_initializeWidget (xmDrawingAreaWidgetClass, parent -> d_widget, U"drawingArea");
583 		_GuiObject_setUserData (my d_widget, me.get());
584 		my d_widget -> window = CreateWindowEx (0, Melder_peek32toW (_GuiWin_getDrawingAreaClassName ()), L"drawingArea",
585 			WS_CHILD | WS_BORDER | WS_CLIPSIBLINGS,
586 			my d_widget -> x, my d_widget -> y, my d_widget -> width, my d_widget -> height, my d_widget -> parent -> window, nullptr, theGui.instance, nullptr);
587 		SetWindowLongPtr (my d_widget -> window, GWLP_USERDATA, (LONG_PTR) my d_widget);
588 		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
589 	#elif cocoa
590 		GuiCocoaDrawingArea *drawingArea = [[GuiCocoaDrawingArea alloc] init];
591 		if (Melder_debug == 55)
592 			Melder_casual (U"\t\tGuiCocoaDrawingArea-", Melder_pointer (drawingArea), U" init in ", Thing_messageNameAndAddress (me.get()));
593 		my d_widget = (GuiObject) drawingArea;
594 		my v_positionInForm (my d_widget, left, right, top, bottom, parent);
595 		[drawingArea   setUserData: me.get()];
596 		if (keyCallback) {
597 			[[drawingArea window]   makeFirstResponder: drawingArea];   // needed in DemoWindow
598 		}
599 	#endif
600 	return me.releaseToAmbiguousOwner();
601 }
602 
GuiDrawingArea_createShown(GuiForm parent,int left,int right,int top,int bottom,GuiDrawingArea_ExposeCallback exposeCallback,GuiDrawingArea_MouseCallback mouseCallback,GuiDrawingArea_KeyCallback keyCallback,GuiDrawingArea_ResizeCallback resizeCallback,Thing boss,uint32 flags)603 GuiDrawingArea GuiDrawingArea_createShown (GuiForm parent, int left, int right, int top, int bottom,
604 	GuiDrawingArea_ExposeCallback exposeCallback,
605 	GuiDrawingArea_MouseCallback mouseCallback,
606 	GuiDrawingArea_KeyCallback keyCallback,
607 	GuiDrawingArea_ResizeCallback resizeCallback, Thing boss,
608 	uint32 flags)
609 {
610 	GuiDrawingArea me = GuiDrawingArea_create (parent, left, right, top, bottom,
611 		exposeCallback, mouseCallback,
612 		keyCallback, resizeCallback, boss, flags
613 	);
614 	GuiThing_show (me);
615 	return me;
616 }
617 
GuiDrawingArea_create(GuiScrolledWindow parent,int width,int height,GuiDrawingArea_ExposeCallback exposeCallback,GuiDrawingArea_MouseCallback mouseCallback,GuiDrawingArea_KeyCallback keyCallback,GuiDrawingArea_ResizeCallback resizeCallback,Thing boss,uint32)618 GuiDrawingArea GuiDrawingArea_create (GuiScrolledWindow parent, int width, int height,
619 	GuiDrawingArea_ExposeCallback exposeCallback,
620 	GuiDrawingArea_MouseCallback mouseCallback,
621 	GuiDrawingArea_KeyCallback keyCallback,
622 	GuiDrawingArea_ResizeCallback resizeCallback, Thing boss,
623 	uint32 /* flags */)
624 {
625 	autoGuiDrawingArea me = Thing_new (GuiDrawingArea);
626 	my d_shell = parent -> d_shell;
627 	my d_shell -> drawingArea = me.get();
628 	my d_parent = parent;
629 	my d_exposeCallback = exposeCallback;
630 	my d_exposeBoss = boss;
631 	my mouseCallback = mouseCallback;
632 	my mouseBoss = boss;
633 	my d_keyCallback = keyCallback;
634 	my d_keyBoss = boss;
635 	my d_resizeCallback = resizeCallback;
636 	my d_resizeBoss = boss;
637 	#if gtk
638 		my d_widget = gtk_drawing_area_new ();
639 		GdkEventMask mask = (GdkEventMask) (GDK_EXPOSURE_MASK   // receive exposure events
640 			| GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK   // receive click events
641 			| GDK_BUTTON_MOTION_MASK                            // receive motion notifies when a button is pressed
642 			| GDK_KEY_PRESS_MASK | GDK_KEY_RELEASE_MASK
643 			| GDK_POINTER_MOTION_HINT_MASK);                    // receive fewer motion notify events (the cb might take time)
644 		gtk_widget_set_events (GTK_WIDGET (my d_widget), mask);
645 		g_signal_connect (G_OBJECT (my d_widget), "draw",         G_CALLBACK (_guiGtkDrawingArea_drawCallback),       me.get());
646 		g_signal_connect (G_OBJECT (my d_widget), "destroy",              G_CALLBACK (_guiGtkDrawingArea_destroyCallback),      me.get());
647 		g_signal_connect (G_OBJECT (my d_widget), "button-press-event",   G_CALLBACK (_guiGtkDrawingArea_mouseDownCallback),    me.get());
648 		g_signal_connect (G_OBJECT (my d_widget), "button-release-event", G_CALLBACK (_guiGtkDrawingArea_mouseUpCallback),      me.get());
649 		g_signal_connect (G_OBJECT (my d_widget), "motion-notify-event",  G_CALLBACK (_guiGtkDrawingArea_mouseDraggedCallback), me.get());
650 		if (parent) {
651 			g_signal_connect (G_OBJECT (gtk_widget_get_toplevel (GTK_WIDGET (parent -> d_widget))), "key-press-event",
652 				G_CALLBACK (_guiGtkDrawingArea_keyCallback), me.get());
653 		}
654 		g_signal_connect (G_OBJECT (my d_widget), "size-allocate", G_CALLBACK (_guiGtkDrawingArea_resizeCallback), me.get());
655 		_GuiObject_setUserData (my d_widget, me.get());
656 		my v_positionInScrolledWindow (my d_widget, width, height, parent);
657     #elif motif
658 		my d_widget = _Gui_initializeWidget (xmDrawingAreaWidgetClass, parent -> d_widget, U"drawingArea");
659 		_GuiObject_setUserData (my d_widget, me.get());
660 		my d_widget -> window = CreateWindowEx (0, Melder_peek32toW (_GuiWin_getDrawingAreaClassName ()), L"drawingArea",
661 			WS_CHILD | WS_BORDER | WS_CLIPSIBLINGS,
662 			0, 0, my d_widget -> width, my d_widget -> height, my d_widget -> parent -> window, nullptr, theGui.instance, nullptr);
663 		SetWindowLongPtr (my d_widget -> window, GWLP_USERDATA, (LONG_PTR) my d_widget);
664 		my v_positionInScrolledWindow (my d_widget, width, height, parent);
665 	#elif cocoa
666 		GuiCocoaDrawingArea *drawingArea = [[GuiCocoaDrawingArea alloc] init];
667 		my d_widget = (GuiObject) drawingArea;
668 		my v_positionInScrolledWindow (my d_widget, width, height, parent);
669 		[drawingArea setUserData: me.get()];
670 	#endif
671 	return me.releaseToAmbiguousOwner();
672 }
673 
GuiDrawingArea_createShown(GuiScrolledWindow parent,int width,int height,GuiDrawingArea_ExposeCallback exposeCallback,GuiDrawingArea_MouseCallback mouseCallback,GuiDrawingArea_KeyCallback keyCallback,GuiDrawingArea_ResizeCallback resizeCallback,Thing boss,uint32 flags)674 GuiDrawingArea GuiDrawingArea_createShown (GuiScrolledWindow parent, int width, int height,
675 	GuiDrawingArea_ExposeCallback exposeCallback,
676 	GuiDrawingArea_MouseCallback mouseCallback,
677 	GuiDrawingArea_KeyCallback keyCallback,
678 	GuiDrawingArea_ResizeCallback resizeCallback, Thing boss,
679 	uint32 flags)
680 {
681 	GuiDrawingArea me = GuiDrawingArea_create (parent, width, height,
682 		exposeCallback, mouseCallback,
683 		keyCallback, resizeCallback, boss, flags
684 	);
685 	GuiThing_show (me);
686 	return me;
687 }
688 
GuiDrawingArea_setSwipable(GuiDrawingArea me,GuiScrollBar horizontalScrollBar,GuiScrollBar verticalScrollBar)689 void GuiDrawingArea_setSwipable (GuiDrawingArea me, GuiScrollBar horizontalScrollBar, GuiScrollBar verticalScrollBar) {
690 	my d_horizontalScrollBar = horizontalScrollBar;
691 	my d_verticalScrollBar = verticalScrollBar;
692 	#if gtk
693 		gtk_widget_add_events (GTK_WIDGET (my d_widget), GDK_SCROLL_MASK);
694 		g_signal_connect (G_OBJECT (my d_widget), "scroll-event", G_CALLBACK (_guiGtkDrawingArea_swipeCallback), me);
695 	#endif
696 }
697 
GuiDrawingArea_setExposeCallback(GuiDrawingArea me,GuiDrawingArea_ExposeCallback callback,Thing boss)698 void GuiDrawingArea_setExposeCallback (GuiDrawingArea me, GuiDrawingArea_ExposeCallback callback, Thing boss) {
699 	my d_exposeCallback = callback;
700 	my d_exposeBoss = boss;
701 }
702 
GuiDrawingArea_setMouseCallback(GuiDrawingArea me,GuiDrawingArea_MouseCallback callback,Thing boss)703 void GuiDrawingArea_setMouseCallback (GuiDrawingArea me, GuiDrawingArea_MouseCallback callback, Thing boss) {
704 	my mouseCallback = callback;
705 	my mouseBoss = boss;
706 }
707 
GuiDrawingArea_setResizeCallback(GuiDrawingArea me,GuiDrawingArea_ResizeCallback callback,Thing boss)708 void GuiDrawingArea_setResizeCallback (GuiDrawingArea me, GuiDrawingArea_ResizeCallback callback, Thing boss) {
709 	my d_resizeCallback = callback;
710 	my d_resizeBoss = boss;
711 }
712 
713 /* End of file GuiDrawingArea.cpp */
714