todolist.tcl
README.md

todolist.tcl

  • todolist.tcl
  • todolist
  • Common
    • Message todolist::Message : Displays a message in statusbar of templates dialogue. msg - message mode - mode of Message
    • CheckEmpty todolist::CheckEmpty : Checks if the TODO list is empty.
    • PrjDateCont todolist::PrjDateCont : Gets project name, date, todo of current tree item and item's index.
    • UpdateTree todolist::UpdateTree : Updates the list. doselect - yes for selecting & focusing current item
    • UpdateInList todolist::UpdateInList : Updates tdlist's item. isel - item index in tdlist prjname - project name date - item date todo - todo's text idtd - ID of item in tdlistOrig (original tdlist)
    • ItemValues todolist::ItemValues : Gets -values option of tree item. item - item ID prj - project name date - date of todo todo - todo text
    • TodoTag todolist::TodoTag : Gets -tag option for current todo (highlighted or normal). date - date of todo
    • ItemIndex todolist::ItemIndex : Gets item's index from item's ID. item - ID of item
    • Tooltip todolist::Tooltip : Gets a tip for tree's item. ID - ID of treeview item nc - column of treeview item
  • Selections
    • Select todolist::Select : Selects an item of the list. item - index (ID) of list item
    • Selected todolist::Selected : Gets ID or index of currently selected item of the list. what - if "index", gets a current item's index domsg - if yes, shows a message about the selection
    • onSelect todolist::onSelect : Handles event "selecting a tree item"
  • Modifications
    • TextModified todolist::TextModified : Processes modifications of TODO text. wtxt - text's path args - not used arguments
    • TODO_save todolist::TODO_save : Saves a reminder on a date. args - may contain project name, new date, todo
  • Filter
    • FilterList todolist::FilterList : Filters todo list. args - when called on validation, contains current filter value
    • ClearFilter todolist::ClearFilter : Clears filter's entry.
  • Tool buttons
    • Tool_button todolist::Tool_button : Fire an event handler (paste/undo/redo) on a reminder. ev - event to fire
    • Paste todolist::Paste : Pastes a text to a reminder.
    • Undo todolist::Undo : Undoes changes of a reminder.
    • Redo todolist::Redo : Redoes changes of a reminder.
    • Delete todolist::Delete : Handles "Delete template" button.
    • MoveTo todolist::MoveTo : Shifts current date by *days*. days - days to shift
    • CheckSwapItems todolist::CheckSwapItems : Checks and at need swaps tree items according to dates. tree - item tree item - current item's ID date - current item's date days - if positive then swap forward, otherwise backward
  • Buttons
    • Ok todolist::Ok : Handles "OK" button.
    • Cancel todolist::Cancel : Handles "Cancel" button.
    • Help todolist::Help : Handles "Help" button.
    • QuitDialog todolist::QuitDialog : Quits the dialog. res - result (1/0 for OK/Cancel)
  • UI
    • _create todolist::_create : Creates "Pending TODO list" dialogue.
    • _run todolist::_run : Runs "Pending TODOs" dialogue. tdlInput - todo list

alited's source

The alited/src directory contains alited's own source files.

Some additional alited's files are also contained in alited/lib/addon directory (alited/lib directory contains all library files).

  • about.tcl - "About alited" dialogue.
  • alited.tcl - The alited's main script to start.
  • bar.tcl - Handles the bar of tabs.
  • check.tcl - "Check Tcl" dialogue and procedures.
  • complete.tcl - Handles auto-completion.
  • edit.tcl - "Edit" menu's procedures.
  • favor.tcl - Handles the favorite and last visited units.
  • favor_ls.tcl - "Saved lists of favorites" dialogue and procedures.
  • file.tcl - "File" menu's procedures.
  • find.tcl - "Find / Replace" dialogue and procedures.
  • format.tcl - "Edit / Formats" menu's procedures.
  • img.tcl - List of images used by alited.
  • indent.tcl - Handles text indentation.
  • info.tcl - Handles the info bar.
  • ini.tcl - Handles initializing alited.
  • keys.tcl - Handles keyboard (mapping etc.).
  • main.tcl - Handles the main form of alited.
  • menu.tcl - Handles alited's menus.
  • msgs.tcl - Localized messages used in several places.
  • paver.tcl - "Tools / Paver" tool.
  • pkgIndex.tcl - Includes README.md text (for Ruff doc generator).
  • pref.tcl - "Preferences" dialogue and procedures.
  • preview.tcl - "Preview (theme, CS)" dialogue called by "Preferences".
  • printer.tcl - "Tools / Project Printer" dialogue and procedures.
  • project.tcl - "Projects" dialogue and procedures.
  • run.tcl - "Tools / Run..." dialogue and procedures.
  • tool.tcl - "Tools" menu's procedures.
  • tree.tcl - Handles the tree of units and files.
  • unit.tcl - Handles the unit tree.
  • unit_tpl.tcl - "Templates" dialogue and procedures.
  • todolist.tcl
    #! /usr/bin/env tclsh
    ###########################################################
    # Name:    todolist.tcl
    # Author:  Alex Plotnikov  (aplsimple@gmail.com)
    # Date:    Jun 19, 2025
    # Brief:   Handles pending TODOs.
    # License: MIT.
    ###########################################################
    
    # _________________________ todolist ________________________ #
    
    namespace eval todolist {
      # apave object
      variable obTdl pavedTODOlist
    
      # "Templates" dialogue's path
      variable win $::alited::al(WIN).todolist
    
      # todo list
      variable tdlist [list]
    
      # list of original dates
      variable tdlistOrigDates [list]
    
      # todo filter
      variable filter {}
    
      # 1st item of list
      variable item1st {}
    }
    
    # ________________________ Common _________________________ #
    
    proc todolist::Message {msg {mode 2}} {
      # Displays a message in statusbar of templates dialogue.
      #   msg - message
      #   mode - mode of Message
    
      variable obTdl
      alited::Message $msg $mode [$obTdl LabMess]
    }
    #_______________________
    
    proc todolist::CheckEmpty {} {
      # Checks if the TODO list is empty.
    
      variable tdlistOrig
      variable obTdl
      if {![llength $tdlistOrig]} {
        set txt [$obTdl TexTdl]
        alited::ini::HighlightFileText $txt .md 0 -cmd ::apave::None
        $obTdl displayText $txt "\n#   [msgcat::mc {No TODO pending!}]"
        alited::ini::HighlightFileText $txt .md 1 -cmd ::apave::None
      }
    }
    #_______________________
    
    proc todolist::PrjDateCont {} {
      # Gets project name, date, todo of current tree item and item's index.
    
      variable tdlist
      set isel [Selected index no]
      if {![string is integer -strict $isel]} {return {}}
      lassign [lindex $tdlist $isel] prjname date todo idtd
      return [list $prjname $date $todo $isel $idtd]
    }
    #_______________________
    
    proc todolist::UpdateTree {{doselect yes}} {
      # Updates the list.
      #   doselect - yes for selecting & focusing current item
    
      variable obTdl
      variable tdlist
      variable item1st
      set tree [$obTdl TreeTdl]
      $tree delete [$tree children {}]
      set item1st {}
      foreach tdl $tdlist {
        set item "TDL[incr itdl]"
        lassign $tdl prj date todo
        if {$date eq {}} {
          # tree branch: project name
          $tree insert {} end -id $item -text $prj -open 1 -tag tagBranch
          set parent $item
        } else {
          set values [ItemValues $item $prj $date $todo]
          $tree insert $parent end -id $item -text $date -values $values -open 1 \
            {*}[TodoTag $date]
        }
        if {$item1st eq {}} {set item1st $item}
      }
      if {$doselect} {
        after 200 [list alited::todolist::Select $item1st]
      }
    }
    #_______________________
    
    proc todolist::UpdateInList {isel prjname date todo idtd} {
      # Updates tdlist's item.
      #   isel - item index in tdlist
      #   prjname - project name
      #   date - item date
      #   todo - todo's text
      #   idtd - ID of item in tdlistOrig (original tdlist)
    
      variable tdlist
      variable tdlistOrig
      set tdlist [lreplace $tdlist $isel $isel [list $prjname $date $todo $idtd]]
      # update original tdlist
      set ilo [set dobreak 0]
      foreach tdinfo $tdlistOrig {
        lassign $tdinfo prj rems
        set newrems [list]
        foreach rem $rems {
          incr idOr
          if {$idOr == $idtd} {
            set rem [list $date $todo]
            set dobreak 1
          }
          lappend newrems $rem
        }
        if {$dobreak} {
          set tdlistOrig [lreplace $tdlistOrig $ilo $ilo [list $prj $newrems]]
          break
        }
        incr ilo
      }
    }
    #_______________________
    
    proc todolist::ItemValues {item prj date todo} {
      # Gets -values option of tree item.
      #   item - item ID
      #   prj - project name
      #   date - date of todo
      #   todo - todo text
    
      set todo [string map [list \n { }] $todo]
      list $todo $item $prj $date
    }
    #_______________________
    
    proc todolist::TodoTag {date} {
      # Gets -tag option for current todo (highlighted or normal).
      #   date - date of todo
    
      set datecur [alited::project::ClockFormat [clock seconds]]
      if {$date == $datecur} {
        set tag {-tag tagBold}
      } elseif {$date < $datecur} {
        set tag {-tag tagTODO}
      } else {
        set tag {}
      }
      return $tag
    }
    #_______________________
    
    proc todolist::ItemIndex {item} {
      # Gets item's index from item's ID.
      #   item - ID of item
    
      expr {[string range $item 3 end]-1}
    }
    #_______________________
    
    proc todolist::Tooltip {ID nc} {
      # Gets a tip for tree's item.
      #   ID - ID of treeview item
      #   nc - column of treeview item
    
      variable obTdl
      variable tdlist
      variable tdlistOrig
      variable tdlistOrigDates
      set tree [$obTdl TreeTdl]
      lassign [$tree item $ID -values] todo item prj date
      set tip {}
      switch -- $nc {
        {#0} {
          foreach td $tdlist {
            lassign $td prj2 date2 todo2 idtd2
            if {$prj2 eq $prj && $date2 eq $date} {
              set i [lsearch -index 0 $tdlistOrigDates $idtd2]
              set dateorig [lindex $tdlistOrigDates $i 1]
              if {$dateorig ne {} && $dateorig ne $date} {
                set pad [string repeat { } 26]
                set tip "$pad\n $dateorig  =>  $date \n$pad"
              }
              break
            }
          }
        }
        {#1} {
          set i [lsearch -exact -index 0 $tdlistOrig $prj]
          set rems [lindex $tdlistOrig $i 1]
          set i [lsearch -index 0 $rems $date]
          set tip [lindex $rems $i 1]
        }
      }
      return $tip
    }
    
    # ________________________ Selections _________________________ #
    
    proc todolist::Select {{item ""}} {
      # Selects an item of the list.
      #   item - index (ID) of list item
    
      variable obTdl
      variable item1st
      catch {
        set tree [$obTdl TreeTdl]
        if {$item eq {}} {set item $item1st}
        focus $tree
        $tree focus $item
        $tree selection set $item
      }
    }
    #_______________________
    
    proc todolist::Selected {what {domsg yes}} {
      # Gets ID or index of currently selected item of the list.
      #   what - if "index", gets a current item's index
      #   domsg - if yes, shows a message about the selection
    
      variable obTdl
      set tree [$obTdl TreeTdl]
      if {[set isel [$tree selection]] eq {} && [set isel [$tree focus]] eq {} && $domsg} {
        Message $::alited::al(MC,prjsel) 4
      }
      if {$isel ne {} && $what eq {index}} {
        set isel [ItemIndex $isel]
      }
      return $isel
    }
    #_______________________
    
    proc todolist::onSelect {} {
      # Handles event "selecting a tree item"
    
      variable obTdl
      lassign [PrjDateCont] prjname date todo
      if {$prjname ne {}} {
        set wtxt [$obTdl TexTdl]
        alited::ini::HighlightFileText $wtxt .md 0 -cmdpos ::apave::None -cmd ::apave::None
        $obTdl displayText $wtxt $todo
        if {$date eq {}} {
          set readonly 1
          set procModified ::apave::None
        } else {
          set readonly 0
          set procModified alited::todolist::TextModified
        }
        alited::ini::HighlightFileText $wtxt .md $readonly -cmd $procModified
      }
    }
    
    # ________________________ Modifications _________________________ #
    
    proc todolist::TextModified {wtxt args} {
      # Processes modifications of TODO text.
      #   wtxt - text's path
      #   args - not used arguments
    
      namespace upvar ::alited al al
      lassign [PrjDateCont] prjname date - isel idtd
      if {$prjname ne {}} {
        set todo [string trim [$wtxt get 1.0 end]]
        UpdateInList $isel $prjname $date $todo $idtd
        set aft _TODO_TextModified
        catch {after cancel $al($aft)}
        set al($aft) [after idle alited::todolist::TODO_save]
      }
    }
    #_______________________
    
    proc todolist::TODO_save {args} {
      # Saves a reminder on a date.
      #   args - may contain project name, new date, todo
    
      variable obTdl
      if {[llength $args]} {
        lassign $args prjname date todo
        set txt $todo
      } else {
        lassign [PrjDateCont] prjname date todo
        set txt [$obTdl TexTdl]
      }
      set item [Selected item no]
      set values [ItemValues $item $prjname $date $todo]
      [$obTdl TreeTdl] item $item -text $date -values $values
      alited::project::Klnd_Dosave $txt $prjname $date no
    }
    
    # ________________________ Filter _________________________ #
    
    proc todolist::FilterList {args} {
      # Filters todo list.
      #   args - when called on validation, contains current filter value
    
      variable tdlist
      variable tdlistOrig
      variable tdlistOrigDates
      variable obTdl
      set tdlist [list]
      set filt [string trim [lindex $args 0]]
      set onvalid [llength $args]
      set wasfilter 0
      foreach tdinfo $tdlistOrig {
        lassign $tdinfo prj rems
        lappend tdlist $prj
        set dofilter 1
        foreach rem $rems {
          incr idtd
          lassign $rem date todo
          if {$filt eq {} || [string match -nocase *$filt* $todo]} {
            lappend tdlist [list $prj $date $todo $idtd]
            set dofilter 0
          }
          if {!$onvalid} {lappend tdlistOrigDates [list $idtd $date]}
        }
        if {$dofilter} {
          set tdlist [lreplace $tdlist end end]
          set wasfilter 1
        }
      }
      if {$onvalid} {  ;# called on validation -> update the list
        UpdateTree no
        set tree [$obTdl TreeTdl]
        if {$wasfilter || [$tree selection] eq {}} {
          Select
          focus [$obTdl EntFilter]
        }
      }
      return 1
    }
    #_______________________
    
    proc todolist::ClearFilter {} {
      # Clears filter's entry.
    
      variable filter
      set filter {}
      FilterList $filter
    }
    
    # ________________________ Tool buttons _________________________ #
    
    proc todolist::Tool_button {ev} {
      # Fire an event handler (paste/undo/redo) on a reminder.
      #   ev - event to fire
    
      variable obTdl
      ::apave::eventOnText [$obTdl TexTdl] $ev
    }
    #_______________________
    
    proc todolist::Paste {} {
      # Pastes a text to a reminder.
    
      Tool_button <<Paste>>
    }
    #_______________________
    
    proc todolist::Undo {} {
      # Undoes changes of a reminder.
    
      Tool_button <<Undo>>
    }
    #_______________________
    
    proc todolist::Redo {} {
      # Redoes changes of a reminder.
    
      Tool_button <<Redo>>
    }
    #_______________________
    
    proc todolist::Delete {} {
      # Handles "Delete template" button.
    
      variable obTdl
      lassign [PrjDateCont] prjname date
      if {$date ne {}} {[$obTdl TexTdl] replace 1.0 end {}}
    }
    #_______________________
    
    proc todolist::MoveTo {days} {
      # Shifts current date by *days*.
      #   days - days to shift
    
      variable obTdl
      variable tdlistOrig
      set item [Selected item]
      lassign [PrjDateCont] prjname date todo isel idtd
      if {$date ne {}} {
        set date2 [alited::project::ClockScan $date]
        set date2 [clock add $date2 $days days]
        set date2 [alited::project::ClockFormat $date2]
        # check if new day is free of TODO
        foreach tdinfo $tdlistOrig {
          lassign $tdinfo prj1 rems
          foreach rem $rems {
            lassign $rem date1
            incr id1
            if {$prj1 eq $prjname && $date1 eq $date2 && $id1 != $idtd} {
              # there is TODO on this day
              Message $::alited::al(MC,tododupl) 4
              return
            }
          }
        }
        TODO_save $prjname $date {}
        TODO_save $prjname $date2 $todo
        UpdateInList $isel $prjname $date2 $todo $idtd
        set tree [$obTdl TreeTdl]
        CheckSwapItems $tree $item $date2 $days
        $tree tag remove tagBold $item
        $tree tag remove tagTODO $item
        lassign [TodoTag $date2] -> tag
        if {$tag ne {}} {$tree tag add $tag $item}
        Select $item
      }
    }
    #_______________________
    
    proc todolist::CheckSwapItems {tree item date days} {
      # Checks and at need swaps tree items according to dates.
      #   tree - item tree
      #   item - current item's ID
      #   date - current item's date
      #   days - if positive then swap forward, otherwise backward
    
      set paritem [$tree parent $item]
      set items [$tree children $paritem]
      set dir [expr {$days>0? 1 : -1}]
      set i 0
      foreach it $items {
        if {$it eq $item} {
          set itSwap [lindex $items [expr {$i+$dir}]]
          if {$itSwap eq {}} return
          set dateSwap [lindex [$tree item $itSwap -values] 3]
          if {$dir>0 && $dateSwap < $date || $dir<0 && $dateSwap > $date} {
            set items [lreplace $items $i $i]
            set items [linsert $items [incr i $dir] $item]
            break
          }
        }
        lappend newitems $it
        incr i
      }
      $tree children $paritem $items
    }
    
    # ________________________ Buttons _________________________ #
    
    proc todolist::Ok {args} {
      # Handles "OK" button.
    
      lassign [PrjDateCont] prjname date todo
      if {$prjname eq {}} {
        Message $::alited::al(MC,prjsel) 4
        Select
        return
      }
      after 500 [list alited::main::ShowOutdatedTODO $prjname $date $todo 1]
      QuitDialog $prjname
    }
    #_______________________
    
    proc todolist::Cancel {args} {
      # Handles "Cancel" button.
    
      QuitDialog {}
    }
    #_______________________
    
    proc todolist::Help {args} {
      # Handles "Help" button.
    
      variable win
      alited::Help $win
    }
    #_______________________
    
    proc todolist::QuitDialog {res} {
      # Quits the dialog.
      #   res - result (1/0 for OK/Cancel)
    
      variable obTdl
      variable win
      set ::alited::project::geotodolist [wm geometry $win]
      $obTdl res $win $res
    }
    
    # ________________________ UI _________________________ #
    
    proc todolist::_create {} {
      # Creates "Pending TODO list" dialogue.
    
      namespace upvar ::alited al al
      variable win
      variable obTdl
      variable tdlist
      ::apave::APave create $obTdl $win
      $obTdl makeWindow $win $al(MC,prjTmore)
      $obTdl paveWindow $win {
        {fraTreeTdl - - 10 10 {-st nswe -rw 3 -pady 8} {}}
        {.TreeTdl - - - - {pack -side left -expand 1 -fill both}
          {-h 12 -columns {C1 C2 C3} -displaycolumns {C1}
          -columnoptions "#0 {-minwidth 50 -width 180 -stretch 0} C1 {-stretch 1}"
          -tip {-BALTIP {alited::todolist::Tooltip %i %c} -SHIFTX 10 -PER10 4000}
          -style TreeNoHL -onevent {
          <<TreeviewSelect>> alited::todolist::onSelect
          <Delete> alited::todolist::Delete
          <Double-Button-1> alited::todolist::Ok
          <Return> alited::todolist::Ok}}}
        {.sbvTdls + L - - {pack -side left -fill both}}
        {fra1 fraTreeTdl T 10 10 {-st nsew}}
        {.btTdel - - - - {pack -side left} {-image alimg_delete
          -com alited::todolist::Delete -tip {$al(MC,prjTdelete)}}}
        {.btTpaste - - - - {pack -side left} {-image alimg_paste
          -com alited::todolist::Paste -tip Paste}}
        {.btTundo - - - - {pack -side left} {-image alimg_undo
          -com alited::todolist::Undo -tip Undo}}
        {.btTredo - - - - {pack -side left} {-image alimg_redo
          -com alited::todolist::Redo -tip Redo}}
        {.sev - - - - {pack -side left -padx 8 -fill y}}
        {.btTprevious2 - - - - {pack -side left} {-image alimg_previous2
          -com {alited::todolist::MoveTo -7} -tip {$al(MC,prjTprevious2)}}}
        {.btTprevious - - - - {pack -side left} {-image alimg_previous
          -com {alited::todolist::MoveTo -1} -tip {$al(MC,prjTprevious)}}}
        {.btTnext - - - - {pack -side left} {-image alimg_next
          -com {alited::todolist::MoveTo 1} -tip {$al(MC,prjTnext)}}}
        {.btTnext2 - - - - {pack -side left} {-image alimg_next2
          -com {alited::todolist::MoveTo 7} -tip {$al(MC,prjTnext2)}}}
        {.h_ - - - - {pack -side left -expand 1 -fill both -padx 8}}
        {.labFilter - - - - {pack -side left -padx 8} {-anchor center -t Filter:}}
        {.EntFilter - - - - {pack -side left}
          {-tvar ::alited::todolist::filter -w 20
          -validate key -validatecommand {alited::todolist::FilterList %P}}}
        {.btTno - - - - {pack -side left} {-com alited::todolist::ClearFilter}}
        {fratex fra1 T 10 10 {-st nsew -rw 1 -cw 1} {}}
        {.TexTdl - - - - {pack -side left -expand 1 -fill both}
          {-h 6 -w 4 -wrap none -tabnext *.butOK
          -tip {-BALTIP {$al(MC,prjTtext)} -MAXEXP 3}}}
        {.sbvTdl + L - - pack {}}
        {LabMess fratex T 1 10 {-st nsew -pady 0 -padx 3} {-style TLabelFS
          -onevent {<Button-1> alited::todolist::ProcMessage}}}
        {fra3 + T 1 10 {-st nsew}}
        {.ButHelp - - - - {pack -side left}
          {-t {$al(MC,help)} -tip F1 -com alited::todolist::Help}}
        {.h_ - - - - {pack -side left -expand 1 -fill both}}
        {.butOK - - - - {pack -side left -padx 2}
          {-t "$al(MC,select)" -com alited::todolist::Ok}}
        {.butCancel - - - - {pack -side left}
          {-t Close -com alited::todolist::Cancel}}
      }
      set tree [$obTdl TreeTdl]
      $tree heading #0 -text "$al(MC,project) / [msgcat::mc Date]"
      $tree heading #1 -text [msgcat::mc Reminder]
      alited::tree::AddTags $tree
      UpdateTree
      after 250 alited::todolist::CheckEmpty
      bind $win <F1> "[$obTdl ButHelp] invoke"
      set res [$obTdl showModal $win -onclose ::alited::todolist::Cancel \
        -parent $::alited::project::win -resizable 1 -minsize {400 250} \
        -geometry $::alited::project::geotodolist -focus $tree]
      catch {destroy $win}
      $obTdl destroy
      return $res
    }
    #_______________________
    
    proc todolist::_run {tdlInput} {
      # Runs "Pending TODOs" dialogue.
      #   tdlInput - todo list
    
      variable win
      variable tdlistOrig
      variable tdlistOrigDates
      variable filter
      if {[winfo exists $win]} {return {}}
      set filter {}
      set tdlistOrig $tdlInput
      set tdlistOrigDates [list]
      FilterList
      set res [_create]
      return $res
    }
    
    
    todolist.tcl