;;;; ;;;; H i e r a r c h y . s t k l o s -- Hierachy management widget ;;;; ;;;; Copyright © 1998-1999 Erick Gallesio - I3S-CNRS/ESSI ;;;; ;;;; Permission to use, copy, modify, distribute,and license this ;;;; software and its documentation for any purpose is hereby granted, ;;;; provided that existing copyright notices are retained in all ;;;; copies and that this notice is included verbatim in any ;;;; distributions. No written agreement, license, or royalty fee is ;;;; required for any of the authorized uses. ;;;; This software is provided ``AS IS'' without express or implied ;;;; warranty. ;;;; ;;;; Author: Erick Gallesio [eg@unice.fr] ;;;; Creation date: 19-Aug-1998 14:02 ;;;; Last file update: 15-Sep-1999 23:15 (eg) (require "Tk-classes") (select-module STklos+Tk) (export add-item add-node add-leave walk-hierarchy ;; methods label-item select-item open-item close-item ;; methods remove-item!) (define *hierarchy-font* '(Helvetica 12)) (define *hierarchy-y* 0) (define *hierarchy-node* (make-image "dir.gif")) (define *hierarchy-open-node* (make-image "diropen.gif")) (define *hierarchy-leave* (make-image "file.gif")) (define *hierarchy-box-plus* (make-image "box-plus.gif")) (define *hierarchy-box-minus* (make-image "box-minus.gif")) ;============================================================================= ; ; < H i e r a r c h y - i t e m > ; ;============================================================================= (define-class () ((parent :init-keyword :parent) (ancestor :init-keyword :ancestor) (data :init-keyword :data) (node? :init-keyword :node?) (icon :init-keyword :icon :init-form #f) (children :init-form '()) (label-id :init-form #f) (open :init-form #f))) (define-method initialize ((self ) initargs) (next-method) (let ((ancestor (slot-ref self 'ancestor))) ;; Update the children list of the ancestor of this item (if ancestor (slot-set! ancestor 'children (append (slot-ref ancestor 'children) (list self)))))) (define-method label-item((self )) (slot-ref self 'data)) (define-method select-item ((self )) (let* ((parent (slot-ref self 'parent)) (old-selection (slot-ref parent 'selection)) (old-data (and old-selection (slot-ref old-selection 'data))) (data (slot-ref self 'data)) (scan-proc (lambda (i) (let ((D (slot-ref i 'data))) (if (and old-selection (eq? D old-data)) (set-item-background parent i #f)) (if (eq? D data) (set-item-background parent i #t)))))) (unless (eq? old-data data) (walk-hierarchy parent scan-proc) (slot-set! parent 'selection self)))) (define-method open-item ((self )) (slot-set! self 'open #t) (if (eq? (slot-ref self 'icon) *hierarchy-node*) (slot-set! self 'icon *hierarchy-open-node*)) (maybe-update-hierarchy (slot-ref self 'parent))) (define-method close-item ((self )) (slot-set! self 'open #f) (if (eq? (slot-ref self 'icon) *hierarchy-open-node*) (slot-set! self 'icon *hierarchy-node*)) (maybe-update-hierarchy (slot-ref self 'parent))) ;============================================================================= ; ; < H i e r a r c h y - t r e e > ; ;============================================================================= (define-class () ((%redisplay :init-form #f) (class :init-keyword :class :init-form "HierarchyTree") (root :init-form #f) (items-type :init-form :keyword :items-type) (selection :init-form #f))) (define-method initialize ((self ) initargs) (next-method) (set! (background (canvas-of self)) "white") ;; Set the canvas bg to white (unless (member :h-scroll-side initargs) ;; no horiz scrollbar (set! (h-scroll-side self) #f)) ;; Create the root of the hierarchy (slot-set! self 'root (add-item self #f #f #t))) ;; self, ancestor, data, node ;;;; ;;;; add-item -- add an item in the hierarchy (low level. See ;;;; add-leave and add-node for higher level functions) ;;;; (define-method add-item ((self ) ancestor data node? icon) (let* ((ancestor (or ancestor (slot-ref self 'root))) (class (slot-ref self 'items-type)) (res (make class :parent self :ancestor ancestor :data data :node? node? :icon icon))) (maybe-update-hierarchy self) res)) (define-method add-item ((self ) ancestor data node?) (add-item self ancestor data node? #f)) ;;==== add-leave (define-method add-leave ((self ) ancestor data) (add-item self ancestor data #f *hierarchy-leave*)) ;;==== add-node (define-method add-node ((self ) ancestor data) (add-item self ancestor data #t *hierarchy-node*)) ;;;; ;;;; remove-item! -- remove an item in the hierarchy ;;;; (define-method remove-item! ((self )) (let* ((parent (slot-ref self 'parent)) (ancestor (slot-ref self 'ancestor)) (children (slot-ref ancestor 'children))) (slot-set! ancestor 'children (remove self children)) (maybe-update-hierarchy parent))) ;;;; ;;;; Internal use methods ;;;; ;; ;; set-item-background -- ;; (define-method set-item-background ((self ) item selected?) (let ((bg (if selected? "red" "black")) (item (slot-ref item 'label-id))) ((Id self) 'itemconfigure item :fill bg))) ;; ;; maybe-update-hierarchy -- retain that a hier. must be updated (if necessary) ;; (define (maybe-update-hierarchy h) (unless (slot-ref h '%redisplay) ;; No redisplay resquested yet. Add a request (slot-set! h '%redisplay (after 'idle (lambda () (update-hierarchy h) (slot-set! h '%redisplay #f)))))) ;; ;; update-hierarchy -- display the current hierarchy ;; (define-method update-hierarchy ((self )) ;; Delete all items in the hierarchy (canvas-delete self 'all) ;; Display something in 0,0 otherwise the canvas is not centered on the origin ;; when we finish display. Why? ((Id self) 'create 'line 0 0 0 0 :fill (background self)) (set! *hierarchy-y* 0) (update-a-hierarchy-level self (slot-ref self 'root) 10) ;; Update the scroll region of the canvas (let ((bbox (bounding-box self 'all))) (unless (null? bbox) (set! (scroll-region self) bbox))) ;; Display the selection (let* ((selection (slot-ref self 'selection)) (data (and selection (slot-ref selection 'data))) (proc (lambda (i) (if (eq? (slot-ref i 'data) data) (set-item-background self i #t))))) (when selection (walk-hierarchy self proc)))) ;; ;; update-a-hierarchy-level -- display a hierachy level (recusrive cal if ;; -- a folder is encountered). Dont call it directly ;; (define-method update-a-hierarchy-level ((self ) item indent) (define w (Id self)) ;; WARNING: We don't use Tk tags for bindings here because we use a ;; lot of bindings for each layer of the tree (several per item in fact). ;; When using tags, Tk doesn't delete the bindings when canvas items ;; are deleted but when the canvas itself is deleted. This consumes a lot ;; of memory for big hierarchies which are opened and closed several time. (define (create-icon-n-label w i x y icon) (let* ((label (label-item i)) (add-bind (lambda (who item) (bind self who "" (lambda () (open-item i))) (bind self who "<1>" (lambda () (select-item i))))) (item (w 'create 'text x y :text label :anchor 'w :font *hierarchy-font*))) (when icon (let ((icon (w 'create 'image (- x 4) y :image icon :anchor 'w))) (add-bind icon item) (w 'move item 16 0))) (slot-set! i 'label-id item) (add-bind item item))) (define (create-box w i x y open) (if open (let ((img (w 'create 'image x y :image *hierarchy-box-minus*))) (update-a-hierarchy-level self i (+ indent 18)) (bind self img "<1>" (lambda() (close-item i)))) (let ((img (w 'create 'image x y :image *hierarchy-box-plus*))) (bind self img "<1>" (lambda() (open-item i)))))) (define (display-item i) (let ((node? (slot-ref i 'node?)) (icon (slot-ref i 'icon)) (x (+ indent 15)) (y (+ (* 20 *hierarchy-y*) 20))) (set! *hierarchy-y* (+ *hierarchy-y* 1)) (w 'create 'line indent y (+ indent 15) y :fill "gray50") (create-icon-n-label w i x y icon) (when node? (create-box w i indent y (slot-ref i 'open))))) ;;============================ (let ((initial-y *hierarchy-y*) (last-y 0)) (for-each (lambda (item) (set! last-y *hierarchy-y*) (display-item item)) (slot-ref item 'children)) ;; Draw the vertical line linking siblings (unless (= initial-y *hierarchy-y*) (let ((line (w 'create 'line indent (+ (* 20 initial-y) 7) indent (* 20 (+ last-y 1)) :fill "gray50"))) ((Id self) 'lower line))))) (define-method walk-hierarchy ((self ) proc) (let Loop ((item (slot-ref self 'root))) (proc item) (if (slot-ref item 'node?) (for-each Loop (slot-ref item 'children))))) (provide "Hierarchy") ;============================================================================= #| Simple usage of the class: (define T (make )) (pack T :expand #t :fill "both") (define d1 (add-node T #f "dir1")) (define d2 (add-node T #f "dir2")) (define d3 (add-node T d1 "dir3")) (add-leave T d1 "file2") (add-leave T d1 "file1") (add-leave T d3 "file3") (add-leave T d2 "file4") Inheritance hierarchy Another example where we make a subclass of and . The hierarchy tree must define the slot item-types which contain the type of the items of the hierarchy. Furthermore, open-item and close-item have been redefined to ensure that the the subtree is cleaned when we close a node (i.e. re-opening a closed node will query its *actual* leaves, even if they have changed). (define-class () ()) (define-class () ((items-type :init-form ))) (define-method open-item((self )) (let* ((data (slot-ref self 'data)) (children (slot-ref self 'children)) (hierarchy (slot-ref self 'parent)) (subclasses (class-direct-subclasses data))) (when (and (null? children) (not (null? subclasses))) (for-each (lambda (x) (if (null? (class-direct-subclasses x)) (add-leave hierarchy self x) (add-node hierarchy self x))) subclasses))) ;; Do the redisplay (next-method)) (define-method close-item((self )) (slot-set! self 'children '()) (next-method)) (define-method label-item((self )) (class-name (slot-ref self 'data))) (define T (make )) (pack T :expand #t :fill "both") (add-node T #f ) File Hierarchy In this example, we don't redefine close-item => we have a cache of the hierarchy (which can be erroneous if disk state has changed) (define-class () ()) (define-class () ((items-type :init-form ))) (define-method open-item((self )) (let* ((data (slot-ref self 'data)) (children (slot-ref self 'children)) (hierarchy (slot-ref self 'parent))) (when (null? children) ;; This is the first time that we open this item and it has children (for-each (lambda (x) (if (file-is-directory? x) (add-node hierarchy self x) (add-leave hierarchy self x))) (sort (glob (string-append data "/*")) string)) (let ((name (slot-ref self 'data))) (if (string=? name "/") "/" (basename name)))) (define-method select-item ((self )) (let ((name (slot-ref self 'data))) (unless (file-is-directory? name) (system (string-append "xedit " name " &")))) (next-method)) (define T (make )) (pack T :expand #t :fill "both") (add-node T #f "/") |#