/* ****************************************************************************** * Copyright (c) 2006-2012 XMind Ltd. and others. * * This file is a part of XMind 3. XMind releases 3 and * above are dual-licensed under the Eclipse Public License (EPL), * which is available at http://www.eclipse.org/legal/epl-v10.html * and the GNU Lesser General Public License (LGPL), * which is available at http://www.gnu.org/licenses/lgpl.html * See http://www.xmind.net/license.html for details. * * Contributors: * XMind Ltd. - initial API and implementation *******************************************************************************/ package org.xmind.ui.internal.editor; import org.eclipse.draw2d.FigureListener; import org.eclipse.draw2d.IFigure; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.events.ControlEvent; import org.eclipse.swt.events.ControlListener; 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.Color; import org.eclipse.swt.graphics.GC; import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.graphics.TextLayout; import org.eclipse.swt.widgets.Canvas; import org.eclipse.swt.widgets.Caret; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.IME; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.ScrollBar; import org.xmind.gef.EditDomain; import org.xmind.gef.GEF; import org.xmind.gef.IGraphicalViewer; import org.xmind.gef.Request; import org.xmind.gef.part.IGraphicalPart; import org.xmind.gef.part.IPart; import org.xmind.gef.tool.PartTextSelection; import org.xmind.gef.ui.editor.IGraphicalEditor; import org.xmind.ui.mindmap.ITitleTextPart; import org.xmind.ui.mindmap.MindMapUI; public class IMESupport implements ISelectionChangedListener, Listener, ControlListener, SelectionListener, FigureListener, PaintListener { public static final String PROP_IGNORE_KEY_DOWN = "org.xmind.ui.ignoreKeyDown"; //$NON-NLS-1$ private static final int DEFAULT_CARET_WIDTH = 2; private MindMapEditorPage page; private IGraphicalViewer viewer; private Canvas canvas; private ScrollBar hBar; private ScrollBar vBar; private IME ime; private Caret caret; private boolean imeNeedDispose; private boolean caretNeedDispose; private int caretWidth; private int caretHeight; private IPart focus; private IFigure focusFigure = null; private TextLayout composition = null; private int compositionLeft = 0; private int compositionTop = 0; private StringBuilder composedCache = null; public IMESupport(MindMapEditorPage page, IGraphicalViewer viewer) { this.page = page; this.viewer = viewer; this.canvas = viewer.getCanvas(); this.canvas.addControlListener(this); this.canvas.addPaintListener(this); this.hBar = this.canvas.getHorizontalBar(); if (this.hBar != null) { this.hBar.addSelectionListener(this); } this.vBar = this.canvas.getVerticalBar(); if (this.vBar != null) { this.vBar.addSelectionListener(this); } this.ime = this.canvas.getIME(); if (this.ime == null) { this.ime = new IME(this.canvas, SWT.NONE); this.imeNeedDispose = true; } else { this.imeNeedDispose = false; } this.ime.addListener(SWT.ImeComposition, this); this.caret = this.canvas.getCaret(); if (this.caret == null) { this.caret = new Caret(this.canvas, SWT.NONE); this.caretNeedDispose = true; } else { this.caretNeedDispose = false; } this.caret.setVisible(false); this.caretWidth = DEFAULT_CARET_WIDTH; this.caretHeight = SWT.DEFAULT; viewer.addFocusedPartChangedListener(this); } public void dispose() { deactivateContext(); if (composition != null) { composition.dispose(); composition = null; } setFocusFigure(null); viewer.removeFocusedPartChangedListener(this); if (ime != null) { if (!ime.isDisposed()) { ime.removeListener(SWT.ImeComposition, this); } if (imeNeedDispose) { ime.dispose(); } ime = null; } if (caret != null) { if (caretNeedDispose) { caret.dispose(); } caret = null; } if (vBar != null && !vBar.isDisposed()) { vBar.removeSelectionListener(this); } if (hBar != null && !hBar.isDisposed()) { hBar.removeSelectionListener(this); } if (canvas != null && !canvas.isDisposed()) { canvas.removeControlListener(this); } } public void selectionChanged(SelectionChangedEvent event) { if (event.getSelection() instanceof IStructuredSelection) { focus = viewer .findPart(((IStructuredSelection) event.getSelection()) .getFirstElement()); } else if (event.getSelection() instanceof PartTextSelection) { focus = ((PartTextSelection) event.getSelection()).getPart(); } else { focus = null; } IFigure figure = null; if (focus instanceof ITitleTextPart) { figure = ((ITitleTextPart) focus).getTextFigure(); } else if (focus != null) { ITitleTextPart title = (ITitleTextPart) focus .getAdapter(ITitleTextPart.class); if (title != null) { figure = title.getTextFigure(); } else if (focus instanceof IGraphicalPart) { figure = ((IGraphicalPart) focus).getFigure(); } } setFocusFigure(figure); } private void setFocusFigure(IFigure figure) { if (figure != focusFigure) { if (focusFigure != null) { focusFigure.removeFigureListener(this); } if (figure != null) { figure.addFigureListener(this); } focusFigure = figure; } updateCompositionLocation(); } private void updateCompositionLocation() { if (focusFigure != null) { Point pos = focusFigure.getBounds().getTopLeft(); Insets border = focusFigure.getInsets(); pos.translate(border.left, border.top); focusFigure.translateToAbsolute(pos); setCompositionLocation(pos.x, pos.y); } else if (!canvas.isDisposed()) { Rectangle r = canvas.getBounds(); setCompositionLocation(Math.max(0, r.width / 2 - 100), Math.max(0, Math.min(r.height / 2 + 100, r.height - caretHeight))); } } private void setCompositionLocation(int x, int y) { this.compositionLeft = x; this.compositionTop = y; updateCaretLocation(); } private void updateCaretLocation() { if (caret == null || caret.isDisposed()) return; int x = compositionLeft; int y = compositionTop; caretWidth = DEFAULT_CARET_WIDTH; caretHeight = 10; if (ime.getCompositionOffset() >= 0 && composition != null && !composition.isDisposed()) { int cacheLength = composedCache == null ? 0 : composedCache.length(); int caretOffset = cacheLength + ime.getCaretOffset(); org.eclipse.swt.graphics.Point p = composition .getLocation(caretOffset, false); x = compositionLeft + p.x; y = compositionTop + p.y; if (ime.getWideCaret()) { Rectangle size = composition.getBounds(cacheLength, caretOffset); caretWidth = size.width; caretHeight = size.height; } else { caretWidth = DEFAULT_CARET_WIDTH; Rectangle size = composition.getBounds(caretOffset, caretOffset); caretHeight = size.height; } } caret.setBounds(x, y, caretWidth, caretHeight); } public void handleEvent(Event event) { if (event.type == SWT.ImeComposition) { // forwardCompositionEvent(event); switch (event.detail) { case SWT.COMPOSITION_CHANGED: handleCompositionChanged(event); break; case SWT.COMPOSITION_SELECTION: handleCompositionSelection(event); break; case SWT.COMPOSITION_OFFSET: handleCompositionOffset(event); break; } } } public void controlMoved(ControlEvent e) { updateCompositionLocation(); } public void controlResized(ControlEvent e) { updateCompositionLocation(); } public void widgetSelected(SelectionEvent e) { updateCompositionLocation(); } public void widgetDefaultSelected(SelectionEvent e) { updateCompositionLocation(); } public void figureMoved(IFigure source) { updateCompositionLocation(); } public void paintControl(PaintEvent e) { paintComposition(e.gc); } private void handleCompositionChanged(final Event event) { final String text = event.text; final int length = text.length(); if (length == ime.getCommitCount()) { // try { // System.out.println("IME completed: " // + Arrays.toString(text.getBytes("UTF-8"))); // } catch (UnsupportedEncodingException e) { // e.printStackTrace(); // } if (length > 0) { if (composedCache == null) { composedCache = new StringBuilder(); } composedCache.append(text); } int ignoreCount = viewer.getProperties() .getInteger(PROP_IGNORE_KEY_DOWN, 0); viewer.getProperties().set(PROP_IGNORE_KEY_DOWN, ignoreCount + text.length()); Display.getCurrent().asyncExec(new Runnable() { public void run() { if (canvas == null || canvas.isDisposed()) return; if (ime.getCompositionOffset() < 0) { if (composedCache != null && composedCache.length() > 0) { final EditDomain domain = viewer.getEditDomain(); Request request = new Request(GEF.REQ_EDIT) .setPrimaryTarget(focus).setDomain(domain) .setViewer(viewer) .setParameter(GEF.PARAM_TEXT, composedCache.toString()); //// .setParameter(GEF.PARAM_TEXT_SELECTION, //// new TextSelection(length, 0)) //// .setParameter(GEF.PARAM_FOCUS, Boolean.FALSE); domain.handleRequest(request); } composedCache = null; removeCompositionFeedback(); } else { showCompositionFeedback(ime.getText()); } } }); } else { // try { // System.out.println("Edit composition: " // + Arrays.toString(text.getBytes("UTF-8"))); // } catch (UnsupportedEncodingException e) { // e.printStackTrace(); // } showCompositionFeedback(text); } } private void handleCompositionSelection(Event event) { // System.out.println("IME require selection: " + ime.getCaretOffset()); event.start = ime.getCompositionOffset() < 0 ? 0 : ime.getCaretOffset(); event.end = ime.getCompositionOffset() < 0 ? 0 : ime.getCaretOffset(); event.text = ""; //$NON-NLS-1$ } private void handleCompositionOffset(Event event) { // System.out.println("IME require offset."); if (ime.getCompositionOffset() < 0 || composition == null || composition.isDisposed()) { event.index = 0; event.count = 0; } else { int x = event.x - compositionLeft; int y = event.y - compositionTop; int[] trailing = new int[1]; int offset = composition.getOffset(x, y, trailing); event.index = offset; event.count = trailing[0]; } } private void showCompositionFeedback(String text) { if (focusFigure == null) { removeCompositionFeedback(); return; } if (composition == null || composition.isDisposed()) { composition = new TextLayout(Display.getCurrent()); } composition.setFont(focusFigure.getFont()); if (composedCache != null) { text = composedCache.toString() + text; } composition.setText(text); canvas.redraw(); updateCaretLocation(); caret.setVisible(true); activateContext(); } private void removeCompositionFeedback() { deactivateContext(); if (composition != null) { composition.dispose(); composition = null; } canvas.redraw(); caret.setVisible(false); } private void activateContext() { IGraphicalEditor editor = page.getParentEditor(); if (editor instanceof MindMapEditor) { ((MindMapEditor) editor) .changeContext(MindMapUI.CONTEXT_MINDMAP_TEXTEDIT); } } private void deactivateContext() { IGraphicalEditor editor = page.getParentEditor(); if (editor instanceof MindMapEditor) { ((MindMapEditor) editor) .changeContext(page.getEditDomain().getActiveTool()); } } private void paintComposition(GC gc) { if (composition == null || composition.isDisposed()) return; Color fg = gc.getForeground(); Color bg = gc.getBackground(); int lineWidth = gc.getLineWidth(); int lineStyle = gc.getLineStyle(); int lineJoin = gc.getLineJoin(); int lineCap = gc.getLineCap(); Rectangle clipping = gc.getClipping(); gc.setLineWidth(1); gc.setLineStyle(SWT.LINE_SOLID); gc.setLineJoin(SWT.JOIN_BEVEL); gc.setLineCap(SWT.CAP_FLAT); Rectangle size = composition.getBounds(); gc.setClipping(compositionLeft - 2, compositionTop - 2, size.width + 4, size.height + 4); gc.setBackground(Display.getCurrent().getSystemColor(SWT.COLOR_WHITE)); gc.fillRectangle(compositionLeft - 2, compositionTop - 2, size.width + 4, size.height + 4); gc.setForeground( Display.getCurrent().getSystemColor(SWT.COLOR_DARK_GRAY)); gc.drawRectangle(compositionLeft - 2, compositionTop - 2, size.width + 3, size.height + 3); gc.setForeground(Display.getCurrent().getSystemColor(SWT.COLOR_BLACK)); composition.draw(gc, compositionLeft, compositionTop); gc.setClipping(clipping); gc.setLineCap(lineCap); gc.setLineJoin(lineJoin); gc.setLineStyle(lineStyle); gc.setLineWidth(lineWidth); gc.setBackground(bg); gc.setForeground(fg); } // private void forwardCompositionEvent(final Event event) { // if (focus == null || !focus.hasRole(GEF.ROLE_EDITABLE)) // return; // final EditDomain domain = viewer.getEditDomain(); // Request request = new Request(GEF.REQ_EDIT).setPrimaryTarget(focus) // .setDomain(domain).setViewer(viewer); ////// .setParameter(GEF.PARAM_TEXT, text) ////// .setParameter(GEF.PARAM_TEXT_SELECTION, ////// new TextSelection(length, 0)) ////// .setParameter(GEF.PARAM_FOCUS, Boolean.FALSE); // domain.handleRequest(request); // // Display.getCurrent().asyncExec(new Runnable() { // public void run() { // ITool tool = domain.getActiveTool(); // if (tool instanceof FloatingTextEditTool) { // FloatingTextEditor editor = ((FloatingTextEditTool) tool) // .getEditor(); // if (editor != null && !editor.isClosed()) { // IME ime2 = editor.getTextViewer().getTextWidget() // .getIME(); // final Event e = new Event(); // e.button = event.button; // e.character = event.character; // e.count = event.count; // e.data = event.data; // e.detail = event.detail; // e.display = event.display; // e.doit = event.doit; // e.end = event.end; // e.gc = event.gc; // e.height = event.height; // e.index = event.index; // e.item = event.item; // e.keyCode = event.keyCode; // e.keyLocation = event.keyLocation; // e.magnification = event.magnification; // e.rotation = event.rotation; // e.segments = event.segments; // e.segmentsChars = event.segmentsChars; // e.start = event.start; // e.stateMask = event.stateMask; // e.text = event.text; // e.time = event.time; // e.touches = event.touches; // e.type = event.type; // e.widget = ime2; // e.width = event.width; // e.x = event.x; // e.xDirection = event.xDirection; // e.y = event.y; // e.yDirection = event.yDirection; // Display.getCurrent().post(e); // // } // } // } // }); // } // public static void main(String[] args) { // final Display display = new Display(); // final Shell shell = new Shell(display); // shell.setBounds(200, 100, 500, 400); // shell.setLayout(new FillLayout()); // // final Canvas canvas = new Canvas(shell, SWT.NONE); // final IME ime = new IME(canvas, SWT.NONE); // final Caret caret = new Caret(canvas, SWT.NONE); // // caret.setBounds(10, 10, 2, 20); // // ime.addListener(SWT.ImeComposition, new Listener() { // public void handleEvent(Event event) { // if (event.detail == SWT.COMPOSITION_CHANGED) { // System.out.println("Composition changed: " + event.text); // } else if (event.detail == SWT.COMPOSITION_OFFSET) { // System.out.println("Composition require offset."); // } else if (event.detail == SWT.COMPOSITION_SELECTION) { // System.out.println("Composition require selection."); // } // } // }); // // Listener canvasListener = new Listener() { // public void handleEvent(Event event) { // if (event.type == SWT.Verify) { // System.out.println("Key verify: " + event.keyCode + ", " // + event.stateMask); // } else if (event.type == SWT.KeyDown) { // System.out.println("Key down: " + event.keyCode + ", " // + event.stateMask); // } else if (event.type == SWT.Traverse) { // System.out.println("Key traverse: " + event.detail); // } // } // }; // canvas.addListener(SWT.Verify, canvasListener); // canvas.addListener(SWT.KeyDown, canvasListener); // canvas.addListener(SWT.Traverse, canvasListener); // // shell.open(); // // while (!shell.isDisposed()) { // if (!display.readAndDispatch()) { // display.sleep(); // } // } // shell.dispose(); // display.dispose(); // } }