5
// Michael Hutchinson <m.j.hutchinson@gmail.com>
7
// Copyright (c) 2013 Xamarin Inc.
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
using MonoDevelop.Components.Commands;
30
using MonoDevelop.Core;
32
using MonoMac.Foundation;
33
using MonoMac.ObjCRuntime;
34
using System.Collections.Generic;
36
namespace MonoDevelop.MacIntegration.MacMenu
38
//TODO: autohide, arrray
39
class MDMenuItem : NSMenuItem, IUpdatableMenuItem
41
public static Selector ActionSel = new Selector ("run:");
44
CommandManager manager;
48
public MDMenuItem (CommandManager manager, CommandEntry ce, ActionCommand command)
51
this.manager = manager;
53
isArrayItem = command.CommandArray;
59
public CommandEntry CommandEntry { get { return ce; } }
62
public void Run (NSMenuItem sender)
64
var a = sender as MDExpandedArrayItem;
65
//if the command opens a modal subloop, give cocoa a chance to unhighlight the menu item
66
GLib.Timeout.Add (1, () => {
68
manager.DispatchCommand (ce.CommandId, a.Info.DataItem, CommandSource.MainMenu);
70
manager.DispatchCommand (ce.CommandId, CommandSource.MainMenu);
76
//NOTE: This is used to disable the whole menu when there's a modal dialog.
77
// We can justify this because safari 3.2.1 does it ("do you want to close all tabs?").
78
public static bool IsGloballyDisabled {
80
return !MonoDevelop.Ide.IdeApp.Workbench.HasToplevelFocus;
84
public void Update (MDMenu parent, ref NSMenuItem lastSeparator, ref int index)
86
var info = manager.GetCommandInfo (ce.CommandId);
89
SetItemValues (this, info);
91
MDMenu.ShowLastSeparator (ref lastSeparator);
97
if (index < parent.Count - 1) {
98
for (int i = index + 1; i < parent.Count; i++) {
99
var nextItem = parent.ItemAt (i);
100
if (nextItem == null || nextItem.Target != this)
102
parent.RemoveItemAt (i);
107
PopulateArrayItems (info.ArrayInfo, parent, ref lastSeparator, ref index);
110
void PopulateArrayItems (CommandArrayInfo infos, NSMenu parent, ref NSMenuItem lastSeparator, ref int index)
112
foreach (CommandInfo ci in infos) {
113
if (ci.IsArraySeparator) {
114
var n = NSMenuItem.SeparatorItem;
118
parent.InsertItem (n, index++);
122
var item = new MDExpandedArrayItem {
128
if (ci is CommandInfoSet) {
129
item.Submenu = new NSMenu ();
131
NSMenuItem sep = null;
132
PopulateArrayItems (((CommandInfoSet)ci).CommandInfos, item.Submenu, ref sep, ref i);
134
SetItemValues (item, ci);
137
MDMenu.ShowLastSeparator (ref lastSeparator);
138
parent.InsertItem (item, index++);
142
class MDExpandedArrayItem : NSMenuItem
144
public CommandInfo Info;
147
static void SetItemValues (NSMenuItem item, CommandInfo info)
149
item.SetTitleWithMnemonic (GetCleanCommandText (info));
150
item.Enabled = !IsGloballyDisabled && info.Enabled;
151
item.Hidden = !info.Visible;
152
SetAccel (item, info.AccelKey);
155
item.State = NSCellStateValue.On;
156
} else if (info.CheckedInconsistent) {
157
item.State = NSCellStateValue.Mixed;
159
item.State = NSCellStateValue.Off;
163
static void SetAccel (NSMenuItem item, string accelKey)
166
Gdk.ModifierType modeMod;
168
Gdk.ModifierType mod;
170
if (!KeyBindingManager.BindingToKeys (accelKey, out modeKey, out modeMod, out key, out mod)) {
171
item.KeyEquivalent = "";
172
item.KeyEquivalentModifierMask = (NSEventModifierMask) 0;
177
LoggingService.LogWarning ("Mac menu cannot display accelerators with mode keys ({0})", accelKey);
178
item.KeyEquivalent = "";
179
item.KeyEquivalentModifierMask = (NSEventModifierMask) 0;
183
var keyEq = GetKeyEquivalent ((Gdk.Key) key);
184
item.KeyEquivalent = keyEq;
185
if (keyEq.Length == 0) {
186
item.KeyEquivalentModifierMask = 0;
189
NSEventModifierMask outMod = 0;
190
if ((mod & Gdk.ModifierType.Mod1Mask) != 0) {
191
outMod |= NSEventModifierMask.AlternateKeyMask;
192
mod ^= Gdk.ModifierType.Mod1Mask;
194
if ((mod & Gdk.ModifierType.ShiftMask) != 0) {
195
outMod |= NSEventModifierMask.ShiftKeyMask;
196
mod ^= Gdk.ModifierType.ShiftMask;
198
if ((mod & Gdk.ModifierType.ControlMask) != 0) {
199
outMod |= NSEventModifierMask.ControlKeyMask;
200
mod ^= Gdk.ModifierType.ControlMask;
202
if ((mod & Gdk.ModifierType.MetaMask) != 0) {
203
outMod |= NSEventModifierMask.CommandKeyMask;
204
mod ^= Gdk.ModifierType.MetaMask;
208
LoggingService.LogWarning ("Mac menu cannot display accelerators with modifiers {0}", mod);
210
item.KeyEquivalentModifierMask = outMod;
213
static string GetKeyEquivalent (Gdk.Key key)
215
char c = (char) Gdk.Keyval.ToUnicode ((uint) key);
217
return c.ToString ();
219
var fk = GetFunctionKey (key);
221
return ((char) fk).ToString ();
223
LoggingService.LogError ("Mac menu cannot display key '{0}", key);
227
static string GetCleanCommandText (CommandInfo ci)
229
string txt = ci.Text;
233
//FIXME: markup stripping could be done better
234
var sb = new StringBuilder ();
235
for (int i = 0; i < txt.Length; i++) {
238
if (i + 1 < txt.Length && txt[i + 1] == '_') {
244
} else if (!ci.UseMarkup) {
246
} else if (ch == '<') {
247
while (++i < txt.Length && txt[i] != '>');
248
} else if (ch == '&') {
250
while (++i < txt.Length && txt[i] != ';');
253
string entityName = txt.Substring (j + 1, i - j - 1);
254
switch (entityName) {
271
LoggingService.LogWarning ("Could not de-markup entity '{0}'", entityName);
280
return sb.ToString ();
283
static FunctionKey GetFunctionKey (Gdk.Key key)
287
return (FunctionKey) (uint) '\n';
288
case Gdk.Key.BackSpace:
289
return (FunctionKey) 0x08;
290
// NSBackspaceCharacter
291
case Gdk.Key.KP_Delete:
293
return (FunctionKey) 0x7F;
297
return FunctionKey.UpArrow;
298
case Gdk.Key.KP_Down:
300
return FunctionKey.DownArrow;
301
case Gdk.Key.KP_Left:
303
return FunctionKey.LeftArrow;
304
case Gdk.Key.KP_Right:
306
return FunctionKey.RightArrow;
308
return FunctionKey.F1;
310
return FunctionKey.F2;
312
return FunctionKey.F3;
314
return FunctionKey.F4;
316
return FunctionKey.F5;
318
return FunctionKey.F6;
320
return FunctionKey.F7;
322
return FunctionKey.F8;
324
return FunctionKey.F9;
326
return FunctionKey.F10;
328
return FunctionKey.F11;
330
return FunctionKey.F12;
332
return FunctionKey.F13;
334
return FunctionKey.F14;
336
return FunctionKey.F15;
338
return FunctionKey.F16;
340
return FunctionKey.F17;
342
return FunctionKey.F18;
344
return FunctionKey.F19;
346
return FunctionKey.F20;
348
return FunctionKey.F21;
350
return FunctionKey.F22;
352
return FunctionKey.F23;
354
return FunctionKey.F24;
356
return FunctionKey.F25;
358
return FunctionKey.F26;
360
return FunctionKey.F27;
362
return FunctionKey.F28;
364
return FunctionKey.F29;
366
return FunctionKey.F30;
368
return FunctionKey.F31;
370
return FunctionKey.F32;
372
return FunctionKey.F33;
374
return FunctionKey.F34;
376
return FunctionKey.F35;
377
case Gdk.Key.KP_Insert:
379
return FunctionKey.Insert;
380
case Gdk.Key.KP_Home:
382
return FunctionKey.Home;
384
return FunctionKey.Begin;
387
return FunctionKey.End;
388
case Gdk.Key.KP_Page_Up:
389
case Gdk.Key.Page_Up:
390
return FunctionKey.PageUp;
391
case Gdk.Key.KP_Page_Down:
392
case Gdk.Key.Page_Down:
393
return FunctionKey.PageDown;
394
case Gdk.Key.Key_3270_PrintScreen:
395
return FunctionKey.PrintScreen;
396
case Gdk.Key.Scroll_Lock:
397
return FunctionKey.ScrollLock;
399
return FunctionKey.Pause;
400
case Gdk.Key.Sys_Req:
401
return FunctionKey.SysReq;
403
return FunctionKey.Break;
404
case Gdk.Key.Key_3270_Reset:
405
return FunctionKey.Reset;
407
return FunctionKey.Menu;
409
return FunctionKey.Print;
411
return FunctionKey.Help;
413
return FunctionKey.Find;
415
return FunctionKey.Undo;
417
return FunctionKey.Redo;
418
case Gdk.Key.Execute:
419
return FunctionKey.Execute;
421
return FunctionKey.Stop;
422
return FunctionKey.User;
423
return FunctionKey.System;
424
return FunctionKey.ClearLine;
425
return FunctionKey.ClearDisplay;
426
return FunctionKey.InsertLine;
427
return FunctionKey.DeleteLine;
428
return FunctionKey.InsertChar;
429
return FunctionKey.DeleteChar;
430
return FunctionKey.Next;
431
return FunctionKey.Prev;
432
return FunctionKey.Select;
433
return FunctionKey.ModeSwitch;
440
// "Function-Key Unicodes" from NSEvent reference
441
enum FunctionKey : ushort
489
PrintScreen = 0xF72E,
501
ClearDisplay = 0xF73A,