feat(gui): pin tabs (PR #2230)

* add ability to pin tabs

* save pinned tabs with Save Project action

* further prevent closing pinned tabs

* add translation entries

* prevent pinning start page

* add pinned tabs tree view

* properly dispose of quickTabsTree

* restructure code

* more unpin context menu items
This commit is contained in:
Mino
2024-08-01 18:06:45 +01:00
committed by GitHub
parent 61855a7ea1
commit 6ab224ea0d
31 changed files with 686 additions and 32 deletions
@@ -136,6 +136,8 @@ public class JadxSettings extends JadxCLIArgs {
private boolean dockLogViewer = true;
private boolean dockQuickTabs = false;
private TabDndGhostType tabDndGhostType = TabDndGhostType.OUTLINE;
private int settingsVersion = CURRENT_SETTINGS_VERSION;
@@ -742,7 +744,16 @@ public class JadxSettings extends JadxCLIArgs {
public void setDockLogViewer(boolean dockLogViewer) {
this.dockLogViewer = dockLogViewer;
partialSync(settings -> this.dockLogViewer = dockLogViewer);
partialSync(settings -> settings.dockLogViewer = dockLogViewer);
}
public boolean isDockQuickTabs() {
return dockQuickTabs;
}
public void setDockQuickTabs(boolean dockQuickTabs) {
this.dockQuickTabs = dockQuickTabs;
partialSync(settings -> settings.dockQuickTabs = dockQuickTabs);
}
public XposedCodegenLanguage getXposedCodegenLanguage() {
@@ -29,6 +29,7 @@ public class TabStateViewAdapter {
tvs.setCaret(viewState.getCaretPos());
tvs.setView(new ViewPoint(viewState.getViewPoint()));
tvs.setActive(viewState.isActive());
tvs.setPinned(viewState.isPinned());
return tvs;
}
@@ -41,6 +42,7 @@ public class TabStateViewAdapter {
}
EditorViewState viewState = new EditorViewState(node, tvs.getSubPath(), tvs.getCaret(), tvs.getView().toPoint());
viewState.setActive(tvs.isActive());
viewState.setPinned(tvs.isPinned());
return viewState;
} catch (Exception e) {
LOG.error("Failed to load tab state: " + tvs, e);
@@ -7,6 +7,7 @@ public class TabViewState {
private int caret;
private ViewPoint view;
boolean active;
boolean pinned;
public String getType() {
return type;
@@ -55,4 +56,12 @@ public class TabViewState {
public void setActive(boolean active) {
this.active = active;
}
public boolean isPinned() {
return pinned;
}
public void setPinned(boolean pinned) {
this.pinned = pinned;
}
}
@@ -68,6 +68,10 @@ public abstract class JNode extends DefaultMutableTreeNode implements Comparable
return javaNode.getName();
}
public boolean isPinnable() {
return true;
}
public @Nullable JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
return null;
}
@@ -7,7 +7,6 @@ import java.awt.DisplayMode;
import java.awt.Font;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTarget;
import java.awt.event.ActionEvent;
@@ -140,6 +139,7 @@ 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.QuickTabsTree;
import jadx.gui.ui.tab.TabbedPane;
import jadx.gui.ui.tab.dnd.TabDndController;
import jadx.gui.ui.treenodes.StartPageNode;
@@ -203,6 +203,7 @@ public class MainWindow extends JFrame {
private transient JSplitPane treeSplitPane;
private transient JSplitPane rightSplitPane;
private transient JSplitPane bottomSplitPane;
private transient JSplitPane quickTabsAndCodeSplitPane;
private JTree tree;
private DefaultTreeModel treeModel;
@@ -228,6 +229,7 @@ public class MainWindow extends JFrame {
private transient IssuesPanel issuesPanel;
private transient @Nullable LogPanel logPanel;
private transient @Nullable JDebuggerPanel debuggerPanel;
private transient @Nullable QuickTabsTree quickTabsTree;
private final List<ILoadListener> loadListeners = new ArrayList<>();
private final List<Consumer<JRoot>> treeUpdateListener = new ArrayList<>();
@@ -888,27 +890,11 @@ public class MainWindow extends JFrame {
@Nullable
private JNode getJNodeUnderMouse(MouseEvent mouseEvent) {
TreePath path = tree.getClosestPathForLocation(mouseEvent.getX(), mouseEvent.getY());
if (path == null) {
return null;
}
// allow 'closest' path only at the right of the item row
Rectangle pathBounds = tree.getPathBounds(path);
if (pathBounds != null) {
int y = mouseEvent.getY();
if (y < pathBounds.y || y > (pathBounds.y + pathBounds.height)) {
return null;
}
if (mouseEvent.getX() < pathBounds.x) {
// exclude expand/collapse events
return null;
}
}
Object obj = path.getLastPathComponent();
if (obj instanceof JNode) {
tree.setSelectionPath(path);
return (JNode) obj;
TreeNode treeNode = UiUtils.getTreeNodeUnderMouse(tree, mouseEvent);
if (treeNode instanceof JNode) {
return (JNode) treeNode;
}
return null;
}
@@ -1061,6 +1047,17 @@ public class MainWindow extends JFrame {
dockLog.setState(settings.isDockLogViewer());
dockLog.addActionListener(event -> settings.setDockLogViewer(!settings.isDockLogViewer()));
JCheckBoxMenuItem dockQuickTabs = new JCheckBoxMenuItem(NLS.str("menu.dock_quick_tabs"));
dockQuickTabs.setState(settings.isDockQuickTabs());
dockQuickTabs.addActionListener(event -> {
boolean visible = quickTabsTree == null;
setQuickTabsVisibility(visible);
settings.setDockQuickTabs(visible);
});
if (dockQuickTabs.getState()) {
setQuickTabsVisibility(true);
}
JadxGuiAction syncAction = new JadxGuiAction(ActionModel.SYNC, this::syncWithEditor);
JadxGuiAction textSearchAction = new JadxGuiAction(ActionModel.TEXT_SEARCH, this::textSearch);
JadxGuiAction clsSearchAction = new JadxGuiAction(ActionModel.CLASS_SEARCH,
@@ -1124,6 +1121,7 @@ public class MainWindow extends JFrame {
view.add(heapUsageBarMenuItem);
view.add(alwaysSelectOpened);
view.add(dockLog);
view.add(dockQuickTabs);
JMenu nav = new JadxMenu(NLS.str("menu.navigation"), shortcutsController);
nav.setMnemonic(KeyEvent.VK_N);
@@ -1348,8 +1346,13 @@ public class MainWindow extends JFrame {
tabbedPane.setMinimumSize(new Dimension(150, 150));
new TabDndController(tabbedPane, settings);
quickTabsAndCodeSplitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);
quickTabsAndCodeSplitPane.setResizeWeight(0.15);
quickTabsAndCodeSplitPane.setDividerSize(0);
quickTabsAndCodeSplitPane.setRightComponent(tabbedPane);
rightSplitPane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
rightSplitPane.setTopComponent(tabbedPane);
rightSplitPane.setTopComponent(quickTabsAndCodeSplitPane);
rightSplitPane.setResizeWeight(SPLIT_PANE_RESIZE_WEIGHT);
treeSplitPane.setRightComponent(rightSplitPane);
@@ -1482,6 +1485,9 @@ public class MainWindow extends JFrame {
if (logPanel != null) {
logPanel.loadSettings();
}
if (quickTabsTree != null) {
quickTabsTree.loadSettings();
}
shortcutsController.loadSettings();
}
@@ -1654,6 +1660,25 @@ public class MainWindow extends JFrame {
rightSplitPane.setBottomComponent(null);
}
private void setQuickTabsVisibility(boolean visible) {
if (visible) {
if (quickTabsTree == null) {
quickTabsTree = new QuickTabsTree(this);
}
quickTabsAndCodeSplitPane.setLeftComponent(quickTabsTree);
quickTabsAndCodeSplitPane.setDividerSize(5);
} else {
quickTabsAndCodeSplitPane.setLeftComponent(null);
quickTabsAndCodeSplitPane.setDividerSize(0);
if (quickTabsTree != null) {
quickTabsTree.dispose();
quickTabsTree = null;
}
}
}
public JMenu getPluginsMenu() {
return pluginsMenu;
}
@@ -12,6 +12,7 @@ public class EditorViewState {
private final Point viewPoint;
private final String subPath;
private boolean active;
private boolean pinned;
public EditorViewState(JNode node, String subPath, int caretPos, Point viewPoint) {
this.node = node;
@@ -44,6 +45,14 @@ public class EditorViewState {
this.active = active;
}
public boolean isPinned() {
return pinned;
}
public void setPinned(boolean pinned) {
this.pinned = pinned;
}
@Override
public String toString() {
return "EditorViewState{node=" + node
@@ -15,6 +15,7 @@ public abstract class ContentPanel extends JPanel {
protected TabbedPane tabbedPane;
protected JNode node;
private boolean pinned;
protected ContentPanel(TabbedPane panel, JNode jnode) {
tabbedPane = panel;
@@ -50,6 +51,18 @@ public abstract class ContentPanel extends JPanel {
return tabbedPane.getMainWindow().getSettings();
}
public boolean isPinned() {
return pinned;
}
public void setPinned(boolean pinned) {
this.pinned = pinned;
}
public boolean isPinnable() {
return node.isPinnable();
}
public void dispose() {
tabbedPane = null;
node = null;
@@ -0,0 +1,7 @@
package jadx.gui.ui.tab;
import jadx.gui.treemodel.JNode;
public interface ITabStatesListener {
void onTabPinChange(JNode node, boolean pinned);
}
@@ -0,0 +1,17 @@
package jadx.gui.ui.tab;
import javax.swing.Icon;
import javax.swing.JPopupMenu;
import javax.swing.tree.DefaultMutableTreeNode;
import jadx.gui.ui.MainWindow;
abstract class QuickTabsBaseNode extends DefaultMutableTreeNode {
JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
return null;
}
Icon getIcon() {
return null;
}
}
@@ -0,0 +1,54 @@
package jadx.gui.ui.tab;
import javax.swing.Icon;
import javax.swing.JMenuItem;
import javax.swing.JPopupMenu;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.NLS;
public class QuickTabsChildNode extends QuickTabsBaseNode {
private final JNode node;
public QuickTabsChildNode(JNode node) {
this.node = node;
}
@Override
public String toString() {
return node.toString();
}
public JNode getJNode() {
return node;
}
@Override
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();
}
});
menu.add(unpinAction, 0);
menu.add(new JPopupMenu.Separator(), 1);
}
return menu;
}
@Override
Icon getIcon() {
return node.getIcon();
}
}
@@ -0,0 +1,57 @@
package jadx.gui.ui.tab;
import java.util.HashMap;
import java.util.Map;
import javax.swing.JPopupMenu;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
abstract class QuickTabsParentNode extends QuickTabsBaseNode {
private final TabbedPane tabbedPane;
private final Map<JNode, QuickTabsChildNode> childrenMap = new HashMap<>();
protected QuickTabsParentNode(TabbedPane tabbedPane) {
super();
this.tabbedPane = tabbedPane;
}
public void addJNode(JNode node) {
if (childrenMap.containsKey(node)) {
return;
}
QuickTabsChildNode childNode = new QuickTabsChildNode(node);
childrenMap.put(node, childNode);
add(childNode);
}
public void removeJNode(JNode node) {
QuickTabsChildNode childNode = childrenMap.remove(node);
if (childNode == null) {
return;
}
remove(childNode);
}
public QuickTabsChildNode getQuickTabsNode(JNode node) {
return childrenMap.get(node);
}
public TabbedPane getTabbedPane() {
return tabbedPane;
}
abstract String getTitle();
@Override
public String toString() {
return getTitle();
}
@Override
JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
return super.onTreePopupMenu(mainWindow);
}
}
@@ -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 QuickTabsPinParentNode extends QuickTabsParentNode {
protected QuickTabsPinParentNode(TabbedPane tabbedPane) {
super(tabbedPane);
}
@Override
public String getTitle() {
return NLS.str("tree.pinned_tabs");
}
@Override
Icon getIcon() {
return Icons.PIN;
}
@Override
JPopupMenu onTreePopupMenu(MainWindow mainWindow) {
if (getChildCount() == 0) {
return null;
}
JPopupMenu menu = new JPopupMenu();
JMenuItem unpinAll = new JMenuItem(NLS.str("tabs.unpin_all"));
unpinAll.addActionListener(e -> getTabbedPane().unpinAll());
menu.add(unpinAll);
return menu;
}
}
@@ -0,0 +1,157 @@
package jadx.gui.ui.tab;
import java.awt.Component;
import java.awt.Font;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JPopupMenu;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import jadx.gui.treemodel.JNode;
import jadx.gui.ui.MainWindow;
import jadx.gui.utils.UiUtils;
public class QuickTabsTree extends JTree implements ITabStatesListener, TreeSelectionListener {
private final MainWindow mainWindow;
private final DefaultTreeModel treeModel;
private final QuickTabsParentNode pinParentNode;
public QuickTabsTree(MainWindow mainWindow) {
this.mainWindow = mainWindow;
mainWindow.getTabbedPane().addTabStateListener(this);
Root root = new Root();
pinParentNode = new QuickTabsPinParentNode(mainWindow.getTabbedPane());
fillPinParentNode();
root.add(pinParentNode);
treeModel = new DefaultTreeModel(root);
setModel(treeModel);
setCellRenderer(new CellRenderer());
setRootVisible(false);
setShowsRootHandles(true);
addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
TreeNode pressedNode = UiUtils.getTreeNodeUnderMouse(QuickTabsTree.this, e);
if (SwingUtilities.isLeftMouseButton(e)) {
if (nodeClickAction(pressedNode)) {
setFocusable(true);
requestFocus();
}
}
if (SwingUtilities.isRightMouseButton(e)) {
triggerRightClickAction(e);
}
}
});
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
nodeClickAction(getLastSelectedPathComponent());
}
}
});
loadSettings();
}
private void triggerRightClickAction(MouseEvent e) {
TreeNode treeNode = UiUtils.getTreeNodeUnderMouse(this, e);
if (!(treeNode instanceof QuickTabsBaseNode)) {
return;
}
QuickTabsBaseNode quickTabsNode = (QuickTabsBaseNode) treeNode;
JPopupMenu menu = quickTabsNode.onTreePopupMenu(mainWindow);
if (menu != null) {
menu.show(e.getComponent(), e.getX(), e.getY());
}
}
private boolean nodeClickAction(Object pressedNode) {
if (pressedNode == null) {
return false;
}
if (pressedNode instanceof QuickTabsChildNode) {
QuickTabsChildNode childNode = (QuickTabsChildNode) pressedNode;
return mainWindow.getTabbedPane().showNode(childNode.getJNode());
}
return false;
}
private void fillPinParentNode() {
mainWindow.getTabbedPane().getPinnedTabs().forEach((contentPanel) -> pinParentNode.addJNode(contentPanel.getNode()));
}
@Override
public void valueChanged(TreeSelectionEvent event) {
DefaultMutableTreeNode selectedNode = (DefaultMutableTreeNode) getLastSelectedPathComponent();
if (selectedNode != null) {
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 });
}
}
public void loadSettings() {
Font font = mainWindow.getSettings().getFont();
Font largerFont = font.deriveFont(font.getSize() + 2.f);
setFont(largerFont);
}
public void dispose() {
mainWindow.getTabbedPane().removeTabStateListener(this);
}
private class Root extends DefaultMutableTreeNode {
}
private class CellRenderer extends DefaultTreeCellRenderer {
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value, boolean sel, boolean expanded, boolean leaf, int row,
boolean hasFocus) {
Component c = super.getTreeCellRendererComponent(tree, value, selected, expanded, leaf, row, hasFocus);
if (value instanceof QuickTabsBaseNode) {
QuickTabsBaseNode quickTabsNode = (QuickTabsBaseNode) value;
setIcon(quickTabsNode.getIcon());
}
return c;
}
}
}
@@ -38,6 +38,8 @@ public class TabComponent extends JPanel {
private final ContentPanel contentPanel;
private JLabel label;
private JButton pinBtn;
private JButton closeBtn;
public TabComponent(TabbedPane tabbedPane, ContentPanel contentPanel) {
this.tabbedPane = tabbedPane;
@@ -74,7 +76,18 @@ public class TabComponent extends JPanel {
((JEditableNode) node).addChangeListener(c -> label.setText(buildTabTitle(node)));
}
final JButton closeBtn = new JButton();
pinBtn = new JButton();
pinBtn.setIcon(Icons.PIN);
pinBtn.setRolloverIcon(Icons.PIN_HOVERED);
pinBtn.setRolloverEnabled(true);
pinBtn.setOpaque(false);
pinBtn.setUI(new BasicButtonUI());
pinBtn.setContentAreaFilled(false);
pinBtn.setBorder(null);
pinBtn.setBorderPainted(false);
pinBtn.addActionListener(e -> togglePin());
closeBtn = new JButton();
closeBtn.setIcon(Icons.CLOSE_INACTIVE);
closeBtn.setRolloverIcon(Icons.CLOSE);
closeBtn.setRolloverEnabled(true);
@@ -84,13 +97,13 @@ public class TabComponent extends JPanel {
closeBtn.setFocusable(false);
closeBtn.setBorder(null);
closeBtn.setBorderPainted(false);
closeBtn.addActionListener(e -> tabbedPane.closeCodePanel(contentPanel));
closeBtn.addActionListener(e -> tabbedPane.closeCodePanel(contentPanel, true));
MouseAdapter clickAdapter = new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isMiddleMouseButton(e)) {
tabbedPane.closeCodePanel(contentPanel);
tabbedPane.closeCodePanel(contentPanel, true);
} else if (SwingUtilities.isRightMouseButton(e)) {
JPopupMenu menu = createTabPopupMenu(contentPanel);
menu.show(e.getComponent(), e.getX(), e.getY());
@@ -105,10 +118,38 @@ public class TabComponent extends JPanel {
addListenerForDnd();
add(label);
add(closeBtn);
updateCloseOrPinButton();
setBorder(BorderFactory.createEmptyBorder(0, 0, 0, 0));
}
public void updateCloseOrPinButton() {
if (contentPanel.isPinned()) {
if (closeBtn.isShowing()) {
remove(closeBtn);
}
if (!pinBtn.isShowing()) {
add(pinBtn);
}
}
if (!contentPanel.isPinned()) {
if (pinBtn.isShowing()) {
remove(pinBtn);
}
if (!closeBtn.isShowing()) {
add(closeBtn);
}
}
}
public void togglePin() {
contentPanel.setPinned(!contentPanel.isPinned());
updateCloseOrPinButton();
if (contentPanel.isPinned()) {
tabbedPane.advanceTab(this);
}
tabbedPane.notifyTabStateChange(this, true);
}
private void addListenerForDnd() {
if (tabbedPane.getDnd() == null) {
return;
@@ -150,8 +191,22 @@ public class TabComponent extends JPanel {
menu.addSeparator();
}
if (contentPanel.isPinnable()) {
String pinTitle = contentPanel.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());
menu.add(unpinAll);
}
JMenuItem closeTab = new JMenuItem(NLS.str("tabs.close"));
closeTab.addActionListener(e -> tabbedPane.closeCodePanel(contentPanel));
closeTab.addActionListener(e -> tabbedPane.closeCodePanel(contentPanel, true));
if (contentPanel.isPinned()) {
closeTab.setEnabled(false);
}
menu.add(closeTab);
List<ContentPanel> tabs = tabbedPane.getTabs();
@@ -160,14 +215,14 @@ public class TabComponent extends JPanel {
closeOther.addActionListener(e -> {
for (ContentPanel panel : tabs) {
if (panel != contentPanel) {
tabbedPane.closeCodePanel(panel);
tabbedPane.closeCodePanel(panel, true);
}
}
});
menu.add(closeOther);
JMenuItem closeAll = new JMenuItem(NLS.str("tabs.closeAll"));
closeAll.addActionListener(e -> tabbedPane.closeAllTabs());
closeAll.addActionListener(e -> tabbedPane.closeAllTabs(true));
menu.add(closeAll);
if (contentPanel != ListUtils.last(tabs)) {
@@ -180,7 +235,7 @@ public class TabComponent extends JPanel {
pastCurrentPanel = true;
}
} else {
tabbedPane.closeCodePanel(panel);
tabbedPane.closeCodePanel(panel, true);
}
}
});
@@ -212,4 +267,8 @@ public class TabComponent extends JPanel {
}
return node.getName();
}
public ContentPanel getContentPanel() {
return contentPanel;
}
}
@@ -49,6 +49,8 @@ public class TabbedPane extends JTabbedPane {
private final transient MainWindow mainWindow;
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;
@@ -343,6 +345,7 @@ public class TabbedPane extends JTabbedPane {
viewState = new EditorViewState(panel.getNode(), "", 0, EditorViewState.ZERO);
}
viewState.setActive(panel == selected);
viewState.setPinned(panel.isPinned());
states.add(viewState);
}
return states;
@@ -356,6 +359,17 @@ public class TabbedPane extends JTabbedPane {
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() {
@@ -386,11 +400,42 @@ public class TabbedPane extends JTabbedPane {
}
public void closeCodePanel(ContentPanel contentPanel) {
closeCodePanel(contentPanel, false);
}
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);
}
public List<ContentPanel> getTabs() {
List<ContentPanel> list = new ArrayList<>(getTabCount());
for (int i = 0; i < getTabCount(); i++) {
@@ -399,10 +444,41 @@ 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);
}
public @Nullable TabComponent getTabComponentByNode(JNode node) {
Component component = getTabComponentAt(indexOfComponent(getTabByNode(node)));
if (!(component instanceof TabComponent)) {
return null;
}
return (TabComponent) component;
}
private @Nullable ContentPanel getContentPanel(JNode node) {
ContentPanel panel = getTabByNode(node);
if (panel != null) {
@@ -417,6 +493,10 @@ public class TabbedPane extends JTabbedPane {
return newPanel;
}
public void unpinAll() {
getPinnedTabComponents().forEach(TabComponent::togglePin);
}
public void refresh(JNode node) {
ContentPanel panel = getTabByNode(node);
if (panel != null) {
@@ -464,8 +544,12 @@ public class TabbedPane extends JTabbedPane {
}
public void closeAllTabs() {
closeAllTabs(false);
}
public void closeAllTabs(boolean considerPins) {
for (ContentPanel panel : getTabs()) {
closeCodePanel(panel);
closeCodePanel(panel, considerPins);
}
}
@@ -32,4 +32,9 @@ public class StartPageNode extends JNode {
public JClass getJParent() {
return null;
}
@Override
public boolean isPinnable() {
return false;
}
}
@@ -15,6 +15,11 @@ public class Icons {
public static final ImageIcon SAVE_ALL = UiUtils.openSvgIcon("ui/menu-saveall");
public static final ImageIcon PIN = UiUtils.openSvgIcon("nodes/pin");
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 STATIC = openSvgIcon("nodes/staticMark");
public static final ImageIcon FINAL = openSvgIcon("nodes/finalMark");
@@ -4,6 +4,7 @@ import java.awt.Component;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.Window;
import java.awt.datatransfer.Clipboard;
@@ -12,6 +13,7 @@ import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
@@ -24,9 +26,12 @@ import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.RootPaneContainer;
import javax.swing.SwingUtilities;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.intellij.lang.annotations.MagicConstant;
import org.jetbrains.annotations.TestOnly;
@@ -296,6 +301,31 @@ public class UiUtils {
return pos;
}
public static TreeNode getTreeNodeUnderMouse(JTree tree, MouseEvent mouseEvent) {
TreePath path = tree.getClosestPathForLocation(mouseEvent.getX(), mouseEvent.getY());
if (path == null) {
return null;
}
// allow 'closest' path only at the right of the item row
Rectangle pathBounds = tree.getPathBounds(path);
if (pathBounds != null) {
int y = mouseEvent.getY();
if (y < pathBounds.y || y > (pathBounds.y + pathBounds.height)) {
return null;
}
if (mouseEvent.getX() < pathBounds.x) {
// exclude expand/collapse events
return null;
}
}
Object obj = path.getLastPathComponent();
if (obj instanceof TreeNode) {
tree.setSelectionPath(path);
return (TreeNode) obj;
}
return null;
}
public static String getEnvVar(String varName, String defValue) {
String envVal = System.getenv(varName);
if (envVal == null) {
@@ -10,6 +10,7 @@ menu.flatten=Codepaket erweitern
menu.heapUsageBar=Speicherverbrauchsleiste anzeigen
menu.alwaysSelectOpened=Immer geöffnete Datei/Klasse auswählen
#menu.dock_log=Dock log viewer
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=Navigation
menu.text_search=Textsuche
menu.class_search=Klassen-Suche
@@ -58,6 +59,7 @@ file.exit=Beenden
tree.sources_title=Quelltexte
tree.resources_title=Ressourcen
tree.loading=Laden…
#tree.pinned_tabs=Pinned Tabs
progress.load=Laden
progress.save_mappings=Zuordnungen exportieren
@@ -79,6 +81,9 @@ search.find=Suchen
tabs.copy_class_name=Klassennamen kopieren
tabs.close=Schließen
tabs.closeOthers=Andere schließen
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=Alles schließen
tabs.closeAllRight=Schließe alles rechts
tabs.code=Code
@@ -10,6 +10,7 @@ menu.flatten=Show flatten packages
menu.heapUsageBar=Show memory usage bar
menu.alwaysSelectOpened=Always Select Opened File/Class
menu.dock_log=Dock log viewer
menu.dock_quick_tabs=Dock quick tabs
menu.navigation=Navigation
menu.text_search=Text search
menu.class_search=Class search
@@ -58,6 +59,7 @@ tree.input_scripts=Scripts
tree.sources_title=Source code
tree.resources_title=Resources
tree.loading=Loading...
tree.pinned_tabs=Pinned Tabs
progress.load=Loading
progress.save_mappings=Saving mappings
@@ -79,6 +81,9 @@ search.results=%s%d results
tabs.copy_class_name=Copy Name
tabs.close=Close
tabs.closeOthers=Close Others
tabs.unpin=Unpin
tabs.unpin_all=Unpin All
tabs.pin=Pin
tabs.closeAll=Close All
tabs.closeAllRight=Close All Right
tabs.code=Code
@@ -10,6 +10,7 @@ menu.flatten=Mostrar paquetes en vista plana
#menu.heapUsageBar=
#menu.alwaysSelectOpened=Always Select Opened File/Class
#menu.dock_log=Dock log viewer
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=Navegación
menu.text_search=Buscar texto
menu.class_search=Buscar clase
@@ -58,6 +59,7 @@ file.exit=Salir
tree.sources_title=Código fuente
tree.resources_title=Recursos
tree.loading=Cargando...
#tree.pinned_tabs=Pinned Tabs
progress.load=Cargando
#progress.save_mappings=
@@ -79,6 +81,9 @@ search.find=Buscar
tabs.copy_class_name=Copy Name
tabs.close=Cerrar
tabs.closeOthers=Cerrar otros
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=Cerrar todo
tabs.closeAllRight=Cierra todo a la derecha
#tabs.code=
@@ -10,6 +10,7 @@ menu.flatten=Tampilkan paket yang diratakan
menu.heapUsageBar=Tampilkan penggunaan memori
menu.alwaysSelectOpened=Selalu Pilih Berkas/Kelas yang Terbuka
menu.dock_log=Kaitkan pemantau log
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=Navigasi
menu.text_search=Pencarian Teks
menu.class_search=Pencarian Kelas
@@ -58,6 +59,7 @@ tree.input_scripts=Skrip
tree.sources_title=Kode Sumber
tree.resources_title=Sumber Daya
tree.loading=Memuat...
#tree.pinned_tabs=Pinned Tabs
progress.load=Memuat
progress.save_mappings=Menyimpan pemetaan
@@ -79,6 +81,9 @@ search.results=%s%d hasil
tabs.copy_class_name=Salin Nama
tabs.close=Tutup
tabs.closeOthers=Tutup yang Lain
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=Tutup Semua
tabs.closeAllRight=Tutup Semua yang Kanan
tabs.code=Kode
@@ -10,6 +10,7 @@ menu.flatten=플랫 패키지 표시
menu.heapUsageBar=메모리 사용량 표시
menu.alwaysSelectOpened=항상 열린 파일/클래스 선택
#menu.dock_log=Dock log viewer
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=네비게이션
menu.text_search=텍스트 검색
menu.class_search=클래스 검색
@@ -58,6 +59,7 @@ start_page.recent=최근 프로젝트
tree.sources_title=소스코드
tree.resources_title=리소스
tree.loading=로딩중...
#tree.pinned_tabs=Pinned Tabs
progress.load=로딩중
progress.save_mappings=매핑 내보내는 중
@@ -79,6 +81,9 @@ search.find=찾기
tabs.copy_class_name=이름 복사
tabs.close=닫기
tabs.closeOthers=이 탭을 제외하고 닫기
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=모두 닫기
tabs.closeAllRight=오른쪽의 모든 것을 닫으십시오
tabs.code=코드
@@ -10,6 +10,7 @@ menu.flatten=Mostrar pacotes achatados
menu.heapUsageBar=Mostrar uso de memória
menu.alwaysSelectOpened=Sempre selecionar arquivo/classe aberta
#menu.dock_log=Dock log viewer
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=Navegação
menu.text_search=Buscar por texto
menu.class_search=Buscar por classe
@@ -58,6 +59,7 @@ start_page.recent=Projetos recentes
tree.sources_title=Código fonte
tree.resources_title=Recursos
tree.loading=Carregando...
#tree.pinned_tabs=Pinned Tabs
progress.load=Carregando
#progress.save_mappings=Saving mappings
@@ -79,6 +81,9 @@ search.find=Encontrar
tabs.copy_class_name=Copiar nome
tabs.close=Fechar
tabs.closeOthers=Fechar outros
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=Fechar todos
tabs.closeAllRight=Feche tudo à direita
tabs.code=Código
@@ -10,6 +10,7 @@ menu.flatten=Плоская структура пакетов
menu.heapUsageBar=Использование ОЗУ
menu.alwaysSelectOpened=Выбирать открытый файл/класс
menu.dock_log=Просмотр логов в панели
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=Навигация
menu.text_search=Поиск строк
menu.class_search=Поиск классов
@@ -58,6 +59,7 @@ tree.input_scripts=Скрипты
tree.sources_title=Исходный код
tree.resources_title=Ресурсы
tree.loading=Загрузка...
#tree.pinned_tabs=Pinned Tabs
progress.load=Загрузка
progress.save_mappings=Сохранить маппинги
@@ -79,6 +81,9 @@ search.results=%s%d результатов
tabs.copy_class_name=Копировать имя
tabs.close=Закрыть
tabs.closeOthers=Закрыть другие
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=Закрыть все
tabs.closeAllRight=Закройте все справа
tabs.code=Код
@@ -10,6 +10,7 @@ menu.flatten=展开显示代码包
menu.heapUsageBar=显示内存使用栏
menu.alwaysSelectOpened=始终选中打开的文件/类
menu.dock_log=停靠日志查看器
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=导航
menu.text_search=文本搜索
menu.class_search=类名搜索
@@ -58,6 +59,7 @@ tree.input_scripts=脚本
tree.sources_title=源代码
tree.resources_title=资源文件
tree.loading=加载中…
#tree.pinned_tabs=Pinned Tabs
progress.load=正在加载
progress.save_mappings=导出映射
@@ -79,6 +81,9 @@ search.results=%s%d 个结果
tabs.copy_class_name=复制名称
tabs.close=关闭
tabs.closeOthers=关闭其他
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=关闭全部
tabs.closeAllRight=关闭右边的所有
tabs.code=代码
@@ -10,6 +10,7 @@ menu.flatten=展開顯示套件
menu.heapUsageBar=顯示記憶體使用率條
menu.alwaysSelectOpened=總是選擇已開啟的檔案/類別
menu.dock_log=固定記錄檔檢視器
#menu.dock_quick_tabs=Dock quick tabs
menu.navigation=瀏覽
menu.text_search=文字搜尋
menu.class_search=類別搜尋
@@ -58,6 +59,7 @@ tree.input_scripts=腳本
tree.sources_title=原始碼
tree.resources_title=資源
tree.loading=載入中...
#tree.pinned_tabs=Pinned Tabs
progress.load=載入中
progress.save_mappings=正在匯出對應
@@ -79,6 +81,9 @@ search.results=%s%d 個結果
tabs.copy_class_name=複製名稱
tabs.close=關閉
tabs.closeOthers=關閉其他
#tabs.unpin=Unpin
#tabs.unpin_all=Unpin All
#tabs.pin=Pin
tabs.closeAll=關閉全部
tabs.closeAllRight=關閉右邊的所有
tabs.code=程式碼
@@ -0,0 +1,5 @@
<!-- Copyright 2000-2022 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 fill-rule="evenodd" clip-rule="evenodd" d="M6.48057 5.70711L6.26727 7.20016C6.19763 7.68767 5.90122 8.07205 5.53634 8.29289C4.78518 8.74751 4.39847 9.41436 4.19857 10H11.8015C11.6016 9.41434 11.2149 8.74746 10.4637 8.29284C10.0988 8.07201 9.80236 7.68762 9.73272 7.20009L9.51943 5.70711C9.39072 4.80611 10.0899 4 11 4V3L5 3V4C5.91014 4 6.60928 4.80611 6.48057 5.70711ZM5.27732 7.05873C5.25463 7.2176 5.15585 7.35428 5.01856 7.43737C3.84968 8.14481 3.35768 9.22759 3.15058 10.0138C3.0099 10.5478 3.44776 11 4.00005 11H12C12.5523 11 12.9902 10.5478 12.8495 10.0138C12.6424 9.22757 12.1504 8.14475 10.9814 7.43732C10.8442 7.35423 10.7454 7.21754 10.7227 7.05867L10.5094 5.56569C10.4667 5.26712 10.6984 5 11 5C11.5523 5 12 4.55228 12 4V3C12 2.44772 11.5523 2 11 2H5C4.44772 2 4 2.44772 4 3V4C4 4.55228 4.44772 5 5 5C5.3016 5 5.53327 5.26712 5.49062 5.56569L5.27732 7.05873Z" fill="#6C707E"/>
<path d="M7.5 11H8.5V14.5C8.5 14.7761 8.27614 15 8 15V15C7.72386 15 7.5 14.7761 7.5 14.5V11Z" fill="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,6 @@
<!-- 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 fill-rule="evenodd" clip-rule="evenodd" d="M6.48057 5.70711L6.26727 7.20016C6.19763 7.68767 5.90122 8.07205 5.53634 8.29289C4.78518 8.74751 4.39847 9.41436 4.19857 10H11.8015C11.6016 9.41434 11.2149 8.74746 10.4637 8.29284C10.0988 8.07201 9.80236 7.68762 9.73272 7.20009L9.51943 5.70711C9.39072 4.80611 10.0899 4 11 4V3L5 3V4C5.91014 4 6.60928 4.80611 6.48057 5.70711ZM5.27732 7.05873C5.25463 7.2176 5.15585 7.35428 5.01856 7.43737C3.84968 8.14481 3.35768 9.22759 3.15058 10.0138C3.0099 10.5478 3.44776 11 4.00005 11H12C12.5523 11 12.9902 10.5478 12.8495 10.0138C12.6424 9.22757 12.1504 8.14475 10.9814 7.43732C10.8442 7.35423 10.7454 7.21754 10.7227 7.05867L10.5094 5.56569C10.4667 5.26712 10.6984 5 11 5C11.5523 5 12 4.55228 12 4V3C12 2.44772 11.5523 2 11 2H5C4.44772 2 4 2.44772 4 3V4C4 4.55228 4.44772 5 5 5C5.3016 5 5.53327 5.26712 5.49062 5.56569L5.27732 7.05873Z" fill="#6C707E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.48073 5.70711L6.26744 7.20016C6.19779 7.68767 5.90138 8.07205 5.5365 8.29289C4.78534 8.74751 4.39863 9.41436 4.19873 10H11.8017C11.6018 9.41434 11.2151 8.74746 10.4639 8.29284C10.099 8.07201 9.80253 7.68762 9.73288 7.20009L9.5196 5.70711C9.39088 4.80611 10.09 4 11.0002 4V3L5.00016 3V4C5.91031 4 6.60944 4.80611 6.48073 5.70711Z" fill="#EBECF0"/>
<path d="M7.5 11H8.5V14.5C8.5 14.7761 8.27614 15 8 15V15C7.72386 15 7.5 14.7761 7.5 14.5V11Z" fill="#6C707E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,6 @@
<!-- 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 fill-rule="evenodd" clip-rule="evenodd" d="M6.48057 5.70711L6.26727 7.20016C6.19763 7.68767 5.90122 8.07205 5.53634 8.29289C4.78518 8.74751 4.39847 9.41436 4.19857 10H11.8015C11.6016 9.41434 11.2149 8.74746 10.4637 8.29284C10.0988 8.07201 9.80236 7.68762 9.73272 7.20009L9.51943 5.70711C9.39072 4.80611 10.0899 4 11 4V3L5 3V4C5.91014 4 6.60928 4.80611 6.48057 5.70711ZM5.27732 7.05873C5.25463 7.2176 5.15585 7.35428 5.01856 7.43737C3.84968 8.14481 3.35768 9.22759 3.15058 10.0138C3.0099 10.5478 3.44776 11 4.00005 11H12C12.5523 11 12.9902 10.5478 12.8495 10.0138C12.6424 9.22757 12.1504 8.14475 10.9814 7.43732C10.8442 7.35423 10.7454 7.21754 10.7227 7.05867L10.5094 5.56569C10.4667 5.26712 10.6984 5 11 5C11.5523 5 12 4.55228 12 4V3C12 2.44772 11.5523 2 11 2H5C4.44772 2 4 2.44772 4 3V4C4 4.55228 4.44772 5 5 5C5.3016 5 5.53327 5.26712 5.49062 5.56569L5.27732 7.05873Z" fill="#CED0D6"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.48073 5.70711L6.26744 7.20016C6.19779 7.68767 5.90138 8.07205 5.5365 8.29289C4.78534 8.74751 4.39863 9.41436 4.19873 10H11.8017C11.6018 9.41434 11.2151 8.74746 10.4639 8.29284C10.099 8.07201 9.80253 7.68762 9.73288 7.20009L9.5196 5.70711C9.39088 4.80611 10.09 4 11.0002 4V3L5.00016 3V4C5.91031 4 6.60944 4.80611 6.48073 5.70711Z" fill="#43454A"/>
<path d="M7.5 11H8.5V14.5C8.5 14.7761 8.27614 15 8 15V15C7.72386 15 7.5 14.7761 7.5 14.5V11Z" fill="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,5 @@
<!-- Copyright 2000-2022 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 fill-rule="evenodd" clip-rule="evenodd" d="M6.48057 5.70711L6.26727 7.20016C6.19763 7.68767 5.90122 8.07205 5.53634 8.29289C4.78518 8.74751 4.39847 9.41436 4.19857 10H11.8015C11.6016 9.41434 11.2149 8.74746 10.4637 8.29284C10.0988 8.07201 9.80236 7.68762 9.73272 7.20009L9.51943 5.70711C9.39072 4.80611 10.0899 4 11 4V3L5 3V4C5.91014 4 6.60928 4.80611 6.48057 5.70711ZM5.27732 7.05873C5.25463 7.2176 5.15585 7.35428 5.01856 7.43737C3.84968 8.14481 3.35768 9.22759 3.15058 10.0138C3.0099 10.5478 3.44776 11 4.00005 11H12C12.5523 11 12.9902 10.5478 12.8495 10.0138C12.6424 9.22757 12.1504 8.14475 10.9814 7.43732C10.8442 7.35423 10.7454 7.21754 10.7227 7.05867L10.5094 5.56569C10.4667 5.26712 10.6984 5 11 5C11.5523 5 12 4.55228 12 4V3C12 2.44772 11.5523 2 11 2H5C4.44772 2 4 2.44772 4 3V4C4 4.55228 4.44772 5 5 5C5.3016 5 5.53327 5.26712 5.49062 5.56569L5.27732 7.05873Z" fill="#CED0D6"/>
<path d="M7.5 11H8.5V14.5C8.5 14.7761 8.27614 15 8 15V15C7.72386 15 7.5 14.7761 7.5 14.5V11Z" fill="#CED0D6"/>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB