1 // Scintilla source code edit control
2 /** @file MarginView.cxx
3 ** Defines the appearance of the editor margin.
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.
26 #include "Scintilla.h"
28 #include "CharacterCategory.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"
38 #include "Indicator.h"
39 #include "LineMarker.h"
41 #include "ViewStyle.h"
42 #include "CharClassify.h"
43 #include "Decoration.h"
44 #include "CaseFolder.h"
46 #include "UniConversion.h"
47 #include "Selection.h"
48 #include "PositionCache.h"
49 #include "EditModel.h"
50 #include "MarginView.h"
53 using 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
;
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 };
91 rel
.LineTo(xa
+ 2 * w
/ 3, y
- dy
);
93 rel
.LineTo(xa
+ 2 * w
/ 3, y
+ dy
);
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...
103 MarginView::MarginView() noexcept
{
104 wrapMarkerPaddingRight
= 3;
105 customDrawWrapMarker
= nullptr;
108 void MarginView::DropGraphics(bool freeObjects
) {
110 pixmapSelMargin
.reset();
111 pixmapSelPattern
.reset();
112 pixmapSelPatternOffset1
.reset();
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
;
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
);
205 ColourDesired colour
;
206 switch (vs
.ms
[margin
].style
) {
208 colour
= vs
.styles
[STYLE_DEFAULT
].back
;
211 colour
= vs
.styles
[STYLE_DEFAULT
].fore
;
213 case SC_MARGIN_COLOUR
:
214 colour
= vs
.ms
[margin
].back
;
217 colour
= vs
.styles
[STYLE_LINENUMBER
].back
;
220 surface
->FillRectangle(rcSelMargin
, colour
);
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
)) {
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
);
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
) {
284 if (levelNum
< levelNextNum
) {
285 if (model
.pcs
->GetExpanded(lineDoc
)) {
286 if (levelNum
== SC_FOLDLEVELBASE
)
287 marks
|= 1 << SC_MARKNUM_FOLDEROPEN
;
289 marks
|= 1 << folderOpenMid
;
291 if (levelNum
== SC_FOLDLEVELBASE
)
292 marks
|= 1 << SC_MARKNUM_FOLDER
;
294 marks
|= 1 << folderEnd
;
296 } else if (levelNum
> SC_FOLDLEVELBASE
) {
297 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
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
))
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;
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
;
338 marks
|= 1 << SC_MARKNUM_FOLDERTAIL
;
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
;
354 marks
|= 1 << SC_MARKNUM_FOLDERTAIL
;
357 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
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
) {
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' : '_',
387 const int state
= model
.pdoc
->GetLineState(lineDoc
);
388 sprintf(number
, "%0X", state
);
392 PRectangle rcNumber
= rcMarker
;
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
);
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
)) {
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
);
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
);
432 for (int markBit
= 0; (markBit
< 32) && marks
; markBit
++) {
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
)) {
440 tFold
= headWithTail
? LineMarker::headWithTail
: LineMarker::head
;
442 if (model
.pcs
->GetExpanded(lineDoc
) || headWithTail
) {
443 tFold
= LineMarker::body
;
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
);
459 yposScreen
+= vs
.lineHeight
;
464 PRectangle rcBlankMargin
= rcMargin
;
465 rcBlankMargin
.left
= rcSelMargin
.right
;
466 surface
->FillRectangle(rcBlankMargin
, vs
.styles
[STYLE_DEFAULT
].back
);