diff --git a/BackgroundLaunch/BackgroundLaunch.csproj b/BackgroundLaunch/BackgroundLaunch.csproj index b07448a..551c238 100644 --- a/BackgroundLaunch/BackgroundLaunch.csproj +++ b/BackgroundLaunch/BackgroundLaunch.csproj @@ -30,7 +30,6 @@ - diff --git a/BackgroundLaunch/Runner.cs b/BackgroundLaunch/Runner.cs index f51a73a..0b6c334 100644 --- a/BackgroundLaunch/Runner.cs +++ b/BackgroundLaunch/Runner.cs @@ -78,7 +78,7 @@ public Runner(LaunchInfo item) if ((item.ItemType == ItemTypeEnum.Solution) || (item.ItemType == ItemTypeEnum.Project)) { startInfo.FileName = this.launchInfo.Target; - startInfo.Arguments = item.Path; + startInfo.Arguments = "\"" + item.Path + "\""; if (!string.IsNullOrEmpty(item.Commands)) { startInfo.Arguments += " " + item.Commands; diff --git a/README.md b/README.md index dd7f146..b7c9c13 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,6 @@ Doing so manually is most often a tedious and time consuming process. Also, as of late, the "Start Window" of Visual Studio is not the most helpful, besides its inherent limitations of managing its recently used list. ## Working Features - - Importing from folders - Importing from any Visual Studio version and profile - Support for VS 2017, 2019 and 2022 @@ -51,6 +50,16 @@ Also, as of late, the "Start Window" of Visual Studio is not the most helpful, b - Support for dark mode - Support for non-Windows environments +## How to use +First either import your whole recent list from VS by using the toolbar button with the VS symbol, or import from folder directly with the folder-up-arrow toolbar button. You can also drag solutions or projects from the explorer directly into it. + +After you have at least one SoP in the list, you can start it by pressing enter on the selected item or double clicking it. When you double click on a folder/group then the whole folder will be launched, each item in its own VS instance as defined in the items settings. + +As expected, you can use drag and drop to order items, alt+enter to open the items settings, del to delete an item, etc. Remember that the settings of a folder/group item are applied before all items. This means that when you set a folder to execute as admin, all the contained SoPs will run as admin too. +You can set for each SoP which (installed) version of Visual Studio you want to open it with. Recomended version is shown for each as it is defined in the solution file. +There are also context menus that you can invoke with right clicking on an item. + + ![SoP Settings](https://github.com/Hefaistos68/VSLauncherX/blob/master/docs/Settings-sample.jpg) diff --git a/VSLXshared/DataModel/VsFolder.cs b/VSLXshared/DataModel/VsFolder.cs index ea31e40..967347a 100644 --- a/VSLXshared/DataModel/VsFolder.cs +++ b/VSLXshared/DataModel/VsFolder.cs @@ -109,6 +109,7 @@ public VsItemList Items /// /// Gets or sets a value indicating whether this folder is expanded /// + [JsonIgnore] public bool Expanded { get; set; } /// diff --git a/VSLXshared/Helpers/FileHelper.cs b/VSLXshared/Helpers/FileHelper.cs new file mode 100644 index 0000000..8b5b63a --- /dev/null +++ b/VSLXshared/Helpers/FileHelper.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace VSLXshared.Helpers +{ + public static class FileHelper + { + public static readonly string ExecutablesFilterString = "Executable files (*.exe)|*.exe|" + + "Batch files (*.bat)|*.bat|" + + "Command files (*.cmd)|*.cmd|" + + "PowerShell files (*.ps1)|*.ps1|" + + "All files (*.*)|*.*"; + + public static readonly string SolutionFilterString = "Solutions (*.sln)|*.sln|" + + "C# Projects (*.csproj)|*.csproj|" + + "F# Projects (*.fsproj)|*.fsproj|" + + "TS/JS Projects (*.esproj, *.tsproj)|*.esproj|" + + "Cxx Projects (*.vcxproj)|*.vcxproj|" + + "All files (*.*)|*.*"; + + + } +} diff --git a/VSLauncherX/Controls/TextBoxEx.Designer.cs b/VSLauncherX/Controls/TextBoxEx.Designer.cs new file mode 100644 index 0000000..adfbba9 --- /dev/null +++ b/VSLauncherX/Controls/TextBoxEx.Designer.cs @@ -0,0 +1,36 @@ +namespace VSLauncher.Controls +{ + partial class TextBoxEx + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + } + + #endregion + } +} diff --git a/VSLauncherX/Controls/TextBoxEx.cs b/VSLauncherX/Controls/TextBoxEx.cs new file mode 100644 index 0000000..7f36cf1 --- /dev/null +++ b/VSLauncherX/Controls/TextBoxEx.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace VSLauncher.Controls +{ + /// + /// The text box ex. + /// + public partial class TextBoxEx : TextBox + { + private readonly Label lblXButton; + + /// + /// Gets or sets a value indicating whether show clear button. + /// + public bool ShowClearButton { get; set; } = true; + + /// + /// Initializes a new instance of the class. + /// + public TextBoxEx() + { + InitializeComponent(); + + this.Resize += PositionX; + this.TextChanged += ShowHideX; + + this.lblXButton = new Label() + { + Location = new Point(100, 0), + AutoSize = true, + Text = " x ", + ForeColor = Color.Gray, + Visible = false, + Font = new Font("Tahoma", this.Font.Size * 0.9F), + // BorderStyle = BorderStyle.FixedSingle, + Cursor = Cursors.Arrow + }; + + Controls.Add(lblXButton); + this.lblXButton.Click += (ss, ee) => { ((Label)ss).Visible = false; this.Text = string.Empty; }; + this.lblXButton.BringToFront(); + } + + private void ShowHideX(object sender, EventArgs e) => this.lblXButton.Visible = this.ShowClearButton && !string.IsNullOrEmpty(Text); + + private void PositionX(object sender, EventArgs e) => this.lblXButton.Location = new Point(this.Width - this.lblXButton.Width - 3, ((Height - this.lblXButton.Height) / 2) - 3); + } +} diff --git a/VSLauncherX/Controls/TextBoxEx.resx b/VSLauncherX/Controls/TextBoxEx.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/VSLauncherX/Controls/TextBoxEx.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/VSLauncherX/Forms/dlgBeforeAfter.cs b/VSLauncherX/Forms/dlgBeforeAfter.cs index 8b0ab1b..8990a9d 100644 --- a/VSLauncherX/Forms/dlgBeforeAfter.cs +++ b/VSLauncherX/Forms/dlgBeforeAfter.cs @@ -1,5 +1,7 @@ using VSLauncher.DataModel; +using VSLXshared.Helpers; + namespace VSLauncher { /// @@ -7,12 +9,6 @@ namespace VSLauncher /// public partial class dlgBeforeAfter : Form { - private readonly string executablesFilterString = "Executable files (*.exe)|*.exe|" + - "Batch files (*.bat)|*.bat|" + - "Command files (*.cmd)|*.cmd|" + - "PowerShell files (*.ps1)|*.ps1|" + - "All files (*.*)|*.*"; - /// /// Initializes a new instance of the class. /// @@ -56,7 +52,7 @@ private void btnSelectAfter_Click(object sender, EventArgs e) { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { - openFileDialog.Filter = executablesFilterString; + openFileDialog.Filter = FileHelper.ExecutablesFilterString; openFileDialog.FilterIndex = 1; openFileDialog.RestoreDirectory = true; @@ -78,7 +74,7 @@ private void btnSelectBefore_Click(object sender, EventArgs e) { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { - openFileDialog.Filter = executablesFilterString; + openFileDialog.Filter = FileHelper.ExecutablesFilterString; openFileDialog.FilterIndex = 1; openFileDialog.RestoreDirectory = true; diff --git a/VSLauncherX/Forms/dlgExecuteVisualStudio.cs b/VSLauncherX/Forms/dlgExecuteVisualStudio.cs index c890be2..273c424 100644 --- a/VSLauncherX/Forms/dlgExecuteVisualStudio.cs +++ b/VSLauncherX/Forms/dlgExecuteVisualStudio.cs @@ -12,6 +12,8 @@ using VSLauncher.DataModel; using VSLauncher.Forms; +using VSLXshared.Helpers; + namespace VSLauncher { /// @@ -19,13 +21,6 @@ namespace VSLauncher /// public partial class dlgExecuteVisualStudio : Form { - private readonly string solutionFilterString = "Solutions (*.sln)|*.sln|" + - "C# Projects (*.csproj)|*.csproj" + - "F# Projects (*.fsproj)|*.fsproj" + - "TS/JS Projects (*.esproj, *.tsproj)|*.esproj" + - "Cxx Projects (*.vcxproj)|*.vcxproj" + - "All files (*.*)|*.*"; - private VsItem? currentItem; /// @@ -165,7 +160,7 @@ private void btnSelectFolder_Click(object sender, EventArgs e) // let the user select a folder through the system dialog using (OpenFileDialog openFileDialog = new()) { - openFileDialog.Filter = solutionFilterString; + openFileDialog.Filter = FileHelper.SolutionFilterString; openFileDialog.FilterIndex = 1; openFileDialog.CheckFileExists = true; openFileDialog.Multiselect = false; diff --git a/VSLauncherX/Helpers/ColumnHelper.cs b/VSLauncherX/Helpers/ColumnHelper.cs index ddb1efa..674442e 100644 --- a/VSLauncherX/Helpers/ColumnHelper.cs +++ b/VSLauncherX/Helpers/ColumnHelper.cs @@ -229,7 +229,29 @@ internal static CheckState GetCheckState(object rowObject) break; case ItemTypeEnum.Folder: - desc = $"Contains {f.ContainedSolutionsCount()} solution{((f.ContainedSolutionsCount() != 1) ? 's' : "")}"; + { + var so = f.ContainedSolutionsCount(); + var pr = f.ContainedProjectsCount(); + desc = "Contains "; + if(pr > 0) + { + desc += $"{pr} project{((pr != 1) ? 's' : "")}"; + } + + if(so > 0) + { + if (pr > 0) + { + desc += " and "; + } + desc += $"{so} solution{((so != 1) ? 's' : "")}"; + } + + if(pr == 0 && so == 0) + { + desc += "nothing yet"; + } + } break; default: diff --git a/VSLauncherX/MainDialog.Designer.cs b/VSLauncherX/MainDialog.Designer.cs index 27de72e..42dfc20 100644 --- a/VSLauncherX/MainDialog.Designer.cs +++ b/VSLauncherX/MainDialog.Designer.cs @@ -1,5 +1,7 @@ using BrightIdeasSoftware; +using VSLauncher.Controls; + namespace VSLauncher { partial class MainDialog @@ -38,7 +40,8 @@ private void InitializeComponent() System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(MainDialog)); TableLayoutPanel leftSubPanel; Label titleLabel; - txtFilter = new TextBox(); + Label label2; + txtFilter = new TextBoxEx(); button1 = new Button(); button2 = new Button(); button3 = new Button(); @@ -53,7 +56,6 @@ private void InitializeComponent() imageList3 = new ImageList(this.components); mainPanel = new TableLayoutPanel(); flowLayoutPanel1 = new FlowLayoutPanel(); - label2 = new Label(); this.selectVisualStudioVersion = new VisualStudioCombobox(); btnMainStartVisualStudio1 = new Button(); btnMainStartVisualStudio2 = new Button(); @@ -72,7 +74,7 @@ private void InitializeComponent() runToolStripMenuItem = new ToolStripMenuItem(); runAsAdminToolStripMenuItem = new ToolStripMenuItem(); renameToolStripMenuItem = new ToolStripMenuItem(); - deleteToolStripMenuItem = new ToolStripMenuItem(); + removeToolStripMenuItem = new ToolStripMenuItem(); settingsToolStripMenuItem = new ToolStripMenuItem(); statusStrip1 = new StatusStrip(); mainStatusLabel = new ToolStripStatusLabel(); @@ -82,6 +84,7 @@ private void InitializeComponent() label3 = new Label(); leftSubPanel = new TableLayoutPanel(); titleLabel = new Label(); + label2 = new Label(); flowLayoutPanel2.SuspendLayout(); leftSubPanel.SuspendLayout(); ((System.ComponentModel.ISupportInitialize)olvFiles).BeginInit(); @@ -136,9 +139,11 @@ private void InitializeComponent() txtFilter.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point); txtFilter.Location = new Point(67, 3); txtFilter.Name = "txtFilter"; + txtFilter.ShowClearButton = true; txtFilter.Size = new Size(439, 29); txtFilter.TabIndex = 1; txtFilter.TextChanged += txtFilter_TextChanged; + txtFilter.KeyPress += txtFilter_KeyPress; // // button1 // @@ -262,6 +267,7 @@ private void InitializeComponent() olvFiles.Name = "olvFiles"; olvFiles.SelectColumnsOnRightClickBehaviour = ObjectListView.ColumnSelectBehaviour.Submenu; olvFiles.ShowCommandMenuOnRightClick = true; + olvFiles.ShowFilterMenuOnRightClick = false; olvFiles.ShowGroups = false; olvFiles.ShowItemToolTips = true; olvFiles.Size = new Size(695, 422); @@ -270,18 +276,18 @@ private void InitializeComponent() olvFiles.UseCompatibleStateImageBehavior = false; olvFiles.UseFilterIndicator = true; olvFiles.UseFiltering = true; + olvFiles.UseWaitCursorWhenExpanding = false; olvFiles.View = View.Details; olvFiles.VirtualMode = true; - olvFiles.CellEditFinished += olvFiles_CellEditFinished; - olvFiles.CellClick += olvFiles_CellClick; olvFiles.CellRightClick += olvFiles_CellRightClick; olvFiles.CellToolTipShowing += olvFiles_CellToolTipShowing; olvFiles.Dropped += olvFiles_Dropped; olvFiles.HotItemChanged += olvFiles_HotItemChanged; olvFiles.AfterLabelEdit += olvFiles_AfterLabelEdit; - olvFiles.ItemActivate += olvFiles_ItemActivate; olvFiles.SelectedIndexChanged += olvFiles_SelectedIndexChanged; olvFiles.DoubleClick += olvFiles_DoubleClick; + olvFiles.KeyDown += olvFiles_KeyDown; + olvFiles.KeyPress += olvFiles_KeyPress; // // olvColumnFilename // @@ -362,6 +368,16 @@ private void InitializeComponent() imageList3.Images.SetKeyName(26, "RunAfter"); imageList3.Images.SetKeyName(27, "RunBefore"); // + // label2 + // + label2.AutoSize = true; + label2.Location = new Point(3, 25); + label2.Margin = new Padding(3, 25, 3, 0); + label2.Name = "label2"; + label2.Size = new Size(119, 15); + label2.TabIndex = 6; + label2.Text = "Visual Studio version:"; + // // mainPanel // mainPanel.Anchor = AnchorStyles.Top | AnchorStyles.Bottom | AnchorStyles.Left | AnchorStyles.Right; @@ -396,16 +412,6 @@ private void InitializeComponent() flowLayoutPanel1.Size = new Size(234, 497); flowLayoutPanel1.TabIndex = 1; // - // label2 - // - label2.AutoSize = true; - label2.Location = new Point(3, 25); - label2.Margin = new Padding(3, 25, 3, 0); - label2.Name = "label2"; - label2.Size = new Size(119, 15); - label2.TabIndex = 6; - label2.Text = "Visual Studio version:"; - // // selectVisualStudioVersion // this.selectVisualStudioVersion.DrawMode = DrawMode.OwnerDrawFixed; @@ -544,7 +550,7 @@ private void InitializeComponent() // // ctxMenu // - ctxMenu.Items.AddRange(new ToolStripItem[] { addToolStripMenuItem, toolStripMenuItem3, runToolStripMenuItem, runAsAdminToolStripMenuItem, renameToolStripMenuItem, toolStripMenuItem1, deleteToolStripMenuItem, toolStripMenuItem2, settingsToolStripMenuItem }); + ctxMenu.Items.AddRange(new ToolStripItem[] { addToolStripMenuItem, toolStripMenuItem3, runToolStripMenuItem, runAsAdminToolStripMenuItem, renameToolStripMenuItem, toolStripMenuItem1, removeToolStripMenuItem, toolStripMenuItem2, settingsToolStripMenuItem }); ctxMenu.Name = "ctxMenu"; ctxMenu.Size = new Size(149, 154); ctxMenu.Opening += ctxMenu_Opening; @@ -603,12 +609,12 @@ private void InitializeComponent() renameToolStripMenuItem.Text = "Rename..."; renameToolStripMenuItem.Click += renameToolStripMenuItem_Click; // - // deleteToolStripMenuItem + // removeToolStripMenuItem // - deleteToolStripMenuItem.Name = "deleteToolStripMenuItem"; - deleteToolStripMenuItem.Size = new Size(148, 22); - deleteToolStripMenuItem.Text = "Delete..."; - deleteToolStripMenuItem.Click += deleteToolStripMenuItem_Click; + removeToolStripMenuItem.Name = "removeToolStripMenuItem"; + removeToolStripMenuItem.Size = new Size(148, 22); + removeToolStripMenuItem.Text = "Remove..."; + removeToolStripMenuItem.Click += removeToolStripMenuItem_Click; // // settingsToolStripMenuItem // @@ -685,10 +691,8 @@ private void InitializeComponent() private ToolStripMenuItem settingsToolStripMenuItem; private StatusStrip statusStrip1; private ToolStripStatusLabel mainStatusLabel; - private ToolStripMenuItem deleteToolStripMenuItem; - private Label label2; - private Label label3; - private TextBox txtFilter; + private ToolStripMenuItem removeToolStripMenuItem; + private TextBoxEx txtFilter; private Button button1; private Button button2; private Button button3; diff --git a/VSLauncherX/MainDialog.cs b/VSLauncherX/MainDialog.cs index 04766d2..d128bb8 100644 --- a/VSLauncherX/MainDialog.cs +++ b/VSLauncherX/MainDialog.cs @@ -8,6 +8,8 @@ using VSLauncher.Forms; using VSLauncher.Helpers; +using VSLXshared.Helpers; + namespace VSLauncher { /// @@ -18,46 +20,15 @@ public partial class MainDialog : Form /// /// used to indicate that some internal update is going on /// - private bool bInUpdate; + private readonly bool bInUpdate; + private readonly VisualStudioInstanceManager visualStudioInstances = new VisualStudioInstanceManager(); private DescribedTaskRenderer? itemRenderer; private VsFolder solutionGroups = new VsFolder(); - private VisualStudioInstanceManager visualStudioInstances = new VisualStudioInstanceManager(); - - /// - /// Initializes a new instance of the class. - /// - public MainDialog() - { - LoadSolutionData(); - - this.solutionGroups.Items.OnChanged += SolutionData_OnChanged; - - InitializeComponent(); - InitializeListview(this.solutionGroups.Items); - SetupDragAndDrop(); - - // BuildTestData(); - - this.bInUpdate = true; - - if (!string.IsNullOrEmpty(Properties.Settings.Default.SelectedVSversion)) - { - var v = this.selectVisualStudioVersion.Versions.Where(v => v.Identifier == Properties.Settings.Default.SelectedVSversion).Single(); - this.selectVisualStudioVersion.SelectedItem = v; - } - else - { - this.selectVisualStudioVersion.SelectedIndex = 0; - } - - this.bInUpdate = false; - - UpdateList(); - } + private DataObject? lastDroppedData; /// - /// Is the control key pressed. + /// Returns if the control key is pressed. /// /// A bool. private static bool IsControlPressed() @@ -65,37 +36,6 @@ private static bool IsControlPressed() return (Control.ModifierKeys & Keys.Control) == Keys.Control; } - /// - /// Builds the test data. - /// - private void BuildTestData() - { - var sg1 = new VsFolder("Main", ""); - sg1.RunBefore = new VsItem("Explorer", "explorer.exe", null); - sg1.Items.Add(new VsSolution("ObjectListView", @"C:\dev\Repos\ObjectListViewDemo\ObjectListView2012.sln")); - - var sg2 = new VsFolder("Some Group", ""); - sg2.RunAsAdmin = true; - - sg2.Items.Add(new VsSolution("Solution 1", @"C:\TestSolution1.sln")); - sg2.Items.Add(new VsSolution("Solution 2", @"C:\Solution2\TestSolution2.sln")); - sg2.Items.Add(new VsSolution("Solution 3", @"C:\Solution3\TestSolution3.sln")); - - var sg3 = new VsFolder ("small sub group", ""); - sg3.RunBefore = new VsItem("Explorer", "explorer.exe", null); - sg3.RunAsAdmin = true; - sg3.RunAfter = new VsItem("Explorer", "explorer.exe", null); - - sg3.Items.Add(new VsSolution("Solution 1", @"C:\TestSolution1.sln")); - sg3.Items.Add(new VsSolution("Solution 2", @"C:\Solution2\TestSolution2.sln")); - - sg2.Items.Add(sg3); - - solutionGroups.Items.Add(sg1); - solutionGroups.Items.Add(sg2); - UpdateList(); - } - /// /// Creates the described task renderer. /// @@ -103,70 +43,41 @@ private void BuildTestData() private DescribedTaskRenderer CreateDescribedRenderer() { // Let's create an appropriately configured renderer. - DescribedTaskRenderer renderer = new DescribedTaskRenderer(); - - // Give the renderer its own collection of images. - // If this isn't set, the renderer will use the SmallImageList from the ObjectListView. - // (this is standard Renderer behaviour, not specific to DescribedTaskRenderer). - renderer.ImageList = this.imageListMainIcons; - - // Tell the renderer which property holds the text to be used as a description - renderer.DescriptionGetter = ColumnHelper.GetDescription; - - // Change the formatting slightly - renderer.TitleFont = new Font("Verdana", 11, FontStyle.Bold); - renderer.DescriptionFont = new Font("Verdana", 8); - renderer.ImageTextSpace = 8; - renderer.TitleDescriptionSpace = 1; - - // Use older Gdi renderering, since most people think the text looks clearer - renderer.UseGdiTextRendering = true; - renderer.Aspect = "Name"; - - return renderer; - } - - /// - /// deletes the tool strip menu item_ click. - /// - /// The sender. - /// The e. - private void deleteToolStripMenuItem_Click(object sender, EventArgs e) - { - var item = olvFiles.SelectedItem.RowObject; - - // only ask the user if the selected item is not an empty folder - if (item is not VsFolder sg || sg.Items.Count != 0) - { - // ask user if he wants to delete this item really - if (MessageBox.Show($"Are you sure you want to delete '{((VsItem)item).Name}'", "Delete item", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) - { - return; - } - } - - VsFolder? owner = this.solutionGroups.FindParent(item); - if (owner != null) + DescribedTaskRenderer renderer = new DescribedTaskRenderer { - owner.Items.Remove((VsItem)item); - } + // Give the renderer its own collection of images. If this isn't set, the renderer will use the + // SmallImageList from the ObjectListView. (this is standard Renderer behaviour, not specific to DescribedTaskRenderer). + ImageList = this.imageListMainIcons, + + // Tell the renderer which property holds the text to be used as a description + DescriptionGetter = ColumnHelper.GetDescription, + + // Change the formatting slightly + TitleFont = new Font("Verdana", 11, FontStyle.Bold), + DescriptionFont = new Font("Verdana", 8), + ImageTextSpace = 8, + TitleDescriptionSpace = 1, + + // Use older Gdi renderering, since most people think the text looks clearer + UseGdiTextRendering = true, + Aspect = "Name" + }; - this.SolutionData_OnChanged(true); + return renderer; } /// /// Initializes the listview. /// /// The list. - private void InitializeListview(VsItemList list) + private void InitializeListview() { this.olvFiles.FullRowSelect = true; this.olvFiles.RowHeight = 56; this.olvFiles.UseHotItem = false; this.olvFiles.OwnerDraw = true; - // Add a more interesting focus for editing operations - // this.olvFiles.AddDecoration(new EditingCellBorderDecoration(true)); + // Add a more interesting focus for editing operations this.olvFiles.AddDecoration(new EditingCellBorderDecoration(true)); this.olvFiles.TreeColumnRenderer.IsShowLines = true; this.olvFiles.TreeColumnRenderer.UseTriangles = true; this.olvFiles.TreeColumnRenderer.CornerRoundness = 0; @@ -175,14 +86,12 @@ private void InitializeListview(VsItemList list) this.olvFiles.UseFilterIndicator = true; - // The following line makes getting aspect about 10x faster. Since getting the aspect is - // the slowest part of building the ListView, it is worthwhile BUT NOT NECESSARY to do. + // The following line makes getting aspect about 10x faster. Since getting the aspect is the slowest part of + // building the ListView, it is worthwhile BUT NOT NECESSARY to do. TypedObjectListView tlist = new TypedObjectListView(this.olvFiles); tlist.GenerateAspectGetters(); - // // setup the files list - // this.olvFiles.CanExpandGetter = delegate (object x) { return x is VsFolder; @@ -206,12 +115,7 @@ private void InitializeListview(VsItemList list) this.olvFiles.ChildrenGetter = delegate (object x) { - if (x is VsFolder sg) - { - return sg.Items; - } - - return null; + return x is VsFolder sg ? sg.Items : (System.Collections.IEnumerable?)null; }; this.itemRenderer = CreateDescribedRenderer(); @@ -221,30 +125,21 @@ private void InitializeListview(VsItemList list) this.olvColumnFilename.ImageGetter = ColumnHelper.GetImageNameForFile; this.olvColumnFilename.CellPadding = new Rectangle(4, 2, 4, 2); - // // - // // setup the Path column - // // - // this.olvColumnPath.AspectGetter = ColumnHelper.GetAspectForPath; - // this.olvColumnPath.Hideable = false; - // this.olvColumnPath.UseFiltering = false; + // // // setup the Path column // this.olvColumnPath.AspectGetter = ColumnHelper.GetAspectForPath; + // this.olvColumnPath.Hideable = false; this.olvColumnPath.UseFiltering = false; - // // setup the Date column - // this.olvColumnDate.AspectGetter = ColumnHelper.GetAspectForDate; - // // setup the Options column - // this.olvColumnOptions.AspectGetter = ColumnHelper.GetAspectForOptions; this.olvColumnOptions.ToolTipText = "*******"; - // Show the attributes for this object - // A FlagRenderer masks off various values and draws zero or more images based - // on the presence of individual bits. + // Show the attributes for this object A FlagRenderer masks off various values and draws zero or more images + // based on the presence of individual bits. FlagRenderer attributesRenderer = new FlagRenderer { - ImageList = imageList3 + ImageList = this.imageList3 }; attributesRenderer.Add(OptionsEnum.RunBeforeOn, "RunBefore"); attributesRenderer.Add(OptionsEnum.RunBeforeOff, "None"); @@ -258,48 +153,40 @@ private void InitializeListview(VsItemList list) // Tell the filtering subsystem that the attributes column is a collection of flags this.olvColumnOptions.ClusteringStrategy = new FlagClusteringStrategy(typeof(OptionsEnum)); - // this.olvFiles.SetObjects(list); + // this.olvFiles.SetObjects(list); } /// - /// Loads the solution data. + /// Initializes a new instance of the class. /// - private void LoadSolutionData() + public MainDialog() { - // load this.solutionGroups data from a JSON file in the users data folder - string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VSLauncher", "VSLauncher.json"); + LoadSolutionData(); - if (File.Exists(fileName)) - { - string json = File.ReadAllText(fileName); - JsonSerializerSettings settings = new JsonSerializerSettings() - { - TypeNameHandling = TypeNameHandling.All - }; + this.solutionGroups.Items.OnChanged += SolutionData_OnChanged; - VsFolder? data = null; - try - { - data = JsonConvert.DeserializeObject(json, settings); - } - catch (System.Exception ex) - { - // probably wrong format - } + InitializeComponent(); + InitializeListview(); + SetupDragAndDrop(); - if (data is null) - { - // alert the user that the datafile was unreadable - MessageBox.Show($"The datafile was unreadable. Please check the file in '{fileName}' and try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - else - { - data.Refresh(); - this.solutionGroups = data; - } + // BuildTestData(); + + this.bInUpdate = true; + + if (!string.IsNullOrEmpty(Properties.Settings.Default.SelectedVSversion)) + { + VisualStudioInstance v = this.selectVisualStudioVersion.Versions.Where(v => v.Identifier == Properties.Settings.Default.SelectedVSversion).Single(); + this.selectVisualStudioVersion.SelectedItem = v; } - } + else + { + this.selectVisualStudioVersion.SelectedIndex = 0; + } + + this.bInUpdate = false; + UpdateList(); + } /// /// Handles the load event. /// @@ -315,93 +202,111 @@ private void MainDialog_Load(object sender, EventArgs e) this.Size = Properties.Settings.Default.AppSize; } - txtFilter.Focus(); + _ = this.txtFilter.Focus(); } /// - /// Handles adding a folder. + /// Handles the form closing, saves state /// /// The sender. /// The e. - private void mainFolderAdd_Click(object sender, EventArgs e) + private void MainDialog_FormClosing(object sender, FormClosingEventArgs e) { - var dlg = new dlgAddFolder(); - if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) + Properties.Settings.Default.AppWindow = this.WindowState; + if (this.WindowState == FormWindowState.Normal) { - var r = this.olvFiles.SelectedItem; - if (r == null) + // save location and size if the state is normal + Properties.Settings.Default.AppLocation = this.Location; + Properties.Settings.Default.AppSize = this.Size; + } + else + { + // save the RestoreBounds if the form is minimized or maximized! + Properties.Settings.Default.AppLocation = this.RestoreBounds.Location; + Properties.Settings.Default.AppSize = this.RestoreBounds.Size; + } + + Properties.Settings.Default.AppState = "saved"; + + // don't forget to save the settings + Properties.Settings.Default.Save(); + } + + /// + /// Merges new items into the selected list item + /// + /// The selected item + /// The source to add + private void MergeNewItems(OLVListItem r, VsItemList source) + { + if (r == null) + { + // nothing selected, add to the end + this.solutionGroups.Items.AddRange(source); + } + else + { + if (r.RowObject is VsFolder sg) { - solutionGroups.Items.Add(dlg.Solution); + // add below selected item + sg.Items.AddRange(source); } else { - if (r.RowObject is VsFolder sg) - { - sg.Items.Add(dlg.Solution); - } - else - { - var p = this.solutionGroups.FindParent(r.RowObject); + // get parent of this item + _ = this.solutionGroups.FindParent(r.RowObject as VsItem); - p?.Items.Insert(p.Items.IndexOf((VsItem)r.RowObject), dlg.Solution); - } + // add at the end + this.solutionGroups.Items.AddRange(source); } - - this.SolutionData_OnChanged(true); } } /// - /// Handles importing from a folder. + /// Merges a new item into the selected list item /// - /// The sender. - /// The e. - private void mainImportFolder_Click(object sender, EventArgs e) + /// The selected list item + /// The source to add + private void MergeNewItem(OLVListItem r, VsItem source) { - var dlg = new dlgImportFolder(); - if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) + if (r == null) { - var r = this.olvFiles.SelectedItem; - if (r == null) + // nothing selected, add to the end + this.solutionGroups.Items.Add(source); + } + else + { + if (r.RowObject is VsFolder sg) { - // nothing selected, add to the end - this.solutionGroups.Items.Add(dlg.Solution.Items.First()); + // add below selected item + sg.Items.Add(source); } else { - if (r.RowObject is VsFolder sg) - { - // add below selected item - sg.Items.AddRange(dlg.Solution.Items); - } - else - { - // get parent of this item - var vsi = this.solutionGroups.FindParent( r.RowObject as VsItem); + // get parent of this item + VsFolder? vsi = this.solutionGroups.FindParent(r.RowObject as VsItem); - // add at the end - this.solutionGroups.Items.AddRange(dlg.Solution.Items); - } + // add at the end + vsi?.Items.Add(source); } - - this.SolutionData_OnChanged(true); } } +#region Main button handling /// - /// Handles importing from VS button. + /// Handles adding a folder. /// /// The sender. /// The e. - private void mainImportVS_Click(object sender, EventArgs e) + private void mainFolderAdd_Click(object sender, EventArgs e) { - var dlg = new dlgImportVisualStudio(); + dlgAddFolder dlg = new dlgAddFolder(); if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - var r = this.olvFiles.SelectedItem; + OLVListItem r = this.olvFiles.SelectedItem; if (r == null) { - solutionGroups = dlg.Solution; + this.solutionGroups.Items.Add(dlg.Solution); } else { @@ -411,127 +316,141 @@ private void mainImportVS_Click(object sender, EventArgs e) } else { + VsFolder? p = this.solutionGroups.FindParent(r.RowObject); + + p?.Items.Insert(p.Items.IndexOf((VsItem)r.RowObject), dlg.Solution); } } - this.SolutionData_OnChanged(true); + _ = SolutionData_OnChanged(true); } } /// - /// Handles the refresh button. + /// Handles importing from a folder. /// /// The sender. /// The e. - private void mainRefresh_Click(object sender, EventArgs e) + private void mainImportFolder_Click(object sender, EventArgs e) { - this.solutionGroups.Items.OnChanged -= SolutionData_OnChanged; - this.solutionGroups.Items.Clear(); - LoadSolutionData(); - this.solutionGroups.Items.OnChanged += SolutionData_OnChanged; + dlgImportFolder dlg = new dlgImportFolder(); + if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) + { + OLVListItem r = this.olvFiles.SelectedItem; + VsItemList source = dlg.Solution.Items; - UpdateList(); + MergeNewItems(r, source); + + _ = SolutionData_OnChanged(true); + } } /// - /// mains the settings_ click. + /// Handles importing from VS button. /// /// The sender. /// The e. - private void mainSettings_Click(object sender, EventArgs e) + private void mainImportVS_Click(object sender, EventArgs e) { - var dlg = new dlgSettings(); - if (dlg.ShowDialog() == DialogResult.OK) + dlgImportVisualStudio dlg = new dlgImportVisualStudio(); + if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK) { - this.SolutionData_OnChanged(true); + OLVListItem r = this.olvFiles.SelectedItem; + VsItemList source = dlg.Solution.Items; + + MergeNewItems(r, source); + + _ = SolutionData_OnChanged(true); } } /// - /// olvs the files_ after label edit. + /// Handles the refresh button. /// /// The sender. /// The e. - private void olvFiles_AfterLabelEdit(object sender, LabelEditEventArgs e) + private void mainRefresh_Click(object sender, EventArgs e) { + this.solutionGroups.Items.OnChanged -= SolutionData_OnChanged; + this.solutionGroups.Items.Clear(); + LoadSolutionData(); + this.solutionGroups.Items.OnChanged += SolutionData_OnChanged; + + UpdateList(); } /// - /// lists the view files_ cell click. + /// mains the settings_ click. /// /// The sender. /// The e. - private void olvFiles_CellClick(object sender, CellClickEventArgs e) + private void mainSettings_Click(object sender, EventArgs e) { - System.Diagnostics.Trace.WriteLine(String.Format("clicked ({0}, {1}). model {2}. click count: {3}", e.RowIndex, e.ColumnIndex, e.Model, e.ClickCount)); + dlgSettings dlg = new dlgSettings(); + + if (dlg.ShowDialog() == DialogResult.OK) + { + _ = SolutionData_OnChanged(true); + } } +#endregion + +#region ListView event handling /// - /// olvs the files_ cell edit finished. + /// Handles ending label editing on the list view. /// /// The sender. /// The e. - private void olvFiles_CellEditFinished(object sender, CellEditEventArgs e) + private void olvFiles_AfterLabelEdit(object sender, LabelEditEventArgs e) { + // Method intentionally left empty. } /// - /// lists the view files_ cell right click. + /// Handles right click on items /// /// The sender. /// The e. private void olvFiles_CellRightClick(object sender, CellRightClickEventArgs e) { - // System.Diagnostics.Trace.WriteLine(String.Format("right clicked {0}, {1}). model {2}", e.RowIndex, e.ColumnIndex, e.Model)); + // System.Diagnostics.Trace.WriteLine(String.Format("right clicked {0}, {1}). model {2}", e.RowIndex, + // e.ColumnIndex, e.Model)); e.MenuStrip = this.ctxMenu; e.MenuStrip.Show(); } /// - /// lists the view files_ cell tool tip showing. + /// Handles showing the tooltip contents. /// /// The sender. /// The e. private void olvFiles_CellToolTipShowing(object sender, ToolTipShowingEventArgs e) { - - if (e.ColumnIndex == olvColumnOptions.DisplayIndex) + if (e.ColumnIndex == this.olvColumnOptions.DisplayIndex) { VsOptions itemOptions = (VsOptions)e.Model; - e.Text = ((VsOptions)itemOptions).ToString(); - } - else if (e.Model is VsFolder) - { - e.Text = e.Item.Text; + e.Text = itemOptions.ToString(); } else { - e.Text = e.SubItem.Text; + e.Text = e.Model is VsFolder ? e.Item.Text : e.SubItem.Text; } } /// - /// olvs the files_ double click. + /// Handles double click on items /// /// The sender. /// The e. private void olvFiles_DoubleClick(object sender, EventArgs e) { - this.runToolStripMenuItem_Click(sender, e); - } - - /// - /// olvs the files_ dropped. - /// - /// The sender. - /// The e. - private void olvFiles_Dropped(object sender, OlvDropEventArgs e) - { + runToolStripMenuItem_Click(sender, e); } /// - /// olv_S the hot item changed. + /// Clears the status text when changing items. /// /// The sender. /// The e. @@ -540,43 +459,161 @@ private void olvFiles_HotItemChanged(object sender, HotItemChangedEventArgs e) if (sender == null) { this.toolStripStatusLabel3.Text = ""; - return; } } - /// - /// lists the view files_ item activate. + /// Handles selected index changed in the list /// /// The sender. /// The e. - private void olvFiles_ItemActivate(object sender, EventArgs e) + private void olvFiles_SelectedIndexChanged(object? sender, EventArgs e) { - Object rowObject = this.olvFiles.SelectedObject; - if (rowObject == null) - return; - - if (rowObject is DirectoryInfo) + // update status bar text with info on selected item + if (this.olvFiles.SelectedItem != null) { + object item = this.olvFiles.SelectedItem.RowObject; + if (item is VsSolution s) + { + this.mainStatusLabel.Text = $"Visual Studio Solution: {s.Projects?.Count} Projects, {s.TypeAsName()}"; + } + else if (item is VsProject p) + { + this.mainStatusLabel.Text = $"Visual Studio Project: {p.TypeAsName()}, .NET {p.FrameworkVersion}"; + } + else if (item is VsFolder sg) + { + this.mainStatusLabel.Text = $"Contains {sg.ContainedSolutionsCount()} solution{((sg.ContainedSolutionsCount() != 1) ? 's' : "")}"; + } } else { - // ShellUtilities.Execute(((FileInfo)rowObject).FullName); + this.mainStatusLabel.Text = string.Empty; } } /// - /// olvs the files_ model can drop handler. + /// olvs the files_ key press. /// /// The sender. /// The e. - private void olvFiles_ModelCanDropHandler(object sender, ModelDropEventArgs e) + private void olvFiles_KeyPress(object sender, KeyPressEventArgs e) { - e.Effect = DragDropEffects.None; - if (e.TargetModel == null) + if (e.KeyChar == (char)Keys.Enter) { - return; - } - + if ((Control.ModifierKeys & Keys.Alt) == Keys.Alt) + { + settingsToolStripMenuItem_Click(sender, e); + } + else + { + runToolStripMenuItem_Click(sender, e); + } + + e.Handled = true; + } + else if (e.KeyChar == (char)Keys.Delete) + { + removeToolStripMenuItem_Click(sender, e); + e.Handled = true; + } + } + + /// + /// olvs the files_ key down. + /// + /// The sender. + /// The e. + private void olvFiles_KeyDown(object sender, KeyEventArgs e) + { + if (e.KeyValue == (char)Keys.Delete) + { + removeToolStripMenuItem_Click(sender, e); + e.Handled = true; + } + } + +#endregion + + #region Drag and Drop handling + + /// + /// Handles files dropped from external source. + /// + /// The sender. + /// The e. + private void olvFiles_Dropped(object? sender, OlvDropEventArgs e) + { + if (this.lastDroppedData is null) + { + this.lastDroppedData = (DataObject)e.DataObject; + string[] files = (string[])this.lastDroppedData.GetData(DataFormats.FileDrop); + + foreach (string file in files) + { + VsItem item = ImportHelper.GetItemFromExtension(Path.GetFileNameWithoutExtension(file), file); + if (item != null && item.ItemType != ItemTypeEnum.Other) + { + MergeNewItem(e.DropTargetItem, item); + } + } + + UpdateList(); + + e.Handled = true; + } + } + + /// + /// Handles the files can drop event from external sources + /// + /// The sender. + /// The e. + private void olvFiles_CanDrop(object? sender, OlvDropEventArgs e) + { + if (e.DataObject is not null) + { + if (((DataObject)e.DataObject).GetDataPresent(DataFormats.FileDrop)) + { + e.Effect = DragDropEffects.Copy; + e.Handled = true; + this.lastDroppedData = null; + } + } + } + + /// + /// Setups the drag and drop. + /// + private void SetupDragAndDrop() + { + // Setup the tree so that it can drop and drop. Drag and drop support You can set up drag and drop + // explicitly (like this) or, in the IDE, you can set IsSimpleDropSource and IsSimpleDragSource and respond + // to CanDrop and Dropped events + + this.olvFiles.DragSource = new SimpleDragSource(); + this.olvFiles.IsSimpleDragSource = true; + this.olvFiles.IsSimpleDropSink = true; + + this.olvFiles.ModelCanDrop += olvFiles_ModelCanDropHandler; + this.olvFiles.ModelDropped += olvFiles_ModelDroppedHandler; + + this.olvFiles.CanDrop += olvFiles_CanDrop; + this.olvFiles.Dropped += olvFiles_Dropped; + } + + /// + /// Handles requests if a model can drop on another model. + /// + /// The sender. + /// The e. + private void olvFiles_ModelCanDropHandler(object sender, ModelDropEventArgs e) + { + e.Effect = DragDropEffects.None; + if (e.TargetModel == null) + { + return; + } + if (e.SourceModels[0] == e.TargetModel) { return; @@ -584,7 +621,7 @@ private void olvFiles_ModelCanDropHandler(object sender, ModelDropEventArgs e) if (e.TargetModel is VsFolder) { - var obj = e.SourceModels[0]; + object? obj = e.SourceModels[0]; if (obj != null) { @@ -608,11 +645,11 @@ private void olvFiles_ModelCanDropHandler(object sender, ModelDropEventArgs e) } /// - /// olvs the files_ model dropped handler. + /// Handles model dropped on another model. /// /// The sender. /// The e. - private void olvFiles_ModelDroppedHandler(object sender, ModelDropEventArgs e) + private void olvFiles_ModelDroppedHandler(object? sender, ModelDropEventArgs e) { if (e.SourceModels.Count == 1) { @@ -631,7 +668,7 @@ private void olvFiles_ModelDroppedHandler(object sender, ModelDropEventArgs e) if (e.Effect == System.Windows.Forms.DragDropEffects.Move) { - var parent = solutionGroups.FindParent(source); + VsFolder? parent = this.solutionGroups.FindParent(source); parent?.Items.Remove((VsItem)source); if (source is VsFolder f) @@ -653,6 +690,7 @@ private void olvFiles_ModelDroppedHandler(object sender, ModelDropEventArgs e) this.olvFiles.UpdateObject(source); this.olvFiles.UpdateObject(parent); } + if (e.Effect == System.Windows.Forms.DragDropEffects.Copy) { // make sure to add "copy" if same parent @@ -666,109 +704,163 @@ private void olvFiles_ModelDroppedHandler(object sender, ModelDropEventArgs e) } } + #endregion + +#region Context Menu item handling + /// - /// olvs the files_ selected index changed. + /// Handles the settings menu item. /// /// The sender. /// The e. - private void olvFiles_SelectedIndexChanged(object sender, EventArgs e) + private void settingsToolStripMenuItem_Click(object sender, EventArgs e) { - // update status bar text with info on selected item - if (olvFiles.SelectedItem != null) + object item = this.olvFiles.SelectedItem.RowObject; + dlgExecuteVisualStudio dlg = new dlgExecuteVisualStudio(item); + + if (dlg.ShowDialog() == DialogResult.OK) { - var item = olvFiles.SelectedItem.RowObject; - if (item is VsSolution s) - { - mainStatusLabel.Text = $"Visual Studio Solution: {s.Projects?.Count} Projects, {s.TypeAsName()}"; - } - else if (item is VsProject p) - { - mainStatusLabel.Text = $"Visual Studio Project: {p.TypeAsName()}, .NET {p.FrameworkVersion}"; - } - else if (item is VsFolder sg) + _ = SolutionData_OnChanged(true); + } + } + + /// + /// Handles the rename menu item + /// + /// The sender. + /// The e. + private void renameToolStripMenuItem_Click(object sender, EventArgs e) + { + object item = this.olvFiles.SelectedItem.RowObject; + if (item is VsItem vsi) + { + dlgRename dlg = new dlgRename(vsi); + if (dlg.ShowDialog() == DialogResult.OK) { - mainStatusLabel.Text = $"Contains {sg.ContainedSolutionsCount()} solution{((sg.ContainedSolutionsCount() != 1) ? 's' : "")}"; + vsi.Name = dlg.ItemName; + + _ = SolutionData_OnChanged(true); } } - else + } + + /// + /// Handles removing an item through the context menu + /// + /// The sender. + /// The e. + private void removeToolStripMenuItem_Click(object sender, EventArgs e) + { + object item = this.olvFiles.SelectedItem.RowObject; + + // only ask the user if the selected item is not an empty folder + if (item is not VsFolder sg || sg.Items.Count != 0) { - mainStatusLabel.Text = string.Empty; + // ask user if he wants to delete this item really + if (MessageBox.Show($"Are you sure you want to delete '{((VsItem)item).Name}'", "Delete item", MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes) + { + return; + } } + + VsFolder? owner = this.solutionGroups.FindParent(item); + owner?.Items.Remove((VsItem)item); + + _ = SolutionData_OnChanged(true); } /// - /// Rebuilds the filters. + /// Handles the New Group context menu item /// - private void RebuildFilters() + /// The sender. + /// The e. + private void newGroupToolStripMenuItem_Click(object sender, EventArgs e) { - this.olvFiles.ModelFilter = String.IsNullOrEmpty(this.txtFilter.Text) ? null : new TextMatchFilter(this.olvFiles, this.txtFilter.Text); - // this.olvFiles.AdditionalFilter = filters.Count == 0 ? null : new CompositeAllFilter(filters); + // redirect to existing code + mainFolderAdd_Click(sender, e); } /// - /// renames the tool strip menu item_ click. + /// Handles the From Folder context menu item /// /// The sender. /// The e. - private void renameToolStripMenuItem_Click(object sender, EventArgs e) + private void fromFolderToolStripMenuItem_Click(object sender, EventArgs e) { - var item = olvFiles.SelectedItem.RowObject; - if (item is VsItem vsi) + // redirect to existing code + mainImportFolder_Click(sender, e); + } + + /// + /// Handles the Solution or project context menu item + /// + /// The sender. + /// The e. + private void solutionProjectToolStripMenuItem_Click(object sender, EventArgs e) + { + OpenFileDialog dlg = new OpenFileDialog { - var dlg = new dlgRename(vsi); - if (dlg.ShowDialog() == DialogResult.OK) - { - vsi.Name = dlg.ItemName; + Filter = FileHelper.SolutionFilterString, + Title = "Select a solution or project file" + }; - this.SolutionData_OnChanged(true); + if (dlg.ShowDialog() == DialogResult.OK) + { + VsItem item = ImportHelper.GetItemFromExtension(Path.GetFileNameWithoutExtension(dlg.FileName), dlg.FileName); + dlgExecuteVisualStudio dlg2 = new dlgExecuteVisualStudio(item); + if (dlg2.ShowDialog() == DialogResult.OK) + { + // add the item to the list + MergeNewItem(this.olvFiles.SelectedItem, dlg2.Item!); + UpdateList(); } } } /// - /// runs the as admin tool strip menu item_ click. + /// Handles the run as admin menu item. /// /// The sender. /// The e. private void runAsAdminToolStripMenuItem_Click(object sender, EventArgs e) { - var item = olvFiles.SelectedItem.RowObject; - var vs = this.selectVisualStudioVersion.SelectedItem as VisualStudioInstance ?? throw new InvalidCastException(); + object item = this.olvFiles.SelectedItem.RowObject; + VisualStudioInstance vs = this.selectVisualStudioVersion.SelectedItem ?? throw new InvalidCastException(); if (item is VsFolder sg) { - new ItemLauncher(sg, vs).Launch(true); + _ = new ItemLauncher(sg, vs).Launch(true); } else if (item is VsSolution s) { - new ItemLauncher(s, vs).Launch(true); + _ = new ItemLauncher(s, vs).Launch(true); } } /// - /// runs the tool strip menu item_ click. + /// Handles the runs menu item. /// /// The sender. /// The e. private void runToolStripMenuItem_Click(object sender, EventArgs e) { - var item = olvFiles.SelectedItem.RowObject; - var vs = this.selectVisualStudioVersion.SelectedItem; - ItemLauncher il = null; + object item = this.olvFiles.SelectedItem.RowObject; + VisualStudioInstance? vs = this.selectVisualStudioVersion.SelectedItem; + ItemLauncher? il = null; if (item is VsFolder f) { - vs = String.IsNullOrEmpty(f.VsVersion) ? visualStudioInstances.GetByIdentifier(f.VsVersion) : vs; + vs = string.IsNullOrEmpty(f.VsVersion) ? this.visualStudioInstances.GetByIdentifier(f.VsVersion) : vs; il = new ItemLauncher(f, vs); } else if (item is VsSolution s) { - vs = visualStudioInstances.GetByVersion(s.RequiredVersion) ?? vs; + vs = this.visualStudioInstances.GetByVersion(s.RequiredVersion) ?? vs; il = new ItemLauncher(s, vs); } else if (item is VsProject p) { - vs = visualStudioInstances.GetByIdentifier(p.VsVersion) ?? vs; + vs = this.visualStudioInstances.GetByIdentifier(p.VsVersion) ?? vs; il = new ItemLauncher(p, vs); } @@ -778,54 +870,14 @@ private void runToolStripMenuItem_Click(object sender, EventArgs e) if (il.LastException != null) { - MessageBox.Show(il.LastException.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + _ = MessageBox.Show(il.LastException.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); } } - } +#endregion /// - /// Saves the solution data. - /// - private void SaveSolutionData() - { - // save this.solutionGroups data to a JSON file in the users data folder - string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VSLauncher", "VSLauncher.json"); - - try - { - var dir = Path.GetDirectoryName(fileName); - if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) - { - // make sure the path exists - Directory.CreateDirectory(dir); - } - - JsonSerializerSettings settings = new JsonSerializerSettings() - { - Formatting = Formatting.Indented, - TypeNameHandling = TypeNameHandling.All - }; - - string json = JsonConvert.SerializeObject(this.solutionGroups, settings); - try - { - File.WriteAllText(fileName, json); - } - catch (System.Exception ex) - { - // alert user of an error saving the data - MessageBox.Show($"There was an error saving the data. \r\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - catch (System.Exception ex) - { - MessageBox.Show($"There was an error saving the data. \r\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); - } - } - - /// - /// selects the visual studio version_ draw item. + /// Handles visual studio version item drawing with icon. /// /// The sender. /// The e. @@ -836,109 +888,176 @@ private void selectVisualStudioVersion_DrawItem(object sender, DrawItemEventArgs { e.DrawBackground(); - var height = 16; // selectVisualStudioVersion.Height - (selectVisualStudioVersion.Margin.Top+selectVisualStudioVersion.Margin.Bottom); + int height = 16; // selectVisualStudioVersion.Height - (selectVisualStudioVersion.Margin.Top+selectVisualStudioVersion.Margin.Bottom); - Rectangle iconRect = new Rectangle(e.Bounds.Left + selectVisualStudioVersion.Margin.Left, - e.Bounds.Top + ((selectVisualStudioVersion.ItemHeight - height) / 2), + Rectangle iconRect = new Rectangle(e.Bounds.Left + this.selectVisualStudioVersion.Margin.Left, + e.Bounds.Top + ((this.selectVisualStudioVersion.ItemHeight - height) / 2), height, height); - e.Graphics.DrawIcon(visualStudioInstances[e.Index].AppIcon, iconRect); - e.Graphics.DrawString(visualStudioInstances[e.Index].Name, e.Font!, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4); + e.Graphics.DrawIcon(this.visualStudioInstances[e.Index].AppIcon, iconRect); + e.Graphics.DrawString(this.visualStudioInstances[e.Index].Name, e.Font!, Brushes.Black, e.Bounds.Left + 20, e.Bounds.Top + 4); } } /// - /// selects the visual studio version_ selected index changed. + /// Handles visual studio version selected index changed. /// /// The sender. /// The e. private void selectVisualStudioVersion_SelectedIndexChanged(object sender, EventArgs e) { // update all buttons with icon from selected visual studio version - if (selectVisualStudioVersion.SelectedIndex >= 0 && selectVisualStudioVersion.SelectedIndex < visualStudioInstances.Count) + if (this.selectVisualStudioVersion.SelectedIndex >= 0 && this.selectVisualStudioVersion.SelectedIndex < this.visualStudioInstances.Count) { // save the selected item as last selected item if (!this.bInUpdate) { - Properties.Settings.Default.SelectedVSversion = visualStudioInstances[selectVisualStudioVersion.SelectedIndex].Identifier; + Properties.Settings.Default.SelectedVSversion = this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].Identifier; Properties.Settings.Default.Save(); } // set icon and text according to selection onto all buttons - var icon = visualStudioInstances[selectVisualStudioVersion.SelectedIndex].AppIcon.ToBitmap(); - string shortName = visualStudioInstances[selectVisualStudioVersion.SelectedIndex].ShortName; + Bitmap icon = this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].AppIcon.ToBitmap(); + string shortName = this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].ShortName; - btnMainStartVisualStudio1.Text = string.Format((string)btnMainStartVisualStudio1.Tag, shortName); - tooltipForButtons.SetToolTip(btnMainStartVisualStudio1, visualStudioInstances[selectVisualStudioVersion.SelectedIndex].ToString()); - btnMainStartVisualStudio1.Image = icon; + this.btnMainStartVisualStudio1.Text = string.Format((string)this.btnMainStartVisualStudio1.Tag, shortName); + this.tooltipForButtons.SetToolTip(this.btnMainStartVisualStudio1, this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].ToString()); + this.btnMainStartVisualStudio1.Image = icon; - btnMainStartVisualStudio2.Text = string.Format((string)btnMainStartVisualStudio2.Tag, shortName); - tooltipForButtons.SetToolTip(btnMainStartVisualStudio2, visualStudioInstances[selectVisualStudioVersion.SelectedIndex].ToString()); - btnMainStartVisualStudio2.Image = icon; + this.btnMainStartVisualStudio2.Text = string.Format((string)this.btnMainStartVisualStudio2.Tag, shortName); + this.tooltipForButtons.SetToolTip(this.btnMainStartVisualStudio2, this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].ToString()); + this.btnMainStartVisualStudio2.Image = icon; - btnMainStartVisualStudio3.Text = string.Format((string)btnMainStartVisualStudio3.Tag, shortName); - tooltipForButtons.SetToolTip(btnMainStartVisualStudio3, visualStudioInstances[selectVisualStudioVersion.SelectedIndex].ToString()); - btnMainStartVisualStudio3.Image = icon; + this.btnMainStartVisualStudio3.Text = string.Format((string)this.btnMainStartVisualStudio3.Tag, shortName); + this.tooltipForButtons.SetToolTip(this.btnMainStartVisualStudio3, this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].ToString()); + this.btnMainStartVisualStudio3.Image = icon; - btnMainStartVisualStudio4.Text = string.Format((string)btnMainStartVisualStudio4.Tag, shortName); - tooltipForButtons.SetToolTip(btnMainStartVisualStudio4, visualStudioInstances[selectVisualStudioVersion.SelectedIndex].ToString()); - btnMainStartVisualStudio4.Image = icon; + this.btnMainStartVisualStudio4.Text = string.Format((string)this.btnMainStartVisualStudio4.Tag, shortName); + this.tooltipForButtons.SetToolTip(this.btnMainStartVisualStudio4, this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].ToString()); + this.btnMainStartVisualStudio4.Image = icon; - btnMainStartVisualStudio5.Text = string.Format((string)btnMainStartVisualStudio5.Tag, shortName); - tooltipForButtons.SetToolTip(btnMainStartVisualStudio5, visualStudioInstances[selectVisualStudioVersion.SelectedIndex].ToString()); - btnMainStartVisualStudio5.Image = icon; + this.btnMainStartVisualStudio5.Text = string.Format((string)this.btnMainStartVisualStudio5.Tag, shortName); + this.tooltipForButtons.SetToolTip(this.btnMainStartVisualStudio5, this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex].ToString()); + this.btnMainStartVisualStudio5.Image = icon; } } +#region Solution Data Handling /// - /// settings the tool strip menu item_ click. + /// Handles changes to Solution data /// - /// The sender. - /// The e. - private void settingsToolStripMenuItem_Click(object sender, EventArgs e) + private bool SolutionData_OnChanged(bool changed) { - object item = olvFiles.SelectedItem.RowObject; - var dlg = new dlgExecuteVisualStudio(item); - - if (dlg.ShowDialog() == DialogResult.OK) + if (changed) { - this.SolutionData_OnChanged(true); + this.solutionGroups.LastModified = DateTime.Now; + SaveSolutionData(); + UpdateList(); } + + return false; } /// - /// Setups the drag and drop. + /// Loads the solution data. /// - private void SetupDragAndDrop() + private void LoadSolutionData() { - // Setup the tree so that it can drop and drop. - // Drag and drop support - // You can set up drag and drop explicitly (like this) or, in the IDE, you can set - // IsSimpleDropSource and IsSimpleDragSource and respond to CanDrop and Dropped events + // load this.solutionGroups data from a JSON file in the users data folder + string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VSLauncher", "VSLauncher.json"); - this.olvFiles.DragSource = new SimpleDragSource(); - this.olvFiles.IsSimpleDragSource = true; - this.olvFiles.IsSimpleDropSink = true; + if (File.Exists(fileName)) + { + string json = File.ReadAllText(fileName); + JsonSerializerSettings settings = new JsonSerializerSettings() + { + TypeNameHandling = TypeNameHandling.All + }; - this.olvFiles.ModelCanDrop += olvFiles_ModelCanDropHandler; - this.olvFiles.ModelDropped += olvFiles_ModelDroppedHandler; + VsFolder? data = null; + + try + { + data = JsonConvert.DeserializeObject(json, settings); + } + catch (System.Exception) + { + // probably wrong format + } + + if (data is null) + { + // alert the user that the datafile was unreadable + _ = MessageBox.Show($"The datafile was unreadable. Please check the file in '{fileName}' and try again.", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + else + { + data.Refresh(); + this.solutionGroups = data; + } + } } /// - /// Solutions the data_ on changed. + /// Saves the solution data. /// - private bool SolutionData_OnChanged(bool changed) + private void SaveSolutionData() { - if (changed) + // save this.solutionGroups data to a JSON file in the users data folder + string fileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "VSLauncher", "VSLauncher.json"); + + try { - this.solutionGroups.LastModified = DateTime.Now; - SaveSolutionData(); - UpdateList(); + string? dir = Path.GetDirectoryName(fileName); + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + { + // make sure the path exists + _ = Directory.CreateDirectory(dir); + } + + JsonSerializerSettings settings = new JsonSerializerSettings() + { + Formatting = Formatting.Indented, + TypeNameHandling = TypeNameHandling.All + }; + + string json = JsonConvert.SerializeObject(this.solutionGroups, settings); + try + { + File.WriteAllText(fileName, json); + } + catch (System.Exception ex) + { + // alert user of an error saving the data + _ = MessageBox.Show($"There was an error saving the data. \r\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } } + catch (System.Exception ex) + { + _ = MessageBox.Show($"There was an error saving the data. \r\n{ex.Message}", "Error", MessageBoxButtons.OK, MessageBoxIcon.Error); + } + } - return false; + /// + /// Rebuilds the filters. + /// + private void RebuildFilters() + { + this.olvFiles.ModelFilter = string.IsNullOrEmpty(this.txtFilter.Text) ? null : new TextMatchFilter(this.olvFiles, this.txtFilter.Text); + // this.olvFiles.AdditionalFilter = filters.Count == 0 ? null : new CompositeAllFilter(filters); + } + + /// + /// Updates the list. + /// + private void UpdateList() + { + // TODO: must verify items before loading, indicate missing items through warning icon + this.olvFiles.SetObjects(this.solutionGroups.Items); + // this.olvFiles.ExpandAll(); } +#endregion - #region Main Buttons +#region Main VS Execution Buttons handling /// /// Handles click on the btnMainStartVisualStudio1 button (Start VS) @@ -948,7 +1067,7 @@ private bool SolutionData_OnChanged(bool changed) private void btnMainStartVisualStudio1_Click(object sender, EventArgs e) { this.Cursor = Cursors.WaitCursor; - var vs = visualStudioInstances[selectVisualStudioVersion.SelectedIndex]; + VisualStudioInstance vs = this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex]; vs.Execute(); this.Cursor = Cursors.Default; } @@ -961,7 +1080,7 @@ private void btnMainStartVisualStudio1_Click(object sender, EventArgs e) private void btnMainStartVisualStudio2_Click(object sender, EventArgs e) { this.Cursor = Cursors.WaitCursor; - var vs = visualStudioInstances[selectVisualStudioVersion.SelectedIndex]; + VisualStudioInstance vs = this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex]; vs.ExecuteAsAdmin(); this.Cursor = Cursors.Default; } @@ -973,11 +1092,11 @@ private void btnMainStartVisualStudio2_Click(object sender, EventArgs e) /// The e. private void btnMainStartVisualStudio3_Click(object sender, EventArgs e) { - var dlg = new dlgNewInstance(); + dlgNewInstance dlg = new dlgNewInstance(); if (dlg.ShowDialog() == DialogResult.OK) { this.Cursor = Cursors.WaitCursor; - var vs = visualStudioInstances[selectVisualStudioVersion.SelectedIndex]; + VisualStudioInstance vs = this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex]; vs.ExecuteWithInstance(IsControlPressed(), dlg.InstanceName); this.Cursor = Cursors.Default; @@ -992,7 +1111,7 @@ private void btnMainStartVisualStudio3_Click(object sender, EventArgs e) private void btnMainStartVisualStudio4_Click(object sender, EventArgs e) { this.Cursor = Cursors.WaitCursor; - var vs = visualStudioInstances[selectVisualStudioVersion.SelectedIndex]; + VisualStudioInstance vs = this.visualStudioInstances[this.selectVisualStudioVersion.SelectedIndex]; vs.ExecuteNewProject(IsControlPressed()); this.Cursor = Cursors.Default; } @@ -1004,12 +1123,12 @@ private void btnMainStartVisualStudio4_Click(object sender, EventArgs e) /// The e. private void btnMainStartVisualStudio5_Click(object sender, EventArgs e) { - var dlg = new dlgExecuteVisualStudio(selectVisualStudioVersion.SelectedIndex); + dlgExecuteVisualStudio dlg = new dlgExecuteVisualStudio(this.selectVisualStudioVersion.SelectedIndex); if (dlg.ShowDialog() == DialogResult.OK) { this.Cursor = Cursors.WaitCursor; - var vs = dlg.VsVersion; + VisualStudioInstance vs = dlg.VsVersion; if (dlg.Item is not null) { @@ -1019,8 +1138,7 @@ private void btnMainStartVisualStudio5_Click(object sender, EventArgs e) this.Cursor = Cursors.Default; } } - - #endregion Main Buttons +#endregion /// /// Handles text changes in the filter field and updates the list @@ -1033,78 +1151,41 @@ private void txtFilter_TextChanged(object sender, EventArgs e) } /// - /// Updates the list. - /// - private void UpdateList() - { - // TODO: must verify items before loading, indicate missing items through warning icon - this.olvFiles.SetObjects(this.solutionGroups.Items); - // this.olvFiles.ExpandAll(); - } - - /// - /// Handles the form closing, saves state + /// Handles resize of the main panel, resizes the filter field to fit the panel /// /// The sender. /// The e. - private void MainDialog_FormClosing(object sender, FormClosingEventArgs e) - { - Properties.Settings.Default.AppWindow = this.WindowState; - if (this.WindowState == FormWindowState.Normal) - { - // save location and size if the state is normal - Properties.Settings.Default.AppLocation = this.Location; - Properties.Settings.Default.AppSize = this.Size; - } - else - { - // save the RestoreBounds if the form is minimized or maximized! - Properties.Settings.Default.AppLocation = this.RestoreBounds.Location; - Properties.Settings.Default.AppSize = this.RestoreBounds.Size; - } - - Properties.Settings.Default.AppState = "saved"; - - // don't forget to save the settings - Properties.Settings.Default.Save(); - } - private void mainPanel_Resize(object sender, EventArgs e) { - int w = txtFilter.Parent.Width; + int w = this.txtFilter.Parent.Width; w -= (34 * 5) + 12; // 5 buttons + spacer - w -= txtFilter.Location.X; - txtFilter.Width = w; - } - - private void newGroupToolStripMenuItem_Click(object sender, EventArgs e) - { - // redirect to existing code - mainFolderAdd_Click(sender, e); + w -= this.txtFilter.Location.X; + this.txtFilter.Width = w; } - private void fromFolderToolStripMenuItem_Click(object sender, EventArgs e) - { - // redirect to existing code - mainImportFolder_Click(sender, e); - } - - private void solutionProjectToolStripMenuItem_Click(object sender, EventArgs e) + /// + /// Handles context menu opening, enables/disables menu items based on the selected list item + /// + /// The sender. + /// The e. + private void ctxMenu_Opening(object sender, System.ComponentModel.CancelEventArgs e) { - + // if the currently selected item is a group, enable the "Add..." menu item, otherwise remove it + this.ctxMenu.Items[0].Enabled = this.olvFiles.SelectedObject is VsFolder; } - private void ctxMenu_Opening(object sender, System.ComponentModel.CancelEventArgs e) + /// + /// Handles key press on the filter field, clears the filter if the user presses ESC + /// + /// The sender. + /// The e. + private void txtFilter_KeyPress(object sender, KeyPressEventArgs e) { - // if the currently selected item is a group, enable the "Add..." menu item, otherwise remove it - if (this.olvFiles.SelectedObject is VsFolder) + if (e.KeyChar == (char)Keys.Escape) { - this.ctxMenu.Items[0].Enabled = true; - } - else - { - this.ctxMenu.Items[0].Enabled = false; + this.txtFilter.Text = string.Empty; } } + } -} \ No newline at end of file +} diff --git a/VSLauncherX/MainDialog.resx b/VSLauncherX/MainDialog.resx index 160a750..5265d09 100644 --- a/VSLauncherX/MainDialog.resx +++ b/VSLauncherX/MainDialog.resx @@ -129,9 +129,6 @@ False - - False - @@ -227,7 +224,7 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAATh4AAAJNU0Z0AUkBTAIBARwB - AAFoAQEBaAEBARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAGAAwABAQEAAQgG + AAGYAQEBmAEBARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAGAAwABAQEAAQgG AAEgGAABgAIAAYADAAKAAQABgAMAAYABAAGAAQACgAIAA8ABAAHAAdwBwAEAAfABygGmAQABMwUAATMB AAEzAQABMwEAAjMCAAMWAQADHAEAAyIBAAMpAQADVQEAA00BAANCAQADOQEAAYABfAH/AQACUAH/AQAB kwEAAdYBAAH/AewBzAEAAcYB1gHvAQAB1gLnAQABkAGpAa0CAAH/ATMDAAFmAwABmQMAAcwCAAEzAwAC @@ -359,8 +356,8 @@ /wHvA/8L - - 17, 17 + + False @@ -630,7 +627,7 @@ AAEAAAD/////AQAAAAAAAAAMAgAAAEZTeXN0ZW0uV2luZG93cy5Gb3JtcywgQ3VsdHVyZT1uZXV0cmFs LCBQdWJsaWNLZXlUb2tlbj1iNzdhNWM1NjE5MzRlMDg5BQEAAAAmU3lzdGVtLldpbmRvd3MuRm9ybXMu SW1hZ2VMaXN0U3RyZWFtZXIBAAAABERhdGEHAgIAAAAJAwAAAA8DAAAAHA8AAAJNU0Z0AUkBTAIBAQoB - AAFYAQEBWAEBARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAEwAwABAQEAAQgG + AAGIAQEBiAEBARABAAEQAQAE/wEJAQAI/wFCAU0BNgEEBgABNgEEAgABKAMAAUADAAEwAwABAQEAAQgG AAEMGAABgAIAAYADAAKAAQABgAMAAYABAAGAAQACgAIAA8ABAAHAAdwBwAEAAfABygGmAQABMwUAATMB AAEzAQABMwEAAjMCAAMWAQADHAEAAyIBAAMpAQADVQEAA00BAANCAQADOQEAAYABfAH/AQACUAH/AQAB kwEAAdYBAAH/AewBzAEAAcYB1gHvAQAB1gLnAQABkAGpAa0CAAH/ATMDAAFmAwABmQMAAcwCAAEzAwAC