A vertical-tab notebook

WikiDbImage vnotebook.jpg

 set docu(VNotebook) {
   [Richard Suchenwirth] 2002-08-20 - This is a study in using the [BWidgets] toolkit,
   in particular using their PagesManager to implement a notebook with
   vertically-stacked tabs, by default to the right. The demo at end shows
   a usage example which may be used as a telephone booklet. As added
   goodie (if you have the code from [An i15d date chooser] and
   [A little calculator] available), the last page contains one of each -
   just to show that you can pack other things than text widgets. Enjoy!
 package require BWidget

 proc VNotebook {w args} {
        frame $w         ;# container around the whole
        frame $w.tabs    ;# will hold tab buttons
        # dummy button, not managed, to keep font info
    set opt(-font) [[button $w.tabs.0] cget -font]
    set defaults {-side right}
    array set opt [concat $defaults $args]
    foreach i {font side} {
        set $i $opt(-$i); unset opt(-$i)
    } ;# so PageManager won't see those
    $w.tabs.0 config -font $font ;# so others can cget it
        eval PagesManager $w.nb [array get opt]
        pack $w.tabs -side $side -fill both
        pack $w.nb   -side $side -fill both -expand 1 -padx 0
        rename $w _$w  ;# won't use it anymore, but must survive
        proc $w {cmd args} {
            set fileopt {-filetypes {{Notebook .not} {{All files} .*}}\
                -defaultextension .not}
            set w [lindex [info level 0] 0] ;# retrieve own name
            switch -- $cmd {
            add    {
                    set font [$w.tabs.0 cget -font]
                    set b [button $w.tabs.b$args -text $args -font $font\
                        -command [list $w raise $args] -bg white \
                        -borderwidth 1 -pady 0]
                    pack $b -fill both -expand 1
            delete {destroy $w.tabs.b$args}
            open {
                set file [eval tk_getOpenFile $fileopt]
                if {$file!=""} {
                    foreach {page content} [source $file] {
                        set t [$w.nb getframe $page].t
                        $t delete 1.0 end
                        $t insert end $content
                return $file
            raise  {
                set bg [$w.tabs.0 cget -bg]
                foreach i [winfo children $w.tabs] {
                    $i configure -bg $bg
                $w.tabs.b$args configure -bg white
                catch {focus [$w.nb getframe $args].t}
            raisewhere {
                foreach page [$w.nb pages] {
                    set f [$w.nb getframe $page]
                    if [regexp -nocase $args [$f.t get 1.0 end]] {
                        $w raise $page
                return ;# this command is not known to P.M.
            save {
                set file [eval tk_getSaveFile $fileopt]
                if {$file!=""} {
                    set fp [open $file w]
                    puts $fp "return {"
                    foreach page [$w.nb pages] {
                        set t [$w.nb getframe $page].t
                        catch {puts $fp [list $page [$t get 1.0 end-1c]]}
                    puts $fp "}"
                    close $fp
                return $file
            eval $w.nb $cmd $args ;# let PagesManager do the rest
        set w
 if {[file tail [info script]]==[file tail $argv0]} {
    # demo and example code, runs when sourced alone
    proc img {name} {
        image create photo -file $::env(BWIDGET_LIBRARY)/images/$name
    frame .0
    button .0.load -image [img open.gif] -command ".v open"
    button .0.save -image [img save.gif] -command ".v save"
    LabelEntry .0.f -label " Find: " -textvariable Find
    .0.f bind <Return> {.v raisewhere $Find}
    eval pack [winfo children .0] -side left -fill y
    pack .0.f -fill x -expand 1
    VNotebook     .v ;#-side left
    pack       .0 .v -fill both -expand 1 -pady 2
    foreach i {AB CD EF GH IJ KL MN OPQ RS TU VW XYZ} {
         set w [.v add $i]
         pack [text $w.t -wrap word -width 40] -fill both -expand 1
    # remove next 3 lines if you don't have/want these goodies
    set w [.v add +]
    source Calendar.tcl; pack [date::chooser $w.c] -fill both -expand 1
    source sep_calc.tcl; pack [calculator $w.f] -fill both -expand 1

    tkwait visibility .v
    set p0 [lindex [.v pages] 0]
    [.v.nb getframe $p0].t insert end [string map {\n " "} $docu(VNotebook)]
    wm protocol . WM_DELETE_WINDOW {.v save; exit}
    after 100 .v raise $p0 ;# delay so top page is correct

escargo 11 Jul 2003 - I saw this code had changed a little, and used my wish-reaper to download it. When I tried to get it to run, I had to define the environment variable BWIDGET_LIBRARY. After that, I discovered that, since I did not have the optional Calendar.tcl and sep_calc.tcl files, I had to modify the code not to source them. Finally, there seems to be no definition of docu(VNotebook). Is that supposed to be defined in some other context?

RS: It is defined, but never used :D I used this convention for a while in the context of htext, so such comments would be available as online documentation too. So it does no harm, but might as well have been the usual if 0 {...}.

escargo - I see the problem now. wish-reaper only collects the code that's indented. The set docu(VNotebook) at the beginning, is not indented, and therefore not seen when the page was reaped. So when I looked in the reaped file, there was no definition of docu at all. Now the source of the problem makes sense. Should the set ... be indented and part of the code?

RS: Technically, it is part of the code even if not indented - as well as the if 0 {...} construct. I like the line formatting of unindented text better...

escargo - I'm not sure I know what you mean by "technically" in this context. According to the reaper applications (such as wish-reaper) if it's code then it needs to be indented. Human interpretation can recognize that the nonindented portion is significant, but the semantics of part of the code is open to interpretation. I know you did it that way, but it does make mechanical capture of the code (by the reapers) a bit hard.

LV Jul 11, 2003 should wish-reaper perhaps look for optional pragma/metadata/some sort of marker that could be used in cases like this to help delineate code?

escargo - The guts of it are in the proc reap body for wish-reaper, which is code I stole from the other reaper application. In short, only lines that start with exactly a single space are written to the output file (right now). This is certainly simple. Perhaps it is too simple. However, some very old pages have turned out to be reapable because they follow this convention already.

RS - On most of my pages, comments longer than a line are always in if 0 {...} blocks, because the whole file is sourced by Tcl, so reaping those shouldn't hurt.

escargo - There are two important aspects to this:

  1. The fact that most pages have the comments in the if 0 { block.
  2. The method by which the wiki page gets turned into the whole file.

I admit the method for transforming a page into a file is simple minded, but there are some advantages to simplicity.

The real question is, would you RS object to the definition of docu(VNotebook) being transformed into the format the reapers expect? - RS: Well, I chose unindented so it gets the better-looking body text formatting when printed. But as this is an episode, I already changed it to indented, for better reapability :-)

escargo 17 Jul 2003 - I have reloaded the code from here, but I ran into another wrinkle. The code for Calculator.tcl (which comes from wiki page 1270) does not define a calculator proc, which is referenced above (source sep_calc.tcl; pack [calculator $w.f] -fill both -expand 1), so presumably what is sourced is a bit different than what appears on that page. I was able to elide lines from the date chooser to get that to work, but that would be unnecessary if the demo script used the idiom of determining whether it was running in its own file or being sourced.

See also: