You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
RnQ/RnQ/chatDlg.pas

1693 lines
42 KiB
Plaintext

{
This file is part of R&Q.
Under same license
}
unit chatDlg;
{$I RnQConfig.inc}
interface
uses
Winapi.Windows, Winapi.Messages, System.SysUtils, System.Classes, System.DateUtils, System.Threading, System.Win.TaskbarCore,
Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.ExtCtrls, Vcl.Taskbar, Vcl.Imaging.PNGImage, Generics.Collections,
RDGlobal, RnQGraphics32, RnQNet, ICQCommon, ICQContacts, ChatBox, events, pluginLib, Murmur2, SpellCheck, Sciter, SciterAPI;
{$I NoRTTI.inc}
const
ClrHistBG = 'history.bg';
type
TPanelEx = class(TPanel)
private
procedure WMEraseBkgnd(var msg: TWMEraseBkgnd); message WM_ERASEBKGND;
end;
TChatType = (CT_IM, CT_PLUGING);
PChatInfo = ^TchatInfo;
TchatInfo = class
public
ID: IntPtr;
chatType: TChatType;
who: TICQContact;
single: Boolean; // single-message
lastInputText: string; // last input.text before quoting sequence
constructor Create;
destructor Destroy; override;
procedure CheckTypingTime;
end; // TchatInfo
Tchats = class(Tlist)
function validIdx(i: Integer): boolean;
function idxOf(c: TICQContact): Integer;
function idxOfUIN(const uin: TUID): Integer;
function idxOfID(ID: Integer): Integer;
function byIdx(i: Integer): TchatInfo;
function byContact(c: TICQContact): TchatInfo;
function byUIN(const uid: TUID): TchatInfo;
function byID(id: Integer): TchatInfo;
procedure CheckTypingTimeAll;
end; // Tchats
TchatFrm = class(TForm)
TaskBar: TTaskbar;
procedure closemenuPopup(Sender: TObject);
procedure FormResize(Sender: TObject);
procedure AvtsplitterMoving(Sender: TObject; var NewSize: Integer; var Accept: boolean);
procedure Close1Click(Sender: TObject);
procedure Viewinfo1Click(Sender: TObject);
procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
procedure FormShow(Sender: TObject);
procedure chatDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: boolean);
procedure chatDragDrop(Sender, Source: TObject; X, Y: Integer);
procedure FormDestroy(Sender: TObject);
procedure DragDropFiles(var Message: TWMDropFiles); message WM_DROPFILES;
procedure OnBeforeHeaderSend(Sender: TObject; const Method : String; Headers: TStrings);
procedure OnSendData(Sender: TObject; Buffer: Pointer; Len: Integer);
procedure TaskBarThumbButtonClick(Sender: TObject; AButtonID: Integer);
procedure FormCreate(Sender: TObject);
protected
procedure WndProc(var Message: TMessage); override;
procedure CreateParams(var Params: TCreateParams); override;
procedure WMEXITSIZEMOVE(var Message: TMessage); message WM_EXITSIZEMOVE;
procedure WMWINDOWPOSCHANGING(Var msg: TWMWINDOWPOSCHANGING); message WM_WINDOWPOSCHANGING;
procedure WMAppCommand(var msg: TMessage); message WM_APPCOMMAND;
procedure WMNCActivate(var msg: TWMNCActivate); message WM_NCACTIVATE;
private
FMouseInControl: Boolean;
procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE;
public
chats: Tchats;
ChatBox: TChatBox;
selectedUIN: TUID;
plugBtns: TPlugButtons;
MainFormWidth: Integer;
menuWasCancelled: boolean;
PagesEnumStr: RawByteString;
DraggingTab: Boolean;
Initialized: Boolean;
procedure UpdateChatSettings;
procedure UpdateChatSmiles;
procedure LoadChatStickers;
procedure LoadSearchResults;
function IsChatOpen(otherHand: TICQContact): boolean;
function OpenChat(otherHand: TICQContact; ForceActive: Boolean = False; isAuto: Boolean = False): Boolean;
function AddEvent_OpenChat(otherHand: TICQContact; ev: Thevent): Boolean;
function AddEvent(c: TICQContact; ev: Thevent): Boolean; overload;
function OpenOn(c: TICQContact; focus: Boolean = True; pShow: Boolean = True): Boolean;
procedure Open(focus: Boolean = True);
function NewIMChannel(c: TICQContact): Integer;
procedure UpdateAvatar(c: TICQContact);
function thisChat: TchatInfo;
function thisChatUID: TUID;
procedure SetCurrent(idx: Integer);
procedure UserChanged(c: TICQContact);
procedure RedrawTabs;
procedure RedrawTab(c: TICQContact);
procedure RedrawPluginTab(ci: TchatInfo);
procedure CloseThisPage;
procedure CloseAllPages(isAuto: boolean = false);
procedure ClosePageAt(idx: Integer);
procedure CloseChat(ci: TchatInfo);
procedure CloseChatWith(c: TICQContact);
procedure CloseChatUID(const uid: TUID);
procedure CloseChatID(ID: Integer);
function SawAllHere: boolean;
function isVisible: boolean;
procedure ApplyFormXY;
procedure UpdateContactStatus(cnt: TICQContact = nil);
function pageIdxAt(X, Y: Integer): Integer;
procedure UpdateChatfrmXY;
procedure UpdateChatfrmPreview;
procedure UpdateStatusBar;
procedure UpdateRelTimes;
procedure SetStatusbar(const s: String);
function HasEvent(c: TICQContact; ev: Thevent): Boolean;
procedure MoveToEvent(c: TICQContact; ev: Thevent);
procedure SetUnreadEvent(c: TICQContact; ev: Thevent);
function thisContact: TICQContact;
procedure flash;
procedure shake;
function Pages2String: RawByteString;
procedure loadPages(const s: RawByteString); overload;
procedure loadPages(const cl: TRnQCList); overload;
procedure UpdateGraphics(c: TICQContact = nil);
procedure ApplyTheme;
procedure ResetHistory;
procedure InitDocument;
procedure RefreshTaskbarButtons;
procedure InitSpellCheck;
procedure SpellCheck;
procedure SetupChatButtons;
procedure SetupStickersButton;
procedure ApplyTitleColors(active: Boolean);
end; // TchatFrm
function CHAT_TAB_ADD(Control: Integer; iIcon: HIcon; const TabCaption: String): Integer;
procedure CHAT_TAB_MODIFY(Control: Integer; iIcon: HIcon; const TabCaption: String);
procedure CHAT_TAB_DELETE(Control: Integer);
var
chatFrm: TchatFrm;
UploadCallbacks: TCallbacks;
TabsIconCache: TDictionary;
EmbeddedImgs: TDictionary;
MouseHook: Cardinal;
implementation
{$R *.DFM}
uses
Clipbrd, ShellAPI, Themes, Math, Types,
RQUtil, RDUtils, RnQSysUtils, RnQTips, RnQPics, RnQLangs, RnQGlobal,
globalLib, outboxlib, utilLib, mainDlg, roasterLib, Protocols_All,
Protocol_ICQ, ICQConsts,
RQThemes, themesLib,
{$IFDEF UNICODE}
AnsiStrings,
Character
{$ENDIF UNICODE};
procedure TPanelEx.WMEraseBkgnd(var msg: TWMEraseBkgnd);
begin
msg.Result := 1;
msg.msg := 0;
end;
constructor TchatInfo.Create;
begin
inherited;
end;
destructor TchatInfo.Destroy;
begin
who := nil;
inherited;
end;
{ Tchats }
{$WARN UNSAFE_CAST OFF}
function Tchats.idxOfUIN(const uin: TUID): Integer;
begin
Result := 0;
while Result < count do
begin
if TchatInfo(items[Result]).chatType = CT_IM then
if TchatInfo(items[Result]).who.equals(uin) then
Exit;
Inc(Result);
end;
Result := -1;
end; // idxOfUIN
function Tchats.idxOfID(ID: Integer): Integer;
begin
Result := 0;
while Result < count do
begin
if TchatInfo(items[Result]).chatType = CT_PLUGING then
if TchatInfo(items[Result]).ID = ID then
Exit;
Inc(Result);
end;
Result := -1;
end; // idxOfID
function Tchats.idxOf(c: TICQContact): Integer;
begin
Result := 0;
while Result < count do
begin
if Assigned(items[Result]) then
if TchatInfo(items[Result]).chatType = CT_IM then
if TchatInfo(items[Result]).who.equals(c) then
Exit;
Inc(Result);
end;
Result := -1;
end; // idxOf
function Tchats.byIdx(i: Integer): TchatInfo;
begin
Result := nil;
if validIdx(i) then
Result := TchatInfo(items[i])
end; // byIdx
{$WARN UNSAFE_CAST ON}
function Tchats.byContact(c: TICQContact): TchatInfo;
begin
Result := byIdx(idxOf(c))
end;
function Tchats.byUIN(const uid: TUID): TchatInfo;
begin
Result := byIdx(idxOfUIN(uid))
end;
function Tchats.byID(id: Integer): TchatInfo;
begin
Result := byIdx(idxOfID(id))
end;
function Tchats.validIdx(i: Integer): boolean;
begin
Result := (i >= 0) and (i < count)
end;
procedure Tchats.CheckTypingTimeAll;
var
i: Integer;
begin
if Assigned(Account.AccProto) then
if (Account.AccProto.SupportTypingNotif) and (Account.AccProto.IsSendTypingNotif) then
if count > 0 then
for i := count - 1 downto 0 do
if TchatInfo(items[i]).chatType = CT_IM then
if Assigned(TchatInfo(items[i]).who) then
TchatInfo(items[i]).CheckTypingTime;
end;
{ TchatFrm }
procedure TchatFrm.FormResize(Sender: TObject);
var
ch: TchatInfo;
begin
UpdateChatfrmXY;
UpdateChatfrmPreview;
ch := thisChat;
if ch = nil then
Exit;
if ch.chatType = CT_PLUGING then
plugins.castEv(PE_SELECTTAB, ch.ID)
end; // formResize
function TchatFrm.AddEvent_OpenChat(otherHand: TICQContact; ev: Thevent): Boolean;
begin
OpenChat(otherHand);
Result := AddEvent(otherHand, ev);
end; // addEvent_openchat
function TchatFrm.AddEvent(c: TICQContact; ev: Thevent): Boolean;
// tells if ev has been inserted in a list, or can be freed
var
ch: TchatInfo;
begin
Result := False;
ch := chats.byContact(c);
if Assigned(ch) then
begin
Result := True;
ChatBox.AddEvent(c.UID, ev);
end;
FreeAndNil(ev);
end; // AddEvent
function TchatFrm.OpenChat(otherHand: TICQContact; ForceActive: Boolean = False; isAuto: Boolean = False): Boolean;
const
MaxNILpages = 101;
var
i, k: Integer;
wasEmpty, alreadyThere: boolean;
cnt: TICQContact;
firstNILpage, NILcount: Integer;
begin
wasEmpty := chats.Count = 0;
i := chats.idxOf(otherHand);
Result := i < 0;
if Result then
i := NewIMChannel(otherHand);
alreadyThere := otherHand.equals(ChatBox.CurrentContact);
if wasEmpty then
begin
if i >= 0 then
setCurrent(i);
// if docking.Docked2chat then
// applyDocking;
end
else
begin
if not alreadyThere and ForceActive then
if i >= 0 then
setCurrent(i)
else
setCurrent(chats.idxOf(otherHand));
if isAuto then
begin // protection against bruteforce
firstNILpage := -1;
NILcount := 0;
for k := 0 to chats.count - 1 do
begin
if chats.byIdx(k).chatType = CT_IM then
begin
cnt := chats.byIdx(k).who;
if Assigned(cnt) and notInList.exists(cnt) then
begin
Inc(NILcount);
if firstNILpage < 0 then
firstNILpage := k;
end;
end;
end;
if (firstNILpage >= 0) and (NILcount > MaxNILpages) then
ClosePageAt(firstNILpage);
end;
end;
if ForceActive and not Visible then
Visible := True;
end;
function TchatFrm.IsChatOpen(otherHand: TICQContact): boolean;
begin
Result := chats.idxOf(otherHand) >= 0
end;
procedure TchatFrm.ApplyFormXY;
begin
with chatfrmXY do
if Width > 0 then
if maximized then
begin
SetBounds(Left, Top, Width, Height);
WindowState := wsMaximized;
end
else
begin
SetBounds(Left, Top, Width, Height);
WindowState := wsNormal
end;
end; // ApplyFormXY
procedure TchatFrm.InitDocument;
begin
if Initialized then
Exit;
chats := Tchats.Create;
plugBtns := TPlugButtons.Create;
DraggingTab := False;
DragAcceptFiles(self.handle, true);
ApplyFormXY;
applyTaskButton(self);
TabsIconCache := TDictionary.Create;
EmbeddedImgs := TDictionary.Create;
UploadCallbacks := TCallbacks.Create;
UploadCallbacks.OnBeforeHeaderSend := OnBeforeHeaderSend;
UploadCallbacks.OnSendData := OnSendData;
ChatBox := TChatBox.Create(Self);
with ChatBox do
begin
Enabled := False;
Align := alClient;
OnDragOver := chatDragOver;
OnDragDrop := chatDragDrop;
Load;
end;
theme.smileNotify := ChatBox.ReloadSmiles;
if EnableStickers then
begin
ChatBox.PreloadPickers;
ChatBox.LoadStickers;
end;
CreateThreading;
Initialized := True;
end;
procedure TchatFrm.InitSpellCheck;
begin
DoInitSpellCheck;
end;
procedure TchatFrm.SpellCheck;
begin
DoSpellCheck;
end;
procedure TchatFrm.SetupChatButtons;
begin
ChatBox.SetupChatButtons;
end;
procedure TchatFrm.SetupStickersButton;
begin
ChatBox.SetupStickersBtn(EnableStickers);
end;
procedure TchatFrm.SetCurrent(idx: Integer);
var
ch: TchatInfo;
begin
if idx < 0 then
Exit;
ch := chats.byIdx(idx);
if ch.chatType = CT_PLUGING then
begin
//OutputDebugString(PChar('setting tab to plugin ' + IntToStr(ch.ID)));
ChatBox.SwitchToPage(ch.ID);
end
else
begin
//OutputDebugString(PChar('setting tab to chat with ' + String(ch.who.UID)));
ChatBox.SwitchToPage(chats.byIdx(idx).who.UID);
end;
end; // SetCurrent
procedure TchatFrm.UserChanged(c: TICQContact);
var
i: Integer;
ch: TchatInfo;
begin
if c = nil then
Exit;
ch := thisChat;
if ch = nil then
Exit;
i := chats.idxOf(c);
if i < 0 then
Exit;
UpdateContactStatus(c);
RefreshTaskbarButtons;
end; // UserChanged
function TchatFrm.OpenOn(c: TICQContact; focus: Boolean = True; pShow: Boolean = True): Boolean;
var
i: Integer;
begin
Result := False;
if c = nil then
Exit;
i := chats.idxOf(c);
if i < 0 then
begin
Result := True;
i := NewIMChannel(c);
end;
if i >= 0 then
SetCurrent(i);
if pShow then
Open(focus);
end; // openOn
function TchatFrm.NewIMChannel(c: TICQContact): Integer;
var
chat: TchatInfo;
begin
chat := TchatInfo.Create;
chat.who := c;
chat.chatType := CT_IM;
chat.single := singleDefault;
chat.who.typing.bIAmTyping := false;
chats.Add(chat);
Result := chats.Count - 1;
ChatBox.OpenPage(c);
// ChatBox.CheckServerHistory(c);
UpdateAvatar(c);
Resize;
saveListsDelayed := true;
RedrawTab(c);
RefreshTaskbarButtons;
end; // NewIMChannel
procedure TchatFrm.UpdateAvatar(c: TICQContact);
var
ci: TchatInfo;
i: Integer;
begin
ci := nil;
i := chats.idxOf(c);
if i >= 0 then
begin
ci := chats.byIdx(i);
if Assigned(ci) and Assigned(ci.who) then
begin
// if (c.icon.ToShow = IS_NONE) or ((c.icon.ToShow < IS_NONE) and (not Assigned(c.icon.bmp) or (not c.icon.IsBmp and (c.icon_Path = '')))) then
if c.icon.ToShow = IS_NONE then
ChatBox.ClearAvatar(ci.who.UID)
else
ChatBox.UpdateAvatar(ci.who.UID);
end;
end;
end;
procedure TchatInfo.CheckTypingTime;
begin
try
if (chatType = CT_IM) and Assigned(who) then
if (who.typing.bIAmTyping) and ((Now - who.typing.TypingTime) * SecsPerDay > TypingInterval) then
Account.AccProto.InputChangedFor(TICQContact(who), False, True);
except
end;
end;
function TchatFrm.thisChat: TchatInfo;
begin
if not Assigned(chats) or (chats.count = 0) then
Result := nil
else
Result := chats.byContact(ChatBox.CurrentContact);
end;
function TchatFrm.thisContact: TICQContact;
var
ch: TchatInfo;
begin
ch := thisChat;
if ch = nil then
Result := nil
else if ch.chatType = CT_IM then
thisContact := ch.who
else
Result := nil;
end; // thisContact
function TchatFrm.thisChatUID: TUID;
var
cnt: TICQContact;
begin
cnt := thisContact;
if Assigned(cnt) then
Result := cnt.UID2cmp
else
Result := '';
end;
function TchatFrm.pageIdxAt(X, Y: Integer): Integer;
var
R: TRect;
begin
Result := 0;
while Result < chats.count do
begin
// SendMessage(pagectrl.handle, TCM_GETITEMRECT, WPARAM(Result), LPARAM(@R));
if Types.ptInRect(R, Types.point(X, Y)) then
exit;
Inc(Result);
end;
Result := -1;
end; // pageIdxAt
procedure TchatFrm.CloseThisPage;
var
ch: TchatInfo;
begin
if DraggingTab then
Exit;
ch := thisChat;
if Assigned(ch) then
CloseChat(ch);
end;
procedure TchatFrm.UpdateGraphics(c: TICQContact = nil);
var
ch: TchatInfo;
begin
ch := thisChat;
if ch = nil then
Exit;
if ch.chatType = CT_PLUGING then
Exit;
if Assigned(c) and not ch.who.equals(c) then
Exit;
ChatBox.SetupSingleBtn(ch.single);
UpdateContactStatus;
UpdateStatusBar;
RefreshTaskbarButtons;
end; // UpdateGraphics
procedure TchatFrm.ApplyTheme;
begin
ChatBox.ApplyTheme;
end;
procedure TchatFrm.ResetHistory;
begin
ChatBox.ResetHistory;
end;
procedure TchatFrm.Close1Click(Sender: TObject);
begin
CloseThisPage
end;
procedure TchatFrm.Viewinfo1Click(Sender: TObject);
var
cnt: TICQContact;
begin
cnt := thisContact;
if Assigned(cnt) then
cnt.ViewInfo;
end;
function TchatFrm.SawAllHere: Boolean;
const
ClearEvents = [EK_msg, EK_auth, EK_authDenied, EK_addedYou];
var
c: TICQContact;
ch: TChatInfo;
k: Integer;
ev0: Thevent;
Found: Boolean;
begin
Result := False;
Found := False;
if eventQ.Count = 0 then
Exit;
ch := thisChat;
if ch = nil then
Exit;
if ch.chatType <> CT_IM then
Exit;
c := ch.who;
k := -1;
repeat
k := eventQ.getNextEventFor(c, k);
if (k >= 0) and (k < eventQ.count) then
begin
ev0 := eventQ.items[k];
if ev0.Kind in ClearEvents then
begin
Found := True;
eventQ.removeAt(k);
if BE_history in behaviour[ev0.Kind].trig then
if not chatFrm.HasEvent(c, ev0) then
chatFrm.AddEvent(c, ev0.clone);
try
FreeAndNil(ev0);
except end;
end else
Inc(k);
end else
k := -1;
until (k < 0);
if Found then
begin
Result := True;
roasterLib.UpdateInPlace(c);
SaveInboxDelayed := true;
end;
TipRemove(c);
end; // SawAllHere
procedure TchatFrm.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
ch: TchatInfo;
begin
ch := thisChat;
if ch = nil then
Exit;
if Shift = [] then
case Key of
VK_BROWSER_BACK:
ChatBox.SwitchToPrevPage;
VK_BROWSER_FORWARD:
ChatBox.SwitchToNextPage;
end
else if (Shift = [ssAlt]) and (ch.chatType = CT_IM) then
case Key of
VK_M:
begin
useSmiles := not useSmiles;
UpdateChatSettings;
UpdateChatSmiles;
end;
end
else if (Shift = [ssCtrl]) or (Shift = [ssCtrl, ssShift]) then
case Key of
VK_TAB:
begin
Key := 0;
if ssShift in Shift then
ChatBox.SwitchToPrevPage
else
ChatBox.SwitchToNextPage;
end;
end;
if (Shift = [ssAlt]) or (Shift = [ssAlt, ssCtrl]) then
case Key of
VK_LEFT:
ChatBox.SwitchToPrevPage;
VK_RIGHT:
ChatBox.SwitchToNextPage;
end
else if Shift = [ssCtrl] then
case Key of
VK_C:
if ch.chatType = CT_IM then
ChatBox.copySel2Clpb;
VK_F5:
ChatBox.SwitchToPrevPage;
VK_F6:
ChatBox.SwitchToNextPage;
VK_F4, VK_W:
try
SawAllHere;
CloseThisPage;
Key := 0;
// Shift := [];
Exit;
except end;
end;
if Assigned(ch) and (ch.chatType = CT_PLUGING) then
SendMessage(ch.ID, WM_KEYDOWN, WPARAM(Key), 0);
inherited;
end; // KeyDown
procedure TchatFrm.Open(focus: Boolean = True);
var
bak: Thandle;
begin
if chats.Count = 0 then
Exit;
if not Visible then
bak := GetForegroundWindow
else
bak := 0;
showForm(self);
if (bak > 0) and not focus then
ForceForegroundWindow(bak);
SetWindowPos(handle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE + SWP_NOSIZE); // bring it atop if it is not
if focus then
ForceForegroundWindow(handle);
ChatBox.SetFocus;
end; // open
function TchatFrm.IsVisible: Boolean;
begin
Result := Visible and (GetForegroundWindow = Handle);
end;
procedure TchatFrm.FormCreate(Sender: TObject);
begin
Initialized := False
end;
procedure TchatFrm.FormClose(Sender: TObject; var Action: TCloseAction);
begin
UpdateChatfrmXY;
TabsIconCache.Clear;
ChatBox.Enabled := False;
if chats.Count = 0 then
EmbeddedImgs.Clear;
end; // form close
procedure TchatFrm.UpdateContactStatus(cnt: TICQContact = nil);
begin
if cnt = nil then
cnt := thisContact;
with ChatBox do
begin
if cnt = nil then
begin
SetSendBtnImage(status2imgName(byte(SC_UNK), False));
Exit;
end;
SetSendBtnImage(rosterImgNameFor(cnt));
if (thisChat.chatType = CT_IM) and not (thisChat.who = nil) then
SetupBuzzBtn(thisChat.who.CanBuzz)
else
SetupBuzzBtn(False);
end;
RedrawTab(cnt);
end; // UpdateContactStatus
procedure TchatFrm.ClosePageAt(idx: Integer);
var
ch: TchatInfo;
cnt: TICQContact;
id: Integer;
ltype: TChatType;
begin
ch := chats.byIdx(idx);
if ch = nil then
Exit;
cnt := ch.who;
id := ch.ID;
ltype := ch.chatType;
ChatBox.LastContact := cnt;
if ltype = CT_PLUGING then
plugins.castEv(PE_CLOSETAB, ch.ID)
else if ltype = CT_IM then
Account.AccProto.InputChangedFor(TICQContact(ch.who), True);
try
chats.Items[idx] := nil;
chats.Delete(idx);
FreeAndNil(ch);
except end;
if ltype = CT_PLUGING then
ChatBox.ClosePage(id)
else if ltype = CT_IM then
ChatBox.ClosePage(cnt.UID);
if userTime > 0 then
saveListsDelayed := true;
RefreshTaskbarButtons;
end; // ClosePageAt
procedure TchatFrm.CloseChat(ci: TchatInfo);
begin
if ci = nil then
Exit;
if ci.chatType = CT_IM then
ClosePageAt(chats.idxOf(ci.who))
else
ClosePageAt(chats.idxOfID(ci.ID));
end;
procedure TchatFrm.CloseChatWith(c: TICQContact);
begin
ClosePageAt(chats.idxOf(c))
end;
procedure TchatFrm.CloseChatUID(const uid: TUID);
begin
ClosePageAt(chats.idxOfUIN(uid))
end;
procedure TchatFrm.CloseChatID(ID: Integer);
begin
ClosePageAt(chats.idxOfID(ID));
end;
procedure TchatFrm.FormShow(Sender: TObject);
begin
theme.pic2ico(RQteFormIcon, PIC_MSG, icon);
ApplyFormXY;
ChatBox.Enabled := True;
UpdateChatfrmPreview;
UpdateContactStatus;
end;
procedure TchatFrm.CloseAllPages(isAuto: boolean = false);
begin
if isAuto then
PagesEnumStr := Pages2String
else
PagesEnumStr := '';
while chats.Count > 0 do
ClosePageAt(0);
end; // CloseAllPages
procedure TchatFrm.RedrawTabs;
var
i: Integer;
ch: TchatInfo;
begin
for i := 0 to chats.Count - 1 do
begin
ch := chats.byIdx(i);
if Assigned(ch) and Assigned(ch.who) then
RedrawTab(ch.who);
end;
end;
procedure TchatFrm.RedrawTab(c: TICQContact);
var
ci: TchatInfo;
ev: Thevent;
tabPic, add: TPicName;
png, pngadd: TPNGImage;
hash, hashadd: LongWord;
mem: TMemoryStream;
begin
ci := chats.byContact(c);
if ci = nil then
Exit;
tabPic := '';
ev := eventQ.firstEventFor(c);
if Assigned(ev) and ((blinking or Account.AccProto.GetStatusDisable.blinking) or not blinkWithStatus) then
begin
//OutputDebugString(PChar('RedrawTab ' + String(ci.who.UID) + ': ' + booltostr(blinking, true) + ', ' + booltostr(c.fProto.getStatusDisable.blinking, true)));
if (blinking or Account.AccProto.GetStatusDisable.blinking) then
tabPic := ev.pic
else
tabPic := c.statusImg
end
else
begin
if c.typing.bIsTyping then
tabPic := PIC_TYPING
else if showStatusOnTabs then
tabPic := c.statusImg
else
tabPic := '';
end;
add := '';
if TICQContact(c).Official then
add := PIC_OFFICIAL;
hash := 0;
hashadd := 0;
if not (tabPic = '') then
begin
// Main icon
if theme.Pic2PNG(tabPic, png) then
if Assigned(png) then
begin
mem := TMemoryStream.Create;
try
png.SaveToStream(mem);
hash := CalcMurmur2(mem);
finally
FreeAndNil(mem);
end;
end;
if not (hash = 0) then
if TabsIconCache.ContainsKey(hash) then
FreeAndNil(png)
else if Assigned(png) then
TabsIconCache.AddOrSetValue(hash, png);
// Additional icon in the bottom right corner
if not (add = '') then
begin
if theme.Pic2PNG(add, pngadd) then
if Assigned(pngadd) then
begin
mem := TMemoryStream.Create;
try
pngadd.SaveToStream(mem);
hashadd := CalcMurmur2(mem);
finally
FreeAndNil(mem);
end;
end;
if not (hashadd = 0) then
if TabsIconCache.ContainsKey(hashadd) then
FreeAndNil(pngadd)
else if Assigned(pngadd) then
TabsIconCache.AddOrSetValue(hashadd, pngadd);
end;
end;
ChatBox.RedrawTab(c, hash, hashadd);
end;
procedure TchatFrm.RedrawPluginTab(ci: TchatInfo);
var
tabPic: TPicName;
png: TPNGImage;
hash: LongWord;
mem: TMemoryStream;
begin
if ci = nil then
Exit;
hash := 0;
tabPic := 'plugintab' + IntToStr(ci.ID);
if theme.Pic2PNG(tabPic, png) then
if Assigned(png) then
begin
mem := TMemoryStream.Create;
try
png.SaveToStream(mem);
hash := CalcMurmur2(mem);
finally
FreeAndNil(mem);
end;
end;
if not (hash = 0) then
if TabsIconCache.ContainsKey(hash) then
FreeAndNil(png)
else if Assigned(png) then
TabsIconCache.AddOrSetValue(hash, png);
ChatBox.RedrawTab(ci.ID, ci.lastInputText, hash);
end;
procedure TchatFrm.WndProc(var Message: TMessage);
begin
case Message.Msg of
WM_SYSCOMMAND:
UpdateChatfrmXY;
WM_MOUSEWHEEL, WM_VSCROLL:
if (Assigned(chats)) and not (thisChat = nil) and (thisChat.chatType = CT_IM) then
if Message.WParam shr 31 > 0 then
ChatBox.ScrollWheel(+1)
else
ChatBox.ScrollWheel(-1);
WM_KEYDOWN:
if (thisChat <> nil) and (thisChat.chatType = CT_PLUGING) then
SendMessage(thisChat.ID, Message.Msg, Message.WParam, Message.LParam);
WM_HELP:
Exit;
// WM_MOUSEHOVER:
// OutputDebugString(PChar('WM_MOUSEHOVER'));
// WM_MOUSELEAVE:
// OutputDebugString(PChar('WM_MOUSELEAVE'));
end;
inherited;
end;
function stripProtocol(const stringData: String): String;
begin
if StartsText('uin:', stringData) then
Result := copy(stringData, 5, length(stringData))
else if StartsText('link:', stringData) then
Result := copy(stringData, 6, length(stringData))
else if StartsText('mailto:', stringData) then
Result := copy(stringData, 8, length(stringData))
else
Result := stringData;
end;
procedure TchatFrm.chatDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: boolean);
begin
// Accept := Source = mainDlg.RnQmain.roster
end;
procedure TchatFrm.chatDragDrop(Sender, Source: TObject; X, Y: Integer);
var
cl: TRnQCList;
begin
if (clickedContact = nil) or (thisContact = nil) then
Exit;
cl := TRnQCList.Create;
cl.Add(clickedContact);
OutboxAdd(OE_contacts, thisContact, 0, cl);
cl.free;
end;
procedure TchatFrm.UpdateChatfrmXY;
begin
if not Visible then
Exit;
if WindowState <> wsMaximized then
begin
chatfrmXY.Top := Top;
chatfrmXY.Left := Left;
chatfrmXY.Height := Height;
chatfrmXY.Width := Width;
end;
if WindowState <> wsMinimized then
chatfrmXY.maximized := WindowState = wsMaximized;
end; // UpdatechatfrmXY
procedure TchatFrm.UpdateChatfrmPreview;
begin
TaskBar.PreviewClipRegion.Height := Min(200, ClientHeight);
TaskBar.PreviewClipRegion.Width := Min(420, ClientWidth);
TaskBar.ApplyClipAreaChanges;
end;
procedure TchatFrm.UpdateStatusBar;
begin
ChatBox.UpdateStatusBar;
end;
procedure TchatFrm.UpdateRelTimes;
begin
ChatBox.UpdateRelTimes;
end;
procedure TchatFrm.SetStatusbar(const s: String);
var
hint: String;
begin
if IsUploading then
hint := GetTranslation('Uploading file') + ': ' + IntToStr(Trunc(UploadedSize / UploadSize * 100)) + '%'
else
hint := s;
ChatBox.SetStatusbarHint(hint);
if CheckWin32Version(6, 1) then
with TaskBar do begin
if (UploadedSize < UploadSize) and IsUploading then
begin
ProgressState := TTaskBarProgressState.Normal;
ProgressValue := Trunc(UploadedSize / UploadSize * 100);
end
else
begin
ProgressState := TTaskBarProgressState.None;
ProgressValue := 0;
end;
ApplyProgressChanges;
end;
end;
procedure TchatFrm.MoveToEvent(c: TICQContact; ev: Thevent);
begin
ChatBox.MoveToTime(c.UID, ev.when);
end;
function TchatFrm.HasEvent(c: TICQContact; ev: Thevent): Boolean;
var
ch: TchatInfo;
tlast: TDateTime;
begin
Result := False;
ch := chats.byContact(c);
if not Assigned(ch) then
Exit;
if logpref.writehistory and (BE_save in behaviour[ev.kind].trig) then
Result := True
else
begin
tlast := ChatBox.GetLastEventTime(c.UID);
if tlast > 0 then
Result := CompareDateTime(tlast, ev.when) >= 0;
end;
end;
procedure TchatFrm.SetUnreadEvent(c: TICQContact; ev: Thevent);
var
ch: TchatInfo;
begin
ch := chats.byContact(c);
if not Assigned(ch) then
Exit;
ChatBox.SetFirstUnreadEvent(c.UID, ev.when);
end;
procedure TchatFrm.flash;
var
rec: FLASHWINFO;
begin
// if doFlashChat then
begin
rec.cbSize := sizeOf(rec);
rec.hwnd := handle;
rec.dwFlags := FLASHW_CAPTION OR FLASHW_TRAY OR FLASHW_TIMERNOFG;
rec.dwTimeout := 0;
rec.uCount := dword(-1);
flashWindowEx(rec);
end;
end; // flash
procedure TchatFrm.shake;
const
MAXDELTA = 8;
SHAKETIMES = 150;
var
Task: ITask;
oRect, wRect: TRect;
wHandle: HWND;
begin
wHandle := chatFrm.handle;
GetWindowRect(wHandle, wRect);
oRect := wRect;
Randomize;
Task := TTask.Create(procedure()
var
cnt: Integer;
begin
for cnt := 0 to SHAKETIMES do
begin
wRect := oRect;
Types.OffsetRect(wRect, Round(Random(2 * MAXDELTA) - MAXDELTA), 0);
MoveWindow(wHandle, wRect.Left, wRect.Top, wRect.Right - wRect.Left, wRect.Bottom - wRect.Top, True);
Sleep(10);
end;
MoveWindow(wHandle, oRect.Left, oRect.Top, oRect.Right - oRect.Left, oRect.Bottom - oRect.Top, True);
end, TThreadPool.Default);
Task.Start;
end;
function TchatFrm.Pages2String: RawByteString;
var
i: Integer;
ch: TchatInfo;
begin
if (userTime < 0) and (chats.count = 0) then
Result := PagesEnumStr
else
begin
Result := '';
for i := 0 to chats.Count - 1 do
try
ch := chats.byIdx(i);
if Assigned(ch) and (ch.chatType = CT_IM) then
Result := Result + UTF(ch.who.uid) + CRLF;
except end;
end;
end;
procedure TchatFrm.loadPages(const s: RawByteString);
var
i: Integer;
chats: TStringList;
begin
chats := TStringList.Create;
chats.Text := s;
for i := 0 to chats.Count - 1 do
openOn(Account.AccProto.GetContact(UnUTF(chats[i])));
open(true);
chats.Free;
end; // loadPages
procedure TchatFrm.loadPages(const cl: TRnQCList);
var
cnt: TICQContact;
begin
for cnt in cl do
openOn(cnt);
open(True);
end;
procedure TchatFrm.AvtsplitterMoving(Sender: TObject; var NewSize: Integer; var Accept: boolean);
begin
Accept := NewSize > 0;
end;
procedure TchatFrm.FormDestroy(Sender: TObject);
begin
FreeAndNil(plugBtns);
FreeAndNil(ChatBox);
FreeAndNil(chats);
FreeAndNil(TabsIconCache);
FreeAndNil(EmbeddedImgs);
FreeAndNil(UploadCallbacks);
ReleaseThreading;
end;
procedure TchatFrm.UpdateChatSettings;
begin
ChatBox.InitSettings;
end;
procedure TchatFrm.UpdateChatSmiles;
begin
ChatBox.UpdateSmiles;
end;
procedure TchatFrm.LoadChatStickers;
begin
ChatBox.LoadStickers;
end;
procedure TchatFrm.LoadSearchResults;
begin
ChatBox.LoadSearchResults;
end;
// ----------------------------------------------------------------------------------------------------------------------
function CHAT_TAB_ADD(Control: Integer; iIcon: HIcon; const TabCaption: string): Integer;
var
chat: TchatInfo;
i: Integer;
begin
i := chatFrm.chats.idxOfID(Control);
if chatFrm.chats.validIdx(i) then
begin
chatFrm.setCurrent(i);
chatFrm.Resize;
Result := -1;
Exit;
end;
with chatFrm do
begin
chat := TchatInfo.Create;
chat.who := nil;
chat.chatType := CT_PLUGING;
chat.single := singleDefault;
chat.lastInputText := TabCaption;
chats.Add(chat);
chat.ID := Control;
if not (iIcon = 0) then
theme.addHIco('plugintab' + IntToStr(chat.ID), iIcon, true);
ChatBox.OpenPage(chat.ID, TabCaption);
RedrawPluginTab(chat);
Resize;
saveListsDelayed := true;
RefreshTaskbarButtons;
Result := Integer(chatBox.Handle);
end;
end;
procedure CHAT_TAB_MODIFY(Control: Integer; iIcon: HIcon; const TabCaption: string);
var
chat: TchatInfo;
i: Integer;
begin
chat := nil;
for i := 0 to chatFrm.chats.count - 1 do
begin
if chatFrm.chats.byIdx(i).ID = Control then
begin
chat := chatFrm.chats.byIdx(i);
break;
end;
end;
if chat = nil then
Exit;
if not (iIcon = 0) then
theme.addHIco('plugintab' + IntToStr(chat.ID), iIcon, true);
// TchatInfo(chatFrm.chatTabs.Tabs[i].Data).ID = Control
// chatFrm.chatTabs.Tabs[i].Caption := TabCaption;
chat.lastInputText := TabCaption;
chatFrm.RedrawPluginTab(chat);
chatFrm.RefreshTaskbarButtons;
end;
procedure CHAT_TAB_DELETE(Control: Integer);
var
i: Integer;
begin
for i := 0 to chatFrm.chats.count - 1 do
begin
if chatFrm.chats.byIdx(i).ID = Control then
begin
chatFrm.ClosePageAt(i);
break;
end;
end;
end;
procedure TchatFrm.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
{ with Params do
begin
// Style := Style and (not WS_CAPTION);
// Style := Style and not WS_OVERLAPPEDWINDOW or WS_BORDER and (not WS_CAPTION);
// Style := (Style or WS_POPUP) and (not WS_DLGFRAME);
Style := Style or WS_SYSMENU;
ExStyle := ExStyle or WS_EX_APPWINDOW or WS_EX_NOPARENTNOTIFY;
end; }
end;
procedure TchatFrm.WMEXITSIZEMOVE(var Message: TMessage);
var
ch: TchatInfo;
begin
inherited;
ch := thisChat;
if ch = nil then
exit;
if ch.chatType = CT_PLUGING then
plugins.castEv(PE_SELECTTAB, ch.ID);
end;
procedure TchatFrm.closemenuPopup(Sender: TObject);
var
ch: TchatInfo;
begin
ch := thisChat;
if ch = nil then
exit;
// chatcloseignore1.visible:= ch.chatType <> CT_PLUGING;
// CloseallandAddtoIgnorelist1.visible:= ch.chatType <> CT_PLUGING;
end;
procedure TchatFrm.WMWINDOWPOSCHANGING(var msg: TWMWINDOWPOSCHANGING);
const
chkLeft = true;
chkRight = true;
chkTop = true;
chkBottom = true;
var
// rWorkArea: TRect;
rMainRect: TRect;
StickAt: Word;
begin
if Assigned(mainDlg.RnQmain) then
if mainDlg.RnQmain.visible then
begin
StickAt := 15; // StrToInt(edStickAt.Text);
rMainRect := mainDlg.RnQmain.BoundsRect;
// SystemParametersInfo
// (SPI_GETWORKAREA, 0, @rWorkArea, 0);
with msg.WindowPos^ do
begin
if chkLeft then
// if ABS(x - rWorkArea.Left) <= StickAt then begin
// x := rWorkArea.Left;
if (ABS(X - rMainRect.Right) <= StickAt) and (Y < rMainRect.Bottom) and (Y + cy > rMainRect.Top) then
X := rMainRect.Right;
if chkRight then
// if abs(x + cx - rWorkArea.Right) <= StickAt then begin
// x := rWorkArea.Right - cx;
if (ABS(X + cx - rMainRect.Left) <= StickAt) and (Y < rMainRect.Bottom) and (Y + cy > rMainRect.Top) then
X := rMainRect.Left - cx;
if chkTop then
if (ABS(Y - rMainRect.Bottom) <= StickAt) and (X < rMainRect.Right) and (X + cx > rMainRect.Left) then
Y := rMainRect.Bottom;
if chkBottom then
if (ABS(Y + cy - rMainRect.Top) <= StickAt) and (X < rMainRect.Right) and (X + cx > rMainRect.Left) then
Y := rMainRect.Top - cy;
end;
end;
inherited;
end;
procedure TchatFrm.WMAppCommand(var msg: TMessage);
begin
case GET_APPCOMMAND_LPARAM(msg.LParam) of
APPCOMMAND_BROWSER_BACKWARD:
begin
ChatBox.SwitchToPrevPage;
msg.Result := 1;
end;
APPCOMMAND_BROWSER_FORWARD:
begin
ChatBox.SwitchToNextPage;
msg.Result := 1;
end;
APPCOMMAND_FIND, APPCOMMAND_BROWSER_SEARCH:
begin
showForm(WF_SEARCH);
msg.Result := 1;
end;
end;
end;
procedure TchatFrm.WMNCActivate(var msg: TWMNCActivate);
begin
inherited;
if Running and HandleAllocated then
ApplyTitleColors(msg.Active);
ApplyTransparency;
end;
procedure TchatFrm.DragDropFiles(var Message: TWMDropFiles);
var
ch: TchatInfo;
cnt: TICQContact;
i, n: Integer;
ss: String;
buffer: array [0 .. 2047] of Char;
begin
ch := thisChat;
if (ch = nil) then
Exit;
if ch.chatType = CT_IM then
begin
cnt := ch.who;
if cnt = nil then
Exit;
if cnt is TICQContact then
begin
ss := '';
n := DragQueryFile(Message.Drop, Cardinal(-1), nil, 0);
for i := 0 to n - 1 do
begin
DragQueryFile(Message.Drop, i, @buffer, sizeOf(buffer));
if i > 0 then
ss := ss + ';';
ss := ss + buffer;
end;
DragFinish(message.Drop);
if n > 0 then
ChatBox.AddToCurrentInput(ChatBox.FileUpload(n > 1, ss));
ss := '';
end;
end;
end;
procedure TchatFrm.OnBeforeHeaderSend(Sender: TObject; const Method : String; Headers: TStrings);
begin
Headers.Add('Pragma: no-cache');
Headers.Add('Cache-Control: no-cache');
end;
procedure TchatFrm.OnSendData(Sender: TObject; Buffer: Pointer; Len: Integer);
begin
Inc(UploadedSize, Len);
if Assigned(chatFrm) and chatFrm.Visible then
SetStatusbar('');
end;
procedure TchatFrm.TaskBarThumbButtonClick(Sender: TObject; AButtonID: Integer);
begin
if AButtonID > chats.Count then
Exit;
SetCurrent(AButtonID);
Open(true);
end;
procedure TchatFrm.RefreshTaskbarButtons;
var
taskBtn: TThumbBarButton;
ci: TchatInfo;
hi: HICON;
i, cnt: Integer;
bmp: TRnQBitmap;
ev: Thevent;
begin
if not running then Exit;
if not CheckWin32Version(6, 1) then Exit;
if not Assigned(TaskBar.TaskBarButtons) then Exit;
cnt := Min(7, chats.Count);
TaskBar.TaskBarButtons.BeginUpdate;
for i := 0 to 6 do
begin
taskBtn := TaskBar.TaskBarButtons[i];
if Assigned(taskBtn.Icon) and taskBtn.Icon.HandleAllocated then
DestroyIcon(taskBtn.Icon.ReleaseHandle);
if (cnt = 0) or (i > cnt - 1) then
begin
taskBtn.Hint := '';
taskBtn.ButtonState := [TThumbButtonState.Hidden];
end
else
begin
taskBtn.ButtonState := [TThumbButtonState.Enabled, TThumbButtonState.DismissOnClick];
ci := TchatInfo(chats.byIdx(i));
if not Assigned(ci) then
Continue;
case ci.chatType of
CT_IM:
begin
if not (taskBtn.Hint = ci.who.displayed) then
taskBtn.Hint := ci.who.displayed;
ev := eventQ.firstEventFor(ci.who);
if (ev <> nil) then
begin
theme.pic2ico(RQteFormIcon, ev.pic, taskBtn.Icon);
Continue;
end;
end;
CT_PLUGING:
begin
if not (taskBtn.Hint = ci.lastInputText) then
taskBtn.Hint := ci.lastInputText;
theme.pic2ico(RQteFormIcon, 'plugintab' + IntToStr(ci.ID), taskBtn.Icon);
Continue;
end;
end;
if avatarShowInTaskBar and Assigned(ci.who.icon.Bmp) then
begin
if (ci.who.icon.Bmp.Animated) then
bmp := ci.who.icon.Bmp.CloneFrame(0)
else
bmp := ci.who.icon.Bmp.CloneAll;
StretchPic(bmp, 16, 16);
bmp.GetHICON(hi);
taskBtn.Icon.Handle := hi;
FreeAndNil(bmp);
end else
theme.pic2ico(RQteFormIcon, ci.who.statusImg, taskBtn.Icon);
end;
end;
TaskBar.TaskBarButtons.EndUpdate;
TaskBar.ApplyButtonsChanges;
end;
procedure TchatFrm.ApplyTitleColors(active: Boolean);
begin
if TOSVersion.Check(10) then
if Prevalence = 0 then
ChatBox.Call('setActiveStyle', [GetRValue(clWhite), GetGValue(clWhite), GetBValue(clWhite)])
else if active then
ChatBox.Call('setActiveStyle', [GetRValue(AccentColor), GetGValue(AccentColor), GetBValue(AccentColor)])
else
ChatBox.Call('setActiveStyle', [GetRValue(AccentColorInactive), GetGValue(AccentColorInactive), GetBValue(AccentColorInactive)])
end;
procedure TchatFrm.CMMouseEnter(var Msg: TMessage);
begin
if Pointer(Msg.LParam) = Pointer(ChatBox) then
if not FMouseInControl and transparency.chgOnMouse then
begin
if AlphaBlend then
AlphaBlendValue := transparency.active;
FMouseInControl := True;
end;
end;
procedure TchatFrm.CMMouseLeave(var Msg: TMessage);
begin
inherited;
if Pointer(Msg.LParam) = Pointer(ChatBox) then
if (transparency.chgOnMouse) and FMouseInControl then
begin
if AlphaBlend then
if Handle <> getForegroundWindow then
AlphaBlendValue := transparency.inactive;
FMouseInControl := False;
end;
end;
end.