]> crepu.dev Git - config.git/blob - djavu-asus/elpa/jabber-20230715.456/jabber-roster.el
Actualizado el Readme
[config.git] / djavu-asus / elpa / jabber-20230715.456 / jabber-roster.el
1 ;; jabber-roster.el - displaying the roster -*- coding: utf-8; -*-
2
3 ;; Copyright (C) 2009 - Kirill A. Korinskiy - catap@catap.ru
4 ;; Copyright (C) 2003, 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu
5 ;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
6
7 ;; This file is a part of jabber.el.
8
9 ;; This program is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2 of the License, or
12 ;; (at your option) any later version.
13
14 ;; This program is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with this program; if not, write to the Free Software
21 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22
23 (require 'jabber-presence)
24 (require 'jabber-util)
25 (require 'jabber-alert)
26 (require 'jabber-keymap)
27 (require 'format-spec)
28 (require 'cl-lib) ;for `find'
29 (require 'jabber-private)
30
31 (defgroup jabber-roster nil "roster display options"
32 :group 'jabber)
33
34 (defcustom jabber-roster-line-format " %a %c %-25n %u %-8s %S"
35 "The format specification of the lines in the roster display.
36
37 These fields are available:
38
39 %a Avatar, if any
40 %c \"*\" if the contact is connected, or \" \" if not
41 %u sUbscription state - see below
42 %n Nickname of contact, or JID if no nickname
43 %j Bare JID of contact (without resource)
44 %r Highest-priority resource of contact
45 %s Availability of contact as string (\"Online\", \"Away\" etc)
46 %S Status string specified by contact
47
48 %u is replaced by one of the strings given by
49 `jabber-roster-subscription-display'."
50 :type 'string
51 :group 'jabber-roster)
52
53 (defcustom jabber-roster-subscription-display '(("none" . " ")
54 ("from" . "< ")
55 ("to" . " >")
56 ("both" . "<->"))
57 "Strings used for indicating subscription status of contacts.
58 \"none\" means that there is no subscription between you and the
59 contact.
60 \"from\" means that the contact has a subscription to you, but you
61 have no subscription to the contact.
62 \"to\" means that you have a subscription to the contact, but the
63 contact has no subscription to you.
64 \"both\" means a mutual subscription.
65
66 Having a \"presence subscription\" means being able to see the
67 other person's presence.
68
69 Some fancy arrows you might want to use, if your system can
70 display them: ← → ⇄ ↔"
71 :type '(list (cons :format "%v" (const :format "" "none") (string :tag "None"))
72 (cons :format "%v" (const :format "" "from") (string :tag "From"))
73 (cons :format "%v" (const :format "" "to") (string :tag "To"))
74 (cons :format "%v" (const :format "" "both") (string :tag "Both")))
75 :group 'jabber-roster)
76
77 (defcustom jabber-resource-line-format " %r - %s (%S), priority %p"
78 "The format specification of resource lines in the roster display.
79 These are displayed when `jabber-show-resources' permits it.
80
81 These fields are available:
82
83 %c \"*\" if the contact is connected, or \" \" if not
84 %n Nickname of contact, or JID if no nickname
85 %j Bare JID of contact (without resource)
86 %p Priority of this resource
87 %r Name of this resource
88 %s Availability of resource as string (\"Online\", \"Away\" etc)
89 %S Status string specified by resource."
90 :type 'string
91 :group 'jabber-roster)
92
93 (defcustom jabber-roster-sort-functions
94 '(jabber-roster-sort-by-status jabber-roster-sort-by-displayname)
95 "Sort roster according to these criteria.
96
97 These functions should take two roster items A and B, and return:
98 <0 if A < B
99 0 if A = B
100 >0 if A > B."
101 :type 'hook
102 :options '(jabber-roster-sort-by-status
103 jabber-roster-sort-by-displayname
104 jabber-roster-sort-by-group)
105 :group 'jabber-roster)
106
107 (defcustom jabber-sort-order '("chat" "" "away" "dnd" "xa")
108 "Sort by status in this order. Anything not in list goes last.
109 Offline is represented as nil."
110 :type '(repeat (restricted-sexp :match-alternatives (stringp nil)))
111 :group 'jabber-roster)
112
113 (defcustom jabber-show-resources 'sometimes
114 "Show contacts' resources in roster?
115 This can be one of the following symbols:
116
117 nil Never show resources
118 sometimes Show resources when there are more than one
119 always Always show resources."
120 :type '(radio (const :tag "Never" nil)
121 (const :tag "When more than one connected resource" sometimes)
122 (const :tag "Always" always))
123 :group 'jabber-roster)
124
125 (defcustom jabber-show-offline-contacts t
126 "Show offline contacts in roster when non-nil."
127 :type 'boolean
128 :group 'jabber-roster)
129
130 (defcustom jabber-remove-newlines t
131 "Remove newlines in status messages?
132 Newlines in status messages mess up the roster display. However,
133 they are essential to status message poets. Therefore, you get to
134 choose the behaviour.
135
136 Trailing newlines are always removed, regardless of this variable."
137 :type 'boolean
138 :group 'jabber-roster)
139
140 (defcustom jabber-roster-show-bindings t
141 "Show keybindings in roster buffer?."
142 :type 'boolean
143 :group 'jabber-roster)
144
145 (defcustom jabber-roster-show-title t
146 "Show title in roster buffer?."
147 :type 'boolean
148 :group 'jabber-roster)
149
150 (defcustom jabber-roster-mode-hook nil
151 "Hook run when entering Roster mode."
152 :group 'jabber-roster
153 :type 'hook)
154
155 (defcustom jabber-roster-default-group-name "other"
156 "Default group name for buddies without groups."
157 :group 'jabber-roster
158 :type 'string
159 :get '(lambda (var)
160 (let ((val (symbol-value var)))
161 (when (stringp val)
162 (set-text-properties 0 (length val) nil val))
163 val))
164 :set '(lambda (var val)
165 (when (stringp val)
166 (set-text-properties 0 (length val) nil val))
167 (custom-set-default var val)))
168
169 (defcustom jabber-roster-show-empty-group nil
170 "Show empty groups in roster?."
171 :group 'jabber-roster
172 :type 'boolean)
173
174 (defcustom jabber-roster-roll-up-group nil
175 "Show empty groups in roster?."
176 :group 'jabber-roster
177 :type 'boolean)
178
179 (defface jabber-roster-user-online
180 '((t (:foreground "blue" :weight bold :slant normal)))
181 "Face for displaying online users."
182 :group 'jabber-roster)
183
184 (defface jabber-roster-user-xa
185 '((((background dark)) (:foreground "magenta" :weight normal :slant italic))
186 (t (:foreground "black" :weight normal :slant italic)))
187 "Face for displaying extended away users."
188 :group 'jabber-roster)
189
190 (defface jabber-roster-user-dnd
191 '((t (:foreground "red" :weight normal :slant italic)))
192 "Face for displaying do not disturb users."
193 :group 'jabber-roster)
194
195 (defface jabber-roster-user-away
196 '((t (:foreground "dark green" :weight normal :slant italic)))
197 "Face for displaying away users."
198 :group 'jabber-roster)
199
200 (defface jabber-roster-user-chatty
201 '((t (:foreground "dark orange" :weight bold :slant normal)))
202 "Face for displaying chatty users."
203 :group 'jabber-roster)
204
205 (defface jabber-roster-user-error
206 '((t (:foreground "red" :weight light :slant italic)))
207 "Face for displaying users sending presence errors."
208 :group 'jabber-roster)
209
210 (defface jabber-roster-user-offline
211 '((t (:foreground "dark grey" :weight light :slant italic)))
212 "Face for displaying offline users."
213 :group 'jabber-roster)
214
215 (defvar jabber-roster-debug nil
216 "Debug roster draw.")
217
218 (defvar jabber-roster-mode-map
219 (let ((map (make-sparse-keymap)))
220 (suppress-keymap map)
221 (set-keymap-parent map jabber-common-keymap)
222 (define-key map [mouse-2] 'jabber-roster-mouse-2-action-at-point)
223 (define-key map (kbd "TAB") 'jabber-go-to-next-roster-item)
224 (define-key map (kbd "S-TAB") 'jabber-go-to-previous-roster-item)
225 (define-key map (kbd "M-TAB") 'jabber-go-to-previous-roster-item)
226 (define-key map (kbd "<backtab>") 'jabber-go-to-previous-roster-item)
227 (define-key map (kbd "RET") 'jabber-roster-ret-action-at-point)
228 (define-key map (kbd "C-k") 'jabber-roster-delete-at-point)
229
230 (define-key map "e" 'jabber-roster-edit-action-at-point)
231 (define-key map "s" 'jabber-send-subscription-request)
232 (define-key map "q" 'bury-buffer)
233 (define-key map "i" 'jabber-get-disco-items)
234 (define-key map "j" 'jabber-muc-join)
235 (define-key map "I" 'jabber-get-disco-info)
236 (define-key map "b" 'jabber-get-browse)
237 (define-key map "v" 'jabber-get-version)
238 (define-key map "a" 'jabber-send-presence)
239 (define-key map "g" 'jabber-display-roster)
240 (define-key map "S" 'jabber-ft-send)
241 (define-key map "o" 'jabber-roster-toggle-offline-display)
242 (define-key map "H" 'jabber-roster-toggle-binding-display)
243 ;;(define-key map "D" 'jabber-disconnect)
244 map))
245
246 (defun jabber-roster-ret-action-at-point ()
247 "Action for RET.
248 Before try to roll up/down group. Eval `chat-with-jid-at-point' is no group at
249 point."
250 (interactive)
251 (let ((group-at-point (get-text-property (point)
252 'jabber-group))
253 (account-at-point (get-text-property (point)
254 'jabber-account))
255 (jid-at-point (get-text-property (point)
256 'jabber-jid)))
257 (if (and group-at-point account-at-point)
258 (jabber-roster-roll-group account-at-point group-at-point)
259 ;; Is this a normal contact, or a groupchat? Let's ask it.
260 (jabber-disco-get-info
261 account-at-point (jabber-jid-user jid-at-point) nil
262 #'jabber-roster-ret-action-at-point-1
263 jid-at-point))))
264
265 (defun jabber-roster-ret-action-at-point-1 (jc jid result)
266 ;; If we get an error, assume it's a normal contact.
267 (if (eq (car result) 'error)
268 (jabber-chat-with jc jid)
269 ;; Otherwise, let's check whether it has a groupchat identity.
270 (let ((identities (car result)))
271 (if (cl-find "conference" (if (sequencep identities) identities nil)
272 :key (lambda (i) (aref i 1))
273 :test #'string=)
274 ;; Yes! Let's join it.
275 (jabber-muc-join jc jid
276 (jabber-muc-read-my-nickname jc jid t)
277 t)
278 ;; No. Let's open a normal chat buffer.
279 (jabber-chat-with jc jid)))))
280
281 (defun jabber-roster-mouse-2-action-at-point (e)
282 "Action for mouse 2.
283 Before try to roll up/down group. Eval `chat-with-jid-at-point' is no group
284 at point."
285 (interactive "e")
286 (mouse-set-point e)
287 (let ((group-at-point (get-text-property (point)
288 'jabber-group))
289 (account-at-point (get-text-property (point)
290 'jabber-account)))
291 (if (and group-at-point account-at-point)
292 (jabber-roster-roll-group account-at-point group-at-point)
293 (jabber-popup-combined-menu))))
294
295 (defun jabber-roster-delete-at-point ()
296 "Delete at point from roster.
297 Try to delete the group from all contaacs.
298 Delete a jid if there is no group at point."
299 (interactive)
300 (let ((group-at-point (get-text-property (point)
301 'jabber-group))
302 (account-at-point (get-text-property (point)
303 'jabber-account)))
304 (if (and group-at-point account-at-point)
305 (let ((jids-with-group
306 (gethash group-at-point
307 (plist-get
308 (fsm-get-state-data account-at-point)
309 :roster-hash))))
310 (jabber-roster-delete-group-from-jids account-at-point
311 jids-with-group
312 group-at-point))
313 (jabber-roster-delete-jid-at-point))))
314
315 (defun jabber-roster-edit-action-at-point ()
316 "Action for e. Before try to edit group name.
317 Eval `jabber-roster-change' is no group at point."
318 (interactive)
319 (let ((group-at-point (get-text-property (point)
320 'jabber-group))
321 (account-at-point (get-text-property (point)
322 'jabber-account)))
323 (if (and group-at-point account-at-point)
324 (let ((jids-with-group
325 (gethash group-at-point
326 (plist-get
327 (fsm-get-state-data account-at-point)
328 :roster-hash))))
329 (jabber-roster-edit-group-from-jids account-at-point
330 jids-with-group
331 group-at-point))
332 (call-interactively 'jabber-roster-change))))
333
334 (defun jabber-roster-roll-group (jc group-name &optional set)
335 "Roll up/down group in roster.
336 If optional SET is t, roll up group.
337 If SET is nor t or nil, roll down group."
338 (let* ((state-data (fsm-get-state-data jc))
339 (roll-groups (plist-get state-data :roster-roll-groups))
340 (new-roll-groups (if (cl-find group-name roll-groups :test 'string=)
341 ;; group is rolled up, roll it down if needed
342 (if (or (not set) (and set (not (eq set t))))
343 (cl-remove-if-not (lambda (group-name-in-list)
344 (not (string= group-name
345 group-name-in-list)))
346 roll-groups)
347 roll-groups)
348 ;; group is rolled down, roll it up if needed
349 (if (or (not set) (and set (eq set t)))
350 (append roll-groups (list group-name))
351 roll-groups))))
352 (unless (equal roll-groups new-roll-groups)
353 (plist-put
354 state-data :roster-roll-groups
355 new-roll-groups)
356 (jabber-display-roster))))
357
358 (defun jabber-roster-mode ()
359 "Major mode for Jabber roster display.
360 Use the keybindings (mnemonic as Chat, Roster, Info, MUC, Service) to
361 bring up menus of actions.
362 \\{jabber-roster-mode-map}"
363 (kill-all-local-variables)
364 (setq major-mode 'jabber-roster-mode
365 mode-name "jabber-roster")
366 (use-local-map jabber-roster-mode-map)
367 (setq buffer-read-only t)
368 (if (fboundp 'run-mode-hooks)
369 (run-mode-hooks 'jabber-roster-mode-hook)
370 (run-hooks 'jabber-roster-mode-hook)))
371
372 (put 'jabber-roster-mode 'mode-class 'special)
373
374 ;;;###autoload
375 (defun jabber-switch-to-roster-buffer (&optional _jc)
376 "Switch to roster buffer.
377 Optional JC argument is ignored; it's there so this function can
378 be used in `jabber-post-connection-hooks'."
379 (interactive)
380 (if (not (get-buffer jabber-roster-buffer))
381 (jabber-display-roster)
382 (switch-to-buffer jabber-roster-buffer)))
383
384 (defun jabber-sort-roster (jc)
385 "Sort roster according to online status.
386 JC is the Jabber connection."
387 (let ((state-data (fsm-get-state-data jc)))
388 (dolist (group (plist-get state-data :roster-groups))
389 (let ((group-name (car group)))
390 (puthash group-name
391 (sort
392 (gethash group-name
393 (plist-get state-data :roster-hash))
394 #'jabber-roster-sort-items)
395 (plist-get state-data :roster-hash))))))
396
397 (defun jabber-roster-prepare-roster (jc)
398 "Make a hash based roster.
399 JC is the Jabber connection."
400 (let* ((state-data (fsm-get-state-data jc))
401 (hash (make-hash-table :test 'equal))
402 (buddies (plist-get state-data :roster))
403 (all-groups '()))
404 (dolist (buddy buddies)
405 (let ((groups (get buddy 'groups)))
406 (if groups
407 (progn
408 (dolist (group groups)
409 (progn
410 (setq all-groups (append all-groups (list group)))
411 (puthash group
412 (append (gethash group hash)
413 (list buddy))
414 hash))))
415 (progn
416 (setq all-groups (append all-groups
417 (list jabber-roster-default-group-name)))
418 (puthash jabber-roster-default-group-name
419 (append (gethash jabber-roster-default-group-name hash)
420 (list buddy))
421 hash)))))
422
423 ;; remove duplicates name of group
424 (setq all-groups (sort
425 (cl-remove-duplicates all-groups
426 :test 'string=)
427 'string<))
428
429 ;; put to state-data all-groups as list of list
430 (plist-put state-data :roster-groups
431 (mapcar #'list all-groups))
432
433 ;; put to state-data hash-roster
434 (plist-put state-data :roster-hash
435 hash)))
436
437 (defun jabber-roster-sort-items (a b)
438 "Sort roster items A and B according to `jabber-roster-sort-functions'.
439 Return t if A is less than B."
440 (let ((result nil))
441 (seq-find (lambda (fn)
442 (setq result (funcall fn a b))
443 (not (= result 0)))
444 jabber-roster-sort-functions)
445 (< result 0)))
446
447 (defun jabber-roster-sort-by-status (a b)
448 "Sort roster items by online status.
449 See `jabber-sort-order' for order used."
450 (cl-flet ((order (item) (length (member (get item 'show) jabber-sort-order))))
451 (let ((a-order (order a))
452 (b-order (order b)))
453 ;; Note reversed test. Items with longer X-order go first.
454 (cond
455 ((< a-order b-order)
456 1)
457 ((> a-order b-order)
458 -1)
459 (t
460 0)))))
461
462 (defun jabber-roster-sort-by-displayname (a b)
463 "Sort roster items by displayed name."
464 (let ((a-name (jabber-jid-displayname a))
465 (b-name (jabber-jid-displayname b)))
466 (cond
467 ((string-lessp a-name b-name) -1)
468 ((string= a-name b-name) 0)
469 (t 1))))
470
471 (defun jabber-roster-sort-by-group (a b)
472 "Sort roster items by group membership."
473 (cl-flet ((first-group (item) (or (car (get item 'groups)) "")))
474 (let ((a-group (first-group a))
475 (b-group (first-group b)))
476 (cond
477 ((string-lessp a-group b-group) -1)
478 ((string= a-group b-group) 0)
479 (t 1)))))
480
481 (defun jabber-fix-status (status)
482 "Make status strings more readable."
483 (when status
484 (when (string-match "\n+$" status)
485 (setq status (replace-match "" t t status)))
486 (when jabber-remove-newlines
487 (while (string-match "\n" status)
488 (setq status (replace-match " " t t status))))
489 status))
490
491 (defvar jabber-roster-ewoc nil
492 "Ewoc displaying the roster.
493 There is only one; we don't rely on buffer-local variables or
494 such.")
495
496 (defun jabber-roster-filter-display (buddies)
497 "Filter BUDDIES for items to be displayed in the roster."
498 (cl-remove-if-not (lambda (buddy) (or jabber-show-offline-contacts
499 (get buddy 'connected)))
500 buddies))
501
502 (defun jabber-roster-toggle-offline-display ()
503 "Toggle display of offline contacts.
504 To change this permanently, customize the `jabber-show-offline-contacts'."
505 (interactive)
506 (setq jabber-show-offline-contacts
507 (not jabber-show-offline-contacts))
508 (jabber-display-roster))
509
510 (defun jabber-roster-toggle-binding-display ()
511 "Toggle display of the roster binding text."
512 (interactive)
513 (setq jabber-roster-show-bindings
514 (not jabber-roster-show-bindings))
515 (jabber-display-roster))
516
517 (defun jabber-display-roster ()
518 "Switch to the main jabber buffer and refresh it.
519 Switch to the roster display and refresh it to reflect the current
520 information."
521 (interactive)
522 (with-current-buffer (get-buffer-create jabber-roster-buffer)
523 (if (not (eq major-mode 'jabber-roster-mode))
524 (jabber-roster-mode))
525 (setq buffer-read-only nil)
526 ;; line-number-at-pos is in Emacs >= 21.4. Only used to avoid
527 ;; excessive scrolling when updating roster, so not absolutely
528 ;; necessary.
529 (let ((current-line (and (fboundp 'line-number-at-pos) (line-number-at-pos)))
530 (current-column (current-column)))
531 (erase-buffer)
532 (setq jabber-roster-ewoc nil)
533 (when jabber-roster-show-title
534 (insert (jabber-propertize "Jabber roster" 'face 'jabber-title-large) "\n"))
535 (when jabber-roster-show-bindings
536 (insert "RET Open chat buffer C-k Delete roster item
537 e Edit item s Send subscription request
538 q Bury buffer i Get disco items
539 I Get disco info b Browse
540 j Join groupchat (MUC) v Get client version
541 a Send presence o Show offline contacts on/off
542 C-c C-c Chat menu C-c C-m Multi-User Chat menu
543 C-c C-i Info menu C-c C-r Roster menu
544 C-c C-s Service menu
545
546 H Toggle displaying this text
547 "))
548 (insert "__________________________________\n\n")
549 (if (null jabber-connections)
550 (insert "Not connected\n")
551 (let ((map (make-sparse-keymap)))
552 (define-key map [mouse-2] #'jabber-send-presence)
553 (insert (jabber-propertize (concat (format " - %s"
554 (cdr (assoc *jabber-current-show* jabber-presence-strings)))
555 (if (not (zerop (length *jabber-current-status*)))
556 (format " (%s)"
557 (jabber-fix-status *jabber-current-status*)))
558 " -")
559 'face (or (cdr (assoc *jabber-current-show* jabber-presence-faces))
560 'jabber-roster-user-online)
561 ;;'mouse-face (cons 'background-color "light grey")
562 'keymap map)
563 "\n")))
564
565 (dolist (jc jabber-connections)
566 ;; use a hash-based roster
567 (when (not (plist-get (fsm-get-state-data jc) :roster-hash))
568 (jabber-roster-prepare-roster jc))
569 ;; We sort everything before putting it in the ewoc
570 (jabber-sort-roster jc)
571 (let ((before-ewoc (point))
572 (ewoc (ewoc-create
573 (let ((jc jc))
574 (lambda (data)
575 (let* ((group (car data))
576 (group-name (car group))
577 (buddy (car (cdr data))))
578 (jabber-display-roster-entry jc group-name buddy))))
579 (concat
580 (jabber-propertize (concat
581 (plist-get (fsm-get-state-data jc) :username)
582 "@"
583 (plist-get (fsm-get-state-data jc) :server))
584 'face 'jabber-title-medium)
585 "\n__________________________________\n")
586 "__________________________________"))
587 (new-groups '()))
588 (plist-put(fsm-get-state-data jc) :roster-ewoc ewoc)
589 (dolist (group (plist-get (fsm-get-state-data jc) :roster-groups))
590 (let* ((group-name (car group))
591 (buddies (jabber-roster-filter-display
592 (gethash group-name
593 (plist-get (fsm-get-state-data jc) :roster-hash)))))
594 (when (or jabber-roster-show-empty-group
595 (> (length buddies) 0))
596 (let ((group-node (ewoc-enter-last ewoc (list group nil))))
597 (if (not (cl-find
598 group-name
599 (plist-get (fsm-get-state-data jc) :roster-roll-groups)
600 :test 'string=))
601 (dolist (buddy (reverse buddies))
602 (ewoc-enter-after ewoc group-node (list group buddy))))))))
603 (goto-char (point-max))
604 (insert "\n")
605 (put-text-property before-ewoc (point)
606 'jabber-account jc)))
607
608 (goto-char (point-min))
609 (setq buffer-read-only t)
610 (if (called-interactively-p 'interactive)
611 (dolist (hook '(jabber-info-message-hooks jabber-alert-info-message-hooks))
612 (run-hook-with-args hook 'roster (current-buffer) (funcall jabber-alert-info-message-function 'roster (current-buffer)))))
613 (when current-line
614 ;; Go back to previous line - don't use goto-line, since it
615 ;; sets the mark.
616 (goto-char (point-min))
617 (forward-line (1- current-line))
618 ;; ...and go back to previous column
619 (move-to-column current-column)))))
620
621 (defun jabber-display-roster-entry (jc group-name buddy)
622 "Format and insert a roster entry for BUDDY at point.
623 BUDDY is a JID symbol. JC is the Jabber connection."
624 (if buddy
625 (let ((buddy-str (format-spec
626 jabber-roster-line-format
627 (list
628 (cons ?a (jabber-propertize " " 'display (get buddy 'avatar)))
629 (cons ?c (if (get buddy 'connected) "*" " "))
630 (cons ?u (cdr (assoc
631 (or
632 (get buddy 'subscription) "none")
633 jabber-roster-subscription-display)))
634 (cons ?n (if (> (length (get buddy 'name)) 0)
635 (get buddy 'name)
636 (symbol-name buddy)))
637 (cons ?j (symbol-name buddy))
638 (cons ?r (or (get buddy 'resource) ""))
639 (cons ?s (or (cdr (assoc (get buddy 'show)
640 jabber-presence-strings))
641 (get buddy 'show)))
642 (cons ?S (if (get buddy 'status)
643 (jabber-fix-status (get buddy 'status))
644 ""))))))
645 (add-text-properties 0
646 (length buddy-str)
647 (list
648 'face
649 (or (cdr (assoc (get buddy 'show) jabber-presence-faces))
650 'jabber-roster-user-online)
651 ;;'mouse-face
652 ;;(cons 'background-color "light grey")
653 'help-echo
654 (symbol-name buddy)
655 'jabber-jid
656 (symbol-name buddy)
657 'jabber-account
658 jc)
659 buddy-str)
660 (insert buddy-str)
661
662 (when (or (eq jabber-show-resources 'always)
663 (and (eq jabber-show-resources 'sometimes)
664 (> (jabber-count-connected-resources buddy) 1)))
665 (dolist (resource (get buddy 'resources))
666 (when (plist-get (cdr resource) 'connected)
667 (let ((resource-str (format-spec jabber-resource-line-format
668 (list
669 (cons ?c "*")
670 (cons ?n (if (>
671 (length
672 (get buddy 'name)) 0)
673 (get buddy 'name)
674 (symbol-name buddy)))
675 (cons ?j (symbol-name buddy))
676 (cons ?r (if (>
677 (length
678 (car resource)) 0)
679 (car resource)
680 "empty"))
681 (cons ?s (or
682 (cdr (assoc
683 (plist-get
684 (cdr resource) 'show)
685 jabber-presence-strings))
686 (plist-get
687 (cdr resource) 'show)))
688 (cons ?S (if (plist-get
689 (cdr resource) 'status)
690 (jabber-fix-status
691 (plist-get (cdr resource)
692 'status))
693 ""))
694 (cons ?p (number-to-string
695 (plist-get (cdr resource)
696 'priority)))))))
697 (add-text-properties 0
698 (length resource-str)
699 (list
700 'face
701 (or (cdr (assoc (plist-get
702 (cdr resource)
703 'show)
704 jabber-presence-faces))
705 'jabber-roster-user-online)
706 'jabber-jid
707 (format "%s/%s" (symbol-name buddy) (car resource))
708 'jabber-account
709 jc)
710 resource-str)
711 (insert "\n" resource-str))))))
712 (let ((group-name (or group-name
713 jabber-roster-default-group-name)))
714 (add-text-properties 0
715 (length group-name)
716 (list
717 'face 'jabber-title-small
718 'jabber-group group-name
719 'jabber-account jc)
720 group-name)
721 (insert group-name))))
722
723 ;;;###autoload
724 (defun jabber-roster-update (jc new-items changed-items deleted-items)
725 "Update roster, in memory and on display.
726 Add NEW-ITEMS, update CHANGED-ITEMS and remove DELETED-ITEMS, all
727 three being lists of JID symbols.
728 JC is the Jabber connection."
729 (let* ((roster (plist-get (fsm-get-state-data jc) :roster))
730 (hash (plist-get (fsm-get-state-data jc) :roster-hash))
731 (ewoc (plist-get (fsm-get-state-data jc) :roster-ewoc))
732 (all-groups (plist-get (fsm-get-state-data jc) :roster-groups))
733 (terminator
734 (lambda (deleted-items)
735 (dolist (delete-this deleted-items)
736 (let ((groups (get delete-this 'groups))
737 (terminator
738 (lambda (g)
739 (let*
740 ((group (or g jabber-roster-default-group-name))
741 (buddies (gethash group hash)))
742 (when (not buddies)
743 (setq new-groups (append new-groups (list group))))
744 (puthash group
745 (delq delete-this buddies)
746 hash)))))
747 (if groups
748 (dolist (group groups)
749 (terminator group))
750 (terminator groups)))))))
751
752 ;; fix a old-roster
753 (dolist (delete-this deleted-items)
754 (setq roster (delq delete-this roster)))
755 (setq roster (append new-items roster))
756 (plist-put (fsm-get-state-data jc) :roster roster)
757
758 ;; update a hash-roster
759 (if (not hash)
760 (jabber-roster-prepare-roster jc)
761
762 (when jabber-roster-debug
763 (message "update hash-based roster"))
764
765 ;; delete items
766 (dolist (delete-this (append deleted-items changed-items))
767 (let ((jid (symbol-name delete-this)))
768 (when jabber-roster-debug
769 (message (concat "delete jid: " jid)))
770 (dolist (group (mapcar (lambda (g) (car g)) all-groups))
771 (when jabber-roster-debug
772 (message (concat "try to delete jid: " jid " from group " group)))
773 (puthash group
774 (delq delete-this (gethash group hash))
775 hash))))
776
777 ;; insert changed-items
778 (dolist (insert-this (append changed-items new-items))
779 (let ((jid (symbol-name insert-this)))
780 (when jabber-roster-debug
781 (message (concat "insert jid: " jid)))
782 (dolist (group (or (get insert-this 'groups)
783 (list jabber-roster-default-group-name)))
784 (when jabber-roster-debug
785 (message (concat "insert jid: " jid " to group " group)))
786 (puthash group
787 (append (gethash group hash)
788 (list insert-this))
789 hash)
790 (setq all-groups (append all-groups (list (list group)))))))
791
792 (when jabber-roster-debug
793 (message "remove duplicates from new group"))
794 (setq all-groups (sort
795 (cl-remove-duplicates all-groups
796 :test (lambda (g1 g2)
797 (let ((g1-name (car g1))
798 (g2-name (car g2)))
799 (string= g1-name
800 g2-name))))
801 (lambda (g1 g2)
802 (let ((g1-name (car g1))
803 (g2-name (car g2)))
804 (string< g1-name
805 g2-name)))))
806
807 (plist-put (fsm-get-state-data jc) :roster-groups all-groups))
808
809 (when jabber-roster-debug
810 (message "re display roster"))
811
812 ;; recreate roster buffer
813 (jabber-display-roster)))
814
815 (defalias 'jabber-presence-update-roster 'ignore)
816 ;;jabber-presence-update-roster is not needed anymore.
817 ;;Its work is done in `jabber-process-presence'."
818 (make-obsolete 'jabber-presence-update-roster 'ignore "27.2")
819
820 (defun jabber-next-property (&optional prev)
821 "Return position of next property appearence or nil if there is none.
822 If optional PREV is non-nil, return position of previous property appearence."
823 (let ((pos (point))
824 (found nil)
825 (nextprev (if prev 'previous-single-property-change
826 'next-single-property-change)))
827 (while (not found)
828 (setq pos
829 (let ((jid (funcall nextprev pos 'jabber-jid))
830 (group (funcall nextprev pos 'jabber-group)))
831 (cond
832 ((not jid) group)
833 ((not group) jid)
834 (t (funcall (if prev 'max 'min) jid group)))))
835 (if (not pos)
836 (setq found t)
837 (setq found (or (get-text-property pos 'jabber-jid)
838 (get-text-property pos 'jabber-group)))))
839 pos))
840
841 (defun jabber-go-to-next-roster-item ()
842 "Move the cursor to the next jid/group in the buffer."
843 (interactive)
844 (let* ((next (jabber-next-property))
845 (next (if (not next)
846 (progn (goto-char (point-min))
847 (jabber-next-property)) next)))
848 (if next (goto-char next)
849 (goto-char (point-min)))))
850
851 (defun jabber-go-to-previous-roster-item ()
852 "Move the cursor to the previous jid/group in the buffer."
853 (interactive)
854 (let* ((previous (jabber-next-property 'prev))
855 (previous (if (not previous)
856 (progn (goto-char (point-max))
857 (jabber-next-property 'prev)) previous)))
858 (if previous (goto-char previous)
859 (goto-char (point-max)))))
860
861 (defun jabber-roster-restore-groups (jc)
862 "Restore roster's groups rolling state from private storage.
863 JC is the Jabber connection."
864 (interactive (list (jabber-read-account)))
865 (jabber-private-get jc 'roster "emacs-jabber"
866 'jabber-roster-restore-groups-1 'ignore))
867
868 (defun jabber-roster-restore-groups-1 (jc xml-data)
869 "Parse roster groups and restore rolling state.
870
871 JC is the Jabber connection.
872 XML-DATA is the parsed tree data from the stream (stanzas)
873 obtained from `xml-parse-region'."
874 (when (string= (jabber-xml-get-xmlns xml-data) "emacs-jabber")
875 (let* ((data (car (last xml-data)))
876 (groups (if (stringp data) (split-string data "\n") nil)))
877 (dolist (group groups)
878 (jabber-roster-roll-group jc group t)))))
879
880 (defun jabber-roster-save-groups ()
881 "Save roster's groups rolling state in private storage."
882 (interactive)
883 (dolist (jc jabber-connections)
884 (let* ((groups (plist-get (fsm-get-state-data jc) :roster-roll-groups))
885 (roll-groups
886 (if groups
887 (mapconcat (lambda (a) (substring-no-properties a)) groups "\n")
888 "")))
889 (jabber-private-set jc
890 `(roster ((xmlns . "emacs-jabber"))
891 ,roll-groups)
892 'jabber-report-success "Roster groups saved"
893 'jabber-report-success "Failed to save roster groups"))))
894
895 (provide 'jabber-roster)
896
897 ;;; arch-tag: 096af063-0526-4dd2-90fd-bc6b5ba07d32