diff options
author | Jose Medrano <jose.medrano@microsoft.com> | 2019-05-10 05:27:18 +0300 |
---|---|---|
committer | Jose Medrano <josmed@microsoft.com> | 2019-07-29 13:00:40 +0300 |
commit | f4cf5afcb68c141d666b0c20bb495d04fc8ae36b (patch) | |
tree | 481fd956b0a8197b1cdf619b5c1c7587d0bd0eb3 /main/src/core/MonoDevelop.Ide | |
parent | 5a8dda4e6daabe1182f28562b0ae894a137a24f4 (diff) |
[Ide] Adds feature in MonoDevelop to pin opened tabs
This feature allows to lock some of your most used windows (tabs)
to easily detect and open like VS2013 added.
Fixes VSTS #890509 - Add support for pinned tabs
- Adds Pin/Unpin tab feature clicking right button (contextual menu) in tab or Shortcut key
-Don't call ping events if isPinned value has no change
-Reuses current CloseAllHandler logic and make its more flexible
-Fixes changes to use a ImmutableArray<ViewContent> instead a List
-Adds a new Tab and Windows options panel and usage of ConfigurationProperty
-Removes nested lambda with capture.
-Removes an extra array conversion and makes the code clearer
-Removes extra allocations using Min/MaxValue instead OrderBy
-Uses a for loop instead a copied collection with a ForEach
Diffstat (limited to 'main/src/core/MonoDevelop.Ide')
19 files changed, 354 insertions, 45 deletions
diff --git a/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml b/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml index 70c27e8047..2e696441b6 100644 --- a/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml +++ b/main/src/core/MonoDevelop.Ide/ExtensionModel/Commands.addin.xml @@ -543,6 +543,18 @@ defaultHandler = "MonoDevelop.Ide.Commands.ReopenClosedTabHandler" _label = "Reopen Closed Tab" _description = "Opens the last tab that has been closed"/> + <Command id = "MonoDevelop.Ide.Commands.FileTabCommands.CloseAllExceptPinned" + defaultHandler = "MonoDevelop.Ide.Commands.CloseAllExceptPinnedHandler" + _label = "Close All Except _Pinned" + _description = "Close all files except pinned" + shortcut = "Control|Shift|P" + macShortcut = "Meta|Shift|P" /> + <Command id = "MonoDevelop.Ide.Commands.FileTabCommands.PinTab" + defaultHandler = "MonoDevelop.Ide.Commands.PinTabHandler" + _label = "_Pin Tab" + _description = "Pin/Unpin current Tab selected" + shortcut = "Control|Alt|P" + macShortcut = "Meta|Alt|P" /> </Category> <!-- ViewCommands --> diff --git a/main/src/core/MonoDevelop.Ide/ExtensionModel/GlobalOptionsDialog.addin.xml b/main/src/core/MonoDevelop.Ide/ExtensionModel/GlobalOptionsDialog.addin.xml index f89f61d487..352a1b8314 100644 --- a/main/src/core/MonoDevelop.Ide/ExtensionModel/GlobalOptionsDialog.addin.xml +++ b/main/src/core/MonoDevelop.Ide/ExtensionModel/GlobalOptionsDialog.addin.xml @@ -13,6 +13,7 @@ <Section id = "Fonts" _label = "Fonts" fill="true" class = "MonoDevelop.Ide.Fonts.FontChooserPanel" icon = "md-prefs-fonts" /> <Section id = "Updates" _label = "Updates" class = "MonoDevelop.Ide.Gui.OptionPanels.AddInsOptionsPanel" icon="md-prefs-updates" /> <Section id = "TaskList" _label = "Tasks" fill="true" class = "MonoDevelop.Ide.Gui.OptionPanels.TasksOptionsPanel" icon="md-prefs-task-list" /> + <Section id = "TabsWindows" _label = "Tabs and Windows" fill="true" class = "MonoDevelop.Ide.Projects.OptionPanels.TabsWindowOptionsPanel" icon="md-prefs-metadata" /> <Section id = "ExternalTools" _label = "External Tools" fill="true" class = "MonoDevelop.Ide.ExternalTools.ExternalToolPane" icon="md-prefs-external-tools" /> </Section> diff --git a/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml b/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml index 70690194fb..c25b4e1b7c 100644 --- a/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml +++ b/main/src/core/MonoDevelop.Ide/ExtensionModel/MonoDevelop.Ide.addin.xml @@ -348,8 +348,10 @@ <Extension path = "/MonoDevelop/Ide/ContextMenu/DocumentTab"> <CommandItem id = "MonoDevelop.Ide.Commands.FileCommands.CloseFile" /> <CommandItem id = "MonoDevelop.Ide.Commands.FileTabCommands.CloseAll" /> + <CommandItem id = "MonoDevelop.Ide.Commands.FileTabCommands.CloseAllExceptPinned" /> <CommandItem id = "MonoDevelop.Ide.Commands.FileTabCommands.CloseAllButThis" /> <CommandItem id = "MonoDevelop.Ide.Commands.FileTabCommands.CloseAllToTheRight" /> + <CommandItem id = "MonoDevelop.Ide.Commands.FileTabCommands.PinTab" /> <CommandItem id = "MonoDevelop.Ide.Commands.FileTabCommands.ReopenClosedTab" /> <SeparatorItem id = "CloseSeparator" /> <CommandItem id = "MonoDevelop.Ide.Commands.FileCommands.Save" /> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebook.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebook.cs index b12ecad3fb..bd13649cf9 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebook.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebook.cs @@ -36,6 +36,7 @@ using MonoDevelop.Ide; using MonoDevelop.Components.AtkCocoaHelper; using MonoDevelop.Core; using MonoDevelop.Ide.Gui.Shell; +using System.Linq; namespace MonoDevelop.Components.DockNotebook { @@ -111,6 +112,23 @@ namespace MonoDevelop.Components.DockNotebook }; allNotebooks.Add (this); + + tabStrip.IsPinEnabled = IdeApp.Preferences.EnablePinnedTabs.Value; + RefreshCurrentTabStrip (); + + IdeApp.Preferences.EnablePinnedTabs.Changed += EnablePinnedTabs_Changed; + } + + void EnablePinnedTabs_Changed (object sender, EventArgs e) => RefreshCurrentTabStrip (); + + void RefreshCurrentTabStrip () + { + tabStrip.IsPinEnabled = IdeApp.Preferences.EnablePinnedTabs.Value; + if (!tabStrip.IsPinEnabled) { + for (int i = 0; i < pages.Count; i++) { + pages[i].IsPinned = false; + } + } } public static DockNotebook ActiveNotebook { @@ -136,6 +154,7 @@ namespace MonoDevelop.Components.DockNotebook public event TabsReorderedHandler TabsReordered; public event EventHandler<TabEventArgs> TabClosed; + public event EventHandler<TabEventArgs> TabPinned; public event EventHandler<TabEventArgs> TabActivated; public event EventHandler<TabEventArgs> PageAdded; @@ -349,6 +368,8 @@ namespace MonoDevelop.Components.DockNotebook PageAdded?.Invoke (this, new TabEventArgs { Tab = tab, }); + tab.OnChangingPinned = OnTabPinned; + NotebookChanged?.Invoke (this, EventArgs.Empty); return tab; @@ -360,6 +381,24 @@ namespace MonoDevelop.Components.DockNotebook ((DockNotebookTab)pages [n]).Index = n; } + void OnTabPinned (DockNotebookTab sender, bool value) + { + if (pages.Count == 1) + return; + + var stickedPages = pages.Where (p => p.IsPinned); + var normalPages = pages.Where (p => !p.IsPinned); + + if (value) { + if (stickedPages.Any ()) + ReorderTab (sender, normalPages.MinValueOrDefault (s => s.Index) ?? stickedPages.MaxValueOrDefault (s => s.Index), false); + else + ReorderTab (sender, pages.FirstOrDefault (), false); + } else { + ReorderTab (sender, stickedPages.MaxValueOrDefault (s => s.Index) ?? normalPages.MinValueOrDefault (s => s.Index), false); + } + } + public DockNotebookTab GetTab (int n) { if (n < 0 || n >= pages.Count) @@ -388,8 +427,11 @@ namespace MonoDevelop.Components.DockNotebook NotebookChanged?.Invoke (this, EventArgs.Empty); } - internal void ReorderTab (DockNotebookTab tab, DockNotebookTab targetTab) + internal void ReorderTab (DockNotebookTab tab, DockNotebookTab targetTab, bool pinCheck = true) { + if (pinCheck && tab.IsPinned != targetTab.IsPinned) + return; + if (tab == targetTab) return; int targetPos = targetTab.Index; @@ -412,6 +454,12 @@ namespace MonoDevelop.Components.DockNotebook TabClosed (this, new TabEventArgs () { Tab = tab }); } + internal void OnPinTab (DockNotebookTab tab) + { + if (TabPinned != null) + TabPinned (this, new TabEventArgs () { Tab = tab }); + } + internal void OnActivateTab (DockNotebookTab tab) { if (TabActivated != null) @@ -442,6 +490,12 @@ namespace MonoDevelop.Components.DockNotebook } base.OnDestroyed (); } + + public override void Dispose () + { + IdeApp.Preferences.EnablePinnedTabs.Changed -= EnablePinnedTabs_Changed; + base.Dispose (); + } } class DockNotebookChangedArgs : EventArgs diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebookTab.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebookTab.cs index fbd6a1ed22..731d75ad41 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebookTab.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/DockNotebookTab.cs @@ -35,6 +35,9 @@ namespace MonoDevelop.Components.DockNotebook { class DockNotebookTab: IAnimatable, IDisposable { + public System.Action<DockNotebookTab, bool> OnChangingPinned; + public System.Action<DockNotebookTab, bool> OnChangedPinned; + DockNotebook notebook; readonly TabStrip strip; @@ -47,6 +50,8 @@ namespace MonoDevelop.Components.DockNotebook Xwt.Drawing.Image icon; Widget content; + internal Cairo.Rectangle PinButtonActiveArea; + Gdk.Rectangle allocation; internal Gdk.Rectangle Allocation { get { @@ -113,6 +118,20 @@ namespace MonoDevelop.Components.DockNotebook public double DirtyStrength { get; set; } + bool isPinned; + public bool IsPinned { + get { return isPinned; } + set { + if (isPinned == value) + return; + if (OnChangingPinned != null) + OnChangingPinned (this, value); + isPinned = value; + if (OnChangedPinned != null) + OnChangedPinned (this, value); + } + } + void IAnimatable.BatchBegin () { } void IAnimatable.BatchCommit () { QueueDraw (); } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/TabStrip.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/TabStrip.cs index 7f7743b443..e8df4e9be6 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/TabStrip.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Components.DockNotebook/TabStrip.cs @@ -37,6 +37,7 @@ using MonoDevelop.Components.Docking; using MonoDevelop.Ide.Gui; using MonoDevelop.Ide; using System.Runtime.InteropServices; +using MonoDevelop.Ide.Editor; namespace MonoDevelop.Components.DockNotebook { @@ -48,6 +49,8 @@ namespace MonoDevelop.Components.DockNotebook static Xwt.Drawing.Image tabBackImage = Xwt.Drawing.Image.FromResource ("tabbar-inactive.9.png"); static Xwt.Drawing.Image tabbarBackImage = Xwt.Drawing.Image.FromResource ("tabbar-back.9.png"); static Xwt.Drawing.Image tabCloseImage = Xwt.Drawing.Image.FromResource ("tab-close-9.png"); + static Xwt.Drawing.Image tabPinnedImage = Xwt.Drawing.Image.FromResource ("tab-pinned-9.png"); + static Xwt.Drawing.Image tabUnPinnedImage = Xwt.Drawing.Image.FromResource ("tab-unpinned-9.png"); static Xwt.Drawing.Image tabDirtyImage = Xwt.Drawing.Image.FromResource ("tab-dirty-9.png"); HBox innerBox; @@ -84,8 +87,11 @@ namespace MonoDevelop.Components.DockNotebook static readonly int VerticalTextSize = 11; const int TabSpacing = 0; const int LeanWidth = 12; + const int ButtonSize = 14; const double CloseButtonMarginRight = 0; const double CloseButtonMarginBottom = -1.0; + const double PinButtonMarginRight = 0; + const double PinButtonMarginBottom = -1.0; const int TextOffset = 1; @@ -109,6 +115,8 @@ namespace MonoDevelop.Components.DockNotebook } } + public bool IsPinEnabled { get; set; } + public bool NavigationButtonsVisible { get { return NextButton.Visible; } set { @@ -235,7 +243,7 @@ namespace MonoDevelop.Components.DockNotebook tab.AccessibilityPressCloseButton += OnAccessibilityPressCloseButton; tab.AccessibilityShowMenu += OnAccessibilityShowMenu; } - }
+ } UpdateAccessibilityTabs (); notebook.PageAdded += PageAddedHandler; notebook.PageRemoved += PageRemovedHandler; @@ -244,6 +252,11 @@ namespace MonoDevelop.Components.DockNotebook closingTabs = new Dictionary<int, DockNotebookTab> (); } + void EnablePinnedTabs_Changed (object sender, EventArgs e) + { + IsPinEnabled = IdeApp.Preferences.EnablePinnedTabs; + } + protected override void OnDestroyed () { this.AbortAnimation ("TabWidth"); @@ -254,14 +267,14 @@ namespace MonoDevelop.Components.DockNotebook void PageAddedHandler (object sender, TabEventArgs args) { - var tab = args.Tab;
+ var tab = args.Tab; - if (tab.Accessible != null) {
+ if (tab.Accessible != null) { Accessible.AddAccessibleElement (tab.Accessible); - Accessible.AddAccessibleElement (tab.CloseButtonAccessible);
-
- tab.AccessibilityPressTab += OnAccessibilityPressTab;
- tab.AccessibilityPressCloseButton += OnAccessibilityPressCloseButton;
+ Accessible.AddAccessibleElement (tab.CloseButtonAccessible); + + tab.AccessibilityPressTab += OnAccessibilityPressTab; + tab.AccessibilityPressCloseButton += OnAccessibilityPressCloseButton; tab.AccessibilityShowMenu += OnAccessibilityShowMenu; } @@ -272,13 +285,13 @@ namespace MonoDevelop.Components.DockNotebook void PageRemovedHandler (object sender, TabEventArgs args) { - var tab = args.Tab;
+ var tab = args.Tab; + + if (tab.Accessible != null) { + tab.AccessibilityPressTab -= OnAccessibilityPressTab; + tab.AccessibilityPressCloseButton -= OnAccessibilityPressCloseButton; + tab.AccessibilityShowMenu -= OnAccessibilityShowMenu; - if (tab.Accessible != null) {
- tab.AccessibilityPressTab -= OnAccessibilityPressTab;
- tab.AccessibilityPressCloseButton -= OnAccessibilityPressCloseButton;
- tab.AccessibilityShowMenu -= OnAccessibilityShowMenu;
-
Accessible.RemoveAccessibleElement (tab.Accessible); Accessible.RemoveAccessibleElement (tab.CloseButtonAccessible); } @@ -290,11 +303,11 @@ namespace MonoDevelop.Components.DockNotebook UpdateAccessibilityTabs (); } - void PageReorderedHandler (DockNotebookTab tab, int oldPlacement, int newPlacement)
+ void PageReorderedHandler (DockNotebookTab tab, int oldPlacement, int newPlacement) { QueueResize (); - UpdateAccessibilityTabs ();
+ UpdateAccessibilityTabs (); } void UpdateAccessibilityTabs () @@ -553,6 +566,7 @@ namespace MonoDevelop.Components.DockNotebook return base.OnMotionNotifyEvent (evnt); } + bool overPinOnPress; bool overCloseOnPress; bool allowDoubleClick; @@ -573,6 +587,13 @@ namespace MonoDevelop.Components.DockNotebook } overCloseOnPress = false; + // Don't select the tab if we are clicking the pin button + if (IsOverPinButton (t, (int)evnt.X, (int)evnt.Y)) { + overPinOnPress = true; + return true; + } + overPinOnPress = false; + if (evnt.Type == EventType.TwoButtonPress) { if (allowDoubleClick) { notebook.OnActivateTab (t); @@ -604,15 +625,22 @@ namespace MonoDevelop.Components.DockNotebook return base.OnButtonReleaseEvent (evnt); } - if (!draggingTab && overCloseOnPress) { + if (!draggingTab) { var t = FindTab ((int)evnt.X, (int)evnt.Y); - if (t != null && IsOverCloseButton (t, (int)evnt.X, (int)evnt.Y)) { + if (t != null && overCloseOnPress && IsOverCloseButton (t, (int)evnt.X, (int)evnt.Y)) { notebook.OnCloseTab (t); allowDoubleClick = false; return true; + } else if (IsPinEnabled && t != null && overPinOnPress && IsOverPinButton (t, (int)evnt.X, (int)evnt.Y)) { + t.IsPinned = !t.IsPinned; + notebook.OnPinTab (t); + allowDoubleClick = false; + QueueDraw (); + return true; } } overCloseOnPress = false; + overPinOnPress = false; allowDoubleClick = true; if (dragX != 0) this.Animate ("EndDrag", @@ -935,6 +963,7 @@ namespace MonoDevelop.Components.DockNotebook // Cancel drag operations and animations buttonPressedOnTab = false; overCloseOnPress = false; + overPinOnPress = false; allowDoubleClick = true; draggingTab = false; dragX = 0; @@ -966,6 +995,11 @@ namespace MonoDevelop.Components.DockNotebook return tab != null && tab.CloseButtonActiveArea.Contains (x, y); } + static bool IsOverPinButton (DockNotebookTab tab, int x, int y) + { + return tab != null && tab.PinButtonActiveArea.Contains (x, y); + } + public void Update () { if (!tracker.Hovered) { @@ -1145,7 +1179,7 @@ namespace MonoDevelop.Components.DockNotebook leftPadding = (leftPadding * Math.Min (1.0, Math.Max (0.5, (tabBounds.Width - 30) / 70.0))); double bottomPadding = active ? TabActivePadding.Bottom : TabPadding.Bottom; - DrawTabBackground (this, ctx, allocation, tabBounds.Width, tabBounds.X, active); + DrawTabBackground (this, ctx, allocation, tabBounds.Width, tabBounds.X, active, IsPinEnabled ? tab.IsPinned : false); ctx.LineWidth = 1; ctx.NewPath (); @@ -1158,9 +1192,17 @@ namespace MonoDevelop.Components.DockNotebook tab.CloseButtonActiveArea = closeButtonAlloation.Inflate (2, 2); + var spinButtonAllocation = new Cairo.Rectangle (closeButtonAlloation.X - rightPadding - PinButtonMarginRight, + closeButtonAlloation.Y, + tabPinnedImage.Width, tabPinnedImage.Height); + + tab.PinButtonActiveArea = spinButtonAllocation.Inflate (2, 2); + bool closeButtonHovered = tracker.Hovered && tab.CloseButtonActiveArea.Contains (tracker.MousePosition); + bool pinButtonHovered = tracker.Hovered && tab.PinButtonActiveArea.Contains (tracker.MousePosition); bool tabHovered = tracker.Hovered && tab.Allocation.Contains (tracker.MousePosition); - bool drawCloseButton = active || tabHovered || focused; + bool drawCloseButton = (IsPinEnabled && tab.IsPinned) || (active || tabHovered || focused); + bool drawPinButton = IsPinEnabled && (tab.IsPinned || tabHovered); if (!closeButtonHovered && tab.DirtyStrength > 0.5) { ctx.DrawImage (this, tabDirtyImage, closeButtonAlloation.X, closeButtonAlloation.Y); @@ -1170,11 +1212,17 @@ namespace MonoDevelop.Components.DockNotebook if (drawCloseButton) ctx.DrawImage (this, tabCloseImage.WithAlpha ((closeButtonHovered ? 1.0 : 0.5) * tab.Opacity), closeButtonAlloation.X, closeButtonAlloation.Y); + if (drawPinButton) + ctx.DrawImage (this, (tab.IsPinned ? tabPinnedImage : tabUnPinnedImage).WithAlpha ((pinButtonHovered ? 1.0 : 0.5) * tab.Opacity), spinButtonAllocation.X, spinButtonAllocation.Y); + // Render Text double tw = tabBounds.Width - (leftPadding + rightPadding); if (drawCloseButton || tab.DirtyStrength > 0.5) tw -= closeButtonAlloation.Width / 2; + if (drawPinButton || tab.DirtyStrength > 0.5) + tw -= spinButtonAllocation.Width / 2 + rightPadding; + double tx = tabBounds.X + leftPadding; var baseline = la.GetLine (0).Layout.GetPixelBaseline (); double ty = tabBounds.Height - bottomPadding - baseline; @@ -1199,11 +1247,11 @@ namespace MonoDevelop.Components.DockNotebook ctx.SetSource (lg); Pango.CairoHelper.ShowLayout (ctx, la.GetLine (0).Layout); } - }
+ } la.Dispose (); } - static void DrawTabBackground (Widget widget, Context ctx, Gdk.Rectangle allocation, int contentWidth, int px, bool active = true) + static void DrawTabBackground (Widget widget, Context ctx, Gdk.Rectangle allocation, int contentWidth, int px, bool active = true, bool isPinned = false) { int lean = Math.Min (LeanWidth, contentWidth / 2); int halfLean = lean / 2; diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs index c2b43f1433..bc209f4124 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileCommands.cs @@ -444,4 +444,5 @@ namespace MonoDevelop.Ide.Commands // MonoDevelop.Ide.Commands.CopyPathNameHandler Implemented in FileTabCommands.cs // MonoDevelop.Ide.Commands.FileTabCommands.ToggleMaximize Implemented in FileTabCommands.cs // MonoDevelop.Ide.Commands.FileTabCommands.ReopenClosedTab Implemented in FileTabCommands.cs + // MonoDevelop.Ide.Commands.FileTabCommands.CloseAllExceptPinned Implemented in FileTabCommands.cs } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileTabCommands.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileTabCommands.cs index 65ade3f49b..468c7b5850 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileTabCommands.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Commands/FileTabCommands.cs @@ -39,6 +39,10 @@ using MonoDevelop.Ide.Gui.Dialogs; using MonoDevelop.Ide.Gui.Documents; using MonoDevelop.Ide.Gui.Shell; +using MonoDevelop.Components.DockNotebook; +using System.Collections.Immutable; +using MonoDevelop.Core; + namespace MonoDevelop.Ide.Commands { public enum FileTabCommands @@ -48,14 +52,26 @@ namespace MonoDevelop.Ide.Commands CopyPathName, ToggleMaximize, ReopenClosedTab, - CloseAllToTheRight + CloseAllToTheRight, + CloseAllExceptPinned, + PinTab, } - class CloseAllHandler : CommandHandler + class CloseAllHandler : TabCommandHandler { - protected virtual Document GetDocumentException () + protected virtual ImmutableArray<Document> GetDocumentExceptions () + { + return ImmutableArray<Document>.Empty; + } + + bool HasDistinctViewContent (ImmutableArray<Document> viewContents, Document document) { - return null; + for (int i = 0; i < viewContents.Length; i++) { + if (document.Window.Document == viewContents[i]) { + return false; + } + } + return true; } protected virtual bool StartAfterException => false; @@ -67,15 +83,15 @@ namespace MonoDevelop.Ide.Commands return; var activeNotebook = ((SdiWorkspaceWindow)active.Window).TabControl; - var except = GetDocumentException (); + var except = GetDocumentExceptions (); var docs = new List<Document> (); var dirtyDialogShown = false; - var startRemoving = except == null || !StartAfterException; + var startRemoving = !except.Any () || !StartAfterException; foreach (var doc in IdeApp.Workbench.Documents) { if (((SdiWorkspaceWindow)doc.Window).TabControl == activeNotebook) { - if (except != null && doc == except) { + if (except.Any () && !HasDistinctViewContent (except, doc)) { startRemoving = true; continue; } @@ -102,34 +118,76 @@ namespace MonoDevelop.Ide.Commands } } - class CloseAllButThisHandler : CloseAllHandler + abstract class TabCommandHandler : CommandHandler { - protected override Document GetDocumentException () + protected DockNotebookTab GetTabFromDocument (Document document) { - return IdeApp.Workbench.ActiveDocument; + var activeWindow = (SdiWorkspaceWindow)document.Window; + var tabControl = activeWindow.TabControl; + return tabControl.Tabs.FirstOrDefault (item => (item.Content as SdiWorkspaceWindow).Equals (activeWindow)); + } + + protected DockNotebookTab GetTabFromActiveDocument () + { + var active = IdeApp.Workbench.ActiveDocument; + if (active == null) + return null; + return GetTabFromDocument (active); } + } + class CloseAllExceptPinnedHandler : CloseAllHandler + { protected override void Update (CommandInfo info) { - var documents = IdeApp.Workbench.Documents; - var activeDoc = IdeApp.Workbench.ActiveDocument; + info.Visible = info.Enabled = IdeApp.Preferences.EnablePinnedTabs && IdeApp.Workbench.Documents.Count != 0; + } - if (activeDoc == null) { - info.Enabled = false; + protected override ImmutableArray<Document> GetDocumentExceptions () + { + var active = IdeApp.Workbench.ActiveDocument; + if (active == null) + return ImmutableArray<Document>.Empty; + var activeNotebook = ((SdiWorkspaceWindow)active.Window).TabControl; + + var contents = IdeApp.Workbench.Documents.Where (doc => ((SdiWorkspaceWindow)doc.Window).TabControl == activeNotebook && GetTabFromDocument (doc).IsPinned) + .Select (s => s.Window.Document); + + return contents.ToImmutableArray (); + } + } + + class PinTabHandler : TabCommandHandler + { + protected override void Update (CommandInfo info) + { + info.Visible = info.Enabled = IdeApp.Preferences.EnablePinnedTabs && IdeApp.Workbench.ActiveDocument != null; + if (!info.Visible) return; + + var selectedTab = GetTabFromActiveDocument (); + if (selectedTab != null) { + info.Text = (selectedTab.IsPinned) ? GettextCatalog.GetString ("Un_pin Tab") : GettextCatalog.GetString ("_Pin Tab"); } + } - var activeNotebook = ((SdiWorkspaceWindow)activeDoc.Window).TabControl; + protected override void Run () + { + var selectedTab = GetTabFromActiveDocument (); + if (selectedTab != null) + selectedTab.IsPinned = !selectedTab.IsPinned; + } + } - // Disable if only document in tab strip - foreach (var doc in documents) { - if (doc != activeDoc && ((SdiWorkspaceWindow)doc.Window).TabControl == activeNotebook) { - info.Enabled = true; - return; - } + class CloseAllButThisHandler : CloseAllHandler + { + protected override ImmutableArray<Document> GetDocumentExceptions () + { + var active = IdeApp.Workbench.ActiveDocument; + if (active == null) { + return ImmutableArray<Document>.Empty; } - - info.Enabled = false; + return ImmutableArray.Create (active.Window.Document); } } diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/TabsWindowOptionsPanel.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/TabsWindowOptionsPanel.cs new file mode 100644 index 0000000000..270addfce6 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.Projects.OptionPanels/TabsWindowOptionsPanel.cs @@ -0,0 +1,104 @@ +// +// TabsWindowOptionsPanel.cs +// +// Author: +// jmedrano <josmed@microsoft.com> +// +// Copyright (c) 2019 +// +// 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 Xwt; +using MonoDevelop.Components; +using MonoDevelop.Ide.Gui.Dialogs; +using MonoDevelop.Projects; +using MonoDevelop.Core; + +namespace MonoDevelop.Ide.Projects.OptionPanels +{ + public class TabsWindowOptionsPanel : ItemOptionsPanel + { + TabsWindowOptionsWidget widget; + + public TabsWindowOptionsPanel () + { + } + + public override void ApplyChanges () + { + widget.Store (); + } + + public override Control CreatePanelWidget () + { + widget = new TabsWindowOptionsWidget (ConfiguredProject, ParentDialog); + return new XwtControl (widget); + } + + } + + class TabsWindowOptionsWidget : Widget + { + private readonly Project configuredProject; + private readonly OptionsDialog parentDialog; + const int margin = 12; + + CheckBox enablePinnedTabsCheckbox; + + public TabsWindowOptionsWidget (Project configuredProject, OptionsDialog parentDialog) + { + this.configuredProject = configuredProject; + this.parentDialog = parentDialog; + + var mainContainer = new VBox (); + mainContainer.PackStart (new Label { Markup = string.Format ("<b>{0}</b>", GettextCatalog.GetString ("Pinned Tabs")) }); + + enablePinnedTabsCheckbox = new CheckBox () { AllowMixed = false }; + + var enableTabsContainer = new HBox (); + mainContainer.PackStart (enableTabsContainer); + enableTabsContainer.PackStart (enablePinnedTabsCheckbox); + enableTabsContainer.PackStart (new Label { Text = GettextCatalog.GetString ("Enable pin a tab in document bar") }); + + Content = mainContainer; + + enablePinnedTabsCheckbox.State = IdeApp.Preferences.EnablePinnedTabs.Value ? CheckBoxState.On : CheckBoxState.Off; + enablePinnedTabsCheckbox.Toggled += EnablePinnedTabsCheckbox_Toggled; + + Show (); + } + + void EnablePinnedTabsCheckbox_Toggled (object sender, EventArgs e) + { + Store (); + } + + internal void Store () + { + IdeApp.Preferences.EnablePinnedTabs.Value = enablePinnedTabsCheckbox.State == CheckBoxState.On; + } + + protected override void Dispose (bool disposing) + { + enablePinnedTabsCheckbox.Clicked -= EnablePinnedTabsCheckbox_Toggled; + base.Dispose (disposing); + } + } + +} diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj index 85a485f1db..192389802d 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide.csproj @@ -142,6 +142,14 @@ <EmbeddedResource Include="templates\EmptyStruct.xft.xml" /> <EmbeddedResource Include="templates\EmptyTextFile.xft.xml" /> <EmbeddedResource Include="templates\EmptyXMLFile.xft.xml" /> + <EmbeddedResource Include="icons\tab-pinned-9.png" /> + <EmbeddedResource Include="icons\tab-pinned-9%402x.png" /> + <EmbeddedResource Include="icons\tab-pinned-9~dark.png" /> + <EmbeddedResource Include="icons\tab-pinned-9~dark%402x.png" /> + <EmbeddedResource Include="icons\tab-unpinned-9.png" /> + <EmbeddedResource Include="icons\tab-unpinned-9%402x.png" /> + <EmbeddedResource Include="icons\tab-unpinned-9~dark.png" /> + <EmbeddedResource Include="icons\tab-unpinned-9~dark%402x.png" /> <EmbeddedResource Include="icons\reference-assembly-16.png" /> <EmbeddedResource Include="icons\reference-assembly-16%402x.png" /> <EmbeddedResource Include="icons\reference-assembly-16~dark.png" /> @@ -4259,6 +4267,7 @@ <Compile Include="MonoDevelop.Components\Mac\NSStackViewExtensions.cs" /> <Compile Include="MonoDevelop.Components\Mac\NSLabel.cs" /> <Compile Include="MonoDevelop.Components\Mac\NSLine.cs" /> + <Compile Include="MonoDevelop.Ide.Projects.OptionPanels\TabsWindowOptionsPanel.cs" /> </ItemGroup> <ItemGroup> <Data Include="options\DefaultEditingLayout.xml" /> diff --git a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs index 5984eb5d87..fb663de6b1 100644 --- a/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs +++ b/main/src/core/MonoDevelop.Ide/MonoDevelop.Ide/IdePreferences.cs @@ -103,6 +103,7 @@ namespace MonoDevelop.Ide public ConfigurationProperty<TargetRuntime> DefaultTargetRuntime => RootWorkspace.DefaultTargetRuntime; + public readonly ConfigurationProperty<bool> EnablePinnedTabs = ConfigurationProperty.Create ("MonoDevelop.TabsAndWindow.EnablePinnedTabs", false); public readonly ConfigurationProperty<string> UserInterfaceLanguage = Runtime.Preferences.UserInterfaceLanguage; public readonly ConfigurationProperty<string> UserInterfaceThemeName = ConfigurationProperty.Create ("MonoDevelop.Ide.UserInterfaceTheme", Platform.IsLinux ? "" : "Light"); public readonly ConfigurationProperty<WorkbenchCompactness> WorkbenchCompactness = ConfigurationProperty.Create ("MonoDevelop.Ide.WorkbenchCompactness", MonoDevelop.Ide.WorkbenchCompactness.Normal); diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9.png b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9.png Binary files differnew file mode 100644 index 0000000000..243d34e4ba --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9.png diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9@2x.png b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9@2x.png Binary files differnew file mode 100644 index 0000000000..e5322c9a55 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9@2x.png diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9~dark.png b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9~dark.png Binary files differnew file mode 100644 index 0000000000..f8e83d730d --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9~dark.png diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9~dark@2x.png b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9~dark@2x.png Binary files differnew file mode 100644 index 0000000000..de2aa003b7 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-pinned-9~dark@2x.png diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9.png b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9.png Binary files differnew file mode 100644 index 0000000000..cb7b84b601 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9.png diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9@2x.png b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9@2x.png Binary files differnew file mode 100644 index 0000000000..fe5fe0bb7c --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9@2x.png diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9~dark.png b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9~dark.png Binary files differnew file mode 100644 index 0000000000..3d451b44a2 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9~dark.png diff --git a/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9~dark@2x.png b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9~dark@2x.png Binary files differnew file mode 100644 index 0000000000..fa4093a8d0 --- /dev/null +++ b/main/src/core/MonoDevelop.Ide/icons/tab-unpinned-9~dark@2x.png |