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