/* * Copyright (c) 2011, the Dart project authors. * * Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html * * 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.google.dart.tools.ui.internal.dialogs; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.PreferenceConstants; import com.google.dart.tools.ui.internal.text.functions.DartWordIterator; import com.ibm.icu.text.BreakIterator; 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.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.ui.PlatformUI; import org.eclipse.ui.commands.ICommandService; import org.eclipse.ui.keys.IBindingService; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /** * Support for camelCase-aware sub-word navigation in dialog fields. */ public class TextFieldNavigationHandler { private static class ComboNavigable extends WorkaroundNavigable { private final Combo fCombo; public ComboNavigable(Combo combo) { fCombo = combo; // workaround for bug 103630: 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 int getCaretPosition() { selectionChanged(); return fCaretPosition; // return fCombo.getCaretPosition(); // not available: bug 103630 } @Override public Control getControl() { return fCombo; } @Override public Point getSelection() { return fCombo.getSelection(); } @Override public String getText() { return fCombo.getText(); } @Override public void setSelection(int start, int end) { fCombo.setSelection(new Point(start, end)); } @Override public void setText(String text) { fCombo.setText(text); } } private static class FocusHandler implements FocusListener { private static final String EMPTY_TEXT = ""; //$NON-NLS-1$ private final DartWordIterator fIterator; private final Navigable fNavigable; private KeyAdapter fKeyListener; private FocusHandler(Navigable navigable) { fIterator = new DartWordIterator(); fNavigable = navigable; Control control = navigable.getControl(); control.addFocusListener(this); if (control.isFocusControl()) { activate(); } control.addDisposeListener(new DisposeListener() { @Override public void widgetDisposed(DisposeEvent e) { deactivate(); } }); } @Override public void focusGained(FocusEvent e) { activate(); } @Override 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 TriggerSequence[] getKeyBindings(BindingManager localBindingManager, ICommandService commandService, String commandID) { Command command = commandService.getCommand(commandID); ParameterizedCommand pCmd = new ParameterizedCommand(command, null); return localBindingManager.getActiveBindingsDisregardingContextFor(pCmd); } private List/* <Submission> */<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) { DartToolsPlugin.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) { DartToolsPlugin.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; } }; } return fKeyListener; } } private abstract static class Navigable { public abstract int getCaretPosition(); public abstract Control getControl(); public abstract Point getSelection(); public abstract String getText(); public abstract void setSelection(int start, int end); public abstract void setText(String text); } private static class StyledTextNavigable extends Navigable { private final StyledText fStyledText; public StyledTextNavigable(StyledText styledText) { fStyledText = styledText; } @Override public int getCaretPosition() { return fStyledText.getCaretOffset(); } @Override public Control getControl() { return fStyledText; } @Override public Point getSelection() { return fStyledText.getSelection(); } @Override public String getText() { return fStyledText.getText(); } @Override public void setSelection(int start, int end) { fStyledText.setSelection(start, end); } @Override public void setText(String text) { fStyledText.setText(text); } } private abstract static class Submission { private final TriggerSequence[] fTriggerSequences; public Submission(TriggerSequence[] triggerSequences) { fTriggerSequences = triggerSequences; } public abstract void execute(); public TriggerSequence[] getTriggerSequences() { return fTriggerSequences; } } 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 || "carbon".equals(SWT.getPlatform()); //$NON-NLS-1$ 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 int getCaretPosition() { if (BUG_106024_TEXT_SELECTION) { selectionChanged(); return fCaretPosition; } else { return fText.getCaretPosition(); } } @Override public Control getControl() { return fText; } @Override public Point getSelection() { return fText.getSelection(); } @Override public String getText() { return fText.getText(); } @Override public void setSelection(int start, int end) { fText.setSelection(start, end); } @Override public void setText(String text) { fText.setText(text); } } private abstract static class WorkaroundNavigable extends Navigable { /* * workarounds for: - bug 103630: Add API: Combo#getCaretPosition() - bug 106024: * 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; } } public static void install(Combo combo) { if (isSubWordNavigationEnabled()) { new FocusHandler(new ComboNavigable(combo)); } } public static void install(StyledText styledText) { if (isSubWordNavigationEnabled()) { new FocusHandler(new StyledTextNavigable(styledText)); } } public static void install(Text text) { if (isSubWordNavigationEnabled()) { new FocusHandler(new TextNavigable(text)); } } private static boolean isSubWordNavigationEnabled() { IPreferenceStore preferenceStore = DartToolsPlugin.getDefault().getCombinedPreferenceStore(); return preferenceStore.getBoolean(PreferenceConstants.EDITOR_SUB_WORD_NAVIGATION); } }