Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/mono/monodevelop.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs')
-rw-r--r--main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs1275
1 files changed, 1275 insertions, 0 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs
new file mode 100644
index 0000000000..398ae0e0e9
--- /dev/null
+++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components/GtkWorkarounds.cs
@@ -0,0 +1,1275 @@
+//
+// GtkWorkarounds.cs
+//
+// Authors: Jeffrey Stedfast <jeff@xamarin.com>
+//
+// Copyright (C) 2011 Xamarin Inc.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+//
+
+using System;
+using System.Drawing;
+using System.Runtime.InteropServices;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Reflection.Emit;
+using Gtk;
+using MonoDevelop.Core;
+using MonoDevelop.Ide.Editor.Highlighting;
+using System.Text.RegularExpressions;
+
+namespace MonoDevelop.Components
+{
+ public static class GtkWorkarounds
+ {
+ const string LIBOBJC ="/usr/lib/libobjc.dylib";
+ const string USER32DLL = "User32.dll";
+
+ [DllImport (LIBOBJC, EntryPoint = "sel_registerName")]
+ static extern IntPtr sel_registerName (string selector);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_getClass")]
+ static extern IntPtr objc_getClass (string klass);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
+ static extern IntPtr objc_msgSend_IntPtr (IntPtr klass, IntPtr selector);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
+ static extern void objc_msgSend_void_bool (IntPtr klass, IntPtr selector, bool arg);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
+ static extern bool objc_msgSend_bool (IntPtr klass, IntPtr selector);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
+ static extern int objc_msgSend_NSInt32_NSInt32 (IntPtr klass, IntPtr selector, int arg);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
+ static extern long objc_msgSend_NSInt64_NSInt64 (IntPtr klass, IntPtr selector, long arg);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
+ static extern uint objc_msgSend_NSUInt32 (IntPtr klass, IntPtr selector);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend")]
+ static extern ulong objc_msgSend_NSUInt64 (IntPtr klass, IntPtr selector);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend_stret")]
+ static extern void objc_msgSend_CGRect32 (out CGRect32 rect, IntPtr klass, IntPtr selector);
+
+ [DllImport (LIBOBJC, EntryPoint = "objc_msgSend_stret")]
+ static extern void objc_msgSend_CGRect64 (out CGRect64 rect, IntPtr klass, IntPtr selector);
+
+ [DllImport (PangoUtil.LIBQUARTZ)]
+ static extern IntPtr gdk_quartz_window_get_nswindow (IntPtr window);
+
+ [DllImport (PangoUtil.LIBQUARTZ)]
+ static extern bool gdk_window_has_embedded_nsview_focus (IntPtr window);
+
+ struct CGRect32
+ {
+ public float X, Y, Width, Height;
+ }
+
+ struct CGRect64
+ {
+ public double X, Y, Width, Height;
+
+ public CGRect64 (CGRect32 rect32)
+ {
+ X = rect32.X;
+ Y = rect32.Y;
+ Width = rect32.Width;
+ Height = rect32.Height;
+ }
+ }
+
+ static IntPtr cls_NSScreen;
+ static IntPtr sel_screens, sel_objectEnumerator, sel_nextObject, sel_frame, sel_visibleFrame,
+ sel_requestUserAttention, sel_setHasShadow, sel_invalidateShadow;
+ static IntPtr sharedApp;
+ static IntPtr cls_NSEvent;
+ static IntPtr sel_modifierFlags;
+
+ const int NSCriticalRequest = 0;
+ const int NSInformationalRequest = 10;
+
+ static System.Reflection.MethodInfo glibObjectGetProp, glibObjectSetProp;
+
+ public static int GtkMinorVersion = 12, GtkMicroVersion = 0;
+ static bool oldMacKeyHacks = false;
+
+ static GtkWorkarounds ()
+ {
+ if (Platform.IsMac) {
+ InitMac ();
+ }
+
+ var flags = System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic;
+ glibObjectSetProp = typeof (GLib.Object).GetMethod ("SetProperty", flags);
+ glibObjectGetProp = typeof (GLib.Object).GetMethod ("GetProperty", flags);
+
+ foreach (int i in new [] { 24, 22, 20, 18, 16, 14 }) {
+ if (Gtk.Global.CheckVersion (2, (uint)i, 0) == null) {
+ GtkMinorVersion = i;
+ break;
+ }
+ }
+
+ for (int i = 1; i < 20; i++) {
+ if (Gtk.Global.CheckVersion (2, (uint)GtkMinorVersion, (uint)i) == null) {
+ GtkMicroVersion = i;
+ } else {
+ break;
+ }
+ }
+
+ //opt into the fixes on GTK+ >= 2.24.8
+ if (Platform.IsMac) {
+ try {
+ gdk_quartz_set_fix_modifiers (true);
+ } catch (EntryPointNotFoundException) {
+ oldMacKeyHacks = true;
+ }
+ }
+
+ keymap.KeysChanged += delegate {
+ mappedKeys.Clear ();
+ };
+ }
+
+ static void InitMac ()
+ {
+ cls_NSScreen = objc_getClass ("NSScreen");
+ cls_NSEvent = objc_getClass ("NSEvent");
+ sel_screens = sel_registerName ("screens");
+ sel_objectEnumerator = sel_registerName ("objectEnumerator");
+ sel_nextObject = sel_registerName ("nextObject");
+ sel_visibleFrame = sel_registerName ("visibleFrame");
+ sel_frame = sel_registerName ("frame");
+ sel_requestUserAttention = sel_registerName ("requestUserAttention:");
+ sel_modifierFlags = sel_registerName ("modifierFlags");
+ sel_setHasShadow = sel_registerName ("setHasShadow:");
+ sel_invalidateShadow = sel_registerName ("invalidateShadow");
+ sharedApp = objc_msgSend_IntPtr (objc_getClass ("NSApplication"), sel_registerName ("sharedApplication"));
+ }
+
+ static Gdk.Rectangle MacGetUsableMonitorGeometry (Gdk.Screen screen, int monitor)
+ {
+ IntPtr array = objc_msgSend_IntPtr (cls_NSScreen, sel_screens);
+ IntPtr iter = objc_msgSend_IntPtr (array, sel_objectEnumerator);
+ Gdk.Rectangle ygeometry = screen.GetMonitorGeometry (monitor);
+ Gdk.Rectangle xgeometry = screen.GetMonitorGeometry (0);
+ IntPtr scrn;
+ int i = 0;
+
+ while ((scrn = objc_msgSend_IntPtr (iter, sel_nextObject)) != IntPtr.Zero && i < monitor)
+ i++;
+
+ if (scrn == IntPtr.Zero)
+ return screen.GetMonitorGeometry (monitor);
+
+ CGRect64 visible, frame;
+
+ if (IntPtr.Size == 8) {
+ objc_msgSend_CGRect64 (out visible, scrn, sel_visibleFrame);
+ objc_msgSend_CGRect64 (out frame, scrn, sel_frame);
+ } else {
+ CGRect32 visible32, frame32;
+ objc_msgSend_CGRect32 (out visible32, scrn, sel_visibleFrame);
+ objc_msgSend_CGRect32 (out frame32, scrn, sel_frame);
+ visible = new CGRect64 (visible32);
+ frame = new CGRect64 (frame32);
+ }
+
+ // Note: Frame and VisibleFrame rectangles are relative to monitor 0, but we need absolute
+ // coordinates.
+ visible.X += xgeometry.X;
+ frame.X += xgeometry.X;
+
+ // VisibleFrame.Y is the height of the Dock if it is at the bottom of the screen, so in order
+ // to get the menu height, we just figure out the difference between the visibleFrame height
+ // and the actual frame height, then subtract the Dock height.
+ //
+ // We need to swap the Y offset with the menu height because our callers expect the Y offset
+ // to be from the top of the screen, not from the bottom of the screen.
+ double x, y, width, height;
+
+ if (visible.Height < frame.Height) {
+ double dockHeight = visible.Y - frame.Y;
+ double menubarHeight = (frame.Height - visible.Height) - dockHeight;
+
+ height = frame.Height - menubarHeight - dockHeight;
+ y = ygeometry.Y + menubarHeight;
+ } else {
+ height = frame.Height;
+ y = ygeometry.Y;
+ }
+
+ // Takes care of the possibility of the Dock being positioned on the left or right edge of the screen.
+ width = System.Math.Min (visible.Width, frame.Width);
+ x = System.Math.Max (visible.X, frame.X);
+
+ return new Gdk.Rectangle ((int) x, (int) y, (int) width, (int) height);
+ }
+
+ static void MacRequestAttention (bool critical)
+ {
+ int kind = critical? NSCriticalRequest : NSInformationalRequest;
+ if (IntPtr.Size == 8) {
+ objc_msgSend_NSInt64_NSInt64 (sharedApp, sel_requestUserAttention, kind);
+ } else {
+ objc_msgSend_NSInt32_NSInt32 (sharedApp, sel_requestUserAttention, kind);
+ }
+ }
+
+ // Note: we can't reuse RectangleF because the layout is different...
+ [StructLayout (LayoutKind.Sequential)]
+ struct Rect {
+ public int Left;
+ public int Top;
+ public int Right;
+ public int Bottom;
+
+ public int X { get { return Left; } }
+ public int Y { get { return Top; } }
+ public int Width { get { return Right - Left; } }
+ public int Height { get { return Bottom - Top; } }
+ }
+
+ const int MonitorInfoFlagsPrimary = 0x01;
+
+ [StructLayout (LayoutKind.Sequential)]
+ unsafe struct MonitorInfo {
+ public int Size;
+ public Rect Frame; // Monitor
+ public Rect VisibleFrame; // Work
+ public int Flags;
+ public fixed byte Device[32];
+ }
+
+ [UnmanagedFunctionPointer (CallingConvention.Winapi)]
+ delegate int EnumMonitorsCallback (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data);
+
+ [DllImport (USER32DLL)]
+ extern static int EnumDisplayMonitors (IntPtr hdc, IntPtr clip, EnumMonitorsCallback callback, IntPtr user_data);
+
+ [DllImport (USER32DLL)]
+ extern static int GetMonitorInfoA (IntPtr hmonitor, ref MonitorInfo info);
+
+ static Gdk.Rectangle WindowsGetUsableMonitorGeometry (Gdk.Screen screen, int monitor_id)
+ {
+ Gdk.Rectangle geometry = screen.GetMonitorGeometry (monitor_id);
+ List<MonitorInfo> screens = new List<MonitorInfo> ();
+
+ EnumDisplayMonitors (IntPtr.Zero, IntPtr.Zero, delegate (IntPtr hmonitor, IntPtr hdc, IntPtr prect, IntPtr user_data) {
+ var info = new MonitorInfo ();
+
+ unsafe {
+ info.Size = sizeof (MonitorInfo);
+ }
+
+ GetMonitorInfoA (hmonitor, ref info);
+
+ // In order to keep the order the same as Gtk, we need to put the primary monitor at the beginning.
+ if ((info.Flags & MonitorInfoFlagsPrimary) != 0)
+ screens.Insert (0, info);
+ else
+ screens.Add (info);
+
+ return 1;
+ }, IntPtr.Zero);
+
+ MonitorInfo monitor = screens[monitor_id];
+ Rect visible = monitor.VisibleFrame;
+ Rect frame = monitor.Frame;
+
+ // Rebase the VisibleFrame off of Gtk's idea of this monitor's geometry (since they use different coordinate systems)
+ int x = geometry.X + (visible.Left - frame.Left);
+ int width = visible.Width;
+
+ int y = geometry.Y + (visible.Top - frame.Top);
+ int height = visible.Height;
+
+ return new Gdk.Rectangle (x, y, width, height);
+ }
+
+ public static Gdk.Rectangle GetUsableMonitorGeometry (this Gdk.Screen screen, int monitor)
+ {
+ if (Platform.IsWindows)
+ return WindowsGetUsableMonitorGeometry (screen, monitor);
+
+ if (Platform.IsMac)
+ return MacGetUsableMonitorGeometry (screen, monitor);
+
+ return screen.GetMonitorGeometry (monitor);
+ }
+
+ public static int RunDialogWithNotification (Gtk.Dialog dialog)
+ {
+ if (Platform.IsMac)
+ MacRequestAttention (dialog.Modal);
+
+ return dialog.Run ();
+ }
+
+ public static void PresentWindowWithNotification (this Gtk.Window window)
+ {
+ window.Present ();
+
+ if (Platform.IsMac) {
+ var dialog = window as Gtk.Dialog;
+ MacRequestAttention (dialog == null? false : dialog.Modal);
+ }
+ }
+
+ public static GLib.Value GetProperty (this GLib.Object obj, string name)
+ {
+ return (GLib.Value) glibObjectGetProp.Invoke (obj, new object[] { name });
+ }
+
+ public static void SetProperty (this GLib.Object obj, string name, GLib.Value value)
+ {
+ glibObjectSetProp.Invoke (obj, new object[] { name, value });
+ }
+
+ public static bool TriggersContextMenu (this Gdk.EventButton evt)
+ {
+ return evt.Type == Gdk.EventType.ButtonPress && IsContextMenuButton (evt);
+ }
+
+ public static bool IsContextMenuButton (this Gdk.EventButton evt)
+ {
+ if (evt.Button == 3 &&
+ (evt.State & (Gdk.ModifierType.Button1Mask | Gdk.ModifierType.Button2Mask)) == 0)
+ return true;
+
+ if (Platform.IsMac) {
+ if (!oldMacKeyHacks &&
+ evt.Button == 1 &&
+ (evt.State & Gdk.ModifierType.ControlMask) != 0 &&
+ (evt.State & (Gdk.ModifierType.Button2Mask | Gdk.ModifierType.Button3Mask)) == 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public static Gdk.ModifierType GetCurrentKeyModifiers ()
+ {
+ if (Platform.IsMac) {
+ Gdk.ModifierType mtype = Gdk.ModifierType.None;
+ ulong mod;
+ if (IntPtr.Size == 8) {
+ mod = objc_msgSend_NSUInt64 (cls_NSEvent, sel_modifierFlags);
+ } else {
+ mod = objc_msgSend_NSUInt32 (cls_NSEvent, sel_modifierFlags);
+ }
+ if ((mod & (1 << 17)) != 0)
+ mtype |= Gdk.ModifierType.ShiftMask;
+ if ((mod & (1 << 18)) != 0)
+ mtype |= Gdk.ModifierType.ControlMask;
+ if ((mod & (1 << 19)) != 0)
+ mtype |= Gdk.ModifierType.Mod1Mask; // Alt key
+ if ((mod & (1 << 20)) != 0)
+ mtype |= Gdk.ModifierType.Mod2Mask; // Command key
+ return mtype;
+ }
+ else {
+ Gdk.ModifierType mtype;
+ Gtk.Global.GetCurrentEventState (out mtype);
+ return mtype;
+ }
+ }
+
+ public static void GetPageScrollPixelDeltas (this Gdk.EventScroll evt, double pageSizeX, double pageSizeY,
+ out double deltaX, out double deltaY)
+ {
+ if (!GetEventScrollDeltas (evt, out deltaX, out deltaY)) {
+ var direction = evt.Direction;
+ deltaX = deltaY = 0;
+ if (pageSizeY != 0 && (direction == Gdk.ScrollDirection.Down || direction == Gdk.ScrollDirection.Up)) {
+ deltaY = System.Math.Pow (pageSizeY, 2.0 / 3.0);
+ deltaX = 0.0;
+ if (direction == Gdk.ScrollDirection.Up)
+ deltaY = -deltaY;
+ } else if (pageSizeX != 0) {
+ deltaX = System.Math.Pow (pageSizeX, 2.0 / 3.0);
+ deltaY = 0.0;
+ if (direction == Gdk.ScrollDirection.Left)
+ deltaX = -deltaX;
+ }
+ }
+ }
+
+ public static void AddValueClamped (this Gtk.Adjustment adj, double value)
+ {
+ adj.Value = System.Math.Max (adj.Lower, System.Math.Min (adj.Value + value, adj.Upper - adj.PageSize));
+ }
+
+ [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
+ extern static bool gdk_event_get_scroll_deltas (IntPtr eventScroll, out double deltaX, out double deltaY);
+ static bool scrollDeltasNotSupported;
+
+ public static bool GetEventScrollDeltas (Gdk.EventScroll evt, out double deltaX, out double deltaY)
+ {
+ if (!scrollDeltasNotSupported) {
+ try {
+ return gdk_event_get_scroll_deltas (evt.Handle, out deltaX, out deltaY);
+ } catch (EntryPointNotFoundException) {
+ scrollDeltasNotSupported = true;
+ }
+ }
+ deltaX = deltaY = 0;
+ return false;
+ }
+
+ /// <summary>Shows a context menu.</summary>
+ /// <param name='menu'>The menu.</param>
+ /// <param name='parent'>The parent widget.</param>
+ /// <param name='evt'>The mouse event. May be null if triggered by keyboard.</param>
+ /// <param name='caret'>The caret/selection position within the parent, if the EventButton is null.</param>
+ public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt, Gdk.Rectangle caret)
+ {
+ Gtk.MenuPositionFunc posFunc = null;
+
+ if (parent != null) {
+ menu.AttachToWidget (parent, null);
+ menu.Hidden += (sender, e) => {
+ if (menu.AttachWidget != null)
+ menu.Detach ();
+ };
+ posFunc = delegate (Gtk.Menu m, out int x, out int y, out bool pushIn) {
+ Gdk.Window window = evt != null? evt.Window : parent.GdkWindow;
+ window.GetOrigin (out x, out y);
+ var alloc = parent.Allocation;
+ if (evt != null) {
+ x += (int) evt.X;
+ y += (int) evt.Y;
+ } else if (caret.X >= alloc.X && caret.Y >= alloc.Y) {
+ x += caret.X;
+ y += caret.Y + caret.Height;
+ } else {
+ x += alloc.X;
+ y += alloc.Y;
+ }
+ Gtk.Requisition request = m.SizeRequest ();
+ var screen = parent.Screen;
+ Gdk.Rectangle geometry = GetUsableMonitorGeometry (screen, screen.GetMonitorAtPoint (x, y));
+
+ //whether to push or flip menus that would extend offscreen
+ //FIXME: this is the correct behaviour for mac, check other platforms
+ bool flip_left = true;
+ bool flip_up = false;
+
+ if (x + request.Width > geometry.X + geometry.Width) {
+ if (flip_left) {
+ x -= request.Width;
+ } else {
+ x = geometry.X + geometry.Width - request.Width;
+ }
+
+ if (x < geometry.Left)
+ x = geometry.Left;
+ }
+
+ if (y + request.Height > geometry.Y + geometry.Height) {
+ if (flip_up) {
+ y -= request.Height;
+ } else {
+ y = geometry.Y + geometry.Height - request.Height;
+ }
+
+ if (y < geometry.Top)
+ y = geometry.Top;
+ }
+
+ pushIn = false;
+ };
+ }
+
+ uint time;
+ uint button;
+
+ if (evt == null) {
+ time = Gtk.Global.CurrentEventTime;
+ button = 0;
+ } else {
+ time = evt.Time;
+ button = evt.Button;
+ }
+
+ //HACK: work around GTK menu issues on mac when passing button to menu.Popup
+ //some menus appear and immediately hide, and submenus don't activate
+ if (Platform.IsMac) {
+ button = 0;
+ }
+
+ menu.Popup (null, null, posFunc, button, time);
+ }
+
+ public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.EventButton evt)
+ {
+ ShowContextMenu (menu, parent, evt, Gdk.Rectangle.Zero);
+ }
+
+ public static void ShowContextMenu (Gtk.Menu menu, Gtk.Widget parent, Gdk.Rectangle caret)
+ {
+ ShowContextMenu (menu, parent, null, caret);
+ }
+
+ struct MappedKeys
+ {
+ public Gdk.Key Key;
+ public Gdk.ModifierType State;
+ public KeyboardShortcut[] Shortcuts;
+ }
+
+ //introduced in GTK 2.20
+ [DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
+ extern static bool gdk_keymap_add_virtual_modifiers (IntPtr keymap, ref Gdk.ModifierType state);
+
+ //Custom patch in Mono Mac w/GTK+ 2.24.8+
+ [DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
+ extern static bool gdk_quartz_set_fix_modifiers (bool fix);
+
+ static Gdk.Keymap keymap = Gdk.Keymap.Default;
+ static Dictionary<ulong,MappedKeys> mappedKeys = new Dictionary<ulong,MappedKeys> ();
+
+ /// <summary>Map raw GTK key input to work around platform bugs and decompose accelerator keys</summary>
+ /// <param name='evt'>The raw key event</param>
+ /// <param name='key'>The composed key</param>
+ /// <param name='state'>The composed modifiers</param>
+ /// <param name='shortcuts'>All the key/modifier decompositions that can be used as accelerators</param>
+ public static void MapKeys (Gdk.EventKey evt, out Gdk.Key key, out Gdk.ModifierType state,
+ out KeyboardShortcut[] shortcuts)
+ {
+ //this uniquely identifies the raw key
+ ulong id;
+ unchecked {
+ id = (((ulong)(uint)evt.State) | (((ulong)evt.HardwareKeycode) << 32) | (((ulong)evt.Group) << 48));
+ }
+
+ bool remapKey = Platform.IsWindows && evt.HardwareKeycode == 0;
+ MappedKeys mapped;
+ if (remapKey || !mappedKeys.TryGetValue (id, out mapped))
+ mappedKeys[id] = mapped = MapKeys (evt);
+
+ shortcuts = mapped.Shortcuts;
+ state = mapped.State;
+ key = mapped.Key;
+
+ if (remapKey) {
+ key = (Gdk.Key)evt.KeyValue;
+ }
+ }
+
+ static MappedKeys MapKeys (Gdk.EventKey evt)
+ {
+ MappedKeys mapped;
+ Gdk.ModifierType modifier = evt.State;
+ byte grp = evt.Group;
+
+ if (GtkMinorVersion >= 20) {
+ gdk_keymap_add_virtual_modifiers (keymap.Handle, ref modifier);
+ }
+
+ //full key mapping
+ uint keyval;
+ int effectiveGroup, level;
+ Gdk.ModifierType consumedModifiers;
+ TranslateKeyboardState (evt, modifier, grp, out keyval, out effectiveGroup,
+ out level, out consumedModifiers);
+ mapped.Key = (Gdk.Key)keyval;
+ mapped.State = FixMacModifiers (evt.State & ~consumedModifiers, grp);
+
+ //decompose the key into accel combinations
+ var accelList = new List<KeyboardShortcut> ();
+
+ const Gdk.ModifierType accelMods = Gdk.ModifierType.ShiftMask | Gdk.ModifierType.Mod1Mask
+ | Gdk.ModifierType.ControlMask | Gdk.ModifierType.SuperMask |Gdk.ModifierType.MetaMask;
+
+ //all accels ignore the lock key
+ modifier &= ~Gdk.ModifierType.LockMask;
+
+ //fully decomposed
+ TranslateKeyboardState (evt, Gdk.ModifierType.None, 0,
+ out keyval, out effectiveGroup, out level, out consumedModifiers);
+ accelList.Add (new KeyboardShortcut ((Gdk.Key)keyval, FixMacModifiers (modifier, grp) & accelMods));
+
+ //with shift composed
+ if ((modifier & Gdk.ModifierType.ShiftMask) != 0) {
+ keymap.TranslateKeyboardState (evt.HardwareKeycode, Gdk.ModifierType.ShiftMask, 0,
+ out keyval, out effectiveGroup, out level, out consumedModifiers);
+
+ if (Platform.IsWindows && evt.HardwareKeycode == 0) {
+ keyval = (ushort)evt.KeyValue;
+ }
+
+ // Prevent consumption of non-Shift modifiers (that we didn't even provide!)
+ consumedModifiers &= Gdk.ModifierType.ShiftMask;
+
+ var m = FixMacModifiers ((modifier & ~consumedModifiers), grp) & accelMods;
+ AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
+ }
+
+ //with group 1 composed
+ if (grp == 1) {
+ TranslateKeyboardState (evt, modifier & ~Gdk.ModifierType.ShiftMask, 1,
+ out keyval, out effectiveGroup, out level, out consumedModifiers);
+
+ // Prevent consumption of Shift modifier (that we didn't even provide!)
+ consumedModifiers &= ~Gdk.ModifierType.ShiftMask;
+
+ var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods;
+ AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
+ }
+
+ //with group 1 and shift composed
+ if (grp == 1 && (modifier & Gdk.ModifierType.ShiftMask) != 0) {
+ TranslateKeyboardState (evt, modifier, 1,
+ out keyval, out effectiveGroup, out level, out consumedModifiers);
+ var m = FixMacModifiers ((modifier & ~consumedModifiers), 0) & accelMods;
+ AddIfNotDuplicate (accelList, new KeyboardShortcut ((Gdk.Key)keyval, m));
+ }
+
+ //and also allow the fully mapped key as an accel
+ AddIfNotDuplicate (accelList, new KeyboardShortcut (mapped.Key, mapped.State & accelMods));
+
+ mapped.Shortcuts = accelList.ToArray ();
+ return mapped;
+ }
+
+ // Workaround for bug "Bug 688247 - Ctrl+Alt key not work on windows7 with bootcamp on a Mac Book Pro"
+ // Ctrl+Alt should behave like right alt key - unfortunately TranslateKeyboardState doesn't handle it.
+ static void TranslateKeyboardState (Gdk.EventKey evt, Gdk.ModifierType state, int group, out uint keyval,
+ out int effective_group, out int level, out Gdk.ModifierType consumed_modifiers)
+ {
+ uint hardware_keycode = evt.HardwareKeycode;
+
+ if (Platform.IsWindows) {
+ const Gdk.ModifierType ctrlAlt = Gdk.ModifierType.ControlMask | Gdk.ModifierType.Mod1Mask;
+ if ((state & ctrlAlt) == ctrlAlt) {
+ state = (state & ~ctrlAlt) | Gdk.ModifierType.Mod2Mask;
+ group = 1;
+ }
+ // Case: Caps lock on + shift + key
+ // See: Bug 8069 - [UI Refresh] If caps lock is on, holding the shift key prevents typed characters from appearing
+ if (state.HasFlag (Gdk.ModifierType.ShiftMask)) {
+ state &= ~Gdk.ModifierType.ShiftMask;
+ }
+ }
+
+ keymap.TranslateKeyboardState (hardware_keycode, state, group, out keyval, out effective_group,
+ out level, out consumed_modifiers);
+
+ if (Platform.IsWindows && hardware_keycode == 0) {
+ keyval = evt.KeyValue;
+ }
+ }
+
+ static Gdk.ModifierType FixMacModifiers (Gdk.ModifierType mod, byte grp)
+ {
+ if (!oldMacKeyHacks)
+ return mod;
+
+ // Mac GTK+ maps the command key to the Mod1 modifier, which usually means alt/
+ // We map this instead to meta, because the Mac GTK+ has mapped the cmd key
+ // to the meta key (yay inconsistency!). IMO super would have been saner.
+ if ((mod & Gdk.ModifierType.Mod1Mask) != 0) {
+ mod ^= Gdk.ModifierType.Mod1Mask;
+ mod |= Gdk.ModifierType.MetaMask;
+ }
+
+ //some versions of GTK map opt as mod5, which converts to the virtual super modifier
+ if ((mod & (Gdk.ModifierType.Mod5Mask | Gdk.ModifierType.SuperMask)) != 0) {
+ mod ^= (Gdk.ModifierType.Mod5Mask | Gdk.ModifierType.SuperMask);
+ mod |= Gdk.ModifierType.Mod1Mask;
+ }
+
+ // When opt modifier is active, we need to decompose this to make the command appear correct for Mac.
+ // In addition, we can only inspect whether the opt/alt key is pressed by examining
+ // the key's "group", because the Mac GTK+ treats opt as a group modifier and does
+ // not expose it as an actual GDK modifier.
+ if (grp == (byte) 1) {
+ mod |= Gdk.ModifierType.Mod1Mask;
+ }
+
+ return mod;
+ }
+
+ static void AddIfNotDuplicate<T> (List<T> list, T item) where T : IEquatable<T>
+ {
+ for (int i = 0; i < list.Count; i++) {
+ if (list[i].Equals (item))
+ return;
+ }
+ list.Add (item);
+ }
+
+ [System.Runtime.InteropServices.DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
+ static extern IntPtr gdk_win32_drawable_get_handle (IntPtr drawable);
+
+ enum DwmWindowAttribute
+ {
+ NcRenderingEnabled = 1,
+ NcRenderingPolicy,
+ TransitionsForceDisabled,
+ AllowNcPaint,
+ CaptionButtonBounds,
+ NonClientRtlLayout,
+ ForceIconicRepresentation,
+ Flip3DPolicy,
+ ExtendedFrameBounds,
+ HasIconicBitmap,
+ DisallowPeek,
+ ExcludedFromPeek,
+ Last,
+ }
+
+ struct Win32Rect
+ {
+ public int Left, Top, Right, Bottom;
+
+ public Win32Rect (int left, int top, int right, int bottom)
+ {
+ this.Left = left;
+ this.Top = top;
+ this.Right = right;
+ this.Bottom = bottom;
+ }
+ }
+
+ [DllImport ("dwmapi.dll")]
+ static extern int DwmGetWindowAttribute (IntPtr hwnd, DwmWindowAttribute attribute, out Win32Rect value, int valueSize);
+
+ [DllImport ("dwmapi.dll")]
+ static extern int DwmIsCompositionEnabled (out bool enabled);
+
+ [DllImport (USER32DLL)]
+ static extern bool GetWindowRect (IntPtr hwnd, out Win32Rect rect);
+
+ public static void SetImCursorLocation (Gtk.IMContext ctx, Gdk.Window clientWindow, Gdk.Rectangle cursor)
+ {
+ // work around GTK+ Bug 663096 - Windows IME position is wrong when Aero glass is enabled
+ // https://bugzilla.gnome.org/show_bug.cgi?id=663096
+ if (Platform.IsWindows && System.Environment.OSVersion.Version.Major >= 6) {
+ bool enabled;
+ if (DwmIsCompositionEnabled (out enabled) == 0 && enabled) {
+ var hwnd = gdk_win32_drawable_get_handle (clientWindow.Toplevel.Handle);
+ Win32Rect rect;
+ // this module gets the WINVER=6 version of GetWindowRect, which returns the correct value
+ if (GetWindowRect (hwnd, out rect)) {
+ int x, y;
+ clientWindow.Toplevel.GetPosition (out x, out y);
+ cursor.X = cursor.X - x + rect.Left;
+ cursor.Y = cursor.Y - y + rect.Top - cursor.Height;
+ }
+ }
+ }
+ ctx.CursorLocation = cursor;
+ }
+
+ /// <summary>X coordinate of the pixels inside the right edge of the rectangle</summary>
+ /// <remarks>Workaround for inconsistency of Right property between GTK# versions</remarks>
+ public static int RightInside (this Gdk.Rectangle rect)
+ {
+ return rect.X + rect.Width - 1;
+ }
+
+ /// <summary>Y coordinate of the pixels inside the bottom edge of the rectangle</summary>
+ /// <remarks>Workaround for inconsistency of Bottom property between GTK# versions#</remarks>
+ public static int BottomInside (this Gdk.Rectangle rect)
+ {
+ return rect.Y + rect.Height - 1;
+ }
+
+ /// <summary>
+ /// Shows or hides the shadow of the window rendered by the native toolkit
+ /// </summary>
+ public static void ShowNativeShadow (Gtk.Window window, bool show)
+ {
+ if (Platform.IsMac) {
+ var ptr = gdk_quartz_window_get_nswindow (window.GdkWindow.Handle);
+ objc_msgSend_void_bool (ptr, sel_setHasShadow, show);
+ }
+ }
+
+ public static void UpdateNativeShadow (Gtk.Window window)
+ {
+ if (!Platform.IsMac)
+ return;
+
+ var ptr = gdk_quartz_window_get_nswindow (window.GdkWindow.Handle);
+ objc_msgSend_IntPtr (ptr, sel_invalidateShadow);
+ }
+
+ public static bool HasNSTextFieldFocus (Gdk.Window window)
+ {
+ if (Platform.IsMac) {
+ try {
+ return gdk_window_has_embedded_nsview_focus (window.Handle);
+ } catch (Exception e) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ [DllImport (PangoUtil.LIBGTKGLUE, CallingConvention = CallingConvention.Cdecl)]
+ static extern void gtksharp_container_leak_fixed_marker ();
+
+ static HashSet<Type> fixedContainerTypes;
+ static Dictionary<IntPtr,ForallDelegate> forallCallbacks;
+ static bool containerLeakFixed;
+
+ // Works around BXC #3801 - Managed Container subclasses are incorrectly resurrected, then leak.
+ // It does this by registering an alternative callback for gtksharp_container_override_forall, which
+ // ignores callbacks if the wrapper no longer exists. This means that the objects no longer enter a
+ // finalized->release->dispose->re-wrap resurrection cycle.
+ // We use a dynamic method to access internal/private GTK# API in a performant way without having to track
+ // per-instance delegates.
+ public static void FixContainerLeak (Gtk.Container c)
+ {
+ if (containerLeakFixed) {
+ return;
+ }
+
+ FixContainerLeak (c.GetType ());
+ }
+
+ static void FixContainerLeak (Type t)
+ {
+ if (containerLeakFixed) {
+ return;
+ }
+
+ if (fixedContainerTypes == null) {
+ try {
+ gtksharp_container_leak_fixed_marker ();
+ containerLeakFixed = true;
+ return;
+ } catch (EntryPointNotFoundException) {
+ }
+ fixedContainerTypes = new HashSet<Type>();
+ forallCallbacks = new Dictionary<IntPtr, ForallDelegate> ();
+ }
+
+ if (!fixedContainerTypes.Add (t)) {
+ return;
+ }
+
+ //need to fix the callback for the type and all the managed supertypes
+ var lookupGType = typeof (GLib.Object).GetMethod ("LookupGType", BindingFlags.Static | BindingFlags.NonPublic);
+ do {
+ var gt = (GLib.GType) lookupGType.Invoke (null, new[] { t });
+ var cb = CreateForallCallback (gt.Val);
+ forallCallbacks[gt.Val] = cb;
+ gtksharp_container_override_forall (gt.Val, cb);
+ t = t.BaseType;
+ } while (fixedContainerTypes.Add (t) && t.Assembly != typeof (Gtk.Container).Assembly);
+ }
+
+ static ForallDelegate CreateForallCallback (IntPtr gtype)
+ {
+ var dm = new DynamicMethod (
+ "ContainerForallCallback",
+ typeof(void),
+ new Type[] { typeof(IntPtr), typeof(bool), typeof(IntPtr), typeof(IntPtr) },
+ typeof(GtkWorkarounds).Module,
+ true);
+
+ var invokerType = typeof(Gtk.Container.CallbackInvoker);
+
+ //this was based on compiling a similar method and disassembling it
+ ILGenerator il = dm.GetILGenerator ();
+ var IL_002b = il.DefineLabel ();
+ var IL_003f = il.DefineLabel ();
+ var IL_0060 = il.DefineLabel ();
+ var label_return = il.DefineLabel ();
+
+ var loc_container = il.DeclareLocal (typeof(Gtk.Container));
+ var loc_obj = il.DeclareLocal (typeof(object));
+ var loc_invoker = il.DeclareLocal (invokerType);
+ var loc_ex = il.DeclareLocal (typeof(Exception));
+
+ //check that the type is an exact match
+ // prevent stack overflow, because the callback on a more derived type will handle everything
+ il.Emit (OpCodes.Ldarg_0);
+ il.Emit (OpCodes.Call, typeof(GLib.ObjectManager).GetMethod ("gtksharp_get_type_id", BindingFlags.Static | BindingFlags.NonPublic));
+
+ il.Emit (OpCodes.Ldc_I8, gtype.ToInt64 ());
+ il.Emit (OpCodes.Newobj, typeof (IntPtr).GetConstructor (new Type[] { typeof (Int64) }));
+ il.Emit (OpCodes.Call, typeof (IntPtr).GetMethod ("op_Equality", BindingFlags.Static | BindingFlags.Public));
+ il.Emit (OpCodes.Brfalse, label_return);
+
+ il.BeginExceptionBlock ();
+ il.Emit (OpCodes.Ldnull);
+ il.Emit (OpCodes.Stloc, loc_container);
+ il.Emit (OpCodes.Ldsfld, typeof (GLib.Object).GetField ("Objects", BindingFlags.Static | BindingFlags.NonPublic));
+ il.Emit (OpCodes.Ldarg_0);
+ il.Emit (OpCodes.Box, typeof (IntPtr));
+ il.Emit (OpCodes.Callvirt, typeof (System.Collections.Hashtable).GetProperty ("Item").GetGetMethod ());
+ il.Emit (OpCodes.Stloc, loc_obj);
+ il.Emit (OpCodes.Ldloc, loc_obj);
+ il.Emit (OpCodes.Brfalse, IL_002b);
+
+ var tref = typeof (GLib.Object).Assembly.GetType ("GLib.ToggleRef");
+ il.Emit (OpCodes.Ldloc, loc_obj);
+ il.Emit (OpCodes.Castclass, tref);
+ il.Emit (OpCodes.Callvirt, tref.GetProperty ("Target").GetGetMethod ());
+ il.Emit (OpCodes.Isinst, typeof (Gtk.Container));
+ il.Emit (OpCodes.Stloc, loc_container);
+
+ il.MarkLabel (IL_002b);
+ il.Emit (OpCodes.Ldloc, loc_container);
+ il.Emit (OpCodes.Brtrue, IL_003f);
+
+ il.Emit (OpCodes.Ldarg_0);
+ il.Emit (OpCodes.Ldarg_1);
+ il.Emit (OpCodes.Ldarg_2);
+ il.Emit (OpCodes.Ldarg_3);
+ il.Emit (OpCodes.Call, typeof (Gtk.Container).GetMethod ("gtksharp_container_base_forall", BindingFlags.Static | BindingFlags.NonPublic));
+ il.Emit (OpCodes.Br, IL_0060);
+
+ il.MarkLabel (IL_003f);
+ il.Emit (OpCodes.Ldloca_S, 2);
+ il.Emit (OpCodes.Ldarg_2);
+ il.Emit (OpCodes.Ldarg_3);
+ il.Emit (OpCodes.Call, invokerType.GetConstructor (
+ BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof (IntPtr), typeof (IntPtr) }, null));
+ il.Emit (OpCodes.Ldloc, loc_container);
+ il.Emit (OpCodes.Ldarg_1);
+ il.Emit (OpCodes.Ldloc, loc_invoker);
+ il.Emit (OpCodes.Box, invokerType);
+ il.Emit (OpCodes.Ldftn, invokerType.GetMethod ("Invoke"));
+ il.Emit (OpCodes.Newobj, typeof (Gtk.Callback).GetConstructor (
+ BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof (object), typeof (IntPtr) }, null));
+ var forallMeth = typeof (Gtk.Container).GetMethod ("ForAll",
+ BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof (bool), typeof (Gtk.Callback) }, null);
+ il.Emit (OpCodes.Callvirt, forallMeth);
+
+ il.MarkLabel (IL_0060);
+
+ il.BeginCatchBlock (typeof (Exception));
+ il.Emit (OpCodes.Stloc, loc_ex);
+ il.Emit (OpCodes.Ldloc, loc_ex);
+ il.Emit (OpCodes.Ldc_I4_0);
+ il.Emit (OpCodes.Call, typeof (GLib.ExceptionManager).GetMethod ("RaiseUnhandledException"));
+ il.Emit (OpCodes.Leave, label_return);
+ il.EndExceptionBlock ();
+
+ il.MarkLabel (label_return);
+ il.Emit (OpCodes.Ret);
+
+ return (ForallDelegate) dm.CreateDelegate (typeof (ForallDelegate));
+ }
+
+ [UnmanagedFunctionPointer (CallingConvention.Cdecl)]
+ delegate void ForallDelegate (IntPtr container, bool include_internals, IntPtr cb, IntPtr data);
+
+ [DllImport(PangoUtil.LIBGTKGLUE, CallingConvention = CallingConvention.Cdecl)]
+ static extern void gtksharp_container_override_forall (IntPtr gtype, ForallDelegate cb);
+
+ const string urlRegexStr = @"(http|ftp)s?\:\/\/[\w\d\.,;_/\-~%@()+:?&^=#!]*[\w\d/]";
+ static readonly Regex UrlRegex = new Regex (urlRegexStr, RegexOptions.Compiled | RegexOptions.ExplicitCapture);
+
+ public static string MarkupLinks (string text)
+ {
+ if (GtkMinorVersion < 18)
+ return text;
+ return UrlRegex.Replace (text, MatchToUrl);
+ }
+
+ static string MatchToUrl (System.Text.RegularExpressions.Match m)
+ {
+ var s = m.ToString ();
+ return String.Format ("<a href='{0}'>{1}</a>", s, s.Replace ("_", "__"));
+ }
+
+ public static void SetLinkHandler (this Gtk.Label label, Action<string> urlHandler)
+ {
+ if (GtkMinorVersion >= 18)
+ new UrlHandlerClosure (urlHandler).ConnectTo (label);
+ }
+
+ //create closure manually so we can apply ConnectBefore
+ class UrlHandlerClosure
+ {
+ Action<string> urlHandler;
+
+ public UrlHandlerClosure (Action<string> urlHandler)
+ {
+ this.urlHandler = urlHandler;
+ }
+
+ [GLib.ConnectBefore]
+ void HandleLink (object sender, ActivateLinkEventArgs args)
+ {
+ urlHandler (args.Url);
+ args.RetVal = true;
+ }
+
+ public void ConnectTo (Gtk.Label label)
+ {
+ var signal = GLib.Signal.Lookup (label, "activate-link", typeof(ActivateLinkEventArgs));
+ signal.AddDelegate (new EventHandler<ActivateLinkEventArgs> (HandleLink));
+ }
+
+ class ActivateLinkEventArgs : GLib.SignalArgs
+ {
+ public string Url { get { return (string)base.Args [0]; } }
+ }
+ }
+
+ static bool canSetOverlayScrollbarPolicy = true;
+
+ [DllImport (PangoUtil.LIBQUARTZ)]
+ static extern void gtk_scrolled_window_set_overlay_policy (IntPtr sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy);
+
+ [DllImport (PangoUtil.LIBQUARTZ)]
+ static extern void gtk_scrolled_window_get_overlay_policy (IntPtr sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy);
+
+ public static void SetOverlayScrollbarPolicy (Gtk.ScrolledWindow sw, Gtk.PolicyType hpolicy, Gtk.PolicyType vpolicy)
+ {
+ if (!canSetOverlayScrollbarPolicy) {
+ return;
+ }
+ try {
+ gtk_scrolled_window_set_overlay_policy (sw.Handle, hpolicy, vpolicy);
+ return;
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ }
+
+ public static void GetOverlayScrollbarPolicy (Gtk.ScrolledWindow sw, out Gtk.PolicyType hpolicy, out Gtk.PolicyType vpolicy)
+ {
+ if (!canSetOverlayScrollbarPolicy) {
+ hpolicy = vpolicy = 0;
+ return;
+ }
+ try {
+ gtk_scrolled_window_get_overlay_policy (sw.Handle, out hpolicy, out vpolicy);
+ return;
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ hpolicy = vpolicy = 0;
+ canSetOverlayScrollbarPolicy = false;
+ }
+
+ [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
+ 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);
+
+ //the GTK# version of this has 'out' instead of 'ref', preventing passing the x,y values in
+ public static bool GetTooltipContext (this TreeView tree, ref int x, ref int y, bool keyboardTip,
+ out TreeModel model, out TreePath path, out Gtk.TreeIter iter)
+ {
+ IntPtr intPtr = Marshal.AllocHGlobal (Marshal.SizeOf (typeof(TreeIter)));
+ IntPtr handle;
+ IntPtr intPtr2;
+ bool result = gtk_tree_view_get_tooltip_context (tree.Handle, ref x, ref y, keyboardTip, out handle, out intPtr2, intPtr);
+ model = TreeModelAdapter.GetObject (handle, false);
+ path = intPtr2 == IntPtr.Zero ? null : ((TreePath)GLib.Opaque.GetOpaque (intPtr2, typeof(TreePath), false));
+ iter = TreeIter.New (intPtr);
+ Marshal.FreeHGlobal (intPtr);
+ return result;
+ }
+
+ static bool supportsHiResIcons = true;
+
+ [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
+ static extern void gtk_icon_source_set_scale (IntPtr source, double scale);
+
+ [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
+ static extern void gtk_icon_source_set_scale_wildcarded (IntPtr source, bool setting);
+
+ [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
+ static extern double gtk_widget_get_scale_factor (IntPtr widget);
+
+ [DllImport (PangoUtil.LIBGDK, CallingConvention = CallingConvention.Cdecl)]
+ static extern double gdk_screen_get_monitor_scale_factor (IntPtr widget, int monitor);
+
+ [DllImport (PangoUtil.LIBGOBJECT, CallingConvention = CallingConvention.Cdecl)]
+ static extern IntPtr g_object_get_data (IntPtr source, string name);
+
+ [DllImport (PangoUtil.LIBGTK, CallingConvention = CallingConvention.Cdecl)]
+ 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);
+
+ public static bool SetSourceScale (Gtk.IconSource source, double scale)
+ {
+ if (!supportsHiResIcons)
+ return false;
+
+ try {
+ gtk_icon_source_set_scale (source.Handle, scale);
+ return true;
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ supportsHiResIcons = false;
+ return false;
+ }
+
+ public static bool SetSourceScaleWildcarded (Gtk.IconSource source, bool setting)
+ {
+ if (!supportsHiResIcons)
+ return false;
+
+ try {
+ gtk_icon_source_set_scale_wildcarded (source.Handle, setting);
+ return true;
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ supportsHiResIcons = false;
+ return false;
+ }
+
+ public static Gdk.Pixbuf Get2xVariant (Gdk.Pixbuf px)
+ {
+ if (!supportsHiResIcons)
+ return null;
+
+ try {
+ IntPtr res = g_object_get_data (px.Handle, "gdk-pixbuf-2x-variant");
+ if (res != IntPtr.Zero && res != px.Handle)
+ return (Gdk.Pixbuf) GLib.Object.GetObject (res);
+ else
+ return null;
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ supportsHiResIcons = false;
+ return null;
+ }
+
+ public static void Set2xVariant (Gdk.Pixbuf px, Gdk.Pixbuf variant2x)
+ {
+ }
+
+ public static double GetScaleFactor (Gtk.Widget w)
+ {
+ if (!supportsHiResIcons)
+ return 1;
+
+ try {
+ return gtk_widget_get_scale_factor (w.Handle);
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ supportsHiResIcons = false;
+ return 1;
+ }
+
+ public static double GetScaleFactor (this Gdk.Screen screen, int monitor)
+ {
+ if (!supportsHiResIcons)
+ return 1;
+
+ try {
+ return gdk_screen_get_monitor_scale_factor (screen.Handle, monitor);
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ supportsHiResIcons = false;
+ return 1;
+ }
+
+ public static double GetScaleFactor ()
+ {
+ return GetScaleFactor (Gdk.Screen.Default, 0);
+ }
+
+ public static double GetPixelScale ()
+ {
+ if (Platform.IsWindows)
+ return GetScaleFactor ();
+ else
+ return 1d;
+ }
+
+ 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)
+ {
+ if (scale == 1d)
+ return iconset.RenderIcon (style, direction, state, size, widget, detail);
+
+ if (!supportsHiResIcons)
+ return null;
+
+ try {
+ IntPtr intPtr = GLib.Marshaller.StringToPtrGStrdup (detail);
+ 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);
+ Gdk.Pixbuf result = (Gdk.Pixbuf) GLib.Object.GetObject (o);
+ GLib.Marshaller.Free (intPtr);
+ return result;
+ } catch (DllNotFoundException) {
+ } catch (EntryPointNotFoundException) {
+ }
+ supportsHiResIcons = false;
+ return null;
+ }
+ }
+
+ public struct KeyboardShortcut : IEquatable<KeyboardShortcut>
+ {
+ public static readonly KeyboardShortcut Empty = new KeyboardShortcut ((Gdk.Key) 0, (Gdk.ModifierType) 0);
+
+ Gdk.ModifierType modifier;
+ Gdk.Key key;
+
+ public KeyboardShortcut (Gdk.Key key, Gdk.ModifierType modifier)
+ {
+ this.modifier = modifier;
+ this.key = key;
+ }
+
+ public Gdk.Key Key {
+ get { return key; }
+ }
+
+ public Gdk.ModifierType Modifier {
+ get { return modifier; }
+ }
+
+ public bool IsEmpty {
+ get { return Key == (Gdk.Key) 0; }
+ }
+
+ public override bool Equals (object obj)
+ {
+ return obj is KeyboardShortcut && this.Equals ((KeyboardShortcut) obj);
+ }
+
+ public override int GetHashCode ()
+ {
+ //FIXME: we're only using a few bits of mod and mostly the lower bits of key - distribute it better
+ return (int) Key ^ (int) Modifier;
+ }
+
+ public bool Equals (KeyboardShortcut other)
+ {
+ return other.Key == Key && other.Modifier == Modifier;
+ }
+ }
+
+}