/** * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license.txt included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ /* * Created on Mar 27, 2006 */ package org.python.pydev.editor.actions; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.IStatusLineManager; import org.eclipse.jface.text.ITextListener; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITextViewerExtension; import org.eclipse.jface.text.TextEvent; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.VerifyKeyListener; 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.VerifyEvent; import org.eclipse.swt.widgets.Table; import org.eclipse.ui.progress.UIJob; import org.eclipse.ui.texteditor.IStatusField; import org.eclipse.ui.texteditor.IStatusFieldExtension; import org.python.pydev.editor.KeyAssistDialog; import org.python.pydev.editor.PyEdit; import com.aptana.shared_core.string.FastStringBuffer; /** * This action is supposed to be subclassed. It provides means to attach a listener to the editor and grab the input until * it is disabled (such as the incremental find: ctrl+j) * * Reference implementation: * * class IncrementalFindTarget * @author Fabio */ public class OfflineActionTarget implements VerifyKeyListener, MouseListener, FocusListener, ISelectionChangedListener, ITextListener { /** The string representing rendered tab */ private final static String TAB = "<TAB>"; /** The text viewer to operate on */ private final ITextViewer fTextViewer; /** The status line manager for output */ private final IStatusLineManager fStatusLine; /** The current find string */ private StringBuffer fFindString = new StringBuffer(); /** A flag indicating listeners are installed. */ private boolean fInstalled; /** * The find status field. */ private IStatusField fStatusField; /** * Tells whether the status field implements * <code>IStatusFieldExtension</code>. * @see IStatusFieldExtension */ private boolean fIsStatusFieldExtension; private PyEdit fEdit; /** * Shows a dialog with the available keys registered. */ private KeyAssistDialog keyAssistDialog; /** * This lock should be used to check for fInstalled and accessing the keyAssistDialog. */ private Object lock = new Object(); /** * Creates an instance of an incremental find target. * @param viewer the text viewer to operate on * @param manager the status line manager for output */ public OfflineActionTarget(ITextViewer viewer, IStatusLineManager manager, PyEdit edit) { Assert.isNotNull(viewer); Assert.isNotNull(manager); fTextViewer = viewer; fStatusLine = manager; fEdit = edit; } public void beginSession() { // Workaround since some accelerators get handled directly by the OS if (fInstalled) { updateStatus(); return; } fFindString.setLength(0); install(); updateStatus(); } public void endSession() { // will uninstall itself } /** * Installs this target. I.e. adds all required listeners. */ private void install() { if (fInstalled) return; StyledText text = fTextViewer.getTextWidget(); if (text == null) return; text.addMouseListener(this); text.addFocusListener(this); fTextViewer.addTextListener(this); ISelectionProvider selectionProvider = fTextViewer.getSelectionProvider(); if (selectionProvider != null) selectionProvider.addSelectionChangedListener(this); if (fTextViewer instanceof ITextViewerExtension) ((ITextViewerExtension) fTextViewer).prependVerifyKeyListener(this); else text.addVerifyKeyListener(this); keyAssistDialog = new KeyAssistDialog(this.fEdit); fInstalled = true; //Wait a bit until showing the key assist dialog new UIJob("") { public IStatus runInUIThread(IProgressMonitor monitor) { synchronized (lock) { if (fInstalled && keyAssistDialog != null) { keyAssistDialog.open(OfflineActionTarget.this.fEdit.getOfflineActionDescriptions(), OfflineActionTarget.this); } } return Status.OK_STATUS; } }.schedule(700); } /** * Uninstalls itself. I.e. removes all listeners installed in <code>install</code>. */ private void uninstall() { synchronized (lock) { if (!fInstalled) { return; } fTextViewer.removeTextListener(this); ISelectionProvider selectionProvider = fTextViewer.getSelectionProvider(); if (selectionProvider != null) selectionProvider.removeSelectionChangedListener(this); StyledText text = fTextViewer.getTextWidget(); if (text != null) { text.removeMouseListener(this); text.removeFocusListener(this); } if (fTextViewer instanceof ITextViewerExtension) { ((ITextViewerExtension) fTextViewer).removeVerifyKeyListener(this); } else { if (text != null) text.removeVerifyKeyListener(this); } if (keyAssistDialog != null) { keyAssistDialog.close(); } keyAssistDialog = null; fInstalled = false; } } /** * Updates the status line. */ private void updateStatus() { if (!fInstalled) return; String string = fFindString.toString(); statusMessage(string); // statusError("Error"); } /* * @see VerifyKeyListener#verifyKey(VerifyEvent) */ public void verifyKey(VerifyEvent event) { if (!event.doit) return; if (event.character == 0) { switch (event.keyCode) { case SWT.ARROW_DOWN: //special case: //if there's a key dialog with a table shown, set its focus when down is pressed synchronized (lock) { KeyAssistDialog tempKeyAssistDialog = this.keyAssistDialog; if (tempKeyAssistDialog != null) { Table completionsTable = this.keyAssistDialog.getCompletionsTable(); if (completionsTable != null && !completionsTable.isDisposed()) { completionsTable.setFocus(); completionsTable.setSelection(0); event.doit = false; break; } } } // ALT, CTRL, ARROW_LEFT, ARROW_RIGHT == leave case SWT.ARROW_LEFT: case SWT.ARROW_RIGHT: case SWT.HOME: case SWT.END: case SWT.PAGE_DOWN: case SWT.PAGE_UP: case SWT.ARROW_UP: leave(); break; } // event.character != 0 } else { switch (event.character) { // ESC = quit case 0x1B: leave(); event.doit = false; break; //CR = exec and quit case 0x0D: boolean executed = doExec(); event.doit = false; if (!executed) { return; //we don't want to update the status } break; // backspace and delete case 0x08: case 0x7F: removeLastCharSearch(); event.doit = false; break; default: if (event.stateMask == 0 || event.stateMask == SWT.SHIFT || event.stateMask == (SWT.ALT | SWT.CTRL)) { // SWT.ALT | SWT.CTRL covers AltGr (see bug 43049) event.doit = false; if (addCharSearch(event.character)) { //ok, triggered some automatic action (does not need enter) executed = doExec(); if (!executed) { return; //we don't want to update the status } } } break; } } updateStatus(); } /** * @return whether an action was successfully executed. */ private boolean doExec() { statusClear(); String key = fFindString.toString(); if (fEdit.hasOfflineAction(key)) { synchronized (lock) { //if the user matched the key, don't show the key assist dialog anymore. if (this.keyAssistDialog != null) { this.keyAssistDialog.close(); this.keyAssistDialog = null; } } } final boolean executed = fEdit.onOfflineAction(key, this); if (executed) { //Don't use leave() because we don't want to clear the final status message //(in case the action actually changed it) uninstall(); } return executed; } /** * Is called from the outside on a backspace action (because it will eat the backspace, we have to make this * 'little' hack) */ public void removeLastCharSearchAndUpdateStatus() { removeLastCharSearch(); updateStatus(); } private void removeLastCharSearch() { final int len = fFindString.length(); if (len > 0) { fFindString.deleteCharAt(len - 1); } } /** * Adds the given character to the search string and repeats the search with the last parameters. * * @param c the character to append to the search pattern * @return <code>true</code> if the action triggered some 'automatic' action */ private boolean addCharSearch(char c) { fFindString.append(c); return fEdit.activatesAutomaticallyOn(fFindString.toString()); } /** * Leaves this incremental search session. */ public void leave() { statusClear(); uninstall(); } /* * @see ITextListener#textChanged(TextEvent) */ public void textChanged(TextEvent event) { if (event.getDocumentEvent() != null) leave(); } /* * @see MouseListener##mouseDoubleClick(MouseEvent) */ public void mouseDoubleClick(MouseEvent e) { leave(); } /* * @see MouseListener#mouseDown(org.eclipse.swt.events.MouseEvent) */ public void mouseDown(MouseEvent e) { leave(); } /* * @see MouseListener#mouseUp(org.eclipse.swt.events.MouseEvent) */ public void mouseUp(MouseEvent e) { leave(); } /* * @see FocusListener#focusGained(org.eclipse.swt.events.FocusEvent) */ public void focusGained(FocusEvent e) { leave(); } /* * @see FocusListener#focusLost(org.eclipse.swt.events.FocusEvent) */ public void focusLost(FocusEvent e) { // When the focus is lost, we have to treat the case where the focus went to the key assist // dialog, so, if that was the case, we won't leave right now, only when the focus is // removed from that dialog or ESC is pressed. KeyAssistDialog tempKeyAssistDialog = keyAssistDialog; if (tempKeyAssistDialog != null) { final Table completionsTable = tempKeyAssistDialog.getCompletionsTable(); if (completionsTable != null && !completionsTable.isDisposed()) { new UIJob("Check leave") { public IStatus runInUIThread(IProgressMonitor monitor) { synchronized (lock) { if (fInstalled && keyAssistDialog != null && !completionsTable.isDisposed()) { if (!completionsTable.isFocusControl()) { leave(); } else { completionsTable.addFocusListener(new FocusListener() { public void focusLost(FocusEvent e) { leave(); } public void focusGained(FocusEvent e) { leave(); } }); completionsTable.addKeyListener(new KeyListener() { public void keyReleased(KeyEvent e) { if (e.character == 0x1B) { //ESC leave(); } } public void keyPressed(KeyEvent e) { } }); } } else { leave(); } } return Status.OK_STATUS; } }.schedule(50); } } } /** * Sets the given string as status message, clears the status error message. * @param string the status message */ private void statusMessage(String string) { if (fStatusField != null) { if (fIsStatusFieldExtension) { ((IStatusFieldExtension) fStatusField).setErrorText(null); fStatusField.setText(escapeTabs(string)); ((IStatusFieldExtension) fStatusField).setVisible(true); fStatusLine.update(true); } else { fStatusLine.setErrorMessage(null); fStatusField.setText(escapeTabs(string)); } } else { fStatusLine.setErrorMessage(null); fStatusLine.setMessage(escapeTabs(string)); } } /** * Sets the status error message, clears the status message. * @param string the status error message */ public void statusError(String string) { if (fStatusField != null) { if (fIsStatusFieldExtension) { ((IStatusFieldExtension) fStatusField).setErrorText(escapeTabs(string)); fStatusField.setText(""); //$NON-NLS-1$ ((IStatusFieldExtension) fStatusField).setVisible(true); fStatusLine.update(true); } else { fStatusLine.setErrorMessage(escapeTabs(string)); fStatusField.setText(""); //$NON-NLS-1$ } } else { fStatusLine.setErrorMessage(escapeTabs(string)); fStatusLine.setMessage(null); } } /** * Clears the status message and the status error message. */ public void statusClear() { if (fStatusField != null) { if (fIsStatusFieldExtension) { fStatusField.setText(""); //$NON-NLS-1$ ((IStatusFieldExtension) fStatusField).setErrorText(null); ((IStatusFieldExtension) fStatusField).setVisible(false); fStatusLine.update(true); } else { fStatusField.setText(""); //$NON-NLS-1$ fStatusLine.setErrorMessage(null); } } else { fStatusLine.setErrorMessage(null); fStatusLine.setMessage(null); } } /** * Translates all tab characters into a proper status line presentation. * @param string the string in which to translate the tabs * @return the given string with all tab characters replace with a proper status line presentation */ private String escapeTabs(String string) { FastStringBuffer buffer = new FastStringBuffer(); int begin = 0; int end = string.indexOf('\t', begin); while (end >= 0) { buffer.append(string.substring(begin, end)); buffer.append(TAB); begin = end + 1; end = string.indexOf('\t', begin); } buffer.append(string.substring(begin)); return buffer.toString(); } /* * @see ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent) */ public void selectionChanged(SelectionChangedEvent e) { //System.out.println("selection changed:"+e); } /** * Sets the find status field for this incremental find target. * * @param statusField the status field */ void setStatusField(IStatusField statusField) { fStatusField = statusField; fIsStatusFieldExtension = fStatusField instanceof IStatusFieldExtension; } public boolean isInstalled() { return fInstalled; } }