diff --git a/Dashboard/MainWindow.xaml.cs b/Dashboard/MainWindow.xaml.cs index 2aaf20ad..6b72b1bb 100644 --- a/Dashboard/MainWindow.xaml.cs +++ b/Dashboard/MainWindow.xaml.cs @@ -1691,7 +1691,6 @@ private void OpenPlanViewerTab() } _mainPlanTabControl = new TabControl - { Background = System.Windows.Media.Brushes.Transparent, BorderThickness = new Thickness(0) @@ -1867,18 +1866,25 @@ private TabItem AddNewEmptyPlanSubTab() var dialog = new Microsoft.Win32.OpenFileDialog { Filter = "SQL Plan Files (*.sqlplan)|*.sqlplan|XML Files (*.xml)|*.xml|All Files (*.*)|*.*", - DefaultExt = ".sqlplan" + DefaultExt = ".sqlplan", + Multiselect = true }; if (dialog.ShowDialog() != true) return; - try - { - var xml = System.IO.File.ReadAllText(dialog.FileName); - LoadPlanIntoSubTab(subTab, xml, System.IO.Path.GetFileName(dialog.FileName)); - } - catch (Exception ex) + var isFirst = true; + foreach (var fileName in dialog.FileNames) { - MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", - MessageBoxButton.OK, MessageBoxImage.Error); + try + { + var xml = System.IO.File.ReadAllText(fileName); + var targetTab = isFirst ? subTab : AddNewEmptyPlanSubTab(); + LoadPlanIntoSubTab(targetTab, xml, System.IO.Path.GetFileName(fileName)); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + isFirst = false; } }; @@ -1985,35 +1991,12 @@ subTab.Header is StackPanel sp && return _mainPlanTabControl.SelectedItem as TabItem; } - public void OpenMainWindowPlanTab(string planXml, string label, string? queryText = null) - { - if (_planViewerTab == null || !ServerTabControl.Items.Contains(_planViewerTab)) - OpenPlanViewerTab(); - - var activeSubTab = GetActivePlanSubTab(); - if (activeSubTab?.Content is Grid g && - g.Children.Count >= 2 && - g.Children[1] is Controls.PlanViewerControl { Visibility: Visibility.Collapsed }) - { - // Active sub-tab is empty — load into it - LoadPlanIntoSubTab(activeSubTab, planXml, label, queryText); - } - else - { - // Active sub-tab already has a plan — open a new one - var newSubTab = AddNewEmptyPlanSubTab(); - LoadPlanIntoSubTab(newSubTab, planXml, label, queryText); - } - - ServerTabControl.SelectedItem = _planViewerTab; - } - private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) + if (files?.Any(IsPlanFile) == true) { e.Effects = DragDropEffects.Copy; e.Handled = true; @@ -2027,9 +2010,24 @@ private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) private void MainWindowPlanViewer_Drop(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return; - var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) - LoadMainWindowPlanFromFileIntoActiveTab(files[0]); + var planFiles = (e.Data.GetData(DataFormats.FileDrop) as string[]) + ?.Where(IsPlanFile).ToArray(); + if (planFiles == null || planFiles.Length == 0) return; + LoadMainWindowPlanFromFileIntoActiveTab(planFiles[0]); + for (var i = 1; i < planFiles.Length; i++) + { + var newTab = AddNewEmptyPlanSubTab(); + try + { + var xml = System.IO.File.ReadAllText(planFiles[i]); + LoadPlanIntoSubTab(newTab, xml, System.IO.Path.GetFileName(planFiles[i])); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } } private void MainWindowPlanViewer_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) diff --git a/Lite/Controls/ServerTab.xaml.cs b/Lite/Controls/ServerTab.xaml.cs index 87ff451b..9c5a61a5 100644 --- a/Lite/Controls/ServerTab.xaml.cs +++ b/Lite/Controls/ServerTab.xaml.cs @@ -235,6 +235,26 @@ public ServerTab(ServerConnection server, DuckDbInitializer duckDb, CredentialSe /* Initial load is triggered by MainWindow.ConnectToServer calling RefreshData() after collectors finish - no Loaded handler needed */ + + KeyDown += ServerTab_KeyDown; + Focusable = true; + } + + private void ServerTab_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + if (e.Key == System.Windows.Input.Key.V && + System.Windows.Input.Keyboard.Modifiers == System.Windows.Input.ModifierKeys.Control && + e.OriginalSource is not System.Windows.Controls.TextBox && + PlanViewerTabItem.IsSelected) + { + var xml = System.Windows.Clipboard.GetText(); + if (!string.IsNullOrWhiteSpace(xml)) + { + e.Handled = true; + OpenPlanTab(xml, "Pasted Plan"); + PlanViewerTabItem.IsSelected = true; + } + } } private void InitializeTimeComboBoxes() diff --git a/Lite/MainWindow.xaml.cs b/Lite/MainWindow.xaml.cs index beba2b76..62c6eba4 100644 --- a/Lite/MainWindow.xaml.cs +++ b/Lite/MainWindow.xaml.cs @@ -1630,18 +1630,25 @@ private TabItem AddNewEmptyPlanSubTab() var dialog = new Microsoft.Win32.OpenFileDialog { Filter = "SQL Plan Files (*.sqlplan)|*.sqlplan|XML Files (*.xml)|*.xml|All Files (*.*)|*.*", - DefaultExt = ".sqlplan" + DefaultExt = ".sqlplan", + Multiselect = true }; if (dialog.ShowDialog() != true) return; - try + var isFirst = true; + foreach (var fileName in dialog.FileNames) { - var xml = System.IO.File.ReadAllText(dialog.FileName); - LoadPlanIntoSubTab(subTab, xml, System.IO.Path.GetFileName(dialog.FileName)); - } - catch (Exception ex) - { - MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", - MessageBoxButton.OK, MessageBoxImage.Error); + try + { + var xml = System.IO.File.ReadAllText(fileName); + var targetTab = isFirst ? subTab : AddNewEmptyPlanSubTab(); + LoadPlanIntoSubTab(targetTab, xml, System.IO.Path.GetFileName(fileName)); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to open file:\n\n{ex.Message}", "Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + isFirst = false; } }; @@ -1745,34 +1752,12 @@ subTab.Header is StackPanel sp && return MainWindowPlanTabControl.SelectedItem as TabItem; } - public void OpenMainWindowPlanTab(string planXml, string label, string? queryText = null) - { - EnsurePlanTabControlInitialized(); - MainWindowPlanViewerTab.Visibility = Visibility.Visible; - - var activeSubTab = GetActivePlanSubTab(); - if (activeSubTab?.Content is Grid g && - g.Children.Count >= 2 && - g.Children[1] is Controls.PlanViewerControl { Visibility: Visibility.Collapsed }) - { - LoadPlanIntoSubTab(activeSubTab, planXml, label, queryText); - } - else - { - var newSubTab = AddNewEmptyPlanSubTab(); - LoadPlanIntoSubTab(newSubTab, planXml, label, queryText); - } - - MainWindowPlanViewerTab.IsSelected = true; - ServerTabControl.Visibility = Visibility.Visible; - } - private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) { if (e.Data.GetDataPresent(DataFormats.FileDrop)) { var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) + if (files?.Any(IsPlanFile) == true) { e.Effects = DragDropEffects.Copy; e.Handled = true; @@ -1786,9 +1771,24 @@ private void MainWindowPlanViewer_DragOver(object sender, DragEventArgs e) private void MainWindowPlanViewer_Drop(object sender, DragEventArgs e) { if (!e.Data.GetDataPresent(DataFormats.FileDrop)) return; - var files = e.Data.GetData(DataFormats.FileDrop) as string[]; - if (files?.Length > 0 && IsPlanFile(files[0])) - LoadMainWindowPlanFromFileIntoActiveTab(files[0]); + var planFiles = (e.Data.GetData(DataFormats.FileDrop) as string[]) + ?.Where(IsPlanFile).ToArray(); + if (planFiles == null || planFiles.Length == 0) return; + LoadMainWindowPlanFromFileIntoActiveTab(planFiles[0]); + for (var i = 1; i < planFiles.Length; i++) + { + var newTab = AddNewEmptyPlanSubTab(); + try + { + var xml = System.IO.File.ReadAllText(planFiles[i]); + LoadPlanIntoSubTab(newTab, xml, System.IO.Path.GetFileName(planFiles[i])); + } + catch (Exception ex) + { + MessageBox.Show($"Failed to load plan file:\n{ex.Message}", "Load Error", + MessageBoxButton.OK, MessageBoxImage.Error); + } + } } private void MainWindowPlanViewer_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)