diff --git a/src/main/java/com/codemagi/burp/ui/BurpTabComponent.java b/src/main/java/com/codemagi/burp/ui/BurpTabComponent.java new file mode 100644 index 0000000..51cb8d3 --- /dev/null +++ b/src/main/java/com/codemagi/burp/ui/BurpTabComponent.java @@ -0,0 +1,217 @@ +package com.codemagi.burp.ui; + +import com.codemagi.burp.BaseExtender; +import java.awt.*; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; +import java.awt.event.FocusListener; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.awt.event.MouseListener; + +import javax.swing.*; +import javax.swing.plaf.LayerUI; +import javax.swing.plaf.basic.BasicButtonUI; + +/** + * Component to be used as tabComponent: Renders instead of a default tab name. + * + * Contains a JLabel to show the (editable) tab name and a JButton to close the tab. + * + * @author August Detlefsen [augustd at codemagi dot com] + */ +public class BurpTabComponent extends JPanel { + + private final JTabbedPane pane; //the pane of tabs that will contain this one + private final JTextField editor = new JTextField(); + private static final Color TRANSPARENT = new Color(255, 255, 255, 0); + + public BurpTabComponent(String title, final JTabbedPane pane) { + //unset default FlowLayout' gaps + super(new FlowLayout(FlowLayout.LEFT, 0, 0)); + if (pane == null) { + throw new NullPointerException("TabbedPane is null"); + } + this.pane = pane; + + //create an editor component + editor.setBorder(BorderFactory.createEmptyBorder(0, 3, 0, 7)); + editor.setText(title); + editor.setBackground(TRANSPARENT); + editor.setOpaque(false); + editor.setEditable(false); + BaseExtender.getCallbacks().customizeUiComponent(editor); + add(new JLayer(editor, new DispatchEventLayerUI())); + + //Make editor not editable until double-click + editor.setEditable(false); + editor.setEnabled(false); + editor.setDisabledTextColor(Color.BLACK); + editor.addMouseListener(MOUSE_CLICK_LISTENER); + editor.addFocusListener(FOCUS_LOST_LISTENER); + + //Add the close button + JButton button = new TabButton(); + add(button); + + //add more space to the top of the component + setBorder(BorderFactory.createEmptyBorder(2, 0, 0, 0)); + setBackground(TRANSPARENT); + setOpaque(false); + } + + private class TabButton extends JButton implements ActionListener { + + public TabButton() { + int size = 9; + setPreferredSize(new Dimension(size, size)); + setToolTipText("close this tab"); + //Make the button looks the same for all Laf's + setUI(new BasicButtonUI()); + //Make it transparent + setContentAreaFilled(false); + //No need to be focusable + setFocusable(false); + setBorder(BorderFactory.createLineBorder(Color.BLACK, 1)); + setBorderPainted(false); + this.setMargin(new Insets(11, 6, 0, 0)); + + //Making nice rollover effect + //we use the same listener for all buttons + addMouseListener(BUTTON_MOUSE_LISTENER); + setRolloverEnabled(true); + //Close the proper tab by clicking the button + addActionListener(this); + BaseExtender.getCallbacks().customizeUiComponent(this); + } + + @Override + public void actionPerformed(ActionEvent e) { + int i = pane.indexOfTabComponent(BurpTabComponent.this); + if (i != -1) { + pane.remove(i); + } + } + + //we don't want to update UI for this button + @Override + public void updateUI() { + } + + //paint the cross + @Override + protected void paintComponent(Graphics g) { + super.paintComponent(g); + Graphics2D g2 = (Graphics2D) g.create(); + //shift the image for pressed buttons + if (getModel().isPressed()) { + g2.translate(1, 1); + } + g2.setStroke(new BasicStroke(1)); + g2.setColor(Color.GRAY); + if (getModel().isRollover()) { + g2.setColor(Color.BLACK); + } + int delta = 6; + g2.drawLine(2, 3, 6, 7); + g2.drawLine(2, 7, 6, 3); + g2.dispose(); + } + } + + /** + * Create rollover effect for X button + */ + private final static MouseListener BUTTON_MOUSE_LISTENER = new MouseAdapter() { + @Override + public void mouseEntered(MouseEvent e) { + Component component = e.getComponent(); + if (component instanceof AbstractButton) { + AbstractButton button = (AbstractButton) component; + button.setBorderPainted(true); + } + } + + @Override + public void mouseExited(MouseEvent e) { + Component component = e.getComponent(); + if (component instanceof AbstractButton) { + AbstractButton button = (AbstractButton) component; + button.setBorderPainted(false); + } + } + }; + + private final MouseListener MOUSE_CLICK_LISTENER = new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + System.out.println("MOUSE_CLICK_LISTENER.mouseClicked()"); + + Component component = e.getComponent(); + component.requestFocusInWindow(); + + //no matter how many clicks, always select the tab + System.out.println("single-click"); + System.out.println("index: " + pane.indexOfTabComponent(BurpTabComponent.this)); + pane.setSelectedIndex(pane.indexOfTabComponent(BurpTabComponent.this)); + + if (e.getClickCount() == 2) { + System.out.println("double-click"); + JTextField field = (JTextField) component; + field.setEditable(true); + field.setEnabled(true); + } + } + }; + + /** + * Make label field un-editable when it loses focus + */ + private static final FocusListener FOCUS_LOST_LISTENER = new FocusAdapter() { + @Override + public void focusLost(FocusEvent e) { + Component component = e.getComponent(); + System.out.println("User exited"); + JTextField field = (JTextField) component; + field.setEditable(false); + field.setEnabled(false); + } + }; + + class DispatchEventLayerUI extends LayerUI { + + @Override + public void installUI(JComponent c) { + super.installUI(c); + if (c instanceof JLayer) { + ((JLayer) c).setLayerEventMask(AWTEvent.MOUSE_MOTION_EVENT_MASK); + } + } + + @Override + public void uninstallUI(JComponent c) { + if (c instanceof JLayer) { + ((JLayer) c).setLayerEventMask(0); + } + super.uninstallUI(c); + } + + /** + * Pass mouse motion events (in/out) to the underlying tabbed pane to + * handle highlighting of the tab. + * + * @param e + * @param l + */ + @Override + protected void processMouseMotionEvent(MouseEvent e, JLayer l) { + Component src = e.getComponent(); + Container tgt = SwingUtilities.getAncestorOfClass(JTabbedPane.class, src); + tgt.dispatchEvent(SwingUtilities.convertMouseEvent(src, e, tgt)); + } + + } + +} diff --git a/src/main/java/com/codemagi/burp/ui/BurpTabbedPane.java b/src/main/java/com/codemagi/burp/ui/BurpTabbedPane.java new file mode 100644 index 0000000..783c54a --- /dev/null +++ b/src/main/java/com/codemagi/burp/ui/BurpTabbedPane.java @@ -0,0 +1,32 @@ +package com.codemagi.burp.ui; + +import java.awt.Component; +import javax.swing.JTabbedPane; + +/** + * Creates a JTabbedPane customized to work with Burp. Includes: + *
  • Ability to rename tabs
  • + *
  • Ability to close tabs
  • + *
  • Burp-like UI
  • + * + * @author August Detlefsen [augustd at codemagi dot com] + */ +public class BurpTabbedPane extends JTabbedPane { + + /** + * Add a new re-nameable, closable tab to the tabbed pane. + * + * @param name + * @param component + * @return + */ + @Override + public Component add(String name, Component component) { + //add the component that renders when the tab is selected + Component output = super.add(name, component); + //add the tab component: renders instead of the default tab name + setTabComponentAt(indexOfComponent(component), new BurpTabComponent(name, this)); + return output; + } + +}