package com.android.monkeyrunner.recorder; import java.awt.BorderLayout; import java.awt.Dimension; import java.awt.Event; import java.awt.Graphics2D; import java.awt.Point; import java.awt.Toolkit; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.KeyEvent; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseMotionListener; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.swing.BorderFactory; import javax.swing.Box; import javax.swing.ImageIcon; import javax.swing.JButton; import javax.swing.JComboBox; import javax.swing.JFileChooser; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JMenu; import javax.swing.JMenuBar; import javax.swing.JMenuItem; import javax.swing.JOptionPane; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.KeyStroke; import javax.swing.SwingUtilities; import javax.swing.Timer; import javax.swing.filechooser.FileNameExtensionFilter; import com.android.chimpchat.core.IChimpDevice; import com.android.chimpchat.core.IChimpImage; import com.android.monkeyrunner.MonkeyDevice; import com.android.monkeyrunner.recorder.actions.Action; import com.android.monkeyrunner.recorder.actions.AdvancePressAction; import com.android.monkeyrunner.recorder.actions.DragAction; import com.android.monkeyrunner.recorder.actions.DragAction.Direction; import com.android.monkeyrunner.recorder.actions.SnapshotAction; import com.android.monkeyrunner.recorder.actions.TouchAction; import com.android.monkeyrunner.recorder.actions.TypeAction; import com.android.monkeyrunner.recorder.actions.WaitAction; import com.android.monkeyrunner.res.R; import com.android.monkeyrunner.utils.SystemUtils; /** * MainFrame for MonkeyRecorder. */ public class MonkeyRecorderFrameExt extends JFrame implements ActionListener { private static final Logger LOG = Logger.getLogger(MonkeyRecorderFrameExt.class.getName()); private final IChimpDevice device; private final String defaultExportDir; private static final long serialVersionUID = 1L; private static int scaleSize = 480; private static final int PORTRAIT = 0x1001; private static final int LANDSCAPE = 0x1002; private int orientation = PORTRAIT; private JMenu[] jMenu = { new JMenu(R.string.menu_file) }; private JMenuItem[] fileMenu = { new JMenuItem(R.string.menu_file_export, KeyEvent.VK_S) }; private JMenuBar jMenuBar = new JMenuBar(); private JPanel jContentPane = null; private JLabel display = null; private JScrollPane historyPanel = null; private Box actionPanel = null; private JButton waitButton = null; private JButton pressButton = null; private JButton typeButton = null; private JButton exportActionButton = null; private JButton snapshotActionButton = null; private JButton resetActionButton = null; private JButton rotateButton = null; private JLabel textLabel = null; private BufferedImage currentImage; // @jve:decl-index=0: private BufferedImage scaledImage = new BufferedImage(scaleSize * 3 / 4, scaleSize, BufferedImage.TYPE_INT_ARGB); // @jve:decl-index=0: private JList historyList; private ActionListModel actionListModel; private ArrayList<Action> actions = new ArrayList<Action>(); private final Timer refreshTimer = new Timer(200, new ActionListener() { @Override public void actionPerformed(ActionEvent e) { refreshDisplay(); // @jve:decl-index=0: } }); private boolean isFling = false; private Point flingStartPoint = new Point(); private Point flingEndPoint = new Point(); /** * This is the default constructor */ public MonkeyRecorderFrameExt(IChimpDevice device, String defaultExportDir) { this.device = device; this.defaultExportDir = defaultExportDir; initialize(); } private void initialize() { this.setLocation(150, 50); this.setSize(400, 600); this.setContentPane(getJContentPane()); this.setTitle("MonkeyRecorder"); for (JMenuItem item : fileMenu) { jMenu[0].add(item); item.setAccelerator(KeyStroke.getKeyStroke(item.getMnemonic(), SystemUtils.isMac() ? Toolkit.getDefaultToolkit().getMenuShortcutKeyMask() : Event.CTRL_MASK)); item.addActionListener(this); } for (JMenu temp : jMenu) { jMenuBar.add(temp); } this.setJMenuBar(jMenuBar); SwingUtilities.invokeLater(new Runnable() { @Override public void run() { refreshDisplay(); } }); refreshTimer.start(); } private void rotateDisplay() { if (orientation == PORTRAIT) { orientation = LANDSCAPE; } else if (orientation == LANDSCAPE) { orientation = PORTRAIT; } refreshDisplay(); } private void refreshDisplay() { if (orientation == PORTRAIT) { IChimpImage snapshot = device.takeSnapshot(); currentImage = snapshot.createBufferedImage(); int width = currentImage.getWidth(); int height = currentImage.getHeight(); int scaleWidth = width * scaleSize / height; int scaleHeight = scaleSize; display.setPreferredSize(new Dimension(scaleWidth, scaleHeight)); if (scaledImage.getWidth() != scaleWidth || scaledImage.getHeight() != scaleHeight) { scaledImage = new BufferedImage(scaleWidth, scaleHeight, BufferedImage.TYPE_INT_ARGB); } Graphics2D g = scaledImage.createGraphics(); g.drawImage(currentImage, 0, 0, scaledImage.getWidth(), scaledImage.getHeight(), null); g.dispose(); display.setIcon(new ImageIcon(scaledImage)); } else if (orientation == LANDSCAPE) { IChimpImage snapshot = device.takeSnapshot(); BufferedImage originImage = snapshot.createBufferedImage(); int width = originImage.getHeight(); int height = originImage.getWidth(); int scaleWidth = scaleSize; int scaleHeight = height * scaleSize / width; display.setPreferredSize(new Dimension(scaleWidth, scaleHeight)); if (scaledImage.getWidth() != scaleWidth || scaledImage.getHeight() != scaleHeight) { scaledImage = new BufferedImage(scaleWidth, scaleHeight, BufferedImage.TYPE_INT_ARGB); } if (currentImage.getWidth() != width || currentImage.getHeight() != height) { currentImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); } Graphics2D g2d = currentImage.createGraphics(); AffineTransform transform1 = new AffineTransform(); transform1.translate((currentImage.getWidth() - originImage.getWidth()) / 2, (currentImage.getHeight() - originImage.getHeight()) / 2); transform1.rotate(Math.toRadians(-90), originImage.getWidth() / 2, originImage.getHeight() / 2); g2d.setTransform(transform1); g2d.drawImage(originImage, 0, 0, null); g2d.dispose(); Graphics2D g2d2 = scaledImage.createGraphics(); g2d2.drawImage(currentImage, 0, 0, scaledImage.getWidth(), scaledImage.getHeight(), null); g2d2.dispose(); display.setIcon(new ImageIcon(scaledImage)); } pack(); } /** * This method initializes jContentPane * * @return javax.swing.JPanel */ private JPanel getJContentPane() { if (jContentPane == null) { jContentPane = new JPanel(); jContentPane.setLayout(new BorderLayout()); jContentPane.add(getDisplayPanel(), BorderLayout.CENTER); jContentPane.add(getHistoryPanel(), BorderLayout.EAST); jContentPane.add(getActionPanel(), BorderLayout.NORTH); } return jContentPane; } private JPanel getDisplayPanel() { JPanel panel = new JPanel(); panel.setLayout(new BorderLayout()); // display = new JLabel(); panel.add(display, BorderLayout.CENTER); display.setPreferredSize(new Dimension(scaleSize * 3 / 4, scaleSize)); display.addMouseListener(new MouseAdapter() { @Override public void mouseClicked(MouseEvent event) { touch(event); } @Override public void mouseReleased(MouseEvent event) { if (isFling) { double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth()); double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight()); int x = (int) (event.getX() * scalex); int y = (int) (event.getY() * scaley); flingEndPoint.x = x; flingEndPoint.y = y; DragAction dragAction = newDragAction(flingStartPoint, flingEndPoint, 300); if (dragAction != null) addAction(dragAction); } isFling = false; } @Override public void mousePressed(MouseEvent event) { double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth()); double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight()); int x = (int) (event.getX() * scalex); int y = (int) (event.getY() * scaley); flingStartPoint.x = x; flingStartPoint.y = y; } }); display.addMouseMotionListener(new MouseMotionListener() { @Override public void mouseMoved(MouseEvent event) { detectMove(event); } @Override public void mouseDragged(MouseEvent event) { isFling = true; } }); return panel; } /** * This method initializes historyPanel * * @return javax.swing.JScrollPane */ private JScrollPane getHistoryPanel() { if (historyPanel == null) { historyPanel = new JScrollPane(); historyPanel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Action History"), BorderFactory.createEmptyBorder(5, 5, 5, 5))); historyPanel.getViewport().setView(getHistoryList()); } return historyPanel; } private JList getHistoryList() { if (historyList == null) { actionListModel = new ActionListModel(); historyList = new JList(actionListModel); } return historyList; } /** * This method initializes actionPanel * * @return javax.swing.JPanel */ private Box getActionPanel() { if (actionPanel == null) { actionPanel = Box.createVerticalBox(); Box horizontalBox1 = Box.createHorizontalBox(); actionPanel.add(horizontalBox1); horizontalBox1.add(getExportActionButton()); horizontalBox1.add(getPressButton()); horizontalBox1.add(getTypeButton()); Box horizontalBox2 = Box.createHorizontalBox(); actionPanel.add(horizontalBox2); horizontalBox2.add(getWaitButton()); horizontalBox2.add(getSnapshotActionButton()); horizontalBox2.add(getRemoveActionButton()); horizontalBox2.add(getRotateButton()); // horizontalBox2.add(getRefreshButton()); actionPanel.add(getTextLabel()); } return actionPanel; } /** * This method initializes waitButton * * @return javax.swing.JButton */ private JButton getWaitButton() { if (waitButton == null) { waitButton = new JButton(); waitButton.setText(R.string.button_wait); waitButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { String howLongStr = JOptionPane.showInputDialog(R.string.text_how_many_second_to_wait); if (howLongStr != null) { float howLong = Float.parseFloat(howLongStr); addAction(new WaitAction(howLong)); } } }); } return waitButton; } /** * This method initializes pressButton * * @return javax.swing.JButton */ private JButton getPressButton() { if (pressButton == null) { pressButton = new JButton(); pressButton.setText(R.string.button_press_a_button); pressButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { JPanel panel = new JPanel(); JLabel text = new JLabel(R.string.text_what_button); JComboBox keys = new JComboBox(AdvancePressAction.KEYS); keys.setEditable(true); JComboBox direction = new JComboBox(AdvancePressAction.DOWNUP_FLAG_MAP.values().toArray()); panel.add(text); panel.add(keys); panel.add(direction); int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION); if (result == JOptionPane.OK_OPTION) { // Look up the "flag" value for the press choice Map<String, String> lookupMap = AdvancePressAction.DOWNUP_FLAG_MAP.inverse(); String flag = lookupMap.get(direction.getSelectedItem()); addAction(new AdvancePressAction((String) keys.getSelectedItem(), flag)); } } }); } return pressButton; } /** * This method initializes typeButton * * @return javax.swing.JButton */ private JButton getTypeButton() { if (typeButton == null) { typeButton = new JButton(); typeButton.setText(R.string.button_type_something); typeButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { String whatToType = JOptionPane.showInputDialog(R.string.text_what_to_type); if (whatToType != null) { addAction(new TypeAction(whatToType)); } } }); } return typeButton; } private DragAction newDragAction(Point startPoint, Point endPoint, long millis) { // TODO int width = Integer.parseInt(device.getProperty(orientation == PORTRAIT ? "display.width" : "display.height")); int height = Integer.parseInt(device.getProperty(orientation == PORTRAIT ? "display.height" : "display.width")); Direction dir = Direction.NORTH; if (Math.abs(startPoint.x - endPoint.x) > width / 5) { if (startPoint.x >= endPoint.x) { dir = Direction.WEST; } else if (startPoint.x < endPoint.x) { dir = Direction.EAST; } } else if (Math.abs(startPoint.y - endPoint.y) > height / 5) { if (startPoint.y >= endPoint.y) { dir = Direction.NORTH; } else if (startPoint.y < endPoint.y) { dir = Direction.SOUTH; } } else { return null; } return new DragAction(dir, startPoint.x, startPoint.y, endPoint.x, endPoint.y, 1, millis); } /** * This method initializes exportActionButton * * @return javax.swing.JButton */ private JButton getExportActionButton() { if (exportActionButton == null) { exportActionButton = new JButton(); exportActionButton.setText(R.string.button_file_export); exportActionButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent ev) { exportActionFile(); } }); } return exportActionButton; } /** * This method initializes snapshotActionButton * * @return javax.swing.JButton */ private JButton getSnapshotActionButton() { if (snapshotActionButton == null) { snapshotActionButton = new JButton(); snapshotActionButton.setText(R.string.button_take_snapshot); snapshotActionButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent ev) { addAction(new SnapshotAction()); } }); } return snapshotActionButton; } /** * This method initializes removeActionButton * * @return javax.swing.JButton */ private JButton getRemoveActionButton() { if (resetActionButton == null) { resetActionButton = new JButton(); resetActionButton.setText(R.string.button_reset_last_action); resetActionButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent ev) { resetLastAction(); } }); } return resetActionButton; } /** * This method initializes rotateButton * * @return javax.swing.JButton */ private JButton getRotateButton() { if (rotateButton == null) { rotateButton = new JButton(); rotateButton.setText(R.string.button_rotate_display); rotateButton.addActionListener(new java.awt.event.ActionListener() { @Override public void actionPerformed(java.awt.event.ActionEvent e) { rotateDisplay(); } }); } return rotateButton; } private JPanel getTextLabel() { JPanel panel = new JPanel(); panel.setBorder( BorderFactory.createCompoundBorder( BorderFactory.createTitledBorder("Coordinates"), BorderFactory.createEmptyBorder(5, 5, 5, 5))); panel.setLayout(new BorderLayout()); if (textLabel == null) { textLabel = new JLabel("x: 0, y:0"); } panel.add(textLabel, BorderLayout.WEST); return panel; } private void touch(MouseEvent event) { // TODO int x = event.getX(); int y = event.getY(); // Since we scaled the image down, our x/y are scaled as well. double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth()); double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight()); x = (int) (x * scalex); y = (int) (y * scaley); switch (event.getID()) { case MouseEvent.MOUSE_CLICKED: addAction(new TouchAction(x, y, MonkeyDevice.DOWN_AND_UP)); break; case MouseEvent.MOUSE_PRESSED: addAction(new TouchAction(x, y, MonkeyDevice.DOWN)); break; case MouseEvent.MOUSE_RELEASED: addAction(new TouchAction(x, y, MonkeyDevice.UP)); break; } } private void detectMove(MouseEvent event) { int x = event.getX(); int y = event.getY(); // Since we scaled the image down, our x/y are scaled as well. double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth()); double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight()); x = (int) (x * scalex); y = (int) (y * scaley); textLabel.setText("x: " + x + ", y: " + y); } public void addAction(Action a) { actions.add(a); actionListModel.add(a); scrollToBottom(); try { if (!(a instanceof WaitAction)) { a.execute(device); } } catch (Exception e) { LOG.log(Level.SEVERE, "Unable to execute action!", e); } } private void scrollToBottom() { javax.swing.SwingUtilities.invokeLater(new Runnable() { public void run() { int lastIndex = historyList.getModel().getSize() - 1; if (lastIndex >= 0) { historyList.ensureIndexIsVisible(lastIndex); } } }); } private void resetLastAction() { if (actions.size() == 0) return; actions.remove(actions.size() - 1); ActionListModel newActionListModel = new ActionListModel(); for (int i = 0; i < actions.size(); i++) { newActionListModel.add(actions.get(i)); } actionListModel = newActionListModel; refreshHistoryPanel(); } private void refreshHistoryPanel() { historyList = new JList(actionListModel); historyPanel.getViewport().setView(historyList); } @Override public void actionPerformed(ActionEvent e) { if (e.getSource() instanceof JMenuItem) { if (((JMenuItem) e.getSource()).getText().equalsIgnoreCase(R.string.menu_file_export)) { exportActionFile(); } } } private void exportActionFile() { FileNameExtensionFilter mrFilter = new FileNameExtensionFilter(".mr", "mr"); JFileChooser fc = new JFileChooser(); fc.setFileFilter(mrFilter); if (defaultExportDir != null) { fc.setCurrentDirectory(new File(defaultExportDir)); } if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { try { String exportFileName = fc.getSelectedFile().getAbsolutePath(); if (!exportFileName.endsWith(".mr")) { exportFileName = exportFileName + ".mr"; } actionListModel.export(new File(exportFileName)); JOptionPane.showMessageDialog(this, R.string.dialog_export_complete); } catch (FileNotFoundException e) { LOG.log(Level.SEVERE, "Unable to save file", e); } } // if (SystemUtils.isMac()) { // FileDialog mrChooser = new FileDialog(this, "Save", FileDialog.SAVE); // if (defaultExportDir != null) { // mrChooser.setDirectory(new File(defaultExportDir).getAbsolutePath()); // } // mrChooser.setLocation(50, 50); // mrChooser.setVisible(true); // if (mrChooser.getFile() != null) { // System.out.println("mrChooser.getFile(): " + mrChooser.getFile()); // try { // String exportFileName = mrChooser.getFile(); // if (!exportFileName.endsWith(".mr")) { // exportFileName = exportFileName + ".mr"; // } // actionListModel.export(new File(exportFileName)); // // JOptionPane.showMessageDialog(this, R.string.dialog_export_complete); // } catch (FileNotFoundException e) { // LOG.log(Level.SEVERE, "Unable to save file", e); // } // } // } // else { // FileNameExtensionFilter mrFilter = new FileNameExtensionFilter(".mr", "mr"); // JFileChooser fc = new JFileChooser(); // fc.setFileFilter(mrFilter); // // if (defaultExportDir != null) { // fc.setCurrentDirectory(new File(defaultExportDir)); // } // // if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) { // try { // String exportFileName = fc.getSelectedFile().getAbsolutePath(); // if (!exportFileName.endsWith(".mr")) { // exportFileName = exportFileName + ".mr"; // } // actionListModel.export(new File(exportFileName)); // // JOptionPane.showMessageDialog(this, R.string.dialog_export_complete); // } catch (FileNotFoundException e) { // LOG.log(Level.SEVERE, "Unable to save file", e); // } // } // } } }