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

1792 lines
48 KiB
Plaintext

{
This file is part of R&Q.
Under same license
}
unit roasterLib;
{$I RnQConfig.inc}
interface
uses
Windows, Classes, StdCtrls, Graphics, Generics.Collections, VirtualTrees,
chatDlg, RDGlobal, RnQPics, ThemesLib, ICQContacts;
{$I NoRTTI.inc}
type
TDivisor = (d_online, d_offline, d_contacts, d_nil, d_recent);
const
divsWithGroups = [d_online, d_contacts, d_offline, d_recent];
divisor2str: array [TDivisor] of AnsiString = ('online', 'offline', 'contacts', 'not in list', 'recent');
divisor2ShowStr: array [TDivisor] of string = ('Online', 'Offline', 'Contacts', 'Not in list', 'Recently went offline');
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);
{$I PubRTTI.inc}
TRnQCLIcons = record
// IDX : Byte;
idx: TRnQCLIconsSet;
Name: String;
IconName: TPicName;
PrefText: AnsiString;
// Cptn : String;
// DefShortCut : String;
// ev : procedure;
end;
{$I NoRTTI.inc}
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'),
(idx: CNT_ICON_XSTS; Name: 'XStatus'; IconName: 'st_custom.cigarette'; PrefText: 'show-xstatus-flag'),
(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: TICQContact;
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_: TICQContact); 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;
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: TICQContact): Boolean; overload;
function Redraw(n: TNode): Boolean; overload;
function Update(c: TICQContact): Boolean;
// function Exists(c: TRnQContact): Boolean;
function Focus(c: TICQContact): 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: TICQContact): Boolean;
procedure Sort(c: TICQContact); 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; ga: TGroupAction = GA_None);
function Focused: TNode;
function FocusedContact: TICQContact;
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;
function GetDivContacts(D: TDivisor): TArray;
procedure SetNewGroupFor(c: TICQContact; 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: TICQContact;
groupId: Integer;
groupAction: TGroupAction;
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,
ICQSession, 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: TICQContact): 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 GetDivContacts(D: TDivisor): TArray;
var
Data: PNode;
Node: PVirtualNode;
begin
SetLength(Result, 0);
Node := RnQmain.roster.GetFirstLevel(0);
while Assigned(Node) do
begin
Data := RnQmain.roster.GetNodeData(Node);
if (Data.kind = NODE_DIV) and (Data.divisor = D) then
begin
Node := RnQmain.roster.GetFirstChild(Node);
while Assigned(Node) do
begin
Data := RnQmain.roster.GetNodeData(Node);
if Data.kind = NODE_CONTACT then
begin
SetLength(Result, Length(Result) + 1);
Result[Length(Result) - 1] := Data.contact;
end else if Data.kind = NODE_DIV then // Jumped to the next divisor, time to stop
Break;
Node := RnQmain.roster.GetNext(Node); // Iterate both groups and contacts
end;
Break;
end;
Node := RnQmain.roster.GetNextSibling(Node);
end;
end;
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;
if (divs[d_recent] <> nil) then
RnQmain.roster.IsVisible[divs[d_recent].treenode] := divs[d_recent].treenode.ChildCount > 0;
for i := 0 to contactsPool.count - 1 do
UpdateHiddenNode(contactsPool[i]);
end; // UpdateHiddenNodes
function CompareContacts(c1, c2: TICQContact): 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
Result := Account.AccProto.compareStatusFor(c1, c2);
if Result = 0 then
Result := compareText(c1.displayed, c2.displayed);
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: TICQContact;
t1, t2: Byte;
tmpI1, tmpI2: Integer;
begin
Result := 0;
if (item1 = item2) or (item1 = nil) or (item2 = nil) then
exit;
tmpC1 := TICQContact(item1);
tmpC2 := TICQContact(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_: TICQContact);
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.SetNode(groupId, 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
p: TGroup;
begin
p := groups.Get(id);
Result := p.Node[d];
if Assigned(Result) then
Exit;
InsertNode(d); // ensure divisor existence
Result := TNode.create(id, d);
Result.order := p.Order;
Result.treenode := RnQmain.roster.addchild(divs[d].treenode, Result);
groups.SetNode(id, d, Result);
if not building then
Sort(Result);
end; // InsertNode
function ShouldBeUnder(c: TICQContact; 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: TICQContact; 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.Get(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: TICQContact; d: TDivisor): TNode; overload;
begin
Result := InsertNode(c, shouldBeUnder(c, d))
end;
function GetContactDiv(c: TICQContact; 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
if EnableRecentlyOffline and TICQContact(c).IsRecent then
Result := d_recent
else 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: TICQContact): 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: TICQContact): 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: TICQContact;
oldFocusedContact: TICQContact;
// oldtopnode : PVirtualNode;
g: TPair;
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;
groups.FillNodes;
// 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);
if (EnableRecentlyOffline) then
InsertNode(d_recent);
InsertNode(d_offline);
end;
for c in rosterList do
begin
if Filtered(c) then
Continue;
d := GetContactDiv(c, True);
InsertNode(c, d);
end;
// Add empty groups too
if ShowGroups and ShowEmptyGroups then
for g in groups.GList do
if Account.AccProto.readList(LT_ROSTER).getCount(g.Value.ID) = 0 then
InsertNode(g.Value.ID, d_contacts);
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);
groups.SetNodesExpanded(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: TICQContact): Boolean;
begin
Result := False;
if c = nil then
Exit;
try
Result := redraw(TCE(c.data^).node);
except end;
UpdateHiddenNode(TCE(c.data^).node);
if Assigned(chatFrm) then
try
chatFrm.RedrawTab(c);
except 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: TICQContact): 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: TICQContact): Boolean;
begin
Result := False;
if not Assigned(c) then
Exit;
with Account.AccProto do
begin
Result := RemoveContact(TICQContact(c)) or Result;
end;
SaveListsDelayed := True;
RemoveNode(c);
if Assigned(chatFrm) then
chatFrm.UserChanged(c);
end; // Remove
function Exists(c: TICQContact): 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: TICQContact): 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
g: TPair;
d: TDivisor;
begin
RnQmain.roster.clear;
while contactsPool.count > 0 do
TNode(contactsPool.last).Free;
for g in groups.GList do
for d := Low(TDivisor) to High(TDivisor) do
if Assigned(g.Value.Node[d]) then
g.Value.Node[d].Free;
for d := Low(TDivisor) to High(TDivisor) do
FreeAndNil(divs[d]);
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));
if not (divs[d_recent] = nil) then
if RnQmain.roster.isVisible[divs[d_recent].treenode] then
Result := nodeBottomSide(lowestVisibleNodeFrom(divs[d_recent].treenode));
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.AddWithValues(0, 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: TICQContact;
d: TDivisor;
begin
Result := groups.Exists(id);
if not Result then
Exit;
cl := Account.AccProto.readList(LT_ROSTER).clone;
for c in cl do
if c.group = id then
Remove(c);
cl.Free;
with groups.Get(id) do
for d := Low(d) to High(d) do
RemoveNode(Node[d]);
groups.Remove(id);
SaveGroupsDelayed := True;
end; // RemoveGroup
procedure Edit(n: TNode; ga: TGroupAction = GA_None);
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 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.groupAction := ga;
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: TICQContact;
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: TICQContact);
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: TICQContact; grp: Integer);
begin
if c.cntIsLocal or Account.AccProto.UpdateGroupOf(TICQContact(c), grp) then
begin
c.group := grp;
update(c);
dbUpdateDelayed := True;
end;
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;
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: TICQContact; 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 Account.AccProto.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 Account.AccProto.GetStatusDisable.blinking)) then
begin
// if (c.invisibleState = 2)or(c.status <> SC_OFFLINE) then
if notinlist.exists(Cnt) then
begin
po.picName := PIC_STATUS_UNK;
if pIsRight then
with theme.GetPicSize(po, 0) do
dec(p.X, cx);
Result := theme.drawPic(DC, p, po);
end
else if Cnt is TICQcontact then
begin
if pIsRight then
begin
Result := statusDrawExt(0, 0, 0, Byte(Cnt.status), False);
dec(p.x, Result.cx);
end;
Result := statusDrawExt(DC, p.x, p.y, Byte(Cnt.status), False);
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;
// else
// size.cx := 0
if TICQContact(Cnt).Official then
theme.drawPic(DC, p.x + Result.cx - 12, p.y + Result.cy - 13, PIC_OFFICIAL);
end
else
begin
Result := ev.PicSize;
if pIsRight then
dec(p.x, Result.cx);
if blinking or Account.AccProto.GetStatusDisable.blinking then
ev.Draw(DC, p.x, p.y)
end;
end;
CNT_ICON_XSTS:
begin
if 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 not Cnt.Authorized and not Cnt.CntIsLocal 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.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
{ 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);
}
if Cnt.typing.bIsTyping then
if not (enableQuietList and quietList.exists(Cnt)) 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);
SetStretchBltMode(DC, HALFTONE);
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;
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);
if Sender.Focused then
begin
// oldCol := cnv.Brush.Color;
cnv.Brush.color := BkgColor;
cnv.FillRect(r);
// cnv.Brush.Color := oldCol;
cnv.Brush.color := FrameColor;
cnv.FrameRect(r);
end;
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.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 Account.AccProto.IsOnline then
begin
if n.contact.IsOnline then
theme.ApplyFont('roaster.online', cnv.font)
else if n.contact.IsOffline then
if n.contact.NoClient then
theme.ApplyFont('roaster.noclient', cnv.font)
else
theme.ApplyFont('roaster.offline', cnv.font);
end;
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_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];
Inc(x, 2);
n.textOfs := x;
cntTxt := r;
cntTxt.Left := x;
Dec(cntTxt.Right, dx);
s := n.contact.displayed;
DrawText(cnv.handle, PChar(s), Length(s), cntTxt, DT_LEFT or DT_SINGLELINE or DT_VCENTER or DT_END_ELLIPSIS or DT_NOPREFIX);
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.