Initial Commit
[packages] / xemacs-packages / jde / lisp / jde-tree-widget.el
1 ;;; jde-tree-widget.el --- Tree widget
2
3 ;; Copyright (C) 2001, 2004 by David Ponce
4
5 ;; Author: David Ponce <david@dponce.com>
6 ;; Maintainer: David Ponce <david@dponce.com>
7 ;; Created: 16 Feb 2001
8 ;; Version: 1.0.5
9 ;; Keywords: extensions
10 ;; VC: $Id: jde-tree-widget.el,v 1.3 2007-12-01 14:30:33 michaels Exp $
11
12 ;; This file is not part of Emacs
13
14 ;; This program is free software; you can redistribute it and/or
15 ;; modify it under the terms of the GNU General Public License as
16 ;; published by the Free Software Foundation; either version 2, or (at
17 ;; your option) any later version.
18
19 ;; This program is distributed in the hope that it will be useful, but
20 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22 ;; General Public License for more details.
23
24 ;; You should have received a copy of the GNU General Public License
25 ;; along with this program; see the file COPYING.  If not, write to
26 ;; the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
27 ;; Boston, MA 02111-1307, USA.
28
29 ;;; Commentary:
30 ;;
31 ;; This library provide a `tree-widget' useful to display data
32 ;; structures organized in hierarchical order.
33 ;; 
34 ;; The following `tree-widget' extra properties are recognized:
35 ;;
36 ;;   :open
37 ;;      Set to non-nil to unfold the tree.  By default the tree is
38 ;;      folded.
39 ;;
40 ;;   :node
41 ;;      The widget used for the tree node.  By default this is an
42 ;;      `item' widget which displays the tree :tag property value if
43 ;;      defined or a string representation of the tree value using the
44 ;;      function `widget-princ-to-string'.
45 ;;
46 ;;   :keep
47 ;;      Specify a list of extra properties to keep when the tree is
48 ;;      folded so they can be recovered when the tree is unfolded.
49 ;;      This property is also honoured in `tree-widget' children.
50 ;;
51 ;;   :dynargs
52 ;;      Specify a function to be called when the tree is unfolded.
53 ;;      This function will receives the tree widget as its argument
54 ;;      and must return a list of children widget definitions.  Thus
55 ;;      dynamlically providing the tree children in response to an
56 ;;      unfold request.  The list of children definitions is kept in
57 ;;      the tree :args property and the :dynargs function can just
58 ;;      return its value when unfolding the tree again.  To force a
59 ;;      new evaluation of the tree content just set its :args property
60 ;;      to nil and redraw the node.
61 ;;
62 ;;   :has-children
63 ;;      Specify if this tree has children.  This property has meaning
64 ;;      only when used with the above :dynargs one.  It indicates that
65 ;;      children widget exist but will be provided when unfolding the
66 ;;      node.
67 ;;
68 ;;   :no-leaf-handle   (default "*---- ")
69 ;;   :close-handle     (default "-- ")
70 ;;   :no-guide         (default "   ")
71 ;;   :open-handle      (default "-, ")
72 ;;   :guide            (default " | ")
73 ;;   :leaf-handle      (default " |--- ")
74 ;;   :last-leaf-handle (default " `--- ")
75 ;;      These properties define the strings used to draw the tree
76 ;;      like the following:
77 ;;
78 ;;      *---- N0        :no-leaf-handle + node
79 ;;
80 ;;      [-]-, N0        node-handle + :open-handle + node
81 ;;          |--- N1     :no-guide + :leaf-handle + node
82 ;;         [-]-, N2     :no-guide + node-handle + :open-handle + node
83 ;;          |  |--- N21 :no-guide + :guide + :leaf-handle + node
84 ;;          |  `--- N22 :no-guide + :guide + :last-leaf-handle + node
85 ;;         [+]-- N3     :no-guide + node-handle + :close-handle + node
86 ;;
87 ;; About leaf node format
88 ;;   To correctly draw the tree, that is insert the current leaf node
89 ;;   prefix, leaf node widgets should prepend the "%p" escape to the
90 ;;   value of the :format property.  And set the :format-handler
91 ;;   property to `tree-widget-format-handler'.  Something like this:
92 ;;
93 ;;   (define-widget 'leaf-node 'item
94 ;;     :format "%p%t\n"
95 ;;     :format-handler #'tree-widget-format-handler)
96 ;;
97 ;; Basic examples of `tree-widget' usage are provided in this file
98 ;; (see commands `tree-widget-example-1' and `tree-widget-example-2').
99 ;; A more sophisticated example is provided in the dir-tree.el
100 ;; source.
101 ;;
102 ;; Installation
103
104 ;; Put this file on your Emacs-Lisp load path and add following into
105 ;; your ~/.emacs startup file
106 ;;
107 ;;   (require 'tree-widget)
108
109 ;; Support
110 ;;
111 ;; This program is available at <http://www.dponce.com/>. Any
112 ;; comments, suggestions, bug reports or upgrade requests are welcome.
113 ;; Please send them to David Ponce <david@dponce.com>.
114
115 ;;; History:
116 ;; 
117 ;; $Log: jde-tree-widget.el,v $
118 ;; Revision 1.3  2007-12-01 14:30:33  michaels
119 ;; 2007-12-01  Mike Sperber  <mike@xemacs.org>
120 ;;
121 ;;      * jde-tree-widget.el (jde-tree-widget): Add another provide for
122 ;;      jde-tree-widget, so regular require forms outside of this package
123 ;;      work.
124 ;;
125 ;; Revision 1.2  2007/11/30 07:16:45  michaels
126 ;; 2007-11-30  Mike Sperber  <mike@xemacs.org>
127 ;;
128 ;;      * jde-tree-widget.el (jde-tree-widget): Fix typo in provide.
129 ;;
130 ;; Revision 1.1  2007/11/26 15:16:49  michaels
131 ;; Update jde to author version 2.3.5.1.
132 ;;
133 ;; Revision 1.1  2004/06/03 02:17:03  paulk
134 ;; Initial revision.
135 ;;
136 ;; Revision 1.4  2001/11/27 22:13:47  jslopez
137 ;; Adding David's change log entry.
138 ;;
139 ;; Revision 1.3  2001/11/27 20:56:25  jslopez
140 ;; Updates to the latest version. David added a hook
141 ;; to be able to persist the JDEBug local variables tree.
142 ;; (tree-widget-after-toggle-functions): New variable.  Hooks run
143 ;; after toggling a `tree-widget' folding.
144 ;; (tree-widget-toggle-folding): Run above hooks.  Updated doc
145 ;; string.
146 ;; Added pagination.  Minor comment changes.
147 ;;
148 ;; Revision 1.2  2001/10/26 11:20:38  jslopez
149 ;; Removing control characters.
150 ;;
151 ;; Revision 1.1  2001/10/26 06:45:57  paulk
152 ;; Initial revision.
153 ;;
154 ;; Revision 1.5  2001/05/11 23:11:18  ponce
155 ;; Updated version to 1.0.5.
156 ;;
157 ;; Revision 1.4  2001/05/11 23:02:14  ponce
158 ;; (tree-widget-value-create): Fixed a bug when the dynamic tree :dynargs
159 ;; function returns nil (no children).
160 ;;
161 ;; Revision 1.3  2001/03/16 14:23:15  ponce
162 ;; (tree-widget-example-1): removed unused free variable
163 ;; `tree-widget-sample'.
164 ;;
165 ;; Revision 1.2  2001/03/16 14:15:09  ponce
166 ;; (tree-widget-children-value-save): use `tree-widget-node' to get the
167 ;; :node value of widgets.  Check node and node-child values before
168 ;; saving properties.
169 ;;
170 ;; (tree-widget-button-keymap): new variable.  Keymap used inside node
171 ;; handle buttons.
172 ;;
173 ;; (tree-widget-node-handle): use `tree-widget-button-keymap'.
174 ;;
175 ;; (tree-widget-map): new utility function.
176 ;;
177 ;; Revision 1.1  2001/02/19 22:51:23  ponce
178 ;; Initial revision.
179 ;;
180
181 ;;; Code:
182
183 (require 'wid-edit)
184
185 ;;; Customization.
186
187 (defgroup tree-widget nil
188   "Customization support for the Tree Widget Library."
189   :group 'widgets)
190
191 (defcustom tree-widget-node-handle-widget 'tree-widget-node-handle
192   "Widget type used for tree node handle."
193   :type  'symbol
194   :group 'tree-widget)
195
196 (defun tree-widget-get-super (widget property)
197   "Return WIDGET super class PROPERTY value."
198   (widget-get
199    (get (widget-type
200          (get (widget-type widget) 'widget-type))
201         'widget-type)
202    property))
203
204 (defun tree-widget-p (widget)
205   "Return non-nil if WIDGET inherits from a 'tree-widget' widget."
206   (let ((type (widget-type widget)))
207     (while (and type (not (eq type 'tree-widget)))
208       (setq type (widget-type (get type 'widget-type))))
209     (eq type 'tree-widget)))
210
211 (defun tree-widget-keep (arg widget)
212   "Save in ARG the WIDGET properties specified by :keep."
213   (let ((plist (widget-get widget :keep))
214         prop)
215     (while plist
216       (setq prop  (car plist)
217             plist (cdr plist))
218       (widget-put arg prop (widget-get widget prop)))))
219   
220 (defun tree-widget-node (widget)
221   "Return the tree WIDGET :node value.
222 If not found setup a default 'item' widget."
223   (or (widget-get widget :node)
224       ;; Take care of actually return the :node property value.
225       ;; Because FSF Emacs `widget-put' returns the property value and
226       ;; XEmacs one returns the widget value!!!  So don't use thing
227       ;; like this ;-)
228       ;; (or (widget-get widget :node)
229       ;;     (widget-put widget :node node))
230       (let ((node `(item :tag ,(or (widget-get widget :tag)
231                                    (widget-princ-to-string
232                                     (widget-value widget))))))
233         (widget-put widget :node node)
234         node)))
235
236 (defun tree-widget-children-value-save (widget &optional args node)
237   "Save WIDGET children values.
238 Children properties and values are saved in ARGS if non-nil else in
239 WIDGET :args property value.  Data node properties and value are saved
240 in NODE if non-nil else in WIDGET :node property value."
241   (let ((args       (or args (widget-get widget :args)))
242         (node       (or node (tree-widget-node widget)))
243         (children   (widget-get widget :children))
244         (node-child (widget-get widget :tree-widget-node))
245         arg child)
246     (while (and args children)
247       (setq arg      (car args)
248             args     (cdr args)
249             child    (car children)
250             children (cdr children))
251       (cond
252
253        ;; The child is a tree node.
254        ((tree-widget-p child)
255
256           ;; Backtrack :args and :node properties.
257         (widget-put arg :args (widget-get child :args))
258         (widget-put arg :node (tree-widget-node child))
259         
260         ;; Save :open property.
261         (widget-put arg :open (widget-get child :open))
262
263         ;; The node is open.
264         (if (widget-get child :open)
265             (progn
266               ;; Save the widget value.
267               (widget-put arg :value (widget-value child))
268               ;; Save properties specified in :keep.
269               (tree-widget-keep arg child)
270               ;; Save children.
271               (tree-widget-children-value-save
272                child
273                (widget-get arg :args)
274                (widget-get arg :node)))))
275
276         ;; Another non tree node.
277         (t
278          ;; Save the widget value
279          (widget-put arg :value (widget-value child))
280          ;; Save properties specified in :keep.
281          (tree-widget-keep arg child))))
282
283     (cond ((and node node-child)
284            ;; Assume that the node child widget is not a tree!
285            ;; Save the node child widget value.
286            (widget-put node :value (widget-value node-child))
287            ;; Save the node child properties specified in :keep.
288            (tree-widget-keep node node-child)))))
289
290 (defvar tree-widget-after-toggle-functions nil
291   "Hooks run after toggling a `tree-widget' folding.
292 Each function will receive the `tree-widget' as its unique argument.
293 This variable should be local to each buffer used to display
294 widgets.")
295
296 (defun tree-widget-toggle-folding (widget &rest ignore)
297   "Toggle a `tree-widget' folding.
298 WIDGET is a `tree-widget-node-handle-widget' and its parent the
299 `tree-widget' itself.  IGNORE other arguments."
300   (let ((parent (widget-get widget :parent))
301         (open   (widget-value widget)))
302      (if open
303          ;; Before folding the node up, save children values so next
304          ;; open can recover them.
305          (tree-widget-children-value-save parent))
306     (widget-put parent :open (not open))
307     (widget-value-set parent (not open))
308     (run-hook-with-args 'tree-widget-after-toggle-functions parent)))
309
310 (defvar tree-widget-button-keymap
311   (let (parent-keymap mouse-button1 keymap)
312     (if (featurep 'xemacs)
313         (setq parent-keymap  widget-button-keymap
314               mouse-button1 [button1])
315       (setq parent-keymap  widget-keymap
316             mouse-button1 [down-mouse-1]))
317     (setq keymap (copy-keymap parent-keymap))
318     (define-key keymap mouse-button1 #'widget-button-click)
319     keymap)
320   "Keymap used inside node handle buttons.")
321
322 (define-widget 'tree-widget-node-handle 'toggle
323   "Tree node handle widget."
324   :button-keymap  tree-widget-button-keymap ; XEmacs
325   :keymap         tree-widget-button-keymap ; Emacs
326   :format         "%[%v%]"
327   :on             "[+]"
328   :off            "[-]"
329   :notify         #'tree-widget-toggle-folding)
330
331 (define-widget 'tree-widget 'default
332   "Tree node widget."
333   :format         "%v"
334   :convert-widget #'widget-types-convert-widget
335   :value-get      #'widget-value-value-get
336   :value-create   #'tree-widget-value-create
337   :value-delete   #'tree-widget-value-delete
338
339   ;; *---- N          :no-leaf-handle + node
340
341   ;; [-]-, N          node-handle + :open-handle + node
342   ;;     |--- N1      :no-guide + :leaf-handle + node
343   ;;    [-]-, N2      :no-guide + node-handle + :open-handle + node
344   ;;     |  |--- N21  :no-guide + :guide + :leaf-handle + node
345   ;;     |  `--- N22  :no-guide + :guide + :last-leaf-handle + node
346   ;;    [+]-- N3      :no-guide + node-handle + :close-handle + node
347   
348   :no-leaf-handle   "*---- "
349   :close-handle     "-- "
350   :no-guide         "   "
351   :open-handle      "-, "
352   :guide            " | "
353   :leaf-handle      " |--- "
354   :last-leaf-handle " `--- ")
355
356 (defun tree-widget-format-handler (widget escape)
357   "Convenient %p format handler to insert a leaf node prefix.
358 WIDGET is a tree leaf node and ESCAPE a format character."
359   (cond
360
361    ;; If %p format insert the leaf node prefix.
362    ((eq escape ?p)
363     (if (widget-get widget :indent)
364         (insert-char ?  (widget-get widget :indent)))
365     (insert
366      (or (widget-get widget :tree-widget-leaf-handle)
367          "")))
368    
369    ;; For other ESCAPE values call the WIDGET super class format
370    ;; handler.
371    (t
372     (let ((handler (tree-widget-get-super widget :format-handler)))
373       (if handler
374           (funcall handler widget escape))))))
375
376 (defun tree-widget-value-delete (widget)
377   "Delete tree WIDGET children."
378   ;; Delete children
379   (widget-children-value-delete widget)
380   ;; Delete node child
381   (widget-delete (widget-get widget :tree-widget-node))
382   (widget-put widget :tree-widget-node nil))
383
384 (defun tree-widget-value-create (widget)
385   "Create the tree WIDGET children."
386   (let ((args (widget-get widget :args))
387         (open (widget-get widget :open))
388         (node (tree-widget-node widget))
389         children buttons prefix)
390     
391     (cond
392
393      ;; Leaf node.
394      ((not (or args
395                ;; Take care of dynamic tree.  If :has-children is
396                ;; non-nil let a chance to open the node later.  So
397                ;; don't consider it as a leaf node even if it has not
398                ;; (yet) any children.
399                (and (widget-get widget :dynargs)
400                     (widget-get widget :has-children))))
401       
402       (insert (or (widget-get widget :tree-widget-leaf-handle)
403                   (widget-get widget :no-leaf-handle)))
404       (widget-put widget :tree-widget-node
405                   (widget-create-child-and-convert widget node)))
406
407      ;; Unfolded node.
408      (open
409
410       ;; Maybe the tree is dynamic.
411       (if (widget-get widget :dynargs)
412           (let ((newargs
413                  ;; Request the definition of children.
414                  (funcall (widget-get widget :dynargs) widget)))
415             ;; Maybe reuse definition from the :args cache.
416             (or (eq args newargs)
417                 ;; Otherwise setup a new :args cache.
418                 (widget-put
419                  widget :args
420                  (setq args (mapcar #'widget-convert newargs))))))
421       
422       (setq buttons
423             (cons (widget-create-child-and-convert
424                    widget tree-widget-node-handle-widget
425                    :value nil :help-echo "Hide node")
426                   buttons))
427       (insert (widget-get widget (if args
428                                      :open-handle
429                                    :close-handle)))
430       (widget-put widget :tree-widget-node
431                   (widget-create-child-and-convert widget node))
432       (setq prefix
433             (concat (or (widget-get widget :tree-widget-prefix) "")
434                     (or (widget-get widget :tree-widget-guide)
435                         (widget-get widget :no-guide))))
436       (if (null args)
437           nil
438         (while (cdr args)
439           (insert prefix)
440           (setq children
441                 (cons (widget-create-child-and-convert
442                        widget (car args)
443                        :tree-widget-prefix prefix
444                        :tree-widget-guide (widget-get widget :guide)
445                        :tree-widget-leaf-handle
446                        (widget-get widget :leaf-handle))
447                       children)
448                 args (cdr args)))
449         ;; The last non tree child uses the :last-leaf-handle.
450         (insert prefix)
451         (setq children
452               (cons (widget-create-child-and-convert
453                      widget (car args)
454                      :tree-widget-prefix prefix
455                      :tree-widget-leaf-handle
456                      (widget-get widget :last-leaf-handle))
457                     children))))
458
459      ;; Folded node.
460      (t
461       
462       (setq buttons
463             (cons
464              (widget-create-child-and-convert
465               widget tree-widget-node-handle-widget
466               :value t :help-echo "Show node")
467             buttons))
468       (insert (widget-get widget :close-handle))
469       (widget-put widget :tree-widget-node
470                   (widget-create-child-and-convert widget node))))
471     
472     (widget-put widget :children (nreverse children))
473     (widget-put widget :buttons  buttons)))
474
475 ;;;;
476 ;;;; Utilities
477 ;;;;
478
479 (defun tree-widget-map (widget fun)
480   "For each WIDGET displayed child call function FUN.
481 FUN is called with three arguments like this:
482
483  (FUN CHILD IS-NODE WIDGET)
484
485 where:
486 - - CHILD is the child widget.
487 - - IS-NODE is non-nil if CHILD is WIDGET node widget."
488   (if (widget-get widget :tree-widget-node)
489       (let ((children (widget-get widget :children))
490             child)
491         (funcall fun (widget-get widget :tree-widget-node)
492                  t widget)
493         (while children
494           (setq child    (car children)
495                 children (cdr children))
496           (if (tree-widget-p child)
497               ;; The child is a tree node.
498               (tree-widget-map child fun)
499             ;; Another non tree node.
500             (funcall fun child nil widget))))))
501
502 ;;;;
503 ;;;; Samples
504 ;;;;
505
506 ;;; Compatibility
507
508 (cond ((featurep 'xemacs)
509
510        (defalias 'tree-widget-sample-overlay-lists
511          (lambda () (list (extent-list))))
512        (defalias 'tree-widget-sample-delete-overlay 'delete-extent))
513
514       (t
515        
516        (defalias 'tree-widget-sample-overlay-lists 'overlay-lists)
517        (defalias 'tree-widget-sample-delete-overlay 'delete-overlay)))
518
519 (defun tree-widget-example-1 ()
520   "A simple usage of the `tree-widget'."
521   (interactive)
522   (switch-to-buffer "*`tree-widget' example 1*")
523   (kill-all-local-variables)
524   (let ((inhibit-read-only t))
525     (erase-buffer))
526   (let ((all (tree-widget-sample-overlay-lists)))
527     (mapcar #'tree-widget-sample-delete-overlay (car all))
528     (mapcar #'tree-widget-sample-delete-overlay (cdr all)))
529
530   (widget-insert (format "%s. \n\n" (buffer-name)))
531
532   (widget-create
533    ;; Open this level.
534    'tree-widget :open t
535    ;; Use a push button for this node.
536    :node '(push-button
537            :tag "Root"
538            :format "%[%t%]\n"
539            :notify
540            (lambda (&rest ignore)
541              (message "This is the Root node")))
542    ;; Add subtrees (their nodes defaut to items).
543    '(tree-widget :tag "Child-1")
544    '(tree-widget :tag "Child-2"
545                  (tree-widget :tag "Child-2.1")
546                  (tree-widget :tag "Child-2.2"
547                               (tree-widget :tag "Child-2.2.1")
548                               (tree-widget :tag "Child-2.2.2")))
549    '(tree-widget :tag "Child-3"
550                  (tree-widget :tag "Child-3.1")
551                  (tree-widget :tag "Child-3.2")))
552   
553   (use-local-map widget-keymap)
554   (widget-setup))
555
556 (defun tree-widget-example-2-dynargs (widget)
557   "Return the children definitions of WIDGET.
558 Reuse the cached :args property value if exists."
559   (or (widget-get widget :args)
560       '((tree-widget :tag "Child-2.1")
561         (tree-widget :tag "Child-2.2"
562                      (tree-widget :tag "Child-2.2.1")
563                      (tree-widget :tag "Child-2.2.2")))))
564   
565 (defun tree-widget-example-2 ()
566   "A simple usage of the `tree-widget' with dynamic expansion."
567   (interactive)
568   (switch-to-buffer "*`tree-widget' example 2*")
569   (kill-all-local-variables)
570   (let ((inhibit-read-only t))
571     (erase-buffer))
572   (let ((all (tree-widget-sample-overlay-lists)))
573     (mapcar #'tree-widget-sample-delete-overlay (car all))
574     (mapcar #'tree-widget-sample-delete-overlay (cdr all)))
575
576   (widget-insert (format "%s. \n\n" (buffer-name)))
577
578   (widget-create
579    ;; Open this level.
580    'tree-widget :open t
581    ;; Use a push button for this node.
582    :node '(push-button
583            :tag "Root"
584            :format "%[%t%]\n"
585            :notify
586            (lambda (&rest ignore)
587              (message "This is the Root node")))
588    ;; Add subtrees (their nodes defaut to items).
589    '(tree-widget :tag "Child-1")
590    ;; Dynamically retrieve children of this node.
591    '(tree-widget :tag "Child-2"
592                  :dynargs tree-widget-example-2-dynargs
593                  :has-children t)
594    '(tree-widget :tag "Child-3"
595                  (tree-widget :tag "Child-3.1")
596                  (tree-widget :tag "Child-3.2")))
597   
598   (use-local-map widget-keymap)
599   (widget-setup))
600
601 (provide 'jde-tree-widget)
602 (provide 'tree-widget)
603
604 ;;; jde-tree-widget.el ends here