aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/MarginView.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'src/MarginView.cxx')
-rw-r--r--src/MarginView.cxx532
1 files changed, 532 insertions, 0 deletions
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);
+}