1# $Id$
2#
3# Annotations (XEP-0145) support
4#
5
6package require xmpp::roster::annotations
7
8namespace eval annotations {
9    # variable to store roster notes
10    array set notes {}
11}
12
13proc annotations::free_notes {xlib} {
14    variable notes
15
16    array unset notes $xlib,*
17}
18
19hook::add disconnected_hook [namespace current]::annotations::free_notes
20
21proc annotations::request_notes {xlib} {
22    ::xmpp::roster::annotations::retrieve $xlib \
23		-command [list [namespace current]::process_notes $xlib]
24}
25
26hook::add connected_hook [namespace current]::annotations::request_notes
27
28proc annotations::process_notes {xlib status noteslist} {
29    variable notes
30
31    if {$status != "ok"} return
32
33    free_notes $xlib
34
35    foreach note $noteslist {
36	create_note $xlib $note
37    }
38}
39
40proc annotations::create_note {xlib note args} {
41    variable notes
42
43    set merge 0
44    foreach {opt val} $args {
45	switch -- $opt {
46	    -merge { set merge $val }
47	    default {
48		return -code error "Bad option \"$opt\":\
49		    must be -merge"
50	    }
51	}
52    }
53
54    array set n $note
55    set jid $n(jid)
56
57    if {!$merge || [more_recent $xlib $n(jid) $n(cdate) $n(mdate)]} {
58	set notes($xlib,jid,$jid)   $n(jid)
59	set notes($xlib,cdate,$jid) $n(cdate)
60	set notes($xlib,mdate,$jid) $n(mdate)
61	set notes($xlib,note,$jid)  $n(note)
62	return 1
63    } else {
64	return 0
65    }
66}
67
68proc annotations::more_recent {xlib jid cdate mdate} {
69    variable notes
70
71    if {![info exists notes($xlib,jid,$jid)]} {
72	return 1
73    } elseif {[info exists notes($xlib,mdate,$jid)]} {
74	return [expr {$mdate > $notes($xlib,mdate,$jid)}]
75    } elseif {[info exists notes($xlib,cdate,$jid)]} {
76	return [expr {$cdate > $notes($xlib,cdate,$jid)}]
77    } else {
78	return 1
79    }
80}
81
82proc annotations::cleanup_and_store_notes {xlib args} {
83    variable notes
84
85    set roster_jids {}
86    foreach rjid [roster::get_jids $xlib] {
87	lappend roster_jids [::xmpp::jid::stripResource $rjid]
88    }
89
90    foreach idx [array names notes $xlib,jid,*] {
91	set jid $notes($idx)
92	if {[lsearch -exact $roster_jids $jid] < 0 || \
93		![info exists notes($xlib,note,$jid)] || \
94		$notes($xlib,note,$jid) == ""} {
95	    catch { unset notes($xlib,jid,$jid) }
96	    catch { unset notes($xlib,cdate,$jid) }
97	    catch { unset notes($xlib,mdate,$jid) }
98	    catch { unset notes($xlib,note,$jid) }
99	}
100    }
101
102    eval [list store_notes $xlib] $args
103}
104
105proc annotations::serialize_notes {xlib} {
106    variable notes
107
108    set notelist {}
109    foreach idx [array names notes $xlib,jid,*] {
110	set jid $notes($idx)
111
112	if {![info exists notes($xlib,note,$jid)] || \
113		$notes($xlib,note,$jid) == ""} continue
114
115	lappend notelist [list jid $jid \
116			       cdate $notes($xlib,cdate,$jid) \
117			       mdate $notes($xlib,mdate,$jid) \
118			       note $notes($xlib,note,$jid)]
119    }
120
121    return $notelist
122}
123
124proc annotations::store_notes {xlib args} {
125    set command [list [namespace current]::store_notes_result $xlib]
126    foreach {opt val} $args {
127	switch -- $opt {
128	    -command { set command $val }
129	    default {
130		return -code error "Bad option \"$opt\":\
131		    must be -command"
132	    }
133	}
134    }
135
136    ::xmpp::roster::annotations::store $xlib [serialize_notes $xlib] \
137		-command $command
138}
139
140proc annotations::store_notes_result {xlib res child} {
141
142    if {$res == "ok"} return
143
144    if {[winfo exists .store_notes_error]} {
145	destroy .store_notes_error
146    }
147    MessageDlg .store_notes_error -aspect 50000 -icon error \
148	-message [::msgcat::mc "Storing roster notes failed: %s" \
149			 [error_to_string $child]] \
150	-type user -buttons ok -default 0 -cancel 0
151}
152
153proc annotations::add_user_popup_info {infovar xlib jid} {
154    variable notes
155    upvar 0 $infovar info
156
157    set jid [::xmpp::jid::stripResource $jid]
158
159    if {[info exists notes($xlib,note,$jid)] && \
160	    $notes($xlib,note,$jid) != ""} {
161	append info "\n\tNote:\t"
162	append info [string map [list "\n" "\n\t\t"] "$notes($xlib,note,$jid)"]
163
164	if {0} {
165	    if {[info exists notes($xlib,cdate,$jid)]} {
166		append info [format "\n\tNote created: %s" \
167				 [clock format $notes($xlib,cdate,$jid) \
168					-format "%Y-%m-%d %T" -gmt false]]
169	    }
170	    if {[info exists notes($xlib,mdate,$jid)]} {
171		append info [format "\n\tNote modified: %s" \
172				 [clock format $notes($xlib,mdate,$jid) \
173					-format "%Y-%m-%d %T" -gmt false]]
174	    }
175	}
176    }
177}
178
179hook::add roster_user_popup_info_hook \
180    [namespace current]::annotations::add_user_popup_info 80
181
182proc annotations::show_dialog {xlib jid} {
183    variable notes
184
185    set jid [::xmpp::jid::stripResource $jid]
186
187    set allowed_name [jid_to_tag $jid]
188    set w .note_edit_[psuffix $xlib]_$allowed_name
189
190    if {[winfo exists $w]} {
191	destroy $w
192    }
193
194    Dialog $w -title [::msgcat::mc "Edit roster notes for %s" $jid] \
195	-modal none -separator 1 -anchor e \
196	-default 0 -cancel 1
197
198    $w add -text [::msgcat::mc "Store"] \
199	-command [list [namespace current]::commit_changes $w $xlib $jid]
200    $w add -text [::msgcat::mc "Cancel"] -command [list destroy $w]
201
202    set f [$w getframe]
203
204    if {[info exists notes($xlib,cdate,$jid)]} {
205	label $f.cdate -text [::msgcat::mc "Created: %s" \
206				     [clock format $notes($xlib,cdate,$jid) \
207					    -format "%Y-%m-%d %T" -gmt false]]
208	pack $f.cdate -side top -anchor w
209    }
210
211    if {[info exists notes($xlib,mdate,$jid)]} {
212	label $f.mdate -text [::msgcat::mc "Modified: %s" \
213				     [clock format $notes($xlib,mdate,$jid) \
214					    -format "%Y-%m-%d %T" -gmt false]]
215	pack $f.mdate -side top -anchor w
216    }
217
218    ScrolledWindow $f.sw
219    pack $f.sw -side top -expand yes -fill both
220    textUndoable $f.note -width 50 -height 5 -wrap word
221    if {[info exists notes($xlib,note,$jid)]} {
222	$f.note insert 0.0 $notes($xlib,note,$jid)
223    }
224    $f.sw setwidget $f.note
225
226    bind $f.note <Control-Key-Return> "[double% $w] invoke default
227				       break"
228    bind $w <Key-Return> { }
229    bind $w <Control-Key-Return> "[double% $w] invoke default
230				  break"
231
232    $w draw $f.note
233}
234
235proc annotations::commit_changes {w xlib jid} {
236    variable notes
237
238    set text [$w getframe].note
239
240    set date [clock seconds]
241
242    set notes($xlib,jid,$jid) $jid
243    if {![info exists notes($xlib,cdate,$jid)]} {
244	set notes($xlib,cdate,$jid) $date
245    }
246    set notes($xlib,mdate,$jid) $date
247    set notes($xlib,note,$jid) [$text get 0.0 "end -1 char"]
248
249    cleanup_and_store_notes $xlib
250
251    destroy $w
252}
253
254proc annotations::prefs_user_menu {m xlib jid} {
255    set rjid [roster::find_jid $xlib $jid]
256    if {$rjid == ""} {
257	set state disabled
258    } else {
259	set state normal
260    }
261    $m add command -label [::msgcat::mc "Edit item notes..."] \
262	-command [list [namespace current]::show_dialog $xlib $rjid] \
263	-state $state
264}
265
266hook::add chat_create_user_menu_hook \
267    [namespace current]::annotations::prefs_user_menu 76
268hook::add roster_conference_popup_menu_hook \
269    [namespace current]::annotations::prefs_user_menu 76
270hook::add roster_service_popup_menu_hook \
271    [namespace current]::annotations::prefs_user_menu 76
272hook::add roster_jid_popup_menu_hook \
273    [namespace current]::annotations::prefs_user_menu 76
274
275proc annotations::note_page {tab xlib jid editable} {
276    variable notes
277
278    if {$editable} return
279
280    set jid [::xmpp::jid::stripResource $jid]
281
282    if {![info exists notes($xlib,note,$jid)] || \
283	    $notes($xlib,note,$jid) == ""} {
284	return
285    }
286
287    set notestab [$tab insert end notes -text [::msgcat::mc "Notes"]]
288    set n [userinfo::pack_frame $notestab.notes [::msgcat::mc "Roster Notes"]]
289
290    if {[info exists notes($xlib,cdate,$jid)]} {
291	label $n.cdate -text [::msgcat::mc "Created: %s" \
292				     [clock format $notes($xlib,cdate,$jid) \
293					    -format "%Y-%m-%d %T" -gmt false]]
294	pack $n.cdate -side top -anchor w
295    }
296
297    if {[info exists notes($xlib,mdate,$jid)]} {
298	label $n.mdate -text [::msgcat::mc "Modified: %s" \
299				     [clock format $notes($xlib,mdate,$jid) \
300					    -format "%Y-%m-%d %T" -gmt false]]
301	pack $n.mdate -side top -anchor w
302    }
303
304    set sw [ScrolledWindow $n.sw -scrollbar vertical]
305    text $n.text -height 12 -wrap word
306    $sw setwidget $n.text
307    $n.text insert 0.0 $notes($xlib,note,$jid)
308    $n.text configure -state disabled
309    pack $sw -side top -fill both -expand yes
310    pack $n -fill both -expand yes
311}
312
313hook::add userinfo_hook [namespace current]::annotations::note_page 40
314
315# vim:ts=8:sw=4:sts=4:noet
316