diff options
Diffstat (limited to 'src/MarginView.cxx')
-rw-r--r-- | src/MarginView.cxx | 532 |
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); +} |