/* ************************************************************************ # # DivConq # # http://divconq.com/ # # Copyright: # Copyright 2014 eTimeline, LLC. All rights reserved. # # License: # See the license.txt file in the project's top-level directory for details. # # Authors: # * Andy White # ************************************************************************ */ package divconq.script.ui; import java.awt.Component; import java.awt.Dimension; import java.awt.Insets; import java.awt.Rectangle; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ComponentAdapter; import java.awt.event.ComponentEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.io.BufferedReader; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import javax.swing.*; import javax.swing.border.BevelBorder; import javax.swing.border.Border; import javax.swing.border.EmptyBorder; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultCaret; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.Theme; import org.fife.ui.rtextarea.Gutter; import org.fife.ui.rtextarea.RTextScrollPane; import divconq.hub.Hub; import divconq.lang.op.OperationContext; import divconq.lang.op.OperationLogger; import divconq.lang.op.OperationObserver; import divconq.lang.op.OperationResult; import divconq.log.DebugLevel; import divconq.script.Activity; import divconq.script.IDebugger; import divconq.session.Session; import divconq.struct.CompositeStruct; import divconq.struct.FieldStruct; import divconq.struct.ListStruct; import divconq.struct.RecordStruct; import divconq.struct.Struct; import divconq.struct.builder.JsonStreamBuilder; import divconq.util.IOUtil; import divconq.work.Task; import divconq.work.TaskRun; @SuppressWarnings("rawtypes") public class EditorPane extends JRootPane implements SyntaxConstants, IDebugger { private static final long serialVersionUID = -162698015493544827L; protected RSyntaxTextArea editor = null; protected JTextArea console = null; protected JList stacklst = null; protected StackListModel stackmodel = new StackListModel(); protected JList varslst = null; protected JTextArea vardetail = null; protected TaskRun currrun = null; protected Session fssession = null; protected RecordStruct debuginfo = null; protected JFileChooser fc = new JFileChooser("."); protected File currfile = null; protected JLabel statuslbl = new JLabel("Status: stopped"); protected SmallButton runbtn = null; protected SmallButton stepbtn = null; protected Timer uitimer = null; protected long lastinstrun = 0; protected OperationLogger logfmt = new OperationLogger(); @SuppressWarnings("unchecked") public EditorPane() { // -------------------------- LEFT -------------------------------- // EDITOR this.editor = this.createTextArea(); this.editor.setSyntaxEditingStyle(SyntaxConstants.SYNTAX_STYLE_XML); // scroll the editor RTextScrollPane edscroll = new RTextScrollPane(this.editor, true); Gutter gutter = edscroll.getGutter(); gutter.setBookmarkingEnabled(true); //URL url = this.getClass().getClassLoader().getResource("bookmark.png"); // review/remove or import location TODO //gutter.setBookmarkIcon(new ImageIcon(url)); edscroll.setMinimumSize(new Dimension(300, 250)); //edscroll.setPreferredSize(new Dimension(1000, 1000)); // CONSOLE JLabel conlbl = new JLabel("Console"); conlbl.setBorder(BorderFactory.createEmptyBorder(4,8,2,8)); conlbl.setAlignmentX(Component.LEFT_ALIGNMENT); this.console = new JTextArea(); // show bottom of text automatically ((DefaultCaret)this.console.getCaret()).setUpdatePolicy(DefaultCaret.ALWAYS_UPDATE); // scroll the console JScrollPane conscroll = new JScrollPane(this.console, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); conscroll.setMinimumSize(new Dimension(300, 250)); conscroll.setAlignmentX(Component.LEFT_ALIGNMENT); JPanel conPane = new JPanel(); conPane.setLayout(new BoxLayout(conPane, BoxLayout.PAGE_AXIS)); conPane.add(conlbl); conPane.add(conscroll); final JSplitPane leftsplit = new JSplitPane(JSplitPane.VERTICAL_SPLIT, edscroll, conPane); // -------------------------- RIGHT -------------------------------- ImageIcon pbicon = new ImageIcon(this.getClass().getResource("/divconq/script/ui/media-playback-start.png"), "Run script"); ImageIcon psicon = new ImageIcon(this.getClass().getResource("/divconq/script/ui/media-playback-stop.png"), "Stop script"); ImageIcon sficon = new ImageIcon(this.getClass().getResource("/divconq/script/ui/media-seek-forward.png"), "Step into script"); Action stepact = new AbstractAction("Step", sficon) { private static final long serialVersionUID = 6291259738564026019L; public void actionPerformed(ActionEvent e) { EditorPane.this.step(); } }; Action runact = new AbstractAction("Run", pbicon) { private static final long serialVersionUID = -7125666000153246057L; public void actionPerformed(ActionEvent e) { EditorPane.this.run(); } }; Action stopact = new AbstractAction("Stop", psicon) { private static final long serialVersionUID = 1389157371678339040L; public void actionPerformed(ActionEvent e) { EditorPane.this.stop(true); } }; JToolBar toolBar = new JToolBar(); toolBar.setAlignmentX(Component.LEFT_ALIGNMENT); this.stepbtn = new SmallButton(stepact, "Step into script"); toolBar.add(this.stepbtn); this.runbtn = new SmallButton(runact, "Run script"); toolBar.add(this.runbtn); toolBar.add(new SmallButton(stopact, "Stop script")); toolBar.addSeparator(new Dimension(24, 4)); //toolBar.add(new JLabel(" | ")); toolBar.add(this.statuslbl); JLabel stacklbl = new JLabel("Stack"); stacklbl.setBorder(BorderFactory.createEmptyBorder(4,8,2,8)); stacklbl.setAlignmentX(Component.LEFT_ALIGNMENT); this.stacklst = new JList(this.stackmodel); this.stacklst.setCellRenderer(new ListCellRenderer() { @Override public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) { RecordStruct rec = (RecordStruct)value; JLabel lbl = new JLabel(rec.getFieldAsString("Command")); lbl.setOpaque(true); if (isSelected) { lbl.setBackground(list.getSelectionBackground()); lbl.setForeground(list.getSelectionForeground()); } else { lbl.setBackground(list.getBackground()); lbl.setForeground(list.getForeground()); } return lbl; } }); this.stacklst.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { RecordStruct currinst = null; int pos = EditorPane.this.stacklst.getSelectedIndex(); if (pos != -1) currinst = (RecordStruct) EditorPane.this.stacklst.getSelectedValue(); EditorPane.this.varslst.setModel(new VarsListModel((currinst == null) ? null : currinst.getFieldAsRecord("Variables"))); } }); this.stacklst.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent arg0) { int idx = EditorPane.this.stacklst.getSelectedIndex(); if (idx != -1) { RecordStruct currinst = (RecordStruct) EditorPane.this.stacklst.getSelectedValue(); long line = currinst.getFieldAsInteger("Line") - 1; try { int pos = EditorPane.this.editor.getLineStartOffset((int)line); EditorPane.this.editor.setCaretPosition(pos); } catch (BadLocationException x) { } } } }); JScrollPane stackscroll = new JScrollPane(this.stacklst, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); stackscroll.setAlignmentX(Component.LEFT_ALIGNMENT); JLabel varslbl = new JLabel("Selected Level Variables"); varslbl.setBorder(BorderFactory.createEmptyBorder(4,8,2,8)); varslbl.setAlignmentX(Component.LEFT_ALIGNMENT); this.varslst = new JList(); this.varslst.addListSelectionListener(new ListSelectionListener() { @Override public void valueChanged(ListSelectionEvent e) { int pos = EditorPane.this.varslst.getSelectedIndex(); if (pos != -1) { String currvar = (String) ((VarsListModel)EditorPane.this.varslst.getModel()).getFormatted(pos); if (currvar != null) EditorPane.this.vardetail.setText(currvar); } } }); JScrollPane varsscroll = new JScrollPane(this.varslst, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); varsscroll.setAlignmentX(Component.LEFT_ALIGNMENT); JLabel vardlbl = new JLabel("Selected Variable Detail"); vardlbl.setBorder(BorderFactory.createEmptyBorder(4,8,2,8)); vardlbl.setAlignmentX(Component.LEFT_ALIGNMENT); this.vardetail = new JTextArea(); // scroll the console JScrollPane varscroll = new JScrollPane(this.vardetail, JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED); varscroll.setAlignmentX(Component.LEFT_ALIGNMENT); JPanel debugPane = new JPanel(); debugPane.setLayout(new BoxLayout(debugPane, BoxLayout.PAGE_AXIS)); debugPane.add(toolBar); debugPane.add(stacklbl); debugPane.add(stackscroll); debugPane.add(varslbl); debugPane.add(varsscroll); debugPane.add(vardlbl); debugPane.add(varscroll); debugPane.setBorder(BorderFactory.createEmptyBorder(10,10,10,10)); final JSplitPane bigsplit = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, leftsplit, debugPane); this.getContentPane().add(bigsplit); this.setJMenuBar(this.createMenuBar()); // resizing this.addComponentListener(new ComponentAdapter() { @Override public void componentShown(ComponentEvent arg0) { bigsplit.setDividerLocation(0.66); leftsplit.setDividerLocation(0.7); } @Override public void componentResized(ComponentEvent arg0) { bigsplit.setDividerLocation(0.66); leftsplit.setDividerLocation(0.7); } }); } public void start(TaskRun r) { this.currrun = r; if (this.currrun == null) { // TODO load last - better management this.setText(new File("./packages/dcTest/dcs/fileops-copy-2.dcs.xml")); } else { Activity act = (Activity) r.getTask().getWork(); act.setInDebugger(true); act.setDebugger(this); this.setText(act.getScript().getSource()); //this.console.setText(r.getLog()); // TODO just the messages r.getMessages().recordStream().forEach(entry -> this.log(entry)); this.lastinstrun = act.getRunCount(); r.getContext().addObserver(new OperationObserver() { @Override public void log(OperationContext or, RecordStruct entry) { EditorPane.this.log(entry); } @Override public void step(OperationContext or, int num, int of, String name) { EditorPane.this.console.append("##### Step " + num + " of " + of + ": " + name + " #####\n"); } @Override public void progress(OperationContext or, String msg) { EditorPane.this.console.append("***** " + msg + " *****\n"); } @Override public void amount(OperationContext or, int v) { EditorPane.this.console.append(">>>>> " + v + "% <<<<<\n"); } }); } ActionListener taskPerformer = new ActionListener() { public void actionPerformed(ActionEvent evt) { //...Perform a task... //System.out.println("Reading SMTP Info."); EditorPane.this.updateDebuggerUI(); } }; this.uitimer = new Timer(1000, taskPerformer); this.uitimer.start(); } public void shutdown() { if (this.uitimer != null) this.uitimer.stop(); } public void prepTask(boolean debugmode) { this.console.setText(""); this.lastinstrun = 0; Activity act = new Activity(); act.setDebugMode(debugmode); act.setInDebugger(true); act.setDebugger(this); OperationResult compilelog = act.compile(this.editor.getText()); // update text pane in case includes where added - TODO make sure user does not save over their file with includes intact this.setText(act.getScript().getSource()); if (compilelog.hasErrors()) { JOptionPane.showMessageDialog(EditorPane.this, "Error compiling script: " + compilelog.getMessage(), "Compile Error", JOptionPane.ERROR_MESSAGE); return; } Task task = new Task() .withRootContext() .withTitle(act.getScript().getXml().getAttribute("Title", "Debugging dcScript")) .withTimeout(0) // no timeout in editor mode .withWork(act); task.withObserver(new OperationObserver() { @Override public void log(OperationContext or, RecordStruct entry) { EditorPane.this.log(entry); } @Override public void step(OperationContext or, int num, int of, String name) { EditorPane.this.console.append("##### Step " + num + " of " + of + ": " + name + " #####\n"); } @Override public void progress(OperationContext or, String msg) { EditorPane.this.console.append("***** " + msg + " *****\n"); } @Override public void amount(OperationContext or, int v) { EditorPane.this.console.append(">>>>> " + v + "% <<<<<\n"); } }); this.currrun = new TaskRun(task); } public void log(RecordStruct entry) { String lvl = entry.getFieldAsString("Level"); // TODO add an option to see Trace as well if (DebugLevel.Trace.getCode() == DebugLevel.valueOf(lvl).getCode()) return; this.console.append(this.logfmt.formatLogEntry(entry) + "\n"); } public void run() { if (this.currrun == null) this.prepTask(false); if (this.currrun == null) return; ((Activity) this.currrun.getTask().getWork()).setDebugMode(false); this.statuslbl.setText("Status: running"); this.stepbtn.setEnabled(false); this.runbtn.setEnabled(false); Hub.instance.getWorkPool().submit(this.currrun); } public void step() { if (this.currrun == null) this.prepTask(true); if (this.currrun == null) return; ((Activity) this.currrun.getTask().getWork()).setDebugMode(true); this.lastinstrun = 0; this.statuslbl.setText("Status: executing"); this.editor.setEditable(false); this.stepbtn.setEnabled(false); this.runbtn.setEnabled(false); Hub.instance.getWorkPool().submit(this.currrun); } public void updateDebuggerUI() { TaskRun run = this.currrun; if (run != null) { Activity act = (Activity) run.getTask().getWork(); // TODO maybe also every 5 seconds refresh? // only update UI if a new instruction has run //if (this.lastinstrun == act.getRunCount()) // return; this.debuginfo = act.getDebugInfo(); ListStruct stack = this.debuginfo.getFieldAsList("Stack"); RecordStruct currinst = stack.getItemAsRecord(stack.getSize() - 1); long line = currinst.getFieldAsInteger("Line") - 1; try { int linemustbevisible = Math.max((int)line - 2, 0); // make sure at least a couple lines appear above the current instruction int viewpos = this.editor.getLineStartOffset(linemustbevisible); Rectangle viewRect = this.editor.modelToView(viewpos); if (!this.editor.getVisibleRect().contains(viewRect)) { this.editor.scrollRectToVisible(viewRect); } else { // also try to make it so that at least a few lines show below current instruction linemustbevisible = (int)line + Math.min(this.editor.getLineCount() - (int)line, 5); viewpos = this.editor.getLineStartOffset(linemustbevisible); viewRect = this.editor.modelToView(viewpos); if (!this.editor.getVisibleRect().contains(viewRect)) this.editor.scrollRectToVisible(viewRect); } int pos = this.editor.getLineStartOffset((int)line); this.editor.setCaretPosition(pos); } catch (BadLocationException x) { } this.stackmodel.update(stack); if (act.isDebugMode() && (this.lastinstrun < act.getRunCount())) { this.statuslbl.setText("Status: ready"); this.stepbtn.setEnabled(true); this.runbtn.setEnabled(true); this.lastinstrun = act.getRunCount(); } if (act.isExitFlag()) { this.statuslbl.setText("Status: ready"); this.stepbtn.setEnabled(true); this.runbtn.setEnabled(true); System.out.println("Script done. #" + act.getRuntime() + " - Code: " + run.getCode() + " - Message: " + run.getMessage()); this.stop(false); } } } @Override public void console(String msg) { this.console.append(msg + "\n"); } protected Runnable uirefresher = new Runnable() { @Override public void run() { EditorPane.this.updateDebuggerUI(); } }; @Override public void stepped() { if (SwingUtilities.isEventDispatchThread()) this.uirefresher.run(); else SwingUtilities.invokeLater(this.uirefresher); } public void stop(boolean kill) { if (this.currrun == null) return; this.stackmodel.clear(); this.vardetail.setText(""); if (kill) this.currrun.kill(); else this.currrun.complete(); this.currrun = null; this.editor.setCaretPosition(0); this.editor.setEditable(true); this.statuslbl.setText("Status: stopped"); this.stepbtn.setEnabled(true); this.runbtn.setEnabled(true); } private JMenuBar createMenuBar() { JMenuBar mb = new JMenuBar(); JMenu menu = new JMenu("File"); menu.add(new AbstractAction("New") { private static final long serialVersionUID = 4212493611151700478L; @Override public void actionPerformed(ActionEvent arg0) { EditorPane.this.currfile = null; EditorPane.this.editor.setText(""); } }); menu.add(new AbstractAction("Open...") { private static final long serialVersionUID = 4212493611151700478L; @Override public void actionPerformed(ActionEvent arg0) { int returnVal = EditorPane.this.fc.showOpenDialog(EditorPane.this); if (returnVal == JFileChooser.APPROVE_OPTION) { EditorPane.this.setText(fc.getSelectedFile()); } } }); menu.add(new AbstractAction("Save") { private static final long serialVersionUID = 4212493611151700478L; @Override public void actionPerformed(ActionEvent arg0) { if (EditorPane.this.currfile == null) { int returnVal = EditorPane.this.fc.showSaveDialog(EditorPane.this); if (returnVal != JFileChooser.APPROVE_OPTION) return; EditorPane.this.currfile = fc.getSelectedFile(); } OperationResult sres = IOUtil.saveEntireFile(EditorPane.this.currfile.toPath(), EditorPane.this.editor.getText()); if (sres.hasErrors()) JOptionPane.showMessageDialog(EditorPane.this, "Error saving file: " + sres.getMessage(), "Save Error", JOptionPane.ERROR_MESSAGE); } }); menu.add(new AbstractAction("Save As...") { private static final long serialVersionUID = 4212493611151700478L; @Override public void actionPerformed(ActionEvent arg0) { int returnVal = EditorPane.this.fc.showSaveDialog(EditorPane.this); if (returnVal == JFileChooser.APPROVE_OPTION) { EditorPane.this.currfile = fc.getSelectedFile(); OperationResult sres = IOUtil.saveEntireFile(EditorPane.this.currfile.toPath(), EditorPane.this.editor.getText()); if (sres.hasErrors()) JOptionPane.showMessageDialog(EditorPane.this, "Error saving file: " + sres.getMessage(), "Save Error", JOptionPane.ERROR_MESSAGE); } } }); menu.add(new AbstractAction("Exit") { private static final long serialVersionUID = 4212493611151700478L; @Override public void actionPerformed(ActionEvent arg0) { ((JFrame) EditorPane.this.getParent()).dispose(); } }); mb.add(menu); menu = new JMenu("Themes"); menu.add(new JMenuItem(new ThemeAction("Default", "/default.xml"))); // TODO move these to better location in resources menu.add(new JMenuItem(new ThemeAction("Dark", "/dark.xml"))); menu.add(new JMenuItem(new ThemeAction("Eclipse", "/eclipse.xml"))); menu.add(new JMenuItem(new ThemeAction("Visual Studio", "/vs.xml"))); mb.add(menu); menu = new JMenu("Help"); JMenuItem item = new JMenuItem(new AboutAction()); menu.add(item); mb.add(menu); return mb; } /** * Creates the text area for this application. * * @return The text area. */ private RSyntaxTextArea createTextArea() { RSyntaxTextArea textArea = new RSyntaxTextArea(25, 70); textArea.setCaretPosition(0); textArea.requestFocusInWindow(); textArea.setMarkOccurrences(true); textArea.setAntiAliasingEnabled(true); textArea.setCodeFoldingEnabled(true); textArea.setClearWhitespaceLinesEnabled(false); //textArea.setFont(new Font("VeraMono.ttf", Font.PLAIN, 13)); //for (int i=0; i<textArea.getSyntaxScheme().styles.length; i++) { // if (textArea.getSyntaxScheme().styles[i]!=null) { // textArea.getSyntaxScheme().styles[i].font = textArea.getFont(); // } //} return textArea; } /** * Focuses the text area. */ void focusTextArea() { this.editor.requestFocusInWindow(); } /** * Sets the content in the text area to that in the specified resource. * * @param resource The resource to load. */ private void setText(File resource) { this.editor.setText(""); this.currfile = resource; BufferedReader r = null; try { r = new BufferedReader(new InputStreamReader( new FileInputStream(resource), "UTF-8")); this.editor.read(r, null); this.editor.setCaretPosition(0); this.editor.discardAllEdits(); } catch (RuntimeException x) { throw x; // FindBugs } catch (Exception x) { // TODO error this.editor.setText("Type here to see syntax highlighting"); } try { if (r != null) r.close(); } catch (IOException x) { } } private void setText(String src) { this.currfile = null; this.editor.setText(src); this.editor.setCaretPosition(0); this.editor.discardAllEdits(); } public class VarsListModel extends AbstractListModel { private static final long serialVersionUID = 100623371649283278L; protected RecordStruct inst = null; protected List<String> names = new ArrayList<String>(); public VarsListModel(RecordStruct inst) { this.inst = inst; if (this.inst == null) return; for (FieldStruct fld : this.inst.getFields()) this.names.add(fld.getName()); Collections.sort(this.names); } public String getFormatted(int pos) { String name = this.names.get(pos); Struct struct = this.inst.getField(name); if (struct != null) if (struct instanceof CompositeStruct) { try { ByteArrayOutputStream os = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(os); ((CompositeStruct) struct).toBuilder(new JsonStreamBuilder(ps, true)); return os.toString("UTF8"); } catch (Exception x) { // TODO System.out.println("Error formatting structure: " + x); } } else return struct.toString(); return "[null]"; } @Override public Object getElementAt(int pos) { String name = this.names.get(pos); return name + " = " + this.inst.getField(name); } @Override public int getSize() { return this.names.size(); } } public class StackListModel extends AbstractListModel { private static final long serialVersionUID = 4548254186810085022L; protected ListStruct stack = null; public void update(ListStruct stack) { int selpos = EditorPane.this.stacklst.getSelectedIndex(); int changed = stack.getSize(); if (this.stack != null) changed -= this.stack.getSize(); else changed = 0; this.stack = stack; if (changed < 0) this.fireIntervalRemoved(this, 0, Math.abs(changed)); else this.fireIntervalAdded(this, 0, changed); selpos = selpos + changed; if (selpos < 0) selpos = 0; EditorPane.this.stacklst.setSelectedIndex(selpos); } public void clear() { if (this.stack == null) return; int changed = this.stack.getSize(); this.stack = null; if (changed > 0) this.fireIntervalRemoved(this, 0, changed); } @Override public Object getElementAt(int pos) { return (this.stack == null) ? null : this.stack.getItem(this.stack.getSize() - pos - 1); } @Override public int getSize() { return (this.stack == null) ? 0 : this.stack.getSize(); } } private class AboutAction extends AbstractAction { private static final long serialVersionUID = 3311869625217732652L; public AboutAction() { super("About dcScript..."); } public void actionPerformed(ActionEvent e) { JOptionPane.showMessageDialog(EditorPane.this, "<html><b>dcScript</b> - Next Gen JCL" + "<br>Version 0.8.1" + "<br><a href=\"http://divconq.com/\">divconq.com</a>" + "<br>Licensed under an Apache license", "About dcScript", JOptionPane.INFORMATION_MESSAGE); } } private class ThemeAction extends AbstractAction { private static final long serialVersionUID = 7828839661874891753L; private String xml = null; public ThemeAction(String name, String xml) { super(name); this.xml = xml; } public void actionPerformed(ActionEvent e) { InputStream in = this.getClass().getResourceAsStream(xml); try { Theme theme = Theme.load(in); theme.apply(editor); } catch (IOException x) { // TODO } } } public class SmallButton extends JButton implements MouseListener { private static final long serialVersionUID = -1851724407908438330L; protected Border raised = null; protected Border lowered = null; protected Border inactive = null; public SmallButton(Action act, String tip) { super((Icon) act.getValue(Action.SMALL_ICON)); this.raised = new BevelBorder(BevelBorder.RAISED); this.lowered = new BevelBorder(BevelBorder.LOWERED); this.inactive = new EmptyBorder(2, 2, 2, 2); this.setBorder(this.inactive); this.setMargin(new Insets(1, 1, 1, 1)); this.setToolTipText(tip); this.addActionListener(act); this.addMouseListener(this); this.setRequestFocusEnabled(false); } @Override public float getAlignmentY() { return 0.5f; } @Override public void mousePressed(MouseEvent e) { setBorder(this.lowered); } @Override public void mouseReleased(MouseEvent e) { setBorder(this.inactive); } @Override public void mouseClicked(MouseEvent e) { } @Override public void mouseEntered(MouseEvent e) { setBorder(this.raised); } @Override public void mouseExited(MouseEvent e) { setBorder(this.inactive); } } }