This is an automated email from the ASF dual-hosted git repository.

hansva pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/hop.git


The following commit(s) were added to refs/heads/main by this push:
     new 3f4b45230f Enable tab "Move to right" in Hop Web, fixes #8458 (#8459)
3f4b45230f is described below

commit 3f4b45230f80313350584d50d2ef7e07cc34db0c
Author: Matt Casters <[email protected]>
AuthorDate: Sat Sep 19 01:03:39 2026 +0200

    Enable tab "Move to right" in Hop Web, fixes #8458 (#8459)
---
 .../perspective/explorer/ExplorerPerspective.java  | 258 ++++++++++++++++++---
 .../explorer/messages/messages_en_US.properties    |   3 +
 .../explorer/messages/messages_pt_BR.properties    |   3 +
 3 files changed, 226 insertions(+), 38 deletions(-)

diff --git 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
index f8a537f101..f61d6b5a52 100644
--- 
a/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
+++ 
b/ui/src/main/java/org/apache/hop/ui/hopgui/perspective/explorer/ExplorerPerspective.java
@@ -2157,43 +2157,44 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
     new TabCloseHandler(this, folder);
     new TabItemReorder(this, folder);
 
-    // Split ("Move to Right") and detach ("Move to New Window") depend on 
native drag and floating
-    // windows, which don't work under RAP, so they are desktop-only (see also 
the drag-to-split
-    // guard in TabItemReorder and the isWeb() overlay gating).
-    if (!EnvironmentUtils.getInstance().isWeb()) {
-      Menu menu = folder.getMenu();
-      new MenuItem(menu, SWT.SEPARATOR);
-      MenuItem miSplitMove = new MenuItem(menu, SWT.NONE);
-      miSplitMove.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveToRight"));
+    // Split ("Move to Right") works in both desktop and web since it operates 
within the docked
+    // editor layout. Detach ("Move to New Window") depends on floating 
windows, which don't work
+    // under RAP, so it is desktop-only.
+    Menu menu = folder.getMenu();
+    new MenuItem(menu, SWT.SEPARATOR);
+    MenuItem miSplitMove = new MenuItem(menu, SWT.NONE);
+    miSplitMove.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveToRight"));
 
-      folder.addListener(
-          SWT.MenuDetect,
-          event -> {
-            Point pt = 
folder.toControl(folder.getDisplay().getCursorLocation());
-            splitMenuTargetTab = folder.getItem(new Point(pt.x, pt.y));
-          });
+    MenuItem miSplitDown = new MenuItem(menu, SWT.NONE);
+    miSplitDown.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveDown"));
 
-      MenuItem miDetach = new MenuItem(menu, SWT.NONE);
-      miDetach.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveToNewWindow"));
+    new MenuItem(menu, SWT.SEPARATOR);
 
-      menu.addListener(
-          SWT.Show,
-          e -> {
-            miSplitMove.setText(
-                BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveToRight"));
-            // Splitting only makes sense if the folder keeps at least one tab 
behind.
-            miSplitMove.setEnabled(splitMenuTargetTab != null && 
folder.getItemCount() > 1);
-            miDetach.setEnabled(splitMenuTargetTab != null);
-          });
+    MenuItem miJoinLeft = new MenuItem(menu, SWT.NONE);
+    miJoinLeft.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.JoinLeft"));
 
-      miSplitMove.addListener(
-          SWT.Selection,
-          e -> {
-            if (splitMenuTargetTab != null && 
!splitMenuTargetTab.isDisposed()) {
-              splitOrMoveTab(splitMenuTargetTab);
-            }
-          });
+    MenuItem miJoinAbove = new MenuItem(menu, SWT.NONE);
+    miJoinAbove.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.JoinAbove"));
+
+    folder.addListener(
+        SWT.MenuDetect,
+        event -> {
+          Point pt =
+              (event.x != 0 || event.y != 0)
+                  ? folder.toControl(event.x, event.y)
+                  : folder.toControl(folder.getDisplay().getCursorLocation());
+          CTabItem item = folder.getItem(new Point(pt.x, pt.y));
+          if (item == null) {
+            item = folder.getSelection();
+          }
+          splitMenuTargetTab = item;
+        });
 
+    final MenuItem miDetach;
+    if (!EnvironmentUtils.getInstance().isWeb()) {
+      new MenuItem(menu, SWT.SEPARATOR);
+      miDetach = new MenuItem(menu, SWT.NONE);
+      miDetach.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveToNewWindow"));
       miDetach.addListener(
           SWT.Selection,
           e -> {
@@ -2201,8 +2202,76 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
               detachTabToWindow(splitMenuTargetTab);
             }
           });
+    } else {
+      miDetach = null;
     }
 
+    menu.addListener(
+        SWT.Show,
+        e -> {
+          miSplitMove.setText(
+              BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveToRight"));
+          miSplitDown.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.MoveDown"));
+          miJoinLeft.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.JoinLeft"));
+          miJoinAbove.setText(BaseMessages.getString(PKG, 
"ExplorerPerspective.TabMenu.JoinAbove"));
+
+          // Splitting creates a new pane, requiring at least one tab to stay 
behind.
+          boolean canSplit = splitMenuTargetTab != null && 
folder.getItemCount() > 1;
+          miSplitMove.setEnabled(canSplit);
+          miSplitDown.setEnabled(canSplit);
+
+          // Joining moves the tab into an existing adjacent pane.
+          CTabFolder leftFolder = findFolderToLeft(folder);
+          miJoinLeft.setEnabled(
+              splitMenuTargetTab != null && leftFolder != null && 
!leftFolder.isDisposed());
+
+          CTabFolder aboveFolder = findFolderAbove(folder);
+          miJoinAbove.setEnabled(
+              splitMenuTargetTab != null && aboveFolder != null && 
!aboveFolder.isDisposed());
+
+          if (miDetach != null) {
+            miDetach.setEnabled(splitMenuTargetTab != null);
+          }
+        });
+
+    miSplitMove.addListener(
+        SWT.Selection,
+        e -> {
+          if (splitMenuTargetTab != null && !splitMenuTargetTab.isDisposed()) {
+            splitAndMoveTab(splitMenuTargetTab, SWT.HORIZONTAL, true);
+          }
+        });
+
+    miSplitDown.addListener(
+        SWT.Selection,
+        e -> {
+          if (splitMenuTargetTab != null && !splitMenuTargetTab.isDisposed()) {
+            splitAndMoveTab(splitMenuTargetTab, SWT.VERTICAL, true);
+          }
+        });
+
+    miJoinLeft.addListener(
+        SWT.Selection,
+        e -> {
+          if (splitMenuTargetTab != null && !splitMenuTargetTab.isDisposed()) {
+            CTabFolder leftFolder = findFolderToLeft(folder);
+            if (leftFolder != null && !leftFolder.isDisposed()) {
+              joinTabToFolder(splitMenuTargetTab, leftFolder);
+            }
+          }
+        });
+
+    miJoinAbove.addListener(
+        SWT.Selection,
+        e -> {
+          if (splitMenuTargetTab != null && !splitMenuTargetTab.isDisposed()) {
+            CTabFolder aboveFolder = findFolderAbove(folder);
+            if (aboveFolder != null && !aboveFolder.isDisposed()) {
+              joinTabToFolder(splitMenuTargetTab, aboveFolder);
+            }
+          }
+        });
+
     return folder;
   }
 
@@ -3180,6 +3249,94 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
     }
   }
 
+  /**
+   * Find the adjacent leaf tab folder immediately to the left of {@code 
folder} in the docked
+   * layout tree, or {@code null} if none exists.
+   */
+  private CTabFolder findFolderToLeft(CTabFolder folder) {
+    if (folder == null || folder.isDisposed() || !isDockedTabFolder(folder)) {
+      return null;
+    }
+    Control current = folder;
+    while (current != null && current.getParent() instanceof SashForm 
parentSash) {
+      if (parentSash.getOrientation() == SWT.HORIZONTAL) {
+        List<Control> children = nodeChildren(parentSash);
+        int idx = children.indexOf(current);
+        if (idx > 0) {
+          CTabFolder target = findRightmostLeaf(children.get(idx - 1));
+          if (target != null && !target.isDisposed()) {
+            return target;
+          }
+        }
+      }
+      current = parentSash;
+    }
+    return null;
+  }
+
+  /**
+   * Find the adjacent leaf tab folder immediately above {@code folder} in the 
docked layout tree,
+   * or {@code null} if none exists.
+   */
+  private CTabFolder findFolderAbove(CTabFolder folder) {
+    if (folder == null || folder.isDisposed() || !isDockedTabFolder(folder)) {
+      return null;
+    }
+    Control current = folder;
+    while (current != null && current.getParent() instanceof SashForm 
parentSash) {
+      if (parentSash.getOrientation() == SWT.VERTICAL) {
+        List<Control> children = nodeChildren(parentSash);
+        int idx = children.indexOf(current);
+        if (idx > 0) {
+          CTabFolder target = findBottommostLeaf(children.get(idx - 1));
+          if (target != null && !target.isDisposed()) {
+            return target;
+          }
+        }
+      }
+      current = parentSash;
+    }
+    return null;
+  }
+
+  private CTabFolder findRightmostLeaf(Control node) {
+    if (node == null || node.isDisposed()) {
+      return null;
+    }
+    if (node instanceof CTabFolder folder) {
+      return folder;
+    }
+    if (node instanceof SashForm sash) {
+      List<Control> children = nodeChildren(sash);
+      for (int i = children.size() - 1; i >= 0; i--) {
+        CTabFolder leaf = findRightmostLeaf(children.get(i));
+        if (leaf != null && !leaf.isDisposed()) {
+          return leaf;
+        }
+      }
+    }
+    return null;
+  }
+
+  private CTabFolder findBottommostLeaf(Control node) {
+    if (node == null || node.isDisposed()) {
+      return null;
+    }
+    if (node instanceof CTabFolder folder) {
+      return folder;
+    }
+    if (node instanceof SashForm sash) {
+      List<Control> children = nodeChildren(sash);
+      for (int i = children.size() - 1; i >= 0; i--) {
+        CTabFolder leaf = findBottommostLeaf(children.get(i));
+        if (leaf != null && !leaf.isDisposed()) {
+          return leaf;
+        }
+      }
+    }
+    return null;
+  }
+
   @GuiMenuElement(
       root = GUI_PLUGIN_CONTEXT_MENU_PARENT_ID,
       parentId = GUI_PLUGIN_CONTEXT_MENU_PARENT_ID,
@@ -5205,24 +5362,45 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
 
   /** Split the tab's folder to the right and move the tab into the new pane. 
*/
   private void splitOrMoveTab(CTabItem tab) {
+    splitAndMoveTab(tab, SWT.HORIZONTAL, true);
+  }
+
+  private void splitAndMoveTab(CTabItem tab, int orientation, boolean after) {
+    if (tab == null || tab.isDisposed()) {
+      return;
+    }
     CTabFolder sourceFolder = tab.getParent();
+    CTabFolder targetFolder = splitFolder(sourceFolder, orientation, after);
+    joinTabToFolder(tab, targetFolder);
+  }
 
-    CTabFolder targetFolder = splitFolder(sourceFolder, SWT.HORIZONTAL, true);
+  private void joinTabToFolder(CTabItem tab, CTabFolder targetFolder) {
+    if (tab == null || tab.isDisposed() || targetFolder == null || 
targetFolder.isDisposed()) {
+      return;
+    }
+    CTabFolder sourceFolder = tab.getParent();
 
     moveTabToFolder(tab, targetFolder);
     targetFolder.setSelection(targetFolder.getItemCount() - 1);
     activeTabFolder = targetFolder;
 
-    // If the source pane emptied out (shouldn't normally happen, the menu 
requires >1 tab),
-    // collapse.
-    collapseFolder(sourceFolder);
+    reclaimFolder(sourceFolder);
+
+    if (sourceFolder != null && !sourceFolder.isDisposed()) {
+      sourceFolder.layout(true, true);
+    }
+    if (!targetFolder.isDisposed()) {
+      targetFolder.layout(true, true);
+    }
 
-    // Match what addPipeline/addWorkflow do after setSelection: give focus to 
the moved tab's
-    // control and refresh the GUI so toolbar/menu state is up to date.
     CTabItem sel = targetFolder.getSelection();
     if (sel != null && sel.getControl() != null && 
!sel.getControl().isDisposed()) {
       sel.getControl().setFocus();
     }
+    if (EnvironmentUtils.getInstance().isWeb()) {
+      notifyZoomHandlerForActiveTab();
+      updateWebUrlForActiveTab();
+    }
     updateGui();
   }
 
@@ -5230,6 +5408,10 @@ public class ExplorerPerspective implements 
IHopPerspective, TabClosable, IFileD
   public void onTabMovedBetweenFolders(CTabFolder sourceFolder, CTabFolder 
targetFolder) {
     activeTabFolder = targetFolder;
     reclaimFolder(sourceFolder);
+    if (EnvironmentUtils.getInstance().isWeb()) {
+      notifyZoomHandlerForActiveTab();
+      updateWebUrlForActiveTab();
+    }
   }
 
   @Override
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
index 4a2ab6e761..c6293128ab 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_en_US.properties
@@ -72,6 +72,9 @@ 
ExplorerPerspective.ToolbarElement.CollapseAll.Tooltip=Collapse all folders
 ExplorerPerspective.ToolbarElement.SelectOpenedFile.Tooltip=Select opened file
 ExplorerPerspective.ToolbarElement.OpenAsText.Label=Open as text
 ExplorerPerspective.TabMenu.MoveToRight=Move to Right
+ExplorerPerspective.TabMenu.MoveDown=Move Down
+ExplorerPerspective.TabMenu.JoinLeft=Join Left
+ExplorerPerspective.TabMenu.JoinAbove=Join Above
 ExplorerPerspective.TabMenu.MoveToLeft=Move to Left
 ExplorerPerspective.TabMenu.MoveToNewWindow=Move to New Window
 ExplorerPerspective.UpdateFileReferences.Title=Update file references
diff --git 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_pt_BR.properties
 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_pt_BR.properties
index 4f73cafa4f..78014fc1e6 100644
--- 
a/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_pt_BR.properties
+++ 
b/ui/src/main/resources/org/apache/hop/ui/hopgui/perspective/explorer/messages/messages_pt_BR.properties
@@ -61,6 +61,9 @@ ExplorerPerspective.Error.OpenFileInExplorer.Title=Erro
 ExplorerPerspective.Error.OpenFileInExplorer.Message=Erro ao abrir o arquivo 
no explorador:
 ExplorerPerspective.Menu.OpenInExplorer=Aberto em explorador
 ExplorerPerspective.TabMenu.MoveToRight=Mover para a direita
+ExplorerPerspective.TabMenu.MoveDown=Mover para baixo
+ExplorerPerspective.TabMenu.JoinLeft=Juntar \u00e0 esquerda
+ExplorerPerspective.TabMenu.JoinAbove=Juntar acima
 ExplorerPerspective.TabMenu.MoveToLeft=Mover para a esquerda
 ExplorerPerspective.UpdateFileReferences.Title=Atualizar referĂȘncias de 
arquivos
 ExplorerPerspective.UpdateFileReferences.Message=Encontrei referĂȘncias em 
arquivos de conduto ou fluxo de trabalho. Atualizar referĂȘncias no caminho novo 
?

Reply via email to