1#  GroupChat.tcl ---
2#
3#      This file is part of The Coccinella application.
4#      It implements the group chat GUI part.
5#
6#  Copyright (c) 2001-2008  Mats Bengtsson
7#
8#   This program is free software: you can redistribute it and/or modify
9#   it under the terms of the GNU General Public License as published by
10#   the Free Software Foundation, either version 3 of the License, or
11#   (at your option) any later version.
12#
13#   This program is distributed in the hope that it will be useful,
14#   but WITHOUT ANY WARRANTY; without even the implied warranty of
15#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16#   GNU General Public License for more details.
17#
18#   You should have received a copy of the GNU General Public License
19#   along with this program.  If not, see <http://www.gnu.org/licenses/>.
20#
21# $Id: GroupChat.tcl,v 1.258 2008-06-12 07:23:36 matben Exp $
22
23package require Create
24package require Enter
25package require History
26package require Bookmarks
27package require JUI
28package require UI::WSearch
29package require colorutils
30package require mstack
31package require jlib::annotations
32
33package provide GroupChat 1.0
34
35namespace eval ::GroupChat {
36
37    # Add all event hooks.
38    ::hooks::register initHook                ::GroupChat::InitHook
39    ::hooks::register quitAppHook             ::GroupChat::QuitAppHook
40    ::hooks::register quitAppHook             ::GroupChat::GetFirstPanePos
41    ::hooks::register newGroupChatMessageHook ::GroupChat::GotMsg
42    ::hooks::register newMessageHook          ::GroupChat::NormalMsgHook
43    ::hooks::register loginHook               ::GroupChat::LoginHook
44    ::hooks::register logoutHook              ::GroupChat::LogoutHook
45    ::hooks::register setPresenceHook         ::GroupChat::StatusSyncHook
46    ::hooks::register groupchatEnterRoomHook  ::GroupChat::EnterHook
47    ::hooks::register menuGroupChatEditPostHook   ::GroupChat::MenuEditPostHook
48
49    # Define all hooks for preference settings.
50    ::hooks::register prefsInitHook           ::GroupChat::InitPrefsHook
51    ::hooks::register prefsBuildHook          ::GroupChat::BuildPrefsHook
52    ::hooks::register prefsSaveHook           ::GroupChat::SavePrefsHook
53    ::hooks::register prefsCancelHook         ::GroupChat::CancelPrefsHook
54    ::hooks::register prefsUserDefaultsHook   ::GroupChat::UserDefaultsHook
55
56    option add *GroupChat*TreeCtrl.background "#e6edf7"         50
57
58    # Icons
59    option add *GroupChat*sendImage            mail-send             widgetDefault
60    option add *GroupChat*sendDisImage         mail-send-Dis         widgetDefault
61    option add *GroupChat*saveImage            document-save             widgetDefault
62    option add *GroupChat*saveDisImage         document-save-Dis          widgetDefault
63    option add *GroupChat*historyImage         view-history          widgetDefault
64    option add *GroupChat*historyDisImage      view-history-Dis       widgetDefault
65    option add *GroupChat*inviteImage          invite           widgetDefault
66    option add *GroupChat*inviteDisImage       invite-Dis        widgetDefault
67    option add *GroupChat*infoImage            dialog-information             widgetDefault
68    option add *GroupChat*infoDisImage         dialog-information-Dis          widgetDefault
69    option add *GroupChat*printImage           document-print            widgetDefault
70    option add *GroupChat*printDisImage        document-print-Dis         widgetDefault
71    option add *GroupChat*whiteboardImage      whiteboard       widgetDefault
72    option add *GroupChat*whiteboardDisImage   whiteboard-Dis    widgetDefault
73
74    option add *GroupChat*tabAlertImage        notify-message               widgetDefault
75
76    # Pre 8.5, cleanup!
77    if {[tk windowingsystem] eq "aqua"} {
78	option add *GroupChat*tabClose16Image        close-aqua         widgetDefault
79	option add *GroupChat*tabCloseActive16Image  close-aqua-active  widgetDefault
80    } else {
81	option add *GroupChat*tabClose16Image        close             widgetDefault
82	option add *GroupChat*tabCloseActive16Image  close             widgetDefault
83    }
84
85    # Text displays.
86    option add *GroupChat*mePreForeground      red              widgetDefault
87    option add *GroupChat*mePreBackground      ""               widgetDefault
88    option add *GroupChat*mePreFont            ""               widgetDefault
89    option add *GroupChat*meTextForeground     ""               widgetDefault
90    option add *GroupChat*meTextBackground     ""               widgetDefault
91    option add *GroupChat*meTextFont           ""               widgetDefault
92    option add *GroupChat*theyPreForeground    blue             widgetDefault
93    option add *GroupChat*theyPreBackground    ""               widgetDefault
94    option add *GroupChat*theyPreFont          ""               widgetDefault
95    option add *GroupChat*theyTextForeground   ""               widgetDefault
96    option add *GroupChat*theyTextBackground   ""               widgetDefault
97    option add *GroupChat*theyTextFont         ""               widgetDefault
98    option add *GroupChat*sysPreForeground     "#26b412"        widgetDefault
99    option add *GroupChat*sysTextForeground    "#26b412"        widgetDefault
100    option add *GroupChat*sysPreFont           ""               widgetDefault
101    option add *GroupChat*sysPreFontSlant      ""               widgetDefault
102    option add *GroupChat*sysTextFont          ""               widgetDefault
103    option add *GroupChat*sysTextFontSlant     "italic"         widgetDefault
104    option add *GroupChat*histHeadForeground   ""               widgetDefault
105    option add *GroupChat*histHeadBackground   gray80           widgetDefault
106    option add *GroupChat*histHeadFont         ""               widgetDefault
107    option add *GroupChat*histHeadFontSlant    "italic"         widgetDefault
108    option add *GroupChat*clockFormat          "%H:%M"          widgetDefault
109    option add *GroupChat*clockFormatNotToday  "%b %d %H:%M"    widgetDefault
110
111    # List of: {tagName optionName resourceName resourceClass}
112    # -fontSlant is special!
113    variable groupChatOptions {
114	{mepre       -foreground          mePreForeground       Foreground}
115	{mepre       -background          mePreBackground       Background}
116	{mepre       -font                mePreFont             Font}
117	{metext      -foreground          meTextForeground      Foreground}
118	{metext      -background          meTextBackground      Background}
119	{metext      -font                meTextFont            Font}
120	{theypre     -foreground          theyPreForeground     Foreground}
121	{theypre     -background          theyPreBackground     Background}
122	{theypre     -font                theyPreFont           Font}
123	{theytext    -foreground          theyTextForeground    Foreground}
124	{theytext    -background          theyTextBackground    Background}
125	{theytext    -font                theyTextFont          Font}
126	{syspre      -foreground          sysPreForeground      Foreground}
127	{syspre      -font                sysPreFont            Font}
128	{syspre      -fontSlant           sysPreFontSlant       ""}
129	{systext     -foreground          sysTextForeground     Foreground}
130	{systext     -font                sysTextFont           Font}
131	{systext     -fontSlant           sysTextFontSlant      ""}
132	{histhead    -foreground          histHeadForeground    Foreground}
133	{histhead    -background          histHeadBackground    Background}
134	{histhead    -font                histHeadFont          Font}
135	{histhead    -fontSlant           sysPreFontSlant       ""}
136    }
137
138    # Standard wigets.
139    if {[tk windowingsystem] eq "aqua"} {
140	option add *GroupChat*TNotebook.padding       {8 8 8 18}       50
141    } else {
142	option add *GroupChat*TNotebook.padding       {8 8 8 8}        50
143    }
144    option add *GroupChatRoom*Text.borderWidth     0               50
145    option add *GroupChatRoom*Text.relief          flat            50
146    option add *GroupChatRoom.padding              {0  0  0  0}    50
147    option add *GroupChatRoom*active.padding       {1}             50
148    option add *GroupChatRoom*TMenubutton.padding  {1}             50
149    option add *GroupChatRoom*top.padding          {12  8 12  8}   50
150    option add *GroupChatRoom*bot.padding          {12  6 20  6}   50
151
152    option add *GroupChatRoom*mid.pv.r.borderWidth 1               widgetDefault
153    option add *GroupChatRoom*mid.pv.r.relief      sunken          widgetDefault
154
155    # Local stuff
156    variable enteruid 0
157    variable dlguid 0
158
159    # Running numbers for tokens.
160    variable uiddlg  0
161    variable uidchat 0
162    variable uidpage 0
163
164    # Local preferences.
165    variable cprefs
166    set cprefs(lastActiveRet) 0
167
168    # Keep track of if we have made autojoin when getting bookmarks.
169    variable autojoinDone 0
170
171    variable userRoleToStr
172    set userRoleToStr(moderator)   [mc "Moderators"]
173    set userRoleToStr(none)        [mc "None"]
174    set userRoleToStr(participant) [mc "Participants"]
175    set userRoleToStr(visitor)     [mc "Visitors"]
176
177    variable userRoleSortOrder
178    array set userRoleSortOrder {
179	moderator   0
180	participant 1
181	visitor     2
182	none        3
183    }
184
185    # Not used.
186    variable show2String
187    set show2String(available)   [mc "available"]
188    # TRANSLATORS; presence state when the user is not physically available at his/her computer or device, for a short moment
189    set show2String(away)        [mc "away"]
190    set show2String(chat)        [mc "free for chat"]
191    # TRANSLATORS; presence state when the user don't wants to be interrupted, except in really urgent circumstances
192    set show2String(dnd)         [mc "do not disturb"]
193    # TRANSLATORS; presence state when the user is not physically available at his/her computer or device, for a longer period
194    set show2String(xa)          [mc "extended away"]
195    # TRANSLATORS; presence state when the user is available, but not visible as available to her or his contacts
196    set show2String(invisible)   [mc "invisible"]
197    set show2String(unavailable) [mc "not available"]
198
199    # @@@ Should get this from a global reaource.
200    variable buttonPressMillis 1000
201    variable waitUntilEditMillis 2000
202
203    # Binding tag for the close croos in notebook tabs.
204    bind GroupChatTab <ButtonPress-1> [namespace code [list OnCloseTab %W %x %y]]
205
206    # Shall we automatically rejoin open groupchat on login?
207    set ::config(groupchat,login-autojoin) 1
208
209    # As jprefs???
210    set ::config(groupchat,show-sysmsgs) 1
211}
212
213proc ::GroupChat::InitHook {} {
214    InitMenus
215}
216
217proc ::GroupChat::InitMenus {} {
218
219    variable popMenuDefs
220    set mDefs {
221	{command   mMessage...      {[mc "&Message"]...}            {::NewMsg::Build -to $jid}    }
222	{command   mChat...         {[mc "Cha&t"]...}               {::Chat::StartThread $jid}    }
223	{command   mSendFile...     {[mc "Send &File"]...}          {::FTrans::Send $jid}         }
224	{command   mBusinessCard... {[mc "View &Business Card"]...} {::UserInfo::Get $jid}        }
225	{command   mEditNick        {[mc "&Edit Nickname"]}         {::GroupChat::TreeEditUserStart $chattoken $jid} }
226	{check     mIgnore          {[mc "&Ignore"]}                {::GroupChat::Ignore $chattoken $jid} {
227	    -variable $chattoken\(ignore,$jid)
228	}}
229    }
230    if {[::Jabber::HaveWhiteboard]} {
231	set mDefs [linsert $mDefs 4 \
232	  {command   mWhiteboard    {[mc "&Whiteboard"]...}         {::JWB::NewWhiteboardTo $jid} }]
233
234    }
235    set popMenuDefs(groupchat,def) $mDefs
236
237    set popMenuDefs(groupchat,type) {
238	{mMessage...        user        }
239	{mChat...           user        }
240	{mSendFile...       user        }
241	{mBusinessCard...   user        }
242	{mWhiteboard        wb          }
243	{mEditNick          me          }
244	{mIgnore            user        }
245    }
246
247    # Keeps track of all registered menu entries.
248    variable regPopMenuDef {}
249    variable regPopMenuType {}
250}
251
252proc ::GroupChat::QuitAppHook {} {
253    global  wDlgs
254
255    ::UI::SaveWinPrefixGeom $wDlgs(jgc)
256}
257
258# GroupChat::HaveMUC --
259#
260#       Should perhaps be in jlib service part.
261#
262# Arguments:
263#       jid         is either a service or a room jid
264
265proc ::GroupChat::HaveMUC {{jid ""}} {
266    upvar ::Jabber::xmppxmlns xmppxmlns
267
268    set ans 0
269    if {$jid eq ""} {
270	set allConfServ [::Jabber::Jlib disco getconferences]
271	foreach serv $allConfServ {
272	    if {[::Jabber::Jlib disco hasfeature $xmppxmlns(muc) $serv]} {
273		set ans 1
274	    }
275	}
276    } else {
277
278	# We must query the service, not the room, for browse to work.
279	jlib::splitjidex $jid node service -
280	if {$service ne ""} {
281	    if {[::Jabber::Jlib disco hasfeature $xmppxmlns(muc) $service]} {
282		set ans 1
283	    }
284	}
285    }
286    ::Debug 4 "::GroupChat::HaveMUC = $ans, jid=$jid"
287
288    return $ans
289}
290
291proc ::GroupChat::OnMenuEnter {} {
292    if {[llength [grab current]]} { return }
293    if {[::JUI::GetConnectState] eq "connectfin"} {
294	EnterOrCreate enter
295    }
296}
297
298proc ::GroupChat::OnMenuCreate {} {
299    if {[llength [grab current]]} { return }
300    if {[::JUI::GetConnectState] eq "connectfin"} {
301	EnterOrCreate create
302    }
303}
304
305proc ::GroupChat::IsInRoom {roomjid} {
306    if {[lsearch -exact [::Jabber::Jlib service allroomsin] $roomjid] < 0} {
307	return 0
308    } else {
309	return 1
310    }
311}
312
313# GroupChat::EnterOrCreate --
314#
315#       Dispatch entering or creating a room to either 'groupchat' (gc-1.0)
316#       or 'muc' methods.
317#
318# Arguments:
319#       what        'enter' or 'create'
320#       args        -server, -roomjid, -autoget, -nickname, -protocol
321#
322# Results:
323#       "cancel" or "enter".
324
325proc ::GroupChat::EnterOrCreate {what args} {
326    global jprefs
327
328    ::Debug 2 "::GroupChat::EnterOrCreate what=$what, args='$args'"
329
330    set service  ""
331    set ans      "cancel"
332
333    array set argsA $args
334    if {[info exists argsA(-roomjid)]} {
335	set roomjid $argsA(-roomjid)
336	jlib::splitjidex $roomjid node service -
337    } elseif {[info exists argsA(-server)]} {
338	set service $argsA(-server)
339    }
340
341    if {[info exists argsA(-protocol)]} {
342	set protocol $argsA(-protocol)
343    } else {
344	set protocol "muc"
345	if {$service ne ""} {
346	    if {($protocol eq "muc") && ![HaveMUC $service]} {
347		set protocol "gc-1.0"
348	    }
349	}
350    }
351
352    ::Debug 2 "\t protocol=$protocol"
353
354    switch -glob -- $what,$protocol {
355	enter,* {
356	    set ans [eval {::Enter::Build $protocol} $args]
357	}
358	create,gc-1.0 {
359	    set ans [eval {::Create::GCBuild} $args]
360	}
361	create,muc {
362	    set ans [eval {::Create::Build} $args]
363	}
364	default {
365	    ::ui::dialog -icon error -title [mc "Error"] \
366	      -message [mc "Cannot find any chatroom service."]
367	}
368    }
369
370    # @@@ BAD only used in JWB.
371    return $ans
372}
373
374proc ::GroupChat::EnterHook {roomjid protocol} {
375
376    ::Debug 2 "::GroupChat::EnterHook roomjid=$roomjid $protocol"
377
378    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
379    if {$chattoken eq ""} {
380
381	# If we haven't a window for this roomjid, make one!
382	set chattoken [NewChat $roomjid]
383    } else {
384
385	# Refresh any existing room widget.
386	variable $chattoken
387	upvar 0 $chattoken chatstate
388
389	TreeDeleteAll $chatstate(wusers)
390	AddUsers $chattoken
391	SetState $chattoken normal
392	#$chatstate(wbtexit) configure -text [mc "Exit"]
393
394	set chatstate(show)           "available"
395	set chatstate(oldShow)        "available"
396	set chatstate(show+status)    [list available ""]
397	set chatstate(oldShow+status) [list available ""]
398    }
399
400    SetProtocol $roomjid $protocol
401
402    ::Jabber::Jlib presence_register_ex [namespace code PresenceEvent] \
403      -from2 $roomjid
404}
405
406# GroupChat::SetProtocol --
407#
408#       Cache groupchat protocol in use for specific room.
409
410proc ::GroupChat::SetProtocol {roomjid _protocol} {
411    variable protocol
412
413    ::Debug 2 "::GroupChat::SetProtocol +++++++++ $roomjid $_protocol"
414    set roomjid [jlib::jidmap $roomjid]
415
416    # We need a separate cache for this since the room may not yet exist.
417    set protocol($roomjid) $_protocol
418
419    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
420    if {$chattoken eq ""} {
421	return
422    }
423
424    if {$_protocol eq "muc"} {
425	variable $chattoken
426	upvar 0 $chattoken chatstate
427
428	set dlgtoken $chatstate(dlgtoken)
429	variable $dlgtoken
430	upvar 0 $dlgtoken dlgstate
431
432	set wtray $dlgstate(wtray)
433	$wtray buttonconfigure invite -state normal
434	$wtray buttonconfigure info   -state normal
435	if {[$wtray exists whiteboard]} {
436	    $wtray buttonconfigure whiteboard -state normal
437	}
438    }
439}
440
441# GroupChat::NormalMsgHook --
442#
443#       MUC (and others) send invitations using normal messages. Catch!
444
445proc ::GroupChat::NormalMsgHook {xmldata uuid} {
446    upvar ::Jabber::xmppxmlns xmppxmlns
447
448    set roomjid [wrapper::getattribute $xmldata from]
449    set xuserE [wrapper::getfirstchild $xmldata x $xmppxmlns(muc,user)]
450
451    set isinvite 0
452
453    if {[llength $xuserE]} {
454	set isinvite 1
455	set str2 ""
456	set inviteE [wrapper::getfirstchildwithtag $xuserE invite]
457	set reasonE [wrapper::getfirstchildwithtag $inviteE reason]
458	set invitejid [wrapper::getattribute $inviteE from]
459	if {[llength $reasonE]} {
460	    append str2 "Reason: [wrapper::getcdata $reasonE]"
461	}
462	set passwordE [wrapper::getfirstchildwithtag $xuserE password]
463	if {[llength $passwordE]} {
464	    append str2 " Password: [wrapper::getcdata $passwordE]"
465	}
466    } else {
467	set cinviteE [wrapper::getfirstchild $xmldata x "jabber:x:conference"]
468	if {[llength $cinviteE]} {
469	    set isinvite 1
470	    set invitejid [wrapper::getattribute $cinviteE jid]
471	    set str2 "Reason: [wrapper::getcdata $cinviteE]"
472	}
473    }
474    if {$isinvite} {
475
476	::Debug 2 "::GroupChat::NormalMsgHook"
477
478	set str [mc "%s invited you to %s. Do you want to enter this chatroom?" $invitejid $roomjid]
479	append str " " $str2
480	set ans [::UI::MessageBox -title [mc "Invite"] -icon info -type yesno \
481	  -message $str]
482	if {$ans eq "yes"} {
483	    EnterOrCreate enter -roomjid $roomjid
484	}
485	return stop
486    } else {
487	return
488    }
489}
490
491# GroupChat::NewChat --
492#
493#       Takes a room JID and handles building of dialog and chat room stuff.
494#       @@@ Add more code here...
495#
496# Results:
497#       chattoken
498
499proc ::GroupChat::NewChat {roomjid} {
500    global jprefs
501
502    if {$jprefs(chat,tabbedui)} {
503	set dlgtoken [GetFirstDlgToken]
504	if {$dlgtoken eq ""} {
505	    set dlgtoken [Build $roomjid]
506	    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
507	} else {
508	    set chattoken [NewPage $dlgtoken $roomjid]
509	}
510    } else {
511	set dlgtoken [Build $roomjid]
512	set chattoken [GetActiveChatToken $dlgtoken]
513    }
514
515    return $chattoken
516}
517
518# GroupChat::GotMsg --
519#
520#       Just got a group chat message. Fill in message in existing dialog.
521#       If no dialog, make a freash one.
522#
523# Arguments:
524#       xmldata
525#
526# Results:
527#       updates UI.
528
529proc ::GroupChat::GotMsg {xmldata} {
530    global  prefs jprefs
531
532
533    set from [wrapper::getattribute $xmldata from]
534    if {$from eq ""} {
535	return
536    }
537    set from [jlib::jidmap $from]
538    jlib::splitjid $from roomjid res
539
540    set body    [wrapper::getcdata [wrapper::getfirstchildwithtag $xmldata body]]
541    set subject [wrapper::getcdata [wrapper::getfirstchildwithtag $xmldata subject]]
542
543    # If we haven't a window for this roomjid, make one!
544    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
545    if {$chattoken eq ""} {
546	set chattoken [NewChat $roomjid]
547    }
548    variable $chattoken
549    upvar 0 $chattoken chatstate
550
551    set dlgtoken $chatstate(dlgtoken)
552    variable $dlgtoken
553    upvar 0 $dlgtoken dlgstate
554
555    # We may get a history from users not in the room anymore.
556    if {[info exists chatstate(ignore,$from)] && $chatstate(ignore,$from)} {
557	return
558    }
559
560    InsertMessage $chattoken $xmldata
561
562    if {$subject ne ""} {
563	set chatstate(subject) $subject
564    }
565    if {$body ne ""} {
566	TabAlert $chattoken $xmldata
567
568	# Put an extra (*) in the windows title if not in focus.
569	if {([set wfocus [focus]] eq "") ||  \
570	  ([winfo toplevel $wfocus] ne $dlgstate(w))} {
571	    incr dlgstate(nhiddenmsgs)
572	    SetTitle [GetActiveChatToken $dlgtoken]
573	}
574
575	# Run display hooks (speech).
576	::hooks::run displayGroupChatMessageHook $xmldata
577    }
578}
579
580# GroupChat::Build --
581#
582#       Builds the group chat dialog.
583#
584# Arguments:
585#       roomjid     The roomname@server
586#
587# Results:
588#       shows window, returns token.
589
590proc ::GroupChat::Build {roomjid} {
591    global  prefs wDlgs this jprefs
592
593    variable protocol
594    variable uiddlg
595    variable cprefs
596
597    ::Debug 2 "::GroupChat::Build roomjid=$roomjid"
598
599    # Initialize the state variable, an array, that keeps is the storage.
600
601    set dlgtoken [namespace current]::dlg[incr uiddlg]
602    variable $dlgtoken
603    upvar 0 $dlgtoken dlgstate
604
605    # Make unique toplevel name.
606    set w $wDlgs(jgc)$uiddlg
607
608    set dlgstate(exists)        1
609    set dlgstate(w)             $w
610    set dlgstate(uid)           0
611    set dlgstate(nhiddenmsgs)   0
612
613    # Toplevel of class GroupChat.
614    ::UI::Toplevel $w -class GroupChat \
615      -macclass {document {toolbarButton standardDocument}} \
616      -usemacmainmenu 1 -closecommand ::GroupChat::CloseCmd
617
618    bind $w <<ToolbarButton>> [list ::GroupChat::OnToolbarButton $dlgtoken]
619
620    # Global frame.
621    ttk::frame $w.frall
622    pack $w.frall -fill both -expand 1
623
624    # Widget paths.
625    set wtop        $w.frall.top
626    set wtray       $w.frall.top.tray
627    set wcont       $w.frall.cc        ;# container frame for wroom or wnb
628    set wroom       $w.frall.room      ;# the chat room widget container
629    set wnb         $w.frall.nb        ;# tabbed notebook
630    set dlgstate(wtop)       $wtop
631    set dlgstate(wtray)      $wtray
632    set dlgstate(wcont)      $wcont
633    set dlgstate(wroom)      $wroom
634    set dlgstate(wnb)        $wnb
635
636    ttk::frame $wtop
637    pack $wtop -side top -fill x
638
639    # Shortcut button part.
640    set iconSend        [::Theme::Find32Icon $w sendImage]
641    set iconSendDis     [::Theme::Find32Icon $w sendDisImage]
642    set iconSave        [::Theme::Find32Icon $w saveImage]
643    set iconSaveDis     [::Theme::Find32Icon $w saveDisImage]
644    set iconHistory     [::Theme::Find32Icon $w historyImage]
645    set iconHistoryDis  [::Theme::Find32Icon $w historyDisImage]
646    set iconInvite      [::Theme::Find32Icon $w inviteImage]
647    set iconInviteDis   [::Theme::Find32Icon $w inviteDisImage]
648    set iconInfo        [::Theme::Find32Icon $w infoImage]
649    set iconInfoDis     [::Theme::Find32Icon $w infoDisImage]
650    set iconPrint       [::Theme::Find32Icon $w printImage]
651    set iconPrintDis    [::Theme::Find32Icon $w printDisImage]
652    set iconWB          [::Theme::Find32Icon $w whiteboardImage]
653    set iconWBDis       [::Theme::Find32Icon $w whiteboardDisImage]
654
655    ::ttoolbar::ttoolbar $wtray
656    pack $wtray -side top -fill x
657
658    $wtray newbutton send -text [mc "Send"] \
659      -image $iconSend -disabledimage $iconSendDis    \
660      -command [list [namespace current]::Send $dlgtoken]
661    $wtray newbutton save -text [mc "Save"] \
662      -image $iconSave -disabledimage $iconSaveDis    \
663       -command [list [namespace current]::Save $dlgtoken]
664    $wtray newbutton history -text [mc "History"] \
665      -image $iconHistory -disabledimage $iconHistoryDis \
666      -command [list [namespace current]::BuildHistory $dlgtoken]
667    $wtray newbutton invite -text [mc "Invite"] \
668      -image $iconInvite -disabledimage $iconInviteDis  \
669      -command [list [namespace current]::Invite $dlgtoken]
670    $wtray newbutton info -text [mc "Configure"] \
671      -image $iconInfo -disabledimage $iconInfoDis    \
672      -command [list [namespace current]::Info $dlgtoken]
673    $wtray newbutton print -text [mc "Print"] \
674      -image $iconPrint -disabledimage $iconPrintDis   \
675      -command [list [namespace current]::Print $dlgtoken]
676    if {[::Jabber::HaveWhiteboard]} {
677	$wtray newbutton whiteboard -text [mc "Whiteboard"] \
678	  -image $iconWB -disabledimage $iconWBDis    \
679	  -command [list [namespace current]::Whiteboard $dlgtoken]
680    }
681
682    ::hooks::run buildGroupChatButtonTrayHook $wtray $roomjid
683
684    set shortBtWidth [expr {[$wtray minwidth] + 8}]
685
686    # Top separator.
687    ttk::separator $w.frall.divt -orient horizontal
688    pack $w.frall.divt -side top -fill x
689    set dlgstate(tsep) $w.frall.divt
690
691    # Having the frame with room frame as a sibling makes it possible
692    # to pack it in a different place.
693    ttk::frame $wcont
694    pack $wcont -side bottom -fill both -expand 1
695
696    # Use an extra frame that contains everything room specific.
697    set chattoken [BuildRoomWidget $dlgtoken $wroom $roomjid]
698    pack $wroom -in $wcont -fill both -expand 1
699
700    if {!( [info exists protocol($roomjid)] && ($protocol($roomjid) eq "muc") )} {
701	$wtray buttonconfigure invite -state disabled
702	$wtray buttonconfigure info   -state disabled
703	if {[$wtray exists whiteboard]} {
704	    $wtray buttonconfigure whiteboard -state disabled
705	}
706    }
707
708    set nwin [llength [::UI::GetPrefixedToplevels $wDlgs(jgc)]]
709    if {$nwin == 1} {
710	::UI::SetWindowGeometry $w $wDlgs(jgc)
711    }
712    SetTitle $chattoken
713
714    wm minsize $w [expr {$shortBtWidth < 240 ? 240 : $shortBtWidth}] 320
715
716    bind $w <<Find>>         [namespace code [list Find $dlgtoken]]
717    bind $w <<FindAgain>>    [namespace code [list FindAgain $dlgtoken]]
718    bind $w <<FindPrevious>> [namespace code [list FindAgain $dlgtoken -1]]
719    # Wrong binding to toplevel.
720    #bind $w <FocusIn>       +[namespace code [list FocusIn $dlgtoken]]
721
722    set tag TopTag$w
723    bindtags $w [concat $tag [bindtags $w]]
724    bind $tag <Destroy> +[list ::GroupChat::OnDestroyDlg $dlgtoken]
725
726    return $dlgtoken
727}
728
729proc ::GroupChat::OnToolbarButton {dlgtoken} {
730    variable $dlgtoken
731    upvar 0 $dlgtoken dlgstate
732
733    if {[llength [grab current]]} { return }
734    if {[winfo ismapped $dlgstate(wtop)]} {
735	HideToolbar $dlgtoken
736	set show 0
737    } else {
738	ShowToolbar $dlgtoken
739	set show 1
740    }
741    ::hooks::run uiGroupChatToggleToolbar $show
742}
743
744proc ::GroupChat::HideToolbar {dlgtoken} {
745    variable $dlgtoken
746    upvar 0 $dlgtoken dlgstate
747
748    pack forget $dlgstate(wtop)
749    pack forget $dlgstate(tsep)
750}
751
752proc ::GroupChat::ShowToolbar {dlgtoken} {
753    variable $dlgtoken
754    upvar 0 $dlgtoken dlgstate
755
756    pack $dlgstate(wtop) -side top -fill x
757    pack $dlgstate(tsep) -side top -fill x
758}
759
760# GroupChat::BuildRoomWidget --
761#
762#       Builds page with all room specific ui parts.
763#
764# Arguments:
765#       dlgtoken    topwindow token
766#       wroom       megawidget frame
767#       roomjid
768#
769# Results:
770#       chattoken
771
772proc ::GroupChat::BuildRoomWidget {dlgtoken wroom roomjid} {
773    global  this config jprefs
774    variable $dlgtoken
775    upvar 0 $dlgtoken dlgstate
776
777    variable uidchat
778    variable cprefs
779    variable protocol
780
781    ::Debug 2 "::GroupChat::BuildRoomWidget, roomjid=$roomjid"
782
783    # Initialize the state variable, an array, that keeps is the storage.
784
785    set chattoken [namespace current]::chat[incr uidchat]
786    variable $chattoken
787    upvar 0 $chattoken chatstate
788
789    lappend dlgstate(chattokens)    $chattoken
790    lappend dlgstate(recentctokens) $chattoken
791
792    # Widget paths.
793    set wtop        $wroom.top
794    set wbot        $wroom.bot
795    set wmid        $wroom.mid
796
797    set wpanev      $wroom.mid.pv
798    set wfrsend     $wroom.mid.pv.b
799    set wtextsend   $wroom.mid.pv.b.text
800    set wyscsend    $wroom.mid.pv.b.ysc
801
802    set wpaneh      $wroom.mid.pv.t
803    set wfrchat     $wroom.mid.pv.l
804    set wfrusers    $wroom.mid.pv.r
805
806    set wtext       $wroom.mid.pv.l.text
807    set wysc        $wroom.mid.pv.l.ysc
808    set wfind       $wroom.mid.pv.l.find
809    set wusers      $wroom.mid.pv.r.tree
810    set wyscusers   $wroom.mid.pv.r.ysc
811
812    set roomjid [jlib::jidmap $roomjid]
813    jlib::splitjidex $roomjid node domain -
814
815    set chatstate(exists)         1
816    set chatstate(wroom)          $wroom
817    set chatstate(roomjid)        $roomjid
818    set chatstate(dlgtoken)       $dlgtoken
819    set chatstate(roomName)       [::Jabber::Jlib disco name $roomjid]
820    set chatstate(subject)        ""
821    set chatstate(show)           "available"
822    set chatstate(oldShow)        "available"
823    set chatstate(show+status)    [list available ""]
824    set chatstate(oldShow+status) [list available ""]
825    set chatstate(ignore,$roomjid)  0
826    set chatstate(afterids)       {}
827    set chatstate(nhiddenmsgs)    0
828    set chatstate(lasttext)       ""
829    set chatstate(mynick)         [::Jabber::Jlib service mynick $roomjid]
830
831    # For the tabs and title etc.
832    if {$chatstate(roomName) ne ""} {
833	set chatstate(displayName) $chatstate(roomName)
834    } else {
835	set chatstate(displayName) $roomjid
836    }
837    set chatstate(roomNode)     $node
838    set chatstate(wtext)        $wtext
839    set chatstate(wfind)        $wfind
840    set chatstate(wtextsend)    $wtextsend
841    set chatstate(wusers)       $wusers
842    set chatstate(wpanev)       $wpanev
843    set chatstate(wpaneh)       $wpaneh
844
845    set chatstate(active)       $cprefs(lastActiveRet)
846    set chatstate(mstack)       [mstack::init 4]
847
848    set chatstate(elidesys)     0
849
850    # Use an extra frame that contains everything room specific.
851    ttk::frame $wroom -class GroupChatRoom
852
853    set w [winfo toplevel $wroom]
854    set chatstate(w) $w
855
856    # Button part.
857    #set wbtexit   $wbot.btcancel
858    set wgroup    $wbot.grp
859    set wbtstatus $wgroup.stat
860    set wbtbmark  $wgroup.bmark
861
862    ttk::frame $wbot
863    ttk::button $wbot.btok -text [mc "Send"]  \
864      -default active -command [list [namespace current]::Send $dlgtoken]
865    #ttk::button $wbot.btcancel -text [mc "Exit"]  \
866     # -command [list [namespace current]::ExitAndClose $chattoken]
867
868    ttk::frame $wgroup
869    ttk::checkbutton $wgroup.active -style Toolbutton \
870      -image [::Theme::FindIconSize 16 keypress-return]   \
871      -command [list [namespace current]::ActiveCmd $chattoken] \
872      -variable $chattoken\(active)
873    ttk::button $wgroup.bmark -style Toolbutton \
874      -image [::Theme::FindIconSize 16 bookmark-new] \
875      -command [list [namespace current]::BookmarkRoom $chattoken]
876
877    if {$config(ui,status,menu) eq "plain"} {
878	::Status::Button $wgroup.stat $chattoken\(show)   \
879	  -command [list [namespace current]::StatusCmd $chattoken]
880	::Status::ConfigImage $wgroup.stat available
881	::Status::MenuConfig $wgroup.stat  \
882	  -postcommand [list [namespace current]::StatusPostCmd $chattoken]
883    } elseif {$config(ui,status,menu) eq "dynamic"} {
884	::Status::ExButton $wgroup.stat $chattoken\(show+status)   \
885	  -command [list [namespace current]::ExStatusCmd $chattoken] \
886	  -postcommand [list [namespace current]::ExStatusPostCmd $chattoken]
887    }
888
889    ::Emoticons::MenuButton $wgroup.smile -text $wtextsend
890    ttk::checkbutton $wgroup.elsys -style Toolbutton \
891      -image [::Theme::FindIconSize 16 dialog-information] \
892      -command [list [namespace current]::ElideSysCmd $chattoken] \
893      -variable $chattoken\(elidesys)
894
895    grid  $wgroup.active  $wgroup.bmark  $wgroup.stat  $wgroup.smile  \
896      $wgroup.elsys  -padx 1 -sticky news
897    foreach c {0 1} {
898	grid columnconfigure $wgroup $c -uniform bt -weight 1
899    }
900    foreach c {2 3} {
901	grid columnconfigure $wgroup $c -uniform mb -weight 1
902    }
903
904    set padx [option get . buttonPadX {}]
905    if {[option get . okcancelButtonOrder {}] eq "cancelok"} {
906	pack $wbot.btok  -side right
907	#pack $wbot.btcancel -side right -padx $padx
908    } else {
909	#pack $wbot.btcancel -side right
910	pack $wbot.btok -side right -padx $padx
911    }
912    pack  $wgroup -side left
913    pack  $wbot   -side bottom -fill x
914
915    set wbtsend $wbot.btok
916
917    ::balloonhelp::balloonforwindow $wgroup.active [mc "If checked, Return sends message, else use Ctrl/Cmd-Return"]
918    ::balloonhelp::balloonforwindow $wgroup.bmark  [mc "Bookmark this chatroom"]
919    ::balloonhelp::balloonforwindow $wgroup.elsys  [mc "Show or hide status changes in chat"]
920
921    # Header fields.
922    ttk::frame $wtop
923    pack $wtop -side top -fill x
924
925    # TRANSLATORS; subject of a chatroom discussion with multiple people
926    ttk::label $wtop.btp -style Small.TLabel -text [mc "Topic"]:
927    ttk::entry $wtop.etp -font CociSmallFont -textvariable $chattoken\(subject)
928
929    grid  $wtop.btp  $wtop.etp  -sticky e -padx 0
930    grid  $wtop.etp  -sticky ew
931    grid columnconfigure $wtop 1 -weight 1
932
933    # Special bindings for setting subject.
934    set wsubject $wtop.etp
935    bind $wsubject <FocusIn>  [list ::GroupChat::OnFocusInSubject $chattoken]
936    bind $wsubject <FocusOut> [list ::GroupChat::OnFocusOutSubject $chattoken]
937    bind $wsubject <Return>   [list ::GroupChat::OnReturnSubject $chattoken]
938
939    # Main frame for panes.
940    frame $wmid -height 250 -width 300
941    pack  $wmid -side top -fill both -expand 1
942
943    # Pane geometry manager.
944    ttk::paned $wpanev -orient vertical
945    pack $wpanev -side top -fill both -expand 1
946
947    # Text send.
948    if {$config(ui,aqua-text)} {
949	frame $wfrsend -height 40 -width 300
950	set wscont [::UI::Text $wtextsend -height 2 -width 1 -undo 1 -wrap word \
951	  -yscrollcommand [list ::UI::ScrollSet $wyscsend \
952	  [list grid $wyscsend -column 1 -row 0 -sticky ns]]]
953    } else {
954	frame $wfrsend -height 40 -width 300 -bd 1 -relief sunken
955	text  $wtextsend -height 2 -width 1 -undo 1 -wrap word \
956	  -yscrollcommand [list ::UI::ScrollSet $wyscsend \
957	  [list grid $wyscsend -column 1 -row 0 -sticky ns]]
958	set wscont $wtextsend
959    }
960    bindtags $wtextsend [linsert [bindtags $wtextsend] 0 UndoText]
961    ttk::scrollbar $wyscsend -orient vertical -command [list $wtextsend yview]
962
963    grid  $wscont     -column 0 -row 0 -sticky news
964    grid  $wyscsend   -column 1 -row 0 -sticky ns
965    grid columnconfigure $wfrsend 0 -weight 1
966    grid rowconfigure    $wfrsend 0 -weight 1
967
968    # Pane for chat and users list.
969    ttk::paned $wpaneh -orient horizontal
970    $wpanev add $wpaneh -weight 1
971    $wpanev add $wfrsend -weight 0
972
973    # Chat text widget.
974    if {$config(ui,aqua-text)} {
975	frame $wfrchat
976	set wtcont [::UI::Text $wtext -height 12 -width 40 -font CociSmallFont -state disabled  \
977	  -wrap word -cursor {}  \
978	  -yscrollcommand [list ::UI::ScrollSet $wysc \
979	  [list grid $wysc -column 1 -row 0 -sticky ns -padx 2]]]
980    } else {
981	frame $wfrchat -bd 1 -relief sunken
982	text  $wtext -height 12 -width 40 -font CociSmallFont -state disabled  \
983	  -wrap word -cursor {}  \
984	  -yscrollcommand [list ::UI::ScrollSet $wysc \
985	  [list grid $wysc -column 1 -row 0 -sticky ns -padx 2]]
986	set wtcont $wtext
987    }
988    ttk::scrollbar $wysc -orient vertical -command [list $wtext yview]
989    bindtags $wtext [linsert [bindtags $wtext] 0 ReadOnlyText]
990
991    grid  $wtcont -column 0 -row 0 -sticky news
992    grid  $wysc   -column 1 -row 0 -sticky ns -padx 2
993    grid columnconfigure $wfrchat 0 -weight 1
994    grid rowconfigure    $wfrchat 0 -weight 1
995
996    bind $wtext <<Copy>> {
997	::JUI::CopyEvent %W
998	break
999    }
1000
1001    # Users list.
1002    #frame $wfrusers -bd 1 -relief sunken
1003    frame $wfrusers
1004    ttk::scrollbar $wyscusers -orient vertical -command [list $wusers yview]
1005    Tree $chattoken $w $wusers $wyscusers
1006
1007    grid  $wusers     -column 0 -row 0 -sticky news
1008    grid  $wyscusers  -column 1 -row 0 -sticky ns -padx 2
1009    grid columnconfigure $wfrusers 0 -weight 1
1010    grid rowconfigure    $wfrusers 0 -weight 1
1011
1012    $wpaneh add $wfrchat  -weight 1
1013    $wpaneh add $wfrusers -weight 0
1014
1015    # The tags.
1016    ConfigureTextTags $w $wtext
1017    if {$jprefs(chatFont) ne ""} {
1018	$chatstate(wtextsend) configure -font $jprefs(chatFont)
1019    }
1020
1021    set chatstate(wbtsend)      $wbtsend
1022    set chatstate(wbtstatus)    $wbtstatus
1023    set chatstate(wbtbmark)     $wbtbmark
1024    #set chatstate(wbtexit)      $wbtexit
1025
1026    set ancient [expr {[clock clicks -milliseconds] - 1000000}]
1027    foreach whom {me you sys} {
1028	set chatstate(last,$whom) $ancient
1029    }
1030
1031    if {$jprefs(chatActiveRet)} {
1032	set chatstate(active) 1
1033    } else {
1034	set chatstate(active) $cprefs(lastActiveRet)
1035    }
1036    if {$chatstate(active)} {
1037	ActiveCmd $chattoken
1038    }
1039    AddUsers $chattoken
1040
1041    ::UI::SetSashPos groupchatDlgVert $wpanev
1042    ::UI::SetSashPos groupchatDlgHori $wpaneh
1043
1044    bind $wtextsend <$this(modkey)-KeyPress-Up> \
1045      [namespace code [list OnKeyUp $chattoken]]
1046    bind $wtextsend <$this(modkey)-KeyPress-Down> \
1047      [namespace code [list OnKeyDown $chattoken]]
1048
1049    bind $wtextsend <Return> \
1050      [list [namespace current]::ReturnKeyPress $chattoken]
1051    bind $wtextsend <$this(modkey)-Return> \
1052      [list [namespace current]::CommandReturnKeyPress $chattoken]
1053    bind $wroom <Destroy> +[list ::GroupChat::OnDestroyChat $chattoken]
1054
1055    bind $chatstate(wtextsend) <Map> { focus %W }
1056
1057    if {([tk windowingsystem] ne "aqua") && ![catch {package require tkdnd}]} {
1058	::JUI::DnDXmppBindTarget $wtext \
1059	  -command [namespace code [list DnDXmppDrop $chattoken]]
1060	::JUI::DnDXmppBindTarget $wtextsend \
1061	  -command [namespace code [list DnDXmppDrop $chattoken]]
1062	::JUI::DnDXmppBindTarget $wusers \
1063	  -command [namespace code [list DnDXmppDrop $chattoken]]
1064    }
1065
1066    ::hooks::run buildGroupChatWidget $roomjid
1067    ::hooks::run textSpellableNewHook $wtextsend
1068
1069    return $chattoken
1070}
1071
1072proc ::GroupChat::ElideSysCmd {chattoken} {
1073    variable $chattoken
1074    upvar 0 $chattoken chatstate
1075
1076    $chatstate(wtext) tag configure sys -elide $chatstate(elidesys)
1077}
1078
1079proc ::GroupChat::DnDXmppDrop {chattoken win data type} {
1080    variable $chattoken
1081    upvar 0 $chattoken chatstate
1082
1083    set jidL [::JUI::DnDXmppExtractJID $data $type]
1084    set jidL [string map {"," ""} $jidL]
1085    ::MUC::Invite $chatstate(roomjid) -jidlist $jidL
1086}
1087
1088proc ::GroupChat::GetWidget {roomjid value} {
1089
1090    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
1091    if {$chattoken ne ""} {
1092	variable $chattoken
1093	upvar 0 $chattoken chatstate
1094
1095	return $chatstate($value)
1096    }
1097}
1098
1099proc ::GroupChat::OnFocusInSubject {chattoken} {
1100    variable $chattoken
1101    upvar 0 $chattoken chatstate
1102
1103    set chatstate(subjectOld) $chatstate(subject)
1104}
1105
1106proc ::GroupChat::OnFocusOutSubject {chattoken} {
1107    variable $chattoken
1108    upvar 0 $chattoken chatstate
1109
1110    # Reset to previous subject.
1111    set chatstate(subject) $chatstate(subjectOld)
1112}
1113
1114proc ::GroupChat::OnReturnSubject {chattoken} {
1115    variable $chattoken
1116    upvar 0 $chattoken chatstate
1117
1118    ::Jabber::Jlib send_message $chatstate(roomjid) -type groupchat \
1119      -subject $chatstate(subject)
1120    focus $chatstate(w)
1121}
1122
1123proc ::GroupChat::Find {dlgtoken} {
1124
1125    set chattoken [GetActiveChatToken $dlgtoken]
1126    if {$chattoken eq ""} {
1127	return
1128    }
1129    variable $chattoken
1130    upvar 0 $chattoken chatstate
1131
1132    set wfind $chatstate(wfind)
1133    if {![winfo exists $wfind]} {
1134	UI::WSearch $wfind $chatstate(wtext) -padding {6 2}
1135	grid  $wfind  -column 0 -row 2 -columnspan 2 -sticky ew
1136    }
1137}
1138
1139proc ::GroupChat::FindAgain {dlgtoken {dir 1}} {
1140
1141    set chattoken [GetActiveChatToken $dlgtoken]
1142    if {$chattoken eq ""} {
1143	return
1144    }
1145    variable $chattoken
1146    upvar 0 $chattoken chatstate
1147
1148    set wfind $chatstate(wfind)
1149    if {[winfo exists $wfind]} {
1150	$wfind [expr {$dir == 1 ? "Next" : "Previous"}]
1151    }
1152}
1153
1154proc ::GroupChat::MenuEditPostHook {wmenu} {
1155
1156    if {[winfo exists [focus]]} {
1157	set w [winfo toplevel [focus]]
1158	set dlgtoken [GetTokenFrom dlg w $w]
1159	if {$dlgtoken eq ""} {
1160	    return
1161	}
1162	set chattoken [GetActiveChatToken $dlgtoken]
1163	if {$chattoken eq ""} {
1164	    return
1165	}
1166	variable $chattoken
1167	upvar 0 $chattoken chatstate
1168
1169	set wfind $chatstate(wfind)
1170	::UI::MenuMethod $wmenu entryconfigure mFind -state normal -label [mc "Find"]
1171	if {[winfo exists $wfind]} {
1172	    ::UI::MenuMethod $wmenu entryconfigure mFindNext -state normal -label [mc "Find Next"]
1173	    ::UI::MenuMethod $wmenu entryconfigure mFindPrevious -state normal -label [mc "Find Previous"]
1174	}
1175    }
1176}
1177
1178proc ::GroupChat::OnDestroyDlg {dlgtoken} {
1179
1180    unset -nocomplain $dlgtoken
1181}
1182
1183proc ::GroupChat::OnDestroyChat {chattoken} {
1184    variable $chattoken
1185    upvar 0 $chattoken chatstate
1186
1187    foreach id $chatstate(afterids) {
1188	after cancel $id
1189    }
1190    mstack::free $chatstate(mstack)
1191
1192    unset -nocomplain $chattoken
1193}
1194
1195# GroupChat::NewPage, ... --
1196#
1197#       Several procs to handle the tabbed interface; creates and deletes
1198#       notebook and pages.
1199
1200proc ::GroupChat::NewPage {dlgtoken roomjid args} {
1201    global  this jprefs
1202    variable $dlgtoken
1203    upvar 0 $dlgtoken dlgstate
1204
1205    # If no notebook, move chat widget to first notebook page.
1206    if {[string equal [winfo class [pack slaves $dlgstate(wcont)]] "GroupChatRoom"]} {
1207	set wroom $dlgstate(wroom)
1208	set chattoken [lindex $dlgstate(chattokens) 0]
1209	variable $chattoken
1210	upvar 0 $chattoken chatstate
1211
1212	# Repack the GroupChatRoom in notebook page.
1213	MoveRoomToPage $dlgtoken $chattoken
1214    }
1215
1216    # Make fresh page with chat widget.
1217    set chattoken [eval {MakeNewPage $dlgtoken $roomjid} $args]
1218    return $chattoken
1219}
1220
1221# Pre 8.5, cleanup!
1222
1223proc ::GroupChat::DrawCloseButton {dlgtoken} {
1224    variable $dlgtoken
1225    upvar 0 $dlgtoken dlgstate
1226
1227    # Close button (exp).
1228    set w $dlgstate(w)
1229
1230    set im  [::Theme::Find16Icon $w tabClose16Image]
1231    set ima [::Theme::Find16Icon $w tabCloseActive16Image]
1232    set wclose $dlgstate(wnb).close
1233
1234    ttk::button $wclose -style Plain  \
1235      -image [list $im active $ima] -compound image  \
1236      -command [list [namespace current]::ClosePageCmd $dlgtoken]
1237    place $wclose -anchor ne -relx 1.0 -x -6 -y 6
1238
1239    ::balloonhelp::balloonforwindow $wclose [mc "Close tab"]
1240    set dlgstate(wclose) $wclose
1241}
1242
1243proc ::GroupChat::MoveRoomToPage {dlgtoken chattoken} {
1244    variable $dlgtoken
1245    upvar 0 $dlgtoken dlgstate
1246    variable $chattoken
1247    upvar 0 $chattoken chatstate
1248
1249    # Repack the  in notebook page.
1250    set wnb      $dlgstate(wnb)
1251    set wcont    $dlgstate(wcont)
1252    set wroom    $chatstate(wroom)
1253    set roomNode $chatstate(roomNode)
1254
1255    pack forget $wroom
1256
1257    ttk::notebook $wnb -style X.TNotebook
1258    bind $wnb <<NotebookTabChanged>> \
1259      [list [namespace current]::TabChanged $dlgtoken]
1260    tileutils::nb::Traversal $wnb
1261    bindtags $wnb [linsert [bindtags $wnb] 0 GroupChatTab]
1262    pack $wnb -in $wcont -fill both -expand true -side right
1263
1264    set wpage $wnb.p[incr dlgstate(uid)]
1265    ttk::frame $wpage
1266    $wnb add $wpage -sticky news -text $roomNode -compound left
1267    pack $wroom -in $wpage -fill both -expand true -side right
1268    raise $wroom
1269
1270    set chatstate(wpage) $wpage
1271    set dlgstate(wpage2token,$wpage) $chattoken
1272}
1273
1274proc ::GroupChat::MakeNewPage {dlgtoken roomjid args} {
1275    variable $dlgtoken
1276    upvar 0 $dlgtoken dlgstate
1277
1278    variable uidpage
1279    array set argsA $args
1280
1281    # Make fresh page with chat widget.
1282    set wnb $dlgstate(wnb)
1283    set wpage $wnb.p[incr dlgstate(uid)]
1284    ttk::frame $wpage
1285    $wnb add $wpage -sticky news -compound left
1286
1287    # We must make the new page a sibling of the notebook in order to be
1288    # able to reparent it when notebook gons.
1289    set wroom $dlgstate(wroom)[incr uidpage]
1290    set chattoken [BuildRoomWidget $dlgtoken $wroom $roomjid]
1291    pack $wroom -in $wpage -fill both -expand true
1292
1293    variable $chattoken
1294    upvar 0 $chattoken chatstate
1295    $wnb tab $wpage -text $chatstate(roomNode)
1296    set chatstate(wpage) $wpage
1297    set dlgstate(wpage2token,$wpage) $chattoken
1298
1299    return $chattoken
1300}
1301
1302# GroupChat::OnCloseTab --
1303#
1304#       ButtonPress-1 binding on notebook used for close crosses.
1305
1306proc ::GroupChat::OnCloseTab {win x y} {
1307
1308    set id [$win identify $x $y]
1309    if {$id eq "crossIcon"} {
1310	set index [$win index @$x,$y]
1311	set dlgtoken [GetAllTokensFrom dlg w [winfo toplevel $win]]
1312	variable $dlgtoken
1313	upvar 0 $dlgtoken dlgstate
1314
1315	# Much better using dicts here!
1316	foreach {key chattoken} [array get dlgstate wpage2token,*] {
1317	    set wpage [string map {wpage2token, ""} $key]
1318	    if {[$win index $wpage] == $index} {
1319		Exit $chattoken
1320		CloseRoomPage $chattoken
1321		return -code break
1322	    }
1323	}
1324    }
1325}
1326
1327proc ::GroupChat::DeletePage {chattoken} {
1328    variable $chattoken
1329    upvar 0 $chattoken chatstate
1330
1331    set dlgtoken $chatstate(dlgtoken)
1332    variable $dlgtoken
1333    upvar 0 $dlgtoken dlgstate
1334
1335    set wpage $chatstate(wpage)
1336    $dlgstate(wnb) forget $wpage
1337    unset dlgstate(wpage2token,$wpage)
1338
1339    # Delete the actual widget.
1340    set dlgstate(chattokens)  \
1341      [lsearch -all -inline -not $dlgstate(chattokens) $chattoken]
1342    destroy $chatstate(wroom)
1343
1344    # If only a single page left then reparent and delete notebook.
1345    if {[llength $dlgstate(chattokens)] == 1} {
1346
1347	# Be sure to remove also the remaining wpage2token.
1348	array unset dlgstate wpage2token,*
1349
1350	set chattoken [lindex $dlgstate(chattokens) 0]
1351	variable $chattoken
1352	upvar 0 $chattoken chatstate
1353
1354	MoveThreadFromPage $dlgtoken $chattoken
1355    }
1356}
1357
1358proc ::GroupChat::MoveThreadFromPage {dlgtoken chattoken} {
1359    variable $dlgtoken
1360    upvar 0 $dlgtoken dlgstate
1361    variable $chattoken
1362    upvar 0 $chattoken chatstate
1363
1364    set wnb     $dlgstate(wnb)
1365    set wcont   $dlgstate(wcont)
1366    set wroom   $chatstate(wroom)
1367
1368    # This seems necessary on mac in order to not get a blank page.
1369    update idletasks
1370
1371    pack forget $wroom
1372    destroy $wnb
1373    pack $wroom -in $wcont -fill both -expand 1
1374
1375    SetRoomState $dlgtoken $chattoken
1376}
1377
1378proc ::GroupChat::ClosePageCmd {dlgtoken} {
1379    variable $dlgtoken
1380    upvar 0 $dlgtoken dlgstate
1381
1382    set chattoken [GetActiveChatToken $dlgtoken]
1383    if {$chattoken ne ""} {
1384	ExitAndClose $chattoken
1385    }
1386}
1387
1388# GroupChat::SelectPage --
1389#
1390#       Make page frontmost.
1391
1392proc ::GroupChat::SelectPage {chattoken} {
1393    variable $chattoken
1394    upvar 0 $chattoken chatstate
1395
1396    set dlgtoken $chatstate(dlgtoken)
1397    variable $dlgtoken
1398    upvar 0 $dlgtoken dlgstate
1399
1400    if {[winfo exists $dlgstate(wnb)]} {
1401	$dlgstate(wnb) select $chatstate(wpage)
1402    }
1403}
1404
1405# GroupChat::TabChanged --
1406#
1407#       Callback command from notebook widget when selecting new tab.
1408
1409proc ::GroupChat::TabChanged {dlgtoken} {
1410    variable $dlgtoken
1411    upvar 0 $dlgtoken dlgstate
1412
1413    Debug 2 "::GroupChat::TabChanged"
1414
1415    set wnb $dlgstate(wnb)
1416    set wpage [GetNotebookWpageFromIndex $wnb [$wnb index current]]
1417    set chattoken $dlgstate(wpage2token,$wpage)
1418
1419    variable $chattoken
1420    upvar 0 $chattoken chatstate
1421
1422    set chatstate(nhiddenmsgs) 0
1423
1424    SetRoomState $dlgtoken $chattoken
1425    SetFocus $dlgtoken $chattoken
1426
1427    lappend dlgstate(recentctokens) $chattoken
1428    set dlgstate(recentctokens) [lrange $dlgstate(recentctokens) end-1 end]
1429
1430    ::hooks::run groupchatTabChangedHook $chattoken
1431}
1432
1433proc ::GroupChat::GetNotebookWpageFromIndex {wnb index} {
1434
1435    set wpage ""
1436    foreach w [$wnb tabs] {
1437	if {[$wnb index $w] == $index} {
1438	    set wpage $w
1439	    break
1440	}
1441    }
1442    return $wpage
1443}
1444
1445proc ::GroupChat::SetRoomState {dlgtoken chattoken} {
1446    variable $dlgtoken
1447    upvar 0 $dlgtoken dlgstate
1448
1449    variable $chattoken
1450    upvar 0 $chattoken chatstate
1451
1452    ::Debug 2 "::GroupChat::SetRoomState $dlgtoken $chattoken"
1453
1454    if {[winfo exists $dlgstate(wnb)]} {
1455	$dlgstate(wnb) tab $chatstate(wpage) -image ""  \
1456	  -text $chatstate(roomNode)
1457    }
1458    SetTitle $chattoken
1459    if {[::Jabber::IsConnected]} {
1460	SetState $chattoken normal
1461    } else {
1462	SetState $chattoken disabled
1463    }
1464}
1465
1466# GroupChat::SetState --
1467#
1468#       Set state of complete dialog to normal or disabled.
1469
1470proc ::GroupChat::SetState {chattoken _state} {
1471    variable $chattoken
1472    upvar 0 $chattoken chatstate
1473
1474    ::Debug 2 "::GroupChat::SetState $chattoken $_state"
1475
1476    if {$_state eq "normal"} {
1477	set tstate {!disabled}
1478    } else {
1479	set tstate {disabled}
1480    }
1481
1482    set dlgtoken $chatstate(dlgtoken)
1483    variable $dlgtoken
1484    upvar 0 $dlgtoken dlgstate
1485
1486    foreach name {send invite info} {
1487	$dlgstate(wtray) buttonconfigure $name -state $_state
1488    }
1489    $chatstate(wbtsend)    state $tstate
1490    $chatstate(wbtstatus)  state $tstate
1491    $chatstate(wbtbmark)   state $tstate
1492    $chatstate(wtextsend)  configure -state $_state
1493}
1494
1495proc ::GroupChat::SetLogout {chattoken} {
1496    variable $chattoken
1497    upvar 0 $chattoken chatstate
1498
1499    set clockFormat [option get $chatstate(w) clockFormat {}]
1500    if {$clockFormat ne ""} {
1501	set theTime [clock format [clock seconds] -format $clockFormat]
1502	set prefix "\[$theTime\] "
1503    } else {
1504	set prefix ""
1505    }
1506    InsertTagString $chattoken $prefix syspre
1507    set logoutstr "  "
1508    append logoutstr [mc "You logged out and exited the chatroom"]\n
1509    InsertTagString $chattoken $logoutstr systext
1510
1511    set nick [::Jabber::Jlib service mynick $chatstate(roomjid)]
1512    set myjid $chatstate(roomjid)/$nick
1513    TreeRemoveUser $chattoken $myjid
1514
1515    #$chatstate(wbtexit) configure -text [mc "Close"]
1516
1517    set chatstate(show)           "unavailable"
1518    set chatstate(oldShow)        "unavailable"
1519    set chatstate(show+status)    [list unavailable ""]
1520    set chatstate(oldShow+status) [list unavailable ""]
1521}
1522
1523# GroupChat::SetFocus --
1524#
1525#       When selecting a new page we must move focus along.
1526#       This does not work reliable on MacOSX.
1527
1528proc ::GroupChat::SetFocus {dlgtoken chattoken} {
1529    global  this
1530    variable $dlgtoken
1531    upvar 0 $dlgtoken dlgstate
1532
1533    variable $chattoken
1534    upvar 0 $chattoken chatstate
1535
1536
1537    # @@@ TODO
1538}
1539
1540proc ::GroupChat::SetTitle {chattoken} {
1541    variable $chattoken
1542    upvar 0 $chattoken chatstate
1543
1544    set name    $chatstate(roomName)
1545    set roomjid $chatstate(roomjid)
1546    set ujid [jlib::unescapejid $roomjid]
1547    if {$name ne ""} {
1548	set str [mc "Chatroom"]
1549	append str ": $name"
1550    } else {
1551	set str [mc "Chatroom"]
1552	append str ": $ujid"
1553    }
1554
1555    # Put an extra (*) in the windows title if not in focus.
1556    set dlgtoken $chatstate(dlgtoken)
1557    variable $dlgtoken
1558    upvar 0 $dlgtoken dlgstate
1559
1560    if {$dlgstate(nhiddenmsgs) > 0} {
1561	set wfocus [focus]
1562	set n $dlgstate(nhiddenmsgs)
1563	if {$wfocus eq ""} {
1564	    append str " ($n)"
1565	} elseif {[winfo toplevel $wfocus] ne $chatstate(w)} {
1566	    append str " ($n)"
1567	}
1568    }
1569    wm title $chatstate(w) $str
1570}
1571
1572proc ::GroupChat::TabAlert {chattoken xmldata} {
1573    variable $chattoken
1574    upvar 0 $chattoken chatstate
1575
1576    set dlgtoken $chatstate(dlgtoken)
1577    variable $dlgtoken
1578    upvar 0 $dlgtoken dlgstate
1579
1580    if {[winfo exists $dlgstate(wnb)]} {
1581	set w       $dlgstate(w)
1582	set wnb     $dlgstate(wnb)
1583
1584	# Show only if not current page.
1585	if {[GetActiveChatToken $dlgtoken] ne $chattoken} {
1586	    incr chatstate(nhiddenmsgs)
1587	    set name $chatstate(roomNode)
1588	    append name " " "($chatstate(nhiddenmsgs))"
1589	    set icon [::Theme::Find16Icon $w tabAlertImage]
1590	    $wnb tab $chatstate(wpage) -image $icon -text $name
1591	}
1592    }
1593}
1594
1595proc ::GroupChat::FocusIn {dlgtoken} {
1596    variable $dlgtoken
1597    upvar 0 $dlgtoken dlgstate
1598
1599    set dlgstate(nhiddenmsgs) 0
1600    SetTitle [GetActiveChatToken $dlgtoken]
1601}
1602
1603# GroupChat::GetDlgTokenValue, GetChatTokenValue --
1604#
1605#       Outside code shall use these to get array values.
1606
1607proc ::GroupChat::GetDlgTokenValue {dlgtoken key} {
1608    variable $dlgtoken
1609    upvar 0 $dlgtoken dlgstate
1610
1611    return $dlgstate($key)
1612}
1613
1614proc ::GroupChat::GetChatTokenValue {chattoken key} {
1615    variable $chattoken
1616    upvar 0 $chattoken chatstate
1617
1618    return $chatstate($key)
1619}
1620
1621# GroupChat::GetActiveChatToken --
1622#
1623#       Returns the chattoken corresponding to the frontmost room.
1624
1625proc ::GroupChat::GetActiveChatToken {dlgtoken} {
1626    variable $dlgtoken
1627    upvar 0 $dlgtoken dlgstate
1628
1629    if {[winfo exists $dlgstate(wnb)]} {
1630	set wnb $dlgstate(wnb)
1631	set wpage [GetNotebookWpageFromIndex $wnb [$wnb index current]]
1632	set chattoken $dlgstate(wpage2token,$wpage)
1633    } else {
1634	set chattoken [lindex $dlgstate(chattokens) 0]
1635    }
1636    return $chattoken
1637}
1638
1639# GroupChat::GetTokenFrom --
1640#
1641#       Try to get the token state array from any stored key.
1642#       Only one token is returned if any.
1643#
1644# Arguments:
1645#       type        'dlg' or 'chat'
1646#       key         w, jid, roomjid etc...
1647#       pattern     glob matching
1648#
1649# Results:
1650#       token or empty if not found.
1651
1652proc ::GroupChat::GetTokenFrom {type key pattern} {
1653
1654    if {$key eq "roomjid"} {
1655	set pattern [jlib::jidmap $pattern]
1656    }
1657
1658    # Search all tokens for this key into state array.
1659    foreach token [GetTokenList $type] {
1660
1661	switch -- $type {
1662	    dlg {
1663		variable $token
1664		upvar 0 $token xstate
1665	    }
1666	    chat {
1667		variable $token
1668		upvar 0 $token xstate
1669	    }
1670	}
1671	if {[info exists xstate($key)] && [string match $pattern $xstate($key)]} {
1672	    return $token
1673	}
1674    }
1675    return
1676}
1677
1678# GroupChat::GetAllTokensFrom --
1679#
1680#       As above but all tokens.
1681
1682proc ::GroupChat::GetAllTokensFrom {type key pattern} {
1683
1684    if {$key eq "roomjid"} {
1685	set pattern [jlib::jidmap $pattern]
1686    }
1687    set alltokens {}
1688
1689    # Search all tokens for this key into state array.
1690    foreach token [GetTokenList $type] {
1691
1692	switch -- $type {
1693	    dlg {
1694		variable $token
1695		upvar 0 $token xstate
1696	    }
1697	    chat {
1698		variable $token
1699		upvar 0 $token xstate
1700	    }
1701	}
1702	if {[info exists xstate($key)] && [string match $pattern $xstate($key)]} {
1703	    lappend alltokens $token
1704	}
1705    }
1706    return $alltokens
1707}
1708
1709proc ::GroupChat::GetFirstDlgToken {} {
1710
1711    set token ""
1712    set dlgtokens [GetTokenList dlg]
1713    foreach dlgtoken $dlgtokens {
1714	variable $dlgtoken
1715	upvar 0 $dlgtoken dlgstate
1716
1717	if {[winfo exists $dlgstate(w)]} {
1718	    set token $dlgtoken
1719	    break
1720	}
1721    }
1722    return $token
1723}
1724
1725# GroupChat::GetTokenList --
1726#
1727# Arguments:
1728#       type        'dlg' or 'chat'
1729
1730proc ::GroupChat::GetTokenList {type} {
1731
1732    # For some strange reason [info vars] reports non existing arrays.
1733    set nskey [namespace current]::$type
1734    set tokens {}
1735    foreach token [concat  \
1736      [info vars ${nskey}\[0-9\]] \
1737      [info vars ${nskey}\[0-9\]\[0-9\]] \
1738      [info vars ${nskey}\[0-9\]\[0-9\]\[0-9\]] \
1739      [info vars ${nskey}\[0-9\]\[0-9\]\[0-9\]\[0-9\]] \
1740      [info vars ${nskey}\[0-9\]\[0-9\]\[0-9\]\[0-9\]\[0-9\]]] {
1741	if {[array exists $token]} {
1742	    variable $token
1743	    upvar 0 $token state
1744	    if {[info exists state(exists)]} {
1745		lappend tokens $token
1746	    }
1747	}
1748    }
1749    return $tokens
1750}
1751
1752#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1753#
1754#   Functions to handle the treectrl widget.
1755#   It isolates some details to the rest of the code.
1756#
1757#   Tags for each item:
1758#       {role $role}
1759#           {jid $jid}
1760#           {jid $jid}
1761#           ...
1762
1763namespace eval ::GroupChat {
1764
1765    variable initedTreeDB 0
1766}
1767
1768proc ::GroupChat::TreeInitDB {} {
1769    global  this
1770    variable initedTreeDB
1771
1772    # Use option database for customization.
1773    # We use a specific format:
1774    #   element options:    prefix:elementName-option
1775    #   style options:      prefix:styleName:elementName-option
1776
1777    set fillT {
1778	white {selected focus !ignore}
1779	black {selected !focus !ignore}
1780	red   {ignore}
1781    }
1782    set fillB [list $this(sysHighlight) {selected focus} gray {selected !focus}]
1783
1784    # Element options:
1785    option add *GroupChat.utree:eText-font         CociSmallFont           widgetDefault
1786    option add *GroupChat.utree:eText-fill         $fillT                  widgetDefault
1787    option add *GroupChat.utree:eRoleText-font     CociSmallBoldFont       widgetDefault
1788    option add *GroupChat.utree:eRoleText-fill     $fillT                  widgetDefault
1789    option add *GroupChat.utree:eBorder-fill       $fillB                  widgetDefault
1790
1791
1792    # Style layout options:
1793    option add *GroupChat.utree:styUser:eText-padx       2                 widgetDefault
1794    option add *GroupChat.utree:styUser:eText-pady       2                 widgetDefault
1795    option add *GroupChat.utree:styUser:eImage-padx      2                 widgetDefault
1796    option add *GroupChat.utree:styUser:eImage-pady      2                 widgetDefault
1797
1798    option add *GroupChat.utree:styRole:eRoleText-padx       2             widgetDefault
1799    option add *GroupChat.utree:styRole:eRoleText-pady       2             widgetDefault
1800    option add *GroupChat.utree:styRole:eImage-padx          4             widgetDefault
1801    option add *GroupChat.utree:styRole:eImage-pady          2             widgetDefault
1802
1803    set initedTreeDB 1
1804}
1805
1806proc ::GroupChat::Tree {chattoken w T wysc} {
1807    global this
1808    variable initedTreeDB
1809
1810    if {!$initedTreeDB} {
1811	TreeInitDB
1812    }
1813
1814    # BUG: Having -showrootlines 0 still indents the complete tree;
1815    #      Must switch off completely -showlines 0
1816    treectrl $T -selectmode extended  \
1817      -showroot 0 -showrootbutton 0 -showbuttons 0 -showheader 0  \
1818      -showrootlines 0 -showlines 0 \
1819      -yscrollcommand [list ::UI::ScrollSet $wysc     \
1820      [list grid $wysc -row 0 -column 1 -sticky ns]]  \
1821      -borderwidth 0 -highlightthickness 0            \
1822      -height 0 -width 120
1823
1824    # State for ignore.
1825    $T state define ignore
1826
1827    # The columns.
1828    $T column create -tags cTree -resize 0 -expand 1
1829    $T column create -tags cTag -visible 0
1830    $T configure -treecolumn cTree
1831
1832    # The elements.
1833    $T element create eImage     image
1834    $T element create eText      text
1835    $T element create eRoleText  text
1836    $T element create eBorder    rect  -open new -showfocus 1
1837    $T element create eWindow    window
1838
1839    # Styles collecting the elements.
1840    set S [$T style create styUser]
1841    $T style elements $S {eBorder eImage eText}
1842    $T style layout $S eImage  -expand ns
1843    $T style layout $S eText   -squeeze x -expand ns
1844    $T style layout $S eBorder -detach 1 -iexpand xy -indent 0
1845
1846    set S [$T style create styEntry]
1847    $T style elements $S {eBorder eImage eWindow}
1848    $T style layout $S eImage  -expand ns
1849    $T style layout $S eWindow -iexpand xy
1850    $T style layout $S eBorder -detach 1 -iexpand xy -indent 0
1851
1852    set S [$T style create styRole]
1853    $T style elements $S {eBorder eImage eRoleText}
1854    $T style layout $S eImage -expand ns
1855    $T style layout $S eRoleText  -squeeze x -expand ns
1856    $T style layout $S eBorder    -detach 1 -iexpand xy -indent 0
1857
1858    set S [$T style create styTag]
1859    $T style elements $S {eText}
1860
1861    $T column configure cTag  -itemstyle styTag
1862
1863    # This automatically cleans up the tag array.
1864    $T notify bind UsersTreeTag <ItemDelete> {
1865	foreach item %i {
1866	    ::GroupChat::TreeUnsetTags %T $item
1867	}
1868    }
1869    bindtags $T [concat UsersTreeTag [bindtags $T]]
1870
1871    bind $T <Button-1>        [list ::GroupChat::TreeButtonPress $chattoken %W %x %y ]
1872    bind $T <ButtonRelease-1> [list ::GroupChat::TreeButtonRelease $chattoken %W %x %y ]
1873    bind $T <<ButtonPopup>>   [list ::GroupChat::TreePopup $chattoken %W %x %y ]
1874    bind $T <Double-1>        { ::GroupChat::DoubleClick %W %x %y }
1875    bind $T <KeyPress>        +[list ::GroupChat::TreeEditTimerCancel $chattoken]
1876    bind $T <Destroy>         {+::GroupChat::TreeOnDestroy %W }
1877
1878    ::treeutil::setdboptions $T $w utree
1879}
1880
1881proc ::GroupChat::TreeUnsetTags {T item} {
1882    variable tag2item
1883
1884    set tag [$T item element cget $item cTag eText -text]
1885    unset -nocomplain tag2item($T,$tag)
1886}
1887
1888proc ::GroupChat::TreeButtonPress {chattoken T x y} {
1889    variable buttonAfterId
1890    variable buttonPressMillis
1891    variable editTimer
1892
1893    if {[tk windowingsystem] eq "aqua"} {
1894	if {[info exists buttonAfterId]} {
1895	    catch {after cancel $buttonAfterId}
1896	}
1897	set cmd [list ::GroupChat::TreePopup $chattoken $T $x $y]
1898	set buttonAfterId [after $buttonPressMillis $cmd]
1899    }
1900
1901    # Edit bindings.
1902    if {[info exists editTimer(after)]} {
1903	set item [$T identify $x $y]
1904	if {$item eq $editTimer(id)} {
1905	    TreeEditUserStart $chattoken $editTimer(jid)
1906	}
1907    }
1908}
1909
1910proc ::GroupChat::TreeButtonRelease {chattoken T x y} {
1911    variable $chattoken
1912    upvar 0 $chattoken chatstate
1913    variable buttonAfterId
1914    variable waitUntilEditMillis
1915    variable editTimer
1916
1917    if {[info exists buttonAfterId]} {
1918	after cancel $buttonAfterId
1919	unset buttonAfterId
1920    }
1921
1922    # Edit bindings.
1923    set id [$T identify $x $y]
1924    if {([lindex $id 0] eq "item") && ([llength $id] == 6)} {
1925	set item [lindex $id 1]
1926	set tags [$T item element cget $item cTag eText -text]
1927
1928	if {[lindex $tags 0] eq "jid"} {
1929	    set jid [lindex $tags 1]
1930	    set nick [::Jabber::Jlib service mynick $chatstate(roomjid)]
1931	    set myjid $chatstate(roomjid)/$nick
1932	    if {[jlib::jidequal $jid $myjid]} {
1933		set cmd [list ::GroupChat::TreeEditTimerCancel $chattoken]
1934		set editTimer(id)    $id
1935		set editTimer(jid)   $jid
1936		set editTimer(after) [after $waitUntilEditMillis $cmd]
1937	    }
1938	}
1939    }
1940}
1941
1942proc ::GroupChat::TreeEditTimerCancel {chattoken} {
1943    variable editTimer
1944
1945    if {[info exists editTimer(after)]} {
1946	after cancel $editTimer(after)
1947    }
1948    unset -nocomplain editTimer
1949}
1950
1951proc ::GroupChat::TreePopup {chattoken T x y} {
1952    set id [$T identify $x $y]
1953    if {[lindex $id 0] eq "item"} {
1954	set item [lindex $id 1]
1955	set tag [$T item element cget $item cTag eText -text]
1956    } else {
1957	set tag [list]
1958    }
1959    Popup $chattoken $T $tag $x $y
1960}
1961
1962proc ::GroupChat::DoubleClick {T x y} {
1963    global jprefs
1964    variable editTimer
1965
1966    unset -nocomplain editTimer
1967
1968    set id [$T identify $x $y]
1969    if {([lindex $id 0] eq "item") && ([llength $id] == 6)} {
1970	set item [lindex $id 1]
1971	set tags [$T item element cget $item cTag eText -text]
1972
1973	if {[lindex $tags 0] eq "jid"} {
1974	    set jid [lindex $tags 1]
1975	    if {[string equal $jprefs(rost,dblClk) "normal"]} {
1976		::NewMsg::Build -to $jid
1977	    } elseif {[string equal $jprefs(rost,dblClk) "chat"]} {
1978		::Chat::StartThread $jid
1979	    }
1980	}
1981    }
1982}
1983
1984proc ::GroupChat::TreeCreateUserItem {chattoken jid3} {
1985    variable $chattoken
1986    upvar 0 $chattoken chatstate
1987    variable userRoleToStr
1988
1989    set T $chatstate(wusers)
1990
1991    # Cover both a "flat" users list and muc's with the roles
1992    # moderator, participant, and visitor.
1993    set role [GetRoleFromJid $jid3]
1994    if {$role eq ""} {
1995	set pitem root
1996    } else {
1997	set ptag [list role $role]
1998	set pitem [TreeFindWithTag $T $ptag]
1999	if {$pitem eq ""} {
2000	    set pitem [TreeCreateWithTag $T $ptag root]
2001	    set text $userRoleToStr($role)
2002	    set image [::Rosticons::ThemeGet application/group-online]
2003	    $T item style set $pitem cTree styRole
2004	    $T item element configure $pitem cTree \
2005	      eRoleText -text $text + eImage -image $image
2006	    $T item sort root -command [list ::GroupChat::TreeSortRoleCmd $T]
2007	}
2008    }
2009    set tag [list jid $jid3]
2010    set item [TreeFindWithTag $T $tag]
2011    if {$item eq ""} {
2012	set item [TreeCreateWithTag $T $tag $pitem]
2013	$T item style set $item cTree styUser
2014    }
2015    set text [::Jabber::Jlib service nick $jid3]
2016    set text [jlib::unescapestr $text]
2017    set image [::Roster::GetPresenceIconFromJid $jid3]
2018    $T item element configure $item cTree  \
2019      eText -text $text + eImage -image $image
2020}
2021
2022proc ::GroupChat::TreeSortRoleCmd {T item1 item2} {
2023    variable userRoleSortOrder
2024
2025    set tag1 [$T item element cget $item1 cTag eText -text]
2026    set tag2 [$T item element cget $item2 cTag eText -text]
2027
2028    if {([lindex $tag1 0] eq "role") && ([lindex $tag2 0] eq "role")} {
2029	set role1 [lindex $tag1 1]
2030	set role2 [lindex $tag2 1]
2031	if {$userRoleSortOrder($role1) < $userRoleSortOrder($role2)} {
2032	    return -1
2033	} elseif {$userRoleSortOrder($role1) > $userRoleSortOrder($role2)} {
2034	    return 1
2035	} else {
2036	    return 0
2037	}
2038    } else {
2039	return 0
2040    }
2041}
2042
2043proc ::GroupChat::TreeCreateWithTag {T tag parent} {
2044    variable tag2item
2045
2046    set item [$T item create -parent $parent]
2047    set tag2item($T,$tag) $item
2048
2049    # Handle the hidden cTag column.
2050    $T item element configure $item cTag eText -text $tag
2051    return $item
2052}
2053
2054proc ::GroupChat::TreeFindWithTag {T tag} {
2055    variable tag2item
2056
2057    if {[info exists tag2item($T,$tag)]} {
2058	return $tag2item($T,$tag)
2059    } else {
2060	return
2061    }
2062}
2063
2064proc ::GroupChat::TreeSetIgnoreState {T jid3 {prefix ""}} {
2065    variable tag2item
2066
2067    set tag [list jid $jid3]
2068    if {[info exists tag2item($T,$tag)]} {
2069	set item $tag2item($T,$tag)
2070	$T item state set $item ${prefix}ignore
2071    }
2072}
2073
2074proc ::GroupChat::TreeEditUserStart {chattoken jid3} {
2075    variable tag2item
2076    variable $chattoken
2077    upvar 0 $chattoken chatstate
2078
2079    set T $chatstate(wusers)
2080    set tag [list jid $jid3]
2081
2082    if {[info exists tag2item($T,$tag)]} {
2083	set item $tag2item($T,$tag)
2084	set image [::Roster::GetPresenceIconFromJid $jid3]
2085	set wentry $T.entry
2086	if {[winfo exists $wentry]} {
2087	    return
2088	}
2089	set chatstate(editNick) [jlib::resourcejid $jid3]
2090	entry $wentry -font CociSmallFont \
2091	  -textvariable $chattoken\(editNick) -width 1
2092	$T item style set $item cTree styEntry
2093	$T item element configure $item cTree \
2094	  eImage -image $image + eWindow -window $wentry
2095	focus $wentry
2096	# This creates a focus out on mac!
2097	#$wentry selection range 0 end
2098	bind $wentry <Return>   \
2099	  [list ::GroupChat::TreeOnReturnEdit $chattoken $jid3]
2100	bind $wentry <KP_Enter>   \
2101	  [list ::GroupChat::TreeOnReturnEdit $chattoken $jid3]
2102	bind $wentry <FocusOut> \
2103	  [list ::GroupChat::TreeEditUserEnd $chattoken $jid3]
2104    }
2105}
2106
2107proc ::GroupChat::TreeOnReturnEdit {chattoken jid3} {
2108    variable $chattoken
2109    upvar 0 $chattoken chatstate
2110
2111    set T $chatstate(wusers)
2112    set wentry $T.entry
2113    set nick $chatstate(editNick)
2114    if {[string length $nick]} {
2115	SetNick $chattoken $nick
2116    }
2117    focus $chatstate(w)
2118}
2119
2120proc ::GroupChat::TreeEditUserEnd {chattoken jid3} {
2121    variable tag2item
2122    variable $chattoken
2123    upvar 0 $chattoken chatstate
2124
2125    set T $chatstate(wusers)
2126    set tag [list jid $jid3]
2127
2128    if {[info exists tag2item($T,$tag)]} {
2129	set item $tag2item($T,$tag)
2130	set image [::Roster::GetPresenceIconFromJid $jid3]
2131	set text [jlib::resourcejid $jid3]
2132	$T item style set $item cTree styUser
2133	$T item element configure $item cTree \
2134	  eImage -image $image + eText -text $text
2135	destroy $T.entry
2136    }
2137}
2138
2139proc ::GroupChat::TreeRemoveUser {chattoken jid3} {
2140    variable $chattoken
2141    upvar 0 $chattoken chatstate
2142
2143    set T $chatstate(wusers)
2144    set tag [list jid $jid3]
2145    TreeDeleteItem $T $tag
2146
2147    unset -nocomplain chatstate(ignore,$jid3)
2148}
2149
2150proc ::GroupChat::TreeDeleteItem {T tag} {
2151    variable tag2item
2152
2153    if {[info exists tag2item($T,$tag)]} {
2154	$T item delete $tag2item($T,$tag)
2155    }
2156}
2157
2158proc ::GroupChat::TreeDeleteAll {T} {
2159    $T item delete all
2160}
2161
2162proc ::GroupChat::TreeOnDestroy {T} {
2163    variable tag2item
2164    array unset tag2item $T,*
2165}
2166
2167#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
2168
2169proc ::GroupChat::StatusPostCmd {chattoken} {
2170    variable $chattoken
2171    upvar 0 $chattoken chatstate
2172
2173    set wbtstatus $chatstate(wbtstatus)
2174    if {[IsInRoom $chatstate(roomjid)]} {
2175	::Status::MenuSetState $wbtstatus all normal
2176    } else {
2177	::Status::MenuSetState $wbtstatus all disabled
2178	::Status::MenuSetState $wbtstatus available normal
2179    }
2180}
2181
2182proc ::GroupChat::StatusCmd {chattoken show args} {
2183    variable $chattoken
2184    upvar 0 $chattoken chatstate
2185
2186    ::Debug 2 "::GroupChat::StatusCmd show=$show, args=$args"
2187
2188    if {$show eq "unavailable"} {
2189	set ans [ExitAndClose $chattoken]
2190	if {$ans eq "no"} {
2191	    set chatstate(show) $chatstate(oldShow)
2192	}
2193    } else {
2194	set roomjid $chatstate(roomjid)
2195	if {[IsInRoom $roomjid]} {
2196	    eval {::Jabber::SetStatus $show -to $roomjid} $args
2197	    set chatstate(oldShow) $show
2198	} else {
2199	    EnterOrCreate enter -roomjid $roomjid
2200	}
2201    }
2202}
2203
2204proc ::GroupChat::ExStatusPostCmd {chattoken} {
2205    variable $chattoken
2206    upvar 0 $chattoken chatstate
2207
2208    set wbtstatus $chatstate(wbtstatus)
2209    set m [::Status::ExGetMenu $wbtstatus]
2210    if {[IsInRoom $chatstate(roomjid)]} {
2211	::Status::ExMenuSetState $m all normal
2212    } else {
2213	::Status::ExMenuSetState $m all disabled
2214	::Status::ExMenuSetState $m available normal
2215    }
2216}
2217
2218proc ::GroupChat::ExStatusCmd {chattoken} {
2219    variable $chattoken
2220    upvar 0 $chattoken chatstate
2221
2222    set show   [lindex $chatstate(show+status) 0]
2223    set status [lindex $chatstate(show+status) 1]
2224    if {$show eq "unavailable"} {
2225	set ans [ExitAndClose $chattoken]
2226	if {$ans eq "no"} {
2227	    set chatstate(show+status) $chatstate(oldShow+status)
2228	}
2229    } else {
2230	set roomjid $chatstate(roomjid)
2231	if {[IsInRoom $roomjid]} {
2232	    ::Jabber::SetStatus $show -to $roomjid -status $status
2233	    set chatstate(oldShow+status) $show
2234	} else {
2235	    EnterOrCreate enter -roomjid $roomjid
2236	}
2237    }
2238}
2239
2240proc ::GroupChat::StatusSyncHook {show args} {
2241    global jprefs
2242
2243    if {$show eq "unavailable"} {
2244	# This is better handled via the logout hook.
2245	return
2246    }
2247    set argsA(-status) ""
2248    array set argsA $args
2249
2250    if {$jprefs(gchat,syncPres) && ![info exists argsA(-to)]} {
2251	foreach chattoken [GetTokenList chat] {
2252	    variable $chattoken
2253	    upvar 0 $chattoken chatstate
2254
2255	    set roomjid $chatstate(roomjid)
2256	    if {[IsInRoom $roomjid]} {
2257		::Jabber::SetStatus $show -to $roomjid -status $argsA(-status)
2258		set chatstate(show)    $show
2259		set chatstate(oldShow) $show
2260		set chatstate(show+status)    [list $show $argsA(-status)]
2261		set chatstate(oldShow+status) [list $show $argsA(-status)]
2262	    }
2263	}
2264    }
2265}
2266
2267# GroupChat::InsertMessage --
2268#
2269#       Puts message in text groupchat window.
2270
2271#proc ::GroupChat::InsertMessage {chattoken from body args}
2272
2273proc ::GroupChat::InsertMessage {chattoken xmldata} {
2274    variable $chattoken
2275    upvar 0 $chattoken chatstate
2276
2277    set tag  [wrapper::gettag $xmldata]
2278    set from [wrapper::getattribute $xmldata from]
2279
2280    set w       $chatstate(w)
2281    set wtext   $chatstate(wtext)
2282    set roomjid $chatstate(roomjid)
2283
2284    puts $wtext
2285
2286    set haveSys 0
2287    if {$tag eq "presence"} {
2288	set sysstr [PresenceGetString $chattoken $xmldata]
2289	set haveSys 1
2290    }
2291
2292    # This can be room name or nick name.
2293    set mynick [::Jabber::Jlib service mynick $roomjid]
2294    set myroomjid $roomjid/$mynick
2295    if {[jlib::jidequal $myroomjid $from]} {
2296	set whom me
2297	set historyTag send
2298    } else {
2299	set whom they
2300	set historyTag recv
2301    }
2302    set nick [jlib::unescapestr [jlib::resourcejid $from]]
2303
2304    set history 0
2305    set msecs [clock clicks -milliseconds]
2306    if {$tag eq "presence"} {
2307	set chatstate(last,sys) $msecs
2308    } else {
2309	set chatstate(last,$whom) $msecs
2310    }
2311
2312    set secs ""
2313    set stamp [::Jabber::GetDelayStamp $xmldata]
2314    if {$stamp ne ""} {
2315	set secs [clock scan $stamp -timezone :UTC]
2316	set history 1
2317    }
2318    if {$secs eq ""} {
2319	set secs [clock seconds]
2320    }
2321    if {[::Utils::IsToday $secs]} {
2322	set clockFormat [option get $w clockFormat {}]
2323    } else {
2324	set clockFormat [option get $w clockFormatNotToday {}]
2325    }
2326    if {$clockFormat ne ""} {
2327	set theTime [clock format $secs -format $clockFormat]
2328	set prefix "\[$theTime\] "
2329    } else {
2330	set prefix ""
2331    }
2332    if {$nick ne ""} {
2333	append prefix "<$nick>"
2334    }
2335    set htag ""
2336    if {$history} {
2337	set htag -history
2338    }
2339    set pretags ${whom}pre${htag}
2340
2341    if {$whom ne "me"} {
2342	set idx [mstack::get $chatstate(mstack) $from]
2343	if {$idx >= 0} {
2344	    lappend pretags scheme-$idx
2345	}
2346    }
2347    $wtext mark set insert end
2348    $wtext configure -state normal
2349
2350    if {$haveSys} {
2351	set spec sys
2352
2353	set syspretags [concat syspre$htag $spec]
2354	set systxttags [concat systext$htag $spec]
2355
2356	$wtext insert end $prefix $syspretags
2357	$wtext insert insert "   "   $systxttags
2358	::Text::ParseMsg groupchat $from $wtext $sysstr $systxttags
2359	$wtext insert end "\n" $systxttags
2360    }
2361
2362    set subjectE [wrapper::getfirstchildwithtag $xmldata "subject"]
2363    if {[llength $subjectE]} {
2364	set subject [wrapper::getcdata $subjectE]
2365	set str [mc "Subject"]
2366	append str ": $subject"
2367	set txttags ${whom}text${htag}
2368
2369	$wtext insert end $prefix $pretags
2370	$wtext insert insert "   "   $txttags
2371	::Text::ParseMsg groupchat $from $wtext $str $txttags
2372	$wtext insert end "\n" $txttags
2373    }
2374
2375    if {$tag eq "message"} {
2376	set bodyE [wrapper::getfirstchildwithtag $xmldata "body"]
2377	if {[llength $bodyE]} {
2378	    set txttags ${whom}text${htag}
2379
2380	    set body [wrapper::getcdata $bodyE]
2381
2382	    $wtext insert end $prefix $pretags
2383	    $wtext insert insert "   "   $txttags
2384	    ::Text::ParseMsg groupchat $from $wtext $body $txttags
2385	    $wtext insert end "\n" $txttags
2386	}
2387    }
2388    $wtext configure -state disabled
2389    $wtext see end
2390
2391    # Even though we also receive what we send, denote this with send anyway.
2392    # This can be used to get our own room JID (nick name).
2393    ::History::XPutItem $historyTag $roomjid $xmldata
2394}
2395
2396proc ::GroupChat::InsertTagString {chattoken str tag} {
2397    variable $chattoken
2398    upvar 0 $chattoken chatstate
2399
2400    set wtext $chatstate(wtext)
2401
2402    $wtext mark set insert end
2403    $wtext configure -state normal
2404
2405    $wtext insert end $str $tag
2406
2407    $wtext configure -state disabled
2408    $wtext see end
2409}
2410
2411# GroupChat::CloseCmd --
2412#
2413#       This gets called from toplevels -closecommand
2414
2415proc ::GroupChat::CloseCmd {wclose} {
2416    global  wDlgs
2417
2418    ::Debug 2  "::GroupChat::CloseCmd $wclose"
2419
2420    set dlgtoken [GetTokenFrom dlg w $wclose]
2421    if {$dlgtoken ne ""} {
2422	variable $dlgtoken
2423	upvar 0 $dlgtoken dlgstate
2424
2425	set chattoken [GetActiveChatToken $dlgtoken]
2426	variable $chattoken
2427	upvar 0 $chattoken chatstate
2428
2429	# Do we want to close each tab or complete window?
2430	set closetab 1
2431	set chattokens $dlgstate(chattokens)
2432	::UI::SaveSashPos groupchatDlgVert $chatstate(wpanev)
2433	::UI::SaveSashPos groupchatDlgHori $chatstate(wpaneh)
2434
2435	# User pressed windows close button.
2436	if {[::UI::GetCloseWindowType] eq "wm"} {
2437	    set closetab 0
2438	}
2439
2440	# All rooms need an explicit Exit, but tab only needs CloseRoomPage.
2441	if {$closetab} {
2442	    if {[llength $chattokens] >= 2} {
2443		Exit $chattoken
2444		CloseRoomPage $chattoken
2445		set closetoplevel 0
2446	    } else {
2447		set closetoplevel 1
2448	    }
2449	} else {
2450	    set closetoplevel 1
2451	}
2452	if {$closetoplevel} {
2453	    ::UI::SaveWinGeom $wDlgs(jgc) $dlgstate(w)
2454	    foreach chattoken $chattokens {
2455		Exit $chattoken
2456	    }
2457	} else {
2458	    # Since we only want to close a tab.
2459	    return "stop"
2460	}
2461    } else {
2462	return
2463    }
2464}
2465
2466proc ::GroupChat::CloseRoomPage {chattoken} {
2467    variable $chattoken
2468    upvar 0 $chattoken chatstate
2469
2470    set dlgtoken $chatstate(dlgtoken)
2471    DeletePage $chattoken
2472    set newchattoken [GetActiveChatToken $dlgtoken]
2473
2474    # Set state of new page.
2475    SetRoomState $dlgtoken $newchattoken
2476}
2477
2478# GroupChat::ExitAndClose --
2479#
2480#       Handles both protocol and ui parts for closing a room.
2481#
2482# Arguments:
2483#       roomjid
2484#
2485# Results:
2486#       yes/no if actually exited or not.
2487
2488proc ::GroupChat::ExitAndClose {chattoken} {
2489    global  wDlgs
2490    variable $chattoken
2491    upvar 0 $chattoken chatstate
2492
2493    ::Debug 2  "::GroupChat::ExitAndClose $chattoken"
2494
2495    set ans "yes"
2496    if {[::Jabber::IsConnected]} {
2497	if {0} {
2498	    # This could be optional.
2499	    set ans [ExitWarn $chattoken]
2500	}
2501	if {$ans eq "yes"} {
2502	    Exit $chattoken
2503	} else {
2504	    return $ans
2505	}
2506    }
2507
2508    # Do we want to close each tab or complete window?
2509    set dlgtoken $chatstate(dlgtoken)
2510    variable $dlgtoken
2511    upvar 0 $dlgtoken dlgstate
2512
2513    set chattokens $dlgstate(chattokens)
2514
2515    if {[llength $chattokens] >= 2} {
2516	::UI::SaveSashPos groupchatDlgVert $chatstate(wpanev)
2517	::UI::SaveSashPos groupchatDlgHori $chatstate(wpaneh)
2518	CloseRoomPage $chattoken
2519    } else {
2520	::UI::SaveWinGeom $wDlgs(jgc) $dlgstate(w)
2521	destroy $dlgstate(w)
2522    }
2523    return $ans
2524}
2525
2526proc ::GroupChat::ExitWarn {chattoken} {
2527    variable $chattoken
2528    upvar 0 $chattoken chatstate
2529
2530    if {[info exists chatstate(w)] && [winfo exists $chatstate(w)]} {
2531	set opts [list -parent $chatstate(w)]
2532    } else {
2533	set opts ""
2534    }
2535    set roomjid $chatstate(roomjid)
2536    return [eval {::UI::MessageBox -icon warning -type yesno  \
2537      -message [mc "Do you want to exit the chatroom %s?" $roomjid]} $opts]
2538}
2539
2540# GroupChat::Exit --
2541#
2542#       Handles the protocol part of exiting room.
2543
2544proc ::GroupChat::Exit {chattoken} {
2545    variable $chattoken
2546    upvar 0 $chattoken chatstate
2547
2548    ::Debug 2  "::GroupChat::Exit $chattoken"
2549
2550    set roomjid $chatstate(roomjid)
2551    ::Jabber::Jlib presence_deregister_ex [namespace code PresenceEvent]  \
2552      -from2 $roomjid
2553    if {[::Jabber::IsConnected]} {
2554	set nick [::Jabber::Jlib service mynick $roomjid]
2555	set myroomjid $roomjid/$nick
2556	set attr [list from $myroomjid to $roomjid type unavailable]
2557	set xmldata [wrapper::createtag "presence" -attrlist $attr]
2558	::History::XPutItem send $roomjid $xmldata
2559
2560	::Jabber::Jlib service exitroom $roomjid
2561	::hooks::run groupchatExitRoomHook $roomjid
2562    }
2563}
2564
2565# GroupChat::ExitRoomJID --
2566#
2567#       Just a wrapper for Exit.
2568
2569proc ::GroupChat::ExitRoomJID {roomjid} {
2570
2571    set roomjid [jlib::jidmap $roomjid]
2572    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
2573    if {$chattoken ne ""} {
2574	return [ExitAndClose $chattoken]
2575    } else {
2576	return ""
2577    }
2578}
2579
2580proc ::GroupChat::ConfigureTextTags {w wtext} {
2581    global jprefs
2582    variable groupChatOptions
2583
2584    ::Debug 2 "::GroupChat::ConfigureTextTags wtext=$wtext"
2585
2586    set space 2
2587    set alltags {mepre metext theypre theytext syspre systext histhead}
2588
2589    if {[string length $jprefs(chatFont)]} {
2590	set chatFont $jprefs(chatFont)
2591    } else {
2592	set chatFont [option get $wtext font Font]
2593    }
2594    set foreground [$wtext cget -foreground]
2595    foreach tag $alltags {
2596	set opts($tag) [list -spacing1 $space -foreground $foreground]
2597    }
2598    foreach spec $groupChatOptions {
2599	lassign $spec tag optName resName resClass
2600	set value [option get $w $resName $resClass]
2601	if {$optName eq "-fontSlant"} {
2602	    if {$value eq "italic"} {
2603		lappend opts($tag) -font [::Utils::FontItalic $chatFont]
2604	    }
2605	} elseif {$optName eq "-font"} {
2606	    set value $chatFont
2607	    if {$value ne ""} {
2608		lappend opts($tag) $optName $value
2609	    }
2610	} else {
2611	    if {$value ne ""} {
2612		lappend opts($tag) $optName $value
2613	    }
2614	}
2615    }
2616    lappend opts(metext)   -spacing3 $space -lmargin1 20 -lmargin2 20
2617    lappend opts(theytext) -spacing3 $space -lmargin1 20 -lmargin2 20
2618    lappend opts(systext)  -spacing3 $space -lmargin1 20 -lmargin2 20
2619    lappend opts(histhead) -spacing1 4 -spacing3 4 -lmargin1 20 -lmargin2 20
2620
2621    foreach tag $alltags {
2622	eval {$wtext tag configure $tag} $opts($tag)
2623    }
2624    ConfigureSchemeTags $wtext
2625
2626    # History tags.
2627    foreach tag $alltags {
2628	set htag ${tag}-history
2629	array unset arr
2630	array set arr $opts($tag)
2631	set arr(-foreground) [::colorutils::getlighter $arr(-foreground)]
2632	eval {$wtext tag configure $htag} [array get arr]
2633    }
2634}
2635
2636proc ::GroupChat::ConfigureSchemeTags {wtext} {
2637    global jprefs
2638    variable schemes
2639
2640    # Color scheme tags.
2641    set use $jprefs(gchat,useScheme)
2642    set name $jprefs(gchat,colScheme)
2643    for {set n 0} {$n < 5} {incr n} {
2644	if {$use} {
2645	    set col [lindex $schemes($name) $n]
2646	} else {
2647	    set col ""
2648	}
2649	$wtext tag configure scheme-$n -foreground $col
2650    }
2651}
2652
2653proc ::GroupChat::SetSchemeAll {} {
2654
2655    foreach chattoken [GetTokenList chat] {
2656	variable $chattoken
2657	upvar 0 $chattoken chatstate
2658	ConfigureSchemeTags $chatstate(wtext)
2659    }
2660}
2661
2662proc ::GroupChat::SetFontAll {} {
2663    global jprefs
2664
2665    foreach chattoken [GetTokenList chat] {
2666	variable $chattoken
2667	upvar 0 $chattoken chatstate
2668
2669	ConfigureTextTags $chatstate(w) $chatstate(wtext)
2670	if {$jprefs(chatFont) eq ""} {
2671	    $chatstate(wtextsend) configure -font \
2672	      [option get $chatstate(wtext) font Font]
2673	} else {
2674	    $chatstate(wtextsend) configure -font $jprefs(chatFont)
2675	}
2676    }
2677}
2678
2679proc ::GroupChat::SetNick {chattoken nick} {
2680    variable $chattoken
2681    upvar 0 $chattoken chatstate
2682
2683    set jid $chatstate(roomjid)/$nick
2684    ::Jabber::Jlib service setnick $chatstate(roomjid) $nick \
2685      -command [list ::GroupChat::SetNickCB $chattoken]
2686
2687    #::Jabber::Jlib send_presence -to $jid \
2688    #  -command [list ::GroupChat::SetNickCB $chattoken]
2689}
2690
2691proc ::GroupChat::SetNickCB {chattoken jlib xmldata} {
2692    variable $chattoken
2693    upvar 0 $chattoken chatstate
2694
2695    set from [wrapper::getattribute $xmldata from]
2696    set type [wrapper::getattribute $xmldata type]
2697
2698    set chatstate(mynick) [::Jabber::Jlib service mynick $chatstate(roomjid)]
2699
2700    if {[string equal $type "error"]} {
2701	set errspec [jlib::getstanzaerrorspec $xmldata]
2702	set errmsg ""
2703	if {[llength $errspec]} {
2704	    set errcode [lindex $errspec 0]
2705	    set errmsg  [lindex $errspec 1]
2706	}
2707	jlib::splitjidex $from roomName - -
2708	set str [mc "Cannot interact with the chatroom %s." $roomName]
2709	append str "\n"
2710	append str [mc "Error"]
2711	append str ": $errmsg"
2712	::UI::MessageBox -type ok -icon error -title [mc "Error"] -message $str
2713    }
2714}
2715
2716proc ::GroupChat::Send {dlgtoken} {
2717
2718    # Check that still connected to server.
2719    if {![::Jabber::IsConnected]} {
2720	::UI::MessageBox -type ok -icon error -title [mc "Error"] \
2721	  -message [mc "Cannot send when not logged in."]
2722	return
2723    }
2724    SendChat [GetActiveChatToken $dlgtoken]
2725}
2726
2727proc ::GroupChat::SendChat {chattoken} {
2728    variable $chattoken
2729    upvar 0 $chattoken chatstate
2730
2731    set wtextsend $chatstate(wtextsend)
2732    set roomjid   $chatstate(roomjid)
2733
2734    # Get text to send. Strip off any ending newlines from Return.
2735    # There might by smiley icons in the text widget. Parse them to text.
2736    set text [::Text::TransformToPureText $wtextsend]
2737    set text [string trimright $text]
2738    set chatstate(lasttext) $text
2739
2740    # Clear send.
2741    $wtextsend delete 1.0 end
2742
2743    # Have hook for complete text.
2744    if {[::hooks::run sendTextGroupChatHook $roomjid $text] eq "stop"} {
2745	return
2746    }
2747
2748    if {[string length $text]} {
2749	::Jabber::Jlib send_message $roomjid -type groupchat -body $text
2750    }
2751}
2752
2753proc ::GroupChat::ActiveCmd {chattoken} {
2754    variable cprefs
2755    variable $chattoken
2756    upvar 0 $chattoken chatstate
2757
2758    # Remember last setting.
2759    set cprefs(lastActiveRet) $chatstate(active)
2760}
2761
2762proc ::GroupChat::OnKeyUp {chattoken} {
2763    variable $chattoken
2764    upvar 0 $chattoken chatstate
2765
2766    $chatstate(wtextsend) delete 1.0 end
2767    $chatstate(wtextsend) insert end $chatstate(lasttext)
2768}
2769
2770proc ::GroupChat::OnKeyDown {chattoken} {
2771    variable $chattoken
2772    upvar 0 $chattoken chatstate
2773
2774    $chatstate(wtextsend) delete 1.0 end
2775}
2776
2777# Suggestion from marc@bruenink.de.
2778#
2779#       inactive mode:
2780#       Ret: word-wrap
2781#       Ctrl+Ret: send messgae
2782#
2783#       active mode:
2784#       Ret: send message
2785#       Ctrl+Ret: word-wrap
2786
2787proc ::GroupChat::ReturnKeyPress {chattoken} {
2788    variable $chattoken
2789    upvar 0 $chattoken chatstate
2790
2791    if {$chatstate(active)} {
2792	SendChat $chattoken
2793
2794	# Stop the actual return to be inserted.
2795	return -code break
2796    }
2797}
2798
2799proc ::GroupChat::CommandReturnKeyPress {chattoken} {
2800    variable $chattoken
2801    upvar 0 $chattoken chatstate
2802
2803    if {!$chatstate(active)} {
2804	SendChat $chattoken
2805
2806	# Stop further handling in Text.
2807	return -code break
2808    }
2809}
2810
2811# GroupChat::PresenceEvent --
2812#
2813#       Callback for any presence change related to roomjid and roomjid/*
2814#       Note that our own "enter presence" comes too early to be detected.
2815#
2816# Some msn components may send presence directly from a room when
2817# a chat invites you to a multichat:
2818# <presence
2819#     from='r1@msn.jabber.ccc.de/marilund60@hotmail.com'
2820#     to='matben@jabber.ccc.de'/>
2821#
2822# Note that a conference service may also be a gateway!
2823
2824proc ::GroupChat::PresenceEvent {jlibname xmldata} {
2825    global  config
2826    upvar ::Jabber::xmppxmlns xmppxmlns
2827
2828    set from [wrapper::getattribute $xmldata from]
2829    set type [wrapper::getattribute $xmldata type]
2830    if {$type eq ""} {
2831	set type available
2832    }
2833    jlib::splitjid $from roomjid nick
2834
2835    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
2836    if {$chattoken ne ""} {
2837	if {[string equal $type "available"]} {
2838	    SetUser $roomjid $from
2839	} elseif {[string equal $type "unavailable"]} {
2840	    RemoveUser $roomjid $from
2841	}
2842
2843	if {$config(groupchat,show-sysmsgs)} {
2844	    lappend chatstate(afterids) [after 200 [list  \
2845	      ::GroupChat::InsertPresenceChange $chattoken $xmldata]]
2846	}
2847
2848	# When kicked etc. from a MUC room...
2849	#
2850	#  <x xmlns='http://jabber.org/protocol/muc#user'>
2851	#    <item affiliation='none' role='none'>
2852	#      <actor jid='fluellen@shakespeare.lit'/>
2853	#      <reason>Avaunt, you cullion!</reason>
2854	#    </item>
2855	#    <status code='307'/>
2856	#  </x>
2857
2858	set xE [wrapper::getfirstchild $xmldata x $xmppxmlns(muc,user)]
2859
2860	# @@@ TODO
2861    }
2862}
2863
2864proc ::GroupChat::InsertPresenceChange {chattoken xmldata} {
2865    variable $chattoken
2866    upvar 0 $chattoken chatstate
2867
2868    if {[info exists chatstate(w)] && [winfo exists $chatstate(w)]} {
2869
2870	# Some services send out presence changes automatically.
2871	# This should only be called if not the room does it.
2872	# ejabberd does not. Skip it!
2873	set ms [clock clicks -milliseconds]
2874	if {[expr {$ms - $chatstate(last,sys) < 400}]} {
2875	    #return
2876	}
2877	InsertMessage $chattoken $xmldata
2878    }
2879}
2880
2881proc ::GroupChat::PresenceGetString {chattoken xmldata} {
2882    variable $chattoken
2883    upvar 0 $chattoken chatstate
2884
2885    set from [wrapper::getattribute $xmldata from]
2886    jlib::splitjid $from jid2 res
2887    if {$res eq ""} {
2888	jlib::splitjidex $from node domain res
2889	set name $node
2890    } else {
2891	set name $res
2892    }
2893    if {$res eq ""} {
2894	array set presA [lindex [::Jabber::Jlib roster getpresence $jid2] 0]
2895    } else {
2896	array set presA [::Jabber::Jlib roster getpresence $jid2 -resource $res]
2897    }
2898    set show $presA(-type)
2899    if {[info exists presA(-show)]} {
2900	set show $presA(-show)
2901    }
2902
2903    # The Gtalk server is playing games by sending out multiple identical
2904    # presence to us. It acts very weird! No workaround.
2905    set str [string tolower [::Roster::MapShowToText $show]]
2906    if {[info exists presA(-status)]} {
2907	append str " " $presA(-status)
2908    }
2909    return $str
2910}
2911
2912proc ::GroupChat::AddUsers {chattoken} {
2913    variable $chattoken
2914    upvar 0 $chattoken chatstate
2915
2916    set roomjid $chatstate(roomjid)
2917
2918    set presenceList [::Jabber::Jlib roster getpresence $roomjid -type available]
2919    foreach pres $presenceList {
2920	unset -nocomplain presA
2921	array set presA $pres
2922
2923	set res $presA(-resource)
2924	if {$res ne ""} {
2925	    set jid3 $roomjid/$res
2926	    SetUser $roomjid $jid3
2927	}
2928    }
2929}
2930
2931# GroupChat::SetUser --
2932#
2933#       Adds or updates a user item in the group chat dialog.
2934#
2935# Arguments:
2936#       roomjid     the room's jid
2937#       jid3        roomjid/hashornick
2938#
2939# Results:
2940#       updated UI.
2941
2942proc ::GroupChat::SetUser {roomjid jid3} {
2943    global  this
2944
2945    variable userRoleToStr
2946
2947    ::Debug 2 "::GroupChat::SetUser roomjid=$roomjid, jid3=$jid3"
2948
2949    set roomjid [jlib::jidmap $roomjid]
2950    set jid3    [jlib::jidmap $jid3]
2951
2952    # If we haven't a window for this thread, make one!
2953    # @@@ This shouldn't be necessary since we fill in all users when
2954    #     making the room widget.
2955    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
2956    if {$chattoken eq ""} {
2957	set chattoken [NewChat $roomjid]
2958    }
2959    variable $chattoken
2960    upvar 0 $chattoken chatstate
2961
2962    # Don't forget to init the ignore state.
2963    if {![info exists chatstate(ignore,$jid3)]} {
2964	set chatstate(ignore,$jid3) 0
2965    }
2966
2967    # Associate a color sceme index for each user except ourself.
2968    set mynick [::Jabber::Jlib service mynick $roomjid]
2969    set myroomjid $roomjid/$mynick
2970    if {![jlib::jidequal $myroomjid $jid3]} {
2971	set mstack $chatstate(mstack)
2972	if {![mstack::exists $mstack $jid3]} {
2973	    mstack::add $mstack $jid3
2974	}
2975    }
2976    TreeCreateUserItem $chattoken $jid3
2977}
2978
2979proc ::GroupChat::GetRoleFromJid {jid3} {
2980
2981    set role ""
2982    set userElem [::Jabber::Jlib roster getx $jid3 "muc#user"]
2983    if {$userElem != {}} {
2984	set ilist [wrapper::getchildswithtag $userElem "item"]
2985	if {$ilist != {}} {
2986	    set item [lindex $ilist 0]
2987	    set role [wrapper::getattribute $item "role"]
2988	}
2989    }
2990    return $role
2991}
2992
2993proc ::GroupChat::GetAnyRoleFromXElem {xelem} {
2994    upvar ::Jabber::xmppxmlns xmppxmlns
2995
2996    set role ""
2997    set clist [wrapper::getnamespacefromchilds $xelem x $xmppxmlns(muc,user)]
2998    set userElem [lindex $clist 0]
2999    if {[llength $userElem]} {
3000	set ilist [wrapper::getchildswithtag $userElem "item"]
3001	set item [lindex $ilist 0]
3002	if {[llength $item]} {
3003	    set role [wrapper::getattribute $item "role"]
3004	}
3005    }
3006    return $role
3007}
3008
3009# GroupChat::RegisterPopupEntry --
3010#
3011#       Components or plugins can add their own menu entries here.
3012
3013proc ::GroupChat::RegisterPopupEntry {menuDef menuType} {
3014    variable regPopMenuDef
3015    variable regPopMenuType
3016
3017    set regPopMenuDef  [concat $regPopMenuDef $menuDef]
3018    set regPopMenuType [concat $regPopMenuType $menuType]
3019}
3020
3021proc ::Disco::UnRegisterPopupEntry {name} {
3022    variable regPopMenuDef
3023    variable regPopMenuType
3024
3025    set idx [lsearch -glob $regPopMenuDef "* $name *"]
3026    if {$idx >= 0} {
3027	set regPopMenuDef [lreplace $regPopMenuDef $idx $idx]
3028    }
3029    set idx [lsearch -glob $regPopMenuType "$name *"]
3030    if {$idx >= 0} {
3031	set regPopMenuType [lreplace $regPopMenuType $idx $idx]
3032    }
3033}
3034
3035# GroupChat::Popup --
3036#
3037#       Handle popup menu in groupchat dialog.
3038#
3039# Arguments:
3040#       w           widgetPath of treectrl
3041#       tag         Tree tag
3042#
3043# Results:
3044#       popup menu displayed
3045
3046proc ::GroupChat::Popup {chattoken w tag x y} {
3047    global  wDlgs this
3048    variable $chattoken
3049    upvar 0 $chattoken chatstate
3050
3051    variable popMenuDefs
3052    variable regPopMenuDef
3053    variable regPopMenuType
3054
3055    set clicked ""
3056    set jid ""
3057    set nick [::Jabber::Jlib service mynick $chatstate(roomjid)]
3058    set myjid $chatstate(roomjid)/$nick
3059    if {[lindex $tag 0] eq "role"} {
3060	set clicked role
3061    } elseif {[lindex $tag 0] eq "jid"} {
3062	set clicked user
3063	set jid [lindex $tag 1]
3064	if {[jlib::jidequal $jid $myjid]} {
3065	    set clicked me
3066	}
3067    }
3068
3069    ::Debug 2 "\t jid=$jid, clicked=$clicked"
3070
3071    # Insert any registered popup menu entries.
3072    set mDef  $popMenuDefs(groupchat,def)
3073    set mType $popMenuDefs(groupchat,type)
3074    if {[llength $regPopMenuDef]} {
3075	set idx [lindex [lsearch -glob -all $mDef {sep*}] end]
3076	if {$idx eq ""} {
3077	    set idx end
3078	}
3079	foreach line $regPopMenuDef {
3080	    set mDef [linsert $mDef $idx $line]
3081	}
3082	set mDef [linsert $mDef $idx {separator}]
3083    }
3084    foreach line $regPopMenuType {
3085	lappend mType $line
3086    }
3087
3088    # Make the appropriate menu.
3089    set m $wDlgs(jpopupgroupchat)
3090    catch {destroy $m}
3091    menu $m -tearoff 0  \
3092      -postcommand [list ::GroupChat::PostMenuCmd $m $mType $clicked]
3093
3094    ::AMenu::Build $m $mDef -varlist [list jid $jid chattoken $chattoken]
3095
3096    # This one is needed on the mac so the menu is built before it is posted.
3097    update idletasks
3098
3099    # Post popup menu.
3100    set X [expr {[winfo rootx $w] + $x}]
3101    set Y [expr {[winfo rooty $w] + $y}]
3102    tk_popup $m [expr {int($X) - 10}] [expr {int($Y) - 10}]
3103}
3104
3105proc ::GroupChat::PostMenuCmd {m mType clicked} {
3106
3107    set online [::Jabber::IsConnected]
3108    ::hooks::run groupchatUserPostCommandHook $m $clicked
3109
3110    foreach mspec $mType {
3111	lassign $mspec name type subType
3112
3113	# State of menu entry.
3114	# We use the 'type' and 'clicked' lists to set the state.
3115	if {$type eq "normal"} {
3116	    set state normal
3117	} elseif {$online} {
3118	    if {[listintersectnonempty $type $clicked]} {
3119		set state normal
3120	    } elseif {$type eq ""} {
3121		set state normal
3122	    } else {
3123		set state disabled
3124	    }
3125	} else {
3126	    set state disabled
3127	}
3128	set midx [::AMenu::GetMenuIndex $m $name]
3129	if {[string equal $state "disabled"]} {
3130	    $m entryconfigure $midx -state disabled
3131	}
3132	if {[llength $subType]} {
3133	    set mt [$m entrycget $midx -menu]
3134	    PostMenuCmd $mt $subType $clicked
3135	}
3136    }
3137}
3138
3139proc ::GroupChat::Ignore {chattoken jid3} {
3140    variable $chattoken
3141    upvar 0 $chattoken chatstate
3142
3143    set T $chatstate(wusers)
3144    if {$chatstate(ignore,$jid3)} {
3145	TreeSetIgnoreState $T $jid3
3146    } else {
3147	TreeSetIgnoreState $T $jid3 !
3148    }
3149}
3150
3151proc ::GroupChat::RemoveUser {roomjid jid3} {
3152
3153    ::Debug 4 "::GroupChat::RemoveUser roomjid=$roomjid, jid3=$jid3"
3154
3155    set roomjid [jlib::jidmap $roomjid]
3156    set chattoken [GetTokenFrom chat roomjid [jlib::ESC $roomjid]]
3157    if {$chattoken ne ""} {
3158	upvar 0 $chattoken chatstate
3159	set idx [mstack::remove $chatstate(mstack) $jid3]
3160	TreeRemoveUser $chattoken $jid3
3161    }
3162}
3163
3164proc ::GroupChat::BuildHistory {dlgtoken} {
3165
3166    set chattoken [GetActiveChatToken $dlgtoken]
3167    variable $chattoken
3168    upvar 0 $chattoken chatstate
3169
3170    ::History::BuildHistory $chatstate(roomjid) groupchat -class GroupChat  \
3171      -tagscommand ::GroupChat::ConfigureTextTags
3172}
3173
3174proc ::GroupChat::Save {dlgtoken} {
3175
3176    set chattoken [GetActiveChatToken $dlgtoken]
3177    variable $chattoken
3178    upvar 0 $chattoken chatstate
3179
3180    set wtext   $chatstate(wtext)
3181    set roomjid $chatstate(roomjid)
3182
3183    set ans [tk_getSaveFile -title [mc "Save"] \
3184      -initialfile "Groupchat ${roomjid}.txt"]
3185
3186    if {[string length $ans]} {
3187	set allText [::Text::TransformToPureText $wtext]
3188	set mynick [::Jabber::Jlib service mynick $roomjid]
3189	set myroomjid $roomjid/$mynick
3190	set fd [open $ans w]
3191	fconfigure $fd -encoding utf-8
3192	puts $fd "Groupchat in:\t$roomjid"
3193	puts $fd "Subject:     \t$chatstate(subject)"
3194	puts $fd "My nick:     \t$mynick"
3195	puts $fd "\n"
3196	puts $fd $allText
3197	close $fd
3198    }
3199}
3200
3201proc ::GroupChat::Invite {dlgtoken} {
3202
3203    set chattoken [GetActiveChatToken $dlgtoken]
3204    variable $chattoken
3205    upvar 0 $chattoken chatstate
3206
3207    ::MUC::Invite $chatstate(roomjid)
3208}
3209
3210proc ::GroupChat::Info {dlgtoken} {
3211
3212    set chattoken [GetActiveChatToken $dlgtoken]
3213    variable $chattoken
3214    upvar 0 $chattoken chatstate
3215
3216    ::MUC::BuildInfo $chatstate(roomjid)
3217}
3218
3219proc ::GroupChat::Whiteboard {dlgtoken} {
3220
3221    set chattoken [GetActiveChatToken $dlgtoken]
3222    variable $chattoken
3223    upvar 0 $chattoken chatstate
3224
3225   ::JWB::NewWhiteboardTo $chatstate(roomjid)
3226}
3227
3228proc ::GroupChat::Print {dlgtoken} {
3229
3230    set chattoken [GetActiveChatToken $dlgtoken]
3231    variable $chattoken
3232    upvar 0 $chattoken chatstate
3233
3234    ::UserActions::DoPrintText $chatstate(wtext)
3235}
3236
3237# GroupChat::LogoutHook --
3238#
3239#       Sets logged out status on all groupchats, that is, disable all buttons.
3240
3241proc ::GroupChat::LogoutHook {} {
3242    variable autojoinDone
3243
3244    set autojoinDone 0
3245
3246    foreach chattoken [GetTokenList chat] {
3247	variable $chattoken
3248	upvar 0 $chattoken chatstate
3249
3250	SetState $chattoken disabled
3251	SetLogout $chattoken
3252	::hooks::run groupchatExitRoomHook $chatstate(roomjid)
3253    }
3254}
3255
3256proc ::GroupChat::LoginHook {} {
3257    global  config
3258
3259    # Perhaps we should autojoin any open groupchat dialogs?
3260    if {$config(groupchat,login-autojoin)} {
3261	JoinAllOpen
3262    }
3263    foreach chattoken [GetTokenList chat] {
3264	variable $chattoken
3265	upvar 0 $chattoken chatstate
3266
3267	$chatstate(wbtstatus) state {!disabled}
3268    }
3269}
3270
3271proc ::GroupChat::JoinAllOpen {} {
3272
3273    foreach chattoken [GetTokenList chat] {
3274	variable $chattoken
3275	upvar 0 $chattoken chatstate
3276	::Enter::EnterRoom $chatstate(roomjid) $chatstate(mynick)
3277    }
3278}
3279
3280proc ::GroupChat::GetFirstPanePos {} {
3281    global  wDlgs
3282
3283    set win [::UI::GetFirstPrefixedToplevel $wDlgs(jgc)]
3284    set chattoken [GetTokenFrom chat w $win]
3285    if {$chattoken ne ""} {
3286	variable $chattoken
3287	upvar 0 $chattoken chatstate
3288
3289	::UI::SaveSashPos groupchatDlgVert $chatstate(wpanev)
3290	::UI::SaveSashPos groupchatDlgHori $chatstate(wpaneh)
3291    }
3292}
3293
3294# --- Support for XEP-0048 ---
3295#
3296# @@@ Perhaps this should be in a separate file?
3297#
3298#       Note that a user can be connected with multiple resources which
3299#       means that we cannot rely that the bookmarks are always in sync.
3300#       We therefore makes some assumptions when they must be obtained:
3301#         1) login
3302#         2) when edit them
3303#
3304#       @@@ There is a potential problem if other types of bookmarks (url)
3305#           are influenced
3306#
3307# <xs:element name='conference'>
3308#    <xs:complexType>
3309#      <xs:sequence>
3310#        <xs:element name='nick' type='xs:string' minOccurs='0'/>
3311#        <xs:element name='password' type='xs:string' minOccurs='0'/>
3312#      </xs:sequence>
3313#      <xs:attribute name='autojoin' type='xs:boolean' use='optional' default='false'/>
3314#      <xs:attribute name='jid' type='xs:string' use='required'/>
3315#      <xs:attribute name='name' type='xs:string' use='required'/>
3316#    </xs:complexType>
3317#  </xs:element>
3318
3319namespace eval ::GroupChat:: {
3320
3321    # Bookmarks stored as {{name jid ?-nick . -password . -autojoin .?} ...}
3322    variable bookmarks {}
3323
3324    ::hooks::register loginHook  ::GroupChat::BookmarkLoginHook
3325    ::hooks::register logoutHook ::GroupChat::BookmarkLogoutHook
3326}
3327
3328proc ::GroupChat::BookmarkLoginHook {} {
3329
3330    ::jlib::annotations::send_get "bookmarks" [namespace current]::BookmarkExtractFromCB
3331}
3332
3333proc ::GroupChat::BookmarkLogoutHook {} {
3334    variable bookmarks
3335
3336    set bookmarks {}
3337}
3338
3339proc ::GroupChat::BookmarkGet {} {
3340    variable bookmarks
3341
3342    return $bookmarks
3343}
3344
3345proc ::GroupChat::BookmarkExtractFromCB {type queryElem args} {
3346
3347    if {$type eq "result"} {
3348	BookmarkExtractFromElem $queryElem
3349	DoAnyAutoJoin
3350    }
3351}
3352
3353proc ::GroupChat::BookmarkExtractFromElem {queryElem} {
3354    variable bookmarks
3355
3356    set bookmarks {}
3357    set storageElem  \
3358      [wrapper::getfirstchild $queryElem "storage" "storage:bookmarks"]
3359    set confElems [wrapper::getchildswithtag $storageElem "conference"]
3360    foreach elem $confElems {
3361	array unset bmarr
3362	array set bmarr [list name "" jid ""]
3363	array set bmarr [wrapper::getattrlist $elem]
3364	set bmark [list $bmarr(name) $bmarr(jid)]
3365	set nickElem [wrapper::getfirstchildwithtag $elem "nick"]
3366	if {$nickElem ne ""} {
3367	    lappend bmark -nick [wrapper::getcdata $nickElem]
3368	}
3369	set passElem [wrapper::getfirstchildwithtag $elem "password"]
3370	if {$passElem ne ""} {
3371	    lappend bmark -password [wrapper::getcdata $passElem]
3372	}
3373	if {[info exists bmarr(autojoin)]} {
3374	    lappend bmark -autojoin $bmarr(autojoin)
3375	}
3376	lappend bookmarks $bmark
3377    }
3378    return $bookmarks
3379}
3380
3381# GroupChat::BookmarkRoom --
3382#
3383
3384proc ::GroupChat::BookmarkRoom {chattoken} {
3385    variable $chattoken
3386    upvar 0 $chattoken chatstate
3387    variable bookmarks
3388
3389    set roomjid $chatstate(roomjid)
3390    set name [::Jabber::Jlib disco name $roomjid]
3391    if {$name eq ""} {
3392	set name $roomjid
3393    }
3394    set nick [::Jabber::Jlib service mynick $roomjid]
3395
3396    # Add only if name not there already.
3397    foreach bmark $bookmarks {
3398	if {[lindex $bmark 0] eq $name} {
3399	    return
3400	}
3401    }
3402    lappend bookmarks [list $name $roomjid -nick $nick]
3403
3404    # We assume here that we already have the complete bookmark list from
3405    # the login hook.
3406    BookmarkSendSet
3407}
3408
3409# GroupChat::BookmarkSendSet --
3410#
3411#       Store the complete 'bookmarks' state on server.
3412
3413proc ::GroupChat::BookmarkSendSet {} {
3414    variable bookmarks
3415
3416    set confElems [list]
3417    foreach bmark $bookmarks {
3418	set name [lindex $bmark 0]
3419	set jid  [lindex $bmark 1]
3420	set opts [lrange $bmark 2 end]
3421	set attrs [list jid $jid name $name]
3422	set elems {}
3423	foreach {key value} $opts {
3424
3425	    switch -- $key {
3426		-nick - -password {
3427		    lappend elems [string trimleft $key -] $value
3428		}
3429		-autojoin {
3430		    lappend attrs autojoin $value
3431		}
3432	    }
3433	}
3434	set confChilds [list]
3435	foreach {tag value} $elems {
3436	    lappend confChilds [wrapper::createtag $tag -chdata $value]
3437	}
3438	set confElem [wrapper::createtag "conference"  \
3439	  -attrlist $attrs -subtags $confChilds]
3440	lappend confElems $confElem
3441    }
3442    ::jlib::annotations::send_set "bookmarks" $confElems
3443}
3444
3445proc ::GroupChat::OnMenuBookmark {} {
3446    if {[llength [grab current]]} { return }
3447    if {[::JUI::GetConnectState] eq "connectfin"} {
3448	EditBookmarks
3449    }
3450}
3451
3452proc ::GroupChat::EditBookmarks {} {
3453    global  wDlgs
3454    variable bookmarksVar
3455
3456    set dlg $wDlgs(jgcbmark)
3457    if {[winfo exists $dlg]} {
3458	raise $dlg
3459	return
3460    }
3461    set m [::JUI::GetMainMenu]
3462    set columns [list  \
3463      0 [mc "Chatroom"] 0 [mc "Location"] \
3464      0 [mc "Nickname"] 0 [mc "Password"] \
3465      0 [mc "Auto Join"]]
3466
3467    set bookmarksVar {}
3468    ::Bookmarks::Dialog $dlg [namespace current]::bookmarksVar  \
3469      -menu $m -geovariable prefs(winGeom,$dlg) -columns $columns  \
3470      -command [namespace current]::BookmarksDlgSave
3471
3472    ::UI::SetMenubarAcceleratorBinds $dlg $m
3473
3474    $dlg boolean 4
3475    $dlg state disabled
3476    $dlg wait
3477
3478    ::jlib::annotations::send_get "bookmarks" [namespace current]::BookmarkSendGetCB
3479}
3480
3481proc ::GroupChat::BookmarkSendGetCB {type queryElem args} {
3482    global  wDlgs
3483    variable bookmarks
3484
3485    set dlg $wDlgs(jgcbmark)
3486    if {![winfo exists $dlg]} {
3487	return
3488    }
3489
3490    if {$type eq "error"} {
3491	::UI::MessageBox -type ok -icon error -title [mc "Error"]  \
3492	  -message "Failed to obtain conference bookmarks: [lindex $queryElem 1]"
3493	destroy $dlg
3494    } else {
3495	$dlg state {!disabled}
3496	$dlg wait 0
3497
3498	# Extract the relevant 'conference' elements.
3499	set bookmarks [BookmarkExtractFromElem $queryElem]
3500	set flat [BookmarkToFlat $bookmarks]
3501	foreach row $flat {
3502	    $dlg add $row
3503	}
3504    }
3505}
3506
3507proc ::GroupChat::BookmarksDlgSave {} {
3508    variable bookmarks
3509    variable bookmarksVar
3510
3511    set bookmarks [BookmarkFlatToBookmarks $bookmarksVar]
3512    BookmarkSendSet
3513
3514    # Let other components that depend on this a chance to update themselves.
3515    ::hooks::run groupchatBookmarksSet
3516}
3517
3518# GroupChat::BookmarkToFlat --
3519#
3520#       Translate internal 'bookmarks' list into {{name jid nick pass} ...}
3521
3522proc ::GroupChat::BookmarkToFlat {bookmarks} {
3523
3524    set flat {}
3525    foreach bmark $bookmarks {
3526	array set opts [list -nick "" -password "" -autojoin 0]
3527	array set opts [lrange $bmark 2 end]
3528	set row [lrange $bmark 0 1]
3529	lappend row $opts(-nick) $opts(-password) $opts(-autojoin)
3530	lappend flat $row
3531    }
3532    return $flat
3533}
3534
3535proc ::GroupChat::BookmarkFlatToBookmarks {flat} {
3536
3537    set bookmarks {}
3538    foreach row $flat {
3539	set bmark [lrange $row 0 1]
3540	set nick     [lindex $row 2]
3541	set password [lindex $row 3]
3542	set autojoin [lindex $row 4]
3543	if {$nick ne ""} {
3544	    lappend bmark -nick $nick
3545	}
3546	if {$password ne ""} {
3547	    lappend bmark -password $password
3548	}
3549	if {$autojoin} {
3550	    lappend bmark -autojoin $autojoin
3551	}
3552	lappend bookmarks $bmark
3553    }
3554    return $bookmarks
3555}
3556
3557proc ::GroupChat::BookmarkBuildMenu {m cmd} {
3558    global jprefs
3559    variable bookmarks
3560
3561    menu $m -tearoff 0
3562
3563    foreach bmark $bookmarks {
3564	set name [lindex $bmark 0]
3565	set jid  [lindex $bmark 1]
3566	set opts [lrange $bmark 2 end]
3567	set mcmd [concat $cmd [list $name $jid $opts]]
3568	$m add command -label $name -command $mcmd
3569    }
3570    return $m
3571}
3572
3573proc ::GroupChat::DoAnyAutoJoin {} {
3574    variable autojoinDone
3575    variable bookmarks
3576
3577    if {!$autojoinDone} {
3578	foreach bmark $bookmarks {
3579	    array unset opts
3580	    set name [lindex $bmark 0]
3581	    set jid  [lindex $bmark 1]
3582	    array set opts [lrange $bmark 2 end]
3583	    if {[info exists opts(-autojoin)] && $opts(-autojoin)} {
3584		if {[info exists opts(-nick)]} {
3585		    set nick $opts(-nick)
3586		} else {
3587		    jlib::splitjidex [::Jabber::Jlib myjid] nick - -
3588		}
3589		set eopts [list -command ::GroupChat::BookmarkAutoJoinCB]
3590		if {[info exists opts(-password)]} {
3591		    lappend eopts -password $opts(-password)
3592		}
3593		lappend eopts -protocol muc
3594		::Debug 4 "::GroupChat::DoAnyAutoJoin jid=$jid, nick=$nick $eopts"
3595		eval {::Enter::EnterRoom $jid $nick} $eopts
3596	    }
3597	}
3598    }
3599    set autojoinDone 1
3600}
3601
3602proc ::GroupChat::BookmarkAutoJoinCB {args} {
3603
3604    ::Debug 4 "::GroupChat::BookmarkAutoJoinCB $args"
3605    # anything ?
3606}
3607
3608# Prefs page ...................................................................
3609
3610namespace eval ::GroupChat {
3611
3612    option add *GroupChatPrefs*cols.Label.borderWidth     0          50
3613    option add *GroupChatPrefs*cols.Label.background      white      50
3614    option add *GroupChatPrefs.schemeSize                 12         50
3615
3616    # Color schemes, see http://kuler.adobe.com/ Make your own!
3617    variable schemes
3618    array set schemes {
3619	"Test"              {"#e8b710" "#0eff06" "#ff2100" "#680ce8" "#0debff"}
3620	"Naive"             {"#ff0000" "#00ff00" "#0000ff" "#ffff00" "#000000"}
3621	"Christmas"         {"#015437" "#1b8f45" "#d6e040" "#f04e5e" "#ae2542"}
3622	"Brighties"         {"#ffbb54" "#ae02be" "#fe08bc" "#00daff" "#44e46c"}
3623	"Jamba Juice"       {"#ca3995" "#f58220" "#ffdf05" "#bed73d" "#61bc46"}
3624	"Sunny"             {"#c1d301" "#76ab01" "#0e6a00" "#083500" "#042200"}
3625	"Crazy Rainbow"     {"#f83531" "#f8952b" "#b2cb0a" "#2187f7" "#f82bbd"}
3626	"Boys vs. Girls"    {"#a80064" "#ed48aa" "#e8e300" "#568bd6" "#0044a6"}
3627	"Green Day"         {"#133800" "#1b4f1b" "#398133" "#5c9548" "#93e036"}
3628	"Psi"               {"#0000ff" "#00ff00" "#ffa500" "#a020f0" "#ff0000"}
3629	"Blue"              {"#000030" "#00a0c0" "#0000c0" "#8040c0" "#d040c0"}
3630	"custom"            {"#ff0000" "#00ff00" "#0000ff" "#ffff00" "#000000"}
3631    }
3632}
3633
3634proc ::GroupChat::InitPrefsHook {} {
3635    global jprefs
3636    variable schemes
3637
3638    # Defaults...
3639    set jprefs(defnick)         ""
3640    set jprefs(gchat,syncPres)  0
3641    set jprefs(gchat,useScheme) 1
3642    set jprefs(gchat,colScheme) "Test"
3643    set jprefs(gchat,cusScheme) {"#ff0000" "#00ff00" "#0000ff" "#ffff00" "#000000"}
3644
3645    # Unused but keep it if we want client stored bookmarks.
3646    set jprefs(gchat,bookmarks) {}
3647
3648    ::PrefUtils::Add [list  \
3649      [list jprefs(defnick)          jprefs_defnick           $jprefs(defnick)]  \
3650      [list jprefs(gchat,syncPres)   jprefs_gchat_syncPres    $jprefs(gchat,syncPres)]  \
3651      [list jprefs(gchat,useScheme)  jprefs_gchat_useScheme   $jprefs(gchat,useScheme)]  \
3652      [list jprefs(gchat,colScheme)  jprefs_gchat_colScheme   $jprefs(gchat,colScheme)]  \
3653      [list jprefs(gchat,cusScheme)  jprefs_gchat_cusScheme   $jprefs(gchat,cusScheme)]  \
3654      [list jprefs(gchat,bookmarks)  jprefs_gchat_bookmarks   $jprefs(gchat,bookmarks)]  \
3655      ]
3656
3657    if {![info exists scheme($jprefs(gchat,colScheme))]} {
3658	set jprefs(gchat,colScheme) "Naive"
3659    }
3660    set schemes(custom) $jprefs(gchat,cusScheme)
3661}
3662
3663proc ::GroupChat::BuildPrefsHook {wtree nbframe} {
3664
3665    ::Preferences::NewTableItem {Jabber Conference} [mc "Chatroom"]
3666
3667    # Conference page ------------------------------------------------------
3668    set wpage [$nbframe page {Conference}]
3669    BuildPageConf $wpage
3670}
3671
3672proc ::GroupChat::BuildPageConf {page} {
3673    global jprefs
3674    variable tmpJPrefs
3675    variable pimage
3676    variable schemes
3677
3678    set tmpJPrefs(gchat,syncPres)  $jprefs(gchat,syncPres)
3679    set tmpJPrefs(gchat,useScheme) $jprefs(gchat,useScheme)
3680    set tmpJPrefs(gchat,colScheme) $jprefs(gchat,colScheme)
3681    set tmpJPrefs(gchat,cusScheme) $jprefs(gchat,cusScheme)
3682    set tmpJPrefs(defnick)         $jprefs(defnick)
3683
3684    # Conference (groupchat) stuff.
3685    set wc $page.c
3686    ttk::frame $wc -padding [option get . notebookPageSmallPadding {}] \
3687      -class GroupChatPrefs
3688    pack $wc -side top -anchor [option get . dialogAnchor {}]
3689
3690    ttk::checkbutton $wc.sync -text [mc "Synchronize chatroom presence with global presence"] \
3691      -variable [namespace current]::tmpJPrefs(gchat,syncPres)
3692    pack $wc.sync -side top -anchor w
3693
3694    set menuDef [list]
3695    foreach name [lsearch -all -inline -not [array names schemes] custom] {
3696	lappend menuDef [list $name]
3697    }
3698    lappend menuDef separator
3699    lappend menuDef [list [mc "Custom Colors"] -value custom]
3700    set size [option get $wc schemeSize {}]
3701
3702    set wcols $wc.cols
3703    # TRANSLATORS; in preferences; use different colors for different chatroom participants
3704    ttk::checkbutton $wc.col -text [mc "Enable nickname coloring"] \
3705      -variable [namespace current]::tmpJPrefs(gchat,useScheme)	\
3706      -command [namespace code [list PrefsSchemeCmd $wcols.mb]]
3707    ttk::frame $wc.cols
3708    ui::optionmenu $wcols.mb -menulist $menuDef \
3709      -variable [namespace current]::tmpJPrefs(gchat,colScheme) \
3710      -command [namespace code PrefsColScheme]
3711    set maxwidth [$wcols.mb maxwidth]
3712    for {set n 0} {$n < 5} {incr n} {
3713	set im [image create photo -width $size -height $size]
3714	$im blank
3715	set pimage($n) $im
3716	label $wcols.$n -image $im
3717	bind $wcols.$n <Button-1> \
3718	  [namespace code [list PrefsCustomCol $wcols.$n $n]]
3719    }
3720    PrefsColScheme $tmpJPrefs(gchat,colScheme)
3721    PrefsSchemeCmd $wcols.mb
3722
3723    pack $wc.col  -side top -anchor w
3724    pack $wc.cols -side top -anchor w
3725
3726    grid  x $wcols.mb  $wcols.0 $wcols.1 $wcols.2 $wcols.3 $wcols.4 -padx 4
3727    grid $wcols.mb -sticky ew
3728    grid columnconfigure $wcols 0 -minsize 24
3729    grid columnconfigure $wcols 1 -minsize $maxwidth
3730
3731    # Nickname
3732    set wnick $wc.n
3733    ttk::frame $wnick
3734    ttk::label $wnick.l -text [mc "Default nickname"]:
3735    ttk::entry $wnick.e \
3736      -textvariable [namespace current]::tmpJPrefs(defnick)
3737    pack $wnick.l $wnick.e -side left
3738    pack $wnick.e -fill x
3739    pack $wnick -side top -anchor w -pady 8 -fill x
3740
3741    ::balloonhelp::balloonforwindow $wnick.e [mc "Familiar name"]
3742
3743    bind $page <Destroy> ::GroupChat::PrefsFree
3744}
3745
3746proc ::GroupChat::PrefsCustomCol {win n} {
3747    variable tmpJPrefs
3748    variable schemes
3749
3750    if {$tmpJPrefs(gchat,colScheme) eq "custom"} {
3751	set name [$win cget -image]
3752	lassign [$name get 1 1] r g b
3753	set present [format "#%02x%02x%02x" $r $g $b]
3754	set col [tk_chooseColor -initialcolor $present -title [mc "Choose Color"]]
3755	if {$col ne ""} {
3756	    $name blank
3757	    set data [$name data -background $col]
3758	    $name put $data
3759	    set tmpJPrefs(gchat,cusScheme) \
3760	      [lreplace $tmpJPrefs(gchat,cusScheme) $n $n $col]
3761	}
3762    }
3763}
3764
3765proc ::GroupChat::PrefsSchemeCmd {mb} {
3766    variable tmpJPrefs
3767    if {$tmpJPrefs(gchat,useScheme)} {
3768	$mb state {!disabled}
3769    } else {
3770	$mb state {disabled}
3771    }
3772}
3773
3774proc ::GroupChat::PrefsColScheme {value} {
3775    variable tmpJPrefs
3776    variable pimage
3777    variable schemes
3778
3779    if {$value eq "custom"} {
3780	set cols $tmpJPrefs(gchat,cusScheme)
3781    } else {
3782	set cols $schemes($value)
3783    }
3784    for {set n 0} {$n < 5} {incr n} {
3785	set col [lindex $cols $n]
3786	set name $pimage($n)
3787	$name blank
3788	set data [$name data -background $col]
3789	$name put $data
3790    }
3791}
3792
3793proc ::GroupChat::SavePrefsHook {} {
3794    global jprefs
3795    variable tmpJPrefs
3796    variable schemes
3797
3798    array set jprefs [array get tmpJPrefs]
3799    set schemes(custom) $jprefs(gchat,cusScheme)
3800    SetSchemeAll
3801}
3802
3803proc ::GroupChat::CancelPrefsHook {} {
3804    global jprefs
3805    variable tmpJPrefs
3806
3807    foreach key [array names tmpJPrefs] {
3808	if {![string equal $jprefs($key) $tmpJPrefs($key)]} {
3809	    ::Preferences::HasChanged
3810	    break
3811	}
3812    }
3813}
3814
3815proc ::GroupChat::UserDefaultsHook {} {
3816    global jprefs
3817    variable tmpJPrefs
3818
3819    foreach key [array names tmpJPrefs] {
3820	set tmpJPrefs($key) $jprefs($key)
3821    }
3822}
3823
3824proc ::GroupChat::PrefsFree {} {
3825    variable tmpJPrefs
3826    variable pimage
3827
3828    unset -nocomplain tmpJPrefs
3829    image delete $pimage(0) $pimage(1) $pimage(2) $pimage(3) $pimage(4)
3830}
3831
3832#-------------------------------------------------------------------------------
3833