diff --git a/images/line.png b/images/line.png new file mode 100644 index 00000000..f3606bcd Binary files /dev/null and b/images/line.png differ diff --git a/src/studio/ui/UserAction.java b/src/studio/ui/UserAction.java index 12b95033..1520ec5f 100755 --- a/src/studio/ui/UserAction.java +++ b/src/studio/ui/UserAction.java @@ -32,6 +32,21 @@ public KeyStroke getKeyStroke() { return (KeyStroke)getValue(ACCELERATOR_KEY); } + public String getKeyString() { + StringBuilder builder = new StringBuilder(getText()); + KeyStroke keyStroke = getKeyStroke(); + if (keyStroke != null) builder.append(" - ").append(keyStroke); + return builder.toString(); + } + + public UserAction addActionToComponent(JComponent component) { + String key = getKeyString(); + component.getActionMap().put(key, this); + component.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(getKeyStroke(), key); + return this; + } + + public static UserAction create(String text, Icon icon, String desc, int mnemonic, KeyStroke key, ActionListener listener) { diff --git a/src/studio/ui/Util.java b/src/studio/ui/Util.java index cb559966..f5bd4aa5 100755 --- a/src/studio/ui/Util.java +++ b/src/studio/ui/Util.java @@ -62,6 +62,8 @@ public class Util { public final static ImageIcon SEARCH_CASE_SENSITIVE_ICON = getImage("/searchCaseSensitive.png"); public final static ImageIcon SEARCH_CASE_SENSITIVE_SHADED_ICON = getImage("/searchCaseSensitive_shaded.png"); + public final static ImageIcon LINE_ICON = getImage("/line.png"); + public static boolean MAC_OS_X = (System.getProperty("os.name").toLowerCase().startsWith("mac os x")); public static boolean WINDOWS = (System.getProperty("os.name").toLowerCase().contains("win")); diff --git a/src/studio/ui/chart/Chart.java b/src/studio/ui/chart/Chart.java index 4f12b89b..5bfc958c 100755 --- a/src/studio/ui/chart/Chart.java +++ b/src/studio/ui/chart/Chart.java @@ -108,10 +108,17 @@ private void initComponents() { return; } - int modifier = Util.MAC_OS_X ? KeyEvent.ALT_MASK : KeyEvent.CTRL_MASK; - JLabel lbl = new JLabel(" Use mouse wheel or select a rectangle to zoom. " + - "Hold " + KeyEvent.getKeyModifiersText(modifier) + " to move the chart. " + - "ESC - to restore scale"); + int plotMoveModifier = Util.MAC_OS_X ? KeyEvent.ALT_MASK : KeyEvent.CTRL_MASK; + int lineDragModifier = Util.MAC_OS_X ? KeyEvent.META_MASK : KeyEvent.CTRL_MASK; + + String defaultLabelText = " Use mouse wheel or select a rectangle to zoom. " + + "Hold " + KeyEvent.getKeyModifiersText(plotMoveModifier) + " to move the chart. " + + "ESC - to restore scale"; + + String selectedLineText = " Move the line wih mouse drag. " + + "Hold " + KeyEvent.getKeyModifiersText(lineDragModifier) + " to change the slope of the line. "; + + JLabel lbl = new JLabel(defaultLabelText); chartPanel = createChartPanel(); pnlConfig = new ChartConfigPanel(this, names, xIndex, yIndex); @@ -119,8 +126,11 @@ private void initComponents() { JToolBar toolbar = new Toolbar(); toolbar.setLayout(new BoxLayout(toolbar, BoxLayout.X_AXIS)); toolbar.setFloatable(false); - toolbar.add(chartPanel.getCopyAction()); - toolbar.add(chartPanel.getSaveAction()); + toolbar.add(chartPanel.getLineAction()); + toolbar.add(chartPanel.getCopyAction()).setFocusable(false); + toolbar.add(chartPanel.getSaveAction()).setFocusable(false); + chartPanel.addNewLineListener(e -> chartPanel.getRootPane().requestFocusInWindow()); + chartPanel.addLineSelectionListener(e -> lbl.setText(e.getLine() == null ? defaultLabelText : selectedLineText) ); JPanel rightPanel = new JPanel(new BorderLayout()); rightPanel.add(pnlConfig, BorderLayout.CENTER); diff --git a/src/studio/ui/chart/ChartPanel.java b/src/studio/ui/chart/ChartPanel.java index 03f80000..646da8f0 100644 --- a/src/studio/ui/chart/ChartPanel.java +++ b/src/studio/ui/chart/ChartPanel.java @@ -8,9 +8,14 @@ import org.jfree.chart.ui.RectangleEdge; import studio.ui.UserAction; import studio.ui.Util; +import studio.ui.chart.event.LineSelectionEvent; +import studio.ui.chart.event.LineSelectionListener; +import studio.ui.chart.event.NewLineEvent; +import studio.ui.chart.event.NewLineListener; import studio.ui.chart.patched.CrosshairOverlay; import javax.swing.*; +import javax.swing.event.EventListenerList; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.KeyEvent; @@ -24,6 +29,7 @@ public class ChartPanel extends studio.ui.chart.patched.ChartPanel { private final Crosshair xCrosshair; private final Crosshair yCrosshair; + private final Action lineAction; private final Action copyAction; private final Action saveAction; private final Action propertiesAction; @@ -31,6 +37,8 @@ public class ChartPanel extends studio.ui.chart.patched.ChartPanel { private final Action zoomInAction; private final Action zoomOutAction; + private final EventListenerList listenerList = new EventListenerList(); + private boolean addingLine = false; private Line newLine = null; private Line selectedLine = null; @@ -54,22 +62,25 @@ public ChartPanel(JFreeChart chart) { setMouseWheelEnabled(true); setMouseZoomable(true, true); - int meta = java.awt.Toolkit.getDefaultToolkit().getMenuShortcutKeyMask(); - copyAction = addAccelerator(copyItem, KeyStroke.getKeyStroke(KeyEvent.VK_C, meta)); + copyAction = addAccelerator(copyItem, KeyStroke.getKeyStroke(KeyEvent.VK_C, DRAG_LINE_MASK)); copyAction.putValue(Action.SMALL_ICON, Util.COPY_ICON); copyAction.putValue(Action.SHORT_DESCRIPTION, "Copy the chart"); - saveAction = addAccelerator(pngItem, KeyStroke.getKeyStroke(KeyEvent.VK_S, meta)); + saveAction = addAccelerator(pngItem, KeyStroke.getKeyStroke(KeyEvent.VK_S, DRAG_LINE_MASK)); saveAction.putValue(Action.SMALL_ICON, Util.DISKS_ICON); saveAction.putValue(Action.SHORT_DESCRIPTION, "Save the chart"); propertiesAction = addAccelerator(propertiesItem, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.ALT_MASK)); - zoomInAction = addAccelerator(zoomInBothMenuItem, KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, meta), - KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, meta), - KeyStroke.getKeyStroke(KeyEvent.VK_ADD, meta)); - zoomOutAction = addAccelerator(zoomOutBothMenuItem, KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, meta), - KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, meta)); + zoomInAction = addAccelerator(zoomInBothMenuItem, KeyStroke.getKeyStroke(KeyEvent.VK_PLUS, DRAG_LINE_MASK), + KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, DRAG_LINE_MASK), + KeyStroke.getKeyStroke(KeyEvent.VK_ADD, DRAG_LINE_MASK)); + zoomOutAction = addAccelerator(zoomOutBothMenuItem, KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, DRAG_LINE_MASK), + KeyStroke.getKeyStroke(KeyEvent.VK_SUBTRACT, DRAG_LINE_MASK)); resetZoomAction = addAccelerator(zoomResetBothMenuItem, KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0)); + + lineAction = UserAction.create("Add line", Util.LINE_ICON, "Add a line", KeyEvent.VK_L,null, + e -> addLineAction() ); +// .addActionToComponent(this); // didn't figure out why keyStroke doesn't trigger the action } private Action addAccelerator(JMenuItem menuItem, KeyStroke... keyStrokes) { @@ -85,6 +96,10 @@ private Action addAccelerator(JMenuItem menuItem, KeyStroke... keyStrokes) { return action; } + public Action getLineAction() { + return lineAction; + } + public Action getCopyAction() { return copyAction; } @@ -93,6 +108,40 @@ public Action getSaveAction() { return saveAction; } + public void addNewLineListener(NewLineListener listener) { + listenerList.add(NewLineListener.class, listener); + } + + public void removeNewLineListener(NewLineListener listener) { + listenerList.remove(NewLineListener.class, listener); + } + + public void addLineSelectionListener(LineSelectionListener listener) { + listenerList.add(LineSelectionListener.class, listener); + } + + public void removeLineSelectionListener(LineSelectionListener listener) { + listenerList.remove(LineSelectionListener.class, listener); + } + + private void notifyListeners(NewLineEvent event) { + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == NewLineListener.class) { + ((NewLineListener) listeners[i + 1]).lineAdded(event); + } + } + } + + private void notifyListeners(LineSelectionEvent event) { + Object[] listeners = listenerList.getListenerList(); + for (int i = listeners.length - 2; i >= 0; i -= 2) { + if (listeners[i] == LineSelectionListener.class) { + ((LineSelectionListener) listeners[i + 1]).lineSelected(event); + } + } + } + @Override public void actionPerformed(ActionEvent event) { if (event.getActionCommand().equals(ZOOM_RESET_BOTH_COMMAND)) { @@ -290,10 +339,14 @@ else if (e.isPopupTrigger()) { @Override public void mouseClicked(MouseEvent event) { - addLine(event); + if (addingLine) addLine(event); super.mouseClicked(event); } + private void addLineAction() { + addingLine = true; + } + private boolean checkLineDrag(MouseEvent event) { if (selectedLine == null) return false; @@ -338,6 +391,7 @@ private void checkLineSelection(Point p) { } if (selectedIndex != newSelectedIndex) { + notifyListeners(new LineSelectionEvent(this, selectedLine)); chartChanged(new AnnotationChangeEvent(this, line)); } } @@ -351,7 +405,9 @@ private void addLine(MouseEvent e) { } else { newLine.addPoint(p); chartChanged(new AnnotationChangeEvent(this, newLine)); + notifyListeners(new NewLineEvent(this, newLine)); newLine = null; + addingLine = false; } } diff --git a/src/studio/ui/chart/event/LineSelectionEvent.java b/src/studio/ui/chart/event/LineSelectionEvent.java new file mode 100644 index 00000000..5949f438 --- /dev/null +++ b/src/studio/ui/chart/event/LineSelectionEvent.java @@ -0,0 +1,19 @@ +package studio.ui.chart.event; + +import studio.ui.chart.Line; + +import java.util.EventObject; + +public class LineSelectionEvent extends EventObject { + + private final Line line; + + public LineSelectionEvent(Object source, Line line) { + super(source); + this.line = line; + } + + public Line getLine() { + return line; + } +} diff --git a/src/studio/ui/chart/event/LineSelectionListener.java b/src/studio/ui/chart/event/LineSelectionListener.java new file mode 100644 index 00000000..ca15c2b7 --- /dev/null +++ b/src/studio/ui/chart/event/LineSelectionListener.java @@ -0,0 +1,8 @@ +package studio.ui.chart.event; + +import java.util.EventListener; + +public interface LineSelectionListener extends EventListener { + + void lineSelected(LineSelectionEvent event); +} diff --git a/src/studio/ui/chart/event/NewLineEvent.java b/src/studio/ui/chart/event/NewLineEvent.java new file mode 100644 index 00000000..763a9b60 --- /dev/null +++ b/src/studio/ui/chart/event/NewLineEvent.java @@ -0,0 +1,19 @@ +package studio.ui.chart.event; + +import studio.ui.chart.Line; + +import java.util.EventObject; + +public class NewLineEvent extends EventObject { + + private final Line line; + + public NewLineEvent(Object source, Line line) { + super(source); + this.line = line; + } + + public Line getLine() { + return line; + } +} diff --git a/src/studio/ui/chart/event/NewLineListener.java b/src/studio/ui/chart/event/NewLineListener.java new file mode 100644 index 00000000..18e86ed6 --- /dev/null +++ b/src/studio/ui/chart/event/NewLineListener.java @@ -0,0 +1,8 @@ +package studio.ui.chart.event; + +import java.util.EventListener; + +public interface NewLineListener extends EventListener { + + void lineAdded(NewLineEvent event); +}