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