;;; uko-select.el -- selection mode of UKO-II

;; Copyright (C) 2003 Michael Kuehn, Universität Koblenz-Landau

;; Author: Hajo Hoffmann rriipp@uni-koblenz.de
;; Maintainer: Michael Kuehn <kuehn@uni-koblenz.de>
;; Created: September 2003
;; 
;; $Id: uko-select.el,v 1.21 2004/07/29 13:29:08 rriipp Exp $ 

;;;* @node UKO_SELECT
;;;* @subsection UKO_SELECT
;;;* this module offers the ability to select a line in a buffer using 
;;;* some kind of incremental search. While typing the desired word 
;;;* with an ambigous keyboard all not-matching lines are removed from 
;;;* the buffer. So the buffers content "shrinks" with each typed letter. 
;;;* At any given time the user can switch to the select-command-mode where
;;;* he can either delete the last typed letter or navigate through the list
;;;* of the remaining words to select the preferred one.

;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;; OBSERVER-Version! ;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;

;;---------------------------------------VARIABLES----------------------------

;;;* startvar
(defvar uko-select-buffer nil
  "Holds the buffer where the user can see the matchlist")
;;;* endvar

;;;* startvar
(defvar uko-select-nth-match 0		; uses next-line -> 0 is the first line!
  "Holds the currently selected match in the matchlist")
;;;* endvar


;;;* startvar
(defvar uko-select-matchlist-buffer nil
  "Holds the buffer where incremental selection takes place.")
;;;* endvar

;;;* startvar
(defvar uko-select-keyboard 1
  "Holds the index of the currently used keyboard (1 - 4)")
;;;* endvar

;;;* startvar
(defvar uko-initial-keyboard uko-keyboard-layout
  "Holds the initial keyboard layout - important for resetting the keyboard when leaving the UKO-SELECT-MODE")
;;;* endvar
  
;;;* startvar
(defvar uko-select-string ""
  "Holds the key sequence already entered for incremental select.")
;;;* endvar

;;;* startvar
(defvar uko-select-stack nil
  "Holds the history of incremental selection.")
;;;* endvar

;;;* startvar
(defvar uko-select-result ""
  "The currently selected match.") 
;;;* endvar

;;;* startvar
(defvar uko-select-startpoint 1
  "The anchor-point for all insertions and extents during this UKO-SELECT-session.")
;;;* endvar

;;;* startvar
(defvar uko-select-use-focus 1
  "Should the 'FOCUS MATCHES'-feature be used (1) or not (0) ")
;;;* endvar

;;---------------------------------------EXTENTS----------------------------


;;;* startvar
(defvar uko-select-type-extent nil
  "The extent for highlighting the typed part in the UKO-SELECT-BUFFER.")
;;;* endvar

;;;* startvar
(defvar uko-select-match-extent nil
  "The extent for highlighting the selected match in the UKO-SELECT-BUFFER.")
;;;* endvar

;;;* startvar
(defvar uko-select-write-type-extent nil
  "The extent for highlighting the typed part in the UKO-WRITE-BUFFER.")
;;;* endvar


; es muss der uko-expand-extent verwendet werden, 
; weil die Tastenbelegung (KP0 bis KP3) nur funktioniert, 
; wenn sich der Point innerhalb dieses speziellen Extents befindet

; ;;;* startvar
; (defvar uko-select-write-match-extent nil
;  "The extent for highlighting the complete match in the UKO-WRITE-BUFFER.")
; ;;;* endvar


;;-----------------------------------------INITIALIZATION----------------------------
;;;


;;;* @node uko-selection
;;;* @subsubsection uko-selection
;;;* startfunc
(defun uko-selection () 
  "Start the UKO-SELECT-MODE using the UKO-PROPOSAL-BUFFER"
;;;* endfunc
  (interactive)
  (uko-select-init uko-proposal-buffer)
  )


;;;* @node uko-select-init
;;;* @subsubsection uko-select-init
;;;* startfunc
(defun uko-select-init (buff)
  "Initialize the uko-select mode to select an entry in the buffer BUFF."
;;;* endfunc
;;;* the initialization function

  ;; INITIALIZE VARIABLES
  (setq uko-select-buffer (get-buffer-create "*uko-select*")) ; use or create *uko-select*-buffer
  (setq uko-select-matchlist-buffer buff) ; set the buffer containing the matchlist
  (setq uko-select-string "")		;empty string
  (setq uko-select-nth-match 0)
  (setq uko-select-stack nil)		;empty stack
  (setq uko-select-keyboard 1)		;set keyboard index to nr 1
  (setq uko-select-result (uko-select-get-match)) ; set UKO-SELECT-RESULT to the first element of the matchlist:
 

  ;; create faces:
  (make-face 'uko-select-match-face)
  (set-face-background 'uko-select-match-face "grey")
  (make-face 'uko-select-type-face)
  (set-face-background 'uko-select-type-face "yellow")
  (make-face 'uko-select-type-face-2)
  (set-face-background 'uko-select-type-face-2 "orange")
  (make-face 'uko-select-type-face-3)
  (set-face-background 'uko-select-type-face-3 "red")
  (make-face 'uko-select-type-face-4)
  (set-face-background 'uko-select-type-face-4 "violett")
  
  ;; display the UKO-SELECT-BUFFER
  (set-window-buffer uko-proposal-window uko-select-buffer)


  ;; create extents in the UKO-SELECT-BUFFER:

  (save-current-buffer
    (set-buffer uko-select-buffer)
    
    ;; init uko-select-type-extent
    (if (null uko-select-type-extent)
	(setq uko-select-type-extent (make-extent 1 1))
      (set-extent-endpoints uko-select-type-extent 1 1)
      )
    (set-extent-face uko-select-type-extent 'uko-select-type-face)

    ;; init uko-select-match-extent:
    (if (null uko-select-match-extent)
	(setq uko-select-match-extent (make-extent 1 1))
      (set-extent-endpoints uko-select-match-extent 1 1)
      )
    (set-extent-face uko-select-match-extent 'uko-select-match-face)

    )

  ;; create extents in the UKO-WRITE-BUFFER:
  (save-current-buffer
    (set-buffer uko-write-buffer)   

    ;; init uko-select-write-type-extent
    (if (null uko-select-write-type-extent)
	(setq uko-select-write-type-extent (make-extent 1 1))
      (set-extent-endpoints uko-select-write-type-extent 1 1)
      )
    (set-extent-face uko-select-write-type-extent 'uko-select-type-face)

    ;; init uko-expand-extent (uko-select-write-match-extent)
    (if (null uko-expand-extent)
	(setq uko-expand-extent (make-extent 1 1))
      (set-extent-endpoints uko-expand-extent 1 1)
      )
    (set-extent-face uko-expand-extent 'uko-select-match-face)

; ;; make UKO-EXPAND-EXTENT invisible:
; (set-extent-endpoints uko-expand-extent 1 1)

    ;; get the anchor point for all actions (issuing extent and word-replacement) in UKO-SELECT
    (setq uko-select-startpoint (- (point) (length uko-select-result)))
    (goto-char uko-select-startpoint)
    )
 

  ;;set keyboard layout
  (uko-select-change-keyboard uko-select-keyboard) 
  ;; set initial state:
  (uko-set-select-command-state)
  ;; update observer-buffers:
  (uko-select-notify)
  )

;;----------- FUNCTIONS --------------------------------------------------

;;;* @node uko-select-notify
;;;* @subsubsection uko-select-notify
;;;* startfunc
(defun uko-select-notify ()
  "Updates the UKO-SELECT-BUFFER and the UKO-WRITE-BUFFER"
;;;* endfunc
;;;* all changes in the UKO-SELECT-MATHCLIST-BUFFER are invisible to the user until this function is called

  ;; update UKO-SELECT-BUFFER 
  (save-current-buffer
    (set-buffer uko-select-buffer)
    
    ;; overwrite matchlist:
    (delete-region (point-min) (point-max))
    (insert (buffer-string uko-select-matchlist-buffer))

    ;; go to the correct line:
    (goto-char (point-min))
    (next-line uko-select-nth-match)
    
    ;; get result, set extents and point: 
    (let* ( (beg (progn (beginning-of-line) (point))) ; startpoint of the match
	    (endt (+ beg (length uko-select-string))) ; endpoint of the current typing 
	    (endm (progn (end-of-line) (point))) ) ;endpoint of the match

      ;; set extents
      (set-extent-endpoints uko-select-match-extent beg endm)
      (set-extent-endpoints uko-select-type-extent beg endt)

      ;; get result
      (setq uko-select-result (buffer-substring beg endm))

      ;; move point to the typing position
      (goto-char endt)
      ) )

  ;; update UKO-WRITE-BUFFER
  (save-current-buffer
    (set-buffer uko-write-buffer)
    
    ;; overwrite match:
    (goto-char uko-select-startpoint)
    (delete-region (point) (+ (point) (length uko-select-result)) )
    (insert uko-select-result)

    ;; set extents and point
    (let* ( (beg uko-select-startpoint) ; startpoint of the match
	    (endt (+ beg (length uko-select-string ))) ; endpoint of the current typing 
	    (endm (+ beg (length uko-select-result ))) ) ;endpoint of the match

      ;; set extents
      (set-extent-endpoints uko-expand-extent beg endm)
      (set-extent-endpoints uko-select-write-type-extent beg endt)

      ;; move point to the typing position
      (goto-char endt)
      ) ) 
  )


;;;* @node uko-select-get-match
;;;* @subsubsection uko-select-get-match
;;;* startfunc
(defun uko-select-get-match ()
  "Get the current match from the UKO-SELECT-MATCHLIST-BUFFER."
;;;* endfunc

  (save-current-buffer
    (set-buffer uko-select-matchlist-buffer)

    (goto-char (point-min))
    (next-line uko-select-nth-match)
    (let ( (beg (progn (beginning-of-line) (point))) 
	   (end (progn (end-of-line) (point))) )
      (buffer-substring beg end)
      ) ) 
  )



;; ----------- MAIN-FUNCTIONS -------------------------------------------------

;;;* @node uko-select-letter
;;;* @subsubsection uko-select-letter
;;;* startfunc
(defun uko-select-letter (key)
  "Proceed incremental selection with key KEY."
;;;* endfunc
;; add the typed key to the UKO-SELECT-STRING and shrink the buffer

  (interactive)
  (if (> (count-lines-buffer uko-select-matchlist-buffer) 1) ; only if there are multiple matches
      (progn

	;; add current buffer to the stack:
	(uko-select-push-stack)

	;; add key
	(setq uko-select-string (concat uko-select-string (int-to-string key)))

	;; shrink the buffer:
	(let ((re (concat (uko-code-to-regex (string-to-list uko-select-string)))));; creating a regular expression (with that all lines are compared)
	  (save-current-buffer
	    (set-buffer uko-select-matchlist-buffer)
	   
	    (goto-char (point-min))
	    (while (not (eobp))
	      (if (looking-at re)
		  (next-line 1)
		(kill-entire-line) 
		) ) ) )
  
	  ;; reset line:
	  (setq uko-select-nth-match 0)

	  ;; change keyboard:
	  (when (= (length uko-select-string) (length uko-select-result))
	    (setq uko-select-keyboard (+ 1 uko-select-keyboard)) ; use next keyboard
	    (uko-select-change-keyboard uko-select-keyboard) ; change keyboard
	    (setq uko-select-string "") ; reset UKO-SELECT-STRING
	    )

	  ;; update observers:
	  (uko-select-notify)
 	
	) ) )



;;;* @node uko-select-previous
;;;* @subsubsection uko-select-previous
;;;* startfunc
(defun uko-select-previous ()
  "Select the previous line or delete the last letter of the search-string."
;;;* endfunc
  (interactive)
  (save-current-buffer
    (if (= uko-select-use-focus 1)
	(progn
	  (set-buffer uko-select-matchlist-buffer)
      
	  (if (= uko-select-nth-match 0)	; first line?
	      (if (null uko-select-stack)	; stack empty?
		  
		  ;;"FOCUS MATCHES":  
		  (uko-set-select-state)
		
		;;"BACKSPACE":
		(uko-select-pop-stack) )
	    
	    ;;"PREVIOUS":
	    (setq uko-select-nth-match (- uko-select-nth-match 1))  
	    
	    ;; rewrite button labels:
	    (if (= uko-select-nth-match 0)	; we are in the first line -> next one is "backspace"
		(if (null uko-select-stack)	; stack ist empty -> no "backspace" possible -> next one is "focus matches"
		    (uko-display-keyboard-buttons (cdr (assoc* 'select-command uko-button-labels)))
		  (uko-display-keyboard-buttons (cdr (assoc* 'select-command-backspace uko-button-labels)))
		  ) ) 
	    ) 
	  
	  ;; update observers:
	  (uko-select-notify)    

	  ) 

      ;; else (do not use focus matches)

      ;;"PREVIOUS": 
      (setq uko-select-nth-match (- uko-select-nth-match 1))  
      (uko-select-notify)
      ) ) )



;;;* @node uko-select-next
;;;* @subsubsection uko-select-next
;;;* startfunc
(defun uko-select-next ()
  "Select the next line."
;;;* endfunc
;;;* Selects the next line of the uko-select-buffer and renames the "backspace"-button to "previous"

  (interactive)
  (save-current-buffer
    (set-buffer uko-select-matchlist-buffer)

    (if (>= uko-select-nth-match (count-lines-buffer)) ; am I in the last line?
	(ding)
      (setq uko-select-nth-match (+ uko-select-nth-match 1))

      ;; rewrite button labels
      (if (= uko-select-nth-match 1)
	  (uko-display-keyboard-buttons (cdr (assoc* 'select-command-previous uko-button-labels))) )
      ) 
    
    ;; update observers:
    (uko-select-notify)    

    ) )
     

;;;* @node uko-select-accept
;;;* @subsubsection uko-select-accept
;;;* startfunc
(defun uko-select-accept ()
  "Acknowledge currently accepted match and leave the UKO-SELECT-MODE."
;;;* endfunc
  (interactive)

  (save-current-buffer
    (set-buffer uko-select-buffer)
    
    ;; reset extents and clear buffer:
    (set-extent-endpoints uko-select-type-extent 1 1)
    (set-extent-endpoints uko-select-match-extent 1 1)
    (delete-region (point-min) (point-max))

    ;; display the UKO-PROPOSAL-BUFFER
    (set-window-buffer uko-proposal-window uko-proposal-buffer)

    )
  
  (save-current-buffer
    (set-buffer uko-write-buffer)   
    
    (goto-char (+ uko-select-startpoint (+ (length uko-select-result) 1 )))    

    ;; reset extents:
    (set-extent-endpoints uko-select-write-type-extent 1 1)
    (set-extent-endpoints uko-expand-extent uko-select-startpoint (+ uko-select-startpoint (+ (length uko-select-result) 1 )))
    (set-extent-face uko-expand-extent 'highlight)
    
    ;; reset variables:
    (setq uko-select-string "")
    (setq uko-select-stack nil)	
    ;; reset keyboard
    (setq uko-keyboard-layout uko-initial-keyboard)
    
    ;; select the current match (using UKO-SELECT from uko-ii.el)
    (uko-select uko-select-result)
    
    )
  )


;;;* @node uko-select-cancel
;;;* @subsubsection uko-select-cancel
;;;* startfunc
(defun uko-select-cancel ()
  "Undo all changes and leave the UKO-SELECT-MODE."
;;;* endfunc
  (interactive)

  (save-current-buffer
    (set-buffer uko-select-buffer)
    
    (uko-select-withdraw-stack) ; get the first saved state
    (setq uko-select-result (uko-select-get-match)) ; get first match
    (uko-select-notify) ; update observers

    ;; reset keyboard:
    (setq uko-keyboard-layout uko-initial-keyboard)

    ;; reset extents and clear buffer:
    (set-extent-endpoints uko-select-type-extent 1 1)
    (set-extent-endpoints uko-select-match-extent 1 1)
    (delete-region (point-min) (point-max))
    
    ;; display the UKO-PROPOSAL-BUFFER
    (set-window-buffer uko-proposal-window uko-proposal-buffer)

    ) 

  (save-current-buffer
    (set-buffer uko-write-buffer)   

    ;; reset extents:
    (set-extent-endpoints uko-select-write-type-extent 1 1)
    (set-extent-endpoints uko-expand-extent uko-select-startpoint (+ uko-select-startpoint (length uko-select-result)))
    (set-extent-face uko-expand-extent 'highlight)
    
    ;; reset cursor position:
    (goto-char uko-select-startpoint)
    
    ;; cancel selection (using UKO-COMMAND-UNDO from uko-ii.el)
    (uko-command-undo)
    )


)



(defun uko-select-letter-key-1 ()
  "Enter the letter 1."
  (interactive)
  (uko-select-letter 1)
  )

(defun uko-select-letter-key-2 ()
  "Enter the letter 1."
  (interactive)
  (uko-select-letter 2)
  )

(defun uko-select-letter-key-3 ()
  "Enter the letter 1."
  (interactive)
  (uko-select-letter 3)
  )
  
(defun uko-select-command ()
  "Switch to the select-command-mode."
  (interactive)
  (uko-set-select-command-state)
  )

;; ---------- STACK-HANDLING ----------------------------------------------------------

;;;* @node uko-select-push-stack
;;;* @subsubsection uko-select-push-stack
;;;* startfunc
(defun uko-select-push-stack ()
  "Store UKO-SELECT-STRING, UKO-SELECT-KEYBOARD and UKO-SELECT-BUFFER content on UKO-SELECT-STACK."
;;;* endfunc
  (setq uko-select-stack 
	(cons (cons (cons uko-select-string 
			  uko-select-keyboard) 
		    (buffer-string uko-select-matchlist-buffer) ) 
	      uko-select-stack) 
	) )


;;;* @node uko-select-pop-stack
;;;* @subsubsection uko-select-pop-stack
;;;* startfunc
(defun uko-select-pop-stack ()
  "Restore last UKO-SELECT-STRING, UKO-SELECT-KEYBOARD and UKO-SELECT-BUFFER content from UKO-SELECT-STACK."
;;;* endfunc
  (save-current-buffer
    (set-buffer uko-select-matchlist-buffer)

    (if (null uko-select-stack)		; stack empty?
	(ding)

      (setq uko-select-string (caaar uko-select-stack))

      (setq uko-select-keyboard (cdaar uko-select-stack))
      (uko-select-change-keyboard uko-select-keyboard)

      ;; set buffer content:
      (delete-region (point-min) (point-max))
      (insert (cdar uko-select-stack))

      (setq uko-select-stack (cdr uko-select-stack))
      (setq uko-select-nth-match 0)
      ) ) )


;;;* @node uko-select-withdraw-stack
;;;* @subsubsection uko-select-withdraw-stack
;;;* startfunc
(defun uko-select-withdraw-stack ()
  "Restore the very first element of the stack and delete all other entries."
;;;* endfunc
    (save-current-buffer
    (set-buffer uko-select-matchlist-buffer)

    (if (null uko-select-stack)		; stack empty?
	(ding)

      (setq uko-select-string (caaar (last uko-select-stack)))

      (setq uko-select-keyboard (cdaar (last uko-select-stack)))
      (uko-select-change-keyboard uko-select-keyboard)

      ;; set buffer content:
      (delete-region (point-min) (point-max))
      (insert (cdar (last uko-select-stack)))

      (setq uko-select-stack nil)
      (setq uko-select-nth-match 0)
      ) ) )
  



;;---------------------------------------KEYBOARD-HANDLING----------------------------

;;;* @node uko-select-change-keyboard
;;;* @subsubsection uko-select-change-keyboard
;;;* startfunc
(defun uko-select-change-keyboard (k)
  "Change UKO-KEYBOARD-LAYOUT to the keyboard with the index K - considering UKO-LANGUAGE"
;;;* endfunc

  (interactive)
  (progn
    (if (= uko-language 1)
	(cond ((= k 1) 
	       (setq uko-keyboard-layout uko-select-german-keyboard-1 ) 
	       (set-extent-face uko-select-type-extent 'uko-select-type-face) ;yellow
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face))

	      ((= k 2) 
	       (setq uko-keyboard-layout uko-select-german-keyboard-2 )
	       (set-extent-face uko-select-type-extent 'uko-select-type-face-2)
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face-2)) ;orange
	      
	      ((= k 3)
	       (setq uko-keyboard-layout uko-select-german-keyboard-3 ) 
	       (set-extent-face uko-select-type-extent 'uko-select-type-face-3)
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face-3)) ;red
	      
	      ((= k 4) 
	       (setq uko-keyboard-layout uko-select-german-keyboard-4 ) 
	       (set-extent-face uko-select-type-extent 'uko-select-type-face-4)
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face-4)) ;violett
	      ) 
      )
    
    (if (= uko-language 2)
	(cond ((= k 1) 
	       (setq uko-keyboard-layout uko-select-english-keyboard-1 )
	       (set-extent-face uko-select-type-extent 'uko-select-type-face)
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face))
	      
	      ((= k 2) 
	       (setq uko-keyboard-layout uko-select-english-keyboard-2 )
	       (set-extent-face uko-select-type-extent 'uko-select-type-face-2)
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face-2)) 
	      
	      ((= k 3)
	       (setq uko-keyboard-layout uko-select-english-keyboard-3 )
	       (set-extent-face uko-select-type-extent 'uko-select-type-face-3)
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face-3)) 
	      
	      ((= k 4) 
	       (setq uko-keyboard-layout uko-select-english-keyboard-4 )
	       (set-extent-face uko-select-type-extent 'uko-select-type-face-4)
	       (set-extent-face uko-select-write-type-extent 'uko-select-type-face-4)) 
	      ) 
      )
    
    (uko-set-select-state)		; activate keyboard-layout
    ) )

;;---------- STATES ---------------------------------------------------------------

;;;* @node uko-set-select-state
;;;* @subsubsection uko-set-select-state
;;;* startfunc
(defun uko-set-select-state ()
  "Generate the keyboard for the uko-select-mode."
;;;* endfunc
  (uko-display-keyboard-buttons
   (append (mapcar 'uko-keyboard-layout-button uko-keyboard-layout)
	   (list "    Command") ))
  (uko-switch-keymap uko-select-keymap)
  )


;;;* @node uko-set-select-command-state
;;;* @subsubsection uko-set-select-command-state
;;;* startfunc
(defun uko-set-select-command-state ()
  "Generate the keyboard for the uko-select-command-mode."
;;;* endfunc
  
  (if (null uko-select-stack) ; check the UKO-SELECT-STACK -> offer "backspace" or "focus matches"
      (uko-display-keyboard-buttons (cdr (assoc* 'select-command uko-button-labels))) ; -> focus matches
    (uko-display-keyboard-buttons (cdr (assoc* 'select-command-backspace uko-button-labels))) ; -> backspace
    )
  (uko-switch-keymap uko-select-command-keymap)  
  )
