;;; dvc-ui.el --- User interface (keybinding, menus) for DVC

;; Copyright (C) 2005-2009 by all contributors

;; Author: Matthieu Moy <Matthieu.Moy@imag.fr>
;; Contributions from:
;;    Stefan Reichoer, <stefan@xsteve.at>

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

;; This file 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 GNU Emacs; see the file COPYING.  If not, write to
;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Commentary:

;;

;;; Code:

(eval-and-compile
  (require 'dvc-utils))

(require 'dvc-register)

;;;###autoload
(eval-and-compile
  (require 'easymenu))

(require 'dvc-register)

;; ----------------------------------------------------------------------------
;; Key bindings
;; ----------------------------------------------------------------------------
;;
;; Conventions
;;
;; - Meta Rules:
;; 0. If you feel a binding odd more than 3 times, report it to dvc dev mailing
;;    list. Especially about some danger functions like undo, merge; and functions
;;    taking longer time to be executed.
;;
;; 1. Our key binding should not surprise "general users" even if the
;;    binding is convenience. Instead, provide hooks for customization.
;;    We know it is difficult to define "general users".
;;
;; 2. Write the result of discussion here.
;;
;; 3. See http://mail.gnu.org/archive/html/emacs-devel/2004-03/msg00608.html
;;
;;
;; - Generic Rules:
;;
;; 1. dvc-status should have similar key bindings to pcl-cvs.
;;    If a pcl-cvs's binding is too odd, talk it in dvc dev mailing list.
;;
;; 2. Define common prefix for command groups like '>'.
;;    So a key binding for a grouped command has following structure:
;;
;;    ?{prefix} ?{suffix}
;;
;;    e.g. `get something commands' should have `>' as prefix.
;;
;;    About suffix part, ? should show the help for the groups.
;;
;;    e.g. `help for `get something commands'' is >?.
;;
;;    BTW, The prefix ? is for help related command.
;;    So `? >' can stand for "show the help for get-something related
;;    command". In other word, prefix and suffix is swappable if
;;    prefix or suffix is `?'.
;;
;; 3. Upper case for commands taking longer time to be executed.
;; 4. Lower case for commands taking shorter time to be executed.
;; 5. dired's binding is also helpful.
;;
;;
;; - Concrete Rules:
;;

;; t  ?    list all toggles
;; c       dvc-edit-log
;; RET     Open the thing at point
;;

;;
;; Definitions for key concrete rules
;;

;; common keys
;;;###autoload
(defvar dvc-key-help        ??)         ; help
(defvar dvc-key-mark-prefix ?*)         ; other mark related command prefix
(defvar dvc-key-add-bookmark    ?b)     ; add this to bookmark
(defvar dvc-key-get      ?>)            ; prefix for getting something
(defvar dvc-key-reflect  ?<)            ; mirror, apply, install...
(defvar dvc-key-parent   ?^)            ; visit uppper XXX. e.g. directory
;;;###autoload
(defvar dvc-key-diff     ?=)            ; one shot
;;;###autoload
(defvar dvc-key-status   ?s)            ; one shot

(defvar dvc-key-add      ?a)            ; prefix for adding something
;;;###autoload
(defvar dvc-key-show-bookmark ?b)       ; show bookmark
(defvar dvc-key-diff-prefix ?d)
;;;###autoload
(defvar dvc-key-file-diff ?d)
;;;###autoload
(defvar dvc-key-tree-lint ?l)
;;;###autoload
(defvar dvc-key-logs      ?L)
;;;###autoload
(defvar dvc-key-ediff     ?e)
;;;###autoload
(defvar dvc-key-log-entry ?a)
(defvar dvc-key-refresh   ?g)           ; refresh buffer
;;;###autoload
(defvar dvc-key-inventory ?i)           ; inventory
(defvar dvc-key-mark      ?m)           ; mark
(defvar dvc-key-next      ?n)           ; next item
(defvar dvc-key-previous  ?p)           ; previous item
(defvar dvc-key-quit      ?q)           ; quit
(defvar dvc-key-remove    ?r)           ; prefix for remove something
(defvar dvc-key-move      ?R)           ; prefix for move/rename something
(defvar dvc-key-toggle    ?t)           ; prefix for toggle
(defvar dvc-key-unmark    ?u)           ; unmark
(defvar dvc-key-popup-menu ?\C-j)
;;;###autoload
(defvar dvc-key-kill-ring-prefix ?w)
;;;###autoload
(defvar dvc-key-commit    ?c)           ; actually edit-log, but
                                        ; that's what you do when you
                                        ; want to commit.
;;;###autoload
(defvar dvc-key-update     ?u)          ; to run dvc update
(defvar dvc-key-replay     ?r)          ; to run dvc replay
(defvar dvc-key-star-merge ?s)          ; to run dvc star-merge
;;;###autoload
(defvar dvc-key-missing    ?m)          ; to run dvc missing

;;;###autoload
(defvar dvc-key-buffer-prefix ?B)       ; prefix to switch to XXX buffer
(defvar dvc-key-view-buffer-prefix ?v)  ; prefix to view XXX buffer
(defvar dvc-key-directory-prefix ?D)

;;;###autoload
(defvar dvc-key-file-prefix ?f)         ; file specific functions
(defvar dvc-key-branch-prefix ?o)       ; branch specific functions
(defvar dvc-key-merge-prefix ?M)
(defvar dvc-key-tag ?T)
(defvar dvc-key-revert ?U)
(defvar dvc-key-working-copy ?W)        ; Affecting on working copy
(defvar dvc-key-partner-file-prefix ?f) ; Only used in the bookmarks buffer
(defvar dvc-key-tagging-method-prefix ?#)
(defvar dvc-key-id ?t)                  ; `t' for `t'ag.

;; functions for creating key groups
;;;###autoload
(progn
  (defun dvc-key-group (prefix &rest keys)
    (apply 'vector prefix keys)))

(defun  dvc-prefix-toggle (&rest keys)
  (dvc-key-group dvc-key-toggle keys))

(defun dvc-prefix-add (&rest keys)
  (dvc-key-group dvc-key-add keys))

(defun dvc-prefix-remove (&rest keys)
  (dvc-key-group dvc-key-remove keys))

(defun dvc-prefix-move (&rest keys)
  (dvc-key-group dvc-key-move keys))

(defun dvc-prefix-mark (&rest keys)
  (dvc-key-group dvc-key-mark-prefix keys))

(defun dvc-prefix-diff (&rest keys)
  (dvc-key-group dvc-key-diff-prefix keys))

(defun dvc-prefix-merge (&rest keys)
  (dvc-key-group dvc-key-merge-prefix keys))

(defun dvc-prefix-directory (&rest keys)
  (dvc-key-group dvc-key-directory-prefix keys))

;;;###autoload
(progn
  (defun dvc-prefix-file (&rest keys)
    (dvc-key-group dvc-key-file-prefix keys)))

;;;###autoload
(progn
  (defun dvc-prefix-branch (&rest keys)
    (dvc-key-group dvc-key-branch-prefix keys)))

;;;###autoload
(progn
  (defun dvc-prefix-kill-ring (&rest keys)
    (dvc-key-group dvc-key-kill-ring-prefix keys)))

;;;###autoload
(progn
  (defun dvc-prefix-view-buffer (&rest keys)
    (dvc-key-group dvc-key-view-buffer-prefix keys)))

;;;###autoload
(progn
  (defun dvc-prefix-buffer (&rest keys)
    (dvc-key-group dvc-key-buffer-prefix keys)))

(defun dvc-prefix-working-copy (&rest keys)
  (dvc-key-group dvc-key-working-copy keys))

(defun dvc-prefix-partner-file (&rest keys)
  (dvc-key-group dvc-key-partner-file-prefix keys))

(defun dvc-prefix-tag (&rest keys)
  (dvc-key-group dvc-key-tag keys))

(defun dvc-prefix-tagging-method (&rest keys)
  (dvc-key-group dvc-key-tagging-method-prefix keys))

;; predefined key vectors
(defvar dvc-keyvec-toggle-set     (dvc-prefix-toggle ?+))
(defvar dvc-keyvec-toggle-reset   (dvc-prefix-toggle ?-))
(defvar dvc-keyvec-toggle-invert  (dvc-prefix-toggle ?~))

;;;###autoload
(defvar dvc-keyvec-help    (vector dvc-key-help))
(defvar dvc-keyvec-parent  (vector dvc-key-parent))
(defvar dvc-keyvec-add     (vector dvc-key-add))
(defvar dvc-keyvec-remove  (vector dvc-key-remove))
(defvar dvc-keyvec-get     (vector dvc-key-get))
(defvar dvc-keyvec-refresh (vector dvc-key-refresh))

(defvar dvc-keyvec-next     (vector dvc-key-next))
(defvar dvc-keyvec-previous (vector dvc-key-previous))

(defvar dvc-keyvec-mark     (vector dvc-key-mark))
(defvar dvc-keyvec-unmark   (vector dvc-key-unmark))
(defvar dvc-keyvec-mark-all (dvc-prefix-mark ?*))
(defvar dvc-keyvec-unmark-all (dvc-prefix-mark ?!))
(defvar dvc-keyvec-quit (vector dvc-key-quit))
(defvar dvc-keyvec-popup-menu   (vector dvc-key-popup-menu))


;;;###autoload
(defvar dvc-keyvec-ediff (vector dvc-key-ediff))
;;;###autoload
(defvar dvc-keyvec-tree-lint (vector dvc-key-tree-lint))
;;;###autoload
(defvar dvc-keyvec-logs      (vector dvc-key-logs))
;;;###autoload
(defvar dvc-keyvec-log-entry (vector dvc-key-log-entry))
;;;###autoload
(defvar dvc-keyvec-diff (vector dvc-key-diff))
;;;###autoload
(defvar dvc-keyvec-status (vector dvc-key-status))
;;;###autoload
(defvar dvc-keyvec-file-diff (vector dvc-key-file-diff))
;;;###autoload
(defvar dvc-keyvec-file-diff (vector dvc-key-file-diff))
;;;###autoload
(defvar dvc-keyvec-commit (vector dvc-key-commit))
;;;###autoload
(defvar dvc-keyvec-update     (vector dvc-key-update))
;;;###autoload
(defvar dvc-keyvec-missing     (vector dvc-key-missing))
(defvar dvc-keyvec-replay     (vector dvc-key-replay))
(defvar dvc-keyvec-star-merge (vector dvc-key-star-merge))

(defvar dvc-keyvec-reflect  (vector dvc-key-reflect))
(defvar dvc-keyvec-revert   (vector dvc-key-revert))

;;;###autoload
(defvar dvc-keyvec-inventory (vector dvc-key-inventory))

;;;###autoload
(defvar dvc-keyvec-show-bookmark (vector dvc-key-show-bookmark))
(defvar dvc-keyvec-add-bookmark (vector dvc-key-add-bookmark))

(defvar dvc-keyvec-tag (vector dvc-key-tag))
(defvar dvc-keyvec-kill-ring (vector dvc-key-kill-ring-prefix))

(defvar dvc-keyvec-id (vector dvc-key-id))
(defvar dvc-keyvec-toggle (vector dvc-key-toggle))


;;
;; Global
;;
;; FIXME: replace all those tla-... by dvc-... !!!
;;;###autoload
(defvar dvc-global-keymap
  (let ((map (make-sparse-keymap)))
    (define-key map [?U]                     'tla-undo)
    (define-key map [?R]                     'tla-redo)
    (define-key map [?t]                     'tla-tag-insert)
    (define-key map [?r]                     'tla-tree-revisions)
    (define-key map [(meta ?l)]              'tla-tree-lint)
    ;;(define-key map [(meta ?o)]            'tla-file-view-original)
    (define-key map [?p]                     'dvc-submit-patch)
    (define-key map dvc-keyvec-log-entry     'dvc-add-log-entry)
    (define-key map [?A] 'tla-archives)
    (define-key map dvc-keyvec-file-diff     'dvc-file-diff)
    (define-key map dvc-keyvec-ediff         'dvc-file-ediff)
    (define-key map dvc-keyvec-diff          'dvc-diff)
    (define-key map dvc-keyvec-status        'dvc-status)
    (define-key map dvc-keyvec-commit        'dvc-log-edit)
    (define-key map dvc-keyvec-inventory     'dvc-inventory)
    (define-key map dvc-keyvec-logs          'dvc-log)
    ;; dvc: l runs changelog, M-l runs tree-lint for Arch
    (define-key map [?l]                     'dvc-changelog)
    (define-key map [?I]                     'dvc-init)
    (define-key map [?C]                     'dvc-clone)
    (define-key map [?F]                     'dvc-pull)
    (define-key map [?P]                     'dvc-push)
    (define-key map dvc-keyvec-update        'dvc-update)
    (define-key map [?m]                     'dvc-missing)
    (define-key map [?M]                     'dvc-merge)
    (define-key map dvc-keyvec-show-bookmark 'dvc-bookmarks)
    (define-key map dvc-keyvec-help          'tla-help)

    ;; branch handling
    (define-key map (dvc-prefix-branch ?c)   'dvc-create-branch)
    (define-key map (dvc-prefix-branch ?s)   'dvc-select-branch)
    (define-key map (dvc-prefix-branch ?l)   'dvc-list-branches)

    ;; file specific functionality
    (define-key map (dvc-prefix-file ?a) 'dvc-add-files)
    (define-key map (dvc-prefix-file ?D) 'dvc-remove-files)
    (define-key map (dvc-prefix-file ?R) 'dvc-revert-files)
    (define-key map (dvc-prefix-file ?M) 'dvc-rename)
    (define-key map (dvc-prefix-file ?X) 'dvc-purge-files)
    (define-key map (dvc-prefix-file ?=) 'dvc-file-diff)

    (define-key map (dvc-prefix-view-buffer
                     ?p)                     'dvc-show-process-buffer)
    (define-key map (dvc-prefix-view-buffer
                     ?e)                     'dvc-show-last-error-buffer)
    (define-key map (dvc-prefix-view-buffer
                     ?l)                     'dvc-open-internal-log-buffer)
    (define-key map (dvc-prefix-view-buffer
                     dvc-key-diff)           'tla-changes-goto)
    (define-key map (dvc-prefix-view-buffer
                     dvc-key-status)         'baz-status-goto)
    (define-key map (dvc-prefix-view-buffer
                     dvc-key-inventory)      'tla-inventory-goto)
    (define-key map (dvc-prefix-view-buffer
                     ?L)                     'tla-tree-lint-goto)
    (define-key map (dvc-prefix-view-buffer ?r)   'tla-tree-revisions-goto)

    (define-key map (dvc-prefix-kill-ring ?a) 'tla-save-archive-to-kill-ring)
    (define-key map (dvc-prefix-kill-ring ?v) 'tla-save-version-to-kill-ring)
    (define-key map (dvc-prefix-kill-ring ?r) 'tla-save-revision-to-kill-ring)

    map)
  "Global keymap used by DVC.")


;;;###autoload
(defcustom dvc-prefix-key [(control x) ?V]
  "Prefix key for the DVC commands in the global keymap.

If you wish to disable the prefix key, set this variable to nil."
  :type '(choice (const [(control x) ?V])
                 (const [(control x) ?T])
                 (const [(control x) ?t])
                 (const [(control x) ?v ?t])
                 (const [(super v)])
                 (const [(hyper v)])
                 (const [(super t)])
                 (const [(hyper t)])
                 (const :tag "None" nil)
                 (sexp))
  :group 'tla-bindings
  :set  (lambda (var value)
          (if (boundp var)
              (global-unset-key (symbol-value var)))
          (set var value)
          (global-set-key (symbol-value var) dvc-global-keymap)))

;;;###autoload
(defun dvc-enable-prefix-key ()
  "Install the DVC prefix key globally."
  (interactive)
  (when dvc-prefix-key
    (global-set-key dvc-prefix-key dvc-global-keymap)))

;;;###autoload
(dvc-enable-prefix-key)

;; It is important that DVC has this key, so steal it from other
;; programs, but give the user a chance to override this.
;;;###autoload
(add-hook 'after-init-hook 'dvc-enable-prefix-key t)

;;;###autoload
(define-key ctl-x-4-map [?T] 'dvc-add-log-entry)

(defvar dvc-cmenu-map-template
  (let ((map (make-sparse-keymap)))
    ;; TODO: [return], "\C-m" => tla--generic-context-action
    (define-key map dvc-keyvec-help 'describe-mode)
    (define-key map [down-mouse-3] 'dvc-cmenu-popup-by-mouse)
    (define-key map dvc-keyvec-popup-menu 'dvc-cmenu-popup)
    map)
  "Template for keymaps used in items, files, changes, etc.")

;;
;; Global
;;
;;;###autoload
(easy-menu-add-item
 (and (boundp 'menu-bar-tools-menu) (dvc-do-in-gnu-emacs menu-bar-tools-menu))
 (dvc-do-in-xemacs '("Tools"))
 '("DVC"
   ["Show Bookmarks" dvc-bookmarks t]
   "---"
   "Tree Commands:"
   ["View Diff" dvc-diff t]
   ["View Status" dvc-status t]
   ["View Missing" dvc-missing t]
   ["View Log" dvc-log t]
   ["View ChangeLog" dvc-changelog t]
   ["Edit Commit Log" dvc-log-edit t]
   "---"
   "File Commands:"
   ["Add Files"  dvc-add-files t]
   ["Revert Files"  dvc-revert-files t]
   ["Remove Files"  dvc-remove-files t]
   ["Add Log Entry"  dvc-add-log-entry t]
   ;; ["Insert Arch Tag" tla-tag-insert t]
   ;; ["View File Diff" tla-file-diff t]
   ;; ["View File Ediff" tla-file-ediff t]
   ;; ["View Original" tla-file-view-original t]
   ;; ["View Conflicts" tla-view-conflicts t]
   "---"
   ["Initialize repository" dvc-init t]
   )
 "PCL-CVS")


;; Show the selected DVC in the modeline: M-x dvc-show-active-dvc
(defvar dvc-show-active-dvc nil)
(defvar dvc-show-active-dvc-string "")
(make-variable-buffer-local 'dvc-show-active-dvc-string)

(add-to-list 'minor-mode-alist '(dvc-show-active-dvc dvc-show-active-dvc-string))

(add-hook 'find-file-hooks 'dvc-find-file-hook)
(add-hook 'dired-mode-hook 'dvc-dired-hook)


(defun dvc-show-active-dvc (arg)
  "Toggle displaying a DVC string in the modeline.

If ARG is null, toggle displaying
If ARG is a number and is greater than zero, turn on visualization; otherwise,
turn off visualization."
  (interactive "P")
  (setq dvc-show-active-dvc (if arg
                                (> (prefix-numeric-value arg) 0)
                              (not dvc-show-active-dvc)))
  (when dvc-show-active-dvc
    (dvc-actualize-modeline)))

(defun dvc-dvc-file-has-conflict-p (filename)
  nil)

(defun dvc-find-file-hook ()
  "Set dvc-show-active-dvc-string, after loading a file.  Enter
smerge mode if the file has conflicts (detected by
<dvc>-dvc-file-has-conflict-p)."
  (when (dvc-current-active-dvc)
    (dvc-actualize-modeline)
    (when (dvc-call "dvc-file-has-conflict-p" (buffer-file-name))
      (dvc-funcall-if-exists smerge-mode 1)
      (message
       "Conflicts in file%s. Use M-x dvc-resolved RET when done."
       (if (boundp 'smerge-mode) ", entering SMerge mode" "")))))

(defun dvc-dired-hook ()
  "Set dvc-show-active-dvc-string for dired buffers."
  (dvc-actualize-modeline))

(defun dvc-actualize-modeline ()
  (let ((dvc (dvc-current-active-dvc)))
    ;;(when dvc-show-active-dvc (dvc-trace "dvc-actualize-modeline: %S %S" default-directory dvc))
    (setq dvc-show-active-dvc-string (if dvc (concat " DVC:" (symbol-name dvc))
                                       ""))))


(provide 'dvc-ui)
;;; dvc-ui.el ends here
