Home | History | Annotate | Download | only in ui
      1 package com.android.hierarchyviewer.ui;
      2 
      3 import com.android.ddmlib.IDevice;
      4 import com.android.ddmlib.RawImage;
      5 import com.android.hierarchyviewer.util.WorkerThread;
      6 import com.android.hierarchyviewer.scene.ViewNode;
      7 import com.android.hierarchyviewer.ui.util.PngFileFilter;
      8 import com.android.hierarchyviewer.ui.util.IconLoader;
      9 
     10 import javax.swing.JComponent;
     11 import javax.swing.JScrollPane;
     12 import javax.swing.Timer;
     13 import javax.swing.JPanel;
     14 import javax.swing.SwingUtilities;
     15 import javax.swing.BorderFactory;
     16 import javax.swing.JLabel;
     17 import javax.swing.JSlider;
     18 import javax.swing.Box;
     19 import javax.swing.JCheckBox;
     20 import javax.swing.JButton;
     21 import javax.swing.JFileChooser;
     22 import javax.swing.event.ChangeListener;
     23 import javax.swing.event.ChangeEvent;
     24 import javax.imageio.ImageIO;
     25 
     26 import org.jdesktop.swingworker.SwingWorker;
     27 
     28 import java.io.IOException;
     29 import java.io.File;
     30 import java.awt.image.BufferedImage;
     31 import java.awt.Graphics;
     32 import java.awt.Dimension;
     33 import java.awt.BorderLayout;
     34 import java.awt.Graphics2D;
     35 import java.awt.Color;
     36 import java.awt.Rectangle;
     37 import java.awt.Point;
     38 import java.awt.GridBagLayout;
     39 import java.awt.GridBagConstraints;
     40 import java.awt.Insets;
     41 import java.awt.FlowLayout;
     42 import java.awt.AlphaComposite;
     43 import java.awt.RenderingHints;
     44 import java.awt.event.ActionListener;
     45 import java.awt.event.ActionEvent;
     46 import java.awt.event.MouseAdapter;
     47 import java.awt.event.MouseEvent;
     48 import java.awt.event.MouseMotionAdapter;
     49 import java.awt.event.MouseWheelEvent;
     50 import java.awt.event.MouseWheelListener;
     51 import java.util.concurrent.ExecutionException;
     52 
     53 class ScreenViewer extends JPanel implements ActionListener {
     54     private final Workspace workspace;
     55     private final IDevice device;
     56 
     57     private GetScreenshotTask task;
     58     private BufferedImage image;
     59     private int[] scanline;
     60     private volatile boolean isLoading;
     61 
     62     private BufferedImage overlay;
     63     private AlphaComposite overlayAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.3f);
     64 
     65     private ScreenViewer.LoupeStatus status;
     66     private ScreenViewer.LoupeViewer loupe;
     67     private ScreenViewer.Crosshair crosshair;
     68 
     69     private int zoom = 8;
     70     private int y = 0;
     71 
     72     private Timer timer;
     73     private ViewNode node;
     74 
     75     private JSlider zoomSlider;
     76 
     77     ScreenViewer(Workspace workspace, IDevice device, int spacing) {
     78         setLayout(new GridBagLayout());
     79         setOpaque(false);
     80 
     81         this.workspace = workspace;
     82         this.device = device;
     83 
     84         timer = new Timer(5000, this);
     85         timer.setInitialDelay(0);
     86         timer.setRepeats(true);
     87 
     88         JPanel panel = buildViewerAndControls();
     89         add(panel, new GridBagConstraints(0, 0, 1, 1, 0.3f, 1.0f,
     90                 GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
     91                 new Insets(0, 0, 0, 0), 0, 0));
     92 
     93         JPanel loupePanel = buildLoupePanel(spacing);
     94         add(loupePanel, new GridBagConstraints(1, 0, 1, 1, 0.7f, 1.0f,
     95                 GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
     96                 new Insets(0, 0, 0, 0), 0, 0));
     97 
     98         SwingUtilities.invokeLater(new Runnable() {
     99             public void run() {
    100                 timer.start();
    101             }
    102         });
    103     }
    104 
    105     private JPanel buildLoupePanel(int spacing) {
    106         loupe = new LoupeViewer();
    107         loupe.addMouseWheelListener(new WheelZoomListener());
    108         CrosshairPanel crosshairPanel = new CrosshairPanel(loupe);
    109 
    110         JPanel loupePanel = new JPanel(new BorderLayout());
    111         loupePanel.add(crosshairPanel);
    112         status = new LoupeStatus();
    113         loupePanel.add(status, BorderLayout.SOUTH);
    114 
    115         loupePanel.setBorder(BorderFactory.createEmptyBorder(0, spacing, 0, 0));
    116         return loupePanel;
    117     }
    118 
    119     private class WheelZoomListener implements MouseWheelListener {
    120         public void mouseWheelMoved(MouseWheelEvent e) {
    121             if (zoomSlider != null) {
    122                 int val = zoomSlider.getValue();
    123                 val -= e.getWheelRotation() * 2;
    124                 zoomSlider.setValue(val);
    125             }
    126         }
    127     }
    128 
    129     private JPanel buildViewerAndControls() {
    130         JPanel panel = new JPanel(new GridBagLayout());
    131         crosshair = new Crosshair(new ScreenshotViewer());
    132         crosshair.addMouseWheelListener(new WheelZoomListener());
    133         JScrollPane scroller = new JScrollPane(crosshair);
    134         scroller.setPreferredSize(new Dimension(320, 480));
    135         scroller.setBorder(null);
    136         panel.add(scroller,
    137                 new GridBagConstraints(0, y++, 2, 1, 1.0f, 1.0f,
    138                     GridBagConstraints.FIRST_LINE_START, GridBagConstraints.BOTH,
    139                     new Insets(0, 0, 0, 0), 0, 0));
    140         buildSlider(panel, "Overlay:", "0%", "100%", 0, 100, 30, 1).addChangeListener(
    141                 new ChangeListener() {
    142                     public void stateChanged(ChangeEvent event) {
    143                         float opacity = ((JSlider) event.getSource()).getValue() / 100.0f;
    144                         overlayAlpha = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, opacity);
    145                         repaint();
    146                     }
    147         });
    148         buildOverlayExtraControls(panel);
    149         buildSlider(panel, "Refresh Rate:", "1s", "40s", 1, 40, 5, 1).addChangeListener(
    150                 new ChangeListener() {
    151                     public void stateChanged(ChangeEvent event) {
    152                         int rate = ((JSlider) event.getSource()).getValue() * 1000;
    153                         timer.setDelay(rate);
    154                         timer.setInitialDelay(0);
    155                         timer.restart();
    156                     }
    157         });
    158         zoomSlider = buildSlider(panel, "Zoom:", "2x", "24x", 2, 24, 8, 2);
    159         zoomSlider.addChangeListener(
    160                 new ChangeListener() {
    161                     public void stateChanged(ChangeEvent event) {
    162                         zoom = ((JSlider) event.getSource()).getValue();
    163                         loupe.clearGrid = true;
    164                         loupe.moveToPoint(crosshair.crosshair.x, crosshair.crosshair.y);
    165                         repaint();
    166                     }
    167         });
    168         panel.add(Box.createVerticalGlue(),
    169                 new GridBagConstraints(0, y++, 2, 1, 1.0f, 1.0f,
    170                     GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
    171                     new Insets(0, 0, 0, 0), 0, 0));
    172         return panel;
    173     }
    174 
    175     private void buildOverlayExtraControls(JPanel panel) {
    176         JPanel extras = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
    177 
    178         JButton loadOverlay = new JButton("Load...");
    179         loadOverlay.addActionListener(new ActionListener() {
    180             public void actionPerformed(ActionEvent event) {
    181                 SwingWorker<?, ?> worker = openOverlay();
    182                 if (worker != null) {
    183                     worker.execute();
    184                 }
    185             }
    186         });
    187         extras.add(loadOverlay);
    188 
    189         JCheckBox showInLoupe = new JCheckBox("Show in Loupe");
    190         showInLoupe.setSelected(false);
    191         showInLoupe.addActionListener(new ActionListener() {
    192             public void actionPerformed(ActionEvent event) {
    193                 loupe.showOverlay = ((JCheckBox) event.getSource()).isSelected();
    194                 loupe.repaint();
    195             }
    196         });
    197         extras.add(showInLoupe);
    198 
    199         panel.add(extras, new GridBagConstraints(1, y++, 1, 1, 1.0f, 0.0f,
    200                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    201                         new Insets(0, 0, 0, 0), 0, 0));
    202     }
    203 
    204     public SwingWorker<?, ?> openOverlay() {
    205         JFileChooser chooser = new JFileChooser();
    206         chooser.setFileFilter(new PngFileFilter());
    207         int choice = chooser.showOpenDialog(this);
    208         if (choice == JFileChooser.APPROVE_OPTION) {
    209             return new OpenOverlayTask(chooser.getSelectedFile());
    210         } else {
    211             return null;
    212         }
    213     }
    214 
    215     private JSlider buildSlider(JPanel panel, String title, String minName, String maxName,
    216             int min, int max, int value, int tick) {
    217         panel.add(new JLabel(title), new GridBagConstraints(0, y, 1, 1, 1.0f, 0.0f,
    218                     GridBagConstraints.LINE_END, GridBagConstraints.NONE,
    219                     new Insets(0, 0, 0, 6), 0, 0));
    220         JPanel sliderPanel = new JPanel(new FlowLayout(FlowLayout.LEFT, 0, 0));
    221         sliderPanel.add(new JLabel(minName));
    222         JSlider slider = new JSlider(min, max, value);
    223         slider.setMinorTickSpacing(tick);
    224         slider.setMajorTickSpacing(tick);
    225         slider.setSnapToTicks(true);
    226         sliderPanel.add(slider);
    227         sliderPanel.add(new JLabel(maxName));
    228         panel.add(sliderPanel, new GridBagConstraints(1, y++, 1, 1, 1.0f, 0.0f,
    229                     GridBagConstraints.FIRST_LINE_START, GridBagConstraints.NONE,
    230                         new Insets(0, 0, 0, 0), 0, 0));
    231         return slider;
    232     }
    233 
    234     void stop() {
    235         timer.stop();
    236     }
    237 
    238     void start() {
    239         timer.start();
    240     }
    241 
    242     void select(ViewNode node) {
    243         this.node = node;
    244         repaint();
    245     }
    246 
    247     class LoupeViewer extends JComponent {
    248         private final Color lineColor = new Color(1.0f, 1.0f, 1.0f, 0.3f);
    249 
    250         private int width;
    251         private int height;
    252         private BufferedImage grid;
    253         private int left;
    254         private int top;
    255         public boolean clearGrid;
    256 
    257         private final Rectangle clip = new Rectangle();
    258         private boolean showOverlay = false;
    259 
    260         LoupeViewer() {
    261             addMouseListener(new MouseAdapter() {
    262                 @Override
    263                 public void mousePressed(MouseEvent event) {
    264                     moveToPoint(event);
    265                 }
    266             });
    267             addMouseMotionListener(new MouseMotionAdapter() {
    268                 @Override
    269                 public void mouseDragged(MouseEvent event) {
    270                     moveToPoint(event);
    271                 }
    272             });
    273         }
    274 
    275         @Override
    276         protected void paintComponent(Graphics g) {
    277             if (isLoading) {
    278                 return;
    279             }
    280 
    281             g.translate(-left, -top);
    282 
    283             if (image != null) {
    284                 Graphics2D g2 = (Graphics2D) g.create();
    285                 g2.setRenderingHint(RenderingHints.KEY_INTERPOLATION,
    286                         RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
    287                 g2.scale(zoom, zoom);
    288                 g2.drawImage(image, 0, 0, null);
    289                 if (overlay != null && showOverlay) {
    290                     g2.setComposite(overlayAlpha);
    291                     g2.drawImage(overlay, 0, image.getHeight() - overlay.getHeight(), null);
    292                 }
    293                 g2.dispose();
    294             }
    295 
    296             int width = getWidth();
    297             int height = getHeight();
    298 
    299             Graphics2D g2 = null;
    300             if (width != this.width || height != this.height) {
    301                 this.width = width;
    302                 this.height = height;
    303 
    304                 grid = new BufferedImage(width + zoom + 1, height + zoom + 1,
    305                         BufferedImage.TYPE_INT_ARGB);
    306                 clearGrid = true;
    307                 g2 = grid.createGraphics();
    308             } else if (clearGrid) {
    309                 g2 = grid.createGraphics();
    310                 g2.setComposite(AlphaComposite.Clear);
    311                 g2.fillRect(0, 0, grid.getWidth(), grid.getHeight());
    312                 g2.setComposite(AlphaComposite.SrcOver);
    313             }
    314 
    315             if (clearGrid) {
    316                 clearGrid = false;
    317 
    318                 g2.setColor(lineColor);
    319                 width += zoom;
    320                 height += zoom;
    321 
    322                 for (int x = zoom; x <= width; x += zoom) {
    323                     g2.drawLine(x, 0, x, height);
    324                 }
    325 
    326                 for (int y = 0; y <= height; y += zoom) {
    327                     g2.drawLine(0, y, width, y);
    328                 }
    329 
    330                 g2.dispose();
    331             }
    332 
    333             if (image != null) {
    334                 g.getClipBounds(clip);
    335                 g.clipRect(0, 0, image.getWidth() * zoom + 1, image.getHeight() * zoom + 1);
    336                 g.drawImage(grid, clip.x - clip.x % zoom, clip.y - clip.y % zoom, null);
    337             }
    338 
    339             g.translate(left, top);
    340         }
    341 
    342         void moveToPoint(MouseEvent event) {
    343             int x = Math.max(0, Math.min((event.getX() + left) / zoom, image.getWidth() - 1));
    344             int y = Math.max(0, Math.min((event.getY() + top) / zoom, image.getHeight() - 1));
    345             moveToPoint(x, y);
    346             crosshair.moveToPoint(x, y);
    347         }
    348 
    349         void moveToPoint(int x, int y) {
    350             left = x * zoom - width / 2 + zoom / 2;
    351             top = y * zoom - height / 2 + zoom / 2;
    352             repaint();
    353         }
    354     }
    355 
    356     class LoupeStatus extends JPanel {
    357         private JLabel xLabel;
    358         private JLabel yLabel;
    359         private JLabel rLabel;
    360         private JLabel gLabel;
    361         private JLabel bLabel;
    362         private JLabel hLabel;
    363         private ScreenViewer.LoupeStatus.ColoredSquare square;
    364         private Color color;
    365 
    366         LoupeStatus() {
    367             setOpaque(true);
    368             setLayout(new GridBagLayout());
    369             setBorder(BorderFactory.createEmptyBorder(4, 4, 4, 4));
    370 
    371             square = new ColoredSquare();
    372             add(square, new GridBagConstraints(0, 0, 1, 2, 0.0f, 0.0f,
    373                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    374                     new Insets(0, 0, 0, 12), 0, 0 ));
    375 
    376             JLabel label;
    377 
    378             add(label = new JLabel("#ffffff"), new GridBagConstraints(0, 2, 1, 1, 0.0f, 0.0f,
    379                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    380                     new Insets(0, 0, 0, 12), 0, 0 ));
    381             label.setForeground(Color.WHITE);
    382             hLabel = label;
    383 
    384             add(label = new JLabel("R:"), new GridBagConstraints(1, 0, 1, 1, 0.0f, 0.0f,
    385                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    386                     new Insets(0, 6, 0, 6), 0, 0 ));
    387             label.setForeground(Color.WHITE);
    388             add(label = new JLabel("255"), new GridBagConstraints(2, 0, 1, 1, 0.0f, 0.0f,
    389                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    390                     new Insets(0, 0, 0, 12), 0, 0 ));
    391             label.setForeground(Color.WHITE);
    392             rLabel = label;
    393 
    394             add(label = new JLabel("G:"), new GridBagConstraints(1, 1, 1, 1, 0.0f, 0.0f,
    395                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    396                     new Insets(0, 6, 0, 6), 0, 0 ));
    397             label.setForeground(Color.WHITE);
    398             add(label = new JLabel("255"), new GridBagConstraints(2, 1, 1, 1, 0.0f, 0.0f,
    399                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    400                     new Insets(0, 0, 0, 12), 0, 0 ));
    401             label.setForeground(Color.WHITE);
    402             gLabel = label;
    403 
    404             add(label = new JLabel("B:"), new GridBagConstraints(1, 2, 1, 1, 0.0f, 0.0f,
    405                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    406                     new Insets(0, 6, 0, 6), 0, 0 ));
    407             label.setForeground(Color.WHITE);
    408             add(label = new JLabel("255"), new GridBagConstraints(2, 2, 1, 1, 0.0f, 0.0f,
    409                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    410                     new Insets(0, 0, 0, 12), 0, 0 ));
    411             label.setForeground(Color.WHITE);
    412             bLabel = label;
    413 
    414             add(label = new JLabel("X:"), new GridBagConstraints(3, 0, 1, 1, 0.0f, 0.0f,
    415                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    416                     new Insets(0, 6, 0, 6), 0, 0 ));
    417             label.setForeground(Color.WHITE);
    418             add(label = new JLabel("0 px"), new GridBagConstraints(4, 0, 1, 1, 0.0f, 0.0f,
    419                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    420                     new Insets(0, 0, 0, 12), 0, 0 ));
    421             label.setForeground(Color.WHITE);
    422             xLabel = label;
    423 
    424             add(label = new JLabel("Y:"), new GridBagConstraints(3, 1, 1, 1, 0.0f, 0.0f,
    425                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    426                     new Insets(0, 6, 0, 6), 0, 0 ));
    427             label.setForeground(Color.WHITE);
    428             add(label = new JLabel("0 px"), new GridBagConstraints(4, 1, 1, 1, 0.0f, 0.0f,
    429                     GridBagConstraints.LINE_START, GridBagConstraints.NONE,
    430                     new Insets(0, 0, 0, 12), 0, 0 ));
    431             label.setForeground(Color.WHITE);
    432             yLabel = label;
    433 
    434             add(Box.createHorizontalGlue(), new GridBagConstraints(5, 0, 1, 1, 1.0f, 0.0f,
    435                     GridBagConstraints.LINE_START, GridBagConstraints.BOTH,
    436                     new Insets(0, 0, 0, 0), 0, 0 ));
    437         }
    438 
    439         @Override
    440         protected void paintComponent(Graphics g) {
    441             g.setColor(Color.BLACK);
    442             g.fillRect(0, 0, getWidth(), getHeight());
    443         }
    444 
    445         void showPixel(int x, int y) {
    446             xLabel.setText(x + " px");
    447             yLabel.setText(y + " px");
    448 
    449             int pixel = image.getRGB(x, y);
    450             color = new Color(pixel);
    451             hLabel.setText("#" + Integer.toHexString(pixel));
    452             rLabel.setText(String.valueOf((pixel >> 16) & 0xff));
    453             gLabel.setText(String.valueOf((pixel >>  8) & 0xff));
    454             bLabel.setText(String.valueOf((pixel      ) & 0xff));
    455 
    456             square.repaint();
    457         }
    458 
    459         private class ColoredSquare extends JComponent {
    460             @Override
    461             public Dimension getPreferredSize() {
    462                 Dimension d = super.getPreferredSize();
    463                 d.width = 60;
    464                 d.height = 30;
    465                 return d;
    466             }
    467 
    468             @Override
    469             protected void paintComponent(Graphics g) {
    470                 g.setColor(color);
    471                 g.fillRect(0, 0, getWidth(), getHeight());
    472 
    473                 g.setColor(Color.WHITE);
    474                 g.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
    475             }
    476         }
    477     }
    478 
    479     class Crosshair extends JPanel {
    480         // magenta = 0xff5efe
    481         private final Color crosshairColor = new Color(0x00ffff);
    482         Point crosshair = new Point();
    483         private int width;
    484         private int height;
    485         private final ScreenshotViewer screenshotViewer;
    486 
    487         Crosshair(ScreenshotViewer screenshotViewer) {
    488             this.screenshotViewer = screenshotViewer;
    489             setOpaque(true);
    490             setLayout(new BorderLayout());
    491             add(screenshotViewer);
    492             addMouseListener(new MouseAdapter() {
    493                 @Override
    494                 public void mousePressed(MouseEvent event) {
    495                     moveToPoint(event);
    496                 }
    497             });
    498             addMouseMotionListener(new MouseMotionAdapter() {
    499                 @Override
    500                 public void mouseDragged(MouseEvent event) {
    501                     moveToPoint(event);
    502                 }
    503             });
    504         }
    505 
    506         void moveToPoint(int x, int y) {
    507             crosshair.x = x;
    508             crosshair.y = y;
    509             status.showPixel(crosshair.x, crosshair.y);
    510             repaint();
    511         }
    512 
    513         private void moveToPoint(MouseEvent event) {
    514             crosshair.x = Math.max(0, Math.min(image.getWidth() - 1, event.getX()));
    515             crosshair.y = Math.max(0, Math.min(image.getHeight() - 1, event.getY()));
    516             loupe.moveToPoint(crosshair.x, crosshair.y);
    517             status.showPixel(crosshair.x, crosshair.y);
    518 
    519             repaint();
    520         }
    521 
    522         @Override
    523         public Dimension getPreferredSize() {
    524             return screenshotViewer.getPreferredSize();
    525         }
    526 
    527         @Override
    528         public Dimension getMaximumSize() {
    529             return screenshotViewer.getPreferredSize();
    530         }
    531 
    532         @Override
    533         public void paint(Graphics g) {
    534             super.paint(g);
    535 
    536             if (crosshair == null || width != getWidth() || height != getHeight()) {
    537                 width = getWidth();
    538                 height = getHeight();
    539                 crosshair = new Point(width / 2, height / 2);
    540             }
    541 
    542             g.setColor(crosshairColor);
    543 
    544             g.drawLine(crosshair.x, 0, crosshair.x, height);
    545             g.drawLine(0, crosshair.y, width, crosshair.y);
    546         }
    547 
    548         @Override
    549         protected void paintComponent(Graphics g) {
    550             super.paintComponent(g);
    551             g.setColor(Color.BLACK);
    552             g.fillRect(0, 0, getWidth(), getHeight());
    553         }
    554     }
    555 
    556     class ScreenshotViewer extends JComponent {
    557         private final Color boundsColor = new Color(0xff5efe);
    558 
    559         ScreenshotViewer() {
    560             setOpaque(true);
    561         }
    562 
    563         @Override
    564         protected void paintComponent(Graphics g) {
    565             g.setColor(Color.BLACK);
    566             g.fillRect(0, 0, getWidth(), getHeight());
    567 
    568             if (isLoading) {
    569                 return;
    570             }
    571 
    572             if (image != null) {
    573                 g.drawImage(image, 0, 0, null);
    574                 if (overlay != null) {
    575                     Graphics2D g2 = (Graphics2D) g.create();
    576                     g2.setComposite(overlayAlpha);
    577                     g2.drawImage(overlay, 0, image.getHeight() - overlay.getHeight(), null);
    578                 }
    579             }
    580 
    581             if (node != null) {
    582                 Graphics s = g.create();
    583                 s.setColor(boundsColor);
    584                 ViewNode p = node.parent;
    585                 while (p != null) {
    586                     s.translate(p.left - p.scrollX, p.top - p.scrollY);
    587                     p = p.parent;
    588                 }
    589                 s.drawRect(node.left, node.top, node.width - 1, node.height - 1);
    590                 s.translate(node.left, node.top);
    591 
    592                 s.setXORMode(Color.WHITE);
    593                 if ((node.paddingBottom | node.paddingLeft |
    594                         node.paddingTop | node.paddingRight) != 0) {
    595                     s.setColor(Color.BLACK);
    596                     s.drawRect(node.paddingLeft, node.paddingTop,
    597                             node.width - node.paddingRight - node.paddingLeft - 1,
    598                             node.height - node.paddingBottom - node.paddingTop - 1);
    599                 }
    600                 if (node.hasMargins && (node.marginLeft | node.marginBottom |
    601                         node.marginRight | node.marginRight) != 0) {
    602                     s.setColor(Color.BLACK);
    603                     s.drawRect(-node.marginLeft, -node.marginTop,
    604                             node.marginLeft + node.width + node.marginRight - 1,
    605                             node.marginTop + node.height + node.marginBottom - 1);
    606                 }
    607 
    608                 s.dispose();
    609             }
    610         }
    611 
    612         @Override
    613         public Dimension getPreferredSize() {
    614             if (image == null) {
    615                 return new Dimension(320, 480);
    616             }
    617             return new Dimension(image.getWidth(), image.getHeight());
    618         }
    619     }
    620 
    621     private class CrosshairPanel extends JPanel {
    622         private final Color crosshairColor = new Color(0xff5efe);
    623         private final Insets insets = new Insets(0, 0, 0, 0);
    624 
    625         CrosshairPanel(LoupeViewer loupe) {
    626             setLayout(new BorderLayout());
    627             add(loupe);
    628         }
    629 
    630         @Override
    631         public void paint(Graphics g) {
    632             super.paint(g);
    633 
    634             g.setColor(crosshairColor);
    635 
    636             int width = getWidth();
    637             int height = getHeight();
    638 
    639             getInsets(insets);
    640 
    641             int x = (width - insets.left - insets.right) / 2;
    642             int y = (height - insets.top - insets.bottom) / 2;
    643 
    644             g.drawLine(insets.left + x, insets.top, insets.left + x, height - insets.bottom);
    645             g.drawLine(insets.left, insets.top + y, width - insets.right, insets.top + y);
    646         }
    647 
    648         @Override
    649         protected void paintComponent(Graphics g) {
    650             g.setColor(Color.BLACK);
    651             Insets insets = getInsets();
    652             g.fillRect(insets.left, insets.top, getWidth() - insets.left - insets.right,
    653                     getHeight() - insets.top - insets.bottom);
    654         }
    655     }
    656 
    657     public void actionPerformed(ActionEvent event) {
    658         if (task != null && !task.isDone()) {
    659             return;
    660         }
    661         task = new GetScreenshotTask();
    662         task.execute();
    663     }
    664 
    665     private class GetScreenshotTask extends SwingWorker<Boolean, Void> {
    666         private GetScreenshotTask() {
    667             workspace.beginTask();
    668         }
    669 
    670         @Override
    671         @WorkerThread
    672         protected Boolean doInBackground() throws Exception {
    673             RawImage rawImage;
    674             try {
    675                 rawImage = device.getScreenshot();
    676             } catch (IOException ioe) {
    677                 return false;
    678             }
    679 
    680             boolean resize = false;
    681             isLoading = true;
    682             try {
    683                 if (rawImage != null) {
    684                     if (image == null || rawImage.width != image.getWidth() ||
    685                             rawImage.height != image.getHeight()) {
    686                         image = new BufferedImage(rawImage.width, rawImage.height,
    687                                 BufferedImage.TYPE_INT_ARGB);
    688                         scanline = new int[rawImage.width];
    689                         resize = true;
    690                     }
    691 
    692                     switch (rawImage.bpp) {
    693                         case 16:
    694                             rawImage16toARGB(rawImage);
    695                             break;
    696                         case 32:
    697                             rawImage32toARGB(rawImage);
    698                             break;
    699                     }
    700                 }
    701             } finally {
    702                 isLoading = false;
    703             }
    704 
    705             return resize;
    706         }
    707 
    708         private int getMask(int length) {
    709             int res = 0;
    710             for (int i = 0 ; i < length ; i++) {
    711                 res = (res << 1) + 1;
    712             }
    713 
    714             return res;
    715         }
    716 
    717         private void rawImage32toARGB(RawImage rawImage) {
    718             byte[] buffer = rawImage.data;
    719             int index = 0;
    720 
    721             final int redOffset = rawImage.red_offset;
    722             final int redLength = rawImage.red_length;
    723             final int redMask = getMask(redLength);
    724             final int greenOffset = rawImage.green_offset;
    725             final int greenLength = rawImage.green_length;
    726             final int greenMask = getMask(greenLength);
    727             final int blueOffset = rawImage.blue_offset;
    728             final int blueLength = rawImage.blue_length;
    729             final int blueMask = getMask(blueLength);
    730             final int alphaLength = rawImage.alpha_length;
    731             final int alphaOffset = rawImage.alpha_offset;
    732             final int alphaMask = getMask(alphaLength);
    733 
    734             for (int y = 0 ; y < rawImage.height ; y++) {
    735                 for (int x = 0 ; x < rawImage.width ; x++) {
    736                     int value = buffer[index++] & 0x00FF;
    737                     value |= (buffer[index++] & 0x00FF) << 8;
    738                     value |= (buffer[index++] & 0x00FF) << 16;
    739                     value |= (buffer[index++] & 0x00FF) << 24;
    740 
    741                     int r = ((value >>> redOffset) & redMask) << (8 - redLength);
    742                     int g = ((value >>> greenOffset) & greenMask) << (8 - greenLength);
    743                     int b = ((value >>> blueOffset) & blueMask) << (8 - blueLength);
    744                     int a = 0xFF;
    745 
    746                     if (alphaLength != 0) {
    747                         a = ((value >>> alphaOffset) & alphaMask) << (8 - alphaLength);
    748                     }
    749 
    750                     scanline[x] = a << 24 | r << 16 | g << 8 | b;
    751                 }
    752 
    753                 image.setRGB(0, y, rawImage.width, 1, scanline,
    754                         0, rawImage.width);
    755             }
    756         }
    757 
    758         private void rawImage16toARGB(RawImage rawImage) {
    759             byte[] buffer = rawImage.data;
    760             int index = 0;
    761 
    762             for (int y = 0 ; y < rawImage.height ; y++) {
    763                 for (int x = 0 ; x < rawImage.width ; x++) {
    764                     int value = buffer[index++] & 0x00FF;
    765                     value |= (buffer[index++] << 8) & 0x0FF00;
    766 
    767                     int r = ((value >> 11) & 0x01F) << 3;
    768                     int g = ((value >> 5) & 0x03F) << 2;
    769                     int b = ((value     ) & 0x01F) << 3;
    770 
    771                     scanline[x] = 0xFF << 24 | r << 16 | g << 8 | b;
    772                 }
    773 
    774                 image.setRGB(0, y, rawImage.width, 1, scanline,
    775                         0, rawImage.width);
    776             }
    777         }
    778 
    779         @Override
    780         protected void done() {
    781             workspace.endTask();
    782             try {
    783                 if (get()) {
    784                     validate();
    785                     crosshair.crosshair = new Point(image.getWidth() / 2,
    786                             image.getHeight() / 2);
    787                     status.showPixel(image.getWidth() / 2, image.getHeight() / 2);
    788                     loupe.moveToPoint(image.getWidth() / 2, image.getHeight() / 2);
    789                 }
    790             } catch (InterruptedException e) {
    791                 e.printStackTrace();
    792             } catch (ExecutionException e) {
    793                 e.printStackTrace();
    794             }
    795             repaint();
    796         }
    797     }
    798 
    799     private class OpenOverlayTask extends SwingWorker<BufferedImage, Void> {
    800         private File file;
    801 
    802         private OpenOverlayTask(File file) {
    803             this.file = file;
    804             workspace.beginTask();
    805         }
    806 
    807         @Override
    808         @WorkerThread
    809         protected BufferedImage doInBackground() {
    810             try {
    811                 return IconLoader.toCompatibleImage(ImageIO.read(file));
    812             } catch (IOException ex) {
    813                 ex.printStackTrace();
    814             }
    815             return null;
    816         }
    817 
    818         @Override
    819         protected void done() {
    820             try {
    821                 overlay = get();
    822                 repaint();
    823             } catch (InterruptedException e) {
    824                 e.printStackTrace();
    825             } catch (ExecutionException e) {
    826                 e.printStackTrace();
    827             } finally {
    828                 workspace.endTask();
    829             }
    830         }
    831     }
    832 }
    833