e17f2a8a3177e01076c7d8e2a106c507b8a7c30b
[gnus] / lisp / gnus-win.el
1 ;;; gnus-win.el --- window configuration functions for Gnus
2 ;; Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2002, 2003
3 ;;        Free Software Foundation, Inc.
4
5 ;; Author: Lars Magne Ingebrigtsen <larsi@gnus.org>
6 ;; Keywords: news
7
8 ;; This file is part of GNU Emacs.
9
10 ;; GNU Emacs is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
14
15 ;; GNU Emacs is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18 ;; GNU General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Commentary:
26
27 ;;; Code:
28
29 (eval-when-compile (require 'cl))
30
31 (require 'gnus)
32 (require 'gnus-util)
33
34 (defgroup gnus-windows nil
35   "Window configuration."
36   :group 'gnus)
37
38 (defcustom gnus-use-full-window t
39   "*If non-nil, use the entire Emacs screen."
40   :group 'gnus-windows
41   :type 'boolean)
42
43 (defvar gnus-window-configuration nil
44   "Obsolete variable.  See `gnus-buffer-configuration'.")
45
46 (defcustom gnus-window-min-width 2
47   "*Minimum width of Gnus buffers."
48   :group 'gnus-windows
49   :type 'integer)
50
51 (defcustom gnus-window-min-height 1
52   "*Minimum height of Gnus buffers."
53   :group 'gnus-windows
54   :type 'integer)
55
56 (defcustom gnus-always-force-window-configuration nil
57   "*If non-nil, always force the Gnus window configurations."
58   :group 'gnus-windows
59   :type 'boolean)
60
61 (defcustom gnus-use-frames-on-any-display nil
62   "*If non-nil, frames on all displays will be considered useable by Gnus.
63 When nil, only frames on the same display as the selected frame will be
64 used to display Gnus windows."
65   :group 'gnus-windows
66   :type 'boolean)
67
68 (defvar gnus-buffer-configuration
69   '((group
70      (vertical 1.0
71                (group 1.0 point)
72                (if gnus-carpal '(group-carpal 4))))
73     (summary
74      (vertical 1.0
75                (summary 1.0 point)
76                (if gnus-carpal '(summary-carpal 4))))
77     (article
78      (cond
79       (gnus-use-trees
80        '(vertical 1.0
81                   (summary 0.25 point)
82                   (tree 0.25)
83                   (article 1.0)))
84       (t
85        '(vertical 1.0
86                   (summary 0.25 point)
87                   (if gnus-carpal '(summary-carpal 4))
88                   (article 1.0)))))
89     (server
90      (vertical 1.0
91                (server 1.0 point)
92                (if gnus-carpal '(server-carpal 2))))
93     (browse
94      (vertical 1.0
95                (browse 1.0 point)
96                (if gnus-carpal '(browse-carpal 2))))
97     (message
98      (vertical 1.0
99                (message 1.0 point)))
100     (pick
101      (vertical 1.0
102                (article 1.0 point)))
103     (info
104      (vertical 1.0
105                (info 1.0 point)))
106     (summary-faq
107      (vertical 1.0
108                (summary 0.25)
109                (faq 1.0 point)))
110     (edit-article
111      (vertical 1.0
112                (article 1.0 point)))
113     (edit-form
114      (vertical 1.0
115                (group 0.5)
116                (edit-form 1.0 point)))
117     (edit-score
118      (vertical 1.0
119                (summary 0.25)
120                (edit-score 1.0 point)))
121     (post
122      (vertical 1.0
123                (post 1.0 point)))
124     (reply
125      (vertical 1.0
126                (article 0.5)
127                (message 1.0 point)))
128     (forward
129      (vertical 1.0
130                (message 1.0 point)))
131     (reply-yank
132      (vertical 1.0
133                (message 1.0 point)))
134     (mail-bounce
135      (vertical 1.0
136                (article 0.5)
137                (message 1.0 point)))
138     (pipe
139      (vertical 1.0
140                (summary 0.25 point)
141                (if gnus-carpal '(summary-carpal 4))
142                ("*Shell Command Output*" 1.0)))
143     (bug
144      (vertical 1.0
145                (if gnus-bug-create-help-buffer '("*Gnus Help Bug*" 0.5))
146                ("*Gnus Bug*" 1.0 point)))
147     (score-trace
148      (vertical 1.0
149                (summary 0.5 point)
150                ("*Score Trace*" 1.0)))
151     (score-words
152      (vertical 1.0
153                (summary 0.5 point)
154                ("*Score Words*" 1.0)))
155     (split-trace
156      (vertical 1.0
157                (summary 0.5 point)
158                ("*Split Trace*" 1.0)))
159     (category
160      (vertical 1.0
161                (category 1.0)))
162     (compose-bounce
163      (vertical 1.0
164                (article 0.5)
165                (message 1.0 point)))
166     (display-term
167       (vertical 1.0
168                 ("*display*" 1.0))))
169   "Window configuration for all possible Gnus buffers.
170 See the Gnus manual for an explanation of the syntax used.")
171
172 (defvar gnus-window-to-buffer
173   '((group . gnus-group-buffer)
174     (summary . gnus-summary-buffer)
175     (article . gnus-article-buffer)
176     (server . gnus-server-buffer)
177     (browse . "*Gnus Browse Server*")
178     (edit-group . gnus-group-edit-buffer)
179     (edit-form . gnus-edit-form-buffer)
180     (edit-server . gnus-server-edit-buffer)
181     (group-carpal . gnus-carpal-group-buffer)
182     (summary-carpal . gnus-carpal-summary-buffer)
183     (server-carpal . gnus-carpal-server-buffer)
184     (browse-carpal . gnus-carpal-browse-buffer)
185     (edit-score . gnus-score-edit-buffer)
186     (message . gnus-message-buffer)
187     (mail . gnus-message-buffer)
188     (post-news . gnus-message-buffer)
189     (faq . gnus-faq-buffer)
190     (tree . gnus-tree-buffer)
191     (score-trace . "*Score Trace*")
192     (split-trace . "*Split Trace*")
193     (info . gnus-info-buffer)
194     (category . gnus-category-buffer)
195     (article-copy . gnus-article-copy)
196     (draft . gnus-draft-buffer))
197   "Mapping from short symbols to buffer names or buffer variables.")
198
199 (defcustom gnus-configure-windows-hook nil
200   "*A hook called when configuring windows."
201   :group 'gnus-windows
202   :type 'hook)
203
204 ;;; Internal variables.
205
206 (defvar gnus-current-window-configuration nil
207   "The most recently set window configuration.")
208
209 (defvar gnus-created-frames nil)
210 (defvar gnus-window-frame-focus nil)
211
212 (defun gnus-kill-gnus-frames ()
213   "Kill all frames Gnus has created."
214   (while gnus-created-frames
215     (when (frame-live-p (car gnus-created-frames))
216       ;; We slap a condition-case around this `delete-frame' to ensure
217       ;; against errors if we try do delete the single frame that's left.
218       (ignore-errors
219         (delete-frame (car gnus-created-frames))))
220     (pop gnus-created-frames)))
221
222 (defun gnus-window-configuration-element (list)
223   (while (and list
224               (not (assq (car list) gnus-window-configuration)))
225     (pop list))
226   (cadr (assq (car list) gnus-window-configuration)))
227
228 (defun gnus-windows-old-to-new (setting)
229   ;; First we take care of the really, really old Gnus 3 actions.
230   (when (symbolp setting)
231     (setq setting
232           ;; Take care of ooold GNUS 3.x values.
233           (cond ((eq setting 'SelectArticle) 'article)
234                 ((memq setting '(SelectNewsgroup SelectSubject ExpandSubject))
235                  'summary)
236                 ((memq setting '(ExitNewsgroup)) 'group)
237                 (t setting))))
238   (if (or (listp setting)
239           (not (and gnus-window-configuration
240                     (memq setting '(group summary article)))))
241       setting
242     (let* ((elem
243             (cond
244              ((eq setting 'group)
245               (gnus-window-configuration-element
246                '(group newsgroups ExitNewsgroup)))
247              ((eq setting 'summary)
248               (gnus-window-configuration-element
249                '(summary SelectNewsgroup SelectSubject ExpandSubject)))
250              ((eq setting 'article)
251               (gnus-window-configuration-element
252                '(article SelectArticle)))))
253            (total (apply '+ elem))
254            (types '(group summary article))
255            (pbuf (if (eq setting 'newsgroups) 'group 'summary))
256            (i 0)
257            perc out)
258       (while (< i 3)
259         (or (not (numberp (nth i elem)))
260             (zerop (nth i elem))
261             (progn
262               (setq perc (if (= i 2)
263                              1.0
264                            (/ (float (nth i elem)) total)))
265               (push (if (eq pbuf (nth i types))
266                         (list (nth i types) perc 'point)
267                       (list (nth i types) perc))
268                     out)))
269         (incf i))
270       `(vertical 1.0 ,@(nreverse out)))))
271
272 ;;;###autoload
273 (defun gnus-add-configuration (conf)
274   "Add the window configuration CONF to `gnus-buffer-configuration'."
275   (setq gnus-buffer-configuration
276         (cons conf (delq (assq (car conf) gnus-buffer-configuration)
277                          gnus-buffer-configuration))))
278
279 (defvar gnus-frame-list nil)
280
281 (defun gnus-window-to-buffer-helper (obj)
282   (cond ((not (symbolp obj))
283          obj)
284         ((boundp obj)
285          (symbol-value obj))
286         ((fboundp obj)
287          (funcall obj))
288         (t
289          nil)))
290
291 (defun gnus-configure-frame (split &optional window)
292   "Split WINDOW according to SPLIT."
293   (let ((current-window
294          (or (get-buffer-window (current-buffer)) (selected-window))))
295     (unless window
296       (setq window current-window))
297     (select-window window)
298     ;; This might be an old-style buffer config.
299     (when (vectorp split)
300       (setq split (append split nil)))
301     (when (or (consp (car split))
302               (vectorp (car split)))
303       (push 1.0 split)
304       (push 'vertical split))
305     ;; The SPLIT might be something that is to be evaled to
306     ;; return a new SPLIT.
307     (while (and (not (assq (car split) gnus-window-to-buffer))
308                 (functionp (car split)))
309       (setq split (eval split)))
310     (let* ((type (car split))
311            (subs (cddr split))
312            (len (if (eq type 'horizontal) (window-width) (window-height)))
313            (total 0)
314            (window-min-width (or gnus-window-min-width window-min-width))
315            (window-min-height (or gnus-window-min-height window-min-height))
316            s result new-win rest comp-subs size sub)
317       (cond
318        ;; Nothing to do here.
319        ((null split))
320        ;; Don't switch buffers.
321        ((null type)
322         (and (memq 'point split) window))
323        ;; This is a buffer to be selected.
324        ((not (memq type '(frame horizontal vertical)))
325         (let ((buffer (cond ((stringp type) type)
326                             (t (cdr (assq type gnus-window-to-buffer))))))
327           (unless buffer
328             (error "Invalid buffer type: %s" type))
329           (let ((buf (gnus-get-buffer-create
330                       (gnus-window-to-buffer-helper buffer))))
331             (if (eq buf (window-buffer (selected-window))) (set-buffer buf)
332               (switch-to-buffer buf)))
333           (when (memq 'frame-focus split)
334             (setq gnus-window-frame-focus window))
335           ;; We return the window if it has the `point' spec.
336           (and (memq 'point split) window)))
337        ;; This is a frame split.
338        ((eq type 'frame)
339         (unless gnus-frame-list
340           (setq gnus-frame-list (list (window-frame current-window))))
341         (let ((i 0)
342               params frame fresult)
343           (while (< i (length subs))
344             ;; Frame parameter is gotten from the sub-split.
345             (setq params (cadr (elt subs i)))
346             ;; It should be a list.
347             (unless (listp params)
348               (setq params nil))
349             ;; Create a new frame?
350             (unless (setq frame (elt gnus-frame-list i))
351               (nconc gnus-frame-list (list (setq frame (make-frame params))))
352               (push frame gnus-created-frames))
353             ;; Is the old frame still alive?
354             (unless (frame-live-p frame)
355               (setcar (nthcdr i gnus-frame-list)
356                       (setq frame (make-frame params))))
357             ;; Select the frame in question and do more splits there.
358             (select-frame frame)
359             (setq fresult (or (gnus-configure-frame (elt subs i)) fresult))
360             (incf i))
361           ;; Select the frame that has the selected buffer.
362           (when fresult
363             (select-frame (window-frame fresult)))))
364        ;; This is a normal split.
365        (t
366         (when (> (length subs) 0)
367           ;; First we have to compute the sizes of all new windows.
368           (while subs
369             (setq sub (append (pop subs) nil))
370             (while (and (not (assq (car sub) gnus-window-to-buffer))
371                         (functionp (car sub)))
372               (setq sub (eval sub)))
373             (when sub
374               (push sub comp-subs)
375               (setq size (cadar comp-subs))
376               (cond ((equal size 1.0)
377                      (setq rest (car comp-subs))
378                      (setq s 0))
379                     ((floatp size)
380                      (setq s (floor (* size len))))
381                     ((integerp size)
382                      (setq s size))
383                     (t
384                      (error "Invalid size: %s" size)))
385               ;; Try to make sure that we are inside the safe limits.
386               (cond ((zerop s))
387                     ((eq type 'horizontal)
388                      (setq s (max s window-min-width)))
389                     ((eq type 'vertical)
390                      (setq s (max s window-min-height))))
391               (setcar (cdar comp-subs) s)
392               (incf total s)))
393           ;; Take care of the "1.0" spec.
394           (if rest
395               (setcar (cdr rest) (- len total))
396             (error "No 1.0 specs in %s" split))
397           ;; The we do the actual splitting in a nice recursive
398           ;; fashion.
399           (setq comp-subs (nreverse comp-subs))
400           (while comp-subs
401             (if (null (cdr comp-subs))
402                 (setq new-win window)
403               (setq new-win
404                     (split-window window (cadar comp-subs)
405                                   (eq type 'horizontal))))
406             (setq result (or (gnus-configure-frame
407                               (car comp-subs) window)
408                              result))
409             (select-window new-win)
410             (setq window new-win)
411             (setq comp-subs (cdr comp-subs))))
412         ;; Return the proper window, if any.
413         (when result
414           (select-window result)))))))
415
416 (defvar gnus-frame-split-p nil)
417
418 (defun gnus-configure-windows (setting &optional force)
419   (if (window-configuration-p setting)
420       (set-window-configuration setting)
421     (setq gnus-current-window-configuration setting)
422     (setq force (or force gnus-always-force-window-configuration))
423     (setq setting (gnus-windows-old-to-new setting))
424     (let ((split (if (symbolp setting)
425                      (cadr (assq setting gnus-buffer-configuration))
426                    setting))
427           all-visible)
428
429       (setq gnus-frame-split-p nil)
430
431       (unless split
432         (error "No such setting in `gnus-buffer-configuration': %s" setting))
433
434       (if (and (setq all-visible (gnus-all-windows-visible-p split))
435                (not force))
436           ;; All the windows mentioned are already visible, so we just
437           ;; put point in the assigned buffer, and do not touch the
438           ;; winconf.
439           (select-window all-visible)
440
441         ;; Make sure "the other" buffer, nntp-server-buffer, is live.
442         (unless (gnus-buffer-live-p nntp-server-buffer)
443           (nnheader-init-server-buffer))
444
445         ;; Either remove all windows or just remove all Gnus windows.
446         (let ((frame (selected-frame)))
447           (unwind-protect
448               (if gnus-use-full-window
449                   ;; We want to remove all other windows.
450                   (if (not gnus-frame-split-p)
451                       ;; This is not a `frame' split, so we ignore the
452                       ;; other frames.
453                       (delete-other-windows)
454                   ;; This is a `frame' split, so we delete all windows
455                     ;; on all frames.
456                     (gnus-delete-windows-in-gnusey-frames))
457                 ;; Just remove some windows.
458                 (gnus-remove-some-windows)
459                 (if (featurep 'xemacs)
460                     (switch-to-buffer nntp-server-buffer)
461                   (set-buffer nntp-server-buffer)))
462             (select-frame frame)))
463
464         (let (gnus-window-frame-focus)
465           (if (featurep 'xemacs)
466               (switch-to-buffer nntp-server-buffer)
467             (set-buffer nntp-server-buffer))
468           (gnus-configure-frame split)
469           (run-hooks 'gnus-configure-windows-hook)
470           (when gnus-window-frame-focus
471             (select-frame (window-frame gnus-window-frame-focus))))))))
472
473 (defun gnus-delete-windows-in-gnusey-frames ()
474   "Do a `delete-other-windows' in all frames that have Gnus windows."
475   (let ((buffers (gnus-buffers)))
476     (mapcar
477      (lambda (frame)
478        (unless (eq (cdr (assq 'minibuffer
479                               (frame-parameters frame)))
480                    'only)
481          (select-frame frame)
482          (let (do-delete)
483            (walk-windows
484             (lambda (window)
485               (when (memq (window-buffer window) buffers)
486                 (setq do-delete t))))
487            (when do-delete
488              (delete-other-windows)))))
489      (frame-list))))
490
491 (defun gnus-all-windows-visible-p (split)
492   "Say whether all buffers in SPLIT are currently visible.
493 In particular, the value returned will be the window that
494 should have point."
495   (let ((stack (list split))
496         (all-visible t)
497         type buffer win buf)
498     (while (and (setq split (pop stack))
499                 all-visible)
500       ;; Be backwards compatible.
501       (when (vectorp split)
502         (setq split (append split nil)))
503       (when (or (consp (car split))
504                 (vectorp (car split)))
505         (push 1.0 split)
506         (push 'vertical split))
507       ;; The SPLIT might be something that is to be evaled to
508       ;; return a new SPLIT.
509       (while (and (not (assq (car split) gnus-window-to-buffer))
510                   (functionp (car split)))
511         (setq split (eval split)))
512
513       (setq type (elt split 0))
514       (cond
515        ;; Nothing here.
516        ((null split) t)
517        ;; A buffer.
518        ((not (memq type '(horizontal vertical frame)))
519         (setq buffer (cond ((stringp type) type)
520                            (t (cdr (assq type gnus-window-to-buffer)))))
521         (unless buffer
522           (error "Invalid buffer type: %s" type))
523         (if (and (setq buf (get-buffer (gnus-window-to-buffer-helper buffer)))
524                  (setq win (gnus-get-buffer-window buf t)))
525             (if (memq 'point split)
526                 (setq all-visible win))
527           (setq all-visible nil)))
528        (t
529         (when (eq type 'frame)
530           (setq gnus-frame-split-p t))
531         (setq stack (append (cddr split) stack)))))
532     (unless (eq all-visible t)
533       all-visible)))
534
535 (defun gnus-window-top-edge (&optional window)
536   (nth 1 (window-edges window)))
537
538 (defun gnus-remove-some-windows ()
539   (let ((buffers (gnus-buffers))
540         buf bufs lowest-buf lowest)
541     (save-excursion
542       ;; Remove windows on all known Gnus buffers.
543       (while (setq buf (pop buffers))
544         (when (get-buffer-window buf)
545           (push buf bufs)
546           (pop-to-buffer buf)
547           (when (or (not lowest)
548                     (< (gnus-window-top-edge) lowest))
549             (setq lowest (gnus-window-top-edge)
550                   lowest-buf buf))))
551       (when lowest-buf
552         (pop-to-buffer lowest-buf)
553         (if (featurep 'xemacs)
554             (switch-to-buffer nntp-server-buffer)
555           (set-buffer nntp-server-buffer)))
556       (mapcar (lambda (b) (delete-windows-on b t))
557               (delq lowest-buf bufs)))))
558
559 (eval-and-compile
560   (cond
561    ((fboundp 'frames-on-display-list)
562     (defalias 'gnus-frames-on-display-list 'frames-on-display-list))
563    ((and (featurep 'xemacs) (fboundp 'frame-device))
564     (defun gnus-frames-on-display-list ()
565       (apply 'filtered-frame-list 'identity (list (frame-device nil)))))
566    (t
567     (defalias 'gnus-frames-on-display-list 'frame-list))))
568
569 (defun gnus-get-buffer-window (buffer &optional frame)
570   (cond ((and (null gnus-use-frames-on-any-display)
571               (memq frame '(t 0 visible)))
572          (car
573           (let ((frames (gnus-frames-on-display-list)))
574             (gnus-remove-if (lambda (win) (not (memq (window-frame win)
575                                                      frames)))
576                             (get-buffer-window-list buffer nil frame)))))
577         (t
578          (get-buffer-window buffer frame))))
579
580 (provide 'gnus-win)
581
582 ;;; gnus-win.el ends here