1 ;;; erc-nicklist.el --- Display channel nicknames in a side buffer.
3 ;; Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc.
5 ;; Filename: erc-nicklist.el
6 ;; Author: Lawrence Mitchell <wence@gmx.li>
8 ;; Keywords: IRC chat client Internet
10 ;; This file is part of GNU Emacs.
12 ;; GNU Emacs is free software; you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation; either version 2, or (at your option)
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs; see the file COPYING. If not, write to the
24 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
25 ;; Boston, MA 02110-1301, USA.
29 ;; This provides a minimal mIRC style nicklist buffer for ERC. To
30 ;; activate, do M-x erc-nicklist RET in the channel buffer you want
31 ;; the nicklist to appear for. To close and quit the nicklist
32 ;; buffer, do M-x erc-nicklist-quit RET.
35 ;; o Somehow associate nicklist windows with channel windows so they
36 ;; appear together, and if one gets buried, then the other does.
38 ;; o Make "Query" and "Message" work.
40 ;; o Prettify the actual list of nicks in some way.
42 ;; o Add a proper erc-module that people can turn on and off, figure
43 ;; out a way of creating the nicklist window at an appropriate time
44 ;; --- probably in `erc-join-hook'.
46 ;; o Ensure XEmacs compatibility --- the mouse-menu support is likely
49 ;; o Add option to display in a separate frame --- will again need to
50 ;; be able to associate the nicklist with the currently active
51 ;; channel buffer or something similar.
53 ;; o Allow toggling of visibility of nicklist via ERC commands.
58 ;; Changes by Edgar Gonçalves <edgar.goncalves@inesc-id.pt>
60 ;; - images are changed to a standard set of names.
61 ;; - /images now contain gaim's status icons.
63 ;; - tooltips are improved. they try to access bbdb for a nice nick!
65 ;; - erc-nicklist-channel-users-info was fixed (sorting bug)
66 ;; - Away names don't need parenthesis when using icons
68 ;; - nicks can display icons of their connection type (msn, icq, for now)
70 ;; - nicks now are different for unvoiced and op users
71 ;; - nicks now have tooltips displaying more info
73 ;; - queries now work ok, both on menu and keyb shortcut RET.
74 ;; - nicklist is now sorted ignoring the case. Voiced nicks will
75 ;; appear according to `erc-nicklist-voiced-position'.
83 (eval-when-compile (require 'cl))
85 (defconst erc-nicklist-version "$Revision: 1.14.2.1 $"
86 "ERC Nicklist version.")
88 (defgroup erc-nicklist nil
89 "Display a list of nicknames in a separate window."
92 (defcustom erc-nicklist-use-icons t
93 "*If non-nil, display an icon instead of the name of the chat medium.
94 By \"chat medium\", we mean IRC, AOL, MSN, ICQ, etc."
98 (defcustom erc-nicklist-icons-directory
99 (concat default-directory "images/")
100 "*Directory of the PNG files for chat icons.
101 Icons are displayed if `erc-nicklist-use-icons' is non-nil."
105 (defcustom erc-nicklist-voiced-position 'bottom
106 "*Position of voiced nicks in the nicklist.
107 The value can be `top', `bottom' or nil (don't sort)."
110 (const :tag "Top" 'top)
111 (const :tag "Bottom" 'bottom)
112 (const :tag "Mixed" nil)))
114 (defcustom erc-nicklist-window-size 20.0
115 "*The size of the nicklist window.
117 This specifies a percentage of the channel window width.
119 A negative value means the nicklist window appears on the left of the
120 channel window, and vice versa."
125 (defun erc-nicklist-buffer-name (&optional buffer)
126 "Return the buffer name for a nicklist associated with BUFFER.
128 If BUFFER is nil, use the value of `current-buffer'."
129 (format " *%s-nicklist*" (buffer-name (or buffer (current-buffer)))))
131 (defun erc-nicklist-make-window ()
132 "Create an ERC nicklist window.
134 See also `erc-nicklist-window-size'."
135 (let ((width (floor (* (window-width) (/ erc-nicklist-window-size 100.0))))
136 (buffer (erc-nicklist-buffer-name))
138 (split-window-horizontally (- width))
139 (setq window (next-window))
140 (set-window-buffer window (get-buffer-create buffer))
141 (with-current-buffer buffer
142 (set-window-dedicated-p window t))))
145 (defvar erc-nicklist-images-alist '()
146 "Alist that maps a connection type to an icon.")
148 (defun erc-nicklist-insert-medium-name-or-icon (host channel is-away)
149 "Inserts an icon or a string identifying the current host type.
150 This is configured using `erc-nicklist-use-icons' and
151 `erc-nicklist-icons-directory'."
152 ;; identify the network (for bitlebee usage):
153 (let ((bitlbee-p (save-match-data
154 (string-match "\\`&bitlbee\\b"
155 (buffer-name channel)))))
156 (cond ((and bitlbee-p
157 (string= "login.icq.com" host))
158 (if erc-nicklist-use-icons
160 (insert-image (cdr (assoc 'icq-away
161 erc-nicklist-images-alist)))
162 (insert-image (cdr (assoc 'icq
163 erc-nicklist-images-alist))))
166 (if erc-nicklist-use-icons
168 (insert-image (cdr (assoc 'msn-away
169 erc-nicklist-images-alist)))
170 (insert-image (cdr (assoc 'msn
171 erc-nicklist-images-alist))))
174 (if erc-nicklist-use-icons
176 (insert-image (cdr (assoc 'irc-away
177 erc-nicklist-images-alist)))
178 (insert-image (cdr (assoc 'irc
179 erc-nicklist-images-alist))))
183 (defun erc-nicklist-search-for-nick (finger-host)
184 "Return the bitlbee-nick field for this contact given FINGER-HOST.
185 Seach for the BBDB record of this contact. If not found, return nil."
186 (when (boundp 'erc-bbdb-bitlbee-name-field)
190 (let ((fingers (bbdb-record-finger-host r)))
192 (string-match finger-host
193 (car (bbdb-record-finger-host r))))))
196 (bbdb-get-field record erc-bbdb-bitlbee-name-field)))))
198 (defun erc-nicklist-insert-contents (channel)
199 "Insert the nicklist contents, with text properties and the optional images."
200 (setq buffer-read-only nil)
202 (dolist (u (erc-nicklist-channel-users-info channel))
203 (let* ((server-user (car u))
204 (channel-user (cdr u))
205 (nick (erc-server-user-nickname server-user))
206 (host (erc-server-user-host server-user))
207 (login (erc-server-user-login server-user))
208 (full-name(erc-server-user-full-name server-user))
209 (info (erc-server-user-info server-user))
210 (channels (erc-server-user-buffers server-user))
211 (op (erc-channel-user-op channel-user))
212 (voice (erc-channel-user-voice channel-user))
213 (bbdb-nick (erc-nicklist-search-for-nick (concat login "@" host)))
214 (away-status (if voice "" "\n(Away)"))
215 (balloon-text (concat bbdb-nick (if (string= "" bbdb-nick)
217 "Login: " login "@" host
219 (erc-nicklist-insert-medium-name-or-icon host channel (not voice))
220 (unless (or voice erc-nicklist-use-icons)
221 (setq nick (concat "(" nick ")")))
223 (setq nick (concat nick " (OP)")))
224 (insert (erc-propertize nick
225 'erc-nicklist-nick nick
226 'mouse-face 'highlight
227 'erc-nicklist-channel channel
228 'help-echo balloon-text)
233 (defun erc-nicklist ()
234 "Create an ERC nicklist buffer."
236 (let ((channel (current-buffer)))
237 (unless (or (not erc-nicklist-use-icons)
238 erc-nicklist-images-alist)
239 (setq erc-nicklist-images-alist
240 `((msn . ,(create-image (concat erc-nicklist-icons-directory
242 (msn-away . ,(create-image (concat erc-nicklist-icons-directory
244 (irc . ,(create-image (concat erc-nicklist-icons-directory
246 (irc-away . ,(create-image (concat erc-nicklist-icons-directory
248 (icq . ,(create-image (concat erc-nicklist-icons-directory
250 (icq-away . ,(create-image (concat erc-nicklist-icons-directory
251 "icq-offline.png"))))))
252 (erc-nicklist-make-window)
253 (with-current-buffer (get-buffer (erc-nicklist-buffer-name channel))
254 (erc-nicklist-insert-contents channel)))
255 (add-hook 'erc-channel-members-changed-hook #'erc-nicklist-update))
257 (defun erc-nicklist-update ()
258 "Update the ERC nicklist buffer."
259 (let ((b (get-buffer (erc-nicklist-buffer-name)))
260 (channel (current-buffer)))
262 (with-current-buffer b
263 (erc-nicklist-insert-contents channel)))))
265 (defvar erc-nicklist-mode-map
266 (let ((map (make-sparse-keymap)))
267 (define-key map (kbd "<down-mouse-3>") 'erc-nicklist-menu)
268 (define-key map "\C-j" 'erc-nicklist-kbd-menu)
269 (define-key map "q" 'erc-nicklist-quit)
270 (define-key map (kbd "RET") 'erc-nicklist-kbd-cmd-QUERY)
272 "Keymap for `erc-nicklist-mode'.")
274 (define-derived-mode erc-nicklist-mode fundamental-mode
276 "Major mode for the ERC nicklist buffer."
277 (setq buffer-read-only t))
279 (defun erc-nicklist-call-erc-command (command point buffer window)
280 "Call an ERC COMMAND.
282 Depending on what COMMAND is, it's called with one of POINT, BUFFER,
283 or WINDOW as arguments."
285 (let* ((p (text-properties-at point))
286 (b (plist-get p 'erc-nicklist-channel)))
287 (if (memq command '(erc-nicklist-quit ignore))
288 (funcall command window)
289 ;; EEEK! Horrble, but it's the only way we can ensure the
290 ;; response goes to the correct buffer.
291 (erc-set-active-buffer b)
292 (switch-to-buffer-other-window b)
293 (funcall command (plist-get p 'erc-nicklist-nick))))))
295 (defun erc-nicklist-cmd-QUERY (user &optional server)
296 "Opens a query buffer with USER."
297 ;; FIXME: find a way to switch to that buffer afterwards...
298 (let ((send (if server
299 (format "QUERY %s %s" user server)
300 (format "QUERY %s" user))))
304 (defun erc-nicklist-kbd-cmd-QUERY (&optional window)
306 (let* ((p (text-properties-at (point)))
307 (server (plist-get p 'erc-nicklist-channel))
308 (nick (plist-get p 'erc-nicklist-nick))
309 (nick (or (and (string-match "(\\(.*\\))" nick)
310 (match-string 1 nick))
312 (nick (or (and (string-match "\\+\\(.*\\)" nick)
313 (match-string 1 nick))
315 (send (format "QUERY %s %s" nick server)))
316 (switch-to-buffer-other-window server)
317 (erc-cmd-QUERY nick)))
320 (defvar erc-nicklist-menu
321 (let ((map (make-sparse-keymap "Action")))
322 (define-key map [erc-cmd-WHOIS]
323 '("Whois" . erc-cmd-WHOIS))
324 (define-key map [erc-cmd-DEOP]
325 '("Deop" . erc-cmd-DEOP))
326 (define-key map [erc-cmd-MSG]
327 '("Message" . erc-cmd-MSG)) ;; TODO!
328 (define-key map [erc-nicklist-cmd-QUERY]
329 '("Query" . erc-nicklist-kbd-cmd-QUERY))
330 (define-key map [ignore]
331 '("Cancel" . ignore))
332 (define-key map [erc-nicklist-quit]
333 '("Close nicklist" . erc-nicklist-quit))
335 "Menu keymap for the ERC nicklist.")
337 (defun erc-nicklist-quit (&optional window)
338 "Delete the ERC nicklist.
340 Deletes WINDOW and stops updating the nicklist buffer."
342 (let ((b (window-buffer window)))
343 (with-current-buffer b
344 (set-buffer-modified-p nil)
346 (remove-hook 'erc-channel-members-changed-hook 'erc-nicklist-update))))
349 (defun erc-nicklist-kbd-menu ()
350 "Show the ERC nicklist menu."
352 (let* ((point (point))
353 (window (selected-window))
354 (buffer (current-buffer)))
355 (with-current-buffer buffer
356 (erc-nicklist-call-erc-command
357 (car (x-popup-menu point
363 (defun erc-nicklist-menu (&optional arg)
364 "Show the ERC nicklist menu.
366 ARG is a parametrized event (see `interactive')."
368 (let* ((point (nth 1 (cadr arg)))
369 (window (car (cadr arg)))
370 (buffer (window-buffer window)))
371 (with-current-buffer buffer
372 (erc-nicklist-call-erc-command
373 (car (x-popup-menu arg
380 (defun erc-nicklist-channel-users-info (channel)
381 "Return a nick-sorted list of all users on CHANNEL.
382 Result are elements in the form (SERVER-USER . CHANNEL-USER). The
383 list has all the voiced users according to
384 `erc-nicklist-voiced-position'."
385 (let* ((nicks (erc-sort-channel-users-alphabetically
386 (with-current-buffer channel (erc-get-channel-user-list)))))
387 (if erc-nicklist-voiced-position
388 (let ((voiced-nicks (erc-remove-if-not
390 (null (erc-channel-user-voice (cdr x))))
392 (devoiced-nicks (erc-remove-if-not
394 (erc-channel-user-voice
397 (cond ((eq erc-nicklist-voiced-position 'top)
398 (append devoiced-nicks voiced-nicks))
399 ((eq erc-nicklist-voiced-position 'bottom)
400 (append voiced-nicks devoiced-nicks))))
405 (provide 'erc-nicklist)
407 ;;; erc-nicklist.el ends here
410 ;; indent-tabs-mode: t
414 ;; arch-tag: db37a256-87a7-4544-bd90-e5f16c9f5ca5