PageRenderTime 56ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/src/lib/perl5/Tk/FileDialog.pm

https://github.com/ocoskun/ORAC-DR
Perl | 1256 lines | 829 code | 294 blank | 133 comment | 90 complexity | b01d092401cf269f22461beb93c81ea1 MD5 | raw file
  1. # M O D U L E D O C U M E N T I O N -----------------------------------------
  2. #+
  3. # Name:
  4. # Tk::FileDialog
  5. # Purposes:
  6. # A reuseable File Dialog (ORAC-DR style)
  7. # Description:
  8. # A reusable FileDialog based on Brent Powers original code distributed
  9. # with Perl::Tk. The Dialog has been modified to fit in the Xoracdr and
  10. # FITS Editor style.
  11. # Language:
  12. # Perl
  13. # Revision:
  14. # $Id$
  15. # Authors:
  16. # BBP: Brent B. Powers (powers@swaps-comm.ml.com)
  17. # EKR: Erik K. Ringger (ringger@cs.rochester.edu)
  18. # MKR: Martin Kraegeloh (mkr@dm-server.cv.com)
  19. # AA: Alasdair Allan (aa@astro.ex.ac.uk)
  20. # History:
  21. # ??-???-?? BBP:
  22. # Initial implementation
  23. # 15-Jan-96 EKR:
  24. # Fixed dialogue box creation.
  25. # Added option for selecting directories.
  26. # 29-Feb-96 BBP:
  27. # Rewrote and componentized, and added a bunch of options.
  28. # Now requires perl 5.002gamma
  29. # 30-May-96 MKR:
  30. # Add support for single character navigation within lists,
  31. # e.g. typing G in list will navigate to first entry starting with G
  32. # 30-May-96 MKR:
  33. # Fixed problem with click in empty file list
  34. # 31-May-96 BBP:
  35. # refixed problem with click in empty list (couldn't click the
  36. # first entry in list box). Altered single character navigation to
  37. # prevent nudging the mouse while in an entry from grabbing focus.
  38. # Altered single character navigation to go from first to next to next
  39. # to first (et al) & Finished keyboard navigation with tabs.
  40. # 03-Jun-96 BBP:
  41. # Version 1.2: Added Version, ship
  42. # 09-Jun-96 BBP:
  43. # Version 1.3: Added Version, and then fixed the Makefile.PM#
  44. # 09-Sep-01 AA:
  45. # Version 1.4: Modified for use in FITS Editor, customised to
  46. # blend in with GUI style, added packForget() lines for File label
  47. # (FEF) and File Entry (FileEntry) widgets if -SelDir option enabled.
  48. # 10-Sep-01 AA:
  49. # Version 1.4.1: Reformated POD documentation. Turned selection in
  50. # FileListBox to "background color" in case of -SelDir option.
  51. # 14-Sep-01 AA:
  52. # version 1.4.2: Added -Font configuration option
  53. # 01-Oct-01 AA:
  54. # version 1.4.3: Bug fix for font sizes in some widgets
  55. #-
  56. # ---------------------------------------------------------------------------
  57. =head1 NAME
  58. Tk::FileDialog - A highly configurable File Dialog widget for Perl/Tk.
  59. =head1 DESCRIPTION
  60. The widget is composed of a number of sub-widgets, namely, a listbox for files
  61. and (optionally) directories, an entry for filename, an (optional) entry for
  62. pathname, an entry for a filter pattern, a 'ShowAll' checkbox (for enabling
  63. display of .* files and directories), and three buttons, namely OK, Rescan, and
  64. Cancel. Note that the labels for all subwidgets (including the text for the
  65. buttons and Checkbox) are configurable for foreign language support. The
  66. Listboxes will respond to characters typed over them with scrolling to the
  67. first line that starts with the given character (or next etc. if this character
  68. is not present).
  69. =head1 SYNOPSIS
  70. =over 4
  71. =head2 Usage Description
  72. To use FileDialog, simply create your FileDialog objects during initialization
  73. (or at least before a Show). When you wish to display the FileDialog, invoke
  74. the 'Show' method on the FileDialog object; The method will return either a
  75. file name, a path name, or undef. undef is returned only if the user pressed
  76. the Cancel button.
  77. =head2 Example Code
  78. The following code creates a FileDialog and calls it. Note that perl5.002gamma
  79. is required.
  80. =over 4
  81. =item
  82. #!/usr/local/bin/perl -w
  83. use Tk;
  84. use Tk::FileDialog;
  85. use strict;
  86. my($main) = MainWindow->new;
  87. my($Horiz) = 1;
  88. my($fname);
  89. my($LoadDialog) = $main->FileDialog(-Title =>'This is my title',
  90. -Create => 0);
  91. print "Using FileDialog Version ",$LoadDialog->Version,"\n";
  92. $LoadDialog->configure(-FPat => '*pl',
  93. -ShowAll => 'NO');
  94. $main->Entry(-textvariable => \$fname)
  95. ->pack(-expand => 1,
  96. -fill => 'x');
  97. $main->Button(-text => 'Kick me!',
  98. -command => sub {
  99. $fname = $LoadDialog->Show(-Horiz => $Horiz);
  100. if (!defined($fname)) {
  101. $fname = "Fine,Cancel, but no Chdir anymore!!!";
  102. $LoadDialog->configure(-Chdir =>'NO');
  103. }
  104. })
  105. ->pack(-expand => 1,
  106. -fill => 'x');
  107. $main->Checkbutton(-text => 'Horizontal',
  108. -variable => \$Horiz)
  109. ->pack(-expand => 1,
  110. -fill => 'x');
  111. $main->Button(-text => 'Exit',
  112. -command => sub {
  113. $main->destroy;
  114. })
  115. ->pack(-expand => 1,
  116. -fill => 'x');
  117. MainLoop;
  118. print "Exit Stage right!\n";
  119. exit;
  120. =back
  121. =back
  122. =head1 METHODS
  123. =over 4
  124. =item
  125. The following non-standard methods may be used with a FileDialog object
  126. =item
  127. =head2 Show
  128. =over 4
  129. Displays the file dialog box for the user to operate. Additional configuration
  130. items may be passed in at Show-time In other words, this code snippet:
  131. $fd->Show(-Title => 'Ooooh, Preeeeeety!');
  132. is the same as this code snippet:
  133. $fd->configure(-Title => 'Ooooh, Preeeeeety!');
  134. $fd->Show;
  135. =back
  136. =item
  137. =head2 Version
  138. Returns the current Version of FileDialog
  139. =back
  140. =head1 CONFIGURATION
  141. Any of the following configuration items may be set via the configure (or Show)
  142. method, or retrieved via the cget method.
  143. =head2 I<Flags>
  144. Flags may be configured with either 1,'true', or 'yes' for 1, or 0, 'false', or
  145. 'no' for 0. Any portion of 'true', 'yes', 'false', or 'no' may be used, and
  146. case does not matter.
  147. =over 4
  148. =item
  149. =head2 -Chdir
  150. =over 8
  151. =item
  152. Enable the user to change directories. The default is 1. If disabled, the
  153. directory list box will not be shown.
  154. =back
  155. =head2 -Create
  156. =over 8
  157. =item
  158. Enable the user to specify a file that does not exist. If not enabled, and the
  159. user specifies a non-existent file, a dialog box will be shown informing the
  160. user of the error (This Dialog Box is configurable via the EDlg* switches,
  161. below).
  162. default: 1
  163. =back
  164. =head2 -ShowAll
  165. =over 8
  166. =item
  167. Determines whether hidden files (.*) are displayed in the File and Directory
  168. Listboxes. The default is 0. The Show All Checkbox reflects the setting of this
  169. switch.
  170. =back
  171. =head2 -DisableShowAll
  172. =over 8
  173. =item
  174. Disables the ability of the user to change the status of the ShowAll flag. The
  175. default is 0 (the user is by default allowed to change the status).
  176. =back
  177. =head2 -Font
  178. =over 8
  179. =item
  180. Changes the font style of the widget, defaults to 8pt Helvetica.
  181. =back
  182. =head2 -Grab
  183. =over 8
  184. =item
  185. Enables the File Dialog to do an application Grab when displayed. The default
  186. is 1.
  187. =back
  188. =head2 -Horiz
  189. =over 8
  190. =item
  191. True sets the File List box to be to the right of the Directory List Box. If 0,
  192. the File List box will be below the Directory List box. The default is 1.
  193. =back
  194. =head2 -SelDir
  195. =over 8
  196. =item
  197. If True, enables selection of a directory rather than a file, and disables the
  198. actions of the File List Box. The default is 0.
  199. =back
  200. =back
  201. =head2 I<Special>
  202. =over 4
  203. =item
  204. =head2 -FPat
  205. =over 8
  206. =item
  207. Sets the default file selection pattern. The default is '*'. Only files matching
  208. this pattern will be displayed in the File List Box.
  209. =back
  210. =head2 -Geometry
  211. =over 8
  212. =item
  213. Sets the geometry of the File Dialog. Setting the size is a dangerous thing to
  214. do. If not configured, or set to '', the File Dialog will be centered.
  215. =back
  216. =head2 -SelHook
  217. =over 8
  218. =item
  219. SelHook is configured with a reference to a routine that will be called when a
  220. file is chosen. The file is called with a sole parameter of the full path and
  221. file name of the file chosen. If the Create flag is disabled (and the user is
  222. not allowed to specify new files), the file will be known to exist at the time
  223. that SelHook is called. Note that SelHook will also be called with directories
  224. if the SelDir Flag is enabled, and that the FileDialog box will still be
  225. displayed. The FileDialog box should B<not> be destroyed from within the
  226. SelHook routine, although it may generally be configured.
  227. SelHook routines return 0 to reject the selection and allow the user to
  228. reselect, and any other value to accept the selection. If a SelHook routine
  229. returns non-zero, the FileDialog will immediately be withdrawn, and the file
  230. will be returned to the caller.
  231. There may be only one SelHook routine active at any time. Configuring the
  232. SelHook routine replaces any existing SelHook routine. Configuring the SelHook
  233. routine with 0 removes the SelHook routine. The default SelHook routine is
  234. undef.
  235. =back
  236. =back
  237. =head2 I<Strings>
  238. The following two switches may be used to set default variables, and to get
  239. final values after the Show method has returned (but has not been explicitly
  240. destroyed by the caller)
  241. =over 4
  242. =item
  243. B<-File> The file selected, or the default file. The default is ''.
  244. B<-Path> The path of the selected file, or the initial path. The default is $ENV{'HOME'}.
  245. =back
  246. =head2 I<Labels and Captions>
  247. For support of internationalization, the text on any of the subwidgets may be
  248. changed.
  249. =over 4
  250. =item
  251. B<-Title> The Title of the dialog box. The default is 'Select File:'.
  252. B<-DirLBCaption> The Caption above the Directory List Box. The default is 'Directories'.
  253. B<-FileLBCaption> The Caption above the File List Box. The default is 'Files'.
  254. B<-FileEntryLabel> The label to the left of the File Entry. The Default is 'Filename:'.
  255. B<-PathEntryLabel> The label to the left of the Path Entry. The default is 'Pathname:'.
  256. B<-FltEntryLabel> The label to the left of the Filter entry. The default is 'Filter:'.
  257. B<-ShowAllLabel> The text of the Show All Checkbutton. The default is 'Show All'.
  258. =back
  259. =head2 I<Button Text>
  260. For support of internationalization, the text on the three buttons may be changed.
  261. =over 4
  262. =item
  263. B<-OKButtonLabel> The text for the OK button. The default is 'OK'.
  264. B<-RescanButtonLabel> The text for the Rescan button. The default is 'Rescan'.
  265. B<-CancelButtonLabel> The text for the Cancel button. The default is 'Cancel'.
  266. =back
  267. =head2 I<Error Dialog Switches>
  268. If the Create switch is set to 0, and the user specifies a file that does not
  269. exist, a dialog box will be displayed informing the user of the error. These
  270. switches allow some configuration of that dialog box.
  271. =over 4
  272. =item
  273. =head2 -EDlgTitle
  274. =over 8
  275. =item
  276. The title of the Error Dialog Box. The default is 'File does not exist!'.
  277. =back
  278. =head2 -EDlgText
  279. =over 8
  280. =item
  281. The message of the Error Dialog Box. The variables $path, $file, and $filename
  282. (the full path and filename of the selected file) are available. The default
  283. is I<"You must specify an existing file.\n(\$filename not found)">
  284. =back
  285. =back
  286. =head1 Author
  287. B<Brent B. Powers, Merrill Lynch (B2Pi)>
  288. powers@ml.com
  289. This code may be distributed under the same conditions as Perl itself.
  290. =cut
  291. package Tk::FileDialog;
  292. use Tk;
  293. use Tk::Dialog;
  294. use Tk::Listbox;
  295. use Carp;
  296. use strict;
  297. @Tk::FileDialog::ISA = qw(Tk::Toplevel);
  298. Tk::Widget->Construct('FileDialog');
  299. $Tk::FileDialog::VERSION = '1.3';
  300. ### Global Variables (Convenience only)
  301. my(@topPack) = (-side => 'top', -anchor => 'center');
  302. my(@rightPack) = (-side => 'right', -anchor => 'center');
  303. my(@leftPack) = (-side => 'left', -anchor => 'center');
  304. my(@xfill) = (-fill => 'x');
  305. my(@yfill) = (-fill => 'y');
  306. my(@bothFill) = (-fill => 'both');
  307. my(@expand) = (-expand => 1);
  308. my(@TabOrder) = qw (DirList
  309. FileList
  310. FileEntry
  311. DirEntry
  312. PatEntry
  313. SABox
  314. OK
  315. Rescan
  316. Can
  317. DirList);
  318. sub Populate {
  319. ## File Dialog constructor, inherits new from Toplevel
  320. my($FDialog, @args) = @_;
  321. $FDialog->SUPER::Populate(@args);
  322. $FDialog->withdraw;
  323. $FDialog->protocol('WM_DELETE_WINDOW' => sub {
  324. if (defined($FDialog->{'Can'}) && $FDialog->{'Can'}->IsWidget ) {
  325. $FDialog->{'Can'}->invoke;
  326. }
  327. });
  328. $FDialog->transient($FDialog->Parent->toplevel);
  329. foreach (@TabOrder) {
  330. $FDialog->{'TabSel'}->{$_} = 1;
  331. }
  332. ## Initialize variables that won't be initialized later
  333. $FDialog->{'Retval'} = -1;
  334. $FDialog->{'DFFrame'} = 0;
  335. $FDialog->{Configure}{-Horiz} = 1;
  336. $FDialog->{Configure}{-Font} = 'Helvetica 8';
  337. $FDialog->BuildFDWindow;
  338. $FDialog->{'activefore'} = $FDialog->{'SABox'}->cget(-foreground);
  339. $FDialog->{'inactivefore'} = $FDialog->{'SABox'}->cget(-disabledforeground);
  340. $FDialog->{'backgroundcol'} = $FDialog->{'SABox'}->cget(-background);
  341. $FDialog->ConfigSpecs(-Font => ['PASSIVE', undef, undef, 'Helvetica 8'],
  342. -Chdir => ['PASSIVE', undef, undef, 1],
  343. -Create => ['PASSIVE', undef, undef, 1],
  344. -DisableShowAll => ['PASSIVE', undef, undef, 0],
  345. -FPat => ['PASSIVE', undef, undef, '*'],
  346. -File => ['PASSIVE', undef, undef, ''],
  347. -Geometry => ['PASSIVE', undef, undef, undef],
  348. -Grab => ['PASSIVE', undef, undef, 1],
  349. -Horiz => ['PASSIVE', undef, undef, 1],
  350. -Path => ['PASSIVE', undef, undef, "$ENV{'HOME'}"],
  351. -SelDir => ['PASSIVE', undef, undef, 0],
  352. -DirLBCaption => ['PASSIVE', undef, undef, 'Directories:'],
  353. -FileLBCaption => ['PASSIVE', undef, undef, 'File:'],
  354. -FileEntryLabel => ['METHOD', undef, undef, 'Filename:'],
  355. -PathEntryLabel => ['METHOD', undef, undef, 'Pathname:'],
  356. -FltEntryLabel => ['METHOD', undef, undef, 'Filter:'],
  357. -ShowAllLabel => ['METHOD', undef, undef, 'ShowAll'],
  358. -OKButtonLabel => ['METHOD', undef, undef, 'OK'],
  359. -RescanButtonLabel => ['METHOD', undef, undef, 'Rescan'],
  360. -CancelButtonLabel => ['METHOD', undef, undef, 'Cancel'],
  361. -SelHook => ['PASSIVE', undef, undef, undef],
  362. -ShowAll => ['PASSIVE', undef, undef, 0],
  363. -Title => ['PASSIVE', undef, undef, "Select File:"],
  364. -EDlgTitle => ['PASSIVE', undef, undef,
  365. 'File does not exist!'],
  366. -EDlgText => ['PASSIVE', undef, undef,
  367. "You must specify an existing file.\n"
  368. . "(\$filename not found)"]);
  369. }
  370. ### A few methods for configuration
  371. sub OKButtonLabel {
  372. &SetButton('OK',@_);
  373. }
  374. sub RescanButtonLabel {
  375. &SetButton('Rescan',@_);
  376. }
  377. sub CancelButtonLabel {
  378. &SetButton('Can',@_);
  379. }
  380. sub SetButton {
  381. my($widg, $self, $title) = @_;
  382. if (defined($title)) {
  383. ## This is a configure
  384. $self->{$widg}->configure(-font => $self->{Configure}{-Font}, -text => $title);
  385. }
  386. ## Return the current value
  387. $self->{$widg}->cget(-text);
  388. }
  389. sub FileEntryLabel {
  390. &SetLabel('FEF', @_);
  391. }
  392. sub PathEntryLabel {
  393. &SetLabel('PEF', @_);
  394. }
  395. sub FltEntryLabel {
  396. &SetLabel('patFrame', @_);
  397. }
  398. sub ShowAllLabel {
  399. &SetButton('SABox', @_);
  400. }
  401. sub SetLabel {
  402. my($widg, $self, $title) = @_;
  403. if (defined($title)) {
  404. ## This is a configure
  405. $self->{$widg}->{'Label'}->configure(-font => $self->{Configure}{-Font}, -text => $title);
  406. }
  407. ## Return the current value
  408. $self->{$widg}->{'Label'}->cget(-text);
  409. }
  410. sub SetFlag {
  411. ## Set the given flag to either 1 or 0, as appropriate
  412. my($self, $flag, $dflt) = @_;
  413. $flag = "-$flag";
  414. ## We know it's defined as there was a ConfigDefault call after the Populate
  415. ## call. Therefore, all we have to do is parse the non-numerics
  416. if (&IsNum($self->{Configure}{$flag})) {
  417. $self->{Configure}{$flag} = 1 unless $self->{Configure}{$flag} == 0;
  418. } else {
  419. my($val) = $self->{Configure}{$flag};
  420. my($fc) = lc(substr($val,0,1));
  421. if (($fc eq 'y') || ($fc eq 't')) {
  422. $val = 1;
  423. } elsif (($fc eq 'n') || ($fc eq 'f')) {
  424. $val = 0;
  425. } else {
  426. ## bad value, complain about it
  427. carp ("\"$val\" is not a valid flag ($flag)!");
  428. $dflt = 0 if !defined($dflt);
  429. $val = $dflt;
  430. }
  431. $self->{Configure}{$flag} = $val;
  432. }
  433. return $self->{Configure}{$flag};
  434. }
  435. sub Version {return $Tk::FileDialog::VERSION;}
  436. sub Show {
  437. my ($self) = shift;
  438. $self->configure(@_);
  439. ## Clean up flag variables
  440. $self->SetFlag('Chdir');
  441. $self->SetFlag('Create');
  442. $self->SetFlag('ShowAll');
  443. $self->SetFlag('DisableShowAll');
  444. $self->SetFlag('Horiz');
  445. $self->SetFlag('Grab');
  446. $self->SetFlag('SelDir');
  447. croak "Can't SelDir and Not ChDir" if $self->{Configure}{-SelDir} &&
  448. !$self->{Configure}{-Chdir};
  449. ## Set up, or remove, the directory box
  450. &BuildListBoxes($self);
  451. ## Enable, or disable, the show all box
  452. if ($self->{Configure}{-DisableShowAll}) {
  453. $self->{'SABox'}->configure(-state => 'disabled');
  454. $self->{'TabSel'}->{'SABox'} = 0;
  455. } else {
  456. $self->{'SABox'}->configure(-state => 'normal');
  457. $self->{'TabSel'}->{'SABox'} = 1;
  458. }
  459. # Fix the fonts for FileEntry, DirEntry and PatEntry
  460. $self->{'FileEntry'}->configure(-font => $self->{Configure}{-Font});
  461. $self->{'DirEntry'}->configure(-font => $self->{Configure}{-Font});
  462. $self->{'PatEntry'}->configure(-font => $self->{Configure}{-Font});
  463. ## Enable or disable the file entry box
  464. if ($self->{Configure}{-SelDir}) {
  465. $self->{Configure}{-File} = '';
  466. $self->{'FileEntry'}->configure(-state => 'disabled',
  467. -font => $self->{Configure}{-Font},
  468. -foreground => $self->{'inactivefore'});
  469. $self->{'FileList'}->configure(-selectforeground => $self->{'inactivefore'});
  470. # we have -DirSel forget the file entry widget
  471. $self->{'FEF'}->packForget();
  472. $self->{'FileEntry'}->packForget();
  473. # and turn off selection to the FileList
  474. $self->{'FileList'}->configure(-takefocus => 0,
  475. -selectbackground => $self->{'backgroundcol'});
  476. $self->{'FileList'}->configure(-foreground => $self->{'inactivefore'});
  477. $self->{'TabSel'}->{'FileEntry'} = $self->{'TabSel'}->{'FileList'} = 0;
  478. } else {
  479. $self->{'FileEntry'}->configure(-state => 'normal',
  480. -font => $self->{Configure}{-Font},
  481. -foreground => $self->{'activefore'});
  482. $self->{'FileList'}->configure(-selectforeground => $self->{'activefore'});
  483. $self->{'FileList'}->configure(-foreground => $self->{'activefore'});
  484. $self->{'TabSel'}->{'FileEntry'} = $self->{'TabSel'}->{'FileList'} = 1;
  485. }
  486. ## Set the title
  487. $self->title($self->{Configure}{-Title});
  488. ## Create window position (Center unless configured)
  489. $self->update;
  490. if (defined($self->{Configure}{-Geometry})) {
  491. $self->geometry($self->{Configure}{-Geometry});
  492. } else {
  493. my($x,$y);
  494. $x = int(($self->screenwidth - $self->reqwidth)/2 - $self->parent->vrootx);
  495. $y = int(($self->screenheight - $self->reqheight)/2 - $self->parent->vrooty);
  496. $self->geometry("+$x+$y");
  497. }
  498. ## Set up the tab order
  499. &SetTabs($self);
  500. ## Fill the list boxes
  501. &RescanFiles($self);
  502. ## Restore the window, and go
  503. $self->update;
  504. $self->deiconify;
  505. ## Set up the grab
  506. $self->grab if ($self->{Configure}{-Grab});
  507. ## Initialize status variables
  508. $self->{'Retval'} = 0;
  509. $self->{'RetFile'} = "";
  510. my($i) = 0;
  511. while (!$i) {
  512. $self->tkwait('variable',\$self->{'Retval'});
  513. $i = $self->{'Retval'};
  514. if ($i != -1) {
  515. ## No cancel, so call the hook if it's defined
  516. if (defined($self->{Configure}{-SelHook})) {
  517. ## The hook returns 0 to ignore the result,
  518. ## non-zero to accept. Must release the grab before calling
  519. $self->grab('release') if (defined($self->grab('current')));
  520. $i = &{$self->{Configure}{-SelHook}}($self->{'RetFile'});
  521. $self->grab if ($self->{Configure}{-Grab});
  522. }
  523. } else {
  524. $self->{'RetFile'} = undef;
  525. }
  526. }
  527. $self->grab('release') if (defined($self->grab('current')));
  528. $self->withdraw;
  529. my $path = $self->{Configure}{-Path};
  530. my $fname = $self->{Configure}{-File};
  531. #print "Path: " . $path . "\n";
  532. #print "File: " . $fname . "\n";
  533. return ( $path, $fname );
  534. }
  535. #### PRIVATE METHODS AND SUBROUTINES ####
  536. sub IsNum {
  537. my($parm) = @_;
  538. my($warnSave) = $;
  539. $ = 0;
  540. my($res) = (($parm + 0) eq $parm);
  541. $ = $warnSave;
  542. return $res;
  543. }
  544. sub TabNext {
  545. my($self, $inc) = @_;
  546. my($f) = $self->{'OK'}->focusCurrent;
  547. return if !defined($f);
  548. ## Find the object with the matching focus...
  549. my($i) = 0;
  550. foreach (@TabOrder) {
  551. if ($self->{$_} eq $f) {
  552. $i = $#TabOrder if !$i && $inc == -1;
  553. $i += $inc;
  554. while (!defined($TabOrder[$i]) ||
  555. !ref($self->{$TabOrder[$i]}) ||
  556. !$self->{$TabOrder[$i]}->IsWidget ||
  557. !($self->{'TabSel'}->{$TabOrder[$i]})) {
  558. # (($TabOrder[$i] == 'FileList') &&
  559. # $self->{Configure}{-SelDir}) ) {
  560. $i += $inc;
  561. $i = $#TabOrder if !$i && $inc == -1;
  562. $i = 0 if $i > $#TabOrder && $inc == 1;
  563. }
  564. $self->{$TabOrder[$i]}->focus;
  565. $self->break;
  566. return;
  567. }
  568. $i++;
  569. }
  570. }
  571. sub SetTabs {
  572. my($self) = shift;
  573. foreach (@TabOrder) {
  574. next if (!defined($self->{$_}) ||
  575. !ref $self->{$_} ||
  576. !$self->{$_}->IsWidget);
  577. $self->{$_}->bind("<Tab>", sub {\&TabNext($self, 1);});
  578. $self->{$_}->bind("<Shift-Tab>", sub {\&TabNext($self, -1);});
  579. }
  580. }
  581. sub BuildListBox {
  582. my($self, $fvar, $flabel, $listvar,$hpack, $vpack) = @_;
  583. ## Create the subframe
  584. $self->{"$fvar"} = $self->{'DFFrame'}->Frame
  585. ->pack(-side => $self->{Configure}{-Horiz} ? $hpack : $vpack,
  586. -anchor => 'center',
  587. @bothFill, @expand);
  588. ## Create the label
  589. $self->{"$fvar"}->Label(-font => $self->{Configure}{-Font},
  590. -relief => 'flat', -text => "$flabel")
  591. ->pack(@topPack, @xfill);
  592. ## Create the frame for the list box
  593. my($fbf) = $self->{"$fvar"}->Frame
  594. ->pack(@topPack, @bothFill, @expand);
  595. ## And the scrollbar and listbox in it
  596. $self->{"$listvar"} = $fbf->Listbox( -borderwidth => 1,
  597. -selectbackground => 'blue',
  598. -font => $self->{Configure}{-Font},
  599. -selectforeground => 'white',
  600. -selectmode => 'single')
  601. ->pack(@leftPack, @expand, @bothFill);
  602. $fbf->AddScrollbars($self->{"$listvar"});
  603. $fbf->configure(-scrollbars => 'rse');
  604. }
  605. sub DirSel {
  606. my($self, $lbdir) = @_;
  607. my($np) = $lbdir->curselection;
  608. return if !defined($np);
  609. $np = $lbdir->get($np);
  610. if ($np eq "..") {
  611. ## Moving up one directory
  612. $_ = $self->{Configure}{-Path};
  613. chop if m!/$!;
  614. s!(.*/)[^/]*$!$1!;
  615. $self->{Configure}{-Path} = $_;
  616. } else {
  617. ## Going down into a directory
  618. $self->{Configure}{-Path} .= "/" . "$np/";
  619. }
  620. $self->{Configure}{-Path} =~ s!//*!/!g;
  621. \&RescanFiles($self);
  622. }
  623. sub BindDir {
  624. ### Set up the bindings for the directory selection list box
  625. my($self) = @_;
  626. my($lbdir) = $self->{'DirList'};
  627. $lbdir->bind("<Double-1>" => sub {&DirSel($self, $lbdir);});
  628. # binding to take focus if clicked
  629. $lbdir->bind("<1>", sub{$self->{'DirList'}->focus;});
  630. # binding to attract focus if mouse over list
  631. $lbdir->bind("<Enter>", sub{&listFocus($self, 'DirList');});
  632. # binding to move to requested line by hitting a key
  633. $lbdir->bind("<KeyPress>", sub{\&list_see($self,'Dir');});
  634. # binding to select and rescan if someone hits return
  635. $lbdir->bind("<Return>", sub {&DirSel($self,$lbdir);});
  636. }
  637. sub FileSel {
  638. my($self) = shift;
  639. if (!$self->{Configure}{-SelDir}) {
  640. my($f) = $self->{'FileList'}->curselection;
  641. return if !defined($f);
  642. $self->{'File'} = $self->{'FileList'}->get($f);
  643. $self->{'OK'}->invoke;
  644. }
  645. }
  646. sub BindFile {
  647. ### Set up the bindings for the file selection list box
  648. my($self) = @_;
  649. $self->{'FileList'}->configure( -selectforeground => 'white');
  650. ## A single click selects the file...
  651. $self->{'FileList'}->bind("<ButtonRelease-1>", sub {
  652. if (!$self->{Configure}{-SelDir}) {
  653. my($n);
  654. return if (!defined($n=$self->{'FileList'}->curselection));
  655. ($self->{Configure}{-File} = $self->{'FileList'}->get($n));
  656. ##if defined($n);
  657. }
  658. });
  659. ## A double-click selects the file for good
  660. $self->{'FileList'}->bind("<Double-1>", sub {&FileSel($self);});
  661. # binding to take focus if clicked
  662. $self->{'FileList'}->bind("<1>", sub{
  663. $self->{'FileList'}->focus;
  664. });
  665. # binding to attract focus if mouse over list
  666. $self->{'FileList'}->bind("<Enter>", sub{
  667. &listFocus($self, 'FileList');
  668. });
  669. # binding to select and be done if someone hits return
  670. $self->{'FileList'}->bind("<Return>", sub {&FileSel($self);});
  671. # binding to move to requested line by hitting a key
  672. $self->{'FileList'}->bind("<KeyPress>", sub{&list_see($self,'File');});
  673. }
  674. sub BuildEntry {
  675. ### Build the entry, label, and frame indicated. This is a
  676. ### convenience routine to avoid duplication of code between
  677. ### the file and the path entry widgets
  678. my($self, $LabelVar, $entry) = @_;
  679. $LabelVar = "-$LabelVar";
  680. ## Create the entry frame
  681. my $eFrame = $self->Frame()
  682. ->pack(@topPack, @xfill);
  683. ## Now create and pack the title and entry
  684. $eFrame->{'Label'} = $eFrame->Label( -relief => 'flat', -font => $self->{Configure}{-Font})
  685. ->pack(@leftPack);
  686. $self->{"$entry"} = $eFrame->Entry(
  687. -textvariable => \$self->{Configure}{$LabelVar},
  688. -exportselection => 1,
  689. -font => $self->{Configure}{-Font},
  690. -selectbackground => 'blue',
  691. -selectforeground => 'white',
  692. -justify => 'left' )
  693. ->pack(@rightPack, @expand, @xfill);
  694. $self->{"$entry"}->bind("<Return>",sub {
  695. &RescanFiles($self);
  696. $self->{'OK'}->focus;
  697. });
  698. return $eFrame;
  699. }
  700. sub BuildListBoxes {
  701. my($self) = shift;
  702. ## Destroy both, if they're there
  703. if ($self->{'DFFrame'} && $self->{'DFFrame'}->IsWidget) {
  704. $self->{'DFFrame'}->destroy;
  705. }
  706. $self->{'DFFrame'} = $self->Frame;
  707. $self->{'DFFrame'}->pack(-before => $self->{'FEF'},
  708. @topPack, @bothFill, @expand);
  709. ## Build the file window before the directory window, even
  710. ## though the file window is below the directory window, we'll
  711. ## pack the directory window before.
  712. &BuildListBox($self, 'FileFrame',
  713. $self->{Configure}{-FileLBCaption},
  714. 'FileList','right','bottom');
  715. ## Set up the bindings for the file list
  716. &BindFile($self);
  717. if ($self->{Configure}{-Chdir}) {
  718. &BuildListBox($self,'DirFrame',$self->{Configure}{-DirLBCaption},
  719. 'DirList','left','top');
  720. &BindDir($self);
  721. }
  722. $self->{'FileList'}->configure(-selectforeground => 'white' );
  723. }
  724. sub BuildFDWindow {
  725. ### Build the entire file dialog window
  726. my($self) = shift;
  727. ### Build the filename entry box
  728. $self->{'FEF'} = &BuildEntry($self, 'File', 'FileEntry');
  729. ### Build the pathname directory box
  730. $self->{'PEF'} = &BuildEntry($self, 'Path','DirEntry');
  731. ### Now comes the multi-part frame
  732. my $patFrame = $self->Frame()
  733. ->pack(@topPack, @xfill);
  734. ## Label first...
  735. $self->{'patFrame'}->{'Label'} = $patFrame->Label(-relief => 'flat',
  736. -font => $self->{Configure}{-Font})
  737. ->pack(@leftPack);
  738. ## Now the entry...
  739. $self->{'PatEntry'} = $patFrame->Entry(-font => $self->{Configure}{-Font},
  740. -exportselection => 1,
  741. -font => $self->{Configure}{-Font},
  742. -selectbackground => 'blue',
  743. -selectforeground => 'white',
  744. -justify => 'left',
  745. -textvariable => \$self->{Configure}{-FPat})
  746. ->pack(@leftPack, @expand, @xfill);
  747. $self->{'PatEntry'}->bind("<Return>",sub {\&RescanFiles($self);});
  748. ## and the Check box
  749. $self->{'SABox'} = $patFrame->Checkbutton( -font => $self->{Configure}{-Font},
  750. -text => 'Insert',
  751. -selectcolor => 'blue',
  752. -onvalue => 'plain',
  753. -variable => \$self->{Configure}{-ShowAll},
  754. -command => sub {\&RescanFiles($self);})
  755. ->pack(@leftPack);
  756. ### FINALLY!!! the button frame
  757. my $butFrame = $self->Frame();
  758. $butFrame->pack(@topPack, @xfill);
  759. $self->{'OK'} = $butFrame->Button(-font => $self->{Configure}{-Font},
  760. -activeforeground => 'white',
  761. -activebackground => 'blue',
  762. -command => sub {
  763. \&GetReturn($self);
  764. })
  765. ->pack(@leftPack, @expand, @xfill);
  766. $self->{'Rescan'} = $butFrame->Button(-font => $self->{Configure}{-Font},
  767. -activeforeground => 'white',
  768. -activebackground => 'blue',
  769. -command => sub {
  770. \&RescanFiles($self);
  771. })
  772. ->pack(@leftPack, @expand, @xfill);
  773. $self->{'Can'} = $butFrame->Button(-font => $self->{Configure}{-Font},
  774. -activeforeground => 'white',
  775. -activebackground => 'blue',
  776. -command => sub {
  777. $self->{'Retval'} = -1;
  778. })
  779. ->pack(@leftPack, @expand, @xfill);
  780. }
  781. sub RescanFiles {
  782. ### Fill the file and directory boxes
  783. my($self) = shift;
  784. my($fl) = $self->{'FileList'};
  785. my($dl) = $self->{'DirList'};
  786. my($path) = $self->{Configure}{-Path};
  787. my($show) = $self->{Configure}{-ShowAll};
  788. my($chdir) = $self->{Configure}{-Chdir};
  789. ### Remove a final / if it is there, and add it
  790. $path = '' if !defined($path);
  791. if ((length($path) == 0) || (substr($path,-1,1) ne '/')) {
  792. $path .= '/';
  793. $self->{Configure}{-Path} = $path;
  794. }
  795. ### path now has a trailing / no matter what
  796. if (!-d $path) {
  797. carp "$path is NOT a directory\n";
  798. return 0;
  799. }
  800. $self->configure(-cursor => 'watch');
  801. my($OldGrab) = $self->grab('current');
  802. $self->{'OK'}->grab;
  803. $self->{'OK'}->configure(-state => 'disabled');
  804. $self->update;
  805. opendir(ALLFILES,$path);
  806. my(@allfiles) = readdir(ALLFILES);
  807. closedir(ALLFILES);
  808. my($direntry);
  809. ## First, get the directories...
  810. if ($chdir) {
  811. $dl->delete(0,'end');
  812. my %see; # hold index if first occurrence of first character of direntry
  813. my $n=0; # number of entry in list
  814. foreach $direntry (sort @allfiles) {
  815. next if !-d "$path$direntry";
  816. next if $direntry eq ".";
  817. if ( !$show
  818. && (substr($direntry,0,1) eq ".")
  819. && $direntry ne "..") {
  820. next;
  821. }
  822. $dl->insert('end',$direntry);
  823. if(! exists($see{substr($direntry,0,1)})){
  824. $see{substr($direntry,0,1)}=$n;
  825. }
  826. $n++;
  827. }
  828. $self->{see_Dir}=\%see;
  829. }
  830. ## Now, get the files
  831. $fl->delete(0,'end');
  832. $_ = $self->{Configure}{-FPat};
  833. s/^\s*|\s*$//;
  834. $_ = $self->{Configure}{-FPat} = '*' if $_ eq '';
  835. my($pat) = $_;
  836. undef @allfiles;
  837. @allfiles = <$path.$pat> if $show;
  838. @allfiles = (@allfiles, <$path$pat>);
  839. my %see; # hold index if first occurrence of first character of fileentry
  840. my $n=0; # number of entry in list
  841. my $fileentry;
  842. foreach $fileentry (sort @allfiles) {
  843. if (-f $fileentry) {
  844. $fileentry =~ s!.*/!!; # mkr s!.*/([^/]*)$!$1!;
  845. $fl->insert('end',$fileentry);
  846. if(! exists($see{substr($fileentry,0,1)})){
  847. $see{substr($fileentry,0,1)}=$n;
  848. }
  849. $n++;
  850. }
  851. $self->{see_File}=\%see;
  852. }
  853. $self->configure(-cursor => 'top_left_arrow');
  854. $self->{'OK'}->grab('release') if $self->grab('current') == $self->{'OK'};
  855. $OldGrab->grab if defined($OldGrab);
  856. $self->{'OK'}->configure(-state => 'normal');
  857. $self->update;
  858. return 1;
  859. }
  860. sub GetReturn {
  861. my ($self) = @_;
  862. ## Construct the filename
  863. my $path = $self->{Configure}{-Path};
  864. my $fname;
  865. $path .= "/" if (substr($path, -1, 1) ne '/');
  866. if ($self->{Configure}{-SelDir}) {
  867. $fname = $self->{'DirList'};
  868. if (defined($fname->curselection)) {
  869. $fname = $fname->get($fname->curselection);
  870. } else {
  871. $fname = '';
  872. }
  873. $fname = $path . $fname;
  874. $fname =~ s/\/$//;
  875. } else {
  876. $fname = $path . $self->{Configure}{-File};
  877. ## Make sure that the file exists, if the user is not allowed
  878. ## to create
  879. if (!$self->{Configure}{-Create} && !(-f $fname)) {
  880. ## Put up no create dialog
  881. my($path) = $self->{Configure}{-Path};
  882. my($file) = $self->{Configure}{-File};
  883. my($filename) = $fname;
  884. eval "\$fname = \"$self->{Configure}{-EDlgText}\"";
  885. $self->Dialog(-title => $self->{Configure}{-EDlgTitle},
  886. -text => $fname,
  887. -bitmap => 'error',
  888. -font => $self->{Configure}{-Font})
  889. ->Show;
  890. ## And return
  891. return;
  892. }
  893. }
  894. $self->{'RetFile'} = $fname;
  895. $self->{'Retval'} = 1;
  896. }
  897. sub listFocus {
  898. ## Change focus ONLY if current focus is not an entry...
  899. my ($self, $lb) = @_;
  900. my($f) = $self->{$lb}->focusCurrent;
  901. if (defined($f) &&
  902. $f ne $self->{'FileEntry'} &&
  903. $f ne $self->{'DirEntry'} &&
  904. $f ne $self->{'PatEntry'}) {
  905. $self->{$lb}->focus;
  906. }
  907. }
  908. sub list_see{
  909. my ($self, $what) = @_; # $what is dir or file
  910. my $list=$self->{"${what}List"}; # Dir or File
  911. my $see=$self->{"see_$what"}; # index hash
  912. my $char=$list->XEvent->A; # key pressed
  913. if (exists($see->{$char})) { # line with char there?
  914. ## Yes, it is...
  915. my ($ndx) = $see->{$char};
  916. ## Is it already selected?
  917. my($cs) = $list->curselection;
  918. if (defined($cs)) {
  919. if (($cs == $ndx) ||
  920. (substr($list->get($cs),0,1) eq $char)) {
  921. ## does the next in the list start with the same char?
  922. if (defined($list->get($cs + 1)) &&
  923. (substr($list->get($cs + 1),0,1) eq $char)) {
  924. $ndx = ++$cs;
  925. }
  926. }
  927. }
  928. $list->see($ndx); # yes, so show it.
  929. $list->selection('clear',0,'end');
  930. return if $self->{Configure}{-SelDir};
  931. $list->selection('set', $ndx);
  932. $self->{Configure}{-File} = $list->get($ndx) if $what eq 'File';
  933. } else { # search next line in sequence
  934. while(!exists($see->{$char}) && length($char) == 1) {
  935. $char++;
  936. }
  937. if(exists($see->{$char})){
  938. $char = $see->{$char};
  939. $list->see($char-1) if $char; # Show the one before
  940. $list->see($char); # and show this one...
  941. # } else {
  942. # $list->bell;
  943. }
  944. }
  945. }
  946. ### Return 1 to the calling use statement ###
  947. 1;
  948. ### End of file FileDialog.pm ###
  949. __END__