Message Boards Message Boards

GROUPS:

[Utilities] One To-Do list a day keeps the oblivion away

Posted 6 months ago
1634 Views
|
3 Replies
|
18 Total Likes
|

enter image description here


One To-Do list a day keeps the oblivion away

Organizing your life and work with a To-Do list, in Mathematica!

Because of the well-known reason, lots of people have temporarily changed to a quite different lifestyle. For some of us who stay at home and work from home for a long time, it can become hard to track a regular life and work routine. For many of us who are supposed to reduce our frequency of visiting crowded public place like a market, it would be a good practice to watch our pantries and have plans in advance for necessities.

For those kinds of purposes, To-Do list is a quite handy choice. To avoid installing yet another program for just a simple purpose, I decided to make my own using the flexible Notebook stack -- as I nearly always have a Mathematica running on desktop all the time. This small tool helped me a lot in last 2 months during which time I have to stay at home, so I'm sharing it to the community, hoping it can be of help for others.


What does it feel like? -- Experience of the To-Do list

My approach to a To-Do list is directly through notebook programming. But instead of heavily relying on Dynamic framework, I try to minimize and localize it's usage as much as possible, so we can have a mostly static notebook.

The state pickers (the RadioButtonBars at the right end) of item Cells are implemented in the stylesheet, so the To-Do list it self is pure static and easy to programly manipulate.

TODO - md - stylesheet

Currently the state of an item is encoded as style options of its cell, which is not good and subject to change. (I'm still looking for a nice and generic way to store data locally in a cell.)

Features

  • Every item has a state picker at the right end:

TODO - md - feature 1

The default stylesheet has 3 states:

TODO - md - feature 1 - 2

  • Multiple item levels are available, with progressive indentations:

TODO - md - feature 2

  • Items of different levels group and fold like Section/Subsection styles:

TODO - md - feature 3

Shortcuts

  • Pressing Alt + a key will create a new level 1 item cell.

TODO - md - shortcut - 1

  • Pressing Enter key in an item cell will create a new item cell of the same level. This behaviour minics the Item/Subitem styles.

TODO - md - shortcut - 2

  • Pressing Tab or * key at the beginning of an item cell will push it to 1 level down. Pressing Shift + Tab or Backspace key at the beginning of an item cell will pull it to 1 level up. This behaviour minics the Item/Subitem styles.

TODO - md - shortcut - 3


What does it feel like? -- Meta-experience of the To-Do list

Readers who are happy with our default To-Do stylesheet can safely ignore the section below, where we'll walk through how this To-Do list is made. But if you want to generate your own stylesheet, please do take a look.

As a highly programmable GUI, we are of course not stopping at one stylesheet. In fact we have this much more efficient way to generate To-Do list stylesheets as many as we want through notebook programming.

TODO - md - stylesheet gallery

General design

Implementation of features

  • In general design, we indent cells with CellMargins, stylize them with CellFrame and CellFrameStyle.

  • The "state picker" is implemented as a stand-alone cell in CellFrameLabels. We'll talk about it later.

  • Section-like grouping behaviour is realized through setting proper CellGroupingRules.

Implementation of shortcuts

  • MenuCommandKey is used to implement 1st shortcut.

  • Two options, DefaultNewCellStyle and ReturnCreatesNewCell, are used to implement 2nd shortcut.

  • StyleKeyMapping is used to implement 3rd shortcut.

The state picker

Our state picker is a RadioButtonBar[Dynamic[status, ...]], but we are doing more than usual inside the 2nd argument of Dynamic.

Firstly we using notebook API SelectionMove and SelectedCells to locate whatever cell this controller lives in, then we change its style according to the localized controller state status, a dummy variable that doesn't persist in the notebook file. (This behaviour is subject to change as soon as I determine how I want to persist data.)

RadioButtonBar[
 Dynamic[status, (
    status = #
    ; workCell = (
      SelectionMove[EvaluationCell[], All, EvaluationCell]
      ; SelectedCells[][[1]]
      )
    ; SetOptions[workCell
     , FontVariations -> {"StrikeThrough" -> Not[status === defaultStatus]}
     , FontColor      -> colorOpt[status]
     , Background     -> If[status === defaultStatus, None, GrayLevel[0.95]]
     ]
    ) &]
 , {val1 -> lbl1, val2 -> lbl2, ... }
 ]

Code

We put the full code we used to generate our To-Do list stylesheets here.

Helper functions

pipe = RightComposition;
branch = Through@*{##} &;

Configuration

A To-Do list stylesheet is fully described by two configuration variables.

TODO - md - config

State picker

myStatusConfig is used to describe all possible states and their styles.

Any number of states should be fine, the first one will be used as the default state. Localization can be done by using different language in "txt".

myStatusConfig = <|
   "value" -> {"todo", "done", "partial", "pass"}
   , "txt" -> Map[Style[#, FontFamily -> "Segoe Script"] &, {"To-Do", "Done", "Half-Donw", "Pass"}]
   , "color" -> { GrayLevel[0.2]
                , RGBColor[0.5254901960784314, 0.7450980392156863, 0.19607843137254902`]
                , RGBColor[1, 0.7450980392156863, 0.19607843137254902`], GrayLevel[0.75]
                }|>;
To-Do list

myStyleConfig is used to describe the appearance of the To-Do list itself.

  • "levelRange" determines the most up and down levels the stylesheet will cover.

  • "indent0" and "indentStep" determine how the indentation behaves.

  • "frameRange" determines range of thickness of the leading bar in front of each cell.

All ranges are running from the most up level to deepest level.

myStyleConfig = <|
   "levelRange" -> {1, 6}
   , "indent0" -> 10, "indentStep" -> 24
   , "frameRange" -> {10, 2}
   , "fontSizeRange" -> {13, 8}
   , "colorFunc" -> ColorData["Rainbow"]
   |>;

Construction of state picker

We have gone through the basic principle above. Here is the full code for it.

ClearAll[todoBar]
todoBar = With[{
    defaultStatus = myStatusConfig[["value", 1]]
    , colorOpt = myStatusConfig // pipe[
            Lookup[{"value", "color"}]
            , Apply@AssociationThread
       ]
    , lblSet = myStatusConfig // pipe[
            Lookup[{"value", "txt"}]
            , MapThread[#1 -> Framed[#2, FrameMargins -> None, FrameStyle -> None] &]
       ]
    },
   DynamicModule[{status = defaultStatus, workCell}
       ,RadioButtonBar[Dynamic[status
         , Function[
                status = #
                ; workCell = (SelectionMove[EvaluationCell[], All, EvaluationCell]; SelectedCells[][[1]])
                ; SetOptions[workCell
                    , FontVariations -> {"StrikeThrough" -> Not[status === defaultStatus]}
                    , FontColor -> colorOpt[status]
                    , Background -> If[status === defaultStatus, None, GrayLevel[.95]]
                 ]
          ]
         ]
        , lblSet
        , Method -> "Active", Appearance -> "Horizontal"]
       ] // Echo // ToBoxes //
    Cell[BoxData[#]
      , Background -> White
      , FontWeight -> Bold, FontColor -> GrayLevel[0.2], FontSize -> 8
      , FontVariations -> {"StrikeThrough" -> False}
      , ShowStringCharacters -> False
      ] &
   ];

Construction of stylesheet

Generating an instance of the stylesheet from myStyleConfig is basically applying a predefined template.

ClearAll[todoStylesheet]
todoStylesheet = With[{
     levelRange = myStyleConfig@"levelRange"
     , indent0 = myStyleConfig@"indent0", 
     indentStep = myStyleConfig@"indentStep"
     , frameRange = myStyleConfig@"frameRange"
     , fontSizeRange = myStyleConfig@"fontSizeRange"
     , colorFunc = myStyleConfig@"colorFunc"
     },
    Module[{
          lvlCounter = #,
          currentLvl, superiorLvl, inferiorLvl,
          frameThickness,
          themeColor,
          cellIndentMarg, groupingLvl,
          ftsize, ftFamily
          },
         themeColor = lvlCounter //
           pipe[
            Rescale[#, levelRange, {0, 1}] &
            , colorFunc, ColorConvert[#, "RGB"] &
            ]
         ;
         ftsize = Rescale[lvlCounter, levelRange, fontSizeRange] // Round
         ; 
         ftFamily = Which[(*ftsize<=11*)lvlCounter > 1, "Arial", True, "Times"]
         ;
         {currentLvl, cellIndentMarg, groupingLvl, frameThickness} =
           {
                StringJoin[{"TodoItem_", ToString@lvlCounter}]
                , indent0 + indentStep (lvlCounter - 1)
                , 15000 + 100 (lvlCounter - 1)
                , Rescale[lvlCounter, levelRange, frameRange] // Ceiling
           }
         ; superiorLvl = StringJoin[{"TodoItem_", ToString[lvlCounter - 1]}]
         ; inferiorLvl = StringJoin[{"TodoItem_", ToString[lvlCounter + 1]}]
         ;
         Cell[
          StyleData[currentLvl]
          , CellFrame -> {{frameThickness, 1}, {1, 1}}
          , System`CellFrameStyle -> {{themeColor, GrayLevel[1, 0]}, {GrayLevel[1, 0], GrayLevel[1, 0]}}
          , CellFrameLabels -> {{None, todoBar}, {None, None}}
          , CellFrameMargins -> {{10, 5}, {5, 1}}
          , CellMargins -> {{cellIndentMarg, 50}, {1, 0}}
          , System`ReturnCreatesNewCell -> True
          , StyleKeyMapping -> {
                "Tab" -> inferiorLvl
                , "*" -> inferiorLvl
                , "Backspace" -> superiorLvl
                , System`KeyEvent["Tab", System`Modifiers -> {System`Shift}] -> superiorLvl
            }
          , CellGroupingRules -> {"SectionGrouping", groupingLvl}
          , CellFrameLabelMargins -> 4
          , DefaultNewCellStyle -> currentLvl
          , CounterIncrements -> currentLvl
          , CounterAssignments -> {{inferiorLvl, 0}}
          , FontFamily -> ftFamily
          , FontSize -> ftsize
          , FontColor -> GrayLevel[0.2]
          , If[lvlCounter == 1, Inactive[Rule][FontWeight, "Bold"], Inactive[Sequence][]]
          , If[lvlCounter == 1, Inactive[Rule][MenuCommandKey, "a"], Inactive[Sequence][]]
          ]
         ] & /@ Range @@ levelRange //
      Join[
            {
              Cell[StyleData[StyleDefinitions -> "Default.nb"]]
            , Cell[StyleData["Notebook"]
                , ShowCellBracket -> False
                , ShowGroupOpener -> "Inline"
                , System`TrackCellChangeTimes -> False
                , Magnification -> 1]
            }
          , #
          , {
              Cell[StyleData["Text"]
               , FontSize -> fontSizeRange[[2]] + 1
               , CellMargins -> {{indent0 + indentStep (levelRange[[2]] - 1) + 10, 5}, {1, 0}}
               ]
            }
        ] & // Activate
    ] //
   Notebook[#, StyleDefinitions -> "Default.nb"] &;

We can have a look at the stylesheet:

NotebookPut@todoStylesheet

TODO - md - stylesheet example

Using the stylesheet

If you are satisfied with your newly generated stylesheet, you can choose to save it to user's stylesheet folder:

FileNameJoin[{$UserBaseDirectory, "SystemFiles\\FrontEnd\\StyleSheets"}]

You might need to restart the Front End to actually see it under menu-bar » Format » Stylesheet.

Or you can use it directly as a private stylesheet:

Notebook[Join[
        {Cell["Full Style List", "Section"]}
        , pipe[StringTemplate["TodoItem_``"], Cell[#, #] &] /@ Range[6]
        , {Cell["Text", "Text"]}
   ]
  , StyleDefinitions -> todoStylesheet] //
 NotebookPut

TODO - md - list example

Hope this small To-Do list can be of help for those who need one. And it would be even better if you found notebook programming is interesting!

Attachments:
3 Replies

enter image description here -- you have earned Featured Contributor Badge enter image description here

Your exceptional post has been selected for our editorial column Staff Picks http://wolfr.am/StaffPicks and Your Profile is now distinguished by a Featured Contributor Badge and is displayed on the Featured Contributor Board. Thank you!

One possibility to store data in a cell is by using TaggingRules. Like, e.g.,

CurrentValue[PreviousCell[], {TaggingRules, "mydata"}] = 42

It looks promising! Thank you Rolf! I thought TaggingRules can only be stored at the notebook level.

Reply to this discussion
Community posts can be styled and formatted using the Markdown syntax.
Reply Preview
Attachments
Remove
or Discard

Group Abstract Group Abstract