Home | History | Annotate | Download | only in ui
      1 /*
      2  * Copyright (C) 2013 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.ide.eclipse.adt.internal.editors.draw9patch.ui;
     18 
     19 import com.android.ide.eclipse.adt.AdtPlugin;
     20 import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage;
     21 import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Chunk;
     22 import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Tick;
     23 
     24 import org.eclipse.swt.SWT;
     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.MouseMoveListener;
     30 import org.eclipse.swt.events.PaintEvent;
     31 import org.eclipse.swt.events.PaintListener;
     32 import org.eclipse.swt.events.SelectionAdapter;
     33 import org.eclipse.swt.events.SelectionEvent;
     34 import org.eclipse.swt.graphics.Color;
     35 import org.eclipse.swt.graphics.GC;
     36 import org.eclipse.swt.graphics.Image;
     37 import org.eclipse.swt.graphics.ImageData;
     38 import org.eclipse.swt.graphics.Point;
     39 import org.eclipse.swt.graphics.RGB;
     40 import org.eclipse.swt.graphics.Rectangle;
     41 import org.eclipse.swt.widgets.Canvas;
     42 import org.eclipse.swt.widgets.Composite;
     43 import org.eclipse.swt.widgets.ScrollBar;
     44 
     45 import java.util.ArrayList;
     46 import java.util.List;
     47 import java.util.concurrent.ArrayBlockingQueue;
     48 
     49 /**
     50  * View and edit Draw 9-patch image.
     51  */
     52 public class ImageViewer extends Canvas implements PaintListener, KeyListener, MouseListener,
     53         MouseMoveListener {
     54     private static final boolean DEBUG = false;
     55 
     56     public static final String HELP_MESSAGE_KEY_TIPS = "Press Shift to erase pixels."
     57             + " Press Control to draw layout bounds.";
     58 
     59     public static final String HELP_MESSAGE_KEY_TIPS2 = "Release Shift to draw pixels.";
     60 
     61     private static final Color BLACK_COLOR = AdtPlugin.getDisplay().getSystemColor(SWT.COLOR_BLACK);
     62     private static final Color RED_COLOR = AdtPlugin.getDisplay().getSystemColor(SWT.COLOR_RED);
     63 
     64     private static final Color BACK_COLOR
     65             = new Color(AdtPlugin.getDisplay(), new RGB(0x00, 0xFF, 0x00));
     66     private static final Color LOCK_COLOR
     67             = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0x00, 0x00));
     68     private static final Color PATCH_COLOR
     69             = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0xFF, 0x00));
     70     private static final Color PATCH_ONEWAY_COLOR
     71             = new Color(AdtPlugin.getDisplay(), new RGB(0x00, 0x00, 0xFF));
     72     private static final Color CORRUPTED_COLOR
     73             = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0x00, 0x00));
     74 
     75     private static final int NONE_ALPHA = 0xFF;
     76     private static final int LOCK_ALPHA = 50;
     77     private static final int PATCH_ALPHA = 50;
     78     private static final int GUIDE_ALPHA = 60;
     79 
     80     private static final int MODE_NONE = 0x00;
     81     private static final int MODE_BLACK_TICK = 0x01;
     82     private static final int MODE_RED_TICK = 0x02;
     83     private static final int MODE_ERASE = 0xFF;
     84 
     85     private int mDrawMode = MODE_NONE;
     86 
     87     private static final int MARGIN = 5;
     88     private static final String CHECKER_PNG_PATH = "/icons/checker.png";
     89 
     90     private Image mBackgroundLayer = null;
     91 
     92     private NinePatchedImage mNinePatchedImage = null;
     93 
     94     private Chunk[][] mChunks = null;
     95     private Chunk[][] mBadChunks = null;
     96 
     97     private boolean mIsLockShown = true;
     98     private boolean mIsPatchesShown = false;
     99     private boolean mIsBadPatchesShown = false;
    100 
    101     private ScrollBar mHorizontalBar;
    102     private ScrollBar mVerticalBar;
    103 
    104     private int mZoom = 500;
    105 
    106     private int mHorizontalScroll = 0;
    107     private int mVerticalScroll = 0;
    108 
    109     private final Rectangle mPadding = new Rectangle(0, 0, 0, 0);
    110 
    111     // one pixel size that considered zoom
    112     private int mZoomedPixelSize = 1;
    113 
    114     private Image mBufferImage = null;
    115 
    116     private boolean isCtrlPressed = false;
    117     private boolean isShiftPressed = false;
    118 
    119     private final List<UpdateListener> mUpdateListenerList
    120             = new ArrayList<UpdateListener>();
    121 
    122     private final Point mCursorPoint = new Point(0, 0);
    123 
    124     private static final int DEFAULT_UPDATE_QUEUE_SIZE = 10;
    125 
    126     private final ArrayBlockingQueue<NinePatchedImage> mUpdateQueue
    127             = new ArrayBlockingQueue<NinePatchedImage>(DEFAULT_UPDATE_QUEUE_SIZE);
    128 
    129     private final Runnable mUpdateRunnable = new Runnable() {
    130         @Override
    131         public void run() {
    132             if (isDisposed()) {
    133                 return;
    134             }
    135 
    136             redraw();
    137 
    138             fireUpdateEvent();
    139         }
    140     };
    141 
    142     private final Thread mUpdateThread = new Thread() {
    143         @Override
    144         public void run() {
    145             while (!isDisposed()) {
    146                 try {
    147                     mUpdateQueue.take();
    148                     mNinePatchedImage.findPatches();
    149                     mNinePatchedImage.findContentsArea();
    150 
    151                     mChunks = mNinePatchedImage.getChunks(mChunks);
    152                     mBadChunks = mNinePatchedImage.getCorruptedChunks(mBadChunks);
    153 
    154                     AdtPlugin.getDisplay().asyncExec(mUpdateRunnable);
    155 
    156                 } catch (InterruptedException e) {
    157                 }
    158             }
    159         }
    160     };
    161 
    162     private StatusChangedListener mStatusChangedListener = null;
    163 
    164     public void addUpdateListener(UpdateListener l) {
    165         mUpdateListenerList.add(l);
    166     }
    167 
    168     public void removeUpdateListener(UpdateListener l) {
    169         mUpdateListenerList.remove(l);
    170     }
    171 
    172     private void fireUpdateEvent() {
    173         int len = mUpdateListenerList.size();
    174         for(int i=0; i < len; i++) {
    175             mUpdateListenerList.get(i).update(mNinePatchedImage);
    176         }
    177     }
    178 
    179     public void setStatusChangedListener(StatusChangedListener l) {
    180         mStatusChangedListener = l;
    181         if (mStatusChangedListener != null) {
    182             mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS);
    183         }
    184     }
    185 
    186     void setShowLock(boolean show) {
    187         mIsLockShown = show;
    188         redraw();
    189     }
    190 
    191     void setShowPatchesArea(boolean show) {
    192         mIsPatchesShown = show;
    193         redraw();
    194     }
    195 
    196     void setShowBadPatchesArea(boolean show) {
    197         mIsBadPatchesShown = show;
    198         redraw();
    199     }
    200 
    201     void setZoom(int zoom) {
    202         mZoom = zoom;
    203         mZoomedPixelSize = getZoomedPixelSize(1);
    204         redraw();
    205     }
    206 
    207     public ImageViewer(Composite parent, int style) {
    208         super(parent, style);
    209 
    210         mUpdateThread.start();
    211 
    212         mBackgroundLayer = AdtPlugin.getImageDescriptor(CHECKER_PNG_PATH).createImage();
    213 
    214         addMouseListener(this);
    215         addMouseMoveListener(this);
    216         addPaintListener(this);
    217 
    218         mHorizontalBar = getHorizontalBar();
    219         mHorizontalBar.setThumb(1);
    220         mHorizontalBar.addSelectionListener(new SelectionAdapter() {
    221             @Override
    222             public void widgetSelected(SelectionEvent event) {
    223                 super.widgetSelected(event);
    224                 ScrollBar bar = (ScrollBar) event.widget;
    225                 if (mHorizontalBar.isVisible()
    226                         && mHorizontalScroll != bar.getSelection()) {
    227                     mHorizontalScroll = bar.getSelection();
    228                     redraw();
    229                 }
    230             }
    231         });
    232 
    233         mVerticalBar = getVerticalBar();
    234         mVerticalBar.setThumb(1);
    235         mVerticalBar.addSelectionListener(new SelectionAdapter() {
    236             @Override
    237             public void widgetSelected(SelectionEvent event) {
    238                 super.widgetSelected(event);
    239                 ScrollBar bar = (ScrollBar) event.widget;
    240                 if (mVerticalBar.isVisible()
    241                         && mVerticalScroll != bar.getSelection()) {
    242                     mVerticalScroll = bar.getSelection();
    243                     redraw();
    244                 }
    245             }
    246         });
    247     }
    248 
    249     /**
    250      * Load the image file.
    251      *
    252      * @param fileName must be absolute path
    253      */
    254     public NinePatchedImage loadFile(String fileName) {
    255         mNinePatchedImage = new NinePatchedImage(fileName);
    256 
    257         return mNinePatchedImage;
    258     }
    259 
    260     /**
    261      * Start displaying the image.
    262      */
    263     public void startDisplay() {
    264         mZoomedPixelSize = getZoomedPixelSize(1);
    265 
    266         scheduleUpdate();
    267     }
    268 
    269     private void draw(int x, int y, int drawMode) {
    270         if (drawMode == MODE_ERASE) {
    271             erase(x, y);
    272         } else {
    273             int color = (drawMode == MODE_RED_TICK) ? NinePatchedImage.RED_TICK
    274                     : NinePatchedImage.BLACK_TICK;
    275             mNinePatchedImage.setPatch(x, y, color);
    276             redraw();
    277 
    278             scheduleUpdate();
    279         }
    280     }
    281 
    282     private void erase(int x, int y) {
    283         mNinePatchedImage.erase(x, y);
    284         redraw();
    285 
    286         scheduleUpdate();
    287     }
    288 
    289     private void scheduleUpdate() {
    290         try {
    291             mUpdateQueue.put(mNinePatchedImage);
    292         } catch (InterruptedException e) {
    293         }
    294     }
    295 
    296     @Override
    297     public void mouseDown(MouseEvent event) {
    298         if (event.button == 1 && !isShiftPressed) {
    299             mDrawMode = !isCtrlPressed ? MODE_BLACK_TICK : MODE_RED_TICK;
    300             draw(mCursorPoint.x, mCursorPoint.y, mDrawMode);
    301         } else if (event.button == 3 || isShiftPressed) {
    302             mDrawMode = MODE_ERASE;
    303             erase(mCursorPoint.x, mCursorPoint.y);
    304         }
    305     }
    306 
    307     @Override
    308     public void mouseUp(MouseEvent event) {
    309         mDrawMode = MODE_NONE;
    310     }
    311 
    312     @Override
    313     public void mouseDoubleClick(MouseEvent event) {
    314     }
    315 
    316     private int getLogicalPositionX(int x) {
    317         return Math.round((x - mPadding.x + mHorizontalScroll) / ((float) mZoom / 100));
    318     }
    319 
    320     private int getLogicalPositionY(int y) {
    321         return Math.round((y - mPadding.y + mVerticalScroll) / ((float) mZoom / 100));
    322     }
    323 
    324     @Override
    325     public void mouseMove(MouseEvent event) {
    326         int posX = getLogicalPositionX(event.x);
    327         int posY = getLogicalPositionY(event.y);
    328 
    329         int width = mNinePatchedImage.getWidth();
    330         int height = mNinePatchedImage.getHeight();
    331 
    332         if (posX < 0) {
    333             posX = 0;
    334         }
    335         if (posX >= width) {
    336             posX = width - 1;
    337         }
    338         if (posY < 0) {
    339             posY = 0;
    340         }
    341         if (posY >= height) {
    342             posY = height - 1;
    343         }
    344 
    345         if (mDrawMode != MODE_NONE) {
    346             int drawMode = mDrawMode;
    347             if (isShiftPressed) {
    348                 drawMode = MODE_ERASE;
    349             } else if (mDrawMode != MODE_ERASE) {
    350                 drawMode = !isCtrlPressed ? MODE_BLACK_TICK : MODE_RED_TICK;
    351             }
    352 
    353             /*
    354              * Consider the previous cursor position because mouseMove events are
    355              * scatter.
    356              */
    357             int x = mCursorPoint.x;
    358             int y = mCursorPoint.y;
    359             for (; y != posY; y += (y > posY) ? -1 : 1) {
    360                 draw(x, y, drawMode);
    361             }
    362 
    363             x = mCursorPoint.x;
    364             y = mCursorPoint.y;
    365             for (; x != posX; x += (x > posX) ? -1 : 1) {
    366                 draw(x, y, drawMode);
    367             }
    368         }
    369         mCursorPoint.x = posX;
    370         mCursorPoint.y = posY;
    371 
    372         redraw();
    373 
    374         if (mStatusChangedListener != null) {
    375             // Update position on status panel if position is in logical size.
    376             if (posX >= 0 && posY >= 0
    377                     && posX <= mNinePatchedImage.getWidth()
    378                     && posY <= mNinePatchedImage.getHeight()) {
    379                 mStatusChangedListener.cursorPositionChanged(posX + 1, posY + 1);
    380             }
    381         }
    382     }
    383 
    384     private synchronized void calcPaddings(int width, int height) {
    385         Point canvasSize = getSize();
    386 
    387         mPadding.width = getZoomedPixelSize(width);
    388         mPadding.height = getZoomedPixelSize(height);
    389 
    390         int margin = getZoomedPixelSize(MARGIN);
    391 
    392         if (mPadding.width < canvasSize.x) {
    393             mPadding.x = (canvasSize.x - mPadding.width) / 2;
    394         } else {
    395             mPadding.x = margin;
    396         }
    397 
    398         if (mPadding.height < canvasSize.y) {
    399             mPadding.y = (canvasSize.y - mPadding.height) / 2;
    400         } else {
    401             mPadding.y = margin;
    402         }
    403     }
    404 
    405     private void calcScrollBarSettings() {
    406         Point size = getSize();
    407         int screenWidth = size.x;
    408         int screenHeight = size.y;
    409 
    410         int imageWidth = getZoomedPixelSize(mNinePatchedImage.getWidth() + 1);
    411         int imageHeight = getZoomedPixelSize(mNinePatchedImage.getHeight() + 1);
    412 
    413         // consider the scroll bar sizes
    414         int verticalBarSize = mVerticalBar.getSize().x;
    415         int horizontalBarSize = mHorizontalBar.getSize().y;
    416 
    417         int horizontalScroll = imageWidth - (screenWidth - verticalBarSize);
    418         int verticalScroll = imageHeight - (screenHeight - horizontalBarSize);
    419 
    420         int margin = getZoomedPixelSize(MARGIN) * 2;
    421 
    422         if (horizontalScroll > 0) {
    423             mHorizontalBar.setVisible(true);
    424 
    425             // horizontal maximum
    426             int max = horizontalScroll + verticalBarSize + margin;
    427             mHorizontalBar.setMaximum(max);
    428 
    429             // set corrected scroll size
    430             int value = mHorizontalBar.getSelection();
    431             value = max < value ? max : value;
    432 
    433             mHorizontalBar.setSelection(value);
    434             mHorizontalScroll = value;
    435 
    436         } else {
    437             mHorizontalBar.setSelection(0);
    438             mHorizontalBar.setMaximum(0);
    439             mHorizontalBar.setVisible(false);
    440         }
    441 
    442         if (verticalScroll > 0) {
    443             mVerticalBar.setVisible(true);
    444 
    445             // vertical maximum
    446             int max = verticalScroll + horizontalBarSize + margin;
    447             mVerticalBar.setMaximum(max);
    448 
    449             // set corrected scroll size
    450             int value = mVerticalBar.getSelection();
    451             value = max < value ? max : value;
    452 
    453             mVerticalBar.setSelection(value);
    454             mVerticalScroll = value;
    455 
    456         } else {
    457             mVerticalBar.setSelection(0);
    458             mVerticalBar.setMaximum(0);
    459             mVerticalBar.setVisible(false);
    460         }
    461     }
    462 
    463     private int getZoomedPixelSize(int val) {
    464         return Math.round(val * (float) mZoom / 100);
    465     }
    466 
    467     @Override
    468     public void paintControl(PaintEvent pe) {
    469         if (mNinePatchedImage == null) {
    470             return;
    471         }
    472 
    473         // Use buffer
    474         GC bufferGc = null;
    475         if (mBufferImage == null) {
    476             mBufferImage = new Image(AdtPlugin.getDisplay(), pe.width, pe.height);
    477         } else {
    478             int width = mBufferImage.getBounds().width;
    479             int height = mBufferImage.getBounds().height;
    480             if (width != pe.width || height != pe.height) {
    481                 mBufferImage = new Image(AdtPlugin.getDisplay(), pe.width, pe.height);
    482             }
    483         }
    484 
    485         // Draw previous image once for prevent flicking
    486         pe.gc.drawImage(mBufferImage, 0, 0);
    487 
    488         bufferGc = new GC(mBufferImage);
    489         bufferGc.setAdvanced(true);
    490 
    491         // Make interpolation disable
    492         bufferGc.setInterpolation(SWT.NONE);
    493 
    494         // clear buffer
    495         bufferGc.fillRectangle(0, 0, pe.width, pe.height);
    496 
    497         calcScrollBarSettings();
    498 
    499         // padding from current zoom
    500         int width = mNinePatchedImage.getWidth();
    501         int height = mNinePatchedImage.getHeight();
    502         calcPaddings(width, height);
    503 
    504         int baseX = mPadding.x - mHorizontalScroll;
    505         int baseY = mPadding.y - mVerticalScroll;
    506 
    507         // draw checker image
    508         bufferGc.drawImage(mBackgroundLayer,
    509                 0, 0, mBackgroundLayer.getImageData().width,
    510                 mBackgroundLayer.getImageData().height,
    511                 baseX, baseY, mPadding.width, mPadding.height);
    512 
    513         if (DEBUG) {
    514             System.out.println(String.format("%d,%d %d,%d %d,%d",
    515                     width, height, baseX, baseY, mPadding.width, mPadding.height));
    516         }
    517 
    518         // draw image
    519         /* TODO: Do not draw invisible area, for better performance. */
    520         bufferGc.drawImage(mNinePatchedImage.getImage(), 0, 0, width, height, baseX, baseY,
    521                 mPadding.width, mPadding.height);
    522 
    523         bufferGc.setBackground(BLACK_COLOR);
    524 
    525         // draw patch ticks
    526         drawHorizontalPatches(bufferGc, baseX, baseY);
    527         drawVerticalPatches(bufferGc, baseX, baseY);
    528 
    529         // draw content ticks
    530         drawHorizontalContentArea(bufferGc, baseX, baseY);
    531         drawVerticalContentArea(bufferGc, baseX, baseY);
    532 
    533         if (mNinePatchedImage.isValid(mCursorPoint.x, mCursorPoint.y)) {
    534             bufferGc.setForeground(BLACK_COLOR);
    535         } else if (mIsLockShown) {
    536             drawLockArea(bufferGc, baseX, baseY);
    537         }
    538 
    539         // Patches
    540         if (mIsPatchesShown) {
    541             drawPatchAreas(bufferGc, baseX, baseY);
    542         }
    543 
    544         // Bad patches
    545         if (mIsBadPatchesShown) {
    546             drawBadPatchAreas(bufferGc, baseX, baseY);
    547         }
    548 
    549         if (mNinePatchedImage.isValid(mCursorPoint.x, mCursorPoint.y)) {
    550             bufferGc.setForeground(BLACK_COLOR);
    551         } else {
    552             bufferGc.setForeground(RED_COLOR);
    553         }
    554 
    555         drawGuideLine(bufferGc, baseX, baseY);
    556 
    557         bufferGc.dispose();
    558 
    559         pe.gc.drawImage(mBufferImage, 0, 0);
    560     }
    561 
    562     private static final Color getColor(int color) {
    563         switch (color) {
    564             case NinePatchedImage.RED_TICK:
    565                 return RED_COLOR;
    566             default:
    567                 return BLACK_COLOR;
    568         }
    569     }
    570 
    571     private void drawVerticalPatches(GC gc, int baseX, int baseY) {
    572         List<Tick> verticalPatches = mNinePatchedImage.getVerticalPatches();
    573         for (Tick t : verticalPatches) {
    574             if (t.color != NinePatchedImage.TRANSPARENT_TICK) {
    575                 gc.setBackground(getColor(t.color));
    576                 gc.fillRectangle(
    577                         baseX,
    578                         baseY + getZoomedPixelSize(t.start),
    579                         mZoomedPixelSize,
    580                         getZoomedPixelSize(t.getLength()));
    581             }
    582         }
    583     }
    584 
    585     private void drawHorizontalPatches(GC gc, int baseX, int baseY) {
    586         List<Tick> horizontalPatches = mNinePatchedImage.getHorizontalPatches();
    587         for (Tick t : horizontalPatches) {
    588             if (t.color != NinePatchedImage.TRANSPARENT_TICK) {
    589                 gc.setBackground(getColor(t.color));
    590                 gc.fillRectangle(
    591                         baseX + getZoomedPixelSize(t.start),
    592                         baseY,
    593                         getZoomedPixelSize(t.getLength()),
    594                         mZoomedPixelSize);
    595             }
    596         }
    597     }
    598 
    599     private void drawHorizontalContentArea(GC gc, int baseX, int baseY) {
    600         List<Tick> horizontalContentArea = mNinePatchedImage.getHorizontalContents();
    601         for (Tick t : horizontalContentArea) {
    602             if (t.color != NinePatchedImage.TRANSPARENT_TICK) {
    603                 gc.setBackground(getColor(t.color));
    604                 gc.fillRectangle(
    605                         baseX + getZoomedPixelSize(t.start),
    606                         baseY + getZoomedPixelSize(mNinePatchedImage.getHeight() - 1),
    607                         getZoomedPixelSize(t.getLength()),
    608                         mZoomedPixelSize);
    609             }
    610         }
    611 
    612     }
    613 
    614     private void drawVerticalContentArea(GC gc, int baseX, int baseY) {
    615         List<Tick> verticalContentArea = mNinePatchedImage.getVerticalContents();
    616         for (Tick t : verticalContentArea) {
    617             if (t.color != NinePatchedImage.TRANSPARENT_TICK) {
    618                 gc.setBackground(getColor(t.color));
    619                 gc.fillRectangle(
    620                         baseX + getZoomedPixelSize(mNinePatchedImage.getWidth() - 1),
    621                         baseY + getZoomedPixelSize(t.start),
    622                         mZoomedPixelSize,
    623                         getZoomedPixelSize(t.getLength()));
    624             }
    625         }
    626     }
    627 
    628     private void drawLockArea(GC gc, int baseX, int baseY) {
    629         gc.setAlpha(LOCK_ALPHA);
    630         gc.setForeground(LOCK_COLOR);
    631         gc.setBackground(LOCK_COLOR);
    632 
    633         gc.fillRectangle(
    634                 baseX + mZoomedPixelSize,
    635                 baseY + mZoomedPixelSize,
    636                 getZoomedPixelSize(mNinePatchedImage.getWidth() - 2),
    637                 getZoomedPixelSize(mNinePatchedImage.getHeight() - 2));
    638         gc.setAlpha(NONE_ALPHA);
    639 
    640     }
    641 
    642     private void drawPatchAreas(GC gc, int baseX, int baseY) {
    643         if (mChunks != null) {
    644             int yLen = mChunks.length;
    645             int xLen = mChunks[0].length;
    646 
    647             gc.setAlpha(PATCH_ALPHA);
    648 
    649             for (int yPos = 0; yPos < yLen; yPos++) {
    650                 for (int xPos = 0; xPos < xLen; xPos++) {
    651                     Chunk c = mChunks[yPos][xPos];
    652 
    653                     if (c.type == Chunk.TYPE_FIXED) {
    654                         gc.setBackground(BACK_COLOR);
    655                     } else if (c.type == Chunk.TYPE_HORIZONTAL) {
    656                         gc.setBackground(PATCH_ONEWAY_COLOR);
    657                     } else if (c.type == Chunk.TYPE_VERTICAL) {
    658                         gc.setBackground(PATCH_ONEWAY_COLOR);
    659                     } else if (c.type == Chunk.TYPE_HORIZONTAL + Chunk.TYPE_VERTICAL) {
    660                         gc.setBackground(PATCH_COLOR);
    661                     }
    662                     Rectangle r = c.rect;
    663                     gc.fillRectangle(
    664                             baseX + getZoomedPixelSize(r.x),
    665                             baseY + getZoomedPixelSize(r.y),
    666                             getZoomedPixelSize(r.width),
    667                             getZoomedPixelSize(r.height));
    668                 }
    669             }
    670         }
    671         gc.setAlpha(NONE_ALPHA);
    672     }
    673 
    674     private void drawBadPatchAreas(GC gc, int baseX, int baseY) {
    675         if (mBadChunks != null) {
    676             int yLen = mBadChunks.length;
    677             int xLen = mBadChunks[0].length;
    678 
    679             gc.setAlpha(NONE_ALPHA);
    680             gc.setForeground(CORRUPTED_COLOR);
    681             gc.setBackground(CORRUPTED_COLOR);
    682 
    683             for (int yPos = 0; yPos < yLen; yPos++) {
    684                 for (int xPos = 0; xPos < xLen; xPos++) {
    685                     Chunk c = mBadChunks[yPos][xPos];
    686                     if ((c.type & Chunk.TYPE_CORRUPT) != 0) {
    687                         Rectangle r = c.rect;
    688                         gc.drawRectangle(
    689                                 baseX + getZoomedPixelSize(r.x),
    690                                 baseY + getZoomedPixelSize(r.y),
    691                                 getZoomedPixelSize(r.width),
    692                                 getZoomedPixelSize(r.height));
    693                     }
    694                 }
    695             }
    696         }
    697     }
    698 
    699     private void drawGuideLine(GC gc, int baseX, int baseY) {
    700         gc.setAntialias(SWT.ON);
    701         gc.setInterpolation(SWT.HIGH);
    702 
    703         int x = Math.round((mCursorPoint.x * ((float) mZoom / 100) + baseX)
    704                 + ((float) mZoom / 100 / 2));
    705         int y = Math.round((mCursorPoint.y * ((float) mZoom / 100) + baseY)
    706                 + ((float) mZoom / 100 / 2));
    707         gc.setAlpha(GUIDE_ALPHA);
    708 
    709         Point size = getSize();
    710         gc.drawLine(x, 0, x, size.y);
    711         gc.drawLine(0, y, size.x, y);
    712 
    713         gc.setAlpha(NONE_ALPHA);
    714     }
    715 
    716     @Override
    717     public void keyPressed(KeyEvent event) {
    718         int keycode = event.keyCode;
    719         if (keycode == SWT.CTRL) {
    720             isCtrlPressed = true;
    721         }
    722         if (keycode == SWT.SHIFT) {
    723             isShiftPressed = true;
    724             if (mStatusChangedListener != null) {
    725                 mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS2);
    726             }
    727         }
    728     }
    729 
    730     @Override
    731     public void keyReleased(KeyEvent event) {
    732         int keycode = event.keyCode;
    733         if (keycode == SWT.CTRL) {
    734             isCtrlPressed = false;
    735         }
    736         if (keycode == SWT.SHIFT) {
    737             isShiftPressed = false;
    738             if (mStatusChangedListener != null) {
    739                 mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS);
    740             }
    741         }
    742     }
    743 
    744     @Override
    745     public void dispose() {
    746         mBackgroundLayer.dispose();
    747         super.dispose();
    748     }
    749 
    750     /**
    751      * Listen image updated event.
    752      */
    753     public interface UpdateListener {
    754         /**
    755          * 9-patched image has been updated.
    756          */
    757         public void update(NinePatchedImage image);
    758     }
    759 
    760     /**
    761      * Listen status changed event.
    762      */
    763     public interface StatusChangedListener {
    764         /**
    765          * Mouse cursor position has been changed.
    766          */
    767         public void cursorPositionChanged(int x, int y);
    768 
    769         /**
    770          * Help text has been changed.
    771          */
    772         public void helpTextChanged(String text);
    773     }
    774 }
    775