package com.niklim.clicktrace.dialog.settings;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import net.miginfocom.swing.MigLayout;
import org.jnativehook.GlobalScreen;
import org.jnativehook.mouse.NativeMouseEvent;
import org.jnativehook.mouse.NativeMouseMotionListener;
import com.google.common.base.Optional;
import com.niklim.clicktrace.capture.ScreenUtils;
/**
* Responsible for setting capture area. User may choose full screen or a
* fragment of screen confined by two points.
*/
public class CaptureAreaComponent {
// TODO make me simpler, please...
static final String FULL_SCREEN_CHECKBOX_NAME = "fullScreenCheckbox";
static enum PointWidget {
START("startPanel", "startButton", "startTextFieldName", "start point"), END("endPanel", "endButton",
"endTextFieldName", "end point");
String panelName;
String buttonName;
String textFieldName;
String labelText;
PointWidget(String panelName, String buttonName, String textFieldName, String labelText) {
this.panelName = panelName;
this.buttonName = buttonName;
this.textFieldName = textFieldName;
this.labelText = labelText;
}
}
static final String CHANGE_TXT = "change";
static final String CANCEL_TXT = "cancel";
static final String FULLSCREEN_TXT = "full screen";
static final String SET_POINT_TXT = "Press CTRL+ENTER to set point.";
private JDialog settingsDialog;
private JCheckBox fullScreen;
private JTextField startPointField;
private JTextField endPointField;
private JPanel startPanel;
private JPanel endPanel;
private JPanel infoPanel;
private JButton startButton;
private JButton endButton;
private ActiveButtonXorAvailabilityListener xor = new ActiveButtonXorAvailabilityListener();
private AreaSettingAction startSettingAction;
private AreaSettingAction endSettingAction;
/**
* Enables only one point button, when the other was activated.
*/
private class ActiveButtonXorAvailabilityListener implements ActionListener {
JButton activeButton;
public void actionPerformed(ActionEvent e) {
if (activeButton == e.getSource()) {
activeButton = null;
enableBoth();
} else {
activeButton = (JButton) e.getSource();
if (activeButton == startButton) {
endButton.setEnabled(false);
} else {
startButton.setEnabled(false);
}
}
}
public void enableBoth() {
startButton.setEnabled(true);
endButton.setEnabled(true);
}
}
private static interface MouseMoveAction {
void moved(int x, int y);
}
private static class MouseMoveCapture implements NativeMouseMotionListener {
private MouseMoveAction action;
public MouseMoveCapture() {
GlobalScreen.getInstance().addNativeMouseMotionListener(this);
}
public void setAction(MouseMoveAction action) {
this.action = action;
}
public void cancelAction() {
this.action = null;
}
@Override
public void nativeMouseMoved(NativeMouseEvent e) {
if (action != null) {
action.moved(e.getX(), e.getY());
}
}
@Override
public void nativeMouseDragged(NativeMouseEvent arg0) {
}
};
/**
* Handles setting point coords. When point button is clicked, then it sets
* {@link MouseMoveAction} which updates corresponding coords textfield.
* When point button is clicked again (cancel), then its state is restored.
*/
private class AreaSettingAction implements ActionListener {
private JTextField pointTextField;
private JButton button;
private MouseMoveCapture capture = new MouseMoveCapture();
private Point oldPoint;
private Point newPoint = new Point();
public AreaSettingAction(Point oldPoint, JButton button, JTextField pointTextField) {
this.oldPoint = oldPoint;
this.newPoint = new Point(oldPoint);
this.button = button;
this.pointTextField = pointTextField;
}
MouseMoveAction pointTextFieldUpdaterOnMouseMove = new MouseMoveAction() {
public void moved(int x, int y) {
newPoint.x = x;
newPoint.y = y;
pointTextField.setText(pointToStr(newPoint));
}
};
ActionListener cancelSettingPoint = new ActionListener() {
public void actionPerformed(ActionEvent e) {
awaitSetting();
newPoint = new Point(oldPoint);
pointTextField.setText(pointToStr(newPoint));
}
};
ActionListener acceptSettingPoint = new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
awaitSetting();
}
};
public void actionPerformed(ActionEvent e) {
button.setText(CANCEL_TXT);
infoPanel.setVisible(true);
settingsDialog.pack();
capture.setAction(pointTextFieldUpdaterOnMouseMove);
replaceButtonListener(this, cancelSettingPoint);
settingsDialog.getRootPane().registerKeyboardAction(acceptSettingPoint,
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK),
JComponent.WHEN_IN_FOCUSED_WINDOW);
}
private void awaitSetting() {
button.setText(CHANGE_TXT);
infoPanel.setVisible(false);
settingsDialog.pack();
capture.cancelAction();
settingsDialog.getRootPane().unregisterKeyboardAction(
KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, KeyEvent.CTRL_DOWN_MASK));
replaceButtonListener(cancelSettingPoint, this);
xor.enableBoth();
}
private void replaceButtonListener(ActionListener oldListener, ActionListener newListener) {
button.removeActionListener(oldListener);
button.addActionListener(newListener);
}
public void shutdown() {
button.removeActionListener(this);
button.removeActionListener(cancelSettingPoint);
capture.cancelAction();
}
}
public CaptureAreaComponent(final JDialog settingsDialog) {
this.settingsDialog = settingsDialog;
fullScreen = new JCheckBox(FULLSCREEN_TXT);
fullScreen.setName(FULL_SCREEN_CHECKBOX_NAME);
startPointField = new JTextField();
startPointField.setName(PointWidget.START.textFieldName);
startPointField.setEditable(false);
endPointField = new JTextField();
endPointField.setName(PointWidget.END.textFieldName);
endPointField.setEditable(false);
startButton = new JButton(CHANGE_TXT);
startButton.setName(PointWidget.START.buttonName);
endButton = new JButton(CHANGE_TXT);
endButton.setName(PointWidget.END.buttonName);
startPanel = new JPanel(new MigLayout());
startPanel.setName(PointWidget.START.panelName);
startPanel.add(new JLabel(PointWidget.START.labelText), "w 65");
startPanel.add(startPointField, "w 100");
startPanel.add(startButton);
endPanel = new JPanel(new MigLayout());
endPanel.setName(PointWidget.END.panelName);
endPanel.add(new JLabel(PointWidget.END.labelText), "w 65");
endPanel.add(endPointField, "w 100");
endPanel.add(endButton);
infoPanel = new JPanel(new MigLayout());
infoPanel.add(new JLabel(SET_POINT_TXT));
infoPanel.setVisible(false);
layoutWidgets(settingsDialog);
fullScreen.addChangeListener(new ChangeListener() {
public void stateChanged(ChangeEvent arg0) {
startPanel.setVisible(!fullScreen.isSelected());
endPanel.setVisible(!fullScreen.isSelected());
infoPanel.setVisible(infoPanel.isVisible() && !fullScreen.isSelected());
settingsDialog.pack();
}
});
startButton.addActionListener(xor);
endButton.addActionListener(xor);
}
public void layoutWidgets(JDialog dialog) {
dialog.add(new JLabel("Capture area"));
dialog.add(fullScreen, "wrap");
layoutPanel(dialog, startPanel);
layoutPanel(dialog, endPanel);
layoutPanel(dialog, infoPanel);
}
private void layoutPanel(JDialog dialog, JPanel panel) {
JPanel gapPanel = new JPanel();
gapPanel.setVisible(false);
dialog.add(gapPanel);
dialog.add(panel, "span 2, wrap");
}
public void init(boolean captureFullScreen, Rectangle captureRectangle) {
this.fullScreen.setSelected(captureFullScreen);
Model model = createModel(captureRectangle);
if (captureRectangle != null) {
model.startPoint = new Point(captureRectangle.x, captureRectangle.y);
model.endPoint = new Point(captureRectangle.x + captureRectangle.width, captureRectangle.y
+ captureRectangle.height);
} else {
Dimension screenSize = ScreenUtils.getPrimarySize();
model.startPoint = new Point(0, 0);
model.endPoint = new Point(screenSize.width, screenSize.height);
}
startPointField.setText(pointToStr(model.startPoint));
endPointField.setText(pointToStr(model.endPoint));
startSettingAction = new AreaSettingAction(model.startPoint, startButton, startPointField);
startButton.addActionListener(startSettingAction);
endSettingAction = new AreaSettingAction(model.endPoint, endButton, endPointField);
endButton.addActionListener(endSettingAction);
}
public void clear() {
if (startSettingAction != null) {
startSettingAction.shutdown();
endSettingAction.shutdown();
}
startButton.setText(CHANGE_TXT);
endButton.setText(CHANGE_TXT);
xor.enableBoth();
infoPanel.setVisible(false);
}
private Model createModel(Rectangle captureRectangle) {
final Point startPoint;
final Point endPoint;
if (captureRectangle != null) {
startPoint = new Point(captureRectangle.x, captureRectangle.y);
endPoint = new Point(captureRectangle.x + captureRectangle.width, captureRectangle.y
+ captureRectangle.height);
} else {
Dimension screenSize = ScreenUtils.getPrimarySize();
startPoint = new Point(0, 0);
endPoint = new Point(screenSize.width, screenSize.height);
}
return new Model(startPoint, endPoint);
}
private static class Model {
Point startPoint;
Point endPoint;
public Model(Point startPoint, Point endPoint) {
this.startPoint = startPoint;
this.endPoint = endPoint;
}
}
static String pointToStr(Point point) {
return point.x + "," + point.y;
}
public Optional<Rectangle> getCaptureRectangleOpt() {
if (fullScreen.isSelected()) {
return Optional.<Rectangle> absent();
}
int leftTopX = Math.min(startSettingAction.newPoint.x, endSettingAction.newPoint.x);
int leftTopY = Math.min(startSettingAction.newPoint.y, endSettingAction.newPoint.y);
int width = Math.abs(startSettingAction.newPoint.x - endSettingAction.newPoint.x);
int height = Math.abs(startSettingAction.newPoint.y - endSettingAction.newPoint.y);
return Optional.of(new Rectangle(leftTopX, leftTopY, width, height));
}
}