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.models.PixelPerfectModel;
     20 import com.android.hierarchyviewerlib.models.PixelPerfectModel.IImageChangeListener;
     21 
     22 import org.eclipse.swt.SWT;
     23 import org.eclipse.swt.events.DisposeEvent;
     24 import org.eclipse.swt.events.DisposeListener;
     25 import org.eclipse.swt.events.KeyEvent;
     26 import org.eclipse.swt.events.KeyListener;
     27 import org.eclipse.swt.events.MouseEvent;
     28 import org.eclipse.swt.events.MouseListener;
     29 import org.eclipse.swt.events.MouseWheelListener;
     30 import org.eclipse.swt.events.PaintEvent;
     31 import org.eclipse.swt.events.PaintListener;
     32 import org.eclipse.swt.graphics.Color;
     33 import org.eclipse.swt.graphics.GC;
     34 import org.eclipse.swt.graphics.Image;
     35 import org.eclipse.swt.graphics.ImageData;
     36 import org.eclipse.swt.graphics.PaletteData;
     37 import org.eclipse.swt.graphics.Point;
     38 import org.eclipse.swt.graphics.RGB;
     39 import org.eclipse.swt.graphics.Rectangle;
     40 import org.eclipse.swt.graphics.Transform;
     41 import org.eclipse.swt.widgets.Canvas;
     42 import org.eclipse.swt.widgets.Composite;
     43 import org.eclipse.swt.widgets.Display;
     44 
     45 public class PixelPerfectLoupe extends Canvas implements IImageChangeListener {
     46     private PixelPerfectModel mModel;
     47 
     48     private Image mImage;
     49 
     50     private Image mGrid;
     51 
     52     private Color mCrosshairColor;
     53 
     54     private int mWidth;
     55 
     56     private int mHeight;
     57 
     58     private Point mCrosshairLocation;
     59 
     60     private int mZoom;
     61 
     62     private Transform mTransform;
     63 
     64     private int mCanvasWidth;
     65 
     66     private int mCanvasHeight;
     67 
     68     private Image mOverlayImage;
     69 
     70     private double mOverlayTransparency;
     71 
     72     private boolean mShowOverlay = false;
     73 
     74     public PixelPerfectLoupe(Composite parent) {
     75         super(parent, SWT.NONE);
     76         mModel = PixelPerfectModel.getModel();
     77         mModel.addImageChangeListener(this);
     78 
     79         addPaintListener(mPaintListener);
     80         addMouseListener(mMouseListener);
     81         addMouseWheelListener(mMouseWheelListener);
     82         addDisposeListener(mDisposeListener);
     83         addKeyListener(mKeyListener);
     84 
     85         mCrosshairColor = new Color(Display.getDefault(), new RGB(255, 94, 254));
     86 
     87         mTransform = new Transform(Display.getDefault());
     88 
     89         imageLoaded();
     90     }
     91 
     92     public void setShowOverlay(boolean value) {
     93         synchronized (this) {
     94             mShowOverlay = value;
     95         }
     96         doRedraw();
     97     }
     98 
     99     private DisposeListener mDisposeListener = new DisposeListener() {
    100         @Override
    101         public void widgetDisposed(DisposeEvent e) {
    102             mModel.removeImageChangeListener(PixelPerfectLoupe.this);
    103             mCrosshairColor.dispose();
    104             mTransform.dispose();
    105             if (mGrid != null) {
    106                 mGrid.dispose();
    107             }
    108         }
    109     };
    110 
    111     private MouseListener mMouseListener = new MouseListener() {
    112 
    113         @Override
    114         public void mouseDoubleClick(MouseEvent e) {
    115             // pass
    116         }
    117 
    118         @Override
    119         public void mouseDown(MouseEvent e) {
    120             handleMouseEvent(e);
    121         }
    122 
    123         @Override
    124         public void mouseUp(MouseEvent e) {
    125             //
    126         }
    127 
    128     };
    129 
    130     private MouseWheelListener mMouseWheelListener = new MouseWheelListener() {
    131         @Override
    132         public void mouseScrolled(MouseEvent e) {
    133             int newZoom = -1;
    134             synchronized (PixelPerfectLoupe.this) {
    135                 if (mImage != null && mCrosshairLocation != null) {
    136                     if (e.count > 0) {
    137                         newZoom = mZoom + 1;
    138                     } else {
    139                         newZoom = mZoom - 1;
    140                     }
    141                 }
    142             }
    143             if (newZoom != -1) {
    144                 mModel.setZoom(newZoom);
    145             }
    146         }
    147     };
    148 
    149     private void handleMouseEvent(MouseEvent e) {
    150         int newX = -1;
    151         int newY = -1;
    152         synchronized (PixelPerfectLoupe.this) {
    153             if (mImage == null) {
    154                 return;
    155             }
    156             int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2;
    157             int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2;
    158             int x = (e.x - zoomedX) / mZoom;
    159             int y = (e.y - zoomedY) / mZoom;
    160             if (x >= 0 && x < mWidth && y >= 0 && y < mHeight) {
    161                 newX = x;
    162                 newY = y;
    163             }
    164         }
    165         if (newX != -1) {
    166             mModel.setCrosshairLocation(newX, newY);
    167         }
    168     }
    169 
    170     private KeyListener mKeyListener = new KeyListener() {
    171 
    172         @Override
    173         public void keyPressed(KeyEvent e) {
    174             boolean crosshairMoved = false;
    175             synchronized (PixelPerfectLoupe.this) {
    176                 if (mImage != null) {
    177                     switch (e.keyCode) {
    178                         case SWT.ARROW_UP:
    179                             if (mCrosshairLocation.y != 0) {
    180                                 mCrosshairLocation.y--;
    181                                 crosshairMoved = true;
    182                             }
    183                             break;
    184                         case SWT.ARROW_DOWN:
    185                             if (mCrosshairLocation.y != mHeight - 1) {
    186                                 mCrosshairLocation.y++;
    187                                 crosshairMoved = true;
    188                             }
    189                             break;
    190                         case SWT.ARROW_LEFT:
    191                             if (mCrosshairLocation.x != 0) {
    192                                 mCrosshairLocation.x--;
    193                                 crosshairMoved = true;
    194                             }
    195                             break;
    196                         case SWT.ARROW_RIGHT:
    197                             if (mCrosshairLocation.x != mWidth - 1) {
    198                                 mCrosshairLocation.x++;
    199                                 crosshairMoved = true;
    200                             }
    201                             break;
    202                     }
    203                 }
    204             }
    205             if (crosshairMoved) {
    206                 mModel.setCrosshairLocation(mCrosshairLocation.x, mCrosshairLocation.y);
    207             }
    208         }
    209 
    210         @Override
    211         public void keyReleased(KeyEvent e) {
    212             // pass
    213         }
    214 
    215     };
    216 
    217     private PaintListener mPaintListener = new PaintListener() {
    218         @Override
    219         public void paintControl(PaintEvent e) {
    220             synchronized (PixelPerfectLoupe.this) {
    221                 e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
    222                 e.gc.fillRectangle(0, 0, getSize().x, getSize().y);
    223                 if (mImage != null && mCrosshairLocation != null) {
    224                     int zoomedX = -mCrosshairLocation.x * mZoom - mZoom / 2 + getBounds().width / 2;
    225                     int zoomedY = -mCrosshairLocation.y * mZoom - mZoom / 2 + getBounds().height / 2;
    226                     mTransform.translate(zoomedX, zoomedY);
    227                     mTransform.scale(mZoom, mZoom);
    228                     e.gc.setInterpolation(SWT.NONE);
    229                     e.gc.setTransform(mTransform);
    230                     e.gc.drawImage(mImage, 0, 0);
    231                     if (mShowOverlay && mOverlayImage != null) {
    232                         e.gc.setAlpha((int) (mOverlayTransparency * 255));
    233                         e.gc.drawImage(mOverlayImage, 0, mHeight - mOverlayImage.getBounds().height);
    234                         e.gc.setAlpha(255);
    235                     }
    236 
    237                     mTransform.identity();
    238                     e.gc.setTransform(mTransform);
    239 
    240                     // If the size of the canvas has changed, we need to make
    241                     // another grid.
    242                     if (mGrid != null
    243                             && (mCanvasWidth != getBounds().width || mCanvasHeight != getBounds().height)) {
    244                         mGrid.dispose();
    245                         mGrid = null;
    246                     }
    247                     mCanvasWidth = getBounds().width;
    248                     mCanvasHeight = getBounds().height;
    249                     if (mGrid == null) {
    250                         // Make a transparent image;
    251                         ImageData imageData =
    252                                 new ImageData(mCanvasWidth + mZoom + 1, mCanvasHeight + mZoom + 1, 1,
    253                                         new PaletteData(new RGB[] {
    254                                             new RGB(0, 0, 0)
    255                                         }));
    256                         imageData.transparentPixel = 0;
    257 
    258                         // Draw the grid.
    259                         mGrid = new Image(Display.getDefault(), imageData);
    260                         GC gc = new GC(mGrid);
    261                         gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
    262                         for (int x = 0; x <= mCanvasWidth + mZoom; x += mZoom) {
    263                             gc.drawLine(x, 0, x, mCanvasHeight + mZoom);
    264                         }
    265                         for (int y = 0; y <= mCanvasHeight + mZoom; y += mZoom) {
    266                             gc.drawLine(0, y, mCanvasWidth + mZoom, y);
    267                         }
    268                         gc.dispose();
    269                     }
    270 
    271                     e.gc.setClipping(new Rectangle(zoomedX, zoomedY, mWidth * mZoom + 1, mHeight
    272                             * mZoom + 1));
    273                     e.gc.setAlpha(76);
    274                     e.gc.drawImage(mGrid, (mCanvasWidth / 2 - mZoom / 2) % mZoom - mZoom,
    275                             (mCanvasHeight / 2 - mZoom / 2) % mZoom - mZoom);
    276                     e.gc.setAlpha(255);
    277 
    278                     e.gc.setForeground(mCrosshairColor);
    279                     e.gc.drawLine(0, mCanvasHeight / 2, mCanvasWidth - 1, mCanvasHeight / 2);
    280                     e.gc.drawLine(mCanvasWidth / 2, 0, mCanvasWidth / 2, mCanvasHeight - 1);
    281                 }
    282             }
    283         }
    284     };
    285 
    286     private void doRedraw() {
    287         Display.getDefault().syncExec(new Runnable() {
    288             @Override
    289             public void run() {
    290                 redraw();
    291             }
    292         });
    293     }
    294 
    295     private void loadImage() {
    296         mImage = mModel.getImage();
    297         if (mImage != null) {
    298             mWidth = mImage.getBounds().width;
    299             mHeight = mImage.getBounds().height;
    300         } else {
    301             mWidth = 0;
    302             mHeight = 0;
    303         }
    304     }
    305 
    306     // Note the syncExec and then synchronized... It avoids deadlock
    307     @Override
    308     public void imageLoaded() {
    309         Display.getDefault().syncExec(new Runnable() {
    310             @Override
    311             public void run() {
    312                 synchronized (this) {
    313                     loadImage();
    314                     mCrosshairLocation = mModel.getCrosshairLocation();
    315                     mZoom = mModel.getZoom();
    316                     mOverlayImage = mModel.getOverlayImage();
    317                     mOverlayTransparency = mModel.getOverlayTransparency();
    318                 }
    319             }
    320         });
    321         doRedraw();
    322     }
    323 
    324     @Override
    325     public void imageChanged() {
    326         Display.getDefault().syncExec(new Runnable() {
    327             @Override
    328             public void run() {
    329                 synchronized (this) {
    330                     loadImage();
    331                 }
    332             }
    333         });
    334         doRedraw();
    335     }
    336 
    337     @Override
    338     public void crosshairMoved() {
    339         synchronized (this) {
    340             mCrosshairLocation = mModel.getCrosshairLocation();
    341         }
    342         doRedraw();
    343     }
    344 
    345     @Override
    346     public void selectionChanged() {
    347         // pass
    348     }
    349 
    350     @Override
    351     public void treeChanged() {
    352         // pass
    353     }
    354 
    355     @Override
    356     public void zoomChanged() {
    357         Display.getDefault().syncExec(new Runnable() {
    358             @Override
    359             public void run() {
    360                 synchronized (this) {
    361                     if (mGrid != null) {
    362                         // To notify that the zoom level has changed, we get rid
    363                         // of the
    364                         // grid.
    365                         mGrid.dispose();
    366                         mGrid = null;
    367                     }
    368                     mZoom = mModel.getZoom();
    369                 }
    370             }
    371         });
    372         doRedraw();
    373     }
    374 
    375     @Override
    376     public void overlayChanged() {
    377         synchronized (this) {
    378             mOverlayImage = mModel.getOverlayImage();
    379             mOverlayTransparency = mModel.getOverlayTransparency();
    380         }
    381         doRedraw();
    382     }
    383 
    384     @Override
    385     public void overlayTransparencyChanged() {
    386         synchronized (this) {
    387             mOverlayTransparency = mModel.getOverlayTransparency();
    388         }
    389         doRedraw();
    390     }
    391 }
    392