mwcustomedit.pas

来自「本人买的<<VC++项目开发实例>>源代码配套光盘.」· PAS 代码 · 共 1,920 行 · 第 1/5 页

PAS
1,920 行
字号

procedure TmwCustomEdit.KeyPress(var Key: Char);
begin
  {$IFDEF MWE_MBCSSUPPORT}
  if (fImeCount > 0) then begin
    Dec(fImeCount);
    Exit;
  end;
  {$ENDIF}
  if Assigned(OnKeyPress) then
    OnKeyPress(Self, Key);

  if fProcChar = Key then
    fProcChar := #0
  else
    CommandProcessor(ecChar, Key, nil);
end;

function TmwCustomEdit.LeftSpaces(const Line: string): Integer;
var
  p: PChar;
begin
  p := pointer(Line);
  if Assigned(p) and (mweoAutoIndent in fOptions) then begin
    Result := 0;
    while p^ in [#1..#32] do begin
      Inc(p);
      Inc(Result);
    end;
  end else
    Result := 0;
end;

procedure TmwCustomEdit.LinesChanging(Sender: TObject);
begin
  Include(fStateFlags, mwsfLinesChanging);
end;

procedure TmwCustomEdit.LinesChanged(Sender: TObject);
begin
  Exclude(fStateFlags, mwsfLinesChanging);
  if HandleAllocated then begin
    UpdateScrollBars(FALSE);
    SetBlockBegin(CaretXY);
    InvalidateRect(Handle, @fInvalidateRect, False);
    FillChar(fInvalidateRect, SizeOf(TRect), 0);
  end;
end;

procedure TmwCustomEdit.MouseDown(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
var
  bWasSel: boolean;
  bStartDrag: boolean;
begin
  if (Button = mbRight) and (Shift = [ssRight]) and
    Assigned(PopupMenu) and SelAvail
    then
    exit;
  bWasSel := false;
  bStartDrag := FALSE;
  if Button = mbLeft then begin
    if ssDouble in Shift then Exit;
    if SelAvail then begin
        //remember selection state, as it will be cleared later
      bWasSel := true;
      fDragBlockBegin := BlockBegin;
      fDragBlockEnd := BlockEnd;
      fMouseDownX := X;
      fMouseDownY := Y;
    end;
  end;
  inherited MouseDown(Button, Shift, X, Y);
  ComputeCaret(X, Y);
  if Button = mbLeft then begin
    MouseCapture := True;
      //if mousedown occured in selected block then begin drag operation
    Exclude(fStateFlags, mwsfWaitForDragging);
    if bWasSel and (mweoDragDropEditing in fOptions) and
      IsPointInSelection(CaretXY)
      then
      bStartDrag := TRUE;
  end;
  if (Button = mbLeft) and bStartDrag then
    Include(fStateFlags, mwsfWaitForDragging)
  else begin
    if not (mwsfDblClicked in fStateFlags) then begin
      if ssShift in Shift then
        SetBlockEnd(CaretXY)
      else
        SetBlockBegin(CaretXY);
    end;
  end;
  Windows.SetFocus(Handle);
end;

procedure TmwCustomEdit.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  bDoSleep: boolean;
  nDelta: integer;

  function IsInEditRect(bGetCoor: boolean): boolean;
  var
    ptMouse: TPoint;
  begin
    if bGetCoor then begin
      GetCursorPos(ptMouse);
      ptMouse := ScreenToClient(ptMouse);
      X := ptMouse.X;
      Y := ptMouse.Y;
    end;
    Result := (X >= fGutterWidth) and (X < ClientWidth) and
      (Y >= 0) and (Y < ClientHeight);
  end;

begin
  inherited MouseMove(Shift, x, y);
  if MouseCapture and (mwsfWaitForDragging in fStateFlags) and not ReadOnly then
    begin
    if (Abs(fMouseDownX - X) >= GetSystemMetrics(SM_CXDRAG)) or
      (Abs(fMouseDownY - Y) >= GetSystemMetrics(SM_CYDRAG)) then begin
      Exclude(fStateFlags, mwsfWaitForDragging);
      BeginDrag(false);
    end;
  end
  else begin
    if not (mwsfInScrollLoop in fStateFlags) and
      (ssLeft in Shift) and MouseCapture
      then begin
      Include(fStateFlags, mwsfInScrollLoop);
      try
        if IsInEditRect(FALSE) then ComputeCaret(X, Y);
        SetBlockEnd(CaretXY);
        // InScrollLoop is to prevent reentrancy of this code block here
        Application.ProcessMessages;
        // we have to constantly update the mouse position!
        while MouseCapture and not IsInEditRect(TRUE) do begin
          bDoSleep := TRUE;
          // changes to line / column in one go
          IncPaintLock;
          try
            // horizontal scrolling
            if X < fGutterWidth then begin
              nDelta := fGutterWidth - X;
              if nDelta < 10 then LeftChar := LeftChar - 1
              else LeftChar := LeftChar - 8;
              if nDelta >= 42 then bDoSleep := FALSE;
              CaretX := LeftChar;
              SetBlockEnd(CaretXY);
            end else if X > ClientWidth then begin
              nDelta := X - ClientWidth;
              if nDelta < 10 then LeftChar := LeftChar + 1
              else LeftChar := LeftChar + 8;
              if nDelta >= 42 then bDoSleep := FALSE;
              CaretX := LeftChar + CharsInWindow;
              SetBlockEnd(CaretXY);
            end;
            // vertical scrolling
            if Y < 0 then begin
              if Y <= -10 then TopLine := TopLine - LinesInWindow
              else TopLine := TopLine - 1;
              if Y <= -42 then bDoSleep := FALSE;
              CaretY := TopLine;
              SetBlockEnd(CaretXY);
            end else if Y > ClientHeight then begin
              nDelta := Y - ClientHeight;
              if nDelta >= 10 then TopLine := TopLine + LinesInWindow
              else TopLine := TopLine + 1;
              if nDelta >= 42 then bDoSleep := FALSE;
              CaretY := TopLine + LinesInWindow - 1;
              SetBlockEnd(CaretXY);
            end;
          finally
            DecPaintLock;
            Update;
          end;
          // paint and update cursor and so on...
          Application.ProcessMessages;
          if bDoSleep then Sleep(100);
        end;
      finally
        Exclude(fStateFlags, mwsfInScrollLoop);
      end;
    end;
  end;
end;

procedure TmwCustomEdit.MouseUp(Button: TMouseButton; Shift: TShiftState;
  X, Y: Integer);
begin
  inherited MouseUp(Button, Shift, X, Y);
  if (Button = mbRight) and (Shift = [ssRight]) and Assigned(PopupMenu)
    then exit;
  MouseCapture := False;
  if fStateFlags * [mwsfDblClicked, mwsfWaitForDragging] = [mwsfWaitForDragging]
  then begin
    ComputeCaret(X, Y);
    SetBlockBegin(CaretXY);
    SetBlockEnd(CaretXY);
    Exclude(fStateFlags, mwsfWaitForDragging);
  end;
  Exclude(fStateFlags, mwsfDblClicked);
end;

// Complete rewrite of the painting code again. Removed Brads code with the
// Windows regions, since it made painting selected text very slow (the lines
// were scanned twice by the assigned highlighter) - on my system it took up to
// threetimes as long to paint the window if everything was selected.
// Splitting the paint into two operations caused weird effects on slower
// systems when scrolling in the column selection mode.
// The new code features the following:
// - No more doublebuffering of the drawing operations. The bitmap member has
//   been removed completely.
// - The highlighters don't touch the canvas of the control any more. Instead
//   the active token attribute is read from the highlighter, and only the
//   necessary changes to the font and the brush are made. This removes a lot
//   of color changes for selected text. Also the font color for spaces is
//   ignored, since no chars are visible anyway.
// - Tokens with the same font and brush attributes (also depending on the
//   selection state) are concatenated and painted in one go, this reduces
//   the number of ExtTextOut calls.
// - New event to assign special font and background colors to a line. Can be
//   used to paint breakpoint or current lines like seen in the Delphi IDE.
// - The font width array for the ExtTextOut calls is dynamically allocated
//   (only the maximum number of visible chars, not fCharsInWindow) and filled
//   with the fCharWidth value. This should save memory... Thanks to James
//   Jacobson for spotting this one.

// A lot of changes have been made by Tohru Hanai before the new version was
// released. Speed is now considerably higher, thanks to the new text painter
// classes and the changes in the client area invalidation code.
// I did however not mark the changes, but removed all the commented out lines
// for the sake of clarity. Please consider the painting code a joint effort of
// Tohru and me. The gains in speed come mostly from Tohrus new code.

procedure TmwCustomEdit.Paint;
var
  rcClip, rcDraw: TRect;
  nL1, nL2, nC1, nC2: integer;
begin
  // Get the invalidated rect. Compute the invalid area in lines / columns.
  rcClip := Canvas.ClipRect;
  // columns
  nC1 := LeftChar;
  if (rcClip.Left > fGutterWidth + 2) then
    Inc(nC1, (rcClip.Left - fGutterWidth - 2) div CharWidth);
  nC2 := nC1 +
    (rcClip.Right - fGutterWidth - 2 + CharWidth - 1) div CharWidth;
  // lines
  nL1 := Max(TopLine + rcClip.Top div fTextHeight, TopLine);
  nL2 := Min(TopLine + (rcClip.Bottom + fTextHeight - 1) div fTextHeight,
    Lines.Count);
  // Now paint everything while the caret is hidden.
  HideCaret;
  try
    // First paint the gutter area if it was (partly) invalidated.
    if (rcClip.Left < fGutterWidth) then begin
      rcDraw := rcClip;
      rcDraw.Right := fGutterWidth;
      PaintGutter(rcDraw, nL1, nL2);
    end;
    // Then paint the text area if it was (partly) invalidated.
    if (rcClip.Right > fGutterWidth) then begin
      rcDraw := rcClip;
      rcDraw.Left := Max(rcDraw.Left, fGutterWidth);
      PaintTextLines(rcDraw, nL1, nL2, nC1, nC2);
    end;
    // If there is a custom paint handler call it.
    if Assigned(fOnPaint) then begin
      Font.Assign(Font);
      Brush.Color := Color;
      fOnPaint(Self, Canvas);
    end;
  finally
    UpdateCaret;
  end;
end;

procedure TmwCustomEdit.PaintGutter(AClip: TRect; FirstLine, LastLine: integer);
var
  i, iLine: integer;
  rcLine: TRect;
  bHasOtherMarks: boolean;
  aGutterOffs: PIntArray;
  s: string;
  dc: HDC;

  procedure DrawMark(iMark: integer);
  var
    iLine: integer;
  begin
    if Assigned(fBookMarkOpt.BookmarkImages) and not Marks[i].InternalImage then
      begin
      if Marks[iMark].ImageIndex <= fBookMarkOpt.BookmarkImages.Count then begin
        iLine := Marks[iMark].Line - TopLine;
        with fBookMarkOpt do
          BookmarkImages.Draw(Canvas, LeftMargin + aGutterOffs^[iLine],
            iLine * fTextHeight, Marks[iMark].ImageIndex);
        Inc(aGutterOffs^[iLine], fBookMarkOpt.XOffset);
      end;
    end else begin
      if Marks[iMark].ImageIndex in [0..9] then begin
        iLine := Marks[iMark].Line - TopLine;
        if not Assigned(fInternalImage) then
          fInternalImage := TmwInternalImage.Create('mweInternalImages', 10);
        fInternalImage.DrawMark(Canvas, Marks[iMark].ImageIndex,
          fBookMarkOpt.LeftMargin + aGutterOffs^[iLine],
          iLine * fTextHeight, fTextHeight);
        Inc(aGutterOffs^[iLine], fBookMarkOpt.XOffset);
      end;
    end;
  end;

begin
  // Changed to use fTextDrawer.BeginDrawing and fTextDrawer.EndDrawing only
  // when absolutely necessary.  Note: Never change brush / pen / font of the
  // canvas inside of this block (only through methods of fTextDrawer)!
  Canvas.Brush.Color := Gutter.Color;
  // If we have to draw the line numbers then we don't want to erase
  // the background first. Do it line by line with TextRect instead
  // and fill only the area after the last visible line.
  dc := Canvas.Handle;
  if fGutter.ShowLineNumbers then begin
    fTextDrawer.BeginDrawing(dc);
    try
      fTextDrawer.SetBackColor(fGutter.Color);
      fTextDrawer.SetForeColor(Self.Font.Color);
      fTextDrawer.Style := [];
      // prepare the rect initially
      rcLine := AClip;
      rcLine.Right := Max(rcLine.Right, fGutterWidth - 2);
      rcLine.Bottom := (FirstLine - TopLine) * fTextHeight;
      for iLine := FirstLine to LastLine do begin
        // next line rect
        rcLine.Top := rcLine.Bottom;
        Inc(rcLine.Bottom, fTextHeight);
        // erase the background and draw the line number string in one go
        s := fGutter.FormatLineNumber(iLine);
        Windows.ExtTextOut(DC, fGutter.LeftOffset, rcLine.Top, ETO_OPAQUE,
          @rcLine, PChar(s), Length(s), nil);
      end;
      // now erase the remaining area if any
      if AClip.Bottom > rcLine.Bottom then begin
        rcLine.Top := rcLine.Bottom;
        rcLine.Bottom := AClip.Bottom;
        with rcLine do
          fTextDrawer.ExtTextOut(Left, Top, ETO_OPAQUE, rcLine, nil, 0);
      end;
    finally
      fTextDrawer.EndDrawing;
    end;
  end else
    InternalFillRect(dc, AClip);
  // the gutter separator if visible
  if AClip.Right >= fGutterWidth - 2 then
    with Canvas do begin
      Pen.Color := clBtnHighlight;
      Pen.Width := 1;
      with AClip do begin
        MoveTo(fGutterWidth - 2, Top);
        LineTo(fGutterWidth - 2, Bottom);
        Pen.Color := clBtnShadow;
        MoveTo(fGutterWidth - 1, Top);
        LineTo(fGutterWidth - 1, Bottom);
      end;
    end;
  // now the gutter marks
  if BookMarkOptions.GlyphsVisible and (Marks.Count > 0) then begin
    aGutterOffs := AllocMem((fLinesInWindow + 1) * SizeOf(integer));
    try
      // Instead of making a two pass loop we look while drawing the bookmarks
      // whether there is any other mark to be drawn
      bHasOtherMarks := FALSE;
      for i := 0 to Marks.Count - 1 do begin
        if not Marks[i].Visible or (Marks[i].Line < FirstLine) or
          (Marks[i].Line > LastLine)
          then continue;
        if Marks[i].IsBookmark then bHasOtherMarks := TRUE
        else DrawMark(i);
      end;
      if bHasOtherMarks then for i := 0 to Marks.Count - 1 do
          with Marks[i] do
            if Visible and IsBookmark and

⌨️ 快捷键说明

复制代码Ctrl + C
搜索代码Ctrl + F
全屏模式F11
增大字号Ctrl + =
减小字号Ctrl + -
显示快捷键?