| /******************************************************************************* |
| * Copyright (c) 2005, 2006 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 com.ibm.icu.text.BreakIterator; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| 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.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.ui.PlatformUI; |
| import org.eclipse.ui.commands.ICommandService; |
| import org.eclipse.ui.contexts.IContextActivation; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.handlers.IHandlerService; |
| 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: 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 |
| || "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() { |
| public void keyReleased(KeyEvent e) { |
| selectionChanged(); |
| } |
| }); |
| fText.addMouseListener(new MouseAdapter() { |
| public void mouseUp(MouseEvent e) { |
| selectionChanged(); |
| } |
| }); |
| } |
| } |
| |
| public Control getControl() { |
| return fText; |
| } |
| |
| public String getText() { |
| return fText.getText(); |
| } |
| |
| public void setText(String text) { |
| fText.setText(text); |
| } |
| |
| public Point getSelection() { |
| return fText.getSelection(); |
| } |
| |
| public int getCaretPosition() { |
| if (BUG_106024_TEXT_SELECTION) { |
| selectionChanged(); |
| return fCaretPosition; |
| } else { |
| return fText.getCaretPosition(); |
| } |
| } |
| |
| 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; |
| } |
| |
| public Control getControl() { |
| return fStyledText; |
| } |
| |
| public String getText() { |
| return fStyledText.getText(); |
| } |
| |
| public void setText(String text) { |
| fStyledText.setText(text); |
| } |
| |
| public Point getSelection() { |
| return fStyledText.getSelection(); |
| } |
| |
| public int getCaretPosition() { |
| return fStyledText.getCaretOffset(); |
| } |
| |
| 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: |
| fLastSelection= getSelection(); |
| fCaretPosition= fLastSelection.y; |
| fCombo.addKeyListener(new KeyAdapter() { |
| public void keyReleased(KeyEvent e) { |
| selectionChanged(); |
| } |
| }); |
| fCombo.addMouseListener(new MouseAdapter() { |
| public void mouseUp(MouseEvent e) { |
| selectionChanged(); |
| } |
| }); |
| } |
| |
| public Control getControl() { |
| return fCombo; |
| } |
| |
| public String getText() { |
| return fCombo.getText(); |
| } |
| |
| public void setText(String text) { |
| fCombo.setText(text); |
| } |
| |
| public Point getSelection() { |
| return fCombo.getSelection(); |
| } |
| |
| public int getCaretPosition() { |
| selectionChanged(); |
| return fCaretPosition; |
| // return fCombo.getCaretPosition(); // not available: bug 103630 |
| } |
| |
| 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 static final String TEXT_EDITOR_CONTEXT_ID= "org.eclipse.ui.textEditorScope"; //$NON-NLS-1$ |
| private final boolean IS_WORKAROUND= (fNavigable instanceof ComboNavigable) |
| || (fNavigable instanceof TextNavigable && TextNavigable.BUG_106024_TEXT_SELECTION); |
| private List/*<Submission>*/ fSubmissions; |
| |
| 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 iter= getSubmissions().iterator(); iter.hasNext();) { |
| Submission 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(); |
| |
| IContextService contextService= (IContextService) PlatformUI.getWorkbench().getAdapter(IContextService.class); |
| ICommandService commandService= (ICommandService) PlatformUI.getWorkbench().getAdapter(ICommandService.class); |
| IHandlerService handlerService= (IHandlerService) PlatformUI.getWorkbench().getAdapter(IHandlerService.class); |
| IBindingService bindingService= (IBindingService) PlatformUI.getWorkbench().getAdapter(IBindingService.class); |
| if (contextService == null || commandService == null || handlerService == null || bindingService == null) |
| return fSubmissions; |
| |
| IContextActivation[] contextActivations; |
| contextActivations= new IContextActivation[] { |
| contextService.activateContext(IContextService.CONTEXT_ID_WINDOW), // XXX relying on workbench feature https://bugs.eclipse.org/bugs/show_bug.cgi?id=115460#c11 |
| contextService.activateContext(TEXT_EDITOR_CONTEXT_ID) |
| }; |
| |
| fSubmissions.add(new Submission(bindingService.getActiveBindingsFor(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT)) { |
| 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(bindingService.getActiveBindingsFor(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS)) { |
| 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(bindingService.getActiveBindingsFor(ITextEditorActionDefinitionIds.WORD_NEXT)) { |
| 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(bindingService.getActiveBindingsFor(ITextEditorActionDefinitionIds.WORD_PREVIOUS)) { |
| 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(bindingService.getActiveBindingsFor(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD)) { |
| 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(bindingService.getActiveBindingsFor(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD)) { |
| 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); |
| } |
| }); |
| |
| for (int i= 0; i < contextActivations.length; i++) { |
| contextService.deactivateContext(contextActivations[i]); |
| } |
| |
| return fSubmissions; |
| } |
| |
| }; |
| } |
| 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(); |
| } |
| |
| } |