diff options
author | iain holmes <iain@xamarin.com> | 2017-05-31 16:42:34 +0300 |
---|---|---|
committer | iain holmes <iain@xamarin.com> | 2017-05-31 16:42:34 +0300 |
commit | 4eeb2f1cb67315ad871ac198885dac384352bdb6 (patch) | |
tree | 4fe389bde046c4678019b192b733e998d0976724 /main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking | |
parent | a4cd3ac67fc56193172464f04553d9a3af836d4c (diff) |
[A11y] Make the DockItemTitleTabs keyboard accessible
Implement tabbing through the dock items, showing their focus
Diffstat (limited to 'main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking')
-rw-r--r-- | main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockItemTitleTab.cs | 237 | ||||
-rw-r--r-- | main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/TabStrip.cs | 5 |
2 files changed, 212 insertions, 30 deletions
diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockItemTitleTab.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockItemTitleTab.cs index 1e385e7637..9d94608c51 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockItemTitleTab.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/DockItemTitleTab.cs @@ -25,7 +25,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -using Gtk; +using Gtk; using System; using MonoDevelop.Ide.Gui; @@ -38,8 +38,8 @@ using MonoDevelop.Ide.Fonts; namespace MonoDevelop.Components.Docking { - - class DockItemTitleTab: Gtk.EventBox + + class DockItemTitleTab : Gtk.EventBox { static Xwt.Drawing.Image dockTabActiveBackImage = Xwt.Drawing.Image.FromResource ("padbar-active.9.png"); static Xwt.Drawing.Image dockTabBackImage = Xwt.Drawing.Image.FromResource ("padbar-inactive.9.png"); @@ -60,6 +60,7 @@ namespace MonoDevelop.Components.Docking DockItem item; bool allowPlaceholderDocking; bool mouseOver; + Widget currentFocus = null; // Currently focused child IDisposable subscribedLeaveEvent; @@ -72,6 +73,8 @@ namespace MonoDevelop.Components.Docking static readonly Xwt.WidgetSpacing TabPadding; static readonly Xwt.WidgetSpacing TabActivePadding; + internal event EventHandler<EventArgs> TabPressed; + static DockItemTitleTab () { pixClose = Xwt.Drawing.Image.FromResource ("pad-close-9.png"); @@ -95,7 +98,7 @@ namespace MonoDevelop.Components.Docking tabActiveBackImage9 = dockTabActiveBackImage as Xwt.Drawing.NinePatchImage; TabActivePadding = tabActiveBackImage9.Padding; } - + public DockItemTitleTab (DockItem item, DockFrame frame) { var actionHandler = new ActionDelegate (this); @@ -105,6 +108,7 @@ namespace MonoDevelop.Components.Docking Accessible.SetRole (AtkCocoa.Roles.AXGroup, "pad header"); Accessible.SetSubRole ("XAPadHeader"); + CanFocus = true; this.item = item; this.frame = frame; this.VisibleWindow = false; @@ -194,10 +198,10 @@ namespace MonoDevelop.Components.Docking tabIcon = new ImageView ();
tabIcon.Accessible.SetShouldIgnore (true);
tabIcon.Show ();
- box.PackStart (tabIcon, false, false, 3);
-
- labelWidget = new ExtendedLabel (label);
- // Ignore the label because the title tab already contains its name
+ box.PackStart (tabIcon, false, false, 3); + + labelWidget = new ExtendedLabel (label); + // Ignore the label because the title tab already contains its name labelWidget.Accessible.SetShouldIgnore (true);
labelWidget.UseMarkup = true;
var alignLabel = new Alignment (0.0f, 0.5f, 1, 1);
@@ -210,8 +214,8 @@ namespace MonoDevelop.Components.Docking btnDock = new ImageButton ();
btnDock.Image = pixAutoHide;
btnDock.TooltipText = GettextCatalog.GetString ("Auto Hide");
- btnDock.CanFocus = false;
- // btnDock.WidthRequest = btnDock.HeightRequest = 17;
+ btnDock.CanFocus = true; + // btnDock.WidthRequest = btnDock.HeightRequest = 17; btnDock.Clicked += OnClickDock;
btnDock.ButtonPressEvent += (o, args) => args.RetVal = true;
btnDock.WidthRequest = btnDock.SizeRequest ().Width;
@@ -220,8 +224,8 @@ namespace MonoDevelop.Components.Docking btnClose = new ImageButton ();
btnClose.Image = pixClose;
btnClose.TooltipText = GettextCatalog.GetString ("Close");
- btnClose.CanFocus = false;
- // btnClose.WidthRequest = btnClose.HeightRequest = 17;
+ btnClose.CanFocus = true; + // btnClose.WidthRequest = btnClose.HeightRequest = 17; btnClose.WidthRequest = btnDock.SizeRequest ().Width;
btnClose.Clicked += delegate {
item.Visible = false;
@@ -250,8 +254,7 @@ namespace MonoDevelop.Components.Docking btnClose.Name = string.Format ("btnClose_{0}", labelNoSpaces ?? string.Empty);
realLabel = GettextCatalog.GetString ("Close {0}", label);
realHelp = GettextCatalog.GetString ("Close the {0} pad", label);
- }
- else {
+ } else {
labelWidget.Parent.Hide ();
realLabel = GettextCatalog.GetString ("Close pad");
realHelp = GettextCatalog.GetString ("Close the pad");
@@ -266,7 +269,7 @@ namespace MonoDevelop.Components.Docking Accessible.SetTitle (label); Accessible.SetLabel (label); } - + // Get the required size before setting the ellipsize property, since ellipsized labels // have a width request of 0 box.ShowAll (); @@ -320,7 +323,7 @@ namespace MonoDevelop.Components.Docking public int MinWidth { get { return minWidth; } } - + public bool Active { get { return active; @@ -341,7 +344,7 @@ namespace MonoDevelop.Components.Docking return page; } } - + public void UpdateBehavior () { if (btnClose == null) @@ -349,7 +352,7 @@ namespace MonoDevelop.Components.Docking btnClose.Visible = (item.Behavior & DockItemBehavior.CantClose) == 0; btnDock.Visible = (item.Behavior & DockItemBehavior.CantAutoHide) == 0; - + if (active || mouseOver) { if (btnClose.Image == null) btnClose.Image = pixClose; @@ -371,6 +374,13 @@ namespace MonoDevelop.Components.Docking bool tabPressed, tabActivated; double pressX, pressY; + protected override void OnActivate () + { + TabPressed?.Invoke (this, EventArgs.Empty); + + base.OnActivate (); + } + protected override bool OnButtonPressEvent (Gdk.EventButton evnt) { if (evnt.TriggersContextMenu ()) { @@ -381,6 +391,7 @@ namespace MonoDevelop.Components.Docking tabPressed = true; pressX = evnt.X; pressY = evnt.Y; + TabPressed?.Invoke (this, EventArgs.Empty); } else if (evnt.Type == Gdk.EventType.TwoButtonPress) { tabActivated = true; } @@ -398,8 +409,7 @@ namespace MonoDevelop.Components.Docking else item.Status = DockItemStatus.AutoHide; } - } - else if (!evnt.TriggersContextMenu () && evnt.Button == 1) { + } else if (!evnt.TriggersContextMenu () && evnt.Button == 1) { frame.DockInPlaceholder (item); frame.HidePlaceholder (); if (GdkWindow != null) @@ -411,15 +421,25 @@ namespace MonoDevelop.Components.Docking return base.OnButtonReleaseEvent (evnt); } + protected override bool OnFocusInEvent (Gdk.EventFocus evnt) + { + mouseOver = true; + UpdateBehavior (); + return base.OnFocusInEvent (evnt); + } + + protected override bool OnFocusOutEvent (Gdk.EventFocus evnt) + { + if (currentFocus == null) { + mouseOver = false; + UpdateBehavior (); + } + return base.OnFocusOutEvent (evnt); + } + void HandlePress (object sender, EventArgs args) { - // FIXME: How to support double click? - frame.DockInPlaceholder (item); - frame.HidePlaceholder (); - if (GdkWindow != null) - GdkWindow.Cursor = null; - frame.Toplevel.KeyPressEvent -= HeaderKeyPress; - frame.Toplevel.KeyReleaseEvent -= HeaderKeyRelease; + TabPressed?.Invoke (this, EventArgs.Empty); } void HandleShowMenu (object sender, EventArgs args) @@ -455,6 +475,163 @@ namespace MonoDevelop.Components.Docking UpdateBehavior (); } + enum FocusWidget + { + None, + Widget, + DockButton, + CloseButton + }; + + bool FocusCurrentWidget (DirectionType direction) + { + if (currentFocus == null) { + return false; + } + + return currentFocus.ChildFocus (direction); + } + + bool MoveFocusToWidget (FocusWidget widget, DirectionType direction) + { + switch (widget) { + case FocusWidget.Widget: + GrabFocus (); + currentFocus = null; + return true; + + case FocusWidget.DockButton: + currentFocus = btnDock; + return btnDock.ChildFocus (direction); + + case FocusWidget.CloseButton: + currentFocus = btnClose; + return btnClose.ChildFocus (direction); + + case FocusWidget.None: + break; + } + + return false; + } + + FocusWidget GetNextWidgetToFocus (FocusWidget widget, DirectionType direction) { + FocusWidget nextSite; + + switch (widget) { + case FocusWidget.CloseButton: + switch (direction) { + case DirectionType.TabForward: + case DirectionType.Right: + case DirectionType.Down: + return FocusWidget.None; + + case DirectionType.TabBackward: + case DirectionType.Left: + case DirectionType.Up: + if (btnDock.Image != null) { + nextSite = FocusWidget.DockButton; + } else { + nextSite = FocusWidget.Widget; + } + return nextSite; + } + + break; + + case FocusWidget.DockButton: + switch (direction) { + case DirectionType.TabForward: + case DirectionType.Right: + case DirectionType.Down: + return btnClose.Image == null ? FocusWidget.None : FocusWidget.CloseButton; + + case DirectionType.TabBackward: + case DirectionType.Left: + case DirectionType.Up: + return FocusWidget.Widget; + } + + break; + + case FocusWidget.Widget: + switch (direction) { + case DirectionType.TabForward: + case DirectionType.Right: + case DirectionType.Down: + if (btnDock.Image != null) { + nextSite = FocusWidget.DockButton; + } else if (btnClose.Image != null) { + nextSite = FocusWidget.CloseButton; + } else { + nextSite = FocusWidget.None; + } + return nextSite; + + case DirectionType.TabBackward: + case DirectionType.Left: + case DirectionType.Up: + return FocusWidget.None; + } + + break; + case FocusWidget.None: + switch (direction) { + case DirectionType.TabForward: + case DirectionType.Right: + case DirectionType.Down: + return FocusWidget.Widget; + + case DirectionType.TabBackward: + case DirectionType.Left: + case DirectionType.Up: + if (btnClose.Image != null) { + nextSite = FocusWidget.CloseButton; + } else if (btnDock.Image != null) { + nextSite = FocusWidget.DockButton; + } else { + nextSite = FocusWidget.Widget; + } + return nextSite; + } + + break; + } + + return FocusWidget.None; + } + + protected override bool OnFocused (DirectionType direction) + { + if (!FocusCurrentWidget (direction)) { + FocusWidget focus = FocusWidget.None; + + if (currentFocus == btnClose) { + focus = FocusWidget.CloseButton; + } else if (currentFocus == btnDock) { + focus = FocusWidget.DockButton; + } else if (IsFocus) { + focus = FocusWidget.Widget; + } + + while ((focus = GetNextWidgetToFocus (focus, direction)) != FocusWidget.None) { + if (MoveFocusToWidget (focus, direction)) { + return true; + } + } + + // Clean up the icons because OnFocusOutEvent has already been called + // so we need the icons to hide again + mouseOver = false; + UpdateBehavior (); + + currentFocus = null; + return false; + } + + return true; + } + [GLib.ConnectBeforeAttribute] void HeaderKeyPress (object ob, Gtk.KeyPressEventArgs a) { @@ -527,6 +704,12 @@ namespace MonoDevelop.Components.Docking DrawAsBrowser (evnt); else DrawNormal (evnt); + + if (HasFocus) { + var alloc = labelWidget.Allocation; + Gtk.Style.PaintFocus (Style, GdkWindow, State, alloc, this, "label", + alloc.X, alloc.Y, alloc.Width, alloc.Height); + } return base.OnExposeEvent (evnt); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/TabStrip.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/TabStrip.cs index 82dc1dd48e..c9e986f21a 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/TabStrip.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.Docking/TabStrip.cs @@ -94,7 +94,7 @@ namespace MonoDevelop.Components.Docking tab.Page.Hide (); } - tab.ButtonPressEvent += OnTabPress; + tab.TabPressed += OnTabPress; UpdateAccessibilityTabs (); } @@ -198,13 +198,12 @@ namespace MonoDevelop.Components.Docking box.Remove (w); } - void OnTabPress (object s, Gtk.ButtonPressEventArgs args) + void OnTabPress (object s, EventArgs args) { CurrentTab = Array.IndexOf (box.Children, s); DockItemTitleTab t = (DockItemTitleTab) s; DockItem.SetFocus (t.Page); QueueDraw (); - args.RetVal = true; } protected override void OnSizeAllocated (Gdk.Rectangle allocation) |