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
?