| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458 |
- {
- Double Commander
- -------------------------------------------------------------------------
- Custom edit control with the look and feel like TLabel
- Copyright (C) 2017-2024 Alexander Koblov ([email protected])
- This program is free software; you can redistribute it and/or
- modify it under the terms of the GNU General Public License
- as published by the Free Software Foundation; either version 2
- of the License, or (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- }
- unit KASCDEdit;
- {$mode delphi}
- interface
- uses
- Classes, SysUtils, LResources, Controls, Graphics, Dialogs, Types,
- Menus, CustomDrawnControls, CustomDrawnDrawers, CustomDrawn_Common;
- type
- { TKASCDEdit }
- TKASCDEdit = class(TCDEdit)
- private
- FDragDropStarted: Boolean;
- FEditMenu: TPopupMenu; static;
- private
- procedure CreatePopupMenu;
- procedure ShowMenu(Data: PtrInt);
- procedure MenuCopy(Sender: TObject);
- procedure MenuSelectAll(Sender: TObject);
- function MousePosToCaretPos(X, Y: Integer): TPoint;
- protected
- procedure RealSetText(const Value: TCaption); override;
- procedure CalculatePreferredSize(var PreferredWidth, PreferredHeight: Integer;
- WithThemeSpace: Boolean); override;
- procedure CalculateSize(MaxWidth: Integer; var NeededWidth, NeededHeight: Integer);
- procedure KeyDown(var Key: word; Shift: TShiftState); override;
- public
- constructor Create(AOwner: TComponent); override;
- procedure MouseMove(Shift: TShiftState; X, Y: integer); override;
- procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: integer); override;
- procedure MouseUp(Button: TMouseButton; Shift:TShiftState; X,Y:Integer); override;
- public
- procedure SelectAll;
- procedure CopyToClipboard;
- published
- property Color default clDefault;
- property Cursor default crIBeam;
- property ReadOnly default True;
- property OnMouseDown;
- property OnMouseMove;
- property OnMouseUp;
- end;
- { TKASCDDrawer }
- TKASCDDrawer = class(TCDDrawerCommon)
- public
- function GetMeasures(AMeasureID: Integer): Integer; override;
- procedure DrawEditBackground(ADest: TCanvas; ADestPos: TPoint; ASize: TSize;
- AState: TCDControlState; AStateEx: TCDEditStateEx); override;
- procedure DrawEdit(ADest: TCanvas; ASize: TSize;
- AState: TCDControlState; AStateEx: TCDEditStateEx); override;
- end;
- procedure Register;
- implementation
- uses
- Math, Forms, Clipbrd, LCLType, LCLIntf, LazUTF8;
- resourcestring
- rsMnuCopyToClipboard = 'Copy';
- rsMnuSelectAll = 'Select &All';
- procedure Register;
- begin
- RegisterComponents('KASComponents', [TKASCDEdit]);
- end;
- { TKASCDDrawer }
- function TKASCDDrawer.GetMeasures(AMeasureID: Integer): Integer;
- begin
- case AMeasureID of
- TCDEDIT_LEFT_TEXT_SPACING: Result := 0;
- TCDEDIT_RIGHT_TEXT_SPACING: Result := 0;
- else Result:= inherited GetMeasures(AMeasureID);
- end;
- end;
- procedure TKASCDDrawer.DrawEditBackground(ADest: TCanvas; ADestPos: TPoint;
- ASize: TSize; AState: TCDControlState; AStateEx: TCDEditStateEx);
- begin
- // None
- end;
- procedure TKASCDDrawer.DrawEdit(ADest: TCanvas; ASize: TSize;
- AState: TCDControlState; AStateEx: TCDEditStateEx);
- var
- lVisibleText, lControlText: TCaption;
- lSelLeftPos, lSelLength, lSelRightPos: Integer;
- lLineHeight, lLineTop: Integer;
- lControlTextLen: PtrInt;
- lTextLeftSpacing, lTextTopSpacing, lTextBottomSpacing: Integer;
- lTextColor: TColor;
- i, lVisibleLinesCount: Integer;
- AClipRect: TRect;
- begin
- // General text configurations which apply to all lines
- // Configure the text color
- if csfEnabled in AState then
- lTextColor := AStateEx.Font.Color
- else
- lTextColor := clGrayText;
- ADest.Brush.Style := bsClear;
- ADest.Font.Assign(AStateEx.Font);
- ADest.Font.Color := lTextColor;
- lTextLeftSpacing := GetMeasures(TCDEDIT_LEFT_TEXT_SPACING);
- //lTextRightSpacing := GetMeasures(TCDEDIT_RIGHT_TEXT_SPACING);
- lTextTopSpacing := GetMeasures(TCDEDIT_TOP_TEXT_SPACING);
- lTextBottomSpacing := GetMeasures(TCDEDIT_BOTTOM_TEXT_SPACING);
- lLineHeight := ADest.TextHeight(cddTestStr)+2;
- lLineHeight := Min(ASize.cy-lTextBottomSpacing, lLineHeight);
- // Fill this to be used in other parts
- AStateEx.LineHeight := lLineHeight;
- AStateEx.FullyVisibleLinesCount := ASize.cy - lTextTopSpacing - lTextBottomSpacing;
- AStateEx.FullyVisibleLinesCount := AStateEx.FullyVisibleLinesCount div lLineHeight;
- AStateEx.FullyVisibleLinesCount := Min(AStateEx.FullyVisibleLinesCount, AStateEx.Lines.Count);
- // Calculate how many lines to draw
- if AStateEx.Multiline then
- lVisibleLinesCount := AStateEx.FullyVisibleLinesCount + 1
- else
- lVisibleLinesCount := 1;
- lVisibleLinesCount := Min(lVisibleLinesCount, AStateEx.Lines.Count);
- // Now draw each line
- for i := 0 to lVisibleLinesCount - 1 do
- begin
- lControlText := AStateEx.Lines.Strings[AStateEx.VisibleTextStart.Y+i];
- lControlText := VisibleText(lControlText, AStateEx.PasswordChar);
- lControlTextLen := UTF8Length(lControlText);
- lLineTop := lTextTopSpacing + i * lLineHeight;
- // The text
- ADest.Pen.Style := psClear;
- ADest.Brush.Style := bsClear;
- lVisibleText := UTF8Copy(lControlText, AStateEx.VisibleTextStart.X, lControlTextLen);
- // ToDo: Implement multi-line selection
- if (AStateEx.SelLength = 0) or (AStateEx.SelStart.Y <> AStateEx.VisibleTextStart.Y+i) then
- begin
- ADest.TextOut(lTextLeftSpacing, lLineTop, lVisibleText);
- end
- // Text and Selection
- else
- begin
- lSelLeftPos := AStateEx.SelStart.X;
- if AStateEx.SelLength < 0 then lSelLeftPos := lSelLeftPos + AStateEx.SelLength;
- lSelRightPos := AStateEx.SelStart.X;
- if AStateEx.SelLength > 0 then lSelRightPos := lSelRightPos + AStateEx.SelLength;
- lSelLength := AStateEx.SelLength;
- if lSelLength < 0 then lSelLength := lSelLength * -1;
- // Draw a normal text
- ADest.Font.Color := lTextColor;
- ADest.TextOut(lTextLeftSpacing, lLineTop, lVisibleText);
- // Draw a selected text
- ADest.Brush.Color := clHighlight;
- ADest.Font.Color := clHighlightText;
- // Calculate a clip rect
- AClipRect := ADest.ClipRect;
- AClipRect.Left := ADest.TextWidth(UTF8Copy(lVisibleText, 1, lSelLeftPos));
- AClipRect.Right := ADest.TextWidth(UTF8Copy(lVisibleText, 1, lSelLeftPos + lSelLength));
- IntersectClipRect(ADest.Handle, AClipRect.Left, AClipRect.Top, AClipRect.Right, AClipRect.Bottom);
- ADest.TextOut(lTextLeftSpacing, lLineTop, lVisibleText);
- end;
- end;
- // And the caret
- DrawCaret(ADest, Point(0, 0), ASize, AState, AStateEx);
- end;
- { TKASCDEdit }
- procedure TKASCDEdit.CreatePopupMenu;
- var
- MenuItem: TMenuItem;
- begin
- if not Assigned(FEditMenu) then
- begin
- FEditMenu:= TPopupMenu.Create(Application);
- MenuItem:= TMenuItem.Create(FEditMenu);
- MenuItem.Caption:= rsMnuCopyToClipboard;
- MenuItem.OnClick:= MenuCopy;
- FEditMenu.Items.Add(MenuItem);
- MenuItem:= TMenuItem.Create(FEditMenu);
- MenuItem.Caption:= '-';
- FEditMenu.Items.Add(MenuItem);
- MenuItem:= TMenuItem.Create(FEditMenu);
- MenuItem.Caption:= rsMnuSelectAll;
- MenuItem.OnClick:= MenuSelectAll;
- FEditMenu.Items.Add(MenuItem);
- end;
- end;
- procedure TKASCDEdit.ShowMenu(Data: PtrInt);
- begin
- FEditMenu.Tag:= Data;
- FEditMenu.PopUp;
- end;
- procedure TKASCDEdit.MenuCopy(Sender: TObject);
- begin
- TKASCDEdit(TMenuItem(Sender).Owner.Tag).CopyToClipboard;
- end;
- procedure TKASCDEdit.MenuSelectAll(Sender: TObject);
- begin
- TKASCDEdit(TMenuItem(Sender).Owner.Tag).SelectAll;
- end;
- function TKASCDEdit.MousePosToCaretPos(X, Y: Integer): TPoint;
- var
- lStrLen, i: PtrInt;
- lBeforeStr: String;
- lTextLeftSpacing: Integer;
- lVisibleStr, lCurChar: String;
- lPos: Integer;
- lBestDiff: Cardinal = $FFFFFFFF;
- lLastDiff: Cardinal = $FFFFFFFF;
- lCurDiff, lBestMatch: Integer;
- begin
- // Find the best Y position
- lPos := Y - FDrawer.GetMeasures(TCDEDIT_TOP_TEXT_SPACING);
- Result.Y := lPos div FEditState.LineHeight;
- Result.Y := Min(Result.Y, FEditState.FullyVisibleLinesCount);
- Result.Y := Min(Result.Y, FEditState.Lines.Count-1);
- if Result.Y < 0 then
- begin
- Result.X := 1;
- Result.Y := 0;
- Exit;
- end;
- // Find the best X position
- Canvas.Font := Font;
- lVisibleStr := Lines.Strings[Result.Y];
- lVisibleStr := LazUTF8.UTF8Copy(lVisibleStr, FEditState.VisibleTextStart.X, Length(lVisibleStr));
- lVisibleStr := TCDDrawer.VisibleText(lVisibleStr, FEditState.PasswordChar);
- lStrLen := LazUTF8.UTF8Length(lVisibleStr);
- lTextLeftSpacing := FDrawer.GetMeasures(TCDEDIT_LEFT_TEXT_SPACING);
- lBestMatch := 0;
- lBeforeStr := EmptyStr;
- lPos := lTextLeftSpacing;
- for i := 0 to lStrLen do
- begin
- lCurDiff := X - lPos;
- if lCurDiff < 0 then lCurDiff := lCurDiff * -1;
- if lCurDiff < lBestDiff then
- begin
- lBestDiff := lCurDiff;
- lBestMatch := i;
- end;
- // When the diff starts to grow we already found the caret pos, so exit
- if lCurDiff > lLastDiff then Break
- else lLastDiff := lCurDiff;
- if i <> lStrLen then
- begin
- lCurChar := LazUTF8.UTF8Copy(lVisibleStr, i + 1, 1);
- lBeforeStr := lBeforeStr + lCurChar;
- lPos := lTextLeftSpacing + Canvas.TextWidth(lBeforeStr);
- end;
- end;
- Result.X := lBestMatch+(FEditState.VisibleTextStart.X-1);
- Result.X := Min(Result.X, FEditState.VisibleTextStart.X+lStrLen-1);
- end;
- procedure TKASCDEdit.RealSetText(const Value: TCaption);
- begin
- Lines.Text := Value;
- inherited RealSetText(Value);
- end;
- procedure TKASCDEdit.CalculatePreferredSize(var PreferredWidth,
- PreferredHeight: Integer; WithThemeSpace: Boolean);
- var
- AWidth: Integer;
- begin
- if (Parent = nil) or (not Parent.HandleAllocated) then Exit;
- AWidth := Constraints.MinMaxWidth(10000);
- CalculateSize(AWidth, PreferredWidth, PreferredHeight);
- end;
- procedure TKASCDEdit.CalculateSize(MaxWidth: Integer; var NeededWidth,
- NeededHeight: Integer);
- var
- DC: HDC;
- R: TRect;
- Flags: Cardinal;
- OldFont: HGDIOBJ;
- LabelText: String;
- lTextLeftSpacing, lTextTopSpacing,
- lTextBottomSpacing, lTextRightSpacing: Integer;
- begin
- LabelText := Text;
- if LabelText = '' then
- begin
- NeededWidth:= 1;
- NeededHeight:= 1;
- Exit;
- end;
- lTextLeftSpacing := FDrawer.GetMeasures(TCDEDIT_LEFT_TEXT_SPACING);
- lTextTopSpacing := FDrawer.GetMeasures(TCDEDIT_TOP_TEXT_SPACING);
- lTextRightSpacing := FDrawer.GetMeasures(TCDEDIT_RIGHT_TEXT_SPACING);
- lTextBottomSpacing := FDrawer.GetMeasures(TCDEDIT_BOTTOM_TEXT_SPACING);
- DC := GetDC(Parent.Handle);
- try
- R := Rect(0, 0, MaxWidth, 10000);
- OldFont := SelectObject(DC, HGDIOBJ(Font.Reference.Handle));
- Flags := DT_CALCRECT or DT_EXPANDTABS;
- if not MultiLine then Flags := Flags or DT_SINGLELINE;
- DrawText(DC, PAnsiChar(LabelText), Length(LabelText), R, Flags);
- SelectObject(DC, OldFont);
- NeededWidth := R.Right - R.Left + lTextLeftSpacing + lTextRightSpacing;
- NeededHeight := R.Bottom - R.Top + lTextTopSpacing + lTextBottomSpacing;
- finally
- ReleaseDC(Parent.Handle, DC);
- end;
- end;
- procedure TKASCDEdit.KeyDown(var Key: word; Shift: TShiftState);
- begin
- if (ssModifier in Shift) then
- begin
- case Key of
- VK_A:
- begin
- SelectAll;
- Key:= 0;
- end;
- VK_C:
- begin
- CopyToClipboard;
- Key:= 0;
- end;
- end;
- end;
- if ReadOnly and (Key in [VK_BACK, VK_DELETE]) then
- begin
- Key:= 0;
- end;
- inherited KeyDown(Key, Shift);
- end;
- constructor TKASCDEdit.Create(AOwner: TComponent);
- begin
- CreatePopupMenu;
- inherited Create(AOwner);
- ReadOnly:= True;
- Cursor:= crIBeam;
- Color:= clDefault;
- DrawStyle:= dsExtra1;
- ControlStyle:= ControlStyle + [csParentBackground] - [csOpaque];
- end;
- procedure TKASCDEdit.MouseMove(Shift: TShiftState; X, Y: integer);
- begin
- inherited MouseMove(Shift, X, Y);
- // Mouse dragging selection
- if FDragDropStarted then
- begin
- FEditState.CaretPos := MousePosToCaretPos(X, Y);
- FEditState.SelLength := FEditState.CaretPos.X - FEditState.SelStart.X;
- Invalidate;
- end;
- end;
- procedure TKASCDEdit.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
- Y: integer);
- begin
- if (Button = mbLeft) or (GetSelLength = 0) then
- begin
- inherited MouseDown(Button, Shift, X, Y);
- FDragDropStarted := True;
- // Caret positioning
- FEditState.CaretPos := MousePosToCaretPos(X, Y);
- FEditState.SelStart.X := FEditState.CaretPos.X;
- FEditState.SelStart.Y := FEditState.CaretPos.Y;
- Invalidate;
- end
- else if Assigned(OnMouseDown) then begin
- OnMouseDown(Self, Button, Shift, X, Y);
- end;
- end;
- procedure TKASCDEdit.MouseUp(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
- begin
- inherited MouseUp(Button, Shift, X, Y);
- FDragDropStarted := False;
- if Button = mbRight then begin
- Application.QueueAsyncCall(ShowMenu, PtrInt(Self));
- end;
- end;
- procedure TKASCDEdit.SelectAll;
- begin
- FEditState.SelStart.X:= 0;
- FEditState.SelLength:= UTF8Length(Text);
- Invalidate;
- end;
- procedure TKASCDEdit.CopyToClipboard;
- begin
- if (FEditState.SelLength >= 0) then
- Clipboard.AsText:= UTF8Copy(Text, FEditState.SelStart.X + 1, FEditState.SelLength)
- else begin
- Clipboard.AsText:= UTF8Copy(Text, FEditState.SelStart.X + FEditState.SelLength + 1, -FEditState.SelLength);
- end;
- end;
- initialization
- RegisterDrawer(TKASCDDrawer.Create, dsExtra1);
- end.
|