// Scintilla source code edit control /** @file Editor.cxx ** Main code for the edit control. **/ // Copyright 1998-2001 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include "Platform.h" #include "Scintilla.h" #if PLAT_WX || PLAT_GTK #include "WinDefs.h" #endif #include "ContractionState.h" #include "SVector.h" #include "CellBuffer.h" #include "KeyMap.h" #include "Indicator.h" #include "LineMarker.h" #include "Style.h" #include "ViewStyle.h" #include "Document.h" #include "Editor.h" Caret::Caret() : active(false), on(false), period(500) {} Timer::Timer() : ticking(false), ticksToWait(0), tickerID(0) {} Editor::Editor() { ctrlID = 0; stylesValid = false; printMagnification = 0; printColourMode = SC_PRINT_NORMAL; cursorMode = SC_CURSORNORMAL; hasFocus = false; hideSelection = false; inOverstrike = false; errorStatus = 0; mouseDownCaptures = true; bufferedDraw = true; lastClickTime = 0; dwellDelay = SC_TIME_FOREVER; ticksToDwell = SC_TIME_FOREVER; dwelling = false; ptMouseLast.x = 0; ptMouseLast.y = 0; firstExpose = true; inDragDrop = false; dropWentOutside = false; posDrag = invalidPosition; posDrop = invalidPosition; selectionType = selChar; lastXChosen = 0; lineAnchor = 0; originalAnchorPos = 0; dragChars = 0; lenDrag = 0; dragIsRectangle = false; selType = selStream; xStartSelect = 0; xEndSelect = 0; primarySelection = true; caretPolicy = CARET_SLOP; caretSlop = 0; visiblePolicy = VISIBLE_SLOP; visibleSlop = 0; searchAnchor = 0; ucWheelScrollLines = 0; cWheelDelta = 0; //wheel delta from roll xOffset = 0; xCaretMargin = 50; horizontalScrollBarVisible = true; currentPos = 0; anchor = 0; targetStart = 0; targetEnd = 0; searchFlags = 0; topLine = 0; posTopLine = 0; needUpdateUI = true; braces[0] = invalidPosition; braces[1] = invalidPosition; bracesMatchStyle = STYLE_BRACEBAD; highlightGuideColumn = 0; theEdge = 0; paintState = notPainting; modEventMask = SC_MODEVENTMASKALL; displayPopupMenu = true; pdoc = new Document(); pdoc ->AddRef(); pdoc->AddWatcher(this, 0); #ifdef MACRO_SUPPORT recordingMacro = 0; #endif foldFlags = 0; } Editor::~Editor() { pdoc->RemoveWatcher(this, 0); pdoc->Release(); pdoc = 0; DropGraphics(); delete []dragChars; dragChars = 0; lenDrag = 0; } void Editor::Finalise() { CancelModes(); } void Editor::DropGraphics() { pixmapLine.Release(); pixmapSelMargin.Release(); pixmapSelPattern.Release(); pixmapIndentGuide.Release(); } void Editor::InvalidateStyleData() { stylesValid = false; palette.Release(); DropGraphics(); } void Editor::InvalidateStyleRedraw() { InvalidateStyleData(); Redraw(); } void Editor::RefreshColourPalette(Palette &pal, bool want) { vs.RefreshColourPalette(pal, want); } void Editor::RefreshStyleData() { if (!stylesValid) { stylesValid = true; Surface surface; surface.Init(); vs.Refresh(surface); RefreshColourPalette(palette, true); palette.Allocate(wMain); RefreshColourPalette(palette, false); SetScrollBars(); } } PRectangle Editor::GetClientRectangle() { return wMain.GetClientPosition(); } PRectangle Editor::GetTextRectangle() { PRectangle rc = GetClientRectangle(); rc.left += vs.fixedColumnWidth; rc.right -= vs.rightMarginWidth; return rc; } int Editor::LinesOnScreen() { PRectangle rcClient = GetClientRectangle(); int htClient = rcClient.bottom - rcClient.top; //Platform::DebugPrintf("lines on screen = %d\n", htClient / lineHeight + 1); return htClient / vs.lineHeight; } int Editor::LinesToScroll() { int retVal = LinesOnScreen() - 1; if (retVal < 1) return 1; else return retVal; } int Editor::MaxScrollPos() { //Platform::DebugPrintf("Lines %d screen = %d maxScroll = %d\n", //LinesTotal(), LinesOnScreen(), LinesTotal() - LinesOnScreen() + 1); int retVal = cs.LinesDisplayed() - LinesOnScreen(); if (retVal < 0) return 0; else return retVal; } static inline bool IsControlCharacter(char ch) { // iscntrl returns true for lots of chars > 127 which are displayable return ch >= 0 && ch < ' '; } const char *ControlCharacterString(unsigned char ch) { const char *reps[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; if (ch < (sizeof(reps) / sizeof(reps[0]))) { return reps[ch]; } else { return "BAD"; } } Point Editor::LocationFromPosition(int pos) { Point pt; RefreshStyleData(); if (pos == INVALID_POSITION) return pt; int line = pdoc->LineFromPosition(pos); int lineVisible = cs.DisplayFromDoc(line); //Platform::DebugPrintf("line=%d\n", line); Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); pt.y = (lineVisible - topLine) * vs.lineHeight; // + half a lineheight? unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); if ((pos - posLineStart) > LineLayout::maxLineLength) { // very long line so put x at arbitrary large position pt.x = ll.positions[LineLayout::maxLineLength] + vs.fixedColumnWidth - xOffset; } else { pt.x = ll.positions[pos - posLineStart] + vs.fixedColumnWidth - xOffset; } return pt; } int Editor::XFromPosition(int pos) { Point pt = LocationFromPosition(pos); return pt.x - vs.fixedColumnWidth + xOffset; } int Editor::LineFromLocation(Point pt) { return cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); } void Editor::SetTopLine(int topLineNew) { topLine = topLineNew; posTopLine = pdoc->LineStart(topLine); } int Editor::PositionFromLocation(Point pt) { RefreshStyleData(); pt.x = pt.x - vs.fixedColumnWidth + xOffset; int line = cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); if (pt.y < 0) { // Division rounds towards 0 line = cs.DocFromDisplay((pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine); } if (line < 0) return 0; if (line >= pdoc->LinesTotal()) return pdoc->Length(); Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); for (int i = 0; i < ll.numCharsInLine; i++) { if (pt.x < ((ll.positions[i] + ll.positions[i + 1]) / 2) || ll.chars[i] == '\r' || ll.chars[i] == '\n') { return i + posLineStart; } } return ll.numCharsInLine + posLineStart; } // Like PositionFromLocation but INVALID_POSITION returned when not near any text. int Editor::PositionFromLocationClose(Point pt) { RefreshStyleData(); PRectangle rcClient = GetTextRectangle(); if (!rcClient.Contains(pt)) return INVALID_POSITION; if (pt.x < vs.fixedColumnWidth) return INVALID_POSITION; if (pt.y < 0) return INVALID_POSITION; pt.x = pt.x - vs.fixedColumnWidth + xOffset; int line = cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); if (pt.y < 0) { // Division rounds towards 0 line = cs.DocFromDisplay((pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine); } if (line < 0) return INVALID_POSITION; if (line >= pdoc->LinesTotal()) return INVALID_POSITION; Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); for (int i = 0; i < ll.numCharsInLine; i++) { if (pt.x < ((ll.positions[i] + ll.positions[i + 1]) / 2) || ll.chars[i] == '\r' || ll.chars[i] == '\n') { return i + posLineStart; } } return INVALID_POSITION; } int Editor::PositionFromLineX(int line, int x) { RefreshStyleData(); if (line >= pdoc->LinesTotal()) return pdoc->Length(); //Platform::DebugPrintf("Position of (%d,%d) line = %d top=%d\n", pt.x, pt.y, line, topLine); Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); for (int i = 0; i < ll.numCharsInLine; i++) { if (x < ((ll.positions[i] + ll.positions[i + 1]) / 2) || ll.chars[i] == '\r' || ll.chars[i] == '\n') { return i + posLineStart; } } return ll.numCharsInLine + posLineStart; } void Editor::RedrawRect(PRectangle rc) { //Platform::DebugPrintf("Redraw %0d,%0d - %0d,%0d\n", rc.left, rc.top, rc.right, rc.bottom); // Clip the redraw rectangle into the client area PRectangle rcClient = GetClientRectangle(); if (rc.top < rcClient.top) rc.top = rcClient.top; if (rc.bottom > rcClient.bottom) rc.bottom = rcClient.bottom; if (rc.left < rcClient.left) rc.left = rcClient.left; if (rc.right > rcClient.right) rc.right = rcClient.right; if ((rc.bottom > rc.top) && (rc.right > rc.left)) { wMain.InvalidateRectangle(rc); } } void Editor::Redraw() { //Platform::DebugPrintf("Redraw all\n"); wMain.InvalidateAll(); } void Editor::RedrawSelMargin() { if (vs.maskInLine) { Redraw(); } else { PRectangle rcSelMargin = GetClientRectangle(); rcSelMargin.right = vs.fixedColumnWidth; wMain.InvalidateRectangle(rcSelMargin); } } PRectangle Editor::RectangleFromRange(int start, int end) { int minPos = start; if (minPos > end) minPos = end; int maxPos = start; if (maxPos < end) maxPos = end; int minLine = cs.DisplayFromDoc(pdoc->LineFromPosition(minPos)); int maxLine = cs.DisplayFromDoc(pdoc->LineFromPosition(maxPos)); PRectangle rcClient = GetTextRectangle(); PRectangle rc; rc.left = vs.fixedColumnWidth; rc.top = (minLine - topLine) * vs.lineHeight; if (rc.top < 0) rc.top = 0; rc.right = rcClient.right; rc.bottom = (maxLine - topLine + 1) * vs.lineHeight; // Ensure PRectangle is within 16 bit space rc.top = Platform::Clamp(rc.top, -32000, 32000); rc.bottom = Platform::Clamp(rc.bottom, -32000, 32000); return rc; } void Editor::InvalidateRange(int start, int end) { RedrawRect(RectangleFromRange(start, end)); } int Editor::CurrentPosition() { return currentPos; } bool Editor::SelectionEmpty() { return anchor == currentPos; } int Editor::SelectionStart(int line) { if ((line == -1) || (selType == selStream)) { return Platform::Minimum(currentPos, anchor); } else { // selType == selRectangle int selStart = SelectionStart(); int selEnd = SelectionEnd(); int lineStart = pdoc->LineFromPosition(selStart); int lineEnd = pdoc->LineFromPosition(selEnd); if (line < lineStart || line > lineEnd) { return -1; } else { int minX = Platform::Minimum(xStartSelect, xEndSelect); return PositionFromLineX(line, minX); } } } int Editor::SelectionEnd(int line) { if ((line == -1) || (selType == selStream)) { return Platform::Maximum(currentPos, anchor); } else { // selType == selRectangle int selStart = SelectionStart(); int selEnd = SelectionEnd(); int lineStart = pdoc->LineFromPosition(selStart); int lineEnd = pdoc->LineFromPosition(selEnd); if (line < lineStart || line > lineEnd) { return -1; } else { int maxX = Platform::Maximum(xStartSelect, xEndSelect); // measure line and return character closest to minx return PositionFromLineX(line, maxX); } } } void Editor::SetSelection(int currentPos_, int anchor_) { currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); anchor_ = pdoc->ClampPositionIntoDocument(anchor_); if ((currentPos != currentPos_) || (anchor != anchor_)) { int firstAffected = anchor; if (firstAffected > currentPos) firstAffected = currentPos; if (firstAffected > anchor_) firstAffected = anchor_; if (firstAffected > currentPos_) firstAffected = currentPos_; int lastAffected = anchor; if (lastAffected < currentPos) lastAffected = currentPos; if (lastAffected < anchor_) lastAffected = anchor_; if (lastAffected < (currentPos_ + 1)) // +1 ensures caret repainted lastAffected = (currentPos_ + 1); currentPos = currentPos_; anchor = anchor_; needUpdateUI = true; InvalidateRange(firstAffected, lastAffected); } ClaimSelection(); } void Editor::SetSelection(int currentPos_) { currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); if (currentPos != currentPos_) { int firstAffected = anchor; if (firstAffected > currentPos) firstAffected = currentPos; if (firstAffected > currentPos_) firstAffected = currentPos_; int lastAffected = anchor; if (lastAffected < currentPos) lastAffected = currentPos; if (lastAffected < (currentPos_ + 1)) // +1 ensures caret repainted lastAffected = (currentPos_ + 1); currentPos = currentPos_; needUpdateUI = true; InvalidateRange(firstAffected, lastAffected); } ClaimSelection(); } void Editor::SetEmptySelection(int currentPos_) { selType = selStream; SetSelection(currentPos_, currentPos_); } int Editor::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) { // Asks document to find a good position and then moves out of any invisible positions pos = pdoc->MovePositionOutsideChar(pos, moveDir, checkLineEnd); int mask = pdoc->stylingBitsMask; if (moveDir > 0) { while ((pos < pdoc->Length()) && (!vs.styles[pdoc->StyleAt(pos - 1) & mask].visible)) pos++; } else { while ((pos > 0) && (!vs.styles[pdoc->StyleAt(pos - 1) & mask].visible)) pos--; } return pos; } int Editor::MovePositionTo(int newPos, bool extend) { int delta = newPos - currentPos; newPos = pdoc->ClampPositionIntoDocument(newPos); newPos = MovePositionOutsideChar(newPos, delta); if (extend) { SetSelection(newPos); } else { SetEmptySelection(newPos); } EnsureCaretVisible(); ShowCaretAtCurrentPosition(); NotifyMove(newPos); return 0; } int Editor::MovePositionSoVisible(int pos, int moveDir) { pos = pdoc->ClampPositionIntoDocument(pos); pos = MovePositionOutsideChar(pos, moveDir); int lineDoc = pdoc->LineFromPosition(pos); if (cs.GetVisible(lineDoc)) { return pos; } else { int lineDisplay = cs.DisplayFromDoc(lineDoc); if (moveDir > 0) { lineDisplay = Platform::Clamp(lineDisplay + 1, 0, cs.LinesDisplayed()); return pdoc->LineStart(cs.DocFromDisplay(lineDisplay)); } else { // lineDisplay is already line before fold as lines in fold use display line of line before fold lineDisplay = Platform::Clamp(lineDisplay, 0, cs.LinesDisplayed()); return pdoc->LineEndPosition(pdoc->LineStart(cs.DocFromDisplay(lineDisplay))); } } } // Choose the x position that the caret will try to stick to as it is moves up and down void Editor::SetLastXChosen() { Point pt = LocationFromPosition(currentPos); lastXChosen = pt.x; } void Editor::ScrollTo(int line) { int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); if (topLineNew != topLine) { // Try to optimise small scrolls int linesToMove = topLine - topLineNew; SetTopLine(topLineNew); ShowCaretAtCurrentPosition(); // Perform redraw rather than scroll if many lines would be redrawn anyway. if (abs(linesToMove) <= 10) { ScrollText(linesToMove); } else { Redraw(); } SetVerticalScrollPos(); } } void Editor::ScrollText(int /* linesToMove */) { //Platform::DebugPrintf("Editor::ScrollText %d\n", linesToMove); Redraw(); } void Editor::HorizontalScrollTo(int xPos) { //Platform::DebugPrintf("HorizontalScroll %d\n", xPos); xOffset = xPos; if (xOffset < 0) xOffset = 0; SetHorizontalScrollPos(); RedrawRect(GetClientRectangle()); } void Editor::MoveCaretInsideView() { PRectangle rcClient = GetTextRectangle(); Point pt = LocationFromPosition(currentPos); if (pt.y < rcClient.top) { MovePositionTo(PositionFromLocation( Point(lastXChosen, rcClient.top))); } else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { int yOfLastLineFullyDisplayed = rcClient.top + (LinesOnScreen() - 1) * vs.lineHeight; MovePositionTo(PositionFromLocation( Point(lastXChosen, rcClient.top + yOfLastLineFullyDisplayed))); } } void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { //Platform::DebugPrintf("EnsureCaretVisible %d %s\n", xOffset, useMargin ? " margin" : " "); PRectangle rcClient = GetTextRectangle(); //int rcClientFullWidth = rcClient.Width(); int posCaret = currentPos; if (posDrag >= 0) posCaret = posDrag; Point pt = LocationFromPosition(posCaret); Point ptEOL = LocationFromPosition(pdoc->LineEndPosition(posCaret)); Point ptBottomCaret = pt; int lineCaret = cs.DisplayFromDoc(pdoc->LineFromPosition(posCaret)); ptBottomCaret.y += vs.lineHeight - 1; // Ensure the caret is reasonably visible in context: // xMargin must equal to xCaretMargin, with a minimum of 2 and a maximum of // slightly less than half the width of the text area. int xMargin = Platform::Clamp(xCaretMargin, 2, Platform::Maximum(rcClient.Width() - 10, 4) / 2); if (!useMargin) xMargin = 2; // If we scroll the display, we use a minimum amount of xMargin. int offsetLeft = rcClient.left + xMargin; int offsetRight = rcClient.right - xMargin; // If we are in XJUMPS mode, then when the margin is reached, the // offset jumps so that it won't need to move agin for a while. if (!(caretPolicy & CARET_XJUMPS)) { rcClient.left = offsetLeft; rcClient.right = offsetRight; } // Vertical positioning if (vert && (!rcClient.Contains(pt) || !rcClient.Contains(ptBottomCaret) || (caretPolicy & CARET_STRICT))) { //Platform::DebugPrintf("EnsureCaretVisible move, (%d,%d)(%d,%d)\n", pt.x, pt.y, rcClient.left, rcClient.right); // It should be possible to scroll the window to show the caret, // but this fails to remove the caret on GTK+ if (caretPolicy & CARET_SLOP) { if ((topLine > lineCaret) || ((caretPolicy & CARET_STRICT) && (topLine + caretSlop > lineCaret))) { SetTopLine(Platform::Clamp(lineCaret - caretSlop, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } else if ((lineCaret > topLine + LinesOnScreen() - 1) || ((caretPolicy & CARET_STRICT) && (lineCaret > topLine + LinesOnScreen() - 1 - caretSlop))) { SetTopLine(Platform::Clamp(lineCaret - LinesOnScreen() + 1 + caretSlop, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } } else { if ((topLine > lineCaret) || (lineCaret > topLine + LinesOnScreen() - 1) || (caretPolicy & CARET_STRICT)) { SetTopLine(Platform::Clamp(lineCaret - LinesOnScreen() / 2 + 1, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } } } // Horizontal positioning if (horiz) { int xOffsetNew = xOffset; if (pt.x < rcClient.left) { xOffsetNew = xOffset - (offsetLeft - pt.x); } else if ((!(caretPolicy & CARET_XEVEN) && ((xOffset > 0) && useMargin)) || pt.x >= rcClient.right) { xOffsetNew = xOffset + (pt.x - offsetRight); int xOffsetEOL = xOffset + (ptEOL.x - offsetRight) - xMargin + 2; //Platform::DebugPrintf("Margin %d %d\n", xOffsetNew, xOffsetEOL); // Ensure don't scroll out into empty space if (xOffsetNew > xOffsetEOL) xOffsetNew = xOffsetEOL; } if (xOffsetNew < 0) xOffsetNew = 0; if (xOffset != xOffsetNew) { xOffset = xOffsetNew; SetHorizontalScrollPos(); Redraw(); } } } void Editor::ShowCaretAtCurrentPosition() { if (!hasFocus) { caret.active = false; caret.on = false; return; } caret.active = true; caret.on = true; SetTicking(true); } void Editor::DropCaret() { caret.active = false; InvalidateCaret(); } void Editor::InvalidateCaret() { if (posDrag >= 0) InvalidateRange(posDrag, posDrag + 1); else InvalidateRange(currentPos, currentPos + 1); } int Editor::SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) { if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) return markerDefault; return markerCheck; } void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { if (vs.fixedColumnWidth == 0) return; PRectangle rcMargin = GetClientRectangle(); rcMargin.right = vs.fixedColumnWidth; if (!rc.Intersects(rcMargin)) return; Surface *surface; if (bufferedDraw) { surface = &pixmapSelMargin; } else { surface = surfWindow; } PRectangle rcSelMargin = rcMargin; rcSelMargin.right = rcMargin.left; for (int margin = 0; margin < vs.margins; margin++) { if (vs.ms[margin].width > 0) { rcSelMargin.left = rcSelMargin.right; rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; if (vs.ms[margin].symbol) { /* alternate scheme: if (vs.ms[margin].mask & SC_MASK_FOLDERS) surface->FillRectangle(rcSelMargin, vs.styles[STYLE_DEFAULT].back.allocated); else // Required because of special way brush is created for selection margin surface->FillRectangle(rcSelMargin, pixmapSelPattern); */ if (vs.ms[margin].mask & SC_MASK_FOLDERS) // Required because of special way brush is created for selection margin surface->FillRectangle(rcSelMargin, pixmapSelPattern); else surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back.allocated); } else { surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back.allocated); } int visibleLine = topLine; int line = cs.DocFromDisplay(visibleLine); int yposScreen = 0; // Work out whether the top line is whitespace located after a // lessening of fold level which implies a 'fold tail' but which should not // be displayed until the last of a sequence of whitespace. bool needWhiteClosure = false; int level = pdoc->GetLevel(line); if (level & SC_FOLDLEVELWHITEFLAG) { int lineBack = line; int levelPrev = level; while ((lineBack > 0) && (levelPrev & SC_FOLDLEVELWHITEFLAG)) { lineBack--; levelPrev = pdoc->GetLevel(lineBack); } if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) { if ((level & SC_FOLDLEVELNUMBERMASK) < (levelPrev & SC_FOLDLEVELNUMBERMASK)) needWhiteClosure = true; } } // Old code does not know about new markers needed to distinguish all cases int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID, SC_MARKNUM_FOLDEROPEN); int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND, SC_MARKNUM_FOLDER); while ((visibleLine < cs.LinesDisplayed()) && yposScreen < rcMargin.bottom) { // Decide which fold indicator should be displayed level = pdoc->GetLevel(line); int levelNext = pdoc->GetLevel(line+1); int marks = pdoc->GetMark(line); int levelNum = level & SC_FOLDLEVELNUMBERMASK; int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; if (level & SC_FOLDLEVELHEADERFLAG) { if (cs.GetExpanded(line)) { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDEROPEN; else marks |= 1 << folderOpenMid; } else { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDER; else marks |= 1 << folderEnd; } needWhiteClosure = false; } else if (level & SC_FOLDLEVELWHITEFLAG) { if (needWhiteClosure) { if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; needWhiteClosure = false; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; needWhiteClosure = false; } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { needWhiteClosure = false; if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; needWhiteClosure = true; } else if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } marks &= vs.ms[margin].mask; PRectangle rcMarker = rcSelMargin; rcMarker.top = yposScreen; rcMarker.bottom = yposScreen + vs.lineHeight; if (!vs.ms[margin].symbol) { char number[100]; number[0] = '\0'; sprintf(number, "%d", line + 1); if (foldFlags & 64) sprintf(number, "%X", pdoc->GetLevel(line)); PRectangle rcNumber = rcMarker; // Right justify int width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, strlen(number)); int xpos = rcNumber.right - width - 3; rcNumber.left = xpos; if ((visibleLine < cs.LinesDisplayed()) && cs.GetVisible(line)) { surface->DrawText(rcNumber, vs.styles[STYLE_LINENUMBER].font, rcNumber.top + vs.maxAscent, number, strlen(number), vs.styles[STYLE_LINENUMBER].fore.allocated, vs.styles[STYLE_LINENUMBER].back.allocated); } } if (marks) { for (int markBit = 0; (markBit < 32) && marks; markBit++) { if (marks & 1) { vs.markers[markBit].Draw(surface, rcMarker); } marks >>= 1; } } visibleLine++; line = cs.DocFromDisplay(visibleLine); yposScreen += vs.lineHeight; } } } PRectangle rcBlankMargin = rcMargin; rcBlankMargin.left = rcSelMargin.right; surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back.allocated); if (bufferedDraw) { surfWindow->Copy(rcMargin, Point(), pixmapSelMargin); } } void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { int ydiff = (rcTab.bottom - rcTab.top) / 2; int xhead = rcTab.right - 1 - ydiff; if ((rcTab.left + 2) < (rcTab.right - 1)) surface->MoveTo(rcTab.left + 2, ymid); else surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid - ydiff); surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid + ydiff); } /** * Fill in the LineLayout data for the given line. * Copy the given @a line and its styles from the document into local arrays. * Also determine the x position at which each character starts. */ void Editor::LayoutLine(int line, Surface *surface, ViewStyle &vstyle, LineLayout &ll) { int numCharsInLine = 0; int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); Font &ctrlCharsFont = vstyle.styles[STYLE_CONTROLCHAR].font; char styleByte = 0; int styleMask = pdoc->stylingBitsMask; ll.xHighlightGuide = 0; // If the line is very long, limit the treatment to a length that should fit in the viewport if (posLineEnd > (posLineStart + LineLayout::maxLineLength)) { posLineEnd = posLineStart + LineLayout::maxLineLength; } // Fill base line layout for (int charInDoc = posLineStart; charInDoc < posLineEnd; charInDoc++) { char chDoc = pdoc->CharAt(charInDoc); styleByte = pdoc->StyleAt(charInDoc); if (vstyle.viewEOL || ((chDoc != '\r') && (chDoc != '\n'))) { ll.chars[numCharsInLine] = chDoc; ll.styles[numCharsInLine] = static_cast(styleByte & styleMask); ll.indicators[numCharsInLine] = static_cast(styleByte & ~styleMask); if (vstyle.styles[ll.styles[numCharsInLine]].caseForce == Style::caseUpper) ll.chars[numCharsInLine] = static_cast(toupper(chDoc)); else if (vstyle.styles[ll.styles[numCharsInLine]].caseForce == Style::caseLower) ll.chars[numCharsInLine] = static_cast(tolower(chDoc)); numCharsInLine++; } } // Extra element at the end of the line to hold end x position and act as ll.chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character ll.styles[numCharsInLine] = styleByte; // For eolFilled ll.indicators[numCharsInLine] = 0; // Layout the line, determining the position of each character, // with an extra element at the end for the end of the line. int startseg = 0; // Start of the current segment, in char. number int startsegx = 0; // Start of the current segment, in pixels ll.positions[0] = 0; unsigned int tabWidth = vstyle.spaceWidth * pdoc->tabInChars; bool lastSegItalics = false; for (int charInLine = 0; charInLine < numCharsInLine; charInLine++) { if ((ll.styles[charInLine] != ll.styles[charInLine + 1]) || IsControlCharacter(ll.chars[charInLine]) || IsControlCharacter(ll.chars[charInLine + 1])) { ll.positions[startseg] = 0; if (vstyle.styles[ll.styles[charInLine]].visible) { if (IsControlCharacter(ll.chars[charInLine])) { if (ll.chars[charInLine] == '\t') { ll.positions[charInLine + 1] = ((((startsegx + 2) / tabWidth) + 1) * tabWidth) - startsegx; } else { const char *ctrlChar = ControlCharacterString(ll.chars[charInLine]); // +3 For a blank on front and rounded edge each side: ll.positions[charInLine + 1] = surface->WidthText(ctrlCharsFont, ctrlChar, strlen(ctrlChar)) + 3; } lastSegItalics = false; } else { // Regular character lastSegItalics = vstyle.styles[ll.styles[charInLine]].italic; int lenSeg = charInLine - startseg + 1; if ((lenSeg == 1) && (' ' == ll.chars[startseg])) { // Over half the segments are single characters and of these about half are space characters. ll.positions[charInLine + 1] = vstyle.styles[ll.styles[charInLine]].spaceWidth; } else { surface->MeasureWidths(vstyle.styles[ll.styles[charInLine]].font, ll.chars + startseg, lenSeg, ll.positions + startseg + 1); } } } else { // invisible for (int posToZero = startseg; posToZero <= (charInLine + 1); posToZero++) { ll.positions[posToZero] = 0; } } for (int posToIncrease = startseg; posToIncrease <= (charInLine + 1); posToIncrease++) { ll.positions[posToIncrease] += startsegx; } startsegx = ll.positions[charInLine + 1]; startseg = charInLine + 1; } } // Small hack to make lines that end with italics not cut off the edge of the last character if ((startseg > 0) && lastSegItalics) { ll.positions[startseg] += 2; } ll.numCharsInLine = numCharsInLine; } void Editor::DrawLine(Surface *surface, ViewStyle &vsDraw, int line, int lineVisible, int xStart, PRectangle rcLine, LineLayout &ll) { PRectangle rcSegment = rcLine; // Using one font for all control characters so it can be controlled independently to ensure // the box goes around the characters tightly. Seems to be no way to work out what height // is taken by an individual character - internal leading gives varying results. Font &ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; bool overrideBackground = false; Colour background = Colour(0, 0, 0); if (caret.active && vsDraw.showCaretLineBackground && ll.containsCaret) { overrideBackground = true; background = vsDraw.caretLineBackground.allocated; } if (vsDraw.maskInLine) { int marks = pdoc->GetMark(line) & vsDraw.maskInLine; if (marks) { overrideBackground = true; for (int markBit = 0; (markBit < 32) && marks; markBit++) { if (marks & 1) { background = vsDraw.markers[markBit].back.allocated; } marks >>= 1; } } } bool inIndentation = true; int indentWidth = pdoc->indentInChars * vsDraw.spaceWidth; if (indentWidth == 0) indentWidth = pdoc->tabInChars * vsDraw.spaceWidth; int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); int styleMask = pdoc->stylingBitsMask; int startseg = 0; for (int i = 0; i < ll.numCharsInLine; i++) { int iDoc = i + posLineStart; // If there is the end of a style run for any reason if ((ll.styles[i] != ll.styles[i + 1]) || IsControlCharacter(ll.chars[i]) || IsControlCharacter(ll.chars[i + 1]) || ((ll.selStart != ll.selEnd) && ((iDoc + 1 == ll.selStart) || (iDoc + 1 == ll.selEnd))) || (i == (ll.edgeColumn - 1))) { int styleMain = ll.styles[i]; Colour textBack = vsDraw.styles[styleMain].back.allocated; Colour textFore = vsDraw.styles[styleMain].fore.allocated; Font &textFont = vsDraw.styles[styleMain].font; bool inSelection = (iDoc >= ll.selStart) && (iDoc < ll.selEnd) && (ll.selStart != ll.selEnd); if (inSelection) { if (vsDraw.selbackset) { if (primarySelection) textBack = vsDraw.selbackground.allocated; else textBack = vsDraw.selbackground2.allocated; } if (vsDraw.selforeset) textFore = vsDraw.selforeground.allocated; } else { if (overrideBackground) textBack = background; if ((vsDraw.edgeState == EDGE_BACKGROUND) && (i >= ll.edgeColumn) && (ll.chars[i] != '\n') && (ll.chars[i] != '\r')) textBack = vsDraw.edgecolour.allocated; } // Manage tab display if (ll.chars[i] == '\t') { rcSegment.left = ll.positions[i] + xStart; rcSegment.right = ll.positions[i + 1] + xStart; surface->FillRectangle(rcSegment, textBack); if ((vsDraw.viewWhitespace != wsInvisible) || ((inIndentation && vsDraw.viewIndentationGuides))) { surface->PenColour(textFore); } if (inIndentation && vsDraw.viewIndentationGuides) { for (int xIG = ll.positions[i] / indentWidth * indentWidth; xIG < ll.positions[i + 1]; xIG += indentWidth) { if (xIG >= ll.positions[i] && xIG > 0) { Point from(0, ((lineVisible & 1) && (vsDraw.lineHeight & 1)) ? 1 : 0); PRectangle rcCopyArea(xIG + xStart + 1, rcSegment.top, xIG + xStart + 2, rcSegment.bottom); surface->Copy(rcCopyArea, from, (ll.xHighlightGuide == xIG) ? pixmapIndentGuideHighlight : pixmapIndentGuide); } } } if (vsDraw.viewWhitespace != wsInvisible) { if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); DrawTabArrow(surface, rcTab, rcSegment.top + vsDraw.lineHeight / 2); } } // Manage control character display } else if (IsControlCharacter(ll.chars[i])) { inIndentation = false; const char *ctrlChar = ControlCharacterString(ll.chars[i]); rcSegment.left = ll.positions[i] + xStart; rcSegment.right = ll.positions[i + 1] + xStart; surface->FillRectangle(rcSegment, textBack); int normalCharHeight = surface->Ascent(ctrlCharsFont) - surface->InternalLeading(ctrlCharsFont); PRectangle rcCChar = rcSegment; rcCChar.left = rcCChar.left + 1; rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight; rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1; PRectangle rcCentral = rcCChar; rcCentral.top++; rcCentral.bottom--; surface->FillRectangle(rcCentral, textFore); PRectangle rcChar = rcCChar; rcChar.left++; rcChar.right--; surface->DrawTextClipped(rcChar, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, ctrlChar, strlen(ctrlChar), textBack, textFore); // Manage normal display } else { rcSegment.left = ll.positions[startseg] + xStart; rcSegment.right = ll.positions[i + 1] + xStart; // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (rcSegment.left <= rcLine.right) { surface->DrawText(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, ll.chars + startseg, i - startseg + 1, textFore, textBack); if (vsDraw.viewWhitespace != wsInvisible || (inIndentation && vsDraw.viewIndentationGuides)) { for (int cpos = 0; cpos <= i - startseg; cpos++) { if (ll.chars[cpos + startseg] == ' ') { if (vsDraw.viewWhitespace != wsInvisible) { if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { int xmid = (ll.positions[cpos + startseg] + ll.positions[cpos + startseg + 1]) / 2; PRectangle rcDot(xmid + xStart, rcSegment.top + vsDraw.lineHeight / 2, 0, 0); rcDot.right = rcDot.left + 1; rcDot.bottom = rcDot.top + 1; surface->FillRectangle(rcDot, textFore); } } if (inIndentation && vsDraw.viewIndentationGuides) { int startSpace = ll.positions[cpos + startseg]; if (startSpace > 0 && (startSpace % indentWidth == 0)) { Point from(0, ((lineVisible & 1) && (vsDraw.lineHeight & 1)) ? 1 : 0); PRectangle rcCopyArea(startSpace + xStart + 1, rcSegment.top, startSpace + xStart + 2, rcSegment.bottom); surface->Copy(rcCopyArea, from, (ll.xHighlightGuide == ll.positions[cpos + startseg]) ? pixmapIndentGuideHighlight : pixmapIndentGuide); } } } else { inIndentation = false; } } } } if (vsDraw.styles[styleMain].underline) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; surface->FillRectangle(rcUL, textFore); } } startseg = i + 1; } } // Draw indicators int indStart[INDIC_MAX + 1] = {0}; for (int indica = 0; indica <= INDIC_MAX; indica++) indStart[indica] = 0; for (int indicPos = 0; indicPos < ll.numCharsInLine; indicPos++) { if (ll.indicators[indicPos] != ll.indicators[indicPos + 1]) { int mask = 1 << pdoc->stylingBits; for (int indicnum = 0; mask < 0x100; indicnum++) { if ((ll.indicators[indicPos + 1] & mask) && !(ll.indicators[indicPos] & mask)) { indStart[indicnum] = ll.positions[indicPos + 1]; } if (!(ll.indicators[indicPos + 1] & mask) && (ll.indicators[indicPos] & mask)) { PRectangle rcIndic( indStart[indicnum] + xStart, rcLine.top + vsDraw.maxAscent, ll.positions[indicPos + 1] + xStart, rcLine.top + vsDraw.maxAscent + 3); vsDraw.indicators[indicnum].Draw(surface, rcIndic); } mask = mask << 1; } } } // End of the drawing of the current line // Fill in a PRectangle representing the end of line characters int xEol = ll.positions[ll.numCharsInLine]; rcSegment.left = xEol + xStart; rcSegment.right = xEol + vsDraw.aveCharWidth + xStart; bool eolInSelection = (posLineEnd > ll.selStart) && (posLineEnd <= ll.selEnd) && (ll.selStart != ll.selEnd); if (eolInSelection && vsDraw.selbackset && (line < pdoc->LinesTotal() - 1)) { if (primarySelection) surface->FillRectangle(rcSegment, vsDraw.selbackground.allocated); else surface->FillRectangle(rcSegment, vsDraw.selbackground2.allocated); } else if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else { surface->FillRectangle(rcSegment, vsDraw.styles[ll.styles[ll.numCharsInLine] & styleMask].back.allocated); } rcSegment.left = xEol + vsDraw.aveCharWidth + xStart; rcSegment.right = rcLine.right; if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else if (vsDraw.styles[ll.styles[ll.numCharsInLine] & styleMask].eolFilled) { surface->FillRectangle(rcSegment, vsDraw.styles[ll.styles[ll.numCharsInLine] & styleMask].back.allocated); } else { surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back.allocated); } if (vsDraw.edgeState == EDGE_LINE) { int edgeX = ll.edgeColumn * vsDraw.spaceWidth; rcSegment.left = edgeX + xStart; rcSegment.right = rcSegment.left + 1; surface->FillRectangle(rcSegment, vsDraw.edgecolour.allocated); } } void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { //Platform::DebugPrintf("Paint %d %d - %d %d\n", rcArea.left, rcArea.top, rcArea.right, rcArea.bottom); RefreshStyleData(); PRectangle rcClient = GetClientRectangle(); //Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", // rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); if (!pixmapSelPattern.Initialised()) { pixmapSelPattern.InitPixMap(8, 8, surfaceWindow); // This complex procedure is to reproduce the checker board dithered pattern used by windows // for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half // way between the chrome colour and the chrome highlight colour making a nice transition // between the window chrome and the content area. And it works in low colour depths. PRectangle rcPattern(0, 0, 8, 8); if (vs.selbarlight.desired == Colour(0xff, 0xff, 0xff)) { pixmapSelPattern.FillRectangle(rcPattern, vs.selbar.allocated); pixmapSelPattern.PenColour(vs.selbarlight.allocated); for (int stripe = 0; stripe < 8; stripe++) { pixmapSelPattern.MoveTo(0, stripe * 2); pixmapSelPattern.LineTo(8, stripe * 2 - 8); } } else { // User has chosen an unusual chrome colour scheme so just use the highlight edge colour. pixmapSelPattern.FillRectangle(rcPattern, vs.selbarlight.allocated); } } if (!pixmapIndentGuide.Initialised()) { // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line pixmapIndentGuide.InitPixMap(1, vs.lineHeight + 1, surfaceWindow); pixmapIndentGuideHighlight.InitPixMap(1, vs.lineHeight + 1, surfaceWindow); PRectangle rcIG(0, 0, 1, vs.lineHeight); pixmapIndentGuide.FillRectangle(rcIG, vs.styles[STYLE_INDENTGUIDE].back.allocated); pixmapIndentGuide.PenColour(vs.styles[STYLE_INDENTGUIDE].fore.allocated); pixmapIndentGuideHighlight.FillRectangle(rcIG, vs.styles[STYLE_BRACELIGHT].back.allocated); pixmapIndentGuideHighlight.PenColour(vs.styles[STYLE_BRACELIGHT].fore.allocated); for (int stripe = 1; stripe < vs.lineHeight + 1; stripe += 2) { pixmapIndentGuide.MoveTo(0, stripe); pixmapIndentGuide.LineTo(2, stripe); pixmapIndentGuideHighlight.MoveTo(0, stripe); pixmapIndentGuideHighlight.LineTo(2, stripe); } } if (bufferedDraw) { if (!pixmapLine.Initialised()) { pixmapLine.InitPixMap(rcClient.Width(), rcClient.Height(), surfaceWindow); pixmapSelMargin.InitPixMap(vs.fixedColumnWidth, rcClient.Height(), surfaceWindow); } } surfaceWindow->SetPalette(&palette, true); pixmapLine.SetPalette(&palette, !hasFocus); //Platform::DebugPrintf("Paint: (%3d,%3d) ... (%3d,%3d)\n", // rcArea.left, rcArea.top, rcArea.right, rcArea.bottom); int screenLinePaintFirst = rcArea.top / vs.lineHeight; // The area to be painted plus one extra line is styled. // The extra line is to determine when a style change, such as starting a comment flows on to other lines. int lineStyleLast = topLine + (rcArea.bottom - 1) / vs.lineHeight + 1; //Platform::DebugPrintf("Paint lines = %d .. %d\n", topLine + screenLinePaintFirst, lineStyleLast); int endPosPaint = pdoc->Length(); if (lineStyleLast < cs.LinesDisplayed()) endPosPaint = pdoc->LineStart(cs.DocFromDisplay(lineStyleLast + 1)); int xStart = vs.fixedColumnWidth - xOffset; int ypos = 0; if (!bufferedDraw) ypos += screenLinePaintFirst * vs.lineHeight; int yposScreen = screenLinePaintFirst * vs.lineHeight; // Ensure we are styled as far as we are painting. pdoc->EnsureStyledTo(endPosPaint); if (needUpdateUI) { NotifyUpdateUI(); needUpdateUI = false; } PaintSelMargin(surfaceWindow, rcArea); PRectangle rcRightMargin = rcClient; rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; if (rcArea.Intersects(rcRightMargin)) { surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back.allocated); } if (paintState == paintAbandoned) { // Either styling or NotifyUpdateUI noticed that painting is needed // outside the current painting rectangle //Platform::DebugPrintf("Abandoning paint\n"); return; } //Platform::DebugPrintf("start display %d, offset = %d\n", pdoc->Length(), xOffset); // Do the painting if (rcArea.right > vs.fixedColumnWidth) { Surface *surface = surfaceWindow; if (bufferedDraw) { surface = &pixmapLine; } surface->SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); int visibleLine = topLine + screenLinePaintFirst; int line = cs.DocFromDisplay(visibleLine); int posCaret = currentPos; if (posDrag >= 0) posCaret = posDrag; int lineCaret = pdoc->LineFromPosition(posCaret); // Remove selection margin from drawing area so text will not be drawn // on it in unbuffered mode. PRectangle rcTextArea = rcClient; rcTextArea.left = vs.fixedColumnWidth; rcTextArea.right -= vs.rightMarginWidth; surfaceWindow->SetClip(rcTextArea); // Loop on visible lines //GTimer *tim=g_timer_new(); while (visibleLine < cs.LinesDisplayed() && yposScreen < rcArea.bottom) { //g_timer_start(tim); //Platform::DebugPrintf("Painting line %d\n", line); // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. LineLayout ll; LayoutLine(line, surface, vs, ll); ll.selStart = SelectionStart(line); ll.selEnd = SelectionEnd(line); ll.containsCaret = line == lineCaret; if (hideSelection) { ll.selStart = -1; ll.selEnd = -1; ll.containsCaret = false; } // Need to fix this up so takes account of Unicode and DBCS ll.edgeColumn = theEdge; int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); //Platform::DebugPrintf("line %d %d - %d\n", line, posLineStart, posLineEnd); PRectangle rcLine = rcClient; rcLine.top = ypos; rcLine.bottom = ypos + vs.lineHeight; // Highlight the current braces if any if ((braces[0] >= posLineStart) && (braces[0] < posLineEnd)) { int braceOffset = braces[0] - posLineStart; if (braceOffset < ll.numCharsInLine) ll.styles[braceOffset] = static_cast(bracesMatchStyle); } if ((braces[1] >= posLineStart) && (braces[1] < posLineEnd)) { int braceOffset = braces[1] - posLineStart; if (braceOffset < ll.numCharsInLine) ll.styles[braceOffset] = static_cast(bracesMatchStyle); } if ((braces[0] >= posLineStart && braces[1] <= posLineEnd) || (braces[1] >= posLineStart && braces[0] <= posLineEnd)) { ll.xHighlightGuide = highlightGuideColumn * vs.spaceWidth; } // Draw the line if (cs.GetVisible(line)) DrawLine(surface, vs, line, visibleLine, xStart, rcLine, ll); bool expanded = cs.GetExpanded(line); if ( (expanded && (foldFlags & 2)) || (!expanded && (foldFlags & 4)) ) { if (pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) { PRectangle rcFoldLine = rcLine; rcFoldLine.bottom = rcFoldLine.top + 1; surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); } } if ( (expanded && (foldFlags & 8)) || (!expanded && (foldFlags & 16)) ) { if (pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) { PRectangle rcFoldLine = rcLine; rcFoldLine.top = rcFoldLine.bottom - 1; surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); } } // Draw the Caret if (line == lineCaret) { int offset = Platform::Minimum(posCaret - posLineStart, LineLayout::maxLineLength); int xposCaret = ll.positions[offset] + xStart; int widthOverstrikeCaret; if (posCaret == pdoc->Length()) { // At end of document widthOverstrikeCaret = vs.aveCharWidth; } else if ((posCaret - posLineStart) >= ll.numCharsInLine) { // At end of line widthOverstrikeCaret = vs.aveCharWidth; } else { widthOverstrikeCaret = ll.positions[offset + 1] - ll.positions[offset]; } if (widthOverstrikeCaret < 3) // Make sure its visible widthOverstrikeCaret = 3; if (((caret.active && caret.on) || (posDrag >= 0)) && xposCaret >= 0) { PRectangle rcCaret = rcLine; int caretWidthOffset = 0; if ((offset > 0) && (vs.caretWidth > 1)) caretWidthOffset = 1; // Move back so overlaps both character cells. if (posDrag >= 0) { rcCaret.left = xposCaret - caretWidthOffset; rcCaret.right = rcCaret.left + vs.caretWidth; } else { if (inOverstrike) { rcCaret.top = rcCaret.bottom - 2; rcCaret.left = xposCaret + 1; rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; } else { rcCaret.left = xposCaret - caretWidthOffset; rcCaret.right = rcCaret.left + vs.caretWidth; } } surface->FillRectangle(rcCaret, vs.caretcolour.allocated); } } if (cs.GetVisible(line)) { if (bufferedDraw) { Point from(vs.fixedColumnWidth, 0); PRectangle rcCopyArea(vs.fixedColumnWidth, yposScreen, rcClient.right, yposScreen + vs.lineHeight); surfaceWindow->Copy(rcCopyArea, from, pixmapLine); } } if (!bufferedDraw) { ypos += vs.lineHeight; } yposScreen += vs.lineHeight; visibleLine++; line = cs.DocFromDisplay(visibleLine); //gdk_flush(); //g_timer_stop(tim); //Platform::DebugPrintf("Paint [%0d] took %g\n", line, g_timer_elapsed(tim, 0)); } //g_timer_destroy(tim); // Right column limit indicator PRectangle rcBeyondEOF = rcClient; rcBeyondEOF.left = vs.fixedColumnWidth; rcBeyondEOF.right = rcBeyondEOF.right; rcBeyondEOF.top = (cs.LinesDisplayed() - topLine) * vs.lineHeight; if (rcBeyondEOF.top < rcBeyondEOF.bottom) { surfaceWindow->FillRectangle(rcBeyondEOF, vs.styles[STYLE_DEFAULT].back.allocated); if (vs.edgeState == EDGE_LINE) { int edgeX = theEdge * vs.spaceWidth; rcBeyondEOF.left = edgeX + xStart; rcBeyondEOF.right = rcBeyondEOF.left + 1; surfaceWindow->FillRectangle(rcBeyondEOF, vs.edgecolour.allocated); } } NotifyPainted(); } } // Space (3 space characters) between line numbers and text when printing. #define lineNumberPrintSpace " " Colour InvertedLight(Colour orig) { unsigned int r = orig.GetRed(); unsigned int g = orig.GetGreen(); unsigned int b = orig.GetBlue(); unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye unsigned int il = 0xff - l; if (l == 0) return Colour(0xff, 0xff, 0xff); r = r * il / l; g = g * il / l; b = b * il / l; return Colour(Platform::Minimum(r, 0xff), Platform::Minimum(g, 0xff), Platform::Minimum(b, 0xff)); } // This is mostly copied from the Paint method but with some things omitted // such as the margin markers, line numbers, selection and caret // Should be merged back into a combined Draw method. long Editor::FormatRange(bool draw, RangeToFormat *pfr) { if (!pfr) return 0; Surface *surface = new Surface(); surface->Init(pfr->hdc); surface->SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); Surface *surfaceMeasure = new Surface(); surfaceMeasure->Init(pfr->hdcTarget); surfaceMeasure->SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); ViewStyle vsPrint(vs); // Modify the view style for printing as do not normally want any of the transient features to be printed // Printing supports only the line number margin. int lineNumberIndex = -1; for (int margin = 0; margin < ViewStyle::margins; margin++) { if ((!vsPrint.ms[margin].symbol) && (vsPrint.ms[margin].width > 0)) { lineNumberIndex = margin; } else { vsPrint.ms[margin].width = 0; } } vsPrint.showMarkedLines = false; vsPrint.fixedColumnWidth = 0; vsPrint.zoomLevel = printMagnification; vsPrint.viewIndentationGuides = false; // Don't show the selection when printing vsPrint.selbackset = false; vsPrint.selforeset = false; vsPrint.showCaretLineBackground = false; // Set colours for printing according to users settings for (int sty = 0;sty <= STYLE_MAX;sty++) { if (printColourMode == SC_PRINT_INVERTLIGHT) { vsPrint.styles[sty].fore.desired = InvertedLight(vsPrint.styles[sty].fore.desired); vsPrint.styles[sty].back.desired = InvertedLight(vsPrint.styles[sty].back.desired); } else if (printColourMode == SC_PRINT_BLACKONWHITE) { vsPrint.styles[sty].fore.desired = Colour(0, 0, 0); vsPrint.styles[sty].back.desired = Colour(0xff, 0xff, 0xff); } else if (printColourMode == SC_PRINT_COLOURONWHITE) { vsPrint.styles[sty].back.desired = Colour(0xff, 0xff, 0xff); } else if (printColourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { if (sty <= STYLE_DEFAULT) { vsPrint.styles[sty].back.desired = Colour(0xff, 0xff, 0xff); } } } // White background for the line numbers vsPrint.styles[STYLE_LINENUMBER].back.desired = Colour(0xff, 0xff, 0xff); vsPrint.Refresh(*surfaceMeasure); // Ensure colours are set up vsPrint.RefreshColourPalette(palette, true); vsPrint.RefreshColourPalette(palette, false); // Determining width must hapen after fonts have been realised in Refresh int lineNumberWidth = 0; if (lineNumberIndex >= 0) { lineNumberWidth = surface->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, "99999" lineNumberPrintSpace, 5 + strlen(lineNumberPrintSpace)); vsPrint.ms[lineNumberIndex].width = lineNumberWidth; } int linePrintStart = pdoc->LineFromPosition(pfr->chrg.cpMin); int linePrintLast = linePrintStart + (pfr->rc.bottom - pfr->rc.top) / vsPrint.lineHeight - 1; if (linePrintLast < linePrintStart) linePrintLast = linePrintStart; int linePrintMax = pdoc->LineFromPosition(pfr->chrg.cpMax - 1); if (linePrintLast > linePrintMax) linePrintLast = linePrintMax; //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n", // linePrintStart, linePrintLast, linePrintMax, pfr->rc.top, pfr->rc.bottom, vsPrint.lineHeight, // surfaceMeasure->Height(vsPrint.styles[STYLE_LINENUMBER].font)); int endPosPrint = pdoc->Length(); if (linePrintLast < pdoc->LinesTotal()) endPosPrint = pdoc->LineStart(linePrintLast + 1); // Ensure we are styled to where we are formatting. pdoc->EnsureStyledTo(endPosPrint); int xStart = vsPrint.fixedColumnWidth + pfr->rc.left + lineNumberWidth; int ypos = pfr->rc.top; int line = linePrintStart; if (draw) { // Otherwise just measuring while (line <= linePrintLast && ypos < pfr->rc.bottom) { // When printing, the hdc and hdcTarget may be the same, so // changing the state of surfaceMeasure may change the underlying // state of surface. Therefore, any cached state is discarded before // using each surface. surfaceMeasure->FlushCachedState(); // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. LineLayout ll; LayoutLine(line, surfaceMeasure, vsPrint, ll); ll.selStart = -1; ll.selEnd = -1; ll.containsCaret = false; // Need to fix this up so takes account of Unicode and DBCS ll.edgeColumn = theEdge; PRectangle rcLine; rcLine.left = pfr->rc.left + lineNumberWidth; rcLine.top = ypos; rcLine.right = pfr->rc.right; rcLine.bottom = ypos + vsPrint.lineHeight; if (lineNumberWidth) { char number[100]; sprintf(number, "%d" lineNumberPrintSpace, line + 1); PRectangle rcNumber = rcLine; rcNumber.right = rcNumber.left + lineNumberWidth; // Right justify rcNumber.left -= surface->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, number, strlen(number)); surface->DrawText(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, ypos + vsPrint.maxAscent, number, strlen(number), vsPrint.styles[STYLE_LINENUMBER].fore.allocated, vsPrint.styles[STYLE_LINENUMBER].back.allocated); } // Draw the line surface->FlushCachedState(); DrawLine(surface, vsPrint, line, line, xStart, rcLine, ll); ypos += vsPrint.lineHeight; line++; } } delete surface; delete surfaceMeasure; return endPosPrint; } // Empty method is overridden on GTK+ to show / hide scrollbars void Editor::ReconfigureScrollBars() {} void Editor::SetScrollBarsTo(PRectangle) { RefreshStyleData(); int nMax = cs.LinesDisplayed(); int nPage = cs.LinesDisplayed() - MaxScrollPos() + 1; bool modified = ModifyScrollBars(nMax, nPage); // TODO: ensure always showing as many lines as possible // May not be, if, for example, window made larger if (topLine > MaxScrollPos()) { SetTopLine(Platform::Clamp(topLine, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } if (modified) Redraw(); //Platform::DebugPrintf("end max = %d page = %d\n", nMax, nPage); } void Editor::SetScrollBars() { PRectangle rsClient = GetClientRectangle(); SetScrollBarsTo(rsClient); } void Editor::AddChar(char ch) { char s[2]; s[0] = ch; s[1] = '\0'; AddCharUTF(s, 1); } void Editor::AddCharUTF(char *s, unsigned int len) { bool wasSelection = currentPos != anchor; ClearSelection(); if (inOverstrike && !wasSelection) { if (currentPos < (pdoc->Length() - 1)) { if ((pdoc->CharAt(currentPos) != '\r') && (pdoc->CharAt(currentPos) != '\n')) { pdoc->DelChar(currentPos); } } } pdoc->InsertString(currentPos, s, len); SetEmptySelection(currentPos + len); EnsureCaretVisible(); // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); SetLastXChosen(); int byte = static_cast(s[0]); if ((byte < 0xC0) || (1 == len)) { // Handles UTF-8 characters between 0x01 and 0x7F and single byte // characters when not in UTF-8 mode. // Also treats \0 and naked trail bytes 0x80 to 0xBF as valid // characters representing themselves. } else { // Unroll 1 to 3 byte UTF-8 sequences. See reference data at: // http://www.cl.cam.ac.uk/~mgk25/unicode.html // http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt if (byte < 0xE0) { int byte2 = static_cast(s[1]); if ((byte2 & 0xC0) == 0x80) { // Two-byte-character lead-byte followed by a trail-byte. byte = (((byte & 0x1F) << 6) | (byte2 & 0x3F)); } // A two-byte-character lead-byte not followed by trail-byte // represents itself. } else if (byte < 0xF0) { int byte2 = static_cast(s[1]); int byte3 = static_cast(s[2]); if (((byte2 & 0xC0) == 0x80) && ((byte3 & 0xC0) == 0x80)) { // Three-byte-character lead byte followed by two trail bytes. byte = (((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F)); } // A three-byte-character lead-byte not followed by two trail-bytes // represents itself. } } NotifyChar(byte); } void Editor::ClearSelection() { if (selType == selRectangle) { pdoc->BeginUndoAction(); int lineStart = pdoc->LineFromPosition(SelectionStart()); int lineEnd = pdoc->LineFromPosition(SelectionEnd()); int startPos = SelectionStart(); for (int line = lineEnd; line >= lineStart; line--) { startPos = SelectionStart(line); unsigned int chars = SelectionEnd(line) - startPos; if (0 != chars) { pdoc->DeleteChars(startPos, chars); } } SetEmptySelection(startPos); pdoc->EndUndoAction(); selType = selStream; } else { int startPos = SelectionStart(); unsigned int chars = SelectionEnd() - startPos; SetEmptySelection(startPos); if (0 != chars) { pdoc->BeginUndoAction(); pdoc->DeleteChars(startPos, chars); pdoc->EndUndoAction(); } } } void Editor::ClearAll() { pdoc->BeginUndoAction(); if (0 != pdoc->Length()) { pdoc->DeleteChars(0, pdoc->Length()); } cs.Clear(); pdoc->EndUndoAction(); anchor = 0; currentPos = 0; SetTopLine(0); SetVerticalScrollPos(); } void Editor::ClearDocumentStyle() { pdoc->StartStyling(0, '\377'); pdoc->SetStyleFor(pdoc->Length(), 0); cs.ShowAll(); pdoc->ClearLevels(); } void Editor::Cut() { if (!pdoc->IsReadOnly()) { Copy(); ClearSelection(); } } void Editor::PasteRectangular(int pos, const char *ptr, int len) { if (pdoc->IsReadOnly()) { return; } currentPos = pos; int insertPos = currentPos; int xInsert = XFromPosition(currentPos); int line = pdoc->LineFromPosition(currentPos); bool prevCr = false; pdoc->BeginUndoAction(); for (int i = 0; i < len; i++) { if ((ptr[i] == '\r') || (ptr[i] == '\n')) { if ((ptr[i] == '\r') || (!prevCr)) line++; if (line >= pdoc->LinesTotal()) { if (pdoc->eolMode != SC_EOL_LF) pdoc->InsertChar(pdoc->Length(), '\r'); if (pdoc->eolMode != SC_EOL_CR) pdoc->InsertChar(pdoc->Length(), '\n'); } // Pad the end of lines with spaces if required currentPos = PositionFromLineX(line, xInsert); if ((XFromPosition(currentPos) < xInsert) && (i + 1 < len)) { for (int i = 0; i < xInsert - XFromPosition(currentPos); i++) { pdoc->InsertChar(currentPos, ' '); currentPos++; } insertPos = currentPos; } prevCr = ptr[i] == '\r'; } else { pdoc->InsertString(currentPos, ptr + i, 1); currentPos++; insertPos = currentPos; prevCr = false; } } pdoc->EndUndoAction(); SetEmptySelection(insertPos); } bool Editor::CanPaste() { return !pdoc->IsReadOnly(); } void Editor::Clear() { if (currentPos == anchor) { DelChar(); } else { ClearSelection(); } SetEmptySelection(currentPos); } void Editor::SelectAll() { SetSelection(0, pdoc->Length()); Redraw(); } void Editor::Undo() { if (pdoc->CanUndo()) { InvalidateCaret(); int newPos = pdoc->Undo(); SetEmptySelection(newPos); EnsureCaretVisible(); } } void Editor::Redo() { if (pdoc->CanRedo()) { int newPos = pdoc->Redo(); SetEmptySelection(newPos); EnsureCaretVisible(); } } void Editor::DelChar() { pdoc->DelChar(currentPos); // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); } void Editor::DelCharBack() { if (currentPos == anchor) { int lineCurrentPos = pdoc->LineFromPosition(currentPos); if (pdoc->GetColumn(currentPos) <= pdoc->GetLineIndentation(lineCurrentPos) && pdoc->GetColumn(currentPos) > 0 && pdoc->backspaceUnindents) { pdoc->BeginUndoAction(); int indentation = pdoc->GetLineIndentation(lineCurrentPos); int indentationStep = (pdoc->indentInChars ? pdoc->indentInChars : pdoc->tabInChars); if (indentation % indentationStep == 0) { pdoc->SetLineIndentation(lineCurrentPos, indentation - indentationStep); } else { pdoc->SetLineIndentation(lineCurrentPos, indentation - (indentation % indentationStep)); } SetEmptySelection(pdoc->GetLineIndentPosition(lineCurrentPos)); pdoc->EndUndoAction(); } else { int newPos = pdoc->DelCharBack(currentPos); SetEmptySelection(newPos); } } else { ClearSelection(); SetEmptySelection(currentPos); } // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); } void Editor::NotifyFocus(bool) {} void Editor::NotifyStyleToNeeded(int endStyleNeeded) { SCNotification scn; scn.nmhdr.code = SCN_STYLENEEDED; scn.position = endStyleNeeded; NotifyParent(scn); } void Editor::NotifyStyleNeeded(Document*, void *, int endStyleNeeded) { NotifyStyleToNeeded(endStyleNeeded); } void Editor::NotifyChar(int ch) { SCNotification scn; scn.nmhdr.code = SCN_CHARADDED; scn.ch = ch; NotifyParent(scn); #ifdef MACRO_SUPPORT if (recordingMacro) { char txt[2]; txt[0] = static_cast(ch); txt[1] = '\0'; NotifyMacroRecord(SCI_REPLACESEL, 0, reinterpret_cast(txt)); } #endif } void Editor::NotifySavePoint(bool isSavePoint) { SCNotification scn; if (isSavePoint) { scn.nmhdr.code = SCN_SAVEPOINTREACHED; } else { scn.nmhdr.code = SCN_SAVEPOINTLEFT; } NotifyParent(scn); } void Editor::NotifyModifyAttempt() { SCNotification scn; scn.nmhdr.code = SCN_MODIFYATTEMPTRO; NotifyParent(scn); } void Editor::NotifyDoubleClick(Point, bool) { SCNotification scn; scn.nmhdr.code = SCN_DOUBLECLICK; NotifyParent(scn); } void Editor::NotifyUpdateUI() { SCNotification scn; scn.nmhdr.code = SCN_UPDATEUI; NotifyParent(scn); } void Editor::NotifyPainted() { SCNotification scn; scn.nmhdr.code = SCN_PAINTED; NotifyParent(scn); } bool Editor::NotifyMarginClick(Point pt, bool shift, bool ctrl, bool alt) { int marginClicked = -1; int x = 0; for (int margin = 0; margin < ViewStyle::margins; margin++) { if ((pt.x > x) && (pt.x < x + vs.ms[margin].width)) marginClicked = margin; x += vs.ms[margin].width; } if ((marginClicked >= 0) && vs.ms[marginClicked].sensitive) { SCNotification scn; scn.nmhdr.code = SCN_MARGINCLICK; scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); scn.position = pdoc->LineStart(LineFromLocation(pt)); scn.margin = marginClicked; NotifyParent(scn); return true; } else { return false; } } void Editor::NotifyNeedShown(int pos, int len) { SCNotification scn; scn.nmhdr.code = SCN_NEEDSHOWN; scn.position = pos; scn.length = len; NotifyParent(scn); } void Editor::NotifyDwelling(Point pt, bool state) { SCNotification scn; scn.nmhdr.code = state ? SCN_DWELLSTART : SCN_DWELLEND; scn.position = PositionFromLocationClose(pt); scn.x = pt.x; scn.y = pt.y; NotifyParent(scn); } // Notifications from document void Editor::NotifyModifyAttempt(Document*, void *) { //Platform::DebugPrintf("** Modify Attempt\n"); NotifyModifyAttempt(); } void Editor::NotifyMove(int position) { SCNotification scn; scn.nmhdr.code = SCN_POSCHANGED; scn.position = position; NotifyParent(scn); } void Editor::NotifySavePoint(Document*, void *, bool atSavePoint) { //Platform::DebugPrintf("** Save Point %s\n", atSavePoint ? "On" : "Off"); NotifySavePoint(atSavePoint); } void Editor::NotifyModified(Document*, DocModification mh, void *) { needUpdateUI = true; if (paintState == painting) { CheckForChangeOutsidePaint(Range(mh.position, mh.position + mh.length)); } else if (paintState HTTP/1.1 200 OK Connection: keep-alive Connection: keep-alive Content-Disposition: inline; filename="Editor.cxx" Content-Disposition: inline; filename="Editor.cxx" Content-Length: 139767 Content-Length: 139767 Content-Security-Policy: default-src 'none' Content-Security-Policy: default-src 'none' Content-Type: text/plain; charset=UTF-8 Content-Type: text/plain; charset=UTF-8 Date: Sun, 19 Oct 2025 02:03:38 UTC ETag: "5b72ac579d28886b60c18035c20d7f1ddd955c85" ETag: "5b72ac579d28886b60c18035c20d7f1ddd955c85" Expires: Wed, 17 Oct 2035 02:03:38 GMT Expires: Wed, 17 Oct 2035 02:03:38 GMT Last-Modified: Sun, 19 Oct 2025 02:03:38 GMT Last-Modified: Sun, 19 Oct 2025 02:03:38 GMT Server: OpenBSD httpd Server: OpenBSD httpd X-Content-Type-Options: nosniff X-Content-Type-Options: nosniff // Scintilla source code edit control /** @file Editor.cxx ** Main code for the edit control. **/ // Copyright 1998-2001 by Neil Hodgson // The License.txt file describes the conditions under which this software may be distributed. #include #include #include #include #include "Platform.h" #include "Scintilla.h" #if PLAT_WX || PLAT_GTK #include "WinDefs.h" #endif #include "ContractionState.h" #include "SVector.h" #include "CellBuffer.h" #include "KeyMap.h" #include "Indicator.h" #include "LineMarker.h" #include "Style.h" #include "ViewStyle.h" #include "Document.h" #include "Editor.h" Caret::Caret() : active(false), on(false), period(500) {} Timer::Timer() : ticking(false), ticksToWait(0), tickerID(0) {} Editor::Editor() { ctrlID = 0; stylesValid = false; printMagnification = 0; printColourMode = SC_PRINT_NORMAL; cursorMode = SC_CURSORNORMAL; hasFocus = false; hideSelection = false; inOverstrike = false; errorStatus = 0; mouseDownCaptures = true; bufferedDraw = true; lastClickTime = 0; dwellDelay = SC_TIME_FOREVER; ticksToDwell = SC_TIME_FOREVER; dwelling = false; ptMouseLast.x = 0; ptMouseLast.y = 0; firstExpose = true; inDragDrop = false; dropWentOutside = false; posDrag = invalidPosition; posDrop = invalidPosition; selectionType = selChar; lastXChosen = 0; lineAnchor = 0; originalAnchorPos = 0; dragChars = 0; lenDrag = 0; dragIsRectangle = false; selType = selStream; xStartSelect = 0; xEndSelect = 0; primarySelection = true; caretPolicy = CARET_SLOP; caretSlop = 0; visiblePolicy = VISIBLE_SLOP; visibleSlop = 0; searchAnchor = 0; ucWheelScrollLines = 0; cWheelDelta = 0; //wheel delta from roll xOffset = 0; xCaretMargin = 50; horizontalScrollBarVisible = true; currentPos = 0; anchor = 0; targetStart = 0; targetEnd = 0; searchFlags = 0; topLine = 0; posTopLine = 0; needUpdateUI = true; braces[0] = invalidPosition; braces[1] = invalidPosition; bracesMatchStyle = STYLE_BRACEBAD; highlightGuideColumn = 0; theEdge = 0; paintState = notPainting; modEventMask = SC_MODEVENTMASKALL; displayPopupMenu = true; pdoc = new Document(); pdoc ->AddRef(); pdoc->AddWatcher(this, 0); #ifdef MACRO_SUPPORT recordingMacro = 0; #endif foldFlags = 0; } Editor::~Editor() { pdoc->RemoveWatcher(this, 0); pdoc->Release(); pdoc = 0; DropGraphics(); delete []dragChars; dragChars = 0; lenDrag = 0; } void Editor::Finalise() { CancelModes(); } void Editor::DropGraphics() { pixmapLine.Release(); pixmapSelMargin.Release(); pixmapSelPattern.Release(); pixmapIndentGuide.Release(); } void Editor::InvalidateStyleData() { stylesValid = false; palette.Release(); DropGraphics(); } void Editor::InvalidateStyleRedraw() { InvalidateStyleData(); Redraw(); } void Editor::RefreshColourPalette(Palette &pal, bool want) { vs.RefreshColourPalette(pal, want); } void Editor::RefreshStyleData() { if (!stylesValid) { stylesValid = true; Surface surface; surface.Init(); vs.Refresh(surface); RefreshColourPalette(palette, true); palette.Allocate(wMain); RefreshColourPalette(palette, false); SetScrollBars(); } } PRectangle Editor::GetClientRectangle() { return wMain.GetClientPosition(); } PRectangle Editor::GetTextRectangle() { PRectangle rc = GetClientRectangle(); rc.left += vs.fixedColumnWidth; rc.right -= vs.rightMarginWidth; return rc; } int Editor::LinesOnScreen() { PRectangle rcClient = GetClientRectangle(); int htClient = rcClient.bottom - rcClient.top; //Platform::DebugPrintf("lines on screen = %d\n", htClient / lineHeight + 1); return htClient / vs.lineHeight; } int Editor::LinesToScroll() { int retVal = LinesOnScreen() - 1; if (retVal < 1) return 1; else return retVal; } int Editor::MaxScrollPos() { //Platform::DebugPrintf("Lines %d screen = %d maxScroll = %d\n", //LinesTotal(), LinesOnScreen(), LinesTotal() - LinesOnScreen() + 1); int retVal = cs.LinesDisplayed() - LinesOnScreen(); if (retVal < 0) return 0; else return retVal; } static inline bool IsControlCharacter(char ch) { // iscntrl returns true for lots of chars > 127 which are displayable return ch >= 0 && ch < ' '; } const char *ControlCharacterString(unsigned char ch) { const char *reps[] = { "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL", "BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI", "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB", "CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US" }; if (ch < (sizeof(reps) / sizeof(reps[0]))) { return reps[ch]; } else { return "BAD"; } } Point Editor::LocationFromPosition(int pos) { Point pt; RefreshStyleData(); if (pos == INVALID_POSITION) return pt; int line = pdoc->LineFromPosition(pos); int lineVisible = cs.DisplayFromDoc(line); //Platform::DebugPrintf("line=%d\n", line); Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); pt.y = (lineVisible - topLine) * vs.lineHeight; // + half a lineheight? unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); if ((pos - posLineStart) > LineLayout::maxLineLength) { // very long line so put x at arbitrary large position pt.x = ll.positions[LineLayout::maxLineLength] + vs.fixedColumnWidth - xOffset; } else { pt.x = ll.positions[pos - posLineStart] + vs.fixedColumnWidth - xOffset; } return pt; } int Editor::XFromPosition(int pos) { Point pt = LocationFromPosition(pos); return pt.x - vs.fixedColumnWidth + xOffset; } int Editor::LineFromLocation(Point pt) { return cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); } void Editor::SetTopLine(int topLineNew) { topLine = topLineNew; posTopLine = pdoc->LineStart(topLine); } int Editor::PositionFromLocation(Point pt) { RefreshStyleData(); pt.x = pt.x - vs.fixedColumnWidth + xOffset; int line = cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); if (pt.y < 0) { // Division rounds towards 0 line = cs.DocFromDisplay((pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine); } if (line < 0) return 0; if (line >= pdoc->LinesTotal()) return pdoc->Length(); Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); for (int i = 0; i < ll.numCharsInLine; i++) { if (pt.x < ((ll.positions[i] + ll.positions[i + 1]) / 2) || ll.chars[i] == '\r' || ll.chars[i] == '\n') { return i + posLineStart; } } return ll.numCharsInLine + posLineStart; } // Like PositionFromLocation but INVALID_POSITION returned when not near any text. int Editor::PositionFromLocationClose(Point pt) { RefreshStyleData(); PRectangle rcClient = GetTextRectangle(); if (!rcClient.Contains(pt)) return INVALID_POSITION; if (pt.x < vs.fixedColumnWidth) return INVALID_POSITION; if (pt.y < 0) return INVALID_POSITION; pt.x = pt.x - vs.fixedColumnWidth + xOffset; int line = cs.DocFromDisplay(pt.y / vs.lineHeight + topLine); if (pt.y < 0) { // Division rounds towards 0 line = cs.DocFromDisplay((pt.y - (vs.lineHeight - 1)) / vs.lineHeight + topLine); } if (line < 0) return INVALID_POSITION; if (line >= pdoc->LinesTotal()) return INVALID_POSITION; Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); for (int i = 0; i < ll.numCharsInLine; i++) { if (pt.x < ((ll.positions[i] + ll.positions[i + 1]) / 2) || ll.chars[i] == '\r' || ll.chars[i] == '\n') { return i + posLineStart; } } return INVALID_POSITION; } int Editor::PositionFromLineX(int line, int x) { RefreshStyleData(); if (line >= pdoc->LinesTotal()) return pdoc->Length(); //Platform::DebugPrintf("Position of (%d,%d) line = %d top=%d\n", pt.x, pt.y, line, topLine); Surface surface; surface.Init(); surface.SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); unsigned int posLineStart = pdoc->LineStart(line); LineLayout ll; LayoutLine(line, &surface, vs, ll); for (int i = 0; i < ll.numCharsInLine; i++) { if (x < ((ll.positions[i] + ll.positions[i + 1]) / 2) || ll.chars[i] == '\r' || ll.chars[i] == '\n') { return i + posLineStart; } } return ll.numCharsInLine + posLineStart; } void Editor::RedrawRect(PRectangle rc) { //Platform::DebugPrintf("Redraw %0d,%0d - %0d,%0d\n", rc.left, rc.top, rc.right, rc.bottom); // Clip the redraw rectangle into the client area PRectangle rcClient = GetClientRectangle(); if (rc.top < rcClient.top) rc.top = rcClient.top; if (rc.bottom > rcClient.bottom) rc.bottom = rcClient.bottom; if (rc.left < rcClient.left) rc.left = rcClient.left; if (rc.right > rcClient.right) rc.right = rcClient.right; if ((rc.bottom > rc.top) && (rc.right > rc.left)) { wMain.InvalidateRectangle(rc); } } void Editor::Redraw() { //Platform::DebugPrintf("Redraw all\n"); wMain.InvalidateAll(); } void Editor::RedrawSelMargin() { if (vs.maskInLine) { Redraw(); } else { PRectangle rcSelMargin = GetClientRectangle(); rcSelMargin.right = vs.fixedColumnWidth; wMain.InvalidateRectangle(rcSelMargin); } } PRectangle Editor::RectangleFromRange(int start, int end) { int minPos = start; if (minPos > end) minPos = end; int maxPos = start; if (maxPos < end) maxPos = end; int minLine = cs.DisplayFromDoc(pdoc->LineFromPosition(minPos)); int maxLine = cs.DisplayFromDoc(pdoc->LineFromPosition(maxPos)); PRectangle rcClient = GetTextRectangle(); PRectangle rc; rc.left = vs.fixedColumnWidth; rc.top = (minLine - topLine) * vs.lineHeight; if (rc.top < 0) rc.top = 0; rc.right = rcClient.right; rc.bottom = (maxLine - topLine + 1) * vs.lineHeight; // Ensure PRectangle is within 16 bit space rc.top = Platform::Clamp(rc.top, -32000, 32000); rc.bottom = Platform::Clamp(rc.bottom, -32000, 32000); return rc; } void Editor::InvalidateRange(int start, int end) { RedrawRect(RectangleFromRange(start, end)); } int Editor::CurrentPosition() { return currentPos; } bool Editor::SelectionEmpty() { return anchor == currentPos; } int Editor::SelectionStart(int line) { if ((line == -1) || (selType == selStream)) { return Platform::Minimum(currentPos, anchor); } else { // selType == selRectangle int selStart = SelectionStart(); int selEnd = SelectionEnd(); int lineStart = pdoc->LineFromPosition(selStart); int lineEnd = pdoc->LineFromPosition(selEnd); if (line < lineStart || line > lineEnd) { return -1; } else { int minX = Platform::Minimum(xStartSelect, xEndSelect); return PositionFromLineX(line, minX); } } } int Editor::SelectionEnd(int line) { if ((line == -1) || (selType == selStream)) { return Platform::Maximum(currentPos, anchor); } else { // selType == selRectangle int selStart = SelectionStart(); int selEnd = SelectionEnd(); int lineStart = pdoc->LineFromPosition(selStart); int lineEnd = pdoc->LineFromPosition(selEnd); if (line < lineStart || line > lineEnd) { return -1; } else { int maxX = Platform::Maximum(xStartSelect, xEndSelect); // measure line and return character closest to minx return PositionFromLineX(line, maxX); } } } void Editor::SetSelection(int currentPos_, int anchor_) { currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); anchor_ = pdoc->ClampPositionIntoDocument(anchor_); if ((currentPos != currentPos_) || (anchor != anchor_)) { int firstAffected = anchor; if (firstAffected > currentPos) firstAffected = currentPos; if (firstAffected > anchor_) firstAffected = anchor_; if (firstAffected > currentPos_) firstAffected = currentPos_; int lastAffected = anchor; if (lastAffected < currentPos) lastAffected = currentPos; if (lastAffected < anchor_) lastAffected = anchor_; if (lastAffected < (currentPos_ + 1)) // +1 ensures caret repainted lastAffected = (currentPos_ + 1); currentPos = currentPos_; anchor = anchor_; needUpdateUI = true; InvalidateRange(firstAffected, lastAffected); } ClaimSelection(); } void Editor::SetSelection(int currentPos_) { currentPos_ = pdoc->ClampPositionIntoDocument(currentPos_); if (currentPos != currentPos_) { int firstAffected = anchor; if (firstAffected > currentPos) firstAffected = currentPos; if (firstAffected > currentPos_) firstAffected = currentPos_; int lastAffected = anchor; if (lastAffected < currentPos) lastAffected = currentPos; if (lastAffected < (currentPos_ + 1)) // +1 ensures caret repainted lastAffected = (currentPos_ + 1); currentPos = currentPos_; needUpdateUI = true; InvalidateRange(firstAffected, lastAffected); } ClaimSelection(); } void Editor::SetEmptySelection(int currentPos_) { selType = selStream; SetSelection(currentPos_, currentPos_); } int Editor::MovePositionOutsideChar(int pos, int moveDir, bool checkLineEnd) { // Asks document to find a good position and then moves out of any invisible positions pos = pdoc->MovePositionOutsideChar(pos, moveDir, checkLineEnd); int mask = pdoc->stylingBitsMask; if (moveDir > 0) { while ((pos < pdoc->Length()) && (!vs.styles[pdoc->StyleAt(pos - 1) & mask].visible)) pos++; } else { while ((pos > 0) && (!vs.styles[pdoc->StyleAt(pos - 1) & mask].visible)) pos--; } return pos; } int Editor::MovePositionTo(int newPos, bool extend) { int delta = newPos - currentPos; newPos = pdoc->ClampPositionIntoDocument(newPos); newPos = MovePositionOutsideChar(newPos, delta); if (extend) { SetSelection(newPos); } else { SetEmptySelection(newPos); } EnsureCaretVisible(); ShowCaretAtCurrentPosition(); NotifyMove(newPos); return 0; } int Editor::MovePositionSoVisible(int pos, int moveDir) { pos = pdoc->ClampPositionIntoDocument(pos); pos = MovePositionOutsideChar(pos, moveDir); int lineDoc = pdoc->LineFromPosition(pos); if (cs.GetVisible(lineDoc)) { return pos; } else { int lineDisplay = cs.DisplayFromDoc(lineDoc); if (moveDir > 0) { lineDisplay = Platform::Clamp(lineDisplay + 1, 0, cs.LinesDisplayed()); return pdoc->LineStart(cs.DocFromDisplay(lineDisplay)); } else { // lineDisplay is already line before fold as lines in fold use display line of line before fold lineDisplay = Platform::Clamp(lineDisplay, 0, cs.LinesDisplayed()); return pdoc->LineEndPosition(pdoc->LineStart(cs.DocFromDisplay(lineDisplay))); } } } // Choose the x position that the caret will try to stick to as it is moves up and down void Editor::SetLastXChosen() { Point pt = LocationFromPosition(currentPos); lastXChosen = pt.x; } void Editor::ScrollTo(int line) { int topLineNew = Platform::Clamp(line, 0, MaxScrollPos()); if (topLineNew != topLine) { // Try to optimise small scrolls int linesToMove = topLine - topLineNew; SetTopLine(topLineNew); ShowCaretAtCurrentPosition(); // Perform redraw rather than scroll if many lines would be redrawn anyway. if (abs(linesToMove) <= 10) { ScrollText(linesToMove); } else { Redraw(); } SetVerticalScrollPos(); } } void Editor::ScrollText(int /* linesToMove */) { //Platform::DebugPrintf("Editor::ScrollText %d\n", linesToMove); Redraw(); } void Editor::HorizontalScrollTo(int xPos) { //Platform::DebugPrintf("HorizontalScroll %d\n", xPos); xOffset = xPos; if (xOffset < 0) xOffset = 0; SetHorizontalScrollPos(); RedrawRect(GetClientRectangle()); } void Editor::MoveCaretInsideView() { PRectangle rcClient = GetTextRectangle(); Point pt = LocationFromPosition(currentPos); if (pt.y < rcClient.top) { MovePositionTo(PositionFromLocation( Point(lastXChosen, rcClient.top))); } else if ((pt.y + vs.lineHeight - 1) > rcClient.bottom) { int yOfLastLineFullyDisplayed = rcClient.top + (LinesOnScreen() - 1) * vs.lineHeight; MovePositionTo(PositionFromLocation( Point(lastXChosen, rcClient.top + yOfLastLineFullyDisplayed))); } } void Editor::EnsureCaretVisible(bool useMargin, bool vert, bool horiz) { //Platform::DebugPrintf("EnsureCaretVisible %d %s\n", xOffset, useMargin ? " margin" : " "); PRectangle rcClient = GetTextRectangle(); //int rcClientFullWidth = rcClient.Width(); int posCaret = currentPos; if (posDrag >= 0) posCaret = posDrag; Point pt = LocationFromPosition(posCaret); Point ptEOL = LocationFromPosition(pdoc->LineEndPosition(posCaret)); Point ptBottomCaret = pt; int lineCaret = cs.DisplayFromDoc(pdoc->LineFromPosition(posCaret)); ptBottomCaret.y += vs.lineHeight - 1; // Ensure the caret is reasonably visible in context: // xMargin must equal to xCaretMargin, with a minimum of 2 and a maximum of // slightly less than half the width of the text area. int xMargin = Platform::Clamp(xCaretMargin, 2, Platform::Maximum(rcClient.Width() - 10, 4) / 2); if (!useMargin) xMargin = 2; // If we scroll the display, we use a minimum amount of xMargin. int offsetLeft = rcClient.left + xMargin; int offsetRight = rcClient.right - xMargin; // If we are in XJUMPS mode, then when the margin is reached, the // offset jumps so that it won't need to move agin for a while. if (!(caretPolicy & CARET_XJUMPS)) { rcClient.left = offsetLeft; rcClient.right = offsetRight; } // Vertical positioning if (vert && (!rcClient.Contains(pt) || !rcClient.Contains(ptBottomCaret) || (caretPolicy & CARET_STRICT))) { //Platform::DebugPrintf("EnsureCaretVisible move, (%d,%d)(%d,%d)\n", pt.x, pt.y, rcClient.left, rcClient.right); // It should be possible to scroll the window to show the caret, // but this fails to remove the caret on GTK+ if (caretPolicy & CARET_SLOP) { if ((topLine > lineCaret) || ((caretPolicy & CARET_STRICT) && (topLine + caretSlop > lineCaret))) { SetTopLine(Platform::Clamp(lineCaret - caretSlop, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } else if ((lineCaret > topLine + LinesOnScreen() - 1) || ((caretPolicy & CARET_STRICT) && (lineCaret > topLine + LinesOnScreen() - 1 - caretSlop))) { SetTopLine(Platform::Clamp(lineCaret - LinesOnScreen() + 1 + caretSlop, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } } else { if ((topLine > lineCaret) || (lineCaret > topLine + LinesOnScreen() - 1) || (caretPolicy & CARET_STRICT)) { SetTopLine(Platform::Clamp(lineCaret - LinesOnScreen() / 2 + 1, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } } } // Horizontal positioning if (horiz) { int xOffsetNew = xOffset; if (pt.x < rcClient.left) { xOffsetNew = xOffset - (offsetLeft - pt.x); } else if ((!(caretPolicy & CARET_XEVEN) && ((xOffset > 0) && useMargin)) || pt.x >= rcClient.right) { xOffsetNew = xOffset + (pt.x - offsetRight); int xOffsetEOL = xOffset + (ptEOL.x - offsetRight) - xMargin + 2; //Platform::DebugPrintf("Margin %d %d\n", xOffsetNew, xOffsetEOL); // Ensure don't scroll out into empty space if (xOffsetNew > xOffsetEOL) xOffsetNew = xOffsetEOL; } if (xOffsetNew < 0) xOffsetNew = 0; if (xOffset != xOffsetNew) { xOffset = xOffsetNew; SetHorizontalScrollPos(); Redraw(); } } } void Editor::ShowCaretAtCurrentPosition() { if (!hasFocus) { caret.active = false; caret.on = false; return; } caret.active = true; caret.on = true; SetTicking(true); } void Editor::DropCaret() { caret.active = false; InvalidateCaret(); } void Editor::InvalidateCaret() { if (posDrag >= 0) InvalidateRange(posDrag, posDrag + 1); else InvalidateRange(currentPos, currentPos + 1); } int Editor::SubstituteMarkerIfEmpty(int markerCheck, int markerDefault) { if (vs.markers[markerCheck].markType == SC_MARK_EMPTY) return markerDefault; return markerCheck; } void Editor::PaintSelMargin(Surface *surfWindow, PRectangle &rc) { if (vs.fixedColumnWidth == 0) return; PRectangle rcMargin = GetClientRectangle(); rcMargin.right = vs.fixedColumnWidth; if (!rc.Intersects(rcMargin)) return; Surface *surface; if (bufferedDraw) { surface = &pixmapSelMargin; } else { surface = surfWindow; } PRectangle rcSelMargin = rcMargin; rcSelMargin.right = rcMargin.left; for (int margin = 0; margin < vs.margins; margin++) { if (vs.ms[margin].width > 0) { rcSelMargin.left = rcSelMargin.right; rcSelMargin.right = rcSelMargin.left + vs.ms[margin].width; if (vs.ms[margin].symbol) { /* alternate scheme: if (vs.ms[margin].mask & SC_MASK_FOLDERS) surface->FillRectangle(rcSelMargin, vs.styles[STYLE_DEFAULT].back.allocated); else // Required because of special way brush is created for selection margin surface->FillRectangle(rcSelMargin, pixmapSelPattern); */ if (vs.ms[margin].mask & SC_MASK_FOLDERS) // Required because of special way brush is created for selection margin surface->FillRectangle(rcSelMargin, pixmapSelPattern); else surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back.allocated); } else { surface->FillRectangle(rcSelMargin, vs.styles[STYLE_LINENUMBER].back.allocated); } int visibleLine = topLine; int line = cs.DocFromDisplay(visibleLine); int yposScreen = 0; // Work out whether the top line is whitespace located after a // lessening of fold level which implies a 'fold tail' but which should not // be displayed until the last of a sequence of whitespace. bool needWhiteClosure = false; int level = pdoc->GetLevel(line); if (level & SC_FOLDLEVELWHITEFLAG) { int lineBack = line; int levelPrev = level; while ((lineBack > 0) && (levelPrev & SC_FOLDLEVELWHITEFLAG)) { lineBack--; levelPrev = pdoc->GetLevel(lineBack); } if (!(levelPrev & SC_FOLDLEVELHEADERFLAG)) { if ((level & SC_FOLDLEVELNUMBERMASK) < (levelPrev & SC_FOLDLEVELNUMBERMASK)) needWhiteClosure = true; } } // Old code does not know about new markers needed to distinguish all cases int folderOpenMid = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEROPENMID, SC_MARKNUM_FOLDEROPEN); int folderEnd = SubstituteMarkerIfEmpty(SC_MARKNUM_FOLDEREND, SC_MARKNUM_FOLDER); while ((visibleLine < cs.LinesDisplayed()) && yposScreen < rcMargin.bottom) { // Decide which fold indicator should be displayed level = pdoc->GetLevel(line); int levelNext = pdoc->GetLevel(line+1); int marks = pdoc->GetMark(line); int levelNum = level & SC_FOLDLEVELNUMBERMASK; int levelNextNum = levelNext & SC_FOLDLEVELNUMBERMASK; if (level & SC_FOLDLEVELHEADERFLAG) { if (cs.GetExpanded(line)) { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDEROPEN; else marks |= 1 << folderOpenMid; } else { if (levelNum == SC_FOLDLEVELBASE) marks |= 1 << SC_MARKNUM_FOLDER; else marks |= 1 << folderEnd; } needWhiteClosure = false; } else if (level & SC_FOLDLEVELWHITEFLAG) { if (needWhiteClosure) { if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; } else if (levelNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; needWhiteClosure = false; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; needWhiteClosure = false; } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } } else if (levelNum > SC_FOLDLEVELBASE) { if (levelNextNum < levelNum) { needWhiteClosure = false; if (levelNext & SC_FOLDLEVELWHITEFLAG) { marks |= 1 << SC_MARKNUM_FOLDERSUB; needWhiteClosure = true; } else if (levelNextNum > SC_FOLDLEVELBASE) { marks |= 1 << SC_MARKNUM_FOLDERMIDTAIL; } else { marks |= 1 << SC_MARKNUM_FOLDERTAIL; } } else { marks |= 1 << SC_MARKNUM_FOLDERSUB; } } marks &= vs.ms[margin].mask; PRectangle rcMarker = rcSelMargin; rcMarker.top = yposScreen; rcMarker.bottom = yposScreen + vs.lineHeight; if (!vs.ms[margin].symbol) { char number[100]; number[0] = '\0'; sprintf(number, "%d", line + 1); if (foldFlags & 64) sprintf(number, "%X", pdoc->GetLevel(line)); PRectangle rcNumber = rcMarker; // Right justify int width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, strlen(number)); int xpos = rcNumber.right - width - 3; rcNumber.left = xpos; if ((visibleLine < cs.LinesDisplayed()) && cs.GetVisible(line)) { surface->DrawText(rcNumber, vs.styles[STYLE_LINENUMBER].font, rcNumber.top + vs.maxAscent, number, strlen(number), vs.styles[STYLE_LINENUMBER].fore.allocated, vs.styles[STYLE_LINENUMBER].back.allocated); } } if (marks) { for (int markBit = 0; (markBit < 32) && marks; markBit++) { if (marks & 1) { vs.markers[markBit].Draw(surface, rcMarker); } marks >>= 1; } } visibleLine++; line = cs.DocFromDisplay(visibleLine); yposScreen += vs.lineHeight; } } } PRectangle rcBlankMargin = rcMargin; rcBlankMargin.left = rcSelMargin.right; surface->FillRectangle(rcBlankMargin, vs.styles[STYLE_DEFAULT].back.allocated); if (bufferedDraw) { surfWindow->Copy(rcMargin, Point(), pixmapSelMargin); } } void DrawTabArrow(Surface *surface, PRectangle rcTab, int ymid) { int ydiff = (rcTab.bottom - rcTab.top) / 2; int xhead = rcTab.right - 1 - ydiff; if ((rcTab.left + 2) < (rcTab.right - 1)) surface->MoveTo(rcTab.left + 2, ymid); else surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid - ydiff); surface->MoveTo(rcTab.right - 1, ymid); surface->LineTo(xhead, ymid + ydiff); } /** * Fill in the LineLayout data for the given line. * Copy the given @a line and its styles from the document into local arrays. * Also determine the x position at which each character starts. */ void Editor::LayoutLine(int line, Surface *surface, ViewStyle &vstyle, LineLayout &ll) { int numCharsInLine = 0; int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); Font &ctrlCharsFont = vstyle.styles[STYLE_CONTROLCHAR].font; char styleByte = 0; int styleMask = pdoc->stylingBitsMask; ll.xHighlightGuide = 0; // If the line is very long, limit the treatment to a length that should fit in the viewport if (posLineEnd > (posLineStart + LineLayout::maxLineLength)) { posLineEnd = posLineStart + LineLayout::maxLineLength; } // Fill base line layout for (int charInDoc = posLineStart; charInDoc < posLineEnd; charInDoc++) { char chDoc = pdoc->CharAt(charInDoc); styleByte = pdoc->StyleAt(charInDoc); if (vstyle.viewEOL || ((chDoc != '\r') && (chDoc != '\n'))) { ll.chars[numCharsInLine] = chDoc; ll.styles[numCharsInLine] = static_cast(styleByte & styleMask); ll.indicators[numCharsInLine] = static_cast(styleByte & ~styleMask); if (vstyle.styles[ll.styles[numCharsInLine]].caseForce == Style::caseUpper) ll.chars[numCharsInLine] = static_cast(toupper(chDoc)); else if (vstyle.styles[ll.styles[numCharsInLine]].caseForce == Style::caseLower) ll.chars[numCharsInLine] = static_cast(tolower(chDoc)); numCharsInLine++; } } // Extra element at the end of the line to hold end x position and act as ll.chars[numCharsInLine] = 0; // Also triggers processing in the loops as this is a control character ll.styles[numCharsInLine] = styleByte; // For eolFilled ll.indicators[numCharsInLine] = 0; // Layout the line, determining the position of each character, // with an extra element at the end for the end of the line. int startseg = 0; // Start of the current segment, in char. number int startsegx = 0; // Start of the current segment, in pixels ll.positions[0] = 0; unsigned int tabWidth = vstyle.spaceWidth * pdoc->tabInChars; bool lastSegItalics = false; for (int charInLine = 0; charInLine < numCharsInLine; charInLine++) { if ((ll.styles[charInLine] != ll.styles[charInLine + 1]) || IsControlCharacter(ll.chars[charInLine]) || IsControlCharacter(ll.chars[charInLine + 1])) { ll.positions[startseg] = 0; if (vstyle.styles[ll.styles[charInLine]].visible) { if (IsControlCharacter(ll.chars[charInLine])) { if (ll.chars[charInLine] == '\t') { ll.positions[charInLine + 1] = ((((startsegx + 2) / tabWidth) + 1) * tabWidth) - startsegx; } else { const char *ctrlChar = ControlCharacterString(ll.chars[charInLine]); // +3 For a blank on front and rounded edge each side: ll.positions[charInLine + 1] = surface->WidthText(ctrlCharsFont, ctrlChar, strlen(ctrlChar)) + 3; } lastSegItalics = false; } else { // Regular character lastSegItalics = vstyle.styles[ll.styles[charInLine]].italic; int lenSeg = charInLine - startseg + 1; if ((lenSeg == 1) && (' ' == ll.chars[startseg])) { // Over half the segments are single characters and of these about half are space characters. ll.positions[charInLine + 1] = vstyle.styles[ll.styles[charInLine]].spaceWidth; } else { surface->MeasureWidths(vstyle.styles[ll.styles[charInLine]].font, ll.chars + startseg, lenSeg, ll.positions + startseg + 1); } } } else { // invisible for (int posToZero = startseg; posToZero <= (charInLine + 1); posToZero++) { ll.positions[posToZero] = 0; } } for (int posToIncrease = startseg; posToIncrease <= (charInLine + 1); posToIncrease++) { ll.positions[posToIncrease] += startsegx; } startsegx = ll.positions[charInLine + 1]; startseg = charInLine + 1; } } // Small hack to make lines that end with italics not cut off the edge of the last character if ((startseg > 0) && lastSegItalics) { ll.positions[startseg] += 2; } ll.numCharsInLine = numCharsInLine; } void Editor::DrawLine(Surface *surface, ViewStyle &vsDraw, int line, int lineVisible, int xStart, PRectangle rcLine, LineLayout &ll) { PRectangle rcSegment = rcLine; // Using one font for all control characters so it can be controlled independently to ensure // the box goes around the characters tightly. Seems to be no way to work out what height // is taken by an individual character - internal leading gives varying results. Font &ctrlCharsFont = vsDraw.styles[STYLE_CONTROLCHAR].font; bool overrideBackground = false; Colour background = Colour(0, 0, 0); if (caret.active && vsDraw.showCaretLineBackground && ll.containsCaret) { overrideBackground = true; background = vsDraw.caretLineBackground.allocated; } if (vsDraw.maskInLine) { int marks = pdoc->GetMark(line) & vsDraw.maskInLine; if (marks) { overrideBackground = true; for (int markBit = 0; (markBit < 32) && marks; markBit++) { if (marks & 1) { background = vsDraw.markers[markBit].back.allocated; } marks >>= 1; } } } bool inIndentation = true; int indentWidth = pdoc->indentInChars * vsDraw.spaceWidth; if (indentWidth == 0) indentWidth = pdoc->tabInChars * vsDraw.spaceWidth; int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); int styleMask = pdoc->stylingBitsMask; int startseg = 0; for (int i = 0; i < ll.numCharsInLine; i++) { int iDoc = i + posLineStart; // If there is the end of a style run for any reason if ((ll.styles[i] != ll.styles[i + 1]) || IsControlCharacter(ll.chars[i]) || IsControlCharacter(ll.chars[i + 1]) || ((ll.selStart != ll.selEnd) && ((iDoc + 1 == ll.selStart) || (iDoc + 1 == ll.selEnd))) || (i == (ll.edgeColumn - 1))) { int styleMain = ll.styles[i]; Colour textBack = vsDraw.styles[styleMain].back.allocated; Colour textFore = vsDraw.styles[styleMain].fore.allocated; Font &textFont = vsDraw.styles[styleMain].font; bool inSelection = (iDoc >= ll.selStart) && (iDoc < ll.selEnd) && (ll.selStart != ll.selEnd); if (inSelection) { if (vsDraw.selbackset) { if (primarySelection) textBack = vsDraw.selbackground.allocated; else textBack = vsDraw.selbackground2.allocated; } if (vsDraw.selforeset) textFore = vsDraw.selforeground.allocated; } else { if (overrideBackground) textBack = background; if ((vsDraw.edgeState == EDGE_BACKGROUND) && (i >= ll.edgeColumn) && (ll.chars[i] != '\n') && (ll.chars[i] != '\r')) textBack = vsDraw.edgecolour.allocated; } // Manage tab display if (ll.chars[i] == '\t') { rcSegment.left = ll.positions[i] + xStart; rcSegment.right = ll.positions[i + 1] + xStart; surface->FillRectangle(rcSegment, textBack); if ((vsDraw.viewWhitespace != wsInvisible) || ((inIndentation && vsDraw.viewIndentationGuides))) { surface->PenColour(textFore); } if (inIndentation && vsDraw.viewIndentationGuides) { for (int xIG = ll.positions[i] / indentWidth * indentWidth; xIG < ll.positions[i + 1]; xIG += indentWidth) { if (xIG >= ll.positions[i] && xIG > 0) { Point from(0, ((lineVisible & 1) && (vsDraw.lineHeight & 1)) ? 1 : 0); PRectangle rcCopyArea(xIG + xStart + 1, rcSegment.top, xIG + xStart + 2, rcSegment.bottom); surface->Copy(rcCopyArea, from, (ll.xHighlightGuide == xIG) ? pixmapIndentGuideHighlight : pixmapIndentGuide); } } } if (vsDraw.viewWhitespace != wsInvisible) { if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { PRectangle rcTab(rcSegment.left + 1, rcSegment.top + 4, rcSegment.right - 1, rcSegment.bottom - vsDraw.maxDescent); DrawTabArrow(surface, rcTab, rcSegment.top + vsDraw.lineHeight / 2); } } // Manage control character display } else if (IsControlCharacter(ll.chars[i])) { inIndentation = false; const char *ctrlChar = ControlCharacterString(ll.chars[i]); rcSegment.left = ll.positions[i] + xStart; rcSegment.right = ll.positions[i + 1] + xStart; surface->FillRectangle(rcSegment, textBack); int normalCharHeight = surface->Ascent(ctrlCharsFont) - surface->InternalLeading(ctrlCharsFont); PRectangle rcCChar = rcSegment; rcCChar.left = rcCChar.left + 1; rcCChar.top = rcSegment.top + vsDraw.maxAscent - normalCharHeight; rcCChar.bottom = rcSegment.top + vsDraw.maxAscent + 1; PRectangle rcCentral = rcCChar; rcCentral.top++; rcCentral.bottom--; surface->FillRectangle(rcCentral, textFore); PRectangle rcChar = rcCChar; rcChar.left++; rcChar.right--; surface->DrawTextClipped(rcChar, ctrlCharsFont, rcSegment.top + vsDraw.maxAscent, ctrlChar, strlen(ctrlChar), textBack, textFore); // Manage normal display } else { rcSegment.left = ll.positions[startseg] + xStart; rcSegment.right = ll.positions[i + 1] + xStart; // Only try to draw if really visible - enhances performance by not calling environment to // draw strings that are completely past the right side of the window. if (rcSegment.left <= rcLine.right) { surface->DrawText(rcSegment, textFont, rcSegment.top + vsDraw.maxAscent, ll.chars + startseg, i - startseg + 1, textFore, textBack); if (vsDraw.viewWhitespace != wsInvisible || (inIndentation && vsDraw.viewIndentationGuides)) { for (int cpos = 0; cpos <= i - startseg; cpos++) { if (ll.chars[cpos + startseg] == ' ') { if (vsDraw.viewWhitespace != wsInvisible) { if (!inIndentation || vsDraw.viewWhitespace == wsVisibleAlways) { int xmid = (ll.positions[cpos + startseg] + ll.positions[cpos + startseg + 1]) / 2; PRectangle rcDot(xmid + xStart, rcSegment.top + vsDraw.lineHeight / 2, 0, 0); rcDot.right = rcDot.left + 1; rcDot.bottom = rcDot.top + 1; surface->FillRectangle(rcDot, textFore); } } if (inIndentation && vsDraw.viewIndentationGuides) { int startSpace = ll.positions[cpos + startseg]; if (startSpace > 0 && (startSpace % indentWidth == 0)) { Point from(0, ((lineVisible & 1) && (vsDraw.lineHeight & 1)) ? 1 : 0); PRectangle rcCopyArea(startSpace + xStart + 1, rcSegment.top, startSpace + xStart + 2, rcSegment.bottom); surface->Copy(rcCopyArea, from, (ll.xHighlightGuide == ll.positions[cpos + startseg]) ? pixmapIndentGuideHighlight : pixmapIndentGuide); } } } else { inIndentation = false; } } } } if (vsDraw.styles[styleMain].underline) { PRectangle rcUL = rcSegment; rcUL.top = rcUL.top + vsDraw.maxAscent + 1; rcUL.bottom = rcUL.top + 1; surface->FillRectangle(rcUL, textFore); } } startseg = i + 1; } } // Draw indicators int indStart[INDIC_MAX + 1] = {0}; for (int indica = 0; indica <= INDIC_MAX; indica++) indStart[indica] = 0; for (int indicPos = 0; indicPos < ll.numCharsInLine; indicPos++) { if (ll.indicators[indicPos] != ll.indicators[indicPos + 1]) { int mask = 1 << pdoc->stylingBits; for (int indicnum = 0; mask < 0x100; indicnum++) { if ((ll.indicators[indicPos + 1] & mask) && !(ll.indicators[indicPos] & mask)) { indStart[indicnum] = ll.positions[indicPos + 1]; } if (!(ll.indicators[indicPos + 1] & mask) && (ll.indicators[indicPos] & mask)) { PRectangle rcIndic( indStart[indicnum] + xStart, rcLine.top + vsDraw.maxAscent, ll.positions[indicPos + 1] + xStart, rcLine.top + vsDraw.maxAscent + 3); vsDraw.indicators[indicnum].Draw(surface, rcIndic); } mask = mask << 1; } } } // End of the drawing of the current line // Fill in a PRectangle representing the end of line characters int xEol = ll.positions[ll.numCharsInLine]; rcSegment.left = xEol + xStart; rcSegment.right = xEol + vsDraw.aveCharWidth + xStart; bool eolInSelection = (posLineEnd > ll.selStart) && (posLineEnd <= ll.selEnd) && (ll.selStart != ll.selEnd); if (eolInSelection && vsDraw.selbackset && (line < pdoc->LinesTotal() - 1)) { if (primarySelection) surface->FillRectangle(rcSegment, vsDraw.selbackground.allocated); else surface->FillRectangle(rcSegment, vsDraw.selbackground2.allocated); } else if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else { surface->FillRectangle(rcSegment, vsDraw.styles[ll.styles[ll.numCharsInLine] & styleMask].back.allocated); } rcSegment.left = xEol + vsDraw.aveCharWidth + xStart; rcSegment.right = rcLine.right; if (overrideBackground) { surface->FillRectangle(rcSegment, background); } else if (vsDraw.styles[ll.styles[ll.numCharsInLine] & styleMask].eolFilled) { surface->FillRectangle(rcSegment, vsDraw.styles[ll.styles[ll.numCharsInLine] & styleMask].back.allocated); } else { surface->FillRectangle(rcSegment, vsDraw.styles[STYLE_DEFAULT].back.allocated); } if (vsDraw.edgeState == EDGE_LINE) { int edgeX = ll.edgeColumn * vsDraw.spaceWidth; rcSegment.left = edgeX + xStart; rcSegment.right = rcSegment.left + 1; surface->FillRectangle(rcSegment, vsDraw.edgecolour.allocated); } } void Editor::Paint(Surface *surfaceWindow, PRectangle rcArea) { //Platform::DebugPrintf("Paint %d %d - %d %d\n", rcArea.left, rcArea.top, rcArea.right, rcArea.bottom); RefreshStyleData(); PRectangle rcClient = GetClientRectangle(); //Platform::DebugPrintf("Client: (%3d,%3d) ... (%3d,%3d) %d\n", // rcClient.left, rcClient.top, rcClient.right, rcClient.bottom); if (!pixmapSelPattern.Initialised()) { pixmapSelPattern.InitPixMap(8, 8, surfaceWindow); // This complex procedure is to reproduce the checker board dithered pattern used by windows // for scroll bars and Visual Studio for its selection margin. The colour of this pattern is half // way between the chrome colour and the chrome highlight colour making a nice transition // between the window chrome and the content area. And it works in low colour depths. PRectangle rcPattern(0, 0, 8, 8); if (vs.selbarlight.desired == Colour(0xff, 0xff, 0xff)) { pixmapSelPattern.FillRectangle(rcPattern, vs.selbar.allocated); pixmapSelPattern.PenColour(vs.selbarlight.allocated); for (int stripe = 0; stripe < 8; stripe++) { pixmapSelPattern.MoveTo(0, stripe * 2); pixmapSelPattern.LineTo(8, stripe * 2 - 8); } } else { // User has chosen an unusual chrome colour scheme so just use the highlight edge colour. pixmapSelPattern.FillRectangle(rcPattern, vs.selbarlight.allocated); } } if (!pixmapIndentGuide.Initialised()) { // 1 extra pixel in height so can handle odd/even positions and so produce a continuous line pixmapIndentGuide.InitPixMap(1, vs.lineHeight + 1, surfaceWindow); pixmapIndentGuideHighlight.InitPixMap(1, vs.lineHeight + 1, surfaceWindow); PRectangle rcIG(0, 0, 1, vs.lineHeight); pixmapIndentGuide.FillRectangle(rcIG, vs.styles[STYLE_INDENTGUIDE].back.allocated); pixmapIndentGuide.PenColour(vs.styles[STYLE_INDENTGUIDE].fore.allocated); pixmapIndentGuideHighlight.FillRectangle(rcIG, vs.styles[STYLE_BRACELIGHT].back.allocated); pixmapIndentGuideHighlight.PenColour(vs.styles[STYLE_BRACELIGHT].fore.allocated); for (int stripe = 1; stripe < vs.lineHeight + 1; stripe += 2) { pixmapIndentGuide.MoveTo(0, stripe); pixmapIndentGuide.LineTo(2, stripe); pixmapIndentGuideHighlight.MoveTo(0, stripe); pixmapIndentGuideHighlight.LineTo(2, stripe); } } if (bufferedDraw) { if (!pixmapLine.Initialised()) { pixmapLine.InitPixMap(rcClient.Width(), rcClient.Height(), surfaceWindow); pixmapSelMargin.InitPixMap(vs.fixedColumnWidth, rcClient.Height(), surfaceWindow); } } surfaceWindow->SetPalette(&palette, true); pixmapLine.SetPalette(&palette, !hasFocus); //Platform::DebugPrintf("Paint: (%3d,%3d) ... (%3d,%3d)\n", // rcArea.left, rcArea.top, rcArea.right, rcArea.bottom); int screenLinePaintFirst = rcArea.top / vs.lineHeight; // The area to be painted plus one extra line is styled. // The extra line is to determine when a style change, such as starting a comment flows on to other lines. int lineStyleLast = topLine + (rcArea.bottom - 1) / vs.lineHeight + 1; //Platform::DebugPrintf("Paint lines = %d .. %d\n", topLine + screenLinePaintFirst, lineStyleLast); int endPosPaint = pdoc->Length(); if (lineStyleLast < cs.LinesDisplayed()) endPosPaint = pdoc->LineStart(cs.DocFromDisplay(lineStyleLast + 1)); int xStart = vs.fixedColumnWidth - xOffset; int ypos = 0; if (!bufferedDraw) ypos += screenLinePaintFirst * vs.lineHeight; int yposScreen = screenLinePaintFirst * vs.lineHeight; // Ensure we are styled as far as we are painting. pdoc->EnsureStyledTo(endPosPaint); if (needUpdateUI) { NotifyUpdateUI(); needUpdateUI = false; } PaintSelMargin(surfaceWindow, rcArea); PRectangle rcRightMargin = rcClient; rcRightMargin.left = rcRightMargin.right - vs.rightMarginWidth; if (rcArea.Intersects(rcRightMargin)) { surfaceWindow->FillRectangle(rcRightMargin, vs.styles[STYLE_DEFAULT].back.allocated); } if (paintState == paintAbandoned) { // Either styling or NotifyUpdateUI noticed that painting is needed // outside the current painting rectangle //Platform::DebugPrintf("Abandoning paint\n"); return; } //Platform::DebugPrintf("start display %d, offset = %d\n", pdoc->Length(), xOffset); // Do the painting if (rcArea.right > vs.fixedColumnWidth) { Surface *surface = surfaceWindow; if (bufferedDraw) { surface = &pixmapLine; } surface->SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); int visibleLine = topLine + screenLinePaintFirst; int line = cs.DocFromDisplay(visibleLine); int posCaret = currentPos; if (posDrag >= 0) posCaret = posDrag; int lineCaret = pdoc->LineFromPosition(posCaret); // Remove selection margin from drawing area so text will not be drawn // on it in unbuffered mode. PRectangle rcTextArea = rcClient; rcTextArea.left = vs.fixedColumnWidth; rcTextArea.right -= vs.rightMarginWidth; surfaceWindow->SetClip(rcTextArea); // Loop on visible lines //GTimer *tim=g_timer_new(); while (visibleLine < cs.LinesDisplayed() && yposScreen < rcArea.bottom) { //g_timer_start(tim); //Platform::DebugPrintf("Painting line %d\n", line); // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. LineLayout ll; LayoutLine(line, surface, vs, ll); ll.selStart = SelectionStart(line); ll.selEnd = SelectionEnd(line); ll.containsCaret = line == lineCaret; if (hideSelection) { ll.selStart = -1; ll.selEnd = -1; ll.containsCaret = false; } // Need to fix this up so takes account of Unicode and DBCS ll.edgeColumn = theEdge; int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); //Platform::DebugPrintf("line %d %d - %d\n", line, posLineStart, posLineEnd); PRectangle rcLine = rcClient; rcLine.top = ypos; rcLine.bottom = ypos + vs.lineHeight; // Highlight the current braces if any if ((braces[0] >= posLineStart) && (braces[0] < posLineEnd)) { int braceOffset = braces[0] - posLineStart; if (braceOffset < ll.numCharsInLine) ll.styles[braceOffset] = static_cast(bracesMatchStyle); } if ((braces[1] >= posLineStart) && (braces[1] < posLineEnd)) { int braceOffset = braces[1] - posLineStart; if (braceOffset < ll.numCharsInLine) ll.styles[braceOffset] = static_cast(bracesMatchStyle); } if ((braces[0] >= posLineStart && braces[1] <= posLineEnd) || (braces[1] >= posLineStart && braces[0] <= posLineEnd)) { ll.xHighlightGuide = highlightGuideColumn * vs.spaceWidth; } // Draw the line if (cs.GetVisible(line)) DrawLine(surface, vs, line, visibleLine, xStart, rcLine, ll); bool expanded = cs.GetExpanded(line); if ( (expanded && (foldFlags & 2)) || (!expanded && (foldFlags & 4)) ) { if (pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) { PRectangle rcFoldLine = rcLine; rcFoldLine.bottom = rcFoldLine.top + 1; surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); } } if ( (expanded && (foldFlags & 8)) || (!expanded && (foldFlags & 16)) ) { if (pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) { PRectangle rcFoldLine = rcLine; rcFoldLine.top = rcFoldLine.bottom - 1; surface->FillRectangle(rcFoldLine, vs.styles[STYLE_DEFAULT].fore.allocated); } } // Draw the Caret if (line == lineCaret) { int offset = Platform::Minimum(posCaret - posLineStart, LineLayout::maxLineLength); int xposCaret = ll.positions[offset] + xStart; int widthOverstrikeCaret; if (posCaret == pdoc->Length()) { // At end of document widthOverstrikeCaret = vs.aveCharWidth; } else if ((posCaret - posLineStart) >= ll.numCharsInLine) { // At end of line widthOverstrikeCaret = vs.aveCharWidth; } else { widthOverstrikeCaret = ll.positions[offset + 1] - ll.positions[offset]; } if (widthOverstrikeCaret < 3) // Make sure its visible widthOverstrikeCaret = 3; if (((caret.active && caret.on) || (posDrag >= 0)) && xposCaret >= 0) { PRectangle rcCaret = rcLine; int caretWidthOffset = 0; if ((offset > 0) && (vs.caretWidth > 1)) caretWidthOffset = 1; // Move back so overlaps both character cells. if (posDrag >= 0) { rcCaret.left = xposCaret - caretWidthOffset; rcCaret.right = rcCaret.left + vs.caretWidth; } else { if (inOverstrike) { rcCaret.top = rcCaret.bottom - 2; rcCaret.left = xposCaret + 1; rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; } else { rcCaret.left = xposCaret - caretWidthOffset; rcCaret.right = rcCaret.left + vs.caretWidth; } } surface->FillRectangle(rcCaret, vs.caretcolour.allocated); } } if (cs.GetVisible(line)) { if (bufferedDraw) { Point from(vs.fixedColumnWidth, 0); PRectangle rcCopyArea(vs.fixedColumnWidth, yposScreen, rcClient.right, yposScreen + vs.lineHeight); surfaceWindow->Copy(rcCopyArea, from, pixmapLine); } } if (!bufferedDraw) { ypos += vs.lineHeight; } yposScreen += vs.lineHeight; visibleLine++; line = cs.DocFromDisplay(visibleLine); //gdk_flush(); //g_timer_stop(tim); //Platform::DebugPrintf("Paint [%0d] took %g\n", line, g_timer_elapsed(tim, 0)); } //g_timer_destroy(tim); // Right column limit indicator PRectangle rcBeyondEOF = rcClient; rcBeyondEOF.left = vs.fixedColumnWidth; rcBeyondEOF.right = rcBeyondEOF.right; rcBeyondEOF.top = (cs.LinesDisplayed() - topLine) * vs.lineHeight; if (rcBeyondEOF.top < rcBeyondEOF.bottom) { surfaceWindow->FillRectangle(rcBeyondEOF, vs.styles[STYLE_DEFAULT].back.allocated); if (vs.edgeState == EDGE_LINE) { int edgeX = theEdge * vs.spaceWidth; rcBeyondEOF.left = edgeX + xStart; rcBeyondEOF.right = rcBeyondEOF.left + 1; surfaceWindow->FillRectangle(rcBeyondEOF, vs.edgecolour.allocated); } } NotifyPainted(); } } // Space (3 space characters) between line numbers and text when printing. #define lineNumberPrintSpace " " Colour InvertedLight(Colour orig) { unsigned int r = orig.GetRed(); unsigned int g = orig.GetGreen(); unsigned int b = orig.GetBlue(); unsigned int l = (r + g + b) / 3; // There is a better calculation for this that matches human eye unsigned int il = 0xff - l; if (l == 0) return Colour(0xff, 0xff, 0xff); r = r * il / l; g = g * il / l; b = b * il / l; return Colour(Platform::Minimum(r, 0xff), Platform::Minimum(g, 0xff), Platform::Minimum(b, 0xff)); } // This is mostly copied from the Paint method but with some things omitted // such as the margin markers, line numbers, selection and caret // Should be merged back into a combined Draw method. long Editor::FormatRange(bool draw, RangeToFormat *pfr) { if (!pfr) return 0; Surface *surface = new Surface(); surface->Init(pfr->hdc); surface->SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); Surface *surfaceMeasure = new Surface(); surfaceMeasure->Init(pfr->hdcTarget); surfaceMeasure->SetUnicodeMode(SC_CP_UTF8 == pdoc->dbcsCodePage); ViewStyle vsPrint(vs); // Modify the view style for printing as do not normally want any of the transient features to be printed // Printing supports only the line number margin. int lineNumberIndex = -1; for (int margin = 0; margin < ViewStyle::margins; margin++) { if ((!vsPrint.ms[margin].symbol) && (vsPrint.ms[margin].width > 0)) { lineNumberIndex = margin; } else { vsPrint.ms[margin].width = 0; } } vsPrint.showMarkedLines = false; vsPrint.fixedColumnWidth = 0; vsPrint.zoomLevel = printMagnification; vsPrint.viewIndentationGuides = false; // Don't show the selection when printing vsPrint.selbackset = false; vsPrint.selforeset = false; vsPrint.showCaretLineBackground = false; // Set colours for printing according to users settings for (int sty = 0;sty <= STYLE_MAX;sty++) { if (printColourMode == SC_PRINT_INVERTLIGHT) { vsPrint.styles[sty].fore.desired = InvertedLight(vsPrint.styles[sty].fore.desired); vsPrint.styles[sty].back.desired = InvertedLight(vsPrint.styles[sty].back.desired); } else if (printColourMode == SC_PRINT_BLACKONWHITE) { vsPrint.styles[sty].fore.desired = Colour(0, 0, 0); vsPrint.styles[sty].back.desired = Colour(0xff, 0xff, 0xff); } else if (printColourMode == SC_PRINT_COLOURONWHITE) { vsPrint.styles[sty].back.desired = Colour(0xff, 0xff, 0xff); } else if (printColourMode == SC_PRINT_COLOURONWHITEDEFAULTBG) { if (sty <= STYLE_DEFAULT) { vsPrint.styles[sty].back.desired = Colour(0xff, 0xff, 0xff); } } } // White background for the line numbers vsPrint.styles[STYLE_LINENUMBER].back.desired = Colour(0xff, 0xff, 0xff); vsPrint.Refresh(*surfaceMeasure); // Ensure colours are set up vsPrint.RefreshColourPalette(palette, true); vsPrint.RefreshColourPalette(palette, false); // Determining width must hapen after fonts have been realised in Refresh int lineNumberWidth = 0; if (lineNumberIndex >= 0) { lineNumberWidth = surface->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, "99999" lineNumberPrintSpace, 5 + strlen(lineNumberPrintSpace)); vsPrint.ms[lineNumberIndex].width = lineNumberWidth; } int linePrintStart = pdoc->LineFromPosition(pfr->chrg.cpMin); int linePrintLast = linePrintStart + (pfr->rc.bottom - pfr->rc.top) / vsPrint.lineHeight - 1; if (linePrintLast < linePrintStart) linePrintLast = linePrintStart; int linePrintMax = pdoc->LineFromPosition(pfr->chrg.cpMax - 1); if (linePrintLast > linePrintMax) linePrintLast = linePrintMax; //Platform::DebugPrintf("Formatting lines=[%0d,%0d,%0d] top=%0d bottom=%0d line=%0d %0d\n", // linePrintStart, linePrintLast, linePrintMax, pfr->rc.top, pfr->rc.bottom, vsPrint.lineHeight, // surfaceMeasure->Height(vsPrint.styles[STYLE_LINENUMBER].font)); int endPosPrint = pdoc->Length(); if (linePrintLast < pdoc->LinesTotal()) endPosPrint = pdoc->LineStart(linePrintLast + 1); // Ensure we are styled to where we are formatting. pdoc->EnsureStyledTo(endPosPrint); int xStart = vsPrint.fixedColumnWidth + pfr->rc.left + lineNumberWidth; int ypos = pfr->rc.top; int line = linePrintStart; if (draw) { // Otherwise just measuring while (line <= linePrintLast && ypos < pfr->rc.bottom) { // When printing, the hdc and hdcTarget may be the same, so // changing the state of surfaceMeasure may change the underlying // state of surface. Therefore, any cached state is discarded before // using each surface. surfaceMeasure->FlushCachedState(); // Copy this line and its styles from the document into local arrays // and determine the x position at which each character starts. LineLayout ll; LayoutLine(line, surfaceMeasure, vsPrint, ll); ll.selStart = -1; ll.selEnd = -1; ll.containsCaret = false; // Need to fix this up so takes account of Unicode and DBCS ll.edgeColumn = theEdge; PRectangle rcLine; rcLine.left = pfr->rc.left + lineNumberWidth; rcLine.top = ypos; rcLine.right = pfr->rc.right; rcLine.bottom = ypos + vsPrint.lineHeight; if (lineNumberWidth) { char number[100]; sprintf(number, "%d" lineNumberPrintSpace, line + 1); PRectangle rcNumber = rcLine; rcNumber.right = rcNumber.left + lineNumberWidth; // Right justify rcNumber.left -= surface->WidthText(vsPrint.styles[STYLE_LINENUMBER].font, number, strlen(number)); surface->DrawText(rcNumber, vsPrint.styles[STYLE_LINENUMBER].font, ypos + vsPrint.maxAscent, number, strlen(number), vsPrint.styles[STYLE_LINENUMBER].fore.allocated, vsPrint.styles[STYLE_LINENUMBER].back.allocated); } // Draw the line surface->FlushCachedState(); DrawLine(surface, vsPrint, line, line, xStart, rcLine, ll); ypos += vsPrint.lineHeight; line++; } } delete surface; delete surfaceMeasure; return endPosPrint; } // Empty method is overridden on GTK+ to show / hide scrollbars void Editor::ReconfigureScrollBars() {} void Editor::SetScrollBarsTo(PRectangle) { RefreshStyleData(); int nMax = cs.LinesDisplayed(); int nPage = cs.LinesDisplayed() - MaxScrollPos() + 1; bool modified = ModifyScrollBars(nMax, nPage); // TODO: ensure always showing as many lines as possible // May not be, if, for example, window made larger if (topLine > MaxScrollPos()) { SetTopLine(Platform::Clamp(topLine, 0, MaxScrollPos())); SetVerticalScrollPos(); Redraw(); } if (modified) Redraw(); //Platform::DebugPrintf("end max = %d page = %d\n", nMax, nPage); } void Editor::SetScrollBars() { PRectangle rsClient = GetClientRectangle(); SetScrollBarsTo(rsClient); } void Editor::AddChar(char ch) { char s[2]; s[0] = ch; s[1] = '\0'; AddCharUTF(s, 1); } void Editor::AddCharUTF(char *s, unsigned int len) { bool wasSelection = currentPos != anchor; ClearSelection(); if (inOverstrike && !wasSelection) { if (currentPos < (pdoc->Length() - 1)) { if ((pdoc->CharAt(currentPos) != '\r') && (pdoc->CharAt(currentPos) != '\n')) { pdoc->DelChar(currentPos); } } } pdoc->InsertString(currentPos, s, len); SetEmptySelection(currentPos + len); EnsureCaretVisible(); // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); SetLastXChosen(); int byte = static_cast(s[0]); if ((byte < 0xC0) || (1 == len)) { // Handles UTF-8 characters between 0x01 and 0x7F and single byte // characters when not in UTF-8 mode. // Also treats \0 and naked trail bytes 0x80 to 0xBF as valid // characters representing themselves. } else { // Unroll 1 to 3 byte UTF-8 sequences. See reference data at: // http://www.cl.cam.ac.uk/~mgk25/unicode.html // http://www.cl.cam.ac.uk/~mgk25/ucs/examples/UTF-8-test.txt if (byte < 0xE0) { int byte2 = static_cast(s[1]); if ((byte2 & 0xC0) == 0x80) { // Two-byte-character lead-byte followed by a trail-byte. byte = (((byte & 0x1F) << 6) | (byte2 & 0x3F)); } // A two-byte-character lead-byte not followed by trail-byte // represents itself. } else if (byte < 0xF0) { int byte2 = static_cast(s[1]); int byte3 = static_cast(s[2]); if (((byte2 & 0xC0) == 0x80) && ((byte3 & 0xC0) == 0x80)) { // Three-byte-character lead byte followed by two trail bytes. byte = (((byte & 0x0F) << 12) | ((byte2 & 0x3F) << 6) | (byte3 & 0x3F)); } // A three-byte-character lead-byte not followed by two trail-bytes // represents itself. } } NotifyChar(byte); } void Editor::ClearSelection() { if (selType == selRectangle) { pdoc->BeginUndoAction(); int lineStart = pdoc->LineFromPosition(SelectionStart()); int lineEnd = pdoc->LineFromPosition(SelectionEnd()); int startPos = SelectionStart(); for (int line = lineEnd; line >= lineStart; line--) { startPos = SelectionStart(line); unsigned int chars = SelectionEnd(line) - startPos; if (0 != chars) { pdoc->DeleteChars(startPos, chars); } } SetEmptySelection(startPos); pdoc->EndUndoAction(); selType = selStream; } else { int startPos = SelectionStart(); unsigned int chars = SelectionEnd() - startPos; SetEmptySelection(startPos); if (0 != chars) { pdoc->BeginUndoAction(); pdoc->DeleteChars(startPos, chars); pdoc->EndUndoAction(); } } } void Editor::ClearAll() { pdoc->BeginUndoAction(); if (0 != pdoc->Length()) { pdoc->DeleteChars(0, pdoc->Length()); } cs.Clear(); pdoc->EndUndoAction(); anchor = 0; currentPos = 0; SetTopLine(0); SetVerticalScrollPos(); } void Editor::ClearDocumentStyle() { pdoc->StartStyling(0, '\377'); pdoc->SetStyleFor(pdoc->Length(), 0); cs.ShowAll(); pdoc->ClearLevels(); } void Editor::Cut() { if (!pdoc->IsReadOnly()) { Copy(); ClearSelection(); } } void Editor::PasteRectangular(int pos, const char *ptr, int len) { if (pdoc->IsReadOnly()) { return; } currentPos = pos; int insertPos = currentPos; int xInsert = XFromPosition(currentPos); int line = pdoc->LineFromPosition(currentPos); bool prevCr = false; pdoc->BeginUndoAction(); for (int i = 0; i < len; i++) { if ((ptr[i] == '\r') || (ptr[i] == '\n')) { if ((ptr[i] == '\r') || (!prevCr)) line++; if (line >= pdoc->LinesTotal()) { if (pdoc->eolMode != SC_EOL_LF) pdoc->InsertChar(pdoc->Length(), '\r'); if (pdoc->eolMode != SC_EOL_CR) pdoc->InsertChar(pdoc->Length(), '\n'); } // Pad the end of lines with spaces if required currentPos = PositionFromLineX(line, xInsert); if ((XFromPosition(currentPos) < xInsert) && (i + 1 < len)) { for (int i = 0; i < xInsert - XFromPosition(currentPos); i++) { pdoc->InsertChar(currentPos, ' '); currentPos++; } insertPos = currentPos; } prevCr = ptr[i] == '\r'; } else { pdoc->InsertString(currentPos, ptr + i, 1); currentPos++; insertPos = currentPos; prevCr = false; } } pdoc->EndUndoAction(); SetEmptySelection(insertPos); } bool Editor::CanPaste() { return !pdoc->IsReadOnly(); } void Editor::Clear() { if (currentPos == anchor) { DelChar(); } else { ClearSelection(); } SetEmptySelection(currentPos); } void Editor::SelectAll() { SetSelection(0, pdoc->Length()); Redraw(); } void Editor::Undo() { if (pdoc->CanUndo()) { InvalidateCaret(); int newPos = pdoc->Undo(); SetEmptySelection(newPos); EnsureCaretVisible(); } } void Editor::Redo() { if (pdoc->CanRedo()) { int newPos = pdoc->Redo(); SetEmptySelection(newPos); EnsureCaretVisible(); } } void Editor::DelChar() { pdoc->DelChar(currentPos); // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); } void Editor::DelCharBack() { if (currentPos == anchor) { int lineCurrentPos = pdoc->LineFromPosition(currentPos); if (pdoc->GetColumn(currentPos) <= pdoc->GetLineIndentation(lineCurrentPos) && pdoc->GetColumn(currentPos) > 0 && pdoc->backspaceUnindents) { pdoc->BeginUndoAction(); int indentation = pdoc->GetLineIndentation(lineCurrentPos); int indentationStep = (pdoc->indentInChars ? pdoc->indentInChars : pdoc->tabInChars); if (indentation % indentationStep == 0) { pdoc->SetLineIndentation(lineCurrentPos, indentation - indentationStep); } else { pdoc->SetLineIndentation(lineCurrentPos, indentation - (indentation % indentationStep)); } SetEmptySelection(pdoc->GetLineIndentPosition(lineCurrentPos)); pdoc->EndUndoAction(); } else { int newPos = pdoc->DelCharBack(currentPos); SetEmptySelection(newPos); } } else { ClearSelection(); SetEmptySelection(currentPos); } // Avoid blinking during rapid typing: ShowCaretAtCurrentPosition(); } void Editor::NotifyFocus(bool) {} void Editor::NotifyStyleToNeeded(int endStyleNeeded) { SCNotification scn; scn.nmhdr.code = SCN_STYLENEEDED; scn.position = endStyleNeeded; NotifyParent(scn); } void Editor::NotifyStyleNeeded(Document*, void *, int endStyleNeeded) { NotifyStyleToNeeded(endStyleNeeded); } void Editor::NotifyChar(int ch) { SCNotification scn; scn.nmhdr.code = SCN_CHARADDED; scn.ch = ch; NotifyParent(scn); #ifdef MACRO_SUPPORT if (recordingMacro) { char txt[2]; txt[0] = static_cast(ch); txt[1] = '\0'; NotifyMacroRecord(SCI_REPLACESEL, 0, reinterpret_cast(txt)); } #endif } void Editor::NotifySavePoint(bool isSavePoint) { SCNotification scn; if (isSavePoint) { scn.nmhdr.code = SCN_SAVEPOINTREACHED; } else { scn.nmhdr.code = SCN_SAVEPOINTLEFT; } NotifyParent(scn); } void Editor::NotifyModifyAttempt() { SCNotification scn; scn.nmhdr.code = SCN_MODIFYATTEMPTRO; NotifyParent(scn); } void Editor::NotifyDoubleClick(Point, bool) { SCNotification scn; scn.nmhdr.code = SCN_DOUBLECLICK; NotifyParent(scn); } void Editor::NotifyUpdateUI() { SCNotification scn; scn.nmhdr.code = SCN_UPDATEUI; NotifyParent(scn); } void Editor::NotifyPainted() { SCNotification scn; scn.nmhdr.code = SCN_PAINTED; NotifyParent(scn); } bool Editor::NotifyMarginClick(Point pt, bool shift, bool ctrl, bool alt) { int marginClicked = -1; int x = 0; for (int margin = 0; margin < ViewStyle::margins; margin++) { if ((pt.x > x) && (pt.x < x + vs.ms[margin].width)) marginClicked = margin; x += vs.ms[margin].width; } if ((marginClicked >= 0) && vs.ms[marginClicked].sensitive) { SCNotification scn; scn.nmhdr.code = SCN_MARGINCLICK; scn.modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); scn.position = pdoc->LineStart(LineFromLocation(pt)); scn.margin = marginClicked; NotifyParent(scn); return true; } else { return false; } } void Editor::NotifyNeedShown(int pos, int len) { SCNotification scn; scn.nmhdr.code = SCN_NEEDSHOWN; scn.position = pos; scn.length = len; NotifyParent(scn); } void Editor::NotifyDwelling(Point pt, bool state) { SCNotification scn; scn.nmhdr.code = state ? SCN_DWELLSTART : SCN_DWELLEND; scn.position = PositionFromLocationClose(pt); scn.x = pt.x; scn.y = pt.y; NotifyParent(scn); } // Notifications from document void Editor::NotifyModifyAttempt(Document*, void *) { //Platform::DebugPrintf("** Modify Attempt\n"); NotifyModifyAttempt(); } void Editor::NotifyMove(int position) { SCNotification scn; scn.nmhdr.code = SCN_POSCHANGED; scn.position = position; NotifyParent(scn); } void Editor::NotifySavePoint(Document*, void *, bool atSavePoint) { //Platform::DebugPrintf("** Save Point %s\n", atSavePoint ? "On" : "Off"); NotifySavePoint(atSavePoint); } void Editor::NotifyModified(Document*, DocModification mh, void *) { needUpdateUI = true; if (paintState == painting) { CheckForChangeOutsidePaint(Range(mh.position, mh.position + mh.length)); } else if (paintState == notPainting) { if (mh.modificationType & SC_MOD_CHANGESTYLE) { if (mh.position < pdoc->LineStart(topLine)) { // Styling performed before this view Redraw(); } else { InvalidateRange(mh.position, mh.position + mh.length); } } else { // Move selection and brace highlights if (mh.modificationType & SC_MOD_INSERTTEXT) { if (currentPos > mh.position) { currentPos += mh.length; } if (anchor > mh.position) { anchor += mh.length; } if (braces[0] > mh.position) { braces[0] += mh.length; } if (braces[1] > mh.position) { braces[1] += mh.length; } } else { // SC_MOD_DELETETEXT int endPos = mh.position + mh.length; if (currentPos > mh.position) { if (currentPos > endPos) { currentPos -= mh.length; } else { currentPos = endPos; } } if (anchor > mh.position) { if (anchor > endPos) { anchor -= mh.length; } else { anchor = endPos; } } if (braces[0] > mh.position) { if (braces[0] > endPos) { braces[0] -= mh.length; } else { braces[0] = endPos; } } if (braces[1] > mh.position) { if (braces[1] > endPos) { braces[1] -= mh.length; } else { braces[1] = endPos; } } } if (cs.LinesDisplayed() < cs.LinesInDoc()) { // Some lines are hidden so may need shown. // TODO: check if the modified area is hidden. if (mh.modificationType & SC_MOD_BEFOREINSERT) { NotifyNeedShown(mh.position, mh.length); } else if (mh.modificationType & SC_MOD_BEFOREDELETE) { NotifyNeedShown(mh.position, mh.length); } } if (mh.linesAdded != 0) { // Update contraction state for inserted and removed lines // lineOfPos should be calculated in context of state before modification, shouldn't it int lineOfPos = pdoc->LineFromPosition(mh.position); if (mh.linesAdded > 0) { cs.InsertLines(lineOfPos, mh.linesAdded); } else { cs.DeleteLines(lineOfPos, -mh.linesAdded); } // Avoid scrolling of display if change before current display if (mh.position < posTopLine) { int newTop = Platform::Clamp(topLine + mh.linesAdded, 0, MaxScrollPos()); if (newTop != topLine) { SetTopLine(newTop); SetVerticalScrollPos(); } } //Platform::DebugPrintf("** %x Doc Changed\n", this); // TODO: could invalidate from mh.startModification to end of screen //InvalidateRange(mh.position, mh.position + mh.length); Redraw(); } else { //Platform::DebugPrintf("** %x Line Changed %d .. %d\n", this, // mh.position, mh.position + mh.length); InvalidateRange(mh.position, mh.position + mh.length); } } } // else paintState == paintAbandoned so no need to do anything if (mh.linesAdded != 0) { SetScrollBars(); } if (mh.modificationType & SC_MOD_CHANGEMARKER) { RedrawSelMargin(); } // If client wants to see this modification if (mh.modificationType & modEventMask) { if ((mh.modificationType & SC_MOD_CHANGESTYLE) == 0) { // Real modification made to text of document. NotifyChange(); // Send EN_CHANGE } SCNotification scn; scn.nmhdr.code = SCN_MODIFIED; scn.position = mh.position; scn.modificationType = mh.modificationType; scn.text = mh.text; scn.length = mh.length; scn.linesAdded = mh.linesAdded; scn.line = mh.line; scn.foldLevelNow = mh.foldLevelNow; scn.foldLevelPrev = mh.foldLevelPrev; NotifyParent(scn); } } void Editor::NotifyDeleted(Document *, void *) { /* Do nothing */ } #ifdef MACRO_SUPPORT void Editor::NotifyMacroRecord(unsigned int iMessage, unsigned long wParam, long lParam) { // Enumerates all macroable messages switch (iMessage) { case SCI_CUT: case SCI_COPY: case SCI_PASTE: case SCI_CLEAR: case WM_CUT: case WM_COPY: case WM_PASTE: case WM_CLEAR: case SCI_REPLACESEL: case SCI_ADDTEXT: case SCI_INSERTTEXT: case SCI_CLEARALL: case SCI_SELECTALL: case SCI_GOTOLINE: case SCI_GOTOPOS: case SCI_SEARCHANCHOR: case SCI_SEARCHNEXT: case SCI_SEARCHPREV: case SCI_LINEDOWN: case SCI_LINEDOWNEXTEND: case SCI_LINEUP: case SCI_LINEUPEXTEND: case SCI_CHARLEFT: case SCI_CHARLEFTEXTEND: case SCI_CHARRIGHT: case SCI_CHARRIGHTEXTEND: case SCI_WORDLEFT: case SCI_WORDLEFTEXTEND: case SCI_WORDRIGHT: case SCI_WORDRIGHTEXTEND: case SCI_WORDPARTLEFT: case SCI_WORDPARTLEFTEXTEND: case SCI_WORDPARTRIGHT: case SCI_WORDPARTRIGHTEXTEND: case SCI_HOME: case SCI_HOMEEXTEND: case SCI_LINEEND: case SCI_LINEENDEXTEND: case SCI_DOCUMENTSTART: case SCI_DOCUMENTSTARTEXTEND: case SCI_DOCUMENTEND: case SCI_DOCUMENTENDEXTEND: case SCI_PAGEUP: case SCI_PAGEUPEXTEND: case SCI_PAGEDOWN: case SCI_PAGEDOWNEXTEND: case SCI_EDITTOGGLEOVERTYPE: case SCI_CANCEL: case SCI_DELETEBACK: case SCI_TAB: case SCI_BACKTAB: case SCI_FORMFEED: case SCI_VCHOME: case SCI_VCHOMEEXTEND: case SCI_DELWORDLEFT: case SCI_DELWORDRIGHT: case SCI_DELLINELEFT: case SCI_DELLINERIGHT: case SCI_LINECUT: case SCI_LINEDELETE: case SCI_LINETRANSPOSE: case SCI_LOWERCASE: case SCI_UPPERCASE: break; // Filter out all others like display changes. Also, newlines are redundant // with char insert messages. case SCI_NEWLINE: default: // printf("Filtered out %ld of macro recording\n", iMessage); return; } // Send notification SCNotification scn; scn.nmhdr.code = SCN_MACRORECORD; scn.message = iMessage; scn.wParam = wParam; scn.lParam = lParam; NotifyParent(scn); } #endif // Force scroll and keep position relative to top of window void Editor::PageMove(int direction, bool extend) { Point pt = LocationFromPosition(currentPos); int topLineNew = Platform::Clamp( topLine + direction * LinesToScroll(), 0, MaxScrollPos()); int newPos = PositionFromLocation( Point(lastXChosen, pt.y + direction * (vs.lineHeight * LinesToScroll()))); if (topLineNew != topLine) { SetTopLine(topLineNew); MovePositionTo(newPos, extend); Redraw(); SetVerticalScrollPos(); } else { MovePositionTo(newPos, extend); } } void Editor::ChangeCaseOfSelection(bool makeUpperCase) { pdoc->BeginUndoAction(); int startCurrent = currentPos; int startAnchor = anchor; if (selType == selRectangle) { int lineStart = pdoc->LineFromPosition(SelectionStart()); int lineEnd = pdoc->LineFromPosition(SelectionEnd()); for (int line = lineEnd; line >= lineStart; line--) { pdoc->ChangeCase( Range(SelectionStart(line), SelectionEnd(line)), makeUpperCase); } // Would be nicer to keep the rectangular selection but this is complex selType = selStream; SetSelection(startCurrent, startCurrent); } else { pdoc->ChangeCase(Range(SelectionStart(), SelectionEnd()), makeUpperCase); SetSelection(startCurrent, startAnchor); } pdoc->EndUndoAction(); } void Editor::LineTranspose() { int line = pdoc->LineFromPosition(currentPos); if (line > 0) { int startPrev = pdoc->LineStart(line - 1); int endPrev = pdoc->LineEnd(line - 1); int start = pdoc->LineStart(line); int end = pdoc->LineEnd(line); int startNext = pdoc->LineStart(line + 1); if (end < pdoc->Length()) { end = startNext; char *thisLine = CopyRange(start, end); pdoc->DeleteChars(start, end - start); pdoc->InsertString(startPrev, thisLine, end - start); MovePositionTo(startPrev + end - start); delete []thisLine; } else { // Last line so line has no line end char *thisLine = CopyRange(start, end); char *prevEnd = CopyRange(endPrev, start); pdoc->DeleteChars(endPrev, end - endPrev); pdoc->InsertString(startPrev, thisLine, end - start); pdoc->InsertString(startPrev + end - start, prevEnd, start - endPrev); MovePositionTo(startPrev + end - endPrev); delete []thisLine; delete []prevEnd; } } } void Editor::CancelModes() {} int Editor::KeyCo