diff options
author | Lluis Sanchez <lluis@novell.com> | 2010-03-17 15:35:28 +0300 |
---|---|---|
committer | Lluis Sanchez <lluis@novell.com> | 2010-03-17 15:35:28 +0300 |
commit | 8fa37870e9cc22ffccdd494fa951b2c3807d7978 (patch) | |
tree | bda14806802947c51676c183b08f166878964c40 /main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands | |
parent | f1a8582658af8aeb0f6fa459965a2e4d0684c347 (diff) | |
parent | 585086f0ea0a49166046bb8f48d2def87907d0e0 (diff) |
Merged MD.Projects into MD.Core, and MD.Projects.Gui, MD.Core.Gui and MD.Components into MD.Ide.
svn path=/trunk/monodevelop/; revision=153730
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands')
42 files changed, 5886 insertions, 0 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ActionCommand.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ActionCommand.cs new file mode 100644 index 0000000000..0f06f0cd38 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ActionCommand.cs @@ -0,0 +1,134 @@ +// +// ActionCommand.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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 Mono.Addins; +using MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public class ActionCommand: Command + { + ActionType type; + bool commandArray; + Type defaultHandlerType; + CommandHandler defaultHandler; + + RuntimeAddin defaultHandlerAddin; + string defaultHandlerTypeName; + + public ActionCommand () + { + } + + public ActionCommand (object id, string text): base (id, text) + { + } + + public ActionCommand (object id, string text, IconId icon): base (id, text) + { + Icon = icon; + } + + public ActionCommand (object id, string text, IconId icon, string accelKey, ActionType type): base (id, text) + { + Icon = icon; + AccelKey = accelKey; + this.type = type; + } + + public ActionType ActionType { + get { return type; } + set { type = value; } + } + + public bool CommandArray { + get { return commandArray; } + set { commandArray = value; } + } + + public Type DefaultHandlerType { + get { + if (defaultHandlerType != null) + return defaultHandlerType; + if (defaultHandlerAddin != null) + return defaultHandlerType = defaultHandlerAddin.GetType (defaultHandlerTypeName, true); + return null; + } + set { + if (!typeof (CommandHandler).IsAssignableFrom (value)) + throw new ArgumentException ("Value must be a subclass of CommandHandler (" + value + ")"); + + defaultHandlerType = value; + } + } + + public void SetDefaultHandlerTypeInfo (RuntimeAddin addin, string typeName) + { + defaultHandlerAddin = addin; + defaultHandlerTypeName = typeName; + } + + public CommandHandler DefaultHandler { + get { return defaultHandler; } + set { defaultHandler = value; } + } + + public virtual bool DispatchCommand (object dataItem) + { + if (defaultHandler == null) { + if (DefaultHandlerType == null) + return false; + defaultHandler = (CommandHandler) Activator.CreateInstance (DefaultHandlerType); + } + defaultHandler.InternalRun (dataItem); + return true; + } + + public virtual void UpdateCommandInfo (CommandInfo info) + { + if (defaultHandler == null) { + if (DefaultHandlerType == null) { + info.Enabled = false; + if (!DisabledVisible) + info.Visible = false; + return; + } + defaultHandler = (CommandHandler) Activator.CreateInstance (DefaultHandlerType); + } + if (commandArray) { + info.ArrayInfo = new CommandArrayInfo (info); + defaultHandler.InternalUpdate (info.ArrayInfo); + } + else + defaultHandler.InternalUpdate (info); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ActionType.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ActionType.cs new file mode 100644 index 0000000000..40c3d71491 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ActionType.cs @@ -0,0 +1,39 @@ +// +// ActionType.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public enum ActionType + { + Normal, + Check, + Radio + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/Command.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/Command.cs new file mode 100644 index 0000000000..bc1079bca4 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/Command.cs @@ -0,0 +1,127 @@ +// +// Command.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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 MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public delegate void KeyBindingChangedEventHandler (object s, KeyBindingChangedEventArgs args); + + public abstract class Command + { + public static readonly object Separator = new Object (); + + object id; + string text; + string description; + IconId icon; + string accelKey; + string category; + bool disabledVisible; + internal string AccelPath; + internal object HandlerData; // Used internally when dispatching the command + + public Command () + { + } + + public Command (object id, string text) + { + this.id = CommandManager.ToCommandId (id); + this.text = text; + } + + public object Id { + get { return id; } + set { id = CommandManager.ToCommandId (value); } + } + + public string Text { + get { return text; } + set { text = value; } + } + + public IconId Icon { + get { return icon; } + set { icon = value; } + } + + public string AccelKey { + get { return accelKey; } + set { + string binding = accelKey; + accelKey = value == String.Empty ? null : value; + + if (KeyBindingChanged != null && accelKey != binding) + KeyBindingChanged (this, new KeyBindingChangedEventArgs (this, binding)); + } + } + + public bool DisabledVisible { + get { return disabledVisible; } + set { disabledVisible = value; } + } + + public string Description { + get { return description; } + set { description = value; } + } + + public string Category { + get { return category == null ? string.Empty : category; } + set { category = value; } + } + + public event KeyBindingChangedEventHandler KeyBindingChanged; + } + + public class KeyBindingChangedEventArgs { + Command command; + string binding; + + public KeyBindingChangedEventArgs (Command command, string oldBinding) + { + this.command = command; + this.binding = oldBinding; + } + + public Command Command { + get { return command; } + } + + public string OldKeyBinding { + get { return binding; } + } + + public string NewKeyBinding { + get { return command.AccelKey; } + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandArrayInfo.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandArrayInfo.cs new file mode 100644 index 0000000000..d81d07e729 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandArrayInfo.cs @@ -0,0 +1,124 @@ +// +// CommandArrayInfo.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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.Collections; +using System.Collections.Generic; + +namespace MonoDevelop.Components.Commands +{ + public class CommandArrayInfo: IEnumerable + { + List<CommandInfo> list = new List<CommandInfo> (); + CommandInfo defaultInfo; + bool bypass; + internal object UpdateHandlerData; + + internal CommandArrayInfo (CommandInfo defaultInfo) + { + this.defaultInfo = defaultInfo; + } + + public void Clear () + { + list.Clear (); + } + + public void Insert (int index, CommandInfoSet infoSet) + { + Insert (index, infoSet, null); + } + + public void Insert (int index, CommandInfo info, object dataItem) + { + info.DataItem = dataItem; + if (info.Text == null) info.Text = defaultInfo.Text; + if (info.Icon.IsNull) info.Icon = defaultInfo.Icon; + list.Insert (index, info); + } + + public CommandInfo Insert (int index, string text, object dataItem) + { + CommandInfo info = new CommandInfo (text); + Insert (index, info, dataItem); + return info; + } + + public void Add (CommandInfoSet infoSet) + { + Add (infoSet, null); + } + + public void Add (CommandInfo info, object dataItem) + { + info.DataItem = dataItem; + if (info.Text == null) info.Text = defaultInfo.Text; + if (info.Icon.IsNull) info.Icon = defaultInfo.Icon; + list.Add (info); + } + + public CommandInfo Add (string text, object dataItem) + { + CommandInfo info = new CommandInfo (text); + Add (info, dataItem); + return info; + } + + public CommandInfo this [int n] { + get { return list [n]; } + } + + public int Count { + get { return list.Count; } + } + + + public void AddSeparator () + { + CommandInfo info = new CommandInfo ("-"); + info.IsArraySeparator = true; + Add (info, null); + } + + public CommandInfo DefaultCommandInfo { + get { return defaultInfo; } + } + + public IEnumerator GetEnumerator () + { + return list.GetEnumerator (); + } + + // When set in an update handler, the command manager will ignore this handler method + // and will keep looking in the command route. + public bool Bypass { + get { return bypass; } + set { bypass = value; } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandCheckMenuItem.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandCheckMenuItem.cs new file mode 100644 index 0000000000..cf14ea9cb6 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandCheckMenuItem.cs @@ -0,0 +1,178 @@ +// +// CommandCheckMenuItem.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + internal class CommandCheckMenuItem: Gtk.CheckMenuItem, ICommandMenuItem + { + CommandManager commandManager; + object commandId; + bool updating; + bool isArrayItem; + object arrayDataItem; + object initialTarget; + string overrideLabel; + CommandInfo lastCmdInfo; + bool disabledVisible = true; + + public CommandCheckMenuItem (object commandId, CommandManager commandManager, string overrideLabel, bool disabledVisible): base ("") + { + this.commandId = commandId; + this.commandManager = commandManager; + this.overrideLabel = overrideLabel; + this.disabledVisible = disabledVisible; + + ActionCommand cmd = commandManager.GetCommand (commandId) as ActionCommand; + if (cmd != null && cmd.ActionType == ActionType.Radio) + this.DrawAsRadio = true; + } + + public CommandCheckMenuItem (object commandId, CommandManager commandManager): this (commandId, commandManager, null, true) + { + } + + void ICommandUserItem.Update (object initialTarget) + { + if (commandManager != null && !isArrayItem) { + CommandInfo cinfo = commandManager.GetCommandInfo (commandId, initialTarget); + this.initialTarget = initialTarget; + Update (cinfo); + } + } + + void ICommandMenuItem.SetUpdateInfo (CommandInfo cmdInfo, object initialTarget) + { + isArrayItem = true; + arrayDataItem = cmdInfo.DataItem; + this.initialTarget = initialTarget; + Update (cmdInfo); + } + + protected override void OnParentSet (Gtk.Widget parent) + { + base.OnParentSet (parent); + if (Parent == null) + return; + + ((ICommandUserItem)this).Update (null); + + // Make sure the accelerators always work for this item + // while the menu is hidden + Sensitive = true; + Visible = true; + } + + protected override void OnActivated () + { + base.OnActivated (); + + if (updating) + return; + + if (commandManager == null) + throw new InvalidOperationException (); + + commandManager.DispatchCommand (commandId, arrayDataItem, initialTarget); + } + + protected override void OnSelected () + { + if (commandManager != null) + commandManager.NotifySelected (lastCmdInfo); + base.OnSelected (); + } + + protected override void OnDeselected () + { + if (commandManager != null) + commandManager.NotifyDeselected (); + base.OnDeselected (); + } + + void Update (CommandInfo cmdInfo) + { + lastCmdInfo = cmdInfo; + + Gtk.Widget child = Child; + if (child == null) + return; + + updating = true; + + Gtk.Label accel_label = null; + Gtk.Label label = null; + + if (!(child is Gtk.HBox)) { + child = new Gtk.HBox (false, 0); + accel_label = new Gtk.Label (""); + accel_label.UseUnderline = false; + accel_label.Xalign = 1.0f; + accel_label.Show (); + + label = new Gtk.Label (""); + label.UseUnderline = true; + label.Xalign = 0.0f; + label.Show (); + + ((Gtk.Box) child).PackStart (label); + ((Gtk.Box) child).PackStart (accel_label); + child.Show (); + + this.Remove (Child); + this.Add (child); + } else { + accel_label = (Gtk.Label) ((Gtk.Box) child).Children[1]; + label = (Gtk.Label) ((Gtk.Box) child).Children[0]; + } + + if (cmdInfo.AccelKey != null) + accel_label.Text = " " + KeyBindingManager.BindingToDisplayLabel (cmdInfo.AccelKey, true); + else + accel_label.Text = String.Empty; + + if (cmdInfo.UseMarkup) { + label.Markup = overrideLabel ?? cmdInfo.Text; + label.UseMarkup = true; + } else { + label.Text = overrideLabel ?? cmdInfo.Text; + label.UseMarkup = false; + } + + label.UseUnderline = true; + + Sensitive = cmdInfo.Enabled; + Visible = cmdInfo.Visible && (disabledVisible || cmdInfo.Enabled); + Active = cmdInfo.Checked; + Inconsistent = cmdInfo.CheckedInconsistent; + + updating = false; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandEntry.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandEntry.cs new file mode 100644 index 0000000000..28e4bb4d3f --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandEntry.cs @@ -0,0 +1,165 @@ +// +// CommandEntry.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CommandEntry + { + object cmdId; + string overrideLabel; + bool disabledVisible = true; + Command localCmd; + + public CommandEntry (object cmdId, string overrideLabel, bool disabledVisible) + { + this.cmdId = CommandManager.ToCommandId (cmdId); + this.overrideLabel = overrideLabel; + this.disabledVisible = disabledVisible; + } + + public CommandEntry (object cmdId, string overrideLabel): this (cmdId, overrideLabel, true) + { + } + + public CommandEntry (object cmdId) : this (cmdId, null, true) + { + } + + public CommandEntry (Command localCmd) : this (localCmd.Id, null, true) + { + this.localCmd = localCmd; + } + + public object CommandId { + get { return cmdId; } + set { cmdId = CommandManager.ToCommandId (value); } + } + + public bool DisabledVisible { + get { return disabledVisible; } + set { disabledVisible = value; } + } + + public virtual Command GetCommand (CommandManager manager) + { + if (localCmd != null) { + if (manager.GetCommand (localCmd.Id) == null) + manager.RegisterCommand (localCmd); + localCmd = null; + } + + return manager.GetCommand (cmdId); + } + + internal protected virtual Gtk.MenuItem CreateMenuItem (CommandManager manager) + { + return CreateMenuItem (manager, GetCommand (manager), cmdId, true, overrideLabel, disabledVisible); + } + + internal protected virtual Gtk.ToolItem CreateToolItem (CommandManager manager) + { + if (cmdId == CommandManager.ToCommandId (Command.Separator)) + return new Gtk.SeparatorToolItem (); + + Command cmd = GetCommand (manager); + if (cmd == null) + return new Gtk.ToolItem (); + + if (cmd is CustomCommand) { + Gtk.Widget child = (Gtk.Widget) Activator.CreateInstance (((CustomCommand)cmd).WidgetType); + Gtk.ToolItem ti; + if (child is Gtk.ToolItem) + ti = (Gtk.ToolItem) child; + else { + ti = new Gtk.ToolItem (); + ti.Child = child; + } + if (cmd.Text != null && cmd.Text.Length > 0) { + //strip "_" accelerators from tooltips + string text = cmd.Text; + while (true) { + int underscoreIndex = text.IndexOf ('_'); + if (underscoreIndex > -1) + text = text.Remove (underscoreIndex, 1); + else + break; + } + ti.TooltipText = text; + } + return ti; + } + + ActionCommand acmd = cmd as ActionCommand; + if (acmd == null) + throw new InvalidOperationException ("Unknown cmd type."); + + if (acmd.CommandArray) { + CommandMenu menu = new CommandMenu (manager); + menu.Append (CreateMenuItem (manager)); + return new MenuToolButton (menu, acmd.Icon); + } + else if (acmd.ActionType == ActionType.Normal) + return new CommandToolButton (cmdId, manager); + else + return new CommandToggleToolButton (cmdId, manager); + } + + internal static Gtk.MenuItem CreateMenuItem (CommandManager manager, object cmdId, bool isArrayMaster) + { + return CreateMenuItem (manager, null, cmdId, isArrayMaster, null, true); + } + + static Gtk.MenuItem CreateMenuItem (CommandManager manager, Command cmd, object cmdId, bool isArrayMaster, string overrideLabel, bool disabledVisible) + { + cmdId = CommandManager.ToCommandId (cmdId); + if (cmdId == CommandManager.ToCommandId (Command.Separator)) + return new Gtk.SeparatorMenuItem (); + + if (cmd == null) + cmd = manager.GetCommand (cmdId); + if (cmd == null) + return new Gtk.MenuItem ("<Unknown Command>"); + + if (cmd is CustomCommand) { + Gtk.Widget child = (Gtk.Widget) Activator.CreateInstance (((CustomCommand)cmd).WidgetType); + CustomMenuItem ti = new CustomMenuItem (); + ti.Child = child; + return ti; + } + + ActionCommand acmd = cmd as ActionCommand; + if (acmd.ActionType == ActionType.Normal || (isArrayMaster && acmd.CommandArray)) + return new CommandMenuItem (cmdId, manager, overrideLabel, disabledVisible); + else + return new CommandCheckMenuItem (cmdId, manager, overrideLabel, disabledVisible); + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandEntrySet.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandEntrySet.cs new file mode 100644 index 0000000000..999fae15fc --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandEntrySet.cs @@ -0,0 +1,148 @@ +// +// CommandEntrySet.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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.Collections; +using MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public class CommandEntrySet: CommandEntry, IEnumerable + { + ArrayList cmds = new ArrayList (); + string name; + IconId icon; + bool autoHide; + + public CommandEntrySet (): base ((object)null) + { + } + + public CommandEntrySet (string name, IconId icon): base ((object)null) + { + this.name = name; + this.icon = icon; + } + + public string Name { + get { return name; } + set { name = value; } + } + + public IconId Icon { + get { return icon; } + set { icon = value; } + } + + public bool AutoHide { + get { return autoHide; } + set { autoHide = value; } + } + + public void Add (CommandEntry entry) + { + cmds.Add (entry); + } + + public void Add (Command cmd) + { + cmds.Add (new CommandEntry (cmd)); + } + + public CommandEntry AddItem (object cmdId) + { + CommandEntry cmd = new CommandEntry (cmdId); + cmds.Add (cmd); + return cmd; + } + + public void AddSeparator () + { + AddItem (Command.Separator); + } + + public CommandEntrySet AddItemSet (string name) + { + return AddItemSet (name, ""); + } + + public CommandEntrySet AddItemSet (string name, IconId icon) + { + CommandEntrySet cmdset = new CommandEntrySet (name, icon); + cmds.Add (cmdset); + return cmdset; + } + + public IEnumerator GetEnumerator () + { + return cmds.GetEnumerator (); + } + + public int Count { + get { return cmds.Count; } + } + + internal protected override Gtk.MenuItem CreateMenuItem (CommandManager manager) + { + Gtk.MenuItem mi; + if (autoHide) + mi = new AutoHideMenuItem (name != null ? name : ""); + else + mi = new Gtk.MenuItem (name != null ? name : ""); + mi.Submenu = new CommandMenu (manager, this); + return mi; + } + + internal protected override Gtk.ToolItem CreateToolItem (CommandManager manager) + { + Gtk.Menu menu = manager.CreateMenu (this); + return new MenuToolButton (menu, icon); + } + } + + class AutoHideMenuItem: Gtk.ImageMenuItem + { + public AutoHideMenuItem (string name): base (name) + { + ShowAll (); + } + + public bool HasVisibleChildren { + get { + if (!Submenu.IsRealized) + Submenu.Realize (); + foreach (Gtk.Widget item in ((Gtk.Menu)Submenu).Children) { + if (item.Visible) + return true; + } + return false; + } + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandErrorHandler.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandErrorHandler.cs new file mode 100644 index 0000000000..af685bf593 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandErrorHandler.cs @@ -0,0 +1,60 @@ +// +// CommandErrorHandler.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public delegate void CommandErrorHandler (object sender, CommandErrorArgs args); + + public class CommandErrorArgs + { + object commandId; + string errorMessage; + Exception ex; + + public CommandErrorArgs (object commandId, string errorMessage, Exception ex) + { + this.errorMessage = errorMessage; + this.commandId = commandId; + this.ex = ex; + } + + public object CommandId { + get { return commandId; } + } + + public string ErrorMessage { + get { return errorMessage; } + } + + public Exception Exception { + get { return ex; } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandFrame.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandFrame.cs new file mode 100644 index 0000000000..218a1d75f6 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandFrame.cs @@ -0,0 +1,74 @@ +// +// CommandFrame.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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 Mono.Addins; +using MonoDevelop.Components.DockToolbars; + +namespace MonoDevelop.Components.Commands +{ + public class CommandFrame: DockToolbarFrame + { + CommandManager manager; + + public CommandFrame (CommandManager manager) + { + this.manager = manager; + manager.RegisterGlobalHandler (this); + } + + [CommandHandler (CommandSystemCommands.ToolbarList)] + protected void OnViewToolbar (object ob) + { + IDockToolbar bar = (IDockToolbar) ob; + bar.Visible = !bar.Visible; + } + + [CommandUpdateHandler (CommandSystemCommands.ToolbarList)] + protected void OnUpdateViewToolbar (CommandArrayInfo info) + { + foreach (IDockToolbar bar in Toolbars) { + CommandInfo cmd = new CommandInfo (bar.Title); + cmd.Checked = bar.Visible; + cmd.Description = AddinManager.CurrentLocalizer.GetString ("Show toolbar '{0}'", bar.Title); + info.Add (cmd, bar); + } + } + + protected override void OnPanelClick (Gdk.EventButton e, Placement placement) + { + if (e.Button == 3) { + CommandEntrySet opset = new CommandEntrySet (); + opset.AddItem (CommandSystemCommands.ToolbarList); + Gtk.Menu menu = manager.CreateMenu (opset); + menu.Popup (null, null, null, 0, e.Time); + } + base.OnPanelClick (e, placement); + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandHandler.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandHandler.cs new file mode 100644 index 0000000000..af0bb209c3 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandHandler.cs @@ -0,0 +1,73 @@ +// +// CommandHandler.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public abstract class CommandHandler + { + internal void InternalRun () + { + Run (); + } + + internal void InternalRun (object dataItem) + { + Run (dataItem); + } + + internal void InternalUpdate (CommandInfo info) + { + Update (info); + } + + internal void InternalUpdate (CommandArrayInfo info) + { + Update (info); + } + + protected virtual void Run () + { + } + + protected virtual void Run (object dataItem) + { + Run (); + } + + protected virtual void Update (CommandInfo info) + { + } + + protected virtual void Update (CommandArrayInfo info) + { + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandHandlerAttribute.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandHandlerAttribute.cs new file mode 100644 index 0000000000..68d5ce6b37 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandHandlerAttribute.cs @@ -0,0 +1,63 @@ +// +// CommandMethodAttribute.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CommandMethodAttribute: Attribute + { + object commandId; + + internal CommandMethodAttribute (object commandId) + { + this.commandId = commandId; + } + + public object CommandId { + get { return commandId; } + set { commandId = value; } + } + } + + [AttributeUsage (AttributeTargets.Method, AllowMultiple=true)] + public class CommandHandlerAttribute: CommandMethodAttribute + { + public CommandHandlerAttribute (object commandId): base (commandId) + { + } + } + + [AttributeUsage (AttributeTargets.Method, AllowMultiple=true)] + public class CommandUpdateHandlerAttribute: CommandMethodAttribute + { + public CommandUpdateHandlerAttribute (object commandId): base (commandId) + { + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandInfo.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandInfo.cs new file mode 100644 index 0000000000..a62b8bc5e5 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandInfo.cs @@ -0,0 +1,136 @@ +// +// CommandInfo.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2006 Novell, Inc (http://www.novell.com) +// +// 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 MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public class CommandInfo + { + string text; + IconId icon; + string accelKey; + string description; + bool enabled = true; + bool visible = true; + bool checkd; + bool useMarkup; + bool bypass; + bool checkedInconsistent; + internal object UpdateHandlerData; + + internal CommandInfo (Command cmd) + { + text = cmd.Text; + icon = cmd.Icon; + accelKey = cmd.AccelKey; + description = cmd.Description; + } + + public CommandInfo () + { + } + + public CommandInfo (string text) + { + Text = text; + } + + public CommandInfo (string text, bool enabled, bool checkd) + { + Text = text; + this.enabled = enabled; + this.checkd = checkd; + } + + public string Text { + get { return text; } + set { text = value; } + } + + public IconId Icon { + get { return icon; } + set { icon = value; } + } + + public string AccelKey { + get { return accelKey; } + set { accelKey = value; } + } + + public string Description { + get { return description; } + set { description = value; } + } + + public bool Enabled { + get { return enabled; } + set { enabled = value; } + } + + public bool Visible { + get { return visible; } + set { visible = value; } + } + + public bool Checked { + get { return checkd; } + set { checkd = value; } + } + + public bool CheckedInconsistent { + get { return checkedInconsistent; } + set { checkedInconsistent = value; } + } + + public bool UseMarkup { + get { return useMarkup; } + set { useMarkup = value; } + } + + // When set in an update handler, the command manager will ignore this handler method + // and will keep looking in the command route. + public bool Bypass { + get { return bypass; } + set { bypass = value; } + } + + public CommandArrayInfo ArrayInfo { + get; internal set; + } + + public object DataItem { + get; internal set; + } + + public bool IsArraySeparator { + get; internal set; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandInfoSet.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandInfoSet.cs new file mode 100644 index 0000000000..80ebafe453 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandInfoSet.cs @@ -0,0 +1,47 @@ +// +// CommandInfoSet.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CommandInfoSet: CommandInfo + { + CommandArrayInfo commandInfos; + + public CommandInfoSet () + { + commandInfos = new CommandArrayInfo (this); + } + + public CommandArrayInfo CommandInfos { + get { return commandInfos; } + } + } + +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs new file mode 100644 index 0000000000..96ce6e241f --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs @@ -0,0 +1,1448 @@ +// +// CommandManager.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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.Reflection; +using System.Collections; +using System.Collections.Generic; +using MonoDevelop.Components.Commands.ExtensionNodes; +using Mono.Addins; + +namespace MonoDevelop.Components.Commands +{ + public class CommandManager: IDisposable + { + public static CommandManager Main = new CommandManager (); + Gtk.Window rootWidget; + KeyBindingManager bindings; + Gtk.AccelGroup accelGroup; + string mode; + + Dictionary<object,Command> cmds = new Dictionary<object,Command> (); + Hashtable handlerInfo = new Hashtable (); + List<ICommandBar> toolbars = new List<ICommandBar> (); + CommandTargetChain globalHandlerChain; + ArrayList commandUpdateErrors = new ArrayList (); + ArrayList visitors = new ArrayList (); + Dictionary<Gtk.Window,Gtk.Window> topLevelWindows = new Dictionary<Gtk.Window,Gtk.Window> (); + Stack delegatorStack = new Stack (); + + HashSet<object> visitedTargets = new HashSet<object> (); + + bool disposed; + bool toolbarUpdaterRunning; + bool enableToolbarUpdate; + int guiLock; + + // Fields used to keep track of the application focus + bool appHasFocus; + Gtk.Window lastFocused; + DateTime focusCheckDelayTimeout = DateTime.MinValue; + + internal static readonly object CommandRouteTerminator = new object (); + + internal bool handlerFoundInMulticast; + + public CommandManager (): this (null) + { + } + + public CommandManager (Gtk.Window root) + { + rootWidget = root; + bindings = new KeyBindingManager (); + ActionCommand c = new ActionCommand (CommandSystemCommands.ToolbarList, "Toolbar List", null, null, ActionType.Check); + c.CommandArray = true; + RegisterCommand (c); + } + + public void LoadCommands (string addinPath) + { + AddinManager.AddExtensionNodeHandler (addinPath, OnExtensionChange); + } + + public void LoadKeyBindingSchemes (string addinPath) + { + KeyBindingService.LoadBindingsFromExtensionPath (addinPath); + } + + void OnExtensionChange (object s, ExtensionNodeEventArgs args) + { + if (args.Change == ExtensionChange.Add) { + if (args.ExtensionNode is CommandCodon) + RegisterCommand ((Command) args.ExtensionObject); + else + // It's a category node. Track changes in the category. + args.ExtensionNode.ExtensionNodeChanged += OnExtensionChange; + } + else { + if (args.ExtensionNode is CommandCodon) + UnregisterCommand ((Command)args.ExtensionObject); + else + args.ExtensionNode.ExtensionNodeChanged -= OnExtensionChange; + } + } + + public Gtk.MenuBar CreateMenuBar (string addinPath) + { + CommandEntrySet cset = CreateCommandEntrySet (addinPath); + return CreateMenuBar (addinPath, cset); + } + + public Gtk.Toolbar[] CreateToolbarSet (string addinPath) + { + ArrayList bars = new ArrayList (); + + CommandEntrySet cset = CreateCommandEntrySet (addinPath); + foreach (CommandEntry ce in cset) { + CommandEntrySet ces = ce as CommandEntrySet; + if (ces != null) + bars.Add (CreateToolbar (addinPath + "/" + ces.CommandId, ces)); + } + return (Gtk.Toolbar[]) bars.ToArray (typeof(Gtk.Toolbar)); + } + + public Gtk.Toolbar CreateToolbar (string addinPath) + { + CommandEntrySet cset = CreateCommandEntrySet (addinPath); + return CreateToolbar (addinPath, cset); + } + + public Gtk.Menu CreateMenu (string addinPath) + { + CommandEntrySet cset = CreateCommandEntrySet (addinPath); + return CreateMenu (cset); + } + + public void ShowContextMenu (string addinPath) + { + ShowContextMenu (CreateCommandEntrySet (addinPath)); + } + + public CommandEntrySet CreateCommandEntrySet (ExtensionContext ctx, string addinPath) + { + CommandEntrySet cset = new CommandEntrySet (); + object[] items = ctx.GetExtensionObjects (addinPath, false); + foreach (CommandEntry e in items) + cset.Add (e); + return cset; + } + + public CommandEntrySet CreateCommandEntrySet (string addinPath) + { + CommandEntrySet cset = new CommandEntrySet (); + object[] items = AddinManager.GetExtensionObjects (addinPath, false); + foreach (CommandEntry e in items) + cset.Add (e); + return cset; + } + + bool CanUseBinding (string mode, string binding) + { + if (!bindings.BindingExists (binding)) + return false; + + if (mode == null && bindings.ModeExists (binding)) { + // binding is a simple accel and is registered as a mode... modes take precedence + return false; + } + + return true; + } + + [GLib.ConnectBefore] + void OnKeyPressed (object o, Gtk.KeyPressEventArgs e) + { + bool complete; + string accel = KeyBindingManager.AccelFromKey (e.Event, out complete); + + if (!complete) { + // incomplete accel + e.RetVal = true; + return; + } + + string binding = KeyBindingManager.Binding (mode, accel); + List<Command> commands = null; + + if (CanUseBinding (mode, binding)) { + commands = bindings.Commands (binding); + e.RetVal = true; + mode = null; + } else if (mode != null && CanUseBinding (null, accel)) { + // fall back to accel + commands = bindings.Commands (accel); + e.RetVal = true; + mode = null; + } else if (bindings.ModeExists (accel)) { + e.RetVal = true; + mode = accel; + return; + } else { + e.RetVal = false; + mode = null; + return; + } + + for (int i = 0; i < commands.Count; i++) { + CommandInfo cinfo = GetCommandInfo (commands[i].Id, null); + if (cinfo.Enabled && cinfo.Visible && DispatchCommand (commands[i].Id)) + return; + } + + e.RetVal = false; + mode = null; + } + + public void SetRootWindow (Gtk.Window root) + { + if (rootWidget != null) + rootWidget.KeyPressEvent -= OnKeyPressed; + + rootWidget = root; + rootWidget.AddAccelGroup (AccelGroup); + RegisterTopWindow (rootWidget); + } + + void RegisterTopWindow (Gtk.Window win) + { + if (!topLevelWindows.ContainsKey (win)) { + topLevelWindows.Add (win, win); + win.KeyPressEvent += OnKeyPressed; + win.Destroyed += TopLevelDestroyed; + } + } + + void TopLevelDestroyed (object o, EventArgs args) + { + Gtk.Window w = (Gtk.Window) o; + w.Destroyed -= TopLevelDestroyed; + w.KeyPressEvent -= OnKeyPressed; + topLevelWindows.Remove (w); + if (w == lastFocused) + lastFocused = null; + } + + public void Dispose () + { + disposed = true; + bindings.Dispose (); + lastFocused = null; + } + + public void LockAll () + { + guiLock++; + if (guiLock == 1) { + foreach (ICommandBar toolbar in toolbars) + toolbar.SetEnabled (false); + } + } + + public void UnlockAll () + { + if (guiLock == 1) { + foreach (ICommandBar toolbar in toolbars) + toolbar.SetEnabled (true); + } + + if (guiLock > 0) + guiLock--; + } + + public bool EnableIdleUpdate { + get { return enableToolbarUpdate; } + set { + if (enableToolbarUpdate != value) { + if (value) { + if (toolbars.Count > 0 || visitors.Count > 0) { + if (!toolbarUpdaterRunning) { + GLib.Timeout.Add (500, new GLib.TimeoutHandler (UpdateStatus)); + toolbarUpdaterRunning = true; + } + } + } else { + toolbarUpdaterRunning = false; + } + enableToolbarUpdate = value; + } + } + } + + public void RegisterCommand (Command cmd) + { + KeyBindingService.StoreDefaultBinding (cmd); + KeyBindingService.LoadBinding (cmd); + + cmds[cmd.Id] = cmd; + bindings.RegisterCommand (cmd); + } + + public void UnregisterCommand (Command cmd) + { + bindings.UnregisterCommand (cmd); + cmds.Remove (cmd.Id); + } + + public void LoadUserBindings () + { + foreach (Command cmd in cmds.Values) + KeyBindingService.LoadBinding (cmd); + } + + public void RegisterGlobalHandler (object handler) + { + globalHandlerChain = CommandTargetChain.AddTarget (globalHandlerChain, handler); + } + + public void UnregisterGlobalHandler (object handler) + { + globalHandlerChain = CommandTargetChain.RemoveTarget (globalHandlerChain, handler); + } + + public void RegisterCommandTargetVisitor (ICommandTargetVisitor visitor) + { + visitors.Add (visitor); + if (enableToolbarUpdate && !toolbarUpdaterRunning) { + GLib.Timeout.Add (500, new GLib.TimeoutHandler (UpdateStatus)); + toolbarUpdaterRunning = true; + } + } + + public void UnregisterCommandTargetVisitor (ICommandTargetVisitor visitor) + { + visitors.Remove (visitor); + } + + public Command GetCommand (object cmdId) + { + // Include the type name when converting enum members to ids. + cmdId = ToCommandId (cmdId); + + Command cmd; + if (cmds.TryGetValue (cmdId, out cmd)) + return cmd; + else + return null; + } + + public IEnumerable<Command> GetCommands () + { + return cmds.Values; + } + + public ActionCommand GetActionCommand (object cmdId) + { + return GetCommand (cmdId) as ActionCommand; + } + + public Gtk.MenuBar CreateMenuBar (string name, CommandEntrySet entrySet) + { + Gtk.MenuBar topMenu = new CommandMenuBar (this); + foreach (CommandEntry entry in entrySet) { + Gtk.MenuItem mi = entry.CreateMenuItem (this); + CustomItem ci = mi.Child as CustomItem; + if (ci != null) + ci.SetMenuStyle (topMenu); + topMenu.Append (mi); + } + return topMenu; + } + +/* public Gtk.Toolbar CreateToolbar (CommandEntrySet entrySet) + { + return CreateToolbar ("", entrySet); + } + +*/ + public Gtk.Menu CreateMenu (CommandEntrySet entrySet, CommandMenu menu) + { + foreach (CommandEntry entry in entrySet) { + Gtk.MenuItem mi = entry.CreateMenuItem (this); + CustomItem ci = mi.Child as CustomItem; + if (ci != null) + ci.SetMenuStyle (menu); + menu.Append (mi); + } + return menu; + } + + public Gtk.Menu CreateMenu (CommandEntrySet entrySet) + { + return CreateMenu (entrySet, new CommandMenu (this)); + } + + public void InsertOptions (Gtk.Menu menu, CommandEntrySet entrySet, int index) + { + foreach (CommandEntry entry in entrySet) { + Gtk.MenuItem item = entry.CreateMenuItem (this); + CustomItem ci = item.Child as CustomItem; + if (ci != null) + ci.SetMenuStyle (menu); + int n = menu.Children.Length; + menu.Insert (item, index); + if (item is ICommandUserItem) + ((ICommandUserItem)item).Update (null); + else + item.Show (); + index += menu.Children.Length - n; + } + } + + public void ShowContextMenu (CommandEntrySet entrySet) + { + ShowContextMenu (entrySet, null); + } + + public void ShowContextMenu (CommandEntrySet entrySet, object initialTarget) + { + CommandMenu menu = (CommandMenu) CreateMenu (entrySet); + ShowContextMenu (menu, initialTarget); + } + + public void ShowContextMenu (Gtk.Menu menu) + { + menu.Popup (null, null, null, 0, Gtk.Global.CurrentEventTime); + } + + public void ShowContextMenu (Gtk.Menu menu, object initialCommandTarget) + { + if (menu is CommandMenu) { + ((CommandMenu)menu).InitialCommandTarget = initialCommandTarget; + } + ShowContextMenu (menu); + } + + public Gtk.Toolbar CreateToolbar (CommandEntrySet entrySet) + { + return CreateToolbar ("", entrySet, null); + } + + public Gtk.Toolbar CreateToolbar (CommandEntrySet entrySet, object initialTarget) + { + return CreateToolbar ("", entrySet, initialTarget); + } + + public Gtk.Toolbar CreateToolbar (string id, CommandEntrySet entrySet) + { + return CreateToolbar (id, entrySet, null); + } + + public Gtk.Toolbar CreateToolbar (string id, CommandEntrySet entrySet, object initialTarget) + { + CommandToolbar toolbar = new CommandToolbar (this, id, entrySet.Name); + toolbar.InitialCommandTarget = initialTarget; + + foreach (CommandEntry entry in entrySet) { + Gtk.ToolItem ti = entry.CreateToolItem (this); + CustomItem ci = ti.Child as CustomItem; + if (ci != null) + ci.SetToolbarStyle (toolbar); + toolbar.Add (ti); + } + ToolbarTracker tt = new ToolbarTracker (); + tt.Track (toolbar); + return toolbar; + } + + public bool DispatchCommand (object commandId) + { + return DispatchCommand (commandId, null, null); + } + + public bool DispatchCommand (object commandId, object dataItem) + { + return DispatchCommand (commandId, dataItem, null); + } + + public bool DispatchCommand (object commandId, object dataItem, object initialTarget) + { + if (guiLock > 0) + return false; + + List<HandlerCallback> handlers = new List<HandlerCallback> (); + ActionCommand cmd = null; + try { + cmd = GetActionCommand (commandId); + if (cmd == null) + return false; + + object cmdTarget = GetFirstCommandTarget (initialTarget); + CommandInfo info = new CommandInfo (cmd); + + while (cmdTarget != null) + { + HandlerTypeInfo typeInfo = GetTypeHandlerInfo (cmdTarget); + + bool bypass = false; + + CommandUpdaterInfo cui = typeInfo.GetCommandUpdater (commandId); + if (cui != null) { + if (cmd.CommandArray) { + // Make sure that the option is still active + CommandArrayInfo ainfo = new CommandArrayInfo (info); + cui.Run (cmdTarget, ainfo); + if (!ainfo.Bypass) { + bool found = false; + foreach (CommandInfo ci in ainfo) { + if (dataItem == ci.DataItem || Object.Equals (dataItem, ci.DataItem)) { + found = true; + break; + } + } + + if (!found) + return false; + } else + bypass = true; + } else { + info.Bypass = false; + cui.Run (cmdTarget, info); + bypass = info.Bypass; + + if (!bypass && (!info.Enabled || !info.Visible)) + return false; + } + } + + if (!bypass) { + CommandHandlerInfo chi = typeInfo.GetCommandHandler (commandId); + if (chi != null) { + object localTarget = cmdTarget; + if (cmd.CommandArray) { + handlers.Add (delegate { + chi.Run (localTarget, cmd, dataItem); + }); + } + else { + handlers.Add (delegate { + chi.Run (localTarget, cmd); + }); + } + handlerFoundInMulticast = true; + cmdTarget = NextMulticastTarget (); + if (cmdTarget == null) + break; + else + continue; + } + } + cmdTarget = GetNextCommandTarget (cmdTarget); + } + + if (handlers.Count > 0) { + foreach (HandlerCallback c in handlers) + c (); + UpdateToolbars (); + return true; + } + + if (cmd.DispatchCommand (dataItem)) { + UpdateToolbars (); + return true; + } + } + catch (Exception ex) { + string name = (cmd != null && cmd.Text != null && cmd.Text.Length > 0) ? cmd.Text : commandId.ToString (); + name = name.Replace ("_",""); + ReportError (commandId, "Error while executing command: " + name, ex); + } + return false; + } + + public CommandInfo GetCommandInfo (object commandId, object initialTarget) + { + ActionCommand cmd = GetActionCommand (commandId); + if (cmd == null) + throw new InvalidOperationException ("Invalid action command id: " + commandId); + + NotifyCommandTargetScanStarted (); + CommandInfo info = new CommandInfo (cmd); + + try { + object cmdTarget = GetFirstCommandTarget (initialTarget); + bool multiCastEnabled = true; + bool multiCastVisible = false; + + while (cmdTarget != null) + { + HandlerTypeInfo typeInfo = GetTypeHandlerInfo (cmdTarget); + CommandUpdaterInfo cui = typeInfo.GetCommandUpdater (commandId); + + bool bypass = false; + bool handlerFound = false; + + if (cui != null) { + if (cmd.CommandArray) { + info.ArrayInfo = new CommandArrayInfo (info); + cui.Run (cmdTarget, info.ArrayInfo); + if (!info.ArrayInfo.Bypass) { + if (guiLock > 0) + info.Enabled = false; + handlerFound = true; + } + } + else { + info.Bypass = false; + cui.Run (cmdTarget, info); + if (!info.Bypass) { + if (guiLock > 0) + info.Enabled = false; + handlerFound = true; + } + } + if (!handlerFound) + bypass = true; + } + + if (handlerFound) { + handlerFoundInMulticast = true; + if (!info.Enabled || !info.Visible) + multiCastEnabled = false; + if (info.Visible) + multiCastVisible = true; + cmdTarget = NextMulticastTarget (); + if (cmdTarget == null) { + if (!multiCastEnabled) + info.Enabled = false; + if (multiCastVisible) + info.Visible = true; + return info; + } + continue; + } + else if (!bypass && typeInfo.GetCommandHandler (commandId) != null) { + info.Enabled = guiLock == 0; + info.Visible = true; + return info; + } + + cmdTarget = GetNextCommandTarget (cmdTarget); + } + + cmd.UpdateCommandInfo (info); + } + catch (Exception ex) { + if (!commandUpdateErrors.Contains (commandId)) { + commandUpdateErrors.Add (commandId); + ReportError (commandId, "Error while updating status of command: " + commandId, ex); + } + info.Enabled = false; + info.Visible = true; + } finally { + NotifyCommandTargetScanFinished (); + } + + if (guiLock > 0) + info.Enabled = false; + return info; + } + + public object VisitCommandTargets (ICommandTargetVisitor visitor, object initialTarget) + { + object cmdTarget = GetFirstCommandTarget (initialTarget); + + while (cmdTarget != null) + { + if (visitor.Visit (cmdTarget)) + return cmdTarget; + + cmdTarget = GetNextCommandTarget (cmdTarget); + } + + visitor.Visit (null); + return null; + } + + internal bool DispatchCommandFromAccel (object commandId, object dataItem, object initialTarget) + { + // Dispatches a command that has been fired by an accelerator. + // The difference from a normal dispatch is that there may + // be several commands bound to the same accelerator, and in + // this case it will execute the one that is enabled. + + // If the original key has been modified + // by a CommandUpdate handler, it won't work. That's a limitation, + // but checking all possible commands would be too slow. + + Command cmd = GetCommand (commandId); + if (cmd == null) + return false; + + string accel = cmd.AccelKey; + if (accel == null) + return DispatchCommand (commandId, dataItem, initialTarget); + + List<Command> list = bindings.Commands (accel); + if (list == null || list.Count == 1) + // The command is not overloaded, so it can be handled normally. + return DispatchCommand (commandId, dataItem, initialTarget); + + // Get the accelerator used to fire the command and make sure it has not changed. + CommandInfo accelInfo = GetCommandInfo (commandId, initialTarget); + bool res = DispatchCommand (commandId, accelInfo.DataItem, initialTarget); + + // If the accelerator has changed, we can't handle overloading. + if (res || accel != accelInfo.AccelKey) + return res; + + // Execution failed. Now try to execute alternate commands + // bound to the same key. + + for (int i = 0; i < list.Count; i++) { + if (list[i].Id == commandId) // already handled above. + continue; + + CommandInfo cinfo = GetCommandInfo (list[i].Id, initialTarget); + if (cinfo.AccelKey != accel) // Key changed by a handler, just ignore the command. + continue; + + if (DispatchCommand (list[i].Id, cinfo.DataItem, initialTarget)) + return true; + } + + return false; + } + + internal Gtk.AccelGroup AccelGroup { + get { + if (accelGroup == null) { + accelGroup = new Gtk.AccelGroup (); + } + return accelGroup; + } + } + + internal void NotifySelected (CommandInfo cmdInfo) + { + if (CommandSelected != null) { + CommandSelectedEventArgs args = new CommandSelectedEventArgs (cmdInfo); + CommandSelected (this, args); + } + } + + internal void NotifyDeselected () + { + if (CommandDeselected != null) + CommandDeselected (this, EventArgs.Empty); + } + + HandlerTypeInfo GetTypeHandlerInfo (object cmdTarget) + { + HandlerTypeInfo typeInfo = (HandlerTypeInfo) handlerInfo [cmdTarget.GetType ()]; + if (typeInfo != null) return typeInfo; + + Type type = cmdTarget.GetType (); + typeInfo = new HandlerTypeInfo (); + + List<CommandHandlerInfo> handlers = new List<CommandHandlerInfo> (); + List<CommandUpdaterInfo> updaters = new List<CommandUpdaterInfo> (); + + Type curType = type; + while (curType != null && curType.Assembly != typeof(Gtk.Widget).Assembly && curType.Assembly != typeof(object).Assembly) { + MethodInfo[] methods = curType.GetMethods (BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.DeclaredOnly); + foreach (MethodInfo method in methods) { + + ICommandUpdateHandler customHandlerChain = null; + ICommandArrayUpdateHandler customArrayHandlerChain = null; + ICommandTargetHandler customTargetHandlerChain = null; + ICommandArrayTargetHandler customArrayTargetHandlerChain = null; + List<CommandHandlerInfo> methodHandlers = new List<CommandHandlerInfo> (); + + foreach (object attr in method.GetCustomAttributes (true)) { + if (attr is CommandHandlerAttribute) + methodHandlers.Add (new CommandHandlerInfo (method, (CommandHandlerAttribute) attr)); + else if (attr is CommandUpdateHandlerAttribute) + AddUpdater (updaters, method, (CommandUpdateHandlerAttribute) attr); + else { + customHandlerChain = ChainHandler (customHandlerChain, attr); + customArrayHandlerChain = ChainHandler (customArrayHandlerChain, attr); + customTargetHandlerChain = ChainHandler (customTargetHandlerChain, attr); + customArrayTargetHandlerChain = ChainHandler (customArrayTargetHandlerChain, attr); + } + } + + foreach (object attr in type.GetCustomAttributes (true)) { + customHandlerChain = ChainHandler (customHandlerChain, attr); + customArrayHandlerChain = ChainHandler (customArrayHandlerChain, attr); + customTargetHandlerChain = ChainHandler (customTargetHandlerChain, attr); + customArrayTargetHandlerChain = ChainHandler (customArrayTargetHandlerChain, attr); + } + + if (methodHandlers.Count > 0) { + if (customHandlerChain != null || customArrayHandlerChain != null) { + // There are custom handlers. Create update handlers for all commands + // that the method handles so the custom update handlers can be chained + foreach (CommandHandlerInfo ci in methodHandlers) { + CommandUpdaterInfo c = AddUpdateHandler (updaters, ci.CommandId); + c.AddCustomHandlers (customHandlerChain, customArrayHandlerChain); + } + } + if (customTargetHandlerChain != null || customArrayTargetHandlerChain != null) { + foreach (CommandHandlerInfo ci in methodHandlers) + ci.AddCustomHandlers (customTargetHandlerChain, customArrayTargetHandlerChain); + } + } + handlers.AddRange (methodHandlers); + } + curType = curType.BaseType; + } + + if (handlers.Count > 0) + typeInfo.CommandHandlers = handlers.ToArray (); + if (updaters.Count > 0) + typeInfo.CommandUpdaters = updaters.ToArray (); + + handlerInfo [type] = typeInfo; + return typeInfo; + } + + CommandUpdaterInfo AddUpdateHandler (List<CommandUpdaterInfo> methodUpdaters, object cmdId) + { + foreach (CommandUpdaterInfo ci in methodUpdaters) { + if (ci.CommandId.Equals (cmdId)) + return ci; + } + // Not found, it needs to be added + CommandUpdaterInfo cinfo = new CommandUpdaterInfo (cmdId); + methodUpdaters.Add (cinfo); + return cinfo; + } + + void AddUpdater (List<CommandUpdaterInfo> methodUpdaters, MethodInfo method, CommandUpdateHandlerAttribute attr) + { + foreach (CommandUpdaterInfo ci in methodUpdaters) { + if (ci.CommandId.Equals (CommandManager.ToCommandId (attr.CommandId))) { + ci.Init (method, attr); + return; + } + } + // Not found, it needs to be added + CommandUpdaterInfo cinfo = new CommandUpdaterInfo (method, attr); + methodUpdaters.Add (cinfo); + } + + ICommandArrayUpdateHandler ChainHandler (ICommandArrayUpdateHandler chain, object attr) + { + ICommandArrayUpdateHandler h = attr as ICommandArrayUpdateHandler; + if (h == null) return chain; + h.Next = chain ?? DefaultCommandHandler.Instance; + return h; + } + + ICommandUpdateHandler ChainHandler (ICommandUpdateHandler chain, object attr) + { + ICommandUpdateHandler h = attr as ICommandUpdateHandler; + if (h == null) return chain; + h.Next = chain ?? DefaultCommandHandler.Instance; + return h; + } + + ICommandTargetHandler ChainHandler (ICommandTargetHandler chain, object attr) + { + ICommandTargetHandler h = attr as ICommandTargetHandler; + if (h == null) return chain; + h.Next = chain ?? DefaultCommandHandler.Instance; + return h; + } + + ICommandArrayTargetHandler ChainHandler (ICommandArrayTargetHandler chain, object attr) + { + ICommandArrayTargetHandler h = attr as ICommandArrayTargetHandler; + if (h == null) return chain; + h.Next = chain ?? DefaultCommandHandler.Instance; + return h; + } + + object GetFirstCommandTarget (object initialTarget) + { + delegatorStack.Clear (); + visitedTargets.Clear (); + handlerFoundInMulticast = false; + object cmdTarget; + if (initialTarget != null) + cmdTarget = initialTarget; + else { + cmdTarget = GetActiveWidget (rootWidget); + if (cmdTarget == null) { + cmdTarget = globalHandlerChain; + } + } + visitedTargets.Add (cmdTarget); + return cmdTarget; + } + + object GetNextCommandTarget (object cmdTarget) + { + if (cmdTarget is IMultiCastCommandRouter) + cmdTarget = new MultiCastDelegator (this, (IMultiCastCommandRouter)cmdTarget); + + if (cmdTarget is ICommandDelegatorRouter) { + object oldCmdTarget = cmdTarget; + cmdTarget = ((ICommandDelegatorRouter)oldCmdTarget).GetDelegatedCommandTarget (); + if (cmdTarget != null) + delegatorStack.Push (oldCmdTarget); + else + cmdTarget = ((ICommandDelegatorRouter)oldCmdTarget).GetNextCommandTarget (); + } + else if (cmdTarget is ICommandRouter) + cmdTarget = ((ICommandRouter)cmdTarget).GetNextCommandTarget (); + else if (cmdTarget is Gtk.Widget) + cmdTarget = ((Gtk.Widget)cmdTarget).Parent; + else + cmdTarget = null; + + if (cmdTarget == null || !visitedTargets.Add (cmdTarget)) { + if (delegatorStack.Count > 0) { + ICommandDelegatorRouter del = (ICommandDelegatorRouter) delegatorStack.Pop (); + cmdTarget = del.GetNextCommandTarget (); + if (cmdTarget == CommandManager.CommandRouteTerminator) + return null; + if (cmdTarget != null) + return cmdTarget; + } + return globalHandlerChain; + } else + return cmdTarget; + } + + internal object NextMulticastTarget () + { + while (delegatorStack.Count > 0) { + MultiCastDelegator del = delegatorStack.Pop () as MultiCastDelegator; + if (del != null) { + object cmdTarget = GetNextCommandTarget (del); + return cmdTarget == globalHandlerChain ? null : cmdTarget; + } + } + return null; + } + + Gtk.Widget GetActiveWidget (Gtk.Window win) + { + Gtk.Window[] wins = Gtk.Window.ListToplevels (); + + bool hasFocus = false; + bool lastFocusedExists = lastFocused == null; + Gtk.Window newFocused = null; + foreach (Gtk.Window w in wins) { + if (w.Visible) { + if (w.HasToplevelFocus) { + hasFocus = true; + newFocused = w; + } + if (w.IsActive && w.Type == Gtk.WindowType.Toplevel && !(w is Gtk.Dialog)) { + win = w; + break; + } + if (lastFocused == w) { + lastFocusedExists = true; + } + } + } + + lastFocused = newFocused; + UpdateAppFocusStatus (hasFocus, lastFocusedExists); + + if (win != null) { + RegisterTopWindow (win); + Gtk.Widget widget = win; + while (widget is Gtk.Container) { + Gtk.Widget child = ((Gtk.Container)widget).FocusChild; + if (child != null) + widget = child; + else + break; + } + return widget; + } + return win; + } + + bool UpdateStatus () + { + if (!disposed) + UpdateToolbars (); + else + toolbarUpdaterRunning = false; + + return toolbarUpdaterRunning; + } + + public void RegisterCommandBar (ICommandBar commandBar) + { + if (toolbars.Contains (commandBar)) + return; + + toolbars.Add (commandBar); + if (enableToolbarUpdate && !toolbarUpdaterRunning) { + GLib.Timeout.Add (500, new GLib.TimeoutHandler (UpdateStatus)); + toolbarUpdaterRunning = true; + } + commandBar.SetEnabled (guiLock == 0); + + object activeWidget = GetActiveWidget (rootWidget); + commandBar.Update (activeWidget); + } + + public void UnregisterCommandBar (ICommandBar commandBar) + { + toolbars.Remove (commandBar); + } + + void UpdateToolbars () + { + // This might get called after the app has exited, e.g. after executing the quit command + // It then queries widgets, which resurrects widget wrappers, which breaks on managed widgets + if (this.disposed) + return; + + object activeWidget = GetActiveWidget (rootWidget); + foreach (ICommandBar toolbar in toolbars) { + toolbar.Update (activeWidget); + } + foreach (ICommandTargetVisitor v in visitors) + VisitCommandTargets (v, null); + } + + void UpdateAppFocusStatus (bool hasFocus, bool lastFocusedExists) + { + if (hasFocus != appHasFocus) { + // The last focused window has been destroyed. Wait a few ms since another app's window + // may gain focus again + DateTime now = DateTime.Now; + if (now < focusCheckDelayTimeout) + return; + if (!hasFocus && !lastFocusedExists) { + focusCheckDelayTimeout = now.AddMilliseconds (100); + return; + } + focusCheckDelayTimeout = DateTime.MinValue; + + appHasFocus = hasFocus; + if (appHasFocus) { + if (ApplicationFocusIn != null) + ApplicationFocusIn (this, EventArgs.Empty); + } else { + if (ApplicationFocusOut != null) + ApplicationFocusOut (this, EventArgs.Empty); + } + } + } + + public void ReportError (object commandId, string message, Exception ex) + { + if (CommandError != null) { + CommandErrorArgs args = new CommandErrorArgs (commandId, message, ex); + CommandError (this, args); + } + } + + internal static object ToCommandId (object ob) + { + // Include the type name when converting enum members to ids. + if (ob == null) + return null; + else if (ob.GetType ().IsEnum) + return ob.GetType ().FullName + "." + ob; + else + return ob; + } + + void NotifyCommandTargetScanStarted () + { + if (CommandTargetScanStarted != null) + CommandTargetScanStarted (this, EventArgs.Empty); + } + + void NotifyCommandTargetScanFinished () + { + if (CommandTargetScanFinished != null) + CommandTargetScanFinished (this, EventArgs.Empty); + } + + public event CommandErrorHandler CommandError; + public event EventHandler<CommandSelectedEventArgs> CommandSelected; + public event EventHandler CommandDeselected; + public event EventHandler ApplicationFocusIn; // Fired when the application gets the focus + public event EventHandler ApplicationFocusOut; // Fired when the application loses the focus + public event EventHandler CommandTargetScanStarted; + public event EventHandler CommandTargetScanFinished; + } + + internal class HandlerTypeInfo + { + public CommandHandlerInfo[] CommandHandlers; + public CommandUpdaterInfo[] CommandUpdaters; + + public CommandHandlerInfo GetCommandHandler (object commandId) + { + if (CommandHandlers == null) return null; + foreach (CommandHandlerInfo cui in CommandHandlers) + if (cui.CommandId.Equals (commandId)) + return cui; + return null; + } + + public CommandUpdaterInfo GetCommandUpdater (object commandId) + { + if (CommandUpdaters == null) return null; + foreach (CommandUpdaterInfo cui in CommandUpdaters) + if (cui.CommandId.Equals (commandId)) + return cui; + return null; + } + } + + + internal class CommandMethodInfo + { + public object CommandId; + protected MethodInfo Method; + + public CommandMethodInfo (MethodInfo method, CommandMethodAttribute attr) + { + Init (method, attr); + } + + protected void Init (MethodInfo method, CommandMethodAttribute attr) + { + // Don't assign the method if there is already one assigned (maybe from a subclass) + if (this.Method == null) { + this.Method = method; + CommandId = CommandManager.ToCommandId (attr.CommandId); + } + } + + public CommandMethodInfo (object commandId) + { + CommandId = CommandManager.ToCommandId (commandId); + } + } + + internal class CommandHandlerInfo: CommandMethodInfo + { + ICommandTargetHandler customHandlerChain; + ICommandArrayTargetHandler customArrayHandlerChain; + + public CommandHandlerInfo (MethodInfo method, CommandHandlerAttribute attr): base (method, attr) + { + ParameterInfo[] pars = method.GetParameters (); + if (pars.Length > 1) + throw new InvalidOperationException ("Invalid signature for command handler: " + method.DeclaringType + "." + method.Name + "()"); + } + + public void Run (object cmdTarget, Command cmd) + { + if (customHandlerChain != null) { + cmd.HandlerData = Method; + customHandlerChain.Run (cmdTarget, cmd); + } + else + Method.Invoke (cmdTarget, null); + } + + public void Run (object cmdTarget, Command cmd, object dataItem) + { + if (customArrayHandlerChain != null) { + cmd.HandlerData = Method; + customArrayHandlerChain.Run (cmdTarget, cmd, dataItem); + } + else + Method.Invoke (cmdTarget, new object[] {dataItem}); + } + + public void AddCustomHandlers (ICommandTargetHandler handlerChain, ICommandArrayTargetHandler arrayHandlerChain) + { + this.customHandlerChain = handlerChain; + this.customArrayHandlerChain = arrayHandlerChain; + } + } + + internal class CommandUpdaterInfo: CommandMethodInfo + { + ICommandUpdateHandler customHandlerChain; + ICommandArrayUpdateHandler customArrayHandlerChain; + + bool isArray; + + public CommandUpdaterInfo (object commandId): base (commandId) + { + } + + public CommandUpdaterInfo (MethodInfo method, CommandUpdateHandlerAttribute attr): base (method, attr) + { + Init (method, attr); + } + + public void Init (MethodInfo method, CommandUpdateHandlerAttribute attr) + { + base.Init (method, attr); + ParameterInfo[] pars = method.GetParameters (); + if (pars.Length == 1) { + Type t = pars[0].ParameterType; + + if (t == typeof(CommandArrayInfo)) { + isArray = true; + return; + } else if (t == typeof(CommandInfo)) + return; + } + throw new InvalidOperationException ("Invalid signature for command update handler: " + method.DeclaringType + "." + method.Name + "()"); + } + + public void AddCustomHandlers (ICommandUpdateHandler handlerChain, ICommandArrayUpdateHandler arrayHandlerChain) + { + this.customHandlerChain = handlerChain; + this.customArrayHandlerChain = arrayHandlerChain; + } + + public void Run (object cmdTarget, CommandInfo info) + { + if (customHandlerChain != null) { + info.UpdateHandlerData = Method; + customHandlerChain.CommandUpdate (cmdTarget, info); + } else { + if (Method == null) + throw new InvalidOperationException ("Invalid custom update handler. An implementation of ICommandUpdateHandler was expected."); + if (isArray) + throw new InvalidOperationException ("Invalid signature for command update handler: " + Method.DeclaringType + "." + Method.Name + "()"); + Method.Invoke (cmdTarget, new object[] {info} ); + } + } + + public void Run (object cmdTarget, CommandArrayInfo info) + { + if (customArrayHandlerChain != null) { + info.UpdateHandlerData = Method; + customArrayHandlerChain.CommandUpdate (cmdTarget, info); + } else { + if (Method == null) + throw new InvalidOperationException ("Invalid custom update handler. An implementation of ICommandArrayUpdateHandler was expected."); + if (!isArray) + throw new InvalidOperationException ("Invalid signature for command update handler: " + Method.DeclaringType + "." + Method.Name + "()"); + Method.Invoke (cmdTarget, new object[] {info} ); + } + } + } + + class DefaultCommandHandler: ICommandUpdateHandler, ICommandArrayUpdateHandler, ICommandTargetHandler, ICommandArrayTargetHandler + { + public static DefaultCommandHandler Instance = new DefaultCommandHandler (); + + public void CommandUpdate (object target, CommandInfo info) + { + MethodInfo mi = (MethodInfo) info.UpdateHandlerData; + if (mi != null) + mi.Invoke (target, new object[] {info} ); + } + + public void CommandUpdate (object target, CommandArrayInfo info) + { + MethodInfo mi = (MethodInfo) info.UpdateHandlerData; + if (mi != null) + mi.Invoke (target, new object[] {info} ); + } + + public void Run (object target, Command cmd) + { + MethodInfo mi = (MethodInfo) cmd.HandlerData; + if (mi != null) + mi.Invoke (target, new object[0] ); + } + + public void Run (object target, Command cmd, object data) + { + MethodInfo mi = (MethodInfo) cmd.HandlerData; + if (mi != null) + mi.Invoke (target, new object[] {data} ); + } + + ICommandArrayTargetHandler ICommandArrayTargetHandler.Next { + get { + return null; + } + set { + } + } + + ICommandTargetHandler ICommandTargetHandler.Next { + get { + return null; + } + set { + } + } + + ICommandArrayUpdateHandler ICommandArrayUpdateHandler.Next { + get { + // Last one in the chain + return null; + } + set { + } + } + + public ICommandUpdateHandler Next { + get { + // Last one in the chain + return null; + } + set { + } + } + } + + internal class ToolbarTracker + { + Gtk.IconSize lastSize; + + public void Track (Gtk.Toolbar toolbar) + { + lastSize = toolbar.IconSize; + toolbar.AddNotification (OnToolbarPropChanged); + } + + void OnToolbarPropChanged (object ob, GLib.NotifyArgs args) + { + Gtk.Toolbar t = (Gtk.Toolbar) ob; + if (lastSize != t.IconSize || args.Property == "orientation" || args.Property == "toolbar-style") + UpdateCustomItems (t); + lastSize = t.IconSize; + } + + void UpdateCustomItems (Gtk.Toolbar t) + { + foreach (Gtk.ToolItem ti in t.Children) { + CustomItem ci = ti.Child as CustomItem; + if (ci != null) + ci.SetToolbarStyle (t); + } + } + } + + class MultiCastDelegator: ICommandDelegatorRouter + { + IEnumerator enumerator; + object nextTarget; + CommandManager manager; + bool done; + + public MultiCastDelegator (CommandManager manager, IMultiCastCommandRouter mcr) + { + this.manager = manager; + enumerator = mcr.GetCommandTargets ().GetEnumerator (); + } + + public object GetNextCommandTarget () + { + if (nextTarget != null) + return this; + else { + if (manager.handlerFoundInMulticast) + return manager.NextMulticastTarget (); + else + return null; + } + } + + public object GetDelegatedCommandTarget () + { + object currentTarget; + if (done) + return null; + if (nextTarget != null) { + currentTarget = nextTarget; + nextTarget = null; + } else { + if (enumerator.MoveNext ()) + currentTarget = enumerator.Current; + else + return null; + } + + if (enumerator.MoveNext ()) + nextTarget = enumerator.Current; + else { + done = true; + nextTarget = null; + } + + return currentTarget; + } + } + + class CommandTargetChain: ICommandDelegatorRouter + { + object target; + internal CommandTargetChain Next; + + public CommandTargetChain (object target) + { + this.target = target; + } + + public object GetNextCommandTarget () + { + if (Next == null) + return CommandManager.CommandRouteTerminator; + else + return Next; + } + + public object GetDelegatedCommandTarget () + { + return target; + } + + public static CommandTargetChain RemoveTarget (CommandTargetChain chain, object target) + { + if (chain == null) + return null; + if (chain.target == target) + return chain.Next; + else if (chain.Next != null) + chain.Next = CommandTargetChain.RemoveTarget (chain.Next, target); + return chain; + } + + public static CommandTargetChain AddTarget (CommandTargetChain chain, object target) + { + if (chain == null) + return new CommandTargetChain (target); + else { + chain.Next = AddTarget (chain.Next, target); + return chain; + } + } + } + + delegate void HandlerCallback (); +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenu.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenu.cs new file mode 100644 index 0000000000..c921b21e86 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenu.cs @@ -0,0 +1,145 @@ +// +// CommandMenu.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CommandMenu: Gtk.Menu + { + CommandEntrySet commandEntrySet; + CommandManager manager; + object initialCommandTarget; + + public CommandMenu (CommandManager manager) + { + this.manager = manager; + this.AccelGroup = manager.AccelGroup; + } + + public CommandMenu (CommandManager manager, CommandEntrySet commandEntrySet) : this (manager) + { + this.commandEntrySet = commandEntrySet; + } + + + public object InitialCommandTarget { + get { return initialCommandTarget; } + set { initialCommandTarget = value; } + } + + internal CommandManager CommandManager { + get { + if (manager == null) { + Gtk.Widget w = Parent; + while (w != null && !(w is CommandMenu) && !(w is CommandMenuBar)) { + w = w.Parent; + } + if (w is CommandMenu) + manager = ((CommandMenu)w).CommandManager; + else if (w is CommandMenuBar) + manager = ((CommandMenuBar)w).CommandManager; + else + throw new InvalidOperationException ("Menu not bound to a CommandManager."); + } + return manager; + } + } + + protected CommandMenu (IntPtr ptr): base (ptr) + { + } + + protected override void OnShown () + { + base.OnShown (); + Update (); + } + protected override void OnRealized () + { + base.OnRealized (); + if (commandEntrySet != null) { + manager.CreateMenu (commandEntrySet, this); + Update (); + commandEntrySet = null; + } + } + + internal void Update () + { + foreach (Gtk.Widget item in Children) { + if (item is ICommandUserItem) + ((ICommandUserItem)item).Update (initialCommandTarget); + else if (item is Gtk.MenuItem) { + Gtk.MenuItem mitem = (Gtk.MenuItem) item; + CommandMenu men = mitem.Submenu as CommandMenu; + if (men != null) + men.InitialCommandTarget = initialCommandTarget; + item.Show (); + if (item is AutoHideMenuItem) { + men.Update (); + if (!((AutoHideMenuItem)item).HasVisibleChildren) + item.Hide (); + } + } + else + item.Show (); + } + + // After updating the menu, hide the separators which don't actually + // separate items. + bool prevWasItem = false; + Gtk.Widget lastSeparator = null; + foreach (Gtk.Widget item in Children) { + if (item is Gtk.SeparatorMenuItem) { + if (!prevWasItem) + item.Hide (); + else { + prevWasItem = false; + lastSeparator = item; + } + } else if (item.Visible) + prevWasItem = true; + } + if (!prevWasItem && lastSeparator != null) + lastSeparator.Hide (); + } + + protected override void OnHidden () + { + base.OnHidden (); + + // Make sure the accelerators allways work for this item + // while the menu is hidden + foreach (Gtk.MenuItem item in Children) { + item.Sensitive = true; + item.Visible = true; + } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenuBar.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenuBar.cs new file mode 100644 index 0000000000..e5e4724b7f --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenuBar.cs @@ -0,0 +1,46 @@ +// +// CommandMenuBar.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CommandMenuBar: Gtk.MenuBar + { + CommandManager manager; + + public CommandMenuBar (CommandManager manager) + { + this.manager = manager; + } + + internal CommandManager CommandManager { + get { return manager; } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenuItem.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenuItem.cs new file mode 100644 index 0000000000..131cb88243 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandMenuItem.cs @@ -0,0 +1,239 @@ +// +// CommandMenuItem.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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.Collections; +using MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public class CommandMenuItem: Gtk.ImageMenuItem, ICommandMenuItem + { + CommandManager commandManager; + object commandId; + bool isArray; + bool isArrayItem; + object arrayDataItem; + ArrayList itemArray; + IconId lastIcon; + string overrideLabel; + bool wasButtonActivation; + object initialTarget; + bool disabledVisible = true; + CommandInfo lastCmdInfo; + + public CommandMenuItem (object commandId, CommandManager commandManager, string overrideLabel, bool disabledVisible): base ("") + { + this.commandId = commandId; + this.commandManager = commandManager; + this.overrideLabel = overrideLabel; + this.disabledVisible = disabledVisible; + ActionCommand cmd = commandManager.GetCommand (commandId) as ActionCommand; + if (cmd != null) + isArray = cmd.CommandArray; + } + + public CommandMenuItem (object commandId, CommandManager commandManager): this (commandId, commandManager, null, true) + { + } + + void ICommandUserItem.Update (object initialTarget) + { + if (commandManager != null && !isArrayItem) { + CommandInfo cinfo = commandManager.GetCommandInfo (commandId, initialTarget); + this.initialTarget = initialTarget; + Update (cinfo); + } + } + + void ICommandMenuItem.SetUpdateInfo (CommandInfo cmdInfo, object initialTarget) + { + isArrayItem = true; + this.initialTarget = initialTarget; + arrayDataItem = cmdInfo.DataItem; + Update (cmdInfo); + } + + protected override void OnParentSet (Gtk.Widget parent) + { + base.OnParentSet (parent); + if (Parent == null) + return; + + ((ICommandUserItem)this).Update (null); + + if (!isArrayItem) { + // Make sure the accelerators always work for this item + // while the menu is hidden + Sensitive = true; + Visible = true; + } + } + + protected override bool OnButtonReleaseEvent (Gdk.EventButton ev) + { + wasButtonActivation = true; + return base.OnButtonReleaseEvent (ev); + } + + protected override void OnActivated () + { + base.OnActivated (); + + if (commandManager == null) + throw new InvalidOperationException (); + + if (!wasButtonActivation) { + // It's being activated by an accelerator. + if (Submenu == null) + commandManager.DispatchCommandFromAccel (commandId, arrayDataItem, initialTarget); + } else { + wasButtonActivation = false; + commandManager.DispatchCommand (commandId, arrayDataItem, initialTarget); + } + } + + protected override void OnSelected () + { + if (commandManager != null) + commandManager.NotifySelected (lastCmdInfo); + base.OnSelected (); + } + + protected override void OnDeselected () + { + if (commandManager != null) + commandManager.NotifyDeselected (); + base.OnDeselected (); + } + + void Update (CommandInfo cmdInfo) + { + lastCmdInfo = cmdInfo; + if (isArray && !isArrayItem) { + this.Visible = false; + Gtk.Menu menu = (Gtk.Menu) Parent; + + if (itemArray != null) { + foreach (Gtk.MenuItem item in itemArray) + menu.Remove (item); + } + + itemArray = new ArrayList (); + int i = Array.IndexOf (menu.Children, this); + + if (cmdInfo.ArrayInfo != null) { + foreach (CommandInfo info in cmdInfo.ArrayInfo) { + Gtk.MenuItem item; + if (info.IsArraySeparator) { + item = new Gtk.SeparatorMenuItem (); + item.Show (); + } else { + item = CommandEntry.CreateMenuItem (commandManager, commandId, false); + ICommandMenuItem mi = (ICommandMenuItem) item; + mi.SetUpdateInfo (info, initialTarget); + } + menu.Insert (item, ++i); + itemArray.Add (item); + } + } + } else { + Gtk.Widget child = Child; + if (child == null) + return; + + Gtk.Label accel_label = null; + Gtk.Label label = null; + + if (!(child is Gtk.HBox)) { + child = new Gtk.HBox (false, 0); + accel_label = new Gtk.Label (""); + accel_label.UseUnderline = false; + accel_label.Xalign = 1.0f; + accel_label.Show (); + + label = new Gtk.Label (""); + label.UseUnderline = true; + label.Xalign = 0.0f; + label.Show (); + + ((Gtk.Box) child).PackStart (label); + ((Gtk.Box) child).PackStart (accel_label); + child.Show (); + + this.Remove (Child); + this.Add (child); + } else { + accel_label = (Gtk.Label) ((Gtk.Box) child).Children[1]; + label = (Gtk.Label) ((Gtk.Box) child).Children[0]; + } + + if (cmdInfo.AccelKey != null) + accel_label.Text = " " + KeyBindingManager.BindingToDisplayLabel (cmdInfo.AccelKey, true); + else + accel_label.Text = String.Empty; + + if (cmdInfo.UseMarkup) { + label.Markup = overrideLabel ?? cmdInfo.Text; + label.UseMarkup = true; + } else { + label.Text = overrideLabel ?? cmdInfo.Text; + label.UseMarkup = false; + } + + label.UseUnderline = true; + + this.Sensitive = cmdInfo.Enabled; + this.Visible = cmdInfo.Visible && (disabledVisible || cmdInfo.Enabled); + + if (!cmdInfo.Icon.IsNull && cmdInfo.Icon != lastIcon) { + Image = new Gtk.Image (cmdInfo.Icon, Gtk.IconSize.Menu); + lastIcon = cmdInfo.Icon; + } + + if (cmdInfo is CommandInfoSet) { + CommandInfoSet ciset = (CommandInfoSet) cmdInfo; + Gtk.Menu smenu = new Gtk.Menu (); + Submenu = smenu; + foreach (CommandInfo info in ciset.CommandInfos) { + Gtk.MenuItem item; + if (info.IsArraySeparator) { + item = new Gtk.SeparatorMenuItem (); + item.Show (); + } else { + item = CommandEntry.CreateMenuItem (commandManager, commandId, false); + ICommandMenuItem mi = (ICommandMenuItem) item; + mi.SetUpdateInfo (info, initialTarget); + } + smenu.Add (item); + } + } + } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandRouterContainer.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandRouterContainer.cs new file mode 100644 index 0000000000..53612e944b --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandRouterContainer.cs @@ -0,0 +1,64 @@ +// +// CommandRouterContainer.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CommandRouterContainer: Gtk.HBox, ICommandDelegatorRouter + { + bool continueToParent; + + protected object Delegated { + get; + set; + } + + protected CommandRouterContainer (bool continueToParent) + { + this.continueToParent = continueToParent; + } + + public CommandRouterContainer (Gtk.Widget child, object target, bool continueToParent) : this (continueToParent) + { + if (child != null) + PackStart (child, true, true, 0); + Delegated = target; + } + + public virtual object GetNextCommandTarget () + { + return continueToParent ? Parent : null; + } + + public virtual object GetDelegatedCommandTarget () + { + return Delegated; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandSelectedEventArgs.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandSelectedEventArgs.cs new file mode 100644 index 0000000000..4d87a0198b --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandSelectedEventArgs.cs @@ -0,0 +1,45 @@ +// CommandSelectedArgs.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CommandSelectedEventArgs: EventArgs + { + CommandInfo cmd; + + public CommandSelectedEventArgs (CommandInfo cmd) + { + this.cmd = cmd; + } + + public CommandInfo CommandInfo { + get { return cmd; } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandSystemCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandSystemCommands.cs new file mode 100644 index 0000000000..4b2895ad3d --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandSystemCommands.cs @@ -0,0 +1,38 @@ +// +// CommandSystemCommands.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public enum CommandSystemCommands + { + ToolbarList + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToggleToolButton.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToggleToolButton.cs new file mode 100644 index 0000000000..1f8f765f5e --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToggleToolButton.cs @@ -0,0 +1,91 @@ +// +// CommandToggleToolButton.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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 MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public class CommandToggleToolButton: Gtk.ToggleToolButton, ICommandUserItem + { + CommandManager commandManager; + object commandId; + bool updating; + object initialTarget; + + public CommandToggleToolButton (object commandId, CommandManager commandManager): base ("") + { + this.commandId = commandId; + this.commandManager = commandManager; + } + + protected override void OnParentSet (Gtk.Widget parent) + { + base.OnParentSet (parent); + if (Parent == null) return; + + ((ICommandUserItem)this).Update (null); + } + + void ICommandUserItem.Update (object initialTarget) + { + if (commandManager != null) { + CommandInfo cinfo = commandManager.GetCommandInfo (commandId, initialTarget); + this.initialTarget = initialTarget; + Update (cinfo); + } + } + + protected override void OnClicked () + { + base.OnClicked (); + if (updating) return; + + if (commandManager == null) + throw new InvalidOperationException (); + + commandManager.DispatchCommand (commandId, null, initialTarget); + } + + IconId stockId = null; + + void Update (CommandInfo cmdInfo) + { + updating = true; + Label = cmdInfo.Text; + if (cmdInfo.Icon != stockId) { + stockId = cmdInfo.Icon; + this.IconWidget = new Gtk.Image (cmdInfo.Icon, Gtk.IconSize.Menu); + } + Sensitive = cmdInfo.Enabled; + Visible = cmdInfo.Visible; + Active = cmdInfo.Checked; + updating = false; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToolButton.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToolButton.cs new file mode 100644 index 0000000000..7509b0b67f --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToolButton.cs @@ -0,0 +1,104 @@ +// +// CommandToolButton.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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 MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public class CommandToolButton: Gtk.ToolButton, ICommandUserItem + { + CommandManager commandManager; + object commandId; + string lastDesc; + object initialTarget; + + public CommandToolButton (object commandId, CommandManager commandManager): base ("") + { + this.commandId = commandId; + this.commandManager = commandManager; + UseUnderline = true; + } + + protected override void OnParentSet (Gtk.Widget parent) + { + base.OnParentSet (parent); + if (Parent == null) return; + + ((ICommandUserItem)this).Update (null); + } + + void ICommandUserItem.Update (object initialTarget) + { + if (commandManager != null) { + CommandInfo cinfo = commandManager.GetCommandInfo (commandId, initialTarget); + this.initialTarget = initialTarget; + Update (cinfo); + } + } + + protected override void OnClicked () + { + base.OnClicked (); + + if (commandManager == null) + throw new InvalidOperationException (); + + commandManager.DispatchCommand (commandId, null, initialTarget); + } + + IconId stockId = null; + + void Update (CommandInfo cmdInfo) + { + if (lastDesc != cmdInfo.Description) { + string toolTip; + if (string.IsNullOrEmpty (cmdInfo.AccelKey)) { + toolTip = cmdInfo.Description; + } else { + toolTip = cmdInfo.Description + " (" + KeyBindingManager.BindingToDisplayLabel (cmdInfo.AccelKey, false) + ")"; + } + TooltipText = toolTip; + lastDesc = cmdInfo.Description; + } + + if (Label != cmdInfo.Text) + Label = cmdInfo.Text; + if (cmdInfo.Icon != stockId) { + stockId = cmdInfo.Icon; + this.IconWidget = new Gtk.Image (cmdInfo.Icon, Gtk.IconSize.Menu); + } + if (cmdInfo.Enabled != Sensitive) + Sensitive = cmdInfo.Enabled; + if (cmdInfo.Visible != Visible) + Visible = cmdInfo.Visible; + if (cmdInfo.Icon.IsNull) + IsImportant = true; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToolbar.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToolbar.cs new file mode 100644 index 0000000000..c3518642a0 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandToolbar.cs @@ -0,0 +1,78 @@ +// +// CommandToolbar.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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 MonoDevelop.Components.DockToolbars; + +namespace MonoDevelop.Components.Commands +{ + public class CommandToolbar: DockToolbar, ICommandBar + { + object initialCommandTarget; + + public CommandToolbar (CommandManager manager, string id, string title): base (id, title) + { + manager.RegisterCommandBar (this); + } + + internal object InitialCommandTarget { + get { return initialCommandTarget; } + set { initialCommandTarget = value; } + } + + protected override void OnShown () + { + base.OnShown (); + ((ICommandBar)this).Update (null); + } + + void ICommandBar.Update (object defaultTarget) + { + if (!Visible) + return; + + if (initialCommandTarget != null) + defaultTarget = initialCommandTarget; + + foreach (Gtk.Widget item in Children) { + if (item is ICommandUserItem) + ((ICommandUserItem)item).Update (defaultTarget); + else + item.Show (); + } + } + + public void SetEnabled (bool enabled) + { + foreach (Gtk.Widget item in Children) { + if (item.GetType () == typeof(Gtk.ToolItem)) + item.Sensitive = enabled; + } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommand.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommand.cs new file mode 100644 index 0000000000..d30cd57beb --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommand.cs @@ -0,0 +1,56 @@ +// +// CustomCommand.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CustomCommand: Command + { + Type widgetType; + + public CustomCommand () + { + } + + public CustomCommand (object id, string text, Type widgetType): base (id, text) + { + WidgetType = widgetType; + } + + public Type WidgetType { + get { return widgetType; } + set { + if (!typeof(Gtk.Widget).IsAssignableFrom (value)) + throw new ArgumentException ("Value is not a subclass of Gtk.Widget."); + widgetType = value; + } + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommandTargetAttribute.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommandTargetAttribute.cs new file mode 100644 index 0000000000..289dab82ec --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommandTargetAttribute.cs @@ -0,0 +1,75 @@ +// CustomCommandTargetAttribute.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class CustomCommandTargetAttribute: Attribute, ICommandTargetHandler, ICommandArrayTargetHandler + { + ICommandTargetHandler next; + ICommandArrayTargetHandler nextArray; + + void ICommandArrayTargetHandler.Run (object target, Command cmd, object data) + { + Run (target, cmd, data); + } + + void ICommandTargetHandler.Run (object target, Command cmd) + { + Run (target, cmd); + } + + protected virtual void Run (object target, Command cmd, object data) + { + nextArray.Run (target, cmd, data); + } + + protected virtual void Run (object target, Command cmd) + { + next.Run (target, cmd); + } + + ICommandTargetHandler ICommandTargetHandler.Next { + get { + return next; + } + set { + next = value; + } + } + + ICommandArrayTargetHandler ICommandArrayTargetHandler.Next { + get { + return nextArray; + } + set { + nextArray = value; + } + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommandUpdaterAttribute.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommandUpdaterAttribute.cs new file mode 100644 index 0000000000..ba8f4adfe2 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomCommandUpdaterAttribute.cs @@ -0,0 +1,78 @@ +// CustomCommandUpdaterAttribute.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + // This base class can be used to create attribute classes which provide custom + // command updating behavior. When applied to a method, the overriden CommandUpdate method + // will be called to update the command status. + public abstract class CustomCommandUpdaterAttribute: Attribute, ICommandUpdateHandler, ICommandArrayUpdateHandler + { + ICommandUpdateHandler next; + ICommandArrayUpdateHandler nextArray; + + void ICommandUpdateHandler.CommandUpdate (object target, CommandInfo cinfo) + { + CommandUpdate (target, cinfo); + } + + ICommandUpdateHandler ICommandUpdateHandler.Next { + get { + return next; + } + set { + next = value; + } + } + + void ICommandArrayUpdateHandler.CommandUpdate (object target, CommandArrayInfo cinfo) + { + CommandUpdate (target, cinfo); + } + + ICommandArrayUpdateHandler ICommandArrayUpdateHandler.Next { + get { + return nextArray; + } + set { + nextArray = value; + } + } + + protected virtual void CommandUpdate (object target, CommandInfo cinfo) + { + next.CommandUpdate (target, cinfo); + } + + protected virtual void CommandUpdate (object target, CommandArrayInfo cinfo) + { + nextArray.CommandUpdate (target, cinfo); + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomItem.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomItem.cs new file mode 100644 index 0000000000..a49432879f --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomItem.cs @@ -0,0 +1,43 @@ +// CustomItem.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2007 Novell, Inc (http://www.novell.com) +// +// 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 Gtk; + +namespace MonoDevelop.Components.Commands +{ + public class CustomItem: HBox + { + public virtual void SetToolbarStyle (Gtk.Toolbar toolbar) + { + } + + public virtual void SetMenuStyle (Gtk.MenuShell menu) + { + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomMenuItem.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomMenuItem.cs new file mode 100644 index 0000000000..e70bf24edd --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CustomMenuItem.cs @@ -0,0 +1,46 @@ +// +// CustomMenuItem.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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.Collections; + +namespace MonoDevelop.Components.Commands +{ + class CustomMenuItem: Gtk.MenuItem + { + protected override bool OnButtonPressEvent (Gdk.EventButton e) + { + return true; + } + + protected override bool OnButtonReleaseEvent (Gdk.EventButton e) + { + return true; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandBar.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandBar.cs new file mode 100644 index 0000000000..f3a7ea3e51 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandBar.cs @@ -0,0 +1,37 @@ +// +// ICommandUserInterface.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2010 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public interface ICommandBar + { + void Update (object activeTarget); + void SetEnabled (bool enabled); + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandDelegatorRouter.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandDelegatorRouter.cs new file mode 100644 index 0000000000..31600c9d74 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandDelegatorRouter.cs @@ -0,0 +1,41 @@ +// +// ICommandDelegatorRouter.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + // Redirects the command route to the object returned by + // GetDelegatedCommandTarget and when done, continues with + // GetNextCommandTarget. + public interface ICommandDelegatorRouter + { + object GetNextCommandTarget (); + object GetDelegatedCommandTarget (); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandMenuItem.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandMenuItem.cs new file mode 100644 index 0000000000..37bac948e5 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandMenuItem.cs @@ -0,0 +1,37 @@ +// +// ICommandMenuItem.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + internal interface ICommandMenuItem: ICommandUserItem + { + void SetUpdateInfo (CommandInfo cmdInfo, object initialTarget); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandRouter.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandRouter.cs new file mode 100644 index 0000000000..6ac9e6be51 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandRouter.cs @@ -0,0 +1,37 @@ +// +// ICommandRouter.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public interface ICommandRouter + { + object GetNextCommandTarget (); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandTargetVisitor.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandTargetVisitor.cs new file mode 100644 index 0000000000..c3fe21d5ff --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandTargetVisitor.cs @@ -0,0 +1,37 @@ +// ICommandTargetVisitor.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2007 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public interface ICommandTargetVisitor + { + bool Visit (object ob); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandUpdateHandler.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandUpdateHandler.cs new file mode 100644 index 0000000000..88056cf28b --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandUpdateHandler.cs @@ -0,0 +1,55 @@ +// ICommandUpdateHandler.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public interface ICommandUpdateHandler + { + ICommandUpdateHandler Next { get; set; } + void CommandUpdate (object target, CommandInfo cinfo); + } + + public interface ICommandArrayUpdateHandler + { + ICommandArrayUpdateHandler Next { get; set; } + void CommandUpdate (object target, CommandArrayInfo cinfo); + } + + public interface ICommandTargetHandler + { + ICommandTargetHandler Next { get; set; } + void Run (object target, Command cmd); + } + + public interface ICommandArrayTargetHandler + { + ICommandArrayTargetHandler Next { get; set; } + void Run (object target, Command cmd, object data); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandUserItem.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandUserItem.cs new file mode 100644 index 0000000000..212cae9bd6 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/ICommandUserItem.cs @@ -0,0 +1,37 @@ +// +// ICommandUserItem.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public interface ICommandUserItem + { + void Update (object initialTarget); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/IMultiCastCommandRouter.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/IMultiCastCommandRouter.cs new file mode 100644 index 0000000000..725514fda9 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/IMultiCastCommandRouter.cs @@ -0,0 +1,37 @@ +// IMultiCastCommandRouter.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// 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.Collections; + +namespace MonoDevelop.Components.Commands +{ + public interface IMultiCastCommandRouter + { + IEnumerable GetCommandTargets (); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingManager.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingManager.cs new file mode 100644 index 0000000000..2790be19aa --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingManager.cs @@ -0,0 +1,910 @@ +// +// KeyBindingManager.cs +// +// Author: Jeffrey Stedfast <fejj@novell.com> +// +// Copyright (C) 2007 Novell, Inc (http://www.novell.com) +// +// 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.Collections; +using System.Collections.Generic; + +// Terminology: +// mode: A 'mode' is a key binding prefix / modifier meant to allow a +// wider set of key bindings to exist. If you are familiar with +// GNU/Emacs, you might be familiar with the key binding for +// 'Save'. This key binding would have a 'mode' of 'Control+x' +// and an accel of 'Control+s' - the full binding being: +// 'Control+x Control+s'. +// +// accel: A (possibly partial) key binding for some command/action. +// When combined with the mode prefix, results in the full key +// binding. +// +// binding: The full key binding for a command/action. +// + +namespace MonoDevelop.Components.Commands +{ + public class KeyBindingManager : IDisposable + { + static bool isX11 = false; + + Dictionary<string, List<Command>> bindings = new Dictionary<string, List<Command>> (); + Dictionary<string, int> modes = new Dictionary<string, int> (); + List<Command> commands = new List<Command> (); + + static KeyBindingManager () + { + isX11 = !isMac && System.Environment.OSVersion.Platform == PlatformID.Unix; + + if (isMac) { + SelectionModifierAlt = Gdk.ModifierType.Mod5Mask; + SelectionModifierControl = Gdk.ModifierType.Mod1Mask | Gdk.ModifierType.MetaMask; + SelectionModifierSuper = Gdk.ModifierType.ControlMask; + } else { + SelectionModifierAlt = Gdk.ModifierType.Mod1Mask; + SelectionModifierControl = Gdk.ModifierType.ControlMask; + SelectionModifierSuper = Gdk.ModifierType.SuperMask; + } + } + + static bool isMac { + get { return MonoDevelop.Core.PropertyService.IsMac; } + } + + public void Dispose () + { + if (commands == null) + return; + + for (int i = 0; i < commands.Count; i++) + commands[i].KeyBindingChanged -= OnKeyBindingChanged; + commands = null; + } + + #region Platform-dependent selection modifiers + //FIXME: these should be named for what they do, not what they are + + public static Gdk.ModifierType SelectionModifierAlt { + get; private set; + } + + public static Gdk.ModifierType SelectionModifierControl { + get; private set; + } + + public static Gdk.ModifierType SelectionModifierSuper { + get; private set; + } + + #endregion + + #region Static Public Helpers For Building Key-Binding Strings + + static string KeyToString (Gdk.Key key) + { + char c = (char) Gdk.Keyval.ToUnicode ((uint) key); + + if (c != 0) { + if (c == ' ') + return "Space"; + + return Char.ToUpper (c).ToString (); + } + + //HACK: Page_Down and Next are synonyms for the same enum value, but alphabetically, Next gets returned + // first by enum ToString(). Similarly, Page_Up and Prior are synonyms, but Page_Up is returned. Hence the + // default pairing is Next and Page_Up, which is confusingly inconsistent, so we fix this here. + // + //The same problem applies to some other common keys, so we ensure certain values are mapped + // to the most common name. + switch (key) { + case Gdk.Key.Next: + return "Page_Down"; + case Gdk.Key.L1: + return "F11"; + case Gdk.Key.L2: + return "F12"; + } + + return key.ToString (); + } + + //USED INTERNALLY ONLY + static string AccelFromKey (Gdk.Key key, Gdk.ModifierType modifier) + { + bool complete; + return AccelFromKey (key, modifier, out complete); + } + + public static string AccelFromKey (Gdk.EventKey raw, out bool complete) + { + Gdk.Key key; + Gdk.ModifierType modifier; + MapRawKeys (raw, out key, out modifier); + return AccelFromKey (key, modifier, out complete); + } + + static string AccelFromKey (Gdk.Key key, Gdk.ModifierType modifier, out bool complete) + { + bool keyIsModifier; + string modifierlabel = ModifierToPartialAccel (modifier, key, out keyIsModifier); + complete = !keyIsModifier; + + if (keyIsModifier) { + return modifierlabel; + } else { + return modifierlabel + KeyToString (key); + } + } + + static bool AccelToKeyPartial (string accel, ref int i, out uint key, out Gdk.ModifierType modifier) + { + Gdk.ModifierType mod, mask = 0; + string str; + uint k = 0; + int j = i; + + modifier = Gdk.ModifierType.None; + key = 0; + + if (accel == null || i >= accel.Length) + return false; + + while (i < accel.Length) { + for (j = i + 1; j < accel.Length; j++) { + if (accel[j] == '+' || accel[j] == '|') + break; + } + + str = accel.Substring (i, j - i); + if ((mod = ModifierMask (str)) == Gdk.ModifierType.None) { + if (str == "Space") + k = (uint) Gdk.Key.space; + else if (str.Length > 1) + k = Gdk.Keyval.FromName (str); + else + k = (uint) str[0]; + + break; + } + + mask |= mod; + i = j + 1; + } + + i = j; + + modifier = mask; + key = k; + + return k != 0; + } + + static bool AccelToKeyPartial (string accel, out uint key, out Gdk.ModifierType modifier) + { + int i = 0; + + modifier = Gdk.ModifierType.None; + key = 0; + + if (accel == null || accel == String.Empty) + return false; + + if (AccelToKeyPartial (accel, ref i, out key, out modifier) && i == accel.Length) + return true; + + return false; + } + + public static bool AccelToKey (string accel, out uint key, out Gdk.ModifierType modifier) + { + if (AccelToKeyPartial (accel, out key, out modifier)) + return true; + modifier = Gdk.ModifierType.None; + key = 0; + return false; + } + + static bool BindingToKeysPartial (string binding, out uint modeKey, out Gdk.ModifierType modeModifier, out uint key, out Gdk.ModifierType modifier) + { + int i = 0; + + modeModifier = Gdk.ModifierType.None; + modeKey = 0; + modifier = Gdk.ModifierType.None; + key = 0; + + if (binding == null || binding == String.Empty) + return false; + + if (!AccelToKeyPartial (binding, ref i, out key, out modifier)) + return false; + + if (i == binding.Length) { + // no mode in this binding, we're done + return true; + } + + if (binding[i] != '|') { + // bad format + return false; + } + + modeModifier = modifier; + modeKey = key; + i++; + + return AccelToKeyPartial (binding, ref i, out key, out modifier) && i == binding.Length; + } + + public static bool BindingToKeys (string binding, out uint modeKey, out Gdk.ModifierType modeModifier, out uint key, out Gdk.ModifierType modifier) + { + if (BindingToKeysPartial (binding, out modeKey, out modeModifier, out key, out modifier)) + return true; + + modeModifier = modifier = Gdk.ModifierType.None; + modeKey = key = 0; + return false; + } + + public static string Binding (string mode, string accel) + { + if (mode == null) { + if (accel == String.Empty) + return null; + + return accel; + } + + if (accel == String.Empty) + return mode; + + return mode + "|" + accel; + } + + + static void MapRawKeys (Gdk.EventKey evt, out Gdk.Key key, out Gdk.ModifierType mod) + { + key = evt.Key; + mod = evt.State; + + if (isX11) { + //this is a workaround for a common X mapping issue + //where the alt key is mapped to the meta key when the shift modifier is active + if (key.Equals (Gdk.Key.Meta_L) || key.Equals (Gdk.Key.Meta_R)) + key = Gdk.Key.Alt_L; + } + + //HACK: the MAC GTK+ port currently does some horrible, un-GTK-ish key mappings + // so we work around them by playing some tricks to remap and decompose modifiers. + // We also decompose keys to the root physical key so that the Mac command + // combinations appear as expected, e.g. shift-{ is treated as shift-[. + if (isMac && !isX11) { + // 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; + } + + // If Mod5 is active it *might* mean that opt/alt is active, + // so we can unset this and map it back to the normal modifier. + if ((mod & Gdk.ModifierType.Mod5Mask) != 0) { + mod ^= Gdk.ModifierType.Mod5Mask; + mod |= Gdk.ModifierType.Mod1Mask; + } + + // When shift or opt modifiers are active, we need to decompose keys down to the + // "root" physical key to make the command appear correct for Mac. + if (evt.Group == (byte) 1 || (mod & Gdk.ModifierType.ShiftMask) != 0) + key = GetRootKey (evt); + + // 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 (evt.Group == (byte) 1) + mod |= Gdk.ModifierType.Mod1Mask; + } + } + + static Dictionary<Gdk.Key,Gdk.Key> hardwareMappings; + static Gdk.Keymap keymap; + + static void InitKeymaps () + { + if (keymap != null) + return; + + keymap = Gdk.Keymap.Default; + keymap.KeysChanged += delegate { + hardwareMappings.Clear (); + }; + hardwareMappings = new Dictionary<Gdk.Key,Gdk.Key> (); + } + + static Gdk.Key GetRootKey (Gdk.EventKey evt) + { + InitKeymaps (); + Gdk.Key ret; + if (hardwareMappings.TryGetValue (evt.Key, out ret)) + return ret; + + //FIXME: LookupKey isn't implemented on Mac, so we have to use this workaround + uint[] keyvals; + Gdk.KeymapKey [] keys; + keymap.GetEntriesForKeycode (evt.HardwareKeycode, out keys, out keyvals); + + for (uint i = 0; i < keys.Length; i++) { + if (keys[i].Group == 0 && keys[i].Level == 0) { + ret = (Gdk.Key)keyvals[i]; + foreach (Gdk.Key key in keyvals) + hardwareMappings[key] = ret; + return ret; + } + } + + //failed, but avoid looking it up again + return hardwareMappings[evt.Key] = evt.Key; + } + + static string ModifierToPartialAccel (Gdk.ModifierType mod, Gdk.Key key, out bool keyIsModifier) + { + string label = String.Empty; + + if ((mod & Gdk.ModifierType.ControlMask) != 0) + label += "Control+"; + if ((mod & Gdk.ModifierType.Mod1Mask) != 0) + label += "Alt+"; + if ((mod & Gdk.ModifierType.ShiftMask) != 0) + label += "Shift+"; + if ((mod & Gdk.ModifierType.MetaMask) != 0) + label += "Meta+"; + if ((mod & Gdk.ModifierType.SuperMask) != 0) + label += "Super+"; + + keyIsModifier = true; + if (key.Equals (Gdk.Key.Control_L) || key.Equals (Gdk.Key.Control_R)) + label += "Control+"; + else if (key.Equals (Gdk.Key.Alt_L) || key.Equals (Gdk.Key.Alt_R)) + label += "Alt+"; + else if (key.Equals (Gdk.Key.Shift_L) || key.Equals (Gdk.Key.Shift_R)) + label += "Shift+"; + else if (key.Equals (Gdk.Key.Meta_L) || key.Equals (Gdk.Key.Meta_R)) + label += "Meta+"; + else if (key.Equals (Gdk.Key.Super_L) || key.Equals (Gdk.Key.Super_L)) + label += "Super+"; + else + keyIsModifier = false; + + return label; + } + + #endregion + + #region Display label formatting + + public static string BindingToDisplayLabel (string binding, bool concise) + { + return BindingToDisplayLabel (binding, concise, false); + } + + public static string BindingToDisplayLabel (string binding, bool concise, bool includeIncomplete) + { + Gdk.ModifierType modeMod, mod; + string label = String.Empty; + uint modeKey, key; + + if (includeIncomplete) { + BindingToKeysPartial (binding, out modeKey, out modeMod, out key, out mod); + } else if (!BindingToKeys (binding, out modeKey, out modeMod, out key, out mod)) { + return null; + } + + if (modeKey != 0) { + label += ModifierToDisplayLabel (modeMod, concise) + KeyToDisplayLabel ((Gdk.Key)modeKey) + (isMac? " " : "|"); + } + label += ModifierToDisplayLabel (mod, concise); + if (key != 0) + label += KeyToDisplayLabel ((Gdk.Key)key); + + return label; + } + + static string KeyToDisplayLabel (Gdk.Key key) + { + if (isMac) { + char appl = AppleMapKeyToSymbol (key); + if (appl != '\0') { + return new string (appl, 1); + } + } + return KeyToString (key); + } + + static string ModifierToDisplayLabel (Gdk.ModifierType mod, bool concise) + { + if (isMac) { + return AppleMapModifierToSymbols (mod); + } + + string label = String.Empty; + + if ((mod & Gdk.ModifierType.ControlMask) != 0) + label += concise? "Ctrl+" : "Control+"; + if ((mod & Gdk.ModifierType.Mod1Mask) != 0) + label += "Alt+"; + if ((mod & Gdk.ModifierType.ShiftMask) != 0) + label += "Shift+"; + if ((mod & Gdk.ModifierType.MetaMask) != 0) + label += "Meta+"; + if ((mod & Gdk.ModifierType.SuperMask) != 0) + label += "Super+"; + + return label; + } + + static Gdk.ModifierType ModifierMask (string name) + { + switch (name) { + case "Alt": + return Gdk.ModifierType.Mod1Mask; + case "Shift": + return Gdk.ModifierType.ShiftMask; + case "Control": + case "Ctrl": + return Gdk.ModifierType.ControlMask; + case "Meta": + return Gdk.ModifierType.MetaMask; + case "Super": + return Gdk.ModifierType.SuperMask; + default: + return Gdk.ModifierType.None; + } + } + + /// + /// Converts the old shortcut format into the new "key binding" format + /// + static string ShortcutToBinding (string shortcut) + { + Gdk.ModifierType mod, mask = 0; + int i = 0, j; + Gdk.Key key; + string str; + + if (shortcut == null || shortcut == String.Empty) + return null; + + while (i < shortcut.Length) { + if ((j = shortcut.IndexOf ('|', i + 1)) == -1) { + // we are at the last part of the shortcut, the Gdk.Key + j = shortcut.Length; + } + + str = shortcut.Substring (i, j - i); + if ((mod = ModifierMask (str)) == Gdk.ModifierType.None) { + if (str.Length > 1) + key = (Gdk.Key) Gdk.Key.Parse (typeof (Gdk.Key), str); + else + key = (Gdk.Key) str[0]; + + if (j < shortcut.Length) + Console.WriteLine ("WARNING: trailing data after Gdk.Key portion of shortcut {0}", shortcut); + + return AccelFromKey (key, mask); + } + + mask |= mod; + i = j + 1; + } + + Console.WriteLine ("WARNING: Incomplete shortcut '{0}'?", shortcut); + + return null; + } + + /// + /// Returns true if @shortcut is in the old string format or false if it is the newer string format. + /// + static bool IsShortcutFormat (string shortcut) + { + int i = 0; + + if (shortcut == null || shortcut == String.Empty) + return false; + + if (shortcut.Length == 1 && (shortcut[0] == '|' || shortcut[0] == '+')) { + // single-key binding + return false; + } + + while (i < shortcut.Length && (shortcut[i] != '|' && shortcut[i] != '+')) + i++; + + return i < shortcut.Length && shortcut[i] == '|' && ModifierMask (shortcut.Substring (0, i)) != Gdk.ModifierType.None; + } + + public static string CanonicalizeBinding (string binding) + { + Gdk.ModifierType modeMod, mod; + string accel, mode = null; + uint modeKey, key; + + if (binding == null || binding == String.Empty) + return null; + + if (IsShortcutFormat (binding)) + return ShortcutToBinding (binding); + + if (!BindingToKeys (binding, out modeKey, out modeMod, out key, out mod)) { + Console.WriteLine ("WARNING: failed to canonicalize binding {0}", binding); + return null; + } + + if (modeKey != 0) + mode = AccelFromKey ((Gdk.Key) modeKey, modeMod); + + accel = AccelFromKey ((Gdk.Key) key, mod); + + return Binding (mode, accel); + } + + #endregion + + #region Public Methods + + public bool BindingExists (string binding) + { + return bindings.ContainsKey (binding); + } + + public bool BindingExists (string mode, string accel) + { + return BindingExists (Binding (mode, accel)); + } + + public bool ModeExists (string mode) + { + return modes.ContainsKey (mode); + } + + string BindingMode (string binding) + { + bool plus = false; + int i; + + if (binding == null) + return null; + + for (i = 0; i < binding.Length; i++) { + if (!plus && binding[i] == '|') + return binding.Substring (0, i); + + if (binding[i] == '+') + plus = !plus; + else + plus = false; + } + + return null; + } + + void SetBinding (Command command, string oldMode, string oldBinding, string newMode, string newBinding) + { + List<Command> list; + int refs; + + if (newMode != oldMode) { + // keep track of valid modes + if (oldMode != null) { + if ((refs = modes[oldMode] - 1) == 0) + modes.Remove (oldMode); + else + modes[oldMode] = refs; + } + + if (newMode != null) { + if (modes.ContainsKey (newMode)) { + modes[newMode]++; + } else + modes.Add (newMode, 1); + } + } + + if (oldBinding != null) { + list = bindings[oldBinding]; + list.Remove (command); + + if (list.Count == 0) + bindings.Remove (oldBinding); + } + + if (newBinding != null) { + if (!bindings.ContainsKey (newBinding)) { + list = new List<Command> (); + list.Add (command); + + bindings.Add (newBinding, list); + } else { + list = bindings[newBinding]; + list.Add (command); + } + } + } + + void SetBinding (Command command, string oldBinding, string newBinding) + { + SetBinding (command, BindingMode (oldBinding), oldBinding, BindingMode (newBinding), newBinding); + } + + void OnKeyBindingChanged (object o, KeyBindingChangedEventArgs args) + { + SetBinding (args.Command, args.OldKeyBinding, args.NewKeyBinding); + } + + public bool CommandIsRegistered (Command command) + { + return commands.Contains (command); + } + + public void RegisterCommand (Command command) + { + if (commands.Contains (command)) { + Console.WriteLine ("WARNING: trying to re-register command {0}", command); + return; + } + + SetBinding (command, null, null, BindingMode (command.AccelKey), command.AccelKey); + command.KeyBindingChanged += new KeyBindingChangedEventHandler (OnKeyBindingChanged); + commands.Add (command); + } + + public void UnregisterCommand (Command command) + { + List<Command> list; + string mode; + int refs; + + if (!commands.Contains (command)) { + Console.WriteLine ("WARNING: trying to unregister unknown command {0}", command); + return; + } + + command.KeyBindingChanged -= OnKeyBindingChanged; + commands.Remove (command); + + if (string.IsNullOrEmpty (command.AccelKey)) + return; + + list = bindings[command.AccelKey]; + list.Remove (command); + if (list.Count == 0) + bindings.Remove (command.AccelKey); + + mode = BindingMode (command.AccelKey); + if (mode != null && modes.ContainsKey (mode)) { + if ((refs = modes[mode] - 1) == 0) + modes.Remove (mode); + else + modes[mode] = refs; + } + } + + /// + /// Returns the oldest registered command with the specified key binding. + /// + public Command Command (string binding) + { + List<Command> list; + + if (!BindingExists (binding)) + return null; + + list = bindings[binding]; + + return list[0]; + } + + /// + /// Returns the list of commands registered for the specified key binding. + /// + public List<Command> Commands (string binding) + { + if (!BindingExists (binding)) + return null; + + return bindings[binding]; + } + + #endregion + + #region Apple symbol mappings + + static string AppleMapModifierToSymbols (Gdk.ModifierType mod) + { + string ret = ""; + + if ((mod & Gdk.ModifierType.ControlMask) != 0) { + ret += "⌃"; + mod ^= Gdk.ModifierType.ControlMask; + } + if ((mod & Gdk.ModifierType.Mod1Mask) != 0) { + ret += "⌥"; + mod ^= Gdk.ModifierType.Mod1Mask; + } + if ((mod & Gdk.ModifierType.ShiftMask) != 0) { + ret += "⇧"; + mod ^= Gdk.ModifierType.ShiftMask; + } + if ((mod & Gdk.ModifierType.MetaMask) != 0) { + ret += "⌘"; + mod ^= Gdk.ModifierType.MetaMask; + } + if (mod != 0) + throw new InvalidOperationException ("Unexpected modifiers: " + mod.ToString ()); + return ret; + } + + static char AppleMapKeyToSymbol (Gdk.Key key) + { + // unicode codes from http://macbiblioblog.blogspot.com/2005/05/special-key-symbols.html + // unmapped: + // ⇤ tab back + // ⌤ enter + + switch (key) { + case Gdk.Key.Escape: + return '⎋'; + case Gdk.Key.Tab: + return '⇥'; + case Gdk.Key.Caps_Lock: + return '⇪'; + case Gdk.Key.Shift_L: + case Gdk.Key.Shift_R: + return '⇧'; + case Gdk.Key.Control_L: + case Gdk.Key.Control_R: + return '⌃'; + case Gdk.Key.BackSpace: + return '⌫'; + case Gdk.Key.Delete: + return '⌦'; + case Gdk.Key.Home: + return '⇱'; + case Gdk.Key.End: + return '⇲'; + case Gdk.Key.Page_Up: + return '⇞'; + case Gdk.Key.Page_Down: + return '⇟'; + case Gdk.Key.Up: + return '↑'; + case Gdk.Key.Down: + return '↓'; + case Gdk.Key.Left: + return '←'; + case Gdk.Key.Right: + return '→'; + case Gdk.Key.Clear: + return '⌧'; + case Gdk.Key.Num_Lock: + return '⇭'; + case Gdk.Key.Return: + return '⏎'; + case Gdk.Key.space: + return '␣'; + case Gdk.Key.Meta_L: + case Gdk.Key.Meta_R: + return '⌘'; + case Gdk.Key.Alt_L: + case Gdk.Key.Alt_R: + return '⌥'; + } + return (char)0; + } + +/* static Gdk.Key AppleMapSymbolToKey (char ch) + { + // unicode codes from http://macbiblioblog.blogspot.com/2005/05/special-key-symbols.html + // unmapped: + // ⇤ tab back + // ⌥ option (alt, alternative) + // ⌘ command + // ⌤ enter + + switch (ch) { + case '⎋': + return Gdk.Key.Escape; + case '⇥': + return Gdk.Key.Tab; + case '⇪': + return Gdk.Key.Caps_Lock; + case '⇧': + return Gdk.Key.Shift_L; + case '⌃': + return Gdk.Key.Control_L; + case '⌫': + return Gdk.Key.BackSpace; + case '⌦': + return Gdk.Key.Delete; + case '⇱': + return Gdk.Key.Home; + case '⇲': + return Gdk.Key.End; + case '⇞': + return Gdk.Key.Page_Up; + case '⇟': + return Gdk.Key.Page_Down; + case '↑': + return Gdk.Key.Up; + case '↓': + return Gdk.Key.Down; + case '←': + return Gdk.Key.Left; + case '→': + return Gdk.Key.Right; + case '⌧': + return Gdk.Key.Clear; + case '⇭': + return Gdk.Key.Num_Lock; + case '⏎': + return Gdk.Key.Return; + case '␣': + return Gdk.Key.space; + case '⌘': + return Gdk.Key.Meta_L; + case '⌥': + return Gdk.Key.Alt_L; + } + return (Gdk.Key)0; + } + */ + #endregion + } + +//#region KeyBindingException +// public class KeyBindingConflictException : Exception { +// Command command; +// string binding; +// +// public KeyBindingConflictException (Command command, string binding) +// : base (String.Format ("An existing command, {0}, is already bound to {1}", +// command, binding)) +// { +// this.command = command; +// this.binding = binding; +// } +// +// public Command Command { +// get { return command; } +// } +// +// public string Binding { +// get { return binding; } +// } +// } +//#endregion +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingScheme.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingScheme.cs new file mode 100644 index 0000000000..8b2ab77989 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingScheme.cs @@ -0,0 +1,38 @@ +// KeyBindingScheme.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public interface KeyBindingScheme + { + string Id { get; } + string Name { get; } + KeyBindingSet GetKeyBindingSet (); + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingService.cs new file mode 100644 index 0000000000..a698f7d97a --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingService.cs @@ -0,0 +1,210 @@ +// KeyBindingService.cs +// +// Author: Jeffrey Stedfast <fejj@novell.com> +// +// Copyright (c) 2007 Novell, Inc (http://www.novell.com) +// +// 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.IO; +using System.Xml; +using System.Text; +using System.Collections; +using System.Collections.Generic; +using Mono.Addins; + +using Unix = Mono.Unix.Native; + +using MonoDevelop.Core; +using MonoDevelop.Components.Commands.ExtensionNodes; + +namespace MonoDevelop.Components.Commands +{ + public class KeyBindingService + { + const string configFileName = "KeyBindings.xml"; + const string configFileNameMac = "KeyBindingsMac.xml"; + + static KeyBindingSet current; + static SortedDictionary<string, KeyBindingScheme> schemes; + static KeyBindingSet defaultSchemeBindings; + static DefaultScheme defaultScheme; + + static KeyBindingService () + { + schemes = new SortedDictionary<string,KeyBindingScheme> (); + + // Initialize the default scheme + defaultSchemeBindings = new KeyBindingSet (); + defaultScheme = new DefaultScheme (defaultSchemeBindings); + schemes.Add (defaultScheme.Id, defaultScheme); + + // Initialize the current bindings + current = new KeyBindingSet (defaultSchemeBindings); + } + + static string ConfigFileName { + get { + return Path.Combine (PropertyService.ConfigPath, PropertyService.IsMac? configFileNameMac : configFileName); + } + } + + internal static KeyBindingSet DefaultKeyBindingSet { + get { return defaultSchemeBindings; } + } + + public static KeyBindingSet CurrentKeyBindingSet { + get { return current; } + } + + public static KeyBindingScheme GetScheme (string id) + { + KeyBindingScheme scheme; + if (schemes.TryGetValue (id, out scheme)) + return scheme; + else + return null; + } + + public static KeyBindingScheme GetSchemeByName (string name) + { + foreach (KeyBindingScheme scheme in schemes.Values) + if (scheme.Name == name) + return scheme; + return null; + } + + public static IEnumerable<KeyBindingScheme> Schemes { + get { + foreach (KeyBindingScheme s in schemes.Values) + yield return s; + } + } + + public static void LoadBindingsFromExtensionPath (string path) + { + AddinManager.AddExtensionNodeHandler (path, OnBindingExtensionChanged); + } + + static void OnBindingExtensionChanged (object s, ExtensionNodeEventArgs args) + { + if (args.Change == ExtensionChange.Add) { + SchemeExtensionNode node = (SchemeExtensionNode) args.ExtensionNode; + if (node.IsForMac == PropertyService.IsMac) + schemes.Add (node.Id, node); + } + else { + SchemeExtensionNode node = (SchemeExtensionNode) args.ExtensionNode; + if (node.IsForMac == PropertyService.IsMac) + schemes.Remove (node.Name); + } + } + + public static void LoadBinding (Command cmd) + { + current.LoadBinding (cmd); + } + + public static void StoreDefaultBinding (Command cmd) + { + defaultSchemeBindings.StoreBinding (cmd); + } + + public static void ResetCurrent (KeyBindingSet kbset) + { + current = kbset.Clone (); + } + + public static void ResetCurrent () + { + ResetCurrent ((string)null); + } + + public static void ResetCurrent (string schemeId) + { + if (schemeId != null) { + KeyBindingScheme scheme = GetScheme (schemeId); + if (scheme != null) { + current = scheme.GetKeyBindingSet ().Clone (); + return; + } + } + + current.ClearBindings (); + } + + public static void StoreBinding (Command cmd) + { + current.StoreBinding (cmd); + } + + internal static string GetCommandKey (Command cmd) + { + if (cmd.Id is Enum) + return cmd.Id.GetType () + "." + cmd.Id; + else + return cmd.Id.ToString (); + } + + public static void LoadCurrentBindings (string defaultSchemaId) + { + XmlTextReader reader = null; + + try { + reader = new XmlTextReader (ConfigFileName); + current.LoadScheme (reader, "current"); + } catch { + ResetCurrent (defaultSchemaId); + } finally { + if (reader != null) + reader.Close (); + } + } + + public static void SaveCurrentBindings () + { + current.Save (ConfigFileName, "current"); + } + } + + class DefaultScheme: KeyBindingScheme + { + KeyBindingSet bindings; + + public DefaultScheme (KeyBindingSet bindings) + { + this.bindings = bindings; + } + + public string Id { + get { return "Default"; } + } + + public string Name { + get { return AddinManager.CurrentLocalizer.GetString ("Default"); } + } + + public KeyBindingSet GetKeyBindingSet () + { + return bindings; + } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingSet.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingSet.cs new file mode 100644 index 0000000000..039064767b --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/KeyBindingSet.cs @@ -0,0 +1,276 @@ +// KeyBindingSet.cs +// +// Author: +// Lluis Sanchez Gual <lluis@novell.com> +// Jeffrey Stedfast <fejj@novell.com> +// +// Copyright (c) 2008 Novell, Inc (http://www.novell.com) +// +// 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.IO; +using System.Collections.Generic; +using System.Xml; +using System.Text; +using MonoDevelop.Core; +using Unix = Mono.Unix.Native; + +namespace MonoDevelop.Components.Commands +{ + public class KeyBindingSet + { + const string version = "1.0"; + Dictionary<string, string> bindings = new Dictionary<string, string> (); + KeyBindingSet parent; + + public KeyBindingSet() + { + } + + public KeyBindingSet (KeyBindingSet parent) + { + this.parent = parent; + } + + public KeyBindingConflict[] CheckKeyBindingConflicts (IEnumerable<Command> commands) + { + Dictionary<string, List<Command>> bindings = new Dictionary<string, List<Command>> (); + List<KeyBindingConflict> conflicts = new List<KeyBindingConflict> (); + + // Load all bindings + foreach (Command cmd in commands) { + string key = GetBinding (cmd); + if (key.Length == 0) + continue; + List<Command> clist; + if (!bindings.TryGetValue (key, out clist)) { + clist = new List<Command> (); + bindings.Add (key, clist); + } + clist.Add (cmd); + if (clist.Count == 2) + // We have a conflict + conflicts.Add (new KeyBindingConflict (key, clist)); + } + + // Check if there are conflicts with key combinations prefixes. + // For example, ctrl+X would conflict with ctrl+X|A + foreach (Command cmd in commands) { + string key = GetBinding (cmd); + int i = key.IndexOf ('|'); + if (i != -1) { + key = key.Substring (0, i); + List<Command> clist; + if (bindings.TryGetValue (key, out clist)) { + clist.Add (cmd); + if (clist.Count == 2) + // We have a conflict + conflicts.Add (new KeyBindingConflict (key, clist)); + } + } + } + return conflicts.ToArray (); + } + + public KeyBindingSet Clone () + { + KeyBindingSet kset = new KeyBindingSet (parent); + kset.bindings = new Dictionary<string, string> (bindings); + return kset; + } + + public bool Equals (KeyBindingSet other) + { + if (bindings.Count != other.bindings.Count) + return false; + foreach (KeyValuePair<string, string> binding in bindings) { + string accel; + if (!other.bindings.TryGetValue (binding.Key, out accel) || accel != binding.Value) + return false; + } + return true; + } + + public void ClearBindings () + { + bindings.Clear (); + } + + public void StoreBinding (Command cmd) + { + SetBinding (cmd, cmd.AccelKey); + } + + public void SetBinding (Command cmd, string accelKey) + { + string key = KeyBindingService.GetCommandKey (cmd); + if (accelKey == null) { + bindings.Remove (key); + return; + } + + if (parent == null) + bindings [key] = accelKey; + else { + // If the key is the same as the default, remove it from the scheme + string pbind = parent.GetBinding (cmd); + if ((accelKey == pbind) || (string.IsNullOrEmpty (accelKey) && string.IsNullOrEmpty (pbind))) + bindings.Remove (key); + else + bindings[key] = accelKey; + } + } + + public void LoadBinding (Command cmd) + { + cmd.AccelKey = GetBinding (cmd); + } + + public string GetBinding (Command cmd) + { + string key = KeyBindingService.GetCommandKey (cmd); + + // Schemes use default key bindings when not explicitly overriden + string accel; + if (bindings.TryGetValue (key, out accel)) + return accel; + else if (parent != null) + return parent.GetBinding (cmd); + else + return string.Empty; + } + + const string commandAttr = "command"; + const string shortcutAttr = "shortcut"; + + internal bool LoadScheme (XmlTextReader reader, string id) + { + bool foundSchemes = false; + bool foundScheme = false; + string command; + string binding; + + bindings.Clear (); + + while (reader.Read ()) { + if (reader.IsStartElement ("schemes") || reader.IsStartElement ("scheme")) { + foundSchemes = true; + break; + } + } + + if (!foundSchemes || reader.GetAttribute ("version") != version) + return false; + + if (reader.IsStartElement ("schemes")) { + while (reader.Read ()) { + if (reader.IsStartElement ("scheme") && reader.GetAttribute ("name") == id) { + foundScheme = true; + break; + } + } + if (!foundScheme) + return false; + } + + while (reader.Read ()) { + if (reader.IsStartElement ()) { + switch (reader.LocalName) { + case "scheme": + // this is the beginning of the next scheme + return true; + case "binding": + command = reader.GetAttribute (commandAttr); + binding = reader.GetAttribute (shortcutAttr); + + if (command != null && command != String.Empty) + bindings.Add (command, binding); + + break; + } + } + } + + return true; + } + + public void Save (XmlTextWriter writer, string id) + { + writer.WriteStartElement ("scheme"); + writer.WriteAttributeString ("name", id); + + foreach (KeyValuePair<string, string> binding in bindings) { + writer.WriteStartElement ("binding"); + writer.WriteAttributeString (commandAttr, binding.Key); + writer.WriteAttributeString (shortcutAttr, binding.Value); + writer.WriteEndElement (); + } + + writer.WriteEndElement (); + } + + public void Save (string fileName, string id) + { + XmlTextWriter writer = null; + Stream stream = null; + bool success = false; + + try { + stream = new FileStream (fileName + '~', FileMode.Create); + writer = new XmlTextWriter (stream, Encoding.UTF8); + writer.Formatting = Formatting.Indented; + writer.IndentChar = ' '; + writer.Indentation = 2; + + writer.WriteStartElement ("schemes"); + writer.WriteAttributeString ("version", version); + Save (writer, id); + writer.WriteEndElement (); + + success = true; + } catch (Exception e) { + LoggingService.LogError (e.ToString ()); + } finally { + if (writer != null) + writer.Close (); + + if (stream != null) + stream.Close (); + } + + if (success) + FileService.SystemRename (fileName + '~', fileName); + } + } + + public class KeyBindingConflict + { + internal KeyBindingConflict (string key, IEnumerable<Command> cmds) + { + Key = key; + Commands = cmds; + } + + public string Key { get; internal set; } + public IEnumerable<Command> Commands { get; internal set; } + } +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/LinkCommandEntry.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/LinkCommandEntry.cs new file mode 100644 index 0000000000..8aa306233d --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/LinkCommandEntry.cs @@ -0,0 +1,106 @@ +// +// LinkCommandEntry.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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.Diagnostics; +using Mono.Addins; +using MonoDevelop.Core; + +namespace MonoDevelop.Components.Commands +{ + public class LinkCommandEntry: CommandEntry + { + static object Id = new object (); + string text; + string url; + IconId icon = Gtk.Stock.JumpTo; + + public LinkCommandEntry (string text, string url): base (Id) + { + this.text = text; + this.url = url; + } + + public LinkCommandEntry (string text, string url, IconId icon): base (Id) + { + this.text = text; + this.url = url; + this.icon = icon; + } + + public string Text { + get { return text; } + set { text = value; } + } + + public string Url { + get { return url; } + set { url = value; } + } + + internal void HandleActivation (object sender, EventArgs e) + { + try { + System.Diagnostics.Process.Start (url); + } catch (Exception) { + string msg = AddinManager.CurrentLocalizer.GetString ("Could not open the url {0}", url); + Gtk.MessageDialog md = new Gtk.MessageDialog (null, Gtk.DialogFlags.Modal | Gtk.DialogFlags.DestroyWithParent, Gtk.MessageType.Error, Gtk.ButtonsType.Ok, msg); + if (sender != null && sender is Gtk.Widget) + md.TransientFor = (sender as Gtk.Widget).Toplevel as Gtk.Window; + md.Run (); + md.Hide (); + } + } + + internal protected override Gtk.MenuItem CreateMenuItem (CommandManager manager) + { + Gtk.ImageMenuItem item = new Gtk.ImageMenuItem (text != null ? text : url); + item.Image = new Gtk.Image (icon, Gtk.IconSize.Menu); + item.Activated += new EventHandler (HandleActivation); + item.Selected += delegate { + CommandInfo ci = new CommandInfo (Text); + ci.Icon = icon; + ci.Description = AddinManager.CurrentLocalizer.GetString ("Open {0}", Url); + manager.NotifySelected (ci); + }; + item.Deselected += delegate { + manager.NotifyDeselected (); + }; + return item; + } + + internal protected override Gtk.ToolItem CreateToolItem (CommandManager manager) + { + Gtk.ToolButton item = new Gtk.ToolButton (text); + item.StockId = icon; + item.Clicked += new EventHandler (HandleActivation); + return item; + } + } +} + diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/MenuToolButton.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/MenuToolButton.cs new file mode 100644 index 0000000000..1524a03214 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/MenuToolButton.cs @@ -0,0 +1,64 @@ +// +// MenuToolButton.cs +// +// Author: +// Lluis Sanchez Gual +// +// Copyright (C) 2005 Novell, Inc (http://www.novell.com) +// +// 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; + +namespace MonoDevelop.Components.Commands +{ + public class MenuToolButton: Gtk.ToolButton + { + Gtk.Menu menu; + + public MenuToolButton (Gtk.Menu menu, string icon): base (icon) + { + this.menu = menu; + Child.ButtonPressEvent += new Gtk.ButtonPressEventHandler (OnButtonPress); + + if (string.IsNullOrEmpty (icon)) { + this.Expand = false; + this.Homogeneous = false; + this.IconWidget = new Gtk.Arrow (Gtk.ArrowType.Down, Gtk.ShadowType.None); + } + } + + [GLib.ConnectBeforeAttribute] + void OnButtonPress (object sender, Gtk.ButtonPressEventArgs e) + { + menu.Popup (null, null, new Gtk.MenuPositionFunc (OnPosition), 3, Gtk.Global.CurrentEventTime); + e.RetVal = true; + } + + void OnPosition (Gtk.Menu menu, out int x, out int y, out bool pushIn) + { + this.ParentWindow.GetOrigin (out x, out y); + x += this.Allocation.X; + y += this.Allocation.Y + this.Allocation.Height; + pushIn = true; + } + } +} |