diff options
Diffstat (limited to 'src/Editor.cxx')
-rw-r--r-- | src/Editor.cxx | 3705 |
1 files changed, 3705 insertions, 0 deletions
diff --git a/src/Editor.cxx b/src/Editor.cxx new file mode 100644 index 000000000..1ead18d34 --- /dev/null +++ b/src/Editor.cxx @@ -0,0 +1,3705 @@ +// Scintilla source code edit control +// Editor.cxx - main code for the edit control +// Copyright 1998-2000 by Neil Hodgson <neilh@scintilla.org> +// The License.txt file describes the conditions under which this software may be distributed. + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <ctype.h> + +#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<len; i++) { + if ((ptr[i] == '\r') || (ptr[i] == '\n')) { + if ((ptr[i] == '\r') || (!prevCr)) + line++; + if (line >= pdoc->LinesTotal()) { + if (pdoc->eolMode != SC_EOL_LF) + pdoc->InsertChar(pdoc->Length(), '\r'); + if (pdoc->eolMode != SC_EOL_CR) + pdoc->InsertChar(pdoc->Length(), '\n'); + } + 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<FINDTEXTEX *>(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<char *>(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);i<SelectionEnd(line);i++) { + text[j++] = pdoc->CharAt(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<char *>(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<char *>(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<char *>(lParam); + WORD *pBufSize = reinterpret_cast<WORD *>(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<PRectangle *>(lParam)) = GetClientRectangle(); + break; + + case EM_GETSEL: + if (wParam) + *reinterpret_cast<int *>(wParam) = SelectionStart(); + if (lParam) + *reinterpret_cast<int *>(lParam) = SelectionEnd(); + return MAKELONG(SelectionStart(), SelectionEnd()); + + case EM_EXGETSEL: { + if (lParam == 0) + return 0; + CHARRANGE *pCR = reinterpret_cast<CHARRANGE *>(lParam); + pCR->cpMin = SelectionStart(); + pCR->cpMax = SelectionEnd(); + } + break; + + case EM_SETSEL: { + int nStart = static_cast<int>(wParam); + int nEnd = static_cast<int>(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<CHARRANGE *>(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<char *>(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<int>(wParam) < 0) + wParam = SelectionStart(); + return pdoc->LineFromPosition(wParam); + + case EM_EXLINEFROMCHAR: + if (static_cast<int>(lParam) < 0) + lParam = SelectionStart(); // Not specified, but probably OK + return pdoc->LineFromPosition(lParam); + + case EM_LINEINDEX: + if (static_cast<int>(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<int>(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<int>(wParam) < 0) // Who use this anyway? + return 0; // Should be... Too complex to describe here, see MS specs! + if (static_cast<int>(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<int>(wParam) < 0) || + (static_cast<int>(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<char *>(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<Point *>(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<Point *>(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<TEXTRANGE *>(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<FORMATRANGE *>(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<char *>(lParam), wParam); + SetEmptySelection(currentPos + wParam); + return 0; + } + + case SCI_ADDSTYLEDTEXT: { + if (lParam == 0) + return 0; + pdoc->InsertStyledString(CurrentPosition() * 2, reinterpret_cast<char *>(lParam), wParam); + SetEmptySelection(currentPos + wParam / 2); + return 0; + } + + case SCI_INSERTTEXT: { + if (lParam == 0) + return 0; + int insertPos = wParam; + if (static_cast<short>(wParam) == -1) + insertPos = CurrentPosition(); + int newCurrent = CurrentPosition(); + int newAnchor = anchor; + char *sz = reinterpret_cast<char *>(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<enum undoCollectionType>(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<unsigned char *>(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<short>(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<TEXTRANGE *>(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<char *>(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<char *>(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<int>(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<const char *>(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<char *>(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<int>(wParam), lParam, STYLE_BRACELIGHT); + break; + + case SCI_BRACEBADLIGHT: + SetBraceHighlight(static_cast<int>(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<LRESULT>(pdoc); + + case SCI_SETDOCPOINTER: + SetDocPointer(reinterpret_cast<Document *>(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; +} |