From 9ed64453369aa657d09c0a9bde5ed93bdce2429c Mon Sep 17 00:00:00 2001 From: Neil Date: Tue, 8 Jul 2014 16:35:34 +1000 Subject: Split parts of Editor class off into EditModel, MarginView, and EditView classes. --- src/Editor.cxx | 6691 +++++++++++++++++++++++++------------------------ src/Editor.h | 199 +- src/PositionCache.cxx | 25 +- src/PositionCache.h | 5 +- 4 files changed, 3518 insertions(+), 3402 deletions(-) (limited to 'src') diff --git a/src/Editor.cxx b/src/Editor.cxx index 89b61a222..360540a83 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -102,3791 +102,3873 @@ static inline bool IsAllSpacesOrTabs(char *s, unsigned int len) { return true; } -PrintParameters::PrintParameters() { - magnification = 0; - colourMode = SC_PRINT_NORMAL; - wrapState = eWrapWord; -} - -Editor::Editor() { - ctrlID = 0; - - stylesValid = false; - technology = SC_TECHNOLOGY_DEFAULT; - scaleRGBAImage = 100.0f; - - cursorMode = SC_CURSORNORMAL; - - hasFocus = false; - hideSelection = false; +EditModel::EditModel() { inOverstrike = false; - drawOverstrikeCaret = true; - 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 = ddNone; - dropWentOutside = false; - posDrag = SelectionPosition(invalidPosition); - posDrop = SelectionPosition(invalidPosition); - hotSpotClickPos = INVALID_POSITION; - selectionType = selChar; - - lastXChosen = 0; - lineAnchorPos = 0; - originalAnchorPos = 0; - wordSelectAnchorStartPos = 0; - wordSelectAnchorEndPos = 0; - wordSelectInitialCaretPos = -1; - - primarySelection = true; - - caretXPolicy = CARET_SLOP | CARET_EVEN; - caretXSlop = 50; - - caretYPolicy = CARET_EVEN; - caretYSlop = 0; - - visiblePolicy = 0; - visibleSlop = 0; - - searchAnchor = 0; - xOffset = 0; - xCaretMargin = 50; - horizontalScrollBarVisible = true; - scrollWidth = 2000; trackLineWidth = false; - lineWidthMaxSeen = 0; - verticalScrollBarVisible = true; - endAtLastLine = true; - caretSticky = SC_CARETSTICKY_OFF; - marginOptions = SC_MARGINOPTION_NONE; - mouseSelectionRectangularSwitch = false; - multipleSelection = false; - additionalSelectionTyping = false; - multiPasteMode = SC_MULTIPASTE_ONCE; - additionalCaretsBlink = true; - additionalCaretsVisible = true; - virtualSpaceOptions = SCVS_NONE; - - pixmapLine = 0; - pixmapSelMargin = 0; - pixmapSelPattern = 0; - pixmapSelPatternOffset1 = 0; - pixmapIndentGuide = 0; - pixmapIndentGuideHighlight = 0; - - targetStart = 0; - targetEnd = 0; - searchFlags = 0; - - topLine = 0; - posTopLine = 0; - - lengthForEncode = -1; - - needUpdateUI = 0; - ContainerNeedsUpdate(SC_UPDATE_CONTENT); + posDrag = SelectionPosition(invalidPosition); braces[0] = invalidPosition; braces[1] = invalidPosition; bracesMatchStyle = STYLE_BRACEBAD; highlightGuideColumn = 0; - - paintState = notPainting; - paintAbandonedByStyling = false; - paintingAllText = false; - willRedrawAll = false; - - modEventMask = SC_MODEVENTMASKALL; - - pdoc = new Document(); - pdoc->AddRef(); - pdoc->AddWatcher(this, 0); - - recordingMacro = false; + primarySelection = true; foldFlags = 0; - foldAutomatic = 0; - - wrapWidth = LineLayout::wrapWidthInfinite; - - convertPastes = true; - hotspot = Range(invalidPosition); - - llc.SetLevel(LineLayoutCache::llcCaret); - posCache.SetSize(0x400); - - SetRepresentations(); + wrapWidth = LineLayout::wrapWidthInfinite; + pdoc = new Document(); + pdoc->AddRef(); } -Editor::~Editor() { - pdoc->RemoveWatcher(this, 0); +EditModel::~EditModel() { pdoc->Release(); pdoc = 0; - DropGraphics(true); -} - -void Editor::Finalise() { - SetIdle(false); - CancelModes(); } -void Editor::SetRepresentations() { - reprs.Clear(); - - // C0 control set - 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" - }; - for (size_t j=0; j < ELEMENTS(reps); j++) { - char c[2] = { static_cast(j), 0 }; - reprs.SetRepresentation(c, reps[j]); - } - - // C1 control set - // As well as Unicode mode, ISO-8859-1 should use these - if (IsUnicodeMode()) { - const char *repsC1[] = { - "PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", - "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3", - "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", - "SOS", "SGCI", "SCI", "CSI", "ST", "OSC", "PM", "APC" - }; - for (size_t j=0; j < ELEMENTS(repsC1); j++) { - char c1[3] = { '\xc2', static_cast(0x80+j), 0 }; - reprs.SetRepresentation(c1, repsC1[j]); +bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) { + if (st.multipleStyles) { + for (size_t iStyle = 0; iStyle(k), 0 }; - char hexits[4]; - sprintf(hexits, "x%2X", k); - reprs.SetRepresentation(hiByte, hexits); - } +static int WidthStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, + const char *text, const unsigned char *styles, size_t len) { + int width = 0; + size_t start = 0; + while (start < len) { + size_t style = styles[start]; + size_t endSegment = start; + while ((endSegment + 1 < len) && (static_cast(styles[endSegment + 1]) == style)) + endSegment++; + FontAlias fontText = vs.styles[style + styleOffset].font; + width += static_cast(surface->WidthText(fontText, text + start, + static_cast(endSegment - start + 1))); + start = endSegment + 1; } + return width; } -void Editor::DropGraphics(bool freeObjects) { - if (freeObjects) { - delete pixmapLine; - pixmapLine = 0; - delete pixmapSelMargin; - pixmapSelMargin = 0; - delete pixmapSelPattern; - pixmapSelPattern = 0; - delete pixmapSelPatternOffset1; - pixmapSelPatternOffset1 = 0; - delete pixmapIndentGuide; - pixmapIndentGuide = 0; - delete pixmapIndentGuideHighlight; - pixmapIndentGuideHighlight = 0; - } else { - if (pixmapLine) - pixmapLine->Release(); - if (pixmapSelMargin) - pixmapSelMargin->Release(); - if (pixmapSelPattern) - pixmapSelPattern->Release(); - if (pixmapSelPatternOffset1) - pixmapSelPatternOffset1->Release(); - if (pixmapIndentGuide) - pixmapIndentGuide->Release(); - if (pixmapIndentGuideHighlight) - pixmapIndentGuideHighlight->Release(); +static int WidestLineWidth(Surface *surface, const ViewStyle &vs, int styleOffset, const StyledText &st) { + int widthMax = 0; + size_t start = 0; + while (start < st.length) { + size_t lenLine = st.LineLength(start); + int widthSubLine; + if (st.multipleStyles) { + widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine); + } else { + FontAlias fontText = vs.styles[styleOffset + st.style].font; + widthSubLine = static_cast(surface->WidthText(fontText, + st.text + start, static_cast(lenLine))); + } + if (widthSubLine > widthMax) + widthMax = widthSubLine; + start += lenLine + 1; } + return widthMax; } -void Editor::AllocateGraphics() { - if (!pixmapLine) - pixmapLine = Surface::Allocate(technology); - if (!pixmapSelMargin) - pixmapSelMargin = Surface::Allocate(technology); - if (!pixmapSelPattern) - pixmapSelPattern = Surface::Allocate(technology); - if (!pixmapSelPatternOffset1) - pixmapSelPatternOffset1 = Surface::Allocate(technology); - if (!pixmapIndentGuide) - pixmapIndentGuide = Surface::Allocate(technology); - if (!pixmapIndentGuideHighlight) - pixmapIndentGuideHighlight = Surface::Allocate(technology); +static void DrawTextInStyle(Surface *surface, PRectangle rcText, const Style &style, XYPOSITION ybase, const char *s, size_t length) { + FontAlias fontText = style.font; + surface->DrawTextNoClip(rcText, fontText, ybase, s, static_cast(length), + style.fore, style.back); } -void Editor::InvalidateStyleData() { - stylesValid = false; - vs.technology = technology; - DropGraphics(false); - AllocateGraphics(); - llc.Invalidate(LineLayout::llInvalid); - posCache.Clear(); -} +static void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText, + const StyledText &st, size_t start, size_t length) { -void Editor::InvalidateStyleRedraw() { - NeedWrapping(); - InvalidateStyleData(); - Redraw(); -} - -void Editor::RefreshStyleData() { - if (!stylesValid) { - stylesValid = true; - AutoSurface surface(this); - if (surface) { - vs.Refresh(*surface, pdoc->tabInChars); + if (st.multipleStyles) { + int x = static_cast(rcText.left); + size_t i = 0; + while (i < length) { + size_t end = i; + size_t style = st.styles[i + start]; + while (end < length - 1 && st.styles[start + end + 1] == style) + end++; + style += styleOffset; + FontAlias fontText = vs.styles[style].font; + const int width = static_cast(surface->WidthText(fontText, + st.text + start + i, static_cast(end - i + 1))); + PRectangle rcSegment = rcText; + rcSegment.left = static_cast(x); + rcSegment.right = static_cast(x + width + 1); + DrawTextInStyle(surface, rcSegment, vs.styles[style], rcText.top + vs.maxAscent, + st.text + start + i, end - i + 1); + x += width; + i = end + 1; } - SetScrollBars(); - SetRectangularRange(); + } else { + const size_t style = st.style + styleOffset; + DrawTextInStyle(surface, rcText, vs.styles[style], rcText.top + vs.maxAscent, + st.text + start, length); } } -Point Editor::GetVisibleOriginInMain() const { - return Point(0,0); -} +static void DrawWrapMarker(Surface *surface, PRectangle rcPlace, + bool isEndMarker, ColourDesired wrapColour) { + surface->PenColour(wrapColour); -Point Editor::DocumentPointFromView(Point ptView) const { - Point ptDocument = ptView; - if (wMargin.GetID()) { - Point ptOrigin = GetVisibleOriginInMain(); - ptDocument.x += ptOrigin.x; - ptDocument.y += ptOrigin.y; - } else { - ptDocument.x += xOffset; - ptDocument.y += topLine * vs.lineHeight; - } - return ptDocument; -} + enum { xa = 1 }; // gap before start + int w = static_cast(rcPlace.right - rcPlace.left) - xa - 1; -int Editor::TopLineOfMain() const { - if (wMargin.GetID()) - return 0; - else - return topLine; -} + bool xStraight = isEndMarker; // x-mirrored symbol for start marker -PRectangle Editor::GetClientRectangle() const { - Window &win = const_cast(wMain); - return win.GetClientPosition(); -} + int x0 = static_cast(xStraight ? rcPlace.left : rcPlace.right - 1); + int y0 = static_cast(rcPlace.top); -PRectangle Editor::GetClientDrawingRectangle() { - return GetClientRectangle(); -} + int dy = static_cast(rcPlace.bottom - rcPlace.top) / 5; + int y = static_cast(rcPlace.bottom - rcPlace.top) / 2 + dy; -PRectangle Editor::GetTextRectangle() const { - PRectangle rc = GetClientRectangle(); - rc.left += vs.textStart; - rc.right -= vs.rightMarginWidth; - return rc; -} + 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, 1 }; -int Editor::LinesOnScreen() const { - PRectangle rcClient = GetClientRectangle(); - int htClient = static_cast(rcClient.bottom - rcClient.top); - //Platform::DebugPrintf("lines on screen = %d\n", htClient / lineHeight + 1); - return htClient / vs.lineHeight; + // 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); } -int Editor::LinesToScroll() const { - int retVal = LinesOnScreen() - 1; - if (retVal < 1) - return 1; - else - return retVal; +MarginView::MarginView() { + pixmapSelMargin = 0; + pixmapSelPattern = 0; + pixmapSelPatternOffset1 = 0; } -int Editor::MaxScrollPos() const { - //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; +void MarginView::DropGraphics(bool freeObjects) { + if (freeObjects) { + delete pixmapSelMargin; + pixmapSelMargin = 0; + delete pixmapSelPattern; + pixmapSelPattern = 0; + delete pixmapSelPatternOffset1; + pixmapSelPatternOffset1 = 0; } else { - return retVal; + if (pixmapSelMargin) + pixmapSelMargin->Release(); + if (pixmapSelPattern) + pixmapSelPattern->Release(); + if (pixmapSelPatternOffset1) + pixmapSelPatternOffset1->Release(); } } -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 < ELEMENTS(reps)) { - return reps[ch]; - } else { - return "BAD"; - } +void MarginView::AllocateGraphics(const ViewStyle &vsDraw) { + if (!pixmapSelMargin) + pixmapSelMargin = Surface::Allocate(vsDraw.technology); + if (!pixmapSelPattern) + pixmapSelPattern = Surface::Allocate(vsDraw.technology); + if (!pixmapSelPatternOffset1) + pixmapSelPatternOffset1 = Surface::Allocate(vsDraw.technology); } -/** - * Convenience class to ensure LineLayout objects are always disposed. - */ -class AutoLineLayout { - LineLayoutCache &llc; - LineLayout *ll; - AutoLineLayout &operator=(const AutoLineLayout &); -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_; - } -}; +void MarginView::RefreshPixMaps(Surface *surfaceWindow, WindowID wid, const ViewStyle &vsDraw) { + if (!pixmapSelPattern->Initialised()) { + const int patternSize = 8; + pixmapSelPattern->InitPixMap(patternSize, patternSize, surfaceWindow, wid); + pixmapSelPatternOffset1->InitPixMap(patternSize, patternSize, surfaceWindow, wid); + // 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 = PRectangle::FromInts(0, 0, patternSize, patternSize); -SelectionPosition Editor::ClampPositionIntoDocument(SelectionPosition sp) const { - if (sp.Position() < 0) { - return SelectionPosition(0); - } else if (sp.Position() > pdoc->Length()) { - return SelectionPosition(pdoc->Length()); - } else { - // If not at end of line then set offset to 0 - if (!pdoc->IsLineEndPosition(sp.Position())) - sp.SetVirtualSpace(0); - return sp; - } -} + // Initialize default colours based on the chrome colour scheme. Typically the highlight is white. + ColourDesired colourFMFill = vsDraw.selbar; + ColourDesired colourFMStripes = vsDraw.selbarlight; -Point Editor::LocationFromPosition(SelectionPosition pos) { - Point pt; - RefreshStyleData(); - if (pos.Position() == INVALID_POSITION) - return pt; - const int line = pdoc->LineFromPosition(pos.Position()); - const int lineVisible = cs.DisplayFromDoc(line); - //Platform::DebugPrintf("line=%d\n", line); - AutoSurface surface(this); - AutoLineLayout ll(llc, RetrieveLineLayout(line)); - if (surface && ll) { - const int posLineStart = pdoc->LineStart(line); - LayoutLine(line, surface, vs, ll, wrapWidth); - const int posInLine = pos.Position() - posLineStart; - pt = ll->PointFromPosition(posInLine, vs.lineHeight); - pt.y += (lineVisible - topLine) * vs.lineHeight; - pt.x += vs.textStart - xOffset; + if (!(vsDraw.selbarlight == 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 = vsDraw.selbarlight; + } + + if (vsDraw.foldmarginColour.isSet) { + // override default fold margin colour + colourFMFill = vsDraw.foldmarginColour; + } + if (vsDraw.foldmarginHighlightColour.isSet) { + // override default fold margin highlight colour + colourFMStripes = vsDraw.foldmarginHighlightColour; + } + + pixmapSelPattern->FillRectangle(rcPattern, colourFMFill); + pixmapSelPatternOffset1->FillRectangle(rcPattern, colourFMStripes); + for (int y = 0; y < patternSize; y++) { + for (int x = y % 2; x < patternSize; x += 2) { + PRectangle rcPixel = PRectangle::FromInts(x, y, x + 1, y + 1); + pixmapSelPattern->FillRectangle(rcPixel, colourFMStripes); + pixmapSelPatternOffset1->FillRectangle(rcPixel, colourFMFill); + } + } } - pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth; - return pt; } -Point Editor::LocationFromPosition(int pos) { - return LocationFromPosition(SelectionPosition(pos)); +static int SubstituteMarkerIfEmpty(int markerCheck, int markerDefault, const ViewStyle &vs) { + if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) + return markerDefault; + return markerCheck; } -int Editor::XFromPosition(int pos) { - Point pt = LocationFromPosition(pos); - return static_cast(pt.x) - vs.textStart + xOffset; -} +void MarginView::PaintMargin(Surface *surface, int topLine, PRectangle rc, PRectangle rcMargin, + const EditModel &model, const ViewStyle &vs) { -int Editor::XFromPosition(SelectionPosition sp) { - Point pt = LocationFromPosition(sp); - return static_cast(pt.x) - vs.textStart + xOffset; -} + PRectangle rcSelMargin = rcMargin; + rcSelMargin.right = rcMargin.left; + if (rcSelMargin.bottom < rc.bottom) + rcSelMargin.bottom = rc.bottom; -int Editor::LineFromLocation(Point pt) const { - return cs.DocFromDisplay(static_cast(pt.y) / vs.lineHeight + topLine); -} + Point ptOrigin = model.GetVisibleOriginInMain(); + FontAlias fontLineNumber = vs.styles[STYLE_LINENUMBER].font; + for (int margin = 0; margin <= SC_MAX_MARGIN; margin++) { + if (vs.ms[margin].width > 0) { -void Editor::SetTopLine(int topLineNew) { - if ((topLine != topLineNew) && (topLineNew >= 0)) { - topLine = topLineNew; - ContainerNeedsUpdate(SC_UPDATE_V_SCROLL); - } - posTopLine = pdoc->LineStart(cs.DocFromDisplay(topLine)); -} + rcSelMargin.left = rcSelMargin.right; + rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; -SelectionPosition Editor::SPositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition, bool virtualSpace) { - RefreshStyleData(); - if (canReturnInvalid) { - PRectangle rcClient = GetTextRectangle(); - // May be in scroll view coordinates so translate back to main view - Point ptOrigin = GetVisibleOriginInMain(); - rcClient.Move(-ptOrigin.x, -ptOrigin.y); - if (!rcClient.Contains(pt)) - return SelectionPosition(INVALID_POSITION); - if (pt.x < vs.textStart) - return SelectionPosition(INVALID_POSITION); - if (pt.y < 0) - return SelectionPosition(INVALID_POSITION); - } - pt = DocumentPointFromView(pt); - pt.x = pt.x - vs.textStart; - int visibleLine = static_cast(floor(pt.y / vs.lineHeight)); - if (!canReturnInvalid && (visibleLine < 0)) - visibleLine = 0; - const int lineDoc = cs.DocFromDisplay(visibleLine); - if (canReturnInvalid && (lineDoc < 0)) - return SelectionPosition(INVALID_POSITION); - if (lineDoc >= pdoc->LinesTotal()) - return SelectionPosition(canReturnInvalid ? INVALID_POSITION : pdoc->Length()); - const int posLineStart = pdoc->LineStart(lineDoc); - AutoSurface surface(this); - AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc)); - if (surface && ll) { - LayoutLine(lineDoc, surface, vs, ll, wrapWidth); - const int lineStartSet = cs.DisplayFromDoc(lineDoc); - const int subLine = visibleLine - lineStartSet; - if (subLine < ll->lines) { - const Range rangeSubLine = ll->SubLineRange(subLine); - const XYPOSITION subLineStart = ll->positions[rangeSubLine.start]; - if (subLine > 0) // Wrapped - pt.x -= ll->wrapIndent; - const int positionInLine = ll->FindPositionFromX(pt.x + subLineStart, rangeSubLine, charPosition); - if (positionInLine < rangeSubLine.end) { - return SelectionPosition(pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1)); - } - if (virtualSpace) { - const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; - const int spaceOffset = static_cast( - (pt.x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); - return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); - } else if (canReturnInvalid) { - if (pt.x < (ll->positions[rangeSubLine.end] - subLineStart)) { - return SelectionPosition(pdoc->MovePositionOutsideChar(rangeSubLine.end + posLineStart, 1)); + if (vs.ms[margin].style != SC_MARGIN_NUMBER) { + if (vs.ms[margin].mask & SC_MASK_FOLDERS) { + // Required because of special way brush is created for selection margin + // Ensure patterns line up when scrolling with separate margin view + // by choosing correctly aligned variant. + bool invertPhase = static_cast(ptOrigin.y) & 1; + surface->FillRectangle(rcSelMargin, + invertPhase ? *pixmapSelPattern : *pixmapSelPatternOffset1); + } else { + ColourDesired colour; + switch (vs.ms[margin].style) { + case SC_MARGIN_BACK: + colour = vs.styles[STYLE_DEFAULT].back; + break; + case SC_MARGIN_FORE: + colour = vs.styles[STYLE_DEFAULT].fore; + break; + default: + colour = vs.styles[STYLE_LINENUMBER].back; + break; + } + surface->FillRectangle(rcSelMargin, colour); } } else { - return SelectionPosition(rangeSubLine.end + posLineStart); + surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back); + } + + const int lineStartPaint = static_cast(rcMargin.top + ptOrigin.y) / vs.lineHeight; + int visibleLine = model.TopLineOfMain() + lineStartPaint; + int yposScreen = lineStartPaint * vs.lineHeight - static_cast(ptOrigin.y); + // 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; + if (vs.ms[margin].mask & SC_MASK_FOLDERS) { + int level = model.pdoc->GetLevel(model.cs.DocFromDisplay(visibleLine)); + if (level & SC_FOLDLEVELWHITEFLAG) { + int lineBack = model.cs.DocFromDisplay(visibleLine); + int levelPrev = level; + while ((lineBack > 0) && (levelPrev & SC_FOLDLEVELWHITEFLAG)) { + lineBack--; + levelPrev = model.pdoc->GetLevel(lineBack); + } + if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) { + if ((level & SC_FOLDLEVELNUMBERMASK) < (levelPrev & SC_FOLDLEVELNUMBERMASK)) + needWhiteClosure = true; + } + } + if (highlightDelimiter.isEnabled) { + int lastLine = model.cs.DocFromDisplay(topLine + model.LinesOnScreen()) + 1; + model.pdoc->GetHighlightDelimiters(highlightDelimiter, model.pdoc->LineFromPosition(model.sel.MainCaret()), lastLine); + } + } + + // Old code does not know about new markers needed to distinguish all cases + const int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID, + SC_MARKNUM_FOLDEROPEN, vs); + const int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND, + SC_MARKNUM_FOLDER, vs); + + while ((visibleLine < model.cs.LinesDisplayed()) && yposScreen < rc.bottom) { + + PLATFORM_ASSERT(visibleLine < model.cs.LinesDisplayed()); + const int lineDoc = model.cs.DocFromDisplay(visibleLine); + PLATFORM_ASSERT(model.cs.GetVisible(lineDoc)); + const bool firstSubLine = visibleLine == model.cs.DisplayFromDoc(lineDoc); + const bool lastSubLine = visibleLine == model.cs.DisplayLastFromDoc(lineDoc); + + int marks = model.pdoc->GetMark(lineDoc); + if (!firstSubLine) + marks = 0; + + bool headWithTail = false; + + if (vs.ms[margin].mask & SC_MASK_FOLDERS) { + // Decide which fold indicator should be displayed + const int level = model.pdoc->GetLevel(lineDoc); + const int levelNext = model.pdoc->GetLevel(lineDoc + 1); + const int levelNum = level & SC_FOLDLEVELNUMBERMASK; + const int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; + if (level & SC_FOLDLEVELHEADERFLAG) { + if (firstSubLine) { + if (levelNum < levelNextNum) { + if (model.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 if (levelNum > SC_FOLDLEVELBASE) { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } + } else { + if (levelNum < levelNextNum) { + if (model.cs.GetExpanded(lineDoc)) { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } else if (levelNum > SC_FOLDLEVELBASE) { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } + } else if (levelNum > SC_FOLDLEVELBASE) { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } + } + needWhiteClosure = false; + const int firstFollowupLine = model.cs.DocFromDisplay(model.cs.DisplayFromDoc(lineDoc + 1)); + const int firstFollowupLineLevel = model.pdoc->GetLevel(firstFollowupLine); + const int secondFollowupLineLevelNum = model.pdoc->GetLevel(firstFollowupLine + 1) & SC_FOLDLEVELNUMBERMASK; + if (!model.cs.GetExpanded(lineDoc)) { + if ((firstFollowupLineLevel & SC_FOLDLEVELWHITEFLAG) && + (levelNum > secondFollowupLineLevelNum)) + needWhiteClosure = true; + + if (highlightDelimiter.IsFoldBlockHighlighted(firstFollowupLine)) + headWithTail = true; + } + } else if (level & SC_FOLDLEVELWHITEFLAG) { + if (needWhiteClosure) { + if (levelNext & SC_FOLDLEVELWHITEFLAG) { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } else if (levelNextNum > 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 (lastSubLine) { + if (levelNextNum > SC_FOLDLEVELBASE) { + marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; + } else { + marks |= 1 << SC_MARKNUM_FOLDERTAIL; + } + } else { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } + } else { + marks |= 1 << SC_MARKNUM_FOLDERSUB; + } + } + } + + marks &= vs.ms[margin].mask; + + PRectangle rcMarker = rcSelMargin; + rcMarker.top = static_cast(yposScreen); + rcMarker.bottom = static_cast(yposScreen + vs.lineHeight); + if (vs.ms[margin].style == SC_MARGIN_NUMBER) { + if (firstSubLine) { + char number[100] = ""; + if (lineDoc >= 0) + sprintf(number, "%d", lineDoc + 1); + if (model.foldFlags & (SC_FOLDFLAG_LEVELNUMBERS | SC_FOLDFLAG_LINESTATE)) { + if (model.foldFlags & SC_FOLDFLAG_LEVELNUMBERS) { + int lev = model.pdoc->GetLevel(lineDoc); + sprintf(number, "%c%c %03X %03X", + (lev & SC_FOLDLEVELHEADERFLAG) ? 'H' : '_', + (lev & SC_FOLDLEVELWHITEFLAG) ? 'W' : '_', + lev & SC_FOLDLEVELNUMBERMASK, + lev >> 16 + ); + } else { + int state = model.pdoc->GetLineState(lineDoc); + sprintf(number, "%0X", state); + } + } + PRectangle rcNumber = rcMarker; + // Right justify + XYPOSITION width = surface->WidthText(fontLineNumber, number, static_cast(strlen(number))); + XYPOSITION xpos = rcNumber.right - width - vs.marginNumberPadding; + rcNumber.left = xpos; + DrawTextInStyle(surface, rcNumber, vs.styles[STYLE_LINENUMBER], + rcNumber.top + vs.maxAscent, number, strlen(number)); + } else if (vs.wrapVisualFlags & SC_WRAPVISUALFLAG_MARGIN) { + PRectangle rcWrapMarker = rcMarker; + rcWrapMarker.right -= 3; + rcWrapMarker.left = rcWrapMarker.right - vs.styles[STYLE_LINENUMBER].aveCharWidth; + DrawWrapMarker(surface, rcWrapMarker, false, vs.styles[STYLE_LINENUMBER].fore); + } + } else if (vs.ms[margin].style == SC_MARGIN_TEXT || vs.ms[margin].style == SC_MARGIN_RTEXT) { + if (firstSubLine) { + const StyledText stMargin = model.pdoc->MarginStyledText(lineDoc); + if (stMargin.text && ValidStyledText(vs, vs.marginStyleOffset, stMargin)) { + surface->FillRectangle(rcMarker, + vs.styles[stMargin.StyleAt(0) + vs.marginStyleOffset].back); + if (vs.ms[margin].style == SC_MARGIN_RTEXT) { + int width = WidestLineWidth(surface, vs, vs.marginStyleOffset, stMargin); + rcMarker.left = rcMarker.right - width - 3; + } + DrawStyledText(surface, vs, vs.marginStyleOffset, rcMarker, + stMargin, 0, stMargin.length); + } + } + } + + if (marks) { + for (int markBit = 0; (markBit < 32) && marks; markBit++) { + if (marks & 1) { + LineMarker::typeOfFold tFold = LineMarker::undefined; + if ((vs.ms[margin].mask & SC_MASK_FOLDERS) && highlightDelimiter.IsFoldBlockHighlighted(lineDoc)) { + if (highlightDelimiter.IsBodyOfFoldBlock(lineDoc)) { + tFold = LineMarker::body; + } else if (highlightDelimiter.IsHeadOfFoldBlock(lineDoc)) { + if (firstSubLine) { + tFold = headWithTail ? LineMarker::headWithTail : LineMarker::head; + } else { + if (model.cs.GetExpanded(lineDoc) || headWithTail) { + tFold = LineMarker::body; + } else { + tFold = LineMarker::undefined; + } + } + } else if (highlightDelimiter.IsTailOfFoldBlock(lineDoc)) { + tFold = LineMarker::tail; + } + } + vs.markers[markBit].Draw(surface, rcMarker, fontLineNumber, tFold, vs.ms[margin].style); + } + marks >>= 1; + } + } + + visibleLine++; + yposScreen += vs.lineHeight; } } - if (!canReturnInvalid) - return SelectionPosition(ll->numCharsInLine + posLineStart); } - return SelectionPosition(canReturnInvalid ? INVALID_POSITION : posLineStart); -} -int Editor::PositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition) { - return SPositionFromLocation(pt, canReturnInvalid, charPosition, false).Position(); + PRectangle rcBlankMargin = rcMargin; + rcBlankMargin.left = rcSelMargin.right; + surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back); } /** - * 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. - * This method is used for rectangular selections and does not work on wrapped lines. - */ -SelectionPosition Editor::SPositionFromLineX(int lineDoc, int x) { - RefreshStyleData(); - if (lineDoc >= pdoc->LinesTotal()) - return SelectionPosition(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)); - if (surface && ll) { - const int posLineStart = pdoc->LineStart(lineDoc); - LayoutLine(lineDoc, surface, vs, ll, wrapWidth); - const Range rangeSubLine = ll->SubLineRange(0); - const XYPOSITION subLineStart = ll->positions[rangeSubLine.start]; - const int positionInLine = ll->FindPositionFromX(x + subLineStart, rangeSubLine, false); - if (positionInLine < rangeSubLine.end) { - return SelectionPosition(pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1)); - } - const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; - const int spaceOffset = static_cast( - (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); - return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); +* Convenience class to ensure LineLayout objects are always disposed. +*/ +class AutoLineLayout { + LineLayoutCache &llc; + LineLayout *ll; + AutoLineLayout &operator=(const AutoLineLayout &); +public: + AutoLineLayout(LineLayoutCache &llc_, LineLayout *ll_) : llc(llc_), ll(ll_) {} + ~AutoLineLayout() { + llc.Dispose(ll); + ll = 0; } - return SelectionPosition(0); + LineLayout *operator->() const { + return ll; + } + operator LineLayout *() const { + return ll; + } + void Set(LineLayout *ll_) { + llc.Dispose(ll); + ll = ll_; + } +}; + +PrintParameters::PrintParameters() { + magnification = 0; + colourMode = SC_PRINT_NORMAL; + wrapState = eWrapWord; } -int Editor::PositionFromLineX(int lineDoc, int x) { - return SPositionFromLineX(lineDoc, x).Position(); +const XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues + +EditView::EditView() { + hideSelection = false; + drawOverstrikeCaret = true; + bufferedDraw = true; + twoPhaseDraw = true; + lineWidthMaxSeen = 0; + additionalCaretsBlink = true; + additionalCaretsVisible = true; + pixmapLine = 0; + pixmapIndentGuide = 0; + pixmapIndentGuideHighlight = 0; + llc.SetLevel(LineLayoutCache::llcCaret); + posCache.SetSize(0x400); } -/** - * 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; +void EditView::DropGraphics(bool freeObjects) { + if (freeObjects) { + delete pixmapLine; + pixmapLine = 0; + delete pixmapIndentGuide; + pixmapIndentGuide = 0; + delete pixmapIndentGuideHighlight; + pixmapIndentGuideHighlight = 0; + } else { + if (pixmapLine) + pixmapLine->Release(); + if (pixmapIndentGuide) + pixmapIndentGuide->Release(); + if (pixmapIndentGuideHighlight) + pixmapIndentGuideHighlight->Release(); } - 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; +void EditView::AllocateGraphics(const ViewStyle &vsDraw) { + if (!pixmapLine) + pixmapLine = Surface::Allocate(vsDraw.technology); + if (!pixmapIndentGuide) + pixmapIndentGuide = Surface::Allocate(vsDraw.technology); + if (!pixmapIndentGuideHighlight) + pixmapIndentGuideHighlight = Surface::Allocate(vsDraw.technology); +} - if ((rc.bottom > rc.top) && (rc.right > rc.left)) { - wMain.InvalidateRectangle(rc); +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 < ELEMENTS(reps)) { + return reps[ch]; + } else { + return "BAD"; } } -void Editor::DiscardOverdraw() { - // Overridden on platforms that may draw outside visible area. +void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { + int ydiff = static_cast(rcTab.bottom - rcTab.top) / 2; + int xhead = static_cast(rcTab.right) - 1 - ydiff; + if (xhead <= rcTab.left) { + ydiff -= static_cast(rcTab.left) - xhead - 1; + xhead = static_cast(rcTab.left) - 1; + } + if ((rcTab.left + 2) < (rcTab.right - 1)) + surface->MoveTo(static_cast(rcTab.left) + 2, ymid); + else + surface->MoveTo(static_cast(rcTab.right) - 1, ymid); + surface->LineTo(static_cast(rcTab.right) - 1, ymid); + surface->LineTo(xhead, ymid - ydiff); + surface->MoveTo(static_cast(rcTab.right) - 1, ymid); + surface->LineTo(xhead, ymid + ydiff); } -void Editor::Redraw() { - //Platform::DebugPrintf("Redraw all\n"); - PRectangle rcClient = GetClientRectangle(); - wMain.InvalidateRectangle(rcClient); - if (wMargin.GetID()) - wMargin.InvalidateAll(); - //wMain.InvalidateAll(); +void EditView::RefreshPixMaps(Surface *surfaceWindow, WindowID wid, const ViewStyle &vsDraw) { + if (!pixmapIndentGuide->Initialised()) { + // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line + pixmapIndentGuide->InitPixMap(1, vsDraw.lineHeight + 1, surfaceWindow, wid); + pixmapIndentGuideHighlight->InitPixMap(1, vsDraw.lineHeight + 1, surfaceWindow, wid); + PRectangle rcIG = PRectangle::FromInts(0, 0, 1, vsDraw.lineHeight); + pixmapIndentGuide->FillRectangle(rcIG, vsDraw.styles[STYLE_INDENTGUIDE].back); + pixmapIndentGuide->PenColour(vsDraw.styles[STYLE_INDENTGUIDE].fore); + pixmapIndentGuideHighlight->FillRectangle(rcIG, vsDraw.styles[STYLE_BRACELIGHT].back); + pixmapIndentGuideHighlight->PenColour(vsDraw.styles[STYLE_BRACELIGHT].fore); + for (int stripe = 1; stripe < vsDraw.lineHeight + 1; stripe += 2) { + PRectangle rcPixel = PRectangle::FromInts(0, stripe, 1, stripe + 1); + pixmapIndentGuide->FillRectangle(rcPixel, vsDraw.styles[STYLE_INDENTGUIDE].fore); + pixmapIndentGuideHighlight->FillRectangle(rcPixel, vsDraw.styles[STYLE_BRACELIGHT].fore); + } + } } -void Editor::RedrawSelMargin(int line, bool allAfter) { - bool abandonDraw = false; - if (!wMargin.GetID()) // Margin in main window so may need to abandon and retry - abandonDraw = AbandonPaint(); - if (!abandonDraw) { - if (vs.maskInLine) { - Redraw(); - } else { - PRectangle rcSelMargin = GetClientRectangle(); - rcSelMargin.right = rcSelMargin.left + vs.fixedColumnWidth; - if (line != -1) { - PRectangle rcLine = RectangleFromRange(Range(pdoc->LineStart(line))); +LineLayout *EditView::RetrieveLineLayout(int lineNumber, const EditModel &model) { + int posLineStart = model.pdoc->LineStart(lineNumber); + int posLineEnd = model.pdoc->LineStart(lineNumber + 1); + PLATFORM_ASSERT(posLineEnd >= posLineStart); + int lineCaret = model.pdoc->LineFromPosition(model.sel.MainCaret()); + return llc.Retrieve(lineNumber, lineCaret, + posLineEnd - posLineStart, model.pdoc->GetStyleClock(), + model.LinesOnScreen() + 1, model.pdoc->LinesTotal()); +} - // Inflate line rectangle if there are image markers with height larger than line height - if (vs.largestMarkerHeight > vs.lineHeight) { - int delta = (vs.largestMarkerHeight - vs.lineHeight + 1) / 2; - rcLine.top -= delta; - rcLine.bottom += delta; - if (rcLine.top < rcSelMargin.top) - rcLine.top = rcSelMargin.top; - if (rcLine.bottom > rcSelMargin.bottom) - rcLine.bottom = rcSelMargin.bottom; - } +/** +* 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 EditView::LayoutLine(int line, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, const EditModel &model, int width) { + if (!ll) + return; - rcSelMargin.top = rcLine.top; - if (!allAfter) - rcSelMargin.bottom = rcLine.bottom; - if (rcSelMargin.Empty()) - return; + PLATFORM_ASSERT(line < model.pdoc->LinesTotal()); + PLATFORM_ASSERT(ll->chars != NULL); + int posLineStart = model.pdoc->LineStart(line); + int posLineEnd = model.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) { + lineLength = model.pdoc->LineEnd(line) - posLineStart; + } + if (lineLength == ll->numCharsInLine) { + // See if chars, styles, indicators, are all the same + bool allSame = true; + // Check base line layout + char styleByte = 0; + int numCharsInLine = 0; + while (numCharsInLine < lineLength) { + int charInDoc = numCharsInLine + posLineStart; + char chDoc = model.pdoc->CharAt(charInDoc); + styleByte = model.pdoc->StyleAt(charInDoc); + allSame = allSame && + (ll->styles[numCharsInLine] == static_cast(styleByte)); + 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(tolower(chDoc))); + else // Style::caseUpper + allSame = allSame && + (ll->chars[numCharsInLine] == static_cast(toupper(chDoc))); + numCharsInLine++; } - if (wMargin.GetID()) { - Point ptOrigin = GetVisibleOriginInMain(); - rcSelMargin.Move(-ptOrigin.x, -ptOrigin.y); - wMargin.InvalidateRectangle(rcSelMargin); + allSame = allSame && (ll->styles[numCharsInLine] == styleByte); // For eolFilled + if (allSame) { + ll->validity = LineLayout::llPositions; } else { - wMain.InvalidateRectangle(rcSelMargin); + ll->validity = LineLayout::llInvalid; } + } else { + ll->validity = LineLayout::llInvalid; } } -} + if (ll->validity == LineLayout::llInvalid) { + ll->widthLine = LineLayout::wrapWidthInfinite; + ll->lines = 1; + if (vstyle.edgeState == EDGE_BACKGROUND) { + ll->edgeColumn = model.pdoc->FindColumn(line, vstyle.theEdge); + if (ll->edgeColumn >= posLineStart) { + ll->edgeColumn -= posLineStart; + } + } else { + ll->edgeColumn = -1; + } -PRectangle Editor::RectangleFromRange(Range r) { - const int minLine = cs.DisplayFromDoc(pdoc->LineFromPosition(r.First())); - const int maxLine = cs.DisplayLastFromDoc(pdoc->LineFromPosition(r.Last())); - const PRectangle rcClientDrawing = GetClientDrawingRectangle(); - PRectangle rc; - const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; - rc.left = static_cast(vs.textStart - leftTextOverlap); - rc.top = static_cast((minLine - TopLineOfMain()) * vs.lineHeight); - if (rc.top < rcClientDrawing.top) - rc.top = rcClientDrawing.top; - // Extend to right of prepared area if any to prevent artifacts from caret line highlight - rc.right = rcClientDrawing.right; - rc.bottom = static_cast((maxLine - TopLineOfMain() + 1) * vs.lineHeight); + // Fill base line layout + const int lineLength = posLineEnd - posLineStart; + model.pdoc->GetCharRange(ll->chars, posLineStart, lineLength); + model.pdoc->GetStyleRange(ll->styles, posLineStart, lineLength); + int numCharsBeforeEOL = model.pdoc->LineEnd(line) - posLineStart; + const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL; + for (int styleInLine = 0; styleInLine < numCharsInLine; styleInLine++) { + const unsigned char styleByte = ll->styles[styleInLine]; + ll->styles[styleInLine] = styleByte; + } + const unsigned char styleByteLast = (lineLength > 0) ? ll->styles[lineLength - 1] : 0; + if (vstyle.someStylesForceCase) { + for (int charInLine = 0; charInLinechars[charInLine]; + if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseUpper) + ll->chars[charInLine] = static_cast(toupper(chDoc)); + else if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseLower) + ll->chars[charInLine] = static_cast(tolower(chDoc)); + } + } + 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] = styleByteLast; // For eolFilled - return rc; -} + // Layout the line, determining the position of each character, + // with an extra element at the end for the end of the line. + ll->positions[0] = 0; + bool lastSegItalics = false; -void Editor::InvalidateRange(int start, int end) { - RedrawRect(RectangleFromRange(Range(start, end))); -} + BreakFinder bfLayout(ll, NULL, Range(0, numCharsInLine), posLineStart, 0, false, model.pdoc, &model.reprs); + while (bfLayout.More()) { -int Editor::CurrentPosition() const { - return sel.MainCaret(); -} + const TextSegment ts = bfLayout.Next(); -bool Editor::SelectionEmpty() const { - return sel.Empty(); -} + std::fill(&ll->positions[ts.start + 1], &ll->positions[ts.end() + 1], 0.0f); + if (vstyle.styles[ll->styles[ts.start]].visible) { + if (ts.representation) { + XYPOSITION representationWidth = vstyle.controlCharWidth; + if (ll->chars[ts.start] == '\t') { + // Tab is a special case of representation, taking a variable amount of space + representationWidth = + ((static_cast((ll->positions[ts.start] + 2) / vstyle.tabWidth) + 1) * vstyle.tabWidth) - ll->positions[ts.start]; + } else { + if (representationWidth <= 0.0) { + XYPOSITION positionsRepr[256]; // Should expand when needed + posCache.MeasureWidths(surface, vstyle, STYLE_CONTROLCHAR, ts.representation->stringRep.c_str(), + static_cast(ts.representation->stringRep.length()), positionsRepr, model.pdoc); + representationWidth = positionsRepr[ts.representation->stringRep.length() - 1] + vstyle.ctrlCharPadding; + } + } + for (int ii = 0; ii < ts.length; ii++) + ll->positions[ts.start + 1 + ii] = representationWidth; + } else { + if ((ts.length == 1) && (' ' == ll->chars[ts.start])) { + // Over half the segments are single characters and of these about half are space characters. + ll->positions[ts.start + 1] = vstyle.styles[ll->styles[ts.start]].spaceWidth; + } else { + posCache.MeasureWidths(surface, vstyle, ll->styles[ts.start], ll->chars + ts.start, + ts.length, ll->positions + ts.start + 1, model.pdoc); + } + } + lastSegItalics = (!ts.representation) && ((ll->chars[ts.end() - 1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic); + } -SelectionPosition Editor::SelectionStart() { - return sel.RangeMain().Start(); + for (int posToIncrease = ts.start + 1; posToIncrease <= ts.end(); posToIncrease++) { + ll->positions[posToIncrease] += ll->positions[ts.start]; + } + } + + // Small hack to make lines that end with italics not cut off the edge of the last character + if (lastSegItalics) { + ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset; + } + ll->numCharsInLine = numCharsInLine; + ll->numCharsBeforeEOL = numCharsBeforeEOL; + 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 (vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { + width -= static_cast(vstyle.aveCharWidth); // take into account the space for end wrap mark + } + XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line + if (vstyle.wrapIndentMode == SC_WRAPINDENT_INDENT) { + wrapAddIndent = model.pdoc->IndentSize() * vstyle.spaceWidth; + } else if (vstyle.wrapIndentMode == SC_WRAPINDENT_FIXED) { + wrapAddIndent = vstyle.wrapVisualStartIndent * vstyle.aveCharWidth; + } + ll->wrapIndent = wrapAddIndent; + if (vstyle.wrapIndentMode != SC_WRAPINDENT_FIXED) + for (int i = 0; i < ll->numCharsInLine; i++) { + if (!IsSpaceOrTab(ll->chars[i])) { + ll->wrapIndent += ll->positions[i]; // Add line indent + break; + } + } + // Check for text width minimum + if (ll->wrapIndent > width - static_cast(vstyle.aveCharWidth) * 15) + ll->wrapIndent = wrapAddIndent; + // Check for wrapIndent minimum + if ((vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (ll->wrapIndent < vstyle.aveCharWidth)) + ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual + ll->lines = 0; + // Calculate line start positions based upon width. + int lastGoodBreak = 0; + int lastLineStart = 0; + XYACCUMULATOR 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 = model.pdoc->MovePositionOutsideChar(p + posLineStart, -1) + - posLineStart; + } + if (lastGoodBreak == lastLineStart) { + // Ensure at least one character on line. + lastGoodBreak = model.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 -= ll->wrapIndent; + p = lastGoodBreak + 1; + continue; + } + if (p > 0) { + if (vstyle.wrapState == eWrapChar) { + lastGoodBreak = model.pdoc->MovePositionOutsideChar(p + posLineStart, -1) + - posLineStart; + p = model.pdoc->MovePositionOutsideChar(p + 1 + posLineStart, 1) - posLineStart; + continue; + } else if ((vstyle.wrapState == eWrapWord) && (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; + } } -SelectionPosition Editor::SelectionEnd() { - return sel.RangeMain().End(); +Point EditView::LocationFromPosition(Surface *surface, SelectionPosition pos, int topLine, const EditModel &model, const ViewStyle &vs) { + Point pt; + if (pos.Position() == INVALID_POSITION) + return pt; + const int line = model.pdoc->LineFromPosition(pos.Position()); + const int lineVisible = model.cs.DisplayFromDoc(line); + //Platform::DebugPrintf("line=%d\n", line); + AutoLineLayout ll(llc, RetrieveLineLayout(line, model)); + if (surface && ll) { + const int posLineStart = model.pdoc->LineStart(line); + LayoutLine(line, surface, vs, ll, model, model.wrapWidth); + const int posInLine = pos.Position() - posLineStart; + pt = ll->PointFromPosition(posInLine, vs.lineHeight); + pt.y += (lineVisible - topLine) * vs.lineHeight; + pt.x += vs.textStart - model.xOffset; + } + pt.x += pos.VirtualSpace() * vs.styles[ll->EndLineStyle()].spaceWidth; + return pt; } -void Editor::SetRectangularRange() { - if (sel.IsRectangular()) { - int xAnchor = XFromPosition(sel.Rectangular().anchor); - int xCaret = XFromPosition(sel.Rectangular().caret); - if (sel.selType == Selection::selThin) { - xCaret = xAnchor; - } - int lineAnchorRect = pdoc->LineFromPosition(sel.Rectangular().anchor.Position()); - int lineCaret = pdoc->LineFromPosition(sel.Rectangular().caret.Position()); - int increment = (lineCaret > lineAnchorRect) ? 1 : -1; - for (int line=lineAnchorRect; line != lineCaret+increment; line += increment) { - SelectionRange range(SPositionFromLineX(line, xCaret), SPositionFromLineX(line, xAnchor)); - if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) == 0) - range.ClearVirtualSpace(); - if (line == lineAnchorRect) - sel.SetSelection(range); - else - sel.AddSelectionWithoutTrim(range); +SelectionPosition EditView::SPositionFromLocation(Surface *surface, Point pt, bool canReturnInvalid, bool charPosition, bool virtualSpace, const EditModel &model, const ViewStyle &vs) { + pt.x = pt.x - vs.textStart; + int visibleLine = static_cast(floor(pt.y / vs.lineHeight)); + if (!canReturnInvalid && (visibleLine < 0)) + visibleLine = 0; + const int lineDoc = model.cs.DocFromDisplay(visibleLine); + if (canReturnInvalid && (lineDoc < 0)) + return SelectionPosition(INVALID_POSITION); + if (lineDoc >= model.pdoc->LinesTotal()) + return SelectionPosition(canReturnInvalid ? INVALID_POSITION : model.pdoc->Length()); + const int posLineStart = model.pdoc->LineStart(lineDoc); + AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); + if (surface && ll) { + LayoutLine(lineDoc, surface, vs, ll, model, model.wrapWidth); + const int lineStartSet = model.cs.DisplayFromDoc(lineDoc); + const int subLine = visibleLine - lineStartSet; + if (subLine < ll->lines) { + const Range rangeSubLine = ll->SubLineRange(subLine); + const XYPOSITION subLineStart = ll->positions[rangeSubLine.start]; + if (subLine > 0) // Wrapped + pt.x -= ll->wrapIndent; + const int positionInLine = ll->FindPositionFromX(pt.x + subLineStart, rangeSubLine, charPosition); + if (positionInLine < rangeSubLine.end) { + return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1)); + } + if (virtualSpace) { + const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; + const int spaceOffset = static_cast( + (pt.x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); + return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); + } else if (canReturnInvalid) { + if (pt.x < (ll->positions[rangeSubLine.end] - subLineStart)) { + return SelectionPosition(model.pdoc->MovePositionOutsideChar(rangeSubLine.end + posLineStart, 1)); + } + } else { + return SelectionPosition(rangeSubLine.end + posLineStart); + } } + if (!canReturnInvalid) + return SelectionPosition(ll->numCharsInLine + posLineStart); } + return SelectionPosition(canReturnInvalid ? INVALID_POSITION : posLineStart); } -void Editor::ThinRectangularRange() { - if (sel.IsRectangular()) { - sel.selType = Selection::selThin; - if (sel.Rectangular().caret < sel.Rectangular().anchor) { - sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).caret, sel.Range(0).anchor); - } else { - sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).anchor, sel.Range(0).caret); +/** +* 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. +* This method is used for rectangular selections and does not work on wrapped lines. +*/ +SelectionPosition EditView::SPositionFromLineX(Surface *surface, int lineDoc, int x, const EditModel &model, const ViewStyle &vs) { + AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); + if (surface && ll) { + const int posLineStart = model.pdoc->LineStart(lineDoc); + LayoutLine(lineDoc, surface, vs, ll, model, model.wrapWidth); + const Range rangeSubLine = ll->SubLineRange(0); + const XYPOSITION subLineStart = ll->positions[rangeSubLine.start]; + const int positionInLine = ll->FindPositionFromX(x + subLineStart, rangeSubLine, false); + if (positionInLine < rangeSubLine.end) { + return SelectionPosition(model.pdoc->MovePositionOutsideChar(positionInLine + posLineStart, 1)); } - SetRectangularRange(); + const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; + const int spaceOffset = static_cast( + (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); + return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); } + return SelectionPosition(0); } -void Editor::InvalidateSelection(SelectionRange newMain, bool invalidateWholeSelection) { - if (sel.Count() > 1 || !(sel.RangeMain().anchor == newMain.anchor) || sel.IsRectangular()) { - invalidateWholeSelection = true; - } - int firstAffected = Platform::Minimum(sel.RangeMain().Start().Position(), newMain.Start().Position()); - // +1 for lastAffected ensures caret repainted - int lastAffected = Platform::Maximum(newMain.caret.Position()+1, newMain.anchor.Position()); - lastAffected = Platform::Maximum(lastAffected, sel.RangeMain().End().Position()); - if (invalidateWholeSelection) { - for (size_t r=0; rLineFromPosition(pos); + int lineDisplay = model.cs.DisplayFromDoc(lineDoc); + AutoLineLayout ll(llc, RetrieveLineLayout(lineDoc, model)); + if (surface && ll) { + LayoutLine(lineDoc, surface, vs, ll, model, model.wrapWidth); + unsigned int posLineStart = model.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++; + } } } - ContainerNeedsUpdate(SC_UPDATE_SELECTION); - InvalidateRange(firstAffected, lastAffected); + return lineDisplay; } -void Editor::SetSelection(SelectionPosition currentPos_, SelectionPosition anchor_) { - currentPos_ = ClampPositionIntoDocument(currentPos_); - anchor_ = ClampPositionIntoDocument(anchor_); - int currentLine = pdoc->LineFromPosition(currentPos_.Position()); - /* For Line selection - ensure the anchor and caret are always - at the beginning and end of the region lines. */ - if (sel.selType == Selection::selLines) { - if (currentPos_ > anchor_) { - anchor_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(anchor_.Position()))); - currentPos_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(currentPos_.Position()))); - } else { - currentPos_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(currentPos_.Position()))); - anchor_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(anchor_.Position()))); +int EditView::StartEndDisplayLine(Surface *surface, int pos, bool start, const EditModel &model, const ViewStyle &vs) { + int line = model.pdoc->LineFromPosition(pos); + AutoLineLayout ll(llc, RetrieveLineLayout(line, model)); + int posRet = INVALID_POSITION; + if (surface && ll) { + unsigned int posLineStart = model.pdoc->LineStart(line); + LayoutLine(line, surface, vs, ll, model, model.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; + } + } + } } } - SelectionRange rangeNew(currentPos_, anchor_); - if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { - InvalidateSelection(rangeNew); - } - sel.RangeMain() = rangeNew; - SetRectangularRange(); - ClaimSelection(); - - if (highlightDelimiter.NeedsDrawing(currentLine)) { - RedrawSelMargin(); - } - QueueIdleWork(WorkNeeded::workUpdateUI); + return posRet; } -void Editor::SetSelection(int currentPos_, int anchor_) { - SetSelection(SelectionPosition(currentPos_), SelectionPosition(anchor_)); +static ColourDesired SelectionBackground(const ViewStyle &vsDraw, bool main, bool primarySelection) { + return main ? + (primarySelection ? vsDraw.selColours.back : vsDraw.selBackground2) : + vsDraw.selAdditionalBackground; } -// Just move the caret on the main selection -void Editor::SetSelection(SelectionPosition currentPos_) { - currentPos_ = ClampPositionIntoDocument(currentPos_); - int currentLine = pdoc->LineFromPosition(currentPos_.Position()); - if (sel.Count() > 1 || !(sel.RangeMain().caret == currentPos_)) { - InvalidateSelection(SelectionRange(currentPos_)); - } - if (sel.IsRectangular()) { - sel.Rectangular() = - SelectionRange(SelectionPosition(currentPos_), sel.Rectangular().anchor); - SetRectangularRange(); +static ColourDesired TextBackground(const ViewStyle &vsDraw, + ColourOptional background, int inSelection, bool inHotspot, int styleMain, int i, + LineLayout *ll, const EditModel &model) { + if (inSelection == 1) { + if (vsDraw.selColours.back.isSet && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { + return SelectionBackground(vsDraw, true, model.primarySelection); + } + } else if (inSelection == 2) { + if (vsDraw.selColours.back.isSet && (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)) { + return SelectionBackground(vsDraw, false, model.primarySelection); + } } else { - sel.RangeMain() = - SelectionRange(SelectionPosition(currentPos_), sel.RangeMain().anchor); + if ((vsDraw.edgeState == EDGE_BACKGROUND) && + (i >= ll->edgeColumn) && + (i < ll->numCharsBeforeEOL)) + return vsDraw.edgecolour; + if (inHotspot && vsDraw.hotspotColours.back.isSet) + return vsDraw.hotspotColours.back; } - ClaimSelection(); - - if (highlightDelimiter.NeedsDrawing(currentLine)) { - RedrawSelMargin(); + if (background.isSet && (styleMain != STYLE_BRACELIGHT) && (styleMain != STYLE_BRACEBAD)) { + return background; + } else { + return vsDraw.styles[styleMain].back; } - QueueIdleWork(WorkNeeded::workUpdateUI); } -void Editor::SetSelection(int currentPos_) { - SetSelection(SelectionPosition(currentPos_)); +void EditView::DrawIndentGuide(Surface *surface, int lineVisible, int lineHeight, int start, PRectangle rcSegment, bool highlight) { + Point from = Point::FromInts(0, ((lineVisible & 1) && (lineHeight & 1)) ? 1 : 0); + PRectangle rcCopyArea = PRectangle::FromInts(start + 1, static_cast(rcSegment.top), start + 2, static_cast(rcSegment.bottom)); + surface->Copy(rcCopyArea, from, + highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); } -void Editor::SetEmptySelection(SelectionPosition currentPos_) { - int currentLine = pdoc->LineFromPosition(currentPos_.Position()); - SelectionRange rangeNew(ClampPositionIntoDocument(currentPos_)); - if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { - InvalidateSelection(rangeNew); - } - sel.Clear(); - sel.RangeMain() = rangeNew; - SetRectangularRange(); - ClaimSelection(); - - if (highlightDelimiter.NeedsDrawing(currentLine)) { - RedrawSelMargin(); +static void SimpleAlphaRectangle(Surface *surface, PRectangle rc, ColourDesired fill, int alpha) { + if (alpha != SC_ALPHA_NOALPHA) { + surface->AlphaRectangle(rc, 0, fill, alpha, fill, alpha, 0); } - QueueIdleWork(WorkNeeded::workUpdateUI); -} - -void Editor::SetEmptySelection(int currentPos_) { - SetEmptySelection(SelectionPosition(currentPos_)); } -bool Editor::RangeContainsProtected(int start, int end) const { - if (vs.ProtectionActive()) { - if (start > end) { - int t = start; - start = end; - end = t; - } - for (int pos = start; pos < end; pos++) { - if (vs.styles[pdoc->StyleAt(pos)].IsProtected()) - return true; - } +void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment, + const char *s, ColourDesired textBack, ColourDesired textFore, bool twoPhaseDraw) { + if (!twoPhaseDraw) { + surface->FillRectangle(rcSegment, textBack); } - return false; + FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; + int normalCharHeight = static_cast(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, s, static_cast(s ? strlen(s) : 0), + textBack, textFore); } -bool Editor::SelectionContainsProtected() { - for (size_t r=0; rLineStart(line); + PRectangle rcSegment = rcLine; + + const bool lastSubLine = subLine == (ll->lines - 1); + XYPOSITION virtualSpace = 0; + if (lastSubLine) { + const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; + virtualSpace = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)) * spaceWidth; + } + XYPOSITION xEol = static_cast(ll->positions[lineEnd] - subLineStart); + + // Fill the virtual space and show selections within it + if (virtualSpace) { + rcSegment.left = xEol + xStart; + rcSegment.right = xEol + xStart + virtualSpace; + surface->FillRectangle(rcSegment, background.isSet ? background : vsDraw.styles[ll->styles[ll->numCharsInLine]].back); + if (!hideSelection && ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA))) { + SelectionSegment virtualSpaceRange(SelectionPosition(model.pdoc->LineEnd(line)), SelectionPosition(model.pdoc->LineEnd(line), model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)))); + for (size_t r = 0; rEndLineStyle()].spaceWidth; + rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - + static_cast(subLineStart)+portion.start.VirtualSpace() * spaceWidth; + rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - + static_cast(subLineStart)+portion.end.VirtualSpace() * spaceWidth; + rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; + rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; + surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, r == model.sel.Main(), model.primarySelection)); + } + } + } } } - return false; -} -/** - * Asks document to find a good position and then moves out of any invisible positions. - */ -int Editor::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) const { - return MovePositionOutsideChar(SelectionPosition(pos), moveDir, checkLineEnd).Position(); -} + int eolInSelection = 0; + int alpha = SC_ALPHA_NOALPHA; + if (!hideSelection) { + int posAfterLineEnd = model.pdoc->LineStart(line + 1); + eolInSelection = (subLine == (ll->lines - 1)) ? model.sel.InSelectionForEOL(posAfterLineEnd) : 0; + alpha = (eolInSelection == 1) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; + } -SelectionPosition Editor::MovePositionOutsideChar(SelectionPosition pos, int moveDir, bool checkLineEnd) const { - int posMoved = pdoc->MovePositionOutsideChar(pos.Position(), moveDir, checkLineEnd); - if (posMoved != pos.Position()) - pos.SetPosition(posMoved); - if (vs.ProtectionActive()) { - if (moveDir > 0) { - if ((pos.Position() > 0) && vs.styles[pdoc->StyleAt(pos.Position() - 1)].IsProtected()) { - while ((pos.Position() < pdoc->Length()) && - (vs.styles[pdoc->StyleAt(pos.Position())].IsProtected())) - pos.Add(1); + // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on + XYPOSITION blobsWidth = 0; + if (lastSubLine) { + for (int eolPos = ll->numCharsBeforeEOL; eolPosnumCharsInLine; eolPos++) { + rcSegment.left = xStart + ll->positions[eolPos] - static_cast(subLineStart)+virtualSpace; + rcSegment.right = xStart + ll->positions[eolPos + 1] - static_cast(subLineStart)+virtualSpace; + blobsWidth += rcSegment.Width(); + char hexits[4]; + const char *ctrlChar; + unsigned char chEOL = ll->chars[eolPos]; + int styleMain = ll->styles[eolPos]; + ColourDesired textBack = TextBackground(vsDraw, background, eolInSelection, false, styleMain, eolPos, ll, model); + if (UTF8IsAscii(chEOL)) { + ctrlChar = ControlCharacterString(chEOL); + } else { + const Representation *repr = model.reprs.RepresentationFromCharacter(ll->chars + eolPos, ll->numCharsInLine - eolPos); + if (repr) { + ctrlChar = repr->stringRep.c_str(); + eolPos = ll->numCharsInLine; + } else { + sprintf(hexits, "x%2X", chEOL); + ctrlChar = hexits; + } } - } else if (moveDir < 0) { - if (vs.styles[pdoc->StyleAt(pos.Position())].IsProtected()) { - while ((pos.Position() > 0) && - (vs.styles[pdoc->StyleAt(pos.Position() - 1)].IsProtected())) - pos.Add(-1); + ColourDesired textFore = vsDraw.styles[styleMain].fore; + if (eolInSelection && vsDraw.selColours.fore.isSet) { + textFore = (eolInSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; + } + if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1)) { + if (alpha == SC_ALPHA_NOALPHA) { + surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection)); + } else { + surface->FillRectangle(rcSegment, textBack); + } + } else { + surface->FillRectangle(rcSegment, textBack); + } + DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, textBack, textFore, twoPhaseDraw); + if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { + SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection), alpha); } } } - return pos; -} -int Editor::MovePositionTo(SelectionPosition newPos, Selection::selTypes selt, bool ensureVisible) { - bool simpleCaret = (sel.Count() == 1) && sel.Empty(); - SelectionPosition spCaret = sel.Last(); + // Draw the eol-is-selected rectangle + rcSegment.left = xEol + xStart + virtualSpace + blobsWidth; + rcSegment.right = rcSegment.left + vsDraw.aveCharWidth; - int delta = newPos.Position() - sel.MainCaret(); - newPos = ClampPositionIntoDocument(newPos); - newPos = MovePositionOutsideChar(newPos, delta); - if (!multipleSelection && sel.IsRectangular() && (selt == Selection::selStream)) { - // Can't turn into multiple selection so clear additional selections - InvalidateSelection(SelectionRange(newPos), true); - SelectionRange rangeMain = sel.RangeMain(); - sel.SetSelection(rangeMain); - } - if (!sel.IsRectangular() && (selt == Selection::selRectangle)) { - // Switching to rectangular - InvalidateSelection(sel.RangeMain(), false); - SelectionRange rangeMain = sel.RangeMain(); - sel.Clear(); - sel.Rectangular() = rangeMain; - } - if (selt != Selection::noSel) { - sel.selType = selt; - } - if (selt != Selection::noSel || sel.MoveExtends()) { - SetSelection(newPos); + if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { + surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection)); } else { - SetEmptySelection(newPos); + if (background.isSet) { + surface->FillRectangle(rcSegment, background); + } else if (line < model.pdoc->LinesTotal() - 1) { + surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); + } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) { + surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); + } else { + surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); + } + if (eolInSelection && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { + SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection), alpha); + } } - ShowCaretAtCurrentPosition(); - int currentLine = pdoc->LineFromPosition(newPos.Position()); - if (ensureVisible) { - // In case in need of wrapping to ensure DisplayFromDoc works. - if (currentLine >= wrapPending.start) - WrapLines(wsAll); - XYScrollPosition newXY = XYScrollToMakeVisible( - SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), xysDefault); - if (simpleCaret && (newXY.xOffset == xOffset)) { - // simple vertical scroll then invalidate - ScrollTo(newXY.topLine); - InvalidateSelection(SelectionRange(spCaret), true); + // Fill the remainder of the line + rcSegment.left = rcSegment.right; + if (rcSegment.left < rcLine.left) + rcSegment.left = rcLine.left; + rcSegment.right = rcLine.right; + + if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { + surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection)); + } else { + if (background.isSet) { + surface->FillRectangle(rcSegment, background); + } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) { + surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); } else { - SetXYScroll(newXY); + surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); + } + if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < model.pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { + SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1, model.primarySelection), alpha); } } - if (highlightDelimiter.NeedsDrawing(currentLine)) { - RedrawSelMargin(); + bool drawWrapMarkEnd = false; + + if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { + if (subLine + 1 < ll->lines) { + drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; + } } - return 0; -} -int Editor::MovePositionTo(int newPos, Selection::selTypes selt, bool ensureVisible) { - return MovePositionTo(SelectionPosition(newPos), selt, ensureVisible); -} + if (drawWrapMarkEnd) { + PRectangle rcPlace = rcSegment; -SelectionPosition Editor::MovePositionSoVisible(SelectionPosition pos, int moveDir) { - pos = ClampPositionIntoDocument(pos); - pos = MovePositionOutsideChar(pos, moveDir); - int lineDoc = pdoc->LineFromPosition(pos.Position()); - 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 SelectionPosition(pdoc->LineStart(cs.DocFromDisplay(lineDisplay))); + if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_END_BY_TEXT) { + rcPlace.left = xEol + xStart + virtualSpace; + rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; } else { - lineDisplay = Platform::Clamp(lineDisplay - 1, 0, cs.LinesDisplayed()); - return SelectionPosition(pdoc->LineEnd(cs.DocFromDisplay(lineDisplay))); + // rcLine is clipped to text area + rcPlace.right = rcLine.right; + rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; } + DrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour()); } } -SelectionPosition Editor::MovePositionSoVisible(int pos, int moveDir) { - return MovePositionSoVisible(SelectionPosition(pos), moveDir); -} - -Point Editor::PointMainCaret() { - return LocationFromPosition(sel.Range(sel.Main()).caret); +static void DrawIndicator(int indicNum, int startPos, int endPos, Surface *surface, const ViewStyle &vsDraw, + int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { + const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)]; + PRectangle rcIndic( + ll->positions[startPos] + xStart - subLineStart, + rcLine.top + vsDraw.maxAscent, + ll->positions[endPos] + xStart - subLineStart, + rcLine.top + vsDraw.maxAscent + 3); + vsDraw.indicators[indicNum].Draw(surface, rcIndic, rcLine); } -/** - * Choose the x position that the caret will try to stick to - * as it moves up and down. - */ -void Editor::SetLastXChosen() { - Point pt = PointMainCaret(); - lastXChosen = static_cast(pt.x) + xOffset; -} +static void DrawIndicators(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, int lineEnd, bool under, const EditModel &model) { + // Draw decorators + const int posLineStart = model.pdoc->LineStart(line); + const int lineStart = ll->LineStart(subLine); + const int posLineEnd = posLineStart + lineEnd; -void Editor::ScrollTo(int line, bool moveThumb) { - int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); - if (topLineNew != topLine) { - // Try to optimise small scrolls -#ifndef UNDER_CE - int linesToMove = topLine - topLineNew; - bool performBlit = (abs(linesToMove) <= 10) && (paintState == notPainting); - willRedrawAll = !performBlit; -#endif - SetTopLine(topLineNew); - // Optimize by styling the view as this will invalidate any needed area - // which could abort the initial paint if discovered later. - StyleToPositionInView(PositionAfterArea(GetClientRectangle())); -#ifndef UNDER_CE - // Perform redraw rather than scroll if many lines would be redrawn anyway. - if (performBlit) { - ScrollText(linesToMove); - } else { - Redraw(); - } - willRedrawAll = false; -#else - Redraw(); -#endif - if (moveThumb) { - SetVerticalScrollPos(); + for (Decoration *deco = model.pdoc->decorations.root; deco; deco = deco->next) { + if (under == vsDraw.indicators[deco->indicator].under) { + int startPos = posLineStart + lineStart; + if (!deco->rs.ValueAt(startPos)) { + startPos = deco->rs.EndRun(startPos); + } + while ((startPos < posLineEnd) && (deco->rs.ValueAt(startPos))) { + int endPos = deco->rs.EndRun(startPos); + if (endPos > posLineEnd) + endPos = posLineEnd; + DrawIndicator(deco->indicator, startPos - posLineStart, endPos - posLineStart, + surface, vsDraw, xStart, rcLine, ll, subLine); + startPos = endPos; + if (!deco->rs.ValueAt(startPos)) { + startPos = deco->rs.EndRun(startPos); + } + } } } -} - -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 (!Wrapping() && (xOffset != xPos)) { - xOffset = xPos; - ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); - SetHorizontalScrollPos(); - RedrawRect(GetClientRectangle()); + // Use indicators to highlight matching braces + if ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACELIGHT)) || + (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACEBAD))) { + int braceIndicator = (model.bracesMatchStyle == STYLE_BRACELIGHT) ? vsDraw.braceHighlightIndicator : vsDraw.braceBadLightIndicator; + if (under == vsDraw.indicators[braceIndicator].under) { + Range rangeLine(posLineStart + lineStart, posLineEnd); + if (rangeLine.ContainsCharacter(model.braces[0])) { + int braceOffset = model.braces[0] - posLineStart; + if (braceOffset < ll->numCharsInLine) { + DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); + } + } + if (rangeLine.ContainsCharacter(model.braces[1])) { + int braceOffset = model.braces[1] - posLineStart; + if (braceOffset < ll->numCharsInLine) { + DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); + } + } + } } } -void Editor::VerticalCentreCaret() { - int lineDoc = pdoc->LineFromPosition(sel.IsRectangular() ? sel.Rectangular().caret.Position() : sel.MainCaret()); - int lineDisplay = cs.DisplayFromDoc(lineDoc); - int newTop = lineDisplay - (LinesOnScreen() / 2); - if (topLine != newTop) { - SetTopLine(newTop > 0 ? newTop : 0); - RedrawRect(GetClientRectangle()); +void EditView::DrawAnnotation(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) { + int indent = static_cast(model.pdoc->GetLineIndentation(line) * vsDraw.spaceWidth); + PRectangle rcSegment = rcLine; + int annotationLine = subLine - ll->lines; + const StyledText stAnnotation = model.pdoc->AnnotationStyledText(line); + if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) { + surface->FillRectangle(rcSegment, vsDraw.styles[0].back); + rcSegment.left = static_cast(xStart); + if (model.trackLineWidth || (vsDraw.annotationVisible == ANNOTATION_BOXED)) { + // Only care about calculating width if tracking or need to draw box + int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation); + if (vsDraw.annotationVisible == ANNOTATION_BOXED) { + widthAnnotation += static_cast(vsDraw.spaceWidth * 2); // Margins + } + if (widthAnnotation > lineWidthMaxSeen) + lineWidthMaxSeen = widthAnnotation; + if (vsDraw.annotationVisible == ANNOTATION_BOXED) { + rcSegment.left = static_cast(xStart + indent); + rcSegment.right = rcSegment.left + widthAnnotation; + } + } + const int annotationLines = model.pdoc->AnnotationLines(line); + size_t start = 0; + size_t lengthAnnotation = stAnnotation.LineLength(start); + int lineInAnnotation = 0; + while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) { + start += lengthAnnotation + 1; + lengthAnnotation = stAnnotation.LineLength(start); + lineInAnnotation++; + } + PRectangle rcText = rcSegment; + if (vsDraw.annotationVisible == ANNOTATION_BOXED) { + surface->FillRectangle(rcText, + vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back); + rcText.left += vsDraw.spaceWidth; + } + DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText, + stAnnotation, start, lengthAnnotation); + if (vsDraw.annotationVisible == ANNOTATION_BOXED) { + surface->PenColour(vsDraw.styles[vsDraw.annotationStyleOffset].fore); + surface->MoveTo(static_cast(rcSegment.left), static_cast(rcSegment.top)); + surface->LineTo(static_cast(rcSegment.left), static_cast(rcSegment.bottom)); + surface->MoveTo(static_cast(rcSegment.right), static_cast(rcSegment.top)); + surface->LineTo(static_cast(rcSegment.right), static_cast(rcSegment.bottom)); + if (subLine == ll->lines) { + surface->MoveTo(static_cast(rcSegment.left), static_cast(rcSegment.top)); + surface->LineTo(static_cast(rcSegment.right), static_cast(rcSegment.top)); + } + if (subLine == ll->lines + annotationLines - 1) { + surface->MoveTo(static_cast(rcSegment.left), static_cast(rcSegment.bottom - 1)); + surface->LineTo(static_cast(rcSegment.right), static_cast(rcSegment.bottom - 1)); + } + } } } -// Avoid 64 bit compiler warnings. -// Scintilla does not support text buffers larger than 2**31 -static int istrlen(const char *s) { - return static_cast(s ? strlen(s) : 0); -} - -void Editor::MoveSelectedLines(int lineDelta) { - - // if selection doesn't start at the beginning of the line, set the new start - int selectionStart = SelectionStart().Position(); - int startLine = pdoc->LineFromPosition(selectionStart); - int beginningOfStartLine = pdoc->LineStart(startLine); - selectionStart = beginningOfStartLine; +static void DrawBlockCaret(Surface *surface, const ViewStyle &vsDraw, LineLayout *ll, int subLine, + int xStart, int offset, int posCaret, PRectangle rcCaret, ColourDesired caretColour, const EditModel &model) { - // if selection doesn't end at the beginning of a line greater than that of the start, - // then set it at the beginning of the next one - int selectionEnd = SelectionEnd().Position(); - int endLine = pdoc->LineFromPosition(selectionEnd); - int beginningOfEndLine = pdoc->LineStart(endLine); - bool appendEol = false; - if (selectionEnd > beginningOfEndLine - || selectionStart == selectionEnd) { - selectionEnd = pdoc->LineStart(endLine + 1); - appendEol = (selectionEnd == pdoc->Length() && pdoc->LineFromPosition(selectionEnd) == endLine); - } + int lineStart = ll->LineStart(subLine); + int posBefore = posCaret; + int posAfter = model.pdoc->MovePositionOutsideChar(posCaret + 1, 1); + int numCharsToDraw = posAfter - posCaret; - // if there's nowhere for the selection to move - // (i.e. at the beginning going up or at the end going down), - // stop it right there! - if ((selectionStart == 0 && lineDelta < 0) - || (selectionEnd == pdoc->Length() && lineDelta > 0) - || selectionStart == selectionEnd) { - return; + // Work out where the starting and ending offsets are. We need to + // see if the previous character shares horizontal space, such as a + // glyph / combining character. If so we'll need to draw that too. + int offsetFirstChar = offset; + int offsetLastChar = offset + (posAfter - posCaret); + while ((posBefore > 0) && ((offsetLastChar - numCharsToDraw) >= lineStart)) { + if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - numCharsToDraw]) > 0) { + // The char does not share horizontal space + break; + } + // Char shares horizontal space, update the numChars to draw + // Update posBefore to point to the prev char + posBefore = model.pdoc->MovePositionOutsideChar(posBefore - 1, -1); + numCharsToDraw = posAfter - posBefore; + offsetFirstChar = offset - (posCaret - posBefore); } - UndoGroup ug(pdoc); - - if (lineDelta > 0 && selectionEnd == pdoc->LineStart(pdoc->LinesTotal() - 1)) { - SetSelection(pdoc->MovePositionOutsideChar(selectionEnd - 1, -1), selectionEnd); - ClearSelection(); - selectionEnd = CurrentPosition(); + // See if the next character shares horizontal space, if so we'll + // need to draw that too. + if (offsetFirstChar < 0) + offsetFirstChar = 0; + numCharsToDraw = offsetLastChar - offsetFirstChar; + while ((offsetLastChar < ll->LineStart(subLine + 1)) && (offsetLastChar <= ll->numCharsInLine)) { + // Update posAfter to point to the 2nd next char, this is where + // the next character ends, and 2nd next begins. We'll need + // to compare these two + posBefore = posAfter; + posAfter = model.pdoc->MovePositionOutsideChar(posAfter + 1, 1); + offsetLastChar = offset + (posAfter - posCaret); + if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - (posAfter - posBefore)]) > 0) { + // The char does not share horizontal space + break; + } + // Char shares horizontal space, update the numChars to draw + numCharsToDraw = offsetLastChar - offsetFirstChar; } - SetSelection(selectionStart, selectionEnd); - - SelectionText selectedText; - CopySelectionRange(&selectedText); - - int selectionLength = SelectionRange(selectionStart, selectionEnd).Length(); - Point currentLocation = LocationFromPosition(CurrentPosition()); - int currentLine = LineFromLocation(currentLocation); - - if (appendEol) - SetSelection(pdoc->MovePositionOutsideChar(selectionStart - 1, -1), selectionEnd); - ClearSelection(); - const char *eol = StringFromEOLMode(pdoc->eolMode); - if (currentLine + lineDelta >= pdoc->LinesTotal()) - pdoc->InsertString(pdoc->Length(), eol, istrlen(eol)); - GoToLine(currentLine + lineDelta); + // We now know what to draw, update the caret drawing rectangle + rcCaret.left = ll->positions[offsetFirstChar] - ll->positions[lineStart] + xStart; + rcCaret.right = ll->positions[offsetFirstChar + numCharsToDraw] - ll->positions[lineStart] + xStart; - selectionLength = pdoc->InsertString(CurrentPosition(), selectedText.Data(), selectionLength); - if (appendEol) { - const int lengthInserted = pdoc->InsertString(CurrentPosition() + selectionLength, eol, istrlen(eol)); - selectionLength += lengthInserted; + // Adjust caret position to take into account any word wrapping symbols. + if ((ll->wrapIndent != 0) && (lineStart != 0)) { + XYPOSITION wordWrapCharWidth = ll->wrapIndent; + rcCaret.left += wordWrapCharWidth; + rcCaret.right += wordWrapCharWidth; } - SetSelection(CurrentPosition(), CurrentPosition() + selectionLength); -} -void Editor::MoveSelectedLinesUp() { - MoveSelectedLines(-1); + // This character is where the caret block is, we override the colours + // (inversed) for drawing the caret here. + int styleMain = ll->styles[offsetFirstChar]; + FontAlias fontText = vsDraw.styles[styleMain].font; + surface->DrawTextClipped(rcCaret, fontText, + rcCaret.top + vsDraw.maxAscent, ll->chars + offsetFirstChar, + numCharsToDraw, vsDraw.styles[styleMain].back, + caretColour); } -void Editor::MoveSelectedLinesDown() { - MoveSelectedLines(1); -} +void EditView::DrawCarets(Surface *surface, const ViewStyle &vsDraw, int lineDoc, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) const { + // When drag is active it is the only caret drawn + bool drawDrag = model.posDrag.IsValid(); + if (hideSelection && !drawDrag) + return; + const int posLineStart = model.pdoc->LineStart(lineDoc); + // For each selection draw + for (size_t r = 0; (rEndLineStyle()].spaceWidth; + const XYPOSITION virtualOffset = posCaret.VirtualSpace() * spaceWidth; + if (ll->InLine(offset, subLine) && offset <= ll->numCharsBeforeEOL) { + XYPOSITION xposCaret = ll->positions[offset] + virtualOffset - ll->positions[ll->LineStart(subLine)]; + if (ll->wrapIndent != 0) { + int lineStart = ll->LineStart(subLine); + if (lineStart != 0) // Wrapped + xposCaret += ll->wrapIndent; + } + bool caretBlinkState = (model.caret.active && model.caret.on) || (!additionalCaretsBlink && !mainCaret); + bool caretVisibleState = additionalCaretsVisible || mainCaret; + if ((xposCaret >= 0) && (vsDraw.caretWidth > 0) && (vsDraw.caretStyle != CARETSTYLE_INVISIBLE) && + ((model.posDrag.IsValid()) || (caretBlinkState && caretVisibleState))) { + bool caretAtEOF = false; + bool caretAtEOL = false; + bool drawBlockCaret = false; + XYPOSITION widthOverstrikeCaret; + XYPOSITION caretWidthOffset = 0; + PRectangle rcCaret = rcLine; -void Editor::MoveCaretInsideView(bool ensureVisible) { - PRectangle rcClient = GetTextRectangle(); - Point pt = PointMainCaret(); - if (pt.y < rcClient.top) { - MovePositionTo(SPositionFromLocation( - Point::FromInts(lastXChosen - xOffset, static_cast(rcClient.top)), - false, false, UserVirtualSpace()), - Selection::noSel, ensureVisible); - } else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { - int yOfLastLineFullyDisplayed = static_cast(rcClient.top) + (LinesOnScreen() - 1) * vs.lineHeight; - MovePositionTo(SPositionFromLocation( - Point::FromInts(lastXChosen - xOffset, static_cast(rcClient.top) + yOfLastLineFullyDisplayed), - false, false, UserVirtualSpace()), - Selection::noSel, ensureVisible); - } -} + if (posCaret.Position() == model.pdoc->Length()) { // At end of document + caretAtEOF = true; + widthOverstrikeCaret = vsDraw.aveCharWidth; + } else if ((posCaret.Position() - posLineStart) >= ll->numCharsInLine) { // At end of line + caretAtEOL = true; + widthOverstrikeCaret = vsDraw.aveCharWidth; + } else { + widthOverstrikeCaret = ll->positions[offset + 1] - ll->positions[offset]; + } + if (widthOverstrikeCaret < 3) // Make sure its visible + widthOverstrikeCaret = 3; -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++; + if (xposCaret > 0) + caretWidthOffset = 0.51f; // Move back so overlaps both character cells. + xposCaret += xStart; + if (model.posDrag.IsValid()) { + /* Dragging text, use a line caret */ + rcCaret.left = static_cast(RoundXYPosition(xposCaret - caretWidthOffset)); + rcCaret.right = rcCaret.left + vsDraw.caretWidth; + } else if (model.inOverstrike && drawOverstrikeCaret) { + /* Overstrike (insert mode), use a modified bar caret */ + rcCaret.top = rcCaret.bottom - 2; + rcCaret.left = xposCaret + 1; + rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; + } else if (vsDraw.caretStyle == CARETSTYLE_BLOCK) { + /* Block caret */ + rcCaret.left = xposCaret; + if (!caretAtEOL && !caretAtEOF && (ll->chars[offset] != '\t') && !(IsControlCharacter(ll->chars[offset]))) { + drawBlockCaret = true; + rcCaret.right = xposCaret + widthOverstrikeCaret; + } else { + rcCaret.right = xposCaret + vsDraw.aveCharWidth; + } + } else { + /* Line caret */ + rcCaret.left = static_cast(RoundXYPosition(xposCaret - caretWidthOffset)); + rcCaret.right = rcCaret.left + vsDraw.caretWidth; + } + ColourDesired caretColour = mainCaret ? vsDraw.caretcolour : vsDraw.additionalCaretColour; + if (drawBlockCaret) { + DrawBlockCaret(surface, vsDraw, ll, subLine, xStart, offset, posCaret.Position(), rcCaret, caretColour, model); + } else { + surface->FillRectangle(rcCaret, caretColour); + } } } + if (drawDrag) + break; } - return lineDisplay; } -/** - * Ensure the caret is reasonably visible in context. - * -Caret policy in SciTE +void EditView::DrawLine(Surface *surface, const ViewStyle &vsDraw, int line, int lineVisible, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) { -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 (subLine >= ll->lines) { + DrawAnnotation(surface, vsDraw, line, xStart, rcLine, ll, subLine, model); + return; // No further drawing + } -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. + // 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. + FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; -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. + // See if something overrides the line background color. + const ColourOptional background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret); -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 beginning of lines, -where most code reside, and the lines after the caret, eg. the body of a function. + const bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && + (!background.isSet) && (vsDraw.whitespaceColours.back.isSet); - | | | | | -slop | strict | jumps | even | Caret can go to the margin | When reaching limit (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 -*/ + const XYPOSITION indentWidth = model.pdoc->IndentSize() * vsDraw.spaceWidth; -Editor::XYScrollPosition Editor::XYScrollToMakeVisible(const SelectionRange &range, const XYScrollOptions options) { - PRectangle rcClient = GetTextRectangle(); - Point pt = LocationFromPosition(range.caret); - Point ptAnchor = LocationFromPosition(range.anchor); - const Point ptOrigin = GetVisibleOriginInMain(); - pt.x += ptOrigin.x; - pt.y += ptOrigin.y; - ptAnchor.x += ptOrigin.x; - ptAnchor.y += ptOrigin.y; - const Point ptBottomCaret(pt.x, pt.y + vs.lineHeight - 1); + const int posLineStart = model.pdoc->LineStart(line); - XYScrollPosition newXY(xOffset, topLine); - if (rcClient.Empty()) { - return newXY; + const Range lineRange = ll->SubLineRange(subLine); + const XYACCUMULATOR subLineStart = ll->positions[lineRange.start]; + + if ((ll->wrapIndent != 0) && (subLine > 0) && (subLine < ll->lines)) { + // default bgnd here.. + surface->FillRectangle(rcLine, background.isSet ? background : + vsDraw.styles[STYLE_DEFAULT].back); + + if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_START) { + + // draw continuation rect + PRectangle rcPlace = rcLine; + + rcPlace.left = static_cast(xStart); + rcPlace.right = rcPlace.left + ll->wrapIndent; + + if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_START_BY_TEXT) + rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; + else + rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; + + DrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour()); + } + + xStart += static_cast(ll->wrapIndent); } - // Vertical positioning - if ((options & xysVertical) && (pt.y < rcClient.top || ptBottomCaret.y >= rcClient.bottom || (caretYPolicy & CARET_STRICT) != 0)) { - const int lineCaret = DisplayFromPosition(range.caret.Position()); - const int linesOnScreen = LinesOnScreen(); - const int halfScreen = Platform::Maximum(linesOnScreen - 1, 2) / 2; - const bool bSlop = (caretYPolicy & CARET_SLOP) != 0; - const bool bStrict = (caretYPolicy & CARET_STRICT) != 0; - const bool bJump = (caretYPolicy & CARET_JUMPS) != 0; - const bool bEven = (caretYPolicy & CARET_EVEN) != 0; + const bool selBackDrawn = vsDraw.selColours.back.isSet && + ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)); - // 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 (!(options & xysUseMargin)) { - // 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; + // Does not take margin into account but not significant + const int xStartVisible = static_cast(subLineStart)-xStart; + + if (twoPhaseDraw) { + bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. + BreakFinder bfBack(ll, &model.sel, lineRange, posLineStart, xStartVisible, selBackDrawn, model.pdoc, &model.reprs); + + // Background drawing loop + while (bfBack.More()) { + + const TextSegment ts = bfBack.Next(); + const int i = ts.end() - 1; + const int iDoc = i + posLineStart; + + PRectangle rcSegment = rcLine; + rcSegment.left = ll->positions[ts.start] + xStart - static_cast(subLineStart); + rcSegment.right = ll->positions[ts.end()] + xStart - static_cast(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.Intersects(rcLine)) { + // Clip to line rectangle, since may have a huge position which will not work with some platforms + if (rcSegment.left < rcLine.left) + rcSegment.left = rcLine.left; + if (rcSegment.right > rcLine.right) + rcSegment.right = rcLine.right; + + const int inSelection = hideSelection ? 0 : model.sel.CharacterInSelection(iDoc); + const bool inHotspot = (ll->hotspot.Valid()) && ll->hotspot.ContainsCharacter(iDoc); + ColourDesired textBack = TextBackground(vsDraw, background, inSelection, + inHotspot, ll->styles[i], i, ll, model); + if (ts.representation) { + if (ll->chars[i] == '\t') { + // Tab display + if (drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) + textBack = vsDraw.whitespaceColours.back; } else { - yMarginB = linesOnScreen - yMarginT - 1; - } - } - yMoveT = yMarginT; - if (bEven) { - if (bJump) { - yMoveT = Platform::Clamp(caretYSlop * 3, 1, halfScreen); + // Blob display + inIndentation = false; } - yMoveB = yMoveT; - } else { - yMoveB = linesOnScreen - yMoveT - 1; - } - if (lineCaret < topLine + yMarginT) { - // Caret goes too high - newXY.topLine = lineCaret - yMoveT; - } else if (lineCaret > topLine + linesOnScreen - 1 - yMarginB) { - // Caret goes too low - newXY.topLine = lineCaret - linesOnScreen + 1 + yMoveB; - } - } else { // Not strict - yMoveT = bJump ? caretYSlop * 3 : caretYSlop; - yMoveT = Platform::Clamp(yMoveT, 1, halfScreen); - if (bEven) { - yMoveB = yMoveT; + surface->FillRectangle(rcSegment, textBack); } else { - yMoveB = linesOnScreen - yMoveT - 1; - } - if (lineCaret < topLine) { - // Caret goes too high - newXY.topLine = lineCaret - yMoveT; - } else if (lineCaret > topLine + linesOnScreen - 1) { - // Caret goes too low - newXY.topLine = lineCaret - linesOnScreen + 1 + yMoveB; - } - } - } else { // No slop - if (!bStrict && !bJump) { - // Minimal move - if (lineCaret < topLine) { - // Caret goes too high - newXY.topLine = lineCaret; - } else if (lineCaret > topLine + linesOnScreen - 1) { - // Caret goes too low - if (bEven) { - newXY.topLine = lineCaret - linesOnScreen + 1; - } else { - newXY.topLine = lineCaret; + // Normal text display + surface->FillRectangle(rcSegment, textBack); + if (vsDraw.viewWhitespace != wsInvisible || + (inIndentation && vsDraw.viewIndentationGuides == ivReal)) { + for (int cpos = 0; cpos <= i - ts.start; cpos++) { + if (ll->chars[cpos + ts.start] == ' ') { + if (drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { + PRectangle rcSpace( + ll->positions[cpos + ts.start] + xStart - static_cast(subLineStart), + rcSegment.top, + ll->positions[cpos + ts.start + 1] + xStart - static_cast(subLineStart), + rcSegment.bottom); + surface->FillRectangle(rcSpace, vsDraw.whitespaceColours.back); + } + } else { + inIndentation = false; + } + } } } - } else { // Strict or going out of display - if (bEven) { - // Always center caret - newXY.topLine = lineCaret - halfScreen; - } else { - // Always put caret on top of display - newXY.topLine = lineCaret; - } + } else if (rcSegment.left > rcLine.right) { + break; } } - if (!(range.caret == range.anchor)) { - const int lineAnchor = DisplayFromPosition(range.anchor.Position()); - if (lineAnchor < lineCaret) { - // Shift up to show anchor or as much of range as possible - newXY.topLine = std::min(newXY.topLine, lineAnchor); - newXY.topLine = std::max(newXY.topLine, lineCaret - LinesOnScreen()); - } else { - // Shift down to show anchor or as much of range as possible - newXY.topLine = std::max(newXY.topLine, lineAnchor - LinesOnScreen()); - newXY.topLine = std::min(newXY.topLine, lineCaret); - } + + DrawEOL(surface, vsDraw, rcLine, ll, line, lineRange.end, + xStart, subLine, subLineStart, background, model); + } + + DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineRange.end, true, model); + + if (vsDraw.edgeState == EDGE_LINE) { + PRectangle rcSegment = rcLine; + int edgeX = static_cast(vsDraw.theEdge * vsDraw.spaceWidth); + rcSegment.left = static_cast(edgeX + xStart); + if ((ll->wrapIndent != 0) && (lineRange.start != 0)) + rcSegment.left -= ll->wrapIndent; + rcSegment.right = rcSegment.left + 1; + surface->FillRectangle(rcSegment, vsDraw.edgecolour); + } + + // Draw underline mark as part of background if not transparent + int marks = model.pdoc->GetMark(line); + int markBit; + for (markBit = 0; (markBit < 32) && marks; markBit++) { + if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE) && + (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { + PRectangle rcUnderline = rcLine; + rcUnderline.top = rcUnderline.bottom - 2; + surface->FillRectangle(rcUnderline, vsDraw.markers[markBit].back); } - newXY.topLine = Platform::Clamp(newXY.topLine, 0, MaxScrollPos()); + marks >>= 1; } - // Horizontal positioning - if ((options & xysHorizontal) && !Wrapping()) { - const int halfScreen = Platform::Maximum(static_cast(rcClient.Width()) - 4, 4) / 2; - const bool bSlop = (caretXPolicy & CARET_SLOP) != 0; - const bool bStrict = (caretXPolicy & CARET_STRICT) != 0; - const bool bJump = (caretXPolicy & CARET_JUMPS) != 0; - const bool bEven = (caretXPolicy & CARET_EVEN) != 0; + bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. + // Foreground drawing loop + BreakFinder bfFore(ll, &model.sel, lineRange, posLineStart, xStartVisible, + ((!twoPhaseDraw && selBackDrawn) || vsDraw.selColours.fore.isSet), model.pdoc, &model.reprs); - if (bSlop) { // A margin is defined - int xMoveL, xMoveR; - if (bStrict) { - int xMarginL, xMarginR; - if (!(options & xysUseMargin)) { - // 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 = static_cast(rcClient.Width()) - xMarginR - 4; + while (bfFore.More()) { + + const TextSegment ts = bfFore.Next(); + const int i = ts.end() - 1; + const int iDoc = i + posLineStart; + + PRectangle rcSegment = rcLine; + rcSegment.left = ll->positions[ts.start] + xStart - static_cast(subLineStart); + rcSegment.right = ll->positions[ts.end()] + xStart - static_cast(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.Intersects(rcLine)) { + int styleMain = ll->styles[i]; + ColourDesired textFore = vsDraw.styles[styleMain].fore; + FontAlias textFont = vsDraw.styles[styleMain].font; + //hotspot foreground + const bool inHotspot = (ll->hotspot.Valid()) && ll->hotspot.ContainsCharacter(iDoc); + if (inHotspot) { + if (vsDraw.hotspotColours.fore.isSet) + textFore = vsDraw.hotspotColours.fore; + } + const int inSelection = hideSelection ? 0 : model.sel.CharacterInSelection(iDoc); + if (inSelection && (vsDraw.selColours.fore.isSet)) { + textFore = (inSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; + } + ColourDesired textBack = TextBackground(vsDraw, background, inSelection, inHotspot, styleMain, i, ll, model); + if (ts.representation) { + if (ll->chars[i] == '\t') { + // Tab display + if (!twoPhaseDraw) { + if (drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) + textBack = vsDraw.whitespaceColours.back; + surface->FillRectangle(rcSegment, textBack); } - } - 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) { - newXY.xOffset -= xMoveL; - } else { - // Move just enough to allow to display the caret - newXY.xOffset -= static_cast((rcClient.left + xMarginL) - pt.x); + if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { + for (int indentCount = static_cast((ll->positions[i] + epsilon) / indentWidth); + indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth; + indentCount++) { + if (indentCount > 0) { + int xIndent = static_cast(indentCount * indentWidth); + DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, + (ll->xHighlightGuide == xIndent)); + } + } } - } else if (pt.x >= rcClient.right - xMarginR) { - // Caret is on the right of the display - if (bJump && bEven) { - newXY.xOffset += xMoveR; - } else { - // Move just enough to allow to display the caret - newXY.xOffset += static_cast(pt.x - (rcClient.right - xMarginR) + 1); + if (vsDraw.viewWhitespace != wsInvisible) { + if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { + if (vsDraw.whitespaceColours.fore.isSet) + textFore = vsDraw.whitespaceColours.fore; + surface->PenColour(textFore); + PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, + rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); + DrawTabArrow(surface, rcTab, static_cast(rcSegment.top + vsDraw.lineHeight / 2)); + } } - } - } else { // Not strict - xMoveR = bJump ? caretXSlop * 3 : caretXSlop; - xMoveR = Platform::Clamp(xMoveR, 1, halfScreen); - if (bEven) { - xMoveL = xMoveR; - } else { - xMoveL = static_cast(rcClient.Width()) - xMoveR - 4; - } - if (pt.x < rcClient.left) { - // Caret is on the left of the display - newXY.xOffset -= xMoveL; - } else if (pt.x >= rcClient.right) { - // Caret is on the right of the display - newXY.xOffset += 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 - newXY.xOffset += static_cast(pt.x - rcClient.left - halfScreen); } else { - // Put caret on right - newXY.xOffset += static_cast(pt.x - rcClient.right + 1); + inIndentation = false; + if (vsDraw.controlCharSymbol >= 32) { + char cc[2] = { static_cast(vsDraw.controlCharSymbol), '\0' }; + surface->DrawTextNoClip(rcSegment, ctrlCharsFont, + rcSegment.top + vsDraw.maxAscent, + cc, 1, textBack, textFore); + } else { + DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep.c_str(), textBack, textFore, twoPhaseDraw); + } } } 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) { - newXY.xOffset -= static_cast(rcClient.left - pt.x); + // Normal text display + if (vsDraw.styles[styleMain].visible) { + if (twoPhaseDraw) { + surface->DrawTextTransparent(rcSegment, textFont, + rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, + i - ts.start + 1, textFore); } else { - newXY.xOffset += static_cast(pt.x - rcClient.right) + 1; + surface->DrawTextNoClip(rcSegment, textFont, + rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, + i - ts.start + 1, textFore, textBack); + } + } + if (vsDraw.viewWhitespace != wsInvisible || + (inIndentation && vsDraw.viewIndentationGuides != ivNone)) { + for (int cpos = 0; cpos <= i - ts.start; cpos++) { + if (ll->chars[cpos + ts.start] == ' ') { + if (vsDraw.viewWhitespace != wsInvisible) { + if (vsDraw.whitespaceColours.fore.isSet) + textFore = vsDraw.whitespaceColours.fore; + if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { + XYPOSITION xmid = (ll->positions[cpos + ts.start] + ll->positions[cpos + ts.start + 1]) / 2; + if (!twoPhaseDraw && drawWhitespaceBackground && + (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { + textBack = vsDraw.whitespaceColours.back; + PRectangle rcSpace( + ll->positions[cpos + ts.start] + xStart - static_cast(subLineStart), + rcSegment.top, + ll->positions[cpos + ts.start + 1] + xStart - static_cast(subLineStart), + rcSegment.bottom); + surface->FillRectangle(rcSpace, textBack); + } + PRectangle rcDot(xmid + xStart - static_cast(subLineStart), + rcSegment.top + vsDraw.lineHeight / 2, 0.0f, 0.0f); + rcDot.right = rcDot.left + vsDraw.whitespaceSize; + rcDot.bottom = rcDot.top + vsDraw.whitespaceSize; + surface->FillRectangle(rcDot, textFore); + } + } + if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { + for (int indentCount = static_cast((ll->positions[cpos + ts.start] + epsilon) / indentWidth); + indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth; + indentCount++) { + if (indentCount > 0) { + int xIndent = static_cast(indentCount * indentWidth); + DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, + (ll->xHighlightGuide == xIndent)); + } + } + } + } else { + inIndentation = false; + } } - } else if (pt.x >= rcClient.right) { - // Caret is on the right of the display - newXY.xOffset += static_cast(pt.x - rcClient.right) + 1; } } + if (ll->hotspot.Valid() && vsDraw.hotspotUnderline && ll->hotspot.ContainsCharacter(iDoc)) { + PRectangle rcUL = rcSegment; + rcUL.top = rcUL.top + vsDraw.maxAscent + 1; + rcUL.bottom = rcUL.top + 1; + if (vsDraw.hotspotColours.fore.isSet) + surface->FillRectangle(rcUL, vsDraw.hotspotColours.fore); + 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; } - // In case of a jump (find result) largely out of display, adjust the offset to display the caret - if (pt.x + xOffset < rcClient.left + newXY.xOffset) { - newXY.xOffset = static_cast(pt.x + xOffset - rcClient.left) - 2; - } else if (pt.x + xOffset >= rcClient.right + newXY.xOffset) { - newXY.xOffset = static_cast(pt.x + xOffset - rcClient.right) + 2; - if (vs.caretStyle == CARETSTYLE_BLOCK) { - // Ensure we can see a good portion of the block caret - newXY.xOffset += static_cast(vs.aveCharWidth); + } + if ((vsDraw.viewIndentationGuides == ivLookForward || vsDraw.viewIndentationGuides == ivLookBoth) + && (subLine == 0)) { + int indentSpace = model.pdoc->GetLineIndentation(line); + int xStartText = static_cast(ll->positions[model.pdoc->GetLineIndentPosition(line) - posLineStart]); + + // Find the most recent line with some text + + int lineLastWithText = line; + while (lineLastWithText > Platform::Maximum(line - 20, 0) && model.pdoc->IsWhiteLine(lineLastWithText)) { + lineLastWithText--; + } + if (lineLastWithText < line) { + xStartText = 100000; // Don't limit to visible indentation on empty line + // This line is empty, so use indentation of last line with text + int indentLastWithText = model.pdoc->GetLineIndentation(lineLastWithText); + int isFoldHeader = model.pdoc->GetLevel(lineLastWithText) & SC_FOLDLEVELHEADERFLAG; + if (isFoldHeader) { + // Level is one more level than parent + indentLastWithText += model.pdoc->IndentSize(); + } + if (vsDraw.viewIndentationGuides == ivLookForward) { + // In viLookForward mode, previous line only used if it is a fold header + if (isFoldHeader) { + indentSpace = Platform::Maximum(indentSpace, indentLastWithText); + } + } else { // viLookBoth + indentSpace = Platform::Maximum(indentSpace, indentLastWithText); } } - if (!(range.caret == range.anchor)) { - if (ptAnchor.x < pt.x) { - // Shift to left to show anchor or as much of range as possible - int maxOffset = static_cast(ptAnchor.x + xOffset - rcClient.left) - 1; - int minOffset = static_cast(pt.x + xOffset - rcClient.right) + 1; - newXY.xOffset = std::min(newXY.xOffset, maxOffset); - newXY.xOffset = std::max(newXY.xOffset, minOffset); - } else { - // Shift to right to show anchor or as much of range as possible - int minOffset = static_cast(ptAnchor.x + xOffset - rcClient.right) + 1; - int maxOffset = static_cast(pt.x + xOffset - rcClient.left) - 1; - newXY.xOffset = std::max(newXY.xOffset, minOffset); - newXY.xOffset = std::min(newXY.xOffset, maxOffset); + + int lineNextWithText = line; + while (lineNextWithText < Platform::Minimum(line + 20, model.pdoc->LinesTotal()) && model.pdoc->IsWhiteLine(lineNextWithText)) { + lineNextWithText++; + } + if (lineNextWithText > line) { + xStartText = 100000; // Don't limit to visible indentation on empty line + // This line is empty, so use indentation of first next line with text + indentSpace = Platform::Maximum(indentSpace, + model.pdoc->GetLineIndentation(lineNextWithText)); + } + + for (int indentPos = model.pdoc->IndentSize(); indentPos < indentSpace; indentPos += model.pdoc->IndentSize()) { + int xIndent = static_cast(indentPos * vsDraw.spaceWidth); + if (xIndent < xStartText) { + DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcLine, + (ll->xHighlightGuide == xIndent)); } } - if (newXY.xOffset < 0) { - newXY.xOffset = 0; - } } - return newXY; -} + DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineRange.end, false, model); -void Editor::SetXYScroll(XYScrollPosition newXY) { - if ((newXY.topLine != topLine) || (newXY.xOffset != xOffset)) { - if (newXY.topLine != topLine) { - SetTopLine(newXY.topLine); - SetVerticalScrollPos(); + // End of the drawing of the current line + if (!twoPhaseDraw) { + DrawEOL(surface, vsDraw, rcLine, ll, line, lineRange.end, + xStart, subLine, subLineStart, background, model); + } + if (!hideSelection && ((vsDraw.selAlpha != SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha != SC_ALPHA_NOALPHA))) { + // For each selection draw + int virtualSpaces = 0; + if (subLine == (ll->lines - 1)) { + virtualSpaces = model.sel.VirtualSpaceFor(model.pdoc->LineEnd(line)); } - if (newXY.xOffset != xOffset) { - xOffset = newXY.xOffset; - ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); - if (newXY.xOffset > 0) { - PRectangle rcText = GetTextRectangle(); - if (horizontalScrollBarVisible && - rcText.Width() + xOffset > scrollWidth) { - scrollWidth = xOffset + static_cast(rcText.Width()); - SetScrollBars(); + SelectionPosition posStart(posLineStart + lineRange.start); + SelectionPosition posEnd(posLineStart + lineRange.end, virtualSpaces); + SelectionSegment virtualSpaceRange(posStart, posEnd); + for (size_t r = 0; rEndLineStyle()].spaceWidth; + PRectangle rcSegment = rcLine; + rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - + static_cast(subLineStart)+portion.start.VirtualSpace() * spaceWidth; + rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - + static_cast(subLineStart)+portion.end.VirtualSpace() * spaceWidth; + if ((ll->wrapIndent != 0) && (lineRange.start != 0)) { + if ((portion.start.Position() - posLineStart) == lineRange.start && model.sel.Range(r).ContainsCharacter(portion.start.Position() - 1)) + rcSegment.left -= static_cast(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here + } + rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; + rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; + if (rcSegment.right > rcLine.left) + SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, r == model.sel.Main(), model.primarySelection), alpha); } } - SetHorizontalScrollPos(); } - Redraw(); - UpdateSystemCaret(); } -} - -void Editor::ScrollRange(SelectionRange range) { - SetXYScroll(XYScrollToMakeVisible(range, xysDefault)); -} - -void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { - SetXYScroll(XYScrollToMakeVisible(SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), - static_cast((useMargin?xysUseMargin:0)|(vert?xysVertical:0)|(horiz?xysHorizontal:0)))); -} -void Editor::ShowCaretAtCurrentPosition() { - if (hasFocus) { - caret.active = true; - caret.on = true; - SetTicking(true); - } else { - caret.active = false; - caret.on = false; + // Draw any translucent whole line states + if ((model.caret.active || vsDraw.alwaysShowCaretLineBackground) && vsDraw.showCaretLineBackground && ll->containsCaret) { + SimpleAlphaRectangle(surface, rcLine, vsDraw.caretLineBackground, vsDraw.caretLineAlpha); + } + marks = model.pdoc->GetMark(line); + for (markBit = 0; (markBit < 32) && marks; markBit++) { + if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND)) { + SimpleAlphaRectangle(surface, rcLine, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); + } else if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE)) { + PRectangle rcUnderline = rcLine; + rcUnderline.top = rcUnderline.bottom - 2; + SimpleAlphaRectangle(surface, rcUnderline, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); + } + marks >>= 1; + } + if (vsDraw.maskInLine) { + int marksMasked = model.pdoc->GetMark(line) & vsDraw.maskInLine; + if (marksMasked) { + for (markBit = 0; (markBit < 32) && marksMasked; markBit++) { + if ((marksMasked & 1) && (vsDraw.markers[markBit].markType != SC_MARK_EMPTY)) { + SimpleAlphaRectangle(surface, rcLine, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); + } + marksMasked >>= 1; + } + } } - InvalidateCaret(); } -void Editor::DropCaret() { - caret.active = false; - InvalidateCaret(); -} +void EditView::PaintText(Surface *surfaceWindow, PRectangle rcArea, PRectangle rcClient, + const EditModel &model, const ViewStyle &vsDraw) { + // Allow text at start of line to overlap 1 pixel into the margin as this displays + // serifs and italic stems for aliased text. + const int leftTextOverlap = ((model.xOffset == 0) && (vsDraw.leftMarginWidth > 0)) ? 1 : 0; -void Editor::CaretSetPeriod(int period) { - if (caret.period != period) { - caret.period = period; - caret.on = true; - InvalidateCaret(); - } -} + // Do the painting + if (rcArea.right > vsDraw.textStart - leftTextOverlap) { -void Editor::InvalidateCaret() { - if (posDrag.IsValid()) { - InvalidateRange(posDrag.Position(), posDrag.Position() + 1); - } else { - for (size_t r=0; rInitialised()); } - } - UpdateSystemCaret(); -} + surface->SetUnicodeMode(SC_CP_UTF8 == model.pdoc->dbcsCodePage); + surface->SetDBCSMode(model.pdoc->dbcsCodePage); -void Editor::UpdateSystemCaret() { -} + Point ptOrigin = model.GetVisibleOriginInMain(); -bool Editor::Wrapping() const { - return vs.wrapState != eWrapNone; -} + int screenLinePaintFirst = static_cast(rcArea.top) / vsDraw.lineHeight; + int ypos = 0; + if (!bufferedDraw) + ypos += screenLinePaintFirst * vsDraw.lineHeight; + int xStart = vsDraw.textStart - model.xOffset + static_cast(ptOrigin.x); -void Editor::NeedWrapping(int docLineStart, int docLineEnd) { -//Platform::DebugPrintf("\nNeedWrapping: %0d..%0d\n", docLineStart, docLineEnd); - if (wrapPending.AddRange(docLineStart, docLineEnd)) { - llc.Invalidate(LineLayout::llPositions); - } - // Wrap lines during idle. - if (Wrapping() && wrapPending.NeedsWrap()) { - SetIdle(true); - } -} + int visibleLine = model.TopLineOfMain() + screenLinePaintFirst; + int yposScreen = screenLinePaintFirst * vsDraw.lineHeight; -bool Editor::WrapOneLine(Surface *surface, int lineToWrap) { - AutoLineLayout ll(llc, RetrieveLineLayout(lineToWrap)); - int linesWrapped = 1; - if (ll) { - LayoutLine(lineToWrap, surface, vs, ll, wrapWidth); - linesWrapped = ll->lines; - } - return cs.SetHeight(lineToWrap, linesWrapped + - (vs.annotationVisible ? pdoc->AnnotationLines(lineToWrap) : 0)); -} + SelectionPosition posCaret = model.sel.RangeMain().caret; + if (model.posDrag.IsValid()) + posCaret = model.posDrag; + int lineCaret = model.pdoc->LineFromPosition(posCaret.Position()); -// Perform wrapping for a subset of the lines needing wrapping. -// wsAll: wrap all lines which need wrapping in this single call -// wsVisible: wrap currently visible lines -// wsIdle: wrap one page + 100 lines -// Return true if wrapping occurred. -bool Editor::WrapLines(enum wrapScope ws) { - int goodTopLine = topLine; - bool wrapOccurred = false; - if (!Wrapping()) { - if (wrapWidth != LineLayout::wrapWidthInfinite) { - wrapWidth = LineLayout::wrapWidthInfinite; - for (int lineDoc = 0; lineDoc < pdoc->LinesTotal(); lineDoc++) { - cs.SetHeight(lineDoc, 1 + - (vs.annotationVisible ? pdoc->AnnotationLines(lineDoc) : 0)); - } - wrapOccurred = true; + PRectangle rcTextArea = rcClient; + if (vsDraw.marginInside) { + rcTextArea.left += vsDraw.textStart; + rcTextArea.right -= vsDraw.rightMarginWidth; + } else { + rcTextArea = rcArea; } - wrapPending.Reset(); - } else if (wrapPending.NeedsWrap()) { - wrapPending.start = std::min(wrapPending.start, pdoc->LinesTotal()); - if (!SetIdle(true)) { - // Idle processing not supported so full wrap required. - ws = wsAll; - } - // Decide where to start wrapping - int lineToWrap = wrapPending.start; - int lineToWrapEnd = std::min(wrapPending.end, pdoc->LinesTotal()); - const int lineDocTop = cs.DocFromDisplay(topLine); - const int subLineTop = topLine - cs.DisplayFromDoc(lineDocTop); - if (ws == wsVisible) { - lineToWrap = Platform::Clamp(lineDocTop-5, wrapPending.start, pdoc->LinesTotal()); - // Priority wrap to just after visible area. - // Since wrapping could reduce display lines, treat each - // as taking only one display line. - lineToWrapEnd = lineDocTop; - int lines = LinesOnScreen() + 1; - while ((lineToWrapEnd < cs.LinesInDoc()) && (lines>0)) { - if (cs.GetVisible(lineToWrapEnd)) - lines--; - lineToWrapEnd++; - } - // .. and if the paint window is outside pending wraps - if ((lineToWrap > wrapPending.end) || (lineToWrapEnd < wrapPending.start)) { - // Currently visible text does not need wrapping - return false; - } - } else if (ws == wsIdle) { - lineToWrapEnd = lineToWrap + LinesOnScreen() + 100; + // Remove selection margin from drawing area so text will not be drawn + // on it in unbuffered mode. + if (!bufferedDraw && vsDraw.marginInside) { + PRectangle rcClipText = rcTextArea; + rcClipText.left -= leftTextOverlap; + surfaceWindow->SetClip(rcClipText); } - const int lineEndNeedWrap = std::min(wrapPending.end, pdoc->LinesTotal()); - lineToWrapEnd = std::min(lineToWrapEnd, lineEndNeedWrap); - // Ensure all lines being wrapped are styled. - pdoc->EnsureStyledTo(pdoc->LineStart(lineToWrapEnd)); + // 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); + while (visibleLine < model.cs.LinesDisplayed() && yposScreen < rcArea.bottom) { - if (lineToWrap < lineToWrapEnd) { + int lineDoc = model.cs.DocFromDisplay(visibleLine); + // Only visible lines should be handled by the code within the loop + PLATFORM_ASSERT(model.cs.GetVisible(lineDoc)); + int lineStartSet = model.cs.DisplayFromDoc(lineDoc); + int subLine = visibleLine - lineStartSet; - PRectangle rcTextArea = GetClientRectangle(); - rcTextArea.left = static_cast(vs.textStart); - rcTextArea.right -= vs.rightMarginWidth; - wrapWidth = static_cast(rcTextArea.Width()); - RefreshStyleData(); - AutoSurface surface(this); - if (surface) { -//Platform::DebugPrintf("Wraplines: scope=%0d need=%0d..%0d perform=%0d..%0d\n", ws, wrapPending.start, wrapPending.end, lineToWrap, lineToWrapEnd); + // 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); + ll.Set(RetrieveLineLayout(lineDoc, model)); + LayoutLine(lineDoc, surface, vsDraw, ll, model, model.wrapWidth); + lineDocPrevious = lineDoc; + } + //durLayout += et.Duration(true); - while (lineToWrap < lineToWrapEnd) { - if (WrapOneLine(surface, lineToWrap)) { - wrapOccurred = true; - } - wrapPending.Wrapped(lineToWrap); - lineToWrap++; + if (ll) { + ll->containsCaret = lineDoc == lineCaret; + if (hideSelection) { + ll->containsCaret = false; } - goodTopLine = cs.DisplayFromDoc(lineDocTop) + std::min(subLineTop, cs.GetHeight(lineDocTop)-1); - } - } - - // If wrapping is done, bring it to resting position - if (wrapPending.start >= lineEndNeedWrap) { - wrapPending.Reset(); - } - } + ll->hotspot = model.GetHotSpotRange(); - if (wrapOccurred) { - SetScrollBars(); - SetTopLine(Platform::Clamp(goodTopLine, 0, MaxScrollPos())); - SetVerticalScrollPos(); - } + PRectangle rcLine = rcTextArea; + rcLine.top = static_cast(ypos); + rcLine.bottom = static_cast(ypos + vsDraw.lineHeight); - return wrapOccurred; -} + bool bracesIgnoreStyle = false; + if ((vsDraw.braceHighlightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACELIGHT)) || + (vsDraw.braceBadLightIndicatorSet && (model.bracesMatchStyle == STYLE_BRACEBAD))) { + bracesIgnoreStyle = true; + } + Range rangeLine(model.pdoc->LineStart(lineDoc), model.pdoc->LineStart(lineDoc + 1)); + // Highlight the current braces if any + ll->SetBracesHighlight(rangeLine, model.braces, static_cast(model.bracesMatchStyle), + static_cast(model.highlightGuideColumn * vsDraw.spaceWidth), bracesIgnoreStyle); -void Editor::LinesJoin() { - if (!RangeContainsProtected(targetStart, targetEnd)) { - UndoGroup ug(pdoc); - bool prevNonWS = true; - for (int pos = targetStart; pos < targetEnd; pos++) { - if (pdoc->IsPositionInLineEnd(pos)) { - targetEnd -= pdoc->LenChar(pos); - pdoc->DelChar(pos); - if (prevNonWS) { - // Ensure at least one space separating previous lines - const int lengthInserted = pdoc->InsertString(pos, " ", 1); - targetEnd += lengthInserted; + if (leftTextOverlap && bufferedDraw) { + PRectangle rcSpacer = rcLine; + rcSpacer.right = rcSpacer.left; + rcSpacer.left -= 1; + surface->FillRectangle(rcSpacer, vsDraw.styles[STYLE_DEFAULT].back); } - } else { - prevNonWS = pdoc->CharAt(pos) != ' '; - } - } - } -} -const char *Editor::StringFromEOLMode(int eolMode) { - if (eolMode == SC_EOL_CRLF) { - return "\r\n"; - } else if (eolMode == SC_EOL_CR) { - return "\r"; - } else { - return "\n"; - } -} + // Draw the line + DrawLine(surface, vsDraw, lineDoc, visibleLine, xStart, rcLine, ll, subLine, model); + //durPaint += et.Duration(true); -void Editor::LinesSplit(int pixelWidth) { - if (!RangeContainsProtected(targetStart, targetEnd)) { - if (pixelWidth == 0) { - PRectangle rcText = GetTextRectangle(); - pixelWidth = static_cast(rcText.Width()); - } - int lineStart = pdoc->LineFromPosition(targetStart); - int lineEnd = pdoc->LineFromPosition(targetEnd); - const char *eol = StringFromEOLMode(pdoc->eolMode); - UndoGroup ug(pdoc); - 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); - int lengthInsertedTotal = 0; - for (int subLine = 1; subLine < ll->lines; subLine++) { - const int lengthInserted = pdoc->InsertString( - static_cast(posLineStart + lengthInsertedTotal + - ll->LineStart(subLine)), - eol, istrlen(eol)); - targetEnd += lengthInserted; - lengthInsertedTotal += lengthInserted; + // Restore the previous styles for the brace highlights in case layout is in cache. + ll->RestoreBracesHighlight(rangeLine, model.braces, bracesIgnoreStyle); + + bool expanded = model.cs.GetExpanded(lineDoc); + const int level = model.pdoc->GetLevel(lineDoc); + const int levelNext = model.pdoc->GetLevel(lineDoc + 1); + if ((level & SC_FOLDLEVELHEADERFLAG) && + ((level & SC_FOLDLEVELNUMBERMASK) < (levelNext & SC_FOLDLEVELNUMBERMASK))) { + // Paint the line above the fold + if ((expanded && (model.foldFlags & SC_FOLDFLAG_LINEBEFORE_EXPANDED)) + || + (!expanded && (model.foldFlags & SC_FOLDFLAG_LINEBEFORE_CONTRACTED))) { + PRectangle rcFoldLine = rcLine; + rcFoldLine.bottom = rcFoldLine.top + 1; + surface->FillRectangle(rcFoldLine, vsDraw.styles[STYLE_DEFAULT].fore); + } + // Paint the line below the fold + if ((expanded && (model.foldFlags & SC_FOLDFLAG_LINEAFTER_EXPANDED)) + || + (!expanded && (model.foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { + PRectangle rcFoldLine = rcLine; + rcFoldLine.top = rcFoldLine.bottom - 1; + surface->FillRectangle(rcFoldLine, vsDraw.styles[STYLE_DEFAULT].fore); + } } - } - lineEnd = pdoc->LineFromPosition(targetEnd); - } - } -} -int Editor::SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) const { - if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) - return markerDefault; - return markerCheck; -} + DrawCarets(surface, vsDraw, lineDoc, xStart, rcLine, ll, subLine, model); -bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) { - if (st.multipleStyles) { - for (size_t iStyle=0; iStyle(rcClient.right - vsDraw.rightMarginWidth), + yposScreen + vsDraw.lineHeight); + surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); + } -static int WidthStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, - const char *text, const unsigned char *styles, size_t len) { - int width = 0; - size_t start = 0; - while (start < len) { - size_t style = styles[start]; - size_t endSegment = start; - while ((endSegment+1 < len) && (static_cast(styles[endSegment+1]) == style)) - endSegment++; - FontAlias fontText = vs.styles[style + styleOffset].font; - width += static_cast(surface->WidthText(fontText, text + start, - static_cast(endSegment - start + 1))); - start = endSegment + 1; - } - return width; -} + lineWidthMaxSeen = Platform::Maximum( + lineWidthMaxSeen, static_cast(ll->positions[ll->numCharsInLine])); + //durCopy += et.Duration(true); + } -static int WidestLineWidth(Surface *surface, const ViewStyle &vs, int styleOffset, const StyledText &st) { - int widthMax = 0; - size_t start = 0; - while (start < st.length) { - size_t lenLine = st.LineLength(start); - int widthSubLine; - if (st.multipleStyles) { - widthSubLine = WidthStyledText(surface, vs, styleOffset, st.text + start, st.styles + start, lenLine); - } else { - FontAlias fontText = vs.styles[styleOffset + st.style].font; - widthSubLine = static_cast(surface->WidthText(fontText, - st.text + start, static_cast(lenLine))); - } - if (widthSubLine > widthMax) - widthMax = widthSubLine; - start += lenLine + 1; - } - return widthMax; -} + if (!bufferedDraw) { + ypos += vsDraw.lineHeight; + } -static void DrawTextInStyle(Surface *surface, PRectangle rcText, const Style &style, XYPOSITION ybase, const char *s, size_t length) { - FontAlias fontText = style.font; - surface->DrawTextNoClip(rcText, fontText, ybase, s, static_cast(length), - style.fore, style.back); -} + yposScreen += vsDraw.lineHeight; + visibleLine++; -static void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText, - const StyledText &st, size_t start, size_t length) { + //gdk_flush(); + } + ll.Set(0); + //if (durPaint < 0.00000001) + // durPaint = 0.00000001; - if (st.multipleStyles) { - int x = static_cast(rcText.left); - size_t i = 0; - while (i < length) { - size_t end = i; - size_t style = st.styles[i + start]; - while (end < length-1 && st.styles[start+end+1] == style) - end++; - style += styleOffset; - FontAlias fontText = vs.styles[style].font; - const int width = static_cast(surface->WidthText(fontText, - st.text + start + i, static_cast(end - i + 1))); - PRectangle rcSegment = rcText; - rcSegment.left = static_cast(x); - rcSegment.right = static_cast(x + width + 1); - DrawTextInStyle(surface, rcSegment, vs.styles[style], rcText.top + vs.maxAscent, - st.text + start + i, end - i + 1); - x += width; - i = end + 1; + // Right column limit indicator + PRectangle rcBeyondEOF = (vsDraw.marginInside) ? rcClient : rcArea; + rcBeyondEOF.left = static_cast(vsDraw.textStart); + rcBeyondEOF.right = rcBeyondEOF.right - ((vsDraw.marginInside) ? vsDraw.rightMarginWidth : 0); + rcBeyondEOF.top = static_cast((model.cs.LinesDisplayed() - model.TopLineOfMain()) * vsDraw.lineHeight); + if (rcBeyondEOF.top < rcBeyondEOF.bottom) { + surfaceWindow->FillRectangle(rcBeyondEOF, vsDraw.styles[STYLE_DEFAULT].back); + if (vsDraw.edgeState == EDGE_LINE) { + int edgeX = static_cast(vsDraw.theEdge * vsDraw.spaceWidth); + rcBeyondEOF.left = static_cast(edgeX + xStart); + rcBeyondEOF.right = rcBeyondEOF.left + 1; + surfaceWindow->FillRectangle(rcBeyondEOF, vsDraw.edgecolour); + } } - } else { - const size_t style = st.style + styleOffset; - DrawTextInStyle(surface, rcText, vs.styles[style], rcText.top + vs.maxAscent, - st.text + start, length); + //Platform::DebugPrintf("start display %d, offset = %d\n", pdoc->Length(), xOffset); + + //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()); } } -void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { - if (vs.fixedColumnWidth == 0) - return; - - AllocateGraphics(); - RefreshStyleData(); - RefreshPixMaps(surfWindow); +// Space (3 space characters) between line numbers and text when printing. +#define lineNumberPrintSpace " " - // On GTK+ with Ubuntu overlay scroll bars, the surface may have been finished - // at this point. The Initialised call checks for this case and sets the status - // to be bad which avoids crashes in following calls. - if (!surfWindow->Initialised()) { - return; - } +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)); +} - PRectangle rcMargin = GetClientRectangle(); - Point ptOrigin = GetVisibleOriginInMain(); - rcMargin.Move(0, -ptOrigin.y); - rcMargin.left = 0; - rcMargin.right = static_cast(vs.fixedColumnWidth); +long EditView::FormatRange(bool draw, Sci_RangeToFormat *pfr, Surface *surface, Surface *surfaceMeasure, + const EditModel &model, const ViewStyle &vs) { + // Can't use measurements cached for screen + posCache.Clear(); - if (!rc.Intersects(rcMargin)) - return; + ViewStyle vsPrint(vs); + vsPrint.technology = SC_TECHNOLOGY_DEFAULT; - Surface *surface; - if (bufferedDraw) { - surface = pixmapSelMargin; - } else { - surface = surfWindow; + // 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 <= SC_MAX_MARGIN; margin++) { + if ((vsPrint.ms[margin].style == SC_MARGIN_NUMBER) && (vsPrint.ms[margin].width > 0)) { + lineNumberIndex = margin; + } else { + vsPrint.ms[margin].width = 0; + } } + vsPrint.fixedColumnWidth = 0; + vsPrint.zoomLevel = printParameters.magnification; + // Don't show indentation guides + // If this ever gets changed, cached pixmap would need to be recreated if technology != SC_TECHNOLOGY_DEFAULT + vsPrint.viewIndentationGuides = ivNone; + // Don't show the selection when printing + vsPrint.selColours.back.isSet = false; + vsPrint.selColours.fore.isSet = false; + vsPrint.selAlpha = SC_ALPHA_NOALPHA; + vsPrint.selAdditionalAlpha = SC_ALPHA_NOALPHA; + vsPrint.whitespaceColours.back.isSet = false; + vsPrint.whitespaceColours.fore.isSet = false; + vsPrint.showCaretLineBackground = false; + vsPrint.alwaysShowCaretLineBackground = false; + // Don't highlight matching braces using indicators + vsPrint.braceHighlightIndicatorSet = false; + vsPrint.braceBadLightIndicatorSet = false; - // Clip vertically to paint area to avoid drawing line numbers - if (rcMargin.bottom > rc.bottom) - rcMargin.bottom = rc.bottom; - if (rcMargin.top < rc.top) - rcMargin.top = rc.top; - - PRectangle rcSelMargin = rcMargin; - rcSelMargin.right = rcMargin.left; - if (rcSelMargin.bottom < rc.bottom) - rcSelMargin.bottom = rc.bottom; + // Set colours for printing according to users settings + for (size_t sty = 0; sty < vsPrint.styles.size(); sty++) { + if (printParameters.colourMode == SC_PRINT_INVERTLIGHT) { + vsPrint.styles[sty].fore = InvertedLight(vsPrint.styles[sty].fore); + vsPrint.styles[sty].back = InvertedLight(vsPrint.styles[sty].back); + } else if (printParameters.colourMode == SC_PRINT_BLACKONWHITE) { + vsPrint.styles[sty].fore = ColourDesired(0, 0, 0); + vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); + } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITE) { + vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); + } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { + if (sty <= STYLE_DEFAULT) { + vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); + } + } + } + // White background for the line numbers + vsPrint.styles[STYLE_LINENUMBER].back = ColourDesired(0xff, 0xff, 0xff); - for (int margin = 0; margin <= SC_MAX_MARGIN; margin++) { - if (vs.ms[margin].width > 0) { + // Printing uses different margins, so reset screen margins + vsPrint.leftMarginWidth = 0; + vsPrint.rightMarginWidth = 0; - rcSelMargin.left = rcSelMargin.right; - rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; + vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); + // Determining width must happen after fonts have been realised in Refresh + int lineNumberWidth = 0; + if (lineNumberIndex >= 0) { + lineNumberWidth = static_cast(surfaceMeasure->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, + "99999" lineNumberPrintSpace, 5 + static_cast(strlen(lineNumberPrintSpace)))); + vsPrint.ms[lineNumberIndex].width = lineNumberWidth; + vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Recalculate fixedColumnWidth + } - if (vs.ms[margin].style != SC_MARGIN_NUMBER) { - if (vs.ms[margin].mask & SC_MASK_FOLDERS) { - // Required because of special way brush is created for selection margin - // Ensure patterns line up when scrolling with separate margin view - // by choosing correctly aligned variant. - bool invertPhase = static_cast(ptOrigin.y) & 1; - surface->FillRectangle(rcSelMargin, - invertPhase ? *pixmapSelPattern : *pixmapSelPatternOffset1); - } else { - ColourDesired colour; - switch (vs.ms[margin].style) { - case SC_MARGIN_BACK: - colour = vs.styles[STYLE_DEFAULT].back; - break; - case SC_MARGIN_FORE: - colour = vs.styles[STYLE_DEFAULT].fore; - break; - default: - colour = vs.styles[STYLE_LINENUMBER].back; - break; - } - surface->FillRectangle(rcSelMargin, colour); - } - } else { - surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back); - } + int linePrintStart = model.pdoc->LineFromPosition(pfr->chrg.cpMin); + int linePrintLast = linePrintStart + (pfr->rc.bottom - pfr->rc.top) / vsPrint.lineHeight - 1; + if (linePrintLast < linePrintStart) + linePrintLast = linePrintStart; + int linePrintMax = model.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 = model.pdoc->Length(); + if (linePrintLast < model.pdoc->LinesTotal()) + endPosPrint = model.pdoc->LineStart(linePrintLast + 1); - const int lineStartPaint = static_cast(rcMargin.top + ptOrigin.y) / vs.lineHeight; - int visibleLine = TopLineOfMain() + lineStartPaint; - int yposScreen = lineStartPaint * vs.lineHeight - static_cast(ptOrigin.y); - // 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; - if (vs.ms[margin].mask & SC_MASK_FOLDERS) { - int level = pdoc->GetLevel(cs.DocFromDisplay(visibleLine)); - if (level & SC_FOLDLEVELWHITEFLAG) { - int lineBack = cs.DocFromDisplay(visibleLine); - 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; - } - } - if (highlightDelimiter.isEnabled) { - int lastLine = cs.DocFromDisplay(topLine + LinesOnScreen()) + 1; - pdoc->GetHighlightDelimiters(highlightDelimiter, pdoc->LineFromPosition(CurrentPosition()), lastLine); - } - } + // Ensure we are styled to where we are formatting. + model.pdoc->EnsureStyledTo(endPosPrint); - // Old code does not know about new markers needed to distinguish all cases - const int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID, - SC_MARKNUM_FOLDEROPEN); - const int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND, - SC_MARKNUM_FOLDER); + int xStart = vsPrint.fixedColumnWidth + pfr->rc.left; + int ypos = pfr->rc.top; - while ((visibleLine < cs.LinesDisplayed()) && yposScreen < rc.bottom) { + int lineDoc = linePrintStart; - PLATFORM_ASSERT(visibleLine < cs.LinesDisplayed()); - const int lineDoc = cs.DocFromDisplay(visibleLine); - PLATFORM_ASSERT(cs.GetVisible(lineDoc)); - const bool firstSubLine = visibleLine == cs.DisplayFromDoc(lineDoc); - const bool lastSubLine = visibleLine == cs.DisplayLastFromDoc(lineDoc); + int nPrintPos = pfr->chrg.cpMin; + int visibleLine = 0; + int widthPrint = pfr->rc.right - pfr->rc.left - vsPrint.fixedColumnWidth; + if (printParameters.wrapState == eWrapNone) + widthPrint = LineLayout::wrapWidthInfinite; - int marks = pdoc->GetMark(lineDoc); - if (!firstSubLine) - marks = 0; + while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) { - bool headWithTail = false; + // 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(); - if (vs.ms[margin].mask & SC_MASK_FOLDERS) { - // Decide which fold indicator should be displayed - const int level = pdoc->GetLevel(lineDoc); - const int levelNext = pdoc->GetLevel(lineDoc + 1); - const int levelNum = level & SC_FOLDLEVELNUMBERMASK; - const int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; - if (level & SC_FOLDLEVELHEADERFLAG) { - if (firstSubLine) { - if (levelNum < levelNextNum) { - 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 if (levelNum > SC_FOLDLEVELBASE) { - marks |= 1 << SC_MARKNUM_FOLDERSUB; - } - } else { - if (levelNum < levelNextNum) { - if (cs.GetExpanded(lineDoc)) { - marks |= 1 << SC_MARKNUM_FOLDERSUB; - } else if (levelNum > SC_FOLDLEVELBASE) { - marks |= 1 << SC_MARKNUM_FOLDERSUB; - } - } else if (levelNum > SC_FOLDLEVELBASE) { - marks |= 1 << SC_MARKNUM_FOLDERSUB; - } - } - needWhiteClosure = false; - const int firstFollowupLine = cs.DocFromDisplay(cs.DisplayFromDoc(lineDoc + 1)); - const int firstFollowupLineLevel = pdoc->GetLevel(firstFollowupLine); - const int secondFollowupLineLevelNum = pdoc->GetLevel(firstFollowupLine + 1) & SC_FOLDLEVELNUMBERMASK; - if (!cs.GetExpanded(lineDoc)) { - if ((firstFollowupLineLevel & SC_FOLDLEVELWHITEFLAG) && - (levelNum > secondFollowupLineLevelNum)) - needWhiteClosure = true; + // Copy this line and its styles from the document into local arrays + // and determine the x position at which each character starts. + LineLayout ll(model.pdoc->LineStart(lineDoc + 1) - model.pdoc->LineStart(lineDoc) + 1); + LayoutLine(lineDoc, surfaceMeasure, vsPrint, &ll, model, widthPrint); - if (highlightDelimiter.IsFoldBlockHighlighted(firstFollowupLine)) - headWithTail = true; - } - } else if (level & SC_FOLDLEVELWHITEFLAG) { - if (needWhiteClosure) { - if (levelNext & SC_FOLDLEVELWHITEFLAG) { - marks |= 1 << SC_MARKNUM_FOLDERSUB; - } else if (levelNextNum > 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 (lastSubLine) { - if (levelNextNum > SC_FOLDLEVELBASE) { - marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; - } else { - marks |= 1 << SC_MARKNUM_FOLDERTAIL; - } - } else { - marks |= 1 << SC_MARKNUM_FOLDERSUB; - } - } else { - marks |= 1 << SC_MARKNUM_FOLDERSUB; - } - } - } + ll.containsCaret = false; - marks &= vs.ms[margin].mask; + PRectangle rcLine = PRectangle::FromInts( + pfr->rc.left, + ypos, + pfr->rc.right - 1, + ypos + vsPrint.lineHeight); - PRectangle rcMarker = rcSelMargin; - rcMarker.top = static_cast(yposScreen); - rcMarker.bottom = static_cast(yposScreen + vs.lineHeight); - if (vs.ms[margin].style == SC_MARGIN_NUMBER) { - if (firstSubLine) { - char number[100] = ""; - if (lineDoc >= 0) - sprintf(number, "%d", lineDoc + 1); - if (foldFlags & (SC_FOLDFLAG_LEVELNUMBERS | SC_FOLDFLAG_LINESTATE)) { - 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 - ); - } else { - int state = pdoc->GetLineState(lineDoc); - sprintf(number, "%0X", state); - } - } - PRectangle rcNumber = rcMarker; - // Right justify - XYPOSITION width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, istrlen(number)); - XYPOSITION xpos = rcNumber.right - width - vs.marginNumberPadding; - rcNumber.left = xpos; - DrawTextInStyle(surface, rcNumber, vs.styles[STYLE_LINENUMBER], - rcNumber.top + vs.maxAscent, number, strlen(number)); - } else if (vs.wrapVisualFlags & SC_WRAPVISUALFLAG_MARGIN) { - PRectangle rcWrapMarker = rcMarker; - rcWrapMarker.right -= 3; - rcWrapMarker.left = rcWrapMarker.right - vs.styles[STYLE_LINENUMBER].aveCharWidth; - DrawWrapMarker(surface, rcWrapMarker, false, vs.styles[STYLE_LINENUMBER].fore); - } - } else if (vs.ms[margin].style == SC_MARGIN_TEXT || vs.ms[margin].style == SC_MARGIN_RTEXT) { - if (firstSubLine) { - const StyledText stMargin = pdoc->MarginStyledText(lineDoc); - if (stMargin.text && ValidStyledText(vs, vs.marginStyleOffset, stMargin)) { - surface->FillRectangle(rcMarker, - vs.styles[stMargin.StyleAt(0)+vs.marginStyleOffset].back); - if (vs.ms[margin].style == SC_MARGIN_RTEXT) { - int width = WidestLineWidth(surface, vs, vs.marginStyleOffset, stMargin); - rcMarker.left = rcMarker.right - width - 3; - } - DrawStyledText(surface, vs, vs.marginStyleOffset, rcMarker, - stMargin, 0, stMargin.length); - } - } + // 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 - model.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 (marks) { - for (int markBit = 0; (markBit < 32) && marks; markBit++) { - if (marks & 1) { - LineMarker::typeOfFold tFold = LineMarker::undefined; - if ((vs.ms[margin].mask & SC_MASK_FOLDERS) && highlightDelimiter.IsFoldBlockHighlighted(lineDoc)) { - if (highlightDelimiter.IsBodyOfFoldBlock(lineDoc)) { - tFold = LineMarker::body; - } else if (highlightDelimiter.IsHeadOfFoldBlock(lineDoc)) { - if (firstSubLine) { - tFold = headWithTail ? LineMarker::headWithTail : LineMarker::head; - } else { - if (cs.GetExpanded(lineDoc) || headWithTail) { - tFold = LineMarker::body; - } else { - tFold = LineMarker::undefined; - } - } - } else if (highlightDelimiter.IsTailOfFoldBlock(lineDoc)) { - tFold = LineMarker::tail; - } - } - vs.markers[markBit].Draw(surface, rcMarker, vs.styles[STYLE_LINENUMBER].font, tFold, vs.ms[margin].style); - } - marks >>= 1; + 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 = rcNumber.right - surfaceMeasure->WidthText( + vsPrint.styles[STYLE_LINENUMBER].font, number, static_cast(strlen(number))); + surface->FlushCachedState(); + surface->DrawTextNoClip(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, + static_cast(ypos + vsPrint.maxAscent), number, static_cast(strlen(number)), + vsPrint.styles[STYLE_LINENUMBER].fore, + vsPrint.styles[STYLE_LINENUMBER].back); + } + + // 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 = static_cast(ypos); + rcLine.bottom = static_cast(ypos + vsPrint.lineHeight); + DrawLine(surface, vsPrint, lineDoc, visibleLine, xStart, rcLine, &ll, iwl, model); } + ypos += vsPrint.lineHeight; } - visibleLine++; - yposScreen += vs.lineHeight; + if (iwl == ll.lines - 1) + nPrintPos = model.pdoc->LineStart(lineDoc + 1); + else + nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl); } } + + ++lineDoc; } - PRectangle rcBlankMargin = rcMargin; - rcBlankMargin.left = rcSelMargin.right; - surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back); + // Clear cache so measurements are not used for screen + posCache.Clear(); - if (bufferedDraw) { - surfWindow->Copy(rcMargin, Point(rcMargin.left, rcMargin.top), *pixmapSelMargin); - } + return nPrintPos; } -void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { - int ydiff = static_cast(rcTab.bottom - rcTab.top) / 2; - int xhead = static_cast(rcTab.right) - 1 - ydiff; - if (xhead <= rcTab.left) { - ydiff -= static_cast(rcTab.left) - xhead - 1; - xhead = static_cast(rcTab.left) - 1; - } - if ((rcTab.left + 2) < (rcTab.right - 1)) - surface->MoveTo(static_cast(rcTab.left) + 2, ymid); - else - surface->MoveTo(static_cast(rcTab.right) - 1, ymid); - surface->LineTo(static_cast(rcTab.right) - 1, ymid); - surface->LineTo(xhead, ymid - ydiff); - surface->MoveTo(static_cast(rcTab.right) - 1, ymid); - surface->LineTo(xhead, ymid + ydiff); -} +Editor::Editor() { + ctrlID = 0; -LineLayout *Editor::RetrieveLineLayout(int lineNumber) { - int posLineStart = pdoc->LineStart(lineNumber); - int posLineEnd = pdoc->LineStart(lineNumber + 1); - PLATFORM_ASSERT(posLineEnd >= posLineStart); - int lineCaret = pdoc->LineFromPosition(sel.MainCaret()); - return llc.Retrieve(lineNumber, lineCaret, - posLineEnd - posLineStart, pdoc->GetStyleClock(), - LinesOnScreen() + 1, pdoc->LinesTotal()); -} + stylesValid = false; + technology = SC_TECHNOLOGY_DEFAULT; + scaleRGBAImage = 100.0f; -/** - * 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, const ViewStyle &vstyle, LineLayout *ll, int width) { - if (!ll) - return; + cursorMode = SC_CURSORNORMAL; - PLATFORM_ASSERT(line < pdoc->LinesTotal()); - PLATFORM_ASSERT(ll->chars != NULL); - 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) { - lineLength = pdoc->LineEnd(line) - posLineStart; - } - if (lineLength == ll->numCharsInLine) { - // See if chars, styles, indicators, are all the same - bool allSame = true; - // 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(styleByte)); - 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(tolower(chDoc))); - else // Style::caseUpper - allSame = allSame && - (ll->chars[numCharsInLine] == static_cast(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; - if (vstyle.edgeState == EDGE_BACKGROUND) { - ll->edgeColumn = pdoc->FindColumn(line, vstyle.theEdge); - if (ll->edgeColumn >= posLineStart) { - ll->edgeColumn -= posLineStart; - } - } else { - ll->edgeColumn = -1; - } + hasFocus = false; + errorStatus = 0; + mouseDownCaptures = true; - // Fill base line layout - const int lineLength = posLineEnd - posLineStart; - pdoc->GetCharRange(ll->chars, posLineStart, lineLength); - pdoc->GetStyleRange(ll->styles, posLineStart, lineLength); - int numCharsBeforeEOL = pdoc->LineEnd(line) - posLineStart; - const int numCharsInLine = (vstyle.viewEOL) ? lineLength : numCharsBeforeEOL; - for (int styleInLine = 0; styleInLine < numCharsInLine; styleInLine++) { - const unsigned char styleByte = ll->styles[styleInLine]; - ll->styles[styleInLine] = styleByte; - } - const unsigned char styleByteLast = (lineLength > 0) ? ll->styles[lineLength-1] : 0; - if (vstyle.someStylesForceCase) { - for (int charInLine = 0; charInLinechars[charInLine]; - if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseUpper) - ll->chars[charInLine] = static_cast(toupper(chDoc)); - else if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseLower) - ll->chars[charInLine] = static_cast(tolower(chDoc)); - } - } - 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] = styleByteLast; // For eolFilled + lastClickTime = 0; + dwellDelay = SC_TIME_FOREVER; + ticksToDwell = SC_TIME_FOREVER; + dwelling = false; + ptMouseLast.x = 0; + ptMouseLast.y = 0; + inDragDrop = ddNone; + dropWentOutside = false; + posDrop = SelectionPosition(invalidPosition); + hotSpotClickPos = INVALID_POSITION; + selectionType = selChar; - // Layout the line, determining the position of each character, - // with an extra element at the end for the end of the line. - ll->positions[0] = 0; - bool lastSegItalics = false; + lastXChosen = 0; + lineAnchorPos = 0; + originalAnchorPos = 0; + wordSelectAnchorStartPos = 0; + wordSelectAnchorEndPos = 0; + wordSelectInitialCaretPos = -1; - BreakFinder bfLayout(ll, NULL, 0, numCharsInLine, posLineStart, 0, false, pdoc, &reprs); - while (bfLayout.More()) { + caretXPolicy = CARET_SLOP | CARET_EVEN; + caretXSlop = 50; - const TextSegment ts = bfLayout.Next(); + caretYPolicy = CARET_EVEN; + caretYSlop = 0; - std::fill(&ll->positions[ts.start+1], &ll->positions[ts.end()+1], 0.0f); - if (vstyle.styles[ll->styles[ts.start]].visible) { - if (ts.representation) { - XYPOSITION representationWidth = vstyle.controlCharWidth; - if (ll->chars[ts.start] == '\t') { - // Tab is a special case of representation, taking a variable amount of space - representationWidth = - ((static_cast((ll->positions[ts.start] + 2) / vstyle.tabWidth) + 1) * vstyle.tabWidth) - ll->positions[ts.start]; - } else { - if (representationWidth <= 0.0) { - XYPOSITION positionsRepr[256]; // Should expand when needed - posCache.MeasureWidths(surface, vstyle, STYLE_CONTROLCHAR, ts.representation->stringRep.c_str(), - static_cast(ts.representation->stringRep.length()), positionsRepr, pdoc); - representationWidth = positionsRepr[ts.representation->stringRep.length()-1] + vstyle.ctrlCharPadding; - } - } - for (int ii=0; ii < ts.length; ii++) - ll->positions[ts.start + 1 + ii] = representationWidth; - } else { - if ((ts.length == 1) && (' ' == ll->chars[ts.start])) { - // Over half the segments are single characters and of these about half are space characters. - ll->positions[ts.start + 1] = vstyle.styles[ll->styles[ts.start]].spaceWidth; - } else { - posCache.MeasureWidths(surface, vstyle, ll->styles[ts.start], ll->chars + ts.start, - ts.length, ll->positions + ts.start + 1, pdoc); - } - } - lastSegItalics = (!ts.representation) && ((ll->chars[ts.end()-1] != ' ') && vstyle.styles[ll->styles[ts.start]].italic); - } + visiblePolicy = 0; + visibleSlop = 0; - for (int posToIncrease = ts.start+1; posToIncrease <= ts.end(); posToIncrease++) { - ll->positions[posToIncrease] += ll->positions[ts.start]; - } - } + searchAnchor = 0; - // Small hack to make lines that end with italics not cut off the edge of the last character - if (lastSegItalics) { - ll->positions[numCharsInLine] += vstyle.lastSegItalicsOffset; - } - ll->numCharsInLine = numCharsInLine; - ll->numCharsBeforeEOL = numCharsBeforeEOL; - ll->validity = LineLayout::llPositions; + xCaretMargin = 50; + horizontalScrollBarVisible = true; + scrollWidth = 2000; + verticalScrollBarVisible = true; + endAtLastLine = true; + caretSticky = SC_CARETSTICKY_OFF; + marginOptions = SC_MARGINOPTION_NONE; + mouseSelectionRectangularSwitch = false; + multipleSelection = false; + additionalSelectionTyping = false; + multiPasteMode = SC_MULTIPASTE_ONCE; + virtualSpaceOptions = SCVS_NONE; + + targetStart = 0; + targetEnd = 0; + searchFlags = 0; + + topLine = 0; + posTopLine = 0; + + lengthForEncode = -1; + + needUpdateUI = 0; + ContainerNeedsUpdate(SC_UPDATE_CONTENT); + + paintState = notPainting; + paintAbandonedByStyling = false; + paintingAllText = false; + willRedrawAll = false; + + modEventMask = SC_MODEVENTMASKALL; + + pdoc->AddWatcher(this, 0); + + recordingMacro = false; + foldAutomatic = 0; + + convertPastes = true; + + SetRepresentations(); +} + +Editor::~Editor() { + pdoc->RemoveWatcher(this, 0); + DropGraphics(true); +} + +void Editor::Finalise() { + SetIdle(false); + CancelModes(); +} + +void Editor::SetRepresentations() { + reprs.Clear(); + + // C0 control set + 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" + }; + for (size_t j=0; j < ELEMENTS(reps); j++) { + char c[2] = { static_cast(j), 0 }; + reprs.SetRepresentation(c, reps[j]); } - // Hard to cope when too narrow, so just assume there is space - if (width < 20) { - width = 20; + + // C1 control set + // As well as Unicode mode, ISO-8859-1 should use these + if (IsUnicodeMode()) { + const char *repsC1[] = { + "PAD", "HOP", "BPH", "NBH", "IND", "NEL", "SSA", "ESA", + "HTS", "HTJ", "VTS", "PLD", "PLU", "RI", "SS2", "SS3", + "DCS", "PU1", "PU2", "STS", "CCH", "MW", "SPA", "EPA", + "SOS", "SGCI", "SCI", "CSI", "ST", "OSC", "PM", "APC" + }; + for (size_t j=0; j < ELEMENTS(repsC1); j++) { + char c1[3] = { '\xc2', static_cast(0x80+j), 0 }; + reprs.SetRepresentation(c1, repsC1[j]); + } + reprs.SetRepresentation("\xe2\x80\xa8", "LS"); + reprs.SetRepresentation("\xe2\x80\xa9", "PS"); } - 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 (vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { - width -= static_cast(vstyle.aveCharWidth); // take into account the space for end wrap mark - } - XYPOSITION wrapAddIndent = 0; // This will be added to initial indent of line - if (vstyle.wrapIndentMode == SC_WRAPINDENT_INDENT) { - wrapAddIndent = pdoc->IndentSize() * vstyle.spaceWidth; - } else if (vstyle.wrapIndentMode == SC_WRAPINDENT_FIXED) { - wrapAddIndent = vstyle.wrapVisualStartIndent * vstyle.aveCharWidth; - } - ll->wrapIndent = wrapAddIndent; - if (vstyle.wrapIndentMode != SC_WRAPINDENT_FIXED) - for (int i = 0; i < ll->numCharsInLine; i++) { - if (!IsSpaceOrTab(ll->chars[i])) { - ll->wrapIndent += ll->positions[i]; // Add line indent - break; - } - } - // Check for text width minimum - if (ll->wrapIndent > width - static_cast(vstyle.aveCharWidth) * 15) - ll->wrapIndent = wrapAddIndent; - // Check for wrapIndent minimum - if ((vstyle.wrapVisualFlags & SC_WRAPVISUALFLAG_START) && (ll->wrapIndent < vstyle.aveCharWidth)) - ll->wrapIndent = vstyle.aveCharWidth; // Indent to show start visual - ll->lines = 0; - // Calculate line start positions based upon width. - int lastGoodBreak = 0; - int lastLineStart = 0; - XYACCUMULATOR 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 -= ll->wrapIndent; - p = lastGoodBreak + 1; - continue; - } - if (p > 0) { - if (vstyle.wrapState == eWrapChar) { - lastGoodBreak = pdoc->MovePositionOutsideChar(p + posLineStart, -1) - - posLineStart; - p = pdoc->MovePositionOutsideChar(p + 1 + posLineStart, 1) - posLineStart; - continue; - } else if ((vstyle.wrapState == eWrapWord) && (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++; + + // UTF-8 invalid bytes + if (IsUnicodeMode()) { + for (int k=0x80; k < 0x100; k++) { + char hiByte[2] = { static_cast(k), 0 }; + char hexits[4]; + sprintf(hexits, "x%2X", k); + reprs.SetRepresentation(hiByte, hexits); } - ll->validity = LineLayout::llLines; } } -ColourDesired Editor::SelectionBackground(const ViewStyle &vsDraw, bool main) const { - return main ? - (primarySelection ? vsDraw.selColours.back : vsDraw.selBackground2) : - vsDraw.selAdditionalBackground; +void Editor::DropGraphics(bool freeObjects) { + marginView.DropGraphics(freeObjects); + view.DropGraphics(freeObjects); } -ColourDesired Editor::TextBackground(const ViewStyle &vsDraw, - ColourOptional background, int inSelection, bool inHotspot, int styleMain, int i, LineLayout *ll) const { - if (inSelection == 1) { - if (vsDraw.selColours.back.isSet && (vsDraw.selAlpha == SC_ALPHA_NOALPHA)) { - return SelectionBackground(vsDraw, true); - } - } else if (inSelection == 2) { - if (vsDraw.selColours.back.isSet && (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)) { - return SelectionBackground(vsDraw, false); +void Editor::AllocateGraphics() { + marginView.AllocateGraphics(vs); + view.AllocateGraphics(vs); +} + +void Editor::InvalidateStyleData() { + stylesValid = false; + vs.technology = technology; + DropGraphics(false); + AllocateGraphics(); + view.llc.Invalidate(LineLayout::llInvalid); + view.posCache.Clear(); +} + +void Editor::InvalidateStyleRedraw() { + NeedWrapping(); + InvalidateStyleData(); + Redraw(); +} + +void Editor::RefreshStyleData() { + if (!stylesValid) { + stylesValid = true; + AutoSurface surface(this); + if (surface) { + vs.Refresh(*surface, pdoc->tabInChars); } - } else { - if ((vsDraw.edgeState == EDGE_BACKGROUND) && - (i >= ll->edgeColumn) && - (i < ll->numCharsBeforeEOL)) - return vsDraw.edgecolour; - if (inHotspot && vsDraw.hotspotColours.back.isSet) - return vsDraw.hotspotColours.back; - } - if (background.isSet && (styleMain != STYLE_BRACELIGHT) && (styleMain != STYLE_BRACEBAD)) { - return background; - } else { - return vsDraw.styles[styleMain].back; + SetScrollBars(); + SetRectangularRange(); } } -void Editor::DrawIndentGuide(Surface *surface, int lineVisible, int lineHeight, int start, PRectangle rcSegment, bool highlight) { - Point from = Point::FromInts(0, ((lineVisible & 1) && (lineHeight & 1)) ? 1 : 0); - PRectangle rcCopyArea = PRectangle::FromInts(start + 1, static_cast(rcSegment.top), start + 2, static_cast(rcSegment.bottom)); - surface->Copy(rcCopyArea, from, - highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); +Point Editor::GetVisibleOriginInMain() const { + return Point(0,0); } -void Editor::DrawWrapMarker(Surface *surface, PRectangle rcPlace, - bool isEndMarker, ColourDesired wrapColour) { - surface->PenColour(wrapColour); - - enum { xa = 1 }; // gap before start - int w = static_cast(rcPlace.right - rcPlace.left) - xa - 1; +Point Editor::DocumentPointFromView(Point ptView) const { + Point ptDocument = ptView; + if (wMargin.GetID()) { + Point ptOrigin = GetVisibleOriginInMain(); + ptDocument.x += ptOrigin.x; + ptDocument.y += ptOrigin.y; + } else { + ptDocument.x += xOffset; + ptDocument.y += topLine * vs.lineHeight; + } + return ptDocument; +} - bool xStraight = isEndMarker; // x-mirrored symbol for start marker +int Editor::TopLineOfMain() const { + if (wMargin.GetID()) + return 0; + else + return topLine; +} - int x0 = static_cast(xStraight ? rcPlace.left : rcPlace.right - 1); - int y0 = static_cast(rcPlace.top); +PRectangle Editor::GetClientRectangle() const { + Window &win = const_cast(wMain); + return win.GetClientPosition(); +} - int dy = static_cast(rcPlace.bottom - rcPlace.top) / 5; - int y = static_cast(rcPlace.bottom - rcPlace.top) / 2 + dy; +PRectangle Editor::GetClientDrawingRectangle() { + return GetClientRectangle(); +} - 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, 1}; +PRectangle Editor::GetTextRectangle() const { + PRectangle rc = GetClientRectangle(); + rc.left += vs.textStart; + rc.right -= vs.rightMarginWidth; + return rc; +} - // 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); +int Editor::LinesOnScreen() const { + PRectangle rcClient = GetClientRectangle(); + int htClient = static_cast(rcClient.bottom - rcClient.top); + //Platform::DebugPrintf("lines on screen = %d\n", htClient / lineHeight + 1); + return htClient / vs.lineHeight; +} - // 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); +int Editor::LinesToScroll() const { + int retVal = LinesOnScreen() - 1; + if (retVal < 1) + return 1; + else + return retVal; } -static void SimpleAlphaRectangle(Surface *surface, PRectangle rc, ColourDesired fill, int alpha) { - if (alpha != SC_ALPHA_NOALPHA) { - surface->AlphaRectangle(rc, 0, fill, alpha, fill, alpha, 0); +int Editor::MaxScrollPos() const { + //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; } } -void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment, - const char *s, ColourDesired textBack, ColourDesired textFore, bool twoPhaseDraw) { - if (!twoPhaseDraw) { - surface->FillRectangle(rcSegment, textBack); +SelectionPosition Editor::ClampPositionIntoDocument(SelectionPosition sp) const { + if (sp.Position() < 0) { + return SelectionPosition(0); + } else if (sp.Position() > pdoc->Length()) { + return SelectionPosition(pdoc->Length()); + } else { + // If not at end of line then set offset to 0 + if (!pdoc->IsLineEndPosition(sp.Position())) + sp.SetVirtualSpace(0); + return sp; } - FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; - int normalCharHeight = static_cast(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, s, istrlen(s), - textBack, textFore); } -void Editor::DrawEOL(Surface *surface, const ViewStyle &vsDraw, PRectangle rcLine, LineLayout *ll, - int line, int lineEnd, int xStart, int subLine, XYACCUMULATOR subLineStart, - ColourOptional background) { +Point Editor::LocationFromPosition(SelectionPosition pos) { + RefreshStyleData(); + AutoSurface surface(this); + return view.LocationFromPosition(surface, pos, topLine, *this, vs); +} - const int posLineStart = pdoc->LineStart(line); - PRectangle rcSegment = rcLine; +Point Editor::LocationFromPosition(int pos) { + return LocationFromPosition(SelectionPosition(pos)); +} - const bool lastSubLine = subLine == (ll->lines - 1); - XYPOSITION virtualSpace = 0; - if (lastSubLine) { - const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; - virtualSpace = sel.VirtualSpaceFor(pdoc->LineEnd(line)) * spaceWidth; - } - XYPOSITION xEol = static_cast(ll->positions[lineEnd] - subLineStart); +int Editor::XFromPosition(int pos) { + Point pt = LocationFromPosition(pos); + return static_cast(pt.x) - vs.textStart + xOffset; +} - // Fill the virtual space and show selections within it - if (virtualSpace) { - rcSegment.left = xEol + xStart; - rcSegment.right = xEol + xStart + virtualSpace; - surface->FillRectangle(rcSegment, background.isSet ? background : vsDraw.styles[ll->styles[ll->numCharsInLine]].back); - if (!hideSelection && ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA))) { - SelectionSegment virtualSpaceRange(SelectionPosition(pdoc->LineEnd(line)), SelectionPosition(pdoc->LineEnd(line), sel.VirtualSpaceFor(pdoc->LineEnd(line)))); - for (size_t r=0; rEndLineStyle()].spaceWidth; - rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - - static_cast(subLineStart) + portion.start.VirtualSpace() * spaceWidth; - rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - - static_cast(subLineStart) + portion.end.VirtualSpace() * spaceWidth; - rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; - rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; - surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, r == sel.Main())); - } - } - } - } - } +int Editor::XFromPosition(SelectionPosition sp) { + Point pt = LocationFromPosition(sp); + return static_cast(pt.x) - vs.textStart + xOffset; +} - int eolInSelection = 0; - int alpha = SC_ALPHA_NOALPHA; - if (!hideSelection) { - int posAfterLineEnd = pdoc->LineStart(line + 1); - eolInSelection = (subLine == (ll->lines - 1)) ? sel.InSelectionForEOL(posAfterLineEnd) : 0; - alpha = (eolInSelection == 1) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; - } +SelectionPosition Editor::SPositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition, bool virtualSpace) { + RefreshStyleData(); + AutoSurface surface(this); - // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on - XYPOSITION blobsWidth = 0; - if (lastSubLine) { - for (int eolPos=ll->numCharsBeforeEOL; eolPosnumCharsInLine; eolPos++) { - rcSegment.left = xStart + ll->positions[eolPos] - static_cast(subLineStart) + virtualSpace; - rcSegment.right = xStart + ll->positions[eolPos + 1] - static_cast(subLineStart) + virtualSpace; - blobsWidth += rcSegment.Width(); - char hexits[4]; - const char *ctrlChar; - unsigned char chEOL = ll->chars[eolPos]; - int styleMain = ll->styles[eolPos]; - ColourDesired textBack = TextBackground(vsDraw, background, eolInSelection, false, styleMain, eolPos, ll); - if (UTF8IsAscii(chEOL)) { - ctrlChar = ControlCharacterString(chEOL); - } else { - const Representation *repr = reprs.RepresentationFromCharacter(ll->chars + eolPos, ll->numCharsInLine - eolPos); - if (repr) { - ctrlChar = repr->stringRep.c_str(); - eolPos = ll->numCharsInLine; - } else { - sprintf(hexits, "x%2X", chEOL); - ctrlChar = hexits; - } - } - ColourDesired textFore = vsDraw.styles[styleMain].fore; - if (eolInSelection && vsDraw.selColours.fore.isSet) { - textFore = (eolInSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; - } - if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1)) { - if (alpha == SC_ALPHA_NOALPHA) { - surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); - } else { - surface->FillRectangle(rcSegment, textBack); - } - } else { - surface->FillRectangle(rcSegment, textBack); - } - DrawTextBlob(surface, vsDraw, rcSegment, ctrlChar, textBack, textFore, twoPhaseDraw); - if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { - SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); - } - } + if (canReturnInvalid) { + PRectangle rcClient = GetTextRectangle(); + // May be in scroll view coordinates so translate back to main view + Point ptOrigin = GetVisibleOriginInMain(); + rcClient.Move(-ptOrigin.x, -ptOrigin.y); + if (!rcClient.Contains(pt)) + return SelectionPosition(INVALID_POSITION); + if (pt.x < vs.textStart) + return SelectionPosition(INVALID_POSITION); + if (pt.y < 0) + return SelectionPosition(INVALID_POSITION); } + pt = DocumentPointFromView(pt); + return view.SPositionFromLocation(surface, pt, canReturnInvalid, charPosition, virtualSpace, *this, vs); +} - // Draw the eol-is-selected rectangle - rcSegment.left = xEol + xStart + virtualSpace + blobsWidth; - rcSegment.right = rcSegment.left + vsDraw.aveCharWidth; +int Editor::PositionFromLocation(Point pt, bool canReturnInvalid, bool charPosition) { + return SPositionFromLocation(pt, canReturnInvalid, charPosition, false).Position(); +} - if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { - surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); - } else { - if (background.isSet) { - surface->FillRectangle(rcSegment, background); - } else if (line < pdoc->LinesTotal() - 1) { - surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); - } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) { - surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); - } else { - surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); - } - if (eolInSelection && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { - SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); - } - } +/** +* 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. +* This method is used for rectangular selections and does not work on wrapped lines. +*/ +SelectionPosition Editor::SPositionFromLineX(int lineDoc, int x) { + RefreshStyleData(); + if (lineDoc >= pdoc->LinesTotal()) + return SelectionPosition(pdoc->Length()); + //Platform::DebugPrintf("Position of (%d,%d) line = %d top=%d\n", pt.x, pt.y, line, topLine); + AutoSurface surface(this); + return view.SPositionFromLineX(surface, lineDoc, x, *this, vs); +} - // Fill the remainder of the line - rcSegment.left = rcSegment.right; - if (rcSegment.left < rcLine.left) - rcSegment.left = rcLine.left; - rcSegment.right = rcLine.right; +int Editor::PositionFromLineX(int lineDoc, int x) { + return SPositionFromLineX(lineDoc, x).Position(); +} - if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha == SC_ALPHA_NOALPHA)) { - surface->FillRectangle(rcSegment, SelectionBackground(vsDraw, eolInSelection == 1)); - } else { - if (background.isSet) { - surface->FillRectangle(rcSegment, background); - } else if (vsDraw.styles[ll->styles[ll->numCharsInLine]].eolFilled) { - surface->FillRectangle(rcSegment, vsDraw.styles[ll->styles[ll->numCharsInLine]].back); - } else { - surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back); - } - if (eolInSelection && vsDraw.selEOLFilled && vsDraw.selColours.back.isSet && (line < pdoc->LinesTotal() - 1) && (alpha != SC_ALPHA_NOALPHA)) { - SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, eolInSelection == 1), alpha); - } - } +int Editor::LineFromLocation(Point pt) const { + return cs.DocFromDisplay(static_cast(pt.y) / vs.lineHeight + topLine); +} - bool drawWrapMarkEnd = false; +void Editor::SetTopLine(int topLineNew) { + if ((topLine != topLineNew) && (topLineNew >= 0)) { + topLine = topLineNew; + ContainerNeedsUpdate(SC_UPDATE_V_SCROLL); + } + posTopLine = pdoc->LineStart(cs.DocFromDisplay(topLine)); +} - if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { - if (subLine + 1 < ll->lines) { - drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; - } +/** + * 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; +} - if (drawWrapMarkEnd) { - PRectangle rcPlace = rcSegment; +void Editor::RedrawRect(PRectangle rc) { + //Platform::DebugPrintf("Redraw %0d,%0d - %0d,%0d\n", rc.left, rc.top, rc.right, rc.bottom); - if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_END_BY_TEXT) { - rcPlace.left = xEol + xStart + virtualSpace; - rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; - } else { - // rcLine is clipped to text area - rcPlace.right = rcLine.right; - rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; - } - DrawWrapMarker(surface, rcPlace, true, vsDraw.WrapColour()); + // 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::DrawIndicator(int indicNum, int startPos, int endPos, Surface *surface, const ViewStyle &vsDraw, - int xStart, PRectangle rcLine, LineLayout *ll, int subLine) { - const XYPOSITION subLineStart = ll->positions[ll->LineStart(subLine)]; - PRectangle rcIndic( - ll->positions[startPos] + xStart - subLineStart, - rcLine.top + vsDraw.maxAscent, - ll->positions[endPos] + xStart - subLineStart, - rcLine.top + vsDraw.maxAscent + 3); - vsDraw.indicators[indicNum].Draw(surface, rcIndic, rcLine); +void Editor::DiscardOverdraw() { + // Overridden on platforms that may draw outside visible area. } -void Editor::DrawIndicators(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine, int lineEnd, bool under) { - // Draw decorators - const int posLineStart = pdoc->LineStart(line); - const int lineStart = ll->LineStart(subLine); - const int posLineEnd = posLineStart + lineEnd; +void Editor::Redraw() { + //Platform::DebugPrintf("Redraw all\n"); + PRectangle rcClient = GetClientRectangle(); + wMain.InvalidateRectangle(rcClient); + if (wMargin.GetID()) + wMargin.InvalidateAll(); + //wMain.InvalidateAll(); +} - for (Decoration *deco = pdoc->decorations.root; deco; deco = deco->next) { - if (under == vsDraw.indicators[deco->indicator].under) { - int startPos = posLineStart + lineStart; - if (!deco->rs.ValueAt(startPos)) { - startPos = deco->rs.EndRun(startPos); - } - while ((startPos < posLineEnd) && (deco->rs.ValueAt(startPos))) { - int endPos = deco->rs.EndRun(startPos); - if (endPos > posLineEnd) - endPos = posLineEnd; - DrawIndicator(deco->indicator, startPos - posLineStart, endPos - posLineStart, - surface, vsDraw, xStart, rcLine, ll, subLine); - startPos = endPos; - if (!deco->rs.ValueAt(startPos)) { - startPos = deco->rs.EndRun(startPos); - } - } - } - } +void Editor::RedrawSelMargin(int line, bool allAfter) { + bool abandonDraw = false; + if (!wMargin.GetID()) // Margin in main window so may need to abandon and retry + abandonDraw = AbandonPaint(); + if (!abandonDraw) { + if (vs.maskInLine) { + Redraw(); + } else { + PRectangle rcSelMargin = GetClientRectangle(); + rcSelMargin.right = rcSelMargin.left + vs.fixedColumnWidth; + if (line != -1) { + PRectangle rcLine = RectangleFromRange(Range(pdoc->LineStart(line))); - // Use indicators to highlight matching braces - if ((vsDraw.braceHighlightIndicatorSet && (bracesMatchStyle == STYLE_BRACELIGHT)) || - (vsDraw.braceBadLightIndicatorSet && (bracesMatchStyle == STYLE_BRACEBAD))) { - int braceIndicator = (bracesMatchStyle == STYLE_BRACELIGHT) ? vsDraw.braceHighlightIndicator : vsDraw.braceBadLightIndicator; - if (under == vsDraw.indicators[braceIndicator].under) { - Range rangeLine(posLineStart + lineStart, posLineEnd); - if (rangeLine.ContainsCharacter(braces[0])) { - int braceOffset = braces[0] - posLineStart; - if (braceOffset < ll->numCharsInLine) { - DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); - } - } - if (rangeLine.ContainsCharacter(braces[1])) { - int braceOffset = braces[1] - posLineStart; - if (braceOffset < ll->numCharsInLine) { - DrawIndicator(braceIndicator, braceOffset, braceOffset + 1, surface, vsDraw, xStart, rcLine, ll, subLine); + // Inflate line rectangle if there are image markers with height larger than line height + if (vs.largestMarkerHeight > vs.lineHeight) { + int delta = (vs.largestMarkerHeight - vs.lineHeight + 1) / 2; + rcLine.top -= delta; + rcLine.bottom += delta; + if (rcLine.top < rcSelMargin.top) + rcLine.top = rcSelMargin.top; + if (rcLine.bottom > rcSelMargin.bottom) + rcLine.bottom = rcSelMargin.bottom; } - } - } - } -} -void Editor::DrawAnnotation(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine) { - int indent = static_cast(pdoc->GetLineIndentation(line) * vsDraw.spaceWidth); - PRectangle rcSegment = rcLine; - int annotationLine = subLine - ll->lines; - const StyledText stAnnotation = pdoc->AnnotationStyledText(line); - if (stAnnotation.text && ValidStyledText(vsDraw, vsDraw.annotationStyleOffset, stAnnotation)) { - surface->FillRectangle(rcSegment, vsDraw.styles[0].back); - rcSegment.left = static_cast(xStart); - if (trackLineWidth || (vsDraw.annotationVisible == ANNOTATION_BOXED)) { - // Only care about calculating width if tracking or need to draw box - int widthAnnotation = WidestLineWidth(surface, vsDraw, vsDraw.annotationStyleOffset, stAnnotation); - if (vsDraw.annotationVisible == ANNOTATION_BOXED) { - widthAnnotation += static_cast(vsDraw.spaceWidth * 2); // Margins - } - if (widthAnnotation > lineWidthMaxSeen) - lineWidthMaxSeen = widthAnnotation; - if (vsDraw.annotationVisible == ANNOTATION_BOXED) { - rcSegment.left = static_cast(xStart + indent); - rcSegment.right = rcSegment.left + widthAnnotation; - } - } - const int annotationLines = pdoc->AnnotationLines(line); - size_t start = 0; - size_t lengthAnnotation = stAnnotation.LineLength(start); - int lineInAnnotation = 0; - while ((lineInAnnotation < annotationLine) && (start < stAnnotation.length)) { - start += lengthAnnotation + 1; - lengthAnnotation = stAnnotation.LineLength(start); - lineInAnnotation++; - } - PRectangle rcText = rcSegment; - if (vsDraw.annotationVisible == ANNOTATION_BOXED) { - surface->FillRectangle(rcText, - vsDraw.styles[stAnnotation.StyleAt(start) + vsDraw.annotationStyleOffset].back); - rcText.left += vsDraw.spaceWidth; - } - DrawStyledText(surface, vsDraw, vsDraw.annotationStyleOffset, rcText, - stAnnotation, start, lengthAnnotation); - if (vsDraw.annotationVisible == ANNOTATION_BOXED) { - surface->PenColour(vsDraw.styles[vsDraw.annotationStyleOffset].fore); - surface->MoveTo(static_cast(rcSegment.left), static_cast(rcSegment.top)); - surface->LineTo(static_cast(rcSegment.left), static_cast(rcSegment.bottom)); - surface->MoveTo(static_cast(rcSegment.right), static_cast(rcSegment.top)); - surface->LineTo(static_cast(rcSegment.right), static_cast(rcSegment.bottom)); - if (subLine == ll->lines) { - surface->MoveTo(static_cast(rcSegment.left), static_cast(rcSegment.top)); - surface->LineTo(static_cast(rcSegment.right), static_cast(rcSegment.top)); + rcSelMargin.top = rcLine.top; + if (!allAfter) + rcSelMargin.bottom = rcLine.bottom; + if (rcSelMargin.Empty()) + return; } - if (subLine == ll->lines+annotationLines-1) { - surface->MoveTo(static_cast(rcSegment.left), static_cast(rcSegment.bottom - 1)); - surface->LineTo(static_cast(rcSegment.right), static_cast(rcSegment.bottom - 1)); + if (wMargin.GetID()) { + Point ptOrigin = GetVisibleOriginInMain(); + rcSelMargin.Move(-ptOrigin.x, -ptOrigin.y); + wMargin.InvalidateRectangle(rcSelMargin); + } else { + wMain.InvalidateRectangle(rcSelMargin); } } } } -void Editor::DrawLine(Surface *surface, const ViewStyle &vsDraw, int line, int lineVisible, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine) { - - if (subLine >= ll->lines) { - DrawAnnotation(surface, vsDraw, line, xStart, rcLine, ll, subLine); - return; // No further drawing - } +PRectangle Editor::RectangleFromRange(Range r) { + const int minLine = cs.DisplayFromDoc(pdoc->LineFromPosition(r.First())); + const int maxLine = cs.DisplayLastFromDoc(pdoc->LineFromPosition(r.Last())); + const PRectangle rcClientDrawing = GetClientDrawingRectangle(); + PRectangle rc; + const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; + rc.left = static_cast(vs.textStart - leftTextOverlap); + rc.top = static_cast((minLine - TopLineOfMain()) * vs.lineHeight); + if (rc.top < rcClientDrawing.top) + rc.top = rcClientDrawing.top; + // Extend to right of prepared area if any to prevent artifacts from caret line highlight + rc.right = rcClientDrawing.right; + rc.bottom = static_cast((maxLine - TopLineOfMain() + 1) * vs.lineHeight); - PRectangle rcSegment = rcLine; + return rc; +} - // 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. - FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; +void Editor::InvalidateRange(int start, int end) { + RedrawRect(RectangleFromRange(Range(start, end))); +} - // See if something overrides the line background color. - const ColourOptional background = vsDraw.Background(pdoc->GetMark(line), caret.active, ll->containsCaret); +int Editor::CurrentPosition() const { + return sel.MainCaret(); +} - const bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && - (!background.isSet) && (vsDraw.whitespaceColours.back.isSet); +bool Editor::SelectionEmpty() const { + return sel.Empty(); +} - bool inIndentation = subLine == 0; // Do not handle indentation except on first subline. - const XYPOSITION indentWidth = pdoc->IndentSize() * vsDraw.spaceWidth; - const XYPOSITION epsilon = 0.0001f; // A small nudge to avoid floating point precision issues +SelectionPosition Editor::SelectionStart() { + return sel.RangeMain().Start(); +} - const int posLineStart = pdoc->LineStart(line); +SelectionPosition Editor::SelectionEnd() { + return sel.RangeMain().End(); +} - const int startseg = ll->LineStart(subLine); - const XYACCUMULATOR subLineStart = ll->positions[startseg]; - int lineStart = 0; - int lineEnd = 0; - if (subLine < ll->lines) { - lineStart = ll->LineStart(subLine); - lineEnd = ll->LineStart(subLine + 1); - if (subLine == ll->lines - 1) { - lineEnd = ll->numCharsBeforeEOL; +void Editor::SetRectangularRange() { + if (sel.IsRectangular()) { + int xAnchor = XFromPosition(sel.Rectangular().anchor); + int xCaret = XFromPosition(sel.Rectangular().caret); + if (sel.selType == Selection::selThin) { + xCaret = xAnchor; + } + int lineAnchorRect = pdoc->LineFromPosition(sel.Rectangular().anchor.Position()); + int lineCaret = pdoc->LineFromPosition(sel.Rectangular().caret.Position()); + int increment = (lineCaret > lineAnchorRect) ? 1 : -1; + for (int line=lineAnchorRect; line != lineCaret+increment; line += increment) { + SelectionRange range(SPositionFromLineX(line, xCaret), SPositionFromLineX(line, xAnchor)); + if ((virtualSpaceOptions & SCVS_RECTANGULARSELECTION) == 0) + range.ClearVirtualSpace(); + if (line == lineAnchorRect) + sel.SetSelection(range); + else + sel.AddSelectionWithoutTrim(range); } } +} - if (ll->wrapIndent != 0) { - - bool continuedWrapLine = false; - if (subLine < ll->lines) { - continuedWrapLine = ll->LineStart(subLine) != 0; +void Editor::ThinRectangularRange() { + if (sel.IsRectangular()) { + sel.selType = Selection::selThin; + if (sel.Rectangular().caret < sel.Rectangular().anchor) { + sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).caret, sel.Range(0).anchor); + } else { + sel.Rectangular() = SelectionRange(sel.Range(sel.Count()-1).anchor, sel.Range(0).caret); } + SetRectangularRange(); + } +} - if (continuedWrapLine) { - // draw continuation rect - PRectangle rcPlace = rcSegment; +void Editor::InvalidateSelection(SelectionRange newMain, bool invalidateWholeSelection) { + if (sel.Count() > 1 || !(sel.RangeMain().anchor == newMain.anchor) || sel.IsRectangular()) { + invalidateWholeSelection = true; + } + int firstAffected = Platform::Minimum(sel.RangeMain().Start().Position(), newMain.Start().Position()); + // +1 for lastAffected ensures caret repainted + int lastAffected = Platform::Maximum(newMain.caret.Position()+1, newMain.anchor.Position()); + lastAffected = Platform::Maximum(lastAffected, sel.RangeMain().End().Position()); + if (invalidateWholeSelection) { + for (size_t r=0; rpositions[startseg] + xStart - static_cast(subLineStart); - rcPlace.right = rcPlace.left + ll->wrapIndent; +void Editor::SetSelection(SelectionPosition currentPos_, SelectionPosition anchor_) { + currentPos_ = ClampPositionIntoDocument(currentPos_); + anchor_ = ClampPositionIntoDocument(anchor_); + int currentLine = pdoc->LineFromPosition(currentPos_.Position()); + /* For Line selection - ensure the anchor and caret are always + at the beginning and end of the region lines. */ + if (sel.selType == Selection::selLines) { + if (currentPos_ > anchor_) { + anchor_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(anchor_.Position()))); + currentPos_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(currentPos_.Position()))); + } else { + currentPos_ = SelectionPosition(pdoc->LineStart(pdoc->LineFromPosition(currentPos_.Position()))); + anchor_ = SelectionPosition(pdoc->LineEnd(pdoc->LineFromPosition(anchor_.Position()))); + } + } + SelectionRange rangeNew(currentPos_, anchor_); + if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { + InvalidateSelection(rangeNew); + } + sel.RangeMain() = rangeNew; + SetRectangularRange(); + ClaimSelection(); - // default bgnd here.. - surface->FillRectangle(rcSegment, background.isSet ? background : - vsDraw.styles[STYLE_DEFAULT].back); + if (marginView.highlightDelimiter.NeedsDrawing(currentLine)) { + RedrawSelMargin(); + } + QueueIdleWork(WorkNeeded::workUpdateUI); +} - // 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); +void Editor::SetSelection(int currentPos_, int anchor_) { + SetSelection(SelectionPosition(currentPos_), SelectionPosition(anchor_)); +} - if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_START) { +// Just move the caret on the main selection +void Editor::SetSelection(SelectionPosition currentPos_) { + currentPos_ = ClampPositionIntoDocument(currentPos_); + int currentLine = pdoc->LineFromPosition(currentPos_.Position()); + if (sel.Count() > 1 || !(sel.RangeMain().caret == currentPos_)) { + InvalidateSelection(SelectionRange(currentPos_)); + } + if (sel.IsRectangular()) { + sel.Rectangular() = + SelectionRange(SelectionPosition(currentPos_), sel.Rectangular().anchor); + SetRectangularRange(); + } else { + sel.RangeMain() = + SelectionRange(SelectionPosition(currentPos_), sel.RangeMain().anchor); + } + ClaimSelection(); - if (vsDraw.wrapVisualFlagsLocation & SC_WRAPVISUALFLAGLOC_START_BY_TEXT) - rcPlace.left = rcPlace.right - vsDraw.aveCharWidth; - else - rcPlace.right = rcPlace.left + vsDraw.aveCharWidth; + if (marginView.highlightDelimiter.NeedsDrawing(currentLine)) { + RedrawSelMargin(); + } + QueueIdleWork(WorkNeeded::workUpdateUI); +} - DrawWrapMarker(surface, rcPlace, false, vsDraw.WrapColour()); - } +void Editor::SetSelection(int currentPos_) { + SetSelection(SelectionPosition(currentPos_)); +} - xStart += static_cast(ll->wrapIndent); - } +void Editor::SetEmptySelection(SelectionPosition currentPos_) { + int currentLine = pdoc->LineFromPosition(currentPos_.Position()); + SelectionRange rangeNew(ClampPositionIntoDocument(currentPos_)); + if (sel.Count() > 1 || !(sel.RangeMain() == rangeNew)) { + InvalidateSelection(rangeNew); } + sel.Clear(); + sel.RangeMain() = rangeNew; + SetRectangularRange(); + ClaimSelection(); - const bool selBackDrawn = vsDraw.selColours.back.isSet && - ((vsDraw.selAlpha == SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha == SC_ALPHA_NOALPHA)); - - // Does not take margin into account but not significant - const int xStartVisible = static_cast(subLineStart) - xStart; + if (marginView.highlightDelimiter.NeedsDrawing(currentLine)) { + RedrawSelMargin(); + } + QueueIdleWork(WorkNeeded::workUpdateUI); +} - if (twoPhaseDraw) { - BreakFinder bfBack(ll, &sel, lineStart, lineEnd, posLineStart, xStartVisible, selBackDrawn, pdoc, &reprs); +void Editor::SetEmptySelection(int currentPos_) { + SetEmptySelection(SelectionPosition(currentPos_)); +} - // Background drawing loop - while (bfBack.More()) { +bool Editor::RangeContainsProtected(int start, int end) const { + if (vs.ProtectionActive()) { + if (start > end) { + int t = start; + start = end; + end = t; + } + for (int pos = start; pos < end; pos++) { + if (vs.styles[pdoc->StyleAt(pos)].IsProtected()) + return true; + } + } + return false; +} - const TextSegment ts = bfBack.Next(); - const int i = ts.end() - 1; - const int iDoc = i + posLineStart; +bool Editor::SelectionContainsProtected() { + for (size_t r=0; rpositions[ts.start] + xStart - static_cast(subLineStart); - rcSegment.right = ll->positions[ts.end()] + xStart - static_cast(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.Intersects(rcLine)) { - // Clip to line rectangle, since may have a huge position which will not work with some platforms - if (rcSegment.left < rcLine.left) - rcSegment.left = rcLine.left; - if (rcSegment.right > rcLine.right) - rcSegment.right = rcLine.right; +/** + * Asks document to find a good position and then moves out of any invisible positions. + */ +int Editor::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) const { + return MovePositionOutsideChar(SelectionPosition(pos), moveDir, checkLineEnd).Position(); +} - const int inSelection = hideSelection ? 0 : sel.CharacterInSelection(iDoc); - const bool inHotspot = (ll->hotspot.Valid()) && ll->hotspot.ContainsCharacter(iDoc); - ColourDesired textBack = TextBackground(vsDraw, background, inSelection, - inHotspot, ll->styles[i], i, ll); - if (ts.representation) { - if (ll->chars[i] == '\t') { - // Tab display - if (drawWhitespaceBackground && - (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) - textBack = vsDraw.whitespaceColours.back; - } else { - // Blob display - inIndentation = false; - } - surface->FillRectangle(rcSegment, textBack); - } else { - // Normal text display - surface->FillRectangle(rcSegment, textBack); - if (vsDraw.viewWhitespace != wsInvisible || - (inIndentation && vsDraw.viewIndentationGuides == ivReal)) { - for (int cpos = 0; cpos <= i - ts.start; cpos++) { - if (ll->chars[cpos + ts.start] == ' ') { - if (drawWhitespaceBackground && - (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { - PRectangle rcSpace( - ll->positions[cpos + ts.start] + xStart - static_cast(subLineStart), - rcSegment.top, - ll->positions[cpos + ts.start + 1] + xStart - static_cast(subLineStart), - rcSegment.bottom); - surface->FillRectangle(rcSpace, vsDraw.whitespaceColours.back); - } - } else { - inIndentation = false; - } - } - } - } - } else if (rcSegment.left > rcLine.right) { - break; +SelectionPosition Editor::MovePositionOutsideChar(SelectionPosition pos, int moveDir, bool checkLineEnd) const { + int posMoved = pdoc->MovePositionOutsideChar(pos.Position(), moveDir, checkLineEnd); + if (posMoved != pos.Position()) + pos.SetPosition(posMoved); + if (vs.ProtectionActive()) { + if (moveDir > 0) { + if ((pos.Position() > 0) && vs.styles[pdoc->StyleAt(pos.Position() - 1)].IsProtected()) { + while ((pos.Position() < pdoc->Length()) && + (vs.styles[pdoc->StyleAt(pos.Position())].IsProtected())) + pos.Add(1); + } + } else if (moveDir < 0) { + if (vs.styles[pdoc->StyleAt(pos.Position())].IsProtected()) { + while ((pos.Position() > 0) && + (vs.styles[pdoc->StyleAt(pos.Position() - 1)].IsProtected())) + pos.Add(-1); } } - - DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, - xStart, subLine, subLineStart, background); } + return pos; +} - DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineEnd, true); +int Editor::MovePositionTo(SelectionPosition newPos, Selection::selTypes selt, bool ensureVisible) { + bool simpleCaret = (sel.Count() == 1) && sel.Empty(); + SelectionPosition spCaret = sel.Last(); - if (vsDraw.edgeState == EDGE_LINE) { - int edgeX = static_cast(vsDraw.theEdge * vsDraw.spaceWidth); - rcSegment.left = static_cast(edgeX + xStart); - if ((ll->wrapIndent != 0) && (lineStart != 0)) - rcSegment.left -= ll->wrapIndent; - rcSegment.right = rcSegment.left + 1; - surface->FillRectangle(rcSegment, vsDraw.edgecolour); + int delta = newPos.Position() - sel.MainCaret(); + newPos = ClampPositionIntoDocument(newPos); + newPos = MovePositionOutsideChar(newPos, delta); + if (!multipleSelection && sel.IsRectangular() && (selt == Selection::selStream)) { + // Can't turn into multiple selection so clear additional selections + InvalidateSelection(SelectionRange(newPos), true); + SelectionRange rangeMain = sel.RangeMain(); + sel.SetSelection(rangeMain); + } + if (!sel.IsRectangular() && (selt == Selection::selRectangle)) { + // Switching to rectangular + InvalidateSelection(sel.RangeMain(), false); + SelectionRange rangeMain = sel.RangeMain(); + sel.Clear(); + sel.Rectangular() = rangeMain; } + if (selt != Selection::noSel) { + sel.selType = selt; + } + if (selt != Selection::noSel || sel.MoveExtends()) { + SetSelection(newPos); + } else { + SetEmptySelection(newPos); + } + ShowCaretAtCurrentPosition(); - // Draw underline mark as part of background if not transparent - int marks = pdoc->GetMark(line); - int markBit; - for (markBit = 0; (markBit < 32) && marks; markBit++) { - if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE) && - (vsDraw.markers[markBit].alpha == SC_ALPHA_NOALPHA)) { - PRectangle rcUnderline = rcLine; - rcUnderline.top = rcUnderline.bottom - 2; - surface->FillRectangle(rcUnderline, vsDraw.markers[markBit].back); + int currentLine = pdoc->LineFromPosition(newPos.Position()); + if (ensureVisible) { + // In case in need of wrapping to ensure DisplayFromDoc works. + if (currentLine >= wrapPending.start) + WrapLines(wsAll); + XYScrollPosition newXY = XYScrollToMakeVisible( + SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), xysDefault); + if (simpleCaret && (newXY.xOffset == xOffset)) { + // simple vertical scroll then invalidate + ScrollTo(newXY.topLine); + InvalidateSelection(SelectionRange(spCaret), true); + } else { + SetXYScroll(newXY); } - marks >>= 1; } - inIndentation = subLine == 0; // Do not handle indentation except on first subline. - // Foreground drawing loop - BreakFinder bfFore(ll, &sel, lineStart, lineEnd, posLineStart, xStartVisible, - ((!twoPhaseDraw && selBackDrawn) || vsDraw.selColours.fore.isSet), pdoc, &reprs); - - while (bfFore.More()) { - - const TextSegment ts = bfFore.Next(); - const int i = ts.end() - 1; - const int iDoc = i + posLineStart; + if (marginView.highlightDelimiter.NeedsDrawing(currentLine)) { + RedrawSelMargin(); + } + return 0; +} - rcSegment.left = ll->positions[ts.start] + xStart - static_cast(subLineStart); - rcSegment.right = ll->positions[ts.end()] + xStart - static_cast(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.Intersects(rcLine)) { - int styleMain = ll->styles[i]; - ColourDesired textFore = vsDraw.styles[styleMain].fore; - FontAlias textFont = vsDraw.styles[styleMain].font; - //hotspot foreground - const bool inHotspot = (ll->hotspot.Valid()) && ll->hotspot.ContainsCharacter(iDoc); - if (inHotspot) { - if (vsDraw.hotspotColours.fore.isSet) - textFore = vsDraw.hotspotColours.fore; - } - const int inSelection = hideSelection ? 0 : sel.CharacterInSelection(iDoc); - if (inSelection && (vsDraw.selColours.fore.isSet)) { - textFore = (inSelection == 1) ? vsDraw.selColours.fore : vsDraw.selAdditionalForeground; - } - ColourDesired textBack = TextBackground(vsDraw, background, inSelection, inHotspot, styleMain, i, ll); - if (ts.representation) { - if (ll->chars[i] == '\t') { - // Tab display - if (!twoPhaseDraw) { - if (drawWhitespaceBackground && - (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) - textBack = vsDraw.whitespaceColours.back; - surface->FillRectangle(rcSegment, textBack); - } - if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { - for (int indentCount = static_cast((ll->positions[i] + epsilon) / indentWidth); - indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth; - indentCount++) { - if (indentCount > 0) { - int xIndent = static_cast(indentCount * indentWidth); - DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, - (ll->xHighlightGuide == xIndent)); - } - } - } - if (vsDraw.viewWhitespace != wsInvisible) { - if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { - if (vsDraw.whitespaceColours.fore.isSet) - textFore = vsDraw.whitespaceColours.fore; - surface->PenColour(textFore); - PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, - rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); - DrawTabArrow(surface, rcTab, static_cast(rcSegment.top + vsDraw.lineHeight / 2)); - } - } - } else { - inIndentation = false; - if (vsDraw.controlCharSymbol >= 32) { - char cc[2] = { static_cast(vsDraw.controlCharSymbol), '\0' }; - surface->DrawTextNoClip(rcSegment, ctrlCharsFont, - rcSegment.top + vsDraw.maxAscent, - cc, 1, textBack, textFore); - } else { - DrawTextBlob(surface, vsDraw, rcSegment, ts.representation->stringRep.c_str(), textBack, textFore, twoPhaseDraw); - } - } - } else { - // Normal text display - if (vsDraw.styles[styleMain].visible) { - if (twoPhaseDraw) { - surface->DrawTextTransparent(rcSegment, textFont, - rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, - i - ts.start + 1, textFore); - } else { - surface->DrawTextNoClip(rcSegment, textFont, - rcSegment.top + vsDraw.maxAscent, ll->chars + ts.start, - i - ts.start + 1, textFore, textBack); - } - } - if (vsDraw.viewWhitespace != wsInvisible || - (inIndentation && vsDraw.viewIndentationGuides != ivNone)) { - for (int cpos = 0; cpos <= i - ts.start; cpos++) { - if (ll->chars[cpos + ts.start] == ' ') { - if (vsDraw.viewWhitespace != wsInvisible) { - if (vsDraw.whitespaceColours.fore.isSet) - textFore = vsDraw.whitespaceColours.fore; - if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { - XYPOSITION xmid = (ll->positions[cpos + ts.start] + ll->positions[cpos + ts.start + 1]) / 2; - if (!twoPhaseDraw && drawWhitespaceBackground && - (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways)) { - textBack = vsDraw.whitespaceColours.back; - PRectangle rcSpace( - ll->positions[cpos + ts.start] + xStart - static_cast(subLineStart), - rcSegment.top, - ll->positions[cpos + ts.start + 1] + xStart - static_cast(subLineStart), - rcSegment.bottom); - surface->FillRectangle(rcSpace, textBack); - } - PRectangle rcDot(xmid + xStart - static_cast(subLineStart), - rcSegment.top + vsDraw.lineHeight / 2, 0.0f, 0.0f); - rcDot.right = rcDot.left + vsDraw.whitespaceSize; - rcDot.bottom = rcDot.top + vsDraw.whitespaceSize; - surface->FillRectangle(rcDot, textFore); - } - } - if (inIndentation && vsDraw.viewIndentationGuides == ivReal) { - for (int indentCount = static_cast((ll->positions[cpos + ts.start] + epsilon) / indentWidth); - indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth; - indentCount++) { - if (indentCount > 0) { - int xIndent = static_cast(indentCount * indentWidth); - DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, - (ll->xHighlightGuide == xIndent)); - } - } - } - } else { - inIndentation = false; - } - } - } - } - if (ll->hotspot.Valid() && vsDraw.hotspotUnderline && ll->hotspot.ContainsCharacter(iDoc)) { - PRectangle rcUL = rcSegment; - rcUL.top = rcUL.top + vsDraw.maxAscent + 1; - rcUL.bottom = rcUL.top + 1; - if (vsDraw.hotspotColours.fore.isSet) - surface->FillRectangle(rcUL, vsDraw.hotspotColours.fore); - 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; +int Editor::MovePositionTo(int newPos, Selection::selTypes selt, bool ensureVisible) { + return MovePositionTo(SelectionPosition(newPos), selt, ensureVisible); +} + +SelectionPosition Editor::MovePositionSoVisible(SelectionPosition pos, int moveDir) { + pos = ClampPositionIntoDocument(pos); + pos = MovePositionOutsideChar(pos, moveDir); + int lineDoc = pdoc->LineFromPosition(pos.Position()); + 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 SelectionPosition(pdoc->LineStart(cs.DocFromDisplay(lineDisplay))); + } else { + lineDisplay = Platform::Clamp(lineDisplay - 1, 0, cs.LinesDisplayed()); + return SelectionPosition(pdoc->LineEnd(cs.DocFromDisplay(lineDisplay))); } } - if ((vsDraw.viewIndentationGuides == ivLookForward || vsDraw.viewIndentationGuides == ivLookBoth) - && (subLine == 0)) { - int indentSpace = pdoc->GetLineIndentation(line); - int xStartText = static_cast(ll->positions[pdoc->GetLineIndentPosition(line) - posLineStart]); +} - // Find the most recent line with some text +SelectionPosition Editor::MovePositionSoVisible(int pos, int moveDir) { + return MovePositionSoVisible(SelectionPosition(pos), moveDir); +} - int lineLastWithText = line; - while (lineLastWithText > Platform::Maximum(line-20, 0) && pdoc->IsWhiteLine(lineLastWithText)) { - lineLastWithText--; - } - if (lineLastWithText < line) { - xStartText = 100000; // Don't limit to visible indentation on empty line - // This line is empty, so use indentation of last line with text - int indentLastWithText = pdoc->GetLineIndentation(lineLastWithText); - int isFoldHeader = pdoc->GetLevel(lineLastWithText) & SC_FOLDLEVELHEADERFLAG; - if (isFoldHeader) { - // Level is one more level than parent - indentLastWithText += pdoc->IndentSize(); - } - if (vsDraw.viewIndentationGuides == ivLookForward) { - // In viLookForward mode, previous line only used if it is a fold header - if (isFoldHeader) { - indentSpace = Platform::Maximum(indentSpace, indentLastWithText); - } - } else { // viLookBoth - indentSpace = Platform::Maximum(indentSpace, indentLastWithText); - } - } +Point Editor::PointMainCaret() { + return LocationFromPosition(sel.Range(sel.Main()).caret); +} - int lineNextWithText = line; - while (lineNextWithText < Platform::Minimum(line+20, pdoc->LinesTotal()) && pdoc->IsWhiteLine(lineNextWithText)) { - lineNextWithText++; - } - if (lineNextWithText > line) { - xStartText = 100000; // Don't limit to visible indentation on empty line - // This line is empty, so use indentation of first next line with text - indentSpace = Platform::Maximum(indentSpace, - pdoc->GetLineIndentation(lineNextWithText)); - } +/** + * Choose the x position that the caret will try to stick to + * as it moves up and down. + */ +void Editor::SetLastXChosen() { + Point pt = PointMainCaret(); + lastXChosen = static_cast(pt.x) + xOffset; +} - for (int indentPos = pdoc->IndentSize(); indentPos < indentSpace; indentPos += pdoc->IndentSize()) { - int xIndent = static_cast(indentPos * vsDraw.spaceWidth); - if (xIndent < xStartText) { - DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcSegment, - (ll->xHighlightGuide == xIndent)); - } +void Editor::ScrollTo(int line, bool moveThumb) { + int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); + if (topLineNew != topLine) { + // Try to optimise small scrolls +#ifndef UNDER_CE + int linesToMove = topLine - topLineNew; + bool performBlit = (abs(linesToMove) <= 10) && (paintState == notPainting); + willRedrawAll = !performBlit; +#endif + SetTopLine(topLineNew); + // Optimize by styling the view as this will invalidate any needed area + // which could abort the initial paint if discovered later. + StyleToPositionInView(PositionAfterArea(GetClientRectangle())); +#ifndef UNDER_CE + // Perform redraw rather than scroll if many lines would be redrawn anyway. + if (performBlit) { + ScrollText(linesToMove); + } else { + Redraw(); + } + willRedrawAll = false; +#else + Redraw(); +#endif + if (moveThumb) { + SetVerticalScrollPos(); } } +} - DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineEnd, false); +void Editor::ScrollText(int /* linesToMove */) { + //Platform::DebugPrintf("Editor::ScrollText %d\n", linesToMove); + Redraw(); +} - // End of the drawing of the current line - if (!twoPhaseDraw) { - DrawEOL(surface, vsDraw, rcLine, ll, line, lineEnd, - xStart, subLine, subLineStart, background); +void Editor::HorizontalScrollTo(int xPos) { + //Platform::DebugPrintf("HorizontalScroll %d\n", xPos); + if (xPos < 0) + xPos = 0; + if (!Wrapping() && (xOffset != xPos)) { + xOffset = xPos; + ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); + SetHorizontalScrollPos(); + RedrawRect(GetClientRectangle()); } - if (!hideSelection && ((vsDraw.selAlpha != SC_ALPHA_NOALPHA) || (vsDraw.selAdditionalAlpha != SC_ALPHA_NOALPHA))) { - // For each selection draw - int virtualSpaces = 0; - if (subLine == (ll->lines - 1)) { - virtualSpaces = sel.VirtualSpaceFor(pdoc->LineEnd(line)); - } - SelectionPosition posStart(posLineStart + lineStart); - SelectionPosition posEnd(posLineStart + lineEnd, virtualSpaces); - SelectionSegment virtualSpaceRange(posStart, posEnd); - for (size_t r=0; rEndLineStyle()].spaceWidth; - rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - - static_cast(subLineStart) + portion.start.VirtualSpace() * spaceWidth; - rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - - static_cast(subLineStart) + portion.end.VirtualSpace() * spaceWidth; - if ((ll->wrapIndent != 0) && (lineStart != 0)) { - if ((portion.start.Position() - posLineStart) == lineStart && sel.Range(r).ContainsCharacter(portion.start.Position() - 1)) - rcSegment.left -= static_cast(ll->wrapIndent); // indentation added to xStart was truncated to int, so we do the same here - } - rcSegment.left = (rcSegment.left > rcLine.left) ? rcSegment.left : rcLine.left; - rcSegment.right = (rcSegment.right < rcLine.right) ? rcSegment.right : rcLine.right; - if (rcSegment.right > rcLine.left) - SimpleAlphaRectangle(surface, rcSegment, SelectionBackground(vsDraw, r == sel.Main()), alpha); - } - } - } +} + +void Editor::VerticalCentreCaret() { + int lineDoc = pdoc->LineFromPosition(sel.IsRectangular() ? sel.Rectangular().caret.Position() : sel.MainCaret()); + int lineDisplay = cs.DisplayFromDoc(lineDoc); + int newTop = lineDisplay - (LinesOnScreen() / 2); + if (topLine != newTop) { + SetTopLine(newTop > 0 ? newTop : 0); + RedrawRect(GetClientRectangle()); } +} - // Draw any translucent whole line states - rcSegment = rcLine; - if ((caret.active || vsDraw.alwaysShowCaretLineBackground) && vsDraw.showCaretLineBackground && ll->containsCaret) { - SimpleAlphaRectangle(surface, rcSegment, vsDraw.caretLineBackground, vsDraw.caretLineAlpha); +// Avoid 64 bit compiler warnings. +// Scintilla does not support text buffers larger than 2**31 +static int istrlen(const char *s) { + return static_cast(s ? strlen(s) : 0); +} + +void Editor::MoveSelectedLines(int lineDelta) { + + // if selection doesn't start at the beginning of the line, set the new start + int selectionStart = SelectionStart().Position(); + int startLine = pdoc->LineFromPosition(selectionStart); + int beginningOfStartLine = pdoc->LineStart(startLine); + selectionStart = beginningOfStartLine; + + // if selection doesn't end at the beginning of a line greater than that of the start, + // then set it at the beginning of the next one + int selectionEnd = SelectionEnd().Position(); + int endLine = pdoc->LineFromPosition(selectionEnd); + int beginningOfEndLine = pdoc->LineStart(endLine); + bool appendEol = false; + if (selectionEnd > beginningOfEndLine + || selectionStart == selectionEnd) { + selectionEnd = pdoc->LineStart(endLine + 1); + appendEol = (selectionEnd == pdoc->Length() && pdoc->LineFromPosition(selectionEnd) == endLine); } - marks = pdoc->GetMark(line); - for (markBit = 0; (markBit < 32) && marks; markBit++) { - if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_BACKGROUND)) { - SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); - } else if ((marks & 1) && (vsDraw.markers[markBit].markType == SC_MARK_UNDERLINE)) { - PRectangle rcUnderline = rcSegment; - rcUnderline.top = rcUnderline.bottom - 2; - SimpleAlphaRectangle(surface, rcUnderline, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); - } - marks >>= 1; + + // if there's nowhere for the selection to move + // (i.e. at the beginning going up or at the end going down), + // stop it right there! + if ((selectionStart == 0 && lineDelta < 0) + || (selectionEnd == pdoc->Length() && lineDelta > 0) + || selectionStart == selectionEnd) { + return; } - if (vsDraw.maskInLine) { - int marksMasked = pdoc->GetMark(line) & vsDraw.maskInLine; - if (marksMasked) { - for (markBit = 0; (markBit < 32) && marksMasked; markBit++) { - if ((marksMasked & 1) && (vsDraw.markers[markBit].markType != SC_MARK_EMPTY)) { - SimpleAlphaRectangle(surface, rcSegment, vsDraw.markers[markBit].back, vsDraw.markers[markBit].alpha); - } - marksMasked >>= 1; - } - } + + UndoGroup ug(pdoc); + + if (lineDelta > 0 && selectionEnd == pdoc->LineStart(pdoc->LinesTotal() - 1)) { + SetSelection(pdoc->MovePositionOutsideChar(selectionEnd - 1, -1), selectionEnd); + ClearSelection(); + selectionEnd = CurrentPosition(); } -} + SetSelection(selectionStart, selectionEnd); -void Editor::DrawBlockCaret(Surface *surface, const ViewStyle &vsDraw, LineLayout *ll, int subLine, - int xStart, int offset, int posCaret, PRectangle rcCaret, ColourDesired caretColour) const { + SelectionText selectedText; + CopySelectionRange(&selectedText); - int lineStart = ll->LineStart(subLine); - int posBefore = posCaret; - int posAfter = MovePositionOutsideChar(posCaret + 1, 1); - int numCharsToDraw = posAfter - posCaret; + int selectionLength = SelectionRange(selectionStart, selectionEnd).Length(); + Point currentLocation = LocationFromPosition(CurrentPosition()); + int currentLine = LineFromLocation(currentLocation); - // Work out where the starting and ending offsets are. We need to - // see if the previous character shares horizontal space, such as a - // glyph / combining character. If so we'll need to draw that too. - int offsetFirstChar = offset; - int offsetLastChar = offset + (posAfter - posCaret); - while ((posBefore > 0) && ((offsetLastChar - numCharsToDraw) >= lineStart)) { - if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - numCharsToDraw]) > 0) { - // The char does not share horizontal space - break; - } - // Char shares horizontal space, update the numChars to draw - // Update posBefore to point to the prev char - posBefore = MovePositionOutsideChar(posBefore - 1, -1); - numCharsToDraw = posAfter - posBefore; - offsetFirstChar = offset - (posCaret - posBefore); - } + if (appendEol) + SetSelection(pdoc->MovePositionOutsideChar(selectionStart - 1, -1), selectionEnd); + ClearSelection(); - // See if the next character shares horizontal space, if so we'll - // need to draw that too. - if (offsetFirstChar < 0) - offsetFirstChar = 0; - numCharsToDraw = offsetLastChar - offsetFirstChar; - while ((offsetLastChar < ll->LineStart(subLine + 1)) && (offsetLastChar <= ll->numCharsInLine)) { - // Update posAfter to point to the 2nd next char, this is where - // the next character ends, and 2nd next begins. We'll need - // to compare these two - posBefore = posAfter; - posAfter = MovePositionOutsideChar(posAfter + 1, 1); - offsetLastChar = offset + (posAfter - posCaret); - if ((ll->positions[offsetLastChar] - ll->positions[offsetLastChar - (posAfter - posBefore)]) > 0) { - // The char does not share horizontal space - break; - } - // Char shares horizontal space, update the numChars to draw - numCharsToDraw = offsetLastChar - offsetFirstChar; + const char *eol = StringFromEOLMode(pdoc->eolMode); + if (currentLine + lineDelta >= pdoc->LinesTotal()) + pdoc->InsertString(pdoc->Length(), eol, istrlen(eol)); + GoToLine(currentLine + lineDelta); + + selectionLength = pdoc->InsertString(CurrentPosition(), selectedText.Data(), selectionLength); + if (appendEol) { + const int lengthInserted = pdoc->InsertString(CurrentPosition() + selectionLength, eol, istrlen(eol)); + selectionLength += lengthInserted; } + SetSelection(CurrentPosition(), CurrentPosition() + selectionLength); +} - // We now know what to draw, update the caret drawing rectangle - rcCaret.left = ll->positions[offsetFirstChar] - ll->positions[lineStart] + xStart; - rcCaret.right = ll->positions[offsetFirstChar+numCharsToDraw] - ll->positions[lineStart] + xStart; +void Editor::MoveSelectedLinesUp() { + MoveSelectedLines(-1); +} + +void Editor::MoveSelectedLinesDown() { + MoveSelectedLines(1); +} - // Adjust caret position to take into account any word wrapping symbols. - if ((ll->wrapIndent != 0) && (lineStart != 0)) { - XYPOSITION wordWrapCharWidth = ll->wrapIndent; - rcCaret.left += wordWrapCharWidth; - rcCaret.right += wordWrapCharWidth; +void Editor::MoveCaretInsideView(bool ensureVisible) { + PRectangle rcClient = GetTextRectangle(); + Point pt = PointMainCaret(); + if (pt.y < rcClient.top) { + MovePositionTo(SPositionFromLocation( + Point::FromInts(lastXChosen - xOffset, static_cast(rcClient.top)), + false, false, UserVirtualSpace()), + Selection::noSel, ensureVisible); + } else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { + int yOfLastLineFullyDisplayed = static_cast(rcClient.top) + (LinesOnScreen() - 1) * vs.lineHeight; + MovePositionTo(SPositionFromLocation( + Point::FromInts(lastXChosen - xOffset, static_cast(rcClient.top) + yOfLastLineFullyDisplayed), + false, false, UserVirtualSpace()), + Selection::noSel, ensureVisible); } +} - // This character is where the caret block is, we override the colours - // (inversed) for drawing the caret here. - int styleMain = ll->styles[offsetFirstChar]; - FontAlias fontText = vsDraw.styles[styleMain].font; - surface->DrawTextClipped(rcCaret, fontText, - rcCaret.top + vsDraw.maxAscent, ll->chars + offsetFirstChar, - numCharsToDraw, vsDraw.styles[styleMain].back, - caretColour); +int Editor::DisplayFromPosition(int pos) { + AutoSurface surface(this); + return view.DisplayFromPosition(surface, pos, *this, vs); } -void Editor::RefreshPixMaps(Surface *surfaceWindow) { - if (!pixmapSelPattern->Initialised()) { - const int patternSize = 8; - pixmapSelPattern->InitPixMap(patternSize, patternSize, surfaceWindow, wMain.GetID()); - pixmapSelPatternOffset1->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 = PRectangle::FromInts(0, 0, patternSize, patternSize); +/** + * Ensure the caret is reasonably visible in context. + * +Caret policy in SciTE - // Initialize default colours based on the chrome colour scheme. Typically the highlight is white. - ColourDesired colourFMFill = vs.selbar; - ColourDesired colourFMStripes = vs.selbarlight; +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 (!(vs.selbarlight == 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; - } +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 (vs.foldmarginColour.isSet) { - // override default fold margin colour - colourFMFill = vs.foldmarginColour; - } - if (vs.foldmarginHighlightColour.isSet) { - // override default fold margin highlight colour - colourFMStripes = vs.foldmarginHighlightColour; - } +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. - pixmapSelPattern->FillRectangle(rcPattern, colourFMFill); - pixmapSelPatternOffset1->FillRectangle(rcPattern, colourFMStripes); - for (int y = 0; y < patternSize; y++) { - for (int x = y % 2; x < patternSize; x+=2) { - PRectangle rcPixel = PRectangle::FromInts(x, y, x + 1, y + 1); - pixmapSelPattern->FillRectangle(rcPixel, colourFMStripes); - pixmapSelPatternOffset1->FillRectangle(rcPixel, colourFMFill); - } - } - } +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 beginning of lines, +where most code reside, and the lines after the caret, eg. the body of a function. - 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 = PRectangle::FromInts(0, 0, 1, vs.lineHeight); - pixmapIndentGuide->FillRectangle(rcIG, vs.styles[STYLE_INDENTGUIDE].back); - pixmapIndentGuide->PenColour(vs.styles[STYLE_INDENTGUIDE].fore); - pixmapIndentGuideHighlight->FillRectangle(rcIG, vs.styles[STYLE_BRACELIGHT].back); - pixmapIndentGuideHighlight->PenColour(vs.styles[STYLE_BRACELIGHT].fore); - for (int stripe = 1; stripe < vs.lineHeight + 1; stripe += 2) { - PRectangle rcPixel = PRectangle::FromInts(0, stripe, 1, stripe + 1); - pixmapIndentGuide->FillRectangle(rcPixel, vs.styles[STYLE_INDENTGUIDE].fore); - pixmapIndentGuideHighlight->FillRectangle(rcPixel, vs.styles[STYLE_BRACELIGHT].fore); - } + | | | | | +slop | strict | jumps | even | Caret can go to the margin | When reaching limit (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 +*/ + +Editor::XYScrollPosition Editor::XYScrollToMakeVisible(const SelectionRange &range, const XYScrollOptions options) { + PRectangle rcClient = GetTextRectangle(); + Point pt = LocationFromPosition(range.caret); + Point ptAnchor = LocationFromPosition(range.anchor); + const Point ptOrigin = GetVisibleOriginInMain(); + pt.x += ptOrigin.x; + pt.y += ptOrigin.y; + ptAnchor.x += ptOrigin.x; + ptAnchor.y += ptOrigin.y; + const Point ptBottomCaret(pt.x, pt.y + vs.lineHeight - 1); + + XYScrollPosition newXY(xOffset, topLine); + if (rcClient.Empty()) { + return newXY; } - if (bufferedDraw) { - if (!pixmapLine->Initialised()) { - PRectangle rcClient = GetClientRectangle(); - pixmapLine->InitPixMap(static_cast(rcClient.Width()), vs.lineHeight, - surfaceWindow, wMain.GetID()); - pixmapSelMargin->InitPixMap(vs.fixedColumnWidth, - static_cast(rcClient.Height()), surfaceWindow, wMain.GetID()); + // Vertical positioning + if ((options & xysVertical) && (pt.y < rcClient.top || ptBottomCaret.y >= rcClient.bottom || (caretYPolicy & CARET_STRICT) != 0)) { + const int lineCaret = DisplayFromPosition(range.caret.Position()); + const int linesOnScreen = LinesOnScreen(); + const int halfScreen = Platform::Maximum(linesOnScreen - 1, 2) / 2; + const bool bSlop = (caretYPolicy & CARET_SLOP) != 0; + const bool bStrict = (caretYPolicy & CARET_STRICT) != 0; + const bool bJump = (caretYPolicy & CARET_JUMPS) != 0; + const bool 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 (!(options & xysUseMargin)) { + // 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 + newXY.topLine = lineCaret - yMoveT; + } else if (lineCaret > topLine + linesOnScreen - 1 - yMarginB) { + // Caret goes too low + newXY.topLine = 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 + newXY.topLine = lineCaret - yMoveT; + } else if (lineCaret > topLine + linesOnScreen - 1) { + // Caret goes too low + newXY.topLine = lineCaret - linesOnScreen + 1 + yMoveB; + } + } + } else { // No slop + if (!bStrict && !bJump) { + // Minimal move + if (lineCaret < topLine) { + // Caret goes too high + newXY.topLine = lineCaret; + } else if (lineCaret > topLine + linesOnScreen - 1) { + // Caret goes too low + if (bEven) { + newXY.topLine = lineCaret - linesOnScreen + 1; + } else { + newXY.topLine = lineCaret; + } + } + } else { // Strict or going out of display + if (bEven) { + // Always center caret + newXY.topLine = lineCaret - halfScreen; + } else { + // Always put caret on top of display + newXY.topLine = lineCaret; + } + } + } + if (!(range.caret == range.anchor)) { + const int lineAnchor = DisplayFromPosition(range.anchor.Position()); + if (lineAnchor < lineCaret) { + // Shift up to show anchor or as much of range as possible + newXY.topLine = std::min(newXY.topLine, lineAnchor); + newXY.topLine = std::max(newXY.topLine, lineCaret - LinesOnScreen()); + } else { + // Shift down to show anchor or as much of range as possible + newXY.topLine = std::max(newXY.topLine, lineAnchor - LinesOnScreen()); + newXY.topLine = std::min(newXY.topLine, lineCaret); + } } + newXY.topLine = Platform::Clamp(newXY.topLine, 0, MaxScrollPos()); } -} -void Editor::DrawCarets(Surface *surface, const ViewStyle &vsDraw, int lineDoc, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine) { - // When drag is active it is the only caret drawn - bool drawDrag = posDrag.IsValid(); - if (hideSelection && !drawDrag) - return; - const int posLineStart = pdoc->LineStart(lineDoc); - // For each selection draw - for (size_t r=0; (rEndLineStyle()].spaceWidth; - const XYPOSITION virtualOffset = posCaret.VirtualSpace() * spaceWidth; - if (ll->InLine(offset, subLine) && offset <= ll->numCharsBeforeEOL) { - XYPOSITION xposCaret = ll->positions[offset] + virtualOffset - ll->positions[ll->LineStart(subLine)]; - if (ll->wrapIndent != 0) { - int lineStart = ll->LineStart(subLine); - if (lineStart != 0) // Wrapped - xposCaret += ll->wrapIndent; - } - bool caretBlinkState = (caret.active && caret.on) || (!additionalCaretsBlink && !mainCaret); - bool caretVisibleState = additionalCaretsVisible || mainCaret; - if ((xposCaret >= 0) && (vsDraw.caretWidth > 0) && (vsDraw.caretStyle != CARETSTYLE_INVISIBLE) && - ((posDrag.IsValid()) || (caretBlinkState && caretVisibleState))) { - bool caretAtEOF = false; - bool caretAtEOL = false; - bool drawBlockCaret = false; - XYPOSITION widthOverstrikeCaret; - XYPOSITION caretWidthOffset = 0; - PRectangle rcCaret = rcLine; + // Horizontal positioning + if ((options & xysHorizontal) && !Wrapping()) { + const int halfScreen = Platform::Maximum(static_cast(rcClient.Width()) - 4, 4) / 2; + const bool bSlop = (caretXPolicy & CARET_SLOP) != 0; + const bool bStrict = (caretXPolicy & CARET_STRICT) != 0; + const bool bJump = (caretXPolicy & CARET_JUMPS) != 0; + const bool bEven = (caretXPolicy & CARET_EVEN) != 0; - if (posCaret.Position() == pdoc->Length()) { // At end of document - caretAtEOF = true; - widthOverstrikeCaret = vsDraw.aveCharWidth; - } else if ((posCaret.Position() - posLineStart) >= ll->numCharsInLine) { // At end of line - caretAtEOL = true; - widthOverstrikeCaret = vsDraw.aveCharWidth; + if (bSlop) { // A margin is defined + int xMoveL, xMoveR; + if (bStrict) { + int xMarginL, xMarginR; + if (!(options & xysUseMargin)) { + // 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 = static_cast(rcClient.Width()) - xMarginR - 4; + } + } + if (bJump && bEven) { + // Jump is used only in even mode + xMoveL = xMoveR = Platform::Clamp(caretXSlop * 3, 1, halfScreen); } else { - widthOverstrikeCaret = ll->positions[offset + 1] - ll->positions[offset]; + xMoveL = xMoveR = 0; // Not used, avoid a warning } - if (widthOverstrikeCaret < 3) // Make sure its visible - widthOverstrikeCaret = 3; - - if (xposCaret > 0) - caretWidthOffset = 0.51f; // Move back so overlaps both character cells. - xposCaret += xStart; - if (posDrag.IsValid()) { - /* Dragging text, use a line caret */ - rcCaret.left = static_cast(RoundXYPosition(xposCaret - caretWidthOffset)); - rcCaret.right = rcCaret.left + vsDraw.caretWidth; - } else if (inOverstrike && drawOverstrikeCaret) { - /* Overstrike (insert mode), use a modified bar caret */ - rcCaret.top = rcCaret.bottom - 2; - rcCaret.left = xposCaret + 1; - rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; - } else if (vsDraw.caretStyle == CARETSTYLE_BLOCK) { - /* Block caret */ - rcCaret.left = xposCaret; - if (!caretAtEOL && !caretAtEOF && (ll->chars[offset] != '\t') && !(IsControlCharacter(ll->chars[offset]))) { - drawBlockCaret = true; - rcCaret.right = xposCaret + widthOverstrikeCaret; + if (pt.x < rcClient.left + xMarginL) { + // Caret is on the left of the display + if (bJump && bEven) { + newXY.xOffset -= xMoveL; } else { - rcCaret.right = xposCaret + vsDraw.aveCharWidth; + // Move just enough to allow to display the caret + newXY.xOffset -= static_cast((rcClient.left + xMarginL) - pt.x); + } + } else if (pt.x >= rcClient.right - xMarginR) { + // Caret is on the right of the display + if (bJump && bEven) { + newXY.xOffset += xMoveR; + } else { + // Move just enough to allow to display the caret + newXY.xOffset += static_cast(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 { - /* Line caret */ - rcCaret.left = static_cast(RoundXYPosition(xposCaret - caretWidthOffset)); - rcCaret.right = rcCaret.left + vsDraw.caretWidth; + xMoveL = static_cast(rcClient.Width()) - xMoveR - 4; } - ColourDesired caretColour = mainCaret ? vsDraw.caretcolour : vsDraw.additionalCaretColour; - if (drawBlockCaret) { - DrawBlockCaret(surface, vsDraw, ll, subLine, xStart, offset, posCaret.Position(), rcCaret, caretColour); + if (pt.x < rcClient.left) { + // Caret is on the left of the display + newXY.xOffset -= xMoveL; + } else if (pt.x >= rcClient.right) { + // Caret is on the right of the display + newXY.xOffset += 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 + newXY.xOffset += static_cast(pt.x - rcClient.left - halfScreen); } else { - surface->FillRectangle(rcCaret, caretColour); + // Put caret on right + newXY.xOffset += static_cast(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) { + newXY.xOffset -= static_cast(rcClient.left - pt.x); + } else { + newXY.xOffset += static_cast(pt.x - rcClient.right) + 1; + } + } else if (pt.x >= rcClient.right) { + // Caret is on the right of the display + newXY.xOffset += static_cast(pt.x - rcClient.right) + 1; } } } - if (drawDrag) - break; - } -} - -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); - AllocateGraphics(); - - RefreshStyleData(); - if (paintState == paintAbandoned) - return; // Scroll bars may have changed so need redraw - RefreshPixMaps(surfaceWindow); - - paintAbandonedByStyling = false; - - StyleToPositionInView(PositionAfterArea(rcArea)); - - PRectangle rcClient = GetClientRectangle(); - Point ptOrigin = GetVisibleOriginInMain(); - //Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", - // rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); - - int screenLinePaintFirst = static_cast(rcArea.top) / vs.lineHeight; - - int xStart = vs.textStart - xOffset + static_cast(ptOrigin.x); - int ypos = 0; - if (!bufferedDraw) - ypos += screenLinePaintFirst * vs.lineHeight; - int yposScreen = screenLinePaintFirst * vs.lineHeight; - - if (NotifyUpdateUI()) { - RefreshStyleData(); - RefreshPixMaps(surfaceWindow); - } - - // Wrap the visible lines if needed. - if (WrapLines(wsVisible)) { - // 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()); - - if (!bufferedDraw) - surfaceWindow->SetClip(rcArea); - - if (paintState != paintAbandoned) { - if (vs.marginInside) { - PaintSelMargin(surfaceWindow, rcArea); - PRectangle rcRightMargin = rcClient; - rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; - if (rcArea.Intersects(rcRightMargin)) { - surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back); + // In case of a jump (find result) largely out of display, adjust the offset to display the caret + if (pt.x + xOffset < rcClient.left + newXY.xOffset) { + newXY.xOffset = static_cast(pt.x + xOffset - rcClient.left) - 2; + } else if (pt.x + xOffset >= rcClient.right + newXY.xOffset) { + newXY.xOffset = static_cast(pt.x + xOffset - rcClient.right) + 2; + if (vs.caretStyle == CARETSTYLE_BLOCK) { + // Ensure we can see a good portion of the block caret + newXY.xOffset += static_cast(vs.aveCharWidth); } - } else { // Else separate view so separate paint event but leftMargin included to allow overlap - PRectangle rcLeftMargin = rcArea; - rcLeftMargin.left = 0; - rcLeftMargin.right = rcLeftMargin.left + vs.leftMarginWidth; - if (rcArea.Intersects(rcLeftMargin)) { - surfaceWindow->FillRectangle(rcLeftMargin, vs.styles[STYLE_DEFAULT].back); + } + if (!(range.caret == range.anchor)) { + if (ptAnchor.x < pt.x) { + // Shift to left to show anchor or as much of range as possible + int maxOffset = static_cast(ptAnchor.x + xOffset - rcClient.left) - 1; + int minOffset = static_cast(pt.x + xOffset - rcClient.right) + 1; + newXY.xOffset = std::min(newXY.xOffset, maxOffset); + newXY.xOffset = std::max(newXY.xOffset, minOffset); + } else { + // Shift to right to show anchor or as much of range as possible + int minOffset = static_cast(ptAnchor.x + xOffset - rcClient.right) + 1; + int maxOffset = static_cast(pt.x + xOffset - rcClient.left) - 1; + newXY.xOffset = std::max(newXY.xOffset, minOffset); + newXY.xOffset = std::min(newXY.xOffset, maxOffset); } } + if (newXY.xOffset < 0) { + newXY.xOffset = 0; + } } - if (paintState == paintAbandoned) { - // Either styling or NotifyUpdateUI noticed that painting is needed - // outside the current painting rectangle - //Platform::DebugPrintf("Abandoning paint\n"); - if (Wrapping()) { - 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 newXY; +} + +void Editor::SetXYScroll(XYScrollPosition newXY) { + if ((newXY.topLine != topLine) || (newXY.xOffset != xOffset)) { + if (newXY.topLine != topLine) { + SetTopLine(newXY.topLine); + SetVerticalScrollPos(); + } + if (newXY.xOffset != xOffset) { + xOffset = newXY.xOffset; + ContainerNeedsUpdate(SC_UPDATE_H_SCROLL); + if (newXY.xOffset > 0) { + PRectangle rcText = GetTextRectangle(); + if (horizontalScrollBarVisible && + rcText.Width() + xOffset > scrollWidth) { + scrollWidth = xOffset + static_cast(rcText.Width()); + SetScrollBars(); + } } + SetHorizontalScrollPos(); } - return; + Redraw(); + UpdateSystemCaret(); } - //Platform::DebugPrintf("start display %d, offset = %d\n", pdoc->Length(), xOffset); - - // Allow text at start of line to overlap 1 pixel into the margin as this displays - // serifs and italic stems for aliased text. - const int leftTextOverlap = ((xOffset == 0) && (vs.leftMarginWidth > 0)) ? 1 : 0; +} - // Do the painting - if (rcArea.right > vs.textStart - leftTextOverlap) { +void Editor::ScrollRange(SelectionRange range) { + SetXYScroll(XYScrollToMakeVisible(range, xysDefault)); +} - Surface *surface = surfaceWindow; - if (bufferedDraw) { - surface = pixmapLine; - PLATFORM_ASSERT(pixmapLine->Initialised()); - } - surface->SetUnicodeMode(IsUnicodeMode()); - surface->SetDBCSMode(CodePage()); +void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { + SetXYScroll(XYScrollToMakeVisible(SelectionRange(posDrag.IsValid() ? posDrag : sel.RangeMain().caret), + static_cast((useMargin?xysUseMargin:0)|(vert?xysVertical:0)|(horiz?xysHorizontal:0)))); +} - int visibleLine = TopLineOfMain() + screenLinePaintFirst; +void Editor::ShowCaretAtCurrentPosition() { + if (hasFocus) { + caret.active = true; + caret.on = true; + SetTicking(true); + } else { + caret.active = false; + caret.on = false; + } + InvalidateCaret(); +} - SelectionPosition posCaret = sel.RangeMain().caret; - if (posDrag.IsValid()) - posCaret = posDrag; - int lineCaret = pdoc->LineFromPosition(posCaret.Position()); +void Editor::DropCaret() { + caret.active = false; + InvalidateCaret(); +} - PRectangle rcTextArea = rcClient; - if (vs.marginInside) { - rcTextArea.left += vs.textStart; - rcTextArea.right -= vs.rightMarginWidth; - } else { - rcTextArea = rcArea; - } +void Editor::CaretSetPeriod(int period) { + if (caret.period != period) { + caret.period = period; + caret.on = true; + InvalidateCaret(); + } +} - // Remove selection margin from drawing area so text will not be drawn - // on it in unbuffered mode. - if (!bufferedDraw && vs.marginInside) { - PRectangle rcClipText = rcTextArea; - rcClipText.left -= leftTextOverlap; - surfaceWindow->SetClip(rcClipText); +void Editor::InvalidateCaret() { + if (posDrag.IsValid()) { + InvalidateRange(posDrag.Position(), posDrag.Position() + 1); + } else { + for (size_t r=0; rcontainsCaret = lineDoc == lineCaret; - if (hideSelection) { - ll->containsCaret = false; - } +void Editor::NeedWrapping(int docLineStart, int docLineEnd) { +//Platform::DebugPrintf("\nNeedWrapping: %0d..%0d\n", docLineStart, docLineEnd); + if (wrapPending.AddRange(docLineStart, docLineEnd)) { + view.llc.Invalidate(LineLayout::llPositions); + } + // Wrap lines during idle. + if (Wrapping() && wrapPending.NeedsWrap()) { + SetIdle(true); + } +} - ll->hotspot = GetHotSpotRange(); +bool Editor::WrapOneLine(Surface *surface, int lineToWrap) { + AutoLineLayout ll(view.llc, view.RetrieveLineLayout(lineToWrap, *this)); + int linesWrapped = 1; + if (ll) { + view.LayoutLine(lineToWrap, surface, vs, ll, *this, wrapWidth); + linesWrapped = ll->lines; + } + return cs.SetHeight(lineToWrap, linesWrapped + + (vs.annotationVisible ? pdoc->AnnotationLines(lineToWrap) : 0)); +} - PRectangle rcLine = rcTextArea; - rcLine.top = static_cast(ypos); - rcLine.bottom = static_cast(ypos + vs.lineHeight); +// Perform wrapping for a subset of the lines needing wrapping. +// wsAll: wrap all lines which need wrapping in this single call +// wsVisible: wrap currently visible lines +// wsIdle: wrap one page + 100 lines +// Return true if wrapping occurred. +bool Editor::WrapLines(enum wrapScope ws) { + int goodTopLine = topLine; + bool wrapOccurred = false; + if (!Wrapping()) { + if (wrapWidth != LineLayout::wrapWidthInfinite) { + wrapWidth = LineLayout::wrapWidthInfinite; + for (int lineDoc = 0; lineDoc < pdoc->LinesTotal(); lineDoc++) { + cs.SetHeight(lineDoc, 1 + + (vs.annotationVisible ? pdoc->AnnotationLines(lineDoc) : 0)); + } + wrapOccurred = true; + } + wrapPending.Reset(); - bool bracesIgnoreStyle = false; - if ((vs.braceHighlightIndicatorSet && (bracesMatchStyle == STYLE_BRACELIGHT)) || - (vs.braceBadLightIndicatorSet && (bracesMatchStyle == STYLE_BRACEBAD))) { - bracesIgnoreStyle = true; - } - Range rangeLine(pdoc->LineStart(lineDoc), pdoc->LineStart(lineDoc + 1)); - // Highlight the current braces if any - ll->SetBracesHighlight(rangeLine, braces, static_cast(bracesMatchStyle), - static_cast(highlightGuideColumn * vs.spaceWidth), bracesIgnoreStyle); + } else if (wrapPending.NeedsWrap()) { + wrapPending.start = std::min(wrapPending.start, pdoc->LinesTotal()); + if (!SetIdle(true)) { + // Idle processing not supported so full wrap required. + ws = wsAll; + } + // Decide where to start wrapping + int lineToWrap = wrapPending.start; + int lineToWrapEnd = std::min(wrapPending.end, pdoc->LinesTotal()); + const int lineDocTop = cs.DocFromDisplay(topLine); + const int subLineTop = topLine - cs.DisplayFromDoc(lineDocTop); + if (ws == wsVisible) { + lineToWrap = Platform::Clamp(lineDocTop-5, wrapPending.start, pdoc->LinesTotal()); + // Priority wrap to just after visible area. + // Since wrapping could reduce display lines, treat each + // as taking only one display line. + lineToWrapEnd = lineDocTop; + int lines = LinesOnScreen() + 1; + while ((lineToWrapEnd < cs.LinesInDoc()) && (lines>0)) { + if (cs.GetVisible(lineToWrapEnd)) + lines--; + lineToWrapEnd++; + } + // .. and if the paint window is outside pending wraps + if ((lineToWrap > wrapPending.end) || (lineToWrapEnd < wrapPending.start)) { + // Currently visible text does not need wrapping + return false; + } + } else if (ws == wsIdle) { + lineToWrapEnd = lineToWrap + LinesOnScreen() + 100; + } + const int lineEndNeedWrap = std::min(wrapPending.end, pdoc->LinesTotal()); + lineToWrapEnd = std::min(lineToWrapEnd, lineEndNeedWrap); - if (leftTextOverlap && bufferedDraw) { - PRectangle rcSpacer = rcLine; - rcSpacer.right = rcSpacer.left; - rcSpacer.left -= 1; - surface->FillRectangle(rcSpacer, vs.styles[STYLE_DEFAULT].back); - } + // Ensure all lines being wrapped are styled. + pdoc->EnsureStyledTo(pdoc->LineStart(lineToWrapEnd)); - // Draw the line - DrawLine(surface, vs, lineDoc, visibleLine, xStart, rcLine, ll, subLine); - //durPaint += et.Duration(true); + if (lineToWrap < lineToWrapEnd) { - // Restore the previous styles for the brace highlights in case layout is in cache. - ll->RestoreBracesHighlight(rangeLine, braces, bracesIgnoreStyle); + PRectangle rcTextArea = GetClientRectangle(); + rcTextArea.left = static_cast(vs.textStart); + rcTextArea.right -= vs.rightMarginWidth; + wrapWidth = static_cast(rcTextArea.Width()); + RefreshStyleData(); + AutoSurface surface(this); + if (surface) { +//Platform::DebugPrintf("Wraplines: scope=%0d need=%0d..%0d perform=%0d..%0d\n", ws, wrapPending.start, wrapPending.end, lineToWrap, lineToWrapEnd); - bool expanded = cs.GetExpanded(lineDoc); - const int level = pdoc->GetLevel(lineDoc); - const int levelNext = pdoc->GetLevel(lineDoc + 1); - if ((level & SC_FOLDLEVELHEADERFLAG) && - ((level & SC_FOLDLEVELNUMBERMASK) < (levelNext & SC_FOLDLEVELNUMBERMASK))) { - // Paint the line above the fold - if ((expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_EXPANDED)) - || - (!expanded && (foldFlags & SC_FOLDFLAG_LINEBEFORE_CONTRACTED))) { - PRectangle rcFoldLine = rcLine; - rcFoldLine.bottom = rcFoldLine.top + 1; - surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore); - } - // Paint the line below the fold - if ((expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_EXPANDED)) - || - (!expanded && (foldFlags & SC_FOLDFLAG_LINEAFTER_CONTRACTED))) { - PRectangle rcFoldLine = rcLine; - rcFoldLine.top = rcFoldLine.bottom - 1; - surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore); + while (lineToWrap < lineToWrapEnd) { + if (WrapOneLine(surface, lineToWrap)) { + wrapOccurred = true; } + wrapPending.Wrapped(lineToWrap); + lineToWrap++; } - DrawCarets(surface, vs, lineDoc, xStart, rcLine, ll, subLine); - - if (bufferedDraw) { - Point from = Point::FromInts(vs.textStart-leftTextOverlap, 0); - PRectangle rcCopyArea = PRectangle::FromInts(vs.textStart - leftTextOverlap, yposScreen, - static_cast(rcClient.right - vs.rightMarginWidth), - yposScreen + vs.lineHeight); - surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); - } - - lineWidthMaxSeen = Platform::Maximum( - lineWidthMaxSeen, static_cast(ll->positions[ll->numCharsInLine])); - //durCopy += et.Duration(true); + goodTopLine = cs.DisplayFromDoc(lineDocTop) + std::min(subLineTop, cs.GetHeight(lineDocTop)-1); } + } - if (!bufferedDraw) { - ypos += vs.lineHeight; - } + // If wrapping is done, bring it to resting position + if (wrapPending.start >= lineEndNeedWrap) { + wrapPending.Reset(); + } + } - yposScreen += vs.lineHeight; - visibleLine++; + if (wrapOccurred) { + SetScrollBars(); + SetTopLine(Platform::Clamp(goodTopLine, 0, MaxScrollPos())); + SetVerticalScrollPos(); + } - //gdk_flush(); - } - ll.Set(0); - //if (durPaint < 0.00000001) - // durPaint = 0.00000001; + return wrapOccurred; +} - // Right column limit indicator - PRectangle rcBeyondEOF = (vs.marginInside) ? rcClient : rcArea; - rcBeyondEOF.left = static_cast(vs.textStart); - rcBeyondEOF.right = rcBeyondEOF.right - ((vs.marginInside) ? vs.rightMarginWidth : 0); - rcBeyondEOF.top = static_cast((cs.LinesDisplayed() - TopLineOfMain()) * vs.lineHeight); - if (rcBeyondEOF.top < rcBeyondEOF.bottom) { - surfaceWindow->FillRectangle(rcBeyondEOF, vs.styles[STYLE_DEFAULT].back); - if (vs.edgeState == EDGE_LINE) { - int edgeX = static_cast(vs.theEdge * vs.spaceWidth); - rcBeyondEOF.left = static_cast(edgeX + xStart); - rcBeyondEOF.right = rcBeyondEOF.left + 1; - surfaceWindow->FillRectangle(rcBeyondEOF, vs.edgecolour); +void Editor::LinesJoin() { + if (!RangeContainsProtected(targetStart, targetEnd)) { + UndoGroup ug(pdoc); + bool prevNonWS = true; + for (int pos = targetStart; pos < targetEnd; pos++) { + if (pdoc->IsPositionInLineEnd(pos)) { + targetEnd -= pdoc->LenChar(pos); + pdoc->DelChar(pos); + if (prevNonWS) { + // Ensure at least one space separating previous lines + const int lengthInserted = pdoc->InsertString(pos, " ", 1); + targetEnd += lengthInserted; + } + } else { + prevNonWS = pdoc->CharAt(pos) != ' '; } } - //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)); +const char *Editor::StringFromEOLMode(int eolMode) { + if (eolMode == SC_EOL_CRLF) { + return "\r\n"; + } else if (eolMode == SC_EOL_CR) { + return "\r"; + } else { + return "\n"; + } } -// 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, Sci_RangeToFormat *pfr) { - if (!pfr) - return 0; - - AutoSurface surface(pfr->hdc, this, SC_TECHNOLOGY_DEFAULT); - if (!surface) - return 0; - AutoSurface surfaceMeasure(pfr->hdcTarget, this, SC_TECHNOLOGY_DEFAULT); - if (!surfaceMeasure) { - return 0; +void Editor::LinesSplit(int pixelWidth) { + if (!RangeContainsProtected(targetStart, targetEnd)) { + if (pixelWidth == 0) { + PRectangle rcText = GetTextRectangle(); + pixelWidth = static_cast(rcText.Width()); + } + int lineStart = pdoc->LineFromPosition(targetStart); + int lineEnd = pdoc->LineFromPosition(targetEnd); + const char *eol = StringFromEOLMode(pdoc->eolMode); + UndoGroup ug(pdoc); + for (int line = lineStart; line <= lineEnd; line++) { + AutoSurface surface(this); + AutoLineLayout ll(view.llc, view.RetrieveLineLayout(line, *this)); + if (surface && ll) { + unsigned int posLineStart = pdoc->LineStart(line); + view.LayoutLine(line, surface, vs, ll, *this, pixelWidth); + int lengthInsertedTotal = 0; + for (int subLine = 1; subLine < ll->lines; subLine++) { + const int lengthInserted = pdoc->InsertString( + static_cast(posLineStart + lengthInsertedTotal + + ll->LineStart(subLine)), + eol, istrlen(eol)); + targetEnd += lengthInserted; + lengthInsertedTotal += lengthInserted; + } + } + lineEnd = pdoc->LineFromPosition(targetEnd); + } } +} - // Can't use measurements cached for screen - posCache.Clear(); +void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { + if (vs.fixedColumnWidth == 0) + return; - ViewStyle vsPrint(vs); - vsPrint.technology = SC_TECHNOLOGY_DEFAULT; + AllocateGraphics(); + RefreshStyleData(); + RefreshPixMaps(surfWindow); - // 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 <= SC_MAX_MARGIN; margin++) { - if ((vsPrint.ms[margin].style == SC_MARGIN_NUMBER) && (vsPrint.ms[margin].width > 0)) { - lineNumberIndex = margin; - } else { - vsPrint.ms[margin].width = 0; - } + // On GTK+ with Ubuntu overlay scroll bars, the surface may have been finished + // at this point. The Initialised call checks for this case and sets the status + // to be bad which avoids crashes in following calls. + if (!surfWindow->Initialised()) { + return; } - vsPrint.fixedColumnWidth = 0; - vsPrint.zoomLevel = printParameters.magnification; - // Don't show indentation guides - // If this ever gets changed, cached pixmap would need to be recreated if technology != SC_TECHNOLOGY_DEFAULT - vsPrint.viewIndentationGuides = ivNone; - // Don't show the selection when printing - vsPrint.selColours.back.isSet = false; - vsPrint.selColours.fore.isSet = false; - vsPrint.selAlpha = SC_ALPHA_NOALPHA; - vsPrint.selAdditionalAlpha = SC_ALPHA_NOALPHA; - vsPrint.whitespaceColours.back.isSet = false; - vsPrint.whitespaceColours.fore.isSet = false; - vsPrint.showCaretLineBackground = false; - vsPrint.alwaysShowCaretLineBackground = false; - // Don't highlight matching braces using indicators - vsPrint.braceHighlightIndicatorSet = false; - vsPrint.braceBadLightIndicatorSet = false; - // Set colours for printing according to users settings - for (size_t sty = 0; sty < vsPrint.styles.size(); sty++) { - if (printParameters.colourMode == SC_PRINT_INVERTLIGHT) { - vsPrint.styles[sty].fore = InvertedLight(vsPrint.styles[sty].fore); - vsPrint.styles[sty].back = InvertedLight(vsPrint.styles[sty].back); - } else if (printParameters.colourMode == SC_PRINT_BLACKONWHITE) { - vsPrint.styles[sty].fore = ColourDesired(0, 0, 0); - vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); - } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITE) { - vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); - } else if (printParameters.colourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { - if (sty <= STYLE_DEFAULT) { - vsPrint.styles[sty].back = ColourDesired(0xff, 0xff, 0xff); - } - } - } - // White background for the line numbers - vsPrint.styles[STYLE_LINENUMBER].back = ColourDesired(0xff, 0xff, 0xff); + PRectangle rcMargin = GetClientRectangle(); + Point ptOrigin = GetVisibleOriginInMain(); + rcMargin.Move(0, -ptOrigin.y); + rcMargin.left = 0; + rcMargin.right = static_cast(vs.fixedColumnWidth); - // Printing uses different margins, so reset screen margins - vsPrint.leftMarginWidth = 0; - vsPrint.rightMarginWidth = 0; + if (!rc.Intersects(rcMargin)) + return; - vsPrint.Refresh(*surfaceMeasure, pdoc->tabInChars); - // Determining width must happen after fonts have been realised in Refresh - int lineNumberWidth = 0; - if (lineNumberIndex >= 0) { - lineNumberWidth = static_cast(surfaceMeasure->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, - "99999" lineNumberPrintSpace, 5 + istrlen(lineNumberPrintSpace))); - vsPrint.ms[lineNumberIndex].width = lineNumberWidth; - vsPrint.Refresh(*surfaceMeasure, pdoc->tabInChars); // Recalculate fixedColumnWidth + Surface *surface; + if (view.bufferedDraw) { + surface = marginView.pixmapSelMargin; + } else { + surface = surfWindow; } - 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); + // Clip vertically to paint area to avoid drawing line numbers + if (rcMargin.bottom > rc.bottom) + rcMargin.bottom = rc.bottom; + if (rcMargin.top < rc.top) + rcMargin.top = rc.top; - int xStart = vsPrint.fixedColumnWidth + pfr->rc.left; - int ypos = pfr->rc.top; + marginView.PaintMargin(surface, topLine, rc, rcMargin, *this, vs); - int lineDoc = linePrintStart; + if (view.bufferedDraw) { + surfWindow->Copy(rcMargin, Point(rcMargin.left, rcMargin.top), *marginView.pixmapSelMargin); + } +} - int nPrintPos = pfr->chrg.cpMin; - int visibleLine = 0; - int widthPrint = pfr->rc.right - pfr->rc.left - vsPrint.fixedColumnWidth; - if (printParameters.wrapState == eWrapNone) - widthPrint = LineLayout::wrapWidthInfinite; +void Editor::RefreshPixMaps(Surface *surfaceWindow) { + view.RefreshPixMaps(surfaceWindow, wMain.GetID(), vs); + marginView.RefreshPixMaps(surfaceWindow, wMain.GetID(), vs); + if (view.bufferedDraw) { + PRectangle rcClient = GetClientRectangle(); + if (!view.pixmapLine->Initialised()) { - while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) { + view.pixmapLine->InitPixMap(static_cast(rcClient.Width()), vs.lineHeight, + surfaceWindow, wMain.GetID()); + } + if (!marginView.pixmapSelMargin->Initialised()) { + marginView.pixmapSelMargin->InitPixMap(vs.fixedColumnWidth, + static_cast(rcClient.Height()), surfaceWindow, wMain.GetID()); + } + } +} - // 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(); +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); + AllocateGraphics(); - // Copy this line and its styles from the document into local arrays - // and determine the x position at which each character starts. - LineLayout ll(pdoc->LineStart(lineDoc+1)-pdoc->LineStart(lineDoc)+1); - LayoutLine(lineDoc, surfaceMeasure, vsPrint, &ll, widthPrint); + RefreshStyleData(); + if (paintState == paintAbandoned) + return; // Scroll bars may have changed so need redraw + RefreshPixMaps(surfaceWindow); - ll.containsCaret = false; + paintAbandonedByStyling = false; - PRectangle rcLine = PRectangle::FromInts( - pfr->rc.left, - ypos, - pfr->rc.right - 1, - ypos + vsPrint.lineHeight); + StyleToPositionInView(PositionAfterArea(rcArea)); - // 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; - } - } + PRectangle rcClient = GetClientRectangle(); + //Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", + // rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); - if (ll.lines > 1 && startWithinLine >= ll.LineStart(ll.lines - 1)) { - visibleLine = -(ll.lines - 1); - } - } + if (NotifyUpdateUI()) { + RefreshStyleData(); + RefreshPixMaps(surfaceWindow); + } - 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 = rcNumber.right - surfaceMeasure->WidthText( - vsPrint.styles[STYLE_LINENUMBER].font, number, istrlen(number)); - surface->FlushCachedState(); - surface->DrawTextNoClip(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, - static_cast(ypos + vsPrint.maxAscent), number, istrlen(number), - vsPrint.styles[STYLE_LINENUMBER].fore, - vsPrint.styles[STYLE_LINENUMBER].back); + // Wrap the visible lines if needed. + if (WrapLines(wsVisible)) { + // 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(marginView.pixmapSelPattern->Initialised()); - // Draw the line - surface->FlushCachedState(); + if (!view.bufferedDraw) + surfaceWindow->SetClip(rcArea); - for (int iwl = 0; iwl < ll.lines; iwl++) { - if (ypos + vsPrint.lineHeight <= pfr->rc.bottom) { - if (visibleLine >= 0) { - if (draw) { - rcLine.top = static_cast(ypos); - rcLine.bottom = static_cast(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); + if (paintState != paintAbandoned) { + if (vs.marginInside) { + PaintSelMargin(surfaceWindow, rcArea); + PRectangle rcRightMargin = rcClient; + rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; + if (rcArea.Intersects(rcRightMargin)) { + surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back); + } + } else { // Else separate view so separate paint event but leftMargin included to allow overlap + PRectangle rcLeftMargin = rcArea; + rcLeftMargin.left = 0; + rcLeftMargin.right = rcLeftMargin.left + vs.leftMarginWidth; + if (rcArea.Intersects(rcLeftMargin)) { + surfaceWindow->FillRectangle(rcLeftMargin, vs.styles[STYLE_DEFAULT].back); } } + } - ++lineDoc; + if (paintState == paintAbandoned) { + // Either styling or NotifyUpdateUI noticed that painting is needed + // outside the current painting rectangle + //Platform::DebugPrintf("Abandoning paint\n"); + if (Wrapping()) { + 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; } - // Clear cache so measurements are not used for screen - posCache.Clear(); + view.PaintText(surfaceWindow, rcArea, rcClient, *this, vs); - return nPrintPos; + NotifyPainted(); +} + +// 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, Sci_RangeToFormat *pfr) { + if (!pfr) + return 0; + + AutoSurface surface(pfr->hdc, this, SC_TECHNOLOGY_DEFAULT); + if (!surface) + return 0; + AutoSurface surfaceMeasure(pfr->hdcTarget, this, SC_TECHNOLOGY_DEFAULT); + if (!surfaceMeasure) { + return 0; + } + return view.FormatRange(draw, pfr, surface, surfaceMeasure, *this, vs); } int Editor::TextWidth(int style, const char *text) { @@ -4593,7 +4675,7 @@ void Editor::NotifySavePoint(Document *, void *, bool atSavePoint) { void Editor::CheckModificationForWrap(DocModification mh) { if (mh.modificationType & (SC_MOD_INSERTTEXT | SC_MOD_DELETETEXT)) { - llc.Invalidate(LineLayout::llCheckTextAndStyle); + view.llc.Invalidate(LineLayout::llCheckTextAndStyle); int lineDoc = pdoc->LineFromPosition(mh.position); int lines = Platform::Maximum(0, mh.linesAdded); if (Wrapping()) { @@ -4663,7 +4745,7 @@ void Editor::NotifyModified(Document *, DocModification mh, void *) { } } if (mh.modificationType & SC_MOD_CHANGESTYLE) { - llc.Invalidate(LineLayout::llCheckTextAndStyle); + view.llc.Invalidate(LineLayout::llCheckTextAndStyle); } } else { // Move selection and brace highlights @@ -4744,7 +4826,7 @@ void Editor::NotifyModified(Document *, DocModification mh, void *) { if ((!willRedrawAll) && ((paintState == notPainting) || !PaintContainsMargin())) { if (mh.modificationType & SC_MOD_CHANGEFOLD) { // Fold changes can affect the drawing of following lines so redraw whole margin - RedrawSelMargin(highlightDelimiter.isEnabled ? -1 : mh.line-1, true); + RedrawSelMargin(marginView.highlightDelimiter.isEnabled ? -1 : mh.line - 1, true); } else { RedrawSelMargin(mh.line); } @@ -5197,29 +5279,8 @@ void Editor::ParaUpOrDown(int direction, Selection::selTypes selt) { 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; - } - } - } - } - } + int posRet = view.StartEndDisplayLine(surface, pos, start, *this, vs); if (posRet == INVALID_POSITION) { return pos; } else { @@ -6721,8 +6782,8 @@ void Editor::Tick() { } } } - if (horizontalScrollBarVisible && trackLineWidth && (lineWidthMaxSeen > scrollWidth)) { - scrollWidth = lineWidthMaxSeen; + if (horizontalScrollBarVisible && trackLineWidth && (view.lineWidthMaxSeen > scrollWidth)) { + scrollWidth = view.lineWidthMaxSeen; SetScrollBars(); } if ((dwellDelay < SC_TIME_FOREVER) && @@ -6882,9 +6943,9 @@ void Editor::SetAnnotationHeights(int start, int end) { int linesWrapped = 1; if (Wrapping()) { AutoSurface surface(this); - AutoLineLayout ll(llc, RetrieveLineLayout(line)); + AutoLineLayout ll(view.llc, view.RetrieveLineLayout(line, *this)); if (surface && ll) { - LayoutLine(line, surface, vs, ll, wrapWidth); + view.LayoutLine(line, surface, vs, ll, *this, wrapWidth); linesWrapped = ll->lines; } } @@ -6924,7 +6985,7 @@ void Editor::SetDocPointer(Document *document) { cs.Clear(); cs.InsertLines(0, pdoc->LinesTotal() - 1); SetAnnotationHeights(0, pdoc->LinesTotal()); - llc.Deallocate(); + view.llc.Deallocate(); NeedWrapping(); pdoc->AddWatcher(this, 0); @@ -7237,10 +7298,10 @@ int Editor::CodePage() const { int Editor::WrapCount(int line) { AutoSurface surface(this); - AutoLineLayout ll(llc, RetrieveLineLayout(line)); + AutoLineLayout ll(view.llc, view.RetrieveLineLayout(line, *this)); if (surface && ll) { - LayoutLine(line, surface, vs, ll, wrapWidth); + view.LayoutLine(line, surface, vs, ll, *this, wrapWidth); return ll->lines; } else { return 1; @@ -7706,7 +7767,7 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { } case SCI_HIDESELECTION: - hideSelection = wParam != 0; + view.hideSelection = wParam != 0; Redraw(); break; @@ -7894,25 +7955,25 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { break; case SCI_SETPRINTMAGNIFICATION: - printParameters.magnification = static_cast(wParam); + view.printParameters.magnification = static_cast(wParam); break; case SCI_GETPRINTMAGNIFICATION: - return printParameters.magnification; + return view.printParameters.magnification; case SCI_SETPRINTCOLOURMODE: - printParameters.colourMode = static_cast(wParam); + view.printParameters.colourMode = static_cast(wParam); break; case SCI_GETPRINTCOLOURMODE: - return printParameters.colourMode; + return view.printParameters.colourMode; case SCI_SETPRINTWRAPMODE: - printParameters.wrapState = (wParam == SC_WRAP_WORD) ? eWrapWord : eWrapNone; + view.printParameters.wrapState = (wParam == SC_WRAP_WORD) ? eWrapWord : eWrapNone; break; case SCI_GETPRINTWRAPMODE: - return printParameters.wrapState; + return view.printParameters.wrapState; case SCI_GETSTYLEAT: if (static_cast(wParam) >= pdoc->Length()) @@ -8054,17 +8115,17 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { break; case SCI_SETBUFFEREDDRAW: - bufferedDraw = wParam != 0; + view.bufferedDraw = wParam != 0; break; case SCI_GETBUFFEREDDRAW: - return bufferedDraw; + return view.bufferedDraw; case SCI_GETTWOPHASEDRAW: - return twoPhaseDraw; + return view.twoPhaseDraw; case SCI_SETTWOPHASEDRAW: - twoPhaseDraw = wParam != 0; + view.twoPhaseDraw = wParam != 0; InvalidateStyleRedraw(); break; @@ -8199,23 +8260,23 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { return vs.wrapIndentMode; case SCI_SETLAYOUTCACHE: - llc.SetLevel(static_cast(wParam)); + view.llc.SetLevel(static_cast(wParam)); break; case SCI_GETLAYOUTCACHE: - return llc.GetLevel(); + return view.llc.GetLevel(); case SCI_SETPOSITIONCACHE: - posCache.SetSize(wParam); + view.posCache.SetSize(wParam); break; case SCI_GETPOSITIONCACHE: - return posCache.GetSize(); + return view.posCache.GetSize(); case SCI_SETSCROLLWIDTH: PLATFORM_ASSERT(wParam > 0); if ((wParam > 0) && (wParam != static_cast(scrollWidth))) { - lineWidthMaxSeen = 0; + view.lineWidthMaxSeen = 0; scrollWidth = static_cast(wParam); SetScrollBars(); } @@ -8376,7 +8437,7 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { RedrawSelMargin(); break; case SCI_MARKERENABLEHIGHLIGHT: - highlightDelimiter.isEnabled = wParam == 1; + marginView.highlightDelimiter.isEnabled = wParam == 1; RedrawSelMargin(); break; case SCI_MARKERSETBACK: @@ -9443,20 +9504,20 @@ sptr_t Editor::WndProc(unsigned int iMessage, uptr_t wParam, sptr_t lParam) { return multiPasteMode; case SCI_SETADDITIONALCARETSBLINK: - additionalCaretsBlink = wParam != 0; + view.additionalCaretsBlink = wParam != 0; InvalidateCaret(); break; case SCI_GETADDITIONALCARETSBLINK: - return additionalCaretsBlink; + return view.additionalCaretsBlink; case SCI_SETADDITIONALCARETSVISIBLE: - additionalCaretsVisible = wParam != 0; + view.additionalCaretsVisible = wParam != 0; InvalidateCaret(); break; case SCI_GETADDITIONALCARETSVISIBLE: - return additionalCaretsVisible; + return view.additionalCaretsVisible; case SCI_GETSELECTIONS: return sel.Count(); diff --git a/src/Editor.h b/src/Editor.h index 64e46f83d..fd368df39 100644 --- a/src/Editor.h +++ b/src/Editor.h @@ -167,9 +167,132 @@ struct PrintParameters { PrintParameters(); }; +/** +* EditModel holds the state that needs to be seen by EditView. +*/ +class EditModel { + // Private so EditModel objects can not be copied + EditModel(const EditModel &); + EditModel &operator=(const EditModel &); + +public: + bool inOverstrike; + int xOffset; ///< Horizontal scrolled amount in pixels + bool trackLineWidth; + + SpecialRepresentations reprs; + Caret caret; + SelectionPosition posDrag; + Position braces[2]; + int bracesMatchStyle; + int highlightGuideColumn; + Selection sel; + bool primarySelection; + + int foldFlags; + ContractionState cs; + + // Hotspot support + Range hotspot; + + // Wrapping support + int wrapWidth; + + Document *pdoc; + + EditModel(); + ~EditModel(); + + virtual int TopLineOfMain() const = 0; + virtual Point GetVisibleOriginInMain() const = 0; + virtual int LinesOnScreen() const = 0; + virtual Range GetHotSpotRange() const = 0; +}; + +/** +* MarginView draws the margins. +*/ +class MarginView { +public: + // Highlight current folding block + HighlightDelimiter highlightDelimiter; + Surface *pixmapSelMargin; + Surface *pixmapSelPattern; + Surface *pixmapSelPatternOffset1; + + MarginView(); + + void DropGraphics(bool freeObjects); + void AllocateGraphics(const ViewStyle &vsDraw); + void RefreshPixMaps(Surface *surfaceWindow, WindowID wid, const ViewStyle &vsDraw); + void PaintMargin(Surface *surface, int topLine, PRectangle rc, PRectangle rcMargin, + const EditModel &model, const ViewStyle &vs); +}; + +/** +* EditView draws the main text area. +*/ +class EditView { +public: + PrintParameters printParameters; + + bool hideSelection; + bool drawOverstrikeCaret; + + /** In bufferedDraw mode, graphics operations are drawn to a pixmap and then copied to + * the screen. This avoids flashing but is about 30% slower. */ + bool bufferedDraw; + /** In twoPhaseDraw mode, drawing is performed in two phases, first the background + * and then the foreground. This avoids chopping off characters that overlap the next run. */ + bool twoPhaseDraw; + + int lineWidthMaxSeen; + bool additionalCaretsBlink; + bool additionalCaretsVisible; + + Surface *pixmapLine; + Surface *pixmapIndentGuide; + Surface *pixmapIndentGuideHighlight; + + LineLayoutCache llc; + PositionCache posCache; + + EditView(); + + void DropGraphics(bool freeObjects); + void AllocateGraphics(const ViewStyle &vsDraw); + void RefreshPixMaps(Surface *surfaceWindow, WindowID wid, const ViewStyle &vsDraw); + + LineLayout *RetrieveLineLayout(int lineNumber, const EditModel &model); + void LayoutLine(int line, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, + const EditModel &model, int width = LineLayout::wrapWidthInfinite); + + Point LocationFromPosition(Surface *surface, SelectionPosition pos, int topLine, const EditModel &model, const ViewStyle &vs); + SelectionPosition SPositionFromLocation(Surface *surface, Point pt, bool canReturnInvalid, bool charPosition, bool virtualSpace, + const EditModel &model, const ViewStyle &vs); + SelectionPosition SPositionFromLineX(Surface *surface, int lineDoc, int x, const EditModel &model, const ViewStyle &vs); + int DisplayFromPosition(Surface *surface, int pos, const EditModel &model, const ViewStyle &vs); + int StartEndDisplayLine(Surface *surface, int pos, bool start, const EditModel &model, const ViewStyle &vs); + + void DrawIndentGuide(Surface *surface, int lineVisible, int lineHeight, int start, PRectangle rcSegment, bool highlight); + void DrawEOL(Surface *surface, const ViewStyle &vsDraw, PRectangle rcLine, LineLayout *ll, + int line, int lineEnd, int xStart, int subLine, XYACCUMULATOR subLineStart, + ColourOptional background, const EditModel &model); + void DrawAnnotation(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model); + void DrawCarets(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) const; + void DrawLine(Surface *surface, const ViewStyle &vsDraw, int line, int lineVisible, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model); + void PaintText(Surface *surfaceWindow, PRectangle rcArea, PRectangle rcClient, + const EditModel &model, const ViewStyle &vsDraw); + long FormatRange(bool draw, Sci_RangeToFormat *pfr, Surface *surface, Surface *surfaceMeasure, + const EditModel &model, const ViewStyle &vs); +}; + /** */ -class Editor : public DocWatcher { +class Editor : public EditModel, public DocWatcher { // Private so Editor objects can not be copied Editor(const Editor &); Editor &operator=(const Editor &); @@ -189,32 +312,17 @@ protected: // ScintillaBase subclass needs access to much of Editor Point sizeRGBAImage; float scaleRGBAImage; - PrintParameters printParameters; + MarginView marginView; + EditView view; int cursorMode; - // Highlight current folding block - HighlightDelimiter highlightDelimiter; - bool hasFocus; - bool hideSelection; - bool inOverstrike; - bool drawOverstrikeCaret; bool mouseDownCaptures; - /** In bufferedDraw mode, graphics operations are drawn to a pixmap and then copied to - * the screen. This avoids flashing but is about 30% slower. */ - bool bufferedDraw; - /** In twoPhaseDraw mode, drawing is performed in two phases, first the background - * and then the foreground. This avoids chopping off characters that overlap the next run. */ - bool twoPhaseDraw; - - int xOffset; ///< Horizontal scrolled amount in pixels int xCaretMargin; ///< Ensure this many pixels visible on both sides of caret bool horizontalScrollBarVisible; int scrollWidth; - bool trackLineWidth; - int lineWidthMaxSeen; bool verticalScrollBarVisible; bool endAtLastLine; int caretSticky; @@ -223,25 +331,11 @@ protected: // ScintillaBase subclass needs access to much of Editor bool multipleSelection; bool additionalSelectionTyping; int multiPasteMode; - bool additionalCaretsBlink; - bool additionalCaretsVisible; int virtualSpaceOptions; - Surface *pixmapLine; - Surface *pixmapSelMargin; - Surface *pixmapSelPattern; - Surface *pixmapSelPatternOffset1; - Surface *pixmapIndentGuide; - Surface *pixmapIndentGuideHighlight; - - LineLayoutCache llc; - PositionCache posCache; - SpecialRepresentations reprs; - KeyMap kmap; - Caret caret; Timer timer; Timer autoScrollTimer; enum { autoScrollDelay = 200 }; @@ -257,7 +351,6 @@ protected: // ScintillaBase subclass needs access to much of Editor Point ptMouseLast; enum { ddNone, ddInitial, ddDragging } inDragDrop; bool dropWentOutside; - SelectionPosition posDrag; SelectionPosition posDrop; int hotSpotClickPos; int lastXChosen; @@ -274,9 +367,6 @@ protected: // ScintillaBase subclass needs access to much of Editor int lengthForEncode; int needUpdateUI; - Position braces[2]; - int bracesMatchStyle; - int highlightGuideColumn; enum { notPainting, painting, paintAbandoned } paintState; bool paintAbandonedByStyling; @@ -288,8 +378,6 @@ protected: // ScintillaBase subclass needs access to much of Editor int modEventMask; SelectionText drag; - Selection sel; - bool primarySelection; int caretXPolicy; int caretXSlop; ///< Ensure this many pixels visible on both sides of caret @@ -304,21 +392,13 @@ protected: // ScintillaBase subclass needs access to much of Editor bool recordingMacro; - int foldFlags; int foldAutomatic; - ContractionState cs; - - // Hotspot support - Range hotspot; // Wrapping support - int wrapWidth; WrapPending wrapPending; bool convertPastes; - Document *pdoc; - Editor(); virtual ~Editor(); virtual void Initialise() = 0; @@ -340,7 +420,7 @@ protected: // ScintillaBase subclass needs access to much of Editor virtual PRectangle GetClientDrawingRectangle(); PRectangle GetTextRectangle() const; - int LinesOnScreen() const; + virtual int LinesOnScreen() const; int LinesToScroll() const; int MaxScrollPos() const; SelectionPosition ClampPositionIntoDocument(SelectionPosition sp) const; @@ -349,7 +429,7 @@ protected: // ScintillaBase subclass needs access to much of Editor int XFromPosition(int pos); int XFromPosition(SelectionPosition sp); SelectionPosition SPositionFromLocation(Point pt, bool canReturnInvalid=false, bool charPosition=false, bool virtualSpace=true); - int PositionFromLocation(Point pt, bool canReturnInvalid=false, bool charPosition=false); + int PositionFromLocation(Point pt, bool canReturnInvalid = false, bool charPosition = false); SelectionPosition SPositionFromLineX(int lineDoc, int x); int PositionFromLineX(int line, int x); int LineFromLocation(Point pt) const; @@ -431,30 +511,7 @@ protected: // ScintillaBase subclass needs access to much of Editor void LinesJoin(); void LinesSplit(int pixelWidth); - int SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) const; void PaintSelMargin(Surface *surface, PRectangle &rc); - LineLayout *RetrieveLineLayout(int lineNumber); - void LayoutLine(int line, Surface *surface, const ViewStyle &vstyle, LineLayout *ll, - int width=LineLayout::wrapWidthInfinite); - ColourDesired SelectionBackground(const ViewStyle &vsDraw, bool main) const; - ColourDesired TextBackground(const ViewStyle &vsDraw, ColourOptional background, int inSelection, bool inHotspot, int styleMain, int i, LineLayout *ll) const; - void DrawIndentGuide(Surface *surface, int lineVisible, int lineHeight, int start, PRectangle rcSegment, bool highlight); - static void DrawWrapMarker(Surface *surface, PRectangle rcPlace, bool isEndMarker, ColourDesired wrapColour); - void DrawEOL(Surface *surface, const ViewStyle &vsDraw, PRectangle rcLine, LineLayout *ll, - int line, int lineEnd, int xStart, int subLine, XYACCUMULATOR subLineStart, - ColourOptional background); - static void DrawIndicator(int indicNum, int startPos, int endPos, Surface *surface, const ViewStyle &vsDraw, - int xStart, PRectangle rcLine, LineLayout *ll, int subLine); - void DrawIndicators(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine, int lineEnd, bool under); - void DrawAnnotation(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine); - void DrawLine(Surface *surface, const ViewStyle &vsDraw, int line, int lineVisible, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine); - void DrawBlockCaret(Surface *surface, const ViewStyle &vsDraw, LineLayout *ll, int subLine, - int xStart, int offset, int posCaret, PRectangle rcCaret, ColourDesired caretColour) const; - void DrawCarets(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine); void RefreshPixMaps(Surface *surfaceWindow); void Paint(Surface *surfaceWindow, PRectangle rcArea); long FormatRange(bool draw, Sci_RangeToFormat *pfr); diff --git a/src/PositionCache.cxx b/src/PositionCache.cxx index 9e55c1a82..53767e032 100644 --- a/src/PositionCache.cxx +++ b/src/PositionCache.cxx @@ -438,13 +438,12 @@ void BreakFinder::Insert(int val) { } } -BreakFinder::BreakFinder(const LineLayout *ll_, const Selection *psel, int lineStart_, int lineEnd_, int posLineStart_, +BreakFinder::BreakFinder(const LineLayout *ll_, const Selection *psel, Range lineRange_, int posLineStart_, int xStart, bool breakForSelection, const Document *pdoc_, const SpecialRepresentations *preprs_) : ll(ll_), - lineStart(lineStart_), - lineEnd(lineEnd_), + lineRange(lineRange_), posLineStart(posLineStart_), - nextBreak(lineStart_), + nextBreak(lineRange_.start), saeCurrentPos(0), saeNext(0), subBreak(-1), @@ -455,15 +454,15 @@ BreakFinder::BreakFinder(const LineLayout *ll_, const Selection *psel, int lineS // Search for first visible break // First find the first visible character if (xStart > 0.0f) - nextBreak = ll->FindBefore(static_cast(xStart), lineStart, lineEnd); + nextBreak = ll->FindBefore(static_cast(xStart), lineRange.start, lineRange.end); // Now back to a style break - while ((nextBreak > lineStart) && (ll->styles[nextBreak] == ll->styles[nextBreak - 1])) { + while ((nextBreak > lineRange.start) && (ll->styles[nextBreak] == ll->styles[nextBreak - 1])) { nextBreak--; } if (breakForSelection) { SelectionPosition posStart(posLineStart); - SelectionPosition posEnd(posLineStart + lineEnd); + SelectionPosition posEnd(posLineStart + lineRange.end); SelectionSegment segmentLine(posStart, posEnd); for (size_t r=0; rCount(); r++) { SelectionSegment portion = psel->Range(r).Intersect(segmentLine); @@ -477,7 +476,7 @@ BreakFinder::BreakFinder(const LineLayout *ll_, const Selection *psel, int lineS } Insert(ll->edgeColumn); - Insert(lineEnd); + Insert(lineRange.end); saeNext = (!selAndEdge.empty()) ? selAndEdge[0] : -1; } @@ -487,19 +486,19 @@ BreakFinder::~BreakFinder() { TextSegment BreakFinder::Next() { if (subBreak == -1) { int prev = nextBreak; - while (nextBreak < lineEnd) { + while (nextBreak < lineRange.end) { int charWidth = 1; if (encodingFamily == efUnicode) - charWidth = UTF8DrawBytes(reinterpret_cast(ll->chars) + nextBreak, lineEnd - nextBreak); + charWidth = UTF8DrawBytes(reinterpret_cast(ll->chars) + nextBreak, lineRange.end - nextBreak); else if (encodingFamily == efDBCS) charWidth = pdoc->IsDBCSLeadByte(ll->chars[nextBreak]) ? 2 : 1; const Representation *repr = preprs->RepresentationFromCharacter(ll->chars + nextBreak, charWidth); if (((nextBreak > 0) && (ll->styles[nextBreak] != ll->styles[nextBreak - 1])) || repr || (nextBreak == saeNext)) { - while ((nextBreak >= saeNext) && (saeNext < lineEnd)) { + while ((nextBreak >= saeNext) && (saeNext < lineRange.end)) { saeCurrentPos++; - saeNext = (saeCurrentPos < selAndEdge.size()) ? selAndEdge[saeCurrentPos] : lineEnd; + saeNext = (saeCurrentPos < selAndEdge.size()) ? selAndEdge[saeCurrentPos] : lineRange.end; } if ((nextBreak > prev) || repr) { // Have a segment to report @@ -540,7 +539,7 @@ TextSegment BreakFinder::Next() { } bool BreakFinder::More() const { - return (subBreak >= 0) || (nextBreak < lineEnd); + return (subBreak >= 0) || (nextBreak < lineRange.end); } PositionCacheEntry::PositionCacheEntry() : diff --git a/src/PositionCache.h b/src/PositionCache.h index 05005e9ac..c81740ee3 100644 --- a/src/PositionCache.h +++ b/src/PositionCache.h @@ -148,8 +148,7 @@ struct TextSegment { // Class to break a line of text into shorter runs at sensible places. class BreakFinder { const LineLayout *ll; - int lineStart; - int lineEnd; + Range lineRange; int posLineStart; int nextBreak; std::vector selAndEdge; @@ -168,7 +167,7 @@ public: enum { lengthStartSubdivision = 300 }; // Try to make each subdivided run lengthEachSubdivision or shorter. enum { lengthEachSubdivision = 100 }; - BreakFinder(const LineLayout *ll_, const Selection *psel, int lineStart_, int lineEnd_, int posLineStart_, + BreakFinder(const LineLayout *ll_, const Selection *psel, Range rangeLine_, int posLineStart_, int xStart, bool breakForSelection, const Document *pdoc_, const SpecialRepresentations *preprs_); ~BreakFinder(); TextSegment Next(); -- cgit v1.2.3