1
#===============================================================================
3
# Copyright (C) 2000-2001 Kristi Thompson <kristi@kristi.ca>
4
# Copyright (C) 2002-2005,2010 Michael Carman <mjcarman@mchsi.com>
5
# Last Modified: 2/16/2010
6
#===============================================================================
7
BEGIN { require 5.004 }
14
require Tk::BrowseEntry;
20
use base 'Tk::Toplevel';
21
Construct Tk::Widget 'DirSelect';
23
use vars qw'$VERSION';
29
#-------------------------------------------------------------------------------
30
# Subroutine : ClassInit()
31
# Purpose : Class initialzation.
33
#-------------------------------------------------------------------------------
35
my ($class, $mw) = @_;
36
$class->SUPER::ClassInit($mw);
38
$isWin32 = $^O eq 'MSWin32';
40
# Get system colors from a Text widget for use in DirTree
42
foreach my $x (qw'-background -selectbackground -selectforeground') {
43
$colors{$x} = $t->cget($x);
49
#-------------------------------------------------------------------------------
50
# Subroutine : Populate()
51
# Purpose : Create the DirSelect widget
53
#-------------------------------------------------------------------------------
56
my $directory = delete $args->{-dir} || cwd();
57
my $title = delete $args->{-title} || 'Select Directory';
60
$w->SUPER::Populate($args);
61
$w->ConfigSpecs(-title => ['METHOD', 'title', 'Title', $title]);
62
$w->bind('<Escape>', sub { $w->{dir} = undef });
65
drive => $w->Frame->pack(-anchor => 'n', -fill => 'x'),
66
button => $w->Frame->pack(-side => 'bottom', -anchor => 's', -fill => 'x', -ipady => 6),
67
tree => $w->Frame->pack(-fill => 'both', -expand => 1),
70
$w->{tree} = $f{tree}->Scrolled('DirTree',
71
-scrollbars => 'osoe',
72
-selectmode => 'single',
78
)->pack(-fill => 'both', -expand => 1);
80
$w->{tree}->configure(-command => sub { $w->{tree}->opencmd($_[0]) });
81
$w->{tree}->configure(-browsecmd => sub { $w->{tree}->anchorClear });
86
-command => sub { $w->{dir} = $w->{tree}->selectionGet() },
87
)->pack(-side => 'left', -expand => 1);
92
-command => sub { $w->{dir} = undef },
93
)->pack(-side => 'left', -expand => 1);
96
$f{drive}->Label(-text => 'Drive:')->pack(-side => 'left');
97
$w->{drive} = $f{drive}->BrowseEntry(
98
-variable => \$w->{selected_drive},
99
-browsecmd => [\&_browse, $w->{tree}],
100
-state => 'readonly',
101
)->pack(-side => 'left', -fill => 'x', -expand => 1);
103
if ($Tk::VERSION >= 804) {
104
# widget is readonly, but shouldn't appear disabled
105
for my $e ($w->{drive}->Subwidget('entry')->Subwidget('entry')) {
106
$e->configure(-disabledforeground => $colors{-foreground});
107
$e->configure(-disabledbackground => $colors{-background});
115
# right-click context menu
119
[qw/command ~New/, -command => [\&_mkdir , $w]],
120
[qw/command ~Rename/, -command => [\&_rename, $w]],
121
[qw/command ~Delete/, -command => [\&_rmdir, $w]],
124
$menu->bind('<FocusOut>' => sub {$menu->unpost});
125
$w->{tree}->bind('<Button-3>' => [\&_context, $menu, Ev('X'), Ev('Y')]);
127
# popup overlay for renaming directories
128
$w->{renameval} = undef;
129
$w->{popup} = $w->Toplevel();
130
$w->{rename} = $w->{popup}->Entry(
133
)->pack(-fill => 'x', -expand => 1);
134
$w->{popup}->overrideredirect(1);
135
$w->{popup}->withdraw;
136
$w->{rename}->bind('<Escape>', sub {$w->{renameval} = undef});
137
$w->{rename}->bind('<FocusOut>', sub {$w->{renameval} = undef});
138
$w->{rename}->bind('<KeyPress-Return>', sub {$w->{renameval} = $w->{rename}->get});
144
#-------------------------------------------------------------------------------
145
# Subroutine : Show()
146
# Purpose : Display the DirSelect widget.
148
#-------------------------------------------------------------------------------
153
my $focus = $w->focusSave;
154
my $grab = $w->grabSave;
156
$dir = $cwd unless defined $dir && -d $dir;
160
# populate the drive list
161
my @drives = _get_volume_info();
162
$w->{drive}->delete(0, 'end');
163
my $startdrive = _drive($dir);
165
foreach my $d (@drives) {
166
$w->{drive}->insert('end', $d);
167
if ($startdrive eq _drive($d)) {
168
$w->{selected_drive} = $d;
173
# show initial directory
174
_showdir($w->{tree}, $dir);
176
$w->Popup(@_); # show widget
177
$w->focus; # seize focus
178
$w->grab; # seize grab
179
$w->waitVariable(\$w->{dir}); # wait for user selection (or cancel)
180
$w->grabRelease; # release grab
181
$w->withdraw; # run and hide
182
$focus->(); # restore prior focus
183
$grab->(); # restore prior grab
184
chdir($cwd) # restore working directory
185
or warn "Could not chdir() back to '$cwd' [$!]\n";
187
# HList SelectionGet() behavior changed around Tk 804.025
188
if (ref $w->{dir} eq 'ARRAY') {
189
$w->{dir} = $w->{dir}[0];
194
$w->{dir} .= '/' if ($isWin32 && $w->{dir} =~ /:$/);
201
#-------------------------------------------------------------------------------
202
# Subroutine : _browse()
203
# Purpose : Browse to a mounted filesystem (Win32)
205
#-------------------------------------------------------------------------------
207
my ($w, undef, $d) = @_;
208
$d = _drive($d) . '/';
212
# Workaround: Under Win* versions of Perl/Tk, scrollbars have a tendancy
213
# to show up but be disabled.
214
$w->yview(scroll => 1, 'units');
216
$w->yview(scroll => -1, 'units');
220
#-------------------------------------------------------------------------------
221
# Subroutine : _showdir()
222
# Purpose : Show the requested directory
224
#-------------------------------------------------------------------------------
233
#-------------------------------------------------------------------------------
234
# Subroutine : _get_volume_info()
235
# Purpose : Get volume information (Win32)
237
#-------------------------------------------------------------------------------
238
sub _get_volume_info {
239
require Win32API::File;
244
'Removable disk drive',
252
foreach my $ld (Win32API::File::getLogicalDrives()) {
253
my $drive = _drive($ld);
254
my $type = $drivetype[Win32API::File::GetDriveType($drive)];
257
Win32API::File::GetVolumeInformation(
258
$drive, $label, [], [], [], [], [], []);
260
push @drives, "$drive [$label] $type";
267
#-------------------------------------------------------------------------------
268
# Subroutine : _drive()
269
# Purpose : Get the drive letter (Win32)
271
#-------------------------------------------------------------------------------
278
#-------------------------------------------------------------------------------
280
# Purpose : Display the context menu
282
#-------------------------------------------------------------------------------
284
my ($w, $m, $x, $y) = @_;
285
my $wy = $y - $w->rooty;
286
$w->selectionClear();
287
$w->selectionSet($w->nearest($wy));
293
#-------------------------------------------------------------------------------
295
# Purpose : Create a new directory under the current selection
297
#-------------------------------------------------------------------------------
301
my ($sel) = $dt->selectionGet();
303
my $cwd = Cwd::cwd();
305
my $base = 'NewDirectory';
309
while (-d $name && $i < 1000) {
310
$name = $base . $i++;
316
$dt->selectionClear();
317
$dt->selectionSet($sel . '/' . $name);
322
-title => 'Unable to create directory',
323
-message => "The directory '$name' could not be created.\n$!",
333
warn "Unable to chdir() for mkdir() [$!]\n";
338
#-------------------------------------------------------------------------------
340
# Purpose : Delete the selected directory
342
#-------------------------------------------------------------------------------
346
my ($sel) = $dt->selectionGet();
348
my @path = File::Spec->splitdir($sel);
350
my $pdir = File::Spec->catdir(@path);
352
my $cwd = Cwd::cwd();
355
_showdir($dt, $pdir);
359
-title => 'Unable to delete directory',
360
-message => "The directory '$dir' could not be deleted.\n$!",
368
warn "Unable to chdir() for rmdir() [$!]\n";
372
#-------------------------------------------------------------------------------
374
# Purpose : Rename the selected directory
376
#-------------------------------------------------------------------------------
380
my $popup = $w->{popup};
381
my $entry = $w->{rename};
382
my ($sel) = $dt->selectionGet();
383
my ($x, $y, $x1, $y1) = $dt->infoBbox($sel);
385
my @path = File::Spec->splitdir($sel);
387
my $pdir = File::Spec->catdir(@path);
389
$entry->delete(0, 'end');
390
$entry->insert(0, $dir);
391
$entry->selectionRange(0, 'end');
394
my $font = ($entry->configure(-font))[4];
395
my $text = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ';
396
my $width = $entry->fontMeasure($font, $text) / length($text);
397
$entry->configure(-width => ($x1 - $x) / $width);
399
$popup->Post($dt->rootx + $x, $dt->rooty + $y);
400
$popup->waitVariable(\$w->{renameval});
403
if (defined $w->{renameval} && $w->{renameval} ne $dir) {
404
my $cwd = Cwd::cwd();
407
unless (rename($dir, $w->{renameval})) {
409
-title => 'Unable to rename directory',
410
-message => "The directory '$dir' could not be renamed.\n$!",
416
_showdir($dt, $pdir); # rebrowse to update the display
419
warn "Unable to chdir() for rename() [$!]\n";
432
Tk::DirSelect - Cross-platform directory selection widget.
437
my $ds = $mw->DirSelect();
438
my $dir = $ds->Show();
442
This module provides a cross-platform directory selection widget. For
443
systems running Microsoft Windows, this includes selection of local and
444
mapped network drives. A context menu (right-click or E<lt>Button3E<gt>)
445
allows the creation, renaming, and deletion of directories while
448
Note: Perl/Tk 804 added the C<chooseDirectory> method which uses native
449
system dialogs where available. (i.e. Windows) If you want a native feel
450
for your program, you probably want to use that method instead --
451
possibly using this module as a fallback for systems with older versions
456
=head2 C<DirSelect([-title =E<gt> 'title'], [options])>
458
Constructs a new DirSelect widget as a child of the invoking object
459
(usually a MainWindow).
461
The title for the widget can be set by specifying C<-title =E<gt>
462
'Title'>. Any other options provided will be passed through to the
463
DirTree widget that displays directories, so be sure they're appropriate
466
=head2 C<Show([directory], [options])>
468
Displays the DirSelect widget and returns the user selected directory or
469
C<undef> if the operation is canceled.
471
All arguments are optional. The first argument (if defined) is the
472
initial directory to display. The default is to display the current
473
working directory. Any additional options are passed through to the
474
Popup() method. This means that you can do something like
476
$ds->Show(undef, -popover => $mw);
478
to center the dialog over your application.
488
=item * Win32API::File (under Microsoft Windows only)
492
=head1 LICENSE AND COPYRIGHT
494
Copyright 2000-2001 Kristi Thompson <kristi@kristi.ca>
495
Copyright 2002-2005,2010 Michael Carman <mjcarman@cpan.org>
497
This program is free software; you can redistribute it and/or modify it
498
under the terms of either: the GNU General Public License as published
499
by the Free Software Foundation; or the Artistic License.
501
See http://dev.perl.org/licenses/ for more information.