# -*- perl -*- # # tkfbox.tcl -- # # Implements the "TK" standard file selection dialog box. This # dialog box is used on the Unix platforms whenever the tk_strictMotif # flag is not set. # # The "TK" standard file selection dialog box is similar to the # file selection dialog box on Win95(TM). The user can navigate # the directories by clicking on the folder icons or by # selecting the "Directory" option menu. The user can select # files by clicking on the file icons or by entering a filename # in the "Filename:" entry. # # Copyright (c) 1994-1996 Sun Microsystems, Inc. # # See the file "license.terms" for information on usage and redistribution # of this file, and for a DISCLAIMER OF ALL WARRANTIES. # # Translated to perl/Tk by Slaven Rezic . # #---------------------------------------------------------------------- # # F I L E D I A L O G # #---------------------------------------------------------------------- # tkFDialog -- # # Implements the TK file selection dialog. This dialog is used when # the tk_strictMotif flag is set to false. This procedure shouldn't # be called directly. Call tk_getOpenFile or tk_getSaveFile instead. # package Tk::FBox; require Tk::Toplevel; use strict; use vars qw($VERSION $updirImage $folderImage $fileImage); $VERSION = sprintf '4.%03d', q$Revision: #18 $ =~ /\D(\d+)\s*$/; use base qw(Tk::Toplevel); Construct Tk::Widget 'FBox'; sub import { if (defined $_[1] and $_[1] eq 'as_default') { local $^W = 0; package Tk; if ($Tk::VERSION < 804) { *FDialog = \&Tk::FBox::FDialog; *MotifFDialog = \&Tk::FBox::FDialog; } else { *tk_getOpenFile = sub { Tk::FBox::FDialog("tk_getOpenFile", @_); }; *tk_getSaveFile = sub { Tk::FBox::FDialog("tk_getSaveFile", @_); }; } } } # Note that -sortcmd is experimental and the interface is likely to change. # Using -sortcmd is really strange :-( # $top->getOpenFile(-sortcmd => sub { package Tk::FBox; uc $b cmp uc $a}); # or, un-perlish, but useable (now activated in code): # $top->getOpenFile(-sortcmd => sub { uc $_[1] cmp uc $_[0]}); sub Populate { my($w, $args) = @_; require Tk::IconList; require File::Basename; require Cwd; $w->SUPER::Populate($args); # f1: the frame with the directory option menu my $f1 = $w->Frame; my $lab = $f1->Label(-text => 'Directory:', -underline => 0); $w->{'dirMenu'} = my $dirMenu = $f1->Optionmenu(-variable => \$w->{'selectPath'}, -textvariable => \$w->{'selectPath'}, -command => ['SetPath', $w]); my $upBtn = $f1->Button; if (!defined $updirImage->{$w->MainWindow}) { $updirImage->{$w->MainWindow} = $w->Bitmap(-data => <configure(-image => $updirImage->{$w->MainWindow}); $dirMenu->configure(-takefocus => 1, -highlightthickness => 2); $upBtn->pack(-side => 'right', -padx => 4, -fill => 'both'); $lab->pack(-side => 'left', -padx => 4, -fill => 'both'); $dirMenu->pack(-expand => 'yes', -fill => 'both', -padx => 4); $w->{'icons'} = my $icons = $w->IconList(-command => ['OkCmd', $w, 'iconlist'], ); $icons->bind('<>' => [$w, 'ListBrowse']); # f2: the frame with the OK button and the "file name" field my $f2 = $w->Frame(-bd => 0); #XXX File name => File names if multiple my $f2_lab = $f2->Label(-text => 'File name:', -anchor => 'e', -width => 14, -underline => 5, -pady => 0); $w->{'ent'} = my $ent = $f2->Entry; # The font to use for the icons. The default Canvas font on Unix # is just deviant. # $w->{'icons'}{'font'} = $ent->cget(-font); $w->{'icons'}->configure(-font => $ent->cget(-font)); # f3: the frame with the cancel button and the file types field my $f3 = $w->Frame(-bd => 0); # The "File of types:" label needs to be grayed-out when # -filetypes are not specified. The label widget does not support # grayed-out text on monochrome displays. Therefore, we have to # use a button widget to emulate a label widget (by setting its # bindtags) $w->{'typeMenuLab'} = my $typeMenuLab = $f3->Button (-text => 'Files of type:', -anchor => 'e', -width => 14, -underline => 9, -bd => $f2_lab->cget(-bd), -highlightthickness => $f2_lab->cget(-highlightthickness), -relief => $f2_lab->cget(-relief), -padx => $f2_lab->cget(-padx), -pady => $f2_lab->cget(-pady), -takefocus => 0, ); $typeMenuLab->bindtags([$typeMenuLab, 'Label', $typeMenuLab->toplevel, 'all']); $w->{'typeMenuBtn'} = my $typeMenuBtn = $f3->Menubutton(-indicatoron => 1, -tearoff => 0); $typeMenuBtn->configure(-takefocus => 1, -highlightthickness => 2, -relief => 'raised', -bd => 2, -anchor => 'w', ); # the okBtn is created after the typeMenu so that the keyboard traversal # is in the right order $w->{'okBtn'} = my $okBtn = $f2->Button (-text => 'OK', -underline => 0, -width => 6, -default => 'active', -pady => 3, ); my $cancelBtn = $f3->Button (-text => 'Cancel', -underline => 0, -width => 6, -default => 'normal', -pady => 3, ); # pack the widgets in f2 and f3 $okBtn->pack(-side => 'right', -padx => 4, -anchor => 'e'); $f2_lab->pack(-side => 'left', -padx => 4); $ent->pack(-expand => 'yes', -fill => 'x', -padx => 2, -pady => 0); $cancelBtn->pack(-side => 'right', -padx => 4, -anchor => 'w'); $typeMenuLab->pack(-side => 'left', -padx => 4); $typeMenuBtn->pack(-expand => 'yes', -fill => 'x', -side => 'right'); # Pack all the frames together. We are done with widget construction. $f1->pack(-side => 'top', -fill => 'x', -pady => 4); $f3->pack(-side => 'bottom', -fill => 'x'); $f2->pack(-side => 'bottom', -fill => 'x'); $icons->pack(-expand => 'yes', -fill => 'both', -padx => 4, -pady => 1); # Set up the event handlers $ent->bind('',[$w,'ActivateEnt']); $upBtn->configure(-command => ['UpDirCmd', $w]); $okBtn->configure(-command => ['OkCmd', $w]); $cancelBtn->configure(-command, ['CancelCmd', $w]); $w->bind('',[$dirMenu,'focus']); $w->bind('',sub { if ($typeMenuBtn->cget(-state) eq 'normal') { $typeMenuBtn->focus; } }); $w->bind('',[$ent,'focus']); $w->bind('',[$cancelBtn,'invoke']); $w->bind('',[$cancelBtn,'invoke']); $w->bind('',['InvokeBtn','Open']); $w->bind('',['InvokeBtn','Save']); $w->protocol('WM_DELETE_WINDOW', ['CancelCmd', $w]); $w->OnDestroy(['CancelCmd', $w]); # Build the focus group for all the entries $w->FG_Create; $w->FG_BindIn($ent, ['EntFocusIn', $w]); $w->FG_BindOut($ent, ['EntFocusOut', $w]); $w->SetPath(_cwd()); $w->ConfigSpecs(-defaultextension => ['PASSIVE', undef, undef, undef], -filetypes => ['PASSIVE', undef, undef, undef], -initialdir => ['PASSIVE', undef, undef, undef], -initialfile => ['PASSIVE', undef, undef, undef], # -sortcmd => ['PASSIVE', undef, undef, sub { lc($a) cmp lc($b) }], -sortcmd => ['PASSIVE', undef, undef, sub { lc($_[0]) cmp lc($_[1]) }], -title => ['PASSIVE', undef, undef, undef], -type => ['PASSIVE', undef, undef, 'open'], -filter => ['PASSIVE', undef, undef, '*'], -force => ['PASSIVE', undef, undef, 0], -multiple => ['PASSIVE', undef, undef, 0], 'DEFAULT' => [$icons], ); # So-far-failed attempt to break reference loops ... $w->_OnDestroy(qw(dirMenu icons typeMenuLab typeMenuBtn okBtn ent updateId)); $w; } # -initialdir fix with ResolveFile sub Show { my $w = shift; $w->configure(@_); # Dialog boxes should be transient with respect to their parent, # so that they will always stay on top of their parent window. However, # some window managers will create the window as withdrawn if the parent # window is withdrawn or iconified. Combined with the grab we put on the # window, this can hang the entire application. Therefore we only make # the dialog transient if the parent is viewable. if (Tk::Exists($w->Parent) && $w->Parent->viewable) { $w->transient($w->Parent); } # set the default directory and selection according to the -initial # settings { my $initialdir = $w->cget(-initialdir); if (defined $initialdir) { my ($flag, $path, $file) = ResolveFile($initialdir, 'junk'); if ($flag eq 'OK' or $flag eq 'FILE') { $w->{'selectPath'} = $path; } else { $w->Error("\"$initialdir\" is not a valid directory"); } } $w->{'selectFile'} = $w->cget(-initialfile); } # Set -multiple to a one or zero value (not other boolean types # like "yes") so we can use it in tests more easily. if ($w->cget('-type') ne 'open') { $w->configure(-multiple => 0); } else { $w->configure(-multiple => !!$w->cget('-multiple')); } $w->{'icons'}->configure(-multiple => $w->cget('-multiple')); # Initialize the file types menu my $typeMenuBtn = $w->{'typeMenuBtn'}; my $typeMenuLab = $w->{'typeMenuLab'}; if (defined $w->cget('-filetypes')) { my(@filetypes) = GetFileTypes($w->cget('-filetypes')); my $typeMenu = $typeMenuBtn->cget(-menu); $typeMenu->delete(0, 'end'); foreach my $ft (@filetypes) { my $title = $ft->[0]; my $filter = join(' ', @{ $ft->[1] }); $typeMenuBtn->command (-label => $title, -command => ['SetFilter', $w, $title, $filter], ); } $w->SetFilter($filetypes[0]->[0], join(' ', @{ $filetypes[0]->[1] })); $typeMenuBtn->configure(-state => 'normal'); $typeMenuLab->configure(-state => 'normal'); } else { #XXX $w->configure(-filter => '*'); $typeMenuBtn->configure(-state => 'disabled', -takefocus => 0); $typeMenuLab->configure(-state => 'disabled'); } $w->UpdateWhenIdle; { my $title = $w->cget(-title); if (!defined $title) { my $type = $w->cget(-type); $title = ($type eq 'dir') ? 'Choose Directory' : ($type eq 'save') ? 'Save As' : 'Open'; } $w->title($title); } # Withdraw the window, then update all the geometry information # so we know how big it wants to be, then center the window in the # display and de-iconify it. $w->withdraw; $w->idletasks; if (0) { #XXX use Tk::Wm::Popup? or Tk::PlaceWindow? my $x = int($w->screenwidth / 2 - $w->reqwidth / 2 - $w->parent->vrootx); my $y = int($w->screenheight / 2 - $w->reqheight / 2 - $w->parent->vrooty); $w->geometry("+$x+$y"); $w->deiconify; } else { $w->Popup; } # Set a grab and claim the focus too. #XXX use Tk::setFocusGrab when it's available my $oldFocus = $w->focusCurrent; my $oldGrab = $w->grabCurrent; my $grabStatus = $oldGrab->grabStatus if ($oldGrab); $w->grab; my $ent = $w->{'ent'}; $ent->focus; $ent->delete(0, 'end'); if (defined $w->{'selectFile'} && $w->{'selectFile'} ne '') { $ent->insert(0, $w->{'selectFile'}); $ent->selectionRange(0,'end'); $ent->icursor('end'); } # 8. Wait for the user to respond, then restore the focus and # return the index of the selected button. Restore the focus # before deleting the window, since otherwise the window manager # may take the focus away so we can't redirect it. Finally, # restore any grab that was in effect. $w->waitVariable(\$w->{'selectFilePath'}); eval { $oldFocus->focus if $oldFocus; }; if (Tk::Exists($w)) { # widget still exists $w->grabRelease; $w->withdraw; } if (Tk::Exists($oldGrab) && $oldGrab->viewable) { if ($grabStatus eq 'global') { $oldGrab->grabGlobal; } else { $oldGrab->grab; } } return $w->{'selectFilePath'}; } # tkFDialog_UpdateWhenIdle -- # # Creates an idle event handler which updates the dialog in idle # time. This is important because loading the directory may take a long # time and we don't want to load the same directory for multiple times # due to multiple concurrent events. # sub UpdateWhenIdle { my $w = shift; if (exists $w->{'updateId'}) { return; } else { $w->{'updateId'} = $w->after('idle', [$w, 'Update']); } } # tkFDialog_Update -- # # Loads the files and directories into the IconList widget. Also # sets up the directory option menu for quick access to parent # directories. # sub Update { my $w = shift; my $dataName = $w->name; # This proc may be called within an idle handler. Make sure that the # window has not been destroyed before this proc is called if (!Tk::Exists($w) || $w->class ne 'FBox') { return; } else { delete $w->{'updateId'}; } unless (defined $folderImage->{$w->MainWindow}) { require Tk::Pixmap; $folderImage->{$w->MainWindow} = $w->Pixmap(-file => Tk->findINC('folder.xpm')); $fileImage->{$w->MainWindow} = $w->Pixmap(-file => Tk->findINC('file.xpm')); } my $folder = $folderImage->{$w->MainWindow}; my $file = $fileImage->{$w->MainWindow}; my $appPWD = _cwd(); if (!ext_chdir($w->{'selectPath'})) { # We cannot change directory to $data(selectPath). $data(selectPath) # should have been checked before tkFDialog_Update is called, so # we normally won't come to here. Anyways, give an error and abort # action. $w->messageBox(-type => 'OK', -message => 'Cannot change to the directory "' . $w->{'selectPath'} . "\".\nPermission denied.", -icon => 'warning', ); ext_chdir($appPWD); return; } # Turn on the busy cursor. BUG?? We haven't disabled X events, though, # so the user may still click and cause havoc ... my $ent = $w->{'ent'}; my $entCursor = $ent->cget(-cursor); my $dlgCursor = $w->cget(-cursor); $ent->configure(-cursor => 'watch'); $w->configure(-cursor => 'watch'); $w->idletasks; my $icons = $w->{'icons'}; $icons->DeleteAll; # Make the dir & file list my $cwd = _cwd(); local *FDIR; if (opendir(FDIR, $cwd)) { my @files; # my $sortcmd = $w->cget(-sortcmd); my $sortcmd = sub { $w->cget(-sortcmd)->($a,$b) }; my $flt = $w->cget(-filter); my $fltcb; if (ref $flt eq 'CODE') { $fltcb = $flt; } else { $flt = _rx_to_glob($flt); } my $type_dir = $w->cget(-type) eq 'dir'; foreach my $f (sort $sortcmd readdir(FDIR)) { next if $f eq '.' or $f eq '..'; next if $type_dir && ! -d "$cwd/$f"; # XXX use File::Spec? if ($fltcb) { next if !$fltcb->($w, $f, $cwd); } else { next if -f $f && $f !~ m!$flt!; } if (-d $f) { $icons->Add($folder, $f); } else { push @files, $f; } } closedir(FDIR); $icons->Add($file, @files); } $icons->Arrange; # Update the Directory: option menu my @list; my $dir = ''; foreach my $subdir (TclFileSplit($w->{'selectPath'})) { $dir = TclFileJoin($dir, $subdir); push @list, $dir; } my $dirMenu = $w->{'dirMenu'}; $dirMenu->configure(-options => \@list); # Restore the PWD to the application's PWD ext_chdir($appPWD); # Restore the Save label if ($w->cget(-type) eq 'save') { $w->{'okBtn'}->configure(-text => 'Save'); } # turn off the busy cursor. $ent->configure(-cursor => $entCursor); $w->configure(-cursor => $dlgCursor); } # tkFDialog_SetPathSilently -- # # Sets data(selectPath) without invoking the trace procedure # sub SetPathSilently { my($w, $path) = @_; $w->{'selectPath'} = $path; } # This proc gets called whenever data(selectPath) is set # sub SetPath { my $w = shift; $w->{'selectPath'} = $_[0] if @_; $w->UpdateWhenIdle; } # This proc gets called whenever data(filter) is set # #XXX here's much more code in the tcl version ... check it out sub SetFilter { my($w, $title, $filter) = @_; $w->configure(-filter => $filter); $w->{'typeMenuBtn'}->configure(-text => $title, -indicatoron => 1); $w->{'icons'}->Subwidget('sbar')->set(0.0, 0.0); $w->UpdateWhenIdle; } # tkFDialogResolveFile -- # # Interpret the user's text input in a file selection dialog. # Performs: # # (1) ~ substitution # (2) resolve all instances of . and .. # (3) check for non-existent files/directories # (4) check for chdir permissions # # Arguments: # context: the current directory you are in # text: the text entered by the user # defaultext: the default extension to add to files with no extension # # Return value: # [list $flag $directory $file] # # flag = OK : valid input # = PATTERN : valid directory/pattern # = PATH : the directory does not exist # = FILE : the directory exists but the file doesn't # exist # = CHDIR : Cannot change to the directory # = ERROR : Invalid entry # # directory : valid only if flag = OK or PATTERN or FILE # file : valid only if flag = OK or PATTERN # # directory may not be the same as context, because text may contain # a subdirectory name # sub ResolveFile { my($context, $text, $defaultext) = @_; my $appPWD = _cwd(); my $path = JoinFile($context, $text); # If the file has no extension, append the default. Be careful not # to do this for directories, otherwise typing a dirname in the box # will give back "dirname.extension" instead of trying to change dir. if (!-d $path && $path !~ /\..+$/ && defined $defaultext) { $path = "$path$defaultext"; } # Cannot just test for existance here as non-existing files are # not an error for getSaveFile type dialogs. # return ('ERROR', $path, "") if (!-e $path); my($directory, $file, $flag); if (-e $path) { if (-d $path) { if (!ext_chdir($path)) { return ('CHDIR', $path, ''); } $directory = _cwd(); $file = ''; $flag = 'OK'; ext_chdir($appPWD); } else { my $dirname = File::Basename::dirname($path); if (!ext_chdir($dirname)) { return ('CHDIR', $dirname, ''); } $directory = _cwd(); $file = File::Basename::basename($path); $flag = 'OK'; ext_chdir($appPWD); } } else { my $dirname = File::Basename::dirname($path); if (-e $dirname) { if (!ext_chdir($dirname)) { return ('CHDIR', $dirname, ''); } $directory = _cwd(); $file = File::Basename::basename($path); if ($file =~ /[*?]/) { $flag = 'PATTERN'; } else { $flag = 'FILE'; } ext_chdir($appPWD); } else { $directory = $dirname; $file = File::Basename::basename($path); $flag = 'PATH'; } } return ($flag,$directory,$file); } # Gets called when the entry box gets keyboard focus. We clear the selection # from the icon list . This way the user can be certain that the input in the # entry box is the selection. # sub EntFocusIn { my $w = shift; my $ent = $w->{'ent'}; if ($ent->get ne '') { $ent->selectionRange(0, 'end'); $ent->icursor('end'); } else { $ent->selectionClear; } #XXX is this missing in the tcl version, too??? $w->{'icons'}->Selection('clear'); my $okBtn = $w->{'okBtn'}; if ($w->cget(-type) ne 'save') { $okBtn->configure(-text => 'Open'); } else { $okBtn->configure(-text => 'Save'); } } sub EntFocusOut { my $w = shift; $w->{'ent'}->selectionClear; } # Gets called when user presses Return in the "File name" entry. # sub ActivateEnt { my $w = shift; my $ent = $w->{'ent'}; my $text = $ent->get; if ($w->cget(-multiple)) { # For the multiple case we have to be careful to get the file # names as a true list, watching out for a single file with a # space in the name. Thus we query the IconList directly. $w->{'selectFile'} = []; for my $item ($w->{'icons'}->Curselection) { $w->VerifyFileName($w->{'icons'}->Get($item)); } } else { $w->VerifyFileName($text); } } # Verification procedure # sub VerifyFileName { my($w, $text) = @_; #XXX leave this here? # $text =~ s/^\s+//; # $text =~ s/\s+$//; my($flag, $path, $file) = ResolveFile($w->{'selectPath'}, $text, $w->cget(-defaultextension)); my $ent = $w->{'ent'}; if ($flag eq 'OK') { if ($file eq '') { # user has entered an existing (sub)directory $w->SetPath($path); $ent->delete(0, 'end'); } else { $w->SetPathSilently($path); if ($w->cget(-multiple)) { push @{ $w->{'selectFile'} }, $file; } else { $w->{'selectFile'} = $file; } $w->Done; } } elsif ($flag eq 'PATTERN') { $w->SetPath($path); $w->configure(-filter => $file); } elsif ($flag eq 'FILE') { if ($w->cget(-type) eq 'open') { $w->messageBox(-icon => 'warning', -type => 'OK', -message => 'File "' . TclFileJoin($path, $file) . '" does not exist.'); $ent->selectionRange(0, 'end'); $ent->icursor('end'); } elsif ($w->cget(-type) eq 'save') { $w->SetPathSilently($path); if ($w->cget(-multiple)) { push @{ $w->{'selectFile'} }, $file; } else { $w->{'selectFile'} = $file; } $w->Done; } } elsif ($flag eq 'PATH') { $w->messageBox(-icon => 'warning', -type => 'OK', -message => "Directory \'$path\' does not exist."); $ent->selectionRange(0, 'end'); $ent->icursor('end'); } elsif ($flag eq 'CHDIR') { $w->messageBox(-type => 'OK', -message => "Cannot change to the directory \"$path\".\nPermission denied.", -icon => 'warning'); $ent->selectionRange(0, 'end'); $ent->icursor('end'); } elsif ($flag eq 'ERROR') { $w->messageBox(-type => 'OK', -message => "Invalid file name \"$path\".", -icon => 'warning'); $ent->selectionRange(0, 'end'); $ent->icursor('end'); } } # Gets called when user presses the Alt-s or Alt-o keys. # sub InvokeBtn { my($w, $key) = @_; my $okBtn = $w->{'okBtn'}; $okBtn->invoke if ($okBtn->cget(-text) eq $key); } # Gets called when user presses the "parent directory" button # sub UpDirCmd { my $w = shift; $w->SetPath(File::Basename::dirname($w->{'selectPath'})) unless ($w->{'selectPath'} eq '/'); } # Join a file name to a path name. The "file join" command will break # if the filename begins with ~ sub JoinFile { my($path, $file) = @_; if ($file =~ /^~/ && -e "$path/$file") { TclFileJoin($path, "./$file"); } else { TclFileJoin($path, $file); } } # XXX replace with File::Spec when perl/Tk depends on 5.005 sub TclFileJoin { my $path = ''; foreach (@_) { if (m|^/|) { $path = $_; } elsif (m|^[a-z]:/|i) { # DOS-ish $path = $_; } elsif ($_ eq '~') { $path = _get_homedir(); } elsif (m|^~/(.*)|) { $path = _get_homedir() . "/" . $1; } elsif (m|^~([^/]+)(.*)|) { my($user, $p) = ($1, $2); my $dir = _get_homedir($user); if (!defined $dir) { $path = "~$user$p"; } else { $path = $dir . $p; } } elsif ($path eq '/' or $path eq '') { $path .= $_; } else { $path .= "/$_"; } } $path; } sub TclFileSplit { my $path = shift; my @comp; $path =~ s|/+|/|g; # strip multiple slashes if ($path =~ m|^/|) { push @comp, '/'; $path = substr($path, 1); } push @comp, split /\//, $path; @comp; } # Gets called when user presses the "OK" button # sub OkCmd { my $w = shift; my $from = shift || "button"; my $filenames = []; for my $item ($w->{'icons'}->Curselection) { push @$filenames, $w->{'icons'}->Get($item); } my $filename = $filenames->[0]; if ($w->cget('-type') eq 'dir' && $from ne "iconlist") { my $file = $filename eq '' ? $w->{'selectPath'} : JoinFile($w->{'selectPath'}, $filename); $w->Done($file); } elsif ((@$filenames && !$w->cget('-multiple')) || ($w->cget('-multiple') && @$filenames == 1)) { my $file = JoinFile($w->{'selectPath'}, $filename); if (-d $file) { $w->ListInvoke($filename); return; } } $w->ActivateEnt; } # Gets called when user presses the "Cancel" button # sub CancelCmd { my $w = shift; undef $w->{'selectFilePath'}; } # Gets called when user browses the IconList widget (dragging mouse, arrow # keys, etc) # sub ListBrowse { my($w) = @_; my $text = []; for my $item ($w->{'icons'}->Curselection) { push @$text, $w->{'icons'}->Get($item); } return if @$text == 0; my $isDir; if (@$text > 1) { my $newtext = []; for my $file (@$text) { my $fullfile = JoinFile($w->{'selectPath'}, $file); if (!-d $fullfile) { push @$newtext, $file; } } $text = $newtext; $isDir = 0; } else { my $file = JoinFile($w->{'selectPath'}, $text->[0]); $isDir = -d $file; } my $ent = $w->{'ent'}; my $okBtn = $w->{'okBtn'}; if (!$isDir) { $ent->delete(qw(0 end)); $ent->insert(0, "@$text"); # XXX quote! if ($w->cget('-type') ne 'save') { $okBtn->configure(-text => 'Open'); } else { $okBtn->configure(-text => 'Save'); } } else { $okBtn->configure(-text => 'Open'); } } # Gets called when user invokes the IconList widget (double-click, # Return key, etc) # sub ListInvoke { my($w, @filenames) = @_; return if !@filenames; my $file = JoinFile($w->{'selectPath'}, $filenames[0]); if (-d $file) { my $appPWD = _cwd(); if (!ext_chdir($file)) { $w->messageBox(-type => 'OK', -message => "Cannot change to the directory \"$file\".\nPermission denied.", -icon => 'warning'); } else { ext_chdir($appPWD); $w->SetPath($file); } } else { if ($w->cget('-multiple')) { $w->{'selectFile'} = [@filenames]; } else { $w->{'selectFile'} = $file; } $w->Done; } } # tkFDialog_Done -- # # Gets called when user has input a valid filename. Pops up a # dialog box to confirm selection when necessary. Sets the # tkPriv(selectFilePath) variable, which will break the "tkwait" # loop in tkFDialog and return the selected filename to the # script that calls tk_getOpenFile or tk_getSaveFile # sub Done { my $w = shift; my $selectFilePath = (@_) ? shift : ''; if ($selectFilePath eq '') { if ($w->cget('-multiple')) { $selectFilePath = []; for my $f (@{ $w->{'selectFile'} }) { push @$selectFilePath, JoinFile($w->{'selectPath'}, $f); } } else { $selectFilePath = JoinFile($w->{'selectPath'}, $w->{'selectFile'}); } if ($w->cget(-type) eq 'save' and -e $selectFilePath and !$w->cget(-force)) { my $reply = $w->messageBox (-icon => 'warning', -type => 'YesNo', -message => "File \"$selectFilePath\" already exists.\nDo you want to overwrite it?"); return unless (lc($reply) eq 'yes'); } } $w->{'selectFilePath'} = ($selectFilePath ne '' ? $selectFilePath : undef); } sub FDialog { my $cmd = shift; if ($cmd =~ /Save/) { push @_, -type => 'save'; } elsif ($cmd =~ /Directory/) { push @_, -type => 'dir'; } Tk::DialogWrapper('FBox', $cmd, @_); } # tkFDGetFileTypes -- # # Process the string given by the -filetypes option of the file # dialogs. Similar to the C function TkGetFileFilters() on the Mac # and Windows platform. # sub GetFileTypes { my $in = shift; my %fileTypes; foreach my $t (@$in) { if (@$t < 2 || @$t > 3) { require Carp; Carp::croak("bad file type \"$t\", should be \"typeName [extension ?extensions ...?] ?[macType ?macTypes ...?]?\""); } push @{ $fileTypes{$t->[0]} }, (ref $t->[1] eq 'ARRAY' ? @{ $t->[1] } : $t->[1]); } my @types; my %hasDoneType; my %hasGotExt; foreach my $t (@$in) { my $label = $t->[0]; my @exts; next if (exists $hasDoneType{$label}); my $name = "$label ("; my $sep = ''; foreach my $ext (@{ $fileTypes{$label} }) { next if ($ext eq ''); $ext =~ s/^\./*./; if (!exists $hasGotExt{$label}->{$ext}) { $name .= "$sep$ext"; push @exts, $ext; $hasGotExt{$label}->{$ext}++; } $sep = ','; } $name .= ')'; push @types, [$name, \@exts]; $hasDoneType{$label}++; } return @types; } # ext_chdir -- # # Change directory with tilde substitution # sub ext_chdir { my $dir = shift; if ($dir eq '~') { chdir _get_homedir(); } elsif ($dir =~ m|^~/(.*)|) { chdir _get_homedir() . "/" . $1; } elsif ($dir =~ m|^~([^/]+(.*))|) { chdir _get_homedir($1) . $2; } else { chdir $dir; } } # _get_homedir -- # # Get home directory of the current user # sub _get_homedir { my($user) = @_; if (!defined $user) { eval { local $SIG{__DIE__}; (getpwuid($<))[7]; } || $ENV{HOME} || undef; # chdir undef changes to home directory, too } else { eval { local $SIG{__DIE__}; (getpwnam($user))[7]; }; } } sub _cwd { #Cwd::cwd(); Cwd::fastcwd(); # this is taint-safe } sub _untaint { my $s = shift; $s =~ /^(.*)$/; $1; } sub _rx_to_glob { my $arg = shift; $arg = join('|', split(' ', $arg)); $arg =~ s!([\.\+])!\\$1!g; $arg =~ s!\*!.*!g; $arg = "^" . $arg . "\$"; if ($] >= 5.005) { $arg = qr/$arg/; } $arg; } 1;