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