/src/main/java/com/ribomation/droidAtScreen/gui/DeviceFrame.java * DroidAtScreenApplication.java * DeviceFrame frame = new DeviceFrame(DroidAtScreenApplication.this, dev); {{{ public void connected(final AndroidDevice dev) {^M log.debug("connected: dev=" + dev);^M ^M SwingUtilities.invokeLater(new Runnable() {^M @Override^M public void run() {^M getAppFrame().getStatusBar().message("Connected to " + dev.getName());^M ^M DeviceFrame frame = new DeviceFrame(DroidAtScreenApplication.this, dev);^M deviceTableModel.add(frame);^M fireDeviceConnected(dev);^M ^M frame.setVisible(!getSettings().isHideEmulators() || !dev.isEmulator());^M ^M updateDeviceFramePositionsOnScreen(frame);^M }^M });^M }^M }}} * deviceTableModel.add(frame); * updateDeviceFramePositionsOnScreen(frame); * 이 부분을 확실히 알아볼 것 * table 에는 등록 * 등록을 위한 class 와 디스플레이를 위한 class 로 분리하는 것은 어떨까? * select device 를 하고, 선택된 디바이스 하나에 대해 deviceframe 생성할 것 * design 의 문제임. 우선 하나만 사용하는 것으로 정하면, deviceFrame 의 기능을 applicationFrame 으로 옮길 수 있을 것 같음. === Gui files === {{{ tcheun@tcheun-system:~/my-app/src/main/java/com/ribomation/droidAtScreen/gui$ ls ApplicationFrame.java GuiUtil.java StatusBar.java DeviceFrame.java RecordingListener.java DeviceTableModel.java SkinUtil.java }}} * ApplicationFrame.java * public class ApplicationFrame extends JFrame * public ApplicationFrame(Application app) * public StatusBar getStatusBar() * public void initGUI() * protected JMenuBar createMenubar() * private JComponent createDevicesTable() === DeviceTableModel.java === * DroidAtScreenApplication.java * private final List deviceListeners = new ArrayList(); * private final DeviceTableModel deviceTableModel = new DeviceTableModel(); * public void updateDeviceFramePositionsOnScreen(DeviceFrame newFrame) * synchronized (deviceTableModel) * List devices = deviceTableModel.getDevices(); * public List getDevices() * return getDeviceTableModel().getDevices(); * public DeviceTableModel getDeviceTableModel() * return deviceTableModel; * public void connected(final AndroidDevice dev) * SwingUtilities.invokeLater(new Runnable() * public void run() * DeviceFrame frame = new DeviceFrame(DroidAtScreenApplication.this, dev); * deviceTableModel.add(frame); * DroidAtScreenApplication.java * private void initGUI() * appFrame = new ApplicationFrame(this); * appFrame.initGUI(); * ApplicationFrame.java * public void initGUI() * add(createDevicesTable(), BorderLayout.CENTER); * private JComponent createDevicesTable() {{{ private JComponent createDevicesTable() {^M JTable tbl = new JTable(app.getDeviceTableModel());^M tbl.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);^M tbl.setRowSelectionAllowed(true);^M tbl.setShowHorizontalLines(true);^M tbl.setFillsViewportHeight(true);^M tbl.setPreferredScrollableViewportSize(new Dimension(450, 150));^M ^M JScrollPane pane = new JScrollPane(tbl);^M pane.setBorder(BorderFactory.createTitledBorder("Devices"));^M ^M return pane;^M }^M }}} * DeviceTable 과 관련된 DeviceFrame 를 찾아서 다른 클래스로 대체한다. * connected 된 순간, table 이 update 된 후, select device 를 수행한다. * select 된 device 에 대해서만, DeviceFrame 를 생성한다. === DeviceFrame.java === {{{ 2016-09-07 08:58:42,748 INFO AndroidDeviceManager - ADB changed 2016-09-07 08:58:42,748 INFO AndroidDeviceManager - Connected to ADB via /127.0.0.1:5037 2016-09-07 08:58:42,761 INFO AndroidDeviceManager - Device connected: 5d027563 2016-09-07 08:58:42,762 DEBUG DroidAtScreenApplication - connected: dev=5d027563 (device) 2016-09-07 08:58:42,792 DEBUG DeviceFrame:5d027563 - DeviceFrame(device=5d027563 (device)) 2016-09-07 08:58:42,867 DEBUG DeviceFrame:5d027563 - getPreferredSize: screen=java.awt.Dimension[width=1855,height=1056], canvas=java.awt.Dimension[width=200,height=300], frame=java.awt.Dimension[width=251,height=348] 2016-09-07 08:58:42,867 DEBUG DeviceFrame:5d027563 - getPreferredSize: frame2=java.awt.Dimension[width=210,height=330] 2016-09-07 08:58:42,869 DEBUG DroidAtScreenApplication - devices size: count=1 2016-09-07 08:58:42,993 DEBUG AndroidDeviceManager - Device changed: 5d027563, mask=CHANGE_BUILD_INFO 2016-09-07 08:58:43,945 DEBUG DeviceFrame:SHV-E330K - getPreferredSize: screen=java.awt.Dimension[width=1855,height=1056], canvas=java.awt.Dimension[width=1080,height=1920], frame=java.awt.Dimension[width=1121,height=1938] 2016-09-07 08:58:43,945 DEBUG DeviceFrame:SHV-E330K - getPreferredSize: frame2=java.awt.Dimension[width=1121,height=950] 2016-09-07 08:58:43,947 DEBUG DroidAtScreenApplication - devices size: count=1 2016-09-07 08:58:56,972 DEBUG DeviceFrame:SHV-E330K - windowClosing 2016-09-07 08:59:01,250 INFO AndroidDeviceManager - ADB changed }}} * public class DeviceFrame extends JFrame * private final class AnimationActionListener implements ActionListener * private final class AnimationTimer extends Timer * public DeviceFrame(Application app, final AndroidDevice device) * setting * scale 는 100으로, landscapemode 는 false 로 설정 * canvas = new ImageCanvas(); * canvasScrollable = new JScrollPane(canvas); * add(canvasScrollable, BorderLayout.CENTER); * add(toolBar = createToolBar(), BorderLayout.WEST); * add(infoPane = new InfoPane(), BorderLayout.SOUTH);^ * canvas.setBorder(BorderFactory.createEmptyBorder());^ * canvas.addMouseListener(new MouseAdapter() * startRetriever();^ * pack(); attachment:DeviceFrame.png * * public Dimension getPreferredSize() * private void applySkin() * skin = SkinUtil.loadSkin(device.getName().toLowerCase()) {{{ if (skin != null) {^M // Apply skin^M canvas.setSkin(skin);^M pack();^M boolean wasVisible = isVisible();^M dispose();^M setUndecorated(true);^M setBackground(new Color(1.0f, 1.0f, 1.0f, 0.0f));^M toolBar.setVisible(false);^M infoPane.setVisible(false);^M setVisible(wasVisible);^M forceRepaint();^M }^M }}} * * public void startRetriever() * retriever = new Retriever(); * app.getTimer().schedule(retriever, 0, 500); * public void stopRetriever() * class Retriever extends TimerTask * public void run() * ScreenImage image = device.getScreenImage(); device 의 image 를 가져옴. * boolean fresh = canvas.getScreenshot() == null; 이미 가지고 온 이미지가 canvas 에 있는가? canvas 의 image 가 null 이면 처음 image 를 가지고 온 것임. * canvas.setScreenshot(image); 가지고 온 image 를 canvas 의 image 에 설정 * infoPane.setSizeInfo(canvas); {{{ if (fresh) {^M log = Logger.getLogger(DeviceFrame.class.getName() + ":" + device.getName());^M setTitle(device.getName());^M pack();^M applySkin();^M app.getDeviceTableModel().refresh();^M app.updateDeviceFramePositionsOnScreen(null);^M }^M }}} * * protected JComponent createToolBar() * public class InfoPane extends JPanel * class Unit * class ImageCanvas extends JComponent * public ImageCanvas() * public void setSkin(Skin skin) * public void setScreenshot(ScreenImage image) * public ScreenImage getScreenshot() * protected void paintComponent(Graphics g) * Graphics2D g2 = (Graphics2D) g; * g2.clearRect(0, 0, canvas.getWidth(), canvas.getHeight()); * AffineTransform TX = new AffineTransform(); * BufferedImage bufImg = image.toBufferedImage();^ * g2.drawImage(bufImg, TX, null); * BufferedImage toLandscape(BufferedImage img) * BufferedImage rotate(int quadrants, BufferedImage img) * public Dimension getPreferredSize() * public Dimension getMinimumSize() * public void setLandscapeMode(boolean landscape) * public void setScale(int scalePercentage) * public void updateScale(int scalePercentage) * public void setUpsideDown(boolean upsideDown) * public void setRecordingListener(RecordingListener recordingListener) * public ScreenImage getLastScreenshot() * public InfoPane getInfoPane() * public AndroidDevice getDevice() * public String getName() * public boolean isLandscapeMode() * public int getScale() * public boolean isUpsideDown() * private int scale(int value) * getScaleFactor() 의 리턴값 factor 을 0에서 100 사이의 값으로 변환 * private double getScaleFactor() * 모니터의 크기를 읽는다. {{{ final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();^M final double screenHeight = screen.getHeight();^M final double screenWidth = screen.getWidth();^M }}} * * * * DeviceFrame 의 getHeight() 와 비교하여 scale factor 을 0.75, 0.5, 0.25 중 하나로 정한다. * private void forceRepaint() * public boolean equals(Object o) * public int hashCode() * public int compareTo(DeviceFrame that) * public void setLocation(int x, int y, boolean animate) * GuiUtil.java * DeviceTableModel.java * SkinUtil.java === point === * DeviceFrame.java * public class DeviceFrame extends JFrame implements Comparable * private ImageCanvas canvas; * public DeviceFrame(Application app, final AndroidDevice device) * canvas = new ImageCanvas(); * canvasScrollable = new JScrollPane(canvas); * add(canvasScrollable, BorderLayout.CENTER); * canvas.setBorder(BorderFactory.createEmptyBorder()); {{{ canvas.addMouseListener(new MouseAdapter() {^M @Override^M public void mouseClicked(MouseEvent e) {^M Point p = e.getPoint();^M log.debug(String.format("mouse: %s", p));^M p = new Point(^M (int) (p.getX() * 100) / getScale(),^M (int) (p.getY() * 100) / getScale()^M );^M log.debug(String.format("scaled: %s", p));^M device.tap(p);^M }^M });^M }}} * class ImageCanvas extends JComponent {{{ public void setScreenshot(ScreenImage image) {^M this.image = image;^M repaint();^M }^M }}} === scale === * DroidAtScreenApplication.java 에서 connected 에서 DeviceFrame 클래스를 생성한다. * public class DeviceFrame extends JFrame * private int scalePercentage = 100; * private ImageCanvas canvas;^ * private JScrollPane canvasScrollable; * private JComponent toolBar;^ * private InfoPane infoPane;^ * public DeviceFrame(Application app, final AndroidDevice device) {{{ canvas = new ImageCanvas();^M canvasScrollable = new JScrollPane(canvas);^M ^M add(canvasScrollable, BorderLayout.CENTER);^M add(toolBar = createToolBar(), BorderLayout.WEST);^M add(infoPane = new InfoPane(), BorderLayout.SOUTH);^M startRetriever();^M pack();^ }}} * * class ImageCanvas extends JComponent * private ScreenImage image; * public ImageCanvas() * setBorder(BorderFactory.createLoweredBevelBorder()); * public Dimension getPreferredSize() * return new Dimension(200, 300); * return new Dimension(scale(image.getWidth()), scale(image.getHeight())); {{{ private int scale(int value) {^M double factor = getScaleFactor();^M if (factor != 1) {^M scalePercentage = (int) (factor * 100);^M }^M if (scalePercentage == 100) {^M return value;^M }^M return (int) Math.round(value * scalePercentage / 100.0);^M }^M }}} * private double getScaleFactor() * screenHeight 와 DeviceFrame 의 height 를 비교하여 1, 0.75, 0.5, 0.25 의 값을 리턴 === misc === * JFrame 의 pack() 에 의해 layout manager of the content pane 이 getPreferredSize method 를 call 한다. * void deviceChanged(IDevice device, int changeMask) * Sent when a device data changed, or when clients are started/terminated on the device. This is sent from a non UI thread. {{{ public void deviceChanged(IDevice device, int changeMask) { 119 // Only fire events if the phone properties are available 120 if (IDevice.CHANGE_BUILD_INFO == changeMask && device.isEmulator() == false) { 121 for (HardwareDeviceListener listener : deviceListeners) { 122 listener.onDeviceConnected(connectedDevices.get(device)); 123 } 124 } 125 } }}} * app.getTimer().schedule(retriever, 0, 500); 0.5초에 한번씩 * Application.java * java.util.Timer getTimer(); * schedule(TimerTask task, long delay, long period) * device.getScreenImage(); * private final AndroidDevice device; * AndroidDevice.java {{{ public ScreenImage getScreenImage() {^M try {^M RawImage rawImage = target.getScreenshot();^M if (rawImage == null)^M return null;^M setState(target.getState());^M return new ScreenImage(rawImage);^M } catch (IOException e) {^M setState(ConnectionState.error);^M log.error("Failed to get screenshot: " + e);^M } catch (TimeoutException e) {^M setState(ConnectionState.timeout);^M log.warn("Got timeout");^M } catch (AdbCommandRejectedException e) {^M setState(ConnectionState.rejected);^M log.error("ADB command rejected: OFFLINE=" + e.isDeviceOffline());^M }^M return null;^M }^M }}} * private final IDevice target;^ {{{ public IDevice getDevice() {^M return target;^M }^M }}} * package com.android.ddmlib; * public interface IDevice extends IShellEnabledDevice * public RawImage getScreenshot() {{{ /** * Takes a screen shot of the device and returns it as a {@link RawImage}. * * @return the screenshot as a RawImage or null if something * went wrong. * @throws TimeoutException in case of timeout on the connection. * @throws AdbCommandRejectedException if adb rejects the command * @throws IOException in case of I/O error on the connection. */ }}} * /GetPreferredSize