diff --git a/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java b/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java index 32b761324..ad8406c46 100644 --- a/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java +++ b/jadx-gui/src/main/java/jadx/gui/events/services/RenameService.java @@ -27,10 +27,10 @@ import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JRenameNode; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.ui.codearea.CodeArea; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.CacheObject; import jadx.gui.utils.JNodeCache; import jadx.gui.utils.NLS; diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/mappings/JInputMapping.java b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/JInputMapping.java index 480bcfa7e..ac0720e9a 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/mappings/JInputMapping.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/JInputMapping.java @@ -18,9 +18,9 @@ import jadx.core.utils.files.FileUtils; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JEditableNode; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.CodeContentPanel; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; import jadx.gui.utils.ui.SimpleMenuItem; diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/mappings/RenameMappingsGui.java b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/RenameMappingsGui.java index bc2fa0bfc..cbe0e4838 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/mappings/RenameMappingsGui.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/mappings/RenameMappingsGui.java @@ -30,10 +30,10 @@ import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JRoot; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.filedialog.FileDialogWrapper; import jadx.gui.ui.filedialog.FileOpenMode; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; import jadx.gui.utils.ui.ActionHandler; diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java index 91cb2ce94..e8f59c6b0 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportNode.java @@ -19,9 +19,9 @@ import jadx.api.ICodeInfo; import jadx.api.impl.SimpleCodeInfo; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.HtmlPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.UiUtils; public class QuarkReportNode extends JNode { diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java index 552008a7c..18213572d 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/quark/QuarkReportPanel.java @@ -41,8 +41,8 @@ import jadx.core.utils.Utils; import jadx.gui.JadxWrapper; import jadx.gui.treemodel.JMethod; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.JNodeCache; import jadx.gui.utils.ui.NodeLabel; diff --git a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java index f9657afc7..5d60687fc 100644 --- a/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/plugins/script/ScriptContentPanel.java @@ -28,12 +28,12 @@ import jadx.gui.settings.JadxSettings; import jadx.gui.settings.LineNumbersMode; import jadx.gui.treemodel.JInputScript; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.action.ActionModel; import jadx.gui.ui.action.JadxGuiAction; import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.AbstractCodeContentPanel; import jadx.gui.ui.codearea.SearchBar; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java b/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java index d0bab7c43..c5fdc7ebc 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/ApkSignature.java @@ -21,9 +21,9 @@ import jadx.api.ResourceFile; import jadx.api.ResourceType; import jadx.api.impl.SimpleCodeInfo; import jadx.gui.JadxWrapper; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.HtmlPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.CertificateManager; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java index ba67fd5a7..d74498167 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JClass.java @@ -23,10 +23,10 @@ import jadx.core.dex.attributes.AFlag; import jadx.core.dex.info.AccessInfo; import jadx.core.dex.nodes.ICodeNode; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.ClassCodeContentPanel; import jadx.gui.ui.dialog.RenameDialog; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.CacheObject; import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java index a1595a8a0..d73ca3643 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JInputScript.java @@ -17,8 +17,8 @@ import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.utils.files.FileUtils; import jadx.gui.plugins.script.ScriptContentPanel; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; import jadx.gui.utils.ui.SimpleMenuItem; diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java index 2900de1cf..814a4ec61 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JNode.java @@ -16,8 +16,8 @@ import jadx.api.ICodeInfo; import jadx.api.JavaNode; import jadx.api.metadata.ICodeNodeRef; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; public abstract class JNode extends DefaultMutableTreeNode implements Comparable { diff --git a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java index 45da2f71b..f918b5504 100644 --- a/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java +++ b/jadx-gui/src/main/java/jadx/gui/treemodel/JResource.java @@ -20,11 +20,11 @@ import jadx.api.impl.SimpleCodeInfo; import jadx.core.utils.ListUtils; import jadx.core.utils.Utils; import jadx.core.xmlgen.ResContainer; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.BinaryContentPanel; import jadx.gui.ui.codearea.CodeContentPanel; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.ImagePanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java index 87961a228..046b854bb 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/MainWindow.java @@ -140,6 +140,8 @@ import jadx.gui.ui.panel.IssuesPanel; import jadx.gui.ui.panel.JDebuggerPanel; import jadx.gui.ui.panel.ProgressPanel; import jadx.gui.ui.popupmenu.RecentProjectsMenuListener; +import jadx.gui.ui.tab.TabbedPane; +import jadx.gui.ui.tab.dnd.TabDndController; import jadx.gui.ui.treenodes.StartPageNode; import jadx.gui.ui.treenodes.SummaryNode; import jadx.gui.update.JadxUpdate; @@ -1304,6 +1306,7 @@ public class MainWindow extends JFrame { tabbedPane = new TabbedPane(this); tabbedPane.setMinimumSize(new Dimension(150, 150)); + new TabDndController(tabbedPane); rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT); rightSplitPane.setTopComponent(tabbedPane); diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java index eaa0e1a2f..07107e7bf 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/AbstractCodeContentPanel.java @@ -1,8 +1,8 @@ package jadx.gui.ui.codearea; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.TabbedPane; /** * The abstract base class for a content panel that show text based code or a.g. a resource diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/BinaryContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/BinaryContentPanel.java index fcdc5fd5a..07f7c6564 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/BinaryContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/BinaryContentPanel.java @@ -10,7 +10,7 @@ import javax.swing.border.EmptyBorder; import jadx.gui.settings.JadxSettings; import jadx.gui.settings.LineNumbersMode; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.tab.TabbedPane; public class BinaryContentPanel extends AbstractCodeContentPanel { private final transient CodePanel textCodePanel; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java index 13722d84d..ad703105c 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/ClassCodeContentPanel.java @@ -17,9 +17,9 @@ import org.slf4j.LoggerFactory; import jadx.api.DecompilationMode; import jadx.gui.jobs.BackgroundExecutor; import jadx.gui.treemodel.JClass; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.mode.JCodeMode; import jadx.gui.ui.panel.IViewStateSupport; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.NLS; import static com.formdev.flatlaf.FlatClientProperties.TABBED_PANE_TRAILING_COMPONENT; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java index 913d17d16..c9f929283 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/codearea/CodeContentPanel.java @@ -7,8 +7,8 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.IViewStateSupport; +import jadx.gui.ui.tab.TabbedPane; public final class CodeContentPanel extends AbstractCodeContentPanel implements IViewStateSupport { private static final long serialVersionUID = 5310536092010045565L; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java index 2ebcedf59..53dca461d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/dialog/CommonSearchDialog.java @@ -50,9 +50,9 @@ import jadx.gui.logs.LogOptions; import jadx.gui.treemodel.JNode; import jadx.gui.treemodel.JResSearchNode; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.panel.ProgressPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.CacheObject; import jadx.gui.utils.JNodeCache; import jadx.gui.utils.JumpPosition; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java index 13a95b8fa..34261deff 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/ContentPanel.java @@ -7,7 +7,7 @@ import org.jetbrains.annotations.Nullable; import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.tab.TabbedPane; public abstract class ContentPanel extends JPanel { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java index 581906aa5..735c25352 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/HtmlPanel.java @@ -10,7 +10,7 @@ import javax.swing.JScrollPane; import jadx.gui.settings.JadxSettings; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.ui.ZoomActions; public final class HtmlPanel extends ContentPanel { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/ImagePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/ImagePanel.java index c539b2649..e1c332474 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/ImagePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/ImagePanel.java @@ -17,8 +17,8 @@ import jadx.core.utils.Utils; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.core.xmlgen.ResContainer; import jadx.gui.treemodel.JResource; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.codearea.AbstractCodeArea; +import jadx.gui.ui.tab.TabbedPane; public class ImagePanel extends ContentPanel { private static final long serialVersionUID = 4071356367073142688L; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java b/jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java index ad8c2675b..c06b7a24d 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/panel/StartPagePanel.java @@ -18,7 +18,7 @@ import javax.swing.border.TitledBorder; import jadx.gui.settings.JadxSettings; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.ui.treenodes.StartPageNode; import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java similarity index 86% rename from jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java rename to jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java index 88e704a13..6947fbe09 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabComponent.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java @@ -1,7 +1,12 @@ -package jadx.gui.ui; +package jadx.gui.ui.tab; import java.awt.FlowLayout; import java.awt.Font; +import java.awt.Point; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragSource; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.util.List; @@ -20,6 +25,7 @@ import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JEditableNode; import jadx.gui.treemodel.JNode; import jadx.gui.ui.panel.ContentPanel; +import jadx.gui.ui.tab.dnd.TabDndGestureListener; import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; import jadx.gui.utils.UiUtils; @@ -42,6 +48,9 @@ public class TabComponent extends JPanel { public void loadSettings() { label.setFont(getLabelFont()); + if (tabbedPane.getDnd() != null) { + tabbedPane.getDnd().loadSettings(); + } } private Font getLabelFont() { @@ -57,7 +66,7 @@ public class TabComponent extends JPanel { label.setFont(getLabelFont()); String toolTip = contentPanel.getTabTooltip(); if (toolTip != null) { - label.setToolTipText(toolTip); + setToolTipText(toolTip); } label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10)); label.setIcon(node.getIcon()); @@ -93,14 +102,28 @@ public class TabComponent extends JPanel { } }; addMouseListener(clickAdapter); - label.addMouseListener(clickAdapter); - closeBtn.addMouseListener(clickAdapter); + addListenerForDnd(); add(label); add(closeBtn); setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0)); } + private void addListenerForDnd() { + if (tabbedPane.getDnd() == null) { + return; + } + TabComponent comp = this; + DragGestureListener dgl = new TabDndGestureListener(tabbedPane.getDnd()) { + @Override + protected Point getDragOrigin(DragGestureEvent e) { + return SwingUtilities.convertPoint(comp, e.getDragOrigin(), tabbedPane); + } + }; + DragSource.getDefaultDragSource() + .createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_COPY_OR_MOVE, dgl); + } + private String buildTabTitle(JNode node) { String tabTitle; if (node.getRootClass() != null) { diff --git a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java similarity index 97% rename from jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java rename to jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java index 24f7f6f86..f3a391b9e 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/TabbedPane.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/TabbedPane.java @@ -1,4 +1,4 @@ -package jadx.gui.ui; +package jadx.gui.ui.tab; import java.awt.Component; import java.awt.KeyEventDispatcher; @@ -26,6 +26,7 @@ import jadx.api.metadata.annotations.NodeDeclareRef; import jadx.core.utils.exceptions.JadxRuntimeException; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; +import jadx.gui.ui.MainWindow; import jadx.gui.ui.codearea.AbstractCodeArea; import jadx.gui.ui.codearea.AbstractCodeContentPanel; import jadx.gui.ui.codearea.ClassCodeContentPanel; @@ -35,6 +36,7 @@ import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.HtmlPanel; import jadx.gui.ui.panel.IViewStateSupport; import jadx.gui.ui.panel.ImagePanel; +import jadx.gui.ui.tab.dnd.TabDndController; import jadx.gui.utils.JumpManager; import jadx.gui.utils.JumpPosition; import jadx.gui.utils.NLS; @@ -52,12 +54,17 @@ public class TabbedPane extends JTabbedPane { private transient ContentPanel curTab; private transient ContentPanel lastTab; - TabbedPane(MainWindow window) { + private transient TabDndController dnd; + + public TabbedPane(MainWindow window) { this.mainWindow = window; setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT); addMouseWheelListener(event -> { + if (dnd != null && dnd.isDragging()) { + return; + } int direction = event.getWheelRotation(); if (getTabCount() == 0 || direction == 0) { return; @@ -483,6 +490,14 @@ public class TabbedPane extends JTabbedPane { return FocusManager.getFocusedComp(); } + public TabDndController getDnd() { + return dnd; + } + + public void setDnd(TabDndController dnd) { + this.dnd = dnd; + } + private static class FocusManager implements FocusListener { private static final FocusManager INSTANCE = new FocusManager(); private static @Nullable Component focusedComp; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndController.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndController.java new file mode 100644 index 000000000..7fad1d284 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndController.java @@ -0,0 +1,323 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 TERAI Atsuhiro + * Copyright (c) 2024 Skylot + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package jadx.gui.ui.tab.dnd; + +import java.awt.Component; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.GraphicsConfiguration; +import java.awt.GraphicsDevice; +import java.awt.GraphicsEnvironment; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.dnd.DnDConstants; +import java.awt.dnd.DragSource; +import java.awt.dnd.DropTarget; +import java.awt.image.BufferedImage; +import java.util.Objects; + +import javax.swing.Icon; +import javax.swing.JButton; +import javax.swing.JTabbedPane; +import javax.swing.SwingUtilities; +import javax.swing.plaf.metal.MetalTabbedPaneUI; + +import jadx.gui.ui.tab.TabbedPane; + +public class TabDndController { + + private final transient JTabbedPane pane; + + private static final int DROP_TARGET_MARK_SIZE = 4; + private static final int SCROLL_AREA_SIZE = 30; + private static final int SCROLL_AREA_EXTRA = 30; // Making area with scroll buttons a bit bigger. + private static final String ACTION_SCROLL_FORWARD = "scrollTabsForwardAction"; + private static final String ACTION_SCROLL_BACKWARD = "scrollTabsBackwardAction"; + + private final transient TabDndGhostPane tabDndGhostPane; + protected int dragTabIndex = -1; + + protected boolean drawGhost = true; // Semi-transparent tab copy moving along with cursor. + protected boolean paintScrollTriggerAreas = false; // For debug purposes. + + protected Rectangle rectBackward = new Rectangle(); + protected Rectangle rectForward = new Rectangle(); + private boolean isDragging = false; + + public TabDndController(TabbedPane pane) { + pane.setDnd(this); + this.pane = pane; + + tabDndGhostPane = new TabDndGhostPane(this); + + new DropTarget(tabDndGhostPane, DnDConstants.ACTION_COPY_OR_MOVE, new TabDndTargetListener(this), true); + DragSource.getDefaultDragSource().createDefaultDragGestureRecognizer(pane, + DnDConstants.ACTION_COPY_OR_MOVE, + new TabDndGestureListener(this)); + } + + public static boolean isHorizontalTabPlacement(int tabPlacement) { + return tabPlacement == JTabbedPane.TOP || tabPlacement == JTabbedPane.BOTTOM; + } + + /** + * Check if dragging near edges and scroll to according + * direction through programmatically clicking system's scroll buttons. + * + * @param glassPt Cursor position in TabbedPane coordinates. + */ + public void scrollIfNeeded(Point glassPt) { + Rectangle r = getTabAreaBounds(); + boolean isHorizontal = isHorizontalTabPlacement(pane.getTabPlacement()); + + // Trying to avoid calculating two directions simultaneously. Forward first. + if (isHorizontal) { + rectForward.setBounds(r.x + r.width - SCROLL_AREA_SIZE - SCROLL_AREA_EXTRA, + r.y, + SCROLL_AREA_SIZE + SCROLL_AREA_EXTRA, + r.height); + } else { + rectForward.setBounds(r.x, + r.y + r.height - SCROLL_AREA_SIZE - SCROLL_AREA_EXTRA, + r.width, + SCROLL_AREA_SIZE + SCROLL_AREA_EXTRA); + } + rectForward = SwingUtilities.convertRectangle(pane.getParent(), rectForward, tabDndGhostPane); + if (rectForward.contains(glassPt)) { + clickScrollButton(ACTION_SCROLL_FORWARD); + } + + // Backward. + if (isHorizontal) { + rectBackward.setBounds(r.x, r.y, SCROLL_AREA_SIZE, r.height); + } else { + rectBackward.setBounds(r.x, r.y, r.width, SCROLL_AREA_SIZE); + } + rectBackward = SwingUtilities.convertRectangle(pane.getParent(), rectBackward, tabDndGhostPane); + if (rectBackward.contains(glassPt)) { + clickScrollButton(ACTION_SCROLL_BACKWARD); + } + } + + private void clickScrollButton(String actionKey) { + JButton forwardButton = null; + JButton backwardButton = null; + for (Component c : pane.getComponents()) { + if (c instanceof JButton) { + if (Objects.isNull(forwardButton)) { + forwardButton = (JButton) c; + } else { + backwardButton = (JButton) c; + break; + } + } + } + JButton scrollButton = ACTION_SCROLL_FORWARD.equals(actionKey) ? forwardButton : backwardButton; + if (scrollButton != null && scrollButton.isEnabled()) { + scrollButton.doClick(); + } + } + + /** + * Finds the tab index by cursor position. If tabs first half contains the point, + * then its index is returned. Second half means inserting at next index. + * + * @param glassPt Cursor position in TabbedPane coordinates. + * @return Tab index. + */ + protected int getTargetTabIndex(Point glassPt) { + Point tabPt = SwingUtilities.convertPoint(tabDndGhostPane, glassPt, pane); + boolean isHorizontal = isHorizontalTabPlacement(pane.getTabPlacement()); + for (int i = 0; i < pane.getTabCount(); ++i) { + Rectangle r = pane.getBoundsAt(i); + + // First half. + if (isHorizontal) { + r.width = r.width / 2 + 1; + } else { + r.height = r.height / 2 + 1; + } + if (r.contains(tabPt)) { + return i; + } + + // Second half. + if (isHorizontal) { + r.x = r.x + r.width; + } else { + r.y = r.y + r.height; + } + if (r.contains(tabPt)) { + return i + 1; + } + } + + int count = pane.getTabCount(); + if (count == 0) { + return -1; + } + Rectangle lastRect = pane.getBoundsAt(count - 1); + Point d = isHorizontal ? new Point(1, 0) : new Point(0, 1); + lastRect.translate(lastRect.width * d.x, lastRect.height * d.y); + return lastRect.contains(tabPt) ? count : -1; + } + + protected void swapTabs(int oldIdx, int newIdx) { + if (newIdx < 0 || oldIdx == newIdx) { + return; + } + final Component cmp = pane.getComponentAt(oldIdx); + final Component tab = pane.getTabComponentAt(oldIdx); + final String title = pane.getTitleAt(oldIdx); + final Icon icon = pane.getIconAt(oldIdx); + final String tip = pane.getToolTipTextAt(oldIdx); + final boolean isEnabled = pane.isEnabledAt(oldIdx); + newIdx = oldIdx > newIdx ? newIdx : (newIdx - 1); + pane.remove(oldIdx); + pane.insertTab(title, icon, cmp, tip, newIdx); + pane.setEnabledAt(newIdx, isEnabled); + if (isEnabled) { + pane.setSelectedIndex(newIdx); + } + pane.setTabComponentAt(newIdx, tab); + } + + protected void updateTargetMark(int tabIdx) { + boolean isSideNeighbor = tabIdx < 0 || dragTabIndex == tabIdx || tabIdx == dragTabIndex + 1; + if (isSideNeighbor) { + tabDndGhostPane.setTargetRect(0, 0, 0, 0); + return; + } + Rectangle boundsRect = pane.getBoundsAt(Math.max(0, tabIdx - 1)); + final Rectangle r = SwingUtilities.convertRectangle(pane, boundsRect, tabDndGhostPane); + int a = Math.min(tabIdx, 1); + if (isHorizontalTabPlacement(pane.getTabPlacement())) { + tabDndGhostPane.setTargetRect(r.x + r.width * a - DROP_TARGET_MARK_SIZE / 2, + r.y, + DROP_TARGET_MARK_SIZE, + r.height); + } else { + tabDndGhostPane.setTargetRect(r.x, + r.y + r.height * a - DROP_TARGET_MARK_SIZE / 2, + r.width, + DROP_TARGET_MARK_SIZE); + } + } + + protected void initGlassPane(Point tabPt) { + pane.getRootPane().setGlassPane(tabDndGhostPane); + if (drawGhost) { + Component c = pane.getTabComponentAt(dragTabIndex); + if (c == null) { + return; + } + Dimension d = c.getPreferredSize(); + switch (tabDndGhostPane.getGhostType()) { + case IMAGE: { + GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); + GraphicsDevice device = env.getDefaultScreenDevice(); + GraphicsConfiguration config = device.getDefaultConfiguration(); + BufferedImage image = config.createCompatibleImage(d.width, d.height, BufferedImage.TRANSLUCENT); + Graphics2D g2 = image.createGraphics(); + SwingUtilities.paintComponent(g2, c, tabDndGhostPane, 0, 0, d.width, d.height); + g2.dispose(); + tabDndGhostPane.setGhostImage(image); + pane.setTabComponentAt(dragTabIndex, c); + break; + } + case COLORFUL_RECT: { + tabDndGhostPane.setGhostSize(d); + break; + } + case NONE: + break; + } + } + Point glassPt = SwingUtilities.convertPoint(pane, tabPt, tabDndGhostPane); + tabDndGhostPane.setPoint(glassPt); + tabDndGhostPane.setVisible(true); + } + + protected Rectangle getTabAreaBounds() { + Rectangle tabbedRect = pane.getBounds(); + Rectangle compRect; + if (pane.getSelectedComponent() != null) { + compRect = pane.getSelectedComponent().getBounds(); + } else { + compRect = new Rectangle(); + } + int tabPlacement = pane.getTabPlacement(); + if (isHorizontalTabPlacement(tabPlacement)) { + tabbedRect.height = tabbedRect.height - compRect.height; + if (tabPlacement == JTabbedPane.BOTTOM) { + tabbedRect.y += compRect.y + compRect.height; + } + } else { + tabbedRect.width = tabbedRect.width - compRect.width; + if (tabPlacement == JTabbedPane.RIGHT) { + tabbedRect.x += compRect.x + compRect.width; + } + } + tabbedRect.grow(2, 2); + return tabbedRect; + } + + public void onPaintGlassPane(Graphics2D g) { + boolean isScrollLayout = pane.getTabLayoutPolicy() == JTabbedPane.SCROLL_TAB_LAYOUT; + if (isScrollLayout && paintScrollTriggerAreas) { + g.setPaint(tabDndGhostPane.getColor()); + g.fill(rectBackward); + g.fill(rectForward); + } + } + + public boolean onStartDrag(Point pt) { + setDragging(true); + int idx = pane.indexAtLocation(pt.x, pt.y); + int selIdx = pane.getSelectedIndex(); + boolean isTabRunsRotated = + !(pane.getUI() instanceof MetalTabbedPaneUI) && pane.getTabLayoutPolicy() == JTabbedPane.WRAP_TAB_LAYOUT && idx != selIdx; + dragTabIndex = isTabRunsRotated ? selIdx : idx; + if (dragTabIndex >= 0 && pane.isEnabledAt(dragTabIndex)) { + initGlassPane(pt); + return true; + } + + return false; + } + + public void loadSettings() { + tabDndGhostPane.loadSettings(); + } + + public boolean isDragging() { + return isDragging; + } + + public void setDragging(boolean dragging) { + isDragging = dragging; + } + + public TabDndGhostPane getDndGhostPane() { + return tabDndGhostPane; + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGestureListener.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGestureListener.java new file mode 100644 index 000000000..dc2b787be --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGestureListener.java @@ -0,0 +1,54 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 TERAI Atsuhiro + * Copyright (c) 2024 Skylot + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package jadx.gui.ui.tab.dnd; + +import java.awt.Point; +import java.awt.dnd.DragGestureEvent; +import java.awt.dnd.DragGestureListener; +import java.awt.dnd.DragSource; +import java.awt.dnd.InvalidDnDOperationException; + +public class TabDndGestureListener implements DragGestureListener { + + private final transient TabDndController dnd; + + public TabDndGestureListener(TabDndController dnd) { + this.dnd = dnd; + } + + @Override + public void dragGestureRecognized(DragGestureEvent e) { + Point tabPt = getDragOrigin(e); + if (!dnd.onStartDrag(tabPt)) { + return; + } + try { + e.startDrag(DragSource.DefaultMoveDrop, new TabDndTransferable(), new TabDndSourceListener(dnd)); + } catch (InvalidDnDOperationException ex) { + throw new IllegalStateException(ex); + } + } + + protected Point getDragOrigin(DragGestureEvent e) { + return e.getDragOrigin(); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGhostPane.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGhostPane.java new file mode 100644 index 000000000..e334c4ba6 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGhostPane.java @@ -0,0 +1,153 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 TERAI Atsuhiro + * Copyright (c) 2024 Skylot + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package jadx.gui.ui.tab.dnd; + +import java.awt.AlphaComposite; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.Graphics2D; +import java.awt.Insets; +import java.awt.Point; +import java.awt.Rectangle; +import java.awt.image.BufferedImage; + +import javax.swing.JComponent; +import javax.swing.UIManager; + +public class TabDndGhostPane extends JComponent { + + private final TabDndController dnd; + private final Rectangle lineRect = new Rectangle(); + private final Point location = new Point(); + private transient BufferedImage ghostImage; + private TabDndGhostType tabDndGhostType = TabDndGhostType.COLORFUL_RECT; + private Dimension ghostSize; + private Color ghostColor; + private Insets insets; + + protected TabDndGhostPane(TabDndController dnd) { + super(); + this.dnd = dnd; + loadSettings(); + } + + public void loadSettings() { + Color systemColor = UIManager.getColor("Component.focusColor"); + Color fallbackColor = new Color(0, 100, 255); + ghostColor = systemColor != null ? systemColor : fallbackColor; + + Insets ins = UIManager.getInsets("TabbedPane.tabInsets"); + insets = ins != null ? ins : new Insets(0, 0, 0, 0); + } + + public void setTargetRect(int x, int y, int width, int height) { + lineRect.setBounds(x, y, width, height); + } + + public void setGhostImage(BufferedImage ghostImage) { + this.ghostImage = ghostImage; + } + + public void setGhostSize(Dimension ghostSize) { + ghostSize.setSize(ghostSize.width + insets.left + insets.right, ghostSize.height + insets.top + insets.bottom); + this.ghostSize = ghostSize; + } + + public void setGhostType(TabDndGhostType tabDndGhostType) { + this.tabDndGhostType = tabDndGhostType; + } + + public TabDndGhostType getGhostType() { + return this.tabDndGhostType; + } + + public void setColor(Color color) { + this.ghostColor = color; + } + + public Color getColor() { + return this.ghostColor; + } + + public void setPoint(Point pt) { + this.location.setLocation(pt); + } + + @Override + public boolean isOpaque() { + return false; + } + + @Override + public void setVisible(boolean v) { + super.setVisible(v); + if (!v) { + setTargetRect(0, 0, 0, 0); + setGhostImage(null); + setGhostSize(new Dimension()); + } + } + + @Override + protected void paintComponent(Graphics g) { + Graphics2D g2 = (Graphics2D) g.create(); + dnd.onPaintGlassPane(g2); + renderMark(g2); + renderGhost(g2); + g2.dispose(); + } + + private void renderGhost(Graphics2D g) { + switch (tabDndGhostType) { + case IMAGE: { + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f)); + if (ghostImage == null) { + return; + } + double x = location.getX() - ghostImage.getWidth(this) / 2d; + double y = location.getY() - ghostImage.getHeight(this) / 2d; + g.drawImage(ghostImage, (int) x, (int) y, this); + break; + } + case COLORFUL_RECT: { + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .2f)); + if (ghostSize == null) { + return; + } + double x = location.getX() - ghostSize.getWidth() / 2d; + double y = location.getY() - ghostSize.getHeight() / 2d; + g.setPaint(ghostColor); + g.fillRect((int) x, (int) y, ghostSize.width, ghostSize.height); + break; + } + case NONE: + break; + } + } + + private void renderMark(Graphics2D g) { + g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .7f)); + g.setPaint(ghostColor); + g.fill(lineRect); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGhostType.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGhostType.java new file mode 100644 index 000000000..a65a48cab --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndGhostType.java @@ -0,0 +1,19 @@ +package jadx.gui.ui.tab.dnd; + +public enum TabDndGhostType { + /** + * Bitmap is rendered from tabs component and dragged along with cursor. + * May be impactful on performance. + */ + IMAGE, + + /** + * Colored rect of tabs size is dragged along with cursor. + */ + COLORFUL_RECT, + + /** + * Only insert mark is rendered. + */ + NONE, +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndSourceListener.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndSourceListener.java new file mode 100644 index 000000000..36dbbeac0 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndSourceListener.java @@ -0,0 +1,71 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 TERAI Atsuhiro + * Copyright (c) 2024 Skylot + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package jadx.gui.ui.tab.dnd; + +import java.awt.Component; +import java.awt.dnd.DragSource; +import java.awt.dnd.DragSourceDragEvent; +import java.awt.dnd.DragSourceDropEvent; +import java.awt.dnd.DragSourceEvent; +import java.awt.dnd.DragSourceListener; + +import javax.swing.JComponent; +import javax.swing.JRootPane; + +class TabDndSourceListener implements DragSourceListener { + + private final transient TabDndController dnd; + + public TabDndSourceListener(TabDndController dnd) { + this.dnd = dnd; + } + + @Override + public void dragEnter(DragSourceDragEvent e) { + e.getDragSourceContext().setCursor(DragSource.DefaultMoveDrop); + } + + @Override + public void dragExit(DragSourceEvent e) { + e.getDragSourceContext().setCursor(DragSource.DefaultMoveNoDrop); + } + + @Override + public void dragOver(DragSourceDragEvent e) { + } + + @Override + public void dragDropEnd(DragSourceDropEvent e) { + dnd.setDragging(false); + Component c = e.getDragSourceContext().getComponent(); + if (c instanceof JComponent) { + JRootPane rp = ((JComponent) c).getRootPane(); + if (rp.getGlassPane() != null) { + rp.getGlassPane().setVisible(false); + } + } + } + + @Override + public void dropActionChanged(DragSourceDragEvent e) { + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndTargetListener.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndTargetListener.java new file mode 100644 index 000000000..e531d10fb --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndTargetListener.java @@ -0,0 +1,102 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 TERAI Atsuhiro + * Copyright (c) 2024 Skylot + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package jadx.gui.ui.tab.dnd; + +import java.awt.Point; +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; +import java.awt.dnd.DropTargetDragEvent; +import java.awt.dnd.DropTargetDropEvent; +import java.awt.dnd.DropTargetEvent; +import java.awt.dnd.DropTargetListener; + +class TabDndTargetListener implements DropTargetListener { + private static final Point HIDDEN_POINT = new Point(0, -1000); + + private final transient TabDndController dnd; + + public TabDndTargetListener(TabDndController dnd) { + this.dnd = dnd; + } + + @Override + public void dragEnter(DropTargetDragEvent e) { + TabDndGhostPane pane = dnd.getDndGhostPane(); + if (pane == null || e.getDropTargetContext().getComponent() != pane) { + return; + } + Transferable t = e.getTransferable(); + DataFlavor[] f = e.getCurrentDataFlavors(); + if (t.isDataFlavorSupported(f[0])) { + e.acceptDrag(e.getDropAction()); + } else { + e.rejectDrag(); + } + } + + @Override + public void dragExit(DropTargetEvent e) { + TabDndGhostPane pane = dnd.getDndGhostPane(); + if (pane == null || e.getDropTargetContext().getComponent() != pane) { + return; + } + pane.setPoint(HIDDEN_POINT); + pane.setTargetRect(0, 0, 0, 0); + pane.repaint(); + } + + @Override + public void dropActionChanged(DropTargetDragEvent e) { + } + + @Override + public void dragOver(DropTargetDragEvent e) { + TabDndGhostPane pane = dnd.getDndGhostPane(); + if (pane == null || e.getDropTargetContext().getComponent() != pane) { + return; + } + Point glassPt = e.getLocation(); + dnd.updateTargetMark(dnd.getTargetTabIndex(glassPt)); + dnd.scrollIfNeeded(glassPt); // backward and forward scrolling + pane.setPoint(glassPt); + pane.repaint(); + } + + @Override + public void drop(DropTargetDropEvent e) { + TabDndGhostPane pane = dnd.getDndGhostPane(); + if (pane == null || e.getDropTargetContext().getComponent() != pane) { + return; + } + Transferable t = e.getTransferable(); + DataFlavor[] f = t.getTransferDataFlavors(); + int oldIdx = dnd.dragTabIndex; + int newIdx = dnd.getTargetTabIndex(e.getLocation()); + if (t.isDataFlavorSupported(f[0]) && oldIdx != newIdx) { + dnd.swapTabs(oldIdx, newIdx); + e.dropComplete(true); + } else { + e.dropComplete(false); + } + pane.setVisible(false); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndTransferable.java b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndTransferable.java new file mode 100644 index 000000000..c97f22d56 --- /dev/null +++ b/jadx-gui/src/main/java/jadx/gui/ui/tab/dnd/TabDndTransferable.java @@ -0,0 +1,44 @@ +/* + * The MIT License (MIT) + * Copyright (c) 2015 TERAI Atsuhiro + * Copyright (c) 2024 Skylot + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package jadx.gui.ui.tab.dnd; + +import java.awt.datatransfer.DataFlavor; +import java.awt.datatransfer.Transferable; + +class TabDndTransferable implements Transferable { + private static final String NAME = "Transferable Tab"; + + @Override + public Object getTransferData(DataFlavor flavor) { + return this; + } + + @Override + public DataFlavor[] getTransferDataFlavors() { + return new DataFlavor[] { new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType, NAME) }; + } + + @Override + public boolean isDataFlavorSupported(DataFlavor flavor) { + return NAME.equals(flavor.getHumanPresentableName()); + } +} diff --git a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/StartPageNode.java b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/StartPageNode.java index 1bdf18c32..0e4866f08 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/StartPageNode.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/StartPageNode.java @@ -4,9 +4,9 @@ import javax.swing.Icon; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.StartPagePanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.Icons; import jadx.gui.utils.NLS; diff --git a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java index c7b57c4d8..3ce15f423 100644 --- a/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java +++ b/jadx-gui/src/main/java/jadx/gui/ui/treenodes/SummaryNode.java @@ -29,9 +29,9 @@ import jadx.gui.JadxWrapper; import jadx.gui.treemodel.JClass; import jadx.gui.treemodel.JNode; import jadx.gui.ui.MainWindow; -import jadx.gui.ui.TabbedPane; import jadx.gui.ui.panel.ContentPanel; import jadx.gui.ui.panel.HtmlPanel; +import jadx.gui.ui.tab.TabbedPane; import jadx.gui.utils.UiUtils; public class SummaryNode extends JNode {