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