;; ################################################################################## ;; # ============================================================================== # ;; # Graph Editor # ;; # http://www.hexahedron.hu/private/peteri/ # ;; # Copyright (C) Peter Ivanyi, 2007 # ;; # ============================================================================== # ;; # # ;; # This program is free software; you can redistribute it and/or # ;; # modify it under the terms of the GNU General Public License # ;; # as published by the Free Software Foundation; either version 2 # ;; # of the License, or (at your option) any later version. # ;; # # ;; # This program is distributed in the hope that it will be useful, # ;; # but WITHOUT ANY WARRANTY; without even the implied warranty of # ;; # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # ;; # GNU General Public License for more details. # ;; # # ;; # You should have received a copy of the GNU General Public License # ;; # along with this program; if not, write to the Free Software # ;; # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # ;; # # ;; ################################################################################## (module graph-editor mzscheme (require (lib "class.ss") (lib "mred.ss" "mred") (lib "list.ss") ) (define tab-width 10) (define tab-height 10) (define node-width 60) (define node-height 25) (define node-buffer 5) (define node-color (make-object color% 91 91 184)) (define white-color (make-object color% "white")) (provide line%) (define line% (class object% (init-field (source #f) ; stores the source tab object (target #f) ; stores the destination tab object (editor #f) ) ; user defined data structure ; if the user wants to store anything in the node (define data #f) (public get-data) (define (get-data) data ) (public set-data) (define (set-data dat) (set! data dat) ) (define visible? #f) (public is-shown?) (define (is-shown?) visible? ) (public get-source) (define (get-source) source ) (public get-target) (define (get-target) target ) (define (draw show?) (set! visible? show?) (let ((dc (send editor get-dc)) (src-type (send source get-type)) (dst-type (send target get-type)) ) (if show? (send dc set-pen "black" 1 'solid) (send dc set-pen "white" 1 'solid) ) (let-values (((sx sy) (send source get-position)) ((tx ty) (send target get-position)) ) (cond ((equal? src-type 'out) (set! sy (+ sy tab-height -1))) ((equal? src-type 'in) (set! sy (- sy tab-height)))) (cond ((equal? dst-type 'out) (set! ty (+ ty tab-height -1))) ((equal? dst-type 'in) (set! ty (- ty tab-height)))) (send dc draw-line sx sy tx ty) ) ) ) (public show) (define (show) (draw #t) ) (public hide) (define (hide) (draw #f) ) (super-new) ) ) ; ---------------------------------------------------------------------------- ; tab ; ---------------------------------------------------------------------------- (provide tab%) (define tab% (class object% (init-field (x 0) (y 0) (type #f) ; can be in or out (node #f) (editor #f) ) (define visible? #f) ; all lines connected to this tab (define lines '()) (public line-add) (define (line-add line) (set! lines (cons line lines)) ) (public line-del) (define (line-del line) (set! lines (remove line lines)) ) (public get-lines) (define (get-lines) (if (and (equal? type 'in) (> (length lines) 1)) (error "too many lines connected to an in tab") ) lines ) (public connected?) (define (connected?) (if (> (length lines) 0) #t #f ) ) (public is-shown?) (define (is-shown?) visible? ) (public get-type) (define (get-type) type ) (public get-node) (define (get-node) node ) (public x-set!) (define (x-set! cx) (set! x cx) ) (public y-set!) (define (y-set! cy) (set! y cy) ) (public get-position) (define (get-position) (values x y) ) (public get-line-position) (define (get-line-position) (if (equal? type 'in) (values x (- y tab-height)) (values x (+ y tab-height -1)) ) ) (define (draw show?) (set! visible? show?) (let ((dc (send editor get-dc))) (if show? (send dc set-pen "black" 1 'solid) (send dc set-pen "white" 1 'solid) ) (send dc set-brush node-color 'solid) (cond ((equal? type 'in) (send dc draw-rectangle (- x (* tab-width 0.5)) (+ (- y tab-height) 0) tab-width tab-height) (send dc set-pen "white" 1 'solid) (send dc draw-line (- x (* tab-width 0.5)) (+ (- y tab-height) 0) (- x (* tab-width 0.5)) (+ (- y tab-height) tab-height)) (send dc draw-line (- x (* tab-width 0.5)) (+ (- y tab-height) 0) (+ (- x (* tab-width 0.5)) tab-width) (+ (- y tab-height) 0)) ) ((equal? type 'out) (send dc draw-rectangle (- x (* tab-width 0.5)) (- y 0) tab-width tab-height) (send dc set-pen "white" 1 'solid) (send dc draw-line (- x (* tab-width 0.5)) (- y 0) (- x (* tab-width 0.5)) (+ y tab-height)) (send dc draw-line (- x (* tab-width 0.5)) (- y 0) (+ (- x (* tab-width 0.5)) tab-width) (- y 0)) ) ) (for-each (lambda (lin) (if show? (send lin show) (send lin hide) ) ) lines ) ) ) (public show) (define (show) (draw #t) ) (public hide) (define (hide) (draw #f) ) (public on-mouse) (define (on-mouse etype cx cy) (cond ((member etype '(left-down left-up)) (cond ((and (equal? type 'in) (<= (- x (* tab-width 0.5)) cx (+ x (* tab-width 0.5))) (<= (- y tab-height) cy y)) this ) ((and (equal? type 'out) (<= (- x (* tab-width 0.5)) cx (+ x (* tab-width 0.5))) (<= y cy (+ y tab-height))) this ) (else #f) ) ) (else #f) ) ) (super-new) ) ) ; ---------------------------------------------------------------------------- ; node ; ---------------------------------------------------------------------------- (provide node%) (define node% (class object% (init-field (id #f) ;id of the node in the graph editor (name #f) ; name to display (x 0) ; center x coordinate of the rectangle (y 0) ; center y coordinate of the rectangle (editor #f) (style '()) ) (define tab-in (make-hash-table 'equal)) (define tab-out (make-hash-table 'equal)) (define width 0) (define height 0) (define offset 5) (define visible? #f) ; user defined data structure ; if the user wants to store anything in the node (define data #f) (public get-data) (define (get-data) data ) (public set-data) (define (set-data dat) (set! data dat) ) (public get-id) (define (get-id) id ) (public get-name) (define (get-name) name ) (public set-name) (define (set-name str) (hide #f) (set! name str) (set! width (get-real-width)) (show #f) (send editor layout id) ) (public get-x) (define (get-x) x ) (public get-y) (define (get-y) y ) (public get-style) (define (get-style) style ) (public is-shown?) (define (is-shown?) visible? ) (define (get-real-width) (let ((dc (send editor get-dc))) (let-values (((tw th td ta) (send dc get-text-extent name #f #f 0))) (max 70 (+ (* offset 2) tw)) ) ) ) (define (get-real-height) (let ((dc (send editor get-dc))) (let-values (((tw th td ta) (send dc get-text-extent name #f #f 0))) (max 30 (+ (* offset 2) ta td th)) ) ) ) (public x-set!) (define (x-set! cx) (set! x cx) (let* ((n (- (hash-table-count tab-in) 1)) (req-width (* (+ n n 3) tab-width)) (left (* (- width req-width) 0.5)) (sx (+ (- x (* width 0.5)) left (* 1.5 tab-width))) ) (do ((i 0 (+ i 1))) ((= i (hash-table-count tab-in))) (let ((tab (hash-table-get tab-in i))) (send tab x-set! sx) (set! sx (+ sx (* 2 tab-width))) ) ) ) (let* ((n (- (hash-table-count tab-out) 1)) (req-width (* (+ n n 3) tab-width)) (left (* (- width req-width) 0.5)) (sx (+ (- x (* width 0.5)) left (* 1.5 tab-width))) ) (do ((i 0 (+ i 1))) ((= i (hash-table-count tab-out))) (let ((tab (hash-table-get tab-out i))) (send tab x-set! sx) (set! sx (+ sx (* 2 tab-width))) ) ) ) ) (public y-set!) (define (y-set! cy) (set! y cy) (do ((i 0 (+ i 1))) ((= i (hash-table-count tab-in))) (let ((tab (hash-table-get tab-in i))) (send tab y-set! (- y (/ height 2.0))) ) ) (do ((i 0 (+ i 1))) ((= i (hash-table-count tab-out))) (let ((tab (hash-table-get tab-out i))) (send tab y-set! (+ y (/ height 2.0))) ) ) ) (public get-position) (define (get-position) (values x y) ) (public get-size) (define (get-size) (values width height) ) (public tab-in-count) (define (tab-in-count) (hash-table-count tab-in) ) (public tab-out-count) (define (tab-out-count) (hash-table-count tab-out) ) (public tab-in-ref) (define (tab-in-ref i) (if (>= i 0) (hash-table-get tab-in i #f) ) ) (public tab-out-ref) (define (tab-out-ref i) (if (>= i 0) (hash-table-get tab-out i #f) ) ) (public tab-in-connected?) (define (tab-in-connected?) (tab-connected? tab-in) ) (public tab-out-connected?) (define (tab-out-connected?) (tab-connected? tab-out) ) ; check whether any of the tabs is connected (define (tab-connected? tab-list) (let ((connected? #f) (n (hash-table-count tab-list)) ) (do ((i 0 (+ i 1))) ((or connected? (= i n))) (set! connected? (or connected? (send (hash-table-get tab-list i #f) connected?))) ) connected? ) ) (public tab-in-decr) (define (tab-in-decr) (tab-decr tab-in 'in) ) (public tab-out-decr) (define (tab-out-decr) (tab-decr tab-out 'out) ) (define (tab-decr tab-list type) (let* ((n (hash-table-count tab-list))) (if (> n 0) (let ((last (hash-table-get tab-list (- n 1)))) (if (not (send last connected?)) (let ((n (- n 1)) (ni (hash-table-count tab-in)) (no (hash-table-count tab-out)) (max-width #f) (req-width (* (+ n n 0) tab-width)) ) (hide #f) (hash-table-remove! tab-list n) (if (equal? type 'in) (set! max-width (max (* (- (+ ni ni) 1) tab-width) (* (+ (+ no no) 1) tab-width))) (set! max-width (max (* (+ (+ ni ni) 1) tab-width) (* (- (+ no no) 1) tab-width))) ) (set! width (max (get-real-width) max-width)) (let* ((left (* (- width req-width) 0.5)) (sx (+ (- x (* width 0.5)) left (* 2 tab-width))) ) (do ((i 0 (+ i 1))) ((= i n)) (let ((tab (hash-table-get tab-list i))) (send tab x-set! sx) (set! sx (+ sx (* 2 tab-width))) ) ) ) (show #f) (send editor on-paint) ) ) ) ) ) ) (public tab-in-incr) (define (tab-in-incr) (tab-incr tab-in 'in) ) (public tab-out-incr) (define (tab-out-incr) (tab-incr tab-out 'out) ) (define (tab-incr tab-list type) (hide #f) (let* ((n (hash-table-count tab-list)) (ni (hash-table-count tab-in)) (no (hash-table-count tab-out)) (max-width #f) (req-width (* (+ n n 3) tab-width)) ) (if (equal? type 'in) (set! max-width (max (* (+ ni ni 3) tab-width) (* (+ no no 1) tab-width))) (set! max-width (max (* (+ ni ni 1) tab-width) (* (+ no no 3) tab-width))) ) (set! width (max (get-real-width) max-width)) (let* ((left (* (- width req-width) 0.5)) (sx (+ (- x (* width 0.5)) left (* 1.5 tab-width))) ) (do ((i 0 (+ i 1))) ((= i n)) (let ((tab (hash-table-get tab-list i))) (send tab x-set! sx) (set! sx (+ sx (* 2 tab-width))) ) ) (let ((tab (make-object tab% sx (if (equal? type 'in) (- y (/ height 2.0)) (+ y (/ height 2.0)) ) type this editor))) (hash-table-put! tab-list n tab) ) ) (show #f) (send editor layout id) ) ) (define (draw show? selected?) (set! visible? show?) (let ((dc (send editor get-dc)) (color #f) ) ; draw or undraw selected square (if (and show? selected?) (set! color (make-object color% 240 240 240)) (set! color (get-panel-background)) ) (send dc set-pen color 1 'solid) (send dc set-brush color 'solid) (send dc draw-rectangle (- x (/ width 2.0)) (- y (/ height 2.0) tab-height) width (+ height tab-height tab-height)) ; draw the node square (if show? (send dc set-pen "black" 1 'solid) (send dc set-pen "white" 1 'solid) ) (send dc set-brush node-color 'solid) ; draw boundary border (send dc draw-rectangle (- x (/ width 2.0)) (- y (/ height 2.0)) width height) ; draw a white line on top and left (send dc set-pen "white" 1 'solid) (send dc draw-line (- x (/ width 2.0)) (- y (/ height 2.0)) (- x (/ width 2.0)) (+ (- y (/ height 2.0)) height)) (send dc draw-line (- x (/ width 2.0)) (- y (/ height 2.0)) (+ (- x (/ width 2.0)) width) (- y (/ height 2.0))) ; draw the text (if show? (let-values (((tw th td ta) (send dc get-text-extent name #f #f 0))) (send dc set-text-foreground white-color) (send dc draw-text name (- x (* tw 0.5)) (- y (* (+ th ta) 0.5)) #f 0 0) ) ) ) (do ((i 0 (+ i 1))) ((= i (hash-table-count tab-in))) (let ((tab (hash-table-get tab-in i))) (if show? (send tab show) (send tab hide) ) ) ) (do ((i 0 (+ i 1))) ((= i (hash-table-count tab-out))) (let ((tab (hash-table-get tab-out i))) (if show? (send tab show) (send tab hide) ) ) ) ) (public show) (define (show selected?) (draw #t selected?) ) (public hide) (define (hide selected?) (draw #f selected?) ) (public on-mouse) (define (on-mouse type cx cy) (cond ((member type '(left-down left-up)) (cond ((and (<= (- x (* width 0.5)) cx (+ x (* width 0.5))) (<= (- y (* height 0.5)) cy (+ y (* height 0.5)))) this ) (else (let ((found? #f) (n-in (hash-table-count tab-in)) (n-out (hash-table-count tab-out)) ) (do ((i 0 (+ i 1))) ((or found? (= i n-in))) (set! found? (send (hash-table-get tab-in i #f) on-mouse type cx cy)) ) (do ((i 0 (+ i 1))) ((or found? (= i n-out))) (set! found? (send (hash-table-get tab-out i #f) on-mouse type cx cy)) ) found? ) ) ) ) (else #f) ) ) (super-new) ; determine the size (set! width (get-real-width)) (set! height (get-real-height)) ; create input tabs (if (not (member 'no-input style)) (hash-table-put! tab-in (hash-table-count tab-in) (make-object tab% x (- y (/ height 2.0)) 'in this editor))) ; create output tabs (if (not (member 'no-output style)) (hash-table-put! tab-out (hash-table-count tab-out) (make-object tab% x (+ y (/ height 2.0)) 'out this editor))) ) ) ; ---------------------------------------------------------------------------- ; graph editor ; ---------------------------------------------------------------------------- (provide graph-editor%) (define graph-editor% (class canvas% (init-field (callback #f) (font #f) ) (unless (or (not callback) (procedure-arity-includes? callback 2)) (raise-type-error 'graph-editor% "procedure of arity 2" callback) ) (unless (or (not font) (is-a? font font%)) (raise-type-error 'graph-editor% "fond%" font) ) (if (not font) (set! font normal-control-font) ) ; these variables are used for smooth scrolling ; we use this bitmap instead of the default bitmap (define bitmap #f) (define b-dc #f) (define bitmap-width 0) (define bitmap-height 0) (define x-offset 0) (define y-offset 0) (define vertical-scroll-step 20) (define horizontal-scroll-step 20) (define/override (get-dc) b-dc ) ; the selected node (define selected-node #f) ; the nodes in the graph, nodes are identified by an integer number (define nodes (make-hash-table 'equal)) ; the maximum id number in the hash table (define max-id 0) ; ; the edge in the graph, indexed by source tabs ; (define line-src (make-hash-table 'equal)) ; ; the edge in the graph, indexed by destination tabs ; (define line-dst (make-hash-table 'equal)) (public get-selected) (define (get-selected) selected-node ) (define (distance x1 y1 x2 y2) (let ((dx (- x2 x1)) (dy (- y2 y1))) (sqrt (+ (* dx dx) (* dy dy))) ) ) (define (is-inside? x1 y1 x2 y2 x y) (and (<= (min x1 x2) x (max x1 x2)) (<= (min y1 y2) y (max y1 y2))) ) (define (overlap? x1 y1 w1 h1 x2 y2 w2 h2) (let ((halfw1 (* 0.5 w1)) (halfh1 (* 0.5 h1)) (halfw2 (* 0.5 w2)) (halfh2 (* 0.5 h2)) ) (or (is-inside? (- x2 halfw2) (- y2 halfh2) (+ x2 halfw2) (+ y2 halfh2) (- x1 halfw1) (- y1 halfh1)) (is-inside? (- x2 halfw2) (- y2 halfh2) (+ x2 halfw2) (+ y2 halfh2) (+ x1 halfw1) (- y1 halfh1)) (is-inside? (- x2 halfw2) (- y2 halfh2) (+ x2 halfw2) (+ y2 halfh2) (+ x1 halfw1) (+ y1 halfh1)) (is-inside? (- x2 halfw2) (- y2 halfh2) (+ x2 halfw2) (+ y2 halfh2) (- x1 halfw1) (+ y1 halfh1)) ;;;;; (is-inside? (- x1 halfw1) (- y1 halfh1) (+ x1 halfw1) (+ y1 halfh1) (- x2 halfw2) (- y2 halfh2)) (is-inside? (- x1 halfw1) (- y1 halfh1) (+ x1 halfw1) (+ y1 halfh1) (+ x2 halfw2) (- y2 halfh2)) (is-inside? (- x1 halfw1) (- y1 halfh1) (+ x1 halfw1) (+ y1 halfh1) (+ x2 halfw2) (+ y2 halfh2)) (is-inside? (- x1 halfw1) (- y1 halfh1) (+ x1 halfw1) (+ y1 halfh1) (- x2 halfw2) (+ y2 halfh2)) ) ) ) ; this function enforces the node placement strategy, so ; the nodes cannot overlap (define (layout-aux id) (let ((ok? #t) (n max-id) (anode (hash-table-get nodes id #f))) ; maybe anode does not exist (do ((i 0 (+ i 1))) ((or anode (= i n))) (set! id i) (set! anode (hash-table-get nodes id #f)) ) (if anode (let-values (((ax ay) (send anode get-position)) ((aw ah) (send anode get-size))) (do ((j 0 (+ j 1))) ((= j n)) (if (not (= j id)) (let ((bnode (hash-table-get nodes j #f))) (if bnode (let-values (((bx by) (send bnode get-position)) ((bw bh) (send bnode get-size)) ) (if (overlap? ax ay aw (+ ah tab-height tab-height) bx by bw (+ bh tab-height tab-height)) (begin (send bnode x-set! (+ ax (+ (* aw 0.55) (* bw 0.55)) node-buffer)) (layout-aux j)))))))))))) (public layout) (define (layout id) (layout-aux id) (update-bitmap) (on-paint) ) (public clear) (define (clear) (let ((allowed? (if callback (callback 'before-clear #f) #t))) (if allowed? (begin (set! nodes (make-hash-table 'equal)) ; (set! line-src (make-hash-table 'equal)) ; (set! line-dst (make-hash-table 'equal)) (layout #f) (if callback (callback 'after-clear #f) ) ) ) ) ) (public node-add) (define (node-add name x y style) (let ((allowed? (if callback (callback 'before-node-add name) #t))) (if allowed? ; store new node (let* ((id max-id) (node (new node% (id id) (name name) (x x) (y y) (editor this) (style style)))) ; add to the hash table (hash-table-put! nodes id node) ; increment the maximum id number (set! max-id (+ max-id 1)) ; ensure that the node is not outside of screen (let-values (((w h) (send node get-size))) (if (< x 0) (send node x-set! (* w 0.5)) ) (if (< y 0) (send node y-set! (* 0.5 (+ h tab-height tab-height))) ) ) ; do a full layout (layout id) (if callback (callback 'after-node-add node) ) node ) #f ) ) ) (public node-del) (define (node-del node) (if (and (not (send node tab-in-connected?)) (not (send node tab-out-connected?))) (let ((allowed? (if callback (callback 'before-node-del node) #t))) (if allowed? ; delete the node (let* ((id (send node get-id))) (hash-table-remove! nodes id) ; ensure that it all of them are unselected (set! selected-node #f) ; do a full layout (layout #f) (if callback (callback 'after-node-del id) ) ) ) ) ) ) (public node-get-by-name) (define (node-get-by-name name) (let ((ok? #f) (n max-id)) (do ((i 0 (+ i 1))) ((or ok? (= i n))) (let ((node (hash-table-get nodes i #f))) (if (and node (equal? (send node get-name) name)) (set! ok? node)))) ok? ) ) ; func has two arguments ; key : (an integer number) ; value : a node object (public node-for-each) (define (node-for-each func) (hash-table-for-each nodes func ) ) (public node-del!) (define (node-del! node) (let ((ni (send node tab-in-count)) (no (send node tab-out-count))) ; go through the input tabs and delete connected lines (do ((i 0 (+ i 1))) ((= i ni)) (let ((tab (send node tab-in-ref i))) (if (send tab connected?) ; if lines are conencected to tab then delete the lines (for-each (lambda (line) (let ((src (send line get-source)) (dst (send line get-target))) (line-del line src dst))) (send tab get-lines))))) ; go through the output tabs and delete connected lines (do ((i 0 (+ i 1))) ((= i no)) (let ((tab (send node tab-out-ref i))) (if (send tab connected?) ; if lines are conencected to tab then delete the lines (for-each (lambda (line) (let ((src (send line get-source)) (dst (send line get-target))) (line-del line src dst))) (send tab get-lines))))) ; delete the node itself (node-del node) ) ) ; it returns the line object (public line-add) (define (line-add src-tab dst-tab) (let ((allowed? (if callback (callback 'before-line-add (list src-tab dst-tab)) #t))) (if allowed? (let ((line (make-object line% src-tab dst-tab this)) ; (src-lst (hash-table-get line-src src-tab #f)) ; (dst-lst (hash-table-get line-dst dst-tab #f)) ) ; (if src-lst ; (hash-table-put! line-src src-tab (cons line src-lst)) ; (hash-table-put! line-src src-tab (list line)) ; ) ; (if dst-lst ; (hash-table-put! line-dst dst-tab (cons line dst-lst)) ; (hash-table-put! line-dst dst-tab (list line)) ; ) (send src-tab line-add line) (send dst-tab line-add line) (send line show) (if callback (callback 'after-line-add line) ) (on-paint) line ) #f ) ) ) (define (line-del line src-tab dst-tab) (let ((allowed? (if callback (callback 'before-line-del (list src-tab dst-tab)) #t))) (if allowed? (let* ( ; (src-lst (hash-table-get line-src src-tab #f)) ; (dst-lst (hash-table-get line-dst dst-tab #f)) ; (src-rest (remove line src-lst)) ; (dst-rest (remove line dst-lst)) ) ; (if (null? src-rest) ; (hash-table-remove! line-src src-tab) ; (hash-table-put! line-src src-tab src-rest) ; ) ; (if (null? dst-rest) ; (hash-table-remove! line-dst dst-tab) ; (hash-table-put! line-dst dst-tab dst-rest) ; ) (send src-tab line-del line) (send dst-tab line-del line) (send line hide) (if callback (callback 'after-line-del #f) ) (on-paint) ) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ; get the real width of the network (define (get-real-width) (let ((width 0)) (hash-table-for-each nodes (lambda (idx node) (let-values (((x y) (send node get-position)) ((w h) (send node get-size))) (set! width (max width (+ x (* w 0.5)))) ) ) ) (inexact->exact (round width)) ) ) (define (get-real-height) (let ((height 0)) (hash-table-for-each nodes (lambda (idx node) (let-values (((x y) (send node get-position)) ((w h) (send node get-size))) (set! height (max height (+ y (* (+ h tab-height tab-height) 0.5)))) ) ) ) (inexact->exact (round height)) ) ) (define (update-bitmap) (update-scroll-bars) ;; We will need the virtual size... (let-values (((width height) (send this get-virtual-size))) ;; We create a bitmap, with the *real* width and height (at least, if they are ;; greater than the virtual width and height...), ;; in order to be able to scroll very quickly... (let* ((w (max (get-real-width) width 1)) (h (max (get-real-height) height 1))) (if (or (not bitmap) (> w bitmap-width) (> h bitmap-height)) (let* ((n (round (max (/ w 1000) (/ h 1000)))) (size (* (+ n 1) 1000))) (set! bitmap (make-object bitmap% size size #f)) ;; And then, we update the associated bitmap-dc... (set! b-dc (instantiate bitmap-dc% (bitmap))) (set! bitmap-width size) (set! bitmap-height size) (on-paint) ) ) ) ) ) ;; This function updates the scroll bars properties for update-bitmap (define (update-scroll-bars) (let ((horizontal? #f) (vertical? #f)) ;; hide both of the scroll bars (send this show-scrollbars #f #f) ;; We need to get the virtual size... (let-values (((width height) (send this get-virtual-size))) ;; Then, we update the scroll range for both the horizontal and ;; the vertical scroll bars. ;; The right range is the full real height (width, respectively) minus the ;; virtual height (resp. width). ;; vertical scrollbar (send this set-scroll-range 'vertical (max 0 (- (get-real-height) height))) ;; If the window can contain the whole height, no y offset is needed. (if (= 0 (send this get-scroll-range 'vertical)) (set! y-offset 0) (begin (send this show-scrollbars #f #t) (set! vertical? #t) ) ) ) (let-values (((width height) (send this get-virtual-size))) ;; horizontal scrollbar (send this set-scroll-range 'horizontal (max 0 (- (get-real-width) width))) ;; If the window can contain the whole width, no x offset is needed. (if (= 0 (send this get-scroll-range 'horizontal)) (set! x-offset 0) (begin (send this show-scrollbars #t vertical?) (set! horizontal? #t) ) ) ) (let-values (((width height) (send this get-virtual-size))) ;; check vertical scrolling again (send this set-scroll-range 'vertical (max 0 (- (get-real-height) height))) ;; If the window can contain the whole height, no y offset is needed. (if (= 0 (send this get-scroll-range 'vertical)) (set! y-offset 0) (send this show-scrollbars horizontal? vertical?) ) ;; This is the number of scroll steps - ;; vertically we want to scroll not exactly one page, ;; but one page minus one line (as it is always implemented). (send this set-scroll-page 'horizontal (max 1 width)) (send this set-scroll-page 'vertical (max 1 (- height vertical-scroll-step))) ) ) ) (define/override (on-scroll scroll-event) ;; What we are going to do depends on the event direction... (case (send scroll-event get-direction) ;; Is it vertical? ((vertical) ;; Then it depends on the event type... (let ((type (send scroll-event get-event-type))) ;; If the event type is line down... (cond ((eq? type 'line-down) ;; ...then we need the virtual size... (let-values (((width height) (send this get-virtual-size))) ;; ...update the scroll-bars positions ;; (to force scrolling more than 1 step [1 line, actually])... (send this set-scroll-pos 'vertical (min (+ (send this get-scroll-pos 'vertical) vertical-scroll-step -1) (send this get-scroll-range 'vertical))) ;; ...and the vertical offset. (set! y-offset (min (+ y-offset vertical-scroll-step) (send this get-scroll-range 'vertical))))) ;; If the event type is line-up... ((eq? type 'line-up) ;; ...then we update the scroll-bars positions ;; (to force scrolling more than 1 step...)... (send this set-scroll-pos 'vertical (max (- (send this get-scroll-pos 'vertical) vertical-scroll-step -1) 0)) ;; ...and the vertical offset. (set! y-offset (max (- y-offset vertical-scroll-step) 0))) ;; Otherwise, we do not have to update the scroll-bars positions, ;; just to update the vertical offset... (else (set! y-offset (send scroll-event get-position)))))) ;; Is it horizontal? ((horizontal) ;; Then it depends on the event type... (let ((type (send scroll-event get-event-type))) ;; If the event type is line down... (cond ((eq? type 'line-down) ;; ...then we need the virtual size... (let-values (((width height) (send this get-virtual-size))) ;; ...update the scroll-bars positions ;; (to force scrolling more than 1 step... [1 "horizontal scroll step", actually])... (send this set-scroll-pos 'horizontal (min (+ (send this get-scroll-pos 'horizontal) horizontal-scroll-step -1) (send this get-scroll-range 'horizontal))) ;; ...and the horizontal offset. (set! x-offset (min (+ x-offset horizontal-scroll-step) (send this get-scroll-range 'horizontal))))) ;; If the event type is line-up... ((eq? type 'line-up) ;; ...then we update the scroll-bars positions ;; (to force scrolling more than 1 step...)... (send this set-scroll-pos 'horizontal (max (- (send this get-scroll-pos 'horizontal) horizontal-scroll-step -1) 0)) ;; ...and the horizontal offset. (set! x-offset (max (- x-offset horizontal-scroll-step) 0))) ;; Otherwise, we do not have to update the scroll-bars positions, ;; just to update the horizontal offset... (else (set! x-offset (send scroll-event get-position))))))) ;; And then, we directly draw the bitmap, which has already been drawn during the last ;; call of (on-paint), at the right position - which is *much* faster than calling on-paint ;; directly and allows a smooth scrolling. (let ((dc (super get-dc))) (send dc draw-bitmap bitmap (- x-offset) (- y-offset)) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; on-size ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define/override (on-size width height) ;; We just update the bitmap... (update-bitmap) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; on-paint ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define (refresh) (if (send bitmap ok?) (let ((dc (super get-dc))) (send dc draw-bitmap bitmap (- x-offset) (- y-offset)) ) ) ) ; redrawing event (define/override (on-paint) (let ((dc (get-dc)) (n max-id) ) ; clear the background (send dc set-background (get-panel-background)) (send dc clear) ; draw all nodes with tabs and lines (do ((i 0 (+ i 1))) ((= i n)) (let ((node (hash-table-get nodes i #f))) (if node (if (equal? node selected-node) (send node show #t) (send node show #f) ) ) ) ) ) ; draw the bitmap (refresh) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; on-char ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define/override (on-subwindow-char widget event) (let ((key (send event get-key-code)) (ctrl (send event get-control-down)) ) (cond ((equal? key #\rubout) (let ((node selected-node)) (if (and node (not (send node tab-in-connected?)) (not (send node tab-out-connected?))) (node-del node) ) ) ) ) ) ) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; on-event ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (define clicked #f) (define px #f) (define py #f) (define (find-clicked type x y) (let ((obj #f)) ; go through all nodes (hash-table-for-each nodes (lambda (name node) (let ((selected (send node on-mouse type x y))) (cond ((is-a? selected node%) (set! obj selected) ) ((is-a? selected tab%) (set! obj selected) ) ) ) ) ) obj ) ) ; mouse event (define/override (on-event event) (send this focus) (let ((type (send event get-event-type)) (x (+ (send event get-x) x-offset)) (y (+ (send event get-y) y-offset)) ) (cond ; left button clicked ((equal? type 'left-down) (set! clicked (find-clicked type x y)) (if (not clicked) (begin (if selected-node (begin (if callback (callback 'deselect selected-node) ) (send selected-node show #f) ; we have to refresh only one node (refresh) ) ) (set! selected-node #f) ) ) ) ; out tab is clicked and dragging ((and (equal? type 'motion) clicked (is-a? clicked tab%) (equal? (send clicked get-type) 'out)) (let-values (((sx sy) (send clicked get-line-position))) (let ((dc (get-dc))) (send dc set-pen "black" 1 'xor) (if (and px py) (send dc draw-line sx sy px py) ) (send dc draw-line sx sy x y) ; we have to refresh only the bitmap (refresh) (set! px x) (set! py y) ) ) ) ; out tab is clicked and finished dragging ((and (equal? type 'left-up) clicked (is-a? clicked tab%) (equal? (send clicked get-type) 'out)) ; xoring the last line is not required as we will do a full repaint ; check to add line (let ((dest (find-clicked type x y))) ; if there is a destination, it is an tab and its type is 'in' (if (and dest (is-a? dest tab%) (equal? (send dest get-type) 'in) ) (if (not (send dest connected?)) (line-add clicked dest) (message-box "Error" "Tab is already connected" #f '(ok stop)) ) ) ) ; full redraw (on-paint) ; clean-up (set! clicked #f) (set! px #f) (set! py #f) ) ; in tab is clicked, a line is connected and dragging ((and (equal? type 'motion) clicked (is-a? clicked tab%) (equal? (send clicked get-type) 'in) (send clicked connected?)) (let* ((line (car (send clicked get-lines))) (src (send line get-source))) ; hide the line (send line hide) (let-values (((sx sy) (send src get-line-position))) (let ((dc (get-dc))) (send dc set-pen "black" 1 'xor) (if (and px py) (send dc draw-line sx sy px py) ) (send dc draw-line sx sy x y) ; we have to refresh only the bitmap (refresh) (set! px x) (set! py y) ) ) ) ) ; in tab is clicked and finished dragging ((and (equal? type 'left-up) clicked (is-a? clicked tab%) (equal? (send clicked get-type) 'in) (send clicked connected?)) (let* ((line (car (send clicked get-lines))) (src (send line get-source)) (dst (send line get-target)) ) ; xoring the last line is not required as we will do a full repaint ; check to remove line (let ((new-dst (find-clicked type x y))) (cond ((equal? new-dst dst) (send line show) ) ; there is a new destination, it is an 'in' tab ((and new-dst (is-a? new-dst tab%) (equal? (send new-dst get-type) 'in)) ; first delete the previous line (line-del line src dst) ; then add the line between the old source and new destination (line-add src new-dst) ) (else (line-del line src dst) ) ) ) ; full redraw (on-paint) ; clean-up (set! clicked #f) (set! px #f) (set! py #f) ) ) ; node is clicked and dragging ((and (equal? type 'motion) clicked (is-a? clicked node%)) (if (send clicked is-shown?) (send clicked hide #f) ) (let-values (((sw sh) (send clicked get-size))) (let ((dc (get-dc))) (send dc set-pen "black" 1 'xor) (send dc set-brush "black" 'xor) (if (and px py) (send dc draw-rectangle (- px (* sw 0.5)) (- py (* sh 0.5)) sw sh) ) (send dc draw-rectangle (- x (* sw 0.5)) (- y (* sh 0.5)) sw sh) ; we have to refresh only the bitmap (refresh) (set! px x) (set! py y) ) ) ) ; node is clicked and finished dragging ((and (equal? type 'left-up) clicked (is-a? clicked node%)) ; hiding the dragged square is not necessary, we will do a full redraw ; do a layout if there was a dragging (if (and px py) (let-values (((w h) (send clicked get-size))) ; ensure that no negative coordinates are allowed (if (< x 0) (send clicked x-set! (* w 0.5)) (send clicked x-set! x) ) (if (< y 0) (send clicked y-set! (* 0.5 (+ h tab-height tab-height))) (send clicked y-set! y) ) (layout (send clicked get-id)) ) ) (send clicked show #t) (set! selected-node clicked) (if callback (callback 'select selected-node) ) ; full redraw (on-paint) (set! clicked #f) (set! px #f) (set! py #f) ) ) ) ) (super-new (style '(vscroll hscroll border))) (update-bitmap) (send (send this get-dc) set-font font) ) ) ); end of module