1 ;;; newst-treeview.el --- Treeview frontend for newsticker.
3 ;; Copyright (C) 2008 Free Software Foundation, Inc.
5 ;; Author: Ulf Jasper <ulf.jasper@web.de>
6 ;; Filename: newst-treeview.el
7 ;; URL: http://www.nongnu.org/newsticker
9 ;; Keywords: News, RSS, Atom
10 ;; Time-stamp: "21. Juni 2008, 17:35:21 (ulf)"
12 ;; ======================================================================
14 ;; This file is part of GNU Emacs.
16 ;; GNU Emacs is free software: you can redistribute it and/or modify
17 ;; it under the terms of the GNU General Public License as published by
18 ;; the Free Software Foundation, either version 3 of the License, or
19 ;; (at your option) any later version.
21 ;; GNU Emacs is distributed in the hope that it will be useful,
22 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
23 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24 ;; GNU General Public License for more details.
26 ;; You should have received a copy of the GNU General Public License
27 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
29 ;; ======================================================================
34 ;; ======================================================================
39 ;; ======================================================================
41 (require 'newsticker-reader
"newst-reader")
43 (require 'tree-widget
)
46 ;; ======================================================================
48 ;; ======================================================================
49 (defgroup newsticker-treeview nil
50 "Settings for the tree view reader."
51 :group
'newsticker-reader
)
53 (defface newsticker-treeview-face
54 '((((class color
) (background dark
))
55 (:family
"helvetica" :foreground
"misty rose" :bold nil
))
56 (((class color
) (background light
))
57 (:family
"helvetica" :foreground
"black" :bold nil
)))
58 "Face for newsticker tree."
59 :group
'newsticker-treeview
)
61 (defface newsticker-treeview-new-face
62 '((((class color
) (background dark
))
63 (:inherit newsticker-treeview-face
:bold t
))
64 (((class color
) (background light
))
65 (:inherit newsticker-treeview-face
:bold t
)))
66 "Face for newsticker tree."
67 :group
'newsticker-treeview
)
69 (defface newsticker-treeview-old-face
70 '((((class color
) (background dark
))
71 (:inherit newsticker-treeview-face
))
72 (((class color
) (background light
))
73 (:inherit newsticker-treeview-face
)))
74 "Face for newsticker tree."
75 :group
'newsticker-treeview
)
77 (defface newsticker-treeview-immortal-face
78 '((((class color
) (background dark
))
79 (:inherit newsticker-treeview-face
:foreground
"orange" :italic t
))
80 (((class color
) (background light
))
81 (:inherit newsticker-treeview-face
:foreground
"blue" :italic t
)))
82 "Face for newsticker tree."
83 :group
'newsticker-treeview
)
85 (defface newsticker-treeview-obsolete-face
86 '((((class color
) (background dark
))
87 (:inherit newsticker-treeview-face
:strike-through t
))
88 (((class color
) (background light
))
89 (:inherit newsticker-treeview-face
:strike-through t
)))
90 "Face for newsticker tree."
91 :group
'newsticker-treeview
)
93 (defface newsticker-treeview-selection-face
94 '((((class color
) (background dark
))
95 (:background
"#bbbbff"))
96 (((class color
) (background light
))
97 (:background
"#bbbbff")))
98 "Face for newsticker selection."
99 :group
'newsticker-treeview
)
101 (defcustom newsticker-treeview-own-frame
103 "Decides whether newsticker creates and uses its own frame."
105 :group
'newsticker-treeview
)
107 (defcustom newsticker-treeview-automatically-mark-displayed-items-as-old
109 "Decides whether to automatically mark displayed items as old.
110 If t an item is marked as old as soon as it is displayed. This
111 applies to newsticker only."
113 :group
'newsticker-treeview
)
115 (defvar newsticker-groups
117 "List of feed groups, used in the treeview frontend.
118 First element is a string giving the group name. Remaining
119 elements are either strings giving a feed name or lists having
120 the same structure as `newsticker-groups'. (newsticker-groups :=
121 groupdefinition, groupdefinition := groupname groupcontent*,
122 groupcontent := feedname | groupdefinition)
124 Example: (\"Topmost group\" \"feed1\" (\"subgroup1\" \"feed 2\")
127 (defcustom newsticker-groups-filename
128 "~/.newsticker-groups"
129 "Name of the newsticker groups settings file."
131 :group
'newsticker-treeview
)
133 ;; ======================================================================
134 ;;; internal variables
135 ;; ======================================================================
136 (defvar newsticker--treeview-windows nil
)
137 (defvar newsticker--treeview-buffers nil
)
138 (defvar newsticker--treeview-current-feed nil
)
139 (defvar newsticker--treeview-current-vfeed nil
)
140 (defvar newsticker--treeview-list-show-feed nil
)
141 (defvar newsticker--saved-window-config nil
)
142 (defvar newsticker--selection-overlay nil
143 "Highlight the selected tree node.")
144 (defvar newsticker--tree-selection-overlay nil
145 "Highlight the selected list item.")
146 (defvar newsticker--frame nil
"Special frame for newsticker windows.")
147 (defvar newsticker--treeview-list-sort-order
'sort-by-time
)
148 (defvar newsticker--treeview-current-node-id nil
)
149 (defvar newsticker--treeview-current-tree nil
)
150 (defvar newsticker--treeview-feed-tree nil
)
151 (defvar newsticker--treeview-vfeed-tree nil
)
153 ;; maps for the clickable portions
154 (defvar newsticker--treeview-url-keymap
155 (let ((map (make-sparse-keymap 'newsticker--treeview-url-keymap
)))
156 (define-key map
[mouse-1
] 'newsticker-treeview-mouse-browse-url
)
157 (define-key map
[mouse-2
] 'newsticker-treeview-mouse-browse-url
)
158 (define-key map
"\n" 'newsticker-treeview-browse-url
)
159 (define-key map
"\C-m" 'newsticker-treeview-browse-url
)
160 (define-key map
[(control return
)] 'newsticker-handle-url
)
162 "Key map for click-able headings in the newsticker treeview buffers.")
165 ;; ======================================================================
167 ;; ======================================================================
168 (defsubst newsticker--treeview-tree-buffer
()
169 "Return the tree buffer of the newsticker treeview."
170 (nth 0 newsticker--treeview-buffers
))
171 (defsubst newsticker--treeview-list-buffer
()
172 "Return the list buffer of the newsticker treeview."
173 (nth 1 newsticker--treeview-buffers
))
174 (defsubst newsticker--treeview-item-buffer
()
175 "Return the item buffer of the newsticker treeview."
176 (nth 2 newsticker--treeview-buffers
))
177 (defsubst newsticker--treeview-tree-window
()
178 "Return the tree window of the newsticker treeview."
179 (nth 0 newsticker--treeview-windows
))
180 (defsubst newsticker--treeview-list-window
()
181 "Return the list window of the newsticker treeview."
182 (nth 1 newsticker--treeview-windows
))
183 (defsubst newsticker--treeview-item-window
()
184 "Return the item window of the newsticker treeview."
185 (nth 2 newsticker--treeview-windows
))
187 ;; ======================================================================
188 ;;; utility functions
189 ;; ======================================================================
190 (defun newsticker--treeview-get-id (parent i
)
191 "Create an id for a newsticker treeview node.
192 PARENT is the node's parent, I is an integer."
193 ;;(message "newsticker--treeview-get-id %s"
194 ;; (format "%s-%d" (widget-get parent :nt-id) i))
195 (format "%s-%d" (widget-get parent
:nt-id
) i
))
197 (defun newsticker--treeview-ids-eq (id1 id2
)
198 "Return non-nil if ids ID1 and ID2 are equal."
199 ;;(message "%s/%s" (or id1 -1) (or id2 -1))
200 (and id1 id2
(string= id1 id2
)))
202 (defun newsticker--treeview-nodes-eq (node1 node2
)
203 "Compare treeview nodes NODE1 and NODE2 for equality.
204 Nodes are equal if the have the same newsticker-id. Note that
205 during re-tagging and collapsing/expanding nodes change, while
206 their id stays constant."
207 (let ((id1 (widget-get node1
:nt-id
))
208 (id2 (widget-get node2
:nt-id
)))
209 ;;(message "%s/%s %s/%s" (widget-get node1 :tag) (widget-get node2 :tag)
210 ;; (or id1 -1) (or id2 -1))
211 (or (newsticker--treeview-ids-eq id1 id2
)
212 (string= (widget-get node1
:tag
) (widget-get node2
:tag
)))))
214 (defun newsticker--treeview-do-get-node-of-feed (feed-name startnode
)
215 "Recursivly search node for feed FEED-NAME starting from STARTNODE."
216 ;;(message "%s/%s" feed-name (widget-get startnode :nt-feed))
217 (if (string= feed-name
(or (widget-get startnode
:nt-feed
)
218 (widget-get startnode
:nt-vfeed
)))
219 (throw 'found startnode
)
220 (let ((children (widget-get startnode
:children
)))
222 (newsticker--treeview-do-get-node-of-feed feed-name w
)))))
224 (defun newsticker--treeview-get-node-of-feed (feed-name)
225 "Return node for feed FEED-NAME in newsticker treeview tree."
227 (newsticker--treeview-do-get-node-of-feed feed-name
228 newsticker--treeview-feed-tree
)
229 (newsticker--treeview-do-get-node-of-feed feed-name
230 newsticker--treeview-vfeed-tree
)))
232 (defun newsticker--treeview-do-get-node (id startnode
)
233 "Recursivly search node with ID starting from STARTNODE."
234 (if (newsticker--treeview-ids-eq id
(widget-get startnode
:nt-id
))
235 (throw 'found startnode
)
236 (let ((children (widget-get startnode
:children
)))
238 (newsticker--treeview-do-get-node id w
)))))
240 (defun newsticker--treeview-get-node (id)
241 "Return node with ID in newsticker treeview tree."
243 (newsticker--treeview-do-get-node id newsticker--treeview-feed-tree
)
244 (newsticker--treeview-do-get-node id newsticker--treeview-vfeed-tree
)))
246 (defun newsticker--treeview-get-current-node ()
247 "Return current node in newsticker treeview tree."
248 (newsticker--treeview-get-node newsticker--treeview-current-node-id
))
250 ;; ======================================================================
252 (declare-function w3m-toggle-inline-images
"ext:w3m" (&optional force no-cache
))
254 (defun newsticker--treeview-render-text (start end
)
255 "Render text between markers START and END."
256 (if newsticker-html-renderer
257 (condition-case error-data
259 (set-marker-insertion-type end t
)
260 ;; check whether it is necessary to call html renderer
261 ;; (regexp inspired by htmlr.el)
263 (when (re-search-forward
264 "</?[A-Za-z1-6]*\\|&#?[A-Za-z0-9]+;" end t
)
265 ;; (message "%s" (newsticker--title item))
266 (let ((w3m-fill-column (if newsticker-use-full-width
268 (w3-maximum-line-length
269 (if newsticker-use-full-width nil fill-column
)))
271 (funcall newsticker-html-renderer start end
)))
272 ;;(cond ((eq newsticker-html-renderer 'w3m-region)
273 ;; (add-text-properties start end (list 'keymap
274 ;; w3m-minor-mode-map)))
275 ;;((eq newsticker-html-renderer 'w3-region)
276 ;;(add-text-properties start end (list 'keymap w3-mode-map))))
277 (if (eq newsticker-html-renderer
'w3m-region
)
278 (w3m-toggle-inline-images t
))
281 (message "Error: HTML rendering failed: %s, %s"
282 (car error-data
) (cdr error-data
))
286 ;; ======================================================================
288 ;; ======================================================================
289 (defun newsticker--treeview-list-add-item (item feed
&optional show-feed
)
290 "Add news ITEM for FEED to newsticker treeview list window.
291 If string SHOW-FEED is non-nil it is shown in the item string."
292 (setq newsticker--treeview-list-show-feed show-feed
)
294 (set-buffer (newsticker--treeview-list-buffer))
295 (let* ((inhibit-read-only t
)
297 (goto-char (point-max))
298 (setq pos1
(point-marker))
300 (insert (propertize " " 'display
'(space :align-to
2)))
301 (insert (if show-feed
304 (format "%-10s" (newsticker--real-feed-name
307 (propertize " " 'display
'(space :align-to
12)))
309 (insert (format-time-string "%d.%m.%y, %H:%M"
310 (newsticker--time item
)))
311 (insert (propertize " " 'display
312 (list 'space
:align-to
(if show-feed
28 18))))
313 (setq pos2
(point-marker))
314 (insert (newsticker--title item
))
316 (newsticker--treeview-render-text pos2
(point-marker))
318 (while (search-forward "\n" nil t
)
320 (let ((map (make-sparse-keymap)))
321 (define-key map
[mouse-1
] 'newsticker-treeview-tree-click
)
322 (define-key map
"\n" 'newsticker-treeview-show-item
)
323 (define-key map
"\C-m" 'newsticker-treeview-show-item
)
324 (add-text-properties pos1
(point-max)
327 :nt-link
(newsticker--link item
)
328 'mouse-face
'highlight
330 'help-echo
(buffer-substring pos2
334 (defun newsticker--treeview-list-clear ()
335 "Clear the newsticker treeview list window."
337 (set-buffer (newsticker--treeview-list-buffer))
338 (let ((inhibit-read-only t
))
340 (kill-all-local-variables)
343 (defun newsticker--treeview-list-items-with-age-callback (widget
346 "Fill newsticker treeview list window with items of certain age.
347 This is a callback function for the treeview nodes.
348 Argument WIDGET is the calling treeview widget.
349 Argument CHANGED-WIDGET is the widget that actually has changed.
350 Optional argument AGES is the list of ages that are to be shown."
351 (newsticker--treeview-list-clear)
352 (widget-put widget
:nt-selected t
)
353 (apply 'newsticker--treeview-list-items-with-age ages
))
355 (defun newsticker--treeview-list-items-with-age (&rest ages
)
356 "Actually fill newsticker treeview list window with items of certain age.
357 AGES is the list of ages that are to be shown."
359 (let ((feed-name-symbol (intern (car feed
))))
361 (when (memq (newsticker--age item
) ages
)
362 (newsticker--treeview-list-add-item
363 item feed-name-symbol t
)))
364 (newsticker--treeview-list-sort-items
365 (cdr (newsticker--cache-get-feed feed-name-symbol
))))))
366 (append newsticker-url-list-defaults newsticker-url-list
))
367 (newsticker--treeview-list-update nil
))
369 (defun newsticker--treeview-list-new-items (widget changed-widget
371 "Fill newsticker treeview list window with new items.
372 This is a callback function for the treeview nodes.
373 Argument WIDGET is the calling treeview widget.
374 Argument CHANGED-WIDGET is the widget that actually has changed.
375 Optional argument EVENT is the mouse event that triggered this action."
376 (newsticker--treeview-list-items-with-age-callback widget changed-widget
378 (newsticker--treeview-item-show-text
380 "This is a virtual feed containing all new items"))
382 (defun newsticker--treeview-list-immortal-items (widget changed-widget
384 "Fill newsticker treeview list window with immortal items.
385 This is a callback function for the treeview nodes.
386 Argument WIDGET is the calling treeview widget.
387 Argument CHANGED-WIDGET is the widget that actually has changed.
388 Optional argument EVENT is the mouse event that triggered this action."
389 (newsticker--treeview-list-items-with-age-callback widget changed-widget
391 (newsticker--treeview-item-show-text
393 "This is a virtual feed containing all immortal items."))
395 (defun newsticker--treeview-list-obsolete-items (widget changed-widget
397 "Fill newsticker treeview list window with obsolete items.
398 This is a callback function for the treeview nodes.
399 Argument WIDGET is the calling treeview widget.
400 Argument CHANGED-WIDGET is the widget that actually has changed.
401 Optional argument EVENT is the mouse event that triggered this action."
402 (newsticker--treeview-list-items-with-age-callback widget changed-widget
404 (newsticker--treeview-item-show-text
406 "This is a virtual feed containing all obsolete items."))
408 (defun newsticker--treeview-list-all-items (widget changed-widget
410 "Fill newsticker treeview list window with all items.
411 This is a callback function for the treeview nodes.
412 Argument WIDGET is the calling treeview widget.
413 Argument CHANGED-WIDGET is the widget that actually has changed.
414 Optional argument EVENT is the mouse event that triggered this action."
415 (newsticker--treeview-list-items-with-age-callback widget changed-widget
418 (newsticker--treeview-item-show-text
420 "This is a virtual feed containing all items."))
422 (defun newsticker--treeview-list-items-v (vfeed-name)
423 "List items for virtual feed VFEED-NAME."
425 (cond ((string-match "\\*new\\*" vfeed-name
)
426 (newsticker--treeview-list-items-with-age 'new
))
427 ((string-match "\\*immortal\\*" vfeed-name
)
428 (newsticker--treeview-list-items-with-age 'immortal
))
429 ((string-match "\\*old\\*" vfeed-name
)
430 (newsticker--treeview-list-items-with-age 'old nil
)))
431 (newsticker--treeview-list-update nil
)
434 (defun newsticker--treeview-list-items (feed-name)
435 "List items for feed FEED-NAME."
437 (if (newsticker--treeview-virtual-feed-p feed-name
)
438 (newsticker--treeview-list-items-v feed-name
)
440 (if (eq (newsticker--age item
) 'feed
)
441 (newsticker--treeview-item-show item
(intern feed-name
))
442 (newsticker--treeview-list-add-item item
443 (intern feed-name
))))
444 (newsticker--treeview-list-sort-items
445 (cdr (newsticker--cache-get-feed (intern feed-name
)))))
446 (newsticker--treeview-list-update nil
))))
448 (defun newsticker--treeview-list-feed-items (widget changed-widget
450 "Callback function for listing feed items.
451 Argument WIDGET is the calling treeview widget.
452 Argument CHANGED-WIDGET is the widget that actually has changed.
453 Optional argument EVENT is the mouse event that triggered this action."
454 (newsticker--treeview-list-clear)
455 (widget-put widget
:nt-selected t
)
456 (let ((feed-name (widget-get widget
:nt-feed
))
457 (vfeed-name (widget-get widget
:nt-vfeed
)))
459 (newsticker--treeview-list-items feed-name
)
460 (newsticker--treeview-list-items-v vfeed-name
))))
462 (defun newsticker--treeview-list-compare-item-by-age (item1 item2
)
463 "Compare two news items ITEM1 and ITEM2 wrt age."
465 (let ((age1 (newsticker--age item1
))
466 (age2 (newsticker--age item2
)))
467 (cond ((eq age1
'new
)
470 (cond ((eq age2
'new
)
477 (cond ((eq age2
'new
)
488 (defun newsticker--treeview-list-compare-item-by-age-reverse (item1 item2
)
489 "Compare two news items ITEM1 and ITEM2 wrt age in reverse order."
490 (newsticker--treeview-list-compare-item-by-age item2 item1
))
492 (defun newsticker--treeview-list-compare-item-by-time (item1 item2
)
493 "Compare two news items ITEM1 and ITEM2 wrt time values."
494 (newsticker--cache-item-compare-by-time item1 item2
))
496 (defun newsticker--treeview-list-compare-item-by-time-reverse (item1 item2
)
497 "Compare two news items ITEM1 and ITEM2 wrt time values in reverse order."
498 (newsticker--cache-item-compare-by-time item2 item1
))
500 (defun newsticker--treeview-list-compare-item-by-title (item1 item2
)
501 "Compare two news items ITEM1 and ITEM2 wrt title."
502 (newsticker--cache-item-compare-by-title item1 item2
))
504 (defun newsticker--treeview-list-compare-item-by-title-reverse (item1 item2
)
505 "Compare two news items ITEM1 and ITEM2 wrt title in reverse order."
506 (newsticker--cache-item-compare-by-title item2 item1
))
508 (defun newsticker--treeview-list-sort-items (items)
509 "Return sorted copy of list ITEMS.
510 The sort function is chosen according to the value of
511 `newsticker--treeview-list-sort-order'."
513 (cond ((eq newsticker--treeview-list-sort-order
'sort-by-age
)
514 'newsticker--treeview-list-compare-item-by-age
)
515 ((eq newsticker--treeview-list-sort-order
516 'sort-by-age-reverse
)
517 'newsticker--treeview-list-compare-item-by-age-reverse
)
518 ((eq newsticker--treeview-list-sort-order
'sort-by-time
)
519 'newsticker--treeview-list-compare-item-by-time
)
520 ((eq newsticker--treeview-list-sort-order
521 'sort-by-time-reverse
)
522 'newsticker--treeview-list-compare-item-by-time-reverse
)
523 ((eq newsticker--treeview-list-sort-order
'sort-by-title
)
524 'newsticker--treeview-list-compare-item-by-title
)
525 ((eq newsticker--treeview-list-sort-order
526 'sort-by-title-reverse
)
527 'newsticker--treeview-list-compare-item-by-title-reverse
)
529 'newsticker--treeview-list-compare-item-by-title
))))
530 (sort (copy-sequence items
) sort-fun
)))
532 (defun newsticker--treeview-list-update-faces ()
533 "Update faces in the treeview list buffer."
536 (set-buffer (newsticker--treeview-list-buffer))
537 (let ((inhibit-read-only t
))
538 (goto-char (point-min))
540 (let* ((pos (save-excursion (end-of-line) (point)))
541 (item (get-text-property (point) :nt-item
))
542 (age (newsticker--age item
))
543 (selected (get-text-property (point) :nt-selected
))
544 (face (cond ((eq age
'new
)
545 'newsticker-treeview-new-face
)
547 'newsticker-treeview-old-face
)
549 'newsticker-treeview-immortal-face
)
551 'newsticker-treeview-obsolete-face
)
554 (put-text-property (point) pos
'face face
)
556 (move-overlay newsticker--selection-overlay
(point)
557 (1+ pos
) ;include newline
559 (if selected
(setq pos-sel
(point)))
561 (beginning-of-line))))) ;; FIXME!?
563 (set-window-point (newsticker--treeview-list-window) pos-sel
))))
565 (defun newsticker--treeview-list-clear-highlight ()
566 "Clear the highlight in the treeview list buffer."
568 (set-buffer (newsticker--treeview-list-buffer))
569 (let ((inhibit-read-only t
))
570 (put-text-property (point-min) (point-max) :nt-selected nil
))
571 (newsticker--treeview-list-update-faces)))
573 (defun newsticker--treeview-list-update-highlight ()
574 "Update the highlight in the treeview list buffer."
575 (newsticker--treeview-list-clear-highlight)
578 (set-buffer (newsticker--treeview-list-buffer))
579 (let ((inhibit-read-only t
))
580 (put-text-property (save-excursion (beginning-of-line) (point))
581 (save-excursion (end-of-line) (point))
583 (newsticker--treeview-list-update-faces))))
585 (defun newsticker--treeview-list-highlight-start ()
586 "Return position of selection in treeview list buffer."
588 (set-buffer (newsticker--treeview-list-buffer))
589 (goto-char (point-min))
590 (next-single-property-change (point) :nt-selected
)))
592 (defun newsticker--treeview-list-update (clear-buffer)
593 "Update the faces and highlight in the treeview list buffer.
594 If CLEAR-BUFFER is non-nil the list buffer is completely erased."
596 (set-window-buffer (newsticker--treeview-list-window)
597 (newsticker--treeview-list-buffer))
598 (set-buffer (newsticker--treeview-list-buffer))
600 (let ((inhibit-read-only t
))
602 (newsticker-treeview-list-mode)
603 (newsticker--treeview-list-update-faces)
604 (goto-char (point-min))))
606 (defvar newsticker-treeview-list-sort-button-map
607 (let ((map (make-sparse-keymap)))
608 (define-key map
[header-line mouse-1
]
609 'newsticker--treeview-list-sort-by-column
)
610 (define-key map
[header-line mouse-2
]
611 'newsticker--treeview-list-sort-by-column
)
613 "Local keymap for newsticker treeview list window sort buttons.")
615 (defun newsticker--treeview-list-sort-by-column (&optional event
)
616 "Sort the newsticker list window buffer by the column clicked on.
617 Optional argument EVENT is the mouse event that triggered this action."
618 (interactive (list last-input-event
))
619 (if e
(mouse-select-window e
))
620 (let* ((pos (event-start e
))
621 (obj (posn-object pos
))
623 (get-text-property (cdr obj
) 'sort-order
(car obj
))
624 (get-text-property (posn-point pos
) 'sort-order
))))
625 (setq newsticker--treeview-list-sort-order
626 (cond ((eq sort-order
'sort-by-age
)
627 (if (eq newsticker--treeview-list-sort-order
'sort-by-age
)
630 ((eq sort-order
'sort-by-time
)
631 (if (eq newsticker--treeview-list-sort-order
'sort-by-time
)
632 'sort-by-time-reverse
634 ((eq sort-order
'sort-by-title
)
635 (if (eq newsticker--treeview-list-sort-order
'sort-by-title
)
636 'sort-by-title-reverse
638 (newsticker-treeview-update)))
640 (defun newsticker-treeview-list-make-sort-button (name sort-order
)
641 "Create propertized string for headerline button.
642 NAME is the button text, SORT-ORDER is the associated sort order
644 (let ((face (if (string-match (symbol-name sort-order
)
646 newsticker--treeview-list-sort-order
))
650 'sort-order sort-order
651 'help-echo
(concat "Sort by " name
)
652 'mouse-face
'highlight
654 'keymap newsticker-treeview-list-sort-button-map
)))
656 ;; ======================================================================
658 ;; ======================================================================
659 (defun newsticker--treeview-item-show-text (title description
)
660 "Show text in treeview item buffer consisting of TITLE and DESCRIPTION."
662 (set-buffer (newsticker--treeview-item-buffer))
663 (when (fboundp 'w3m-process-stop
)
664 (w3m-process-stop (current-buffer)))
665 (let ((inhibit-read-only t
))
667 (kill-all-local-variables)
670 (put-text-property (point-min) (point) 'face
'newsticker-feed-face
)
671 (insert "\n\n" description
)
672 (when newsticker-justification
673 (fill-region (point-min) (point-max) newsticker-justification
))
674 (newsticker-treeview-mode)
675 (goto-char (point-min)))))
677 (defun newsticker--treeview-item-show (item feed
)
678 "Show news ITEM coming from FEED in treeview item buffer."
680 (set-buffer (newsticker--treeview-item-buffer))
681 (when (fboundp 'w3m-process-stop
)
682 (w3m-process-stop (current-buffer)))
683 (let ((inhibit-read-only t
)
684 (is-rendered-HTML nil
)
686 (marker1 (make-marker))
687 (marker2 (make-marker)))
689 (kill-all-local-variables)
692 (when (and item feed
)
693 (let ((wwidth (1- (window-width (newsticker--treeview-item-window)))))
694 (if newsticker-use-full-width
695 (set (make-local-variable 'fill-column
) wwidth
))
696 (set (make-local-variable 'fill-column
) (min fill-column
698 (let ((desc (newsticker--desc item
)))
699 (insert "\n" (or desc
"[No Description]")))
700 (set-marker marker1
(1+ (point-min)))
701 (set-marker marker2
(point-max))
702 (setq is-rendered-HTML
(newsticker--treeview-render-text marker1
704 (when (and newsticker-justification
705 (not is-rendered-HTML
))
706 (fill-region marker1 marker2 newsticker-justification
))
708 (newsticker-treeview-mode)
709 (goto-char (point-min))
710 ;; insert logo at top
711 (let* ((newsticker-enable-logo-manipulations nil
)
712 (img (newsticker--image-read feed nil
)))
713 (if (and (display-images-p) img
)
714 (newsticker--insert-image img
(car item
))
715 (insert (newsticker--real-feed-name feed
))))
716 (add-text-properties (point-min) (point)
717 (list 'face
'newsticker-feed-face
718 'mouse-face
'highlight
719 'help-echo
"Visit in web browser."
720 :nt-link
(newsticker--link item
)
721 'keymap newsticker--treeview-url-keymap
))
727 (insert (newsticker--title item
) "\n")
728 (set-marker marker1 pos
)
729 (set-marker marker2
(point))
730 (newsticker--treeview-render-text marker1 marker2
)
731 (put-text-property pos
(point) 'face
'newsticker-treeview-new-face
)
735 (put-text-property marker2
(point) 'face
'newsticker-treeview-face
)
736 (set-marker marker2
(point))
737 (when newsticker-justification
738 (fill-region marker1 marker2 newsticker-justification
))
740 (add-text-properties marker1
(1- (point))
741 (list 'mouse-face
'highlight
742 'help-echo
"Visit in web browser."
743 :nt-link
(newsticker--link item
)
744 'keymap newsticker--treeview-url-keymap
))
745 (insert (format-time-string newsticker-date-format
746 (newsticker--time item
)))
750 ;; insert enclosures and rest at bottom
751 (goto-char (point-max))
754 (newsticker--insert-enclosure item newsticker--treeview-url-keymap
)
755 (put-text-property pos
(point) 'face
'newsticker-enclosure-face
)
758 (newsticker--print-extra-elements item newsticker--treeview-url-keymap
)
759 (put-text-property pos
(point) 'face
'newsticker-extra-face
)
760 (goto-char (point-min)))))
761 (if (and newsticker-treeview-automatically-mark-displayed-items-as-old
763 (memq (newsticker--age item
) '(new obsolete
)))
764 (let ((newsticker-treeview-automatically-mark-displayed-items-as-old nil
))
765 (newsticker-treeview-mark-item-old t
)
766 (newsticker--treeview-list-update-faces)))
767 (set-window-point (newsticker--treeview-item-window) 1))
769 (defun newsticker--treeview-item-update ()
770 "Update the treeview item buffer and window."
772 (set-window-buffer (newsticker--treeview-item-window)
773 (newsticker--treeview-item-buffer))
774 (set-buffer (newsticker--treeview-item-buffer))
775 (let ((inhibit-read-only t
))
777 (newsticker-treeview-mode)))
779 ;; ======================================================================
781 ;; ======================================================================
782 (defun newsticker--treeview-tree-expand (tree)
784 Callback function for tree widget that adds nodes for feeds and subgroups."
785 (tree-widget-set-theme "folder")
786 (let ((group (widget-get tree
:nt-group
))
790 (setq nt-id
(newsticker--treeview-get-id tree i
))
793 (let* ((g-name (car g
)))
795 :tag
,(newsticker--treeview-tree-get-tag g-name nil nt-id
)
796 :expander newsticker--treeview-tree-expand
797 :expander-p
(lambda (&rest ignore
) t
)
801 :keep
(:nt-feed
:num-new
:nt-id
:open
);; :nt-group
803 (let ((tag (newsticker--treeview-tree-get-tag g nil nt-id
)))
805 :leaf-icon newsticker--tree-widget-leaf-icon
807 :action newsticker--treeview-list-feed-items
813 (defun newsticker--treeview-tree-expand-status (tree &optional changed-widget
815 "Expand the vfeed TREE.
816 Optional arguments CHANGED-WIDGET and EVENT are ignored."
817 (tree-widget-set-theme "folder")
818 (list `(item :tag
,(newsticker--treeview-tree-get-tag nil
"new")
820 :action newsticker--treeview-list-new-items
821 :nt-id
,(newsticker--treeview-get-id tree
0)
823 `(item :tag
,(newsticker--treeview-tree-get-tag nil
"immortal")
825 :action newsticker--treeview-list-immortal-items
826 :nt-id
,(newsticker--treeview-get-id tree
1)
828 `(item :tag
,(newsticker--treeview-tree-get-tag nil
"obsolete")
830 :action newsticker--treeview-list-obsolete-items
831 :nt-id
,(newsticker--treeview-get-id tree
2)
833 `(item :tag
,(newsticker--treeview-tree-get-tag nil
"all")
835 :action newsticker--treeview-list-all-items
836 :nt-id
,(newsticker--treeview-get-id tree
3)
839 (defun newsticker--treeview-virtual-feed-p (feed-name)
840 "Return non-nil if FEED-NAME is a virtual feed."
841 (string-match "\\*.*\\*" feed-name
))
843 (define-widget 'newsticker--tree-widget-leaf-icon
'tree-widget-icon
844 "Icon for a tree-widget leaf node."
847 :button-face
'default
)
849 (defun newsticker--treeview-tree-update ()
850 "Update treeview tree buffer and window."
852 (set-window-buffer (newsticker--treeview-tree-window)
853 (newsticker--treeview-tree-buffer))
854 (set-buffer (newsticker--treeview-tree-buffer))
855 (kill-all-local-variables)
856 (let ((inhibit-read-only t
))
858 (tree-widget-set-theme "folder")
859 (setq newsticker--treeview-feed-tree
860 (widget-create 'tree-widget
861 :tag
(newsticker--treeview-propertize-tag
863 :expander
'newsticker--treeview-tree-expand
864 :expander-p
(lambda (&rest ignore
) t
)
865 :leaf-icon
'newsticker--tree-widget-leaf-icon
866 :nt-group
(cdr newsticker-groups
)
870 (setq newsticker--treeview-vfeed-tree
871 (widget-create 'tree-widget
872 :tag
(newsticker--treeview-propertize-tag
873 "Virtual Feeds" 0 "vfeeds")
874 :expander
'newsticker--treeview-tree-expand-status
875 :expander-p
(lambda (&rest ignore
) t
)
876 :leaf-icon
'newsticker--tree-widget-leaf-icon
880 (use-local-map widget-keymap
)
882 (newsticker-treeview-mode)))
884 (defun newsticker--treeview-propertize-tag (tag &optional num-new nt-id feed
886 "Return propertized copy of string TAG.
887 Optional argument NUM-NEW is used for choosing face, other
888 arguments NT-ID, FEED, and VFEED are added as properties."
889 ;;(message "newsticker--treeview-propertize-tag '%s' %s" feed nt-id)
890 (let ((face 'newsticker-treeview-face
)
891 (map (make-sparse-keymap)))
892 (if (and num-new
(> num-new
0))
893 (setq face
'newsticker-treeview-new-face
))
894 (define-key map
[mouse-1
] 'newsticker-treeview-tree-click
)
895 (define-key map
"\n" 'newsticker-treeview-tree-do-click
)
896 (define-key map
"\C-m" 'newsticker-treeview-tree-do-click
)
897 (propertize tag
'face face
'keymap map
902 'mouse-face
'highlight
)))
904 (defun newsticker--treeview-tree-get-tag (feed-name vfeed-name
906 "Return a tag string for either FEED-NAME or, if it is nil, for VFEED-NAME.
907 Optional argument NT-ID is added to the tag's properties."
908 (let (tag (num-new 0))
910 (cond ((string= vfeed-name
"new")
911 (setq num-new
(newsticker--stat-num-items-total 'new
))
912 (setq tag
(format "New items (%d)" num-new
)))
913 ((string= vfeed-name
"immortal")
914 (setq num-new
(newsticker--stat-num-items-total 'immortal
))
915 (setq tag
(format "Immortal items (%d)" num-new
)))
916 ((string= vfeed-name
"obsolete")
917 (setq num-new
(newsticker--stat-num-items-total 'obsolete
))
918 (setq tag
(format "Obsolete items (%d)" num-new
)))
919 ((string= vfeed-name
"all")
920 (setq num-new
(newsticker--stat-num-items-total))
921 (setq tag
(format "All items (%d)" num-new
)))))
923 (setq num-new
(newsticker--stat-num-items-for-group
924 (intern feed-name
) 'new
'immortal
))
927 (newsticker--real-feed-name (intern feed-name
))
930 (newsticker--treeview-propertize-tag tag num-new
932 feed-name vfeed-name
))))
934 (defun newsticker--stat-num-items-for-group (feed-name-symbol &rest ages
)
935 "Count number of items in feed FEED-NAME-SYMBOL that have an age matching AGES."
936 ;;(message "newsticker--stat-num-items-for-group %s %s" feed-name-symbol ages)
937 (let ((result (apply 'newsticker--stat-num-items feed-name-symbol ages
)))
939 (setq result
(+ result
940 (apply 'newsticker--stat-num-items
(intern f-n
)
942 (newsticker--group-get-feeds
943 (newsticker--group-get-group (symbol-name feed-name-symbol
)) t
))
946 (defun newsticker--treeview-count-node-items (feed &optional isvirtual
)
947 "Count number of relevant items for a treeview node.
948 FEED gives the name of the feed or group. If ISVIRTUAL is non-nil
949 the feed is a virtual feed."
953 (cond ((string= feed
"new")
954 (setq num-new
(newsticker--stat-num-items-total 'new
)))
955 ((string= feed
"immortal")
956 (setq num-new
(newsticker--stat-num-items-total 'immortal
)))
957 ((string= feed
"obsolete")
958 (setq num-new
(newsticker--stat-num-items-total 'obsolete
)))
959 ((string= feed
"all")
960 (setq num-new
(newsticker--stat-num-items-total))))
961 (setq num-new
(newsticker--stat-num-items-for-group
962 (intern feed
) 'new
'immortal
))))
965 (defun newsticker--treeview-tree-update-tag (w &optional recursive
967 "Update tag for tree widget W.
968 If RECURSIVE is non-nil recursively update parent widgets as
969 well. Argument IGNORE is ignored. Note that this function, if
970 called recursively, makes w invalid. You should keep w's nt-id in
972 ;;(message "newsticker--treeview-tree-update-tag %s, %s" (widget-get w :tag)
974 (let* ((parent (widget-get w
:parent
))
975 (feed (or (widget-get w
:nt-feed
) (widget-get parent
:nt-feed
)))
976 (vfeed (or (widget-get w
:nt-vfeed
) (widget-get parent
:nt-vfeed
)))
977 (nt-id (or (widget-get w
:nt-id
) (widget-get parent
:nt-id
)))
978 (num-new (newsticker--treeview-count-node-items (or feed vfeed
)
980 (tag (newsticker--treeview-tree-get-tag feed vfeed nt-id
))
981 (n (widget-get w
:node
)))
984 (newsticker--treeview-tree-update-tag parent
)))
987 (widget-put n
:tag tag
))
988 (widget-put w
:num-new num-new
)
989 (widget-put w
:tag tag
)
990 (when (marker-position (widget-get w
:from
))
992 (notify (widget-get w
:notify
)))
993 ;; FIXME: This moves point!!!!
995 (set-buffer (newsticker--treeview-tree-buffer))
996 (widget-value-set w
(widget-value w
)))
999 (defun newsticker--treeview-tree-do-update-tags (widget)
1000 "Actually recursively update tags for WIDGET."
1002 (let ((children (widget-get widget
:children
)))
1003 (dolist (w children
)
1004 (newsticker--treeview-tree-do-update-tags w
))
1005 (newsticker--treeview-tree-update-tag widget
))))
1007 (defun newsticker--treeview-tree-update-tags (&rest ignore
)
1008 "Update all tags of all trees.
1009 Arguments IGNORE are ignored."
1010 (save-current-buffer
1011 (set-buffer (newsticker--treeview-tree-buffer))
1012 (let ((inhibit-read-only t
))
1013 (newsticker--treeview-tree-do-update-tags
1014 newsticker--treeview-feed-tree
)
1015 (newsticker--treeview-tree-do-update-tags
1016 newsticker--treeview-vfeed-tree
))
1017 (tree-widget-set-theme "folder")))
1019 (defun newsticker--treeview-tree-update-highlight ()
1020 "Update highlight in tree buffer."
1021 (let ((pos (widget-get (newsticker--treeview-get-current-node) :from
)))
1022 (unless (or (integerp pos
) (and (markerp pos
) (marker-position pos
)))
1023 (setq pos
(widget-get (widget-get
1024 (newsticker--treeview-get-current-node)
1026 (when (or (integerp pos
) (and (markerp pos
) (marker-position pos
)))
1028 (set-buffer (newsticker--treeview-tree-buffer))
1030 (move-overlay newsticker--tree-selection-overlay
1031 (save-excursion (beginning-of-line) (point))
1032 (save-excursion (end-of-line) (1+ (point)))
1034 (set-window-point (newsticker--treeview-tree-window) pos
))))
1036 ;; ======================================================================
1038 ;; ======================================================================
1039 ;;(makunbound 'newsticker-treeview-tool-bar-map)
1040 (defvar newsticker-treeview-tool-bar-map
1041 (if (featurep 'xemacs
)
1043 (if (boundp 'tool-bar-map
)
1044 (let ((tool-bar-map (make-sparse-keymap)))
1045 (define-key tool-bar-map
[newsticker-sep-1
]
1046 (list 'menu-item
"--double-line"))
1047 (define-key tool-bar-map
[newsticker-browse-url
]
1048 (list 'menu-item
"newsticker-browse-url"
1049 'newsticker-browse-url
1051 :help
"Browse URL for item at point"
1052 :image newsticker--browse-image
))
1053 (define-key tool-bar-map
[newsticker-buffer-force-update
]
1054 (list 'menu-item
"newsticker-treeview-update"
1055 'newsticker-treeview-update
1057 :help
"Update newsticker buffer"
1058 :image newsticker--update-image
1060 (define-key tool-bar-map
[newsticker-get-all-news
]
1061 (list 'menu-item
"newsticker-get-all-news" 'newsticker-get-all-news
1063 :help
"Get news for all feeds"
1064 :image newsticker--get-all-image
))
1065 (define-key tool-bar-map
[newsticker-mark-item-at-point-as-read
]
1066 (list 'menu-item
"newsticker-treeview-mark-item-old"
1067 'newsticker-treeview-mark-item-old
1069 :image newsticker--mark-read-image
1070 :help
"Mark current item as read"
1071 ;;:enable '(newsticker-item-not-old-p) FIXME
1073 (define-key tool-bar-map
[newsticker-mark-item-at-point-as-immortal
]
1074 (list 'menu-item
"newsticker-treeview-toggle-item-immortal"
1075 'newsticker-treeview-toggle-item-immortal
1077 :image newsticker--mark-immortal-image
1078 :help
"Toggle current item as immortal"
1080 ;;'(newsticker-item-not-immortal-p) FIXME
1082 (define-key tool-bar-map
[newsticker-next-feed
]
1083 (list 'menu-item
"newsticker-treeview-next-feed"
1084 'newsticker-treeview-next-feed
1086 :help
"Go to next feed"
1087 :image newsticker--next-feed-image
1089 ;;'(newsticker-next-feed-available-p) FIXME
1091 (define-key tool-bar-map
[newsticker-treeview-next-item
]
1092 (list 'menu-item
"newsticker-treeview-next-item"
1093 'newsticker-treeview-next-item
1095 :help
"Go to next item"
1096 :image newsticker--next-item-image
1098 ;;'(newsticker-next-item-available-p) FIXME
1100 (define-key tool-bar-map
[newsticker-treeview-prev-item
]
1101 (list 'menu-item
"newsticker-treeview-prev-item"
1102 'newsticker-treeview-prev-item
1104 :help
"Go to previous item"
1105 :image newsticker--previous-item-image
1107 ;;'(newsticker-previous-item-available-p) FIXME
1109 (define-key tool-bar-map
[newsticker-treeview-prev-feed
]
1110 (list 'menu-item
"newsticker-treeview-prev-feed"
1111 'newsticker-treeview-prev-feed
1113 :help
"Go to previous feed"
1114 :image newsticker--previous-feed-image
1116 ;;'(newsticker-previous-feed-available-p) FIXME
1118 ;; standard icons / actions
1119 (tool-bar-add-item "close"
1120 'newsticker-treeview-quit
1121 'newsticker-treeview-quit
1122 :help
"Close newsticker")
1123 (tool-bar-add-item "preferences"
1124 'newsticker-customize
1125 'newsticker-customize
1126 :help
"Customize newsticker")
1129 ;; ======================================================================
1131 ;; ======================================================================
1133 (defun newsticker-treeview-mouse-browse-url (event)
1134 "Call `browse-url' for the link of the item at which the EVENT occurred."
1137 (switch-to-buffer (window-buffer (posn-window (event-end event
))))
1138 (let ((url (get-text-property (posn-point (event-end event
))
1142 (if newsticker-automatically-mark-visited-items-as-old
1143 (newsticker-treeview-mark-item-old))))))
1145 (defun newsticker-treeview-browse-url ()
1146 "Call `browse-url' for the link of the item at point."
1149 (set-buffer (newsticker--treeview-list-buffer))
1150 (let ((url (get-text-property (point) :nt-link
)))
1153 (if newsticker-automatically-mark-visited-items-as-old
1154 (newsticker-treeview-mark-item-old))))))
1156 (defun newsticker--treeview-buffer-init ()
1157 "Initialize all treeview buffers."
1158 (setq newsticker--treeview-buffers nil
)
1159 (add-to-list 'newsticker--treeview-buffers
1160 (get-buffer-create "*Newsticker Tree*") t
)
1161 (add-to-list 'newsticker--treeview-buffers
1162 (get-buffer-create "*Newsticker List*") t
)
1163 (add-to-list 'newsticker--treeview-buffers
1164 (get-buffer-create "*Newsticker Item*") t
)
1166 (unless newsticker--selection-overlay
1168 (set-buffer (newsticker--treeview-list-buffer))
1169 (setq newsticker--selection-overlay
(make-overlay (point-min)
1171 (overlay-put newsticker--selection-overlay
'face
1172 'newsticker-treeview-selection-face
)))
1173 (unless newsticker--tree-selection-overlay
1175 (set-buffer (newsticker--treeview-tree-buffer))
1176 (setq newsticker--tree-selection-overlay
(make-overlay (point-min)
1178 (overlay-put newsticker--tree-selection-overlay
'face
1179 'newsticker-treeview-selection-face
)))
1181 (newsticker--treeview-tree-update)
1182 (newsticker--treeview-list-update t
)
1183 (newsticker--treeview-item-update))
1185 (defun newsticker-treeview-update ()
1186 "Update all treeview buffers and windows."
1188 (newsticker--cache-update)
1189 (newsticker--group-manage-orphan-feeds)
1190 (newsticker--treeview-list-update t
)
1191 (newsticker--treeview-item-update)
1192 (newsticker--treeview-tree-update-tags)
1193 (cond (newsticker--treeview-current-feed
1194 (newsticker--treeview-list-items newsticker--treeview-current-feed
))
1195 (newsticker--treeview-current-vfeed
1196 (newsticker--treeview-list-items-with-age
1197 (intern newsticker--treeview-current-vfeed
))))
1198 (newsticker--treeview-tree-update-highlight)
1199 (newsticker--treeview-list-update-highlight))
1201 (defun newsticker-treeview-quit ()
1202 "Quit newsticker treeview."
1204 (newsticker-treeview-save)
1205 (setq newsticker--sentinel-callback nil
)
1206 (bury-buffer "*Newsticker Tree*")
1207 (bury-buffer "*Newsticker List*")
1208 (bury-buffer "*Newsticker Item*")
1209 (set-window-configuration newsticker--saved-window-config
)
1210 (when newsticker--frame
1211 (if (frame-live-p newsticker--frame
)
1212 (delete-frame newsticker--frame
))
1213 (setq newsticker--frame nil
)))
1215 (defun newsticker-treeview-save ()
1216 "Save newsticker data including treeview settings."
1218 (newsticker--cache-save)
1220 (let ((coding-system-for-write 'utf-8
)
1221 (buf (find-file-noselect newsticker-groups-filename
)))
1224 (setq buffer-undo-list t
)
1226 (insert ";; -*- coding: utf-8 -*-\n")
1227 (insert (prin1-to-string newsticker-groups
))
1230 (defun newsticker--treeview-load ()
1231 "Load treeview settings."
1232 (let* ((coding-system-for-read 'utf-8
)
1233 (buf (and (file-exists-p newsticker-groups-filename
)
1234 (find-file-noselect newsticker-groups-filename
))))
1237 (goto-char (point-min))
1239 (setq newsticker-groups
(read buf
))
1241 (message "Error while reading newsticker groups file!")
1242 (setq newsticker-groups nil
))))))
1245 (defun newsticker-treeview-scroll-item ()
1246 "Scroll current item."
1248 (save-selected-window
1249 (select-window (newsticker--treeview-item-window) t
)
1252 (defun newsticker-treeview-show-item ()
1253 "Show current item."
1255 (newsticker--treeview-list-update-highlight)
1257 (set-buffer (newsticker--treeview-list-buffer))
1259 (let ((item (get-text-property (point) :nt-item
))
1260 (feed (get-text-property (point) :nt-feed
)))
1261 (newsticker--treeview-item-show item feed
)))
1262 (newsticker--treeview-tree-update-tag
1263 (newsticker--treeview-get-current-node) t
)
1264 (newsticker--treeview-tree-update-highlight))
1266 (defun newsticker-treeview-next-item ()
1267 "Move to next item."
1269 (newsticker--treeview-restore-buffers)
1270 (save-current-buffer
1271 (set-buffer (newsticker--treeview-list-buffer))
1272 (if (newsticker--treeview-list-highlight-start)
1276 (newsticker-treeview-show-item))
1278 (defun newsticker-treeview-prev-item ()
1279 "Move to previous item."
1281 (newsticker--treeview-restore-buffers)
1282 (save-current-buffer
1283 (set-buffer (newsticker--treeview-list-buffer))
1285 (newsticker-treeview-show-item))
1287 (defun newsticker-treeview-next-new-or-immortal-item ()
1288 "Move to next new or immortal item."
1290 (newsticker--treeview-restore-buffers)
1291 (newsticker--treeview-list-clear-highlight)
1293 (let ((index (newsticker-treeview-next-item)))
1295 (save-current-buffer
1296 (set-buffer (newsticker--treeview-list-buffer))
1300 (throw 'found nil
)))
1301 (when (memq (newsticker--age
1302 (newsticker--treeview-get-selected-item)) '(new immortal
))
1303 (newsticker-treeview-show-item)
1304 (throw 'found t
))))))
1306 (defun newsticker-treeview-prev-new-or-immortal-item ()
1307 "Move to previous new or immortal item."
1309 (newsticker--treeview-restore-buffers)
1310 (newsticker--treeview-list-clear-highlight)
1312 (let ((index (newsticker-treeview-next-item)))
1314 (save-current-buffer
1315 (set-buffer (newsticker--treeview-list-buffer))
1318 (throw 'found nil
)))
1319 (when (memq (newsticker--age
1320 (newsticker--treeview-get-selected-item)) '(new immortal
))
1321 (newsticker-treeview-show-item)
1322 (throw 'found t
))))))
1324 (defun newsticker--treeview-get-selected-item ()
1325 "Return item that is currently selected in list buffer."
1327 (set-buffer (newsticker--treeview-list-buffer))
1329 (get-text-property (point) :nt-item
)))
1331 (defun newsticker-treeview-mark-item-old (&optional dont-proceed
)
1332 "Mark current item as old unless it is obsolete.
1333 Move to next item unless DONT-PROCEED is non-nil."
1335 (let ((item (newsticker--treeview-get-selected-item)))
1336 (unless (eq (newsticker--age item
) 'obsolete
)
1337 (newsticker--treeview-mark-item item
'old
)))
1338 (unless dont-proceed
1339 (newsticker-treeview-next-item)))
1341 (defun newsticker-treeview-toggle-item-immortal ()
1342 "Toggle immortality of current item."
1344 (let* ((item (newsticker--treeview-get-selected-item))
1345 (new-age (if (eq (newsticker--age item
) 'immortal
)
1348 (newsticker--treeview-mark-item item new-age
)
1349 (newsticker-treeview-next-item)))
1351 (defun newsticker--treeview-mark-item (item new-age
)
1352 "Mark ITEM with NEW-AGE."
1354 (setcar (nthcdr 4 item
) new-age
)
1355 ;; clean up ticker FIXME
1358 (defun newsticker-treeview-mark-list-items-old ()
1359 "Mark all listed items as old."
1361 (let ((current-feed (or newsticker--treeview-current-feed
1362 newsticker--treeview-current-vfeed
)))
1364 (set-buffer (newsticker--treeview-list-buffer))
1365 (goto-char (point-min))
1367 (let ((item (get-text-property (point) :nt-item
)))
1368 (unless (memq (newsticker--age item
) '(immortal obsolete
))
1369 (newsticker--treeview-mark-item item
'old
)))
1371 (newsticker--treeview-tree-update-tags)
1373 (newsticker-treeview-jump current-feed
))))
1375 (defun newsticker-treeview-save-item ()
1376 "Save current item."
1378 (newsticker-save-item (or newsticker--treeview-current-feed
1379 newsticker--treeview-current-vfeed
)
1380 (newsticker--treeview-get-selected-item)))
1382 (defun newsticker--treeview-set-current-node (node)
1383 "Make NODE the current node."
1385 (set-buffer (newsticker--treeview-tree-buffer))
1386 (setq newsticker--treeview-current-node-id
1387 (widget-get node
:nt-id
))
1388 (setq newsticker--treeview-current-feed
(widget-get node
:nt-feed
))
1389 (setq newsticker--treeview-current-vfeed
(widget-get node
:nt-vfeed
))
1390 ;;(message "newsticker--treeview-set-current-node %s/%s" (widget-get node :tag)
1391 ;; (widget-get node :nt-id))
1393 (newsticker--treeview-tree-update-highlight)))
1395 (defun newsticker--treeview-get-first-child (node)
1396 "Get first child of NODE."
1397 (let ((children (widget-get node
:children
)))
1402 (defun newsticker--treeview-get-second-child (node)
1403 "Get scond child of NODE."
1404 (let ((children (widget-get node
:children
)))
1406 (car (cdr children
))
1409 (defun newsticker--treeview-get-last-child (node)
1410 "Get last child of NODE."
1411 ;;(message "newsticker--treeview-get-last-child %s" (widget-get node :tag))
1412 (let ((children (widget-get node
:children
)))
1414 (car (reverse children
))
1417 (defun newsticker--treeview-get-feed-vfeed (node)
1418 "Get (virtual) feed of NODE."
1419 (or (widget-get node
:nt-feed
) (widget-get node
:nt-vfeed
)))
1421 (defun newsticker--treeview-get-next-sibling (node)
1422 "Get next sibling of NODE."
1423 (let ((parent (widget-get node
:parent
)))
1425 (let ((children (widget-get parent
:children
)))
1427 (if (newsticker--treeview-nodes-eq (car children
) node
)
1428 (throw 'found
(car (cdr children
))))
1429 (setq children
(cdr children
)))))))
1431 (defun newsticker--treeview-get-prev-sibling (node)
1432 "Get previous sibling of NODE."
1433 (let ((parent (widget-get node
:parent
)))
1435 (let ((children (widget-get parent
:children
))
1438 (if (and (newsticker--treeview-nodes-eq (car children
) node
)
1439 (widget-get prev
:nt-id
))
1440 (throw 'found prev
))
1441 (setq prev
(car children
))
1442 (setq children
(cdr children
)))))))
1444 (defun newsticker--treeview-get-next-uncle (node)
1445 "Get next uncle of NODE, i.e. parent's next sibling."
1446 (let* ((parent (widget-get node
:parent
))
1447 (grand-parent (widget-get parent
:parent
)))
1449 (let ((uncles (widget-get grand-parent
:children
)))
1451 (if (newsticker--treeview-nodes-eq (car uncles
) parent
)
1452 (throw 'found
(car (cdr uncles
))))
1453 (setq uncles
(cdr uncles
)))))))
1455 (defun newsticker--treeview-get-prev-uncle (node)
1456 "Get previous uncle of NODE, i.e. parent's previous sibling."
1457 (let* ((parent (widget-get node
:parent
))
1458 (grand-parent (widget-get parent
:parent
)))
1460 (let ((uncles (widget-get grand-parent
:children
))
1463 (if (newsticker--treeview-nodes-eq (car uncles
) parent
)
1464 (throw 'found prev
))
1465 (setq prev
(car uncles
))
1466 (setq uncles
(cdr uncles
)))))))
1468 (defun newsticker--treeview-get-other-tree ()
1470 (if (and (newsticker--treeview-get-current-node)
1471 (widget-get (newsticker--treeview-get-current-node) :nt-feed
))
1472 newsticker--treeview-vfeed-tree
1473 newsticker--treeview-feed-tree
))
1475 (defun newsticker--treeview-activate-node (node &optional backward
)
1477 If NODE is a tree widget the node's first subnode is activated.
1478 If BACKWARD is non-nil the last subnode of the previous sibling
1480 (newsticker--treeview-set-current-node node
)
1481 (save-current-buffer
1482 (set-buffer (newsticker--treeview-tree-buffer))
1483 (cond ((eq (widget-type node
) 'tree-widget
)
1484 (unless (widget-get node
:open
)
1485 (widget-put node
:open nil
)
1486 (widget-apply-action node
))
1487 (newsticker--treeview-activate-node
1489 (newsticker--treeview-get-last-child node
)
1490 (newsticker--treeview-get-second-child node
))))
1492 (widget-apply-action node
)))))
1494 (defun newsticker-treeview-next-feed ()
1495 "Move to next feed."
1497 (newsticker--treeview-restore-buffers)
1498 (let ((cur (newsticker--treeview-get-current-node)))
1499 ;;(message "newsticker-treeview-next-feed from %s"
1500 ;; (widget-get cur :tag))
1502 (let ((new (or (newsticker--treeview-get-next-sibling cur
)
1503 (newsticker--treeview-get-next-uncle cur
)
1504 (newsticker--treeview-get-other-tree))))
1505 (newsticker--treeview-activate-node new
))
1506 (newsticker--treeview-activate-node
1507 (car (widget-get newsticker--treeview-feed-tree
:children
)))))
1508 (newsticker--treeview-tree-update-highlight))
1510 (defun newsticker-treeview-prev-feed ()
1511 "Move to previous feed."
1513 (newsticker--treeview-restore-buffers)
1514 (let ((cur (newsticker--treeview-get-current-node)))
1515 (message "newsticker-treeview-prev-feed from %s"
1516 (widget-get cur
:tag
))
1518 (let ((new (or (newsticker--treeview-get-prev-sibling cur
)
1519 (newsticker--treeview-get-prev-uncle cur
)
1520 (newsticker--treeview-get-other-tree))))
1521 (newsticker--treeview-activate-node new t
))
1522 (newsticker--treeview-activate-node
1523 (car (widget-get newsticker--treeview-feed-tree
:children
)) t
)))
1524 (newsticker--treeview-tree-update-highlight))
1526 (defun newsticker-treeview-next-page ()
1527 "Scroll item buffer."
1529 (save-selected-window
1530 (select-window (newsticker--treeview-item-window) t
)
1534 (goto-char (point-min))))))
1537 (defun newsticker--treeview-unfold-node (feed-name)
1538 "Recursively show subtree above the node that represents FEED-NAME."
1539 (let ((node (newsticker--treeview-get-node-of-feed feed-name
)))
1541 (let* ((group-name (or (car (newsticker--group-find-group-for-feed
1543 (newsticker--group-get-parent-group
1545 (newsticker--treeview-unfold-node group-name
))
1546 (setq node
(newsticker--treeview-get-node-of-feed feed-name
)))
1549 (set-buffer (newsticker--treeview-tree-buffer))
1550 (widget-put node
:nt-selected t
)
1551 (widget-apply-action node
)
1552 (newsticker--treeview-set-current-node node
)))))
1554 (defun newsticker-treeview-jump (feed-name)
1555 "Jump to feed FEED-NAME in newsticker treeview."
1557 (list (let ((completion-ignore-case t
))
1560 (mapcar 'car
(append newsticker-url-list
1561 newsticker-url-list-defaults
))
1563 (newsticker--treeview-unfold-node feed-name
))
1565 ;; ======================================================================
1567 ;; ======================================================================
1568 (defun newsticker--group-do-find-group-for-feed (feed-name node
)
1569 "Recursively find FEED-NAME in NODE."
1570 (if (member feed-name
(cdr node
))
1574 (newsticker--group-do-find-group-for-feed feed-name n
)))
1577 (defun newsticker--group-find-group-for-feed (feed-name)
1578 "Find group containing FEED-NAME."
1580 (newsticker--group-do-find-group-for-feed feed-name
1584 (defun newsticker--group-do-get-group (name node
)
1585 "Recursively find group with NAME below NODE."
1586 (if (string= name
(car node
))
1590 (newsticker--group-do-get-group name n
)))
1593 (defun newsticker--group-get-group (name)
1594 "Find group with NAME."
1598 (newsticker--group-do-get-group name n
)))
1602 (defun newsticker--group-do-get-parent-group (name node parent
)
1603 "Recursively find parent group for NAME from NODE which is a child of PARENT."
1604 (if (string= name
(car node
))
1605 (throw 'found parent
)
1608 (newsticker--group-do-get-parent-group name n
(car node
))))
1611 (defun newsticker--group-get-parent-group (name)
1612 "Find parent group for group named NAME."
1616 (newsticker--group-do-get-parent-group
1617 name n
(car newsticker-groups
))))
1622 (defun newsticker--group-get-subgroups (group &optional recursive
)
1623 "Return list of subgroups for GROUP.
1624 If RECURSIVE is non-nil recursively get subgroups and return a nested list."
1628 (setq result
(cons (car n
) result
))
1629 (let ((subgroups (newsticker--group-get-subgroups n recursive
)))
1631 (setq result
(append subgroups result
))))))
1635 (defun newsticker--group-all-groups ()
1636 "Return nested list of all groups."
1637 (newsticker--group-get-subgroups newsticker-groups t
))
1639 (defun newsticker--group-get-feeds (group &optional recursive
)
1640 "Return list of all feeds in GROUP.
1641 If RECURSIVE is non-nil recursively get feeds of subgroups and
1642 return a nested list."
1646 (setq result
(cons n result
))
1648 (let ((subfeeds (newsticker--group-get-feeds n t
)))
1650 (setq result
(append subfeeds result
)))))))
1654 (defun newsticker-group-add-group (name parent
)
1655 "Add group NAME to group PARENT."
1657 (list (read-string "Group Name: ")
1658 (let ((completion-ignore-case t
))
1659 (completing-read "Parent Group: " (newsticker--group-all-groups)
1661 (if (newsticker--group-get-group name
)
1662 (error "Group %s exists already" name
))
1663 (let ((p (if (and parent
(not (string= parent
"")))
1664 (newsticker--group-get-group parent
)
1665 newsticker-groups
)))
1667 (error "Parent %s does not exist" parent
))
1668 (setcdr p
(cons (list name
) (cdr p
))))
1669 (newsticker--treeview-tree-update))
1671 (defun newsticker-group-move-feed (name group-name
&optional no-update
)
1672 "Move feed NAME to group GROUP-NAME.
1673 Update teeview afterwards unless NO-UPDATE is non-nil."
1675 (let ((completion-ignore-case t
))
1676 (list (completing-read "Feed Name: "
1677 (mapcar 'car newsticker-url-list
)
1678 nil t newsticker--treeview-current-feed
)
1679 (completing-read "Group Name: " (newsticker--group-all-groups)
1681 (let ((group (if (and group-name
(not (string= group-name
"")))
1682 (newsticker--group-get-group group-name
)
1683 newsticker-groups
)))
1685 (error "Group %s does not exist" group-name
))
1686 (while (let ((old-group
1687 (newsticker--group-find-group-for-feed name
)))
1689 (delete name old-group
))
1691 (setcdr group
(cons name
(cdr group
)))
1693 (newsticker--treeview-tree-update)
1694 (newsticker-treeview-update))))
1696 (defun newsticker-group-delete-group (name)
1697 "Remove group NAME."
1699 (let ((completion-ignore-case t
))
1700 (list (completing-read "Group Name: " (newsticker--group-all-groups)
1702 (let* ((g (newsticker--group-get-group name
))
1703 (p (or (newsticker--group-get-parent-group name
)
1704 newsticker-groups
)))
1706 (error "Group %s does not exist" name
))
1708 (newsticker--treeview-tree-update))
1710 (defun newsticker--count-groups (group)
1711 "Recursively count number of subgroups of GROUP."
1715 (setq result
(+ result
(newsticker--count-groups g
)))))
1719 (defun newsticker--count-grouped-feeds (group)
1720 "Recursively count number of feeds in GROUP and its subgroups."
1724 (setq result
(+ result
(newsticker--count-grouped-feeds g
)))
1725 (setq result
(1+ result
))))
1729 (defun newsticker--group-remove-obsolete-feeds (group)
1730 "Recursively remove obselete feeds from GROUP."
1732 (urls (append newsticker-url-list newsticker-url-list-defaults
)))
1736 (newsticker--group-remove-obsolete-feeds g
)))
1738 (setq result
(cons sub-groups result
))))
1740 (setq result
(cons g result
)))))
1743 (cons (car group
) (reverse result
))
1746 (defun newsticker--group-manage-orphan-feeds ()
1747 "Put unmanaged feeds into `newsticker-groups'.
1748 Remove obsolete feeds as well."
1749 (unless newsticker-groups
1750 (setq newsticker-groups
'("Feeds")))
1751 (let ((new-feed nil
)
1752 (grouped-feeds (newsticker--count-grouped-feeds newsticker-groups
)))
1754 (unless (newsticker--group-find-group-for-feed (car f
))
1756 (newsticker-group-move-feed (car f
) nil t
)))
1757 (append newsticker-url-list-defaults newsticker-url-list
))
1758 (setq newsticker-groups
1759 (newsticker--group-remove-obsolete-feeds newsticker-groups
))
1761 (not (= grouped-feeds
1762 (newsticker--count-grouped-feeds newsticker-groups
))))
1763 (newsticker--treeview-tree-update))))
1765 ;; ======================================================================
1767 ;; ======================================================================
1768 (defun newsticker--treeview-create-groups-menu (group-list
1770 "Create menu for GROUP-LIST omitting EXCLUDED-GROUP."
1771 (let ((menu (make-sparse-keymap (if (stringp (car group-list
))
1773 "Move to group..."))))
1776 (let ((title (if (stringp (car g
))
1778 "Move to group...")))
1779 (unless (eq g excluded-group
)
1780 (define-key menu
(vector (intern title
))
1781 (list 'menu-item title
1782 (newsticker--treeview-create-groups-menu
1783 (cdr g
) excluded-group
)))))))
1784 (reverse group-list
))
1787 (defun newsticker--treeview-create-tree-menu (feed-name)
1788 "Create tree menu for FEED-NAME."
1789 (let ((menu (make-sparse-keymap feed-name
)))
1790 (define-key menu
[newsticker-treeview-mark-list-items-old
]
1791 (list 'menu-item
"Mark all items old"
1792 'newsticker-treeview-mark-list-items-old
))
1793 (define-key menu
[move]
1794 (list 'menu-item "Move to group..."
1795 (newsticker--treeview-create-groups-menu
1797 (newsticker--group-get-group feed-name))))
1800 (defvar newsticker-treeview-list-menu
1801 (let ((menu (make-sparse-keymap "Newsticker List")))
1802 (define-key menu [newsticker-treeview-mark-list-items-old]
1803 (list 'menu-item "Mark all items old"
1804 'newsticker-treeview-mark-list-items-old))
1806 "Map for newsticker tree menu.")
1808 (defvar newsticker-treeview-mode-map
1809 (let ((map (make-sparse-keymap 'newsticker-treeview-mode-map)))
1810 (define-key map " " 'newsticker-treeview-next-page)
1811 (define-key map "a" 'newsticker-add-url)
1812 (define-key map "F" 'newsticker-treeview-prev-feed)
1813 (define-key map "f" 'newsticker-treeview-next-feed)
1814 (define-key map "g" 'newsticker-treeview-get-news)
1815 (define-key map "G" 'newsticker-get-all-news)
1816 (define-key map "i" 'newsticker-treeview-toggle-item-immortal)
1817 (define-key map "j" 'newsticker-treeview-jump)
1818 (define-key map "n" 'newsticker-treeview-next-item)
1819 (define-key map "N" 'newsticker-treeview-next-new-or-immortal-item)
1820 (define-key map "O" 'newsticker-treeview-mark-list-items-old)
1821 (define-key map "o" 'newsticker-treeview-mark-item-old)
1822 (define-key map "p" 'newsticker-treeview-prev-item)
1823 (define-key map "P" 'newsticker-treeview-prev-new-or-immortal-item)
1824 (define-key map "q" 'newsticker-treeview-quit)
1825 (define-key map "S" 'newsticker-treeview-save-item)
1826 (define-key map "s" 'newsticker-treeview-save)
1827 (define-key map "u" 'newsticker-treeview-update)
1828 (define-key map "v" 'newsticker-treeview-browse-url)
1829 ;;(define-key map "\n" 'newsticker-treeview-scroll-item)
1830 ;;(define-key map "\C-m" 'newsticker-treeview-scroll-item)
1831 (define-key map "\M-m" 'newsticker-group-move-feed)
1832 (define-key map "\M-a" 'newsticker-group-add-group)
1834 "Mode map for newsticker treeview.")
1836 (defun newsticker-treeview-mode ()
1837 "Major mode for Newsticker Treeview.
1838 \\{newsticker-treeview-mode-map}"
1839 (kill-all-local-variables)
1840 (use-local-map newsticker-treeview-mode-map)
1841 (setq major-mode 'newsticker-treeview-mode)
1842 (setq mode-name "Newsticker TV")
1843 (if (boundp 'tool-bar-map)
1844 (set (make-local-variable 'tool-bar-map)
1845 newsticker-treeview-tool-bar-map))
1846 (setq buffer-read-only t
1849 (define-derived-mode newsticker-treeview-list-mode newsticker-treeview-mode
1851 (let ((header (concat
1852 (propertize " " 'display '(space :align-to 0))
1853 (newsticker-treeview-list-make-sort-button "*" 'sort-by-age)
1854 (propertize " " 'display '(space :align-to 2))
1855 (if newsticker--treeview-list-show-feed
1857 (propertize " " 'display '(space :align-to 12)))
1859 (newsticker-treeview-list-make-sort-button "Date"
1861 (if newsticker--treeview-list-show-feed
1862 (propertize " " 'display '(space :align-to 28))
1863 (propertize " " 'display '(space :align-to 18)))
1864 (newsticker-treeview-list-make-sort-button "Title"
1866 (setq header-line-format header))
1867 (define-key newsticker-treeview-list-mode-map [down-mouse-3]
1868 newsticker-treeview-list-menu))
1870 (defun newsticker-treeview-tree-click (event)
1871 "Handle click EVENT on a tag in the newsticker tree."
1874 (switch-to-buffer (window-buffer (posn-window (event-end event))))
1875 (newsticker-treeview-tree-do-click (posn-point (event-end event)))))
1877 (defun newsticker-treeview-tree-do-click (&optional pos event)
1878 "Actually handle click event.
1879 POS gives the position where EVENT occurred."
1881 (unless pos (setq pos (point)))
1882 (let ((pos (or pos (point)))
1883 (nt-id (get-text-property pos :nt-id))
1884 (item (get-text-property pos :nt-item)))
1886 ;; click in list buffer
1887 (newsticker-treeview-show-item))
1889 ;; click in tree buffer
1890 (let ((w (newsticker--treeview-get-node nt-id)))
1892 (newsticker--treeview-tree-update-tag w t t)
1893 (setq w (newsticker--treeview-get-node nt-id))
1894 (widget-put w :nt-selected t)
1895 (widget-apply w :action event)
1896 (newsticker--treeview-set-current-node w))))))
1897 (newsticker--treeview-tree-update-highlight))
1899 (defun newsticker--treeview-restore-buffers ()
1900 "Restore treeview buffers."
1903 (let ((win (nth i newsticker--treeview-windows))
1904 (buf (nth i newsticker--treeview-buffers)))
1905 (unless (window-live-p win)
1906 (newsticker--treeview-window-init)
1907 (newsticker--treeview-buffer-init)
1909 (unless (eq (window-buffer win) buf)
1910 (set-window-buffer win buf t))))))
1912 (defun newsticker--treeview-frame-init ()
1913 "Initialize treeview frame."
1914 (when newsticker-treeview-own-frame
1915 (unless (and newsticker--frame (frame-live-p newsticker--frame))
1916 (setq newsticker--frame (make-frame '((name . "Newsticker")))))
1917 (select-frame-set-input-focus newsticker--frame)
1918 (raise-frame newsticker--frame)))
1920 (defun newsticker--treeview-window-init ()
1921 "Initialize treeview windows."
1922 (setq newsticker--saved-window-config (current-window-configuration))
1923 (setq newsticker--treeview-windows nil)
1924 (setq newsticker--treeview-buffers nil)
1925 (delete-other-windows)
1926 (split-window-horizontally 25)
1927 (add-to-list 'newsticker--treeview-windows (selected-window) t)
1929 (split-window-vertically 10)
1930 (add-to-list 'newsticker--treeview-windows (selected-window) t)
1932 (add-to-list 'newsticker--treeview-windows (selected-window) t)
1936 (defun newsticker-treeview ()
1937 "Start newsticker treeview."
1939 (newsticker--treeview-load)
1940 (setq newsticker--sentinel-callback 'newsticker-treeview-update)
1941 (newsticker--treeview-frame-init)
1942 (newsticker--treeview-window-init)
1943 (newsticker--treeview-buffer-init)
1944 (newsticker--group-manage-orphan-feeds)
1945 (newsticker--treeview-set-current-node newsticker--treeview-feed-tree)
1946 (newsticker-start t) ;; will start only if not running
1947 (newsticker-treeview-update)
1948 (newsticker--treeview-item-show-text
1950 "Welcome to newsticker!"))
1952 (defun newsticker-treeview-get-news ()
1953 "Get news for current feed."
1955 (when newsticker--treeview-current-feed
1956 (newsticker-get-news newsticker--treeview-current-feed)))
1958 (provide 'newsticker-treeview)
1960 ;; arch-tag: 5dbaff48-1f3e-4fc6-8ebd-e966fc90d2d4
1961 ;;; newst-treeview.el ends here