blob: 18f7f623a620e6c6fa3d03c7dfe36a21082ec37d [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2018 IBM Corporation and others.
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
*******************************************************************************/
package org.eclipse.dltk.internal.ui.dialogs;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.internal.ui.text.DLTKWordIterator;
import org.eclipse.jface.bindings.TriggerSequence;
import org.eclipse.jface.bindings.keys.KeySequence;
import org.eclipse.jface.bindings.keys.SWTKeySupport;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
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.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 com.ibm.icu.text.BreakIterator;
/**
* 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() {
if (DLTKCore.DEBUG) {
System.err.println("Add language dependent code here"); //$NON-NLS-1$
}
// IPreferenceStore preferenceStore= DLTKPlugin.getDefault().getCombinedPreferenceStore();
// return preferenceStore.getBoolean(PreferenceConstants.EDITOR_SUB_WORD_NAVIGATION);
return true;
}
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" //$NON-NLS-1$
.equals(SWT.getPlatform())
// 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 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;
}
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:
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(); // not available: bug 103630
}
@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 DLTKWordIterator fIterator;
private final Navigable fNavigable;
private KeyAdapter fKeyListener;
private FocusHandler(Navigable navigable) {
fIterator = new DLTKWordIterator();
fNavigable = navigable;
Control control = navigable.getControl();
control.addFocusListener(this);
if (control.isFocusControl())
activate();
control.addDisposeListener(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 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;
@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 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 = PlatformUI
.getWorkbench()
.getAdapter(IContextService.class);
ICommandService commandService = PlatformUI
.getWorkbench()
.getAdapter(ICommandService.class);
IHandlerService handlerService = PlatformUI
.getWorkbench()
.getAdapter(IHandlerService.class);
IBindingService bindingService = 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)) {
@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(
bindingService.getActiveBindingsFor(
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(
bindingService.getActiveBindingsFor(
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(
bindingService.getActiveBindingsFor(
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(
bindingService.getActiveBindingsFor(
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(
bindingService.getActiveBindingsFor(
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);
}
});
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();
}
}