Database access for options of all widgets in an application with Bwise

by Theo Verelst

This page is about a 'little chore' I did in the progress of making bwise canvasses save not just drawable elements, but also widgets, which can be part of bwise blocks like entries, the oscilloscope, shells, etc.

Much earlier I liked to save the UI of a Tk application by foreach-ing all widgets with a recursive list routine, for instance ilist, and then be able to read a prototyped UI back in the next session by sourcing the result with all options from a file. At some point, it become unclear to me how the options actually held together, because that technique generated errors with later Tcl versions, which was a pity.

Now, I thought I'd first have a look at the options and the non-writable ones, before starting a save-upgrade, and it seemed natural to spend a few script lines to use (at least 6/7 years old) Database routines for in core, which are list based, and simple enough to use with default settings.

Image Bwise optiondb1.jpg

Also, I'll probably find opportunity to make a upgraded complete, one-about-hundred-K-file bwise available, possibly when I'm done.

To get a list of all widgets, the latest bwise thus far has a function called ilist, which returns a list of all widgets with default arguments.

One could also use pro_args and that procedure (or simply the right arguments, pro_args just helps with named arguments) to make another widget list in this case only one deep:

 eval [pro_args ilist {{begin .fb} {maxdepth 1}}]

To make a list-contained database (a list of lists) of the configuration option values of all widgets currently present in the application:

 set dbvar {}
 foreach w [ilist] {
    set t [list [list widget $w]];
    foreach i [$w conf] {
       lappend t [list [lindex $i 0] [lindex $i end]]
    } ;
    lappend dbvar $t
 }

After possibly changing the routine 'onefield' from bwise (just double click it in the function window list) to use a smaller font to make the normal option list fit on your screen (I used point size 7 for the above), unless you have 1200 vertical pixels, then you should be fine, type:

 dbaccess

which shows you the above database entry window, where you can Previous Next and other database access options through the widget database. Adapt the dbform procedure to have a 'Do' button which lets you update widgets to reflect the changes you make to the database browse window (between the #####'s is the addition):

proc dbform { {fields} {title {Database Form}} {window {.dbf}} {fw {20}} {ew {20}} } {

   global dbcurrent ccontent  cname cvars currententry
   global searchstring searchfields
   if {[winfo exists $window] == 0}  {toplevel $window}  {foreach i [winfo children $window] {destroy $i} };
   $window conf -bg white;
   label $window.t -text $title -font "helvetica 11" -bg yellow -fg blue;
   pack $window.t -anchor n -padx 3 -pady 3;

   list2array $fields

   foreach i $cvars {
      set wn [string tolower $i];
 #      onefield $window.$wn $cname($i) ccontent($i) $fw $ew;
      onefield $window.$wn $i ccontent($i) $fw $ew;
   }
   global currententry newcurrententry
   frame $window.wb; pack $window.wb -side bottom -anchor s -fill x -expand n
   button $window.wb.ne -text Next -command {
      global newcurrententry dbvar
      if {$newcurrententry < [expr [llength $dbvar] -1]} {incr newcurrententry}
      display_entry $newcurrententry
   }
   pack $window.wb.ne -side right
   button $window.wb.pre -text Previous -command {
      global newcurrententry
      incr newcurrententry -1
      if {$newcurrententry < 0} {set newcurrententry 0}
      display_entry $newcurrententry
   }
   entry $window.wb.ee  -width 5 -textvar newcurrententry
   pack $window.wb.ee -side right
   bind $window.wb.ee <Return> {
      global newcurrententry currententry dbvar
      if {$newcurrententry > [expr [llength $dbvar] -1]} {
          set newcurrententry $currententry }
      if {$newcurrententry < 0 } { set newcurrententry $currententry }
      display_entry $newcurrententry
   }
   pack $window.wb.pre -side right
   button $window.wb.undo -text Unedit -command {list2array $dbcurrent}
   pack $window.wb.undo -side left
   frame $window.ws; pack $window.ws -side bottom -anchor s -fill x -expand n
   label $window.ws.l -text "Field(s), Search string" -font "helvetica 12"
   pack $window.ws.l -side left
   entry $window.ws.es -textvar searchstring -width 16
   pack $window.ws.es -side right
   entry $window.ws.ef -textvar searchfields -width 10
   pack $window.ws.ef -side right
   bind $window.ws.es <Return> {
      global newcurrententry;
      set newcurrententry [lindex [lindex [dbsearch $searchstring $searchfields [list [expr $newcurrententry+1] end]] 0] 0];
      set t [bind .dbf.wb.ee <Return>];
      eval $t
   }
 #####################
   button .dbf.wb.do -text Do -command {
      set dbcurrent [array2list];
      foreach i $dbcurrent {
         if {[lindex $i 0] == "widget"} {
            set wi [lindex $i 1]
         } ;
      }
      foreach i $dbcurrent {if [catch {eval $wi conf $i} j] {puts $i,$j} }
   }
   pack .dbf.wb.do -side left
 #####################

   bind $window <Key-F1> "$window.wb.pre invoke"
   bind $window <Key-F2> "$window.wb.ne invoke"
 } 

This dbform procedure prints the caught errors when pressing the 'Do' button to update the widget from the 'widget' field, option by option, which is done blindly, assuming all fields are options.

It appears both on Microsoft Windows XP and Linux (RH 9 with Window Maker[L1 ]) with info patchlevel version 8.4.4 that for all widgets I tested thus far the configuration options which cannot be read back as they are reported by the last element of each config sublist are:

   -bd
   -bg
   -fg

So probably, for error free read back of all options, checking for options starting with - is sufficient.

I'd want to save for instance bwise canvasses with widgets on it, and preferably as a source-able file, so all errors, preferably on all tcl versions must be prevented, because one option read error would break the 'source' command, and probably further commands wouldn't be read from it, not even using

   catch {source widgets.tcl}

reading lines by hand and selecting valid options, or catching per command or option is probably slower, a hassle, and not as generally applicable.

For normal use a save/load combination which works for one machine would be a good target, but it would make bwise canvas saves which include widgets possibly not readable error free on another system. Not good.

As a UI issue, this setup can also be used to edit itself, just select an element from the above window in the database, after having run the script from the top of the page to update the database, so that the dbvar variable also contains the options of the .dbf window with all the option fields, and then move into that parts of the 'database' with Next or by filling in a higher index. When changing some property related to the database interaction window, press 'Do' just the same to see the results in the chosen field or control widget immediately, but only for one widget, because selecting another database entry redraws all window components as it is.

A page about the database procedures: Database routines for in core .

On Linux, the entries take more space around them, so the font would have to be smaller still to fit all conf options on a normal screen, so you may want to -borderwidth 1 or even zero in the onefield procedure where 'entry $path.e ..' is defined. Also, because of this the label grays are no longer uniformly adjacent.