Merged from jeho@jeho.org--2005 (patch 51-53), mwolson@gnu.org--2006 (patch 15)
[planner-el.git] / planner-timeclock.el
blob57defebca1469d03397fe33da7386cd04d53074e
1 ;;; planner-timeclock.el --- Timeclock integration for the Emacs Planner
3 ;; Copyright (C) 2001, 2004, 2005 Free Software Foundation, Inc.
4 ;; Parts copyright (C) 2005 Peter K. Lee
6 ;; Author: John Wiegley <johnw@gnu.org>
7 ;; Keywords: planner, timeclock
8 ;; URL: http://www.plannerlove.com/
10 ;; This file is free software; you can redistribute it and/or modify
11 ;; it under the terms of the GNU General Public License as published by
12 ;; the Free Software Foundation; either version 2, or (at your option)
13 ;; any later version.
15 ;; This file is distributed in the hope that it will be useful,
16 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ;; GNU General Public License for more details.
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING. If not, write to
22 ;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 ;; Boston, MA 02110-1301, USA.
25 ;;; Commentary:
27 ;; This module allows you to clock in and clock out of your projects.
28 ;; You can also generate reports with the <timeclock-report> tag.
30 ;; timeclock.el is part of GNU Emacs. If you are using XEmacs, please
31 ;; use the version in the contrib directory -- otherwise, just use the
32 ;; version that comes with Emacs.
34 ;;; Contributors:
36 ;; Yann Hodique helped port this to Muse.
38 ;; Peter K. Lee helped fix an issue involving planner-multi.el with
39 ;; the Muse port.
41 ;;; Code:
43 (require 'planner)
44 (require 'timeclock)
45 (require 'planner-schedule)
46 (require 'easymenu)
47 (require 'advice)
49 (defvar planner-timeclock-current-task nil
50 "Description of current task.")
52 (defalias 'planner-timeclock-in 'planner-task-in-progress)
54 (defadvice timeclock-out (after planner-timeclock activate)
55 "Clear `planner-timeclock-current-task.'"
56 (setq planner-timeclock-current-task nil))
58 (defun planner-timeclock-plan-string (task-info &optional plans)
59 "Return the string for the plan part of the timeclock entry for TASK-INFO."
60 (let ((plan-link (if (featurep 'planner-multi)
61 ;; Remove the date links
62 (planner-multi-make-link
63 (or (planner-multi-filter-links
64 planner-date-regexp
65 (if plans
66 (planner-multi-split plans)
67 (planner-multi-task-link-as-list task-info))
69 (list (planner-task-plan task-info))))
70 (planner-make-link
71 (or plans (planner-task-plan task-info))))))
72 (if plan-link
73 (concat plan-link ": ")
74 "")))
76 (defun planner-timeclock-task-marked (old-status new-status)
77 "Clock out if the task description matches the one clocked in."
78 (cond
79 ((string= new-status "X")
80 (when (and planner-timeclock-current-task
81 (string= (planner-task-description (planner-current-task-info))
82 planner-timeclock-current-task)
83 (timeclock-currently-in-p))
84 (timeclock-out 1)))
85 ((string= new-status "P")
86 (when (and planner-timeclock-current-task
87 (string= (planner-task-description (planner-current-task-info))
88 planner-timeclock-current-task)
89 (timeclock-currently-in-p))
90 (timeclock-out)))
91 ((string= new-status "o")
92 (let* ((task-info (planner-current-task-info))
93 (project (concat
94 (planner-timeclock-plan-string task-info)
95 (planner-task-description task-info))))
96 (if (timeclock-currently-in-p)
97 (timeclock-change nil project)
98 (timeclock-in nil project))
99 (setq planner-timeclock-current-task (planner-task-description task-info)))))
101 (add-hook 'planner-mark-task-hook 'planner-timeclock-task-marked)
103 (defadvice planner-replan-task (around planner-timeclock activate)
104 "Edit the clocked in task as well."
105 (let ((info (planner-current-task-info)))
106 ad-do-it
107 (with-current-buffer (find-file-noselect timeclock-file)
108 (goto-char (point-min))
109 (while (re-search-forward
110 (concat
111 "^. [^ ]+ [^ ]+ "
112 "\\(" (regexp-quote (planner-timeclock-plan-string info)) "\\)"
113 (regexp-quote (planner-task-description info)) "$") nil t)
114 (replace-match
115 (save-match-data (planner-timeclock-plan-string nil (ad-get-arg 0)))
116 t t nil 1))
117 (save-buffer)
118 (kill-buffer (current-buffer)))))
120 (defadvice planner-edit-task-description (around planner-timeclock activate)
121 "Update the timelog as well. Warning! Do not have duplicate tasks!"
122 (let ((info (planner-current-task-info)))
123 (when (string= (planner-task-description info) planner-timeclock-current-task)
124 (setq planner-timeclock-current-task (ad-get-arg 0)))
125 ad-do-it
126 (with-current-buffer (find-file-noselect timeclock-file)
127 (goto-char (point-min))
128 (while (re-search-forward
129 (concat
130 "^. [^ ]+ [^ ]+ "
131 (regexp-quote (planner-timeclock-plan-string info))
132 "\\("
133 (regexp-quote (planner-task-description info))
134 "\\)$")
135 nil t)
136 (replace-match (ad-get-arg 0) t t nil 1))
137 (save-buffer)
138 (kill-buffer (current-buffer)))))
140 ;;;###autoload
141 (defun planner-timeclock-report-tag (beg end highlight-p)
142 "Replace the region BEG to END with a timeclock report.
143 If HIGHLIGHT-P is non-nil, the output is also displayed."
144 (require 'timeclock)
145 (if highlight-p
146 (add-text-properties
147 beg end (list 'display
148 (with-temp-buffer
149 (timeclock-generate-report muse-publishing-p)
150 (buffer-string))))
151 (delete-region beg end)
152 (timeclock-generate-report muse-publishing-p)
153 (add-text-properties beg (point) '(read-only t))))
155 (add-to-list 'planner-markup-tags
156 '("timeclock-report" nil nil t planner-timeclock-report-tag))
157 (planner-option-customized 'planner-markup-tags planner-markup-tags)
159 (defun planner-timeclock-task-plan (info)
160 "Return the first plan page associated with INFO."
161 (car (elt info 0)))
163 (defun planner-timeclock-task-plan-as-list (info)
164 "Return all the plan pages associated with INFO."
165 (elt info 0))
167 (defun planner-timeclock-task-description (info)
168 "Return the descrption associated with INFO."
169 (elt info 1))
171 (defun planner-timeclock-task-length (info)
172 "Return the length associated with INFO."
173 (elt info 2))
175 (defun planner-timeclock-task-info (entry)
176 "Parse ENTRY and return a list of the form (plan task length).
177 See `timeclock-log-data' for the format of ENTRY. Note that the
178 project field in `timeclock-log-data' is 'project: task' here."
179 (let ((project (if (stringp entry) entry (timeclock-entry-project entry)))
180 plan task)
181 (when project
182 (with-planner
183 (cond
184 ;; No plan, just the task
185 ((string-match "^\\s-*:\\s-+" project)
186 (setq task (substring project (match-end 0))))
187 ;; Single link
188 ((string-match (concat "^\\(" muse-explicit-link-regexp "\\): ") project)
189 (setq plan (list (match-string 1 project)))
190 (setq task (substring project (match-end 0))))
191 ;; Multi link
192 ((and
193 (featurep 'planner-multi)
194 (string-match
195 (concat "^\\(\\(?:" muse-explicit-link-regexp "\\)"
196 "\\(?:" planner-multi-separator
197 "\\(?:" muse-explicit-link-regexp "\\)\\)*\\): ")
198 project))
199 (setq task (substring project (match-end 0)))
200 (setq plan (planner-multi-split (match-string 1 project))))
201 ;; Nothing whatsoever.
202 (t (setq task project)))
203 (list plan task (unless (stringp entry) (timeclock-entry-length entry)))))))
205 (condition-case nil
206 (let ((map planner-mode-map))
207 (define-key map "\C-c\C-i" 'planner-task-in-progress)
208 (define-key map "\C-c\C-o" 'timeclock-out)
209 (if (featurep 'xemacs)
210 (progn
211 (define-key map (kbd "C-c C-T C-i") 'planner-task-in-progress)
212 (define-key map (kbd "C-c C-T C-o") 'timeclock-out))
213 (define-key map (kbd "C-c C-S-t C-i") 'planner-task-in-progress)
214 (define-key map (kbd "C-c C-S-t C-o") 'timeclock-out)))
215 (error (message "Could not bind timeclock keys in planner mode")))
217 (condition-case nil
218 ;; XEmacs seems to be missing this function in some released
219 ;; versions of XEmacs21.
220 (if (fboundp 'easy-menu-create-menu)
221 (easy-menu-add-item planner-mode-map
222 '("Planner")
223 (easy-menu-create-menu
224 "Timeclock"
225 '(["Clock into a task" planner-timeclock-in]
226 ["Clock out" timeclock-out])))
227 (easy-menu-add-item planner-menu
228 '("Timeclock")
229 ["Clock into a task" planner-timeclock-in t]
230 "Plan")
231 (easy-menu-add-item planner-menu
232 '("Timeclock")
233 ["Clock out" timeclock-out t]
234 "Plan")))
236 (provide 'planner-timeclock)
238 ;;; planner-timeclock.el ends here