/*=============================================================================#
# Copyright (c) 2000-2016 Stephan Wahlbrink (WalWare.de) 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 implementation for AbstractTextEditor
# Stephan Wahlbrink - separate API and implementation
#=============================================================================*/
package de.walware.ecommons.text.ui;
import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ST;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Caret;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.ui.texteditor.ITextEditorExtension3.InsertMode;
public class TextViewerCustomCaretSupport {
/**
* The caret width for the wide (double) caret.
* Value: {@value}
*/
private static final int WIDE_CARET_WIDTH = 2;
/**
* The caret width for the narrow (single) caret.
* Value: {@value}
*/
private static final int SINGLE_CARET_WIDTH = 1;
private class ToggleOverwriteHandler extends AbstractHandler {
@Override
public Object execute(final ExecutionEvent event) throws ExecutionException {
toggleOverwriteMode();
return null;
}
}
private final TextViewer fTextViewer;
private final IPreferenceStore fPreferenceStore;
private final IPropertyChangeListener fPreferencePropertyListener = new IPropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent event) {
final String property = event.getProperty();
if (AbstractTextEditor.PREFERENCE_USE_CUSTOM_CARETS.equals(property)
|| AbstractTextEditor.PREFERENCE_WIDE_CARET.equals(property) ) {
updateCaret();
}
}
};
private final IPropertyChangeListener fFontPropertyListener = new IPropertyChangeListener() {
@Override
public void propertyChange(final PropertyChangeEvent event) {
final String property = event.getProperty();
if (JFaceResources.TEXT_FONT.equals(property) ) {
updateCaret();
}
}
};
/**
* Whether the overwrite mode is currently on.
*/
private boolean fIsOverwriting = false;
/**
* The non-default caret.
*/
private Caret fNonDefaultCaret;
/**
* The image used in non-default caret.
*/
private Image fNonDefaultCaretImage;
/**
* The styled text's initial caret.
*/
private Caret fInitialCaret;
public TextViewerCustomCaretSupport(final TextViewer textViewer, final IPreferenceStore preferences) {
if (textViewer == null || preferences == null) {
throw new NullPointerException();
}
fTextViewer = textViewer;
fPreferenceStore = preferences;
fTextViewer.getTextWidget().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(final DisposeEvent e) {
dispose();
}
});
fPreferenceStore.addPropertyChangeListener(fPreferencePropertyListener);
JFaceResources.getFontRegistry().addListener(fFontPropertyListener);
updateCaret();
}
public void initActions(final IHandlerService handlerService) {
fTextViewer.getTextWidget().setKeyBinding(SWT.INSERT, SWT.NULL);
handlerService.activateHandler(ITextEditorActionDefinitionIds.TOGGLE_OVERWRITE,
new ToggleOverwriteHandler());
}
private void toggleOverwriteMode() {
if (isOverwriteEnabled()) {
fIsOverwriting = !fIsOverwriting;
fTextViewer.getTextWidget().invokeAction(ST.TOGGLE_OVERWRITE);
updateCaret();
}
}
private boolean isOverwriteEnabled() {
return true;
}
private int getCaretWidthPreference() {
return (fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_WIDE_CARET)) ?
WIDE_CARET_WIDTH : SINGLE_CARET_WIDTH;
}
private void updateCaret() {
if (fTextViewer == null) {
return;
}
final StyledText styledText = fTextViewer.getTextWidget();
final InsertMode mode = ITextEditorExtension3.SMART_INSERT;
styledText.setCaret(null);
disposeNonDefaultCaret();
if (!fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_USE_CUSTOM_CARETS)) {
assert (fNonDefaultCaret == null);
}
else if (fIsOverwriting) {
fNonDefaultCaret = createOverwriteCaret(styledText);
}
else if (mode == ITextEditorExtension3.SMART_INSERT) {
fNonDefaultCaret = createInsertCaret(styledText);
}
else if (mode == ITextEditorExtension3.INSERT) {
fNonDefaultCaret = createRawInsertModeCaret(styledText);
}
if (fNonDefaultCaret != null) {
styledText.setCaret(fNonDefaultCaret);
fNonDefaultCaretImage= fNonDefaultCaret.getImage();
}
else if (fInitialCaret != styledText.getCaret()) {
styledText.setCaret(fInitialCaret);
}
}
private Caret createInsertCaret(final StyledText styledText) {
final Caret caret = new Caret(styledText, SWT.NULL);
caret.setSize(getCaretWidthPreference(), styledText.getLineHeight());
caret.setFont(styledText.getFont());
return caret;
}
private Caret createRawInsertModeCaret(final StyledText styledText) {
// // don't draw special raw caret if no smart mode is enabled
// if (!getLegalInsertModes().contains(SMART_INSERT)) {
// return createInsertCaret(styledText);
// }
final Caret caret = new Caret(styledText, SWT.NULL);
final Image image = createRawInsertModeCaretImage(styledText);
if (image != null) {
caret.setImage(image);
}
else {
caret.setSize(getCaretWidthPreference(), styledText.getLineHeight());
}
caret.setFont(styledText.getFont());
return caret;
}
private Image createRawInsertModeCaretImage(final StyledText styledText) {
final PaletteData caretPalette = new PaletteData(new RGB[] {new RGB (0,0,0), new RGB (255,255,255)});
final int width = getCaretWidthPreference();
final int widthOffset = width - 1;
final ImageData imageData= new ImageData(4 + widthOffset, styledText.getLineHeight(), 1, caretPalette);
final Display display = styledText.getDisplay();
final Image bracketImage = new Image(display, imageData);
final GC gc = new GC (bracketImage);
gc.setForeground(display.getSystemColor(SWT.COLOR_WHITE));
gc.setLineWidth(0); // NOTE: 0 means width is 1 but with optimized performance
final int height = imageData.height / 3;
for (int i = 0; i < width ; i++) {
gc.drawLine(i, 0, i, height - 1);
gc.drawLine(i, imageData.height - height, i, imageData.height - 1);
}
gc.dispose();
return bracketImage;
}
private Caret createOverwriteCaret(final StyledText styledText) {
final Caret caret = new Caret(styledText, SWT.NULL);
final GC gc = new GC(styledText);
// this overwrite box is not proportional-font aware
// take 'a' as a medium sized character
final Point charSize= gc.stringExtent("a"); //$NON-NLS-1$
caret.setSize(charSize.x, styledText.getLineHeight());
caret.setFont(styledText.getFont());
gc.dispose();
return caret;
}
private void disposeNonDefaultCaret() {
if (fNonDefaultCaretImage != null) {
fNonDefaultCaretImage.dispose();
fNonDefaultCaretImage = null;
}
if (fNonDefaultCaret != null) {
fNonDefaultCaret.dispose();
fNonDefaultCaret = null;
}
}
private void dispose() {
fPreferenceStore.removePropertyChangeListener(fPreferencePropertyListener);
JFaceResources.getFontRegistry().removeListener(fFontPropertyListener);
disposeNonDefaultCaret();
fInitialCaret = null;
}
}