1
#===============================================================================
3
# Copyright (C) 2000-2001 Kristi Thompson <kristi@kristi.ca>
4
# Copyright (C) 2002-2005 Michael J. Carman <mjcarman@mchsi.com>
5
# Last Modified: 8/19/2005 9:42AM
6
#===============================================================================
7
# This is free software under the terms of the Perl Artistic License.
8
#===============================================================================
9
BEGIN { require 5.004 }
11
package Tk::DirSelect;
16
require Tk::BrowseEntry;
22
use base 'Tk::Toplevel';
23
Construct Tk::Widget 'DirSelect';
25
use vars qw'$VERSION';
31
#-------------------------------------------------------------------------------
32
# Subroutine : ClassInit()
33
# Purpose : Class initialzation.
35
#-------------------------------------------------------------------------------
37
my ($class, $mw) = @_;
38
$class->SUPER::ClassInit($mw);
40
$isWin32 = $^O eq 'MSWin32';
42
# Get system colors from a Text widget for use in DirTree
44
foreach my $x (qw'-background -selectbackground -selectforeground') {
45
$colors{$x} = $t->cget($x);
51
#-------------------------------------------------------------------------------
52
# Subroutine : Populate()
53
# Purpose : Create the DirSelect widget
55
#-------------------------------------------------------------------------------
58
my $directory = delete $args->{-dir} || cwd();
59
my $title = delete $args->{-title} || 'Select Directory';
62
$w->SUPER::Populate($args);
63
$w->ConfigSpecs(-title => ['METHOD', 'title', 'Title', $title]);
64
$w->bind('<Escape>', sub { $w->{dir} = undef });
67
drive => $w->Frame->pack(-anchor => 'n', -fill => 'x'),
68
button => $w->Frame->pack(-side => 'bottom', -anchor => 's', -fill => 'x', -ipady => 6),
69
tree => $w->Frame->pack(-fill => 'both', -expand => 1),
72
$w->{tree} = $f{tree}->Scrolled('DirTree',
73
-scrollbars => 'osoe',
74
-selectmode => 'single',
80
)->pack(-fill => 'both', -expand => 1);
82
$w->{tree}->configure(-command => sub { $w->{tree}->opencmd($_[0]) });
83
$w->{tree}->configure(-browsecmd => sub { $w->{tree}->anchorClear });
88
-command => sub { $w->{dir} = $w->{tree}->selectionGet() },
89
)->pack(-side => 'left', -expand => 1);
94
-command => sub { $w->{dir} = undef },
95
)->pack(-side => 'left', -expand => 1);
98
$f{drive}->Label(-text => 'Drive:')->pack(-side => 'left');
99
$w->{drive} = $f{drive}->BrowseEntry(
100
-variable => \$w->{selected_drive},
101
-browsecmd => [\&_browse, $w->{tree}],
102
-state => 'readonly',
103
)->pack(-side => 'left', -fill => 'x', -expand => 1);
105
if ($Tk::VERSION >= 804) {
106
# widget is readonly, but shouldn't appear disabled
107
for my $e ($w->{drive}->Subwidget('entry')->Subwidget('entry')) {
108
$e->configure(-disabledforeground => $colors{-foreground});
109
$e->configure(-disabledbackground => $colors{-background});
117
# right-click context menu
121
[qw/command ~New/, -command => [\&_mkdir , $w]],
122
[qw/command ~Rename/, -command => [\&_rename, $w]],
123
[qw/command ~Delete/, -command => [\&_rmdir, $w]],
126
$menu->bind('<FocusOut>' => sub {$menu->unpost});
127
$w->{tree}->bind('<Button-3>' => [\&_context, $menu, Ev('X'), Ev('Y')]);
129
# popup overlay for renaming directories
130
$w->{renameval} = undef;
131
$w->{popup} = $w->Toplevel();
132
$w->{rename} = $w->{popup}->Entry(
135
)->pack(-fill => 'x', -expand => 1);
136
$w->{popup}->overrideredirect(1);
137
$w->{popup}->withdraw;
138
$w->{rename}->bind('<Escape>', sub {$w->{renameval} = undef});
139
$w->{rename}->bind('<FocusOut>', sub {$w->{renameval} = undef});
140
$w->{rename}->bind('<KeyPress-Return>', sub {$w->{renameval} = $w->{rename}->get});
146
#-------------------------------------------------------------------------------
147
# Subroutine : Show()
148
# Purpose : Display the DirSelect widget.
150
#-------------------------------------------------------------------------------
155
my $focus = $w->focusSave;
156
my $grab = $w->grabSave;
158
$dir = $cwd unless defined $dir && -d $dir;
162
# populate the drive list
163
my @drives = _get_volume_info();
164
$w->{drive}->delete(0, 'end');
165
my $startdrive = _drive($dir);
167
foreach my $d (@drives) {
168
$w->{drive}->insert('end', $d);
169
if ($startdrive eq _drive($d)) {
170
$w->{selected_drive} = $d;
175
# show initial directory
176
_showdir($w->{tree}, $dir);
178
$w->Popup(@_); # show widget
179
$w->focus; # seize focus
180
$w->grab; # seize grab
181
$w->waitVariable(\$w->{dir}); # wait for user selection (or cancel)
182
$w->grabRelease; # release grab
183
$w->withdraw; # run and hide
184
$focus->(); # restore prior focus
185
$grab->(); # restore prior grab
186
chdir($cwd) # restore working directory
187
or warn "Could not chdir() back to '$cwd' [$!]\n";
189
# HList SelectionGet() behavior changed around Tk 804.025
190
if (ref $w->{dir} eq 'ARRAY') {
191
$w->{dir} = $w->{dir}[0];
196
$w->{dir} .= '/' if ($isWin32 && $w->{dir} =~ /:$/);
203
#-------------------------------------------------------------------------------
204
# Subroutine : _browse()
205
# Purpose : Browse to a mounted filesystem (Win32)
207
#-------------------------------------------------------------------------------
209
my ($w, undef, $d) = @_;
210
$d = _drive($d) . '/';
214
# Workaround: Under Win* versions of Perl/Tk, scrollbars have a tendancy
215
# to show up but be disabled.
216
$w->yview(scroll => 1, 'units');
218
$w->yview(scroll => -1, 'units');
222
#-------------------------------------------------------------------------------
223
# Subroutine : _showdir()
224
# Purpose : Show the requested directory
226
#-------------------------------------------------------------------------------
235
#-------------------------------------------------------------------------------
236
# Subroutine : _get_volume_info()
237
# Purpose : Get volume information (Win32)
239
#-------------------------------------------------------------------------------
240
sub _get_volume_info {
241
require Win32API::File;
246
'Removable disk drive',
254
foreach my $ld (Win32API::File::getLogicalDrives()) {
255
my $drive = _drive($ld);
256
my $type = $drivetype[Win32API::File::GetDriveType($drive)];
259
Win32API::File::GetVolumeInformation(
260
$drive, $label, [], [], [], [], [], []);
262
push @drives, "$drive [$label] $type";
269
#-------------------------------------------------------------------------------
270
# Subroutine : _drive()
271
# Purpose : Get the drive letter (Win32)
273
#-------------------------------------------------------------------------------
280
#-------------------------------------------------------------------------------
282
# Purpose : Display the context menu
284
#-------------------------------------------------------------------------------
286
my ($w, $m, $x, $y) = @_;
287
my $wy = $y - $w->rooty;
288
$w->selectionClear();
289
$w->selectionSet($w->nearest($wy));
295
#-------------------------------------------------------------------------------
297
# Purpose : Create a new directory under the current selection
299
#-------------------------------------------------------------------------------
303
my ($sel) = $dt->selectionGet();
305
my $cwd = Cwd::cwd();
307
my $base = 'NewDirectory';
311
while (-d $name && $i < 1000) {
312
$name = $base . $i++;
318
$dt->selectionClear();
319
$dt->selectionSet($sel . '/' . $name);
324
-title => 'Unable to create directory',
325
-message => "The directory '$name' could not be created.\n$!",
335
warn "Unable to chdir() for mkdir() [$!]\n";
340
#-------------------------------------------------------------------------------
342
# Purpose : Delete the selected directory
344
#-------------------------------------------------------------------------------
348
my ($sel) = $dt->selectionGet();
350
my @path = File::Spec->splitdir($sel);
352
my $pdir = File::Spec->catdir(@path);
354
my $cwd = Cwd::cwd();
357
_showdir($dt, $pdir);
361
-title => 'Unable to delete directory',
362
-message => "The directory '$dir' could not be deleted.\n$!",
370
warn "Unable to chdir() for rmdir() [$!]\n";
374
#-------------------------------------------------------------------------------
376
# Purpose : Rename the selected directory
378
#-------------------------------------------------------------------------------
382
my $popup = $w->{popup};
383
my $entry = $w->{rename};
384
my ($sel) = $dt->selectionGet();
385
my ($x, $y, $x1, $y1) = $dt->infoBbox($sel);
387
my @path = File::Spec->splitdir($sel);
389
my $pdir = File::Spec->catdir(@path);
391
$entry->delete(0, 'end');
392
$entry->insert(0, $dir);
393
$entry->selectionRange(0, 'end');
396
my $font = ($entry->configure(-font))[4];
397
my $text = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ';
398
my $width = $entry->fontMeasure($font, $text) / length($text);
399
$entry->configure(-width => ($x1 - $x) / $width);
401
$popup->Post($dt->rootx + $x, $dt->rooty + $y);
402
$popup->waitVariable(\$w->{renameval});
405
if (defined $w->{renameval} && $w->{renameval} ne $dir) {
406
my $cwd = Cwd::cwd();
409
unless (rename($dir, $w->{renameval})) {
411
-title => 'Unable to rename directory',
412
-message => "The directory '$dir' could not be renamed.\n$!",
418
_showdir($dt, $pdir); # rebrowse to update the display
421
warn "Unable to chdir() for rename() [$!]\n";
434
Tk::DirSelect - Cross-platform directory selection widget.
439
my $ds = $mw->DirSelect();
440
my $dir = $ds->Show();
444
This module provides a cross-platform directory selection widget. For
445
systems running Microsoft Windows, this includes selection of local and
446
mapped network drives. A context menu (right-click or E<lt>Button3E<gt>)
447
allows the creation, renaming, and deletion of directories while
450
Note: Perl/Tk 804 added the C<chooseDirectory> method which uses native
451
system dialogs where available. (i.e. Windows) If you want a native feel
452
for your program, you probably want to use that method instead --
453
possibly using this module as a fallback for systems with older versions
458
=head2 C<DirSelect([-title =E<gt> 'title'], [options])>
460
Constructs a new DirSelect widget as a child of the invoking object
461
(usually a MainWindow).
463
The title for the widget can be set by specifying C<-title =E<gt>
464
'Title'>. Any other options provided will be passed through to the
465
DirTree widget that displays directories, so be sure they're appropriate
468
=head2 C<Show([directory], [options])>
470
Displays the DirSelect widget and returns the user selected directory or
471
C<undef> if the operation is canceled.
473
All arguments are optional. The first argument (if defined) is the
474
initial directory to display. The default is to display the current
475
working directory. Any additional options are passed through to the
476
Popup() method. This means that you can do something like
478
$ds->Show(undef, -popover => $mw);
480
to center the dialog over your application.
490
=item * Win32API::File (under Microsoft Windows only)
496
Original author Kristi Thompson <kristi@kristi.ca>
498
Current maintainer Michael J. Carman <mjcarman@mchsi.com>
500
This is free software under the terms of the Perl Artistic License.