Skip to content

Commit

Permalink
Add BurpTabbedPane GUI component
Browse files Browse the repository at this point in the history
Renameable, closable, tabs styled like Burp.
  • Loading branch information
augustd committed Jan 10, 2020
1 parent 14b34a7 commit 9003a54
Show file tree
Hide file tree
Showing 2 changed files with 249 additions and 0 deletions.
217 changes: 217 additions & 0 deletions src/main/java/com/codemagi/burp/ui/BurpTabComponent.java
Original file line number Diff line number Diff line change
@@ -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<JTextField>(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<JTextField> {

@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<? extends JTextField> l) {
Component src = e.getComponent();
Container tgt = SwingUtilities.getAncestorOfClass(JTabbedPane.class, src);
tgt.dispatchEvent(SwingUtilities.convertMouseEvent(src, e, tgt));
}

}

}
32 changes: 32 additions & 0 deletions src/main/java/com/codemagi/burp/ui/BurpTabbedPane.java
Original file line number Diff line number Diff line change
@@ -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:
* <li>Ability to rename tabs</li>
* <li>Ability to close tabs</li>
* <li>Burp-like UI</li>
*
* @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;
}

}

0 comments on commit 9003a54

Please sign in to comment.