feat(gui): Quick Tabs Overhaul (PR #2241)

* restructure quick tabs code

* code formatting

* display open tabs

* added bookmark tabs feature

* fix tabs pin and bookmark not saved

* fix NPE treeModel not initialized

* Fix hardcoded strings

* remove unused statement

* fix NPE again

* added bookmark overlay

* preserve tabs order

* fix context menu actions

* remove unnecessary public modifier

* save tabs in tabbedpane order

* remove unreferenced tabs

* Update jadx-gui/src/main/java/jadx/gui/ui/tab/TabComponent.java

---------

Co-authored-by: skylot <118523+skylot@users.noreply.github.com>
This commit is contained in:
Mino
2024-08-06 23:47:25 +01:00
committed by GitHub
parent 500aa8a68d
commit ffbf800404
36 changed files with 951 additions and 231 deletions
@@ -30,6 +30,8 @@ public class TabStateViewAdapter {
tvs.setView(new ViewPoint(viewState.getViewPoint()));
tvs.setActive(viewState.isActive());
tvs.setPinned(viewState.isPinned());
tvs.setBookmarked(viewState.isBookmarked());
tvs.setHidden(viewState.isHidden());
return tvs;
}
@@ -43,6 +45,8 @@ public class TabStateViewAdapter {
EditorViewState viewState = new EditorViewState(node, tvs.getSubPath(), tvs.getCaret(), tvs.getView().toPoint());
viewState.setActive(tvs.isActive());
viewState.setPinned(tvs.isPinned());
viewState.setBookmarked(tvs.isBookmarked());
viewState.setHidden(tvs.isHidden());
return viewState;
} catch (Exception e) {
LOG.error("Failed to load tab state: " + tvs, e);
@@ -8,6 +8,8 @@ public class TabViewState {
private ViewPoint view;
boolean active;
boolean pinned;
boolean bookmarked;
boolean hidden;
public String getType() {
return type;
@@ -64,4 +66,20 @@ public class TabViewState {
public void setPinned(boolean pinned) {
this.pinned = pinned;
}
public boolean isBookmarked() {
return bookmarked;
}
public void setBookmarked(boolean bookmarked) {
this.bookmarked = bookmarked;
}
public boolean isHidden() {
return hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
}
@@ -68,7 +68,7 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
return javaNode.getName();
}
public boolean isPinnable() {
public boolean supportsQuickTabs() {
return true;
}
@@ -141,6 +141,7 @@ import jadx.gui.ui.panel.ProgressPanel;
import jadx.gui.ui.popupmenu.RecentProjectsMenuListener;
import jadx.gui.ui.tab.QuickTabsTree;
import jadx.gui.ui.tab.TabbedPane;
import jadx.gui.ui.tab.TabsController;
import jadx.gui.ui.tab.dnd.TabDndController;
import jadx.gui.ui.treenodes.StartPageNode;
import jadx.gui.ui.treenodes.SummaryNode;
@@ -208,6 +209,7 @@ public class MainWindow extends JFrame {
private JTree tree;
private DefaultTreeModel treeModel;
private JRoot treeRoot;
private TabsController tabsController;
private TabbedPane tabbedPane;
private HeapUsageBar heapUsageBar;
private transient boolean treeReloading;
@@ -370,6 +372,7 @@ public class MainWindow extends JFrame {
}
private void saveProject() {
saveOpenTabs();
if (!project.isSaveFileSelected()) {
saveProjectAs();
} else {
@@ -563,7 +566,7 @@ public class MainWindow extends JFrame {
resetCache();
LogCollector.getInstance().reset();
wrapper.close();
tabbedPane.closeAllTabs();
tabsController.forceCloseAllTabs();
UiUtils.resetClipboardOwner();
System.gc();
update();
@@ -1342,7 +1345,8 @@ public class MainWindow extends JFrame {
leftPane.add(bottomPane, BorderLayout.PAGE_END);
treeSplitPane.setLeftComponent(leftPane);
tabbedPane = new TabbedPane(this);
tabsController = new TabsController(this);
tabbedPane = new TabbedPane(this, tabsController);
tabbedPane.setMinimumSize(new Dimension(150, 150));
new TabDndController(tabbedPane, settings);
@@ -1512,7 +1516,7 @@ public class MainWindow extends JFrame {
}
private void saveOpenTabs() {
project.saveOpenTabs(tabbedPane.getEditorViewStates());
project.saveOpenTabs(tabsController.getEditorViewStates());
}
private void restoreOpenTabs(List<EditorViewState> openTabs) {
@@ -1521,8 +1525,9 @@ public class MainWindow extends JFrame {
return;
}
for (EditorViewState viewState : openTabs) {
tabbedPane.restoreEditorViewState(viewState);
tabsController.restoreEditorViewState(viewState);
}
tabsController.notifyRestoreEditorViewStateDone();
}
private void preLoadOpenTabs(List<EditorViewState> openTabs) {
@@ -1572,6 +1577,10 @@ public class MainWindow extends JFrame {
return tabbedPane;
}
public TabsController getTabsController() {
return tabsController;
}
public JadxSettings getSettings() {
return settings;
}
@@ -147,12 +147,14 @@ public final class ClassCodeContentPanel extends AbstractCodeContentPanel implem
}
@Override
public EditorViewState getEditorViewState() {
public void saveEditorViewState(EditorViewState viewState) {
CodePanel codePanel = (CodePanel) areaTabbedPane.getSelectedComponent();
int caretPos = codePanel.getCodeArea().getCaretPosition();
Point viewPoint = codePanel.getCodeScrollPane().getViewport().getViewPosition();
String subPath = codePanel == javaCodePanel ? "java" : "smali";
return new EditorViewState(getNode(), subPath, caretPos, viewPoint);
viewState.setSubPath(subPath);
viewState.setCaretPos(caretPos);
viewState.setViewPoint(viewPoint);
}
@Override
@@ -56,10 +56,11 @@ public final class CodeContentPanel extends AbstractCodeContentPanel implements
}
@Override
public EditorViewState getEditorViewState() {
public void saveEditorViewState(EditorViewState viewState) {
int caretPos = codePanel.getCodeArea().getCaretPosition();
Point viewPoint = codePanel.getCodeScrollPane().getViewport().getViewPosition();
return new EditorViewState(getNode(), "", caretPos, viewPoint);
viewState.setCaretPos(caretPos);
viewState.setViewPoint(viewPoint);
}
@Override
@@ -8,11 +8,19 @@ public class EditorViewState {
public static final Point ZERO = new Point(0, 0);
private final JNode node;
private final int caretPos;
private final Point viewPoint;
private final String subPath;
private int caretPos;
private Point viewPoint;
private String subPath;
private boolean active;
private boolean pinned;
private boolean bookmarked;
private boolean hidden;
public EditorViewState(JNode node) {
this(node, "", 0, EditorViewState.ZERO);
}
public EditorViewState(JNode node, String subPath, int caretPos, Point viewPoint) {
this.node = node;
@@ -29,14 +37,26 @@ public class EditorViewState {
return caretPos;
}
public void setCaretPos(int caretPos) {
this.caretPos = caretPos;
}
public Point getViewPoint() {
return viewPoint;
}
public void setViewPoint(Point viewPoint) {
this.viewPoint = viewPoint;
}
public String getSubPath() {
return subPath;
}
public void setSubPath(String subPath) {
this.subPath = subPath;
}
public boolean isActive() {
return active;
}
@@ -53,6 +73,22 @@ public class EditorViewState {
this.pinned = pinned;
}
public boolean isBookmarked() {
return bookmarked;
}
public void setBookmarked(boolean bookmarked) {
this.bookmarked = bookmarked;
}
public boolean isHidden() {
return hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
@Override
public String toString() {
return "EditorViewState{node=" + node
@@ -15,11 +15,10 @@ public abstract class ContentPanel extends JPanel {
protected TabbedPane tabbedPane;
protected JNode node;
private boolean pinned;
protected ContentPanel(TabbedPane panel, JNode jnode) {
protected ContentPanel(TabbedPane panel, JNode node) {
tabbedPane = panel;
node = jnode;
this.node = node;
}
public abstract void loadSettings();
@@ -40,27 +39,19 @@ public abstract class ContentPanel extends JPanel {
*/
@Nullable
public String getTabTooltip() {
JClass jClass = node.getRootClass();
JClass jClass = getNode().getRootClass();
if (jClass != null) {
return jClass.getFullName();
}
return node.getName();
return getNode().getName();
}
public JadxSettings getSettings() {
return tabbedPane.getMainWindow().getSettings();
}
public boolean isPinned() {
return pinned;
}
public void setPinned(boolean pinned) {
this.pinned = pinned;
}
public boolean isPinnable() {
return node.isPinnable();
public boolean supportsQuickTabs() {
return getNode().supportsQuickTabs();
}
public void dispose() {
@@ -4,7 +4,7 @@ import jadx.gui.ui.codearea.EditorViewState;
public interface IViewStateSupport {
EditorViewState getEditorViewState();
void saveEditorViewState(EditorViewState viewState);
void restoreEditorViewState(EditorViewState viewState);
}
@@ -1,7 +1,29 @@
package jadx.gui.ui.tab;
import jadx.gui.treemodel.JNode;
import java.util.ArrayList;
import jadx.gui.ui.codearea.EditorViewState;
public interface ITabStatesListener {
void onTabPinChange(JNode node, boolean pinned);
void onTabOpen(TabBlueprint blueprint);
void onTabSelect(TabBlueprint blueprint);
void onTabClose(TabBlueprint blueprint);
void onTabPositionFirst(TabBlueprint blueprint);
void onTabPinChange(TabBlueprint blueprint);
void onTabBookmarkChange(TabBlueprint blueprint);
void onTabVisibilityChange(TabBlueprint blueprint);
void onTabRestore(TabBlueprint blueprint, EditorViewState viewState);
void onTabsRestoreDone();
void onTabsReorder(ArrayList<TabBlueprint> blueprints);
void onTabSave(TabBlueprint blueprint, EditorViewState viewState);
}
@@ -0,0 +1,39 @@
package jadx.gui.ui.tab;
import javax.swing.Icon;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
public class QuickTabsBookmarkParentNode extends QuickTabsParentNode {
protected QuickTabsBookmarkParentNode(TabsController tabsController) {
super(tabsController);
}
@Override
public String getTitle() {
return NLS.str("tree.bookmarked_tabs");
}
@Override
Icon getIcon() {
return Icons.BOOKMARK_DARK;
}
@Override
JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
if (getChildCount() == 0) {
return null;
}
JPopupMenu menu = new JPopupMenu();
JMenuItem unbookmarkAll = new JMenuItem(NLS.str("tabs.unbookmark_all"));
unbookmarkAll.addActionListener(e -> getTabsController().unbookmarkAllTabs());
menu.add(unbookmarkAll);
return menu;
}
}
@@ -28,20 +28,37 @@ public class QuickTabsChildNode extends QuickTabsBaseNode {
public JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
JPopupMenu menu = node.onTreePopupMenu(mainWindow);
if (node.isPinnable()) {
if (menu == null) {
menu = new JPopupMenu();
}
JMenuItem unpinAction = new JMenuItem(NLS.str("tabs.unpin"));
unpinAction.addActionListener(e -> {
TabComponent tabComponent = mainWindow.getTabbedPane().getTabComponentByNode(node);
if (tabComponent != null) {
tabComponent.togglePin();
if (node.supportsQuickTabs()) {
if (getParent() instanceof QuickTabsPinParentNode) {
if (menu == null) {
menu = new JPopupMenu();
}
});
menu.add(unpinAction, 0);
menu.add(new JPopupMenu.Separator(), 1);
JMenuItem closeAction = new JMenuItem(NLS.str("tabs.close"));
closeAction.addActionListener(e -> mainWindow.getTabsController().closeTab(node, true));
menu.add(closeAction, 0);
menu.add(new JPopupMenu.Separator(), 1);
}
if (getParent() instanceof QuickTabsPinParentNode) {
if (menu == null) {
menu = new JPopupMenu();
}
JMenuItem unpinAction = new JMenuItem(NLS.str("tabs.unpin"));
unpinAction.addActionListener(e -> mainWindow.getTabsController().setTabPinned(node, false));
menu.add(unpinAction, 0);
menu.add(new JPopupMenu.Separator(), 1);
}
if (getParent() instanceof QuickTabsBookmarkParentNode) {
if (menu == null) {
menu = new JPopupMenu();
}
JMenuItem unbookmarkAction = new JMenuItem(NLS.str("tabs.unbookmark"));
unbookmarkAction.addActionListener(e -> mainWindow.getTabsController().setTabBookmarked(node, false));
menu.add(unbookmarkAction, 0);
menu.add(new JPopupMenu.Separator(), 1);
}
}
return menu;
@@ -0,0 +1,39 @@
package jadx.gui.ui.tab;
import javax.swing.Icon;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
public class QuickTabsOpenParentNode extends QuickTabsParentNode {
protected QuickTabsOpenParentNode(TabsController tabsController) {
super(tabsController);
}
@Override
public String getTitle() {
return NLS.str("tree.open_tabs");
}
@Override
Icon getIcon() {
return Icons.FOLDER;
}
@Override
JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
if (getChildCount() == 0) {
return null;
}
JPopupMenu menu = new JPopupMenu();
JMenuItem closeAll = new JMenuItem(NLS.str("tabs.closeAll"));
closeAll.addActionListener(e -> getTabsController().closeAllTabs(true));
menu.add(closeAll);
return menu;
}
}
@@ -9,38 +9,44 @@ import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
abstract class QuickTabsParentNode extends QuickTabsBaseNode {
private final TabbedPane tabbedPane;
private final TabsController tabsController;
private final Map<JNode, QuickTabsChildNode> childrenMap = new HashMap<>();
protected QuickTabsParentNode(TabbedPane tabbedPane) {
protected QuickTabsParentNode(TabsController tabsController) {
super();
this.tabbedPane = tabbedPane;
this.tabsController = tabsController;
}
public void addJNode(JNode node) {
public boolean addJNode(JNode node) {
if (childrenMap.containsKey(node)) {
return;
return false;
}
QuickTabsChildNode childNode = new QuickTabsChildNode(node);
childrenMap.put(node, childNode);
add(childNode);
return true;
}
public void removeJNode(JNode node) {
public boolean removeJNode(JNode node) {
QuickTabsChildNode childNode = childrenMap.remove(node);
if (childNode == null) {
return;
return false;
}
remove(childNode);
return true;
}
public void removeAllNodes() {
removeAllChildren();
}
public QuickTabsChildNode getQuickTabsNode(JNode node) {
return childrenMap.get(node);
}
public TabbedPane getTabbedPane() {
return tabbedPane;
public TabsController getTabsController() {
return tabsController;
}
abstract String getTitle();
@@ -9,8 +9,8 @@ import jadx.gui.utils.Icons;
import jadx.gui.utils.NLS;
public class QuickTabsPinParentNode extends QuickTabsParentNode {
protected QuickTabsPinParentNode(TabbedPane tabbedPane) {
super(tabbedPane);
protected QuickTabsPinParentNode(TabsController tabsController) {
super(tabsController);
}
@Override
@@ -31,7 +31,7 @@ public class QuickTabsPinParentNode extends QuickTabsParentNode {
JPopupMenu menu = new JPopupMenu();
JMenuItem unpinAll = new JMenuItem(NLS.str("tabs.unpin_all"));
unpinAll.addActionListener(e -> getTabbedPane().unpinAll());
unpinAll.addActionListener(e -> getTabsController().unpinAllTabs());
menu.add(unpinAll);
return menu;
@@ -6,6 +6,7 @@ import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
@@ -19,23 +20,29 @@ import javax.swing.tree.TreeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorViewState;
import jadx.gui.utils.UiUtils;
public class QuickTabsTree extends JTree implements ITabStatesListener, TreeSelectionListener {
private final MainWindow mainWindow;
private final DefaultTreeModel treeModel;
private final QuickTabsParentNode openParentNode;
private final QuickTabsParentNode pinParentNode;
private final QuickTabsParentNode bookmarkParentNode;
public QuickTabsTree(MainWindow mainWindow) {
this.mainWindow = mainWindow;
mainWindow.getTabbedPane().addTabStateListener(this);
mainWindow.getTabsController().addListener(this);
Root root = new Root();
pinParentNode = new QuickTabsPinParentNode(mainWindow.getTabbedPane());
fillPinParentNode();
pinParentNode = new QuickTabsPinParentNode(mainWindow.getTabsController());
openParentNode = new QuickTabsOpenParentNode(mainWindow.getTabsController());
bookmarkParentNode = new QuickTabsBookmarkParentNode(mainWindow.getTabsController());
root.add(openParentNode);
root.add(pinParentNode);
root.add(bookmarkParentNode);
treeModel = new DefaultTreeModel(root);
setModel(treeModel);
@@ -69,6 +76,10 @@ public class QuickTabsTree extends JTree implements ITabStatesListener, TreeSele
});
loadSettings();
fillOpenParentNode();
fillPinParentNode();
fillBookmarkParentNode();
}
private void triggerRightClickAction(MouseEvent e) {
@@ -91,14 +102,50 @@ public class QuickTabsTree extends JTree implements ITabStatesListener, TreeSele
if (pressedNode instanceof QuickTabsChildNode) {
QuickTabsChildNode childNode = (QuickTabsChildNode) pressedNode;
return mainWindow.getTabbedPane().showNode(childNode.getJNode());
mainWindow.getTabsController().selectTab(childNode.getJNode());
return true;
}
return false;
}
private void fillOpenParentNode() {
mainWindow.getTabsController().getOpenTabs().forEach(this::onTabOpen);
}
private void fillPinParentNode() {
mainWindow.getTabbedPane().getPinnedTabs().forEach((contentPanel) -> pinParentNode.addJNode(contentPanel.getNode()));
mainWindow.getTabsController().getPinnedTabs().forEach(this::onTabPinChange);
}
private void fillBookmarkParentNode() {
mainWindow.getTabsController().getBookmarkedTabs().forEach(this::onTabBookmarkChange);
}
private void clearParentNode(QuickTabsParentNode parentNode) {
int[] childIndices = new int[parentNode.getChildCount()];
Object[] objects = new Object[parentNode.getChildCount()];
for (int i = 0; i < childIndices.length; i++) {
childIndices[i] = i;
objects[i] = parentNode.getChildAt(i);
}
parentNode.removeAllNodes();
treeModel.nodesWereRemoved(parentNode, childIndices, objects);
}
private void addJNode(QuickTabsParentNode parentNode, JNode node) {
if (parentNode.addJNode(node)) {
treeModel.nodesWereInserted(parentNode, new int[] { parentNode.getChildCount() - 1 });
}
}
private void removeJNode(QuickTabsParentNode parentNode, JNode node) {
QuickTabsChildNode child = parentNode.getQuickTabsNode(node);
if (child != null) {
int removedIndex = parentNode.getIndex(child);
if (parentNode.removeJNode(node)) {
treeModel.nodesWereRemoved(parentNode, new int[] { removedIndex }, new Object[] { child });
}
}
}
@Override
@@ -108,22 +155,10 @@ public class QuickTabsTree extends JTree implements ITabStatesListener, TreeSele
if (selectedNode instanceof QuickTabsChildNode) {
QuickTabsChildNode childNode = (QuickTabsChildNode) selectedNode;
JNode jNode = childNode.getJNode();
TabbedPane tabbedPane = mainWindow.getTabbedPane();
tabbedPane.selectTab(tabbedPane.getTabByNode(jNode));
}
}
}
@Override
public void onTabPinChange(JNode node, boolean pinned) {
if (pinned) {
pinParentNode.addJNode(node);
treeModel.nodesWereInserted(pinParentNode, new int[] { pinParentNode.getChildCount() - 1 });
} else {
QuickTabsChildNode child = pinParentNode.getQuickTabsNode(node);
int removedIndex = pinParentNode.getIndex(child);
pinParentNode.removeJNode(node);
treeModel.nodesWereRemoved(pinParentNode, new int[] { removedIndex }, new Object[] { child });
TabsController tabsController = mainWindow.getTabsController();
tabsController.selectTab(jNode);
}
}
}
@@ -135,7 +170,81 @@ public class QuickTabsTree extends JTree implements ITabStatesListener, TreeSele
}
public void dispose() {
mainWindow.getTabbedPane().removeTabStateListener(this);
mainWindow.getTabsController().removeListener(this);
}
@Override
public void onTabOpen(TabBlueprint blueprint) {
if (!blueprint.isHidden() && blueprint.getNode().supportsQuickTabs()) {
addJNode(openParentNode, blueprint.getNode());
}
}
@Override
public void onTabSelect(TabBlueprint blueprint) {
}
@Override
public void onTabClose(TabBlueprint blueprint) {
removeJNode(openParentNode, blueprint.getNode());
removeJNode(pinParentNode, blueprint.getNode());
removeJNode(bookmarkParentNode, blueprint.getNode());
}
@Override
public void onTabPositionFirst(TabBlueprint blueprint) {
}
@Override
public void onTabPinChange(TabBlueprint blueprint) {
JNode node = blueprint.getNode();
if (blueprint.isPinned()) {
addJNode(pinParentNode, node);
} else {
removeJNode(pinParentNode, node);
}
}
@Override
public void onTabBookmarkChange(TabBlueprint blueprint) {
JNode node = blueprint.getNode();
if (blueprint.isBookmarked()) {
addJNode(bookmarkParentNode, node);
} else {
removeJNode(bookmarkParentNode, node);
}
}
@Override
public void onTabVisibilityChange(TabBlueprint blueprint) {
JNode node = blueprint.getNode();
if (!blueprint.isHidden()) {
addJNode(openParentNode, node);
} else {
removeJNode(openParentNode, node);
}
}
@Override
public void onTabRestore(TabBlueprint blueprint, EditorViewState viewState) {
}
@Override
public void onTabsRestoreDone() {
}
@Override
public void onTabsReorder(ArrayList<TabBlueprint> blueprints) {
}
@Override
public void onTabSave(TabBlueprint blueprint, EditorViewState viewState) {
}
private class Root extends DefaultMutableTreeNode {
@@ -0,0 +1,50 @@
package jadx.gui.ui.tab;
import jadx.gui.treemodel.JNode;
public class TabBlueprint {
private final JNode node;
private boolean pinned;
private boolean bookmarked;
private boolean hidden;
public TabBlueprint(JNode node) {
this.node = node;
}
public JNode getNode() {
return node;
}
public boolean isPinned() {
return pinned;
}
public void setPinned(boolean pinned) {
this.pinned = pinned;
}
public boolean isBookmarked() {
return bookmarked;
}
public void setBookmarked(boolean bookmarked) {
this.bookmarked = bookmarked;
}
public boolean supportsQuickTabs() {
return node.supportsQuickTabs();
}
public boolean isReferenced() {
return isBookmarked();
}
public boolean isHidden() {
return hidden;
}
public void setHidden(boolean hidden) {
this.hidden = hidden;
}
}
@@ -21,6 +21,7 @@ import javax.swing.SwingUtilities;
import javax.swing.plaf.basic.BasicButtonUI;
import jadx.core.utils.ListUtils;
import jadx.core.utils.exceptions.JadxRuntimeException;
import jadx.gui.treemodel.JClass;
import jadx.gui.treemodel.JEditableNode;
import jadx.gui.treemodel.JNode;
@@ -28,6 +29,7 @@ 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.OverlayIcon;
import jadx.gui.utils.UiUtils;
import jadx.gui.utils.ui.NodeLabel;
@@ -35,14 +37,17 @@ public class TabComponent extends JPanel {
private static final long serialVersionUID = -8147035487543610321L;
private final TabbedPane tabbedPane;
private final TabsController tabsController;
private final ContentPanel contentPanel;
private OverlayIcon icon;
private JLabel label;
private JButton pinBtn;
private JButton closeBtn;
public TabComponent(TabbedPane tabbedPane, ContentPanel contentPanel) {
this.tabbedPane = tabbedPane;
this.tabsController = tabbedPane.getMainWindow().getTabsController();
this.contentPanel = contentPanel;
init();
@@ -56,14 +61,16 @@ public class TabComponent extends JPanel {
}
private Font getLabelFont() {
return tabbedPane.getMainWindow().getSettings().getFont().deriveFont(Font.BOLD);
return tabsController.getMainWindow().getSettings().getFont().deriveFont(Font.BOLD);
}
private void init() {
setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
setOpaque(false);
JNode node = contentPanel.getNode();
JNode node = getNode();
icon = new OverlayIcon(node.getIcon());
label = new NodeLabel(buildTabTitle(node), node.disableHtml());
label.setFont(getLabelFont());
String toolTip = contentPanel.getTabTooltip();
@@ -71,7 +78,8 @@ public class TabComponent extends JPanel {
setToolTipText(toolTip);
}
label.setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 10));
label.setIcon(node.getIcon());
label.setIcon(icon);
updateBookmarkIcon();
if (node instanceof JEditableNode) {
((JEditableNode) node).addChangeListener(c -> label.setText(buildTabTitle(node)));
}
@@ -97,20 +105,20 @@ public class TabComponent extends JPanel {
closeBtn.setFocusable(false);
closeBtn.setBorder(null);
closeBtn.setBorderPainted(false);
closeBtn.addActionListener(e -> tabbedPane.closeCodePanel(contentPanel, true));
closeBtn.addActionListener(e -> {
tabsController.closeTab(node, true);
});
MouseAdapter clickAdapter = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isMiddleMouseButton(e)) {
tabbedPane.closeCodePanel(contentPanel, true);
tabsController.closeTab(node, true);
} else if (SwingUtilities.isRightMouseButton(e)) {
JPopupMenu menu = createTabPopupMenu(contentPanel);
JPopupMenu menu = createTabPopupMenu();
menu.show(e.getComponent(), e.getX(), e.getY());
} else if (SwingUtilities.isLeftMouseButton(e)) {
if (tabbedPane.getSelectedComponent() != contentPanel) {
tabbedPane.selectTab(contentPanel);
}
tabsController.selectTab(node);
}
}
};
@@ -123,15 +131,14 @@ public class TabComponent extends JPanel {
}
public void updateCloseOrPinButton() {
if (contentPanel.isPinned()) {
if (getBlueprint().isPinned()) {
if (closeBtn.isShowing()) {
remove(closeBtn);
}
if (!pinBtn.isShowing()) {
add(pinBtn);
}
}
if (!contentPanel.isPinned()) {
} else {
if (pinBtn.isShowing()) {
remove(pinBtn);
}
@@ -141,13 +148,27 @@ public class TabComponent extends JPanel {
}
}
public void togglePin() {
contentPanel.setPinned(!contentPanel.isPinned());
updateCloseOrPinButton();
if (contentPanel.isPinned()) {
tabbedPane.advanceTab(this);
public void updateBookmarkIcon() {
icon.clear();
if (getBlueprint().isBookmarked()) {
icon.add(Icons.BOOKMARK_OVERLAY_DARK);
}
tabbedPane.notifyTabStateChange(this, true);
label.repaint();
}
private void togglePin() {
boolean pinned = !getBlueprint().isPinned();
tabsController.setTabPinned(getNode(), pinned);
if (pinned) {
tabsController.setTabPositionFirst(getNode());
}
}
private void toggleBookmark() {
boolean bookmarked = !getBlueprint().isBookmarked();
tabsController.setTabBookmarked(getNode(), bookmarked);
}
private void addListenerForDnd() {
@@ -180,7 +201,7 @@ public class TabComponent extends JPanel {
return tabTitle;
}
private JPopupMenu createTabPopupMenu(final ContentPanel contentPanel) {
private JPopupMenu createTabPopupMenu() {
JPopupMenu menu = new JPopupMenu();
String nodeFullName = getNodeFullName(contentPanel);
@@ -191,51 +212,63 @@ public class TabComponent extends JPanel {
menu.addSeparator();
}
if (contentPanel.isPinnable()) {
String pinTitle = contentPanel.isPinned() ? NLS.str("tabs.unpin") : NLS.str("tabs.pin");
if (getBlueprint().supportsQuickTabs()) {
String pinTitle = getBlueprint().isPinned() ? NLS.str("tabs.unpin") : NLS.str("tabs.pin");
JMenuItem pinTab = new JMenuItem(pinTitle);
pinTab.addActionListener(e -> togglePin());
menu.add(pinTab);
JMenuItem unpinAll = new JMenuItem(NLS.str("tabs.unpin_all"));
unpinAll.addActionListener(e -> tabbedPane.unpinAll());
unpinAll.addActionListener(e -> tabsController.unpinAllTabs());
menu.add(unpinAll);
String bookmarkTitle = getBlueprint().isBookmarked() ? NLS.str("tabs.unbookmark") : NLS.str("tabs.bookmark");
JMenuItem bookmarkTab = new JMenuItem(bookmarkTitle);
bookmarkTab.addActionListener(e -> toggleBookmark());
menu.add(bookmarkTab);
JMenuItem unbookmarkAll = new JMenuItem(NLS.str("tabs.unbookmark_all"));
unbookmarkAll.addActionListener(e -> tabsController.unbookmarkAllTabs());
menu.add(unbookmarkAll);
}
JMenuItem closeTab = new JMenuItem(NLS.str("tabs.close"));
closeTab.addActionListener(e -> tabbedPane.closeCodePanel(contentPanel, true));
if (contentPanel.isPinned()) {
closeTab.addActionListener(e -> tabsController.closeTab(getNode(), true));
if (getBlueprint().isPinned()) {
closeTab.setEnabled(false);
}
menu.add(closeTab);
List<ContentPanel> tabs = tabbedPane.getTabs();
List<TabBlueprint> tabs = tabsController.getOpenTabs();
if (tabs.size() > 1) {
JMenuItem closeOther = new JMenuItem(NLS.str("tabs.closeOthers"));
closeOther.addActionListener(e -> {
for (ContentPanel panel : tabs) {
if (panel != contentPanel) {
tabbedPane.closeCodePanel(panel, true);
for (TabBlueprint tab : tabs) {
if (tab != getBlueprint()) {
tabsController.closeTab(getNode(), true);
}
}
});
menu.add(closeOther);
JMenuItem closeAll = new JMenuItem(NLS.str("tabs.closeAll"));
closeAll.addActionListener(e -> tabbedPane.closeAllTabs(true));
closeAll.addActionListener(e -> tabsController.closeAllTabs(true));
menu.add(closeAll);
if (contentPanel != ListUtils.last(tabs)) {
// We don't use TabsController here because tabs position is
// specific to TabbedPane
List<ContentPanel> contentPanels = tabbedPane.getTabs();
if (contentPanel != ListUtils.last(contentPanels)) {
JMenuItem closeAllRight = new JMenuItem(NLS.str("tabs.closeAllRight"));
closeAllRight.addActionListener(e -> {
boolean pastCurrentPanel = false;
for (ContentPanel panel : tabs) {
for (ContentPanel panel : contentPanels) {
if (!pastCurrentPanel) {
if (panel == contentPanel) {
pastCurrentPanel = true;
}
} else {
tabbedPane.closeCodePanel(panel, true);
tabsController.closeTab(panel.getNode(), true);
}
}
});
@@ -243,15 +276,15 @@ public class TabComponent extends JPanel {
}
menu.addSeparator();
ContentPanel selectedContentPanel = tabbedPane.getSelectedContentPanel();
for (ContentPanel tab : tabs) {
if (tab == selectedContentPanel) {
TabBlueprint selectedTab = tabsController.getSelectedTab();
for (TabBlueprint tab : tabs) {
if (tab == selectedTab) {
continue;
}
JNode node = tab.getNode();
final String clsName = node.makeLongString();
JMenuItem item = new JMenuItem(clsName);
item.addActionListener(e -> tabbedPane.setSelectedComponent(tab));
item.addActionListener(e -> tabsController.selectTab(node));
item.setIcon(node.getIcon());
menu.add(item);
}
@@ -271,4 +304,17 @@ public class TabComponent extends JPanel {
public ContentPanel getContentPanel() {
return contentPanel;
}
public TabBlueprint getBlueprint() {
TabBlueprint blueprint = tabsController.getTabByNode(contentPanel.getNode());
if (blueprint == null) {
throw new JadxRuntimeException("TabComponent does not have a corresponding TabBlueprint");
}
return blueprint;
}
public JNode getNode() {
return contentPanel.getNode();
}
}
@@ -11,6 +11,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import javax.swing.JTabbedPane;
import javax.swing.SwingUtilities;
@@ -42,15 +43,15 @@ import jadx.gui.utils.JumpPosition;
import jadx.gui.utils.NLS;
import jadx.gui.utils.UiUtils;
public class TabbedPane extends JTabbedPane {
public class TabbedPane extends JTabbedPane implements ITabStatesListener {
private static final long serialVersionUID = -8833600618794570904L;
private static final Logger LOG = LoggerFactory.getLogger(TabbedPane.class);
private final transient MainWindow mainWindow;
private final transient TabsController controller;
private final transient Map<JNode, ContentPanel> tabsMap = new HashMap<>();
private final ArrayList<ITabStatesListener> tabStatesListeners = new ArrayList<>();
private final transient JumpManager jumps = new JumpManager();
private transient ContentPanel curTab;
@@ -58,9 +59,11 @@ public class TabbedPane extends JTabbedPane {
private transient TabDndController dnd;
public TabbedPane(MainWindow window) {
public TabbedPane(MainWindow window, TabsController controller) {
this.mainWindow = window;
this.controller = controller;
controller.addListener(this);
setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
addMouseWheelListener(event -> {
@@ -299,10 +302,7 @@ public class TabbedPane extends JTabbedPane {
}
public void selectTab(ContentPanel contentPanel) {
setSelectedComponent(contentPanel);
if (mainWindow.getSettings().isAlwaysSelectOpened()) {
mainWindow.syncWithEditor();
}
controller.selectTab(contentPanel.getNode());
}
public void smaliJump(JClass cls, int pos, boolean debugMode) {
@@ -334,44 +334,6 @@ public class TabbedPane extends JTabbedPane {
return null;
}
public List<EditorViewState> getEditorViewStates() {
ContentPanel selected = getSelectedContentPanel();
List<EditorViewState> states = new ArrayList<>();
for (ContentPanel panel : getTabs()) {
EditorViewState viewState;
if (panel instanceof IViewStateSupport) {
viewState = ((IViewStateSupport) panel).getEditorViewState();
} else {
viewState = new EditorViewState(panel.getNode(), "", 0, EditorViewState.ZERO);
}
viewState.setActive(panel == selected);
viewState.setPinned(panel.isPinned());
states.add(viewState);
}
return states;
}
public void restoreEditorViewState(EditorViewState viewState) {
ContentPanel contentPanel = getContentPanel(viewState.getNode());
if (contentPanel instanceof IViewStateSupport) {
((IViewStateSupport) contentPanel).restoreEditorViewState(viewState);
}
if (viewState.isActive()) {
setSelectedComponent(contentPanel);
}
if (contentPanel != null) {
boolean pinned = viewState.isPinned();
contentPanel.setPinned(pinned);
Component component = getTabComponentAt(indexOfComponent(contentPanel));
if (component instanceof TabComponent) {
TabComponent tabComponent = (TabComponent) component;
JNode node = contentPanel.getNode();
tabComponent.updateCloseOrPinButton();
tabStatesListeners.forEach(l -> l.onTabPinChange(node, pinned));
}
}
}
public void navBack() {
if (jumps.size() > 1) {
jumps.updateCurPosition(getCurrentPosition());
@@ -404,36 +366,7 @@ public class TabbedPane extends JTabbedPane {
}
public void closeCodePanel(ContentPanel contentPanel, boolean considerPins) {
if (considerPins && contentPanel.isPinned()) {
return;
}
tabsMap.remove(contentPanel.getNode());
remove(contentPanel);
contentPanel.dispose();
}
public void advanceTab(TabComponent tabComponent) {
remove(tabComponent.getContentPanel());
add(tabComponent.getContentPanel(), 0);
setTabComponentAt(0, tabComponent);
selectTab(tabComponent.getContentPanel());
}
public void notifyTabStateChange(TabComponent tabComponent, boolean pinChange) {
if (pinChange) {
JNode node = tabComponent.getContentPanel().getNode();
boolean pinned = tabComponent.getContentPanel().isPinned();
tabStatesListeners.forEach(l -> l.onTabPinChange(node, pinned));
}
}
public void addTabStateListener(ITabStatesListener listener) {
tabStatesListeners.add(listener);
}
public void removeTabStateListener(ITabStatesListener listener) {
tabStatesListeners.remove(listener);
controller.closeTab(contentPanel.getNode(), considerPins);
}
public List<ContentPanel> getTabs() {
@@ -444,28 +377,6 @@ public class TabbedPane extends JTabbedPane {
return list;
}
public List<ContentPanel> getPinnedTabs() {
List<ContentPanel> list = new ArrayList<>(getTabCount());
for (int i = 0; i < getTabCount(); i++) {
ContentPanel contentPanel = (ContentPanel) getComponentAt(i);
if (contentPanel.isPinned()) {
list.add(contentPanel);
}
}
return list;
}
public List<TabComponent> getPinnedTabComponents() {
List<TabComponent> list = new ArrayList<>(getTabCount());
for (int i = 0; i < getTabCount(); i++) {
ContentPanel contentPanel = (ContentPanel) getComponentAt(i);
if (contentPanel.isPinned()) {
list.add((TabComponent) getTabComponentAt(i));
}
}
return list;
}
public @Nullable ContentPanel getTabByNode(JNode node) {
return tabsMap.get(node);
}
@@ -480,21 +391,8 @@ public class TabbedPane extends JTabbedPane {
}
private @Nullable ContentPanel getContentPanel(JNode node) {
ContentPanel panel = getTabByNode(node);
if (panel != null) {
return panel;
}
ContentPanel newPanel = node.getContentPanel(this);
if (newPanel == null) {
return null;
}
FocusManager.listen(newPanel);
addContentPanel(newPanel);
return newPanel;
}
public void unpinAll() {
getPinnedTabComponents().forEach(TabComponent::togglePin);
controller.openTab(node);
return getTabByNode(node);
}
public void refresh(JNode node) {
@@ -517,19 +415,18 @@ public class TabbedPane extends JTabbedPane {
continue;
}
ContentPanel oldPanel = (ContentPanel) getComponentAt(i);
EditorViewState viewState = null;
if (oldPanel instanceof IViewStateSupport) {
viewState = ((IViewStateSupport) oldPanel).getEditorViewState();
TabBlueprint tab = controller.getTabByNode(oldPanel.getNode());
if (tab == null) {
continue;
}
EditorViewState viewState = controller.getEditorViewState(tab);
JNode node = oldPanel.getNode();
ContentPanel panel = node.getContentPanel(this);
if (viewState != null && panel instanceof IViewStateSupport) {
((IViewStateSupport) panel).restoreEditorViewState(viewState);
}
FocusManager.listen(panel);
tabsMap.put(node, panel);
setComponentAt(i, panel);
setTabComponentAt(i, makeTabComponent(panel));
controller.restoreEditorViewState(viewState);
}
fireStateChanged();
}
@@ -582,6 +479,123 @@ public class TabbedPane extends JTabbedPane {
this.dnd = dnd;
}
@Override
public void onTabOpen(TabBlueprint blueprint) {
if (blueprint.isHidden()) {
return;
}
ContentPanel newPanel = blueprint.getNode().getContentPanel(this);
FocusManager.listen(newPanel);
addContentPanel(newPanel);
}
@Override
public void onTabSelect(TabBlueprint blueprint) {
ContentPanel contentPanel = getContentPanel(blueprint.getNode());
setSelectedComponent(contentPanel);
if (mainWindow.getSettings().isAlwaysSelectOpened()) {
mainWindow.syncWithEditor();
}
}
@Override
public void onTabClose(TabBlueprint blueprint) {
ContentPanel contentPanel = getTabByNode(blueprint.getNode());
if (contentPanel == null) {
return;
}
tabsMap.remove(contentPanel.getNode());
remove(contentPanel);
contentPanel.dispose();
}
@Override
public void onTabPositionFirst(TabBlueprint blueprint) {
ContentPanel contentPanel = getTabByNode(blueprint.getNode());
if (contentPanel == null) {
return;
}
setTabPosition(contentPanel, 0);
}
private void setTabPosition(ContentPanel contentPanel, int position) {
TabComponent tabComponent = getTabComponentByNode(contentPanel.getNode());
if (tabComponent == null) {
return;
}
remove(contentPanel);
add(contentPanel, position);
setTabComponentAt(position, tabComponent);
}
@Override
public void onTabPinChange(TabBlueprint blueprint) {
TabComponent tabComponent = getTabComponentByNode(blueprint.getNode());
if (tabComponent == null) {
return;
}
tabComponent.updateCloseOrPinButton();
}
@Override
public void onTabBookmarkChange(TabBlueprint blueprint) {
TabComponent tabComponent = getTabComponentByNode(blueprint.getNode());
if (tabComponent == null) {
return;
}
tabComponent.updateBookmarkIcon();
}
@Override
public void onTabVisibilityChange(TabBlueprint blueprint) {
if (!blueprint.isHidden() && !tabsMap.containsKey(blueprint.getNode())) {
onTabOpen(blueprint);
}
if (blueprint.isHidden() && tabsMap.containsKey(blueprint.getNode())) {
onTabClose(blueprint);
}
}
@Override
public void onTabRestore(TabBlueprint blueprint, EditorViewState viewState) {
ContentPanel contentPanel = getTabByNode(blueprint.getNode());
if (contentPanel instanceof IViewStateSupport) {
((IViewStateSupport) contentPanel).restoreEditorViewState(viewState);
}
}
@Override
public void onTabsRestoreDone() {
}
@Override
public void onTabsReorder(ArrayList<TabBlueprint> blueprints) {
ArrayList<TabBlueprint> newBlueprints = new ArrayList<>();
for (ContentPanel contentPanel : getTabs()) {
Optional<TabBlueprint> blueprintFindResult = blueprints.stream()
.filter(b -> b.getNode() == contentPanel.getNode())
.findFirst();
if (blueprintFindResult.isPresent()) {
TabBlueprint blueprint = blueprintFindResult.get();
blueprints.remove(blueprint);
newBlueprints.add(blueprint);
}
}
// Add back hidden tabs
newBlueprints.addAll(blueprints);
blueprints.clear();
blueprints.addAll(newBlueprints);
}
@Override
public void onTabSave(TabBlueprint blueprint, EditorViewState viewState) {
ContentPanel contentPanel = getTabByNode(blueprint.getNode());
if (contentPanel instanceof IViewStateSupport) {
((IViewStateSupport) contentPanel).saveEditorViewState(viewState);
}
}
private static class FocusManager implements FocusListener {
private static final FocusManager INSTANCE = new FocusManager();
private static @Nullable Component focusedComp;
@@ -0,0 +1,244 @@
package jadx.gui.ui.tab;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.jetbrains.annotations.Nullable;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.ui.codearea.EditorViewState;
public class TabsController {
private final transient MainWindow mainWindow;
private final Map<JNode, TabBlueprint> tabsMap = new HashMap<>();
private final ArrayList<ITabStatesListener> listeners = new ArrayList<>();
private boolean forceClose;
private TabBlueprint selectedTab = null;
public TabsController(MainWindow mainWindow) {
this.mainWindow = mainWindow;
}
public MainWindow getMainWindow() {
return mainWindow;
}
public void addListener(ITabStatesListener listener) {
listeners.add(listener);
}
public void removeListener(ITabStatesListener listener) {
listeners.remove(listener);
}
public @Nullable TabBlueprint getTabByNode(JNode node) {
return tabsMap.get(node);
}
public TabBlueprint openTab(JNode node) {
return openTab(node, false);
}
public TabBlueprint openTab(JNode node, boolean hidden) {
TabBlueprint blueprint = getTabByNode(node);
if (blueprint == null) {
TabBlueprint newBlueprint = new TabBlueprint(node);
tabsMap.put(node, newBlueprint);
newBlueprint.setHidden(hidden);
listeners.forEach(l -> l.onTabOpen(newBlueprint));
if (hidden) {
listeners.forEach(l -> l.onTabVisibilityChange(newBlueprint));
}
blueprint = newBlueprint;
}
setTabHiddenInternal(blueprint, hidden);
return blueprint;
}
public void selectTab(JNode node) {
TabBlueprint blueprint = openTab(node);
selectedTab = blueprint;
listeners.forEach(l -> l.onTabSelect(blueprint));
}
public void closeTab(JNode node) {
closeTab(node, false);
}
public void closeTab(JNode node, boolean considerPins) {
TabBlueprint blueprint = getTabByNode(node);
if (blueprint == null) {
return;
}
if (forceClose) {
closeTabForce(blueprint);
return;
}
if (!considerPins || !blueprint.isPinned()) {
if (!blueprint.isReferenced()) {
closeTabForce(blueprint);
} else {
closeTabSoft(blueprint);
}
}
}
/*
* Removes Tab from everywhere
*/
private void closeTabForce(TabBlueprint blueprint) {
listeners.forEach(l -> l.onTabClose(blueprint));
tabsMap.remove(blueprint.getNode());
}
/*
* Hides Tab from TabbedPane
*/
private void closeTabSoft(TabBlueprint blueprint) {
setTabHidden(blueprint.getNode(), true);
}
public void setTabPositionFirst(JNode node) {
TabBlueprint blueprint = openTab(node);
listeners.forEach(l -> l.onTabPositionFirst(blueprint));
}
public void setTabPinned(JNode node, boolean pinned) {
TabBlueprint blueprint = openTab(node);
setTabPinnedInternal(blueprint, pinned);
}
public void setTabPinnedInternal(TabBlueprint blueprint, boolean pinned) {
if (blueprint.isPinned() != pinned) {
blueprint.setPinned(pinned);
listeners.forEach(l -> l.onTabPinChange(blueprint));
}
}
public void setTabBookmarked(JNode node, boolean bookmarked) {
TabBlueprint blueprint = openTab(node);
setTabBookmarkedInternal(blueprint, bookmarked);
}
private void setTabBookmarkedInternal(TabBlueprint blueprint, boolean bookmarked) {
if (blueprint.isBookmarked() != bookmarked) {
blueprint.setBookmarked(bookmarked);
listeners.forEach(l -> l.onTabBookmarkChange(blueprint));
removeTabIfNotReferenced(blueprint);
}
}
public void setTabHidden(JNode node, boolean hidden) {
TabBlueprint blueprint = getTabByNode(node);
setTabHiddenInternal(blueprint, hidden);
}
private void setTabHiddenInternal(TabBlueprint blueprint, boolean hidden) {
if (blueprint != null && blueprint.isHidden() != hidden) {
blueprint.setHidden(hidden);
listeners.forEach(l -> l.onTabVisibilityChange(blueprint));
}
}
private void removeTabIfNotReferenced(TabBlueprint blueprint) {
if (blueprint.isHidden() && !blueprint.isReferenced()) {
tabsMap.remove(blueprint.getNode());
}
}
public void closeAllTabs() {
closeAllTabs(false);
}
public void forceCloseAllTabs() {
forceClose = true;
closeAllTabs();
forceClose = false;
}
public boolean isForceClose() {
return forceClose;
}
public void closeAllTabs(boolean considerPins) {
List.copyOf(tabsMap.values()).forEach(t -> closeTab(t.getNode(), considerPins));
}
public void unpinAllTabs() {
tabsMap.values().forEach(t -> setTabPinned(t.getNode(), false));
}
public void unbookmarkAllTabs() {
tabsMap.values().forEach(t -> setTabBookmarked(t.getNode(), false));
}
public TabBlueprint getSelectedTab() {
return selectedTab;
}
public List<TabBlueprint> getTabs() {
return List.copyOf(tabsMap.values());
}
public List<TabBlueprint> getOpenTabs() {
return List.copyOf(tabsMap.values());
}
public List<TabBlueprint> getPinnedTabs() {
return tabsMap.values().stream()
.filter(TabBlueprint::isPinned)
.collect(Collectors.toUnmodifiableList());
}
public List<TabBlueprint> getBookmarkedTabs() {
return tabsMap.values().stream()
.filter(TabBlueprint::isBookmarked)
.collect(Collectors.toUnmodifiableList());
}
public void restoreEditorViewState(EditorViewState viewState) {
JNode node = viewState.getNode();
TabBlueprint blueprint = openTab(node, viewState.isHidden());
setTabPinnedInternal(blueprint, viewState.isPinned());
setTabBookmarkedInternal(blueprint, viewState.isBookmarked());
listeners.forEach(l -> l.onTabRestore(blueprint, viewState));
if (viewState.isActive()) {
selectTab(node);
}
}
public void notifyRestoreEditorViewStateDone() {
listeners.forEach(ITabStatesListener::onTabsRestoreDone);
}
public List<EditorViewState> getEditorViewStates() {
ArrayList<TabBlueprint> reorderedTabs = new ArrayList<>(tabsMap.values());
listeners.forEach(l -> l.onTabsReorder(reorderedTabs));
List<EditorViewState> states = new ArrayList<>();
for (TabBlueprint blueprint : reorderedTabs) {
states.add(getEditorViewState(blueprint));
}
return states;
}
public EditorViewState getEditorViewState(TabBlueprint blueprint) {
EditorViewState viewState = new EditorViewState(blueprint.getNode());
listeners.forEach(l -> l.onTabSave(blueprint, viewState));
viewState.setActive(blueprint == selectedTab);
viewState.setPinned(blueprint.isPinned());
viewState.setBookmarked(blueprint.isBookmarked());
viewState.setHidden(blueprint.isHidden());
return viewState;
}
}
@@ -34,7 +34,7 @@ public class StartPageNode extends JNode {
}
@Override
public boolean isPinnable() {
public boolean supportsQuickTabs() {
return false;
}
}
@@ -19,6 +19,10 @@ public class Icons {
public static final ImageIcon PIN_DARK = UiUtils.openSvgIcon("nodes/pin_dark");
public static final ImageIcon PIN_HOVERED = UiUtils.openSvgIcon("nodes/pinHovered");
public static final ImageIcon PIN_HOVERED_DARK = UiUtils.openSvgIcon("nodes/pinHovered_dark");
public static final ImageIcon BOOKMARK = UiUtils.openSvgIcon("nodes/bookmark");
public static final ImageIcon BOOKMARK_OVERLAY = UiUtils.openSvgIcon("nodes/bookmark_overlay");
public static final ImageIcon BOOKMARK_DARK = UiUtils.openSvgIcon("nodes/bookmark_dark");
public static final ImageIcon BOOKMARK_OVERLAY_DARK = UiUtils.openSvgIcon("nodes/bookmark_overlay_dark");
public static final ImageIcon STATIC = openSvgIcon("nodes/staticMark");
public static final ImageIcon FINAL = openSvgIcon("nodes/finalMark");
@@ -54,6 +54,14 @@ public class OverlayIcon implements Icon {
icons.add(icon);
}
public void remove(Icon icon) {
icons.remove(icon);
}
public void clear() {
icons.clear();
}
public List<Icon> getIcons() {
return icons;
}
@@ -60,6 +60,8 @@ tree.sources_title=Quelltexte
tree.resources_title=Ressourcen
tree.loading=Laden…
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=Laden
progress.save_mappings=Zuordnungen exportieren
@@ -83,6 +85,9 @@ tabs.close=Schließen
tabs.closeOthers=Andere schließen
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=Alles schließen
tabs.closeAllRight=Schließe alles rechts
@@ -60,6 +60,8 @@ tree.sources_title=Source code
tree.resources_title=Resources
tree.loading=Loading...
tree.pinned_tabs=Pinned Tabs
tree.open_tabs=Open Tabs
tree.bookmarked_tabs=Bookmarked Tabs
progress.load=Loading
progress.save_mappings=Saving mappings
@@ -83,6 +85,9 @@ tabs.close=Close
tabs.closeOthers=Close Others
tabs.unpin=Unpin
tabs.unpin_all=Unpin All
tabs.bookmark=Bookmark
tabs.unbookmark=Unbookmark
tabs.unbookmark_all=Unbookmark All
tabs.pin=Pin
tabs.closeAll=Close All
tabs.closeAllRight=Close All Right
@@ -60,6 +60,8 @@ tree.sources_title=Código fuente
tree.resources_title=Recursos
tree.loading=Cargando...
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=Cargando
#progress.save_mappings=
@@ -83,6 +85,9 @@ tabs.close=Cerrar
tabs.closeOthers=Cerrar otros
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=Cerrar todo
tabs.closeAllRight=Cierra todo a la derecha
@@ -60,6 +60,8 @@ tree.sources_title=Kode Sumber
tree.resources_title=Sumber Daya
tree.loading=Memuat...
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=Memuat
progress.save_mappings=Menyimpan pemetaan
@@ -83,6 +85,9 @@ tabs.close=Tutup
tabs.closeOthers=Tutup yang Lain
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=Tutup Semua
tabs.closeAllRight=Tutup Semua yang Kanan
@@ -60,6 +60,8 @@ tree.sources_title=소스코드
tree.resources_title=리소스
tree.loading=로딩중...
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=로딩중
progress.save_mappings=매핑 내보내는 중
@@ -83,6 +85,9 @@ tabs.close=닫기
tabs.closeOthers=이 탭을 제외하고 닫기
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=모두 닫기
tabs.closeAllRight=오른쪽의 모든 것을 닫으십시오
@@ -60,6 +60,8 @@ tree.sources_title=Código fonte
tree.resources_title=Recursos
tree.loading=Carregando...
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=Carregando
#progress.save_mappings=Saving mappings
@@ -83,6 +85,9 @@ tabs.close=Fechar
tabs.closeOthers=Fechar outros
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=Fechar todos
tabs.closeAllRight=Feche tudo à direita
@@ -60,6 +60,8 @@ tree.sources_title=Исходный код
tree.resources_title=Ресурсы
tree.loading=Загрузка...
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=Загрузка
progress.save_mappings=Сохранить маппинги
@@ -83,6 +85,9 @@ tabs.close=Закрыть
tabs.closeOthers=Закрыть другие
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=Закрыть все
tabs.closeAllRight=Закройте все справа
@@ -60,6 +60,8 @@ tree.sources_title=源代码
tree.resources_title=资源文件
tree.loading=加载中…
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=正在加载
progress.save_mappings=导出映射
@@ -83,6 +85,9 @@ tabs.close=关闭
tabs.closeOthers=关闭其他
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=关闭全部
tabs.closeAllRight=关闭右边的所有
@@ -60,6 +60,8 @@ tree.sources_title=原始碼
tree.resources_title=資源
tree.loading=載入中...
#tree.pinned_tabs=Pinned Tabs
#tree.open_tabs=Open Tabs
#tree.bookmarked_tabs=Bookmarked Tabs
progress.load=載入中
progress.save_mappings=正在匯出對應
@@ -83,6 +85,9 @@ tabs.close=關閉
tabs.closeOthers=關閉其他
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.bookmark=Bookmark
#tabs.unbookmark=Unbookmark
#tabs.unbookmark_all=Unbookmark All
#tabs.pin=Pin
tabs.closeAll=關閉全部
tabs.closeAllRight=關閉右邊的所有
@@ -0,0 +1,4 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3.5C3 2.39543 3.89543 1.5 5 1.5H10.9989C12.1034 1.5 12.9989 2.39543 12.9989 3.5V14.9184C12.9989 15.3379 12.5134 15.5709 12.1861 15.3085L7.99943 11.9524L3.81273 15.3085C3.48543 15.5709 3 15.3379 3 14.9184V3.5Z" fill="#FFAF0F"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3.5C3 2.39543 3.89543 1.5 5 1.5H10.9989C12.1034 1.5 12.9989 2.39543 12.9989 3.5V14.9184C12.9989 15.3379 12.5134 15.5709 12.1861 15.3085L7.99943 11.9524L3.81273 15.3085C3.48543 15.5709 3 15.3379 3 14.9184V3.5Z" fill="#F2C55C"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3.5C3 2.39543 3.89543 1.5 5 1.5H10.9989C12.1034 1.5 12.9989 2.39543 12.9989 3.5V14.9184C12.9989 15.3379 12.5134 15.5709 12.1861 15.3085L7.99943 11.9524L3.81273 15.3085C3.48543 15.5709 3 15.3379 3 14.9184V3.5Z" fill="#FFAF0F"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B

@@ -0,0 +1,4 @@
<!-- Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -->
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M3 3.5C3 2.39543 3.89543 1.5 5 1.5H10.9989C12.1034 1.5 12.9989 2.39543 12.9989 3.5V14.9184C12.9989 15.3379 12.5134 15.5709 12.1861 15.3085L7.99943 11.9524L3.81273 15.3085C3.48543 15.5709 3 15.3379 3 14.9184V3.5Z" fill="#F2C55C"/>
</svg>

After

Width:  |  Height:  |  Size: 469 B