diff options
author | Neil <nyamatongwe@gmail.com> | 2014-07-08 18:05:35 +1000 |
---|---|---|
committer | Neil <nyamatongwe@gmail.com> | 2014-07-08 18:05:35 +1000 |
commit | 5bfc97a368eb7c8d3c78b0c454be806d6dcb6340 (patch) | |
tree | ccca6ba6e81d9cb3e98abf36d1eeaf7eb98c92a4 /src | |
parent | 1e8908c6d5fd998e9d8a9408a6b86ef4a2ff64e8 (diff) | |
download | scintilla-mirror-5bfc97a368eb7c8d3c78b0c454be806d6dcb6340.tar.gz |
Split out EditModel, MarginView, and EditView classes into separate files.
Diffstat (limited to 'src')
-rw-r--r-- | src/EditModel.cxx | 74 | ||||
-rw-r--r-- | src/EditModel.h | 67 | ||||
-rw-r--r-- | src/EditView.cxx | 1815 | ||||
-rw-r--r-- | src/EditView.h | 113 | ||||
-rw-r--r-- | src/Editor.cxx | 2298 | ||||
-rw-r--r-- | src/Editor.h | 141 | ||||
-rw-r--r-- | src/MarginView.cxx | 532 | ||||
-rw-r--r-- | src/MarginView.h | 47 | ||||
-rw-r--r-- | src/ScintillaBase.cxx | 3 |
9 files changed, 2654 insertions, 2436 deletions
diff --git a/src/EditModel.cxx b/src/EditModel.cxx new file mode 100644 index 000000000..815d22781 --- /dev/null +++ b/src/EditModel.cxx @@ -0,0 +1,74 @@ +// Scintilla source code edit control +/** @file EditModel.cxx + ** Defines the editor state that must be visible to EditorView. + **/ +// Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <assert.h> +#include <ctype.h> + +#include <string> +#include <vector> +#include <map> +#include <algorithm> +#include <memory> + +#include "Platform.h" + +#include "ILexer.h" +#include "Scintilla.h" + +#include "StringCopy.h" +#include "SplitVector.h" +#include "Partitioning.h" +#include "RunStyles.h" +#include "ContractionState.h" +#include "CellBuffer.h" +#include "KeyMap.h" +#include "Indicator.h" +#include "XPM.h" +#include "LineMarker.h" +#include "Style.h" +#include "ViewStyle.h" +#include "CharClassify.h" +#include "Decoration.h" +#include "CaseFolder.h" +#include "Document.h" +#include "UniConversion.h" +#include "Selection.h" +#include "PositionCache.h" +#include "EditModel.h" + +#ifdef SCI_NAMESPACE +using namespace Scintilla; +#endif + +Caret::Caret() : + active(false), on(false), period(500) {} + +EditModel::EditModel() { + inOverstrike = false; + xOffset = 0; + trackLineWidth = false; + posDrag = SelectionPosition(invalidPosition); + braces[0] = invalidPosition; + braces[1] = invalidPosition; + bracesMatchStyle = STYLE_BRACEBAD; + highlightGuideColumn = 0; + primarySelection = true; + foldFlags = 0; + hotspot = Range(invalidPosition); + wrapWidth = LineLayout::wrapWidthInfinite; + pdoc = new Document(); + pdoc->AddRef(); +} + +EditModel::~EditModel() { + pdoc->Release(); + pdoc = 0; +} diff --git a/src/EditModel.h b/src/EditModel.h new file mode 100644 index 000000000..86d2cd40e --- /dev/null +++ b/src/EditModel.h @@ -0,0 +1,67 @@ +// Scintilla source code edit control +/** @file EditModel.h + ** Defines the editor state that must be visible to EditorView. + **/ +// Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef EDITMODEL_H +#define EDITMODEL_H + +#ifdef SCI_NAMESPACE +namespace Scintilla { +#endif + +/** +*/ +class Caret { +public: + bool active; + bool on; + int period; + + Caret(); +}; + +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; +}; + +#ifdef SCI_NAMESPACE +} +#endif + +#endif diff --git a/src/EditView.cxx b/src/EditView.cxx new file mode 100644 index 000000000..26ede6cd9 --- /dev/null +++ b/src/EditView.cxx @@ -0,0 +1,1815 @@ +// Scintilla source code edit control +/** @file Editor.cxx + ** Defines the appearance of the main text area of the editor window. + **/ +// Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <assert.h> +#include <ctype.h> + +#include <string> +#include <vector> +#include <map> +#include <algorithm> +#include <memory> + +#include "Platform.h" + +#include "ILexer.h" +#include "Scintilla.h" + +#include "StringCopy.h" +#include "SplitVector.h" +#include "Partitioning.h" +#include "RunStyles.h" +#include "ContractionState.h" +#include "CellBuffer.h" +#include "KeyMap.h" +#include "Indicator.h" +#include "XPM.h" +#include "LineMarker.h" +#include "Style.h" +#include "ViewStyle.h" +#include "CharClassify.h" +#include "Decoration.h" +#include "CaseFolder.h" +#include "Document.h" +#include "UniConversion.h" +#include "Selection.h" +#include "PositionCache.h" +#include "EditModel.h" +#include "MarginView.h" +#include "EditView.h" + +#ifdef SCI_NAMESPACE +using namespace Scintilla; +#endif + +static inline bool IsControlCharacter(int ch) { + // iscntrl returns true for lots of chars > 127 which are displayable + return ch >= 0 && ch < ' '; +} + +PrintParameters::PrintParameters() { + magnification = 0; + colourMode = SC_PRINT_NORMAL; + wrapState = eWrapWord; +} + +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); +} + +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(); + } +} + +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); +} + +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 DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { + int ydiff = static_cast<int>(rcTab.bottom - rcTab.top) / 2; + int xhead = static_cast<int>(rcTab.right) - 1 - ydiff; + if (xhead <= rcTab.left) { + ydiff -= static_cast<int>(rcTab.left) - xhead - 1; + xhead = static_cast<int>(rcTab.left) - 1; + } + if ((rcTab.left + 2) < (rcTab.right - 1)) + surface->MoveTo(static_cast<int>(rcTab.left) + 2, ymid); + else + surface->MoveTo(static_cast<int>(rcTab.right) - 1, ymid); + surface->LineTo(static_cast<int>(rcTab.right) - 1, ymid); + surface->LineTo(xhead, ymid - ydiff); + surface->MoveTo(static_cast<int>(rcTab.right) - 1, ymid); + surface->LineTo(xhead, ymid + ydiff); +} + +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); + } + } +} + +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()); +} + +/** +* 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; + + 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<unsigned char>(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<char>(tolower(chDoc))); + else // Style::caseUpper + allSame = allSame && + (ll->chars[numCharsInLine] == static_cast<char>(toupper(chDoc))); + numCharsInLine++; + } + allSame = allSame && (ll->styles[numCharsInLine] == styleByte); // For eolFilled + if (allSame) { + ll->validity = LineLayout::llPositions; + } else { + ll->validity = LineLayout::llInvalid; + } + } else { + ll->validity = LineLayout::llInvalid; + } + } + if (ll->validity == LineLayout::llInvalid) { + ll->widthLine = LineLayout::wrapWidthInfinite; + ll->lines = 1; + if (vstyle.edgeState == EDGE_BACKGROUND) { + ll->edgeColumn = model.pdoc->FindColumn(line, vstyle.theEdge); + if (ll->edgeColumn >= posLineStart) { + ll->edgeColumn -= posLineStart; + } + } else { + ll->edgeColumn = -1; + } + + // 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; charInLine<lineLength; charInLine++) { + char chDoc = ll->chars[charInLine]; + if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseUpper) + ll->chars[charInLine] = static_cast<char>(toupper(chDoc)); + else if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseLower) + ll->chars[charInLine] = static_cast<char>(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 + + // 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; + + BreakFinder bfLayout(ll, NULL, Range(0, numCharsInLine), posLineStart, 0, false, model.pdoc, &model.reprs); + while (bfLayout.More()) { + + const TextSegment ts = bfLayout.Next(); + + 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<int>((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<unsigned int>(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); + } + + 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<int>(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<int>(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; + } +} + +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; +} + +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<int>(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<int>( + (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); +} + +/** +* 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)); + } + const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; + const int spaceOffset = static_cast<int>( + (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); + return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); + } + return SelectionPosition(0); +} + +int EditView::DisplayFromPosition(Surface *surface, int pos, const EditModel &model, const ViewStyle &vs) { + int lineDoc = model.pdoc->LineFromPosition(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++; + } + } + } + return lineDisplay; +} + +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; + } + } + } + } + } + return posRet; +} + +static ColourDesired SelectionBackground(const ViewStyle &vsDraw, bool main, bool primarySelection) { + return main ? + (primarySelection ? vsDraw.selColours.back : vsDraw.selBackground2) : + vsDraw.selAdditionalBackground; +} + +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 { + 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; + } +} + +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<int>(rcSegment.top), start + 2, static_cast<int>(rcSegment.bottom)); + surface->Copy(rcCopyArea, from, + highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); +} + +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); + } +} + +void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment, + const char *s, ColourDesired textBack, ColourDesired textFore, bool twoPhaseDraw) { + if (!twoPhaseDraw) { + surface->FillRectangle(rcSegment, textBack); + } + FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; + int normalCharHeight = static_cast<int>(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<int>(s ? strlen(s) : 0), + textBack, textFore); +} + +void EditView::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) { + + const int posLineStart = model.pdoc->LineStart(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<XYPOSITION>(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; r<model.sel.Count(); r++) { + int alpha = (r == model.sel.Main()) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; + if (alpha == SC_ALPHA_NOALPHA) { + SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange); + if (!portion.Empty()) { + const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; + rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - + static_cast<XYPOSITION>(subLineStart)+portion.start.VirtualSpace() * spaceWidth; + rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - + static_cast<XYPOSITION>(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)); + } + } + } + } + } + + 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; + } + + // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on + XYPOSITION blobsWidth = 0; + if (lastSubLine) { + for (int eolPos = ll->numCharsBeforeEOL; eolPos<ll->numCharsInLine; eolPos++) { + rcSegment.left = xStart + ll->positions[eolPos] - static_cast<XYPOSITION>(subLineStart)+virtualSpace; + rcSegment.right = xStart + ll->positions[eolPos + 1] - static_cast<XYPOSITION>(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; + } + } + 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); + } + } + } + + // Draw the eol-is-selected rectangle + rcSegment.left = xEol + xStart + virtualSpace + blobsWidth; + rcSegment.right = rcSegment.left + vsDraw.aveCharWidth; + + 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 { + 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); + } + } + + // 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 { + 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); + } + } + + bool drawWrapMarkEnd = false; + + if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { + if (subLine + 1 < ll->lines) { + drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; + } + } + + if (drawWrapMarkEnd) { + PRectangle rcPlace = rcSegment; + + 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()); + } +} + +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); +} + +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; + + 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); + } + } + } + } + + // 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 EditView::DrawAnnotation(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) { + int indent = static_cast<int>(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<XYPOSITION>(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<int>(vsDraw.spaceWidth * 2); // Margins + } + if (widthAnnotation > lineWidthMaxSeen) + lineWidthMaxSeen = widthAnnotation; + if (vsDraw.annotationVisible == ANNOTATION_BOXED) { + rcSegment.left = static_cast<XYPOSITION>(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<int>(rcSegment.left), static_cast<int>(rcSegment.top)); + surface->LineTo(static_cast<int>(rcSegment.left), static_cast<int>(rcSegment.bottom)); + surface->MoveTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.top)); + surface->LineTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.bottom)); + if (subLine == ll->lines) { + surface->MoveTo(static_cast<int>(rcSegment.left), static_cast<int>(rcSegment.top)); + surface->LineTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.top)); + } + if (subLine == ll->lines + annotationLines - 1) { + surface->MoveTo(static_cast<int>(rcSegment.left), static_cast<int>(rcSegment.bottom - 1)); + surface->LineTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.bottom - 1)); + } + } + } +} + +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) { + + int lineStart = ll->LineStart(subLine); + int posBefore = posCaret; + int posAfter = model.pdoc->MovePositionOutsideChar(posCaret + 1, 1); + int numCharsToDraw = posAfter - posCaret; + + // 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); + } + + // 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; + } + + // 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; + + // 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; + } + + // 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 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; (r<model.sel.Count()) || drawDrag; r++) { + const bool mainCaret = r == model.sel.Main(); + const SelectionPosition posCaret = (drawDrag ? model.posDrag : model.sel.Range(r).caret); + const int offset = posCaret.Position() - posLineStart; + const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].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; + + 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; + + 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<XYPOSITION>(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<XYPOSITION>(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; + } +} + +void EditView::DrawLine(Surface *surface, const ViewStyle &vsDraw, int line, int lineVisible, int xStart, + PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) { + + if (subLine >= ll->lines) { + DrawAnnotation(surface, vsDraw, line, xStart, rcLine, ll, subLine, model); + return; // No further drawing + } + + // 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; + + // See if something overrides the line background color. + const ColourOptional background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret); + + const bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && + (!background.isSet) && (vsDraw.whitespaceColours.back.isSet); + + const XYPOSITION indentWidth = model.pdoc->IndentSize() * vsDraw.spaceWidth; + + const int posLineStart = model.pdoc->LineStart(line); + + 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<XYPOSITION>(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<int>(ll->wrapIndent); + } + + 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<int>(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<XYPOSITION>(subLineStart); + rcSegment.right = ll->positions[ts.end()] + xStart - static_cast<XYPOSITION>(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 { + // 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<XYPOSITION>(subLineStart), + rcSegment.top, + ll->positions[cpos + ts.start + 1] + xStart - static_cast<XYPOSITION>(subLineStart), + rcSegment.bottom); + surface->FillRectangle(rcSpace, vsDraw.whitespaceColours.back); + } + } else { + inIndentation = false; + } + } + } + } + } else if (rcSegment.left > rcLine.right) { + break; + } + } + + 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<int>(vsDraw.theEdge * vsDraw.spaceWidth); + rcSegment.left = static_cast<XYPOSITION>(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); + } + marks >>= 1; + } + + 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); + + 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<XYPOSITION>(subLineStart); + rcSegment.right = ll->positions[ts.end()] + xStart - static_cast<XYPOSITION>(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 (inIndentation && vsDraw.viewIndentationGuides == ivReal) { + for (int indentCount = static_cast<int>((ll->positions[i] + epsilon) / indentWidth); + indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth; + indentCount++) { + if (indentCount > 0) { + int xIndent = static_cast<int>(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<int>(rcSegment.top + vsDraw.lineHeight / 2)); + } + } + } else { + inIndentation = false; + if (vsDraw.controlCharSymbol >= 32) { + char cc[2] = { static_cast<char>(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<XYPOSITION>(subLineStart), + rcSegment.top, + ll->positions[cpos + ts.start + 1] + xStart - static_cast<XYPOSITION>(subLineStart), + rcSegment.bottom); + surface->FillRectangle(rcSpace, textBack); + } + PRectangle rcDot(xmid + xStart - static_cast<XYPOSITION>(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<int>((ll->positions[cpos + ts.start] + epsilon) / indentWidth); + indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth; + indentCount++) { + if (indentCount > 0) { + int xIndent = static_cast<int>(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; + } + } + if ((vsDraw.viewIndentationGuides == ivLookForward || vsDraw.viewIndentationGuides == ivLookBoth) + && (subLine == 0)) { + int indentSpace = model.pdoc->GetLineIndentation(line); + int xStartText = static_cast<int>(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); + } + } + + 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<int>(indentPos * vsDraw.spaceWidth); + if (xIndent < xStartText) { + DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcLine, + (ll->xHighlightGuide == xIndent)); + } + } + } + + DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineRange.end, false, model); + + // 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)); + } + SelectionPosition posStart(posLineStart + lineRange.start); + SelectionPosition posEnd(posLineStart + lineRange.end, virtualSpaces); + SelectionSegment virtualSpaceRange(posStart, posEnd); + for (size_t r = 0; r<model.sel.Count(); r++) { + int alpha = (r == model.sel.Main()) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; + if (alpha != SC_ALPHA_NOALPHA) { + SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange); + if (!portion.Empty()) { + const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; + PRectangle rcSegment = rcLine; + rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - + static_cast<XYPOSITION>(subLineStart)+portion.start.VirtualSpace() * spaceWidth; + rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - + static_cast<XYPOSITION>(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<int>(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); + } + } + } + } + + // 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; + } + } + } +} + +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; + + // Do the painting + if (rcArea.right > vsDraw.textStart - leftTextOverlap) { + + Surface *surface = surfaceWindow; + if (bufferedDraw) { + surface = pixmapLine; + PLATFORM_ASSERT(pixmapLine->Initialised()); + } + surface->SetUnicodeMode(SC_CP_UTF8 == model.pdoc->dbcsCodePage); + surface->SetDBCSMode(model.pdoc->dbcsCodePage); + + Point ptOrigin = model.GetVisibleOriginInMain(); + + int screenLinePaintFirst = static_cast<int>(rcArea.top) / vsDraw.lineHeight; + int ypos = 0; + if (!bufferedDraw) + ypos += screenLinePaintFirst * vsDraw.lineHeight; + int xStart = vsDraw.textStart - model.xOffset + static_cast<int>(ptOrigin.x); + + int visibleLine = model.TopLineOfMain() + screenLinePaintFirst; + int yposScreen = screenLinePaintFirst * vsDraw.lineHeight; + + SelectionPosition posCaret = model.sel.RangeMain().caret; + if (model.posDrag.IsValid()) + posCaret = model.posDrag; + int lineCaret = model.pdoc->LineFromPosition(posCaret.Position()); + + PRectangle rcTextArea = rcClient; + if (vsDraw.marginInside) { + rcTextArea.left += vsDraw.textStart; + rcTextArea.right -= vsDraw.rightMarginWidth; + } else { + rcTextArea = rcArea; + } + + // 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); + } + + // 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) { + + 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; + + // 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); + + if (ll) { + ll->containsCaret = lineDoc == lineCaret; + if (hideSelection) { + ll->containsCaret = false; + } + + ll->hotspot = model.GetHotSpotRange(); + + PRectangle rcLine = rcTextArea; + rcLine.top = static_cast<XYPOSITION>(ypos); + rcLine.bottom = static_cast<XYPOSITION>(ypos + vsDraw.lineHeight); + + 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<char>(model.bracesMatchStyle), + static_cast<int>(model.highlightGuideColumn * vsDraw.spaceWidth), bracesIgnoreStyle); + + if (leftTextOverlap && bufferedDraw) { + PRectangle rcSpacer = rcLine; + rcSpacer.right = rcSpacer.left; + rcSpacer.left -= 1; + surface->FillRectangle(rcSpacer, vsDraw.styles[STYLE_DEFAULT].back); + } + + // Draw the line + DrawLine(surface, vsDraw, lineDoc, visibleLine, xStart, rcLine, ll, subLine, model); + //durPaint += et.Duration(true); + + // 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); + } + } + + DrawCarets(surface, vsDraw, lineDoc, xStart, rcLine, ll, subLine, model); + + if (bufferedDraw) { + Point from = Point::FromInts(vsDraw.textStart - leftTextOverlap, 0); + PRectangle rcCopyArea = PRectangle::FromInts(vsDraw.textStart - leftTextOverlap, yposScreen, + static_cast<int>(rcClient.right - vsDraw.rightMarginWidth), + yposScreen + vsDraw.lineHeight); + surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); + } + + lineWidthMaxSeen = Platform::Maximum( + lineWidthMaxSeen, static_cast<int>(ll->positions[ll->numCharsInLine])); + //durCopy += et.Duration(true); + } + + if (!bufferedDraw) { + ypos += vsDraw.lineHeight; + } + + yposScreen += vsDraw.lineHeight; + visibleLine++; + + //gdk_flush(); + } + ll.Set(0); + //if (durPaint < 0.00000001) + // durPaint = 0.00000001; + + // Right column limit indicator + PRectangle rcBeyondEOF = (vsDraw.marginInside) ? rcClient : rcArea; + rcBeyondEOF.left = static_cast<XYPOSITION>(vsDraw.textStart); + rcBeyondEOF.right = rcBeyondEOF.right - ((vsDraw.marginInside) ? vsDraw.rightMarginWidth : 0); + rcBeyondEOF.top = static_cast<XYPOSITION>((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<int>(vsDraw.theEdge * vsDraw.spaceWidth); + rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart); + rcBeyondEOF.right = rcBeyondEOF.left + 1; + surfaceWindow->FillRectangle(rcBeyondEOF, vsDraw.edgecolour); + } + } + //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()); + } +} + +// 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)); +} + +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(); + + ViewStyle vsPrint(vs); + vsPrint.technology = SC_TECHNOLOGY_DEFAULT; + + // 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; + + // 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); + + // Printing uses different margins, so reset screen margins + vsPrint.leftMarginWidth = 0; + vsPrint.rightMarginWidth = 0; + + 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<int>(surfaceMeasure->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, + "99999" lineNumberPrintSpace, 5 + static_cast<int>(strlen(lineNumberPrintSpace)))); + vsPrint.ms[lineNumberIndex].width = lineNumberWidth; + vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Recalculate fixedColumnWidth + } + + 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); + + // Ensure we are styled to where we are formatting. + model.pdoc->EnsureStyledTo(endPosPrint); + + int xStart = vsPrint.fixedColumnWidth + pfr->rc.left; + int ypos = pfr->rc.top; + + int lineDoc = linePrintStart; + + 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; + + while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) { + + // When printing, the hdc and hdcTarget may be the same, so + // changing the state of surfaceMeasure may change the underlying + // state of surface. Therefore, any cached state is discarded before + // using each surface. + surfaceMeasure->FlushCachedState(); + + // Copy this line and its styles from the document into local arrays + // and determine the x position at which each character starts. + LineLayout ll(model.pdoc->LineStart(lineDoc + 1) - model.pdoc->LineStart(lineDoc) + 1); + LayoutLine(lineDoc, surfaceMeasure, vsPrint, &ll, model, widthPrint); + + ll.containsCaret = false; + + PRectangle rcLine = PRectangle::FromInts( + pfr->rc.left, + ypos, + pfr->rc.right - 1, + ypos + vsPrint.lineHeight); + + // When document line is wrapped over multiple display lines, find where + // to start printing from to ensure a particular position is on the first + // line of the page. + if (visibleLine == 0) { + int startWithinLine = nPrintPos - 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 (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<int>(strlen(number))); + surface->FlushCachedState(); + surface->DrawTextNoClip(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, + static_cast<XYPOSITION>(ypos + vsPrint.maxAscent), number, static_cast<int>(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<XYPOSITION>(ypos); + rcLine.bottom = static_cast<XYPOSITION>(ypos + vsPrint.lineHeight); + DrawLine(surface, vsPrint, lineDoc, visibleLine, xStart, rcLine, &ll, iwl, model); + } + ypos += vsPrint.lineHeight; + } + visibleLine++; + if (iwl == ll.lines - 1) + nPrintPos = model.pdoc->LineStart(lineDoc + 1); + else + nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl); + } + } + + ++lineDoc; + } + + // Clear cache so measurements are not used for screen + posCache.Clear(); + + return nPrintPos; +} diff --git a/src/EditView.h b/src/EditView.h new file mode 100644 index 000000000..f55a6f331 --- /dev/null +++ b/src/EditView.h @@ -0,0 +1,113 @@ +// Scintilla source code edit control +/** @file EditView.h + ** Defines the appearance of the main text area of the editor window. + **/ +// Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef EDITVIEW_H +#define EDITVIEW_H + +#ifdef SCI_NAMESPACE +namespace Scintilla { +#endif + +struct PrintParameters { + int magnification; + int colourMode; + WrapMode wrapState; + PrintParameters(); +}; + +/** +* 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); +}; + +/** +* 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_; + } +}; + +#ifdef SCI_NAMESPACE +} +#endif + +#endif diff --git a/src/Editor.cxx b/src/Editor.cxx index 360540a83..a23683698 100644 --- a/src/Editor.cxx +++ b/src/Editor.cxx @@ -42,6 +42,9 @@ #include "UniConversion.h" #include "Selection.h" #include "PositionCache.h" +#include "EditModel.h" +#include "MarginView.h" +#include "EditView.h" #include "Editor.h" #ifdef SCI_NAMESPACE @@ -79,20 +82,12 @@ static bool IsLastStep(const DocModification &mh) { && (mh.modificationType & SC_MULTILINEUNDOREDO) != 0; } -Caret::Caret() : - active(false), on(false), period(500) {} - Timer::Timer() : ticking(false), ticksToWait(0), tickerID(0) {} Idler::Idler() : state(false), idlerID(0) {} -static inline bool IsControlCharacter(int ch) { - // iscntrl returns true for lots of chars > 127 which are displayable - return ch >= 0 && ch < ' '; -} - static inline bool IsAllSpacesOrTabs(char *s, unsigned int len) { for (unsigned int i = 0; i < len; i++) { // This is safe because IsSpaceOrTab() will return false for null terminators @@ -102,2293 +97,6 @@ static inline bool IsAllSpacesOrTabs(char *s, unsigned int len) { return true; } -EditModel::EditModel() { - inOverstrike = false; - xOffset = 0; - trackLineWidth = false; - posDrag = SelectionPosition(invalidPosition); - braces[0] = invalidPosition; - braces[1] = invalidPosition; - bracesMatchStyle = STYLE_BRACEBAD; - highlightGuideColumn = 0; - primarySelection = true; - foldFlags = 0; - hotspot = Range(invalidPosition); - wrapWidth = LineLayout::wrapWidthInfinite; - pdoc = new Document(); - pdoc->AddRef(); -} - -EditModel::~EditModel() { - pdoc->Release(); - pdoc = 0; -} - -bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) { - if (st.multipleStyles) { - for (size_t iStyle = 0; iStyle<st.length; iStyle++) { - if (!vs.ValidStyle(styleOffset + st.styles[iStyle])) - return false; - } - } else { - if (!vs.ValidStyle(styleOffset + st.style)) - return false; - } - return true; -} - -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<size_t>(styles[endSegment + 1]) == style)) - endSegment++; - FontAlias fontText = vs.styles[style + styleOffset].font; - width += static_cast<int>(surface->WidthText(fontText, text + start, - static_cast<int>(endSegment - start + 1))); - start = endSegment + 1; - } - return width; -} - -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<int>(surface->WidthText(fontText, - st.text + start, static_cast<int>(lenLine))); - } - if (widthSubLine > widthMax) - widthMax = widthSubLine; - start += lenLine + 1; - } - return widthMax; -} - -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<int>(length), - style.fore, style.back); -} - -static void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText, - const StyledText &st, size_t start, size_t length) { - - if (st.multipleStyles) { - int x = static_cast<int>(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<int>(surface->WidthText(fontText, - st.text + start + i, static_cast<int>(end - i + 1))); - PRectangle rcSegment = rcText; - rcSegment.left = static_cast<XYPOSITION>(x); - rcSegment.right = static_cast<XYPOSITION>(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; - } - } else { - const size_t style = st.style + styleOffset; - DrawTextInStyle(surface, rcText, vs.styles[style], rcText.top + vs.maxAscent, - st.text + start, length); - } -} - -static void DrawWrapMarker(Surface *surface, PRectangle rcPlace, - bool isEndMarker, ColourDesired wrapColour) { - surface->PenColour(wrapColour); - - enum { xa = 1 }; // gap before start - int w = static_cast<int>(rcPlace.right - rcPlace.left) - xa - 1; - - bool xStraight = isEndMarker; // x-mirrored symbol for start marker - - int x0 = static_cast<int>(xStraight ? rcPlace.left : rcPlace.right - 1); - int y0 = static_cast<int>(rcPlace.top); - - int dy = static_cast<int>(rcPlace.bottom - rcPlace.top) / 5; - int y = static_cast<int>(rcPlace.bottom - rcPlace.top) / 2 + dy; - - struct Relative { - Surface *surface; - int xBase; - int xDir; - int yBase; - int yDir; - void MoveTo(int xRelative, int yRelative) { - surface->MoveTo(xBase + xDir * xRelative, yBase + yDir * yRelative); - } - void LineTo(int xRelative, int yRelative) { - surface->LineTo(xBase + xDir * xRelative, yBase + yDir * yRelative); - } - }; - Relative rel = { surface, x0, xStraight ? 1 : -1, y0, 1 }; - - // arrow head - rel.MoveTo(xa, y); - rel.LineTo(xa + 2 * w / 3, y - dy); - rel.MoveTo(xa, y); - rel.LineTo(xa + 2 * w / 3, y + dy); - - // arrow body - rel.MoveTo(xa, y); - rel.LineTo(xa + w, y); - rel.LineTo(xa + w, y - 2 * dy); - rel.LineTo(xa - 1, // on windows lineto is exclusive endpoint, perhaps GTK not... - y - 2 * dy); -} - -MarginView::MarginView() { - pixmapSelMargin = 0; - pixmapSelPattern = 0; - pixmapSelPatternOffset1 = 0; -} - -void MarginView::DropGraphics(bool freeObjects) { - if (freeObjects) { - delete pixmapSelMargin; - pixmapSelMargin = 0; - delete pixmapSelPattern; - pixmapSelPattern = 0; - delete pixmapSelPatternOffset1; - pixmapSelPatternOffset1 = 0; - } else { - if (pixmapSelMargin) - pixmapSelMargin->Release(); - if (pixmapSelPattern) - pixmapSelPattern->Release(); - if (pixmapSelPatternOffset1) - pixmapSelPatternOffset1->Release(); - } -} - -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); -} - -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); - - // Initialize default colours based on the chrome colour scheme. Typically the highlight is white. - ColourDesired colourFMFill = vsDraw.selbar; - ColourDesired colourFMStripes = vsDraw.selbarlight; - - 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); - } - } - } -} - -static int SubstituteMarkerIfEmpty(int markerCheck, int markerDefault, const ViewStyle &vs) { - if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) - return markerDefault; - return markerCheck; -} - -void MarginView::PaintMargin(Surface *surface, int topLine, PRectangle rc, PRectangle rcMargin, - const EditModel &model, const ViewStyle &vs) { - - PRectangle rcSelMargin = rcMargin; - rcSelMargin.right = rcMargin.left; - if (rcSelMargin.bottom < rc.bottom) - rcSelMargin.bottom = rc.bottom; - - 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) { - - rcSelMargin.left = rcSelMargin.right; - rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; - - 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<int>(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); - } - - const int lineStartPaint = static_cast<int>(rcMargin.top + ptOrigin.y) / vs.lineHeight; - int visibleLine = model.TopLineOfMain() + lineStartPaint; - int yposScreen = lineStartPaint * vs.lineHeight - static_cast<int>(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<XYPOSITION>(yposScreen); - rcMarker.bottom = static_cast<XYPOSITION>(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<int>(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; - } - } - } - - PRectangle rcBlankMargin = rcMargin; - rcBlankMargin.left = rcSelMargin.right; - surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back); -} - -/** -* 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_; - } -}; - -PrintParameters::PrintParameters() { - magnification = 0; - colourMode = SC_PRINT_NORMAL; - wrapState = eWrapWord; -} - -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); -} - -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(); - } -} - -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); -} - -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 DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { - int ydiff = static_cast<int>(rcTab.bottom - rcTab.top) / 2; - int xhead = static_cast<int>(rcTab.right) - 1 - ydiff; - if (xhead <= rcTab.left) { - ydiff -= static_cast<int>(rcTab.left) - xhead - 1; - xhead = static_cast<int>(rcTab.left) - 1; - } - if ((rcTab.left + 2) < (rcTab.right - 1)) - surface->MoveTo(static_cast<int>(rcTab.left) + 2, ymid); - else - surface->MoveTo(static_cast<int>(rcTab.right) - 1, ymid); - surface->LineTo(static_cast<int>(rcTab.right) - 1, ymid); - surface->LineTo(xhead, ymid - ydiff); - surface->MoveTo(static_cast<int>(rcTab.right) - 1, ymid); - surface->LineTo(xhead, ymid + ydiff); -} - -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); - } - } -} - -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()); -} - -/** -* 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; - - 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<unsigned char>(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<char>(tolower(chDoc))); - else // Style::caseUpper - allSame = allSame && - (ll->chars[numCharsInLine] == static_cast<char>(toupper(chDoc))); - numCharsInLine++; - } - allSame = allSame && (ll->styles[numCharsInLine] == styleByte); // For eolFilled - if (allSame) { - ll->validity = LineLayout::llPositions; - } else { - ll->validity = LineLayout::llInvalid; - } - } else { - ll->validity = LineLayout::llInvalid; - } - } - if (ll->validity == LineLayout::llInvalid) { - ll->widthLine = LineLayout::wrapWidthInfinite; - ll->lines = 1; - if (vstyle.edgeState == EDGE_BACKGROUND) { - ll->edgeColumn = model.pdoc->FindColumn(line, vstyle.theEdge); - if (ll->edgeColumn >= posLineStart) { - ll->edgeColumn -= posLineStart; - } - } else { - ll->edgeColumn = -1; - } - - // 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; charInLine<lineLength; charInLine++) { - char chDoc = ll->chars[charInLine]; - if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseUpper) - ll->chars[charInLine] = static_cast<char>(toupper(chDoc)); - else if (vstyle.styles[ll->styles[charInLine]].caseForce == Style::caseLower) - ll->chars[charInLine] = static_cast<char>(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 - - // 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; - - BreakFinder bfLayout(ll, NULL, Range(0, numCharsInLine), posLineStart, 0, false, model.pdoc, &model.reprs); - while (bfLayout.More()) { - - const TextSegment ts = bfLayout.Next(); - - 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<int>((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<unsigned int>(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); - } - - 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<int>(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<int>(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; - } -} - -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; -} - -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<int>(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<int>( - (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); -} - -/** -* 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)); - } - const XYPOSITION spaceWidth = vs.styles[ll->EndLineStyle()].spaceWidth; - const int spaceOffset = static_cast<int>( - (x + subLineStart - ll->positions[rangeSubLine.end] + spaceWidth / 2) / spaceWidth); - return SelectionPosition(rangeSubLine.end + posLineStart, spaceOffset); - } - return SelectionPosition(0); -} - -int EditView::DisplayFromPosition(Surface *surface, int pos, const EditModel &model, const ViewStyle &vs) { - int lineDoc = model.pdoc->LineFromPosition(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++; - } - } - } - return lineDisplay; -} - -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; - } - } - } - } - } - return posRet; -} - -static ColourDesired SelectionBackground(const ViewStyle &vsDraw, bool main, bool primarySelection) { - return main ? - (primarySelection ? vsDraw.selColours.back : vsDraw.selBackground2) : - vsDraw.selAdditionalBackground; -} - -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 { - 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; - } -} - -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<int>(rcSegment.top), start + 2, static_cast<int>(rcSegment.bottom)); - surface->Copy(rcCopyArea, from, - highlight ? *pixmapIndentGuideHighlight : *pixmapIndentGuide); -} - -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); - } -} - -void DrawTextBlob(Surface *surface, const ViewStyle &vsDraw, PRectangle rcSegment, - const char *s, ColourDesired textBack, ColourDesired textFore, bool twoPhaseDraw) { - if (!twoPhaseDraw) { - surface->FillRectangle(rcSegment, textBack); - } - FontAlias ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; - int normalCharHeight = static_cast<int>(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<int>(s ? strlen(s) : 0), - textBack, textFore); -} - -void EditView::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) { - - const int posLineStart = model.pdoc->LineStart(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<XYPOSITION>(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; r<model.sel.Count(); r++) { - int alpha = (r == model.sel.Main()) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; - if (alpha == SC_ALPHA_NOALPHA) { - SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange); - if (!portion.Empty()) { - const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; - rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - - static_cast<XYPOSITION>(subLineStart)+portion.start.VirtualSpace() * spaceWidth; - rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - - static_cast<XYPOSITION>(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)); - } - } - } - } - } - - 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; - } - - // Draw the [CR], [LF], or [CR][LF] blobs if visible line ends are on - XYPOSITION blobsWidth = 0; - if (lastSubLine) { - for (int eolPos = ll->numCharsBeforeEOL; eolPos<ll->numCharsInLine; eolPos++) { - rcSegment.left = xStart + ll->positions[eolPos] - static_cast<XYPOSITION>(subLineStart)+virtualSpace; - rcSegment.right = xStart + ll->positions[eolPos + 1] - static_cast<XYPOSITION>(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; - } - } - 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); - } - } - } - - // Draw the eol-is-selected rectangle - rcSegment.left = xEol + xStart + virtualSpace + blobsWidth; - rcSegment.right = rcSegment.left + vsDraw.aveCharWidth; - - 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 { - 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); - } - } - - // 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 { - 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); - } - } - - bool drawWrapMarkEnd = false; - - if (vsDraw.wrapVisualFlags & SC_WRAPVISUALFLAG_END) { - if (subLine + 1 < ll->lines) { - drawWrapMarkEnd = ll->LineStart(subLine + 1) != 0; - } - } - - if (drawWrapMarkEnd) { - PRectangle rcPlace = rcSegment; - - 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()); - } -} - -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); -} - -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; - - 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); - } - } - } - } - - // 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 EditView::DrawAnnotation(Surface *surface, const ViewStyle &vsDraw, int line, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) { - int indent = static_cast<int>(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<XYPOSITION>(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<int>(vsDraw.spaceWidth * 2); // Margins - } - if (widthAnnotation > lineWidthMaxSeen) - lineWidthMaxSeen = widthAnnotation; - if (vsDraw.annotationVisible == ANNOTATION_BOXED) { - rcSegment.left = static_cast<XYPOSITION>(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<int>(rcSegment.left), static_cast<int>(rcSegment.top)); - surface->LineTo(static_cast<int>(rcSegment.left), static_cast<int>(rcSegment.bottom)); - surface->MoveTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.top)); - surface->LineTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.bottom)); - if (subLine == ll->lines) { - surface->MoveTo(static_cast<int>(rcSegment.left), static_cast<int>(rcSegment.top)); - surface->LineTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.top)); - } - if (subLine == ll->lines + annotationLines - 1) { - surface->MoveTo(static_cast<int>(rcSegment.left), static_cast<int>(rcSegment.bottom - 1)); - surface->LineTo(static_cast<int>(rcSegment.right), static_cast<int>(rcSegment.bottom - 1)); - } - } - } -} - -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) { - - int lineStart = ll->LineStart(subLine); - int posBefore = posCaret; - int posAfter = model.pdoc->MovePositionOutsideChar(posCaret + 1, 1); - int numCharsToDraw = posAfter - posCaret; - - // 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); - } - - // 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; - } - - // 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; - - // 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; - } - - // 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 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; (r<model.sel.Count()) || drawDrag; r++) { - const bool mainCaret = r == model.sel.Main(); - const SelectionPosition posCaret = (drawDrag ? model.posDrag : model.sel.Range(r).caret); - const int offset = posCaret.Position() - posLineStart; - const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].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; - - 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; - - 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<XYPOSITION>(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<XYPOSITION>(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; - } -} - -void EditView::DrawLine(Surface *surface, const ViewStyle &vsDraw, int line, int lineVisible, int xStart, - PRectangle rcLine, LineLayout *ll, int subLine, const EditModel &model) { - - if (subLine >= ll->lines) { - DrawAnnotation(surface, vsDraw, line, xStart, rcLine, ll, subLine, model); - return; // No further drawing - } - - // 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; - - // See if something overrides the line background color. - const ColourOptional background = vsDraw.Background(model.pdoc->GetMark(line), model.caret.active, ll->containsCaret); - - const bool drawWhitespaceBackground = (vsDraw.viewWhitespace != wsInvisible) && - (!background.isSet) && (vsDraw.whitespaceColours.back.isSet); - - const XYPOSITION indentWidth = model.pdoc->IndentSize() * vsDraw.spaceWidth; - - const int posLineStart = model.pdoc->LineStart(line); - - 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<XYPOSITION>(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<int>(ll->wrapIndent); - } - - 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<int>(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<XYPOSITION>(subLineStart); - rcSegment.right = ll->positions[ts.end()] + xStart - static_cast<XYPOSITION>(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 { - // 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<XYPOSITION>(subLineStart), - rcSegment.top, - ll->positions[cpos + ts.start + 1] + xStart - static_cast<XYPOSITION>(subLineStart), - rcSegment.bottom); - surface->FillRectangle(rcSpace, vsDraw.whitespaceColours.back); - } - } else { - inIndentation = false; - } - } - } - } - } else if (rcSegment.left > rcLine.right) { - break; - } - } - - 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<int>(vsDraw.theEdge * vsDraw.spaceWidth); - rcSegment.left = static_cast<XYPOSITION>(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); - } - marks >>= 1; - } - - 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); - - 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<XYPOSITION>(subLineStart); - rcSegment.right = ll->positions[ts.end()] + xStart - static_cast<XYPOSITION>(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 (inIndentation && vsDraw.viewIndentationGuides == ivReal) { - for (int indentCount = static_cast<int>((ll->positions[i] + epsilon) / indentWidth); - indentCount <= (ll->positions[i + 1] - epsilon) / indentWidth; - indentCount++) { - if (indentCount > 0) { - int xIndent = static_cast<int>(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<int>(rcSegment.top + vsDraw.lineHeight / 2)); - } - } - } else { - inIndentation = false; - if (vsDraw.controlCharSymbol >= 32) { - char cc[2] = { static_cast<char>(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<XYPOSITION>(subLineStart), - rcSegment.top, - ll->positions[cpos + ts.start + 1] + xStart - static_cast<XYPOSITION>(subLineStart), - rcSegment.bottom); - surface->FillRectangle(rcSpace, textBack); - } - PRectangle rcDot(xmid + xStart - static_cast<XYPOSITION>(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<int>((ll->positions[cpos + ts.start] + epsilon) / indentWidth); - indentCount <= (ll->positions[cpos + ts.start + 1] - epsilon) / indentWidth; - indentCount++) { - if (indentCount > 0) { - int xIndent = static_cast<int>(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; - } - } - if ((vsDraw.viewIndentationGuides == ivLookForward || vsDraw.viewIndentationGuides == ivLookBoth) - && (subLine == 0)) { - int indentSpace = model.pdoc->GetLineIndentation(line); - int xStartText = static_cast<int>(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); - } - } - - 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<int>(indentPos * vsDraw.spaceWidth); - if (xIndent < xStartText) { - DrawIndentGuide(surface, lineVisible, vsDraw.lineHeight, xIndent + xStart, rcLine, - (ll->xHighlightGuide == xIndent)); - } - } - } - - DrawIndicators(surface, vsDraw, line, xStart, rcLine, ll, subLine, lineRange.end, false, model); - - // 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)); - } - SelectionPosition posStart(posLineStart + lineRange.start); - SelectionPosition posEnd(posLineStart + lineRange.end, virtualSpaces); - SelectionSegment virtualSpaceRange(posStart, posEnd); - for (size_t r = 0; r<model.sel.Count(); r++) { - int alpha = (r == model.sel.Main()) ? vsDraw.selAlpha : vsDraw.selAdditionalAlpha; - if (alpha != SC_ALPHA_NOALPHA) { - SelectionSegment portion = model.sel.Range(r).Intersect(virtualSpaceRange); - if (!portion.Empty()) { - const XYPOSITION spaceWidth = vsDraw.styles[ll->EndLineStyle()].spaceWidth; - PRectangle rcSegment = rcLine; - rcSegment.left = xStart + ll->positions[portion.start.Position() - posLineStart] - - static_cast<XYPOSITION>(subLineStart)+portion.start.VirtualSpace() * spaceWidth; - rcSegment.right = xStart + ll->positions[portion.end.Position() - posLineStart] - - static_cast<XYPOSITION>(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<int>(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); - } - } - } - } - - // 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; - } - } - } -} - -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; - - // Do the painting - if (rcArea.right > vsDraw.textStart - leftTextOverlap) { - - Surface *surface = surfaceWindow; - if (bufferedDraw) { - surface = pixmapLine; - PLATFORM_ASSERT(pixmapLine->Initialised()); - } - surface->SetUnicodeMode(SC_CP_UTF8 == model.pdoc->dbcsCodePage); - surface->SetDBCSMode(model.pdoc->dbcsCodePage); - - Point ptOrigin = model.GetVisibleOriginInMain(); - - int screenLinePaintFirst = static_cast<int>(rcArea.top) / vsDraw.lineHeight; - int ypos = 0; - if (!bufferedDraw) - ypos += screenLinePaintFirst * vsDraw.lineHeight; - int xStart = vsDraw.textStart - model.xOffset + static_cast<int>(ptOrigin.x); - - int visibleLine = model.TopLineOfMain() + screenLinePaintFirst; - int yposScreen = screenLinePaintFirst * vsDraw.lineHeight; - - SelectionPosition posCaret = model.sel.RangeMain().caret; - if (model.posDrag.IsValid()) - posCaret = model.posDrag; - int lineCaret = model.pdoc->LineFromPosition(posCaret.Position()); - - PRectangle rcTextArea = rcClient; - if (vsDraw.marginInside) { - rcTextArea.left += vsDraw.textStart; - rcTextArea.right -= vsDraw.rightMarginWidth; - } else { - rcTextArea = rcArea; - } - - // 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); - } - - // 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) { - - 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; - - // 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); - - if (ll) { - ll->containsCaret = lineDoc == lineCaret; - if (hideSelection) { - ll->containsCaret = false; - } - - ll->hotspot = model.GetHotSpotRange(); - - PRectangle rcLine = rcTextArea; - rcLine.top = static_cast<XYPOSITION>(ypos); - rcLine.bottom = static_cast<XYPOSITION>(ypos + vsDraw.lineHeight); - - 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<char>(model.bracesMatchStyle), - static_cast<int>(model.highlightGuideColumn * vsDraw.spaceWidth), bracesIgnoreStyle); - - if (leftTextOverlap && bufferedDraw) { - PRectangle rcSpacer = rcLine; - rcSpacer.right = rcSpacer.left; - rcSpacer.left -= 1; - surface->FillRectangle(rcSpacer, vsDraw.styles[STYLE_DEFAULT].back); - } - - // Draw the line - DrawLine(surface, vsDraw, lineDoc, visibleLine, xStart, rcLine, ll, subLine, model); - //durPaint += et.Duration(true); - - // 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); - } - } - - DrawCarets(surface, vsDraw, lineDoc, xStart, rcLine, ll, subLine, model); - - if (bufferedDraw) { - Point from = Point::FromInts(vsDraw.textStart - leftTextOverlap, 0); - PRectangle rcCopyArea = PRectangle::FromInts(vsDraw.textStart - leftTextOverlap, yposScreen, - static_cast<int>(rcClient.right - vsDraw.rightMarginWidth), - yposScreen + vsDraw.lineHeight); - surfaceWindow->Copy(rcCopyArea, from, *pixmapLine); - } - - lineWidthMaxSeen = Platform::Maximum( - lineWidthMaxSeen, static_cast<int>(ll->positions[ll->numCharsInLine])); - //durCopy += et.Duration(true); - } - - if (!bufferedDraw) { - ypos += vsDraw.lineHeight; - } - - yposScreen += vsDraw.lineHeight; - visibleLine++; - - //gdk_flush(); - } - ll.Set(0); - //if (durPaint < 0.00000001) - // durPaint = 0.00000001; - - // Right column limit indicator - PRectangle rcBeyondEOF = (vsDraw.marginInside) ? rcClient : rcArea; - rcBeyondEOF.left = static_cast<XYPOSITION>(vsDraw.textStart); - rcBeyondEOF.right = rcBeyondEOF.right - ((vsDraw.marginInside) ? vsDraw.rightMarginWidth : 0); - rcBeyondEOF.top = static_cast<XYPOSITION>((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<int>(vsDraw.theEdge * vsDraw.spaceWidth); - rcBeyondEOF.left = static_cast<XYPOSITION>(edgeX + xStart); - rcBeyondEOF.right = rcBeyondEOF.left + 1; - surfaceWindow->FillRectangle(rcBeyondEOF, vsDraw.edgecolour); - } - } - //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()); - } -} - -// 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)); -} - -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(); - - ViewStyle vsPrint(vs); - vsPrint.technology = SC_TECHNOLOGY_DEFAULT; - - // 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; - - // 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); - - // Printing uses different margins, so reset screen margins - vsPrint.leftMarginWidth = 0; - vsPrint.rightMarginWidth = 0; - - 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<int>(surfaceMeasure->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, - "99999" lineNumberPrintSpace, 5 + static_cast<int>(strlen(lineNumberPrintSpace)))); - vsPrint.ms[lineNumberIndex].width = lineNumberWidth; - vsPrint.Refresh(*surfaceMeasure, model.pdoc->tabInChars); // Recalculate fixedColumnWidth - } - - 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); - - // Ensure we are styled to where we are formatting. - model.pdoc->EnsureStyledTo(endPosPrint); - - int xStart = vsPrint.fixedColumnWidth + pfr->rc.left; - int ypos = pfr->rc.top; - - int lineDoc = linePrintStart; - - 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; - - while (lineDoc <= linePrintLast && ypos < pfr->rc.bottom) { - - // When printing, the hdc and hdcTarget may be the same, so - // changing the state of surfaceMeasure may change the underlying - // state of surface. Therefore, any cached state is discarded before - // using each surface. - surfaceMeasure->FlushCachedState(); - - // Copy this line and its styles from the document into local arrays - // and determine the x position at which each character starts. - LineLayout ll(model.pdoc->LineStart(lineDoc + 1) - model.pdoc->LineStart(lineDoc) + 1); - LayoutLine(lineDoc, surfaceMeasure, vsPrint, &ll, model, widthPrint); - - ll.containsCaret = false; - - PRectangle rcLine = PRectangle::FromInts( - pfr->rc.left, - ypos, - pfr->rc.right - 1, - ypos + vsPrint.lineHeight); - - // When document line is wrapped over multiple display lines, find where - // to start printing from to ensure a particular position is on the first - // line of the page. - if (visibleLine == 0) { - int startWithinLine = nPrintPos - 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 (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<int>(strlen(number))); - surface->FlushCachedState(); - surface->DrawTextNoClip(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, - static_cast<XYPOSITION>(ypos + vsPrint.maxAscent), number, static_cast<int>(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<XYPOSITION>(ypos); - rcLine.bottom = static_cast<XYPOSITION>(ypos + vsPrint.lineHeight); - DrawLine(surface, vsPrint, lineDoc, visibleLine, xStart, rcLine, &ll, iwl, model); - } - ypos += vsPrint.lineHeight; - } - visibleLine++; - if (iwl == ll.lines - 1) - nPrintPos = model.pdoc->LineStart(lineDoc + 1); - else - nPrintPos += ll.LineStart(iwl + 1) - ll.LineStart(iwl); - } - } - - ++lineDoc; - } - - // Clear cache so measurements are not used for screen - posCache.Clear(); - - return nPrintPos; -} - Editor::Editor() { ctrlID = 0; diff --git a/src/Editor.h b/src/Editor.h index fd368df39..53da90906 100644 --- a/src/Editor.h +++ b/src/Editor.h @@ -14,17 +14,6 @@ namespace Scintilla { /** */ -class Caret { -public: - bool active; - bool on; - int period; - - Caret(); -}; - -/** - */ class Timer { public: bool ticking; @@ -160,136 +149,6 @@ struct WrapPending { } }; -struct PrintParameters { - int magnification; - int colourMode; - WrapMode wrapState; - 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 EditModel, public DocWatcher { diff --git a/src/MarginView.cxx b/src/MarginView.cxx new file mode 100644 index 000000000..efe84ecb5 --- /dev/null +++ b/src/MarginView.cxx @@ -0,0 +1,532 @@ +// Scintilla source code edit control +/** @file MarginView.cxx + ** Defines the appearance of the editor margin. + **/ +// Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <math.h> +#include <assert.h> +#include <ctype.h> + +#include <string> +#include <vector> +#include <map> +#include <algorithm> +#include <memory> + +#include "Platform.h" + +#include "ILexer.h" +#include "Scintilla.h" + +#include "StringCopy.h" +#include "SplitVector.h" +#include "Partitioning.h" +#include "RunStyles.h" +#include "ContractionState.h" +#include "CellBuffer.h" +#include "KeyMap.h" +#include "Indicator.h" +#include "XPM.h" +#include "LineMarker.h" +#include "Style.h" +#include "ViewStyle.h" +#include "CharClassify.h" +#include "Decoration.h" +#include "CaseFolder.h" +#include "Document.h" +#include "UniConversion.h" +#include "Selection.h" +#include "PositionCache.h" +#include "EditModel.h" +#include "MarginView.h" +#include "EditView.h" + +#ifdef SCI_NAMESPACE +using namespace Scintilla; +#endif + +bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st) { + if (st.multipleStyles) { + for (size_t iStyle = 0; iStyle<st.length; iStyle++) { + if (!vs.ValidStyle(styleOffset + st.styles[iStyle])) + return false; + } + } else { + if (!vs.ValidStyle(styleOffset + st.style)) + return false; + } + return true; +} + +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<size_t>(styles[endSegment + 1]) == style)) + endSegment++; + FontAlias fontText = vs.styles[style + styleOffset].font; + width += static_cast<int>(surface->WidthText(fontText, text + start, + static_cast<int>(endSegment - start + 1))); + start = endSegment + 1; + } + return width; +} + +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<int>(surface->WidthText(fontText, + st.text + start, static_cast<int>(lenLine))); + } + if (widthSubLine > widthMax) + widthMax = widthSubLine; + start += lenLine + 1; + } + return widthMax; +} + +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<int>(length), + style.fore, style.back); +} + +void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText, + const StyledText &st, size_t start, size_t length) { + + if (st.multipleStyles) { + int x = static_cast<int>(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<int>(surface->WidthText(fontText, + st.text + start + i, static_cast<int>(end - i + 1))); + PRectangle rcSegment = rcText; + rcSegment.left = static_cast<XYPOSITION>(x); + rcSegment.right = static_cast<XYPOSITION>(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; + } + } else { + const size_t style = st.style + styleOffset; + DrawTextInStyle(surface, rcText, vs.styles[style], rcText.top + vs.maxAscent, + st.text + start, length); + } +} + +void DrawWrapMarker(Surface *surface, PRectangle rcPlace, + bool isEndMarker, ColourDesired wrapColour) { + surface->PenColour(wrapColour); + + enum { xa = 1 }; // gap before start + int w = static_cast<int>(rcPlace.right - rcPlace.left) - xa - 1; + + bool xStraight = isEndMarker; // x-mirrored symbol for start marker + + int x0 = static_cast<int>(xStraight ? rcPlace.left : rcPlace.right - 1); + int y0 = static_cast<int>(rcPlace.top); + + int dy = static_cast<int>(rcPlace.bottom - rcPlace.top) / 5; + int y = static_cast<int>(rcPlace.bottom - rcPlace.top) / 2 + dy; + + struct Relative { + Surface *surface; + int xBase; + int xDir; + int yBase; + int yDir; + void MoveTo(int xRelative, int yRelative) { + surface->MoveTo(xBase + xDir * xRelative, yBase + yDir * yRelative); + } + void LineTo(int xRelative, int yRelative) { + surface->LineTo(xBase + xDir * xRelative, yBase + yDir * yRelative); + } + }; + Relative rel = { surface, x0, xStraight ? 1 : -1, y0, 1 }; + + // arrow head + rel.MoveTo(xa, y); + rel.LineTo(xa + 2 * w / 3, y - dy); + rel.MoveTo(xa, y); + rel.LineTo(xa + 2 * w / 3, y + dy); + + // arrow body + rel.MoveTo(xa, y); + rel.LineTo(xa + w, y); + rel.LineTo(xa + w, y - 2 * dy); + rel.LineTo(xa - 1, // on windows lineto is exclusive endpoint, perhaps GTK not... + y - 2 * dy); +} + +MarginView::MarginView() { + pixmapSelMargin = 0; + pixmapSelPattern = 0; + pixmapSelPatternOffset1 = 0; +} + +void MarginView::DropGraphics(bool freeObjects) { + if (freeObjects) { + delete pixmapSelMargin; + pixmapSelMargin = 0; + delete pixmapSelPattern; + pixmapSelPattern = 0; + delete pixmapSelPatternOffset1; + pixmapSelPatternOffset1 = 0; + } else { + if (pixmapSelMargin) + pixmapSelMargin->Release(); + if (pixmapSelPattern) + pixmapSelPattern->Release(); + if (pixmapSelPatternOffset1) + pixmapSelPatternOffset1->Release(); + } +} + +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); +} + +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); + + // Initialize default colours based on the chrome colour scheme. Typically the highlight is white. + ColourDesired colourFMFill = vsDraw.selbar; + ColourDesired colourFMStripes = vsDraw.selbarlight; + + 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); + } + } + } +} + +static int SubstituteMarkerIfEmpty(int markerCheck, int markerDefault, const ViewStyle &vs) { + if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) + return markerDefault; + return markerCheck; +} + +void MarginView::PaintMargin(Surface *surface, int topLine, PRectangle rc, PRectangle rcMargin, + const EditModel &model, const ViewStyle &vs) { + + PRectangle rcSelMargin = rcMargin; + rcSelMargin.right = rcMargin.left; + if (rcSelMargin.bottom < rc.bottom) + rcSelMargin.bottom = rc.bottom; + + 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) { + + rcSelMargin.left = rcSelMargin.right; + rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; + + 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<int>(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); + } + + const int lineStartPaint = static_cast<int>(rcMargin.top + ptOrigin.y) / vs.lineHeight; + int visibleLine = model.TopLineOfMain() + lineStartPaint; + int yposScreen = lineStartPaint * vs.lineHeight - static_cast<int>(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<XYPOSITION>(yposScreen); + rcMarker.bottom = static_cast<XYPOSITION>(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<int>(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; + } + } + } + + PRectangle rcBlankMargin = rcMargin; + rcBlankMargin.left = rcSelMargin.right; + surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back); +} diff --git a/src/MarginView.h b/src/MarginView.h new file mode 100644 index 000000000..8c401ae4a --- /dev/null +++ b/src/MarginView.h @@ -0,0 +1,47 @@ +// Scintilla source code edit control +/** @file MarginView.h + ** Defines the appearance of the editor margin. + **/ +// Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#ifndef MARGINVIEW_H +#define MARGINVIEW_H + +#ifdef SCI_NAMESPACE +namespace Scintilla { +#endif + +bool ValidStyledText(const ViewStyle &vs, size_t styleOffset, const StyledText &st); + +void DrawStyledText(Surface *surface, const ViewStyle &vs, int styleOffset, PRectangle rcText, + const StyledText &st, size_t start, size_t length); +int WidestLineWidth(Surface *surface, const ViewStyle &vs, int styleOffset, const StyledText &st); + +void DrawWrapMarker(Surface *surface, PRectangle rcPlace, bool isEndMarker, ColourDesired wrapColour); + +/** +* MarginView draws the margins. +*/ +class MarginView { +public: + Surface *pixmapSelMargin; + Surface *pixmapSelPattern; + Surface *pixmapSelPatternOffset1; + // Highlight current folding block + HighlightDelimiter highlightDelimiter; + + 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); +}; + +#ifdef SCI_NAMESPACE +} +#endif + +#endif diff --git a/src/ScintillaBase.cxx b/src/ScintillaBase.cxx index d42dfc7ff..48ec46808 100644 --- a/src/ScintillaBase.cxx +++ b/src/ScintillaBase.cxx @@ -50,6 +50,9 @@ #include "Document.h" #include "Selection.h" #include "PositionCache.h" +#include "EditModel.h" +#include "MarginView.h" +#include "EditView.h" #include "Editor.h" #include "AutoComplete.h" #include "ScintillaBase.h" |