Version 13 of A vertical-tab notebook

Updated 2003-07-12 05:04:30

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 -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?

Mike Tuxford: It's more than what the wish-reaper expects. I only use my human-reaper method and I had the same problems by doing a clip/paste into emacs and then running the script. I had not read the comments first, I wanted a look/see at the app itself first. The wiki didn't recognize it as part of the script's code. The wish-reaper didn't, and my human-brain-reader didn't. I think the verdict's in.

Category Example | Category GUI