unit synMemo; interface uses Messages, Windows, SysUtils, Classes, controls, Graphics, forms, stdctrls, extctrls, Mask, Buttons, ComCtrls, db, dbctrls; type eSynMemoError = class(exception); tSynMemoScrollEnum = (smAutoScroll, smVertical, smHoriz, smBoth, smNoScroll); tSynMargins = record left, right : word; end; TSynCustomMemo = class(TCustomMemo) private fScrolling : tSynMemoScrollEnum; fUseFontMargin : boolean; oldwinproc : pointer; newWinProc : pointer; fSubClassDone : boolean; procedure SubClassParentProc(var Msg : tmessage); function getScrollOption : tsynMemoScrollEnum; procedure setScrollOption(aOption : tSynMemoScrollEnum); function getSize: integer; function getLinesShowing : integer; procedure setLinesShowing(numLines : integer); function getTopLine: integer; procedure setTopLine(aline : integer); function getRow: integer; procedure setRow(aRow : integer); function getCol: integer; procedure setCol(aCol : integer); function getLineHeight : integer; function getLineWidth(aline: integer): integer; function getRowCol : tpoint; procedure setRowCol(at : tpoint); function getLeftMargin: word; procedure setLeftMargin(amargin : word); function getrightMargin: word; procedure setrightMargin(amargin : word); Procedure UseFontMargin(abool : boolean); procedure WMERASEBKGND(var Message: TMessage); message WM_ERASEBKGND; procedure WMSIZE(var message: tmessage); message WM_SIZE; procedure TurnOnSubClassing; Procedure TurnOffSubClassing; protected procedure CheckScrolling; virtual; property ScrollOption : tsynMemoScrollEnum read getSCrollOption write setScrollOption default smAutoScroll; property LinesShowing : integer read getLinesShowing write setLinesShowing default 3; property MarginLeft : word read getLeftMargin write setLeftMargin; Property MarginRight : word read getRightMargin write setRightMargin; property MarginFont : boolean read fUseFontMargin write UseFontMargin; public constructor create(aOwner : tcomponent); override; destructor destroy; override; procedure SetToTop(focusit: boolean); function LongestLine : integer; property Row : integer read getRow write setRow; property Col : integer read GetCol write setCol; property RowCol : tpoint read getRowCol write setRowCol; property LineHeight : integer read GetLineHeight; property LineWidth[aline : integer] : integer read GetLineWidth; property TopLine : integer read getTopLine write setTopline; Property NumBytes : integer read GetSize; published end; TsynMemo = class(tSynCustomMemo) published property ScrollOption; property LinesShowing; property MarginLeft; Property MarginRight; property MarginFont; property Align; property Alignment; property BorderStyle; property Color; property Ctl3D; property DragCursor; property DragMode; property Enabled; property Font; property HideSelection; property Lines; property MaxLength; property OEMConvert; property ParentColor; property ParentCtl3D; property ParentFont; property ParentShowHint; property PopupMenu; property ReadOnly; // property ScrollBars; property ShowHint; property TabOrder; property TabStop; property Visible; property WantReturns; property WantTabs; property WordWrap; property OnChange; property OnClick; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnEnter; property OnExit; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnMouseDown; property OnMouseMove; property OnMouseUp; end; TSynDBMemo = class(TsynCustomMemo) private FDataLink: TFieldDataLink; FAutoDisplay: Boolean; FFocused: Boolean; FMemoLoaded: Boolean; FPaintControl: TPaintControl; procedure DataChange(Sender: TObject); procedure EditingChange(Sender: TObject); function GetDataField: string; function GetDataSource: TDataSource; function GetField: TField; function GetReadOnly: Boolean; procedure SetDataField(const Value: string); procedure SetDataSource(Value: TDataSource); procedure SetReadOnly(Value: Boolean); procedure SetAutoDisplay(Value: Boolean); procedure SetFocused(Value: Boolean); procedure UpdateData(Sender: TObject); procedure WMCut(var Message: TMessage); message WM_CUT; procedure WMPaste(var Message: TMessage); message WM_PASTE; procedure CMEnter(var Message: TCMEnter); message CM_ENTER; procedure CMExit(var Message: TCMExit); message CM_EXIT; procedure WMLButtonDblClk(var Message: TWMLButtonDblClk); message WM_LBUTTONDBLCLK; procedure WMPaint(var Message: TWMPaint); message WM_PAINT; procedure CMGetDataLink(var Message: TMessage); message CM_GETDATALINK; protected procedure CheckScrolling; override; procedure Change; override; procedure KeyDown(var Key: Word; Shift: TShiftState); override; procedure KeyPress(var Key: Char); override; procedure Loaded; override; procedure Notification(AComponent: TComponent; Operation: TOperation); override; procedure WndProc(var Message: TMessage); override; public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure LoadMemo; property Field: TField read GetField; published property ScrollOption; property LinesShowing; property MarginLeft; Property MarginRight; property MarginFont; property Align; property Alignment; property AutoDisplay: Boolean read FAutoDisplay write SetAutoDisplay default True; property BorderStyle; property Color; property Ctl3D; property DataField: string read GetDataField write SetDataField; property DataSource: TDataSource read GetDataSource write SetDataSource; property DragCursor; property DragMode; property Enabled; property Font; property ImeMode; property ImeName; property MaxLength; property ParentColor; property ParentCtl3D; property ParentFont; property ParentShowHint; property PopupMenu; property ReadOnly: Boolean read GetReadOnly write SetReadOnly default False; // property ScrollBars; property ShowHint; property TabOrder; property TabStop; property Visible; property WantTabs; property WordWrap; property OnChange; property OnClick; property OnDblClick; property OnDragDrop; property OnDragOver; property OnEndDrag; property OnEnter; property OnExit; property OnKeyDown; property OnKeyPress; property OnKeyUp; property OnMouseDown; property OnMouseMove; property OnMouseUp; property OnStartDrag; end; Procedure Register; implementation resourcestring rsMemoSubClassFail = 'Failed to subclass synMemo Parent, no AutoScroll available.'; const cCopyright = 'Synature, copyright 1997: TSynMemo by Brandon C. Smith'; {============= support functions ============} function maxOf(const first, second : integer):integer; begin if first > second then result := first else result := second; end; {============ the memo itself ==========} constructor TsynCustomMemo.create(aOwner : tcomponent); begin inherited create(aOwner); fScrolling := smAutoScroll; fsubClassDone := false; end; Destructor TsynCustomMemo.destroy; begin FreeObjectInstance(NewWinProc); inherited destroy; end; procedure TsynCustomMemo.WMERASEBKGND(var Message: TMessage); begin inherited; if not fSubclassDone then TurnOnSubclassing; end; procedure TsynCustomMemo.wmsize(var message: tMessage); begin inherited; if showing then checkScrolling; end; Procedure TsynCustomMemo.TurnOnSubclassing; begin if ( not (csDesigning in ComponentState)) and (not (csLoading in ComponentState)) and HandleAllocated and (not fsubclassDone) then begin if NewWinProc <> nil then FreeObjectInstance(newWinProc); NewWinProc := MakeObjectInstance(SubClassParentProc); OldWinProc := Pointer(setWindowLong(parent.handle, GWL_WNDPROC, longint(NewWinProc))); if OldWinProc = nil then raise eSynMemoError.create(rsMemoSubClassFail); fSubClassDone := true; end; end; Procedure TsynCustomMemo.TurnOffSubclassing; begin SetWindowLong(parent.handle, GWL_WNDPROC, longint(OldWinProc)); fSubclassDone := false; end; function TsynCustomMemo.getScrollOption : tsynMemoScrollEnum; begin result := fScrolling end; procedure TsynCustomMemo.setScrollOption(aOption : tSynMemoScrollEnum); begin fSCrolling := aOption; CheckScrolling; end; Procedure TsynCustomMemo.CheckScrolling; var here : tpoint; tmpScroll : tSCrollStyle; function Translate(customScrollEnum : tSynMemoScrollEnum ): tscrollstyle; begin result := ssNone; case CustomScrollEnum of smAutoScroll : result := ssNone; smVertical : result := ssVertical; smHoriz : result := ssHorizontal; smBoth : result := ssBoth; smNoScroll : result := ssNone; end; end; begin tmpScroll := translate(fScrolling); here := GetRowCol; if fScrolling = smAutoScroll then begin if not fSubClassDone then TurnOnSubClassing; if lines.count*lineheight > clientheight then tmpScroll := ssVertical else tmpScroll := ssNone; if WordWrap = false then if (longestline > clientwidth) and (getLinesShowing > 1) then begin if TmpScroll = ssVertical then TmpScroll := ssBoth else TmpScroll := ssHorizontal; end else begin if TmpScroll = ssBoth then TmpScroll := ssVertical else if lines.count*lineheight > clientheight then TmpScroll := ssVertical else TmpScroll := ssNone; end; end else TurnOffSubClassing; case fSCrolling of smAutoSCroll : if scrollbars <> TmpScroll then scrollbars := tmpScroll; smVertical : if scrollbars <> ssVertical then scrollbars := ssVertical; smHoriz : if scrollbars <> ssHorizontal then scrollbars := ssHorizontal; smBoth : if scrollbars <> ssBoth then scrollbars := ssboth; smNoScroll : if scrollbars <> ssNone then scrollbars := ssnone; end; SetRowCol(here); invalidate; end; procedure TsynCustomMemo.SubClassParentProc(var Msg : tmessage); begin with msg do begin Result := CallWindowProc(OldWinProc, Parent.handle, Msg, wParam, lParam); if (msg = WM_COMMAND) and (lparam = handle) then if WParamHi = en_change then CheckSCrolling; if msg = WM_DESTROY then SetWindowLong(parent.handle, GWL_WNDPROC, longint(OldWinProc)); end; end; (*procedure TsynCustomMemo.CMRecreateWnd(var Message: TMessage); var oldhandle : hwnd; begin oldHandle := handle; inherited; if oldHandle <> handle then begin lines.add('old = '+intTostr(oldhandle)); lines.add('new = '+IntToStr(handle)); end else begin lines.add('Apparently still the same'); end; end; *) function TsynCustomMemo.getRow: integer; begin result := Perform(EM_LineFromChar, $FFFF, 0); end; procedure TsynCustomMemo.setRow(aRow : integer); begin selstart := perform(EM_LineIndex, arow, 0); end; function TsynCustomMemo.getCol: integer; begin result := SelStart - perform(EM_lineIndex, $FFFF, 0); end; procedure TsynCustomMemo.setCol(aCol : integer); var fCol, frow : integer; begin frow := getRow; fCol := perform(EM_lineLength, perform(EM_lineIndex, fRow, 0), 0); if fCol > acol then fCol := aCol; selStart := perform(em_lineIndex, fRow, 0)+fCol; end; function TsynCustomMemo.getRowCol : tpoint; begin result.x := GetRow; result.y := getCol; end; procedure TsynCustomMemo.setRowCol(at : tpoint); begin autoselect := false; SetRow(at.x); SetCol(at.y); perform(EM_SCROLLCARET, 0, 0); // setfocus; end; Procedure TsynCustomMemo.SetToTop(focusit : boolean); var here : tpoint; begin here.x := 0; here.y := 0; setRowCol(here); if focusit then setfocus; end; function TsynCustomMemo.getLineHeight : integer; begin result := abs(font.height) + height - clientheight + 2 end; (* Var oldfont: HFont; {the old font} dc: THandle; {a dc handle} tm: TTextMetric; {text metric structure} textSize : tSize; const junk : pchar = 'X'; begin // result := (height - clientheight); This gives us a good number sometimes, but not consistently... dc := GetDC(handle); {Get the Dc for the memo} oldFont := SelectObject(dc, Font.handle); {now make sure we have the memo's font} {if I don't do the line above, then the text size is 2 pixels too big...} GetTextMetrics(dc, tm); {Get the text metric info} GetTextExtentPoint32(dc, junk, 1, textSize); {and get the height in this font} result := textsize.cy + 2*tm.tmExternalLeading; SelectObject(dc, oldfont); {Select the old font -- I'm not sure if or why we need this, but Lloyd's file said so...} ReleaseDC(handle, dc); {Release the Dc} end; *) function TsynCustomMemo.getLineWidth(aline: integer): integer; var oldfont: HFont; {the old font} dc: THandle; {a dc handle} textSize : tSize; begin dc := GetDC(handle); oldFont := SelectObject(dc, Font.handle); {Select the memo's font} GetTextExtentPoint32(dc, pchar(lines[aline]), length(lines[aline]), textSize); result := textsize.cx; SelectObject(dc, oldfont); {Select the old font} ReleaseDC(handle, dc); {Release the Dc} end; function TsynCustomMemo.getTopLine : integer; begin result := perform(EM_GETFIRSTVISIBLELINE, 0, 0); end; procedure TsynCustomMemo.setTopLine(aline : integer); var numToScroll, i : integer; begin NumToScroll := GetRow; for i := 1 to NumToScroll do perform(EM_SCROLL, SB_LINEUP, 0); For i := 1 to aline do perform(EM_SCROLL, SB_LINEDOWN, 0) end; function TsynCustomMemo.LongestLine: integer; var i : integer; begin result := 0; for i := 0 to lines.count - 1 do result := maxof(result, LineWidth[i]); end; function TsynCustomMemo.getLinesShowing : integer; begin result := (Height - (height - clientHeight+2)) div lineheight; end; procedure TsynCustomMemo.setLinesShowing(numLines : integer); begin height := (maxof(numlines,0))*lineheight + (Height - ClientHeight+2); end; function TsynCustomMemo.getLeftMargin: word; var margins : longint; begin margins := perform(EM_GETMARGINS,0,0); result := tsynMargins(Margins).left; end; procedure TsynCustomMemo.setLeftMargin(amargin : word); var margins : tsynMargins; begin Margins.left := amargin; perform(EM_SETMARGINS, EC_LEFTMARGIN, MakeLong(margins.left, margins.right)); end; function TsynCustomMemo.getrightMargin: word; var margins : tsynMargins; begin longint(Margins) := perform(EM_GETMARGINS,0,0); result := Margins.right; end; procedure TsynCustomMemo.setrightMargin(amargin : word); var margins : tsynMargins; begin Margins.right := amargin; perform(EM_SETMARGINS, EC_rightMARGIN, MakeLong(margins.right, margins.right)); end; procedure TsynCustomMemo.UseFontMargin(abool : boolean); begin fUseFontMargin := abool; if abool then Perform(EM_SETMARGINS, EC_USEFONTINFO, 0); end; function TsynCustomMemo.getSize: integer; begin result := GetWindowTextLength(handle); end; (* procedure TsynCustomMemo.FillFromTable(fromField : tfield); procedure GetMemo; var BS: TBlobStream; begin BS := TBlobStream.Create(TMemoField(fromField), bmRead); try lines.LoadFromStream(BS); finally BS.Free; end; end; begin if fromField is tStringField then text := fromField.asString; if fromField is tMemoField then getMemo; end; *) constructor TsynDBmemo.Create(AOwner: TComponent); begin inherited Create(AOwner); inherited ReadOnly := True; ControlStyle := ControlStyle + [csReplicatable]; FAutoDisplay := True; FDataLink := TFieldDataLink.Create; FDataLink.Control := Self; FDataLink.OnDataChange := DataChange; FDataLink.OnEditingChange := EditingChange; FDataLink.OnUpdateData := UpdateData; FPaintControl := TPaintControl.Create(Self, 'EDIT'); end; destructor TsynDBmemo.Destroy; begin FPaintControl.Free; FDataLink.Free; FDataLink := nil; inherited Destroy; end; procedure TsynDBmemo.Loaded; begin inherited Loaded; if (csDesigning in ComponentState) then DataChange(Self); end; procedure TsynDBmemo.Notification(AComponent: TComponent; Operation: TOperation); begin inherited Notification(AComponent, Operation); if (Operation = opRemove) and (FDataLink <> nil) and (AComponent = DataSource) then DataSource := nil; end; procedure TsynDBmemo.KeyDown(var Key: Word; Shift: TShiftState); begin inherited KeyDown(Key, Shift); if FMemoLoaded then begin if (Key = VK_DELETE) or ((Key = VK_INSERT) and (ssShift in Shift)) then FDataLink.Edit; end; end; procedure TsynDBmemo.KeyPress(var Key: Char); begin inherited KeyPress(Key); if FMemoLoaded then begin if (Key in [#32..#255]) and (FDataLink.Field <> nil) and not FDataLink.Field.IsValidChar(Key) then begin MessageBeep(0); Key := #0; end; case Key of ^H, ^I, ^J, ^M, ^V, ^X, #32..#255: FDataLink.Edit; #27: FDataLink.Reset; end; end else begin if Key = #13 then LoadMemo; Key := #0; end; end; procedure TsynDBmemo.Change; begin if FMemoLoaded then FDataLink.Modified; FMemoLoaded := True; inherited Change; end; function TsynDBmemo.GetDataSource: TDataSource; begin Result := FDataLink.DataSource; end; procedure TsynDBmemo.SetDataSource(Value: TDataSource); begin FDataLink.DataSource := Value; if Value <> nil then Value.FreeNotification(Self); end; function TsynDBmemo.GetDataField: string; begin Result := FDataLink.FieldName; end; procedure TsynDBmemo.SetDataField(const Value: string); begin FDataLink.FieldName := Value; end; function TsynDBmemo.GetReadOnly: Boolean; begin Result := FDataLink.ReadOnly; end; procedure TsynDBmemo.SetReadOnly(Value: Boolean); begin FDataLink.ReadOnly := Value; end; function TsynDBmemo.GetField: TField; begin Result := FDataLink.Field; end; procedure TsynDBmemo.LoadMemo; begin if not FMemoLoaded and Assigned(FDataLink.Field) and FDataLink.Field.IsBlob then begin try Lines.Text := FDataLink.Field.AsString; FMemoLoaded := True; except { Memo too large } on E:EInvalidOperation do Lines.Text := Format('(%s)', [E.Message]); end; EditingChange(Self); end; end; procedure TsynDBmemo.DataChange(Sender: TObject); begin if FDataLink.Field <> nil then if FDataLink.Field.IsBlob then begin if FAutoDisplay or (FDataLink.Editing and FMemoLoaded) then begin FMemoLoaded := False; LoadMemo; end else begin Text := Format('(%s)', [FDataLink.Field.DisplayLabel]); FMemoLoaded := False; end; end else begin if FFocused and FDataLink.CanModify then Text := FDataLink.Field.Text else Text := FDataLink.Field.DisplayText; FMemoLoaded := True; end else begin if csDesigning in ComponentState then Text := Name else Text := ''; FMemoLoaded := False; end; if HandleAllocated then RedrawWindow(Handle, nil, 0, RDW_INVALIDATE or RDW_ERASE or RDW_FRAME); CheckScrolling; end; Procedure TsynDBMemo.CheckScrolling; var here : tpoint; tmpScroll : tSCrollStyle; function Translate(customScrollEnum : tSynMemoScrollEnum ): tscrollstyle; begin result := ssNone; case CustomScrollEnum of smAutoScroll : result := ssNone; smVertical : result := ssVertical; smHoriz : result := ssHorizontal; smBoth : result := ssBoth; smNoScroll : result := ssNone; end; end; begin tmpScroll := translate(fScrolling); here := GetRowCol; if fScrolling = smAutoScroll then begin if lines.count > GetlinesShowing // lines.count*lineheight > (clientheight-1) then tmpScroll := ssVertical else tmpScroll := ssNone; if WordWrap = false then if (longestline > clientwidth) and (getLinesShowing > 1) then begin if TmpScroll = ssVertical then TmpScroll := ssBoth else TmpScroll := ssHorizontal; end else begin if TmpScroll = ssBoth then TmpScroll := ssVertical else if lines.count*lineheight > clientheight then TmpScroll := ssVertical else TmpScroll := ssNone; end; end; case fSCrolling of smAutoSCroll : if scrollbars <> TmpScroll then scrollbars := tmpScroll; smVertical : if scrollbars <> ssVertical then scrollbars := ssVertical; smHoriz : if scrollbars <> ssHorizontal then scrollbars := ssHorizontal; smBoth : if scrollbars <> ssBoth then scrollbars := ssboth; smNoScroll : if scrollbars <> ssNone then scrollbars := ssnone; end; SetRowCol(here); invalidate; end; procedure TsynDBmemo.EditingChange(Sender: TObject); begin inherited ReadOnly := not (FDataLink.Editing and FMemoLoaded); end; procedure TsynDBmemo.UpdateData(Sender: TObject); begin FDataLink.Field.AsString := Text; end; procedure TsynDBmemo.SetFocused(Value: Boolean); begin if FFocused <> Value then begin FFocused := Value; if not Assigned(FDataLink.Field) or not FDataLink.Field.IsBlob then FDataLink.Reset; end; end; procedure TsynDBmemo.WndProc(var Message: TMessage); begin with Message do if (Msg = WM_CREATE) or (Msg = WM_WINDOWPOSCHANGED) or (Msg = CM_FONTCHANGED) then FPaintControl.DestroyHandle; inherited; end; procedure TsynDBmemo.CMEnter(var Message: TCMEnter); begin SetFocused(True); inherited; if SysLocale.FarEast and FDataLink.CanModify then inherited ReadOnly := False; end; procedure TsynDBmemo.CMExit(var Message: TCMExit); begin try FDataLink.UpdateRecord; except SetFocus; raise; end; SetFocused(False); inherited; end; procedure TsynDBmemo.SetAutoDisplay(Value: Boolean); begin if FAutoDisplay <> Value then begin FAutoDisplay := Value; if Value then LoadMemo; end; end; procedure TsynDBmemo.WMLButtonDblClk(var Message: TWMLButtonDblClk); begin if not FMemoLoaded then LoadMemo else inherited; end; procedure TsynDBmemo.WMCut(var Message: TMessage); begin FDataLink.Edit; inherited; end; procedure TsynDBmemo.WMPaste(var Message: TMessage); begin FDataLink.Edit; inherited; end; procedure TsynDBmemo.CMGetDataLink(var Message: TMessage); begin Message.Result := Integer(FDataLink); end; procedure TsynDBmemo.WMPaint(var Message: TWMPaint); var S: string; begin if not (csPaintCopy in ControlState) then inherited else begin if FDataLink.Field <> nil then if FDataLink.Field.IsBlob then begin if FAutoDisplay then S := AdjustLineBreaks(FDataLink.Field.AsString) else S := Format('(%s)', [FDataLink.Field.DisplayLabel]); end else S := FDataLink.Field.DisplayText; SendMessage(FPaintControl.Handle, WM_SETTEXT, 0, Integer(PChar(S))); SendMessage(FPaintControl.Handle, WM_ERASEBKGND, Message.DC, 0); SendMessage(FPaintControl.Handle, WM_PAINT, Message.DC, 0); end; end; procedure Register; var x : string; begin x := copy(cCopyright,1,8); RegisterComponents(x, [TsynMemo, TsynDBMemo]); end; end.