Home | History | Annotate | Download | only in ui
      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  */
     16 
     17 package com.android.hierarchyviewerlib.ui;
     18 
     19 import com.android.hierarchyviewerlib.device.ViewNode;
     20 import com.android.hierarchyviewerlib.models.PixelPerfectModel;
     21 import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener;
     22 
     23 import org.eclipse.swt.SWT;
     24 import org.eclipse.swt.custom.ScrolledComposite;
     25 import org.eclipse.swt.events.DisposeEvent;
     26 import org.eclipse.swt.events.DisposeListener;
     27 import org.eclipse.swt.events.KeyEvent;
     28 import org.eclipse.swt.events.KeyListener;
     29 import org.eclipse.swt.events.MouseEvent;
     30 import org.eclipse.swt.events.MouseListener;
     31 import org.eclipse.swt.events.MouseMoveListener;
     32 import org.eclipse.swt.events.PaintEvent;
     33 import org.eclipse.swt.events.PaintListener;
     34 import org.eclipse.swt.graphics.Color;
     35 import org.eclipse.swt.graphics.Image;
     36 import org.eclipse.swt.graphics.Point;
     37 import org.eclipse.swt.graphics.RGB;
     38 import org.eclipse.swt.widgets.Canvas;
     39 import org.eclipse.swt.widgets.Composite;
     40 import org.eclipse.swt.widgets.Display;
     41 
     42 public class PixelPerfect extends ScrolledComposite implements IImageChangeListener {
     43     private Canvas mCanvas;
     44 
     45     private PixelPerfectModel mModel;
     46 
     47     private Image mImage;
     48 
     49     private Color mCrosshairColor;
     50 
     51     private Color mMarginColor;
     52 
     53     private Color mBorderColor;
     54 
     55     private Color mPaddingColor;
     56 
     57     private int mWidth;
     58 
     59     private int mHeight;
     60 
     61     private Point mCrosshairLocation;
     62 
     63     private ViewNode mSelectedNode;
     64 
     65     private Image mOverlayImage;
     66 
     67     private double mOverlayTransparency;
     68 
     69     public PixelPerfect(Composite parent) {
     70         super(parent, SWT.H_SCROLL | SWT.V_SCROLL);
     71         mCanvas = new Canvas(this, SWT.NONE);
     72         setContent(mCanvas);
     73         setExpandHorizontal(true);
     74         setExpandVertical(true);
     75         mModel = PixelPerfectModel.getModel();
     76         mModel.addImageChangeListener(this);
     77 
     78         mCanvas.addPaintListener(mPaintListener);
     79         mCanvas.addMouseListener(mMouseListener);
     80         mCanvas.addMouseMoveListener(mMouseMoveListener);
     81         mCanvas.addKeyListener(mKeyListener);
     82 
     83         addDisposeListener(mDisposeListener);
     84 
     85         mCrosshairColor = new Color(Display.getDefault(), new RGB(0, 255, 255));
     86         mBorderColor = new Color(Display.getDefault(), new RGB(255, 0, 0));
     87         mMarginColor = new Color(Display.getDefault(), new RGB(0, 255, 0));
     88         mPaddingColor = new Color(Display.getDefault(), new RGB(0, 0, 255));
     89 
     90         imageLoaded();
     91     }
     92 
     93     private DisposeListener mDisposeListener = new DisposeListener() {
     94         @Override
     95         public void widgetDisposed(DisposeEvent e) {
     96             mModel.removeImageChangeListener(PixelPerfect.this);
     97             mCrosshairColor.dispose();
     98             mBorderColor.dispose();
     99             mPaddingColor.dispose();
    100         }
    101     };
    102 
    103     @Override
    104     public boolean setFocus() {
    105         return mCanvas.setFocus();
    106     }
    107 
    108     private MouseListener mMouseListener = new MouseListener() {
    109 
    110         @Override
    111         public void mouseDoubleClick(MouseEvent e) {
    112             // pass
    113         }
    114 
    115         @Override
    116         public void mouseDown(MouseEvent e) {
    117             handleMouseEvent(e);
    118         }
    119 
    120         @Override
    121         public void mouseUp(MouseEvent e) {
    122             handleMouseEvent(e);
    123         }
    124 
    125     };
    126 
    127     private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
    128         @Override
    129         public void mouseMove(MouseEvent e) {
    130             if (e.stateMask != 0) {
    131                 handleMouseEvent(e);
    132             }
    133         }
    134     };
    135 
    136     private void handleMouseEvent(MouseEvent e) {
    137         synchronized (PixelPerfect.this) {
    138             if (mImage == null) {
    139                 return;
    140             }
    141             int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
    142             int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
    143             e.x -= leftOffset;
    144             e.y -= topOffset;
    145             e.x = Math.max(e.x, 0);
    146             e.x = Math.min(e.x, mWidth - 1);
    147             e.y = Math.max(e.y, 0);
    148             e.y = Math.min(e.y, mHeight - 1);
    149         }
    150         mModel.setCrosshairLocation(e.x, e.y);
    151     }
    152 
    153     private KeyListener mKeyListener = new KeyListener() {
    154 
    155         @Override
    156         public void keyPressed(KeyEvent e) {
    157             boolean crosshairMoved = false;
    158             synchronized (PixelPerfect.this) {
    159                 if (mImage != null) {
    160                     switch (e.keyCode) {
    161                         case SWT.ARROW_UP:
    162                             if (mCrosshairLocation.y != 0) {
    163                                 mCrosshairLocation.y--;
    164                                 crosshairMoved = true;
    165                             }
    166                             break;
    167                         case SWT.ARROW_DOWN:
    168                             if (mCrosshairLocation.y != mHeight - 1) {
    169                                 mCrosshairLocation.y++;
    170                                 crosshairMoved = true;
    171                             }
    172                             break;
    173                         case SWT.ARROW_LEFT:
    174                             if (mCrosshairLocation.x != 0) {
    175                                 mCrosshairLocation.x--;
    176                                 crosshairMoved = true;
    177                             }
    178                             break;
    179                         case SWT.ARROW_RIGHT:
    180                             if (mCrosshairLocation.x != mWidth - 1) {
    181                                 mCrosshairLocation.x++;
    182                                 crosshairMoved = true;
    183                             }
    184                             break;
    185                     }
    186                 }
    187             }
    188             if (crosshairMoved) {
    189                 mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y);
    190             }
    191         }
    192 
    193         @Override
    194         public void keyReleased(KeyEvent e) {
    195             // pass
    196         }
    197 
    198     };
    199 
    200     private PaintListener mPaintListener = new PaintListener() {
    201         @Override
    202         public void paintControl(PaintEvent e) {
    203             synchronized (PixelPerfect.this) {
    204                 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
    205                 e.gc.fillRectangle(0, 0, mCanvas.getSize().x, mCanvas.getSize().y);
    206                 if (mImage != null) {
    207                     // Let's be cool and put it in the center...
    208                     int leftOffset = mCanvas.getSize().x / 2 - mWidth / 2;
    209                     int topOffset = mCanvas.getSize().y / 2 - mHeight / 2;
    210                     e.gc.drawImage(mImage, leftOffset, topOffset);
    211                     if (mOverlayImage != null) {
    212                         e.gc.setAlpha((int) (mOverlayTransparency * 255));
    213                         int overlayTopOffset =
    214                                 mCanvas.getSize().y / 2 + mHeight / 2
    215                                         - mOverlayImage.getBounds().height;
    216                         e.gc.drawImage(mOverlayImage, leftOffset, overlayTopOffset);
    217                         e.gc.setAlpha(255);
    218                     }
    219 
    220                     if (mSelectedNode != null) {
    221                         // If the screen is in landscape mode, the
    222                         // coordinates are backwards.
    223                         int leftShift = 0;
    224                         int topShift = 0;
    225                         int nodeLeft = mSelectedNode.left;
    226                         int nodeTop = mSelectedNode.top;
    227                         int nodeWidth = mSelectedNode.width;
    228                         int nodeHeight = mSelectedNode.height;
    229                         int nodeMarginLeft = mSelectedNode.marginLeft;
    230                         int nodeMarginTop = mSelectedNode.marginTop;
    231                         int nodeMarginRight = mSelectedNode.marginRight;
    232                         int nodeMarginBottom = mSelectedNode.marginBottom;
    233                         int nodePadLeft = mSelectedNode.paddingLeft;
    234                         int nodePadTop = mSelectedNode.paddingTop;
    235                         int nodePadRight = mSelectedNode.paddingRight;
    236                         int nodePadBottom = mSelectedNode.paddingBottom;
    237                         ViewNode cur = mSelectedNode;
    238                         while (cur.parent != null) {
    239                             leftShift += cur.parent.left - cur.parent.scrollX;
    240                             topShift += cur.parent.top - cur.parent.scrollY;
    241                             cur = cur.parent;
    242                         }
    243 
    244                         // Everything is sideways.
    245                         if (cur.width > cur.height) {
    246                             e.gc.setForeground(mPaddingColor);
    247                             e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
    248                                     + nodePadBottom,
    249                                     topOffset + leftShift + nodeLeft + nodePadLeft, nodeHeight
    250                                             - nodePadBottom - nodePadTop, nodeWidth - nodePadRight
    251                                             - nodePadLeft);
    252                             e.gc.setForeground(mMarginColor);
    253                             e.gc.drawRectangle(leftOffset + mWidth - nodeTop - topShift - nodeHeight
    254                                     - nodeMarginBottom, topOffset + leftShift + nodeLeft
    255                                     - nodeMarginLeft,
    256                                     nodeHeight + nodeMarginBottom + nodeMarginTop, nodeWidth
    257                                             + nodeMarginRight + nodeMarginLeft);
    258                             e.gc.setForeground(mBorderColor);
    259                             e.gc.drawRectangle(
    260                                     leftOffset + mWidth - nodeTop - topShift - nodeHeight, topOffset
    261                                             + leftShift + nodeLeft, nodeHeight, nodeWidth);
    262                         } else {
    263                             e.gc.setForeground(mPaddingColor);
    264                             e.gc.drawRectangle(leftOffset + leftShift + nodeLeft + nodePadLeft,
    265                                     topOffset + topShift + nodeTop + nodePadTop, nodeWidth
    266                                             - nodePadRight - nodePadLeft, nodeHeight
    267                                             - nodePadBottom - nodePadTop);
    268                             e.gc.setForeground(mMarginColor);
    269                             e.gc.drawRectangle(leftOffset + leftShift + nodeLeft - nodeMarginLeft,
    270                                     topOffset + topShift + nodeTop - nodeMarginTop, nodeWidth
    271                                             + nodeMarginRight + nodeMarginLeft, nodeHeight
    272                                             + nodeMarginBottom + nodeMarginTop);
    273                             e.gc.setForeground(mBorderColor);
    274                             e.gc.drawRectangle(leftOffset + leftShift + nodeLeft, topOffset
    275                                     + topShift + nodeTop, nodeWidth, nodeHeight);
    276                         }
    277                     }
    278                     if (mCrosshairLocation != null) {
    279                         e.gc.setForeground(mCrosshairColor);
    280                         e.gc.drawLine(leftOffset, topOffset + mCrosshairLocation.y, leftOffset
    281                                 + mWidth - 1, topOffset + mCrosshairLocation.y);
    282                         e.gc.drawLine(leftOffset + mCrosshairLocation.x, topOffset, leftOffset
    283                                 + mCrosshairLocation.x, topOffset + mHeight - 1);
    284                     }
    285                 }
    286             }
    287         }
    288     };
    289 
    290     private void doRedraw() {
    291         Display.getDefault().syncExec(new Runnable() {
    292             @Override
    293             public void run() {
    294                 mCanvas.redraw();
    295             }
    296         });
    297     }
    298 
    299     private void loadImage() {
    300         mImage = mModel.getImage();
    301         if (mImage != null) {
    302             mWidth = mImage.getBounds().width;
    303             mHeight = mImage.getBounds().height;
    304         } else {
    305             mWidth = 0;
    306             mHeight = 0;
    307         }
    308         setMinSize(mWidth, mHeight);
    309     }
    310 
    311     @Override
    312     public void imageLoaded() {
    313         Display.getDefault().syncExec(new Runnable() {
    314             @Override
    315             public void run() {
    316                 synchronized (this) {
    317                     loadImage();
    318                     mCrosshairLocation = mModel.getCrosshairLocation();
    319                     mSelectedNode = mModel.getSelected();
    320                     mOverlayImage = mModel.getOverlayImage();
    321                     mOverlayTransparency = mModel.getOverlayTransparency();
    322                 }
    323             }
    324         });
    325         doRedraw();
    326     }
    327 
    328     @Override
    329     public void imageChanged() {
    330         Display.getDefault().syncExec(new Runnable() {
    331             @Override
    332             public void run() {
    333                 synchronized (this) {
    334                     loadImage();
    335                 }
    336             }
    337         });
    338         doRedraw();
    339     }
    340 
    341     @Override
    342     public void crosshairMoved() {
    343         synchronized (this) {
    344             mCrosshairLocation = mModel.getCrosshairLocation();
    345         }
    346         doRedraw();
    347     }
    348 
    349     @Override
    350     public void selectionChanged() {
    351         synchronized (this) {
    352             mSelectedNode = mModel.getSelected();
    353         }
    354         doRedraw();
    355     }
    356 
    357     // Note the syncExec and then synchronized... It avoids deadlock
    358     @Override
    359     public void treeChanged() {
    360         Display.getDefault().syncExec(new Runnable() {
    361             @Override
    362             public void run() {
    363                 synchronized (this) {
    364                     mSelectedNode = mModel.getSelected();
    365                 }
    366             }
    367         });
    368         doRedraw();
    369     }
    370 
    371     @Override
    372     public void zoomChanged() {
    373         // pass
    374     }
    375 
    376     @Override
    377     public void overlayChanged() {
    378         synchronized (this) {
    379             mOverlayImage = mModel.getOverlayImage();
    380             mOverlayTransparency = mModel.getOverlayTransparency();
    381         }
    382         doRedraw();
    383     }
    384 
    385     @Override
    386     public void overlayTransparencyChanged() {
    387         synchronized (this) {
    388             mOverlayTransparency = mModel.getOverlayTransparency();
    389         }
    390         doRedraw();
    391     }
    392 }
    393