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.
25 #include "Scintilla.h"
27 #include "StringCopy.h"
28 #include "SplitVector.h"
29 #include "Partitioning.h"
30 #include "RunStyles.h"
31 #include "ContractionState.h"
32 #include "CellBuffer.h"
34 #include "Indicator.h"
36 #include "LineMarker.h"
38 #include "ViewStyle.h"
39 #include "CharClassify.h"
40 #include "Decoration.h"
41 #include "CaseFolder.h"
43 #include "UniConversion.h"
44 #include "Selection.h"
45 #include "PositionCache.h"
46 #include "EditModel.h"
47 #include "MarginView.h"
51 using namespace Scintilla
;
58 void DrawWrapMarker(Surface
*surface
, PRectangle rcPlace
,
59 bool isEndMarker
, ColourDesired wrapColour
) {
60 surface
->PenColour(wrapColour
);
62 enum { xa
= 1 }; // gap before start
63 int w
= static_cast<int>(rcPlace
.right
- rcPlace
.left
) - xa
- 1;
65 bool xStraight
= isEndMarker
; // x-mirrored symbol for start marker
67 int x0
= static_cast<int>(xStraight
? rcPlace
.left
: rcPlace
.right
- 1);
68 int y0
= static_cast<int>(rcPlace
.top
);
70 int dy
= static_cast<int>(rcPlace
.bottom
- rcPlace
.top
) / 5;
71 int y
= static_cast<int>(rcPlace
.bottom
- rcPlace
.top
) / 2 + dy
;
79 void MoveTo(int xRelative
, int yRelative
) {
80 surface
->MoveTo(xBase
+ xDir
* xRelative
, yBase
+ yDir
* yRelative
);
82 void LineTo(int xRelative
, int yRelative
) {
83 surface
->LineTo(xBase
+ xDir
* xRelative
, yBase
+ yDir
* yRelative
);
86 Relative rel
= { surface
, x0
, xStraight
? 1 : -1, y0
, 1 };
90 rel
.LineTo(xa
+ 2 * w
/ 3, y
- dy
);
92 rel
.LineTo(xa
+ 2 * w
/ 3, y
+ dy
);
96 rel
.LineTo(xa
+ w
, y
);
97 rel
.LineTo(xa
+ w
, y
- 2 * dy
);
98 rel
.LineTo(xa
- 1, // on windows lineto is exclusive endpoint, perhaps GTK not...
102 MarginView::MarginView() {
104 pixmapSelPattern
= 0;
105 pixmapSelPatternOffset1
= 0;
106 wrapMarkerPaddingRight
= 3;
107 customDrawWrapMarker
= NULL
;
110 void MarginView::DropGraphics(bool freeObjects
) {
112 delete pixmapSelMargin
;
114 delete pixmapSelPattern
;
115 pixmapSelPattern
= 0;
116 delete pixmapSelPatternOffset1
;
117 pixmapSelPatternOffset1
= 0;
120 pixmapSelMargin
->Release();
121 if (pixmapSelPattern
)
122 pixmapSelPattern
->Release();
123 if (pixmapSelPatternOffset1
)
124 pixmapSelPatternOffset1
->Release();
128 void MarginView::AllocateGraphics(const ViewStyle
&vsDraw
) {
129 if (!pixmapSelMargin
)
130 pixmapSelMargin
= Surface::Allocate(vsDraw
.technology
);
131 if (!pixmapSelPattern
)
132 pixmapSelPattern
= Surface::Allocate(vsDraw
.technology
);
133 if (!pixmapSelPatternOffset1
)
134 pixmapSelPatternOffset1
= Surface::Allocate(vsDraw
.technology
);
137 void MarginView::RefreshPixMaps(Surface
*surfaceWindow
, WindowID wid
, const ViewStyle
&vsDraw
) {
138 if (!pixmapSelPattern
->Initialised()) {
139 const int patternSize
= 8;
140 pixmapSelPattern
->InitPixMap(patternSize
, patternSize
, surfaceWindow
, wid
);
141 pixmapSelPatternOffset1
->InitPixMap(patternSize
, patternSize
, surfaceWindow
, wid
);
142 // This complex procedure is to reproduce the checkerboard dithered pattern used by windows
143 // for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half
144 // way between the chrome colour and the chrome highlight colour making a nice transition
145 // between the window chrome and the content area. And it works in low colour depths.
146 PRectangle rcPattern
= PRectangle::FromInts(0, 0, patternSize
, patternSize
);
148 // Initialize default colours based on the chrome colour scheme. Typically the highlight is white.
149 ColourDesired colourFMFill
= vsDraw
.selbar
;
150 ColourDesired colourFMStripes
= vsDraw
.selbarlight
;
152 if (!(vsDraw
.selbarlight
== ColourDesired(0xff, 0xff, 0xff))) {
153 // User has chosen an unusual chrome colour scheme so just use the highlight edge colour.
154 // (Typically, the highlight colour is white.)
155 colourFMFill
= vsDraw
.selbarlight
;
158 if (vsDraw
.foldmarginColour
.isSet
) {
159 // override default fold margin colour
160 colourFMFill
= vsDraw
.foldmarginColour
;
162 if (vsDraw
.foldmarginHighlightColour
.isSet
) {
163 // override default fold margin highlight colour
164 colourFMStripes
= vsDraw
.foldmarginHighlightColour
;
167 pixmapSelPattern
->FillRectangle(rcPattern
, colourFMFill
);
168 pixmapSelPatternOffset1
->FillRectangle(rcPattern
, colourFMStripes
);
169 for (int y
= 0; y
< patternSize
; y
++) {
170 for (int x
= y
% 2; x
< patternSize
; x
+= 2) {
171 PRectangle rcPixel
= PRectangle::FromInts(x
, y
, x
+ 1, y
+ 1);
172 pixmapSelPattern
->FillRectangle(rcPixel
, colourFMStripes
);
173 pixmapSelPatternOffset1
->FillRectangle(rcPixel
, colourFMFill
);
179 static int SubstituteMarkerIfEmpty(int markerCheck
, int markerDefault
, const ViewStyle
&vs
) {
180 if (vs
.markers
[markerCheck
].markType
== SC_MARK_EMPTY
)
181 return markerDefault
;
185 void MarginView::PaintMargin(Surface
*surface
, int topLine
, PRectangle rc
, PRectangle rcMargin
,
186 const EditModel
&model
, const ViewStyle
&vs
) {
188 PRectangle rcSelMargin
= rcMargin
;
189 rcSelMargin
.right
= rcMargin
.left
;
190 if (rcSelMargin
.bottom
< rc
.bottom
)
191 rcSelMargin
.bottom
= rc
.bottom
;
193 Point ptOrigin
= model
.GetVisibleOriginInMain();
194 FontAlias fontLineNumber
= vs
.styles
[STYLE_LINENUMBER
].font
;
195 for (int margin
= 0; margin
<= SC_MAX_MARGIN
; margin
++) {
196 if (vs
.ms
[margin
].width
> 0) {
198 rcSelMargin
.left
= rcSelMargin
.right
;
199 rcSelMargin
.right
= rcSelMargin
.left
+ vs
.ms
[margin
].width
;
201 if (vs
.ms
[margin
].style
!= SC_MARGIN_NUMBER
) {
202 if (vs
.ms
[margin
].mask
& SC_MASK_FOLDERS
) {
203 // Required because of special way brush is created for selection margin
204 // Ensure patterns line up when scrolling with separate margin view
205 // by choosing correctly aligned variant.
206 bool invertPhase
= static_cast<int>(ptOrigin
.y
) & 1;
207 surface
->FillRectangle(rcSelMargin
,
208 invertPhase
? *pixmapSelPattern
: *pixmapSelPatternOffset1
);
210 ColourDesired colour
;
211 switch (vs
.ms
[margin
].style
) {
213 colour
= vs
.styles
[STYLE_DEFAULT
].back
;
216 colour
= vs
.styles
[STYLE_DEFAULT
].fore
;
219 colour
= vs
.styles
[STYLE_LINENUMBER
].back
;
222 surface
->FillRectangle(rcSelMargin
, colour
);
225 surface
->FillRectangle(rcSelMargin
, vs
.styles
[STYLE_LINENUMBER
].back
);
228 const int lineStartPaint
= static_cast<int>(rcMargin
.top
+ ptOrigin
.y
) / vs
.lineHeight
;
229 int visibleLine
= model
.TopLineOfMain() + lineStartPaint
;
230 int yposScreen
= lineStartPaint
* vs
.lineHeight
- static_cast<int>(ptOrigin
.y
);
231 // Work out whether the top line is whitespace located after a
232 // lessening of fold level which implies a 'fold tail' but which should not
233 // be displayed until the last of a sequence of whitespace.
234 bool needWhiteClosure
= false;
235 if (vs
.ms
[margin
].mask
& SC_MASK_FOLDERS
) {
236 int level
= model
.pdoc
->GetLevel(model
.cs
.DocFromDisplay(visibleLine
));
237 if (level
& SC_FOLDLEVELWHITEFLAG
) {
238 int lineBack
= model
.cs
.DocFromDisplay(visibleLine
);
239 int levelPrev
= level
;
240 while ((lineBack
> 0) && (levelPrev
& SC_FOLDLEVELWHITEFLAG
)) {
242 levelPrev
= model
.pdoc
->GetLevel(lineBack
);
244 if (!(levelPrev
& SC_FOLDLEVELHEADERFLAG
)) {
245 if ((level
& SC_FOLDLEVELNUMBERMASK
) < (levelPrev
& SC_FOLDLEVELNUMBERMASK
))
246 needWhiteClosure
= true;
249 if (highlightDelimiter
.isEnabled
) {
250 int lastLine
= model
.cs
.DocFromDisplay(topLine
+ model
.LinesOnScreen()) + 1;
251 model
.pdoc
->GetHighlightDelimiters(highlightDelimiter
, model
.pdoc
->LineFromPosition(model
.sel
.MainCaret()), lastLine
);
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
);
261 while ((visibleLine
< model
.cs
.LinesDisplayed()) && yposScreen
< rc
.bottom
) {
263 PLATFORM_ASSERT(visibleLine
< model
.cs
.LinesDisplayed());
264 const int lineDoc
= model
.cs
.DocFromDisplay(visibleLine
);
265 PLATFORM_ASSERT(model
.cs
.GetVisible(lineDoc
));
266 const bool firstSubLine
= visibleLine
== model
.cs
.DisplayFromDoc(lineDoc
);
267 const bool lastSubLine
= visibleLine
== model
.cs
.DisplayLastFromDoc(lineDoc
);
269 int marks
= model
.pdoc
->GetMark(lineDoc
);
273 bool headWithTail
= false;
275 if (vs
.ms
[margin
].mask
& SC_MASK_FOLDERS
) {
276 // Decide which fold indicator should be displayed
277 const int level
= model
.pdoc
->GetLevel(lineDoc
);
278 const int levelNext
= model
.pdoc
->GetLevel(lineDoc
+ 1);
279 const int levelNum
= level
& SC_FOLDLEVELNUMBERMASK
;
280 const int levelNextNum
= levelNext
& SC_FOLDLEVELNUMBERMASK
;
281 if (level
& SC_FOLDLEVELHEADERFLAG
) {
283 if (levelNum
< levelNextNum
) {
284 if (model
.cs
.GetExpanded(lineDoc
)) {
285 if (levelNum
== SC_FOLDLEVELBASE
)
286 marks
|= 1 << SC_MARKNUM_FOLDEROPEN
;
288 marks
|= 1 << folderOpenMid
;
290 if (levelNum
== SC_FOLDLEVELBASE
)
291 marks
|= 1 << SC_MARKNUM_FOLDER
;
293 marks
|= 1 << folderEnd
;
295 } else if (levelNum
> SC_FOLDLEVELBASE
) {
296 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
299 if (levelNum
< levelNextNum
) {
300 if (model
.cs
.GetExpanded(lineDoc
)) {
301 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
302 } else if (levelNum
> SC_FOLDLEVELBASE
) {
303 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
305 } else if (levelNum
> SC_FOLDLEVELBASE
) {
306 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
309 needWhiteClosure
= false;
310 const int firstFollowupLine
= model
.cs
.DocFromDisplay(model
.cs
.DisplayFromDoc(lineDoc
+ 1));
311 const int firstFollowupLineLevel
= model
.pdoc
->GetLevel(firstFollowupLine
);
312 const int secondFollowupLineLevelNum
= model
.pdoc
->GetLevel(firstFollowupLine
+ 1) & SC_FOLDLEVELNUMBERMASK
;
313 if (!model
.cs
.GetExpanded(lineDoc
)) {
314 if ((firstFollowupLineLevel
& SC_FOLDLEVELWHITEFLAG
) &&
315 (levelNum
> secondFollowupLineLevelNum
))
316 needWhiteClosure
= true;
318 if (highlightDelimiter
.IsFoldBlockHighlighted(firstFollowupLine
))
321 } else if (level
& SC_FOLDLEVELWHITEFLAG
) {
322 if (needWhiteClosure
) {
323 if (levelNext
& SC_FOLDLEVELWHITEFLAG
) {
324 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
325 } else if (levelNextNum
> SC_FOLDLEVELBASE
) {
326 marks
|= 1 << SC_MARKNUM_FOLDERMIDTAIL
;
327 needWhiteClosure
= false;
329 marks
|= 1 << SC_MARKNUM_FOLDERTAIL
;
330 needWhiteClosure
= false;
332 } else if (levelNum
> SC_FOLDLEVELBASE
) {
333 if (levelNextNum
< levelNum
) {
334 if (levelNextNum
> SC_FOLDLEVELBASE
) {
335 marks
|= 1 << SC_MARKNUM_FOLDERMIDTAIL
;
337 marks
|= 1 << SC_MARKNUM_FOLDERTAIL
;
340 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
343 } else if (levelNum
> SC_FOLDLEVELBASE
) {
344 if (levelNextNum
< levelNum
) {
345 needWhiteClosure
= false;
346 if (levelNext
& SC_FOLDLEVELWHITEFLAG
) {
347 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
348 needWhiteClosure
= true;
349 } else if (lastSubLine
) {
350 if (levelNextNum
> SC_FOLDLEVELBASE
) {
351 marks
|= 1 << SC_MARKNUM_FOLDERMIDTAIL
;
353 marks
|= 1 << SC_MARKNUM_FOLDERTAIL
;
356 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
359 marks
|= 1 << SC_MARKNUM_FOLDERSUB
;
364 marks
&= vs
.ms
[margin
].mask
;
366 PRectangle rcMarker
= rcSelMargin
;
367 rcMarker
.top
= static_cast<XYPOSITION
>(yposScreen
);
368 rcMarker
.bottom
= static_cast<XYPOSITION
>(yposScreen
+ vs
.lineHeight
);
369 if (vs
.ms
[margin
].style
== SC_MARGIN_NUMBER
) {
371 char number
[100] = "";
373 sprintf(number
, "%d", lineDoc
+ 1);
374 if (model
.foldFlags
& (SC_FOLDFLAG_LEVELNUMBERS
| SC_FOLDFLAG_LINESTATE
)) {
375 if (model
.foldFlags
& SC_FOLDFLAG_LEVELNUMBERS
) {
376 int lev
= model
.pdoc
->GetLevel(lineDoc
);
377 sprintf(number
, "%c%c %03X %03X",
378 (lev
& SC_FOLDLEVELHEADERFLAG
) ? 'H' : '_',
379 (lev
& SC_FOLDLEVELWHITEFLAG
) ? 'W' : '_',
380 lev
& SC_FOLDLEVELNUMBERMASK
,
384 int state
= model
.pdoc
->GetLineState(lineDoc
);
385 sprintf(number
, "%0X", state
);
388 PRectangle rcNumber
= rcMarker
;
390 XYPOSITION width
= surface
->WidthText(fontLineNumber
, number
, static_cast<int>(strlen(number
)));
391 XYPOSITION xpos
= rcNumber
.right
- width
- vs
.marginNumberPadding
;
392 rcNumber
.left
= xpos
;
393 DrawTextNoClipPhase(surface
, rcNumber
, vs
.styles
[STYLE_LINENUMBER
],
394 rcNumber
.top
+ vs
.maxAscent
, number
, static_cast<int>(strlen(number
)), drawAll
);
395 } else if (vs
.wrapVisualFlags
& SC_WRAPVISUALFLAG_MARGIN
) {
396 PRectangle rcWrapMarker
= rcMarker
;
397 rcWrapMarker
.right
-= wrapMarkerPaddingRight
;
398 rcWrapMarker
.left
= rcWrapMarker
.right
- vs
.styles
[STYLE_LINENUMBER
].aveCharWidth
;
399 if (customDrawWrapMarker
== NULL
) {
400 DrawWrapMarker(surface
, rcWrapMarker
, false, vs
.styles
[STYLE_LINENUMBER
].fore
);
402 customDrawWrapMarker(surface
, rcWrapMarker
, false, vs
.styles
[STYLE_LINENUMBER
].fore
);
405 } else if (vs
.ms
[margin
].style
== SC_MARGIN_TEXT
|| vs
.ms
[margin
].style
== SC_MARGIN_RTEXT
) {
407 const StyledText stMargin
= model
.pdoc
->MarginStyledText(lineDoc
);
408 if (stMargin
.text
&& ValidStyledText(vs
, vs
.marginStyleOffset
, stMargin
)) {
409 surface
->FillRectangle(rcMarker
,
410 vs
.styles
[stMargin
.StyleAt(0) + vs
.marginStyleOffset
].back
);
411 if (vs
.ms
[margin
].style
== SC_MARGIN_RTEXT
) {
412 int width
= WidestLineWidth(surface
, vs
, vs
.marginStyleOffset
, stMargin
);
413 rcMarker
.left
= rcMarker
.right
- width
- 3;
415 DrawStyledText(surface
, vs
, vs
.marginStyleOffset
, rcMarker
,
416 stMargin
, 0, stMargin
.length
, drawAll
);
422 for (int markBit
= 0; (markBit
< 32) && marks
; markBit
++) {
424 LineMarker::typeOfFold tFold
= LineMarker::undefined
;
425 if ((vs
.ms
[margin
].mask
& SC_MASK_FOLDERS
) && highlightDelimiter
.IsFoldBlockHighlighted(lineDoc
)) {
426 if (highlightDelimiter
.IsBodyOfFoldBlock(lineDoc
)) {
427 tFold
= LineMarker::body
;
428 } else if (highlightDelimiter
.IsHeadOfFoldBlock(lineDoc
)) {
430 tFold
= headWithTail
? LineMarker::headWithTail
: LineMarker::head
;
432 if (model
.cs
.GetExpanded(lineDoc
) || headWithTail
) {
433 tFold
= LineMarker::body
;
435 tFold
= LineMarker::undefined
;
438 } else if (highlightDelimiter
.IsTailOfFoldBlock(lineDoc
)) {
439 tFold
= LineMarker::tail
;
442 vs
.markers
[markBit
].Draw(surface
, rcMarker
, fontLineNumber
, tFold
, vs
.ms
[margin
].style
);
449 yposScreen
+= vs
.lineHeight
;
454 PRectangle rcBlankMargin
= rcMargin
;
455 rcBlankMargin
.left
= rcSelMargin
.right
;
456 surface
->FillRectangle(rcBlankMargin
, vs
.styles
[STYLE_DEFAULT
].back
);