|
|
// Scintilla source code edit control |
|
|
/** @file Editor.cxx |
|
|
** Main code for the edit control. |
|
|
**/ |
|
|
// Copyright 1998-2004 by Neil Hodgson <neilh@scintilla.org> |
|
|
// The License.txt file describes the conditions under which this software may be distributed. |
|
|
|
|
|
#include <stdlib.h> |
|
|
#include <string.h> |
|
|
#include <stdio.h> |
|
|
#include <ctype.h> |
|
|
|
|
|
#include "Platform.h" |
|
|
|
|
|
#ifndef PLAT_QT |
|
|
#define INCLUDE_DEPRECATED_FEATURES |
|
|
#endif |
|
|
#include "Scintilla.h" |
|
|
|
|
|
#include "ContractionState.h" |
|
|
#include "SVector.h" |
|
|
#include "CellBuffer.h" |
|
|
#include "KeyMap.h" |
|
|
#include "Indicator.h" |
|
|
#include "XPM.h" |
|
|
#include "LineMarker.h" |
|
|
#include "Style.h" |
|
|
#include "ViewStyle.h" |
|
|
#include "CharClassify.h" |
|
|
#include "Document.h" |
|
|
#include "Editor.h" |
|
|
|
|
|
/* |
|
|
return whether this modification represents an operation that |
|
|
may reasonably be deferred (not done now OR [possibly] at all) |
|
|
*/ |
|
|
static bool CanDeferToLastStep(const DocModification& mh) { |
|
|
if (mh.modificationType & (SC_MOD_BEFOREINSERT|SC_MOD_BEFOREDELETE)) |
|
|
return true; // CAN skip |
|
|
if (!(mh.modificationType & (SC_PERFORMED_UNDO|SC_PERFORMED_REDO))) |
|
|
return false; // MUST do |
|
|
if (mh.modificationType & SC_MULTISTEPUNDOREDO) |
|
|
return true; // CAN skip |
|
|
return false; // PRESUMABLY must do |
|
|
} |
|
|
|
|
|
static bool CanEliminate(const DocModification& mh) { |
|
|
return |
|
|
(mh.modificationType & (SC_MOD_BEFOREINSERT|SC_MOD_BEFOREDELETE)) != 0; |
|
|
} |
|
|
|
|
|
/* |
|
|
return whether this modification represents the FINAL step |
|
|
in a [possibly lengthy] multi-step Undo/Redo sequence |
|
|
*/ |
|
|
static bool IsLastStep(const DocModification& mh) { |
|
|
return |
|
|
(mh.modificationType & (SC_PERFORMED_UNDO|SC_PERFORMED_REDO)) != 0 |
|
|
&& (mh.modificationType & SC_MULTISTEPUNDOREDO) != 0 |
|
|
&& (mh.modificationType & SC_LASTSTEPINUNDOREDO) != 0 |
|
|
&& (mh.modificationType & SC_MULTILINEUNDOREDO) != 0; |
|
|
} |
|
|
|
|
|
Caret::Caret() : |
|
|
active(false), on(false), period(500) {} |
|
|
|
|
|
Timer::Timer() : |
|
|
ticking(false), ticksToWait(0), tickerID(0) {} |
|
|
|
|
|
Idler::Idler() : |
|
|
state(false), idlerID(0) {} |
|
|
|
|
|
LineLayout::LineLayout(int maxLineLength_) : |
|
|
lineStarts(0), |
|
|
lenLineStarts(0), |
|
|
lineNumber(-1), |
|
|
inCache(false), |
|
|
maxLineLength(-1), |
|
|
numCharsInLine(0), |
|
|
validity(llInvalid), |
|
|
xHighlightGuide(0), |
|
|
highlightColumn(0), |
|
|
selStart(0), |
|
|
selEnd(0), |
|
|
containsCaret(false), |
|
|
edgeColumn(0), |
|
|
chars(0), |
|
|
styles(0), |
|
|
styleBitsSet(0), |
|
|
indicators(0), |
|
|
positions(0), |
|
|
hsStart(0), |
|
|
hsEnd(0), |
|
|
widthLine(wrapWidthInfinite), |
|
|
lines(1) { |
|
|
Resize(maxLineLength_); |
|
|
} |
|
|
|
|
|
LineLayout::~LineLayout() { |
|
|
Free(); |
|
|
} |
|
|
|
|
|
void LineLayout::Resize(int maxLineLength_) { |
|
|
if (maxLineLength_ > maxLineLength) { |
|
|
Free(); |
|
|
chars = new char[maxLineLength_ + 1]; |
|
|
styles = new unsigned char[maxLineLength_ + 1]; |
|
|
indicators = new char[maxLineLength_ + 1]; |
|
|
// Extra position allocated as sometimes the Windows |
|
|
// GetTextExtentExPoint API writes an extra element. |
|
|
positions = new int[maxLineLength_ + 1 + 1]; |
|
|
maxLineLength = maxLineLength_; |
|
|
} |
|
|
} |
|
|
|
|
|
void LineLayout::Free() { |
|
|
delete []chars; |
|
|
chars = 0; |
|
|
delete []styles; |
|
|
styles = 0; |
|
|
delete []indicators; |
|
|
indicators = 0; |
|
|
delete []positions; |
|
|
positions = 0; |
|
|
delete []lineStarts; |
|
|
lineStarts = 0; |
|
|
} |
|
|
|
|
|
void LineLayout::Invalidate(validLevel validity_) { |
|
|
if (validity > validity_) |
|
|
validity = validity_; |
|
|
} |
|
|
|
|
|
void LineLayout::SetLineStart(int line, int start) { |
|
|
if ((line >= lenLineStarts) && (line != 0)) { |
|
|
int newMaxLines = line + 20; |
|
|
int *newLineStarts = new int[newMaxLines]; |
|
|
if (!newLineStarts) |
|
|
return; |
|
|
for (int i = 0; i < newMaxLines; i++) { |
|
|
if (i < lenLineStarts) |
|
|
newLineStarts[i] = lineStarts[i]; |
|
|
else |
|
|
newLineStarts[i] = 0; |
|
|
} |
|
|
delete []lineStarts; |
|
|
lineStarts = newLineStarts; |
|
|
lenLineStarts = newMaxLines; |
|
|
} |
|
|
lineStarts[line] = start; |
|
|
} |
|
|
|
|
|
void LineLayout::SetBracesHighlight(Range rangeLine, Position braces[], |
|
|
char bracesMatchStyle, int xHighlight) { |
|
|
if (rangeLine.ContainsCharacter(braces[0])) { |
|
|
int braceOffset = braces[0] - rangeLine.start; |
|
|
if (braceOffset < numCharsInLine) { |
|
|
bracePreviousStyles[0] = styles[braceOffset]; |
|
|
styles[braceOffset] = bracesMatchStyle; |
|
|
} |
|
|
} |
|
|
if (rangeLine.ContainsCharacter(braces[1])) { |
|
|
int braceOffset = braces[1] - rangeLine.start; |
|
|
if (braceOffset < numCharsInLine) { |
|
|
bracePreviousStyles[1] = styles[braceOffset]; |
|
|
styles[braceOffset] = bracesMatchStyle; |
|
|
} |
|
|
} |
|
|
if ((braces[0] >= rangeLine.start && braces[1] <= rangeLine.end) || |
|
|
(braces[1] >= rangeLine.start && braces[0] <= rangeLine.end)) { |
|
|
xHighlightGuide = xHighlight; |
|
|
} |
|
|
} |
|
|
|
|
|
void LineLayout::RestoreBracesHighlight(Range rangeLine, Position braces[]) { |
|
|
if (rangeLine.ContainsCharacter(braces[0])) { |
|
|
int braceOffset = braces[0] - rangeLine.start; |
|
|
if (braceOffset < numCharsInLine) { |
|
|
styles[braceOffset] = bracePreviousStyles[0]; |
|
|
} |
|
|
} |
|
|
if (rangeLine.ContainsCharacter(braces[1])) { |
|
|
int braceOffset = braces[1] - rangeLine.start; |
|
|
if (braceOffset < numCharsInLine) { |
|
|
styles[braceOffset] = bracePreviousStyles[1]; |
|
|
} |
|
|
} |
|
|
xHighlightGuide = 0; |
|
|
} |
|
|
|
|
|
LineLayoutCache::LineLayoutCache() : |
|
|
level(0), length(0), size(0), cache(0), |
|
|
allInvalidated(false), styleClock(-1), useCount(0) { |
|
|
Allocate(0); |
|
|
} |
|
|
|
|
|
LineLayoutCache::~LineLayoutCache() { |
|
|
Deallocate(); |
|
|
} |
|
|
|
|
|
void LineLayoutCache::Allocate(int length_) { |
|
|
PLATFORM_ASSERT(cache == NULL); |
|
|
allInvalidated = false; |
|
|
length = length_; |
|
|
size = length; |
|
|
if (size > 1) { |
|
|
size = (size / 16 + 1) * 16; |
|
|
} |
|
|
if (size > 0) { |
|
|
cache = new LineLayout * [size]; |
|
|
} |
|
|
for (int i = 0; i < size; i++) |
|
|
cache[i] = 0; |
|
|
} |
|
|
|
|
|
void LineLayoutCache::AllocateForLevel(int linesOnScreen, int linesInDoc) { |
|
|
PLATFORM_ASSERT(useCount == 0); |
|
|
int lengthForLevel = 0; |
|
|
if (level == llcCaret) { |
|
|
lengthForLevel = 1; |
|
|
} else if (level == llcPage) { |
|
|
lengthForLevel = linesOnScreen + 1; |
|
|
} else if (level == llcDocument) { |
|
|
lengthForLevel = linesInDoc; |
|
|
} |
|
|
if (lengthForLevel > size) { |
|
|
Deallocate(); |
|
|
Allocate(lengthForLevel); |
|
|
} else { |
|
|
if (lengthForLevel < length) { |
|
|
for (int i = lengthForLevel; i < length; i++) { |
|
|
delete cache[i]; |
|
|
cache[i] = 0; |
|
|
} |
|
|
} |
|
|
length = lengthForLevel; |
|
|
} |
|
|
PLATFORM_ASSERT(length == lengthForLevel); |
|
|
PLATFORM_ASSERT(cache != NULL || length == 0); |
|
|
} |
|
|
|
|
|
void LineLayoutCache::Deallocate() { |
|
|
PLATFORM_ASSERT(useCount == 0); |
|
|
for (int i = 0; i < length; i++) |
|
|
delete cache[i]; |
|
|
delete []cache; |
|
|
cache = 0; |
|
|
length = 0; |
|
|
size = 0; |
|
|
} |
|
|
|
|
|
void LineLayoutCache::Invalidate(LineLayout::validLevel validity_) { |
|
|
if (cache && !allInvalidated) { |
|
|
for (int i = 0; i < length; i++) { |
|
|
if (cache[i]) { |
|
|
cache[i]->Invalidate(validity_); |
|
|
} |
|
|
} |
|
|
if (validity_ == LineLayout::llInvalid) { |
|
|
allInvalidated = true; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void LineLayoutCache::SetLevel(int level_) { |
|
|
allInvalidated = false; |
|
|
if ((level_ != -1) && (level != level_)) { |
|
|
level = level_; |
|
|
Deallocate(); |
|
|
} |
|
|
} |
|
|
|
|
|
LineLayout *LineLayoutCache::Retrieve(int lineNumber, int lineCaret, int maxChars, int styleClock_, |
|
|
int linesOnScreen, int linesInDoc) { |
|
|
AllocateForLevel(linesOnScreen, linesInDoc); |
|
|
if (styleClock != styleClock_) { |
|
|
Invalidate(LineLayout::llCheckTextAndStyle); |
|
|
styleClock = styleClock_; |
|
|
} |
|
|
allInvalidated = false; |
|
|
int pos = -1; |
|
|
LineLayout *ret = 0; |
|
|
if (level == llcCaret) { |
|
|
pos = 0; |
|
|
} else if (level == llcPage) { |
|
|
if (lineNumber == lineCaret) { |
|
|
pos = 0; |
|
|
} else if (length > 1) { |
|
|
pos = 1 + (lineNumber % (length - 1)); |
|
|
} |
|
|
} else if (level == llcDocument) { |
|
|
pos = lineNumber; |
|
|
} |
|
|
if (pos >= 0) { |
|
|
PLATFORM_ASSERT(useCount == 0); |
|
|
if (cache && (pos < length)) { |
|
|
if (cache[pos]) { |
|
|
if ((cache[pos]->lineNumber != lineNumber) || |
|
|
(cache[pos]->maxLineLength < maxChars)) { |
|
|
delete cache[pos]; |
|
|
cache[pos] = 0; |
|
|
} |
|
|
} |
|
|
if (!cache[pos]) { |
|
|
cache[pos] = new LineLayout(maxChars); |
|
|
} |
|
|
if (cache[pos]) { |
|
|
cache[pos]->lineNumber = lineNumber; |
|
|
cache[pos]->inCache = true; |
|
|
ret = cache[pos]; |
|
|
useCount++; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (!ret) { |
|
|
ret = new LineLayout(maxChars); |
|
|
ret->lineNumber = lineNumber; |
|
|
} |
|
|
|
|
|
return ret; |
|
|
} |
|
|
|
|
|
void LineLayoutCache::Dispose(LineLayout *ll) { |
|
|
allInvalidated = false; |
|
|
if (ll) { |
|
|
if (!ll->inCache) { |
|
|
delete ll; |
|
|
} else { |
|
|
useCount--; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
Editor::Editor() { |
|
|
ctrlID = 0; |
|
|
|
|
|
stylesValid = false; |
|
|
|
|
|
printMagnification = 0; |
|
|
printColourMode = SC_PRINT_NORMAL; |
|
|
printWrapState = eWrapWord; |
|
|
cursorMode = SC_CURSORNORMAL; |
|
|
controlCharSymbol = 0; /* Draw the control characters */ |
|
|
|
|
|
hasFocus = false; |
|
|
hideSelection = false; |
|
|
inOverstrike = false; |
|
|
errorStatus = 0; |
|
|
mouseDownCaptures = true; |
|
|
|
|
|
bufferedDraw = true; |
|
|
twoPhaseDraw = true; |
|
|
|
|
|
lastClickTime = 0; |
|
|
dwellDelay = SC_TIME_FOREVER; |
|
|
ticksToDwell = SC_TIME_FOREVER; |
|
|
dwelling = false; |
|
|
ptMouseLast.x = 0; |
|
|
ptMouseLast.y = 0; |
|
|
inDragDrop = false; |
|
|
dropWentOutside = false; |
|
|
posDrag = invalidPosition; |
|
|
posDrop = invalidPosition; |
|
|
selectionType = selChar; |
|
|
|
|
|
lastXChosen = 0; |
|
|
lineAnchor = 0; |
|
|
originalAnchorPos = 0; |
|
|
|
|
|
selType = selStream; |
|
|
moveExtendsSelection = false; |
|
|
xStartSelect = 0; |
|
|
xEndSelect = 0; |
|
|
primarySelection = true; |
|
|
|
|
|
caretXPolicy = CARET_SLOP | CARET_EVEN; |
|
|
caretXSlop = 50; |
|
|
|
|
|
caretYPolicy = CARET_EVEN; |
|
|
caretYSlop = 0; |
|
|
|
|
|
searchAnchor = 0; |
|
|
|
|
|
xOffset = 0; |
|
|
xCaretMargin = 50; |
|
|
horizontalScrollBarVisible = true; |
|
|
scrollWidth = 2000; |
|
|
verticalScrollBarVisible = true; |
|
|
endAtLastLine = true; |
|
|
caretSticky = false; |
|
|
|
|
|
pixmapLine = Surface::Allocate(); |
|
|
pixmapSelMargin = Surface::Allocate(); |
|
|
pixmapSelPattern = Surface::Allocate(); |
|
|
pixmapIndentGuide = Surface::Allocate(); |
|
|
pixmapIndentGuideHighlight = Surface::Allocate(); |
|
|
|
|
|
currentPos = 0; |
|
|
anchor = 0; |
|
|
|
|
|
targetStart = 0; |
|
|
targetEnd = 0; |
|
|
searchFlags = 0; |
|
|
|
|
|
topLine = 0; |
|
|
posTopLine = 0; |
|
|
|
|
|
lengthForEncode = -1; |
|
|
|
|
|
needUpdateUI = true; |
|
|
braces[0] = invalidPosition; |
|
|
braces[1] = invalidPosition; |
|
|
bracesMatchStyle = STYLE_BRACEBAD; |
|
|
highlightGuideColumn = 0; |
|
|
|
|
|
theEdge = 0; |
|
|
|
|
|
paintState = notPainting; |
|
|
|
|
|
modEventMask = SC_MODEVENTMASKALL; |
|
|
|
|
|
pdoc = new Document(); |
|
|
pdoc->AddRef(); |
|
|
pdoc->AddWatcher(this, 0); |
|
|
|
|
|
recordingMacro = false; |
|
|
foldFlags = 0; |
|
|
|
|
|
wrapState = eWrapNone; |
|
|
wrapWidth = LineLayout::wrapWidthInfinite; |
|
|
wrapStart = wrapLineLarge; |
|
|
wrapEnd = wrapLineLarge; |
|
|
wrapVisualFlags = 0; |
|
|
wrapVisualFlagsLocation = 0; |
|
|
wrapVisualStartIndent = 0; |
|
|
actualWrapVisualStartIndent = 0; |
|
|
|
|
|
convertPastes = true; |
|
|
|
|
|
hsStart = -1; |
|
|
hsEnd = -1; |
|
|
|
|
|
llc.SetLevel(LineLayoutCache::llcCaret); |
|
|
} |
|
|
|
|
|
Editor::~Editor() { |
|
|
pdoc->RemoveWatcher(this, 0); |
|
|
pdoc->Release(); |
|
|
pdoc = 0; |
|
|
DropGraphics(); |
|
|
delete pixmapLine; |
|
|
delete pixmapSelMargin; |
|
|
delete pixmapSelPattern; |
|
|
delete pixmapIndentGuide; |
|
|
delete pixmapIndentGuideHighlight; |
|
|
} |
|
|
|
|
|
void Editor::Finalise() { |
|
|
SetIdle(false); |
|
|
CancelModes(); |
|
|
} |
|
|
|
|
|
void Editor::DropGraphics() { |
|
|
pixmapLine->Release(); |
|
|
pixmapSelMargin->Release(); |
|
|
pixmapSelPattern->Release(); |
|
|
pixmapIndentGuide->Release(); |
|
|
pixmapIndentGuideHighlight->Release(); |
|
|
} |
|
|
|
|
|
void Editor::InvalidateStyleData() { |
|
|
stylesValid = false; |
|
|
palette.Release(); |
|
|
DropGraphics(); |
|
|
llc.Invalidate(LineLayout::llInvalid); |
|
|
if (selType == selRectangle) { |
|
|
xStartSelect = XFromPosition(anchor); |
|
|
xEndSelect = XFromPosition(currentPos); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::InvalidateStyleRedraw() { |
|
|
NeedWrapping(); |
|
|
InvalidateStyleData(); |
|
|
Redraw(); |
|
|
} |
|
|
|
|
|
void Editor::RefreshColourPalette(Palette &pal, bool want) { |
|
|
vs.RefreshColourPalette(pal, want); |
|
|
} |
|
|
|
|
|
void Editor::RefreshStyleData() { |
|
|
if (!stylesValid) { |
|
|
stylesValid = true; |
|
|
AutoSurface surface(this); |
|
|
if (surface) { |
|
|
vs.Refresh(*surface); |
|
|
RefreshColourPalette(palette, true); |
|
|
palette.Allocate(wMain); |
|
|
RefreshColourPalette(palette, false); |
|
|
} |
|
|
SetScrollBars(); |
|
|
} |
|
|
} |
|
|
|
|
|
PRectangle Editor::GetClientRectangle() { |
|
|
return wMain.GetClientPosition(); |
|
|
} |
|
|
|
|
|
PRectangle Editor::GetTextRectangle() { |
|
|
PRectangle rc = GetClientRectangle(); |
|
|
rc.left += vs.fixedColumnWidth; |
|
|
rc.right -= vs.rightMarginWidth; |
|
|
return rc; |
|
|
} |
|
|
|
|
|
int Editor::LinesOnScreen() { |
|
|
PRectangle rcClient = GetClientRectangle(); |
|
|
int htClient = rcClient.bottom - rcClient.top; |
|
|
//Platform::DebugPrintf("lines on screen = %d\n", htClient / lineHeight + 1); |
|
|
return htClient / vs.lineHeight; |
|
|
} |
|
|
|
|
|
int Editor::LinesToScroll() { |
|
|
int retVal = LinesOnScreen() - 1; |
|
|
if (retVal < 1) |
|
|
return 1; |
|
|
else |
|
|
return retVal; |
|
|
} |
|
|
|
|
|
int Editor::MaxScrollPos() { |
|
|
//Platform::DebugPrintf("Lines %d screen = %d maxScroll = %d\n", |
|
|
//LinesTotal(), LinesOnScreen(), LinesTotal() - LinesOnScreen() + 1); |
|
|
int retVal = cs.LinesDisplayed(); |
|
|
if (endAtLastLine) { |
|
|
retVal -= LinesOnScreen(); |
|
|
} else { |
|
|
retVal--; |
|
|
} |
|
|
if (retVal < 0) { |
|
|
return 0; |
|
|
} else { |
|
|
return retVal; |
|
|
} |
|
|
} |
|
|
|
|
|
static inline bool IsControlCharacter(int ch) { |
|
|
// iscntrl returns true for lots of chars > 127 which are displayable |
|
|
return ch >= 0 && ch < ' '; |
|
|
} |
|
|
|
|
|
const char *ControlCharacterString(unsigned char ch) { |
|
|
const char *reps[] = { |
|
|
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", |
|
|
"BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", |
|
|
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", |
|
|
"CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" |
|
|
}; |
|
|
if (ch < (sizeof(reps) / sizeof(reps[0]))) { |
|
|
return reps[ch]; |
|
|
} else { |
|
|
return "BAD"; |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
* Convenience class to ensure LineLayout objects are always disposed. |
|
|
*/ |
|
|
class AutoLineLayout { |
|
|
LineLayoutCache &llc; |
|
|
LineLayout *ll; |
|
|
AutoLineLayout &operator=(const AutoLineLayout &) { return * this; } |
|
|
public: |
|
|
AutoLineLayout(LineLayoutCache &llc_, LineLayout *ll_) : llc(llc_), ll(ll_) {} |
|
|
~AutoLineLayout() { |
|
|
llc.Dispose(ll); |
|
|
ll = 0; |
|
|
} |
|
|
LineLayout *operator->() const { |
|
|
return ll; |
|
|
} |
|
|
operator LineLayout *() const { |
|
|
return ll; |
|
|
} |
|
|
void Set(LineLayout *ll_) { |
|
|
llc.Dispose(ll); |
|
|
ll = ll_; |
|
|
} |
|
|
}; |
|
|
|
|
|
/** |
|
|
* Allows to iterate through the lines of a selection. |
|
|
* Althought it can be called for a stream selection, in most cases |
|
|
* it is inefficient and it should be used only for |
|
|
* a rectangular or a line selection. |
|
|
*/ |
|
|
class SelectionLineIterator { |
|
|
private: |
|
|
Editor *ed; |
|
|
int line; ///< Current line within the iteration. |
|
|
bool forward; ///< True if iterating by increasing line number, false otherwise. |
|
|
int selStart, selEnd; ///< Positions of the start and end of the selection relative to the start of the document. |
|
|
int minX, maxX; ///< Left and right of selection rectangle. |
|
|
|
|
|
public: |
|
|
int lineStart, lineEnd; ///< Line numbers, first and last lines of the selection. |
|
|
int startPos, endPos; ///< Positions of the beginning and end of the selection on the current line. |
|
|
|
|
|
void Reset() { |
|
|
if (forward) { |
|
|
line = lineStart; |
|
|
} else { |
|
|
line = lineEnd; |
|
|
} |
|
|
} |
|
|
|
|
|
SelectionLineIterator(Editor *ed_, bool forward_ = true) : line(0), startPos(0), endPos(0) { |
|
|
ed = ed_; |
|
|
forward = forward_; |
|
|
selStart = ed->SelectionStart(); |
|
|
selEnd = ed->SelectionEnd(); |
|
|
lineStart = ed->pdoc->LineFromPosition(selStart); |
|
|
lineEnd = ed->pdoc->LineFromPosition(selEnd); |
|
|
// Left of rectangle |
|
|
minX = Platform::Minimum(ed->xStartSelect, ed->xEndSelect); |
|
|
// Right of rectangle |
|
|
maxX = Platform::Maximum(ed->xStartSelect, ed->xEndSelect); |
|
|
Reset(); |
|
|
} |
|
|
~SelectionLineIterator() {} |
|
|
|
|
|
void SetAt(int line) { |
|
|
if (line < lineStart || line > lineEnd) { |
|
|
startPos = endPos = INVALID_POSITION; |
|
|
} else { |
|
|
if (ed->selType == ed->selRectangle) { |
|
|
// Measure line and return character closest to minX |
|
|
startPos = ed->PositionFromLineX(line, minX); |
|
|
// Measure line and return character closest to maxX |
|
|
endPos = ed->PositionFromLineX(line, maxX); |
|
|
} else if (ed->selType == ed->selLines) { |
|
|
startPos = ed->pdoc->LineStart(line); |
|
|
endPos = ed->pdoc->LineStart(line + 1); |
|
|
} else { // Stream selection, here only for completion |
|
|
if (line == lineStart) { |
|
|
startPos = selStart; |
|
|
} else { |
|
|
startPos = ed->pdoc->LineStart(line); |
|
|
} |
|
|
if (line == lineEnd) { |
|
|
endPos = selEnd; |
|
|
} else { |
|
|
endPos = ed->pdoc->LineStart(line + 1); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
bool Iterate() { |
|
|
SetAt(line); |
|
|
if (forward) { |
|
|
line++; |
|
|
} else { |
|
|
line--; |
|
|
} |
|
|
return startPos != INVALID_POSITION; |
|
|
} |
|
|
}; |
|
|
|
|
|
Point Editor::LocationFromPosition(int pos) { |
|
|
Point pt; |
|
|
RefreshStyleData(); |
|
|
if (pos == INVALID_POSITION) |
|
|
return pt; |
|
|
int line = pdoc->LineFromPosition(pos); |
|
|
int lineVisible = cs.DisplayFromDoc(line); |
|
|
//Platform::DebugPrintf("line=%d\n", line); |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(line)); |
|
|
if (surface && ll) { |
|
|
// -1 because of adding in for visible lines in following loop. |
|
|
pt.y = (lineVisible - topLine - 1) * vs.lineHeight; |
|
|
pt.x = 0; |
|
|
unsigned int posLineStart = pdoc->LineStart(line); |
|
|
LayoutLine(line, surface, vs, ll, wrapWidth); |
|
|
int posInLine = pos - posLineStart; |
|
|
// In case of very long line put x at arbitrary large position |
|
|
if (posInLine > ll->maxLineLength) { |
|
|
pt.x = ll->positions[ll->maxLineLength] - ll->positions[ll->LineStart(ll->lines)]; |
|
|
} |
|
|
|
|
|
for (int subLine = 0; subLine < ll->lines; subLine++) { |
|
|
if ((posInLine >= ll->LineStart(subLine)) && (posInLine <= ll->LineStart(subLine + 1))) { |
|
|
pt.x = ll->positions[posInLine] - ll->positions[ll->LineStart(subLine)]; |
|
|
if (actualWrapVisualStartIndent != 0) { |
|
|
int lineStart = ll->LineStart(subLine); |
|
|
if (lineStart != 0) // Wrapped |
|
|
pt.x += actualWrapVisualStartIndent * vs.aveCharWidth; |
|
|
} |
|
|
} |
|
|
if (posInLine >= ll->LineStart(subLine)) { |
|
|
pt.y += vs.lineHeight; |
|
|
} |
|
|
} |
|
|
pt.x += vs.fixedColumnWidth - xOffset; |
|
|
} |
|
|
return pt; |
|
|
} |
|
|
|
|
|
int Editor::XFromPosition(int pos) { |
|
|
Point pt = LocationFromPosition(pos); |
|
|
return pt.x - vs.fixedColumnWidth + xOffset; |
|
|
} |
|
|
|
|
|
int Editor::LineFromLocation(Point pt) { |
|
|
return cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); |
|
|
} |
|
|
|
|
|
void Editor::SetTopLine(int topLineNew) { |
|
|
topLine = topLineNew; |
|
|
posTopLine = pdoc->LineStart(cs.DocFromDisplay(topLine)); |
|
|
} |
|
|
|
|
|
static inline bool IsEOLChar(char ch) { |
|
|
return (ch == '\r') || (ch == '\n'); |
|
|
} |
|
|
|
|
|
int Editor::PositionFromLocation(Point pt) { |
|
|
RefreshStyleData(); |
|
|
pt.x = pt.x - vs.fixedColumnWidth + xOffset; |
|
|
int visibleLine = pt.y / vs.lineHeight + topLine; |
|
|
if (pt.y < 0) { // Division rounds towards 0 |
|
|
visibleLine = (pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine; |
|
|
} |
|
|
if (visibleLine < 0) |
|
|
visibleLine = 0; |
|
|
int lineDoc = cs.DocFromDisplay(visibleLine); |
|
|
if (lineDoc >= pdoc->LinesTotal()) |
|
|
return pdoc->Length(); |
|
|
unsigned int posLineStart = pdoc->LineStart(lineDoc); |
|
|
int retVal = posLineStart; |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); |
|
|
if (surface && ll) { |
|
|
LayoutLine(lineDoc, surface, vs, ll, wrapWidth); |
|
|
int lineStartSet = cs.DisplayFromDoc(lineDoc); |
|
|
int subLine = visibleLine - lineStartSet; |
|
|
if (subLine < ll->lines) { |
|
|
int lineStart = ll->LineStart(subLine); |
|
|
int lineEnd = ll->LineStart(subLine + 1); |
|
|
int subLineStart = ll->positions[lineStart]; |
|
|
|
|
|
if (actualWrapVisualStartIndent != 0) { |
|
|
if (lineStart != 0) // Wrapped |
|
|
pt.x -= actualWrapVisualStartIndent * vs.aveCharWidth; |
|
|
} |
|
|
for (int i = lineStart; i < lineEnd; i++) { |
|
|
if (pt.x < (((ll->positions[i] + ll->positions[i + 1]) / 2) - subLineStart) || |
|
|
IsEOLChar(ll->chars[i])) { |
|
|
return pdoc->MovePositionOutsideChar(i + posLineStart, 1); |
|
|
} |
|
|
} |
|
|
return lineEnd + posLineStart; |
|
|
} |
|
|
retVal = ll->numCharsInLine + posLineStart; |
|
|
} |
|
|
return retVal; |
|
|
} |
|
|
|
|
|
// Like PositionFromLocation but INVALID_POSITION returned when not near any text. |
|
|
int Editor::PositionFromLocationClose(Point pt) { |
|
|
RefreshStyleData(); |
|
|
PRectangle rcClient = GetTextRectangle(); |
|
|
if (!rcClient.Contains(pt)) |
|
|
return INVALID_POSITION; |
|
|
if (pt.x < vs.fixedColumnWidth) |
|
|
return INVALID_POSITION; |
|
|
if (pt.y < 0) |
|
|
return INVALID_POSITION; |
|
|
pt.x = pt.x - vs.fixedColumnWidth + xOffset; |
|
|
int visibleLine = pt.y / vs.lineHeight + topLine; |
|
|
if (pt.y < 0) { // Division rounds towards 0 |
|
|
visibleLine = (pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine; |
|
|
} |
|
|
int lineDoc = cs.DocFromDisplay(visibleLine); |
|
|
if (lineDoc < 0) |
|
|
return INVALID_POSITION; |
|
|
if (lineDoc >= pdoc->LinesTotal()) |
|
|
return INVALID_POSITION; |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); |
|
|
if (surface && ll) { |
|
|
LayoutLine(lineDoc, surface, vs, ll, wrapWidth); |
|
|
unsigned int posLineStart = pdoc->LineStart(lineDoc); |
|
|
int lineStartSet = cs.DisplayFromDoc(lineDoc); |
|
|
int subLine = visibleLine - lineStartSet; |
|
|
if (subLine < ll->lines) { |
|
|
int lineStart = ll->LineStart(subLine); |
|
|
int lineEnd = ll->LineStart(subLine + 1); |
|
|
int subLineStart = ll->positions[lineStart]; |
|
|
|
|
|
if (actualWrapVisualStartIndent != 0) { |
|
|
if (lineStart != 0) // Wrapped |
|
|
pt.x -= actualWrapVisualStartIndent * vs.aveCharWidth; |
|
|
} |
|
|
for (int i = lineStart; i < lineEnd; i++) { |
|
|
if (pt.x < (((ll->positions[i] + ll->positions[i + 1]) / 2) - subLineStart) || |
|
|
IsEOLChar(ll->chars[i])) { |
|
|
return pdoc->MovePositionOutsideChar(i + posLineStart, 1); |
|
|
} |
|
|
} |
|
|
if (pt.x < (ll->positions[lineEnd] - subLineStart)) { |
|
|
return pdoc->MovePositionOutsideChar(lineEnd + posLineStart, 1); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
return INVALID_POSITION; |
|
|
} |
|
|
|
|
|
/** |
|
|
* Find the document position corresponding to an x coordinate on a particular document line. |
|
|
* Ensure is between whole characters when document is in multi-byte or UTF-8 mode. |
|
|
*/ |
|
|
int Editor::PositionFromLineX(int lineDoc, int x) { |
|
|
RefreshStyleData(); |
|
|
if (lineDoc >= pdoc->LinesTotal()) |
|
|
return pdoc->Length(); |
|
|
//Platform::DebugPrintf("Position of (%d,%d) line = %d top=%d\n", pt.x, pt.y, line, topLine); |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); |
|
|
int retVal = 0; |
|
|
if (surface && ll) { |
|
|
unsigned int posLineStart = pdoc->LineStart(lineDoc); |
|
|
LayoutLine(lineDoc, surface, vs, ll, wrapWidth); |
|
|
retVal = ll->numCharsInLine + posLineStart; |
|
|
int subLine = 0; |
|
|
int lineStart = ll->LineStart(subLine); |
|
|
int lineEnd = ll->LineStart(subLine + 1); |
|
|
int subLineStart = ll->positions[lineStart]; |
|
|
|
|
|
if (actualWrapVisualStartIndent != 0) { |
|
|
if (lineStart != 0) // Wrapped |
|
|
x -= actualWrapVisualStartIndent * vs.aveCharWidth; |
|
|
} |
|
|
for (int i = lineStart; i < lineEnd; i++) { |
|
|
if (x < (((ll->positions[i] + ll->positions[i + 1]) / 2) - subLineStart) || |
|
|
IsEOLChar(ll->chars[i])) { |
|
|
retVal = pdoc->MovePositionOutsideChar(i + posLineStart, 1); |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
return retVal; |
|
|
} |
|
|
|
|
|
/** |
|
|
* If painting then abandon the painting because a wider redraw is needed. |
|
|
* @return true if calling code should stop drawing. |
|
|
*/ |
|
|
bool Editor::AbandonPaint() { |
|
|
if ((paintState == painting) && !paintingAllText) { |
|
|
paintState = paintAbandoned; |
|
|
} |
|
|
return paintState == paintAbandoned; |
|
|
} |
|
|
|
|
|
void Editor::RedrawRect(PRectangle rc) { |
|
|
//Platform::DebugPrintf("Redraw %0d,%0d - %0d,%0d\n", rc.left, rc.top, rc.right, rc.bottom); |
|
|
|
|
|
// Clip the redraw rectangle into the client area |
|
|
PRectangle rcClient = GetClientRectangle(); |
|
|
if (rc.top < rcClient.top) |
|
|
rc.top = rcClient.top; |
|
|
if (rc.bottom > rcClient.bottom) |
|
|
rc.bottom = rcClient.bottom; |
|
|
if (rc.left < rcClient.left) |
|
|
rc.left = rcClient.left; |
|
|
if (rc.right > rcClient.right) |
|
|
rc.right = rcClient.right; |
|
|
|
|
|
if ((rc.bottom > rc.top) && (rc.right > rc.left)) { |
|
|
wMain.InvalidateRectangle(rc); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::Redraw() { |
|
|
//Platform::DebugPrintf("Redraw all\n"); |
|
|
PRectangle rcClient = GetClientRectangle(); |
|
|
wMain.InvalidateRectangle(rcClient); |
|
|
//wMain.InvalidateAll(); |
|
|
} |
|
|
|
|
|
void Editor::RedrawSelMargin(int line) { |
|
|
if (!AbandonPaint()) { |
|
|
if (vs.maskInLine) { |
|
|
Redraw(); |
|
|
} else { |
|
|
PRectangle rcSelMargin = GetClientRectangle(); |
|
|
rcSelMargin.right = vs.fixedColumnWidth; |
|
|
if (line != -1) { |
|
|
int position = pdoc->LineStart(line); |
|
|
PRectangle rcLine = RectangleFromRange(position, position); |
|
|
rcSelMargin.top = rcLine.top; |
|
|
rcSelMargin.bottom = rcLine.bottom; |
|
|
} |
|
|
wMain.InvalidateRectangle(rcSelMargin); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
PRectangle Editor::RectangleFromRange(int start, int end) { |
|
|
int minPos = start; |
|
|
if (minPos > end) |
|
|
minPos = end; |
|
|
int maxPos = start; |
|
|
if (maxPos < end) |
|
|
maxPos = end; |
|
|
int minLine = cs.DisplayFromDoc(pdoc->LineFromPosition(minPos)); |
|
|
int lineDocMax = pdoc->LineFromPosition(maxPos); |
|
|
int maxLine = cs.DisplayFromDoc(lineDocMax) + cs.GetHeight(lineDocMax) - 1; |
|
|
PRectangle rcClient = GetTextRectangle(); |
|
|
PRectangle rc; |
|
|
rc.left = vs.fixedColumnWidth; |
|
|
rc.top = (minLine - topLine) * vs.lineHeight; |
|
|
if (rc.top < 0) |
|
|
rc.top = 0; |
|
|
rc.right = rcClient.right; |
|
|
rc.bottom = (maxLine - topLine + 1) * vs.lineHeight; |
|
|
// Ensure PRectangle is within 16 bit space |
|
|
rc.top = Platform::Clamp(rc.top, -32000, 32000); |
|
|
rc.bottom = Platform::Clamp(rc.bottom, -32000, 32000); |
|
|
|
|
|
return rc; |
|
|
} |
|
|
|
|
|
void Editor::InvalidateRange(int start, int end) { |
|
|
RedrawRect(RectangleFromRange(start, end)); |
|
|
} |
|
|
|
|
|
int Editor::CurrentPosition() { |
|
|
return currentPos; |
|
|
} |
|
|
|
|
|
bool Editor::SelectionEmpty() { |
|
|
return anchor == currentPos; |
|
|
} |
|
|
|
|
|
int Editor::SelectionStart() { |
|
|
return Platform::Minimum(currentPos, anchor); |
|
|
} |
|
|
|
|
|
int Editor::SelectionEnd() { |
|
|
return Platform::Maximum(currentPos, anchor); |
|
|
} |
|
|
|
|
|
void Editor::SetRectangularRange() { |
|
|
if (selType == selRectangle) { |
|
|
xStartSelect = XFromPosition(anchor); |
|
|
xEndSelect = XFromPosition(currentPos); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::InvalidateSelection(int currentPos_, int anchor_) { |
|
|
int firstAffected = anchor; |
|
|
if (firstAffected > currentPos) |
|
|
firstAffected = currentPos; |
|
|
if (firstAffected > anchor_) |
|
|
firstAffected = anchor_; |
|
|
if (firstAffected > currentPos_) |
|
|
firstAffected = currentPos_; |
|
|
int lastAffected = anchor; |
|
|
if (lastAffected < currentPos) |
|
|
lastAffected = currentPos; |
|
|
if (lastAffected < anchor_) |
|
|
lastAffected = anchor_; |
|
|
if (lastAffected < (currentPos_ + 1)) // +1 ensures caret repainted |
|
|
lastAffected = (currentPos_ + 1); |
|
|
needUpdateUI = true; |
|
|
InvalidateRange(firstAffected, lastAffected); |
|
|
} |
|
|
|
|
|
void Editor::SetSelection(int currentPos_, int anchor_) { |
|
|
currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); |
|
|
anchor_ = pdoc->ClampPositionIntoDocument(anchor_); |
|
|
if ((currentPos != currentPos_) || (anchor != anchor_)) { |
|
|
InvalidateSelection(currentPos_, anchor_); |
|
|
currentPos = currentPos_; |
|
|
anchor = anchor_; |
|
|
} |
|
|
SetRectangularRange(); |
|
|
ClaimSelection(); |
|
|
} |
|
|
|
|
|
void Editor::SetSelection(int currentPos_) { |
|
|
currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); |
|
|
if (currentPos != currentPos_) { |
|
|
InvalidateSelection(currentPos_, currentPos_); |
|
|
currentPos = currentPos_; |
|
|
} |
|
|
SetRectangularRange(); |
|
|
ClaimSelection(); |
|
|
} |
|
|
|
|
|
void Editor::SetEmptySelection(int currentPos_) { |
|
|
selType = selStream; |
|
|
moveExtendsSelection = false; |
|
|
SetSelection(currentPos_, currentPos_); |
|
|
} |
|
|
|
|
|
bool Editor::RangeContainsProtected(int start, int end) const { |
|
|
if (vs.ProtectionActive()) { |
|
|
if (start > end) { |
|
|
int t = start; |
|
|
start = end; |
|
|
end = t; |
|
|
} |
|
|
int mask = pdoc->stylingBitsMask; |
|
|
for (int pos = start; pos < end; pos++) { |
|
|
if (vs.styles[pdoc->StyleAt(pos) & mask].IsProtected()) |
|
|
return true; |
|
|
} |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
bool Editor::SelectionContainsProtected() { |
|
|
// DONE, but untested...: make support rectangular selection |
|
|
bool scp = false; |
|
|
if (selType == selStream) { |
|
|
scp = RangeContainsProtected(anchor, currentPos); |
|
|
} else { |
|
|
SelectionLineIterator lineIterator(this); |
|
|
while (lineIterator.Iterate()) { |
|
|
if (RangeContainsProtected(lineIterator.startPos, lineIterator.endPos)) { |
|
|
scp = true; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
return scp; |
|
|
} |
|
|
|
|
|
/** |
|
|
* Asks document to find a good position and then moves out of any invisible positions. |
|
|
*/ |
|
|
int Editor::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) { |
|
|
pos = pdoc->MovePositionOutsideChar(pos, moveDir, checkLineEnd); |
|
|
if (vs.ProtectionActive()) { |
|
|
int mask = pdoc->stylingBitsMask; |
|
|
if (moveDir > 0) { |
|
|
if ((pos > 0) && vs.styles[pdoc->StyleAt(pos - 1) & mask].IsProtected()) { |
|
|
while ((pos < pdoc->Length()) && |
|
|
(vs.styles[pdoc->StyleAt(pos) & mask].IsProtected())) |
|
|
pos++; |
|
|
} |
|
|
} else if (moveDir < 0) { |
|
|
if (vs.styles[pdoc->StyleAt(pos) & mask].IsProtected()) { |
|
|
while ((pos > 0) && |
|
|
(vs.styles[pdoc->StyleAt(pos - 1) & mask].IsProtected())) |
|
|
pos--; |
|
|
} |
|
|
} |
|
|
} |
|
|
return pos; |
|
|
} |
|
|
|
|
|
int Editor::MovePositionTo(int newPos, selTypes sel, bool ensureVisible) { |
|
|
int delta = newPos - currentPos; |
|
|
newPos = pdoc->ClampPositionIntoDocument(newPos); |
|
|
newPos = MovePositionOutsideChar(newPos, delta); |
|
|
if (sel != noSel) { |
|
|
selType = sel; |
|
|
} |
|
|
if (sel != noSel || moveExtendsSelection) { |
|
|
SetSelection(newPos); |
|
|
} else { |
|
|
SetEmptySelection(newPos); |
|
|
} |
|
|
ShowCaretAtCurrentPosition(); |
|
|
if (ensureVisible) { |
|
|
EnsureCaretVisible(); |
|
|
} |
|
|
NotifyMove(newPos); |
|
|
return 0; |
|
|
} |
|
|
|
|
|
int Editor::MovePositionSoVisible(int pos, int moveDir) { |
|
|
pos = pdoc->ClampPositionIntoDocument(pos); |
|
|
pos = MovePositionOutsideChar(pos, moveDir); |
|
|
int lineDoc = pdoc->LineFromPosition(pos); |
|
|
if (cs.GetVisible(lineDoc)) { |
|
|
return pos; |
|
|
} else { |
|
|
int lineDisplay = cs.DisplayFromDoc(lineDoc); |
|
|
if (moveDir > 0) { |
|
|
// lineDisplay is already line before fold as lines in fold use display line of line after fold |
|
|
lineDisplay = Platform::Clamp(lineDisplay, 0, cs.LinesDisplayed()); |
|
|
return pdoc->LineStart(cs.DocFromDisplay(lineDisplay)); |
|
|
} else { |
|
|
lineDisplay = Platform::Clamp(lineDisplay - 1, 0, cs.LinesDisplayed()); |
|
|
return pdoc->LineEnd(cs.DocFromDisplay(lineDisplay)); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
* Choose the x position that the caret will try to stick to |
|
|
* as it moves up and down. |
|
|
*/ |
|
|
void Editor::SetLastXChosen() { |
|
|
Point pt = LocationFromPosition(currentPos); |
|
|
lastXChosen = pt.x; |
|
|
} |
|
|
|
|
|
void Editor::ScrollTo(int line, bool moveThumb) { |
|
|
int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); |
|
|
if (topLineNew != topLine) { |
|
|
// Try to optimise small scrolls |
|
|
int linesToMove = topLine - topLineNew; |
|
|
SetTopLine(topLineNew); |
|
|
ShowCaretAtCurrentPosition(); |
|
|
// Perform redraw rather than scroll if many lines would be redrawn anyway. |
|
|
#ifndef UNDER_CE |
|
|
if (abs(linesToMove) <= 10) { |
|
|
ScrollText(linesToMove); |
|
|
} else { |
|
|
Redraw(); |
|
|
} |
|
|
#else |
|
|
Redraw(); |
|
|
#endif |
|
|
if (moveThumb) { |
|
|
SetVerticalScrollPos(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::ScrollText(int /* linesToMove */) { |
|
|
//Platform::DebugPrintf("Editor::ScrollText %d\n", linesToMove); |
|
|
Redraw(); |
|
|
} |
|
|
|
|
|
void Editor::HorizontalScrollTo(int xPos) { |
|
|
//Platform::DebugPrintf("HorizontalScroll %d\n", xPos); |
|
|
if (xPos < 0) |
|
|
xPos = 0; |
|
|
if ((wrapState == eWrapNone) && (xOffset != xPos)) { |
|
|
xOffset = xPos; |
|
|
SetHorizontalScrollPos(); |
|
|
RedrawRect(GetClientRectangle()); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::MoveCaretInsideView(bool ensureVisible) { |
|
|
PRectangle rcClient = GetTextRectangle(); |
|
|
Point pt = LocationFromPosition(currentPos); |
|
|
if (pt.y < rcClient.top) { |
|
|
MovePositionTo(PositionFromLocation( |
|
|
Point(lastXChosen, rcClient.top)), |
|
|
noSel, ensureVisible); |
|
|
} else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { |
|
|
int yOfLastLineFullyDisplayed = rcClient.top + (LinesOnScreen() - 1) * vs.lineHeight; |
|
|
MovePositionTo(PositionFromLocation( |
|
|
Point(lastXChosen, rcClient.top + yOfLastLineFullyDisplayed)), |
|
|
noSel, ensureVisible); |
|
|
} |
|
|
} |
|
|
|
|
|
int Editor::DisplayFromPosition(int pos) { |
|
|
int lineDoc = pdoc->LineFromPosition(pos); |
|
|
int lineDisplay = cs.DisplayFromDoc(lineDoc); |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); |
|
|
if (surface && ll) { |
|
|
LayoutLine(lineDoc, surface, vs, ll, wrapWidth); |
|
|
unsigned int posLineStart = pdoc->LineStart(lineDoc); |
|
|
int posInLine = pos - posLineStart; |
|
|
lineDisplay--; // To make up for first increment ahead. |
|
|
for (int subLine = 0; subLine < ll->lines; subLine++) { |
|
|
if (posInLine >= ll->LineStart(subLine)) { |
|
|
lineDisplay++; |
|
|
} |
|
|
} |
|
|
} |
|
|
return lineDisplay; |
|
|
} |
|
|
|
|
|
/** |
|
|
* Ensure the caret is reasonably visible in context. |
|
|
* |
|
|
Caret policy in SciTE |
|
|
|
|
|
If slop is set, we can define a slop value. |
|
|
This value defines an unwanted zone (UZ) where the caret is... unwanted. |
|
|
This zone is defined as a number of pixels near the vertical margins, |
|
|
and as a number of lines near the horizontal margins. |
|
|
By keeping the caret away from the edges, it is seen within its context, |
|
|
so it is likely that the identifier that the caret is on can be completely seen, |
|
|
and that the current line is seen with some of the lines following it which are |
|
|
often dependent on that line. |
|
|
|
|
|
If strict is set, the policy is enforced... strictly. |
|
|
The caret is centred on the display if slop is not set, |
|
|
and cannot go in the UZ if slop is set. |
|
|
|
|
|
If jumps is set, the display is moved more energetically |
|
|
so the caret can move in the same direction longer before the policy is applied again. |
|
|
'3UZ' notation is used to indicate three time the size of the UZ as a distance to the margin. |
|
|
|
|
|
If even is not set, instead of having symmetrical UZs, |
|
|
the left and bottom UZs are extended up to right and top UZs respectively. |
|
|
This way, we favour the displaying of useful information: the begining of lines, |
|
|
where most code reside, and the lines after the caret, eg. the body of a function. |
|
|
|
|
|
| | | | | |
|
|
slop | strict | jumps | even | Caret can go to the margin | When reaching limit<EFBFBD>(caret going out of |
|
|
| | | | | visibility or going into the UZ) display is... |
|
|
-----+--------+-------+------+--------------------------------------------+-------------------------------------------------------------- |
|
|
0 | 0 | 0 | 0 | Yes | moved to put caret on top/on right |
|
|
0 | 0 | 0 | 1 | Yes | moved by one position |
|
|
0 | 0 | 1 | 0 | Yes | moved to put caret on top/on right |
|
|
0 | 0 | 1 | 1 | Yes | centred on the caret |
|
|
0 | 1 | - | 0 | Caret is always on top/on right of display | - |
|
|
0 | 1 | - | 1 | No, caret is always centred | - |
|
|
1 | 0 | 0 | 0 | Yes | moved to put caret out of the asymmetrical UZ |
|
|
1 | 0 | 0 | 1 | Yes | moved to put caret out of the UZ |
|
|
1 | 0 | 1 | 0 | Yes | moved to put caret at 3UZ of the top or right margin |
|
|
1 | 0 | 1 | 1 | Yes | moved to put caret at 3UZ of the margin |
|
|
1 | 1 | - | 0 | Caret is always at UZ of top/right margin | - |
|
|
1 | 1 | 0 | 1 | No, kept out of UZ | moved by one position |
|
|
1 | 1 | 1 | 1 | No, kept out of UZ | moved to put caret at 3UZ of the margin |
|
|
*/ |
|
|
void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { |
|
|
//Platform::DebugPrintf("EnsureCaretVisible %d %s\n", xOffset, useMargin ? " margin" : " "); |
|
|
PRectangle rcClient = GetTextRectangle(); |
|
|
//int rcClientFullWidth = rcClient.Width(); |
|
|
int posCaret = currentPos; |
|
|
if (posDrag >= 0) { |
|
|
posCaret = posDrag; |
|
|
} |
|
|
Point pt = LocationFromPosition(posCaret); |
|
|
Point ptBottomCaret = pt; |
|
|
ptBottomCaret.y += vs.lineHeight - 1; |
|
|
int lineCaret = DisplayFromPosition(posCaret); |
|
|
bool bSlop, bStrict, bJump, bEven; |
|
|
|
|
|
// Vertical positioning |
|
|
if (vert && (pt.y < rcClient.top || ptBottomCaret.y > rcClient.bottom || (caretYPolicy & CARET_STRICT) != 0)) { |
|
|
int linesOnScreen = LinesOnScreen(); |
|
|
int halfScreen = Platform::Maximum(linesOnScreen - 1, 2) / 2; |
|
|
int newTopLine = topLine; |
|
|
bSlop = (caretYPolicy & CARET_SLOP) != 0; |
|
|
bStrict = (caretYPolicy & CARET_STRICT) != 0; |
|
|
bJump = (caretYPolicy & CARET_JUMPS) != 0; |
|
|
bEven = (caretYPolicy & CARET_EVEN) != 0; |
|
|
|
|
|
// It should be possible to scroll the window to show the caret, |
|
|
// but this fails to remove the caret on GTK+ |
|
|
if (bSlop) { // A margin is defined |
|
|
int yMoveT, yMoveB; |
|
|
if (bStrict) { |
|
|
int yMarginT, yMarginB; |
|
|
if (!useMargin) { |
|
|
// In drag mode, avoid moves |
|
|
// otherwise, a double click will select several lines. |
|
|
yMarginT = yMarginB = 0; |
|
|
} else { |
|
|
// yMarginT must equal to caretYSlop, with a minimum of 1 and |
|
|
// a maximum of slightly less than half the heigth of the text area. |
|
|
yMarginT = Platform::Clamp(caretYSlop, 1, halfScreen); |
|
|
if (bEven) { |
|
|
yMarginB = yMarginT; |
|
|
} else { |
|
|
yMarginB = linesOnScreen - yMarginT - 1; |
|
|
} |
|
|
} |
|
|
yMoveT = yMarginT; |
|
|
if (bEven) { |
|
|
if (bJump) { |
|
|
yMoveT = Platform::Clamp(caretYSlop * 3, 1, halfScreen); |
|
|
} |
|
|
yMoveB = yMoveT; |
|
|
} else { |
|
|
yMoveB = linesOnScreen - yMoveT - 1; |
|
|
} |
|
|
if (lineCaret < topLine + yMarginT) { |
|
|
// Caret goes too high |
|
|
newTopLine = lineCaret - yMoveT; |
|
|
} else if (lineCaret > topLine + linesOnScreen - 1 - yMarginB) { |
|
|
// Caret goes too low |
|
|
newTopLine = lineCaret - linesOnScreen + 1 + yMoveB; |
|
|
} |
|
|
} else { // Not strict |
|
|
yMoveT = bJump ? caretYSlop * 3 : caretYSlop; |
|
|
yMoveT = Platform::Clamp(yMoveT, 1, halfScreen); |
|
|
if (bEven) { |
|
|
yMoveB = yMoveT; |
|
|
} else { |
|
|
yMoveB = linesOnScreen - yMoveT - 1; |
|
|
} |
|
|
if (lineCaret < topLine) { |
|
|
// Caret goes too high |
|
|
newTopLine = lineCaret - yMoveT; |
|
|
} else if (lineCaret > topLine + linesOnScreen - 1) { |
|
|
// Caret goes too low |
|
|
newTopLine = lineCaret - linesOnScreen + 1 + yMoveB; |
|
|
} |
|
|
} |
|
|
} else { // No slop |
|
|
if (!bStrict && !bJump) { |
|
|
// Minimal move |
|
|
if (lineCaret < topLine) { |
|
|
// Caret goes too high |
|
|
newTopLine = lineCaret; |
|
|
} else if (lineCaret > topLine + linesOnScreen - 1) { |
|
|
// Caret goes too low |
|
|
if (bEven) { |
|
|
newTopLine = lineCaret - linesOnScreen + 1; |
|
|
} else { |
|
|
newTopLine = lineCaret; |
|
|
} |
|
|
} |
|
|
} else { // Strict or going out of display |
|
|
if (bEven) { |
|
|
// Always center caret |
|
|
newTopLine = lineCaret - halfScreen; |
|
|
} else { |
|
|
// Always put caret on top of display |
|
|
newTopLine = lineCaret; |
|
|
} |
|
|
} |
|
|
} |
|
|
newTopLine = Platform::Clamp(newTopLine, 0, MaxScrollPos()); |
|
|
if (newTopLine != topLine) { |
|
|
Redraw(); |
|
|
SetTopLine(newTopLine); |
|
|
SetVerticalScrollPos(); |
|
|
} |
|
|
} |
|
|
|
|
|
// Horizontal positioning |
|
|
if (horiz && (wrapState == eWrapNone)) { |
|
|
int halfScreen = Platform::Maximum(rcClient.Width() - 4, 4) / 2; |
|
|
int xOffsetNew = xOffset; |
|
|
bSlop = (caretXPolicy & CARET_SLOP) != 0; |
|
|
bStrict = (caretXPolicy & CARET_STRICT) != 0; |
|
|
bJump = (caretXPolicy & CARET_JUMPS) != 0; |
|
|
bEven = (caretXPolicy & CARET_EVEN) != 0; |
|
|
|
|
|
if (bSlop) { // A margin is defined |
|
|
int xMoveL, xMoveR; |
|
|
if (bStrict) { |
|
|
int xMarginL, xMarginR; |
|
|
if (!useMargin) { |
|
|
// In drag mode, avoid moves unless very near of the margin |
|
|
// otherwise, a simple click will select text. |
|
|
xMarginL = xMarginR = 2; |
|
|
} else { |
|
|
// xMargin must equal to caretXSlop, with a minimum of 2 and |
|
|
// a maximum of slightly less than half the width of the text area. |
|
|
xMarginR = Platform::Clamp(caretXSlop, 2, halfScreen); |
|
|
if (bEven) { |
|
|
xMarginL = xMarginR; |
|
|
} else { |
|
|
xMarginL = rcClient.Width() - xMarginR - 4; |
|
|
} |
|
|
} |
|
|
if (bJump && bEven) { |
|
|
// Jump is used only in even mode |
|
|
xMoveL = xMoveR = Platform::Clamp(caretXSlop * 3, 1, halfScreen); |
|
|
} else { |
|
|
xMoveL = xMoveR = 0; // Not used, avoid a warning |
|
|
} |
|
|
if (pt.x < rcClient.left + xMarginL) { |
|
|
// Caret is on the left of the display |
|
|
if (bJump && bEven) { |
|
|
xOffsetNew -= xMoveL; |
|
|
} else { |
|
|
// Move just enough to allow to display the caret |
|
|
xOffsetNew -= (rcClient.left + xMarginL) - pt.x; |
|
|
} |
|
|
} else if (pt.x >= rcClient.right - xMarginR) { |
|
|
// Caret is on the right of the display |
|
|
if (bJump && bEven) { |
|
|
xOffsetNew += xMoveR; |
|
|
} else { |
|
|
// Move just enough to allow to display the caret |
|
|
xOffsetNew += pt.x - (rcClient.right - xMarginR) + 1; |
|
|
} |
|
|
} |
|
|
} else { // Not strict |
|
|
xMoveR = bJump ? caretXSlop * 3 : caretXSlop; |
|
|
xMoveR = Platform::Clamp(xMoveR, 1, halfScreen); |
|
|
if (bEven) { |
|
|
xMoveL = xMoveR; |
|
|
} else { |
|
|
xMoveL = rcClient.Width() - xMoveR - 4; |
|
|
} |
|
|
if (pt.x < rcClient.left) { |
|
|
// Caret is on the left of the display |
|
|
xOffsetNew -= xMoveL; |
|
|
} else if (pt.x >= rcClient.right) { |
|
|
// Caret is on the right of the display |
|
|
xOffsetNew += xMoveR; |
|
|
} |
|
|
} |
|
|
} else { // No slop |
|
|
if (bStrict || |
|
|
(bJump && (pt.x < rcClient.left || pt.x >= rcClient.right))) { |
|
|
// Strict or going out of display |
|
|
if (bEven) { |
|
|
// Center caret |
|
|
xOffsetNew += pt.x - rcClient.left - halfScreen; |
|
|
} else { |
|
|
// Put caret on right |
|
|
xOffsetNew += pt.x - rcClient.right + 1; |
|
|
} |
|
|
} else { |
|
|
// Move just enough to allow to display the caret |
|
|
if (pt.x < rcClient.left) { |
|
|
// Caret is on the left of the display |
|
|
if (bEven) { |
|
|
xOffsetNew -= rcClient.left - pt.x; |
|
|
} else { |
|
|
xOffsetNew += pt.x - rcClient.right + 1; |
|
|
} |
|
|
} else if (pt.x >= rcClient.right) { |
|
|
// Caret is on the right of the display |
|
|
xOffsetNew += pt.x - rcClient.right + 1; |
|
|
} |
|
|
} |
|
|
} |
|
|
// In case of a jump (find result) largely out of display, adjust the offset to display the caret |
|
|
if (pt.x + xOffset < rcClient.left + xOffsetNew) { |
|
|
xOffsetNew = pt.x + xOffset - rcClient.left; |
|
|
} else if (pt.x + xOffset >= rcClient.right + xOffsetNew) { |
|
|
xOffsetNew = pt.x + xOffset - rcClient.right + 1; |
|
|
} |
|
|
if (xOffsetNew < 0) { |
|
|
xOffsetNew = 0; |
|
|
} |
|
|
if (xOffset != xOffsetNew) { |
|
|
xOffset = xOffsetNew; |
|
|
if (xOffsetNew > 0) { |
|
|
PRectangle rcText = GetTextRectangle(); |
|
|
if (horizontalScrollBarVisible == true && |
|
|
rcText.Width() + xOffset > scrollWidth) { |
|
|
scrollWidth = xOffset + rcText.Width(); |
|
|
SetScrollBars(); |
|
|
} |
|
|
} |
|
|
SetHorizontalScrollPos(); |
|
|
Redraw(); |
|
|
} |
|
|
} |
|
|
UpdateSystemCaret(); |
|
|
} |
|
|
|
|
|
void Editor::ShowCaretAtCurrentPosition() { |
|
|
if (hasFocus) { |
|
|
caret.active = true; |
|
|
caret.on = true; |
|
|
SetTicking(true); |
|
|
} else { |
|
|
caret.active = false; |
|
|
caret.on = false; |
|
|
} |
|
|
InvalidateCaret(); |
|
|
} |
|
|
|
|
|
void Editor::DropCaret() { |
|
|
caret.active = false; |
|
|
InvalidateCaret(); |
|
|
} |
|
|
|
|
|
void Editor::InvalidateCaret() { |
|
|
if (posDrag >= 0) |
|
|
InvalidateRange(posDrag, posDrag + 1); |
|
|
else |
|
|
InvalidateRange(currentPos, currentPos + 1); |
|
|
UpdateSystemCaret(); |
|
|
} |
|
|
|
|
|
void Editor::UpdateSystemCaret() { |
|
|
} |
|
|
|
|
|
void Editor::NeedWrapping(int docLineStart, int docLineEnd) { |
|
|
docLineStart = Platform::Clamp(docLineStart, 0, pdoc->LinesTotal()); |
|
|
if (wrapStart > docLineStart) { |
|
|
wrapStart = docLineStart; |
|
|
llc.Invalidate(LineLayout::llPositions); |
|
|
} |
|
|
if (wrapEnd < docLineEnd) { |
|
|
wrapEnd = docLineEnd; |
|
|
} |
|
|
wrapEnd = Platform::Clamp(wrapEnd, 0, pdoc->LinesTotal()); |
|
|
// Wrap lines during idle. |
|
|
if ((wrapState != eWrapNone) && (wrapEnd != wrapStart)) { |
|
|
SetIdle(true); |
|
|
} |
|
|
} |
|
|
|
|
|
// Check if wrapping needed and perform any needed wrapping. |
|
|
// fullwrap: if true, all lines which need wrapping will be done, |
|
|
// in this single call. |
|
|
// priorityWrapLineStart: If greater than zero, all lines starting from |
|
|
// here to 1 page + 100 lines past will be wrapped (even if there are |
|
|
// more lines under wrapping process in idle). |
|
|
// If it is neither fullwrap, nor priorityWrap, then 1 page + 100 lines will be |
|
|
// wrapped, if there are any wrapping going on in idle. (Generally this |
|
|
// condition is called only from idler). |
|
|
// Return true if wrapping occurred. |
|
|
bool Editor::WrapLines(bool fullWrap, int priorityWrapLineStart) { |
|
|
// If there are any pending wraps, do them during idle if possible. |
|
|
int linesInOneCall = LinesOnScreen() + 100; |
|
|
if (wrapState != eWrapNone) { |
|
|
if (wrapStart < wrapEnd) { |
|
|
if (!SetIdle(true)) { |
|
|
// Idle processing not supported so full wrap required. |
|
|
fullWrap = true; |
|
|
} |
|
|
} |
|
|
if (!fullWrap && priorityWrapLineStart >= 0 && |
|
|
// .. and if the paint window is outside pending wraps |
|
|
(((priorityWrapLineStart + linesInOneCall) < wrapStart) || |
|
|
(priorityWrapLineStart > wrapEnd))) { |
|
|
// No priority wrap pending |
|
|
return false; |
|
|
} |
|
|
} |
|
|
int goodTopLine = topLine; |
|
|
bool wrapOccurred = false; |
|
|
if (wrapStart <= pdoc->LinesTotal()) { |
|
|
if (wrapState == eWrapNone) { |
|
|
if (wrapWidth != LineLayout::wrapWidthInfinite) { |
|
|
wrapWidth = LineLayout::wrapWidthInfinite; |
|
|
for (int lineDoc = 0; lineDoc < pdoc->LinesTotal(); lineDoc++) { |
|
|
cs.SetHeight(lineDoc, 1); |
|
|
} |
|
|
wrapOccurred = true; |
|
|
} |
|
|
wrapStart = wrapLineLarge; |
|
|
wrapEnd = wrapLineLarge; |
|
|
} else { |
|
|
if (wrapEnd >= pdoc->LinesTotal()) |
|
|
wrapEnd = pdoc->LinesTotal(); |
|
|
//ElapsedTime et; |
|
|
int lineDocTop = cs.DocFromDisplay(topLine); |
|
|
int subLineTop = topLine - cs.DisplayFromDoc(lineDocTop); |
|
|
PRectangle rcTextArea = GetClientRectangle(); |
|
|
rcTextArea.left = vs.fixedColumnWidth; |
|
|
rcTextArea.right -= vs.rightMarginWidth; |
|
|
wrapWidth = rcTextArea.Width(); |
|
|
// Ensure all of the document is styled. |
|
|
pdoc->EnsureStyledTo(pdoc->Length()); |
|
|
RefreshStyleData(); |
|
|
AutoSurface surface(this); |
|
|
if (surface) { |
|
|
bool priorityWrap = false; |
|
|
int lastLineToWrap = wrapEnd; |
|
|
int lineToWrap = wrapStart; |
|
|
if (!fullWrap) { |
|
|
if (priorityWrapLineStart >= 0) { |
|
|
// This is a priority wrap. |
|
|
lineToWrap = priorityWrapLineStart; |
|
|
lastLineToWrap = priorityWrapLineStart + linesInOneCall; |
|
|
priorityWrap = true; |
|
|
} else { |
|
|
// This is idle wrap. |
|
|
lastLineToWrap = wrapStart + linesInOneCall; |
|
|
} |
|
|
if (lastLineToWrap >= wrapEnd) |
|
|
lastLineToWrap = wrapEnd; |
|
|
} // else do a fullWrap. |
|
|
|
|
|
// Platform::DebugPrintf("Wraplines: full = %d, priorityStart = %d (wrapping: %d to %d)\n", fullWrap, priorityWrapLineStart, lineToWrap, lastLineToWrap); |
|
|
// Platform::DebugPrintf("Pending wraps: %d to %d\n", wrapStart, wrapEnd); |
|
|
while (lineToWrap < lastLineToWrap) { |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(lineToWrap)); |
|
|
int linesWrapped = 1; |
|
|
if (ll) { |
|
|
LayoutLine(lineToWrap, surface, vs, ll, wrapWidth); |
|
|
linesWrapped = ll->lines; |
|
|
} |
|
|
if (cs.SetHeight(lineToWrap, linesWrapped)) { |
|
|
wrapOccurred = true; |
|
|
} |
|
|
lineToWrap++; |
|
|
} |
|
|
if (!priorityWrap) |
|
|
wrapStart = lineToWrap; |
|
|
// If wrapping is done, bring it to resting position |
|
|
if (wrapStart >= wrapEnd) { |
|
|
wrapStart = wrapLineLarge; |
|
|
wrapEnd = wrapLineLarge; |
|
|
} |
|
|
} |
|
|
goodTopLine = cs.DisplayFromDoc(lineDocTop); |
|
|
if (subLineTop < cs.GetHeight(lineDocTop)) |
|
|
goodTopLine += subLineTop; |
|
|
else |
|
|
goodTopLine += cs.GetHeight(lineDocTop); |
|
|
//double durWrap = et.Duration(true); |
|
|
//Platform::DebugPrintf("Wrap:%9.6g \n", durWrap); |
|
|
} |
|
|
} |
|
|
if (wrapOccurred) { |
|
|
SetScrollBars(); |
|
|
SetTopLine(Platform::Clamp(goodTopLine, 0, MaxScrollPos())); |
|
|
SetVerticalScrollPos(); |
|
|
} |
|
|
return wrapOccurred; |
|
|
} |
|
|
|
|
|
void Editor::LinesJoin() { |
|
|
if (!RangeContainsProtected(targetStart, targetEnd)) { |
|
|
pdoc->BeginUndoAction(); |
|
|
bool prevNonWS = true; |
|
|
for (int pos = targetStart; pos < targetEnd; pos++) { |
|
|
if (IsEOLChar(pdoc->CharAt(pos))) { |
|
|
targetEnd -= pdoc->LenChar(pos); |
|
|
pdoc->DelChar(pos); |
|
|
if (prevNonWS) { |
|
|
// Ensure at least one space separating previous lines |
|
|
pdoc->InsertChar(pos, ' '); |
|
|
} |
|
|
} else { |
|
|
prevNonWS = pdoc->CharAt(pos) != ' '; |
|
|
} |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
} |
|
|
} |
|
|
|
|
|
const char *StringFromEOLMode(int eolMode) { |
|
|
if (eolMode == SC_EOL_CRLF) { |
|
|
return "\r\n"; |
|
|
} else if (eolMode == SC_EOL_CR) { |
|
|
return "\r"; |
|
|
} else { |
|
|
return "\n"; |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::LinesSplit(int pixelWidth) { |
|
|
if (!RangeContainsProtected(targetStart, targetEnd)) { |
|
|
if (pixelWidth == 0) { |
|
|
PRectangle rcText = GetTextRectangle(); |
|
|
pixelWidth = rcText.Width(); |
|
|
} |
|
|
int lineStart = pdoc->LineFromPosition(targetStart); |
|
|
int lineEnd = pdoc->LineFromPosition(targetEnd); |
|
|
const char *eol = StringFromEOLMode(pdoc->eolMode); |
|
|
pdoc->BeginUndoAction(); |
|
|
for (int line = lineStart; line <= lineEnd; line++) { |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(line)); |
|
|
if (surface && ll) { |
|
|
unsigned int posLineStart = pdoc->LineStart(line); |
|
|
LayoutLine(line, surface, vs, ll, pixelWidth); |
|
|
for (int subLine = 1; subLine < ll->lines; subLine++) { |
|
|
pdoc->InsertString(posLineStart + (subLine - 1) * strlen(eol) + |
|
|
ll->LineStart(subLine), eol); |
|
|
targetEnd += static_cast<int>(strlen(eol)); |
|
|
} |
|
|
} |
|
|
lineEnd = pdoc->LineFromPosition(targetEnd); |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
} |
|
|
} |
|
|
|
|
|
int Editor::SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) { |
|
|
if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) |
|
|
return markerDefault; |
|
|
return markerCheck; |
|
|
} |
|
|
|
|
|
// Avoid 64 bit compiler warnings. |
|
|
// Scintilla does not support text buffers larger than 2**31 |
|
|
static int istrlen(const char *s) { |
|
|
return static_cast<int>(strlen(s)); |
|
|
} |
|
|
|
|
|
void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { |
|
|
if (vs.fixedColumnWidth == 0) |
|
|
return; |
|
|
|
|
|
PRectangle rcMargin = GetClientRectangle(); |
|
|
rcMargin.right = vs.fixedColumnWidth; |
|
|
|
|
|
if (!rc.Intersects(rcMargin)) |
|
|
return; |
|
|
|
|
|
Surface *surface; |
|
|
if (bufferedDraw) { |
|
|
surface = pixmapSelMargin; |
|
|
} else { |
|
|
surface = surfWindow; |
|
|
} |
|
|
|
|
|
PRectangle rcSelMargin = rcMargin; |
|
|
rcSelMargin.right = rcMargin.left; |
|
|
|
|
|
for (int margin = 0; margin < vs.margins; margin++) { |
|
|
if (vs.ms[margin].width > 0) { |
|
|
|
|
|
rcSelMargin.left = rcSelMargin.right; |
|
|
rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; |
|
|
|
|
|
if (vs.ms[margin].style != SC_MARGIN_NUMBER) { |
|
|
/* alternate scheme: |
|
|
if (vs.ms[margin].mask & SC_MASK_FOLDERS) |
|
|
surface->FillRectangle(rcSelMargin, vs.styles[STYLE_DEFAULT].back.allocated); |
|
|
else |
|
|
// Required because of special way brush is created for selection margin |
|
|
surface->FillRectangle(rcSelMargin, pixmapSelPattern); |
|
|
*/ |
|
|
if (vs.ms[margin].mask & SC_MASK_FOLDERS) |
|
|
// Required because of special way brush is created for selection margin |
|
|
surface->FillRectangle(rcSelMargin, *pixmapSelPattern); |
|
|
else { |
|
|
ColourAllocated colour; |
|
|
switch (vs.ms[margin].style) { |
|
|
case SC_MARGIN_BACK: |
|
|
colour = vs.styles[STYLE_DEFAULT].back.allocated; |
|
|
break; |
|
|
case SC_MARGIN_FORE: |
|
|
colour = vs.styles[STYLE_DEFAULT].fore.allocated; |
|
|
break; |
|
|
default: |
|
|
colour = vs.styles[STYLE_LINENUMBER].back.allocated; |
|
|
break; |
|
|
} |
|
|
surface->FillRectangle(rcSelMargin, colour); |
|
|
} |
|
|
} else { |
|
|
surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back.allocated); |
|
|
} |
|
|
|
|
|
int visibleLine = topLine; |
|
|
int yposScreen = 0; |
|
|
|
|
|
// Work out whether the top line is whitespace located after a |
|
|
// lessening of fold level which implies a 'fold tail' but which should not |
|
|
// be displayed until the last of a sequence of whitespace. |
|
|
bool needWhiteClosure = false; |
|
|
int level = pdoc->GetLevel(cs.DocFromDisplay(topLine)); |
|
|
if (level & SC_FOLDLEVELWHITEFLAG) { |
|
|
int lineBack = cs.DocFromDisplay(topLine); |
|
|
int levelPrev = level; |
|
|
while ((lineBack > 0) && (levelPrev & SC_FOLDLEVELWHITEFLAG)) { |
|
|
lineBack--; |
|
|
levelPrev = pdoc->GetLevel(lineBack); |
|
|
} |
|
|
if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) { |
|
|
if ((level & SC_FOLDLEVELNUMBERMASK) < (levelPrev & SC_FOLDLEVELNUMBERMASK)) |
|
|
needWhiteClosure = true; |
|
|
} |
|
|
} |
|
|
|
|
|
// Old code does not know about new markers needed to distinguish all cases |
|
|
int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID, |
|
|
SC_MARKNUM_FOLDEROPEN); |
|
|
int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND, |
|
|
SC_MARKNUM_FOLDER); |
|
|
|
|
|
while ((visibleLine < cs.LinesDisplayed()) && yposScreen < rcMargin.bottom) { |
|
|
|
|
|
PLATFORM_ASSERT(visibleLine < cs.LinesDisplayed()); |
|
|
|
|
|
int lineDoc = cs.DocFromDisplay(visibleLine); |
|
|
PLATFORM_ASSERT(cs.GetVisible(lineDoc)); |
|
|
bool firstSubLine = visibleLine == cs.DisplayFromDoc(lineDoc); |
|
|
|
|
|
// Decide which fold indicator should be displayed |
|
|
level = pdoc->GetLevel(lineDoc); |
|
|
int levelNext = pdoc->GetLevel(lineDoc + 1); |
|
|
int marks = pdoc->GetMark(lineDoc); |
|
|
if (!firstSubLine) |
|
|
marks = 0; |
|
|
int levelNum = level & SC_FOLDLEVELNUMBERMASK; |
|
|
int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; |
|
|
if (level & SC_FOLDLEVELHEADERFLAG) { |
|
|
if (firstSubLine) { |
|
|
if (cs.GetExpanded(lineDoc)) { |
|
|
if (levelNum == SC_FOLDLEVELBASE) |
|
|
marks |= 1 << SC_MARKNUM_FOLDEROPEN; |
|
|
else |
|
|
marks |= 1 << folderOpenMid; |
|
|
} else { |
|
|
if (levelNum == SC_FOLDLEVELBASE) |
|
|
marks |= 1 << SC_MARKNUM_FOLDER; |
|
|
else |
|
|
marks |= 1 << folderEnd; |
|
|
} |
|
|
} else { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERSUB; |
|
|
} |
|
|
needWhiteClosure = false; |
|
|
} else if (level & SC_FOLDLEVELWHITEFLAG) { |
|
|
if (needWhiteClosure) { |
|
|
if (levelNext & SC_FOLDLEVELWHITEFLAG) { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERSUB; |
|
|
} else if (levelNum > SC_FOLDLEVELBASE) { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; |
|
|
needWhiteClosure = false; |
|
|
} else { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERTAIL; |
|
|
needWhiteClosure = false; |
|
|
} |
|
|
} else if (levelNum > SC_FOLDLEVELBASE) { |
|
|
if (levelNextNum < levelNum) { |
|
|
if (levelNextNum > SC_FOLDLEVELBASE) { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; |
|
|
} else { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERTAIL; |
|
|
} |
|
|
} else { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERSUB; |
|
|
} |
|
|
} |
|
|
} else if (levelNum > SC_FOLDLEVELBASE) { |
|
|
if (levelNextNum < levelNum) { |
|
|
needWhiteClosure = false; |
|
|
if (levelNext & SC_FOLDLEVELWHITEFLAG) { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERSUB; |
|
|
needWhiteClosure = true; |
|
|
} else if (levelNextNum > SC_FOLDLEVELBASE) { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; |
|
|
} else { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERTAIL; |
|
|
} |
|
|
} else { |
|
|
marks |= 1 << SC_MARKNUM_FOLDERSUB; |
|
|
} |
|
|
} |
|
|
|
|
|
marks &= vs.ms[margin].mask; |
|
|
PRectangle rcMarker = rcSelMargin; |
|
|
rcMarker.top = yposScreen; |
|
|
rcMarker.bottom = yposScreen + vs.lineHeight; |
|
|
if (vs.ms[margin].style == SC_MARGIN_NUMBER) { |
|
|
char number[100]; |
|
|
number[0] = '\0'; |
|
|
if (firstSubLine) |
|
|
sprintf(number, "%d", lineDoc + 1); |
|
|
if (foldFlags & SC_FOLDFLAG_LEVELNUMBERS) { |
|
|
int lev = pdoc->GetLevel(lineDoc); |
|
|
sprintf(number, "%c%c %03X %03X", |
|
|
(lev & SC_FOLDLEVELHEADERFLAG) ? 'H' : '_', |
|
|
(lev & SC_FOLDLEVELWHITEFLAG) ? 'W' : '_', |
|
|
lev & SC_FOLDLEVELNUMBERMASK, |
|
|
lev >> 16 |
|
|
); |
|
|
} |
|
|
PRectangle rcNumber = rcMarker; |
|
|
// Right justify |
|
|
int width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, istrlen(number)); |
|
|
int xpos = rcNumber.right - width - 3; |
|
|
rcNumber.left = xpos; |
|
|
surface->DrawTextNoClip(rcNumber, vs.styles[STYLE_LINENUMBER].font, |
|
|
rcNumber.top + vs.maxAscent, number, istrlen(number), |
|
|
vs.styles[STYLE_LINENUMBER].fore.allocated, |
|
|
vs.styles[STYLE_LINENUMBER].back.allocated); |
|
|
} |
|
|
|
|
|
if (marks) { |
|
|
for (int markBit = 0; (markBit < 32) && marks; markBit++) { |
|
|
if (marks & 1) { |
|
|
vs.markers[markBit].Draw(surface, rcMarker, vs.styles[STYLE_LINENUMBER].font); |
|
|
} |
|
|
marks >>= 1; |
|
|
} |
|
|
} |
|
|
|
|
|
visibleLine++; |
|
|
yposScreen += vs.lineHeight; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
PRectangle rcBlankMargin = rcMargin; |
|
|
rcBlankMargin.left = rcSelMargin.right; |
|
|
surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back.allocated); |
|
|
|
|
|
if (bufferedDraw) { |
|
|
surfWindow->Copy(rcMargin, Point(), *pixmapSelMargin); |
|
|
} |
|
|
} |
|
|
|
|
|
void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { |
|
|
int ydiff = (rcTab.bottom - rcTab.top) / 2; |
|
|
int xhead = rcTab.right - 1 - ydiff; |
|
|
if (xhead <= rcTab.left) { |
|
|
ydiff -= rcTab.left - xhead - 1; |
|
|
xhead = rcTab.left - 1; |
|
|
} |
|
|
if ((rcTab.left + 2) < (rcTab.right - 1)) |
|
|
surface->MoveTo(rcTab.left + 2, ymid); |
|
|
else |
|
|
surface->MoveTo(rcTab.right - 1, ymid); |
|
|
surface->LineTo(rcTab.right - 1, ymid); |
|
|
surface->LineTo(xhead, ymid - ydiff); |
|
|
surface->MoveTo(rcTab.right - 1, ymid); |
|
|
surface->LineTo(xhead, ymid + ydiff); |
|
|
} |
|
|
|
|
|
static bool IsSpaceOrTab(char ch) { |
|
|
return ch == ' ' || ch == '\t'; |
|
|
} |
|
|
|
|
|
LineLayout *Editor::RetrieveLineLayout(int lineNumber) { |
|
|
int posLineStart = pdoc->LineStart(lineNumber); |
|
|
int posLineEnd = pdoc->LineStart(lineNumber + 1); |
|
|
int lineCaret = pdoc->LineFromPosition(currentPos); |
|
|
return llc.Retrieve(lineNumber, lineCaret, |
|
|
posLineEnd - posLineStart, pdoc->GetStyleClock(), |
|
|
LinesOnScreen() + 1, pdoc->LinesTotal()); |
|
|
} |
|
|
|
|
|
/** |
|
|
* Fill in the LineLayout data for the given line. |
|
|
* Copy the given @a line and its styles from the document into local arrays. |
|
|
* Also determine the x position at which each character starts. |
|
|
*/ |
|
|
void Editor::LayoutLine(int line, Surface *surface, ViewStyle &vstyle, LineLayout *ll, int width) { |
|
|
if (!ll) |
|
|
return; |
|
|
PLATFORM_ASSERT(line < pdoc->LinesTotal()); |
|
|
int posLineStart = pdoc->LineStart(line); |
|
|
int posLineEnd = pdoc->LineStart(line + 1); |
|
|
// If the line is very long, limit the treatment to a length that should fit in the viewport |
|
|
if (posLineEnd > (posLineStart + ll->maxLineLength)) { |
|
|
posLineEnd = posLineStart + ll->maxLineLength; |
|
|
} |
|
|
if (ll->validity == LineLayout::llCheckTextAndStyle) { |
|
|
int lineLength = posLineEnd - posLineStart; |
|
|
if (!vstyle.viewEOL) { |
|
|
int cid = posLineEnd - 1; |
|
|
while ((cid > posLineStart) && IsEOLChar(pdoc->CharAt(cid))) { |
|
|
cid--; |
|
|
lineLength--; |
|
|
} |
|
|
} |
|
|
if (lineLength == ll->numCharsInLine) { |
|
|
// See if chars, styles, indicators, are all the same |
|
|
bool allSame = true; |
|
|
const int styleMask = pdoc->stylingBitsMask; |
|
|
// Check base line layout |
|
|
char styleByte = 0; |
|
|
int numCharsInLine = 0; |
|
|
while (numCharsInLine < lineLength) { |
|
|
int charInDoc = numCharsInLine + posLineStart; |
|
|
char chDoc = pdoc->CharAt(charInDoc); |
|
|
styleByte = pdoc->StyleAt(charInDoc); |
|
|
allSame = allSame && |
|
|
(ll->styles[numCharsInLine] == static_cast<unsigned char>(styleByte & styleMask)); |
|
|
allSame = allSame && |
|
|
(ll->indicators[numCharsInLine] == static_cast<char>(styleByte & ~styleMask)); |
|
|
if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseMixed) |
|
|
allSame = allSame && |
|
|
(ll->chars[numCharsInLine] == chDoc); |
|
|
else if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseLower) |
|
|
allSame = allSame && |
|
|
(ll->chars[numCharsInLine] == static_cast<char>(tolower(chDoc))); |
|
|
else // Style::caseUpper |
|
|
allSame = allSame && |
|
|
(ll->chars[numCharsInLine] == static_cast<char>(toupper(chDoc))); |
|
|
numCharsInLine++; |
|
|
} |
|
|
allSame = allSame && (ll->styles[numCharsInLine] == styleByte); // For eolFilled |
|
|
if (allSame) { |
|
|
ll->validity = LineLayout::llPositions; |
|
|
} else { |
|
|
ll->validity = LineLayout::llInvalid; |
|
|
} |
|
|
} else { |
|
|
ll->validity = LineLayout::llInvalid; |
|
|
} |
|
|
} |
|
|
if (ll->validity == LineLayout::llInvalid) { |
|
|
ll->widthLine = LineLayout::wrapWidthInfinite; |
|
|
ll->lines = 1; |
|
|
int numCharsInLine = 0; |
|
|
if (vstyle.edgeState == EDGE_BACKGROUND) { |
|
|
ll->edgeColumn = pdoc->FindColumn(line, theEdge); |
|
|
if (ll->edgeColumn >= posLineStart) { |
|
|
ll->edgeColumn -= posLineStart; |
|
|
} |
|
|
} else { |
|
|
ll->edgeColumn = -1; |
|
|
} |
|
|
|
|
|
char styleByte = 0; |
|
|
int styleMask = pdoc->stylingBitsMask; |
|
|
ll->styleBitsSet = 0; |
|
|
// Fill base line layout |
|
|
for (int charInDoc = posLineStart; charInDoc < posLineEnd; charInDoc++) { |
|
|
char chDoc = pdoc->CharAt(charInDoc); |
|
|
styleByte = pdoc->StyleAt(charInDoc); |
|
|
ll->styleBitsSet |= styleByte; |
|
|
if (vstyle.viewEOL || (!IsEOLChar(chDoc))) { |
|
|
ll->chars[numCharsInLine] = chDoc; |
|
|
ll->styles[numCharsInLine] = static_cast<char>(styleByte & styleMask); |
|
|
ll->indicators[numCharsInLine] = static_cast<char>(styleByte & ~styleMask); |
|
|
if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseUpper) |
|
|
ll->chars[numCharsInLine] = static_cast<char>(toupper(chDoc)); |
|
|
else if (vstyle.styles[ll->styles[numCharsInLine]].caseForce == Style::caseLower) |
|
|
ll->chars[numCharsInLine] = static_cast<char>(tolower(chDoc)); |
|
|
numCharsInLine++; |
|
|
} |
|
|
} |
|
|
ll->xHighlightGuide = 0; |
|
|
// Extra element at the end of the line to hold end x position and act as |
|
|
ll->chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character |
|
|
ll->styles[numCharsInLine] = styleByte; // For eolFilled |
|
|
ll->indicators[numCharsInLine] = 0; |
|
|
|
|
|
// Layout the line, determining the position of each character, |
|
|
// with an extra element at the end for the end of the line. |
|
|
int startseg = 0; // Start of the current segment, in char. number |
|
|
int startsegx = 0; // Start of the current segment, in pixels |
|
|
ll->positions[0] = 0; |
|
|
unsigned int tabWidth = vstyle.spaceWidth * pdoc->tabInChars; |
|
|
bool lastSegItalics = false; |
|
|
Font &ctrlCharsFont = vstyle.styles[STYLE_CONTROLCHAR].font; |
|
|
|
|
|
int ctrlCharWidth[32] = {0}; |
|
|
bool isControlNext = IsControlCharacter(ll->chars[0]); |
|
|
for (int charInLine = 0; charInLine < numCharsInLine; charInLine++) { |
|
|
bool isControl = isControlNext; |
|
|
isControlNext = IsControlCharacter(ll->chars[charInLine + 1]); |
|
|
if ((ll->styles[charInLine] != ll->styles[charInLine + 1]) || |
|
|
isControl || isControlNext) { |
|
|
ll->positions[startseg] = 0; |
|
|
if (vstyle.styles[ll->styles[charInLine]].visible) { |
|
|
if (isControl) { |
|
|
if (ll->chars[charInLine] == '\t') { |
|
|
ll->positions[charInLine + 1] = ((((startsegx + 2) / |
|
|
tabWidth) + 1) * tabWidth) - startsegx; |
|
|
} else if (controlCharSymbol < 32) { |
|
|
if (ctrlCharWidth[ll->chars[charInLine]] == 0) { |
|
|
const char *ctrlChar = ControlCharacterString(ll->chars[charInLine]); |
|
|
// +3 For a blank on front and rounded edge each side: |
|
|
ctrlCharWidth[ll->chars[charInLine]] = |
|
|
surface->WidthText(ctrlCharsFont, ctrlChar, istrlen(ctrlChar)) + 3; |
|
|
} |
|
|
ll->positions[charInLine + 1] = ctrlCharWidth[ll->chars[charInLine]]; |
|
|
} else { |
|
|
char cc[2] = { static_cast<char>(controlCharSymbol), '\0' }; |
|
|
surface->MeasureWidths(ctrlCharsFont, cc, 1, |
|
|
ll->positions + startseg + 1); |
|
|
} |
|
|
lastSegItalics = false; |
|
|
} else { // Regular character |
|
|
int lenSeg = charInLine - startseg + 1; |
|
|
if ((lenSeg == 1) && (' ' == ll->chars[startseg])) { |
|
|
lastSegItalics = false; |
|
|
// Over half the segments are single characters and of these about half are space characters. |
|
|
ll->positions[charInLine + 1] = vstyle.styles[ll->styles[charInLine]].spaceWidth; |
|
|
} else { |
|
|
lastSegItalics = vstyle.styles[ll->styles[charInLine]].italic; |
|
|
surface->MeasureWidths(vstyle.styles[ll->styles[charInLine]].font, ll->chars + startseg, |
|
|
lenSeg, ll->positions + startseg + 1); |
|
|
} |
|
|
} |
|
|
} else { // invisible |
|
|
for (int posToZero = startseg; posToZero <= (charInLine + 1); posToZero++) { |
|
|
ll->positions[posToZero] = 0; |
|
|
} |
|
|
} |
|
|
for (int posToIncrease = startseg; posToIncrease <= (charInLine + 1); posToIncrease++) { |
|
|
ll->positions[posToIncrease] += startsegx; |
|
|
} |
|
|
startsegx = ll->positions[charInLine + 1]; |
|
|
startseg = charInLine + 1; |
|
|
} |
|
|
} |
|
|
// Small hack to make lines that end with italics not cut off the edge of the last character |
|
|
if ((startseg > 0) && lastSegItalics) { |
|
|
ll->positions[startseg] += 2; |
|
|
} |
|
|
ll->numCharsInLine = numCharsInLine; |
|
|
ll->validity = LineLayout::llPositions; |
|
|
} |
|
|
// Hard to cope when too narrow, so just assume there is space |
|
|
if (width < 20) { |
|
|
width = 20; |
|
|
} |
|
|
if ((ll->validity == LineLayout::llPositions) || (ll->widthLine != width)) { |
|
|
ll->widthLine = width; |
|
|
if (width == LineLayout::wrapWidthInfinite) { |
|
|
ll->lines = 1; |
|
|
} else if (width > ll->positions[ll->numCharsInLine]) { |
|
|
// Simple common case where line does not need wrapping. |
|
|
ll->lines = 1; |
|
|
} else { |
|
|
if (wrapVisualFlags & SC_WRAPVISUALFLAG_END) { |
|
|
width -= vstyle.aveCharWidth; // take into account the space for end wrap mark |
|
|
} |
|
|
ll->lines = 0; |
|
|
// Calculate line start positions based upon width. |
|
|
// For now this is simplistic - wraps on byte rather than character and |
|
|
// in the middle of words. Should search for spaces or style changes. |
|
|
int lastGoodBreak = 0; |
|
|
int lastLineStart = 0; |
|
|
int startOffset = 0; |
|
|
int p = 0; |
|
|
while (p < ll->numCharsInLine) { |
|
|
if ((ll->positions[p + 1] - startOffset) >= width) { |
|
|
if (lastGoodBreak == lastLineStart) { |
|
|
// Try moving to start of last character |
|
|
if (p > 0) { |
|
|
lastGoodBreak = pdoc->MovePositionOutsideChar(p + posLineStart, -1) |
|
|
- posLineStart; |
|
|
} |
|
|
if (lastGoodBreak == lastLineStart) { |
|
|
// Ensure at least one character on line. |
|
|
lastGoodBreak = pdoc->MovePositionOutsideChar(lastGoodBreak + posLineStart + 1, 1) |
|
|
- posLineStart; |
|
|
} |
|
|
} |
|
|
lastLineStart = lastGoodBreak; |
|
|
ll->lines++; |
|
|
ll->SetLineStart(ll->lines, lastGoodBreak); |
|
|
startOffset = ll->positions[lastGoodBreak]; |
|
|
// take into account the space for start wrap mark and indent |
|
|
startOffset -= actualWrapVisualStartIndent * vstyle.aveCharWidth; |
|
|
p = lastGoodBreak + 1; |
|
|
continue; |
|
|
} |
|
|
if (p > 0) { |
|
|
if (wrapState == eWrapChar) { |
|
|
lastGoodBreak = pdoc->MovePositionOutsideChar(p + posLineStart, -1) |
|
|
- posLineStart; |
|
|
p = pdoc->MovePositionOutsideChar(p + 1 + posLineStart, 1) - posLineStart; |
|
|
continue; |
|
|
} else if (ll->styles[p] != ll->styles[p - 1]) { |
|
|
lastGoodBreak = p; |
|
|
} else if (IsSpaceOrTab(ll->chars[p - 1]) && !IsSpaceOrTab(ll->chars[p])) { |
|
|
lastGoodBreak = p; |
|
|
} |
|
|
} |
|
|
p++; |
|
|
} |
|
|
ll->lines++; |
|
|
} |
|
|
ll->validity = LineLayout::llLines; |
|
|
} |
|
|
} |
|
|
|
|
|
ColourAllocated Editor::SelectionBackground(ViewStyle &vsDraw) { |
|
|
return primarySelection ? vsDraw.selbackground.allocated : vsDraw.selbackground2.allocated; |
|
|
} |
|
|
|
|
|
ColourAllocated Editor::TextBackground(ViewStyle &vsDraw, bool overrideBackground, |
|
|
ColourAllocated background, bool inSelection, bool inHotspot, int styleMain, int i, LineLayout *ll) { |
|
|
if (inSelection) { |
|
|
if (vsDraw.selbackset && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { |
|
|
return SelectionBackground(vsDraw); |
|
|
} |
|
|
} else { |
|
|
if ((vsDraw.edgeState == EDGE_BACKGROUND) && |
|
|
(i >= ll->edgeColumn) && |
|
|
!IsEOLChar(ll->chars[i])) |
|
|
return vsDraw.edgecolour.allocated; |
|
|
if (inHotspot && vsDraw.hotspotBackgroundSet) |
|
|
return vsDraw.hotspotBackground.allocated; |
|
|
if (overrideBackground) |
|
|
return background; |
|
|
} |
|
|
return vsDraw.styles[styleMain].back.allocated; |
|
|
} |
|
|
|
|
|
void Editor::DrawIndentGuide(Surface *surface, int lineVisible, int lineHeight, int start, PRectangle rcSegment, bool highlight) { |
|
|
Point from(0, ((lineVisible & 1) && (lineHeight & 1)) ? 1 : 0); |
|
|
PRectangle rcCopyArea(start + 1, rcSegment.top, start + 2, rcSegment.bottom); |
|
|
surface->Copy(rcCopyArea, from, |
|
|
highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); |
|
|
} |
|
|
|
|
|
void Editor::DrawWrapMarker(Surface *surface, PRectangle rcPlace, |
|
|
bool isEndMarker, ColourAllocated wrapColour) { |
|
|
surface->PenColour(wrapColour); |
|
|
|
|
|
enum { xa = 1 }; // gap before start |
|
|
int w = rcPlace.right - rcPlace.left - xa - 1; |
|
|
|
|
|
bool xStraight = isEndMarker; // x-mirrored symbol for start marker |
|
|
bool yStraight = true; |
|
|
//bool yStraight= isEndMarker; // comment in for start marker y-mirrowed |
|
|
|
|
|
int x0 = xStraight ? rcPlace.left : rcPlace.right - 1; |
|
|
int y0 = yStraight ? rcPlace.top : rcPlace.bottom - 1; |
|
|
|
|
|
int dy = (rcPlace.bottom - rcPlace.top) / 5; |
|
|
int y = (rcPlace.bottom - rcPlace.top) / 2 + dy; |
|
|
|
|
|
struct Relative { |
|
|
Surface *surface; |
|
|
int xBase; |
|
|
int xDir; |
|
|
int yBase; |
|
|
int yDir; |
|
|
void MoveTo(int xRelative, int yRelative) { |
|
|
surface->MoveTo(xBase + xDir * xRelative, yBase + yDir * yRelative); |
|
|
} |
|
|
void LineTo(int xRelative, int yRelative) { |
|
|
surface->LineTo(xBase + xDir * xRelative, yBase + yDir * yRelative); |
|
|
} |
|
|
}; |
|
|
Relative rel = {surface, x0, xStraight ? 1 : -1, y0, yStraight ? 1 : -1}; |
|
|
|
|
|
// arrow head |
|
|
rel.MoveTo(xa, y); |
|
|
rel.LineTo(xa + 2*w / 3, y - dy); |
|
|
rel.MoveTo(xa, y); |
|
|
rel.LineTo(xa + 2*w / 3, y + dy); |
|
|
|
|
|
// arrow body |
|
|
rel.MoveTo(xa, y); |
|
|
rel.LineTo(xa + w, y); |
|
|
rel.LineTo(xa + w, y - 2 * dy); |
|
|
rel.LineTo(xa - 1, // on windows lineto is exclusive endpoint, perhaps GTK not... |
|
|
y - 2 * dy); |
|
|
} |
|
|
|
|
|
static void SimpleAlphaRectangle(Surface *surface, PRectangle rc, ColourAllocated fill, int alpha) { |
|
|
if (alpha != SC_ALPHA_NOALPHA) { |
|
|
surface->AlphaRectangle(rc, 0, fill, alpha, fill, alpha, 0); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::DrawEOL(Surface *surface, ViewStyle &vsDraw, PRectangle rcLine, LineLayout *ll, |
|
|
int line, int lineEnd, int xStart, int subLine, int subLineStart, |
|
|
bool overrideBackground, ColourAllocated background, |
|
|
bool drawWrapMarkEnd, ColourAllocated wrapColour) { |
|
|
|
|
|
int styleMask = pdoc->stylingBitsMask; |
|
|
PRectangle rcSegment = rcLine; |
|
|
|
|
|
// Fill in a PRectangle representing the end of line characters |
|
|
int xEol = ll->positions[lineEnd] - subLineStart; |
|
|
rcSegment.left = xEol + xStart; |
|
|
rcSegment.right = xEol + vsDraw.aveCharWidth + xStart; |
|
|
int posLineEnd = pdoc->LineStart(line + 1); |
|
|
bool eolInSelection = (subLine == (ll->lines - 1)) && |
|
|
(posLineEnd > ll->selStart) && (posLineEnd <= ll->selEnd) && (ll->selStart != ll->selEnd); |
|
|
|
|
|
if (eolInSelection && vsDraw.selbackset && (line < pdoc->LinesTotal() - 1) && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { |
|
|
surface->FillRectangle(rcSegment, SelectionBackground(vsDraw)); |
|
|
} else { |
|
|
if (overrideBackground) { |
|
|
surface->FillRectangle(rcSegment, background); |
|
|
} else { |
|
|
surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back.allocated); |
|
|
} |
|
|
if (eolInSelection && vsDraw.selbackset && (line < pdoc->LinesTotal() - 1) && (vsDraw.selAlpha != SC_ALPHA_NOALPHA)) { |
|
|
SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw), vsDraw.selAlpha); |
|
|
} |
|
|
} |
|
|
|
|
|
rcSegment.left = xEol + vsDraw.aveCharWidth + xStart; |
|
|
rcSegment.right = rcLine.right; |
|
|
if (overrideBackground) { |
|
|
surface->FillRectangle(rcSegment, background); |
|
|
} else if (vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].eolFilled) { |
|
|
surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine] & styleMask].back.allocated); |
|
|
} else { |
|
|
surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back.allocated); |
|
|
} |
|
|
|
|
|
if (drawWrapMarkEnd) { |
|
|
PRectangle rcPlace = rcSegment; |
|
|
|
|
|
if (wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_END_BY_TEXT) { |
|
|
rcPlace.left = xEol + xStart; |
|
|
rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; |
|
|
} else { |
|
|
// draw left of the right text margin, to avoid clipping by the current clip rect |
|
|
rcPlace.right = rcLine.right - vs.rightMarginWidth; |
|
|
rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; |
|
|
} |
|
|
DrawWrapMarker(surface, rcPlace, true, wrapColour); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::DrawLine(Surface *surface, ViewStyle &vsDraw, int line, int lineVisible, int xStart, |
|
|
PRectangle rcLine, LineLayout *ll, int subLine) { |
|
|
|
|
|
PRectangle rcSegment = rcLine; |
|
|
|
|
|
// Using one font for all control characters so it can be controlled independently to ensure |
|
|
// the box goes around the characters tightly. Seems to be no way to work out what height |
|
|
// is taken by an individual character - internal leading gives varying results. |
|
|
Font &ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; |
|
|
|
|
|
// See if something overrides the line background color: Either if caret is on the line |
|
|
// and background color is set for that, or if a marker is defined that forces its background |
|
|
// color onto the line, or if a marker is defined but has no selection margin in which to |
|
|
// display itself (as long as it's not an SC_MARK_EMPTY marker). These are checked in order |
|
|
// with the earlier taking precedence. When multiple markers cause background override, |
|
|
// the color for the highest numbered one is used. |
|
|
bool overrideBackground = false; |
|
|
ColourAllocated background; |
|
|
if (caret.active && vsDraw.showCaretLineBackground && (vsDraw.caretLineAlpha == SC_ALPHA_NOALPHA) && ll->containsCaret) { |
|
|
overrideBackground = true; |
|
|
background = vsDraw.caretLineBackground.allocated; |
|
|
} |
|
|
if (!overrideBackground) { |
|
|
int marks = pdoc->GetMark(line); |
|
|
for (int markBit = 0; (markBit < 32) && marks; markBit++) { |
|
|
if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND) && |
|
|
(vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { |
|
|
background = vsDraw.markers[markBit].back.allocated; |
|
|
overrideBackground = true; |
|
|
} |
|
|
marks >>= 1; |
|
|
} |
|
|
} |
|
|
if (!overrideBackground) { |
|
|
if (vsDraw.maskInLine) { |
|
|
int marksMasked = pdoc->GetMark(line) & vsDraw.maskInLine; |
|
|
if (marksMasked) { |
|
|
for (int markBit = 0; (markBit < 32) && marksMasked; markBit++) { |
|
|
if ((marksMasked & 1) && (vsDraw.markers[markBit].markType != SC_MARK_EMPTY) && |
|
|
(vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { |
|
|
overrideBackground = true; |
|
|
background = vsDraw.markers[markBit].back.allocated; |
|
|
} |
|
|
marksMasked >>= 1; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && |
|
|
(!overrideBackground) && (vsDraw.whitespaceBackgroundSet); |
|
|
|
|
|
bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. |
|
|
int indentWidth = pdoc->IndentSize() * vsDraw.spaceWidth; |
|
|
|
|
|
int posLineStart = pdoc->LineStart(line); |
|
|
|
|
|
int startseg = ll->LineStart(subLine); |
|
|
int subLineStart = ll->positions[startseg]; |
|
|
int lineStart = 0; |
|
|
int lineEnd = 0; |
|
|
if (subLine < ll->lines) { |
|
|
lineStart = ll->LineStart(subLine); |
|
|
lineEnd = ll->LineStart(subLine + 1); |
|
|
} |
|
|
|
|
|
bool drawWrapMarkEnd = false; |
|
|
|
|
|
if (wrapVisualFlags & SC_WRAPVISUALFLAG_END) { |
|
|
if (subLine + 1 < ll->lines) { |
|
|
drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; |
|
|
} |
|
|
} |
|
|
|
|
|
if (actualWrapVisualStartIndent != 0) { |
|
|
|
|
|
bool continuedWrapLine = false; |
|
|
if (subLine < ll->lines) { |
|
|
continuedWrapLine = ll->LineStart(subLine) != 0; |
|
|
} |
|
|
|
|
|
if (continuedWrapLine) { |
|
|
// draw continuation rect |
|
|
PRectangle rcPlace = rcSegment; |
|
|
|
|
|
rcPlace.left = ll->positions[startseg] + xStart - subLineStart; |
|
|
rcPlace.right = rcPlace.left + actualWrapVisualStartIndent * vsDraw.aveCharWidth; |
|
|
|
|
|
// default bgnd here.. |
|
|
surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back.allocated); |
|
|
|
|
|
// main line style would be below but this would be inconsistent with end markers |
|
|
// also would possibly not be the style at wrap point |
|
|
//int styleMain = ll->styles[lineStart]; |
|
|
//surface->FillRectangle(rcPlace, vsDraw.styles[styleMain].back.allocated); |
|
|
|
|
|
if (wrapVisualFlags & SC_WRAPVISUALFLAG_START) { |
|
|
|
|
|
if (wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_START_BY_TEXT) |
|
|
rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; |
|
|
else |
|
|
rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; |
|
|
|
|
|
DrawWrapMarker(surface, rcPlace, false, vsDraw.whitespaceForeground.allocated); |
|
|
} |
|
|
|
|
|
xStart += actualWrapVisualStartIndent * vsDraw.aveCharWidth; |
|
|
} |
|
|
} |
|
|
|
|
|
int i; |
|
|
|
|
|
// Background drawing loop |
|
|
for (i = lineStart; twoPhaseDraw && (i < lineEnd); i++) { |
|
|
|
|
|
int iDoc = i + posLineStart; |
|
|
// If there is the end of a style run for any reason |
|
|
if ((ll->styles[i] != ll->styles[i + 1]) || |
|
|
i == (lineEnd - 1) || |
|
|
IsControlCharacter(ll->chars[i]) || IsControlCharacter(ll->chars[i + 1]) || |
|
|
((ll->selStart != ll->selEnd) && ((iDoc + 1 == ll->selStart) || (iDoc + 1 == ll->selEnd))) || |
|
|
(i == (ll->edgeColumn - 1))) { |
|
|
rcSegment.left = ll->positions[startseg] + xStart - subLineStart; |
|
|
rcSegment.right = ll->positions[i + 1] + xStart - subLineStart; |
|
|
// Only try to draw if really visible - enhances performance by not calling environment to |
|
|
// draw strings that are completely past the right side of the window. |
|
|
if ((rcSegment.left <= rcLine.right) && (rcSegment.right >= rcLine.left)) { |
|
|
int styleMain = ll->styles[i]; |
|
|
bool inSelection = (iDoc >= ll->selStart) && (iDoc < ll->selEnd) && (ll->selStart != ll->selEnd); |
|
|
bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); |
|
|
ColourAllocated textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, styleMain, i, ll); |
|
|
if (ll->chars[i] == '\t') { |
|
|
// Tab display |
|
|
if (drawWhitespaceBackground && |
|
|
(!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) |
|
|
textBack = vsDraw.whitespaceBackground.allocated; |
|
|
surface->FillRectangle(rcSegment, textBack); |
|
|
} else if (IsControlCharacter(ll->chars[i])) { |
|
|
// Control character display |
|
|
inIndentation = false; |
|
|
surface->FillRectangle(rcSegment, textBack); |
|
|
} else { |
|
|
// Normal text display |
|
|
surface->FillRectangle(rcSegment, textBack); |
|
|
if (vsDraw.viewWhitespace != wsInvisible || |
|
|
(inIndentation && vsDraw.viewIndentationGuides)) { |
|
|
for (int cpos = 0; cpos <= i - startseg; cpos++) { |
|
|
if (ll->chars[cpos + startseg] == ' ') { |
|
|
if (drawWhitespaceBackground && |
|
|
(!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { |
|
|
PRectangle rcSpace(ll->positions[cpos + startseg] + xStart, rcSegment.top, |
|
|
ll->positions[cpos + startseg + 1] + xStart, rcSegment.bottom); |
|
|
surface->FillRectangle(rcSpace, vsDraw.whitespaceBackground.allocated); |
|
|
} |
|
|
} else { |
|
|
inIndentation = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} else if (rcSegment.left > rcLine.right) { |
|
|
break; |
|
|
} |
|
|
startseg = i + 1; |
|
|
} |
|
|
} |
|
|
|
|
|
if (twoPhaseDraw) { |
|
|
DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, |
|
|
xStart, subLine, subLineStart, overrideBackground, background, |
|
|
drawWrapMarkEnd, vsDraw.whitespaceForeground.allocated); |
|
|
} |
|
|
|
|
|
inIndentation = subLine == 0; // Do not handle indentation except on first subline. |
|
|
startseg = ll->LineStart(subLine); |
|
|
// Foreground drawing loop |
|
|
for (i = lineStart; i < lineEnd; i++) { |
|
|
|
|
|
int iDoc = i + posLineStart; |
|
|
// If there is the end of a style run for any reason |
|
|
if ((ll->styles[i] != ll->styles[i + 1]) || |
|
|
i == (lineEnd - 1) || |
|
|
IsControlCharacter(ll->chars[i]) || IsControlCharacter(ll->chars[i + 1]) || |
|
|
((ll->selStart != ll->selEnd) && ((iDoc + 1 == ll->selStart) || (iDoc + 1 == ll->selEnd))) || |
|
|
(i == (ll->edgeColumn - 1))) { |
|
|
rcSegment.left = ll->positions[startseg] + xStart - subLineStart; |
|
|
rcSegment.right = ll->positions[i + 1] + xStart - subLineStart; |
|
|
// Only try to draw if really visible - enhances performance by not calling environment to |
|
|
// draw strings that are completely past the right side of the window. |
|
|
if ((rcSegment.left <= rcLine.right) && (rcSegment.right >= rcLine.left)) { |
|
|
int styleMain = ll->styles[i]; |
|
|
ColourAllocated textFore = vsDraw.styles[styleMain].fore.allocated; |
|
|
Font &textFont = vsDraw.styles[styleMain].font; |
|
|
//hotspot foreground |
|
|
if (ll->hsStart != -1 && iDoc >= ll->hsStart && iDoc < hsEnd) { |
|
|
if (vsDraw.hotspotForegroundSet) |
|
|
textFore = vsDraw.hotspotForeground.allocated; |
|
|
} |
|
|
bool inSelection = (iDoc >= ll->selStart) && (iDoc < ll->selEnd) && (ll->selStart != ll->selEnd); |
|
|
if (inSelection && (vsDraw.selforeset)) { |
|
|
textFore = vsDraw.selforeground.allocated; |
|
|
} |
|
|
bool inHotspot = (ll->hsStart != -1) && (iDoc >= ll->hsStart) && (iDoc < ll->hsEnd); |
|
|
ColourAllocated textBack = TextBackground(vsDraw, overrideBackground, background, inSelection, inHotspot, styleMain, i, ll); |
|
|
if (ll->chars[i] == '\t') { |
|
|
// Tab display |
|
|
if (!twoPhaseDraw) { |
|
|
if (drawWhitespaceBackground && |
|
|
(!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) |
|
|
textBack = vsDraw.whitespaceBackground.allocated; |
|
|
surface->FillRectangle(rcSegment, textBack); |
|
|
} |
|
|
if ((vsDraw.viewWhitespace != wsInvisible) || ((inIndentation && vsDraw.viewIndentationGuides))) { |
|
|
if (vsDraw.whitespaceForegroundSet) |
|
|
textFore = vsDraw.whitespaceForeground.allocated; |
|
|
surface->PenColour(textFore); |
|
|
} |
|
|
if (inIndentation && vsDraw.viewIndentationGuides) { |
|
|
for (int xIG = ll->positions[i] / indentWidth * indentWidth; xIG < ll->positions[i + 1]; xIG += indentWidth) { |
|
|
if (xIG >= ll->positions[i] && xIG > 0) { |
|
|
DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIG + xStart, rcSegment, |
|
|
(ll->xHighlightGuide == xIG)); |
|
|
} |
|
|
} |
|
|
} |
|
|
if (vsDraw.viewWhitespace != wsInvisible) { |
|
|
if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { |
|
|
PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, |
|
|
rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); |
|
|
DrawTabArrow(surface, rcTab, rcSegment.top + vsDraw.lineHeight / 2); |
|
|
} |
|
|
} |
|
|
} else if (IsControlCharacter(ll->chars[i])) { |
|
|
// Control character display |
|
|
inIndentation = false; |
|
|
if (controlCharSymbol < 32) { |
|
|
// Draw the character |
|
|
const char *ctrlChar = ControlCharacterString(ll->chars[i]); |
|
|
if (!twoPhaseDraw) { |
|
|
surface->FillRectangle(rcSegment, textBack); |
|
|
} |
|
|
int normalCharHeight = surface->Ascent(ctrlCharsFont) - |
|
|
surface->InternalLeading(ctrlCharsFont); |
|
|
PRectangle rcCChar = rcSegment; |
|
|
rcCChar.left = rcCChar.left + 1; |
|
|
rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight; |
|
|
rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1; |
|
|
PRectangle rcCentral = rcCChar; |
|
|
rcCentral.top++; |
|
|
rcCentral.bottom--; |
|
|
surface->FillRectangle(rcCentral, textFore); |
|
|
PRectangle rcChar = rcCChar; |
|
|
rcChar.left++; |
|
|
rcChar.right--; |
|
|
surface->DrawTextClipped(rcChar, ctrlCharsFont, |
|
|
rcSegment.top + vsDraw.maxAscent, ctrlChar, istrlen(ctrlChar), |
|
|
textBack, textFore); |
|
|
} else { |
|
|
char cc[2] = { static_cast<char>(controlCharSymbol), '\0' }; |
|
|
surface->DrawTextNoClip(rcSegment, ctrlCharsFont, |
|
|
rcSegment.top + vsDraw.maxAscent, |
|
|
cc, 1, textBack, textFore); |
|
|
} |
|
|
} else { |
|
|
// Normal text display |
|
|
if (vsDraw.styles[styleMain].visible) { |
|
|
if (twoPhaseDraw) { |
|
|
surface->DrawTextTransparent(rcSegment, textFont, |
|
|
rcSegment.top + vsDraw.maxAscent, ll->chars + startseg, |
|
|
i - startseg + 1, textFore); |
|
|
} else { |
|
|
surface->DrawTextNoClip(rcSegment, textFont, |
|
|
rcSegment.top + vsDraw.maxAscent, ll->chars + startseg, |
|
|
i - startseg + 1, textFore, textBack); |
|
|
} |
|
|
} |
|
|
if (vsDraw.viewWhitespace != wsInvisible || |
|
|
(inIndentation && vsDraw.viewIndentationGuides)) { |
|
|
for (int cpos = 0; cpos <= i - startseg; cpos++) { |
|
|
if (ll->chars[cpos + startseg] == ' ') { |
|
|
if (vsDraw.viewWhitespace != wsInvisible) { |
|
|
if (vsDraw.whitespaceForegroundSet) |
|
|
textFore = vsDraw.whitespaceForeground.allocated; |
|
|
if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { |
|
|
int xmid = (ll->positions[cpos + startseg] + ll->positions[cpos + startseg + 1]) / 2; |
|
|
if (!twoPhaseDraw && drawWhitespaceBackground && |
|
|
(!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { |
|
|
textBack = vsDraw.whitespaceBackground.allocated; |
|
|
PRectangle rcSpace(ll->positions[cpos + startseg] + xStart, rcSegment.top, ll->positions[cpos + startseg + 1] + xStart, rcSegment.bottom); |
|
|
surface->FillRectangle(rcSpace, textBack); |
|
|
} |
|
|
PRectangle rcDot(xmid + xStart - subLineStart, rcSegment.top + vsDraw.lineHeight / 2, 0, 0); |
|
|
rcDot.right = rcDot.left + 1; |
|
|
rcDot.bottom = rcDot.top + 1; |
|
|
surface->FillRectangle(rcDot, textFore); |
|
|
} |
|
|
} |
|
|
if (inIndentation && vsDraw.viewIndentationGuides) { |
|
|
int startSpace = ll->positions[cpos + startseg]; |
|
|
if (startSpace > 0 && (startSpace % indentWidth == 0)) { |
|
|
DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, startSpace + xStart, rcSegment, |
|
|
(ll->xHighlightGuide == ll->positions[cpos + startseg])); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
inIndentation = false; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
if (ll->hsStart != -1 && vsDraw.hotspotUnderline && iDoc >= ll->hsStart && iDoc < ll->hsEnd ) { |
|
|
PRectangle rcUL = rcSegment; |
|
|
rcUL.top = rcUL.top + vsDraw.maxAscent + 1; |
|
|
rcUL.bottom = rcUL.top + 1; |
|
|
if (vsDraw.hotspotForegroundSet) |
|
|
surface->FillRectangle(rcUL, vsDraw.hotspotForeground.allocated); |
|
|
else |
|
|
surface->FillRectangle(rcUL, textFore); |
|
|
} else if (vsDraw.styles[styleMain].underline) { |
|
|
PRectangle rcUL = rcSegment; |
|
|
rcUL.top = rcUL.top + vsDraw.maxAscent + 1; |
|
|
rcUL.bottom = rcUL.top + 1; |
|
|
surface->FillRectangle(rcUL, textFore); |
|
|
} |
|
|
} else if (rcSegment.left > rcLine.right) { |
|
|
break; |
|
|
} |
|
|
startseg = i + 1; |
|
|
} |
|
|
} |
|
|
|
|
|
// Draw indicators |
|
|
// foreach indicator... |
|
|
for (int indicnum = 0, mask = 1 << pdoc->stylingBits; mask < 0x100; indicnum++) { |
|
|
if (!(mask & ll->styleBitsSet)) { |
|
|
mask <<= 1; |
|
|
continue; |
|
|
} |
|
|
int startPos = -1; |
|
|
// foreach style pos in line... |
|
|
for (int indicPos = lineStart; indicPos <= lineEnd; indicPos++) { |
|
|
// look for starts... |
|
|
if (startPos < 0) { |
|
|
// NOT in indicator run, looking for START |
|
|
if (indicPos < lineEnd && (ll->indicators[indicPos] & mask)) |
|
|
startPos = indicPos; |
|
|
} |
|
|
// ... or ends |
|
|
if (startPos >= 0) { |
|
|
// IN indicator run, looking for END |
|
|
if (indicPos >= lineEnd || !(ll->indicators[indicPos] & mask)) { |
|
|
// AT end of indicator run, DRAW it! |
|
|
PRectangle rcIndic( |
|
|
ll->positions[startPos] + xStart - subLineStart, |
|
|
rcLine.top + vsDraw.maxAscent, |
|
|
ll->positions[indicPos] + xStart - subLineStart, |
|
|
rcLine.top + vsDraw.maxAscent + 3); |
|
|
vsDraw.indicators[indicnum].Draw(surface, rcIndic, rcLine); |
|
|
// RESET control var |
|
|
startPos = -1; |
|
|
} |
|
|
} |
|
|
} |
|
|
mask <<= 1; |
|
|
} |
|
|
// End of the drawing of the current line |
|
|
if (!twoPhaseDraw) { |
|
|
DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, |
|
|
xStart, subLine, subLineStart, overrideBackground, background, |
|
|
drawWrapMarkEnd, vsDraw.whitespaceForeground.allocated); |
|
|
} |
|
|
if ((vsDraw.selAlpha != SC_ALPHA_NOALPHA) && (ll->selStart >= 0) && (ll->selEnd >= 0)) { |
|
|
int startPosSel = (ll->selStart < posLineStart) ? posLineStart : ll->selStart; |
|
|
int endPosSel = (ll->selEnd < (lineEnd + posLineStart)) ? ll->selEnd : (lineEnd + posLineStart); |
|
|
if (startPosSel < endPosSel) { |
|
|
rcSegment.left = xStart + ll->positions[startPosSel - posLineStart] - subLineStart; |
|
|
rcSegment.right = xStart + ll->positions[endPosSel - posLineStart] - subLineStart; |
|
|
SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw), vsDraw.selAlpha); |
|
|
} |
|
|
} |
|
|
|
|
|
if (vsDraw.edgeState == EDGE_LINE) { |
|
|
int edgeX = theEdge * vsDraw.spaceWidth; |
|
|
rcSegment.left = edgeX + xStart; |
|
|
rcSegment.right = rcSegment.left + 1; |
|
|
surface->FillRectangle(rcSegment, vsDraw.edgecolour.allocated); |
|
|
} |
|
|
|
|
|
// Draw any translucent whole line states |
|
|
rcSegment.left = xStart; |
|
|
rcSegment.right = rcLine.right - 1; |
|
|
if (caret.active && vsDraw.showCaretLineBackground && ll->containsCaret) { |
|
|
SimpleAlphaRectangle(surface, rcSegment, vsDraw.caretLineBackground.allocated, vsDraw.caretLineAlpha); |
|
|
} |
|
|
int marks = pdoc->GetMark(line); |
|
|
for (int markBit = 0; (markBit < 32) && marks; markBit++) { |
|
|
if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND)) { |
|
|
SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back.allocated, vsDraw.markers[markBit].alpha); |
|
|
} |
|
|
marks >>= 1; |
|
|
} |
|
|
if (vsDraw.maskInLine) { |
|
|
int marksMasked = pdoc->GetMark(line) & vsDraw.maskInLine; |
|
|
if (marksMasked) { |
|
|
for (int markBit = 0; (markBit < 32) && marksMasked; markBit++) { |
|
|
if ((marksMasked & 1) && (vsDraw.markers[markBit].markType != SC_MARK_EMPTY)) { |
|
|
SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back.allocated, vsDraw.markers[markBit].alpha); |
|
|
} |
|
|
marksMasked >>= 1; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::RefreshPixMaps(Surface *surfaceWindow) { |
|
|
if (!pixmapSelPattern->Initialised()) { |
|
|
const int patternSize = 8; |
|
|
pixmapSelPattern->InitPixMap(patternSize, patternSize, surfaceWindow, wMain.GetID()); |
|
|
// This complex procedure is to reproduce the checkerboard dithered pattern used by windows |
|
|
// for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half |
|
|
// way between the chrome colour and the chrome highlight colour making a nice transition |
|
|
// between the window chrome and the content area. And it works in low colour depths. |
|
|
PRectangle rcPattern(0, 0, patternSize, patternSize); |
|
|
|
|
|
// Initialize default colours based on the chrome colour scheme. Typically the highlight is white. |
|
|
ColourAllocated colourFMFill = vs.selbar.allocated; |
|
|
ColourAllocated colourFMStripes = vs.selbarlight.allocated; |
|
|
|
|
|
if (!(vs.selbarlight.desired == ColourDesired(0xff, 0xff, 0xff))) { |
|
|
// User has chosen an unusual chrome colour scheme so just use the highlight edge colour. |
|
|
// (Typically, the highlight colour is white.) |
|
|
colourFMFill = vs.selbarlight.allocated; |
|
|
} |
|
|
|
|
|
if (vs.foldmarginColourSet) { |
|
|
// override default fold margin colour |
|
|
colourFMFill = vs.foldmarginColour.allocated; |
|
|
} |
|
|
if (vs.foldmarginHighlightColourSet) { |
|
|
// override default fold margin highlight colour |
|
|
colourFMStripes = vs.foldmarginHighlightColour.allocated; |
|
|
} |
|
|
|
|
|
pixmapSelPattern->FillRectangle(rcPattern, colourFMFill); |
|
|
pixmapSelPattern->PenColour(colourFMStripes); |
|
|
for (int stripe = 0; stripe < patternSize; stripe++) { |
|
|
// Alternating 1 pixel stripes is same as checkerboard. |
|
|
pixmapSelPattern->MoveTo(0, stripe * 2); |
|
|
pixmapSelPattern->LineTo(patternSize, stripe * 2 - patternSize); |
|
|
} |
|
|
} |
|
|
|
|
|
if (!pixmapIndentGuide->Initialised()) { |
|
|
// 1 extra pixel in height so can handle odd/even positions and so produce a continuous line |
|
|
pixmapIndentGuide->InitPixMap(1, vs.lineHeight + 1, surfaceWindow, wMain.GetID()); |
|
|
pixmapIndentGuideHighlight->InitPixMap(1, vs.lineHeight + 1, surfaceWindow, wMain.GetID()); |
|
|
PRectangle rcIG(0, 0, 1, vs.lineHeight); |
|
|
pixmapIndentGuide->FillRectangle(rcIG, vs.styles[STYLE_INDENTGUIDE].back.allocated); |
|
|
pixmapIndentGuide->PenColour(vs.styles[STYLE_INDENTGUIDE].fore.allocated); |
|
|
pixmapIndentGuideHighlight->FillRectangle(rcIG, vs.styles[STYLE_BRACELIGHT].back.allocated); |
|
|
pixmapIndentGuideHighlight->PenColour(vs.styles[STYLE_BRACELIGHT].fore.allocated); |
|
|
for (int stripe = 1; stripe < vs.lineHeight + 1; stripe += 2) { |
|
|
pixmapIndentGuide->MoveTo(0, stripe); |
|
|
pixmapIndentGuide->LineTo(2, stripe); |
|
|
pixmapIndentGuideHighlight->MoveTo(0, stripe); |
|
|
pixmapIndentGuideHighlight->LineTo(2, stripe); |
|
|
} |
|
|
} |
|
|
|
|
|
if (bufferedDraw) { |
|
|
if (!pixmapLine->Initialised()) { |
|
|
PRectangle rcClient = GetClientRectangle(); |
|
|
pixmapLine->InitPixMap(rcClient.Width(), vs.lineHeight, |
|
|
surfaceWindow, wMain.GetID()); |
|
|
pixmapSelMargin->InitPixMap(vs.fixedColumnWidth, |
|
|
rcClient.Height(), surfaceWindow, wMain.GetID()); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { |
|
|
//Platform::DebugPrintf("Paint:%1d (%3d,%3d) ... (%3d,%3d)\n", |
|
|
// paintingAllText, rcArea.left, rcArea.top, rcArea.right, rcArea.bottom); |
|
|
|
|
|
RefreshStyleData(); |
|
|
RefreshPixMaps(surfaceWindow); |
|
|
|
|
|
PRectangle rcClient = GetClientRectangle(); |
|
|
//Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", |
|
|
// rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); |
|
|
|
|
|
surfaceWindow->SetPalette(&palette, true); |
|
|
pixmapLine->SetPalette(&palette, !hasFocus); |
|
|
|
|
|
int screenLinePaintFirst = rcArea.top / vs.lineHeight; |
|
|
// The area to be painted plus one extra line is styled. |
|
|
// The extra line is to determine when a style change, such as starting a comment flows on to other lines. |
|
|
int lineStyleLast = topLine + (rcArea.bottom - 1) / vs.lineHeight + 1; |
|
|
//Platform::DebugPrintf("Paint lines = %d .. %d\n", topLine + screenLinePaintFirst, lineStyleLast); |
|
|
int endPosPaint = pdoc->Length(); |
|
|
if (lineStyleLast < cs.LinesDisplayed()) |
|
|
endPosPaint = pdoc->LineStart(cs.DocFromDisplay(lineStyleLast + 1)); |
|
|
|
|
|
int xStart = vs.fixedColumnWidth - xOffset; |
|
|
int ypos = 0; |
|
|
if (!bufferedDraw) |
|
|
ypos += screenLinePaintFirst * vs.lineHeight; |
|
|
int yposScreen = screenLinePaintFirst * vs.lineHeight; |
|
|
|
|
|
// Ensure we are styled as far as we are painting. |
|
|
pdoc->EnsureStyledTo(endPosPaint); |
|
|
bool paintAbandonedByStyling = paintState == paintAbandoned; |
|
|
if (needUpdateUI) { |
|
|
NotifyUpdateUI(); |
|
|
needUpdateUI = false; |
|
|
RefreshStyleData(); |
|
|
RefreshPixMaps(surfaceWindow); |
|
|
} |
|
|
|
|
|
// Call priority lines wrap on a window of lines which are likely |
|
|
// to rendered with the following paint (that is wrap the visible |
|
|
// lines first). |
|
|
int startLineToWrap = cs.DocFromDisplay(topLine) - 5; |
|
|
if (startLineToWrap < 0) |
|
|
startLineToWrap = -1; |
|
|
if (WrapLines(false, startLineToWrap)) { |
|
|
// The wrapping process has changed the height of some lines so |
|
|
// abandon this paint for a complete repaint. |
|
|
if (AbandonPaint()) { |
|
|
return; |
|
|
} |
|
|
RefreshPixMaps(surfaceWindow); // In case pixmaps invalidated by scrollbar change |
|
|
} |
|
|
PLATFORM_ASSERT(pixmapSelPattern->Initialised()); |
|
|
|
|
|
PaintSelMargin(surfaceWindow, rcArea); |
|
|
|
|
|
PRectangle rcRightMargin = rcClient; |
|
|
rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; |
|
|
if (rcArea.Intersects(rcRightMargin)) { |
|
|
surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back.allocated); |
|
|
} |
|
|
|
|
|
if (paintState == paintAbandoned) { |
|
|
// Either styling or NotifyUpdateUI noticed that painting is needed |
|
|
// outside the current painting rectangle |
|
|
//Platform::DebugPrintf("Abandoning paint\n"); |
|
|
if (wrapState != eWrapNone) { |
|
|
if (paintAbandonedByStyling) { |
|
|
// Styling has spilled over a line end, such as occurs by starting a multiline |
|
|
// comment. The width of subsequent text may have changed, so rewrap. |
|
|
NeedWrapping(cs.DocFromDisplay(topLine)); |
|
|
} |
|
|
} |
|
|
return; |
|
|
} |
|
|
//Platform::DebugPrintf("start display %d, offset = %d\n", pdoc->Length(), xOffset); |
|
|
|
|
|
// Do the painting |
|
|
if (rcArea.right > vs.fixedColumnWidth) { |
|
|
|
|
|
Surface *surface = surfaceWindow; |
|
|
if (bufferedDraw) { |
|
|
surface = pixmapLine; |
|
|
PLATFORM_ASSERT(pixmapLine->Initialised()); |
|
|
} |
|
|
surface->SetUnicodeMode(IsUnicodeMode()); |
|
|
surface->SetDBCSMode(CodePage()); |
|
|
|
|
|
int visibleLine = topLine + screenLinePaintFirst; |
|
|
|
|
|
int posCaret = currentPos; |
|
|
if (posDrag >= 0) |
|
|
posCaret = posDrag; |
|
|
int lineCaret = pdoc->LineFromPosition(posCaret); |
|
|
|
|
|
// Remove selection margin from drawing area so text will not be drawn |
|
|
// on it in unbuffered mode. |
|
|
PRectangle rcTextArea = rcClient; |
|
|
rcTextArea.left = vs.fixedColumnWidth; |
|
|
rcTextArea.right -= vs.rightMarginWidth; |
|
|
surfaceWindow->SetClip(rcTextArea); |
|
|
|
|
|
// Loop on visible lines |
|
|
//double durLayout = 0.0; |
|
|
//double durPaint = 0.0; |
|
|
//double durCopy = 0.0; |
|
|
//ElapsedTime etWhole; |
|
|
int lineDocPrevious = -1; // Used to avoid laying out one document line multiple times |
|
|
AutoLineLayout ll(llc, 0); |
|
|
SelectionLineIterator lineIterator(this); |
|
|
while (visibleLine < cs.LinesDisplayed() && yposScreen < rcArea.bottom) { |
|
|
|
|
|
int lineDoc = cs.DocFromDisplay(visibleLine); |
|
|
// Only visible lines should be handled by the code within the loop |
|
|
PLATFORM_ASSERT(cs.GetVisible(lineDoc)); |
|
|
int lineStartSet = cs.DisplayFromDoc(lineDoc); |
|
|
int subLine = visibleLine - lineStartSet; |
|
|
|
|
|
// Copy this line and its styles from the document into local arrays |
|
|
// and determine the x position at which each character starts. |
|
|
//ElapsedTime et; |
|
|
if (lineDoc != lineDocPrevious) { |
|
|
ll.Set(0); |
|
|
// For rectangular selection this accesses the layout cache so should be after layout returned. |
|
|
lineIterator.SetAt(lineDoc); |
|
|
ll.Set(RetrieveLineLayout(lineDoc)); |
|
|
LayoutLine(lineDoc, surface, vs, ll, wrapWidth); |
|
|
lineDocPrevious = lineDoc; |
|
|
} |
|
|
//durLayout += et.Duration(true); |
|
|
|
|
|
if (ll) { |
|
|
if (selType == selStream) { |
|
|
ll->selStart = SelectionStart(); |
|
|
ll->selEnd = SelectionEnd(); |
|
|
} else { |
|
|
ll->selStart = lineIterator.startPos; |
|
|
ll->selEnd = lineIterator.endPos; |
|
|
} |
|
|
ll->containsCaret = lineDoc == lineCaret; |
|
|
if (hideSelection) { |
|
|
ll->selStart = -1; |
|
|
ll->selEnd = -1; |
|
|
ll->containsCaret = false; |
|
|
} |
|
|
|
|
|
GetHotSpotRange(ll->hsStart, ll->hsEnd); |
|
|
|
|
|
PRectangle rcLine = rcClient; |
|
|
rcLine.top = ypos; |
|
|
rcLine.bottom = ypos + vs.lineHeight; |
|
|
|
|
|
Range rangeLine(pdoc->LineStart(lineDoc), pdoc->LineStart(lineDoc + 1)); |
|
|
// Highlight the current braces if any |
|
|
ll->SetBracesHighlight(rangeLine, braces, static_cast<char>(bracesMatchStyle), |
|
|
highlightGuideColumn * vs.spaceWidth); |
|
|
|
|
|
// Draw the line |
|
|
DrawLine(surface, vs, lineDoc, visibleLine, xStart, rcLine, ll, subLine); |
|
|
//durPaint += et.Duration(true); |
|
|
|
|
|
// Restore the previous styles for the brace highlights in case layout is in cache. |
|
|
ll->RestoreBracesHighlight(rangeLine, braces); |
|
|
|
|
|
bool expanded = cs.GetExpanded(lineDoc); |
|
|
if ((foldFlags & SC_FOLDFLAG_BOX) == 0) { |
|
|
// Paint the line above the fold |
|
|
if ((expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_EXPANDED)) |
|
|
|| |
|
|
(!expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_CONTRACTED))) { |
|
|
if (pdoc->GetLevel(lineDoc) & SC_FOLDLEVELHEADERFLAG) { |
|
|
PRectangle rcFoldLine = rcLine; |
|
|
rcFoldLine.bottom = rcFoldLine.top + 1; |
|
|
surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); |
|
|
} |
|
|
} |
|
|
// Paint the line below the fold |
|
|
if ((expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_EXPANDED)) |
|
|
|| |
|
|
(!expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { |
|
|
if (pdoc->GetLevel(lineDoc) & SC_FOLDLEVELHEADERFLAG) { |
|
|
PRectangle rcFoldLine = rcLine; |
|
|
rcFoldLine.top = rcFoldLine.bottom - 1; |
|
|
surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
int FoldLevelCurr = (pdoc->GetLevel(lineDoc) & SC_FOLDLEVELNUMBERMASK) - SC_FOLDLEVELBASE; |
|
|
int FoldLevelPrev = (pdoc->GetLevel(lineDoc - 1) & SC_FOLDLEVELNUMBERMASK) - SC_FOLDLEVELBASE; |
|
|
int FoldLevelFlags = (pdoc->GetLevel(lineDoc) & ~SC_FOLDLEVELNUMBERMASK) & ~(0xFFF0000); |
|
|
int indentationStep = pdoc->IndentSize(); |
|
|
// Draw line above fold |
|
|
if ((FoldLevelPrev < FoldLevelCurr) |
|
|
|| |
|
|
(FoldLevelFlags & SC_FOLDLEVELBOXHEADERFLAG |
|
|
&& |
|
|
(pdoc->GetLevel(lineDoc - 1) & SC_FOLDLEVELBOXFOOTERFLAG) == 0)) { |
|
|
PRectangle rcFoldLine = rcLine; |
|
|
rcFoldLine.bottom = rcFoldLine.top + 1; |
|
|
rcFoldLine.left += xStart + FoldLevelCurr * vs.spaceWidth * indentationStep - 1; |
|
|
surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); |
|
|
} |
|
|
|
|
|
// Line below the fold (or below a contracted fold) |
|
|
if (FoldLevelFlags & SC_FOLDLEVELBOXFOOTERFLAG |
|
|
|| |
|
|
(!expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { |
|
|
PRectangle rcFoldLine = rcLine; |
|
|
rcFoldLine.top = rcFoldLine.bottom - 1; |
|
|
rcFoldLine.left += xStart + (FoldLevelCurr) * vs.spaceWidth * indentationStep - 1; |
|
|
surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); |
|
|
} |
|
|
|
|
|
PRectangle rcBoxLine = rcLine; |
|
|
// Draw vertical line for every fold level |
|
|
for (int i = 0; i <= FoldLevelCurr; i++) { |
|
|
rcBoxLine.left = xStart + i * vs.spaceWidth * indentationStep - 1; |
|
|
rcBoxLine.right = rcBoxLine.left + 1; |
|
|
surface->FillRectangle(rcBoxLine, vs.styles[STYLE_DEFAULT].fore.allocated); |
|
|
} |
|
|
} |
|
|
|
|
|
// Draw the Caret |
|
|
if (lineDoc == lineCaret) { |
|
|
int offset = Platform::Minimum(posCaret - rangeLine.start, ll->maxLineLength); |
|
|
if ((offset >= ll->LineStart(subLine)) && |
|
|
((offset < ll->LineStart(subLine + 1)) || offset == ll->numCharsInLine)) { |
|
|
int xposCaret = ll->positions[offset] - ll->positions[ll->LineStart(subLine)] + xStart; |
|
|
|
|
|
if (actualWrapVisualStartIndent != 0) { |
|
|
int lineStart = ll->LineStart(subLine); |
|
|
if (lineStart != 0) // Wrapped |
|
|
xposCaret += actualWrapVisualStartIndent * vs.aveCharWidth; |
|
|
} |
|
|
int widthOverstrikeCaret; |
|
|
if (posCaret == pdoc->Length()) { // At end of document |
|
|
widthOverstrikeCaret = vs.aveCharWidth; |
|
|
} else if ((posCaret - rangeLine.start) >= ll->numCharsInLine) { // At end of line |
|
|
widthOverstrikeCaret = vs.aveCharWidth; |
|
|
} else { |
|
|
widthOverstrikeCaret = ll->positions[offset + 1] - ll->positions[offset]; |
|
|
} |
|
|
if (widthOverstrikeCaret < 3) // Make sure its visible |
|
|
widthOverstrikeCaret = 3; |
|
|
if (((caret.active && caret.on) || (posDrag >= 0)) && xposCaret >= 0) { |
|
|
PRectangle rcCaret = rcLine; |
|
|
int caretWidthOffset = 0; |
|
|
if ((offset > 0) && (vs.caretWidth > 1)) |
|
|
caretWidthOffset = 1; // Move back so overlaps both character cells. |
|
|
if (posDrag >= 0) { |
|
|
rcCaret.left = xposCaret - caretWidthOffset; |
|
|
rcCaret.right = rcCaret.left + vs.caretWidth; |
|
|
} else { |
|
|
if (inOverstrike) { |
|
|
rcCaret.top = rcCaret.bottom - 2; |
|
|
rcCaret.left = xposCaret + 1; |
|
|
rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; |
|
|
} else { |
|
|
rcCaret.left = xposCaret - caretWidthOffset; |
|
|
rcCaret.right = rcCaret.left + vs.caretWidth; |
|
|
} |
|
|
} |
|
|
surface->FillRectangle(rcCaret, vs.caretcolour.allocated); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (bufferedDraw) { |
|
|
Point from(vs.fixedColumnWidth, 0); |
|
|
PRectangle rcCopyArea(vs.fixedColumnWidth, yposScreen, |
|
|
rcClient.right, yposScreen + vs.lineHeight); |
|
|
surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); |
|
|
} |
|
|
//durCopy += et.Duration(true); |
|
|
} |
|
|
|
|
|
if (!bufferedDraw) { |
|
|
ypos += vs.lineHeight; |
|
|
} |
|
|
|
|
|
yposScreen += vs.lineHeight; |
|
|
visibleLine++; |
|
|
//gdk_flush(); |
|
|
} |
|
|
ll.Set(0); |
|
|
//if (durPaint < 0.00000001) |
|
|
// durPaint = 0.00000001; |
|
|
|
|
|
// Right column limit indicator |
|
|
PRectangle rcBeyondEOF = rcClient; |
|
|
rcBeyondEOF.left = vs.fixedColumnWidth; |
|
|
rcBeyondEOF.right = rcBeyondEOF.right; |
|
|
rcBeyondEOF.top = (cs.LinesDisplayed() - topLine) * vs.lineHeight; |
|
|
if (rcBeyondEOF.top < rcBeyondEOF.bottom) { |
|
|
surfaceWindow->FillRectangle(rcBeyondEOF, vs.styles[STYLE_DEFAULT].back.allocated); |
|
|
if (vs.edgeState == EDGE_LINE) { |
|
|
int edgeX = theEdge * vs.spaceWidth; |
|
|
rcBeyondEOF.left = edgeX + xStart; |
|
|
rcBeyondEOF.right = rcBeyondEOF.left + 1; |
|
|
surfaceWindow->FillRectangle(rcBeyondEOF, vs.edgecolour.allocated); |
|
|
} |
|
|
} |
|
|
//Platform::DebugPrintf( |
|
|
//"Layout:%9.6g Paint:%9.6g Ratio:%9.6g Copy:%9.6g Total:%9.6g\n", |
|
|
//durLayout, durPaint, durLayout / durPaint, durCopy, etWhole.Duration()); |
|
|
NotifyPainted(); |
|
|
} |
|
|
} |
|
|
|
|
|
// Space (3 space characters) between line numbers and text when printing. |
|
|
#define lineNumberPrintSpace " " |
|
|
|
|
|
ColourDesired InvertedLight(ColourDesired orig) { |
|
|
unsigned int r = orig.GetRed(); |
|
|
unsigned int g = orig.GetGreen(); |
|
|
unsigned int b = orig.GetBlue(); |
|
|
unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye |
|
|
unsigned int il = 0xff - l; |
|
|
if (l == 0) |
|
|
return ColourDesired(0xff, 0xff, 0xff); |
|
|
r = r * il / l; |
|
|
g = g * il / l; |
|
|
b = b * il / l; |
|
|
return ColourDesired(Platform::Minimum(r, 0xff), Platform::Minimum(g, 0xff), Platform::Minimum(b, 0xff)); |
|
|
} |
|
|
|
|
|
// This is mostly copied from the Paint method but with some things omitted |
|
|
// such as the margin markers, line numbers, selection and caret |
|
|
// Should be merged back into a combined Draw method. |
|
|
long Editor::FormatRange(bool draw, RangeToFormat *pfr) { |
|
|
if (!pfr) |
|
|
return 0; |
|
|
|
|
|
AutoSurface surface(pfr->hdc, this); |
|
|
if (!surface) |
|
|
return 0; |
|
|
AutoSurface surfaceMeasure(pfr->hdcTarget, this); |
|
|
if (!surfaceMeasure) { |
|
|
return 0; |
|
|
} |
|
|
|
|
|
ViewStyle vsPrint(vs); |
|
|
|
|
|
// Modify the view style for printing as do not normally want any of the transient features to be printed |
|
|
// Printing supports only the line number margin. |
|
|
int lineNumberIndex = -1; |
|
|
for (int margin = 0; margin < ViewStyle::margins; margin++) { |
|
|
if ((vsPrint.ms[margin].style == SC_MARGIN_NUMBER) && (vsPrint.ms[margin].width > 0)) { |
|
|
lineNumberIndex = margin; |
|
|
} else { |
|
|
vsPrint.ms[margin].width = 0; |
|
|
} |
|
|
} |
|
|
vsPrint.showMarkedLines = false; |
|
|
vsPrint.fixedColumnWidth = 0; |
|
|
vsPrint.zoomLevel = printMagnification; |
|
|
vsPrint.viewIndentationGuides = false; |
|
|
// Don't show the selection when printing |
|
|
vsPrint.selbackset = false; |
|
|
vsPrint.selforeset = false; |
|
|
vsPrint.selAlpha = SC_ALPHA_NOALPHA; |
|
|
vsPrint.whitespaceBackgroundSet = false; |
|
|
vsPrint.whitespaceForegroundSet = false; |
|
|
vsPrint.showCaretLineBackground = false; |
|
|
|
|
|
// Set colours for printing according to users settings |
|
|
for (int sty = 0;sty <= STYLE_MAX;sty++) { |
|
|
if (printColourMode == SC_PRINT_INVERTLIGHT) { |
|
|
vsPrint.styles[sty].fore.desired = InvertedLight(vsPrint.styles[sty].fore.desired); |
|
|
vsPrint.styles[sty].back.desired = InvertedLight(vsPrint.styles[sty].back.desired); |
|
|
} else if (printColourMode == SC_PRINT_BLACKONWHITE) { |
|
|
vsPrint.styles[sty].fore.desired = ColourDesired(0, 0, 0); |
|
|
vsPrint.styles[sty].back.desired = ColourDesired(0xff, 0xff, 0xff); |
|
|
} else if (printColourMode == SC_PRINT_COLOURONWHITE) { |
|
|
vsPrint.styles[sty].back.desired = ColourDesired(0xff, 0xff, 0xff); |
|
|
} else if (printColourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { |
|
|
if (sty <= STYLE_DEFAULT) { |
|
|
vsPrint.styles[sty].back.desired = ColourDesired(0xff, 0xff, 0xff); |
|
|
} |
|
|
} |
|
|
} |
|
|
// White background for the line numbers |
|
|
vsPrint.styles[STYLE_LINENUMBER].back.desired = ColourDesired(0xff, 0xff, 0xff); |
|
|
|
|
|
vsPrint.Refresh(*surfaceMeasure); |
|
|
// Ensure colours are set up |
|
|
vsPrint.RefreshColourPalette(palette, true); |
|
|
vsPrint.RefreshColourPalette(palette, false); |
|
|
// Determining width must hapen after fonts have been realised in Refresh |
|
|
int lineNumberWidth = 0; |
|
|
if (lineNumberIndex >= 0) { |
|
|
lineNumberWidth = surfaceMeasure->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, |
|
|
"99999" lineNumberPrintSpace, 5 + istrlen(lineNumberPrintSpace)); |
|
|
vsPrint.ms[lineNumberIndex].width = lineNumberWidth; |
|
|
} |
|
|
|
|
|
int linePrintStart = pdoc->LineFromPosition(pfr->chrg.cpMin); |
|
|
int linePrintLast = linePrintStart + (pfr->rc.bottom - pfr->rc.top) / vsPrint.lineHeight - 1; |
|
|
if (linePrintLast < linePrintStart) |
|
|
linePrintLast = linePrintStart; |
|
|
int linePrintMax = pdoc->LineFromPosition(pfr->chrg.cpMax); |
|
|
if (linePrintLast > linePrintMax) |
|
|
linePrintLast = linePrintMax; |
|
|
//Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n", |
|
|
// linePrintStart, linePrintLast, linePrintMax, pfr->rc.top, pfr->rc.bottom, vsPrint.lineHeight, |
|
|
// surfaceMeasure->Height(vsPrint.styles[STYLE_LINENUMBER].font)); |
|
|
int endPosPrint = pdoc->Length(); |
|
|
if (linePrintLast < pdoc->LinesTotal()) |
|
|
endPosPrint = pdoc->LineStart(linePrintLast + 1); |
|
|
|
|
|
// Ensure we are styled to where we are formatting. |
|
|
pdoc->EnsureStyledTo(endPosPrint); |
|
|
|
|
|
int xStart = vsPrint.fixedColumnWidth + pfr->rc.left + lineNumberWidth; |
|
|
int ypos = pfr->rc.top; |
|
|
|
|
|
int lineDoc = linePrintStart; |
|
|
|
|
|
int nPrintPos = pfr->chrg.cpMin; |
|
|
int visibleLine = 0; |
|
|
int widthPrint = pfr->rc.Width() - lineNumberWidth; |
|
|
if (printWrapState == eWrapNone) |
|
|
widthPrint = LineLayout::wrapWidthInfinite; |
|
|
|
|
|
while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) { |
|
|
|
|
|
// When printing, the hdc and hdcTarget may be the same, so |
|
|
// changing the state of surfaceMeasure may change the underlying |
|
|
// state of surface. Therefore, any cached state is discarded before |
|
|
// using each surface. |
|
|
surfaceMeasure->FlushCachedState(); |
|
|
|
|
|
// Copy this line and its styles from the document into local arrays |
|
|
// and determine the x position at which each character starts. |
|
|
LineLayout ll(8000); |
|
|
LayoutLine(lineDoc, surfaceMeasure, vsPrint, &ll, widthPrint); |
|
|
|
|
|
ll.selStart = -1; |
|
|
ll.selEnd = -1; |
|
|
ll.containsCaret = false; |
|
|
|
|
|
PRectangle rcLine; |
|
|
rcLine.left = pfr->rc.left + lineNumberWidth; |
|
|
rcLine.top = ypos; |
|
|
rcLine.right = pfr->rc.right - 1; |
|
|
rcLine.bottom = ypos + vsPrint.lineHeight; |
|
|
|
|
|
// When document line is wrapped over multiple display lines, find where |
|
|
// to start printing from to ensure a particular position is on the first |
|
|
// line of the page. |
|
|
if (visibleLine == 0) { |
|
|
int startWithinLine = nPrintPos - pdoc->LineStart(lineDoc); |
|
|
for (int iwl = 0; iwl < ll.lines - 1; iwl++) { |
|
|
if (ll.LineStart(iwl) <= startWithinLine && ll.LineStart(iwl + 1) >= startWithinLine) { |
|
|
visibleLine = -iwl; |
|
|
} |
|
|
} |
|
|
|
|
|
if (ll.lines > 1 && startWithinLine >= ll.LineStart(ll.lines - 1)) { |
|
|
visibleLine = -(ll.lines - 1); |
|
|
} |
|
|
} |
|
|
|
|
|
if (draw && lineNumberWidth && |
|
|
(ypos + vsPrint.lineHeight <= pfr->rc.bottom) && |
|
|
(visibleLine >= 0)) { |
|
|
char number[100]; |
|
|
sprintf(number, "%d" lineNumberPrintSpace, lineDoc + 1); |
|
|
PRectangle rcNumber = rcLine; |
|
|
rcNumber.right = rcNumber.left + lineNumberWidth; |
|
|
// Right justify |
|
|
rcNumber.left -= surfaceMeasure->WidthText( |
|
|
vsPrint.styles[STYLE_LINENUMBER].font, number, istrlen(number)); |
|
|
surface->FlushCachedState(); |
|
|
surface->DrawTextNoClip(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, |
|
|
ypos + vsPrint.maxAscent, number, istrlen(number), |
|
|
vsPrint.styles[STYLE_LINENUMBER].fore.allocated, |
|
|
vsPrint.styles[STYLE_LINENUMBER].back.allocated); |
|
|
} |
|
|
|
|
|
// Draw the line |
|
|
surface->FlushCachedState(); |
|
|
|
|
|
for (int iwl = 0; iwl < ll.lines; iwl++) { |
|
|
if (ypos + vsPrint.lineHeight <= pfr->rc.bottom) { |
|
|
if (visibleLine >= 0) { |
|
|
if (draw) { |
|
|
rcLine.top = ypos; |
|
|
rcLine.bottom = ypos + vsPrint.lineHeight; |
|
|
DrawLine(surface, vsPrint, lineDoc, visibleLine, xStart, rcLine, &ll, iwl); |
|
|
} |
|
|
ypos += vsPrint.lineHeight; |
|
|
} |
|
|
visibleLine++; |
|
|
if (iwl == ll.lines - 1) |
|
|
nPrintPos = pdoc->LineStart(lineDoc + 1); |
|
|
else |
|
|
nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl); |
|
|
} |
|
|
} |
|
|
|
|
|
++lineDoc; |
|
|
} |
|
|
|
|
|
return nPrintPos; |
|
|
} |
|
|
|
|
|
int Editor::TextWidth(int style, const char *text) { |
|
|
RefreshStyleData(); |
|
|
AutoSurface surface(this); |
|
|
if (surface) { |
|
|
return surface->WidthText(vs.styles[style].font, text, istrlen(text)); |
|
|
} else { |
|
|
return 1; |
|
|
} |
|
|
} |
|
|
|
|
|
// Empty method is overridden on GTK+ to show / hide scrollbars |
|
|
void Editor::ReconfigureScrollBars() {} |
|
|
|
|
|
void Editor::SetScrollBars() { |
|
|
RefreshStyleData(); |
|
|
|
|
|
int nMax = MaxScrollPos(); |
|
|
int nPage = LinesOnScreen(); |
|
|
bool modified = ModifyScrollBars(nMax + nPage - 1, nPage); |
|
|
if (modified) { |
|
|
DwellEnd(true); |
|
|
} |
|
|
|
|
|
// TODO: ensure always showing as many lines as possible |
|
|
// May not be, if, for example, window made larger |
|
|
if (topLine > MaxScrollPos()) { |
|
|
SetTopLine(Platform::Clamp(topLine, 0, MaxScrollPos())); |
|
|
SetVerticalScrollPos(); |
|
|
Redraw(); |
|
|
} |
|
|
if (modified) { |
|
|
if (!AbandonPaint()) |
|
|
Redraw(); |
|
|
} |
|
|
//Platform::DebugPrintf("end max = %d page = %d\n", nMax, nPage); |
|
|
} |
|
|
|
|
|
void Editor::ChangeSize() { |
|
|
DropGraphics(); |
|
|
SetScrollBars(); |
|
|
if (wrapState != eWrapNone) { |
|
|
PRectangle rcTextArea = GetClientRectangle(); |
|
|
rcTextArea.left = vs.fixedColumnWidth; |
|
|
rcTextArea.right -= vs.rightMarginWidth; |
|
|
if (wrapWidth != rcTextArea.Width()) { |
|
|
NeedWrapping(); |
|
|
Redraw(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::AddChar(char ch) { |
|
|
char s[2]; |
|
|
s[0] = ch; |
|
|
s[1] = '\0'; |
|
|
AddCharUTF(s, 1); |
|
|
} |
|
|
|
|
|
void Editor::AddCharUTF(char *s, unsigned int len, bool treatAsDBCS) { |
|
|
bool wasSelection = currentPos != anchor; |
|
|
ClearSelection(); |
|
|
bool charReplaceAction = false; |
|
|
if (inOverstrike && !wasSelection && !RangeContainsProtected(currentPos, currentPos + 1)) { |
|
|
if (currentPos < (pdoc->Length())) { |
|
|
if (!IsEOLChar(pdoc->CharAt(currentPos))) { |
|
|
charReplaceAction = true; |
|
|
pdoc->BeginUndoAction(); |
|
|
pdoc->DelChar(currentPos); |
|
|
} |
|
|
} |
|
|
} |
|
|
if (pdoc->InsertString(currentPos, s, len)) { |
|
|
SetEmptySelection(currentPos + len); |
|
|
} |
|
|
if (charReplaceAction) { |
|
|
pdoc->EndUndoAction(); |
|
|
} |
|
|
EnsureCaretVisible(); |
|
|
// Avoid blinking during rapid typing: |
|
|
ShowCaretAtCurrentPosition(); |
|
|
if (!caretSticky) { |
|
|
SetLastXChosen(); |
|
|
} |
|
|
|
|
|
if (treatAsDBCS) { |
|
|
NotifyChar((static_cast<unsigned char>(s[0]) << 8) | |
|
|
static_cast<unsigned char>(s[1])); |
|
|
} else { |
|
|
int byte = static_cast<unsigned char>(s[0]); |
|
|
if ((byte < 0xC0) || (1 == len)) { |
|
|
// Handles UTF-8 characters between 0x01 and 0x7F and single byte |
|
|
// characters when not in UTF-8 mode. |
|
|
// Also treats \0 and naked trail bytes 0x80 to 0xBF as valid |
|
|
// characters representing themselves. |
|
|
} else { |
|
|
// Unroll 1 to 3 byte UTF-8 sequences. See reference data at: |
|
|
// http://www.cl.cam.ac.uk/~mgk25/unicode.html |
|
|
// http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt |
|
|
if (byte < 0xE0) { |
|
|
int byte2 = static_cast<unsigned char>(s[1]); |
|
|
if ((byte2 & 0xC0) == 0x80) { |
|
|
// Two-byte-character lead-byte followed by a trail-byte. |
|
|
byte = (((byte & 0x1F) << 6) | (byte2 & 0x3F)); |
|
|
} |
|
|
// A two-byte-character lead-byte not followed by trail-byte |
|
|
// represents itself. |
|
|
} else if (byte < 0xF0) { |
|
|
int byte2 = static_cast<unsigned char>(s[1]); |
|
|
int byte3 = static_cast<unsigned char>(s[2]); |
|
|
if (((byte2 & 0xC0) == 0x80) && ((byte3 & 0xC0) == 0x80)) { |
|
|
// Three-byte-character lead byte followed by two trail bytes. |
|
|
byte = (((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | |
|
|
(byte3 & 0x3F)); |
|
|
} |
|
|
// A three-byte-character lead-byte not followed by two trail-bytes |
|
|
// represents itself. |
|
|
} |
|
|
} |
|
|
NotifyChar(byte); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::ClearSelection() { |
|
|
if (!SelectionContainsProtected()) { |
|
|
int startPos = SelectionStart(); |
|
|
if (selType == selStream) { |
|
|
unsigned int chars = SelectionEnd() - startPos; |
|
|
if (0 != chars) { |
|
|
pdoc->BeginUndoAction(); |
|
|
pdoc->DeleteChars(startPos, chars); |
|
|
pdoc->EndUndoAction(); |
|
|
} |
|
|
} else { |
|
|
pdoc->BeginUndoAction(); |
|
|
SelectionLineIterator lineIterator(this, false); |
|
|
while (lineIterator.Iterate()) { |
|
|
startPos = lineIterator.startPos; |
|
|
unsigned int chars = lineIterator.endPos - startPos; |
|
|
if (0 != chars) { |
|
|
pdoc->DeleteChars(startPos, chars); |
|
|
} |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
selType = selStream; |
|
|
} |
|
|
SetEmptySelection(startPos); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::ClearAll() { |
|
|
pdoc->BeginUndoAction(); |
|
|
if (0 != pdoc->Length()) { |
|
|
pdoc->DeleteChars(0, pdoc->Length()); |
|
|
} |
|
|
if (!pdoc->IsReadOnly()) { |
|
|
cs.Clear(); |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
anchor = 0; |
|
|
currentPos = 0; |
|
|
SetTopLine(0); |
|
|
SetVerticalScrollPos(); |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
|
|
|
void Editor::ClearDocumentStyle() { |
|
|
pdoc->StartStyling(0, '\377'); |
|
|
pdoc->SetStyleFor(pdoc->Length(), 0); |
|
|
cs.ShowAll(); |
|
|
pdoc->ClearLevels(); |
|
|
} |
|
|
|
|
|
void Editor::Cut() { |
|
|
if (!pdoc->IsReadOnly() && !SelectionContainsProtected()) { |
|
|
Copy(); |
|
|
ClearSelection(); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::PasteRectangular(int pos, const char *ptr, int len) { |
|
|
if (pdoc->IsReadOnly() || SelectionContainsProtected()) { |
|
|
return; |
|
|
} |
|
|
currentPos = pos; |
|
|
int xInsert = XFromPosition(currentPos); |
|
|
int line = pdoc->LineFromPosition(currentPos); |
|
|
bool prevCr = false; |
|
|
pdoc->BeginUndoAction(); |
|
|
for (int i = 0; i < len; i++) { |
|
|
if (IsEOLChar(ptr[i])) { |
|
|
if ((ptr[i] == '\r') || (!prevCr)) |
|
|
line++; |
|
|
if (line >= pdoc->LinesTotal()) { |
|
|
if (pdoc->eolMode != SC_EOL_LF) |
|
|
pdoc->InsertChar(pdoc->Length(), '\r'); |
|
|
if (pdoc->eolMode != SC_EOL_CR) |
|
|
pdoc->InsertChar(pdoc->Length(), '\n'); |
|
|
} |
|
|
// Pad the end of lines with spaces if required |
|
|
currentPos = PositionFromLineX(line, xInsert); |
|
|
if ((XFromPosition(currentPos) < xInsert) && (i + 1 < len)) { |
|
|
for (int i = 0; i < xInsert - XFromPosition(currentPos); i++) { |
|
|
pdoc->InsertChar(currentPos, ' '); |
|
|
currentPos++; |
|
|
} |
|
|
} |
|
|
prevCr = ptr[i] == '\r'; |
|
|
} else { |
|
|
pdoc->InsertString(currentPos, ptr + i, 1); |
|
|
currentPos++; |
|
|
prevCr = false; |
|
|
} |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
SetEmptySelection(pos); |
|
|
} |
|
|
|
|
|
bool Editor::CanPaste() { |
|
|
return !pdoc->IsReadOnly() && !SelectionContainsProtected(); |
|
|
} |
|
|
|
|
|
void Editor::Clear() { |
|
|
if (currentPos == anchor) { |
|
|
if (!RangeContainsProtected(currentPos, currentPos + 1)) { |
|
|
DelChar(); |
|
|
} |
|
|
} else { |
|
|
ClearSelection(); |
|
|
} |
|
|
SetEmptySelection(currentPos); |
|
|
} |
|
|
|
|
|
void Editor::SelectAll() { |
|
|
SetSelection(0, pdoc->Length()); |
|
|
Redraw(); |
|
|
} |
|
|
|
|
|
void Editor::Undo() { |
|
|
if (pdoc->CanUndo()) { |
|
|
InvalidateCaret(); |
|
|
int newPos = pdoc->Undo(); |
|
|
if (newPos >= 0) |
|
|
SetEmptySelection(newPos); |
|
|
EnsureCaretVisible(); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::Redo() { |
|
|
if (pdoc->CanRedo()) { |
|
|
int newPos = pdoc->Redo(); |
|
|
if (newPos >= 0) |
|
|
SetEmptySelection(newPos); |
|
|
EnsureCaretVisible(); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::DelChar() { |
|
|
if (!RangeContainsProtected(currentPos, currentPos + 1)) { |
|
|
pdoc->DelChar(currentPos); |
|
|
} |
|
|
// Avoid blinking during rapid typing: |
|
|
ShowCaretAtCurrentPosition(); |
|
|
} |
|
|
|
|
|
void Editor::DelCharBack(bool allowLineStartDeletion) { |
|
|
if (currentPos == anchor) { |
|
|
if (!RangeContainsProtected(currentPos - 1, currentPos)) { |
|
|
int lineCurrentPos = pdoc->LineFromPosition(currentPos); |
|
|
if (allowLineStartDeletion || (pdoc->LineStart(lineCurrentPos) != currentPos)) { |
|
|
if (pdoc->GetColumn(currentPos) <= pdoc->GetLineIndentation(lineCurrentPos) && |
|
|
pdoc->GetColumn(currentPos) > 0 && pdoc->backspaceUnindents) { |
|
|
pdoc->BeginUndoAction(); |
|
|
int indentation = pdoc->GetLineIndentation(lineCurrentPos); |
|
|
int indentationStep = pdoc->IndentSize(); |
|
|
if (indentation % indentationStep == 0) { |
|
|
pdoc->SetLineIndentation(lineCurrentPos, indentation - indentationStep); |
|
|
} else { |
|
|
pdoc->SetLineIndentation(lineCurrentPos, indentation - (indentation % indentationStep)); |
|
|
} |
|
|
SetEmptySelection(pdoc->GetLineIndentPosition(lineCurrentPos)); |
|
|
pdoc->EndUndoAction(); |
|
|
} else { |
|
|
pdoc->DelCharBack(currentPos); |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
ClearSelection(); |
|
|
SetEmptySelection(currentPos); |
|
|
} |
|
|
// Avoid blinking during rapid typing: |
|
|
ShowCaretAtCurrentPosition(); |
|
|
} |
|
|
|
|
|
void Editor::NotifyFocus(bool) {} |
|
|
|
|
|
void Editor::NotifyStyleToNeeded(int endStyleNeeded) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_STYLENEEDED; |
|
|
scn.position = endStyleNeeded; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyStyleNeeded(Document*, void *, int endStyleNeeded) { |
|
|
NotifyStyleToNeeded(endStyleNeeded); |
|
|
} |
|
|
|
|
|
void Editor::NotifyChar(int ch) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_CHARADDED; |
|
|
scn.ch = ch; |
|
|
NotifyParent(scn); |
|
|
if (recordingMacro) { |
|
|
char txt[2]; |
|
|
txt[0] = static_cast<char>(ch); |
|
|
txt[1] = '\0'; |
|
|
NotifyMacroRecord(SCI_REPLACESEL, 0, reinterpret_cast<sptr_t>(txt)); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::NotifySavePoint(bool isSavePoint) { |
|
|
SCNotification scn = {0}; |
|
|
if (isSavePoint) { |
|
|
scn.nmhdr.code = SCN_SAVEPOINTREACHED; |
|
|
} else { |
|
|
scn.nmhdr.code = SCN_SAVEPOINTLEFT; |
|
|
} |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyModifyAttempt() { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_MODIFYATTEMPTRO; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyDoubleClick(Point pt, bool) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_DOUBLECLICK; |
|
|
scn.line = LineFromLocation(pt); |
|
|
scn.position = PositionFromLocationClose(pt); |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyHotSpotDoubleClicked(int position, bool shift, bool ctrl, bool alt) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_HOTSPOTDOUBLECLICK; |
|
|
scn.position = position; |
|
|
scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | |
|
|
(alt ? SCI_ALT : 0); |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyHotSpotClicked(int position, bool shift, bool ctrl, bool alt) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_HOTSPOTCLICK; |
|
|
scn.position = position; |
|
|
scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | |
|
|
(alt ? SCI_ALT : 0); |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyUpdateUI() { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_UPDATEUI; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyPainted() { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_PAINTED; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
bool Editor::NotifyMarginClick(Point pt, bool shift, bool ctrl, bool alt) { |
|
|
int marginClicked = -1; |
|
|
int x = 0; |
|
|
for (int margin = 0; margin < ViewStyle::margins; margin++) { |
|
|
if ((pt.x > x) && (pt.x < x + vs.ms[margin].width)) |
|
|
marginClicked = margin; |
|
|
x += vs.ms[margin].width; |
|
|
} |
|
|
if ((marginClicked >= 0) && vs.ms[marginClicked].sensitive) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_MARGINCLICK; |
|
|
scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | |
|
|
(alt ? SCI_ALT : 0); |
|
|
scn.position = pdoc->LineStart(LineFromLocation(pt)); |
|
|
scn.margin = marginClicked; |
|
|
NotifyParent(scn); |
|
|
return true; |
|
|
} else { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::NotifyNeedShown(int pos, int len) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_NEEDSHOWN; |
|
|
scn.position = pos; |
|
|
scn.length = len; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyDwelling(Point pt, bool state) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = state ? SCN_DWELLSTART : SCN_DWELLEND; |
|
|
scn.position = PositionFromLocationClose(pt); |
|
|
scn.x = pt.x; |
|
|
scn.y = pt.y; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifyZoom() { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_ZOOM; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
// Notifications from document |
|
|
void Editor::NotifyModifyAttempt(Document*, void *) { |
|
|
//Platform::DebugPrintf("** Modify Attempt\n"); |
|
|
NotifyModifyAttempt(); |
|
|
} |
|
|
|
|
|
void Editor::NotifyMove(int position) { |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_POSCHANGED; |
|
|
scn.position = position; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
void Editor::NotifySavePoint(Document*, void *, bool atSavePoint) { |
|
|
//Platform::DebugPrintf("** Save Point %s\n", atSavePoint ? "On" : "Off"); |
|
|
NotifySavePoint(atSavePoint); |
|
|
} |
|
|
|
|
|
void Editor::CheckModificationForWrap(DocModification mh) { |
|
|
if (mh.modificationType & (SC_MOD_INSERTTEXT|SC_MOD_DELETETEXT)) { |
|
|
llc.Invalidate(LineLayout::llCheckTextAndStyle); |
|
|
if (wrapState != eWrapNone) { |
|
|
int lineDoc = pdoc->LineFromPosition(mh.position); |
|
|
int lines = Platform::Maximum(0, mh.linesAdded); |
|
|
NeedWrapping(lineDoc, lineDoc + lines + 1); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// Move a position so it is still after the same character as before the insertion. |
|
|
static inline int MovePositionForInsertion(int position, int startInsertion, int length) { |
|
|
if (position > startInsertion) { |
|
|
return position + length; |
|
|
} |
|
|
return position; |
|
|
} |
|
|
|
|
|
// Move a position so it is still after the same character as before the deletion if that |
|
|
// character is still present else after the previous surviving character. |
|
|
static inline int MovePositionForDeletion(int position, int startDeletion, int length) { |
|
|
if (position > startDeletion) { |
|
|
int endDeletion = startDeletion + length; |
|
|
if (position > endDeletion) { |
|
|
return position - length; |
|
|
} else { |
|
|
return startDeletion; |
|
|
} |
|
|
} else { |
|
|
return position; |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::NotifyModified(Document*, DocModification mh, void *) { |
|
|
needUpdateUI = true; |
|
|
if (paintState == painting) { |
|
|
CheckForChangeOutsidePaint(Range(mh.position, mh.position + mh.length)); |
|
|
} |
|
|
if (mh.modificationType & SC_MOD_CHANGESTYLE) { |
|
|
pdoc->IncrementStyleClock(); |
|
|
if (paintState == notPainting) { |
|
|
if (mh.position < pdoc->LineStart(topLine)) { |
|
|
// Styling performed before this view |
|
|
Redraw(); |
|
|
} else { |
|
|
InvalidateRange(mh.position, mh.position + mh.length); |
|
|
} |
|
|
} |
|
|
llc.Invalidate(LineLayout::llCheckTextAndStyle); |
|
|
} else { |
|
|
// Move selection and brace highlights |
|
|
if (mh.modificationType & SC_MOD_INSERTTEXT) { |
|
|
currentPos = MovePositionForInsertion(currentPos, mh.position, mh.length); |
|
|
anchor = MovePositionForInsertion(anchor, mh.position, mh.length); |
|
|
braces[0] = MovePositionForInsertion(braces[0], mh.position, mh.length); |
|
|
braces[1] = MovePositionForInsertion(braces[1], mh.position, mh.length); |
|
|
} else if (mh.modificationType & SC_MOD_DELETETEXT) { |
|
|
currentPos = MovePositionForDeletion(currentPos, mh.position, mh.length); |
|
|
anchor = MovePositionForDeletion(anchor, mh.position, mh.length); |
|
|
braces[0] = MovePositionForDeletion(braces[0], mh.position, mh.length); |
|
|
braces[1] = MovePositionForDeletion(braces[1], mh.position, mh.length); |
|
|
} |
|
|
if (cs.LinesDisplayed() < cs.LinesInDoc()) { |
|
|
// Some lines are hidden so may need shown. |
|
|
// TODO: check if the modified area is hidden. |
|
|
if (mh.modificationType & SC_MOD_BEFOREINSERT) { |
|
|
NotifyNeedShown(mh.position, 0); |
|
|
} else if (mh.modificationType & SC_MOD_BEFOREDELETE) { |
|
|
NotifyNeedShown(mh.position, mh.length); |
|
|
} |
|
|
} |
|
|
if (mh.linesAdded != 0) { |
|
|
// Update contraction state for inserted and removed lines |
|
|
// lineOfPos should be calculated in context of state before modification, shouldn't it |
|
|
int lineOfPos = pdoc->LineFromPosition(mh.position); |
|
|
if (mh.linesAdded > 0) { |
|
|
cs.InsertLines(lineOfPos, mh.linesAdded); |
|
|
} else { |
|
|
cs.DeleteLines(lineOfPos, -mh.linesAdded); |
|
|
} |
|
|
} |
|
|
CheckModificationForWrap(mh); |
|
|
if (mh.linesAdded != 0) { |
|
|
// Avoid scrolling of display if change before current display |
|
|
if (mh.position < posTopLine && !CanDeferToLastStep(mh)) { |
|
|
int newTop = Platform::Clamp(topLine + mh.linesAdded, 0, MaxScrollPos()); |
|
|
if (newTop != topLine) { |
|
|
SetTopLine(newTop); |
|
|
SetVerticalScrollPos(); |
|
|
} |
|
|
} |
|
|
|
|
|
//Platform::DebugPrintf("** %x Doc Changed\n", this); |
|
|
// TODO: could invalidate from mh.startModification to end of screen |
|
|
//InvalidateRange(mh.position, mh.position + mh.length); |
|
|
if (paintState == notPainting && !CanDeferToLastStep(mh)) { |
|
|
Redraw(); |
|
|
} |
|
|
} else { |
|
|
//Platform::DebugPrintf("** %x Line Changed %d .. %d\n", this, |
|
|
// mh.position, mh.position + mh.length); |
|
|
if (paintState == notPainting && mh.length && !CanEliminate(mh)) { |
|
|
InvalidateRange(mh.position, mh.position + mh.length); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
if (mh.linesAdded != 0 && !CanDeferToLastStep(mh)) { |
|
|
SetScrollBars(); |
|
|
} |
|
|
|
|
|
if (mh.modificationType & SC_MOD_CHANGEMARKER) { |
|
|
if ((paintState == notPainting) || !PaintContainsMargin()) { |
|
|
if (mh.modificationType & SC_MOD_CHANGEFOLD) { |
|
|
// Fold changes can affect the drawing of following lines so redraw whole margin |
|
|
RedrawSelMargin(); |
|
|
} else { |
|
|
RedrawSelMargin(mh.line); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
// NOW pay the piper WRT "deferred" visual updates |
|
|
if (IsLastStep(mh)) { |
|
|
SetScrollBars(); |
|
|
Redraw(); |
|
|
} |
|
|
|
|
|
// If client wants to see this modification |
|
|
if (mh.modificationType & modEventMask) { |
|
|
if ((mh.modificationType & SC_MOD_CHANGESTYLE) == 0) { |
|
|
// Real modification made to text of document. |
|
|
NotifyChange(); // Send EN_CHANGE |
|
|
} |
|
|
|
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_MODIFIED; |
|
|
scn.position = mh.position; |
|
|
scn.modificationType = mh.modificationType; |
|
|
scn.text = mh.text; |
|
|
scn.length = mh.length; |
|
|
scn.linesAdded = mh.linesAdded; |
|
|
scn.line = mh.line; |
|
|
scn.foldLevelNow = mh.foldLevelNow; |
|
|
scn.foldLevelPrev = mh.foldLevelPrev; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::NotifyDeleted(Document *, void *) { |
|
|
/* Do nothing */ |
|
|
} |
|
|
|
|
|
void Editor::NotifyMacroRecord(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { |
|
|
|
|
|
// Enumerates all macroable messages |
|
|
switch (iMessage) { |
|
|
case SCI_CUT: |
|
|
case SCI_COPY: |
|
|
case SCI_PASTE: |
|
|
case SCI_CLEAR: |
|
|
case SCI_REPLACESEL: |
|
|
case SCI_ADDTEXT: |
|
|
case SCI_INSERTTEXT: |
|
|
case SCI_APPENDTEXT: |
|
|
case SCI_CLEARALL: |
|
|
case SCI_SELECTALL: |
|
|
case SCI_GOTOLINE: |
|
|
case SCI_GOTOPOS: |
|
|
case SCI_SEARCHANCHOR: |
|
|
case SCI_SEARCHNEXT: |
|
|
case SCI_SEARCHPREV: |
|
|
case SCI_LINEDOWN: |
|
|
case SCI_LINEDOWNEXTEND: |
|
|
case SCI_PARADOWN: |
|
|
case SCI_PARADOWNEXTEND: |
|
|
case SCI_LINEUP: |
|
|
case SCI_LINEUPEXTEND: |
|
|
case SCI_PARAUP: |
|
|
case SCI_PARAUPEXTEND: |
|
|
case SCI_CHARLEFT: |
|
|
case SCI_CHARLEFTEXTEND: |
|
|
case SCI_CHARRIGHT: |
|
|
case SCI_CHARRIGHTEXTEND: |
|
|
case SCI_WORDLEFT: |
|
|
case SCI_WORDLEFTEXTEND: |
|
|
case SCI_WORDRIGHT: |
|
|
case SCI_WORDRIGHTEXTEND: |
|
|
case SCI_WORDPARTLEFT: |
|
|
case SCI_WORDPARTLEFTEXTEND: |
|
|
case SCI_WORDPARTRIGHT: |
|
|
case SCI_WORDPARTRIGHTEXTEND: |
|
|
case SCI_WORDLEFTEND: |
|
|
case SCI_WORDLEFTENDEXTEND: |
|
|
case SCI_WORDRIGHTEND: |
|
|
case SCI_WORDRIGHTENDEXTEND: |
|
|
case SCI_HOME: |
|
|
case SCI_HOMEEXTEND: |
|
|
case SCI_LINEEND: |
|
|
case SCI_LINEENDEXTEND: |
|
|
case SCI_HOMEWRAP: |
|
|
case SCI_HOMEWRAPEXTEND: |
|
|
case SCI_LINEENDWRAP: |
|
|
case SCI_LINEENDWRAPEXTEND: |
|
|
case SCI_DOCUMENTSTART: |
|
|
case SCI_DOCUMENTSTARTEXTEND: |
|
|
case SCI_DOCUMENTEND: |
|
|
case SCI_DOCUMENTENDEXTEND: |
|
|
case SCI_STUTTEREDPAGEUP: |
|
|
case SCI_STUTTEREDPAGEUPEXTEND: |
|
|
case SCI_STUTTEREDPAGEDOWN: |
|
|
case SCI_STUTTEREDPAGEDOWNEXTEND: |
|
|
case SCI_PAGEUP: |
|
|
case SCI_PAGEUPEXTEND: |
|
|
case SCI_PAGEDOWN: |
|
|
case SCI_PAGEDOWNEXTEND: |
|
|
case SCI_EDITTOGGLEOVERTYPE: |
|
|
case SCI_CANCEL: |
|
|
case SCI_DELETEBACK: |
|
|
case SCI_TAB: |
|
|
case SCI_BACKTAB: |
|
|
case SCI_FORMFEED: |
|
|
case SCI_VCHOME: |
|
|
case SCI_VCHOMEEXTEND: |
|
|
case SCI_VCHOMEWRAP: |
|
|
case SCI_VCHOMEWRAPEXTEND: |
|
|
case SCI_DELWORDLEFT: |
|
|
case SCI_DELWORDRIGHT: |
|
|
case SCI_DELLINELEFT: |
|
|
case SCI_DELLINERIGHT: |
|
|
case SCI_LINECOPY: |
|
|
case SCI_LINECUT: |
|
|
case SCI_LINEDELETE: |
|
|
case SCI_LINETRANSPOSE: |
|
|
case SCI_LINEDUPLICATE: |
|
|
case SCI_LOWERCASE: |
|
|
case SCI_UPPERCASE: |
|
|
case SCI_LINESCROLLDOWN: |
|
|
case SCI_LINESCROLLUP: |
|
|
case SCI_DELETEBACKNOTLINE: |
|
|
case SCI_HOMEDISPLAY: |
|
|
case SCI_HOMEDISPLAYEXTEND: |
|
|
case SCI_LINEENDDISPLAY: |
|
|
case SCI_LINEENDDISPLAYEXTEND: |
|
|
case SCI_SETSELECTIONMODE: |
|
|
case SCI_LINEDOWNRECTEXTEND: |
|
|
case SCI_LINEUPRECTEXTEND: |
|
|
case SCI_CHARLEFTRECTEXTEND: |
|
|
case SCI_CHARRIGHTRECTEXTEND: |
|
|
case SCI_HOMERECTEXTEND: |
|
|
case SCI_VCHOMERECTEXTEND: |
|
|
case SCI_LINEENDRECTEXTEND: |
|
|
case SCI_PAGEUPRECTEXTEND: |
|
|
case SCI_PAGEDOWNRECTEXTEND: |
|
|
case SCI_SELECTIONDUPLICATE: |
|
|
break; |
|
|
|
|
|
// Filter out all others like display changes. Also, newlines are redundant |
|
|
// with char insert messages. |
|
|
case SCI_NEWLINE: |
|
|
default: |
|
|
// printf("Filtered out %ld of macro recording\n", iMessage); |
|
|
return ; |
|
|
} |
|
|
|
|
|
// Send notification |
|
|
SCNotification scn = {0}; |
|
|
scn.nmhdr.code = SCN_MACRORECORD; |
|
|
scn.message = iMessage; |
|
|
scn.wParam = wParam; |
|
|
scn.lParam = lParam; |
|
|
NotifyParent(scn); |
|
|
} |
|
|
|
|
|
/** |
|
|
* Force scroll and keep position relative to top of window. |
|
|
* |
|
|
* If stuttered = true and not already at first/last row, move to first/last row of window. |
|
|
* If stuttered = true and already at first/last row, scroll as normal. |
|
|
*/ |
|
|
void Editor::PageMove(int direction, selTypes sel, bool stuttered) { |
|
|
int topLineNew, newPos; |
|
|
|
|
|
// I consider only the caretYSlop, and ignore the caretYPolicy-- is that a problem? |
|
|
int currentLine = pdoc->LineFromPosition(currentPos); |
|
|
int topStutterLine = topLine + caretYSlop; |
|
|
int bottomStutterLine = topLine + LinesToScroll() - caretYSlop; |
|
|
|
|
|
if (stuttered && (direction < 0 && currentLine > topStutterLine)) { |
|
|
topLineNew = topLine; |
|
|
newPos = PositionFromLocation(Point(lastXChosen, vs.lineHeight * caretYSlop)); |
|
|
|
|
|
} else if (stuttered && (direction > 0 && currentLine < bottomStutterLine)) { |
|
|
topLineNew = topLine; |
|
|
newPos = PositionFromLocation(Point(lastXChosen, vs.lineHeight * (LinesToScroll() - caretYSlop))); |
|
|
|
|
|
} else { |
|
|
Point pt = LocationFromPosition(currentPos); |
|
|
|
|
|
topLineNew = Platform::Clamp( |
|
|
topLine + direction * LinesToScroll(), 0, MaxScrollPos()); |
|
|
newPos = PositionFromLocation( |
|
|
Point(lastXChosen, pt.y + direction * (vs.lineHeight * LinesToScroll()))); |
|
|
} |
|
|
|
|
|
if (topLineNew != topLine) { |
|
|
SetTopLine(topLineNew); |
|
|
MovePositionTo(newPos, sel); |
|
|
Redraw(); |
|
|
SetVerticalScrollPos(); |
|
|
} else { |
|
|
MovePositionTo(newPos, sel); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::ChangeCaseOfSelection(bool makeUpperCase) { |
|
|
pdoc->BeginUndoAction(); |
|
|
int startCurrent = currentPos; |
|
|
int startAnchor = anchor; |
|
|
if (selType == selStream) { |
|
|
pdoc->ChangeCase(Range(SelectionStart(), SelectionEnd()), |
|
|
makeUpperCase); |
|
|
SetSelection(startCurrent, startAnchor); |
|
|
} else { |
|
|
SelectionLineIterator lineIterator(this, false); |
|
|
while (lineIterator.Iterate()) { |
|
|
pdoc->ChangeCase( |
|
|
Range(lineIterator.startPos, lineIterator.endPos), |
|
|
makeUpperCase); |
|
|
} |
|
|
// Would be nicer to keep the rectangular selection but this is complex |
|
|
SetEmptySelection(startCurrent); |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
} |
|
|
|
|
|
void Editor::LineTranspose() { |
|
|
int line = pdoc->LineFromPosition(currentPos); |
|
|
if (line > 0) { |
|
|
int startPrev = pdoc->LineStart(line - 1); |
|
|
int endPrev = pdoc->LineEnd(line - 1); |
|
|
int start = pdoc->LineStart(line); |
|
|
int end = pdoc->LineEnd(line); |
|
|
int startNext = pdoc->LineStart(line + 1); |
|
|
if (end < pdoc->Length()) { |
|
|
end = startNext; |
|
|
char *thisLine = CopyRange(start, end); |
|
|
pdoc->DeleteChars(start, end - start); |
|
|
if (pdoc->InsertString(startPrev, thisLine, end - start)) { |
|
|
MovePositionTo(startPrev + end - start); |
|
|
} |
|
|
delete []thisLine; |
|
|
} else { |
|
|
// Last line so line has no line end |
|
|
char *thisLine = CopyRange(start, end); |
|
|
char *prevEnd = CopyRange(endPrev, start); |
|
|
pdoc->DeleteChars(endPrev, end - endPrev); |
|
|
pdoc->InsertString(startPrev, thisLine, end - start); |
|
|
if (pdoc->InsertString(startPrev + end - start, prevEnd, start - endPrev)) { |
|
|
MovePositionTo(startPrev + end - endPrev); |
|
|
} |
|
|
delete []thisLine; |
|
|
delete []prevEnd; |
|
|
} |
|
|
|
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::Duplicate(bool forLine) { |
|
|
int start = SelectionStart(); |
|
|
int end = SelectionEnd(); |
|
|
if (start == end) { |
|
|
forLine = true; |
|
|
} |
|
|
if (forLine) { |
|
|
int line = pdoc->LineFromPosition(currentPos); |
|
|
start = pdoc->LineStart(line); |
|
|
end = pdoc->LineEnd(line); |
|
|
} |
|
|
char *text = CopyRange(start, end); |
|
|
if (forLine) { |
|
|
const char *eol = StringFromEOLMode(pdoc->eolMode); |
|
|
pdoc->InsertString(end, eol); |
|
|
pdoc->InsertString(end + istrlen(eol), text, end - start); |
|
|
} else { |
|
|
pdoc->InsertString(end, text, end - start); |
|
|
} |
|
|
delete []text; |
|
|
} |
|
|
|
|
|
void Editor::CancelModes() { |
|
|
moveExtendsSelection = false; |
|
|
} |
|
|
|
|
|
void Editor::NewLine() { |
|
|
ClearSelection(); |
|
|
const char *eol = "\n"; |
|
|
if (pdoc->eolMode == SC_EOL_CRLF) { |
|
|
eol = "\r\n"; |
|
|
} else if (pdoc->eolMode == SC_EOL_CR) { |
|
|
eol = "\r"; |
|
|
} // else SC_EOL_LF -> "\n" already set |
|
|
if (pdoc->InsertString(currentPos, eol)) { |
|
|
SetEmptySelection(currentPos + istrlen(eol)); |
|
|
while (*eol) { |
|
|
NotifyChar(*eol); |
|
|
eol++; |
|
|
} |
|
|
} |
|
|
SetLastXChosen(); |
|
|
EnsureCaretVisible(); |
|
|
// Avoid blinking during rapid typing: |
|
|
ShowCaretAtCurrentPosition(); |
|
|
} |
|
|
|
|
|
void Editor::CursorUpOrDown(int direction, selTypes sel) { |
|
|
Point pt = LocationFromPosition(currentPos); |
|
|
int posNew = PositionFromLocation( |
|
|
Point(lastXChosen, pt.y + direction * vs.lineHeight)); |
|
|
if (direction < 0) { |
|
|
// Line wrapping may lead to a location on the same line, so |
|
|
// seek back if that is the case. |
|
|
// There is an equivalent case when moving down which skips |
|
|
// over a line but as that does not trap the user it is fine. |
|
|
Point ptNew = LocationFromPosition(posNew); |
|
|
while ((posNew > 0) && (pt.y == ptNew.y)) { |
|
|
posNew--; |
|
|
ptNew = LocationFromPosition(posNew); |
|
|
} |
|
|
} |
|
|
MovePositionTo(posNew, sel); |
|
|
} |
|
|
|
|
|
void Editor::ParaUpOrDown(int direction, selTypes sel) { |
|
|
int lineDoc, savedPos = currentPos; |
|
|
do { |
|
|
MovePositionTo(direction > 0 ? pdoc->ParaDown(currentPos) : pdoc->ParaUp(currentPos), sel); |
|
|
lineDoc = pdoc->LineFromPosition(currentPos); |
|
|
if (direction > 0) { |
|
|
if (currentPos >= pdoc->Length() && !cs.GetVisible(lineDoc)) { |
|
|
if (sel == noSel) { |
|
|
MovePositionTo(pdoc->LineEndPosition(savedPos)); |
|
|
} |
|
|
break; |
|
|
} |
|
|
} |
|
|
} while (!cs.GetVisible(lineDoc)); |
|
|
} |
|
|
|
|
|
int Editor::StartEndDisplayLine(int pos, bool start) { |
|
|
RefreshStyleData(); |
|
|
int line = pdoc->LineFromPosition(pos); |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(line)); |
|
|
int posRet = INVALID_POSITION; |
|
|
if (surface && ll) { |
|
|
unsigned int posLineStart = pdoc->LineStart(line); |
|
|
LayoutLine(line, surface, vs, ll, wrapWidth); |
|
|
int posInLine = pos - posLineStart; |
|
|
if (posInLine <= ll->maxLineLength) { |
|
|
for (int subLine = 0; subLine < ll->lines; subLine++) { |
|
|
if ((posInLine >= ll->LineStart(subLine)) && (posInLine <= ll->LineStart(subLine + 1))) { |
|
|
if (start) { |
|
|
posRet = ll->LineStart(subLine) + posLineStart; |
|
|
} else { |
|
|
if (subLine == ll->lines - 1) |
|
|
posRet = ll->LineStart(subLine + 1) + posLineStart; |
|
|
else |
|
|
posRet = ll->LineStart(subLine + 1) + posLineStart - 1; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
if (posRet == INVALID_POSITION) { |
|
|
return pos; |
|
|
} else { |
|
|
return posRet; |
|
|
} |
|
|
} |
|
|
|
|
|
int Editor::KeyCommand(unsigned int iMessage) { |
|
|
switch (iMessage) { |
|
|
case SCI_LINEDOWN: |
|
|
CursorUpOrDown(1); |
|
|
break; |
|
|
case SCI_LINEDOWNEXTEND: |
|
|
CursorUpOrDown(1, selStream); |
|
|
break; |
|
|
case SCI_LINEDOWNRECTEXTEND: |
|
|
CursorUpOrDown(1, selRectangle); |
|
|
break; |
|
|
case SCI_PARADOWN: |
|
|
ParaUpOrDown(1); |
|
|
break; |
|
|
case SCI_PARADOWNEXTEND: |
|
|
ParaUpOrDown(1, selStream); |
|
|
break; |
|
|
case SCI_LINESCROLLDOWN: |
|
|
ScrollTo(topLine + 1); |
|
|
MoveCaretInsideView(false); |
|
|
break; |
|
|
case SCI_LINEUP: |
|
|
CursorUpOrDown(-1); |
|
|
break; |
|
|
case SCI_LINEUPEXTEND: |
|
|
CursorUpOrDown(-1, selStream); |
|
|
break; |
|
|
case SCI_LINEUPRECTEXTEND: |
|
|
CursorUpOrDown(-1, selRectangle); |
|
|
break; |
|
|
case SCI_PARAUP: |
|
|
ParaUpOrDown(-1); |
|
|
break; |
|
|
case SCI_PARAUPEXTEND: |
|
|
ParaUpOrDown(-1, selStream); |
|
|
break; |
|
|
case SCI_LINESCROLLUP: |
|
|
ScrollTo(topLine - 1); |
|
|
MoveCaretInsideView(false); |
|
|
break; |
|
|
case SCI_CHARLEFT: |
|
|
if (SelectionEmpty() || moveExtendsSelection) { |
|
|
MovePositionTo(MovePositionSoVisible(currentPos - 1, -1)); |
|
|
} else { |
|
|
MovePositionTo(SelectionStart()); |
|
|
} |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_CHARLEFTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(currentPos - 1, -1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_CHARLEFTRECTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(currentPos - 1, -1), selRectangle); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_CHARRIGHT: |
|
|
if (SelectionEmpty() || moveExtendsSelection) { |
|
|
MovePositionTo(MovePositionSoVisible(currentPos + 1, 1)); |
|
|
} else { |
|
|
MovePositionTo(SelectionEnd()); |
|
|
} |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_CHARRIGHTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(currentPos + 1, 1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_CHARRIGHTRECTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(currentPos + 1, 1), selRectangle); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDLEFT: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, -1), -1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDLEFTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, -1), -1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDRIGHT: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, 1), 1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDRIGHTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, 1), 1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
|
|
|
case SCI_WORDLEFTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, -1), -1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDLEFTENDEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, -1), -1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDRIGHTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, 1), 1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDRIGHTENDEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->NextWordEnd(currentPos, 1), 1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
|
|
|
case SCI_HOME: |
|
|
MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos))); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_HOMEEXTEND: |
|
|
MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos)), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_HOMERECTEXTEND: |
|
|
MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos)), selRectangle); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_LINEEND: |
|
|
MovePositionTo(pdoc->LineEndPosition(currentPos)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_LINEENDEXTEND: |
|
|
MovePositionTo(pdoc->LineEndPosition(currentPos), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_LINEENDRECTEXTEND: |
|
|
MovePositionTo(pdoc->LineEndPosition(currentPos), selRectangle); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_HOMEWRAP: { |
|
|
int homePos = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); |
|
|
if (currentPos <= homePos) |
|
|
homePos = pdoc->LineStart(pdoc->LineFromPosition(currentPos)); |
|
|
MovePositionTo(homePos); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_HOMEWRAPEXTEND: { |
|
|
int homePos = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); |
|
|
if (currentPos <= homePos) |
|
|
homePos = pdoc->LineStart(pdoc->LineFromPosition(currentPos)); |
|
|
MovePositionTo(homePos, selStream); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_LINEENDWRAP: { |
|
|
int endPos = MovePositionSoVisible(StartEndDisplayLine(currentPos, false), 1); |
|
|
int realEndPos = pdoc->LineEndPosition(currentPos); |
|
|
if (endPos > realEndPos // if moved past visible EOLs |
|
|
|| currentPos >= endPos) // if at end of display line already |
|
|
endPos = realEndPos; |
|
|
MovePositionTo(endPos); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_LINEENDWRAPEXTEND: { |
|
|
int endPos = MovePositionSoVisible(StartEndDisplayLine(currentPos, false), 1); |
|
|
int realEndPos = pdoc->LineEndPosition(currentPos); |
|
|
if (endPos > realEndPos // if moved past visible EOLs |
|
|
|| currentPos >= endPos) // if at end of display line already |
|
|
endPos = realEndPos; |
|
|
MovePositionTo(endPos, selStream); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_DOCUMENTSTART: |
|
|
MovePositionTo(0); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_DOCUMENTSTARTEXTEND: |
|
|
MovePositionTo(0, selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_DOCUMENTEND: |
|
|
MovePositionTo(pdoc->Length()); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_DOCUMENTENDEXTEND: |
|
|
MovePositionTo(pdoc->Length(), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_STUTTEREDPAGEUP: |
|
|
PageMove(-1, noSel, true); |
|
|
break; |
|
|
case SCI_STUTTEREDPAGEUPEXTEND: |
|
|
PageMove(-1, selStream, true); |
|
|
break; |
|
|
case SCI_STUTTEREDPAGEDOWN: |
|
|
PageMove(1, noSel, true); |
|
|
break; |
|
|
case SCI_STUTTEREDPAGEDOWNEXTEND: |
|
|
PageMove(1, selStream, true); |
|
|
break; |
|
|
case SCI_PAGEUP: |
|
|
PageMove(-1); |
|
|
break; |
|
|
case SCI_PAGEUPEXTEND: |
|
|
PageMove(-1, selStream); |
|
|
break; |
|
|
case SCI_PAGEUPRECTEXTEND: |
|
|
PageMove(-1, selRectangle); |
|
|
break; |
|
|
case SCI_PAGEDOWN: |
|
|
PageMove(1); |
|
|
break; |
|
|
case SCI_PAGEDOWNEXTEND: |
|
|
PageMove(1, selStream); |
|
|
break; |
|
|
case SCI_PAGEDOWNRECTEXTEND: |
|
|
PageMove(1, selRectangle); |
|
|
break; |
|
|
case SCI_EDITTOGGLEOVERTYPE: |
|
|
inOverstrike = !inOverstrike; |
|
|
DropCaret(); |
|
|
ShowCaretAtCurrentPosition(); |
|
|
NotifyUpdateUI(); |
|
|
break; |
|
|
case SCI_CANCEL: // Cancel any modes - handled in subclass |
|
|
// Also unselect text |
|
|
CancelModes(); |
|
|
break; |
|
|
case SCI_DELETEBACK: |
|
|
DelCharBack(true); |
|
|
if (!caretSticky) { |
|
|
SetLastXChosen(); |
|
|
} |
|
|
EnsureCaretVisible(); |
|
|
break; |
|
|
case SCI_DELETEBACKNOTLINE: |
|
|
DelCharBack(false); |
|
|
if (!caretSticky) { |
|
|
SetLastXChosen(); |
|
|
} |
|
|
EnsureCaretVisible(); |
|
|
break; |
|
|
case SCI_TAB: |
|
|
Indent(true); |
|
|
if (!caretSticky) { |
|
|
SetLastXChosen(); |
|
|
} |
|
|
EnsureCaretVisible(); |
|
|
break; |
|
|
case SCI_BACKTAB: |
|
|
Indent(false); |
|
|
if (!caretSticky) { |
|
|
SetLastXChosen(); |
|
|
} |
|
|
EnsureCaretVisible(); |
|
|
break; |
|
|
case SCI_NEWLINE: |
|
|
NewLine(); |
|
|
break; |
|
|
case SCI_FORMFEED: |
|
|
AddChar('\f'); |
|
|
break; |
|
|
case SCI_VCHOME: |
|
|
MovePositionTo(pdoc->VCHomePosition(currentPos)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_VCHOMEEXTEND: |
|
|
MovePositionTo(pdoc->VCHomePosition(currentPos), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_VCHOMERECTEXTEND: |
|
|
MovePositionTo(pdoc->VCHomePosition(currentPos), selRectangle); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_VCHOMEWRAP: { |
|
|
int homePos = pdoc->VCHomePosition(currentPos); |
|
|
int viewLineStart = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); |
|
|
if ((viewLineStart < currentPos) && (viewLineStart > homePos)) |
|
|
homePos = viewLineStart; |
|
|
|
|
|
MovePositionTo(homePos); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_VCHOMEWRAPEXTEND: { |
|
|
int homePos = pdoc->VCHomePosition(currentPos); |
|
|
int viewLineStart = MovePositionSoVisible(StartEndDisplayLine(currentPos, true), -1); |
|
|
if ((viewLineStart < currentPos) && (viewLineStart > homePos)) |
|
|
homePos = viewLineStart; |
|
|
|
|
|
MovePositionTo(homePos, selStream); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_ZOOMIN: |
|
|
if (vs.zoomLevel < 20) { |
|
|
vs.zoomLevel++; |
|
|
InvalidateStyleRedraw(); |
|
|
NotifyZoom(); |
|
|
} |
|
|
break; |
|
|
case SCI_ZOOMOUT: |
|
|
if (vs.zoomLevel > -10) { |
|
|
vs.zoomLevel--; |
|
|
InvalidateStyleRedraw(); |
|
|
NotifyZoom(); |
|
|
} |
|
|
break; |
|
|
case SCI_DELWORDLEFT: { |
|
|
int startWord = pdoc->NextWordStart(currentPos, -1); |
|
|
pdoc->DeleteChars(startWord, currentPos - startWord); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_DELWORDRIGHT: { |
|
|
int endWord = pdoc->NextWordStart(currentPos, 1); |
|
|
pdoc->DeleteChars(currentPos, endWord - currentPos); |
|
|
} |
|
|
break; |
|
|
case SCI_DELLINELEFT: { |
|
|
int line = pdoc->LineFromPosition(currentPos); |
|
|
int start = pdoc->LineStart(line); |
|
|
pdoc->DeleteChars(start, currentPos - start); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_DELLINERIGHT: { |
|
|
int line = pdoc->LineFromPosition(currentPos); |
|
|
int end = pdoc->LineEnd(line); |
|
|
pdoc->DeleteChars(currentPos, end - currentPos); |
|
|
} |
|
|
break; |
|
|
case SCI_LINECOPY: { |
|
|
int lineStart = pdoc->LineFromPosition(SelectionStart()); |
|
|
int lineEnd = pdoc->LineFromPosition(SelectionEnd()); |
|
|
CopyRangeToClipboard(pdoc->LineStart(lineStart), |
|
|
pdoc->LineStart(lineEnd + 1)); |
|
|
} |
|
|
break; |
|
|
case SCI_LINECUT: { |
|
|
int lineStart = pdoc->LineFromPosition(SelectionStart()); |
|
|
int lineEnd = pdoc->LineFromPosition(SelectionEnd()); |
|
|
int start = pdoc->LineStart(lineStart); |
|
|
int end = pdoc->LineStart(lineEnd + 1); |
|
|
SetSelection(start, end); |
|
|
Cut(); |
|
|
SetLastXChosen(); |
|
|
} |
|
|
break; |
|
|
case SCI_LINEDELETE: { |
|
|
int line = pdoc->LineFromPosition(currentPos); |
|
|
int start = pdoc->LineStart(line); |
|
|
int end = pdoc->LineStart(line + 1); |
|
|
pdoc->DeleteChars(start, end - start); |
|
|
} |
|
|
break; |
|
|
case SCI_LINETRANSPOSE: |
|
|
LineTranspose(); |
|
|
break; |
|
|
case SCI_LINEDUPLICATE: |
|
|
Duplicate(true); |
|
|
break; |
|
|
case SCI_SELECTIONDUPLICATE: |
|
|
Duplicate(false); |
|
|
break; |
|
|
case SCI_LOWERCASE: |
|
|
ChangeCaseOfSelection(false); |
|
|
break; |
|
|
case SCI_UPPERCASE: |
|
|
ChangeCaseOfSelection(true); |
|
|
break; |
|
|
case SCI_WORDPARTLEFT: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->WordPartLeft(currentPos), -1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDPARTLEFTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->WordPartLeft(currentPos), -1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDPARTRIGHT: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->WordPartRight(currentPos), 1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_WORDPARTRIGHTEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible(pdoc->WordPartRight(currentPos), 1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_HOMEDISPLAY: |
|
|
MovePositionTo(MovePositionSoVisible( |
|
|
StartEndDisplayLine(currentPos, true), -1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_HOMEDISPLAYEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible( |
|
|
StartEndDisplayLine(currentPos, true), -1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_LINEENDDISPLAY: |
|
|
MovePositionTo(MovePositionSoVisible( |
|
|
StartEndDisplayLine(currentPos, false), 1)); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
case SCI_LINEENDDISPLAYEXTEND: |
|
|
MovePositionTo(MovePositionSoVisible( |
|
|
StartEndDisplayLine(currentPos, false), 1), selStream); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
} |
|
|
return 0; |
|
|
} |
|
|
|
|
|
int Editor::KeyDefault(int, int) { |
|
|
return 0; |
|
|
} |
|
|
|
|
|
int Editor::KeyDown(int key, bool shift, bool ctrl, bool alt, bool *consumed) { |
|
|
DwellEnd(false); |
|
|
int modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | |
|
|
(alt ? SCI_ALT : 0); |
|
|
int msg = kmap.Find(key, modifiers); |
|
|
if (msg) { |
|
|
if (consumed) |
|
|
*consumed = true; |
|
|
return WndProc(msg, 0, 0); |
|
|
} else { |
|
|
if (consumed) |
|
|
*consumed = false; |
|
|
return KeyDefault(key, modifiers); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::SetWhitespaceVisible(int view) { |
|
|
vs.viewWhitespace = static_cast<WhiteSpaceVisibility>(view); |
|
|
} |
|
|
|
|
|
int Editor::GetWhitespaceVisible() { |
|
|
return vs.viewWhitespace; |
|
|
} |
|
|
|
|
|
void Editor::Indent(bool forwards) { |
|
|
//Platform::DebugPrintf("INdent %d\n", forwards); |
|
|
int lineOfAnchor = pdoc->LineFromPosition(anchor); |
|
|
int lineCurrentPos = pdoc->LineFromPosition(currentPos); |
|
|
if (lineOfAnchor == lineCurrentPos) { |
|
|
if (forwards) { |
|
|
pdoc->BeginUndoAction(); |
|
|
ClearSelection(); |
|
|
if (pdoc->GetColumn(currentPos) <= pdoc->GetColumn(pdoc->GetLineIndentPosition(lineCurrentPos)) && |
|
|
pdoc->tabIndents) { |
|
|
int indentation = pdoc->GetLineIndentation(lineCurrentPos); |
|
|
int indentationStep = pdoc->IndentSize(); |
|
|
pdoc->SetLineIndentation(lineCurrentPos, indentation + indentationStep - indentation % indentationStep); |
|
|
SetEmptySelection(pdoc->GetLineIndentPosition(lineCurrentPos)); |
|
|
} else { |
|
|
if (pdoc->useTabs) { |
|
|
pdoc->InsertChar(currentPos, '\t'); |
|
|
SetEmptySelection(currentPos + 1); |
|
|
} else { |
|
|
int numSpaces = (pdoc->tabInChars) - |
|
|
(pdoc->GetColumn(currentPos) % (pdoc->tabInChars)); |
|
|
if (numSpaces < 1) |
|
|
numSpaces = pdoc->tabInChars; |
|
|
for (int i = 0; i < numSpaces; i++) { |
|
|
pdoc->InsertChar(currentPos + i, ' '); |
|
|
} |
|
|
SetEmptySelection(currentPos + numSpaces); |
|
|
} |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
} else { |
|
|
if (pdoc->GetColumn(currentPos) <= pdoc->GetLineIndentation(lineCurrentPos) && |
|
|
pdoc->tabIndents) { |
|
|
pdoc->BeginUndoAction(); |
|
|
int indentation = pdoc->GetLineIndentation(lineCurrentPos); |
|
|
int indentationStep = pdoc->IndentSize(); |
|
|
pdoc->SetLineIndentation(lineCurrentPos, indentation - indentationStep); |
|
|
SetEmptySelection(pdoc->GetLineIndentPosition(lineCurrentPos)); |
|
|
pdoc->EndUndoAction(); |
|
|
} else { |
|
|
int newColumn = ((pdoc->GetColumn(currentPos) - 1) / pdoc->tabInChars) * |
|
|
pdoc->tabInChars; |
|
|
if (newColumn < 0) |
|
|
newColumn = 0; |
|
|
int newPos = currentPos; |
|
|
while (pdoc->GetColumn(newPos) > newColumn) |
|
|
newPos--; |
|
|
SetEmptySelection(newPos); |
|
|
} |
|
|
} |
|
|
} else { |
|
|
int anchorPosOnLine = anchor - pdoc->LineStart(lineOfAnchor); |
|
|
int currentPosPosOnLine = currentPos - pdoc->LineStart(lineCurrentPos); |
|
|
// Multiple lines selected so indent / dedent |
|
|
int lineTopSel = Platform::Minimum(lineOfAnchor, lineCurrentPos); |
|
|
int lineBottomSel = Platform::Maximum(lineOfAnchor, lineCurrentPos); |
|
|
if (pdoc->LineStart(lineBottomSel) == anchor || pdoc->LineStart(lineBottomSel) == currentPos) |
|
|
lineBottomSel--; // If not selecting any characters on a line, do not indent |
|
|
pdoc->BeginUndoAction(); |
|
|
pdoc->Indent(forwards, lineBottomSel, lineTopSel); |
|
|
pdoc->EndUndoAction(); |
|
|
if (lineOfAnchor < lineCurrentPos) { |
|
|
if (currentPosPosOnLine == 0) |
|
|
SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor)); |
|
|
else |
|
|
SetSelection(pdoc->LineStart(lineCurrentPos + 1), pdoc->LineStart(lineOfAnchor)); |
|
|
} else { |
|
|
if (anchorPosOnLine == 0) |
|
|
SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor)); |
|
|
else |
|
|
SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor + 1)); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
* Search of a text in the document, in the given range. |
|
|
* @return The position of the found text, -1 if not found. |
|
|
*/ |
|
|
long Editor::FindText( |
|
|
uptr_t wParam, ///< Search modes : @c SCFIND_MATCHCASE, @c SCFIND_WHOLEWORD, |
|
|
///< @c SCFIND_WORDSTART, @c SCFIND_REGEXP or @c SCFIND_POSIX. |
|
|
sptr_t lParam) { ///< @c TextToFind structure: The text to search for in the given range. |
|
|
|
|
|
TextToFind *ft = reinterpret_cast<TextToFind *>(lParam); |
|
|
int lengthFound = istrlen(ft->lpstrText); |
|
|
int pos = pdoc->FindText(ft->chrg.cpMin, ft->chrg.cpMax, ft->lpstrText, |
|
|
(wParam & SCFIND_MATCHCASE) != 0, |
|
|
(wParam & SCFIND_WHOLEWORD) != 0, |
|
|
(wParam & SCFIND_WORDSTART) != 0, |
|
|
(wParam & SCFIND_REGEXP) != 0, |
|
|
(wParam & SCFIND_POSIX) != 0, |
|
|
&lengthFound); |
|
|
if (pos != -1) { |
|
|
ft->chrgText.cpMin = pos; |
|
|
ft->chrgText.cpMax = pos + lengthFound; |
|
|
} |
|
|
return pos; |
|
|
} |
|
|
|
|
|
/** |
|
|
* Relocatable search support : Searches relative to current selection |
|
|
* point and sets the selection to the found text range with |
|
|
* each search. |
|
|
*/ |
|
|
/** |
|
|
* Anchor following searches at current selection start: This allows |
|
|
* multiple incremental interactive searches to be macro recorded |
|
|
* while still setting the selection to found text so the find/select |
|
|
* operation is self-contained. |
|
|
*/ |
|
|
void Editor::SearchAnchor() { |
|
|
searchAnchor = SelectionStart(); |
|
|
} |
|
|
|
|
|
/** |
|
|
* Find text from current search anchor: Must call @c SearchAnchor first. |
|
|
* Used for next text and previous text requests. |
|
|
* @return The position of the found text, -1 if not found. |
|
|
*/ |
|
|
long Editor::SearchText( |
|
|
unsigned int iMessage, ///< Accepts both @c SCI_SEARCHNEXT and @c SCI_SEARCHPREV. |
|
|
uptr_t wParam, ///< Search modes : @c SCFIND_MATCHCASE, @c SCFIND_WHOLEWORD, |
|
|
///< @c SCFIND_WORDSTART, @c SCFIND_REGEXP or @c SCFIND_POSIX. |
|
|
sptr_t lParam) { ///< The text to search for. |
|
|
|
|
|
const char *txt = reinterpret_cast<char *>(lParam); |
|
|
int pos; |
|
|
int lengthFound = istrlen(txt); |
|
|
if (iMessage == SCI_SEARCHNEXT) { |
|
|
pos = pdoc->FindText(searchAnchor, pdoc->Length(), txt, |
|
|
(wParam & SCFIND_MATCHCASE) != 0, |
|
|
(wParam & SCFIND_WHOLEWORD) != 0, |
|
|
(wParam & SCFIND_WORDSTART) != 0, |
|
|
(wParam & SCFIND_REGEXP) != 0, |
|
|
(wParam & SCFIND_POSIX) != 0, |
|
|
&lengthFound); |
|
|
} else { |
|
|
pos = pdoc->FindText(searchAnchor, 0, txt, |
|
|
(wParam & SCFIND_MATCHCASE) != 0, |
|
|
(wParam & SCFIND_WHOLEWORD) != 0, |
|
|
(wParam & SCFIND_WORDSTART) != 0, |
|
|
(wParam & SCFIND_REGEXP) != 0, |
|
|
(wParam & SCFIND_POSIX) != 0, |
|
|
&lengthFound); |
|
|
} |
|
|
|
|
|
if (pos != -1) { |
|
|
SetSelection(pos, pos + lengthFound); |
|
|
} |
|
|
|
|
|
return pos; |
|
|
} |
|
|
|
|
|
/** |
|
|
* Search for text in the target range of the document. |
|
|
* @return The position of the found text, -1 if not found. |
|
|
*/ |
|
|
long Editor::SearchInTarget(const char *text, int length) { |
|
|
int lengthFound = length; |
|
|
int pos = pdoc->FindText(targetStart, targetEnd, text, |
|
|
(searchFlags & SCFIND_MATCHCASE) != 0, |
|
|
(searchFlags & SCFIND_WHOLEWORD) != 0, |
|
|
(searchFlags & SCFIND_WORDSTART) != 0, |
|
|
(searchFlags & SCFIND_REGEXP) != 0, |
|
|
(searchFlags & SCFIND_POSIX) != 0, |
|
|
&lengthFound); |
|
|
if (pos != -1) { |
|
|
targetStart = pos; |
|
|
targetEnd = pos + lengthFound; |
|
|
} |
|
|
return pos; |
|
|
} |
|
|
|
|
|
void Editor::GoToLine(int lineNo) { |
|
|
if (lineNo > pdoc->LinesTotal()) |
|
|
lineNo = pdoc->LinesTotal(); |
|
|
if (lineNo < 0) |
|
|
lineNo = 0; |
|
|
SetEmptySelection(pdoc->LineStart(lineNo)); |
|
|
ShowCaretAtCurrentPosition(); |
|
|
EnsureCaretVisible(); |
|
|
} |
|
|
|
|
|
static bool Close(Point pt1, Point pt2) { |
|
|
if (abs(pt1.x - pt2.x) > 3) |
|
|
return false; |
|
|
if (abs(pt1.y - pt2.y) > 3) |
|
|
return false; |
|
|
return true; |
|
|
} |
|
|
|
|
|
char *Editor::CopyRange(int start, int end) { |
|
|
char *text = 0; |
|
|
if (start < end) { |
|
|
int len = end - start; |
|
|
text = new char[len + 1]; |
|
|
if (text) { |
|
|
for (int i = 0; i < len; i++) { |
|
|
text[i] = pdoc->CharAt(start + i); |
|
|
} |
|
|
text[len] = '\0'; |
|
|
} |
|
|
} |
|
|
return text; |
|
|
} |
|
|
|
|
|
void Editor::CopySelectionFromRange(SelectionText *ss, int start, int end) { |
|
|
ss->Set(CopyRange(start, end), end - start + 1, |
|
|
pdoc->dbcsCodePage, vs.styles[STYLE_DEFAULT].characterSet, false); |
|
|
} |
|
|
|
|
|
void Editor::CopySelectionRange(SelectionText *ss) { |
|
|
if (selType == selStream) { |
|
|
CopySelectionFromRange(ss, SelectionStart(), SelectionEnd()); |
|
|
} else { |
|
|
char *text = 0; |
|
|
int size = 0; |
|
|
SelectionLineIterator lineIterator(this); |
|
|
while (lineIterator.Iterate()) { |
|
|
size += lineIterator.endPos - lineIterator.startPos; |
|
|
if (selType != selLines) { |
|
|
size++; |
|
|
if (pdoc->eolMode == SC_EOL_CRLF) { |
|
|
size++; |
|
|
} |
|
|
} |
|
|
} |
|
|
if (size > 0) { |
|
|
text = new char[size + 1]; |
|
|
if (text) { |
|
|
int j = 0; |
|
|
lineIterator.Reset(); |
|
|
while (lineIterator.Iterate()) { |
|
|
for (int i = lineIterator.startPos; |
|
|
i < lineIterator.endPos; |
|
|
i++) { |
|
|
text[j++] = pdoc->CharAt(i); |
|
|
} |
|
|
if (selType != selLines) { |
|
|
if (pdoc->eolMode != SC_EOL_LF) { |
|
|
text[j++] = '\r'; |
|
|
} |
|
|
if (pdoc->eolMode != SC_EOL_CR) { |
|
|
text[j++] = '\n'; |
|
|
} |
|
|
} |
|
|
} |
|
|
text[size] = '\0'; |
|
|
} |
|
|
} |
|
|
ss->Set(text, size + 1, pdoc->dbcsCodePage, |
|
|
vs.styles[STYLE_DEFAULT].characterSet, selType == selRectangle); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::CopyRangeToClipboard(int start, int end) { |
|
|
start = pdoc->ClampPositionIntoDocument(start); |
|
|
end = pdoc->ClampPositionIntoDocument(end); |
|
|
SelectionText selectedText; |
|
|
selectedText.Set(CopyRange(start, end), end - start + 1, |
|
|
pdoc->dbcsCodePage, vs.styles[STYLE_DEFAULT].characterSet, false); |
|
|
CopyToClipboard(selectedText); |
|
|
} |
|
|
|
|
|
void Editor::CopyText(int length, const char *text) { |
|
|
SelectionText selectedText; |
|
|
selectedText.Copy(text, length + 1, |
|
|
pdoc->dbcsCodePage, vs.styles[STYLE_DEFAULT].characterSet, false); |
|
|
CopyToClipboard(selectedText); |
|
|
} |
|
|
|
|
|
void Editor::SetDragPosition(int newPos) { |
|
|
if (newPos >= 0) { |
|
|
newPos = MovePositionOutsideChar(newPos, 1); |
|
|
posDrop = newPos; |
|
|
} |
|
|
if (posDrag != newPos) { |
|
|
caret.on = true; |
|
|
SetTicking(true); |
|
|
InvalidateCaret(); |
|
|
posDrag = newPos; |
|
|
InvalidateCaret(); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::DisplayCursor(Window::Cursor c) { |
|
|
if (cursorMode == SC_CURSORNORMAL) |
|
|
wMain.SetCursor(c); |
|
|
else |
|
|
wMain.SetCursor(static_cast<Window::Cursor>(cursorMode)); |
|
|
} |
|
|
|
|
|
void Editor::StartDrag() { |
|
|
// Always handled by subclasses |
|
|
//SetMouseCapture(true); |
|
|
//DisplayCursor(Window::cursorArrow); |
|
|
} |
|
|
|
|
|
void Editor::DropAt(int position, const char *value, bool moving, bool rectangular) { |
|
|
//Platform::DebugPrintf("DropAt %d\n", inDragDrop); |
|
|
if (inDragDrop) |
|
|
dropWentOutside = false; |
|
|
|
|
|
int positionWasInSelection = PositionInSelection(position); |
|
|
|
|
|
bool positionOnEdgeOfSelection = |
|
|
(position == SelectionStart()) || (position == SelectionEnd()); |
|
|
|
|
|
if ((!inDragDrop) || !(0 == positionWasInSelection) || |
|
|
(positionOnEdgeOfSelection && !moving)) { |
|
|
|
|
|
int selStart = SelectionStart(); |
|
|
int selEnd = SelectionEnd(); |
|
|
|
|
|
pdoc->BeginUndoAction(); |
|
|
|
|
|
int positionAfterDeletion = position; |
|
|
if (inDragDrop && moving) { |
|
|
// Remove dragged out text |
|
|
if (rectangular || selType == selLines) { |
|
|
SelectionLineIterator lineIterator(this); |
|
|
while (lineIterator.Iterate()) { |
|
|
if (position >= lineIterator.startPos) { |
|
|
if (position > lineIterator.endPos) { |
|
|
positionAfterDeletion -= lineIterator.endPos - lineIterator.startPos; |
|
|
} else { |
|
|
positionAfterDeletion -= position - lineIterator.startPos; |
|
|
} |
|
|
} |
|
|
} |
|
|
} else { |
|
|
if (position > selStart) { |
|
|
positionAfterDeletion -= selEnd - selStart; |
|
|
} |
|
|
} |
|
|
ClearSelection(); |
|
|
} |
|
|
position = positionAfterDeletion; |
|
|
|
|
|
if (rectangular) { |
|
|
PasteRectangular(position, value, istrlen(value)); |
|
|
pdoc->EndUndoAction(); |
|
|
// Should try to select new rectangle but it may not be a rectangle now so just select the drop position |
|
|
SetEmptySelection(position); |
|
|
} else { |
|
|
position = MovePositionOutsideChar(position, currentPos - position); |
|
|
if (pdoc->InsertString(position, value)) { |
|
|
SetSelection(position + istrlen(value), position); |
|
|
} |
|
|
pdoc->EndUndoAction(); |
|
|
} |
|
|
} else if (inDragDrop) { |
|
|
SetEmptySelection(position); |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
* @return -1 if given position is before the selection, |
|
|
* 1 if position is after the selection, |
|
|
* 0 if position is inside the selection, |
|
|
*/ |
|
|
int Editor::PositionInSelection(int pos) { |
|
|
pos = MovePositionOutsideChar(pos, currentPos - pos); |
|
|
if (pos < SelectionStart()) { |
|
|
return -1; |
|
|
} |
|
|
if (pos > SelectionEnd()) { |
|
|
return 1; |
|
|
} |
|
|
if (selType == selStream) { |
|
|
return 0; |
|
|
} else { |
|
|
SelectionLineIterator lineIterator(this); |
|
|
lineIterator.SetAt(pdoc->LineFromPosition(pos)); |
|
|
if (pos < lineIterator.startPos) { |
|
|
return -1; |
|
|
} else if (pos > lineIterator.endPos) { |
|
|
return 1; |
|
|
} else { |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
bool Editor::PointInSelection(Point pt) { |
|
|
int pos = PositionFromLocation(pt); |
|
|
if (0 == PositionInSelection(pos)) { |
|
|
// Probably inside, but we must make a finer test |
|
|
int selStart, selEnd; |
|
|
if (selType == selStream) { |
|
|
selStart = SelectionStart(); |
|
|
selEnd = SelectionEnd(); |
|
|
} else { |
|
|
SelectionLineIterator lineIterator(this); |
|
|
lineIterator.SetAt(pdoc->LineFromPosition(pos)); |
|
|
selStart = lineIterator.startPos; |
|
|
selEnd = lineIterator.endPos; |
|
|
} |
|
|
if (pos == selStart) { |
|
|
// see if just before selection |
|
|
Point locStart = LocationFromPosition(pos); |
|
|
if (pt.x < locStart.x) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
if (pos == selEnd) { |
|
|
// see if just after selection |
|
|
Point locEnd = LocationFromPosition(pos); |
|
|
if (pt.x > locEnd.x) { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
return true; |
|
|
} |
|
|
return false; |
|
|
} |
|
|
|
|
|
bool Editor::PointInSelMargin(Point pt) { |
|
|
// Really means: "Point in a margin" |
|
|
if (vs.fixedColumnWidth > 0) { // There is a margin |
|
|
PRectangle rcSelMargin = GetClientRectangle(); |
|
|
rcSelMargin.right = vs.fixedColumnWidth - vs.leftMarginWidth; |
|
|
return rcSelMargin.Contains(pt); |
|
|
} else { |
|
|
return false; |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::LineSelection(int lineCurrent_, int lineAnchor_) { |
|
|
if (lineAnchor_ < lineCurrent_) { |
|
|
SetSelection(pdoc->LineStart(lineCurrent_ + 1), |
|
|
pdoc->LineStart(lineAnchor_)); |
|
|
} else if (lineAnchor_ > lineCurrent_) { |
|
|
SetSelection(pdoc->LineStart(lineCurrent_), |
|
|
pdoc->LineStart(lineAnchor_ + 1)); |
|
|
} else { // Same line, select it |
|
|
SetSelection(pdoc->LineStart(lineAnchor_ + 1), |
|
|
pdoc->LineStart(lineAnchor_)); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::DwellEnd(bool mouseMoved) { |
|
|
if (mouseMoved) |
|
|
ticksToDwell = dwellDelay; |
|
|
else |
|
|
ticksToDwell = SC_TIME_FOREVER; |
|
|
if (dwelling && (dwellDelay < SC_TIME_FOREVER)) { |
|
|
dwelling = false; |
|
|
NotifyDwelling(ptMouseLast, dwelling); |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::ButtonDown(Point pt, unsigned int curTime, bool shift, bool ctrl, bool alt) { |
|
|
//Platform::DebugPrintf("Scintilla:ButtonDown %d %d = %d alt=%d\n", curTime, lastClickTime, curTime - lastClickTime, alt); |
|
|
ptMouseLast = pt; |
|
|
int newPos = PositionFromLocation(pt); |
|
|
newPos = MovePositionOutsideChar(newPos, currentPos - newPos); |
|
|
inDragDrop = false; |
|
|
moveExtendsSelection = false; |
|
|
|
|
|
bool processed = NotifyMarginClick(pt, shift, ctrl, alt); |
|
|
if (processed) |
|
|
return; |
|
|
|
|
|
bool inSelMargin = PointInSelMargin(pt); |
|
|
if (shift & !inSelMargin) { |
|
|
SetSelection(newPos); |
|
|
} |
|
|
if (((curTime - lastClickTime) < Platform::DoubleClickTime()) && Close(pt, lastClick)) { |
|
|
//Platform::DebugPrintf("Double click %d %d = %d\n", curTime, lastClickTime, curTime - lastClickTime); |
|
|
SetMouseCapture(true); |
|
|
SetEmptySelection(newPos); |
|
|
bool doubleClick = false; |
|
|
// Stop mouse button bounce changing selection type |
|
|
if (!Platform::MouseButtonBounce() || curTime != lastClickTime) { |
|
|
if (selectionType == selChar) { |
|
|
selectionType = selWord; |
|
|
doubleClick = true; |
|
|
} else if (selectionType == selWord) { |
|
|
selectionType = selLine; |
|
|
} else { |
|
|
selectionType = selChar; |
|
|
originalAnchorPos = currentPos; |
|
|
} |
|
|
} |
|
|
|
|
|
if (selectionType == selWord) { |
|
|
if (currentPos >= originalAnchorPos) { // Moved forward |
|
|
SetSelection(pdoc->ExtendWordSelect(currentPos, 1), |
|
|
pdoc->ExtendWordSelect(originalAnchorPos, -1)); |
|
|
} else { // Moved backward |
|
|
SetSelection(pdoc->ExtendWordSelect(currentPos, -1), |
|
|
pdoc->ExtendWordSelect(originalAnchorPos, 1)); |
|
|
} |
|
|
} else if (selectionType == selLine) { |
|
|
lineAnchor = LineFromLocation(pt); |
|
|
SetSelection(pdoc->LineStart(lineAnchor + 1), pdoc->LineStart(lineAnchor)); |
|
|
//Platform::DebugPrintf("Triple click: %d - %d\n", anchor, currentPos); |
|
|
} else { |
|
|
SetEmptySelection(currentPos); |
|
|
} |
|
|
//Platform::DebugPrintf("Double click: %d - %d\n", anchor, currentPos); |
|
|
if (doubleClick) { |
|
|
NotifyDoubleClick(pt, shift); |
|
|
if (PositionIsHotspot(newPos)) |
|
|
NotifyHotSpotDoubleClicked(newPos, shift, ctrl, alt); |
|
|
} |
|
|
} else { // Single click |
|
|
if (inSelMargin) { |
|
|
selType = selStream; |
|
|
if (ctrl) { |
|
|
SelectAll(); |
|
|
lastClickTime = curTime; |
|
|
return; |
|
|
} |
|
|
if (!shift) { |
|
|
lineAnchor = LineFromLocation(pt); |
|
|
// Single click in margin: select whole line |
|
|
LineSelection(lineAnchor, lineAnchor); |
|
|
SetSelection(pdoc->LineStart(lineAnchor + 1), |
|
|
pdoc->LineStart(lineAnchor)); |
|
|
} else { |
|
|
// Single shift+click in margin: select from line anchor to clicked line |
|
|
if (anchor > currentPos) |
|
|
lineAnchor = pdoc->LineFromPosition(anchor - 1); |
|
|
else |
|
|
lineAnchor = pdoc->LineFromPosition(anchor); |
|
|
int lineStart = LineFromLocation(pt); |
|
|
LineSelection(lineStart, lineAnchor); |
|
|
//lineAnchor = lineStart; // Keep the same anchor for ButtonMove |
|
|
} |
|
|
|
|
|
SetDragPosition(invalidPosition); |
|
|
SetMouseCapture(true); |
|
|
selectionType = selLine; |
|
|
} else { |
|
|
if (PointIsHotspot(pt)) { |
|
|
NotifyHotSpotClicked(newPos, shift, ctrl, alt); |
|
|
} |
|
|
if (!shift) { |
|
|
inDragDrop = PointInSelection(pt) && !SelectionEmpty(); |
|
|
} |
|
|
if (inDragDrop) { |
|
|
SetMouseCapture(false); |
|
|
SetDragPosition(newPos); |
|
|
CopySelectionRange(&drag); |
|
|
StartDrag(); |
|
|
} else { |
|
|
SetDragPosition(invalidPosition); |
|
|
SetMouseCapture(true); |
|
|
if (!shift) { |
|
|
SetEmptySelection(newPos); |
|
|
} |
|
|
selType = alt ? selRectangle : selStream; |
|
|
selectionType = selChar; |
|
|
originalAnchorPos = currentPos; |
|
|
SetRectangularRange(); |
|
|
} |
|
|
} |
|
|
} |
|
|
lastClickTime = curTime; |
|
|
lastXChosen = pt.x; |
|
|
ShowCaretAtCurrentPosition(); |
|
|
} |
|
|
|
|
|
bool Editor::PositionIsHotspot(int position) { |
|
|
return vs.styles[pdoc->StyleAt(position) & pdoc->stylingBitsMask].hotspot; |
|
|
} |
|
|
|
|
|
bool Editor::PointIsHotspot(Point pt) { |
|
|
int pos = PositionFromLocationClose(pt); |
|
|
if (pos == INVALID_POSITION) |
|
|
return false; |
|
|
return PositionIsHotspot(pos); |
|
|
} |
|
|
|
|
|
void Editor::SetHotSpotRange(Point *pt) { |
|
|
if (pt) { |
|
|
int pos = PositionFromLocation(*pt); |
|
|
|
|
|
// If we don't limit this to word characters then the |
|
|
// range can encompass more than the run range and then |
|
|
// the underline will not be drawn properly. |
|
|
int hsStart_ = pdoc->ExtendStyleRange(pos, -1, vs.hotspotSingleLine); |
|
|
int hsEnd_ = pdoc->ExtendStyleRange(pos, 1, vs.hotspotSingleLine); |
|
|
|
|
|
// Only invalidate the range if the hotspot range has changed... |
|
|
if (hsStart_ != hsStart || hsEnd_ != hsEnd) { |
|
|
if (hsStart != -1) { |
|
|
InvalidateRange(hsStart, hsEnd); |
|
|
} |
|
|
hsStart = hsStart_; |
|
|
hsEnd = hsEnd_; |
|
|
InvalidateRange(hsStart, hsEnd); |
|
|
} |
|
|
} else { |
|
|
if (hsStart != -1) { |
|
|
int hsStart_ = hsStart; |
|
|
int hsEnd_ = hsEnd; |
|
|
hsStart = -1; |
|
|
hsEnd = -1; |
|
|
InvalidateRange(hsStart_, hsEnd_); |
|
|
} else { |
|
|
hsStart = -1; |
|
|
hsEnd = -1; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::GetHotSpotRange(int& hsStart_, int& hsEnd_) { |
|
|
hsStart_ = hsStart; |
|
|
hsEnd_ = hsEnd; |
|
|
} |
|
|
|
|
|
void Editor::ButtonMove(Point pt) { |
|
|
if ((ptMouseLast.x != pt.x) || (ptMouseLast.y != pt.y)) { |
|
|
DwellEnd(true); |
|
|
} |
|
|
ptMouseLast = pt; |
|
|
//Platform::DebugPrintf("Move %d %d\n", pt.x, pt.y); |
|
|
if (HaveMouseCapture()) { |
|
|
|
|
|
// Slow down autoscrolling/selection |
|
|
autoScrollTimer.ticksToWait -= timer.tickSize; |
|
|
if (autoScrollTimer.ticksToWait > 0) |
|
|
return; |
|
|
autoScrollTimer.ticksToWait = autoScrollDelay; |
|
|
|
|
|
// Adjust selection |
|
|
int movePos = PositionFromLocation(pt); |
|
|
movePos = MovePositionOutsideChar(movePos, currentPos - movePos); |
|
|
if (posDrag >= 0) { |
|
|
SetDragPosition(movePos); |
|
|
} else { |
|
|
if (selectionType == selChar) { |
|
|
SetSelection(movePos); |
|
|
} else if (selectionType == selWord) { |
|
|
// Continue selecting by word |
|
|
if (movePos == originalAnchorPos) { // Didn't move |
|
|
// No need to do anything. Previously this case was lumped |
|
|
// in with "Moved forward", but that can be harmful in this |
|
|
// case: a handler for the NotifyDoubleClick re-adjusts |
|
|
// the selection for a fancier definition of "word" (for |
|
|
// example, in Perl it is useful to include the leading |
|
|
// '$', '%' or '@' on variables for word selection). In this |
|
|
// the ButtonMove() called via Tick() for auto-scrolling |
|
|
// could result in the fancier word selection adjustment |
|
|
// being unmade. |
|
|
} else if (movePos > originalAnchorPos) { // Moved forward |
|
|
SetSelection(pdoc->ExtendWordSelect(movePos, 1), |
|
|
pdoc->ExtendWordSelect(originalAnchorPos, -1)); |
|
|
} else { // Moved backward |
|
|
SetSelection(pdoc->ExtendWordSelect(movePos, -1), |
|
|
pdoc->ExtendWordSelect(originalAnchorPos, 1)); |
|
|
} |
|
|
} else { |
|
|
// Continue selecting by line |
|
|
int lineMove = LineFromLocation(pt); |
|
|
LineSelection(lineMove, lineAnchor); |
|
|
} |
|
|
} |
|
|
// While dragging to make rectangular selection, we don't want the current |
|
|
// position to jump to the end of smaller or empty lines. |
|
|
//xEndSelect = pt.x - vs.fixedColumnWidth + xOffset; |
|
|
xEndSelect = XFromPosition(movePos); |
|
|
|
|
|
// Autoscroll |
|
|
PRectangle rcClient = GetClientRectangle(); |
|
|
if (pt.y > rcClient.bottom) { |
|
|
int lineMove = cs.DisplayFromDoc(LineFromLocation(pt)); |
|
|
if (lineMove < 0) { |
|
|
lineMove = cs.DisplayFromDoc(pdoc->LinesTotal() - 1); |
|
|
} |
|
|
ScrollTo(lineMove - LinesOnScreen() + 5); |
|
|
Redraw(); |
|
|
} else if (pt.y < rcClient.top) { |
|
|
int lineMove = cs.DisplayFromDoc(LineFromLocation(pt)); |
|
|
ScrollTo(lineMove - 5); |
|
|
Redraw(); |
|
|
} |
|
|
EnsureCaretVisible(false, false, true); |
|
|
|
|
|
if (hsStart != -1 && !PositionIsHotspot(movePos)) |
|
|
SetHotSpotRange(NULL); |
|
|
|
|
|
} else { |
|
|
if (vs.fixedColumnWidth > 0) { // There is a margin |
|
|
if (PointInSelMargin(pt)) { |
|
|
DisplayCursor(Window::cursorReverseArrow); |
|
|
return; // No need to test for selection |
|
|
} |
|
|
} |
|
|
// Display regular (drag) cursor over selection |
|
|
if (PointInSelection(pt) && !SelectionEmpty()) { |
|
|
DisplayCursor(Window::cursorArrow); |
|
|
} else if (PointIsHotspot(pt)) { |
|
|
DisplayCursor(Window::cursorHand); |
|
|
SetHotSpotRange(&pt); |
|
|
} else { |
|
|
DisplayCursor(Window::cursorText); |
|
|
SetHotSpotRange(NULL); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::ButtonUp(Point pt, unsigned int curTime, bool ctrl) { |
|
|
//Platform::DebugPrintf("ButtonUp %d\n", HaveMouseCapture()); |
|
|
if (HaveMouseCapture()) { |
|
|
if (PointInSelMargin(pt)) { |
|
|
DisplayCursor(Window::cursorReverseArrow); |
|
|
} else { |
|
|
DisplayCursor(Window::cursorText); |
|
|
SetHotSpotRange(NULL); |
|
|
} |
|
|
ptMouseLast = pt; |
|
|
SetMouseCapture(false); |
|
|
int newPos = PositionFromLocation(pt); |
|
|
newPos = MovePositionOutsideChar(newPos, currentPos - newPos); |
|
|
if (inDragDrop) { |
|
|
int selStart = SelectionStart(); |
|
|
int selEnd = SelectionEnd(); |
|
|
if (selStart < selEnd) { |
|
|
if (drag.len) { |
|
|
if (ctrl) { |
|
|
if (pdoc->InsertString(newPos, drag.s, drag.len)) { |
|
|
SetSelection(newPos, newPos + drag.len); |
|
|
} |
|
|
} else if (newPos < selStart) { |
|
|
pdoc->DeleteChars(selStart, drag.len); |
|
|
if (pdoc->InsertString(newPos, drag.s, drag.len)) { |
|
|
SetSelection(newPos, newPos + drag.len); |
|
|
} |
|
|
} else if (newPos > selEnd) { |
|
|
pdoc->DeleteChars(selStart, drag.len); |
|
|
newPos -= drag.len; |
|
|
if (pdoc->InsertString(newPos, drag.s, drag.len)) { |
|
|
SetSelection(newPos, newPos + drag.len); |
|
|
} |
|
|
} else { |
|
|
SetEmptySelection(newPos); |
|
|
} |
|
|
drag.Free(); |
|
|
} |
|
|
selectionType = selChar; |
|
|
} |
|
|
} else { |
|
|
if (selectionType == selChar) { |
|
|
SetSelection(newPos); |
|
|
} |
|
|
} |
|
|
SetRectangularRange(); |
|
|
lastClickTime = curTime; |
|
|
lastClick = pt; |
|
|
lastXChosen = pt.x; |
|
|
if (selType == selStream) { |
|
|
SetLastXChosen(); |
|
|
} |
|
|
inDragDrop = false; |
|
|
EnsureCaretVisible(false); |
|
|
} |
|
|
} |
|
|
|
|
|
// Called frequently to perform background UI including |
|
|
// caret blinking and automatic scrolling. |
|
|
void Editor::Tick() { |
|
|
if (HaveMouseCapture()) { |
|
|
// Auto scroll |
|
|
ButtonMove(ptMouseLast); |
|
|
} |
|
|
if (caret.period > 0) { |
|
|
timer.ticksToWait -= timer.tickSize; |
|
|
if (timer.ticksToWait <= 0) { |
|
|
caret.on = !caret.on; |
|
|
timer.ticksToWait = caret.period; |
|
|
if (caret.active) { |
|
|
InvalidateCaret(); |
|
|
} |
|
|
} |
|
|
} |
|
|
if ((dwellDelay < SC_TIME_FOREVER) && |
|
|
(ticksToDwell > 0) && |
|
|
(!HaveMouseCapture())) { |
|
|
ticksToDwell -= timer.tickSize; |
|
|
if (ticksToDwell <= 0) { |
|
|
dwelling = true; |
|
|
NotifyDwelling(ptMouseLast, dwelling); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
bool Editor::Idle() { |
|
|
|
|
|
bool idleDone; |
|
|
|
|
|
bool wrappingDone = wrapState == eWrapNone; |
|
|
|
|
|
if (!wrappingDone) { |
|
|
// Wrap lines during idle. |
|
|
WrapLines(false, -1); |
|
|
// No more wrapping |
|
|
if (wrapStart == wrapEnd) |
|
|
wrappingDone = true; |
|
|
} |
|
|
|
|
|
// Add more idle things to do here, but make sure idleDone is |
|
|
// set correctly before the function returns. returning |
|
|
// false will stop calling this idle funtion until SetIdle() is |
|
|
// called again. |
|
|
|
|
|
idleDone = wrappingDone; // && thatDone && theOtherThingDone... |
|
|
|
|
|
return !idleDone; |
|
|
} |
|
|
|
|
|
void Editor::SetFocusState(bool focusState) { |
|
|
hasFocus = focusState; |
|
|
NotifyFocus(hasFocus); |
|
|
if (hasFocus) { |
|
|
ShowCaretAtCurrentPosition(); |
|
|
} else { |
|
|
CancelModes(); |
|
|
DropCaret(); |
|
|
} |
|
|
} |
|
|
|
|
|
bool Editor::PaintContains(PRectangle rc) { |
|
|
return rcPaint.Contains(rc); |
|
|
} |
|
|
|
|
|
bool Editor::PaintContainsMargin() { |
|
|
PRectangle rcSelMargin = GetClientRectangle(); |
|
|
rcSelMargin.right = vs.fixedColumnWidth; |
|
|
return PaintContains(rcSelMargin); |
|
|
} |
|
|
|
|
|
void Editor::CheckForChangeOutsidePaint(Range r) { |
|
|
if (paintState == painting && !paintingAllText) { |
|
|
//Platform::DebugPrintf("Checking range in paint %d-%d\n", r.start, r.end); |
|
|
if (!r.Valid()) |
|
|
return; |
|
|
|
|
|
PRectangle rcRange = RectangleFromRange(r.start, r.end); |
|
|
PRectangle rcText = GetTextRectangle(); |
|
|
if (rcRange.top < rcText.top) { |
|
|
rcRange.top = rcText.top; |
|
|
} |
|
|
if (rcRange.bottom > rcText.bottom) { |
|
|
rcRange.bottom = rcText.bottom; |
|
|
} |
|
|
|
|
|
if (!PaintContains(rcRange)) { |
|
|
AbandonPaint(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::SetBraceHighlight(Position pos0, Position pos1, int matchStyle) { |
|
|
if ((pos0 != braces[0]) || (pos1 != braces[1]) || (matchStyle != bracesMatchStyle)) { |
|
|
if ((braces[0] != pos0) || (matchStyle != bracesMatchStyle)) { |
|
|
CheckForChangeOutsidePaint(Range(braces[0])); |
|
|
CheckForChangeOutsidePaint(Range(pos0)); |
|
|
braces[0] = pos0; |
|
|
} |
|
|
if ((braces[1] != pos1) || (matchStyle != bracesMatchStyle)) { |
|
|
CheckForChangeOutsidePaint(Range(braces[1])); |
|
|
CheckForChangeOutsidePaint(Range(pos1)); |
|
|
braces[1] = pos1; |
|
|
} |
|
|
bracesMatchStyle = matchStyle; |
|
|
if (paintState == notPainting) { |
|
|
Redraw(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::SetDocPointer(Document *document) { |
|
|
//Platform::DebugPrintf("** %x setdoc to %x\n", pdoc, document); |
|
|
pdoc->RemoveWatcher(this, 0); |
|
|
pdoc->Release(); |
|
|
if (document == NULL) { |
|
|
pdoc = new Document(); |
|
|
} else { |
|
|
pdoc = document; |
|
|
} |
|
|
pdoc->AddRef(); |
|
|
|
|
|
// Ensure all positions within document |
|
|
selType = selStream; |
|
|
currentPos = 0; |
|
|
anchor = 0; |
|
|
targetStart = 0; |
|
|
targetEnd = 0; |
|
|
|
|
|
braces[0] = invalidPosition; |
|
|
braces[1] = invalidPosition; |
|
|
|
|
|
// Reset the contraction state to fully shown. |
|
|
cs.Clear(); |
|
|
cs.InsertLines(0, pdoc->LinesTotal() - 1); |
|
|
llc.Deallocate(); |
|
|
NeedWrapping(); |
|
|
|
|
|
pdoc->AddWatcher(this, 0); |
|
|
SetScrollBars(); |
|
|
Redraw(); |
|
|
} |
|
|
|
|
|
/** |
|
|
* Recursively expand a fold, making lines visible except where they have an unexpanded parent. |
|
|
*/ |
|
|
void Editor::Expand(int &line, bool doExpand) { |
|
|
int lineMaxSubord = pdoc->GetLastChild(line); |
|
|
line++; |
|
|
while (line <= lineMaxSubord) { |
|
|
if (doExpand) |
|
|
cs.SetVisible(line, line, true); |
|
|
int level = pdoc->GetLevel(line); |
|
|
if (level & SC_FOLDLEVELHEADERFLAG) { |
|
|
if (doExpand && cs.GetExpanded(line)) { |
|
|
Expand(line, true); |
|
|
} else { |
|
|
Expand(line, false); |
|
|
} |
|
|
} else { |
|
|
line++; |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
void Editor::ToggleContraction(int line) { |
|
|
if (line >= 0) { |
|
|
if ((pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) == 0) { |
|
|
line = pdoc->GetFoldParent(line); |
|
|
if (line < 0) |
|
|
return; |
|
|
} |
|
|
|
|
|
if (cs.GetExpanded(line)) { |
|
|
int lineMaxSubord = pdoc->GetLastChild(line); |
|
|
cs.SetExpanded(line, 0); |
|
|
if (lineMaxSubord > line) { |
|
|
cs.SetVisible(line + 1, lineMaxSubord, false); |
|
|
|
|
|
int lineCurrent = pdoc->LineFromPosition(currentPos); |
|
|
if (lineCurrent > line && lineCurrent <= lineMaxSubord) { |
|
|
// This does not re-expand the fold |
|
|
EnsureCaretVisible(); |
|
|
} |
|
|
|
|
|
SetScrollBars(); |
|
|
Redraw(); |
|
|
} |
|
|
|
|
|
} else { |
|
|
if (!(cs.GetVisible(line))) { |
|
|
EnsureLineVisible(line, false); |
|
|
GoToLine(line); |
|
|
} |
|
|
cs.SetExpanded(line, 1); |
|
|
Expand(line, true); |
|
|
SetScrollBars(); |
|
|
Redraw(); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
/** |
|
|
* Recurse up from this line to find any folds that prevent this line from being visible |
|
|
* and unfold them all. |
|
|
*/ |
|
|
void Editor::EnsureLineVisible(int lineDoc, bool enforcePolicy) { |
|
|
|
|
|
// In case in need of wrapping to ensure DisplayFromDoc works. |
|
|
WrapLines(true, -1); |
|
|
|
|
|
if (!cs.GetVisible(lineDoc)) { |
|
|
int lineParent = pdoc->GetFoldParent(lineDoc); |
|
|
if (lineParent >= 0) { |
|
|
if (lineDoc != lineParent) |
|
|
EnsureLineVisible(lineParent, enforcePolicy); |
|
|
if (!cs.GetExpanded(lineParent)) { |
|
|
cs.SetExpanded(lineParent, 1); |
|
|
Expand(lineParent, true); |
|
|
} |
|
|
} |
|
|
SetScrollBars(); |
|
|
Redraw(); |
|
|
} |
|
|
if (enforcePolicy) { |
|
|
int lineDisplay = cs.DisplayFromDoc(lineDoc); |
|
|
if (visiblePolicy & VISIBLE_SLOP) { |
|
|
if ((topLine > lineDisplay) || ((visiblePolicy & VISIBLE_STRICT) && (topLine + visibleSlop > lineDisplay))) { |
|
|
SetTopLine(Platform::Clamp(lineDisplay - visibleSlop, 0, MaxScrollPos())); |
|
|
SetVerticalScrollPos(); |
|
|
Redraw(); |
|
|
} else if ((lineDisplay > topLine + LinesOnScreen() - 1) || |
|
|
((visiblePolicy & VISIBLE_STRICT) && (lineDisplay > topLine + LinesOnScreen() - 1 - visibleSlop))) { |
|
|
SetTopLine(Platform::Clamp(lineDisplay - LinesOnScreen() + 1 + visibleSlop, 0, MaxScrollPos())); |
|
|
SetVerticalScrollPos(); |
|
|
Redraw(); |
|
|
} |
|
|
} else { |
|
|
if ((topLine > lineDisplay) || (lineDisplay > topLine + LinesOnScreen() - 1) || (visiblePolicy & VISIBLE_STRICT)) { |
|
|
SetTopLine(Platform::Clamp(lineDisplay - LinesOnScreen() / 2 + 1, 0, MaxScrollPos())); |
|
|
SetVerticalScrollPos(); |
|
|
Redraw(); |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
int Editor::ReplaceTarget(bool replacePatterns, const char *text, int length) { |
|
|
pdoc->BeginUndoAction(); |
|
|
if (length == -1) |
|
|
length = istrlen(text); |
|
|
if (replacePatterns) { |
|
|
text = pdoc->SubstituteByPosition(text, &length); |
|
|
if (!text) |
|
|
return 0; |
|
|
} |
|
|
if (targetStart != targetEnd) |
|
|
pdoc->DeleteChars(targetStart, targetEnd - targetStart); |
|
|
targetEnd = targetStart; |
|
|
pdoc->InsertString(targetStart, text, length); |
|
|
targetEnd = targetStart + length; |
|
|
pdoc->EndUndoAction(); |
|
|
return length; |
|
|
} |
|
|
|
|
|
bool Editor::IsUnicodeMode() const { |
|
|
return pdoc && (SC_CP_UTF8 == pdoc->dbcsCodePage); |
|
|
} |
|
|
|
|
|
int Editor::CodePage() const { |
|
|
if (pdoc) |
|
|
return pdoc->dbcsCodePage; |
|
|
else |
|
|
return 0; |
|
|
} |
|
|
|
|
|
int Editor::WrapCount(int line) { |
|
|
AutoSurface surface(this); |
|
|
AutoLineLayout ll(llc, RetrieveLineLayout(line)); |
|
|
|
|
|
if (surface && ll) { |
|
|
LayoutLine(line, surface, vs, ll, wrapWidth); |
|
|
return ll->lines; |
|
|
} else { |
|
|
return 1; |
|
|
} |
|
|
} |
|
|
|
|
|
static bool ValidMargin(unsigned long wParam) { |
|
|
return wParam < ViewStyle::margins; |
|
|
} |
|
|
|
|
|
static char *CharPtrFromSPtr(sptr_t lParam) { |
|
|
return reinterpret_cast<char *>(lParam); |
|
|
} |
|
|
|
|
|
sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { |
|
|
//Platform::DebugPrintf("S start wnd proc %d %d %d\n",iMessage, wParam, lParam); |
|
|
|
|
|
// Optional macro recording hook |
|
|
if (recordingMacro) |
|
|
NotifyMacroRecord(iMessage, wParam, lParam); |
|
|
|
|
|
switch (iMessage) { |
|
|
|
|
|
case SCI_GETTEXT: { |
|
|
if (lParam == 0) |
|
|
return pdoc->Length() + 1; |
|
|
if (wParam == 0) |
|
|
return 0; |
|
|
char *ptr = CharPtrFromSPtr(lParam); |
|
|
unsigned int iChar = 0; |
|
|
for (; iChar < wParam - 1; iChar++) |
|
|
ptr[iChar] = pdoc->CharAt(iChar); |
|
|
ptr[iChar] = '\0'; |
|
|
return iChar; |
|
|
} |
|
|
|
|
|
case SCI_SETTEXT: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
pdoc->BeginUndoAction(); |
|
|
pdoc->DeleteChars(0, pdoc->Length()); |
|
|
SetEmptySelection(0); |
|
|
pdoc->InsertString(0, CharPtrFromSPtr(lParam)); |
|
|
pdoc->EndUndoAction(); |
|
|
return 1; |
|
|
} |
|
|
|
|
|
case SCI_GETTEXTLENGTH: |
|
|
return pdoc->Length(); |
|
|
|
|
|
case SCI_CUT: |
|
|
Cut(); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
|
|
|
case SCI_COPY: |
|
|
Copy(); |
|
|
break; |
|
|
|
|
|
case SCI_COPYRANGE: |
|
|
CopyRangeToClipboard(wParam, lParam); |
|
|
break; |
|
|
|
|
|
case SCI_COPYTEXT: |
|
|
CopyText(wParam, CharPtrFromSPtr(lParam)); |
|
|
break; |
|
|
|
|
|
case SCI_PASTE: |
|
|
Paste(); |
|
|
if (!caretSticky) { |
|
|
SetLastXChosen(); |
|
|
} |
|
|
EnsureCaretVisible(); |
|
|
break; |
|
|
|
|
|
case SCI_CLEAR: |
|
|
Clear(); |
|
|
SetLastXChosen(); |
|
|
EnsureCaretVisible(); |
|
|
break; |
|
|
|
|
|
case SCI_UNDO: |
|
|
Undo(); |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
|
|
|
case SCI_CANUNDO: |
|
|
return (pdoc->CanUndo() && !pdoc->IsReadOnly()) ? 1 : 0; |
|
|
|
|
|
case SCI_EMPTYUNDOBUFFER: |
|
|
pdoc->DeleteUndoHistory(); |
|
|
return 0; |
|
|
|
|
|
case SCI_GETFIRSTVISIBLELINE: |
|
|
return topLine; |
|
|
|
|
|
case SCI_GETLINE: { // Risk of overwriting the end of the buffer |
|
|
int lineStart = pdoc->LineStart(wParam); |
|
|
int lineEnd = pdoc->LineStart(wParam + 1); |
|
|
if (lParam == 0) { |
|
|
return lineEnd - lineStart; |
|
|
} |
|
|
char *ptr = CharPtrFromSPtr(lParam); |
|
|
int iPlace = 0; |
|
|
for (int iChar = lineStart; iChar < lineEnd; iChar++) { |
|
|
ptr[iPlace++] = pdoc->CharAt(iChar); |
|
|
} |
|
|
return iPlace; |
|
|
} |
|
|
|
|
|
case SCI_GETLINECOUNT: |
|
|
if (pdoc->LinesTotal() == 0) |
|
|
return 1; |
|
|
else |
|
|
return pdoc->LinesTotal(); |
|
|
|
|
|
case SCI_GETMODIFY: |
|
|
return !pdoc->IsSavePoint(); |
|
|
|
|
|
case SCI_SETSEL: { |
|
|
int nStart = static_cast<int>(wParam); |
|
|
int nEnd = static_cast<int>(lParam); |
|
|
if (nEnd < 0) |
|
|
nEnd = pdoc->Length(); |
|
|
if (nStart < 0) |
|
|
nStart = nEnd; // Remove selection |
|
|
selType = selStream; |
|
|
SetSelection(nEnd, nStart); |
|
|
EnsureCaretVisible(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETSELTEXT: { |
|
|
if (lParam == 0) { |
|
|
if (selType == selStream) { |
|
|
return 1 + SelectionEnd() - SelectionStart(); |
|
|
} else { |
|
|
// TODO: why is selLines handled the slow way? |
|
|
int size = 0; |
|
|
int extraCharsPerLine = 0; |
|
|
if (selType != selLines) |
|
|
extraCharsPerLine = (pdoc->eolMode == SC_EOL_CRLF) ? 2 : 1; |
|
|
SelectionLineIterator lineIterator(this); |
|
|
while (lineIterator.Iterate()) { |
|
|
size += lineIterator.endPos + extraCharsPerLine - lineIterator.startPos; |
|
|
} |
|
|
|
|
|
return 1 + size; |
|
|
} |
|
|
} |
|
|
SelectionText selectedText; |
|
|
CopySelectionRange(&selectedText); |
|
|
char *ptr = CharPtrFromSPtr(lParam); |
|
|
int iChar = 0; |
|
|
if (selectedText.len) { |
|
|
for (; iChar < selectedText.len; iChar++) |
|
|
ptr[iChar] = selectedText.s[iChar]; |
|
|
} else { |
|
|
ptr[0] = '\0'; |
|
|
} |
|
|
return iChar; |
|
|
} |
|
|
|
|
|
case SCI_LINEFROMPOSITION: |
|
|
if (static_cast<int>(wParam) < 0) |
|
|
return 0; |
|
|
return pdoc->LineFromPosition(wParam); |
|
|
|
|
|
case SCI_POSITIONFROMLINE: |
|
|
if (static_cast<int>(wParam) < 0) |
|
|
wParam = pdoc->LineFromPosition(SelectionStart()); |
|
|
if (wParam == 0) |
|
|
return 0; // Even if there is no text, there is a first line that starts at 0 |
|
|
if (static_cast<int>(wParam) > pdoc->LinesTotal()) |
|
|
return -1; |
|
|
//if (wParam > pdoc->LineFromPosition(pdoc->Length())) // Useful test, anyway... |
|
|
// return -1; |
|
|
return pdoc->LineStart(wParam); |
|
|
|
|
|
// Replacement of the old Scintilla interpretation of EM_LINELENGTH |
|
|
case SCI_LINELENGTH: |
|
|
if ((static_cast<int>(wParam) < 0) || |
|
|
(static_cast<int>(wParam) > pdoc->LineFromPosition(pdoc->Length()))) |
|
|
return 0; |
|
|
return pdoc->LineStart(wParam + 1) - pdoc->LineStart(wParam); |
|
|
|
|
|
case SCI_REPLACESEL: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
pdoc->BeginUndoAction(); |
|
|
ClearSelection(); |
|
|
char *replacement = CharPtrFromSPtr(lParam); |
|
|
pdoc->InsertString(currentPos, replacement); |
|
|
pdoc->EndUndoAction(); |
|
|
SetEmptySelection(currentPos + istrlen(replacement)); |
|
|
EnsureCaretVisible(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_SETTARGETSTART: |
|
|
targetStart = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_GETTARGETSTART: |
|
|
return targetStart; |
|
|
|
|
|
case SCI_SETTARGETEND: |
|
|
targetEnd = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_GETTARGETEND: |
|
|
return targetEnd; |
|
|
|
|
|
case SCI_TARGETFROMSELECTION: |
|
|
if (currentPos < anchor) { |
|
|
targetStart = currentPos; |
|
|
targetEnd = anchor; |
|
|
} else { |
|
|
targetStart = anchor; |
|
|
targetEnd = currentPos; |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_REPLACETARGET: |
|
|
PLATFORM_ASSERT(lParam); |
|
|
return ReplaceTarget(false, CharPtrFromSPtr(lParam), wParam); |
|
|
|
|
|
case SCI_REPLACETARGETRE: |
|
|
PLATFORM_ASSERT(lParam); |
|
|
return ReplaceTarget(true, CharPtrFromSPtr(lParam), wParam); |
|
|
|
|
|
case SCI_SEARCHINTARGET: |
|
|
PLATFORM_ASSERT(lParam); |
|
|
return SearchInTarget(CharPtrFromSPtr(lParam), wParam); |
|
|
|
|
|
case SCI_SETSEARCHFLAGS: |
|
|
searchFlags = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_GETSEARCHFLAGS: |
|
|
return searchFlags; |
|
|
|
|
|
case SCI_POSITIONBEFORE: |
|
|
return pdoc->MovePositionOutsideChar(wParam-1, -1, true); |
|
|
|
|
|
case SCI_POSITIONAFTER: |
|
|
return pdoc->MovePositionOutsideChar(wParam+1, 1, true); |
|
|
|
|
|
case SCI_LINESCROLL: |
|
|
ScrollTo(topLine + lParam); |
|
|
HorizontalScrollTo(xOffset + wParam * vs.spaceWidth); |
|
|
return 1; |
|
|
|
|
|
case SCI_SETXOFFSET: |
|
|
xOffset = wParam; |
|
|
SetHorizontalScrollPos(); |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETXOFFSET: |
|
|
return xOffset; |
|
|
|
|
|
case SCI_CHOOSECARETX: |
|
|
SetLastXChosen(); |
|
|
break; |
|
|
|
|
|
case SCI_SCROLLCARET: |
|
|
EnsureCaretVisible(); |
|
|
break; |
|
|
|
|
|
case SCI_SETREADONLY: |
|
|
pdoc->SetReadOnly(wParam != 0); |
|
|
return 1; |
|
|
|
|
|
case SCI_GETREADONLY: |
|
|
return pdoc->IsReadOnly(); |
|
|
|
|
|
case SCI_CANPASTE: |
|
|
return CanPaste(); |
|
|
|
|
|
case SCI_POINTXFROMPOSITION: |
|
|
if (lParam < 0) { |
|
|
return 0; |
|
|
} else { |
|
|
Point pt = LocationFromPosition(lParam); |
|
|
return pt.x; |
|
|
} |
|
|
|
|
|
case SCI_POINTYFROMPOSITION: |
|
|
if (lParam < 0) { |
|
|
return 0; |
|
|
} else { |
|
|
Point pt = LocationFromPosition(lParam); |
|
|
return pt.y; |
|
|
} |
|
|
|
|
|
case SCI_FINDTEXT: |
|
|
return FindText(wParam, lParam); |
|
|
|
|
|
case SCI_GETTEXTRANGE: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
TextRange *tr = reinterpret_cast<TextRange *>(lParam); |
|
|
int cpMax = tr->chrg.cpMax; |
|
|
if (cpMax == -1) |
|
|
cpMax = pdoc->Length(); |
|
|
PLATFORM_ASSERT(cpMax <= pdoc->Length()); |
|
|
int len = cpMax - tr->chrg.cpMin; // No -1 as cpMin and cpMax are referring to inter character positions |
|
|
pdoc->GetCharRange(tr->lpstrText, tr->chrg.cpMin, len); |
|
|
// Spec says copied text is terminated with a NUL |
|
|
tr->lpstrText[len] = '\0'; |
|
|
return len; // Not including NUL |
|
|
} |
|
|
|
|
|
case SCI_HIDESELECTION: |
|
|
hideSelection = wParam != 0; |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_FORMATRANGE: |
|
|
return FormatRange(wParam != 0, reinterpret_cast<RangeToFormat *>(lParam)); |
|
|
|
|
|
case SCI_GETMARGINLEFT: |
|
|
return vs.leftMarginWidth; |
|
|
|
|
|
case SCI_GETMARGINRIGHT: |
|
|
return vs.rightMarginWidth; |
|
|
|
|
|
case SCI_SETMARGINLEFT: |
|
|
vs.leftMarginWidth = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETMARGINRIGHT: |
|
|
vs.rightMarginWidth = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
// Control specific mesages |
|
|
|
|
|
case SCI_ADDTEXT: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
pdoc->InsertString(CurrentPosition(), CharPtrFromSPtr(lParam), wParam); |
|
|
SetEmptySelection(currentPos + wParam); |
|
|
return 0; |
|
|
} |
|
|
|
|
|
case SCI_ADDSTYLEDTEXT: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
pdoc->InsertStyledString(CurrentPosition() * 2, CharPtrFromSPtr(lParam), wParam); |
|
|
SetEmptySelection(currentPos + wParam / 2); |
|
|
return 0; |
|
|
} |
|
|
|
|
|
case SCI_INSERTTEXT: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
int insertPos = wParam; |
|
|
if (static_cast<int>(wParam) == -1) |
|
|
insertPos = CurrentPosition(); |
|
|
int newCurrent = CurrentPosition(); |
|
|
char *sz = CharPtrFromSPtr(lParam); |
|
|
pdoc->InsertString(insertPos, sz); |
|
|
if (newCurrent > insertPos) |
|
|
newCurrent += istrlen(sz); |
|
|
SetEmptySelection(newCurrent); |
|
|
return 0; |
|
|
} |
|
|
|
|
|
case SCI_APPENDTEXT: |
|
|
pdoc->InsertString(pdoc->Length(), CharPtrFromSPtr(lParam), wParam); |
|
|
return 0; |
|
|
|
|
|
case SCI_CLEARALL: |
|
|
ClearAll(); |
|
|
return 0; |
|
|
|
|
|
case SCI_CLEARDOCUMENTSTYLE: |
|
|
ClearDocumentStyle(); |
|
|
return 0; |
|
|
|
|
|
case SCI_SETUNDOCOLLECTION: |
|
|
pdoc->SetUndoCollection(wParam != 0); |
|
|
return 0; |
|
|
|
|
|
case SCI_GETUNDOCOLLECTION: |
|
|
return pdoc->IsCollectingUndo(); |
|
|
|
|
|
case SCI_BEGINUNDOACTION: |
|
|
pdoc->BeginUndoAction(); |
|
|
return 0; |
|
|
|
|
|
case SCI_ENDUNDOACTION: |
|
|
pdoc->EndUndoAction(); |
|
|
return 0; |
|
|
|
|
|
case SCI_GETCARETPERIOD: |
|
|
return caret.period; |
|
|
|
|
|
case SCI_SETCARETPERIOD: |
|
|
caret.period = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_SETWORDCHARS: { |
|
|
pdoc->SetDefaultCharClasses(false); |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
pdoc->SetCharClasses(reinterpret_cast<unsigned char *>(lParam), CharClassify::ccWord); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_SETWHITESPACECHARS: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
pdoc->SetCharClasses(reinterpret_cast<unsigned char *>(lParam), CharClassify::ccSpace); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_SETCHARSDEFAULT: |
|
|
pdoc->SetDefaultCharClasses(true); |
|
|
break; |
|
|
|
|
|
case SCI_GETLENGTH: |
|
|
return pdoc->Length(); |
|
|
|
|
|
case SCI_ALLOCATE: |
|
|
pdoc->Allocate(wParam); |
|
|
break; |
|
|
|
|
|
case SCI_GETCHARAT: |
|
|
return pdoc->CharAt(wParam); |
|
|
|
|
|
case SCI_SETCURRENTPOS: |
|
|
SetSelection(wParam, anchor); |
|
|
break; |
|
|
|
|
|
case SCI_GETCURRENTPOS: |
|
|
return currentPos; |
|
|
|
|
|
case SCI_SETANCHOR: |
|
|
SetSelection(currentPos, wParam); |
|
|
break; |
|
|
|
|
|
case SCI_GETANCHOR: |
|
|
return anchor; |
|
|
|
|
|
case SCI_SETSELECTIONSTART: |
|
|
SetSelection(Platform::Maximum(currentPos, wParam), wParam); |
|
|
break; |
|
|
|
|
|
case SCI_GETSELECTIONSTART: |
|
|
return Platform::Minimum(anchor, currentPos); |
|
|
|
|
|
case SCI_SETSELECTIONEND: |
|
|
SetSelection(wParam, Platform::Minimum(anchor, wParam)); |
|
|
break; |
|
|
|
|
|
case SCI_GETSELECTIONEND: |
|
|
return Platform::Maximum(anchor, currentPos); |
|
|
|
|
|
case SCI_SETPRINTMAGNIFICATION: |
|
|
printMagnification = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_GETPRINTMAGNIFICATION: |
|
|
return printMagnification; |
|
|
|
|
|
case SCI_SETPRINTCOLOURMODE: |
|
|
printColourMode = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_GETPRINTCOLOURMODE: |
|
|
return printColourMode; |
|
|
|
|
|
case SCI_SETPRINTWRAPMODE: |
|
|
printWrapState = (wParam == SC_WRAP_WORD) ? eWrapWord : eWrapNone; |
|
|
break; |
|
|
|
|
|
case SCI_GETPRINTWRAPMODE: |
|
|
return printWrapState; |
|
|
|
|
|
case SCI_GETSTYLEAT: |
|
|
if (static_cast<int>(wParam) >= pdoc->Length()) |
|
|
return 0; |
|
|
else |
|
|
return pdoc->StyleAt(wParam); |
|
|
|
|
|
case SCI_REDO: |
|
|
Redo(); |
|
|
break; |
|
|
|
|
|
case SCI_SELECTALL: |
|
|
SelectAll(); |
|
|
break; |
|
|
|
|
|
case SCI_SETSAVEPOINT: |
|
|
pdoc->SetSavePoint(); |
|
|
break; |
|
|
|
|
|
case SCI_GETSTYLEDTEXT: { |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
TextRange *tr = reinterpret_cast<TextRange *>(lParam); |
|
|
int iPlace = 0; |
|
|
for (int iChar = tr->chrg.cpMin; iChar < tr->chrg.cpMax; iChar++) { |
|
|
tr->lpstrText[iPlace++] = pdoc->CharAt(iChar); |
|
|
tr->lpstrText[iPlace++] = pdoc->StyleAt(iChar); |
|
|
} |
|
|
tr->lpstrText[iPlace] = '\0'; |
|
|
tr->lpstrText[iPlace + 1] = '\0'; |
|
|
return iPlace; |
|
|
} |
|
|
|
|
|
case SCI_CANREDO: |
|
|
return (pdoc->CanRedo() && !pdoc->IsReadOnly()) ? 1 : 0; |
|
|
|
|
|
case SCI_MARKERLINEFROMHANDLE: |
|
|
return pdoc->LineFromHandle(wParam); |
|
|
|
|
|
case SCI_MARKERDELETEHANDLE: |
|
|
pdoc->DeleteMarkFromHandle(wParam); |
|
|
break; |
|
|
|
|
|
case SCI_GETVIEWWS: |
|
|
return vs.viewWhitespace; |
|
|
|
|
|
case SCI_SETVIEWWS: |
|
|
vs.viewWhitespace = static_cast<WhiteSpaceVisibility>(wParam); |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_POSITIONFROMPOINT: |
|
|
return PositionFromLocation(Point(wParam, lParam)); |
|
|
|
|
|
case SCI_POSITIONFROMPOINTCLOSE: |
|
|
return PositionFromLocationClose(Point(wParam, lParam)); |
|
|
|
|
|
case SCI_GOTOLINE: |
|
|
GoToLine(wParam); |
|
|
break; |
|
|
|
|
|
case SCI_GOTOPOS: |
|
|
SetEmptySelection(wParam); |
|
|
EnsureCaretVisible(); |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETCURLINE: { |
|
|
int lineCurrentPos = pdoc->LineFromPosition(currentPos); |
|
|
int lineStart = pdoc->LineStart(lineCurrentPos); |
|
|
unsigned int lineEnd = pdoc->LineStart(lineCurrentPos + 1); |
|
|
if (lParam == 0) { |
|
|
return 1 + lineEnd - lineStart; |
|
|
} |
|
|
PLATFORM_ASSERT(wParam > 0); |
|
|
char *ptr = CharPtrFromSPtr(lParam); |
|
|
unsigned int iPlace = 0; |
|
|
for (unsigned int iChar = lineStart; iChar < lineEnd && iPlace < wParam - 1; iChar++) { |
|
|
ptr[iPlace++] = pdoc->CharAt(iChar); |
|
|
} |
|
|
ptr[iPlace] = '\0'; |
|
|
return currentPos - lineStart; |
|
|
} |
|
|
|
|
|
case SCI_GETENDSTYLED: |
|
|
return pdoc->GetEndStyled(); |
|
|
|
|
|
case SCI_GETEOLMODE: |
|
|
return pdoc->eolMode; |
|
|
|
|
|
case SCI_SETEOLMODE: |
|
|
pdoc->eolMode = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_STARTSTYLING: |
|
|
pdoc->StartStyling(wParam, static_cast<char>(lParam)); |
|
|
break; |
|
|
|
|
|
case SCI_SETSTYLING: |
|
|
pdoc->SetStyleFor(wParam, static_cast<char>(lParam)); |
|
|
break; |
|
|
|
|
|
case SCI_SETSTYLINGEX: // Specify a complete styling buffer |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
pdoc->SetStyles(wParam, CharPtrFromSPtr(lParam)); |
|
|
break; |
|
|
|
|
|
case SCI_SETBUFFEREDDRAW: |
|
|
bufferedDraw = wParam != 0; |
|
|
break; |
|
|
|
|
|
case SCI_GETBUFFEREDDRAW: |
|
|
return bufferedDraw; |
|
|
|
|
|
case SCI_GETTWOPHASEDRAW: |
|
|
return twoPhaseDraw; |
|
|
|
|
|
case SCI_SETTWOPHASEDRAW: |
|
|
twoPhaseDraw = wParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETTABWIDTH: |
|
|
if (wParam > 0) { |
|
|
pdoc->tabInChars = wParam; |
|
|
if (pdoc->indentInChars == 0) |
|
|
pdoc->actualIndentInChars = pdoc->tabInChars; |
|
|
} |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETTABWIDTH: |
|
|
return pdoc->tabInChars; |
|
|
|
|
|
case SCI_SETINDENT: |
|
|
pdoc->indentInChars = wParam; |
|
|
if (pdoc->indentInChars != 0) |
|
|
pdoc->actualIndentInChars = pdoc->indentInChars; |
|
|
else |
|
|
pdoc->actualIndentInChars = pdoc->tabInChars; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETINDENT: |
|
|
return pdoc->indentInChars; |
|
|
|
|
|
case SCI_SETUSETABS: |
|
|
pdoc->useTabs = wParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETUSETABS: |
|
|
return pdoc->useTabs; |
|
|
|
|
|
case SCI_SETLINEINDENTATION: |
|
|
pdoc->SetLineIndentation(wParam, lParam); |
|
|
break; |
|
|
|
|
|
case SCI_GETLINEINDENTATION: |
|
|
return pdoc->GetLineIndentation(wParam); |
|
|
|
|
|
case SCI_GETLINEINDENTPOSITION: |
|
|
return pdoc->GetLineIndentPosition(wParam); |
|
|
|
|
|
case SCI_SETTABINDENTS: |
|
|
pdoc->tabIndents = wParam != 0; |
|
|
break; |
|
|
|
|
|
case SCI_GETTABINDENTS: |
|
|
return pdoc->tabIndents; |
|
|
|
|
|
case SCI_SETBACKSPACEUNINDENTS: |
|
|
pdoc->backspaceUnindents = wParam != 0; |
|
|
break; |
|
|
|
|
|
case SCI_GETBACKSPACEUNINDENTS: |
|
|
return pdoc->backspaceUnindents; |
|
|
|
|
|
case SCI_SETMOUSEDWELLTIME: |
|
|
dwellDelay = wParam; |
|
|
ticksToDwell = dwellDelay; |
|
|
break; |
|
|
|
|
|
case SCI_GETMOUSEDWELLTIME: |
|
|
return dwellDelay; |
|
|
|
|
|
case SCI_WORDSTARTPOSITION: |
|
|
return pdoc->ExtendWordSelect(wParam, -1, lParam != 0); |
|
|
|
|
|
case SCI_WORDENDPOSITION: |
|
|
return pdoc->ExtendWordSelect(wParam, 1, lParam != 0); |
|
|
|
|
|
case SCI_SETWRAPMODE: |
|
|
switch (wParam) { |
|
|
case SC_WRAP_WORD: |
|
|
wrapState = eWrapWord; |
|
|
break; |
|
|
case SC_WRAP_CHAR: |
|
|
wrapState = eWrapChar; |
|
|
break; |
|
|
default: |
|
|
wrapState = eWrapNone; |
|
|
break; |
|
|
} |
|
|
xOffset = 0; |
|
|
InvalidateStyleRedraw(); |
|
|
ReconfigureScrollBars(); |
|
|
break; |
|
|
|
|
|
case SCI_GETWRAPMODE: |
|
|
return wrapState; |
|
|
|
|
|
case SCI_SETWRAPVISUALFLAGS: |
|
|
wrapVisualFlags = wParam; |
|
|
actualWrapVisualStartIndent = wrapVisualStartIndent; |
|
|
if ((wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (actualWrapVisualStartIndent == 0)) |
|
|
actualWrapVisualStartIndent = 1; // must indent to show start visual |
|
|
InvalidateStyleRedraw(); |
|
|
ReconfigureScrollBars(); |
|
|
break; |
|
|
|
|
|
case SCI_GETWRAPVISUALFLAGS: |
|
|
return wrapVisualFlags; |
|
|
|
|
|
case SCI_SETWRAPVISUALFLAGSLOCATION: |
|
|
wrapVisualFlagsLocation = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETWRAPVISUALFLAGSLOCATION: |
|
|
return wrapVisualFlagsLocation; |
|
|
|
|
|
case SCI_SETWRAPSTARTINDENT: |
|
|
wrapVisualStartIndent = wParam; |
|
|
actualWrapVisualStartIndent = wrapVisualStartIndent; |
|
|
if ((wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (actualWrapVisualStartIndent == 0)) |
|
|
actualWrapVisualStartIndent = 1; // must indent to show start visual |
|
|
InvalidateStyleRedraw(); |
|
|
ReconfigureScrollBars(); |
|
|
break; |
|
|
|
|
|
case SCI_GETWRAPSTARTINDENT: |
|
|
return wrapVisualStartIndent; |
|
|
|
|
|
case SCI_SETLAYOUTCACHE: |
|
|
llc.SetLevel(wParam); |
|
|
break; |
|
|
|
|
|
case SCI_GETLAYOUTCACHE: |
|
|
return llc.GetLevel(); |
|
|
|
|
|
case SCI_SETSCROLLWIDTH: |
|
|
PLATFORM_ASSERT(wParam > 0); |
|
|
if ((wParam > 0) && (wParam != static_cast<unsigned int >(scrollWidth))) { |
|
|
scrollWidth = wParam; |
|
|
SetScrollBars(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETSCROLLWIDTH: |
|
|
return scrollWidth; |
|
|
|
|
|
case SCI_LINESJOIN: |
|
|
LinesJoin(); |
|
|
break; |
|
|
|
|
|
case SCI_LINESSPLIT: |
|
|
LinesSplit(wParam); |
|
|
break; |
|
|
|
|
|
case SCI_TEXTWIDTH: |
|
|
PLATFORM_ASSERT(wParam <= STYLE_MAX); |
|
|
PLATFORM_ASSERT(lParam); |
|
|
return TextWidth(wParam, CharPtrFromSPtr(lParam)); |
|
|
|
|
|
case SCI_TEXTHEIGHT: |
|
|
return vs.lineHeight; |
|
|
|
|
|
case SCI_SETENDATLASTLINE: |
|
|
PLATFORM_ASSERT((wParam == 0) || (wParam == 1)); |
|
|
if (endAtLastLine != (wParam != 0)) { |
|
|
endAtLastLine = wParam != 0; |
|
|
SetScrollBars(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETENDATLASTLINE: |
|
|
return endAtLastLine; |
|
|
|
|
|
case SCI_SETCARETSTICKY: |
|
|
PLATFORM_ASSERT((wParam == 0) || (wParam == 1)); |
|
|
if (caretSticky != (wParam != 0)) { |
|
|
caretSticky = wParam != 0; |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETCARETSTICKY: |
|
|
return caretSticky; |
|
|
|
|
|
case SCI_TOGGLECARETSTICKY: |
|
|
caretSticky = !caretSticky; |
|
|
break; |
|
|
|
|
|
case SCI_GETCOLUMN: |
|
|
return pdoc->GetColumn(wParam); |
|
|
|
|
|
case SCI_FINDCOLUMN: |
|
|
return pdoc->FindColumn(wParam, lParam); |
|
|
|
|
|
case SCI_SETHSCROLLBAR : |
|
|
if (horizontalScrollBarVisible != (wParam != 0)) { |
|
|
horizontalScrollBarVisible = wParam != 0; |
|
|
SetScrollBars(); |
|
|
ReconfigureScrollBars(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETHSCROLLBAR: |
|
|
return horizontalScrollBarVisible; |
|
|
|
|
|
case SCI_SETVSCROLLBAR: |
|
|
if (verticalScrollBarVisible != (wParam != 0)) { |
|
|
verticalScrollBarVisible = wParam != 0; |
|
|
SetScrollBars(); |
|
|
ReconfigureScrollBars(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETVSCROLLBAR: |
|
|
return verticalScrollBarVisible; |
|
|
|
|
|
case SCI_SETINDENTATIONGUIDES: |
|
|
vs.viewIndentationGuides = wParam != 0; |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETINDENTATIONGUIDES: |
|
|
return vs.viewIndentationGuides; |
|
|
|
|
|
case SCI_SETHIGHLIGHTGUIDE: |
|
|
if ((highlightGuideColumn != static_cast<int>(wParam)) || (wParam > 0)) { |
|
|
highlightGuideColumn = wParam; |
|
|
Redraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETHIGHLIGHTGUIDE: |
|
|
return highlightGuideColumn; |
|
|
|
|
|
case SCI_GETLINEENDPOSITION: |
|
|
return pdoc->LineEnd(wParam); |
|
|
|
|
|
case SCI_SETCODEPAGE: |
|
|
if (ValidCodePage(wParam)) { |
|
|
pdoc->dbcsCodePage = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETCODEPAGE: |
|
|
return pdoc->dbcsCodePage; |
|
|
|
|
|
case SCI_SETUSEPALETTE: |
|
|
palette.allowRealization = wParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETUSEPALETTE: |
|
|
return palette.allowRealization; |
|
|
|
|
|
// Marker definition and setting |
|
|
case SCI_MARKERDEFINE: |
|
|
if (wParam <= MARKER_MAX) |
|
|
vs.markers[wParam].markType = lParam; |
|
|
InvalidateStyleData(); |
|
|
RedrawSelMargin(); |
|
|
break; |
|
|
case SCI_MARKERSETFORE: |
|
|
if (wParam <= MARKER_MAX) |
|
|
vs.markers[wParam].fore.desired = ColourDesired(lParam); |
|
|
InvalidateStyleData(); |
|
|
RedrawSelMargin(); |
|
|
break; |
|
|
case SCI_MARKERSETBACK: |
|
|
if (wParam <= MARKER_MAX) |
|
|
vs.markers[wParam].back.desired = ColourDesired(lParam); |
|
|
InvalidateStyleData(); |
|
|
RedrawSelMargin(); |
|
|
break; |
|
|
case SCI_MARKERSETALPHA: |
|
|
if (wParam <= MARKER_MAX) |
|
|
vs.markers[wParam].alpha = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
case SCI_MARKERADD: { |
|
|
int markerID = pdoc->AddMark(wParam, lParam); |
|
|
return markerID; |
|
|
} |
|
|
case SCI_MARKERADDSET: |
|
|
if (lParam != 0) |
|
|
pdoc->AddMarkSet(wParam, lParam); |
|
|
break; |
|
|
|
|
|
case SCI_MARKERDELETE: |
|
|
pdoc->DeleteMark(wParam, lParam); |
|
|
break; |
|
|
|
|
|
case SCI_MARKERDELETEALL: |
|
|
pdoc->DeleteAllMarks(static_cast<int>(wParam)); |
|
|
break; |
|
|
|
|
|
case SCI_MARKERGET: |
|
|
return pdoc->GetMark(wParam); |
|
|
|
|
|
case SCI_MARKERNEXT: { |
|
|
int lt = pdoc->LinesTotal(); |
|
|
for (int iLine = wParam; iLine < lt; iLine++) { |
|
|
if ((pdoc->GetMark(iLine) & lParam) != 0) |
|
|
return iLine; |
|
|
} |
|
|
} |
|
|
return -1; |
|
|
|
|
|
case SCI_MARKERPREVIOUS: { |
|
|
for (int iLine = wParam; iLine >= 0; iLine--) { |
|
|
if ((pdoc->GetMark(iLine) & lParam) != 0) |
|
|
return iLine; |
|
|
} |
|
|
} |
|
|
return -1; |
|
|
|
|
|
case SCI_MARKERDEFINEPIXMAP: |
|
|
if (wParam <= MARKER_MAX) { |
|
|
vs.markers[wParam].SetXPM(CharPtrFromSPtr(lParam)); |
|
|
}; |
|
|
InvalidateStyleData(); |
|
|
RedrawSelMargin(); |
|
|
break; |
|
|
|
|
|
case SCI_SETMARGINTYPEN: |
|
|
if (ValidMargin(wParam)) { |
|
|
vs.ms[wParam].style = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETMARGINTYPEN: |
|
|
if (ValidMargin(wParam)) |
|
|
return vs.ms[wParam].style; |
|
|
else |
|
|
return 0; |
|
|
|
|
|
case SCI_SETMARGINWIDTHN: |
|
|
if (ValidMargin(wParam)) { |
|
|
// Short-circuit if the width is unchanged, to avoid unnecessary redraw. |
|
|
if (vs.ms[wParam].width != lParam) { |
|
|
vs.ms[wParam].width = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETMARGINWIDTHN: |
|
|
if (ValidMargin(wParam)) |
|
|
return vs.ms[wParam].width; |
|
|
else |
|
|
return 0; |
|
|
|
|
|
case SCI_SETMARGINMASKN: |
|
|
if (ValidMargin(wParam)) { |
|
|
vs.ms[wParam].mask = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETMARGINMASKN: |
|
|
if (ValidMargin(wParam)) |
|
|
return vs.ms[wParam].mask; |
|
|
else |
|
|
return 0; |
|
|
|
|
|
case SCI_SETMARGINSENSITIVEN: |
|
|
if (ValidMargin(wParam)) { |
|
|
vs.ms[wParam].sensitive = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETMARGINSENSITIVEN: |
|
|
if (ValidMargin(wParam)) |
|
|
return vs.ms[wParam].sensitive ? 1 : 0; |
|
|
else |
|
|
return 0; |
|
|
|
|
|
case SCI_STYLECLEARALL: |
|
|
vs.ClearStyles(); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_STYLESETFORE: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].fore.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETBACK: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].back.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETBOLD: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].bold = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETITALIC: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].italic = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETEOLFILLED: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].eolFilled = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETSIZE: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].size = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETFONT: |
|
|
if (lParam == 0) |
|
|
return 0; |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.SetStyleFontName(wParam, CharPtrFromSPtr(lParam)); |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETUNDERLINE: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].underline = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETCASE: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].caseForce = static_cast<Style::ecaseForced>(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETCHARACTERSET: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].characterSet = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETVISIBLE: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].visible = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETCHANGEABLE: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].changeable = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
case SCI_STYLESETHOTSPOT: |
|
|
if (wParam <= STYLE_MAX) { |
|
|
vs.styles[wParam].hotspot = lParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_STYLERESETDEFAULT: |
|
|
vs.ResetDefaultStyle(); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
case SCI_SETSTYLEBITS: |
|
|
pdoc->SetStylingBits(wParam); |
|
|
break; |
|
|
|
|
|
case SCI_GETSTYLEBITS: |
|
|
return pdoc->stylingBits; |
|
|
|
|
|
case SCI_SETLINESTATE: |
|
|
return pdoc->SetLineState(wParam, lParam); |
|
|
|
|
|
case SCI_GETLINESTATE: |
|
|
return pdoc->GetLineState(wParam); |
|
|
|
|
|
case SCI_GETMAXLINESTATE: |
|
|
return pdoc->GetMaxLineState(); |
|
|
|
|
|
case SCI_GETCARETLINEVISIBLE: |
|
|
return vs.showCaretLineBackground; |
|
|
case SCI_SETCARETLINEVISIBLE: |
|
|
vs.showCaretLineBackground = wParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
case SCI_GETCARETLINEBACK: |
|
|
return vs.caretLineBackground.desired.AsLong(); |
|
|
case SCI_SETCARETLINEBACK: |
|
|
vs.caretLineBackground.desired = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
case SCI_GETCARETLINEBACKALPHA: |
|
|
return vs.caretLineAlpha; |
|
|
case SCI_SETCARETLINEBACKALPHA: |
|
|
vs.caretLineAlpha = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
// Folding messages |
|
|
|
|
|
case SCI_VISIBLEFROMDOCLINE: |
|
|
return cs.DisplayFromDoc(wParam); |
|
|
|
|
|
case SCI_DOCLINEFROMVISIBLE: |
|
|
return cs.DocFromDisplay(wParam); |
|
|
|
|
|
case SCI_WRAPCOUNT: |
|
|
return WrapCount(wParam); |
|
|
|
|
|
case SCI_SETFOLDLEVEL: { |
|
|
int prev = pdoc->SetLevel(wParam, lParam); |
|
|
if (prev != lParam) |
|
|
RedrawSelMargin(); |
|
|
return prev; |
|
|
} |
|
|
|
|
|
case SCI_GETFOLDLEVEL: |
|
|
return pdoc->GetLevel(wParam); |
|
|
|
|
|
case SCI_GETLASTCHILD: |
|
|
return pdoc->GetLastChild(wParam, lParam); |
|
|
|
|
|
case SCI_GETFOLDPARENT: |
|
|
return pdoc->GetFoldParent(wParam); |
|
|
|
|
|
case SCI_SHOWLINES: |
|
|
cs.SetVisible(wParam, lParam, true); |
|
|
SetScrollBars(); |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_HIDELINES: |
|
|
cs.SetVisible(wParam, lParam, false); |
|
|
SetScrollBars(); |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETLINEVISIBLE: |
|
|
return cs.GetVisible(wParam); |
|
|
|
|
|
case SCI_SETFOLDEXPANDED: |
|
|
if (cs.SetExpanded(wParam, lParam != 0)) { |
|
|
RedrawSelMargin(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_GETFOLDEXPANDED: |
|
|
return cs.GetExpanded(wParam); |
|
|
|
|
|
case SCI_SETFOLDFLAGS: |
|
|
foldFlags = wParam; |
|
|
Redraw(); |
|
|
break; |
|
|
|
|
|
case SCI_TOGGLEFOLD: |
|
|
ToggleContraction(wParam); |
|
|
break; |
|
|
|
|
|
case SCI_ENSUREVISIBLE: |
|
|
EnsureLineVisible(wParam, false); |
|
|
break; |
|
|
|
|
|
case SCI_ENSUREVISIBLEENFORCEPOLICY: |
|
|
EnsureLineVisible(wParam, true); |
|
|
break; |
|
|
|
|
|
case SCI_SEARCHANCHOR: |
|
|
SearchAnchor(); |
|
|
break; |
|
|
|
|
|
case SCI_SEARCHNEXT: |
|
|
case SCI_SEARCHPREV: |
|
|
return SearchText(iMessage, wParam, lParam); |
|
|
|
|
|
#ifdef INCLUDE_DEPRECATED_FEATURES |
|
|
case SCI_SETCARETPOLICY: // Deprecated |
|
|
caretXPolicy = caretYPolicy = wParam; |
|
|
caretXSlop = caretYSlop = lParam; |
|
|
break; |
|
|
#endif |
|
|
|
|
|
case SCI_SETXCARETPOLICY: |
|
|
caretXPolicy = wParam; |
|
|
caretXSlop = lParam; |
|
|
break; |
|
|
|
|
|
case SCI_SETYCARETPOLICY: |
|
|
caretYPolicy = wParam; |
|
|
caretYSlop = lParam; |
|
|
break; |
|
|
|
|
|
case SCI_SETVISIBLEPOLICY: |
|
|
visiblePolicy = wParam; |
|
|
visibleSlop = lParam; |
|
|
break; |
|
|
|
|
|
case SCI_LINESONSCREEN: |
|
|
return LinesOnScreen(); |
|
|
|
|
|
case SCI_SETSELFORE: |
|
|
vs.selforeset = wParam != 0; |
|
|
vs.selforeground.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETSELBACK: |
|
|
vs.selbackset = wParam != 0; |
|
|
vs.selbackground.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETSELALPHA: |
|
|
vs.selAlpha = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETSELALPHA: |
|
|
return vs.selAlpha; |
|
|
|
|
|
case SCI_SETWHITESPACEFORE: |
|
|
vs.whitespaceForegroundSet = wParam != 0; |
|
|
vs.whitespaceForeground.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETWHITESPACEBACK: |
|
|
vs.whitespaceBackgroundSet = wParam != 0; |
|
|
vs.whitespaceBackground.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETCARETFORE: |
|
|
vs.caretcolour.desired = ColourDesired(wParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETCARETFORE: |
|
|
return vs.caretcolour.desired.AsLong(); |
|
|
|
|
|
case SCI_SETCARETWIDTH: |
|
|
if (wParam <= 0) |
|
|
vs.caretWidth = 0; |
|
|
else if (wParam >= 3) |
|
|
vs.caretWidth = 3; |
|
|
else |
|
|
vs.caretWidth = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETCARETWIDTH: |
|
|
return vs.caretWidth; |
|
|
|
|
|
case SCI_ASSIGNCMDKEY: |
|
|
kmap.AssignCmdKey(Platform::LowShortFromLong(wParam), |
|
|
Platform::HighShortFromLong(wParam), lParam); |
|
|
break; |
|
|
|
|
|
case SCI_CLEARCMDKEY: |
|
|
kmap.AssignCmdKey(Platform::LowShortFromLong(wParam), |
|
|
Platform::HighShortFromLong(wParam), SCI_NULL); |
|
|
break; |
|
|
|
|
|
case SCI_CLEARALLCMDKEYS: |
|
|
kmap.Clear(); |
|
|
break; |
|
|
|
|
|
case SCI_INDICSETSTYLE: |
|
|
if (wParam <= INDIC_MAX) { |
|
|
vs.indicators[wParam].style = lParam; |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_INDICGETSTYLE: |
|
|
return (wParam <= INDIC_MAX) ? vs.indicators[wParam].style : 0; |
|
|
|
|
|
case SCI_INDICSETFORE: |
|
|
if (wParam <= INDIC_MAX) { |
|
|
vs.indicators[wParam].fore.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
} |
|
|
break; |
|
|
|
|
|
case SCI_INDICGETFORE: |
|
|
return (wParam <= INDIC_MAX) ? vs.indicators[wParam].fore.desired.AsLong() : 0; |
|
|
|
|
|
case SCI_LINEDOWN: |
|
|
case SCI_LINEDOWNEXTEND: |
|
|
case SCI_PARADOWN: |
|
|
case SCI_PARADOWNEXTEND: |
|
|
case SCI_LINEUP: |
|
|
case SCI_LINEUPEXTEND: |
|
|
case SCI_PARAUP: |
|
|
case SCI_PARAUPEXTEND: |
|
|
case SCI_CHARLEFT: |
|
|
case SCI_CHARLEFTEXTEND: |
|
|
case SCI_CHARRIGHT: |
|
|
case SCI_CHARRIGHTEXTEND: |
|
|
case SCI_WORDLEFT: |
|
|
case SCI_WORDLEFTEXTEND: |
|
|
case SCI_WORDRIGHT: |
|
|
case SCI_WORDRIGHTEXTEND: |
|
|
case SCI_WORDLEFTEND: |
|
|
case SCI_WORDLEFTENDEXTEND: |
|
|
case SCI_WORDRIGHTEND: |
|
|
case SCI_WORDRIGHTENDEXTEND: |
|
|
case SCI_HOME: |
|
|
case SCI_HOMEEXTEND: |
|
|
case SCI_LINEEND: |
|
|
case SCI_LINEENDEXTEND: |
|
|
case SCI_HOMEWRAP: |
|
|
case SCI_HOMEWRAPEXTEND: |
|
|
case SCI_LINEENDWRAP: |
|
|
case SCI_LINEENDWRAPEXTEND: |
|
|
case SCI_DOCUMENTSTART: |
|
|
case SCI_DOCUMENTSTARTEXTEND: |
|
|
case SCI_DOCUMENTEND: |
|
|
case SCI_DOCUMENTENDEXTEND: |
|
|
|
|
|
case SCI_STUTTEREDPAGEUP: |
|
|
case SCI_STUTTEREDPAGEUPEXTEND: |
|
|
case SCI_STUTTEREDPAGEDOWN: |
|
|
case SCI_STUTTEREDPAGEDOWNEXTEND: |
|
|
|
|
|
case SCI_PAGEUP: |
|
|
case SCI_PAGEUPEXTEND: |
|
|
case SCI_PAGEDOWN: |
|
|
case SCI_PAGEDOWNEXTEND: |
|
|
case SCI_EDITTOGGLEOVERTYPE: |
|
|
case SCI_CANCEL: |
|
|
case SCI_DELETEBACK: |
|
|
case SCI_TAB: |
|
|
case SCI_BACKTAB: |
|
|
case SCI_NEWLINE: |
|
|
case SCI_FORMFEED: |
|
|
case SCI_VCHOME: |
|
|
case SCI_VCHOMEEXTEND: |
|
|
case SCI_VCHOMEWRAP: |
|
|
case SCI_VCHOMEWRAPEXTEND: |
|
|
case SCI_ZOOMIN: |
|
|
case SCI_ZOOMOUT: |
|
|
case SCI_DELWORDLEFT: |
|
|
case SCI_DELWORDRIGHT: |
|
|
case SCI_DELLINELEFT: |
|
|
case SCI_DELLINERIGHT: |
|
|
case SCI_LINECOPY: |
|
|
case SCI_LINECUT: |
|
|
case SCI_LINEDELETE: |
|
|
case SCI_LINETRANSPOSE: |
|
|
case SCI_LINEDUPLICATE: |
|
|
case SCI_LOWERCASE: |
|
|
case SCI_UPPERCASE: |
|
|
case SCI_LINESCROLLDOWN: |
|
|
case SCI_LINESCROLLUP: |
|
|
case SCI_WORDPARTLEFT: |
|
|
case SCI_WORDPARTLEFTEXTEND: |
|
|
case SCI_WORDPARTRIGHT: |
|
|
case SCI_WORDPARTRIGHTEXTEND: |
|
|
case SCI_DELETEBACKNOTLINE: |
|
|
case SCI_HOMEDISPLAY: |
|
|
case SCI_HOMEDISPLAYEXTEND: |
|
|
case SCI_LINEENDDISPLAY: |
|
|
case SCI_LINEENDDISPLAYEXTEND: |
|
|
case SCI_LINEDOWNRECTEXTEND: |
|
|
case SCI_LINEUPRECTEXTEND: |
|
|
case SCI_CHARLEFTRECTEXTEND: |
|
|
case SCI_CHARRIGHTRECTEXTEND: |
|
|
case SCI_HOMERECTEXTEND: |
|
|
case SCI_VCHOMERECTEXTEND: |
|
|
case SCI_LINEENDRECTEXTEND: |
|
|
case SCI_PAGEUPRECTEXTEND: |
|
|
case SCI_PAGEDOWNRECTEXTEND: |
|
|
case SCI_SELECTIONDUPLICATE: |
|
|
return KeyCommand(iMessage); |
|
|
|
|
|
case SCI_BRACEHIGHLIGHT: |
|
|
SetBraceHighlight(static_cast<int>(wParam), lParam, STYLE_BRACELIGHT); |
|
|
break; |
|
|
|
|
|
case SCI_BRACEBADLIGHT: |
|
|
SetBraceHighlight(static_cast<int>(wParam), -1, STYLE_BRACEBAD); |
|
|
break; |
|
|
|
|
|
case SCI_BRACEMATCH: |
|
|
// wParam is position of char to find brace for, |
|
|
// lParam is maximum amount of text to restyle to find it |
|
|
return pdoc->BraceMatch(wParam, lParam); |
|
|
|
|
|
case SCI_GETVIEWEOL: |
|
|
return vs.viewEOL; |
|
|
|
|
|
case SCI_SETVIEWEOL: |
|
|
vs.viewEOL = wParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETZOOM: |
|
|
vs.zoomLevel = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
NotifyZoom(); |
|
|
break; |
|
|
|
|
|
case SCI_GETZOOM: |
|
|
return vs.zoomLevel; |
|
|
|
|
|
case SCI_GETEDGECOLUMN: |
|
|
return theEdge; |
|
|
|
|
|
case SCI_SETEDGECOLUMN: |
|
|
theEdge = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETEDGEMODE: |
|
|
return vs.edgeState; |
|
|
|
|
|
case SCI_SETEDGEMODE: |
|
|
vs.edgeState = wParam; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETEDGECOLOUR: |
|
|
return vs.edgecolour.desired.AsLong(); |
|
|
|
|
|
case SCI_SETEDGECOLOUR: |
|
|
vs.edgecolour.desired = ColourDesired(wParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_GETDOCPOINTER: |
|
|
return reinterpret_cast<sptr_t>(pdoc); |
|
|
|
|
|
case SCI_SETDOCPOINTER: |
|
|
CancelModes(); |
|
|
SetDocPointer(reinterpret_cast<Document *>(lParam)); |
|
|
return 0; |
|
|
|
|
|
case SCI_CREATEDOCUMENT: { |
|
|
Document *doc = new Document(); |
|
|
if (doc) { |
|
|
doc->AddRef(); |
|
|
} |
|
|
return reinterpret_cast<sptr_t>(doc); |
|
|
} |
|
|
|
|
|
case SCI_ADDREFDOCUMENT: |
|
|
(reinterpret_cast<Document *>(lParam))->AddRef(); |
|
|
break; |
|
|
|
|
|
case SCI_RELEASEDOCUMENT: |
|
|
(reinterpret_cast<Document *>(lParam))->Release(); |
|
|
break; |
|
|
|
|
|
case SCI_SETMODEVENTMASK: |
|
|
modEventMask = wParam; |
|
|
return 0; |
|
|
|
|
|
case SCI_GETMODEVENTMASK: |
|
|
return modEventMask; |
|
|
|
|
|
case SCI_CONVERTEOLS: |
|
|
pdoc->ConvertLineEnds(wParam); |
|
|
SetSelection(currentPos, anchor); // Ensure selection inside document |
|
|
return 0; |
|
|
|
|
|
case SCI_SETLENGTHFORENCODE: |
|
|
lengthForEncode = wParam; |
|
|
return 0; |
|
|
|
|
|
case SCI_SELECTIONISRECTANGLE: |
|
|
return selType == selRectangle ? 1 : 0; |
|
|
|
|
|
case SCI_SETSELECTIONMODE: { |
|
|
switch (wParam) { |
|
|
case SC_SEL_STREAM: |
|
|
moveExtendsSelection = !moveExtendsSelection || (selType != selStream); |
|
|
selType = selStream; |
|
|
break; |
|
|
case SC_SEL_RECTANGLE: |
|
|
moveExtendsSelection = !moveExtendsSelection || (selType != selRectangle); |
|
|
selType = selRectangle; |
|
|
break; |
|
|
case SC_SEL_LINES: |
|
|
moveExtendsSelection = !moveExtendsSelection || (selType != selLines); |
|
|
selType = selLines; |
|
|
break; |
|
|
default: |
|
|
moveExtendsSelection = !moveExtendsSelection || (selType != selStream); |
|
|
selType = selStream; |
|
|
} |
|
|
InvalidateSelection(currentPos, anchor); |
|
|
} |
|
|
case SCI_GETSELECTIONMODE: |
|
|
switch (selType) { |
|
|
case selStream: |
|
|
return SC_SEL_STREAM; |
|
|
case selRectangle: |
|
|
return SC_SEL_RECTANGLE; |
|
|
case selLines: |
|
|
return SC_SEL_LINES; |
|
|
default: // ?! |
|
|
return SC_SEL_STREAM; |
|
|
} |
|
|
case SCI_GETLINESELSTARTPOSITION: { |
|
|
SelectionLineIterator lineIterator(this); |
|
|
lineIterator.SetAt(wParam); |
|
|
return lineIterator.startPos; |
|
|
} |
|
|
case SCI_GETLINESELENDPOSITION: { |
|
|
SelectionLineIterator lineIterator(this); |
|
|
lineIterator.SetAt(wParam); |
|
|
return lineIterator.endPos; |
|
|
} |
|
|
|
|
|
case SCI_SETOVERTYPE: |
|
|
inOverstrike = wParam != 0; |
|
|
break; |
|
|
|
|
|
case SCI_GETOVERTYPE: |
|
|
return inOverstrike ? 1 : 0; |
|
|
|
|
|
case SCI_SETFOCUS: |
|
|
SetFocusState(wParam != 0); |
|
|
break; |
|
|
|
|
|
case SCI_GETFOCUS: |
|
|
return hasFocus; |
|
|
|
|
|
case SCI_SETSTATUS: |
|
|
errorStatus = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_GETSTATUS: |
|
|
return errorStatus; |
|
|
|
|
|
case SCI_SETMOUSEDOWNCAPTURES: |
|
|
mouseDownCaptures = wParam != 0; |
|
|
break; |
|
|
|
|
|
case SCI_GETMOUSEDOWNCAPTURES: |
|
|
return mouseDownCaptures; |
|
|
|
|
|
case SCI_SETCURSOR: |
|
|
cursorMode = wParam; |
|
|
DisplayCursor(Window::cursorText); |
|
|
break; |
|
|
|
|
|
case SCI_GETCURSOR: |
|
|
return cursorMode; |
|
|
|
|
|
case SCI_SETCONTROLCHARSYMBOL: |
|
|
controlCharSymbol = wParam; |
|
|
break; |
|
|
|
|
|
case SCI_GETCONTROLCHARSYMBOL: |
|
|
return controlCharSymbol; |
|
|
|
|
|
case SCI_STARTRECORD: |
|
|
recordingMacro = true; |
|
|
return 0; |
|
|
|
|
|
case SCI_STOPRECORD: |
|
|
recordingMacro = false; |
|
|
return 0; |
|
|
|
|
|
case SCI_MOVECARETINSIDEVIEW: |
|
|
MoveCaretInsideView(); |
|
|
break; |
|
|
|
|
|
case SCI_SETFOLDMARGINCOLOUR: |
|
|
vs.foldmarginColourSet = wParam != 0; |
|
|
vs.foldmarginColour.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETFOLDMARGINHICOLOUR: |
|
|
vs.foldmarginHighlightColourSet = wParam != 0; |
|
|
vs.foldmarginHighlightColour.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETHOTSPOTACTIVEFORE: |
|
|
vs.hotspotForegroundSet = wParam != 0; |
|
|
vs.hotspotForeground.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETHOTSPOTACTIVEBACK: |
|
|
vs.hotspotBackgroundSet = wParam != 0; |
|
|
vs.hotspotBackground.desired = ColourDesired(lParam); |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETHOTSPOTACTIVEUNDERLINE: |
|
|
vs.hotspotUnderline = wParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETHOTSPOTSINGLELINE: |
|
|
vs.hotspotSingleLine = wParam != 0; |
|
|
InvalidateStyleRedraw(); |
|
|
break; |
|
|
|
|
|
case SCI_SETPASTECONVERTENDINGS: |
|
|
convertPastes = wParam != 0; |
|
|
break; |
|
|
|
|
|
case SCI_GETPASTECONVERTENDINGS: |
|
|
return convertPastes ? 1 : 0; |
|
|
|
|
|
default: |
|
|
return DefWndProc(iMessage, wParam, lParam); |
|
|
} |
|
|
//Platform::DebugPrintf("end wnd proc\n"); |
|
|
return 0l; |
|
|
}
|
|
|
|