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.ddmuilib.ImageLoader;
     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 import com.android.hierarchyviewerlib.ui.util.DrawableViewNode.Rectangle;
     25 
     26 import org.eclipse.swt.SWT;
     27 import org.eclipse.swt.events.DisposeEvent;
     28 import org.eclipse.swt.events.DisposeListener;
     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.GC;
     35 import org.eclipse.swt.graphics.Image;
     36 import org.eclipse.swt.graphics.Path;
     37 import org.eclipse.swt.graphics.Transform;
     38 import org.eclipse.swt.widgets.Canvas;
     39 import org.eclipse.swt.widgets.Composite;
     40 import org.eclipse.swt.widgets.Display;
     41 import org.eclipse.swt.widgets.Event;
     42 import org.eclipse.swt.widgets.Listener;
     43 
     44 public class TreeViewOverview extends Canvas implements ITreeChangeListener {
     45 
     46     private TreeViewModel mModel;
     47 
     48     private DrawableViewNode mTree;
     49 
     50     private Rectangle mViewport;
     51 
     52     private Transform mTransform;
     53 
     54     private Transform mInverse;
     55 
     56     private Rectangle mBounds = new Rectangle();
     57 
     58     private double mScale;
     59 
     60     private boolean mDragging = false;
     61 
     62     private DrawableViewNode mSelectedNode;
     63 
     64     private static Image sNotSelectedImage;
     65 
     66     private static Image sSelectedImage;
     67 
     68     private static Image sFilteredImage;
     69 
     70     private static Image sFilteredSelectedImage;
     71 
     72     public TreeViewOverview(Composite parent) {
     73         super(parent, SWT.NONE);
     74 
     75         mModel = TreeViewModel.getModel();
     76         mModel.addTreeChangeListener(this);
     77 
     78         loadResources();
     79 
     80         addPaintListener(mPaintListener);
     81         addMouseListener(mMouseListener);
     82         addMouseMoveListener(mMouseMoveListener);
     83         addListener(SWT.Resize, mResizeListener);
     84         addDisposeListener(mDisposeListener);
     85 
     86         mTransform = new Transform(Display.getDefault());
     87         mInverse = new Transform(Display.getDefault());
     88 
     89         loadAllData();
     90     }
     91 
     92     private void loadResources() {
     93         ImageLoader loader = ImageLoader.getLoader(this.getClass());
     94         sNotSelectedImage = loader.loadImage("not-selected.png", Display.getDefault()); //$NON-NLS-1$
     95         sSelectedImage = loader.loadImage("selected-small.png", Display.getDefault()); //$NON-NLS-1$
     96         sFilteredImage = loader.loadImage("filtered.png", Display.getDefault()); //$NON-NLS-1$
     97         sFilteredSelectedImage =
     98                 loader.loadImage("selected-filtered-small.png", Display.getDefault()); //$NON-NLS-1$
     99     }
    100 
    101     private DisposeListener mDisposeListener = new DisposeListener() {
    102         @Override
    103         public void widgetDisposed(DisposeEvent e) {
    104             mModel.removeTreeChangeListener(TreeViewOverview.this);
    105             mTransform.dispose();
    106             mInverse.dispose();
    107         }
    108     };
    109 
    110     private MouseListener mMouseListener = new MouseListener() {
    111 
    112         @Override
    113         public void mouseDoubleClick(MouseEvent e) {
    114             // pass
    115         }
    116 
    117         @Override
    118         public void mouseDown(MouseEvent e) {
    119             boolean redraw = false;
    120             synchronized (TreeViewOverview.this) {
    121                 if (mTree != null && mViewport != null) {
    122                     mDragging = true;
    123                     redraw = true;
    124                     handleMouseEvent(transformPoint(e.x, e.y));
    125                 }
    126             }
    127             if (redraw) {
    128                 mModel.removeTreeChangeListener(TreeViewOverview.this);
    129                 mModel.setViewport(mViewport);
    130                 mModel.addTreeChangeListener(TreeViewOverview.this);
    131                 doRedraw();
    132             }
    133         }
    134 
    135         @Override
    136         public void mouseUp(MouseEvent e) {
    137             boolean redraw = false;
    138             synchronized (TreeViewOverview.this) {
    139                 if (mTree != null && mViewport != null) {
    140                     mDragging = false;
    141                     redraw = true;
    142                     handleMouseEvent(transformPoint(e.x, e.y));
    143 
    144                     // Update bounds and transform only on mouse up. That way,
    145                     // you don't get confusing behaviour during mouse drag and
    146                     // it snaps neatly at the end
    147                     setBounds();
    148                     setTransform();
    149                 }
    150             }
    151             if (redraw) {
    152                 mModel.removeTreeChangeListener(TreeViewOverview.this);
    153                 mModel.setViewport(mViewport);
    154                 mModel.addTreeChangeListener(TreeViewOverview.this);
    155                 doRedraw();
    156             }
    157         }
    158 
    159     };
    160 
    161     private MouseMoveListener mMouseMoveListener = new MouseMoveListener() {
    162         @Override
    163         public void mouseMove(MouseEvent e) {
    164             boolean moved = false;
    165             synchronized (TreeViewOverview.this) {
    166                 if (mDragging) {
    167                     moved = true;
    168                     handleMouseEvent(transformPoint(e.x, e.y));
    169                 }
    170             }
    171             if (moved) {
    172                 mModel.removeTreeChangeListener(TreeViewOverview.this);
    173                 mModel.setViewport(mViewport);
    174                 mModel.addTreeChangeListener(TreeViewOverview.this);
    175                 doRedraw();
    176             }
    177         }
    178     };
    179 
    180     private void handleMouseEvent(Point pt) {
    181         mViewport.x = pt.x - mViewport.width / 2;
    182         mViewport.y = pt.y - mViewport.height / 2;
    183         if (mViewport.x < mBounds.x) {
    184             mViewport.x = mBounds.x;
    185         }
    186         if (mViewport.y < mBounds.y) {
    187             mViewport.y = mBounds.y;
    188         }
    189         if (mViewport.x + mViewport.width > mBounds.x + mBounds.width) {
    190             mViewport.x = mBounds.x + mBounds.width - mViewport.width;
    191         }
    192         if (mViewport.y + mViewport.height > mBounds.y + mBounds.height) {
    193             mViewport.y = mBounds.y + mBounds.height - mViewport.height;
    194         }
    195     }
    196 
    197     private Point transformPoint(double x, double y) {
    198         float[] pt = {
    199                 (float) x, (float) y
    200         };
    201         mInverse.transform(pt);
    202         return new Point(pt[0], pt[1]);
    203     }
    204 
    205     private Listener mResizeListener = new Listener() {
    206         @Override
    207         public void handleEvent(Event arg0) {
    208             synchronized (TreeViewOverview.this) {
    209                 setTransform();
    210             }
    211             doRedraw();
    212         }
    213     };
    214 
    215     private PaintListener mPaintListener = new PaintListener() {
    216         @Override
    217         public void paintControl(PaintEvent e) {
    218             synchronized (TreeViewOverview.this) {
    219                 if (mTree != null) {
    220                     e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_BLACK));
    221                     e.gc.setForeground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
    222                     e.gc.fillRectangle(0, 0, getBounds().width, getBounds().height);
    223                     e.gc.setTransform(mTransform);
    224                     e.gc.setLineWidth((int) Math.ceil(0.7 / mScale));
    225                     Path connectionPath = new Path(Display.getDefault());
    226                     paintRecursive(e.gc, mTree, connectionPath);
    227                     e.gc.drawPath(connectionPath);
    228                     connectionPath.dispose();
    229 
    230                     if (mViewport != null) {
    231                         e.gc.setAlpha(50);
    232                         e.gc.setBackground(Display.getDefault().getSystemColor(SWT.COLOR_WHITE));
    233                         e.gc.fillRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
    234                                 .ceil(mViewport.width), (int) Math.ceil(mViewport.height));
    235 
    236                         e.gc.setAlpha(255);
    237                         e.gc
    238                                 .setForeground(Display.getDefault().getSystemColor(
    239                                         SWT.COLOR_DARK_GRAY));
    240                         e.gc.setLineWidth((int) Math.ceil(2 / mScale));
    241                         e.gc.drawRectangle((int) mViewport.x, (int) mViewport.y, (int) Math
    242                                 .ceil(mViewport.width), (int) Math.ceil(mViewport.height));
    243                     }
    244                 }
    245             }
    246         }
    247     };
    248 
    249     private void paintRecursive(GC gc, DrawableViewNode node, Path connectionPath) {
    250         if (mSelectedNode == node && node.viewNode.filtered) {
    251             gc.drawImage(sFilteredSelectedImage, node.left, (int) Math.round(node.top));
    252         } else if (mSelectedNode == node) {
    253             gc.drawImage(sSelectedImage, node.left, (int) Math.round(node.top));
    254         } else if (node.viewNode.filtered) {
    255             gc.drawImage(sFilteredImage, node.left, (int) Math.round(node.top));
    256         } else {
    257             gc.drawImage(sNotSelectedImage, node.left, (int) Math.round(node.top));
    258         }
    259         int N = node.children.size();
    260         if (N == 0) {
    261             return;
    262         }
    263         float childSpacing =
    264                 (1.0f * (DrawableViewNode.NODE_HEIGHT - 2 * TreeView.LINE_PADDING)) / N;
    265         for (int i = 0; i < N; i++) {
    266             DrawableViewNode child = node.children.get(i);
    267             paintRecursive(gc, child, connectionPath);
    268             float x1 = node.left + DrawableViewNode.NODE_WIDTH;
    269             float y1 =
    270                     (float) node.top + TreeView.LINE_PADDING + childSpacing * i + childSpacing / 2;
    271             float x2 = child.left;
    272             float y2 = (float) child.top + DrawableViewNode.NODE_HEIGHT / 2.0f;
    273             float cx1 = x1 + TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
    274             float cy1 = y1;
    275             float cx2 = x2 - TreeView.BEZIER_FRACTION * DrawableViewNode.PARENT_CHILD_SPACING;
    276             float cy2 = y2;
    277             connectionPath.moveTo(x1, y1);
    278             connectionPath.cubicTo(cx1, cy1, cx2, cy2, x2, y2);
    279         }
    280     }
    281 
    282     private void doRedraw() {
    283         Display.getDefault().syncExec(new Runnable() {
    284             @Override
    285             public void run() {
    286                 redraw();
    287             }
    288         });
    289     }
    290 
    291     public void loadAllData() {
    292         Display.getDefault().syncExec(new Runnable() {
    293             @Override
    294             public void run() {
    295                 synchronized (this) {
    296                     mTree = mModel.getTree();
    297                     mSelectedNode = mModel.getSelection();
    298                     mViewport = mModel.getViewport();
    299                     setBounds();
    300                     setTransform();
    301                 }
    302             }
    303         });
    304     }
    305 
    306     // Note the syncExec and then synchronized... It avoids deadlock
    307     @Override
    308     public void treeChanged() {
    309         Display.getDefault().syncExec(new Runnable() {
    310             @Override
    311             public void run() {
    312                 synchronized (this) {
    313                     mTree = mModel.getTree();
    314                     mSelectedNode = mModel.getSelection();
    315                     mViewport = mModel.getViewport();
    316                     setBounds();
    317                     setTransform();
    318                 }
    319             }
    320         });
    321         doRedraw();
    322     }
    323 
    324     private void setBounds() {
    325         if (mViewport != null && mTree != null) {
    326             mBounds.x = Math.min(mViewport.x, mTree.bounds.x);
    327             mBounds.y = Math.min(mViewport.y, mTree.bounds.y);
    328             mBounds.width =
    329                     Math.max(mViewport.x + mViewport.width, mTree.bounds.x + mTree.bounds.width)
    330                             - mBounds.x;
    331             mBounds.height =
    332                     Math.max(mViewport.y + mViewport.height, mTree.bounds.y + mTree.bounds.height)
    333                             - mBounds.y;
    334         } else if (mTree != null) {
    335             mBounds.x = mTree.bounds.x;
    336             mBounds.y = mTree.bounds.y;
    337             mBounds.width = mTree.bounds.x + mTree.bounds.width - mBounds.x;
    338             mBounds.height = mTree.bounds.y + mTree.bounds.height - mBounds.y;
    339         }
    340     }
    341 
    342     private void setTransform() {
    343         if (mTree != null) {
    344 
    345             mTransform.identity();
    346             mInverse.identity();
    347             final Point size = new Point();
    348             size.x = getBounds().width;
    349             size.y = getBounds().height;
    350             if (mBounds.width == 0 || mBounds.height == 0 || size.x == 0 || size.y == 0) {
    351                 mScale = 1;
    352             } else {
    353                 mScale = Math.min(size.x / mBounds.width, size.y / mBounds.height);
    354             }
    355             mTransform.scale((float) mScale, (float) mScale);
    356             mInverse.scale((float) mScale, (float) mScale);
    357             mTransform.translate((float) -mBounds.x, (float) -mBounds.y);
    358             mInverse.translate((float) -mBounds.x, (float) -mBounds.y);
    359             if (size.x / mBounds.width < size.y / mBounds.height) {
    360                 mTransform.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
    361                 mInverse.translate(0, (float) (size.y / mScale - mBounds.height) / 2);
    362             } else {
    363                 mTransform.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
    364                 mInverse.translate((float) (size.x / mScale - mBounds.width) / 2, 0);
    365             }
    366             mInverse.invert();
    367         }
    368     }
    369 
    370     @Override
    371     public void viewportChanged() {
    372         Display.getDefault().syncExec(new Runnable() {
    373             @Override
    374             public void run() {
    375                 synchronized (this) {
    376                     mViewport = mModel.getViewport();
    377                     setBounds();
    378                     setTransform();
    379                 }
    380             }
    381         });
    382         doRedraw();
    383     }
    384 
    385     @Override
    386     public void zoomChanged() {
    387         viewportChanged();
    388     }
    389 
    390     @Override
    391     public void selectionChanged() {
    392         synchronized (this) {
    393             mSelectedNode = mModel.getSelection();
    394         }
    395         doRedraw();
    396     }
    397 }
    398