// Scintilla source code edit control // Editor.cxx - main code for the edit control // Copyright 1998-2000 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" #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(true), on(true), period(500) {} Timer::Timer() : ticking(false), ticksToWait(0), tickerID(0) {} Editor::Editor() { ctrlID = 0; stylesValid = false; hideSelection = false; inOverstrike = false; bufferedDraw = true; lastClickTime = 0; 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; caretPolicy = CARET_SLOP; caretSlop = 0; searchAnchor = 0; ucWheelScrollLines = 0; cWheelDelta = 0; //wheel delta from roll xOffset = 0; xCaretMargin = 50; currentPos = 0; anchor = 0; topLine = 0; posTopLine = 0; needUpdateUI = true; braces[0]=invalidPosition; braces[1]=invalidPosition; bracesMatchStyle = STYLE_BRACEBAD; edgeState = EDGE_NONE; theEdge = 0; paintState = notPainting; modEventMask = SC_MODEVENTMASKALL; 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() { } void Editor::DropGraphics() { pixmapLine.Release(); pixmapSelMargin.Release(); pixmapSelPattern.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 wDraw.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; } bool IsControlCharacter(char ch) { // iscntrl returns true for lots of chars > 127 which are displayable return ch >= 0 && ch < ' '; } const char *ControlCharacterString(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(unsigned int pos) { RefreshStyleData(); int line = pdoc->LineFromPosition(pos); int lineVisible = cs.DisplayFromDoc(line); //Platform::DebugPrintf("line=%d\n", line); Surface surface; surface.Init(); Point pt; pt.y = (lineVisible - topLine) * vs.lineHeight; // + half a lineheight? unsigned int posLineStart = pdoc->LineStart(line); if ((pos - posLineStart) > LineLayout::maxLineLength) { // very long line so put x at arbitrary large position pt.x = 30000 + vs.fixedColumnWidth - xOffset; } else { LineLayout ll; LayoutLine(line, &surface, vs, ll); pt.x = ll.positions[pos - posLineStart] + vs.fixedColumnWidth - xOffset; } return pt; } int Editor::XFromPosition(unsigned 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(); //Platform::DebugPrintf("Position of (%d,%d) line = %d top=%d\n", pt.x, pt.y, line, topLine); Surface surface; surface.Init(); 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; } 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(); 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 %d %d - %d %d\n", rc.left, rc.top, rc.right, rc.bottom); wDraw.InvalidateRectangle(rc); } void Editor::Redraw() { //Platform::DebugPrintf("Redraw all\n"); wDraw.InvalidateAll(); } void Editor::RedrawSelMargin() { if (vs.maskInLine) { Redraw(); } else { PRectangle rcSelMargin = GetClientRectangle(); rcSelMargin.right = vs.fixedColumnWidth; wDraw.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 + vs.fixedColumnWidth - xOffset); 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_) { SetSelection(currentPos_, currentPos_); } int Editor::MovePositionTo(int newPos, bool extend) { int delta = newPos - currentPos; newPos = pdoc->ClampPositionIntoDocument(newPos); newPos = pdoc->MovePositionOutsideChar(newPos, delta); if (extend) { SetSelection(newPos); } else { SetEmptySelection(newPos); } EnsureCaretVisible(); ShowCaretAtCurrentPosition(); return 0; } int Editor::MovePositionSoVisible(int pos, int moveDir) { pos = pdoc->ClampPositionIntoDocument(pos); pos = pdoc->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(); Redraw(); } void Editor::EnsureCaretVisible(bool useMargin) { //Platform::DebugPrintf("EnsureCaretVisible %d\n", xOffset); PRectangle rcClient = GetTextRectangle(); 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. int xMargin = Platform::Clamp(xCaretMargin, 2, Platform::Maximum(rcClient.Width() - 10, 4) / 2); if (!useMargin) xMargin = 2; // Ensure certain amount of text visible on both sides of caretSo move if caret just on edge rcClient.left = rcClient.left + xMargin; rcClient.right = rcClient.right - xMargin; if (!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(); } } int xOffsetNew = xOffset; if (pt.x < rcClient.left) { xOffsetNew = xOffset - (rcClient.left - pt.x); } else if (pt.x >= rcClient.right) { xOffsetNew = xOffset + (pt.x - rcClient.right); int xOffsetEOL = xOffset + (ptEOL.x - rcClient.right) - 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 (!wMain.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); } 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; while (line < pdoc->LinesTotal() && yposScreen < rcMargin.bottom) { int marks = pdoc->GetMark(line); if (pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) { if (cs.GetExpanded(line)) { marks |= 1 << SC_MARKNUM_FOLDEROPEN; } else { marks |= 1 << SC_MARKNUM_FOLDER; } } 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)); int xpos = 0; PRectangle rcNumber=rcMarker; // Right justify int width = surface->WidthText(vs.styles[STYLE_LINENUMBER].font, number, strlen(number)); 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) { rcMarker.top++; rcMarker.bottom--; 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); } 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; for (int charInDoc = posLineStart; charInDoc < posLineEnd && numCharsInLine < LineLayout::maxLineLength - 1; charInDoc++) { char chDoc = pdoc->CharAt(charInDoc); styleByte = pdoc->StyleAt(charInDoc); if (vstyle.viewEOL || ((chDoc != '\r') && (chDoc != '\n'))) { ll.chars[numCharsInLine] = chDoc; ll.styles[numCharsInLine] = styleByte & styleMask; ll.indicators[numCharsInLine] = styleByte & ~styleMask; numCharsInLine++; } } ll.chars[numCharsInLine] = 0; ll.styles[numCharsInLine] = styleByte; // For eolFilled ll.indicators[numCharsInLine] = 0; // Layout the line, determining the position of each character int startseg = 0; int startsegx = 0; ll.positions[0] = 0; unsigned int tabWidth = vstyle.spaceWidth * pdoc->tabInChars; 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 (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; } } else { surface->MeasureWidths(vstyle.styles[ll.styles[charInLine]].font, ll.chars + startseg, charInLine - startseg + 1, ll.positions + startseg + 1); } for (int posToIncrease = startseg; posToIncrease <= (charInLine + 1); posToIncrease++) { ll.positions[posToIncrease] += startsegx; } startsegx = ll.positions[charInLine + 1]; startseg = charInLine + 1; } } ll.numCharsInLine = numCharsInLine; } void Editor::DrawLine(Surface *surface, ViewStyle &vsDraw, int line, 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; int marks = 0; Colour markBack = Colour(0, 0, 0); if (vsDraw.maskInLine) { marks = pdoc->GetMark(line) & vsDraw.maskInLine; if (marks) { for (int markBit = 0; (markBit < 32) && marks; markBit++) { if (marks & 1) { markBack = vsDraw.markers[markBit].back.allocated; } marks >>= 1; } } marks = pdoc->GetMark(line) & vsDraw.maskInLine; } int posLineStart = pdoc->LineStart(line); int posLineEnd = pdoc->LineStart(line + 1); int selStart = SelectionStart(line); int selEnd = SelectionEnd(line); 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]) || ((selStart != selEnd) && ((iDoc + 1 == selStart) || (iDoc + 1 == selEnd))) || (i == (theEdge-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 >= selStart) && (iDoc < selEnd) && (selStart != selEnd); if (inSelection && !hideSelection) { if (vsDraw.selbackset) textBack = vsDraw.selbackground.allocated; if (vsDraw.selforeset) textFore = vsDraw.selforeground.allocated; } else { if (marks) textBack = markBack; if ((edgeState == EDGE_BACKGROUND) && (i >= theEdge) && (ll.chars[i] != '\n') && (ll.chars[i] != '\r')) textBack = vs.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) { surface->PenColour(textFore); 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])) { 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 do 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) { for (int cpos = 0; cpos <= i - startseg; cpos++) { if (ll.chars[cpos + startseg] == ' ') { 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); } } } } } 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 > selStart) && (posLineEnd <= selEnd) && (selStart != selEnd); if (eolInSelection && !hideSelection && vsDraw.selbackset && (line < pdoc->LinesTotal()-1)) { surface->FillRectangle(rcSegment, vsDraw.selbackground.allocated); } else if (marks) { surface->FillRectangle(rcSegment, markBack); } else { surface->FillRectangle(rcSegment, vsDraw.styles[ll.styles[ll.numCharsInLine] & styleMask].back.allocated); } rcSegment.left = xEol + vsDraw.aveCharWidth + xStart; rcSegment.right = rcLine.right; if (marks) { surface->FillRectangle(rcSegment, markBack); } 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 (edgeState == EDGE_LINE) { int edgeX = theEdge * vsDraw.spaceWidth; rcSegment.left = edgeX + xStart; rcSegment.right = rcSegment.left + 1; surface->FillRectangle(rcSegment, vs.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 (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, !wMain.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 statrting 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; if (endPosPaint > pdoc->GetEndStyled()) { // Notify container to do some more styling NotifyStyleNeeded(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 NotifyStyleNeeded 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); Surface *surface = 0; if (rcArea.right > vs.fixedColumnWidth) { if (bufferedDraw) { surface = &pixmapLine; } else { surface = surfaceWindow; } 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); //GTimer *tim=g_timer_new(); while (visibleLine <= cs.LinesDisplayed() && yposScreen < rcArea.bottom) { //g_timer_start(tim); //Platform::DebugPrintf("Painting line %d\n", line); 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; // 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); // Highlight the current braces if any if ((braces[0] >= posLineStart) && (braces[0] < posLineEnd)) ll.styles[braces[0] - posLineStart] = bracesMatchStyle; if ((braces[1] >= posLineStart) && (braces[1] < posLineEnd)) ll.styles[braces[1] - posLineStart] = bracesMatchStyle; // Draw the line if (cs.GetVisible(line)) DrawLine(surface, vs, line, 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 xposCaret = ll.positions[posCaret - posLineStart] + xStart; int widthOverstrikeCaret = ll.positions[posCaret - posLineStart + 1] - ll.positions[posCaret - posLineStart]; if (posCaret == pdoc->Length()) // At end of document widthOverstrikeCaret = vs.aveCharWidth; if ((posCaret - posLineStart) >= ll.numCharsInLine) // At end of line widthOverstrikeCaret = vs.aveCharWidth; if (widthOverstrikeCaret < 3) // Make sure its visible widthOverstrikeCaret = 3; if (((caret.active && caret.on) || (posDrag >= 0)) && xposCaret >= 0) { PRectangle rcCaret = rcLine; if (posDrag >= 0) { rcCaret.left = xposCaret; rcCaret.right = xposCaret + 1; } else { if (inOverstrike) { rcCaret.top = rcCaret.bottom - 2; rcCaret.left = xposCaret + 1; rcCaret.right = rcCaret.left + widthOverstrikeCaret - 1; } else { rcCaret.left = xposCaret; rcCaret.right = xposCaret + 1; } } 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); 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 (edgeState == EDGE_LINE) { int edgeX = theEdge * vs.spaceWidth; rcBeyondEOF.left = edgeX + xStart; rcBeyondEOF.right = rcBeyondEOF.left + 1; surfaceWindow->FillRectangle(rcBeyondEOF, vs.edgecolour.allocated); } } } } // Space (3 space characters) between line numbers and text when printing. #define lineNumberPrintSpace " " // 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, FORMATRANGE *pfr) { if (!pfr) return 0; Surface *surface = new Surface(); surface->Init(pfr->hdc); Surface *surfaceMeasure = new Surface(); surfaceMeasure->Init(pfr->hdcTarget); 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 = 0; // Don't show the selection when printing vsPrint.selbackset = false; vsPrint.selforeset = false; // 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, "9999" lineNumberPrintSpace, 4 + 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); if (endPosPrint > pdoc->GetEndStyled()) { // Notify container to do some more styling NotifyStyleNeeded(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) { 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 += lineNumberWidth - 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); } // 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); // Draw the line DrawLine(surface, vsPrint, line, xStart, rcLine, ll); ypos += vsPrint.lineHeight; line++; } } delete surface; delete surfaceMeasure; return endPosPrint; } 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) { 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->InsertChar(currentPos, ch); SetEmptySelection(currentPos + 1); EnsureCaretVisible(); SetLastXChosen(); NotifyChar(ch); } void Editor::ClearSelection() { if (selType == selRectangle) { pdoc->BeginUndoAction(); int lineStart = pdoc->LineFromPosition(SelectionStart()); int lineEnd = pdoc->LineFromPosition(SelectionEnd()); int startPos = SelectionStart(); int line; for (line=lineStart; line <= lineEnd; line++) { startPos = SelectionStart(line); unsigned int chars = SelectionEnd(line) - startPos; if (0 != chars) { pdoc->DeleteChars(startPos, chars); } } SetEmptySelection(startPos); selType = selStream; pdoc->EndUndoAction(); } else { int startPos = SelectionStart(); unsigned int chars = SelectionEnd() - startPos; SetEmptySelection(startPos); if (0 != chars) { pdoc->DeleteChars(startPos, chars); } } } void Editor::ClearAll() { if (0 != pdoc->Length()) { pdoc->DeleteChars(0, pdoc->Length()); } cs.Clear(); anchor = 0; currentPos = 0; SetTopLine(0); SetVerticalScrollPos(); } void Editor::Cut() { Copy(); ClearSelection(); } void Editor::PasteRectangular(int pos, const char *ptr, int len) { currentPos = pos; int insertPos = currentPos; int xInsert = XFromPosition(currentPos); int line = pdoc->LineFromPosition(currentPos); bool prevCr = false; for (int i=0; i= pdoc->LinesTotal()) { if (pdoc->eolMode != SC_EOL_LF) pdoc->InsertChar(pdoc->Length(), '\r'); if (pdoc->eolMode != SC_EOL_CR) pdoc->InsertChar(pdoc->Length(), '\n'); } currentPos = PositionFromLineX(line, xInsert); prevCr = ptr[i] == '\r'; } else { pdoc->InsertString(currentPos, ptr+i, 1); currentPos++; insertPos = currentPos; prevCr = false; } } SetEmptySelection(insertPos); } 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()) { 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); } void Editor::DelCharBack() { if (currentPos == anchor) { int newPos = pdoc->DelCharBack(currentPos); SetEmptySelection(newPos); } else { ClearSelection(); SetEmptySelection(currentPos); } } void Editor::NotifyFocus(bool) { } void Editor::NotifyStyleNeeded(int endStyleNeeded) { SCNotification scn; scn.nmhdr.code = SCN_STYLENEEDED; scn.position = endStyleNeeded; NotifyParent(scn); } void Editor::NotifyChar(char ch) { SCNotification scn; scn.nmhdr.code = SCN_CHARADDED; scn.ch = ch; NotifyParent(scn); #ifdef MACRO_SUPPORT if (recordingMacro) { char txt[2]; txt[0] = ch; txt[1] = '\0'; NotifyMacroRecord(EM_REPLACESEL, 0, (LPARAM) 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); } 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); } // Notifications from document void Editor::NotifyModifyAttempt(Document*, void *) { //Platform::DebugPrintf("** Modify Attempt\n"); NotifyModifyAttempt(); } 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 (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) { NotifyNeedShown(mh.position, mh.length); 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 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 *document, void *userData) { /* Do nothing */ } #ifdef MACRO_SUPPORT void Editor::NotifyMacroRecord(UINT iMessage, WPARAM wParam, LPARAM lParam) { // Enumerates all macroable messages switch (iMessage) { case WM_CUT: case WM_COPY: case WM_PASTE: case WM_CLEAR: case EM_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_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_NEWLINE: case SCI_FORMFEED: case SCI_VCHOME: case SCI_VCHOMEEXTEND: case SCI_DELWORDLEFT: case SCI_DELWORDRIGHT: break; // Filter out all others (display changes, etc) 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); } } int Editor::KeyCommand(UINT iMessage) { Point pt = LocationFromPosition(currentPos); switch (iMessage) { case SCI_LINEDOWN: MovePositionTo(PositionFromLocation( Point(lastXChosen, pt.y + vs.lineHeight))); break; case SCI_LINEDOWNEXTEND: MovePositionTo(PositionFromLocation( Point(lastXChosen, pt.y + vs.lineHeight)), true); break; case SCI_LINEUP: MovePositionTo(PositionFromLocation( Point(lastXChosen, pt.y - vs.lineHeight))); break; case SCI_LINEUPEXTEND: MovePositionTo(PositionFromLocation( Point(lastXChosen, pt.y - vs.lineHeight)), true); break; case SCI_CHARLEFT: if (SelectionEmpty()) { MovePositionTo(MovePositionSoVisible(currentPos - 1, -1)); } else { MovePositionTo(SelectionStart()); } SetLastXChosen(); break; case SCI_CHARLEFTEXTEND: MovePositionTo(MovePositionSoVisible(currentPos - 1, -1), true); SetLastXChosen(); break; case SCI_CHARRIGHT: if (SelectionEmpty()) { MovePositionTo(MovePositionSoVisible(currentPos + 1, 1)); } else { MovePositionTo(SelectionEnd()); } SetLastXChosen(); break; case SCI_CHARRIGHTEXTEND: MovePositionTo(MovePositionSoVisible(currentPos + 1, 1), true); SetLastXChosen(); break; case SCI_WORDLEFT: MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, -1), -1)); SetLastXChosen(); break; case SCI_WORDLEFTEXTEND: MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, -1), -1), true); SetLastXChosen(); break; case SCI_WORDRIGHT: MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, 1), 1)); SetLastXChosen(); break; case SCI_WORDRIGHTEXTEND: MovePositionTo(MovePositionSoVisible(pdoc->NextWordStart(currentPos, 1), 1), true); SetLastXChosen(); break; case SCI_HOME: MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos))); SetLastXChosen(); break; case SCI_HOMEEXTEND: MovePositionTo(pdoc->LineStart(pdoc->LineFromPosition(currentPos)), true); SetLastXChosen(); break; case SCI_LINEEND: MovePositionTo(pdoc->LineEndPosition(currentPos)); SetLastXChosen(); break; case SCI_LINEENDEXTEND: MovePositionTo(pdoc->LineEndPosition(currentPos), true); SetLastXChosen(); break; case SCI_DOCUMENTSTART: MovePositionTo(0); SetLastXChosen(); break; case SCI_DOCUMENTSTARTEXTEND: MovePositionTo(0, true); SetLastXChosen(); break; case SCI_DOCUMENTEND: MovePositionTo(pdoc->Length()); SetLastXChosen(); break; case SCI_DOCUMENTENDEXTEND: MovePositionTo(pdoc->Length(), true); SetLastXChosen(); break; case SCI_PAGEUP: PageMove( -1); break; case SCI_PAGEUPEXTEND: PageMove( -1, true); break; case SCI_PAGEDOWN: PageMove(1); break; case SCI_PAGEDOWNEXTEND: PageMove(1, true); break; case SCI_EDITTOGGLEOVERTYPE: inOverstrike = !inOverstrike; DropCaret(); ShowCaretAtCurrentPosition(); break; case SCI_CANCEL: // Cancel any modes - handled in subclass // Also unselect text SetEmptySelection(currentPos); break; case SCI_DELETEBACK: DelCharBack(); EnsureCaretVisible(); break; case SCI_TAB: Indent(true); break; case SCI_BACKTAB: Indent(false); break; case SCI_NEWLINE: ClearSelection(); if (pdoc->eolMode == SC_EOL_CRLF) { pdoc->InsertString(currentPos, "\r\n"); SetEmptySelection(currentPos + 2); NotifyChar('\r'); NotifyChar('\n'); } else if (pdoc->eolMode == SC_EOL_CR) { pdoc->InsertChar(currentPos, '\r'); SetEmptySelection(currentPos + 1); NotifyChar('\r'); } else if (pdoc->eolMode == SC_EOL_LF) { pdoc->InsertChar(currentPos, '\n'); SetEmptySelection(currentPos + 1); NotifyChar('\n'); } SetLastXChosen(); EnsureCaretVisible(); break; case SCI_FORMFEED: AddChar('\f'); break; case SCI_VCHOME: MovePositionTo(pdoc->VCHomePosition(currentPos)); SetLastXChosen(); break; case SCI_VCHOMEEXTEND: MovePositionTo(pdoc->VCHomePosition(currentPos), true); SetLastXChosen(); break; case SCI_ZOOMIN: if (vs.zoomLevel < 20) vs.zoomLevel++; InvalidateStyleRedraw(); break; case SCI_ZOOMOUT: if (vs.zoomLevel > -10) vs.zoomLevel--; InvalidateStyleRedraw(); break; case SCI_DELWORDLEFT: { int startWord = pdoc->NextWordStart(currentPos, -1); pdoc->DeleteChars(startWord, currentPos - startWord); MovePositionTo(startWord); } break; case SCI_DELWORDRIGHT: { int endWord = pdoc->NextWordStart(currentPos, 1); pdoc->DeleteChars(currentPos, endWord - currentPos); } break; } return 0; } int Editor::KeyDefault(int, int) { return 0; } int Editor::KeyDown(int key, bool shift, bool ctrl, bool alt) { int modifiers = (shift ? SCI_SHIFT : 0) | (ctrl ? SCI_CTRL : 0) | (alt ? SCI_ALT : 0); int msg = kmap.Find(key, modifiers); if (msg) return WndProc(msg, 0, 0); else return KeyDefault(key, modifiers); } void Editor::SetWhitespaceVisible(bool view) { vs.viewWhitespace = view; } bool Editor::GetWhitespaceVisible() { return vs.viewWhitespace; } void Editor::Indent(bool forwards) { //Platform::DebugPrintf("INdent %d\n", forwards); int lineOfAnchor = pdoc->LineFromPosition(anchor); int lineCurrentPos = pdoc->LineFromPosition(currentPos); if (lineOfAnchor == lineCurrentPos) { ClearSelection(); pdoc->InsertChar(currentPos, '\t'); //pdoc->InsertChar(currentPos++, '\t'); SetEmptySelection(currentPos + 1); } else { int anchorPosOnLine = anchor - pdoc->LineStart(lineOfAnchor); int currentPosPosOnLine = currentPos - pdoc->LineStart(lineCurrentPos); // Multiple lines selected so indent / dedent int lineTopSel = Platform::Minimum(lineOfAnchor, lineCurrentPos); int lineBottomSel = Platform::Maximum(lineOfAnchor, lineCurrentPos); if (pdoc->LineStart(lineBottomSel) == anchor || pdoc->LineStart(lineBottomSel) == currentPos) lineBottomSel--; // If not selecting any characters on a line, do not indent pdoc->BeginUndoAction(); pdoc->Indent(forwards, lineBottomSel, lineTopSel); pdoc->EndUndoAction(); if (lineOfAnchor < lineCurrentPos) { if (currentPosPosOnLine == 0) SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor)); else SetSelection(pdoc->LineStart(lineCurrentPos + 1), pdoc->LineStart(lineOfAnchor)); } else { if (anchorPosOnLine == 0) SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor)); else SetSelection(pdoc->LineStart(lineCurrentPos), pdoc->LineStart(lineOfAnchor + 1)); } } } long Editor::FindText(UINT iMessage, WPARAM wParam, LPARAM lParam) { FINDTEXTEX *ft = reinterpret_cast(lParam); int pos = pdoc->FindText(ft->chrg.cpMin, ft->chrg.cpMax, ft->lpstrText, wParam & FR_MATCHCASE, wParam & FR_WHOLEWORD); if (pos != -1) { if (iMessage == EM_FINDTEXTEX) { ft->chrgText.cpMin = pos; ft->chrgText.cpMax = pos + strlen(ft->lpstrText); } } return pos; } // Relocatable search support : Searches relative to current selection // point and sets the selection to the found text range with // each search. // Anchor following searches at current selection start: This allows // multiple incremental interactive searches to be macro recorded // while still setting the selection to found text so the find/select // operation is self-contained. void Editor::SearchAnchor() { searchAnchor = SelectionStart(); } // Find text from current search anchor: Must call SearchAnchor first. // Accepts both SCI_SEARCHNEXT and SCI_SEARCHPREV. // wParam contains search modes : ORed FR_MATCHCASE and FR_WHOLEWORD. // lParam contains the text to search for. long Editor::SearchText(UINT iMessage, WPARAM wParam, LPARAM lParam) { const char *txt = reinterpret_cast(lParam); int pos; if (iMessage == SCI_SEARCHNEXT) { pos = pdoc->FindText(searchAnchor, pdoc->Length(), txt, wParam & FR_MATCHCASE, wParam & FR_WHOLEWORD); } else { pos = pdoc->FindText(searchAnchor, 0, txt, wParam & FR_MATCHCASE, wParam & FR_WHOLEWORD); } if (pos != -1) { SetSelection(pos, pos + strlen(txt)); } return pos; } void Editor::GoToLine(int lineNo) { if (lineNo > pdoc->LinesTotal()) lineNo = pdoc->LinesTotal(); if (lineNo < 0) lineNo = 0; SetEmptySelection(pdoc->LineStart(lineNo)); ShowCaretAtCurrentPosition(); EnsureCaretVisible(); } static bool Close(Point pt1, Point pt2) { if (abs(pt1.x - pt2.x) > 3) return false; if (abs(pt1.y - pt2.y) > 3) return false; return true; } char *Editor::CopyRange(int start, int end) { char *text = 0; if (start < end) { int len = end - start; text = new char[len + 1]; if (text) { for (int i = 0; i < len; i++) { text[i] = pdoc->CharAt(start + i); } text[len] = '\0'; } } return text; } int Editor::SelectionRangeLength() { if (selType == selRectangle) { int lineStart = pdoc->LineFromPosition(SelectionStart()); int lineEnd = pdoc->LineFromPosition(SelectionEnd()); int totalSize = 0; for (int line=lineStart; line <= lineEnd; line++) { totalSize += SelectionEnd(line) - SelectionStart(line) + 1; if (pdoc->eolMode == SC_EOL_CRLF) totalSize++; } return totalSize; } else { return SelectionEnd() - SelectionStart(); } } char *Editor::CopySelectionRange() { if (selType == selRectangle) { char *text = 0; int lineStart = pdoc->LineFromPosition(SelectionStart()); int lineEnd = pdoc->LineFromPosition(SelectionEnd()); int totalSize = SelectionRangeLength(); if (totalSize > 0) { text = new char[totalSize + 1]; if (text) { int j = 0; for (int line=lineStart; line <= lineEnd; line++) { for (int i=SelectionStart(line);iCharAt(i); } if (pdoc->eolMode != SC_EOL_LF) text[j++] = '\r'; if (pdoc->eolMode != SC_EOL_CR) text[j++] = '\n'; } text[totalSize] = '\0'; } } return text; } else { return CopyRange(SelectionStart(), SelectionEnd()); } } void Editor::CopySelectionIntoDrag() { delete []dragChars; dragChars = 0; lenDrag = SelectionRangeLength(); dragChars = CopySelectionRange(); dragIsRectangle = selType == selRectangle; if (!dragChars) { lenDrag = 0; } } void Editor::SetDragPosition(int newPos) { if (newPos >= 0) { newPos = pdoc->MovePositionOutsideChar(newPos, 1); posDrop = newPos; } if (posDrag != newPos) { caret.on = true; SetTicking(true); InvalidateCaret(); posDrag = newPos; InvalidateCaret(); } } void Editor::StartDrag() { // Always handled by subclasses //SetMouseCapture(true); //wDraw.SetCursor(Window::cursorArrow); } void Editor::DropAt(int position, const char *value, bool moving, bool rectangular) { //Platform::DebugPrintf("DropAt %d\n", inDragDrop); if (inDragDrop) dropWentOutside = false; int positionWasInSelection = PositionInSelection(position); if ((!inDragDrop) || !(0 == positionWasInSelection)) { int selStart = SelectionStart(); int selEnd = SelectionEnd(); pdoc->BeginUndoAction(); int positionAfterDeletion = position; if (inDragDrop && moving) { // Remove dragged out text if (rectangular) { int lineStart = pdoc->LineFromPosition(SelectionStart()); int lineEnd = pdoc->LineFromPosition(SelectionEnd()); for (int line=lineStart; line <= lineEnd; line++) { int startPos = SelectionStart(line); int endPos = SelectionEnd(line); if (position >= startPos) { if (position > endPos) { positionAfterDeletion -= endPos - startPos; } else { positionAfterDeletion -= position - startPos; } } } } else { if (position > selStart) { positionAfterDeletion -= selEnd - selStart; } } ClearSelection(); } position = positionAfterDeletion; if (rectangular) { PasteRectangular(position, value, strlen(value)); pdoc->EndUndoAction(); // Should try to select new rectangle but it may not be a rectangle now so just select the drop position SetSelection(position, position); } else { position = pdoc->MovePositionOutsideChar(position, currentPos - position); pdoc->InsertString(position, value); pdoc->EndUndoAction(); SetSelection(position + strlen(value), position); } } else if (inDragDrop) { SetSelection(position, position); } } static int BeforeInOrAfter(int val, int minim, int maxim) { if (val < minim) return -1; else if (val > maxim) return 1; else return 0; } int Editor::PositionInSelection(int pos) { pos = pdoc->MovePositionOutsideChar(pos, currentPos - pos); if (selType == selRectangle) { if (pos < SelectionStart()) return -1; if (pos > SelectionEnd()) return 1; int linePos = pdoc->LineFromPosition(pos); return BeforeInOrAfter(pos, SelectionStart(linePos), SelectionEnd(linePos)); } else { if (currentPos > anchor) { return BeforeInOrAfter(pos, anchor, currentPos); } else if (currentPos < anchor) { return BeforeInOrAfter(pos, currentPos, anchor); } } return 1; } bool Editor::PointInSelection(Point pt) { // TODO: fix up for rectangular selection int pos = PositionFromLocation(pt); if (0 == PositionInSelection(pos)) { if (pos == SelectionStart()) { // see if just before selection Point locStart = LocationFromPosition(pos); if (pt.x < locStart.x) return false; } if (pos == SelectionEnd()) { // see if just after selection Point locEnd = LocationFromPosition(pos); if (pt.x > locEnd.x) return false; } return true; } return false; } bool Editor::PointInSelMargin(Point pt) { // Really means: "Point in a margin" if (vs.fixedColumnWidth > 0) { // There is a margin PRectangle rcSelMargin = GetClientRectangle(); rcSelMargin.right = vs.fixedColumnWidth - vs.leftMarginWidth; return rcSelMargin.Contains(pt); } else { return false; } } void Editor::ButtonDown(Point pt, unsigned int curTime, bool shift, bool ctrl, bool alt) { //Platform::DebugPrintf("Scintilla:ButtonDown %d %d = %d alt=%d\n", curTime, lastClickTime, curTime - lastClickTime, alt); ptMouseLast = pt; int newPos = PositionFromLocation(pt); newPos = pdoc->MovePositionOutsideChar(newPos, currentPos - newPos); inDragDrop = false; bool processed = NotifyMarginClick(pt, shift, ctrl, alt); if (processed) return; if (shift) { SetSelection(newPos); } if (((curTime - lastClickTime) < Platform::DoubleClickTime()) && Close(pt, lastClick)) { //Platform::DebugPrintf("Double click %d %d = %d\n", curTime, lastClickTime, curTime - lastClickTime); SetMouseCapture(true); SetEmptySelection(newPos); bool doubleClick = false; // Stop mouse button bounce changing selection type if (curTime != lastClickTime) { if (selectionType == selChar) { selectionType = selWord; doubleClick = true; } else if (selectionType == selWord) { selectionType = selLine; } else { selectionType = selChar; originalAnchorPos = currentPos; } } if (selectionType == selWord) { if (currentPos >= originalAnchorPos) { // Moved forward SetSelection(pdoc->ExtendWordSelect(currentPos, 1), pdoc->ExtendWordSelect(originalAnchorPos, -1)); } else { // Moved backward SetSelection(pdoc->ExtendWordSelect(currentPos, -1), pdoc->ExtendWordSelect(originalAnchorPos, 1)); } } else if (selectionType == selLine) { lineAnchor = LineFromLocation(pt); SetSelection(pdoc->LineStart(lineAnchor + 1), pdoc->LineStart(lineAnchor)); //Platform::DebugPrintf("Triple click: %d - %d\n", anchor, currentPos); } else { SetEmptySelection(currentPos); } //Platform::DebugPrintf("Double click: %d - %d\n", anchor, currentPos); if (doubleClick) NotifyDoubleClick(pt, shift); } else { // Single click if (PointInSelMargin(pt)) { if (ctrl) { SelectAll(); lastClickTime = curTime; return; } lineAnchor = LineFromLocation(pt); // While experimenting with folding turn off line selection if (!shift) { // Single click in margin: select whole line SetSelection(pdoc->LineStart(lineAnchor + 1), pdoc->LineStart(lineAnchor)); } else { // Single shift+click in margin: select from anchor to beginning of clicked line SetSelection(pdoc->LineStart(lineAnchor), anchor); } SetDragPosition(invalidPosition); SetMouseCapture(true); selectionType = selLine; } else { if (!shift) { inDragDrop = PointInSelection(pt); } if (inDragDrop) { SetMouseCapture(false); SetDragPosition(newPos); CopySelectionIntoDrag(); StartDrag(); } else { selType = alt ? selRectangle : selStream; xStartSelect = pt.x - vs.fixedColumnWidth + xOffset; xEndSelect = pt.x - vs.fixedColumnWidth + xOffset; SetDragPosition(invalidPosition); SetMouseCapture(true); if (!shift) SetEmptySelection(newPos); selectionType = selChar; originalAnchorPos = currentPos; } } } lastClickTime = curTime; lastXChosen = pt.x; ShowCaretAtCurrentPosition(); } void Editor::ButtonMove(Point pt) { //Platform::DebugPrintf("Move %d %d\n", pt.x, pt.y); if (HaveMouseCapture()) { xEndSelect = pt.x - vs.fixedColumnWidth + xOffset; ptMouseLast = pt; int movePos = PositionFromLocation(pt); movePos = pdoc->MovePositionOutsideChar(movePos, currentPos - movePos); if (posDrag >= 0) { SetDragPosition(movePos); } else { if (selectionType == selChar) { SetSelection(movePos); } else if (selectionType == selWord) { // Continue selecting by word if (currentPos > originalAnchorPos) { // Moved forward SetSelection(pdoc->ExtendWordSelect(movePos, 1), pdoc->ExtendWordSelect(originalAnchorPos, -1)); } else { // Moved backward SetSelection(pdoc->ExtendWordSelect(movePos, -1), pdoc->ExtendWordSelect(originalAnchorPos, 1)); } } else { // Continue selecting by line int lineMove = LineFromLocation(pt); if (lineAnchor < lineMove) { SetSelection(pdoc->LineStart(lineMove + 1), pdoc->LineStart(lineAnchor)); } else { SetSelection(pdoc->LineStart(lineMove), pdoc->LineStart(lineAnchor + 1)); } } } EnsureCaretVisible(false); } else { if (vs.fixedColumnWidth > 0) { // There is a margin if (PointInSelMargin(pt)) { wDraw.SetCursor(Window::cursorReverseArrow); return; // No need to test for selection } } // Display regular (drag) cursor over selection if (PointInSelection(pt)) wDraw.SetCursor(Window::cursorArrow); else wDraw.SetCursor(Window::cursorText); } } void Editor::ButtonUp(Point pt, unsigned int curTime, bool ctrl) { //Platform::DebugPrintf("ButtonUp %d\n", HaveMouseCapture()); if (HaveMouseCapture()) { if (PointInSelMargin(pt)) { wDraw.SetCursor(Window::cursorReverseArrow); } else { wDraw.SetCursor(Window::cursorText); } xEndSelect = pt.x - vs.fixedColumnWidth + xOffset; ptMouseLast = pt; SetMouseCapture(false); int newPos = PositionFromLocation(pt); newPos = pdoc->MovePositionOutsideChar(newPos, currentPos - newPos); if (inDragDrop) { int selStart = SelectionStart(); int selEnd = SelectionEnd(); if (selStart < selEnd) { if (dragChars && lenDrag) { if (ctrl) { pdoc->InsertString(newPos, dragChars, lenDrag); SetSelection(newPos, newPos + lenDrag); } else if (newPos < selStart) { pdoc->DeleteChars(selStart, lenDrag); pdoc->InsertString(newPos, dragChars, lenDrag); SetSelection(newPos, newPos + lenDrag); } else if (newPos > selEnd) { pdoc->DeleteChars(selStart, lenDrag); newPos -= lenDrag; pdoc->InsertString(newPos, dragChars, lenDrag); SetSelection(newPos, newPos + lenDrag); } else { SetEmptySelection(newPos); } delete []dragChars; dragChars = 0; lenDrag = 0; } selectionType = selChar; } } else { if (selectionType == selChar) { SetSelection(newPos); } } lastClickTime = curTime; lastClick = pt; lastXChosen = pt.x; inDragDrop = false; EnsureCaretVisible(false); } } // Called frequently to perform background UI including // caret blinking and automatic scrolling. void Editor::Tick() { if (HaveMouseCapture()) { // Auto scroll ButtonMove(ptMouseLast); } if (caret.period > 0) { timer.ticksToWait -= timer.tickSize; if (timer.ticksToWait <= 0) { caret.on = !caret.on; timer.ticksToWait = caret.period; InvalidateCaret(); } } } static bool IsIn(int a, int minimum, int maximum) { return (a >= minimum) && (a <= maximum); } static bool IsOverlap(int mina, int maxa, int minb, int maxb) { return IsIn(mina, minb, maxb) || IsIn(maxa, minb, maxb) || IsIn(minb, mina, maxa) || IsIn(maxb, mina, maxa); } void Editor::CheckForChangeOutsidePaint(Range r) { if (paintState == painting && !paintingAllText) { //Platform::DebugPrintf("Checking range in paint %d-%d\n", r.start, r.end); if (!r.Valid()) return; PRectangle rcText = GetTextRectangle(); // Determine number of lines displayed including a possible partially displayed last line int linesDisplayed = (rcText.bottom - rcText.top - 1) / vs.lineHeight + 1; int bottomLine = topLine + linesDisplayed - 1; int lineRangeStart = cs.DisplayFromDoc(pdoc->LineFromPosition(r.start)); int lineRangeEnd = cs.DisplayFromDoc(pdoc->LineFromPosition(r.end)); if (!IsOverlap(topLine, bottomLine, lineRangeStart, lineRangeEnd)) { //Platform::DebugPrintf("No overlap (%d-%d) with window(%d-%d)\n", // lineRangeStart, lineRangeEnd, topLine, bottomLine); return; } // Assert rcPaint contained within or equal to rcText if (rcPaint.top > rcText.top) { // does range intersect rcText.top .. rcPaint.top int paintTopLine = ((rcPaint.top - rcText.top-1) / vs.lineHeight) + topLine; // paintTopLine is the top line of the paint rectangle or the line just above if that line is completely inside the paint rectangle if (IsOverlap(topLine, paintTopLine, lineRangeStart, lineRangeEnd)) { //Platform::DebugPrintf("Change (%d-%d) in top npv(%d-%d)\n", // lineRangeStart, lineRangeEnd, topLine, paintTopLine); paintState = paintAbandoned; return; } } if (rcPaint.bottom < rcText.bottom) { // does range intersect rcPaint.bottom .. rcText.bottom int paintBottomLine = ((rcPaint.bottom - rcText.top-1) / vs.lineHeight + 1) + topLine; // paintTopLine is the bottom line of the paint rectangle or the line just below if that line is completely inside the paint rectangle if (IsOverlap(paintBottomLine, bottomLine, lineRangeStart, lineRangeEnd)) { //Platform::DebugPrintf("Change (%d-%d) in bottom npv(%d-%d)\n", // lineRangeStart, lineRangeEnd, paintBottomLine, bottomLine); paintState = paintAbandoned; return; } } } } char BraceOpposite(char ch) { switch (ch) { case '(': return ')'; case ')': return '('; case '[': return ']'; case ']': return '['; case '{': return '}'; case '}': return '{'; case '<': return '>'; case '>': return '<'; default: return '\0'; } } // TODO: should be able to extend styled region to find matching brace // TODO: may need to make DBCS safe // so should be moved into Document int Editor::BraceMatch(int position, int maxReStyle) { char chBrace = pdoc->CharAt(position); char chSeek = BraceOpposite(chBrace); if (!chSeek) return - 1; char styBrace = pdoc->StyleAt(position) & pdoc->stylingBitsMask; int direction = -1; if (chBrace == '(' || chBrace == '[' || chBrace == '{' || chBrace == '<') direction = 1; int depth = 1; position = position + direction; while ((position >= 0) && (position < pdoc->Length())) { char chAtPos = pdoc->CharAt(position); char styAtPos = pdoc->StyleAt(position) & pdoc->stylingBitsMask; if ((position > pdoc->GetEndStyled()) || (styAtPos == styBrace)) { if (chAtPos == chBrace) depth++; if (chAtPos == chSeek) depth--; if (depth == 0) return position; } position = position + direction; } return - 1; } void Editor::SetBraceHighlight(Position pos0, Position pos1, int matchStyle) { if ((pos0 != braces[0]) || (pos1 != braces[1]) || (matchStyle != bracesMatchStyle)) { if ((braces[0] != pos0) || (matchStyle != bracesMatchStyle)) { CheckForChangeOutsidePaint(Range(braces[0])); CheckForChangeOutsidePaint(Range(pos0)); braces[0] = pos0; } if ((braces[1] != pos1) || (matchStyle != bracesMatchStyle)) { CheckForChangeOutsidePaint(Range(braces[1])); CheckForChangeOutsidePaint(Range(pos1)); braces[1] = pos1; } bracesMatchStyle = matchStyle; if (paintState == notPainting) { Redraw(); } } } void Editor::SetDocPointer(Document *document) { //Platform::DebugPrintf("** %x setdoc to %x\n", pdoc, document); pdoc->RemoveWatcher(this, 0); pdoc->Release(); if (document == NULL) { pdoc = new Document(); } else { pdoc = document; } pdoc->AddRef(); // Reset the contraction state to fully shown. cs.Clear(); cs.InsertLines(0, pdoc->LinesTotal()); pdoc->AddWatcher(this, 0); Redraw(); SetScrollBars(); } // Recursively expand a fold, making lines visible except where they have an unexpanded parent void Editor::Expand(int &line, bool doExpand) { int lineMaxSubord = pdoc->GetLastChild(line); line++; while (line <= lineMaxSubord) { if (doExpand) cs.SetVisible(line, line, true); int level = pdoc->GetLevel(line); if (level & SC_FOLDLEVELHEADERFLAG) { if (doExpand && cs.GetExpanded(line)) { Expand(line, true); } else { Expand(line, false); } } else { line++; } } } void Editor::ToggleContraction(int line) { if (pdoc->GetLevel(line) & SC_FOLDLEVELHEADERFLAG) { if (cs.GetExpanded(line)) { int lineMaxSubord = pdoc->GetLastChild(line); cs.SetExpanded(line, 0); if (lineMaxSubord > line) { cs.SetVisible(line+1, lineMaxSubord, false); SetScrollBars(); Redraw(); } } else { cs.SetExpanded(line, 1); Expand(line, true); SetScrollBars(); Redraw(); } } } // Recurse up from this line to find any folds that prevent this line from being visible // and unfold them all. void Editor::EnsureLineVisible(int line) { if (!cs.GetVisible(line)) { int lineParent = pdoc->GetFoldParent(line); if (lineParent >= 0) { if (line != lineParent) EnsureLineVisible(lineParent); if (!cs.GetExpanded(lineParent)) { cs.SetExpanded(lineParent, 1); Expand(lineParent, true); } } SetScrollBars(); Redraw(); } } LRESULT Editor::WndProc(UINT iMessage, WPARAM wParam, LPARAM lParam) { //Platform::DebugPrintf("S start wnd proc %d %d %d\n",iMessage, wParam, lParam); // Optional macro recording hook #ifdef MACRO_SUPPORT if (recordingMacro) NotifyMacroRecord(iMessage, wParam, lParam); #endif switch (iMessage) { case WM_GETTEXT: { if (lParam == 0) return 0; char *ptr = reinterpret_cast(lParam); unsigned int iChar = 0; for (; iChar < wParam-1; iChar++) ptr[iChar] = pdoc->CharAt(iChar); ptr[iChar] = '\0'; return iChar; } case WM_SETTEXT: { if (lParam == 0) return FALSE; pdoc->DeleteChars(0, pdoc->Length()); SetEmptySelection(0); pdoc->InsertString(0, reinterpret_cast(lParam)); return TRUE; } case WM_GETTEXTLENGTH: return pdoc->Length(); case WM_NOTIFY: //Platform::DebugPrintf("S notify %d %d\n", wParam, lParam); break; case WM_CUT: Cut(); SetLastXChosen(); break; case WM_COPY: Copy(); break; case WM_PASTE: Paste(); SetLastXChosen(); break; case WM_CLEAR: Clear(); SetLastXChosen(); break; case WM_UNDO: Undo(); SetLastXChosen(); break; // Edit control mesages // Not supported (no-ops): // EM_GETWORDBREAKPROC // EM_GETWORDBREAKPROCEX // EM_SETWORDBREAKPROC // EM_SETWORDBREAKPROCEX // EM_GETWORDWRAPMODE // EM_SETWORDWRAPMODE // EM_LIMITTEXT // EM_EXLIMITTEXT // EM_SETRECT // EM_SETRECTNP // EM_FMTLINES // EM_GETHANDLE // EM_SETHANDLE // EM_GETPASSWORDCHAR // EM_SETPASSWORDCHAR // EM_SETTABSTOPS // EM_FINDWORDBREAK // EM_GETCHARFORMAT // EM_SETCHARFORMAT // EM_GETOLEINTERFACE // EM_SETOLEINTERFACE // EM_SETOLECALLBACK // EM_GETPARAFORMAT // EM_SETPARAFORMAT // EM_PASTESPECIAL // EM_REQUESTRESIZE // EM_GETBKGNDCOLOR // EM_SETBKGNDCOLOR // EM_STREAMIN // EM_STREAMOUT // EM_GETIMECOLOR // EM_SETIMECOLOR // EM_GETIMEOPTIONS // EM_SETIMEOPTIONS // EM_GETOPTIONS // EM_SETOPTIONS // EM_GETPUNCTUATION // EM_SETPUNCTUATION // EM_GETTHUMB // Not supported but should be: // EM_GETEVENTMASK // EM_SETEVENTMASK // For printing: // EM_DISPLAYBAND // EM_SETTARGETDEVICE case EM_CANUNDO: return pdoc->CanUndo() ? TRUE : FALSE; case EM_UNDO: Undo(); break; case EM_EMPTYUNDOBUFFER: pdoc->DeleteUndoHistory(); return 0; case EM_GETFIRSTVISIBLELINE: return topLine; case EM_GETLINE: { if (lParam == 0) return 0; int lineStart = pdoc->LineStart(wParam); int lineEnd = pdoc->LineStart(wParam + 1); char *ptr = reinterpret_cast(lParam); WORD *pBufSize = reinterpret_cast(lParam); if (*pBufSize < lineEnd - lineStart) { ptr[0] = '\0'; // If no characters copied have to put a NUL into buffer return 0; } int iPlace = 0; for (int iChar = lineStart; iChar < lineEnd; iChar++) ptr[iPlace++] = pdoc->CharAt(iChar); return iPlace; } case EM_GETLINECOUNT: if (pdoc->LinesTotal() == 0) return 1; else return pdoc->LinesTotal(); case EM_GETMODIFY: return !pdoc->IsSavePoint(); case EM_SETMODIFY: // Not really supported now that there is the save point stuff //pdoc->isModified = wParam; //return pdoc->isModified; return false; case EM_GETRECT: if (lParam == 0) return 0; *(reinterpret_cast(lParam)) = GetClientRectangle(); break; case EM_GETSEL: if (wParam) *reinterpret_cast(wParam) = SelectionStart(); if (lParam) *reinterpret_cast(lParam) = SelectionEnd(); return MAKELONG(SelectionStart(), SelectionEnd()); case EM_EXGETSEL: { if (lParam == 0) return 0; CHARRANGE *pCR = reinterpret_cast(lParam); pCR->cpMin = SelectionStart(); pCR->cpMax = SelectionEnd(); } break; case EM_SETSEL: { int nStart = static_cast(wParam); int nEnd = static_cast(lParam); if (nEnd < 0) nEnd = pdoc->Length(); if (nStart < 0) nStart = nEnd; // Remove selection SetSelection(nEnd, nStart); EnsureCaretVisible(); } break; case EM_EXSETSEL: { if (lParam == 0) return 0; CHARRANGE *pCR = reinterpret_cast(lParam); if (pCR->cpMax == -1) { SetSelection(pCR->cpMin, pdoc->Length()); } else { SetSelection(pCR->cpMin, pCR->cpMax); } EnsureCaretVisible(); return pdoc->LineFromPosition(SelectionStart()); } case EM_GETSELTEXT: { if (lParam == 0) return 0; char *ptr = reinterpret_cast(lParam); int selSize = SelectionRangeLength(); char *text = CopySelectionRange(); int iChar = 0; if (text) { for (; iChar < selSize; iChar++) ptr[iChar] = text[iChar]; ptr[iChar] = '\0'; delete []text; } return iChar; } case EM_GETWORDBREAKPROC: return 0; case EM_SETWORDBREAKPROC: break; case EM_LIMITTEXT: // wParam holds the number of characters control should be limited to break; case EM_GETLIMITTEXT: return 0xffffffff; case EM_GETOLEINTERFACE: return 0; case EM_LINEFROMCHAR: if (static_cast(wParam) < 0) wParam = SelectionStart(); return pdoc->LineFromPosition(wParam); case EM_EXLINEFROMCHAR: if (static_cast(lParam) < 0) lParam = SelectionStart(); // Not specified, but probably OK return pdoc->LineFromPosition(lParam); case EM_LINEINDEX: if (static_cast(wParam) < 0) wParam = pdoc->LineFromPosition(SelectionStart()); if (wParam == 0) return 0; // Even if there is no text, there is a first line that starts at 0 if (static_cast(wParam) > pdoc->LinesTotal()) return - 1; //if (wParam > pdoc->LineFromPosition(pdoc->Length())) // Useful test, anyway... // return -1; return pdoc->LineStart(wParam); case EM_LINELENGTH: { if (static_cast(wParam) < 0) // Who use this anyway? return 0; // Should be... Too complex to describe here, see MS specs! if (static_cast(wParam) > pdoc->Length()) // Useful test, anyway... return 0; int line = pdoc->LineFromPosition(wParam); int charsOnLine = 0; for (int pos = pdoc->LineStart(line); pos < pdoc->LineStart(line + 1); pos++) { if ((pdoc->CharAt(pos) != '\r') && (pdoc->CharAt(pos) != '\n')) charsOnLine++; } return charsOnLine; } // Replacement of the old Scintilla interpretation of EM_LINELENGTH case SCI_LINELENGTH: if ((static_cast(wParam) < 0) || (static_cast(wParam) > pdoc->LineFromPosition(pdoc->Length()))) return 0; return pdoc->LineStart(wParam + 1) - pdoc->LineStart(wParam); case EM_REPLACESEL: { if (lParam == 0) return 0; pdoc->BeginUndoAction(); ClearSelection(); char *replacement = reinterpret_cast(lParam); pdoc->InsertString(currentPos, replacement); pdoc->EndUndoAction(); SetEmptySelection(currentPos + strlen(replacement)); EnsureCaretVisible(); } break; case EM_LINESCROLL: ScrollTo(topLine + lParam); HorizontalScrollTo(xOffset + wParam * vs.spaceWidth); return TRUE; case EM_SCROLLCARET: EnsureCaretVisible(); break; case EM_SETREADONLY: pdoc->SetReadOnly(wParam); return TRUE; case EM_SETRECT: break; case EM_CANPASTE: return 1; case EM_CHARFROMPOS: { if (lParam == 0) return 0; Point *ppt = reinterpret_cast(lParam); int pos = PositionFromLocation(*ppt); int line = pdoc->LineFromPosition(pos); return MAKELONG(pos, line); } case EM_POSFROMCHAR: { // The MS specs for this have changed 3 times: using the RichEdit 3 version if (wParam == 0) return 0; Point *ppt = reinterpret_cast(wParam); if (lParam < 0) { *ppt = Point(0, 0); } else { *ppt = LocationFromPosition(lParam); } return 0; } case EM_FINDTEXT: return FindText(iMessage, wParam, lParam); case EM_FINDTEXTEX: return FindText(iMessage, wParam, lParam); case EM_GETTEXTRANGE: { if (lParam == 0) return 0; TEXTRANGE *tr = reinterpret_cast(lParam); int cpMax = tr->chrg.cpMax; if (cpMax == -1) cpMax = pdoc->Length(); int len = cpMax - tr->chrg.cpMin; // No -1 as cpMin and cpMax are referring to inter character positions pdoc->GetCharRange(tr->lpstrText, tr->chrg.cpMin, len); // Spec says copied text is terminated with a NUL tr->lpstrText[len] = '\0'; return len; // Not including NUL } case EM_SELECTIONTYPE: if (currentPos == anchor) return SEL_EMPTY; else return SEL_TEXT; case EM_HIDESELECTION: hideSelection = wParam; Redraw(); break; case EM_FORMATRANGE: return FormatRange(wParam, reinterpret_cast(lParam)); case EM_GETMARGINS: return MAKELONG(vs.leftMarginWidth, vs.rightMarginWidth); case EM_SETMARGINS: if (wParam & EC_LEFTMARGIN) { vs.leftMarginWidth = LOWORD(lParam); } if (wParam & EC_RIGHTMARGIN) { vs.rightMarginWidth = HIWORD(lParam); } if (wParam == EC_USEFONTINFO) { vs.leftMarginWidth = vs.aveCharWidth / 2; vs.rightMarginWidth = vs.aveCharWidth / 2; } InvalidateStyleRedraw(); break; // Control specific mesages case SCI_ADDTEXT: { if (lParam == 0) return 0; pdoc->InsertString(CurrentPosition(), reinterpret_cast(lParam), wParam); SetEmptySelection(currentPos + wParam); return 0; } case SCI_ADDSTYLEDTEXT: { if (lParam == 0) return 0; pdoc->InsertStyledString(CurrentPosition() * 2, reinterpret_cast(lParam), wParam); SetEmptySelection(currentPos + wParam / 2); return 0; } case SCI_INSERTTEXT: { if (lParam == 0) return 0; int insertPos = wParam; if (static_cast(wParam) == -1) insertPos = CurrentPosition(); int newCurrent = CurrentPosition(); int newAnchor = anchor; char *sz = reinterpret_cast(lParam); pdoc->InsertString(insertPos, sz); if (newCurrent > insertPos) newCurrent += strlen(sz); if (newAnchor > insertPos) newAnchor += strlen(sz); SetEmptySelection(newCurrent); return 0; } case SCI_CLEARALL: ClearAll(); return 0; case SCI_SETUNDOCOLLECTION: pdoc->SetUndoCollection(static_cast(wParam)); return 0; #ifdef INCLUDE_DEPRECATED_FEATURES case SCI_APPENDUNDOSTARTACTION: pdoc->AppendUndoStartAction(); return 0; #endif case SCI_BEGINUNDOACTION: pdoc->BeginUndoAction(); return 0; case SCI_ENDUNDOACTION: pdoc->EndUndoAction(); return 0; case SCI_GETCARETPERIOD: return caret.period; case SCI_SETCARETPERIOD: caret.period = wParam; break; case SCI_SETWORDCHARS: { if (lParam == 0) return 0; pdoc->SetWordChars(reinterpret_cast(lParam)); } break; case SCI_GETLENGTH: return pdoc->Length(); case SCI_GETCHARAT: return pdoc->CharAt(wParam); case SCI_GETCURRENTPOS: return currentPos; case SCI_GETANCHOR: return anchor; case SCI_GETSTYLEAT: if (static_cast(wParam) >= pdoc->Length()) return 0; else return pdoc->StyleAt(wParam); case SCI_REDO: Redo(); break; case SCI_SELECTALL: SelectAll(); break; case SCI_SETSAVEPOINT: pdoc->SetSavePoint(); NotifySavePoint(true); break; case SCI_GETSTYLEDTEXT: { if (lParam == 0) return 0; TEXTRANGE *tr = reinterpret_cast(lParam); int iPlace = 0; for (int iChar = tr->chrg.cpMin; iChar < tr->chrg.cpMax; iChar++) { tr->lpstrText[iPlace++] = pdoc->CharAt(iChar); tr->lpstrText[iPlace++] = pdoc->StyleAt(iChar); } tr->lpstrText[iPlace] = '\0'; tr->lpstrText[iPlace + 1] = '\0'; return iPlace; } case SCI_CANREDO: return pdoc->CanRedo() ? TRUE : FALSE; case SCI_MARKERLINEFROMHANDLE: return pdoc->LineFromHandle(wParam); case SCI_MARKERDELETEHANDLE: pdoc->DeleteMarkFromHandle(wParam); break; case SCI_GETVIEWWS: return vs.viewWhitespace; case SCI_SETVIEWWS: vs.viewWhitespace = wParam; Redraw(); break; case SCI_GOTOLINE: GoToLine(wParam); break; case SCI_GOTOPOS: SetEmptySelection(wParam); EnsureCaretVisible(); Redraw(); break; case SCI_SETANCHOR: SetSelection(currentPos, wParam); break; case SCI_GETCURLINE: { if (lParam == 0) return 0; int lineCurrentPos = pdoc->LineFromPosition(currentPos); int lineStart = pdoc->LineStart(lineCurrentPos); unsigned int lineEnd = pdoc->LineStart(lineCurrentPos + 1); char *ptr = reinterpret_cast(lParam); unsigned int iPlace = 0; for (unsigned int iChar = lineStart; iChar < lineEnd && iPlace < wParam; iChar++) ptr[iPlace++] = pdoc->CharAt(iChar); ptr[iPlace] = '\0'; return currentPos - lineStart; } case SCI_GETENDSTYLED: return pdoc->GetEndStyled(); case SCI_GETEOLMODE: return pdoc->eolMode; case SCI_SETEOLMODE: pdoc->eolMode = wParam; break; case SCI_STARTSTYLING: pdoc->StartStyling(wParam, lParam); break; case SCI_SETSTYLING: pdoc->SetStyleFor(wParam, lParam); break; case SCI_SETSTYLINGEX: // Specify a complete styling buffer if (lParam == 0) return 0; pdoc->SetStyles(wParam, reinterpret_cast(lParam)); break; #ifdef INCLUDE_DEPRECATED_FEATURES case SCI_SETMARGINWIDTH: if (wParam < 100) { vs.ms[1].width = wParam; } InvalidateStyleRedraw(); break; #endif case SCI_SETBUFFEREDDRAW: bufferedDraw = wParam; break; case SCI_SETTABWIDTH: if (wParam > 0) pdoc->tabInChars = wParam; InvalidateStyleRedraw(); break; case SCI_SETCODEPAGE: pdoc->dbcsCodePage = wParam; break; #ifdef INCLUDE_DEPRECATED_FEATURES case SCI_SETLINENUMBERWIDTH: if (wParam < 200) { vs.ms[0].width = wParam; } InvalidateStyleRedraw(); break; #endif case SCI_SETUSEPALETTE: palette.allowRealization = wParam; InvalidateStyleRedraw(); break; // Marker definition and setting case SCI_MARKERDEFINE: if (wParam <= MARKER_MAX) vs.markers[wParam].markType = lParam; InvalidateStyleData(); RedrawSelMargin(); break; case SCI_MARKERSETFORE: if (wParam <= MARKER_MAX) vs.markers[wParam].fore.desired = Colour(lParam); InvalidateStyleData(); RedrawSelMargin(); break; case SCI_MARKERSETBACK: if (wParam <= MARKER_MAX) vs.markers[wParam].back.desired = Colour(lParam); InvalidateStyleData(); RedrawSelMargin(); break; case SCI_MARKERADD: { int markerID = pdoc->AddMark(wParam, lParam); RedrawSelMargin(); return markerID; } case SCI_MARKERDELETE: pdoc->DeleteMark(wParam, lParam); RedrawSelMargin(); break; case SCI_MARKERDELETEALL: pdoc->DeleteAllMarks(static_cast(wParam)); RedrawSelMargin(); break; case SCI_MARKERGET: return pdoc->GetMark(wParam); case SCI_MARKERNEXT: { int lt = pdoc->LinesTotal(); for (int iLine = wParam; iLine < lt; iLine++) { if ((pdoc->GetMark(iLine) & lParam) != 0) return iLine; } } return -1; case SCI_MARKERPREVIOUS: { for (int iLine = wParam; iLine >= 0; iLine--) { if ((pdoc->GetMark(iLine) & lParam) != 0) return iLine; } } return -1; case SCI_SETMARGINTYPEN: if (wParam >= 0 && wParam < ViewStyle::margins) { vs.ms[wParam].symbol = (lParam == SC_MARGIN_SYMBOL); InvalidateStyleRedraw(); } break; case SCI_GETMARGINTYPEN: if (wParam >= 0 && wParam < ViewStyle::margins) return vs.ms[wParam].symbol ? SC_MARGIN_SYMBOL : SC_MARGIN_NUMBER; else return 0; case SCI_SETMARGINWIDTHN: if (wParam >= 0 && wParam < ViewStyle::margins) { vs.ms[wParam].width = lParam; InvalidateStyleRedraw(); } break; case SCI_GETMARGINWIDTHN: if (wParam >= 0 && wParam < ViewStyle::margins) return vs.ms[wParam].width; else return 0; case SCI_SETMARGINMASKN: if (wParam >= 0 && wParam < ViewStyle::margins) { vs.ms[wParam].mask = lParam; InvalidateStyleRedraw(); } break; case SCI_GETMARGINMASKN: if (wParam >= 0 && wParam < ViewStyle::margins) return vs.ms[wParam].mask; else return 0; case SCI_SETMARGINSENSITIVEN: if (wParam >= 0 && wParam < ViewStyle::margins) { vs.ms[wParam].sensitive = lParam; InvalidateStyleRedraw(); } break; case SCI_GETMARGINSENSITIVEN: if (wParam >= 0 && wParam < ViewStyle::margins) return vs.ms[wParam].sensitive ? 1 : 0; else return 0; case SCI_STYLECLEARALL: vs.ClearStyles(); InvalidateStyleRedraw(); break; case SCI_STYLESETFORE: if (wParam <= STYLE_MAX) { vs.styles[wParam].fore.desired = Colour(lParam); InvalidateStyleRedraw(); } break; case SCI_STYLESETBACK: if (wParam <= STYLE_MAX) { vs.styles[wParam].back.desired = Colour(lParam); InvalidateStyleRedraw(); } break; case SCI_STYLESETBOLD: if (wParam <= STYLE_MAX) { vs.styles[wParam].bold = lParam; InvalidateStyleRedraw(); } break; case SCI_STYLESETITALIC: if (wParam <= STYLE_MAX) { vs.styles[wParam].italic = lParam; InvalidateStyleRedraw(); } break; case SCI_STYLESETEOLFILLED: if (wParam <= STYLE_MAX) { vs.styles[wParam].eolFilled = lParam; InvalidateStyleRedraw(); } break; case SCI_STYLESETSIZE: if (wParam <= STYLE_MAX) { vs.styles[wParam].size = lParam; InvalidateStyleRedraw(); } break; case SCI_STYLESETFONT: if (lParam == 0) return 0; if (wParam <= STYLE_MAX) { vs.SetStyleFontName(wParam, reinterpret_cast(lParam)); InvalidateStyleRedraw(); } break; case SCI_STYLERESETDEFAULT: vs.ResetDefaultStyle(); InvalidateStyleRedraw(); break; case SCI_SETSTYLEBITS: pdoc->SetStylingBits(wParam); break; case SCI_GETSTYLEBITS: return pdoc->stylingBits; case SCI_SETLINESTATE: return pdoc->SetLineState(wParam, lParam); case SCI_GETLINESTATE: return pdoc->GetLineState(wParam); case SCI_GETMAXLINESTATE: return pdoc->GetMaxLineState(); // Folding messages case SCI_VISIBLEFROMDOCLINE: return cs.DisplayFromDoc(wParam); case SCI_DOCLINEFROMVISIBLE: return cs.DocFromDisplay(wParam); case SCI_SETFOLDLEVEL: { int prev = pdoc->SetLevel(wParam, lParam); if (prev != lParam) RedrawSelMargin(); return prev; } case SCI_GETFOLDLEVEL: return pdoc->GetLevel(wParam); case SCI_GETLASTCHILD: return pdoc->GetLastChild(wParam, lParam); case SCI_GETFOLDPARENT: return pdoc->GetFoldParent(wParam); case SCI_SHOWLINES: cs.SetVisible(wParam, lParam, true); SetScrollBars(); Redraw(); break; case SCI_HIDELINES: cs.SetVisible(wParam, lParam, false); SetScrollBars(); Redraw(); break; case SCI_GETLINEVISIBLE: return cs.GetVisible(wParam); case SCI_SETFOLDEXPANDED: if (cs.SetExpanded(wParam, lParam)) { RedrawSelMargin(); } break; case SCI_GETFOLDEXPANDED: return cs.GetExpanded(wParam); case SCI_SETFOLDFLAGS: foldFlags = wParam; Redraw(); break; case SCI_TOGGLEFOLD: ToggleContraction(wParam); break; case SCI_ENSUREVISIBLE: EnsureLineVisible(wParam); break; case SCI_SEARCHANCHOR: SearchAnchor(); break; case SCI_SEARCHNEXT: case SCI_SEARCHPREV: return SearchText(iMessage, wParam, lParam); break; case SCI_SETCARETPOLICY: caretPolicy = wParam; caretSlop = lParam; break; #ifdef INCLUDE_DEPRECATED_FEATURES case SCI_SETFORE: vs.styles[STYLE_DEFAULT].fore.desired = Colour(wParam); InvalidateStyleRedraw(); break; case SCI_SETBACK: vs.styles[STYLE_DEFAULT].back.desired = Colour(wParam); InvalidateStyleRedraw(); break; case SCI_SETBOLD: vs.styles[STYLE_DEFAULT].bold = wParam; InvalidateStyleRedraw(); break; case SCI_SETITALIC: vs.styles[STYLE_DEFAULT].italic = wParam; InvalidateStyleRedraw(); break; case SCI_SETSIZE: vs.styles[STYLE_DEFAULT].size = wParam; InvalidateStyleRedraw(); break; case SCI_SETFONT: if (wParam == 0) return 0; strcpy(vs.styles[STYLE_DEFAULT].fontName, reinterpret_cast(wParam)); InvalidateStyleRedraw(); break; #endif case SCI_SETSELFORE: vs.selforeset = wParam; vs.selforeground.desired = Colour(lParam); InvalidateStyleRedraw(); break; case SCI_SETSELBACK: vs.selbackset = wParam; vs.selbackground.desired = Colour(lParam); InvalidateStyleRedraw(); break; case SCI_SETCARETFORE: vs.caretcolour.desired = Colour(wParam); InvalidateStyleRedraw(); break; case SCI_ASSIGNCMDKEY: kmap.AssignCmdKey(LOWORD(wParam), HIWORD(wParam), lParam); break; case SCI_CLEARCMDKEY: kmap.AssignCmdKey(LOWORD(wParam), HIWORD(wParam), WM_NULL); break; case SCI_CLEARALLCMDKEYS: kmap.Clear(); break; case SCI_INDICSETSTYLE: if (wParam <= INDIC_MAX) { vs.indicators[wParam].style = lParam; InvalidateStyleRedraw(); } break; case SCI_INDICGETSTYLE: return (wParam <= INDIC_MAX) ? vs.indicators[wParam].style : 0; case SCI_INDICSETFORE: if (wParam <= INDIC_MAX) { vs.indicators[wParam].fore.desired = Colour(lParam); InvalidateStyleRedraw(); } break; case SCI_INDICGETFORE: return (wParam <= INDIC_MAX) ? vs.indicators[wParam].fore.desired.AsLong() : 0; 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_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_NEWLINE: case SCI_FORMFEED: case SCI_VCHOME: case SCI_VCHOMEEXTEND: case SCI_ZOOMIN: case SCI_ZOOMOUT: case SCI_DELWORDLEFT: case SCI_DELWORDRIGHT: return KeyCommand(iMessage); case SCI_BRACEHIGHLIGHT: SetBraceHighlight(static_cast(wParam), lParam, STYLE_BRACELIGHT); break; case SCI_BRACEBADLIGHT: SetBraceHighlight(static_cast(wParam), -1, STYLE_BRACEBAD); break; case SCI_BRACEMATCH: // wParam is position of char to find brace for, // lParam is maximum amount of text to restyle to find it return BraceMatch(wParam, lParam); case SCI_GETVIEWEOL: return vs.viewEOL; case SCI_SETVIEWEOL: vs.viewEOL = wParam; Redraw(); break; case SCI_GETEDGECOLUMN: return theEdge; case SCI_SETEDGECOLUMN: theEdge = wParam; InvalidateStyleRedraw(); break; case SCI_GETEDGEMODE: return edgeState; case SCI_SETEDGEMODE: edgeState = wParam; InvalidateStyleRedraw(); break; case SCI_GETEDGECOLOUR: return vs.edgecolour.desired.AsLong(); case SCI_SETEDGECOLOUR: vs.edgecolour.desired = Colour(wParam); InvalidateStyleRedraw(); break; case SCI_GETDOCPOINTER: return reinterpret_cast(pdoc); case SCI_SETDOCPOINTER: SetDocPointer(reinterpret_cast(lParam)); return 0; case SCI_SETMODEVENTMASK: modEventMask = wParam; return 0; case SCI_CONVERTEOLS: pdoc->ConvertLineEnds(wParam); SetSelection(currentPos, anchor); // Ensure selection inside document return 0; #ifdef MACRO_SUPPORT case SCI_STARTRECORD: recordingMacro = 1; return 0; case SCI_STOPRECORD: recordingMacro = 0; return 0; #endif default: return DefWndProc(iMessage, wParam, lParam); } //Platform::DebugPrintf("end wnd proc\n"); return 0l; }