diff options
Diffstat (limited to 'eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java')
-rw-r--r-- | eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java | 774 |
1 files changed, 774 insertions, 0 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java new file mode 100644 index 000000000..2414a39d7 --- /dev/null +++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/draw9patch/ui/ImageViewer.java @@ -0,0 +1,774 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.ide.eclipse.adt.internal.editors.draw9patch.ui; + +import com.android.ide.eclipse.adt.AdtPlugin; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Chunk; +import com.android.ide.eclipse.adt.internal.editors.draw9patch.graphics.NinePatchedImage.Tick; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.MouseMoveListener; +import org.eclipse.swt.events.PaintEvent; +import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.RGB; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Canvas; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.ScrollBar; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +/** + * View and edit Draw 9-patch image. + */ +public class ImageViewer extends Canvas implements PaintListener, KeyListener, MouseListener, + MouseMoveListener { + private static final boolean DEBUG = false; + + public static final String HELP_MESSAGE_KEY_TIPS = "Press Shift to erase pixels." + + " Press Control to draw layout bounds."; + + public static final String HELP_MESSAGE_KEY_TIPS2 = "Release Shift to draw pixels."; + + private static final Color BLACK_COLOR = AdtPlugin.getDisplay().getSystemColor(SWT.COLOR_BLACK); + private static final Color RED_COLOR = AdtPlugin.getDisplay().getSystemColor(SWT.COLOR_RED); + + private static final Color BACK_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0x00, 0xFF, 0x00)); + private static final Color LOCK_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0x00, 0x00)); + private static final Color PATCH_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0xFF, 0x00)); + private static final Color PATCH_ONEWAY_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0x00, 0x00, 0xFF)); + private static final Color CORRUPTED_COLOR + = new Color(AdtPlugin.getDisplay(), new RGB(0xFF, 0x00, 0x00)); + + private static final int NONE_ALPHA = 0xFF; + private static final int LOCK_ALPHA = 50; + private static final int PATCH_ALPHA = 50; + private static final int GUIDE_ALPHA = 60; + + private static final int MODE_NONE = 0x00; + private static final int MODE_BLACK_TICK = 0x01; + private static final int MODE_RED_TICK = 0x02; + private static final int MODE_ERASE = 0xFF; + + private int mDrawMode = MODE_NONE; + + private static final int MARGIN = 5; + private static final String CHECKER_PNG_PATH = "/icons/checker.png"; + + private Image mBackgroundLayer = null; + + private NinePatchedImage mNinePatchedImage = null; + + private Chunk[][] mChunks = null; + private Chunk[][] mBadChunks = null; + + private boolean mIsLockShown = true; + private boolean mIsPatchesShown = false; + private boolean mIsBadPatchesShown = false; + + private ScrollBar mHorizontalBar; + private ScrollBar mVerticalBar; + + private int mZoom = 500; + + private int mHorizontalScroll = 0; + private int mVerticalScroll = 0; + + private final Rectangle mPadding = new Rectangle(0, 0, 0, 0); + + // one pixel size that considered zoom + private int mZoomedPixelSize = 1; + + private Image mBufferImage = null; + + private boolean isCtrlPressed = false; + private boolean isShiftPressed = false; + + private final List<UpdateListener> mUpdateListenerList + = new ArrayList<UpdateListener>(); + + private final Point mCursorPoint = new Point(0, 0); + + private static final int DEFAULT_UPDATE_QUEUE_SIZE = 10; + + private final ArrayBlockingQueue<NinePatchedImage> mUpdateQueue + = new ArrayBlockingQueue<NinePatchedImage>(DEFAULT_UPDATE_QUEUE_SIZE); + + private final Runnable mUpdateRunnable = new Runnable() { + @Override + public void run() { + if (isDisposed()) { + return; + } + + redraw(); + + fireUpdateEvent(); + } + }; + + private final Thread mUpdateThread = new Thread() { + @Override + public void run() { + while (!isDisposed()) { + try { + mUpdateQueue.take(); + mNinePatchedImage.findPatches(); + mNinePatchedImage.findContentsArea(); + + mChunks = mNinePatchedImage.getChunks(mChunks); + mBadChunks = mNinePatchedImage.getCorruptedChunks(mBadChunks); + + AdtPlugin.getDisplay().asyncExec(mUpdateRunnable); + + } catch (InterruptedException e) { + } + } + } + }; + + private StatusChangedListener mStatusChangedListener = null; + + public void addUpdateListener(UpdateListener l) { + mUpdateListenerList.add(l); + } + + public void removeUpdateListener(UpdateListener l) { + mUpdateListenerList.remove(l); + } + + private void fireUpdateEvent() { + int len = mUpdateListenerList.size(); + for(int i=0; i < len; i++) { + mUpdateListenerList.get(i).update(mNinePatchedImage); + } + } + + public void setStatusChangedListener(StatusChangedListener l) { + mStatusChangedListener = l; + if (mStatusChangedListener != null) { + mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS); + } + } + + void setShowLock(boolean show) { + mIsLockShown = show; + redraw(); + } + + void setShowPatchesArea(boolean show) { + mIsPatchesShown = show; + redraw(); + } + + void setShowBadPatchesArea(boolean show) { + mIsBadPatchesShown = show; + redraw(); + } + + void setZoom(int zoom) { + mZoom = zoom; + mZoomedPixelSize = getZoomedPixelSize(1); + redraw(); + } + + public ImageViewer(Composite parent, int style) { + super(parent, style); + + mUpdateThread.start(); + + mBackgroundLayer = AdtPlugin.getImageDescriptor(CHECKER_PNG_PATH).createImage(); + + addMouseListener(this); + addMouseMoveListener(this); + addPaintListener(this); + + mHorizontalBar = getHorizontalBar(); + mHorizontalBar.setThumb(1); + mHorizontalBar.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + ScrollBar bar = (ScrollBar) event.widget; + if (mHorizontalBar.isVisible() + && mHorizontalScroll != bar.getSelection()) { + mHorizontalScroll = bar.getSelection(); + redraw(); + } + } + }); + + mVerticalBar = getVerticalBar(); + mVerticalBar.setThumb(1); + mVerticalBar.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + super.widgetSelected(event); + ScrollBar bar = (ScrollBar) event.widget; + if (mVerticalBar.isVisible() + && mVerticalScroll != bar.getSelection()) { + mVerticalScroll = bar.getSelection(); + redraw(); + } + } + }); + } + + /** + * Load the image file. + * + * @param fileName must be absolute path + */ + public NinePatchedImage loadFile(String fileName) { + mNinePatchedImage = new NinePatchedImage(fileName); + + return mNinePatchedImage; + } + + /** + * Start displaying the image. + */ + public void startDisplay() { + mZoomedPixelSize = getZoomedPixelSize(1); + + scheduleUpdate(); + } + + private void draw(int x, int y, int drawMode) { + if (drawMode == MODE_ERASE) { + erase(x, y); + } else { + int color = (drawMode == MODE_RED_TICK) ? NinePatchedImage.RED_TICK + : NinePatchedImage.BLACK_TICK; + mNinePatchedImage.setPatch(x, y, color); + redraw(); + + scheduleUpdate(); + } + } + + private void erase(int x, int y) { + mNinePatchedImage.erase(x, y); + redraw(); + + scheduleUpdate(); + } + + private void scheduleUpdate() { + try { + mUpdateQueue.put(mNinePatchedImage); + } catch (InterruptedException e) { + } + } + + @Override + public void mouseDown(MouseEvent event) { + if (event.button == 1 && !isShiftPressed) { + mDrawMode = !isCtrlPressed ? MODE_BLACK_TICK : MODE_RED_TICK; + draw(mCursorPoint.x, mCursorPoint.y, mDrawMode); + } else if (event.button == 3 || isShiftPressed) { + mDrawMode = MODE_ERASE; + erase(mCursorPoint.x, mCursorPoint.y); + } + } + + @Override + public void mouseUp(MouseEvent event) { + mDrawMode = MODE_NONE; + } + + @Override + public void mouseDoubleClick(MouseEvent event) { + } + + private int getLogicalPositionX(int x) { + return Math.round((x - mPadding.x + mHorizontalScroll) / ((float) mZoom / 100)); + } + + private int getLogicalPositionY(int y) { + return Math.round((y - mPadding.y + mVerticalScroll) / ((float) mZoom / 100)); + } + + @Override + public void mouseMove(MouseEvent event) { + int posX = getLogicalPositionX(event.x); + int posY = getLogicalPositionY(event.y); + + int width = mNinePatchedImage.getWidth(); + int height = mNinePatchedImage.getHeight(); + + if (posX < 0) { + posX = 0; + } + if (posX >= width) { + posX = width - 1; + } + if (posY < 0) { + posY = 0; + } + if (posY >= height) { + posY = height - 1; + } + + if (mDrawMode != MODE_NONE) { + int drawMode = mDrawMode; + if (isShiftPressed) { + drawMode = MODE_ERASE; + } else if (mDrawMode != MODE_ERASE) { + drawMode = !isCtrlPressed ? MODE_BLACK_TICK : MODE_RED_TICK; + } + + /* + * Consider the previous cursor position because mouseMove events are + * scatter. + */ + int x = mCursorPoint.x; + int y = mCursorPoint.y; + for (; y != posY; y += (y > posY) ? -1 : 1) { + draw(x, y, drawMode); + } + + x = mCursorPoint.x; + y = mCursorPoint.y; + for (; x != posX; x += (x > posX) ? -1 : 1) { + draw(x, y, drawMode); + } + } + mCursorPoint.x = posX; + mCursorPoint.y = posY; + + redraw(); + + if (mStatusChangedListener != null) { + // Update position on status panel if position is in logical size. + if (posX >= 0 && posY >= 0 + && posX <= mNinePatchedImage.getWidth() + && posY <= mNinePatchedImage.getHeight()) { + mStatusChangedListener.cursorPositionChanged(posX + 1, posY + 1); + } + } + } + + private synchronized void calcPaddings(int width, int height) { + Point canvasSize = getSize(); + + mPadding.width = getZoomedPixelSize(width); + mPadding.height = getZoomedPixelSize(height); + + int margin = getZoomedPixelSize(MARGIN); + + if (mPadding.width < canvasSize.x) { + mPadding.x = (canvasSize.x - mPadding.width) / 2; + } else { + mPadding.x = margin; + } + + if (mPadding.height < canvasSize.y) { + mPadding.y = (canvasSize.y - mPadding.height) / 2; + } else { + mPadding.y = margin; + } + } + + private void calcScrollBarSettings() { + Point size = getSize(); + int screenWidth = size.x; + int screenHeight = size.y; + + int imageWidth = getZoomedPixelSize(mNinePatchedImage.getWidth() + 1); + int imageHeight = getZoomedPixelSize(mNinePatchedImage.getHeight() + 1); + + // consider the scroll bar sizes + int verticalBarSize = mVerticalBar.getSize().x; + int horizontalBarSize = mHorizontalBar.getSize().y; + + int horizontalScroll = imageWidth - (screenWidth - verticalBarSize); + int verticalScroll = imageHeight - (screenHeight - horizontalBarSize); + + int margin = getZoomedPixelSize(MARGIN) * 2; + + if (horizontalScroll > 0) { + mHorizontalBar.setVisible(true); + + // horizontal maximum + int max = horizontalScroll + verticalBarSize + margin; + mHorizontalBar.setMaximum(max); + + // set corrected scroll size + int value = mHorizontalBar.getSelection(); + value = max < value ? max : value; + + mHorizontalBar.setSelection(value); + mHorizontalScroll = value; + + } else { + mHorizontalBar.setSelection(0); + mHorizontalBar.setMaximum(0); + mHorizontalBar.setVisible(false); + } + + if (verticalScroll > 0) { + mVerticalBar.setVisible(true); + + // vertical maximum + int max = verticalScroll + horizontalBarSize + margin; + mVerticalBar.setMaximum(max); + + // set corrected scroll size + int value = mVerticalBar.getSelection(); + value = max < value ? max : value; + + mVerticalBar.setSelection(value); + mVerticalScroll = value; + + } else { + mVerticalBar.setSelection(0); + mVerticalBar.setMaximum(0); + mVerticalBar.setVisible(false); + } + } + + private int getZoomedPixelSize(int val) { + return Math.round(val * (float) mZoom / 100); + } + + @Override + public void paintControl(PaintEvent pe) { + if (mNinePatchedImage == null) { + return; + } + + // Use buffer + GC bufferGc = null; + if (mBufferImage == null) { + mBufferImage = new Image(AdtPlugin.getDisplay(), pe.width, pe.height); + } else { + int width = mBufferImage.getBounds().width; + int height = mBufferImage.getBounds().height; + if (width != pe.width || height != pe.height) { + mBufferImage = new Image(AdtPlugin.getDisplay(), pe.width, pe.height); + } + } + + // Draw previous image once for prevent flicking + pe.gc.drawImage(mBufferImage, 0, 0); + + bufferGc = new GC(mBufferImage); + bufferGc.setAdvanced(true); + + // Make interpolation disable + bufferGc.setInterpolation(SWT.NONE); + + // clear buffer + bufferGc.fillRectangle(0, 0, pe.width, pe.height); + + calcScrollBarSettings(); + + // padding from current zoom + int width = mNinePatchedImage.getWidth(); + int height = mNinePatchedImage.getHeight(); + calcPaddings(width, height); + + int baseX = mPadding.x - mHorizontalScroll; + int baseY = mPadding.y - mVerticalScroll; + + // draw checker image + bufferGc.drawImage(mBackgroundLayer, + 0, 0, mBackgroundLayer.getImageData().width, + mBackgroundLayer.getImageData().height, + baseX, baseY, mPadding.width, mPadding.height); + + if (DEBUG) { + System.out.println(String.format("%d,%d %d,%d %d,%d", + width, height, baseX, baseY, mPadding.width, mPadding.height)); + } + + // draw image + /* TODO: Do not draw invisible area, for better performance. */ + bufferGc.drawImage(mNinePatchedImage.getImage(), 0, 0, width, height, baseX, baseY, + mPadding.width, mPadding.height); + + bufferGc.setBackground(BLACK_COLOR); + + // draw patch ticks + drawHorizontalPatches(bufferGc, baseX, baseY); + drawVerticalPatches(bufferGc, baseX, baseY); + + // draw content ticks + drawHorizontalContentArea(bufferGc, baseX, baseY); + drawVerticalContentArea(bufferGc, baseX, baseY); + + if (mNinePatchedImage.isValid(mCursorPoint.x, mCursorPoint.y)) { + bufferGc.setForeground(BLACK_COLOR); + } else if (mIsLockShown) { + drawLockArea(bufferGc, baseX, baseY); + } + + // Patches + if (mIsPatchesShown) { + drawPatchAreas(bufferGc, baseX, baseY); + } + + // Bad patches + if (mIsBadPatchesShown) { + drawBadPatchAreas(bufferGc, baseX, baseY); + } + + if (mNinePatchedImage.isValid(mCursorPoint.x, mCursorPoint.y)) { + bufferGc.setForeground(BLACK_COLOR); + } else { + bufferGc.setForeground(RED_COLOR); + } + + drawGuideLine(bufferGc, baseX, baseY); + + bufferGc.dispose(); + + pe.gc.drawImage(mBufferImage, 0, 0); + } + + private static final Color getColor(int color) { + switch (color) { + case NinePatchedImage.RED_TICK: + return RED_COLOR; + default: + return BLACK_COLOR; + } + } + + private void drawVerticalPatches(GC gc, int baseX, int baseY) { + List<Tick> verticalPatches = mNinePatchedImage.getVerticalPatches(); + for (Tick t : verticalPatches) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX, + baseY + getZoomedPixelSize(t.start), + mZoomedPixelSize, + getZoomedPixelSize(t.getLength())); + } + } + } + + private void drawHorizontalPatches(GC gc, int baseX, int baseY) { + List<Tick> horizontalPatches = mNinePatchedImage.getHorizontalPatches(); + for (Tick t : horizontalPatches) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX + getZoomedPixelSize(t.start), + baseY, + getZoomedPixelSize(t.getLength()), + mZoomedPixelSize); + } + } + } + + private void drawHorizontalContentArea(GC gc, int baseX, int baseY) { + List<Tick> horizontalContentArea = mNinePatchedImage.getHorizontalContents(); + for (Tick t : horizontalContentArea) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX + getZoomedPixelSize(t.start), + baseY + getZoomedPixelSize(mNinePatchedImage.getHeight() - 1), + getZoomedPixelSize(t.getLength()), + mZoomedPixelSize); + } + } + + } + + private void drawVerticalContentArea(GC gc, int baseX, int baseY) { + List<Tick> verticalContentArea = mNinePatchedImage.getVerticalContents(); + for (Tick t : verticalContentArea) { + if (t.color != NinePatchedImage.TRANSPARENT_TICK) { + gc.setBackground(getColor(t.color)); + gc.fillRectangle( + baseX + getZoomedPixelSize(mNinePatchedImage.getWidth() - 1), + baseY + getZoomedPixelSize(t.start), + mZoomedPixelSize, + getZoomedPixelSize(t.getLength())); + } + } + } + + private void drawLockArea(GC gc, int baseX, int baseY) { + gc.setAlpha(LOCK_ALPHA); + gc.setForeground(LOCK_COLOR); + gc.setBackground(LOCK_COLOR); + + gc.fillRectangle( + baseX + mZoomedPixelSize, + baseY + mZoomedPixelSize, + getZoomedPixelSize(mNinePatchedImage.getWidth() - 2), + getZoomedPixelSize(mNinePatchedImage.getHeight() - 2)); + gc.setAlpha(NONE_ALPHA); + + } + + private void drawPatchAreas(GC gc, int baseX, int baseY) { + if (mChunks != null) { + int yLen = mChunks.length; + int xLen = mChunks[0].length; + + gc.setAlpha(PATCH_ALPHA); + + for (int yPos = 0; yPos < yLen; yPos++) { + for (int xPos = 0; xPos < xLen; xPos++) { + Chunk c = mChunks[yPos][xPos]; + + if (c.type == Chunk.TYPE_FIXED) { + gc.setBackground(BACK_COLOR); + } else if (c.type == Chunk.TYPE_HORIZONTAL) { + gc.setBackground(PATCH_ONEWAY_COLOR); + } else if (c.type == Chunk.TYPE_VERTICAL) { + gc.setBackground(PATCH_ONEWAY_COLOR); + } else if (c.type == Chunk.TYPE_HORIZONTAL + Chunk.TYPE_VERTICAL) { + gc.setBackground(PATCH_COLOR); + } + Rectangle r = c.rect; + gc.fillRectangle( + baseX + getZoomedPixelSize(r.x), + baseY + getZoomedPixelSize(r.y), + getZoomedPixelSize(r.width), + getZoomedPixelSize(r.height)); + } + } + } + gc.setAlpha(NONE_ALPHA); + } + + private void drawBadPatchAreas(GC gc, int baseX, int baseY) { + if (mBadChunks != null) { + int yLen = mBadChunks.length; + int xLen = mBadChunks[0].length; + + gc.setAlpha(NONE_ALPHA); + gc.setForeground(CORRUPTED_COLOR); + gc.setBackground(CORRUPTED_COLOR); + + for (int yPos = 0; yPos < yLen; yPos++) { + for (int xPos = 0; xPos < xLen; xPos++) { + Chunk c = mBadChunks[yPos][xPos]; + if ((c.type & Chunk.TYPE_CORRUPT) != 0) { + Rectangle r = c.rect; + gc.drawRectangle( + baseX + getZoomedPixelSize(r.x), + baseY + getZoomedPixelSize(r.y), + getZoomedPixelSize(r.width), + getZoomedPixelSize(r.height)); + } + } + } + } + } + + private void drawGuideLine(GC gc, int baseX, int baseY) { + gc.setAntialias(SWT.ON); + gc.setInterpolation(SWT.HIGH); + + int x = Math.round((mCursorPoint.x * ((float) mZoom / 100) + baseX) + + ((float) mZoom / 100 / 2)); + int y = Math.round((mCursorPoint.y * ((float) mZoom / 100) + baseY) + + ((float) mZoom / 100 / 2)); + gc.setAlpha(GUIDE_ALPHA); + + Point size = getSize(); + gc.drawLine(x, 0, x, size.y); + gc.drawLine(0, y, size.x, y); + + gc.setAlpha(NONE_ALPHA); + } + + @Override + public void keyPressed(KeyEvent event) { + int keycode = event.keyCode; + if (keycode == SWT.CTRL) { + isCtrlPressed = true; + } + if (keycode == SWT.SHIFT) { + isShiftPressed = true; + if (mStatusChangedListener != null) { + mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS2); + } + } + } + + @Override + public void keyReleased(KeyEvent event) { + int keycode = event.keyCode; + if (keycode == SWT.CTRL) { + isCtrlPressed = false; + } + if (keycode == SWT.SHIFT) { + isShiftPressed = false; + if (mStatusChangedListener != null) { + mStatusChangedListener.helpTextChanged(HELP_MESSAGE_KEY_TIPS); + } + } + } + + @Override + public void dispose() { + mBackgroundLayer.dispose(); + super.dispose(); + } + + /** + * Listen image updated event. + */ + public interface UpdateListener { + /** + * 9-patched image has been updated. + */ + public void update(NinePatchedImage image); + } + + /** + * Listen status changed event. + */ + public interface StatusChangedListener { + /** + * Mouse cursor position has been changed. + */ + public void cursorPositionChanged(int x, int y); + + /** + * Help text has been changed. + */ + public void helpTextChanged(String text); + } +} |