Merge pull request #2212 from TwlyY29/bibtex-parser
[geany-mirror.git] / scintilla / src / MarginView.cxx
bloba2fea70a7cce3fa902e6c4e92874f13be246e159
1 // Scintilla source code edit control
2 /** @file MarginView.cxx
3 ** Defines the appearance of the editor margin.
4 **/
5 // Copyright 1998-2014 by Neil Hodgson <neilh@scintilla.org>
6 // The License.txt file describes the conditions under which this software may be distributed.
8 #include <cstddef>
9 #include <cstdlib>
10 #include <cassert>
11 #include <cstring>
12 #include <cstdio>
13 #include <cmath>
15 #include <stdexcept>
16 #include <string>
17 #include <vector>
18 #include <map>
19 #include <algorithm>
20 #include <memory>
22 #include "Platform.h"
24 #include "ILoader.h"
25 #include "ILexer.h"
26 #include "Scintilla.h"
28 #include "CharacterCategory.h"
29 #include "Position.h"
30 #include "IntegerRectangle.h"
31 #include "UniqueString.h"
32 #include "SplitVector.h"
33 #include "Partitioning.h"
34 #include "RunStyles.h"
35 #include "ContractionState.h"
36 #include "CellBuffer.h"
37 #include "KeyMap.h"
38 #include "Indicator.h"
39 #include "LineMarker.h"
40 #include "Style.h"
41 #include "ViewStyle.h"
42 #include "CharClassify.h"
43 #include "Decoration.h"
44 #include "CaseFolder.h"
45 #include "Document.h"
46 #include "UniConversion.h"
47 #include "Selection.h"
48 #include "PositionCache.h"
49 #include "EditModel.h"
50 #include "MarginView.h"
51 #include "EditView.h"
53 using namespace Scintilla;
55 namespace Scintilla {
57 void DrawWrapMarker(Surface *surface, PRectangle rcPlace,
58 bool isEndMarker, ColourDesired wrapColour) {
59 surface->PenColour(wrapColour);
61 const IntegerRectangle ircPlace(rcPlace);
63 enum { xa = 1 }; // gap before start
64 const int w = ircPlace.Width() - xa - 1;
66 const bool xStraight = isEndMarker; // x-mirrored symbol for start marker
68 const int x0 = xStraight ? ircPlace.left : ircPlace.right - 1;
69 const int y0 = ircPlace.top;
71 const int dy = ircPlace.Height() / 5;
72 const int y = ircPlace.Height() / 2 + dy;
74 struct Relative {
75 Surface *surface;
76 int xBase;
77 int xDir;
78 int yBase;
79 int yDir;
80 void MoveTo(int xRelative, int yRelative) {
81 surface->MoveTo(xBase + xDir * xRelative, yBase + yDir * yRelative);
83 void LineTo(int xRelative, int yRelative) {
84 surface->LineTo(xBase + xDir * xRelative, yBase + yDir * yRelative);
87 Relative rel = { surface, x0, xStraight ? 1 : -1, y0, 1 };
89 // arrow head
90 rel.MoveTo(xa, y);
91 rel.LineTo(xa + 2 * w / 3, y - dy);
92 rel.MoveTo(xa, y);
93 rel.LineTo(xa + 2 * w / 3, y + dy);
95 // arrow body
96 rel.MoveTo(xa, y);
97 rel.LineTo(xa + w, y);
98 rel.LineTo(xa + w, y - 2 * dy);
99 rel.LineTo(xa - 1, // on windows lineto is exclusive endpoint, perhaps GTK not...
100 y - 2 * dy);
103 MarginView::MarginView() noexcept {
104 wrapMarkerPaddingRight = 3;
105 customDrawWrapMarker = nullptr;
108 void MarginView::DropGraphics(bool freeObjects) {
109 if (freeObjects) {
110 pixmapSelMargin.reset();
111 pixmapSelPattern.reset();
112 pixmapSelPatternOffset1.reset();
113 } else {
114 if (pixmapSelMargin)
115 pixmapSelMargin->Release();
116 if (pixmapSelPattern)
117 pixmapSelPattern->Release();
118 if (pixmapSelPatternOffset1)
119 pixmapSelPatternOffset1->Release();
123 void MarginView::AllocateGraphics(const ViewStyle &vsDraw) {
124 if (!pixmapSelMargin)
125 pixmapSelMargin.reset(Surface::Allocate(vsDraw.technology));
126 if (!pixmapSelPattern)
127 pixmapSelPattern.reset(Surface::Allocate(vsDraw.technology));
128 if (!pixmapSelPatternOffset1)
129 pixmapSelPatternOffset1.reset(Surface::Allocate(vsDraw.technology));
132 void MarginView::RefreshPixMaps(Surface *surfaceWindow, WindowID wid, const ViewStyle &vsDraw) {
133 if (!pixmapSelPattern->Initialised()) {
134 const int patternSize = 8;
135 pixmapSelPattern->InitPixMap(patternSize, patternSize, surfaceWindow, wid);
136 pixmapSelPatternOffset1->InitPixMap(patternSize, patternSize, surfaceWindow, wid);
137 // This complex procedure is to reproduce the checkerboard dithered pattern used by windows
138 // for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half
139 // way between the chrome colour and the chrome highlight colour making a nice transition
140 // between the window chrome and the content area. And it works in low colour depths.
141 const PRectangle rcPattern = PRectangle::FromInts(0, 0, patternSize, patternSize);
143 // Initialize default colours based on the chrome colour scheme. Typically the highlight is white.
144 ColourDesired colourFMFill = vsDraw.selbar;
145 ColourDesired colourFMStripes = vsDraw.selbarlight;
147 if (!(vsDraw.selbarlight == ColourDesired(0xff, 0xff, 0xff))) {
148 // User has chosen an unusual chrome colour scheme so just use the highlight edge colour.
149 // (Typically, the highlight colour is white.)
150 colourFMFill = vsDraw.selbarlight;
153 if (vsDraw.foldmarginColour.isSet) {
154 // override default fold margin colour
155 colourFMFill = vsDraw.foldmarginColour;
157 if (vsDraw.foldmarginHighlightColour.isSet) {
158 // override default fold margin highlight colour
159 colourFMStripes = vsDraw.foldmarginHighlightColour;
162 pixmapSelPattern->FillRectangle(rcPattern, colourFMFill);
163 pixmapSelPatternOffset1->FillRectangle(rcPattern, colourFMStripes);
164 for (int y = 0; y < patternSize; y++) {
165 for (int x = y % 2; x < patternSize; x += 2) {
166 const PRectangle rcPixel = PRectangle::FromInts(x, y, x + 1, y + 1);
167 pixmapSelPattern->FillRectangle(rcPixel, colourFMStripes);
168 pixmapSelPatternOffset1->FillRectangle(rcPixel, colourFMFill);
174 static int SubstituteMarkerIfEmpty(int markerCheck, int markerDefault, const ViewStyle &vs) {
175 if (vs.markers[markerCheck].markType == SC_MARK_EMPTY)
176 return markerDefault;
177 return markerCheck;
180 void MarginView::PaintMargin(Surface *surface, Sci::Line topLine, PRectangle rc, PRectangle rcMargin,
181 const EditModel &model, const ViewStyle &vs) {
183 PRectangle rcSelMargin = rcMargin;
184 rcSelMargin.right = rcMargin.left;
185 if (rcSelMargin.bottom < rc.bottom)
186 rcSelMargin.bottom = rc.bottom;
188 const Point ptOrigin = model.GetVisibleOriginInMain();
189 FontAlias fontLineNumber = vs.styles[STYLE_LINENUMBER].font;
190 for (size_t margin = 0; margin < vs.ms.size(); margin++) {
191 if (vs.ms[margin].width > 0) {
193 rcSelMargin.left = rcSelMargin.right;
194 rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width;
196 if (vs.ms[margin].style != SC_MARGIN_NUMBER) {
197 if (vs.ms[margin].mask & SC_MASK_FOLDERS) {
198 // Required because of special way brush is created for selection margin
199 // Ensure patterns line up when scrolling with separate margin view
200 // by choosing correctly aligned variant.
201 const bool invertPhase = static_cast<int>(ptOrigin.y) & 1;
202 surface->FillRectangle(rcSelMargin,
203 invertPhase ? *pixmapSelPattern : *pixmapSelPatternOffset1);
204 } else {
205 ColourDesired colour;
206 switch (vs.ms[margin].style) {
207 case SC_MARGIN_BACK:
208 colour = vs.styles[STYLE_DEFAULT].back;
209 break;
210 case SC_MARGIN_FORE:
211 colour = vs.styles[STYLE_DEFAULT].fore;
212 break;
213 case SC_MARGIN_COLOUR:
214 colour = vs.ms[margin].back;
215 break;
216 default:
217 colour = vs.styles[STYLE_LINENUMBER].back;
218 break;
220 surface->FillRectangle(rcSelMargin, colour);
222 } else {
223 surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back);
226 const int lineStartPaint = static_cast<int>(rcMargin.top + ptOrigin.y) / vs.lineHeight;
227 Sci::Line visibleLine = model.TopLineOfMain() + lineStartPaint;
228 Sci::Position yposScreen = lineStartPaint * vs.lineHeight - static_cast<Sci::Position>(ptOrigin.y);
229 // Work out whether the top line is whitespace located after a
230 // lessening of fold level which implies a 'fold tail' but which should not
231 // be displayed until the last of a sequence of whitespace.
232 bool needWhiteClosure = false;
233 if (vs.ms[margin].mask & SC_MASK_FOLDERS) {
234 const int level = model.pdoc->GetLevel(model.pcs->DocFromDisplay(visibleLine));
235 if (level & SC_FOLDLEVELWHITEFLAG) {
236 Sci::Line lineBack = model.pcs->DocFromDisplay(visibleLine);
237 int levelPrev = level;
238 while ((lineBack > 0) && (levelPrev & SC_FOLDLEVELWHITEFLAG)) {
239 lineBack--;
240 levelPrev = model.pdoc->GetLevel(lineBack);
242 if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) {
243 if (LevelNumber(level) < LevelNumber(levelPrev))
244 needWhiteClosure = true;
247 if (highlightDelimiter.isEnabled) {
248 const Sci::Line lastLine = model.pcs->DocFromDisplay(topLine + model.LinesOnScreen()) + 1;
249 model.pdoc->GetHighlightDelimiters(highlightDelimiter,
250 model.pdoc->SciLineFromPosition(model.sel.MainCaret()), lastLine);
254 // Old code does not know about new markers needed to distinguish all cases
255 const int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID,
256 SC_MARKNUM_FOLDEROPEN, vs);
257 const int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND,
258 SC_MARKNUM_FOLDER, vs);
260 while ((visibleLine < model.pcs->LinesDisplayed()) && yposScreen < rc.bottom) {
262 PLATFORM_ASSERT(visibleLine < model.pcs->LinesDisplayed());
263 const Sci::Line lineDoc = model.pcs->DocFromDisplay(visibleLine);
264 PLATFORM_ASSERT(model.pcs->GetVisible(lineDoc));
265 const Sci::Line firstVisibleLine = model.pcs->DisplayFromDoc(lineDoc);
266 const Sci::Line lastVisibleLine = model.pcs->DisplayLastFromDoc(lineDoc);
267 const bool firstSubLine = visibleLine == firstVisibleLine;
268 const bool lastSubLine = visibleLine == lastVisibleLine;
270 int marks = model.pdoc->GetMark(lineDoc);
271 if (!firstSubLine)
272 marks = 0;
274 bool headWithTail = false;
276 if (vs.ms[margin].mask & SC_MASK_FOLDERS) {
277 // Decide which fold indicator should be displayed
278 const int level = model.pdoc->GetLevel(lineDoc);
279 const int levelNext = model.pdoc->GetLevel(lineDoc + 1);
280 const int levelNum = LevelNumber(level);
281 const int levelNextNum = LevelNumber(levelNext);
282 if (level & SC_FOLDLEVELHEADERFLAG) {
283 if (firstSubLine) {
284 if (levelNum < levelNextNum) {
285 if (model.pcs->GetExpanded(lineDoc)) {
286 if (levelNum == SC_FOLDLEVELBASE)
287 marks |= 1 << SC_MARKNUM_FOLDEROPEN;
288 else
289 marks |= 1 << folderOpenMid;
290 } else {
291 if (levelNum == SC_FOLDLEVELBASE)
292 marks |= 1 << SC_MARKNUM_FOLDER;
293 else
294 marks |= 1 << folderEnd;
296 } else if (levelNum > SC_FOLDLEVELBASE) {
297 marks |= 1 << SC_MARKNUM_FOLDERSUB;
299 } else {
300 if (levelNum < levelNextNum) {
301 if (model.pcs->GetExpanded(lineDoc)) {
302 marks |= 1 << SC_MARKNUM_FOLDERSUB;
303 } else if (levelNum > SC_FOLDLEVELBASE) {
304 marks |= 1 << SC_MARKNUM_FOLDERSUB;
306 } else if (levelNum > SC_FOLDLEVELBASE) {
307 marks |= 1 << SC_MARKNUM_FOLDERSUB;
310 needWhiteClosure = false;
311 const Sci::Line firstFollowupLine = model.pcs->DocFromDisplay(model.pcs->DisplayFromDoc(lineDoc + 1));
312 const int firstFollowupLineLevel = model.pdoc->GetLevel(firstFollowupLine);
313 const int secondFollowupLineLevelNum = LevelNumber(model.pdoc->GetLevel(firstFollowupLine + 1));
314 if (!model.pcs->GetExpanded(lineDoc)) {
315 if ((firstFollowupLineLevel & SC_FOLDLEVELWHITEFLAG) &&
316 (levelNum > secondFollowupLineLevelNum))
317 needWhiteClosure = true;
319 if (highlightDelimiter.IsFoldBlockHighlighted(firstFollowupLine))
320 headWithTail = true;
322 } else if (level & SC_FOLDLEVELWHITEFLAG) {
323 if (needWhiteClosure) {
324 if (levelNext & SC_FOLDLEVELWHITEFLAG) {
325 marks |= 1 << SC_MARKNUM_FOLDERSUB;
326 } else if (levelNextNum > SC_FOLDLEVELBASE) {
327 marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL;
328 needWhiteClosure = false;
329 } else {
330 marks |= 1 << SC_MARKNUM_FOLDERTAIL;
331 needWhiteClosure = false;
333 } else if (levelNum > SC_FOLDLEVELBASE) {
334 if (levelNextNum < levelNum) {
335 if (levelNextNum > SC_FOLDLEVELBASE) {
336 marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL;
337 } else {
338 marks |= 1 << SC_MARKNUM_FOLDERTAIL;
340 } else {
341 marks |= 1 << SC_MARKNUM_FOLDERSUB;
344 } else if (levelNum > SC_FOLDLEVELBASE) {
345 if (levelNextNum < levelNum) {
346 needWhiteClosure = false;
347 if (levelNext & SC_FOLDLEVELWHITEFLAG) {
348 marks |= 1 << SC_MARKNUM_FOLDERSUB;
349 needWhiteClosure = true;
350 } else if (lastSubLine) {
351 if (levelNextNum > SC_FOLDLEVELBASE) {
352 marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL;
353 } else {
354 marks |= 1 << SC_MARKNUM_FOLDERTAIL;
356 } else {
357 marks |= 1 << SC_MARKNUM_FOLDERSUB;
359 } else {
360 marks |= 1 << SC_MARKNUM_FOLDERSUB;
365 marks &= vs.ms[margin].mask;
367 PRectangle rcMarker = rcSelMargin;
368 rcMarker.top = static_cast<XYPOSITION>(yposScreen);
369 rcMarker.bottom = static_cast<XYPOSITION>(yposScreen + vs.lineHeight);
370 if (vs.ms[margin].style == SC_MARGIN_NUMBER) {
371 if (firstSubLine) {
372 std::string sNumber;
373 if (lineDoc >= 0) {
374 sNumber = std::to_string(lineDoc + 1);
376 if (model.foldFlags & (SC_FOLDFLAG_LEVELNUMBERS | SC_FOLDFLAG_LINESTATE)) {
377 char number[100] = "";
378 if (model.foldFlags & SC_FOLDFLAG_LEVELNUMBERS) {
379 const int lev = model.pdoc->GetLevel(lineDoc);
380 sprintf(number, "%c%c %03X %03X",
381 (lev & SC_FOLDLEVELHEADERFLAG) ? 'H' : '_',
382 (lev & SC_FOLDLEVELWHITEFLAG) ? 'W' : '_',
383 LevelNumber(lev),
384 lev >> 16
386 } else {
387 const int state = model.pdoc->GetLineState(lineDoc);
388 sprintf(number, "%0X", state);
390 sNumber = number;
392 PRectangle rcNumber = rcMarker;
393 // Right justify
394 const XYPOSITION width = surface->WidthText(fontLineNumber, sNumber.c_str(), static_cast<int>(sNumber.length()));
395 const XYPOSITION xpos = rcNumber.right - width - vs.marginNumberPadding;
396 rcNumber.left = xpos;
397 DrawTextNoClipPhase(surface, rcNumber, vs.styles[STYLE_LINENUMBER],
398 rcNumber.top + vs.maxAscent, sNumber.c_str(), static_cast<int>(sNumber.length()), drawAll);
399 } else if (vs.wrapVisualFlags & SC_WRAPVISUALFLAG_MARGIN) {
400 PRectangle rcWrapMarker = rcMarker;
401 rcWrapMarker.right -= wrapMarkerPaddingRight;
402 rcWrapMarker.left = rcWrapMarker.right - vs.styles[STYLE_LINENUMBER].aveCharWidth;
403 if (!customDrawWrapMarker) {
404 DrawWrapMarker(surface, rcWrapMarker, false, vs.styles[STYLE_LINENUMBER].fore);
405 } else {
406 customDrawWrapMarker(surface, rcWrapMarker, false, vs.styles[STYLE_LINENUMBER].fore);
409 } else if (vs.ms[margin].style == SC_MARGIN_TEXT || vs.ms[margin].style == SC_MARGIN_RTEXT) {
410 const StyledText stMargin = model.pdoc->MarginStyledText(lineDoc);
411 if (stMargin.text && ValidStyledText(vs, vs.marginStyleOffset, stMargin)) {
412 if (firstSubLine) {
413 surface->FillRectangle(rcMarker,
414 vs.styles[stMargin.StyleAt(0) + vs.marginStyleOffset].back);
415 if (vs.ms[margin].style == SC_MARGIN_RTEXT) {
416 const int width = WidestLineWidth(surface, vs, vs.marginStyleOffset, stMargin);
417 rcMarker.left = rcMarker.right - width - 3;
419 DrawStyledText(surface, vs, vs.marginStyleOffset, rcMarker,
420 stMargin, 0, stMargin.length, drawAll);
421 } else {
422 // if we're displaying annotation lines, color the margin to match the associated document line
423 const int annotationLines = model.pdoc->AnnotationLines(lineDoc);
424 if (annotationLines && (visibleLine > lastVisibleLine - annotationLines)) {
425 surface->FillRectangle(rcMarker, vs.styles[stMargin.StyleAt(0) + vs.marginStyleOffset].back);
431 if (marks) {
432 for (int markBit = 0; (markBit < 32) && marks; markBit++) {
433 if (marks & 1) {
434 LineMarker::typeOfFold tFold = LineMarker::undefined;
435 if ((vs.ms[margin].mask & SC_MASK_FOLDERS) && highlightDelimiter.IsFoldBlockHighlighted(lineDoc)) {
436 if (highlightDelimiter.IsBodyOfFoldBlock(lineDoc)) {
437 tFold = LineMarker::body;
438 } else if (highlightDelimiter.IsHeadOfFoldBlock(lineDoc)) {
439 if (firstSubLine) {
440 tFold = headWithTail ? LineMarker::headWithTail : LineMarker::head;
441 } else {
442 if (model.pcs->GetExpanded(lineDoc) || headWithTail) {
443 tFold = LineMarker::body;
444 } else {
445 tFold = LineMarker::undefined;
448 } else if (highlightDelimiter.IsTailOfFoldBlock(lineDoc)) {
449 tFold = LineMarker::tail;
452 vs.markers[markBit].Draw(surface, rcMarker, fontLineNumber, tFold, vs.ms[margin].style);
454 marks >>= 1;
458 visibleLine++;
459 yposScreen += vs.lineHeight;
464 PRectangle rcBlankMargin = rcMargin;
465 rcBlankMargin.left = rcSelMargin.right;
466 surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back);