;;; gnus-cus.el --- customization commands for Gnus
-;;
-;; Copyright (C) 1996, 1999, 2000, 2001, 2002, 2003
-;; Free Software Foundation, Inc.
+
+;; Copyright (C) 1996, 1999, 2000, 2001, 2002, 2003, 2004,
+;; 2005, 2006, 2007, 2008 Free Software Foundation, Inc.
;; Author: Per Abrahamsen <abraham@dina.kvl.dk>
;; Keywords: news
;; This file is part of GNU Emacs.
-;; GNU Emacs is free software; you can redistribute it and/or modify
+;; GNU Emacs 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.
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
;; GNU Emacs 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
+;; 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., 59 Temple Place - Suite 330,
-;; Boston, MA 02111-1307, USA.
+;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
(require 'wid-edit)
(require 'gnus)
+(require 'gnus-agent)
(require 'gnus-score)
(require 'gnus-topic)
(require 'gnus-art)
(set (make-local-variable 'widget-push-button-suffix) "")
(set (make-local-variable 'widget-link-prefix) "")
(set (make-local-variable 'widget-link-suffix) ""))
- (gnus-run-hooks 'gnus-custom-mode-hook))
+ (gnus-run-mode-hooks 'gnus-custom-mode-hook))
;;; Group Customization:
string) "\
Where expired messages end up.
-Overrides `nnmail-expiry-target', which see.")
+Overrides `nnmail-expiry-target'.")
(score-file (file :tag "Score File") "\
Make the specified file into the current score file.
An arbitrary comment on the group.")
(visible (const :tag "Permanently visible" t) "\
-Always display this group, even when there are no unread articles
-in it..")
+Always display this group, even when there are no unread articles in it.")
(highlight-words
(choice :tag "Highlight words"
(const signature-file)
(const organization)
(const address)
+ (const x-face-file)
(const name)
- (const body))
+ (const body)
+ (symbol)
+ (string :tag "Header"))
(string :format "%v"))))
"post style.
See `gnus-posting-styles'."))
Each entry has the form (NAME TYPE DOC), where NAME is the parameter
itself (a symbol), TYPE is the parameters type (a sexp widget), and
DOC is a documentation string for the parameter.")
+
+(eval-and-compile
+ (defconst gnus-agent-parameters
+ '((agent-predicate
+ (sexp :tag "Selection Predicate" :value false)
+ "Predicate used to automatically select articles for downloading."
+ gnus-agent-cat-predicate)
+ (agent-score
+ (choice :tag "Score File" :value nil
+ (const file :tag "Use group's score files")
+ (repeat (list (string :format "%v" :tag "File name"))))
+ "Which score files to use when using score to select articles to fetch.
+
+ `nil'
+ All articles will be scored to zero (0).
+
+ `file'
+ The group's score files will be used to score the articles.
+
+ `List'
+ A list of score file names."
+ gnus-agent-cat-score-file)
+ (agent-short-article
+ (integer :tag "Max Length of Short Article" :value "")
+ "The SHORT predicate will evaluate to true when the article is
+shorter than this length." gnus-agent-cat-length-when-short)
+ (agent-long-article
+ (integer :tag "Min Length of Long Article" :value "")
+ "The LONG predicate will evaluate to true when the article is
+longer than this length." gnus-agent-cat-length-when-long)
+ (agent-low-score
+ (integer :tag "Low Score Limit" :value "")
+ "The LOW predicate will evaluate to true when the article scores
+lower than this limit." gnus-agent-cat-low-score)
+ (agent-high-score
+ (integer :tag "High Score Limit" :value "")
+ "The HIGH predicate will evaluate to true when the article scores
+higher than this limit." gnus-agent-cat-high-score)
+ (agent-days-until-old
+ (integer :tag "Days Until Old" :value "")
+ "The OLD predicate will evaluate to true when the fetched article
+has been stored locally for at least this many days."
+ gnus-agent-cat-days-until-old)
+ (agent-enable-expiration
+ (radio :tag "Expire in this Group or Topic" :value nil
+ (const :format "Enable " ENABLE)
+ (const :format "Disable " DISABLE))
+ "\nEnable, or disable, agent expiration in this group or topic."
+ gnus-agent-cat-enable-expiration)
+ (agent-enable-undownloaded-faces
+ (boolean :tag "Enable Agent Faces")
+ "Have the summary buffer use the agent's undownloaded faces.
+These faces, when enabled, act as a warning that an article has not
+been fetched into either the agent nor the cache. This is of most use
+to users who use the agent as a cache (i.e. they only operate on
+articles that have been downloaded). Leave disabled to display normal
+article faces even when the article hasn't been downloaded."
+gnus-agent-cat-enable-undownloaded-faces))
+ "Alist of group parameters that are not also topic parameters.
+
+Each entry has the form (NAME TYPE DOC ACCESSOR), where NAME is the
+parameter itself (a symbol), TYPE is the parameters type (a sexp
+widget), DOC is a documentation string for the parameter, and ACCESSOR
+is a function (symbol) that extracts the current value from the
+category."))
+
(defvar gnus-custom-params)
(defvar gnus-custom-method)
(defvar gnus-custom-group)
gnus-group-parameters
(if group
gnus-extra-group-parameters
- gnus-extra-topic-parameters)))))
+ gnus-extra-topic-parameters))))
+ (agent (mapcar (lambda (entry)
+ (let ((type (nth 1 entry))
+ vcons)
+ (if (listp type)
+ (setq type (copy-sequence type)))
+
+ (setq vcons (cdr (memq :value type)))
+
+ (if (symbolp (car vcons))
+ (condition-case nil
+ (setcar vcons (symbol-value (car vcons)))
+ (error)))
+ `(cons :format "%v%h\n"
+ :doc ,(nth 2 entry)
+ (const :format "" ,(nth 0 entry))
+ ,type)))
+ (if gnus-agent
+ gnus-agent-parameters))))
(unless (or group topic)
(error "No group on current line"))
(when (and group topic)
(unless (or topic (setq info (gnus-get-info group)))
(error "Killed group; can't be edited"))
;; Ready.
- (kill-buffer (gnus-get-buffer-create "*Gnus Customize*"))
+ (gnus-kill-buffer (gnus-get-buffer-create "*Gnus Customize*"))
(switch-to-buffer (gnus-get-buffer-create "*Gnus Customize*"))
(gnus-custom-mode)
(make-local-variable 'gnus-custom-group)
:action 'gnus-group-customize-done)
(widget-insert ".\n\n")
(make-local-variable 'gnus-custom-params)
- (setq gnus-custom-params
- (widget-create 'group
- :value (if group
- (gnus-info-params info)
- (gnus-topic-parameters topic))
- `(set :inline t
- :greedy t
- :tag "Parameters"
- :format "%t:\n%h%v"
- :doc "\
+
+ (let ((values (if group
+ (gnus-info-params info)
+ (gnus-topic-parameters topic))))
+
+ ;; The parameters in values may contain duplicates. This is
+ ;; normally OK as assq returns the first. However, right here
+ ;; every duplicate ends up being displayed. So, rather than
+ ;; display them, remove them from the list.
+
+ (let ((tmp (setq values (gnus-copy-sequence values)))
+ elem)
+ (while (cdr tmp)
+ (while (setq elem (assq (caar tmp) (cdr tmp)))
+ (delq elem tmp))
+ (setq tmp (cdr tmp))))
+
+ (setq gnus-custom-params
+ (apply 'widget-create 'group
+ :value values
+ (delq nil
+ (list `(set :inline t
+ :greedy t
+ :tag "Parameters"
+ :format "%t:\n%h%v"
+ :doc "\
These special parameters are recognized by Gnus.
Check the [ ] for the parameters you want to apply to this group or
to the groups in this topic, then edit the value to suit your taste."
- ,@types)
- '(repeat :inline t
- :tag "Variables"
- :format "%t:\n%h%v%i\n\n"
- :doc "\
+ ,@types)
+ (when gnus-agent
+ `(set :inline t
+ :greedy t
+ :tag "Agent Parameters"
+ :format "%t:\n%h%v"
+ :doc "\ These agent parameters are
+recognized by Gnus. They control article selection and expiration for
+use in the unplugged cache. Check the [ ] for the parameters you want
+to apply to this group or to the groups in this topic, then edit the
+value to suit your taste.
+
+For those interested, group parameters override topic parameters while
+topic parameters override agent category parameters. Underlying
+category parameters are the customizable variables." ,@agent))
+ '(repeat :inline t
+ :tag "Variables"
+ :format "%t:\n%h%v%i\n\n"
+ :doc "\
Set variables local to the group you are entering.
If you want to turn threading off in `news.answers', you could put
put something like `(dummy-variable (ding))' in the parameters of that
group. `dummy-variable' will be set to the result of the `(ding)'
form, but who cares?"
- (list :format "%v" :value (nil nil)
- (symbol :tag "Variable")
- (sexp :tag
- "Value")))
-
- '(repeat :inline t
- :tag "Unknown entries"
- sexp)))
+ (list :format "%v" :value (nil nil)
+ (symbol :tag "Variable")
+ (sexp :tag
+ "Value")))
+
+ '(repeat :inline t
+ :tag "Unknown entries"
+ sexp))))))
(when group
(widget-insert "\n\nYou can also edit the ")
(widget-create 'info-link
,group))))
widget)
+(define-widget 'gnus-score-extra 'group
+ "Edit score entries for extra headers."
+ :convert-widget 'gnus-score-extra-convert)
+
+(defun gnus-score-extra-convert (widget)
+ ;; Set args appropriately.
+ (let* ((tag (widget-get widget :tag))
+ (item `(const :format "" :value ,(downcase tag)))
+ (match '(string :tag "Match"))
+ (score '(choice :tag "Score"
+ (const :tag "default" nil)
+ (integer :format "%v"
+ :hide-front-space t)))
+ (expire '(choice :tag "Expire"
+ (const :tag "off" nil)
+ (integer :format "%v"
+ :hide-front-space t)))
+ (type '(choice :tag "Type"
+ :value s
+ ;; I should really create a forgiving :match
+ ;; function for each type below, that only
+ ;; looked at the first letter.
+ (const :tag "Regexp" r)
+ (const :tag "Regexp (fixed case)" R)
+ (const :tag "Substring" s)
+ (const :tag "Substring (fixed case)" S)
+ (const :tag "Exact" e)
+ (const :tag "Exact (fixed case)" E)
+ (const :tag "Word" w)
+ (const :tag "Word (fixed case)" W)
+ (const :tag "default" nil)))
+ (header (if gnus-extra-headers
+ (let (name)
+ `(choice :tag "Header"
+ ,@(mapcar (lambda (h)
+ (setq name (symbol-name h))
+ (list 'const :tag name name))
+ gnus-extra-headers)
+ (string :tag "Other" :format "%v")))
+ '(string :tag "Header")))
+ (group `(group ,match ,score ,expire ,type ,header))
+ (doc (concat (or (widget-get widget :doc)
+ (concat "Change score based on the " tag
+ " header.\n")))))
+ (widget-put
+ widget :args
+ `(,item
+ (repeat :inline t
+ :indent 0
+ :tag ,tag
+ :doc ,doc
+ :format "%t:\n%h%v%i\n\n"
+ (choice :format "%v"
+ :value ("" nil nil s
+ ,(if gnus-extra-headers
+ (symbol-name (car gnus-extra-headers))
+ ""))
+ ,group
+ sexp)))))
+ widget)
+
(defvar gnus-custom-scores)
(defvar gnus-custom-score-alist)
This can be changed using the `\\[gnus-score-change-score-file]' command."
(interactive (list gnus-current-score-file))
(unless file
- (error (format "No score file for %s"
- (gnus-group-decoded-name gnus-newsgroup-name))))
+ (error "No score file for %s"
+ (gnus-group-decoded-name gnus-newsgroup-name)))
(let ((scores (gnus-score-load file))
(types (mapcar (lambda (entry)
`(group :format "%v%h\n"
(gnus-score-string :tag "Subject")
(gnus-score-string :tag "References")
(gnus-score-string :tag "Xref")
- (gnus-score-string :tag "Extra")
+ (gnus-score-extra :tag "Extra")
(gnus-score-string :tag "Message-ID")
(gnus-score-integer :tag "Lines")
(gnus-score-integer :tag "Chars")
(gnus-score-set 'touched '(t) alist))
(bury-buffer))
+(defvar category-fields nil)
+(defvar gnus-agent-cat-name)
+(defvar gnus-agent-cat-score-file)
+(defvar gnus-agent-cat-length-when-short)
+(defvar gnus-agent-cat-length-when-long)
+(defvar gnus-agent-cat-low-score)
+(defvar gnus-agent-cat-high-score)
+(defvar gnus-agent-cat-enable-expiration)
+(defvar gnus-agent-cat-days-until-old)
+(defvar gnus-agent-cat-predicate)
+(defvar gnus-agent-cat-groups)
+(defvar gnus-agent-cat-enable-undownloaded-faces)
+
+(defun gnus-trim-whitespace (s)
+ (when (string-match "\\`[ \n\t]+" s)
+ (setq s (substring s (match-end 0))))
+ (when (string-match "[ \n\t]+\\'" s)
+ (setq s (substring s 0 (match-beginning 0))))
+ s)
+
+(defmacro gnus-agent-cat-prepare-category-field (parameter)
+ (let* ((entry (assq parameter gnus-agent-parameters))
+ (field (nth 3 entry)))
+ `(let* ((type (copy-sequence
+ (nth 1 (assq ',parameter gnus-agent-parameters))))
+ (val (,field info))
+ (deflt (if (,field defaults)
+ (concat " [" (gnus-trim-whitespace
+ (gnus-pp-to-string (,field defaults)))
+ "]")))
+ symb)
+
+ (if (eq (car type) 'radio)
+ (let* ((rtype (nreverse type))
+ (rt rtype))
+ (while (listp (or (cadr rt) 'not-list))
+ (setq rt (cdr rt)))
+
+ (setcdr rt (cons '(const :format "Inherit " nil) (cdr rt)))
+ (setq type (nreverse rtype))))
+
+ (if deflt
+ (let ((tag (cdr (memq :tag type))))
+ (when (string-match "\n" deflt)
+ (while (progn (setq deflt (replace-match "\n " t t
+ deflt))
+ (string-match "\n" deflt (match-end 0))))
+ (setq deflt (concat "\n" deflt)))
+
+ (setcar tag (concat (car tag) deflt))))
+
+ (widget-insert "\n")
+
+ (setq val (if val
+ (widget-create type :value val)
+ (widget-create type))
+ symb (set (make-local-variable ',field) val))
+
+ (widget-put symb :default val)
+ (widget-put symb :accessor ',field)
+ (push symb category-fields))))
+
+(defun gnus-agent-customize-category (category)
+ "Edit the CATEGORY."
+ (interactive (list (gnus-category-name)))
+ (let ((info (assq category gnus-category-alist))
+ (defaults (list nil '(agent-predicate . false)
+ (cons 'agent-enable-expiration
+ gnus-agent-enable-expiration)
+ '(agent-days-until-old . 7)
+ (cons 'agent-length-when-short
+ gnus-agent-short-article)
+ (cons 'agent-length-when-long gnus-agent-long-article)
+ (cons 'agent-low-score gnus-agent-low-score)
+ (cons 'agent-high-score gnus-agent-high-score))))
+
+ (let ((old (get-buffer "*Gnus Agent Category Customize*")))
+ (when old
+ (gnus-kill-buffer old)))
+ (switch-to-buffer (gnus-get-buffer-create
+ "*Gnus Agent Category Customize*"))
+
+ (let ((inhibit-read-only t))
+ (gnus-custom-mode)
+ (buffer-disable-undo)
+
+ (let* ((name (gnus-agent-cat-name info)))
+ (widget-insert "Customize the Agent Category '")
+ (widget-insert (symbol-name name))
+ (widget-insert "' and press ")
+ (widget-create
+ 'push-button
+ :notify
+ '(lambda (&rest ignore)
+ (let* ((info (assq gnus-agent-cat-name gnus-category-alist))
+ (widgets category-fields))
+ (while widgets
+ (let* ((widget (pop widgets))
+ (value (condition-case nil (widget-value widget) (error))))
+ (eval `(setf (,(widget-get widget :accessor) ',info)
+ ',value)))))
+ (gnus-category-write)
+ (gnus-kill-buffer (current-buffer))
+ (when (get-buffer gnus-category-buffer)
+ (switch-to-buffer (get-buffer gnus-category-buffer))
+ (gnus-category-list)))
+ "Done")
+ (widget-insert
+ "\n Note: Empty fields default to the customizable global\
+ variables.\n\n")
+
+ (set (make-local-variable 'gnus-agent-cat-name)
+ name))
+
+ (set (make-local-variable 'category-fields) nil)
+ (gnus-agent-cat-prepare-category-field agent-predicate)
+
+ (gnus-agent-cat-prepare-category-field agent-score)
+ (gnus-agent-cat-prepare-category-field agent-short-article)
+ (gnus-agent-cat-prepare-category-field agent-long-article)
+ (gnus-agent-cat-prepare-category-field agent-low-score)
+ (gnus-agent-cat-prepare-category-field agent-high-score)
+
+ ;; The group list is NOT handled with
+ ;; gnus-agent-cat-prepare-category-field as I don't want the
+ ;; group list to appear when customizing a topic.
+ (widget-insert "\n")
+
+ (let ((symb
+ (set
+ (make-local-variable 'gnus-agent-cat-groups)
+ (widget-create
+ `(choice
+ :format "%[Select Member Groups%]\n%v" :value ignore
+ (const :menu-tag "do not change" :tag "" :value ignore)
+ (checklist :entry-format "%b %v"
+ :menu-tag "display group selectors"
+ :greedy t
+ :value
+ ,(delq nil
+ (mapcar
+ (lambda (newsrc)
+ (car (member
+ (gnus-info-group newsrc)
+ (gnus-agent-cat-groups info))))
+ (cdr gnus-newsrc-alist)))
+ ,@(mapcar (lambda (newsrc)
+ `(const ,(gnus-info-group newsrc)))
+ (cdr gnus-newsrc-alist))))))))
+
+ (widget-put symb :default (gnus-agent-cat-groups info))
+ (widget-put symb :accessor 'gnus-agent-cat-groups)
+ (push symb category-fields))
+
+ (widget-insert "\nExpiration Settings ")
+
+ (gnus-agent-cat-prepare-category-field agent-enable-expiration)
+ (gnus-agent-cat-prepare-category-field agent-days-until-old)
+
+ (widget-insert "\nVisual Settings ")
+
+ (gnus-agent-cat-prepare-category-field agent-enable-undownloaded-faces)
+
+ (use-local-map widget-keymap)
+ (widget-setup)
+ (buffer-enable-undo))))
+
;;; The End:
(provide 'gnus-cus)
+;; arch-tag: a37c285a-49bc-4235-8244-804536effeaf
;;; gnus-cus.el ends here