;;; cursor-chg.el --- Change cursor dynamically, depending on the context.
;; 
;; Filename: cursor-chg.el
;; Description: Change cursor dynamically, depending on the context.
;; Author: Drew Adams
;; Maintainer: Drew Adams
;; Copyright (C) 2006-2009, Drew Adams, all rights reserved.
;; Created: Tue Aug 29 11:23:06 2006
;; Version: 20.1
;; Last-Updated: Sat Aug  1 15:14:46 2009 (-0700)
;;           By: dradams
;;     Update #: 191
;; URL: http://www.emacswiki.org/cgi-bin/wiki/cursor-chg.el
;; Keywords: cursor, accessibility
;; Compatibility: GNU Emacs: 20.x, 21.x, 22.x, 23.x
;; 
;; Features that might be required by this library:
;;
;;   None
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Commentary:
;;
;;  This library provides three kinds of changes to the text cursor:
;;
;;  1. When a buffer is read-only or is in overwrite mode, the cursor
;;     type changes to `curchg-overwrite/read-only-cursor-type'.  This
;;     is controlled by command `change-cursor-mode' and user option
;;     `curchg-change-cursor-on-overwrite/read-only-flag'.
;;
;;  2. When an input method is in use, the cursor color changes to
;;     `curchg-input-method-cursor-color'.  This is controlled by
;;     command `change-cursor-mode' and user option
;;     `curchg-change-cursor-on-input-method-flag'.
;;
;;  3. When Emacs is idle, the cursor type changes to
;;     `curchg-idle-cursor-type'.  This is controlled by command
;;     `toggle-cursor-type-when-idle'.
;;
;;  To turn on all three types of cursor change by default, put the
;;  following in your Emacs init file (~/.emacs):
;;
;;    (require 'cursor-chg)  ; Load this library
;;    (change-cursor-mode 1) ; On for overwrite/read-only/input mode
;;    (toggle-cursor-type-when-idle 1) ; On when idle
;; 
;;  Note: Library `oneonone.el' provides the same functionality as
;;  library `cursor-chg.el', and more.  If you use library
;;  `oneonone.el', then do NOT also use library `cursor-chg.el'.
;;
;;  Note for Emacs 20: There is a bug in Emacs 20 which can lead to a
;;  fatal error (Emacs crash) when using `query-replace' with
;;  idle-cursor change enabled.  If you use Emacs 20, then consider
;;  using `toggle-cursor-type-when-idle' to disable idle-cursor change
;;  while you use `query-replace'.
;; 
;;  User options defined here:
;;
;;    `curchg-change-cursor-on-input-method-flag',
;;    `curchg-change-cursor-on-overwrite/read-only-flag',
;;    `curchg-default-cursor-color', `curchg-default-cursor-type',
;;    `curchg-idle-cursor-type', `curchg-input-method-cursor-color',
;;    `curchg-overwrite/read-only-cursor-type'.
;;
;;  Commands defined here:
;;
;;    `change-cursor-mode', `curchg-change-cursor-when-idle-interval',
;;    `curchg-set-cursor-type', `curchg-toggle-cursor-type-when-idle',
;;    `set-cursor-type', `toggle-cursor-type-when-idle'.
;;
;;  Internal variables defined here:
;;
;;    `curchg-change-cursor-when-idle-p', `curchg-idle-interval',
;;    `curchg-idle-timer', `curchg-last-cursor-type'.
;;
;;  Non-interactive functions defined here:
;;
;;    `curchg-change-cursor-on-input-method',
;;    `curchg-change-cursor-on-overwrite/read-only',
;;    `curchg-change-cursor-to-idle-type',
;;    `curchg-change-cursor-to-idle-type-off'.
;;
;;  Acknowledgements:
;;
;;  The cursor-changing on input method and read-only was inspired by
;;  Juri Linkov <juri@jurta.org>.  Joe Casadonte <joc@netaxs.com>
;;  wrote a similar hook (`joc-cursor-type-set-hook'), which he got
;;  from Steve Kemp...
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;;; Change log:
;;
;; 2006/10/28 dadams
;;     curchg-default-cursor-color, curchg-input-method-cursor-color:
;;       Changed :type to 'color for Emacs 21+.
;; 2006/09/04 dadams
;;     curchg-idle-timer: Cancel beforehand, and cancel after defining.
;;     curchg-toggle-cursor-type-when-idle:
;;       Use curchg-change-cursor-to-idle-type-off on pre-command-hook.
;;       Don't read an event; just turn it on.
;;     Added: curchg-change-cursor-to-idle-type-off.
;; 2006/09/03 dadams
;;     Created.
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;; 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, 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; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth
;; Floor, Boston, MA 02110-1301, USA.
;; 
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; 
;;; Code:


;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;


;;------- User Options -------------------------------------

;; Emacs 20 only
(unless (fboundp 'define-minor-mode)
  (defcustom change-cursor-mode nil
    "*Toggle changing cursor type and color.
Setting this variable directly does not take effect;
use either \\[customize] or command `change-cursor-mode'."
    :set (lambda (symbol value) (change-cursor-mode (if value 1 -1)))
    :initialize 'custom-initialize-default
    :type 'boolean :group 'cursor :require 'cursor-chg))

(defcustom curchg-change-cursor-on-input-method-flag t
  "*Non-nil means to use a different cursor when using an input method."
  :type 'boolean :group 'cursor)

(defcustom curchg-change-cursor-on-overwrite/read-only-flag t
  "*Non-nil means use a different cursor for overwrite mode or read-only."
  :type 'boolean :group 'cursor)

(defcustom curchg-default-cursor-color (or (cdr (assq 'cursor-color default-frame-alist))
                                           "Red")
  "*Default text cursor color for non-special frames."
  :type (if (>= emacs-major-version 21) 'color 'string) :group 'cursor)

(defcustom curchg-default-cursor-type 'bar "*Default text cursor type."
  :type 'symbol :group 'cursor)

(defcustom curchg-idle-cursor-type 'box
  "*Text cursor type when Emacs is idle."
  :type 'symbol :group 'cursor)

(defcustom curchg-input-method-cursor-color "Orange"
  "*Default cursor color if using an input method.
This has no effect if `curchg-change-cursor-on-input-method-flag' is nil."
  :type (if (>= emacs-major-version 21) 'color 'string) :group 'cursor)

(defcustom curchg-overwrite/read-only-cursor-type 'box
  "*Default text cursor type for overwrite mode or read-only buffer.
This applies only to non-special frames.  This has no effect if
`curchg-change-cursor-on-overwrite/read-only-flag' is nil."
  :type 'symbol :group 'cursor)


;;------- Internal Variables -------------------------------

(defvar curchg-last-cursor-type curchg-default-cursor-type "Saved last cursor type.")

(defvar curchg-idle-interval 2
  "Number of seconds to wait before changing to alternate cursor type.
The alternate cursor type is `curchg-idle-cursor-type'.
Do NOT change this yourself to change the wait period; instead, use
`\\[curchg-change-cursor-when-idle-interval]'.")

(defvar curchg-idle-timer
  (progn                                ; Cancel to prevent duplication.
    (when (boundp 'curchg-idle-timer) (cancel-timer curchg-idle-timer))
    (run-with-idle-timer curchg-idle-interval t 'curchg-change-cursor-to-idle-type))
  "Timer used to change the cursor to alternate type when Emacs is idle.")

;; Turn it off, by default.  You must use `toggle-cursor-type-when-idle' to turn it on.
(cancel-timer curchg-idle-timer)

(defvar curchg-change-cursor-when-idle-p nil
  "Non-nil means to use an alternate cursor type whenever Emacs is idle.
Do NOT change this yourself; instead, use `\\[toggle-cursor-type-when-idle]'.")


;;------- Commands -----------------------------------------

(unless (fboundp 'set-cursor-type) (defalias 'set-cursor-type 'curchg-set-cursor-type))
;; This is essentially from Juri Linkov <juri@jurta.org>.
(defun curchg-set-cursor-type (cursor-type)
  "Set the cursor type of the selected frame to CURSOR-TYPE.
When called interactively, prompt for the type to use.
To get the frame's current cursor type, use `frame-parameters'."
  (interactive
   (list (intern (completing-read "Cursor type: "
                                  (mapcar 'list '("box" "hollow" "bar" "hbar" nil))))))
  (modify-frame-parameters (selected-frame) (list (cons 'cursor-type cursor-type))))

(defalias 'toggle-cursor-type-when-idle 'curchg-toggle-cursor-type-when-idle)
(defun curchg-toggle-cursor-type-when-idle (&optional arg)
"Turn on or off automatically changing cursor type when Emacs is idle.
When on, use `curchg-idle-cursor-type' whenever Emacs is idle.
With prefix argument, turn on if ARG > 0; else turn off."
  (interactive "P")
  (setq curchg-change-cursor-when-idle-p
        (if arg (> (prefix-numeric-value arg) 0) (not curchg-change-cursor-when-idle-p)))
  (cond (curchg-change-cursor-when-idle-p
         (timer-activate-when-idle curchg-idle-timer)
         (add-hook 'pre-command-hook 'curchg-change-cursor-to-idle-type-off)
         (message "Turned ON changing cursor when Emacs is idle."))
        (t
         (cancel-timer curchg-idle-timer)
         (remove-hook 'pre-command-hook 'curchg-change-cursor-to-idle-type-off)
         (message "Turned OFF changing cursor when Emacs is idle."))))

(defun curchg-change-cursor-when-idle-interval (secs)
  "Set wait until automatically change cursor type when Emacs is idle.
Whenever Emacs is idle for this many seconds, the cursor type will
change to `curchg-idle-cursor-type'.

To turn on or off automatically changing the cursor type when idle,
use `\\[toggle-cursor-type-when-idle]."
  (interactive
   "nSeconds to idle, before changing cursor type: ")
  (timer-set-idle-time curchg-idle-timer
                       (setq curchg-idle-interval secs)
                       t))

(if (fboundp 'define-minor-mode)
    ;; Emacs 21 and later.
    (define-minor-mode change-cursor-mode
        "Toggle changing cursor type and color.
With numeric ARG, turn cursor changing on if and only if ARG is positive.

When this mode is on, `curchg-change-cursor-on-input-method' and
`curchg-change-cursor-on-overwrite/read-only-flag' control cursor
changing."
      :init-value nil :global t :group 'frames
      :link `(url-link :tag "Send Bug Report"
              ,(concat "mailto:" "drew.adams" "@" "oracle" ".com?subject=\
cursor-chg.el bug: \
&body=Describe bug here, starting with `emacs -q'.  \
Don't forget to mention your Emacs and library versions."))
      :link '(url-link :tag "Other Libraries by Drew"
              "http://www.emacswiki.org/cgi-bin/wiki/DrewsElispLibraries")
      :link '(url-link :tag "Download" "http://www.emacswiki.org/cgi-bin/wiki/cursor-chg.el")
      :link '(url-link :tag "Description"
              "http://www.emacswiki.org/cgi-bin/wiki/ChangingCursorDynamically")
      :link '(emacs-commentary-link :tag "Commentary" "cursor-chg")
      (cond (change-cursor-mode
             (if curchg-change-cursor-on-overwrite/read-only-flag
                 (add-hook 'post-command-hook 'curchg-change-cursor-on-overwrite/read-only)
               (curchg-set-cursor-type curchg-default-cursor-type)
               (remove-hook 'post-command-hook 'curchg-change-cursor-on-overwrite/read-only))
             (if curchg-change-cursor-on-input-method-flag
                 (add-hook 'post-command-hook 'curchg-change-cursor-on-input-method)
               (setq current-input-method nil)
               (curchg-change-cursor-on-input-method)
               (remove-hook 'post-command-hook 'curchg-change-cursor-on-input-method)))
            (t
             (curchg-set-cursor-type curchg-default-cursor-type)
             (setq current-input-method nil)
             (curchg-change-cursor-on-input-method)
             (remove-hook 'post-command-hook 'curchg-change-cursor-on-overwrite/read-only)
             (remove-hook 'post-command-hook 'curchg-change-cursor-on-input-method))))

  ;; Emacs 20
  (defun change-cursor-mode (&optional arg)
    "Toggle changing cursor type and color.
With numeric ARG, turn cursor changing on if and only if ARG is positive.

When this mode is on, `curchg-change-cursor-on-input-method' and
`curchg-change-cursor-on-overwrite/read-only-flag' control cursor
changing."
    (interactive "P")
    (setq change-cursor-mode
          (if arg (> (prefix-numeric-value arg) 0) (not change-cursor-mode)))
    (cond (change-cursor-mode
           (if curchg-change-cursor-on-overwrite/read-only-flag
               (add-hook 'post-command-hook 'curchg-change-cursor-on-overwrite/read-only)
             (curchg-set-cursor-type curchg-default-cursor-type)
             (remove-hook 'post-command-hook 'curchg-change-cursor-on-overwrite/read-only))
           (if curchg-change-cursor-on-input-method-flag
               (add-hook 'post-command-hook 'curchg-change-cursor-on-input-method)
             (setq current-input-method nil)
             (curchg-change-cursor-on-input-method)
             (remove-hook 'post-command-hook 'curchg-change-cursor-on-input-method))
           (message "Change cursor on overwrite/read-only: %s; on input method: %s"
             (if curchg-change-cursor-on-overwrite/read-only-flag "ON" "OFF")
             (if curchg-change-cursor-on-input-method-flag "ON" "OFF")))
          (t
           (curchg-set-cursor-type curchg-default-cursor-type)
           (setq current-input-method nil)
           (curchg-change-cursor-on-input-method)
           (remove-hook 'post-command-hook 'curchg-change-cursor-on-overwrite/read-only)
           (remove-hook 'post-command-hook 'curchg-change-cursor-on-input-method)
           (message "Turned OFF changing cursor on overwrite/read-only and input method")))))


;;------- Non-Interactive Functions ------------------------

;; This is inspired by code from Juri Linkov <juri@jurta.org>.
(defun curchg-change-cursor-on-input-method ()
  "Set cursor type depending on whether an input method is used or not."
  (set-cursor-color (if current-input-method
                        curchg-input-method-cursor-color
                      curchg-default-cursor-color)))

;; This is from Juri Linkov <juri@jurta.org>, with read-only added.
(defun curchg-change-cursor-on-overwrite/read-only ()
  "Set cursor type differently for overwrite mode and read-only buffer.
That is, use one cursor type for overwrite mode and read-only buffers,
and another cursor type otherwise."
  (curchg-set-cursor-type (if (or buffer-read-only overwrite-mode)
                            curchg-overwrite/read-only-cursor-type
                          curchg-default-cursor-type)))

(defun curchg-change-cursor-to-idle-type ()
  "Change the cursor to `curchg-idle-cursor-type' when Emacs is idle."
  (let ((type (cdr (assoc 'cursor-type (frame-parameters)))))
    (unless (eq type curchg-idle-cursor-type)
      (setq curchg-last-cursor-type type)
      (curchg-set-cursor-type curchg-idle-cursor-type))))

(defun curchg-change-cursor-to-idle-type-off ()
  "Turn off changing the cursor to `curchg-idle-cursor-type' when idle."
  (when curchg-last-cursor-type (curchg-set-cursor-type curchg-last-cursor-type)))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

(provide 'cursor-chg)

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; cursor-chg.el ends here
