blob: 1686bc8c70c5a20624f8de5649062dec057368d8 [file] [log] [blame]
/*******************************************************************************
* 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();
}
}