1# JivePhone.tcl -- 2# 3# JivePhone bindings for the jive server and Asterisk. 4# 5# Contributions and testing by Antonio Cano damas 6# 7# This program is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <http://www.gnu.org/licenses/>. 19# 20# $Id: JivePhone.tcl,v 1.37 2008-06-09 09:50:59 matben Exp $ 21 22# My notes on the present "Phone Integration Proto-JEP" document from 23# Jive Software: 24# 25# 1) server support for this is indicated by the disco child of the server 26# where it should instead be a disco info feature element. 27# 28# 2) "The username must be set as the node attribute on the query" 29# when obtaining info if a particular user has support for this. 30# This seems wrong since only a specific instance of a user specified 31# by an additional resource can have specific features. 32 33# I could imagine a dialer as a tab page, but then we need nice buttons. 34# 35 36namespace eval ::JivePhone { 37 38 component::define JivePhone \ 39 "VoIP notifications from the Openfire plugin Asterisk-IM" 40} 41 42proc ::JivePhone::Init {} { 43 44 component::register JivePhone 45 46 # Add event hooks. 47 ::hooks::register presenceHook ::JivePhone::PresenceHook 48 ::hooks::register newMessageHook ::JivePhone::MessageHook 49 ::hooks::register loginHook ::JivePhone::LoginHook 50 ::hooks::register logoutHook ::JivePhone::LogoutHook 51 ::hooks::register rosterPostCommandHook ::JivePhone::RosterPostCommandHook 52 53 ::hooks::register buildChatButtonTrayHook ::JivePhone::buildChatButtonTrayHook 54 55 variable xmlns 56 set xmlns(jivephone) "http://jivesoftware.com/xmlns/phone" 57 58 # Note the difference! 59 variable feature 60 set feature(jivephone) "http://jivesoftware.com/phone" 61 62 variable statuses {AVAILABLE RING DIALED ON_PHONE HANG_UP} 63 64 65 #--------------- Variables Uses For PopUP Menus ------------------------- 66 variable popMenuDef 67 variable popMenuType 68 set popMenuDef(call) { 69 command mJiveCall {[mc "Call"]} {::JivePhone::DialJID $jid "DIAL"} 70 } 71 set popMenuDef(forward) { 72 command mJiveForward {[mc "Forward Call"]} {::JivePhone::DialJID $jid "FORWARD"} 73 } 74 set popMenuType(call) { 75 mJiveCall {user available} 76 } 77 set popMenuType(forward) { 78 mJiveCall {user available} 79 } 80 81 variable menuDef 82 set menuDef \ 83 {command mJiveCall {[mc "Call"]} {::JivePhone::DoDial "DIAL"} {}} 84 85 86 #--------------- Variables Uses For SpeedDial Addressbook Tab ---------------- 87 variable wtab - 88 variable abline [list] 89 90 set popMenuDef(addressbook) { 91 mJiveCall jid {[mc "Call"]} {::JivePhone::DialExtension $jid "DIAL"} 92 separator {} {} {} 93 mNewAB jid {[mc "New"]} {::JivePhone::NewAddressbookDlg} 94 mModifyAB jid {[mc "Modify"]} {::JivePhone::ModifyAddressbookDlg $jid} 95 mRemoveAB jid {[mc "Remove"]} {::JivePhone::RemoveAddressbookDlg $jid} 96 } 97 98 99 InitState 100} 101 102proc ::JivePhone::InitState { } { 103 variable state 104 105 array set state { 106 phoneserver 0 107 setui 0 108 win .dial 109 wstatus - 110 phone - 111 abphonename - 112 abphonenumber - 113 } 114} 115 116 117#---------------------------------------------------------------------------- 118#-------------------- JEP Messages Function Handlers ------------------------ 119#---------------------------------------------------------------------------- 120proc ::JivePhone::LoginHook { } { 121 122 set server [::Jabber::GetServerJid] 123 ::Jabber::Jlib disco get_async items $server ::JivePhone::OnDiscoServer 124} 125 126proc ::JivePhone::OnDiscoServer {jlibname type from subiq args} { 127 variable state 128 129 Debug "::JivePhone::OnDiscoServer" 130 131 # See comments above what my opinion is... 132 if {$type eq "result"} { 133 set childs [::Jabber::Jlib disco children $from] 134 foreach service $childs { 135 set name [::Jabber::Jlib disco name $service] 136 137 Debug "\t service=$service, name=$name" 138 139 if {$name eq "phone"} { 140 set state(phoneserver) 1 141 set state(service) $service 142 break 143 } 144 } 145 } 146 if {$state(phoneserver)} { 147 148 # @@@ It is a bit unclear if we shall disco the phone service with 149 # the username as each node. 150 151 # We may not yet have obtained the roster. Sync issue! 152 if {[::Jabber::RosterCmd haveroster]} { 153 DiscoForUsers 154 } else { 155 ::hooks::register rosterExit ::JivePhone::RosterHook 156 } 157 } 158} 159 160proc ::JivePhone::RosterHook {} { 161 162 Debug "::JivePhone::RosterHook" 163 ::hooks::deregister rosterExit ::JivePhone::RosterHook 164 DiscoForUsers 165} 166 167proc ::JivePhone::DiscoForUsers {} { 168 variable state 169 170 Debug "::JivePhone::DiscoForUsers" 171 set users [::Jabber::RosterCmd getusers] 172 173 # We add ourselves to this list to figure out if we've got a jive phone. 174 lappend users [::Jabber::Jlib getthis myjid2] 175 176 foreach jid $users { 177 jlib::splitjidex $jid node domain - 178 if {[::Jabber::GetServerJid] eq $domain} { 179 ::Jabber::Jlib disco get_async info $state(service) \ 180 ::JivePhone::OnDiscoUserNode -node $node 181 } 182 } 183} 184 185proc ::JivePhone::OnDiscoUserNode {jlibname type from subiq args} { 186 variable xmlns 187 variable state 188 variable feature 189 190 Debug "::JivePhone::OnDiscoUserNode" 191 192 if {$type eq "result"} { 193 set node [wrapper::getattribute $subiq "node"] 194 set havePhone [::Jabber::Jlib disco hasfeature $feature(jivephone) \ 195 $from $node] 196 197 Debug "\t from=$from, node=$node, havePhone=$havePhone" 198 199 if {$havePhone} { 200 201 # @@@ What now? 202 # @@@ But if we've already got phone presence? 203 204 # Really stupid! It assumes user exist on login server. 205 set server [::Jabber::Jlib getserver] 206 set jid [jlib::joinjid $node $server ""] 207 #puts "\t jid=$jid" 208 209 # Cache this info. 210 #set state(phone,$jid) 211 212 # Since we added ourselves to the list take action if have phone. 213 set myjid2 [::Jabber::Jlib getthis myjid2] 214 if {[jlib::jidequal $jid $myjid2]} { 215 WeHavePhone 216 } else { 217 218 # Attempt to set icon only if this user is unavailable since 219 # we do not have the full jid! 220 # This way we shouldn't interfere with phone presence. 221 # We could use [roster isavailable $jid] instead. 222 223 set item [::RosterTree::FindWithTag [list jid $jid]] 224 if {$item ne ""} { 225 set image [::Rosticons::ThemeGet phone/online] 226 ::RosterTree::StyleSetItemAlternative $jid jivephone \ 227 image $image 228 } 229 } 230 } 231 } 232} 233 234proc ::JivePhone::WeHavePhone { } { 235 variable state 236 variable popMenuDef 237 variable popMenuType 238 variable menuDef 239 240 NewPage 241 if {$state(setui)} { 242 return 243 } 244 ::Roster::RegisterPopupEntry $popMenuDef(call) $popMenuType(call) 245 ::JUI::RegisterMenuEntry action $menuDef 246 247 set image [::Rosticons::ThemeGet [string tolower phone/online]] 248 set win [::JUI::SetAlternativeStatusImage jivephone $image] 249 bind $win <Button-1> [list ::JivePhone::DoDial "DIAL"] 250 ::balloonhelp::balloonforwindow $win [mc "Call"] 251 252 set state(wstatus) $win 253 set state(setui) 1 254 255} 256 257proc ::JivePhone::LogoutHook { } { 258 variable state 259 variable wtab 260 variable abline 261 262 ::Roster::DeRegisterPopupEntry mJiveCall 263 ::Roster::DeRegisterPopupEntry mJiveForward 264 ::JUI::DeRegisterMenuEntry action mJiveCall 265 ::JUI::RemoveAlternativeStatusImage jivephone 266 267 if {[winfo exists $state(wstatus)]} { 268 destroy $state(wstatus) 269 } 270 unset -nocomplain state 271 272 destroy $wtab 273 set abline [list] 274 275 InitState 276} 277 278# JivePhone::PresenceHook -- 279# 280# A user's presence is updated when on a phone call. 281 282proc ::JivePhone::PresenceHook {jid type args} { 283 variable xmlns 284 variable state 285 286 Debug "::JivePhone::PresenceHook jid=$jid, type=$type, $args" 287 288 if {$type ne "available"} { 289 return 290 } 291 292 array set argsArr $args 293 if {[info exists argsArr(-xmldata)]} { 294 set xmldata $argsArr(-xmldata) 295 set elems [wrapper::getchildswithtagandxmlns $xmldata \ 296 phone-status $xmlns(jivephone)] 297 if {$elems ne ""} { 298 set from [wrapper::getattribute $xmldata from] 299 set elem [lindex $elems 0] 300 set status [wrapper::getattribute $elem "status"] 301 if {$status eq ""} { 302 set status available 303 } 304 # Cache this info. 305 # @@@ How do we get unavailable status? 306 # Must check for "normal" presence info. 307 set state(status,$from) $status 308 309 set image [::Rosticons::ThemeGet [string tolower phone/$status]] 310 ::RosterTree::StyleSetItemAlternative $from jivephone image $image 311 eval {::hooks::run jivePhonePresence $from $type} $args 312 } 313 } 314 return 315} 316 317# JivePhone::MessageHook -- 318# 319# Events are sent to the user when their phone is ringing, ... 320# ... message packets are used to send events for the time being. 321 322proc ::JivePhone::MessageHook {xmldata uuid} { 323 variable xmlns 324 variable popMenuDef 325 variable popMenuType 326 variable state 327 variable callID 328 329 Debug "::JivePhone::MessageHook" 330 331 set elem [wrapper::getfirstchildwithtag $xmldata "phone-event"] 332 if {[llength $elem]} { 333 set status [wrapper::getattribute $elem "status"] 334 if {$status eq ""} { 335 set status available 336 } 337 set cidElem [wrapper::getfirstchildwithtag $elem callerID] 338 if {$cidElem != {}} { 339 set cid [wrapper::getcdata $cidElem] 340 } else { 341 set cid [mc "Unknown"] 342 } 343 set image [::Rosticons::ThemeGet [string tolower phone/$status]] 344 345 set win [::JUI::SetAlternativeStatusImage jivephone $image] 346 set type [wrapper::getattribute $elem "type"] 347 348 # @@@ What to do more? 349 if {$type eq "RING" } { 350 set callID [wrapper::getattribute $elem "callID"] 351 352 ::Roster::RegisterPopupEntry $popMenuDef(forward) $popMenuType(forward) 353 bind $win <Button-1> [list ::JivePhone::DoDial "FORWARD"] 354 ::balloonhelp::balloonforwindow $win [mc "Forward current call to"]... 355 ::hooks::run jivePhoneEvent $type $cid $callID $xmldata 356 } 357 if {$type eq "HANG_UP"} { 358 ::Roster::DeRegisterPopupEntry mJiveForward 359 360 bind $win <Button-1> [list ::JivePhone::DoDial "DIAL"] 361 ::balloonhelp::balloonforwindow $win [mc "Call"] 362 ::hooks::run jivePhoneEvent $type $cid "" $xmldata 363 } 364 365 # Provide a default notifier? 366 # if {[hooks::info jivePhoneEvent] eq {}} { 367 # NotifyCall::InboundCall{ $cid } 368 # set title [mc "Ring, ring"]... 369 # set msg [mc "Phone is ringing from %s" $cid] 370 # ui::dialog -icon info -buttons {} -title $title \ 371 # -message $msg -timeout 4000 372 # } 373 } 374 return 375} 376 377proc ::JivePhone::RosterPostCommandHook {m jidL clicked presL} { 378 variable state 379 380 set jid3 [lindex $jidL 0] 381 jlib::splitjid $jid3 jid2 - 382 set jid $jid2 383 384 Debug "RosterPostCommandHook $jidL $clicked $presL" 385 386 if {$clicked ne "user"} { 387 return 388 } 389 if {[llength $jidL] != 1} { 390 return 391 } 392 if {[lsearch $presL "available"] < 0} { 393 return 394 } 395 if {[info exists state(phone,$jid]} { 396 if {[info exists state(status,$jid)]} { 397 398 switch -- $state(status,$jid3) { 399 AVAILABLE - HANG_UP { 400 set midx [::AMenu::GetMenuIndex $m mJiveCall] 401 if {$midx eq ""} { 402 # Probably a submenu. 403 return 404 } 405 $m entryconfigure $midx -state normal 406 } 407 XXXX { 408 # @@@ ??? 409 set midx [::AMenu::GetMenuIndex $m mJiveForward] 410 if {$midx eq ""} { 411 # Probably a submenu. 412 return 413 } 414 $m entryconfigure $midx -state normal 415 } 416 } 417 } 418 } 419} 420 421#----------------------------------------------------------------------- 422#------------------------ JivePhone Dialer Window ---------------------- 423#---------------------- (Dial/Forward - Extension/Jid) ----------------- 424#----------------------------------------------------------------------- 425 426 427# JivePhone::DoDial -- 428# 429# type: FORWARD | DIAL 430 431proc ::JivePhone::DoDial {type {jid ""}} { 432 variable state 433 variable phoneNumber 434 435 set win $state(win) 436 if {$jid eq ""} { 437 BuildDialer $win $type 438 } else { 439 jlib::splitjidex $jid node domain - 440 if {[::Jabber::GetServerJid] eq $domain} { 441 set phoneNumber "" 442 OnDial $win $type $jid 443 } else { 444 BuildDialer $win $type 445 } 446 } 447} 448 449# JivePhone::BuildDialer -- 450# 451# A toplevel dialer. 452 453proc ::JivePhone::BuildDialer {w type} { 454 variable state 455 variable phoneNumber 456 457 # Make sure only single instance of this dialog. 458 if {[winfo exists $w]} { 459 raise $w 460 return 461 } 462 463 ::UI::Toplevel $w -class PhoneDialer \ 464 -usemacmainmenu 1 -macstyle documentProc -macclass {document closeBox} \ 465 -closecommand [namespace current]::CloseDialer 466 467 if {$type eq "DIAL"} { 468 wm title $w [mc "Call to"]... 469 } else { 470 wm title $w [mc "Forward to"]... 471 } 472 473 ::UI::SetWindowPosition $w 474 set phoneNumber "" 475 476 # Global frame. 477 ttk::frame $w.f 478 pack $w.f -fill x 479 480 ttk::label $w.f.head -style Headlabel -text [mc "Phone"] 481 pack $w.f.head -side top -fill both -expand 1 482 483 ttk::separator $w.f.s -orient horizontal 484 pack $w.f.s -side top -fill x 485 486 set wbox $w.f.f 487 ttk::frame $wbox -padding [option get . dialogPadding {}] 488 pack $wbox -fill both -expand 1 489 490 set box $wbox.b 491 ttk::frame $box 492 pack $box -side bottom -fill x 493 494 ttk::label $box.l -text [mc "Number"]: 495 ttk::entry $box.e -textvariable [namespace current]::phoneNumber \ 496 -width 18 497 ttk::button $box.dial -text [mc "Dial"] \ 498 -command [list [namespace current]::OnDial $w $type] 499 500 grid $box.l $box.e $box.dial -padx 1 -pady 4 501 502 focus $box.e 503 wm resizable $w 0 0 504} 505 506proc ::JivePhone::CloseDialer {w} { 507 508 ::UI::SaveWinGeom $w 509} 510 511#------------------------------------------------------------------------- 512#------------------- JivePhone Send IQ Actions --------------------------- 513#------------------------------------------------------------------------- 514 515proc ::JivePhone::OnDial {w type {jid ""}} { 516 variable phoneNumber 517 variable xmlns 518 variable state 519 variable callID 520 521 Debug "::JivePhone::OnDial w=$w, type=$type, phoneNumber=$phoneNumber" 522 523 if {!$state(phoneserver)} { 524 return 525 } 526 527 if {$jid ne ""} { 528 set dnid $jid 529 set extensionElem [wrapper::createtag "jid" -chdata $jid] 530 } elseif {$phoneNumber ne ""} { 531 set extensionElem [wrapper::createtag "extension" -chdata $phoneNumber] 532 set dnid $phoneNumber 533 } else { 534 Debug "\t return" 535 return 536 } 537 538 if {$type eq "DIAL"} { 539 set command "DIAL" 540 set attr [list xmlns $xmlns(jivephone) type $command] 541 } else { 542 set command "FORWARD" 543 set attr [list xmlns $xmlns(jivephone) id $callID type $command] 544 } 545 set phoneElem [wrapper::createtag "phone-action" \ 546 -attrlist $attr -subtags [list $extensionElem]] 547 548 ::Jabber::Jlib send_iq set [list $phoneElem] \ 549 -to $state(service) -command [list ::JivePhone::DialCB $dnid] 550 551 ::hooks::run jivePhoneEvent $command $dnid $callID 552 553 destroy $w 554} 555 556proc ::JivePhone::DialJID {jid type {callID ""}} { 557 variable state 558 variable xmlns 559 560 if {!$state(phoneserver)} { 561 return 562 } 563 set extensionElem [wrapper::createtag "jid" -chdata $jid] 564 565 if {$type eq "DIAL"} { 566 set command "DIAL" 567 set attr [list xmlns $xmlns(jivephone) type $command] 568 } else { 569 # @@@ Where comes callID from? 570 set command "FORWARD" 571 set attr [list xmlns $xmlns(jivephone) id $callID type $command] 572 } 573 set phoneElem [wrapper::createtag "phone-action" \ 574 -attrlist $attr -subtags [list $extensionElem]] 575 576 ::Jabber::Jlib send_iq set [list $phoneElem] \ 577 -to $state(service) -command [list ::JivePhone::DialCB $jid] 578 579 ::hooks::run jivePhoneEvent $command $jid $callID 580} 581 582proc ::JivePhone::DialExtension {extension type {callID ""}} { 583 variable state 584 variable xmlns 585 586 if {!$state(phoneserver)} { 587 return 588 } 589 set extensionElem [wrapper::createtag "extension" -chdata $extension] 590 591 if {$type eq "DIAL"} { 592 set command "DIAL" 593 set attr [list xmlns $xmlns(jivephone) type $command] 594 } else { 595 # @@@ Where comes callID from? 596 set command "FORWARD" 597 set attr [list xmlns $xmlns(jivephone) id $callID type $command] 598 } 599 600 set phoneElem [wrapper::createtag "phone-action" \ 601 -attrlist $attr -subtags [list $extensionElem]] 602 603 ::Jabber::Jlib send_iq set [list $phoneElem] \ 604 -to $state(service) -command [list ::JivePhone::DialCB $extension] 605 606 ::hooks::run jivePhoneEvent $command $extension $callID 607} 608 609proc ::JivePhone::DialCB {dnid type subiq args} { 610 611 if {$type eq "error"} { 612 ui::dialog -title [mc "Error"] -icon error -type ok \ 613 -message [mc "Failed calling %s" $dnid] -detail $subiq 614 } 615} 616 617 618#--------------------------------------------------------------------------- 619#------------------- JivePhone Addressbook SpeedDial Tab ------------------- 620#--------------------------------------------------------------------------- 621 622proc ::JivePhone::NewPage {} { 623 variable wtab 624 625 set wnb [::JUI::GetNotebook] 626 set wtab $wnb.ab 627 if {![winfo exists $wtab]} { 628 Build $wtab 629 $wnb add $wtab -text [mc "Address Book"] 630 } 631} 632 633# JivePhone::Build -- 634# 635# This is supposed to create a frame which is pretty object like, 636# and handles most stuff internally without intervention. 637# 638# Arguments: 639# w frame for everything 640# args 641# 642# Results: 643# w 644 645proc ::JivePhone::Build {w args} { 646 global prefs this jprefs 647 648 variable waddressbook 649 variable wtree 650 variable wwave 651 upvar ::Jabber::jstate jstate 652 upvar ::Jabber::jserver jserver 653 variable abline 654 655 ::Debug 2 "::JivePhone::Build w=$w" 656 set jstate(wpopup,addressbook) .jpopupab 657 set waddressbook $w 658 set wwave $w.fs 659 set wbox $w.box 660 set wtree $wbox.tree 661 set wxsc $wbox.xsc 662 set wysc $wbox.ysc 663 664 # The frame. 665 ttk::frame $w -class AddressBook 666 667 # D = -border 1 -relief sunken 668 frame $wbox 669 pack $wbox -side top -fill both -expand 1 670 671 ttk::scrollbar $wxsc -orient horizontal -command [list $wtree xview] 672 ttk::scrollbar $wysc -orient vertical -command [list $wtree yview] 673 674 ::ITree::New $wtree $wxsc $wysc \ 675 -buttonpress ::JivePhone::Popup \ 676 -buttonpopup ::JivePhone::Popup 677 678 grid $wtree -row 0 -column 0 -sticky news 679 grid $wysc -row 0 -column 1 -sticky ns 680 grid $wxsc -row 1 -column 0 -sticky ew 681 grid columnconfigure $wbox 0 -weight 1 682 grid rowconfigure $wbox 0 -weight 1 683 684 #--------- Load Entries of AddressBook into NewPage Tab --------- 685 LoadEntries 686 if { [llength $abline] } { 687 foreach {name phone} $abline { 688 set opts {-text "$name ($phone)"} 689 if {$name ne ""} { 690 lappend opts -text "$name ($phone)" 691 eval {::ITree::Item $wtree $phone} $opts 692 } 693 } 694 } 695 return $w 696} 697 698proc ::JivePhone::LoadEntries {} { 699 variable abline 700 global prefs this 701 702 set fileName [file join $this(prefsPath) addressbook.csv] 703 704 set abline [list] 705 if { [ file exists $fileName ] } { 706 set hFile [open $fileName "r"] 707 while {[eof $hFile] <= 0} { 708 gets $hFile line 709 set temp [split $line ":"] 710 foreach i $temp { 711 lappend abline $i 712 } 713 } 714 close $hFile 715 } 716} 717 718# JivePhone::Popup -- 719# 720# Handle popup menus in JivePhone, typically from right-clicking. 721# 722# Arguments: 723# w widget that issued the command: tree or text 724# v for the tree widget it is the item path, 725# for text the jidhash. 726# 727# Results: 728# popup menu displayed 729 730proc ::JivePhone::Popup {w v x y} { 731 global wDlgs this 732 variable popMenuDef 733 734 upvar ::Jabber::jstate jstate 735 736 ::Debug 2 "::JivePhone::Popup w=$w, v='$v', x=$x, y=$y" 737 738 # The last element of $v is either a jid, (a namespace,) 739 # a header in roster, a group, or an agents xml tag. 740 # The variables name 'jid' is a misnomer. 741 # Find also type of thing clicked, 'typeClicked'. 742 743 set typeClicked "" 744 745 set jid [lindex $v end] 746 set jid3 $jid 747 set childs [::ITree::Children $w $v] 748 749 if {$jid ne ""} { 750 set typeClicked jid 751 } 752 753 if {[string length $jid] == 0} { 754 set typeClicked "" 755 } 756 set X [expr {[winfo rootx $w] + $x}] 757 set Y [expr {[winfo rooty $w] + $y}] 758 759 ::Debug 2 "\t jid=$jid, typeClicked=$typeClicked" 760 761 # Mads Linden's workaround for menu post problem on mac: 762 # all in menubutton commands i add "after 40 the_command" 763 # this way i can never have to posting error. 764 # it is important after the tk_popup f.ex to 765 # 766 # destroy .mb 767 # update 768 # 769 # this way the .mb is destroyd before the next window comes up, thats how I 770 # got around this. 771 772 # Make the appropriate menu. 773 set m $jstate(wpopup,addressbook) 774 set i 0 775 catch {destroy $m} 776 menu $m -tearoff 0 777 778 foreach {item type lname cmd} $popMenuDef(addressbook) { 779 if {[string index $cmd 0] == "@"} { 780 set mt [menu ${m}.sub${i} -tearoff 0] 781 set locname [eval concat $lname] 782 $m add cascade -label $locname -menu $mt -state disabled 783 eval [string range $cmd 1 end] $mt 784 incr i 785 } elseif {[string equal $item "separator"]} { 786 $m add separator 787 continue 788 } else { 789 790 # Substitute the jid arguments. Preserve list structure! 791 set cmd [eval list $cmd] 792 set locname [eval concat $lname] 793 $m add command -label $locname -command [list after 40 $cmd] \ 794 -state disabled 795 } 796 797 # If a menu should be enabled even if not connected do it here. 798 799 if {![::Jabber::IsConnected]} { 800 continue 801 } 802 if {[string equal $type "any"]} { 803 $m entryconfigure $locname -state normal 804 continue 805 } 806 807 # State of menu entry. We use the 'type' and 'typeClicked' to sort 808 # out which capabilities to offer for the clicked item. 809 set state disabled 810 811 if {[string equal $item "mNewAB"]} { 812 set state normal 813 } 814 815 if {[string equal $type $typeClicked]} { 816 set state normal 817 } 818 if {[string equal $state "normal"]} { 819 $m entryconfigure $locname -state normal 820 } 821 } 822 823 # This one is needed on the mac so the menu is built before it is posted. 824 update idletasks 825 826 # Post popup menu. 827 tk_popup $m [expr {int($X) - 10}] [expr {int($Y) - 10}] 828 829 # Mac bug... (else can't post menu while already posted if toplevel...) 830 if {[string equal "macintosh" $this(platform)]} { 831 catch {destroy $m} 832 update 833 } 834} 835 836proc ::JivePhone::RemoveAddressbookDlg {jid} { 837 variable abline 838 variable wtree 839 840 set index [lsearch -exact $abline $jid] 841 842 set tmp [lreplace $abline [expr {$index-1}] $index] 843 set abline $tmp 844 845 eval {::ITree::DeleteItem $wtree $jid} 846 847 SaveEntries 848 849} 850 851 852proc ::JivePhone::NewAddressbookDlg {} { 853 global this wDlgs 854 855 variable abName 856 variable abPhoneNumber 857 858 set abName "" 859 set abPhoneNumber "" 860 861 set w ".nadbdlg" 862 ::UI::Toplevel $w \ 863 -macstyle documentProc -macclass {document closeBox} -usemacmainmenu 1 \ 864 -closecommand [namespace current]::CloseCmd 865 wm title $w [mc "New address book"] 866 867 set nwin [llength [::UI::GetPrefixedToplevels $wDlgs(jmucenter)]] 868 if {$nwin == 1} { 869 ::UI::SetWindowPosition $w ".nadbdlg" 870 } 871 872 # Global frame. 873 ttk::frame $w.frall 874 pack $w.frall -fill both -expand 1 875 876 set wbox $w.frall.f 877 ttk::frame $wbox -padding [option get . dialogPadding {}] 878 pack $wbox -fill both -expand 1 879 880 ttk::label $wbox.msg -style Small.TLabel \ 881 -padding {0 0 0 6} -wraplength 260 -justify left -text [mc "New address book"] 882 pack $wbox.msg -side top -anchor w 883 884 set frmid $wbox.frmid 885 ttk::frame $frmid 886 pack $frmid -side top -fill both -expand 1 887 888 ttk::label $frmid.lname -text [mc "Name"]: 889 ttk::entry $frmid.ename -textvariable [namespace current]::abName 890 891 ttk::label $frmid.lphone -text [mc "Phone number"]: 892 ttk::entry $frmid.ephone -textvariable [namespace current]::abPhoneNumber 893 894 grid $frmid.lname $frmid.ename - -sticky e -pady 2 895 grid $frmid.lphone $frmid.ephone - -sticky e -pady 2 896 grid $frmid.ephone $frmid.ename -sticky ew 897 grid columnconfigure $frmid 1 -weight 1 898 899 # Button part. 900 set frbot $wbox.b 901 set wenter $frbot.btok 902 ttk::frame $frbot 903 ttk::button $wenter -text [mc "Enter"] \ 904 -default active -command [list [namespace current]::addItemAddressBook $w] 905 ttk::button $frbot.btcancel -text [mc "Cancel"] \ 906 -command [list [namespace current]::CancelEnter $w] 907 908 set padx [option get . buttonPadX {}] 909 if {[option get . okcancelButtonOrder {}] eq "cancelok"} { 910 pack $frbot.btok -side right 911 pack $frbot.btcancel -side right -padx $padx 912 } else { 913 pack $frbot.btcancel -side right 914 pack $frbot.btok -side right -padx $padx 915 } 916 pack $frbot -side bottom -fill x 917 918 wm resizable $w 0 0 919 920 bind $w <Return> [list $wenter invoke] 921 922 # Trick to resize the labels wraplength. 923 set script [format { 924 update idletasks 925 %s configure -wraplength [expr {[winfo reqwidth %s] - 20}] 926 } $wbox.msg $w] 927 after idle $script 928} 929 930proc ::JivePhone::ModifyAddressbookDlg {jid} { 931 global this wDlgs 932 933 variable abName 934 variable abPhoneNumber 935 variable abline 936 937 #Get Entry data from abline list 938 set index [lsearch -exact $abline $jid] 939 set abName [lindex $abline [expr {$index-1}]] 940 set abPhoneNumber [lindex $abline [expr {$index}]] 941 set oldPhoneNumber $abPhoneNumber 942 943 set w ".madbdlg" 944 ::UI::Toplevel $w \ 945 -macstyle documentProc -macclass {document closeBox} -usemacmainmenu 1 \ 946 -closecommand [namespace current]::CloseCmd 947 wm title $w [mc "Modify address book"] 948 949 set nwin [llength [::UI::GetPrefixedToplevels $wDlgs(jmucenter)]] 950 if {$nwin == 1} { 951 ::UI::SetWindowPosition $w ".madbdlg" 952 } 953 954 # Global frame. 955 ttk::frame $w.frall 956 pack $w.frall -fill both -expand 1 957 958 set wbox $w.frall.f 959 ttk::frame $wbox -padding [option get . dialogPadding {}] 960 pack $wbox -fill both -expand 1 961 962 ttk::label $wbox.msg -style Small.TLabel \ 963 -padding {0 0 0 6} -wraplength 260 -justify left -text [mc "Modify address book"] 964 pack $wbox.msg -side top -anchor w 965 966 set frmid $wbox.frmid 967 ttk::frame $frmid 968 pack $frmid -side top -fill both -expand 1 969 970 ttk::label $frmid.lname -text [mc "Name"]: 971 ttk::entry $frmid.ename -textvariable [namespace current]::abName 972 973 ttk::label $frmid.lphone -text [mc "Phone number"]: 974 ttk::entry $frmid.ephone -textvariable [namespace current]::abPhoneNumber 975 976 grid $frmid.lname $frmid.ename - -sticky e -pady 2 977 grid $frmid.lphone $frmid.ephone - -sticky e -pady 2 978 grid $frmid.ephone $frmid.ename -sticky ew 979 grid columnconfigure $frmid 1 -weight 1 980 981 # Button part. 982 set frbot $wbox.b 983 set wenter $frbot.btok 984 ttk::frame $frbot 985 ttk::button $wenter -text [mc "Enter"] \ 986 -default active -command [list [namespace current]::modifyItemAddressBook $w $oldPhoneNumber] 987 ttk::button $frbot.btcancel -text [mc "Cancel"] \ 988 -command [list [namespace current]::CancelEnter $w] 989 990 set padx [option get . buttonPadX {}] 991 if {[option get . okcancelButtonOrder {}] eq "cancelok"} { 992 pack $frbot.btok -side right 993 pack $frbot.btcancel -side right -padx $padx 994 } else { 995 pack $frbot.btcancel -side right 996 pack $frbot.btok -side right -padx $padx 997 } 998 pack $frbot -side bottom -fill x 999 1000 wm resizable $w 0 0 1001 1002 bind $w <Return> [list $wenter invoke] 1003 1004 # Trick to resize the labels wraplength. 1005 set script [format { 1006 update idletasks 1007 %s configure -wraplength [expr {[winfo reqwidth %s] - 20}] 1008 } $wbox.msg $w] 1009 after idle $script 1010} 1011 1012 1013proc ::JivePhone::addItemAddressBook {w} { 1014 variable abName 1015 variable abPhoneNumber 1016 variable abline 1017 variable wtree 1018 1019 if { $abName ne "" && $abPhoneNumber ne ""} { 1020 lappend abline $abName 1021 lappend abline $abPhoneNumber 1022 1023 set opts {-text "$abName ($abPhoneNumber)"} 1024 eval {::ITree::Item $wtree $abPhoneNumber} $opts 1025 SaveEntries 1026 1027 ::UI::SaveWinGeom $w 1028 destroy $w 1029 } 1030} 1031 1032proc ::JivePhone::modifyItemAddressBook {w oldPhoneNumber} { 1033 variable abName 1034 variable abPhoneNumber 1035 variable abline 1036 variable wtree 1037 1038 if { $abName ne "" && $abPhoneNumber ne "" } { 1039 #---------- Updates Memory Addressbook ----------------- 1040 set index [lsearch -exact $abline $oldPhoneNumber] 1041 1042 set tmp [lreplace $abline [expr {$index-1}] $index $abName $abPhoneNumber] 1043 set abline $tmp 1044 1045 #----- Updates GUI --------- 1046 eval {::ITree::DeleteItem $wtree $oldPhoneNumber} 1047 set opts {-text "$abName ($abPhoneNumber)"} 1048 eval {::ITree::Item $wtree $abPhoneNumber} $opts 1049 1050 #----- Updates Database ------- 1051 SaveEntries 1052 1053 ::UI::SaveWinGeom $w 1054 destroy $w 1055 } 1056} 1057 1058proc ::JivePhone::CancelEnter {w} { 1059 1060 ::UI::SaveWinGeom $w 1061 destroy $w 1062} 1063 1064proc ::JivePhone::CloseCmd {w} { 1065 1066 ::UI::SaveWinGeom $w 1067} 1068 1069proc ::JivePhone::SaveEntries {} { 1070 variable abline 1071 global prefs this 1072 1073 # @@@ Mats 1074 set hFile [open [file join $this(prefsPath) addressbook.csv] "w"] 1075 1076 foreach {name phonenumber} $abline { 1077 if {$name ne ""} { 1078 puts $hFile "$name:$phonenumber" 1079 } 1080 } 1081 1082 close $hFile 1083} 1084 1085proc ::JivePhone::Debug {msg} { 1086 1087 if {0} { 1088 puts "-------- $msg" 1089 } 1090} 1091 1092proc ::JivePhone::buildChatButtonTrayHook {wtray dlgtoken args} { 1093 global this prefs wDlgs 1094 variable state 1095 1096 if { $state(phoneserver) == 1 } { 1097 variable $dlgtoken 1098 upvar 0 $dlgtoken dlgstate 1099 1100 set w $dlgstate(w) 1101 1102 set iconCall [::Theme::FindIconSize 32 phone-call] 1103 set iconCallDis [::Theme::FindIconSize 32 phone-call-Dis] 1104 1105 $wtray newbutton call \ 1106 -text [mc "Call"] -image $iconCall \ 1107 -disabledimage $iconCallDis \ 1108 -command [list [namespace current]::chatCall $dlgtoken] 1109 } 1110} 1111 1112proc ::JivePhone::chatCall {dlgtoken} { 1113 set chattoken [::Chat::GetActiveChatToken $dlgtoken] 1114 variable $chattoken 1115 upvar 0 $chattoken chatstate 1116 set jid $chatstate(fromjid) 1117 1118 DialJID $jid "DIAL" 1119} 1120 1121#------------------------------------------------------------------------------- 1122