diff options
author | iain holmes <iain@xamarin.com> | 2019-07-10 18:40:51 +0300 |
---|---|---|
committer | monojenkins <jo.shields+jenkins@xamarin.com> | 2019-07-17 10:33:33 +0300 |
commit | 1b66b1df26ac0705356cbed38415a9d1de45e09b (patch) | |
tree | 5e4cb67ce32ab0d7262e3963b134dcb1542e4adb /main | |
parent | e2be0e1c3811151a114357e9c5a070b766e0bbcf (diff) |
[Mac] Fix keyboard input in Global Search when in fullscreen
In fullscreen Cocoa stops delivering performKeyEquivalent messages to the
SearchBar which breaks handling of cursor keys and similar. Instead of using
it, use our own delegate to catch control:textView:doCommandBySelector: and
create a command from the selector.
Fixes VSTS #937857
Diffstat (limited to 'main')
10 files changed, 205 insertions, 40 deletions
diff --git a/main/src/addins/MacPlatform/MainToolbar/MainToolbar.cs b/main/src/addins/MacPlatform/MainToolbar/MainToolbar.cs index dbae2c656b..c5ac94797d 100644 --- a/main/src/addins/MacPlatform/MainToolbar/MainToolbar.cs +++ b/main/src/addins/MacPlatform/MainToolbar/MainToolbar.cs @@ -70,11 +70,11 @@ namespace MonoDevelop.MacIntegration.MainToolbar void AttachToolbarEvents (SearchBar bar) { - bar.Changed += (o, e) => { - SearchEntryChanged?.Invoke (o, e); + bar.PerformCommand += (o, e) => { + PerformCommand?.Invoke (o, e); }; bar.KeyPressed += (o, e) => { - SearchEntryKeyPressed?.Invoke (o, e); + SearchEntryChanged?.Invoke (o, e); }; bar.LostFocus += (o, e) => { SearchEntryLostFocus?.Invoke (o, e); @@ -209,6 +209,7 @@ namespace MonoDevelop.MacIntegration.MainToolbar public event EventHandler RunButtonClicked; public event EventHandler SearchEntryChanged; public event EventHandler<Xwt.KeyEventArgs> SearchEntryKeyPressed; + public event EventHandler<SearchEntryCommandArgs> PerformCommand; public event EventHandler SearchEntryLostFocus; #pragma warning disable 0067 diff --git a/main/src/addins/MacPlatform/MainToolbar/SearchBar.cs b/main/src/addins/MacPlatform/MainToolbar/SearchBar.cs index 72b492c48f..3acaf0a486 100644 --- a/main/src/addins/MacPlatform/MainToolbar/SearchBar.cs +++ b/main/src/addins/MacPlatform/MainToolbar/SearchBar.cs @@ -32,11 +32,12 @@ using MonoDevelop.Core; using MonoDevelop.Ide; using Xwt.Mac; +using MonoDevelop.Components.MainToolbar; namespace MonoDevelop.MacIntegration.MainToolbar { [Register] - class SearchBar : NSSearchField + class SearchBar : NSSearchField, INSSearchFieldDelegate { internal Widget gtkWidget; internal event EventHandler<Xwt.KeyEventArgs> KeyPressed; @@ -183,6 +184,8 @@ namespace MonoDevelop.MacIntegration.MainToolbar { Cell = new DarkThemeSearchFieldCell (); + Delegate = this; + var nsa = (INSAccessibility)this; AccessibilitySubrole = NSAccessibilitySubroles.SearchFieldSubrole; @@ -198,6 +201,19 @@ namespace MonoDevelop.MacIntegration.MainToolbar UpdateLayout (); } + // Protection against the delegate being replaced by an external caller. + // The delegate needs to be set to make key handling work correctly + public override NSObject WeakDelegate { + get => base.WeakDelegate; + set { + if (base.WeakDelegate != null) { + throw new ApplicationException ("Cannot change the delegate of SearchBar"); + } + + base.WeakDelegate = value; + } + } + public override bool AccessibilityPerformShowMenu () { Cell.SearchButtonCell.PerformClick (this); @@ -259,20 +275,59 @@ namespace MonoDevelop.MacIntegration.MainToolbar return false; } - bool SendKeyPressed (Xwt.KeyEventArgs kargs) + void SendKeyPressed (Xwt.KeyEventArgs kargs) { if (KeyPressed != null) KeyPressed (this, kargs); + } + + internal event EventHandler<SearchEntryCommandArgs> PerformCommand; + + [Export ("control:textView:doCommandBySelector:")] + bool CommandBySelector (NSControl control, NSTextField field, ObjCRuntime.Selector sel) + { + SearchPopupCommand command; + + switch (sel.Name) { + case "moveDown:": // down arrow + command = SearchPopupCommand.NextItem; + break; + + case "moveUp:": // up arrow + command = SearchPopupCommand.PreviousItem; + break; + + case "scrollPageDown:": // page down + case "moveToEndOfDocument:": // cmd+down arrow + command = SearchPopupCommand.NextCategory; + break; + + case "scrollPageUp:": // page up + case "moveToBeginningOfDocument:": // cmd+up arrow + command = SearchPopupCommand.PreviousCategory; + break; + + case "insertNewline:": // Return + command = SearchPopupCommand.Activate; + break; + + case "cancelOperation:": // Escape + command = SearchPopupCommand.Cancel; + break; + + default: + return false; + } - return kargs.Handled; + var args = new SearchEntryCommandArgs (command); + PerformCommand?.Invoke (this, args); + return args.Handled; } - public override bool PerformKeyEquivalent (NSEvent theEvent) + [Export ("controlTextDidChange:")] + void ControlTextDidChange (NSNotification note) { - var popupHandled = SendKeyPressed (theEvent.ToXwtKeyEventArgs ()); - if (popupHandled) - return true; - return base.PerformKeyEquivalent (theEvent);; + SendKeyPressed (null); } bool ignoreEndEditing = false; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs index f6fbea24c3..326e5a8299 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Commands/CommandManager.cs @@ -92,7 +92,7 @@ namespace MonoDevelop.Components.Commands internal static readonly object CommandRouteTerminator = new object (); internal bool handlerFoundInMulticast; - Gtk.Widget lastActiveWidget; + Gtk.Widget activeWidget; #if MAC Foundation.NSObject keyMonitor; @@ -108,6 +108,9 @@ namespace MonoDevelop.Components.Commands } } + WeakReference lastCommandTarget; + internal object LastCommandTarget => lastCommandTarget?.Target; + public CommandManager (): this (null) { } @@ -2277,10 +2280,11 @@ namespace MonoDevelop.Components.Commands widget = GetFocusedChild (widget); } - if (widget != lastActiveWidget) { + if (widget != activeWidget) { if (ActiveWidgetChanged != null) - ActiveWidgetChanged (this, new ActiveWidgetEventArgs () { OldActiveWidget = lastActiveWidget, NewActiveWidget = widget }); - lastActiveWidget = widget; + ActiveWidgetChanged (this, new ActiveWidgetEventArgs () { OldActiveWidget = activeWidget, NewActiveWidget = widget }); + lastCommandTarget = new WeakReference (activeWidget); + activeWidget = widget; } return widget; } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/CommandSearchCategory.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/CommandSearchCategory.cs index cd690cdb02..9084175639 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/CommandSearchCategory.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/CommandSearchCategory.cs @@ -77,7 +77,8 @@ namespace MonoDevelop.Components.MainToolbar try { if (pattern.HasLineNumber) return; - CommandTargetRoute route = new CommandTargetRoute (MainToolbar.LastCommandTarget); + var route = new CommandTargetRoute (IdeApp.CommandService.LastCommandTarget); + var matcher = StringMatcher.GetMatcher (pattern.Pattern, false); foreach (var cmdTuple in allCommands) { diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/IMainToolbarView.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/IMainToolbarView.cs index dd6fc74ace..5722d31d1b 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/IMainToolbarView.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/IMainToolbarView.cs @@ -39,6 +39,16 @@ namespace MonoDevelop.Components.MainToolbar Stop } + public enum SearchPopupCommand + { + PreviousItem, + NextItem, + PreviousCategory, + NextCategory, + Cancel, + Activate + }; + /// <summary> /// Event arguments which specify if the event succeeded in at least one handler. /// </summary> @@ -197,6 +207,7 @@ namespace MonoDevelop.Components.MainToolbar /// Occurs when a key is pressed in the search entry. /// </summary> event EventHandler<Xwt.KeyEventArgs> SearchEntryKeyPressed; + event EventHandler<SearchEntryCommandArgs> PerformCommand; /// <summary> /// Occurs when the search entry is resized. diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbar.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbar.cs index ea36fc1404..0fc393e2fa 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbar.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbar.cs @@ -23,6 +23,8 @@ // 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. +#if !MAC + using System; using Gtk; using MonoDevelop.Components.Commands; @@ -43,6 +45,7 @@ using System.Threading; using MonoDevelop.Ide.Editor; using System.Text; using MonoDevelop.Components.AtkCocoaHelper; +using System.Diagnostics; namespace MonoDevelop.Components.MainToolbar { @@ -90,10 +93,6 @@ namespace MonoDevelop.Components.MainToolbar } } - internal static object LastCommandTarget { - get { return lastCommandTarget != null ? lastCommandTarget.Target : null; } - } - static bool RuntimeIsSeparator (TreeModel model, TreeIter iter) { var runtime = (IRuntimeModel)model.GetValue (iter, 0); @@ -271,10 +270,6 @@ namespace MonoDevelop.Components.MainToolbar button.Clicked += HandleStartButtonClicked; - IdeApp.CommandService.ActiveWidgetChanged += (sender, e) => { - lastCommandTarget = new WeakReference (e.OldActiveWidget); - }; - this.ShowAll (); this.statusArea.statusIconBox.HideAll (); } @@ -323,21 +318,54 @@ namespace MonoDevelop.Components.MainToolbar void HandleSearchEntryKeyPressed (object sender, KeyPressEventArgs e) { - if (SearchEntryKeyPressed != null) { - // TODO: Refactor this in Xwt as an extension method. - var k = (Xwt.Key)e.Event.KeyValue; - var m = Xwt.ModifierKeys.None; - if ((e.Event.State & Gdk.ModifierType.ShiftMask) != 0) - m |= Xwt.ModifierKeys.Shift; - if ((e.Event.State & Gdk.ModifierType.ControlMask) != 0) - m |= Xwt.ModifierKeys.Control; - if ((e.Event.State & Gdk.ModifierType.Mod1Mask) != 0) - m |= Xwt.ModifierKeys.Alt; - // TODO: Backport this one. - if ((e.Event.State & Gdk.ModifierType.Mod2Mask) != 0) - m |= Xwt.ModifierKeys.Command; - var kargs = new Xwt.KeyEventArgs (k, m, false, (long)e.Event.Time); - SearchEntryKeyPressed (sender, kargs); + if (PerformCommand != null) { + bool cmdPressed = (e.Event.State & Gdk.ModifierType.Mod2Mask) != 0; + SearchPopupCommand command; + + switch ((Gdk.Key)e.Event.KeyValue) { + case Gdk.Key.Down: + case Gdk.Key.downarrow: + if (cmdPressed) { + goto case Gdk.Key.Page_Down; + } + + command = SearchPopupCommand.NextItem; + break; + + case Gdk.Key.Up: + case Gdk.Key.uparrow: + if (cmdPressed) { + goto case Gdk.Key.Page_Up; + } + + command = SearchPopupCommand.PreviousItem; + break; + + case Gdk.Key.KP_Page_Down: + case Gdk.Key.Page_Down: + command = SearchPopupCommand.NextCategory; + break; + + case Gdk.Key.KP_Page_Up: + case Gdk.Key.Page_Up: + command = SearchPopupCommand.PreviousCategory; + break; + + case Gdk.Key.Escape: + command = SearchPopupCommand.Cancel; + break; + + case Gdk.Key.Return: + case Gdk.Key.KP_Enter: + command = SearchPopupCommand.Activate; + break; + + default: + return; + } + + var kargs = new SearchEntryCommandArgs (command); + PerformCommand?.Invoke (sender, kargs); e.RetVal = kargs.Handled; } } @@ -582,6 +610,7 @@ namespace MonoDevelop.Components.MainToolbar public event EventHandler SearchEntryChanged; public event EventHandler SearchEntryActivated; public event EventHandler<Xwt.KeyEventArgs> SearchEntryKeyPressed; + public event EventHandler<SearchEntryCommandArgs> PerformCommand; public event EventHandler SearchEntryResized; public event EventHandler SearchEntryLostFocus; @@ -628,3 +657,4 @@ namespace MonoDevelop.Components.MainToolbar } } +#endif
\ No newline at end of file diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbarController.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbarController.cs index e371e00cb5..b497d148ff 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbarController.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/MainToolbarController.cs @@ -77,6 +77,7 @@ namespace MonoDevelop.Components.MainToolbar ToolbarView.SearchEntryChanged += HandleSearchEntryChanged; ToolbarView.SearchEntryActivated += HandleSearchEntryActivated; ToolbarView.SearchEntryKeyPressed += HandleSearchEntryKeyPressed; + ToolbarView.PerformCommand += HandleSearchEntryCommand; ToolbarView.SearchEntryResized += (o, e) => PositionPopup (); ToolbarView.SearchEntryLostFocus += (o, e) => { ToolbarView.SearchText = ""; @@ -741,6 +742,21 @@ namespace MonoDevelop.Components.MainToolbar popup.OpenFile (); } + void HandleSearchEntryCommand (object sender, SearchEntryCommandArgs args) + { + if (args.Command == SearchPopupCommand.Cancel) { + DestroyPopup (); + var doc = IdeApp.Workbench.ActiveDocument; + if (doc != null) + doc.Select (); + return; + } + + if (popup != null) { + args.Handled = popup.ProcessCommand (args.Command); + } + } + void HandleSearchEntryKeyPressed (object sender, Xwt.KeyEventArgs e) { if (e.Key == Xwt.Key.Escape) {
@@ -1181,5 +1197,15 @@ namespace MonoDevelop.Components.MainToolbar public event EventHandler Activated; } } + + public class SearchEntryCommandArgs : HandledEventArgs + { + public SearchPopupCommand Command { get; private set; } + + public SearchEntryCommandArgs (SearchPopupCommand command) + { + Command = command; + } + } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchPopupWindow.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchPopupWindow.cs index f7a04db856..6a21e68944 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchPopupWindow.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchPopupWindow.cs @@ -81,6 +81,11 @@ namespace MonoDevelop.Components.MainToolbar Content.OpenFile (); } + internal bool ProcessCommand (SearchPopupCommand command) + { + return Content.ProcessCommand (command); + } + internal bool ProcessKey (Key key, ModifierKeys state) { return Content.ProcessKey (key, state); @@ -781,6 +786,31 @@ namespace MonoDevelop.Components.MainToolbar QueueDraw (); } + internal bool ProcessCommand (SearchPopupCommand command) + { + switch (command) { + case SearchPopupCommand.PreviousItem: + SelectItemUp (); + return true; + case SearchPopupCommand.NextItem: + SelectItemDown (); + return true; + case SearchPopupCommand.NextCategory: + SelectNextCategory (); + return true; + case SearchPopupCommand.PreviousCategory: + SelectPrevCategory (); + return true; + case SearchPopupCommand.Activate: + OnItemActivated (EventArgs.Empty); + return true; + + default: + break; + } + return false; + } + internal bool ProcessKey (Xwt.Key key, Xwt.ModifierKeys state) { switch (key) { diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchResult.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchResult.cs index 5ef61ef0ed..6115550e5a 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchResult.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.MainToolbar/SearchResult.cs @@ -269,7 +269,7 @@ namespace MonoDevelop.Components.MainToolbar return false; } Runtime.RunInMainThread (delegate { - ci = IdeApp.CommandService.GetCommandInfo (command.Id, new CommandTargetRoute (MainToolbar.LastCommandTarget)); + ci = IdeApp.CommandService.GetCommandInfo (command.Id, new CommandTargetRoute (IdeApp.CommandService.LastCommandTarget)); }).Wait (); } return ci.Enabled && ci.Visible; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs index 994699d316..676ba907bb 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Desktop/PlatformService.cs @@ -409,14 +409,21 @@ namespace MonoDevelop.Ide.Desktop internal virtual IMainToolbarView CreateMainToolbar (Gtk.Window window) { + // This is overridden by MacPlatform +#if MAC + return null; +#else return new MainToolbar (); +#endif } internal virtual void AttachMainToolbar (Gtk.VBox parent, IMainToolbarView toolbar) { +#if !MAC var toolbarBox = new Gtk.HBox (); parent.PackStart (toolbarBox, false, false, 0); toolbarBox.PackStart ((MainToolbar)toolbar, true, true, 0); +#endif } public virtual bool GetIsFullscreen (Window window) |