From b67d3ad6918d871d0288e9cd0eec7682d54168cb Mon Sep 17 00:00:00 2001 From: John Lewin Date: Sun, 7 Oct 2018 11:36:52 -0700 Subject: [PATCH] Add Library tab, replace PrintLibrary widget with Favorites bar --- MatterControlLib/ActionBar/PrinterSelector.cs | 3 +- .../ActionBar/TemperatureWidgetBase.cs | 5 + .../ApplicationView/ApplicationController.cs | 25 +- .../ApplicationView/WidescreenPanel.cs | 50 +- .../CustomWidgets/TreeView/TreeNode.cs | 93 +- .../CustomWidgets/TreeView/TreeView.cs | 4 +- .../Library/Providers/DynamicContainerLink.cs | 14 +- .../Library/Providers/LibraryConfig.cs | 19 +- .../LibraryCollectionContainer.cs | 3 + .../MatterControl/RootHistoryContainer.cs | 11 +- .../Library/Widgets/ContainerTreeNode.cs | 53 + .../Library/Widgets/InventoryTabPage.cs | 360 ++++++ .../Library/Widgets/InventoryTreeView.cs | 225 ++++ .../Library/Widgets/LibraryWidget.cs | 1099 +++++++++++++++++ .../Library/Widgets/ListView/ListView.cs | 6 +- .../Library/Widgets/PrintLibraryWidget.cs | 33 + .../Library/Widgets/PrinterDetails.cs | 179 +++ .../PartPreviewWindow/LibraryBrowserPage.cs | 2 +- .../PartPreviewWindow/NewTabButton.cs | 5 +- .../PartPreviewWindow/PartPreviewContent.cs | 82 +- .../PartPreviewWindow/PartTabPage.cs | 33 +- .../PartPreviewWindow/PopupButton.cs | 11 +- .../StartPage/ExploreItem.cs | 52 +- .../{StartTabPage.cs => StoreTabPage.cs} | 16 +- MatterControlLib/PartPreviewWindow/Tabs.cs | 65 +- .../View3D/PrinterBar/PrinterActionsBar.cs | 2 +- .../PartPreviewWindow/ViewControls3D.cs | 123 +- .../PrinterConnections/PrinterSetup.cs | 11 +- .../SetupStepMakeModelName.cs | 20 + .../PrinterConnections/ShowAuthPanel.cs | 134 -- .../SettingsManagement/OemSettings.cs | 30 + .../Settings/PrinterSettings.cs | 5 +- .../Settings/ProfileManager.cs | 57 + .../Settings/SettingsHelpers.cs | 24 - .../Utilities/WebUtilities/RequestManager.cs | 2 +- StaticData/Icons/Library/cloud_20x20.png | Bin 0 -> 1034 bytes .../Icons/Library/design_apps_20x20.png | Bin 0 -> 1019 bytes StaticData/Icons/Library/download_20x20.png | Bin 0 -> 912 bytes StaticData/Icons/Library/file_20x20.png | Bin 0 -> 298 bytes StaticData/Icons/Library/folder_20x20.png | Bin 0 -> 741 bytes StaticData/Icons/Library/history_20x20.png | Bin 0 -> 1048 bytes StaticData/Icons/Library/library_20x20.png | Bin 0 -> 932 bytes StaticData/Icons/Library/purchased_20x20.png | Bin 0 -> 1004 bytes StaticData/Icons/Library/queue_20x20.png | Bin 0 -> 925 bytes StaticData/Icons/Library/sd_20x20.png | Bin 0 -> 994 bytes StaticData/Icons/Library/shared_20x20.png | Bin 0 -> 977 bytes StaticData/Icons/Library/up_folder_20.png | Bin 0 -> 291 bytes StaticData/Icons/cube_add.png | Bin 0 -> 809 bytes StaticData/Icons/cube_export.png | Bin 0 -> 908 bytes StaticData/Icons/filament.png | Bin 0 -> 546 bytes StaticData/Icons/printer.png | Bin 0 -> 984 bytes StaticData/Icons/save_grey_16x.png | Bin 0 -> 244 bytes 52 files changed, 2467 insertions(+), 389 deletions(-) create mode 100644 MatterControlLib/Library/Widgets/ContainerTreeNode.cs create mode 100644 MatterControlLib/Library/Widgets/InventoryTabPage.cs create mode 100644 MatterControlLib/Library/Widgets/InventoryTreeView.cs create mode 100644 MatterControlLib/Library/Widgets/LibraryWidget.cs create mode 100644 MatterControlLib/Library/Widgets/PrinterDetails.cs rename MatterControlLib/PartPreviewWindow/StartPage/{StartTabPage.cs => StoreTabPage.cs} (87%) delete mode 100644 MatterControlLib/PrinterControls/PrinterConnections/ShowAuthPanel.cs create mode 100644 StaticData/Icons/Library/cloud_20x20.png create mode 100644 StaticData/Icons/Library/design_apps_20x20.png create mode 100644 StaticData/Icons/Library/download_20x20.png create mode 100644 StaticData/Icons/Library/file_20x20.png create mode 100644 StaticData/Icons/Library/folder_20x20.png create mode 100644 StaticData/Icons/Library/history_20x20.png create mode 100644 StaticData/Icons/Library/library_20x20.png create mode 100644 StaticData/Icons/Library/purchased_20x20.png create mode 100644 StaticData/Icons/Library/queue_20x20.png create mode 100644 StaticData/Icons/Library/sd_20x20.png create mode 100644 StaticData/Icons/Library/shared_20x20.png create mode 100644 StaticData/Icons/Library/up_folder_20.png create mode 100644 StaticData/Icons/cube_add.png create mode 100644 StaticData/Icons/cube_export.png create mode 100644 StaticData/Icons/filament.png create mode 100644 StaticData/Icons/printer.png create mode 100644 StaticData/Icons/save_grey_16x.png diff --git a/MatterControlLib/ActionBar/PrinterSelector.cs b/MatterControlLib/ActionBar/PrinterSelector.cs index 4170fe9ed..ba4da7766 100644 --- a/MatterControlLib/ActionBar/PrinterSelector.cs +++ b/MatterControlLib/ActionBar/PrinterSelector.cs @@ -98,7 +98,8 @@ namespace MatterHackers.MatterControl { if (ProfileManager.Instance.ActiveProfile != null) { - ProfileManager.Instance.ActiveProfile.Name = activePrinter.Settings.GetValue(SettingsKey.printer_name); + // TODO: Looks very wrong, if this widget was driving the profile name value, it will no longer be synced + ////////////////ProfileManager.Instance.ActiveProfile.Name = activePrinter.Settings.GetValue(SettingsKey.printer_name); Rebuild(); } } diff --git a/MatterControlLib/ActionBar/TemperatureWidgetBase.cs b/MatterControlLib/ActionBar/TemperatureWidgetBase.cs index 643addcb0..84c14de13 100644 --- a/MatterControlLib/ActionBar/TemperatureWidgetBase.cs +++ b/MatterControlLib/ActionBar/TemperatureWidgetBase.cs @@ -115,6 +115,11 @@ namespace MatterHackers.MatterControl.ActionBar { enableControls(); }, ref unregisterEvents); + + foreach (var child in this.Children) + { + child.Selectable = false; + } } protected void DisplayCurrentTemperature() diff --git a/MatterControlLib/ApplicationView/ApplicationController.cs b/MatterControlLib/ApplicationView/ApplicationController.cs index f021f4742..24aaba711 100644 --- a/MatterControlLib/ApplicationView/ApplicationController.cs +++ b/MatterControlLib/ApplicationView/ApplicationController.cs @@ -388,11 +388,26 @@ namespace MatterHackers.MatterControl } } + // Plugin Registration Points + + // Returns the user printer profile from the webservices plugin public static Func> GetPrinterProfileAsync; + + // Executes the user printer profile sync logic in the webservices plugin public static Func, Task> SyncPrinterProfiles; + + // Returns all public printer profiles from the webservices plugin public static Func> GetPublicProfileList; + + // Returns the public printer profile from the webservices plugin public static Func> DownloadPublicProfileAsync; + // Indicates if guest, rather than an authenticated user, is active + public static Func GuestUserActive { get; set; } + + // Returns the authentication dialog from the authentication plugin + public static Func GetAuthPage; + public SlicePresetsPage EditMaterialPresetsPage { get; set; } public SlicePresetsPage EditQualityPresetsWindow { get; set; } @@ -747,6 +762,7 @@ namespace MatterHackers.MatterControl this.Library.RegisterContainer( new DynamicContainerLink( () => "Downloads".Localize(), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "download_20x20.png")), AggContext.StaticData.LoadIcon(Path.Combine("Library", "download_folder.png")), () => new FileSystemContainer(ApplicationDataStorage.Instance.DownloadsDirectory) { @@ -759,6 +775,7 @@ namespace MatterHackers.MatterControl this.Library.RegisterContainer( new DynamicContainerLink( () => "Library".Localize(), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "library_20x20.png")), AggContext.StaticData.LoadIcon(Path.Combine("Library", "library_folder.png")), () => this.Library.LibraryCollectionContainer)); @@ -781,6 +798,7 @@ namespace MatterHackers.MatterControl this.Library.RegisterContainer( new DynamicContainerLink( () => "SD Card".Localize(), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "sd_20x20.png")), AggContext.StaticData.LoadIcon(Path.Combine("Library", "sd_folder.png")), () => new SDCardContainer(), () => @@ -799,6 +817,7 @@ namespace MatterHackers.MatterControl this.Library.RegisterContainer( new DynamicContainerLink( () => "History".Localize(), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_20x20.png")), AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_folder.png")), () => new RootHistoryContainer())); } @@ -1672,11 +1691,7 @@ namespace MatterHackers.MatterControl public void OnLoadActions() { - // TODO: Calling UserChanged seems wrong. Load the right user before we spin up controls, rather than after - // Pushing this after load fixes that empty printer list - /////////////////////ApplicationController.Instance.UserChanged(); - - bool showAuthWindow = PrinterSetup.ShouldShowAuthPanel?.Invoke() ?? false; + bool showAuthWindow = ApplicationController.GuestUserActive?.Invoke() ?? false; if (showAuthWindow) { if (ApplicationSettings.Instance.get(ApplicationSettingsKey.SuppressAuthPanel) != "True") diff --git a/MatterControlLib/ApplicationView/WidescreenPanel.cs b/MatterControlLib/ApplicationView/WidescreenPanel.cs index df11145db..a863a2bd3 100644 --- a/MatterControlLib/ApplicationView/WidescreenPanel.cs +++ b/MatterControlLib/ApplicationView/WidescreenPanel.cs @@ -59,21 +59,6 @@ namespace MatterHackers.MatterControl // Push TouchScreenMode into GuiWidget GuiWidget.TouchScreenMode = UserSettings.Instance.IsTouchScreen; - var library3DViewSplitter = new Splitter() - { - SplitterDistance = UserSettings.Instance.LibraryViewWidth, - SplitterSize = theme.SplitterWidth, - SplitterBackground = theme.SplitterBackground - }; - library3DViewSplitter.AnchorAll(); - - library3DViewSplitter.DistanceChanged += (s, e) => - { - UserSettings.Instance.LibraryViewWidth = library3DViewSplitter.SplitterDistance; - }; - - this.AddChild(library3DViewSplitter); - // put in the right column var partPreviewContent = new PartPreviewContent(theme) { @@ -81,31 +66,7 @@ namespace MatterHackers.MatterControl HAnchor = HAnchor.Left | HAnchor.Right }; - library3DViewSplitter.Panel2.AddChild(partPreviewContent); - - // put in the left column - var leftNav = new FlowLayoutWidget(FlowDirection.TopToBottom); - using (leftNav.LayoutLock()) - { - leftNav.AddChild(new BrandMenuButton(theme) - { - HAnchor = HAnchor.Stretch, - VAnchor = VAnchor.Fit, - BackgroundColor = theme.TabBarBackground, - Border = new BorderDouble(right: 1), - BorderColor = theme.MinimalShade, - Padding = theme.TabbarPadding.Clone(right: 0) - }); - - leftNav.AddChild(new PrintLibraryWidget(partPreviewContent, theme) - { - BackgroundColor = theme.ActiveTabColor - }); - } - - leftNav.AnchorAll(); - - library3DViewSplitter.Panel1.AddChild(leftNav); + this.AddChild(partPreviewContent); } } @@ -115,7 +76,7 @@ namespace MatterHackers.MatterControl { this.Name = "MatterControl BrandMenuButton"; this.VAnchor = VAnchor.Fit; - this.HAnchor = HAnchor.Stretch; + this.HAnchor = HAnchor.Fit; this.Margin = 0; this.PopupContent = new ApplicationSettingsWidget(ApplicationController.Instance.MenuTheme) { @@ -126,7 +87,7 @@ namespace MatterHackers.MatterControl var row = new FlowLayoutWidget() { - HAnchor = HAnchor.Stretch, + HAnchor = HAnchor.Fit, VAnchor = VAnchor.Fit, }; this.AddChild(row); @@ -142,6 +103,11 @@ namespace MatterHackers.MatterControl { VAnchor = VAnchor.Center }); + + foreach (var child in this.Children) + { + child.Selectable = false; + } } } } diff --git a/MatterControlLib/CustomWidgets/TreeView/TreeNode.cs b/MatterControlLib/CustomWidgets/TreeView/TreeNode.cs index 71fefa78c..25255e7d3 100644 --- a/MatterControlLib/CustomWidgets/TreeView/TreeNode.cs +++ b/MatterControlLib/CustomWidgets/TreeView/TreeNode.cs @@ -72,35 +72,24 @@ namespace MatterHackers.MatterControl.CustomWidgets expandWidget = new TreeExpandWidget(theme) { Expandable = GetNodeCount(false) != 0, - VAnchor = VAnchor.Fit, + VAnchor = VAnchor.Fit | VAnchor.Center, Height = 16, Width = 16 }; - var expandCheckBox = new CheckBox(expandWidget) - { - Checked = this.Expanded, - VAnchor = VAnchor.Center, - }; - - this.ExpandedChanged += (s, e) => + expandWidget.Click += (s, e) => { + this.Expanded = !this.Expanded; expandWidget.Expanded = this.Expanded; - expandCheckBox.Checked = this.Expanded; }; - expandCheckBox.CheckedStateChanged += (s, e) => - { - Expanded = expandCheckBox.Checked; - }; - - this.TitleBar.AddChild(expandCheckBox); + this.TitleBar.AddChild(expandWidget); this.HighlightRegion = new FlowLayoutWidget() { VAnchor = VAnchor.Fit, HAnchor = HAnchor.Fit, - Padding = useIcon ? 0 : new BorderDouble(4, 2), + Padding = useIcon ? new BorderDouble(2) : new BorderDouble(4, 2), Selectable = false }; this.TitleBar.AddChild(this.HighlightRegion); @@ -113,8 +102,7 @@ namespace MatterHackers.MatterControl.CustomWidgets this.HighlightRegion.AddChild(imageWidget = new ImageWidget(this.Image) { VAnchor = VAnchor.Center, - BackgroundColor = new Color(theme.Colors.PrimaryTextColor, 12), - Margin = 2, + Margin = new BorderDouble(right: 4), Selectable = false }); }; @@ -133,9 +121,9 @@ namespace MatterHackers.MatterControl.CustomWidgets Name = "content", Margin = new BorderDouble(12, 3), }; - AddChild(content); + this.AddChild(content); - Nodes.CollectionChanged += (s, e) => isDirty = true; + this.Nodes.CollectionChanged += (s, e) => isDirty = true; } public FlowLayoutWidget TitleBar { get; } @@ -218,14 +206,30 @@ namespace MatterHackers.MatterControl.CustomWidgets public bool Editing { get; } + public bool Expandable + { + get => expandWidget.Expandable; + set => expandWidget.Expandable = value; + } + + public bool ReserveIconSpace + { + get => expandWidget.ReserveIconSpace; + set => expandWidget.ReserveIconSpace = value; + } + + private bool _expanded; public bool Expanded { - get => content?.Visible == true; + get => _expanded; set { - if (content.Visible != value) + if (_expanded != value || content.Visible != value) { - content.Visible = value; + _expanded = value; + expandWidget.Expanded = _expanded; + + content.Visible = _expanded && this.Nodes.Count > 0; ExpandedChanged?.Invoke(this, null); } } @@ -413,8 +417,7 @@ namespace MatterHackers.MatterControl.CustomWidgets arrowDown = AggContext.StaticData.LoadIcon("fa-angle-down_12.png", theme.InvertIcons); placeholder = new ImageBuffer(16, 16); - //this.VAnchor = VAnchor.Center; - this.Margin = new BorderDouble(right: 5); + this.Margin = new BorderDouble(right: 4); imageButton = new IconButton(placeholder, theme) { @@ -448,16 +451,9 @@ namespace MatterHackers.MatterControl.CustomWidgets if (_expandable != value) { _expandable = value; - - if (!value) - { - imageButton.SetIcon(placeholder); - } - else - { - imageButton.SetIcon((this.Expanded) ? arrowDown : arrowRight); - } } + + this.EnsureExpansionState(); } } @@ -471,17 +467,30 @@ namespace MatterHackers.MatterControl.CustomWidgets { _expanded = value; - if (!this.Expandable) - { - imageButton.SetIcon(placeholder); - } - else - { - imageButton.SetIcon((_expanded) ? arrowDown : arrowRight); - } + this.EnsureExpansionState(); } } } + + private void EnsureExpansionState() + { + if (!this.Expandable) + { + if (this.ReserveIconSpace) + { + imageButton.SetIcon(placeholder); + } + + imageButton.Visible = this.ReserveIconSpace; + } + else + { + imageButton.Visible = true; + imageButton.SetIcon((_expanded) ? arrowDown : arrowRight); + } + } + + public bool ReserveIconSpace { get; set; } = true; } } } \ No newline at end of file diff --git a/MatterControlLib/CustomWidgets/TreeView/TreeView.cs b/MatterControlLib/CustomWidgets/TreeView/TreeView.cs index 0257cd1b4..fcf088cf0 100644 --- a/MatterControlLib/CustomWidgets/TreeView/TreeView.cs +++ b/MatterControlLib/CustomWidgets/TreeView/TreeView.cs @@ -36,7 +36,7 @@ using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.CustomWidgets { /// - /// Make this object a terminating leafe in the Design History. Do + /// Make this object a terminating leaf in the Design History. Do /// not show any of its children /// public interface IVisualLeafNode @@ -45,7 +45,7 @@ namespace MatterHackers.MatterControl.CustomWidgets public class TreeView : ScrollableWidget { - private ThemeConfig theme; + protected ThemeConfig theme; public TreeView(ThemeConfig theme) : this(0, 0, theme) diff --git a/MatterControlLib/Library/Providers/DynamicContainerLink.cs b/MatterControlLib/Library/Providers/DynamicContainerLink.cs index 715e3f46c..04b0bc3d1 100644 --- a/MatterControlLib/Library/Providers/DynamicContainerLink.cs +++ b/MatterControlLib/Library/Providers/DynamicContainerLink.cs @@ -49,8 +49,15 @@ namespace MatterHackers.MatterControl.Library private Func containerCreator; private Func visibilityResolver; private Func nameResolver; + private ImageBuffer microIcon; - public DynamicContainerLink(Func nameResolver, ImageBuffer thumbnail, Func creator = null, Func visibilityResolver = null) + public DynamicContainerLink(Func nameResolver, ImageBuffer microIcon, ImageBuffer thumbnail, Func creator = null, Func visibilityResolver = null) + : this (nameResolver, thumbnail, creator, visibilityResolver) + { + this.microIcon = microIcon; + } + + private DynamicContainerLink(Func nameResolver, ImageBuffer thumbnail, Func creator = null, Func visibilityResolver = null) { this.thumbnail = thumbnail?.SetPreMultiply(); this.nameResolver = nameResolver; @@ -69,6 +76,11 @@ namespace MatterHackers.MatterControl.Library public Task GetThumbnail(int width, int height) { + if (width < 24 && height < 24) + { + return Task.FromResult(microIcon?.AlphaToPrimaryAccent()); + } + //return Task.FromResult(thumbnail); return Task.FromResult(thumbnail?.AlphaToPrimaryAccent()); } diff --git a/MatterControlLib/Library/Providers/LibraryConfig.cs b/MatterControlLib/Library/Providers/LibraryConfig.cs index 39ac0d5eb..32af5c6ae 100644 --- a/MatterControlLib/Library/Providers/LibraryConfig.cs +++ b/MatterControlLib/Library/Providers/LibraryConfig.cs @@ -68,7 +68,10 @@ namespace MatterHackers.MatterControl.Library private ILibraryContainer activeContainer; private static ImageBuffer defaultFolderIcon = AggContext.StaticData.LoadIcon(Path.Combine("Library", "folder.png")).SetPreMultiply(); + private static ImageBuffer defaultFolderIconx20 = AggContext.StaticData.LoadIcon(Path.Combine("Library", "folder_20x20.png")).SetPreMultiply(); + private static ImageBuffer defaultItemIcon = AggContext.StaticData.LoadIcon(Path.Combine("Library", "file.png")); + private static ImageBuffer defaultItemIconx20 = AggContext.StaticData.LoadIcon(Path.Combine("Library", "file_20x20.png")); public LibraryConfig() { @@ -261,7 +264,21 @@ namespace MatterHackers.MatterControl.Library if (thumbnail == null) { // Use the listview defaults - thumbnail = ((libraryItem is ILibraryContainerLink) ? defaultFolderIcon : defaultItemIcon).AlphaToPrimaryAccent(); + if (thumbHeight < 24 && thumbWidth < 24) + { + thumbnail = ((libraryItem is ILibraryContainerLink) ? defaultFolderIconx20 : defaultItemIconx20); ; + + //if (!theme.InvertIcons) + //{ + // thumbnail = thumbnail.InvertLightness(); + //} + + thumbnail = thumbnail.MultiplyWithPrimaryAccent(); + } + else + { + thumbnail = ((libraryItem is ILibraryContainerLink) ? defaultFolderIcon : defaultItemIcon).AlphaToPrimaryAccent(); + } } // TODO: Resolve and implement diff --git a/MatterControlLib/Library/Providers/MatterControl/LibraryCollectionContainer.cs b/MatterControlLib/Library/Providers/MatterControl/LibraryCollectionContainer.cs index 399355864..604e3e886 100644 --- a/MatterControlLib/Library/Providers/MatterControl/LibraryCollectionContainer.cs +++ b/MatterControlLib/Library/Providers/MatterControl/LibraryCollectionContainer.cs @@ -49,6 +49,7 @@ namespace MatterHackers.MatterControl.Library this.ChildContainers.Add( new DynamicContainerLink( () => "Local Library".Localize(), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "library_20x20.png")), AggContext.StaticData.LoadIcon(Path.Combine("Library", "library_folder.png")), () => new SqliteLibraryContainer(rootLibraryCollection.Id))); } @@ -56,6 +57,7 @@ namespace MatterHackers.MatterControl.Library this.ChildContainers.Add( new DynamicContainerLink( () => "Calibration Parts".Localize(), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "folder_20x20.png")), AggContext.StaticData.LoadIcon(Path.Combine("Library", "folder.png")), () => new CalibrationPartsContainer()) { @@ -65,6 +67,7 @@ namespace MatterHackers.MatterControl.Library this.ChildContainers.Add( new DynamicContainerLink( () => "Print Queue".Localize(), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "queue_20x20.png")), AggContext.StaticData.LoadIcon(Path.Combine("Library", "queue_folder.png")), () => new PrintQueueContainer())); } diff --git a/MatterControlLib/Library/Providers/MatterControl/RootHistoryContainer.cs b/MatterControlLib/Library/Providers/MatterControl/RootHistoryContainer.cs index 34729d962..cf393758a 100644 --- a/MatterControlLib/Library/Providers/MatterControl/RootHistoryContainer.cs +++ b/MatterControlLib/Library/Providers/MatterControl/RootHistoryContainer.cs @@ -28,6 +28,8 @@ either expressed or implied, of the FreeBSD Project. */ using System.Collections.Generic; +using System.IO; +using MatterHackers.Agg.Platform; using MatterHackers.Localizations; namespace MatterHackers.MatterControl.Library @@ -46,7 +48,8 @@ namespace MatterHackers.MatterControl.Library this.ChildContainers.Add( new DynamicContainerLink( () => "Plating History".Localize(), - null, + AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_20x20.png")), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_folder.png")), () => new PlatingHistoryContainer()) { IsReadOnly = true @@ -55,7 +58,8 @@ namespace MatterHackers.MatterControl.Library this.ChildContainers.Add( new DynamicContainerLink( () => "Part History".Localize(), - null, + AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_20x20.png")), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_folder.png")), () => new PartHistoryContainer()) { IsReadOnly = true @@ -64,7 +68,8 @@ namespace MatterHackers.MatterControl.Library this.ChildContainers.Add( new DynamicContainerLink( () => "Print History".Localize(), - null, + AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_20x20.png")), + AggContext.StaticData.LoadIcon(Path.Combine("Library", "history_folder.png")), () => new PrintHistoryContainer()) { IsReadOnly = true diff --git a/MatterControlLib/Library/Widgets/ContainerTreeNode.cs b/MatterControlLib/Library/Widgets/ContainerTreeNode.cs new file mode 100644 index 000000000..8532f8fa0 --- /dev/null +++ b/MatterControlLib/Library/Widgets/ContainerTreeNode.cs @@ -0,0 +1,53 @@ +/* +Copyright (c) 2017, Kevin Pope, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + + +using System; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.Library; + +namespace MatterHackers.MatterControl.PrintLibrary +{ + public class ContainerTreeNode : TreeNode + { + public ContainerTreeNode(ThemeConfig theme) + : base (theme, true) + { + } + + public bool ContainerAcquired => this.Container != null; + + public ILibraryContainer Container { get; set; } = null; + + //public override void OnChildAdded(EventArgs e) + //{ + // base.OnChildAdded(e); + //} + } +} diff --git a/MatterControlLib/Library/Widgets/InventoryTabPage.cs b/MatterControlLib/Library/Widgets/InventoryTabPage.cs new file mode 100644 index 000000000..f53886567 --- /dev/null +++ b/MatterControlLib/Library/Widgets/InventoryTabPage.cs @@ -0,0 +1,360 @@ +/* +Copyright (c) 2018, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System.Linq; +using System.Threading.Tasks; + +using MatterHackers.Agg; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.Library; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.MatterControl.SlicerConfiguration; + +namespace MatterHackers.MatterControl.PrintLibrary +{ + public class InventoryTabPage : GuiWidget + { + private FlowLayoutWidget buttonPanel; + + private GuiWidget searchInput; + + private ThemeConfig theme; + private OverflowBar navBar; + private GuiWidget searchButton; + private TreeView treeView; + + public InventoryTabPage(ThemeConfig theme) + { + this.theme = theme; + this.Padding = 0; + this.AnchorAll(); + + var allControls = new FlowLayoutWidget(FlowDirection.TopToBottom); + + navBar = new OverflowBar(theme) + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit, + Visible = false + }; + allControls.AddChild(navBar); + theme.ApplyBottomBorder(navBar); + + var searchPanel = new SearchInputBox(theme) + { + Visible = false, + Margin = new BorderDouble(10, 0, 5, 0), + }; + searchPanel.searchInput.ActualTextEditWidget.EnterPressed += (s, e) => + { + this.PerformSearch(); + }; + searchPanel.ResetButton.Click += (s, e) => + { + searchPanel.Visible = false; + + searchPanel.searchInput.Text = ""; + + this.ClearSearch(); + }; + + // Store a reference to the input field + this.searchInput = searchPanel.searchInput; + + navBar.AddChild(searchPanel); + + searchButton = theme.CreateSearchButton(); + searchButton.Enabled = false; + searchButton.Name = "Search Library Button"; + searchButton.Click += (s, e) => + { + if (searchPanel.Visible) + { + PerformSearch(); + } + else + { + searchPanel.Visible = true; + searchInput.Focus(); + } + }; + navBar.AddChild(searchButton); + + PopupMenuButton viewOptionsButton; + + navBar.AddChild( + viewOptionsButton = new PopupMenuButton( + new ImageWidget(AggContext.StaticData.LoadIcon("fa-sort_16.png", 32, 32, theme.InvertIcons)) + { + //VAnchor = VAnchor.Center + }, + theme) + { + AlignToRightEdge = true, + Name = "Print Library View Options" + }); + + viewOptionsButton.DynamicPopupContent = () => + { + var popupMenu = new PopupMenu(ApplicationController.Instance.MenuTheme); + + popupMenu.CreateMenuItem("xxx"); + + return popupMenu; + }; + + var horizontalSplitter = new Splitter() + { + SplitterDistance = UserSettings.Instance.LibraryViewWidth, + SplitterSize = theme.SplitterWidth, + SplitterBackground = theme.SplitterBackground + }; + horizontalSplitter.AnchorAll(); + + horizontalSplitter.DistanceChanged += (s, e) => + { + UserSettings.Instance.LibraryViewWidth = horizontalSplitter.SplitterDistance; + }; + + allControls.AddChild(horizontalSplitter); + + treeView = new InventoryTreeView(theme) + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Stretch, + Width = 300, + Margin = 5 + }; + treeView.NodeMouseClick += (s, e) => + { + if (e is MouseEventArgs sourceEvent + && s is GuiWidget clickedWidget + && sourceEvent.Button == MouseButtons.Right) + { + UiThread.RunOnIdle(() => + { + var menu = new PopupMenu(ApplicationController.Instance.MenuTheme); + + var openMenuItem = menu.CreateMenuItem("Open".Localize()); + openMenuItem.Click += (s2, e2) => + { + if (treeView?.SelectedNode.Tag is PrinterInfo printerInfo) + { + // Open printer + PrinterDetails.SwitchPrinters(printerInfo.ID); + } + }; + + menu.CreateHorizontalLine(); + + var deleteMenuItem = menu.CreateMenuItem("Delete".Localize()); + deleteMenuItem.Click += (s2, e2) => + { + // Delete printer + StyledMessageBox.ShowMessageBox( + (deletePrinter) => + { + if (deletePrinter) + { + if (treeView.SelectedNode.Tag is PrinterInfo printerInfo) + { + ProfileManager.Instance.DeletePrinter(printerInfo.ID, true); + } + } + }, + "Are you sure you want to delete your currently selected printer?".Localize(), + "Delete Printer?".Localize(), + StyledMessageBox.MessageType.YES_NO, + "Delete Printer".Localize()); + }; + + + var systemWindow = this.Parents().FirstOrDefault(); + systemWindow.ShowPopup( + new MatePoint(clickedWidget) + { + Mate = new MateOptions(MateEdge.Left, MateEdge.Top), + AltMate = new MateOptions(MateEdge.Left, MateEdge.Top) + }, + new MatePoint(menu) + { + Mate = new MateOptions(MateEdge.Left, MateEdge.Top), + AltMate = new MateOptions(MateEdge.Right, MateEdge.Top) + }, + altBounds: new RectangleDouble(sourceEvent.X + 1, sourceEvent.Y + 1, sourceEvent.X + 1, sourceEvent.Y + 1)); + }); + } + }; + + + treeView.ScrollArea.HAnchor = HAnchor.Stretch; + + treeView.AfterSelect += async (s, e) => + { + if (treeView.SelectedNode.Tag is PrinterInfo printerInfo) + { + horizontalSplitter.Panel2.CloseAllChildren(); + horizontalSplitter.Panel2.AddChild(new PrinterDetails(printerInfo, theme) + { + HAnchor = HAnchor.MaxFitOrStretch, + VAnchor = VAnchor.Stretch, + Padding = theme.DefaultContainerPadding + }); + } + }; + horizontalSplitter.Panel1.AddChild(treeView); + + horizontalSplitter.Panel2.AddChild(new GuiWidget() + { + HAnchor =HAnchor.Stretch, + VAnchor = VAnchor.Stretch, + BackgroundColor = theme.AccentMimimalOverlay + }); + + buttonPanel = new FlowLayoutWidget() + { + HAnchor = HAnchor.Stretch, + Padding = theme.ToolbarPadding, + DebugShowBounds = true + }; + allControls.AddChild(buttonPanel); + + allControls.AnchorAll(); + + this.AddChild(allControls); + } + + private async Task GetExpansionItems(ILibraryItem containerItem, ContainerTreeNode treeNode) + { + if (containerItem is ILibraryContainerLink containerLink) + { + // Prevent invalid assignment of container.Parent due to overlapping load attempts that + // would otherwise result in containers with self referencing parent properties + //if (loadingContainerLink != containerLink) + //{ + // loadingContainerLink = containerLink; + + try + { + // Container items + var container = await containerLink.GetContainer(null); + if (container != null) + { + await Task.Run(() => + { + container.Load(); + }); + + if (treeNode.NodeParent is ContainerTreeNode parentNode) + { + container.Parent = parentNode.Container; + } + + foreach (var childContainer in container.ChildContainers) + { + treeNode.Nodes.Add(CreateTreeNode(childContainer)); + } + + treeNode.Container = container; + + treeNode.AlwaysExpandable = treeNode.Nodes.Count > 0; + treeNode.Expandable = treeNode.Nodes.Count > 0; + treeNode.Expanded = treeNode.Nodes.Count > 0; + + treeNode.Invalidate(); + + this.BackgroundColor = Color.Transparent; + + // container.Parent = ActiveContainer; + // SetActiveContainer(container); + } + } + catch { } + finally + { + // Clear the loading guard and any completed load attempt + // loadingContainerLink = null; + } + ///////////////////} + } + } + + private TreeNode CreateTreeNode(ILibraryItem containerItem) + { + var treeNode = new ContainerTreeNode(theme) + { + Text = containerItem.Name, + Tag = containerItem, + AlwaysExpandable = true + }; + + ApplicationController.Instance.Library.LoadItemThumbnail( + (icon) => + { + treeNode.Image = icon.SetPreMultiply(); + }, + null, + containerItem, + null, + 16, + 16, + theme).ConfigureAwait(false); + + treeNode.ExpandedChanged += (s, e) => + { + this.EnsureExpanded(containerItem, treeNode).ConfigureAwait(false); + }; + + return treeNode; + } + + public async Task EnsureExpanded(ILibraryItem libraryItem, ContainerTreeNode treeNode) + { + if (!treeNode.ContainerAcquired) + { + await GetExpansionItems(libraryItem, treeNode).ConfigureAwait(false); + } + } + + private void PerformSearch() + { + UiThread.RunOnIdle(() => + { + ApplicationController.Instance.Library.ActiveContainer.KeywordFilter = searchInput.Text.Trim(); + }); + } + + private void ClearSearch() + { + } + } +} diff --git a/MatterControlLib/Library/Widgets/InventoryTreeView.cs b/MatterControlLib/Library/Widgets/InventoryTreeView.cs new file mode 100644 index 000000000..e501ef129 --- /dev/null +++ b/MatterControlLib/Library/Widgets/InventoryTreeView.cs @@ -0,0 +1,225 @@ +/* +Copyright (c) 2018, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System; +using System.IO; +using System.Linq; + +using MatterHackers.Agg; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.PrinterControls.PrinterConnections; +using MatterHackers.MatterControl.SettingsManagement; +using MatterHackers.MatterControl.SlicerConfiguration; + +namespace MatterHackers.MatterControl.PrintLibrary +{ + public class InventoryTreeView : TreeView + { + private TreeNode printersNode; + private FlowLayoutWidget rootColumn; + private EventHandler unregisterEvents; + + public InventoryTreeView(ThemeConfig theme) + : base (theme) + { + UiThread.RunOnIdle(() => + { + rootColumn = new FlowLayoutWidget(FlowDirection.TopToBottom) + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit + }; + this.AddChild(rootColumn); + + // Printers + printersNode = new TreeNode(theme) + { + Text = "Printers".Localize(), + HAnchor = HAnchor.Stretch, + AlwaysExpandable = true, + Image = AggContext.StaticData.LoadIcon("printer.png", 16, 16, theme.InvertIcons) + }; + printersNode.TreeView = this; + + var forcedHeight = 20; + var mainRow = printersNode.Children.FirstOrDefault(); + mainRow.HAnchor = HAnchor.Stretch; + mainRow.AddChild(new HorizontalSpacer()); + + // add in the create printer button + var createPrinter = new IconButton(AggContext.StaticData.LoadIcon("md-add-circle_18.png", 18, 18, theme.InvertIcons), theme) + { + Name = "Create Printer", + VAnchor = VAnchor.Center, + Margin = theme.ButtonSpacing.Clone(left: theme.ButtonSpacing.Right), + ToolTipText = "Create Printer".Localize(), + Height = forcedHeight, + Width = forcedHeight + }; + + createPrinter.Click += (s, e) => UiThread.RunOnIdle(() => + { + if (ApplicationController.Instance.ActivePrinter.Connection.PrinterIsPrinting + || ApplicationController.Instance.ActivePrinter.Connection.PrinterIsPaused) + { + StyledMessageBox.ShowMessageBox("Please wait until the print has finished and try again.".Localize(), "Can't add printers while printing".Localize()); + } + else + { + DialogWindow.Show(PrinterSetup.GetBestStartPage(PrinterSetup.StartPageOptions.ShowMakeModel)); + } + }); + mainRow.AddChild(createPrinter); + + // add in the import printer button + var importPrinter = new IconButton(AggContext.StaticData.LoadIcon("md-import_18.png", 18, 18, theme.InvertIcons), theme) + { + VAnchor = VAnchor.Center, + Margin = theme.ButtonSpacing, + ToolTipText = "Import Printer".Localize(), + Height = forcedHeight, + Width = forcedHeight + }; + importPrinter.Click += (s, e) => UiThread.RunOnIdle(() => + { + AggContext.FileDialogs.OpenFileDialog( + new OpenFileDialogParams( + "settings files|*.ini;*.printer;*.slice"), + (result) => + { + if (!string.IsNullOrEmpty(result.FileName) + && File.Exists(result.FileName)) + { + //simpleTabs.RemoveTab(simpleTabs.ActiveTab); + if (ProfileManager.ImportFromExisting(result.FileName)) + { + string importPrinterSuccessMessage = "You have successfully imported a new printer profile. You can find '{0}' in your list of available printers.".Localize(); + DialogWindow.Show( + new ImportSucceeded( + importPrinterSuccessMessage.FormatWith(Path.GetFileNameWithoutExtension(result.FileName)))); + } + else + { + StyledMessageBox.ShowMessageBox("Oops! Settings file '{0}' did not contain any settings we could import.".Localize().FormatWith(Path.GetFileName(result.FileName)), "Unable to Import".Localize()); + } + } + }); + }); + mainRow.AddChild(importPrinter); + + rootColumn.AddChild(printersNode); + + this.RebuildPrintersList(); + + // Filament + var materialsNode = new TreeNode(theme) + { + Text = "Materials".Localize(), + AlwaysExpandable = true, + Image = AggContext.StaticData.LoadIcon("filament.png", 16, 16, theme.InvertIcons) + }; + materialsNode.TreeView = this; + + rootColumn.AddChild(materialsNode); + + }, 1); + + PrinterSettings.SettingChanged.RegisterEvent((s, e) => + { + var activePrinter = ApplicationController.Instance.ActivePrinter; + + string settingsName = (e as StringEventArgs)?.Data; + if (settingsName != null && settingsName == SettingsKey.printer_name) + { + if (ProfileManager.Instance.ActiveProfile != null) + { + ProfileManager.Instance.ActiveProfile.Name = activePrinter.Settings.GetValue(SettingsKey.printer_name); + this.RebuildPrintersList(); + } + } + }, ref unregisterEvents); + + + + PrinterSettings.SettingChanged.RegisterEvent((s, e) => + { + string settingsName = (e as StringEventArgs)?.Data; + if (settingsName == SettingsKey.printer_name) + { + this.RebuildPrintersList(); + } + }, ref unregisterEvents); + + //// Rebuild the droplist any time the ActivePrinter changes + //ApplicationController.Instance.ActivePrinterChanged.RegisterEvent((s, e) => + //{ + // this.Rebuild(); + //}, ref unregisterEvents); + + // Rebuild the droplist any time the Profiles list changes + ProfileManager.ProfilesListChanged.RegisterEvent((s, e) => + { + this.RebuildPrintersList(); + }, ref unregisterEvents); + } + + private void RebuildPrintersList() + { + if (printersNode == null) + { + return; + } + + printersNode.Nodes.Clear(); + + //Add the menu items to the menu itself + foreach (var printer in ProfileManager.Instance.ActiveProfiles.OrderBy(p => p.Name)) + { + var printerNode = new TreeNode(theme) + { + Text = printer.Name, + Tag = printer + }; + + printerNode.Load += (s, e) => + { + printerNode.Image = OemSettings.Instance.GetIcon(printer.Make); + }; + + printersNode.Nodes.Add(printerNode); + } + + printersNode.Expanded = true; + this.Invalidate(); + } + } +} diff --git a/MatterControlLib/Library/Widgets/LibraryWidget.cs b/MatterControlLib/Library/Widgets/LibraryWidget.cs new file mode 100644 index 000000000..04a721c31 --- /dev/null +++ b/MatterControlLib/Library/Widgets/LibraryWidget.cs @@ -0,0 +1,1099 @@ +/* +Copyright (c) 2018, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; + +using MatterHackers.Agg; +using MatterHackers.Agg.Platform; +using MatterHackers.Agg.UI; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.Library; +using MatterHackers.MatterControl.PartPreviewWindow; +using MatterHackers.MatterControl.PrinterCommunication; +using MatterHackers.MatterControl.PrintQueue; +using static MatterHackers.MatterControl.PrintLibrary.PrintLibraryWidget; + +namespace MatterHackers.MatterControl.PrintLibrary +{ + public class LibraryWidget : GuiWidget + { + private FlowLayoutWidget buttonPanel; + private ListView libraryView; + private GuiWidget providerMessageContainer; + private TextWidget providerMessageWidget; + + private List menuActions = new List(); + + private FolderBreadCrumbWidget breadCrumbWidget; + private GuiWidget searchInput; + private ILibraryContainer searchContainer; + + private PartPreviewContent partPreviewContent; + private ThemeConfig theme; + private OverflowBar navBar; + private GuiWidget searchButton; + private TreeView treeView; + + public LibraryWidget(PartPreviewContent partPreviewContent, ThemeConfig theme) + { + this.theme = theme; + this.partPreviewContent = partPreviewContent; + this.Padding = 0; + this.AnchorAll(); + + var allControls = new FlowLayoutWidget(FlowDirection.TopToBottom); + + libraryView = new ListView(ApplicationController.Instance.Library, theme) + { + Name = "LibraryView", + // Drop containers if ShowContainers != 1 + ContainerFilter = (container) => UserSettings.Instance.ShowContainers, + BackgroundColor = theme.ActiveTabColor, + Border = new BorderDouble(top: 1) + }; + + libraryView.SelectedItems.CollectionChanged += SelectedItems_CollectionChanged; + + ApplicationController.Instance.Library.ContainerChanged += Library_ContainerChanged; + + navBar = new OverflowBar(theme) + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Fit, + }; + allControls.AddChild(navBar); + theme.ApplyBottomBorder(navBar); + + breadCrumbWidget = new FolderBreadCrumbWidget(libraryView, theme); + navBar.AddChild(breadCrumbWidget); + + var searchPanel = new SearchInputBox(theme) + { + Visible = false, + Margin = new BorderDouble(10, 0, 5, 0), + }; + searchPanel.searchInput.ActualTextEditWidget.EnterPressed += (s, e) => + { + this.PerformSearch(); + }; + searchPanel.ResetButton.Click += (s, e) => + { + breadCrumbWidget.Visible = true; + searchPanel.Visible = false; + + searchPanel.searchInput.Text = ""; + + this.ClearSearch(); + }; + + // Store a reference to the input field + this.searchInput = searchPanel.searchInput; + + navBar.AddChild(searchPanel); + + searchButton = theme.CreateSearchButton(); + searchButton.Enabled = false; + searchButton.Name = "Search Library Button"; + searchButton.Click += (s, e) => + { + if (searchPanel.Visible) + { + PerformSearch(); + } + else + { + searchContainer = ApplicationController.Instance.Library.ActiveContainer; + + breadCrumbWidget.Visible = false; + searchPanel.Visible = true; + searchInput.Focus(); + } + }; + navBar.AddChild(searchButton); + + PopupMenuButton viewOptionsButton; + + navBar.AddChild( + viewOptionsButton = new PopupMenuButton( + new ImageWidget(AggContext.StaticData.LoadIcon("fa-sort_16.png", 32, 32, theme.InvertIcons)) + { + //VAnchor = VAnchor.Center + }, + theme) + { + AlignToRightEdge = true, + Name = "Print Library View Options" + }); + + viewOptionsButton.DynamicPopupContent = () => + { + var popupMenu = new PopupMenu(ApplicationController.Instance.MenuTheme); + + var siblingList = new List(); + + popupMenu.CreateBoolMenuItem( + "Date Created".Localize(), + () => libraryView.ActiveSort == ListView.SortKey.CreatedDate, + (v) => libraryView.ActiveSort = ListView.SortKey.CreatedDate, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + popupMenu.CreateBoolMenuItem( + "Date Modified".Localize(), + () => libraryView.ActiveSort == ListView.SortKey.ModifiedDate, + (v) => libraryView.ActiveSort = ListView.SortKey.ModifiedDate, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + popupMenu.CreateBoolMenuItem( + "Name".Localize(), + () => libraryView.ActiveSort == ListView.SortKey.Name, + (v) => libraryView.ActiveSort = ListView.SortKey.Name, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + popupMenu.CreateHorizontalLine(); + + siblingList = new List(); + + popupMenu.CreateBoolMenuItem( + "Ascending".Localize(), + () => libraryView.Ascending, + (v) => libraryView.Ascending = true, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + popupMenu.CreateBoolMenuItem( + "Descending".Localize(), + () => !libraryView.Ascending, + (v) => libraryView.Ascending = false, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + return popupMenu; + }; + + PopupMenuButton viewMenuButton; + + navBar.AddChild( + viewMenuButton = new PopupMenuButton( + new ImageWidget(AggContext.StaticData.LoadIcon("mi-view-list_10.png", 32, 32, theme.InvertIcons)) + { + //VAnchor = VAnchor.Center + }, + theme) + { + AlignToRightEdge = true + }); + + viewMenuButton.DynamicPopupContent = () => + { + var popupMenu = new PopupMenu(ApplicationController.Instance.MenuTheme); + + var listView = this.libraryView; + + var siblingList = new List(); + + popupMenu.CreateBoolMenuItem( + "View List".Localize(), + () => ApplicationController.Instance.ViewState.LibraryViewMode == ListViewModes.RowListView, + (isChecked) => + { + ApplicationController.Instance.ViewState.LibraryViewMode = ListViewModes.RowListView; + listView.ListContentView = new RowListView(theme); + listView.Reload().ConfigureAwait(false); + }, + useRadioStyle: true, + siblingRadioButtonList: siblingList); +#if DEBUG + popupMenu.CreateBoolMenuItem( + "View XSmall Icons".Localize(), + () => ApplicationController.Instance.ViewState.LibraryViewMode == ListViewModes.IconListView18, + (isChecked) => + { + ApplicationController.Instance.ViewState.LibraryViewMode = ListViewModes.IconListView18; + listView.ListContentView = new IconListView(theme, 18); + listView.Reload().ConfigureAwait(false); + }, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + popupMenu.CreateBoolMenuItem( + "View Small Icons".Localize(), + () => ApplicationController.Instance.ViewState.LibraryViewMode == ListViewModes.IconListView70, + (isChecked) => + { + ApplicationController.Instance.ViewState.LibraryViewMode = ListViewModes.IconListView70; + listView.ListContentView = new IconListView(theme, 70); + listView.Reload().ConfigureAwait(false); + }, + useRadioStyle: true, + siblingRadioButtonList: siblingList); +#endif + popupMenu.CreateBoolMenuItem( + "View Icons".Localize(), + () => ApplicationController.Instance.ViewState.LibraryViewMode == ListViewModes.IconListView, + (isChecked) => + { + ApplicationController.Instance.ViewState.LibraryViewMode = ListViewModes.IconListView; + listView.ListContentView = new IconListView(theme); + listView.Reload().ConfigureAwait(false); + }, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + popupMenu.CreateBoolMenuItem( + "View Large Icons".Localize(), + () => ApplicationController.Instance.ViewState.LibraryViewMode == ListViewModes.IconListView256, + (isChecked) => + { + ApplicationController.Instance.ViewState.LibraryViewMode = ListViewModes.IconListView256; + listView.ListContentView = new IconListView(theme, 256); + listView.Reload().ConfigureAwait(false); + }, + useRadioStyle: true, + siblingRadioButtonList: siblingList); + + return popupMenu; + }; + + var horizontalSplitter = new Splitter() + { + SplitterDistance = UserSettings.Instance.LibraryViewWidth, + SplitterSize = theme.SplitterWidth, + SplitterBackground = theme.SplitterBackground + }; + horizontalSplitter.AnchorAll(); + + horizontalSplitter.DistanceChanged += (s, e) => + { + UserSettings.Instance.LibraryViewWidth = horizontalSplitter.SplitterDistance; + }; + + allControls.AddChild(horizontalSplitter); + + treeView = new TreeView(theme) + { + HAnchor = HAnchor.Absolute, + VAnchor = VAnchor.Stretch, + Width = 300, + Margin = 5 + }; + treeView.AfterSelect += async (s, e) => + { + if (treeView.SelectedNode is ContainerTreeNode treeNode) + { + if (!treeNode.ContainerAcquired) + { + await this.EnsureExpanded(treeNode.Tag as ILibraryItem, treeNode); + } + + if (treeNode.ContainerAcquired) + { + ApplicationController.Instance.Library.ActiveContainer = treeNode.Container; + } + } + }; + horizontalSplitter.Panel1.AddChild(treeView); + + var rootColumn = new FlowLayoutWidget(FlowDirection.TopToBottom) + { + HAnchor = HAnchor.Fit, + VAnchor = VAnchor.Fit, + Margin = new BorderDouble(left: 10) + }; + treeView.AddChild(rootColumn); + + UiThread.RunOnIdle(() => + { + foreach (var item in ApplicationController.Instance.Library.ActiveContainer.ChildContainers) + { + var rootNode = this.CreateTreeNode(item); + rootNode.TreeView = treeView; + + rootColumn.AddChild(rootNode); + } + }, 1); + horizontalSplitter.Panel2.AddChild(libraryView); + + buttonPanel = new FlowLayoutWidget() + { + HAnchor = HAnchor.Stretch, + Padding = theme.ToolbarPadding, + DebugShowBounds = true + }; + AddLibraryButtonElements(); + allControls.AddChild(buttonPanel); + + allControls.AnchorAll(); + + this.AddChild(allControls); + } + + private async Task GetExpansionItems(ILibraryItem containerItem, ContainerTreeNode treeNode) + { + if (containerItem is ILibraryContainerLink containerLink) + { + // Prevent invalid assignment of container.Parent due to overlapping load attempts that + // would otherwise result in containers with self referencing parent properties + //if (loadingContainerLink != containerLink) + //{ + // loadingContainerLink = containerLink; + + try + { + // Container items + var container = await containerLink.GetContainer(null); + if (container != null) + { + await Task.Run(() => + { + container.Load(); + }); + + if (treeNode.NodeParent is ContainerTreeNode parentNode) + { + container.Parent = parentNode.Container; + } + + foreach (var childContainer in container.ChildContainers) + { + treeNode.Nodes.Add(CreateTreeNode(childContainer)); + } + + treeNode.Container = container; + + treeNode.AlwaysExpandable = treeNode.Nodes.Count > 0; + treeNode.Expandable = treeNode.Nodes.Count > 0; + treeNode.Expanded = treeNode.Nodes.Count > 0; + + treeNode.Invalidate(); + + this.BackgroundColor = Color.Transparent; + + // container.Parent = ActiveContainer; + // SetActiveContainer(container); + } + } + catch { } + finally + { + // Clear the loading guard and any completed load attempt + // loadingContainerLink = null; + } + ///////////////////} + } + } + + private TreeNode CreateTreeNode(ILibraryItem containerItem) + { + var treeNode = new ContainerTreeNode(theme) + { + Text = containerItem.Name, + Tag = containerItem, + AlwaysExpandable = true + }; + + ApplicationController.Instance.Library.LoadItemThumbnail( + (icon) => + { + treeNode.Image = icon.SetPreMultiply(); + }, + null, + containerItem, + null, + 16, + 16, + theme).ConfigureAwait(false); + + treeNode.ExpandedChanged += (s, e) => + { + this.EnsureExpanded(containerItem, treeNode).ConfigureAwait(false); + }; + + return treeNode; + } + + public async Task EnsureExpanded(ILibraryItem libraryItem, ContainerTreeNode treeNode) + { + if (!treeNode.ContainerAcquired) + { + await GetExpansionItems(libraryItem, treeNode).ConfigureAwait(false); + } + } + + private void PerformSearch() + { + UiThread.RunOnIdle(() => + { + ApplicationController.Instance.Library.ActiveContainer.KeywordFilter = searchInput.Text.Trim(); + }); + } + + private void ClearSearch() + { + if (searchContainer == null) + { + return; + } + + UiThread.RunOnIdle(() => + { + searchContainer.KeywordFilter = ""; + + // Restore the original ActiveContainer before search started - some containers may change context + ApplicationController.Instance.Library.ActiveContainer = searchContainer; + + searchContainer = null; + }); + } + + private void SelectedItems_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e) + { + if (e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Reset) + { + foreach (var item in libraryView.Items) + { + item.ViewWidget.IsSelected = false; + } + } + + if (e.OldItems != null) + { + foreach (var item in e.OldItems.OfType()) + { + item.ViewWidget.IsSelected = false; + } + } + + if (e.NewItems != null) + { + foreach (var item in e.NewItems.OfType()) + { + item.ViewWidget.IsSelected = true; + } + } + } + + private void Library_ContainerChanged(object sender, ContainerChangedEventArgs e) + { + // Release + if (e.PreviousContainer != null) + { + e.PreviousContainer.ContentChanged -= UpdateStatus; + } + + var activeContainer = this.libraryView.ActiveContainer; + + bool containerSupportsEdits = activeContainer is ILibraryWritableContainer; + + var owningNode = treeView.SelectedNode?.Parents().Where(p => p.Container == activeContainer).FirstOrDefault(); + if (owningNode != null) + { + treeView.SelectedNode = owningNode; + } + + searchInput.Text = activeContainer.KeywordFilter; + breadCrumbWidget.SetContainer(activeContainer); + + activeContainer.ContentChanged += UpdateStatus; + + searchButton.Enabled = activeContainer.Parent != null; + + UpdateStatus(null, null); + } + + private void UpdateStatus(object sender, EventArgs e) + { + string message = this.libraryView.ActiveContainer?.StatusMessage; + if (!string.IsNullOrEmpty(message)) + { + providerMessageWidget.Text = message; + providerMessageContainer.Visible = true; + } + else + { + providerMessageContainer.Visible = false; + } + } + + private void AddLibraryButtonElements() + { + buttonPanel.RemoveAllChildren(); + + // add in the message widget + providerMessageContainer = new GuiWidget() + { + VAnchor = VAnchor.Fit | VAnchor.Top, + HAnchor = HAnchor.Stretch, + Visible = false, + }; + buttonPanel.AddChild(providerMessageContainer, -1); + + providerMessageWidget = new TextWidget("") + { + PointSize = 8, + HAnchor = HAnchor.Right, + VAnchor = VAnchor.Bottom, + TextColor = ActiveTheme.Instance.SecondaryTextColor, + Margin = new BorderDouble(6), + AutoExpandBoundsToText = true, + }; + providerMessageContainer.AddChild(providerMessageWidget); + } + + private void CreateMenuActions() + { + menuActions.Add(new PrintItemAction() + { + Icon = AggContext.StaticData.LoadIcon("cube.png", 16, 16, ApplicationController.Instance.MenuTheme.InvertIcons), + Title = "Add".Localize(), + ToolTipText = "Add an.stl, .obj, .amf, .gcode or.zip file to the Library".Localize(), + Action = (selectedLibraryItems, listView) => + { + UiThread.RunOnIdle(() => + { + AggContext.FileDialogs.OpenFileDialog( + new OpenFileDialogParams(ApplicationSettings.OpenPrintableFileParams, multiSelect: true), + (openParams) => + { + if (openParams.FileNames != null) + { + var writableContainer = this.libraryView.ActiveContainer as ILibraryWritableContainer; + if (writableContainer != null + && openParams.FileNames.Length > 0) + { + writableContainer.Add(openParams.FileNames.Select(f => new FileSystemFileItem(f))); + } + } + }); + }); + }, + IsEnabled = (s, l) => this.libraryView.ActiveContainer is ILibraryWritableContainer + }); + + menuActions.Add(new PrintItemAction() + { + Title = "Create Folder".Localize(), + Icon = AggContext.StaticData.LoadIcon("fa-folder-new_16.png", 16, 16, ApplicationController.Instance.MenuTheme.InvertIcons), + Action = (selectedLibraryItems, listView) => + { + DialogWindow.Show( + new InputBoxPage( + "Create Folder".Localize(), + "Folder Name".Localize(), + "", + "Enter New Name Here".Localize(), + "Create".Localize(), + (newName) => + { + if (!string.IsNullOrEmpty(newName) + && this.libraryView.ActiveContainer is ILibraryWritableContainer writableContainer) + { + writableContainer.Add(new[] + { + new CreateFolderItem() { Name = newName } + }); + } + })); + }, + IsEnabled = (s, l) => + { + return this.libraryView.ActiveContainer is ILibraryWritableContainer writableContainer + && writableContainer?.AllowAction(ContainerActions.AddContainers) == true; + } + }); + + menuActions.Add(new PrintItemAction() + { + Title = "Print".Localize(), + Action = (selectedLibraryItems, listView) => + { + // TODO: Sort out the right way to have an ActivePrinter context that looks and behaves correctly + var activeContext = ApplicationController.Instance.DragDropData; + var printer = activeContext.Printer; + + switch (selectedLibraryItems.FirstOrDefault()) + { + case SDCardFileItem sdcardItem: + // TODO: Confirm SD printing? + // TODO: Need to rewrite library menu item validation can write one off validations like below so we don't end up here + // - ActiveSliceSettings.Instance.GetValue(SettingsKey.has_sd_card_reader) + printer.Connection.StartSdCardPrint(sdcardItem.Name.ToLower()); + break; + case FileSystemFileItem fileItem when Path.GetExtension(fileItem.FileName).IndexOf(".gco", StringComparison.OrdinalIgnoreCase) == 0: + if (printer != null) + { + UiThread.RunOnIdle(async () => + { + await printer.Bed.StashAndPrintGCode(fileItem); + }); + } + + break; + default: + //TODO: Otherwise add the selected items to the plate and print the plate? + if (printer != null) + { + UiThread.RunOnIdle(async () => + { + await printer.Bed.StashAndPrint(selectedLibraryItems); + }); + } + break; + } + }, + IsEnabled = (selectedListItems, listView) => + { + var communicationState = ApplicationController.Instance.DragDropData?.Printer?.Connection.CommunicationState; + + // Singleselect - disallow containers + return listView.SelectedItems.Count == 1 + && selectedListItems.FirstOrDefault()?.Model is ILibraryItem firstItem + && !(firstItem is ILibraryContainer) + && (communicationState == CommunicationStates.Connected + || communicationState == CommunicationStates.FinishedPrint); + } + }); + + // edit menu item + menuActions.Add(new PrintItemAction() + { + Title = "Add to Plate".Localize(), + Action = (selectedLibraryItems, listView) => + { + var activeContext = ApplicationController.Instance.DragDropData; + var printer = activeContext.Printer; + + if (listView.SelectedItems.Count == 1 && + selectedLibraryItems.FirstOrDefault() is ILibraryAssetStream assetStream + && assetStream.ContentType == "gcode") + { + // Change loaded scene to new context + printer.Bed.LoadContent( + new EditContext() + { + SourceItem = assetStream, + // No content store for GCode + ContentStore = null + }).ConfigureAwait(false); + } + else + { + activeContext.SceneContext.AddToPlate(selectedLibraryItems); + } + }, + IsEnabled = (selectedListItems, listView) => + { + // Multiselect - disallow containers, require View3DWidget context + return ApplicationController.Instance.DragDropData.View3DWidget != null + && listView.SelectedItems.Any() + && listView.SelectedItems.All(i => !(i.Model is ILibraryContainer)); + } + }); + + // edit menu item + menuActions.Add(new PrintItemAction() + { + Title = "Edit".Localize(), + Action = async (selectedLibraryItems, listView) => + { + if (selectedLibraryItems.FirstOrDefault() is ILibraryItem firstItem + && ApplicationController.Instance.Library.ActiveContainer is ILibraryWritableContainer writableContainer) + { + BedConfig bed; + + var newTab = partPreviewContent.CreatePartTab( + firstItem.Name, + bed = new BedConfig(ApplicationController.Instance.Library.PartHistory), + theme); + + // Load content after UI widgets to support progress notification during acquire/load + await bed.LoadContent( + new EditContext() + { + ContentStore = writableContainer, + SourceItem = firstItem + }); + + if (newTab.TabContent is PartTabPage partTab) + { + // TODO: Restore ability to render progress loading + } + } + }, + IsEnabled = (selectedListItems, listView) => + { + // Singleselect, WritableContainer, mcx only - disallow containers and protected items + return listView.SelectedItems.Count == 1 + && selectedListItems.FirstOrDefault()?.Model is ILibraryItem firstItem + && !(firstItem is ILibraryContainer) + && !firstItem.IsProtected + && firstItem is ILibraryAsset asset && asset.ContentType == "mcx" + && ApplicationController.Instance.Library.ActiveContainer is ILibraryWritableContainer; + } + }); + + // rename menu item + menuActions.Add(new PrintItemAction() + { + Title = "Rename".Localize(), + Action = (selectedLibraryItems, listView) => + { + if (libraryView.SelectedItems.Count == 1) + { + var selectedItem = libraryView.SelectedItems.FirstOrDefault(); + if (selectedItem == null) + { + return; + } + + DialogWindow.Show( + new InputBoxPage( + "Rename Item".Localize(), + "Name".Localize(), + selectedItem.Model.Name, + "Enter New Name Here".Localize(), + "Rename".Localize(), + (newName) => + { + var model = libraryView.SelectedItems.FirstOrDefault()?.Model; + if (model != null) + { + var container = libraryView.ActiveContainer as ILibraryWritableContainer; + if (container != null) + { + container.Rename(model, newName); + libraryView.SelectedItems.Clear(); + } + } + })); + } + }, + IsEnabled = (selectedListItems, listView) => + { + // Singleselect, WritableContainer - disallow protected items + return listView.SelectedItems.Count == 1 + && selectedListItems.FirstOrDefault()?.Model is ILibraryItem firstItem + && !firstItem.IsProtected + && ApplicationController.Instance.Library.ActiveContainer is ILibraryWritableContainer; + } + }); + + // move menu item + menuActions.Add(new PrintItemAction() + { + Title = "Move".Localize(), + Action = (selectedLibraryItems, listView) => + { + var partItems = selectedLibraryItems.Where(item => item is ILibraryAssetStream || item is ILibraryContainerLink); + if (partItems.Any() + && libraryView.ActiveContainer is ILibraryWritableContainer sourceContainer) + { + DialogWindow.Show(new MoveItemPage((newName, destinationContainer) => + { + destinationContainer.Move(partItems, sourceContainer); + + // Discover if item was moved to an already loaded and now stale view on an ancestor and force reload + var openParent = ApplicationController.Instance.Library.ActiveContainer.Ancestors().FirstOrDefault(c => c.ID == destinationContainer.ID); + if (openParent != null) + { + // TODO: Consider changing this brute force approach to instead mark as dirty and allow Activate base method to reload if dirty + Task.Run(() => openParent.Load()); + } + + libraryView.SelectedItems.Clear(); + })); + } + }, + IsEnabled = (selectedListItems, listView) => + { + // Multiselect, WritableContainer - disallow protected + return listView.SelectedItems.Any() + && listView.SelectedItems.All(i => !i.Model.IsProtected + && ApplicationController.Instance.Library.ActiveContainer is ILibraryWritableContainer); + } + }); + + // remove menu item + menuActions.Add(new PrintItemAction() + { + Title = "Remove".Localize(), + Action = (selectedLibraryItems, listView) => deleteFromLibraryButton_Click(selectedLibraryItems, null), + IsEnabled = (selectedListItems, listView) => + { + // Multiselect, WritableContainer - disallow protected + return listView.SelectedItems.Any() + && listView.SelectedItems.All(i => !i.Model.IsProtected + && ApplicationController.Instance.Library.ActiveContainer is ILibraryWritableContainer); + } + }); + + menuActions.Add(new MenuSeparator("Export")); + + // export menu item + menuActions.Add(new PrintItemAction() + { + Title = "Export".Localize(), + Action = (selectedLibraryItems, listView) => exportButton_Click(selectedLibraryItems, null), + IsEnabled = (selectedListItems, listView) => + { + // Multiselect - disallow containers + return listView.SelectedItems.Any() + && listView.SelectedItems.All(i => !(i.Model is ILibraryContainerLink)); + }, + }); + + // share menu item + menuActions.Add(new PrintItemAction() + { + Title = "Share".Localize(), + Action = (selectedLibraryItems, listView) => shareFromLibraryButton_Click(selectedLibraryItems, null), + IsEnabled = (selectedListItems, listView) => + { + // Singleselect - disallow containers and protected items + return listView.SelectedItems.Count == 1 + && selectedListItems.FirstOrDefault()?.Model is ILibraryItem firstItem + && listView.ActiveContainer.GetType().Name.IndexOf("Cloud", StringComparison.OrdinalIgnoreCase) >= 0 + && !(firstItem is ILibraryContainer) + && !firstItem.IsProtected; + } + }); + + // Extension point - RegisteredLibraryActions not defined in this file/assembly can insert here via this named token + menuActions.AddRange(ApplicationController.Instance.RegisteredLibraryActions("StandardLibraryOperations")); + +#if !__ANDROID__ + menuActions.Add(new MenuSeparator("Other")); + + // PDF export is limited to Windows + if (AggContext.OperatingSystem == OSType.Windows) + { + menuActions.Add(new PrintItemAction() + { + Title = "Create Part Sheet".Localize(), + Action = (selectedLibraryItems, listView) => + { + UiThread.RunOnIdle(() => + { + var printItems = selectedLibraryItems.OfType(); + if (printItems.Any()) + { + AggContext.FileDialogs.SaveFileDialog( + new SaveFileDialogParams("Save Parts Sheet|*.pdf") + { + ActionButtonLabel = "Save Parts Sheet".Localize(), + Title = ApplicationController.Instance.ProductName + " - " + "Save".Localize() + }, + (saveParams) => + { + if (!string.IsNullOrEmpty(saveParams.FileName)) + { + var feedbackWindow = new SavePartsSheetFeedbackWindow( + printItems.Count(), + printItems.FirstOrDefault()?.Name, + ActiveTheme.Instance.PrimaryBackgroundColor); + + var currentPartsInQueue = new PartsSheet(printItems, saveParams.FileName); + currentPartsInQueue.UpdateRemainingItems += feedbackWindow.StartingNextPart; + currentPartsInQueue.DoneSaving += feedbackWindow.DoneSaving; + + feedbackWindow.ShowAsSystemWindow(); + + currentPartsInQueue.SaveSheets(); + } + }); + } + }); + }, + IsEnabled = (selectedListItems, listView) => + { + // Multiselect - disallow containers + return listView.SelectedItems.Any() + && listView.SelectedItems.All(i => !(i.Model is ILibraryContainer)); + } + }); + } +#endif + + menuActions.Add(new PrintItemAction() + { + Title = "Open Package".Localize(), + Action = (selectedItems, listView) => + { + var firstItem = selectedItems.First(); + + if (firstItem is ILibraryAsset libraryAsset) + { + var container = new McxContainer(libraryAsset); + container.Load(); + + container.Parent = ApplicationController.Instance.Library.ActiveContainer; + + ApplicationController.Instance.Library.ActiveContainer = container; + } + }, + IsEnabled = (selectedListItems, listView) => + { + return listView.SelectedItems.Count == 1 + && selectedListItems.FirstOrDefault()?.Model is ILibraryAsset libraryAsset + && libraryAsset.ContentType == "mcx"; + } + }); + + libraryView.MenuActions = menuActions; + } + + public override void OnClosed(EventArgs e) + { + if (libraryView?.ActiveContainer != null) + { + libraryView.ActiveContainer.ContentChanged -= UpdateStatus; + ApplicationController.Instance.Library.ContainerChanged -= Library_ContainerChanged; + } + + base.OnClosed(e); + } + + private void deleteFromLibraryButton_Click(object sender, EventArgs e) + { + // ask before remove + var libraryItems = libraryView.SelectedItems.Select(p => p.Model); + if (libraryItems.Any()) + { + if (libraryView.ActiveContainer is ILibraryWritableContainer container) + { + if (container is FileSystemContainer) + { + container.Remove(libraryItems); + libraryView.SelectedItems.Clear(); + } + else + { + StyledMessageBox.ShowMessageBox( + (doDelete) => + { + if (doDelete) + { + container.Remove(libraryItems); + libraryView.SelectedItems.Clear(); + } + }, + "Are you sure you want to remove the currently selected items?".Localize(), + "Remove Items?".Localize(), + StyledMessageBox.MessageType.YES_NO, + "Remove".Localize()); + } + } + } + } + + private void shareFromLibraryButton_Click(object sender, EventArgs e) + { + // TODO: Should be rewritten to Register from cloudlibrary, include logic to add to library as needed + + ApplicationController.Instance.ShareLibraryItem(libraryView.SelectedItems.Select(i => i.Model).FirstOrDefault()); + } + + private void exportButton_Click(object sender, EventArgs e) + { + //Open export options + var exportPage = new ExportPrintItemPage(libraryView.SelectedItems.Select(item => item.Model), true); + + DialogWindow.Show(exportPage); + } + + public override void OnMouseMove(MouseEventArgs mouseEvent) + { + if (PositionWithinLocalBounds(mouseEvent.X, mouseEvent.Y) + && mouseEvent.DragFiles?.Count > 0) + { + if (libraryView?.ActiveContainer.IsProtected == false) + { + // Allow drag-drop if IsLoadable or extension == '.zip' + mouseEvent.AcceptDrop = mouseEvent.DragFiles?.Count > 0 + && mouseEvent.DragFiles.TrueForAll(filePath => ApplicationController.Instance.IsLoadableFile(filePath) + || (Path.GetExtension(filePath) is string extension + && string.Equals(extension, ".zip", StringComparison.OrdinalIgnoreCase))); + } + } + + base.OnMouseMove(mouseEvent); + } + + public override void OnMouseUp(MouseEventArgs mouseEvent) + { + // TODO: Does this fire when .AcceptDrop is false? Looks like it should + if (mouseEvent.DragFiles?.Count > 0 + && libraryView?.ActiveContainer.IsProtected == false) + { + var container = libraryView.ActiveContainer as ILibraryWritableContainer; + container?.Add(mouseEvent.DragFiles.Select(f => new FileSystemFileItem(f))); + } + + base.OnMouseUp(mouseEvent); + } + + public override void OnLoad(EventArgs args) + { + // Defer creating menu items until plugins have loaded + CreateMenuActions(); + + navBar.OverflowButton.Name = "Print Library Overflow Menu"; + navBar.ExtendOverflowMenu = (popupMenu) => + { + // Create menu items in the DropList for each element in this.menuActions + foreach (var menuAction in menuActions) + { + if (menuAction is MenuSeparator) + { + popupMenu.CreateHorizontalLine(); + } + else + { + var menuItem = popupMenu.CreateMenuItem(menuAction.Title, menuAction.Icon); + menuItem.Name = $"{menuAction.Title} Menu Item"; + menuItem.Enabled = menuAction.Action != null && menuAction.IsEnabled(libraryView.SelectedItems, libraryView); + menuItem.Click += (s, e) => + { + menuAction.Action?.Invoke(libraryView.SelectedItems.Select(i => i.Model), libraryView); + }; + } + } + }; + + base.OnLoad(args); + } + } +} diff --git a/MatterControlLib/Library/Widgets/ListView/ListView.cs b/MatterControlLib/Library/Widgets/ListView/ListView.cs index f3c96c914..2b176187c 100644 --- a/MatterControlLib/Library/Widgets/ListView/ListView.cs +++ b/MatterControlLib/Library/Widgets/ListView/ListView.cs @@ -92,6 +92,8 @@ namespace MatterHackers.MatterControl.CustomWidgets public bool ShowItems { get; set; } = true; + public bool AllowContextMenu { get; set; } = true; + public Predicate ContainerFilter { get; set; } = (o) => true; public Predicate ItemFilter { get; set; } = (o) => true; @@ -227,7 +229,7 @@ namespace MatterHackers.MatterControl.CustomWidgets items.Add(listViewItem); listViewItem.ViewWidget = itemsContentView.AddItem(listViewItem); - listViewItem.ViewWidget.HasMenu = true; + listViewItem.ViewWidget.HasMenu = this.AllowContextMenu; listViewItem.ViewWidget.Name = childContainer.Name + " Row Item Collection"; } @@ -247,7 +249,7 @@ namespace MatterHackers.MatterControl.CustomWidgets items.Add(listViewItem); listViewItem.ViewWidget = itemsContentView.AddItem(listViewItem); - listViewItem.ViewWidget.HasMenu = true; + listViewItem.ViewWidget.HasMenu = this.AllowContextMenu; listViewItem.ViewWidget.Name = "Row Item " + item.Name; } diff --git a/MatterControlLib/Library/Widgets/PrintLibraryWidget.cs b/MatterControlLib/Library/Widgets/PrintLibraryWidget.cs index 4140d69ca..d33473e9d 100644 --- a/MatterControlLib/Library/Widgets/PrintLibraryWidget.cs +++ b/MatterControlLib/Library/Widgets/PrintLibraryWidget.cs @@ -171,6 +171,39 @@ namespace MatterHackers.MatterControl.PrintLibrary }; toolbar.AddChild(showFolders); + var openButton = new TextButton("Open", theme) + { + Margin = theme.ButtonSpacing, + }; + openButton.Click += (s, e) => + { + var extensionsWithoutPeriod = new HashSet(ApplicationSettings.OpenDesignFileParams.Split('|').First().Split(',').Select(t => t.Trim().Trim('.'))); + + foreach (var extension in ApplicationController.Instance.Library.ContentProviders.Keys) + { + extensionsWithoutPeriod.Add(extension.ToUpper()); + } + + var extensionsArray = extensionsWithoutPeriod.OrderBy(t => t).ToArray(); + + string filter = string.Format( + "{0}|{1}", + string.Join(",", extensionsArray), + string.Join("", extensionsArray.Select(t => $"*.{t.ToLower()};").ToArray())); + + UiThread.RunOnIdle(() => + { + AggContext.FileDialogs.OpenFileDialog( + new OpenFileDialogParams(filter, multiSelect: true), + (openParams) => + { + ViewControls3D.LoadAndAddPartsToPlate(this, openParams.FileNames, ApplicationController.Instance.DragDropData.SceneContext); + }); + }); + }; + + toolbar.AddChild(openButton); + PopupMenuButton viewMenuButton; toolbar.AddChild( diff --git a/MatterControlLib/Library/Widgets/PrinterDetails.cs b/MatterControlLib/Library/Widgets/PrinterDetails.cs new file mode 100644 index 000000000..4d22c9c49 --- /dev/null +++ b/MatterControlLib/Library/Widgets/PrinterDetails.cs @@ -0,0 +1,179 @@ +/* +Copyright (c) 2018, John Lewin +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR +ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND +ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +The views and conclusions contained in the software and documentation are those +of the authors and should not be interpreted as representing official policies, +either expressed or implied, of the FreeBSD Project. +*/ + + +using MatterHackers.Agg; +using MatterHackers.Agg.Image; +using MatterHackers.Agg.UI; +using MatterHackers.Localizations; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.SettingsManagement; +using MatterHackers.MatterControl.SlicerConfiguration; + +namespace MatterHackers.MatterControl.PrintLibrary +{ + public class PrinterDetails : FlowLayoutWidget + { + private ThemeConfig theme; + + public PrinterDetails(PrinterInfo printerInfo, ThemeConfig theme) + : base (FlowDirection.TopToBottom) + { + this.theme = theme; + + var headingRow = this.AddHeading( + OemSettings.Instance.GetIcon(printerInfo.Make), + printerInfo.Name); + + headingRow.AddChild(new HorizontalSpacer()); + headingRow.HAnchor = HAnchor.Stretch; + + var openButton = new TextButton("Open".Localize(), theme) + { + BackgroundColor = theme.AccentMimimalOverlay + }; + openButton.Click += (s, e) => + { + PrinterDetails.SwitchPrinters(printerInfo.ID); + }; + headingRow.AddChild(openButton); + + + this.AddChild(headingRow); + + GuiWidget row; + + row = this.AddHeading("Parts & Accessories"); + row.Margin = row.Margin.Clone(top: 20); + this.AddChild(row); + + if (printerInfo.Make == "BCN") + { + var accessoriesImage = new ImageBuffer(); + row = new ImageWidget(accessoriesImage); + this.AddChild(row); + + ApplicationController.Instance.LoadRemoteImage(accessoriesImage, "https://i.imgur.com/io37z8h.png", false).ConfigureAwait(false); + } + + row = this.AddHeading("Upgrades"); + row.Margin = row.Margin.Clone(top: 20); + this.AddChild(row); + + var upgradesImage = new ImageBuffer(); + row = new ImageWidget(upgradesImage); + this.AddChild(row); + + ApplicationController.Instance.LoadRemoteImage(upgradesImage, "https://i.imgur.com/kDiV2Da.png", false).ConfigureAwait(false); + + + if (printerInfo.Make == "BCN") + { + var accessoriesImage = new ImageBuffer(); + row = new ImageWidget(accessoriesImage) + { + Margin = new BorderDouble(top: 30) + }; + this.AddChild(row); + + ApplicationController.Instance.LoadRemoteImage(accessoriesImage, "https://i.imgur.com/rrEwKY9.png", false).ConfigureAwait(false); + } + } + + public static void SwitchPrinters(string printerID) + { + var activePrinter = ApplicationController.Instance.ActivePrinter; + + if (printerID == "new" + || string.IsNullOrEmpty(printerID) + || printerID == activePrinter.Settings.ID) + { + // do nothing + } + else + { + // TODO: when this opens a new tab we will not need to check any printer + if (activePrinter.Connection.PrinterIsPrinting + || activePrinter.Connection.PrinterIsPaused) + { + // TODO: Rather than block here, the UI elements driving the change should be disabled while printing/paused + UiThread.RunOnIdle(() => + StyledMessageBox.ShowMessageBox("Please wait until the print has finished and try again.".Localize(), "Can't switch printers while printing".Localize()) + ); + } + else + { + ProfileManager.Instance.LastProfileID = printerID; + ProfileManager.Instance.LoadPrinter().ConfigureAwait(false); + } + } + } + + private GuiWidget AddHeading(ImageBuffer icon, string text) + { + var row = new FlowLayoutWidget() + { + Margin = new BorderDouble(top: 5) + }; + + row.AddChild(new ImageWidget(icon, false) + { + Margin = new BorderDouble(right: 4), + VAnchor = VAnchor.Center + }); + + row.AddChild( + new TextWidget( + text, + textColor: theme.Colors.PrimaryTextColor, + pointSize: theme.DefaultFontSize) + { + VAnchor = VAnchor.Center + }); + + return row; + } + + private GuiWidget AddHeading(string text) + { + var row = new FlowLayoutWidget() + { + Margin = new BorderDouble(top: 5) + }; + + row.AddChild( + new TextWidget( + text, + textColor: theme.Colors.PrimaryTextColor, + pointSize: theme.DefaultFontSize)); + + return row; + } + } +} diff --git a/MatterControlLib/PartPreviewWindow/LibraryBrowserPage.cs b/MatterControlLib/PartPreviewWindow/LibraryBrowserPage.cs index 334c3c1a6..2a6cefb23 100644 --- a/MatterControlLib/PartPreviewWindow/LibraryBrowserPage.cs +++ b/MatterControlLib/PartPreviewWindow/LibraryBrowserPage.cs @@ -65,7 +65,7 @@ namespace MatterHackers.MatterControl librarySelectorWidget = new ListView(libraryNavContext, new IconListView(theme, 75), theme) { BackgroundColor = ActiveTheme.Instance.TertiaryBackgroundColor, - ShowItems = false, + ShowItems = true, ContainerFilter = (container) => !container.IsReadOnly, }; diff --git a/MatterControlLib/PartPreviewWindow/NewTabButton.cs b/MatterControlLib/PartPreviewWindow/NewTabButton.cs index 76d0183c7..5def02c6f 100644 --- a/MatterControlLib/PartPreviewWindow/NewTabButton.cs +++ b/MatterControlLib/PartPreviewWindow/NewTabButton.cs @@ -28,12 +28,11 @@ either expressed or implied, of the FreeBSD Project. */ using MatterHackers.Agg; -using MatterHackers.Agg.Image; using MatterHackers.Agg.UI; -using MatterHackers.MatterControl.CustomWidgets; namespace MatterHackers.MatterControl.PartPreviewWindow { + // Holds the space and draws the trailing tabs LowerLeft notch public class TabTrailer : GuiWidget { private SimpleTabs parentTabControl; @@ -41,8 +40,6 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public ITab LastTab { get; set; } - public IconButton IconButton { get; } - public TabTrailer(SimpleTabs parentTabControl, ThemeConfig theme) { this.parentTabControl = parentTabControl; diff --git a/MatterControlLib/PartPreviewWindow/PartPreviewContent.cs b/MatterControlLib/PartPreviewWindow/PartPreviewContent.cs index 55b1d617a..38b5e3791 100644 --- a/MatterControlLib/PartPreviewWindow/PartPreviewContent.cs +++ b/MatterControlLib/PartPreviewWindow/PartPreviewContent.cs @@ -34,6 +34,7 @@ using MatterHackers.Agg.Platform; using MatterHackers.Agg.UI; using MatterHackers.Localizations; using MatterHackers.MatterControl.PartPreviewWindow.PlusTab; +using MatterHackers.MatterControl.PrintLibrary; using MatterHackers.MatterControl.SlicerConfiguration; using MatterHackers.VectorMath; using Newtonsoft.Json; @@ -65,10 +66,6 @@ namespace MatterHackers.MatterControl.PartPreviewWindow BackgroundColor = ActiveTheme.Instance.PrimaryBackgroundColor, BorderColor = theme.MinimalShade, Border = new BorderDouble(left: 1), - NewTabPage = () => - { - return new StartTabPage(this, theme); - } }; tabControl.ActiveTabChanged += (s, e) => { @@ -244,26 +241,74 @@ namespace MatterHackers.MatterControl.PartPreviewWindow ApplicationController.Instance.NotifyPrintersTabRightElement(extensionArea); - // Show fixed start page - tabControl.AddTab( - new ChromeTab("Start", "Start".Localize(), tabControl, tabControl.NewTabPage(), theme, hasClose: false) - { - MinimumSize = new Vector2(0, theme.TabButtonHeight), - Name = "Start Tab", - Padding = new BorderDouble(15, 0) - }); - // Add a tab for the current printer if (ApplicationController.Instance.ActivePrinter.Settings.PrinterSelected) { this.CreatePrinterTab(ApplicationController.Instance.ActivePrinter, theme); } + // Library tab + var libraryWidget = new LibraryWidget(this, theme) + { + BackgroundColor = theme.ActiveTabColor + }; + + tabControl.AddTab( + new ChromeTab("Library", "Library".Localize(), tabControl, libraryWidget, theme, hasClose: false) + { + MinimumSize = new Vector2(0, theme.TabButtonHeight), + Name = "Library Tab", + Padding = new BorderDouble(15, 0) + }); + + tabControl.AddTab( + new ChromeTab("Inventory", "Hardware".Localize(), tabControl, new InventoryTabPage(theme), theme, hasClose: false) + { + MinimumSize = new Vector2(0, theme.TabButtonHeight), + Name = "Library Tab", + Padding = new BorderDouble(15, 0) + }); + + // Store tab + tabControl.AddTab( + new ChromeTab("Store", "Store".Localize(), tabControl, new StoreTabPage(this, theme), theme, hasClose: false) + { + MinimumSize = new Vector2(0, theme.TabButtonHeight), + Name = "Store Tab", + Padding = new BorderDouble(15, 0) + }); + + var brandMenu = new BrandMenuButton(theme) + { + HAnchor = HAnchor.Fit, + VAnchor = VAnchor.Fit, + BackgroundColor = theme.TabBarBackground, + Border = new BorderDouble(right: 1), + BorderColor = theme.MinimalShade, + Padding = theme.TabbarPadding.Clone(right: 0) + }; + + tabControl.TabBar.ActionArea.AddChild(brandMenu, 0); + // Restore active tabs foreach (var bed in ApplicationController.Instance.Workspaces) { this.CreatePartTab("New Part", bed, theme); } + + // TODO: Initial hack to prototype desired behavior. Ideally loading the printer would occur during the loading screen and be initialized before widget load + UiThread.RunOnIdle(() => + { + ProfileManager.Instance.LoadPrinter().ContinueWith(task => + { + var printer = task.Result; + if (printer.Settings.PrinterSelected) + { + printer.ViewState.ViewMode = PartViewMode.Model; + printer.Bed.LoadPlateFromHistory().ConfigureAwait(false); + } + }); + }); } public ChromeTabs TabControl => tabControl; @@ -271,7 +316,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow private ChromeTab CreatePrinterTab(PrinterConfig printer, ThemeConfig theme) { // Printer page is in fixed position - var tab1 = tabControl.AllTabs.Skip(1).FirstOrDefault(); + var tab1 = tabControl.AllTabs.Skip(0).FirstOrDefault(); var printerTabPage = tab1?.TabContent as PrinterTabPage; if (printerTabPage == null @@ -309,7 +354,14 @@ namespace MatterHackers.MatterControl.PartPreviewWindow // Add printer into fixed position - tabControl.AddTab(printerTab, 1); + if (tabControl.AllTabs.Any()) + { + tabControl.AddTab(printerTab, 0); + } + else + { + tabControl.AddTab(printerTab); + } return printerTab; } diff --git a/MatterControlLib/PartPreviewWindow/PartTabPage.cs b/MatterControlLib/PartPreviewWindow/PartTabPage.cs index 1e88f5441..1bd8e4133 100644 --- a/MatterControlLib/PartPreviewWindow/PartTabPage.cs +++ b/MatterControlLib/PartPreviewWindow/PartTabPage.cs @@ -29,7 +29,10 @@ either expressed or implied, of the FreeBSD Project. using System; using System.Linq; +using MatterHackers.Agg; using MatterHackers.Agg.UI; +using MatterHackers.MatterControl.CustomWidgets; +using MatterHackers.MatterControl.Library; using MatterHackers.MeshVisualizer; using MatterHackers.VectorMath; @@ -116,7 +119,35 @@ namespace MatterHackers.MatterControl.PartPreviewWindow VAnchor = VAnchor.Stretch }; toolbarAndView3DWidget.AddChild(viewControls3D); - toolbarAndView3DWidget.AddChild(view3DWidget); + + var favoritesBarAndView3DWidget = new FlowLayoutWidget() + { + HAnchor = HAnchor.Stretch, + VAnchor = VAnchor.Stretch + }; + + var dummyContext = new LibraryConfig() + { + ActiveContainer = ApplicationController.Instance.Library.ActiveContainer + }; + + var favoritesBar = new ListView(dummyContext, theme) + { + Name = "LibraryView", + // Drop containers + ContainerFilter = (container) => false, + BackgroundColor = new Color(theme.MinimalShade, 25), + ListContentView = new IconListView(theme, 22), + Border = new BorderDouble(top: 1), + HAnchor = HAnchor.Absolute, + Width = 33, + AllowContextMenu = false + }; + + favoritesBarAndView3DWidget.AddChild(favoritesBar); + favoritesBarAndView3DWidget.AddChild(view3DWidget); + toolbarAndView3DWidget.AddChild(favoritesBarAndView3DWidget); + view3DContainer.AddChild(toolbarAndView3DWidget); leftToRight.AddChild(view3DContainer); diff --git a/MatterControlLib/PartPreviewWindow/PopupButton.cs b/MatterControlLib/PartPreviewWindow/PopupButton.cs index c690c8e77..518850b56 100644 --- a/MatterControlLib/PartPreviewWindow/PopupButton.cs +++ b/MatterControlLib/PartPreviewWindow/PopupButton.cs @@ -76,19 +76,14 @@ namespace MatterHackers.MatterControl.PartPreviewWindow base.OnMouseDown(mouseEvent); } - public override void OnMouseUp(MouseEventArgs mouseEvent) + public override void OnClick(MouseEventArgs mouseEvent) { - // HACK: Child controls seem to be interfering with this.MouseCaptured - this short term workaround ensure we get clicks but likely mean mouse down outside of the control will fire the popup - bool mouseUpInBounds = this.PositionWithinLocalBounds(mouseEvent.X, mouseEvent.Y); - - // Only show the popup if the menu was hidden as the mouse events started - if ((mouseUpInBounds || buttonView?.MouseCaptured == true) - && !menuVisibileAtMouseDown) + if (!menuVisibileAtMouseDown) { UiThread.RunOnIdle(this.ShowPopup); } - base.OnMouseUp(mouseEvent); + base.OnClick(mouseEvent); } public override void OnDraw(Graphics2D graphics2D) diff --git a/MatterControlLib/PartPreviewWindow/StartPage/ExploreItem.cs b/MatterControlLib/PartPreviewWindow/StartPage/ExploreItem.cs index 85fe20795..2be940a7f 100644 --- a/MatterControlLib/PartPreviewWindow/StartPage/ExploreItem.cs +++ b/MatterControlLib/PartPreviewWindow/StartPage/ExploreItem.cs @@ -37,14 +37,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.PlusTab { private FeedItemData item; private ThemeConfig theme; - private ImageBuffer image; public static int IconSize => (int)(40 * GuiWidget.DeviceScale); public static int ItemSpacing { get; } = 10; - private ImageBuffer hoverImage = null; - private ImageWidget imageWidget; - public ExploreItem(FeedItemData item, ThemeConfig theme) { this.HAnchor = HAnchor.Absolute; @@ -54,33 +50,21 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.PlusTab this.item = item; this.theme = theme; - image = new ImageBuffer(IconSize, IconSize); + var image = new ImageBuffer(IconSize, IconSize); if (item.icon != null) { - imageWidget = new ImageWidget(image) + var imageWidget = new ImageWidget(image) { Selectable = false, VAnchor = VAnchor.Top, Margin = new BorderDouble(right: ItemSpacing) }; - imageWidget.Load += async (s, e) => - { - var loadInto = new ImageBuffer(IconSize, IconSize); - await ApplicationController.Instance.LoadRemoteImage(loadInto, item.icon, true, new BlenderPreMultBGRA()); - - var grayscale = new ImageBuffer(loadInto); - ApplicationController.Instance.MakeGrayscale(grayscale); - - image = grayscale; - imageWidget.Image = image; - - hoverImage = loadInto; - }; + imageWidget.Load += (s, e) => ApplicationController.Instance.DownloadToImageAsync(image, item.icon, true, new BlenderPreMultBGRA()); this.AddChild(imageWidget); } - else if(item.widget_url != null) + else if (item.widget_url != null) { var whiteBackground = new GuiWidget(IconSize, IconSize) { @@ -90,25 +74,13 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.PlusTab }; this.AddChild(whiteBackground); - imageWidget = new ImageWidget(image) + var imageWidget = new ImageWidget(image) { Selectable = false, VAnchor = VAnchor.Center, }; - imageWidget.Load += async (s, e) => - { - var loadInto = new ImageBuffer(IconSize, IconSize); - await ApplicationController.Instance.LoadRemoteImage(loadInto, item.widget_url, true, new BlenderPreMultBGRA()); - - var grayscale = new ImageBuffer(loadInto); - ApplicationController.Instance.MakeGrayscale(grayscale); - - image = grayscale; - imageWidget.Image = image; - - hoverImage = loadInto; - }; + imageWidget.Load += (s, e) => ApplicationController.Instance.DownloadToImageAsync(image, item.widget_url, true, new BlenderPreMultBGRA()); whiteBackground.AddChild(imageWidget); } @@ -138,22 +110,14 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.PlusTab public override void OnMouseEnterBounds(MouseEventArgs mouseEvent) { mouseInBounds = true; - - if (hoverImage != null) - { - imageWidget.Image = hoverImage; - this.Invalidate(); - } - base.OnMouseEnterBounds(mouseEvent); - + + this.Invalidate(); } public override void OnMouseLeaveBounds(MouseEventArgs mouseEvent) { mouseInBounds = false; - - imageWidget.Image = image; base.OnMouseLeaveBounds(mouseEvent); this.Invalidate(); diff --git a/MatterControlLib/PartPreviewWindow/StartPage/StartTabPage.cs b/MatterControlLib/PartPreviewWindow/StartPage/StoreTabPage.cs similarity index 87% rename from MatterControlLib/PartPreviewWindow/StartPage/StartTabPage.cs rename to MatterControlLib/PartPreviewWindow/StartPage/StoreTabPage.cs index bafa06423..022ae79a2 100644 --- a/MatterControlLib/PartPreviewWindow/StartPage/StartTabPage.cs +++ b/MatterControlLib/PartPreviewWindow/StartPage/StoreTabPage.cs @@ -30,14 +30,13 @@ either expressed or implied, of the FreeBSD Project. using MatterHackers.Agg; using MatterHackers.Agg.UI; using MatterHackers.MatterControl.SettingsManagement; -using MatterHackers.MatterControl.SlicerConfiguration; using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.PartPreviewWindow.PlusTab { - public class StartTabPage : ScrollableWidget + public class StoreTabPage : ScrollableWidget { - public StartTabPage(PartPreviewContent partPreviewContent, ThemeConfig theme) + public StoreTabPage(PartPreviewContent partPreviewContent, ThemeConfig theme) { this.AutoScroll = true; this.ScrollArea.Padding = new BorderDouble(3); @@ -47,7 +46,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.PlusTab this.MinimumSize = new Vector2(0, 200); this.BackgroundColor = theme.TabBodyBackground; - this.Name = "PlusTabPage"; + this.Name = "StoreTab"; var topToBottom = new FlowLayoutWidget(FlowDirection.TopToBottom) { @@ -55,11 +54,10 @@ namespace MatterHackers.MatterControl.PartPreviewWindow.PlusTab }; this.AddChild(topToBottom); - var lastProfileID = ProfileManager.Instance.LastProfileID; - var lastProfile = ProfileManager.Instance[lastProfileID]; - - topToBottom.AddChild( - new PrinterBar(partPreviewContent, lastProfile, theme)); + if (OemSettings.Instance.ShowShopButton) + { + topToBottom.AddChild(new ExplorePanel(theme, "banners?sk=ii2gffs6e89c2cdd9er21v", "BannerFeed.json")); + } if (OemSettings.Instance.ShowShopButton) { diff --git a/MatterControlLib/PartPreviewWindow/Tabs.cs b/MatterControlLib/PartPreviewWindow/Tabs.cs index 4fd504418..cbfab4ecd 100644 --- a/MatterControlLib/PartPreviewWindow/Tabs.cs +++ b/MatterControlLib/PartPreviewWindow/Tabs.cs @@ -107,6 +107,18 @@ namespace MatterHackers.MatterControl.PartPreviewWindow this.TabContainer.AddChild(iTab.TabContent); } + public virtual void AddTab(GuiWidget tabWidget, int tabPosition, int widgetPosition) + { + var iTab = tabWidget as ITab; + _allTabs.Insert(tabPosition, iTab); + + tabWidget.Click += TabWidget_Click; + + this.TabBar.ActionArea.AddChild(tabWidget, widgetPosition); + + this.TabContainer.AddChild(iTab.TabContent); + } + private void TabWidget_Click(object sender, MouseEventArgs e) { var tab = sender as ITab; @@ -211,11 +223,12 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { private TabTrailer tabTrailer; + private GuiWidget leadingTabAdornment; + public ChromeTabs(GuiWidget rightAnchorItem, ThemeConfig theme) : base(theme, rightAnchorItem) { - // TODO: add in the printers and designs that are currently open (or were open last run). - var leadingTabAdornment = new GuiWidget() + leadingTabAdornment = new GuiWidget() { MinimumSize = new Vector2(16, theme.TabButtonHeight), VAnchor = VAnchor.Bottom @@ -226,7 +239,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow ChromeTab.DrawTabLowerRight(e.Graphics2D, leadingTabAdornment.LocalBounds, (firstItem == this.ActiveTab) ? theme.ActiveTabColor : theme.InactiveTabColor); }; this.TabBar.ActionArea.AddChild(leadingTabAdornment); - // TODO: add in the printers and designs that are currently open (or were open last run). + tabTrailer = new TabTrailer(this, theme) { VAnchor = VAnchor.Bottom, @@ -238,7 +251,15 @@ namespace MatterHackers.MatterControl.PartPreviewWindow public override void AddTab(GuiWidget tabWidget, int tabIndex = -1) { - var position = this.TabBar.ActionArea.GetChildIndex(tabTrailer); + // Default position if tabIndex == -1 is just before the tabTrailer + var widgetPosition = this.TabBar.ActionArea.GetChildIndex(tabTrailer); + var firstTabPosition = this.TabBar.ActionArea.GetChildIndex(leadingTabAdornment) + 1; + + if (tabIndex != -1) + { + // Adjust position to be the head of the list + the tabIndex offset + widgetPosition = firstTabPosition + tabIndex; + } if (tabWidget is ChromeTab newTab) { @@ -246,20 +267,31 @@ namespace MatterHackers.MatterControl.PartPreviewWindow if (tabIndex == -1) { - leftTab = this.AllTabs.OfType().LastOrDefault(); + leftTab = AllTabs.OfType().LastOrDefault(); } else { - leftTab = this.AllTabs.Skip(tabIndex - 1).FirstOrDefault() as ChromeTab; - - var rightTab = leftTab.NextTab; - if (rightTab != null) + if (tabIndex == 0) { - // Insert us in the middle - rightTab.PreviousTab = newTab; + leftTab = null; + var firstTab = AllTabs.OfType().FirstOrDefault(); - // Set Next - newTab.NextTab = rightTab; + newTab.NextTab = firstTab; + firstTab.PreviousTab = newTab; + } + else + { + leftTab = this.AllTabs.Skip(tabIndex - 1).FirstOrDefault() as ChromeTab; + + var rightTab = leftTab?.NextTab; + if (rightTab != null) + { + // Insert us in the middle + rightTab.PreviousTab = newTab; + + // Set Next + newTab.NextTab = rightTab; + } } } @@ -272,13 +304,8 @@ namespace MatterHackers.MatterControl.PartPreviewWindow leftTab.NextTab = newTab; } - if (tabIndex != -1) - { - position = this.TabBar.ActionArea.GetChildIndex(leftTab) + 1; - } - // Call AddTab(widget, int) in base explicitly - base.AddTab(tabWidget, position); + base.AddTab(tabWidget, widgetPosition - firstTabPosition, widgetPosition); this.ActiveTab = newTab; } diff --git a/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs b/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs index a088fbd91..90290991c 100644 --- a/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs +++ b/MatterControlLib/PartPreviewWindow/View3D/PrinterBar/PrinterActionsBar.cs @@ -361,7 +361,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow { if (doDelete) { - printer.Settings.Helpers.SetMarkedForDelete(true); + ProfileManager.Instance.DeleteActivePrinter(true); } }, "Are you sure you want to delete your currently selected printer?".Localize(), diff --git a/MatterControlLib/PartPreviewWindow/ViewControls3D.cs b/MatterControlLib/PartPreviewWindow/ViewControls3D.cs index bd674ea76..2990f7ed0 100644 --- a/MatterControlLib/PartPreviewWindow/ViewControls3D.cs +++ b/MatterControlLib/PartPreviewWindow/ViewControls3D.cs @@ -41,6 +41,7 @@ using MatterHackers.Localizations; using MatterHackers.MatterControl.CustomWidgets; using MatterHackers.MatterControl.DataStorage; using MatterHackers.MatterControl.Library; +using MatterHackers.MatterControl.PrintLibrary; using MatterHackers.VectorMath; namespace MatterHackers.MatterControl.PartPreviewWindow @@ -152,7 +153,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } public ViewControls3D(BedConfig sceneContext, ThemeConfig theme, UndoBuffer undoBuffer, bool isPrinterType) - : base (theme) + : base(theme) { this.ActionArea.Click += (s, e) => { @@ -164,7 +165,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow string iconPath; - this.AddChild(CreateBedButton(sceneContext, theme)); + this.AddChild(CreateAddButton(sceneContext, theme)); this.AddChild(new ToolbarSeparator(theme)); @@ -199,6 +200,18 @@ namespace MatterHackers.MatterControl.PartPreviewWindow }; this.AddChild(redoButton); + this.AddChild(CreateSaveButton(theme)); + + var exportButton = new IconButton(AggContext.StaticData.LoadIcon("cube_export.png", 16, 16, theme.InvertIcons), theme) + { + ToolTipText = "Export".Localize() + }; + exportButton.Click += (s, e) => + { + this.MenuActions.FirstOrDefault(m => m.ID == "Export")?.Action?.Invoke(); + }; + this.AddChild(exportButton); + this.AddChild(new ToolbarSeparator(theme)); undoBuffer.Changed += (sender, e) => @@ -331,11 +344,13 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } } - private GuiWidget CreateBedButton(BedConfig sceneContext, ThemeConfig theme) + private PartPreviewContent partPreviewContent = null; + + private GuiWidget CreateAddButton(BedConfig sceneContext, ThemeConfig theme) { var buttonView = new TextIconButton( "", - AggContext.StaticData.LoadIcon("cube.png", theme.InvertIcons), + AggContext.StaticData.LoadIcon("cube_add.png", 16, 16, theme.InvertIcons), theme); // Remove right Padding for drop style @@ -353,12 +368,39 @@ namespace MatterHackers.MatterControl.PartPreviewWindow return new PopupMenuButton(buttonView, theme) { Name = "Bed Options Menu", + ToolTipText = "Add Content".Localize(), DynamicPopupContent = () => { - var menuContent = theme.CreateMenuItems(popupMenu, this.MenuActions); - menuContent.MinimumSize = new Vector2(200, 0); + if (partPreviewContent == null) + { + partPreviewContent = this.Parents().FirstOrDefault(); + } - return menuContent; + var widget = new GuiWidget() + { + VAnchor = VAnchor.Fit, + HAnchor = HAnchor.Fit, + BackgroundColor = theme.TabBarBackground, + Padding = new BorderDouble(theme.DefaultContainerPadding / 2, 0) + }; + + var height = view3DWidget.Height - theme.DefaultContainerPadding; + + widget.AddChild(new PrintLibraryWidget(partPreviewContent, theme) + { + HAnchor = HAnchor.Absolute, + VAnchor = VAnchor.Absolute, + Height = height, + Width = 400, + MinimumSize = new Vector2(400, height) + }); + + return widget; + + //var menuContent = theme.CreateMenuItems(popupMenu, this.MenuActions); + //menuContent.MinimumSize = new Vector2(200, 0); + + //return menuContent; }, BackgroundColor = theme.ToolbarButtonBackground, HoverColor = theme.ToolbarButtonHover, @@ -368,17 +410,65 @@ namespace MatterHackers.MatterControl.PartPreviewWindow }; } - private async void LoadAndAddPartsToPlate(string[] filesToLoad, InteractiveScene scene) + private GuiWidget CreateSaveButton(ThemeConfig theme) + { + var iconButton = new IconButton( + AggContext.StaticData.LoadIcon("save_grey_16x.png", 16, 16, theme.InvertIcons), + theme) + { + ToolTipText = "Save".Localize(), + }; + + iconButton.Click += (s, e) => + { + ApplicationController.Instance.Tasks.Execute("Saving".Localize(), sceneContext.SaveChanges).ConfigureAwait(false); + }; + + // Remove right Padding for drop style + iconButton.Padding = iconButton.Padding.Clone(right: 0); + + var button = new PopupMenuButton(iconButton, theme) + { + Name = "Save SplitButton", + ToolTipText = "Save As".Localize(), + DynamicPopupContent = () => + { + var popupMenu = new PopupMenu(ApplicationController.Instance.MenuTheme); + + var saveAs = popupMenu.CreateMenuItem("Save As".Localize()); + saveAs.Click += (s, e) => UiThread.RunOnIdle(() => + { + this.MenuActions.FirstOrDefault(m => m.ID == "SaveAs")?.Action?.Invoke(); + }); + + return popupMenu; + }, + BackgroundColor = theme.ToolbarButtonBackground, + HoverColor = theme.ToolbarButtonHover, + MouseDownColor = theme.ToolbarButtonDown, + DrawArrow = true, + Margin = theme.ButtonSpacing, + }; + + iconButton.Selectable = true; + + return button; + } + + public static async void LoadAndAddPartsToPlate(GuiWidget originatingWidget, string[] filesToLoad, BedConfig sceneContext) { if (filesToLoad != null && filesToLoad.Length > 0) { - await Task.Run(() => loadAndAddPartsToPlate(filesToLoad, scene)); + + await Task.Run(() => loadAndAddPartsToPlate(filesToLoad, sceneContext)); - if (HasBeenClosed) + if (originatingWidget.HasBeenClosed) { return; } + var scene = sceneContext.Scene; + bool addingOnlyOneItem = scene.Children.Count == scene.Children.Count + 1; if (scene.HasChildren()) @@ -391,14 +481,15 @@ namespace MatterHackers.MatterControl.PartPreviewWindow } scene.Invalidate(new InvalidateArgs(null, InvalidateType.Content, null)); - this.Invalidate(); } } - private async Task loadAndAddPartsToPlate(string[] filesToLoadIncludingZips, InteractiveScene scene) + private static async Task loadAndAddPartsToPlate(string[] filesToLoadIncludingZips, BedConfig sceneContext) { if (filesToLoadIncludingZips?.Any() == true) { + var scene = sceneContext.Scene; + // When a single gcode file is selected, swap the plate to the new GCode content if (filesToLoadIncludingZips.Count() == 1 && filesToLoadIncludingZips.FirstOrDefault() is string firstFilePath @@ -427,7 +518,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow ) { filesToLoad.Add(loadedFileName); - } + } else if (extension == ".ZIP") { List partFiles = ProjectFileHandler.ImportFromProjectArchive(loadedFileName); @@ -508,7 +599,7 @@ namespace MatterHackers.MatterControl.PartPreviewWindow new OpenFileDialogParams(filter, multiSelect: true), (openParams) => { - this.LoadAndAddPartsToPlate(openParams.FileNames, sceneContext.Scene); + ViewControls3D.LoadAndAddPartsToPlate(this, openParams.FileNames, sceneContext); }); }); } @@ -553,9 +644,9 @@ namespace MatterHackers.MatterControl.PartPreviewWindow ID = "Save", Title = "Save".Localize(), Shortcut = "Ctrl+S", - Action = async () => + Action = () => { - await ApplicationController.Instance.Tasks.Execute("Saving".Localize(), sceneContext.SaveChanges); + ApplicationController.Instance.Tasks.Execute("Saving".Localize(), sceneContext.SaveChanges).ConfigureAwait(false); }, IsEnabled = () => sceneContext.EditableScene }, diff --git a/MatterControlLib/PrinterControls/PrinterConnections/PrinterSetup.cs b/MatterControlLib/PrinterControls/PrinterConnections/PrinterSetup.cs index 7aa548c58..8e7be6fe3 100644 --- a/MatterControlLib/PrinterControls/PrinterConnections/PrinterSetup.cs +++ b/MatterControlLib/PrinterControls/PrinterConnections/PrinterSetup.cs @@ -33,26 +33,17 @@ namespace MatterHackers.MatterControl.PrinterControls.PrinterConnections { public static class PrinterSetup { - public static Func ShouldShowAuthPanel { get; set; } - public static Action ShowAuthDialog; - public static Action ChangeToAccountCreate; - public enum StartPageOptions { Default, SkipWifiSetup, ShowMakeModel } public static DialogPage GetBestStartPage(StartPageOptions options = StartPageOptions.Default) { // Do the printer setup logic bool WifiDetected = AppContext.Platform.IsNetworkConnected(); - if (!WifiDetected + if (!WifiDetected && options != StartPageOptions.SkipWifiSetup) { return new SetupWizardWifi(); } - else if (ShouldShowAuthPanel?.Invoke() == true - && options != StartPageOptions.ShowMakeModel) - { - return new ShowAuthPanel(); - } else { return new SetupStepMakeModelName(); diff --git a/MatterControlLib/PrinterControls/PrinterConnections/SetupStepMakeModelName.cs b/MatterControlLib/PrinterControls/PrinterConnections/SetupStepMakeModelName.cs index d5c865ce9..e611ddd00 100644 --- a/MatterControlLib/PrinterControls/PrinterConnections/SetupStepMakeModelName.cs +++ b/MatterControlLib/PrinterControls/PrinterConnections/SetupStepMakeModelName.cs @@ -34,6 +34,7 @@ using MatterHackers.Agg; using MatterHackers.Agg.Platform; using MatterHackers.Agg.UI; using MatterHackers.Localizations; +using MatterHackers.MatterControl.CustomWidgets; using MatterHackers.MatterControl.SettingsManagement; using MatterHackers.MatterControl.SlicerConfiguration; @@ -98,6 +99,25 @@ namespace MatterHackers.MatterControl.PrinterControls.PrinterConnections contentRow.AddChild(printerModelContainer); contentRow.AddChild(createPrinterNameContainer()); + if (ApplicationController.GuestUserActive()) + { + var signInRow = new FlowLayoutWidget() + { + HAnchor = HAnchor.Stretch, + }; + + signInRow.AddChild(new TextWidget("Sign in to access your existing printers", pointSize: theme.DefaultFontSize, textColor: theme.Colors.PrimaryTextColor)); + signInRow.AddChild(new HorizontalSpacer()); + + var signInLink = new LinkLabel("Sign In", theme, pointSize: theme.DefaultFontSize); + signInLink.Click += (s, e) => UiThread.RunOnIdle(() => + { + this.DialogWindow.ChangeToPage(ApplicationController.GetAuthPage()); + }); + signInRow.AddChild(signInLink); + contentRow.AddChild(signInRow); + } + //Construct buttons nextButton = theme.CreateDialogButton("Next".Localize()); nextButton.Name = "Save & Continue Button"; diff --git a/MatterControlLib/PrinterControls/PrinterConnections/ShowAuthPanel.cs b/MatterControlLib/PrinterControls/PrinterConnections/ShowAuthPanel.cs deleted file mode 100644 index 8d7dfd345..000000000 --- a/MatterControlLib/PrinterControls/PrinterConnections/ShowAuthPanel.cs +++ /dev/null @@ -1,134 +0,0 @@ -/* -Copyright (c) 2018, Greg Diaz, John Lewin -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this - list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, - this list of conditions and the following disclaimer in the documentation - and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -The views and conclusions contained in the software and documentation are those -of the authors and should not be interpreted as representing official policies, -either expressed or implied, of the FreeBSD Project. -*/ - -using System; -using System.Linq; -using MatterHackers.Agg.UI; -using MatterHackers.Localizations; -using MatterHackers.MatterControl.CustomWidgets; -using MatterHackers.MatterControl.SlicerConfiguration; - -namespace MatterHackers.MatterControl.PrinterControls.PrinterConnections -{ - public class ShowAuthPanel : DialogPage - { - public ShowAuthPanel() - : base("Skip".Localize()) - { - this.WindowTitle = "Setup Wizard".Localize(); - - contentRow.AddChild(new WrappedTextWidget("Sign in to access your cloud printer profiles.\n\nOnce signed in you will be able to access".Localize() + ":") - { - TextColor = ActiveTheme.Instance.PrimaryTextColor, - }); - - AddBulletPointAndDescription(contentRow, - "Cloud Library".Localize(), - "Save your designs to the cloud and access them from anywhere in the world. You can also share them any time with anyone you want.".Localize()); - AddBulletPointAndDescription(contentRow, - "Cloud Printer Profiles".Localize(), - "Create your machine settings once, and have them available anywhere you want to print. All your changes appear on all your devices.".Localize()); - AddBulletPointAndDescription(contentRow, - "Remote Monitoring".Localize(), - "Check on your prints from anywhere. With cloud monitoring, you have access to your printer no matter where you go.".Localize()); - - contentRow.AddChild(new VerticalSpacer()); - - var rememberChoice = new CheckBox("Don't remind me again".Localize(), ActiveTheme.Instance.PrimaryTextColor); - contentRow.AddChild(rememberChoice); - rememberChoice.CheckedStateChanged += (s, e) => - { - ApplicationSettings.Instance.set(ApplicationSettingsKey.SuppressAuthPanel, rememberChoice.Checked.ToString()); - }; - - this.SetCancelButtonName("Connection Wizard Skip Sign In Button"); - - var createAccountButton = new TextButton("Create Account".Localize(), theme) - { - Name = "Create Account From Connection Wizard Button", - Margin = new Agg.BorderDouble(right: 5), - BackgroundColor = theme.MinimalShade - }; - createAccountButton.Click += (s, e) => - { - UiThread.RunOnIdle(() => - { - DialogWindow.Close(); - PrinterSetup.ChangeToAccountCreate(); - }); - }; - this.AddPageAction(createAccountButton); - - var signInButton = new TextButton("Sign In".Localize(), theme) - { - Name = "Sign In From Connection Wizard Button", - BackgroundColor = theme.MinimalShade - }; - signInButton.Click += (s, e) => - { - UiThread.RunOnIdle(() => - { - DialogWindow.Close(); - PrinterSetup.ShowAuthDialog?.Invoke(); - }); - }; - this.AddPageAction(signInButton); - } - - protected override void OnCancel(out bool abortCancel) - { - if (!ProfileManager.Instance.ActiveProfiles.Any()) - { - abortCancel = true; - - UiThread.RunOnIdle(() => - { - DialogWindow.ChangeToPage(); - }); - } - - abortCancel = false; - } - - private void AddBulletPointAndDescription(FlowLayoutWidget contentRow, string v1, string v2) - { - contentRow.AddChild(new TextWidget("• " + v1) - { - HAnchor = HAnchor.Left, - TextColor = ActiveTheme.Instance.PrimaryTextColor, - Margin = new Agg.BorderDouble(0, 0, 0, 10), - }); - contentRow.AddChild(new WrappedTextWidget(v2) - { - TextColor = ActiveTheme.Instance.SecondaryTextColor, - Margin = new Agg.BorderDouble(20, 5, 5, 5), - }); - } - } -} \ No newline at end of file diff --git a/MatterControlLib/SettingsManagement/OemSettings.cs b/MatterControlLib/SettingsManagement/OemSettings.cs index 1606bdba3..46891fc50 100644 --- a/MatterControlLib/SettingsManagement/OemSettings.cs +++ b/MatterControlLib/SettingsManagement/OemSettings.cs @@ -34,7 +34,9 @@ using System.Linq; using System.Runtime.Serialization; using System.Threading.Tasks; using MatterHackers.Agg; +using MatterHackers.Agg.Image; using MatterHackers.Agg.Platform; +using MatterHackers.MatterControl.Library; using MatterHackers.MatterControl.SlicerConfiguration; using Newtonsoft.Json; @@ -76,6 +78,34 @@ namespace MatterHackers.MatterControl.SettingsManagement public List PreloadedLibraryFiles { get; } = new List(); + public ImageBuffer GetIcon(string oemName) + { + string cachePath = ApplicationController.CacheablePath("OemIcons", oemName + ".png"); + try + { + if (File.Exists(cachePath)) + { + return AggContext.ImageIO.LoadImage(cachePath); + } + } + catch + { + } + + var imageBuffer = new ImageBuffer(16, 16).MultiplyWithPrimaryAccent(); + + ApplicationController.Instance.LoadRemoteImage( + imageBuffer, + ApplicationController.Instance.GetFavIconUrl(oemName), + scaleToImageX: false).ContinueWith(t => + { + AggContext.ImageIO.SaveImageData(cachePath, imageBuffer); + }); + + return imageBuffer; + } + + internal void SetManufacturers(IEnumerable> unorderedManufacturers, List whitelist = null) { // Sort manufacturers by name diff --git a/MatterControlLib/SlicerConfiguration/Settings/PrinterSettings.cs b/MatterControlLib/SlicerConfiguration/Settings/PrinterSettings.cs index 9e8f2be1c..291ea7f91 100644 --- a/MatterControlLib/SlicerConfiguration/Settings/PrinterSettings.cs +++ b/MatterControlLib/SlicerConfiguration/Settings/PrinterSettings.cs @@ -43,6 +43,7 @@ using MatterHackers.DataConverters3D; using MatterHackers.Localizations; using MatterHackers.MatterControl.ConfigurationPage.PrintLeveling; using MatterHackers.MatterControl.ContactForm; +using MatterHackers.MatterControl.PrinterControls.PrinterConnections; using MatterHackers.MatterControl.SettingsManagement; using MatterHackers.MeshVisualizer; using MatterHackers.VectorMath; @@ -84,8 +85,6 @@ namespace MatterHackers.MatterControl.SlicerConfiguration public string ID { get; set; } - public static Func ShouldShowAuthPanel { get; set; } - private static object writeLock = new object(); [JsonIgnore] @@ -468,7 +467,7 @@ namespace MatterHackers.MatterControl.SlicerConfiguration public async static Task RecoverProfile(PrinterInfo printerInfo) { - bool userIsLoggedIn = !ShouldShowAuthPanel?.Invoke() ?? false; + bool userIsLoggedIn = !ApplicationController.GuestUserActive?.Invoke() ?? false; if (userIsLoggedIn && printerInfo != null) { // Attempt to load from MCWS history diff --git a/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs b/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs index b1ab7c967..f4385e3e9 100644 --- a/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs +++ b/MatterControlLib/SlicerConfiguration/Settings/ProfileManager.cs @@ -129,6 +129,63 @@ namespace MatterHackers.MatterControl.SlicerConfiguration return userProfilesDirectory; } + public void DeletePrinter(string printerID, bool markedForDelete) + { + + bool isActivePrinter = printerID == this.ActiveProfile.ID; + + var printerInfo = ProfileManager.Instance[printerID]; + if (printerInfo != null) + { + printerInfo.MarkedForDelete = markedForDelete; + ProfileManager.Instance.Save(); + } + + if (isActivePrinter) + { + // Clear selected printer state + ProfileManager.Instance.LastProfileID = ""; + } + + UiThread.RunOnIdle(async () => + { + if (isActivePrinter) + { + await ApplicationController.Instance.ClearActivePrinter(); + } + + // Notify listeners of a ProfileListChange event due to this printers removal + ProfileManager.ProfilesListChanged.CallEvents(this, null); + + // Force sync after marking for delete if assigned + ApplicationController.SyncPrinterProfiles?.Invoke("SettingsHelpers.SetMarkedForDelete()", null); + }); + } + + public void DeleteActivePrinter(bool markedForDelete) + { + var printerInfo = ProfileManager.Instance.ActiveProfile; + if (printerInfo != null) + { + printerInfo.MarkedForDelete = markedForDelete; + ProfileManager.Instance.Save(); + } + + // Clear selected printer state + ProfileManager.Instance.LastProfileID = ""; + + UiThread.RunOnIdle(async () => + { + await ApplicationController.Instance.ClearActivePrinter(); + + // Notify listeners of a ProfileListChange event due to this printers removal + ProfileManager.ProfilesListChanged.CallEvents(this, null); + + // Force sync after marking for delete if assigned + ApplicationController.SyncPrinterProfiles?.Invoke("SettingsHelpers.SetMarkedForDelete()", null); + }); + } + [JsonIgnore] public bool IsGuestProfile => this.UserName == "guest"; diff --git a/MatterControlLib/SlicerConfiguration/Settings/SettingsHelpers.cs b/MatterControlLib/SlicerConfiguration/Settings/SettingsHelpers.cs index e9894f366..2675115ed 100644 --- a/MatterControlLib/SlicerConfiguration/Settings/SettingsHelpers.cs +++ b/MatterControlLib/SlicerConfiguration/Settings/SettingsHelpers.cs @@ -242,30 +242,6 @@ namespace MatterHackers.MatterControl.SlicerConfiguration return firstLayerValue; } - public void SetMarkedForDelete(bool markedForDelete) - { - var printerInfo = ProfileManager.Instance.ActiveProfile; - if (printerInfo != null) - { - printerInfo.MarkedForDelete = markedForDelete; - ProfileManager.Instance.Save(); - } - - // Clear selected printer state - ProfileManager.Instance.LastProfileID = ""; - - UiThread.RunOnIdle(async () => - { - await ApplicationController.Instance.ClearActivePrinter(); - - // Notify listeners of a ProfileListChange event due to this printers removal - ProfileManager.ProfilesListChanged.CallEvents(this, null); - - // Force sync after marking for delete if assigned - ApplicationController.SyncPrinterProfiles?.Invoke("SettingsHelpers.SetMarkedForDelete()", null); - }); - } - public void SetBaudRate(string baudRate) { printerSettings.SetValue(SettingsKey.baud_rate, baudRate); diff --git a/MatterControlLib/Utilities/WebUtilities/RequestManager.cs b/MatterControlLib/Utilities/WebUtilities/RequestManager.cs index 26895465d..c3fd3fd07 100644 --- a/MatterControlLib/Utilities/WebUtilities/RequestManager.cs +++ b/MatterControlLib/Utilities/WebUtilities/RequestManager.cs @@ -157,7 +157,7 @@ namespace MatterHackers.MatterControl } catch (Exception e) { - System.Diagnostics.Trace.WriteLine(e.Message); + System.Diagnostics.Trace.WriteLine(e.Message); } } return request; diff --git a/StaticData/Icons/Library/cloud_20x20.png b/StaticData/Icons/Library/cloud_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..79d082ab4b26ed75f2469291c4c73ed708750e29 GIT binary patch literal 1034 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7r87e!N+NuHtdjF{^%7I^ zlT!66atjzhz{b9!ATc>RwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kJZ zJzX3_EP9__wADYHD8l;Te*Ejy1%ggGIy_m1nHvm8S=)qWAG|rQcz*G_o!>uQ_o#bdZP;u#rJz~)PI!IJ3gveP zqd2}7Hn`_3d~Du1Z9~NI8lLJCA(NiED}T~RV7gtg%BbR)Q-)W?f$%1^Gf52M8jqGZ z-Dp+xd2H6QcgJBqo@Cn-E^m{L7q-Q|>Z0wkkX3odw7h*%o;|j$k3XIH8*xRwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kIT zc)B=-SoBW4tn1wpD8Tw4{cqI%Nf9i9W*oCjIwVXv)SB~R>YHb<&i=_ymL_uCN#|=5gZ$#tu)_;FD&!phlnK{@g&=uL~02cXDoZ;YH?ici|V9V5ywk9 zo}ZfZsQlPEsS1w5t=E~>UMLm2uKLcdm^H4sKrTK*d`GjeYbIvu=vJT?`M>KDe!SkIscPA X`LkwQ!J?NXpgim8>gTe~DWM4fnU|CE literal 0 HcmV?d00001 diff --git a/StaticData/Icons/Library/download_20x20.png b/StaticData/Icons/Library/download_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..6400c79f3905f76478c74f76fb9f33a8b8eeddc0 GIT binary patch literal 912 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7r87e!N+NuHtdjF{^%7I^ zlT!66atjzhz{b9!ATc>RwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kIP zJzX3_EP9g@76=$Tni$=D~Cmb@aK5rcOwG>1L2~nR?N(Y&2?tYH!?8j5WK|0Y?(2u z-q^t4hQR8Xjf|6ylq*RFxOAmSOh`&ds5ss*OWlJlkB5h+W{QM^00YB;Eiw;W|2};U O%1EBBelF{r5}E*_mRl15 literal 0 HcmV?d00001 diff --git a/StaticData/Icons/Library/file_20x20.png b/StaticData/Icons/Library/file_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..4e431e058d6893cfbabfbad6e857f0206b373e3d GIT binary patch literal 298 zcmeAS@N?(olHy`uVBq!ia0y~yU=RUe4mJh`hTcwDUIqpRmUKs7M+SzC{oH>NSs54@ zI14-?iy0XB4uLSEsD@Vq0|SFZiEBhjaDG}zd16s2LqTF@UWr~_YKel0o}r#$veq1L z1_p)|o-U3d7XEK9ujOKL6lr@H81Y~4kn{&Omy4o_2TuzaM&+cfP7puCcDbxsVMpP? z2Y-LaE#t6y?qkUD`w;Wtw*HwL-s!(iXih%B$n0Y7-WTf7DeTsf)!%Ag_qNCE$+qC1 z38Kak&fK5=NM2cU&{S`S)4clTS*9A>vlnz4a;=DLe*ZO)w=Rr#l`HSN4PV?hE57?D wp?6sLQ+A}>4X3qzdOU1_lNOPgg&ebxsLQ0Gc0cm;e9( literal 0 HcmV?d00001 diff --git a/StaticData/Icons/Library/folder_20x20.png b/StaticData/Icons/Library/folder_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..7866714a6d5ff21a68178f1857c106d8d0a0fda6 GIT binary patch literal 741 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s78#A4q1B!#(ofJG>oy`m_ z&CGO-^b8poG|o?*xZ9iAQJ{VQk7MkDqFXm`EDGy9!r>_0%@V&r>?2FOZ_zVFm5)2T z1vZN2Fmo;HUGMatDVH_dF+jvoXU!VX+>moSV-%E@6ilD{^V@do!{>CSXnLBahEym{ zJ@;ba#fI#%ynCU4eRM-jF1lq$m~J`wQm}Q>1Q3R4`7+PPFXxCx?g8-u*ZJBo*Fyrr%wvDl+AkRBP zT~{NV(`Q<68trgB>-BBLj~OolXL?>+^Y8!L)q=0T-@N&nIpAl?to*wHds**WUjAca za`wHlMtw_niDBJS2DW>y=S8=!t1ZYoa5FWq`^|x$YFq3x5^d*9Kb1Q5Xwru&Cf?!; zHN0E7%g%E>m$~1(NiV*Xb$9gzA z-~Hc34_18mdZbXtQGS}g&jPNUCS|fReKFVXO1B*>ja}|;bKcrD(Bx2_U2iOV`^06L zd!BCBkm1qf$uHe)`)RfvtK){F@0hGsnu+MKb}b6n8MDxBZ{hJZGmmujq(rKn-lUgv z``M1Gd^ZcjCg|*AZk4%t_BiVzjZ6CXWRwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kHv zJzX3_EP8t{TkE$33a~vmt|u)x-EEQBzb2>~K?Ubd%H;{1maV;7Aocb@M7sy?V~MNGUtLs=dWIMVzf!!=qqAG$M5lO?9RJ&# zla;<^@>AoO3>sO#wBN5gviX0^JC|R*o9}Xj-Z{~xrkj7{z2mJ@r#E!DO_17gZ14QQ zx`}UnE&UsEcJ`%Ie`qw1N$@{sB)V|X@9(vbxgBTvDfaK|y8z12p00i_>zopr06dRwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kHA zdb&7OWV{G%h!qUQ(nOKk+cv>HPLHg`6{Mwfx^dnc*2$`NySCa_{7J&7w08rrtTPaQw?6 zk@dF^Nr}!Wbdq#`5_m|?RiuDpc8>h^8wrh36)XGn$`dr`TfKZd7g?Too!EE l$(~#va*^+=+l!xy>&~n-WL0~3G6j^mJYD@<);T3K0RVJuY@PrB literal 0 HcmV?d00001 diff --git a/StaticData/Icons/Library/purchased_20x20.png b/StaticData/Icons/Library/purchased_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..080fedcc9bcce46377257f4aefe54f69fda129cc GIT binary patch literal 1004 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7r87e!N+NuHtdjF{^%7I^ zlT!66atjzhz{b9!ATc>RwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kH| zdb&7lF z+SFx_3EmS5*nZ76(qk+{BM6OX6pT)(yEdM2ZPq8nSnB?r>mdK II;Vst0Prf2wg3PC literal 0 HcmV?d00001 diff --git a/StaticData/Icons/Library/queue_20x20.png b/StaticData/Icons/Library/queue_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..78804baf32dc43a09531eafc8019ad5b65b1ef4d GIT binary patch literal 925 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7r87e!N+NuHtdjF{^%7I^ zlT!66atjzhz{b9!ATc>RwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kI5 zdAc};SoGe#w3m-DQK0o9`$yr48Zk^;;}%c4s-Y6qDP8Ez)#{a$dpPt(h@OL!^Tzm% ztVio)m(QH+^XzQOvk#wFiPkhR>djcxuRX2eVVj=Z{RGXvgXtCv#M`c~c--PwxFV|P zv0z!oai)YG-KdQWGZLEJbQ=DcU6g*a@qjVgghahPn{^)aNWPmttJB88$@jt$F6WRZ dj=#+JGA>;%E_(WC_iRwE@^tlcS?83{1OUANX|wRwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kJu zd%8G=SoB`KxZmqgpbYDWekDCcCl}}XZDy?;RSg**1e@k9I(l&D^%sn@q@qRU-0E&q zh>DUqa&X~=A2a_+)qQ85EU|=)#rCUuhEb04?6OC#o8U_-4(3CJrlzg9pqgX zCh)*dEfp zH6x*;^4{_et#coG{BIWVPi(1d%1=~3E%fGrN{3#`ZM}z9XTBG99X}y*hxK~tbLO=% xBHThO>6cZyCx3Gnx3wHF6*2UngH!*kdOcX literal 0 HcmV?d00001 diff --git a/StaticData/Icons/Library/shared_20x20.png b/StaticData/Icons/Library/shared_20x20.png new file mode 100644 index 0000000000000000000000000000000000000000..c7c031990f331f652e8455fb104903cbd28ef2cf GIT binary patch literal 977 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s7r87e!N+NuHtdjF{^%7I^ zlT!66atjzhz{b9!ATc>RwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9b|85kJO zdAc};SoB`KXz1q@DA4+lU&q8vNL5NWifOKN0jtTIhOSFpmwIpcQecs1m#3eS3j3^P6B&L_>rj=57~yZ&T1B_tI0?cR5& z_4OjfUgqZXTA%cUo|{(+*F1TDko(NrDIYnfP0+ufo$sN;*|wprcFp#t8Z&3^PtEL4 znvcaU|IL>ks5`^yUd^F1xm8X2+xrU~xaATUAHOqU^JL05c;&~Z`{S&M#wPZw52Ux1 to%+G{^Y+}gQ$F&p`eV1FSM3MG^PcF4@F_>C85kHCJYD@<);T3K0RWpYZwLSY literal 0 HcmV?d00001 diff --git a/StaticData/Icons/cube_add.png b/StaticData/Icons/cube_add.png new file mode 100644 index 0000000000000000000000000000000000000000..f9e96958d2410f2f82b6be20969f618a83536b43 GIT binary patch literal 809 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s78#A4q1B!#(ofJG>oy`m_ z&CGO-^b8poG|o?*xZ9iAQJ~%a{`!t4F-;x5&L)AQ>>XxeockPfCkgl5iqX?o>^vUp zawKensECVneCt2fTU^(g7HBj@L_})eS}~`1r-RFeg6TJZe%o$+_?*s^RhhQQQ_G#z zW*a*%W=vHxS{-5=*cra2jn8*`&&dxRr{+KYJ5A_#e7V)mANN;uAJvuhTGiTKa`+5q zKc~OWbDs9?EwjH)5fio({5JcO!$WEJ{#}3G6kK}h-|lNFGUb+3>(nRweg6_{Q`1_O z7S+uBGAXX2Ke$tU-dw)!e9`QKN=GypMUF|TdDz?XoVw+&zPIFLpZ%lVpFalcc&*V} zeUM9yTg7wYVf|N=Qu@37JLIO%OgsAW|NDJWo%`a0JfAT7eKgzo@KW5$IW}Uv{xa>K@rmQ!+Z#8v@ z+8&jP+t_ZmB{^S*n|1QhWzq)(<2Ed5^yg=FKANVz`NxrI_LqZF@_Qm?e&{}TwV*>i zWQp=zEs~B&4(V3B%9N3@xu@CTxY>TO z9!LAxcAxVWB%dwueHpQ#*-|G%K!`i3#Mk=P-@pxx(Pw^aE<45ARkmgKyYoI*{&Xbs zWI5zCrm0snloy`m_ z&CGO-^b8poG|o?*xZ9iAQJ~%a{`!t4F-;x5&L)AQ>>XxeockPfCkgl5iqX?o>^vUp zawKensECVneCt2fTU^(g7HBj@L_})eS}~`1r-RFeg6TJZe%o$+_?*s^RhhQQQ_G#z zW*a*%W=vHxS{-5=*cra2jn8*`&&dxRr{+KYJ5A_#e7V)mANN;uAJvuhTGiTKa`+5q zKc~OWbDs9?EwjH)5fio({5JcO!$WEJ{#}3G6kK}h-|lNFGUb+3>(nRweg6_{Q`1_O z7S+uBGAXX2Ke$tU-dw)!e9`QKN=GypMUF|TdDz?XoVw+&zPIFLpZ%lVpFalcc&*V} zeUM9yTg7wYVf|N=Qu@37JLIO%OgsAW|NDJWo%`aMe5rvw!hEV3wES{fm4e)izWZ4VDMMh2ZLO1U~M zMkdGk?YVmr_rIUHJy&+~!`1P7zyGiQ|9-D|uhUeorTdC@{$bmrFfFxr!CgI{{hn=K z*2z?!da}=aceSdMQ0EcVThRvuj%gg6F=_cek$Za!P8hM=Hl4w)m9F)u;McQ@*>kN0 zoD(Ohmn*ScbKAhRDS@G;+n4Y2mfz3A6Q9jGyod9fd_`P;{_>Q@5*I8UQ8n#bjjQPx;^4|Z_LA8nxHyO@8xNlVZ$|G?NMQuI!JvI9ViB%im6FWME*BC~=J_3C>R|DNig)Whh9@%q!8$ zOD$0_(KFODOxBv?&A`Bz=;`7ZV&T8`@?P!sK#>C<)y4j?uyr3)dYWpMc;wKtr7HRp zue&VTAm~~)MPkaP&M8SteAJX@PZARR_}5O>yuKlLzSZ2~cg6GX?>(>o`@DvW=Bo=m zbvkUt+27_Anz?G9``D>}X0e>_3%+O9-dH|flN~er>?7v;6)w!T4BGD|KJ#uApr#RFzum{?ClsFF2hexSs7VO`lWh z+okp8Q23rh!ZsT0sSuOYTGjvbWLFr(X1J#tY)?BcnI1d&koCM{EYk`Z*zbluyY{9x znM>Cru+MqVL#E#!zT6FjI_uMi)vpe4wiz}^<{XFx8@BvILiYswv$9eTS67AVf4HUB zymM>#odhAfFA_f!We#5c=GgqRwL~E)H9a%WR_Xoj{Yna%DYi=CroINg16w1-Ypui3%0DIeEoa6}C!XbFK1lpi<;HsXMd|v6mX?*7iAWdWaj57fXqxx$}cUkRZ`LiS)vaT z3373>- z?S`g|$Ez>wcZfb1B_QG|z^$F_Dm}IM90zAo+RVB6^}CJVoNF?j5)xE3RXXV98Zn9Q zmd~Gw&-teQYQ@5tWw#D>t@C3)?i8}+d$dm4o_EICp?2TbU)6UnW!2ewNV#iOYO8w7 z^03XTPDUSE>!q|ur$*_{>XWRk`98KmPtqqpcKv4($<6vFAy=sE*lCNPV_r_)oTpD0 zE}dlg^t;_NZ<&idNs{rCUva9gt%nqlQX7W}$s5V!l_KF)a7!#^wxde%E{ zusu+@Ys&na?F-E>a;u)*%HI3Bpepc^((Qt6jI!UeBDnv1m=$L0u<0MaV8E{%9WSUmCOEi4~mtVp`S(}Cp zj9U^6Qf^N4`7>$y%*xMu_gCr{cj);=eF=E1{fWJ;@r&$4tAe_T<}xwKJvvH{4|H02 zxCx$P|6+K6b)M26={XODbP0l+XkKc1(l- literal 0 HcmV?d00001 diff --git a/StaticData/Icons/save_grey_16x.png b/StaticData/Icons/save_grey_16x.png new file mode 100644 index 0000000000000000000000000000000000000000..911d7819bd4c41435182987205b4361ca20689ed GIT binary patch literal 244 zcmeAS@N?(olHy`uVBq!ia0y~yU=RRd4mJh`2Kmqb6B!s77>k44ofy`glX=O&z`&C3 z=qc&oEhQjyD4XL#n5XV~B*TTe z6K9$aW*X*S-@Z!jFR$yVlzTt)hUCM2hH`cw3mH6J{an^LB{Ts5uzXU- literal 0 HcmV?d00001