1 ;;; emchat-status.el --- Status code for EMchat
3 ;; Copyright (C) 2002 - 2011 Steve Youngs
5 ;; Author: Steve Youngs <steve@emchat.org>
6 ;; Maintainer: Steve Youngs <steve@emchat.org>
8 ;; Homepage: http://www.emchat.org/
11 ;; This file is part of EMchat.
13 ;; Redistribution and use in source and binary forms, with or without
14 ;; modification, are permitted provided that the following conditions
17 ;; 1. Redistributions of source code must retain the above copyright
18 ;; notice, this list of conditions and the following disclaimer.
20 ;; 2. Redistributions in binary form must reproduce the above copyright
21 ;; notice, this list of conditions and the following disclaimer in the
22 ;; documentation and/or other materials provided with the distribution.
24 ;; 3. Neither the name of the author nor the names of any contributors
25 ;; may be used to endorse or promote products derived from this
26 ;; software without specific prior written permission.
28 ;; THIS SOFTWARE IS PROVIDED BY THE AUTHOR "AS IS" AND ANY EXPRESS OR
29 ;; IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
30 ;; WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
31 ;; DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 ;; FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
33 ;; CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
34 ;; SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
35 ;; BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
36 ;; WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
37 ;; OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
38 ;; IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 (require 'emchat-world)
43 (require 'emchat-meta)
48 (defvar emchat-buddy-view)
49 (defvar emchat-wharf-frame-use-p))
51 (autoload 'emchat-buddy-update-face "emchat-buddy")
54 (defcustom emchat-buddy-status-color-hint-flag t
55 "*Non-nil means put status color hints."
60 (defcustom emchat-status-window-height 8
61 "*Height of window for `emchat-status-buffer'."
62 :group 'emchat-interface)
64 (defcustom emchat-status-use-gutter nil
65 "*When non-nil, display statuses in the gutter, not in a buffer."
67 :group 'emchat-interface)
69 (defcustom emchat-status-gutter-orientation 'top
70 "Where to display the status gutter.
72 Valid values are: top, bottom, left, right."
73 :type '(choice (item :tag "Top" :value top)
74 (item :tag "Bottom" :value bottom)
75 (item :tag "Left" :value left)
76 (item :tag "Right" :value right))
77 :group 'emchat-interface)
80 (defvar emchat-valid-statuses
81 '("online" "away" "occ" "dnd" "ffc" "na")
82 "All statuses valid for selection.
83 Used by `emchat-change-status' and in `emchat-buddy-buffer'.")
86 (defcustom emchat-user-initial-status "online"
87 "*Initial user status when login."
92 (lambda (x) (list 'item x))
93 emchat-valid-statuses)))
95 (defcustom emchat-status-update-hook nil
96 "*Hooks to run when a buddy change his status.
97 Dynamically ALIAS and STATUS are binded to be used in hooks."
101 (defface emchat-face-online
102 '((((background dark))
103 (:foreground "green"))
104 (((background light))
105 (:foreground "green4")))
106 "Face for ONLINE status."
107 :group 'emchat-buddy)
109 (defface emchat-face-away
110 '((((background dark))
112 (((background light))
113 (:foreground "red4")))
114 "Face for AWAY status."
115 :group 'emchat-buddy)
117 (defface emchat-face-occ
118 '((((background dark))
119 (:foreground "orange"))
120 (((background light))
121 (:foreground "orange4")))
122 "Face for OCCUPIED status."
123 :group 'emchat-buddy)
125 (defface emchat-face-dnd
126 '((((background dark))
127 (:foreground "lightblue"))
128 (((background light))
129 (:foreground "blue")))
130 "Face for DO NOT DISTURB status."
131 :group 'emchat-buddy)
133 (defface emchat-face-ffc
134 '((((background dark))
135 (:foreground "yellow"))
136 (((background light))
137 (:foreground "yellow4")))
138 "Face for FREE FOR CHAT status."
139 :group 'emchat-buddy)
141 (defface emchat-face-na
142 '((((background dark))
143 (:foreground "pink"))
144 (((background light))
145 (:foreground "deeppink")))
146 "Face for NOT AVAILABLE status."
147 :group 'emchat-buddy)
149 (defface emchat-face-invisible
150 '((((background dark))
151 (:foreground "grey"))
152 (((background light))
153 (:foreground "grey40")))
154 "Face for OFFLINE status."
155 :group 'emchat-buddy)
157 ;;; Internal variables
159 (defvar emchat-statuses
160 ;; basically status is only ONE byte (except for invisible?)
161 ;; byte after status byte is random
162 '((online "online" emchat-face-online)
163 (away "away" emchat-face-away emchat-auto-reply-away)
164 (na "na" emchat-face-na emchat-auto-reply-na)
165 (occupied "occ" emchat-face-occ emchat-auto-reply-occ)
166 (dnd "dnd" emchat-face-dnd emchat-auto-reply-dnd)
167 (ffc "ffc" emchat-face-ffc)
168 (offline "offline" nil)
169 (invisible "invisible" emchat-face-invisible))
170 "Status info: v8 status, text code, face, auto-reply.")
172 (defun emchat-status-face (name)
173 "Return the face of status from its NAME."
175 (member* name emchat-statuses
179 (defun emchat-status-v8 (name)
180 "Return the symbol for status NAME."
182 (member* name emchat-statuses
186 (defun emchat-status-auto-reply (name)
187 "Return the symbol of auto-reply of status from its NAME."
189 (member* name emchat-statuses
193 (defun emchat-status-idle-reply (name)
194 "Return the symbol of idle-reply of status from its NAME."
195 (let ((sym (emchat-status-auto-reply name)))
197 (insert (symbol-name sym))
198 (while (search-backward "auto" nil t)
199 (replace-match "idle" nil t))
200 (intern (buffer-string)))))
202 (defun emchat-status-name (proto-status)
203 "Return the name of status from its the binary string BIN."
204 (cadr (assoc proto-status emchat-statuses)))
206 (defun emchat-buddy-update-status (alias status)
207 "Update ALIAS with new STATUS."
208 ;; update alias variables
209 (unless (member status (mapcar 'second emchat-statuses))
210 (push (cons 'unknown-status emchat-recent-packet)
211 emchat-error-packets)
212 (emchat-log-error "Unknown status: %s" status)
213 (setq status "online")) ; assumed online
216 (when (and (equal alias emchat-user-alias)
217 (string= status "invisible"))
218 (setq status emchat-user-status))
220 (unless (emchat-world-getf alias 'status)
221 (emchat-world-putf alias 'status "offline"))
223 (unless (equal status (emchat-world-getf alias 'status))
224 (emchat-world-putf alias 'status status)
225 (emchat-log-buddy-status alias "***| %s" status)
226 (when (string= status "online")
227 (emchat-play-sound-maybe 'buddy-sound))
228 (if (string= status "offline")
229 (if (member alias emchat-connected-aliases)
230 (setq emchat-connected-aliases
231 (delete alias emchat-connected-aliases))
232 (emchat-log-buddy-status alias "***| has been invisible"))
234 (add-to-list 'emchat-connected-aliases alias))
238 ;; view != all + offline -> delete
239 ;; view = all + offline -> offline-face
240 (if (and (string= status "offline")
241 (not (eq emchat-buddy-view 'emchat-all-aliases)))
242 (emchat-buddy-update-face alias 'delete)
243 (if (or (member alias (symbol-value emchat-buddy-view))
244 (string= status "offline"))
245 (emchat-buddy-update-face alias)))))
248 (defvar emchat-user-status "offline"
249 "Current user status.")
251 (defun emchat-do-status-update (ectx uin status)
252 "Handle server command 01a4 in PACKET."
253 (let ((alias (emchat-uin-alias (emchat-stringular-uin uin)))
254 (status (emchat-status-name status)))
255 (emchat-buddy-update-status alias status)
256 (run-hooks 'emchat-status-update-hook)))
258 (defun emchat-turn-on-invisibility ()
259 (emchat-v8-snac-cli-setstatus
260 emchat-ctx (append (list (emchat-status-v8 emchat-user-status))
261 (and emchat-user-meta-web-aware '(web-aware))
263 (emchat-log-buddy-status emchat-user-alias "***| %s (invisible)" emchat-user-status)
264 (setq emchat-user-meta-invisible t))
266 (defun emchat-turn-off-invisibility ()
267 (emchat-v8-snac-cli-setstatus
268 emchat-ctx (append (list (emchat-status-v8 emchat-user-status))
269 (and emchat-user-meta-web-aware '(web-aware))))
270 (emchat-log-buddy-status emchat-user-alias "***| %s (visible)" emchat-user-status)
271 (setq emchat-user-meta-invisible nil))
273 (defun emchat-toggle-invisibility ()
274 "Toggle \"invisible\" status."
276 (setq emchat-user-meta-invisible (null emchat-user-meta-invisible))
277 (if emchat-user-meta-invisible
278 (emchat-turn-on-invisibility)
279 (emchat-turn-off-invisibility))
280 (with-current-buffer emchat-log-buffer
281 (emchat-log-update-modeline)))
283 (defun emchat-change-status (status &optional no-network)
284 "Change to new STATUS.
285 Non-nil NO-NETWORK means not to send any network packet, only update
286 variable and modeline."
288 (list (emchat-completing-read "status: " emchat-valid-statuses nil t)))
289 (unless (equal status emchat-user-status)
290 (when emchat-user-auto-away-p
291 (setq emchat-user-auto-away-p nil))
292 (emchat-log-system "Changed status to %s" status)
293 (when (equal status "online")
294 (setq emchat-auto-reply-never emchat-auto-response-never-send-to)
295 (loop for alias in emchat-online-notifiers
296 do (emchat-v8-send-simple-message
297 emchat-ctx (emchat-numeric-uin (emchat-alias-uin alias))
299 You won't be notified again unless you re-request
300 it with \",,notify-me\".")
301 do (emchat-log-system (format "Online notification sent to: %s" alias)))
302 (setq emchat-online-notifiers nil))
303 (setq emchat-user-status status)
304 (when emchat-wharf-frame-use-p
305 (declare-fboundp (emchat-wharf-update-status)))
306 (redraw-modeline 'all)
308 (emchat-v8-snac-cli-setstatus
309 emchat-ctx (append (list (emchat-status-v8 status))
310 (and emchat-user-meta-web-aware '(web-aware))
311 (and emchat-user-meta-invisible '(invisible)))))))
314 (defvar emchat-status-buffer nil
315 "Buffer for statuses.")
318 (defun emchat-status-show-buffer (&optional new no-select)
319 "Switch to `emchat-status-buffer'.
320 Create buffer if buffer does not exists already or
322 Don't select status window if NO-SELECT is non-nil."
324 (when (or (not (buffer-live-p emchat-status-buffer))
326 (setq emchat-status-buffer (get-buffer-create "*Status*"))
327 (set-buffer emchat-status-buffer)
328 (set-specifier horizontal-scrollbar-visible-p nil
329 (cons (current-buffer) nil))
330 (set-specifier vertical-scrollbar-visible-p nil
331 (cons (current-buffer) nil))
332 (set-specifier has-modeline-p nil
333 (cons (current-buffer) nil))
335 (set (make-local-variable 'widget-button-face) 'emchat-face-online)
337 :help-echo "Change status to \"Online\""
338 :action (lambda (&rest ignore)
339 (emchat-change-status "online"))
342 (set (make-local-variable 'widget-button-face) 'emchat-face-away)
344 :help-echo "Change status to \"Away\""
345 :action (lambda (&rest ignore)
346 (emchat-change-status "away"))
349 (set (make-local-variable 'widget-button-face) 'emchat-face-occ)
351 :help-echo "Change status to \"Occupied\""
352 :action (lambda (&rest ignore)
353 (emchat-change-status "occ"))
356 (set (make-local-variable 'widget-button-face) 'emchat-face-dnd)
358 :help-echo "Change status to \"Do Not Disturb\""
359 :action (lambda (&rest ignore)
360 (emchat-change-status "dnd"))
363 (set (make-local-variable 'widget-button-face) 'emchat-face-na)
365 :help-echo "Change status to \"Not Available\""
366 :action (lambda (&rest ignore)
367 (emchat-change-status "na"))
370 (set (make-local-variable 'widget-button-face) 'emchat-face-ffc)
372 :help-echo "Change status to \"Free For Chat\""
373 :action (lambda (&rest ignore)
374 (emchat-change-status "ffc"))
377 (set (make-local-variable 'widget-button-face) 'emchat-face-invisible)
379 :help-echo "Toggle your visibility"
380 :action (lambda (&rest ignore)
381 (emchat-toggle-invisibility))
385 (switch-to-buffer emchat-status-buffer))))
388 ;;; WARNING: This is experimental and unfinished.
389 (defvar emchat-status-gutter-tab nil
390 "A tab widget in the gutter for switching online statuses.
391 Do not set this. Use `set-glyph-image' to change the properties of
394 (defun emchat-status-maybe-login ()
395 "Convenience function for the status gutter.
396 If a connection to the ICQ server exists, just change status to
397 online, otherwise login."
398 (if (emchat-connected-p emchat-ctx)
399 (emchat-change-status "online")
402 (defvar emchat-status-tabs
403 '(["Online" (emchat-status-maybe-login)
404 :selected (equal emchat-user-status "online")]
405 ["Away" (emchat-change-status "away")
406 :selected (equal emchat-user-status "away")]
407 ["Occupied" (emchat-change-status "occ")
408 :selected (equal emchat-user-status "occ")]
409 ["Do Not Disturb" (emchat-change-status "dnd")
410 :selected (equal emchat-user-status "dnd")]
411 ["Not Available" (emchat-change-status "na")
412 :selected (equal emchat-user-status "na")]
413 ["FFC" (emchat-change-status "ffc")
414 :selected (equal emchat-user-status "ffc")]
415 ["Inv on/off" (emchat-toggle-invisibility) :selected nil]
416 ["Offline" (emchat-logout)
417 :selected (equal emchat-user-status "offline")])
418 "Buttons for the EMchat status gutter.")
420 ;;; FIXME: Left and right gutters are broken, also, I don't like
421 ;;; setting `default-gutter-position' to change the orientation
422 ;;; although the docs seem to suggest that this is the only way to
424 ;; ,----[ C-h v right-gutter RET ]
425 ;; | `right-gutter' is a built-in constant specifier variable.
427 ;; | Value: #<gutter-specifier global=<unspecified> fallback=((nil)) 0x145>
430 ;; | Specifier for the gutter at the right edge of the frame.
431 ;; | Use `set-specifier' to change this.
432 ;; | See `default-gutter' for a description of a valid gutter instantiator.
434 ;; | Note that, unless the `default-gutter-position' is `right', by
435 ;; | default the height of the right gutter (controlled by
436 ;; | `right-gutter-width') is 0; thus, a right gutter will not be
437 ;; | displayed even if you provide a value for `right-gutter'.
440 (defun emchat-add-tab-to-gutter ()
441 (let* ((gutter-string (copy-sequence "\n"))
442 (status-gutter-extent (make-extent 0 1 gutter-string)))
443 (set-extent-begin-glyph status-gutter-extent
444 (setq emchat-status-gutter-tab
448 (remove-gutter-element top-gutter 'status-tab emchat-frame x)
449 (remove-gutter-element bottom-gutter 'status-tab emchat-frame x)
450 (remove-gutter-element left-gutter 'status-tab emchat-frame x)
451 (remove-gutter-element right-gutter 'status-tab emchat-frame x))
455 (when (valid-image-instantiator-format-p 'tab-control x)
456 (cond ((eq emchat-status-gutter-orientation 'top)
457 (set-default-gutter-position 'top)
458 (set-specifier top-gutter-visible-p t emchat-frame x)
459 (set-specifier top-gutter-border-width 0 emchat-frame x)
460 (set-gutter-element top-gutter 'status-tab
461 gutter-string emchat-frame x))
462 ((eq emchat-status-gutter-orientation 'bottom)
463 (set-default-gutter-position 'bottom)
464 (set-specifier bottom-gutter-visible-p t emchat-frame x)
465 (set-specifier bottom-gutter-border-width 0 emchat-frame x)
466 (set-gutter-element bottom-gutter 'status-tab
467 gutter-string emchat-frame x))
468 ((eq emchat-status-gutter-orientation 'left)
469 (set-default-gutter-position 'left)
470 (set-specifier left-gutter-visible-p t emchat-frame x)
471 (set-specifier left-gutter-border-width 0 emchat-frame x)
472 (set-gutter-element left-gutter 'status-tab
473 gutter-string emchat-frame x))
474 ((eq emchat-status-gutter-orientation 'right)
475 (set-default-gutter-position 'right)
476 (set-specifier right-gutter-visible-p t emchat-frame x)
477 (set-specifier right-gutter-border-width 0 emchat-frame x)
478 (set-gutter-element right-gutter 'status-tab
479 gutter-string emchat-frame x)))))
480 (console-type-list))))
482 ;;; FIXME: When the gutter code in (S)XEmacs can put different faces
483 ;;; on different buttons update this so that the status tabs have the
484 ;;; right faces... emchat-face-{online,away,na,occ,dnd,ffc,invisible}.
485 (defun emchat-update-tab-in-gutter ()
486 "Update the tab control in the gutter area."
487 (unless (or (window-dedicated-p (frame-selected-window emchat-frame))
488 (frame-property emchat-frame 'popup))
489 (emchat-add-tab-to-gutter)
490 (when (valid-image-instantiator-format-p 'tab-control emchat-frame)
492 emchat-status-gutter-tab
493 (vector 'tab-control :descriptor "Status"
495 :orientation emchat-status-gutter-orientation
496 (if (or (eq emchat-status-gutter-orientation 'top)
497 (eq emchat-status-gutter-orientation 'bottom))
498 :pixel-width :pixel-height)
499 (if (or (eq emchat-status-gutter-orientation 'top)
500 (eq emchat-status-gutter-orientation 'bottom))
501 '(gutter-pixel-width) '(gutter-pixel-height))
502 :items (eval 'emchat-status-tabs))
504 ;; set-glyph-image will not make the gutter dirty
505 (set-gutter-dirty-p emchat-status-gutter-orientation))))
507 (provide 'emchat-status)
509 ;;; emchat-status.el ends here