From 713328eb606b207f00dee212476db98389b13bd0 Mon Sep 17 00:00:00 2001 From: Carsten Dominik Date: Mon, 20 Oct 2008 09:38:23 +0200 Subject: [PATCH 1/2] Intermediate --- doc/org.texi | 15 +++++- lisp/ChangeLog | 7 +++ lisp/org-agenda.el | 115 ++++++++++++++++++++++++++++++--------------- 3 files changed, 97 insertions(+), 40 deletions(-) diff --git a/doc/org.texi b/doc/org.texi index 31996e0f0..f8abfa415 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -5994,6 +5994,9 @@ that entry would be in the original buffer (taken from a property, from a @code{org-columns-default-format}), will be used in the agenda. @tsubheading{Secondary filtering and query editing} +@cindex filtering, by tag and effort, in agenda +@cindex tag filtering, in agenda +@cindex effort filtering, in agenda @cindex query editing, in agenda @kindex / @@ -6004,7 +6007,17 @@ completion to select a tag (including any tags that do not have a selection character). The command then hides all entries that do not contain or inherit this tag. When called with prefix arg, remove the entries that @emph{do} have the tag. A second @kbd{/} at the prompt will unhide any -hidden entries. +hidden entries. If the first key you press is either @kbd{+} or @kbd{-}, the +previous filter will be narrowed by requiring or forbidding the selected +additional tag. Instead of pressing @kbd{+} or {-}, you can also use the +following command. + +@kindex \ +@item \ +Narrow the curent agenda fiter by an additional condition. When called with +prefix arg, remove the entries that @emph{do} have the tag. You can achive +the same effect by pressing @kbd{+} or @kbd{-} as the first key after the +@kbd{/} command. @kindex [ @kindex ] diff --git a/lisp/ChangeLog b/lisp/ChangeLog index 35ce9c39b..ea766c1aa 100755 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,5 +1,12 @@ + 2008-10-18 Carsten Dominik + * org-agenda.el (org-agenda-filter-tags,org-agenda-filter-form): + New variables. + (org-prepare-agenda): Reset the filter tags. + (org-agenda-filter-by-tag, org-agenda-filter-by-tag-show-all): + Show filter tags in mode line. + * org-table.el (orgtbl-to-html): Bind `html-table-tag' for the formatter. diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index e65d80bb6..f15067cce 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -1169,6 +1169,7 @@ The following commands are available: (org-defkey org-agenda-mode-map "{" 'org-agenda-manipulate-query-add-re) (org-defkey org-agenda-mode-map "}" 'org-agenda-manipulate-query-subtract-re) (org-defkey org-agenda-mode-map "/" 'org-agenda-filter-by-tag) +(org-defkey org-agenda-mode-map "\\" 'org-agenda-filter-by-tag-refine) (defvar org-agenda-keymap (copy-keymap org-agenda-mode-map) "Local keymap for agenda entries from Org-mode.") @@ -1958,6 +1959,7 @@ higher priority settings." (defun org-prepare-agenda (&optional name) (setq org-todo-keywords-for-agenda nil) (setq org-done-keywords-for-agenda nil) + (setq org-agenda-filter-tags nil) (if org-agenda-multi (progn (setq buffer-read-only nil) @@ -4118,47 +4120,76 @@ When this is the global TODO list, a prefix argument will be interpreted." (recenter window-line))) (defvar org-global-tags-completion-table nil) -(defun org-agenda-filter-by-tag (strip &optional char) +(defvar org-agenda-filter-tags nil) +(defvar org-agenda-filter-form nil) +(defun org-agenda-filter-by-tag (strip &optional char narrow) "Keep only those lines in the agenda buffer that have a specific tag. The tag is selected with its fast selection letter, as configured. -With prefix argument STRIP, remove all lines that do have the tag." +With prefix argument STRIP, remove all lines that do have the tag. +A lisp caller can specify CHAR. NARROW means that the new tag should be +used to narrow the search - the interactive user can also press `-' or `+' +to switch to narrowing." (interactive "P") - (let (char a tag tags (inhibit-read-only t)) - (message "Select tag [%s] or no tag [ ], [TAB] to complete, [/] to restore: " - (mapconcat - (lambda (x) (if (cdr x) (char-to-string (cdr x)) "")) - org-tag-alist-for-agenda "")) - (setq char (read-char)) - (when (equal char ?\t) - (unless (local-variable-p 'org-global-tags-completion-table (current-buffer)) - (org-set-local 'org-global-tags-completion-table - (org-global-tags-completion-table))) - (let ((completion-ignore-case t)) - (setq tag (completing-read - "Tag: " org-global-tags-completion-table)))) - (cond - ((equal char ?/) (org-agenda-filter-by-tag-show-all)) - ((or (equal char ?\ ) - (setq a (rassoc char org-tag-alist-for-agenda)) - (and tag (setq a (cons tag nil)))) - (org-agenda-filter-by-tag-show-all) - (setq tag (car a)) - (save-excursion - (goto-char (point-min)) - (while (not (eobp)) - (if (get-text-property (point) 'org-marker) - (progn - (setq tags (get-text-property (point) 'tags)) - (if (not tag) - (if (or (and strip (not tags)) - (and (not strip) tags)) - (org-agenda-filter-by-tag-hide-line)) - (if (or (and (member tag tags) strip) - (and (not (member tag tags)) (not strip))) - (org-agenda-filter-by-tag-hide-line))) - (beginning-of-line 2)) - (beginning-of-line 2))))) - (t (error "Invalid tag selection character %c" char))))) + (let ((tag-chars (mapconcat (lambda (x) (if (cdr x) (char-to-string (cdr x)) "")) + org-tag-alist-for-agenda "")) + char a tag tags (inhibit-read-only t) (current org-agenda-filter-tags)) + (unless char + (message + "%s by tag [%s ], [TAB] to complete, [/]:restore, [+-]:narrow, [>=<]:effort: " + (if narrow "Filter" "Narrow") tag-chars) + (setq char (read-char))) + (when (member char '(?+ ?-)) + (cond ((equal char ?-) (setq strip t narrow t)) + ((equal char ?+) (setq strip nil narrow t))) + (message + "Narrow by tag [%s ], [TAB] to complete, [/]:restore, [>=<]:effort: " tag-chars) + (setq char (read-char))) + (when (equal char ?\t) + (unless (local-variable-p 'org-global-tags-completion-table (current-buffer)) + (org-set-local 'org-global-tags-completion-table + (org-global-tags-completion-table))) + (let ((completion-ignore-case t)) + (setq tag (completing-read + "Tag: " org-global-tags-completion-table)))) + (cond + ((equal char ?/) (org-agenda-filter-by-tag-show-all)) + ((or (equal char ?\ ) + (setq a (rassoc char org-tag-alist-for-agenda)) + (and tag (setq a (cons tag nil)))) + (org-agenda-filter-by-tag-show-all) + (setq tag (car a)) + (setq org-agenda-filter-tags + (cons (concat (if strip "-" "+") tag) + (if narrow current nil))) + (setq org-agenda-filter-form (org-agenda-filter-make-matcher)) + (org-agenda-set-mode-name) + (save-excursion + (goto-char (point-min)) + (while (not (eobp)) + (if (get-text-property (point) 'org-marker) + (progn + (setq tags (get-text-property (point) 'tags)) + (if (not (eval org-agenda-filter-form)) + (org-agenda-filter-by-tag-hide-line)) + (beginning-of-line 2)) + (beginning-of-line 2))))) + (t (error "Invalid tag selection character %c" char))))) + +(defun org-agenda-filter-by-tag-refine (strip &optional char) + "Refine the current filter. See `org-agenda-filter-by-tag." + (interactive "P") + (org-agenda-filter-by-tag strip char 'refine)) + +(defun org-agenda-filter-make-matcher () + (let (f f1) + (dolist (x org-agenda-filter-tags) + (if (member x '("-" "+")) + (setq f1 '(not tags)) + (setq f1 (list 'member (substring x 1) 'tags)) + (if (equal (string-to-char x) ?-) + (setq f1 (list 'not f1)))) + (push f1 f)) + (cons 'and (nreverse f)))) (defvar org-agenda-filter-overlays nil) @@ -4183,7 +4214,10 @@ With prefix argument STRIP, remove all lines that do have the tag." (defun org-agenda-filter-by-tag-show-all () (mapc 'org-delete-overlay org-agenda-filter-overlays) - (setq org-agenda-filter-overlays nil)) + (setq org-agenda-filter-overlays nil) + (setq org-agenda-filter-tags nil) + (setq org-agenda-filter-form nil) + (org-agenda-set-mode-name)) (defun org-agenda-manipulate-query-add () "Manipulate the query by adding a search term with positive selection. @@ -4522,6 +4556,9 @@ so that the date SD will be in that range." (if org-agenda-include-diary " Diary" "") (if org-agenda-use-time-grid " Grid" "") (if org-agenda-show-log " Log" "") + (if org-agenda-filter-tags + (concat " {" (mapconcat 'identity org-agenda-filter-tags "") "}") + "") (if org-agenda-archives-mode (if (eq org-agenda-archives-mode t) " Archives" From aead878b464fad352bdfa8471b9e52a2fd58f3e3 Mon Sep 17 00:00:00 2001 From: Carsten Dominik Date: Mon, 20 Oct 2008 23:01:23 +0200 Subject: [PATCH 2/2] Implement narrowing existing agenda filters, and effort filters. --- ORGWEBPAGE/Changes.org | 46 +++++++++++++++++++++++++++ doc/org.texi | 62 ++++++++++++++++++++++++------------ lisp/ChangeLog | 4 +++ lisp/org-agenda.el | 72 +++++++++++++++++++++++++++++++++++++----- 4 files changed, 155 insertions(+), 29 deletions(-) diff --git a/ORGWEBPAGE/Changes.org b/ORGWEBPAGE/Changes.org index c60193646..2aafa2943 100644 --- a/ORGWEBPAGE/Changes.org +++ b/ORGWEBPAGE/Changes.org @@ -17,6 +17,52 @@ :END: ** Details + +*** Enhancements to secondary agenda filtering + +**** You can now refining the current filter by an additional criterion + When filtering an existing agenda view with =/=, you can + now narrow down the existing selection by an additional + condition. Do do this, use =\= instead of =/= to add the + aditional criterion. You can also press =+= or =-= after + =/= to add a positive or negative condition. A condition + can be a TAG, or an effort estimate limit, see below. + +**** It is now possible to filter for effort estimates + This means to filter the agenda for the value of the Effort + property. For this you should best set up global allowed + values for effort estimates, with + +#+begin_src emacs-lisp +(setq org-global-properties + '(("Effort_ALL" . "0 0:10 0:30 1:00 2:00 3:00 4:00"))) +#+end_src + + You may then select effort limits with single keys in the + filter. It works like this: After =/= or =\=, first select + the operater which you want to use to compare effort + estimates: + + : < Select entries with effort smaller than or equal to the limit + : > Select entries with effort larger than or equal to the limit + : = Select entries with effort equal to the limit + + After that, you can press a single digit number which is + used as an index to the allowed effort estimates. + + If you do not use digits to fast-select tags, you can even + skip the operator, which will then default to + `org-agenda-filter-effort-default-operator', which is by + default =<=. + + Thanks to Manish for the great idea to include fast effort + filtering into the agenda filtering process. + +**** The mode line will show the active filter + For example, if there is a filter in place that does select + for HOME tags, against EMAILtags, and for tasks with an + estimated effort smaller than 30 minutes, the mode-line with + show =+HOME-EMAIL+<0:30= *** Setting tags has now its own binding, =C-c C-q= diff --git a/doc/org.texi b/doc/org.texi index f8abfa415..724a7c47c 100644 --- a/doc/org.texi +++ b/doc/org.texi @@ -4863,10 +4863,10 @@ you want to clock your time). For a specific buffer you can use @end example @noindent -or you can set up these values globally by customizing the variables -@code{org-global-properties} and @code{org-columns-default-format}. In -particular if you want to use this setup also in the agenda, a global setup -may be advised. +or, even better, you can set up these values globally by customizing the +variables @code{org-global-properties} and @code{org-columns-default-format}. +In particular if you want to use this setup also in the agenda, a global +setup may be advised. The way to assign estimates to individual items is then to switch to column mode, and to use @kbd{S-@key{right}} and @kbd{S-@key{left}} to change the @@ -4882,6 +4882,10 @@ option @code{org-agenda-columns-add-appointments-to-effort-sum}. The appointments on a day that take place over a specified time interval will then also be added to the load estimate of the day. +Effort estimates can be used in secondary agenda filtering that is triggered +with the @kbd{/} key in the agenda (@pxref{Agenda commands}). If you have +these estimates defined consistently, two or three key presses will narrow +down the list to stuff that fits into an available time slot. @node Capture, Agenda Views, Dates and Times, Top @chapter Capture @@ -5835,9 +5839,7 @@ sequence in which they are found in the agenda files. Sorting can be customized using the variable @code{org-agenda-sorting-strategy}, and may also include criteria based on -the estimated effort of an entry. -@c FIXME: link!!!!!!!! - +the estimated effort of an entry (@pxref{Effort estimates}). @node Agenda commands, Custom agenda views, Presentation and sorting, Agenda Views @section Commands in the agenda buffer @@ -6001,23 +6003,41 @@ that entry would be in the original buffer (taken from a property, from a @kindex / @item / -Filter the current agenda view with respect to a tag. You will be prompted -for a tag selection letter. Pressing @key{TAB} at that prompt will offer use -completion to select a tag (including any tags that do not have a selection -character). The command then hides all entries that do not contain or -inherit this tag. When called with prefix arg, remove the entries that -@emph{do} have the tag. A second @kbd{/} at the prompt will unhide any -hidden entries. If the first key you press is either @kbd{+} or @kbd{-}, the -previous filter will be narrowed by requiring or forbidding the selected -additional tag. Instead of pressing @kbd{+} or {-}, you can also use the -following command. +Filter the current agenda view with respect to a tag and/or effort estimates. +The difference between this and a custom agenda commands is that filtering is +very fast, so that you can switch quickly between different filters without +having to recreate the agenda. + +You will be prompted for a tag selection letter. Pressing @key{TAB} at that +prompt will offer use completion to select a tag (including any tags that do +not have a selection character). The command then hides all entries that do +not contain or inherit this tag. When called with prefix arg, remove the +entries that @emph{do} have the tag. A second @kbd{/} at the prompt will +turn off the filter and unhide any hidden entries. If the first key you +press is either @kbd{+} or @kbd{-}, the previous filter will be narrowed by +requiring or forbidding the selected additional tag. Instead of pressing +@kbd{+} or {-}, you can also use the @kbd{\} command. + +In order to filter for effort estimates, you should set-up allowed +efforts globally, for example +@lisp +(setq org-global-properties + '(("Effort_ALL". "0 0:10 0:30 1:00 2:00 3:00 4:00"))) +@end lisp +You can then filter for an effort by first typing an operator, one of @kbd{<}, +@kbd{>}, and @kbd{=}, and then the one-digit index of an effort estimate in +your array of allowed values, where @kbd{0} means the 10th value. The filter +will then restrict to entries with effort smaller-or-equal, equal, or +larger-or-equal than the selected value. If the digits 0-9 are not used as +fast access keys to tags, you can also simply press the index digit directly +without an operator. In this case, @kbd{<} will be assumed. @kindex \ @item \ -Narrow the curent agenda fiter by an additional condition. When called with -prefix arg, remove the entries that @emph{do} have the tag. You can achive -the same effect by pressing @kbd{+} or @kbd{-} as the first key after the -@kbd{/} command. +Narrow the current agenda filter by an additional condition. When called with +prefix arg, remove the entries that @emph{do} have the tag, or that do match +the effort criterion. You can achieve the same effect by pressing @kbd{+} or +@kbd{-} as the first key after the @kbd{/} command. @kindex [ @kindex ] diff --git a/lisp/ChangeLog b/lisp/ChangeLog index ea766c1aa..361256cff 100755 --- a/lisp/ChangeLog +++ b/lisp/ChangeLog @@ -1,3 +1,7 @@ +2008-10-20 Carsten Dominik + + * org-agenda.el (org-agenda-filter-effort-default-operator): New + option. 2008-10-18 Carsten Dominik diff --git a/lisp/org-agenda.el b/lisp/org-agenda.el index f15067cce..1e938a6a4 100644 --- a/lisp/org-agenda.el +++ b/lisp/org-agenda.el @@ -387,6 +387,14 @@ or `C-c a #' to produce the list." (repeat :tag "Projects are *not* stuck if they have an entry with TAG being any of" (string)) (regexp :tag "Projects are *not* stuck if this regexp matches\ninside the subtree"))) +(defcustom org-agenda-filter-effort-default-operator "<" + "The default operator for effort estimate filtering. +If you select an effort estimate limit with first pressing an operator, +this one will be used." + :group 'org-agenda-custom-commands + :type '(choice (const :tag "less or equal" "<") + (const :tag "greater or equal"">") + (const :tag "equal" "="))) (defgroup org-agenda-skip nil "Options concerning skipping parts of agenda files." @@ -4119,6 +4127,7 @@ When this is the global TODO list, a prefix argument will be interpreted." (goto-line line) (recenter window-line))) + (defvar org-global-tags-completion-table nil) (defvar org-agenda-filter-tags nil) (defvar org-agenda-filter-form nil) @@ -4130,20 +4139,45 @@ A lisp caller can specify CHAR. NARROW means that the new tag should be used to narrow the search - the interactive user can also press `-' or `+' to switch to narrowing." (interactive "P") - (let ((tag-chars (mapconcat (lambda (x) (if (cdr x) (char-to-string (cdr x)) "")) - org-tag-alist-for-agenda "")) - char a tag tags (inhibit-read-only t) (current org-agenda-filter-tags)) + (let* ((alist org-tag-alist-for-agenda) + (tag-chars (mapconcat + (lambda (x) (if (cdr x) (char-to-string (cdr x)) "")) + alist "")) + (efforts (org-split-string + (or (cdr (assoc (concat org-effort-property "_ALL") + org-global-properties)) + "0 0:10 0:30 1:00 2:00 3:00 4:00 5:00 6:00 7:00 8:00" ""))) + (effort-op org-agenda-filter-effort-default-operator) + (effort-prompt "") + (inhibit-read-only t) + (current org-agenda-filter-tags) + char a tag tags) (unless char (message - "%s by tag [%s ], [TAB] to complete, [/]:restore, [+-]:narrow, [>=<]:effort: " - (if narrow "Filter" "Narrow") tag-chars) + "%s by tag [%s ], [TAB], [/]:off, [+-]:narrow, [>=<]:effort: " + (if narrow "Narrow" "Filter") tag-chars) (setq char (read-char))) (when (member char '(?+ ?-)) + ;; Narrowing down (cond ((equal char ?-) (setq strip t narrow t)) ((equal char ?+) (setq strip nil narrow t))) (message - "Narrow by tag [%s ], [TAB] to complete, [/]:restore, [>=<]:effort: " tag-chars) + "Narrow by tag [%s ], [TAB], [/]:off, [>=<]:effort: " tag-chars) (setq char (read-char))) + (when (member char '(?< ?> ?=)) + ;; An effort operator + (setq effort-op (char-to-string char)) + (loop for i from 0 to 9 do + (setq effort-prompt + (concat + effort-prompt " [" + (if (= i 9) "0" (int-to-string (1+ i))) + "]" (nth i efforts)))) + (setq alist nil) ; to make sure it will be interpreted as effort. + (message "Effort%s: %s " effort-op effort-prompt) + (setq char (read-char)) + (when (or (< char ?0) (> char ?9)) + (error "Need 1-9,0 to select effort" ))) (when (equal char ?\t) (unless (local-variable-p 'org-global-tags-completion-table (current-buffer)) (org-set-local 'org-global-tags-completion-table @@ -4154,7 +4188,11 @@ to switch to narrowing." (cond ((equal char ?/) (org-agenda-filter-by-tag-show-all)) ((or (equal char ?\ ) - (setq a (rassoc char org-tag-alist-for-agenda)) + (setq a (rassoc char alist)) + (and (>= char ?0) (<= char ?9) + (setq n (if (= char ?0) 9 (- char ?0 1)) + tag (concat effort-op (nth n efforts)) + a (cons tag nil))) (and tag (setq a (cons tag nil)))) (org-agenda-filter-by-tag-show-all) (setq tag (car a)) @@ -4185,12 +4223,30 @@ to switch to narrowing." (dolist (x org-agenda-filter-tags) (if (member x '("-" "+")) (setq f1 '(not tags)) - (setq f1 (list 'member (substring x 1) 'tags)) + (if (string-match "[<=>]" x) + (setq f1 (org-agenda-filter-effort-form x)) + (setq f1 (list 'member (substring x 1) 'tags))) (if (equal (string-to-char x) ?-) (setq f1 (list 'not f1)))) (push f1 f)) (cons 'and (nreverse f)))) +(defun org-agenda-filter-effort-form (e) + "Return the form to compare the effort of the current line with what E says. +E looks line \"+<2:25\"." + (let (op) + (setq e (substring e 1)) + (setq op (string-to-char e) e (substring e 1)) + (setq op (if (equal op ?<) '<= (if (equal op ?>) '>= '=))) + (list 'org-agenda-compare-effort (list 'quote op) + (org-hh:mm-string-to-minutes e)))) + +(defun org-agenda-compare-effort (op value) + (let ((eff (get-text-property 'effort-minutes))) + (if (not eff) + nil ; we don't have an effort defined + (funcall op eff value)))) + (defvar org-agenda-filter-overlays nil) (defun org-agenda-filter-by-tag-hide-line ()