mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 22:51:17 +07:00
3845 lines
128 KiB
Java
3845 lines
128 KiB
Java
package com.intellij.openapi.editor.impl;
|
|
|
|
import com.intellij.Patches;
|
|
import com.intellij.codeInsight.hint.DocumentFragmentTooltipRenderer;
|
|
import com.intellij.codeInsight.hint.HintManager;
|
|
import com.intellij.codeInsight.hint.TooltipController;
|
|
import com.intellij.codeInsight.hint.TooltipGroup;
|
|
import com.intellij.openapi.actionSystem.ActionManager;
|
|
import com.intellij.openapi.actionSystem.DataConstants;
|
|
import com.intellij.openapi.actionSystem.DataContext;
|
|
import com.intellij.openapi.actionSystem.IdeActions;
|
|
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.util.concurrency.ThreadingAssertions;
|
|
import com.intellij.util.concurrency.ThreadingAssertions;
|
|
import com.intellij.openapi.application.ModalityState;
|
|
import com.intellij.openapi.command.CommandProcessor;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.actionSystem.EditorAction;
|
|
import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
|
|
import com.intellij.openapi.editor.actionSystem.EditorActionManager;
|
|
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
|
import com.intellij.openapi.editor.ex.util.EmptyEditorHighlighter;
|
|
import com.intellij.openapi.editor.impl.event.MarkupModelEvent;
|
|
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
|
|
import com.intellij.openapi.progress.ProgressManager;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.vfs.VirtualFile;
|
|
import com.intellij.ui.JScrollPane2;
|
|
import com.intellij.util.Alarm;
|
|
import com.intellij.util.IJSwingUtilities;
|
|
import gnu.trove.THashMap;
|
|
import gnu.trove.TIntArrayList;
|
|
import org.jdom.Element;
|
|
|
|
import javax.swing.plaf.ScrollBarUI;
|
|
import javax.swing.plaf.basic.BasicScrollBarUI;
|
|
import java.awt.dnd.DropTargetAdapter;
|
|
import java.awt.dnd.DropTargetDragEvent;
|
|
import java.awt.dnd.DropTargetDropEvent;
|
|
import java.awt.font.TextHitInfo;
|
|
import java.awt.im.InputMethodRequests;
|
|
import java.beans.PropertyChangeListener;
|
|
import java.beans.PropertyChangeSupport;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.InvocationTargetException;
|
|
import java.text.AttributedCharacterIterator;
|
|
import java.text.AttributedString;
|
|
import java.text.CharacterIterator;
|
|
|
|
public final class EditorImpl implements EditorEx {
|
|
private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.EditorImpl");
|
|
private static final Key DND_COMMAND_KEY = Key.create("DndCommand");
|
|
private final DocumentImpl myDocument;
|
|
|
|
private JPanel myPanel;
|
|
private JScrollPane myScrollPane;
|
|
private EditorComponentImpl myEditorComponent;
|
|
private EditorGutterComponentImpl myGutterComponent;
|
|
|
|
|
|
private CommandProcessor myCommandProcessor;
|
|
private MyScrollBar myVerticalScrollBar;
|
|
private MyScrollBar myHorizontalScrollBar;
|
|
|
|
private ArrayList<EditorMouseListener> myMouseListeners = new ArrayList<EditorMouseListener>();
|
|
private ArrayList<EditorMouseMotionListener> myMouseMotionListeners = new ArrayList<EditorMouseMotionListener>();
|
|
|
|
private int myCharHeight = -1;
|
|
private int myLineHeight = -1;
|
|
private int myDescent = -1;
|
|
|
|
private boolean myIsInsertMode = true;
|
|
|
|
private final CaretCursor myCaretCursor;
|
|
private final ScrollingTimer myScrollingTimer = new ScrollingTimer();
|
|
|
|
private final Key<Object> MOUSE_DRAGGED_GROUP = Key.create("MouseDraggedGroup");
|
|
private final Map<Object, Object> myUserDataMap = new THashMap<Object, Object>();
|
|
|
|
private final DocumentListener myEditorDocumentAdapter;
|
|
|
|
private SettingsImpl mySettings;
|
|
|
|
private boolean isReleased = false;
|
|
|
|
private MouseEvent myMousePressedEvent = null;
|
|
|
|
private int mySavedSelectionStart = -1;
|
|
private int mySavedSelectionEnd = -1;
|
|
private int myLastColumnNumber = 0;
|
|
|
|
private final PropertyChangeSupport myPropertyChangeSupport = new PropertyChangeSupport(this);
|
|
private MyEditable myEditable;
|
|
|
|
private EditorColorsScheme myScheme;
|
|
private final boolean myIsViewer;
|
|
private final SelectionModelImpl mySelectionModel;
|
|
private final EditorMarkupModelImpl myMarkupModel;
|
|
private final FoldingModelImpl myFoldingModel;
|
|
private final ScrollingModelImpl myScrollingModel;
|
|
private final CaretModelImpl myCaretModel;
|
|
|
|
private static final RepaintCursorThread ourCaretThread;
|
|
|
|
// private final BorderEffect myBorderEffect = new BorderEffect();
|
|
|
|
private int myMouseSelectionState = MOUSE_SELECTION_STATE_NONE;
|
|
private FoldRegion myMouseSelectedRegion = null;
|
|
|
|
private static final int MOUSE_SELECTION_STATE_NONE = 0;
|
|
private static final int MOUSE_SELECTION_STATE_WORD_SELECTED = 1;
|
|
private static final int MOUSE_SELECTION_STATE_LINE_SELECTED = 2;
|
|
|
|
private final MarkupModelListener myMarkupModelListener;
|
|
|
|
private EditorHighlighter myHighlighter;
|
|
|
|
private int myScrollbarOrientation;
|
|
private boolean myMousePressedInsideSelection;
|
|
private FontMetrics myPlainFontMetrics;
|
|
private FontMetrics myBoldFontMetrics;
|
|
private FontMetrics myItalicFontMetrics;
|
|
private FontMetrics myBoldItalicFontMetrics;
|
|
|
|
private static final int CACHED_CHARS_BUFFER_SIZE = 300;
|
|
|
|
private ArrayList<CachedFontContent> myFontCache = null;
|
|
private FontInfo myCurrentFontType = null;
|
|
|
|
private boolean myIsBlockSelectionMode;
|
|
private EditorSizeContainer mySizeContainer = new EditorSizeContainer();
|
|
|
|
private Runnable myCursorUpdater;
|
|
private int myCaretUpdateVShift;
|
|
Project myProject;
|
|
private long myMouseSelectionChangeTimestamp;
|
|
private int mySavedCaretOffsetForDNDUndoHack;
|
|
private ArrayList<FocusChangeListener> myFocusListeners = new ArrayList<FocusChangeListener>();
|
|
|
|
private MyInputMethodHandler myInputMethodRequestsHandler;
|
|
private InputMethodRequests myInputMethodRequestsSwingWrapper;
|
|
private boolean myIsOneLineMode;
|
|
private boolean myIsRendererMode;
|
|
private VirtualFile myVirtualFile;
|
|
private boolean myIsColumnMode = false;
|
|
private Color myForcedBackground = null;
|
|
private Dimension myPreferredSize;
|
|
private Runnable myGutterSizeUpdater = null;
|
|
private Alarm myAppleRepaintAlarm;
|
|
private boolean myEmbeddedIntoDialogWrapper;
|
|
|
|
static {
|
|
ourCaretThread = new RepaintCursorThread();
|
|
ourCaretThread.start();
|
|
}
|
|
|
|
|
|
public EditorImpl(Document document, boolean viewer, Project project) {
|
|
myProject = project;
|
|
myDocument = (DocumentImpl)document;
|
|
myScheme = new MyColorSchemeDelegate();
|
|
myIsViewer = viewer;
|
|
mySettings = new SettingsImpl(this);
|
|
|
|
mySelectionModel = new SelectionModelImpl(this);
|
|
myMarkupModel = new EditorMarkupModelImpl(this);
|
|
myFoldingModel = new FoldingModelImpl(this);
|
|
myCaretModel = new CaretModelImpl(this);
|
|
myIsBlockSelectionMode = false;
|
|
mySizeContainer.reset();
|
|
|
|
myCommandProcessor = CommandProcessor.getInstance();
|
|
|
|
myEditorDocumentAdapter = new EditorDocumentAdapter();
|
|
|
|
myMarkupModelListener = new MarkupModelListener() {
|
|
public void rangeHighlighterChanged(MarkupModelEvent event) {
|
|
RangeHighlighterImpl rangeHighlighter = (RangeHighlighterImpl)event.getHighlighter();
|
|
if (rangeHighlighter.isValid()) {
|
|
int start = rangeHighlighter.getAffectedAreaStartOffset();
|
|
int end = rangeHighlighter.getAffectedAreaEndOffset();
|
|
int startLine = myDocument.getLineNumber(start);
|
|
int endLine = myDocument.getLineNumber(end);
|
|
repaintLines(Math.max(0, startLine - 1), Math.min(endLine + 1, getDocument().getLineCount()));
|
|
}
|
|
else {
|
|
repaint(0, getDocument().getTextLength());
|
|
}
|
|
((EditorMarkupModelImpl)getMarkupModel()).repaint();
|
|
((EditorMarkupModelImpl)getMarkupModel()).markDirtied();
|
|
GutterIconRenderer renderer = rangeHighlighter.getGutterIconRenderer();
|
|
if (renderer != null) {
|
|
updateGutterSize();
|
|
}
|
|
updateCaretCursor();
|
|
}
|
|
};
|
|
|
|
((MarkupModelImpl)myDocument.getMarkupModel(myProject)).addMarkupModelListener(myMarkupModelListener);
|
|
((MarkupModelImpl)getMarkupModel()).addMarkupModelListener(myMarkupModelListener);
|
|
|
|
myDocument.addDocumentListener(myFoldingModel);
|
|
myDocument.addDocumentListener(myCaretModel);
|
|
myDocument.addDocumentListener(mySelectionModel);
|
|
myDocument.addDocumentListener(myEditorDocumentAdapter);
|
|
|
|
myCaretCursor = new CaretCursor();
|
|
|
|
myFoldingModel.flushCaretShift();
|
|
myScrollbarOrientation = EditorEx.VERTICAL_SCROLLBAR_RIGHT;
|
|
|
|
EditorHighlighter highlighter = new EmptyEditorHighlighter(myScheme.getAttributes(HighlighterColors.TEXT));
|
|
setHighlighter(highlighter);
|
|
|
|
initComponent();
|
|
|
|
myScrollingModel = new ScrollingModelImpl(this);
|
|
|
|
myGutterComponent.updateSize();
|
|
myEditorComponent.setSize(getContentSize());
|
|
|
|
Dimension preferredSize = getPreferredSize();
|
|
myScrollPane.getViewport().setViewSize(preferredSize);
|
|
|
|
if (Patches.APPLE_BUG_ID_3716835) {
|
|
myScrollingModel.addVisibleAreaListener(new VisibleAreaListener() {
|
|
public void visibleAreaChanged(VisibleAreaEvent e) {
|
|
if (myAppleRepaintAlarm == null) {
|
|
myAppleRepaintAlarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD);
|
|
}
|
|
myAppleRepaintAlarm.cancelAllRequests();
|
|
myAppleRepaintAlarm.addRequest(new Runnable() {
|
|
public void run() {
|
|
repaint(0, myDocument.getTextLength());
|
|
}
|
|
}, 50, ModalityState.stateForComponent(myEditorComponent));
|
|
}
|
|
});
|
|
}
|
|
|
|
updateCaretCursor();
|
|
|
|
// This hacks context layout problem where editor appears scrolled to the right just after it is created.
|
|
SwingUtilities.invokeLater(new Runnable() {
|
|
public void run() {
|
|
myScrollingModel.disableAnimation();
|
|
myScrollingModel.scrollHorizontally(0);
|
|
myScrollingModel.enableAnimation();
|
|
}
|
|
});
|
|
}
|
|
|
|
public boolean isViewer() {
|
|
return myIsViewer || myIsRendererMode;
|
|
}
|
|
|
|
public boolean isRendererMode() {
|
|
return myIsRendererMode;
|
|
}
|
|
|
|
public void setRendererMode(boolean isRendererMode) {
|
|
myIsRendererMode = isRendererMode;
|
|
}
|
|
|
|
public void setFile(VirtualFile vFile) {
|
|
myVirtualFile = vFile;
|
|
reinitSettings();
|
|
}
|
|
|
|
public VirtualFile getVirtualFile() {
|
|
return myVirtualFile;
|
|
}
|
|
|
|
public SelectionModel getSelectionModel() {
|
|
return mySelectionModel;
|
|
}
|
|
|
|
public MarkupModel getMarkupModel() {
|
|
return myMarkupModel;
|
|
}
|
|
|
|
public FoldingModel getFoldingModel() {
|
|
return myFoldingModel;
|
|
}
|
|
|
|
public CaretModel getCaretModel() {
|
|
return myCaretModel;
|
|
}
|
|
|
|
public ScrollingModel getScrollingModel() {
|
|
return myScrollingModel;
|
|
}
|
|
|
|
public EditorSettings getSettings() {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
return mySettings;
|
|
}
|
|
|
|
public void reinitSettings() {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
myCharHeight = -1;
|
|
myLineHeight = -1;
|
|
myDescent = -1;
|
|
myPlainFontMetrics = null;
|
|
|
|
myCaretModel.reinitSettings();
|
|
mySelectionModel.reinitSettings();
|
|
mySettings.reinitSettings();
|
|
ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
|
|
ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
|
|
mySizeContainer.reset();
|
|
myFoldingModel.refreshSettings();
|
|
myFoldingModel.rebuild();
|
|
|
|
myHighlighter.setColorScheme(myScheme);
|
|
|
|
myGutterComponent.reinitSettings();
|
|
myGutterComponent.revalidate();
|
|
|
|
myEditorComponent.repaint();
|
|
|
|
updateCaretCursor();
|
|
}
|
|
|
|
public void release() {
|
|
isReleased = true;
|
|
myDocument.removeDocumentListener(myHighlighter);
|
|
myDocument.removeDocumentListener(myEditorDocumentAdapter);
|
|
myDocument.removeDocumentListener(myFoldingModel);
|
|
myDocument.removeDocumentListener(myCaretModel);
|
|
myDocument.removeDocumentListener(mySelectionModel);
|
|
MarkupModelImpl markupModel = (MarkupModelImpl)myDocument.getMarkupModel(myProject, false);
|
|
if (markupModel != null) markupModel.removeMarkupModelListener(myMarkupModelListener);
|
|
myMarkupModel.dispose();
|
|
myLineHeight = -1;
|
|
myCharHeight = -1;
|
|
myDescent = -1;
|
|
myPlainFontMetrics = null;
|
|
myScrollingModel.dispose();
|
|
myGutterComponent.dispose();
|
|
}
|
|
|
|
public <T> void putUserData(Key<T> key, T value) {
|
|
if (value != null) {
|
|
myUserDataMap.put(key, value);
|
|
}
|
|
else {
|
|
myUserDataMap.remove(key);
|
|
}
|
|
}
|
|
|
|
public <T> T getUserData(Key<T> key) {
|
|
return (T)myUserDataMap.get(key);
|
|
}
|
|
|
|
private void initComponent() {
|
|
myEditorComponent = new EditorComponentImpl(this);
|
|
// myStatusBar = new EditorStatusBarImpl();
|
|
|
|
myScrollPane = new JScrollPane2() {
|
|
protected void processMouseWheelEvent(MouseWheelEvent e) {
|
|
if (mySettings.isWheelFontChangeEnabled()) {
|
|
boolean changeFontSize = SystemInfo.isMac
|
|
? !e.isControlDown() && e.isMetaDown() && !e.isAltDown() && !e.isShiftDown()
|
|
: e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown();
|
|
if (changeFontSize) {
|
|
setFontSize(myScheme.getEditorFontSize() + e.getWheelRotation());
|
|
return;
|
|
}
|
|
}
|
|
|
|
super.processMouseWheelEvent(e);
|
|
}
|
|
};
|
|
myPanel = new JPanel() {
|
|
public void addNotify() {
|
|
super.addNotify();
|
|
if (((JComponent)getParent()).getBorder() != null) myScrollPane.setBorder(null);
|
|
}
|
|
};
|
|
|
|
//myPanel.setLayout(new BoxLayout(myPanel, BoxLayout.Y_AXIS));
|
|
myPanel.setLayout(new BorderLayout());
|
|
|
|
myVerticalScrollBar = new MyScrollBar(JScrollBar.VERTICAL);
|
|
myHorizontalScrollBar = new MyScrollBar(JScrollBar.HORIZONTAL);
|
|
|
|
myGutterComponent = new EditorGutterComponentImpl(this);
|
|
myGutterComponent.setOpaque(true);
|
|
|
|
myScrollPane.setVerticalScrollBar(myVerticalScrollBar);
|
|
myScrollPane.setHorizontalScrollBar(myHorizontalScrollBar);
|
|
myScrollPane.setViewportView(myEditorComponent);
|
|
//myScrollPane.setBorder(null);
|
|
myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
|
myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
|
|
|
|
|
myScrollPane.setRowHeaderView(myGutterComponent);
|
|
stopOptimizedScrolling();
|
|
|
|
myEditorComponent.setTransferHandler(new MyTransferHandler());
|
|
myEditorComponent.setAutoscrolls(true);
|
|
|
|
/* Default mode till 1.4.0
|
|
* myScrollPane.getViewport().setScrollMode(JViewport.BLIT_SCROLL_MODE);
|
|
*/
|
|
myPanel.add(myScrollPane);
|
|
|
|
myEditorComponent.addKeyListener(new KeyAdapter() {
|
|
public void keyTyped(KeyEvent event) {
|
|
if (Patches.APPLE_BUG_ID_3337563) return; // Everything is going through InputMethods under MacOS X in JDK releases earlier than 1.4.2_03-117.1
|
|
if (event.isConsumed()) {
|
|
return;
|
|
}
|
|
if (processKeyTyped(event)) {
|
|
event.consume();
|
|
}
|
|
}
|
|
});
|
|
|
|
MyMouseAdapter mouseAdapter = new MyMouseAdapter();
|
|
myEditorComponent.addMouseListener(mouseAdapter);
|
|
myGutterComponent.addMouseListener(mouseAdapter);
|
|
|
|
MyMouseMotionListener mouseMotionListener = new MyMouseMotionListener();
|
|
myEditorComponent.addMouseMotionListener(mouseMotionListener);
|
|
myGutterComponent.addMouseMotionListener(mouseMotionListener);
|
|
|
|
myEditorComponent.addFocusListener(new FocusAdapter() {
|
|
public void focusGained(FocusEvent e) {
|
|
myCaretCursor.activate();
|
|
int caretLine = getCaretModel().getLogicalPosition().line;
|
|
repaintLines(caretLine, caretLine);
|
|
fireFocusGained();
|
|
}
|
|
|
|
public void focusLost(FocusEvent e) {
|
|
synchronized (ourCaretThread) {
|
|
if (ourCaretThread.myEditor == EditorImpl.this) {
|
|
ourCaretThread.myEditor = null;
|
|
}
|
|
}
|
|
int caretLine = getCaretModel().getLogicalPosition().line;
|
|
repaintLines(caretLine, caretLine);
|
|
fireFocusLost();
|
|
}
|
|
});
|
|
|
|
// myBorderEffect.reset();
|
|
|
|
try {
|
|
myEditorComponent.getDropTarget().addDropTargetListener(new DropTargetAdapter() {
|
|
public void drop(DropTargetDropEvent dtde) {
|
|
}
|
|
|
|
public void dragOver(DropTargetDragEvent dtde) {
|
|
Point location = dtde.getLocation();
|
|
|
|
moveCaretToScreenPos(location.x, location.y);
|
|
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
|
}
|
|
});
|
|
}
|
|
catch (TooManyListenersException e) {
|
|
}
|
|
}
|
|
|
|
public void setFontSize(final int fontSize) {
|
|
int oldFontSize = myScheme.getEditorFontSize();
|
|
myScheme.setEditorFontSize(fontSize);
|
|
myPropertyChangeSupport.firePropertyChange(EditorEx.PROP_FONT_SIZE, oldFontSize, fontSize);
|
|
}
|
|
|
|
private void processKeyTyped(char c) {
|
|
// [vova] This is patch for Mac OS X. Under Mac "input methods"
|
|
// is handled before our EventQueue consume upcoming KeyEvents.
|
|
IdeEventQueue queue = IdeEventQueue.getInstance();
|
|
if (queue.isWaitingForSecondKeyStroke() || ProgressManager.getInstance().hasModalProgressIndicator()) {
|
|
return;
|
|
}
|
|
ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
|
|
DataContext dataContext = getDataContext();
|
|
actionManager.fireBeforeEditorTyping(c, dataContext);
|
|
EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
|
|
}
|
|
|
|
private void fireFocusLost() {
|
|
FocusChangeListener[] listeners = getFocusListeners();
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
FocusChangeListener listener = listeners[i];
|
|
listener.focusLost(this);
|
|
}
|
|
}
|
|
|
|
private FocusChangeListener[] getFocusListeners() {
|
|
return myFocusListeners.toArray(new FocusChangeListener[myFocusListeners.size()]);
|
|
}
|
|
|
|
private void fireFocusGained() {
|
|
FocusChangeListener[] listeners = getFocusListeners();
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
FocusChangeListener listener = listeners[i];
|
|
listener.focusGained(this);
|
|
}
|
|
}
|
|
|
|
public void setHighlighter(EditorHighlighter highlighter) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
if (myHighlighter != null) {
|
|
getDocument().removeDocumentListener(myHighlighter);
|
|
}
|
|
|
|
getDocument().addDocumentListener(highlighter);
|
|
highlighter.setText(getDocument().getCharsSequence());
|
|
myHighlighter = highlighter;
|
|
myHighlighter.setEditor(this);
|
|
if (myPanel != null) {
|
|
reinitSettings();
|
|
}
|
|
}
|
|
|
|
public EditorHighlighter getHighlighter() {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
return myHighlighter;
|
|
}
|
|
|
|
public JComponent getContentComponent() {
|
|
return myEditorComponent;
|
|
}
|
|
|
|
public EditorGutterComponentEx getGutterComponentEx() {
|
|
return myGutterComponent;
|
|
}
|
|
|
|
public void addPropertyChangeListener(PropertyChangeListener listener) {
|
|
myPropertyChangeSupport.addPropertyChangeListener(listener);
|
|
}
|
|
|
|
public void removePropertyChangeListener(PropertyChangeListener listener) {
|
|
myPropertyChangeSupport.removePropertyChangeListener(listener);
|
|
}
|
|
|
|
public void setInsertMode(boolean mode) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
boolean oldValue = myIsInsertMode;
|
|
myIsInsertMode = mode;
|
|
myPropertyChangeSupport.firePropertyChange(EditorEx.PROP_INSERT_MODE, oldValue, mode);
|
|
//Repaint the caret line by moving caret to the same place
|
|
LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
|
|
getCaretModel().moveToLogicalPosition(caretPosition);
|
|
}
|
|
|
|
public boolean isInsertMode() {
|
|
return myIsInsertMode;
|
|
}
|
|
|
|
public void setColumnMode(boolean mode) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
boolean oldValue = myIsColumnMode;
|
|
myIsColumnMode = mode;
|
|
myPropertyChangeSupport.firePropertyChange(PROP_COLUMN_MODE, oldValue, mode);
|
|
}
|
|
|
|
public boolean isColumnMode() {
|
|
return myIsColumnMode;
|
|
}
|
|
|
|
public void setBlockSelectionMode(boolean isBlockSelectionMode) {
|
|
myIsBlockSelectionMode = isBlockSelectionMode;
|
|
}
|
|
|
|
public boolean isBlockSelectionMode() {
|
|
return myIsBlockSelectionMode;
|
|
}
|
|
|
|
private int yPositionToVisibleLineNumber(int y) {
|
|
return y / getLineHeight();
|
|
}
|
|
|
|
public int getSpaceWidth(int fontType) {
|
|
int width = charWidth(' ', fontType);
|
|
return width > 0 ? width : 1;
|
|
}
|
|
|
|
public VisualPosition xyToVisualPosition(Point p) {
|
|
int line = yPositionToVisibleLineNumber(p.y);
|
|
|
|
int x = 0;
|
|
int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
|
|
int textLength = myDocument.getTextLength();
|
|
|
|
if (offset >= textLength) return new VisualPosition(line, 0);
|
|
|
|
int column = 0;
|
|
int prevX = 0;
|
|
CharSequence text = myDocument.getCharsNoThreadCheck();
|
|
char c = ' ';
|
|
IterationState state = new IterationState(this, offset, false);
|
|
|
|
int fontType = state.getMergedAttributes().getFontType();
|
|
int spaceSize = getSpaceWidth(fontType);
|
|
|
|
outer:
|
|
while (true) {
|
|
if (offset >= textLength) break;
|
|
|
|
if (offset >= state.getEndOffset()) {
|
|
state.advance();
|
|
fontType = state.getMergedAttributes().getFontType();
|
|
}
|
|
|
|
FoldRegion region = state.getCurrentFold();
|
|
if (region != null) {
|
|
char[] placeholder = region.getPlaceholderText().toCharArray();
|
|
for (int j = 0; j < placeholder.length; j++) {
|
|
c = placeholder[j];
|
|
x += charWidth(c, fontType);
|
|
if (x >= p.x) break outer;
|
|
column++;
|
|
}
|
|
offset = region.getEndOffset();
|
|
}
|
|
else {
|
|
prevX = x;
|
|
c = text.charAt(offset);
|
|
if (c == '\n') {
|
|
break;
|
|
}
|
|
if (c == '\t') {
|
|
x = nextTabStop(x);
|
|
}
|
|
else {
|
|
x += charWidth(c, fontType);
|
|
}
|
|
|
|
if (x >= p.x) break;
|
|
|
|
if (c == '\t') {
|
|
column += (x - prevX) / spaceSize;
|
|
}
|
|
else {
|
|
column++;
|
|
}
|
|
|
|
offset++;
|
|
}
|
|
}
|
|
|
|
int charWidth = charWidth(c, fontType);
|
|
|
|
if (x >= p.x && c == '\t') {
|
|
if (mySettings.isCaretInsideTabs()) {
|
|
column += (p.x - prevX) / spaceSize;
|
|
if ((p.x - prevX) % spaceSize > spaceSize / 2) column++;
|
|
}
|
|
else {
|
|
if ((x - p.x) * 2 < x - prevX) {
|
|
column += (x - prevX) / spaceSize;
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
if (x >= p.x) {
|
|
if ((x - p.x) * 2 < charWidth) column++;
|
|
}
|
|
else {
|
|
column += (p.x - x) / getSpaceWidth(fontType);
|
|
}
|
|
}
|
|
|
|
return new VisualPosition(line, column);
|
|
}
|
|
|
|
public VisualPosition offsetToVisualPosition(int offset) {
|
|
return logicalToVisualPosition(offsetToLogicalPosition(offset));
|
|
}
|
|
|
|
public LogicalPosition offsetToLogicalPosition(int offset) {
|
|
int line = calcLogicalLineNumber(offset);
|
|
int column = calcColumnNumber(offset, line);
|
|
return new LogicalPosition(line, column);
|
|
}
|
|
|
|
public LogicalPosition xyToLogicalPosition(Point p) {
|
|
final Point pp;
|
|
if (p.x >= 0 && p.y >= 0) {
|
|
pp = p;
|
|
}
|
|
else {
|
|
pp = new Point(Math.max(p.x, 0), Math.max(p.y, 0));
|
|
}
|
|
|
|
return visualToLogicalPosition(xyToVisualPosition(pp));
|
|
}
|
|
|
|
public Point logicalPositionToXY(LogicalPosition pos) {
|
|
VisualPosition visible = logicalToVisualPosition(pos);
|
|
int y = visibleLineNumberToYPosition(visible.line);
|
|
|
|
int lineStartOffset;
|
|
|
|
if (pos.line == 0) {
|
|
lineStartOffset = 0;
|
|
}
|
|
else {
|
|
if (pos.line >= myDocument.getLineCount()) {
|
|
lineStartOffset = myDocument.getTextLength();
|
|
}
|
|
else {
|
|
lineStartOffset = myDocument.getLineStartOffset(pos.line);
|
|
}
|
|
}
|
|
|
|
int x = getTabbedTextWidth(lineStartOffset, visible);
|
|
return new Point(x, y);
|
|
}
|
|
|
|
public Point visualPositionToXY(VisualPosition visible) {
|
|
int y = visibleLineNumberToYPosition(visible.line);
|
|
int logLine = visualToLogicalPosition(new VisualPosition(visible.line, 0)).line;
|
|
|
|
int lineStartOffset;
|
|
|
|
if (logLine == 0) {
|
|
lineStartOffset = 0;
|
|
}
|
|
else {
|
|
if (logLine >= myDocument.getLineCount()) {
|
|
lineStartOffset = myDocument.getTextLength();
|
|
}
|
|
else {
|
|
lineStartOffset = myDocument.getLineStartOffset(logLine);
|
|
}
|
|
}
|
|
|
|
int x = getTabbedTextWidth(lineStartOffset, visible);
|
|
return new Point(x, y);
|
|
}
|
|
|
|
private int getTabbedTextWidth(int lineStartOffset, VisualPosition pos) {
|
|
if (pos.column == 0) return 0;
|
|
|
|
int x = 0;
|
|
int offset = lineStartOffset;
|
|
CharSequence text = myDocument.getCharsNoThreadCheck();
|
|
int textLength = myDocument.getTextLength();
|
|
IterationState state = new IterationState(this, offset, false);
|
|
int fontType = state.getMergedAttributes().getFontType();
|
|
int spaceSize = getSpaceWidth(fontType);
|
|
|
|
int column = 0;
|
|
outer:
|
|
while (column < pos.column) {
|
|
if (offset >= textLength) break;
|
|
|
|
if (offset >= state.getEndOffset()) {
|
|
state.advance();
|
|
fontType = state.getMergedAttributes().getFontType();
|
|
}
|
|
|
|
FoldRegion region = state.getCurrentFold();
|
|
|
|
if (region != null) {
|
|
char[] placeholder = region.getPlaceholderText().toCharArray();
|
|
for (int j = 0; j < placeholder.length; j++) {
|
|
x += charWidth(placeholder[j], fontType);
|
|
column++;
|
|
if (column >= pos.column) break outer;
|
|
}
|
|
offset = region.getEndOffset();
|
|
}
|
|
else {
|
|
char c = text.charAt(offset);
|
|
if (c == '\n') {
|
|
break;
|
|
}
|
|
if (c == '\t') {
|
|
int prevX = x;
|
|
x = nextTabStop(x);
|
|
column += (x - prevX) / spaceSize;
|
|
}
|
|
else {
|
|
x += charWidth(c, fontType);
|
|
column++;
|
|
}
|
|
offset++;
|
|
}
|
|
}
|
|
|
|
if (column != pos.column) {
|
|
x += getSpaceWidth(fontType) * (pos.column - column);
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
public int nextTabStop(int x) {
|
|
int tabSize = mySettings.getTabSize(myProject);
|
|
if (tabSize <= 0) {
|
|
tabSize = 1;
|
|
}
|
|
|
|
tabSize *= getSpaceWidth(Font.PLAIN);
|
|
|
|
int nTabs = x / tabSize;
|
|
return (nTabs + 1) * tabSize;
|
|
}
|
|
|
|
public int visibleLineNumberToYPosition(int lineNum) {
|
|
if (lineNum < 0) throw new IndexOutOfBoundsException("Wrong line: " + lineNum);
|
|
return lineNum * getLineHeight();
|
|
}
|
|
|
|
public void repaint(int startOffset, int endOffset) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
if (myScrollPane == null) {
|
|
return;
|
|
}
|
|
if (endOffset > myDocument.getTextLength()) {
|
|
endOffset = myDocument.getTextLength();
|
|
}
|
|
if (startOffset < endOffset) {
|
|
int startLine = myDocument.getLineNumber(startOffset);
|
|
int endLine = myDocument.getLineNumber(endOffset);
|
|
repaintLines(startLine, endLine);
|
|
}
|
|
}
|
|
|
|
public void repaintLines(int startLine, int endLine) {
|
|
Rectangle visibleRect = getScrollingModel().getVisibleArea();
|
|
int yStartLine = logicalPositionToXY(new LogicalPosition(startLine, 0)).y;
|
|
int yEndLine = logicalPositionToXY(new LogicalPosition(endLine, 0)).y + getLineHeight() + WAVE_HEIGHT;
|
|
|
|
myEditorComponent.repaintEditorComponent(visibleRect.x,
|
|
yStartLine,
|
|
visibleRect.x + visibleRect.width,
|
|
yEndLine - yStartLine);
|
|
myGutterComponent.repaint(0, yStartLine, myGutterComponent.getWidth(), yEndLine - yStartLine);
|
|
}
|
|
|
|
private void beforeChangedUpdate(DocumentEvent e) {
|
|
Rectangle viewRect = getScrollingModel().getVisibleArea();
|
|
Point pos = visualPositionToXY(getCaretModel().getVisualPosition());
|
|
myCaretUpdateVShift = pos.y - viewRect.y;
|
|
mySizeContainer.beforeChange(e);
|
|
}
|
|
|
|
private void changedUpdate(DocumentEvent e) {
|
|
if (myScrollPane == null) return;
|
|
|
|
stopOptimizedScrolling();
|
|
mySelectionModel.removeBlockSelection();
|
|
|
|
mySizeContainer.changedUpdate(e);
|
|
int startVisualLine = offsetToVisualPosition(e.getOffset()).line;
|
|
int endVisualLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
|
|
|
|
if (myDocument.getTextLength() > 0) {
|
|
int startDocLine = myDocument.getLineNumber(e.getOffset());
|
|
int endDocLine = myDocument.getLineNumber(e.getOffset() + e.getNewLength());
|
|
if (e.getOldLength() > e.getNewLength() || startDocLine != endDocLine) {
|
|
updateGutterSize();
|
|
}
|
|
}
|
|
|
|
updateCaretCursor();
|
|
repaintLines(startVisualLine, endVisualLine);
|
|
|
|
Point caretLocation = visualPositionToXY(getCaretModel().getVisualPosition());
|
|
int scrollOffset = caretLocation.y - myCaretUpdateVShift;
|
|
getScrollingModel().scrollVertically(scrollOffset);
|
|
}
|
|
|
|
private void updateGutterSize() {
|
|
if (myGutterSizeUpdater != null) return;
|
|
myGutterSizeUpdater = new Runnable() {
|
|
public void run() {
|
|
myGutterComponent.updateSize();
|
|
myGutterSizeUpdater = null;
|
|
}
|
|
};
|
|
}
|
|
|
|
void validateSize() {
|
|
if (myGutterSizeUpdater != null) {
|
|
SwingUtilities.invokeLater(myGutterSizeUpdater);
|
|
}
|
|
|
|
Dimension dim = getPreferredSize();
|
|
|
|
if (!dim.equals(myPreferredSize)) {
|
|
myPreferredSize = dim;
|
|
|
|
stopOptimizedScrolling();
|
|
myScrollPane.getViewport().setViewSize(myPreferredSize);
|
|
int lineNum = Math.max(1, getDocument().getLineCount());
|
|
|
|
myGutterComponent.setLineNumberAreaWidth(getFontMetrics(Font.PLAIN).stringWidth(Integer.toString(lineNum + 2)) + 6);
|
|
final JViewport rowHeader = myScrollPane.getRowHeader();
|
|
if (rowHeader != null) {
|
|
rowHeader.setViewSize(myGutterComponent.getPreferredSize());
|
|
}
|
|
|
|
getScrollPane().revalidate();
|
|
|
|
myEditorComponent.repaint();
|
|
myScrollPane.repaint();
|
|
myMarkupModel.repaint();
|
|
}
|
|
}
|
|
|
|
void recalcSizeAndRepaint() {
|
|
mySizeContainer.reset();
|
|
|
|
validateSize();
|
|
|
|
Dimension size = getPreferredSize();
|
|
|
|
myScrollPane.getVerticalScrollBar().setMaximum(size.height);
|
|
myScrollPane.getHorizontalScrollBar().setMaximum(size.width);
|
|
|
|
getScrollPane().revalidate();
|
|
myMarkupModel.repaint();
|
|
|
|
stopOptimizedScrolling();
|
|
myEditorComponent.repaintEditorComponent();
|
|
|
|
myGutterComponent.repaint();
|
|
}
|
|
|
|
public Document getDocument() {
|
|
return myDocument;
|
|
}
|
|
|
|
public JComponent getComponent() {
|
|
return myPanel;
|
|
}
|
|
|
|
public void addEditorMouseListener(EditorMouseListener listener) {
|
|
myMouseListeners.add(listener);
|
|
}
|
|
|
|
public void removeEditorMouseListener(EditorMouseListener listener) {
|
|
boolean success = myMouseListeners.remove(listener);
|
|
LOG.assertTrue(success);
|
|
}
|
|
|
|
public void addEditorMouseMotionListener(EditorMouseMotionListener listener) {
|
|
myMouseMotionListeners.add(listener);
|
|
}
|
|
|
|
public void removeEditorMouseMotionListener(EditorMouseMotionListener listener) {
|
|
boolean success = myMouseMotionListeners.remove(listener);
|
|
LOG.assertTrue(success);
|
|
}
|
|
|
|
public boolean isDisposed() {
|
|
return isReleased;
|
|
}
|
|
|
|
public void paint(Graphics g) {
|
|
validateSize();
|
|
startOptimizedScrolling();
|
|
|
|
if (myCursorUpdater != null) {
|
|
myCursorUpdater.run();
|
|
myCursorUpdater = null;
|
|
}
|
|
|
|
Rectangle clip = getClipBounds(g);
|
|
|
|
if (clip == null) {
|
|
return;
|
|
}
|
|
|
|
Rectangle viewRect = getScrollingModel().getVisibleArea();
|
|
if (viewRect == null) {
|
|
return;
|
|
}
|
|
|
|
if (isReleased) {
|
|
g.setColor(new Color(128, 255, 128));
|
|
g.fillRect(clip.x, clip.y, clip.width, clip.height);
|
|
|
|
return;
|
|
}
|
|
|
|
Color background = getBackroundColor();
|
|
g.setColor(background);
|
|
g.fillRect(clip.x, clip.y, clip.width, clip.height);
|
|
|
|
paintBackgrounds(g, clip);
|
|
paintRectangularSelection(g);
|
|
paintRightMargin(g, clip);
|
|
final MarkupModel docMarkup = myDocument.getMarkupModel(myProject);
|
|
if (docMarkup != null) {
|
|
paintLineMarkersSeparators(g, clip, docMarkup);
|
|
}
|
|
paintLineMarkersSeparators(g, clip, myMarkupModel);
|
|
paintText(g, clip);
|
|
paintSegmentHighlightersBorderAndAfterEndOfLine(g, clip);
|
|
BorderEffect borderEffect = new BorderEffect(this, g);
|
|
borderEffect.paintHighlighters(getHighlighter());
|
|
if (docMarkup != null) {
|
|
borderEffect.paintHighlighters(docMarkup.getAllHighlighters());
|
|
}
|
|
borderEffect.paintHighlighters((getMarkupModel()).getAllHighlighters());
|
|
paintCaretCursor(g);
|
|
|
|
paintComposedTextDecoration((Graphics2D)g);
|
|
}
|
|
|
|
public void setBackgroundColor(Color color) {
|
|
myForcedBackground = color;
|
|
}
|
|
|
|
public void resetBackgourndColor() {
|
|
myForcedBackground = null;
|
|
}
|
|
|
|
public Color getForegroundColor() {
|
|
return myScheme.getDefaultForeground();
|
|
}
|
|
|
|
public Color getBackroundColor() {
|
|
if (myForcedBackground != null) return myForcedBackground;
|
|
|
|
Color color = myScheme.getDefaultBackground();
|
|
if (myDocument.isWritable()) {
|
|
return color;
|
|
}
|
|
Color readOnlyColor = myScheme.getColor(EditorColors.READONLY_BACKGROUND_COLOR);
|
|
return readOnlyColor != null ? readOnlyColor : color;
|
|
}
|
|
|
|
private void paintComposedTextDecoration(Graphics2D g) {
|
|
if (myInputMethodRequestsHandler != null && myInputMethodRequestsHandler.composedText != null) {
|
|
VisualPosition visStart = offsetToVisualPosition(
|
|
Math.min(myInputMethodRequestsHandler.composedTextStart, myDocument.getTextLength()));
|
|
int y = visibleLineNumberToYPosition(visStart.line) + getLineHeight() - getDescent() + 1;
|
|
Point p1 = visualPositionToXY(visStart);
|
|
Point p2 = logicalPositionToXY(
|
|
offsetToLogicalPosition(Math.min(myInputMethodRequestsHandler.composedTextEnd, myDocument.getTextLength())));
|
|
|
|
Stroke saved = g.getStroke();
|
|
BasicStroke dotted = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0, new float[]{0, 2, 0, 2},
|
|
0);
|
|
g.setStroke(dotted);
|
|
g.drawLine(p1.x, y, p2.x, y);
|
|
g.setStroke(saved);
|
|
}
|
|
}
|
|
|
|
private static Rectangle getClipBounds(Graphics g) {
|
|
return g.getClipBounds();
|
|
}
|
|
|
|
private void paintRightMargin(Graphics g, Rectangle clip) {
|
|
Color rightMargin = myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR);
|
|
if (!mySettings.isRightMarginShown() || rightMargin == null) {
|
|
return;
|
|
}
|
|
int x = mySettings.getRightMargin(myProject) * getSpaceWidth(Font.PLAIN);
|
|
if (x >= clip.x && x < clip.x + clip.width) {
|
|
g.setColor(rightMargin);
|
|
g.drawLine(x, clip.y, x, clip.y + clip.height);
|
|
}
|
|
}
|
|
|
|
private void paintSegmentHighlightersBorderAndAfterEndOfLine(Graphics g, Rectangle clip) {
|
|
if (myDocument.getLineCount() == 0) return;
|
|
int startLineNumber = yPositionToVisibleLineNumber(clip.y);
|
|
int endLineNumber = yPositionToVisibleLineNumber(clip.y + clip.height) + 1;
|
|
|
|
RangeHighlighter[] segmentHighlighters;
|
|
final MarkupModel docMarkup = myDocument.getMarkupModel(myProject);
|
|
if (docMarkup != null) {
|
|
segmentHighlighters = docMarkup.getAllHighlighters();
|
|
for (int i = 0; i < segmentHighlighters.length; i++) {
|
|
paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighters[i], startLineNumber,
|
|
endLineNumber);
|
|
}
|
|
}
|
|
|
|
segmentHighlighters = getMarkupModel().getAllHighlighters();
|
|
for (int i = 0; i < segmentHighlighters.length; i++) {
|
|
RangeHighlighter segmentHighlighter = segmentHighlighters[i];
|
|
paintSegmentHighlighterAfterEndOfLine(g, (RangeHighlighterEx)segmentHighlighter, startLineNumber, endLineNumber);
|
|
}
|
|
}
|
|
|
|
private void paintSegmentHighlighterAfterEndOfLine(Graphics g, RangeHighlighterEx segmentHighlighter,
|
|
int startLineNumber, int endLineNumber) {
|
|
if (!segmentHighlighter.isValid()) {
|
|
return;
|
|
}
|
|
if (segmentHighlighter.isAfterEndOfLine()) {
|
|
int startOffset = segmentHighlighter.getStartOffset();
|
|
int visibleStartLine = offsetToVisualPosition(startOffset).line;
|
|
|
|
if (!getFoldingModel().isOffsetCollapsed(startOffset)) {
|
|
if (visibleStartLine >= startLineNumber && visibleStartLine <= endLineNumber) {
|
|
int logStartLine = offsetToLogicalPosition(startOffset).line;
|
|
LogicalPosition logPosition = offsetToLogicalPosition(myDocument.getLineEndOffset(logStartLine));
|
|
Point end = logicalPositionToXY(logPosition);
|
|
int charWidth = getSpaceWidth(Font.PLAIN);
|
|
int lineHeight = getLineHeight();
|
|
TextAttributes attributes = segmentHighlighter.getTextAttributes();
|
|
if (attributes != null && attributes.getBackgroundColor() != null) {
|
|
g.setColor(attributes.getBackgroundColor());
|
|
g.fillRect(end.x, end.y, charWidth, lineHeight);
|
|
}
|
|
if (attributes != null && attributes.getEffectColor() != null) {
|
|
int y = visibleLineNumberToYPosition(visibleStartLine) + getLineHeight() - getDescent() + 1;
|
|
g.setColor(attributes.getEffectColor());
|
|
if (attributes.getEffectType() == EffectType.WAVE_UNDERSCORE) {
|
|
drawWave(g, end.x, end.x + charWidth - 1, y);
|
|
}
|
|
else if (attributes.getEffectType() != EffectType.BOXED) {
|
|
g.drawLine(end.x, y, end.x + charWidth - 1, y);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public int getMaxWidthInRange(int startOffset, int endOffset) {
|
|
int width = 0;
|
|
VisualPosition start = offsetToVisualPosition(startOffset);
|
|
VisualPosition end = offsetToVisualPosition(endOffset);
|
|
|
|
for (int i = start.line; i <= end.line; i++) {
|
|
int lastColumn = EditorUtil.getLastVisualLineColumnNumber(this, i) + 1;
|
|
int lineWidth = visualPositionToXY(new VisualPosition(i, lastColumn)).x;
|
|
|
|
if (lineWidth > width) {
|
|
width = lineWidth;
|
|
}
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
private void paintBackgrounds(Graphics g, Rectangle clip) {
|
|
int lineHeight = getLineHeight();
|
|
|
|
int visibleLineNumber = clip.y / lineHeight;
|
|
|
|
int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
|
|
|
|
if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) {
|
|
return;
|
|
}
|
|
|
|
int start = myDocument.getLineStartOffset(startLineNumber);
|
|
|
|
IterationState iterationState = new IterationState(this, start, true);
|
|
|
|
LineIterator lIterator = createLineIterator();
|
|
lIterator.start(start);
|
|
if (lIterator.atEnd()) {
|
|
return;
|
|
}
|
|
|
|
TextAttributes attributes = iterationState.getMergedAttributes();
|
|
Color backColor = attributes.getBackgroundColor();
|
|
Point position = new Point(0, visibleLineNumber * lineHeight);
|
|
int fontType = attributes.getFontType();
|
|
CharSequence text = myDocument.getCharsNoThreadCheck();
|
|
int lastLineIndex = Math.max(0, myDocument.getLineCount() - 1);
|
|
while (!iterationState.atEnd() && !lIterator.atEnd()) {
|
|
int hEnd = iterationState.getEndOffset();
|
|
int lEnd = lIterator.getEnd();
|
|
|
|
if (hEnd >= lEnd) {
|
|
FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
|
|
if (collapsedFolderAt == null) {
|
|
position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()),
|
|
position, fontType);
|
|
|
|
if (lIterator.getLineNumber() < lastLineIndex) {
|
|
if (backColor != null) {
|
|
Color saved = g.getColor();
|
|
g.setColor(backColor);
|
|
g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
|
|
g.setColor(saved);
|
|
}
|
|
}
|
|
else {
|
|
paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
|
|
break;
|
|
}
|
|
|
|
position.x = 0;
|
|
if (position.y > clip.y + clip.height) break;
|
|
position.y += lineHeight;
|
|
start = lEnd;
|
|
}
|
|
|
|
lIterator.advance();
|
|
}
|
|
else {
|
|
FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
|
|
if (collapsedFolderAt != null) {
|
|
position.x = drawBackground(g, backColor, collapsedFolderAt.getPlaceholderText(),
|
|
position, fontType);
|
|
}
|
|
else {
|
|
if (hEnd > lEnd - lIterator.getSeparatorLength()) {
|
|
position.x = drawBackground(g, backColor, text.subSequence(start, lEnd - lIterator.getSeparatorLength()),
|
|
position, fontType);
|
|
}
|
|
else {
|
|
position.x = drawBackground(g, backColor, text.subSequence(start, hEnd), position, fontType);
|
|
}
|
|
}
|
|
|
|
iterationState.advance();
|
|
attributes = iterationState.getMergedAttributes();
|
|
backColor = attributes.getBackgroundColor();
|
|
fontType = attributes.getFontType();
|
|
start = iterationState.getStartOffset();
|
|
}
|
|
}
|
|
|
|
if (lIterator.getLineNumber() >= lastLineIndex && position.y <= clip.y + clip.height) {
|
|
paintAfterFileEndBackground(iterationState, g, position, clip, lineHeight);
|
|
}
|
|
}
|
|
|
|
private void paintRectangularSelection(Graphics g) {
|
|
final SelectionModel model = getSelectionModel();
|
|
if (!model.hasBlockSelection()) return;
|
|
final Point start = logicalPositionToXY(model.getBlockStart());
|
|
final Point end = logicalPositionToXY(model.getBlockEnd());
|
|
g.setColor(myScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR));
|
|
final int y;
|
|
final int height;
|
|
if (start.y <= end.y) {
|
|
y = start.y;
|
|
height = end.y - y + getLineHeight();
|
|
}
|
|
else {
|
|
y = end.y;
|
|
height = start.y - end.y + getLineHeight();
|
|
}
|
|
final int x = Math.min(start.x, end.x);
|
|
final int width = Math.max(2, Math.abs(end.x - start.x));
|
|
g.fillRect(x, y, width, height);
|
|
}
|
|
|
|
private static void paintAfterFileEndBackground(IterationState iterationState, Graphics g, Point position,
|
|
Rectangle clip, int lineHeight) {
|
|
Color backColor = iterationState.getPastFileEndBackground();
|
|
if (backColor != null) {
|
|
Color saved = g.getColor();
|
|
g.setColor(backColor);
|
|
g.fillRect(position.x, position.y, clip.x + clip.width - position.x, lineHeight);
|
|
g.setColor(saved);
|
|
}
|
|
}
|
|
|
|
private int drawBackground(Graphics g,
|
|
Color backColor,
|
|
CharSequence text,
|
|
Point position,
|
|
int fontType) {
|
|
int w = getTextSegmentWidth(text, position.x, fontType);
|
|
|
|
if (backColor != null) {
|
|
Color savedColor = g.getColor();
|
|
g.setColor(backColor);
|
|
g.fillRect(position.x, position.y, w, getLineHeight());
|
|
g.setColor(savedColor);
|
|
}
|
|
|
|
return position.x + w;
|
|
}
|
|
|
|
private LineIterator createLineIterator() {
|
|
return myDocument.createLineIterator();
|
|
}
|
|
|
|
private void paintText(Graphics g, Rectangle clip) {
|
|
int lineHeight = getLineHeight();
|
|
|
|
int visibleLineNumber = clip.y / lineHeight;
|
|
|
|
int startLineNumber = xyToLogicalPosition(new Point(0, clip.y)).line;
|
|
|
|
if (startLineNumber >= myDocument.getLineCount() || startLineNumber < 0) {
|
|
return;
|
|
}
|
|
|
|
int start = myDocument.getLineStartOffset(startLineNumber);
|
|
|
|
IterationState iterationState = new IterationState(this, start, true);
|
|
|
|
LineIterator lIterator = createLineIterator();
|
|
lIterator.start(start);
|
|
if (lIterator.atEnd()) {
|
|
return;
|
|
}
|
|
|
|
TextAttributes attributes = iterationState.getMergedAttributes();
|
|
Color currentColor = attributes.getForegroundColor();
|
|
if (currentColor == null) {
|
|
currentColor = getForegroundColor();
|
|
}
|
|
Color effectColor = attributes.getEffectColor();
|
|
EffectType effectType = attributes.getEffectType();
|
|
int fontType = attributes.getFontType();
|
|
myCurrentFontType = null;
|
|
myFontCache = new ArrayList<CachedFontContent>();
|
|
g.setColor(currentColor);
|
|
Point position = new Point(0, visibleLineNumber * lineHeight);
|
|
while (!iterationState.atEnd() && !lIterator.atEnd()) {
|
|
int hEnd = iterationState.getEndOffset();
|
|
int lEnd = lIterator.getEnd();
|
|
if (hEnd >= lEnd) {
|
|
FoldRegion collapsedFolderAt = myFoldingModel.getCollapsedRegionAtOffset(start);
|
|
if (collapsedFolderAt == null) {
|
|
drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor, effectType,
|
|
fontType, currentColor);
|
|
position.x = 0;
|
|
if (position.y > clip.y + clip.height) break;
|
|
position.y += lineHeight;
|
|
start = lEnd;
|
|
}
|
|
|
|
// myBorderEffect.eolReached(g, this);
|
|
lIterator.advance();
|
|
}
|
|
else {
|
|
FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
|
|
if (collapsedFolderAt != null) {
|
|
int foldingXStart = position.x;
|
|
position.x = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType,
|
|
fontType, currentColor);
|
|
BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, position.x, getLineHeight(), effectColor,
|
|
effectType);
|
|
|
|
}
|
|
else {
|
|
if (hEnd > lEnd - lIterator.getSeparatorLength()) {
|
|
position.x = drawString(g, start, lEnd - lIterator.getSeparatorLength(), position, clip, effectColor,
|
|
effectType, fontType, currentColor);
|
|
}
|
|
else {
|
|
position.x = drawString(g, start, hEnd, position, clip, effectColor, effectType, fontType, currentColor);
|
|
}
|
|
}
|
|
|
|
iterationState.advance();
|
|
attributes = iterationState.getMergedAttributes();
|
|
Color color = attributes.getForegroundColor();
|
|
if (color == null) {
|
|
color = getForegroundColor();
|
|
}
|
|
if (color != currentColor) {
|
|
g.setColor(color);
|
|
currentColor = color;
|
|
}
|
|
effectColor = attributes.getEffectColor();
|
|
effectType = attributes.getEffectType();
|
|
fontType = attributes.getFontType();
|
|
|
|
start = iterationState.getStartOffset();
|
|
}
|
|
}
|
|
|
|
FoldRegion collapsedFolderAt = iterationState.getCurrentFold();
|
|
if (collapsedFolderAt != null) {
|
|
int foldingXStart = position.x;
|
|
int foldingXEnd = drawString(g, collapsedFolderAt.getPlaceholderText(), position, clip, effectColor, effectType, fontType,
|
|
currentColor);
|
|
BorderEffect.paintFoldedEffect(g, foldingXStart, position.y, foldingXEnd, getLineHeight(), effectColor,
|
|
effectType);
|
|
// myBorderEffect.collapsedFolderReached(g, this);
|
|
}
|
|
|
|
flushCachedChars(g);
|
|
}
|
|
|
|
private class CachedFontContent {
|
|
final CharSequence[] data = new CharSequence[CACHED_CHARS_BUFFER_SIZE];
|
|
final int[] x = new int[CACHED_CHARS_BUFFER_SIZE];
|
|
final int[] y = new int[CACHED_CHARS_BUFFER_SIZE];
|
|
final Color[] color = new Color[CACHED_CHARS_BUFFER_SIZE];
|
|
|
|
int myCount = 0;
|
|
final FontInfo myFontType;
|
|
|
|
public CachedFontContent(FontInfo fontInfo) {
|
|
myFontType = fontInfo;
|
|
}
|
|
|
|
public void flushContent(Graphics g) {
|
|
if (myCount != 0) {
|
|
if (myCurrentFontType != myFontType) {
|
|
myCurrentFontType = myFontType;
|
|
g.setFont(myFontType.getFont());
|
|
}
|
|
Color currentColor = null;
|
|
for (int i = 0; i < myCount; i++) {
|
|
if (!color[i].equals(currentColor)) {
|
|
currentColor = color[i];
|
|
g.setColor(currentColor);
|
|
}
|
|
|
|
drawChars(g, data[i], x[i], y[i]);
|
|
color[i] = null;
|
|
data[i] = null;
|
|
}
|
|
|
|
myCount = 0;
|
|
}
|
|
}
|
|
|
|
public void addContent(Graphics g, CharSequence data, int x, int y, Color color) {
|
|
this.data[myCount] = data;
|
|
this.x[myCount] = x;
|
|
this.y[myCount] = y;
|
|
this.color[myCount] = color;
|
|
|
|
myCount++;
|
|
if (myCount >= CACHED_CHARS_BUFFER_SIZE || SystemInfo.isMac) {
|
|
flushContent(g);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void flushCachedChars(Graphics g) {
|
|
for (int i = 0; i < myFontCache.size(); i++) {
|
|
myFontCache.get(i).flushContent(g);
|
|
}
|
|
myFontCache = null;
|
|
}
|
|
|
|
private void paintCaretCursor(Graphics g) {
|
|
myCaretCursor.paint(g);
|
|
}
|
|
|
|
private void paintLineMarkersSeparators(Graphics g, Rectangle clip, MarkupModel markupModel) {
|
|
if (markupModel == null) return;
|
|
RangeHighlighter[] lineMarkers = markupModel.getAllHighlighters();
|
|
for (int i = 0; i < lineMarkers.length; i++) {
|
|
RangeHighlighter lineMarker = lineMarkers[i];
|
|
paintLineMarkerSeparator(lineMarker, clip, g);
|
|
}
|
|
}
|
|
|
|
private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
|
|
if (!marker.isValid()) {
|
|
return;
|
|
}
|
|
Color separatorColor = marker.getLineSeparatorColor();
|
|
if (separatorColor != null) {
|
|
int lineNumber = marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP ? marker.getDocument()
|
|
.getLineNumber(marker.getStartOffset()) : marker.getDocument().getLineNumber(marker.getEndOffset());
|
|
if (lineNumber < 0 || lineNumber >= myDocument.getLineCount()) {
|
|
return;
|
|
}
|
|
|
|
int endShift = clip.x + clip.width;
|
|
g.setColor(separatorColor);
|
|
|
|
if (mySettings.isRightMarginShown()
|
|
&& myScheme.getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
|
|
endShift =
|
|
Math.min(endShift, mySettings.getRightMargin(myProject) * getSpaceWidth(Font.PLAIN));
|
|
}
|
|
|
|
int y = visibleLineNumberToYPosition(logicalToVisualPosition(new LogicalPosition(lineNumber, 0)).line);
|
|
|
|
if (marker.getLineSeparatorPlacement() != SeparatorPlacement.TOP) {
|
|
y += getLineHeight();
|
|
}
|
|
|
|
g.drawLine(0, y - 1, endShift, y - 1);
|
|
}
|
|
}
|
|
|
|
private int drawString(Graphics g, int start, int end, Point position, Rectangle clip, Color effectColor,
|
|
EffectType effectType, int fontType, Color fontColor) {
|
|
if (start >= end) return position.x;
|
|
|
|
CharSequence text = myDocument.getCharsNoThreadCheck();
|
|
boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
|
|
|
|
if (!isInClip) return position.x;
|
|
|
|
int y = getLineHeight() - getDescent() + position.y;
|
|
int x = position.x;
|
|
return drawTabbedString(g, text.subSequence(start, end), x, y, effectColor, effectType, fontType, fontColor);
|
|
}
|
|
|
|
private int drawString(Graphics g, String text, Point position, Rectangle clip, Color effectColor,
|
|
EffectType effectType, int fontType, Color fontColor) {
|
|
boolean isInClip = (getLineHeight() + position.y >= clip.y) && (position.y <= clip.y + clip.height);
|
|
|
|
if (!isInClip) return position.x;
|
|
|
|
int y = getLineHeight() - getDescent() + position.y;
|
|
int x = position.x;
|
|
return drawTabbedString(g, text, x, y, effectColor, effectType, fontType,
|
|
fontColor);
|
|
}
|
|
|
|
private int drawTabbedString(Graphics g, CharSequence text, int x, int y, Color effectColor,
|
|
EffectType effectType, int fontType, Color fontColor) {
|
|
int xStart = x;
|
|
|
|
int start = 0;
|
|
|
|
final int textLength = text.length();
|
|
for (int i = 0; i < textLength; i++) {
|
|
if (text.charAt(i) != '\t') continue;
|
|
|
|
x = drawTablessString(text, start, i, g, x, y, fontType, fontColor);
|
|
|
|
int x1 = nextTabStop(x);
|
|
drawTabPlacer(g, y, x, x1);
|
|
x = x1;
|
|
start = i + 1;
|
|
}
|
|
|
|
x = drawTablessString(text, start, textLength, g, x, y, fontType, fontColor);
|
|
|
|
if (effectColor != null) {
|
|
Color savedColor = g.getColor();
|
|
|
|
int w = getTextSegmentWidth(text, xStart, fontType);
|
|
// myBorderEffect.flushIfCantProlong(g, this, effectType, effectColor);
|
|
if (effectType == EffectType.LINE_UNDERSCORE) {
|
|
g.setColor(effectColor);
|
|
g.drawLine(xStart, y + 1, xStart + w, y + 1);
|
|
g.setColor(savedColor);
|
|
}
|
|
else {
|
|
if (effectType == EffectType.STRIKEOUT) {
|
|
g.setColor(effectColor);
|
|
int y1 = y - getCharHeight() / 2;
|
|
g.drawLine(xStart, y1, xStart + w, y1);
|
|
g.setColor(savedColor);
|
|
}
|
|
else {
|
|
if (effectType == EffectType.WAVE_UNDERSCORE) {
|
|
g.setColor(effectColor);
|
|
drawWave(g, xStart, xStart + w, y + 1);
|
|
g.setColor(savedColor);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return x;
|
|
}
|
|
|
|
private int drawTablessString(final CharSequence text,
|
|
final int start,
|
|
final int end,
|
|
final Graphics g,
|
|
final int x,
|
|
final int y,
|
|
final int fontType,
|
|
final Color fontColor) {
|
|
int endX = x;
|
|
if (start < end) {
|
|
final FontInfo font = fontForChar(text.charAt(start), fontType);
|
|
for (int j = start; j < end; j++) {
|
|
final char c = text.charAt(j);
|
|
FontInfo newFont = fontForChar(c, fontType);
|
|
if (font != newFont) {
|
|
int x1 = drawTablessString(text, start, j, g, x, y, fontType, fontColor);
|
|
return drawTablessString(text, j, end, g, x1, y, fontType, fontColor);
|
|
}
|
|
endX += font.charWidth(c, myEditorComponent);
|
|
}
|
|
|
|
drawCharsCached(g, text.subSequence(start, end), x, y, fontType, fontColor);
|
|
}
|
|
return endX;
|
|
}
|
|
|
|
private FontInfo fontForChar(final char c, int style) {
|
|
return ComplementaryFontsRegistry.getFontAbleToDisplay(c, myScheme.getEditorFontSize(), style, myScheme.getEditorFontName());
|
|
}
|
|
|
|
public int charWidth(char c, int fontType) {
|
|
return fontForChar(c, fontType).charWidth(c, myEditorComponent);
|
|
}
|
|
|
|
private void drawTabPlacer(Graphics g, int y, int start, int stop) {
|
|
if (mySettings.isWhitespacesShown()) {
|
|
stop -= g.getFontMetrics().charWidth(' ') / 2;
|
|
Color oldColor = g.getColor();
|
|
g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR));
|
|
final int charHeight = getCharHeight();
|
|
final int halfCharHeight = charHeight / 2;
|
|
int mid = y - halfCharHeight;
|
|
int top = y - charHeight;
|
|
g.drawLine(start, mid, stop, mid);
|
|
g.drawLine(stop, y, stop, top);
|
|
g.fillPolygon(new int[]{stop - halfCharHeight, stop - halfCharHeight, stop},
|
|
new int[]{y, y - charHeight, y - halfCharHeight}, 3);
|
|
g.setColor(oldColor);
|
|
}
|
|
}
|
|
<caret>
|
|
private void drawCharsCached(Graphics g,
|
|
CharSequence data,
|
|
int x,
|
|
int y,
|
|
int fontType,
|
|
Color color) {
|
|
FontInfo fnt = fontForChar(data.charAt(0), fontType);
|
|
CachedFontContent cache = null;
|
|
for (int i = 0; i < myFontCache.size(); i++) {
|
|
CachedFontContent cache1 = myFontCache.get(i);
|
|
if (cache1.myFontType == fnt) {
|
|
cache = cache1;
|
|
break;
|
|
}
|
|
}
|
|
if (cache == null) {
|
|
cache = new CachedFontContent(fnt);
|
|
myFontCache.add(cache);
|
|
}
|
|
cache.addContent(g, data, x, y, color);
|
|
}
|
|
|
|
private void drawChars(Graphics g, CharSequence data, int x, int y) {
|
|
g.drawString(data.toString(), x, y);
|
|
|
|
if (mySettings.isWhitespacesShown()) {
|
|
Color oldColor = g.getColor();
|
|
g.setColor(myScheme.getColor(EditorColors.WHITESPACES_COLOR));
|
|
final FontMetrics metrics = g.getFontMetrics();
|
|
int halfSpaceWidth = metrics.charWidth(' ') / 2;
|
|
for (int i = 0; i < data.length(); i++) {
|
|
if (data.charAt(i) == ' ') {
|
|
g.fillRect(x + halfSpaceWidth, y, 1, 1);
|
|
}
|
|
x += metrics.charWidth(data.charAt(i));
|
|
}
|
|
g.setColor(oldColor);
|
|
}
|
|
}
|
|
|
|
private static final int WAVE_HEIGHT = 2;
|
|
private static final int WAVE_SEGMENT_LENGTH = 4;
|
|
|
|
private static void drawWave(Graphics g, int xStart, int xEnd, int y) {
|
|
int startSegment = xStart / WAVE_SEGMENT_LENGTH;
|
|
int endSegment = xEnd / WAVE_SEGMENT_LENGTH;
|
|
for (int i = startSegment; i < endSegment; i++) {
|
|
drawWaveSegment(g, WAVE_SEGMENT_LENGTH * i, y);
|
|
}
|
|
|
|
int x = WAVE_SEGMENT_LENGTH * endSegment;
|
|
g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
|
|
}
|
|
|
|
private static void drawWaveSegment(Graphics g, int x, int y) {
|
|
g.drawLine(x, y + WAVE_HEIGHT, x + WAVE_SEGMENT_LENGTH / 2, y);
|
|
g.drawLine(x + WAVE_SEGMENT_LENGTH / 2, y, x + WAVE_SEGMENT_LENGTH, y + WAVE_HEIGHT);
|
|
}
|
|
|
|
private int getTextSegmentWidth(CharSequence text, int xStart, int fontType) {
|
|
int start = 0;
|
|
int x = xStart;
|
|
|
|
for (int i = 0; i < text.length(); i++) {
|
|
if (text.charAt(i) != '\t') continue;
|
|
|
|
if (i > start) {
|
|
for (int j = start; j < i; j++) x += charWidth(text.charAt(j), fontType);
|
|
}
|
|
x = nextTabStop(x);
|
|
start = i + 1;
|
|
}
|
|
|
|
if (start < text.length()) {
|
|
for (int j = start; j < text.length(); j++) x += charWidth(text.charAt(j), fontType);
|
|
}
|
|
|
|
return x - xStart;
|
|
}
|
|
|
|
public int getLineHeight() {
|
|
if (myLineHeight != -1) return myLineHeight;
|
|
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
|
|
FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
|
|
myLineHeight = (int)(fontMetrics.getHeight() * (isOneLineMode() ? 1 : myScheme.getLineSpacing()));
|
|
if (myLineHeight == 0) {
|
|
myLineHeight = fontMetrics.getHeight();
|
|
if (myLineHeight == 0) {
|
|
myLineHeight = 12;
|
|
}
|
|
}
|
|
|
|
return myLineHeight;
|
|
}
|
|
|
|
int getDescent() {
|
|
if (myDescent != -1) {
|
|
return myDescent;
|
|
}
|
|
FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
|
|
myDescent = fontMetrics.getDescent();
|
|
return myDescent;
|
|
}
|
|
|
|
public FontMetrics getFontMetrics(int fontType) {
|
|
if (myPlainFontMetrics == null) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
myPlainFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
|
|
myBoldFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD));
|
|
myItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.ITALIC));
|
|
myBoldItalicFontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.BOLD_ITALIC));
|
|
}
|
|
|
|
if (fontType == Font.PLAIN) return myPlainFontMetrics;
|
|
if (fontType == Font.BOLD) return myBoldFontMetrics;
|
|
if (fontType == Font.ITALIC) return myItalicFontMetrics;
|
|
if (fontType == Font.BOLD + Font.ITALIC) return myBoldItalicFontMetrics;
|
|
|
|
LOG.assertTrue(false, "Unknown font type: " + fontType);
|
|
|
|
return myPlainFontMetrics;
|
|
}
|
|
|
|
private int getCharHeight() {
|
|
if (myCharHeight == -1) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
FontMetrics fontMetrics = myEditorComponent.getFontMetrics(myScheme.getFont(EditorFontType.PLAIN));
|
|
myCharHeight = fontMetrics.charWidth('a');
|
|
}
|
|
return myCharHeight;
|
|
}
|
|
|
|
public Dimension getPreferredSize() {
|
|
Dimension draft = getSizeWithoutCaret();
|
|
int caretX = visualPositionToXY(getCaretModel().getVisualPosition()).x;
|
|
draft.width = Math.max(caretX, draft.width) + mySettings.getAdditionalColumnsCount() * getSpaceWidth(Font.PLAIN);
|
|
return draft;
|
|
}
|
|
|
|
private Dimension getSizeWithoutCaret() {
|
|
Dimension size = mySizeContainer.getContentSize();
|
|
if (isOneLineMode()) return new Dimension(size.width, getLineHeight());
|
|
if (mySettings.isAdditionalPageAtBottom()) {
|
|
int lineHeight = getLineHeight();
|
|
return new Dimension(size.width,
|
|
size.height +
|
|
Math.max(getScrollingModel().getVisibleArea().height - 2 * lineHeight, lineHeight));
|
|
}
|
|
|
|
return getContentSize();
|
|
}
|
|
|
|
public Dimension getContentSize() {
|
|
Dimension size = mySizeContainer.getContentSize();
|
|
return new Dimension(size.width, size.height + mySettings.getAdditionalLinesCount() * getLineHeight());
|
|
}
|
|
|
|
public JScrollPane getScrollPane() {
|
|
return myScrollPane;
|
|
}
|
|
|
|
public int logicalPositionToOffset(LogicalPosition pos) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
if (myDocument.getLineCount() == 0) return 0;
|
|
|
|
if (pos.line < 0) throw new IndexOutOfBoundsException("Wrong line: " + pos.line);
|
|
if (pos.column < 0) throw new IndexOutOfBoundsException("Wrong column:" + pos.column);
|
|
|
|
if (pos.line >= myDocument.getLineCount()) {
|
|
return myDocument.getTextLength();
|
|
}
|
|
|
|
int start = myDocument.getLineStartOffset(pos.line);
|
|
int end = myDocument.getLineEndOffset(pos.line);
|
|
|
|
CharSequence text = myDocument.getCharsNoThreadCheck();
|
|
|
|
if (pos.column == 0) return start;
|
|
return EditorUtil.calcOffset(this, text, start, end, pos.column, mySettings.getTabSize(myProject));
|
|
}
|
|
|
|
public void setLastColumnNumber(int val) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
myLastColumnNumber = val;
|
|
}
|
|
|
|
public int getLastColumnNumber() {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
return myLastColumnNumber;
|
|
}
|
|
|
|
int getVisibleLineCount() {
|
|
int line = getDocument().getLineCount();
|
|
line -= myFoldingModel.getFoldedLinesCountBefore(getDocument().getTextLength() + 1);
|
|
return line;
|
|
}
|
|
|
|
public VisualPosition logicalToVisualPosition(LogicalPosition logicalPos) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
if (!myFoldingModel.isFoldingEnabled()) return new VisualPosition(logicalPos.line, logicalPos.column);
|
|
|
|
int offset = logicalPositionToOffset(logicalPos);
|
|
|
|
FoldRegion outermostCollapsed = myFoldingModel.getCollapsedRegionAtOffset(offset);
|
|
if (outermostCollapsed != null && offset > outermostCollapsed.getStartOffset()) {
|
|
if (offset < getDocument().getTextLength() - 1) {
|
|
offset = outermostCollapsed.getStartOffset();
|
|
LogicalPosition foldStart = offsetToLogicalPosition(offset);
|
|
return logicalToVisualPosition(foldStart);
|
|
}
|
|
else {
|
|
offset = outermostCollapsed.getEndOffset() + 3;
|
|
}
|
|
}
|
|
|
|
int line = logicalPos.line;
|
|
int column = logicalPos.column;
|
|
|
|
line -= myFoldingModel.getFoldedLinesCountBefore(offset);
|
|
|
|
FoldRegion[] toplevel = myFoldingModel.fetchTopLevel();
|
|
for (int idx = myFoldingModel.getLastTopLevelIndexBefore(offset); idx >= 0; idx--) {
|
|
FoldRegion region = toplevel[idx];
|
|
if (region.isValid()) {
|
|
if (region.getDocument().getLineNumber(region.getEndOffset()) == logicalPos.line &&
|
|
region.getEndOffset() <= offset) {
|
|
LogicalPosition foldStart = offsetToLogicalPosition(region.getStartOffset());
|
|
LogicalPosition foldEnd = offsetToLogicalPosition(region.getEndOffset());
|
|
column += foldStart.column + region.getPlaceholderText().length() - foldEnd.column;
|
|
offset = region.getStartOffset();
|
|
logicalPos = foldStart;
|
|
}
|
|
else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
LOG.assertTrue(line >= 0);
|
|
|
|
return new VisualPosition(line, Math.max(0, column));
|
|
}
|
|
|
|
private FoldRegion getLastCollapsedBeforePosition(VisualPosition visual) {
|
|
FoldRegion[] topLevelCollapsed = myFoldingModel.fetchTopLevel();
|
|
|
|
if (topLevelCollapsed == null) return null;
|
|
|
|
int start = 0;
|
|
int end = topLevelCollapsed.length - 1;
|
|
int i = 0;
|
|
|
|
while (start <= end) {
|
|
i = (start + end) / 2;
|
|
FoldRegion region = topLevelCollapsed[i];
|
|
LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
|
|
VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
|
|
if (visFoldEnd.line < visual.line) {
|
|
start = i + 1;
|
|
}
|
|
else {
|
|
if (visFoldEnd.line > visual.line) {
|
|
end = i - 1;
|
|
}
|
|
else {
|
|
if (visFoldEnd.column < visual.column) {
|
|
start = i + 1;
|
|
}
|
|
else {
|
|
if (visFoldEnd.column > visual.column) {
|
|
end = i - 1;
|
|
}
|
|
else {
|
|
i--;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
while (i >= 0 && i < topLevelCollapsed.length) {
|
|
if (topLevelCollapsed[i].isValid()) break;
|
|
i--;
|
|
}
|
|
|
|
if (i >= 0 && i < topLevelCollapsed.length) {
|
|
FoldRegion region = topLevelCollapsed[i];
|
|
LogicalPosition logFoldEnd = offsetToLogicalPosition(region.getEndOffset() - 1);
|
|
VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
|
|
if (visFoldEnd.line > visual.line || visFoldEnd.line == visual.line && visFoldEnd.column > visual.column) {
|
|
i--;
|
|
if (i >= 0) {
|
|
return topLevelCollapsed[i];
|
|
}
|
|
else {
|
|
return null;
|
|
}
|
|
}
|
|
return region;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
public LogicalPosition visualToLogicalPosition(VisualPosition visiblePos) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
if (!myFoldingModel.isFoldingEnabled()) return new LogicalPosition(visiblePos.line, visiblePos.column);
|
|
|
|
int line = visiblePos.line;
|
|
int column = visiblePos.column;
|
|
|
|
FoldRegion lastCollapsedBefore = getLastCollapsedBeforePosition(visiblePos);
|
|
|
|
if (lastCollapsedBefore != null) {
|
|
LogicalPosition logFoldEnd = offsetToLogicalPosition(lastCollapsedBefore.getEndOffset());
|
|
VisualPosition visFoldEnd = logicalToVisualPosition(logFoldEnd);
|
|
|
|
line = logFoldEnd.line + (visiblePos.line - visFoldEnd.line);
|
|
if (visFoldEnd.line == visiblePos.line) {
|
|
if (visiblePos.column >= visFoldEnd.column) {
|
|
column = logFoldEnd.column + (visiblePos.column - visFoldEnd.column);
|
|
}
|
|
else {
|
|
return offsetToLogicalPosition(lastCollapsedBefore.getStartOffset());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (column < 0) column = 0;
|
|
|
|
return new LogicalPosition(line, column);
|
|
}
|
|
|
|
private int calcLogicalLineNumber(int offset) {
|
|
int textLength = myDocument.getTextLength();
|
|
if (textLength == 0) return 0;
|
|
|
|
if (offset > textLength || offset < 0) {
|
|
throw new IndexOutOfBoundsException("Wrong offset: " + offset + " textLength: " + textLength);
|
|
}
|
|
|
|
int lineIndex = myDocument.getLineNumber(offset);
|
|
|
|
LOG.assertTrue(lineIndex >= 0 && lineIndex < myDocument.getLineCount());
|
|
|
|
return lineIndex;
|
|
}
|
|
|
|
private int calcColumnNumber(int offset, int lineIndex) {
|
|
if (myDocument.getTextLength() == 0) return 0;
|
|
|
|
CharSequence text = myDocument.getCharsSequence();
|
|
int start = myDocument.getLineStartOffset(lineIndex);
|
|
if (start == offset) return 0;
|
|
return EditorUtil.calcColumnNumber(this, text, start, offset, mySettings.getTabSize(myProject));
|
|
}
|
|
|
|
private void moveCaretToScreenPos(int x, int y) {
|
|
if (x < 0) {
|
|
x = 0;
|
|
}
|
|
|
|
LogicalPosition pos = xyToLogicalPosition(new Point(x, y));
|
|
|
|
int columnNumber = pos.column;
|
|
int lineNumber = pos.line;
|
|
|
|
if (lineNumber >= myDocument.getLineCount()) {
|
|
lineNumber = myDocument.getLineCount() - 1;
|
|
}
|
|
if (!mySettings.isVirtualSpace()) {
|
|
if (lineNumber >= 0) {
|
|
int lineEndOffset = myDocument.getLineEndOffset(lineNumber);
|
|
int lineEndColumnNumber = calcColumnNumber(lineEndOffset, lineNumber);
|
|
if (columnNumber > lineEndColumnNumber) {
|
|
columnNumber = lineEndColumnNumber;
|
|
}
|
|
}
|
|
}
|
|
if (lineNumber < 0) {
|
|
lineNumber = 0;
|
|
columnNumber = 0;
|
|
}
|
|
if (!mySettings.isCaretInsideTabs()) {
|
|
int offset = logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber));
|
|
CharSequence text = myDocument.getCharsSequence();
|
|
if (offset >= 0 && offset < myDocument.getTextLength()) {
|
|
if (text.charAt(offset) == '\t') {
|
|
columnNumber = calcColumnNumber(offset, lineNumber);
|
|
}
|
|
}
|
|
}
|
|
LogicalPosition pos1 = new LogicalPosition(lineNumber, columnNumber);
|
|
getCaretModel().moveToLogicalPosition(pos1);
|
|
}
|
|
|
|
private void runMousePressedCommand(final MouseEvent e) {
|
|
myMousePressedEvent = e;
|
|
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
|
|
|
|
EditorMouseListener[] mouseListeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
|
|
for (int i = 0; i < mouseListeners.length; i++) {
|
|
mouseListeners[i].mousePressed(event);
|
|
}
|
|
|
|
// On some systems (for example on Linux) popup trigger is MOUSE_PRESSED event.
|
|
// But this trigger is always consumed by popup handler. In that case we have to
|
|
// also move caret.
|
|
if (event.isConsumed() &&
|
|
!(event.getMouseEvent().isPopupTrigger() || event.getArea() == EditorMouseEventArea.EDITING_AREA)) {
|
|
return;
|
|
}
|
|
|
|
if (myCommandProcessor != null) {
|
|
Runnable runnable = new Runnable() {
|
|
public void run() {
|
|
processMousePressed(e);
|
|
}
|
|
};
|
|
myCommandProcessor.executeCommand(myProject, runnable, "", null);
|
|
}
|
|
else {
|
|
processMousePressed(e);
|
|
}
|
|
}
|
|
|
|
private void runMouseClickedCommand(final MouseEvent e) {
|
|
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
|
|
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
listeners[i].mouseClicked(event);
|
|
if (event.isConsumed()) {
|
|
e.consume();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void runMouseReleasedCommand(final MouseEvent e) {
|
|
myScrollingTimer.stop();
|
|
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
|
|
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
listeners[i].mouseReleased(event);
|
|
if (event.isConsumed()) {
|
|
e.consume();
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (myCommandProcessor != null) {
|
|
Runnable runnable = new Runnable() {
|
|
public void run() {
|
|
processMouseReleased(e);
|
|
}
|
|
};
|
|
myCommandProcessor.executeCommand(myProject, runnable, "", null);
|
|
}
|
|
else {
|
|
processMouseReleased(e);
|
|
}
|
|
}
|
|
|
|
private void runMouseEnteredCommand(MouseEvent e) {
|
|
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
|
|
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
listeners[i].mouseEntered(event);
|
|
if (event.isConsumed()) {
|
|
e.consume();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void runMouseExitedCommand(MouseEvent e) {
|
|
EditorMouseEvent event = new EditorMouseEvent(this, e, getMouseEventArea(e));
|
|
EditorMouseListener[] listeners = myMouseListeners.toArray(new EditorMouseListener[myMouseListeners.size()]);
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
listeners[i].mouseExited(event);
|
|
if (event.isConsumed()) {
|
|
e.consume();
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void processMousePressed(MouseEvent e) {
|
|
if (myMouseSelectionState != MOUSE_SELECTION_STATE_NONE &&
|
|
System.currentTimeMillis() - myMouseSelectionChangeTimestamp > 1000) {
|
|
setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
|
|
}
|
|
|
|
int x = e.getX();
|
|
int y = e.getY();
|
|
|
|
if (x < 0) x = 0;
|
|
if (y < 0) y = 0;
|
|
|
|
final EditorMouseEventArea eventArea = getMouseEventArea(e);
|
|
if (eventArea == EditorMouseEventArea.FOLDING_OUTLINE_AREA) {
|
|
final FoldRegion range = myGutterComponent.findFoldingAnchorAt(x, y);
|
|
if (range != null) {
|
|
final boolean expansion = !range.isExpanded();
|
|
|
|
int scrollShift = y - getScrollingModel().getVerticalScrollOffset();
|
|
Runnable processor = new Runnable() {
|
|
public void run() {
|
|
myFoldingModel.flushCaretShift();
|
|
range.setExpanded(expansion);
|
|
}
|
|
};
|
|
getFoldingModel().runBatchFoldingOperation(processor);
|
|
y = myGutterComponent.getHeadCenterY(range);
|
|
getScrollingModel().scrollVertically(y - scrollShift);
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (e.getSource() == myGutterComponent) {
|
|
if (eventArea == EditorMouseEventArea.LINE_MARKERS_AREA ||
|
|
eventArea == EditorMouseEventArea.ANNOTATIONS_AREA) {
|
|
myGutterComponent.mousePressed(e);
|
|
if (e.isConsumed()) return;
|
|
}
|
|
x = 0;
|
|
}
|
|
|
|
int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
|
|
moveCaretToScreenPos(x, y);
|
|
|
|
if (e.isPopupTrigger()) return;
|
|
|
|
requestFocus();
|
|
|
|
int caretOffset = getCaretModel().getOffset();
|
|
|
|
myMouseSelectedRegion = myFoldingModel.getFoldingPlaceholderAt(new Point(x, y));
|
|
myMousePressedInsideSelection = mySelectionModel.hasSelection() &&
|
|
caretOffset >= mySelectionModel.getSelectionStart() &&
|
|
caretOffset <= mySelectionModel.getSelectionEnd();
|
|
|
|
if (!myMousePressedInsideSelection && mySelectionModel.hasBlockSelection()) {
|
|
int[] starts = mySelectionModel.getBlockSelectionStarts();
|
|
int[] ends = mySelectionModel.getBlockSelectionEnds();
|
|
for (int i = 0; i < starts.length; i++) {
|
|
if (caretOffset >= starts[i] && caretOffset < ends[i]) {
|
|
myMousePressedInsideSelection = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (getMouseEventArea(e) == EditorMouseEventArea.LINE_NUMBERS_AREA && e.getClickCount() == 1) {
|
|
mySelectionModel.selectLineAtCaret();
|
|
setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
|
|
mySavedSelectionStart = mySelectionModel.getSelectionStart();
|
|
mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
|
|
return;
|
|
}
|
|
|
|
if (e.isShiftDown() && !e.isControlDown() && !e.isAltDown()) {
|
|
if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
|
|
if (caretOffset < mySavedSelectionStart) {
|
|
mySelectionModel.setSelection(mySavedSelectionEnd, caretOffset);
|
|
}
|
|
else {
|
|
mySelectionModel.setSelection(mySavedSelectionStart, caretOffset);
|
|
}
|
|
}
|
|
else {
|
|
mySelectionModel.setSelection(oldSelectionStart, caretOffset);
|
|
}
|
|
}
|
|
else {
|
|
if (!myMousePressedInsideSelection && getSelectionModel().hasSelection()) {
|
|
setMouseSelectionState(MOUSE_SELECTION_STATE_NONE);
|
|
mySelectionModel.setSelection(caretOffset, caretOffset);
|
|
}
|
|
else {
|
|
if (!e.isPopupTrigger()) {
|
|
switch (e.getClickCount()) {
|
|
case 2:
|
|
mySelectionModel.selectWordAtCaret(mySettings.isMouseClickSelectionHonorsCamelWords());
|
|
setMouseSelectionState(MOUSE_SELECTION_STATE_WORD_SELECTED);
|
|
mySavedSelectionStart = mySelectionModel.getSelectionStart();
|
|
mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
|
|
getCaretModel().moveToOffset(mySavedSelectionEnd);
|
|
break;
|
|
|
|
case 3:
|
|
mySelectionModel.selectLineAtCaret();
|
|
setMouseSelectionState(MOUSE_SELECTION_STATE_LINE_SELECTED);
|
|
mySavedSelectionStart = mySelectionModel.getSelectionStart();
|
|
mySavedSelectionEnd = mySelectionModel.getSelectionEnd();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static boolean isControlKeyDown(MouseEvent mouseEvent) {
|
|
return SystemInfo.isMac ? mouseEvent.isMetaDown() : mouseEvent.isControlDown();
|
|
}
|
|
|
|
private void processMouseReleased(MouseEvent e) {
|
|
if (e.getSource() == myGutterComponent) {
|
|
myGutterComponent.mouseReleased(e);
|
|
}
|
|
|
|
if (getMouseEventArea(e) != EditorMouseEventArea.EDITING_AREA || e.getY() < 0 || e.getX() < 0) {
|
|
return;
|
|
}
|
|
|
|
// if (myMousePressedInsideSelection) getSelectionModel().removeSelection();
|
|
final FoldRegion region = ((FoldingModelEx)getFoldingModel()).getFoldingPlaceholderAt(e.getPoint());
|
|
if (e.getX() >= 0 && e.getY() >= 0 && region != null && region == myMouseSelectedRegion) {
|
|
getFoldingModel().runBatchFoldingOperation(new Runnable() {
|
|
public void run() {
|
|
myFoldingModel.flushCaretShift();
|
|
region.setExpanded(true);
|
|
}
|
|
});
|
|
}
|
|
|
|
if (myMousePressedEvent != null && myMousePressedEvent.getClickCount() == 1 && myMousePressedInsideSelection) {
|
|
getSelectionModel().removeSelection();
|
|
}
|
|
}
|
|
|
|
public DataContext getDataContext() {
|
|
return getProjectAwareDataContext(DataManager.getInstance().getDataContext(getContentComponent()));
|
|
}
|
|
|
|
public DataContext getProjectAwareDataContext(final DataContext original) {
|
|
if (original.getData(DataConstants.PROJECT) == myProject) return original;
|
|
|
|
return new DataContext() {
|
|
public Object getData(String dataId) {
|
|
if (DataConstants.PROJECT.equals(dataId)) {
|
|
return myProject;
|
|
}
|
|
return original.getData(dataId);
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
public EditorMouseEventArea getMouseEventArea(MouseEvent e) {
|
|
if (myGutterComponent != e.getSource()) return EditorMouseEventArea.EDITING_AREA;
|
|
|
|
int x = myGutterComponent.convertX(e.getX());
|
|
|
|
if (x >= myGutterComponent.getLineNumberAreaOffset() &&
|
|
x < myGutterComponent.getLineNumberAreaOffset() + myGutterComponent.getLineNumberAreaWidth()) {
|
|
return EditorMouseEventArea.LINE_NUMBERS_AREA;
|
|
}
|
|
|
|
if (x >= myGutterComponent.getAnnotationsAreaOffset() &&
|
|
x <= myGutterComponent.getAnnotationsAreaOffset() + myGutterComponent.getAnnotationsAreaWidth()) {
|
|
return EditorMouseEventArea.ANNOTATIONS_AREA;
|
|
}
|
|
|
|
if (x >= myGutterComponent.getLineMarkerAreaOffset() &&
|
|
x < myGutterComponent.getLineMarkerAreaOffset() + myGutterComponent.getLineMarkerAreaWidth()) {
|
|
return EditorMouseEventArea.LINE_MARKERS_AREA;
|
|
}
|
|
|
|
if (x >= myGutterComponent.getFoldingAreaOffset() &&
|
|
x < myGutterComponent.getFoldingAreaOffset() + myGutterComponent.getFoldingAreaWidth()) {
|
|
return EditorMouseEventArea.FOLDING_OUTLINE_AREA;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private void requestFocus() {
|
|
myEditorComponent.requestFocus();
|
|
}
|
|
|
|
private void validateMousePointer(MouseEvent e) {
|
|
if (e.getSource() == myGutterComponent) {
|
|
FoldRegion foldingAtCursor = myGutterComponent.findFoldingAnchorAt(e.getX(), e.getY());
|
|
myGutterComponent.setActiveFoldRegion(foldingAtCursor);
|
|
if (foldingAtCursor != null) {
|
|
myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
|
|
}
|
|
else {
|
|
myGutterComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
|
}
|
|
}
|
|
else {
|
|
myGutterComponent.setActiveFoldRegion(null);
|
|
if (getSelectionModel().hasSelection() &&
|
|
(e.getModifiersEx() & (MouseEvent.BUTTON1_DOWN_MASK | MouseEvent.BUTTON2_DOWN_MASK)) == 0) {
|
|
int offset = logicalPositionToOffset(xyToLogicalPosition(e.getPoint()));
|
|
if (getSelectionModel().getSelectionStart() <= offset && offset < getSelectionModel().getSelectionEnd()) {
|
|
myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
|
|
return;
|
|
}
|
|
}
|
|
myEditorComponent.setCursor(Cursor.getPredefinedCursor(Cursor.TEXT_CURSOR));
|
|
}
|
|
}
|
|
|
|
private void runMouseDraggedCommand(final MouseEvent e) {
|
|
if (myCommandProcessor == null || myMousePressedEvent != null && myMousePressedEvent.isConsumed()) {
|
|
return;
|
|
}
|
|
myCommandProcessor.executeCommand(myProject, new Runnable() {
|
|
public void run() {
|
|
processMouseDragged(e);
|
|
}
|
|
}, "", MOUSE_DRAGGED_GROUP);
|
|
}
|
|
|
|
private void processMouseDragged(MouseEvent e) {
|
|
if (SwingUtilities.isRightMouseButton(e)) {
|
|
return;
|
|
}
|
|
Rectangle rect = getScrollingModel().getVisibleArea();
|
|
|
|
int dx = 0;
|
|
int x = e.getX();
|
|
|
|
if (e.getSource() == myGutterComponent) {
|
|
x = 0;
|
|
}
|
|
|
|
if (x < rect.x) {
|
|
dx = x - rect.x;
|
|
}
|
|
else {
|
|
if (x > rect.x + rect.width) {
|
|
dx = x - rect.x - rect.width;
|
|
}
|
|
}
|
|
|
|
int dy = 0;
|
|
int y = e.getY();
|
|
if (y < rect.y) {
|
|
dy = y - rect.y;
|
|
}
|
|
else {
|
|
if (y > rect.y + rect.height) {
|
|
dy = y - rect.y - rect.height;
|
|
}
|
|
}
|
|
if (dx == 0 && dy == 0) {
|
|
myScrollingTimer.stop();
|
|
|
|
SelectionModel selectionModel = getSelectionModel();
|
|
int oldSelectionStart = selectionModel.getLeadSelectionOffset();
|
|
int oldCaretOffset = getCaretModel().getOffset();
|
|
LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition();
|
|
moveCaretToScreenPos(x, y);
|
|
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
|
|
|
int newCaretOffset = getCaretModel().getOffset();
|
|
int caretShift = newCaretOffset - mySavedSelectionStart;
|
|
|
|
if (myMousePressedEvent != null &&
|
|
getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.EDITING_AREA &&
|
|
getMouseEventArea(myMousePressedEvent) != EditorMouseEventArea.LINE_NUMBERS_AREA) {
|
|
selectionModel.setSelection(oldSelectionStart, newCaretOffset);
|
|
}
|
|
else {
|
|
if (isColumnMode()) {
|
|
final LogicalPosition blockStart = selectionModel.hasBlockSelection()
|
|
? selectionModel.getBlockStart()
|
|
: oldLogicalCaret;
|
|
selectionModel.setBlockSelection(blockStart, getCaretModel().getLogicalPosition());
|
|
}
|
|
else {
|
|
if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
|
|
if (caretShift < 0) {
|
|
int newSelection = newCaretOffset;
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
|
|
newSelection = mySelectionModel.getWordAtCaretStart();
|
|
}
|
|
else {
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
|
|
newSelection = logicalPositionToOffset(
|
|
visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line, 0)));
|
|
}
|
|
}
|
|
if (newSelection < 0) newSelection = newCaretOffset;
|
|
selectionModel.setSelection(mySavedSelectionEnd, newSelection);
|
|
getCaretModel().moveToOffset(newSelection);
|
|
}
|
|
else {
|
|
int newSelection = newCaretOffset;
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
|
|
newSelection = mySelectionModel.getWordAtCaretEnd();
|
|
}
|
|
else {
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
|
|
newSelection = logicalPositionToOffset(
|
|
visualToLogicalPosition(new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)));
|
|
}
|
|
}
|
|
if (newSelection < 0) newSelection = newCaretOffset;
|
|
selectionModel.setSelection(mySavedSelectionStart, newSelection);
|
|
getCaretModel().moveToOffset(newSelection);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!myMousePressedInsideSelection) {
|
|
selectionModel.setSelection(oldSelectionStart, newCaretOffset);
|
|
}
|
|
else {
|
|
if (caretShift != 0) {
|
|
if (myMousePressedEvent != null) {
|
|
if (mySettings.isDndEnabled()) {
|
|
boolean isCopy = isControlKeyDown(e) || isViewer() || !getDocument().isWritable();
|
|
mySavedCaretOffsetForDNDUndoHack = oldCaretOffset;
|
|
getContentComponent().getTransferHandler().exportAsDrag(getContentComponent(), e,
|
|
isCopy
|
|
? TransferHandler.COPY
|
|
: TransferHandler.MOVE);
|
|
}
|
|
else {
|
|
selectionModel.removeSelection();
|
|
}
|
|
myMousePressedEvent = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
myScrollingTimer.start(dx, dy);
|
|
}
|
|
}
|
|
|
|
private static class RepaintCursorThread extends Thread {
|
|
private long mySleepTime = 500;
|
|
private boolean myIsBlinkCaret = true;
|
|
private EditorImpl myEditor = null;
|
|
private boolean isStopped = false;
|
|
private MyRepaintRunnable myRepaintRunnable;
|
|
|
|
public RepaintCursorThread() {
|
|
super("EditorCaretThread");
|
|
myRepaintRunnable = new MyRepaintRunnable();
|
|
}
|
|
|
|
private class MyRepaintRunnable implements Runnable {
|
|
public void run() {
|
|
if (myEditor != null) {
|
|
myEditor.myCaretCursor.repaint();
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setBlinkPeriod(int blinkPeriod) {
|
|
mySleepTime = blinkPeriod > 10 ? blinkPeriod : 10;
|
|
}
|
|
|
|
public void setBlinkCaret(boolean value) {
|
|
myIsBlinkCaret = value;
|
|
}
|
|
|
|
public synchronized void stopThread() {
|
|
isStopped = true;
|
|
}
|
|
|
|
public void run() {
|
|
while (true) {
|
|
try {
|
|
Thread.sleep(myIsBlinkCaret ? mySleepTime : 1000);
|
|
}
|
|
catch (InterruptedException e) {
|
|
}
|
|
|
|
synchronized (this) {
|
|
if (isStopped) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (myEditor == null) {
|
|
continue;
|
|
}
|
|
CaretCursor activeCursor = myEditor.myCaretCursor;
|
|
|
|
long time = System.currentTimeMillis();
|
|
time -= activeCursor.myStartTime;
|
|
|
|
if (time > mySleepTime) {
|
|
boolean toRepaint = true;
|
|
if (myIsBlinkCaret) {
|
|
activeCursor.isVisible = !activeCursor.isVisible;
|
|
}
|
|
else {
|
|
|
|
toRepaint = !activeCursor.isVisible;
|
|
activeCursor.isVisible = true;
|
|
}
|
|
|
|
if (toRepaint) {
|
|
SwingUtilities.invokeLater(myRepaintRunnable);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void updateCaretCursor() {
|
|
if (!IJSwingUtilities.hasFocus(getContentComponent())) {
|
|
stopOptimizedScrolling();
|
|
}
|
|
|
|
if (myCursorUpdater == null) {
|
|
myCursorUpdater = new Runnable() {
|
|
public void run() {
|
|
if (myCursorUpdater == null) return;
|
|
myCursorUpdater = null;
|
|
|
|
if (getDocument().getMarkupModel(myProject) == null) return;
|
|
|
|
VisualPosition caretPosition = getCaretModel().getVisualPosition();
|
|
Point pos1 = visualPositionToXY(caretPosition);
|
|
Point pos2 = visualPositionToXY(new VisualPosition(caretPosition.line, caretPosition.column + 1));
|
|
myCaretCursor.setPosition(pos1, pos2.x - pos1.x);
|
|
}
|
|
};
|
|
}
|
|
}
|
|
|
|
public void setCaretVisible(boolean b) {
|
|
if (b) {
|
|
myCaretCursor.activate();
|
|
}
|
|
else {
|
|
myCaretCursor.passivate();
|
|
}
|
|
}
|
|
|
|
public void addFocusListener(FocusChangeListener listener) {
|
|
myFocusListeners.add(listener);
|
|
}
|
|
|
|
public Project getProject() {
|
|
return myProject;
|
|
}
|
|
|
|
public boolean isOneLineMode() {
|
|
return myIsOneLineMode;
|
|
}
|
|
|
|
public boolean isEmbeddedIntoDialogWrapper() {
|
|
return myEmbeddedIntoDialogWrapper;
|
|
}
|
|
|
|
public void setEmbeddedIntoDialogWrapper(boolean b) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
|
|
myEmbeddedIntoDialogWrapper = b;
|
|
myScrollPane.setFocusable(!b);
|
|
myEditorComponent.setFocusCycleRoot(!b);
|
|
myEditorComponent.setFocusable(b);
|
|
}
|
|
|
|
public void setOneLineMode(boolean isOneLineMode) {
|
|
myIsOneLineMode = isOneLineMode;
|
|
getScrollPane().setInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, null);
|
|
reinitSettings();
|
|
}
|
|
|
|
public void stopOptimizedScrolling() {
|
|
if (SystemInfo.isMac) return;
|
|
myEditorComponent.setOpaque(false);
|
|
}
|
|
|
|
private void startOptimizedScrolling() {
|
|
if (SystemInfo.isMac) return;
|
|
myEditorComponent.setOpaque(true);
|
|
}
|
|
|
|
private class CaretCursor {
|
|
private Point myLocation;
|
|
private int myWidth;
|
|
private boolean isVisible = true;
|
|
private long myStartTime = 0;
|
|
|
|
public CaretCursor() {
|
|
myLocation = new Point(0, 0);
|
|
}
|
|
|
|
public void activate() {
|
|
synchronized (ourCaretThread) {
|
|
ourCaretThread.myEditor = EditorImpl.this;
|
|
ourCaretThread.setBlinkCaret(mySettings.isBlinkCaret());
|
|
ourCaretThread.setBlinkPeriod(mySettings.getCaretBlinkPeriod());
|
|
isVisible = true;
|
|
}
|
|
}
|
|
|
|
public void passivate() {
|
|
synchronized (ourCaretThread) {
|
|
isVisible = false;
|
|
}
|
|
}
|
|
|
|
public void setPosition(Point location, int width) {
|
|
myStartTime = System.currentTimeMillis();
|
|
myLocation = location;
|
|
isVisible = true;
|
|
myWidth = Math.max(width, 2);
|
|
repaint();
|
|
}
|
|
|
|
private void repaint() {
|
|
myEditorComponent.repaintEditorComponent(myLocation.x,
|
|
myLocation.y,
|
|
myWidth,
|
|
getLineHeight());
|
|
}
|
|
|
|
public void paint(Graphics g) {
|
|
if (!isVisible || !IJSwingUtilities.hasFocus(getContentComponent()) || isRendererMode()) return;
|
|
|
|
int x = myLocation.x;
|
|
int lineHeight = getLineHeight();
|
|
int y = myLocation.y;
|
|
|
|
Rectangle viewRect = getScrollingModel().getVisibleArea();
|
|
if (x - viewRect.x < 0) {
|
|
return;
|
|
}
|
|
|
|
|
|
g.setColor(myScheme.getColor(EditorColors.CARET_COLOR));
|
|
if (!SystemInfo.isMac) {
|
|
Color background = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
|
|
if (background == null) background = getBackroundColor();
|
|
g.setXORMode(background);
|
|
}
|
|
|
|
if (EditorImpl.this.myIsInsertMode != mySettings.isBlockCursor()) {
|
|
for (int i = 0; i < mySettings.getLineCursorWidth(); i++) {
|
|
g.drawLine(x + i, y, x + i, y + lineHeight - 1);
|
|
}
|
|
}
|
|
else {
|
|
g.fillRect(x, y, myWidth, lineHeight - 1);
|
|
}
|
|
|
|
g.setPaintMode();
|
|
}
|
|
}
|
|
|
|
private class ScrollingTimer {
|
|
Timer myTimer;
|
|
private static final int TIMER_PERIOD = 100;
|
|
private static final int CYCLE_SIZE = 20;
|
|
private int myXCycles;
|
|
private int myYCycles;
|
|
private int myDx;
|
|
private int myDy;
|
|
private int xPassedCycles = 0;
|
|
private int yPassedCycles = 0;
|
|
|
|
public void start(int dx, int dy) {
|
|
myDx = 0;
|
|
myDy = 0;
|
|
if (dx > 0) {
|
|
myXCycles = CYCLE_SIZE / dx + 1;
|
|
myDx = 1 + dx / CYCLE_SIZE;
|
|
}
|
|
else {
|
|
if (dx < 0) {
|
|
myXCycles = -CYCLE_SIZE / dx + 1;
|
|
myDx = -1 + dx / CYCLE_SIZE;
|
|
}
|
|
}
|
|
|
|
if (dy > 0) {
|
|
myYCycles = CYCLE_SIZE / dy + 1;
|
|
myDy = 1 + dy / CYCLE_SIZE;
|
|
}
|
|
else {
|
|
if (dy < 0) {
|
|
myYCycles = -CYCLE_SIZE / dy + 1;
|
|
myDy = -1 + dy / CYCLE_SIZE;
|
|
}
|
|
}
|
|
|
|
if (myTimer != null) {
|
|
return;
|
|
}
|
|
myTimer = new Timer(TIMER_PERIOD,
|
|
new ActionListener() {
|
|
public void actionPerformed(ActionEvent e) {
|
|
myCommandProcessor.executeCommand(myProject, new Runnable() {
|
|
public void run() {
|
|
int oldSelectionStart = mySelectionModel.getLeadSelectionOffset();
|
|
LogicalPosition caretPosition = getCaretModel().getLogicalPosition();
|
|
int columnNumber = caretPosition.column;
|
|
xPassedCycles++;
|
|
if (xPassedCycles >= myXCycles) {
|
|
xPassedCycles = 0;
|
|
columnNumber += myDx;
|
|
}
|
|
|
|
int lineNumber = caretPosition.line;
|
|
yPassedCycles++;
|
|
if (yPassedCycles >= myYCycles) {
|
|
yPassedCycles = 0;
|
|
lineNumber += myDy;
|
|
}
|
|
|
|
LogicalPosition pos = new LogicalPosition(lineNumber, columnNumber);
|
|
getCaretModel().moveToLogicalPosition(pos);
|
|
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
|
|
|
int newCaretOffset = getCaretModel().getOffset();
|
|
int caretShift = newCaretOffset - mySavedSelectionStart;
|
|
|
|
if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) {
|
|
if (caretShift < 0) {
|
|
int newSelection = newCaretOffset;
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
|
|
newSelection = mySelectionModel.getWordAtCaretStart();
|
|
}
|
|
else {
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
|
|
newSelection = logicalPositionToOffset(visualToLogicalPosition(
|
|
new VisualPosition(getCaretModel().getVisualPosition().line, 0)));
|
|
}
|
|
}
|
|
if (newSelection < 0) newSelection = newCaretOffset;
|
|
mySelectionModel.setSelection(mySavedSelectionEnd, newSelection);
|
|
getCaretModel().moveToOffset(newSelection);
|
|
}
|
|
else {
|
|
int newSelection = newCaretOffset;
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) {
|
|
newSelection = mySelectionModel.getWordAtCaretEnd();
|
|
}
|
|
else {
|
|
if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) {
|
|
newSelection = logicalPositionToOffset(visualToLogicalPosition(
|
|
new VisualPosition(getCaretModel().getVisualPosition().line + 1, 0)));
|
|
}
|
|
}
|
|
if (newSelection < 0) newSelection = newCaretOffset;
|
|
mySelectionModel.setSelection(mySavedSelectionStart, newSelection);
|
|
getCaretModel().moveToOffset(newSelection);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (mySelectionModel.hasBlockSelection()) {
|
|
mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(),
|
|
getCaretModel().getLogicalPosition());
|
|
}
|
|
else {
|
|
mySelectionModel.setSelection(oldSelectionStart, getCaretModel().getOffset());
|
|
}
|
|
}
|
|
},
|
|
"Move Cursor", null);
|
|
}
|
|
});
|
|
myTimer.start();
|
|
}
|
|
|
|
public void stop() {
|
|
if (myTimer != null) {
|
|
myTimer.stop();
|
|
myTimer = null;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
class MyScrollBar extends JScrollBar {
|
|
|
|
public MyScrollBar(int orientation) {
|
|
super(orientation);
|
|
setFocusable(false);
|
|
}
|
|
|
|
/**
|
|
* This is helper method. It returns height of the top (descrease) scrollbar
|
|
* button. Please note, that it's possible to return real height only if scrollbar
|
|
* is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
|
|
* value.
|
|
*/
|
|
int getDecScrollButtonHeight() {
|
|
ScrollBarUI ui = getUI();
|
|
Insets insets = getInsets();
|
|
if (ui instanceof BasicScrollBarUI) {
|
|
try {
|
|
Field decrButtonField = BasicScrollBarUI.class.getDeclaredField("decrButton");
|
|
decrButtonField.setAccessible(true);
|
|
JButton decrButtonValue = (JButton)decrButtonField.get(ui);
|
|
LOG.assertTrue(decrButtonValue != null);
|
|
return insets.top + decrButtonValue.getHeight();
|
|
}
|
|
catch (Exception exc) {
|
|
exc.printStackTrace();
|
|
throw new IllegalStateException(exc.getMessage());
|
|
}
|
|
}
|
|
else {
|
|
return insets.top + 15;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* This is helper method. It returns height of the bottom (increase) scrollbar
|
|
* button. Please note, that it's possible to return real height only if scrollbar
|
|
* is instance of BasicScrollBarUI. Otherwise it returns fake (but good enough :) )
|
|
* value.
|
|
*/
|
|
int getIncScrollButtonHeight() {
|
|
ScrollBarUI ui = getUI();
|
|
Insets insets = getInsets();
|
|
if (ui instanceof BasicScrollBarUI) {
|
|
try {
|
|
Field incrButtonField = BasicScrollBarUI.class.getDeclaredField("incrButton");
|
|
incrButtonField.setAccessible(true);
|
|
JButton incrButtonValue = (JButton)incrButtonField.get(ui);
|
|
LOG.assertTrue(incrButtonValue != null);
|
|
return insets.bottom + incrButtonValue.getHeight();
|
|
}
|
|
catch (Exception exc) {
|
|
exc.printStackTrace();
|
|
throw new IllegalStateException(exc.getMessage());
|
|
}
|
|
}
|
|
else if ("apple.laf.AquaScrollBarUI".equals(ui.getClass().getName())) {
|
|
return insets.bottom + 30;
|
|
}
|
|
else {
|
|
return insets.bottom + 15;
|
|
}
|
|
}
|
|
|
|
public int getUnitIncrement(int direction) {
|
|
JViewport vp = myScrollPane.getViewport();
|
|
Rectangle vr = vp.getViewRect();
|
|
return myEditorComponent.getScrollableUnitIncrement(vr, SwingConstants.VERTICAL, direction);
|
|
}
|
|
|
|
public int getBlockIncrement(int direction) {
|
|
JViewport vp = myScrollPane.getViewport();
|
|
Rectangle vr = vp.getViewRect();
|
|
return myEditorComponent.getScrollableBlockIncrement(vr, SwingConstants.VERTICAL, direction);
|
|
}
|
|
}
|
|
|
|
private MyEditable getViewer() {
|
|
if (myEditable == null) {
|
|
myEditable = new MyEditable();
|
|
}
|
|
return myEditable;
|
|
}
|
|
|
|
public CopyProvider getCopyProvider() {
|
|
return getViewer();
|
|
}
|
|
|
|
public CutProvider getCutProvider() {
|
|
return getViewer();
|
|
}
|
|
|
|
public PasteProvider getPasteProvider() {
|
|
|
|
return getViewer();
|
|
}
|
|
|
|
public DeleteProvider getDeleteProvider() {
|
|
return getViewer();
|
|
}
|
|
|
|
private class MyEditable implements CutProvider, CopyProvider, PasteProvider, DeleteProvider {
|
|
public void performCopy(DataContext dataContext) {
|
|
executeAction(IdeActions.ACTION_EDITOR_COPY, dataContext);
|
|
}
|
|
|
|
public boolean isCopyEnabled(DataContext dataContext) {
|
|
return true;
|
|
}
|
|
|
|
public void performCut(DataContext dataContext) {
|
|
executeAction(IdeActions.ACTION_EDITOR_CUT, dataContext);
|
|
}
|
|
|
|
public boolean isCutEnabled(DataContext dataContext) {
|
|
return !isViewer() && getDocument().isWritable();
|
|
}
|
|
|
|
public void performPaste(DataContext dataContext) {
|
|
executeAction(IdeActions.ACTION_EDITOR_PASTE, dataContext);
|
|
}
|
|
|
|
public boolean isPastePossible(DataContext dataContext) {
|
|
// Copy of isPasteEnabled. See interface method javadoc.
|
|
return !isViewer() && getDocument().isWritable();
|
|
}
|
|
|
|
public boolean isPasteEnabled(DataContext dataContext) {
|
|
return !isViewer() && getDocument().isWritable();
|
|
}
|
|
|
|
public void deleteElement(DataContext dataContext) {
|
|
executeAction(IdeActions.ACTION_EDITOR_DELETE, dataContext);
|
|
}
|
|
|
|
public boolean canDeleteElement(DataContext dataContext) {
|
|
return !isViewer() && getDocument().isWritable();
|
|
}
|
|
|
|
private void executeAction(String actionId, DataContext dataContext) {
|
|
EditorAction action = (EditorAction)ActionManager.getInstance().getAction(actionId);
|
|
if (action != null) {
|
|
action.actionPerformed(EditorImpl.this, dataContext);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void setColorsScheme(EditorColorsScheme scheme) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
myScheme = scheme;
|
|
reinitSettings();
|
|
}
|
|
|
|
public EditorColorsScheme getColorsScheme() {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
return myScheme;
|
|
}
|
|
|
|
public void setVerticalScrollbarOrientation(int type) {
|
|
ThreadingAssertions.assertEventDispatchThread();
|
|
int currentHorOffset = myScrollingModel.getHorizontalScrollOffset();
|
|
myScrollbarOrientation = type;
|
|
if (type == EditorEx.VERTICAL_SCROLLBAR_LEFT) {
|
|
myScrollPane.setLayout(new LeftHandScrollbarLayout());
|
|
}
|
|
else {
|
|
myScrollPane.setLayout(new ScrollPaneLayout());
|
|
}
|
|
myScrollingModel.scrollHorizontally(currentHorOffset);
|
|
}
|
|
|
|
public void setVerticalScrollbarVisible(boolean b) {
|
|
if (b) {
|
|
myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
|
|
}
|
|
else {
|
|
myScrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
|
|
}
|
|
}
|
|
|
|
public void setHorizontalScrollbarVisible(boolean b) {
|
|
if (b) {
|
|
myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
|
|
}
|
|
else {
|
|
myScrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
|
|
}
|
|
}
|
|
|
|
public int getVerticalScrollbarOrientation() {
|
|
return myScrollbarOrientation;
|
|
}
|
|
|
|
public MyScrollBar getVerticalScrollBar() {
|
|
return myVerticalScrollBar;
|
|
}
|
|
|
|
public JPanel getPanel() {
|
|
return myPanel;
|
|
}
|
|
|
|
private int getMouseSelectionState() {
|
|
return myMouseSelectionState;
|
|
}
|
|
|
|
private void setMouseSelectionState(int mouseSelectionState) {
|
|
myMouseSelectionState = mouseSelectionState;
|
|
myMouseSelectionChangeTimestamp = System.currentTimeMillis();
|
|
}
|
|
|
|
|
|
public void replaceInputMethodText(InputMethodEvent e) {
|
|
getInputMethodRequests();
|
|
myInputMethodRequestsHandler.replaceInputMethodText(e);
|
|
}
|
|
|
|
public void inputMethodCaretPositionChanged(InputMethodEvent e) {
|
|
getInputMethodRequests();
|
|
myInputMethodRequestsHandler.setInputMethodCaretPosition(e);
|
|
}
|
|
|
|
public InputMethodRequests getInputMethodRequests() {
|
|
if (myInputMethodRequestsHandler == null) {
|
|
myInputMethodRequestsHandler = new MyInputMethodHandler();
|
|
myInputMethodRequestsSwingWrapper = new MyInputMethodHandleSwingThreadWrapper(myInputMethodRequestsHandler);
|
|
}
|
|
return myInputMethodRequestsSwingWrapper;
|
|
}
|
|
|
|
public boolean processKeyTyped(KeyEvent e) {
|
|
if (e.getID() != KeyEvent.KEY_TYPED) return false;
|
|
char c = e.getKeyChar();
|
|
if (isReallyTypedEvent(e) && c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
|
|
processKeyTyped(c);
|
|
return true;
|
|
}
|
|
else {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static boolean isReallyTypedEvent(KeyEvent e) {
|
|
int modifiers = e.getModifiers();
|
|
if (SystemInfo.isMac) {
|
|
return !e.isMetaDown() && !e.isControlDown();
|
|
}
|
|
|
|
return (modifiers & ActionEvent.ALT_MASK) == (modifiers & ActionEvent.CTRL_MASK);
|
|
}
|
|
|
|
public void beforeModalityStateChanged() {
|
|
myScrollingModel.beforeModalityStateChanged();
|
|
}
|
|
|
|
private static class MyInputMethodHandleSwingThreadWrapper implements InputMethodRequests {
|
|
private InputMethodRequests myDelegate;
|
|
|
|
public MyInputMethodHandleSwingThreadWrapper(InputMethodRequests delegate) {
|
|
myDelegate = delegate;
|
|
}
|
|
|
|
public Rectangle getTextLocation(final TextHitInfo offset) {
|
|
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getTextLocation(offset);
|
|
|
|
final Rectangle[] r = new Rectangle[1];
|
|
try {
|
|
SwingUtilities.invokeAndWait(new Runnable() {
|
|
public void run() {
|
|
r[0] = myDelegate.getTextLocation(offset);
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
LOG.error(e);
|
|
}
|
|
return r[0];
|
|
}
|
|
|
|
public TextHitInfo getLocationOffset(final int x, final int y) {
|
|
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getLocationOffset(x, y);
|
|
|
|
final TextHitInfo[] r = new TextHitInfo[1];
|
|
try {
|
|
SwingUtilities.invokeAndWait(new Runnable() {
|
|
public void run() {
|
|
r[0] = myDelegate.getLocationOffset(x, y);
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
LOG.error(e);
|
|
}
|
|
return r[0];
|
|
}
|
|
|
|
public int getInsertPositionOffset() {
|
|
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getInsertPositionOffset();
|
|
|
|
final int[] r = new int[1];
|
|
try {
|
|
SwingUtilities.invokeAndWait(new Runnable() {
|
|
public void run() {
|
|
r[0] = myDelegate.getInsertPositionOffset();
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
LOG.error(e);
|
|
}
|
|
return r[0];
|
|
}
|
|
|
|
public AttributedCharacterIterator getCommittedText(final int beginIndex, final int endIndex,
|
|
final AttributedCharacterIterator.Attribute[] attributes) {
|
|
if (ApplicationManager.getApplication().isDispatchThread()) {
|
|
return myDelegate.getCommittedText(beginIndex,
|
|
endIndex,
|
|
attributes);
|
|
}
|
|
final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
|
|
try {
|
|
SwingUtilities.invokeAndWait(new Runnable() {
|
|
public void run() {
|
|
r[0] = myDelegate.getCommittedText(beginIndex, endIndex, attributes);
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
LOG.error(e);
|
|
}
|
|
return r[0];
|
|
}
|
|
|
|
public int getCommittedTextLength() {
|
|
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getCommittedTextLength();
|
|
final int[] r = new int[1];
|
|
try {
|
|
SwingUtilities.invokeAndWait(new Runnable() {
|
|
public void run() {
|
|
r[0] = myDelegate.getCommittedTextLength();
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
LOG.error(e);
|
|
}
|
|
return r[0];
|
|
}
|
|
|
|
public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
|
|
return null;
|
|
}
|
|
|
|
public AttributedCharacterIterator getSelectedText(final AttributedCharacterIterator.Attribute[] attributes) {
|
|
if (ApplicationManager.getApplication().isDispatchThread()) return myDelegate.getSelectedText(attributes);
|
|
|
|
final AttributedCharacterIterator[] r = new AttributedCharacterIterator[1];
|
|
try {
|
|
SwingUtilities.invokeAndWait(new Runnable() {
|
|
public void run() {
|
|
r[0] = myDelegate.getSelectedText(attributes);
|
|
}
|
|
});
|
|
}
|
|
catch (InterruptedException e) {
|
|
LOG.error(e);
|
|
}
|
|
catch (InvocationTargetException e) {
|
|
LOG.error(e);
|
|
}
|
|
return r[0];
|
|
}
|
|
}
|
|
|
|
private class MyInputMethodHandler implements InputMethodRequests {
|
|
private String composedText;
|
|
private int composedTextStart;
|
|
private int composedTextEnd;
|
|
|
|
public Rectangle getTextLocation(TextHitInfo offset) {
|
|
Point caret = logicalPositionToXY(getCaretModel().getLogicalPosition());
|
|
Rectangle r = new Rectangle(caret, new Dimension(1, getLineHeight()));
|
|
Point p = getContentComponent().getLocationOnScreen();
|
|
r.translate(p.x, p.y);
|
|
|
|
return r;
|
|
}
|
|
|
|
public TextHitInfo getLocationOffset(int x, int y) {
|
|
if (composedText != null) {
|
|
Point p = getContentComponent().getLocationOnScreen();
|
|
p.x = x - p.x;
|
|
p.y = y - p.y;
|
|
int pos = logicalPositionToOffset(xyToLogicalPosition(p));
|
|
if (pos >= composedTextStart && pos <= composedTextEnd) {
|
|
return TextHitInfo.leading(pos - composedTextStart);
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public int getInsertPositionOffset() {
|
|
int composedStartIndex = 0;
|
|
int composedEndIndex = 0;
|
|
if (composedText != null) {
|
|
composedStartIndex = composedTextStart;
|
|
composedEndIndex = composedTextEnd;
|
|
}
|
|
|
|
int caretIndex = getCaretModel().getOffset();
|
|
|
|
if (caretIndex < composedStartIndex) {
|
|
return caretIndex;
|
|
}
|
|
else {
|
|
if (caretIndex < composedEndIndex) {
|
|
return composedStartIndex;
|
|
}
|
|
else {
|
|
return caretIndex - (composedEndIndex - composedStartIndex);
|
|
}
|
|
}
|
|
}
|
|
|
|
private String getText(int startIdx, int endIdx) {
|
|
CharSequence chars = getDocument().getCharsSequence();
|
|
return chars.subSequence(startIdx, endIdx).toString();
|
|
}
|
|
|
|
public AttributedCharacterIterator getCommittedText(int beginIndex, int endIndex,
|
|
AttributedCharacterIterator.Attribute[] attributes) {
|
|
int composedStartIndex = 0;
|
|
int composedEndIndex = 0;
|
|
if (composedText != null) {
|
|
composedStartIndex = composedTextStart;
|
|
composedEndIndex = composedTextEnd;
|
|
}
|
|
|
|
String committed;
|
|
if (beginIndex < composedStartIndex) {
|
|
if (endIndex <= composedStartIndex) {
|
|
committed = getText(beginIndex, endIndex - beginIndex);
|
|
}
|
|
else {
|
|
int firstPartLength = composedStartIndex - beginIndex;
|
|
committed = getText(beginIndex, firstPartLength) +
|
|
getText(composedEndIndex, endIndex - beginIndex - firstPartLength);
|
|
}
|
|
}
|
|
else {
|
|
committed = getText(beginIndex + (composedEndIndex - composedStartIndex),
|
|
endIndex - beginIndex);
|
|
}
|
|
|
|
return new AttributedString(committed).getIterator();
|
|
}
|
|
|
|
public int getCommittedTextLength() {
|
|
int length = getDocument().getTextLength();
|
|
if (composedText != null) {
|
|
length -= composedText.length();
|
|
}
|
|
return length;
|
|
}
|
|
|
|
public AttributedCharacterIterator cancelLatestCommittedText(AttributedCharacterIterator.Attribute[] attributes) {
|
|
return null;
|
|
}
|
|
|
|
public AttributedCharacterIterator getSelectedText(AttributedCharacterIterator.Attribute[] attributes) {
|
|
String text = getSelectionModel().getSelectedText();
|
|
return text == null ? null : new AttributedString(text).getIterator();
|
|
}
|
|
|
|
private void createComposedString(int composedIndex, AttributedCharacterIterator text) {
|
|
StringBuffer strBuf = new StringBuffer();
|
|
|
|
// create attributed string with no attributes
|
|
for (char c = text.setIndex(composedIndex);
|
|
c != CharacterIterator.DONE; c = text.next()) {
|
|
strBuf.append(c);
|
|
}
|
|
|
|
composedText = new String(strBuf);
|
|
}
|
|
|
|
private void setInputMethodCaretPosition(InputMethodEvent e) {
|
|
if (composedText != null) {
|
|
int dot;
|
|
dot = composedTextStart;
|
|
|
|
TextHitInfo caretPos = e.getCaret();
|
|
if (caretPos != null) {
|
|
int index = caretPos.getInsertionIndex();
|
|
dot += index;
|
|
}
|
|
|
|
getCaretModel().moveToOffset(dot);
|
|
getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
|
}
|
|
}
|
|
|
|
private void runUndoTransparent(final Runnable runnable) {
|
|
CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
|
|
public void run() {
|
|
CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
|
|
public void run() {
|
|
ApplicationManager.getApplication().runWriteAction(runnable);
|
|
}
|
|
}, "", null);
|
|
}
|
|
});
|
|
}
|
|
|
|
private void replaceInputMethodText(InputMethodEvent e) {
|
|
int commitCount = e.getCommittedCharacterCount();
|
|
AttributedCharacterIterator text = e.getText();
|
|
int composedTextIndex;
|
|
|
|
// old composed text deletion
|
|
final Document doc = getDocument();
|
|
|
|
if (composedText != null) {
|
|
if (!isViewer() && doc.isWritable()) {
|
|
runUndoTransparent(new Runnable() {
|
|
public void run() {
|
|
doc.deleteString(Math.max(0, composedTextStart), Math.min(composedTextEnd, doc.getTextLength()));
|
|
}
|
|
});
|
|
}
|
|
composedText = null;
|
|
}
|
|
|
|
if (text != null) {
|
|
text.first();
|
|
|
|
// committed text insertion
|
|
if (commitCount > 0) {
|
|
for (char c = text.current(); commitCount > 0; c = text.next(), commitCount--) {
|
|
if (c >= 0x20 && c != 0x7F) { // Hack just like in javax.swing.text.DefaultEditorKit.DefaultKeyTypedAction
|
|
processKeyTyped(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
// new composed text insertion
|
|
if (!isViewer() && doc.isWritable()) {
|
|
composedTextIndex = text.getIndex();
|
|
if (composedTextIndex < text.getEndIndex()) {
|
|
createComposedString(composedTextIndex, text);
|
|
|
|
runUndoTransparent(new Runnable() {
|
|
public void run() {
|
|
EditorModificationUtil.insertStringAtCaret(EditorImpl.this, composedText, false, false);
|
|
}
|
|
});
|
|
|
|
composedTextStart = getCaretModel().getOffset();
|
|
composedTextEnd = getCaretModel().getOffset() + composedText.length();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private class MyMouseAdapter extends MouseAdapter {
|
|
public void mousePressed(MouseEvent e) {
|
|
runMousePressedCommand(e);
|
|
}
|
|
|
|
public void mouseReleased(MouseEvent e) {
|
|
runMouseReleasedCommand(e);
|
|
if (!e.isConsumed() && myMousePressedEvent != null && !myMousePressedEvent.isConsumed() &&
|
|
Math.abs(e.getX() - myMousePressedEvent.getX()) < getSpaceWidth(Font.PLAIN) &&
|
|
Math.abs(e.getY() - myMousePressedEvent.getY()) < getLineHeight()) {
|
|
runMouseClickedCommand(e);
|
|
}
|
|
myMousePressedEvent = null;
|
|
}
|
|
|
|
public void mouseEntered(MouseEvent e) {
|
|
runMouseEnteredCommand(e);
|
|
}
|
|
|
|
public void mouseExited(MouseEvent e) {
|
|
runMouseExitedCommand(e);
|
|
EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
|
|
if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
|
|
myGutterComponent.mouseExited(e);
|
|
}
|
|
|
|
HintManager.getInstance().getTooltipController().cancelTooltip(FOLDING_TOOLTIP_GROUP);
|
|
}
|
|
}
|
|
|
|
private static final TooltipGroup FOLDING_TOOLTIP_GROUP = new TooltipGroup("FOLDING_TOOLTIP_GROUP", 10);
|
|
|
|
private class MyMouseMotionListener implements MouseMotionListener {
|
|
public void mouseDragged(MouseEvent e) {
|
|
validateMousePointer(e);
|
|
runMouseDraggedCommand(e);
|
|
EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
|
|
if (event.getArea() == EditorMouseEventArea.LINE_MARKERS_AREA) {
|
|
myGutterComponent.mouseDragged(e);
|
|
}
|
|
|
|
EditorMouseMotionListener[] listeners = myMouseMotionListeners.toArray(
|
|
new EditorMouseMotionListener[myMouseMotionListeners.size()]);
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
listeners[i].mouseDragged(event);
|
|
}
|
|
}
|
|
|
|
public void mouseMoved(MouseEvent e) {
|
|
validateMousePointer(e);
|
|
EditorMouseEvent event = new EditorMouseEvent(EditorImpl.this, e, getMouseEventArea(e));
|
|
if (e.getSource() == myGutterComponent) {
|
|
myGutterComponent.mouseMoved(e);
|
|
}
|
|
|
|
if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
|
|
FoldRegion fold = myFoldingModel.getFoldingPlaceholderAt(e.getPoint());
|
|
TooltipController controller = HintManager.getInstance().getTooltipController();
|
|
if (fold != null) {
|
|
DocumentFragment range = new DocumentFragment(myDocument, fold.getStartOffset(), fold.getEndOffset());
|
|
final Point p = SwingUtilities.convertPoint((Component)e.getSource(),
|
|
e.getPoint(),
|
|
getComponent().getRootPane().getLayeredPane());
|
|
controller.showTooltip(EditorImpl.this, p, new DocumentFragmentTooltipRenderer(range), false, FOLDING_TOOLTIP_GROUP);
|
|
}
|
|
else {
|
|
controller.cancelTooltip(FOLDING_TOOLTIP_GROUP);
|
|
}
|
|
}
|
|
|
|
EditorMouseMotionListener[] listeners = myMouseMotionListeners.toArray(
|
|
new EditorMouseMotionListener[myMouseMotionListeners.size()]);
|
|
for (int i = 0; i < listeners.length; i++) {
|
|
listeners[i].mouseMoved(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
private class MyColorSchemeDelegate implements EditorColorsScheme {
|
|
private HashMap<TextAttributesKey, TextAttributes> myOwnAttributes = new HashMap<TextAttributesKey, TextAttributes>();
|
|
private HashMap<ColorKey, Color> myOwnColors = new HashMap<ColorKey, Color>();
|
|
private HashMap<EditorFontType, Font> myFontsMap = null;
|
|
private Integer myFontSize = null;
|
|
private String myFaceName = null;
|
|
|
|
|
|
private EditorColorsScheme getGlobal() {
|
|
return EditorColorsManager.getInstance().getGlobalScheme();
|
|
}
|
|
|
|
public String getName() {
|
|
return getGlobal().getName();
|
|
}
|
|
|
|
protected void initFonts() {
|
|
String editorFontName = getEditorFontName();
|
|
int editorFontSize = getEditorFontSize();
|
|
|
|
myFontsMap = new HashMap<EditorFontType, Font>();
|
|
|
|
Font plainFont = new Font(editorFontName, Font.PLAIN, editorFontSize);
|
|
Font boldFont = new Font(editorFontName, Font.BOLD, editorFontSize);
|
|
Font italicFont = new Font(editorFontName, Font.ITALIC, editorFontSize);
|
|
Font boldItalicFont = new Font(editorFontName, Font.BOLD + Font.ITALIC, editorFontSize);
|
|
|
|
myFontsMap.put(EditorFontType.PLAIN, plainFont);
|
|
myFontsMap.put(EditorFontType.BOLD, boldFont);
|
|
myFontsMap.put(EditorFontType.ITALIC, italicFont);
|
|
myFontsMap.put(EditorFontType.BOLD_ITALIC, boldItalicFont);
|
|
|
|
reinitSettings();
|
|
}
|
|
|
|
public void setName(String name) {
|
|
getGlobal().setName(name);
|
|
}
|
|
|
|
public TextAttributes getAttributes(TextAttributesKey key) {
|
|
if (myOwnAttributes.containsKey(key)) return myOwnAttributes.get(key);
|
|
return getGlobal().getAttributes(key);
|
|
}
|
|
|
|
public void setAttributes(TextAttributesKey key, TextAttributes attributes) {
|
|
myOwnAttributes.put(key, attributes);
|
|
}
|
|
|
|
public Color getDefaultBackground() {
|
|
return getGlobal().getDefaultBackground();
|
|
}
|
|
|
|
public Color getDefaultForeground() {
|
|
return getGlobal().getDefaultForeground();
|
|
}
|
|
|
|
public Color getColor(ColorKey key) {
|
|
if (myOwnColors.containsKey(key)) return myOwnColors.get(key);
|
|
return getGlobal().getColor(key);
|
|
}
|
|
|
|
public void setColor(ColorKey key, Color color) {
|
|
myOwnColors.put(key, color);
|
|
|
|
// These two are here because those attributes are cached and I do not whant the clients to call editor's reinit
|
|
// settings in this case.
|
|
myCaretModel.reinitSettings();
|
|
mySelectionModel.reinitSettings();
|
|
}
|
|
|
|
public int getEditorFontSize() {
|
|
if (myFontSize != null) return myFontSize.intValue();
|
|
return getGlobal().getEditorFontSize();
|
|
}
|
|
|
|
public void setEditorFontSize(int fontSize) {
|
|
if (fontSize < 8) fontSize = 8;
|
|
if (fontSize > 20) fontSize = 20;
|
|
myFontSize = new Integer(fontSize);
|
|
initFonts();
|
|
}
|
|
|
|
public String getEditorFontName() {
|
|
if (myFaceName != null) return myFaceName;
|
|
return getGlobal().getEditorFontName();
|
|
}
|
|
|
|
public void setEditorFontName(String fontName) {
|
|
myFaceName = fontName;
|
|
initFonts();
|
|
}
|
|
|
|
public Font getFont(EditorFontType key) {
|
|
if (myFontsMap != null) {
|
|
Font font = myFontsMap.get(key);
|
|
if (font != null) return font;
|
|
}
|
|
return getGlobal().getFont(key);
|
|
}
|
|
|
|
public void setFont(EditorFontType key, Font font) {
|
|
if (myFontsMap == null) {
|
|
initFonts();
|
|
}
|
|
myFontsMap.put(key, font);
|
|
reinitSettings();
|
|
}
|
|
|
|
public float getLineSpacing() {
|
|
return getGlobal().getLineSpacing();
|
|
}
|
|
|
|
public void setLineSpacing(float lineSpacing) {
|
|
getGlobal().setLineSpacing(lineSpacing);
|
|
}
|
|
|
|
public Object clone() {
|
|
return null;
|
|
}
|
|
|
|
public void readExternal(Element element) throws InvalidDataException {
|
|
}
|
|
|
|
public void writeExternal(Element element) throws WriteExternalException {
|
|
}
|
|
}
|
|
|
|
private static class MyTransferHandler extends TransferHandler {
|
|
private RangeMarker myDraggedRange = null;
|
|
|
|
private static Editor getEditor(JComponent comp) {
|
|
EditorComponentImpl editorComponent = (EditorComponentImpl)comp;
|
|
return editorComponent.getEditor();
|
|
}
|
|
|
|
public boolean importData(final JComponent comp, final Transferable t) {
|
|
final EditorImpl editor = (EditorImpl)getEditor(comp);
|
|
|
|
final int caretOffset = editor.getCaretModel().getOffset();
|
|
if (myDraggedRange != null &&
|
|
myDraggedRange.getStartOffset() <= caretOffset &&
|
|
caretOffset < myDraggedRange.getEndOffset()) {
|
|
return false;
|
|
}
|
|
|
|
if (myDraggedRange != null) {
|
|
editor.getCaretModel().moveToOffset(editor.mySavedCaretOffsetForDNDUndoHack);
|
|
}
|
|
|
|
CommandProcessor.getInstance().executeCommand(editor.myProject, new Runnable() {
|
|
public void run() {
|
|
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
|
public void run() {
|
|
try {
|
|
final int offset;
|
|
editor.getSelectionModel().removeSelection();
|
|
if (myDraggedRange != null) {
|
|
editor.getCaretModel().moveToOffset(caretOffset);
|
|
offset = caretOffset;
|
|
}
|
|
else {
|
|
offset = editor.getCaretModel().getOffset();
|
|
}
|
|
if (editor.getDocument().getRangeGuard(offset, offset) != null) return;
|
|
|
|
EditorActionHandler pasteHandler = EditorActionManager.getInstance().getActionHandler(
|
|
IdeActions.ACTION_EDITOR_PASTE);
|
|
Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
|
|
Transferable backup = clipboard.getContents(this);
|
|
ClipboardOwner clipboardOwner = new ClipboardOwner() {
|
|
public void lostOwnership(Clipboard clipboard, Transferable contents) {
|
|
}
|
|
};
|
|
clipboard.setContents(t, clipboardOwner);
|
|
|
|
editor.putUserData(LAST_PASTED_REGION, null);
|
|
pasteHandler.execute(editor, editor.getDataContext());
|
|
clipboard.setContents(backup, clipboardOwner);
|
|
|
|
TextRange range = editor.getUserData(LAST_PASTED_REGION);
|
|
if (range != null) {
|
|
editor.getCaretModel().moveToOffset(range.getStartOffset());
|
|
editor.getSelectionModel().setSelection(range.getStartOffset(), range.getEndOffset());
|
|
}
|
|
}
|
|
catch (Exception exception) {
|
|
LOG.error(exception);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}, "Paste", DND_COMMAND_KEY);
|
|
|
|
return true;
|
|
}
|
|
|
|
public boolean canImport(JComponent comp, DataFlavor[] transferFlavors) {
|
|
Editor editor = getEditor(comp);
|
|
if (editor.isViewer()) return false;
|
|
if (!editor.getDocument().isWritable()) return false;
|
|
|
|
int offset = editor.getCaretModel().getOffset();
|
|
if (editor.getDocument().getRangeGuard(offset, offset) != null) return false;
|
|
|
|
for (int i = 0; i < transferFlavors.length; i++) {
|
|
DataFlavor transferFlavor = transferFlavors[i];
|
|
if (transferFlavor.equals(DataFlavor.stringFlavor)) return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
protected Transferable createTransferable(JComponent c) {
|
|
Editor editor = getEditor(c);
|
|
String s = editor.getSelectionModel().getSelectedText();
|
|
if (s == null) return null;
|
|
int selectionStart = editor.getSelectionModel().getSelectionStart();
|
|
int selectionEnd = editor.getSelectionModel().getSelectionEnd();
|
|
myDraggedRange = editor.getDocument().createRangeMarker(selectionStart, selectionEnd);
|
|
|
|
return new StringSelection(s);
|
|
}
|
|
|
|
public int getSourceActions(JComponent c) {
|
|
return COPY_OR_MOVE;
|
|
}
|
|
|
|
protected void exportDone(final JComponent source, Transferable data, int action) {
|
|
if (data == null) return;
|
|
|
|
if (action == MOVE && !getEditor(source).isViewer() && getEditor(source).getDocument().isWritable()) {
|
|
CommandProcessor.getInstance().executeCommand(((EditorImpl)getEditor(source)).myProject, new Runnable() {
|
|
public void run() {
|
|
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
|
public void run() {
|
|
Document doc = getEditor(source).getDocument();
|
|
doc.startGuardedBlockChecking();
|
|
try {
|
|
doc.deleteString(myDraggedRange.getStartOffset(), myDraggedRange.getEndOffset());
|
|
}
|
|
catch (ReadOnlyFragmentModificationException e) {
|
|
EditorActionManager.getInstance().getReadonlyFragmentModificationHandler().handle(e);
|
|
}
|
|
finally {
|
|
doc.stopGuardedBlockChecking();
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}, "Move selection", DND_COMMAND_KEY);
|
|
}
|
|
|
|
myDraggedRange = null;
|
|
}
|
|
}
|
|
|
|
class EditorDocumentAdapter extends DocumentAdapter {
|
|
public void beforeDocumentChange(DocumentEvent e) {
|
|
beforeChangedUpdate(e);
|
|
}
|
|
|
|
public void documentChanged(DocumentEvent e) {
|
|
changedUpdate(e);
|
|
}
|
|
}
|
|
|
|
private class EditorSizeContainer {
|
|
private TIntArrayList myLineWidths;
|
|
private boolean myIsDirty;
|
|
private int myOldEndLine;
|
|
private Dimension mySize;
|
|
|
|
public void reset() {
|
|
int visLinesCount = getVisibleLineCount();
|
|
myLineWidths = new TIntArrayList(visLinesCount + 300);
|
|
int[] values = new int[visLinesCount];
|
|
Arrays.fill(values, -1);
|
|
myLineWidths.add(values);
|
|
myIsDirty = true;
|
|
}
|
|
|
|
public void beforeChange(DocumentEvent e) {
|
|
myOldEndLine = offsetToVisualPosition(e.getOffset() + e.getOldLength()).line;
|
|
}
|
|
|
|
public void changedUpdate(DocumentEvent e) {
|
|
int startLine = offsetToVisualPosition(e.getOffset()).line;
|
|
int newEndLine = offsetToVisualPosition(e.getOffset() + e.getNewLength()).line;
|
|
int oldEndLine = myOldEndLine;
|
|
|
|
if (myLineWidths.size() == 0) {
|
|
reset();
|
|
}
|
|
else {
|
|
int min = Math.min(oldEndLine, newEndLine);
|
|
for (int i = startLine; i <= min; i++) myLineWidths.set(i, -1);
|
|
if (newEndLine > oldEndLine) {
|
|
int[] delta = new int[newEndLine - oldEndLine];
|
|
Arrays.fill(delta, -1);
|
|
myLineWidths.insert(oldEndLine + 1, delta);
|
|
}
|
|
else if (oldEndLine > newEndLine) {
|
|
myLineWidths.remove(newEndLine + 1, oldEndLine - newEndLine);
|
|
}
|
|
myIsDirty = true;
|
|
}
|
|
}
|
|
|
|
private void validateSizes() {
|
|
if (!myIsDirty) return;
|
|
|
|
CharSequence text = myDocument.getCharsNoThreadCheck();
|
|
int lineCount = myLineWidths.size();
|
|
int end = myDocument.getTextLength();
|
|
int x = 0;
|
|
for (int line = 0; line < lineCount; line++) {
|
|
if (myLineWidths.getQuick(line) != -1) continue;
|
|
x = 0;
|
|
int offset = logicalPositionToOffset(visualToLogicalPosition(new VisualPosition(line, 0)));
|
|
|
|
if (offset >= myDocument.getTextLength()) {
|
|
myLineWidths.set(line, 0);
|
|
break;
|
|
}
|
|
|
|
IterationState state = new IterationState(EditorImpl.this, offset, false);
|
|
int fontType = state.getMergedAttributes().getFontType();
|
|
|
|
while (offset < end) {
|
|
char c = text.charAt(offset);
|
|
if (offset >= state.getEndOffset()) {
|
|
state.advance();
|
|
fontType = state.getMergedAttributes().getFontType();
|
|
}
|
|
|
|
FoldRegion collapsed = state.getCurrentFold();
|
|
if (collapsed != null) {
|
|
String placeholder = collapsed.getPlaceholderText();
|
|
for (int i = 0; i < placeholder.length(); i++) {
|
|
x += charWidth(placeholder.charAt(i), fontType);
|
|
}
|
|
offset = collapsed.getEndOffset();
|
|
}
|
|
else {
|
|
if (c == '\t') {
|
|
x = nextTabStop(x);
|
|
offset++;
|
|
}
|
|
else {
|
|
if (c == '\n') {
|
|
myLineWidths.set(line, x);
|
|
if (line + 1 >= lineCount || myLineWidths.getQuick(line + 1) != -1) break;
|
|
offset++;
|
|
x = 0;
|
|
line++;
|
|
}
|
|
else {
|
|
x += charWidth(c, fontType);
|
|
offset++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (lineCount > 0) {
|
|
myLineWidths.set(lineCount - 1, x); // Last line can be non-zero length and won't be caught by in-loop procedure since latter only react on \n's
|
|
}
|
|
|
|
int maxWidth = 0;
|
|
for (int i = 0; i < lineCount; i++) {
|
|
maxWidth = Math.max(maxWidth, myLineWidths.getQuick(i));
|
|
}
|
|
|
|
mySize = new Dimension(maxWidth, getLineHeight() * lineCount);
|
|
|
|
myIsDirty = false;
|
|
}
|
|
|
|
public Dimension getContentSize() {
|
|
validateSizes();
|
|
return mySize;
|
|
}
|
|
}
|
|
|
|
public EditorGutter getGutter() {
|
|
return getGutterComponentEx();
|
|
}
|
|
}
|