/******************************************************************************* * Copyright (c) 2004, 2008 John Krasnay and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * John Krasnay - initial API and implementation *******************************************************************************/ package net.sf.vex.swt; import java.io.IOException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.undo.CannotRedoException; import javax.swing.undo.CannotUndoException; import javax.xml.parsers.ParserConfigurationException; import net.sf.vex.action.AbstractVexAction; import net.sf.vex.action.DuplicateSelectionAction; import net.sf.vex.action.IVexAction; import net.sf.vex.action.NextTableCellAction; import net.sf.vex.action.PreviousTableCellAction; import net.sf.vex.action.RemoveElementAction; import net.sf.vex.action.SplitAction; import net.sf.vex.action.SplitItemAction; import net.sf.vex.core.Color; import net.sf.vex.core.ColorResource; import net.sf.vex.core.DisplayDevice; import net.sf.vex.core.Graphics; import net.sf.vex.core.Rectangle; import net.sf.vex.css.StyleSheet; import net.sf.vex.dom.Document; import net.sf.vex.dom.DocumentFragment; import net.sf.vex.dom.DocumentValidationException; import net.sf.vex.dom.Element; import net.sf.vex.dom.Node; import net.sf.vex.layout.Box; import net.sf.vex.layout.BoxFactory; import net.sf.vex.widget.HostComponent; import net.sf.vex.widget.IBoxFilter; import net.sf.vex.widget.IVexWidget; import net.sf.vex.widget.VexWidgetImpl; import org.eclipse.jface.action.IAction; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; 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.SelectionEvent; import org.eclipse.swt.events.SelectionListener; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.ScrollBar; import org.xml.sax.SAXException; /** * An implementation of the Vex widget based on SWT. */ public class VexWidget extends Canvas implements IVexWidget, ISelectionProvider { public VexWidget(Composite parent, int style) { super(parent, style); if (DisplayDevice.getCurrent() == null) { DisplayDevice.setCurrent(new SwtDisplayDevice()); } this.impl = new VexWidgetImpl(hostComponent); this.setBackground(this.getDisplay().getSystemColor(SWT.COLOR_WHITE)); ScrollBar vbar = this.getVerticalBar(); if (vbar != null) { // vbar.setIncrement(20); vbar.addSelectionListener(selectionListener); } this.addControlListener(this.controlListener); this.addFocusListener(this.focusListener); this.addKeyListener(this.keyListener); this.addMouseListener(this.mouseListener); // this.addMouseMoveListener(this.mouseMoveListener); this.addPaintListener(this.painter); } public void dispose() { super.dispose(); this.impl.setFocus(false); // This stops the caret timer, in case the control // is disposed before focus is lost. } //----------------------------------------- IInputProvider methods public Object getInput() { return this.impl.getDocument(); } //----------------------------------------- ISelectionProvider methods public void addSelectionChangedListener(ISelectionChangedListener listener) { this.selectionListeners.add(listener); } public ISelection getSelection() { return this.selection; } public void removeSelectionChangedListener(ISelectionChangedListener listener) { this.selectionListeners.remove(listener); } public void setSelection(ISelection selection) { throw new RuntimeException("Unexpected call to setSelection"); } public void beginWork() { this.impl.beginWork(); } public boolean canPaste() { // TODO Auto-generated method stub return false; } /** * @see net.sf.vex.widget.IVexWidget#canPasteText() */ public boolean canPasteText() { // TODO Auto-generated method stub return false; } public boolean canRedo() { return this.impl.canRedo(); } public boolean canUndo() { return this.impl.canUndo(); } public boolean canUnwrap() { return this.impl.canUnwrap(); } public Point computeSize(int wHint, int hHint, boolean changed) { org.eclipse.swt.graphics.Rectangle r = this.getClientArea(); int height = r.height; ScrollBar vbar = this.getVerticalBar(); if (vbar != null) { height = vbar.getMaximum(); } return new Point(r.width, height); } public void copySelection() { // Clipboard clipboard = new Clipboard(this.getDisplay()); // Object[] data = { // this.getSelectedFragment(), // this.getSelectedText() // }; // Transfer[] transfers = { // DocumentFragmentTransfer.getInstance(), // TextTransfer.getInstance() // }; // clipboard.setContents(data, transfers); } public void cutSelection() { this.copySelection(); this.deleteSelection(); } public void deleteNextChar() throws DocumentValidationException { this.impl.deleteNextChar(); } public void deletePreviousChar() throws DocumentValidationException { this.impl.deletePreviousChar(); } public void deleteSelection() { this.impl.deleteSelection(); } public void doWork(Runnable runnable) { this.impl.doWork(runnable); } public void doWork(boolean savePosition, Runnable runnable) { this.impl.doWork(savePosition, runnable); } public void endWork(boolean success) { this.impl.endWork(success); } public Box findInnermostBox(IBoxFilter filter) { return this.impl.findInnermostBox(filter); } public BoxFactory getBoxFactory() { return this.impl.getBoxFactory(); } public int getCaretOffset() { return this.impl.getCaretOffset(); } public Element getCurrentElement() { return this.impl.getCurrentElement(); } public Document getDocument() { return this.impl.getDocument(); } public int getLayoutWidth() { return this.impl.getLayoutWidth(); } public int getSelectionEnd() { return this.impl.getSelectionEnd(); } public int getSelectionStart() { return this.impl.getSelectionStart(); } public DocumentFragment getSelectedFragment() { return this.impl.getSelectedFragment(); } public String getSelectedText() { return this.impl.getSelectedText(); } public StyleSheet getStyleSheet() { return this.impl.getStyleSheet(); } public int getUndoDepth() { return this.impl.getUndoDepth(); } public String[] getValidInsertElements() { return this.impl.getValidInsertElements(); } public String[] getValidMorphElements() { return this.impl.getValidMorphElements(); } public boolean hasSelection() { return this.impl.hasSelection(); } public void insertChar(char c) throws DocumentValidationException { this.impl.insertChar(c); } public void insertFragment(DocumentFragment frag) throws DocumentValidationException { this.impl.insertFragment(frag); } public void insertElement(Element element) throws DocumentValidationException { this.impl.insertElement(element); } public void insertText(String text) throws DocumentValidationException { this.impl.insertText(text); } public boolean isDebugging() { return impl.isDebugging(); } public void morph(Element element) throws DocumentValidationException { this.impl.morph(element); } public void moveBy(int distance) { this.impl.moveBy(distance); } public void moveBy(int distance, boolean select) { this.impl.moveBy(distance, select); } public void moveTo(int offset) { this.impl.moveTo(offset); } public void moveTo(int offset, boolean select) { this.impl.moveTo(offset, select); } public void moveToLineEnd(boolean select) { this.impl.moveToLineEnd(select); } public void moveToLineStart(boolean select) { this.impl.moveToLineStart(select); } public void moveToNextLine(boolean select) { this.impl.moveToNextLine(select); } public void moveToNextPage(boolean select) { this.impl.moveToNextPage(select); } public void moveToNextWord(boolean select) { this.impl.moveToNextWord(select); } public void moveToPreviousLine(boolean select) { this.impl.moveToPreviousLine(select); } public void moveToPreviousPage(boolean select) { this.impl.moveToPreviousPage(select); } public void moveToPreviousWord(boolean select) { this.impl.moveToPreviousWord(select); } public IAction[] getValidInsertActions() { String[] names = this.getValidInsertElements(); IAction[] actions = new IAction[names.length]; for (int i = 0; i < names.length; i++) { actions[i] = new InsertElementAction(names[i]); } return actions; } public IAction[] getValidMorphActions() { String[] names = this.getValidMorphElements(); IAction[] actions = new IAction[names.length]; for (int i = 0; i < names.length; i++) { actions[i] = new MorphElementAction(names[i]); } return actions; } /** * @see net.sf.vex.widget.IVexWidget#paste() */ public void paste() throws DocumentValidationException { // Clipboard clipboard = new Clipboard(this.getDisplay()); // DocumentFragment frag = (DocumentFragment) // clipboard.getContents(DocumentFragmentTransfer.getInstance()); // if (frag != null) { // this.insertFragment(frag); // } else { // this.pasteText(); // } } /** * @see net.sf.vex.widget.IVexWidget#pasteText() */ public void pasteText() throws DocumentValidationException { // Clipboard clipboard = new Clipboard(this.getDisplay()); // String text = (String) // clipboard.getContents(TextTransfer.getInstance()); // if (text != null) { // this.insertText(text); // } } public void redo() throws CannotRedoException { this.impl.redo(); } public void removeAttribute(String attributeName) { this.impl.removeAttribute(attributeName); } public void savePosition(Runnable runnable) { this.impl.savePosition(runnable); } public void selectAll() { this.impl.selectAll(); } public void selectWord() { this.impl.selectWord(); } public void setAttribute(String attributeName, String value) { this.impl.setAttribute(attributeName, value); } public void setBoxFactory(BoxFactory boxFactory) { this.impl.setBoxFactory(boxFactory); } public void setDebugging(boolean debugging) { impl.setDebugging(debugging); } public void setDocument(Document doc, StyleSheet styleSheet) { this.impl.setDocument(doc, styleSheet); } public void setDocument(URL docUrl, URL ssURL) throws IOException, ParserConfigurationException, SAXException { this.impl.setDocument(docUrl, ssURL); } public void setLayoutWidth(int width) { this.impl.setLayoutWidth(width); } public void setStyleSheet(StyleSheet styleSheet) { this.impl.setStyleSheet(styleSheet); } public void setStyleSheet(URL ssUrl) throws IOException { this.impl.setStyleSheet(ssUrl); } /** * Show a popup list of elements that are valid to be inserted at the * current position. If one of the elements is selected, it is inserted * before returning. */ public void showInsertElementPopup() { // XXX RAP auskommentiert cp // PopupList list = new PopupList(this.getShell()); // list.setItems(this.getValidInsertElements()); // // Rectangle caret = this.impl.getCaret().getBounds(); // Point display = this.toDisplay(caret.getX() + 10, caret.getY()); // String selected = list.open(new // org.eclipse.swt.graphics.Rectangle(display.x, display.y, 200, 0)); // if (selected != null) { // try { // this.insertElement(new Element(selected)); // } catch (DocumentValidationException e) { // e.printStackTrace(); // } // } } /** * Show a popup list of elements to which it is valid to morph the current * element. If one of the elements is selected, the current element is * morphed before returning. */ public void showMorphElementPopup() { // XXX RAP auskommentiert cp // PopupList list = new PopupList(this.getShell()); // list.setItems(this.getValidMorphElements()); // // Rectangle caret = this.impl.getCaret().getBounds(); // Point display = this.toDisplay(caret.getX() + 10, caret.getY()); // String selected = list.open(new // org.eclipse.swt.graphics.Rectangle(display.x, display.y, 200, 0)); // if (selected != null) { // try { // this.morph(new Element(selected)); // } catch (DocumentValidationException e) { // e.printStackTrace(); // } // } } public void split() throws DocumentValidationException { this.impl.split(); } public void undo() throws CannotUndoException { this.impl.undo(); } public int viewToModel(int x, int y) { return this.impl.viewToModel(x, y); } //====================================================== PRIVATE //------------------------------------------------------ Fields private static final char CHAR_NONE = 0; private static Map keyMap; private VexWidgetImpl impl; // Fields controlling scrolling int originX = 0; int originY = 0; private List selectionListeners = new ArrayList(); private ISelection selection; //------------------------------------------------------ Inner Classes private Runnable caretTimerRunnable = new Runnable() { public void run() { impl.toggleCaret(); } }; private Timer caretTimer = new Timer(500, this.caretTimerRunnable); private ControlListener controlListener = new ControlListener() { public void controlMoved(ControlEvent e) { } public void controlResized(ControlEvent e) { org.eclipse.swt.graphics.Rectangle r = getClientArea(); // There seems to be a bug in SWT (at least on Linux/GTK+) // When maximizing the editor, the width is first set to 1, // then to the correct width if (r.width == 1) { return; } impl.setLayoutWidth(r.width); ScrollBar vbar = getVerticalBar(); if (vbar != null) { vbar.setThumb(r.height); // XXX RAP auskommentiert cp // vbar.setPageIncrement(Math.round(r.height * 0.9f)); } } }; private FocusListener focusListener = new FocusListener() { public void focusGained(FocusEvent e) { impl.setFocus(true); caretTimer.start(); } public void focusLost(FocusEvent e) { impl.setFocus(false); caretTimer.stop(); } }; private HostComponent hostComponent = new HostComponent() { public Graphics createDefaultGraphics() { if (VexWidget.this.isDisposed()) { System.out.println("*** Woot! VexWidget is disposed!"); } return new SwtGraphics(new GC(VexWidget.this)); } public void fireSelectionChanged() { if (hasSelection()) { Node[] nodes = getDocument().getNodes(getSelectionStart(), getSelectionEnd()); selection = new StructuredSelection(nodes); } else { selection = new StructuredSelection(getCurrentElement()); } SelectionChangedEvent e = new SelectionChangedEvent(VexWidget.this, selection); for (int i = 0; i < selectionListeners.size(); i++) { ISelectionChangedListener listener = (ISelectionChangedListener) selectionListeners.get(i); listener.selectionChanged(e); } caretTimer.reset(); } public Rectangle getViewport() { return new Rectangle( getClientArea().x - originX, getClientArea().y - originY, getClientArea().width, getClientArea().height); } public void invokeLater(Runnable runnable) { VexWidget.this.getDisplay().asyncExec(runnable); } public void repaint() { if (!VexWidget.this.isDisposed()) { // We can sometimes get a repaint from the VexWidgetImpl's // caret timer thread after the Widget is disposed. VexWidget.this.redraw(); } } public void repaint(int x, int y, int width, int height) { VexWidget.this.redraw(x + originX, y + originY, width, height, true); } public void scrollTo(int left, int top) { ScrollBar vbar = getVerticalBar(); if (vbar != null) { vbar.setSelection(top); } setOrigin(-left, -top); } public void setPreferredSize(int width, int height) { ScrollBar vbar = getVerticalBar(); if (vbar != null) { vbar.setMaximum(height); } } @Override public boolean isDisposed() { return VexWidget.this.isDisposed(); } }; private static abstract class Action extends AbstractVexAction { public void run(IVexWidget vexWidget) { try { this.runEx(vexWidget); } catch (Exception ex) { ex.printStackTrace(); } } public abstract void runEx(IVexWidget w) throws Exception; } private KeyListener keyListener = new KeyListener() { public void keyPressed(KeyEvent e) { //System.out.println("Key pressed, keyCode is " + e.keyCode + ", keyChar is " + ((int) e.character) + ", stateMask is " + e.stateMask); KeyStroke keyStroke = new KeyStroke(e); Map map = getKeyMap(); if (map.containsKey(keyStroke)) { Object action = map.get(keyStroke); if (action instanceof IVexAction) { ((IVexAction) action).run(VexWidget.this); } else { try { ((Action) map.get(keyStroke)).runEx(VexWidget.this); } catch (Exception ex) { ex.printStackTrace(); } } } else if (!Character.isISOControl(e.character)) { try { insertChar(e.character); } catch (DocumentValidationException e1) { // TODO Auto-generated catch block e1.printStackTrace(); } } } public void keyReleased(KeyEvent e) { } }; private MouseListener mouseListener = new MouseListener() { public void mouseDoubleClick(MouseEvent e) { if (e.button == 1) { selectWord(); } } public void mouseDown(MouseEvent e) { if (e.button == 1) { int offset = viewToModel(e.x - originX, e.y - originY); boolean select = (e.stateMask == SWT.SHIFT); moveTo(offset, select); } } public void mouseUp(MouseEvent e) { } }; private MouseMoveListener mouseMoveListener = new MouseMoveListener() { public void mouseMove(MouseEvent e) { if ((e.stateMask & SWT.BUTTON1) > 0) { int offset = viewToModel(e.x - originX, e.y - originY); moveTo(offset, true); } } }; private PaintListener painter = new PaintListener() { public void paintControl(PaintEvent e) { SwtGraphics g = new SwtGraphics(e.gc); g.setOrigin(originX, originY); Color bgColor = impl.getBackgroundColor(); if (bgColor == null) { bgColor = new Color(255, 255, 255); } ColorResource color = g.createColor(bgColor); ColorResource oldColor = g.setColor(color); Rectangle r = g.getClipBounds(); g.fillRect(r.getX(), r.getY(), r.getWidth(), r.getHeight()); g.setColor(oldColor); color.dispose(); impl.paint(g, 0, 0); } }; private SelectionListener selectionListener = new SelectionListener() { public void widgetSelected(SelectionEvent e) { ScrollBar vbar = getVerticalBar(); if (vbar != null) { int y = - vbar.getSelection(); setOrigin(0, y); } } public void widgetDefaultSelected(SelectionEvent e) { } }; private class InsertElementAction extends org.eclipse.jface.action.Action { public InsertElementAction(String name) { this.name = name; this.setText(name); } public void run() { try { insertElement(new Element(name)); } catch (DocumentValidationException e) { } } private String name; } private class MorphElementAction extends org.eclipse.jface.action.Action { public MorphElementAction(String elementName) { this.elementName = elementName; this.setText(elementName); } public void run() { try { morph(new Element(elementName)); } catch (DocumentValidationException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private String elementName; } //------------------------------------------------------ Methods private static void addKey(char character, int keyCode, int stateMask, Action action) { keyMap.put(new KeyStroke(character, keyCode, stateMask), action); } private static void addKey(char character, int keyCode, int stateMask, IVexAction action) { keyMap.put(new KeyStroke(character, keyCode, stateMask), action); } private static void buildKeyMap() { addKey(CHAR_NONE, SWT.ARROW_DOWN, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveToNextLine(false); } }); addKey(CHAR_NONE, SWT.ARROW_DOWN, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveToNextLine(true); } }); addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveBy(-1); } }); addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveBy(-1, true); } }); addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveToPreviousWord(false); } }); addKey(CHAR_NONE, SWT.ARROW_LEFT, SWT.SHIFT | SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveToPreviousWord(true); } }); addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveBy(+1); } }); addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveBy(+1, true); } }); addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveToNextWord(false); } }); addKey(CHAR_NONE, SWT.ARROW_RIGHT, SWT.SHIFT | SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveToNextWord(true); } }); addKey(CHAR_NONE, SWT.ARROW_UP, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveToPreviousLine(false); } }); addKey(CHAR_NONE, SWT.ARROW_UP, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveToPreviousLine(true); } }); addKey(SWT.BS, SWT.BS, SWT.NONE, new Action() { public void runEx(IVexWidget w) throws Exception { w.deletePreviousChar(); } }); addKey(SWT.DEL, SWT.DEL, SWT.NONE, new Action() { public void runEx(IVexWidget w) throws Exception { w.deleteNextChar(); } }); addKey(SWT.TAB, SWT.TAB, SWT.NONE, new NextTableCellAction()); addKey(SWT.TAB, SWT.TAB, SWT.SHIFT, new PreviousTableCellAction()); addKey(CHAR_NONE, SWT.END, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveToLineEnd(false); } }); addKey(CHAR_NONE, SWT.END, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveToLineEnd(true); } }); addKey(CHAR_NONE, SWT.END, SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveTo(w.getDocument().getLength() - 1); } }); addKey(CHAR_NONE, SWT.END, SWT.SHIFT | SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveTo(w.getDocument().getLength() - 1, true); } }); addKey(CHAR_NONE, SWT.HOME, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveToLineStart(false); } }); addKey(CHAR_NONE, SWT.HOME, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveToLineStart(true); } }); addKey(CHAR_NONE, SWT.HOME, SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveTo(1); } }); addKey(CHAR_NONE, SWT.HOME, SWT.SHIFT | SWT.CONTROL, new Action() { public void runEx(IVexWidget w) { w.moveTo(1, true); } }); addKey(CHAR_NONE, SWT.PAGE_DOWN, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveToNextPage(false); } }); addKey(CHAR_NONE, SWT.PAGE_DOWN, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveToNextPage(true); } }); addKey(CHAR_NONE, SWT.PAGE_UP, SWT.NONE, new Action() { public void runEx(IVexWidget w) { w.moveToPreviousPage(false); } }); addKey(CHAR_NONE, SWT.PAGE_UP, SWT.SHIFT, new Action() { public void runEx(IVexWidget w) { w.moveToPreviousPage(true); } }); addKey(' ', 0, SWT.CONTROL, new Action() { // Ctrl-Space public void runEx(IVexWidget w) { ((VexWidget)w).showInsertElementPopup(); } }); addKey('\r', 0, SWT.CONTROL, new Action() { // Ctrl-M public void runEx(IVexWidget w) { ((VexWidget)w).showMorphElementPopup(); } }); addKey((char) 23, 0, SWT.CONTROL, new RemoveElementAction()); // addKey('\r', '\r', SWT.NONE, new Action() { // Enter key // public void runEx(IVexWidget w) throws Exception { w.split(); } }); addKey('\r', '\r', SWT.NONE, new SplitAction()); addKey('\r', '\r', SWT.SHIFT, new SplitItemAction()); addKey((char) 4, 100, SWT.CONTROL, new DuplicateSelectionAction()); // Ctrl-D } private static Map getKeyMap() { if (keyMap == null) { keyMap = new HashMap(); buildKeyMap(); } return keyMap; } /** * Scrolls to the given position in the widget. * @param x x-coordinate of the position to which to scroll * @param y y-coordinate of the position to which to scroll */ private void setOrigin(int x, int y) { int destX = x - originX; int destY = y - originY; org.eclipse.swt.graphics.Rectangle ca = getClientArea(); // scroll(destX, destY, 0, 0, ca.width, ca.height, false); originX = x; originY = y; } }