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.HierarchyViewerDirector;
     20 import com.android.hierarchyviewerlib.models.TreeViewModel;
     21 import com.android.hierarchyviewerlib.models.TreeViewModel.ITreeChangeListener;
     22 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode;
     23 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Point;
     24 
     25 import org.eclipse.swt.SWT;
     26 import org.eclipse.swt.events.DisposeEvent;
     27 import org.eclipse.swt.events.DisposeListener;
     28 import org.eclipse.swt.events.MouseEvent;
     29 import org.eclipse.swt.events.MouseListener;
     30 import org.eclipse.swt.events.PaintEvent;
     31 import org.eclipse.swt.events.PaintListener;
     32 import org.eclipse.swt.graphics.GC;
     33 import org.eclipse.swt.graphics.Rectangle;
     34 import org.eclipse.swt.graphics.Transform;
     35 import org.eclipse.swt.widgets.Canvas;
     36 import org.eclipse.swt.widgets.Composite;
     37 import org.eclipse.swt.widgets.Display;
     38 import org.eclipse.swt.widgets.Event;
     39 import org.eclipse.swt.widgets.Listener;
     40 
     41 import java.util.ArrayList;
     42 
     43 public class LayoutViewer extends Canvas implements ITreeChangeListener {
     44 
     45     private TreeViewModel mModel;
     46 
     47     private DrawableViewNode mTree;
     48 
     49     private DrawableViewNode mSelectedNode;
     50 
     51     private Transform mTransform;
     52 
     53     private Transform mInverse;
     54 
     55     private double mScale;
     56 
     57     private boolean mShowExtras = false;
     58 
     59     private boolean mOnBlack = true;
     60 
     61     public LayoutViewer(Composite parent) {
     62         super(parent, SWT.NONE);
     63         mModel = TreeViewModel.getModel();
     64         mModel.addTreeChangeListener(this);
     65 
     66         addDisposeListener(mDisposeListener);
     67         addPaintListener(mPaintListener);
     68         addListener(SWT.Resize, mResizeListener);
     69         addMouseListener(mMouseListener);
     70 
     71         mTransform = new Transform(Display.getDefault());
     72         mInverse = new Transform(Display.getDefault());
     73 
     74         treeChanged();
     75     }
     76 
     77     public void setShowExtras(boolean show) {
     78         mShowExtras = show;
     79         doRedraw();
     80     }
     81 
     82     public void setOnBlack(boolean value) {
     83         mOnBlack = value;
     84         doRedraw();
     85     }
     86 
     87     public boolean getOnBlack() {
     88         return mOnBlack;
     89     }
     90 
     91     private DisposeListener mDisposeListener = new DisposeListener() {
     92         @Override
     93         public void widgetDisposed(DisposeEvent e) {
     94             mModel.removeTreeChangeListener(LayoutViewer.this);
     95             mTransform.dispose();
     96             mInverse.dispose();
     97             if (mSelectedNode != null) {
     98                 mSelectedNode.viewNode.dereferenceImage();
     99             }
    100         }
    101     };
    102 
    103     private Listener mResizeListener = new Listener() {
    104         @Override
    105         public void handleEvent(Event e) {
    106             synchronized (this) {
    107                 setTransform();
    108             }
    109         }
    110     };
    111 
    112     private MouseListener mMouseListener = new MouseListener() {
    113 
    114         @Override
    115         public void mouseDoubleClick(MouseEvent e) {
    116             if (mSelectedNode != null) {
    117                 HierarchyViewerDirector.getDirector()
    118                         .showCapture(getShell(), mSelectedNode.viewNode);
    119             }
    120         }
    121 
    122         @Override
    123         public void mouseDown(MouseEvent e) {
    124             boolean selectionChanged = false;
    125             DrawableViewNode newSelection = null;
    126             synchronized (LayoutViewer.this) {
    127                 if (mTree != null) {
    128                     float[] pt = {
    129                             e.x, e.y
    130                     };
    131                     mInverse.transform(pt);
    132                     newSelection =
    133                             updateSelection(mTree, pt[0], pt[1], 0, 0, 0, 0, mTree.viewNode.width,
    134                                     mTree.viewNode.height);
    135                     if (mSelectedNode != newSelection) {
    136                         selectionChanged = true;
    137                     }
    138                 }
    139             }
    140             if (selectionChanged) {
    141                 mModel.setSelection(newSelection);
    142             }
    143         }
    144 
    145         @Override
    146         public void mouseUp(MouseEvent e) {
    147             // pass
    148         }
    149     };
    150 
    151     private DrawableViewNode updateSelection(DrawableViewNode node, float x, float y, int left,
    152             int top, int clipX, int clipY, int clipWidth, int clipHeight) {
    153         if (!node.treeDrawn) {
    154             return null;
    155         }
    156         // Update the clip
    157         int x1 = Math.max(left, clipX);
    158         int x2 = Math.min(left + node.viewNode.width, clipX + clipWidth);
    159         int y1 = Math.max(top, clipY);
    160         int y2 = Math.min(top + node.viewNode.height, clipY + clipHeight);
    161         clipX = x1;
    162         clipY = y1;
    163         clipWidth = x2 - x1;
    164         clipHeight = y2 - y1;
    165         if (x < clipX || x > clipX + clipWidth || y < clipY || y > clipY + clipHeight) {
    166             return null;
    167         }
    168         final int N = node.children.size();
    169         for (int i = N - 1; i >= 0; i--) {
    170             DrawableViewNode child = node.children.get(i);
    171             DrawableViewNode ret =
    172                     updateSelection(child, x, y,
    173                             left + child.viewNode.left - node.viewNode.scrollX, top
    174                                     + child.viewNode.top - node.viewNode.scrollY, clipX, clipY,
    175                             clipWidth, clipHeight);
    176             if (ret != null) {
    177                 return ret;
    178             }
    179         }
    180         return node;
    181     }
    182 
    183     private PaintListener mPaintListener = new PaintListener() {
    184         @Override
    185         public void paintControl(PaintEvent e) {
    186             synchronized (LayoutViewer.this) {
    187                 if (mOnBlack) {
    188                     e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
    189                 } else {
    190                     e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
    191                 }
    192                 e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
    193                 if (mTree != null) {
    194                     e.gc.setLineWidth((int) Math.ceil(0.3 / mScale));
    195                     e.gc.setTransform(mTransform);
    196                     if (mOnBlack) {
    197                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
    198                     } else {
    199                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
    200                     }
    201                     Rectangle parentClipping = e.gc.getClipping();
    202                     e.gc.setClipping(0, 0, mTree.viewNode.width + (int) Math.ceil(0.3 / mScale),
    203                             mTree.viewNode.height + (int) Math.ceil(0.3 / mScale));
    204                     paintRecursive(e.gc, mTree, 0, 0, true);
    205 
    206                     if (mSelectedNode != null) {
    207                         e.gc.setClipping(parentClipping);
    208 
    209                         // w00t, let's be nice and display the whole path in
    210                         // light red and the selected node in dark red.
    211                         ArrayList<Point> rightLeftDistances = new ArrayList<Point>();
    212                         int left = 0;
    213                         int top = 0;
    214                         DrawableViewNode currentNode = mSelectedNode;
    215                         while (currentNode != mTree) {
    216                             left += currentNode.viewNode.left;
    217                             top += currentNode.viewNode.top;
    218                             currentNode = currentNode.parent;
    219                             left -= currentNode.viewNode.scrollX;
    220                             top -= currentNode.viewNode.scrollY;
    221                             rightLeftDistances.add(new Point(left, top));
    222                         }
    223                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_DARK_RED));
    224                         currentNode = mSelectedNode.parent;
    225                         final int N = rightLeftDistances.size();
    226                         for (int i = 0; i < N; i++) {
    227                             e.gc.drawRectangle((int) (left - rightLeftDistances.get(i).x),
    228                                     (int) (top - rightLeftDistances.get(i).y),
    229                                     currentNode.viewNode.width, currentNode.viewNode.height);
    230                             currentNode = currentNode.parent;
    231                         }
    232 
    233                         if (mShowExtras && mSelectedNode.viewNode.image != null) {
    234                             e.gc.drawImage(mSelectedNode.viewNode.image, left, top);
    235                             if (mOnBlack) {
    236                                 e.gc.setForeground(Display.getDefault().getSystemColor(
    237                                         SWT.COLOR_WHITE));
    238                             } else {
    239                                 e.gc.setForeground(Display.getDefault().getSystemColor(
    240                                         SWT.COLOR_BLACK));
    241                             }
    242                             paintRecursive(e.gc, mSelectedNode, left, top, true);
    243 
    244                         }
    245 
    246                         e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_RED));
    247                         e.gc.setLineWidth((int) Math.ceil(2 / mScale));
    248                         e.gc.drawRectangle(left, top, mSelectedNode.viewNode.width,
    249                                 mSelectedNode.viewNode.height);
    250                     }
    251                 }
    252             }
    253         }
    254     };
    255 
    256     private void paintRecursive(GC gc, DrawableViewNode node, int left, int top, boolean root) {
    257         if (!node.treeDrawn) {
    258             return;
    259         }
    260         // Don't shift the root
    261         if (!root) {
    262             left += node.viewNode.left;
    263             top += node.viewNode.top;
    264         }
    265         Rectangle parentClipping = gc.getClipping();
    266         int x1 = Math.max(parentClipping.x, left);
    267         int x2 =
    268                 Math.min(parentClipping.x + parentClipping.width, left + node.viewNode.width
    269                         + (int) Math.ceil(0.3 / mScale));
    270         int y1 = Math.max(parentClipping.y, top);
    271         int y2 =
    272                 Math.min(parentClipping.y + parentClipping.height, top + node.viewNode.height
    273                         + (int) Math.ceil(0.3 / mScale));
    274 
    275         // Clipping is weird... You set it to -5 and it comes out 17 or
    276         // something.
    277         if (x2 <= x1 || y2 <= y1) {
    278             return;
    279         }
    280         gc.setClipping(x1, y1, x2 - x1, y2 - y1);
    281         final int N = node.children.size();
    282         for (int i = 0; i < N; i++) {
    283             paintRecursive(gc, node.children.get(i), left - node.viewNode.scrollX, top
    284                     - node.viewNode.scrollY, false);
    285         }
    286         gc.setClipping(parentClipping);
    287         if (!node.viewNode.willNotDraw) {
    288             gc.drawRectangle(left, top, node.viewNode.width, node.viewNode.height);
    289         }
    290 
    291     }
    292 
    293     private void doRedraw() {
    294         Display.getDefault().syncExec(new Runnable() {
    295             @Override
    296             public void run() {
    297                 redraw();
    298             }
    299         });
    300     }
    301 
    302     private void setTransform() {
    303         if (mTree != null) {
    304             Rectangle bounds = getBounds();
    305             int leftRightPadding = bounds.width <= 30 ? 0 : 5;
    306             int topBottomPadding = bounds.height <= 30 ? 0 : 5;
    307             mScale =
    308                     Math.min(1.0 * (bounds.width - leftRightPadding * 2) / mTree.viewNode.width, 1.0
    309                             * (bounds.height - topBottomPadding * 2) / mTree.viewNode.height);
    310             int scaledWidth = (int) Math.ceil(mTree.viewNode.width * mScale);
    311             int scaledHeight = (int) Math.ceil(mTree.viewNode.height * mScale);
    312 
    313             mTransform.identity();
    314             mInverse.identity();
    315             mTransform.translate((bounds.width - scaledWidth) / 2.0f,
    316                     (bounds.height - scaledHeight) / 2.0f);
    317             mInverse.translate((bounds.width - scaledWidth) / 2.0f,
    318                     (bounds.height - scaledHeight) / 2.0f);
    319             mTransform.scale((float) mScale, (float) mScale);
    320             mInverse.scale((float) mScale, (float) mScale);
    321             if (bounds.width != 0 && bounds.height != 0) {
    322                 mInverse.invert();
    323             }
    324         }
    325     }
    326 
    327     @Override
    328     public void selectionChanged() {
    329         synchronized (this) {
    330             if (mSelectedNode != null) {
    331                 mSelectedNode.viewNode.dereferenceImage();
    332             }
    333             mSelectedNode = mModel.getSelection();
    334             if (mSelectedNode != null) {
    335                 mSelectedNode.viewNode.referenceImage();
    336             }
    337         }
    338         doRedraw();
    339     }
    340 
    341     // Note the syncExec and then synchronized... It avoids deadlock
    342     @Override
    343     public void treeChanged() {
    344         Display.getDefault().syncExec(new Runnable() {
    345             @Override
    346             public void run() {
    347                 synchronized (this) {
    348                     if (mSelectedNode != null) {
    349                         mSelectedNode.viewNode.dereferenceImage();
    350                     }
    351                     mTree = mModel.getTree();
    352                     mSelectedNode = mModel.getSelection();
    353                     if (mSelectedNode != null) {
    354                         mSelectedNode.viewNode.referenceImage();
    355                     }
    356                     setTransform();
    357                 }
    358             }
    359         });
    360         doRedraw();
    361     }
    362 
    363     @Override
    364     public void viewportChanged() {
    365         // pass
    366     }
    367 
    368     @Override
    369     public void zoomChanged() {
    370         // pass
    371     }
    372 }
    373