summaryrefslogtreecommitdiff
path: root/plugins/HistoryPlusPlus/HistoryGrid.pas
diff options
context:
space:
mode:
Diffstat (limited to 'plugins/HistoryPlusPlus/HistoryGrid.pas')
-rw-r--r--plugins/HistoryPlusPlus/HistoryGrid.pas6780
1 files changed, 6780 insertions, 0 deletions
diff --git a/plugins/HistoryPlusPlus/HistoryGrid.pas b/plugins/HistoryPlusPlus/HistoryGrid.pas
new file mode 100644
index 0000000000..2dec9ab957
--- /dev/null
+++ b/plugins/HistoryPlusPlus/HistoryGrid.pas
@@ -0,0 +1,6780 @@
+(*
+ History++ plugin for Miranda IM: the free IM client for Microsoft* Windows*
+
+ Copyright (C) 2006-2009 theMIROn, 2003-2006 Art Fedorov.
+ History+ parts (C) 2001 Christian Kastner
+
+ 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, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+*)
+
+{ -----------------------------------------------------------------------------
+ HistoryGrid (historypp project)
+
+ Version: 1.4
+ Created: xx.02.2003
+ Author: Oxygen
+
+ [ Description ]
+
+ THistoryGrid to display history items for History++ plugin
+
+ [ History ]
+
+ 1.4
+ - Fixed bug when Select All, Delete causes crash
+
+ 1.3 ()
+ + Fixed scrollbar! Now scrolling is much better
+ + Added XML export
+ + URL & File Highlighting
+ - Fixed bug with changing System font in options, and TextAuthRequest
+ doesn't get changed
+ 1.2
+ 1.1
+ 1.0 (xx.02.03) First version.
+
+ [ Modifications ]
+
+ * (07.03.2006) Added OnFilterData event and UpdateFilter to manually
+ filter messages. Now when filtering, current selection isn't lost
+ (when possible)
+
+ * (01.03.2006) Added OnNameData event. Now you can supply your own
+ user name for each event separately.
+
+ * (29.05.2003) Selecting all and then deleting now works without
+ crashing, just added one check at THistoryGrid.DeleteSelected
+
+ * (31.03.2003) Scrolling now works perfectly! (if you ever can
+ do this with such way of doing scroll)
+
+ [ Known Issues ]
+ * Some visual bugs when track-scrolling. See WMVScroll for details.
+ * Not very good support of EmailExpress events (togeter
+ with HistoryForm.pas)
+
+ Contributors: theMIROn, Art Fedorov
+ ----------------------------------------------------------------------------- }
+
+unit HistoryGrid;
+
+{$I compilers.inc}
+
+interface
+
+{$DEFINE CUST_SB}
+{$IFDEF CUST_SB}
+ {$DEFINE PAGE_SIZE}
+{$ENDIF}
+{$DEFINE RENDER_RICH}
+
+uses
+ Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, ComCtrls, CommCtrl,
+ Menus,
+ StrUtils, WideStrUtils,
+ StdCtrls, Math, mmsystem,
+ hpp_global, hpp_contacts, hpp_itemprocess, hpp_events, hpp_eventfilters,
+ hpp_richedit, hpp_olesmileys, m_api,
+ Contnrs,
+ VertSB,
+ RichEdit, ShellAPI;
+
+type
+
+ TMsgFilter = record
+ nmhdr: nmhdr;
+ msg: UINT;
+ wParam: wParam;
+ lParam: lParam;
+ end;
+
+ TMouseMoveKey = (mmkControl, mmkLButton, mmkMButton, mmkRButton, mmkShift);
+ TMouseMoveKeys = set of TMouseMoveKey;
+
+ TGridState = (gsIdle, gsDelete, gsSearch, gsSearchItem, gsLoad, gsSave, gsInline);
+
+ TXMLItem = record
+ Protocol: AnsiString;
+ Time: AnsiString;
+ Date: AnsiString;
+ Mes: AnsiString;
+ Url: AnsiString;
+ FileName: AnsiString;
+ Contact: AnsiString;
+ From: AnsiString;
+ EventType: AnsiString;
+ ID: AnsiString;
+ end;
+
+ TMCItem = record
+ Size: Integer;
+ Buffer: PByte;
+ end;
+
+ TOnSelect = procedure(Sender: TObject; Item, OldItem: Integer) of object;
+ TOnBookmarkClick = procedure(Sender: TObject; Item: Integer) of object;
+ TGetItemData = procedure(Sender: TObject; Index: Integer; var Item: THistoryItem) of object;
+ TGetNameData = procedure(Sender: TObject; Index: Integer; var Name: String) of object;
+ TGetXMLData = procedure(Sender: TObject; Index: Integer; var Item: TXMLItem) of object;
+ TGetMCData = procedure(Sender: TObject; Index: Integer; var Item: TMCItem; Stage: TSaveStage) of object;
+ TOnPopup = TNotifyEvent;
+ TOnTranslateTime = procedure(Sender: TObject; Time: DWord; var Text: String) of object;
+ TOnProgress = procedure(Sender: TObject; Position, Max: Integer) of object;
+ TOnSearchFinished = procedure(Sender: TObject; Text: String; Found: Boolean) of object;
+ TOnSearched = TOnSearchFinished;
+ TOnItemDelete = procedure(Sender: TObject; Index: Integer) of object;
+ TOnState = procedure(Sender: TObject; State: TGridState) of object;
+ TOnItemFilter = procedure(Sender: TObject; Index: Integer; var Show: Boolean) of object;
+ TOnChar = procedure(Sender: TObject; var achar: WideChar; Shift: TShiftState) of object;
+ TOnRTLChange = procedure(Sender: TObject; BiDiMode: TBiDiMode) of object;
+ TOnProcessInlineChange = procedure(Sender: TObject; Enabled: Boolean) of object;
+ TOnOptionsChange = procedure(Sender: TObject) of object;
+ TOnProcessRichText = procedure(Sender: TObject; Handle: THandle; Item: Integer) of object;
+ TOnSearchItem = procedure(Sender: TObject; Item: Integer; ID: Integer; var Found: Boolean) of object;
+ TOnSelectRequest = TNotifyEvent;
+ TOnFilterChange = TNotifyEvent;
+
+ THistoryGrid = class;
+
+ { IFDEF RENDER_RICH }
+ TUrlClickItemEvent = procedure(Sender: TObject; Item: Integer; Url: String;
+ Button: TMouseButton) of object;
+ { ENDIF }
+ TOnShowIcons = procedure;
+ TOnTextFormatting = procedure(Value: Boolean);
+
+ TGridHitTest = (ghtItem, ghtHeader, ghtText, ghtLink, ghtUnknown, ghtButton, ghtSession,
+ ghtSessHideButton, ghtSessShowButton, ghtBookmark);
+ TGridHitTests = set of TGridHitTest;
+
+ TItemOption = record
+ MessageType: TMessageTypes;
+ textFont: TFont;
+ textColor: TColor;
+ end;
+
+ TItemOptions = array of TItemOption;
+
+ TGridOptions = class(TPersistent)
+ private
+ FLocks: Integer;
+ Changed: Integer;
+ Grids: array of THistoryGrid;
+
+ FColorDivider: TColor;
+ FColorSelectedText: TColor;
+ FColorSelected: TColor;
+ FColorSessHeader: TColor;
+ FColorBackground: TColor;
+ FColorLink: TColor;
+
+ FFontProfile: TFont;
+ FFontContact: TFont;
+ FFontIncomingTimestamp: TFont;
+ FFontOutgoingTimestamp: TFont;
+ FFontSessHeader: TFont;
+ FFontMessage: TFont;
+
+ FItemOptions: TItemOptions;
+
+ // FIconMessage: TIcon;
+ // FIconFile: TIcon;
+ // FIconUrl: TIcon;
+ // FIconOther: TIcon;
+
+ FRTLEnabled: Boolean;
+ // FShowAvatars: Boolean;
+ FShowIcons: Boolean;
+ FOnShowIcons: TOnShowIcons;
+
+ FBBCodesEnabled: Boolean;
+ FSmileysEnabled: Boolean;
+ FMathModuleEnabled: Boolean;
+ FRawRTFEnabled: Boolean;
+ FAvatarsHistoryEnabled: Boolean;
+
+ FTextFormatting: Boolean;
+ FOnTextFormatting: TOnTextFormatting;
+
+ FClipCopyTextFormat: String;
+ FClipCopyFormat: String;
+ FReplyQuotedFormat: String;
+ FReplyQuotedTextFormat: String;
+ FSelectionFormat: String;
+
+ FOpenDetailsMode: Boolean;
+
+ FForceProfileName: Boolean;
+ FProfileName: String;
+ FDateTimeFormat: String;
+
+ procedure SetColorDivider(const Value: TColor);
+ procedure SetColorSelectedText(const Value: TColor);
+ procedure SetColorSelected(const Value: TColor);
+ procedure SetColorSessHeader(const Value: TColor);
+ procedure SetColorBackground(const Value: TColor);
+ procedure SetColorLink(const Value: TColor);
+
+ procedure SetFontContact(const Value: TFont);
+ procedure SetFontProfile(const Value: TFont);
+ procedure SetFontIncomingTimestamp(const Value: TFont);
+ procedure SetFontOutgoingTimestamp(const Value: TFont);
+ procedure SetFontSessHeader(const Value: TFont);
+ procedure SetFontMessage(const Value: TFont);
+
+ // procedure SetIconOther(const Value: TIcon);
+ // procedure SetIconFile(const Value: TIcon);
+ // procedure SetIconURL(const Value: TIcon);
+ // procedure SetIconMessage(const Value: TIcon);
+
+ procedure SetRTLEnabled(const Value: Boolean);
+ procedure SetShowIcons(const Value: Boolean);
+ // procedure SetShowAvatars(const Value: Boolean);
+
+ procedure SetBBCodesEnabled(const Value: Boolean);
+ procedure SetSmileysEnabled(const Value: Boolean);
+ procedure SetMathModuleEnabled(const Value: Boolean);
+ procedure SetRawRTFEnabled(const Value: Boolean);
+ procedure SetAvatarsHistoryEnabled(const Value: Boolean);
+ procedure SetProfileName(const Value: String);
+ procedure SetTextFormatting(const Value: Boolean);
+
+ function GetLocked: Boolean;
+ procedure SetDateTimeFormat(const Value: String);
+ protected
+ procedure DoChange;
+ procedure AddGrid(Grid: THistoryGrid);
+ procedure DeleteGrid(Grid: THistoryGrid);
+ procedure FontChanged(Sender: TObject);
+ public
+ constructor Create;
+ destructor Destroy; override;
+ procedure StartChange;
+ procedure EndChange(const Forced: Boolean = False);
+ function AddItemOptions: Integer;
+ function GetItemOptions(Mes: TMessageTypes; out textFont: TFont; out textColor: TColor): Integer;
+ property OnShowIcons: TOnShowIcons read FOnShowIcons write FOnShowIcons;
+ property OnTextFormatting: TOnTextFormatting read FOnTextFormatting write FOnTextFormatting;
+ published
+ property ClipCopyFormat: String read FClipCopyFormat write FClipCopyFormat;
+ property ClipCopyTextFormat: String read FClipCopyTextFormat write FClipCopyTextFormat;
+ property ReplyQuotedFormat: String read FReplyQuotedFormat write FReplyQuotedFormat;
+ property ReplyQuotedTextFormat: String read FReplyQuotedTextFormat write FReplyQuotedTextFormat;
+ property SelectionFormat: String read FSelectionFormat write FSelectionFormat;
+
+ property Locked: Boolean read GetLocked;
+
+ // property IconOther: TIcon read FIconOther write SetIconOther;
+ // property IconFile: TIcon read FIconFile write SetIconFile;
+ // property IconUrl: TIcon read FIconUrl write SetIconUrl;
+ // property IconMessage: TIcon read FIconMessage write SetIconMessage;
+
+ // property IconHistory: hIcon read FIconHistory write FIconHistory;
+ // property IconSearch: hIcon read FIconSearch write FIconSearch;
+
+ property ColorDivider: TColor read FColorDivider write SetColorDivider;
+ property ColorSelectedText: TColor read FColorSelectedText write SetColorSelectedText;
+ property ColorSelected: TColor read FColorSelected write SetColorSelected;
+ property ColorSessHeader: TColor read FColorSessHeader write SetColorSessHeader;
+ property ColorBackground: TColor read FColorBackground write SetColorBackground;
+ property ColorLink: TColor read FColorLink write SetColorLink;
+
+ property FontProfile: TFont read FFontProfile write SetFontProfile;
+ property FontContact: TFont read FFontContact write SetFontContact;
+ property FontIncomingTimestamp: TFont read FFontIncomingTimestamp write SetFontIncomingTimestamp;
+ property FontOutgoingTimestamp: TFont read FFontOutgoingTimestamp write SetFontOutgoingTimestamp;
+ property FontSessHeader: TFont read FFontSessHeader write SetFontSessHeader;
+ property FontMessage: TFont read FFontMessage write SetFontMessage;
+
+ property ItemOptions: TItemOptions read FItemOptions write FItemOptions;
+
+ property RTLEnabled: Boolean read FRTLEnabled write SetRTLEnabled;
+ property ShowIcons: Boolean read FShowIcons write SetShowIcons;
+ // property ShowAvatars: Boolean read FShowAvatars write SetShowAvatars;
+
+ property BBCodesEnabled: Boolean read FBBCodesEnabled write SetBBCodesEnabled;
+ property SmileysEnabled: Boolean read FSmileysEnabled write SetSmileysEnabled;
+ property MathModuleEnabled: Boolean read FMathModuleEnabled write SetMathModuleEnabled;
+ property RawRTFEnabled: Boolean read FRawRTFEnabled write SetRawRTFEnabled;
+ property AvatarsHistoryEnabled: Boolean read FAvatarsHistoryEnabled write SetAvatarsHistoryEnabled;
+
+ property OpenDetailsMode: Boolean read FOpenDetailsMode write FOpenDetailsMode;
+ property ForceProfileName: Boolean read FForceProfileName;
+ property ProfileName: String read FProfileName write SetProfileName;
+
+ property DateTimeFormat: String read FDateTimeFormat write SetDateTimeFormat;
+ property TextFormatting: Boolean read FTextFormatting write SetTextFormatting;
+ end;
+
+ PRichItem = ^TRichItem;
+
+ TRichItem = record
+ Rich: THPPRichEdit;
+ Bitmap: TBitmap;
+ BitmapDrawn: Boolean;
+ Height: Integer;
+ GridItem: Integer;
+ end;
+
+ PLockedItem = ^TLockedItem;
+
+ TLockedItem = record
+ RichItem: PRichItem;
+ SaveRect: TRect;
+ end;
+
+ TRichCache = class(TObject)
+ private
+ LogX, LogY: Integer;
+ RichEventMasks: DWord;
+ Grid: THistoryGrid;
+ FRichWidth: Integer;
+ FRichHeight: Integer;
+ FLockedList: TList;
+
+ function FindGridItem(GridItem: Integer): Integer;
+ procedure PaintRichToBitmap(Item: PRichItem);
+ procedure ApplyItemToRich(Item: PRichItem);
+
+ procedure OnRichResize(Sender: TObject; Rect: TRect);
+ protected
+ Items: array of PRichItem;
+ procedure MoveToTop(Index: Integer);
+ procedure SetWidth(const Value: Integer);
+ public
+ constructor Create(AGrid: THistoryGrid); overload;
+ destructor Destroy; override;
+
+ procedure ResetAllItems;
+ procedure ResetItems(GridItems: array of Integer);
+ procedure ResetItem(GridItem: Integer);
+ property Width: Integer read FRichWidth write SetWidth;
+ procedure SetHandles;
+
+ procedure WorkOutItemAdded(GridItem: Integer);
+ procedure WorkOutItemDeleted(GridItem: Integer);
+
+ function RequestItem(GridItem: Integer): PRichItem;
+ function CalcItemHeight(GridItem: Integer): Integer;
+ function GetItemRich(GridItem: Integer): THPPRichEdit;
+ function GetItemRichBitmap(GridItem: Integer): TBitmap;
+ function GetItemByHandle(Handle: THandle): PRichItem;
+ function LockItem(Item: PRichItem; SaveRect: TRect): Integer;
+ function UnlockItem(Item: Integer): TRect;
+ end;
+
+ TGridUpdate = (guSize, guAllocate, guFilter, guOptions);
+ TGridUpdates = set of TGridUpdate;
+
+ THistoryGrid = class(TScrollingWinControl)
+ private
+ LogX, LogY: Integer;
+ SessHeaderHeight: Integer;
+ CHeaderHeight, PHeaderheight: Integer;
+ IsCanvasClean: Boolean;
+ ProgressRect: TRect;
+ BarAdjusted: Boolean;
+ Allocated: Boolean;
+ LockCount: Integer;
+ ClipRect: TRect;
+ ShowProgress: Boolean;
+ ProgressPercent: Byte;
+ SearchPattern: String;
+ GridUpdates: TGridUpdates;
+ VLineScrollSize: Integer;
+ FSelItems, TempSelItems: array of Integer;
+ FSelected: Integer;
+ FGetItemData: TGetItemData;
+ FGetNameData: TGetNameData;
+ FPadding: Integer;
+ FItems: array of THistoryItem;
+ FClient: TBitmap;
+ FCanvas: TCanvas;
+ FContact: THandle;
+ FProtocol: AnsiString;
+ FLoadedCount: Integer;
+ FContactName: String;
+ FProfileName: String;
+ FOnPopup: TOnPopup;
+ FTranslateTime: TOnTranslateTime;
+ FFilter: TMessageTypes;
+ FDblClick: TNotifyEvent;
+ FSearchFinished: TOnSearchFinished;
+ FOnProcessRichText: TOnProcessRichText;
+ FItemDelete: TOnItemDelete;
+ FState: TGridState;
+ FHideSelection: Boolean;
+ FGridNotFocused: Boolean;
+
+ FTxtNoItems: String;
+ FTxtStartup: String;
+ FTxtNoSuch: String;
+
+ FTxtFullLog: String;
+ FTxtPartLog: String;
+ FTxtHistExport: String;
+ FTxtGenHist1: String;
+ FTxtGenHist2: String;
+ FTxtSessions: String;
+
+ FSelectionString: String;
+ FSelectionStored: Boolean;
+
+ FOnState: TOnState;
+ FReversed: Boolean;
+ FReversedHeader: Boolean;
+ FOptions: TGridOptions;
+ FMultiSelect: Boolean;
+ FOnSelect: TOnSelect;
+ FOnFilterChange: TOnFilterChange;
+ FGetXMLData: TGetXMLData;
+ FGetMCData: TGetMCData;
+ FOnItemFilter: TOnItemFilter;
+{$IFDEF CUST_SB}
+ FVertScrollBar: TVertScrollBar;
+{$ENDIF}
+{$IFDEF RENDER_RICH}
+ FRichCache: TRichCache;
+ FOnUrlClick: TUrlClickItemEvent;
+ FRich: THPPRichEdit;
+ FRichInline: THPPRichEdit;
+ FItemInline: Integer;
+ FRichSave: THPPRichEdit;
+ FRichSaveItem: THPPRichEdit;
+ FRichSaveOLECB: TRichEditOleCallback;
+
+ FOnInlineKeyDown: TKeyEvent;
+ FOnInlineKeyUp: TKeyEvent;
+ FOnInlinePopup: TOnPopup;
+
+ FRichHeight: Integer;
+ FRichParamsSet: Boolean;
+ FOnSearchItem: TOnSearchItem;
+
+ FRTLMode: TRTLMode;
+ FOnRTLChange: TOnRTLChange;
+
+ FOnOptionsChange: TOnOptionsChange;
+
+ TopItemOffset: Integer;
+ MaxSBPos: Integer;
+ FShowHeaders: Boolean;
+ FCodepage: Cardinal;
+ FOnChar: TOnChar;
+ WindowPrePainting: Boolean;
+ WindowPrePainted: Boolean;
+ FExpandHeaders: Boolean;
+ FOnProcessInlineChange: TOnProcessInlineChange;
+
+ FOnBookmarkClick: TOnBookmarkClick;
+ FShowBookmarks: Boolean;
+ FGroupLinked: Boolean;
+ FShowBottomAligned: Boolean;
+ FOnSelectRequest: TOnSelectRequest;
+ FBorderStyle: TBorderStyle;
+
+ FWheelAccumulator: Integer;
+ FWheelLastTick: Cardinal;
+
+ FHintRect: TRect;
+ // !! function GetHint: WideString;
+ // !! procedure SetHint(const Value: WideString);
+ // !! function IsHintStored: Boolean;
+ procedure CMHintShow(var Message: TMessage); message CM_HINTSHOW;
+
+ procedure SetBorderStyle(Value: TBorderStyle);
+
+ procedure SetCodepage(const Value: Cardinal);
+ procedure SetShowHeaders(const Value: Boolean);
+ function GetIdx(Index: Integer): Integer;
+ // Item offset support
+ // procedure SetScrollBar
+ procedure ScrollGridBy(Offset: Integer; Update: Boolean = True);
+ procedure SetSBPos(Position: Integer);
+ // FRich events
+ // procedure OnRichResize(Sender: TObject; Rect: TRect);
+ // procedure OnMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
+{$ENDIF}
+ procedure WMNotify(var Message: TWMNotify); message WM_NOTIFY;
+ procedure WMSetFocus(var Message: TWMSetFocus); message WM_SETFOCUS;
+ procedure WMKillFocus(var Message: TWMKillFocus); message WM_KILLFOCUS;
+ procedure WMGetDlgCode(var Message: TWMGetDlgCode); message WM_GETDLGCODE;
+ procedure WMSetCursor(var Message: TWMSetCursor); message WM_SETCURSOR;
+ procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;
+ procedure WMRButtonUp(var Message: TWMRButtonUp); message WM_RBUTTONUP;
+ procedure WMRButtonDown(var Message: TWMRButtonDown); message WM_RBUTTONDOWN;
+ procedure WMLButtonDown(var Message: TWMLButtonDown); message WM_LBUTTONDOWN;
+ procedure WMLButtonUp(var Message: TWMLButtonUp); message WM_LBUTTONUP;
+ procedure WMLButtonDblClick(var Message: TWMLButtonDblClk); message WM_LBUTTONDBLCLK;
+ procedure WMMButtonDown(var Message: TWMRButtonDown); message WM_MBUTTONDOWN;
+ procedure WMMButtonUp(var Message: TWMRButtonDown); message WM_MBUTTONUP;
+ procedure WMPaint(var Message: TWMPaint); message WM_PAINT;
+ procedure WMSize(var Message: TWMSize); message WM_SIZE;
+ procedure WMEraseBkgnd(var Message: TWMEraseBkgnd); message WM_ERASEBKGND;
+ procedure WMVScroll(var Message: TWMVScroll); message WM_VSCROLL;
+ procedure CNVScroll(var Message: TWMVScroll); message CN_VSCROLL;
+ procedure WMKeyDown(var Message: TWMKeyDown); message WM_KEYDOWN;
+ procedure WMKeyUp(var Message: TWMKeyUp); message WM_KEYUP;
+ procedure WMSysKeyUp(var Message: TWMSysKeyUp); message WM_SYSKEYUP;
+ procedure WMChar(var Message: TWMChar); message WM_CHAR;
+ procedure WMMouseWheel(var Message: TWMMouseWheel); message WM_MOUSEWHEEL;
+ procedure CMBiDiModeChanged(var Message: TMessage); message CM_BIDIMODECHANGED;
+ procedure CMCtl3DChanged(var Message: TMessage); message CM_CTL3DCHANGED;
+ procedure WMCommand(var Message: TWMCommand); message WM_COMMAND;
+ procedure EMGetSel(var Message: TMessage); message EM_GETSEL;
+ procedure EMExGetSel(var Message: TMessage); message EM_EXGETSEL;
+ procedure EMSetSel(var Message: TMessage); message EM_SETSEL;
+ procedure EMExSetSel(var Message: TMessage); message EM_EXSETSEL;
+ procedure WMGetText(var Message: TWMGetText); message WM_GETTEXT;
+ procedure WMGetTextLength(var Message: TWMGetTextLength); message WM_GETTEXTLENGTH;
+ procedure WMSetText(var Message: TWMSetText); message WM_SETTEXT;
+ function GetCount: Integer;
+ procedure SetContact(const Value: THandle);
+ procedure SetPadding(Value: Integer);
+ procedure SetSelected(const Value: Integer);
+ procedure AddSelected(Item: Integer);
+ procedure RemoveSelected(Item: Integer);
+ procedure MakeRangeSelected(FromItem, ToItem: Integer);
+ procedure MakeSelectedTo(Item: Integer);
+ procedure MakeVisible(Item: Integer);
+ procedure MakeSelected(Value: Integer);
+ function GetSelCount: Integer;
+ procedure SetFilter(const Value: TMessageTypes);
+ function GetTime(Time: DWord): String;
+ function GetItems(Index: Integer): THistoryItem;
+ function IsMatched(Index: Integer): Boolean;
+ function IsUnknown(Index: Integer): Boolean;
+ procedure WriteString(fs: TFileStream; Text: AnsiString);
+ procedure WriteWideString(fs: TFileStream; Text: String);
+ procedure CheckBusy;
+ function GetSelItems(Index: Integer): Integer;
+ procedure SetSelItems(Index: Integer; Item: Integer);
+ procedure SetState(const Value: TGridState);
+ procedure SetReversed(const Value: Boolean);
+ procedure SetReversedHeader(const Value: Boolean);
+ procedure AdjustScrollBar;
+ procedure SetOptions(const Value: TGridOptions);
+ procedure SetMultiSelect(const Value: Boolean);
+{$IFDEF CUST_SB}
+ procedure SetVertScrollBar(const Value: TVertScrollBar);
+ function GetHideScrollBar: Boolean;
+ procedure SetHideScrollBar(const Value: Boolean);
+{$ENDIF}
+ function GetHitTests(X, Y: Integer): TGridHitTests;
+{$IFDEF RENDER_RICH}
+ function GetLinkAtPoint(X, Y: Integer): String;
+ function GetHintAtPoint(X, Y: Integer; var ObjectHint: WideString; var ObjectRect: TRect): Boolean;
+ function GetRichEditRect(Item: Integer; DontClipTop: Boolean = False): TRect;
+{$ENDIF}
+ procedure SetRTLMode(const Value: TRTLMode);
+ procedure SetExpandHeaders(const Value: Boolean);
+ procedure SetProcessInline(const Value: Boolean);
+ function GetBookmarked(Index: Integer): Boolean;
+ procedure SetBookmarked(Index: Integer; const Value: Boolean);
+ procedure SetGroupLinked(const Value: Boolean);
+ procedure SetHideSelection(const Value: Boolean);
+
+ // FRichInline events
+ { procedure OnInlinePopup(Sender: TObject);
+ procedure OnInlineCopyClick(Sender: TObject);
+ procedure OnInlineCopyAllClick(Sender: TObject);
+ procedure OnInlineSelectAllClick(Sender: TObject);
+ procedure OnInlineToggleProcessingClick(Sender: TObject);
+ procedure OnInlineCancelClick(Sender: TObject); }
+
+ procedure OnInlineOnExit(Sender: TObject);
+ procedure OnInlineOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
+ procedure OnInlineOnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
+ procedure OnInlineOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+ procedure OnInlineOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+ procedure OnInlineOnURLClick(Sender: TObject; const URLText: String; Button: TMouseButton);
+
+ function GetProfileName: String;
+ procedure SetProfileName(const Value: String);
+ procedure SetContactName(const Value: String);
+
+ function IsLinkAtPoint(RichEditRect: TRect; X, Y, Item: Integer): Boolean;
+
+ protected
+ DownHitTests: TGridHitTests;
+ HintHitTests: TGridHitTests;
+ procedure CreateWindowHandle(const Params: TCreateParams); override;
+ procedure CreateParams(var Params: TCreateParams); override;
+ // procedure WndProc(var Message: TMessage); override;
+ property Canvas: TCanvas read FCanvas;
+ procedure Paint;
+ procedure PaintHeader(Index: Integer; ItemRect: TRect);
+ procedure PaintItem(Index: Integer; ItemRect: TRect);
+ procedure DrawProgress;
+ procedure DrawMessage(Text: String);
+ procedure LoadItem(Item: Integer; LoadHeight: Boolean = True; Reload: Boolean = False);
+ procedure DoOptionsChanged;
+ procedure DoKeyDown(var Key: Word; ShiftState: TShiftState);
+ procedure DoKeyUp(var Key: Word; ShiftState: TShiftState);
+ procedure DoChar(var Ch: WideChar; ShiftState: TShiftState);
+ procedure DoLButtonDblClick(X, Y: Integer; Keys: TMouseMoveKeys);
+ procedure DoLButtonDown(X, Y: Integer; Keys: TMouseMoveKeys);
+ procedure DoLButtonUp(X, Y: Integer; Keys: TMouseMoveKeys);
+ procedure DoMouseMove(X, Y: Integer; Keys: TMouseMoveKeys);
+ procedure DoRButtonDown(X, Y: Integer; Keys: TMouseMoveKeys);
+ procedure DoRButtonUp(X, Y: Integer; Keys: TMouseMoveKeys);
+ procedure DoMButtonDown(X, Y: Integer; Keys: TMouseMoveKeys);
+ procedure DoMButtonUp(X, Y: Integer; Keys: TMouseMoveKeys);
+ // procedure DoUrlMouseMove(Url: WideString);
+ procedure DoProgress(Position, Max: Integer);
+ function CalcItemHeight(Item: Integer): Integer;
+ procedure ScrollBy(DeltaX, DeltaY: Integer);
+ procedure DeleteItem(Item: Integer);
+ procedure SaveStart(Stream: TFileStream; SaveFormat: TSaveFormat; Caption: String);
+ procedure SaveItem(Stream: TFileStream; Item: Integer; SaveFormat: TSaveFormat);
+ procedure SaveEnd(Stream: TFileStream; SaveFormat: TSaveFormat);
+
+ procedure GridUpdateSize;
+ function GetSelectionString: String;
+ procedure URLClick(Item: Integer; const URLText: String; Button: TMouseButton); dynamic;
+ public
+ constructor Create(AOwner: TComponent); override;
+ destructor Destroy; override;
+ property Count: Integer read GetCount;
+ property Contact: THandle read FContact write SetContact;
+ property Protocol: AnsiString read FProtocol write FProtocol;
+ property LoadedCount: Integer read FLoadedCount;
+ procedure Allocate(ItemsCount: Integer; Scroll: Boolean = True);
+ property Selected: Integer read FSelected write SetSelected;
+ property SelCount: Integer read GetSelCount;
+ function FindItemAt(X, Y: Integer; out ItemRect: TRect): Integer; overload;
+ function FindItemAt(P: TPoint; out ItemRect: TRect): Integer; overload;
+ function FindItemAt(P: TPoint): Integer; overload;
+ function FindItemAt(X, Y: Integer): Integer; overload;
+ function GetItemRect(Item: Integer): TRect;
+ function IsSelected(Item: Integer): Boolean;
+ procedure BeginUpdate;
+ procedure EndUpdate;
+ procedure GridUpdate(Updates: TGridUpdates);
+ function IsVisible(Item: Integer; Partially: Boolean = True): Boolean;
+ procedure Delete(Item: Integer);
+ procedure DeleteSelected;
+ procedure DeleteAll;
+ procedure SelectRange(FromItem, ToItem: Integer);
+ procedure SelectAll;
+ property Items[Index: Integer]: THistoryItem read GetItems;
+ property Bookmarked[Index: Integer]: Boolean read GetBookmarked write SetBookmarked;
+ property SelectedItems[Index: Integer]: Integer read GetSelItems write SetSelItems;
+ function Search(Text: String; CaseSensitive: Boolean; FromStart: Boolean = False;
+ SearchAll: Boolean = False; FromNext: Boolean = False; Down: Boolean = True): Integer;
+ function SearchItem(ItemID: Integer): Integer;
+ procedure AddItem;
+ procedure SaveSelected(FileName: String; SaveFormat: TSaveFormat);
+ procedure SaveAll(FileName: String; SaveFormat: TSaveFormat);
+ function GetNext(Item: Integer; Force: Boolean = False): Integer;
+ function GetDown(Item: Integer): Integer;
+ function GetPrev(Item: Integer; Force: Boolean = False): Integer;
+ function GetUp(Item: Integer): Integer;
+ function GetTopItem: Integer;
+ function GetBottomItem: Integer;
+ property State: TGridState read FState write SetState;
+ function GetFirstVisible: Integer;
+ procedure UpdateFilter;
+
+ procedure EditInline(Item: Integer);
+ procedure CancelInline(DoSetFocus: Boolean = True);
+ procedure AdjustInlineRichedit;
+ function GetItemInline: Integer;
+ property InlineRichEdit: THPPRichEdit read FRichInline write FRichInline;
+ property RichEdit: THPPRichEdit read FRich write FRich;
+
+ property Options: TGridOptions read FOptions write SetOptions;
+ property HotString: String read SearchPattern;
+ property RTLMode: TRTLMode read FRTLMode write SetRTLMode;
+
+ procedure MakeTopmost(Item: Integer);
+ procedure ScrollToBottom;
+ procedure ResetItem(Item: Integer);
+ procedure ResetAllItems;
+
+ procedure IntFormatItem(Item: Integer; var Tokens: TWideStrArray; var SpecialTokens: TIntArray);
+ procedure PrePaintWindow;
+
+ property Codepage: Cardinal read FCodepage write SetCodepage;
+ property Filter: TMessageTypes read FFilter write SetFilter;
+
+ property SelectionString: String read GetSelectionString;
+ published
+ procedure SetRichRTL(RTL: Boolean; RichEdit: THPPRichEdit; ProcessTag: Boolean = True);
+ function GetItemRTL(Item: Integer): Boolean;
+
+ // procedure CopyToClipSelected(const Format: WideString; ACodepage: Cardinal = CP_ACP);
+ procedure ApplyItemToRich(Item: Integer; RichEdit: THPPRichEdit = nil; ForceInline: Boolean = False);
+
+ function FormatItem(Item: Integer; Format: String): String;
+ function FormatItems(ItemList: array of Integer; Format: String): String;
+ function FormatSelected(const Format: String): String;
+
+ property ShowBottomAligned: Boolean read FShowBottomAligned write FShowBottomAligned;
+ property ShowBookmarks: Boolean read FShowBookmarks write FShowBookmarks;
+ property MultiSelect: Boolean read FMultiSelect write SetMultiSelect;
+ property ShowHeaders: Boolean read FShowHeaders write SetShowHeaders;
+ property ExpandHeaders: Boolean read FExpandHeaders write SetExpandHeaders default True;
+ property GroupLinked: Boolean read FGroupLinked write SetGroupLinked default False;
+ property ProcessInline: Boolean write SetProcessInline;
+ property TxtStartup: String read FTxtStartup write FTxtStartup;
+ property TxtNoItems: String read FTxtNoItems write FTxtNoItems;
+ property TxtNoSuch: String read FTxtNoSuch write FTxtNoSuch;
+ property TxtFullLog: String read FTxtFullLog write FTxtFullLog;
+ property TxtPartLog: String read FTxtPartLog write FTxtPartLog;
+ property TxtHistExport: String read FTxtHistExport write FTxtHistExport;
+ property TxtGenHist1: String read FTxtGenHist1 write FTxtGenHist1;
+ property TxtGenHist2: String read FTxtGenHist2 write FTxtGenHist2;
+ property TxtSessions: String read FTxtSessions write FTxtSessions;
+ // property Filter: TMessageTypes read FFilter write SetFilter;
+ property ProfileName: String read GetProfileName write SetProfileName;
+ property ContactName: String read FContactName write SetContactName;
+ property OnDblClick: TNotifyEvent read FDblClick write FDblClick;
+ property OnItemData: TGetItemData read FGetItemData write FGetItemData;
+ property OnNameData: TGetNameData read FGetNameData write FGetNameData;
+ property OnPopup: TOnPopup read FOnPopup write FOnPopup;
+ property OnTranslateTime: TOnTranslateTime read FTranslateTime write FTranslateTime;
+ property OnSearchFinished: TOnSearchFinished read FSearchFinished write FSearchFinished;
+ property OnItemDelete: TOnItemDelete read FItemDelete write FItemDelete;
+ property OnKeyDown;
+ property OnKeyUp;
+ property OnInlineKeyDown: TKeyEvent read FOnInlineKeyDown write FOnInlineKeyDown;
+ property OnInlineKeyUp: TKeyEvent read FOnInlineKeyUp write FOnInlineKeyUp;
+ property OnInlinePopup: TOnPopup read FOnInlinePopup write FOnInlinePopup;
+ property OnProcessInlineChange: TOnProcessInlineChange read FOnProcessInlineChange write FOnProcessInlineChange;
+ property OnOptionsChange: TOnOptionsChange read FOnOptionsChange write FOnOptionsChange;
+ property OnChar: TOnChar read FOnChar write FOnChar;
+ property OnState: TOnState read FOnState write FOnState;
+ property OnSelect: TOnSelect read FOnSelect write FOnSelect;
+ property OnXMLData: TGetXMLData read FGetXMLData write FGetXMLData;
+ property OnMCData: TGetMCData read FGetMCData write FGetMCData;
+ property OnRTLChange: TOnRTLChange read FOnRTLChange write FOnRTLChange;
+ { IFDEF RENDER_RICH }
+ property OnUrlClick: TUrlClickItemEvent read FOnUrlClick write FOnUrlClick;
+ { ENDIF }
+ property OnBookmarkClick: TOnBookmarkClick read FOnBookmarkClick write FOnBookmarkClick;
+ property OnItemFilter: TOnItemFilter read FOnItemFilter write FOnItemFilter;
+ property OnProcessRichText: TOnProcessRichText read FOnProcessRichText write FOnProcessRichText;
+ property OnSearchItem: TOnSearchItem read FOnSearchItem write FOnSearchItem;
+ property OnSelectRequest: TOnSelectRequest read FOnSelectRequest write FOnSelectRequest;
+ property OnFilterChange: TOnFilterChange read FOnFilterChange write FOnFilterChange;
+
+ property Reversed: Boolean read FReversed write SetReversed;
+ property ReversedHeader: Boolean read FReversedHeader write SetReversedHeader;
+ property TopItem: Integer read GetTopItem;
+ property BottomItem: Integer read GetBottomItem;
+ property ItemInline: Integer read GetItemInline;
+ property HideSelection: Boolean read FHideSelection write SetHideSelection default False;
+ property Align;
+ property Anchors;
+ property TabStop;
+ property Font;
+ property Color;
+ property ParentColor;
+ property BiDiMode;
+ property ParentBiDiMode;
+ property BevelEdges;
+ property BevelInner;
+ property BevelKind;
+ property BevelOuter;
+ property BevelWidth;
+ property BorderStyle: TBorderStyle read FBorderStyle write SetBorderStyle default bsSingle;
+ property BorderWidth;
+ property Ctl3D;
+ property ParentCtl3D;
+ property Padding: Integer read FPadding write SetPadding;
+{$IFDEF CUST_SB}
+ property VertScrollBar: TVertScrollBar read FVertScrollBar write SetVertScrollBar;
+ property HideScrollBar: Boolean read GetHideScrollBar write SetHideScrollBar;
+{$ENDIF}
+ // !! property Hint: String read GetHint write SetHint stored IsHintStored;
+ property ShowHint;
+ end;
+
+procedure Register;
+
+implementation
+
+{$I compilers.inc}
+
+uses
+ hpp_options, hpp_arrays, hpp_strparser,
+ ComObj;
+
+type
+ TMCHeader = packed record
+ Signature: array [0 .. 1] of AnsiChar;
+ Version: Integer;
+ DataSize: Integer;
+ end;
+
+const
+ HtmlStop = [#0, #10, #13, '<', '>', '[', ']', ' ', '''', '"'];
+
+var
+ mcHeader: TMCHeader = (Signature: 'HB'; Version: - 1; DataSize: 0;);
+
+function UrlHighlightHtml(Text: AnsiString): AnsiString;
+var
+ UrlStart, UrlCent, UrlEnd: Integer;
+ UrlStr: String;
+begin
+ Result := Text;
+ UrlCent := AnsiPos('://', string(Text));
+ while UrlCent > 0 do
+ begin
+ Text[UrlCent] := '!';
+ UrlStart := UrlCent;
+ UrlEnd := UrlCent + 2;
+ while UrlStart > 0 do
+ begin
+ if (Text[UrlStart - 1] in HtmlStop) then
+ break;
+ Dec(UrlStart);
+ end;
+ while UrlEnd < Length(Text) do
+ begin
+ if (Text[UrlEnd + 1] in HtmlStop) then
+ break;
+ Inc(UrlEnd);
+ end;
+ if (UrlEnd - 2 - UrlCent > 0) and (UrlCent - UrlStart - 1 > 0) then
+ begin
+ UrlStr := '<a class=url href="' + Copy(Result, UrlStart, UrlEnd - UrlStart + 1) + '">';
+ Insert(UrlStr, Result, UrlStart);
+ Insert('</a>', Result, UrlEnd + Length(UrlStr) + 1);
+ UrlStr := StringReplace(UrlStr, '://', '!//', [rfReplaceAll]);
+ Insert(UrlStr, Text, UrlStart);
+ Insert('</a>', Text, UrlEnd + Length(UrlStr) + 1);
+ end;
+ UrlCent := AnsiPos('://', Text);
+ end;
+end;
+
+function MakeTextHtmled(T: AnsiString): AnsiString;
+begin
+ Result := T;
+ // change & to &amp;
+ Result := StringReplace(Result, '&', '&amp;', [rfReplaceAll]);
+ Result := StringReplace(Result, '>', '&gt;', [rfReplaceAll]);
+ Result := StringReplace(Result, '<', '&lt;', [rfReplaceAll]);
+ Result := StringReplace(Result, #9, ' ', [rfReplaceAll]);
+ Result := StringReplace(Result, #13#10, '<br>', [rfReplaceAll]);
+end;
+
+function PointInRect(Pnt: TPoint; Rct: TRect): Boolean;
+begin
+ Result := (Pnt.X >= Rct.Left) and (Pnt.X <= Rct.Right) and (Pnt.Y >= Rct.Top) and
+ (Pnt.Y <= Rct.Bottom);
+end;
+
+function DoRectsIntersect(R1, R2: TRect): Boolean;
+begin
+ Result := (Max(R1.Left, R2.Left) < Min(R1.Right, R2.Right)) and
+ (Max(R1.Top, R2.Top) < Min(R1.Bottom, R2.Bottom));
+end;
+
+function TranslateKeys(const Keys: Integer): TMouseMoveKeys;
+begin
+ Result := [];
+ if Keys and MK_CONTROL > 0 then Result := Result + [mmkControl];
+ if Keys and MK_LBUTTON > 0 then Result := Result + [mmkLButton];
+ if Keys and MK_MBUTTON > 0 then Result := Result + [mmkMButton];
+ if Keys and MK_RBUTTON > 0 then Result := Result + [mmkRButton];
+ if Keys and MK_SHIFT > 0 then Result := Result + [mmkShift];
+end;
+
+function NotZero(X: DWord): DWord; // used that array doesn't store 0 for already loaded data
+begin
+ if X = 0 then
+ Result := 1
+ else
+ Result := X;
+end;
+
+procedure Register;
+begin
+ RegisterComponents('History++', [THistoryGrid]);
+end;
+
+{ THistoryGrid }
+
+constructor THistoryGrid.Create(AOwner: TComponent);
+const
+ GridStyle = [csCaptureMouse, csClickEvents, csDoubleClicks, csReflector, csOpaque,
+ csNeedsBorderPaint];
+var
+ dc: HDC;
+begin
+ inherited;
+ ShowHint := True;
+ HintHitTests := [];
+
+{$IFDEF RENDER_RICH}
+ FRichCache := TRichCache.Create(Self);
+
+ { tmp
+ FRich := TRichEdit.Create(Self);
+ FRich.Name := 'OrgFRich';
+ FRich.Visible := False;
+ // just a dirty hack to workaround problem with
+ // SmileyAdd making richedit visible all the time
+ FRich.Height := 1000;
+ FRich.Top := -1001;
+ // </hack>
+
+ // Don't give him grid as parent, or we'll have
+ // wierd problems with scroll bar
+ FRich.Parent := nil;
+ // on 9x wrong sizing
+ //FRich.PlainText := True;
+ FRich.WordWrap := True;
+ FRich.BorderStyle := bsNone;
+ FRich.OnResizeRequest := OnRichResize;
+ FRich.OnMouseMove := OnMouseMove;
+ // we cann't set specific params to FRich because
+ // it's handle is unknown yet. We do it in WMSize, but
+ // to prevent setting it multiple times
+ // we have this variable
+ }
+ FRichParamsSet := False;
+
+ // Ok, now inlined richedit
+ FRichInline := THPPRichEdit.Create(Self);
+ // workaround of SmileyAdd making richedit visible all the time
+ FRichInline.Top := -MaxInt;
+ FRichInline.Height := -1;
+ FRichInline.Name := 'FRichInline';
+ FRichInline.Visible := False;
+ // FRichInline.Parent := Self.Parent;
+ // FRichInline.PlainText := True;
+ FRichInline.WordWrap := True;
+ FRichInline.BorderStyle := bsNone;
+ FRichInline.ReadOnly := True;
+
+ FRichInline.ScrollBars := ssVertical;
+ FRichInline.HideScrollBars := True;
+
+ FRichInline.OnExit := OnInlineOnExit;
+ FRichInline.OnKeyDown := OnInlineOnKeyDown;
+ FRichInline.OnKeyUp := OnInlineOnKeyUp;
+ FRichInline.OnMouseDown := OnInlineOnMouseDown;
+ FRichInline.OnMouseUp := OnInlineOnMouseUp;
+ FRichInline.OnUrlClick := OnInlineOnURLClick;
+
+ FRichInline.Brush.Style := bsClear;
+
+ FItemInline := -1;
+
+{$ENDIF}
+ FCodepage := CP_ACP;
+ // FRTLMode := hppRTLDefault;
+
+ CHeaderHeight := -1;
+ PHeaderheight := -1;
+ FExpandHeaders := False;
+
+ TabStop := True;
+ MultiSelect := True;
+
+ TxtStartup := 'Starting up...';
+ TxtNoItems := 'History is empty';
+ TxtNoSuch := 'No such items';
+ TxtFullLog := 'Full History Log';
+ TxtPartLog := 'Partial History Log';
+ TxtHistExport := hppName + ' export';
+ TxtGenHist1 := '### (generated by ' + hppName + ' plugin)';
+ TxtGenHist2 := '<h6>Generated by <b dir="ltr">' + hppName + '</b> Plugin</h6>';
+ TxtSessions := 'Conversation started at %s';
+
+ FReversed := False;
+ FReversedHeader := False;
+
+ FState := gsIdle;
+
+ IsCanvasClean := False;
+
+ BarAdjusted := False;
+ Allocated := False;
+
+ ShowBottomAligned := False;
+
+ ProgressPercent := 255;
+ ShowProgress := False;
+
+ if NewStyleControls then
+ ControlStyle := GridStyle
+ else
+ ControlStyle := GridStyle + [csFramed];
+
+ LockCount := 0;
+
+ // fill all events with unknown to force filter reset
+ FFilter := GenerateEvents(FM_EXCLUDE, []) + [mtUnknown, mtCustom];
+
+ FSelected := -1;
+ FContact := 0;
+ FProtocol := '';
+ FPadding := 4;
+ FShowBookmarks := True;
+
+ FClient := TBitmap.Create;
+ FClient.Width := 1;
+ FClient.Height := 1;
+
+ FCanvas := FClient.Canvas;
+ FCanvas.Font.Name := 'MS Shell Dlg';
+
+ // get line scroll size depending on current dpi
+ // default is 13px for standard 96dpi
+ dc := GetDC(0);
+ LogX := GetDeviceCaps(dc, LOGPIXELSX);
+ LogY := GetDeviceCaps(dc, LOGPIXELSY);
+ ReleaseDC(0, dc);
+ VLineScrollSize := MulDiv(LogY, 13, 96);
+
+{$IFDEF CUST_SB}
+ FVertScrollBar := TVertScrollBar.Create(Self, sbVertical);
+{$ENDIF}
+ VertScrollBar.Increment := VLineScrollSize;
+
+ FBorderStyle := bsSingle;
+
+ FHideSelection := False;
+ FGridNotFocused := True;
+
+ FSelectionString := '';
+ FSelectionStored := False;
+end;
+
+destructor THistoryGrid.Destroy;
+begin
+{$IFDEF CUST_SB}
+ FVertScrollBar.Free;
+{$ENDIF}
+{$IFDEF RENDER_RICH}
+ FRichInline.Free;
+ // it gets deleted autmagically because FRich.Owner = Self
+ // FRich.Free;
+ FRich := nil;
+ FRichCache.Free;
+{$ENDIF}
+ if Assigned(Options) then
+ Options.DeleteGrid(Self);
+ FCanvas := nil;
+ FClient.Free;
+ Finalize(FItems);
+ inherited;
+end;
+
+{ //!!
+ function THistoryGrid.IsHintStored: Boolean;
+ begin
+ Result := TntControl_IsHintStored(Self)
+ end;
+
+ function THistoryGrid.GetHint: String;
+ begin
+ Result := TntControl_GetHint(Self)
+ end;
+
+ procedure THistoryGrid.SetHint(const Value: String);
+ begin
+ TntControl_SetHint(Self, Value);
+ end;
+}
+procedure THistoryGrid.CMHintShow(var Message: TMessage);
+var
+ Item: Integer;
+ tempHint: WideString;
+ tempRect: TRect;
+begin
+ With TCMHintShow(Message).HintInfo^ do
+ begin
+ if ghtButton in HintHitTests then
+ begin
+ CursorRect := FHintRect;
+ if ghtBookmark in HintHitTests then
+ begin
+ Item := FindItemAt(CursorPos);
+ if FItems[Item].Bookmarked then
+ Hint := TranslateW('Remove Bookmark')
+ else
+ Hint := TranslateW('Set Bookmark')
+ end
+ else if ghtSessHideButton in HintHitTests then
+ Hint := TranslateW('Hide headers')
+ else if ghtSessShowButton in HintHitTests then
+ Hint := TranslateW('Show headers');
+ Message.Result := 0;
+ end
+ else if (ghtUnknown in HintHitTests) and GetHintAtPoint(CursorPos.X, CursorPos.Y, tempHint,
+ tempRect) then
+ begin
+ Hint := WideStringReplace(tempHint, '|', '¦', [rfReplaceAll]);
+ CursorRect := tempRect;
+ Message.Result := 0;
+ end
+ else
+ Message.Result := 1;
+ end;
+ // !! ProcessCMHintShowMsg(Message);
+ inherited;
+end;
+
+function THistoryGrid.GetBookmarked(Index: Integer): Boolean;
+begin
+ Result := Items[Index].Bookmarked;
+end;
+
+function THistoryGrid.GetBottomItem: Integer;
+begin
+ if Reversed then
+ Result := GetUp(-1)
+ else
+ Result := GetUp(Count);
+end;
+
+function THistoryGrid.GetCount: Integer;
+begin
+ Result := Length(FItems);
+end;
+
+procedure THistoryGrid.Allocate(ItemsCount: Integer; Scroll: Boolean = True);
+var
+ i: Integer;
+ PrevCount: Integer;
+begin
+ PrevCount := Length(FItems);
+ SetLength(FItems, ItemsCount);
+ for i := PrevCount to ItemsCount - 1 do
+ begin
+ FItems[i].Height := -1;
+ FItems[i].MessageType := [mtUnknown];
+ FRichCache.ResetItem(i);
+ end;
+{$IFDEF PAGE_SIZE}
+ VertScrollBar.Range := ItemsCount + FVertScrollBar.PageSize - 1;
+{$ELSE}
+ VertScrollBar.Range := ItemsCount + ClientHeight;
+{$ENDIF}
+ BarAdjusted := False;
+ Allocated := True;
+ // if ItemsCount > 0 then SetSBPos(GetIdx(0));
+ if Scroll then
+ begin
+ if Reversed xor ReversedHeader then
+ SetSBPos(GetIdx(GetBottomItem))
+ else
+ SetSBPos(GetIdx(GetTopItem));
+ end
+ else
+ AdjustScrollBar;
+ Invalidate;
+end;
+
+procedure THistoryGrid.LoadItem(Item: Integer; LoadHeight: Boolean = True; Reload: Boolean = False);
+begin
+ if Reload or IsUnknown(Item) then
+ if Assigned(FGetItemData) then
+ OnItemData(Self, Item, FItems[Item]);
+ if LoadHeight then
+ if FItems[Item].Height = -1 then
+ FItems[Item].Height := CalcItemHeight(Item);
+end;
+
+procedure THistoryGrid.Paint;
+var
+ TextRect, HeaderRect: TRect;
+ Ch, cw: Integer;
+ idx, cnt: Integer;
+ SumHeight: Integer;
+begin
+ if csDesigning in ComponentState then
+ exit;
+
+ if not Allocated then
+ begin
+ DrawMessage(TxtStartup);
+ exit;
+ end
+ else if ShowProgress then
+ begin
+ DrawProgress;
+ exit;
+ end;
+
+ cnt := Count;
+ if cnt = 0 then
+ begin
+ DrawMessage(TxtNoItems);
+ exit;
+ end;
+
+ idx := GetFirstVisible;
+ { REV
+ idx := GetNext(VertScrollBar.Position-1);
+ }
+ if idx = -1 then
+ begin
+ DrawMessage(TxtNoSuch);
+ exit;
+ end;
+
+ if WindowPrePainted then
+ begin
+ WindowPrePainted := False;
+ exit;
+ end;
+
+ SumHeight := -TopItemOffset;
+ Ch := ClientHeight;
+ cw := ClientWidth;
+
+ while (SumHeight < Ch) and (idx >= 0) and (idx < cnt) do
+ begin
+ LoadItem(idx);
+ TextRect := Bounds(0, SumHeight, cw, FItems[idx].Height);
+ if DoRectsIntersect(ClipRect, TextRect) then
+ begin
+ Canvas.Brush.Color := Options.ColorDivider;
+ Canvas.FillRect(TextRect);
+ if (FItems[idx].HasHeader) and (ShowHeaders) and (ExpandHeaders) then
+ begin
+ if Reversed xor ReversedHeader then
+ begin
+ HeaderRect := Rect(0, TextRect.Top, cw, TextRect.Top + SessHeaderHeight);
+ Inc(TextRect.Top, SessHeaderHeight);
+ end
+ else
+ begin
+ HeaderRect := Rect(0, TextRect.Bottom - SessHeaderHeight, cw, TextRect.Bottom);
+ Dec(TextRect.Bottom, SessHeaderHeight);
+ end;
+ PaintHeader(idx, HeaderRect);
+ end;
+ PaintItem(idx, TextRect);
+ end;
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetNext(idx);
+ if idx = -1 then
+ break;
+ end;
+ if SumHeight < Ch then
+ begin
+ TextRect := Rect(0, SumHeight, cw, Ch);
+ if DoRectsIntersect(ClipRect, TextRect) then
+ begin
+ Canvas.Brush.Color := Options.ColorBackground;
+ Canvas.FillRect(TextRect);
+ end;
+ end;
+end;
+
+procedure THistoryGrid.PaintHeader(Index: Integer; ItemRect: TRect);
+var
+ Text: String;
+ RTL: Boolean;
+ RIconOffset, IconOffset, IconTop: Integer;
+ TextOffset: Integer;
+ // ArrIcon: Integer;
+ // BackColor: TColor;
+ // TextFont: TFont;
+begin
+ RTL := GetItemRTL(Index);
+ // Options.GetItemOptions(FItems[Index].MessageType,textFont,BackColor);
+
+ if not(RTL = ((Canvas.TextFlags and ETO_RTLREADING) > 0)) then
+ begin
+ if RTL then
+ Canvas.TextFlags := Canvas.TextFlags or ETO_RTLREADING
+ else
+ Canvas.TextFlags := Canvas.TextFlags and not ETO_RTLREADING;
+ end;
+
+ // leave divider lines:
+ // Inc(ItemRect.Top);
+ Dec(ItemRect.Bottom, 1);
+
+ Canvas.Brush.Color := Options.ColorSessHeader;
+ Canvas.FillRect(ItemRect);
+
+ InflateRect(ItemRect, -3, -3);
+
+ IconOffset := 0;
+ RIconOffset := 0;
+ IconTop := ((ItemRect.Bottom - ItemRect.Top - 16) div 2);
+
+ if (ShowHeaders) and (FItems[Index].HasHeader) and (ExpandHeaders) then
+ begin
+ if RTL then
+ DrawIconEx(Canvas.Handle, ItemRect.Left, ItemRect.Top + IconTop,
+ hppIcons[HPP_ICON_SESS_HIDE].Handle, 16, 16, 0, 0, DI_NORMAL)
+ else
+ DrawIconEx(Canvas.Handle, ItemRect.Right - 16, ItemRect.Top + IconTop,
+ hppIcons[HPP_ICON_SESS_HIDE].Handle, 16, 16, 0, 0, DI_NORMAL);
+ Inc(RIconOffset, 16 + Padding);
+ end;
+
+ if hppIcons[HPP_ICON_SESS_DIVIDER].Handle <> 0 then
+ begin
+ if RTL then
+ DrawIconEx(Canvas.Handle, ItemRect.Right - 16 - IconOffset, ItemRect.Top + IconTop,
+ hppIcons[HPP_ICON_SESS_DIVIDER].Handle, 16, 16, 0, 0, DI_NORMAL)
+ else
+ DrawIconEx(Canvas.Handle, ItemRect.Left + IconOffset, ItemRect.Top + IconTop,
+ hppIcons[HPP_ICON_SESS_DIVIDER].Handle, 16, 16, 0, 0, DI_NORMAL);
+ Inc(IconOffset, 16 + Padding);
+ end;
+
+ Text := Format(TxtSessions, [GetTime(Items[Index].Time)]);
+ // Canvas.Font := Options.FontSessHeader;
+ Canvas.Font.Assign(Options.FontSessHeader);
+ Inc(ItemRect.Left, IconOffset);
+ Dec(ItemRect.Right, RIconOffset);
+ if RTL then
+ begin
+ TextOffset := Canvas.TextExtent(Text).cX;
+ Canvas.TextRect(ItemRect, ItemRect.Right - TextOffset, ItemRect.Top, Text);
+ end
+ else
+ Canvas.TextRect(ItemRect, ItemRect.Left, ItemRect.Top, Text);
+end;
+
+procedure THistoryGrid.SetBookmarked(Index: Integer; const Value: Boolean);
+var
+ r: TRect;
+begin
+ // don't set unknown items, we'll got correct bookmarks when we load them anyway
+ if IsUnknown(Index) then
+ exit;
+ if Bookmarked[Index] = Value then
+ exit;
+ FItems[Index].Bookmarked := Value;
+ if IsVisible(Index) then
+ begin
+ r := GetItemRect(Index);
+ InvalidateRect(Handle, @r, False);
+ Update;
+ end;
+end;
+
+procedure THistoryGrid.SetCodepage(const Value: Cardinal);
+begin
+ if FCodepage = Value then
+ exit;
+ FCodepage := Value;
+ ResetAllItems;
+end;
+
+procedure THistoryGrid.SetContact(const Value: THandle);
+begin
+ if FContact = Value then
+ exit;
+ FContact := Value;
+end;
+
+procedure THistoryGrid.SetExpandHeaders(const Value: Boolean);
+var
+ i: Integer;
+begin
+ if FExpandHeaders = Value then
+ exit;
+ FExpandHeaders := Value;
+ for i := 0 to Length(FItems) - 1 do
+ begin
+ if FItems[i].HasHeader then
+ begin
+ FItems[i].Height := -1;
+ FRichCache.ResetItem(i);
+ end;
+ end;
+ BarAdjusted := False;
+ AdjustScrollBar;
+ Invalidate;
+end;
+
+procedure THistoryGrid.SetGroupLinked(const Value: Boolean);
+var
+ i: Integer;
+begin
+ if FGroupLinked = Value then
+ exit;
+ FGroupLinked := Value;
+ for i := 0 to Length(FItems) - 1 do
+ begin
+ if FItems[i].LinkedToPrev then
+ begin
+ FItems[i].Height := -1;
+ FRichCache.ResetItem(i);
+ end;
+ end;
+ BarAdjusted := False;
+ AdjustScrollBar;
+ Invalidate;
+end;
+
+procedure THistoryGrid.SetProcessInline(const Value: Boolean);
+// var
+// cr: CHARRANGE;
+begin
+ if State = gsInline then
+ begin
+ FRichInline.Lines.BeginUpdate;
+ // FRichInline.Perform(EM_EXGETSEL,0,LPARAM(@cr));
+ ApplyItemToRich(Selected, FRichInline);
+ // FRichInline.Perform(EM_EXSETSEL,0,LPARAM(@cr));
+ // FRichInline.Perform(EM_SCROLLCARET, 0, 0);
+ FRichInline.SelStart := 0;
+ FRichInline.Lines.EndUpdate;
+ end;
+ if Assigned(FOnProcessInlineChange) then
+ FOnProcessInlineChange(Self, Value);
+end;
+
+procedure THistoryGrid.WMEraseBkgnd(var Message: TWMEraseBkgnd);
+begin
+ Message.Result := 1;
+end;
+
+procedure THistoryGrid.WMPaint(var Message: TWMPaint);
+var
+ ps: TagPaintStruct;
+ dc: HDC;
+begin
+ if (LockCount > 0) or (csDestroying in ComponentState) then
+ begin
+ Message.Result := 1;
+ exit;
+ end;
+ dc := BeginPaint(Handle, ps);
+ ClipRect := ps.rcPaint;
+ try
+ Paint;
+ BitBlt(dc, ClipRect.Left, ClipRect.Top, ClipRect.Right - ClipRect.Left,
+ ClipRect.Bottom - ClipRect.Top, Canvas.Handle, ClipRect.Left, ClipRect.Top, SRCCOPY);
+ finally
+ EndPaint(Handle, ps);
+ Message.Result := 0;
+ end;
+end;
+
+procedure THistoryGrid.WMSize(var Message: TWMSize);
+// var
+// re_mask: Longint;
+begin
+ BeginUpdate;
+ if not FRichParamsSet then
+ begin
+ FRichCache.SetHandles;
+ FRichParamsSet := True;
+ FRichInline.ParentWindow := Handle;
+ // re_mask := SendMessage(FRichInline.Handle,EM_GETEVENTMASK,0,0);
+ // SendMessage(FRichInline.Handle,EM_SETEVENTMASK,0,re_mask or ENM_LINK);
+ // SendMessage(FRichInline.Handle,EM_AUTOURLDETECT,1,0);
+ // SendMessage(FRichInline.Handle,EM_SETMARGINS,EC_LEFTMARGIN or EC_RIGHTMARGIN,0);
+ end;
+ // Update;
+ GridUpdate([guSize]);
+ EndUpdate;
+end;
+
+procedure THistoryGrid.SetPadding(Value: Integer);
+begin
+ if Value = FPadding then
+ exit;
+ FPadding := Value;
+end;
+
+procedure THistoryGrid.WMVScroll(var Message: TWMVScroll);
+var
+ r: TRect;
+ Item1, Item2, SBPos: Integer;
+ off, idx, first, ind: Integer;
+begin
+ CheckBusy;
+ if Message.ScrollCode = SB_ENDSCROLL then
+ begin
+ Message.Result := 0;
+ exit;
+ end;
+
+ BeginUpdate;
+ try
+
+ if Message.ScrollCode in [SB_LINEUP, SB_LINEDOWN, SB_PAGEDOWN, SB_PAGEUP] then
+ begin
+ Message.Result := 0;
+ case Message.ScrollCode of
+ SB_LINEDOWN:
+ ScrollGridBy(VLineScrollSize);
+ SB_LINEUP:
+ ScrollGridBy(-VLineScrollSize);
+ SB_PAGEDOWN:
+ ScrollGridBy(ClientHeight);
+ SB_PAGEUP:
+ ScrollGridBy(-ClientHeight);
+ end;
+ exit;
+ end;
+
+ idx := VertScrollBar.Position;
+ ind := idx;
+ first := GetFirstVisible;
+
+ // OXY: This code prevents thumb from staying "between" filtered items
+ // but it leads to thumb "jumping" after user finishes thumbtracking
+ // uncomment if this "stuck-in-between" seems to produce bug
+ { if Message.ScrollCode = SB_THUMBPOSITION then begin
+ Message.Pos := GetIdx(first);
+ VertScrollBar.ScrollMessage(Message);
+ exit;
+ end; }
+
+{$IFDEF CUST_SB}
+ if (Message.ScrollBar = 0) and FVertScrollBar.Visible then
+ FVertScrollBar.ScrollMessage(Message)
+ else
+ inherited;
+{$ELSE}
+ inherited;
+{$ENDIF}
+ SBPos := VertScrollBar.Position;
+ off := SBPos - idx;
+
+ // if (VertScrollBar.Position > MaxSBPos) and (off=0) then begin
+ // SetSBPos(VertScrollBar.Position);
+ // exit;
+ // end;
+ { if (off=0) and (VertScrollBar.Position > MaxSBPos) then begin
+ SetSBPos(VertScrollBar.Position);
+ Invalidate;
+ exit;
+ end; }
+
+ if not(VertScrollBar.Position > MaxSBPos) then
+ TopItemOffset := 0;
+ if off = 0 then
+ exit;
+ if off > 0 then
+ begin
+ idx := GetNext(GetIdx(VertScrollBar.Position - 1));
+ if (idx = first) and (idx <> -1) then
+ begin
+ idx := GetNext(idx);
+ if idx = -1 then
+ idx := first;
+ end;
+ if idx = -1 then
+ begin
+ idx := GetPrev(GetIdx(VertScrollBar.Position + 1));
+ if idx = -1 then
+ idx := ind;
+ end;
+ end;
+ if off < 0 then
+ begin
+ idx := GetPrev(GetIdx(VertScrollBar.Position + 1));
+ if (idx = first) and (idx <> -1) then
+ begin
+ idx := GetPrev(idx);
+ // if idx := -1 then idx := Count-1;
+ end;
+ if (idx <> first) and (idx <> -1) then
+ begin
+ first := idx;
+ idx := GetPrev(idx);
+ if idx <> -1 then
+ idx := first
+ else
+ idx := GetIdx(0);
+ end;
+ if idx = -1 then
+ begin
+ idx := GetNext(GetIdx(VertScrollBar.Position - 1));
+ if idx = -1 then
+ idx := ind;
+ end;
+ end;
+ { BUG HERE (not actually here, but..)
+ If you filter by (for example) files and you have several files
+ and large history, then when tracking throu files, you'll see
+ flicker, like constantly scrolling up & down by 1 event. That's
+ because when you scroll down by 1, this proc finds next event and
+ scrolls to it. But when you continue your move, your track
+ position becomes higher then current pos, and we search backwards,
+ and scroll to prev event. That's why flicker takes place. Need to
+ redesign some things to fix it }
+ // OXY 2006-03-05: THIS BUG FIXED!.!
+ // Now while thumbtracking we look if we are closer to next item
+ // than to original item. If we are closer, then scroll. If not, then
+ // don't change position and wait while user scrolls futher.
+ // With this we have ONE MORE bug: when user stops tracking,
+ // we leave thumb were it left, while we need to put it on the item
+
+ Item1 := GetIdx(first);
+ Item2 := GetIdx(idx);
+ if not(Message.ScrollCode in [SB_THUMBTRACK, SB_THUMBPOSITION]) then
+ SetSBPos(Item2)
+ else
+ begin
+ if (SBPos >= Item1) and (Item2 > MaxSBPos) then
+ SetSBPos(Item2)
+ else if Abs(Item1 - SBPos) > Abs(Item2 - SBPos) then
+ SetSBPos(Item2);
+ end;
+
+ AdjustScrollBar;
+
+ r := ClientRect;
+ InvalidateRect(Handle, @r, False);
+ finally
+ EndUpdate;
+ Update;
+ end;
+end;
+
+procedure THistoryGrid.PaintItem(Index: Integer; ItemRect: TRect);
+var
+ TimeStamp, HeaderName: String;
+ OrgRect, ItemClipRect: TRect;
+ TopIconOffset, IconOffset, TimeOffset: Integer;
+ // icon: TIcon;
+ BackColor: TColor;
+ nameFont, timestampFont, textFont: TFont;
+ Sel: Boolean;
+ RTL: Boolean;
+ FullHeader: Boolean;
+ RichBMP: TBitmap;
+ ic: HICON;
+ HeadRect: TRect;
+ dtf: Integer;
+ er: PEventRecord;
+begin
+ // leave divider line
+ Dec(ItemRect.Bottom);
+ OrgRect := ItemRect;
+
+ Sel := IsSelected(Index);
+ Options.GetItemOptions(FItems[Index].MessageType, textFont, BackColor);
+ if Sel then
+ BackColor := Options.ColorSelected;
+
+ IntersectRect(ItemClipRect, ItemRect, ClipRect);
+ Canvas.Brush.Color := BackColor;
+ Canvas.FillRect(ItemClipRect);
+
+ InflateRect(ItemRect, -Padding, -Padding);
+
+ FullHeader := not(FGroupLinked and FItems[Index].LinkedToPrev);
+ if FullHeader then
+ begin
+ HeadRect := ItemRect;
+ HeadRect.Top := HeadRect.Top - Padding + (Padding div 2);
+ if mtIncoming in FItems[Index].MessageType then
+ HeadRect.Bottom := HeadRect.Top + CHeaderHeight
+ else
+ HeadRect.Bottom := HeadRect.Top + PHeaderheight;
+ ItemRect.Top := HeadRect.Bottom + Padding - (Padding div 2);
+ end;
+
+ if FullHeader and DoRectsIntersect(HeadRect, ClipRect) then
+ begin
+{$IFDEF DEBUG}
+ OutputDebugString(PWideChar('Paint item header ' + intToStr(Index) + ' to screen'));
+{$ENDIF}
+ if mtIncoming in FItems[Index].MessageType then
+ begin
+ nameFont := Options.FontContact;
+ timestampFont := Options.FontIncomingTimestamp;
+ HeaderName := ContactName;
+ end
+ else
+ begin
+ nameFont := Options.FontProfile;
+ timestampFont := Options.FontOutgoingTimestamp;
+ HeaderName := ProfileName;
+ end;
+ if Assigned(FGetNameData) then
+ FGetNameData(Self, Index, HeaderName);
+ HeaderName := HeaderName + ':';
+ TimeStamp := GetTime(FItems[Index].Time);
+
+ RTL := GetItemRTL(Index);
+ if not(RTL = ((Canvas.TextFlags and ETO_RTLREADING) > 0)) then
+ begin
+ if RTL then
+ Canvas.TextFlags := Canvas.TextFlags or ETO_RTLREADING
+ else
+ Canvas.TextFlags := Canvas.TextFlags and not ETO_RTLREADING;
+ end;
+
+ TopIconOffset := ((HeadRect.Bottom - HeadRect.Top) - 16) div 2;
+ if (FItems[Index].HasHeader) and (ShowHeaders) and (not ExpandHeaders) then
+ begin
+ if RTL then
+ begin
+ DrawIconEx(Canvas.Handle, HeadRect.Right - 16, HeadRect.Top + TopIconOffset,
+ hppIcons[HPP_ICON_SESS_DIVIDER].Handle, 16, 16, 0, 0, DI_NORMAL);
+ Dec(HeadRect.Right, 16 + Padding);
+ end
+ else
+ begin
+ DrawIconEx(Canvas.Handle, HeadRect.Left, HeadRect.Top + TopIconOffset,
+ hppIcons[HPP_ICON_SESS_DIVIDER].Handle, 16, 16, 0, 0, DI_NORMAL);
+ Inc(HeadRect.Left, 16 + Padding);
+ end;
+ end;
+
+ if Options.ShowIcons then
+ begin
+ er := GetEventRecord(FItems[Index]);
+ if er.i = -1 then
+ ic := 0
+ else if er.iSkin = -1 then
+ ic := hppIcons[er.i].Handle
+ else
+ ic := skinIcons[er.i].Handle;
+ if ic <> 0 then
+ begin
+ // canvas. draw here can sometimes draw 32x32 icon (sic!)
+ if RTL then
+ begin
+ DrawIconEx(Canvas.Handle, HeadRect.Right - 16, HeadRect.Top + TopIconOffset, ic, 16,
+ 16, 0, 0, DI_NORMAL);
+ Dec(HeadRect.Right, 16 + Padding);
+ end
+ else
+ begin
+ DrawIconEx(Canvas.Handle, HeadRect.Left, HeadRect.Top + TopIconOffset, ic, 16, 16, 0,
+ 0, DI_NORMAL);
+ Inc(HeadRect.Left, 16 + Padding);
+ end;
+ end;
+ end;
+
+ // Canvas.Font := nameFont;
+ Canvas.Font.Assign(nameFont);
+ if Sel then
+ Canvas.Font.Color := Options.ColorSelectedText;
+ dtf := DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER;
+ if RTL then
+ dtf := dtf or DT_RTLREADING or DT_RIGHT
+ else
+ dtf := dtf or DT_LEFT;
+ DrawTextW(Canvas.Handle, PWideChar(HeaderName), Length(HeaderName), HeadRect, dtf);
+
+ // Canvas.Font := timestampFont;
+ Canvas.Font.Assign(timestampFont);
+ if Sel then
+ Canvas.Font.Color := Options.ColorSelectedText;
+ TimeOffset := Canvas.TextExtent(TimeStamp).cX;
+ dtf := DT_NOPREFIX or DT_SINGLELINE or DT_VCENTER;
+ if RTL then
+ dtf := dtf or DT_RTLREADING or DT_LEFT
+ else
+ dtf := dtf or DT_RIGHT;
+ DrawTextW(Canvas.Handle, PWideChar(TimeStamp), Length(TimeStamp), HeadRect, dtf);
+
+ if ShowBookmarks and (Sel or FItems[Index].Bookmarked) then
+ begin
+ IconOffset := TimeOffset + Padding;
+ if FItems[Index].Bookmarked then
+ ic := hppIcons[HPP_ICON_BOOKMARK_ON].Handle
+ else
+ ic := hppIcons[HPP_ICON_BOOKMARK_OFF].Handle;
+ if RTL then
+ DrawIconEx(Canvas.Handle, HeadRect.Left + IconOffset, HeadRect.Top + TopIconOffset, ic,
+ 16, 16, 0, 0, DI_NORMAL)
+ else
+ DrawIconEx(Canvas.Handle, HeadRect.Right - IconOffset - 16,
+ HeadRect.Top + TopIconOffset, ic, 16, 16, 0, 0, DI_NORMAL);
+ end;
+ end;
+
+ if DoRectsIntersect(ItemRect, ClipRect) then
+ begin
+{$IFDEF DEBUG}
+ OutputDebugString(PWideChar('Paint item body ' + intToStr(Index) + ' to screen'));
+{$ENDIF}
+ ApplyItemToRich(Index);
+ RichBMP := FRichCache.GetItemRichBitmap(Index);
+ ItemClipRect := Bounds(ItemRect.Left, ItemRect.Top, RichBMP.Width, RichBMP.Height);
+ IntersectRect(ItemClipRect, ItemClipRect, ClipRect);
+ BitBlt(Canvas.Handle, ItemClipRect.Left, ItemClipRect.Top,
+ ItemClipRect.Right - ItemClipRect.Left, ItemClipRect.Bottom - ItemClipRect.Top,
+ RichBMP.Canvas.Handle, ItemClipRect.Left - ItemRect.Left,
+ ItemClipRect.Top - ItemRect.Top, SRCCOPY);
+ end;
+
+ // if (Focused or WindowPrePainting) and (Index = Selected) then begin
+ if (not FGridNotFocused or WindowPrePainting) and (Index = Selected) then
+ begin
+ DrawFocusRect(Canvas.Handle, OrgRect);
+ end;
+end;
+
+procedure THistoryGrid.PrePaintWindow;
+begin
+ ClipRect := Rect(0, 0, ClientWidth, ClientHeight);
+ WindowPrePainting := True;
+ Paint;
+ WindowPrePainting := False;
+ WindowPrePainted := True;
+end;
+
+procedure THistoryGrid.MakeSelected(Value: Integer);
+var
+ OldSelected: Integer;
+begin
+ OldSelected := FSelected;
+ FSelected := Value;
+ if Value <> -1 then
+ MakeVisible(FSelected);
+ if Assigned(FOnSelect) then
+ begin
+ if IsVisible(FSelected) then
+ FOnSelect(Self, FSelected, OldSelected)
+ else
+ FOnSelect(Self, -1, OldSelected);
+ end;
+ FSelectionStored := False;
+end;
+
+procedure THistoryGrid.SetSelected(const Value: Integer);
+begin
+ // if IsSelected(Value) then exit;
+ FRichCache.ResetItem(Value);
+ // FRichCache.ResetItem(FSelected);
+ FRichCache.ResetItems(FSelItems);
+ if Value <> -1 then
+ begin
+ SetLength(FSelItems, 1);
+ FSelItems[0] := Value;
+ end
+ else
+ SetLength(FSelItems, 0);
+ MakeSelected(Value);
+ Invalidate;
+ Update;
+end;
+
+procedure THistoryGrid.SetShowHeaders(const Value: Boolean);
+var
+ i: Integer;
+begin
+ if FShowHeaders = Value then
+ exit;
+ FShowHeaders := Value;
+ for i := 0 to Length(FItems) - 1 do
+ begin
+ if FItems[i].HasHeader then
+ begin
+ FItems[i].Height := -1;
+ FRichCache.ResetItem(i);
+ end;
+ end;
+ BarAdjusted := False;
+ AdjustScrollBar;
+ Invalidate;
+end;
+
+procedure THistoryGrid.AddSelected(Item: Integer);
+begin
+ if IsSelected(Item) then
+ exit;
+ if IsUnknown(Item) then
+ LoadItem(Item, False);
+ if not IsMatched(Item) then
+ exit;
+ IntSortedArray_Add(TIntArray(FSelItems), Item);
+ FRichCache.ResetItem(Item);
+end;
+
+function THistoryGrid.FindItemAt(X, Y: Integer; out ItemRect: TRect): Integer;
+var
+ SumHeight: Integer;
+ idx: Integer;
+begin
+ Result := -1;
+ ItemRect := Rect(0, 0, 0, 0);
+ if Count = 0 then
+ exit;
+
+ SumHeight := TopItemOffset;
+ if Y < 0 then
+ begin
+ idx := GetFirstVisible;
+ while idx >= 0 do
+ begin
+ if Y > -SumHeight then
+ begin
+ Result := idx;
+ break;
+ end;
+ idx := GetPrev(idx);
+ if idx = -1 then
+ break;
+ LoadItem(idx, True);
+ Inc(SumHeight, FItems[idx].Height);
+ end;
+ exit;
+ end;
+
+ idx := GetFirstVisible;
+
+ SumHeight := -TopItemOffset;
+ while (idx >= 0) and (idx < Length(FItems)) do
+ begin
+ LoadItem(idx, True);
+ if Y < SumHeight + FItems[idx].Height then
+ begin
+ Result := idx;
+ break;
+ end;
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetDown(idx);
+ if idx = -1 then
+ break;
+ end;
+ { FIX: 2004-08-20, can have AV here, how could I miss this line? }
+ if Result = -1 then
+ exit;
+ ItemRect := Rect(0, SumHeight, ClientWidth, SumHeight + FItems[Result].Height);
+end;
+
+function THistoryGrid.FindItemAt(P: TPoint; out ItemRect: TRect): Integer;
+begin
+ Result := FindItemAt(P.X, P.Y, ItemRect);
+end;
+
+function THistoryGrid.FindItemAt(P: TPoint): Integer;
+var
+ r: TRect;
+begin
+ Result := FindItemAt(P.X, P.Y, r);
+end;
+
+function THistoryGrid.FindItemAt(X, Y: Integer): Integer;
+var
+ r: TRect;
+begin
+ Result := FindItemAt(X, Y, r);
+end;
+
+function THistoryGrid.FormatItem(Item: Integer; Format: String): String;
+var
+ tok: TWideStrArray;
+ toksp: TIntArray;
+ i: Integer;
+begin
+ TokenizeString(Format, tok, toksp);
+ LoadItem(Item, False);
+ IntFormatItem(Item, tok, toksp);
+ Result := '';
+ for i := 0 to Length(tok) - 1 do
+ Result := Result + tok[i];
+end;
+
+function THistoryGrid.FormatItems(ItemList: array of Integer; Format: String): String;
+var
+ ifrom, ito, step, i, n: Integer;
+ linebreak: String;
+ tok2, tok: TWideStrArray;
+ toksp, tok_smartdt: TIntArray;
+ prevdt, dt: TDateTime;
+begin
+ // array of items MUST be a sorted list!
+
+ Result := '';
+ linebreak := #13#10;
+ TokenizeString(Format, tok, toksp);
+
+ // detect if we have smart_datetime in the tokens
+ // and cache them if we do
+ for n := 0 to Length(toksp) - 1 do
+ if tok[toksp[n]] = '%smart_datetime%' then
+ begin
+ SetLength(tok_smartdt, Length(tok_smartdt) + 1);
+ tok_smartdt[High(tok_smartdt)] := toksp[n];
+ end;
+ dt := 0;
+ prevdt := 0;
+
+ // start processing all items
+
+ // if Reversed then begin
+ // from older to newer, excluding external grid
+ if not ReversedHeader then
+ begin
+ ifrom := High(ItemList);
+ ito := 0;
+ step := -1;
+ end
+ else
+ begin
+ ifrom := 0;
+ ito := High(ItemList);
+ step := 1;
+ end;
+ i := ifrom;
+ while (i >= 0) and (i <= High(ItemList)) do
+ begin
+ LoadItem(ItemList[i], False);
+ if i = ito then
+ linebreak := ''; // do not put linebr after last item
+ tok2 := Copy(tok, 0, Length(tok));
+
+ // handle smart dates:
+ if Length(tok_smartdt) > 0 then
+ begin
+ dt := TimestampToDateTime(FItems[ItemList[i]].Time);
+ if prevdt <> 0 then
+ if Trunc(dt) = Trunc(prevdt) then
+ for n := 0 to Length(tok_smartdt) - 1 do
+ tok2[tok_smartdt[n]] := '%time%';
+ end; // end smart dates
+
+ IntFormatItem(ItemList[i], tok2, toksp);
+ for n := 0 to Length(tok2) - 1 do
+ Result := Result + tok2[n];
+ Result := Result + linebreak;
+ prevdt := dt;
+ Inc(i, step);
+ end;
+end;
+
+function THistoryGrid.FormatSelected(const Format: String): String;
+begin
+ if SelCount = 0 then
+ Result := ''
+ else
+ Result := FormatItems(FSelItems, Format);
+end;
+
+var
+ // WasDownOnGrid hack was introduced
+ // because I had the following problem: when I have
+ // history of contact A opened and have search results
+ // with messages from A, and if the history is behind the
+ // search results window, when I double click A's message
+ // I get hisory to the front with sometimes multiple messages
+ // selected because it 1) selects right message;
+ // 2) brings history window to front; 3) sends wm_mousemove message
+ // to grid saying that left button is pressed (???) and because
+ // of that shit grid thinks I'm selecting several items. So this
+ // var is used to know whether mouse button was down down on grid
+ // somewhere else
+ WasDownOnGrid: Boolean = False;
+
+procedure THistoryGrid.DoLButtonDown(X, Y: Integer; Keys: TMouseMoveKeys);
+var
+ Item: Integer;
+begin
+ WasDownOnGrid := True;
+ SearchPattern := '';
+ CheckBusy;
+ if Count = 0 then
+ exit;
+
+ DownHitTests := GetHitTests(X, Y);
+
+ // we'll hide/show session headers on button up, don't select item
+ if (ghtButton in DownHitTests) or (ghtLink in DownHitTests) then
+ exit;
+
+ Item := FindItemAt(X, Y);
+
+ if Item <> -1 then
+ begin
+ if (mmkControl in Keys) then
+ begin
+ if IsSelected(Item) then
+ RemoveSelected(Item)
+ else
+ AddSelected(Item);
+ MakeSelected(Item);
+ Invalidate;
+ end
+ else if (Selected <> -1) and (mmkShift in Keys) then
+ begin
+ MakeSelectedTo(Item);
+ MakeSelected(Item);
+ Invalidate;
+ end
+ else
+ Selected := Item;
+ end;
+end;
+
+function THistoryGrid.GetItemRect(Item: Integer): TRect;
+var
+ tmp, idx, SumHeight: Integer;
+ succ: Boolean;
+begin
+ Result := Rect(0, 0, 0, 0);
+ SumHeight := -TopItemOffset;
+ if Item = -1 then
+ exit;
+ if not IsMatched(Item) then
+ exit;
+ if GetIdx(Item) < GetIdx(GetFirstVisible) then
+ begin
+ idx := GetFirstVisible;
+ tmp := GetUp(idx);
+ if tmp <> -1 then
+ idx := tmp;
+ { .TODO: fix here, don't go up, go down from 0 }
+ if Reversed then
+ succ := (idx <= Item)
+ else
+ succ := (idx >= Item);
+ while succ do
+ begin
+ LoadItem(idx);
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetPrev(idx);
+ if idx = -1 then
+ break;
+ if Reversed then
+ succ := (idx <= Item)
+ else
+ succ := (idx >= Item);
+ end;
+ {
+ for i := VertScrollBar.Position-1 downto Item do begin
+ LoadItem(i);
+ Inc(SumHeight,FItems[i].Height);
+ end;
+ }
+ Result := Rect(0, -SumHeight, ClientWidth, -SumHeight + FItems[Item].Height);
+ exit;
+ end;
+
+ idx := GetFirstVisible; // GetIdx(VertScrollBar.Position);
+
+ while GetIdx(idx) < GetIdx(Item) do
+ begin
+ LoadItem(idx);
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetNext(idx);
+ if idx = -1 then
+ break;
+ end;
+
+ Result := Rect(0, SumHeight, ClientWidth, SumHeight + FItems[Item].Height);
+end;
+
+function THistoryGrid.GetItemRTL(Item: Integer): Boolean;
+begin
+ if FItems[Item].RTLMode = hppRTLDefault then
+ begin
+ if RTLMode = hppRTLDefault then
+ Result := Options.RTLEnabled
+ else
+ Result := (RTLMode = hppRTLEnable);
+ end
+ else
+ Result := (FItems[Item].RTLMode = hppRTLEnable)
+end;
+
+function THistoryGrid.IsSelected(Item: Integer): Boolean;
+begin
+ Result := False;
+ if FHideSelection and FGridNotFocused then
+ exit;
+ if Item = -1 then
+ exit;
+ Result := IntSortedArray_Find(TIntArray(FSelItems), Item) <> -1;
+end;
+
+function THistoryGrid.GetSelCount: Integer;
+begin
+ Result := Length(FSelItems);
+end;
+
+procedure THistoryGrid.WMLButtonDown(var Message: TWMLButtonDown);
+begin
+ inherited;
+ if FGridNotFocused then
+ Windows.SetFocus(Handle);
+ DoLButtonDown(Message.XPos, Message.YPos, TranslateKeys(Message.Keys));
+end;
+
+procedure THistoryGrid.WMLButtonUp(var Message: TWMLButtonUp);
+begin
+ inherited;
+ DoLButtonUp(Message.XPos, Message.YPos, TranslateKeys(Message.Keys));
+end;
+
+procedure THistoryGrid.WMMButtonDown(var Message: TWMMButtonDown);
+begin
+ inherited;
+ if FGridNotFocused then
+ Windows.SetFocus(Handle);
+ DoMButtonDown(Message.XPos, Message.YPos, TranslateKeys(Message.Keys));
+end;
+
+procedure THistoryGrid.WMMButtonUp(var Message: TWMMButtonUp);
+begin
+ inherited;
+ DoMButtonUp(Message.XPos, Message.YPos, TranslateKeys(Message.Keys));
+end;
+
+{$IFDEF RENDER_RICH}
+
+procedure THistoryGrid.ApplyItemToRich(Item: Integer; RichEdit: THPPRichEdit = nil; ForceInline: Boolean = False);
+var
+ reItemInline: Boolean;
+ reItemSelected: Boolean;
+ reItemUseFormat: Boolean;
+ reItemUseLinkColor: Boolean;
+ textFont: TFont;
+ textColor, BackColor: TColor;
+ RichItem: PRichItem;
+ RTF, Text: AnsiString;
+ cf, cf2: CharFormat2;
+begin
+ if RichEdit = nil then
+ begin
+ RichItem := FRichCache.RequestItem(Item);
+ FRich := RichItem^.Rich;
+ FRichHeight := RichItem^.Height;
+ exit;
+ end;
+
+ reItemInline := ForceInline or (RichEdit = FRichInline);
+ reItemSelected := (not reItemInline) and IsSelected(Item);
+ reItemUseFormat := not(reItemInline and (not Options.TextFormatting));
+ reItemUseLinkColor := not(Options.ColorLink = clBlue);
+
+ if not reItemInline then
+ FRich := RichEdit;
+
+ Options.GetItemOptions(FItems[Item].MessageType, textFont, BackColor);
+ if reItemSelected then
+ begin
+ textColor := Options.ColorSelectedText;
+ BackColor := Options.ColorSelected;
+ end
+ else
+ begin
+ textColor := textFont.Color;
+ BackColor := BackColor;
+ end;
+
+ // RichEdit.Perform(WM_SETTEXT,0,0);
+ RichEdit.Clear;
+
+ SetRichRTL(GetItemRTL(Item), RichEdit);
+ // for use with WM_COPY
+ RichEdit.Codepage := FItems[Item].Codepage;
+
+ if reItemUseFormat and Options.RawRTFEnabled and IsRTF(FItems[Item].Text) then
+ begin
+ // stored text seems to be RTF
+ RTF := WideToAnsiString(FItems[Item].Text, FItems[Item].Codepage) + #0
+ end
+ else
+ begin
+ RTF := '{\rtf1\ansi\deff0{\fonttbl ';
+ // RTF := Format('{\rtf1\ansi\ansicpg%u\deff0\deflang%u{\fonttbl ',[FItems[Item].Codepage,GetLCIDfromCodepage(CodePage)]);
+ RTF := RTF + Format('{\f0\fnil\fcharset%u %s}', [textFont.CharSet, textFont.Name]);
+ RTF := RTF + '}{\colortbl';
+ RTF := RTF + Format('\red%u\green%u\blue%u;', [textColor and $FF, (textColor shr 8) and $FF,
+ (textColor shr 16) and $FF]);
+ RTF := RTF + Format('\red%u\green%u\blue%u;', [BackColor and $FF, (BackColor shr 8) and $FF,
+ (BackColor shr 16) and $FF]);
+ // add color table for BBCodes
+ if Options.BBCodesEnabled then
+ begin
+ // link color ro [url][/url], [img][/img]
+ RTF := RTF + Format('\red%u\green%u\blue%u;', [Options.ColorLink and $FF,
+ (Options.ColorLink shr 8) and $FF, (Options.ColorLink shr 16) and $FF]);
+ if reItemUseFormat then
+ RTF := RTF + rtf_ctable_text;
+ end;
+ RTF := RTF + '}\li30\ri30\fi0\cf0';
+ if GetItemRTL(Item) then
+ RTF := RTF + '\rtlpar\ltrch\rtlch '
+ else
+ RTF := RTF + '\ltrpar\rtlch\ltrch ';
+ RTF := RTF + AnsiString(Format('\f0\b%d\i%d\ul%d\strike%d\fs%u',
+ [Integer(fsBold in textFont.Style), Integer(fsItalic in textFont.Style),
+ Integer(fsUnderline in textFont.Style), Integer(fsStrikeOut in textFont.Style),
+ Integer(textFont.Size shl 1)]));
+ Text := FormatString2RTF(FItems[Item].Text);
+ { if FGroupLinked and FItems[Item].LinkedToPrev then
+ Text := FormatString2RTF(GetTime(FItems[Item].Time)+': '+FItems[Item].Text) else
+ Text := FormatString2RTF(FItems[Item].Text); }
+ if Options.BBCodesEnabled and reItemUseFormat then
+ Text := DoSupportBBCodesRTF(Text, 3, not reItemSelected);
+ RTF := RTF + Text + '\par }';
+ end;
+
+ SetRichRTF(RichEdit.Handle, RTF, False, False, True);
+
+ (* smart date time in linked item
+ if FGroupLinked and FItems[Item].LinkedToPrev then begin
+ if mtIncoming in FItems[Item].MessageType then
+ textFont := Options.FontIncomingTimestamp
+ else
+ textFont := Options.FontOutgoingTimestamp;
+ if NoDefaultColors then
+ tsColor := textFont.Color
+ else
+ tsColor := Options.ColorSelectedText;
+ RTF := '{\rtf1\ansi\deff0{\fonttbl';
+ RTF := RTF + Format('{\f0\fnil\fcharset%u %s}',[textFont.Charset,textFont.Name]);
+ RTF := RTF + '}{\colortbl';
+ RTF := RTF + Format('\red%u\green%u\blue%u;',[tsColor and $FF,(tsColor shr 8) and $FF,(tsColor shr 16) and $FF]);
+ RTF := RTF + '}';
+ RTF := RTF + Format('\f0\b%d\i%d\ul%d\strike%d\fs%u',
+ [Integer(fsBold in textFont.Style),
+ Integer(fsItalic in textFont.Style),
+ Integer(fsUnderline in textFont.Style),
+ Integer(fsStrikeOut in textFont.Style),
+ Integer(textFont.Size shl 1)]);
+ Text := FormatString2RTF(GetTime(
+ FItems[Item].Time));
+ RTF := RTF + Text + '\par }'+#0;
+ SetRichRTF(RichEdit.Handle,RTF,True,False,True);
+ end;
+ *)
+
+ RichEdit.Perform(EM_SETBKGNDCOLOR, 0, BackColor);
+
+ if reItemUseFormat and Assigned(FOnProcessRichText) then
+ begin
+ try
+ FOnProcessRichText(Self, RichEdit.Handle, Item);
+ except
+ end;
+ if reItemUseLinkColor or reItemSelected or reItemInline then
+ begin
+ ZeroMemory(@cf, SizeOf(cf));
+ cf.cbSize := SizeOf(cf);
+ ZeroMemory(@cf2, SizeOf(cf2));
+ cf2.cbSize := SizeOf(cf2);
+ // do not allow change backcolor of selection
+ if reItemSelected then
+ begin
+ // change CFE_LINK to CFE_REVISED
+ cf.dwMask := CFM_LINK;
+ cf.dwEffects := CFE_LINK;
+ cf2.dwMask := CFM_LINK or CFM_REVISED;
+ cf2.dwEffects := CFE_REVISED;
+ RichEdit.ReplaceCharFormat(cf, cf2);
+ cf.dwMask := CFM_COLOR;
+ cf.crTextColor := textColor;
+ RichEdit.Perform(EM_SETBKGNDCOLOR, 0, BackColor);
+ RichEdit.Perform(EM_SETCHARFORMAT, SCF_ALL, lParam(@cf));
+ end
+ else if reItemInline then
+ begin
+ // change CFE_REVISED to CFE_LINK
+ cf.dwMask := CFM_REVISED;
+ cf.dwEffects := CFE_REVISED;
+ cf2.dwMask := CFM_LINK or CFM_REVISED;
+ cf2.dwEffects := CFM_LINK;
+ RichEdit.ReplaceCharFormat(cf, cf2);
+ end
+ else
+ begin
+ // change CFE_REVISED to CFE_LINK and its color
+ cf.dwMask := CFM_LINK;
+ cf.dwEffects := CFE_LINK;
+ cf2.dwMask := CFM_LINK or CFM_REVISED or CFM_COLOR;
+ cf2.dwEffects := CFE_REVISED;
+ cf2.crTextColor := Options.ColorLink;
+ RichEdit.ReplaceCharFormat(cf, cf2);
+ end;
+ end;
+ end;
+
+{$IFDEF DEBUG}
+ OutputDebugString(PWideChar('Applying item ' + intToStr(Item) + ' to rich'));
+{$ENDIF}
+end;
+{$ENDIF}
+
+procedure THistoryGrid.DoRButtonUp(X, Y: Integer; Keys: TMouseMoveKeys);
+var
+ Item: Integer;
+ ht: TGridHitTests;
+begin
+ SearchPattern := '';
+ CheckBusy;
+
+ Item := FindItemAt(X, Y);
+
+ ht := GetHitTests(X, Y);
+ if (ghtLink in ht) then
+ begin
+ URLClick(Item, GetLinkAtPoint(X, Y), mbRight);
+ exit;
+ end;
+
+ if Selected <> Item then
+ begin
+ if IsSelected(Item) then
+ begin
+ FSelected := Item;
+ MakeVisible(Item);
+ Invalidate;
+ end
+ else
+ begin
+ Selected := Item;
+ end;
+ end;
+
+ if Assigned(FOnPopup) then
+ OnPopup(Self);
+end;
+
+procedure THistoryGrid.DoLButtonUp(X, Y: Integer; Keys: TMouseMoveKeys);
+var
+ Item: Integer;
+ ht: TGridHitTests;
+begin
+ ht := GetHitTests(X, Y) * DownHitTests;
+ DownHitTests := [];
+ WasDownOnGrid := False;
+
+ if ((ghtSessHideButton in ht) or (ghtSessShowButton in ht)) then
+ begin
+ ExpandHeaders := (ghtSessShowButton in ht);
+ exit;
+ end;
+
+ if (ghtBookmark in ht) then
+ begin
+ if Assigned(FOnBookmarkClick) then
+ begin
+ Item := FindItemAt(X, Y);
+ FOnBookmarkClick(Self, Item);
+ end;
+ exit;
+ end;
+
+ if (ghtLink in ht) then
+ begin
+ Item := FindItemAt(X, Y);
+ URLClick(Item, GetLinkAtPoint(X, Y), mbLeft);
+ exit;
+ end;
+
+end;
+
+procedure THistoryGrid.DoMButtonDown(X, Y: Integer; Keys: TMouseMoveKeys);
+begin
+ WasDownOnGrid := True;
+ if Count = 0 then
+ exit;
+ DownHitTests := GetHitTests(X, Y);
+end;
+
+procedure THistoryGrid.DoMButtonUp(X, Y: Integer; Keys: TMouseMoveKeys);
+var
+ Item: Integer;
+ ht: TGridHitTests;
+begin
+ ht := GetHitTests(X, Y) * DownHitTests;
+ DownHitTests := [];
+ WasDownOnGrid := False;
+ if (ghtLink in ht) then
+ begin
+ Item := FindItemAt(X, Y);
+ URLClick(Item, GetLinkAtPoint(X, Y), mbMiddle);
+ exit;
+ end;
+end;
+
+procedure THistoryGrid.WMMouseMove(var Message: TWMMouseMove);
+begin
+ inherited;
+ if Focused then
+ DoMouseMove(Message.XPos, Message.YPos, TranslateKeys(Message.Keys))
+end;
+
+procedure THistoryGrid.DoMouseMove(X, Y: Integer; Keys: TMouseMoveKeys);
+var
+ Item: Integer;
+ SelectMove: Boolean;
+begin
+ CheckBusy;
+ if Count = 0 then
+ exit;
+
+ // do we need to process control here?
+ SelectMove := ((mmkLButton in Keys) and not((mmkControl in Keys) or (mmkShift in Keys))) and
+ (MultiSelect) and (WasDownOnGrid);
+ SelectMove := SelectMove and not((ghtButton in DownHitTests) or (ghtLink in DownHitTests));
+
+ if SelectMove then
+ begin
+ if SelCount = 0 then
+ exit;
+ Item := FindItemAt(X, Y);
+ if Item = -1 then
+ exit;
+ // do not do excessive relisting of items
+ if (not((FSelItems[0] = Item) or (FSelItems[High(FSelItems)] = Item))) or (FSelected <> Item)
+ then
+ begin
+ MakeSelectedTo(Item);
+ MakeSelected(Item);
+ Invalidate;
+ end;
+ end;
+end;
+
+procedure THistoryGrid.WMLButtonDblClick(var Message: TWMLButtonDblClk);
+begin
+ DoLButtonDblClick(Message.XPos, Message.YPos, TranslateKeys(Message.Keys));
+end;
+
+function THistoryGrid.CalcItemHeight(Item: Integer): Integer;
+var
+ hh, h: Integer;
+begin
+ Result := -1;
+ if IsUnknown(Item) then
+ exit;
+
+ ApplyItemToRich(Item);
+ Assert(FRichHeight > 0, 'CalcItemHeight: rich is still <= 0 height');
+ // rude hack, but what the fuck??? First item with rtl chars is 1 line heighted always
+ // probably fixed, see RichCache.ApplyItemToRich
+ if FRichHeight <= 0 then
+ exit
+ else
+ h := FRichHeight;
+
+ if FGroupLinked and FItems[Item].LinkedToPrev then
+ hh := 0
+ else if mtIncoming in FItems[Item].MessageType then
+ hh := CHeaderHeight
+ else
+ hh := PHeaderheight;
+
+ { If you change this, be sure to check out DoMouseMove,
+ DoLButtonDown, DoRButtonDown where I compute offset for
+ clicking & moving over invisible off-screen rich edit
+ control }
+ // compute height =
+ // 1 pix -- border
+ // 2*padding
+ // text height
+ // + HEADER_HEIGHT header
+ Result := 1 + 2 * Padding + h + hh;
+ if (FItems[Item].HasHeader) and (ShowHeaders) then
+ begin
+ if ExpandHeaders then
+ Inc(Result, SessHeaderHeight)
+ else
+ Inc(Result, 0);
+ end;
+end;
+
+procedure THistoryGrid.SetFilter(const Value: TMessageTypes);
+begin
+{$IFDEF DEBUG}
+ OutputDebugString('Filter');
+{$ENDIF}
+ if (Filter = Value) or (Value = []) or (Value = [mtUnknown]) then
+ exit;
+ FFilter := Value;
+ GridUpdate([guFilter]);
+ if Assigned(FOnFilterChange) then
+ FOnFilterChange(Self);
+ { CheckBusy;
+ SetLength(FSelItems,0);
+ FSelected := 0;
+ FFilter := Value;
+ ShowProgress := True;
+ State := gsLoad;
+ try
+ VertScrollBar.Range := Count-1+ClientHeight;
+ if Reversed then
+ Selected := GetPrev(-1)
+ else
+ Selected := GetNext(-1);
+ BarAdjusted := False;
+ AdjustScrollBar;
+ finally
+ State := gsIdle;
+ end;
+ Repaint; }
+end;
+
+procedure THistoryGrid.DrawMessage(Text: String);
+var
+ cr, r: TRect;
+begin
+ // Canvas.Font := Screen.MenuFont;
+ // Canvas.Brush.Color := clWindow;
+ // Canvas.Font.Color := clWindowText;
+ Canvas.Font := Options.FontMessage;
+ Canvas.Brush.Color := Options.ColorBackground;
+ r := ClientRect;
+ cr := ClientRect;
+ Canvas.FillRect(r);
+ // make multiline support
+ // DrawText(Canvas.Handle,PAnsiChar(Text),Length(Text),
+ // r,DT_CENTER or DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE);
+ DrawTextW(Canvas.Handle, PWideChar(Text), Length(Text), r, DT_NOPREFIX or DT_CENTER or
+ DT_CALCRECT);
+ OffsetRect(r, ((cr.Right - cr.Left) - (r.Right - r.Left)) div 2,
+ ((cr.Bottom - cr.Top) - (r.Bottom - r.Top)) div 2);
+ DrawTextW(Canvas.Handle, PWideChar(Text), Length(Text), r, DT_NOPREFIX or DT_CENTER);
+end;
+
+procedure THistoryGrid.WMKeyDown(var Message: TWMKeyDown);
+begin
+ DoKeyDown(Message.CharCode, KeyDataToShiftState(Message.KeyData));
+ inherited;
+end;
+
+procedure THistoryGrid.WMKeyUp(var Message: TWMKeyUp);
+begin
+ DoKeyUp(Message.CharCode, KeyDataToShiftState(Message.KeyData));
+ inherited;
+end;
+
+procedure THistoryGrid.WMSysKeyUp(var Message: TWMSysKeyUp);
+begin
+ DoKeyUp(Message.CharCode, KeyDataToShiftState(Message.KeyData));
+ inherited;
+end;
+
+procedure THistoryGrid.DoKeyDown(var Key: Word; ShiftState: TShiftState);
+var
+ NextItem, Item: Integer;
+ r: TRect;
+begin
+ if Count = 0 then
+ exit;
+ if ssAlt in ShiftState then
+ exit;
+ CheckBusy;
+
+ Item := Selected;
+ if Item = -1 then
+ begin
+ if Reversed then
+ Item := GetPrev(-1)
+ else
+ Item := GetNext(-1);
+ end;
+
+ if (Key = VK_HOME) or ((ssCtrl in ShiftState) and (Key = VK_PRIOR)) then
+ begin
+ SearchPattern := '';
+ NextItem := GetNext(GetIdx(-1));
+ if (not(ssShift in ShiftState)) or (not MultiSelect) then
+ begin
+ Selected := NextItem;
+ end
+ else if NextItem <> -1 then
+ begin
+ MakeSelectedTo(NextItem);
+ MakeSelected(NextItem);
+ Invalidate;
+ end;
+ AdjustScrollBar;
+ Key := 0;
+ end
+ else if (Key = VK_END) or ((ssCtrl in ShiftState) and (Key = VK_NEXT)) then
+ begin
+ SearchPattern := '';
+ NextItem := GetPrev(GetIdx(Count));
+ if (not(ssShift in ShiftState)) or (not MultiSelect) then
+ begin
+ Selected := NextItem;
+ end
+ else if NextItem <> -1 then
+ begin
+ MakeSelectedTo(NextItem);
+ MakeSelected(NextItem);
+ Invalidate;
+ end;
+ AdjustScrollBar;
+ Key := 0;
+ end
+ else if Key = VK_NEXT then
+ begin // PAGE DOWN
+ SearchPattern := '';
+ NextItem := Item;
+ r := GetItemRect(NextItem);
+ NextItem := FindItemAt(0, r.Top + ClientHeight);
+ if NextItem = Item then
+ begin
+ NextItem := GetNext(NextItem);
+ if NextItem = -1 then
+ NextItem := Item;
+ end
+ else if NextItem = -1 then
+ begin
+ NextItem := GetPrev(GetIdx(Count));
+ if NextItem = -1 then
+ NextItem := Item;
+ end;
+ if (not(ssShift in ShiftState)) or (not MultiSelect) then
+ begin
+ Selected := NextItem;
+ end
+ else if NextItem <> -1 then
+ begin
+ MakeSelectedTo(NextItem);
+ MakeSelected(NextItem);
+ Invalidate;
+ end;
+ AdjustScrollBar;
+ Key := 0;
+ end
+ else if Key = VK_PRIOR then
+ begin // PAGE UP
+ SearchPattern := '';
+ NextItem := Item;
+ r := GetItemRect(NextItem);
+ NextItem := FindItemAt(0, r.Top - ClientHeight);
+ if NextItem <> -1 then
+ begin
+ if FItems[NextItem].Height < ClientHeight then
+ NextItem := GetNext(NextItem);
+ end
+ else
+ NextItem := GetNext(NextItem);
+ if NextItem = -1 then
+ begin
+ if IsMatched(GetIdx(0)) then
+ NextItem := GetIdx(0)
+ else
+ NextItem := GetNext(GetIdx(0));
+ end;
+ if (not(ssShift in ShiftState)) or (not MultiSelect) then
+ begin
+ Selected := NextItem;
+ end
+ else if NextItem <> -1 then
+ begin
+ MakeSelectedTo(NextItem);
+ MakeSelected(NextItem);
+ Invalidate;
+ end;
+ AdjustScrollBar;
+ Key := 0;
+ end
+ else if Key = VK_UP then
+ begin
+ if ssCtrl in ShiftState then
+ ScrollGridBy(-VLineScrollSize)
+ else
+ begin
+ SearchPattern := '';
+ if GetIdx(Item) > 0 then
+ Item := GetPrev(Item);
+ if Item = -1 then
+ exit;
+ if (ssShift in ShiftState) and (MultiSelect) then
+ begin
+ MakeSelectedTo(Item);
+ MakeSelected(Item);
+ Invalidate;
+ end
+ else
+ Selected := Item;
+ AdjustScrollBar;
+ end;
+ Key := 0;
+ end
+ else if Key = VK_DOWN then
+ begin
+ if ssCtrl in ShiftState then
+ ScrollGridBy(VLineScrollSize)
+ else
+ begin
+ SearchPattern := '';
+ if GetIdx(Item) < Count - 1 then
+ Item := GetNext(Item);
+ if Item = -1 then
+ exit;
+ if (ssShift in ShiftState) and (MultiSelect) then
+ begin
+ MakeSelectedTo(Item);
+ MakeSelected(Item);
+ Invalidate;
+ end
+ else
+ Selected := Item;
+ AdjustScrollBar;
+ end;
+ Key := 0;
+ end;
+
+end;
+
+procedure THistoryGrid.DoKeyUp(var Key: Word; ShiftState: TShiftState);
+begin
+ if Count = 0 then
+ exit;
+ if (ssAlt in ShiftState) or (ssCtrl in ShiftState) then
+ exit;
+ if (Key = VK_APPS) or ((Key = VK_F10) and (ssShift in ShiftState)) then
+ begin
+ CheckBusy;
+ if Selected = -1 then
+ begin
+ if Reversed then
+ Selected := GetPrev(-1)
+ else
+ Selected := GetNext(-1);
+ end;
+ if Assigned(FOnPopup) then
+ OnPopup(Self);
+ Key := 0;
+ end;
+end;
+
+procedure THistoryGrid.WMGetDlgCode(var Message: TWMGetDlgCode);
+type
+ PWMMsgKey = ^TWMMsgKey;
+
+ TWMMsgKey = packed record
+ hwnd: hwnd;
+ msg: Cardinal;
+ CharCode: Word;
+ Unused: Word;
+ KeyData: Longint;
+ Result: Longint;
+ end;
+
+begin
+ inherited;
+ Message.Result := DLGC_WANTALLKEYS;
+ if (TMessage(Message).lParam <> 0) then
+ begin
+ with PWMMsgKey(TMessage(Message).lParam)^ do
+ begin
+ if (msg = WM_KEYDOWN) or (msg = WM_CHAR) or (msg = WM_SYSCHAR) then
+ case CharCode of
+ VK_TAB:
+ Message.Result := DLGC_WANTARROWS;
+ end;
+ end;
+ end;
+ Message.Result := Message.Result or DLGC_HASSETSEL;
+end;
+
+function THistoryGrid.GetSelectionString: String;
+begin
+ if FSelectionStored then
+ begin
+ Result := FSelectionString;
+ exit;
+ end
+ else
+ Result := '';
+ if csDestroying in ComponentState then
+ exit;
+ if Count = 0 then
+ exit;
+ if State = gsInline then
+ Result := GetRichString(FRichInline.Handle, True)
+ else if Selected <> -1 then
+ begin
+ FSelectionString := FormatSelected(Options.SelectionFormat);
+ FSelectionStored := True;
+ Result := FSelectionString;
+ end;
+end;
+
+procedure THistoryGrid.EMGetSel(var Message: TMessage);
+var
+ M: TWMGetTextLength;
+begin
+ WMGetTextLength(M);
+ Puint_ptr(Message.wParam)^ := 0;
+ Puint_ptr(Message.lParam)^ := M.Result;
+end;
+
+procedure THistoryGrid.EMExGetSel(var Message: TMessage);
+var
+ M: TWMGetTextLength;
+begin
+ Message.wParam := 0;
+ if Message.lParam = 0 then
+ exit;
+ WMGetTextLength(M);
+ TCharRange(Pointer(Message.lParam)^).cpMin := 0;
+ TCharRange(Pointer(Message.lParam)^).cpMax := M.Result;
+end;
+
+procedure THistoryGrid.EMSetSel(var Message: TMessage);
+begin
+ FSelectionStored := False;
+ if csDestroying in ComponentState then
+ exit;
+ if Assigned(FOnSelectRequest) then
+ FOnSelectRequest(Self);
+end;
+
+procedure THistoryGrid.EMExSetSel(var Message: TMessage);
+begin
+ FSelectionStored := False;
+ if csDestroying in ComponentState then
+ exit;
+ if Assigned(FOnSelectRequest) then
+ FOnSelectRequest(Self);
+end;
+
+procedure THistoryGrid.WMGetText(var Message: TWMGetText);
+var
+ len: Integer;
+ str: String;
+begin
+ str := SelectionString;
+ len := Min(Message.TextMax - 1, Length(str));
+ if len >= 0 then { W }
+ StrLCopy(PChar(Message.Text), PChar(str), len);
+ Message.Result := len;
+end;
+
+procedure THistoryGrid.WMGetTextLength(var Message: TWMGetTextLength);
+var
+ str: String;
+begin
+ str := SelectionString;
+ Message.Result := Length(str);
+end;
+
+procedure THistoryGrid.WMSetText(var Message: TWMSetText);
+begin
+ // inherited;
+ FSelectionStored := False;
+end;
+
+procedure THistoryGrid.MakeRangeSelected(FromItem, ToItem: Integer);
+var
+ i: Integer;
+ StartItem, EndItem: Integer;
+ len: Integer;
+ Changed: TIntArray;
+begin
+ // detect start and end
+ if FromItem <= ToItem then
+ begin
+ StartItem := FromItem;
+ EndItem := ToItem;
+ end
+ else
+ begin
+ StartItem := ToItem;
+ EndItem := FromItem;
+ end;
+
+ // fill selected items list
+ len := 0;
+ for i := StartItem to EndItem do
+ begin
+ if IsUnknown(i) then
+ LoadItem(i, False);
+ if not IsMatched(i) then
+ continue;
+ Inc(len);
+ SetLength(TempSelItems, len);
+ TempSelItems[len - 1] := i;
+ end;
+
+ // determine and update changed items
+ Changed := IntSortedArray_NonIntersect(TIntArray(FSelItems), TIntArray(TempSelItems));
+ FRichCache.ResetItems(Changed);
+
+ // set selection
+ FSelItems := TempSelItems;
+end;
+
+procedure THistoryGrid.SelectRange(FromItem, ToItem: Integer);
+begin
+ if (FromItem = -1) or (ToItem = -1) then
+ exit;
+ MakeRangeSelected(FromItem, ToItem);
+ if SelCount = 0 then
+ MakeSelected(-1)
+ else
+ MakeSelected(FSelItems[0]);
+ Invalidate;
+end;
+
+procedure THistoryGrid.SelectAll;
+begin
+ if Count = 0 then
+ exit;
+ MakeRangeSelected(0, Count - 1);
+ if SelCount = 0 then
+ MakeSelected(-1)
+ else
+ MakeSelected(FSelected);
+ Invalidate;
+end;
+
+procedure THistoryGrid.MakeSelectedTo(Item: Integer);
+var
+ first: Integer;
+begin
+ if (FSelected = -1) or (Item = -1) then
+ exit;
+ if FSelItems[0] = FSelected then
+ first := FSelItems[High(FSelItems)]
+ else if FSelItems[High(FSelItems)] = FSelected then
+ first := FSelItems[0]
+ else
+ first := FSelected;
+ MakeRangeSelected(first, Item);
+end;
+
+procedure THistoryGrid.MakeTopmost(Item: Integer);
+begin
+ if (Item < 0) or (Item >= Count) then
+ exit;
+ SetSBPos(GetIdx(Item));
+end;
+
+procedure THistoryGrid.MakeVisible(Item: Integer);
+var
+ first: Integer;
+ SumHeight: Integer;
+ BottomAlign: Boolean;
+begin
+ BottomAlign := ShowBottomAligned and Reversed;
+ ShowBottomAligned := False;
+ if Item = -1 then
+ exit;
+ // load it to make positioning correct
+ LoadItem(Item, True);
+ if not IsMatched(Item) then
+ exit;
+ first := GetFirstVisible;
+ if Item = first then
+ begin
+ if FItems[Item].Height > ClientHeight then
+ begin
+ if BottomAlign or (TopItemOffset > FItems[Item].Height - ClientHeight) then
+ begin
+ TopItemOffset := FItems[Item].Height - ClientHeight;
+ end;
+ ScrollGridBy(0, False);
+ end
+ else
+ ScrollGridBy(-TopItemOffset, False);
+ end
+ else if GetIdx(Item) < GetIdx(first) then
+ SetSBPos(GetIdx(Item))
+ else
+ begin
+ // if IsVisible(Item) then exit;
+ if IsVisible(Item, False) then
+ exit;
+ SumHeight := 0;
+ first := Item;
+ while (Item >= 0) and (Item < Count) do
+ begin
+ LoadItem(Item, True);
+ if (SumHeight + FItems[Item].Height) >= ClientHeight then
+ break;
+ Inc(SumHeight, FItems[Item].Height);
+ Item := GetUp(Item);
+ end;
+ if GetIdx(Item) >= MaxSBPos then
+ begin
+ SetSBPos(GetIdx(Item) + 1);
+ // strange, but if last message is bigger then client,
+ // it always scrolls to down, but grid thinks, that it's
+ // aligned to top (when entering inline mode, for ex.)
+ if Item = first then
+ TopItemOffset := 0;
+ end
+ else
+ begin
+ SetSBPos(GetIdx(Item));
+ if Item <> first then
+ TopItemOffset := (SumHeight + FItems[Item].Height) - ClientHeight;
+ end;
+ end;
+end;
+
+procedure THistoryGrid.DoRButtonDown(X, Y: Integer; Keys: TMouseMoveKeys);
+begin;
+end;
+
+procedure THistoryGrid.WMRButtonDown(var Message: TWMRButtonDown);
+begin
+ inherited;
+ if FGridNotFocused then
+ Windows.SetFocus(Handle);
+ DoRButtonDown(Message.XPos, Message.YPos, TranslateKeys(Message.Keys));
+end;
+
+procedure THistoryGrid.WMRButtonUp(var Message: TWMRButtonDown);
+begin
+ inherited;
+ DoRButtonUp(Message.XPos, Message.YPos, TranslateKeys(Message.Keys));
+end;
+
+procedure THistoryGrid.BeginUpdate;
+begin
+ Inc(LockCount);
+end;
+
+procedure THistoryGrid.EndUpdate;
+begin
+ if LockCount > 0 then
+ Dec(LockCount);
+ if LockCount > 0 then
+ exit;
+ try
+ if guSize in GridUpdates then
+ GridUpdateSize;
+ if guOptions in GridUpdates then
+ DoOptionsChanged;
+ if guFilter in GridUpdates then
+ UpdateFilter;
+ finally
+ GridUpdates := [];
+ end;
+end;
+
+procedure THistoryGrid.GridUpdate(Updates: TGridUpdates);
+begin
+ BeginUpdate;
+ GridUpdates := GridUpdates + Updates;
+ EndUpdate;
+end;
+
+function THistoryGrid.GetTime(Time: DWord): String;
+begin
+ if Assigned(FTranslateTime) then
+ OnTranslateTime(Self, Time, Result)
+ else
+ Result := '';
+end;
+
+function THistoryGrid.GetTopItem: Integer;
+begin
+ if Reversed then
+ Result := GetDown(Count)
+ else
+ Result := GetDown(-1);
+end;
+
+function THistoryGrid.GetUp(Item: Integer): Integer;
+begin
+ Result := GetPrev(Item, False);
+end;
+
+procedure THistoryGrid.GridUpdateSize;
+var
+ w, h: Integer;
+ NewClient: TBitmap;
+ i: Integer;
+ WidthWasUpdated: Boolean;
+begin
+ if State = gsInline then
+ CancelInline;
+
+ w := ClientWidth;
+ h := ClientHeight;
+ WidthWasUpdated := (FClient.Width <> w);
+
+ // avatars!.!
+ // FRichCache.Width := ClientWidth - 3*FPadding - 64;
+ FRichCache.Width := ClientWidth - 2 * FPadding;
+
+ if (w <> 0) and (h <> 0) then
+ begin
+ NewClient := TBitmap.Create;
+ NewClient.Width := w;
+ NewClient.Height := h;
+ NewClient.Canvas.Font.Assign(Canvas.Font);
+ NewClient.Canvas.TextFlags := Canvas.TextFlags;
+
+ FClient.Free;
+ FClient := NewClient;
+ FCanvas := FClient.Canvas;
+ end;
+
+ IsCanvasClean := False;
+
+ if WidthWasUpdated then
+ for i := 0 to Count - 1 do
+ FItems[i].Height := -1;
+
+ BarAdjusted := False;
+ if Allocated then
+ AdjustScrollBar;
+end;
+
+function THistoryGrid.GetDown(Item: Integer): Integer;
+begin
+ Result := GetNext(Item, False);
+end;
+
+function THistoryGrid.GetItems(Index: Integer): THistoryItem;
+begin
+ if (Index < 0) or (Index > High(FItems)) then
+ exit;
+ if IsUnknown(Index) then
+ LoadItem(Index, False);
+ Result := FItems[Index];
+end;
+
+// Call this function to get the link url at given point in grid
+// Call it when you are sure that the point has a link,
+// if no link at a point, the result is ''
+// To know if there's a link, use GetHitTests and look for ghtLink
+function THistoryGrid.GetLinkAtPoint(X, Y: Integer): String;
+var
+ P: TPoint;
+ cr: CHARRANGE;
+ cf: CharFormat2;
+ res: DWord;
+ RichEditRect: TRect;
+ cp, Max, Item: Integer;
+begin
+ Result := '';
+ Item := FindItemAt(X, Y);
+ if Item = -1 then
+ exit;
+ RichEditRect := GetRichEditRect(Item, True);
+
+ P := Point(X - RichEditRect.Left, Y - RichEditRect.Top);
+ ApplyItemToRich(Item);
+
+ cp := FRich.Perform(EM_CHARFROMPOS, 0, lParam(@P));
+ if cp = -1 then
+ exit; // out of richedit area
+ cr.cpMin := cp;
+ cr.cpMax := cp + 1;
+ FRich.Perform(EM_EXSETSEL, 0, lParam(@cr));
+
+ ZeroMemory(@cf, SizeOf(cf));
+ cf.cbSize := SizeOf(cf);
+ cf.dwMask := CFM_LINK or CFM_REVISED;
+ res := FRich.Perform(EM_GETCHARFORMAT, SCF_SELECTION, lParam(@cf));
+ // no link under point
+ if (((res and CFM_LINK) = 0) or ((cf.dwEffects and CFE_LINK) = 0)) and
+ (((res and CFM_REVISED) = 0) or ((cf.dwEffects and CFE_REVISED) = 0)) then
+ exit;
+
+ while cr.cpMin > 0 do
+ begin
+ Dec(cr.cpMin);
+ FRich.Perform(EM_EXSETSEL, 0, lParam(@cr));
+ cf.cbSize := SizeOf(cf);
+ cf.dwMask := CFM_LINK or CFM_REVISED;
+ res := FRich.Perform(EM_GETCHARFORMAT, SCF_SELECTION, lParam(@cf));
+ if (((res and CFM_LINK) = 0) or ((cf.dwEffects and CFE_LINK) = 0)) and
+ (((res and CFM_REVISED) = 0) or ((cf.dwEffects and CFE_REVISED) = 0)) then
+ begin
+ Inc(cr.cpMin);
+ break;
+ end;
+ end;
+
+ Max := FRich.GetTextLength;
+ while cr.cpMax < Max do
+ begin
+ Inc(cr.cpMax);
+ FRich.Perform(EM_EXSETSEL, 0, lParam(@cr));
+ cf.cbSize := SizeOf(cf);
+ cf.dwMask := CFM_LINK or CFM_REVISED;
+ res := FRich.Perform(EM_GETCHARFORMAT, SCF_SELECTION, lParam(@cf));
+ if (((res and CFM_LINK) = 0) or ((cf.dwEffects and CFE_LINK) = 0)) and
+ (((res and CFM_REVISED) = 0) or ((cf.dwEffects and CFE_REVISED) = 0)) then
+ begin
+ Dec(cr.cpMax);
+ break;
+ end;
+ end;
+
+ Result := FRich.GetTextRange(cr.cpMin, cr.cpMax);
+
+ if (Length(Result) > 10) and (Pos('HYPERLINK', Result) = 1) then
+ begin
+ cr.cpMin := PosEx('"', Result, 10);
+ if cr.cpMin > 0 then
+ Inc(cr.cpMin)
+ else
+ exit;
+ cr.cpMax := PosEx('"', Result, cr.cpMin);
+ if cr.cpMin = 0 then
+ exit;
+ Result := Copy(Result, cr.cpMin, cr.cpMax - cr.cpMin);
+ end;
+
+end;
+
+function THistoryGrid.GetHintAtPoint(X, Y: Integer; var ObjectHint: WideString; var ObjectRect: TRect): Boolean;
+var
+ P: TPoint;
+ RichEditRect: TRect;
+ cp, Item: Integer;
+ textDoc: ITextDocument;
+ textRange: ITextRange;
+ iObject: IUnknown;
+ iTooltipCtrl: ITooltipData;
+ Size: TPoint;
+
+begin
+ ObjectHint := '';
+ Result := False;
+
+ Item := FindItemAt(X, Y);
+ if Item = -1 then
+ exit;
+ RichEditRect := GetRichEditRect(Item, True);
+ P := Point(X - RichEditRect.Left, Y - RichEditRect.Top);
+ ApplyItemToRich(Item);
+
+ if FRich.Version < 30 then
+ exit; // TOM is supported from RE 3.0
+ if not Assigned(FRich.RichEditOle) then
+ exit;
+
+ repeat
+ if FRich.RichEditOle.QueryInterface(IID_ITextDocument, textDoc) <> S_OK then
+ break;
+ P := FRich.ClientToScreen(P);
+ textRange := textDoc.RangeFromPoint(P.X, P.Y);
+ if not Assigned(textRange) then
+ break;
+ iObject := textRange.GetEmbeddedObject;
+ if not Assigned(iObject) then
+ begin
+ cp := textRange.Start;
+ textRange.Start := cp - 1;
+ textRange.End_ := cp;
+ iObject := textRange.GetEmbeddedObject;
+ end;
+ if not Assigned(iObject) then
+ break;
+
+ if iObject.QueryInterface(IID_ITooltipData, iTooltipCtrl) = S_OK then
+ OleCheck(iTooltipCtrl.GetTooltip(ObjectHint))
+ else if Supports(iObject, IID_IGifSmileyCtrl) then ObjectHint := TranslateW('Running version of AniSmiley is not supported')
+ else if Supports(iObject, IID_ISmileyAddSmiley) then ObjectHint := TranslateW('Running version of SmileyAdd is not supported')
+ else if Supports(iObject, IID_IEmoticonsImage) then ObjectHint := TranslateW('Running version of Emoticons is not supported')
+ else
+ break;
+ if ObjectHint = '' then
+ break;
+
+ textRange.GetPoint(tomStart + TA_TOP + TA_LEFT, Size.X, Size.Y);
+ Size := FRich.ScreenToClient(Size);
+ ObjectRect.TopLeft := Size;
+
+ textRange.GetPoint(tomStart + TA_BOTTOM + TA_RIGHT, Size.X, Size.Y);
+ Size := FRich.ScreenToClient(Size);
+ ObjectRect.BottomRight := Size;
+
+ OffsetRect(ObjectRect, RichEditRect.Left, RichEditRect.Top);
+ InflateRect(ObjectRect, 1, 1);
+
+ Result := PtInRect(ObjectRect, Point(X, Y));
+ until True;
+
+ if not Result then
+ ObjectHint := '';
+
+ ReleaseObject(iTooltipCtrl);
+ ReleaseObject(iObject);
+ ReleaseObject(textRange);
+ ReleaseObject(textDoc);
+end;
+
+const
+ Substs: array [0 .. 3] of array [0 .. 1] of String = (('\n', #13#10),
+ ('\t', #9), ('\\', '\'), ('\%', '%'));
+
+procedure THistoryGrid.IntFormatItem(Item: Integer; var Tokens: TWideStrArray;
+ var SpecialTokens: TIntArray);
+var
+ i, n: Integer;
+ tok: TWideStrArray;
+ toksp: TIntArray;
+ subst: String;
+ from_nick, to_nick, nick: String;
+ dt: TDateTime;
+ Mes, selmes: String;
+begin
+ // item MUST be loaded before calling IntFormatItem!
+
+ tok := Tokens;
+ toksp := SpecialTokens;
+
+ for i := 0 to Length(toksp) - 1 do
+ begin
+ subst := '';
+ if tok[toksp[i]][1] = '\' then
+ begin
+ for n := 0 to Length(Substs) - 1 do
+ if tok[toksp[i]] = Substs[n][0] then
+ begin
+ subst := Substs[n][1];
+ break;
+ end;
+ end
+ else
+ begin
+ Mes := FItems[Item].Text;
+ if Options.RawRTFEnabled and IsRTF(Mes) then
+ begin
+ ApplyItemToRich(Item);
+ Mes := GetRichString(FRich.Handle, False);
+ end;
+ if State = gsInline then
+ selmes := GetRichString(FRichInline.Handle, True)
+ else
+ selmes := Mes;
+ if mtIncoming in FItems[Item].MessageType then
+ begin
+ from_nick := ContactName;
+ to_nick := ProfileName;
+ end
+ else
+ begin
+ from_nick := ProfileName;
+ to_nick := ContactName;
+ end;
+ nick := from_nick;
+ if Assigned(FGetNameData) then
+ FGetNameData(Self, Item, nick);
+ dt := TimestampToDateTime(FItems[Item].Time);
+ // we are doing many if's here, because I don't want to pre-compose all the
+ // possible tokens into array. That's because some tokens take some time to
+ // be generated, and if they're not used, this time would be wasted.
+ if tok[toksp[i]] = '%mes%' then
+ subst := Mes
+ else if tok[toksp[i]] = '%adj_mes%' then
+ subst := WrapText(Mes, #13#10, [' ', #9, '-'], 72)
+ else if tok[toksp[i]] = '%quot_mes%' then
+ begin
+ subst := WideStringReplace('» ' + Mes, #13#10, #13#10 + '» ', [rfReplaceAll]);
+ subst := WrapText(subst, #13#10 + '» ', [' ', #9, '-'], 70)
+ end
+ else if tok[toksp[i]] = '%selmes%' then
+ subst := selmes
+ else if tok[toksp[i]] = '%adj_selmes%' then
+ subst := WrapText(selmes, #13#10, [' ', #9, '-'], 72)
+ else if tok[toksp[i]] = '%quot_selmes%' then
+ begin
+ subst := WideStringReplace('» ' + selmes, #13#10, #13#10 + '» ', [rfReplaceAll]);
+ subst := WrapText(subst, #13#10 + '» ', [' ', #9, '-'], 70)
+ end
+ else if tok[toksp[i]] = '%nick%' then
+ subst := nick
+ else if tok[toksp[i]] = '%from_nick%' then
+ subst := from_nick
+ else if tok[toksp[i]] = '%to_nick%' then
+ subst := to_nick
+ else if tok[toksp[i]] = '%datetime%' then
+ subst := DateTimeToStr(dt)
+ else if tok[toksp[i]] = '%smart_datetime%' then
+ subst := DateTimeToStr(dt)
+ else if tok[toksp[i]] = '%date%' then
+ subst := DateToStr(dt)
+ else if tok[toksp[i]] = '%time%' then
+ subst := TimeToStr(dt);
+ end;
+ tok[toksp[i]] := subst;
+ end;
+end;
+
+function THistoryGrid.IsMatched(Index: Integer): Boolean;
+var
+ mts: TMessageTypes;
+begin
+ mts := FItems[Index].MessageType;
+ Result := ((MessageTypesToDWord(FFilter) and MessageTypesToDWord(mts)) >=
+ MessageTypesToDWord(mts));
+ if Assigned(FOnItemFilter) then
+ FOnItemFilter(Self, Index, Result);
+end;
+
+function THistoryGrid.IsUnknown(Index: Integer): Boolean;
+begin
+ Result := (mtUnknown in FItems[Index].MessageType);
+end;
+
+function THistoryGrid.GetItemInline: Integer;
+begin
+ if State = gsInline then
+ Result := FItemInline
+ else
+ Result := -1;
+end;
+
+procedure THistoryGrid.AdjustInlineRichedit;
+var
+ r: TRect;
+begin
+ if (ItemInline = -1) or (ItemInline > Count) then
+ exit;
+ r := GetRichEditRect(ItemInline);
+ if IsRectEmpty(r) then
+ exit;
+ // variant 1: move richedit around
+ // variant 2: adjust TopItemOffset
+ // variant 3: made logic changes in adjust toolbar to respect TopItemOffset
+ // FRichInline.Top := r.top;
+ Inc(TopItemOffset, r.Top - FRichInline.Top);
+end;
+
+procedure THistoryGrid.AdjustScrollBar;
+var
+ maxidx, SumHeight, ind, idx: Integer;
+ R1, R2: TRect;
+begin
+ if BarAdjusted then
+ exit;
+ MaxSBPos := -1;
+ if Count = 0 then
+ begin
+ VertScrollBar.Range := 0;
+ exit;
+ end;
+ SumHeight := 0;
+ idx := GetFirstVisible;
+
+ if idx >= 0 then
+ repeat
+ LoadItem(idx);
+ if IsMatched(idx) then
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetDown(idx);
+ until ((SumHeight > ClientHeight) or (idx < 0) or (idx >= Length(FItems)));
+
+ maxidx := idx;
+ // see if the idx is the last
+ if maxidx <> -1 then
+ if GetDown(maxidx) = -1 then
+ maxidx := -1;
+
+ // if we are at the end, look up to find first visible
+ if (maxidx = -1) and (SumHeight > 0) then
+ begin
+ SumHeight := 0;
+ maxidx := GetIdx(Length(FItems));
+ // idx := 0;
+ repeat
+ idx := GetUp(maxidx);
+ if idx = -1 then
+ break;
+ maxidx := idx;
+ LoadItem(maxidx, True);
+ if IsMatched(maxidx) then
+ Inc(SumHeight, FItems[maxidx].Height);
+ until ((SumHeight >= ClientHeight) or (maxidx < 0) or (maxidx >= Length(FItems)));
+ BarAdjusted := True;
+ VertScrollBar.Visible := (idx <> -1);
+{$IFDEF PAGE_SIZE}
+ VertScrollBar.Range := GetIdx(maxidx) + VertScrollBar.PageSize - 1 + 1;
+{$ELSE}
+ VertScrollBar.Range := GetIdx(maxidx) + ClientHeight + 1;
+{$ENDIF}
+ MaxSBPos := GetIdx(maxidx);
+ // if VertScrollBar.Position > MaxSBPos then
+ SetSBPos(VertScrollBar.Position);
+ AdjustInlineRichedit;
+ exit;
+ end;
+
+ if SumHeight = 0 then
+ begin
+ VertScrollBar.Range := 0;
+ exit;
+ end;
+
+ VertScrollBar.Visible := True;
+{$IFDEF PAGE_SIZE}
+ VertScrollBar.Range := Count + VertScrollBar.PageSize - 1;
+{$ELSE}
+ VertScrollBar.Range := Count + ClientHeight;
+{$ENDIF}
+ MaxSBPos := Count - 1;
+ exit;
+
+ if SumHeight < ClientHeight then
+ begin
+ idx := GetPrev(GetIdx(Count));
+ if idx = -1 then
+ Assert(False);
+ R1 := GetItemRect(idx);
+ idx := FindItemAt(0, R1.Bottom - ClientHeight);
+ if idx = -1 then
+ begin
+ idx := GetIdx(0);
+ end
+ else
+ begin
+ ind := idx;
+ R2 := GetItemRect(idx);
+ if R1.Bottom - R2.Top > ClientHeight then
+ begin
+ idx := GetNext(idx);
+ if idx = -1 then
+ idx := ind;
+ end;
+ end;
+ BarAdjusted := True;
+{$IFDEF PAGE_SIZE}
+ VertScrollBar.Range := GetIdx(idx) + VertScrollBar.PageSize - 1;
+{$ELSE}
+ VertScrollBar.Range := GetIdx(idx) + ClientHeight;
+{$ENDIF}
+ MaxSBPos := GetIdx(idx) - 1;
+ SetSBPos(VertScrollBar.Range);
+ end
+ else
+ begin
+{$IFDEF PAGE_SIZE}
+ VertScrollBar.Range := Count + VertScrollBar.PageSize - 1;
+{$ELSE}
+ VertScrollBar.Range := Count + ClientHeight;
+{$ENDIF}
+ MaxSBPos := Count - 1;
+ end;
+end;
+
+procedure THistoryGrid.CreateWindowHandle(const Params: TCreateParams);
+begin
+ // CreateUnicodeHandle(Self, Params, '');
+ inherited;
+end;
+
+procedure THistoryGrid.CreateParams(var Params: TCreateParams);
+const
+ BorderStyles: array [TBorderStyle] of DWord = (0, WS_BORDER);
+ ReadOnlys: array [Boolean] of DWord = (0, ES_READONLY);
+begin
+ inherited CreateParams(Params);
+ with Params do
+ begin
+ Style := dword(Style) or BorderStyles[FBorderStyle] or ReadOnlys[True];
+ if NewStyleControls and Ctl3D and (FBorderStyle = bsSingle) then
+ begin
+ Style := Style and not WS_BORDER;
+ ExStyle := ExStyle or WS_EX_CLIENTEDGE;
+ end;
+ with WindowClass do
+ // style := style or CS_HREDRAW or CS_VREDRAW or CS_BYTEALIGNCLIENT or CS_BYTEALIGNWINDOW;
+ Style := Style or CS_HREDRAW or CS_VREDRAW;
+ end;
+end;
+
+function THistoryGrid.GetNext(Item: Integer; Force: Boolean = False): Integer;
+var
+ Max: Integer;
+ WasLoaded: Boolean;
+begin
+ Result := -1;
+ { REV }
+ if not Force then
+ if Reversed then
+ begin
+ Result := GetPrev(Item, True);
+ exit;
+ end;
+ Inc(Item);
+ Max := Count - 1;
+ WasLoaded := False;
+ { AF 31.03.03 }
+ if Item < 0 then
+ Item := 0;
+ while (Item >= 0) and (Item < Count) do
+ begin
+ if ShowProgress then
+ WasLoaded := not IsUnknown(Item);
+ LoadItem(Item, False);
+ if (State = gsLoad) and ShowProgress and (not WasLoaded) then
+ DoProgress(Item, Max);
+ if IsMatched(Item) then
+ begin
+ Result := Item;
+ break;
+ end;
+ Inc(Item);
+ end;
+ if (State = gsLoad) and ShowProgress then
+ begin
+ ShowProgress := False;
+ DoProgress(0, 0);
+ end;
+end;
+
+function THistoryGrid.GetPrev(Item: Integer; Force: Boolean = False): Integer;
+begin
+ Result := -1;
+ if not Force then
+ if Reversed then
+ begin
+ Result := GetNext(Item, True);
+ exit;
+ end;
+ Dec(Item);
+ { AF 31.03.03 }
+ if Item >= Count then
+ Item := Count - 1;
+
+ while (Item < Count) and (Item >= 0) do
+ begin
+ LoadItem(Item, False);
+ if IsMatched(Item) then
+ begin
+ Result := Item;
+ break;
+ end;
+ Dec(Item);
+ end;
+end;
+
+procedure THistoryGrid.CNVScroll(var Message: TWMVScroll);
+begin;
+end;
+
+(*
+ Return is item is visible on client area
+ EVEN IF IT IS *PARTIALLY* VISIBLE
+*)
+function THistoryGrid.IsVisible(Item: Integer; Partially: Boolean = True): Boolean;
+var
+ idx, SumHeight: Integer;
+begin
+ Result := False;
+ if Item = -1 then
+ exit;
+ if GetIdx(Item) < GetIdx(GetFirstVisible) then
+ exit;
+ if not IsMatched(Item) then
+ exit;
+ SumHeight := -TopItemOffset;
+ idx := GetFirstVisible;
+ LoadItem(idx, True);
+ while (SumHeight < ClientHeight) and (Item <> -1) and (Item < Count) do
+ begin
+ if Item = idx then
+ begin
+ if Partially then
+ Result := True
+ else
+ Result := (SumHeight + FItems[idx].Height <= ClientHeight);
+ break;
+ end;
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetNext(idx);
+ if idx = -1 then
+ break;
+ LoadItem(idx, True);
+ end;
+end;
+
+procedure THistoryGrid.DoLButtonDblClick(X, Y: Integer; Keys: TMouseMoveKeys);
+var
+ Item: Integer;
+ ht: TGridHitTests;
+begin
+ SearchPattern := '';
+ CheckBusy;
+ ht := GetHitTests(X, Y);
+ if (ghtSessShowButton in ht) or (ghtSessHideButton in ht) or (ghtBookmark in ht) then
+ exit;
+ if ghtLink in ht then
+ begin
+ DownHitTests := ht;
+ DoLButtonUp(X, Y, Keys);
+ exit;
+ end;
+ Item := FindItemAt(X, Y);
+ if Item <> Selected then
+ begin
+ Selected := Item;
+ exit;
+ end;
+ if Assigned(OnDblClick) then
+ OnDblClick(Self);
+end;
+
+procedure THistoryGrid.DrawProgress;
+var
+ r: TRect;
+begin
+ r := ClientRect;
+ // Canvas.Brush.Color := clWindow;
+ // Canvas.Font.Color := clWindowText;
+ Canvas.Font := Options.FontMessage;
+ Canvas.Brush.Color := Options.ColorBackground;
+ Canvas.Pen.Color := Options.FontMessage.Color;
+ if not IsCanvasClean then
+ begin
+ Canvas.FillRect(r);
+ ProgressRect := r;
+ InflateRect(r, -30, -((ClientHeight - 17) div 2));
+ IsCanvasClean := True;
+ end
+ else
+ begin
+ InflateRect(r, -30, -((ClientHeight - 17) div 2));
+ ProgressRect := r;
+ end;
+ Canvas.FrameRect(r);
+ // Canvas.FillRect(r);
+ InflateRect(r, -1, -1);
+ // InflateRect(r,-30,-((ClientHeight - 15) div 2));
+ Canvas.Rectangle(r);
+ InflateRect(r, -2, -2);
+ // Canvas.Brush.Color := clHighlight;
+ // Canvas.Brush.Color := Options.ColorSelected;
+ Canvas.Brush.Color := Options.FontMessage.Color;
+ if ProgressPercent < 100 then
+ r.Right := r.Left + Round(((r.Right - r.Left) * ProgressPercent) / 100);
+ Canvas.FillRect(r);
+ // t := IntToStr(ProgressPercent)+'%';
+ // DrawTExt(Canvas.Handle,PAnsiChar(t),Length(t),
+ // r,DT_CENTER or DT_NOPREFIX or DT_VCENTER or DT_SINGLELINE);
+end;
+
+procedure THistoryGrid.DoProgress(Position, Max: Integer);
+var
+ dc: HDC;
+ newp: Byte;
+begin
+ if not ShowProgress then
+ begin
+ IsCanvasClean := False;
+ Invalidate;
+ // InvalidateRect(Handle,@ProgressRect,False);
+ ProgressPercent := 255;
+ exit;
+ end;
+
+ if Max = 0 then
+ exit;
+ newp := (Position * 100 div Max);
+ if newp = ProgressPercent then
+ exit;
+ ProgressPercent := newp;
+ if Position = 0 then
+ exit;
+
+ Paint;
+
+ dc := GetDC(Handle);
+
+ try
+ BitBlt(dc, ProgressRect.Left, ProgressRect.Top, ProgressRect.Right - ProgressRect.Left,
+ ProgressRect.Bottom - ProgressRect.Top, Canvas.Handle, ProgressRect.Left,
+ ProgressRect.Top, SRCCOPY);
+ finally
+ ReleaseDC(Handle, dc);
+ end;
+ Application.ProcessMessages;
+end;
+
+procedure THistoryGrid.WMSetCursor(var Message: TWMSetCursor);
+var
+ P: TPoint;
+ NewCursor: TCursor;
+begin
+ inherited;
+ if State <> gsIdle then
+ exit;
+ if Message.HitTest = SmallInt(HTERROR) then
+ exit;
+ NewCursor := crDefault;
+ P := ScreenToClient(Mouse.CursorPos);
+ HintHitTests := GetHitTests(P.X, P.Y);
+ if HintHitTests * [ghtButton, ghtLink] <> [] then
+ NewCursor := crHandPoint;
+ if Windows.GetCursor <> Screen.Cursors[NewCursor] then
+ begin
+ Windows.SetCursor(Screen.Cursors[NewCursor]);
+ Message.Result := 1;
+ end
+ else
+ Message.Result := 0;
+end;
+
+procedure THistoryGrid.WMSetFocus(var Message: TWMSetFocus);
+var
+ r: TRect;
+begin
+ if not((csDestroying in ComponentState) or IsChild(Handle, Message.FocusedWnd)) then
+ begin
+ CheckBusy;
+ if FHideSelection and FGridNotFocused then
+ begin
+ if SelCount > 0 then
+ begin
+ FRichCache.ResetItems(FSelItems);
+ Invalidate;
+ end;
+ end
+ else if (FSelected <> -1) and IsVisible(FSelected) then
+ begin
+ r := GetItemRect(Selected);
+ InvalidateRect(Handle, @r, False);
+ end;
+ end;
+ FGridNotFocused := False;
+ inherited;
+end;
+
+procedure THistoryGrid.WMKillFocus(var Message: TWMKillFocus);
+var
+ r: TRect;
+begin
+ if not((csDestroying in ComponentState) or IsChild(Handle, Message.FocusedWnd)) then
+ begin
+ if FHideSelection and not FGridNotFocused then
+ begin
+ if SelCount > 0 then
+ begin
+ FRichCache.ResetItems(FSelItems);
+ Invalidate;
+ end;
+ end
+ else if (FSelected <> -1) and IsVisible(FSelected) then
+ begin
+ r := GetItemRect(Selected);
+ InvalidateRect(Handle, @r, False);
+ end;
+ FGridNotFocused := True;
+ end;
+ inherited;
+end;
+
+procedure THistoryGrid.WMCommand(var Message: TWMCommand);
+begin
+ inherited;
+{$IFDEF RENDER_RICH}
+ if csDestroying in ComponentState then
+ exit;
+ if Message.Ctl = FRichInline.Handle then
+ begin
+ case Message.NotifyCode of
+ EN_SETFOCUS:
+ begin
+ if State <> gsInline then
+ begin
+ FGridNotFocused := False;
+ Windows.SetFocus(Handle);
+ FGridNotFocused := True;
+ PostMessage(Handle, WM_SETFOCUS, Handle, 0);
+ end;
+ end;
+ EN_KILLFOCUS:
+ begin
+ if State = gsInline then
+ begin
+ CancelInline(False);
+ PostMessage(Handle, WM_KILLFOCUS, Handle, 0);
+ end;
+ Message.Result := 0;
+ end;
+ end;
+ end;
+{$ENDIF}
+end;
+
+procedure THistoryGrid.WMNotify(var Message: TWMNotify);
+var
+ nmh: PFVCNDATA_NMHDR;
+ RichItem: PRichItem;
+ reRect, smRect: TRect;
+begin
+{$IFDEF RENDER_RICH}
+ if Message.nmhdr^.code = NM_FIREVIEWCHANGE then
+ begin
+ if csDestroying in ComponentState then
+ exit;
+ if Message.nmhdr^.hwndFrom = FRichInline.Handle then
+ exit;
+ nmh := PFVCNDATA_NMHDR(Message.nmhdr);
+ if (nmh.bEvent = FVCN_PREFIRE) and (nmh.bAction = FVCA_DRAW) then
+ begin
+ RichItem := FRichCache.GetItemByHandle(Message.nmhdr^.hwndFrom);
+ nmh.bAction := FVCA_NONE;
+ if Assigned(RichItem) then
+ begin
+ if RichItem.GridItem = -1 then
+ exit;
+ if not RichItem.BitmapDrawn then
+ exit;
+ if (LockCount > 0) or (RichItem.GridItem = FItemInline) or
+ (not IsVisible(RichItem^.GridItem)) then
+ begin
+ RichItem.BitmapDrawn := False;
+ exit;
+ end;
+ nmh.bAction := FVCA_SKIPDRAW;
+ if (State = gsIdle) or (State = gsInline) then
+ begin
+ reRect := GetRichEditRect(RichItem.GridItem, True);
+ smRect := nmh.rcRect;
+ OffsetRect(smRect, reRect.Left, reRect.Top);
+ ClipRect := Canvas.ClipRect;
+ if DoRectsIntersect(smRect, ClipRect) then
+ begin
+ nmh.bAction := FVCA_CUSTOMDRAW;
+ nmh.HDC := RichItem.Bitmap.Canvas.Handle;
+ nmh.clrBackground := RichItem.Bitmap.TransparentColor;
+ nmh.fTransparent := False;
+ nmh.lParam := FRichCache.LockItem(RichItem, smRect);
+ end;
+ end;
+ end;
+ end
+ else if (nmh.bEvent = FVCN_POSTFIRE) and (nmh.bAction = FVCA_CUSTOMDRAW) then
+ begin
+ smRect := FRichCache.UnlockItem(nmh.lParam);
+ IntersectRect(smRect, smRect, ClipRect);
+ if not IsRectEmpty(smRect) then
+ InvalidateRect(Handle, @smRect, False);
+ end
+ else if (nmh.bEvent = FVCN_GETINFO) and (nmh.bAction = FVCA_NONE) then
+ begin
+ RichItem := FRichCache.GetItemByHandle(Message.nmhdr^.hwndFrom);
+ if not Assigned(RichItem) then
+ exit;
+ if (RichItem.GridItem = -1) or (RichItem.GridItem = FItemInline) then
+ exit;
+ if not RichItem.BitmapDrawn then
+ exit;
+ // if (State = gsIdle) or (State = gsInline) then
+ nmh.bAction := FVCA_INFO;
+ nmh.rcRect := GetRichEditRect(RichItem.GridItem, True);
+ nmh.clrBackground := RichItem.Bitmap.TransparentColor;
+ nmh.fTransparent := False;
+ end;
+ end
+ else
+{$ENDIF}
+ inherited;
+end;
+
+procedure THistoryGrid.ScrollBy(DeltaX, DeltaY: Integer);
+begin
+ inherited;
+end;
+
+procedure THistoryGrid.ScrollGridBy(Offset: Integer; Update: Boolean = True);
+var
+ previdx, idx, first: Integer;
+ Pos, SumHeight: Integer;
+
+ function SmoothScrollWindow(hwnd: hwnd; XAmount, YAmount: Integer;
+ Rect, ClipRect: PRect): BOOL;
+ begin
+ Result := ScrollWindow(hwnd, XAmount, YAmount, Rect, ClipRect);
+ UpdateWindow(Handle);
+ end;
+
+begin
+ first := GetFirstVisible;
+ if first = -1 then
+ exit;
+ SumHeight := -TopItemOffset;
+ idx := first;
+
+ while (Offset > 0) do
+ begin
+ LoadItem(idx, True);
+ if SumHeight + FItems[idx].Height > Offset + ClientHeight then
+ break;
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetDown(idx);
+ if idx = -1 then
+ begin
+ // we scroll to the last item, let's SetSBPos do the job
+ SetSBPos(MaxSBPos + 1);
+ Repaint;
+ exit;
+ end;
+ end;
+
+ SumHeight := -TopItemOffset;
+ idx := first;
+ while (Offset > 0) and (idx <> -1) and (idx >= 0) and (idx < Count) do
+ begin
+ LoadItem(idx, True);
+ if SumHeight + FItems[idx].Height > Offset then
+ begin
+ Pos := GetIdx(idx);
+ VertScrollBar.Position := Pos;
+ TopItemOffset := Offset - SumHeight;
+ if Update then
+ SmoothScrollWindow(Handle, 0, -Offset, nil, nil);
+ break;
+ end;
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetDown(idx);
+ end;
+
+ SumHeight := -TopItemOffset;
+ while (Offset < 0) and (idx <> -1) and (idx >= 0) and (idx < Count) do
+ begin
+ previdx := idx;
+ idx := GetUp(idx);
+ if SumHeight <= Offset then
+ begin
+ if idx = -1 then
+ VertScrollBar.Position := 0
+ else
+ VertScrollBar.Position := GetIdx(previdx);
+ TopItemOffset := Offset - SumHeight;
+ if Update then
+ SmoothScrollWindow(Handle, 0, -Offset, nil, nil);
+ break;
+ end;
+ if idx = -1 then
+ begin
+ if previdx = first then
+ VertScrollBar.Position := 0
+ else
+ VertScrollBar.Position := GetIdx(previdx);
+ TopItemOffset := 0;
+ // to lazy to calculate proper offset
+ if Update then
+ Repaint;
+ break;
+ end;
+ LoadItem(idx, True);
+ Dec(SumHeight, FItems[idx].Height);
+ end;
+end;
+
+procedure THistoryGrid.ScrollToBottom;
+begin
+ if not BarAdjusted then
+ AdjustScrollBar;
+ SetSBPos(Count);
+end;
+
+procedure THistoryGrid.Delete(Item: Integer);
+var
+ NextItem, Temp, PrevSelCount: Integer;
+begin
+ if Item = -1 then
+ exit;
+ State := gsDelete;
+ NextItem := 0; // to avoid compiler warning
+ try
+ PrevSelCount := SelCount;
+ if Selected = Item then
+ begin
+ // NextItem := -1;
+ if Reversed then
+ NextItem := GetNext(Item)
+ else
+ NextItem := GetPrev(Item);
+ end;
+ DeleteItem(Item);
+ if Selected = Item then
+ begin
+ FSelected := -1;
+ if Reversed then
+ Temp := GetPrev(NextItem)
+ else
+ Temp := GetNext(NextItem);
+ if Temp <> -1 then
+ NextItem := Temp;
+ if PrevSelCount = 1 then
+ // rebuild FSelItems
+ Selected := NextItem
+ else if PrevSelCount > 1 then
+ begin
+ // don't rebuild, just change focus
+ FSelected := NextItem;
+ // check if we're out of SelItems
+ if FSelected > Math.Max(FSelItems[High(FSelItems)], FSelItems[Low(FSelItems)]) then
+ FSelected := Math.Max(FSelItems[High(FSelItems)], FSelItems[Low(FSelItems)]);
+ if FSelected < Math.Min(FSelItems[High(FSelItems)], FSelItems[Low(FSelItems)]) then
+ FSelected := Math.Min(FSelItems[High(FSelItems)], FSelItems[Low(FSelItems)]);
+ end;
+ end
+ else
+ begin
+ if SelCount > 0 then
+ begin
+ if Item <= FSelected then
+ Dec(FSelected);
+ end;
+ end;
+ BarAdjusted := False;
+ AdjustScrollBar;
+ Invalidate;
+ finally
+ State := gsIdle;
+ end;
+end;
+
+procedure THistoryGrid.DeleteAll;
+var
+ cur, Max: Integer;
+begin
+ State := gsDelete;
+ try
+ BarAdjusted := False;
+
+ FRichCache.ResetAllItems;
+ SetLength(FSelItems, 0);
+ FSelected := -1;
+
+ Max := Length(FItems) - 1;
+ // cur := 0;
+
+ ShowProgress := True;
+
+ for cur := 0 to Max do
+ begin
+ if Assigned(FItemDelete) then
+ FItemDelete(Self, -1);
+ DoProgress(cur, Max);
+ if cur = 0 then
+ Invalidate;
+ end;
+ SetLength(FItems, 0);
+
+ AdjustScrollBar;
+ ShowProgress := False;
+ DoProgress(0, 0);
+ Invalidate;
+ Update;
+ finally
+ State := gsIdle;
+ end;
+end;
+
+const
+ MIN_ITEMS_TO_SHOW_PROGRESS = 10;
+
+procedure THistoryGrid.DeleteSelected;
+var
+ NextItem: Integer;
+ Temp: Integer;
+ s, { e, } Max, cur: Integer;
+begin
+ if SelCount = 0 then
+ exit;
+
+ State := gsDelete;
+ try
+
+ Max := Length(FSelItems) - 1;
+ cur := 0;
+
+ s := Math.Min(FSelItems[0], FSelItems[High(FSelItems)]);
+ // e := Math.Max(FSelItems[0],FSelItems[High(FSelItems)]);
+
+ // nextitem := -1;
+
+ if Reversed then
+ NextItem := GetNext(s)
+ else
+ NextItem := GetPrev(s);
+
+ ShowProgress := (Length(FSelItems) >= MIN_ITEMS_TO_SHOW_PROGRESS);
+ while Length(FSelItems) <> 0 do
+ begin
+ DeleteItem(FSelItems[0]);
+ if ShowProgress then
+ DoProgress(cur, Max);
+ if (ShowProgress) and (cur = 0) then
+ Invalidate;
+ Inc(cur);
+ end;
+
+ BarAdjusted := False;
+ AdjustScrollBar;
+
+ if NextItem < 0 then
+ NextItem := -1;
+ FSelected := -1;
+ if Reversed then
+ Temp := GetPrev(NextItem)
+ else
+ Temp := GetNext(NextItem);
+ if Temp = -1 then
+ Selected := NextItem
+ else
+ Selected := Temp;
+
+ if ShowProgress then
+ begin
+ ShowProgress := False;
+ DoProgress(0, 0);
+ end
+ else
+ Invalidate;
+ Update;
+ finally
+ State := gsIdle;
+ end;
+end;
+
+function THistoryGrid.Search(Text: String; CaseSensitive: Boolean;
+ FromStart: Boolean = False; SearchAll: Boolean = False; FromNext: Boolean = False;
+ Down: Boolean = True): Integer;
+var
+ StartItem: Integer;
+ C, Item: Integer;
+begin
+ Result := -1;
+
+ if not CaseSensitive then
+ Text := WideUpperCase(Text);
+
+ if Selected = -1 then
+ begin
+ FromStart := True;
+ FromNext := False;
+ end;
+
+ if FromStart then
+ begin
+ if Down then
+ StartItem := GetTopItem
+ else
+ StartItem := GetBottomItem;
+ end
+ else if FromNext then
+ begin
+ if Down then
+ StartItem := GetNext(Selected)
+ else
+ StartItem := GetPrev(Selected);
+
+ if StartItem = -1 then
+ begin
+ StartItem := Selected;
+ end;
+ end
+ else
+ begin
+ StartItem := Selected;
+ if Selected = -1 then
+ StartItem := GetNext(-1, True);
+ end;
+
+ Item := StartItem;
+
+ C := Count;
+ CheckBusy;
+ State := gsSearch;
+ try
+ while (Item >= 0) and (Item < C) do
+ begin
+ if CaseSensitive then
+ begin
+ // need to strip bbcodes
+ if Pos(Text, FItems[Item].Text) <> 0 then
+ begin
+ Result := Item;
+ break;
+ end;
+ end
+ else
+ begin
+ // need to strip bbcodes
+ if Pos(Text, string(WideUpperCase(FItems[Item].Text))) <> 0 then
+ begin
+ Result := Item;
+ break;
+ end;
+ end;
+
+ if SearchAll then
+ Inc(Item)
+ else if Down then
+ Item := GetNext(Item)
+ else
+ Item := GetPrev(Item);
+
+ if Item <> -1 then
+ begin
+ // prevent GetNext from drawing progress
+ IsCanvasClean := True;
+ ShowProgress := True;
+ DoProgress(Item, C - 1);
+ ShowProgress := False;
+ end;
+ end;
+
+ ShowProgress := False;
+ DoProgress(0, 0);
+ finally
+ State := gsIdle;
+ end;
+end;
+
+procedure THistoryGrid.WMChar(var Message: TWMChar);
+var
+ Key: WideChar;
+begin
+ Key := WideChar(Message.CharCode); // GetWideCharFromWMCharMsg(Message);
+ DoChar(Key, KeyDataToShiftState(Message.KeyData));
+ Message.CharCode := Word(Key); // SetWideCharForWMCharMsg(Message,Key);
+ inherited;
+end;
+
+const
+ BT_BACKSPACE = #8;
+ // #9 -- TAB
+ // #13 -- ENTER
+ // #27 -- ESC
+ ForbiddenChars: array [0 .. 2] of WideChar = (#9, #13, #27);
+
+procedure THistoryGrid.DoChar(var Ch: WideChar; ShiftState: TShiftState);
+var
+ ForbiddenChar: Boolean;
+ i: Integer;
+begin
+ CheckBusy;
+ ForbiddenChar := ((ssAlt in ShiftState) or (ssCtrl in ShiftState));
+ i := 0;
+ While (not ForbiddenChar) and (i <= High(ForbiddenChars)) do
+ begin
+ ForbiddenChar := (Ch = ForbiddenChars[i]);
+ Inc(i);
+ end;
+ if ForbiddenChar then
+ exit;
+ if Assigned(FOnChar) then
+ FOnChar(Self, Ch, ShiftState);
+end;
+
+procedure THistoryGrid.AddItem;
+var
+ i: Integer;
+begin
+ SetLength(FItems, Count + 1);
+
+ FRichCache.WorkOutItemAdded(0);
+
+ // for i := Length(FItems)-1 downto 1 do
+ // FItems[i] := FItems[i-1];
+ Move(FItems[0], FItems[1], (Length(FItems) - 1) * SizeOf(FItems[0]));
+ FillChar(FItems[0], SizeOf(FItems[0]), 0);
+
+ FItems[0].MessageType := [mtUnknown];
+ FItems[0].Height := -1;
+ FItems[0].Text := '';
+ // change selected here
+ if Selected <> -1 then
+ Inc(FSelected);
+ // change inline edited item
+ if ItemInline <> -1 then
+ Inc(FItemInline);
+ for i := 0 to SelCount - 1 do
+ Inc(FSelItems[i]);
+ BarAdjusted := False;
+ AdjustScrollBar;
+ // or window in background isn't repainted. weired
+ // if IsVisible(0) then begin
+ Invalidate;
+ // end;
+end;
+
+procedure THistoryGrid.WMMouseWheel(var Message: TWMMouseWheel);
+var
+ Lines, code: Integer;
+ FWheelCurrTick: Cardinal;
+begin
+ if State = gsInline then
+ begin
+ with TMessage(Message) do
+ FRichInline.Perform(WM_MOUSEWHEEL, wParam, lParam);
+ exit;
+ end;
+ if (Cardinal(Message.WheelDelta) = WHEEL_PAGESCROLL) or (Mouse.WheelScrollLines < 0) then
+ begin
+ Lines := 1;
+ if Message.WheelDelta < 0 then
+ code := SB_PAGEDOWN
+ else
+ code := SB_PAGEUP;
+ end
+ else
+ begin
+ Lines := Mouse.WheelScrollLines;
+ if Message.WheelDelta < 0 then
+ code := SB_LINEDOWN
+ else
+ code := SB_LINEUP;
+ end;
+
+ // some kind of acceleraion. mb the right place is in WM_VSCROLL?
+ FWheelCurrTick := GetTickCount;
+ if FWheelCurrTick - FWheelLastTick < 10 then
+ begin
+ Lines := Lines shl 1;
+ end;
+ FWheelLastTick := FWheelCurrTick;
+
+ FWheelAccumulator := FWheelAccumulator + Message.WheelDelta * Lines;
+ while Abs(FWheelAccumulator) >= WHEEL_DELTA do
+ begin
+ FWheelAccumulator := Abs(FWheelAccumulator) - WHEEL_DELTA;
+ PostMessage(Self.Handle, WM_VSCROLL, code, 0);
+ end;
+end;
+
+procedure THistoryGrid.DeleteItem(Item: Integer);
+var
+ i: Integer;
+ SelIdx: Integer;
+begin
+ // find item pos in selected array if it is there
+ // and fix other positions becouse we have
+ // to decrease some after we delete the item
+ // from main array
+ SelIdx := -1;
+ FRichCache.WorkOutItemDeleted(Item);
+ for i := 0 to SelCount - 1 do
+ begin
+ if FSelItems[i] = Item then
+ SelIdx := i
+ else if FSelItems[i] > Item then
+ Dec(FSelItems[i]);
+ end;
+
+ // delete item from main array
+ // for i := Item to Length(FItems)-2 do
+ // FItems[i] := FItems[i+1];
+ if Item <> High(FItems) then
+ begin
+ Finalize(FItems[Item]);
+ Move(FItems[Item + 1], FItems[Item], (High(FItems) - Item) * SizeOf(FItems[0]));
+ FillChar(FItems[High(FItems)], SizeOf(FItems[0]), 0);
+ end;
+ SetLength(FItems, High(FItems));
+
+ // if it was in selected array delete there also
+ if SelIdx <> -1 then
+ begin
+ // for i := SelIdx to SelCount-2 do
+ // FSelItems[i] := FSelItems[i+1];
+ if SelIdx <> High(FSelItems) then
+ Move(FSelItems[SelIdx + 1], FSelItems[SelIdx], (High(FSelItems) - SelIdx) *
+ SizeOf(FSelItems[0]));
+ SetLength(FSelItems, High(FSelItems));
+ end;
+
+ // move/delete inline edited item
+ if ItemInline = Item then
+ FItemInline := -1
+ else if ItemInline > Item then
+ Dec(FItemInline);
+
+ // tell others they should clear up that item too
+ if Assigned(FItemDelete) then
+ FItemDelete(Self, Item);
+end;
+
+procedure THistoryGrid.SaveAll(FileName: String; SaveFormat: TSaveFormat);
+var
+ i: Integer;
+ fs: TFileStream;
+begin
+ if Count = 0 then
+ raise Exception.Create('History is empty, nothing to save');
+ State := gsSave;
+ try
+ fs := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
+ SaveStart(fs, SaveFormat, TxtFullLog);
+ ShowProgress := True;
+ if ReversedHeader then
+ for i := 0 to SelCount - 1 do
+ begin
+ SaveItem(fs, FSelItems[i], SaveFormat);
+ DoProgress(i, Count - 1);
+ end
+ else
+ for i := Count - 1 downto 0 do
+ begin
+ SaveItem(fs, i, SaveFormat);
+ DoProgress(Count - 1 - i, Count - 1);
+ end;
+ SaveEnd(fs, SaveFormat);
+ fs.Free;
+ ShowProgress := False;
+ DoProgress(0, 0);
+ finally
+ State := gsIdle;
+ end;
+end;
+
+procedure THistoryGrid.SaveSelected(FileName: String; SaveFormat: TSaveFormat);
+var
+ fs: TFileStream;
+ i: Integer;
+begin
+ Assert((SelCount > 0), 'Save Selection is available when more than 1 item is selected');
+ State := gsSave;
+ try
+ fs := TFileStream.Create(FileName, fmCreate or fmShareExclusive);
+ SaveStart(fs, SaveFormat, TxtPartLog);
+ ShowProgress := True;
+ if (FSelItems[0] > FSelItems[High(FSelItems)]) xor ReversedHeader then
+ for i := 0 to SelCount - 1 do
+ begin
+ SaveItem(fs, FSelItems[i], SaveFormat);
+ DoProgress(i, SelCount);
+ end
+ else
+ for i := SelCount - 1 downto 0 do
+ begin
+ SaveItem(fs, FSelItems[i], SaveFormat);
+ DoProgress(SelCount - 1 - i, SelCount);
+ end;
+ SaveEnd(fs, SaveFormat);
+ fs.Free;
+ ShowProgress := False;
+ DoProgress(0, 0);
+ finally
+ State := gsIdle;
+ end;
+end;
+
+const
+ css = 'h3 { color: #666666; text-align: center; font-family: Verdana, Helvetica, Arial, sans-serif; font-size: 16pt; }'
+ + #13#10 +
+ 'h4 { text-align: center; font-family: Verdana, Helvetica, Arial, sans-serif; font-size: 14pt; }'
+ + #13#10 +
+ 'h6 { font-weight: normal; color: #000000; text-align: center; font-family: Verdana, Helvetica, Arial, sans-serif; font-size: 8pt; }'
+ + #13#10 +
+ '.mes { border-top-width: 1px; border-right-width: 0px; border-bottom-width: 0px;' +
+ 'border-left-width: 0px; border-top-style: solid; border-right-style: solid; border-bottom-style: solid; '
+ + 'border-left-style: solid; border-top-color: #666666; border-bottom-color: #666666; ' +
+ 'padding: 4px; }' + #13#10 + '.text { clear: both; }' + #13#10;
+
+ xml = '<?xml version="1.0" encoding="%s"?>' + #13#10 + '<!DOCTYPE IMHISTORY [' + #13#10 +
+ '<!ELEMENT IMHISTORY (EVENT*)>' + #13#10 +
+ '<!ELEMENT EVENT (CONTACT, FROM, TIME, DATE, PROTOCOL, ID?, TYPE, FILE?, URL?, MESSAGE?)>' +
+ #13#10 + '<!ELEMENT CONTACT (#PCDATA)>' + #13#10 + '<!ELEMENT FROM (#PCDATA)>' + #13#10 +
+ '<!ELEMENT TIME (#PCDATA)>' + #13#10 + '<!ELEMENT DATE (#PCDATA)>' + #13#10 +
+ '<!ELEMENT PROTOCOL (#PCDATA)>' + #13#10 + '<!ELEMENT ID (#PCDATA)>' + #13#10 +
+ '<!ELEMENT TYPE (#PCDATA)>' + #13#10 + '<!ELEMENT FILE (#PCDATA)>' + #13#10 +
+ '<!ELEMENT URL (#PCDATA)>' + #13#10 + '<!ELEMENT MESSAGE (#PCDATA)>' + #13#10 +
+ '<!ENTITY ME "%s">' + #13#10 + '%s' + '<!ENTITY UNK "UNKNOWN">' + #13#10 + ']>' + #13#10 +
+ '<IMHISTORY>' + #13#10;
+
+function ColorToCss(Color: TColor): AnsiString;
+var
+ first2, mid2, last2: AnsiString;
+begin
+ // Result := IntToHex(ColorToRGB(Color),6);
+ Result := IntToHex(Color, 6);
+ if Length(Result) > 6 then
+ SetLength(Result, 6);
+ // rotate for HTML color format from AA AB AC to AC AB AA
+ first2 := Copy(Result, 1, 2);
+ mid2 := Copy(Result, 3, 2);
+ last2 := Copy(Result, 5, 2);
+ Result := '#' + last2 + mid2 + first2;
+end;
+
+function FontToCss(Font: TFont): AnsiString;
+begin
+ Result := 'color: ' + ColorToCss(Font.Color) + '; font: '; // color
+ if fsItalic in Font.Style then // font-style
+ Result := Result + 'italic '
+ else
+ Result := Result + 'normal ';
+ Result := Result + 'normal '; // font-variant
+ if fsBold in Font.Style then // font-weight
+ Result := Result + 'bold '
+ else
+ Result := Result + 'normal ';
+ Result := Result + intToStr(Font.Size) + 'pt '; // font-size
+ Result := Result + 'normal '; // line-height
+ Result := Result + // font-family
+ Font.Name + ', Tahoma, Verdana, Arial, sans-serif; ';
+ Result := Result + 'text-decoration: none;'; // decoration
+end;
+
+procedure THistoryGrid.SaveStart(Stream: TFileStream; SaveFormat: TSaveFormat; Caption: String);
+var
+ ProfileID, ContactID, Proto: String;
+
+ procedure SaveHTML;
+ var
+ title, head1, head2: AnsiString;
+ i: Integer;
+ begin
+ title := UTF8Encode(WideFormat('%s [%s] - [%s]', [Caption, ProfileName, ContactName]));
+ head1 := UTF8Encode(WideFormat('%s', [Caption]));
+ head2 := UTF8Encode(WideFormat('%s (%s: %s) - %s (%s: %s)', [ProfileName, Proto, ProfileID,
+ ContactName, Proto, ContactID]));
+ WriteString(Stream, '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">'
+ + #13#10);
+ // if Options.RTLEnabled then WriteString(Stream,'<html dir="rtl">')
+ if (RTLMode = hppRTLEnable) or ((RTLMode = hppRTLDefault) and Options.RTLEnabled) then
+ WriteString(Stream, '<html dir="rtl">')
+ else
+ WriteString(Stream, '<html dir="ltr">');
+ WriteString(Stream, '<head>' + #13#10);
+ WriteString(Stream, '<meta http-equiv="Content-Type" content="text/html; charset=utf-8">'
+ + #13#10);
+ WriteString(Stream, '<title>' + MakeTextHtmled(title) + '</title>' + #13#10);
+ WriteString(Stream, '<style type="text/css"><!--' + #13#10);
+ WriteString(Stream, css);
+
+ // if Options.RTLEnabled then begin
+ if (RTLMode = hppRTLEnable) or ((RTLMode = hppRTLDefault) and Options.RTLEnabled) then
+ begin
+ WriteString(Stream, '.nick { float: right; }' + #13#10);
+ WriteString(Stream, '.date { float: left; clear: left; }' + #13#10);
+ end
+ else
+ begin
+ WriteString(Stream, '.nick { float: left; }' + #13#10);
+ WriteString(Stream, '.date { float: right; clear: right; }' + #13#10);
+ end;
+ WriteString(Stream, '.nick#inc { ' + FontToCss(Options.FontContact) + ' }' + #13#10);
+ WriteString(Stream, '.nick#out { ' + FontToCss(Options.FontProfile) + ' }' + #13#10);
+ WriteString(Stream, '.date#inc { ' + FontToCss(Options.FontIncomingTimestamp) + ' }'
+ + #13#10);
+ WriteString(Stream, '.date#out { ' + FontToCss(Options.FontOutgoingTimestamp) + ' }'
+ + #13#10);
+ WriteString(Stream, '.url { color: ' + ColorToCss(Options.ColorLink) + '; }' + #13#10);
+ for i := 0 to High(Options.ItemOptions) do
+ WriteString(Stream, AnsiString('.mes#event' + intToStr(i) + ' { background-color: ' +
+ ColorToCss(Options.ItemOptions[i].textColor) + '; ' + FontToCss(Options.ItemOptions[i].textFont) + ' }' + #13#10));
+ if ShowHeaders then
+ WriteString(Stream, '.mes#session { background-color: ' +
+ ColorToCss(Options.ColorSessHeader) + '; ' + FontToCss(Options.FontSessHeader) + ' }'
+ + #13#10);
+ WriteString(Stream, '--></style>' + #13#10 + '</head><body>' + #13#10);
+ WriteString(Stream, '<h4>' + MakeTextHtmled(head1) + '</h4>' + #13#10);
+ WriteString(Stream, '<h3>' + MakeTextHtmled(head2) + '</h3>' + #13#10);
+ end;
+
+ procedure SaveXML;
+ var
+ mt: TMessageType;
+ Messages, enc: String;
+ begin
+ // enc := 'windows-'+IntToStr(GetACP);
+ enc := 'utf-8';
+ Messages := '';
+ for mt := Low(EventRecords) to High(EventRecords) do
+ begin
+ if not(mt in EventsDirection + EventsExclude) then
+ Messages := Messages + Format('<!ENTITY %s "%s">' + #13#10,
+ [EventRecords[mt].xml, UTF8Encode(TranslateUnicodeString(EventRecords[mt].Name))
+ ] { TRANSLATE-IGNORE } );
+ end;
+ WriteString(Stream, AnsiString(Format(xml, [enc, UTF8Encode(ProfileName), Messages])));
+ end;
+
+ procedure SaveUnicode;
+ begin
+ WriteString(Stream, #255#254);
+ WriteWideString(Stream, '###'#13#10);
+ if Caption = '' then
+ Caption := TxtHistExport;
+ WriteWideString(Stream, WideFormat('### %s'#13#10, [Caption]));
+ WriteWideString(Stream, WideFormat('### %s (%s: %s) - %s (%s: %s)'#13#10,
+ [ProfileName, Proto, ProfileID, ContactName, Proto, ContactID]));
+ WriteWideString(Stream, TxtGenHist1 + #13#10);
+ WriteWideString(Stream, '###'#13#10#13#10);
+ end;
+
+ procedure SaveText;
+ begin
+ WriteString(Stream, '###'#13#10);
+ if Caption = '' then
+ Caption := TxtHistExport;
+ WriteString(Stream, WideToAnsiString(WideFormat('### %s'#13#10, [Caption]), Codepage));
+ WriteString(Stream, WideToAnsiString(WideFormat('### %s (%s: %s) - %s (%s: %s)'#13#10,
+ [ProfileName, Proto, ProfileID, ContactName, Proto, ContactID]), Codepage));
+ WriteString(Stream, WideToAnsiString(TxtGenHist1 + #13#10, Codepage));
+ WriteString(Stream, '###'#13#10#13#10);
+ end;
+
+ procedure SaveRTF;
+ begin
+ FRichSaveItem := THPPRichEdit.CreateParented(Handle);
+ FRichSave := THPPRichEdit.CreateParented(Handle);
+ FRichSaveOLECB := TRichEditOleCallback.Create(FRichSave);
+ FRichSave.Perform(EM_SETOLECALLBACK, 0,
+ lParam(TRichEditOleCallback(FRichSaveOLECB) as IRichEditOleCallback));
+ end;
+
+ procedure SaveMContacts;
+ begin
+ mcHeader.DataSize := 0;
+ Stream.Write(mcHeader, SizeOf(mcHeader))
+ end;
+
+begin
+ Proto := AnsiToWideString(Protocol, Codepage);
+ ProfileID := AnsiToWideString(GetContactID(0, Protocol, False), Codepage);
+ ContactID := AnsiToWideString(GetContactID(Contact, Protocol, True), Codepage);
+ case SaveFormat of
+ sfHTML:
+ SaveHTML;
+ sfXML:
+ SaveXML;
+ sfMContacts:
+ SaveMContacts;
+ sfRTF:
+ SaveRTF;
+ sfUnicode:
+ SaveUnicode;
+ sfText:
+ SaveText;
+ end;
+end;
+
+procedure THistoryGrid.SaveEnd(Stream: TFileStream; SaveFormat: TSaveFormat);
+
+ procedure SaveHTML;
+ begin
+ WriteString(Stream, '<div class=mes></div>' + #13#10);
+ WriteString(Stream, UTF8Encode(TxtGenHist2) + #13#10);
+ WriteString(Stream, '</body></html>');
+ end;
+
+ procedure SaveXML;
+ begin
+ WriteString(Stream, '</IMHISTORY>');
+ end;
+
+ procedure SaveUnicode;
+ begin;
+ end;
+
+ procedure SaveText;
+ begin;
+ end;
+
+ procedure SaveRTF;
+ begin
+ FRichSave.Lines.SaveToStream(Stream);
+ FRichSave.Perform(EM_SETOLECALLBACK, 0, 0);
+ FRichSave.Destroy;
+ FRichSaveItem.Destroy;
+ FRichSaveOLECB.Free;
+ end;
+
+ procedure SaveMContacts;
+ begin
+ Stream.Seek(SizeOf(mcHeader) - SizeOf(mcHeader.DataSize), soFromBeginning);
+ Stream.Write(mcHeader.DataSize, SizeOf(mcHeader.DataSize));
+ end;
+
+begin
+ case SaveFormat of
+ sfHTML: SaveHTML;
+ sfXML: SaveXML;
+ sfRTF: SaveRTF;
+ sfMContacts: SaveMContacts;
+ sfUnicode: SaveUnicode;
+ sfText: SaveText;
+ end;
+end;
+
+procedure THistoryGrid.SaveItem(Stream: TFileStream; Item: Integer; SaveFormat: TSaveFormat);
+
+ procedure MesTypeToStyle(mt: TMessageTypes; out mes_id, type_id: AnsiString);
+ var
+ i: Integer;
+ Found: Boolean;
+ begin
+ mes_id := 'unknown';
+ if mtIncoming in mt then
+ type_id := 'inc'
+ else
+ type_id := 'out';
+ i := 0;
+ Found := False;
+ while (not Found) and (i <= High(Options.ItemOptions)) do
+ if (MessageTypesToDWord(Options.ItemOptions[i].MessageType) and MessageTypesToDWord(mt))
+ >= MessageTypesToDWord(mt) then
+ Found := True
+ else
+ Inc(i);
+ mes_id := 'event' + intToStr(i);
+ end;
+
+ procedure SaveHTML;
+ var
+ mes_id, type_id: AnsiString;
+ nick, Mes, Time: String;
+ txt: AnsiString;
+ FullHeader: Boolean;
+ begin
+ MesTypeToStyle(FItems[Item].MessageType, mes_id, type_id);
+ FullHeader := not(FGroupLinked and FItems[Item].LinkedToPrev);
+ if FullHeader then
+ begin
+ Time := GetTime(Items[Item].Time);
+ if mtIncoming in FItems[Item].MessageType then
+ nick := ContactName
+ else
+ nick := ProfileName;
+ if Assigned(FGetNameData) then
+ FGetNameData(Self, Item, nick);
+ nick := nick + ':';
+ end;
+ Mes := FItems[Item].Text;
+ if Options.RawRTFEnabled and IsRTF(FItems[Item].Text) then
+ begin
+ ApplyItemToRich(Item);
+ Mes := GetRichString(FRich.Handle, False);
+ end;
+ txt := MakeTextHtmled(UTF8Encode(Mes));
+ try
+ txt := UrlHighlightHtml(txt);
+ except
+ end;
+ if Options.BBCodesEnabled then
+ begin
+ try
+ txt := DoSupportBBCodesHTML(txt);
+ except
+ end;
+ end;
+ if ShowHeaders and FItems[Item].HasHeader then
+ begin
+ WriteString(Stream, '<div class=mes id=session>' + #13#10);
+ WriteString(Stream, #9 + '<div class=text>' +
+ MakeTextHtmled(UTF8Encode(WideFormat(TxtSessions, [Time]))) + '</div>' + #13#10);
+ WriteString(Stream, '</div>' + #13#10);
+ end;
+ WriteString(Stream, '<div class=mes id=' + mes_id + '>' + #13#10);
+ if FullHeader then
+ begin
+ WriteString(Stream, #9 + '<div class=nick id=' + type_id + '>' +
+ MakeTextHtmled(UTF8Encode(nick)) + '</div>' + #13#10);
+ WriteString(Stream, #9 + '<div class=date id=' + type_id + '>' +
+ MakeTextHtmled(UTF8Encode(Time)) + '</div>' + #13#10);
+ end;
+ WriteString(Stream, #9 + '<div class=text>' + #13#10#9 + txt + #13#10#9 + '</div>'
+ + #13#10);
+ WriteString(Stream, '</div>' + #13#10);
+ end;
+
+ procedure SaveXML;
+ var
+ XmlItem: TXMLItem;
+ begin
+ if not Assigned(FGetXMLData) then
+ exit;
+ FGetXMLData(Self, Item, XmlItem);
+ WriteString(Stream, '<EVENT>' + #13#10);
+ WriteString(Stream, #9 + '<CONTACT>' + XmlItem.Contact + '</CONTACT>' + #13#10);
+ WriteString(Stream, #9 + '<FROM>' + XmlItem.From + '</FROM>' + #13#10);
+ WriteString(Stream, #9 + '<TIME>' + XmlItem.Time + '</TIME>' + #13#10);
+ WriteString(Stream, #9 + '<DATE>' + XmlItem.Date + '</DATE>' + #13#10);
+ WriteString(Stream, #9 + '<PROTOCOL>' + XmlItem.Protocol + '</PROTOCOL>' + #13#10);
+ WriteString(Stream, #9 + '<ID>' + XmlItem.ID + '</ID>' + #13#10);
+ WriteString(Stream, #9 + '<TYPE>' + XmlItem.EventType + '</TYPE>' + #13#10);
+ if XmlItem.Mes <> '' then
+ WriteString(Stream, #9 + '<MESSAGE>' + XmlItem.Mes + '</MESSAGE>' + #13#10);
+ if XmlItem.FileName <> '' then
+ WriteString(Stream, #9 + '<FILE>' + XmlItem.FileName + '</FILE>' + #13#10);
+ if XmlItem.Url <> '' then
+ WriteString(Stream, #9 + '<URL>' + XmlItem.Url + '</URL>' + #13#10);
+ WriteString(Stream, '</EVENT>' + #13#10);
+ end;
+
+ procedure SaveUnicode;
+ var
+ nick, Mes, Time: String;
+ FullHeader: Boolean;
+ begin
+ FullHeader := not(FGroupLinked and FItems[Item].LinkedToPrev);
+ if FullHeader then
+ begin
+ Time := GetTime(FItems[Item].Time);
+ if mtIncoming in FItems[Item].MessageType then
+ nick := ContactName
+ else
+ nick := ProfileName;
+ if Assigned(FGetNameData) then
+ FGetNameData(Self, Item, nick);
+ end;
+ Mes := FItems[Item].Text;
+ if Options.RawRTFEnabled and IsRTF(Mes) then
+ begin
+ ApplyItemToRich(Item);
+ Mes := GetRichString(FRich.Handle, False);
+ end;
+ if Options.BBCodesEnabled then
+ Mes := DoStripBBCodes(Mes);
+ if FullHeader then
+ WriteWideString(Stream, WideFormat('[%s] %s:'#13#10, [Time, nick]));
+ WriteWideString(Stream, Mes + #13#10 + #13#10);
+ end;
+
+ procedure SaveText;
+ var
+ Time: AnsiString;
+ nick, Mes: String;
+ FullHeader: Boolean;
+ begin
+ FullHeader := not(FGroupLinked and FItems[Item].LinkedToPrev);
+ if FullHeader then
+ begin
+ Time := WideToAnsiString(GetTime(FItems[Item].Time), Codepage);
+ if mtIncoming in FItems[Item].MessageType then
+ nick := ContactName
+ else
+ nick := ProfileName;
+ if Assigned(FGetNameData) then
+ FGetNameData(Self, Item, nick);
+ end;
+ Mes := FItems[Item].Text;
+ if Options.RawRTFEnabled and IsRTF(Mes) then
+ begin
+ ApplyItemToRich(Item);
+ Mes := GetRichString(FRich.Handle, False);
+ end;
+ if Options.BBCodesEnabled then
+ Mes := DoStripBBCodes(Mes);
+ if FullHeader then
+ WriteString(Stream, AnsiString(Format('[%s] %s:'#13#10, [Time, nick])));
+ WriteString(Stream, WideToAnsiString(Mes, Codepage) + #13#10 + #13#10);
+ end;
+
+ procedure SaveRTF;
+ var
+ RTFStream: AnsiString;
+ Text: String;
+ FullHeader: Boolean;
+ begin
+ FullHeader := not(FGroupLinked and FItems[Item].LinkedToPrev);
+ if FullHeader then
+ begin
+ if mtIncoming in FItems[Item].MessageType then
+ Text := ContactName
+ else
+ Text := ProfileName;
+ if Assigned(FGetNameData) then
+ FGetNameData(Self, Item, Text);
+ Text := Text + ' [' + GetTime(FItems[Item].Time) + ']:';
+ RTFStream := '{\rtf1\par\b1 ' + FormatString2RTF(Text) + '\b0\par}';
+ SetRichRTF(FRichSave.Handle, RTFStream, True, False, False);
+ end;
+ ApplyItemToRich(Item, FRichSaveItem, True);
+ GetRichRTF(FRichSaveItem.Handle, RTFStream, False, False, False, False);
+ SetRichRTF(FRichSave.Handle, RTFStream, True, False, False);
+ end;
+
+ procedure SaveMContacts;
+ var
+ MCItem: TMCItem;
+ begin
+ if not Assigned(FGetMCData) then
+ exit;
+ FGetMCData(Self, Item, MCItem, ssInit);
+ Stream.Write(MCItem.Buffer^, MCItem.Size);
+ FGetMCData(Self, Item, MCItem, ssDone);
+ Inc(mcHeader.DataSize, MCItem.Size);
+ end;
+
+begin
+ LoadItem(Item, False);
+ case SaveFormat of
+ sfHTML:
+ SaveHTML;
+ sfXML:
+ SaveXML;
+ sfRTF:
+ SaveRTF;
+ sfMContacts:
+ SaveMContacts;
+ sfUnicode:
+ SaveUnicode;
+ sfText:
+ SaveText;
+ end;
+end;
+
+procedure THistoryGrid.WriteString(fs: TFileStream; Text: AnsiString);
+begin
+ fs.Write(Text[1], Length(Text));
+end;
+
+procedure THistoryGrid.WriteWideString(fs: TFileStream; Text: String);
+begin
+ fs.Write(Text[1], Length(Text) * SizeOf(Char));
+end;
+
+procedure THistoryGrid.CheckBusy;
+begin
+ if State = gsInline then
+ CancelInline;
+ if State <> gsIdle then
+ raise EAbort.Create('Grid is busy');
+end;
+
+function THistoryGrid.GetSelItems(Index: Integer): Integer;
+begin
+ Result := FSelItems[Index];
+end;
+
+procedure THistoryGrid.SetSelItems(Index: Integer; Item: Integer);
+begin
+ AddSelected(Item);
+end;
+
+procedure THistoryGrid.SetState(const Value: TGridState);
+begin
+ FState := Value;
+ if Assigned(FOnState) then
+ FOnState(Self, FState);
+end;
+
+procedure THistoryGrid.SetReversed(const Value: Boolean);
+var
+ vis_idx: Integer;
+begin
+ if FReversed = Value then
+ exit;
+ if not Allocated then
+ begin
+ FReversed := Value;
+ exit;
+ end;
+ if Selected = -1 then
+ begin
+ vis_idx := GetFirstVisible;
+ end
+ else
+ begin
+ vis_idx := Selected;
+ end;
+ FReversed := Value;
+
+ // VertScrollBar.Position := getIdx(0);
+ BarAdjusted := False;
+ SetSBPos(GetIdx(0));
+ AdjustScrollBar;
+ MakeVisible(vis_idx);
+ Invalidate;
+ Update;
+end;
+
+procedure THistoryGrid.SetReversedHeader(const Value: Boolean);
+begin
+ if FReversedHeader = Value then
+ exit;
+ FReversedHeader := Value;
+ if not Allocated then
+ exit;
+ Invalidate;
+ Update;
+end;
+
+procedure THistoryGrid.SetRichRTL(RTL: Boolean; RichEdit: THPPRichEdit; ProcessTag: Boolean = True);
+var
+ pf: PARAFORMAT2;
+ ExStyle: DWord;
+begin
+ // we use RichEdit.Tag here to save previous RTL state to prevent from
+ // reapplying same state, because SetRichRTL is called VERY OFTEN
+ // (from ApplyItemToRich)
+ if (RichEdit.Tag = Integer(RTL)) and ProcessTag then
+ exit;
+ ZeroMemory(@pf, SizeOf(pf));
+ pf.cbSize := SizeOf(pf);
+ pf.dwMask := PFM_RTLPARA;
+ ExStyle := DWord(GetWindowLongPtr(RichEdit.Handle, GWL_EXSTYLE)) and
+ not(WS_EX_RTLREADING or WS_EX_LEFTSCROLLBAR or WS_EX_RIGHT or WS_EX_LEFT);
+ if RTL then
+ begin
+ ExStyle := ExStyle or (WS_EX_RTLREADING or WS_EX_LEFTSCROLLBAR or WS_EX_LEFT);
+ pf.wReserved := PFE_RTLPARA;
+ end
+ else
+ begin
+ ExStyle := ExStyle or WS_EX_RIGHT;
+ pf.wReserved := 0;
+ end;
+ RichEdit.Perform(EM_SETPARAFORMAT, 0, lParam(@pf));
+ SetWindowLongPtr(RichEdit.Handle, GWL_EXSTYLE, ExStyle);
+ if ProcessTag then
+ RichEdit.Tag := Integer(RTL);
+end;
+
+(* Index to Position *)
+function THistoryGrid.GetIdx(Index: Integer): Integer;
+begin
+ if Reversed then
+ Result := Count - 1 - Index
+ else
+ Result := Index;
+end;
+
+function THistoryGrid.GetFirstVisible: Integer;
+var
+ Pos: Integer;
+begin
+ Pos := VertScrollBar.Position;
+ if MaxSBPos > -1 then
+ Pos := Min(MaxSBPos, VertScrollBar.Position);
+ Result := GetDown(GetIdx(Pos - 1));
+ if Result = -1 then
+ Result := GetUp(GetIdx(Pos + 1));
+end;
+
+procedure THistoryGrid.SetMultiSelect(const Value: Boolean);
+begin
+ FMultiSelect := Value;
+end;
+
+{ ThgVertScrollBar }
+
+procedure THistoryGrid.DoOptionsChanged;
+var
+ i: Integer;
+ Ch, ph, pth, cth, sh: Integer;
+ // pf: PARAFORMAT2;
+begin
+ // recalc fonts
+ for i := 0 to Length(FItems) - 1 do
+ begin
+ FItems[i].Height := -1;
+ end;
+ FRichCache.ResetAllItems;
+
+ // pf.cbSize := SizeOf(pf);
+ // pf.dwMask := PFM_RTLPARA;
+
+ // RTLEnabled := Options.RTLEnabled;
+
+ // if Options.RTLEnabled then begin
+ { if (RTLMode = hppRTLEnable) or ((RTLMode = hppRTLDefault) and Options.RTLEnabled) then begin
+ // redundant, we do it in ApplyItemToRich
+ //SetRichRTL(True);
+ //pf.wReserved := PFE_RTLPARA;
+ // redundant, we do it PaintItem
+ // Canvas.TextFlags := Canvas.TextFlags or ETO_RTLREADING;
+ end else begin
+ // redundant, we do it in ApplyItemToRich
+ // SetRichRTL(False);
+ //pf.wReserved := 0;
+ // redundant, we do it PaintItem
+ // Canvas.TextFlags := Canvas.TextFlags and not ETO_RTLREADING;
+ end; }
+ // SendMessage(FRich.Handle,EM_SETPARAFORMAT,0,LPARAM(@pf));
+ // SendMessage(FRichInline.Handle,EM_SETPARAFORMAT,0,LPARAM(@pf));
+ // FRich.Perform(EM_SETPARAFORMAT,0,LPARAM(@pf));
+ // FRichInline.Perform(EM_SETPARAFORMAT,0,LPARAM(@pf));
+
+ Canvas.Font := Options.FontProfile; ph := Canvas.TextExtent('Wy').cY;
+ Canvas.Font := Options.FontContact; Ch := Canvas.TextExtent('Wy').cY;
+ Canvas.Font := Options.FontOutgoingTimestamp; pth := Canvas.TextExtent('Wy').cY;
+ Canvas.Font := Options.FontIncomingTimestamp; cth := Canvas.TextExtent('Wy').cY;
+ Canvas.Font := Options.FontSessHeader; sh := Canvas.TextExtent('Wy').cY;
+ // find heighest and don't forget about icons
+ PHeaderheight := Max(ph, pth);
+ CHeaderHeight := Max(Ch, cth);
+ SessHeaderHeight := sh + 1 + 3 * 2;
+ if Options.ShowIcons then
+ begin
+ CHeaderHeight := Max(CHeaderHeight, 16);
+ PHeaderheight := Max(PHeaderheight, 16);
+ end;
+
+ Inc(CHeaderHeight, Padding);
+ Inc(PHeaderheight, Padding);
+
+ SetRTLMode(RTLMode);
+ if Assigned(Self.FOnOptionsChange) then
+ FOnOptionsChange(Self);
+
+ BarAdjusted := False;
+ AdjustScrollBar;
+ Invalidate;
+ Update; // cos when you change from Options it updates with lag
+end;
+
+{ ThgVertScrollBar }
+procedure THistoryGrid.SetOptions(const Value: TGridOptions);
+begin
+ BeginUpdate;
+ { disconnect from options }
+ if Assigned(Options) then
+ Options.DeleteGrid(Self);
+ FOptions := Value;
+ { connect to options }
+ if Assigned(Options) then
+ Options.AddGrid(Self);
+ GridUpdate([guOptions]);
+ EndUpdate;
+end;
+
+procedure THistoryGrid.SetRTLMode(const Value: TRTLMode);
+var
+ NewBiDiMode: TBiDiMode;
+begin
+ if FRTLMode <> Value then
+ begin
+ FRTLMode := Value;
+ FRichCache.ResetAllItems;
+ Repaint;
+ end;
+ if (RTLMode = hppRTLEnable) or ((RTLMode = hppRTLDefault) and Options.RTLEnabled) then
+ NewBiDiMode := bdRightToLeft
+ else
+ NewBiDiMode := bdLeftToRight;
+ if NewBiDiMode <> BiDiMode then
+ begin
+ BiDiMode := NewBiDiMode;
+ if Assigned(FOnRTLChange) then
+ OnRTLChange(Self, NewBiDiMode);
+ end;
+ // no need in it?
+ // cause we set rich's RTL in ApplyItemToRich and
+ // canvas'es RTL in PaintItem
+ // DoOptionsChanged;
+end;
+
+procedure THistoryGrid.SetSBPos(Position: Integer);
+var
+ SumHeight: Integer;
+ // DoAdjust: Boolean;
+ idx: Integer;
+begin
+ TopItemOffset := 0;
+ VertScrollBar.Position := Position;
+ AdjustScrollBar;
+ if GetUp(GetIdx(VertScrollBar.Position)) = -1 then
+ VertScrollBar.Position := 0;
+ if MaxSBPos = -1 then
+ exit;
+ if VertScrollBar.Position > MaxSBPos then
+ begin
+ SumHeight := 0;
+ idx := GetIdx(Length(FItems) - 1);
+ repeat
+ LoadItem(idx, True);
+ if IsMatched(idx) then
+ Inc(SumHeight, FItems[idx].Height);
+ idx := GetUp(idx);
+ if idx = -1 then
+ break;
+ until ((SumHeight >= ClientHeight) or (idx < 0) or (idx >= Length(FItems)));
+ if SumHeight > ClientHeight then
+ begin
+ TopItemOffset := SumHeight - ClientHeight;
+ // Repaint;
+ end;
+ end;
+ {
+ if Allocated and VertScrollBar.Visible then begin
+ idx := GetFirstVisible;
+ SumHeight := -TopItemOffset;
+ DoAdjust := False;
+ while (idx <> -1) do begin
+ DoAdjust := True;
+ LoadItem(idx,True);
+ if SumHeight + FItems[idx].Height >= ClientHeight then begin
+ DoAdjust := False;
+ break;
+ end;
+ Inc(Sumheight,FItems[idx].Height);
+ idx := GetDown(idx);
+ end;
+ if DoAdjust then begin
+ AdjustScrollBar;
+ ScrollGridBy(-(ClientHeight-SumHeight),False);
+
+ end;
+ //TopItemOffset := TopItemOffset + (ClientHeight-SumHeight);
+ end; }
+end;
+
+{$IFDEF CUST_SB}
+
+procedure THistoryGrid.SetVertScrollBar(const Value: TVertScrollBar);
+begin
+ FVertScrollBar.Assign(Value);
+end;
+
+function THistoryGrid.GetHideScrollBar: Boolean;
+begin
+ Result := FVertScrollBar.Hidden;
+end;
+
+procedure THistoryGrid.SetHideScrollBar(const Value: Boolean);
+begin
+ FVertScrollBar.Hidden := Value;
+end;
+{$ENDIF}
+
+procedure THistoryGrid.UpdateFilter;
+begin
+ if not Allocated then
+ exit;
+ CheckBusy;
+ FRichCache.ResetItems(FSelItems);
+ SetLength(FSelItems, 0);
+ State := gsLoad;
+ try
+ VertScrollBar.Visible := True;
+{$IFDEF PAGE_SIZE}
+ VertScrollBar.Range := Count + FVertScrollBar.PageSize - 1;
+{$ELSE}
+ VertScrollBar.Range := Count + ClientHeight;
+{$ENDIF}
+ BarAdjusted := False;
+ if (FSelected = -1) or (not IsMatched(FSelected)) then
+ begin
+ ShowProgress := True;
+ try
+ if FSelected <> -1 then
+ begin
+ FSelected := GetDown(FSelected);
+ if FSelected = -1 then
+ FSelected := GetUp(FSelected);
+ end
+ else
+ begin
+ // FSelected := 0;
+ // SetSBPos(GetIdx(FSelected));
+ if Reversed then
+ // we have multiple selection sets
+ FSelected := GetPrev(-1)
+ else
+ // we have multiple selection sets
+ FSelected := GetNext(-1);
+ end;
+ finally
+ ShowProgress := False;
+ end;
+ end;
+ AdjustScrollBar;
+ finally
+ State := gsIdle;
+ Selected := FSelected;
+ end;
+ Repaint;
+end;
+
+function THistoryGrid.IsLinkAtPoint(RichEditRect: TRect; X, Y, Item: Integer): Boolean;
+var
+ P: TPoint;
+ cr: CHARRANGE;
+ cf: CharFormat2;
+ cp: Integer;
+ res: DWord;
+begin
+ Result := False;
+ P := Point(X - RichEditRect.Left, Y - RichEditRect.Top);
+ ApplyItemToRich(Item);
+
+ cp := FRich.Perform(EM_CHARFROMPOS, 0, lParam(@P));
+ if cp = -1 then
+ exit; // out of richedit area
+ cr.cpMin := cp;
+ cr.cpMax := cp + 1;
+ FRich.Perform(EM_EXSETSEL, 0, lParam(@cr));
+
+ ZeroMemory(@cf, SizeOf(cf));
+ cf.cbSize := SizeOf(cf);
+ cf.dwMask := CFM_LINK;
+ res := FRich.Perform(EM_GETCHARFORMAT, SCF_SELECTION, lParam(@cf));
+ // no link under point
+ Result := (((res and CFM_LINK) > 0) and ((cf.dwEffects and CFE_LINK) > 0)) or
+ (((res and CFM_REVISED) > 0) and ((cf.dwEffects and CFE_REVISED) > 0));
+end;
+
+function THistoryGrid.GetHitTests(X, Y: Integer): TGridHitTests;
+var
+ Item: Integer;
+ ItemRect: TRect;
+ HeaderHeight: Integer;
+ HeaderRect, SessRect: TRect;
+ ButtonRect: TRect;
+ P: TPoint;
+ RTL: Boolean;
+ Sel: Boolean;
+ FullHeader: Boolean;
+ TimestampOffset: Integer;
+begin
+ Result := [];
+ FHintRect := Rect(0, 0, 0, 0);
+ Item := FindItemAt(X, Y);
+ if Item = -1 then
+ exit;
+ Include(Result, ghtItem);
+
+ FullHeader := not(FGroupLinked and FItems[Item].LinkedToPrev);
+ ItemRect := GetItemRect(Item);
+ RTL := GetItemRTL(Item);
+ Sel := IsSelected(Item);
+ P := Point(X, Y);
+
+ if FullHeader and (ShowHeaders) and (ExpandHeaders) and (FItems[Item].HasHeader) then
+ begin
+ if Reversed xor ReversedHeader then
+ begin
+ SessRect := Rect(ItemRect.Left, ItemRect.Top, ItemRect.Right,
+ ItemRect.Top + SessHeaderHeight);
+ Inc(ItemRect.Top, SessHeaderHeight);
+ end
+ else
+ begin
+ SessRect := Rect(ItemRect.Left, ItemRect.Bottom - SessHeaderHeight - 1, ItemRect.Right,
+ ItemRect.Bottom - 1);
+ Dec(ItemRect.Bottom, SessHeaderHeight);
+ end;
+ if PtInRect(SessRect, P) then
+ begin
+ Include(Result, ghtSession);
+ InflateRect(SessRect, -3, -3);
+ if RTL then
+ ButtonRect := Rect(SessRect.Left, SessRect.Top, SessRect.Left + 16, SessRect.Bottom)
+ else
+ ButtonRect := Rect(SessRect.Right - 16, SessRect.Top, SessRect.Right, SessRect.Bottom);
+ if PtInRect(ButtonRect, P) then
+ begin
+ Include(Result, ghtSessHideButton);
+ Include(Result, ghtButton);
+ FHintRect := ButtonRect;
+ end;
+ end;
+ end;
+
+ Dec(ItemRect.Bottom); // divider
+ InflateRect(ItemRect, -Padding, -Padding); // paddings
+
+ if FullHeader then
+ begin
+ Dec(ItemRect.Top, Padding);
+ Inc(ItemRect.Top, Padding div 2);
+
+ if mtIncoming in FItems[Item].MessageType then
+ HeaderHeight := CHeaderHeight
+ else
+ HeaderHeight := PHeaderheight;
+
+ HeaderRect := Rect(ItemRect.Left, ItemRect.Top, ItemRect.Right,
+ ItemRect.Top + HeaderHeight);
+ Inc(ItemRect.Top, HeaderHeight + (Padding - (Padding div 2)));
+ if PtInRect(HeaderRect, P) then
+ begin
+ Include(Result, ghtHeader);
+ if (ShowHeaders) and (not ExpandHeaders) and (FItems[Item].HasHeader) then
+ begin
+ if RTL then
+ ButtonRect := Rect(HeaderRect.Right - 16, HeaderRect.Top, HeaderRect.Right,
+ HeaderRect.Bottom)
+ else
+ ButtonRect := Rect(HeaderRect.Left, HeaderRect.Top, HeaderRect.Left + 16,
+ HeaderRect.Bottom);
+ if PtInRect(ButtonRect, P) then
+ begin
+ Include(Result, ghtSessShowButton);
+ Include(Result, ghtButton);
+ FHintRect := ButtonRect;
+ end;
+ end;
+ if ShowBookmarks and (Sel or FItems[Item].Bookmarked) then
+ begin
+ // TimeStamp := GetTime(FItems[Item].Time);
+ // Canvas.Font.Assign(Options.FontTimeStamp);
+ if mtIncoming in FItems[Item].MessageType then
+ Canvas.Font.Assign(Options.FontIncomingTimestamp)
+ else
+ Canvas.Font.Assign(Options.FontOutgoingTimestamp);
+ TimestampOffset := Canvas.TextExtent(GetTime(FItems[Item].Time)).cX + Padding;
+ if RTL then
+ ButtonRect := Rect(HeaderRect.Left + TimestampOffset, HeaderRect.Top,
+ HeaderRect.Left + TimestampOffset + 16, HeaderRect.Bottom)
+ else
+ ButtonRect := Rect(HeaderRect.Right - 16 - TimestampOffset, HeaderRect.Top,
+ HeaderRect.Right - TimestampOffset, HeaderRect.Bottom);
+ if PtInRect(ButtonRect, P) then
+ begin
+ Include(Result, ghtBookmark);
+ Include(Result, ghtButton);
+ FHintRect := ButtonRect;
+ end;
+ end;
+ end;
+ end;
+
+ if PtInRect(ItemRect, P) then
+ begin
+ Include(Result, ghtText);
+ FHintRect := ItemRect;
+ if IsLinkAtPoint(ItemRect, X, Y, Item) then
+ Include(Result, ghtLink)
+ else
+ Include(Result, ghtUnknown);
+ end;
+end;
+
+procedure THistoryGrid.EditInline(Item: Integer);
+var
+ r: TRect;
+ // cr: CHARRANGE;
+begin
+ if State = gsInline then
+ CancelInline(False);
+ MakeVisible(Item);
+ r := GetRichEditRect(Item);
+ if IsRectEmpty(r) then
+ exit;
+
+ // dunno why, but I have to fix it by 1 pixel
+ // or positioning will be not perfectly correct
+ // who knows why? i want to know! I already make corrections of margins!
+ // Dec(r.left,1);
+ Inc(r.Right, 1);
+
+ // below is not optimal way to show rich edit
+ // (ie me better show it after applying item),
+ // but it's done because now when we have OnProcessItem
+ // event grid state is gsInline, which is how it should be
+ // and you can't set it inline before setting focus
+ // because of CheckBusy abort exception
+ // themiron 03.10.2006. don't need to, 'cose there's check
+ // if inline richedit got the focus
+
+ // FRichInline.Show;
+ // FRichInline.SetFocus;
+ // State := gsInline;
+
+ State := gsInline;
+ FItemInline := Item;
+ ApplyItemToRich(Item, FRichInline);
+
+ // set bounds after applying to avoid vertical scrollbar
+ FRichInline.SetBounds(r.Left, r.Top, r.Right - r.Left, r.Bottom - r.Top);
+ FRichInline.SelLength := 0;
+ FRichInline.SelStart := 0;
+
+ FRichInline.Show;
+ FRichInline.SetFocus;
+end;
+
+procedure THistoryGrid.CancelInline(DoSetFocus: Boolean = True);
+begin
+ if State <> gsInline then
+ exit;
+ FRichInline.Hide;
+ State := gsIdle;
+ FRichInline.Clear;
+ FRichInline.Top := -MaxInt;
+ FRichInline.Height := -1;
+ FItemInline := -1;
+ if DoSetFocus then
+ Windows.SetFocus(Handle);
+end;
+
+procedure THistoryGrid.RemoveSelected(Item: Integer);
+begin
+ IntSortedArray_Remove(TIntArray(FSelItems), Item);
+ FRichCache.ResetItem(Item);
+end;
+
+procedure THistoryGrid.ResetItem(Item: Integer);
+begin
+ // we need to adjust scrollbar after ResetItem if GetIdx(Item) >= MaxSBPos
+ // as it's currently used to handle deletion with headers, adjust
+ // is run after deletion ends, so no point in doing it here
+ if IsUnknown(Item) then
+ exit;
+ FItems[Item].Height := -1;
+ FItems[Item].MessageType := [mtUnknown];
+ FRichCache.ResetItem(Item);
+end;
+
+procedure THistoryGrid.ResetAllItems;
+var
+ DoChanges: Boolean;
+ i: Integer;
+begin
+ if not Allocated then
+ exit;
+ BeginUpdate;
+ DoChanges := False;
+ for i := 0 to Length(FItems) - 1 do
+ if not IsUnknown(i) then
+ begin
+ DoChanges := True;
+ // cose it's faster :)
+ FItems[i].MessageType := [mtUnknown];
+ end;
+ if DoChanges then
+ GridUpdate([guOptions]);
+ EndUpdate;
+end;
+
+procedure THistoryGrid.OnInlineOnExit(Sender: TObject);
+begin
+ CancelInline;
+end;
+
+procedure THistoryGrid.OnInlineOnKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
+begin
+ if ((Key = VK_ESCAPE) or (Key = VK_RETURN)) then
+ begin
+ CancelInline;
+ Key := 0;
+ end
+ else if Assigned(FOnInlineKeyDown) then
+ FOnInlineKeyDown(Sender, Key, Shift);
+end;
+
+procedure THistoryGrid.OnInlineOnKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
+begin
+ if not FRichInline.Visible then
+ begin
+ CancelInline;
+ Key := 0;
+ end
+ else
+
+ if (Key = VK_APPS) or ((Key = VK_F10) and (ssShift in Shift)) then
+ begin
+ if Assigned(FOnInlinePopup) then
+ FOnInlinePopup(Sender);
+ Key := 0;
+ end
+ else
+
+ if Assigned(FOnInlineKeyUp) then
+ FOnInlineKeyUp(Sender, Key, Shift);
+end;
+
+procedure THistoryGrid.OnInlineOnMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+begin;
+end;
+
+procedure THistoryGrid.OnInlineOnMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+begin
+ if (Button = mbRight) and Assigned(FOnInlinePopup) then
+ FOnInlinePopup(Sender);
+end;
+
+procedure THistoryGrid.OnInlineOnURLClick(Sender: TObject; const URLText: String; Button: TMouseButton);
+var
+ P: TPoint;
+ Item: Integer;
+begin
+ if Button = mbLeft then
+ begin
+ P := ScreenToClient(Mouse.CursorPos);
+ Item := FindItemAt(P.X, P.Y);
+ URLClick(Item, URLText, Button);
+ end;
+end;
+
+function THistoryGrid.GetRichEditRect(Item: Integer; DontClipTop: Boolean): TRect;
+var
+ res: TRect;
+ hh: Integer;
+begin
+ Result := Rect(0, 0, 0, 0);
+ if Item = -1 then
+ exit;
+ Result := GetItemRect(Item);
+ Inc(Result.Left, Padding);
+ Dec(Result.Right, Padding);
+ /// avatars!.!
+ // Dec(Result.Right,64+Padding);
+ if FGroupLinked and FItems[Item].LinkedToPrev then
+ hh := 0
+ else if mtIncoming in FItems[Item].MessageType then
+ hh := CHeaderHeight
+ else
+ hh := PHeaderheight;
+ Inc(Result.Top, hh + Padding);
+ Dec(Result.Bottom, Padding + 1);
+ if (Items[Item].HasHeader) and (ShowHeaders) and (ExpandHeaders) then
+ begin
+ if Reversed xor ReversedHeader then
+ Inc(Result.Top, SessHeaderHeight)
+ else
+ Dec(Result.Bottom, SessHeaderHeight);
+ end;
+ res := ClientRect;
+{$IFDEF DEBUG}
+ OutputDebugString
+ (PWideChar(Format('GetRichEditRect client: Top:%d Left:%d Bottom:%d Right:%d',
+ [res.Top, res.Left, res.Bottom, res.Right])));
+ OutputDebugString
+ (PWideChar(Format('GetRichEditRect item_2: Top:%d Left:%d Bottom:%d Right:%d',
+ [Result.Top, Result.Left, Result.Bottom, Result.Right])));
+{$ENDIF}
+ if DontClipTop and (Result.Top < res.Top) then
+ res.Top := Result.Top;
+ IntersectRect(Result, res, Result);
+end;
+
+function THistoryGrid.SearchItem(ItemID: Integer): Integer;
+var
+ i { ,FirstItem } : Integer;
+ Found: Boolean;
+begin
+ if not Assigned(OnSearchItem) then
+ raise Exception.Create('You must handle OnSearchItem event to use SearchItem function');
+ Result := -1;
+ State := gsSearchItem;
+ try
+ // FirstItem := GetNext(-1,True);
+ State := gsSearchItem;
+ ShowProgress := True;
+ for i := 0 to Count - 1 do
+ begin
+ if IsUnknown(i) then
+ LoadItem(i, False);
+ Found := False;
+ OnSearchItem(Self, i, ItemID, Found);
+ if Found then
+ begin
+ Result := i;
+ break;
+ end;
+ DoProgress(i + 1, Count);
+ end;
+ ShowProgress := False;
+ finally
+ State := gsIdle;
+ end;
+end;
+
+procedure THistoryGrid.SetBorderStyle(Value: TBorderStyle);
+var
+ Style, ExStyle: DWord;
+begin
+ if FBorderStyle = Value then
+ exit;
+ FBorderStyle := Value;
+ if HandleAllocated then
+ begin
+ Style := DWord(GetWindowLongPtr(Handle, GWL_STYLE)) and WS_BORDER;
+ ExStyle := DWord(GetWindowLongPtr(Handle, GWL_EXSTYLE)) and not WS_EX_CLIENTEDGE;
+ if Ctl3D and NewStyleControls and (FBorderStyle = bsSingle) then
+ begin
+ Style := Style and not WS_BORDER;
+ ExStyle := ExStyle or WS_EX_CLIENTEDGE;
+ end;
+ SetWindowLongPtr(Handle, GWL_STYLE, Style);
+ SetWindowLongPtr(Handle, GWL_EXSTYLE, ExStyle);
+ end;
+end;
+
+procedure THistoryGrid.CMBiDiModeChanged(var Message: TMessage);
+var
+ ExStyle: Cardinal;
+begin
+ // inherited;
+ if HandleAllocated then
+ begin
+ ExStyle := DWord(GetWindowLongPtr(Handle, GWL_EXSTYLE)) and
+ not(WS_EX_RTLREADING or WS_EX_LEFTSCROLLBAR or WS_EX_RIGHT or WS_EX_LEFT);
+ AddBiDiModeExStyle(ExStyle);
+ SetWindowLongPtr(Handle, GWL_EXSTYLE, ExStyle);
+ end;
+end;
+
+procedure THistoryGrid.CMCtl3DChanged(var Message: TMessage);
+var
+ Style, ExStyle: DWord;
+begin
+ if HandleAllocated then
+ begin
+ Style := DWord(GetWindowLongPtr(Handle, GWL_STYLE)) and WS_BORDER;
+ ExStyle := DWord(GetWindowLongPtr(Handle, GWL_EXSTYLE)) and not WS_EX_CLIENTEDGE;
+ if Ctl3D and NewStyleControls and (FBorderStyle = bsSingle) then
+ begin
+ Style := Style and not WS_BORDER;
+ ExStyle := ExStyle or WS_EX_CLIENTEDGE;
+ end;
+ SetWindowLongPtr(Handle, GWL_STYLE, Style);
+ SetWindowLongPtr(Handle, GWL_EXSTYLE, ExStyle);
+ end;
+end;
+
+procedure THistoryGrid.SetHideSelection(const Value: Boolean);
+begin
+ if FHideSelection = Value then
+ exit;
+ FHideSelection := Value;
+ if FGridNotFocused and (SelCount > 0) then
+ begin
+ FRichCache.ResetItems(FSelItems);
+ Invalidate;
+ end;
+end;
+
+function THistoryGrid.GetProfileName: String;
+begin
+ if Assigned(Options) and Options.ForceProfileName then
+ Result := Options.ProfileName
+ else
+ Result := FProfileName;
+end;
+
+procedure THistoryGrid.SetProfileName(const Value: String);
+begin
+ if FProfileName = Value then
+ exit;
+ FProfileName := Value;
+ if Assigned(Options) and Options.ForceProfileName then
+ exit;
+ Update;
+end;
+
+procedure THistoryGrid.SetContactName(const Value: String);
+begin
+ if FContactName = Value then
+ exit;
+ FContactName := Value;
+ Update;
+end;
+
+procedure THistoryGrid.URLClick(Item: Integer; const URLText: String; Button: TMouseButton);
+begin
+ Application.CancelHint;
+ Cursor := crDefault;
+ if Assigned(OnUrlClick) then
+ OnUrlClick(Self, Item, URLText, Button);
+end;
+
+{ TGridOptions }
+
+procedure TGridOptions.AddGrid(Grid: THistoryGrid);
+var
+ i: Integer;
+begin
+ for i := 0 to Length(Grids) - 1 do
+ if Grids[i] = Grid then
+ exit;
+ SetLength(Grids, Length(Grids) + 1);
+ Grids[High(Grids)] := Grid;
+end;
+
+constructor TGridOptions.Create;
+begin
+ inherited;
+
+ FRTLEnabled := False;
+ FShowIcons := False;
+ // FShowAvatars := False;
+
+ FSmileysEnabled := False;
+ FBBCodesEnabled := False;
+ FMathModuleEnabled := False;
+ FRawRTFEnabled := False;
+ FAvatarsHistoryEnabled := False;
+
+ FOpenDetailsMode := False;
+
+ FProfileName := '';
+ FForceProfileName := False;
+
+ FTextFormatting := True;
+
+ FLocks := 0;
+ Changed := 0;
+
+ // FIconOther := TIcon.Create;
+ // FIconOther.OnChange := FontChanged;
+ // FIconFile := TIcon.Create;
+ // FIconFile.OnChange := FontChanged;
+ // FIconUrl := TIcon.Create;
+ // FIconUrl.OnChange := FontChanged;
+ // FIconMessage := TIcon.Create;
+ // FIconMessage.OnChange := FontChanged;
+
+ FFontContact := TFont.Create;
+ FFontContact.OnChange := FontChanged;
+ FFontProfile := TFont.Create;
+ FFontProfile.OnChange := FontChanged;
+ FFontIncomingTimestamp := TFont.Create;
+ FFontIncomingTimestamp.OnChange := FontChanged;
+ FFontOutgoingTimestamp := TFont.Create;
+ FFontOutgoingTimestamp.OnChange := FontChanged;
+ FFontSessHeader := TFont.Create;
+ FFontSessHeader.OnChange := FontChanged;
+ FFontMessage := TFont.Create;
+ FFontMessage.OnChange := FontChanged;
+
+ // FItemFont := TFont.Create;
+
+end;
+
+procedure TGridOptions.DeleteGrid(Grid: THistoryGrid);
+var
+ i: Integer;
+ idx: Integer;
+begin
+ idx := -1;
+ for i := 0 to Length(Grids) - 1 do
+ if Grids[i] = Grid then
+ begin
+ idx := i;
+ break;
+ end;
+ if idx = -1 then
+ exit;
+ for i := idx to Length(Grids) - 2 do
+ Grids[i] := Grids[i + 1];
+ SetLength(Grids, Length(Grids) - 1);
+end;
+
+destructor TGridOptions.Destroy;
+var
+ i: Integer;
+begin
+ FFontContact.Free;
+ FFontProfile.Free;
+ FFontIncomingTimestamp.Free;
+ FFontOutgoingTimestamp.Free;
+ FFontSessHeader.Free;
+ FFontMessage.Free;
+ // FIconUrl.Free;
+ // FIconMessage.Free;
+ // FIconFile.Free;
+ // FIconOther.Free;
+ for i := 0 to Length(FItemOptions) - 1 do
+ begin
+ FItemOptions[i].textFont.Free;
+ end;
+ // SetLength(FItemOptions,0);
+ Finalize(FItemOptions);
+ // SetLength(Grids,0);
+ Finalize(Grids);
+ inherited;
+end;
+
+procedure TGridOptions.DoChange;
+var
+ i: Integer;
+begin
+ Inc(Changed);
+ if FLocks > 0 then
+ exit;
+ for i := 0 to Length(Grids) - 1 do
+ Grids[i].GridUpdate([guOptions]);
+ Changed := 0;
+end;
+
+procedure TGridOptions.EndChange(const Forced: Boolean = False);
+begin
+ if FLocks = 0 then
+ exit;
+ Dec(FLocks);
+ if Forced then
+ Inc(Changed);
+ if (FLocks = 0) and (Changed > 0) then
+ DoChange;
+end;
+
+procedure TGridOptions.FontChanged(Sender: TObject);
+begin
+ DoChange;
+end;
+
+function TGridOptions.AddItemOptions: Integer;
+var
+ i: Integer;
+begin
+ i := Length(FItemOptions);
+ SetLength(FItemOptions, i + 1);
+ FItemOptions[i].MessageType := [mtOther];
+ FItemOptions[i].textFont := TFont.Create;
+ // FItemOptions[i].textFont.Assign(FItemFont);
+ // FItemOptions[i].textColor := clWhite;
+ Result := i;
+end;
+
+function TGridOptions.GetItemOptions(Mes: TMessageTypes; out textFont: TFont; out textColor: TColor): Integer;
+var
+ i: Integer;
+begin
+ i := 0;
+ Result := 0;
+ while i <= High(FItemOptions) do
+ if (MessageTypesToDWord(FItemOptions[i].MessageType) and MessageTypesToDWord(Mes)) >=
+ MessageTypesToDWord(Mes) then
+ begin
+ textFont := FItemOptions[i].textFont;
+ textColor := FItemOptions[i].textColor;
+ Result := i;
+ break;
+ end
+ else
+ begin
+ if mtOther in FItemOptions[i].MessageType then
+ begin
+ textFont := FItemOptions[i].textFont;
+ textColor := FItemOptions[i].textColor;
+ Result := i;
+ end;
+ Inc(i);
+ end;
+end;
+
+function TGridOptions.GetLocked: Boolean;
+begin
+ Result := (FLocks > 0);
+end;
+
+procedure TGridOptions.SetColorDivider(const Value: TColor);
+begin
+ if FColorDivider = Value then
+ exit;
+ FColorDivider := Value;
+ DoChange;
+end;
+
+procedure TGridOptions.SetColorSelectedText(const Value: TColor);
+begin
+ if FColorSelectedText = Value then
+ exit;
+ FColorSelectedText := Value;
+ DoChange;
+end;
+
+procedure TGridOptions.SetColorSelected(const Value: TColor);
+begin
+ if FColorSelected = Value then
+ exit;
+ FColorSelected := Value;
+ DoChange;
+end;
+
+procedure TGridOptions.SetColorSessHeader(const Value: TColor);
+begin
+ if FColorSessHeader = Value then
+ exit;
+ FColorSessHeader := Value;
+ DoChange;
+end;
+
+procedure TGridOptions.SetDateTimeFormat(const Value: String);
+var
+ NewValue: String;
+begin
+ NewValue := Value;
+ try
+ FormatDateTime(NewValue, Now);
+ except
+ NewValue := DEFFORMAT_DATETIME;
+ end;
+ if FDateTimeFormat = NewValue then
+ exit;
+ FDateTimeFormat := NewValue;
+ DoChange;
+end;
+
+procedure TGridOptions.SetTextFormatting(const Value: Boolean);
+var
+ i: Integer;
+begin
+ if FTextFormatting = Value then
+ exit;
+ FTextFormatting := Value;
+ if FLocks > 0 then
+ exit;
+ try
+ for i := 0 to Length(Grids) - 1 do
+ Grids[i].ProcessInline := Value;
+ finally
+ if Assigned(FOnTextFormatting) then
+ FOnTextFormatting(Value);
+ end;
+end;
+
+procedure TGridOptions.SetColorBackground(const Value: TColor);
+begin
+ if FColorBackground = Value then
+ exit;
+ FColorBackground := Value;
+ DoChange;
+end;
+
+procedure TGridOptions.SetColorLink(const Value: TColor);
+begin
+ if FColorLink = Value then
+ exit;
+ FColorLink := Value;
+ DoChange;
+end;
+
+// procedure TGridOptions.SetIconOther(const Value: TIcon);
+// begin
+// FIconOther.Assign(Value);
+// FIconOther.OnChange := FontChanged;
+// DoChange;
+// end;
+
+// procedure TGridOptions.SetIconFile(const Value: TIcon);
+// begin
+// FIconFile.Assign(Value);
+// FIconFile.OnChange := FontChanged;
+// DoChange;
+// end;
+
+// procedure TGridOptions.SetIconMessage(const Value: TIcon);
+// begin
+// FIconMessage.Assign(Value);
+// FIconMessage.OnChange := FontChanged;
+// DoChange;
+// end;
+
+// procedure TGridOptions.SetIconUrl(const Value: TIcon);
+// begin
+// FIconUrl.Assign(Value);
+// FIconUrl.OnChange := FontChanged;
+// DoChange;
+// end;
+
+procedure TGridOptions.SetShowIcons(const Value: Boolean);
+begin
+ if FShowIcons = Value then
+ exit;
+ FShowIcons := Value;
+ Self.StartChange;
+ try
+ if Assigned(FOnShowIcons) then
+ FOnShowIcons;
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+end;
+
+procedure TGridOptions.SetRTLEnabled(const Value: Boolean);
+begin
+ if FRTLEnabled = Value then
+ exit;
+ FRTLEnabled := Value;
+ Self.StartChange;
+ try
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+end;
+
+{ procedure TGridOptions.SetShowAvatars(const Value: Boolean);
+ begin
+ if FShowAvatars = Value then exit;
+ FShowAvatars := Value;
+ Self.StartChange;
+ try
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+ end; }
+
+procedure TGridOptions.SetBBCodesEnabled(const Value: Boolean);
+begin
+ if FBBCodesEnabled = Value then
+ exit;
+ FBBCodesEnabled := Value;
+ Self.StartChange;
+ try
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+end;
+
+procedure TGridOptions.SetSmileysEnabled(const Value: Boolean);
+begin
+ if FSmileysEnabled = Value then
+ exit;
+ FSmileysEnabled := Value;
+ Self.StartChange;
+ try
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+end;
+
+procedure TGridOptions.SetMathModuleEnabled(const Value: Boolean);
+begin
+ if FMathModuleEnabled = Value then
+ exit;
+ FMathModuleEnabled := Value;
+ Self.StartChange;
+ try
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+end;
+
+procedure TGridOptions.SetRawRTFEnabled(const Value: Boolean);
+begin
+ if FRawRTFEnabled = Value then
+ exit;
+ FRawRTFEnabled := Value;
+ Self.StartChange;
+ try
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+end;
+
+procedure TGridOptions.SetAvatarsHistoryEnabled(const Value: Boolean);
+begin
+ if FAvatarsHistoryEnabled = Value then
+ exit;
+ FAvatarsHistoryEnabled := Value;
+ Self.StartChange;
+ try
+ DoChange;
+ finally
+ Self.EndChange;
+ end;
+end;
+
+procedure TGridOptions.SetFontContact(const Value: TFont);
+begin
+ FFontContact.Assign(Value);
+ FFontContact.OnChange := FontChanged;
+ DoChange;
+end;
+
+procedure TGridOptions.SetFontProfile(const Value: TFont);
+begin
+ FFontProfile.Assign(Value);
+ FFontProfile.OnChange := FontChanged;
+ DoChange;
+end;
+
+procedure TGridOptions.SetFontIncomingTimestamp(const Value: TFont);
+begin
+ FFontIncomingTimestamp.Assign(Value);
+ FFontIncomingTimestamp.OnChange := FontChanged;
+ DoChange;
+end;
+
+procedure TGridOptions.SetFontOutgoingTimestamp(const Value: TFont);
+begin
+ FFontOutgoingTimestamp.Assign(Value);
+ FFontOutgoingTimestamp.OnChange := FontChanged;
+ DoChange;
+end;
+
+procedure TGridOptions.SetFontSessHeader(const Value: TFont);
+begin
+ FFontSessHeader.Assign(Value);
+ FFontSessHeader.OnChange := FontChanged;
+ DoChange;
+end;
+
+procedure TGridOptions.SetFontMessage(const Value: TFont);
+begin
+ FFontMessage.Assign(Value);
+ FFontMessage.OnChange := FontChanged;
+ DoChange;
+end;
+
+procedure TGridOptions.StartChange;
+begin
+ Inc(FLocks);
+end;
+
+procedure TGridOptions.SetProfileName(const Value: String);
+begin
+ if Value = FProfileName then
+ exit;
+ FProfileName := Value;
+ FForceProfileName := (Value <> '');
+ DoChange;
+end;
+
+{ TRichCache }
+
+procedure TRichCache.ApplyItemToRich(Item: PRichItem);
+begin
+ // force to send the size:
+ FRichHeight := -1;
+ // Item^.Rich.HandleNeeded;
+ Item^.Rich.Perform(EM_SETEVENTMASK, 0, 0);
+ Grid.ApplyItemToRich(Item^.GridItem, Item^.Rich);
+ Item^.Rich.Perform(EM_SETEVENTMASK, 0, ENM_REQUESTRESIZE);
+ Item^.Rich.Perform(EM_REQUESTRESIZE, 0, 0);
+ Assert(FRichHeight > 0, 'RichCache.ApplyItemToRich: rich is still <= 0 height');
+ Item^.Rich.Perform(EM_SETEVENTMASK, 0, RichEventMasks);
+end;
+
+function TRichCache.CalcItemHeight(GridItem: Integer): Integer;
+var
+ Item: PRichItem;
+begin
+ Item := RequestItem(GridItem);
+ Assert(Item <> nil);
+ Result := Item^.Height;
+end;
+
+constructor TRichCache.Create(AGrid: THistoryGrid);
+var
+ i: Integer;
+ RichItem: PRichItem;
+ dc: HDC;
+begin
+ inherited Create;
+
+ FRichWidth := -1;
+ FRichHeight := -1;
+ Grid := AGrid;
+ // cache size
+ SetLength(Items, 20);
+
+ RichEventMasks := ENM_LINK;
+
+ dc := GetDC(0);
+ LogX := GetDeviceCaps(dc, LOGPIXELSX);
+ LogY := GetDeviceCaps(dc, LOGPIXELSY);
+ ReleaseDC(0, dc);
+
+ FLockedList := TList.Create;
+
+ for i := 0 to Length(Items) - 1 do
+ begin
+ New(RichItem);
+ RichItem^.Bitmap := TBitmap.Create;
+ RichItem^.Height := -1;
+ RichItem^.GridItem := -1;
+ RichItem^.Rich := THPPRichEdit.Create(nil);
+ RichItem^.Rich.Name := 'CachedRichEdit' + intToStr(i);
+ // workaround of SmileyAdd making richedit visible all the time
+ RichItem^.Rich.Top := -MaxInt;
+ RichItem^.Rich.Height := -1;
+ RichItem^.Rich.Visible := False;
+ { Don't give him grid as parent, or we'll have
+ wierd problems with scroll bar }
+ RichItem^.Rich.Parent := nil;
+ RichItem^.Rich.WordWrap := True;
+ RichItem^.Rich.BorderStyle := bsNone;
+ RichItem^.Rich.OnResizeRequest := OnRichResize;
+ Items[i] := RichItem;
+ end;
+end;
+
+destructor TRichCache.Destroy;
+var
+ i: Integer;
+begin
+ for i := 0 to FLockedList.Count - 1 do
+ Dispose(PLockedItem(FLockedList.Items[i]));
+ FLockedList.Free;
+ for i := 0 to Length(Items) - 1 do
+ begin
+ FreeAndNil(Items[i]^.Rich);
+ FreeAndNil(Items[i]^.Bitmap);
+ Dispose(Items[i]);
+ end;
+ Finalize(Items);
+ inherited;
+end;
+
+function TRichCache.FindGridItem(GridItem: Integer): Integer;
+var
+ i: Integer;
+begin
+ Result := -1;
+ if GridItem = -1 then
+ exit;
+ for i := 0 to Length(Items) - 1 do
+ if Items[i].GridItem = GridItem then
+ begin
+ Result := i;
+ break;
+ end;
+end;
+
+function TRichCache.GetItemRich(GridItem: Integer): THPPRichEdit;
+var
+ Item: PRichItem;
+begin
+ Item := RequestItem(GridItem);
+ Assert(Item <> nil);
+ Result := Item^.Rich;
+end;
+
+function TRichCache.GetItemRichBitmap(GridItem: Integer): TBitmap;
+var
+ Item: PRichItem;
+begin
+ Item := RequestItem(GridItem);
+ Assert(Item <> nil);
+ if not Item^.BitmapDrawn then
+ PaintRichToBitmap(Item);
+ Result := Item^.Bitmap;
+end;
+
+function TRichCache.GetItemByHandle(Handle: THandle): PRichItem;
+var
+ i: Integer;
+begin
+ Result := nil;
+ for i := 0 to High(Items) do
+ if Items[i].Rich.Handle = Handle then
+ begin
+ if Items[i].Height = -1 then
+ break;
+ Result := Items[i];
+ break;
+ end;
+end;
+
+function TRichCache.LockItem(Item: PRichItem; SaveRect: TRect): Integer;
+var
+ LockedItem: PLockedItem;
+begin
+ Result := -1;
+ Assert(Item <> nil);
+ try
+ New(LockedItem);
+ except
+ LockedItem := nil;
+ end;
+ if Assigned(LockedItem) then
+ begin
+ Item.Bitmap.Canvas.Lock;
+ LockedItem.RichItem := Item;
+ LockedItem.SaveRect := SaveRect;
+ Result := FLockedList.Add(LockedItem);
+ end;
+end;
+
+function TRichCache.UnlockItem(Item: Integer): TRect;
+var
+ LockedItem: PLockedItem;
+begin
+ Result := Rect(0, 0, 0, 0);
+ if Item = -1 then
+ exit;
+ LockedItem := FLockedList.Items[Item];
+ if not Assigned(LockedItem) then
+ exit;
+ if Assigned(LockedItem.RichItem) then
+ LockedItem.RichItem.Bitmap.Canvas.Unlock;
+ Result := LockedItem.SaveRect;
+ Dispose(LockedItem);
+ FLockedList.Delete(Item);
+end;
+
+procedure TRichCache.MoveToTop(Index: Integer);
+var
+ i: Integer;
+ Item: PRichItem;
+begin
+ if Index = 0 then
+ exit;
+ Assert(Index < Length(Items));
+ Item := Items[Index];
+ for i := Index downto 1 do
+ Items[i] := Items[i - 1];
+ // Move(Items[0],Items[1],Index*SizeOf(Items[0]));
+ Items[0] := Item;
+end;
+
+procedure TRichCache.OnRichResize(Sender: TObject; Rect: TRect);
+begin
+ FRichHeight := Rect.Bottom - Rect.Top;
+end;
+
+procedure TRichCache.PaintRichToBitmap(Item: PRichItem);
+var
+ BkColor: TCOLORREF;
+ Range: TFormatRange;
+begin
+ if (Item^.Bitmap.Width <> Item^.Rich.Width) or (Item^.Bitmap.Height <> Item^.Height) then
+ begin
+ // to prevent image copy
+ Item^.Bitmap.Assign(nil);
+ Item^.Bitmap.SetSize(Item^.Rich.Width, Item^.Height);
+ end;
+ // because RichEdit sometimes paints smaller image
+ // than it said when calculating height, we need
+ // to fill the background
+ BkColor := Item^.Rich.Perform(EM_SETBKGNDCOLOR, 0, 0);
+ Item^.Rich.Perform(EM_SETBKGNDCOLOR, 0, BkColor);
+ Item^.Bitmap.TransparentColor := BkColor;
+ Item^.Bitmap.Canvas.Brush.Color := BkColor;
+ Item^.Bitmap.Canvas.FillRect(Item^.Bitmap.Canvas.ClipRect);
+ with Range do
+ begin
+ HDC := Item^.Bitmap.Canvas.Handle;
+ hdcTarget := HDC;
+ rc := Rect(0, 0, MulDiv(Item^.Bitmap.Width, 1440, LogX),
+ MulDiv(Item^.Bitmap.Height, 1440, LogY));
+ rcPage := rc;
+ chrg.cpMin := 0;
+ chrg.cpMax := -1;
+ end;
+ SetBkMode(Range.hdcTarget, TRANSPARENT);
+ Item^.Rich.Perform(EM_FORMATRANGE, 1, lParam(@Range));
+ Item^.Rich.Perform(EM_FORMATRANGE, 0, 0);
+ Item^.BitmapDrawn := True;
+end;
+
+function TRichCache.RequestItem(GridItem: Integer): PRichItem;
+var
+ idx: Integer;
+begin
+ Assert(GridItem > -1);
+ idx := FindGridItem(GridItem);
+ if idx <> -1 then
+ begin
+ Result := Items[idx];
+ end
+ else
+ begin
+ idx := High(Items);
+ Result := Items[idx];
+ Result.GridItem := GridItem;
+ Result.Height := -1;
+ end;
+ if Result.Height = -1 then
+ begin
+ ApplyItemToRich(Result);
+ Result.Height := FRichHeight;
+ Result.Rich.Height := FRichHeight;
+ Result.BitmapDrawn := False;
+ MoveToTop(idx);
+ end;
+end;
+
+procedure TRichCache.ResetAllItems;
+var
+ i: Integer;
+begin
+ for i := 0 to High(Items) do
+ begin
+ Items[i].Height := -1;
+ end;
+end;
+
+procedure TRichCache.ResetItem(GridItem: Integer);
+var
+ idx: Integer;
+begin
+ if GridItem = -1 then
+ exit;
+ idx := FindGridItem(GridItem);
+ if idx = -1 then
+ exit;
+ Items[idx].Height := -1;
+end;
+
+procedure TRichCache.ResetItems(GridItems: array of Integer);
+var
+ i: Integer;
+ idx: Integer;
+ ItemsReset: Integer;
+begin
+ ItemsReset := 0;
+ for i := 0 to Length(GridItems) - 1 do
+ begin
+ idx := FindGridItem(GridItems[i]);
+ if idx <> -1 then
+ begin
+ Items[idx].Height := -1;
+ Inc(ItemsReset);
+ end;
+ // no point in searching, we've reset all items
+ if ItemsReset >= Length(Items) then
+ break;
+ end;
+end;
+
+procedure TRichCache.SetHandles;
+var
+ i: Integer;
+ ExStyle: DWord;
+begin
+ for i := 0 to Length(Items) - 1 do
+ begin
+ Items[i].Rich.ParentWindow := Grid.Handle;
+ // make richedit transparent:
+ ExStyle := GetWindowLongPtr(Items[i].Rich.Handle, GWL_EXSTYLE);
+ ExStyle := ExStyle or WS_EX_TRANSPARENT;
+ SetWindowLongPtr(Items[i].Rich.Handle, GWL_EXSTYLE, ExStyle);
+ Items[i].Rich.Brush.Style := bsClear;
+ end;
+end;
+
+procedure TRichCache.SetWidth(const Value: Integer);
+var
+ i: Integer;
+begin
+ if FRichWidth = Value then
+ exit;
+ FRichWidth := Value;
+ for i := 0 to Length(Items) - 1 do
+ begin
+ Items[i].Rich.Width := Value;
+ Items[i].Height := -1;
+ end;
+end;
+
+procedure TRichCache.WorkOutItemAdded(GridItem: Integer);
+var
+ i: Integer;
+begin
+ for i := 0 to Length(Items) - 1 do
+ if Items[i].Height <> -1 then
+ begin
+ if Items[i].GridItem >= GridItem then
+ Inc(Items[i].GridItem);
+ end;
+end;
+
+procedure TRichCache.WorkOutItemDeleted(GridItem: Integer);
+var
+ i: Integer;
+begin
+ for i := 0 to Length(Items) - 1 do
+ if Items[i].Height <> -1 then
+ begin
+ if Items[i].GridItem = GridItem then
+ Items[i].Height := -1
+ else if Items[i].GridItem > GridItem then
+ Dec(Items[i].GridItem);
+ end;
+end;
+
+initialization
+
+ Screen.Cursors[crHandPoint] := LoadCursor(0, IDC_HAND);
+ if Screen.Cursors[crHandPoint] = 0 then
+ Screen.Cursors[crHandPoint] := LoadCursor(hInstance, 'CR_HAND');
+
+end.