Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Dynamically add listboxes

4 views
Skip to first unread message

Kevin Walzer

unread,
Nov 26, 2005, 2:05:19 AM11/26/05
to
I'm trying to create a file browser that navigates horizontally through
listboxes (instead of vertically through a tree hierarchy). This is the
NeXT-style of file browsing.

I'm able to create two listboxes that can navigate through a single
level of the file system, but I don't know how to abstract this enough
to create new listboxes dynamically, i.e. add a new listbox for the next
lower level of the file system (analogous to a new, lower child node on
a tree).

Can anyone look at the code below and suggest how I might make it
abstract enough to navigate through an entire file hierarchy? Thanks in
advance.

--

set w [frame .top]
pack $w

listbox $w.list
listbox $w.listnext

pack $w.list $w.listnext -side left

set contents [glob -directory ~/Desktop -nocomplain -tails *]

foreach item $contents {
if {[file isdirectory "$item"]} {
append item /
}
$w.list insert end $item
}

proc navigate {} {
global w
set subdir [$w.list get [$w.list curselection]]
puts $subdir
if { [file isdirectory "$subdir"]} {
set subcontents [glob -directory $subdir -nocomplain -tails *]
foreach subitem $subcontents {
append subitem /
$w.listnext insert end $subitem
}
}
}

bind all <Double-1> {navigate}


--
Cheers,

Kevin Walzer, PhD
WordTech Software - "Tame the Terminal"
http://www.wordtech-software.com
sw at wordtech-software.com

Kaitzschu

unread,
Nov 26, 2005, 5:14:57 AM11/26/05
to
On Sat, 26 Nov 2005, Kevin Walzer wrote:

> I'm trying to create a file browser that navigates horizontally through
> listboxes (instead of vertically through a tree hierarchy). This is the
> NeXT-style of file browsing.

This was such an interesting idea I had to try it out :) But because I'm
not used to do things easy, I wasted two hours of precious breakfast time
and came up with a very ugly ::nextlist namespace/megawidget, that has no
idea how to handle packing inside itself and creates very ugly toplevels.
I can't help it, doing things easy (or well) just doesn't fascinate me
enough to bother.

> I'm able to create two listboxes that can navigate through a single
> level of the file system, but I don't know how to abstract this enough
> to create new listboxes dynamically, i.e. add a new listbox for the next
> lower level of the file system (analogous to a new, lower child node on
> a tree).

This wasn't easy. Well, creating was easy (simplest way to do this: create
a new child-widget of current listbox). The hard part is you can't pack
the child outside the parent. Boo hoo, no miracle of birth here.

So I needed a counter. The rest was pure Hell, so I won't write it here.
Let's see if I can be any help at all without spoiling the adventure you
are about to take off with. Doubt it.

> Can anyone look at the code below and suggest how I might make it
> abstract enough to navigate through an entire file hierarchy? Thanks in
> advance.

First of all, don't create two listboxes, create just one, it'll make it
easier. Believe me, it will, generalization is the key. Actually, don't
create even that first listbox. Write a proc to create the first listbox
and populate it with contents, and once that works, you are a good way
ahead. I'll scribble here some sort of example dynamic listboxes, as that
is the only thing you really asked for:

[code]

proc createFileListBox {parent counter} {
return [listbox $parent.lb$counter]
}
for {set i 0} {$i < 5} {incr i} {
createFileListBox . $i
}

[/code]

This should give you infinite number (or 5 :) of listboxes to have trouble
with. It needs some error-checking (does parent not exist, does lb$counter
already exist, or have monkeys taken over the world and made humans random
typists to write Shakespeare...

Second, you do want to keep track of directories you are in. Trust me, you
do want to. I don't know how to explain why, so I'll write you an example
(already two examples here!):

[example]

> glob -directory ~/Desktop -tails *
{desktopdir desktopdir2}
> file isdirectory desktopdir
0

[example]

Of course this example fails if your [pwd] happens to be ~/Desktop. But
otherwise you should get the point :) Other way around this is not to use
[glob -tails] and [file tail] at insert. Your call.

Third, write nothing twice. You have two [foreach]s in your code that do
pretty much the same thing. Find some way to melt those two in the same
procedure, that could take some pointers about what it should be doing.

And (at) last: what do you think [navigate] will do if by any chance you
are able to double click your listbox with multiple selected items? You
should take a look at [bind] substitutions and [listbox index @]. Or not,
your call again. And don't [bind all], at least if you will _ever_ have
anything other than these listboxes (like scrollbars).

But you'll find that out sooner or later (the later the merrier :)

As this probably helped not-at-all (or less), you may want to ask a more
specific question or wait for better advice (hopefully not working code,
this was such an exciting thingie to write). See you!

--
-Kaitzschu
s="TCL ";while true;do echo -en "\r$s";s=${s:1:${#s}}${s:0:1};sleep .1;done

Ulrich Schöbel

unread,
Nov 26, 2005, 9:48:45 AM11/26/05
to
Hi Kevin,
do you mean something like that:

#! /usr/local/bin/tclkit
package require Tk

proc next_level {w dir} {
set ::dirlist($w) [glob $dir/*]
listbox $w.l -height 10 -width 20 -yscrollcommand [list $w.s set] \
-listvariable ::dirlist($w)
scrollbar $w.s -orient vert -command [list $w.l yview]
frame $w.f
pack $w.l -side left
pack $w.s -fill y -side left
pack $w.f -side left
bind $w.l <Double-1> \
[list eval next_level $w.f "\[$w.l get \[$w.l curselection]]"]
}
next_level "" /

No cosmetics, no fine tuning yet. Just an example.

Best regards

Ulrich


In article <7e2db$43880932$4275d90a$26...@fuse.net>,

Kevin Walzer

unread,
Nov 26, 2005, 5:50:30 PM11/26/05
to
Ulrich Schöbel wrote:
> Hi Kevin,
> do you mean something like that:
>
> #! /usr/local/bin/tclkit
> package require Tk
>
> proc next_level {w dir} {
> set ::dirlist($w) [glob $dir/*]
> listbox $w.l -height 10 -width 20 -yscrollcommand [list $w.s set] \
> -listvariable ::dirlist($w)
> scrollbar $w.s -orient vert -command [list $w.l yview]
> frame $w.f
> pack $w.l -side left
> pack $w.s -fill y -side left
> pack $w.f -side left
> bind $w.l <Double-1> \
> [list eval next_level $w.f "\[$w.l get \[$w.l curselection]]"]
> }
> next_level "" /
>
> No cosmetics, no fine tuning yet. Just an example.
>
> Best regards
>
> Ulrich
>

Ulrich,

This is a very helpful start, thank you.

What I've noticed is that in each subsequent listbox the entire path of
the file or subdirectory is appended, so that if in listbox 1, the
directory is "/usr," in the second listbox it is "/usr/local," and in
the third listbox it is "/usr/local/bin," and in the fourth listbox it
is "/usr/local/bin/foo."

I've been trying various combinations of glob -tails or [file tail [glob
$dir/*] so that the file or directory name only is rendered in the
appropriate listbox (i.e. if we are in the listbox that represents the
directory /usr/local/bin, only "foo" will show up). I have not yet been
able to do this, because the listvariable is directly tied to appending
the next file or directory name to the full existing path.

I haven't yet tried to test for whether the file is a directory or not,
but if a file is not a directory, it throws an error--that's something I
have to work out later.

If you have any ideas about the [file tail $dir/foo] question, I would
appreciate it.

Thank you again.

Ulrich Schöbel

unread,
Nov 26, 2005, 6:42:23 PM11/26/05
to
Hi Kevin,

as I told you, it was only a quick hack to show the
principle. You're right all checks are still missing,
all bells and whistles as well.

The "tail problem" is easy. See the following snippet.
I also added a simple directory check. It's probably
best to use a helper proc in the binding, which does
the checks and maybe a "Back" button.

It's late here, so I leave that up to you.

Best reagrds

Ulrich

#! /usr/local/bin/tclkit
package require Tk

proc next_level {w dir} {
if {![file isdirectory $dir]} {
puts "selected file is $dir"
return
}
set ::dirlist($w) [glob -tails -directory $dir *]


listbox $w.l -height 10 -width 20 -yscrollcommand [list $w.s set] \
-listvariable ::dirlist($w)
scrollbar $w.s -orient vert -command [list $w.l yview]
frame $w.f
pack $w.l -side left
pack $w.s -fill y -side left
pack $w.f -side left
bind $w.l <Double-1> \

[list eval next_level $w.f "\[file join $dir \[$w.l get \[$w.l curselection]]]"]
}

next_level "" /


In article <3b243$4388e6b7$4275d90a$29...@fuse.net>,

Kaitzschu

unread,
Nov 26, 2005, 7:14:24 PM11/26/05
to
On Sat, 26 Nov 2005, Kevin Walzer wrote:

I don't think I'll honor the fact that this was pointed to Ulrich. Since
my previous post wasn't that helpful I think I'll try again.

I told before that you'll want to keep track of directories. There are two
obvious ways to do that. First is what I did (that created need for state
variables and surrounding namespace) but that is not a good one. Because
one is supposed to do as I say instead of how I do you have a fine reason
to read some theory.

You have a code to create listboxes, check.
You have code to populate listboxes, check.
You have code that binds <Double-1> to create and populate, check.
Your creator/populater does take one additional parameter to tell the
whole path to the listbox being doubleclicked, check?

Thought so. I'd rape Ulrichs code but I can't justify those listvariables
(listboxes aren't being reused), [eval] in call-back (it is being [eval]ed
anyway) or deeply nested frames (hateth!), so I'll scribble something up
instead.

Actually, I won't as that would require my namespace-oddity or deeply
nested frames. Or created listboxes wouldn't be destroyable once user
selects another directory from very beginning. Some raping it is. Without
listvariables, though, since those previous directories must go.

And since this design wasn't very flexible I had to "fix" [eval] with the
next worst thing, [subst]. This is so not the way anything should be done,
but it seems to please the audience.

[code]

proc next_level {w dir {sofar {}}} {


listbox $w.l -height 10 -width 20 -yscrollcommand [list $w.s set]

foreach i [glob [file join $sofar $dir *]] {
$w.l insert end [file tail $i]


}
scrollbar $w.s -orient vert -command [list $w.l yview]
frame $w.f
pack $w.l -side left
pack $w.s -fill y -side left
pack $w.f -side left
bind $w.l <Double-1> \

[subst -nocommands {
next_level $w.f \[$w.l get \[$w.l curselection]] [file join $sofar $dir]
}]
}
next_level "" /

[code]

Now next_level has the information it needs. But this is bad design.

Kevin Walzer

unread,
Nov 26, 2005, 7:32:14 PM11/26/05
to
Kaitzschu and Ulrich,

Thanks to both of you for the detailed help. I will study both samples
carefully.

Cheers,
Kevin

Kaitzschu

unread,
Nov 26, 2005, 9:26:35 PM11/26/05
to
On Sat, 26 Nov 2005, Kaitzschu wrote:

> The rest was pure Hell, so I won't write it here.

Sorry, I can't resist. Must spam the usenet. It is still very much not a
good example (heck, I wouldn't call this even a bad example, maybe a good
example of a very bad example). But an urge is an urge.

[code]

package require Tcl 8.5
package require Tk

namespace eval ::nextlist {namespace export nextlist}

proc ::nextlist::nextlist {wid dir} {
if {![file isdirectory $dir]} {error "non-directory $dir"}
nlist [frame $wid] $dir 0
return $wid
}

proc ::nextlist::nlist {base dir cnt} {
if {![file isdirectory $dir]} {return; # here file-clicketyclicks}
# check for [file readable]? nah
# dear santa, let children be in order
destroy {expand}[lrange [winfo children $base] [expr {2*$cnt}] end]
# exportselection 0 looks good, but selection gets easily out-of-sync
set l [listbox "[set b "$base.nl$cnt"]-list" -yscrollcommand [list "$b-scroll" set] -height 20 -exportselection 0]
pack $l [scrollbar "$b-scroll" -command [list $l yview]] -side left -expand 1 -fill y -anchor w
# I like my directories first and everything dictionarized, love {expand}
# could be done with intermediate list and $l insert end {expand}$lst
foreach item [list {expand}[lsort -dictionary [glob -directory $dir -nocomplain -types {d} -- *]] \
{expand}[lsort -dictionary [glob -directory $dir -nocomplain -types {f} -- *]]] \
{$l insert end "[file tail $item][expr {[file isdirectory $item] ? {/} : {}}]"}
bind $l <Double-1> [list ::nextlist::navigare $dir [incr cnt] %W %x %y]
}

proc ::nextlist::navigare {d c w x y} {
if {[set subdir [$w get [$w index "@$x,$y"]]] eq {}} {return}
nlist [winfo parent $w] [file join $d $subdir] $c
}

[/code]

This can be used as
pack [::nextlist::nextlist .nl ~/Desktop]
and blame ahead. Has huge problems, for example can't descend from start
directory, has that readable thingie in there, trusts ordering of children
and has negative readability (not just lengthy lines!). Otherwise, if we
forget the fact that it can't span itself (creates one very wide widget),
it's good night to me.

Kevin Walzer

unread,
Nov 27, 2005, 9:44:27 AM11/27/05
to
Well, this is certainly an elegant solution to the problem...seems to
work well. Can I suggest posting it at the wiki?

Kaitzschu

unread,
Nov 27, 2005, 12:50:25 PM11/27/05
to
On Sun, 27 Nov 2005, Kevin Walzer wrote:

> Well, this is certainly an elegant solution to the problem...seems to
> work well. Can I suggest posting it at the wiki?

Sure you can. And to make it happen you can post it there :)

0 new messages