blob: f0649531f970fffe8d4ad3095b2dc2bd972bab69 [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2000, 2020 Stephan Wahlbrink and others.
#
# This program and the accompanying materials are made available under the
# terms of the Eclipse Public License 2.0 which is available at
# https://www.eclipse.org/legal/epl-2.0.
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
# IBM Corporation - org.eclipse.platform: initial implementation of AbstractTextEditor
#=============================================================================*/
package org.eclipse.statet.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();
}
this.fTextViewer= textViewer;
this.fPreferenceStore= preferences;
this.fTextViewer.getTextWidget().addDisposeListener(new DisposeListener() {
@Override
public void widgetDisposed(final DisposeEvent e) {
dispose();
}
});
this.fPreferenceStore.addPropertyChangeListener(this.fPreferencePropertyListener);
JFaceResources.getFontRegistry().addListener(this.fFontPropertyListener);
updateCaret();
}
public void initActions(final IHandlerService handlerService) {
this.fTextViewer.getTextWidget().setKeyBinding(SWT.INSERT, SWT.NULL);
handlerService.activateHandler(ITextEditorActionDefinitionIds.TOGGLE_OVERWRITE,
new ToggleOverwriteHandler());
}
private void toggleOverwriteMode() {
if (isOverwriteEnabled()) {
this.fIsOverwriting= !this.fIsOverwriting;
this.fTextViewer.getTextWidget().invokeAction(ST.TOGGLE_OVERWRITE);
updateCaret();
}
}
private boolean isOverwriteEnabled() {
return true;
}
private int getCaretWidthPreference() {
return (this.fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_WIDE_CARET)) ?
WIDE_CARET_WIDTH : SINGLE_CARET_WIDTH;
}
private void updateCaret() {
if (this.fTextViewer == null) {
return;
}
final StyledText styledText= this.fTextViewer.getTextWidget();
final InsertMode mode= ITextEditorExtension3.SMART_INSERT;
styledText.setCaret(null);
disposeNonDefaultCaret();
if (!this.fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_USE_CUSTOM_CARETS)) {
assert (this.fNonDefaultCaret == null);
}
else if (this.fIsOverwriting) {
this.fNonDefaultCaret= createOverwriteCaret(styledText);
}
else if (mode == ITextEditorExtension3.SMART_INSERT) {
this.fNonDefaultCaret= createInsertCaret(styledText);
}
else if (mode == ITextEditorExtension3.INSERT) {
this.fNonDefaultCaret= createRawInsertModeCaret(styledText);
}
if (this.fNonDefaultCaret != null) {
styledText.setCaret(this.fNonDefaultCaret);
this.fNonDefaultCaretImage= this.fNonDefaultCaret.getImage();
}
else if (this.fInitialCaret != styledText.getCaret()) {
styledText.setCaret(this.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 (this.fNonDefaultCaretImage != null) {
this.fNonDefaultCaretImage.dispose();
this.fNonDefaultCaretImage= null;
}
if (this.fNonDefaultCaret != null) {
this.fNonDefaultCaret.dispose();
this.fNonDefaultCaret= null;
}
}
private void dispose() {
this.fPreferenceStore.removePropertyChangeListener(this.fPreferencePropertyListener);
JFaceResources.getFontRegistry().removeListener(this.fFontPropertyListener);
disposeNonDefaultCaret();
this.fInitialCaret= null;
}
}