;;; sliders.el --- graphical sliders for XEmacs. (c) 1997

;; Author:     Jens Lautenbacher <jens@tellux.de>
;; Keywords:   utilities
;; Version:    0.3

;; This file is not (yet?) part of XEmacs.

;; XEmacs 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, or (at your option)
;; any later version.

;; XEmacs 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 XEmacs; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
;; Boston, MA 02111-1307, USA.

;; Code:
;;
;;; Compatibility.

;; There is a bug in XEmacs extents where the order of extent glyphs
;; can get messed up when you have zero-length extents.  SXEmacs has
;; this fixed, but because it is such a long-standing bug code like
;; this was written against the "buggy" extent api and so "breaks"
;; when used with non-buggy extents.  Hence this compatibility snippet
;; here.  --SY.
(eval-and-compile
  (if (featurep 'sxemacs)
      (progn
	(fset #'slider-set-glyph #'set-extent-end-glyph)
	(fset #'slider-glyph #'extent-end-glyph))
    (fset #'slider-set-glyph #'set-extent-begin-glyph)
    (fset #'slider-glyph #'extent-begin-glyph)))

;;; First of all we'll define the needed variables.


(defconst slider-bar-elem-width 4)

(defvar slider-pixmap-dir (locate-data-directory "slider"))

(defvar slider-bar-keymap nil) 
(if slider-bar-keymap ()
  (setq slider-bar-keymap (make-keymap 'slider-bar-keymap))
  (suppress-keymap slider-bar-keymap)
  (define-key slider-bar-keymap "+" 'slider-one-right-this)
  (define-key slider-bar-keymap "-" 'slider-one-left-this)
  (define-key slider-bar-keymap 'button1 'slider-activate-arrow-or-bar))

(defvar slider-knob-keymap nil) 
(if slider-knob-keymap ()
  (setq slider-knob-keymap (make-keymap 'slider-knob-keymap))
  (suppress-keymap slider-knob-keymap)
  (define-key slider-knob-keymap 'button1 'slider-activate-knob))


;;; Add the correct path here!
(defvar slider-left-up    (make-glyph (concat slider-pixmap-dir "left-up.xpm")))
(defvar slider-left-down  (make-glyph (concat slider-pixmap-dir "left-down.xpm")))
(defvar slider-right-up   (make-glyph (concat slider-pixmap-dir "right-up.xpm")))
(defvar slider-right-down (make-glyph (concat slider-pixmap-dir "right-down.xpm")))
(defvar slider-element    (make-glyph (concat slider-pixmap-dir "bar.xpm")))
(defvar slider-knob       (make-glyph (concat slider-pixmap-dir "knob.xpm")))

(defun slider-new (visible-size min-val max-val	stepsize
				&optional callback callback-data read-only)
  (let* ((number-of-bars (/ visible-size slider-bar-elem-width))
	 (left-vector (make-vector (1+ number-of-bars) nil))
	 (right-vector (make-vector (1+ number-of-bars) nil))
	 knob slider tmp)
    ;; creating the extents....
    ;; the left part: left-vector[0] is the left arrow.
    (insert-string "  ")
    (goto-char (1- (point)))

    (setq slider (make-extent (point) (point)))
    (set-extent-property slider 'keymap slider-bar-keymap)
    (set-extent-property slider 'read-only read-only)
    (set-extent-property slider 'start-open t)
    (set-extent-property slider 'slider t)
    (set-extent-property slider	'slider-stepsize stepsize)
    (set-extent-property slider	'slider-left left-vector)
    (set-extent-property slider	'slider-right right-vector)
    (set-extent-property slider	'slider-max max-val)
    (set-extent-property slider	'slider-min min-val)
    (set-extent-property slider	'slider-callback callback)
    (set-extent-property slider 'slider-data callback-data)
    (set-extent-property slider	'slider-value min-val)
    (set-extent-property slider	'slider-value-int 0)
    (set-extent-property slider	'slider-number-of-bars number-of-bars)
    (set-extent-face slider 'bold)
    (setq tmp (make-extent (point) (point)))
    (aset left-vector 0 tmp)
    (set-extent-keymap   tmp slider-bar-keymap)
    (set-extent-property tmp 'slider-action 'slider-one-left)
    (set-extent-property tmp 'slider-down slider-left-down)
    ;; from 1 to number-of-bars: the slider bar to the left.
    (let ((count 1))
      (while (<= count number-of-bars) ; <= to get length + 1 elements
	(setq tmp (make-extent (point) (point)))
	(aset left-vector count tmp)
	(set-extent-keymap tmp slider-bar-keymap)
	(set-extent-property tmp 'slider-action 'slider-left-action)
	(setq count (1+ count))))
    ;; now the sliders "knob"
    (setq knob (make-extent (point) (point)))
    (set-extent-property knob 'slider-this slider)
    ;; the right part:
    ;; from 0 to (1- number-of-bars): the slider bar to the right
    (let ((count 0))
      (while (< count number-of-bars) ; < to get length elements
	(setq tmp (make-extent (point) (point)))
	(aset right-vector count tmp)
	(set-extent-keymap tmp slider-bar-keymap)
	(set-extent-property tmp 'slider-action 'slider-right-action)
	(setq count (1+ count))))
    ;; the right arrow.
    (aset right-vector number-of-bars
	  (setq tmp (make-extent (point) (point))))
    (set-extent-keymap   tmp slider-bar-keymap)
    (set-extent-property tmp 'slider-action 'slider-one-right)
    (set-extent-property tmp 'slider-down slider-right-down)
    ;; initializing the display:
    ;; left arrow glyph:
    (slider-set-glyph (aref left-vector 0) slider-left-up)
    ;; the left bar is invisible, so make the knob glyph:
    (slider-set-glyph knob slider-knob)
    ;; the right part of the bar is fully visible
    (let ((count 0))
      (while (< count number-of-bars)
	(slider-set-glyph (aref right-vector count) slider-element)
	(setq count (1+ count))))
    ;; the right arrow glyph
    (slider-set-glyph (aref right-vector number-of-bars) slider-right-up)
    ;; put some needed information into the knob's properties.
    (set-extent-property knob 'slider-action 'slider-drag-knob)
    (set-extent-keymap knob slider-knob-keymap)
    ;; loop oer the whole left and right side and fill in needed properties
    (let ((count 0))
      (while (<= count number-of-bars)
	(set-extent-property (setq tmp (aref right-vector count))
			     'slider-this slider)
	(set-extent-property tmp 'slider-bar-number (+ 1 count))
	(set-extent-property (setq tmp (aref left-vector count))
			     'slider-this slider)
	(set-extent-property tmp 'slider-bar-number count)
	(setq count (1+ count))))
    ;; return the knob.
    (goto-char (1+ (point)))
    slider))

(defun slider-set (slider abs-value)
  (let* ((max (extent-property slider 'slider-max))
	 (min (extent-property slider 'slider-min))
	 (func (extent-property slider 'slider-callback))
	 (length (extent-property slider 'slider-number-of-bars))
	 (step (floor (* (/ (float length) (float (- max min)))
			 (- abs-value min)))))
    (if (not (sliderp slider))
	(error "No slider: %s" slider))
    (if (or (< abs-value min) (> abs-value max))
	(error "Value %s not in allowed range [%s...%s]"
	       abs-value min max)
      (let ((count 1))
	(while (<= count step)
	  (slider-set-glyph
	   (aref (extent-property slider 'slider-left)
		 count) 
	   slider-element)
	  (setq count (1+ count)))
	(while (<= count length)
	  (slider-set-glyph
	   (aref (extent-property slider 'slider-left)
		 count)
	   nil)
	  (setq count (1+ count))))
      (let ((count 0))
	(while (< count step)
	  (slider-set-glyph
	   (aref (extent-property slider 'slider-right)
		 count) 
	   nil)
	  (setq count (1+ count)))
	(while (< count length)
	  (slider-set-glyph
	   (aref (extent-property slider 'slider-right)
		 count) 
	   slider-element)
	  (setq count (1+ count))))
      (set-extent-property slider 'slider-value abs-value)
      (set-extent-property slider 'slider-value-int step)
      (if func (funcall func slider (extent-property slider 'slider-data)))
      abs-value)))

(defun slider-set-data (slider data)
  (cond ((sliderp slider)
	 (set-extent-property slider 'slider-data data))
	(t
	 (error "No slider: %s" slider))))

(defun slider-get (slider)
  (cond ((sliderp slider)
	 (extent-property slider 'slider-value))
	(t
	 (error "No slider: %s" slider))))

(defun slider-destroy (slider)
  (let ((start (extent-start-position slider))
	(end   (extent-end-position slider)))
    (cond ((sliderp slider)
	   (set-extent-property slider 'read-only nil)
	   (delete-region start end))
	  (t
	   (error "No slider: %s" slider)))))


;;; Internal functions below...

(defun sliderp (obj)
  (if (extentp obj)
      (extent-property obj 'slider)))

(defun slider-one-right (slider)
  (let ((val (slider-get slider))
	(max (extent-property slider 'slider-max)))
    (if (< val max) (slider-set slider (1+ val)))))

(defun slider-one-left (slider)
  (let ((val (slider-get slider))
	(min (extent-property slider 'slider-min)))
    (if (> val min) (slider-set slider (1- val)))))

(defun slider-right-action (slider)
  (let ((val (slider-get slider))
	(stepsize (extent-property slider 'slider-stepsize))
	(max (extent-property slider 'slider-max)))
    (if (< val max) (slider-set slider (min max (+ stepsize val))))))
  
(defun slider-left-action (slider)
  (let ((val (slider-get slider))
	(stepsize (extent-property slider 'slider-stepsize))
	(min (extent-property slider 'slider-min)))
    (if (> val min) (slider-set slider (max min (- val stepsize))))))

(defun slider-one-right-this ()
  (interactive)
  (let ((slider (extent-at (point) (current-buffer) 'slider nil 'at))) 
    (slider-one-right slider)
    (sit-for 0)))

(defun slider-one-left-this ()
  (interactive)
  (let ((slider (extent-at (point) (current-buffer) 'slider nil 'at))) 
    (slider-one-left slider)
    (sit-for 0)))

(defun slider-activate-arrow-or-bar (event)
  (interactive "e")
  (let* ((extent (event-glyph-extent event))
	 (mouse-down t)
	 (action (extent-property extent 'slider-action))
	 up-glyph down-glyph)
    ;; make the glyph look pressed
    (cond  ((setq down-glyph (extent-property extent 'slider-down))
	    (setq up-glyph (slider-glyph extent))
	    (slider-set-glyph extent down-glyph)))
    (while mouse-down
      (if (input-pending-p)
	  (setq event (next-event event))
	(if action (funcall action (extent-property extent 'slider-this)))
	(sit-for 0))
      (if (button-release-event-p event)
	  (setq mouse-down nil)))
    ;; make the glyph look released
    (if down-glyph (slider-set-glyph extent up-glyph))))

(defun slider-activate-knob (event)
  (interactive "e")
  (let* ((extent (event-glyph-extent event))
	 (X (event-x-pixel event))
	 (bar (extent-property (extent-property extent 'slider-this)
			       'slider-value-int))
	 (max-bar (extent-property (extent-property extent 'slider-this)
			       'slider-number-of-bars))
	 (mouse-down t)
	 (action (extent-property extent 'slider-action))
	 X-new X-diff new-bar)
    (while mouse-down
      (setq event (next-event event))
      (cond ((mouse-event-p event)
	     (setq X-new (event-x-pixel event))
	     (setq X-diff (- X-new X))
	     (setq new-bar (max (min (+ bar (/ X-diff slider-bar-elem-width))
				     (1+ max-bar)) 0))
	     (cond ((not (= bar new-bar))
		    (funcall action (extent-property extent 'slider-this)
			     new-bar)
		    (setq X (+ X (* slider-bar-elem-width (- new-bar bar))))
		    (setq bar new-bar)))
	     (if (button-release-event-p event)
		 (setq mouse-down nil)))))))

(defun slider-drag-knob (extent bar)
  (let* ((range (- (extent-property extent 'slider-max)
		   (extent-property extent 'slider-min)))
	 (offset (extent-property extent 'slider-min))
	 (slider-length (length (extent-property extent 'slider-left)))
	 (value (floor (* bar (/ (float range) (float slider-length))))))
    (slider-set extent (+ offset value))))


(provide 'slider)
