;;; envelope editor based on xm module (cf enved.scm)
;;;
;;; (xe-create-enved name parent args axis-bounds) -> new envelope editor (returned value is list)
;;; (xe-envelope editor) -> current envelope (settable)

(if (and (provided? 'snd-gtk)
	 (not (provided? 'gtk4)))
    (error 'gtk-error "xm-enved.scm only works in gtk4"))

(provide 'snd-xm-enved.scm)

(if (and (provided? 'snd-motif)
	 (not (provided? 'snd-snd-motif.scm)))
    (load "snd-motif.scm"))

(define xe-envelope
  (dilambda
   (lambda (editor)
     (or (car editor) 
	 (map (editor 3) '(0 1 2 3)))) ; bounds
   (lambda (editor new-env)
     (set! (editor 0) new-env)
     (xe-redraw editor))))

(define xe-create-enved 
  (let ((xe-ungrfy (lambda (editor y)
		     (let ((bounds (editor 3))
			   (locs (editor 2)))
		       (let ((ay0 (bounds 1))
			     (ay1 (bounds 3))
			     (py0 (locs 1))
			     (py1 (locs 3)))
			 (if (= py0 py1)
			     ay1
			     (min ay1
				  (max ay0
				       (+ ay0 (* (- ay1 ay0)
						 (/ (- py0 y)
						    (- py0 py1)))))))))))
	(xe-ungrfx (lambda (editor x)
		     (let ((bounds (editor 3))
			   (locs (editor 2)))
		       (let ((ax0 (bounds 0))
			     (ax1 (bounds 2))
			     (px0 (locs 0))
			     (px1 (locs 2)))
			 (if (= px0 px1)
			     ax0
			     (min ax1
				  (max ax0
				       (+ ax0 (* (- ax1 ax0)
						 (/ (- x px0)
						    (- px1 px0))))))))))))
    (lambda (name parent args axis-bounds)
      (let ((xe-mouse-down 0)
	    (xe-mouse-pos 0)
	    (xe-mouse-new #f))
      
	(define xe-mouse-press 
	  (letrec ((xe-envelope-position 
		    (lambda (x cur-env)
		      (do ((e cur-env (cddr e)) 
			   (pos 0 (+ pos 2))) 
			  ((= (car e) x) pos))))
		   
		   (xe-on-dot?
		    (let ((xe-mouse-radius .03))
		      (lambda (x y cur-env pos)
			(and (pair? cur-env)
			     (pair? (cdr cur-env))
			     (or (and (< (abs (- (car cur-env) x)) xe-mouse-radius)
				      (< (abs (- (cadr cur-env) y)) xe-mouse-radius)
				      pos)
				 (xe-on-dot? x y (cddr cur-env) (+ pos 2)))))))
		   
		   (xe-add-envelope-point
		    (lambda (x y cur-env)
		      (let ((new-env ()))
			(let search-point ((e cur-env))
			  (cond ((null? e) (append new-env (list x y)))
				((= (car e) x) (append new-env (list x y) (cddr e)))
				((> (car e) x) (append new-env (list x y) e))
				(else
				 (set! new-env (append new-env (list (car e) (cadr e))))
				 (search-point (cddr e)))))))))
	    (lambda (editor xx yy)
	      (let* ((cur-env (xe-envelope editor))
		     (x (xe-ungrfx editor xx))
		     (y (xe-ungrfy editor yy))
		     (pos (xe-on-dot? x y cur-env 0)))
		(set! xe-mouse-new (not pos))
		(set! xe-mouse-down (get-internal-real-time))
		(if pos
		    (set! xe-mouse-pos pos)
		    (begin
		      (set! (xe-envelope editor) (xe-add-envelope-point x y cur-env))
		      (set! xe-mouse-pos (xe-envelope-position x (xe-envelope editor)))))))))
	
	
	(define xe-mouse-drag 
	  (let ((xe-edit-envelope-point 
		 (lambda (pos x y cur-env)
		   (do ((new-env ())
			(e cur-env (cddr e))
			(npos 0 (+ npos 2)))
		       ((= npos pos) 
			(append new-env (list x y) (cddr e)))
		     (set! new-env (append new-env (list (car e) (cadr e))))))))
	    (lambda (editor xx yy)
	      ;; point exists, needs to be edited with check for various bounds
	      (let* ((cur-env (xe-envelope editor))
		     (x (xe-ungrfx editor xx))
		     (y (xe-ungrfy editor yy))
		     (lx (if (= xe-mouse-pos 0)
			     (car cur-env)
			     (if (>= xe-mouse-pos (- (length cur-env) 2))
				 (cur-env (- (length cur-env) 2))
				 (max (cur-env (- xe-mouse-pos 2))
				      (min x (cur-env (+ xe-mouse-pos 2))))))))
		(set! (xe-envelope editor) 
		      (xe-edit-envelope-point xe-mouse-pos lx y cur-env))
		(xe-redraw editor)))))
	
	
	(define xe-mouse-release 
	  (let ((xe-click-time .1)
		(xe-mouse-up 0)
		(xe-remove-envelope-point 
		 (lambda (pos cur-env)
		   (let ((new-env ()))
		     (let search-point ((e cur-env)
					(npos 0))
		       (if (null? e)
			   new-env
			   (if (= pos npos)
			       (append new-env (cddr e))
			       (begin
				 (set! new-env (append new-env (list (car e) (cadr e))))
				 (search-point (cddr e) (+ npos 2))))))))))
	    (lambda (editor)
	      (let ((cur-env (xe-envelope editor)))
		(set! xe-mouse-up (get-internal-real-time))
		(if (not (or xe-mouse-new
			     (> (- xe-mouse-up xe-mouse-down) xe-click-time)
			     (= xe-mouse-pos 0)
			     (>= xe-mouse-pos (- (length cur-env) 2))))
		    (set! (xe-envelope editor)
			  (xe-remove-envelope-point xe-mouse-pos cur-env))))
	      (xe-redraw editor)
	      (set! xe-mouse-new #f))))
	
	(if (provided? 'snd-motif)
	    (with-let (sublet *motif* 
			'args args 'parent parent 'axis-bounds axis-bounds 'name name
			'xe-redraw xe-redraw 'xe-mouse-press xe-mouse-press 'xe-mouse-drag xe-mouse-drag 'xe-mouse-release xe-mouse-release)
	      (if (not (member XmNbackground args))
		  (set! args (append args (list XmNbackground *graph-color*))))
	      (if (not (member XmNforeground args))
		  (set! args (append args (list XmNforeground *data-color*))))
	      (let* ((drawer (XtCreateManagedWidget name xmDrawingAreaWidgetClass parent args))
		     (gc (car (snd-gcs)))
		     (arrow-cursor (XCreateFontCursor (XtDisplay (cadr (main-widgets))) XC_crosshair))
		     (editor (let ((x0 (car axis-bounds))
				   (x1 (cadr axis-bounds)) ; too confusing! -- change internally below
				   (y0 (caddr axis-bounds))
				   (y1 (cadddr axis-bounds)))
			       (list (list x0 y0 x1 y0) ; needs to be in user-coordinates (graph size can change)
				     drawer 
				     #f  ; axis pixel locs filled in when drawn
				     (list x0 y0 x1 y1)
				     (list gc #f)
				     name))))
		(XtAddCallback drawer XmNresizeCallback 
			       (lambda (w context info) 
				 (set! (editor 2) (apply draw-axes drawer gc name axis-bounds))
				 (xe-redraw editor)))
		(XtAddCallback drawer XmNexposeCallback 
			       (lambda (w context info) 
				 (set! (editor 2) (apply draw-axes drawer gc name axis-bounds))
				 (xe-redraw editor)))
		(XtAddEventHandler drawer ButtonPressMask #f 
				   (lambda (w context ev flag) 
				     (xe-mouse-press editor (.x ev) (.y ev))))
		(XtAddEventHandler drawer ButtonMotionMask #f 
				   (lambda (w context ev flag)
				     (xe-mouse-drag editor (.x ev) (.y ev))))
		(XtAddEventHandler drawer ButtonReleaseMask #f 
				   (lambda (w context ev flag)
				     (xe-mouse-release editor)))
		(XtAddEventHandler drawer EnterWindowMask #f
				   (lambda (w context ev flag)
				     (XDefineCursor (XtDisplay w) (XtWindow w) arrow-cursor)))
		(XtAddEventHandler drawer LeaveWindowMask #f
				   (lambda (w context ev flag)
				     (XUndefineCursor (XtDisplay w) (XtWindow w))))
		editor))
	    
	    (with-let (sublet *gtk* 
			'args args 'parent parent 'axis-bounds axis-bounds 'name name
			'xe-redraw xe-redraw 'xe-mouse-press xe-mouse-press 'xe-mouse-drag xe-mouse-drag 'xe-mouse-release xe-mouse-release)
	      (let* ((drawer (gtk_drawing_area_new))
		     (gc (car (snd-gcs)))
		     (x0 (car axis-bounds))
		     (x1 (cadr axis-bounds))
		     (y0 (caddr axis-bounds))
		     (y1 (cadddr axis-bounds))
		     ;(arrow-cursor (gdk_cursor_new_for_display (gdk_display_get_default) GDK_CROSSHAIR))
		     ;(old-cursor (gdk_cursor_new_for_display (gdk_display_get_default) GDK_LEFT_PTR))
		     (editor (list (list x0 y0 x1 y0) ; needs to be in user-coordinates (graph size can change)
				   drawer 
				   #f  ; axis pixel locs filled in when drawn
				   (list x0 y0 x1 y1)
				   (list gc #f)
				   name
				   #f))
		     (dragging #f))

		(define (drawer-expose drw cr width height data)
		  (set! (editor 2) (draw-axes drw gc "hiho" x0 x1 y0 y1 x-axis-in-seconds show-all-axes cr))
		  (set! (editor 6) cr)
		  (xe-redraw editor))

		(gtk_box_pack_start (GTK_BOX parent) drawer)
		(gtk_widget_set_size_request drawer -1 200)
		(gtk_widget_set_hexpand drawer #t)
		(gtk_widget_set_vexpand drawer #t)
		(gtk_widget_show drawer)

		(gtk_drawing_area_set_content_width (GTK_DRAWING_AREA drawer) (gtk_widget_get_allocated_width drawer))
		(gtk_drawing_area_set_content_height (GTK_DRAWING_AREA drawer) (gtk_widget_get_allocated_height drawer))
		(gtk_drawing_area_set_draw_func (GTK_DRAWING_AREA drawer) drawer-expose editor #f)

		(let ((gf (g_cclosure_new (lambda (w e d) 
					    (let ((coords (cdr (gdk_event_get_coords (GDK_EVENT e)))))
					      (set! dragging #t)
					      (xe-mouse-press editor (car coords) (cadr coords))
					      (gtk_widget_queue_draw_area w (max 0 (- (floor (car coords)) 10)) (max 0 (- (floor (cadr coords)) 10)) 20 20))
					    #f)
					  #f #f)))
		  (g_signal_connect_closure_by_id (GPOINTER drawer)
						  (g_signal_lookup "button_press_event" (G_OBJECT_TYPE (G_OBJECT drawer)))
						  0 gf #f))

		(let ((gf (g_cclosure_new (lambda (w e d) 
					    (set! dragging #f)
					    (xe-mouse-release editor)
					    (gtk_widget_queue_draw_area w 0 0 100 100); (gtk_widget_get_width w) (gtk_widget_get_height w))
					    #f)
					  #f #f)))

		  (g_signal_connect_closure_by_id (GPOINTER drawer)
						  (g_signal_lookup "button_release_event" (G_OBJECT_TYPE (G_OBJECT drawer)))
						  0 gf #f))
		(let ((gf (g_cclosure_new (lambda (w e d) 
					    (if dragging
						(let ((coords (cdr (gdk_event_get_coords (GDK_EVENT e)))))
						  (xe-mouse-drag editor (car coords) (cadr coords))
						  (gtk_widget_queue_draw_area w (max 0 (- (floor (car coords)) 10)) (max 0 (- (floor (cadr coords)) 10)) 20 20)))
					    #f)
					  #f #f)))
		  (g_signal_connect_closure_by_id (GPOINTER drawer)
						  (g_signal_lookup "motion_notify_event" (G_OBJECT_TYPE (G_OBJECT drawer)))
						  0 gf #f))
		editor)))))))

(define (xe-redraw editor)
  (let* ((cur-env (xe-envelope editor))
	 (widget (editor 1))
	 (dpy (and (provided? 'snd-motif) ((*motif* 'XtDisplay) widget)))
	 (wn ((if (provided? 'snd-motif) (*motif* 'XtWindow) (*gtk* 'gtk_widget_get_window)) widget))
	 (ax-pix (editor 2))
	 (ax-inf (editor 3))
	 (gc (car (editor 4)))
	 (name (editor 5))
	 (cr (and (>= (length editor) 7) (editor 6))))
    (when (and (list? ax-pix)
	       (list? cur-env)
	       ((if (provided? 'snd-motif) 
		    (*motif* 'XtIsManaged)
		    (*gtk* 'gtk_widget_get_realized))
		widget))
      (let ((py0 (ax-pix 1))
	    (py1 (ax-pix 3))
	    (ix0 (ax-inf 0))
	    (ix1 (ax-inf 2))
	    (iy0 (ax-inf 1))
	    (iy1 (ax-inf 3))
	    (mouse-d 10)
	    (mouse-r 5))
	
	(define xe-grfx
	  (let ((px0 (ax-pix 0))
		(px1 (ax-pix 2)))
	    (lambda (x)
	      (if (= px0 px1)
		  px0
		  (min px1
		       (max px0
			    (floor (+ px0 (* (- px1 px0)
					     (/ (- x ix0)
						(- ix1 ix0)))))))))))
	
	(define (xe-grfy y)
	  (if (= py0 py1)
	      py0
	      (min py0 ; grows downward so y1 < y0
		   (max py1
			(floor (+ py1 (* (- py0 py1)
					 (/ (- y iy1)
					    (- iy0 iy1)))))))))
	
	(when (> py0 py1)
	  (if (provided? 'snd-motif)
	      (begin
		
		;; *motif*
		((*motif* 'XClearWindow) dpy wn)
		(draw-axes widget gc name ix0 ix1 iy0 iy1)
		(do ((lx #f)
		     (ly #f)
		     (len (length cur-env))
		     (i 0 (+ i 2)))
		    ((= i len))
		  (let ((cx (xe-grfx (cur-env i)))
			(cy (xe-grfy (cur-env (+ i 1)))))
		    ((*motif* 'XFillArc) dpy wn gc (- cx mouse-r) (- cy mouse-r) mouse-d mouse-d 0 23040) ; (* 360 64))
		    (if lx
			((*motif* 'XDrawLine) dpy wn gc lx ly cx cy))
		    (set! lx cx)
		    (set! ly cy))))

	      ;; *gtk* 
	      (with-let (sublet *gtk* 
			  :widget widget 
			  :cr cr 
			  :axer (lambda () 
				  (draw-axes widget gc name ix0 ix1 iy0 iy1 x-axis-in-seconds show-all-axes cr))
			  :xe-grfx xe-grfx
			  :xe-grfy xe-grfy
			  ;:mouse-r mouse-r  ; 5
			  :cur-env cur-env)
		(let ((size (widget-size (GTK_WIDGET widget))))
		  (cairo_push_group cr)
		  (cairo_set_source_rgb cr 1.0 1.0 1.0)
		  (cairo_rectangle cr 0 0 (car size) (cadr size))
		  (cairo_fill cr))
		(axer)
		(cairo_set_line_width cr 1.0)
		(cairo_set_source_rgb cr 0.0 0.0 0.0)
		(do ((lx #f)
		     (ly #f)
		     (len (length cur-env))
		     (i 0 (+ i 2)))
		    ((= i len))
		  (let ((cx (xe-grfx (cur-env i)))
			(cy (xe-grfy (cur-env (+ i 1)))))
		    (cairo_arc cr cx cy 5 0.0 (* 2 pi)) ; 5=mouse-r
		    (cairo_fill cr)
		    (if lx
			(begin
			  (cairo_move_to cr lx ly)
			  (cairo_line_to cr cx cy)
			  (cairo_stroke cr)))
		    (set! lx cx)
		    (set! ly cy)))
		(cairo_pop_group_to_source cr)
		(cairo_paint cr)
		)))))))

