/******************************************************************************* * Copyright (c) 2005, 2011 IBM Corporation 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jdt.internal.ui.dialogs; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import com.ibm.icu.text.BreakIterator; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.DisposeEvent; import org.eclipse.swt.events.DisposeListener; import org.eclipse.swt.events.FocusEvent; import org.eclipse.swt.events.FocusListener; import org.eclipse.swt.events.KeyAdapter; import org.eclipse.swt.events.KeyEvent; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Combo; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Text; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.CommandManager; import org.eclipse.core.commands.ParameterizedCommand; import org.eclipse.core.commands.common.NotDefinedException; import org.eclipse.core.commands.contexts.ContextManager; import org.eclipse.jface.bindings.BindingManager; import org.eclipse.jface.bindings.Scheme; import org.eclipse.jface.bindings.TriggerSequence; import org.eclipse.jface.bindings.keys.KeySequence; import org.eclipse.jface.bindings.keys.SWTKeySupport; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.util.Util; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.keys.IBindingService; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.jdt.ui.PreferenceConstants; import org.eclipse.jdt.internal.ui.JavaPlugin; import org.eclipse.jdt.internal.ui.text.JavaWordIterator; /** * Support for camelCase-aware sub-word navigation in dialog fields. */ public class TextFieldNavigationHandler { public static void install(Text text) { if (isSubWordNavigationEnabled()) new FocusHandler(new TextNavigable(text)); } public static void install(StyledText styledText) { if (isSubWordNavigationEnabled()) new FocusHandler(new StyledTextNavigable(styledText)); } public static void install(Combo combo) { if (isSubWordNavigationEnabled()) new FocusHandler(new ComboNavigable(combo)); } private static boolean isSubWordNavigationEnabled() { IPreferenceStore preferenceStore= JavaPlugin.getDefault().getCombinedPreferenceStore(); return preferenceStore.getBoolean(PreferenceConstants.EDITOR_SUB_WORD_NAVIGATION); } private abstract static class WorkaroundNavigable extends Navigable { /* workarounds for: * - bug 103630: Add API: Combo#getCaretPosition() * - bug 106024: Combo and Text#setSelection(int, int) does not handle start > end with SWT.SINGLE */ Point fLastSelection; int fCaretPosition; void selectionChanged() { Point selection= getSelection(); if (selection.equals(fLastSelection)) { // leave caret position } else if (selection.x == selection.y) { //empty range fCaretPosition= selection.x; } else if (fLastSelection.y == selection.y) { fCaretPosition= selection.x; //same end -> assume caret at start } else { fCaretPosition= selection.y; } fLastSelection= selection; } } private abstract static class Navigable { public abstract Control getControl(); public abstract String getText(); public abstract void setText(String text); public abstract Point getSelection(); public abstract void setSelection(int start, int end); public abstract int getCaretPosition(); } private static class TextNavigable extends WorkaroundNavigable { static final boolean BUG_106024_TEXT_SELECTION= "win32".equals(SWT.getPlatform()) //$NON-NLS-1$ // on carbon, getCaretPosition() always returns getSelection().x || Util.isMac(); private final Text fText; public TextNavigable(Text text) { fText= text; // workaround for bug 106024: if (BUG_106024_TEXT_SELECTION) { fLastSelection= getSelection(); fCaretPosition= fLastSelection.y; fText.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { selectionChanged(); } }); fText.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { selectionChanged(); } }); } } @Override public Control getControl() { return fText; } @Override public String getText() { return fText.getText(); } @Override public void setText(String text) { fText.setText(text); } @Override public Point getSelection() { return fText.getSelection(); } @Override public int getCaretPosition() { if (BUG_106024_TEXT_SELECTION) { selectionChanged(); return fCaretPosition; } else { return fText.getCaretPosition(); } } @Override public void setSelection(int start, int end) { fText.setSelection(start, end); } } private static class StyledTextNavigable extends Navigable { private final StyledText fStyledText; public StyledTextNavigable(StyledText styledText) { fStyledText= styledText; } @Override public Control getControl() { return fStyledText; } @Override public String getText() { return fStyledText.getText(); } @Override public void setText(String text) { fStyledText.setText(text); } @Override public Point getSelection() { return fStyledText.getSelection(); } @Override public int getCaretPosition() { return fStyledText.getCaretOffset(); } @Override public void setSelection(int start, int end) { fStyledText.setSelection(start, end); } } private static class ComboNavigable extends WorkaroundNavigable { private final Combo fCombo; public ComboNavigable(Combo combo) { fCombo= combo; // workaround for bug 103630 and bug 106024: fLastSelection= getSelection(); fCaretPosition= fLastSelection.y; fCombo.addKeyListener(new KeyAdapter() { @Override public void keyReleased(KeyEvent e) { selectionChanged(); } }); fCombo.addMouseListener(new MouseAdapter() { @Override public void mouseUp(MouseEvent e) { selectionChanged(); } }); } @Override public Control getControl() { return fCombo; } @Override public String getText() { return fCombo.getText(); } @Override public void setText(String text) { fCombo.setText(text); } @Override public Point getSelection() { return fCombo.getSelection(); } @Override public int getCaretPosition() { selectionChanged(); return fCaretPosition; // return fCombo.getCaretPosition(); // cannot be used as this does not return the logical caret position we need } @Override public void setSelection(int start, int end) { fCombo.setSelection(new Point(start, end)); } } private static class FocusHandler implements FocusListener { private static final String EMPTY_TEXT= ""; //$NON-NLS-1$ private final JavaWordIterator fIterator; private final Navigable fNavigable; private KeyAdapter fKeyListener; private FocusHandler(Navigable navigable) { fIterator= new JavaWordIterator(); fNavigable= navigable; Control control= navigable.getControl(); control.addFocusListener(this); if (control.isFocusControl()) activate(); control.addDisposeListener(new DisposeListener() { public void widgetDisposed(DisposeEvent e) { deactivate(); } }); } public void focusGained(FocusEvent e) { activate(); } public void focusLost(FocusEvent e) { deactivate(); } private void activate() { fNavigable.getControl().addKeyListener(getKeyListener()); } private void deactivate() { if (fKeyListener != null) { Control control= fNavigable.getControl(); if (! control.isDisposed()) control.removeKeyListener(fKeyListener); fKeyListener= null; } } private KeyAdapter getKeyListener() { if (fKeyListener == null) { fKeyListener= new KeyAdapter() { private final boolean IS_WORKAROUND= (fNavigable instanceof ComboNavigable) || (fNavigable instanceof TextNavigable && TextNavigable.BUG_106024_TEXT_SELECTION); private List<Submission> fSubmissions; @Override public void keyPressed(KeyEvent e) { if (IS_WORKAROUND) { if (e.keyCode == SWT.ARROW_LEFT && e.stateMask == SWT.MOD2) { int caretPosition= fNavigable.getCaretPosition(); if (caretPosition != 0) { Point selection= fNavigable.getSelection(); if (caretPosition == selection.x) fNavigable.setSelection(selection.y, caretPosition - 1); else fNavigable.setSelection(selection.x, caretPosition - 1); } e.doit= false; return; } else if (e.keyCode == SWT.ARROW_RIGHT && e.stateMask == SWT.MOD2) { String text= fNavigable.getText(); int caretPosition= fNavigable.getCaretPosition(); if (caretPosition != text.length()) { Point selection= fNavigable.getSelection(); if (caretPosition == selection.y) fNavigable.setSelection(selection.x, caretPosition + 1); else fNavigable.setSelection(selection.y, caretPosition + 1); } e.doit= false; return; } } int accelerator = SWTKeySupport.convertEventToUnmodifiedAccelerator(e); KeySequence keySequence = KeySequence.getInstance(SWTKeySupport.convertAcceleratorToKeyStroke(accelerator)); getSubmissions(); for (Iterator<Submission> iter= getSubmissions().iterator(); iter.hasNext();) { Submission submission= iter.next(); TriggerSequence[] triggerSequences= submission.getTriggerSequences(); for (int i= 0; i < triggerSequences.length; i++) { if (triggerSequences[i].equals(keySequence)) { // XXX does not work for multi-stroke bindings e.doit= false; submission.execute(); return; } } } } private List<Submission> getSubmissions() { if (fSubmissions != null) return fSubmissions; fSubmissions= new ArrayList<Submission>(); ICommandService commandService= (ICommandService) PlatformUI.getWorkbench().getAdapter(ICommandService.class); IBindingService bindingService= (IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class); if (commandService == null || bindingService == null) return fSubmissions; // Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=184502 , // similar to CodeAssistAdvancedConfigurationBlock.getKeyboardShortcut(..): BindingManager localBindingManager= new BindingManager(new ContextManager(), new CommandManager()); final Scheme[] definedSchemes= bindingService.getDefinedSchemes(); if (definedSchemes != null) { try { for (int i = 0; i < definedSchemes.length; i++) { Scheme scheme= definedSchemes[i]; Scheme localSchemeCopy= localBindingManager.getScheme(scheme.getId()); localSchemeCopy.define(scheme.getName(), scheme.getDescription(), scheme.getParentId()); } } catch (final NotDefinedException e) { JavaPlugin.log(e); } } localBindingManager.setLocale(bindingService.getLocale()); localBindingManager.setPlatform(bindingService.getPlatform()); localBindingManager.setBindings(bindingService.getBindings()); try { Scheme activeScheme= bindingService.getActiveScheme(); if (activeScheme != null) localBindingManager.setActiveScheme(activeScheme); } catch (NotDefinedException e) { JavaPlugin.log(e); } fSubmissions.add(new Submission(getKeyBindings(localBindingManager, commandService, ITextEditorActionDefinitionIds.SELECT_WORD_NEXT)) { @Override public void execute() { fIterator.setText(fNavigable.getText()); int caretPosition= fNavigable.getCaretPosition(); int newCaret= fIterator.following(caretPosition); if (newCaret != BreakIterator.DONE) { Point selection= fNavigable.getSelection(); if (caretPosition == selection.y) fNavigable.setSelection(selection.x, newCaret); else fNavigable.setSelection(selection.y, newCaret); } fIterator.setText(EMPTY_TEXT); } }); fSubmissions.add(new Submission(getKeyBindings(localBindingManager, commandService, ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS)) { @Override public void execute() { fIterator.setText(fNavigable.getText()); int caretPosition= fNavigable.getCaretPosition(); int newCaret= fIterator.preceding(caretPosition); if (newCaret != BreakIterator.DONE) { Point selection= fNavigable.getSelection(); if (caretPosition == selection.x) fNavigable.setSelection(selection.y, newCaret); else fNavigable.setSelection(selection.x, newCaret); } fIterator.setText(EMPTY_TEXT); } }); fSubmissions.add(new Submission(getKeyBindings(localBindingManager, commandService, ITextEditorActionDefinitionIds.WORD_NEXT)) { @Override public void execute() { fIterator.setText(fNavigable.getText()); int caretPosition= fNavigable.getCaretPosition(); int newCaret= fIterator.following(caretPosition); if (newCaret != BreakIterator.DONE) fNavigable.setSelection(newCaret, newCaret); fIterator.setText(EMPTY_TEXT); } }); fSubmissions.add(new Submission(getKeyBindings(localBindingManager, commandService, ITextEditorActionDefinitionIds.WORD_PREVIOUS)) { @Override public void execute() { fIterator.setText(fNavigable.getText()); int caretPosition= fNavigable.getCaretPosition(); int newCaret= fIterator.preceding(caretPosition); if (newCaret != BreakIterator.DONE) fNavigable.setSelection(newCaret, newCaret); fIterator.setText(EMPTY_TEXT); } }); fSubmissions.add(new Submission(getKeyBindings(localBindingManager, commandService, ITextEditorActionDefinitionIds.DELETE_NEXT_WORD)) { @Override public void execute() { Point selection= fNavigable.getSelection(); String text= fNavigable.getText(); int start; int end; if (selection.x != selection.y) { start= selection.x; end= selection.y; } else { fIterator.setText(text); start= fNavigable.getCaretPosition(); end= fIterator.following(start); fIterator.setText(EMPTY_TEXT); if (end == BreakIterator.DONE) return; } fNavigable.setText(text.substring(0, start) + text.substring(end)); fNavigable.setSelection(start, start); } }); fSubmissions.add(new Submission(getKeyBindings(localBindingManager, commandService, ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD)) { @Override public void execute() { Point selection= fNavigable.getSelection(); String text= fNavigable.getText(); int start; int end; if (selection.x != selection.y) { start= selection.x; end= selection.y; } else { fIterator.setText(text); end= fNavigable.getCaretPosition(); start= fIterator.preceding(end); fIterator.setText(EMPTY_TEXT); if (start == BreakIterator.DONE) return; } fNavigable.setText(text.substring(0, start) + text.substring(end)); fNavigable.setSelection(start, start); } }); return fSubmissions; } private TriggerSequence[] getKeyBindings(BindingManager localBindingManager, ICommandService commandService, String commandID) { Command command= commandService.getCommand(commandID); ParameterizedCommand pCmd= new ParameterizedCommand(command, null); return localBindingManager.getActiveBindingsDisregardingContextFor(pCmd); } }; } return fKeyListener; } } private abstract static class Submission { private TriggerSequence[] fTriggerSequences; public Submission(TriggerSequence[] triggerSequences) { fTriggerSequences= triggerSequences; } public TriggerSequence[] getTriggerSequences() { return fTriggerSequences; } public abstract void execute(); } }