;;;;
;;;; c l a s s - b r o w s e r . s t k l o s 		-- Class browser
;;;;
;;;; Copyright © 1998-1999 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
;;;; 
;;;; 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: 25-Aug-1998 20:12
;;;; Last file update:  3-Sep-1999 19:49 (eg)

(require "Tk-classes")
(require "method-editor")

(define-module class-browser
  (import STklos STklos+Tk Tk)
  (export class-browser browse-class)

  (define *browser-current*   #f)
  (define *browser-notepad*   #f)
  (define *browser-tree*      #f)
  (define *browser-window*    #f)

  (define-class <Inheritance-item> (<Hierarchy-item>)  ())

  (define-class <Inheritance-tree> (<Hierarchy-tree>)
    ((entry         :init-form #f)
     (items-type    :init-form <Inheritance-item>)))

  (define-method open-item((self <Inheritance-item>))
    (unless (slot-ref self 'open)
      (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)))
		    (sort subclasses
			  (lambda (x y) 
			    (string<? (symbol->string (class-name x))
				      (symbol->string (class-name y))))))))
      ;; Do the redisplay
      (next-method)))

  (define-method close-item((self <Inheritance-item>))
    (slot-set! self 'children '())
    (next-method))

  (define-method label-item((self <Inheritance-item>))
    (let* ((data (slot-ref self 'data))
	   (len  (length (class-direct-supers data)))
	   (name (class-name data)))
      (if (> len 2)
	  (format #f "~A : ~A" name len) ; class has multiple super-classes 
	  name)))

  (define-method select-item ((self <Inheritance-item>))
    (let* ((data   (slot-ref self 'data))
	   (parent (slot-ref self 'parent))
	   (entry  (slot-ref parent 'entry)))
      (unless (equal? data *browser-current*)
	(set! *browser-current* data)
	;; Update all the listboxes with information relative to the current class
	(select-tab (current-tab *browser-notepad*))
	(slot-set! entry  'value (class-name data))
	(next-method))))

  (define (create-hierarchy top)
    (let* ((f (make <Frame> :parent top))
	   (e (make <Labeled-Entry> :parent f :title "Current Class"
		    :relief "ridge" :border-width 2))
	   (h (make <Inheritance-tree> :parent f :relief "raised" :border-width 2)))
      (pack e :expand #f :fill "x" :padx 3 :pady 3)
      (pack h :expand #t :fill "both")
      (slot-set! h 'entry e)
      (add-node h #f <top>)
      (set! *browser-tree* h)
      f))

  ;=============================================================================

  (define (create-notepad toplevel)
    (define lbs   (make-vector 8))
    (define n     (make <Notepad> :parent toplevel))
    (define index 0)

    (define (create-listbox parent txt index)
      (let* ((f  (make <Frame> :parent parent))
	     (l  (make <Label> :parent f :text txt))
	     (lb (make <Scroll-listbox> :parent f)))
	(set! (background (listbox-of lb)) "white")
	(pack l :fill "x" :expand #f)
	(pack lb :fill "both" :expand #t)
	(vector-set! lbs index lb)
	f))

    (define (internal-page index txt1 txt2 proc1 proc2)
      (lambda (parent tab)
	;; If this is the first call for this page, instanciante it
	(unless (page tab)
	  (let* ((paned (make <HPaned> :parent parent))
		 (f1    (create-listbox (top-frame-of paned)    txt1 index))
		 (f2    (create-listbox (bottom-frame-of paned) txt2 (+ index 1))))
	    (pack f1 f2 :expand #t :fill "both")
	    (set! (page tab) paned)))
	;; Executed each time we select this page
	(pack (page tab) :pady 5 :padx 3 :fill "both" :expand #t)
	(when *browser-current*
	  (proc1) 		;; To fill the upper part of the page
	  (proc2)))) 		;; ........... lower ................

    (define (new label txt1 txt2 proc1 proc2)
      (let ((index (- index 2))) ; UGLY! we know index has been ++ by fill-lisbox
	(make  <Notepad-Tab> 
	       :parent n :text label :font '(Helvetica 10 Bold)  
	       :action (internal-page index txt1 txt2 proc1 proc2))))

    (define convert-method
      (lambda (m)
	(format #f "~A ~S" (generic-function-name(method-generic-function m))
		(map* class-name (method-specializers m)))))

    (define (sort-symbols l)
      (sort l (lambda (s1 s2) 
		(string<? (symbol->string (cdr s1)) (symbol->string (cdr s2))))))
    
    (define (sort-strings l)
      (sort l (lambda (s1 s2) (string<? (cdr s1) (cdr s2)))))

    (define (show-slot s)
      (let* ((top  (make <Toplevel> :title "Slot Description"))
	     (edit (make <Scheme-text> :parent top :relief "ridge" :border-width 3))
	     (quit (make <Button> :parent top :text "Quit" 
			 :command (lambda () (destroy top)))))
	(set! (value edit) (format #f "Slot ``~A'' is defined as:\n\n~A" 
				   (slot-definition-name s) (pp s 50 #f)))
	(set! (background (text-of edit)) "white")
	;; change title to differentiate it from the body
	(tag-add (make <Text-tag> :parent edit :underline #t :foreground "brown3")
		 "0.0" "2.0")
	;; pack everybody
	(pack edit :expand #t :fill   'both)
	(pack quit :expand #f :anchor 'w :ipadx 20)))


    (define (fill-listbox convert build-list sortproc selectproc)
      (let* ((idx index)
	     (res (lambda ()
		    (let* ((lb (vector-ref lbs idx))
			   (l1 (build-list  *browser-current*))
			   (l2 (map convert l1))
			   (l3 (sortproc (map cons l1 l2))))
		      ;; l1 is the list of objects. l2 is the list of the names of
		      ;; these objects (to be inserted in the listbox). l3 is the 
		      ;; a A-list built from l1 and l2. l3 is sorted on the values 
		      ;; of l2.
		      (bind lb "<Double-1>"
			    (lambda ()
			      (let ((active (listbox-index lb "active")))
				(selectproc (car (list-ref l3 active))))))
		      (set! (value lb) (map cdr l3))))))
	(set! index (+ index 1))
	res))

    ;; 
    ;; Create-notepad starts here
    ;;
    (new "Super\nClasses" "Direct Super Classes" "Class Precedence List"
	 (fill-listbox class-name class-direct-supers sort-symbols   browse-class)
	 (fill-listbox class-name class-precedence-list (lambda(l)l) browse-class))

    (new  "Sub\nClasses" "Direct Subclasses" "All Subclasses"
	  (fill-listbox class-name class-direct-subclasses sort-symbols 
			browse-class)
	  (fill-listbox class-name class-subclasses sort-symbols browse-class))
    
    (new  "Slots" "Direct Slots" "All Slots"
	  (fill-listbox slot-definition-name class-direct-slots sort-symbols 
			show-slot)
	  (fill-listbox slot-definition-name class-slots sort-symbols show-slot))

    (new  "Methods" "Direct Methods" "All Methods"
	  (fill-listbox convert-method class-direct-methods sort-strings 
			method-editor)
	  (fill-listbox convert-method class-methods sort-strings method-editor))

    (set! *browser-notepad* n)
    n)

  ;=============================================================================

  (define (init-class-browser . parent)
    (let* ((top   (if (null? parent)
		      (make <Toplevel> :title "** STklos Class browser **")
		      (car parent)))		  
	   (paned (make <VPaned> :parent top :width 700 :height 400 :fraction .4))
	   (h	  (create-hierarchy (left-frame-of  paned)))
	   (pad   (create-notepad   (right-frame-of paned)))
	   (quit  (make <Button> :parent top :text "Quit" 
				 :command (lambda() (destroy top)))))

      (pack h pad paned :fill 'both :expand #t)
      (pack quit :anchor 'w :ipadx 20)
      (wm 'protocol (Id top) "WM_DELETE_WINDOW" (lambda () 
						  (set! *browser-window* #f) 
						  (destroy top)))
      (set! *browser-window* top)))

  (define (browse-class class)
    (unless *browser-window* (init-class-browser))
    (let* ((cpl (reverse (class-precedence-list class)))
	   (proc (lambda (i)
		   (let ((data (slot-ref i 'data)))
		     (when (memq data cpl)
		       (open-item i)
		       (if (eq? data class) (select-item i)))))))
      (walk-hierarchy *browser-tree* proc)))

  (define (class-browser . parent)
    (apply init-class-browser parent)
    (browse-class <top>))
)

(import class-browser)

(define class-browser (with-module class-browser class-browser))

(provide "class-browser")