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 }
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:
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!
> 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 *]
> 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.
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>, Kevin Walzer <s...@wordtech-software.com> writes:
>> 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.
>> 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.
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.
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.
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.
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 :)