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/roasterLib.pas

1855 lines
50 KiB
Plaintext

{
This file is part of R&Q.
Under same license
}
unit roasterLib;
{$I RnQConfig.inc}
interface
uses
classes, windows, stdctrls, graphics, VirtualTrees,
chatDlg, RDGlobal, RnQPics,
ThemesLib, RnQProtocol;
{$I NoRTTI.inc}
type
{$IFDEF CHECK_INVIS}
Tdivisor = (d_online, d_invis, d_offline, d_contacts, d_nil);
{$ELSE}
Tdivisor = (d_online, d_offline, d_contacts, d_nil);
{$ENDIF}
const
{$IFDEF CHECK_INVIS}
divsWithGroups = [d_online, d_contacts, d_offline, d_invis];
divisor2str: array [Tdivisor] of AnsiString = ('online', 'invisible', 'offline', 'contacts', 'not in list');
divisor2ShowStr: array [Tdivisor] of string = ('Online', 'Invisible', 'Offline', 'Contacts', 'Not in list');
{$ELSE}
divsWithGroups = [d_online, d_contacts, d_offline];
divisor2str: array [Tdivisor] of AnsiString = ('online', 'offline', 'contacts', 'not in list');
divisor2ShowStr: array [Tdivisor] of string = ('Online', 'Offline', 'Contacts', 'Not in list');
{$ENDIF}
NODE_ROOT = 0;
NODE_DIV = 1;
NODE_GROUP = 2;
NODE_CONTACT = 3;
type
TRnQCLIconsSet = (CNT_ICON_VIS, CNT_ICON_STS, CNT_ICON_XSTS, CNT_ICON_AUTH, CNT_ICON_LCL, CNT_TEXT, CNT_ICON_BIRTH,
CNT_ICON_AVT, CNT_ICON_VER);
TRnQCLIcons = record
// IDX : Byte;
idx: TRnQCLIconsSet;
Name: String;
IconName: TPicName;
PrefText: AnsiString;
// Cptn : String;
// DefShortCut : String;
// ev : procedure;
end;
const
// RnQCLIcons : array[0..6] of TRnQCLIcons = (
RnQCLIcons: array [TRnQCLIconsSet] of TRnQCLIcons = ((idx: CNT_ICON_VIS; Name: 'I''m visible to'; IconName: PIC_VISIBLE_TO;
PrefText: 'visibility-flag'), (idx: CNT_ICON_STS; Name: 'Status'; IconName: 'status.online'; PrefText: 'show-status'),
{$IFDEF RNQ_FULL}
(idx: CNT_ICON_XSTS; Name: 'XStatus'; IconName: 'st_custom.cigarette'; PrefText: 'show-xstatus-flag'),
{$ENDIF RNQ_FULL}
(idx: CNT_ICON_AUTH; Name: 'Not authorized'; IconName: PIC_AUTH_NEED; PrefText: 'show-need-auth-flag'), (idx: CNT_ICON_LCL;
Name: 'Is local'; IconName: PIC_LOCAL; PrefText: 'show-is-local-flag'), (idx: CNT_TEXT; Name: 'Displayed'; IconName: PIC_INFO;
PrefText: 'text'), (idx: CNT_ICON_BIRTH; Name: 'Birthday baloon'; IconName: PIC_BIRTH; PrefText: 'show-birth-day-flag'),
(idx: CNT_ICON_AVT; Name: 'Avatar'; IconName: 'avatar'; PrefText: 'show-avatar-flag'), (idx: CNT_ICON_VER; Name: 'IM icon';
IconName: PIC_RNQ; PrefText: 'show-client-flag'));
var
SHOW_ICONS_ORDER: array [0 .. Byte(High(TRnQCLIconsSet))] of TRnQCLIconsSet;
TO_SHOW_ICON: array [TRnQCLIconsSet] of boolean;
type
PNode = ^Tnode;
Tnode = class
public
kind: integer;
contact: TRnQcontact;
divisor: Tdivisor;
groupId: integer;
textOfs: integer;
outboxRect: Trect;
treenode: PVirtualNode;
order: integer;
constructor create(divisor_: Tdivisor); overload;
constructor create(groupId_: integer; divisor_: Tdivisor); overload;
constructor create(contact_: TRnQcontact); overload;
destructor Destroy; override;
procedure setExpanded(val: boolean; recur: boolean = FALSE);
function isVisible: boolean;
function parent: Tnode;
function childrenCount: integer;
function firstChild: Tnode;
function expanded: boolean;
function level: integer;
function rect: Trect;
function next: Tnode;
function prev: Tnode;
end;
function contactsUnder(n: Tnode): integer;
procedure updateHiddenNodes;
procedure popup(x: integer = -1; y: integer = -1);
procedure popupOn(n: Tnode; x: integer = -1; y: integer = -1);
function compareNodes(Node1, Node2: Tnode): integer;
function str2divisor(const s: AnsiString): Tdivisor;
procedure rebuild;
procedure redraw; overload;
function redraw(c: TRnQcontact): boolean; overload;
function redraw(n: Tnode): boolean; overload;
function update(c: TRnQcontact): boolean;
// function exists(c:TRnQContact):boolean;
function focus(c: TRnQcontact): boolean; overload;
function focus(n: Tnode): boolean; overload;
function focus(tn: PVirtualNode): boolean; overload;
function focusTemp(n: Tnode): boolean;
// function remove(c:Tcontact):boolean;
function remove(c: TRnQcontact): boolean;
procedure sort(c: TRnQcontact); overload;
procedure sort(n: Tnode); overload;
procedure sort(tn: PVirtualNode); overload;
procedure formresized;
function onlineMaxY: integer;
function fullMaxY: integer;
function addGroup(const Name: string): integer;
function removeGroup(id: integer): boolean;
procedure edit(n: Tnode);
function focused: Tnode;
function focusedContact: TRnQcontact;
procedure expand(n: Tnode);
procedure collapse(n: Tnode);
function nodeAt(x, y: integer): Tnode;
procedure focusPrevious;
procedure clear;
procedure setOnlyOnline(v: boolean);
function getNode(tn: PVirtualNode): Tnode;
procedure setNewGroupFor(c: TRnQcontact; grp: integer);
function isUnderDiv(n: Tnode): Tdivisor;
// procedure filter(s : string);
procedure RstrDrawNode(Sender: TBaseVirtualTree; const PaintInfo: TVTPaintInfo);
function ICON_ORDER_PREF: RawByteString;
procedure ICON_ORDER_PREF_parse(const str: RawByteString);
var
building: boolean = FALSE;
dragging: boolean = FALSE; // roster.dragging doesn't work
inplace: record edit: Tedit;
what: integer;
contact: TRnQcontact;
groupId: integer;
node: Tnode;
end;
contactsPool:
Tlist;
expandedByTempFocus:
Tnode;
FilterTextBy:
string;
{$IFDEF USE_SECUREIM}
useSecureIM:
boolean;
{$ENDIF USE_SECUREIM}
implementation
uses
{UxTheme, DwmApi,} Types, UITypes,
RnQGraphics32, RQUtil, RQThemes,
RnQStrings, RnQLangs, RDUtils, RnQSysUtils,
mainDlg, sysutils, utilLib, globalLib, groupsLib,
// ICQConsts,
ICQv9, ICQContacts, ICQconsts,
events,
{$IFDEF USE_SECUREIM}
cryptoppWrap,
{$ENDIF USE_SECUREIM}
// masks,
StrUtils;
var
// declared globally to speedup the compare callback functions
divs: array [Tdivisor] of Tnode;
buildingOnline: boolean;
function Filtered(c: TRnQcontact): boolean;
begin
if FilterTextBy = '' then
Result := FALSE
else if c = nil then
Result := True
else if (Pos(FilterTextBy, AnsiUpperCase(c.UID)) = 0) and (Pos(FilterTextBy, AnsiUpperCase(c.display)) = 0) and
(Pos(FilterTextBy, AnsiUpperCase(c.nick)) = 0) and (Pos(FilterTextBy, AnsiUpperCase(c.first)) = 0) and
(Pos(FilterTextBy, AnsiUpperCase(c.last)) = 0)
// and(Pos(FilterTextBy, AnsiUpperCase(c.email)) = 0)
then
{
if (not MatchesMask(c.UID, FilterTextBy)) and
(not MatchesMask(c.display, FilterTextBy)) and
(not MatchesMask(c.nick, FilterTextBy)) and
(not MatchesMask(c.first, FilterTextBy)) and
(not MatchesMask(c.last, FilterTextBy)) and
(not MatchesMask(c.email, FilterTextBy)) then
}
Result := True
else
Result := FALSE;
end;
function getNode(tn: PVirtualNode): Tnode;
begin
if tn = NIL then
Result := NIL
else
begin
Result := RnQmain.roster.getnodedata(tn);
if Result <> NIL then
Result := Tnode(pointer(Result)^);
end;
end; // getNode
function str2divisor(const s: AnsiString): Tdivisor;
begin
for Result := low(Result) to high(Result) do
if s = divisor2str[Result] then
exit;
raise Exception.create('str2divisor');
end; // str2divisor
procedure updateHiddenNode(n: Tnode);
begin
if (n = NIL) or (n.treenode = NIL) then
exit;
RnQmain.roster.isVisible[n.treenode] := not showOnlyImVisibleTo or n.contact.imVisibleTo;
end; // updateHiddenNode
procedure updateHiddenNodes;
var
i: integer;
begin
if (divs[d_offline] <> NIL) then
RnQmain.roster.isVisible[divs[d_offline].treenode] := not showOnlyOnline;
{$IFDEF CHECK_INVIS}
if (divs[d_invis] <> NIL) then
RnQmain.roster.isVisible[divs[d_invis].treenode] := divs[d_invis].treenode.childcount > 0;
{$ENDIF}
for i := 0 to contactsPool.count - 1 do
updateHiddenNode(contactsPool[i]);
end; // updateHiddenNodes
function compareContacts(c1, c2: TRnQcontact): integer;
var
tmpB1, tmpB2: boolean;
tmpT1, tmpT2: Tdatetime;
begin
Result := 0;
if (c1 = c2) or (c1 = NIL) or (c2 = NIL) then
exit;
if OnlOfflInOne then
begin
{ tmpB1:= not c1.isOnline;
tmpB2:= not c2.isOnline;
if tmpb1 then
Inc(result); //:=+1
if tmpb2 then
dec(result); //:=-1 }
if not c1.isOnline then
Inc(Result); // :=+1
if not c2.isOnline then
dec(Result); // :=-1}
end;
if Result <> 0 then
exit;
case sortBy of
SB_alpha:
Result := compareText(c1.displayed, c2.displayed);
SB_event:
begin
tmpT1 := TCE(c1.data^).lastMsgTime;
tmpT2 := TCE(c2.data^).lastMsgTime;
tmpB1 := True;
if (tmpT1 < startTime) and (tmpT2 < startTime) then
if not(c1.isOnline or c2.isOnline) then
begin
tmpT1 := TCE(c1.data^).lastEventTime;
tmpT2 := TCE(c2.data^).lastEventTime;
if (tmpT1 < startTime) and (tmpT2 < startTime) then
tmpB1 := FALSE
end
else
tmpB1 := FALSE;
if not tmpB1 then
Result := compareText(c1.displayed, c2.displayed)
else if tmpT1 > tmpT2 then
Result := -1
else if tmpT1 < tmpT2 then
Result := +1;
end;
SB_STATUS:
begin
if c1.fProto = c2.fProto then
begin
Result := c1.fProto.compareStatusFor(c1, c2);
if Result = 0 then
Result := compareText(c1.displayed, c2.displayed);
end
else
Result := compareInt(cardinal(c1.fProto), cardinal(c2.fProto));
end;
else
begin
tmpB1 := c1.imVisibleTo;
tmpB2 := c2.imVisibleTo;
if tmpB1 and not tmpB2 then
Result := -1
else if tmpB2 and not tmpB1 then
Result := +1
else
Result := compareText(c1.displayed, c2.displayed);
end;
end;
end; // compareContacts
// this sort criteria is deeper than compareContacts one, sit has to think about groups and online status
function compareContacts4build(item1, item2: pointer): integer;
var
tmpC1, tmpC2: TRnQcontact;
t1, t2: Byte;
tmpI1, tmpI2: integer;
begin
Result := 0;
if (item1 = item2) or (item1 = NIL) or (item2 = NIL) then
exit;
tmpC1 := TRnQcontact(item1);
tmpC2 := TRnQcontact(item2);
if tmpC1.isOnline then
t1 := 3
else if tmpC1.isOffline then
t1 := 2
else
t1 := 1;
if tmpC2.isOnline then
t2 := 3
else if tmpC2.isOffline then
t2 := 2
else
t2 := 1;
// different sections?
if buildingOnline then
if t1 <> t2 then
if t2 > t1 then
Result := +1
else
Result := -1;
{ case tmpC1.status of
SC_UNK:
case tmpC2.status of
SC_OFFLINE: result:=+1;
SC_UNK: ;
else result:=+1;
end;
SC_OFFLINE:
case tmpC2.status of
SC_OFFLINE: ;
SC_UNK: result:=-1;
else result:=+1;
end;
else
case tmpC2.status of
SC_OFFLINE: result:=-1;
SC_UNK: result:=-1;
end;
end; }
{ case t1 of
1: //SC_UNK:
case t2 of
1: ;
2: result:=+1;
else result:=+1;
end;
2: //SC_OFFLINE:
case t2 of
1: result:=-1;
2: ;
else result:=+1;
end;
else
case t2 of
1: result:=-1;
2: result:=-1;
end;
end;
}
if Result <> 0 then
exit;
// different groups?
if showGroups and (tmpC1.group <> tmpC2.group) then
begin
if tmpC1.group = 0 then
Result := -1
else if tmpC2.group = 0 then
Result := +1
else
begin
tmpI1 := groups.get(tmpC1.group).order;
tmpI2 := groups.get(tmpC2.group).order;
if tmpI1 = tmpI2 then
Result := compareText(groups.id2name(tmpC1.group), groups.id2name(tmpC2.group))
else if tmpI1 < tmpI2 then
Result := -1
else
Result := +1;
end;
exit;
end;
Result := compareContacts(tmpC1, tmpC2);
end; // compareContacts4build
function compareNodes(Node1, Node2: Tnode): integer;
var
l1, l2: integer;
begin
Result := 0;
if (Node1 = NIL) or (Node2 = NIL) or (Node1 = Node2) then
exit;
l1 := Node1.level;
l2 := Node2.level;
if l1 < l2 then
Result := -1
else if l1 > l2 then
Result := +1;
if Result <> 0 then
exit;
if (Node1.kind = Node2.kind) and (Node1.order <> Node2.order) then
begin
if Node1.order < Node2.order then
Result := -1
else
Result := +1;
exit;
end;
case Node1.kind of
NODE_DIV:
if Node2.kind <> NODE_DIV then
Result := -1
else
Result := compareInt(ord(Node1.divisor), ord(Node2.divisor));
NODE_GROUP:
case Node2.kind of
NODE_DIV:
Result := +1;
NODE_CONTACT:
Result := +1;
NODE_GROUP:
Result := compareText(groups.id2name(Node1.groupId), groups.id2name(Node2.groupId));
end;
NODE_CONTACT:
case Node2.kind of
NODE_DIV:
Result := +1;
NODE_GROUP:
Result := -1;
NODE_CONTACT:
Result := compareContacts(Node1.contact, Node2.contact);
end;
end;
end; // compareNodes
constructor Tnode.create(divisor_: Tdivisor);
begin
inherited create;
kind := NODE_DIV;
divisor := divisor_;
end; // create
constructor Tnode.create(groupId_: integer; divisor_: Tdivisor);
begin
inherited create;
kind := NODE_GROUP;
groupId := groupId_;
divisor := divisor_;
end; // create
constructor Tnode.create(contact_: TRnQcontact);
begin
inherited create;
kind := NODE_CONTACT;
contact := contact_;
groupId := contact_.group;
TCE(contact_.data^).node := self;
contactsPool.add(self);
end; // create
destructor Tnode.Destroy;
begin
case kind of
NODE_CONTACT:
begin
contactsPool.remove(self);
TCE(contact.data^).node := NIL;
end;
NODE_GROUP:
groups.a[groups.idxOf(groupId)].node[divisor] := NIL;
NODE_DIV:
divs[divisor] := NIL;
end;
inherited;
end; // destroy
function Tnode.parent: Tnode;
begin
if treenode.parent = NIL then
Result := NIL
else
Result := getNode(treenode.parent)
end;
function Tnode.childrenCount: integer;
begin
Result := treenode.childcount
end;
function Tnode.firstChild: Tnode;
begin
Result := getNode(treenode.firstChild)
end;
function Tnode.next: Tnode;
begin
Result := getNode(treenode.NextSibling)
end;
function Tnode.prev: Tnode;
begin
Result := getNode(treenode.PrevSibling)
end;
function Tnode.expanded: boolean;
begin
Result := vsExpanded in treenode.states
end;
function Tnode.level: integer;
begin
Result := RnQmain.roster.getnodelevel(treenode)
end;
function Tnode.isVisible: boolean;
var
n, root: PVirtualNode;
begin
n := treenode;
root := RnQmain.roster.rootnode;
Result := vsVisible in n.states;
repeat
n := n.parent;
if n <> root then
Result := (vsVisible in n.states) and (vsExpanded in n.states);
until not Result or (n = root);
end; // isVisible
procedure Tnode.setExpanded(val: boolean; recur: boolean = FALSE);
begin
if not recur then
RnQmain.roster.expanded[treenode] := val
else if val then
RnQmain.roster.FullExpand(treenode)
else
RnQmain.roster.FullCollapse(treenode);
end; // setexpanded
function Tnode.rect: Trect;
begin
Result := RnQmain.roster.GetDisplayRect(treenode, -1, FALSE)
end;
/// //////////////////////////////////////////////////////////
function insertNode(d: Tdivisor): Tnode; overload;
var
a: Tnode;
begin
Result := NIL;
if not(d in [Low(Tdivisor) .. High(Tdivisor)]) then
exit;
Result := divs[d];
if assigned(Result) then
exit;
a := Tnode.create(d);
Result := a;
Result.treenode := RnQmain.roster.addchild(NIL, Result);
divs[d] := Result;
if not building then
sort(Result);
end; // insertNode
function insertNode(id: integer; d: Tdivisor): Tnode; overload;
var
idx: integer;
begin
idx := groups.idxOf(id);
Result := groups.a[idx].node[d];
if assigned(Result) then
exit;
insertNode(d); // ensure divisor existence
Result := Tnode.create(id, d);
Result.order := groups.a[idx].order;
Result.treenode := RnQmain.roster.addchild(divs[d].treenode, Result);
groups.a[idx].node[d] := Result;
if not building then
sort(Result);
end; // insertNode
function shouldBeUnder(c: TRnQcontact; d: Tdivisor): Tnode;
begin
Result := insertNode(d);
if (c.group = 0) or not showGroups or not(d in divsWithGroups) then
exit;
if not groups.exists(c.group) then
c.group := 0
else
Result := insertNode(c.group, d);
end; // shouldBeUnder
function insertNode(c: TRnQcontact; under: Tnode): Tnode; overload;
procedure checkExpansion;
var
n: Tnode;
d: Tdivisor;
begin
n := Result.parent;
repeat
case n.kind of
NODE_GROUP:
begin
d := n.parent.divisor;
n.setExpanded(groups.a[groups.idxOf(n.groupId)].expanded[d]);
end;
NODE_DIV:
begin
n.setExpanded(True);
break;
end;
end;
n := n.parent;
until n = NIL;
end; // checkExpansion
begin
Result := Tnode.create(c);
Result.treenode := RnQmain.roster.addchild(under.treenode, Result);
// Result.groupId := under.groupId;
updateHiddenNode(Result);
if not building then
checkExpansion;
autosizeDelayed := True;
if not building then
sort(Result);
end; // insertNode
function insertNode(c: TRnQcontact; d: Tdivisor): Tnode; overload;
begin
Result := insertNode(c, shouldBeUnder(c, d))
end;
function GetContactDiv(c: TRnQcontact; isRoster: boolean = FALSE): Tdivisor;
begin
if (not isRoster) and notinlist.exists(c) then
Result := d_nil
else if (not isRoster) and not c.isInRoster then
Result := Tdivisor(13)
else if buildingOnline and not OnlOfflInOne then
{$IFDEF CHECK_INVIS}
if supportInvisCheck and (c.isInvisible) then
Result := d_invis
else
{$ENDIF}
if c.isOffline or (showUnkAsOffline and not c.isOnline) then
Result := d_offline
else
Result := d_online
else if buildingOnline and showOnlyOnline and not c.isOnline then
Result := d_offline
else
Result := d_contacts
end;
function insertNode(c: TRnQcontact): Tnode; overload;
var
d: Tdivisor;
begin
if Filtered(c) then
exit(nil);
d := GetContactDiv(c);
// if d in [d_online..d_nil] then
if d in [Low(Tdivisor) .. High(Tdivisor)] then
Result := insertNode(c, d)
else
Result := nil;
end; // insertNode
function removeNode(n: Tnode): boolean; overload;
var
parent: Tnode;
begin
Result := assigned(n);
if not Result then
exit;
while n.childrenCount > 0 do
removeNode(n.firstChild);
parent := n.parent;
RnQmain.roster.deleteNode(n.treenode);
n.free;
if parent = NIL then
exit;
if parent.childrenCount = 0 then
case parent.kind of
NODE_GROUP:
removeNode(parent);
NODE_DIV:
if parent.divisor in [d_nil, d_contacts] then
removeNode(parent);
end;
autosizeDelayed := True;
end; // removeNode
function removeNode(c: TRnQcontact): boolean; overload;
var
n: Tnode;
tn: PVirtualNode;
begin
n := TCE(c.data^).node;
if assigned(n) and (n.treenode <> NIL) then
if RnQmain.roster.isVisible[n.treenode] and (RnQmain.roster.FocusedNode = n.treenode) then
begin
tn := RnQmain.roster.GetNextVisibleSibling(n.treenode);
if tn <> NIL then
RnQmain.roster.FocusedNode := tn
else
begin
tn := RnQmain.roster.GetPreviousVisibleSibling(n.treenode);
if tn <> NIL then
RnQmain.roster.FocusedNode := tn
end;
end;
Result := removeNode(TCE(c.data^).node);
if Result then
autosizeDelayed := True;
TCE(c.data^).node := NIL;
end;
procedure rebuild;
var
i: integer;
rosterList: TRnQCList;
d: Tdivisor;
c: TRnQcontact;
oldFocusedContact: TRnQcontact;
// oldtopnode : PVirtualNode;
begin
if building then
exit;
FilterTextBy := AnsiUpperCase(FilterTextBy);
oldFocusedContact := focusedContact;
// oldtopnode := RnQmain.roster.TopNode;
try
building := True;
// with RnQmain.roster.treeoptions do autooptions:=autoOptions-[toAutosort];
// reset
RnQmain.roster.BeginUpdate;
clear;
fillChar(divs, sizeOf(divs), 0);
if groups = NIL then
exit;
for i := 0 to groups.count - 1 do
with groups.a[i] do
fillChar(node, sizeOf(node), 0);
// roster section
rosterList := Account.AccProto.readList(LT_ROSTER).clone;
rosterList.sort(compareContacts4build);
buildingOnline := Account.AccProto.isOnline;
if buildingOnline and not OnlOfflInOne then
begin
insertNode(d_online);
{$IFDEF CHECK_INVIS}
insertNode(d_invis);
{$ENDIF}
insertNode(d_offline);
end;
rosterList.resetEnumeration;
while rosterList.hasMore do
begin
c := rosterList.getNext;
if Filtered(c) then
Continue;
d := GetContactDiv(c, True);
insertNode(c, d);
end;
rosterList.free;
// NIL section
if assigned(notinlist) then
with notinlist do
begin
resetEnumeration;
while hasMore do
begin
c := getNext;
if not Filtered(c) then
insertNode(c, d_nil);
end;
end;
// expands all divs
for d := low(d) to high(d) do
if assigned(divs[d]) then
begin
divs[d].setExpanded(True);
for i := 0 to groups.count - 1 do
with groups.a[i] do
if node[d] <> NIL then
node[d].setExpanded(expanded[d])
end;
updateHiddenNodes;
// with RnQmain.roster.treeoptions do autooptions:=autoOptions+[toAutosort];
autosizeDelayed := True;
finally
building := FALSE;
RnQmain.roster.EndUpdate;
// sort(RnQmain.roster.rootnode);
with RnQmain.roster do
begin
show;
// if RnQmain.visible then
// setFocus;
if oldFocusedContact <> NIL then
begin
focus(oldFocusedContact);
// topnode := oldtopnode;
end
else if totalcount > 0 then
begin
FocusedNode := rootnode.firstChild;
topnode := rootnode.firstChild;
end;
end;
end;
end; // rebuild
procedure redraw;
begin
RnQmain.roster.repaint
end;
function redraw(c: TRnQcontact): boolean;
begin
Result := FALSE;
if c = NIL then
exit;
try
Result := redraw(TCE(c.data^).node);
except
end;
updateHiddenNode(TCE(c.data^).node);
if chatFrm <> NIL then
begin
try
chatFrm.setCaptionFor(c);
chatFrm.redrawTab(c);
chatFrm.updateContactStatus;
except
end;
// updateContactStatus;
end;
end; // redraw
function redraw(n: Tnode): boolean;
var
r: Trect;
begin
Result := FALSE;
if (n = NIL) or (n.treenode = NIL) then
exit;
r := RnQmain.roster.GetDisplayRect(n.treenode, -1, FALSE);
InvalidateRect(RnQmain.roster.handle, @r, True);
end; // redraw
function update(c: TRnQcontact): boolean;
var
d: Tdivisor;
wasFocused: boolean;
n: Tnode;
begin
Result := FALSE;
if c = NIL then
exit;
wasFocused := c = focusedContact;
n := TCE(c.data^).node;
if assigned(n) and (isUnderDiv(n) = GetContactDiv(c)) then
begin
if n.groupId <> c.group then
begin
removeNode(c);
n := insertNode(c);
end;
end
else
begin
removeNode(c);
n := insertNode(c);
end;
if n = NIL then
exit;
d := isUnderDiv(n);
if assigned(divs[d]) then
divs[d].setExpanded(True);
sort(c);
if wasFocused then
focus(c);
end; // update
function remove(c: TRnQcontact): boolean;
begin
Result := FALSE;
if not assigned(c) then
exit;
with c.fProto do
begin
// Result := notInList.remove(c);
Result := removeContact(c) or Result;
// if isOnline then
begin
RemFromList(LT_VISIBLE, c);
readList(LT_VISIBLE).remove(c);
end;
end;
saveListsDelayed := True;
removeNode(c);
if chatFrm <> NIL then
chatFrm.userChanged(c);
end; // remove
function exists(c: TRnQcontact): boolean;
begin
Result := (c <> NIL) and (c.data <> NIL) and (TCE(c.data^).node <> NIL)
end;
function focus(tn: PVirtualNode): boolean; overload;
begin
Result := focus(getNode(tn))
end;
function focus(n: Tnode): boolean; overload;
begin
Result := assigned(n) and assigned(n.treenode);
if Result then
RnQmain.roster.FocusedNode := n.treenode
else
RnQmain.roster.FocusedNode := NIL;
clickedNode := n;
clickedContact := focusedContact;
// clickedContact := NIL;
// focusedCnt := focusedContact;
clickedGroup := -1;
if clickedNode = NIL then
exit;
if clickedNode.kind = NODE_GROUP then
clickedGroup := clickedNode.groupId;
end; // focus
function focus(c: TRnQcontact): boolean; overload;
begin
Result := (c <> NIL) and focus(TCE(c.data^).node)
end;
procedure formresized;
var
d: Tdivisor;
begin
if not RnQmain.visible then
exit;
for d := low(Tdivisor) to high(Tdivisor) do
redraw(divs[d]);
end;
procedure clear;
var
d: Tdivisor;
begin
RnQmain.roster.clear;
// RnQmain.roster.
while contactsPool.count > 0 do
begin
Tnode(contactsPool.last).free;
// contactsPool.last
end;
for d := low(Tdivisor) to high(Tdivisor) do
FreeAndNil(divs[d]);
// divs[d].Free;
end; // clear
function lowestVisibleNodeFrom(n: PVirtualNode): PVirtualNode;
begin
while RnQmain.roster.expanded[n] and (n.childcount > 0) do
n := RnQmain.roster.getLastVisibleChild(n);
Result := n;
end;
function nodeBottomSide(n: PVirtualNode): integer;
begin
if n <> nil then
Result := RnQmain.roster.GetDisplayRect(n, -1, FALSE).bottom - RnQmain.roster.GetDisplayRect
(RnQmain.roster.rootnode.firstChild, -1, FALSE).top
else
Result := 0;
end;
function onlineMaxY: integer;
begin
if divs[d_online] = NIL then
Result := -1
else
Result := nodeBottomSide(lowestVisibleNodeFrom(divs[d_online].treenode));
{$IFDEF CHECK_INVIS}
if divs[d_invis] = NIL then
exit // result:=-1
else if RnQmain.roster.isVisible[divs[d_invis].treenode] then
Result := nodeBottomSide(lowestVisibleNodeFrom(divs[d_invis].treenode));
{$ENDIF}
end; // onlineMaxY
function fullMaxY: integer;
begin
if RnQmain.roster.RootNodeCount = 0 then
Result := -1
else
Result := nodeBottomSide(lowestVisibleNodeFrom(RnQmain.roster.rootnode))
end;
function addGroup(const Name: string): integer;
var
d: Tdivisor;
first: Tnode;
begin
// create the new group
Result := groups.add;
groups.a[groups.idxOf(Result)].Name := name;
// add it to divisors, and focus on the first one
first := NIL;
for d := high(d) downto low(d) do
if (d in divsWithGroups) and assigned(divs[d]) then
first := insertNode(Result, d);
focus(first);
saveGroupsDelayed := True;
end; // addGroup
function removeGroup(id: integer): boolean;
var
cl: TRnQCList;
c: TRnQcontact;
d: Tdivisor;
begin
Result := groups.exists(id);
if not Result then
exit;
cl := Account.AccProto.readList(LT_ROSTER).clone;
cl.resetEnumeration;
while cl.hasMore do
begin
c := cl.getNext;
if c.group = id then
remove(c);
end;
cl.free;
with groups.a[groups.idxOf(id)] do
begin
for d := low(d) to high(d) do
removeNode(node[d]);
end;
groups.delete(id);
saveGroupsDelayed := True;
end; // removeGroup
procedure edit(n: Tnode);
begin
FreeAndNil(inplace.edit);
if (n = NIL) or (n.treenode = NIL) then
exit;
if not(n.kind in [NODE_GROUP, NODE_CONTACT]) then
exit;
if not formVisible(RnQmain) then
RnQmain.toggleVisible();
if (n.kind = NODE_CONTACT) and not n.contact.canEdit then
exit;
// if (n.kind = NODE_GROUP) and useSSI and
// not n.groupId then Exit;
if not formVisible(RnQmain) then
exit;
focus(n);
inplace.edit := Tedit.create(RnQmain.roster);
inplace.edit.hide;
inplace.edit.parentFont := FALSE;
inplace.edit.parent := RnQmain.roster;
inplace.edit.DoubleBuffered := True; // DwmCompositionEnabled;
inplace.node := n;
with n.rect do
inplace.edit.SetBounds(n.textOfs, top, right - n.textOfs - 1, bottom - top);
case n.kind of
NODE_CONTACT:
begin
inplace.what := NODE_CONTACT;
inplace.contact := n.contact;
inplace.edit.text := inplace.contact.displayed;
end;
NODE_GROUP:
begin
with inplace.edit.font do
style := style + [fsBold];
inplace.what := NODE_GROUP;
inplace.groupId := n.groupId;
inplace.edit.text := groups.id2name(n.groupId);
end;
end;
inplace.edit.show;
inplace.edit.SetFocus;
inplace.edit.onExit := RnQmain.roasterStopEditing;
inplace.edit.onKeyPress := RnQmain.roasterKeyEditing;
end; // edit
function focused: Tnode;
begin
with RnQmain.roster do
if FocusedNode = NIL then
Result := NIL
else
Result := getNode(FocusedNode)
end; // focused
function focusedContact: TRnQcontact;
var
n: Tnode;
begin
Result := NIL;
n := focused;
if (n <> NIL) and (n.kind = NODE_CONTACT) then
Result := n.contact
end; // focusedContact
procedure expand(n: Tnode);
begin
if n <> NIL then
n.setExpanded(True)
end;
procedure collapse(n: Tnode);
begin
if n <> NIL then
n.setExpanded(FALSE)
end;
function nodeAt(x, y: integer): Tnode;
var
n: PVirtualNode;
begin
with RnQmain.roster do
begin
n := getNodeAt(x, y);
if n = NIL then
Result := NIL
else
Result := getNode(n)
end;
end; // nodeAt
procedure focusPrevious;
begin
with RnQmain.roster do
FocusedNode := GetPreviousVisible(FocusedNode)
end;
procedure setOnlyOnline(v: boolean);
begin
showOnlyOnline := v;
saveCfgDelayed := True;
if OnlOfflInOne then
rebuild
else
updateHiddenNodes;
end; // setOnlyOnline
procedure sort(c: TRnQcontact);
begin
if c <> NIL then
sort(TCE(c.data^).node)
end;
procedure sort(n: Tnode);
begin
if n <> NIL then
sort(n.treenode.parent)
end;
procedure sort(tn: PVirtualNode);
begin
if tn <> NIL then
RnQmain.roster.sort(tn, 0, sdAscending, FALSE)
end;
procedure setNewGroupFor(c: TRnQcontact; grp: integer);
begin
c.group := grp;
c.fProto.updateGroupOf(c);
update(c);
dbUpdateDelayed := True;
end; // setNewGroupFor
function isUnderDiv(n: Tnode): Tdivisor;
begin
Result := d_contacts;
while n <> NIL do
if n.kind = NODE_DIV then
begin
Result := n.divisor;
exit;
end
else
n := n.parent;
enD; // isUnderDiv
function focusTemp(n: Tnode): boolean;
var
p: Tnode;
bak: boolean;
begin
bak := setRosterAnimation(FALSE);
if (expandedByTempFocus <> NIL) and (n <> expandedByTempFocus) then
begin
p := expandedByTempFocus.parent;
if p.kind = NODE_GROUP then
p.setExpanded(FALSE);
end;
if not n.isVisible then
expandedByTempFocus := n;
Result := focus(n);
setRosterAnimation(bak);
end; // focusTemp
procedure popup(x: integer = -1; y: integer = -1);
begin
popupOn(clickedNode)
end;
procedure popupOn(n: Tnode; x: integer = -1; y: integer = -1);
begin
if x < 0 then
begin
x := mousePos.x;
y := mousePos.y;
end;
if n = NIL then
RnQmain.divisorMenu.popup(x, y)
else
case n.kind of
NODE_GROUP:
RnQmain.groupMenu.popup(x, y);
NODE_CONTACT:
RnQmain.contactMenu.popup(x, y);
else
RnQmain.divisorMenu.popup(x, y);
end;
end; // popupOn
function contactsUnder(n: Tnode): integer;
begin
Result := 0;
n := n.firstChild;
while n <> NIL do
begin
if n.childrenCount > 0 then
Inc(Result, contactsUnder(n));
if n.kind = NODE_CONTACT then
Inc(Result);
n := n.next;
end;
end; // contactsUnder
procedure RstrDrawNode(Sender: TBaseVirtualTree; const PaintInfo: TVTPaintInfo);
var
n: Tnode;
// function DrawContactIcon(DC : THandle; pIcon : Byte; x, y : Integer; Cnt : TContact; pIsRight : boolean = false) : TSize;
// function DrawContactIcon(DC : THandle; pIcon : TRnQCLIconsSet; x, y : Integer;
function DrawContactIcon(DC: THandle; pIcon: TRnQCLIconsSet; p: TPoint; Cnt: TRnQcontact; pIsRight: boolean = FALSE): TSize;
var
// newX : Integer;
po: TRnQThemedElementDtls;
// s : String;
// rB:Trect;
rB: TGPRect;
ev: Thevent;
begin
Result.cx := 0;
Result.cy := 0;
po.ThemeToken := -1;
po.Element := RQteDefault;
po.pEnabled := True;
if pIcon in [CNT_ICON_VIS, CNT_ICON_AUTH, CNT_ICON_LCL] then
begin
po.picName := RnQCLIcons[pIcon].IconName;
if pIsRight then
with theme.GetPicSize(po) do
dec(p.x, cx);
end;
case pIcon of
CNT_ICON_VIS:
// if imVisibleTo(cnt) then
if (Cnt.imVisibleTo) then
Result := theme.drawPic(DC, p, po)
else if showVisAndLevelling and Cnt.fProto.isOnline then
Result := theme.GetPicSize(po);
CNT_ICON_STS:
begin
ev := eventQ.firstEventFor(Cnt);
Result.cx := 0;
if (ev = NIL) or (blinkWithStatus and not(blinking or Cnt.fProto.getStatusDisable.blinking)) then
begin
{$IFDEF CHECK_INVIS}
// if (c.invisibleState = 2)or(c.status <> SC_OFFLINE) then
{$ENDIF}
if notinlist.exists(Cnt) then
begin
Result := statusDrawExt(0, 0, 0, Byte(SC_UNK), FALSE, 0);
if pIsRight then
dec(p.x, Result.cx);
statusDrawExt(DC, p.x, p.y, Byte(SC_UNK), FALSE, 0)
end
else if Cnt is TICQcontact then
// if not(not XStatusAsMain and showXStatus and (c.xStatus>0) and (c.status = SC_ONLINE) ) then
begin
if pIsRight then
begin
Result := statusDrawExt(0, 0, 0, Byte(TICQcontact(Cnt).status), TICQcontact(Cnt).invisible,
TICQcontact(Cnt).xStatus);
dec(p.x, Result.cx);
end;
Result := statusDrawExt(DC, p.x, p.y, Byte(TICQcontact(Cnt).status), TICQcontact(Cnt).invisible,
TICQcontact(Cnt).xStatus);
end
// size:=theme.drawPic(cnv, x,y+1, rosterImgNameFor(c))
else
begin
if pIsRight then
begin
Result := theme.GetPicSize(RQteDefault, Cnt.statusImg);
dec(p.x, Result.cx);
end;
Result := theme.drawPic(DC, p.x, p.y, Cnt.statusImg)
end;
{$IFDEF CHECK_INVIS}
// else
// size.cx := 0
{$ENDIF}
end
else
begin
Result := ev.PicSize;
if pIsRight then
dec(p.x, Result.cx);
if blinking or Cnt.fProto.getStatusDisable.blinking then
ev.Draw(DC, p.x, p.y)
end;
end;
CNT_ICON_XSTS:
if (Cnt is TICQcontact) then
begin
if not XStatusAsMain and (TICQcontact(Cnt).xStatus > 0) then
begin
po.picName := XStatusArray[TICQcontact(Cnt).xStatus].picName;
if pIsRight then
with theme.GetPicSize(po) do
dec(p.x, cx);
Result := theme.drawPic(DC, p, po);
end;
end;
CNT_ICON_AUTH:
if (Cnt is TICQcontact) then
begin
if
{$IFDEF UseNotSSI}
// icq.useSSI and
(TicqSession(Cnt.iProto.ProtoElem).UseSSI) and
{$ENDIF UseNotSSI}
not Cnt.Authorized and not Cnt.CntIsLocal and (TICQcontact(Cnt).SSIID <> 0) then
Result := theme.drawPic(DC, p, po);
end
else if not Cnt.Authorized then
Result := theme.drawPic(DC, p, po);
CNT_ICON_LCL:
if (Cnt is TICQcontact) and
{$IFDEF UseNotSSI}
// icq.useSSI and
(TicqSession(Cnt.iProto.ProtoElem).UseSSI) and
{$ENDIF UseNotSSI}
(Cnt.CntIsLocal) then
Result := theme.drawPic(DC, p, po);
CNT_ICON_VER:
begin
// po.picName := cnt.ClientStr;
po.picName := Cnt.ClientPic;
if po.picName > '' then
begin
if pIsRight then
with theme.GetPicSize(po) do
// newX := x - cx
dec(p.x, cx);
// else
// newX := x;
Result := theme.drawPic(DC, p, po);
end;
end;
CNT_ICON_BIRTH:
begin
case Cnt.Days2Bd of
0:
po.picName := PIC_BIRTH;
1:
po.picName := PIC_BIRTH1;
2:
po.picName := PIC_BIRTH2;
else
po.picName := '';
end;
if po.picName > '' then
begin
if pIsRight then
with theme.GetPicSize(po) do
// newX := x - cx
// else
// newX := x;
dec(p.x, cx);
Result := theme.drawPic(DC, p, po);
end;
end;
CNT_TEXT:
begin
{$IFDEF CHECK_INVIS}
{ if CheckInvis.ShowInvisibility then
if c.invisibleState=1 then
inc(x, theme.drawPic(cnv, x,y+1, status2imgName(SC_ONLINE, true)).cx+2)
else
if c.invisibleState=2 then
inc(x, theme.drawPic(cnv, x,y+1, status2imgName(SC_ONLINE, false)).cx+2);
}
{$ENDIF}
if Cnt.typing.bIsTyping then
Result := theme.drawPic(DC, p.x, p.y, PIC_TYPING);
if Account.outbox.stFor(Cnt) then
begin
with theme.GetPicSize(RQteDefault, PIC_OUTBOX) do
n.outboxRect := rect(p.x, p.y, p.x + cx, p.y + cy);
Result := theme.drawPic(DC, p.x, p.y, PIC_OUTBOX);
end
else
n.outboxRect := rect(-1, -1, -1, -1);
end;
{$IFDEF RNQ_AVATARS}
CNT_ICON_AVT:
begin
with Cnt.icon do
if assigned(Bmp) then
begin
rB := DestRect(Bmp.Width, Bmp.Height, PaintInfo.node.NodeHeight - 1, PaintInfo.node.NodeHeight - 1);
// w := Rb.Right - Rb.Left;
if pIsRight then
begin
Inc(rB.x, p.x - PaintInfo.node.NodeHeight);
// inc(Rb.Right, x - paintinfo.node.NodeHeight);
end
else
begin
Inc(rB.x, p.x);
// inc(Rb.Right, x);
end;
Inc(rB.y, p.y);
// inc(Rb.Bottom, y);
DrawRbmp(DC, Bmp, rB);
Result.cx := PaintInfo.node.NodeHeight;
// Result.cy := paintinfo.node.NodeHeight;
Result.cy := Result.cx
end;
end
{$ENDIF RNQ_AVATARS}
else
begin
end;
end;
if Result.cx > 0 then
Inc(Result.cx, 2);
end;
const
f1n = 'roaster.group'; // roaster.groupfont);
f2n = 'roaster.group.num'; // roaster.groupfont);
var
bakmode: integer;
cnv: Tcanvas;
r, rB: Trect;
cntTxt: Trect;
c: TICQcontact;
s: string;
b, isNIL: boolean;
isOnRight: boolean;
ico: TRnQCLIconsSet;
p: TPoint;
i: integer;
// w : Smallint;
size: TSize;
res: TSize;
vPicName: TPicName;
dx: integer;
x, y: integer;
// oldCol : TColor;
BkgColor, FrameColor: TColor;
// dd : TDateTime;
// gr : TGPGraphics;
// br : TGPBrush;
// pen : TGPPen;
// TextLen: Integer;
// TextRect: TRect;
// TextFlags: Cardinal;
// Options: TDTTOpts;
begin
cnv := PaintInfo.canvas;
cnv.font.Assign(Sender.font);
r := PaintInfo.CellRect;
n := Tnode(PNode(Sender.getnodedata(PaintInfo.node))^);
// inc(r.Right);
if PaintInfo.node = Sender.FocusedNode then
begin
rB := r;
rB.bottom := r.top + (r.bottom - r.top) div 2 + 1;
// inc(rB.Right, 2);
BkgColor := theme.GetColor('roaster.selection', $DEDEDE);
FrameColor := theme.GetColor('roaster.selection.frame', clSilver);
// FillGradient(cnv.Handle, rB, FadeColor1, MidColor(FadeColor1, FadeColor2, 0.66), gdVertical);
// rB.Top := rB.Bottom;
// rB.Bottom := r.Bottom;
// FillGradient(cnv.Handle, rB, FadeColor2, MidColor(FadeColor1, FadeColor2, 0.66), gdVertical);
// oldCol := cnv.Brush.Color;
cnv.Brush.color := BkgColor;
cnv.FillRect(r);
// cnv.Brush.Color := oldCol;
cnv.Brush.color := FrameColor;
cnv.FrameRect(r);
end;
theme.ApplyFont(Str_roster, cnv.font);
bakmode := getbkmode(cnv.handle);
SetBkMode(cnv.handle, TRANSPARENT);
// Dec(R.Bottom);
dec(r.right);
x := 0;
y := 0;
case n.kind of
NODE_CONTACT:
begin
Inc(x, 2);
if n.contact is TICQcontact then
c := TICQcontact(n.contact)
else
c := NIL;
if n.contact.UID = '' then
exit;
isNIL := notinlist.exists(n.contact);
if indentRoster and showGroups and (n.contact.group > 0) and not isNIL then
Inc(x, theme.GetPicSize(RQteDefault, PIC_CLOSE_GROUP).cx);
{
if TO_SHOW_ICON[CNT_ICON_VIS] then
inc(x, DrawContactIcon(cnv.Handle, CNT_ICON_VIS,Point(x, y+1), n.contact).cx);
}
isOnRight := FALSE; // <20><> <20><> <20><> <20><> <20><>
dx := 1; // <20><> <20><> <20><> <20><>
i := Byte(low(TRnQCLIconsSet));
while ((not isOnRight) or (SHOW_ICONS_ORDER[i] <> CNT_TEXT)) and
(i in [Byte(low(TRnQCLIconsSet)) .. Byte(High(TRnQCLIconsSet))]) do
begin
ico := SHOW_ICONS_ORDER[i];
if ico = CNT_TEXT then
begin
p := Point(x, y + 1);
// size := DrawContactIcon(cnv.Handle, ico, p, n.contact, isOnRight);
size := DrawContactIcon(cnv.handle, ico, p, n.contact, FALSE);
// if isOnRight then
// inc(dx, size.cx)
// else
Inc(x, size.cx);
isOnRight := True;
i := Byte(High(TRnQCLIconsSet));
Continue;
end;
if isOnRight then
p := Point(r.right - dx - 1, y + 1)
else
p := Point(x, y + 1);
if TO_SHOW_ICON[ico] then
begin
size := DrawContactIcon(cnv.handle, ico, p, n.contact, isOnRight);
if isOnRight then
Inc(dx, size.cx)
else
Inc(x, size.cx);
end;
if isOnRight then
dec(i)
else
Inc(i);
end;
// Text
if isNIL then
theme.ApplyFont('roaster.notinlist', cnv.font)
else if n.contact.fProto.isOnline then
if n.contact.isOnline then
begin
if c.noClient then
theme.ApplyFont('roaster.noclient', cnv.font)
else
theme.ApplyFont('roaster.online', cnv.font)
end
else if n.contact.isOffline then
theme.ApplyFont('roaster.offline', cnv.font);
if PaintInfo.node = Sender.FocusedNode then
cnv.font.color := theme.GetColor('roaster.font.selected', cnv.font.color); // roaster.selectionTextColor
if UseContactThemes and assigned(ContactsTheme) then
begin
ContactsTheme.ApplyFont(TPicName('group.') + TPicName(AnsiLowerCase(groups.id2name(n.contact.group))) + '.roaster',
cnv.font);
ContactsTheme.ApplyFont(TPicName(n.contact.UID2cmp) + '.roaster', cnv.font);
end;
case rosterItalic of
RI_LIST:
b := n.contact.fProto.readList(LT_VISIBLE).exists(n.contact);
RI_VISIBLETO:
b := n.contact.imVisibleTo;
else
b := FALSE;
end;
if b then
if fsItalic in cnv.font.style then
cnv.font.style := cnv.font.style - [fsItalic]
else
cnv.font.style := cnv.font.style + [fsItalic];
n.textOfs := x;
cntTxt := r;
cntTxt.Left := x;
dec(cntTxt.right, dx);
// -=S@x
// cnv.textout(x,y+2, c.displayed);
// S@x=-
// -=S@x
s := dupAmperstand(n.contact.displayed);
{ TextLen := Length(s);
TextFlags := DT_CENTER or DT_VCENTER;
// TextFlags := DT_LEFT or DT_SINGLELINE or DT_VCENTER or DT_END_ELLIPSIS;
TextRect := cntTxt;
// TextRect.Left := cntTxt.;
// TextRect.Top := y-1;
// inc(TextRect.Bottom, 1);
FillChar(Options, SizeOf(Options), 0);
Options.dwSize := SizeOf(Options);
Options.dwFlags := DTT_COMPOSITED or DTT_TEXTCOLOR;
// Options.iGlowSize := 2;
Options.crText := ColorToRGB(cnv.Font.Color);
with StyleServices.GetElementDetails(twCaptionActive) do
DrawThemeTextEx(StyleServices.Theme[teWindow], cnv.Handle, Part, State,
// with StyleServices.GetElementDetails(teEditTextNormal) do
// DrawThemeTextEx(StyleServices.Theme[teEdit], Memdc, Part, State,
PWideChar(WideString(s)), TextLen, TextFlags, @TextRect, Options);
}
// DrawText32(cnv.Handle, cntTxt, s, cnv.Font, DT_CENTER or DT_VCENTER);
DrawText(cnv.handle, PChar(s), Length(s), cntTxt, DT_LEFT or DT_SINGLELINE or DT_VCENTER or DT_END_ELLIPSIS);
// DrawText32(cnv.Handle, cntTxt, s, cnv.Font, DT_LEFT or DT_SINGLELINE or DT_VCENTER or DT_END_ELLIPSIS);
// DrawTextTransparent(cnv.Handle, cntTxt.Left, cntTxt.Top, s, cnv.Font,
// 200, DT_LEFT or DT_SINGLELINE or DT_VCENTER or DT_END_ELLIPSIS);
// S@x=-
end;
NODE_GROUP:
begin
theme.ApplyFont(f1n, cnv.font);
// if n.expanded and (n.childrenCount>0) then
if n.expanded or (n.childrenCount = 0) then
vPicName := PIC_OPEN_GROUP
else
vPicName := PIC_CLOSE_GROUP;
Inc(x, theme.drawPic(cnv.handle, x + 2, y, vPicName).cx + 4);
n.outboxRect := rect(2, 2, x - 2, r.bottom - 1);
n.textOfs := x;
s := groups.id2name(n.groupId);
// cnv.textout(x,y+2,s);
cnv.textout(x, y + 2, s);
GetTextExtentPoint32(cnv.handle, PChar(s), Length(s), res);
x := x + res.cx;
cnv.textout(x, y + 2, ' (');
GetTextExtentPoint32(cnv.handle, ' (', 2, res);
x := x + res.cx;
// cnv.font:=f2;
cnv.font.Assign(Sender.font);
theme.ApplyFont(f2n, cnv.font);
if OnlOfflInOne then
s := intToStr(Account.AccProto.readList(LT_ROSTER).getCount(n.groupId, True))
else
s := intToStr(n.childrenCount);
cnv.textout(x, y + 2, s);
GetTextExtentPoint32(cnv.handle, PChar(s), Length(s), res);
// s := '';
x := x + res.cx;
// cnv.font:=f1;
cnv.font.Assign(Sender.font);
theme.ApplyFont(f1n, cnv.font);
cnv.textout(x, y + 2, '/');
GetTextExtentPoint32(cnv.handle, '/', 1, res);
x := x + res.cx;
// cnv.font:=f2;
cnv.font.Assign(Sender.font);
theme.ApplyFont(f2n, cnv.font);
s := intToStr(Account.AccProto.readList(LT_ROSTER).getCount(n.groupId));
cnv.textout(x, y + 2, s);
GetTextExtentPoint32(cnv.handle, PChar(s), Length(s), res);
x := x + res.cx;
// cnv.font:=f1;
cnv.font.Assign(Sender.font);
theme.ApplyFont(f1n, cnv.font);
cnv.textout(x, y + 2, ')');
end;
NODE_DIV:
begin
theme.ApplyFont('roaster.divisor', cnv.font);
cnv.pen.color := cnv.font.color;
s := getTranslation(divisor2ShowStr[Tdivisor(n.divisor)]) + ' ' + intToStr(contactsUnder(n));
size := txtSize(cnv.handle, s);
x := (r.right + r.Left - size.cx) div 2;
y := (r.bottom + r.top - size.cy) div 2;
if x < 10 then
x := 10;
n.textOfs := x;
cnv.textout(x, y, s);
y := (r.top + r.bottom) div 2 - 2;
cnv.moveTo(r.Left, y);
cnv.lineTo(x - 1, cnv.penpos.y);
cnv.moveTo(x + size.cx + 3, cnv.penpos.y);
cnv.lineTo(r.right, cnv.penpos.y);
end;
end; // case
SetBkMode(cnv.handle, bakmode);
end;
function ICON_ORDER_PREF: RawByteString;
var
a: TRnQCLIconsSet;
begin
Result := ';';
for a in SHOW_ICONS_ORDER do
Result := Result + RnQCLIcons[a].PrefText + ';';
end;
procedure ICON_ORDER_PREF_parse(const str: RawByteString);
function can_add(idx: Byte; a: TRnQCLIconsSet): boolean;
var
i: integer;
begin
Result := True;
for i := 0 to idx - 1 do
if SHOW_ICONS_ORDER[i] = a then
begin
Result := FALSE;
break;
end;
end;
var
a: TRnQCLIconsSet;
cur: Byte;
s: RawByteString;
ss: RawByteString;
begin
cur := Byte(low(TRnQCLIconsSet));
ss := str;
while (s = '') and (ss > '') do
s := chop(AnsiString(';'), ss);
while (s > '') and (ss > '') do
begin
for a in [low(TRnQCLIconsSet) .. High(TRnQCLIconsSet)] do
if (s = RnQCLIcons[a].PrefText) and can_add(cur, a) then
begin
SHOW_ICONS_ORDER[cur] := a;
Inc(cur);
end;
s := chop(AnsiString(';'), ss);
end;
if cur <= Byte(High(TRnQCLIconsSet)) then
for a in [low(TRnQCLIconsSet) .. High(TRnQCLIconsSet)] do
if can_add(cur, a) then
begin
SHOW_ICONS_ORDER[cur] := a;
Inc(cur);
end;
end;
INITIALIZATION
contactsPool := Tlist.create;
sortBy := SB_event;
// ContactThemes := TRQtheme.Create;
{$IFDEF USE_SECUREIM}
useSecureIM := loadlib;
{$ENDIF USE_SECUREIM}
FINALIZATION
contactsPool.free;
contactsPool := NIL;
if assigned(ContactsTheme) then
ContactsTheme.free;
ContactsTheme := NIL;
end.