1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.monkeyrunner.recorder;
17
18import com.android.monkeyrunner.MonkeyDevice;
19import com.android.chimpchat.core.IChimpImage;
20import com.android.chimpchat.core.IChimpDevice;
21import com.android.monkeyrunner.recorder.actions.Action;
22import com.android.monkeyrunner.recorder.actions.DragAction;
23import com.android.monkeyrunner.recorder.actions.DragAction.Direction;
24import com.android.monkeyrunner.recorder.actions.PressAction;
25import com.android.monkeyrunner.recorder.actions.TouchAction;
26import com.android.monkeyrunner.recorder.actions.TypeAction;
27import com.android.monkeyrunner.recorder.actions.WaitAction;
28
29import java.awt.BorderLayout;
30import java.awt.Dimension;
31import java.awt.Graphics2D;
32import java.awt.event.ActionEvent;
33import java.awt.event.ActionListener;
34import java.awt.event.MouseAdapter;
35import java.awt.event.MouseEvent;
36import java.awt.image.BufferedImage;
37import java.io.FileNotFoundException;
38import java.util.Map;
39import java.util.logging.Level;
40import java.util.logging.Logger;
41
42import javax.swing.BoxLayout;
43import javax.swing.ImageIcon;
44import javax.swing.JButton;
45import javax.swing.JComboBox;
46import javax.swing.JFileChooser;
47import javax.swing.JFrame;
48import javax.swing.JLabel;
49import javax.swing.JList;
50import javax.swing.JOptionPane;
51import javax.swing.JPanel;
52import javax.swing.JScrollPane;
53import javax.swing.JTextField;
54import javax.swing.SwingUtilities;
55import javax.swing.Timer;
56
57/**
58 * MainFrame for MonkeyRecorder.
59 */
60public class MonkeyRecorderFrame extends JFrame {
61    private static final Logger LOG =
62        Logger.getLogger(MonkeyRecorderFrame.class.getName());
63
64    private final IChimpDevice device;
65
66    private static final long serialVersionUID = 1L;
67    private JPanel jContentPane = null;
68    private JLabel display = null;
69    private JScrollPane historyPanel = null;
70    private JPanel actionPanel = null;
71    private JButton waitButton = null;
72    private JButton pressButton = null;
73    private JButton typeButton = null;
74    private JButton flingButton = null;
75    private JButton exportActionButton = null;
76
77    private JButton refreshButton = null;
78
79    private BufferedImage currentImage;  //  @jve:decl-index=0:
80    private BufferedImage scaledImage = new BufferedImage(320, 480,
81            BufferedImage.TYPE_INT_ARGB);  //  @jve:decl-index=0:
82
83    private JList historyList;
84    private ActionListModel actionListModel;
85
86    private final Timer refreshTimer = new Timer(1000, new ActionListener() {
87        @Override
88        public void actionPerformed(ActionEvent e) {
89            refreshDisplay();  //  @jve:decl-index=0:
90        }
91    });
92
93    /**
94     * This is the default constructor
95     */
96    public MonkeyRecorderFrame(IChimpDevice device) {
97        this.device = device;
98        initialize();
99    }
100
101    private void initialize() {
102        this.setSize(400, 600);
103        this.setContentPane(getJContentPane());
104        this.setTitle("MonkeyRecorder");
105
106        SwingUtilities.invokeLater(new Runnable() {
107            @Override
108            public void run() {
109                refreshDisplay();
110            }});
111        refreshTimer.start();
112    }
113
114    private void refreshDisplay() {
115        IChimpImage snapshot = device.takeSnapshot();
116        currentImage = snapshot.createBufferedImage();
117
118        Graphics2D g = scaledImage.createGraphics();
119        g.drawImage(currentImage, 0, 0,
120                scaledImage.getWidth(), scaledImage.getHeight(),
121                null);
122        g.dispose();
123
124        display.setIcon(new ImageIcon(scaledImage));
125
126        pack();
127    }
128
129    /**
130     * This method initializes jContentPane
131     *
132     * @return javax.swing.JPanel
133     */
134    private JPanel getJContentPane() {
135        if (jContentPane == null) {
136            display = new JLabel();
137            jContentPane = new JPanel();
138            jContentPane.setLayout(new BorderLayout());
139            jContentPane.add(display, BorderLayout.CENTER);
140            jContentPane.add(getHistoryPanel(), BorderLayout.EAST);
141            jContentPane.add(getActionPanel(), BorderLayout.NORTH);
142
143            display.setPreferredSize(new Dimension(320, 480));
144
145            display.addMouseListener(new MouseAdapter() {
146                @Override
147                public void mouseClicked(MouseEvent event) {
148                    touch(event);
149                }
150            });
151        }
152        return jContentPane;
153    }
154
155    /**
156     * This method initializes historyPanel
157     *
158     * @return javax.swing.JScrollPane
159     */
160    private JScrollPane getHistoryPanel() {
161        if (historyPanel == null) {
162            historyPanel = new JScrollPane();
163            historyPanel.getViewport().setView(getHistoryList());
164        }
165        return historyPanel;
166    }
167
168    private JList getHistoryList() {
169        if (historyList == null) {
170            actionListModel = new ActionListModel();
171            historyList = new JList(actionListModel);
172        }
173        return historyList;
174    }
175
176    /**
177     * This method initializes actionPanel
178     *
179     * @return javax.swing.JPanel
180     */
181    private JPanel getActionPanel() {
182        if (actionPanel == null) {
183            actionPanel = new JPanel();
184            actionPanel.setLayout(new BoxLayout(getActionPanel(), BoxLayout.X_AXIS));
185            actionPanel.add(getWaitButton(), null);
186            actionPanel.add(getPressButton(), null);
187            actionPanel.add(getTypeButton(), null);
188            actionPanel.add(getFlingButton(), null);
189            actionPanel.add(getExportActionButton(), null);
190            actionPanel.add(getRefreshButton(), null);
191        }
192        return actionPanel;
193    }
194
195    /**
196     * This method initializes waitButton
197     *
198     * @return javax.swing.JButton
199     */
200    private JButton getWaitButton() {
201        if (waitButton == null) {
202            waitButton = new JButton();
203            waitButton.setText("Wait");
204            waitButton.addActionListener(new java.awt.event.ActionListener() {
205                @Override
206                public void actionPerformed(java.awt.event.ActionEvent e) {
207                    String howLongStr = JOptionPane.showInputDialog("How many seconds to wait?");
208                    if (howLongStr != null) {
209                        float howLong = Float.parseFloat(howLongStr);
210                        addAction(new WaitAction(howLong));
211                    }
212                }
213            });
214        }
215        return waitButton;
216    }
217
218    /**
219     * This method initializes pressButton
220     *
221     * @return javax.swing.JButton
222     */
223    private JButton getPressButton() {
224        if (pressButton == null) {
225            pressButton = new JButton();
226            pressButton.setText("Press a Button");
227            pressButton.addActionListener(new java.awt.event.ActionListener() {
228                @Override
229                public void actionPerformed(java.awt.event.ActionEvent e) {
230                    JPanel panel = new JPanel();
231                    JLabel text = new JLabel("What button to press?");
232                    JComboBox keys = new JComboBox(PressAction.KEYS);
233                    keys.setEditable(true);
234                    JComboBox direction = new JComboBox(PressAction.DOWNUP_FLAG_MAP.values().toArray());
235                    panel.add(text);
236                    panel.add(keys);
237                    panel.add(direction);
238
239                    int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION);
240                    if (result == JOptionPane.OK_OPTION) {
241                        // Look up the "flag" value for the press choice
242                        Map<String, String> lookupMap = PressAction.DOWNUP_FLAG_MAP.inverse();
243                        String flag = lookupMap.get(direction.getSelectedItem());
244                        addAction(new PressAction((String) keys.getSelectedItem(), flag));
245                    }
246                }
247            });
248        }
249        return pressButton;
250    }
251
252    /**
253     * This method initializes typeButton
254     *
255     * @return javax.swing.JButton
256     */
257    private JButton getTypeButton() {
258        if (typeButton == null) {
259            typeButton = new JButton();
260            typeButton.setText("Type Something");
261            typeButton.addActionListener(new java.awt.event.ActionListener() {
262                @Override
263                public void actionPerformed(java.awt.event.ActionEvent e) {
264                    String whatToType = JOptionPane.showInputDialog("What to type?");
265                    if (whatToType != null) {
266                        addAction(new TypeAction(whatToType));
267                    }
268                }
269            });
270        }
271        return typeButton;
272    }
273
274    /**
275     * This method initializes flingButton
276     *
277     * @return javax.swing.JButton
278     */
279    private JButton getFlingButton() {
280        if (flingButton == null) {
281            flingButton = new JButton();
282            flingButton.setText("Fling");
283            flingButton.addActionListener(new java.awt.event.ActionListener() {
284                @Override
285                public void actionPerformed(java.awt.event.ActionEvent e) {
286                    JPanel panel = new JPanel();
287                    panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
288                    panel.add(new JLabel("Which Direction to fling?"));
289                    JComboBox directionChooser = new JComboBox(DragAction.Direction.getNames());
290                    panel.add(directionChooser);
291                    panel.add(new JLabel("How long to drag (in ms)?"));
292                    JTextField ms = new JTextField();
293                    ms.setText("1000");
294                    panel.add(ms);
295                    panel.add(new JLabel("How many steps to do it in?"));
296                    JTextField steps = new JTextField();
297                    steps.setText("10");
298                    panel.add(steps);
299
300
301
302                    int result = JOptionPane.showConfirmDialog(null, panel, "Input", JOptionPane.OK_CANCEL_OPTION);
303                    if (result == JOptionPane.OK_OPTION) {
304                        DragAction.Direction dir =
305                            DragAction.Direction.valueOf((String) directionChooser.getSelectedItem());
306                        long millis = Long.parseLong(ms.getText());
307                        int numSteps = Integer.parseInt(steps.getText());
308
309                        addAction(newFlingAction(dir, numSteps, millis));
310                    }
311                }
312            });
313        }
314        return flingButton;
315    }
316
317    private DragAction newFlingAction(Direction dir, int numSteps, long millis) {
318        int width = Integer.parseInt(device.getProperty("display.width"));
319        int height = Integer.parseInt(device.getProperty("display.height"));
320
321        // Adjust the w/h to a pct of the total size, so we don't hit things on the "outside"
322        width = (int) (width * 0.8f);
323        height = (int) (height * 0.8f);
324        int minW = (int) (width * 0.2f);
325        int minH = (int) (height * 0.2f);
326
327        int midWidth = width / 2;
328        int midHeight = height / 2;
329
330        int startx = minW;
331        int starty = minH;
332        int endx = minW;
333        int endy = minH;
334
335        switch (dir) {
336            case NORTH:
337                startx = endx = midWidth;
338                starty = height;
339                break;
340            case SOUTH:
341                startx = endx = midWidth;
342                endy = height;
343                break;
344            case EAST:
345                starty = endy = midHeight;
346                endx = width;
347                break;
348            case WEST:
349                starty = endy = midHeight;
350                startx = width;
351                break;
352        }
353
354        return new DragAction(dir, startx, starty, endx, endy, numSteps, millis);
355    }
356
357    /**
358     * This method initializes exportActionButton
359     *
360     * @return javax.swing.JButton
361     */
362    private JButton getExportActionButton() {
363        if (exportActionButton == null) {
364            exportActionButton = new JButton();
365            exportActionButton.setText("Export Actions");
366            exportActionButton.addActionListener(new java.awt.event.ActionListener() {
367                @Override
368                public void actionPerformed(java.awt.event.ActionEvent ev) {
369                    JFileChooser fc = new JFileChooser();
370                    if (fc.showSaveDialog(null) == JFileChooser.APPROVE_OPTION) {
371                        try {
372                            actionListModel.export(fc.getSelectedFile());
373                        } catch (FileNotFoundException e) {
374                            LOG.log(Level.SEVERE, "Unable to save file", e);
375                        }
376                    }
377                }
378            });
379        }
380        return exportActionButton;
381    }
382
383    /**
384     * This method initializes refreshButton
385     *
386     * @return javax.swing.JButton
387     */
388    private JButton getRefreshButton() {
389        if (refreshButton == null) {
390            refreshButton = new JButton();
391            refreshButton.setText("Refresh Display");
392            refreshButton.addActionListener(new java.awt.event.ActionListener() {
393                @Override
394                public void actionPerformed(java.awt.event.ActionEvent e) {
395                    refreshDisplay();
396                }
397            });
398        }
399        return refreshButton;
400    }
401
402    private void touch(MouseEvent event) {
403        int x = event.getX();
404        int y = event.getY();
405
406        // Since we scaled the image down, our x/y are scaled as well.
407        double scalex = ((double) currentImage.getWidth()) / ((double) scaledImage.getWidth());
408        double scaley = ((double) currentImage.getHeight()) / ((double) scaledImage.getHeight());
409
410        x = (int) (x * scalex);
411        y = (int) (y * scaley);
412
413        switch (event.getID()) {
414            case MouseEvent.MOUSE_CLICKED:
415                addAction(new TouchAction(x, y, MonkeyDevice.DOWN_AND_UP));
416                break;
417            case MouseEvent.MOUSE_PRESSED:
418                addAction(new TouchAction(x, y, MonkeyDevice.DOWN));
419                break;
420            case MouseEvent.MOUSE_RELEASED:
421                addAction(new TouchAction(x, y, MonkeyDevice.UP));
422                break;
423        }
424    }
425
426    public void addAction(Action a) {
427        actionListModel.add(a);
428        try {
429            a.execute(device);
430        } catch (Exception e) {
431            LOG.log(Level.SEVERE, "Unable to execute action!", e);
432        }
433    }
434}
435