Revision: miles@gnu.org--gnu-2005/emacs--cvs-trunk--0--patch-572
[bpt/emacs.git] / lisp / dframe.el
CommitLineData
7cfc18c4
CY
1;;; dframe --- dedicate frame support modes
2
3;;; Copyright (C) 1996, 97, 98, 99, 2000, 01, 02, 03, 04 Free Software Foundation
4
5;; Author: Eric M. Ludlam <zappo@gnu.org>
6;; Keywords: file, tags, tools
7cfc18c4
CY
7
8(defvar dframe-version "1.3"
9 "The current version of the dedicated frame library.")
10
11;; This file is part of GNU Emacs.
12
13;; GNU Emacs is free software; you can redistribute it and/or modify
14;; it under the terms of the GNU General Public License as published by
15;; the Free Software Foundation; either version 2, or (at your option)
16;; any later version.
17
18;; GNU Emacs is distributed in the hope that it will be useful,
19;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21;; GNU General Public License for more details.
22
23;; You should have received a copy of the GNU General Public License
24;; along with GNU Emacs; see the file COPYING. If not, write to the
24bbdbef
CY
25;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
26;; Boston, MA 02110-1301, USA.
7cfc18c4
CY
27
28;;; Commentary:
29;;
30;; This code was developed and maintained as a part of speedbar since 1996.
31;; It became its own support utility in Aug 2000.
32;;
33;; Dedicated frame mode is an Emacs independent library for supporting
34;; a program/buffer combination that resides in a dedicated frame.
35;; Support of this nature requires several complex interactions with the
36;; user which this library will provide, including:
37;;
38;; * Creation of a frame. Positioned relatively.
39;; Includes a frame cache for User position caching.
40;; * Switching between frames.
41;; * Timed activities using idle-timers
42;; * Frame/buffer killing hooks
43;; * Mouse-3 position relative menu
44;; * Mouse motion, help-echo hacks
45;; * Mouse clicking, double clicking, & Xemacs image clicking hack
46;; * Mode line hacking
47;; * Utilities for use in a program covering:
48;; o keymap massage for some actions
49;; o working with an associated buffer
50;; o shift-click
51;; o detaching a frame
52;; o focus-shifting & optional frame jumping
53;; o currently active frame.
54;; o message/y-or-n-p
55;; o mouse set point
56;;
57;; To Use:
58;; 1) (require 'dframe)
59;; 2) Variable Setup:
60;; -frame-parameters -- Frame parameters for Emacs.
61;; -frame-plist -- Frame parameters for XEmacs.
62;; -- Not on parameter lists: They can optionally include width
63;; and height. If width or height is not included, then it will
64;; be provided to match the originating frame. In general,
65;; turning off the menu bar, mode line, and minibuffer can
66;; provide a smaller window, or more display area.
67;; -track-mouse-flag -- mouse tracking on/off specific to your tool.
68;; -update-flag -- app toggle for timer use. Init from
69;; `dframe-have-timer-flag'. This is nil for terminals, since
70;; updating a frame in a terminal is not useful to the user.
71;; -key-map -- Your keymap. Call `dframe-update-keymap' on it.
72;; -buffer, -frame, -cached-frame -- Variables used to track your
73;; applications buffer, frame, or frame cache (when hidden). See
74;; `dframe-frame-mode' for details.
75;; -before-delete-hook, -before-popup-hook, -after-create-hook --
76;; Hooks to have called. The `-after-create-hook' probably wants
77;; to call a function which calls `dframe-reposition-frame' in an
78;; appropriate manner.
79;; 3) Function Setup:
80;; your-frame-mode -- function to toggle your app frame on and off.
81;; its tasks are:
82;; a) create a buffer
83;; b) Call `dframe-frame-mode'. (See its doc)
84;; c) If successful (your -frame variable has a value), call
85;; timer setup if applicable.
86;; your-frame-reposition- -- Function to call from after-create-hook to
87;; reposition your frame with `dframe-repsoition-frame'.
88;; your-mode -- Set up the major mode of the buffer for your app.
89;; Set these variables: dframe-track-mouse-function,
90;; dframe-help-echo-function,
91;; dframe-mouse-click-function,
92;; dframe-mouse-position-function.
93;; See speedbar's implementation of these functions.
94;; `speedbar-current-frame', `speedbar-get-focus', `speedbar-message',
95;; `speedbar-y-or-n-p', `speedbar-set-timer', `speedbar-click',
96;; `speedbar-position-cursor-on-line'
97;; 4) Handling mouse clicks, and help text:
98;; dframe-track-mouse, dframe-help-echo-function --
99;; These variables need to be set to functions that display info
100;; based on the mouse's position.
101;; Text propert 'help-echo, set to `dframe-help-echo', which will
102;; call `dframe-help-echo-function'.
103;; Have a `-click' function, it can call `dframe-quick-mouse' for
104;; positioning. If the variable `dframe-power-click' is non-nil,
105;; then `shift' was held down during the click.
106
107;;; Bugs
108;;
109;; * The timer managers doesn't handle multiple different timeouts.
110;; * You can't specify continuous timouts (as opposed to just lidle timers.)
111
112;;; Code:
113(defvar dframe-xemacsp (string-match "XEmacs" emacs-version)
114 "Non-nil if we are running in the XEmacs environment.")
115(defvar dframe-xemacs20p (and dframe-xemacsp
116 (>= emacs-major-version 20)))
117
118;; From custom web page for compatibility between versions of custom
119;; with help from ptype@dera.gov.uk (Proto Type)
120(eval-and-compile
121 (condition-case ()
122 (require 'custom)
123 (error nil))
124 (if (and (featurep 'custom) (fboundp 'custom-declare-variable)
125 ;; Some XEmacsen w/ custom don't have :set keyword.
126 ;; This protects them against custom.
127 (fboundp 'custom-initialize-set))
128 nil ;; We've got what we needed
129 ;; We have the old custom-library, hack around it!
130 (if (boundp 'defgroup)
131 nil
132 (defmacro defgroup (&rest args)
133 nil))
134 (if (boundp 'defface)
135 nil
136 (defmacro defface (var values doc &rest args)
137 (` (progn
138 (defvar (, var) (quote (, var)))
139 ;; To make colors for your faces you need to set your .Xdefaults
140 ;; or set them up ahead of time in your .emacs file.
141 (make-face (, var))
142 ))))
143 (if (boundp 'defcustom)
144 nil
145 (defmacro defcustom (var value doc &rest args)
146 (` (defvar (, var) (, value) (, doc)))))))
147
148\f
149;;; Compatibility functions
150;;
151(if (fboundp 'frame-parameter)
152
153 (defalias 'dframe-frame-parameter 'frame-parameter)
154
155 (defun dframe-frame-parameter (frame parameter)
156 "Return FRAME's PARAMETER value."
157 (cdr (assoc parameter (frame-parameters frame)))))
158
159\f
160;;; Variables
161;;
162(defgroup dframe nil
163 "Faces used in dframe."
164 :prefix "dframe-"
165 :group 'dframe)
166
167(defvar dframe-have-timer-flag
168 (and (or (fboundp 'run-with-idle-timer)
169 (fboundp 'start-itimer)
170 (boundp 'post-command-idle-hook))
171 (if (fboundp 'display-graphic-p)
172 (display-graphic-p)
173 window-system))
174 "Non-nil means that timers are available for this Emacs.")
175
176(defcustom dframe-update-speed
177 (if dframe-xemacsp
178 (if dframe-xemacs20p
179 2 ; 1 is too obrusive in XEmacs
180 5) ; when no idleness, need long delay
181 1)
182 "*Idle time in seconds needed before dframe will update itself.
183Updates occur to allow dframe to display directory information
184relevant to the buffer you are currently editing."
185 :group 'dframe
186 :type 'integer)
187
188(defcustom dframe-activity-change-focus-flag nil
189 "*Non-nil means the selected frame will change based on activity.
190Thus, if a file is selected for edit, the buffer will appear in the
191selected frame and the focus will change to that frame."
192 :group 'dframe
193 :type 'boolean)
194
195(defcustom dframe-after-select-attached-frame-hook nil
196 "*Hook run after dframe has selected the attached frame."
197 :group 'dframe
198 :type 'hook)
199
200(defvar dframe-track-mouse-function nil
201 "*A function to call when the mouse is moved in the given frame.
202Typically used to display info about the line under the mouse.")
203(make-variable-buffer-local 'dframe-track-mouse-function)
204
205(defvar dframe-help-echo-function nil
206 "*A function to call when help-echo is used in newer versions of Emacs.
207Typically used to display info about the line under the mouse.")
208(make-variable-buffer-local 'dframe-help-echo-function)
209
210(defvar dframe-mouse-click-function nil
211 "*A function to call when the mouse is clicked.
212Valid clicks are mouse 2, our double mouse 1.")
213(make-variable-buffer-local 'dframe-mouse-click-function)
214
215(defvar dframe-mouse-position-function nil
216 "*A function to called to position the cursor for a mouse click.")
217(make-variable-buffer-local 'dframe-mouse-position-function)
218
219(defvar dframe-power-click nil
220 "Never set this by hand. Value is t when S-mouse activity occurs.")
221
222(defvar dframe-timer nil
223 "The dframe timer used for updating the buffer.")
224(make-variable-buffer-local 'dframe-timer)
225
226(defvar dframe-attached-frame nil
227 "The frame which started a frame mode.
228This is the frame from which all interesting activities will go
229for the mode using dframe.")
230(make-variable-buffer-local 'dframe-attached-frame)
231
232(defvar dframe-controlled nil
233 "Is this buffer controlled by a dedicated frame.
234Local to those buffers, as a function called that created it.")
235(make-variable-buffer-local 'dframe-controlled)
236
237(defun dframe-update-keymap (map)
238 "Update the keymap MAP for dframe default bindings."
239 ;; Frame control
240 (define-key map "q" 'dframe-close-frame)
241 (define-key map "Q" 'delete-frame)
242
243 ;; Override switch to buffer to never hack our frame.
244 (substitute-key-definition 'switch-to-buffer
245 'dframe-switch-buffer-attached-frame
246 map global-map)
247
248 (if dframe-xemacsp
249 (progn
250 ;; mouse bindings so we can manipulate the items on each line
251 (define-key map 'button2 'dframe-click)
252 (define-key map '(shift button2) 'dframe-power-click)
253 ;; Info doc fix from Bob Weiner
254 (if (featurep 'infodoc)
255 nil
256 (define-key map 'button3 'dframe-xemacs-popup-kludge))
257 )
258
259 ;; mouse bindings so we can manipulate the items on each line
260 (define-key map [down-mouse-1] 'dframe-double-click)
261 (define-key map [mouse-2] 'dframe-click)
262 ;; This is the power click for new frames, or refreshing a cache
263 (define-key map [S-mouse-2] 'dframe-power-click)
264 ;; This adds a small unecessary visual effect
265 ;;(define-key map [down-mouse-2] 'dframe-quick-mouse)
266
267 (define-key map [down-mouse-3] 'dframe-emacs-popup-kludge)
268
269 ;; This lets the user scroll as if we had a scrollbar... well maybe not
270 (define-key map [mode-line mouse-2] 'dframe-mouse-hscroll)
271 ;; another handy place users might click to get our menu.
272 (define-key map [mode-line down-mouse-1]
273 'dframe-emacs-popup-kludge)
274
275 ;; We can't switch buffers with the buffer mouse menu. Lets hack it.
276 (define-key map [C-down-mouse-1] 'dframe-hack-buffer-menu)
277
278 ;; Lastly, we want to track the mouse. Play here
279 (define-key map [mouse-movement] 'dframe-track-mouse)
280 ))
281
282(defun dframe-live-p (frame)
283 "Return non-nil if FRAME is currently available."
284 (and frame (frame-live-p frame) (frame-visible-p frame)))
285
286(defun dframe-frame-mode (arg frame-var cache-var buffer-var frame-name
287 local-mode-fn
288 &optional
289 parameters
290 delete-hook popup-hook create-hook
291 )
292 "Manage a frame for an application, enabling it when ARG is positive.
293FRAME-VAR is a variable used to cache the frame being used.
294This frame is either resurrected, hidden, killed, etc based on
295the value.
296CACHE-VAR is a variable used to cache a cached frame.
297BUFFER-VAR is a variable used to cache the buffer being used in dframe.
298This buffer will have `dframe-mode' run on it.
299FRAME-NAME is the name of the frame to create.
300LOCAL-MODE-FN is the function used to call this one.
301PARAMETERS are frame parameters to apply to this dframe.
302DELETE-HOOK are hooks to run when deleting a frame.
303POPUP-HOOK are hooks to run before showing a frame.
304CREATE-HOOK are hooks to run after creating a frame."
305 ;; toggle frame on and off.
306 (if (not arg) (if (dframe-live-p (symbol-value frame-var))
307 (setq arg -1) (setq arg 1)))
308 ;; Make sure the current buffer is set.
309 (set-buffer (symbol-value buffer-var))
310 ;; turn the frame off on neg number
311 (if (and (numberp arg) (< arg 0))
312 (progn
313 (run-hooks 'delete-hook)
314 (if (and (symbol-value frame-var)
315 (frame-live-p (symbol-value frame-var)))
316 (progn
317 (set cache-var (symbol-value frame-var))
318 (make-frame-invisible (symbol-value frame-var))))
319 (set frame-var nil))
320 ;; Set this as our currently attached frame
321 (setq dframe-attached-frame (selected-frame))
322 (run-hooks 'popup-hook)
323 ;; Updated the buffer passed in to contain all the hacks needed
324 ;; to make it work well in a dedicated window.
325 (save-excursion
326 (set-buffer (symbol-value buffer-var))
327 ;; Declare this buffer a dedicated frame
328 (setq dframe-controlled local-mode-fn)
329
330 (if dframe-xemacsp
331 ;; Hack the XEmacs mouse-motion handler
332 (with-no-warnings
333 ;; Hack the XEmacs mouse-motion handler
334 (set (make-local-variable 'mouse-motion-handler)
335 'dframe-track-mouse-xemacs)
336 ;; Hack the double click handler
337 (make-local-variable 'mouse-track-click-hook)
338 (add-hook 'mouse-track-click-hook
339 (lambda (event count)
340 (if (/= (event-button event) 1)
341 nil ; Do normal operations.
342 (cond ((eq count 1)
343 (dframe-quick-mouse event))
344 ((or (eq count 2)
345 (eq count 3))
346 (dframe-click event)
347 (dframe-quick-mouse event)))
348 ;; Don't do normal operations.
349 t))))
350 ;; Enable mouse tracking in emacs
351 (if dframe-track-mouse-function
352 (set (make-local-variable 'track-mouse) t)) ;this could be messy.
353 ;; disable auto-show-mode for Emacs
354 (setq auto-show-mode nil))
355;;;; DISABLED: This causes problems for users with multiple frames.
356;;;; ;; Set this up special just for the passed in buffer
357;;;; ;; Terminal minibuffer stuff does not require this.
358;;;; (if (and (or (assoc 'minibuffer parameters)
359;;;; ;; XEmacs plist is not an association list
360;;;; (member 'minibuffer parameters))
361;;;; window-system (not (eq window-system 'pc))
362;;;; (null default-minibuffer-frame))
363;;;; (progn
364;;;; (make-local-variable 'default-minibuffer-frame)
365;;;; (setq default-minibuffer-frame dframe-attached-frame))
366;;;; )
367 ;; Override `temp-buffer-show-hook' so that help and such
368 ;; put their stuff into a frame other than our own.
369 ;; Correct use of `temp-buffer-show-function': Bob Weiner
370 (if (and (boundp 'temp-buffer-show-hook)
371 (boundp 'temp-buffer-show-function))
372 (progn (make-local-variable 'temp-buffer-show-hook)
373 (setq temp-buffer-show-hook temp-buffer-show-function)))
374 (make-local-variable 'temp-buffer-show-function)
375 (setq temp-buffer-show-function 'dframe-temp-buffer-show-function)
376 ;; If this buffer is killed, we must make sure that we destroy
377 ;; the frame the dedicated window is in.
378 (add-hook 'kill-buffer-hook `(lambda ()
379 (let ((skilling (boundp 'skilling)))
380 (if skilling
381 nil
382 (if dframe-controlled
383 (progn
384 (funcall dframe-controlled -1)
385 (setq ,buffer-var nil)
386 )))))
387 t t)
388 )
389 ;; Get the frame to work in
390 (if (frame-live-p (symbol-value cache-var))
391 (progn
392 (set frame-var (symbol-value cache-var))
393 (make-frame-visible (symbol-value frame-var))
394 (select-frame (symbol-value frame-var))
395 (set-window-dedicated-p (selected-window) nil)
396 (if (not (eq (current-buffer) (symbol-value buffer-var)))
397 (switch-to-buffer (symbol-value buffer-var)))
398 (set-window-dedicated-p (selected-window) t)
399 (raise-frame (symbol-value frame-var))
400 )
401 (if (frame-live-p (symbol-value frame-var))
402 (raise-frame (symbol-value frame-var))
403 (set frame-var
404 (if dframe-xemacsp
405 ;; Only guess height if it is not specified.
406 (if (member 'height parameters)
407 (make-frame parameters)
408 (make-frame (nconc (list 'height
409 (dframe-needed-height))
410 parameters)))
411 (let* ((mh (dframe-frame-parameter dframe-attached-frame
412 'menu-bar-lines))
413 (paramsa
414 ;; Only add a guessed height if one is not specified
415 ;; in the input parameters.
416 (if (assoc 'height parameters)
417 parameters
418 (append
419 parameters
420 (list (cons 'height (+ mh (frame-height)))))))
421 (params
422 ;; Only add a guessed width if one is not specified
423 ;; in the input parameters.
424 (if (assoc 'width parameters)
425 paramsa
426 (append
427 paramsa
428 (list (cons 'width (frame-width))))))
429 (frame
430 (if (or (< emacs-major-version 20)
431 (not (eq window-system 'x)))
432 (make-frame params)
433 (let ((x-pointer-shape x-pointer-top-left-arrow)
434 (x-sensitive-text-pointer-shape
435 x-pointer-hand2))
436 (make-frame params)))))
437 frame)))
438 ;; Put the buffer into the frame
439 (save-excursion
440 (select-frame (symbol-value frame-var))
441 (switch-to-buffer (symbol-value buffer-var))
442 (set-window-dedicated-p (selected-window) t))
443 ;; Run hooks (like reposition)
444 (run-hooks 'create-hook)
445 ;; Frame name
446 (if (and (or (null window-system) (eq window-system 'pc))
447 (fboundp 'set-frame-name))
448 (save-window-excursion
449 (select-frame (symbol-value frame-var))
450 (set-frame-name frame-name)))
451 ;; On a terminal, raise the frame or the user will
452 ;; be confused.
453 (if (not window-system)
454 (select-frame (symbol-value frame-var)))
455 ))) )
456
457(defun dframe-reposition-frame (new-frame parent-frame location)
458 "Move NEW-FRAME to be relative to PARENT-FRAME.
459LOCATION can be one of 'random, 'left, 'right, 'left-right, or 'top-bottom."
460 (if dframe-xemacsp
461 (dframe-reposition-frame-xemacs new-frame parent-frame location)
462 (dframe-reposition-frame-emacs new-frame parent-frame location)))
463
464(defun dframe-reposition-frame-emacs (new-frame parent-frame location)
465 "Move NEW-FRAME to be relative to PARENT-FRAME.
466LOCATION can be one of 'random, 'left-right, 'top-bottom, or
467a cons cell indicationg a position of the form (LEFT . TOP)."
468 (let* ((pfx (dframe-frame-parameter parent-frame 'left))
469 (pfy (dframe-frame-parameter parent-frame 'top))
470 (pfw (frame-pixel-width parent-frame))
471 (pfh (frame-pixel-height parent-frame))
472 (nfw (frame-pixel-width new-frame))
473 (nfh (frame-pixel-height new-frame))
474 newleft newtop
475 )
476 ;; Position dframe.
477 (if (or (not window-system) (eq window-system 'pc))
478 ;; Do no positioning if not on a windowing system,
479 nil
480 ;; Rebuild pfx,pfy to be absolute positions.
481 (setq pfx (if (not (consp pfx))
482 pfx
483 ;; If pfx is a list, that means we grow
484 ;; from a specific edge of the display.
485 ;; Convert that to the distance from the
486 ;; left side of the display.
487 (if (eq (car pfx) '-)
488 ;; A - means distance from the right edge
489 ;; of the display, or DW - pfx - framewidth
490 (- (x-display-pixel-width) (car (cdr pfx)) pfw)
491 (car (cdr pfx))))
492 pfy (if (not (consp pfy))
493 pfy
494 ;; If pfy is a list, that means we grow
495 ;; from a specific edge of the display.
496 ;; Convert that to the distance from the
497 ;; left side of the display.
498 (if (eq (car pfy) '-)
499 ;; A - means distance from the right edge
500 ;; of the display, or DW - pfx - framewidth
501 (- (x-display-pixel-height) (car (cdr pfy)) pfh)
502 (car (cdr pfy))))
503 )
504 (cond ((eq location 'right)
505 (setq newleft (+ pfx pfw 5)
506 newtop pfy))
507 ((eq location 'left)
508 (setq newleft (+ pfx 10 nfw)
509 newtop pfy))
510 ((eq location 'left-right)
511 (setq newleft
512 ;; Decide which side to put it on. 200 is just a
513 ;; buffer for the left edge of the screen. The
514 ;; extra 10 is just dressings for window
515 ;; decorations.
516 (let* ((left-guess (- pfx 10 nfw))
517 (right-guess (+ pfx pfw 5))
518 (left-margin left-guess)
519 (right-margin (- (x-display-pixel-width)
520 right-guess 5 nfw)))
521 (cond ((>= left-margin 0) left-guess)
522 ((>= right-margin 0) right-guess)
523 ;; otherwise choose side we overlap less
524 ((> left-margin right-margin) 0)
525 (t (- (x-display-pixel-width) nfw 5))))
526 newtop pfy
527 ))
528 ((eq location 'top-bottom)
529 (setq newleft pfx
530 newtop
531 ;; Try and guess if we should be on the top or bottom.
532 (let* ((top-guess (- pfy 15 nfh))
533 (bottom-guess (+ pfy 5 pfh))
534 (top-margin top-guess)
535 (bottom-margin (- (x-display-pixel-height)
536 bottom-guess 5 nfh)))
537 (cond ((>= top-margin 0) top-guess)
538 ((>= bottom-margin 0) bottom-guess)
539 ;; Choose a side to overlap the least.
540 ((> top-margin bottom-margin) 0)
541 (t (- (x-display-pixel-height) nfh 5)))))
542 )
543 ((consp location)
544 (setq newleft (or (car location) 0)
545 newtop (or (cdr location) 0)))
546 (t nil))
547 (modify-frame-parameters new-frame
548 (list (cons 'left newleft)
549 (cons 'top newtop))))))
550
551(defun dframe-reposition-frame-xemacs (new-frame parent-frame location)
552 "Move NEW-FRAME to be relative to PARENT-FRAME.
553LOCATION can be one of 'random, 'left-right, or 'top-bottom."
554 ;; Not yet implemented
555 )
556
557;; XEmacs function only.
558(defun dframe-needed-height (&optional frame)
559 "The needed height for the tool bar FRAME (in characters)."
560 (or frame (setq frame (selected-frame)))
561 ;; The 1 is the missing modeline/minibuffer
562 (+ 1 (/ (frame-pixel-height frame)
563 ;; This obscure code avoids a byte compiler warning in Emacs.
564 (let ((f 'face-height))
565 (funcall f 'default frame)))))
566
567(defun dframe-detach (frame-var cache-var buffer-var)
568 "Detatch the frame in symbol FRAME-VAR.
569CACHE-VAR and BUFFER-VAR are symbols as in `dframe-frame-mode'"
570 (save-excursion
571 (set-buffer (symbol-value buffer-var))
572 (rename-buffer (buffer-name) t)
573 (let ((oldframe (symbol-value frame-var)))
574 (set buffer-var nil)
575 (set frame-var nil)
576 (set cache-var nil)
577 (make-variable-buffer-local frame-var)
578 (set frame-var oldframe)
579 )))
580
581;;; Special frame event proxies
582;;
583(if (boundp 'special-event-map)
584 (progn
585 (define-key special-event-map [make-frame-visible]
586 'dframe-handle-make-frame-visible)
587 (define-key special-event-map [iconify-frame]
588 'dframe-handle-iconify-frame)
589 (define-key special-event-map [delete-frame]
590 'dframe-handle-delete-frame))
591 )
592
593(defvar dframe-make-frame-visible-function nil
594 "Function used when a dframe controlled frame is de-iconified.
595The function must take an EVENT.")
596(defvar dframe-iconify-frame-function nil
597 "Function used when a dframe controlled frame is iconified.
598The function must take an EVENT.")
599(defvar dframe-delete-frame-function nil
600 "Function used when a frame attached to a dframe frame is deleted.
601The function must take an EVENT.")
602
603(defun dframe-handle-make-frame-visible (e)
604 "Handle a `make-frame-visible' event.
605Should enables auto-updating if the last state was also enabled.
606Argument E is the event making the frame visible."
607 (interactive "e")
608 (let ((f last-event-frame))
609 (if (and (dframe-attached-frame f)
610 dframe-make-frame-visible-function)
611 (funcall dframe-make-frame-visible-function e)
612 )))
613
614(defun dframe-handle-iconify-frame (e)
615 "Handle a `iconify-frame' event.
616Should disables auto-updating if the last state was also enabled.
617Argument E is the event iconifying the frame."
618 (interactive "e")
619 (let ((f last-event-frame))
620 (if (and (dframe-attached-frame f)
621 dframe-iconify-frame-function e)
622 (funcall dframe-iconify-frame-function)
623 )))
624
625(defun dframe-handle-delete-frame (e)
626 "Handle `delete-frame' event.
627Argument E is the event deleting the frame."
628 (interactive "e")
629 (let ((fl (frame-list))
630 (sf (selected-frame)))
631 ;; Loop over all frames. If dframe-delete-frame-function is
632 ;; non-nil, call it.
633 (while fl
634 (select-frame (car fl))
635 (if dframe-delete-frame-function
636 (funcall dframe-delete-frame-function e))
637 (setq fl (cdr fl)))
638 (if (frame-live-p sf)
639 (select-frame sf))
640 (handle-delete-frame e)))
641
642
643;;; Utilities
644;;
645(defun dframe-get-focus (frame-var activator &optional hook)
646 "Change frame focus to or from a dedicated frame.
647If the selected frame is not in the symbol FRAME-VAR, then FRAME-VAR
648frame is selected. If the FRAME-VAR is active, then select the
649attached frame. If FRAME-VAR is nil, ACTIVATOR is called to
650created it. HOOK is an optional argument of hooks to run when
651selecting FRAME."
652 (interactive)
653 (if (eq (selected-frame) (symbol-value frame-var))
654 (if (frame-live-p dframe-attached-frame)
655 (dframe-select-attached-frame))
656 ;; make sure we have a frame
657 (if (not (frame-live-p (symbol-value frame-var)))
658 (funcall activator 1))
659 ;; go there
660 (select-frame (symbol-value frame-var))
661 )
662 (other-frame 0)
663 ;; If updates are off, then refresh the frame (they want it now...)
664 (run-hooks 'hook))
665
666
667(defun dframe-close-frame ()
668 "Close the current frame if it is dedicated."
669 (interactive)
670 (if dframe-controlled
671 (let ((b (current-buffer)))
672 (funcall dframe-controlled -1)
673 (kill-buffer b))))
674
675(defun dframe-current-frame (frame-var desired-major-mode)
676 "Return the existing dedicated frame to use.
677FRAME-VAR is the variable storing the currently active dedicated frame.
678If the current frame's buffer uses DESIRED-MAJOR-MODE, then use that frame."
679 (if (not (eq (selected-frame) (symbol-value frame-var)))
680 (if (and (eq major-mode 'desired-major-mode)
681 (get-buffer-window (current-buffer))
682 (window-frame (get-buffer-window (current-buffer))))
683 (window-frame (get-buffer-window (current-buffer)))
684 (symbol-value frame-var))
685 (symbol-value frame-var)))
686
687(defun dframe-attached-frame (&optional frame)
688 "Return the attached frame belonging to the dframe controlled frame FRAME.
689If optional arg FRAME is nil just return `dframe-attached-frame'."
690 (save-excursion
691 (if frame (select-frame frame))
692 dframe-attached-frame))
693
694(defun dframe-select-attached-frame (&optional frame)
695 "Switch to the frame the dframe controlled frame FRAME was started from. If
696optional arg FRAME is nil assume the attached frame is already selected and
697just run the hooks `dframe-after-select-attached-frame-hook'. Return the
698attached frame."
699 (let ((frame (dframe-attached-frame frame)))
700 (if frame (select-frame frame))
701 (prog1 frame
702 (run-hooks 'dframe-after-select-attached-frame-hook))))
703
704(defmacro dframe-with-attached-buffer (&rest forms)
705 "Execute FORMS in the attached frame's special buffer.
706Optionally select that frame if necessary."
707 `(save-selected-window
708 ;;(speedbar-set-timer speedbar-update-speed)
709 (dframe-select-attached-frame)
710 ,@forms
711 (dframe-maybee-jump-to-attached-frame)))
712
713(defun dframe-maybee-jump-to-attached-frame ()
714 "Jump to the attached frame ONLY if this was not a mouse event."
715 (when (or (not (dframe-mouse-event-p last-input-event))
716 dframe-activity-change-focus-flag)
717 (dframe-select-attached-frame)
718 ;; KB: For what is this - raising the frame??
719 (other-frame 0)))
720
721
722(defvar dframe-suppress-message-flag nil
723 "Non-nil means that `dframe-message' should just return a string.")
724
725(defun dframe-message (fmt &rest args)
726 "Like message, but for use in a dedicated frame.
727Argument FMT is the format string, and ARGS are the arguments for message."
728 (save-selected-window
729 (if dframe-suppress-message-flag
730 (apply 'format fmt args)
731 (if dframe-attached-frame
732 ;; KB: Here we do not need calling `dframe-select-attached-frame'
733 (select-frame dframe-attached-frame))
734 (apply 'message fmt args))))
735
736(defun dframe-y-or-n-p (prompt)
737 "Like `y-or-n-p', but for use in a dedicated frame.
738Argument PROMPT is the prompt to use."
739 (save-selected-window
740 (if (and ;;default-minibuffer-frame
741 dframe-attached-frame
742 ;;(not (eq default-minibuffer-frame dframe-attached-frame))
743 )
744 ;; KB: Here we do not need calling `dframe-select-attached-frame'
745 (select-frame dframe-attached-frame))
746 (y-or-n-p prompt)))
747\f
748;;; timer management
749;;
750;; Unlike speedbar with a dedicated set of routines, dframe has one master
751;; timer, and all dframe users will use it. At least until I figure out a way
752;; around that problem.
753;;
754;; Advantage 1: Two apps with timer/frames can munge the master list
755;; to make sure they occur in order.
756;; Advantage 2: If a user hits a key between timer functions, we can
757;; interrupt them safely.
758(defvar dframe-client-functions nil
759 "List of client functions using the dframe timer.")
760
761(defun dframe-set-timer (timeout fn &optional null-on-error)
762 "Apply a timer with TIMEOUT, to call FN, or remove a timer if TIMEOUT is nil.
763TIMEOUT is the number of seconds until the dframe controled program
764timer is called again. When TIMEOUT is nil, turn off all timeouts.
765This function must be called from the buffer belonging to the program
766who requested the timer.
767If NULL-ON-ERROR is a symbol, set it to nil if we cannot create a timer."
768 ;; First, fix up our list of client functions
769 (if timeout
770 (add-to-list 'dframe-client-functions fn)
771 (setq dframe-client-functions (delete fn dframe-client-functions)))
772 ;; Now decided what to do about the timout.
773 (if (or
774 ;; We have a timer, restart the timer with the new time.
775 timeout
776 ;; We have a timer, an off is requested, and no client
777 ;; functions are left, shut er down.
778 (and dframe-timer (not timeout) dframe-client-functions))
779 ;; Only call the low level function if we are changing the state.
780 (dframe-set-timer-internal timeout null-on-error)))
781
782(defun dframe-set-timer-internal (timeout &optional null-on-error)
783 "Apply a timer with TIMEOUT to call the dframe timer manager.
784If NULL-ON-ERROR is a symbol, set it to nil if we cannot create a timer."
785 (cond
786 ;; XEmacs
787 (dframe-xemacsp
788 (with-no-warnings
789 (if dframe-timer
790 (progn (delete-itimer dframe-timer)
791 (setq dframe-timer nil)))
792 (if timeout
793 (if (and dframe-xemacsp
794 (or (>= emacs-major-version 21)
795 (and (= emacs-major-version 20)
796 (> emacs-minor-version 0))
797 (and (= emacs-major-version 19)
798 (>= emacs-minor-version 15))))
799 (setq dframe-timer (start-itimer "dframe"
800 'dframe-timer-fn
801 timeout
802 timeout
803 t))
804 (setq dframe-timer (start-itimer "dframe"
805 'dframe-timer-fn
806 timeout
807 nil))))))
808 ;; Post 19.31 Emacs
809 ((fboundp 'run-with-idle-timer)
810 (if dframe-timer
811 (progn (cancel-timer dframe-timer)
812 (setq dframe-timer nil)))
813 (if timeout
814 (setq dframe-timer
815 (run-with-idle-timer timeout t 'dframe-timer-fn))))
816 ;; Emacs 19.30 (Thanks twice: ptype@dra.hmg.gb)
817 ((fboundp 'post-command-idle-hook)
818 (if timeout
819 (add-hook 'post-command-idle-hook 'dframe-timer-fn)
820 (remove-hook 'post-command-idle-hook 'dframe-timer-fn)))
821 ;; Older or other Emacsen with no timers. Set up so that its
822 ;; obvious this emacs can't handle the updates
823 ((symbolp null-on-error)
824 (set null-on-error nil)))
825 )
826
827(defun dframe-timer-fn ()
828 "Called due to the dframe timer.
829Evaluates all cached timer functions in sequence."
830 (let ((l dframe-client-functions))
831 (while (and l (sit-for 0))
832 (condition-case er
833 (funcall (car l))
834 (error (message "DFRAME TIMER ERROR: %S" er)))
835 (setq l (cdr l)))))
836
837;;; Menu hacking for mouse-3
838;;
839(defconst dframe-pass-event-to-popup-mode-menu
840 (let (max-args)
841 (and (fboundp 'popup-mode-menu)
842 (fboundp 'function-max-args)
843 (setq max-args (function-max-args 'popup-mode-menu))
844 (not (zerop max-args))))
845 "The EVENT arg to 'popup-mode-menu' was introduced in XEmacs 21.4.0.")
846
847;; In XEmacs, we make popup menus work on the item over mouse (as
848;; opposed to where the point happens to be.) We attain this by
849;; temporarily moving the point to that place.
850;; Hrvoje Niksic <hniksic@srce.hr>
851(with-no-warnings
852(defun dframe-xemacs-popup-kludge (event)
853 "Pop up a menu related to the clicked on item.
854Must be bound to EVENT."
855 (interactive "e")
856 (save-excursion
857 (if dframe-pass-event-to-popup-mode-menu
858 (popup-mode-menu event)
859 (goto-char (event-closest-point event))
860 (beginning-of-line)
861 (forward-char (min 5 (- (save-excursion (end-of-line) (point))
862 (save-excursion (beginning-of-line) (point)))))
863 (popup-mode-menu))
864 ;; Wait for menu to bail out. `popup-mode-menu' (and other popup
865 ;; menu functions) return immediately.
866 (let (new)
867 (while (not (misc-user-event-p (setq new (next-event))))
868 (dispatch-event new))
869 (dispatch-event new))))
870);with-no-warnings
871
872(defun dframe-emacs-popup-kludge (e)
873 "Pop up a menu related to the clicked on item.
874Must be bound to event E."
875 (interactive "e")
876 (save-excursion
877 (mouse-set-point e)
878 ;; This gets the cursor where the user can see it.
879 (if (not (bolp)) (forward-char -1))
880 (sit-for 0)
881 (if (< emacs-major-version 20)
882 (mouse-major-mode-menu e)
883 (mouse-major-mode-menu e nil))))
884
885;;; Interactive user functions for the mouse
886;;
887(if dframe-xemacsp
888 (defalias 'dframe-mouse-event-p 'button-press-event-p)
889 (defun dframe-mouse-event-p (event)
890 "Return t if the event is a mouse related event."
891 (if (and (listp event)
892 (member (event-basic-type event)
893 '(mouse-1 mouse-2 mouse-3)))
894 t
895 nil)))
896
897(defun dframe-track-mouse (event)
898 "For motion EVENT, display info about the current line."
899 (interactive "e")
900 (when (and dframe-track-mouse-function
901 (or dframe-xemacsp ;; XEmacs always safe?
902 (windowp (posn-window (event-end event))) ; Sometimes
903 ; there is no window to jump into.
904 ))
905
906 (funcall dframe-track-mouse-function event)))
907
908(defun dframe-track-mouse-xemacs (event)
909 "For motion EVENT, display info about the current line."
910 (if (functionp (default-value 'mouse-motion-handler))
911 (funcall (default-value 'mouse-motion-handler) event))
912 (if dframe-track-mouse-function
913 (funcall dframe-track-mouse-function event)))
914
915(defun dframe-help-echo (window &optional buffer position)
916 "Display help based context.
917The context is in WINDOW, viewing BUFFER, at POSITION.
918BUFFER and POSITION are optional because XEmacs doesn't use them."
919 (when (and (not dframe-track-mouse-function)
920 (bufferp buffer)
921 dframe-help-echo-function)
922 (let ((dframe-suppress-message-flag t))
923 (with-current-buffer buffer
924 (if position (goto-char position))
925 (funcall dframe-help-echo-function)))))
926
927(defun dframe-mouse-set-point (e)
928 "Set POINT based on event E.
929Handles clicking on images in XEmacs."
930 (if (save-excursion
931 (save-window-excursion
932 (mouse-set-point e)
933 (and (fboundp 'event-over-glyph-p) (event-over-glyph-p e))))
934 ;; We are in XEmacs, and clicked on a picture
935 (with-no-warnings
936 (let ((ext (event-glyph-extent e)))
937 ;; This position is back inside the extent where the
938 ;; junk we pushed into the property list lives.
939 (if (extent-end-position ext)
940 (goto-char (1- (extent-end-position ext)))
941 (mouse-set-point e)))
942 );with-no-warnings
943 ;; We are not in XEmacs, OR we didn't click on a picture.
944 (mouse-set-point e)))
945
946(defun dframe-quick-mouse (e)
947 "Since mouse events are strange, this will keep the mouse nicely positioned.
948This should be bound to mouse event E."
949 (interactive "e")
950 (dframe-mouse-set-point e)
951 (if dframe-mouse-position-function
952 (funcall dframe-mouse-position-function)))
953
954(defun dframe-power-click (e)
955 "Activate any `dframe' mouse click as a power click.
956A power click will dispose of cached data (if available) or bring a buffer
957up into a different window.
958This should be bound to mouse event E."
959 (interactive "e")
960 (let ((dframe-power-click t))
961 (select-frame last-event-frame)
962 (dframe-click e)))
963
964(defun dframe-click (e)
965 "Call our clients click function on a user click.
966E is the event causing the click."
967 (interactive "e")
968 (dframe-mouse-set-point e)
969 (when dframe-mouse-click-function
970 ;; On the off chance of buffer switch, or something incorrectly
971 ;; configured.
972 (funcall dframe-mouse-click-function e)))
973
974(defun dframe-double-click (e)
975 "Activate the registered click function on a double click.
976This must be bound to a mouse event.
977This should be bound to mouse event E."
978 (interactive "e")
979 ;; Emacs only. XEmacs handles this via `mouse-track-click-hook'.
980 (cond ((eq (car e) 'down-mouse-1)
981 (dframe-mouse-set-point e))
982 ((eq (car e) 'mouse-1)
983 (dframe-quick-mouse e))
984 ((or (eq (car e) 'double-down-mouse-1)
985 (eq (car e) 'triple-down-mouse-1))
986 (dframe-click e))))
987
988;;; Hacks of normal things.
989;;
990;; Some normal things that happen in one of these dedicated frames
991;; must be handled specially, so that our dedicated frame isn't
992;; messed up.
993(defun dframe-temp-buffer-show-function (buffer)
994 "Placed in the variable `temp-buffer-show-function' in dedicated frames.
995If a user requests help using \\[help-command] <Key> the temp BUFFER will be
996redirected into a window on the attached frame."
997 (if dframe-attached-frame (dframe-select-attached-frame))
998 (pop-to-buffer buffer nil)
999 (other-window -1)
1000 ;; Fix for using this hook on some platforms: Bob Weiner
1001 (cond ((not dframe-xemacsp)
1002 (run-hooks 'temp-buffer-show-hook))
1003 ((fboundp 'run-hook-with-args)
1004 (run-hook-with-args 'temp-buffer-show-hook buffer))
1005 ((and (boundp 'temp-buffer-show-hook)
1006 (listp temp-buffer-show-hook))
1007 (mapcar (function (lambda (hook) (funcall hook buffer)))
1008 temp-buffer-show-hook))))
1009
1010(defun dframe-hack-buffer-menu (e)
1011 "Control mouse 1 is buffer menu.
1012This hack overrides it so that the right thing happens in the main
1013Emacs frame, not in the dedicated frame.
1014Argument E is the event causing this activity."
1015 (interactive "e")
1016 (let ((fn (lookup-key global-map (if dframe-xemacsp
1017 '(control button1)
1018 [C-down-mouse-1])))
1019 (oldbuff (current-buffer))
1020 (newbuff nil))
1021 (unwind-protect
1022 (save-excursion
1023 (set-window-dedicated-p (selected-window) nil)
1024 (call-interactively fn)
1025 (setq newbuff (current-buffer)))
1026 (switch-to-buffer oldbuff)
1027 (set-window-dedicated-p (selected-window) t))
1028 (if (not (eq newbuff oldbuff))
1029 (dframe-with-attached-buffer
1030 (switch-to-buffer newbuff)))))
1031
1032(defun dframe-switch-buffer-attached-frame (&optional buffer)
1033 "Switch to BUFFER in the attached frame, and raise that frame.
1034This overrides the default behavior of `switch-to-buffer' which is
1035broken because of the dedicated frame."
1036 (interactive)
1037 ;; Assume we are in the dedicated frame.
1038 (other-frame 1)
1039 ;; Now switch buffers
1040 (if buffer
1041 (switch-to-buffer buffer)
1042 (call-interactively 'switch-to-buffer nil nil)))
1043
1044;; XEmacs: this can be implemented using modeline keymaps, but there
1045;; is no use, as we have horizontal scrollbar (as the docstring
1046;; hints.)
1047(defun dframe-mouse-hscroll (e)
1048 "Read a mouse event E from the mode line, and horizontally scroll.
1049If the mouse is being clicked on the far left, or far right of the
1050mode-line. This is only useful for non-XEmacs."
1051 (interactive "e")
1052 (let* ((x-point (car (nth 2 (car (cdr e)))))
1053 (pixels-per-10-col (/ (* 10 (frame-pixel-width))
1054 (frame-width)))
1055 (click-col (1+ (/ (* 10 x-point) pixels-per-10-col)))
1056 )
1057 (cond ((< click-col 3)
1058 (scroll-left 2))
1059 ((> click-col (- (window-width) 5))
1060 (scroll-right 2))
1061 (t (dframe-message
1062 "Click on the edge of the modeline to scroll left/right")))
1063 ))
1064
1065(provide 'dframe)
1066
dceb5300 1067;; arch-tag: df9b91b6-e85e-4a76-a02e-b3cb5b686bd4
7cfc18c4 1068;;; dframe.el ends here