diff --git a/core/src/main/java/org/apache/hop/metadata/api/HopMetadataBase.java b/core/src/main/java/org/apache/hop/metadata/api/HopMetadataBase.java index be5b982eafa..e99b1ecbc3d 100644 --- a/core/src/main/java/org/apache/hop/metadata/api/HopMetadataBase.java +++ b/core/src/main/java/org/apache/hop/metadata/api/HopMetadataBase.java @@ -25,6 +25,9 @@ public class HopMetadataBase implements IHopMetadata { /** All metadata objects have a name to uniquely identify it. */ @HopMetadataProperty protected String name; + /** All metadata objects can have a virtual path to organize them */ + @HopMetadataProperty protected String virtualPath; + /** * The metadata provider name is optionally used at runtime to figure out where the metadata came * from. Optionally used by plugins. It's volatile because it's never persisted. @@ -36,6 +39,13 @@ public HopMetadataBase() {} public HopMetadataBase(String name) { this(); this.name = name; + this.virtualPath = ""; + } + + public HopMetadataBase(String name, String virtualPath) { + this(); + this.name = name; + this.virtualPath = virtualPath; } @Override @@ -96,4 +106,41 @@ public String getMetadataProviderName() { public void setMetadataProviderName(String metadataProviderName) { this.metadataProviderName = metadataProviderName; } + + /** + * Get the virtual path set on a metadata item + * + * @return a String representing the virtual path + */ + @Override + public String getVirtualPath() { + return virtualPath; + } + + /** + * Set the virtual path on a metadata item + * + * @param virtualPath the virtual path to set to the metadata item + */ + @Override + public void setVirtualPath(String virtualPath) { + this.virtualPath = virtualPath; + } + + /** + * Return the virtual path and name of the object + * + * @return the virtual path and name of the object + */ + @Override + public String getFullName() { + if (virtualPath == null || virtualPath.isEmpty()) { + return name; + } + if (virtualPath.endsWith("/")) { + return virtualPath + name; + } else { + return virtualPath + "/" + name; + } + } } diff --git a/core/src/main/java/org/apache/hop/metadata/api/IHopMetadata.java b/core/src/main/java/org/apache/hop/metadata/api/IHopMetadata.java index ae0f096dcf9..6e371c6a534 100644 --- a/core/src/main/java/org/apache/hop/metadata/api/IHopMetadata.java +++ b/core/src/main/java/org/apache/hop/metadata/api/IHopMetadata.java @@ -52,4 +52,23 @@ public interface IHopMetadata { * @param metadataProviderName The source of metadata or null if it's not specified */ void setMetadataProviderName(String metadataProviderName); + + /** + * @return the virtual path for organizing metadata items + */ + String getVirtualPath(); + + /** + * Set the virtual path on a metadata item + * + * @param virtualPath the virtual path to set to the metadata item + */ + void setVirtualPath(String virtualPath); + + /** + * Get the complete name of the object (virtual path + name) + * + * @return the full name of the object + */ + String getFullName(); } diff --git a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataManager.java b/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataManager.java index e0a97358373..589026d7dbd 100644 --- a/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataManager.java +++ b/ui/src/main/java/org/apache/hop/ui/core/metadata/MetadataManager.java @@ -447,7 +447,7 @@ public T newMetadata(T element) { } } - public T newMetadataWithEditor() { + public T newMetadataWithEditor(String virtualPath) { HopGui hopGui = HopGui.getInstance(); try { @@ -456,6 +456,7 @@ public T newMetadataWithEditor() { // T element = managedClass.getDeclaredConstructor().newInstance(); initializeElementVariables(element); + element.setVirtualPath(virtualPath); ExtensionPointHandler.callExtensionPoint( hopGui.getLog(), diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/context/metadata/MetadataContextHandler.java b/ui/src/main/java/org/apache/hop/ui/hopgui/context/metadata/MetadataContextHandler.java index 521751503e0..d1342a4dfa2 100644 --- a/ui/src/main/java/org/apache/hop/ui/hopgui/context/metadata/MetadataContextHandler.java +++ b/ui/src/main/java/org/apache/hop/ui/hopgui/context/metadata/MetadataContextHandler.java @@ -81,7 +81,8 @@ public List getSupportedActions() { + " : " + TranslateUtil.translate(hopMetadata.description(), metadataObjectClass), hopMetadata.image(), - (shiftClicked, controlClicked, parameters) -> metadataManager.newMetadataWithEditor()); + (shiftClicked, controlClicked, parameters) -> + metadataManager.newMetadataWithEditor("")); newAction.setClassLoader(metadataObjectClass.getClassLoader()); newAction.setCategory(CONST_METADATA); newAction.setCategoryOrder("2"); diff --git a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java index 43906a01ca6..a13b5644d3e 100644 --- a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java +++ b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/metadata/MetadataPerspective.java @@ -43,7 +43,9 @@ import org.apache.hop.ui.core.ConstUi; import org.apache.hop.ui.core.PropsUi; import org.apache.hop.ui.core.bus.HopGuiEvents; +import org.apache.hop.ui.core.dialog.EnterStringDialog; import org.apache.hop.ui.core.dialog.ErrorDialog; +import org.apache.hop.ui.core.dialog.ShowMessageDialog; import org.apache.hop.ui.core.gui.GuiResource; import org.apache.hop.ui.core.gui.GuiToolbarWidgets; import org.apache.hop.ui.core.metadata.MetadataEditor; @@ -265,38 +267,44 @@ protected void createTree(Composite parent) { // Show the menu // Menu menu = new Menu(tree); + MenuItem menuItem; + + switch ((String) treeItem.getData("type")) { + case "MetadataItem": + case "Folder": + menuItem = new MenuItem(menu, SWT.POP_UP); + menuItem.setText(BaseMessages.getString(PKG, "MetadataPerspective.Menu.New")); + menuItem.addListener(SWT.Selection, e -> onNewMetadata()); + menuItem = new MenuItem(menu, SWT.POP_UP); + menuItem.setText(BaseMessages.getString(PKG, "MetadataPerspective.Menu.NewFolder")); + menuItem.addListener(SWT.Selection, e -> createNewFolder()); + new MenuItem(menu, SWT.SEPARATOR); + break; + case "File": + menuItem = new MenuItem(menu, SWT.POP_UP); + menuItem.setText(BaseMessages.getString(PKG, "MetadataPerspective.Menu.Edit")); + menuItem.addListener(SWT.Selection, e -> onEditMetadata()); - MenuItem menuItem = new MenuItem(menu, SWT.POP_UP); - menuItem.setText("New"); - menuItem.addListener(SWT.Selection, e -> onNewMetadata()); - - new MenuItem(menu, SWT.SEPARATOR); - - if (treeItem.getParentItem() != null) { - - menuItem = new MenuItem(menu, SWT.POP_UP); - menuItem.setText("Edit"); - menuItem.addListener(SWT.Selection, e -> onEditMetadata()); - - menuItem = new MenuItem(menu, SWT.POP_UP); - menuItem.setText("Rename"); - menuItem.addListener(SWT.Selection, e -> onRenameMetadata()); + menuItem = new MenuItem(menu, SWT.POP_UP); + menuItem.setText(BaseMessages.getString(PKG, "MetadataPerspective.Menu.Rename")); + menuItem.addListener(SWT.Selection, e -> onRenameMetadata()); - menuItem = new MenuItem(menu, SWT.POP_UP); - menuItem.setText("Duplicate"); - menuItem.addListener(SWT.Selection, e -> duplicateMetadata()); + menuItem = new MenuItem(menu, SWT.POP_UP); + menuItem.setText(BaseMessages.getString(PKG, "MetadataPerspective.Menu.Duplicate")); + menuItem.addListener(SWT.Selection, e -> duplicateMetadata()); - new MenuItem(menu, SWT.SEPARATOR); + new MenuItem(menu, SWT.SEPARATOR); - menuItem = new MenuItem(menu, SWT.POP_UP); - menuItem.setText("Delete"); - menuItem.addListener(SWT.Selection, e -> onDeleteMetadata()); + menuItem = new MenuItem(menu, SWT.POP_UP); + menuItem.setText(BaseMessages.getString(PKG, "MetadataPerspective.Menu.Delete")); + menuItem.addListener(SWT.Selection, e -> onDeleteMetadata()); - new MenuItem(menu, SWT.SEPARATOR); + new MenuItem(menu, SWT.SEPARATOR); + break; } menuItem = new MenuItem(menu, SWT.POP_UP); - menuItem.setText("Help"); + menuItem.setText(BaseMessages.getString(PKG, "MetadataPerspective.Menu.Help")); menuItem.addListener(SWT.Selection, e -> onHelpMetadata()); tree.setMenu(menu); @@ -526,7 +534,7 @@ public void onNewMetadata() { metadataClass, hopGui.getShell()); - manager.newMetadataWithEditor(); + manager.newMetadataWithEditor((String) treeItem.getData("virtualPath")); hopGui.getEventsHandler().fire(HopGuiEvents.MetadataCreated.name()); } catch (Exception e) { @@ -802,6 +810,8 @@ public void refresh() { classItem.setExpanded(true); classItem.setData(annotation.key()); classItem.setData(KEY_HELP, annotation.description()); + classItem.setData("virtualPath", ""); + classItem.setData("type", "MetadataItem"); // level 1: object names // @@ -811,8 +821,48 @@ public void refresh() { Collections.sort(names); for (final String name : names) { - TreeItem item = new TreeItem(classItem, SWT.NONE); + IHopMetadata hopMetadata = serializer.load(name); + TreeItem parentItem = classItem; + + if (hopMetadata.getVirtualPath() != null && !hopMetadata.getVirtualPath().isEmpty()) { + List folders = + new ArrayList<>(Arrays.asList(hopMetadata.getVirtualPath().split("/"))); + // remove empty elements + folders.removeAll(Arrays.asList("", null)); + + for (String folder : folders) { + TreeItem alreadyExists = null; + if (!folder.isEmpty()) { + // check if folder already exists on this level + alreadyExists = null; + for (TreeItem childItem : parentItem.getItems()) { + if (childItem.getData("type").equals("Folder") + && childItem.getText().equals(folder)) { + alreadyExists = childItem; + } + } + + if (alreadyExists != null) { + parentItem = alreadyExists; + } else { + TreeItem folderItem = new TreeItem(parentItem, SWT.NONE); + folderItem.setText(folder); + folderItem.setData(annotation.key()); + folderItem.setImage(GuiResource.getInstance().getImageFolder()); + folderItem.setData( + "virtualPath", + folderItem.getParentItem().getData("virtualPath") + "/" + folder); + folderItem.setData("type", "Folder"); + parentItem = folderItem; + } + } + } + } + + TreeItem item = new TreeItem(parentItem, SWT.NONE); item.setText(0, Const.NVL(name, "")); + item.setData("virtualPath", parentItem.getData("virtualPath")); + item.setData("type", "File"); MetadataEditor editor = this.findEditor(annotation.key(), name); if (editor != null && editor.hasChanged()) { item.setFont(GuiResource.getInstance().getFontBold()); @@ -1000,4 +1050,43 @@ public void goToElement(Class managedClass, String eleme } } } + + public void createNewFolder() { + TreeItem[] selection = tree.getSelection(); + if (selection == null || selection.length == 0) { + return; + } + TreeItem item = selection[0]; + EnterStringDialog dialog = + new EnterStringDialog( + getShell(), + "", + BaseMessages.getString(PKG, "MetadataPerspective.CreateFolder.Header"), + BaseMessages.getString( + PKG, + "MetadataPerspective.CreateFolder.Message", + (String) item.getData("virtualPath"))); + String folder = dialog.open(); + if (folder != null) { + for (TreeItem treeItem : item.getItems()) { + if (folder.equals(treeItem.getText())) { + ShowMessageDialog msgDialog = + new ShowMessageDialog( + getShell(), + SWT.ICON_INFORMATION | SWT.OK, + BaseMessages.getString(PKG, "MetadataPerspective.CreateFolder.Error.Header"), + BaseMessages.getString(PKG, "MetadataPerspective.CreateFolder.Error.Message"), + false); + msgDialog.open(); + return; + } + } + TreeItem newFolder = new TreeItem(item, SWT.NONE); + newFolder.setText(folder); + newFolder.setData(item.getData()); + newFolder.setImage(GuiResource.getInstance().getImageFolder()); + newFolder.setData("virtualPath", item.getData("virtualPath") + "/" + folder); + newFolder.setData("type", "Folder"); + } + } } diff --git a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/metadata/messages/messages_en_US.properties b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/metadata/messages/messages_en_US.properties index db077d74a60..d60c7e23651 100644 --- a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/metadata/messages/messages_en_US.properties +++ b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/metadata/messages/messages_en_US.properties @@ -31,3 +31,14 @@ MetadataPerspective.ToolbarElement.Delete.Tooltip=Delete MetadataPerspective.ToolbarElement.Edit.Tooltip=Edit MetadataPerspective.ToolbarElement.New.Tooltip=Create a new metadata element MetadataPerspective.ToolbarElement.Refresh.Tooltip=Refresh +MetadataPerspective.CreateFolder.Header=Create directory +MetadataPerspective.CreateFolder.Message=Please enter name of the folder to create in: ''{0}'' +MetadataPerspective.CreateFolder.Error.Header=Cannot Create folder +MetadataPerspective.CreateFolder.Error.Message=Folder already exists +MetadataPerspective.Menu.New=New +MetadataPerspective.Menu.NewFolder=New Folder +MetadataPerspective.Menu.Edit=Edit +MetadataPerspective.Menu.Rename=Rename +MetadataPerspective.Menu.Duplicate=Duplicate +MetadataPerspective.Menu.Delete=Delete +MetadataPerspective.Menu.Help=Help \ No newline at end of file