1 ;; jabber-roster.el - displaying the roster -*- coding: utf-8; -*-
3 ;; Copyright (C) 2003, 2004, 2007, 2008 - Magnus Henoch - mange@freemail.hu
4 ;; Copyright (C) 2002, 2003, 2004 - tom berger - object@intelectronica.net
6 ;; This file is a part of jabber.el.
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 2 of the License, or
11 ;; (at your option) any later version.
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.
18 ;; You should have received a copy of the GNU General Public License
19 ;; along with this program; if not, write to the Free Software
20 ;; Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 (require 'jabber-presence
)
23 (require 'jabber-util
)
24 (require 'jabber-alert
)
25 (require 'jabber-keymap
)
26 (require 'format-spec
)
28 (defgroup jabber-roster nil
"roster display options"
31 (defcustom jabber-roster-line-format
" %a %c %-25n %u %-8s %S"
32 "The format specification of the lines in the roster display.
34 These fields are available:
37 %c \"*\" if the contact is connected, or \" \" if not
38 %u sUbscription state - see below
39 %n Nickname of contact, or JID if no nickname
40 %j Bare JID of contact (without resource)
41 %r Highest-priority resource of contact
42 %s Availability of contact as string (\"Online\", \"Away\" etc)
43 %S Status string specified by contact
45 %u is replaced by one of the strings given by
46 `jabber-roster-subscription-display'."
48 :group
'jabber-roster
)
50 (defcustom jabber-roster-subscription-display
'(("none" .
" ")
54 "Strings used for indicating subscription status of contacts.
55 \"none\" means that there is no subscription between you and the
57 \"from\" means that the contact has a subscription to you, but you
58 have no subscription to the contact.
59 \"to\" means that you have a subscription to the contact, but the
60 contact has no subscription to you.
61 \"both\" means a mutual subscription.
63 Having a \"presence subscription\" means being able to see the
64 other person's presence.
66 Some fancy arrows you might want to use, if your system can
67 display them: ← → ⇄ ↔"
68 :type
'(list (cons :format
"%v" (const :format
"" "none") (string :tag
"None"))
69 (cons :format
"%v" (const :format
"" "from") (string :tag
"From"))
70 (cons :format
"%v" (const :format
"" "to") (string :tag
"To"))
71 (cons :format
"%v" (const :format
"" "both") (string :tag
"Both")))
72 :group
'jabber-roster
)
74 (defcustom jabber-resource-line-format
" %r - %s (%S), priority %p"
75 "The format specification of resource lines in the roster display.
76 These are displayed when `jabber-show-resources' permits it.
78 These fields are available:
80 %c \"*\" if the contact is connected, or \" \" if not
81 %n Nickname of contact, or JID if no nickname
82 %j Bare JID of contact (without resource)
83 %p Priority of this resource
84 %r Name of this resource
85 %s Availability of resource as string (\"Online\", \"Away\" etc)
86 %S Status string specified by resource"
88 :group
'jabber-roster
)
90 (defcustom jabber-roster-sort-functions
91 '(jabber-roster-sort-by-status jabber-roster-sort-by-displayname
)
92 "Sort roster according to these criteria.
94 These functions should take two roster items A and B, and return:
99 :options
'(jabber-roster-sort-by-status
100 jabber-roster-sort-by-displayname
101 jabber-roster-sort-by-group
)
102 :group
'jabber-roster
)
104 (defcustom jabber-sort-order
'("chat" "" "away" "dnd" "xa")
105 "Sort by status in this order. Anything not in list goes last.
106 Offline is represented as nil."
107 :type
'(repeat (restricted-sexp :match-alternatives
(stringp nil
)))
108 :group
'jabber-roster
)
110 (defcustom jabber-show-resources
'sometimes
111 "Show contacts' resources in roster?
112 This can be one of the following symbols:
114 nil Never show resources
115 sometimes Show resources when there are more than one
116 always Always show resources"
117 :type
'(radio (const :tag
"Never" nil
)
118 (const :tag
"When more than one connected resource" sometimes
)
119 (const :tag
"Always" always
))
120 :group
'jabber-roster
)
122 (defcustom jabber-show-offline-contacts t
123 "Show offline contacts in roster when non-nil"
125 :group
'jabber-roster
)
127 (defcustom jabber-remove-newlines t
128 "Remove newlines in status messages?
129 Newlines in status messages mess up the roster display. However,
130 they are essential to status message poets. Therefore, you get to
131 choose the behaviour.
133 Trailing newlines are always removed, regardless of this variable."
135 :group
'jabber-roster
)
137 (defcustom jabber-roster-show-bindings t
138 "Show keybindings in roster buffer?"
140 :group
'jabber-roster
)
142 (defcustom jabber-roster-mode-hook nil
143 "Hook run when entering Roster mode."
144 :group
'jabber-roster
147 (defface jabber-roster-user-online
148 '((t (:foreground
"blue" :weight bold
:slant normal
)))
149 "face for displaying online users"
150 :group
'jabber-roster
)
152 (defface jabber-roster-user-xa
153 '((((background dark
)) (:foreground
"magenta" :weight normal
:slant italic
))
154 (t (:foreground
"black" :weight normal
:slant italic
)))
155 "face for displaying extended away users"
156 :group
'jabber-roster
)
158 (defface jabber-roster-user-dnd
159 '((t (:foreground
"red" :weight normal
:slant italic
)))
160 "face for displaying do not disturb users"
161 :group
'jabber-roster
)
163 (defface jabber-roster-user-away
164 '((t (:foreground
"dark green" :weight normal
:slant italic
)))
165 "face for displaying away users"
166 :group
'jabber-roster
)
168 (defface jabber-roster-user-chatty
169 '((t (:foreground
"dark orange" :weight bold
:slant normal
)))
170 "face for displaying chatty users"
171 :group
'jabber-roster
)
173 (defface jabber-roster-user-error
174 '((t (:foreground
"red" :weight light
:slant italic
)))
175 "face for displaying users sending presence errors"
176 :group
'jabber-roster
)
178 (defface jabber-roster-user-offline
179 '((t (:foreground
"dark grey" :weight light
:slant italic
)))
180 "face for displaying offline users"
181 :group
'jabber-roster
)
183 (defvar jabber-roster-mode-map
184 (let ((map (make-sparse-keymap)))
185 (set-keymap-parent map jabber-common-keymap
)
186 (define-key map
[mouse-2
] 'jabber-popup-combined-menu
)
187 (define-key map
(kbd "TAB") 'jabber-go-to-next-jid
)
188 (define-key map
(kbd "S-TAB") 'jabber-go-to-previous-jid
)
189 (define-key map
(kbd "M-TAB") 'jabber-go-to-previous-jid
)
190 (define-key map
(kbd "<backtab>") 'jabber-go-to-previous-jid
)
191 (define-key map
(kbd "RET") 'jabber-chat-with-jid-at-point
)
192 (define-key map
(kbd "C-k") 'jabber-roster-delete-jid-at-point
)
194 (define-key map
"e" 'jabber-roster-change
)
195 (define-key map
"s" 'jabber-send-subscription-request
)
196 (define-key map
"q" 'bury-buffer
)
197 (define-key map
"i" 'jabber-get-disco-items
)
198 (define-key map
"j" 'jabber-groupchat-join
)
199 (define-key map
"I" 'jabber-get-disco-info
)
200 (define-key map
"b" 'jabber-get-browse
)
201 (define-key map
"v" 'jabber-get-version
)
202 (define-key map
"a" 'jabber-send-presence
)
203 (define-key map
"g" 'jabber-display-roster
)
204 (define-key map
"S" 'jabber-ft-send
)
205 (define-key map
"o" 'jabber-roster-toggle-offline-display
)
206 (define-key map
"H" 'jabber-roster-toggle-binding-display
)
207 ;;(define-key map "D" 'jabber-disconnect)
210 (defun jabber-roster-mode ()
211 "Major mode for Jabber roster display.
212 Use the keybindings (mnemonic as Chat, Roster, Info, MUC, Service) to
213 bring up menus of actions.
214 \\{jabber-roster-mode-map}"
215 (kill-all-local-variables)
216 (setq major-mode
'jabber-roster-mode
217 mode-name
"jabber-roster")
218 (use-local-map jabber-roster-mode-map
)
219 (setq buffer-read-only t
)
220 (if (fboundp 'run-mode-hooks
)
221 (run-mode-hooks 'jabber-roster-mode-hook
)
222 (run-hooks 'jabber-roster-mode-hook
)))
224 (put 'jabber-roster-mode
'mode-class
'special
)
226 (defun jabber-switch-to-roster-buffer (&optional jc
)
227 "Switch to roster buffer.
228 Optional JC argument is ignored; it's there so this function can
229 be used in `jabber-post-connection-hooks'."
231 (if (not (get-buffer jabber-roster-buffer
))
232 (jabber-display-roster)
233 (switch-to-buffer jabber-roster-buffer
)))
235 (defun jabber-sort-roster (jc)
236 "sort roster according to online status"
237 (let ((state-data (fsm-get-state-data jc
)))
238 (plist-put state-data
:roster
240 (plist-get state-data
:roster
)
241 #'jabber-roster-sort-items
))))
243 (defun jabber-roster-sort-items (a b
)
244 "Sort roster items A and B according to `jabber-roster-sort-functions'.
245 Return t if A is less than B."
246 (dolist (fn jabber-roster-sort-functions
)
247 (let ((comparison (funcall fn a b
)))
254 (defun jabber-roster-sort-by-status (a b
)
255 "Sort roster items by online status.
256 See `jabber-sort-order' for order used."
257 (flet ((order (item) (length (member (get item
'show
) jabber-sort-order
))))
258 (let ((a-order (order a
))
260 ;; Note reversed test. Items with longer X-order go first.
269 (defun jabber-roster-sort-by-displayname (a b
)
270 "Sort roster items by displayed name."
271 (let ((a-name (jabber-jid-displayname a
))
272 (b-name (jabber-jid-displayname b
)))
274 ((string-lessp a-name b-name
) -
1)
275 ((string= a-name b-name
) 0)
278 (defun jabber-roster-sort-by-group (a b
)
279 "Sort roster items by group membership."
280 (flet ((first-group (item) (or (car (get item
'groups
)) "")))
281 (let ((a-group (first-group a
))
282 (b-group (first-group b
)))
284 ((string-lessp a-group b-group
) -
1)
285 ((string= a-group b-group
) 0)
288 (defun jabber-fix-status (status)
289 "Make status strings more readable"
291 (when (string-match "\n+$" status
)
292 (setq status
(replace-match "" t t status
)))
293 (when jabber-remove-newlines
294 (while (string-match "\n" status
)
295 (setq status
(replace-match " " t t status
))))
298 (defvar jabber-roster-ewoc nil
299 "Ewoc displaying the roster.
300 There is only one; we don't rely on buffer-local variables or
303 (defun jabber-roster-filter-display (buddies)
304 "Filter BUDDIES for items to be displayed in the roster"
305 (remove-if-not (lambda (buddy) (or jabber-show-offline-contacts
306 (get buddy
'connected
)))
309 (defun jabber-roster-toggle-offline-display ()
310 "Toggle display of offline contacts."
312 (setq jabber-show-offline-contacts
313 (not jabber-show-offline-contacts
))
314 (jabber-display-roster))
316 (defun jabber-roster-toggle-binding-display ()
317 "Toggle display of the roster binding text."
319 (setq jabber-roster-show-bindings
320 (not jabber-roster-show-bindings
))
321 (jabber-display-roster))
323 (defun jabber-display-roster ()
324 "switch to the main jabber buffer and refresh the roster display to reflect the current information"
326 (with-current-buffer (get-buffer-create jabber-roster-buffer
)
327 (if (not (eq major-mode
'jabber-roster-mode
))
328 (jabber-roster-mode))
329 (setq buffer-read-only nil
)
330 ;; line-number-at-pos is in Emacs >= 21.4. Only used to avoid
331 ;; excessive scrolling when updating roster, so not absolutely
333 (let ((current-line (and (fboundp 'line-number-at-pos
) (line-number-at-pos)))
334 (current-column (current-column)))
336 (setq jabber-roster-ewoc nil
)
337 (insert (jabber-propertize "Jabber roster" 'face
'jabber-title-large
) "\n")
338 (when jabber-roster-show-bindings
339 (insert "RET Open chat buffer C-k Delete roster item
340 e Edit item s Send subscription request
341 q Bury buffer i Get disco items
342 I Get disco info b Browse
343 j Join groupchat (MUC) v Get client version
344 a Send presence o Show offline contacts on/off
345 C-c C-c Chat menu C-c C-m Multi-User Chat menu
346 C-c C-i Info menu C-c C-r Roster menu
349 H Toggle displaying this text
351 (insert "__________________________________\n\n")
352 (if (null jabber-connections
)
353 (insert "Not connected\n")
354 (let ((map (make-sparse-keymap)))
355 (define-key map
[mouse-2
] #'jabber-send-presence
)
356 (insert (jabber-propertize (concat (format " - %s"
357 (cdr (assoc *jabber-current-show
* jabber-presence-strings
)))
358 (if (not (zerop (length *jabber-current-status
*)))
360 (jabber-fix-status *jabber-current-status
*)))
362 'face
(or (cdr (assoc *jabber-current-show
* jabber-presence-faces
))
363 'jabber-roster-user-online
)
364 ;;'mouse-face (cons 'background-color "light grey")
368 (dolist (jc jabber-connections
)
369 ;; We sort everything before putting it in the ewoc
370 (jabber-sort-roster jc
)
371 (let ((before-ewoc (point))
373 (lexical-let ((jc jc
))
375 (jabber-display-roster-entry jc buddy
)))
377 (jabber-propertize (concat
378 (plist-get (fsm-get-state-data jc
) :username
)
380 (plist-get (fsm-get-state-data jc
) :server
))
381 'face
'jabber-title-medium
)
382 "\n__________________________________\n")
383 "__________________________________")))
384 (plist-put (fsm-get-state-data jc
) :roster-ewoc ewoc
)
385 (dolist (buddy (jabber-roster-filter-display
386 (plist-get (fsm-get-state-data jc
) :roster
)))
387 (ewoc-enter-last ewoc buddy
))
388 (goto-char (point-max))
390 (put-text-property before-ewoc
(point)
391 'jabber-account jc
)))
393 (goto-char (point-min))
394 (setq buffer-read-only t
)
396 (dolist (hook '(jabber-info-message-hooks jabber-alert-info-message-hooks
))
397 (run-hook-with-args hook
'roster
(current-buffer) (funcall jabber-alert-info-message-function
'roster
(current-buffer)))))
399 ;; Go back to previous line - don't use goto-line, since it
401 (goto-char (point-min))
402 (forward-line (1- current-line
))
403 ;; ...and go back to previous column
404 (move-to-column current-column
)))))
406 (defun jabber-display-roster-entry (jc buddy
)
407 "Format and insert a roster entry for BUDDY at point.
408 BUDDY is a JID symbol."
409 (let ((buddy-str (format-spec jabber-roster-line-format
411 (cons ?a
(jabber-propertize " "
412 'display
(get buddy
'avatar
)))
413 (cons ?c
(if (get buddy
'connected
) "*" " "))
414 (cons ?u
(cdr (assoc (or (get buddy
'subscription
) "none")
415 jabber-roster-subscription-display
)))
416 (cons ?n
(if (> (length (get buddy
'name
)) 0)
418 (symbol-name buddy
)))
419 (cons ?j
(symbol-name buddy
))
420 (cons ?r
(or (get buddy
'resource
) ""))
422 (cdr (assoc (get buddy
'show
) jabber-presence-strings
))
424 (cons ?S
(if (get buddy
'status
)
425 (jabber-fix-status (get buddy
'status
))
427 (add-text-properties 0
431 (or (cdr (assoc (get buddy
'show
) jabber-presence-faces
))
432 'jabber-roster-user-online
)
434 ;;(cons 'background-color "light grey")
442 ;; (let ((map (make-sparse-keymap))
443 ;; (chat-with-func (make-symbol (concat "jabber-chat-with" (symbol-name buddy)))))
444 ;; (fset chat-with-func `(lambda () (interactive) (jabber-chat-with ,(symbol-name buddy))))
445 ;; (define-key map [mouse-2] chat-with-func)
446 ;; (put-text-property 0
447 ;; (length buddy-str)
453 (when (or (eq jabber-show-resources
'always
)
454 (and (eq jabber-show-resources
'sometimes
)
455 (> (jabber-count-connected-resources buddy
) 1)))
456 (dolist (resource (get buddy
'resources
))
457 (when (plist-get (cdr resource
) 'connected
)
458 (let ((resource-str (format-spec jabber-resource-line-format
461 (cons ?n
(if (> (length (get buddy
'name
)) 0)
463 (symbol-name buddy
)))
464 (cons ?j
(symbol-name buddy
))
465 (cons ?r
(if (> (length (car resource
)) 0)
469 (cdr (assoc (plist-get (cdr resource
) 'show
) jabber-presence-strings
))
470 (plist-get (cdr resource
) 'show
)))
471 (cons ?S
(if (plist-get (cdr resource
) 'status
)
472 (jabber-fix-status (plist-get (cdr resource
) 'status
))
474 (cons ?p
(number-to-string (plist-get (cdr resource
) 'priority
)))))))
475 (add-text-properties 0
476 (length resource-str
)
479 (or (cdr (assoc (plist-get (cdr resource
) 'show
) jabber-presence-faces
))
480 'jabber-roster-user-online
)
482 (format "%s/%s" (symbol-name buddy
) (car resource
))
486 (insert "\n" resource-str
)))))))
488 (defun jabber-roster-update (jc new-items changed-items deleted-items
)
489 "Update roster, in memory and on display.
490 Add NEW-ITEMS, update CHANGED-ITEMS and remove DELETED-ITEMS, all
491 three being lists of JID symbols."
492 (let ((roster (plist-get (fsm-get-state-data jc
) :roster
))
493 (ewoc (plist-get (fsm-get-state-data jc
) :roster-ewoc
)))
494 (dolist (delete-this deleted-items
)
495 (setq roster
(delq delete-this roster
)))
496 (setq roster
(append new-items roster
))
497 (plist-put (fsm-get-state-data jc
) :roster roster
)
499 ;; If there is no ewoc yet, create the roster buffer.
501 (jabber-display-roster)
502 ;; Otherwise, do incremental changes.
504 ;; The changed items need to be resorted, so we start by removing
507 (lambda (a) (not (or (member a changed-items
)
508 (member a deleted-items
)))))
510 ;; Now, insert items into ewoc.
511 (let* ((to-be-inserted
512 (sort (jabber-roster-filter-display
513 (append new-items changed-items
))
514 #'jabber-roster-sort-items
))
515 (where (ewoc-nth ewoc
0)))
516 (while to-be-inserted
518 ;; If we are at the end of the ewoc, put all elements there.
520 (dolist (a to-be-inserted
)
521 (ewoc-enter-last ewoc a
))
522 (setq to-be-inserted nil
))
523 ;; If the next element should go here, put it here.
524 ((jabber-roster-sort-items (car to-be-inserted
)
526 (ewoc-enter-before ewoc where
527 (car to-be-inserted
))
528 (setq to-be-inserted
(cdr to-be-inserted
)))
529 ;; Else, advance through the ewoc.
531 (setq where
(ewoc-next ewoc where
)))))))))
533 (defalias 'jabber-presence-update-roster
'ignore
)
534 ;;jabber-presence-update-roster is not needed anymore.
535 ;;Its work is done in `jabber-process-presence'."
536 (make-obsolete 'jabber-presence-update-roster
'ignore
)
538 (defun jabber-go-to-next-jid ()
539 "Move the cursor to the next jid in the buffer"
541 (let ((next (next-single-property-change (point) 'jabber-jid
)))
543 (not (get-text-property next
'jabber-jid
)))
544 (setq next
(next-single-property-change next
'jabber-jid
)))
546 (setq next
(next-single-property-change (point-min) 'jabber-jid
)))
547 (if next
(goto-char (1+ next
))
548 (goto-char (point-min)))))
550 (defun jabber-go-to-previous-jid ()
551 "Move the cursor to the previous jid in the buffer"
553 (let ((previous (previous-single-property-change (point) 'jabber-jid
)))
555 (not (get-text-property previous
'jabber-jid
)))
556 (setq previous
(previous-single-property-change previous
'jabber-jid
)))
558 (setq previous
(previous-single-property-change (point-max) 'jabber-jid
)))
559 (if previous
(goto-char previous
)
560 (goto-char (point-max)))))
562 (provide 'jabber-roster
)
564 ;;; arch-tag: 096af063-0526-4dd2-90fd-bc6b5ba07d32