diff options
| -rw-r--r-- | lisp/net/newsticker.el | 5137 |
1 files changed, 20 insertions, 5117 deletions
diff --git a/lisp/net/newsticker.el b/lisp/net/newsticker.el index dcad4497aca..08f7faf6cfb 100644 --- a/lisp/net/newsticker.el +++ b/lisp/net/newsticker.el | |||
| @@ -9,7 +9,8 @@ | |||
| 9 | ;; URL: http://www.nongnu.org/newsticker | 9 | ;; URL: http://www.nongnu.org/newsticker |
| 10 | ;; Created: 17. June 2003 | 10 | ;; Created: 17. June 2003 |
| 11 | ;; Keywords: News, RSS, Atom | 11 | ;; Keywords: News, RSS, Atom |
| 12 | ;; Time-stamp: "29. Januar 2007, 21:05:09 (ulf)" | 12 | ;; Time-stamp: "7. Juni 2008, 14:04:59 (ulf)" |
| 13 | ;; CVS-Version: $Id: newsticker.el,v 1.175 2008/05/03 18:36:02 u11 Exp $ | ||
| 13 | 14 | ||
| 14 | ;; ====================================================================== | 15 | ;; ====================================================================== |
| 15 | 16 | ||
| @@ -26,7 +27,7 @@ | |||
| 26 | ;; You should have received a copy of the GNU General Public License | 27 | ;; 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/>. | 28 | ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. |
| 28 | 29 | ||
| 29 | (defconst newsticker-version "1.10" "Version number of newsticker.el.") | 30 | (defconst newsticker-version "1.99" "Version number of newsticker.el.") |
| 30 | 31 | ||
| 31 | ;; ====================================================================== | 32 | ;; ====================================================================== |
| 32 | ;;; Commentary: | 33 | ;;; Commentary: |
| @@ -168,9 +169,20 @@ | |||
| 168 | ;; ====================================================================== | 169 | ;; ====================================================================== |
| 169 | ;;; History: | 170 | ;;; History: |
| 170 | 171 | ||
| 172 | ;; 1.99 | ||
| 173 | ;; * Lots! of changes. | ||
| 174 | |||
| 175 | ;; 1.10x | ||
| 176 | ;; * Support for download via url. Setting the new variable | ||
| 177 | ;; `newsticker-download-method' to 'intern will make newsticker | ||
| 178 | ;; use the url-package instead of the external program | ||
| 179 | ;; wget. Default value is 'extern. | ||
| 180 | ;; * Re-enabled `newsticker-default-face'. | ||
| 181 | ;; * Workaround for broken extra-data. | ||
| 182 | |||
| 171 | ;; 1.10 (2007-01-29) | 183 | ;; 1.10 (2007-01-29) |
| 172 | ;; * Bugfixes mostly: `newsticker--decode-iso8601-date', | 184 | ;; * Bugfixes mostly: `newsticker--decode-iso8601-date', |
| 173 | ;; `newsticker--sentinel', and others. | 185 | ;; `newsticker--sentinel', and others. |
| 174 | ;; * Renamed `newsticker--retrieval-timer-list' to | 186 | ;; * Renamed `newsticker--retrieval-timer-list' to |
| 175 | ;; `newsticker-retrieval-timer-list'. Removed | 187 | ;; `newsticker-retrieval-timer-list'. Removed |
| 176 | ;; `newsticker-running-p' -- check newsticker-retrieval-timer-list | 188 | ;; `newsticker-running-p' -- check newsticker-retrieval-timer-list |
| @@ -386,5122 +398,13 @@ | |||
| 386 | ;; * Tested with Emacs 21.3.2 and wget 1.8.2. | 398 | ;; * Tested with Emacs 21.3.2 and wget 1.8.2. |
| 387 | 399 | ||
| 388 | ;; ====================================================================== | 400 | ;; ====================================================================== |
| 389 | ;;; To Do: | ||
| 390 | |||
| 391 | ;; * Image handling for XEmacs (create-image does not exist) | ||
| 392 | |||
| 393 | ;; ====================================================================== | ||
| 394 | ;;; Code: | 401 | ;;; Code: |
| 395 | 402 | ||
| 396 | (require 'derived) | 403 | (require 'newsticker-backend) |
| 397 | (require 'xml) | 404 | (require 'newsticker-ticker) |
| 398 | 405 | (require 'newsticker-reader) | |
| 399 | ;; Silence warnings | 406 | (require 'newsticker-plainview) |
| 400 | (defvar tool-bar-map) | 407 | (require 'newsticker-treeview) |
| 401 | (defvar w3-mode-map) | ||
| 402 | (defvar w3m-minor-mode-map) | ||
| 403 | |||
| 404 | ;; ====================================================================== | ||
| 405 | ;;; Newsticker status | ||
| 406 | ;; ====================================================================== | ||
| 407 | |||
| 408 | (defvar newsticker--retrieval-timer-list nil | ||
| 409 | "List of timers for news retrieval. | ||
| 410 | This is an alist, each element consisting of (feed-name . timer).") | ||
| 411 | |||
| 412 | (defvar newsticker--display-timer nil | ||
| 413 | "Timer for newsticker display.") | ||
| 414 | |||
| 415 | ;;;###autoload | ||
| 416 | (defun newsticker-running-p () | ||
| 417 | "Check whether newsticker is running. | ||
| 418 | Return t if newsticker is running, nil otherwise. Newsticker is | ||
| 419 | considered to be running if the newsticker timer list is not empty." | ||
| 420 | (> (length newsticker--retrieval-timer-list) 0)) | ||
| 421 | |||
| 422 | ;;;###autoload | ||
| 423 | (defun newsticker-ticker-running-p () | ||
| 424 | "Check whether newsticker's actual ticker is running. | ||
| 425 | Return t if ticker is running, nil otherwise. Newsticker is | ||
| 426 | considered to be running if the newsticker timer list is not | ||
| 427 | empty." | ||
| 428 | (timerp newsticker--display-timer)) | ||
| 429 | |||
| 430 | ;; ====================================================================== | ||
| 431 | ;;; Customizables | ||
| 432 | ;; ====================================================================== | ||
| 433 | (defgroup newsticker nil | ||
| 434 | "Aggregator for RSS and Atom feeds." | ||
| 435 | :group 'applications) | ||
| 436 | |||
| 437 | (defconst newsticker--raw-url-list-defaults | ||
| 438 | '(("CNET News.com" | ||
| 439 | "http://export.cnet.com/export/feeds/news/rss/1,11176,,00.xml") | ||
| 440 | ("Debian Security Advisories" | ||
| 441 | "http://www.debian.org/security/dsa.en.rdf") | ||
| 442 | ("Debian Security Advisories - Long format" | ||
| 443 | "http://www.debian.org/security/dsa-long.en.rdf") | ||
| 444 | ("Emacs Wiki" | ||
| 445 | "http://www.emacswiki.org/cgi-bin/wiki.pl?action=rss" | ||
| 446 | nil | ||
| 447 | 3600) | ||
| 448 | ("Freshmeat.net" | ||
| 449 | "http://freshmeat.net/backend/fm.rdf") | ||
| 450 | ("Kuro5hin.org" | ||
| 451 | "http://www.kuro5hin.org/backend.rdf") | ||
| 452 | ("LWN (Linux Weekly News)" | ||
| 453 | "http://lwn.net/headlines/rss") | ||
| 454 | ("NewsForge" | ||
| 455 | "http://newsforge.com/index.rss") | ||
| 456 | ("NY Times: Technology" | ||
| 457 | "http://partners.userland.com/nytRss/technology.xml") | ||
| 458 | ("NY Times" | ||
| 459 | "http://partners.userland.com/nytRss/nytHomepage.xml") | ||
| 460 | ("Quote of the day" | ||
| 461 | "http://www.quotationspage.com/data/qotd.rss" | ||
| 462 | "07:00" | ||
| 463 | 86400) | ||
| 464 | ("The Register" | ||
| 465 | "http://www.theregister.co.uk/tonys/slashdot.rdf") | ||
| 466 | ("slashdot" | ||
| 467 | "http://slashdot.org/index.rss" | ||
| 468 | nil | ||
| 469 | 3600) ;/. will ban you if under 3600 seconds! | ||
| 470 | ("Wired News" | ||
| 471 | "http://www.wired.com/news_drop/netcenter/netcenter.rdf") | ||
| 472 | ("Heise News (german)" | ||
| 473 | "http://www.heise.de/newsticker/heise.rdf") | ||
| 474 | ("Tagesschau (german)" | ||
| 475 | "http://www.tagesschau.de/newsticker.rdf" | ||
| 476 | nil | ||
| 477 | 1800) | ||
| 478 | ("Telepolis (german)" | ||
| 479 | "http://www.heise.de/tp/news.rdf")) | ||
| 480 | "Default URL list in raw form. | ||
| 481 | This list is fed into defcustom via `newsticker--splicer'.") | ||
| 482 | |||
| 483 | (defun newsticker--splicer (item) | ||
| 484 | "Convert ITEM for splicing into `newsticker-url-list-defaults'." | ||
| 485 | (let ((result (list 'list :tag (nth 0 item) (list 'const (nth 0 item)))) | ||
| 486 | (element (cdr item))) | ||
| 487 | (while element | ||
| 488 | (setq result (append result (list (list 'const (car element))))) | ||
| 489 | (setq element (cdr element))) | ||
| 490 | result)) | ||
| 491 | |||
| 492 | ;; ====================================================================== | ||
| 493 | ;;; Customization | ||
| 494 | ;; ====================================================================== | ||
| 495 | (defun newsticker--set-customvar (symbol value) | ||
| 496 | "Set newsticker-variable SYMBOL value to VALUE. | ||
| 497 | |||
| 498 | Calls all necessary actions which are necessary in order to make | ||
| 499 | the new value effective. Changing `newsticker-url-list', for example, | ||
| 500 | will re-start the retrieval-timers." | ||
| 501 | (unless (condition-case nil | ||
| 502 | (eq (symbol-value symbol) value) | ||
| 503 | (error nil)) | ||
| 504 | (set symbol value) | ||
| 505 | (cond ((eq symbol 'newsticker-sort-method) | ||
| 506 | (when (fboundp 'newsticker--cache-sort) | ||
| 507 | (message "Applying new sort method...") | ||
| 508 | (newsticker--cache-sort) | ||
| 509 | (newsticker--buffer-set-uptodate nil) | ||
| 510 | (message "Applying new sort method...done"))) | ||
| 511 | ((memq symbol '(newsticker-url-list-defaults | ||
| 512 | newsticker-url-list | ||
| 513 | newsticker-retrieval-interval)) | ||
| 514 | (when (and (fboundp 'newsticker-running-p) | ||
| 515 | (newsticker-running-p)) | ||
| 516 | (message "Restarting newsticker") | ||
| 517 | (newsticker-stop) | ||
| 518 | (newsticker-start))) | ||
| 519 | ((eq symbol 'newsticker-display-interval) | ||
| 520 | (when (and (fboundp 'newsticker-running-p) | ||
| 521 | (newsticker-running-p)) | ||
| 522 | (message "Restarting ticker") | ||
| 523 | (newsticker-stop-ticker) | ||
| 524 | (newsticker-start-ticker) | ||
| 525 | (message ""))) | ||
| 526 | ((memq symbol '(newsticker-hide-old-items-in-echo-area | ||
| 527 | newsticker-hide-obsolete-items-in-echo-area | ||
| 528 | newsticker-hide-immortal-items-in-echo-area)) | ||
| 529 | (when (and (fboundp 'newsticker-running-p) | ||
| 530 | (newsticker-running-p)) | ||
| 531 | (message "Restarting newsticker") | ||
| 532 | (newsticker-stop-ticker) | ||
| 533 | (newsticker--ticker-text-setup) | ||
| 534 | (newsticker-start-ticker) | ||
| 535 | (message ""))) | ||
| 536 | ((memq symbol '(newsticker-hide-old-items-in-newsticker-buffer | ||
| 537 | newsticker-show-descriptions-of-new-items)) | ||
| 538 | (when (fboundp 'newsticker--buffer-set-uptodate) | ||
| 539 | (newsticker--buffer-set-uptodate nil))) | ||
| 540 | ((memq symbol '(newsticker-heading-format | ||
| 541 | newsticker-item-format | ||
| 542 | newsticker-desc-format | ||
| 543 | newsticker-date-format | ||
| 544 | newsticker-statistics-format | ||
| 545 | newsticker-justification | ||
| 546 | newsticker-use-full-width | ||
| 547 | newsticker-html-renderer | ||
| 548 | newsticker-feed-face | ||
| 549 | newsticker-new-item-face | ||
| 550 | newsticker-old-item-face | ||
| 551 | newsticker-immortal-item-face | ||
| 552 | newsticker-obsolete-item-face | ||
| 553 | newsticker-date-face | ||
| 554 | newsticker-statistics-face | ||
| 555 | ;;newsticker-default-face | ||
| 556 | )) | ||
| 557 | (when (fboundp 'newsticker--forget-preformatted) | ||
| 558 | (newsticker--forget-preformatted))) | ||
| 559 | (t | ||
| 560 | (error "Ooops %s" symbol))))) | ||
| 561 | |||
| 562 | ;; customization group feed | ||
| 563 | (defgroup newsticker-feed nil | ||
| 564 | "Settings for news feeds." | ||
| 565 | :group 'newsticker) | ||
| 566 | |||
| 567 | (defcustom newsticker-url-list-defaults | ||
| 568 | '(("Emacs Wiki" | ||
| 569 | "http://www.emacswiki.org/cgi-bin/wiki.pl?action=rss" | ||
| 570 | nil | ||
| 571 | 3600)) | ||
| 572 | "A customizable list of news feeds to select from. | ||
| 573 | These were mostly extracted from the Radio Community Server at | ||
| 574 | http://subhonker6.userland.com/rcsPublic/rssHotlist. | ||
| 575 | |||
| 576 | You may add other entries in `newsticker-url-list'." | ||
| 577 | :type `(set ,@(mapcar `newsticker--splicer | ||
| 578 | newsticker--raw-url-list-defaults)) | ||
| 579 | :set 'newsticker--set-customvar | ||
| 580 | :group 'newsticker-feed) | ||
| 581 | |||
| 582 | (defcustom newsticker-url-list nil | ||
| 583 | "The news feeds which you like to watch. | ||
| 584 | |||
| 585 | This alist will be used in addition to selection made customizing | ||
| 586 | `newsticker-url-list-defaults'. | ||
| 587 | |||
| 588 | This is an alist. Each element consists of two items: a LABEL and a URL, | ||
| 589 | optionally followed by a START-TIME, INTERVAL specifier and WGET-ARGUMENTS. | ||
| 590 | |||
| 591 | The LABEL gives the name of the news feed. It can be an arbitrary string. | ||
| 592 | |||
| 593 | The URL gives the location of the news feed. It must point to a valid | ||
| 594 | RSS or Atom file. The file is retrieved by calling wget, or whatever you | ||
| 595 | specify as `newsticker-wget-name'. | ||
| 596 | |||
| 597 | The START-TIME can be either a string, or nil. If it is a string it | ||
| 598 | specifies a fixed time at which this feed shall be retrieved for the | ||
| 599 | first time. (Examples: \"11:00pm\", \"23:00\".) If it is nil (or | ||
| 600 | unspecified), this feed will be retrieved immediately after calling | ||
| 601 | `newsticker-start'. | ||
| 602 | |||
| 603 | The INTERVAL specifies the time between retrievals for this feed. If it | ||
| 604 | is nil (or unspecified) the default interval value as set in | ||
| 605 | `newsticker-retrieval-interval' is used. | ||
| 606 | |||
| 607 | \(newsticker.el calls `run-at-time'. The newsticker-parameters START-TIME | ||
| 608 | and INTERVAL correspond to the `run-at-time'-parameters TIME and REPEAT.) | ||
| 609 | |||
| 610 | WGET-ARGUMENTS specifies arguments for wget (see `newsticker-wget-name') | ||
| 611 | which apply for this feed only, overriding the value of | ||
| 612 | `newsticker-wget-arguments'." | ||
| 613 | :type '(repeat (list :tag "News feed" | ||
| 614 | (string :tag "Label") | ||
| 615 | (string :tag "URI") | ||
| 616 | (choice :tag "Start" | ||
| 617 | (const :tag "Default" nil) | ||
| 618 | (string :tag "Fixed Time")) | ||
| 619 | (choice :tag "Interval" | ||
| 620 | (const :tag "Default" nil) | ||
| 621 | (const :tag "Hourly" 3600) | ||
| 622 | (const :tag "Daily" 86400) | ||
| 623 | (const :tag "Weekly" 604800) | ||
| 624 | (integer :tag "Interval")) | ||
| 625 | (choice :tag "Wget Arguments" | ||
| 626 | (const :tag "Default arguments" nil) | ||
| 627 | (repeat :tag "Special arguments" string)))) | ||
| 628 | :set 'newsticker--set-customvar | ||
| 629 | :group 'newsticker-feed) | ||
| 630 | |||
| 631 | (defcustom newsticker-wget-name | ||
| 632 | "wget" | ||
| 633 | "Name of the program which is called to retrieve news from the web. | ||
| 634 | The canonical choice is wget but you may take any other program which is | ||
| 635 | able to return the contents of a news feed file on stdout." | ||
| 636 | :type 'string | ||
| 637 | :group 'newsticker-feed) | ||
| 638 | |||
| 639 | (defcustom newsticker-wget-arguments | ||
| 640 | '("-q" "-O" "-") | ||
| 641 | "Arguments which are passed to wget. | ||
| 642 | There is probably no reason to change the default settings, unless you | ||
| 643 | are living behind a firewall." | ||
| 644 | :type '(repeat (string :tag "Argument")) | ||
| 645 | :group 'newsticker-feed) | ||
| 646 | |||
| 647 | (defcustom newsticker-retrieval-interval | ||
| 648 | 3600 | ||
| 649 | "Time interval for retrieving new news items (seconds). | ||
| 650 | If this value is not positive (i.e. less than or equal to 0) | ||
| 651 | items are retrieved only once! | ||
| 652 | Please note that some feeds, e.g. Slashdot, will ban you if you | ||
| 653 | make it less than 1800 seconds (30 minutes)!" | ||
| 654 | :type '(choice :tag "Interval" | ||
| 655 | (const :tag "No automatic retrieval" 0) | ||
| 656 | (const :tag "Hourly" 3600) | ||
| 657 | (const :tag "Daily" 86400) | ||
| 658 | (const :tag "Weekly" 604800) | ||
| 659 | (integer :tag "Interval")) | ||
| 660 | :set 'newsticker--set-customvar | ||
| 661 | :group 'newsticker-feed) | ||
| 662 | |||
| 663 | (defcustom newsticker-desc-comp-max | ||
| 664 | 100 | ||
| 665 | "Relevant length of headline descriptions. | ||
| 666 | This value gives the maximum number of characters which will be | ||
| 667 | taken into account when newsticker compares two headline | ||
| 668 | descriptions." | ||
| 669 | :type 'integer | ||
| 670 | :group 'newsticker-feed) | ||
| 671 | |||
| 672 | ;; customization group behaviour | ||
| 673 | (defgroup newsticker-headline-processing nil | ||
| 674 | "Settings for the automatic processing of headlines." | ||
| 675 | :group 'newsticker) | ||
| 676 | |||
| 677 | (defcustom newsticker-automatically-mark-items-as-old | ||
| 678 | t | ||
| 679 | "Decides whether to automatically mark items as old. | ||
| 680 | If t a new item is considered as new only after its first retrieval. As | ||
| 681 | soon as it is retrieved a second time, it becomes old. If not t all | ||
| 682 | items stay new until you mark them as old. This is done in the | ||
| 683 | *newsticker* buffer." | ||
| 684 | :type 'boolean | ||
| 685 | :group 'newsticker-headline-processing) | ||
| 686 | |||
| 687 | (defcustom newsticker-automatically-mark-visited-items-as-old | ||
| 688 | t | ||
| 689 | "Decides whether to automatically mark visited items as old. | ||
| 690 | If t an item is marked as old as soon as the associated link is | ||
| 691 | visited, i.e. after pressing RET or mouse2 on the item's | ||
| 692 | headline." | ||
| 693 | |||
| 694 | :type 'boolean | ||
| 695 | :group 'newsticker-headline-processing) | ||
| 696 | |||
| 697 | (defcustom newsticker-keep-obsolete-items | ||
| 698 | t | ||
| 699 | "Decides whether to keep unread items which have been removed from feed. | ||
| 700 | If t a new item, which has been removed from the feed, is kept in | ||
| 701 | the cache until it is marked as read." | ||
| 702 | :type 'boolean | ||
| 703 | :group 'newsticker-headline-processing) | ||
| 704 | |||
| 705 | (defcustom newsticker-obsolete-item-max-age | ||
| 706 | (* 60 60 24) | ||
| 707 | "Maximal age of obsolete items, in seconds. | ||
| 708 | Obsolete items which are older than this value will be silently | ||
| 709 | deleted at the next retrieval." | ||
| 710 | :type 'integer | ||
| 711 | :group 'newsticker-headline-processing) | ||
| 712 | |||
| 713 | (defcustom newsticker-auto-mark-filter-list | ||
| 714 | nil | ||
| 715 | "A list of filters for automatically marking headlines. | ||
| 716 | |||
| 717 | This is an alist of the form (FEED-NAME PATTERN-LIST). I.e. each | ||
| 718 | element consists of a FEED-NAME a PATTERN-LIST. Each element of | ||
| 719 | the pattern-list has the form (AGE TITLE-OR-DESCRIPTION REGEXP). | ||
| 720 | AGE must be one of the symbols 'old or 'immortal. | ||
| 721 | TITLE-OR-DESCRIPTION must be on of the symbols 'title, | ||
| 722 | 'description, or 'all. REGEXP is a regular expression, i.e. a | ||
| 723 | string. | ||
| 724 | |||
| 725 | This filter is checked after a new headline has been retrieved. | ||
| 726 | If FEED-NAME matches the name of the corresponding news feed, the | ||
| 727 | pattern-list is checked: The new headline will be marked as AGE | ||
| 728 | if REGEXP matches the headline's TITLE-OR-DESCRIPTION. | ||
| 729 | |||
| 730 | If, for example, `newsticker-auto-mark-filter-list' looks like | ||
| 731 | \((slashdot ('old 'title \"^Forget me!$\") ('immortal 'title \"Read me\") | ||
| 732 | \('immortal 'all \"important\")))) | ||
| 733 | |||
| 734 | then all articles from slashdot are marked as old if they have | ||
| 735 | the title \"Forget me!\". All articles with a title containing | ||
| 736 | the string \"Read me\" are marked as immortal. All articles which | ||
| 737 | contain the string \"important\" in their title or their | ||
| 738 | description are marked as immortal." | ||
| 739 | :type '(repeat (list :tag "Auto mark filter" | ||
| 740 | (string :tag "Feed name") | ||
| 741 | (repeat | ||
| 742 | (list :tag "Filter element" | ||
| 743 | (choice | ||
| 744 | :tag "Auto-assigned age" | ||
| 745 | (const :tag "Old" old) | ||
| 746 | (const :tag "Immortal" immortal)) | ||
| 747 | (choice | ||
| 748 | :tag "Title/Description" | ||
| 749 | (const :tag "Title" title) | ||
| 750 | (const :tag "Description" description) | ||
| 751 | (const :tag "All" all)) | ||
| 752 | (string :tag "Regexp"))))) | ||
| 753 | :group 'newsticker-headline-processing) | ||
| 754 | |||
| 755 | ;; customization group layout | ||
| 756 | (defgroup newsticker-layout nil | ||
| 757 | "Settings for layout of the feed reader." | ||
| 758 | :group 'newsticker) | ||
| 759 | |||
| 760 | (defcustom newsticker-sort-method | ||
| 761 | 'sort-by-original-order | ||
| 762 | "Sort method for news items. | ||
| 763 | The following sort methods are available: | ||
| 764 | * `sort-by-original-order' keeps the order in which the items | ||
| 765 | appear in the headline file (please note that for immortal items, | ||
| 766 | which have been removed from the news feed, there is no original | ||
| 767 | order), | ||
| 768 | * `sort-by-time' looks at the time at which an item has been seen | ||
| 769 | the first time. The most recent item is put at top, | ||
| 770 | * `sort-by-title' will put the items in an alphabetical order." | ||
| 771 | :type '(choice | ||
| 772 | (const :tag "Keep original order" sort-by-original-order) | ||
| 773 | (const :tag "Sort by time" sort-by-time) | ||
| 774 | (const :tag "Sort by title" sort-by-title)) | ||
| 775 | :set 'newsticker--set-customvar | ||
| 776 | :group 'newsticker-layout) | ||
| 777 | |||
| 778 | (defcustom newsticker-hide-old-items-in-newsticker-buffer | ||
| 779 | nil | ||
| 780 | "Decides whether to automatically hide old items in the *newsticker* buffer. | ||
| 781 | If set to t old items will be completely folded and only new | ||
| 782 | items will show up in the *newsticker* buffer. Otherwise old as | ||
| 783 | well as new items will be visible." | ||
| 784 | :type 'boolean | ||
| 785 | :set 'newsticker--set-customvar | ||
| 786 | :group 'newsticker-layout) | ||
| 787 | |||
| 788 | (defcustom newsticker-show-descriptions-of-new-items | ||
| 789 | t | ||
| 790 | "Whether to automatically show descriptions of new items in *newsticker*. | ||
| 791 | If set to t old items will be folded and new items will be | ||
| 792 | unfolded. Otherwise old as well as new items will be folded." | ||
| 793 | :type 'boolean | ||
| 794 | :set 'newsticker--set-customvar | ||
| 795 | :group 'newsticker-layout) | ||
| 796 | |||
| 797 | (defcustom newsticker-heading-format | ||
| 798 | "%l | ||
| 799 | %t %d %s" | ||
| 800 | "Format string for feed headings. | ||
| 801 | The following printf-like specifiers can be used: | ||
| 802 | %d The date the feed was retrieved. See `newsticker-date-format'. | ||
| 803 | %l The logo (image) of the feed. Most news feeds provide a small | ||
| 804 | image as logo. Newsticker can display them, if Emacs can -- | ||
| 805 | see `image-types' for a list of supported image types. | ||
| 806 | %L The logo (image) of the feed. If the logo is not available | ||
| 807 | the title of the feed is used. | ||
| 808 | %s The statistical data of the feed. See `newsticker-statistics-format'. | ||
| 809 | %t The title of the feed, i.e. its name." | ||
| 810 | :type 'string | ||
| 811 | :set 'newsticker--set-customvar | ||
| 812 | :group 'newsticker-layout) | ||
| 813 | |||
| 814 | (defcustom newsticker-item-format | ||
| 815 | "%t %d" | ||
| 816 | "Format string for news item headlines. | ||
| 817 | The following printf-like specifiers can be used: | ||
| 818 | %d The date the item was (first) retrieved. See `newsticker-date-format'. | ||
| 819 | %l The logo (image) of the feed. Most news feeds provide a small | ||
| 820 | image as logo. Newsticker can display them, if Emacs can -- | ||
| 821 | see `image-types' for a list of supported image types. | ||
| 822 | %L The logo (image) of the feed. If the logo is not available | ||
| 823 | the title of the feed is used. | ||
| 824 | %t The title of the item." | ||
| 825 | :type 'string | ||
| 826 | :set 'newsticker--set-customvar | ||
| 827 | :group 'newsticker-layout) | ||
| 828 | |||
| 829 | (defcustom newsticker-desc-format | ||
| 830 | "%d %c" | ||
| 831 | "Format string for news descriptions (contents). | ||
| 832 | The following printf-like specifiers can be used: | ||
| 833 | %c The contents (description) of the item. | ||
| 834 | %d The date the item was (first) retrieved. See | ||
| 835 | `newsticker-date-format'." | ||
| 836 | :type 'string | ||
| 837 | :set 'newsticker--set-customvar | ||
| 838 | :group 'newsticker-layout) | ||
| 839 | |||
| 840 | (defcustom newsticker-date-format | ||
| 841 | "(%A, %H:%M)" | ||
| 842 | "Format for the date part in item and feed lines. | ||
| 843 | See `format-time-string' for a list of valid specifiers." | ||
| 844 | :type 'string | ||
| 845 | :set 'newsticker--set-customvar | ||
| 846 | :group 'newsticker-layout) | ||
| 847 | |||
| 848 | (defcustom newsticker-statistics-format | ||
| 849 | "[%n + %i + %o + %O = %a]" | ||
| 850 | "Format for the statistics part in feed lines. | ||
| 851 | The following printf-like specifiers can be used: | ||
| 852 | %a The number of all items in the feed. | ||
| 853 | %i The number of immortal items in the feed. | ||
| 854 | %n The number of new items in the feed. | ||
| 855 | %o The number of old items in the feed. | ||
| 856 | %O The number of obsolete items in the feed." | ||
| 857 | :type 'string | ||
| 858 | :set 'newsticker--set-customvar | ||
| 859 | :group 'newsticker-layout) | ||
| 860 | |||
| 861 | (defcustom newsticker-show-all-news-elements | ||
| 862 | nil | ||
| 863 | "Show all news elements." | ||
| 864 | :type 'boolean | ||
| 865 | ;;:set 'newsticker--set-customvar | ||
| 866 | :group 'newsticker-layout) | ||
| 867 | |||
| 868 | ;; image related things | ||
| 869 | (defcustom newsticker-enable-logo-manipulations | ||
| 870 | t | ||
| 871 | "If non-nil newsticker manipulates logo images. | ||
| 872 | This enables the following image properties: heuristic mask for all | ||
| 873 | logos, and laplace-conversion for images without new items." | ||
| 874 | :type 'boolean | ||
| 875 | :group 'newsticker-layout) | ||
| 876 | |||
| 877 | |||
| 878 | ;; rendering | ||
| 879 | (defcustom newsticker-justification | ||
| 880 | 'left | ||
| 881 | "How to fill item descriptions. | ||
| 882 | If non-nil newsticker calls `fill-region' to wrap long lines in | ||
| 883 | item descriptions. However, if an item description contains HTML | ||
| 884 | text and `newsticker-html-renderer' is non-nil, filling is not | ||
| 885 | done." | ||
| 886 | :type '(choice :tag "Justification" | ||
| 887 | (const :tag "No filling" nil) | ||
| 888 | (const :tag "Left" left) | ||
| 889 | (const :tag "Right" right) | ||
| 890 | (const :tag "Center" center) | ||
| 891 | (const :tag "Full" full)) | ||
| 892 | :set 'newsticker--set-customvar | ||
| 893 | :group 'newsticker-layout) | ||
| 894 | |||
| 895 | (defcustom newsticker-use-full-width | ||
| 896 | t | ||
| 897 | "Decides whether to use the full window width when filling. | ||
| 898 | If non-nil newsticker sets `fill-column' so that the whole | ||
| 899 | window is used when filling. See also `newsticker-justification'." | ||
| 900 | :type 'boolean | ||
| 901 | :set 'newsticker--set-customvar | ||
| 902 | :group 'newsticker-layout) | ||
| 903 | |||
| 904 | (defcustom newsticker-html-renderer | ||
| 905 | nil | ||
| 906 | "Function for rendering HTML contents. | ||
| 907 | If non-nil, newsticker.el will call this function whenever it finds | ||
| 908 | HTML-like tags in item descriptions. Possible functions are, for | ||
| 909 | example, `w3m-region', `w3-region', and (if you have htmlr.el installed) | ||
| 910 | `newsticker-htmlr-render'. | ||
| 911 | |||
| 912 | In order to make sure that the HTML renderer is loaded when you | ||
| 913 | run newsticker, you should add one of the following statements to | ||
| 914 | your .emacs. If you use w3m, | ||
| 915 | |||
| 916 | (autoload 'w3m-region \"w3m\" | ||
| 917 | \"Render region in current buffer and replace with result.\" t) | ||
| 918 | |||
| 919 | or, if you use w3, | ||
| 920 | |||
| 921 | (require 'w3-auto) | ||
| 922 | |||
| 923 | or, if you use htmlr | ||
| 924 | |||
| 925 | (require 'htmlr)" | ||
| 926 | :type '(choice :tag "Function" | ||
| 927 | (const :tag "None" nil) | ||
| 928 | (const :tag "w3" w3-region) | ||
| 929 | (const :tag "w3m" w3m-region) | ||
| 930 | (const :tag "htmlr" newsticker-htmlr-render)) | ||
| 931 | :set 'newsticker--set-customvar | ||
| 932 | :group 'newsticker-layout) | ||
| 933 | |||
| 934 | |||
| 935 | ;; faces | ||
| 936 | (defgroup newsticker-faces nil | ||
| 937 | "Settings for the faces of the feed reader." | ||
| 938 | :group 'newsticker-layout) | ||
| 939 | |||
| 940 | (defface newsticker-feed-face | ||
| 941 | '((((class color) (background dark)) | ||
| 942 | (:family "helvetica" :bold t :height 1.2 :foreground "misty rose")) | ||
| 943 | (((class color) (background light)) | ||
| 944 | (:family "helvetica" :bold t :height 1.2 :foreground "black"))) | ||
| 945 | "Face for news feeds." | ||
| 946 | :group 'newsticker-faces) | ||
| 947 | |||
| 948 | (defface newsticker-new-item-face | ||
| 949 | '((((class color) (background dark)) | ||
| 950 | (:family "helvetica" :bold t)) | ||
| 951 | (((class color) (background light)) | ||
| 952 | (:family "helvetica" :bold t))) | ||
| 953 | "Face for new news items." | ||
| 954 | :group 'newsticker-faces) | ||
| 955 | |||
| 956 | (defface newsticker-old-item-face | ||
| 957 | '((((class color) (background dark)) | ||
| 958 | (:family "helvetica" :bold t :foreground "orange3")) | ||
| 959 | (((class color) (background light)) | ||
| 960 | (:family "helvetica" :bold t :foreground "red4"))) | ||
| 961 | "Face for old news items." | ||
| 962 | :group 'newsticker-faces) | ||
| 963 | |||
| 964 | (defface newsticker-immortal-item-face | ||
| 965 | '((((class color) (background dark)) | ||
| 966 | (:family "helvetica" :bold t :italic t :foreground "orange")) | ||
| 967 | (((class color) (background light)) | ||
| 968 | (:family "helvetica" :bold t :italic t :foreground "blue"))) | ||
| 969 | "Face for immortal news items." | ||
| 970 | :group 'newsticker-faces) | ||
| 971 | |||
| 972 | (defface newsticker-obsolete-item-face | ||
| 973 | '((((class color) (background dark)) | ||
| 974 | (:family "helvetica" :bold t :strike-through t)) | ||
| 975 | (((class color) (background light)) | ||
| 976 | (:family "helvetica" :bold t :strike-through t))) | ||
| 977 | "Face for old news items." | ||
| 978 | :group 'newsticker-faces) | ||
| 979 | |||
| 980 | (defface newsticker-date-face | ||
| 981 | '((((class color) (background dark)) | ||
| 982 | (:family "helvetica" :italic t :height 0.8)) | ||
| 983 | (((class color) (background light)) | ||
| 984 | (:family "helvetica" :italic t :height 0.8))) | ||
| 985 | "Face for newsticker dates." | ||
| 986 | :group 'newsticker-faces) | ||
| 987 | |||
| 988 | (defface newsticker-statistics-face | ||
| 989 | '((((class color) (background dark)) | ||
| 990 | (:family "helvetica" :italic t :height 0.8)) | ||
| 991 | (((class color) (background light)) | ||
| 992 | (:family "helvetica" :italic t :height 0.8))) | ||
| 993 | "Face for newsticker dates." | ||
| 994 | :group 'newsticker-faces) | ||
| 995 | |||
| 996 | (defface newsticker-enclosure-face | ||
| 997 | '((((class color) (background dark)) | ||
| 998 | (:bold t :background "orange")) | ||
| 999 | (((class color) (background light)) | ||
| 1000 | (:bold t :background "orange"))) | ||
| 1001 | "Face for enclosed elements." | ||
| 1002 | :group 'newsticker-faces) | ||
| 1003 | |||
| 1004 | (defface newsticker-extra-face | ||
| 1005 | '((((class color) (background dark)) | ||
| 1006 | (:italic t :foreground "gray50" :height 0.8)) | ||
| 1007 | (((class color) (background light)) | ||
| 1008 | (:italic t :foreground "gray50" :height 0.8))) | ||
| 1009 | "Face for newsticker dates." | ||
| 1010 | :group 'newsticker-faces) | ||
| 1011 | |||
| 1012 | ;; (defface newsticker-default-face | ||
| 1013 | ;; '((((class color) (background dark)) | ||
| 1014 | ;; (:inherit default)) | ||
| 1015 | ;; (((class color) (background light)) | ||
| 1016 | ;; (:inherit default))) | ||
| 1017 | ;; "Face for the description of news items." | ||
| 1018 | ;; ;;:set 'newsticker--set-customvar | ||
| 1019 | ;; :group 'newsticker-faces) | ||
| 1020 | |||
| 1021 | |||
| 1022 | ;; customization group ticker | ||
| 1023 | (defgroup newsticker-ticker nil | ||
| 1024 | "Settings for the headline ticker." | ||
| 1025 | :group 'newsticker) | ||
| 1026 | |||
| 1027 | (defcustom newsticker-display-interval | ||
| 1028 | 0.3 | ||
| 1029 | "Time interval for displaying news items in the echo area (seconds). | ||
| 1030 | If equal or less than 0 no messages are shown in the echo area. For | ||
| 1031 | smooth display (see `newsticker-scroll-smoothly') a value of 0.3 seems | ||
| 1032 | reasonable. For non-smooth display a value of 10 is a good starting | ||
| 1033 | point." | ||
| 1034 | :type 'number | ||
| 1035 | :set 'newsticker--set-customvar | ||
| 1036 | :group 'newsticker-ticker) | ||
| 1037 | |||
| 1038 | (defcustom newsticker-scroll-smoothly | ||
| 1039 | t | ||
| 1040 | "Decides whether to flash or scroll news items. | ||
| 1041 | If t the news headlines are scrolled (more-or-less) smoothly in the echo | ||
| 1042 | area. If nil one headline after another is displayed in the echo area. | ||
| 1043 | The variable `newsticker-display-interval' determines how fast this | ||
| 1044 | display moves/changes and whether headlines are shown in the echo area | ||
| 1045 | at all. If you change `newsticker-scroll-smoothly' you should also change | ||
| 1046 | `newsticker-display-interval'." | ||
| 1047 | :type 'boolean | ||
| 1048 | :group 'newsticker-ticker) | ||
| 1049 | |||
| 1050 | (defcustom newsticker-hide-immortal-items-in-echo-area | ||
| 1051 | t | ||
| 1052 | "Decides whether to show immortal/non-expiring news items in the ticker. | ||
| 1053 | If t the echo area will not show immortal items. See also | ||
| 1054 | `newsticker-hide-old-items-in-echo-area'." | ||
| 1055 | :type 'boolean | ||
| 1056 | :set 'newsticker--set-customvar | ||
| 1057 | :group 'newsticker-ticker) | ||
| 1058 | |||
| 1059 | (defcustom newsticker-hide-old-items-in-echo-area | ||
| 1060 | t | ||
| 1061 | "Decides whether to show only the newest news items in the ticker. | ||
| 1062 | If t the echo area will show only new items, i.e. only items which have | ||
| 1063 | been added between the last two retrievals." | ||
| 1064 | :type 'boolean | ||
| 1065 | :set 'newsticker--set-customvar | ||
| 1066 | :group 'newsticker-ticker) | ||
| 1067 | |||
| 1068 | (defcustom newsticker-hide-obsolete-items-in-echo-area | ||
| 1069 | t | ||
| 1070 | "Decides whether to show obsolete items items in the ticker. | ||
| 1071 | If t the echo area will not show obsolete items. See also | ||
| 1072 | `newsticker-hide-old-items-in-echo-area'." | ||
| 1073 | :type 'boolean | ||
| 1074 | :set 'newsticker--set-customvar | ||
| 1075 | :group 'newsticker-ticker) | ||
| 1076 | |||
| 1077 | (defgroup newsticker-hooks nil | ||
| 1078 | "Settings for newsticker hooks." | ||
| 1079 | :group 'newsticker) | ||
| 1080 | |||
| 1081 | (defcustom newsticker-start-hook | ||
| 1082 | nil | ||
| 1083 | "Hook run when starting newsticker. | ||
| 1084 | This hook is run at the very end of `newsticker-start'." | ||
| 1085 | :options '(newsticker-start-ticker) | ||
| 1086 | :type 'hook | ||
| 1087 | :group 'newsticker-hooks) | ||
| 1088 | |||
| 1089 | (defcustom newsticker-stop-hook | ||
| 1090 | nil | ||
| 1091 | "Hook run when stopping newsticker. | ||
| 1092 | This hook is run at the very end of `newsticker-stop'." | ||
| 1093 | :options nil | ||
| 1094 | :type 'hook | ||
| 1095 | :group 'newsticker-hooks) | ||
| 1096 | |||
| 1097 | (defcustom newsticker-new-item-functions | ||
| 1098 | nil | ||
| 1099 | "List of functions run after a new headline has been retrieved. | ||
| 1100 | Each function is called with the following three arguments: | ||
| 1101 | FEED the name of the corresponding news feed, | ||
| 1102 | TITLE the title of the headline, | ||
| 1103 | DESC the decoded description of the headline. | ||
| 1104 | |||
| 1105 | See `newsticker-download-images', and | ||
| 1106 | `newsticker-download-enclosures' for sample functions. | ||
| 1107 | |||
| 1108 | Please note that these functions are called only once for a | ||
| 1109 | headline after it has been retrieved for the first time." | ||
| 1110 | :type 'hook | ||
| 1111 | :options '(newsticker-download-images | ||
| 1112 | newsticker-download-enclosures) | ||
| 1113 | :group 'newsticker-hooks) | ||
| 1114 | |||
| 1115 | (defcustom newsticker-select-item-hook | ||
| 1116 | 'newsticker--buffer-make-item-completely-visible | ||
| 1117 | "List of functions run after a headline has been selected. | ||
| 1118 | Each function is called after one of `newsticker-next-item', | ||
| 1119 | `newsticker-next-new-item', `newsticker-previous-item', | ||
| 1120 | `newsticker-previous-new-item' has been called. | ||
| 1121 | |||
| 1122 | The default value 'newsticker--buffer-make-item-completely-visible | ||
| 1123 | assures that the current item is always completely visible." | ||
| 1124 | :type 'hook | ||
| 1125 | :options '(newsticker--buffer-make-item-completely-visible) | ||
| 1126 | :group 'newsticker-hooks) | ||
| 1127 | |||
| 1128 | (defcustom newsticker-select-feed-hook | ||
| 1129 | 'newsticker--buffer-make-item-completely-visible | ||
| 1130 | "List of functions run after a feed has been selected. | ||
| 1131 | Each function is called after one of `newsticker-next-feed', and | ||
| 1132 | `newsticker-previous-feed' has been called. | ||
| 1133 | |||
| 1134 | The default value 'newsticker--buffer-make-item-completely-visible | ||
| 1135 | assures that the current feed is completely visible." | ||
| 1136 | :type 'hook | ||
| 1137 | :options '(newsticker--buffer-make-item-completely-visible) | ||
| 1138 | :group 'newsticker-hooks) | ||
| 1139 | |||
| 1140 | (defcustom newsticker-buffer-change-hook | ||
| 1141 | 'newsticker-w3m-show-inline-images | ||
| 1142 | "List of functions run after the newsticker buffer has been updated. | ||
| 1143 | Each function is called after `newsticker-buffer-update' has been called. | ||
| 1144 | |||
| 1145 | The default value '`newsticker-w3m-show-inline-images' loads inline | ||
| 1146 | images." | ||
| 1147 | :type 'hook | ||
| 1148 | :group 'newsticker-hooks) | ||
| 1149 | |||
| 1150 | (defcustom newsticker-narrow-hook | ||
| 1151 | 'newsticker-w3m-show-inline-images | ||
| 1152 | "List of functions run after narrowing in newsticker buffer has changed. | ||
| 1153 | Each function is called after | ||
| 1154 | `newsticker-toggle-auto-narrow-to-feed' or | ||
| 1155 | `newsticker-toggle-auto-narrow-to-item' has been called. | ||
| 1156 | |||
| 1157 | The default value '`newsticker-w3m-show-inline-images' loads inline | ||
| 1158 | images." | ||
| 1159 | :type 'hook | ||
| 1160 | :group 'newsticker-hooks) | ||
| 1161 | |||
| 1162 | (defgroup newsticker-miscellaneous nil | ||
| 1163 | "Miscellaneous newsticker settings." | ||
| 1164 | :group 'newsticker) | ||
| 1165 | |||
| 1166 | (defcustom newsticker-cache-filename | ||
| 1167 | "~/.newsticker-cache" | ||
| 1168 | "Name of the newsticker cache file." | ||
| 1169 | :type 'string | ||
| 1170 | :group 'newsticker-miscellaneous) | ||
| 1171 | |||
| 1172 | (defcustom newsticker-imagecache-dirname | ||
| 1173 | "~/.newsticker-images" | ||
| 1174 | "Name of the directory where newsticker stores cached images." | ||
| 1175 | :type 'string | ||
| 1176 | :group 'newsticker-miscellaneous) | ||
| 1177 | |||
| 1178 | ;; debugging | ||
| 1179 | (defcustom newsticker-debug | ||
| 1180 | nil | ||
| 1181 | "Enables some features needed for debugging newsticker.el. | ||
| 1182 | |||
| 1183 | If set to t newsticker.el will print lots of debugging messages, and the | ||
| 1184 | buffers *newsticker-wget-<feed>* will not be closed." | ||
| 1185 | :type 'boolean | ||
| 1186 | ;;:set 'newsticker--set-customvar | ||
| 1187 | :group 'newsticker-miscellaneous) | ||
| 1188 | |||
| 1189 | ;; ====================================================================== | ||
| 1190 | ;;; Compatibility section, XEmacs, Emacs | ||
| 1191 | ;; ====================================================================== | ||
| 1192 | (unless (fboundp 'time-add) | ||
| 1193 | (require 'time-date);;FIXME | ||
| 1194 | (defun time-add (t1 t2) | ||
| 1195 | (seconds-to-time (+ (time-to-seconds t1) (time-to-seconds t2))))) | ||
| 1196 | |||
| 1197 | (unless (fboundp 'match-string-no-properties) | ||
| 1198 | (defalias 'match-string-no-properties 'match-string)) | ||
| 1199 | |||
| 1200 | (when (featurep 'xemacs) | ||
| 1201 | (unless (fboundp 'replace-regexp-in-string) | ||
| 1202 | (defun replace-regexp-in-string (re rp st) | ||
| 1203 | (save-match-data ;; apparently XEmacs needs save-match-data | ||
| 1204 | (replace-in-string st re rp))))) | ||
| 1205 | |||
| 1206 | ;; copied from subr.el | ||
| 1207 | (unless (fboundp 'add-to-invisibility-spec) | ||
| 1208 | (defun add-to-invisibility-spec (arg) | ||
| 1209 | "Add elements to `buffer-invisibility-spec'. | ||
| 1210 | See documentation for `buffer-invisibility-spec' for the kind of elements | ||
| 1211 | that can be added." | ||
| 1212 | (if (eq buffer-invisibility-spec t) | ||
| 1213 | (setq buffer-invisibility-spec (list t))) | ||
| 1214 | (setq buffer-invisibility-spec | ||
| 1215 | (cons arg buffer-invisibility-spec)))) | ||
| 1216 | |||
| 1217 | ;; copied from subr.el | ||
| 1218 | (unless (fboundp 'remove-from-invisibility-spec) | ||
| 1219 | (defun remove-from-invisibility-spec (arg) | ||
| 1220 | "Remove elements from `buffer-invisibility-spec'." | ||
| 1221 | (if (consp buffer-invisibility-spec) | ||
| 1222 | (setq buffer-invisibility-spec | ||
| 1223 | (delete arg buffer-invisibility-spec))))) | ||
| 1224 | |||
| 1225 | ;; ====================================================================== | ||
| 1226 | ;;; Internal variables | ||
| 1227 | ;; ====================================================================== | ||
| 1228 | (defvar newsticker--item-list nil | ||
| 1229 | "List of newsticker items.") | ||
| 1230 | (defvar newsticker--item-position 0 | ||
| 1231 | "Actual position in list of newsticker items.") | ||
| 1232 | (defvar newsticker--prev-message "There was no previous message yet!" | ||
| 1233 | "Last message that the newsticker displayed.") | ||
| 1234 | (defvar newsticker--scrollable-text "" | ||
| 1235 | "The text which is scrolled smoothly in the echo area.") | ||
| 1236 | (defvar newsticker--buffer-uptodate-p nil | ||
| 1237 | "Tells whether the newsticker buffer is up to date.") | ||
| 1238 | (defvar newsticker--latest-update-time (current-time) | ||
| 1239 | "The time at which the latest news arrived.") | ||
| 1240 | (defvar newsticker--process-ids nil | ||
| 1241 | "List of PIDs of active newsticker processes.") | ||
| 1242 | |||
| 1243 | (defvar newsticker--cache nil "Cached newsticker data. | ||
| 1244 | This is a list of the form | ||
| 1245 | |||
| 1246 | ((label1 | ||
| 1247 | (title description link time age index preformatted-contents | ||
| 1248 | preformatted-title) | ||
| 1249 | ...) | ||
| 1250 | (label2 | ||
| 1251 | (title description link time age index preformatted-contents | ||
| 1252 | preformatted-title) | ||
| 1253 | ...) | ||
| 1254 | ...) | ||
| 1255 | |||
| 1256 | where LABEL is a symbol. TITLE, DESCRIPTION, and LINK are | ||
| 1257 | strings. TIME is a time value as returned by `current-time'. | ||
| 1258 | AGE is a symbol: 'new, 'old, 'immortal, and 'obsolete denote | ||
| 1259 | ordinary news items, whereas 'feed denotes an item which is not a | ||
| 1260 | headline but describes the feed itself. INDEX denotes the | ||
| 1261 | original position of the item -- used for restoring the original | ||
| 1262 | order. PREFORMATTED-CONTENTS and PREFORMATTED-TITLE hold the | ||
| 1263 | formatted contents of the item's description and title. This | ||
| 1264 | speeds things up if HTML rendering is used, which is rather | ||
| 1265 | slow.") | ||
| 1266 | |||
| 1267 | (defvar newsticker--auto-narrow-to-feed nil | ||
| 1268 | "Automatically narrow to current news feed. | ||
| 1269 | If non-nil only the items of the current news feed are visible.") | ||
| 1270 | |||
| 1271 | (defvar newsticker--auto-narrow-to-item nil | ||
| 1272 | "Automatically narrow to current news item. | ||
| 1273 | If non-nil only the current headline is visible.") | ||
| 1274 | |||
| 1275 | (defconst newsticker--error-headline | ||
| 1276 | "[COULD NOT DOWNLOAD HEADLINES!]" | ||
| 1277 | "Title of error headline which will be inserted if news retrieval fails.") | ||
| 1278 | |||
| 1279 | ;; ====================================================================== | ||
| 1280 | ;;; Toolbar | ||
| 1281 | ;; ====================================================================== | ||
| 1282 | (defconst newsticker--next-item-image | ||
| 1283 | (if (fboundp 'create-image) | ||
| 1284 | (create-image "/* XPM */ | ||
| 1285 | static char * next_xpm[] = { | ||
| 1286 | \"24 24 42 1\", | ||
| 1287 | \" c None\", | ||
| 1288 | \". c #000000\", | ||
| 1289 | \"+ c #7EB6DE\", | ||
| 1290 | \"@ c #82BBE2\", | ||
| 1291 | \"# c #85BEE4\", | ||
| 1292 | \"$ c #88C1E7\", | ||
| 1293 | \"% c #8AC3E8\", | ||
| 1294 | \"& c #87C1E6\", | ||
| 1295 | \"* c #8AC4E9\", | ||
| 1296 | \"= c #8CC6EA\", | ||
| 1297 | \"- c #8CC6EB\", | ||
| 1298 | \"; c #88C2E7\", | ||
| 1299 | \"> c #8BC5E9\", | ||
| 1300 | \", c #8DC7EB\", | ||
| 1301 | \"' c #87C0E6\", | ||
| 1302 | \") c #8AC4E8\", | ||
| 1303 | \"! c #8BC5EA\", | ||
| 1304 | \"~ c #8BC4E9\", | ||
| 1305 | \"{ c #88C1E6\", | ||
| 1306 | \"] c #89C3E8\", | ||
| 1307 | \"^ c #86BFE5\", | ||
| 1308 | \"/ c #83BBE2\", | ||
| 1309 | \"( c #82BBE1\", | ||
| 1310 | \"_ c #86C0E5\", | ||
| 1311 | \": c #87C0E5\", | ||
| 1312 | \"< c #83BCE2\", | ||
| 1313 | \"[ c #81B9E0\", | ||
| 1314 | \"} c #81BAE1\", | ||
| 1315 | \"| c #78B0D9\", | ||
| 1316 | \"1 c #7BB3DB\", | ||
| 1317 | \"2 c #7DB5DD\", | ||
| 1318 | \"3 c #7DB6DD\", | ||
| 1319 | \"4 c #72A9D4\", | ||
| 1320 | \"5 c #75ACD6\", | ||
| 1321 | \"6 c #76AED7\", | ||
| 1322 | \"7 c #77AFD8\", | ||
| 1323 | \"8 c #6BA1CD\", | ||
| 1324 | \"9 c #6EA4CF\", | ||
| 1325 | \"0 c #6FA6D1\", | ||
| 1326 | \"a c #6298C6\", | ||
| 1327 | \"b c #659BC8\", | ||
| 1328 | \"c c #5C91C0\", | ||
| 1329 | \" \", | ||
| 1330 | \" \", | ||
| 1331 | \" . \", | ||
| 1332 | \" .. \", | ||
| 1333 | \" .+. \", | ||
| 1334 | \" .@#. \", | ||
| 1335 | \" .#$%. \", | ||
| 1336 | \" .&*=-. \", | ||
| 1337 | \" .;>,,,. \", | ||
| 1338 | \" .;>,,,=. \", | ||
| 1339 | \" .')!==~;. \", | ||
| 1340 | \" .#{]*%;^/. \", | ||
| 1341 | \" .(#_':#<. \", | ||
| 1342 | \" .+[@</}. \", | ||
| 1343 | \" .|1232. \", | ||
| 1344 | \" .4567. \", | ||
| 1345 | \" .890. \", | ||
| 1346 | \" .ab. \", | ||
| 1347 | \" .c. \", | ||
| 1348 | \" .. \", | ||
| 1349 | \" . \", | ||
| 1350 | \" \", | ||
| 1351 | \" \", | ||
| 1352 | \" \"}; | ||
| 1353 | " | ||
| 1354 | 'xpm t) | ||
| 1355 | "Image for the next item button.")) | ||
| 1356 | |||
| 1357 | (defconst newsticker--previous-item-image | ||
| 1358 | (if (fboundp 'create-image) | ||
| 1359 | (create-image "/* XPM */ | ||
| 1360 | static char * previous_xpm[] = { | ||
| 1361 | \"24 24 39 1\", | ||
| 1362 | \" c None\", | ||
| 1363 | \". c #000000\", | ||
| 1364 | \"+ c #7BB3DB\", | ||
| 1365 | \"@ c #83BCE2\", | ||
| 1366 | \"# c #7FB8DF\", | ||
| 1367 | \"$ c #89C2E7\", | ||
| 1368 | \"% c #86BFE5\", | ||
| 1369 | \"& c #83BBE2\", | ||
| 1370 | \"* c #8CC6EA\", | ||
| 1371 | \"= c #8BC4E9\", | ||
| 1372 | \"- c #88C2E7\", | ||
| 1373 | \"; c #85BEE4\", | ||
| 1374 | \"> c #8DC7EB\", | ||
| 1375 | \", c #89C3E8\", | ||
| 1376 | \"' c #8AC4E8\", | ||
| 1377 | \") c #8BC5EA\", | ||
| 1378 | \"! c #88C1E6\", | ||
| 1379 | \"~ c #8AC4E9\", | ||
| 1380 | \"{ c #8AC3E8\", | ||
| 1381 | \"] c #86C0E5\", | ||
| 1382 | \"^ c #87C0E6\", | ||
| 1383 | \"/ c #87C0E5\", | ||
| 1384 | \"( c #82BBE2\", | ||
| 1385 | \"_ c #81BAE1\", | ||
| 1386 | \": c #7FB7DF\", | ||
| 1387 | \"< c #7DB6DD\", | ||
| 1388 | \"[ c #7DB5DD\", | ||
| 1389 | \"} c #7CB4DC\", | ||
| 1390 | \"| c #79B1DA\", | ||
| 1391 | \"1 c #76ADD7\", | ||
| 1392 | \"2 c #77AFD8\", | ||
| 1393 | \"3 c #73AAD4\", | ||
| 1394 | \"4 c #70A7D1\", | ||
| 1395 | \"5 c #6EA5D0\", | ||
| 1396 | \"6 c #6CA2CE\", | ||
| 1397 | \"7 c #689ECB\", | ||
| 1398 | \"8 c #6399C7\", | ||
| 1399 | \"9 c #6095C4\", | ||
| 1400 | \"0 c #5C90C0\", | ||
| 1401 | \" \", | ||
| 1402 | \" \", | ||
| 1403 | \" . \", | ||
| 1404 | \" .. \", | ||
| 1405 | \" .+. \", | ||
| 1406 | \" .@#. \", | ||
| 1407 | \" .$%&. \", | ||
| 1408 | \" .*=-;. \", | ||
| 1409 | \" .>>*,%. \", | ||
| 1410 | \" .>>>*,%. \", | ||
| 1411 | \" .')**=-;. \", | ||
| 1412 | \" .;!,~{-%&. \", | ||
| 1413 | \" .;]^/;@#. \", | ||
| 1414 | \" .(@&_:+. \", | ||
| 1415 | \" .<[}|1. \", | ||
| 1416 | \" .2134. \", | ||
| 1417 | \" .567. \", | ||
| 1418 | \" .89. \", | ||
| 1419 | \" .0. \", | ||
| 1420 | \" .. \", | ||
| 1421 | \" . \", | ||
| 1422 | \" \", | ||
| 1423 | \" \", | ||
| 1424 | \" \"}; | ||
| 1425 | " | ||
| 1426 | 'xpm t) | ||
| 1427 | "Image for the previous item button.")) | ||
| 1428 | |||
| 1429 | (defconst newsticker--previous-feed-image | ||
| 1430 | (if (fboundp 'create-image) | ||
| 1431 | (create-image "/* XPM */ | ||
| 1432 | static char * prev_feed_xpm[] = { | ||
| 1433 | \"24 24 52 1\", | ||
| 1434 | \" c None\", | ||
| 1435 | \". c #000000\", | ||
| 1436 | \"+ c #70A7D2\", | ||
| 1437 | \"@ c #75ADD6\", | ||
| 1438 | \"# c #71A8D3\", | ||
| 1439 | \"$ c #79B1DA\", | ||
| 1440 | \"% c #7BB3DB\", | ||
| 1441 | \"& c #7DB5DD\", | ||
| 1442 | \"* c #83BBE2\", | ||
| 1443 | \"= c #7EB6DE\", | ||
| 1444 | \"- c #78B0D9\", | ||
| 1445 | \"; c #7FB7DE\", | ||
| 1446 | \"> c #88C2E7\", | ||
| 1447 | \", c #85BEE4\", | ||
| 1448 | \"' c #80B9E0\", | ||
| 1449 | \") c #80B8DF\", | ||
| 1450 | \"! c #8CC6EA\", | ||
| 1451 | \"~ c #89C3E8\", | ||
| 1452 | \"{ c #86BFE5\", | ||
| 1453 | \"] c #81BAE1\", | ||
| 1454 | \"^ c #7CB4DC\", | ||
| 1455 | \"/ c #7FB8DF\", | ||
| 1456 | \"( c #8DC7EB\", | ||
| 1457 | \"_ c #7BB3DC\", | ||
| 1458 | \": c #7EB7DE\", | ||
| 1459 | \"< c #8BC4E9\", | ||
| 1460 | \"[ c #8AC4E9\", | ||
| 1461 | \"} c #8AC3E8\", | ||
| 1462 | \"| c #87C0E6\", | ||
| 1463 | \"1 c #87C0E5\", | ||
| 1464 | \"2 c #83BCE2\", | ||
| 1465 | \"3 c #75ACD6\", | ||
| 1466 | \"4 c #7FB7DF\", | ||
| 1467 | \"5 c #77AED8\", | ||
| 1468 | \"6 c #71A8D2\", | ||
| 1469 | \"7 c #70A7D1\", | ||
| 1470 | \"8 c #76ADD7\", | ||
| 1471 | \"9 c #6CA2CE\", | ||
| 1472 | \"0 c #699FCC\", | ||
| 1473 | \"a c #73AAD4\", | ||
| 1474 | \"b c #6BA1CD\", | ||
| 1475 | \"c c #669CC9\", | ||
| 1476 | \"d c #6298C5\", | ||
| 1477 | \"e c #689ECB\", | ||
| 1478 | \"f c #6499C7\", | ||
| 1479 | \"g c #6095C3\", | ||
| 1480 | \"h c #5C91C0\", | ||
| 1481 | \"i c #5E93C2\", | ||
| 1482 | \"j c #5B90C0\", | ||
| 1483 | \"k c #588CBC\", | ||
| 1484 | \"l c #578CBC\", | ||
| 1485 | \"m c #5589BA\", | ||
| 1486 | \" \", | ||
| 1487 | \" \", | ||
| 1488 | \" ... . \", | ||
| 1489 | \" .+. .. \", | ||
| 1490 | \" .@. .#. \", | ||
| 1491 | \" .$. .%@. \", | ||
| 1492 | \" .&. .*=-. \", | ||
| 1493 | \" .;. .>,'%. \", | ||
| 1494 | \" .). .!~{]^. \", | ||
| 1495 | \" ./. .(!~{]_. \", | ||
| 1496 | \" .:. .!!<>,'%. \", | ||
| 1497 | \" .&. .~[}>{*=-. \", | ||
| 1498 | \" .$. .|1,2/%@. \", | ||
| 1499 | \" .3. .*]4%56. \", | ||
| 1500 | \" .7. .^$8#9. \", | ||
| 1501 | \" .0. .a7bc. \", | ||
| 1502 | \" .d. .efg. \", | ||
| 1503 | \" .h. .ij. \", | ||
| 1504 | \" .k. .l. \", | ||
| 1505 | \" .m. .. \", | ||
| 1506 | \" ... . \", | ||
| 1507 | \" \", | ||
| 1508 | \" \", | ||
| 1509 | \" \"}; | ||
| 1510 | " | ||
| 1511 | 'xpm t) | ||
| 1512 | "Image for the previous feed button.")) | ||
| 1513 | |||
| 1514 | (defconst newsticker--next-feed-image | ||
| 1515 | (if (fboundp 'create-image) | ||
| 1516 | (create-image "/* XPM */ | ||
| 1517 | static char * next_feed_xpm[] = { | ||
| 1518 | \"24 24 57 1\", | ||
| 1519 | \" c None\", | ||
| 1520 | \". c #000000\", | ||
| 1521 | \"+ c #6CA2CE\", | ||
| 1522 | \"@ c #75ADD6\", | ||
| 1523 | \"# c #71A8D3\", | ||
| 1524 | \"$ c #79B1DA\", | ||
| 1525 | \"% c #7EB7DE\", | ||
| 1526 | \"& c #7DB5DD\", | ||
| 1527 | \"* c #81BAE1\", | ||
| 1528 | \"= c #85BEE4\", | ||
| 1529 | \"- c #78B0D9\", | ||
| 1530 | \"; c #7FB7DE\", | ||
| 1531 | \"> c #83BCE3\", | ||
| 1532 | \", c #87C1E6\", | ||
| 1533 | \"' c #8AC4E9\", | ||
| 1534 | \") c #7BB3DB\", | ||
| 1535 | \"! c #80B8DF\", | ||
| 1536 | \"~ c #88C2E7\", | ||
| 1537 | \"{ c #8BC5E9\", | ||
| 1538 | \"] c #8DC7EB\", | ||
| 1539 | \"^ c #7CB4DC\", | ||
| 1540 | \"/ c #7FB8DF\", | ||
| 1541 | \"( c #84BDE3\", | ||
| 1542 | \"_ c #7BB3DC\", | ||
| 1543 | \": c #83BCE2\", | ||
| 1544 | \"< c #87C0E6\", | ||
| 1545 | \"[ c #8AC4E8\", | ||
| 1546 | \"} c #8BC5EA\", | ||
| 1547 | \"| c #8CC6EA\", | ||
| 1548 | \"1 c #88C1E6\", | ||
| 1549 | \"2 c #89C3E8\", | ||
| 1550 | \"3 c #8AC3E8\", | ||
| 1551 | \"4 c #7EB6DE\", | ||
| 1552 | \"5 c #82BBE1\", | ||
| 1553 | \"6 c #86C0E5\", | ||
| 1554 | \"7 c #87C0E5\", | ||
| 1555 | \"8 c #75ACD6\", | ||
| 1556 | \"9 c #7AB2DA\", | ||
| 1557 | \"0 c #81B9E0\", | ||
| 1558 | \"a c #82BBE2\", | ||
| 1559 | \"b c #71A8D2\", | ||
| 1560 | \"c c #70A7D1\", | ||
| 1561 | \"d c #74ACD6\", | ||
| 1562 | \"e c #699FCC\", | ||
| 1563 | \"f c #6EA5D0\", | ||
| 1564 | \"g c #72A9D4\", | ||
| 1565 | \"h c #669CC9\", | ||
| 1566 | \"i c #6298C5\", | ||
| 1567 | \"j c #679DCA\", | ||
| 1568 | \"k c #6BA1CD\", | ||
| 1569 | \"l c #6095C3\", | ||
| 1570 | \"m c #5C91C0\", | ||
| 1571 | \"n c #5F94C2\", | ||
| 1572 | \"o c #5B90C0\", | ||
| 1573 | \"p c #588CBC\", | ||
| 1574 | \"q c #578CBC\", | ||
| 1575 | \"r c #5589BA\", | ||
| 1576 | \" \", | ||
| 1577 | \" \", | ||
| 1578 | \" . ... \", | ||
| 1579 | \" .. .+. \", | ||
| 1580 | \" .@. .#. \", | ||
| 1581 | \" .$%. .@. \", | ||
| 1582 | \" .&*=. .-. \", | ||
| 1583 | \" .;>,'. .). \", | ||
| 1584 | \" .!=~{]. .^. \", | ||
| 1585 | \" ./(~{]]. ._. \", | ||
| 1586 | \" .%:<[}||. .). \", | ||
| 1587 | \" .&*=12'3~. .-. \", | ||
| 1588 | \" .$45=6<7. .@. \", | ||
| 1589 | \" .8940a:. .b. \", | ||
| 1590 | \" .cd-)&. .+. \", | ||
| 1591 | \" .efg8. .h. \", | ||
| 1592 | \" .ijk. .l. \", | ||
| 1593 | \" .mn. .o. \", | ||
| 1594 | \" .p. .q. \", | ||
| 1595 | \" .. .r. \", | ||
| 1596 | \" . ... \", | ||
| 1597 | \" \", | ||
| 1598 | \" \", | ||
| 1599 | \" \"}; | ||
| 1600 | " | ||
| 1601 | 'xpm t) | ||
| 1602 | "Image for the next feed button.")) | ||
| 1603 | |||
| 1604 | (defconst newsticker--mark-read-image | ||
| 1605 | (if (fboundp 'create-image) | ||
| 1606 | (create-image "/* XPM */ | ||
| 1607 | static char * mark_read_xpm[] = { | ||
| 1608 | \"24 24 44 1\", | ||
| 1609 | \" c None\", | ||
| 1610 | \". c #C20000\", | ||
| 1611 | \"+ c #BE0000\", | ||
| 1612 | \"@ c #C70000\", | ||
| 1613 | \"# c #CE0000\", | ||
| 1614 | \"$ c #C90000\", | ||
| 1615 | \"% c #BD0000\", | ||
| 1616 | \"& c #CB0000\", | ||
| 1617 | \"* c #D10000\", | ||
| 1618 | \"= c #D70000\", | ||
| 1619 | \"- c #D30000\", | ||
| 1620 | \"; c #CD0000\", | ||
| 1621 | \"> c #C60000\", | ||
| 1622 | \", c #D40000\", | ||
| 1623 | \"' c #DA0000\", | ||
| 1624 | \") c #DE0000\", | ||
| 1625 | \"! c #DB0000\", | ||
| 1626 | \"~ c #D60000\", | ||
| 1627 | \"{ c #D00000\", | ||
| 1628 | \"] c #DC0000\", | ||
| 1629 | \"^ c #E00000\", | ||
| 1630 | \"/ c #E40000\", | ||
| 1631 | \"( c #E10000\", | ||
| 1632 | \"_ c #DD0000\", | ||
| 1633 | \": c #D80000\", | ||
| 1634 | \"< c #E50000\", | ||
| 1635 | \"[ c #E70000\", | ||
| 1636 | \"} c #E60000\", | ||
| 1637 | \"| c #E20000\", | ||
| 1638 | \"1 c #E90000\", | ||
| 1639 | \"2 c #E80000\", | ||
| 1640 | \"3 c #E30000\", | ||
| 1641 | \"4 c #DF0000\", | ||
| 1642 | \"5 c #D90000\", | ||
| 1643 | \"6 c #CC0000\", | ||
| 1644 | \"7 c #C10000\", | ||
| 1645 | \"8 c #C30000\", | ||
| 1646 | \"9 c #BF0000\", | ||
| 1647 | \"0 c #B90000\", | ||
| 1648 | \"a c #BC0000\", | ||
| 1649 | \"b c #BB0000\", | ||
| 1650 | \"c c #B80000\", | ||
| 1651 | \"d c #B50000\", | ||
| 1652 | \"e c #B70000\", | ||
| 1653 | \" \", | ||
| 1654 | \" \", | ||
| 1655 | \" \", | ||
| 1656 | \" . + \", | ||
| 1657 | \" +@# $.% \", | ||
| 1658 | \" &*= -;> \", | ||
| 1659 | \" ,') !~{ \", | ||
| 1660 | \" ]^/ (_: \", | ||
| 1661 | \" (<[ }|) \", | ||
| 1662 | \" <[1 2<| \", | ||
| 1663 | \" }222[< \", | ||
| 1664 | \" }}}< \", | ||
| 1665 | \" 333| \", | ||
| 1666 | \" _4^4)] \", | ||
| 1667 | \" ~:' 5=- \", | ||
| 1668 | \" 6{- *#$ \", | ||
| 1669 | \" 7>$ @89 \", | ||
| 1670 | \" 0a+ %bc \", | ||
| 1671 | \" ddc edd \", | ||
| 1672 | \" ddd ddd \", | ||
| 1673 | \" d d \", | ||
| 1674 | \" \", | ||
| 1675 | \" \", | ||
| 1676 | \" \"}; | ||
| 1677 | " | ||
| 1678 | 'xpm t) | ||
| 1679 | "Image for the next feed button.")) | ||
| 1680 | |||
| 1681 | (defconst newsticker--mark-immortal-image | ||
| 1682 | (if (fboundp 'create-image) | ||
| 1683 | (create-image "/* XPM */ | ||
| 1684 | static char * mark_immortal_xpm[] = { | ||
| 1685 | \"24 24 93 2\", | ||
| 1686 | \" c None\", | ||
| 1687 | \". c #171717\", | ||
| 1688 | \"+ c #030303\", | ||
| 1689 | \"@ c #000000\", | ||
| 1690 | \"# c #181818\", | ||
| 1691 | \"$ c #090909\", | ||
| 1692 | \"% c #FFC960\", | ||
| 1693 | \"& c #FFCB61\", | ||
| 1694 | \"* c #FFCB62\", | ||
| 1695 | \"= c #FFC961\", | ||
| 1696 | \"- c #FFC75F\", | ||
| 1697 | \"; c #FFC65E\", | ||
| 1698 | \"> c #FFCA61\", | ||
| 1699 | \", c #FFCD63\", | ||
| 1700 | \"' c #FFCF65\", | ||
| 1701 | \") c #FFD065\", | ||
| 1702 | \"! c #FFCE64\", | ||
| 1703 | \"~ c #FFC35C\", | ||
| 1704 | \"{ c #FFC45D\", | ||
| 1705 | \"] c #FFD166\", | ||
| 1706 | \"^ c #FFD267\", | ||
| 1707 | \"/ c #FFD368\", | ||
| 1708 | \"( c #FFD167\", | ||
| 1709 | \"_ c #FFC05A\", | ||
| 1710 | \": c #010101\", | ||
| 1711 | \"< c #040404\", | ||
| 1712 | \"[ c #FFCC62\", | ||
| 1713 | \"} c #FFD569\", | ||
| 1714 | \"| c #FFD56A\", | ||
| 1715 | \"1 c #FFC860\", | ||
| 1716 | \"2 c #FFC25B\", | ||
| 1717 | \"3 c #FFBB56\", | ||
| 1718 | \"4 c #020202\", | ||
| 1719 | \"5 c #060606\", | ||
| 1720 | \"6 c #FFC15B\", | ||
| 1721 | \"7 c #FFC85F\", | ||
| 1722 | \"8 c #FFD469\", | ||
| 1723 | \"9 c #FFD66A\", | ||
| 1724 | \"0 c #FFBC57\", | ||
| 1725 | \"a c #1B1B1B\", | ||
| 1726 | \"b c #070707\", | ||
| 1727 | \"c c #FFBA55\", | ||
| 1728 | \"d c #FFB451\", | ||
| 1729 | \"e c #FFB954\", | ||
| 1730 | \"f c #FFB350\", | ||
| 1731 | \"g c #FFB652\", | ||
| 1732 | \"h c #FFBE58\", | ||
| 1733 | \"i c #FFCD64\", | ||
| 1734 | \"j c #FFD066\", | ||
| 1735 | \"k c #FFC059\", | ||
| 1736 | \"l c #FFB14E\", | ||
| 1737 | \"m c #0B0B0B\", | ||
| 1738 | \"n c #FFBB55\", | ||
| 1739 | \"o c #FFC15A\", | ||
| 1740 | \"p c #FFB552\", | ||
| 1741 | \"q c #FFAD4B\", | ||
| 1742 | \"r c #080808\", | ||
| 1743 | \"s c #FFAF4C\", | ||
| 1744 | \"t c #FFB853\", | ||
| 1745 | \"u c #FFA948\", | ||
| 1746 | \"v c #050505\", | ||
| 1747 | \"w c #FFB04E\", | ||
| 1748 | \"x c #FFB753\", | ||
| 1749 | \"y c #FFBC56\", | ||
| 1750 | \"z c #FFC55D\", | ||
| 1751 | \"A c #FFC55E\", | ||
| 1752 | \"B c #FFC45C\", | ||
| 1753 | \"C c #FFBD57\", | ||
| 1754 | \"D c #FFB854\", | ||
| 1755 | \"E c #FFB34F\", | ||
| 1756 | \"F c #FFAB4A\", | ||
| 1757 | \"G c #FFA545\", | ||
| 1758 | \"H c #FFAA49\", | ||
| 1759 | \"I c #FFB04D\", | ||
| 1760 | \"J c #FFB551\", | ||
| 1761 | \"K c #FFBF58\", | ||
| 1762 | \"L c #FFB24F\", | ||
| 1763 | \"M c #FFAC4A\", | ||
| 1764 | \"N c #FFA646\", | ||
| 1765 | \"O c #FFA344\", | ||
| 1766 | \"P c #FFA848\", | ||
| 1767 | \"Q c #FFB14F\", | ||
| 1768 | \"R c #FFAF4D\", | ||
| 1769 | \"S c #FFA546\", | ||
| 1770 | \"T c #FFA243\", | ||
| 1771 | \"U c #FFA445\", | ||
| 1772 | \"V c #FFAE4C\", | ||
| 1773 | \"W c #FFA444\", | ||
| 1774 | \"X c #FFA142\", | ||
| 1775 | \"Y c #FF9F41\", | ||
| 1776 | \"Z c #0A0A0A\", | ||
| 1777 | \"` c #FF9E40\", | ||
| 1778 | \" . c #FF9F40\", | ||
| 1779 | \" \", | ||
| 1780 | \" \", | ||
| 1781 | \" \", | ||
| 1782 | \" . + @ @ + # \", | ||
| 1783 | \" $ @ % & * * = - + + \", | ||
| 1784 | \" @ ; > , ' ) ' ! * - ~ @ \", | ||
| 1785 | \" @ { > ! ] ^ / / ( ' * ; _ : \", | ||
| 1786 | \" < _ ; [ ) / } | } / ] , 1 2 3 4 \", | ||
| 1787 | \" 5 6 7 , ] 8 9 9 9 } ^ ! = ~ 0 a \", | ||
| 1788 | \" b c 6 - , ] 8 9 9 9 } ^ ! % ~ 0 d 5 \", | ||
| 1789 | \" : e _ ; * ) / 8 } } / ] , 1 2 3 f 5 \", | ||
| 1790 | \" : g h { = i j ^ / ^ ] ! * ; k e l m \", | ||
| 1791 | \" : f n o ; > , ' ) ' ! * - 2 0 p q r \", | ||
| 1792 | \" : s g 0 6 ; % > * * = - ~ h t l u r \", | ||
| 1793 | \" v u w x y k ~ z A z B o C D E F G b \", | ||
| 1794 | \" 5 H I J e 0 h K h C c x L M N . \", | ||
| 1795 | \" 4 O P q Q d g x g J L R H S T < \", | ||
| 1796 | \" @ T U P F q V q M H N W X + \", | ||
| 1797 | \" @ Y T O W G G W O X Y @ \", | ||
| 1798 | \" 4 Z ` Y Y Y .` 4 4 \", | ||
| 1799 | \" 5 : : @ @ Z \", | ||
| 1800 | \" \", | ||
| 1801 | \" \", | ||
| 1802 | \" \"}; | ||
| 1803 | " | ||
| 1804 | 'xpm t) | ||
| 1805 | "Image for the next feed button.")) | ||
| 1806 | |||
| 1807 | |||
| 1808 | (defconst newsticker--narrow-image | ||
| 1809 | (if (fboundp 'create-image) | ||
| 1810 | (create-image "/* XPM */ | ||
| 1811 | static char * narrow_xpm[] = { | ||
| 1812 | \"24 24 48 1\", | ||
| 1813 | \" c None\", | ||
| 1814 | \". c #000000\", | ||
| 1815 | \"+ c #969696\", | ||
| 1816 | \"@ c #9E9E9E\", | ||
| 1817 | \"# c #A4A4A4\", | ||
| 1818 | \"$ c #AAAAAA\", | ||
| 1819 | \"% c #AEAEAE\", | ||
| 1820 | \"& c #B1B1B1\", | ||
| 1821 | \"* c #B3B3B3\", | ||
| 1822 | \"= c #B4B4B4\", | ||
| 1823 | \"- c #B2B2B2\", | ||
| 1824 | \"; c #AFAFAF\", | ||
| 1825 | \"> c #ABABAB\", | ||
| 1826 | \", c #A6A6A6\", | ||
| 1827 | \"' c #A0A0A0\", | ||
| 1828 | \") c #989898\", | ||
| 1829 | \"! c #909090\", | ||
| 1830 | \"~ c #73AAD4\", | ||
| 1831 | \"{ c #7AB2DA\", | ||
| 1832 | \"] c #7FB8DF\", | ||
| 1833 | \"^ c #84BDE3\", | ||
| 1834 | \"/ c #88C2E7\", | ||
| 1835 | \"( c #8BC5E9\", | ||
| 1836 | \"_ c #8DC7EB\", | ||
| 1837 | \": c #8CC6EA\", | ||
| 1838 | \"< c #89C3E8\", | ||
| 1839 | \"[ c #86BFE5\", | ||
| 1840 | \"} c #81BAE1\", | ||
| 1841 | \"| c #7BB3DC\", | ||
| 1842 | \"1 c #75ACD6\", | ||
| 1843 | \"2 c #6DA4CF\", | ||
| 1844 | \"3 c #979797\", | ||
| 1845 | \"4 c #A3A3A3\", | ||
| 1846 | \"5 c #A8A8A8\", | ||
| 1847 | \"6 c #ADADAD\", | ||
| 1848 | \"7 c #ACACAC\", | ||
| 1849 | \"8 c #A9A9A9\", | ||
| 1850 | \"9 c #A5A5A5\", | ||
| 1851 | \"0 c #9A9A9A\", | ||
| 1852 | \"a c #929292\", | ||
| 1853 | \"b c #8C8C8C\", | ||
| 1854 | \"c c #808080\", | ||
| 1855 | \"d c #818181\", | ||
| 1856 | \"e c #838383\", | ||
| 1857 | \"f c #848484\", | ||
| 1858 | \"g c #858585\", | ||
| 1859 | \"h c #868686\", | ||
| 1860 | \"i c #828282\", | ||
| 1861 | \" \", | ||
| 1862 | \" \", | ||
| 1863 | \" \", | ||
| 1864 | \" .................. \", | ||
| 1865 | \" .+@#$%&*=*-;>,')!. \", | ||
| 1866 | \" .................. \", | ||
| 1867 | \" \", | ||
| 1868 | \" \", | ||
| 1869 | \" .................. \", | ||
| 1870 | \" .~{]^/(___:<[}|12. \", | ||
| 1871 | \" .................. \", | ||
| 1872 | \" \", | ||
| 1873 | \" \", | ||
| 1874 | \" .................. \", | ||
| 1875 | \" .!3@45>666789'0ab. \", | ||
| 1876 | \" .................. \", | ||
| 1877 | \" \", | ||
| 1878 | \" \", | ||
| 1879 | \" .................. \", | ||
| 1880 | \" .cccdefghhgficccc. \", | ||
| 1881 | \" .................. \", | ||
| 1882 | \" \", | ||
| 1883 | \" \", | ||
| 1884 | \" \"}; | ||
| 1885 | " | ||
| 1886 | 'xpm t) | ||
| 1887 | "Image for the next feed button.")) | ||
| 1888 | |||
| 1889 | (defconst newsticker--get-all-image | ||
| 1890 | (if (fboundp 'create-image) | ||
| 1891 | (create-image "/* XPM */ | ||
| 1892 | static char * get_all_xpm[] = { | ||
| 1893 | \"24 24 70 1\", | ||
| 1894 | \" c None\", | ||
| 1895 | \". c #000000\", | ||
| 1896 | \"+ c #F3DA00\", | ||
| 1897 | \"@ c #F5DF00\", | ||
| 1898 | \"# c #F7E300\", | ||
| 1899 | \"$ c #F9E700\", | ||
| 1900 | \"% c #FAEA00\", | ||
| 1901 | \"& c #FBEC00\", | ||
| 1902 | \"* c #FBED00\", | ||
| 1903 | \"= c #FCEE00\", | ||
| 1904 | \"- c #FAEB00\", | ||
| 1905 | \"; c #F9E800\", | ||
| 1906 | \"> c #F8E500\", | ||
| 1907 | \", c #F6E000\", | ||
| 1908 | \"' c #F4DB00\", | ||
| 1909 | \") c #F1D500\", | ||
| 1910 | \"! c #EFD000\", | ||
| 1911 | \"~ c #B7CA00\", | ||
| 1912 | \"{ c #BFD100\", | ||
| 1913 | \"] c #C5D700\", | ||
| 1914 | \"^ c #CBDB00\", | ||
| 1915 | \"/ c #CFDF00\", | ||
| 1916 | \"( c #D2E200\", | ||
| 1917 | \"_ c #D4E400\", | ||
| 1918 | \": c #D3E300\", | ||
| 1919 | \"< c #D0E000\", | ||
| 1920 | \"[ c #CCDD00\", | ||
| 1921 | \"} c #C7D800\", | ||
| 1922 | \"| c #C1D300\", | ||
| 1923 | \"1 c #BACC00\", | ||
| 1924 | \"2 c #B1C500\", | ||
| 1925 | \"3 c #A8BC00\", | ||
| 1926 | \"4 c #20A900\", | ||
| 1927 | \"5 c #22AF00\", | ||
| 1928 | \"6 c #24B500\", | ||
| 1929 | \"7 c #26B900\", | ||
| 1930 | \"8 c #27BC00\", | ||
| 1931 | \"9 c #27BE00\", | ||
| 1932 | \"0 c #28BF00\", | ||
| 1933 | \"a c #27BD00\", | ||
| 1934 | \"b c #26BA00\", | ||
| 1935 | \"c c #25B600\", | ||
| 1936 | \"d c #23B100\", | ||
| 1937 | \"e c #21AB00\", | ||
| 1938 | \"f c #1FA400\", | ||
| 1939 | \"g c #1C9B00\", | ||
| 1940 | \"h c #21AA00\", | ||
| 1941 | \"i c #24B300\", | ||
| 1942 | \"j c #25B800\", | ||
| 1943 | \"k c #25B700\", | ||
| 1944 | \"l c #24B400\", | ||
| 1945 | \"m c #23B000\", | ||
| 1946 | \"n c #1FA500\", | ||
| 1947 | \"o c #1D9E00\", | ||
| 1948 | \"p c #20A800\", | ||
| 1949 | \"q c #21AC00\", | ||
| 1950 | \"r c #23B200\", | ||
| 1951 | \"s c #22AD00\", | ||
| 1952 | \"t c #1D9F00\", | ||
| 1953 | \"u c #20A700\", | ||
| 1954 | \"v c #1EA100\", | ||
| 1955 | \"w c #1C9C00\", | ||
| 1956 | \"x c #1DA000\", | ||
| 1957 | \"y c #1B9800\", | ||
| 1958 | \"z c #1A9600\", | ||
| 1959 | \"A c #1A9700\", | ||
| 1960 | \"B c #1A9500\", | ||
| 1961 | \"C c #199200\", | ||
| 1962 | \"D c #189100\", | ||
| 1963 | \"E c #178C00\", | ||
| 1964 | \" \", | ||
| 1965 | \" \", | ||
| 1966 | \" \", | ||
| 1967 | \" \", | ||
| 1968 | \" ................... \", | ||
| 1969 | \" .+@#$%&*=*&-;>,')!. \", | ||
| 1970 | \" ................... \", | ||
| 1971 | \" \", | ||
| 1972 | \" ................... \", | ||
| 1973 | \" .~{]^/(___:<[}|123. \", | ||
| 1974 | \" ................... \", | ||
| 1975 | \" \", | ||
| 1976 | \" ................... \", | ||
| 1977 | \" .45678909abcdefg. \", | ||
| 1978 | \" .h5icj7jklmeno. \", | ||
| 1979 | \" .pq5drrmshft. \", | ||
| 1980 | \" .fu4h4pnvw. \", | ||
| 1981 | \" .oxvxtwy. \", | ||
| 1982 | \" .zAAzB. \", | ||
| 1983 | \" .CCD. \", | ||
| 1984 | \" .E. \", | ||
| 1985 | \" . \", | ||
| 1986 | \" \", | ||
| 1987 | \" \"}; | ||
| 1988 | " | ||
| 1989 | 'xpm t) | ||
| 1990 | "Image for the next feed button.")) | ||
| 1991 | |||
| 1992 | |||
| 1993 | (defconst newsticker--update-image | ||
| 1994 | (if (fboundp 'create-image) | ||
| 1995 | (create-image "/* XPM */ | ||
| 1996 | static char * update_xpm[] = { | ||
| 1997 | \"24 24 37 1\", | ||
| 1998 | \" c None\", | ||
| 1999 | \". c #076D00\", | ||
| 2000 | \"+ c #0A8600\", | ||
| 2001 | \"@ c #0A8800\", | ||
| 2002 | \"# c #098400\", | ||
| 2003 | \"$ c #087200\", | ||
| 2004 | \"% c #087900\", | ||
| 2005 | \"& c #098500\", | ||
| 2006 | \"* c #098100\", | ||
| 2007 | \"= c #087600\", | ||
| 2008 | \"- c #097E00\", | ||
| 2009 | \"; c #097F00\", | ||
| 2010 | \"> c #0A8700\", | ||
| 2011 | \", c #0A8C00\", | ||
| 2012 | \"' c #097C00\", | ||
| 2013 | \") c #098300\", | ||
| 2014 | \"! c #0A8900\", | ||
| 2015 | \"~ c #0A8E00\", | ||
| 2016 | \"{ c #0B9200\", | ||
| 2017 | \"] c #087700\", | ||
| 2018 | \"^ c #076E00\", | ||
| 2019 | \"/ c #076C00\", | ||
| 2020 | \"( c #076B00\", | ||
| 2021 | \"_ c #076A00\", | ||
| 2022 | \": c #076900\", | ||
| 2023 | \"< c #076800\", | ||
| 2024 | \"[ c #066700\", | ||
| 2025 | \"} c #066500\", | ||
| 2026 | \"| c #066400\", | ||
| 2027 | \"1 c #066300\", | ||
| 2028 | \"2 c #066600\", | ||
| 2029 | \"3 c #066200\", | ||
| 2030 | \"4 c #076700\", | ||
| 2031 | \"5 c #065E00\", | ||
| 2032 | \"6 c #066100\", | ||
| 2033 | \"7 c #065F00\", | ||
| 2034 | \"8 c #066000\", | ||
| 2035 | \" \", | ||
| 2036 | \" \", | ||
| 2037 | \" \", | ||
| 2038 | \" . +@@@+# \", | ||
| 2039 | \" $% &@ +* \", | ||
| 2040 | \" =-# ; \", | ||
| 2041 | \" %*>, ' \", | ||
| 2042 | \" ')!~{ = \", | ||
| 2043 | \" ]$ \", | ||
| 2044 | \" ^ ^ \", | ||
| 2045 | \" . . \", | ||
| 2046 | \" / ( \", | ||
| 2047 | \" _ : \", | ||
| 2048 | \" < [ \", | ||
| 2049 | \" } | \", | ||
| 2050 | \" [[ \", | ||
| 2051 | \" 1 $.:23 \", | ||
| 2052 | \" 3 4}35 \", | ||
| 2053 | \" 6 655 \", | ||
| 2054 | \" 76 85 55 \", | ||
| 2055 | \" 5555555 5 \", | ||
| 2056 | \" \", | ||
| 2057 | \" \", | ||
| 2058 | \" \"}; | ||
| 2059 | " | ||
| 2060 | 'xpm t) | ||
| 2061 | "Image for the update button.")) | ||
| 2062 | |||
| 2063 | (defconst newsticker--browse-image | ||
| 2064 | (if (fboundp 'create-image) | ||
| 2065 | (create-image "/* XPM */ | ||
| 2066 | static char * visit_xpm[] = { | ||
| 2067 | \"24 24 39 1\", | ||
| 2068 | \" c None\", | ||
| 2069 | \". c #000000\", | ||
| 2070 | \"+ c #FFFFFF\", | ||
| 2071 | \"@ c #00E63D\", | ||
| 2072 | \"# c #00E83E\", | ||
| 2073 | \"$ c #00E73D\", | ||
| 2074 | \"% c #00E93E\", | ||
| 2075 | \"& c #00E63C\", | ||
| 2076 | \"* c #00E53C\", | ||
| 2077 | \"= c #00E23B\", | ||
| 2078 | \"- c #00E33B\", | ||
| 2079 | \"; c #00E83D\", | ||
| 2080 | \"> c #00E13A\", | ||
| 2081 | \", c #00DD38\", | ||
| 2082 | \"' c #00DE38\", | ||
| 2083 | \") c #00E23A\", | ||
| 2084 | \"! c #00E43C\", | ||
| 2085 | \"~ c #00DF39\", | ||
| 2086 | \"{ c #00DB37\", | ||
| 2087 | \"] c #00D634\", | ||
| 2088 | \"^ c #00D734\", | ||
| 2089 | \"/ c #00E039\", | ||
| 2090 | \"( c #00DC37\", | ||
| 2091 | \"_ c #00D835\", | ||
| 2092 | \": c #00D332\", | ||
| 2093 | \"< c #00CD2F\", | ||
| 2094 | \"[ c #00DB36\", | ||
| 2095 | \"} c #00D433\", | ||
| 2096 | \"| c #00CF30\", | ||
| 2097 | \"1 c #00DA36\", | ||
| 2098 | \"2 c #00D936\", | ||
| 2099 | \"3 c #00D533\", | ||
| 2100 | \"4 c #00D131\", | ||
| 2101 | \"5 c #00CE2F\", | ||
| 2102 | \"6 c #00CC2F\", | ||
| 2103 | \"7 c #00CA2D\", | ||
| 2104 | \"8 c #00C62B\", | ||
| 2105 | \"9 c #00C52A\", | ||
| 2106 | \"0 c #00BE27\", | ||
| 2107 | \" \", | ||
| 2108 | \" \", | ||
| 2109 | \" . \", | ||
| 2110 | \" .+. \", | ||
| 2111 | \" .+++. \", | ||
| 2112 | \" .++.++. \", | ||
| 2113 | \" .++.@.++. \", | ||
| 2114 | \" .++.##$.++. \", | ||
| 2115 | \" .++.%%%#&.++. \", | ||
| 2116 | \" .++.$%%%#*=.++. \", | ||
| 2117 | \" .++.-@;##$*>,.++. \", | ||
| 2118 | \" .++.')!&@@*=~{].++. \", | ||
| 2119 | \" .++.^{~>---)/(_:<.++. \", | ||
| 2120 | \" .++.^[,~/~'(_}|.++. \", | ||
| 2121 | \" .++.]_1[12^:|.++. \", | ||
| 2122 | \" .++.:}33:45.++. \", | ||
| 2123 | \" .++.<5567.++. \", | ||
| 2124 | \" .++.889.++. \", | ||
| 2125 | \" .++.0.++. \", | ||
| 2126 | \" .++.++. \", | ||
| 2127 | \" .+++. \", | ||
| 2128 | \" .+. \", | ||
| 2129 | \" . \", | ||
| 2130 | \" \"}; | ||
| 2131 | " | ||
| 2132 | 'xpm t) | ||
| 2133 | "Image for the browse button.")) | ||
| 2134 | |||
| 2135 | |||
| 2136 | (defvar newsticker-tool-bar-map | ||
| 2137 | (if (featurep 'xemacs) | ||
| 2138 | nil | ||
| 2139 | (let ((tool-bar-map (make-sparse-keymap))) | ||
| 2140 | (define-key tool-bar-map [newsticker-sep-1] | ||
| 2141 | (list 'menu-item "--double-line")) | ||
| 2142 | (define-key tool-bar-map [newsticker-browse-url] | ||
| 2143 | (list 'menu-item "newsticker-browse-url" 'newsticker-browse-url | ||
| 2144 | :visible t | ||
| 2145 | :help "Browse URL for item at point" | ||
| 2146 | :image newsticker--browse-image)) | ||
| 2147 | (define-key tool-bar-map [newsticker-buffer-force-update] | ||
| 2148 | (list 'menu-item "newsticker-buffer-force-update" | ||
| 2149 | 'newsticker-buffer-force-update | ||
| 2150 | :visible t | ||
| 2151 | :help "Update newsticker buffer" | ||
| 2152 | :image newsticker--update-image | ||
| 2153 | :enable '(not newsticker--buffer-uptodate-p))) | ||
| 2154 | (define-key tool-bar-map [newsticker-get-all-news] | ||
| 2155 | (list 'menu-item "newsticker-get-all-news" 'newsticker-get-all-news | ||
| 2156 | :visible t | ||
| 2157 | :help "Get news for all feeds" | ||
| 2158 | :image newsticker--get-all-image)) | ||
| 2159 | (define-key tool-bar-map [newsticker-mark-item-at-point-as-read] | ||
| 2160 | (list 'menu-item "newsticker-mark-item-at-point-as-read" | ||
| 2161 | 'newsticker-mark-item-at-point-as-read | ||
| 2162 | :visible t | ||
| 2163 | :image newsticker--mark-read-image | ||
| 2164 | :help "Mark current item as read" | ||
| 2165 | :enable '(newsticker-item-not-old-p))) | ||
| 2166 | (define-key tool-bar-map [newsticker-mark-item-at-point-as-immortal] | ||
| 2167 | (list 'menu-item "newsticker-mark-item-at-point-as-immortal" | ||
| 2168 | 'newsticker-mark-item-at-point-as-immortal | ||
| 2169 | :visible t | ||
| 2170 | :image newsticker--mark-immortal-image | ||
| 2171 | :help "Mark current item as immortal" | ||
| 2172 | :enable '(newsticker-item-not-immortal-p))) | ||
| 2173 | (define-key tool-bar-map [newsticker-toggle-auto-narrow-to-feed] | ||
| 2174 | (list 'menu-item "newsticker-toggle-auto-narrow-to-feed" | ||
| 2175 | 'newsticker-toggle-auto-narrow-to-feed | ||
| 2176 | :visible t | ||
| 2177 | :help "Toggle visibility of other feeds" | ||
| 2178 | :image newsticker--narrow-image)) | ||
| 2179 | (define-key tool-bar-map [newsticker-next-feed] | ||
| 2180 | (list 'menu-item "newsticker-next-feed" 'newsticker-next-feed | ||
| 2181 | :visible t | ||
| 2182 | :help "Go to next feed" | ||
| 2183 | :image newsticker--next-feed-image | ||
| 2184 | :enable '(newsticker-next-feed-available-p))) | ||
| 2185 | (define-key tool-bar-map [newsticker-next-item] | ||
| 2186 | (list 'menu-item "newsticker-next-item" 'newsticker-next-item | ||
| 2187 | :visible t | ||
| 2188 | :help "Go to next item" | ||
| 2189 | :image newsticker--next-item-image | ||
| 2190 | :enable '(newsticker-next-item-available-p))) | ||
| 2191 | (define-key tool-bar-map [newsticker-previous-item] | ||
| 2192 | (list 'menu-item "newsticker-previous-item" 'newsticker-previous-item | ||
| 2193 | :visible t | ||
| 2194 | :help "Go to previous item" | ||
| 2195 | :image newsticker--previous-item-image | ||
| 2196 | :enable '(newsticker-previous-item-available-p))) | ||
| 2197 | (define-key tool-bar-map [newsticker-previous-feed] | ||
| 2198 | (list 'menu-item "newsticker-previous-feed" 'newsticker-previous-feed | ||
| 2199 | :visible t | ||
| 2200 | :help "Go to previous feed" | ||
| 2201 | :image newsticker--previous-feed-image | ||
| 2202 | :enable '(newsticker-previous-feed-available-p))) | ||
| 2203 | ;; standard icons / actions | ||
| 2204 | (tool-bar-add-item "close" | ||
| 2205 | 'newsticker-close-buffer | ||
| 2206 | 'newsticker-close-buffer | ||
| 2207 | :help "Close newsticker buffer") | ||
| 2208 | (tool-bar-add-item "preferences" | ||
| 2209 | 'newsticker-customize | ||
| 2210 | 'newsticker-customize | ||
| 2211 | :help "Customize newsticker") | ||
| 2212 | tool-bar-map))) | ||
| 2213 | |||
| 2214 | ;; ====================================================================== | ||
| 2215 | ;;; Newsticker mode | ||
| 2216 | ;; ====================================================================== | ||
| 2217 | |||
| 2218 | (define-derived-mode newsticker-mode fundamental-mode | ||
| 2219 | "NewsTicker" | ||
| 2220 | "Viewing news feeds in Emacs." | ||
| 2221 | (set (make-local-variable 'tool-bar-map) newsticker-tool-bar-map) | ||
| 2222 | (set (make-local-variable 'imenu-sort-function) nil) | ||
| 2223 | (set (make-local-variable 'scroll-conservatively) 999) | ||
| 2224 | (setq imenu-create-index-function 'newsticker--imenu-create-index) | ||
| 2225 | (setq imenu-default-goto-function 'newsticker--imenu-goto) | ||
| 2226 | (setq buffer-read-only t) | ||
| 2227 | (auto-fill-mode -1) ;; turn auto-fill off! | ||
| 2228 | (font-lock-mode -1) ;; turn off font-lock!! | ||
| 2229 | (set (make-local-variable 'font-lock-defaults) nil) | ||
| 2230 | (set (make-local-variable 'line-move-ignore-invisible) t) | ||
| 2231 | (setq mode-line-format | ||
| 2232 | (list "-" | ||
| 2233 | 'mode-line-mule-info | ||
| 2234 | 'mode-line-modified | ||
| 2235 | 'mode-line-frame-identification | ||
| 2236 | " Newsticker (" | ||
| 2237 | '(newsticker--buffer-uptodate-p | ||
| 2238 | "up to date" | ||
| 2239 | "NEED UPDATE") | ||
| 2240 | ") " | ||
| 2241 | '(:eval (format "[%d]" (length newsticker--process-ids))) | ||
| 2242 | " -- " | ||
| 2243 | '(:eval (newsticker--buffer-get-feed-title-at-point)) | ||
| 2244 | ": " | ||
| 2245 | '(:eval (newsticker--buffer-get-item-title-at-point)) | ||
| 2246 | " %-")) | ||
| 2247 | (add-to-invisibility-spec 't) | ||
| 2248 | (unless newsticker-show-all-news-elements | ||
| 2249 | (add-to-invisibility-spec 'extra)) | ||
| 2250 | (newsticker--buffer-set-uptodate nil)) | ||
| 2251 | |||
| 2252 | ;; refine its mode-map | ||
| 2253 | (define-key newsticker-mode-map "sO" 'newsticker-show-old-items) | ||
| 2254 | (define-key newsticker-mode-map "hO" 'newsticker-hide-old-items) | ||
| 2255 | (define-key newsticker-mode-map "sa" 'newsticker-show-all-desc) | ||
| 2256 | (define-key newsticker-mode-map "ha" 'newsticker-hide-all-desc) | ||
| 2257 | (define-key newsticker-mode-map "sf" 'newsticker-show-feed-desc) | ||
| 2258 | (define-key newsticker-mode-map "hf" 'newsticker-hide-feed-desc) | ||
| 2259 | (define-key newsticker-mode-map "so" 'newsticker-show-old-item-desc) | ||
| 2260 | (define-key newsticker-mode-map "ho" 'newsticker-hide-old-item-desc) | ||
| 2261 | (define-key newsticker-mode-map "sn" 'newsticker-show-new-item-desc) | ||
| 2262 | (define-key newsticker-mode-map "hn" 'newsticker-hide-new-item-desc) | ||
| 2263 | (define-key newsticker-mode-map "se" 'newsticker-show-entry) | ||
| 2264 | (define-key newsticker-mode-map "he" 'newsticker-hide-entry) | ||
| 2265 | (define-key newsticker-mode-map "sx" 'newsticker-show-extra) | ||
| 2266 | (define-key newsticker-mode-map "hx" 'newsticker-hide-extra) | ||
| 2267 | |||
| 2268 | (define-key newsticker-mode-map " " 'scroll-up) | ||
| 2269 | (define-key newsticker-mode-map "q" 'newsticker-close-buffer) | ||
| 2270 | (define-key newsticker-mode-map "p" 'newsticker-previous-item) | ||
| 2271 | (define-key newsticker-mode-map "P" 'newsticker-previous-new-item) | ||
| 2272 | (define-key newsticker-mode-map "F" 'newsticker-previous-feed) | ||
| 2273 | (define-key newsticker-mode-map "\t" 'newsticker-next-item) | ||
| 2274 | (define-key newsticker-mode-map "n" 'newsticker-next-item) | ||
| 2275 | (define-key newsticker-mode-map "N" 'newsticker-next-new-item) | ||
| 2276 | (define-key newsticker-mode-map "f" 'newsticker-next-feed) | ||
| 2277 | (define-key newsticker-mode-map "M" 'newsticker-mark-all-items-as-read) | ||
| 2278 | (define-key newsticker-mode-map "m" | ||
| 2279 | 'newsticker-mark-all-items-at-point-as-read-and-redraw) | ||
| 2280 | (define-key newsticker-mode-map "o" | ||
| 2281 | 'newsticker-mark-item-at-point-as-read) | ||
| 2282 | (define-key newsticker-mode-map "O" | ||
| 2283 | 'newsticker-mark-all-items-at-point-as-read) | ||
| 2284 | (define-key newsticker-mode-map "G" 'newsticker-get-all-news) | ||
| 2285 | (define-key newsticker-mode-map "g" 'newsticker-get-news-at-point) | ||
| 2286 | (define-key newsticker-mode-map "u" 'newsticker-buffer-update) | ||
| 2287 | (define-key newsticker-mode-map "U" 'newsticker-buffer-force-update) | ||
| 2288 | (define-key newsticker-mode-map "a" 'newsticker-add-url) | ||
| 2289 | |||
| 2290 | (define-key newsticker-mode-map "i" | ||
| 2291 | 'newsticker-mark-item-at-point-as-immortal) | ||
| 2292 | |||
| 2293 | (define-key newsticker-mode-map "xf" | ||
| 2294 | 'newsticker-toggle-auto-narrow-to-feed) | ||
| 2295 | (define-key newsticker-mode-map "xi" | ||
| 2296 | 'newsticker-toggle-auto-narrow-to-item) | ||
| 2297 | |||
| 2298 | ;; maps for the clickable portions | ||
| 2299 | (defvar newsticker--url-keymap (make-sparse-keymap) | ||
| 2300 | "Key map for click-able headings in the newsticker buffer.") | ||
| 2301 | (define-key newsticker--url-keymap [mouse-2] | ||
| 2302 | 'newsticker-mouse-browse-url) | ||
| 2303 | (define-key newsticker--url-keymap "\n" | ||
| 2304 | 'newsticker-browse-url) | ||
| 2305 | (define-key newsticker--url-keymap "\C-m" | ||
| 2306 | 'newsticker-browse-url) | ||
| 2307 | (define-key newsticker--url-keymap [(control return)] | ||
| 2308 | 'newsticker-handle-url) | ||
| 2309 | |||
| 2310 | ;; newsticker menu | ||
| 2311 | (defvar newsticker-menu (make-sparse-keymap "Newsticker")) | ||
| 2312 | |||
| 2313 | (define-key newsticker-menu [newsticker-browse-url] | ||
| 2314 | '("Browse URL for item at point" . newsticker-browse-url)) | ||
| 2315 | (define-key newsticker-menu [newsticker-separator-1] | ||
| 2316 | '("--")) | ||
| 2317 | (define-key newsticker-menu [newsticker-buffer-update] | ||
| 2318 | '("Update buffer" . newsticker-buffer-update)) | ||
| 2319 | (define-key newsticker-menu [newsticker-separator-2] | ||
| 2320 | '("--")) | ||
| 2321 | (define-key newsticker-menu [newsticker-get-all-news] | ||
| 2322 | '("Get news from all feeds" . newsticker-get-all-news)) | ||
| 2323 | (define-key newsticker-menu [newsticker-get-news-at-point] | ||
| 2324 | '("Get news from feed at point" . newsticker-get-news-at-point)) | ||
| 2325 | (define-key newsticker-menu [newsticker-separator-3] | ||
| 2326 | '("--")) | ||
| 2327 | (define-key newsticker-menu [newsticker-mark-all-items-as-read] | ||
| 2328 | '("Mark all items as read" . newsticker-mark-all-items-as-read)) | ||
| 2329 | (define-key newsticker-menu [newsticker-mark-all-items-at-point-as-read] | ||
| 2330 | '("Mark all items in feed at point as read" . | ||
| 2331 | newsticker-mark-all-items-at-point-as-read)) | ||
| 2332 | (define-key newsticker-menu [newsticker-mark-item-at-point-as-read] | ||
| 2333 | '("Mark item at point as read" . | ||
| 2334 | newsticker-mark-item-at-point-as-read)) | ||
| 2335 | (define-key newsticker-menu [newsticker-mark-item-at-point-as-immortal] | ||
| 2336 | '("Toggle immortality for item at point" . | ||
| 2337 | newsticker-mark-item-at-point-as-immortal)) | ||
| 2338 | (define-key newsticker-menu [newsticker-separator-4] | ||
| 2339 | '("--")) | ||
| 2340 | (define-key newsticker-menu [newsticker-toggle-auto-narrow-to-item] | ||
| 2341 | '("Narrow to single item" . newsticker-toggle-auto-narrow-to-item)) | ||
| 2342 | (define-key newsticker-menu [newsticker-toggle-auto-narrow-to-feed] | ||
| 2343 | '("Narrow to single news feed" . newsticker-toggle-auto-narrow-to-feed)) | ||
| 2344 | (define-key newsticker-menu [newsticker-hide-old-items] | ||
| 2345 | '("Hide old items" . newsticker-hide-old-items)) | ||
| 2346 | (define-key newsticker-menu [newsticker-show-old-items] | ||
| 2347 | '("Show old items" . newsticker-show-old-items)) | ||
| 2348 | (define-key newsticker-menu [newsticker-next-item] | ||
| 2349 | '("Go to next item" . newsticker-next-item)) | ||
| 2350 | (define-key newsticker-menu [newsticker-previous-item] | ||
| 2351 | '("Go to previous item" . newsticker-previous-item)) | ||
| 2352 | |||
| 2353 | ;; bind menu to mouse | ||
| 2354 | (define-key newsticker-mode-map [down-mouse-3] newsticker-menu) | ||
| 2355 | ;; Put menu in menu-bar | ||
| 2356 | (define-key newsticker-mode-map [menu-bar Newsticker] | ||
| 2357 | (cons "Newsticker" newsticker-menu)) | ||
| 2358 | |||
| 2359 | |||
| 2360 | ;; ====================================================================== | ||
| 2361 | ;;; shortcuts | ||
| 2362 | ;; ====================================================================== | ||
| 2363 | (defsubst newsticker--title (item) | ||
| 2364 | "Return title of ITEM." | ||
| 2365 | (nth 0 item)) | ||
| 2366 | (defsubst newsticker--desc (item) | ||
| 2367 | "Return description of ITEM." | ||
| 2368 | (nth 1 item)) | ||
| 2369 | (defsubst newsticker--link (item) | ||
| 2370 | "Return link of ITEM." | ||
| 2371 | (nth 2 item)) | ||
| 2372 | (defsubst newsticker--time (item) | ||
| 2373 | "Return time of ITEM." | ||
| 2374 | (nth 3 item)) | ||
| 2375 | (defsubst newsticker--age (item) | ||
| 2376 | "Return age of ITEM." | ||
| 2377 | (nth 4 item)) | ||
| 2378 | (defsubst newsticker--pos (item) | ||
| 2379 | "Return position/index of ITEM." | ||
| 2380 | (nth 5 item)) | ||
| 2381 | (defsubst newsticker--preformatted-contents (item) | ||
| 2382 | "Return pre-formatted text of ITEM." | ||
| 2383 | (nth 6 item)) | ||
| 2384 | (defsubst newsticker--preformatted-title (item) | ||
| 2385 | "Return pre-formatted title of ITEM." | ||
| 2386 | (nth 7 item)) | ||
| 2387 | (defsubst newsticker--extra (item) | ||
| 2388 | "Return extra attributes of ITEM." | ||
| 2389 | (nth 8 item)) | ||
| 2390 | (defsubst newsticker--guid (item) | ||
| 2391 | "Return guid of ITEM." | ||
| 2392 | (let ((guid (assoc 'guid (newsticker--extra item)))) | ||
| 2393 | (if (stringp guid) | ||
| 2394 | guid | ||
| 2395 | (car (xml-node-children guid))))) | ||
| 2396 | (defsubst newsticker--enclosure (item) | ||
| 2397 | "Return enclosure element of ITEM in the form \(...FIXME...\) or nil." | ||
| 2398 | (let ((enclosure (assoc 'enclosure (newsticker--extra item)))) | ||
| 2399 | (if enclosure | ||
| 2400 | (xml-node-attributes enclosure)))) | ||
| 2401 | |||
| 2402 | ;; ====================================================================== | ||
| 2403 | ;;; User fun | ||
| 2404 | ;; ====================================================================== | ||
| 2405 | |||
| 2406 | ;;;###autoload | ||
| 2407 | (defun newsticker-start (&optional do-not-complain-if-running) | ||
| 2408 | "Start the newsticker. | ||
| 2409 | Start the timers for display and retrieval. If the newsticker, i.e. the | ||
| 2410 | timers, are running already a warning message is printed unless | ||
| 2411 | DO-NOT-COMPLAIN-IF-RUNNING is not nil. | ||
| 2412 | Run `newsticker-start-hook' if newsticker was not running already." | ||
| 2413 | (interactive) | ||
| 2414 | (let ((running (newsticker-running-p))) | ||
| 2415 | ;; read old cache if it exists and newsticker is not running | ||
| 2416 | (unless running | ||
| 2417 | (let* ((coding-system-for-read 'utf-8) | ||
| 2418 | (buf (find-file-noselect newsticker-cache-filename))) | ||
| 2419 | (when buf | ||
| 2420 | (set-buffer buf) | ||
| 2421 | (goto-char (point-min)) | ||
| 2422 | (condition-case nil | ||
| 2423 | (setq newsticker--cache (read buf)) | ||
| 2424 | (error | ||
| 2425 | (message "Error while reading newsticker cache file!") | ||
| 2426 | (setq newsticker--cache nil)))))) | ||
| 2427 | ;; start retrieval timers -- for sake of simplicity we will start | ||
| 2428 | ;; one timer for each feed | ||
| 2429 | (mapc (lambda (item) | ||
| 2430 | (let* ((feed-name (car item)) | ||
| 2431 | (start-time (nth 2 item)) | ||
| 2432 | (interval (or (nth 3 item) | ||
| 2433 | newsticker-retrieval-interval)) | ||
| 2434 | (timer (assoc (car item) | ||
| 2435 | newsticker--retrieval-timer-list))) | ||
| 2436 | (if timer | ||
| 2437 | (or do-not-complain-if-running | ||
| 2438 | (message "Timer for %s is running already!" | ||
| 2439 | feed-name)) | ||
| 2440 | (newsticker--debug-msg "Starting timer for %s: %s, %d" | ||
| 2441 | feed-name start-time interval) | ||
| 2442 | ;; do not repeat retrieval if interval not positive | ||
| 2443 | (if (<= interval 0) | ||
| 2444 | (setq interval nil)) | ||
| 2445 | ;; Suddenly XEmacs doesn't like start-time 0 | ||
| 2446 | (if (or (not start-time) | ||
| 2447 | (and (numberp start-time) (= start-time 0))) | ||
| 2448 | (setq start-time 1)) | ||
| 2449 | ;; (message "start-time %s" start-time) | ||
| 2450 | (setq timer (run-at-time start-time interval | ||
| 2451 | 'newsticker-get-news feed-name)) | ||
| 2452 | (if interval | ||
| 2453 | (add-to-list 'newsticker--retrieval-timer-list | ||
| 2454 | (cons feed-name timer)))))) | ||
| 2455 | (append newsticker-url-list-defaults newsticker-url-list)) | ||
| 2456 | (unless running | ||
| 2457 | (run-hooks 'newsticker-start-hook) | ||
| 2458 | (message "Newsticker started!")))) | ||
| 2459 | |||
| 2460 | ;;;###autoload | ||
| 2461 | (defun newsticker-start-ticker () | ||
| 2462 | "Start newsticker's ticker (but not the news retrieval). | ||
| 2463 | Start display timer for the actual ticker if wanted and not | ||
| 2464 | running already." | ||
| 2465 | (interactive) | ||
| 2466 | (if (and (> newsticker-display-interval 0) | ||
| 2467 | (not newsticker--display-timer)) | ||
| 2468 | (setq newsticker--display-timer | ||
| 2469 | (run-at-time newsticker-display-interval | ||
| 2470 | newsticker-display-interval | ||
| 2471 | 'newsticker--display-tick)))) | ||
| 2472 | |||
| 2473 | (defun newsticker-stop () | ||
| 2474 | "Stop the newsticker and the newsticker-ticker. | ||
| 2475 | Cancel the timers for display and retrieval. Run `newsticker-stop-hook' | ||
| 2476 | if newsticker has been running." | ||
| 2477 | (interactive) | ||
| 2478 | (newsticker--cache-update t) | ||
| 2479 | (newsticker-stop-ticker) | ||
| 2480 | (when (newsticker-running-p) | ||
| 2481 | (mapc (lambda (name-and-timer) | ||
| 2482 | (cancel-timer (cdr name-and-timer))) | ||
| 2483 | newsticker--retrieval-timer-list) | ||
| 2484 | (setq newsticker--retrieval-timer-list nil) | ||
| 2485 | (run-hooks 'newsticker-stop-hook) | ||
| 2486 | (message "Newsticker stopped!"))) | ||
| 2487 | |||
| 2488 | (defun newsticker-stop-ticker () | ||
| 2489 | "Stop newsticker's ticker (but not the news retrieval)." | ||
| 2490 | (interactive) | ||
| 2491 | (when newsticker--display-timer | ||
| 2492 | (cancel-timer newsticker--display-timer) | ||
| 2493 | (setq newsticker--display-timer nil))) | ||
| 2494 | |||
| 2495 | ;; the functions we need for retrieval and display | ||
| 2496 | ;;;###autoload | ||
| 2497 | (defun newsticker-show-news () | ||
| 2498 | "Switch to newsticker buffer. You may want to bind this to a key." | ||
| 2499 | (interactive) | ||
| 2500 | (newsticker-start t) ;; will start only if not running | ||
| 2501 | (newsticker-buffer-update) | ||
| 2502 | (switch-to-buffer "*newsticker*")) | ||
| 2503 | |||
| 2504 | (defun newsticker-buffer-force-update () | ||
| 2505 | "Update the newsticker buffer, even if not necessary." | ||
| 2506 | (interactive) | ||
| 2507 | (newsticker-buffer-update t)) | ||
| 2508 | |||
| 2509 | (defun newsticker-buffer-update (&optional force) | ||
| 2510 | "Update the *newsticker* buffer. | ||
| 2511 | Unless FORCE is t this is done only if necessary, i.e. when the | ||
| 2512 | *newsticker* buffer is not up-to-date." | ||
| 2513 | (interactive) | ||
| 2514 | ;; bring cache data into proper order.... | ||
| 2515 | (newsticker--cache-sort) | ||
| 2516 | ;; fill buffer | ||
| 2517 | (save-excursion | ||
| 2518 | (let ((buf (get-buffer "*newsticker*"))) | ||
| 2519 | (if buf | ||
| 2520 | (switch-to-buffer buf) | ||
| 2521 | (switch-to-buffer (get-buffer-create "*newsticker*")) | ||
| 2522 | (newsticker--buffer-set-uptodate nil))) | ||
| 2523 | (when (or force | ||
| 2524 | (not newsticker--buffer-uptodate-p)) | ||
| 2525 | (message "Preparing newsticker buffer...") | ||
| 2526 | (setq buffer-undo-list t) | ||
| 2527 | (let ((inhibit-read-only t)) | ||
| 2528 | (set-buffer-modified-p nil) | ||
| 2529 | (erase-buffer) | ||
| 2530 | (newsticker-mode) | ||
| 2531 | ;; Emacs 21.3.50 does not care if we turn off auto-fill in the | ||
| 2532 | ;; definition of newsticker-mode, so we do it here (again) | ||
| 2533 | (auto-fill-mode -1) | ||
| 2534 | |||
| 2535 | (set-buffer-file-coding-system 'utf-8) | ||
| 2536 | |||
| 2537 | (if newsticker-use-full-width | ||
| 2538 | (set (make-local-variable 'fill-column) (1- (window-width)))) | ||
| 2539 | (newsticker--buffer-insert-all-items) | ||
| 2540 | |||
| 2541 | ;; FIXME: needed for methods buffer in ecb | ||
| 2542 | ;; (set-visited-file-name "*newsticker*") | ||
| 2543 | |||
| 2544 | (set-buffer-modified-p nil) | ||
| 2545 | (newsticker-hide-all-desc) | ||
| 2546 | (if newsticker-hide-old-items-in-newsticker-buffer | ||
| 2547 | (newsticker-hide-old-items)) | ||
| 2548 | (if newsticker-show-descriptions-of-new-items | ||
| 2549 | (newsticker-show-new-item-desc)) | ||
| 2550 | ) | ||
| 2551 | (message "")) | ||
| 2552 | (newsticker--buffer-set-uptodate t) | ||
| 2553 | (run-hooks 'newsticker-buffer-change-hook))) | ||
| 2554 | |||
| 2555 | (defun newsticker-get-all-news () | ||
| 2556 | "Launch retrieval of news from all configured newsticker sites. | ||
| 2557 | This does NOT start the retrieval timers." | ||
| 2558 | (interactive) | ||
| 2559 | ;; launch retrieval of news | ||
| 2560 | (mapc (lambda (item) | ||
| 2561 | (newsticker-get-news (car item))) | ||
| 2562 | (append newsticker-url-list-defaults newsticker-url-list))) | ||
| 2563 | |||
| 2564 | (defun newsticker-get-news-at-point () | ||
| 2565 | "Launch retrieval of news for the feed point is in. | ||
| 2566 | This does NOT start the retrieval timers." | ||
| 2567 | (interactive) | ||
| 2568 | ;; launch retrieval of news | ||
| 2569 | (let ((feed (get-text-property (point) 'feed))) | ||
| 2570 | (when feed | ||
| 2571 | (newsticker--debug-msg "Getting news for %s" (symbol-name feed)) | ||
| 2572 | (newsticker-get-news (symbol-name feed))))) | ||
| 2573 | |||
| 2574 | (defun newsticker-add-url (url name) | ||
| 2575 | "Add given URL under given NAME to `newsticker-url-list'. | ||
| 2576 | If URL is nil it is searched at point." | ||
| 2577 | (interactive | ||
| 2578 | (list | ||
| 2579 | (read-string "URL: " | ||
| 2580 | (save-excursion | ||
| 2581 | (end-of-line) | ||
| 2582 | (and | ||
| 2583 | (re-search-backward | ||
| 2584 | "http://" | ||
| 2585 | (if (> (point) (+ (point-min) 100)) | ||
| 2586 | (- (point) 100) | ||
| 2587 | (point-min)) | ||
| 2588 | t) | ||
| 2589 | (re-search-forward | ||
| 2590 | "http://[-a-zA-Z0-9&/_.]*" | ||
| 2591 | (if (< (point) (- (point-max) 200)) | ||
| 2592 | (+ (point) 200) | ||
| 2593 | (point-max)) | ||
| 2594 | t) | ||
| 2595 | (buffer-substring-no-properties (match-beginning 0) | ||
| 2596 | (match-end 0))))) | ||
| 2597 | (read-string "Name: "))) | ||
| 2598 | (add-to-list 'newsticker-url-list (list name url nil nil nil) t) | ||
| 2599 | (customize-variable 'newsticker-url-list)) | ||
| 2600 | |||
| 2601 | ;; External. | ||
| 2602 | (declare-function w3m-toggle-inline-image "ext:w3m" (&optional force no-cache)) | ||
| 2603 | |||
| 2604 | (defun newsticker-w3m-show-inline-images () | ||
| 2605 | "Show inline images in visible text ranges. | ||
| 2606 | In-line images in invisible text ranges are hidden. This function | ||
| 2607 | calls `w3m-toggle-inline-image'. It works only if | ||
| 2608 | `newsticker-html-renderer' is set to `w3m-region'." | ||
| 2609 | (interactive) | ||
| 2610 | (if (eq newsticker-html-renderer 'w3m-region) | ||
| 2611 | (let ((inhibit-read-only t)) | ||
| 2612 | (save-excursion | ||
| 2613 | (save-restriction | ||
| 2614 | (widen) | ||
| 2615 | (goto-char (point-min)) | ||
| 2616 | (let ((pos (point))) | ||
| 2617 | (while pos | ||
| 2618 | (setq pos (next-single-property-change pos 'w3m-image)) | ||
| 2619 | (when pos | ||
| 2620 | (goto-char pos) | ||
| 2621 | (when (get-text-property pos 'w3m-image) | ||
| 2622 | (let ((invis (newsticker--lists-intersect-p | ||
| 2623 | (get-text-property (1- (point)) | ||
| 2624 | 'invisible) | ||
| 2625 | buffer-invisibility-spec))) | ||
| 2626 | (unless (car (get-text-property (1- (point)) | ||
| 2627 | 'display)) | ||
| 2628 | (unless invis | ||
| 2629 | (w3m-toggle-inline-image t))))))))))))) | ||
| 2630 | |||
| 2631 | (defadvice w3m-insert-image (after newsticker activate) | ||
| 2632 | (newsticker--buffer-after-w3m-insert-image (ad-get-arg 0) (ad-get-arg 1))) | ||
| 2633 | |||
| 2634 | (defun newsticker--buffer-after-w3m-insert-image (beg end) | ||
| 2635 | "Save preformatted contents after an image has been inserted | ||
| 2636 | between BEG and END." | ||
| 2637 | (when (string= (buffer-name) "*newsticker*") | ||
| 2638 | (save-excursion | ||
| 2639 | (newsticker--buffer-beginning-of-item) | ||
| 2640 | (let* ((pos (point)) | ||
| 2641 | (feed (get-text-property pos 'feed)) | ||
| 2642 | (age (get-text-property pos 'nt-age)) | ||
| 2643 | (title (get-text-property pos 'nt-title)) | ||
| 2644 | (guid (get-text-property pos 'nt-guid)) | ||
| 2645 | (nt-desc (get-text-property pos 'nt-desc)) | ||
| 2646 | (item (newsticker--cache-contains newsticker--cache | ||
| 2647 | feed title nt-desc | ||
| 2648 | nil nil guid)) | ||
| 2649 | (desc-beg (newsticker--buffer-goto '(desc))) | ||
| 2650 | (desc-end (newsticker--buffer-end-of-item))) | ||
| 2651 | ;;(add-text-properties beg end (list nt-type desc)) | ||
| 2652 | (add-text-properties beg end (list 'invisible | ||
| 2653 | (get-text-property end 'invisible))) | ||
| 2654 | ;;(message "newsticker--buffer-after-w3m-insert-image at %s, %s: `%s'" | ||
| 2655 | ;; beg feed title) | ||
| 2656 | (if item | ||
| 2657 | (newsticker--cache-set-preformatted-contents | ||
| 2658 | item (buffer-substring desc-beg desc-end)) | ||
| 2659 | (message "ooops in newsticker--buffer-after-w3m-insert-image at %s, %s: `%s'" | ||
| 2660 | beg feed title)))))) | ||
| 2661 | |||
| 2662 | ;; ====================================================================== | ||
| 2663 | ;;; keymap stuff | ||
| 2664 | ;; ====================================================================== | ||
| 2665 | (defun newsticker-close-buffer () | ||
| 2666 | "Close the newsticker buffer." | ||
| 2667 | (interactive) | ||
| 2668 | (newsticker--cache-update t) | ||
| 2669 | (bury-buffer)) | ||
| 2670 | |||
| 2671 | (defun newsticker-next-new-item (&optional do-not-wrap-at-eob) | ||
| 2672 | "Go to next new news item. | ||
| 2673 | If no new item is found behind point, search is continued at | ||
| 2674 | beginning of buffer unless optional argument DO-NOT-WRAP-AT-EOB | ||
| 2675 | is non-nil." | ||
| 2676 | (interactive) | ||
| 2677 | (widen) | ||
| 2678 | (let ((go-ahead t)) | ||
| 2679 | (while go-ahead | ||
| 2680 | (unless (newsticker--buffer-goto '(item) 'new) | ||
| 2681 | ;; found nothing -- wrap | ||
| 2682 | (unless do-not-wrap-at-eob | ||
| 2683 | (goto-char (point-min)) | ||
| 2684 | (newsticker-next-new-item t)) | ||
| 2685 | (setq go-ahead nil)) | ||
| 2686 | (unless (newsticker--lists-intersect-p | ||
| 2687 | (get-text-property (point) 'invisible) | ||
| 2688 | buffer-invisibility-spec) | ||
| 2689 | ;; this item is invisible -- continue search | ||
| 2690 | (setq go-ahead nil)))) | ||
| 2691 | (run-hooks 'newsticker-select-item-hook) | ||
| 2692 | (point)) | ||
| 2693 | |||
| 2694 | (defun newsticker-previous-new-item (&optional do-not-wrap-at-bob) | ||
| 2695 | "Go to previous new news item. | ||
| 2696 | If no new item is found before point, search is continued at | ||
| 2697 | beginning of buffer unless optional argument DO-NOT-WRAP-AT-BOB | ||
| 2698 | is non-nil." | ||
| 2699 | (interactive) | ||
| 2700 | (widen) | ||
| 2701 | (let ((go-ahead t)) | ||
| 2702 | (while go-ahead | ||
| 2703 | (unless (newsticker--buffer-goto '(item) 'new t) | ||
| 2704 | (unless do-not-wrap-at-bob | ||
| 2705 | (goto-char (point-max)) | ||
| 2706 | (newsticker--buffer-goto '(item) 'new t))) | ||
| 2707 | (unless (newsticker--lists-intersect-p | ||
| 2708 | (get-text-property (point) 'invisible) | ||
| 2709 | buffer-invisibility-spec) | ||
| 2710 | (setq go-ahead nil)))) | ||
| 2711 | (run-hooks 'newsticker-select-item-hook) | ||
| 2712 | (point)) | ||
| 2713 | |||
| 2714 | (defun newsticker-next-item (&optional do-not-wrap-at-eob) | ||
| 2715 | "Go to next news item. | ||
| 2716 | Return new buffer position. | ||
| 2717 | If no item is found below point, search is continued at beginning | ||
| 2718 | of buffer unless optional argument DO-NOT-WRAP-AT-EOB is | ||
| 2719 | non-nil." | ||
| 2720 | (interactive) | ||
| 2721 | (widen) | ||
| 2722 | (let ((go-ahead t) | ||
| 2723 | (search-list '(item))) | ||
| 2724 | (if newsticker--auto-narrow-to-item | ||
| 2725 | (setq search-list '(item feed))) | ||
| 2726 | (while go-ahead | ||
| 2727 | (unless (newsticker--buffer-goto search-list) | ||
| 2728 | ;; found nothing -- wrap | ||
| 2729 | (unless do-not-wrap-at-eob | ||
| 2730 | (goto-char (point-min))) | ||
| 2731 | (setq go-ahead nil)) | ||
| 2732 | (unless (newsticker--lists-intersect-p | ||
| 2733 | (get-text-property (point) 'invisible) | ||
| 2734 | buffer-invisibility-spec) | ||
| 2735 | (setq go-ahead nil)))) | ||
| 2736 | (run-hooks 'newsticker-select-item-hook) | ||
| 2737 | (force-mode-line-update) | ||
| 2738 | (point)) | ||
| 2739 | |||
| 2740 | (defun newsticker-next-item-same-feed () | ||
| 2741 | "Go to next news item in the same feed. | ||
| 2742 | Return new buffer position. If no item is found below point or if | ||
| 2743 | auto-narrow-to-item is enabled, nil is returned." | ||
| 2744 | (interactive) | ||
| 2745 | (if newsticker--auto-narrow-to-item | ||
| 2746 | nil | ||
| 2747 | (let ((go-ahead t) | ||
| 2748 | (current-pos (point)) | ||
| 2749 | (end-of-feed (save-excursion (newsticker--buffer-end-of-feed)))) | ||
| 2750 | (while go-ahead | ||
| 2751 | (unless (newsticker--buffer-goto '(item)) | ||
| 2752 | (setq go-ahead nil)) | ||
| 2753 | (unless (newsticker--lists-intersect-p | ||
| 2754 | (get-text-property (point) 'invisible) | ||
| 2755 | buffer-invisibility-spec) | ||
| 2756 | (setq go-ahead nil))) | ||
| 2757 | (if (and (> (point) current-pos) | ||
| 2758 | (< (point) end-of-feed)) | ||
| 2759 | (point) | ||
| 2760 | (goto-char current-pos) | ||
| 2761 | nil)))) | ||
| 2762 | |||
| 2763 | (defun newsticker-previous-item (&optional do-not-wrap-at-bob) | ||
| 2764 | "Go to previous news item. | ||
| 2765 | Return new buffer position. | ||
| 2766 | If no item is found before point, search is continued at | ||
| 2767 | beginning of buffer unless optional argument DO-NOT-WRAP-AT-BOB | ||
| 2768 | is non-nil." | ||
| 2769 | (interactive) | ||
| 2770 | (widen) | ||
| 2771 | (let ((go-ahead t) | ||
| 2772 | (search-list '(item))) | ||
| 2773 | (if newsticker--auto-narrow-to-item | ||
| 2774 | (setq search-list '(item feed))) | ||
| 2775 | (when (bobp) | ||
| 2776 | (unless do-not-wrap-at-bob | ||
| 2777 | (goto-char (point-max)))) | ||
| 2778 | (while go-ahead | ||
| 2779 | (if (newsticker--buffer-goto search-list nil t) | ||
| 2780 | (unless (newsticker--lists-intersect-p | ||
| 2781 | (get-text-property (point) 'invisible) | ||
| 2782 | buffer-invisibility-spec) | ||
| 2783 | (setq go-ahead nil)) | ||
| 2784 | (goto-char (point-min)) | ||
| 2785 | (setq go-ahead nil)))) | ||
| 2786 | (run-hooks 'newsticker-select-item-hook) | ||
| 2787 | (force-mode-line-update) | ||
| 2788 | (point)) | ||
| 2789 | |||
| 2790 | (defun newsticker-next-feed () | ||
| 2791 | "Go to next news feed. | ||
| 2792 | Return new buffer position." | ||
| 2793 | (interactive) | ||
| 2794 | (widen) | ||
| 2795 | (newsticker--buffer-goto '(feed)) | ||
| 2796 | (run-hooks 'newsticker-select-feed-hook) | ||
| 2797 | (force-mode-line-update) | ||
| 2798 | (point)) | ||
| 2799 | |||
| 2800 | (defun newsticker-previous-feed () | ||
| 2801 | "Go to previous news feed. | ||
| 2802 | Return new buffer position." | ||
| 2803 | (interactive) | ||
| 2804 | (widen) | ||
| 2805 | (newsticker--buffer-goto '(feed) nil t) | ||
| 2806 | (run-hooks 'newsticker-select-feed-hook) | ||
| 2807 | (force-mode-line-update) | ||
| 2808 | (point)) | ||
| 2809 | |||
| 2810 | (defun newsticker-mark-all-items-at-point-as-read-and-redraw () | ||
| 2811 | "Mark all items as read and clear ticker contents." | ||
| 2812 | (interactive) | ||
| 2813 | (when (or newsticker--buffer-uptodate-p | ||
| 2814 | (y-or-n-p | ||
| 2815 | "Buffer is not up to date -- really mark items as read? ")) | ||
| 2816 | (newsticker-mark-all-items-of-feed-as-read | ||
| 2817 | (get-text-property (point) 'feed)))) | ||
| 2818 | |||
| 2819 | (defun newsticker-mark-all-items-of-feed-as-read (feed) | ||
| 2820 | "Mark all items as read, clear ticker, and redraw *newsticker* buffer." | ||
| 2821 | (when feed | ||
| 2822 | (let ((pos (point))) | ||
| 2823 | (message "Marking all items as read for %s" (symbol-name feed)) | ||
| 2824 | (newsticker--cache-replace-age newsticker--cache feed 'new 'old) | ||
| 2825 | (newsticker--cache-replace-age newsticker--cache feed 'obsolete | ||
| 2826 | 'old) | ||
| 2827 | (newsticker--cache-update) | ||
| 2828 | (newsticker--buffer-set-uptodate nil) | ||
| 2829 | (newsticker--ticker-text-setup) | ||
| 2830 | (newsticker-buffer-update) | ||
| 2831 | ;; go back to where we came frome | ||
| 2832 | (goto-char pos) | ||
| 2833 | (end-of-line) | ||
| 2834 | (newsticker--buffer-goto '(feed) nil t)))) | ||
| 2835 | |||
| 2836 | (defun newsticker-mark-all-items-at-point-as-read () | ||
| 2837 | "Mark all items as read and clear ticker contents." | ||
| 2838 | (interactive) | ||
| 2839 | (when (or newsticker--buffer-uptodate-p | ||
| 2840 | (y-or-n-p | ||
| 2841 | "Buffer is not up to date -- really mark items as read? ")) | ||
| 2842 | (newsticker--do-mark-item-at-point-as-read t) | ||
| 2843 | (while (newsticker-next-item-same-feed) | ||
| 2844 | (newsticker--do-mark-item-at-point-as-read t)) | ||
| 2845 | (newsticker-next-item t))) | ||
| 2846 | |||
| 2847 | (defun newsticker-mark-item-at-point-as-read (&optional respect-immortality) | ||
| 2848 | "Mark item at point as read and move to next item. | ||
| 2849 | If optional argument RESPECT-IMMORTALITY is not nil immortal items do | ||
| 2850 | not get changed." | ||
| 2851 | (interactive) | ||
| 2852 | (when (or newsticker--buffer-uptodate-p | ||
| 2853 | (y-or-n-p | ||
| 2854 | "Buffer is not up to date -- really mark this item as read? ")) | ||
| 2855 | (newsticker--do-mark-item-at-point-as-read respect-immortality) | ||
| 2856 | ;; move forward | ||
| 2857 | (newsticker-next-item t))) | ||
| 2858 | |||
| 2859 | (defun newsticker--do-mark-item-at-point-as-read (&optional respect-immortality) | ||
| 2860 | "Mark item at point as read. | ||
| 2861 | If optional argument RESPECT-IMMORTALITY is not nil immortal items do | ||
| 2862 | not get changed." | ||
| 2863 | (let ((feed (get-text-property (point) 'feed))) | ||
| 2864 | (when feed | ||
| 2865 | (save-excursion | ||
| 2866 | (newsticker--buffer-beginning-of-item) | ||
| 2867 | (let ((inhibit-read-only t) | ||
| 2868 | (age (get-text-property (point) 'nt-age)) | ||
| 2869 | (title (get-text-property (point) 'nt-title)) | ||
| 2870 | (guid (get-text-property (point) 'nt-guid)) | ||
| 2871 | (nt-desc (get-text-property (point) 'nt-desc)) | ||
| 2872 | (pos (save-excursion (newsticker--buffer-end-of-item))) | ||
| 2873 | item) | ||
| 2874 | (when (or (eq age 'new) | ||
| 2875 | (eq age 'obsolete) | ||
| 2876 | (and (eq age 'immortal) | ||
| 2877 | (not respect-immortality))) | ||
| 2878 | ;; find item | ||
| 2879 | (setq item (newsticker--cache-contains newsticker--cache | ||
| 2880 | feed title nt-desc | ||
| 2881 | nil nil guid)) | ||
| 2882 | ;; mark as old | ||
| 2883 | (when item | ||
| 2884 | (setcar (nthcdr 4 item) 'old) | ||
| 2885 | (newsticker--do-forget-preformatted item)) | ||
| 2886 | ;; clean up ticker | ||
| 2887 | (if (or (and (eq age 'new) | ||
| 2888 | newsticker-hide-immortal-items-in-echo-area) | ||
| 2889 | (and (memq age '(old immortal)) | ||
| 2890 | (not | ||
| 2891 | (eq newsticker-hide-old-items-in-newsticker-buffer | ||
| 2892 | newsticker-hide-immortal-items-in-echo-area)))) | ||
| 2893 | (newsticker--ticker-text-remove feed title)) | ||
| 2894 | ;; set faces etc. | ||
| 2895 | (save-excursion | ||
| 2896 | (save-restriction | ||
| 2897 | (widen) | ||
| 2898 | (put-text-property (point) pos 'nt-age 'old) | ||
| 2899 | (newsticker--buffer-set-faces (point) pos))) | ||
| 2900 | (set-buffer-modified-p nil))))))) | ||
| 2901 | |||
| 2902 | (defun newsticker-mark-item-at-point-as-immortal () | ||
| 2903 | "Mark item at point as read." | ||
| 2904 | (interactive) | ||
| 2905 | (when (or newsticker--buffer-uptodate-p | ||
| 2906 | (y-or-n-p | ||
| 2907 | "Buffer is not up to date -- really mark this item as read? ")) | ||
| 2908 | (let ((feed (get-text-property (point) 'feed)) | ||
| 2909 | (item nil)) | ||
| 2910 | (when feed | ||
| 2911 | (save-excursion | ||
| 2912 | (newsticker--buffer-beginning-of-item) | ||
| 2913 | (let ((inhibit-read-only t) | ||
| 2914 | (oldage (get-text-property (point) 'nt-age)) | ||
| 2915 | (title (get-text-property (point) 'nt-title)) | ||
| 2916 | (guid (get-text-property (point) 'nt-guid)) | ||
| 2917 | (pos (save-excursion (newsticker--buffer-end-of-item)))) | ||
| 2918 | (let ((newage 'immortal)) | ||
| 2919 | (if (eq oldage 'immortal) | ||
| 2920 | (setq newage 'old)) | ||
| 2921 | (setq item (newsticker--cache-contains newsticker--cache | ||
| 2922 | feed title nil nil nil | ||
| 2923 | guid)) | ||
| 2924 | ;; change age | ||
| 2925 | (when item | ||
| 2926 | (setcar (nthcdr 4 item) newage) | ||
| 2927 | (newsticker--do-forget-preformatted item)) | ||
| 2928 | (if (or (and (eq newage 'immortal) | ||
| 2929 | newsticker-hide-immortal-items-in-echo-area) | ||
| 2930 | (and (eq newage 'obsolete) | ||
| 2931 | newsticker-hide-obsolete-items-in-echo-area) | ||
| 2932 | (and (eq oldage 'immortal) | ||
| 2933 | (not | ||
| 2934 | (eq newsticker-hide-old-items-in-newsticker-buffer | ||
| 2935 | newsticker-hide-immortal-items-in-echo-area)))) | ||
| 2936 | (newsticker--ticker-text-remove feed title) | ||
| 2937 | (newsticker--ticker-text-setup)) | ||
| 2938 | (save-excursion | ||
| 2939 | (save-restriction | ||
| 2940 | (widen) | ||
| 2941 | (put-text-property (point) pos 'nt-age newage) | ||
| 2942 | (if (eq newage 'immortal) | ||
| 2943 | (put-text-property (point) pos 'nt-age 'immortal) | ||
| 2944 | (put-text-property (point) pos 'nt-age 'old)) | ||
| 2945 | (newsticker--buffer-set-faces (point) pos)))))) | ||
| 2946 | (if item | ||
| 2947 | (newsticker-next-item t)))))) | ||
| 2948 | |||
| 2949 | (defun newsticker-mark-all-items-as-read () | ||
| 2950 | "Mark all items as read and clear ticker contents." | ||
| 2951 | (interactive) | ||
| 2952 | (when (or newsticker--buffer-uptodate-p | ||
| 2953 | (y-or-n-p | ||
| 2954 | "Buffer is not up to date -- really mark items as read? ")) | ||
| 2955 | (newsticker--cache-replace-age newsticker--cache 'any 'new 'old) | ||
| 2956 | (newsticker--buffer-set-uptodate nil) | ||
| 2957 | (newsticker--ticker-text-setup) | ||
| 2958 | (newsticker--cache-update) | ||
| 2959 | (newsticker-buffer-update))) | ||
| 2960 | |||
| 2961 | (defun newsticker-hide-extra () | ||
| 2962 | "Hide the extra elements of items." | ||
| 2963 | (interactive) | ||
| 2964 | (newsticker--buffer-hideshow 'extra nil) | ||
| 2965 | (newsticker--buffer-redraw)) | ||
| 2966 | |||
| 2967 | (defun newsticker-show-extra () | ||
| 2968 | "Show the extra elements of items." | ||
| 2969 | (interactive) | ||
| 2970 | (newsticker--buffer-hideshow 'extra t) | ||
| 2971 | (newsticker--buffer-redraw)) | ||
| 2972 | |||
| 2973 | (defun newsticker-hide-old-item-desc () | ||
| 2974 | "Hide the description of old items." | ||
| 2975 | (interactive) | ||
| 2976 | (newsticker--buffer-hideshow 'desc-old nil) | ||
| 2977 | (newsticker--buffer-redraw)) | ||
| 2978 | |||
| 2979 | (defun newsticker-show-old-item-desc () | ||
| 2980 | "Show the description of old items." | ||
| 2981 | (interactive) | ||
| 2982 | (newsticker--buffer-hideshow 'item-old t) | ||
| 2983 | (newsticker--buffer-hideshow 'desc-old t) | ||
| 2984 | (newsticker--buffer-redraw)) | ||
| 2985 | |||
| 2986 | (defun newsticker-hide-new-item-desc () | ||
| 2987 | "Hide the description of new items." | ||
| 2988 | (interactive) | ||
| 2989 | (newsticker--buffer-hideshow 'desc-new nil) | ||
| 2990 | (newsticker--buffer-hideshow 'desc-immortal nil) | ||
| 2991 | (newsticker--buffer-hideshow 'desc-obsolete nil) | ||
| 2992 | (newsticker--buffer-redraw)) | ||
| 2993 | |||
| 2994 | (defun newsticker-show-new-item-desc () | ||
| 2995 | "Show the description of new items." | ||
| 2996 | (interactive) | ||
| 2997 | (newsticker--buffer-hideshow 'desc-new t) | ||
| 2998 | (newsticker--buffer-hideshow 'desc-immortal t) | ||
| 2999 | (newsticker--buffer-hideshow 'desc-obsolete t) | ||
| 3000 | (newsticker--buffer-redraw)) | ||
| 3001 | |||
| 3002 | (defun newsticker-hide-feed-desc () | ||
| 3003 | "Hide the description of feeds." | ||
| 3004 | (interactive) | ||
| 3005 | (newsticker--buffer-hideshow 'desc-feed nil) | ||
| 3006 | (newsticker--buffer-redraw)) | ||
| 3007 | |||
| 3008 | (defun newsticker-show-feed-desc () | ||
| 3009 | "Show the description of old items." | ||
| 3010 | (interactive) | ||
| 3011 | (newsticker--buffer-hideshow 'desc-feed t) | ||
| 3012 | (newsticker--buffer-redraw)) | ||
| 3013 | |||
| 3014 | (defun newsticker-hide-all-desc () | ||
| 3015 | "Hide the descriptions of feeds and all items." | ||
| 3016 | (interactive) | ||
| 3017 | (newsticker--buffer-hideshow 'desc-feed nil) | ||
| 3018 | (newsticker--buffer-hideshow 'desc-immortal nil) | ||
| 3019 | (newsticker--buffer-hideshow 'desc-obsolete nil) | ||
| 3020 | (newsticker--buffer-hideshow 'desc-new nil) | ||
| 3021 | (newsticker--buffer-hideshow 'desc-old nil) | ||
| 3022 | (newsticker--buffer-redraw)) | ||
| 3023 | |||
| 3024 | (defun newsticker-show-all-desc () | ||
| 3025 | "Show the descriptions of feeds and all items." | ||
| 3026 | (interactive) | ||
| 3027 | (newsticker--buffer-hideshow 'desc-feed t) | ||
| 3028 | (newsticker--buffer-hideshow 'desc-immortal t) | ||
| 3029 | (newsticker--buffer-hideshow 'desc-obsolete t) | ||
| 3030 | (newsticker--buffer-hideshow 'desc-new t) | ||
| 3031 | (newsticker--buffer-hideshow 'desc-old t) | ||
| 3032 | (newsticker--buffer-redraw)) | ||
| 3033 | |||
| 3034 | (defun newsticker-hide-old-items () | ||
| 3035 | "Hide old items." | ||
| 3036 | (interactive) | ||
| 3037 | (newsticker--buffer-hideshow 'desc-old nil) | ||
| 3038 | (newsticker--buffer-hideshow 'item-old nil) | ||
| 3039 | (newsticker--buffer-redraw)) | ||
| 3040 | |||
| 3041 | (defun newsticker-show-old-items () | ||
| 3042 | "Show old items." | ||
| 3043 | (interactive) | ||
| 3044 | (newsticker--buffer-hideshow 'item-old t) | ||
| 3045 | (newsticker--buffer-redraw)) | ||
| 3046 | |||
| 3047 | (defun newsticker-hide-entry () | ||
| 3048 | "Hide description of entry at point." | ||
| 3049 | (interactive) | ||
| 3050 | (save-excursion | ||
| 3051 | (let* (pos1 pos2 | ||
| 3052 | (inhibit-read-only t) | ||
| 3053 | inv-prop org-inv-prop | ||
| 3054 | is-invisible) | ||
| 3055 | (newsticker--buffer-beginning-of-item) | ||
| 3056 | (newsticker--buffer-goto '(desc)) | ||
| 3057 | (setq pos1 (max (point-min) (1- (point)))) | ||
| 3058 | (newsticker--buffer-goto '(extra feed item nil)) | ||
| 3059 | (setq pos2 (max (point-min) (1- (point)))) | ||
| 3060 | (setq inv-prop (get-text-property pos1 'invisible)) | ||
| 3061 | (setq org-inv-prop (get-text-property pos1 'org-invisible)) | ||
| 3062 | (cond ((eq inv-prop t) | ||
| 3063 | ;; do nothing | ||
| 3064 | ) | ||
| 3065 | ((eq org-inv-prop nil) | ||
| 3066 | (add-text-properties pos1 pos2 | ||
| 3067 | (list 'invisible (list t) | ||
| 3068 | 'org-invisible inv-prop))) | ||
| 3069 | (t | ||
| 3070 | ;; toggle | ||
| 3071 | (add-text-properties pos1 pos2 | ||
| 3072 | (list 'invisible org-inv-prop)) | ||
| 3073 | (remove-text-properties pos1 pos2 '(org-invisible)))))) | ||
| 3074 | (newsticker--buffer-redraw)) | ||
| 3075 | |||
| 3076 | (defun newsticker-show-entry () | ||
| 3077 | "Show description of entry at point." | ||
| 3078 | (interactive) | ||
| 3079 | (save-excursion | ||
| 3080 | (let* (pos1 pos2 | ||
| 3081 | (inhibit-read-only t) | ||
| 3082 | inv-prop org-inv-prop | ||
| 3083 | is-invisible) | ||
| 3084 | (newsticker--buffer-beginning-of-item) | ||
| 3085 | (newsticker--buffer-goto '(desc)) | ||
| 3086 | (setq pos1 (max (point-min) (1- (point)))) | ||
| 3087 | (newsticker--buffer-goto '(extra feed item)) | ||
| 3088 | (setq pos2 (max (point-min) (1- (point)))) | ||
| 3089 | (setq inv-prop (get-text-property pos1 'invisible)) | ||
| 3090 | (setq org-inv-prop (get-text-property pos1 'org-invisible)) | ||
| 3091 | (cond ((eq org-inv-prop nil) | ||
| 3092 | (add-text-properties pos1 pos2 | ||
| 3093 | (list 'invisible nil | ||
| 3094 | 'org-invisible inv-prop))) | ||
| 3095 | (t | ||
| 3096 | ;; toggle | ||
| 3097 | (add-text-properties pos1 pos2 | ||
| 3098 | (list 'invisible org-inv-prop)) | ||
| 3099 | (remove-text-properties pos1 pos2 '(org-invisible)))))) | ||
| 3100 | (newsticker--buffer-redraw)) | ||
| 3101 | |||
| 3102 | (defun newsticker-toggle-auto-narrow-to-feed () | ||
| 3103 | "Toggle narrowing to current news feed. | ||
| 3104 | If auto-narrowing is active, only news item of the current feed | ||
| 3105 | are visible." | ||
| 3106 | (interactive) | ||
| 3107 | (newsticker-set-auto-narrow-to-feed | ||
| 3108 | (not newsticker--auto-narrow-to-feed))) | ||
| 3109 | |||
| 3110 | (defun newsticker-set-auto-narrow-to-feed (value) | ||
| 3111 | "Turn narrowing to current news feed on or off. | ||
| 3112 | If VALUE is nil, auto-narrowing is turned off, otherwise it is turned on." | ||
| 3113 | (interactive) | ||
| 3114 | (setq newsticker--auto-narrow-to-item nil) | ||
| 3115 | (setq newsticker--auto-narrow-to-feed value) | ||
| 3116 | (widen) | ||
| 3117 | (newsticker--buffer-make-item-completely-visible) | ||
| 3118 | (run-hooks 'newsticker-narrow-hook)) | ||
| 3119 | |||
| 3120 | (defun newsticker-toggle-auto-narrow-to-item () | ||
| 3121 | "Toggle narrowing to current news item. | ||
| 3122 | If auto-narrowing is active, only one item of the current feed | ||
| 3123 | is visible." | ||
| 3124 | (interactive) | ||
| 3125 | (newsticker-set-auto-narrow-to-item | ||
| 3126 | (not newsticker--auto-narrow-to-item))) | ||
| 3127 | |||
| 3128 | (defun newsticker-set-auto-narrow-to-item (value) | ||
| 3129 | "Turn narrowing to current news item on or off. | ||
| 3130 | If VALUE is nil, auto-narrowing is turned off, otherwise it is turned on." | ||
| 3131 | (interactive) | ||
| 3132 | (setq newsticker--auto-narrow-to-feed nil) | ||
| 3133 | (setq newsticker--auto-narrow-to-item value) | ||
| 3134 | (widen) | ||
| 3135 | (newsticker--buffer-make-item-completely-visible) | ||
| 3136 | (run-hooks 'newsticker-narrow-hook)) | ||
| 3137 | |||
| 3138 | (defun newsticker-customize () | ||
| 3139 | "Open the newsticker customization group." | ||
| 3140 | (interactive) | ||
| 3141 | (customize-group "newsticker")) | ||
| 3142 | |||
| 3143 | (defun newsticker-next-feed-available-p () | ||
| 3144 | "Return t if position is before last feed, nil otherwise." | ||
| 3145 | (save-excursion | ||
| 3146 | (let ((p (point))) | ||
| 3147 | (newsticker--buffer-goto '(feed)) | ||
| 3148 | (not (= p (point)))))) | ||
| 3149 | |||
| 3150 | (defun newsticker-previous-feed-available-p () | ||
| 3151 | "Return t if position is behind first feed, nil otherwise." | ||
| 3152 | (save-excursion | ||
| 3153 | (let ((p (point))) | ||
| 3154 | (newsticker--buffer-goto '(feed) nil t) | ||
| 3155 | (not (= p (point)))))) | ||
| 3156 | |||
| 3157 | (defun newsticker-next-item-available-p () | ||
| 3158 | "Return t if position is before last feed, nil otherwise." | ||
| 3159 | (save-excursion | ||
| 3160 | (catch 'result | ||
| 3161 | (while (< (point) (point-max)) | ||
| 3162 | (unless (newsticker--buffer-goto '(item)) | ||
| 3163 | (throw 'result nil)) | ||
| 3164 | (unless (newsticker--lists-intersect-p | ||
| 3165 | (get-text-property (point) 'invisible) | ||
| 3166 | buffer-invisibility-spec) | ||
| 3167 | (throw 'result t)))))) | ||
| 3168 | |||
| 3169 | (defun newsticker-previous-item-available-p () | ||
| 3170 | "Return t if position is behind first item, nil otherwise." | ||
| 3171 | (save-excursion | ||
| 3172 | (catch 'result | ||
| 3173 | (while (> (point) (point-min)) | ||
| 3174 | (unless (newsticker--buffer-goto '(item) nil t) | ||
| 3175 | (throw 'result nil)) | ||
| 3176 | (unless (newsticker--lists-intersect-p | ||
| 3177 | (get-text-property (point) 'invisible) | ||
| 3178 | buffer-invisibility-spec) | ||
| 3179 | (throw 'result t)))))) | ||
| 3180 | |||
| 3181 | (defun newsticker-item-not-old-p () | ||
| 3182 | "Return t if there is an item at point which is not old, nil otherwise." | ||
| 3183 | (when (get-text-property (point) 'feed) | ||
| 3184 | (save-excursion | ||
| 3185 | (newsticker--buffer-beginning-of-item) | ||
| 3186 | (let ((age (get-text-property (point) 'nt-age))) | ||
| 3187 | (and (memq age '(new immortal obsolete)) t))))) | ||
| 3188 | |||
| 3189 | (defun newsticker-item-not-immortal-p () | ||
| 3190 | "Return t if there is an item at point which is not immortal, nil otherwise." | ||
| 3191 | (when (get-text-property (point) 'feed) | ||
| 3192 | (save-excursion | ||
| 3193 | (newsticker--buffer-beginning-of-item) | ||
| 3194 | (let ((age (get-text-property (point) 'nt-age))) | ||
| 3195 | (and (memq age '(new old obsolete)) t))))) | ||
| 3196 | |||
| 3197 | ;; ====================================================================== | ||
| 3198 | ;;; local stuff | ||
| 3199 | ;; ====================================================================== | ||
| 3200 | (defun newsticker-get-news (feed-name) | ||
| 3201 | "Get news from the site FEED-NAME and load feed logo. | ||
| 3202 | FEED-NAME must be a string which occurs as the label (i.e. the first element) | ||
| 3203 | in an element of `newsticker-url-list' or `newsticker-url-list-defaults'." | ||
| 3204 | (newsticker--debug-msg "%s: Getting news for %s" | ||
| 3205 | (format-time-string "%A, %H:%M" (current-time)) | ||
| 3206 | feed-name) | ||
| 3207 | (let* ((buffername (concat " *newsticker-wget-" feed-name "*")) | ||
| 3208 | (item (or (assoc feed-name newsticker-url-list) | ||
| 3209 | (assoc feed-name newsticker-url-list-defaults) | ||
| 3210 | (error | ||
| 3211 | "Cannot get news for %s: Check newsticker-url-list" | ||
| 3212 | feed-name))) | ||
| 3213 | (url (cadr item)) | ||
| 3214 | (wget-arguments (or (car (cdr (cdr (cdr (cdr item))))) | ||
| 3215 | newsticker-wget-arguments))) | ||
| 3216 | (save-excursion | ||
| 3217 | (set-buffer (get-buffer-create buffername)) | ||
| 3218 | (erase-buffer) | ||
| 3219 | ;; throw an error if there is an old wget-process around | ||
| 3220 | (if (get-process feed-name) | ||
| 3221 | (error "Another wget-process is running for %s" feed-name)) | ||
| 3222 | ;; start wget | ||
| 3223 | (let* ((args (append wget-arguments (list url))) | ||
| 3224 | (proc (apply 'start-process feed-name buffername | ||
| 3225 | newsticker-wget-name args))) | ||
| 3226 | (set-process-coding-system proc 'no-conversion 'no-conversion) | ||
| 3227 | (set-process-sentinel proc 'newsticker--sentinel) | ||
| 3228 | (setq newsticker--process-ids (cons (process-id proc) | ||
| 3229 | newsticker--process-ids)) | ||
| 3230 | (force-mode-line-update))))) | ||
| 3231 | |||
| 3232 | (defun newsticker-mouse-browse-url (event) | ||
| 3233 | "Call `browse-url' for the link of the item at which the EVENT occurred." | ||
| 3234 | (interactive "e") | ||
| 3235 | (save-excursion | ||
| 3236 | (switch-to-buffer (window-buffer (posn-window (event-end event)))) | ||
| 3237 | (let ((url (get-text-property (posn-point (event-end event)) | ||
| 3238 | 'nt-link))) | ||
| 3239 | (when url | ||
| 3240 | (browse-url url) | ||
| 3241 | (save-excursion | ||
| 3242 | (goto-char (posn-point (event-end event))) | ||
| 3243 | (if newsticker-automatically-mark-visited-items-as-old | ||
| 3244 | (newsticker-mark-item-at-point-as-read t))))))) | ||
| 3245 | |||
| 3246 | (defun newsticker-browse-url () | ||
| 3247 | "Call `browse-url' for the link of the item at point." | ||
| 3248 | (interactive) | ||
| 3249 | (let ((url (get-text-property (point) 'nt-link))) | ||
| 3250 | (when url | ||
| 3251 | (browse-url url) | ||
| 3252 | (if newsticker-automatically-mark-visited-items-as-old | ||
| 3253 | (newsticker-mark-item-at-point-as-read t))))) | ||
| 3254 | |||
| 3255 | (defvar newsticker-open-url-history | ||
| 3256 | '("wget" "xmms" "realplay") | ||
| 3257 | "...") | ||
| 3258 | |||
| 3259 | (defun newsticker-handle-url () | ||
| 3260 | "Ask for a program to open the link of the item at point." | ||
| 3261 | (interactive) | ||
| 3262 | (let ((url (get-text-property (point) 'nt-link))) | ||
| 3263 | (when url | ||
| 3264 | (let ((prog (read-string "Open url with: " nil | ||
| 3265 | 'newsticker-open-url-history))) | ||
| 3266 | (when prog | ||
| 3267 | (message "%s %s" prog url) | ||
| 3268 | (start-process prog prog prog url) | ||
| 3269 | (if newsticker-automatically-mark-visited-items-as-old | ||
| 3270 | (newsticker-mark-item-at-point-as-read t))))))) | ||
| 3271 | |||
| 3272 | (defun newsticker--sentinel (process event) | ||
| 3273 | "Sentinel for extracting news titles from an RDF buffer. | ||
| 3274 | Argument PROCESS is the process which has just changed its state. | ||
| 3275 | Argument EVENT tells what has happened to the process." | ||
| 3276 | (let* ((p-status (process-status process)) | ||
| 3277 | (exit-status (process-exit-status process)) | ||
| 3278 | (time (current-time)) | ||
| 3279 | (name (process-name process)) | ||
| 3280 | (name-symbol (intern name)) | ||
| 3281 | (something-was-added nil)) | ||
| 3282 | ;; catch known errors (zombie processes, rubbish-xml etc. | ||
| 3283 | ;; if an error occurs the news feed is not updated! | ||
| 3284 | (catch 'oops | ||
| 3285 | (unless (and (eq p-status 'exit) | ||
| 3286 | (= exit-status 0)) | ||
| 3287 | (setq newsticker--cache | ||
| 3288 | (newsticker--cache-add | ||
| 3289 | newsticker--cache | ||
| 3290 | name-symbol | ||
| 3291 | newsticker--error-headline | ||
| 3292 | (format | ||
| 3293 | (concat "%s: Newsticker could not retrieve news from %s.\n" | ||
| 3294 | "Return status: `%s'\n" | ||
| 3295 | "Command was `%s'") | ||
| 3296 | (format-time-string "%A, %H:%M" (current-time)) | ||
| 3297 | name event (process-command process)) | ||
| 3298 | "" | ||
| 3299 | (current-time) | ||
| 3300 | 'new | ||
| 3301 | 0 nil)) | ||
| 3302 | (message "%s: Error while retrieving news from %s" | ||
| 3303 | (format-time-string "%A, %H:%M" (current-time)) | ||
| 3304 | (process-name process)) | ||
| 3305 | (throw 'oops nil)) | ||
| 3306 | (let* ((coding-system 'utf-8) | ||
| 3307 | (node-list | ||
| 3308 | (save-current-buffer | ||
| 3309 | (set-buffer (process-buffer process)) | ||
| 3310 | ;; a very very dirty workaround to overcome the | ||
| 3311 | ;; problems with the newest (20030621) xml.el: | ||
| 3312 | ;; remove all unnecessary whitespace | ||
| 3313 | (goto-char (point-min)) | ||
| 3314 | (while (re-search-forward ">[ \t\r\n]+<" nil t) | ||
| 3315 | (replace-match "><" nil t)) | ||
| 3316 | ;; and another brutal workaround (20031105)! For some | ||
| 3317 | ;; reason the xml parser does not like the colon in the | ||
| 3318 | ;; doctype name "rdf:RDF" | ||
| 3319 | (goto-char (point-min)) | ||
| 3320 | (if (re-search-forward "<!DOCTYPE[ \t\n]+rdf:RDF" nil t) | ||
| 3321 | (replace-match "<!DOCTYPE rdfColonRDF" nil t)) | ||
| 3322 | ;; finally.... ~##^°!!!!! | ||
| 3323 | (goto-char (point-min)) | ||
| 3324 | (while (search-forward "\r\n" nil t) | ||
| 3325 | (replace-match "\n" nil t)) | ||
| 3326 | ;; still more brutal workarounds (20040309)! The xml | ||
| 3327 | ;; parser does not like doctype rss | ||
| 3328 | (goto-char (point-min)) | ||
| 3329 | (if (re-search-forward "<!DOCTYPE[ \t\n]+rss[ \t\n]*>" nil t) | ||
| 3330 | (replace-match "" nil t)) | ||
| 3331 | ;; And another one (20050618)! (Fixed in GNU Emacs 22.0.50.18) | ||
| 3332 | ;; Remove comments to avoid this xml-parsing bug: | ||
| 3333 | ;; "XML files can have only one toplevel tag" | ||
| 3334 | (goto-char (point-min)) | ||
| 3335 | (while (search-forward "<!--" nil t) | ||
| 3336 | (let ((start (match-beginning 0))) | ||
| 3337 | (unless (search-forward "-->" nil t) | ||
| 3338 | (error "Can't find end of comment")) | ||
| 3339 | (delete-region start (point)))) | ||
| 3340 | ;; And another one (20050702)! If description is HTML | ||
| 3341 | ;; encoded and starts with a `<', wrap the whole | ||
| 3342 | ;; description in a CDATA expression. This happened for | ||
| 3343 | ;; http://www.thefreedictionary.com/_/WoD/rss.aspx?type=quote | ||
| 3344 | (goto-char (point-min)) | ||
| 3345 | (while (re-search-forward | ||
| 3346 | "<description>\\(<img.*?\\)</description>" nil t) | ||
| 3347 | (replace-match | ||
| 3348 | "<description><![CDATA[ \\1 ]]></description>")) | ||
| 3349 | ;; And another one (20051123)! XML parser does not like this: | ||
| 3350 | ;; <yweather:location city="Frankfurt/Main" region="" country="GM" /> | ||
| 3351 | ;; try to "fix" empty attributes | ||
| 3352 | ;; This happened for | ||
| 3353 | ;; http://xml.weather.yahoo.com/forecastrss?p=GMXX0040&u=f | ||
| 3354 | (goto-char (point-min)) | ||
| 3355 | (while (re-search-forward "\\(<[^>]*\\)=\"\"" nil t) | ||
| 3356 | (replace-match "\\1=\" \"")) | ||
| 3357 | ;; | ||
| 3358 | (set-buffer-modified-p nil) | ||
| 3359 | ;; check coding system | ||
| 3360 | (goto-char (point-min)) | ||
| 3361 | (if (re-search-forward "encoding=\"\\([^\"]+\\)\"" | ||
| 3362 | nil t) | ||
| 3363 | (setq coding-system (intern (downcase (match-string 1)))) | ||
| 3364 | (setq coding-system | ||
| 3365 | (condition-case nil | ||
| 3366 | (check-coding-system coding-system) | ||
| 3367 | (coding-system-error | ||
| 3368 | (message | ||
| 3369 | "newsticker.el: ignoring coding system %s for %s" | ||
| 3370 | coding-system name) | ||
| 3371 | nil)))) | ||
| 3372 | ;; Decode if possible | ||
| 3373 | (when coding-system | ||
| 3374 | (decode-coding-region (point-min) (point-max) | ||
| 3375 | coding-system)) | ||
| 3376 | (condition-case errordata | ||
| 3377 | ;; The xml parser might fail | ||
| 3378 | ;; or the xml might be bugged | ||
| 3379 | (xml-parse-region (point-min) (point-max)) | ||
| 3380 | (error (message "Could not parse %s: %s" | ||
| 3381 | (buffer-name) (cadr errordata)) | ||
| 3382 | (throw 'oops nil))))) | ||
| 3383 | (topnode (car node-list)) | ||
| 3384 | (channelnode (car (xml-get-children topnode 'channel))) | ||
| 3385 | (imageurl nil)) | ||
| 3386 | ;; mark all items as obsolete | ||
| 3387 | (newsticker--cache-replace-age newsticker--cache | ||
| 3388 | name-symbol | ||
| 3389 | 'new 'obsolete-new) | ||
| 3390 | (newsticker--cache-replace-age newsticker--cache | ||
| 3391 | name-symbol | ||
| 3392 | 'old 'obsolete-old) | ||
| 3393 | (newsticker--cache-replace-age newsticker--cache | ||
| 3394 | name-symbol | ||
| 3395 | 'feed 'obsolete-old) | ||
| 3396 | |||
| 3397 | ;; check Atom/RSS version and call corresponding parser | ||
| 3398 | (condition-case error-data | ||
| 3399 | (if (cond | ||
| 3400 | ;; RSS 0.91 | ||
| 3401 | ((and (eq 'rss (xml-node-name topnode)) | ||
| 3402 | (string= "0.91" (xml-get-attribute topnode 'version))) | ||
| 3403 | (setq imageurl (newsticker--get-logo-url-rss-0.91 topnode)) | ||
| 3404 | (newsticker--parse-rss-0.91 name time topnode)) | ||
| 3405 | ;; RSS 0.92 | ||
| 3406 | ((and (eq 'rss (xml-node-name topnode)) | ||
| 3407 | (string= "0.92" (xml-get-attribute topnode 'version))) | ||
| 3408 | (setq imageurl (newsticker--get-logo-url-rss-0.92 topnode)) | ||
| 3409 | (newsticker--parse-rss-0.92 name time topnode)) | ||
| 3410 | ;; RSS 1.0 | ||
| 3411 | ((eq 'rdf:RDF (xml-node-name topnode)) | ||
| 3412 | (setq imageurl (newsticker--get-logo-url-rss-1.0 topnode)) | ||
| 3413 | (newsticker--parse-rss-1.0 name time topnode)) | ||
| 3414 | ;; RSS 2.0 | ||
| 3415 | ((and (eq 'rss (xml-node-name topnode)) | ||
| 3416 | (string= "2.0" (xml-get-attribute topnode 'version))) | ||
| 3417 | (setq imageurl (newsticker--get-logo-url-rss-2.0 topnode)) | ||
| 3418 | (newsticker--parse-rss-2.0 name time topnode)) | ||
| 3419 | ;; Atom 0.3 | ||
| 3420 | ((and (eq 'feed (xml-node-name topnode)) | ||
| 3421 | (string= "http://purl.org/atom/ns#" | ||
| 3422 | (xml-get-attribute topnode 'xmlns))) | ||
| 3423 | (setq imageurl (newsticker--get-logo-url-atom-0.3 topnode)) | ||
| 3424 | (newsticker--parse-atom-0.3 name time topnode)) | ||
| 3425 | ;; Atom 1.0 | ||
| 3426 | ((and (eq 'feed (xml-node-name topnode)) | ||
| 3427 | (string= "http://www.w3.org/2005/Atom" | ||
| 3428 | (xml-get-attribute topnode 'xmlns))) | ||
| 3429 | (setq imageurl (newsticker--get-logo-url-atom-1.0 topnode)) | ||
| 3430 | (newsticker--parse-atom-1.0 name time topnode)) | ||
| 3431 | ;; unknown feed type | ||
| 3432 | (t | ||
| 3433 | (newsticker--debug-msg "Feed type unknown: %s: %s" | ||
| 3434 | (xml-node-name topnode) name) | ||
| 3435 | nil)) | ||
| 3436 | (setq something-was-added t)) | ||
| 3437 | (xerror (message "sentinelerror in %s: %s" name error-data))) | ||
| 3438 | |||
| 3439 | ;; Remove those old items from cache which have been removed from | ||
| 3440 | ;; the feed | ||
| 3441 | (newsticker--cache-replace-age newsticker--cache | ||
| 3442 | name-symbol 'obsolete-old 'deleteme) | ||
| 3443 | (newsticker--cache-remove newsticker--cache name-symbol | ||
| 3444 | 'deleteme) | ||
| 3445 | ;; Remove those new items from cache which have been removed from | ||
| 3446 | ;; the feed. Or keep them as `obsolete' | ||
| 3447 | (if (not newsticker-keep-obsolete-items) | ||
| 3448 | (newsticker--cache-remove newsticker--cache | ||
| 3449 | name-symbol 'obsolete-new) | ||
| 3450 | (setq newsticker--cache | ||
| 3451 | (newsticker--cache-mark-expired | ||
| 3452 | newsticker--cache name-symbol 'obsolete 'obsolete-expired | ||
| 3453 | newsticker-obsolete-item-max-age)) | ||
| 3454 | (newsticker--cache-remove newsticker--cache | ||
| 3455 | name-symbol 'obsolete-expired) | ||
| 3456 | (newsticker--cache-replace-age newsticker--cache | ||
| 3457 | name-symbol 'obsolete-new | ||
| 3458 | 'obsolete)) | ||
| 3459 | (newsticker--update-process-ids) | ||
| 3460 | ;; setup scrollable text | ||
| 3461 | (when (= 0 (length newsticker--process-ids)) | ||
| 3462 | (newsticker--ticker-text-setup)) | ||
| 3463 | (setq newsticker--latest-update-time (current-time)) | ||
| 3464 | (when something-was-added | ||
| 3465 | ;; FIXME: should we care about removed items as well? | ||
| 3466 | (newsticker--cache-update) | ||
| 3467 | (newsticker--buffer-set-uptodate nil)) | ||
| 3468 | ;; kill the process buffer if wanted | ||
| 3469 | (unless newsticker-debug | ||
| 3470 | (kill-buffer (process-buffer process))) | ||
| 3471 | ;; launch retrieval of image | ||
| 3472 | (when (and imageurl | ||
| 3473 | (string-match "%l" newsticker-heading-format)) | ||
| 3474 | (newsticker--image-get name imageurl)))))) | ||
| 3475 | |||
| 3476 | (defun newsticker--get-logo-url-atom-1.0 (node) | ||
| 3477 | "Return logo URL from atom 1.0 data in NODE." | ||
| 3478 | (car (xml-node-children | ||
| 3479 | (car (xml-get-children node 'logo))))) | ||
| 3480 | |||
| 3481 | (defun newsticker--get-logo-url-atom-0.3 (node) | ||
| 3482 | "Return logo URL from atom 0.3 data in NODE." | ||
| 3483 | (car (xml-node-children | ||
| 3484 | (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | ||
| 3485 | |||
| 3486 | (defun newsticker--get-logo-url-rss-2.0 (node) | ||
| 3487 | "Return logo URL from RSS 2.0 data in NODE." | ||
| 3488 | (car (xml-node-children | ||
| 3489 | (car (xml-get-children | ||
| 3490 | (car (xml-get-children | ||
| 3491 | (car (xml-get-children node 'channel)) 'image)) 'url))))) | ||
| 3492 | |||
| 3493 | (defun newsticker--get-logo-url-rss-1.0 (node) | ||
| 3494 | "Return logo URL from RSS 1.0 data in NODE." | ||
| 3495 | (car (xml-node-children | ||
| 3496 | (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | ||
| 3497 | |||
| 3498 | (defun newsticker--get-logo-url-rss-0.92 (node) | ||
| 3499 | "Return logo URL from RSS 0.92 data in NODE." | ||
| 3500 | (car (xml-node-children | ||
| 3501 | (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | ||
| 3502 | |||
| 3503 | (defun newsticker--get-logo-url-rss-0.91 (node) | ||
| 3504 | "Return logo URL from RSS 0.91 data in NODE." | ||
| 3505 | (car (xml-node-children | ||
| 3506 | (car (xml-get-children (car (xml-get-children node 'image)) 'url))))) | ||
| 3507 | |||
| 3508 | (defun newsticker--parse-atom-0.3 (name time topnode) | ||
| 3509 | "Parse Atom 0.3 data. | ||
| 3510 | Return value as well as arguments NAME, TIME, and TOPNODE are the | ||
| 3511 | same as in `newsticker--parse-atom-1.0'." | ||
| 3512 | (newsticker--debug-msg "Parsing Atom 0.3 feed %s" name) | ||
| 3513 | (let (new-feed new-item) | ||
| 3514 | (setq new-feed (newsticker--parse-generic-feed | ||
| 3515 | name time | ||
| 3516 | ;; title | ||
| 3517 | (car (xml-node-children | ||
| 3518 | (car (xml-get-children topnode 'title)))) | ||
| 3519 | ;; desc | ||
| 3520 | (car (xml-node-children | ||
| 3521 | (car (xml-get-children topnode 'content)))) | ||
| 3522 | ;; link | ||
| 3523 | (xml-get-attribute | ||
| 3524 | (car (xml-get-children topnode 'link)) 'href) | ||
| 3525 | ;; extra-elements | ||
| 3526 | (xml-node-children topnode))) | ||
| 3527 | (setq new-item (newsticker--parse-generic-items | ||
| 3528 | name time (xml-get-children topnode 'entry) | ||
| 3529 | ;; title-fn | ||
| 3530 | (lambda (node) | ||
| 3531 | (car (xml-node-children | ||
| 3532 | (car (xml-get-children node 'title))))) | ||
| 3533 | ;; desc-fn | ||
| 3534 | (lambda (node) | ||
| 3535 | (or (car (xml-node-children | ||
| 3536 | (car (xml-get-children node 'content)))) | ||
| 3537 | (car (xml-node-children | ||
| 3538 | (car (xml-get-children node 'summary)))))) | ||
| 3539 | ;; link-fn | ||
| 3540 | (lambda (node) | ||
| 3541 | (xml-get-attribute | ||
| 3542 | (car (xml-get-children node 'link)) 'href)) | ||
| 3543 | ;; time-fn | ||
| 3544 | (lambda (node) | ||
| 3545 | (newsticker--decode-rfc822-date | ||
| 3546 | (car (xml-node-children | ||
| 3547 | (car (xml-get-children node 'modified)))))) | ||
| 3548 | ;; guid-fn | ||
| 3549 | (lambda (node) | ||
| 3550 | (let ((tguid (assoc 'guid (xml-node-children node)))) | ||
| 3551 | (if (stringp tguid) | ||
| 3552 | tguid | ||
| 3553 | (car (xml-node-children tguid))))) | ||
| 3554 | ;; extra-fn | ||
| 3555 | (lambda (node) | ||
| 3556 | (xml-node-children node)))) | ||
| 3557 | (or new-item new-feed))) | ||
| 3558 | |||
| 3559 | (defun newsticker--parse-atom-1.0 (name time topnode) | ||
| 3560 | "Parse Atom 1.0 data. | ||
| 3561 | Argument NAME gives the name of a news feed. TIME gives the | ||
| 3562 | system time at which the data have been retrieved. TOPNODE | ||
| 3563 | contains the feed data as returned by the xml parser. | ||
| 3564 | |||
| 3565 | For the Atom 1.0 specification see | ||
| 3566 | http://www.atompub.org/2005/08/17/draft-ietf-atompub-format-11.html" | ||
| 3567 | (newsticker--debug-msg "Parsing Atom 1.0 feed %s" name) | ||
| 3568 | (let (new-feed new-item) | ||
| 3569 | (setq new-feed (newsticker--parse-generic-feed | ||
| 3570 | name time | ||
| 3571 | ;; title | ||
| 3572 | (car (xml-node-children | ||
| 3573 | (car (xml-get-children topnode 'title)))) | ||
| 3574 | ;; desc | ||
| 3575 | (car (xml-node-children | ||
| 3576 | (car (xml-get-children topnode 'subtitle)))) | ||
| 3577 | ;; link | ||
| 3578 | (car (xml-node-children | ||
| 3579 | (car (xml-get-children topnode 'link)))) | ||
| 3580 | ;; extra-elements | ||
| 3581 | (xml-node-children topnode))) | ||
| 3582 | (setq new-item (newsticker--parse-generic-items | ||
| 3583 | name time (xml-get-children topnode 'entry) | ||
| 3584 | ;; title-fn | ||
| 3585 | (lambda (node) | ||
| 3586 | (car (xml-node-children | ||
| 3587 | (car (xml-get-children node 'title))))) | ||
| 3588 | ;; desc-fn | ||
| 3589 | (lambda (node) | ||
| 3590 | (or (car (xml-node-children | ||
| 3591 | (car (xml-get-children node 'content)))) | ||
| 3592 | (car (xml-node-children | ||
| 3593 | (car (xml-get-children node 'summary)))))) | ||
| 3594 | ;; link-fn | ||
| 3595 | (lambda (node) | ||
| 3596 | (car (xml-node-children | ||
| 3597 | (car (xml-get-children node 'link))))) | ||
| 3598 | ;; time-fn | ||
| 3599 | (lambda (node) | ||
| 3600 | (newsticker--decode-iso8601-date | ||
| 3601 | (or (car (xml-node-children | ||
| 3602 | (car (xml-get-children node 'updated)))) | ||
| 3603 | (car (xml-node-children | ||
| 3604 | (car (xml-get-children node 'published))))))) | ||
| 3605 | ;; guid-fn | ||
| 3606 | (lambda (node) | ||
| 3607 | (car (xml-node-children | ||
| 3608 | (car (xml-get-children node 'id))))) | ||
| 3609 | ;; extra-fn | ||
| 3610 | (lambda (node) | ||
| 3611 | (xml-node-children node)))) | ||
| 3612 | (or new-item new-feed))) | ||
| 3613 | |||
| 3614 | (defun newsticker--parse-rss-0.91 (name time topnode) | ||
| 3615 | "Parse RSS 0.91 data. | ||
| 3616 | Return value as well as arguments NAME, TIME, and TOPNODE are the | ||
| 3617 | same as in `newsticker--parse-atom-1.0'. | ||
| 3618 | |||
| 3619 | For the RSS 0.91 specification see http://backend.userland.com/rss091 or | ||
| 3620 | http://my.netscape.com/publish/formats/rss-spec-0.91.html." | ||
| 3621 | (newsticker--debug-msg "Parsing RSS 0.91 feed %s" name) | ||
| 3622 | (let* ((channelnode (car (xml-get-children topnode 'channel))) | ||
| 3623 | (pub-date (newsticker--decode-rfc822-date | ||
| 3624 | (car (xml-node-children | ||
| 3625 | (car (xml-get-children channelnode 'pubDate)))))) | ||
| 3626 | is-new-feed has-new-items) | ||
| 3627 | (setq is-new-feed (newsticker--parse-generic-feed | ||
| 3628 | name time | ||
| 3629 | ;; title | ||
| 3630 | (car (xml-node-children | ||
| 3631 | (car (xml-get-children channelnode 'title)))) | ||
| 3632 | ;; desc | ||
| 3633 | (car (xml-node-children | ||
| 3634 | (car (xml-get-children channelnode | ||
| 3635 | 'description)))) | ||
| 3636 | ;; link | ||
| 3637 | (car (xml-node-children | ||
| 3638 | (car (xml-get-children channelnode 'link)))) | ||
| 3639 | ;; extra-elements | ||
| 3640 | (xml-node-children channelnode))) | ||
| 3641 | (setq has-new-items (newsticker--parse-generic-items | ||
| 3642 | name time (xml-get-children channelnode 'item) | ||
| 3643 | ;; title-fn | ||
| 3644 | (lambda (node) | ||
| 3645 | (car (xml-node-children | ||
| 3646 | (car (xml-get-children node 'title))))) | ||
| 3647 | ;; desc-fn | ||
| 3648 | (lambda (node) | ||
| 3649 | (car (xml-node-children | ||
| 3650 | (car (xml-get-children node 'description))))) | ||
| 3651 | ;; link-fn | ||
| 3652 | (lambda (node) | ||
| 3653 | (car (xml-node-children | ||
| 3654 | (car (xml-get-children node 'link))))) | ||
| 3655 | ;; time-fn | ||
| 3656 | (lambda (node) | ||
| 3657 | pub-date) | ||
| 3658 | ;; guid-fn | ||
| 3659 | (lambda (node) | ||
| 3660 | nil) | ||
| 3661 | ;; extra-fn | ||
| 3662 | (lambda (node) | ||
| 3663 | (xml-node-children node)))) | ||
| 3664 | (or has-new-items is-new-feed))) | ||
| 3665 | |||
| 3666 | (defun newsticker--parse-rss-0.92 (name time topnode) | ||
| 3667 | "Parse RSS 0.92 data. | ||
| 3668 | Return value as well as arguments NAME, TIME, and TOPNODE are the | ||
| 3669 | same as in `newsticker--parse-atom-1.0'. | ||
| 3670 | |||
| 3671 | For the RSS 0.92 specification see http://backend.userland.com/rss092." | ||
| 3672 | (newsticker--debug-msg "Parsing RSS 0.92 feed %s" name) | ||
| 3673 | (let* ((channelnode (car (xml-get-children topnode 'channel))) | ||
| 3674 | (pub-date (newsticker--decode-rfc822-date | ||
| 3675 | (car (xml-node-children | ||
| 3676 | (car (xml-get-children channelnode 'pubDate)))))) | ||
| 3677 | is-new-feed has-new-items) | ||
| 3678 | (setq is-new-feed (newsticker--parse-generic-feed | ||
| 3679 | name time | ||
| 3680 | ;; title | ||
| 3681 | (car (xml-node-children | ||
| 3682 | (car (xml-get-children channelnode 'title)))) | ||
| 3683 | ;; desc | ||
| 3684 | (car (xml-node-children | ||
| 3685 | (car (xml-get-children channelnode | ||
| 3686 | 'description)))) | ||
| 3687 | ;; link | ||
| 3688 | (car (xml-node-children | ||
| 3689 | (car (xml-get-children channelnode 'link)))) | ||
| 3690 | ;; extra-elements | ||
| 3691 | (xml-node-children channelnode))) | ||
| 3692 | (setq has-new-items (newsticker--parse-generic-items | ||
| 3693 | name time (xml-get-children channelnode 'item) | ||
| 3694 | ;; title-fn | ||
| 3695 | (lambda (node) | ||
| 3696 | (car (xml-node-children | ||
| 3697 | (car (xml-get-children node 'title))))) | ||
| 3698 | ;; desc-fn | ||
| 3699 | (lambda (node) | ||
| 3700 | (car (xml-node-children | ||
| 3701 | (car (xml-get-children node 'description))))) | ||
| 3702 | ;; link-fn | ||
| 3703 | (lambda (node) | ||
| 3704 | (car (xml-node-children | ||
| 3705 | (car (xml-get-children node 'link))))) | ||
| 3706 | ;; time-fn | ||
| 3707 | (lambda (node) | ||
| 3708 | pub-date) | ||
| 3709 | ;; guid-fn | ||
| 3710 | (lambda (node) | ||
| 3711 | nil) | ||
| 3712 | ;; extra-fn | ||
| 3713 | (lambda (node) | ||
| 3714 | (xml-node-children node)))) | ||
| 3715 | (or has-new-items is-new-feed))) | ||
| 3716 | |||
| 3717 | (defun newsticker--parse-rss-1.0 (name time topnode) | ||
| 3718 | "Parse RSS 1.0 data. | ||
| 3719 | Return value as well as arguments NAME, TIME, and TOPNODE are the | ||
| 3720 | same as in `newsticker--parse-atom-1.0'. | ||
| 3721 | |||
| 3722 | For the RSS 1.0 specification see http://web.resource.org/rss/1.0/spec." | ||
| 3723 | (newsticker--debug-msg "Parsing RSS 1.0 feed %s" name) | ||
| 3724 | (let* ((channelnode (car (xml-get-children topnode 'channel))) | ||
| 3725 | is-new-feed has-new-items) | ||
| 3726 | (setq is-new-feed (newsticker--parse-generic-feed | ||
| 3727 | name time | ||
| 3728 | ;; title | ||
| 3729 | (car (xml-node-children | ||
| 3730 | (car (xml-get-children channelnode 'title)))) | ||
| 3731 | ;; desc | ||
| 3732 | (car (xml-node-children | ||
| 3733 | (car (xml-get-children channelnode | ||
| 3734 | 'description)))) | ||
| 3735 | ;; link | ||
| 3736 | (car (xml-node-children | ||
| 3737 | (car (xml-get-children channelnode 'link)))) | ||
| 3738 | ;; extra-elements | ||
| 3739 | (xml-node-children channelnode))) | ||
| 3740 | (setq has-new-items (newsticker--parse-generic-items | ||
| 3741 | name time (xml-get-children topnode 'item) | ||
| 3742 | ;; title-fn | ||
| 3743 | (lambda (node) | ||
| 3744 | (car (xml-node-children | ||
| 3745 | (car (xml-get-children node 'title))))) | ||
| 3746 | ;; desc-fn | ||
| 3747 | (lambda (node) | ||
| 3748 | (car (xml-node-children | ||
| 3749 | (car (xml-get-children node | ||
| 3750 | 'description))))) | ||
| 3751 | ;; link-fn | ||
| 3752 | (lambda (node) | ||
| 3753 | (car (xml-node-children | ||
| 3754 | (car (xml-get-children node 'link))))) | ||
| 3755 | ;; time-fn | ||
| 3756 | (lambda (node) | ||
| 3757 | (newsticker--decode-iso8601-date | ||
| 3758 | (car (xml-node-children | ||
| 3759 | (car (xml-get-children node 'dc:date)))))) | ||
| 3760 | ;; guid-fn | ||
| 3761 | (lambda (node) | ||
| 3762 | nil) | ||
| 3763 | ;; extra-fn | ||
| 3764 | (lambda (node) | ||
| 3765 | (xml-node-children node)))) | ||
| 3766 | (or has-new-items is-new-feed))) | ||
| 3767 | |||
| 3768 | (defun newsticker--parse-rss-2.0 (name time topnode) | ||
| 3769 | "Parse RSS 2.0 data. | ||
| 3770 | Return value as well as arguments NAME, TIME, and TOPNODE are the | ||
| 3771 | same as in `newsticker--parse-atom-1.0'. | ||
| 3772 | |||
| 3773 | For the RSS 2.0 specification see http://blogs.law.harvard.edu/tech/rss." | ||
| 3774 | (newsticker--debug-msg "Parsing RSS 2.0 feed %s" name) | ||
| 3775 | (let* ((channelnode (car (xml-get-children topnode 'channel))) | ||
| 3776 | is-new-feed has-new-items) | ||
| 3777 | (setq is-new-feed (newsticker--parse-generic-feed | ||
| 3778 | name time | ||
| 3779 | ;; title | ||
| 3780 | (car (xml-node-children | ||
| 3781 | (car (xml-get-children channelnode 'title)))) | ||
| 3782 | ;; desc | ||
| 3783 | (car (xml-node-children | ||
| 3784 | (car (xml-get-children channelnode | ||
| 3785 | 'description)))) | ||
| 3786 | ;; link | ||
| 3787 | (car (xml-node-children | ||
| 3788 | (car (xml-get-children channelnode 'link)))) | ||
| 3789 | ;; extra-elements | ||
| 3790 | (xml-node-children channelnode))) | ||
| 3791 | (setq has-new-items (newsticker--parse-generic-items | ||
| 3792 | name time (xml-get-children channelnode 'item) | ||
| 3793 | ;; title-fn | ||
| 3794 | (lambda (node) | ||
| 3795 | (car (xml-node-children | ||
| 3796 | (car (xml-get-children node 'title))))) | ||
| 3797 | ;; desc-fn | ||
| 3798 | (lambda (node) | ||
| 3799 | (or (car (xml-node-children | ||
| 3800 | (car (xml-get-children node | ||
| 3801 | 'content:encoded)))) | ||
| 3802 | (car (xml-node-children | ||
| 3803 | (car (xml-get-children node | ||
| 3804 | 'description)))))) | ||
| 3805 | ;; link-fn | ||
| 3806 | (lambda (node) | ||
| 3807 | (car (xml-node-children | ||
| 3808 | (car (xml-get-children node 'link))))) | ||
| 3809 | ;; time-fn | ||
| 3810 | (lambda (node) | ||
| 3811 | (newsticker--decode-rfc822-date | ||
| 3812 | (car (xml-node-children | ||
| 3813 | (car (xml-get-children node 'pubDate)))))) | ||
| 3814 | ;; guid-fn | ||
| 3815 | (lambda (node) | ||
| 3816 | (let* ((tguid (assoc 'guid | ||
| 3817 | (xml-node-children node)))) | ||
| 3818 | (if (stringp tguid) | ||
| 3819 | tguid | ||
| 3820 | (car (xml-node-children tguid))))) | ||
| 3821 | ;; extra-fn | ||
| 3822 | (lambda (node) | ||
| 3823 | (xml-node-children node)))) | ||
| 3824 | (or has-new-items is-new-feed))) | ||
| 3825 | |||
| 3826 | (defun newsticker--parse-generic-feed (name time title desc link | ||
| 3827 | extra-elements) | ||
| 3828 | "Parse generic news feed data. | ||
| 3829 | Argument NAME gives the name of a news feed. TIME gives the | ||
| 3830 | system time at which the data have been retrieved. | ||
| 3831 | |||
| 3832 | The arguments TITLE, DESC, LINK, and EXTRA-ELEMENTS give the feed's title, | ||
| 3833 | description, link, and extra elements resp." | ||
| 3834 | (let ((title (or title "[untitled]")) | ||
| 3835 | (link (or link "")) | ||
| 3836 | (old-item nil) | ||
| 3837 | (position 0) | ||
| 3838 | (something-was-added nil)) | ||
| 3839 | ;; decode numeric entities | ||
| 3840 | (setq title (newsticker--decode-numeric-entities title)) | ||
| 3841 | (setq desc (newsticker--decode-numeric-entities desc)) | ||
| 3842 | (setq link (newsticker--decode-numeric-entities link)) | ||
| 3843 | ;; remove whitespace from title, desc, and link | ||
| 3844 | (setq title (newsticker--remove-whitespace title)) | ||
| 3845 | (setq desc (newsticker--remove-whitespace desc)) | ||
| 3846 | (setq link (newsticker--remove-whitespace link)) | ||
| 3847 | |||
| 3848 | ;; handle the feed itself | ||
| 3849 | (unless (newsticker--cache-contains newsticker--cache | ||
| 3850 | (intern name) title | ||
| 3851 | desc link 'feed) | ||
| 3852 | (setq something-was-added t)) | ||
| 3853 | (setq newsticker--cache | ||
| 3854 | (newsticker--cache-add newsticker--cache (intern name) | ||
| 3855 | title desc link time 'feed position | ||
| 3856 | extra-elements 'feed time)) | ||
| 3857 | something-was-added)) | ||
| 3858 | |||
| 3859 | (defun newsticker--parse-generic-items (name time itemlist | ||
| 3860 | title-fn desc-fn | ||
| 3861 | link-fn time-fn | ||
| 3862 | guid-fn extra-fn) | ||
| 3863 | "Parse generic news feed data. | ||
| 3864 | Argument NAME gives the name of a news feed. TIME gives the | ||
| 3865 | system time at which the data have been retrieved. ITEMLIST | ||
| 3866 | contains the news items returned by the xml parser. | ||
| 3867 | |||
| 3868 | The arguments TITLE-FN, DESC-FN, LINK-FN, TIME-FN, GUID-FN, and | ||
| 3869 | EXTRA-FN give functions for extracting title, description, link, | ||
| 3870 | time, guid, and extra-elements resp. They are called with one | ||
| 3871 | argument, which is one of the items in ITEMLIST." | ||
| 3872 | (let (title desc link | ||
| 3873 | (old-item nil) | ||
| 3874 | (position 0) | ||
| 3875 | (something-was-added nil)) | ||
| 3876 | ;; gather all items for this feed | ||
| 3877 | (mapc (lambda (node) | ||
| 3878 | (setq position (1+ position)) | ||
| 3879 | (setq title (or (funcall title-fn node) "[untitled]")) | ||
| 3880 | (setq desc (funcall desc-fn node)) | ||
| 3881 | (setq link (or (funcall link-fn node) "")) | ||
| 3882 | (setq time (or (funcall time-fn node) time)) | ||
| 3883 | ;; It happened that the title or description | ||
| 3884 | ;; contained evil HTML code that confused the | ||
| 3885 | ;; xml parser. Therefore: | ||
| 3886 | (unless (stringp title) | ||
| 3887 | (setq title (prin1-to-string title))) | ||
| 3888 | (unless (or (stringp desc) (not desc)) | ||
| 3889 | (setq desc (prin1-to-string desc))) | ||
| 3890 | ;; ignore items with empty title AND empty desc | ||
| 3891 | (when (or (> (length title) 0) | ||
| 3892 | (> (length desc) 0)) | ||
| 3893 | ;; decode numeric entities | ||
| 3894 | (setq title (newsticker--decode-numeric-entities title)) | ||
| 3895 | (when desc | ||
| 3896 | (setq desc (newsticker--decode-numeric-entities desc))) | ||
| 3897 | (setq link (newsticker--decode-numeric-entities link)) | ||
| 3898 | ;; remove whitespace from title, desc, and link | ||
| 3899 | (setq title (newsticker--remove-whitespace title)) | ||
| 3900 | (setq desc (newsticker--remove-whitespace desc)) | ||
| 3901 | (setq link (newsticker--remove-whitespace link)) | ||
| 3902 | ;; add data to cache | ||
| 3903 | ;; do we have this item already? | ||
| 3904 | (let* ((guid (funcall guid-fn node))) | ||
| 3905 | ;;(message "guid=%s" guid) | ||
| 3906 | (setq old-item | ||
| 3907 | (newsticker--cache-contains newsticker--cache | ||
| 3908 | (intern name) title | ||
| 3909 | desc link nil guid))) | ||
| 3910 | ;; add this item, or mark it as old, or do nothing | ||
| 3911 | (let ((age1 'new) | ||
| 3912 | (age2 'old) | ||
| 3913 | (item-new-p nil)) | ||
| 3914 | (if old-item | ||
| 3915 | (let ((prev-age (newsticker--age old-item))) | ||
| 3916 | (unless | ||
| 3917 | newsticker-automatically-mark-items-as-old | ||
| 3918 | (if (eq prev-age 'obsolete-old) | ||
| 3919 | (setq age2 'old) | ||
| 3920 | (setq age2 'new))) | ||
| 3921 | (if (eq prev-age 'immortal) | ||
| 3922 | (setq age2 'immortal))) | ||
| 3923 | ;; item was not there | ||
| 3924 | (setq item-new-p t) | ||
| 3925 | (setq something-was-added t)) | ||
| 3926 | (setq newsticker--cache | ||
| 3927 | (newsticker--cache-add | ||
| 3928 | newsticker--cache (intern name) title desc link | ||
| 3929 | time age1 position (funcall extra-fn node) | ||
| 3930 | age2)) | ||
| 3931 | (when item-new-p | ||
| 3932 | (let ((item (newsticker--cache-contains | ||
| 3933 | newsticker--cache (intern name) title | ||
| 3934 | desc link nil))) | ||
| 3935 | (if newsticker-auto-mark-filter-list | ||
| 3936 | (newsticker--run-auto-mark-filter name item)) | ||
| 3937 | (run-hook-with-args | ||
| 3938 | 'newsticker-new-item-functions name item)))))) | ||
| 3939 | itemlist) | ||
| 3940 | something-was-added)) | ||
| 3941 | |||
| 3942 | (defun newsticker--display-tick () | ||
| 3943 | "Called from the display timer. | ||
| 3944 | This function calls a display function, according to the variable | ||
| 3945 | `newsticker-scroll-smoothly'." | ||
| 3946 | (if newsticker-scroll-smoothly | ||
| 3947 | (newsticker--display-scroll) | ||
| 3948 | (newsticker--display-jump))) | ||
| 3949 | |||
| 3950 | (defsubst newsticker--echo-area-clean-p () | ||
| 3951 | "Check whether somebody is using the echo area / minibuffer. | ||
| 3952 | Return t if echo area and minibuffer are unused." | ||
| 3953 | (not (or (active-minibuffer-window) | ||
| 3954 | (and (current-message) | ||
| 3955 | (not (string= (current-message) | ||
| 3956 | newsticker--prev-message)))))) | ||
| 3957 | |||
| 3958 | (defun newsticker--display-jump () | ||
| 3959 | "Called from the display timer. | ||
| 3960 | This function displays the next ticker item in the echo area, unless | ||
| 3961 | there is another message displayed or the minibuffer is active." | ||
| 3962 | (let ((message-log-max nil));; prevents message text from being logged | ||
| 3963 | (when (newsticker--echo-area-clean-p) | ||
| 3964 | (setq newsticker--item-position (1+ newsticker--item-position)) | ||
| 3965 | (when (>= newsticker--item-position (length newsticker--item-list)) | ||
| 3966 | (setq newsticker--item-position 0)) | ||
| 3967 | (setq newsticker--prev-message | ||
| 3968 | (nth newsticker--item-position newsticker--item-list)) | ||
| 3969 | (message "%s" newsticker--prev-message)))) | ||
| 3970 | |||
| 3971 | (defun newsticker--display-scroll () | ||
| 3972 | "Called from the display timer. | ||
| 3973 | This function scrolls the ticker items in the echo area, unless | ||
| 3974 | there is another message displayed or the minibuffer is active." | ||
| 3975 | (when (newsticker--echo-area-clean-p) | ||
| 3976 | (let* ((width (- (frame-width) 1)) | ||
| 3977 | (message-log-max nil);; prevents message text from being logged | ||
| 3978 | (i newsticker--item-position) | ||
| 3979 | subtext | ||
| 3980 | (s-text newsticker--scrollable-text) | ||
| 3981 | (l (length s-text))) | ||
| 3982 | ;; don't show anything if there is nothing to show | ||
| 3983 | (unless (< (length s-text) 1) | ||
| 3984 | ;; repeat the ticker string if it is shorter than frame width | ||
| 3985 | (while (< (length s-text) width) | ||
| 3986 | (setq s-text (concat s-text s-text))) | ||
| 3987 | ;; get the width of the printed string | ||
| 3988 | (setq l (length s-text)) | ||
| 3989 | (cond ((< i (- l width)) | ||
| 3990 | (setq subtext (substring s-text i (+ i width)))) | ||
| 3991 | (t | ||
| 3992 | (setq subtext (concat | ||
| 3993 | (substring s-text i l) | ||
| 3994 | (substring s-text 0 (- width (- l i))))))) | ||
| 3995 | ;; Take care of multibyte strings, for which (string-width) is | ||
| 3996 | ;; larger than (length). | ||
| 3997 | ;; Actually, such strings may be smaller than (frame-width) | ||
| 3998 | ;; because return values of (string-width) are too large: | ||
| 3999 | ;; (string-width "<japanese character>") => 2 | ||
| 4000 | (let ((t-width (1- (length subtext)))) | ||
| 4001 | (while (> (string-width subtext) width) | ||
| 4002 | (setq subtext (substring subtext 0 t-width)) | ||
| 4003 | (setq t-width (1- t-width)))) | ||
| 4004 | ;; show the ticker text and save current position | ||
| 4005 | (message "%s" subtext) | ||
| 4006 | (setq newsticker--prev-message subtext) | ||
| 4007 | (setq newsticker--item-position (1+ i)) | ||
| 4008 | (when (>= newsticker--item-position l) | ||
| 4009 | (setq newsticker--item-position 0)))))) | ||
| 4010 | |||
| 4011 | ;; ====================================================================== | ||
| 4012 | ;;; misc | ||
| 4013 | ;; ====================================================================== | ||
| 4014 | (defun newsticker--decode-numeric-entities (string) | ||
| 4015 | "Decode SGML numeric entities by their respective utf characters. | ||
| 4016 | This function replaces numeric entities in the input STRING and | ||
| 4017 | returns the modified string. For example \"*\" gets replaced | ||
| 4018 | by \"*\"." | ||
| 4019 | (if (and string (stringp string)) | ||
| 4020 | (let ((start 0)) | ||
| 4021 | (while (string-match "&#\\([0-9]+\\);" string start) | ||
| 4022 | (condition-case nil | ||
| 4023 | (setq string (replace-match | ||
| 4024 | (string (read (substring string | ||
| 4025 | (match-beginning 1) | ||
| 4026 | (match-end 1)))) | ||
| 4027 | nil nil string)) | ||
| 4028 | (error nil)) | ||
| 4029 | (setq start (1+ (match-beginning 0)))) | ||
| 4030 | string) | ||
| 4031 | nil)) | ||
| 4032 | |||
| 4033 | (defun newsticker--remove-whitespace (string) | ||
| 4034 | "Remove leading and trailing whitespace from STRING." | ||
| 4035 | ;; we must have ...+ but not ...* in the regexps otherwise xemacs loops | ||
| 4036 | ;; endlessly... | ||
| 4037 | (when (and string (stringp string)) | ||
| 4038 | (replace-regexp-in-string | ||
| 4039 | "[ \t\r\n]+$" "" | ||
| 4040 | (replace-regexp-in-string "^[ \t\r\n]+" "" string)))) | ||
| 4041 | |||
| 4042 | (defun newsticker--do-forget-preformatted (item) | ||
| 4043 | "Forget pre-formatted data for ITEM. | ||
| 4044 | Remove the pre-formatted from `newsticker--cache'." | ||
| 4045 | (if (nthcdr 7 item) | ||
| 4046 | (setcar (nthcdr 7 item) nil)) | ||
| 4047 | (if (nthcdr 6 item) | ||
| 4048 | (setcar (nthcdr 6 item) nil))) | ||
| 4049 | |||
| 4050 | (defun newsticker--forget-preformatted () | ||
| 4051 | "Forget all cached pre-formatted data. | ||
| 4052 | Remove the pre-formatted from `newsticker--cache'." | ||
| 4053 | (mapc (lambda (feed) | ||
| 4054 | (mapc 'newsticker--do-forget-preformatted | ||
| 4055 | (cdr feed))) | ||
| 4056 | newsticker--cache) | ||
| 4057 | (newsticker--buffer-set-uptodate nil)) | ||
| 4058 | |||
| 4059 | (defun newsticker--debug-msg (string &rest args) | ||
| 4060 | "Print newsticker debug messages. | ||
| 4061 | This function calls `message' with arguments STRING and ARGS, if | ||
| 4062 | `newsticker-debug' is non-nil." | ||
| 4063 | (and newsticker-debug | ||
| 4064 | ;;(not (active-minibuffer-window)) | ||
| 4065 | ;;(not (current-message)) | ||
| 4066 | (apply 'message string args))) | ||
| 4067 | |||
| 4068 | (defun newsticker--decode-iso8601-date (iso8601-string) | ||
| 4069 | "Return ISO8601-STRING in format like `decode-time'. | ||
| 4070 | Converts from ISO-8601 to Emacs representation. | ||
| 4071 | Examples: | ||
| 4072 | 2004-09-17T05:09:49+00:00 | ||
| 4073 | 2004-09-17T05:09+00:00 | ||
| 4074 | 2004-09-17T05:09:49 | ||
| 4075 | 2004-09-17T05:09 | ||
| 4076 | 2004-09-17 | ||
| 4077 | 2004-09 | ||
| 4078 | 2004" | ||
| 4079 | (if iso8601-string | ||
| 4080 | (when (string-match | ||
| 4081 | (concat | ||
| 4082 | "^ *\\([0-9]\\{4\\}\\)" | ||
| 4083 | "\\(-\\([0-9]\\{2\\}\\)" | ||
| 4084 | "\\(-\\([0-9]\\{2\\}\\)" | ||
| 4085 | "\\(T" | ||
| 4086 | "\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)" | ||
| 4087 | "\\(:\\([0-9]\\{2\\}\\)\\)?" | ||
| 4088 | "\\(\\([-+Z]\\)\\(\\([0-9]\\{2\\}\\):\\([0-9]\\{2\\}\\)\\)?\\)?" | ||
| 4089 | "\\)?\\)?\\)? *$") | ||
| 4090 | iso8601-string) | ||
| 4091 | (let ((year (read (match-string 1 iso8601-string))) | ||
| 4092 | (month (read (or (match-string 3 iso8601-string) | ||
| 4093 | "1"))) | ||
| 4094 | (day (read (or (match-string 5 iso8601-string) | ||
| 4095 | "1"))) | ||
| 4096 | (hour (read (or (match-string 7 iso8601-string) | ||
| 4097 | "0"))) | ||
| 4098 | (minute (read (or (match-string 8 iso8601-string) | ||
| 4099 | "0"))) | ||
| 4100 | ;;(second (read (or (match-string 10 iso8601-string) | ||
| 4101 | ;; "0"))) | ||
| 4102 | (sign (match-string 12 iso8601-string)) | ||
| 4103 | (offset-hour (read (or (match-string 14 iso8601-string) | ||
| 4104 | "0"))) | ||
| 4105 | (offset-minute (read (or (match-string 15 iso8601-string) | ||
| 4106 | "0"))) | ||
| 4107 | (second 0)) | ||
| 4108 | (cond ((string= sign "+") | ||
| 4109 | (setq hour (- hour offset-hour)) | ||
| 4110 | (setq minute (- minute offset-minute))) | ||
| 4111 | ((string= sign "-") | ||
| 4112 | (setq hour (+ hour offset-hour)) | ||
| 4113 | (setq minute (+ minute offset-minute)))) | ||
| 4114 | ;; if UTC subtract current-time-zone offset | ||
| 4115 | ;;(setq second (+ (car (current-time-zone)) second))) | ||
| 4116 | |||
| 4117 | (condition-case nil | ||
| 4118 | (encode-time second minute hour day month year t) | ||
| 4119 | (error | ||
| 4120 | (message "Cannot decode \"%s\"" iso8601-string) | ||
| 4121 | nil)))) | ||
| 4122 | nil)) | ||
| 4123 | |||
| 4124 | (defun newsticker--decode-rfc822-date (rfc822-string) | ||
| 4125 | "Return RFC822-STRING in format like `decode-time'. | ||
| 4126 | Converts from RFC822 to Emacs representation. | ||
| 4127 | Examples: | ||
| 4128 | Sat, 07 Sep 2002 00:00:01 GMT | ||
| 4129 | 07 Sep 2002 00:00:01 GMT | ||
| 4130 | 07 Sep 2002" | ||
| 4131 | (if (and rfc822-string (stringp rfc822-string)) | ||
| 4132 | (when (string-match | ||
| 4133 | (concat | ||
| 4134 | "\\s-*" | ||
| 4135 | ;; week day | ||
| 4136 | "\\(\\(Mon\\|Tue\\|Wed\\|Thu\\|Fri\\|Sat\\|Sun\\)\\s-*,?\\)?\\s-*" | ||
| 4137 | ;; day | ||
| 4138 | "\\([0-9]\\{1,2\\}\\)\\s-+" | ||
| 4139 | ;; month | ||
| 4140 | "\\(Jan\\|Feb\\|Mar\\|Apr\\|May\\|Jun\\|Jul\\|Aug\\|" | ||
| 4141 | "Sep\\|Oct\\|Nov\\|Dec\\)\\s-+" | ||
| 4142 | ;; year | ||
| 4143 | "\\([0-9]\\{2,4\\}\\)" | ||
| 4144 | ;; time may be missing | ||
| 4145 | "\\(\\s-+" | ||
| 4146 | ;; hour | ||
| 4147 | "\\([0-9]\\{2\\}\\)" | ||
| 4148 | ;; minute | ||
| 4149 | ":\\([0-9]\\{2\\}\\)" | ||
| 4150 | ;; second | ||
| 4151 | "\\(:\\([0-9]\\{2\\}\\)\\)?" | ||
| 4152 | ;; zone -- fixme | ||
| 4153 | "\\(\\s-+.*\\)?" | ||
| 4154 | "\\)?") | ||
| 4155 | rfc822-string) | ||
| 4156 | (let ((day (read (match-string 3 rfc822-string))) | ||
| 4157 | (month-name (match-string 4 rfc822-string)) | ||
| 4158 | (month 0) | ||
| 4159 | (year (read (match-string 5 rfc822-string))) | ||
| 4160 | (hour (read (or (match-string 7 rfc822-string) "0"))) | ||
| 4161 | (minute (read (or (match-string 8 rfc822-string) "0"))) | ||
| 4162 | (second (read (or (match-string 10 rfc822-string) "0"))) | ||
| 4163 | ;;(zone (match-string 11 rfc822-string)) | ||
| 4164 | ) | ||
| 4165 | (condition-case error-data | ||
| 4166 | (let ((i 1)) | ||
| 4167 | (mapc (lambda (m) | ||
| 4168 | (if (string= month-name m) | ||
| 4169 | (setq month i)) | ||
| 4170 | (setq i (1+ i))) | ||
| 4171 | '("Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" | ||
| 4172 | "Sep" "Oct" "Nov" "Dec")) | ||
| 4173 | (encode-time second minute hour day month year t)) | ||
| 4174 | (error | ||
| 4175 | (message "Cannot decode \"%s\": %s %s" rfc822-string | ||
| 4176 | (car error-data) (cdr error-data)) | ||
| 4177 | nil)))) | ||
| 4178 | nil)) | ||
| 4179 | |||
| 4180 | (defun newsticker--lists-intersect-p (list1 list2) | ||
| 4181 | "Return t if LIST1 and LIST2 share elements." | ||
| 4182 | (let ((result nil)) | ||
| 4183 | (mapc (lambda (elt) | ||
| 4184 | (if (memq elt list2) | ||
| 4185 | (setq result t))) | ||
| 4186 | list1) | ||
| 4187 | result)) | ||
| 4188 | |||
| 4189 | (defun newsticker--update-process-ids () | ||
| 4190 | "Update list of ids of active newsticker processes. | ||
| 4191 | Checks list of active processes against list of newsticker processes." | ||
| 4192 | (let ((active-procs (process-list)) | ||
| 4193 | (new-list nil)) | ||
| 4194 | (mapc (lambda (proc) | ||
| 4195 | (let ((id (process-id proc))) | ||
| 4196 | (if (memq id newsticker--process-ids) | ||
| 4197 | (setq new-list (cons id new-list))))) | ||
| 4198 | active-procs) | ||
| 4199 | (setq newsticker--process-ids new-list)) | ||
| 4200 | (force-mode-line-update)) | ||
| 4201 | |||
| 4202 | ;; ====================================================================== | ||
| 4203 | ;;; images | ||
| 4204 | ;; ====================================================================== | ||
| 4205 | (defun newsticker--image-get (feed-name url) | ||
| 4206 | "Get image of the news site FEED-NAME from URL. | ||
| 4207 | If the image has been downloaded in the last 24h do nothing." | ||
| 4208 | (let ((image-name (concat newsticker-imagecache-dirname "/" | ||
| 4209 | feed-name))) | ||
| 4210 | (if (and (file-exists-p image-name) | ||
| 4211 | (time-less-p (current-time) | ||
| 4212 | (time-add (nth 5 (file-attributes image-name)) | ||
| 4213 | (seconds-to-time 86400)))) | ||
| 4214 | (newsticker--debug-msg "%s: Getting image for %s skipped" | ||
| 4215 | (format-time-string "%A, %H:%M" (current-time)) | ||
| 4216 | feed-name) | ||
| 4217 | ;; download | ||
| 4218 | (newsticker--debug-msg "%s: Getting image for %s" | ||
| 4219 | (format-time-string "%A, %H:%M" (current-time)) | ||
| 4220 | feed-name) | ||
| 4221 | (let* ((buffername (concat " *newsticker-wget-image-" feed-name "*")) | ||
| 4222 | (item (or (assoc feed-name newsticker-url-list) | ||
| 4223 | (assoc feed-name newsticker-url-list-defaults) | ||
| 4224 | (error | ||
| 4225 | "Cannot get news for %s: Check newsticker-url-list" | ||
| 4226 | feed-name))) | ||
| 4227 | (wget-arguments (or (car (cdr (cdr (cdr (cdr item))))) | ||
| 4228 | newsticker-wget-arguments))) | ||
| 4229 | (save-excursion | ||
| 4230 | (set-buffer (get-buffer-create buffername)) | ||
| 4231 | (erase-buffer) | ||
| 4232 | ;; throw an error if there is an old wget-process around | ||
| 4233 | (if (get-process feed-name) | ||
| 4234 | (error "Another wget-process is running for image %s" | ||
| 4235 | feed-name)) | ||
| 4236 | ;; start wget | ||
| 4237 | (let* ((args (append wget-arguments (list url))) | ||
| 4238 | (proc (apply 'start-process feed-name buffername | ||
| 4239 | newsticker-wget-name args))) | ||
| 4240 | (set-process-coding-system proc 'no-conversion 'no-conversion) | ||
| 4241 | (set-process-sentinel proc 'newsticker--image-sentinel))))))) | ||
| 4242 | |||
| 4243 | (defun newsticker--image-sentinel (process event) | ||
| 4244 | "Sentinel for image-retrieving PROCESS caused by EVENT." | ||
| 4245 | (let* ((p-status (process-status process)) | ||
| 4246 | (exit-status (process-exit-status process)) | ||
| 4247 | (feed-name (process-name process))) | ||
| 4248 | ;; catch known errors (zombie processes, rubbish-xml, etc.) | ||
| 4249 | ;; if an error occurs the news feed is not updated! | ||
| 4250 | (catch 'oops | ||
| 4251 | (unless (and (eq p-status 'exit) | ||
| 4252 | (= exit-status 0)) | ||
| 4253 | (message "%s: Error while retrieving image from %s" | ||
| 4254 | (format-time-string "%A, %H:%M" (current-time)) | ||
| 4255 | feed-name) | ||
| 4256 | (throw 'oops nil)) | ||
| 4257 | (let (image-name) | ||
| 4258 | (save-excursion | ||
| 4259 | (set-buffer (process-buffer process)) | ||
| 4260 | (setq image-name (concat newsticker-imagecache-dirname "/" | ||
| 4261 | feed-name)) | ||
| 4262 | (set-buffer-file-coding-system 'no-conversion) | ||
| 4263 | ;; make sure the cache dir exists | ||
| 4264 | (unless (file-directory-p newsticker-imagecache-dirname) | ||
| 4265 | (make-directory newsticker-imagecache-dirname)) | ||
| 4266 | ;; write and close buffer | ||
| 4267 | (let ((require-final-newline nil) | ||
| 4268 | (backup-inhibited t) | ||
| 4269 | (coding-system-for-write 'no-conversion)) | ||
| 4270 | (write-region nil nil image-name nil 'quiet)) | ||
| 4271 | (set-buffer-modified-p nil) | ||
| 4272 | (kill-buffer (current-buffer))))))) | ||
| 4273 | |||
| 4274 | (defun newsticker--image-read (feed-name-symbol disabled) | ||
| 4275 | "Read the cached image for FEED-NAME-SYMBOL from disk. | ||
| 4276 | If DISABLED is non-nil the image will be converted to a disabled look | ||
| 4277 | \(unless `newsticker-enable-logo-manipulations' is not t\). | ||
| 4278 | Return the image." | ||
| 4279 | (let ((image-name (concat newsticker-imagecache-dirname "/" | ||
| 4280 | (symbol-name feed-name-symbol))) | ||
| 4281 | (img nil)) | ||
| 4282 | (when (file-exists-p image-name) | ||
| 4283 | (condition-case error-data | ||
| 4284 | (setq img (create-image | ||
| 4285 | image-name nil nil | ||
| 4286 | :conversion (and newsticker-enable-logo-manipulations | ||
| 4287 | disabled | ||
| 4288 | 'disabled) | ||
| 4289 | :mask (and newsticker-enable-logo-manipulations | ||
| 4290 | 'heuristic) | ||
| 4291 | :ascent 70)) | ||
| 4292 | (error | ||
| 4293 | (message "Error: cannot create image for %s: %s" | ||
| 4294 | feed-name-symbol error-data)))) | ||
| 4295 | img)) | ||
| 4296 | |||
| 4297 | ;; ====================================================================== | ||
| 4298 | ;;; imenu stuff | ||
| 4299 | ;; ====================================================================== | ||
| 4300 | (defun newsticker--imenu-create-index () | ||
| 4301 | "Scan newsticker buffer and return an index for imenu." | ||
| 4302 | (save-excursion | ||
| 4303 | (goto-char (point-min)) | ||
| 4304 | (let ((index-alist nil) | ||
| 4305 | (feed-list nil) | ||
| 4306 | (go-ahead t)) | ||
| 4307 | (while go-ahead | ||
| 4308 | (let ((type (get-text-property (point) 'nt-type)) | ||
| 4309 | (title (get-text-property (point) 'nt-title))) | ||
| 4310 | (cond ((eq type 'feed) | ||
| 4311 | ;; we're on a feed heading | ||
| 4312 | (when feed-list | ||
| 4313 | (if index-alist | ||
| 4314 | (nconc index-alist (list feed-list)) | ||
| 4315 | (setq index-alist (list feed-list)))) | ||
| 4316 | (setq feed-list (list title))) | ||
| 4317 | (t | ||
| 4318 | (nconc feed-list | ||
| 4319 | (list (cons title (point))))))) | ||
| 4320 | (setq go-ahead (newsticker--buffer-goto '(item feed)))) | ||
| 4321 | (if index-alist | ||
| 4322 | (nconc index-alist (list feed-list)) | ||
| 4323 | (setq index-alist (list feed-list))) | ||
| 4324 | index-alist))) | ||
| 4325 | |||
| 4326 | (defun newsticker--imenu-goto (name pos &rest args) | ||
| 4327 | "Go to item NAME at position POS and show item. | ||
| 4328 | ARGS are ignored." | ||
| 4329 | (goto-char pos) | ||
| 4330 | ;; show headline | ||
| 4331 | (newsticker--buffer-goto '(desc extra feed item)) | ||
| 4332 | (let* ((inhibit-read-only t) | ||
| 4333 | (pos1 (max (point-min) (1- pos))) | ||
| 4334 | (pos2 (max pos1 (1- (point)))) | ||
| 4335 | (inv-prop (get-text-property pos 'invisible)) | ||
| 4336 | (org-inv-prop (get-text-property pos 'org-invisible))) | ||
| 4337 | (when (eq org-inv-prop nil) | ||
| 4338 | (add-text-properties pos1 pos2 (list 'invisible nil | ||
| 4339 | 'org-invisible inv-prop)))) | ||
| 4340 | ;; show desc | ||
| 4341 | (newsticker-show-entry)) | ||
| 4342 | |||
| 4343 | ;; ====================================================================== | ||
| 4344 | ;;; buffer stuff | ||
| 4345 | ;; ====================================================================== | ||
| 4346 | (defun newsticker--buffer-set-uptodate (value) | ||
| 4347 | "Set the uptodate-status of the newsticker buffer to VALUE. | ||
| 4348 | The mode-line is changed accordingly." | ||
| 4349 | (setq newsticker--buffer-uptodate-p value) | ||
| 4350 | (let ((b (get-buffer "*newsticker*"))) | ||
| 4351 | (when b | ||
| 4352 | (save-excursion | ||
| 4353 | (set-buffer b) | ||
| 4354 | (if value | ||
| 4355 | (setq mode-name "Newsticker -- up to date -- ") | ||
| 4356 | (setq mode-name "Newsticker -- NEED UPDATE -- "))) | ||
| 4357 | (force-mode-line-update 0)))) | ||
| 4358 | |||
| 4359 | (defun newsticker--buffer-redraw () | ||
| 4360 | "Redraw the newsticker window." | ||
| 4361 | (if (fboundp 'force-window-update) | ||
| 4362 | (force-window-update (current-buffer)) | ||
| 4363 | (redraw-frame (selected-frame))) | ||
| 4364 | (run-hooks 'newsticker-buffer-change-hook) | ||
| 4365 | (sit-for 0)) | ||
| 4366 | |||
| 4367 | (defun newsticker--buffer-insert-all-items () | ||
| 4368 | "Insert all cached newsticker items into the current buffer. | ||
| 4369 | Keeps order of feeds as given in `newsticker-url-list' and | ||
| 4370 | `newsticker-url-list-defaults'." | ||
| 4371 | (goto-char (point-min)) | ||
| 4372 | (mapc (lambda (url-item) | ||
| 4373 | (let* ((feed-name (car url-item)) | ||
| 4374 | (feed-name-symbol (intern feed-name)) | ||
| 4375 | (feed (assoc feed-name-symbol newsticker--cache)) | ||
| 4376 | (items (cdr feed)) | ||
| 4377 | (pos (point))) | ||
| 4378 | (when feed | ||
| 4379 | ;; insert the feed description | ||
| 4380 | (mapc (lambda (item) | ||
| 4381 | (when (eq (newsticker--age item) 'feed) | ||
| 4382 | (newsticker--buffer-insert-item item | ||
| 4383 | feed-name-symbol))) | ||
| 4384 | items) | ||
| 4385 | ;;insert the items | ||
| 4386 | (mapc (lambda (item) | ||
| 4387 | (if (memq (newsticker--age item) '(new immortal old | ||
| 4388 | obsolete)) | ||
| 4389 | (newsticker--buffer-insert-item item | ||
| 4390 | feed-name-symbol))) | ||
| 4391 | items) | ||
| 4392 | (put-text-property pos (point) 'feed (car feed)) | ||
| 4393 | |||
| 4394 | ;; insert empty line between feeds | ||
| 4395 | (let ((p (point))) | ||
| 4396 | (insert "\n") | ||
| 4397 | (put-text-property p (point) 'hard t))))) | ||
| 4398 | (append newsticker-url-list newsticker-url-list-defaults)) | ||
| 4399 | |||
| 4400 | (newsticker--buffer-set-faces (point-min) (point-max)) | ||
| 4401 | (newsticker--buffer-set-invisibility (point-min) (point-max)) | ||
| 4402 | (goto-char (point-min))) | ||
| 4403 | |||
| 4404 | (defun newsticker--buffer-insert-item (item &optional feed-name-symbol) | ||
| 4405 | "Insert a news item in the current buffer. | ||
| 4406 | Insert a formatted representation of the ITEM. The optional parameter | ||
| 4407 | FEED-NAME-SYMBOL determines how the item is formatted and whether the | ||
| 4408 | item-retrieval time is added as well." | ||
| 4409 | ;; insert headline | ||
| 4410 | (if (eq (newsticker--age item) 'feed) | ||
| 4411 | (newsticker--buffer-do-insert-text item 'feed feed-name-symbol) | ||
| 4412 | (newsticker--buffer-do-insert-text item 'item feed-name-symbol)) | ||
| 4413 | ;; insert the description | ||
| 4414 | (newsticker--buffer-do-insert-text item 'desc feed-name-symbol)) | ||
| 4415 | |||
| 4416 | (defun newsticker--buffer-do-insert-text (item type feed-name-symbol) | ||
| 4417 | "Actually insert contents of news item, format it, render it and all that. | ||
| 4418 | ITEM is a news item, TYPE tells which part of the item shall be inserted, | ||
| 4419 | FEED-NAME-SYMBOL tells to which feed this item belongs." | ||
| 4420 | (let* ((pos (point)) | ||
| 4421 | (format newsticker-desc-format) | ||
| 4422 | (pos-date-start nil) | ||
| 4423 | (pos-date-end nil) | ||
| 4424 | (pos-stat-start nil) | ||
| 4425 | (pos-stat-end nil) | ||
| 4426 | (pos-text-start nil) | ||
| 4427 | (pos-text-end nil) | ||
| 4428 | (pos-extra-start nil) | ||
| 4429 | (pos-extra-end nil) | ||
| 4430 | (pos-enclosure-start nil) | ||
| 4431 | (pos-enclosure-end nil) | ||
| 4432 | (age (newsticker--age item)) | ||
| 4433 | (preformatted-contents (newsticker--preformatted-contents item)) | ||
| 4434 | (preformatted-title (newsticker--preformatted-title item))) | ||
| 4435 | (cond ((and preformatted-contents | ||
| 4436 | (not (eq (aref preformatted-contents 0) ?\n));; we must | ||
| 4437 | ;; NOT have a line | ||
| 4438 | ;; break! | ||
| 4439 | (eq type 'desc)) | ||
| 4440 | (insert preformatted-contents)) | ||
| 4441 | ((and preformatted-title | ||
| 4442 | (not (eq (aref preformatted-title 0) ?\n));; we must NOT have a | ||
| 4443 | ;; line break! | ||
| 4444 | (eq type 'item)) | ||
| 4445 | (insert preformatted-title)) | ||
| 4446 | (t | ||
| 4447 | ;; item was not formatted before. | ||
| 4448 | ;; Let's go. | ||
| 4449 | (if (eq type 'item) | ||
| 4450 | (setq format newsticker-item-format) | ||
| 4451 | (if (eq type 'feed) | ||
| 4452 | (setq format newsticker-heading-format))) | ||
| 4453 | |||
| 4454 | (while (> (length format) 0) | ||
| 4455 | (let ((prefix (if (> (length format) 1) | ||
| 4456 | (substring format 0 2) | ||
| 4457 | ""))) | ||
| 4458 | (cond ((string= "%c" prefix) | ||
| 4459 | ;; contents | ||
| 4460 | (when (newsticker--desc item) | ||
| 4461 | (setq pos-text-start (point-marker)) | ||
| 4462 | (insert (newsticker--desc item)) | ||
| 4463 | (setq pos-text-end (point-marker))) | ||
| 4464 | (setq format (substring format 2))) | ||
| 4465 | ((string= "%d" prefix) | ||
| 4466 | ;; date | ||
| 4467 | (setq pos-date-start (point-marker)) | ||
| 4468 | (if (newsticker--time item) | ||
| 4469 | (insert (format-time-string newsticker-date-format | ||
| 4470 | (newsticker--time item)))) | ||
| 4471 | (setq pos-date-end (point-marker)) | ||
| 4472 | (setq format (substring format 2))) | ||
| 4473 | ((string= "%l" prefix) | ||
| 4474 | ;; logo | ||
| 4475 | (let ((disabled (cond ((eq (newsticker--age item) 'feed) | ||
| 4476 | (= (newsticker--stat-num-items | ||
| 4477 | feed-name-symbol 'new) 0)) | ||
| 4478 | (t | ||
| 4479 | (not (eq (newsticker--age item) | ||
| 4480 | 'new)))))) | ||
| 4481 | (let ((img (newsticker--image-read feed-name-symbol | ||
| 4482 | disabled))) | ||
| 4483 | (when img | ||
| 4484 | (newsticker--insert-image img (car item))))) | ||
| 4485 | (setq format (substring format 2))) | ||
| 4486 | ((string= "%L" prefix) | ||
| 4487 | ;; logo or title | ||
| 4488 | (let ((disabled (cond ((eq (newsticker--age item) 'feed) | ||
| 4489 | (= (newsticker--stat-num-items | ||
| 4490 | feed-name-symbol 'new) 0)) | ||
| 4491 | (t | ||
| 4492 | (not (eq (newsticker--age item) | ||
| 4493 | 'new)))))) | ||
| 4494 | (let ((img (newsticker--image-read feed-name-symbol | ||
| 4495 | disabled))) | ||
| 4496 | (if img | ||
| 4497 | (newsticker--insert-image img (car item)) | ||
| 4498 | (when (car item) | ||
| 4499 | (setq pos-text-start (point-marker)) | ||
| 4500 | (if (eq (newsticker--age item) 'feed) | ||
| 4501 | (insert (newsticker--title item)) | ||
| 4502 | ;; FIXME: This is not the "real" title! | ||
| 4503 | (insert (format "%s" | ||
| 4504 | (car (newsticker--cache-get-feed | ||
| 4505 | feed-name-symbol))))) | ||
| 4506 | (setq pos-text-end (point-marker)))))) | ||
| 4507 | (setq format (substring format 2))) | ||
| 4508 | ((string= "%s" prefix) | ||
| 4509 | ;; statistics | ||
| 4510 | (setq pos-stat-start (point-marker)) | ||
| 4511 | (if (eq (newsticker--age item) 'feed) | ||
| 4512 | (insert (newsticker--buffer-statistics | ||
| 4513 | feed-name-symbol))) | ||
| 4514 | (setq pos-stat-end (point-marker)) | ||
| 4515 | (setq format (substring format 2))) | ||
| 4516 | ((string= "%t" prefix) | ||
| 4517 | ;; title | ||
| 4518 | (when (car item) | ||
| 4519 | (setq pos-text-start (point-marker)) | ||
| 4520 | (insert (car item)) | ||
| 4521 | (setq pos-text-end (point-marker))) | ||
| 4522 | (setq format (substring format 2))) | ||
| 4523 | ((string-match "%." prefix) | ||
| 4524 | ;; unknown specifier! | ||
| 4525 | (insert prefix) | ||
| 4526 | (setq format (substring format 2))) | ||
| 4527 | ((string-match "^\\([^%]*\\)\\(.*\\)" format) ;; FIXME! | ||
| 4528 | ;; everything else | ||
| 4529 | (let ((p (point))) | ||
| 4530 | (insert (substring format | ||
| 4531 | (match-beginning 1) (match-end 1))) | ||
| 4532 | ;; in case that the format string contained newlines | ||
| 4533 | (put-text-property p (point) 'hard t)) | ||
| 4534 | (setq format (substring format (match-beginning 2))))))) | ||
| 4535 | |||
| 4536 | ;; decode HTML if possible... | ||
| 4537 | (let ((is-rendered-HTML nil)) | ||
| 4538 | (when (and newsticker-html-renderer pos-text-start pos-text-end) | ||
| 4539 | (condition-case error-data | ||
| 4540 | (save-excursion | ||
| 4541 | ;; check whether it is necessary to call html renderer | ||
| 4542 | ;; (regexp inspired by htmlr.el) | ||
| 4543 | (goto-char pos-text-start) | ||
| 4544 | (when (re-search-forward | ||
| 4545 | "</?[A-Za-z1-6]*\\|&#?[A-Za-z0-9]+;" pos-text-end t) | ||
| 4546 | ;; (message "%s" (newsticker--title item)) | ||
| 4547 | (let ((w3m-fill-column (if newsticker-use-full-width | ||
| 4548 | -1 fill-column)) | ||
| 4549 | (w3-maximum-line-length | ||
| 4550 | (if newsticker-use-full-width nil fill-column))) | ||
| 4551 | (save-excursion | ||
| 4552 | (funcall newsticker-html-renderer pos-text-start | ||
| 4553 | pos-text-end))) | ||
| 4554 | (cond ((eq newsticker-html-renderer 'w3m-region) | ||
| 4555 | (add-text-properties pos (point-max) | ||
| 4556 | (list 'keymap | ||
| 4557 | w3m-minor-mode-map))) | ||
| 4558 | ((eq newsticker-html-renderer 'w3-region) | ||
| 4559 | (add-text-properties pos (point-max) | ||
| 4560 | (list 'keymap w3-mode-map)))) | ||
| 4561 | (setq is-rendered-HTML t))) | ||
| 4562 | (error | ||
| 4563 | (message "Error: HTML rendering failed: %s, %s" | ||
| 4564 | (car error-data) (cdr error-data))))) | ||
| 4565 | ;; After html rendering there might be chunks of blank | ||
| 4566 | ;; characters between rendered text and date, statistics or | ||
| 4567 | ;; whatever. Remove it | ||
| 4568 | (when (and (eq type 'item) is-rendered-HTML) | ||
| 4569 | (goto-char pos) | ||
| 4570 | (while (re-search-forward "[ \t]*\n[ \t]*" nil t) | ||
| 4571 | (replace-match " " nil nil)) | ||
| 4572 | (goto-char (point-max))) | ||
| 4573 | (when (and newsticker-justification | ||
| 4574 | (memq type '(item desc)) | ||
| 4575 | (not is-rendered-HTML)) | ||
| 4576 | (condition-case nil | ||
| 4577 | (let ((use-hard-newlines t)) | ||
| 4578 | (fill-region pos (point-max) newsticker-justification)) | ||
| 4579 | (error nil)))) | ||
| 4580 | |||
| 4581 | ;; remove leading and trailing newlines | ||
| 4582 | (goto-char pos) | ||
| 4583 | (unless (= 0 (skip-chars-forward " \t\r\n")) | ||
| 4584 | (delete-region pos (point))) | ||
| 4585 | (goto-char (point-max)) | ||
| 4586 | (let ((end (point))) | ||
| 4587 | (unless (= 0 (skip-chars-backward " \t\r\n" (1+ pos))) | ||
| 4588 | (delete-region (point) end))) | ||
| 4589 | (goto-char (point-max)) | ||
| 4590 | ;; closing newline | ||
| 4591 | (unless nil ;;(eq pos (point)) | ||
| 4592 | (insert "\n") | ||
| 4593 | (put-text-property (1- (point)) (point) 'hard t)) | ||
| 4594 | |||
| 4595 | ;; insert enclosure element | ||
| 4596 | (when (eq type 'desc) | ||
| 4597 | (setq pos-enclosure-start (point)) | ||
| 4598 | (newsticker--buffer-insert-enclosure item) | ||
| 4599 | (setq pos-enclosure-end (point))) | ||
| 4600 | |||
| 4601 | ;; show extra elements | ||
| 4602 | (when (eq type 'desc) | ||
| 4603 | (goto-char (point-max)) | ||
| 4604 | (setq pos-extra-start (point)) | ||
| 4605 | (newsticker--buffer-print-extra-elements item) | ||
| 4606 | (setq pos-extra-end (point))) | ||
| 4607 | |||
| 4608 | ;; text properties | ||
| 4609 | (when (memq type '(feed item)) | ||
| 4610 | (add-text-properties pos (1- (point)) | ||
| 4611 | (list 'mouse-face 'highlight | ||
| 4612 | 'nt-link (newsticker--link item) | ||
| 4613 | 'help-echo | ||
| 4614 | (format "mouse-2: visit item (%s)" | ||
| 4615 | (newsticker--link item)) | ||
| 4616 | 'keymap newsticker--url-keymap)) | ||
| 4617 | (add-text-properties pos (point) | ||
| 4618 | (list 'nt-title (newsticker--title item) | ||
| 4619 | 'nt-desc (newsticker--desc item)))) | ||
| 4620 | |||
| 4621 | (add-text-properties pos (point) | ||
| 4622 | (list 'nt-type type | ||
| 4623 | 'nt-face type | ||
| 4624 | 'nt-age age | ||
| 4625 | 'nt-guid (newsticker--guid item))) | ||
| 4626 | (when (and pos-date-start pos-date-end) | ||
| 4627 | (put-text-property pos-date-start pos-date-end 'nt-face 'date)) | ||
| 4628 | (when (and pos-stat-start pos-stat-end) | ||
| 4629 | (put-text-property pos-stat-start pos-stat-end 'nt-face 'stat)) | ||
| 4630 | (when (and pos-extra-start pos-extra-end) | ||
| 4631 | (put-text-property pos-extra-start pos-extra-end | ||
| 4632 | 'nt-face 'extra) | ||
| 4633 | (put-text-property pos-extra-start pos-extra-end | ||
| 4634 | 'nt-type 'extra)) | ||
| 4635 | (when (and pos-enclosure-start pos-enclosure-end | ||
| 4636 | (> pos-enclosure-end pos-enclosure-start)) | ||
| 4637 | (put-text-property pos-enclosure-start (1- pos-enclosure-end) | ||
| 4638 | 'nt-face 'enclosure)) | ||
| 4639 | |||
| 4640 | ;; left margin | ||
| 4641 | ;;(unless (memq type '(feed item)) | ||
| 4642 | ;;(set-left-margin pos (1- (point)) 1)) | ||
| 4643 | |||
| 4644 | ;; save rendered stuff | ||
| 4645 | (cond ((eq type 'desc) | ||
| 4646 | ;; preformatted contents | ||
| 4647 | (newsticker--cache-set-preformatted-contents | ||
| 4648 | item (buffer-substring pos (point)))) | ||
| 4649 | ((eq type 'item) | ||
| 4650 | ;; preformatted title | ||
| 4651 | (newsticker--cache-set-preformatted-title | ||
| 4652 | item (buffer-substring pos (point))))))))) | ||
| 4653 | |||
| 4654 | (defun newsticker--buffer-print-extra-elements (item) | ||
| 4655 | "Insert extra-elements of ITEM in a pretty form into the current buffer." | ||
| 4656 | (let ((ignored-elements '(items link title description | ||
| 4657 | content:encoded | ||
| 4658 | dc:subject dc:date item guid | ||
| 4659 | pubDate enclosure)) | ||
| 4660 | (left-column-width 1)) | ||
| 4661 | (mapc (lambda (extra-element) | ||
| 4662 | (unless (memq (car extra-element) ignored-elements) | ||
| 4663 | (setq left-column-width (max left-column-width | ||
| 4664 | (length (symbol-name | ||
| 4665 | (car extra-element))))))) | ||
| 4666 | (newsticker--extra item)) | ||
| 4667 | (mapc (lambda (extra-element) | ||
| 4668 | (unless (memq (car extra-element) ignored-elements) | ||
| 4669 | (newsticker--buffer-do-print-extra-element extra-element | ||
| 4670 | left-column-width))) | ||
| 4671 | (newsticker--extra item)))) | ||
| 4672 | |||
| 4673 | (defun newsticker--buffer-do-print-extra-element (extra-element width) | ||
| 4674 | "Actually print an EXTRA-ELEMENT using the given WIDTH." | ||
| 4675 | (let ((name (symbol-name (car extra-element)))) | ||
| 4676 | (insert (format "%s: " name)) | ||
| 4677 | (insert (make-string (- width (length name)) ? ))) | ||
| 4678 | (let (;;(attributes (cadr extra-element)) ;FIXME!!!! | ||
| 4679 | (contents (cddr extra-element))) | ||
| 4680 | (cond ((listp contents) | ||
| 4681 | (mapc (lambda (i) | ||
| 4682 | (if (and (stringp i) | ||
| 4683 | (string-match "^http://.*" i)) | ||
| 4684 | (let ((pos (point))) | ||
| 4685 | (insert i " ") ; avoid self-reference from the | ||
| 4686 | ; nt-link thing | ||
| 4687 | (add-text-properties | ||
| 4688 | pos (point) | ||
| 4689 | (list 'mouse-face 'highlight | ||
| 4690 | 'nt-link i | ||
| 4691 | 'help-echo | ||
| 4692 | (format "mouse-2: visit (%s)" i) | ||
| 4693 | 'keymap newsticker--url-keymap))) | ||
| 4694 | (insert (format "%s" i)))) | ||
| 4695 | contents)) | ||
| 4696 | (t | ||
| 4697 | (insert (format "%s" contents)))) | ||
| 4698 | (insert "\n"))) | ||
| 4699 | |||
| 4700 | (defun newsticker--buffer-insert-enclosure (item) | ||
| 4701 | "Insert enclosure element of a news ITEM into the current buffer." | ||
| 4702 | (let ((enclosure (newsticker--enclosure item)) | ||
| 4703 | (beg (point))) | ||
| 4704 | (when enclosure | ||
| 4705 | (let ((url (cdr (assoc 'url enclosure))) | ||
| 4706 | (length (string-to-number (or (cdr (assoc 'length enclosure)) | ||
| 4707 | "0"))) | ||
| 4708 | (type (cdr (assoc 'type enclosure)))) | ||
| 4709 | (cond ((> length 1048576) | ||
| 4710 | (insert (format "Enclosed file (%s, %1.2f MBytes)" type | ||
| 4711 | (/ length 1048576)))) | ||
| 4712 | ((> length 1024) | ||
| 4713 | (insert (format "Enclosed file (%s, %1.2f KBytes)" type | ||
| 4714 | (/ length 1024))))) | ||
| 4715 | (add-text-properties beg (point) | ||
| 4716 | (list 'mouse-face 'highlight | ||
| 4717 | 'nt-link url | ||
| 4718 | 'help-echo (format | ||
| 4719 | "mouse-2: visit (%s)" url) | ||
| 4720 | 'keymap newsticker--url-keymap | ||
| 4721 | 'nt-face 'enclosure | ||
| 4722 | 'nt-type 'desc)) | ||
| 4723 | (insert "\n"))))) | ||
| 4724 | |||
| 4725 | (defun newsticker--buffer-statistics (feed-name-symbol) | ||
| 4726 | "Return a statistic string for the feed given by FEED-NAME-SYMBOL. | ||
| 4727 | See `newsticker-statistics-format'." | ||
| 4728 | (let ((case-fold-search nil)) | ||
| 4729 | (replace-regexp-in-string | ||
| 4730 | "%a" | ||
| 4731 | (format "%d" (newsticker--stat-num-items feed-name-symbol)) | ||
| 4732 | (replace-regexp-in-string | ||
| 4733 | "%i" | ||
| 4734 | (format "%d" (newsticker--stat-num-items feed-name-symbol 'immortal)) | ||
| 4735 | (replace-regexp-in-string | ||
| 4736 | "%n" | ||
| 4737 | (format "%d" (newsticker--stat-num-items feed-name-symbol 'new)) | ||
| 4738 | (replace-regexp-in-string | ||
| 4739 | "%o" | ||
| 4740 | (format "%d" (newsticker--stat-num-items feed-name-symbol 'old)) | ||
| 4741 | (replace-regexp-in-string | ||
| 4742 | "%O" | ||
| 4743 | (format "%d" (newsticker--stat-num-items feed-name-symbol 'obsolete)) | ||
| 4744 | newsticker-statistics-format))))))) | ||
| 4745 | |||
| 4746 | (defun newsticker--buffer-set-faces (start end) | ||
| 4747 | "Add face properties according to mark property. | ||
| 4748 | Scans the buffer between START and END." | ||
| 4749 | (save-excursion | ||
| 4750 | ;;(put-text-property start end 'face 'newsticker-default-face) | ||
| 4751 | (goto-char start) | ||
| 4752 | (let ((pos1 start) | ||
| 4753 | (pos2 1) | ||
| 4754 | (nt-face (get-text-property start 'nt-face)) | ||
| 4755 | (nt-age (get-text-property start 'nt-age))) | ||
| 4756 | (when nt-face | ||
| 4757 | (setq pos2 (next-single-property-change (point) 'nt-face)) | ||
| 4758 | (newsticker--set-face-properties pos1 pos2 nt-face nt-age) | ||
| 4759 | (setq nt-face (get-text-property pos2 'nt-face)) | ||
| 4760 | (setq pos1 pos2)) | ||
| 4761 | (while (and (setq pos2 (next-single-property-change pos1 'nt-face)) | ||
| 4762 | (<= pos2 end) | ||
| 4763 | (> pos2 pos1)) | ||
| 4764 | (newsticker--set-face-properties pos1 pos2 nt-face nt-age) | ||
| 4765 | (setq nt-face (get-text-property pos2 'nt-face)) | ||
| 4766 | (setq nt-age (get-text-property pos2 'nt-age)) | ||
| 4767 | (setq pos1 pos2))))) | ||
| 4768 | |||
| 4769 | (defun newsticker--buffer-set-invisibility (start end) | ||
| 4770 | "Add invisibility properties according to nt-type property. | ||
| 4771 | Scans the buffer between START and END. Sets the 'invisible | ||
| 4772 | property to '(<nt-type>-<nt-age> <nt-type> <nt-age>)." | ||
| 4773 | (save-excursion | ||
| 4774 | ;; reset invisibility settings | ||
| 4775 | (put-text-property start end 'invisible nil) | ||
| 4776 | ;; let's go | ||
| 4777 | (goto-char start) | ||
| 4778 | (let ((pos1 start) | ||
| 4779 | (pos2 1) | ||
| 4780 | (nt-type (get-text-property start 'nt-type)) | ||
| 4781 | (nt-age (get-text-property start 'nt-age))) | ||
| 4782 | (when nt-type | ||
| 4783 | (setq pos2 (next-single-property-change (point) 'nt-type)) | ||
| 4784 | (put-text-property (max (point-min) pos1) (1- pos2) | ||
| 4785 | 'invisible | ||
| 4786 | (list (intern | ||
| 4787 | (concat | ||
| 4788 | (symbol-name | ||
| 4789 | (if (eq nt-type 'extra) 'desc nt-type)) | ||
| 4790 | "-" | ||
| 4791 | (symbol-name nt-age))) | ||
| 4792 | nt-type | ||
| 4793 | nt-age)) | ||
| 4794 | (setq nt-type (get-text-property pos2 'nt-type)) | ||
| 4795 | (setq pos1 pos2)) | ||
| 4796 | (while (and (setq pos2 (next-single-property-change pos1 'nt-type)) | ||
| 4797 | (<= pos2 end) | ||
| 4798 | (> pos2 pos1)) | ||
| 4799 | ;; must shift one char to the left in order to handle inivisible | ||
| 4800 | ;; newlines, motion in invisible text areas and all that correctly | ||
| 4801 | (put-text-property (1- pos1) (1- pos2) | ||
| 4802 | 'invisible | ||
| 4803 | (list (intern | ||
| 4804 | (concat | ||
| 4805 | (symbol-name | ||
| 4806 | (if (eq nt-type 'extra) 'desc nt-type)) | ||
| 4807 | "-" | ||
| 4808 | (symbol-name nt-age))) | ||
| 4809 | nt-type | ||
| 4810 | nt-age)) | ||
| 4811 | (setq nt-type (get-text-property pos2 'nt-type)) | ||
| 4812 | (setq nt-age (get-text-property pos2 'nt-age)) | ||
| 4813 | (setq pos1 pos2))))) | ||
| 4814 | |||
| 4815 | (defun newsticker--set-face-properties (pos1 pos2 nt-face age) | ||
| 4816 | "Set the face for the text between the positions POS1 and POS2. | ||
| 4817 | The face is chosen according the values of NT-FACE and AGE." | ||
| 4818 | (let ((face (cond ((eq nt-face 'feed) | ||
| 4819 | 'newsticker-feed-face) | ||
| 4820 | ((eq nt-face 'item) | ||
| 4821 | (cond ((eq age 'new) | ||
| 4822 | 'newsticker-new-item-face) | ||
| 4823 | ((eq age 'old) | ||
| 4824 | 'newsticker-old-item-face) | ||
| 4825 | ((eq age 'immortal) | ||
| 4826 | 'newsticker-immortal-item-face) | ||
| 4827 | ((eq age 'obsolete) | ||
| 4828 | 'newsticker-obsolete-item-face))) | ||
| 4829 | ((eq nt-face 'date) | ||
| 4830 | 'newsticker-date-face) | ||
| 4831 | ((eq nt-face 'stat) | ||
| 4832 | 'newsticker-statistics-face) | ||
| 4833 | ((eq nt-face 'extra) | ||
| 4834 | 'newsticker-extra-face) | ||
| 4835 | ((eq nt-face 'enclosure) | ||
| 4836 | 'newsticker-enclosure-face)))) | ||
| 4837 | (when face | ||
| 4838 | (put-text-property pos1 (max pos1 pos2) 'face face)))) | ||
| 4839 | |||
| 4840 | (defun newsticker--insert-image (img string) | ||
| 4841 | "Insert IMG with STRING at point." | ||
| 4842 | (insert-image img string)) | ||
| 4843 | |||
| 4844 | ;; ====================================================================== | ||
| 4845 | ;;; HTML rendering | ||
| 4846 | ;; ====================================================================== | ||
| 4847 | |||
| 4848 | ;; External. | ||
| 4849 | (declare-function htmlr-reset "ext:htmlr" ()) | ||
| 4850 | (declare-function htmlr-step "ext:htmlr" ()) | ||
| 4851 | |||
| 4852 | (defun newsticker-htmlr-render (pos1 pos2) ; | ||
| 4853 | "Replacement for `htmlr-render'. | ||
| 4854 | Renders the HTML code in the region POS1 to POS2 using htmlr." | ||
| 4855 | (let ((str (buffer-substring-no-properties pos1 pos2))) | ||
| 4856 | (delete-region pos1 pos2) | ||
| 4857 | (insert | ||
| 4858 | (with-temp-buffer | ||
| 4859 | (insert str) | ||
| 4860 | (goto-char (point-min)) | ||
| 4861 | ;; begin original htmlr-render | ||
| 4862 | (htmlr-reset) | ||
| 4863 | ;; something omitted here... | ||
| 4864 | (while (< (point) (point-max)) | ||
| 4865 | (htmlr-step)) | ||
| 4866 | ;; end original htmlr-render | ||
| 4867 | (newsticker--remove-whitespace (buffer-string)))))) | ||
| 4868 | |||
| 4869 | ;; ====================================================================== | ||
| 4870 | ;;; Functions working on the *newsticker* buffer | ||
| 4871 | ;; ====================================================================== | ||
| 4872 | (defun newsticker--buffer-make-item-completely-visible () | ||
| 4873 | "Scroll buffer until current item is completely visible." | ||
| 4874 | (when newsticker--auto-narrow-to-feed | ||
| 4875 | (let* ((min (or (save-excursion (newsticker--buffer-beginning-of-feed)) | ||
| 4876 | (point-min))) | ||
| 4877 | (max (or (save-excursion (newsticker--buffer-end-of-feed)) | ||
| 4878 | (point-max)))) | ||
| 4879 | (narrow-to-region min max))) | ||
| 4880 | (when newsticker--auto-narrow-to-item | ||
| 4881 | (let* ((min (or (save-excursion (newsticker--buffer-beginning-of-item)) | ||
| 4882 | (point-min))) | ||
| 4883 | (max (or (save-excursion (newsticker--buffer-end-of-item)) | ||
| 4884 | (point-max)))) | ||
| 4885 | (narrow-to-region min max))) | ||
| 4886 | (sit-for 0) | ||
| 4887 | ;; do not count lines and stuff because that does not work when images | ||
| 4888 | ;; are displayed. Do it the simple way: | ||
| 4889 | (save-excursion | ||
| 4890 | (newsticker--buffer-end-of-item) | ||
| 4891 | (unless (pos-visible-in-window-p) | ||
| 4892 | (recenter -1))) | ||
| 4893 | (unless (pos-visible-in-window-p) | ||
| 4894 | (recenter 0))) | ||
| 4895 | |||
| 4896 | (defun newsticker--buffer-get-feed-title-at-point () | ||
| 4897 | "Return feed symbol of headline at point." | ||
| 4898 | (format "%s" (or (get-text-property (point) 'feed) " "))) | ||
| 4899 | |||
| 4900 | (defun newsticker--buffer-get-item-title-at-point () | ||
| 4901 | "Return feed symbol of headline at point." | ||
| 4902 | (format "%s" (or (get-text-property (point) 'nt-title) " "))) | ||
| 4903 | |||
| 4904 | (defun newsticker--buffer-goto (types &optional age backwards) | ||
| 4905 | "Search next occurrence of TYPES in current buffer. | ||
| 4906 | TYPES is a list of symbols. If TYPES is found point is moved, if | ||
| 4907 | not point is left unchanged. If optional parameter AGE is not | ||
| 4908 | nil, the type AND the age must match. If BACKWARDS is t, search | ||
| 4909 | backwards." | ||
| 4910 | (let ((pos (save-excursion | ||
| 4911 | (save-restriction | ||
| 4912 | (widen) | ||
| 4913 | (catch 'found | ||
| 4914 | (let ((tpos (point))) | ||
| 4915 | (while (setq tpos | ||
| 4916 | (if backwards | ||
| 4917 | (if (eq tpos (point-min)) | ||
| 4918 | nil | ||
| 4919 | (or (previous-single-property-change | ||
| 4920 | tpos 'nt-type) | ||
| 4921 | (point-min))) | ||
| 4922 | (next-single-property-change | ||
| 4923 | tpos 'nt-type))) | ||
| 4924 | (and (memq (get-text-property tpos 'nt-type) types) | ||
| 4925 | (or (not age) | ||
| 4926 | (eq (get-text-property tpos 'nt-age) age)) | ||
| 4927 | (throw 'found tpos))))))))) | ||
| 4928 | (when pos | ||
| 4929 | (goto-char pos)) | ||
| 4930 | pos)) | ||
| 4931 | |||
| 4932 | (defun newsticker--buffer-hideshow (mark-age onoff) | ||
| 4933 | "Hide or show items of type MARK-AGE. | ||
| 4934 | If ONOFF is nil the item is hidden, otherwise it is shown." | ||
| 4935 | (if onoff | ||
| 4936 | (remove-from-invisibility-spec mark-age) | ||
| 4937 | (add-to-invisibility-spec mark-age))) | ||
| 4938 | |||
| 4939 | (defun newsticker--buffer-beginning-of-item () | ||
| 4940 | "Move point to the beginning of the item at point. | ||
| 4941 | Return new position." | ||
| 4942 | (if (bobp) | ||
| 4943 | (point) | ||
| 4944 | (let ((type (get-text-property (point) 'nt-type)) | ||
| 4945 | (typebefore (get-text-property (1- (point)) 'nt-type))) | ||
| 4946 | (if (and (memq type '(item feed)) | ||
| 4947 | (not (eq type typebefore))) | ||
| 4948 | (point) | ||
| 4949 | (newsticker--buffer-goto '(item feed) nil t) | ||
| 4950 | (point))))) | ||
| 4951 | |||
| 4952 | (defun newsticker--buffer-beginning-of-feed () | ||
| 4953 | "Move point to the beginning of the feed at point. | ||
| 4954 | Return new position." | ||
| 4955 | (if (bobp) | ||
| 4956 | (point) | ||
| 4957 | (let ((type (get-text-property (point) 'nt-type)) | ||
| 4958 | (typebefore (get-text-property (1- (point)) 'nt-type))) | ||
| 4959 | (if (and (memq type '(feed)) | ||
| 4960 | (not (eq type typebefore))) | ||
| 4961 | (point) | ||
| 4962 | (newsticker--buffer-goto '(feed) nil t) | ||
| 4963 | (point))))) | ||
| 4964 | |||
| 4965 | (defun newsticker--buffer-end-of-item () | ||
| 4966 | "Move point to the end of the item at point. | ||
| 4967 | Take care: end of item is at the end of its last line!" | ||
| 4968 | (when (newsticker--buffer-goto '(item feed nil)) | ||
| 4969 | (point))) | ||
| 4970 | |||
| 4971 | (defun newsticker--buffer-end-of-feed () | ||
| 4972 | "Move point to the end of the last item of the feed at point. | ||
| 4973 | Take care: end of item is at the end of its last line!" | ||
| 4974 | (when (newsticker--buffer-goto '(feed nil)) | ||
| 4975 | (backward-char 1) | ||
| 4976 | (point))) | ||
| 4977 | |||
| 4978 | ;; ====================================================================== | ||
| 4979 | ;;; manipulation of ticker text | ||
| 4980 | ;; ====================================================================== | ||
| 4981 | (defun newsticker--ticker-text-setup () | ||
| 4982 | "Build the ticker text which is scrolled or flashed in the echo area." | ||
| 4983 | ;; reset scrollable text | ||
| 4984 | (setq newsticker--scrollable-text "") | ||
| 4985 | (setq newsticker--item-list nil) | ||
| 4986 | (setq newsticker--item-position 0) | ||
| 4987 | ;; build scrollable text from cache data | ||
| 4988 | (let ((have-something nil)) | ||
| 4989 | (mapc | ||
| 4990 | (lambda (feed) | ||
| 4991 | (let ((feed-name (symbol-name (car feed)))) | ||
| 4992 | (let ((num-new (newsticker--stat-num-items (car feed) 'new)) | ||
| 4993 | (num-old (newsticker--stat-num-items (car feed) 'old)) | ||
| 4994 | (num-imm (newsticker--stat-num-items (car feed) 'immortal)) | ||
| 4995 | (num-obs (newsticker--stat-num-items (car feed) 'obsolete))) | ||
| 4996 | (when (or (> num-new 0) | ||
| 4997 | (and (> num-old 0) | ||
| 4998 | (not newsticker-hide-old-items-in-echo-area)) | ||
| 4999 | (and (> num-imm 0) | ||
| 5000 | (not newsticker-hide-immortal-items-in-echo-area)) | ||
| 5001 | (and (> num-obs 0) | ||
| 5002 | (not newsticker-hide-obsolete-items-in-echo-area))) | ||
| 5003 | (setq have-something t) | ||
| 5004 | (mapc | ||
| 5005 | (lambda (item) | ||
| 5006 | (let ((title (replace-regexp-in-string | ||
| 5007 | "[\r\n]+" " " | ||
| 5008 | (newsticker--title item))) | ||
| 5009 | (age (newsticker--age item))) | ||
| 5010 | (unless (string= title newsticker--error-headline) | ||
| 5011 | (when | ||
| 5012 | (or (eq age 'new) | ||
| 5013 | (and (eq age 'old) | ||
| 5014 | (not newsticker-hide-old-items-in-echo-area)) | ||
| 5015 | (and (eq age 'obsolete) | ||
| 5016 | (not | ||
| 5017 | newsticker-hide-obsolete-items-in-echo-area)) | ||
| 5018 | (and (eq age 'immortal) | ||
| 5019 | (not | ||
| 5020 | newsticker-hide-immortal-items-in-echo-area))) | ||
| 5021 | (setq title (newsticker--remove-whitespace title)) | ||
| 5022 | ;; add to flash list | ||
| 5023 | (add-to-list 'newsticker--item-list | ||
| 5024 | (concat feed-name ": " title) t) | ||
| 5025 | ;; and to the scrollable text | ||
| 5026 | (setq newsticker--scrollable-text | ||
| 5027 | (concat newsticker--scrollable-text | ||
| 5028 | " " feed-name ": " title " +++")))))) | ||
| 5029 | (cdr feed)))))) | ||
| 5030 | newsticker--cache) | ||
| 5031 | (when have-something | ||
| 5032 | (setq newsticker--scrollable-text | ||
| 5033 | (concat "+++ " | ||
| 5034 | (format-time-string "%A, %H:%M" | ||
| 5035 | newsticker--latest-update-time) | ||
| 5036 | " ++++++" newsticker--scrollable-text))))) | ||
| 5037 | |||
| 5038 | (defun newsticker--ticker-text-remove (feed title) | ||
| 5039 | "Remove the item of FEED with TITLE from the ticker text." | ||
| 5040 | ;; reset scrollable text | ||
| 5041 | (setq newsticker--item-position 0) | ||
| 5042 | (let ((feed-name (symbol-name feed)) | ||
| 5043 | (t-title (replace-regexp-in-string "[\r\n]+" " " title))) | ||
| 5044 | ;; remove from flash list | ||
| 5045 | (setq newsticker--item-list (remove (concat feed-name ": " t-title) | ||
| 5046 | newsticker--item-list)) | ||
| 5047 | ;; and from the scrollable text | ||
| 5048 | (setq newsticker--scrollable-text | ||
| 5049 | (replace-regexp-in-string | ||
| 5050 | (regexp-quote (concat " " feed-name ": " t-title " +++")) | ||
| 5051 | "" | ||
| 5052 | newsticker--scrollable-text)) | ||
| 5053 | (if (string-match (concat "^\\+\\+\\+ [A-Z][a-z]+, " | ||
| 5054 | "[012]?[0-9]:[0-9][0-9] \\+\\+\\+\\+\\+\\+$") | ||
| 5055 | newsticker--scrollable-text) | ||
| 5056 | (setq newsticker--scrollable-text "")))) | ||
| 5057 | |||
| 5058 | ;; ====================================================================== | ||
| 5059 | ;;; manipulation of cached data | ||
| 5060 | ;; ====================================================================== | ||
| 5061 | (defun newsticker--cache-set-preformatted-contents (item contents) | ||
| 5062 | "Set preformatted contents of ITEM to CONTENTS." | ||
| 5063 | (if (nthcdr 6 item) | ||
| 5064 | (setcar (nthcdr 6 item) contents) | ||
| 5065 | (setcdr (nthcdr 5 item) (list contents)))) | ||
| 5066 | |||
| 5067 | (defun newsticker--cache-set-preformatted-title (item title) | ||
| 5068 | "Set preformatted title of ITEM to TITLE." | ||
| 5069 | (if (nthcdr 7 item) | ||
| 5070 | (setcar (nthcdr 7 item) title) | ||
| 5071 | (setcdr (nthcdr 6 item) title))) | ||
| 5072 | |||
| 5073 | (defun newsticker--cache-replace-age (data feed old-age new-age) | ||
| 5074 | "Mark all items in DATA in FEED which carry age OLD-AGE with NEW-AGE. | ||
| 5075 | If FEED is 'any it applies to all feeds. If OLD-AGE is 'any, | ||
| 5076 | all marks are replaced by NEW-AGE. Removes all pre-formatted contents." | ||
| 5077 | (mapc (lambda (a-feed) | ||
| 5078 | (when (or (eq feed 'any) | ||
| 5079 | (eq (car a-feed) feed)) | ||
| 5080 | (let ((items (cdr a-feed))) | ||
| 5081 | (mapc (lambda (item) | ||
| 5082 | (when (or (eq old-age 'any) | ||
| 5083 | (eq (newsticker--age item) old-age)) | ||
| 5084 | (setcar (nthcdr 4 item) new-age) | ||
| 5085 | (newsticker--do-forget-preformatted item))) | ||
| 5086 | items)))) | ||
| 5087 | data) | ||
| 5088 | data) | ||
| 5089 | |||
| 5090 | (defun newsticker--cache-mark-expired (data feed old-age new-age time) | ||
| 5091 | "Mark all expired entries. | ||
| 5092 | This function sets the age entries in DATA in the feed FEED. If | ||
| 5093 | an item's age is OLD-AGE it is set to NEW-AGE if the item is | ||
| 5094 | older than TIME." | ||
| 5095 | (mapc | ||
| 5096 | (lambda (a-feed) | ||
| 5097 | (when (or (eq feed 'any) | ||
| 5098 | (eq (car a-feed) feed)) | ||
| 5099 | (let ((items (cdr a-feed))) | ||
| 5100 | (mapc | ||
| 5101 | (lambda (item) | ||
| 5102 | (when (eq (newsticker--age item) old-age) | ||
| 5103 | (let ((exp-time (time-add (newsticker--time item) | ||
| 5104 | (seconds-to-time time)))) | ||
| 5105 | (when (time-less-p exp-time (current-time)) | ||
| 5106 | (newsticker--debug-msg | ||
| 5107 | "Item `%s' from %s has expired on %s" | ||
| 5108 | (newsticker--title item) | ||
| 5109 | (format-time-string "%Y-%02m-%d, %H:%M" | ||
| 5110 | (newsticker--time item)) | ||
| 5111 | (format-time-string "%Y-%02m-%d, %H:%M" exp-time)) | ||
| 5112 | (setcar (nthcdr 4 item) new-age))))) | ||
| 5113 | items)))) | ||
| 5114 | data) | ||
| 5115 | data) | ||
| 5116 | |||
| 5117 | (defun newsticker--cache-contains (data feed title desc link age | ||
| 5118 | &optional guid) | ||
| 5119 | "Check DATA whether FEED contains an item with the given properties. | ||
| 5120 | This function returns the contained item or nil if it is not | ||
| 5121 | contained. | ||
| 5122 | The properties which are checked are TITLE, DESC, LINK, AGE, and | ||
| 5123 | GUID. In general all properties must match in order to return a | ||
| 5124 | certain item, except for the following cases. | ||
| 5125 | |||
| 5126 | If AGE equals 'feed the TITLE, DESCription and LINK do not | ||
| 5127 | matter. If DESC is nil it is ignored as well. If | ||
| 5128 | `newsticker-desc-comp-max' is non-nil, only the first | ||
| 5129 | `newsticker-desc-comp-max' characters of DESC are taken into | ||
| 5130 | account. | ||
| 5131 | |||
| 5132 | If GUID is non-nil it is sufficient to match this value, and the | ||
| 5133 | other properties are ignored." | ||
| 5134 | (condition-case nil | ||
| 5135 | (catch 'found | ||
| 5136 | (when (and desc newsticker-desc-comp-max | ||
| 5137 | (> (length desc) newsticker-desc-comp-max)) | ||
| 5138 | (setq desc (substring desc 0 newsticker-desc-comp-max))) | ||
| 5139 | (mapc | ||
| 5140 | (lambda (this-feed) | ||
| 5141 | (when (eq (car this-feed) feed) | ||
| 5142 | (mapc (lambda (anitem) | ||
| 5143 | (when (or | ||
| 5144 | ;; global unique id can match | ||
| 5145 | (and guid | ||
| 5146 | (string= guid (newsticker--guid anitem))) | ||
| 5147 | ;; or title, desc, etc. | ||
| 5148 | (and | ||
| 5149 | ;;(or (not (eq age 'feed)) | ||
| 5150 | ;; (eq (newsticker--age anitem) 'feed)) | ||
| 5151 | (string= (newsticker--title anitem) | ||
| 5152 | title) | ||
| 5153 | (or (not link) | ||
| 5154 | (string= (newsticker--link anitem) | ||
| 5155 | link)) | ||
| 5156 | (or (not desc) | ||
| 5157 | (if (and desc newsticker-desc-comp-max | ||
| 5158 | (> (length (newsticker--desc anitem)) | ||
| 5159 | newsticker-desc-comp-max)) | ||
| 5160 | (string= (substring | ||
| 5161 | (newsticker--desc anitem) | ||
| 5162 | 0 newsticker-desc-comp-max) | ||
| 5163 | desc) | ||
| 5164 | (string= (newsticker--desc anitem) | ||
| 5165 | desc))))) | ||
| 5166 | (throw 'found anitem))) | ||
| 5167 | (cdr this-feed)))) | ||
| 5168 | data) | ||
| 5169 | nil) | ||
| 5170 | (error nil))) | ||
| 5171 | |||
| 5172 | (defun newsticker--cache-add (data feed-name-symbol title desc link time age | ||
| 5173 | position extra-elements | ||
| 5174 | &optional updated-age updated-time | ||
| 5175 | preformatted-contents | ||
| 5176 | preformatted-title) | ||
| 5177 | "Add another item to cache data. | ||
| 5178 | Add to DATA in the FEED-NAME-SYMBOL an item with TITLE, DESC, | ||
| 5179 | LINK, TIME, AGE, POSITION, and EXTRA-ELEMENTS. If this item is | ||
| 5180 | contained already, its mark is set to UPDATED-AGE, its time is | ||
| 5181 | set to UPDATED-TIME, and its pre-formatted contents is set to | ||
| 5182 | PREFORMATTED-CONTENTS and PREFORMATTED-TITLE. Returns the age | ||
| 5183 | which the item got." | ||
| 5184 | (let ((item (newsticker--cache-contains data feed-name-symbol title | ||
| 5185 | desc link age))) | ||
| 5186 | (if item | ||
| 5187 | ;; does exist already -- change age, update time and position | ||
| 5188 | (progn | ||
| 5189 | (if (nthcdr 5 item) | ||
| 5190 | (setcar (nthcdr 5 item) position) | ||
| 5191 | (setcdr (nthcdr 4 item) (list position))) | ||
| 5192 | (setcar (nthcdr 4 item) updated-age) | ||
| 5193 | (if updated-time | ||
| 5194 | (setcar (nthcdr 3 item) updated-time)) | ||
| 5195 | ;; replace cached pre-formatted contents | ||
| 5196 | (newsticker--cache-set-preformatted-contents | ||
| 5197 | item preformatted-contents) | ||
| 5198 | (newsticker--cache-set-preformatted-title | ||
| 5199 | item preformatted-title)) | ||
| 5200 | ;; did not exist or age equals 'feed-name-symbol | ||
| 5201 | (catch 'found | ||
| 5202 | (mapc (lambda (this-feed) | ||
| 5203 | (when (eq (car this-feed) feed-name-symbol) | ||
| 5204 | (setcdr this-feed (nconc (cdr this-feed) | ||
| 5205 | (list (list title desc link | ||
| 5206 | time age position | ||
| 5207 | preformatted-contents | ||
| 5208 | preformatted-title | ||
| 5209 | extra-elements)))) | ||
| 5210 | (throw 'found this-feed))) | ||
| 5211 | data) | ||
| 5212 | ;; the feed is not contained | ||
| 5213 | (add-to-list 'data (list feed-name-symbol | ||
| 5214 | (list title desc link time age position | ||
| 5215 | preformatted-contents | ||
| 5216 | preformatted-title | ||
| 5217 | extra-elements)) | ||
| 5218 | t)))) | ||
| 5219 | data) | ||
| 5220 | |||
| 5221 | (defun newsticker--cache-remove (data feed-symbol age) | ||
| 5222 | "Remove all entries from DATA in the feed FEED-SYMBOL with AGE. | ||
| 5223 | FEED-SYMBOL may be 'any. Entries from old feeds, which are no longer in | ||
| 5224 | `newsticker-url-list' or `newsticker-url-list-defaults', are removed as | ||
| 5225 | well." | ||
| 5226 | (let* ((pos data) | ||
| 5227 | (feed (car pos)) | ||
| 5228 | (last-pos nil)) | ||
| 5229 | (while feed | ||
| 5230 | (if (or (assoc (symbol-name (car feed)) newsticker-url-list) | ||
| 5231 | (assoc (symbol-name (car feed)) newsticker-url-list-defaults)) | ||
| 5232 | ;; feed is still valid=active | ||
| 5233 | ;; (message "Keeping feed %s" (car feed)) | ||
| 5234 | (if (or (eq feed-symbol 'any) | ||
| 5235 | (eq feed-symbol (car feed))) | ||
| 5236 | (let* ((item-pos (cdr feed)) | ||
| 5237 | (item (car item-pos)) | ||
| 5238 | (prev-pos nil)) | ||
| 5239 | (while item | ||
| 5240 | ;;(message "%s" (car item)) | ||
| 5241 | (if (eq age (newsticker--age item)) | ||
| 5242 | ;; remove this item | ||
| 5243 | (progn | ||
| 5244 | ;;(message "Removing item %s" (car item)) | ||
| 5245 | (if prev-pos | ||
| 5246 | (setcdr prev-pos (cdr item-pos)) | ||
| 5247 | (setcdr feed (cdr item-pos)))) | ||
| 5248 | ;;(message "Keeping item %s" (car item)) | ||
| 5249 | (setq prev-pos item-pos)) | ||
| 5250 | (setq item-pos (cdr item-pos)) | ||
| 5251 | (setq item (car item-pos))))) | ||
| 5252 | ;; feed is not active anymore | ||
| 5253 | ;; (message "Removing feed %s" (car feed)) | ||
| 5254 | (if last-pos | ||
| 5255 | (setcdr last-pos (cdr pos)) | ||
| 5256 | (setq data (cdr pos)))) | ||
| 5257 | (setq last-pos pos) | ||
| 5258 | (setq pos (cdr pos)) | ||
| 5259 | (setq feed (car pos))))) | ||
| 5260 | |||
| 5261 | ;; ====================================================================== | ||
| 5262 | ;;; Sorting | ||
| 5263 | ;; ====================================================================== | ||
| 5264 | (defun newsticker--cache-item-compare-by-time (item1 item2) | ||
| 5265 | "Compare two news items ITEM1 and ITEM2 by comparing their time values." | ||
| 5266 | (catch 'result | ||
| 5267 | (let ((age1 (newsticker--age item1)) | ||
| 5268 | (age2 (newsticker--age item2))) | ||
| 5269 | (if (not (eq age1 age2)) | ||
| 5270 | (cond ((eq age1 'obsolete) | ||
| 5271 | (throw 'result nil)) | ||
| 5272 | ((eq age2 'obsolete) | ||
| 5273 | (throw 'result t))))) | ||
| 5274 | (let* ((time1 (newsticker--time item1)) | ||
| 5275 | (time2 (newsticker--time item2))) | ||
| 5276 | (cond ((< (nth 0 time1) (nth 0 time2)) | ||
| 5277 | nil) | ||
| 5278 | ((> (nth 0 time1) (nth 0 time2)) | ||
| 5279 | t) | ||
| 5280 | ((< (nth 1 time1) (nth 1 time2)) | ||
| 5281 | nil) | ||
| 5282 | ((> (nth 1 time1) (nth 1 time2)) | ||
| 5283 | t) | ||
| 5284 | ((< (or (nth 2 time1) 0) (or (nth 2 time2) 0)) | ||
| 5285 | nil) | ||
| 5286 | ((> (or (nth 2 time1) 0) (or (nth 2 time2) 0)) | ||
| 5287 | t) | ||
| 5288 | (t | ||
| 5289 | nil))))) | ||
| 5290 | |||
| 5291 | (defun newsticker--cache-item-compare-by-title (item1 item2) | ||
| 5292 | "Compare ITEM1 and ITEM2 by comparing their titles." | ||
| 5293 | (catch 'result | ||
| 5294 | (let ((age1 (newsticker--age item1)) | ||
| 5295 | (age2 (newsticker--age item2))) | ||
| 5296 | (if (not (eq age1 age2)) | ||
| 5297 | (cond ((eq age1 'obsolete) | ||
| 5298 | (throw 'result nil)) | ||
| 5299 | ((eq age2 'obsolete) | ||
| 5300 | (throw 'result t))))) | ||
| 5301 | (string< (newsticker--title item1) (newsticker--title item2)))) | ||
| 5302 | |||
| 5303 | (defun newsticker--cache-item-compare-by-position (item1 item2) | ||
| 5304 | "Compare ITEM1 and ITEM2 by comparing their original positions." | ||
| 5305 | (catch 'result | ||
| 5306 | (let ((age1 (newsticker--age item1)) | ||
| 5307 | (age2 (newsticker--age item2))) | ||
| 5308 | (if (not (eq age1 age2)) | ||
| 5309 | (cond ((eq age1 'obsolete) | ||
| 5310 | (throw 'result nil)) | ||
| 5311 | ((eq age2 'obsolete) | ||
| 5312 | (throw 'result t))))) | ||
| 5313 | (< (or (newsticker--pos item1) 0) (or (newsticker--pos item2) 0)))) | ||
| 5314 | |||
| 5315 | (defun newsticker--cache-sort () | ||
| 5316 | "Sort the newsticker cache data." | ||
| 5317 | (let ((sort-fun (cond ((eq newsticker-sort-method 'sort-by-time) | ||
| 5318 | 'newsticker--cache-item-compare-by-time) | ||
| 5319 | ((eq newsticker-sort-method 'sort-by-title) | ||
| 5320 | 'newsticker--cache-item-compare-by-title) | ||
| 5321 | ((eq newsticker-sort-method 'sort-by-original-order) | ||
| 5322 | 'newsticker--cache-item-compare-by-position)))) | ||
| 5323 | (mapc (lambda (feed-list) | ||
| 5324 | (setcdr feed-list (sort (cdr feed-list) | ||
| 5325 | sort-fun))) | ||
| 5326 | newsticker--cache))) | ||
| 5327 | |||
| 5328 | (defun newsticker--cache-update (&optional save) | ||
| 5329 | "Update newsticker cache file. | ||
| 5330 | If optional argument SAVE is not nil the cache file is saved to disk." | ||
| 5331 | (save-excursion | ||
| 5332 | (let ((coding-system-for-write 'utf-8) | ||
| 5333 | (buf (find-file-noselect newsticker-cache-filename))) | ||
| 5334 | (when buf | ||
| 5335 | (set-buffer buf) | ||
| 5336 | (setq buffer-undo-list t) | ||
| 5337 | (erase-buffer) | ||
| 5338 | (insert ";; -*- coding: utf-8 -*-\n") | ||
| 5339 | (insert (prin1-to-string newsticker--cache)) | ||
| 5340 | (when save | ||
| 5341 | (save-buffer)))))) | ||
| 5342 | |||
| 5343 | (defun newsticker--cache-get-feed (feed) | ||
| 5344 | "Return the cached data for the feed FEED. | ||
| 5345 | FEED is a symbol!" | ||
| 5346 | (assoc feed newsticker--cache)) | ||
| 5347 | |||
| 5348 | ;; ====================================================================== | ||
| 5349 | ;;; Statistics | ||
| 5350 | ;; ====================================================================== | ||
| 5351 | (defun newsticker--stat-num-items (feed &optional age) | ||
| 5352 | "Return number of items in the given FEED which have the given AGE. | ||
| 5353 | If AGE is nil, the total number of items is returned." | ||
| 5354 | (let ((items (cdr (newsticker--cache-get-feed feed))) | ||
| 5355 | (num 0)) | ||
| 5356 | (while items | ||
| 5357 | (if age | ||
| 5358 | (if (eq (newsticker--age (car items)) age) | ||
| 5359 | (setq num (1+ num))) | ||
| 5360 | (if (memq (newsticker--age (car items)) '(new old immortal obsolete)) | ||
| 5361 | (setq num (1+ num)))) | ||
| 5362 | (setq items (cdr items))) | ||
| 5363 | num)) | ||
| 5364 | |||
| 5365 | ;; ====================================================================== | ||
| 5366 | ;;; OPML | ||
| 5367 | ;; ====================================================================== | ||
| 5368 | (defun newsticker-opml-export () | ||
| 5369 | "OPML subscription export. | ||
| 5370 | Export subscriptions to a buffer in OPML Format." | ||
| 5371 | (interactive) | ||
| 5372 | (with-current-buffer (get-buffer-create "*OPML Export*") | ||
| 5373 | (set-buffer-file-coding-system 'utf-8) | ||
| 5374 | (insert (concat | ||
| 5375 | "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" | ||
| 5376 | "<!-- OPML generated by Emacs newsticker.el -->\n" | ||
| 5377 | "<opml version=\"1.0\">\n" | ||
| 5378 | " <head>\n" | ||
| 5379 | " <title>mySubscriptions</title>\n" | ||
| 5380 | " <dateCreated>" (format-time-string "%a, %d %b %Y %T %z") | ||
| 5381 | "</dateCreated>\n" | ||
| 5382 | " <ownerEmail>" user-mail-address "</ownerEmail>\n" | ||
| 5383 | " <ownerName>" (user-full-name) "</ownerName>\n" | ||
| 5384 | " </head>\n" | ||
| 5385 | " <body>\n")) | ||
| 5386 | (mapc (lambda (sub) | ||
| 5387 | (insert " <outline text=\"") | ||
| 5388 | (insert (newsticker--title sub)) | ||
| 5389 | (insert "\" xmlUrl=\"") | ||
| 5390 | (insert (cadr sub)) | ||
| 5391 | (insert "\"/>\n")) | ||
| 5392 | (append newsticker-url-list newsticker-url-list-defaults)) | ||
| 5393 | (insert " </body>\n</opml>\n")) | ||
| 5394 | (pop-to-buffer "*OPML Export*") | ||
| 5395 | (when (fboundp 'sgml-mode) | ||
| 5396 | (sgml-mode))) | ||
| 5397 | |||
| 5398 | (defun newsticker-opml-import (filename) | ||
| 5399 | "Import OPML data from FILENAME." | ||
| 5400 | (interactive "fOPML file: ") | ||
| 5401 | (set-buffer (find-file-noselect filename)) | ||
| 5402 | (goto-char (point-min)) | ||
| 5403 | (let* ((node-list (xml-parse-region (point-min) (point-max))) | ||
| 5404 | (body (car (xml-get-children (car node-list) 'body))) | ||
| 5405 | (outlines (xml-get-children body 'outline))) | ||
| 5406 | (mapc (lambda (outline) | ||
| 5407 | (let ((name (xml-get-attribute outline 'text)) | ||
| 5408 | (url (xml-get-attribute outline 'xmlUrl))) | ||
| 5409 | (add-to-list 'newsticker-url-list | ||
| 5410 | (list name url nil nil nil) t))) | ||
| 5411 | outlines)) | ||
| 5412 | (customize-variable 'newsticker-url-list)) | ||
| 5413 | |||
| 5414 | ;; ====================================================================== | ||
| 5415 | ;;; Auto marking | ||
| 5416 | ;; ====================================================================== | ||
| 5417 | (defun newsticker--run-auto-mark-filter (feed item) | ||
| 5418 | "Automatically mark an item as old or immortal. | ||
| 5419 | This function checks the variable `newsticker-auto-mark-filter-list' | ||
| 5420 | for an entry that matches FEED and ITEM." | ||
| 5421 | (let ((case-fold-search t)) | ||
| 5422 | (mapc (lambda (filter) | ||
| 5423 | (let ((filter-feed (car filter)) | ||
| 5424 | (pattern-list (cadr filter))) | ||
| 5425 | (when (string-match filter-feed feed) | ||
| 5426 | (newsticker--do-run-auto-mark-filter item pattern-list)))) | ||
| 5427 | newsticker-auto-mark-filter-list))) | ||
| 5428 | |||
| 5429 | (defun newsticker--do-run-auto-mark-filter (item list) | ||
| 5430 | "Actually compare ITEM against the pattern-LIST | ||
| 5431 | \(from `newsticker-auto-mark-filter-list')." | ||
| 5432 | (mapc (lambda (pattern) | ||
| 5433 | (let ((age (nth 0 pattern)) | ||
| 5434 | (place (nth 1 pattern)) | ||
| 5435 | (regexp (nth 2 pattern)) | ||
| 5436 | (title (newsticker--title item)) | ||
| 5437 | (desc (newsticker--desc item))) | ||
| 5438 | (when (or (eq place 'title) (eq place 'all)) | ||
| 5439 | (when (and title (string-match regexp title)) | ||
| 5440 | (newsticker--debug-msg "Auto-marking as %s: `%s'" | ||
| 5441 | age (newsticker--title item)) | ||
| 5442 | (setcar (nthcdr 4 item) age))) | ||
| 5443 | (when (or (eq place 'description) (eq place 'all)) | ||
| 5444 | (when (and desc (string-match regexp desc)) | ||
| 5445 | (newsticker--debug-msg "Auto-marking as %s: `%s'" | ||
| 5446 | age (newsticker--title item)) | ||
| 5447 | (setcar (nthcdr 4 item) age))))) | ||
| 5448 | list)) | ||
| 5449 | |||
| 5450 | |||
| 5451 | ;; ====================================================================== | ||
| 5452 | ;;; hook samples | ||
| 5453 | ;; ====================================================================== | ||
| 5454 | (defun newsticker-new-item-functions-sample (feed item) | ||
| 5455 | "Demonstrate the use of the `newsticker-new-item-functions' hook. | ||
| 5456 | This function just prints out the values of the FEED and title of the ITEM." | ||
| 5457 | (message (concat "newsticker-new-item-functions-sample: feed=`%s', " | ||
| 5458 | "title=`%s'") | ||
| 5459 | feed (newsticker--title item))) | ||
| 5460 | |||
| 5461 | (defun newsticker-download-images (feed item) | ||
| 5462 | "Download the first image. | ||
| 5463 | If FEED equals \"imagefeed\" download the first image URL found | ||
| 5464 | in the description=contents of ITEM to the directory | ||
| 5465 | \"~/tmp/newsticker/FEED/TITLE\" where TITLE is the title of the item." | ||
| 5466 | (when (string= feed "imagefeed") | ||
| 5467 | (let ((title (newsticker--title item)) | ||
| 5468 | (desc (newsticker--desc item))) | ||
| 5469 | (when (string-match "<img src=\"\\(http://[^ \"]+\\)\"" desc) | ||
| 5470 | (let ((url (substring desc (match-beginning 1) (match-end 1))) | ||
| 5471 | (temp-dir (concat "~/tmp/newsticker/" feed "/" title)) | ||
| 5472 | (org-dir default-directory)) | ||
| 5473 | (unless (file-directory-p temp-dir) | ||
| 5474 | (make-directory temp-dir t)) | ||
| 5475 | (cd temp-dir) | ||
| 5476 | (message "Getting image %s" url) | ||
| 5477 | (apply 'start-process "wget-image" | ||
| 5478 | " *newsticker-wget-download-images*" | ||
| 5479 | newsticker-wget-name | ||
| 5480 | (list url)) | ||
| 5481 | (cd org-dir)))))) | ||
| 5482 | |||
| 5483 | (defun newsticker-download-enclosures (feed item) | ||
| 5484 | "In all FEEDs download the enclosed object of the news ITEM. | ||
| 5485 | The object is saved to the directory \"~/tmp/newsticker/FEED/TITLE\", which | ||
| 5486 | is created if it does not exist. TITLE is the title of the news | ||
| 5487 | item. Argument FEED is ignored. | ||
| 5488 | This function is suited for adding it to `newsticker-new-item-functions'." | ||
| 5489 | (let ((title (newsticker--title item)) | ||
| 5490 | (enclosure (newsticker--enclosure item))) | ||
| 5491 | (when enclosure | ||
| 5492 | (let ((url (cdr (assoc 'url enclosure))) | ||
| 5493 | (temp-dir (concat "~/tmp/newsticker/" feed "/" title)) | ||
| 5494 | (org-dir default-directory)) | ||
| 5495 | (unless (file-directory-p temp-dir) | ||
| 5496 | (make-directory temp-dir t)) | ||
| 5497 | (cd temp-dir) | ||
| 5498 | (message "Getting enclosure %s" url) | ||
| 5499 | (apply 'start-process "wget-enclosure" | ||
| 5500 | " *newsticker-wget-download-enclosures*" | ||
| 5501 | newsticker-wget-name | ||
| 5502 | (list url)) | ||
| 5503 | (cd org-dir))))) | ||
| 5504 | |||
| 5505 | 408 | ||
| 5506 | (provide 'newsticker) | 409 | (provide 'newsticker) |
| 5507 | 410 | ||