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 <[email protected]> 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 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 104 MarginView::MarginView() noexcept { 105 wrapMarkerPaddingRight = 3; 106 customDrawWrapMarker = nullptr; 107 } 108 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 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 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 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 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