sfeed_tk: add TCL/TK guide experiment/concept - randomcrap - random crap programs of varying quality
 (HTM) git clone git://git.codemadness.org/randomcrap
 (DIR) Log
 (DIR) Files
 (DIR) Refs
 (DIR) README
 (DIR) LICENSE
       ---
 (DIR) commit 8729a291b59cea8a37de0785fc534b34c0124de9
 (DIR) parent ab0fceb63efbdafccd7ddb69c5d3a3c493ab2f5b
 (HTM) Author: Hiltjo Posthuma <hiltjo@codemadness.org>
       Date:   Thu, 31 Oct 2024 19:48:43 +0100
       
       sfeed_tk: add TCL/TK guide experiment/concept
       
       Diffstat:
         A sfeed/sfeed_tk                      |     419 +++++++++++++++++++++++++++++++
       
       1 file changed, 419 insertions(+), 0 deletions(-)
       ---
 (DIR) diff --git a/sfeed/sfeed_tk b/sfeed/sfeed_tk
       @@ -0,0 +1,419 @@
       +#!/usr/bin/wish
       +package require Tk
       +
       +# ttk::style theme names (default, clam, classic, etc)
       +ttk::style theme use classic
       +#set scrollbar "ttk::scrollbar"
       +set scrollbar "scrollbar"
       +
       +set fontname TkDefaultFont
       +#set fontname "monospace"
       +font create font_item_normal -family $fontname -size 10 -weight normal
       +font create font_item_bold -family $fontname -size 10 -weight bold
       +
       +# global config variables.
       +set config_onlynew 0
       +set config_newage ""
       +set config_plumber "xdg-open"
       +# global feeds list
       +set comparetime 0
       +set feeds {}
       +set mode_read_stdin 0
       +
       +proc parse_sfeed_line {line} {
       +        return [split $line "\t"]
       +}
       +
       +# read items in a sfeed(5) file and parse all the fields.
       +proc read_sfeed_file {file} {
       +        set total 0
       +        set totalnew 0
       +        set comparetime $::comparetime
       +
       +        # clear list
       +        .tree_items delete [.tree_items children {}]
       +
       +        if {$::mode_read_stdin == 1} {
       +                set f stdin
       +        } else {
       +                set f [open $file]
       +        }
       +        while {[gets $f line] >= 0} {
       +                incr total
       +
       +                set fields [parse_sfeed_line $line]
       +                set timestampstr [lindex $fields 0]
       +                set title [lindex $fields 1]
       +                set link [lindex $fields 2]
       +                set content [lindex $fields 3]
       +                set contenttype [lindex $fields 4]
       +                set id [lindex $fields 5]
       +                set author [lindex $fields 6]
       +                set enclosure [lindex $fields 7]
       +                set category [lindex $fields 8]
       +
       +                # parse timestamp
       +
       +                # convert to long long
       +                set e [scan $timestampstr "%lld" timestamp]
       +
       +                if {$e == 1} {
       +                        if {$timestamp >= $comparetime} {
       +                                incr totalnew
       +                        }
       +                        set date [clock format $timestamp -gmt 0 -format "%Y-%m-%d %H:%M"]
       +                } else {
       +                        set date ""
       +                }
       +
       +                if {$e == 1 && $timestamp >= $comparetime} {
       +                        set isnew "N"
       +                        set style "bold"
       +                } else {
       +                        set isnew ""
       +                        set style "normal"
       +                }
       +                .tree_items insert {} end -id $total -text "" -tags "item item_${style}" \
       +                        -values [list \
       +                                $timestampstr $title $link $content $contenttype $id $author $enclosure $category \
       +                                $isnew $date]
       +        }
       +        if {$::mode_read_stdin == 0} {
       +                close $f
       +        }
       +
       +        set feed(path) $file
       +        set feed(name) [file tail $file]
       +        set feed(total) $total
       +        set feed(totalnew) $totalnew
       +
       +        # return struct serialized
       +        return [array get feed]
       +}
       +
       +# count new items in a sfeed(5) file, but do not parse all the lines.
       +proc count_sfeed_file {file} {
       +        set total 0
       +        set totalnew 0
       +        set comparetime $::comparetime
       +
       +        set f [open $file]
       +        while {[gets $f line] >= 0} {
       +                incr total
       +
       +                # get first timestamp field
       +                set idx [string first "        " $line]
       +                if {$idx == -1} {
       +                        continue
       +                }
       +                set timestampstr [string range $line 0 $idx]
       +                # convert to long long
       +                set e [scan $timestampstr "%lld" timestamp]
       +                if {$e == 1 && $timestamp >= $comparetime} {
       +                        incr totalnew
       +                }
       +        }
       +        close $f
       +
       +        set feed(path) $file
       +        set feed(name) [file tail $file]
       +        set feed(total) $total
       +        set feed(totalnew) $totalnew
       +
       +        # return struct serialized
       +        return [array get feed]
       +}
       +
       +proc getcomparetime {} {
       +        set newage 86400
       +        if { $::config_newage != "" } {
       +                set e [scan $::config_newage "%lld" l]
       +                if {$e == 1} {
       +                        set newage $l
       +                }
       +        }
       +        # get current time
       +        set now [clock seconds]
       +        # 1 day is old news
       +        return [expr {$now - $newage}]
       +}
       +
       +# TODO: set and restore divider position per mode.
       +proc window_set_mode {win mode} {
       +        if {$mode == "1"} {
       +                $win configure -orient horizontal
       +                $win paneconfigure .pw.frame_feed -hide 0
       +        }
       +        if {$mode == "2"} {
       +                $win configure -orient vertical
       +                $win paneconfigure .pw.frame_feed -hide 0
       +        }
       +        # set mode monocle
       +        if {$mode == "3"} {
       +                $win paneconfigure .pw.frame_feed -hide 1
       +        }
       +}
       +
       +proc feeds_update_list {} {
       +        set feeds $::feeds
       +
       +        # clear list
       +        .tree_feed delete [.tree_feed children {}]
       +
       +        foreach f $feeds {
       +                array set feed $f
       +
       +                if { ${feed(totalnew)} > 0 } {
       +                        .tree_feed insert {} end -text "" -tags "item item_bold" \
       +                                -values [list ${feed(name)} ${feed(totalnew)} ${feed(total)} ${feed(path)} ]
       +                } else {
       +                        if {$::config_onlynew == 0} {
       +                                .tree_feed insert {} end -text "" -tags "item item_normal" \
       +                                        -values [list ${feed(name)} ${feed(totalnew)} ${feed(total)} ${feed(path)} ]
       +                        }
       +                }
       +        }
       +}
       +
       +proc global_key_release {key} {
       +        # exit
       +        if {$key == "q"} {
       +                exit 0
       +        }
       +        # "/": start search/filter (if not enabled already).
       +        # TODO: support ctrl-f also? ctrl-n, etc?
       +        if {$key == "slash"} {
       +                # TODO
       +        }
       +        # toggle showing feeds with new items only.
       +        if {$key == "t"} {
       +                if {$::config_onlynew == 0} {
       +                        set ::config_onlynew 1
       +                        feeds_update_list
       +                } else {
       +                        set ::config_onlynew 0
       +                        feeds_update_list
       +                }
       +        }
       +        # set mode horizontal, vertical, monocle
       +        if {$key == "1"} {
       +                window_set_mode .pw $key
       +        }
       +        if {$key == "2"} {
       +                window_set_mode .pw $key
       +        }
       +        if {$key == "3"} {
       +                window_set_mode .pw $key
       +        }
       +}
       +
       +# update status bar
       +proc status_set {text} {
       +        .status configure -state normal
       +#        .status configure -background "#cccccc"
       +        .status delete 1.0 end
       +        .status insert end $text
       +        .status configure -state disabled
       +}
       +
       +proc main {argc argv} {
       +        # environment variables
       +        if {[info exists ::env(SFEED_PLUMBER)]} {
       +                set ::config_plumber $::env(SFEED_PLUMBER)
       +        }
       +        if {[info exists ::env(SFEED_NEW_AGE)]} {
       +                set ::config_newage $::env(SFEED_NEW_AGE)
       +        }
       +        set ::comparetime [getcomparetime]
       +
       +        # make essential widgets
       +
       +        # paned window, set default mode to horizontal.
       +        panedwindow .pw -sashwidth 8
       +
       +        # create treeviews:
       +        # selectmode=browse: select one item at a time.
       +        ttk::treeview .tree_feed -selectmode browse \
       +                -columns "feedname feednew feedtotal filename" \
       +                -displaycolumns "feedname feednew feedtotal" -show headings
       +        ttk::treeview .tree_items -selectmode browse \
       +                -columns "timestamp title link content contenttype id author enclosure category isnew date" \
       +                -displaycolumns "isnew date title" -show headings
       +
       +        frame .pw.frame_feed -width 100
       +        frame .pw.frame_items -width 100
       +
       +        set feedcount 0
       +        set feeds []
       +        set feedcount 0
       +        set feednewcount 0
       +        set totalnew 0
       +        set total 0
       +
       +        # if no files, read from stdin and hide feeds panel by default (monocle layout).
       +        if {$argc == 0} {
       +                set lfiles {"/dev/stdin"}
       +                # read and count sfeed file: stdin cannot be reopened.
       +                set read_func "read_sfeed_file"
       +                set ::mode_read_stdin 1
       +                # set monocle by default.
       +                window_set_mode .pw "3"
       +        } else {
       +                set lfiles $argv
       +                set read_func "count_sfeed_file"
       +                set ::mode_read_stdin 0
       +                # set vertical by default.
       +                window_set_mode .pw "1"
       +        }
       +
       +        foreach file $lfiles {
       +                array set feed [$read_func $file]
       +
       +                # add feed struct serialized
       +                lappend feeds [array get feed]
       +                incr feedcount
       +
       +                set linecount $feed(total)
       +                set filename $feed(name)
       +
       +                set total [expr {$total + $feed(total)}]
       +                set totalnew [expr {$totalnew + $feed(totalnew)}]
       +
       +                if {$feed(totalnew) > 0} {
       +                        incr feednewcount
       +                }
       +        }
       +
       +        set title "(${totalnew}/${total}) - sfeed_tk"
       +
       +        wm title . $title
       +
       +        # UI
       +
       +        # Make the other widgets.
       +
       +        .tree_feed column feedname -anchor w -stretch 1
       +        .tree_feed column feednew -width 50 -anchor e -stretch 0
       +        .tree_feed column feedtotal -width 50 -anchor e -stretch 0
       +        .tree_feed heading feedname -text "Feeds (${feednewcount}/${feedcount})" -anchor w
       +        .tree_feed heading feednew -text $totalnew -anchor e
       +        .tree_feed heading feedtotal -text $total -anchor e
       +        .tree_feed configure -padding {0 0 0 0}
       +
       +        .tree_items column isnew -width 50 -anchor c -stretch 0
       +        .tree_items column date -width 135 -anchor e -stretch 0
       +        .tree_items column title -anchor w -stretch 1
       +        .tree_items heading isnew -text "New?" -anchor c
       +        .tree_items heading date -text "Date" -anchor e
       +        .tree_items heading title -text "Title" -anchor w
       +        .tree_items configure -padding {0 0 0 0}
       +
       +        # scrollbar for feeds
       +        .tree_feed configure -yscrollcommand {.tree_feed_scroll set}
       +        ::scrollbar .tree_feed_scroll -command {.tree_feed yview}
       +
       +        # scrollbar for feed items
       +        .tree_items configure -yscrollcommand {.tree_items_scroll set}
       +        ::scrollbar .tree_items_scroll -command {.tree_items yview}
       +
       +        pack .tree_feed -in .pw.frame_feed -fill both -expand yes -side left
       +        pack .tree_feed_scroll -in .pw.frame_feed -side right -fill y
       +        pack .tree_items -in .pw.frame_items -fill both -expand yes -side left
       +        pack .tree_items_scroll -in .pw.frame_items -side right -fill y
       +
       +        .pw add .pw.frame_feed -minsize 1
       +        .pw add .pw.frame_items -minsize 1
       +
       +        # make global
       +        set ::feeds $feeds
       +        feeds_update_list
       +
       +        proc feeditem_button_1_release {} {
       +        }
       +
       +        # can only (re)read feeds when not stdin.
       +        proc feeditem_doublebutton_1_release {} {
       +                if {$::mode_read_stdin == 0} {
       +                        set item [.tree_feed item [.tree_feed selection] -values]
       +                        set file [lindex $item 3]
       +                        read_sfeed_file $file
       +                }
       +        }
       +
       +        proc feeditem_button_2_release {} {
       +                puts stdout [.tree_feed selection]
       +        }
       +
       +        proc feeditem_button_3_release {} {
       +                puts stdout [.tree_feed selection]
       +        }
       +
       +        proc feeditem_key_release {key} {
       +                if {$key == "Return" } {
       +                        feeditem_doublebutton_1_release
       +                }
       +        }
       +
       +        proc item_button_1_release {} {
       +        }
       +
       +        proc item_doublebutton_1_release {} {
       +                set item [.tree_items item [.tree_items selection] -values]
       +                set link [lindex $item 2]
       +                if {$link != ""} {
       +                        exec $::config_plumber "$link" "2>/dev/null" "&"
       +                }
       +        }
       +
       +        proc item_button_2_release {} {
       +                puts stdout [.tree_items selection]
       +        }
       +
       +        proc item_button_3_release {} {
       +                puts stdout [.tree_items selection]
       +        }
       +
       +        proc item_key_release {key} {
       +                if {$key == "Return" } {
       +                        item_doublebutton_1_release
       +                }
       +        }
       +
       +        proc item_selection_change {} {
       +                set item [.tree_items item [.tree_items selection] -values]
       +                set link [lindex $item 2]
       +                status_set $link
       +        }
       +
       +        .tree_feed tag bind "item" <Double-1> "feeditem_doublebutton_1_release"
       +        .tree_feed tag bind "item" <ButtonRelease-1> "feeditem_button_1_release"
       +        .tree_feed tag bind "item" <ButtonRelease-2> "feeditem_button_2_release"
       +        .tree_feed tag bind "item" <ButtonRelease-3> "feeditem_button_3_release"
       +        .tree_feed tag bind "item" <KeyRelease> "feeditem_key_release %K"
       +
       +        .tree_items tag bind "item" <Double-1> "item_doublebutton_1_release"
       +        .tree_items tag bind "item" <ButtonRelease-1> "item_button_1_release"
       +        .tree_items tag bind "item" <ButtonRelease-2> "item_button_2_release"
       +        .tree_items tag bind "item" <ButtonRelease-3> "item_button_3_release"
       +        .tree_items tag bind "item" <KeyRelease> "item_key_release %K"
       +
       +        .tree_items tag bind "item" <<TreeviewSelect>> "item_selection_change"
       +
       +        # global key handler.
       +        bind . <Key> "global_key_release %K"
       +
       +        .tree_feed tag configure item_normal -font font_item_normal
       +        .tree_feed tag configure item_bold -font font_item_bold
       +        .tree_items tag configure item_normal -font font_item_normal
       +        .tree_items tag configure item_bold -font font_item_bold
       +
       +        # status bar: read-only text field (so text can still be selection and copied).
       +        text .status -height 1 -state disabled
       +
       +        pack .pw -fill both -expand yes -side top
       +        pack .status -side bottom -fill x
       +
       +        status_set ""
       +}
       +
       +main $argc $argv