diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java index 2b833b7904..62cfd44f85 100644 --- a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java +++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/ConfigurationPerspective.java @@ -19,7 +19,9 @@ import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; +import java.util.Map; import org.apache.hop.core.Props; import org.apache.hop.core.gui.plugin.GuiPlugin; import org.apache.hop.core.gui.plugin.GuiRegistry; @@ -29,16 +31,35 @@ import org.apache.hop.core.search.ISearchable; import org.apache.hop.ui.core.PropsUi; import org.apache.hop.ui.core.dialog.ErrorDialog; +import org.apache.hop.ui.core.gui.GuiResource; +import org.apache.hop.ui.core.widget.TableView; import org.apache.hop.ui.hopgui.HopGui; import org.apache.hop.ui.hopgui.context.IGuiContextHandler; import org.apache.hop.ui.hopgui.perspective.HopPerspectivePlugin; import org.apache.hop.ui.hopgui.perspective.IHopPerspective; +import org.apache.hop.ui.hopgui.perspective.configuration.tabs.ConfigPluginOptionsTab; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CTabFolder; import org.eclipse.swt.custom.CTabItem; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; @HopPerspectivePlugin( id = "160-HopConfigurationPerspective", @@ -53,6 +74,15 @@ public class ConfigurationPerspective implements IHopPerspective { private HopGui hopGui; private Composite composite; public CTabFolder configTabs; + private Tree categoryTree; + private Text searchBox; + private Map categoryTabs = new HashMap<>(); + private TreeItem pluginsTreeItem; + private Map originalBackgrounds = new HashMap<>(); + private Map originalFonts = new HashMap<>(); + private List highlightedControls = new ArrayList<>(); + private String currentSearchText = ""; // Track current search for re-applying highlights + private Color highlightColor; // Custom neutral highlight color private static ConfigurationPerspective instance; public static ConfigurationPerspective getInstance() { @@ -95,18 +125,133 @@ public void initialize(HopGui hopGui, Composite parent) { this.hopGui = hopGui; composite = new Composite(parent, SWT.NONE); - composite.setLayout(new FillLayout()); PropsUi.setLook(composite); + FormLayout formLayout = new FormLayout(); + formLayout.marginWidth = 0; + formLayout.marginHeight = 0; + composite.setLayout(formLayout); - configTabs = new CTabFolder(composite, SWT.BORDER); + // Create a neutral highlight color (light blue-gray) + highlightColor = GuiResource.getInstance().getColorLightBlue(); + + // Dispose color when composite is disposed + composite.addListener( + SWT.Dispose, + e -> { + if (highlightColor != null && !highlightColor.isDisposed()) { + highlightColor.dispose(); + } + }); + + // Search box at the top + searchBox = new Text(composite, SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL | SWT.BORDER); + PropsUi.setLook(searchBox); + searchBox.setMessage("Search settings..."); + FormData fdSearchBox = new FormData(); + fdSearchBox.left = new FormAttachment(0, PropsUi.getMargin()); + fdSearchBox.top = new FormAttachment(0, PropsUi.getMargin()); + fdSearchBox.right = new FormAttachment(100, -PropsUi.getMargin()); + searchBox.setLayoutData(fdSearchBox); + searchBox.addListener(SWT.Modify, e -> filterSettings(searchBox.getText())); + + // SashForm for tree on left and content on right + SashForm sashForm = new SashForm(composite, SWT.HORIZONTAL | SWT.SMOOTH); + PropsUi.setLook(sashForm); + FormData fdSashForm = new FormData(); + fdSashForm.left = new FormAttachment(0, 0); + fdSashForm.top = new FormAttachment(searchBox, PropsUi.getMargin()); + fdSashForm.right = new FormAttachment(100, 0); + fdSashForm.bottom = new FormAttachment(100, 0); + sashForm.setLayoutData(fdSashForm); + + // Left side: Tree navigation + Composite treeComposite = new Composite(sashForm, SWT.BORDER); + PropsUi.setLook(treeComposite); + treeComposite.setLayout(new FillLayout()); + + categoryTree = new Tree(treeComposite, SWT.SINGLE | SWT.V_SCROLL); + PropsUi.setLook(categoryTree, Props.WIDGET_STYLE_TREE); + categoryTree.addListener( + SWT.Selection, + e -> { + TreeItem[] selection = categoryTree.getSelection(); + if (selection.length > 0) { + TreeItem selectedItem = selection[0]; + + // Check if this is a plugin sub-item + Boolean isPlugin = (Boolean) selectedItem.getData("isPlugin"); + if (isPlugin != null && isPlugin) { + // This is a plugin sub-item + String pluginName = (String) selectedItem.getData("pluginName"); + + // First, show the Plugins tab (with highlighting) + TreeItem parentItem = selectedItem.getParentItem(); + if (parentItem != null) { + showCategory(parentItem.getText(), false); // Don't apply highlighting yet + + // Then show the specific plugin settings + CTabItem pluginTab = categoryTabs.get(parentItem.getText()); + if (pluginTab != null) { + Control tabControl = pluginTab.getControl(); + if (tabControl instanceof Composite) { + Composite pluginComposite = findPluginComposite((Composite) tabControl); + if (pluginComposite != null) { + ConfigPluginOptionsTab.showConfigPluginSettings(pluginName, pluginComposite); + + // Re-apply highlighting if there's an active search + if (currentSearchText != null && !currentSearchText.trim().isEmpty()) { + applyHighlightingToCurrentTab(); + } + } + } + } + } + } else { + // Regular category item (with highlighting) + String categoryName = selectedItem.getText(); + showCategory(categoryName, true); + + // If it's the Plugins category (parent), show instructions + if (categoryName.equalsIgnoreCase("Plugins") || categoryName.contains("plugin")) { + CTabItem pluginTab = categoryTabs.get(categoryName); + if (pluginTab != null) { + Control tabControl = pluginTab.getControl(); + if (tabControl instanceof Composite) { + Composite pluginComposite = findPluginComposite((Composite) tabControl); + if (pluginComposite != null) { + ConfigPluginOptionsTab.showPluginInstructions(pluginComposite); + } + } + } + } + } + } + }); + + // Right side: Content area - just use the tab folder directly + configTabs = new CTabFolder(sashForm, SWT.BORDER); PropsUi.setLook(configTabs, Props.WIDGET_STYLE_TAB); - configTabs.setMaximized(true); + // Hide the tab bar, we'll use the tree for navigation + configTabs.setTabHeight(0); + + // Load all setting tabs + loadSettingCategories(); + + // Set sash weights (20% tree, 80% content) + sashForm.setWeights(new int[] {20, 80}); + + composite.layout(); + } + + private void loadSettingCategories() { GuiRegistry guiRegistry = GuiRegistry.getInstance(); List tabsList = guiRegistry.getGuiTabsMap().get(CONFIG_PERSPECTIVE_TABS); if (tabsList != null) { tabsList.sort(Comparator.comparing(GuiTabItem::getId)); + + // Invoke all tab methods to populate configTabs for (GuiTabItem tabItem : tabsList) { try { Object object = tabItem.getMethod().getDeclaringClass().getConstructor().newInstance(); @@ -121,17 +266,509 @@ public void initialize(HopGui hopGui, Composite parent) { e); } } - if (configTabs.getItemCount() > 0) { - configTabs.setSelection(0); + + // Now create tree items for each tab + CTabItem[] allTabs = configTabs.getItems(); + for (CTabItem tab : allTabs) { + String categoryName = tab.getText(); + + // Check if this is the Plugins tab - we'll handle it specially + if (categoryName.equalsIgnoreCase("Plugins") || categoryName.contains("plugin")) { + // Create parent Plugins tree item + pluginsTreeItem = new TreeItem(categoryTree, SWT.NONE); + pluginsTreeItem.setText(categoryName); + if (tab.getImage() != null) { + pluginsTreeItem.setImage(tab.getImage()); + } + + // Store the tab for the parent item + categoryTabs.put(categoryName, tab); + + // Try to find plugin sub-items by looking at the tab's control + // If it's a ConfigPluginOptionsTab, it has a list of plugins + expandPluginsIntoTree(tab, pluginsTreeItem); + } else { + // Regular tree item + TreeItem treeItem = new TreeItem(categoryTree, SWT.NONE); + treeItem.setText(categoryName); + if (tab.getImage() != null) { + treeItem.setImage(tab.getImage()); + } + + // Store mapping from category name to tab + categoryTabs.put(categoryName, tab); + } + } + + // Select first item by default + if (categoryTree.getItemCount() > 0) { + categoryTree.setSelection(categoryTree.getItem(0)); + showCategory(categoryTree.getItem(0).getText()); + } + } + } + + private void expandPluginsIntoTree(CTabItem pluginTab, TreeItem parentItem) { + // Store the parent tab mapping + categoryTabs.put(parentItem.getText(), pluginTab); + + // Get plugin names from ConfigPluginOptionsTab + java.util.Set pluginNames = ConfigPluginOptionsTab.getPluginNames(); + + if (pluginNames != null && !pluginNames.isEmpty()) { + // Create a tree item for each plugin + for (String pluginName : pluginNames) { + TreeItem pluginItem = new TreeItem(parentItem, SWT.NONE); + pluginItem.setText(pluginName); + + // Store the plugin name for later use + pluginItem.setData("pluginName", pluginName); + pluginItem.setData("isPlugin", true); + } + + // Expand the parent by default + parentItem.setExpanded(true); + } + } + + private void showCategory(String categoryName) { + showCategory(categoryName, true); + } + + private void showCategory(String categoryName, boolean applyHighlighting) { + // Find the corresponding tab + CTabItem tab = categoryTabs.get(categoryName); + if (tab != null && !tab.isDisposed()) { + configTabs.setSelection(tab); + configTabs.layout(); + + // Re-apply highlighting if there's an active search and it's requested + if (applyHighlighting && currentSearchText != null && !currentSearchText.trim().isEmpty()) { + applyHighlightingToCurrentTab(); + } + } + } + + private void filterSettings(String searchText) { + // Store the current search text + currentSearchText = searchText != null ? searchText : ""; + + // Clear previous highlights + clearHighlights(); + + if (searchText == null || searchText.trim().isEmpty()) { + // Show all categories + for (TreeItem item : categoryTree.getItems()) { + item.setForeground(null); + // Reset children too + for (TreeItem child : item.getItems()) { + child.setForeground(null); + } + } + return; + } + + final String lowerSearch = searchText.toLowerCase(); + Color grayColor = hopGui.getDisplay().getSystemColor(SWT.COLOR_GRAY); + + TreeItem firstMatch = null; + + // Search through all categories and their content + for (TreeItem item : categoryTree.getItems()) { + String categoryName = item.getText().toLowerCase(); + boolean categoryMatches = categoryName.contains(lowerSearch); + boolean hasMatchingContent = false; + + // Check if category name matches + if (categoryMatches) { + item.setForeground(null); + if (firstMatch == null) { + firstMatch = item; + } + } + + // Search within the category's content + CTabItem tab = categoryTabs.get(item.getText()); + if (tab != null && !tab.isDisposed()) { + Control control = tab.getControl(); + if (control instanceof Composite) { + List matches = searchInComposite((Composite) control, lowerSearch); + if (!matches.isEmpty()) { + hasMatchingContent = true; + if (firstMatch == null) { + firstMatch = item; + } + + // Highlight the matched controls + for (Control match : matches) { + highlightControl(match, highlightColor); + } + } + } + } + + // Handle plugin sub-items - need to search their content too + boolean hasMatchingPlugin = false; + for (TreeItem childItem : item.getItems()) { + String pluginName = childItem.getText(); + String pluginNameLower = pluginName.toLowerCase(); + boolean pluginMatches = pluginNameLower.contains(lowerSearch); + + // Also search within the plugin's content + boolean pluginContentMatches = searchInPluginContent(pluginName, lowerSearch); + + if (pluginMatches || pluginContentMatches) { + childItem.setForeground(null); + hasMatchingPlugin = true; + if (firstMatch == null) { + firstMatch = childItem; + } + } else { + childItem.setForeground(grayColor); + } + } + + // Set tree item appearance based on matches + if (categoryMatches || hasMatchingContent || hasMatchingPlugin) { + item.setForeground(null); + item.setExpanded(true); // Expand if has matching children + } else { + item.setForeground(grayColor); } } - configTabs.layout(); + // Navigate to first match + if (firstMatch != null) { + categoryTree.setSelection(firstMatch); + categoryTree.showSelection(); + + // Trigger selection to show the content + Event event = new Event(); + event.item = firstMatch; + categoryTree.notifyListeners(SWT.Selection, event); + + // After navigation, search and highlight in the displayed content + // Use timerExec with delay to ensure plugin content is fully loaded + hopGui + .getDisplay() + .timerExec( + 100, // 100ms delay to allow plugin content to render + () -> { + // Get the currently displayed tab + CTabItem selectedTab = configTabs.getSelection(); + if (selectedTab != null && !selectedTab.isDisposed()) { + Control control = selectedTab.getControl(); + if (control instanceof Composite && !control.isDisposed()) { + List matches = searchInComposite((Composite) control, lowerSearch); + for (Control match : matches) { + if (!match.isDisposed()) { + highlightControl(match, highlightColor); + } + } + // Force a redraw to show highlights + if (!control.isDisposed()) { + control.redraw(); + } + } + } + }); + } + } + + private boolean searchInPluginContent(String pluginName, String searchText) { + // Create a temporary composite to load the plugin's settings + Composite tempComposite = new Composite(composite, SWT.NONE); + tempComposite.setLayout(new FillLayout()); + + try { + // Load the plugin settings into the temp composite + ConfigPluginOptionsTab.showConfigPluginSettings(pluginName, tempComposite); + + // Force layout to ensure all widgets are created + tempComposite.layout(true, true); + + // Search within the composite + List matches = searchInComposite(tempComposite, searchText); + + // Clean up + tempComposite.dispose(); + + return !matches.isEmpty(); + } catch (Exception e) { + // If there's an error loading the plugin, assume no match + if (!tempComposite.isDisposed()) { + tempComposite.dispose(); + } + return false; + } + } + + private List searchInComposite(Composite composite, String searchText) { + List matches = new ArrayList<>(); + + if (composite == null || composite.isDisposed()) { + return matches; + } + + try { + for (Control control : composite.getChildren()) { + if (control == null || control.isDisposed()) { + continue; + } + + boolean controlMatches = false; + + // Check labels + if (control instanceof Label) { + Label label = (Label) control; + String text = label.getText(); + if (text != null && text.toLowerCase().contains(searchText)) { + matches.add(control); + controlMatches = true; + } + } + // Check text fields + else if (control instanceof Text) { + Text text = (Text) control; + String value = text.getText(); + if (value != null && value.toLowerCase().contains(searchText)) { + matches.add(control); + controlMatches = true; + } + } + // Check buttons + else if (control instanceof Button button) { + String text = button.getText(); + if (text != null && text.toLowerCase().contains(searchText)) { + matches.add(control); + controlMatches = true; + } + } + // Check tables (both SWT Table and Hop TableView) + else if (control instanceof Table table) { + // Search through all table items + for (TableItem item : table.getItems()) { + for (int i = 0; i < table.getColumnCount(); i++) { + String cellText = item.getText(i); + if (cellText != null && cellText.toLowerCase().contains(searchText)) { + matches.add(control); + controlMatches = true; + break; + } + } + if (controlMatches) { + break; + } + } + } + // Check Hop's TableView widget + else if (control instanceof TableView tableView) { + Table table = tableView.table; + // Search through all table items + for (TableItem item : table.getItems()) { + for (int i = 0; i < table.getColumnCount(); i++) { + String cellText = item.getText(i); + if (cellText != null && cellText.toLowerCase().contains(searchText)) { + matches.add(control); + controlMatches = true; + break; + } + } + if (controlMatches) { + break; + } + } + } + + // Check tooltips + if (!controlMatches) { + String tooltip = control.getToolTipText(); + if (tooltip != null && tooltip.toLowerCase().contains(searchText)) { + matches.add(control); + controlMatches = true; + } + } + + // Recursively search in child composites (including ScrolledComposite) + if (control instanceof ScrolledComposite sc) { + Control content = sc.getContent(); + if (content instanceof Composite composite1) { + matches.addAll(searchInComposite(composite1, searchText)); + } + } else if (control instanceof Composite composite1) { + matches.addAll(searchInComposite(composite1, searchText)); + } + } + } catch (Exception e) { + // Silently ignore any errors during search + } + + return matches; + } + + private void highlightControl(Control control, Color highlightColor) { + if (control == null || control.isDisposed()) { + return; + } + + // Special handling for tables - highlight matching rows + if (control instanceof Table table) { + highlightTableRows(table, highlightColor); + return; + } else if (control instanceof TableView tableView) { + highlightTableRows(tableView.table, highlightColor); + return; + } + + // Store original background if not already stored + if (!originalBackgrounds.containsKey(control)) { + originalBackgrounds.put(control, control.getBackground()); + } + + // Store original font if not already stored + if (!originalFonts.containsKey(control) && control.getFont() != null) { + originalFonts.put(control, control.getFont()); + } + + // Apply highlight + control.setBackground(highlightColor); + + // Make text bold if it's a label + if (control instanceof Label || control instanceof Text) { + Font currentFont = control.getFont(); + if (currentFont != null) { + FontData[] fontData = currentFont.getFontData(); + for (FontData fd : fontData) { + fd.setStyle(fd.getStyle() | SWT.BOLD); + } + Font boldFont = new Font(control.getDisplay(), fontData); + control.setFont(boldFont); + } + } + + highlightedControls.add(control); + } + + private void highlightTableRows(Table table, Color highlightColor) { + if (table == null + || table.isDisposed() + || currentSearchText == null + || currentSearchText.trim().isEmpty()) { + return; + } + + String lowerSearch = currentSearchText.toLowerCase(); + + // Search through all table items and highlight matching rows + for (TableItem item : table.getItems()) { + boolean rowMatches = false; + + // Check all columns in the row + for (int i = 0; i < table.getColumnCount(); i++) { + String cellText = item.getText(i); + if (cellText != null && cellText.toLowerCase().contains(lowerSearch)) { + rowMatches = true; + break; + } + } + + // Highlight the row if it matches + if (rowMatches) { + // Store original background using the table as key (we'll restore all rows when clearing) + if (!originalBackgrounds.containsKey(table)) { + originalBackgrounds.put(table, table.getBackground()); + } + + item.setBackground(highlightColor); + + // Track this table for cleanup + if (!highlightedControls.contains(table)) { + highlightedControls.add(table); + } + } + } + } + + private void clearHighlights() { + for (Control control : highlightedControls) { + if (!control.isDisposed()) { + // Special handling for tables - need to clear all row backgrounds + if (control instanceof Table table) { + for (TableItem item : table.getItems()) { + if (!item.isDisposed()) { + item.setBackground(null); // Reset to default + } + } + } else { + // Restore original background + Color originalBg = originalBackgrounds.get(control); + if (originalBg != null) { + control.setBackground(originalBg); + } + + // Restore original font + Font originalFont = originalFonts.get(control); + if (originalFont != null) { + // Dispose the bold font before restoring + Font currentFont = control.getFont(); + control.setFont(originalFont); + if (currentFont != null && !currentFont.equals(originalFont)) { + currentFont.dispose(); + } + } + } + } + } + + highlightedControls.clear(); + originalBackgrounds.clear(); + originalFonts.clear(); + } + + private void applyHighlightingToCurrentTab() { + if (currentSearchText == null || currentSearchText.trim().isEmpty()) { + return; + } + + final String lowerSearch = currentSearchText.toLowerCase(); + + // Use timerExec with delay to ensure content is fully loaded (important for plugin content) + hopGui + .getDisplay() + .timerExec( + 100, // 100ms delay to allow plugin content to render + () -> { + // Clear existing highlights first + clearHighlights(); + + // Get the currently displayed tab + CTabItem selectedTab = configTabs.getSelection(); + if (selectedTab != null && !selectedTab.isDisposed()) { + Control control = selectedTab.getControl(); + if (control instanceof Composite composite1 && !control.isDisposed()) { + List matches = searchInComposite(composite1, lowerSearch); + for (Control match : matches) { + if (!match.isDisposed()) { + highlightControl(match, highlightColor); + } + } + // Force a redraw to show highlights + if (!control.isDisposed()) { + control.redraw(); + } + } + } + }); } public void showSystemVariablesTab() { - for (CTabItem tabItem : configTabs.getItems()) { - // Do nothing + // Navigate to system variables in the tree + for (TreeItem item : categoryTree.getItems()) { + if (item.getText().toLowerCase().contains("variable")) { + categoryTree.setSelection(item); + showCategory(item.getText()); + break; + } } } @@ -140,6 +777,28 @@ public Control getControl() { return composite; } + private Composite findPluginComposite(Composite parent) { + // Recursively search for the plugin composite + for (Control child : parent.getChildren()) { + if (child instanceof Composite composite) { + // Check if this is the plugin composite by checking its layout data + Object layoutData = composite.getLayoutData(); + if (layoutData instanceof FormData fd) { + // The plugin composite has specific layout characteristics + if (fd.left != null && fd.right != null && fd.top != null && fd.bottom != null) { + return composite; + } + } + // Recursively search children + Composite found = findPluginComposite(composite); + if (found != null) { + return found; + } + } + } + return null; + } + @Override public List getSearchables() { return new ArrayList<>(); diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigPluginOptionsTab.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigPluginOptionsTab.java index d1124b0530..d62e7a7ef0 100644 --- a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigPluginOptionsTab.java +++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/configuration/tabs/ConfigPluginOptionsTab.java @@ -19,6 +19,8 @@ package org.apache.hop.ui.hopgui.perspective.configuration.tabs; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; import org.apache.hop.core.Const; import org.apache.hop.core.config.plugin.ConfigPluginType; import org.apache.hop.core.gui.plugin.GuiPlugin; @@ -43,7 +45,6 @@ import org.eclipse.swt.layout.FormLayout; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.List; import org.eclipse.swt.widgets.Shell; @GuiPlugin @@ -51,7 +52,7 @@ public class ConfigPluginOptionsTab { public static final String GUI_WIDGETS_PARENT_ID = "EnterOptionsDialog-GuiWidgetsParent"; - private List wPluginsList; + private static Map pluginDataMap = new HashMap<>(); private Composite wPluginComposite; public ConfigPluginOptionsTab() { @@ -80,31 +81,21 @@ public void addPluginOptionsTab(CTabFolder wTabFolder) { lookLayout.marginHeight = PropsUi.getFormMargin(); wPluginsTabComp.setLayout(lookLayout); - // Plugins list on the left + // A composite to show settings for a plugin - takes full width // - wPluginsList = new List(wPluginsTabComp, SWT.SINGLE | SWT.LEFT | SWT.BORDER); - PropsUi.setLook(wPluginsList); - FormData fdPluginsList = new FormData(); - fdPluginsList.left = new FormAttachment(0, 0); - fdPluginsList.right = new FormAttachment(20, 0); - fdPluginsList.top = new FormAttachment(0, 0); - fdPluginsList.bottom = new FormAttachment(100, 0); - wPluginsList.setLayoutData(fdPluginsList); - - // A composite to show settings for a plugin on the right. - // - wPluginComposite = new Composite(wPluginsTabComp, SWT.NO_BACKGROUND | SWT.BORDER); + wPluginComposite = new Composite(wPluginsTabComp, SWT.NO_BACKGROUND); PropsUi.setLook(wPluginComposite); FormData fdPluginComposite = new FormData(); - fdPluginComposite.left = new FormAttachment(wPluginsList, margin); + fdPluginComposite.left = new FormAttachment(0, 0); fdPluginComposite.right = new FormAttachment(100, 0); fdPluginComposite.top = new FormAttachment(0, 0); fdPluginComposite.bottom = new FormAttachment(100, 0); wPluginComposite.setLayoutData(fdPluginComposite); wPluginComposite.setLayout(new FormLayout()); - // Add the list of configuration plugins + // Load all configuration plugins and store their data // + pluginDataMap.clear(); PluginRegistry pluginRegistry = PluginRegistry.getInstance(); java.util.List configPlugins = pluginRegistry.getPlugins(ConfigPluginType.class); for (IPlugin configPlugin : configPlugins) { @@ -118,15 +109,13 @@ public void addPluginOptionsTab(CTabFolder wTabFolder) { Object sourceData = method.invoke(null, (Object[]) null); // This config plugin is also a GUI plugin - // Add a tab + // Store the plugin data in the map // String name = Const.NVL( TranslateUtil.translate(annotation.description(), emptySourceData.getClass()), ""); - wPluginsList.add(name); - wPluginsList.setData(name, sourceData); - wPluginsList.addListener(SWT.Selection, e -> showConfigPluginSettings()); + pluginDataMap.put(name, sourceData); } } catch (Exception e) { new ErrorDialog( @@ -138,7 +127,8 @@ public void addPluginOptionsTab(CTabFolder wTabFolder) { } } - wPluginsList.select(new int[] {}); + // Show a helpful message when no plugin is selected + showPluginInstructions(wPluginComposite); FormData fdPluginsTabComp = new FormData(); fdPluginsTabComp.left = new FormAttachment(0, 0); @@ -150,23 +140,24 @@ public void addPluginOptionsTab(CTabFolder wTabFolder) { wPluginsTab.setControl(wPluginsTabComp); } - /** Someone selected the settings of a plugin to edit */ - private void showConfigPluginSettings() { - int index = wPluginsList.getSelectionIndex(); - if (index < 0) { + /** + * Show settings for a specific plugin. This is called from the tree selection in + * ConfigurationPerspective. + */ + public static void showConfigPluginSettings(String pluginName, Composite targetComposite) { + Object pluginSourceData = pluginDataMap.get(pluginName); + if (pluginSourceData == null) { return; } - String name = wPluginsList.getSelection()[0]; - Object pluginSourceData = wPluginsList.getData(name); - // Delete everything - for (Control child : wPluginComposite.getChildren()) { + // Delete everything in the target composite + for (Control child : targetComposite.getChildren()) { child.dispose(); } // Rebuild ScrolledComposite sPluginsComp = - new ScrolledComposite(wPluginComposite, SWT.V_SCROLL | SWT.H_SCROLL); + new ScrolledComposite(targetComposite, SWT.V_SCROLL | SWT.H_SCROLL); sPluginsComp.setLayout(new FormLayout()); FormData fdsPluginsComp = new FormData(); fdsPluginsComp.left = new FormAttachment(0, 0); @@ -179,7 +170,7 @@ private void showConfigPluginSettings() { PropsUi.setLook(wPluginsComp); wPluginsComp.setLayout(new FormLayout()); - wPluginComposite.layout(); + targetComposite.layout(); GuiCompositeWidgets compositeWidgets = new GuiCompositeWidgets(HopGui.getInstance().getVariables()); @@ -203,6 +194,52 @@ private void showConfigPluginSettings() { sPluginsComp.setMinWidth(bounds.width); sPluginsComp.setMinHeight(bounds.height); - wPluginComposite.layout(true, true); + targetComposite.layout(true, true); + } + + /** Get all available plugin names for tree population */ + public static java.util.Set getPluginNames() { + return pluginDataMap.keySet(); + } + + /** Show instruction message when no plugin is selected */ + public static void showPluginInstructions(Composite targetComposite) { + // Clear the composite + for (Control child : targetComposite.getChildren()) { + child.dispose(); + } + + // Create a centered label with instructions + Composite instructionsComp = new Composite(targetComposite, SWT.NONE); + PropsUi.setLook(instructionsComp); + instructionsComp.setLayout(new FormLayout()); + + FormData fdInstructions = new FormData(); + fdInstructions.left = new FormAttachment(0, 0); + fdInstructions.right = new FormAttachment(100, 0); + fdInstructions.top = new FormAttachment(0, 0); + fdInstructions.bottom = new FormAttachment(100, 0); + instructionsComp.setLayoutData(fdInstructions); + + org.eclipse.swt.widgets.Label instructionLabel = + new org.eclipse.swt.widgets.Label(instructionsComp, SWT.CENTER | SWT.WRAP); + instructionLabel.setText( + "Select a plugin from the tree on the left to configure its settings."); + PropsUi.setLook(instructionLabel); + instructionLabel.setForeground( + instructionsComp.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY)); + + FormData fdLabel = new FormData(); + fdLabel.left = new FormAttachment(20, 0); + fdLabel.right = new FormAttachment(80, 0); + fdLabel.top = new FormAttachment(40, 0); + instructionLabel.setLayoutData(fdLabel); + + targetComposite.layout(true, true); + } + + /** Get the plugin composite for a tab item */ + public Composite getPluginComposite() { + return wPluginComposite; } } diff --git a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties index 8882e90c4b..a4b41f9c0a 100644 --- a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties +++ b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_en_US.properties @@ -111,7 +111,7 @@ EnterOptionsDialog.HideMenuBar.Label=Hide the menu bar EnterOptionsDialog.HideMenuBar.ToolTip=Enabling this option hides the menu bar from the user interface. EnterOptionsDialog.IconSize.Label=Icon size in workspace EnterOptionsDialog.LineWidth.Label=Line width on workspace -EnterOptionsDialog.LookAndFeel.Label=Look && Feel +EnterOptionsDialog.LookAndFeel.Label=Look & Feel EnterOptionsDialog.MaxExecutionLoggingTextSizeSize.Label=Maximum execution logging text size EnterOptionsDialog.MaxExecutionLoggingTextSizeSize.ToolTip=The maximum size of the log text shown in the execution information perspective to prevent out of memory errors. EnterOptionsDialog.NoteFont.Label=Font for notes diff --git a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_it_IT.properties b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_it_IT.properties index 0284e69db5..9b59ff33f0 100644 --- a/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_it_IT.properties +++ b/ui/src/main/resources/org/apache/hop/ui/core/dialog/messages/messages_it_IT.properties @@ -116,7 +116,7 @@ EnterOptionsDialog.MaxExecutionLoggingTextSizeSize.Label=Massima lunghezza del l EnterOptionsDialog.MaxExecutionLoggingTextSizeSize.ToolTip=Imposta la lunghezza massima del testo mostrato nella execution information perspective in maniera da evitare errori di memoria esaurita (out of memory). EnterOptionsDialog.NoteFont.Label=Font per le note\: EnterOptionsDialog.OpenLastFileStartup.Label=Aprire l''ultimo file all''avvio di Hop? -EnterOptionsDialog.RestartWarning.DialogMessage=Per favore riavvia la Hop GUI per applicare le modifiche al look && feel. +EnterOptionsDialog.RestartWarning.DialogMessage=Per favore riavvia la Hop GUI per applicare le modifiche al look & feel. EnterOptionsDialog.RestartWarning.DialogTitle=Riavvia EnterOptionsDialog.RestartWarning.Option1=Chiudi EnterOptionsDialog.RestartWarning.Option2=Per favore non mostrare ancora il messaggio