1 //
2 // GtkWorkarounds.cs
3 //
4 // Authors: Jeffrey Stedfast <jeff@xamarin.com>
5 //
6 // Copyright (C) 2011 Xamarin Inc.
7 //
8 // Permission is hereby granted, free of charge, to any person obtaining a copy
9 // of this software and associated documentation files (the "Software"), to deal
10 // in the Software without restriction, including without limitation the rights
11 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 // copies of the Software, and to permit persons to whom the Software is
13 // furnished to do so, subject to the following conditions:
14 //
15 // The above copyright notice and this permission notice shall be included in
16 // all copies or substantial portions of the Software.
17 //
18 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 // THE SOFTWARE.
25 //
26 
27 using System;
28 using System.Runtime.InteropServices;
29 using System.Collections.Generic;
30 using System.Reflection;
31 using System.Reflection.Emit;
32 using Gtk;
33 
34 namespace Pinta.Docking
35 {
36 	static class GtkWorkarounds
37 	{
38 		const string LIBOBJC ="/usr/lib/libobjc.dylib";
39 		const string USER32DLL = "User32.dll";
40 
41 		[DllImport (LIBOBJC, EntryPoint = "sel_registerName")]
sel_registerName(string selector)42 		static extern IntPtr sel_registerName (string selector);
43 
44 		[DllImport (LIBOBJC, EntryPoint = "objc_getClass")]
objc_getClass(string klass)45 		static extern IntPtr objc_getClass (string klass);
46 
47 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
objc_msgSend_IntPtr(IntPtr klass, IntPtr selector)48 		static extern IntPtr objc_msgSend_IntPtr (IntPtr klass, IntPtr selector);
49 
50 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
objc_msgSend_void_bool(IntPtr klass, IntPtr selector, bool arg)51 		static extern void objc_msgSend_void_bool (IntPtr klass, IntPtr selector, bool arg);
52 
53 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
objc_msgSend_bool(IntPtr klass, IntPtr selector)54 		static extern bool objc_msgSend_bool (IntPtr klass, IntPtr selector);
55 
56 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
objc_msgSend_NSInt32_NSInt32(IntPtr klass, IntPtr selector, int arg)57 		static extern int objc_msgSend_NSInt32_NSInt32 (IntPtr klass, IntPtr selector, int arg);
58 
59 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
objc_msgSend_NSInt64_NSInt64(IntPtr klass, IntPtr selector, long arg)60 		static extern long objc_msgSend_NSInt64_NSInt64 (IntPtr klass, IntPtr selector, long arg);
61 
62 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
objc_msgSend_NSUInt32(IntPtr klass, IntPtr selector)63 		static extern uint objc_msgSend_NSUInt32 (IntPtr klass, IntPtr selector);
64 
65 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
objc_msgSend_NSUInt64(IntPtr klass, IntPtr selector)66 		static extern ulong objc_msgSend_NSUInt64 (IntPtr klass, IntPtr selector);
67 
68 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend_stret")]
objc_msgSend_CGRect32(out CGRect32 rect, IntPtr klass, IntPtr selector)69 		static extern void objc_msgSend_CGRect32 (out CGRect32 rect, IntPtr klass, IntPtr selector);
70 
71 		[DllImport (LIBOBJC, EntryPoint = "objc_msgSend_stret")]
objc_msgSend_CGRect64(out CGRect64 rect, IntPtr klass, IntPtr selector)72 		static extern void objc_msgSend_CGRect64 (out CGRect64 rect, IntPtr klass, IntPtr selector);
73 
74 		[DllImport (PangoUtil.LIBQUARTZ)]
gdk_quartz_window_get_nswindow(IntPtr window)75 		static extern IntPtr gdk_quartz_window_get_nswindow (IntPtr window);
76 
77 		[DllImport (PangoUtil.LIBQUARTZ)]
gdk_window_has_embedded_nsview_focus(IntPtr window)78 		static extern bool gdk_window_has_embedded_nsview_focus (IntPtr window);
79 
80 		struct CGRect32
81 		{
82 			public float X, Y, Width, Height;
83 		}
84 
85 		struct CGRect64
86 		{
87 			public double X, Y, Width, Height;
88 
CGRect64Pinta.Docking.GtkWorkarounds.CGRect6489 			public CGRect64 (CGRect32 rect32)
90 			{
91 				X = rect32.X;
92 				Y = rect32.Y;
93 				Width = rect32.Width;
94 				Height = rect32.Height;
95 			}
96 		}
97 
98 		static IntPtr cls_NSScreen;
99 		static IntPtr sel_screens, sel_objectEnumerator, sel_nextObject, sel_frame, sel_visibleFrame,
100 			sel_requestUserAttention, sel_setHasShadow, sel_invalidateShadow;
101 		static IntPtr sharedApp;
102 		static IntPtr cls_NSEvent;
103 		static IntPtr sel_modifierFlags;
104 
105 		const int NSCriticalRequest = 0;
106 		const int NSInformationalRequest = 10;
107 
108 		static System.Reflection.MethodInfo glibObjectGetProp, glibObjectSetProp;
109 
110 		public static int GtkMinorVersion = 12, GtkMicroVersion = 0;
111 		static bool oldMacKeyHacks = false;
112 
GtkWorkarounds()113 		static GtkWorkarounds ()
114 		{
115 			if (Platform.IsMac) {
116 				InitMac ();
117 			}
118 
119 			var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
120 			glibObjectSetProp = typeof (GLib.Object).GetMethod ("SetProperty", flags);
121 			glibObjectGetProp = typeof (GLib.Object).GetMethod ("GetProperty", flags);
122 
123 			foreach (int i in new [] { 24, 22, 20, 18, 16, 14 }) {
124 				if (Gtk.Global.CheckVersion (2, (uint)i, 0) == null) {
125 					GtkMinorVersion = i;
126 					break;
127 				}
128 			}
129 
130 			for (int i = 1; i < 20; i++) {
131 				if (Gtk.Global.CheckVersion (2, (uint)GtkMinorVersion, (uint)i) == null) {
132 					GtkMicroVersion = i;
133 				} else {
134 					break;
135 				}
136 			}
137 
138 			//opt into the fixes on GTK+ >= 2.24.8
139 			if (Platform.IsMac) {
140 				try {
141 					gdk_quartz_set_fix_modifiers (true);
142 				} catch (EntryPointNotFoundException) {
143 					oldMacKeyHacks = true;
144 				}
145 			}
146 
147 			keymap.KeysChanged += delegate {
148 				mappedKeys.Clear ();
149 			};
150 		}
151 
InitMac()152 		static void InitMac ()
153 		{
154 			cls_NSScreen = objc_getClass ("NSScreen");
155 			cls_NSEvent = objc_getClass ("NSEvent");
156 			sel_screens = sel_registerName ("screens");
157 			sel_objectEnumerator = sel_registerName ("objectEnumerator");
158 			sel_nextObject = sel_registerName ("nextObject");
159 			sel_visibleFrame = sel_registerName ("visibleFrame");
160 			sel_frame = sel_registerName ("frame");
161 			sel_requestUserAttention = sel_registerName ("requestUserAttention:");
162 			sel_modifierFlags = sel_registerName ("modifierFlags");
163 			sel_setHasShadow = sel_registerName ("setHasShadow:");
164 			sel_invalidateShadow = sel_registerName ("invalidateShadow");
165 			sharedApp = objc_msgSend_IntPtr (objc_getClass ("NSApplication"), sel_registerName ("sharedApplication"));
166 		}
167 
MacGetUsableMonitorGeometry(Gdk.Screen screen, int monitor)168 		static Gdk.Rectangle MacGetUsableMonitorGeometry (Gdk.Screen screen, int monitor)
169 		{
170 			IntPtr array = objc_msgSend_IntPtr (cls_NSScreen, sel_screens);
171 			IntPtr iter = objc_msgSend_IntPtr (array, sel_objectEnumerator);
172 			Gdk.Rectangle ygeometry = screen.GetMonitorGeometry (monitor);
173 			Gdk.Rectangle xgeometry = screen.GetMonitorGeometry (0);
174 			IntPtr scrn;
175 			int i = 0;
176 
177 			while ((scrn = objc_msgSend_IntPtr (iter, sel_nextObject)) != IntPtr.Zero && i < monitor)
178 				i++;
179 
180 			if (scrn == IntPtr.Zero)
181 				return screen.GetMonitorGeometry (monitor);
182 
183 			CGRect64 visible, frame;
184 
185 			if (IntPtr.Size == 8) {
186 				objc_msgSend_CGRect64 (out visible, scrn, sel_visibleFrame);
187 				objc_msgSend_CGRect64 (out frame, scrn, sel_frame);
188 			} else {
189 				CGRect32 visible32, frame32;
190 				objc_msgSend_CGRect32 (out visible32, scrn, sel_visibleFrame);
191 				objc_msgSend_CGRect32 (out frame32, scrn, sel_frame);
192 				visible = new CGRect64 (visible32);
193 				frame = new CGRect64 (frame32);
194 			}
195 
196 			// Note: Frame and VisibleFrame rectangles are relative to monitor 0, but we need absolute
197 			// coordinates.
198 			visible.X += xgeometry.X;
199 			frame.X += xgeometry.X;
200 
201 			// VisibleFrame.Y is the height of the Dock if it is at the bottom of the screen, so in order
202 			// to get the menu height, we just figure out the difference between the visibleFrame height
203 			// and the actual frame height, then subtract the Dock height.
204 			//
205 			// We need to swap the Y offset with the menu height because our callers expect the Y offset
206 			// to be from the top of the screen, not from the bottom of the screen.
207 			double x, y, width, height;
208 
209 			if (visible.Height < frame.Height) {
210 				double dockHeight = visible.Y - frame.Y;
211 				double menubarHeight = (frame.Height - visible.Height) - dockHeight;
212 
213 				height = frame.Height - menubarHeight - dockHeight;
214 				y = ygeometry.Y + menubarHeight;
215 			} else {
216 				height = frame.Height;
217 				y = ygeometry.Y;
218 			}
219 
220 			// Takes care of the possibility of the Dock being positioned on the left or right edge of the screen.
221 			width = System.Math.Min (visible.Width, frame.Width);
222 			x = System.Math.Max (visible.X, frame.X);
223 
224 			return new Gdk.Rectangle ((int) x, (int) y, (int) width, (int) height);
225 		}
226 
MacRequestAttention(bool critical)227 		static void MacRequestAttention (bool critical)
228 		{
229 			int kind = critical?  NSCriticalRequest : NSInformationalRequest;
230 			if (IntPtr.Size == 8) {
231 				objc_msgSend_NSInt64_NSInt64 (sharedApp, sel_requestUserAttention, kind);
232 			} else {
233 				objc_msgSend_NSInt32_NSInt32 (sharedApp, sel_requestUserAttention, kind);
234 			}
235 		}
236 
237 		// Note: we can't reuse RectangleF because the layout is different...
238 		[StructLayout (LayoutKind.Sequential)]
239 		struct Rect {
240 			public int Left;
241 			public int Top;
242 			public int Right;
243 			public int Bottom;
244 
245 			public int X { get { return Left; } }
246 			public int Y { get { return Top; } }
247 			public int Width { get { return Right - Left; } }
248 			public int Height { get { return Bottom - Top; } }
249 		}
250 
251 		const int MonitorInfoFlagsPrimary = 0x01;
252 
253 		[StructLayout (LayoutKind.Sequential)]
254 		unsafe struct MonitorInfo {
255 			public int Size;
256 			public Rect Frame;         // Monitor
257 			public Rect VisibleFrame;  // Work
258 			public int Flags;
259 			public fixed byte Device[32];
260 		}
261 
262 		[UnmanagedFunctionPointer (CallingConvention.Winapi)]
EnumMonitorsCallback(IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data)263 		delegate int EnumMonitorsCallback (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data);
264 
265 		[DllImport (USER32DLL)]
EnumDisplayMonitors(IntPtr hdc, IntPtr clip, EnumMonitorsCallback callback, IntPtr user_data)266 		extern static int EnumDisplayMonitors (IntPtr hdc, IntPtr clip, EnumMonitorsCallback callback, IntPtr user_data);
267 
268 		[DllImport (USER32DLL)]
GetMonitorInfoA(IntPtr hmonitor, ref MonitorInfo info)269 		extern static int GetMonitorInfoA (IntPtr hmonitor, ref MonitorInfo info);
270 
WindowsGetUsableMonitorGeometry(Gdk.Screen screen, int monitor_id)271 		static Gdk.Rectangle WindowsGetUsableMonitorGeometry (Gdk.Screen screen, int monitor_id)
272 		{
273 			Gdk.Rectangle geometry = screen.GetMonitorGeometry (monitor_id);
274 			List<MonitorInfo> screens = new List<MonitorInfo> ();
275 
276 			EnumDisplayMonitors (IntPtr.Zero, IntPtr.Zero, delegate (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data) {
277 				var info = new MonitorInfo ();
278 
279 				unsafe {
280 					info.Size = sizeof (MonitorInfo);
281 				}
282 
283 				GetMonitorInfoA (hmonitor, ref info);
284 
285 				// In order to keep the order the same as Gtk, we need to put the primary monitor at the beginning.
286 				if ((info.Flags & MonitorInfoFlagsPrimary) != 0)
287 					screens.Insert (0, info);
288 				else
289 					screens.Add (info);
290 
291 				return 1;
292 			}, IntPtr.Zero);
293 
294 			MonitorInfo monitor = screens[monitor_id];
295 			Rect visible = monitor.VisibleFrame;
296 			Rect frame = monitor.Frame;
297 
298 			// Rebase the VisibleFrame off of Gtk's idea of this monitor's geometry (since they use different coordinate systems)
299 			int x = geometry.X + (visible.Left - frame.Left);
300 			int width = visible.Width;
301 
302 			int y = geometry.Y + (visible.Top - frame.Top);
303 			int height = visible.Height;
304 
305 			return new Gdk.Rectangle (x, y, width, height);
306 		}
307 
GetUsableMonitorGeometry(this Gdk.Screen screen, int monitor)308 		public static Gdk.Rectangle GetUsableMonitorGeometry (this Gdk.Screen screen, int monitor)
309 		{
310 			if (Platform.IsWindows)
311 				return WindowsGetUsableMonitorGeometry (screen, monitor);
312 
313 			if (Platform.IsMac)
314 				return MacGetUsableMonitorGeometry (screen, monitor);
315 
316 			return screen.GetMonitorGeometry (monitor);
317 		}
318 
RunDialogWithNotification(Gtk.Dialog dialog)319 		public static int RunDialogWithNotification (Gtk.Dialog dialog)
320 		{
321 			if (Platform.IsMac)
322 				MacRequestAttention (dialog.Modal);
323 
324 			return dialog.Run ();
325 		}
326 
PresentWindowWithNotification(this Gtk.Window window)327 		public static void PresentWindowWithNotification (this Gtk.Window window)
328 		{
329 			window.Present ();
330 
331 			if (Platform.IsMac) {
332 				var dialog = window as Gtk.Dialog;
333 				MacRequestAttention (dialog == null? false : dialog.Modal);
334 			}
335 		}
336 
GetProperty(this GLib.Object obj, string name)337 		public static GLib.Value GetProperty (this GLib.Object obj, string name)
338 		{
339 			return (GLib.Value) glibObjectGetProp.Invoke (obj, new object[] { name });
340 		}
341 
SetProperty(this GLib.Object obj, string name, GLib.Value value)342 		public static void SetProperty (this GLib.Object obj, string name, GLib.Value value)
343 		{
344 			glibObjectSetProp.Invoke (obj, new object[] { name, value });
345 		}
346 
TriggersContextMenu(this Gdk.EventButton evt)347 		public static bool TriggersContextMenu (this Gdk.EventButton evt)
348 		{
349 			return evt.Type == Gdk.EventType.ButtonPress && IsContextMenuButton (evt);
350 		}
351 
IsContextMenuButton(this Gdk.EventButton evt)352 		public static bool IsContextMenuButton (this Gdk.EventButton evt)
353 		{
354 			if (evt.Button == 3 &&
355 					(evt.State & (Gdk.ModifierType.Button1Mask | Gdk.ModifierType.Button2Mask)) == 0)
356 				return true;
357 
358 			if (Platform.IsMac) {
359 				if (!oldMacKeyHacks &&
360 					evt.Button == 1 &&
361 					(evt.State & Gdk.ModifierType.ControlMask) != 0 &&
362 					(evt.State & (Gdk.ModifierType.Button2Mask | Gdk.ModifierType.Button3Mask)) == 0)
363 				{
364 					return true;
365 				}
366 			}
367 
368 			return false;
369 		}
370 
GetCurrentKeyModifiers()371 		public static Gdk.ModifierType GetCurrentKeyModifiers ()
372 		{
373 			if (Platform.IsMac) {
374 				Gdk.ModifierType mtype = Gdk.ModifierType.None;
375 				ulong mod;
376 				if (IntPtr.Size == 8) {
377 					mod = objc_msgSend_NSUInt64 (cls_NSEvent, sel_modifierFlags);
378 				} else {
379 					mod = objc_msgSend_NSUInt32 (cls_NSEvent, sel_modifierFlags);
380 				}
381 				if ((mod & (1 << 17)) != 0)
382 					mtype |= Gdk.ModifierType.ShiftMask;
383 				if ((mod & (1 << 18)) != 0)
384 					mtype |= Gdk.ModifierType.ControlMask;
385 				if ((mod & (1 << 19)) != 0)
386 					mtype |= Gdk.ModifierType.Mod1Mask; // Alt key
387 				if ((mod & (1 << 20)) != 0)
388 					mtype |= Gdk.ModifierType.Mod2Mask; // Command key
389 				return mtype;
390 			}
391 			else {
392 				Gdk.ModifierType mtype;
393 				Gtk.Global.GetCurrentEventState (out mtype);
394 				return mtype;
395 			}
396 		}
397 
GetPageScrollPixelDeltas(this Gdk.EventScroll evt, double pageSizeX, double pageSizeY, out double deltaX, out double deltaY)398 		public static void GetPageScrollPixelDeltas (this Gdk.EventScroll evt, double pageSizeX, double pageSizeY,
399 			out double deltaX, out double deltaY)
400 		{
401 			if (!GetEventScrollDeltas (evt, out deltaX, out deltaY)) {
402 				var direction = evt.Direction;
403 				deltaX = deltaY = 0;
404 				if (pageSizeY != 0 && (direction == Gdk.ScrollDirection.Down || direction == Gdk.ScrollDirection.Up)) {
405 					deltaY = System.Math.Pow (pageSizeY, 2.0 / 3.0);
406 					deltaX = 0.0;
407 					if (direction == Gdk.ScrollDirection.Up)
408 						deltaY = -deltaY;
409 				} else if (pageSizeX != 0) {
410 					deltaX = System.Math.Pow (pageSizeX, 2.0 / 3.0);
411 					deltaY = 0.0;
412 					if (direction == Gdk.ScrollDirection.Left)
413 						deltaX = -deltaX;
414 				}
415 			}
416 		}
417 
AddValueClamped(this Gtk.Adjustment adj, double value)418 		public static void AddValueClamped (this Gtk.Adjustment adj, double value)
419 		{
420 			adj.Value = System.Math.Max (adj.Lower, System.Math.Min (adj.Value + value, adj.Upper - adj.PageSize));
421 		}
422 
423 		[DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
gdk_event_get_scroll_deltas(IntPtr eventScroll, out double deltaX, out double deltaY)424 		extern static bool gdk_event_get_scroll_deltas (IntPtr eventScroll, out double deltaX, out double deltaY);
425 		static bool scrollDeltasNotSupported;
426 
GetEventScrollDeltas(Gdk.EventScroll evt, out double deltaX, out double deltaY)427 		public static bool GetEventScrollDeltas (Gdk.EventScroll evt, out double deltaX, out double deltaY)
428 		{
429 			if (!scrollDeltasNotSupported) {
430 				try {
431 					return gdk_event_get_scroll_deltas (evt.Handle, out deltaX, out deltaY);
432 				} catch (EntryPointNotFoundException) {
433 					scrollDeltasNotSupported = true;
434 				}
435 			}
436 			deltaX = deltaY = 0;
437 			return false;
438 		}
439 
440 		/// <summary>Shows a context menu.</summary>
441 		/// <param name='menu'>The menu.</param>
442 		/// <param name='parent'>The parent widget.</param>
443 		/// <param name='evt'>The mouse event. May be null if triggered by keyboard.</param>
444 		/// <param name='caret'>The caret/selection position within the parent, if the EventButton is null.</param>
ShowContextMenu(Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt, Gdk.Rectangle caret)445 		public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt, Gdk.Rectangle caret)
446 		{
447 			Gtk.MenuPositionFunc posFunc = null;
448 
449 			if (parent != null) {
450 				menu.AttachToWidget (parent, null);
451 				menu.Hidden += (sender, e) => {
452 					if (menu.AttachWidget != null)
453 						menu.Detach ();
454 				};
455 				posFunc = delegate (Gtk.Menu m, out int x, out int y, out bool pushIn) {
456 					Gdk.Window window = evt != null? evt.Window : parent.GdkWindow;
457 					window.GetOrigin (out x, out y);
458 					var alloc = parent.Allocation;
459 					if (evt != null) {
460 						x += (int) evt.X;
461 						y += (int) evt.Y;
462 					} else if (caret.X >= alloc.X && caret.Y >= alloc.Y) {
463 						x += caret.X;
464 						y += caret.Y + caret.Height;
465 					} else {
466 						x += alloc.X;
467 						y += alloc.Y;
468 					}
469 					Gtk.Requisition request = m.SizeRequest ();
470 					var screen = parent.Screen;
471 					Gdk.Rectangle geometry = GetUsableMonitorGeometry (screen, screen.GetMonitorAtPoint (x, y));
472 
473 					//whether to push or flip menus that would extend offscreen
474 					//FIXME: this is the correct behaviour for mac, check other platforms
475 					bool flip_left = true;
476 					bool flip_up   = false;
477 
478 					if (x + request.Width > geometry.X + geometry.Width) {
479 						if (flip_left) {
480 							x -= request.Width;
481 						} else {
482 							x = geometry.X + geometry.Width - request.Width;
483 						}
484 
485 						if (x < geometry.Left)
486 							x = geometry.Left;
487 					}
488 
489 					if (y + request.Height > geometry.Y + geometry.Height) {
490 						if (flip_up) {
491 							y -= request.Height;
492 						} else {
493 							y = geometry.Y + geometry.Height - request.Height;
494 						}
495 
496 						if (y < geometry.Top)
497 							y = geometry.Top;
498 					}
499 
500 					pushIn = false;
501 				};
502 			}
503 
504 			uint time;
505 			uint button;
506 
507 			if (evt == null) {
508 				time = Gtk.Global.CurrentEventTime;
509 				button = 0;
510 			} else {
511 				time = evt.Time;
512 				button = evt.Button;
513 			}
514 
515 			//HACK: work around GTK menu issues on mac when passing button to menu.Popup
516 			//some menus appear and immediately hide, and submenus don't activate
517 			if (Platform.IsMac) {
518 				button = 0;
519 			}
520 
521 			menu.Popup (null, null, posFunc, button, time);
522 		}
523 
ShowContextMenu(Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt)524 		public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt)
525 		{
526 			ShowContextMenu (menu, parent, evt, Gdk.Rectangle.Zero);
527 		}
528 
ShowContextMenu(Gtk.Menu menu, Gtk.Widget parent, Gdk.Rectangle caret)529 		public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.Rectangle caret)
530 		{
531 			ShowContextMenu (menu, parent, null, caret);
532 		}
533 
534 		struct MappedKeys
535 		{
536 			public Gdk.Key Key;
537 			public Gdk.ModifierType State;
538 			public KeyboardShortcut[] Shortcuts;
539 		}
540 
541 		//introduced in GTK 2.20
542 		[DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
gdk_keymap_add_virtual_modifiers(IntPtr keymap, ref Gdk.ModifierType state)543 		extern static bool gdk_keymap_add_virtual_modifiers (IntPtr keymap, ref Gdk.ModifierType state);
544 
545 		//Custom patch in Mono Mac w/GTK+ 2.24.8+
546 		[DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
gdk_quartz_set_fix_modifiers(bool fix)547 		extern static bool gdk_quartz_set_fix_modifiers (bool fix);
548 
549 		static Gdk.Keymap keymap = Gdk.Keymap.Default;
550 		static Dictionary<ulong,MappedKeys> mappedKeys = new Dictionary<ulong,MappedKeys> ();
551 
552 		/// <summary>Map raw GTK key input to work around platform bugs and decompose accelerator keys</summary>
553 		/// <param name='evt'>The raw key event</param>
554 		/// <param name='key'>The composed key</param>
555 		/// <param name='state'>The composed modifiers</param>
556 		/// <param name='shortcuts'>All the key/modifier decompositions that can be used as accelerators</param>
MapKeys(Gdk.EventKey evt, out Gdk.Key key, out Gdk.ModifierType state, out KeyboardShortcut[] shortcuts)557 		public static void MapKeys (Gdk.EventKey evt, out Gdk.Key key, out Gdk.ModifierType state,
558 		                            out KeyboardShortcut[] shortcuts)
559 		{
560 			//this uniquely identifies the raw key
561 			ulong id;
562 			unchecked {
563 				id = (((ulong)(uint)evt.State) | (((ulong)evt.HardwareKeycode) << 32) | (((ulong)evt.Group) << 48));
564 			}
565 
566 			bool remapKey = Platform.IsWindows && evt.HardwareKeycode == 0;
567 			MappedKeys mapped;
568 			if (remapKey || !mappedKeys.TryGetValue (id, out mapped))
569 				mappedKeys[id] = mapped = MapKeys (evt);
570 
571 			shortcuts = mapped.Shortcuts;
572 			state = mapped.State;
573 			key = mapped.Key;
574 
575 			if (remapKey) {
576 				key = (Gdk.Key)evt.KeyValue;
577 			}
578 		}
579 
MapKeys(Gdk.EventKey evt)580 		static MappedKeys MapKeys (Gdk.EventKey evt)
581 		{
582 			MappedKeys mapped;
583 			Gdk.ModifierType modifier = evt.State;
584 			byte grp = evt.Group;
585 
586 			if (GtkMinorVersion >= 20) {
587 				gdk_keymap_add_virtual_modifiers (keymap.Handle, ref modifier);
588 			}
589 
590 			//full key mapping
591 			uint keyval;
592 			int effectiveGroup, level;
593 			Gdk.ModifierType consumedModifiers;
594 			TranslateKeyboardState (evt, modifier, grp, out keyval, out effectiveGroup,
595 				out level, out consumedModifiers);
596 			mapped.Key = (Gdk.Key)keyval;
597 			mapped.State = FixMacModifiers (evt.State & ~consumedModifiers, grp);
598 
599 			//decompose the key into accel combinations
600 			var accelList = new List<KeyboardShortcut> ();
601 
602 			const Gdk.ModifierType accelMods = Gdk.ModifierType.ShiftMask | Gdk.ModifierType.Mod1Mask
603 				| Gdk.ModifierType.ControlMask | Gdk.ModifierType.SuperMask |Gdk.ModifierType.MetaMask;
604 
605 			//all accels ignore the lock key
606 			modifier &= ~Gdk.ModifierType.LockMask;
607 
608 			//fully decomposed
609 			TranslateKeyboardState (evt, Gdk.ModifierType.None, 0,
610 				out keyval, out effectiveGroup, out level, out consumedModifiers);
611 			accelList.Add (new KeyboardShortcut ((Gdk.Key)keyval, FixMacModifiers (modifier, grp) & accelMods));
612 
613 			//with shift composed
614 			if ((modifier & Gdk.ModifierType.ShiftMask) != 0) {
615 				keymap.TranslateKeyboardState (evt.HardwareKeycode, Gdk.ModifierType.ShiftMask, 0,
616 					out keyval, out effectiveGroup, out level, out consumedModifiers);
617 
618 				if (Platform.IsWindows && evt.HardwareKeycode == 0) {
619 					keyval = (ushort)evt.KeyValue;
620 				}
621 
622 				// Prevent consumption of non-Shift modifiers (that we didn't even provide!)
623 				consumedModifiers &= Gdk.ModifierType.ShiftMask;
624 
625 				var m = FixMacModifiers ((modifier & ~consumedModifiers), grp) & accelMods;
626 				AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
627 			}
628 
629 			//with group 1 composed
630 			if (grp == 1) {
631 				TranslateKeyboardState (evt, modifier & ~Gdk.ModifierType.ShiftMask, 1,
632 					out keyval, out effectiveGroup, out level, out consumedModifiers);
633 
634 				// Prevent consumption of Shift modifier (that we didn't even provide!)
635 				consumedModifiers &= ~Gdk.ModifierType.ShiftMask;
636 
637 				var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods;
638 				AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
639 			}
640 
641 			//with group 1 and shift composed
642 			if (grp == 1 && (modifier & Gdk.ModifierType.ShiftMask) != 0) {
643 				TranslateKeyboardState (evt, modifier, 1,
644 					out keyval, out effectiveGroup, out level, out consumedModifiers);
645 				var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods;
646 				AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
647 			}
648 
649 			//and also allow the fully mapped key as an accel
650 			AddIfNotDuplicate (accelList, new KeyboardShortcut (mapped.Key, mapped.State & accelMods));
651 
652 			mapped.Shortcuts = accelList.ToArray ();
653 			return mapped;
654 		}
655 
656 		// Workaround for bug "Bug 688247 - Ctrl+Alt key not work on windows7 with bootcamp on a Mac Book Pro"
657 		// Ctrl+Alt should behave like right alt key - unfortunately TranslateKeyboardState doesn't handle it.
TranslateKeyboardState(Gdk.EventKey evt, Gdk.ModifierType state, int group, out uint keyval, out int effective_group, out int level, out Gdk.ModifierType consumed_modifiers)658 		static void TranslateKeyboardState (Gdk.EventKey evt, Gdk.ModifierType state, int group, out uint keyval,
659 			out int effective_group, out int level, out Gdk.ModifierType consumed_modifiers)
660 		{
661 			uint hardware_keycode = evt.HardwareKeycode;
662 
663 			if (Platform.IsWindows) {
664 				const Gdk.ModifierType ctrlAlt = Gdk.ModifierType.ControlMask | Gdk.ModifierType.Mod1Mask;
665 				if ((state & ctrlAlt) == ctrlAlt) {
666 					state = (state & ~ctrlAlt) | Gdk.ModifierType.Mod2Mask;
667 					group = 1;
668 				}
669 				// Case: Caps lock on + shift + key
670 				// See: Bug 8069 - [UI Refresh] If caps lock is on, holding the shift key prevents typed characters from appearing
671 				if (state.HasFlag (Gdk.ModifierType.ShiftMask)) {
672 					state &= ~Gdk.ModifierType.ShiftMask;
673 				}
674 			}
675 
676 			keymap.TranslateKeyboardState (hardware_keycode, state, group, out keyval, out effective_group,
677 				out level, out consumed_modifiers);
678 
679 			if (Platform.IsWindows && hardware_keycode == 0) {
680 				keyval = evt.KeyValue;
681 			}
682 		}
683 
FixMacModifiers(Gdk.ModifierType mod, byte grp)684 		static Gdk.ModifierType FixMacModifiers (Gdk.ModifierType mod, byte grp)
685 		{
686 			if (!oldMacKeyHacks)
687 				return mod;
688 
689 			// Mac GTK+ maps the command key to the Mod1 modifier, which usually means alt/
690 			// We map this instead to meta, because the Mac GTK+ has mapped the cmd key
691 			// to the meta key (yay inconsistency!). IMO super would have been saner.
692 			if ((mod & Gdk.ModifierType.Mod1Mask) != 0) {
693 				mod ^= Gdk.ModifierType.Mod1Mask;
694 				mod |= Gdk.ModifierType.MetaMask;
695 			}
696 
697 			//some versions of GTK map opt as mod5, which converts to the virtual super modifier
698 			if ((mod & (Gdk.ModifierType.Mod5Mask | Gdk.ModifierType.SuperMask)) != 0) {
699 				mod ^= (Gdk.ModifierType.Mod5Mask | Gdk.ModifierType.SuperMask);
700 				mod |= Gdk.ModifierType.Mod1Mask;
701 			}
702 
703 			// When opt modifier is active, we need to decompose this to make the command appear correct for Mac.
704 			// In addition, we can only inspect whether the opt/alt key is pressed by examining
705 			// the key's "group", because the Mac GTK+ treats opt as a group modifier and does
706 			// not expose it as an actual GDK modifier.
707 			if (grp == (byte) 1) {
708 				mod |= Gdk.ModifierType.Mod1Mask;
709 			}
710 
711 			return mod;
712 		}
713 
714 		static void AddIfNotDuplicate<T> (List<T> list, T item) where T : IEquatable<T>
715 		{
716 			for (int i = 0; i < list.Count; i++) {
717 				if (list[i].Equals (item))
718 					return;
719 			}
720 			list.Add (item);
721 		}
722 
723 		[System.Runtime.InteropServices.DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
gdk_win32_drawable_get_handle(IntPtr drawable)724 		static extern IntPtr gdk_win32_drawable_get_handle (IntPtr drawable);
725 
726 		enum DwmWindowAttribute
727 		{
728 			NcRenderingEnabled = 1,
729 			NcRenderingPolicy,
730 			TransitionsForceDisabled,
731 			AllowNcPaint,
732 			CaptionButtonBounds,
733 			NonClientRtlLayout,
734 			ForceIconicRepresentation,
735 			Flip3DPolicy,
736 			ExtendedFrameBounds,
737 			HasIconicBitmap,
738 			DisallowPeek,
739 			ExcludedFromPeek,
740 			Last,
741 		}
742 
743 		struct Win32Rect
744 		{
745 			public int Left, Top, Right, Bottom;
746 
Win32RectPinta.Docking.GtkWorkarounds.Win32Rect747 			public Win32Rect (int left, int top, int right, int bottom)
748 			{
749 				this.Left = left;
750 				this.Top = top;
751 				this.Right = right;
752 				this.Bottom = bottom;
753 			}
754 		}
755 
756 		[DllImport ("dwmapi.dll")]
DwmGetWindowAttribute(IntPtr hwnd, DwmWindowAttribute attribute, out Win32Rect value, int valueSize)757 		static extern int DwmGetWindowAttribute (IntPtr hwnd, DwmWindowAttribute attribute, out Win32Rect value, int valueSize);
758 
759 		[DllImport ("dwmapi.dll")]
DwmIsCompositionEnabled(out bool enabled)760 		static extern int DwmIsCompositionEnabled (out bool enabled);
761 
762 		[DllImport (USER32DLL)]
GetWindowRect(IntPtr hwnd, out Win32Rect rect)763 		static extern bool GetWindowRect (IntPtr hwnd, out Win32Rect rect);
764 
SetImCursorLocation(Gtk.IMContext ctx, Gdk.Window clientWindow, Gdk.Rectangle cursor)765 		public static void SetImCursorLocation (Gtk.IMContext ctx, Gdk.Window clientWindow, Gdk.Rectangle cursor)
766 		{
767 			// work around GTK+ Bug 663096 - Windows IME position is wrong when Aero glass is enabled
768 			// https://bugzilla.gnome.org/show_bug.cgi?id=663096
769 			if (Platform.IsWindows && System.Environment.OSVersion.Version.Major >= 6) {
770 				bool enabled;
771 				if (DwmIsCompositionEnabled (out enabled) == 0 && enabled) {
772 					var hwnd = gdk_win32_drawable_get_handle (clientWindow.Toplevel.Handle);
773 					Win32Rect rect;
774 					// this module gets the WINVER=6 version of GetWindowRect, which returns the correct value
775 					if (GetWindowRect (hwnd, out rect)) {
776 						int x, y;
777 						clientWindow.Toplevel.GetPosition (out x, out y);
778 						cursor.X = cursor.X - x + rect.Left;
779 						cursor.Y = cursor.Y - y + rect.Top - cursor.Height;
780 					}
781 				}
782 			}
783 			ctx.CursorLocation = cursor;
784 		}
785 
786 		/// <summary>X coordinate of the pixels inside the right edge of the rectangle</summary>
787 		/// <remarks>Workaround for inconsistency of Right property between GTK# versions</remarks>
RightInside(this Gdk.Rectangle rect)788 		public static int RightInside (this Gdk.Rectangle rect)
789 		{
790 			return rect.X + rect.Width - 1;
791 		}
792 
793 		/// <summary>Y coordinate of the pixels inside the bottom edge of the rectangle</summary>
794 		/// <remarks>Workaround for inconsistency of Bottom property between GTK# versions#</remarks>
BottomInside(this Gdk.Rectangle rect)795 		public static int BottomInside (this Gdk.Rectangle rect)
796 		{
797 			return rect.Y + rect.Height - 1;
798 		}
799 
800 		/// <summary>
801 		/// Shows or hides the shadow of the window rendered by the native toolkit
802 		/// </summary>
ShowNativeShadow(Gtk.Window window, bool show)803 		public static void ShowNativeShadow (Gtk.Window window, bool show)
804 		{
805 			if (Platform.IsMac) {
806 				var ptr = gdk_quartz_window_get_nswindow (window.GdkWindow.Handle);
807 				objc_msgSend_void_bool (ptr, sel_setHasShadow, show);
808 			}
809 		}
810 
UpdateNativeShadow(Gtk.Window window)811 		public static void UpdateNativeShadow (Gtk.Window window)
812 		{
813 			if (!Platform.IsMac)
814 				return;
815 
816 			var ptr = gdk_quartz_window_get_nswindow (window.GdkWindow.Handle);
817 			objc_msgSend_IntPtr (ptr, sel_invalidateShadow);
818 		}
819 
HasNSTextFieldFocus(Gdk.Window window)820 		public static bool HasNSTextFieldFocus (Gdk.Window window)
821 		{
822 			if (Platform.IsMac) {
823 				try {
824 					return gdk_window_has_embedded_nsview_focus (window.Handle);
825 				} catch (Exception) {
826 					return false;
827 				}
828 			} else {
829 				return false;
830 			}
831 		}
832 
833 		[DllImport (PangoUtil.LIBGTKGLUE, CallingConvention = CallingConvention.Cdecl)]
gtksharp_container_leak_fixed_marker()834 		static extern void gtksharp_container_leak_fixed_marker ();
835 
836 		static HashSet<Type> fixedContainerTypes;
837 		static Dictionary<IntPtr,ForallDelegate> forallCallbacks;
838 		static bool containerLeakFixed;
839 
840 		// Works around BXC #3801 - Managed Container subclasses are incorrectly resurrected, then leak.
841 		// It does this by registering an alternative callback for gtksharp_container_override_forall, which
842 		// ignores callbacks if the wrapper no longer exists. This means that the objects no longer enter a
843 		// finalized->release->dispose->re-wrap resurrection cycle.
844 		// We use a dynamic method to access internal/private GTK# API in a performant way without having to track
845 		// per-instance delegates.
FixContainerLeak(Gtk.Container c)846 		public static void FixContainerLeak (Gtk.Container c)
847 		{
848 			if (containerLeakFixed) {
849 				return;
850 			}
851 
852 			FixContainerLeak (c.GetType ());
853 		}
854 
FixContainerLeak(Type t)855 		static void FixContainerLeak (Type t)
856 		{
857 			if (containerLeakFixed) {
858 				return;
859 			}
860 
861 			if (fixedContainerTypes == null) {
862 				try {
863 					gtksharp_container_leak_fixed_marker ();
864 					containerLeakFixed = true;
865 					return;
866 				} catch (EntryPointNotFoundException) {
867 				}
868 				fixedContainerTypes = new HashSet<Type>();
869 				forallCallbacks = new Dictionary<IntPtr, ForallDelegate> ();
870 			}
871 
872 			if (!fixedContainerTypes.Add (t)) {
873 				return;
874 			}
875 
876 			//need to fix the callback for the type and all the managed supertypes
877 			var lookupGType = typeof (GLib.Object).GetMethod ("LookupGType", BindingFlags.Static | BindingFlags.NonPublic);
878 			do {
879 				var gt = (GLib.GType) lookupGType.Invoke (null, new[] { t });
880 				var cb = CreateForallCallback (gt.Val);
881 				forallCallbacks[gt.Val] = cb;
882 				gtksharp_container_override_forall (gt.Val, cb);
883 				t = t.BaseType;
884 			} while (fixedContainerTypes.Add (t) && t.Assembly != typeof (Gtk.Container).Assembly);
885 		}
886 
CreateForallCallback(IntPtr gtype)887 		static ForallDelegate CreateForallCallback (IntPtr gtype)
888 		{
889 			var dm = new DynamicMethod (
890 				"ContainerForallCallback",
891 				typeof(void),
892 				new Type[] { typeof(IntPtr), typeof(bool), typeof(IntPtr), typeof(IntPtr) },
893 				typeof(GtkWorkarounds).Module,
894 				true);
895 
896 			var invokerType = typeof(Gtk.Container.CallbackInvoker);
897 
898 			//this was based on compiling a similar method and disassembling it
899 			ILGenerator il = dm.GetILGenerator ();
900 			var IL_002b = il.DefineLabel ();
901 			var IL_003f = il.DefineLabel ();
902 			var IL_0060 = il.DefineLabel ();
903 			var label_return = il.DefineLabel ();
904 
905 			var loc_container = il.DeclareLocal (typeof(Gtk.Container));
906 			var loc_obj = il.DeclareLocal (typeof(object));
907 			var loc_invoker = il.DeclareLocal (invokerType);
908 			var loc_ex = il.DeclareLocal (typeof(Exception));
909 
910 			//check that the type is an exact match
911 			// prevent stack overflow, because the callback on a more derived type will handle everything
912 			il.Emit (OpCodes.Ldarg_0);
913 			il.Emit (OpCodes.Call, typeof(GLib.ObjectManager).GetMethod ("gtksharp_get_type_id", BindingFlags.Static | BindingFlags.NonPublic));
914 
915 			il.Emit (OpCodes.Ldc_I8, gtype.ToInt64 ());
916 			il.Emit (OpCodes.Newobj, typeof (IntPtr).GetConstructor (new Type[] { typeof (Int64) }));
917 			il.Emit (OpCodes.Call, typeof (IntPtr).GetMethod ("op_Equality", BindingFlags.Static | BindingFlags.Public));
918 			il.Emit (OpCodes.Brfalse, label_return);
919 
920 			il.BeginExceptionBlock ();
921 			il.Emit (OpCodes.Ldnull);
922 			il.Emit (OpCodes.Stloc, loc_container);
923 			il.Emit (OpCodes.Ldsfld, typeof (GLib.Object).GetField ("Objects", BindingFlags.Static | BindingFlags.NonPublic));
924 			il.Emit (OpCodes.Ldarg_0);
925 			il.Emit (OpCodes.Box, typeof (IntPtr));
926 			il.Emit (OpCodes.Callvirt, typeof (System.Collections.Hashtable).GetProperty ("Item").GetGetMethod ());
927 			il.Emit (OpCodes.Stloc, loc_obj);
928 			il.Emit (OpCodes.Ldloc, loc_obj);
929 			il.Emit (OpCodes.Brfalse, IL_002b);
930 
931 			var tref = typeof (GLib.Object).Assembly.GetType ("GLib.ToggleRef");
932 			il.Emit (OpCodes.Ldloc, loc_obj);
933 			il.Emit (OpCodes.Castclass, tref);
934 			il.Emit (OpCodes.Callvirt, tref.GetProperty ("Target").GetGetMethod ());
935 			il.Emit (OpCodes.Isinst, typeof (Gtk.Container));
936 			il.Emit (OpCodes.Stloc, loc_container);
937 
938 			il.MarkLabel (IL_002b);
939 			il.Emit (OpCodes.Ldloc, loc_container);
940 			il.Emit (OpCodes.Brtrue, IL_003f);
941 
942 			il.Emit (OpCodes.Ldarg_0);
943 			il.Emit (OpCodes.Ldarg_1);
944 			il.Emit (OpCodes.Ldarg_2);
945 			il.Emit (OpCodes.Ldarg_3);
946 			il.Emit (OpCodes.Call, typeof (Gtk.Container).GetMethod ("gtksharp_container_base_forall", BindingFlags.Static | BindingFlags.NonPublic));
947 			il.Emit (OpCodes.Br, IL_0060);
948 
949 			il.MarkLabel (IL_003f);
950 			il.Emit (OpCodes.Ldloca_S, 2);
951 			il.Emit (OpCodes.Ldarg_2);
952 			il.Emit (OpCodes.Ldarg_3);
953 			il.Emit (OpCodes.Call, invokerType.GetConstructor (
954 				BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof (IntPtr), typeof (IntPtr) }, null));
955 			il.Emit (OpCodes.Ldloc, loc_container);
956 			il.Emit (OpCodes.Ldarg_1);
957 			il.Emit (OpCodes.Ldloc, loc_invoker);
958 			il.Emit (OpCodes.Box, invokerType);
959 			il.Emit (OpCodes.Ldftn, invokerType.GetMethod ("Invoke"));
960 			il.Emit (OpCodes.Newobj, typeof (Gtk.Callback).GetConstructor (
961 				BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof (object), typeof (IntPtr) }, null));
962 			var forallMeth = typeof (Gtk.Container).GetMethod ("ForAll",
963 				BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof (bool), typeof (Gtk.Callback) }, null);
964 			il.Emit (OpCodes.Callvirt, forallMeth);
965 
966 			il.MarkLabel (IL_0060);
967 
968 			il.BeginCatchBlock (typeof (Exception));
969 			il.Emit (OpCodes.Stloc, loc_ex);
970 			il.Emit (OpCodes.Ldloc, loc_ex);
971 			il.Emit (OpCodes.Ldc_I4_0);
972 			il.Emit (OpCodes.Call, typeof (GLib.ExceptionManager).GetMethod ("RaiseUnhandledException"));
973 			il.Emit (OpCodes.Leave, label_return);
974 			il.EndExceptionBlock ();
975 
976 			il.MarkLabel (label_return);
977 			il.Emit (OpCodes.Ret);
978 
979 			return (ForallDelegate) dm.CreateDelegate (typeof (ForallDelegate));
980 		}
981 
982 		[UnmanagedFunctionPointer (CallingConvention.Cdecl)]
ForallDelegate(IntPtr container, bool include_internals, IntPtr cb, IntPtr data)983 		delegate void ForallDelegate (IntPtr container, bool include_internals, IntPtr cb, IntPtr data);
984 
985 		[DllImport(PangoUtil.LIBGTKGLUE, CallingConvention = CallingConvention.Cdecl)]
gtksharp_container_override_forall(IntPtr gtype, ForallDelegate cb)986 		static extern void gtksharp_container_override_forall (IntPtr gtype, ForallDelegate cb);
987 
988         //public static string MarkupLinks (string text)
989         //{
990         //    if (GtkMinorVersion < 18)
991         //        return text;
992         //    return HighlightUrlSemanticRule.UrlRegex.Replace (text, MatchToUrl);
993         //}
994 
MatchToUrl(System.Text.RegularExpressions.Match m)995 		static string MatchToUrl (System.Text.RegularExpressions.Match m)
996 		{
997 			var s = m.ToString ();
998 			return String.Format ("<a href='{0}'>{1}</a>", s, s.Replace ("_", "__"));
999 		}
1000 
SetLinkHandler(this Gtk.Label label, Action<string> urlHandler)1001 		public static void SetLinkHandler (this Gtk.Label label, Action<string> urlHandler)
1002 		{
1003 			if (GtkMinorVersion >= 18)
1004 				new UrlHandlerClosure (urlHandler).ConnectTo (label);
1005 		}
1006 
1007 		//create closure manually so we can apply ConnectBefore
1008 		class UrlHandlerClosure
1009 		{
1010 			Action<string> urlHandler;
1011 
UrlHandlerClosure(Action<string> urlHandler)1012 			public UrlHandlerClosure (Action<string> urlHandler)
1013 			{
1014 				this.urlHandler = urlHandler;
1015 			}
1016 
1017 			[GLib.ConnectBefore]
HandleLink(object sender, ActivateLinkEventArgs args)1018 			void HandleLink (object sender, ActivateLinkEventArgs args)
1019 			{
1020 				urlHandler (args.Url);
1021 				args.RetVal = true;
1022 			}
1023 
ConnectTo(Gtk.Label label)1024 			public void ConnectTo (Gtk.Label label)
1025 			{
1026 				var signal = GLib.Signal.Lookup (label, "activate-link", typeof(ActivateLinkEventArgs));
1027 				signal.AddDelegate (new EventHandler<ActivateLinkEventArgs> (HandleLink));
1028 			}
1029 
1030 			class ActivateLinkEventArgs : GLib.SignalArgs
1031 			{
1032 				public string Url { get { return (string)base.Args [0]; } }
1033 			}
1034 		}
1035 
1036 		static bool canSetOverlayScrollbarPolicy = true;
1037 
1038 		[DllImport (PangoUtil.LIBQUARTZ)]
gtk_scrolled_window_set_overlay_policy(IntPtr sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy)1039 		static extern void gtk_scrolled_window_set_overlay_policy (IntPtr sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy);
1040 
1041 		[DllImport (PangoUtil.LIBQUARTZ)]
gtk_scrolled_window_get_overlay_policy(IntPtr sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy)1042 		static extern void gtk_scrolled_window_get_overlay_policy (IntPtr sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy);
1043 
SetOverlayScrollbarPolicy(Gtk.ScrolledWindow sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy)1044 		public static void SetOverlayScrollbarPolicy (Gtk.ScrolledWindow sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy)
1045 		{
1046 			if (!canSetOverlayScrollbarPolicy) {
1047 				return;
1048 			}
1049 			try {
1050 				gtk_scrolled_window_set_overlay_policy (sw.Handle, hpolicy, vpolicy);
1051 				return;
1052 			} catch (DllNotFoundException) {
1053 			} catch (EntryPointNotFoundException) {
1054 			}
1055 		}
1056 
GetOverlayScrollbarPolicy(Gtk.ScrolledWindow sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy)1057 		public static void GetOverlayScrollbarPolicy (Gtk.ScrolledWindow sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy)
1058 		{
1059 			if (!canSetOverlayScrollbarPolicy) {
1060 				hpolicy = vpolicy = 0;
1061 				return;
1062 			}
1063 			try {
1064 				gtk_scrolled_window_get_overlay_policy (sw.Handle, out hpolicy, out vpolicy);
1065 				return;
1066 			} catch (DllNotFoundException) {
1067 			} catch (EntryPointNotFoundException) {
1068 			}
1069 			hpolicy = vpolicy = 0;
1070 			canSetOverlayScrollbarPolicy = false;
1071 		}
1072 
1073 		[DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
gtk_tree_view_get_tooltip_context(IntPtr raw, ref int x, ref int y, bool keyboard_tip, out IntPtr model, out IntPtr path, IntPtr iter)1074 		static extern bool gtk_tree_view_get_tooltip_context (IntPtr raw, ref int x, ref int y, bool keyboard_tip, out IntPtr model, out IntPtr path, IntPtr iter);
1075 
1076 		//the GTK# version of this has 'out' instead of 'ref', preventing passing the x,y values in
GetTooltipContext(this TreeView tree, ref int x, ref int y, bool keyboardTip, out TreeModel model, out TreePath path, out Gtk.TreeIter iter)1077 		public static bool GetTooltipContext (this TreeView tree, ref int x, ref int y, bool keyboardTip,
1078 			 out TreeModel model, out TreePath path, out Gtk.TreeIter iter)
1079 		{
1080 			IntPtr intPtr = Marshal.AllocHGlobal (Marshal.SizeOf (typeof(TreeIter)));
1081 			IntPtr handle;
1082 			IntPtr intPtr2;
1083 			bool result = gtk_tree_view_get_tooltip_context (tree.Handle, ref x, ref y, keyboardTip, out handle, out intPtr2, intPtr);
1084 			model = TreeModelAdapter.GetObject (handle, false);
1085 			path = intPtr2 == IntPtr.Zero ? null : ((TreePath)GLib.Opaque.GetOpaque (intPtr2, typeof(TreePath), false));
1086 			iter = TreeIter.New (intPtr);
1087 			Marshal.FreeHGlobal (intPtr);
1088 			return result;
1089 		}
1090 
1091 		static bool supportsHiResIcons = true;
1092 
1093 		[DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
gtk_icon_source_set_scale(IntPtr source, double scale)1094 		static extern void gtk_icon_source_set_scale (IntPtr source, double scale);
1095 
1096 		[DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
gtk_icon_source_set_scale_wildcarded(IntPtr source, bool setting)1097 		static extern void gtk_icon_source_set_scale_wildcarded (IntPtr source, bool setting);
1098 
1099 		[DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
gtk_widget_get_scale_factor(IntPtr widget)1100 		static extern double gtk_widget_get_scale_factor (IntPtr widget);
1101 
1102 		[DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
gdk_screen_get_monitor_scale_factor(IntPtr widget, int monitor)1103 		static extern double gdk_screen_get_monitor_scale_factor (IntPtr widget, int monitor);
1104 
1105 		[DllImport (PangoUtil.LIBGOBJECT, CallingConvention = CallingConvention.Cdecl)]
g_object_get_data(IntPtr source, string name)1106 		static extern IntPtr g_object_get_data (IntPtr source, string name);
1107 
1108 		[DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
gtk_icon_set_render_icon_scaled(IntPtr handle, IntPtr style, int direction, int state, int size, IntPtr widget, IntPtr intPtr, ref double scale)1109 		static extern IntPtr gtk_icon_set_render_icon_scaled (IntPtr handle, IntPtr style, int direction, int state, int size, IntPtr widget, IntPtr intPtr, ref double scale);
1110 
SetSourceScale(Gtk.IconSource source, double scale)1111 		public static bool SetSourceScale (Gtk.IconSource source, double scale)
1112 		{
1113 			if (!supportsHiResIcons)
1114 				return false;
1115 
1116 			try {
1117 				gtk_icon_source_set_scale (source.Handle, scale);
1118 				return true;
1119 			} catch (DllNotFoundException) {
1120 			} catch (EntryPointNotFoundException) {
1121 			}
1122 			supportsHiResIcons = false;
1123 			return false;
1124 		}
1125 
SetSourceScaleWildcarded(Gtk.IconSource source, bool setting)1126 		public static bool SetSourceScaleWildcarded (Gtk.IconSource source, bool setting)
1127 		{
1128 			if (!supportsHiResIcons)
1129 				return false;
1130 
1131 			try {
1132 				gtk_icon_source_set_scale_wildcarded (source.Handle, setting);
1133 				return true;
1134 			} catch (DllNotFoundException) {
1135 			} catch (EntryPointNotFoundException) {
1136 			}
1137 			supportsHiResIcons = false;
1138 			return false;
1139 		}
1140 
Get2xVariant(Gdk.Pixbuf px)1141 		public static Gdk.Pixbuf Get2xVariant (Gdk.Pixbuf px)
1142 		{
1143 			if (!supportsHiResIcons)
1144 				return null;
1145 
1146 			try {
1147 				IntPtr res = g_object_get_data (px.Handle, "gdk-pixbuf-2x-variant");
1148 				if (res != IntPtr.Zero && res != px.Handle)
1149 					return (Gdk.Pixbuf) GLib.Object.GetObject (res);
1150 				else
1151 					return null;
1152 			} catch (DllNotFoundException) {
1153 			} catch (EntryPointNotFoundException) {
1154 			}
1155 			supportsHiResIcons = false;
1156 			return null;
1157 		}
1158 
Set2xVariant(Gdk.Pixbuf px, Gdk.Pixbuf variant2x)1159 		public static void Set2xVariant (Gdk.Pixbuf px, Gdk.Pixbuf variant2x)
1160 		{
1161 		}
1162 
GetScaleFactor(Gtk.Widget w)1163 		public static double GetScaleFactor (Gtk.Widget w)
1164 		{
1165 			if (!supportsHiResIcons)
1166 				return 1;
1167 
1168 			try {
1169 				return gtk_widget_get_scale_factor (w.Handle);
1170 			} catch (DllNotFoundException) {
1171 			} catch (EntryPointNotFoundException) {
1172 			}
1173 			supportsHiResIcons = false;
1174 			return 1;
1175 		}
1176 
GetScaleFactor(this Gdk.Screen screen, int monitor)1177 		public static double GetScaleFactor (this Gdk.Screen screen, int monitor)
1178 		{
1179 			if (!supportsHiResIcons)
1180 				return 1;
1181 
1182 			try {
1183 				return gdk_screen_get_monitor_scale_factor (screen.Handle, monitor);
1184 			} catch (DllNotFoundException) {
1185 			} catch (EntryPointNotFoundException) {
1186 			}
1187 			supportsHiResIcons = false;
1188 			return 1;
1189 		}
1190 
GetScaleFactor()1191 		public static double GetScaleFactor ()
1192 		{
1193 			return GetScaleFactor (Gdk.Screen.Default, 0);
1194 		}
1195 
GetPixelScale()1196 		public static double GetPixelScale ()
1197 		{
1198 			if (Platform.IsWindows)
1199 				return GetScaleFactor ();
1200 			else
1201 				return 1d;
1202 		}
1203 
RenderIcon(this Gtk.IconSet iconset, Gtk.Style style, Gtk.TextDirection direction, Gtk.StateType state, Gtk.IconSize size, Gtk.Widget widget, string detail, double scale)1204 		public static Gdk.Pixbuf RenderIcon (this Gtk.IconSet iconset, Gtk.Style style, Gtk.TextDirection direction, Gtk.StateType state, Gtk.IconSize size, Gtk.Widget widget, string detail, double scale)
1205 		{
1206 			if (scale == 1d)
1207 				return iconset.RenderIcon (style, direction, state, size, widget, detail);
1208 
1209 			if (!supportsHiResIcons)
1210 				return null;
1211 
1212 			try {
1213 				IntPtr intPtr = GLib.Marshaller.StringToPtrGStrdup (detail);
1214 				IntPtr o = gtk_icon_set_render_icon_scaled (iconset.Handle, (style != null) ? style.Handle : IntPtr.Zero, (int)direction, (int)state, (int)size, (widget != null) ? widget.Handle : IntPtr.Zero, intPtr, ref scale);
1215 				Gdk.Pixbuf result = (Gdk.Pixbuf) GLib.Object.GetObject (o);
1216 				GLib.Marshaller.Free (intPtr);
1217 				return result;
1218 			} catch (DllNotFoundException) {
1219 			} catch (EntryPointNotFoundException) {
1220 			}
1221 			supportsHiResIcons = false;
1222 			return null;
1223 		}
1224 	}
1225 
1226 	public struct KeyboardShortcut : IEquatable<KeyboardShortcut>
1227 	{
1228 		public static readonly KeyboardShortcut Empty = new KeyboardShortcut ((Gdk.Key) 0, (Gdk.ModifierType) 0);
1229 
1230 		Gdk.ModifierType modifier;
1231 		Gdk.Key key;
1232 
KeyboardShortcutPinta.Docking.KeyboardShortcut1233 		public KeyboardShortcut (Gdk.Key key, Gdk.ModifierType modifier)
1234 		{
1235 			this.modifier = modifier;
1236 			this.key = key;
1237 		}
1238 
1239 		public Gdk.Key Key {
1240 			get { return key; }
1241 		}
1242 
1243 		public Gdk.ModifierType Modifier {
1244 			get { return modifier; }
1245 		}
1246 
1247 		public bool IsEmpty {
1248 			get { return Key == (Gdk.Key) 0; }
1249 		}
1250 
EqualsPinta.Docking.KeyboardShortcut1251 		public override bool Equals (object obj)
1252 		{
1253 			return obj is KeyboardShortcut && this.Equals ((KeyboardShortcut) obj);
1254 		}
1255 
GetHashCodePinta.Docking.KeyboardShortcut1256 		public override int GetHashCode ()
1257 		{
1258 			//FIXME: we're only using a few bits of mod and mostly the lower bits of key - distribute it better
1259 			return (int) Key ^ (int) Modifier;
1260 		}
1261 
EqualsPinta.Docking.KeyboardShortcut1262 		public bool Equals (KeyboardShortcut other)
1263 		{
1264 			return other.Key == Key && other.Modifier == Modifier;
1265 		}
1266 	}
1267 }
1268