File Coverage

blib/lib/UI/Dialog/Backend.pm
Criterion Covered Total %
statement 208 524 39.6
branch 56 258 21.7
condition 37 201 18.4
subroutine 38 55 69.0
pod 18 31 58.0
total 357 1069 33.4


line stmt bran cond sub pod time code
1             package UI::Dialog::Backend;
2             ###############################################################################
3             # Copyright (C) 2004-2016 Kevin C. Krinke
4             #
5             # This library is free software; you can redistribute it and/or
6             # modify it under the terms of the GNU Lesser General Public
7             # License as published by the Free Software Foundation; either
8             # version 2.1 of the License, or (at your option) any later version.
9             #
10             # This library is distributed in the hope that it will be useful,
11             # but WITHOUT ANY WARRANTY; without even the implied warranty of
12             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13             # Lesser General Public License for more details.
14             #
15             # You should have received a copy of the GNU Lesser General Public
16             # License along with this library; if not, write to the Free Software
17             # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18             ###############################################################################
19 16     16   222 use 5.006;
  16         37  
20 16     16   50 use strict;
  16         16  
  16         254  
21 16     16   46 use warnings;
  16         14  
  16         311  
22 16     16   51 use Carp;
  16         13  
  16         768  
23 16     16   58 use Cwd qw( abs_path );
  16         16  
  16         565  
24 16     16   66 use File::Basename;
  16         24  
  16         919  
25 16     16   6394 use Text::Wrap qw( wrap );
  16         30670  
  16         727  
26 16     16   5622 use String::ShellQuote;
  16         8780  
  16         743  
27 16     16   7469 use File::Slurp;
  16         152300  
  16         1005  
28              
29             BEGIN {
30 16     16   104 use vars qw($VERSION);
  16         17  
  16         502  
31 16     16   31795 $VERSION = '1.21';
32             }
33              
34             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
35             #: Constructor Method
36             #:
37              
38             #: not even really necessary as this class is inherited, and the constructor is
39             #: more often than not overridden by the backend inheriting it.
40             sub new {
41 0     0 0 0 my $proto = shift();
42 0   0     0 my $class = ref($proto) || $proto;
43 0 0       0 my $cfg = ((ref($_[0]) eq "HASH") ? $_[0] : (@_) ? { @_ } : {});
    0          
44 0         0 my $self = { '_opts' => $cfg };
45 0 0       0 $self->{'test_mode'} = $cfg->{'test_mode'} if exists $cfg->{'test_mode'};
46 0         0 $self->{'test_mode_result'} = '';
47 0         0 bless($self, $class);
48 0         0 return($self);
49             }
50              
51             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
52             #: Accessory Methods
53             #:
54              
55             #: Return the path to the dialog variant binary
56             sub get_bin {
57 1 50   1 0 352 return $_[0]->{'_opts'}{'bin'} if defined $_[0]->{'_opts'}{'bin'};
58 0         0 return undef;
59             }
60              
61             #: Provide the API interface to nautilus
62             sub nautilus {
63 0     0 1 0 my $self = $_[0];
64 0   0     0 my $nautilus = $self->{'_nautilus'} || {};
65 0 0       0 unless (ref($nautilus) eq "UI::Dialog::Backend::Nautilus") {
66 0 0       0 if ($self->_find_bin('nautilus')) {
67 0 0       0 if (eval "require UI::Dialog::Backend::Nautilus; 1") {
68 0         0 require UI::Dialog::Backend::Nautilus;
69 0         0 $self->{'_nautilus'} = new UI::Dialog::Backend::Nautilus;
70             }
71             }
72             }
73 0         0 return($self->{'_nautilus'});
74             }
75              
76             #: Provide the API interface to osd_cat (aka: xosd)
77             sub xosd {
78 0     0 1 0 my $self = shift();
79 0 0       0 my @args = (@_ %2 == 0) ? (@_) : ();
80 0   0     0 my $xosd = $self->{'_xosd'} || {};
81 0 0       0 unless (ref($xosd) eq "UI::Dialog::Backend::XOSD") {
82 0 0       0 if ($self->_find_bin('osd_cat')) {
83 0 0       0 if (eval "require UI::Dialog::Backend::XOSD; 1") {
84 0         0 require UI::Dialog::Backend::XOSD;
85 0         0 $self->{'_xosd'} = new UI::Dialog::Backend::XOSD (@args);
86             }
87             }
88             }
89 0         0 return($self->{'_xosd'});
90             }
91              
92             #: Provide the API interface to notify-send
93             sub notify_send {
94 0     0 1 0 my $self = shift();
95 0 0       0 my @args = (@_ %2 == 0) ? (@_) : ();
96 0   0     0 my $notify_send = $self->{'_notify_send'} || {};
97 0 0       0 unless (ref($notify_send) eq "UI::Dialog::Backend::NotifySend") {
98 0 0       0 if ($self->_find_bin('notify-send')) {
99 0 0       0 if (eval "require UI::Dialog::Backend::NotifySend; 1") {
100 0         0 require UI::Dialog::Backend::NotifySend;
101 0         0 $self->{'_notify_send'} = new UI::Dialog::Backend::NotifySend (@args);
102             }
103             }
104             }
105 0         0 return($self->{'_notify_send'});
106             }
107              
108             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
109             #: State Methods
110             #:
111              
112             #: enable altering of attributes
113             sub attr {
114 0     0 1 0 my $self = $_[0];
115 0         0 my $name = $_[1];
116 0 0       0 unless ($_[2]) {
117 0 0       0 return($self->{'_opts'}->{$name}) unless not $self->{'_opts'}->{$name};
118 0         0 return(undef());
119             }
120 0 0 0     0 if ($_[2] == 0 || $_[2] =~ /^NULL$/i) {
121 0         0 $self->{'_opts'}->{$name} = 0;
122             }
123             else {
124 0         0 $self->{'_opts'}->{$name} = $_[2];
125             }
126 0         0 return($self->{'_opts'}->{$name});
127             }
128              
129             #: return the last response data as an ARRAY
130             sub ra {
131 1     1 1 2 my $self = shift();
132 1         1 my (@argv) = @_;
133 1 50       2 if (@argv) {
134 1 50 33     5 if (defined $argv[0] && $argv[0] =~ m!^null$!i) {
135 0         0 $self->{'_state'}{'ra'} = [];
136             } else {
137 1         2 $self->{'_state'}{'ra'} = \@argv;
138             }
139             } else {
140 0   0     0 $self->{'_state'}->{'ra'} ||= [];
141             }
142 1         1 return(@{ $self->{'_state'}->{'ra'} });
  1         2  
143             }
144              
145             #: return the last response data as a SCALAR
146             sub rs {
147 1     1 1 1 my $self = shift();
148 1         2 my (@argv) = @_;
149 1 50       2 if (@argv) {
150 1 50 33     10 if (defined $argv[0] && $argv[0] =~ m!^null$!i) {
151 0         0 $self->{'_state'}{'rs'} = '';
152             } else {
153 1         2 $self->{'_state'}{'rs'} = $argv[0];
154             }
155             }
156 1         2 return($self->{'_state'}->{'rs'});
157             }
158              
159             #: return the last exit code as a SCALAR
160             sub rv {
161 1     1 1 1 my $self = shift();
162 1         2 my (@argv) = @_;
163 1 50       2 if (@argv) {
164 1 50 33     5 if (defined $argv[0] && $argv[0] =~ m!^null$!i) {
165 1         2 $self->{'_state'}{'rv'} = 0;
166             } else {
167 0         0 $self->{'_state'}{'rv'} = $argv[0];
168             }
169             }
170 1         2 return($self->{'_state'}->{'rv'});
171             }
172              
173             #: report on the state of the last dialog variant execution.
174             sub state {
175 0     0 1 0 my $self = shift();
176 0   0     0 my $rv = $self->rv() || 0;
177 0   0     0 $self->_debug((join(" | ",(caller())))." > state() > is: ".($rv||'NULL'),2);
178 0 0 0     0 if ($rv == 1 or $rv == 129) {
    0 0        
    0          
    0          
    0          
    0          
179 0         0 return("CANCEL");
180             }
181             elsif ($rv == 2) {
182 0         0 return("HELP");
183             }
184             elsif ($rv == 3) {
185 0         0 return("EXTRA");
186             }
187             elsif ($rv == 254) {
188 0         0 return("ERROR");
189             }
190             elsif ($rv == 255) {
191 0         0 return("ESC");
192             }
193             elsif (not $rv or $rv =~ /^null$/i) {
194 0         0 return("OK");
195             }
196             else {
197 0         0 return("UNKNOWN(".$rv.")");
198             }
199             }
200              
201             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
202             #: Preparation Methods
203             #
204              
205             #: construct a HASHREF for command {{tag}} substitutions
206             sub make_kvt {
207 10     10 0 9 my ($self,$args,$value) = @_;
208             return
209             {
210             literal => ($args->{'literal'} || 0),
211             width => ($args->{'width'}||'65'),
212 10   50     81 trust => ($args->{'trust-input'} || 0),
      50        
      100        
      50        
213             value => ($value || '')
214             };
215             }
216             sub make_kvl {
217 279     279 0 219 my ($self,$args,$value) = @_;
218             return
219             {
220             literal => 1,
221             width => ($args->{'width'}||'65'),
222 279   50     1550 trust => ($args->{'trust-input'} || 0),
      100        
      100        
223             value => ($value || '')
224             };
225             }
226              
227             #: Helper method to generate a base format string, accepts additional
228             #: strings which are considered trusted programmer template input.
229             sub prepare_format {
230 11     11 0 10 my $self = shift(@_);
231 11         8 my $args = shift(@_);
232             # start with our binary path
233 11         10 my $fmt = $self->{'_opts'}{'bin'};
234 11         19 $fmt = $self->append_format_check($args,$fmt,'title','--title {{title}}');
235 11         14 return $fmt;
236             }
237              
238             sub append_format {
239 46     46 0 42 my ($self,$fmt,$value) = @_;
240 46 50       51 if (ref($fmt) eq "SCALAR") {
241 0         0 $$fmt .= ' '.$value;
242             }
243             else {
244 46         65 $fmt .= ' '.$value;
245             }
246 46         57 return $fmt;
247             }
248              
249             #: simple test and if true; append value to format
250             sub append_format_check {
251 154     154 0 130 my ($self,$args,$fmt,$key,$value) = @_;
252 154 50 66     262 if (exists $args->{$key} and defined $args->{$key} and $args->{$key}) {
      66        
253 22         23 $fmt = $self->append_format($fmt,$value);
254             }
255 154         196 return $fmt;
256             }
257              
258             sub clean_format {
259 9     9 0 9 my ($self,$trust,$sref) = @_;
260 9 50       14 unless (ref($sref) eq "SCALAR") {
261 0         0 die("Programmer error. clean_format requires a SCALAR ref, found: ".ref($sref));
262             }
263 9         12 $$sref =~ s!\x00!!mg; # remove nulls
264             #unless ($trust) {
265             #$$sref =~ s!\`!'!mg;
266             #$$sref =~ s!\$\(!\(!mg;
267             #$$sref =~ s!\$!\\\$!mg;
268             #}
269             #$$sref =~ s!"!\\"!mg; # escape double-quotes
270 9         19 return $sref;
271             }
272              
273             sub trust_quote {
274 285     285 0 270 my ($self,$kv,$string) = @_;
275 285 100       330 if ($kv->{trust}) {
276 25         38 return '"'.$string.'"';
277             }
278 260         359 return shell_quote($string);
279             }
280              
281             #: Given a command string "format" and any key/value replacement pairs,
282             #: construct the exec'able command string.
283             sub prepare_command {
284 11     11 0 9 my $self = shift(@_);
285 11         5 my $args = shift(@_);
286 11         12 my $format = shift(@_);
287 11         19 my (%rpl_add) = @_;
288 11         10 my %rpl = ();
289 11         8 foreach my $key (keys %{$args}) {
  11         45  
290 276   100     539 $rpl{$key} = $self->make_kvl($args,$args->{$key}||'');
291             }
292 11         25 foreach my $key (keys %rpl_add) {
293 13         23 $rpl{$key} = $rpl_add{$key};
294             }
295 11         30 foreach my $key (keys %rpl) {
296 276   100     574 my $value = $rpl{$key}->{value}||'';
297 276 100       411 if (ref($value) eq "ARRAY") {
    50          
298             #: menu, checklist, radiolist...
299 3         3 my $list = '';
300 3         3 foreach my $item (@{$value}) {
  3         4  
301 12 100       104 if (ref($item) eq "ARRAY") {
302             # checklist, radiolist...
303 4 50       5 if (@{$item} == 2) {
  4 0       6  
    0          
304 4         8 $list .= ' '.$self->trust_quote($rpl{$key},$item->[0]);
305 4 100       55 $list .= ' '.($item->[1] ? 'on' : 'off');
306 4         4 next;
307             }
308 0         0 elsif (@{$item} == 3) {
309 0         0 $list .= ' '.$self->trust_quote($rpl{$key},$item->[0]);
310 0 0       0 $list .= ' '.($item->[1] ? 'on' : 'off');
311 0   0     0 $list .= ' '.($self->trust_quote($rpl{$key},$item->[2])||1);
312 0         0 next;
313             }
314 0         0 elsif (@{$item} == 4) {
315 0         0 $list .= ' ' . $self->trust_quote($rpl{$key},$item->[0]);
316 0 0       0 $list .= ' '.($item->[1] ? 'on' : 'off');
317 0   0     0 $list .= ' '.($self->trust_quote($rpl{$key},$item->[2])||1);
318 0         0 $list .= ' '.$self->trust_quote($rpl{$key},$item->[3]);
319 0         0 next;
320             }
321             }
322             # menu...
323 8         11 $list .= ' '.$self->trust_quote($rpl{$key},$item);
324             }
325 3         29 $format =~ s!\{\{\Q${key}\E\}\}!${list}!mg;
326             } # if (ref($value) eq "ARRAY")
327             elsif ($key eq "list") {
328             # assume this has been manipulated already?
329 0         0 $format =~ s!\{\{\Q${key}\E\}\}!${value}!mg;
330             }
331             else {
332 273 50 0     305 $value ||= '' unless defined $value;
333 273 100       482 $value = "$1" if $value =~ m!^(\d+)$!;
334 273 50       365 if (ref(\$value) eq "SCALAR") {
335 273 100 66     504 unless ($rpl{$key}->{'trust'}||$rpl{$key}->{literal}) {
336             $value = $self->_organize_text
337 9         14 ( $value, $rpl{$key}->{width}, $rpl{$key}->{'trust'} );
338             }
339 273         319 $value = $self->trust_quote($rpl{$key},$value);
340 273         4800 $format =~ s!\{\{\Q${key}\E\}\}!${value}!mg;
341             }
342             }
343             }
344 11         110 return $format;
345             }
346              
347              
348             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
349             #: Execution Methods
350             #:
351              
352             sub is_unit_test_mode {
353 11     11 0 10 my ($self) = @_;
354             return 1
355             if ( exists $self->{'test_mode'}
356             &&
357             defined $self->{'test_mode'}
358             &&
359 11 50 33     62 $self->{'test_mode'}
      33        
360             );
361 0         0 return 0;
362             }
363             sub get_unit_test_result {
364 11     11 0 16 my ($self) = @_;
365 11         46 return $self->{'test_mode_result'};
366             }
367              
368             #: run command and return the rv and any text output from stderr
369             sub perform_command {
370 0     0 0 0 my $self = $_[0];
371 0         0 my $cmnd = $_[1];
372 0 0       0 if ($self->is_unit_test_mode()) {
373 0         0 $self->{'test_mode_result'} = $cmnd;
374 0         0 return (0,'test_mode_result');
375             }
376 0         0 $self->_debug("perform_command: ".$cmnd.";");
377 0         0 my $tmp_stderr = $self->gen_tempfile_name();
378 0         0 system($cmnd." 2> ".$tmp_stderr);
379 0         0 my $rv = $? >> 8;
380 0         0 my $text = read_file($tmp_stderr);
381 0 0       0 unlink($tmp_stderr) if -f $tmp_stderr;
382 0         0 $self->_debug("perform_command: stderr=".shell_quote($text),2);
383 0         0 return ($rv,$text);
384             }
385              
386             #: execute a simple command (return the exit code only);
387             sub command_state {
388 5     5 1 5 my $self = $_[0];
389 5         4 my $cmnd = $_[1];
390 5 50       9 if ($self->is_unit_test_mode()) {
391 5         7 $self->{'test_mode_result'} = $cmnd;
392 5         7 return 0;
393             }
394 0         0 my ($rv,$text) = $self->perform_command($cmnd);
395 0         0 $self->_debug("command_state: rv=".$rv,1);
396 0         0 $self->rv($rv);
397 0         0 $self->rs('null');
398 0         0 $self->ra('null');
399 0         0 return($rv);
400             }
401              
402             #: execute a command and return the exit code and one-line SCALAR
403             sub command_string {
404 5     5 1 6 my $self = $_[0];
405 5         4 my $cmnd = $_[1];
406 5 50       7 if ($self->is_unit_test_mode()) {
407 5         7 $self->{'test_mode_result'} = $cmnd;
408 5 50       19 return (wantarray) ? (0,'') : '';
409             }
410 0         0 my ($rv,$text) = $self->perform_command($cmnd);
411 0         0 chomp($text);
412 0         0 $self->_debug("command_string: rv=".$rv.", rs=".shell_quote($text),1);
413 0         0 $self->rv($rv);
414 0         0 $self->rs($text);
415 0         0 $self->ra('null');
416 0 0       0 return($text) unless defined wantarray;
417 0 0       0 return (wantarray) ? ($rv,$text) : $text;
418             }
419              
420             #: execute a command and return the exit code and ARRAY of data
421             sub command_array {
422 1     1 1 2 my $self = $_[0];
423 1         1 my $cmnd = $_[1];
424 1 50       2 if ($self->is_unit_test_mode()) {
425 1         2 $self->{'test_mode_result'} = $cmnd;
426 1 50       5 return (wantarray) ? (0,[]) : [];
427             }
428 0         0 my ($rv,$text) = $self->perform_command($cmnd);
429 0         0 $self->_debug("command_array: rv=".$rv.", rs=".shell_quote($text),1);
430 0         0 $self->rv($rv);
431 0         0 $self->rs($text);
432             # this is so hackish that it may just work
433 0         0 my $alt_text = $text;
434 0         0 $alt_text =~ s!\r??\n!__\\n!mg; #: replace newlines with "a symbol"
435 0         0 my @alt_items = split(/_\\n/,$alt_text); #: split on "part symbol"
436 0         0 my @alt_final = ();
437 0         0 foreach my $alt_item (@alt_items) {
438 0         0 my $i = $alt_item;
439 0         0 $i =~ s!_$!!; #: remove the trailing bit of symbol
440 0         0 push(@alt_final,$i);
441             }
442 0         0 $self->ra(@alt_final); #: final array can now contain blanks
443 0 0 0     0 return([$self->ra()]) unless defined wantarray and wantarray;
444 0 0       0 return (wantarray) ? ($rv,[$self->ra()]) : [$self->ra()];
445             }
446              
447             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
448             #: Utility Methods
449             #:
450              
451             #: make some noise
452             sub beep {
453 0     0 1 0 my $self = $_[0];
454 0         0 return($self->_beep(1));
455             }
456              
457             #: Clear terminal screen.
458             sub clear {
459 0     0 1 0 my $self = $_[0];
460 0         0 return($self->_clear(1));
461             }
462              
463             # word-wrap a line
464             sub word_wrap {
465 9     9 1 10 my $self = shift();
466 9   50     10 my $width = shift() || 65;
467 9   50     48 my $indent = shift() || "";
468 9   50     22 my $sub_indent = shift() || "";
469 9         9 $Text::Wrap::columns = $width - 3;
470 9         24 my $raw = join("\n",@_);
471 9         16 my $string = wrap($indent, $sub_indent, $raw);
472 9         858 return(split(m!\n!,$string));
473             }
474              
475             # generate a temporary file name
476             sub gen_tempfile_name {
477 0     0 1 0 my $self = $_[0];
478 0   0     0 my $template = $self->{'_opts'}->{'tempfiletemplate'} || "UI_Dialog_tempfile_XXXXX";
479 0 0       0 if (eval("require File::Temp; 1")) {
480 16     16   10541 use File::Temp qw( tempfile );
  16         151209  
  16         37325  
481 0 0       0 my ($fh,$filename) = tempfile( UNLINK => 1 ) or croak( "Can't create tempfile: $!" );
482 0 0       0 if (wantarray) {
483 0         0 return($fh,$filename);
484             }
485             else {
486 0         0 close($fh); # actually required on win32
487 0         0 return($filename);
488             }
489 0         0 return($fh,$filename);
490             }
491             else {
492 0         0 my $mktemp = $self->_find_bin('mktemp');
493 0 0 0     0 if ($mktemp && -x $mktemp) {
494 0         0 chomp(my $tempfile = `$mktemp "$template"`);
495 0         0 return($tempfile);
496             }
497             else {
498             #pseudo-random filename coming up!
499 0         0 my $tempdir = "/tmp";
500 0 0       0 unless (-d $tempdir) {
501 0 0       0 if (-d "/var/tmp") {
502 0         0 $tempdir = "/var/tmp";
503             }
504             else {
505 0         0 $tempdir = ".";
506             }
507             }
508 0         0 $self->gen_random_string(5);
509 0         0 my $tempfile = "UI_Dialog_tempfile_".$self->gen_random_string(5);
510 0         0 while (-e $tempdir."/".$tempfile) {
511 0         0 $self->gen_random_string(5);
512 0         0 $tempfile = "UI_Dialog_tempfile_".$self->gen_random_string(5);
513             }
514 0         0 return($tempdir."/".$tempfile);
515             }
516             }
517             }
518              
519             # generate a random string as a (possibly) suitable failover option in the
520             # event that File::Temp is not installed and the 'mktemp' program does not
521             # exist in the path.
522             sub gen_random_string {
523 0     0 1 0 my $self = $_[0];
524 0   0     0 my $length = $_[1] || 5;
525 0         0 my $string = "";
526 0         0 my $counter = 0;
527 0         0 while ($counter < $length) {
528             # 33 - 127
529 0         0 my $num = rand(128);
530 0   0     0 while ($num < 33 or $num > 127) {
531 0         0 $num = rand(128);
532             }
533 0         0 $string .= chr($num);
534 0         0 $counter++;
535             }
536 0         0 return($string);
537             }
538              
539             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
540             #: Widget Wrapping Methods
541             #:
542              
543             #:+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
544             #: file select
545             sub fselect {
546 0     0 1 0 my $self = shift();
547 0   0     0 my $caller = (caller(1))[3] || 'main';
548 0 0 0     0 $caller = ($caller =~ /^UI\:\:Dialog\:\:Backend\:\:/) ? ((caller(2))[3]||'main') : $caller;
549 0 0 0     0 if ($_[0] && $_[0] eq 'caller') {
550 0         0 shift(); $caller = shift();
  0         0  
551             }
552 0         0 my $args = $self->_pre($caller,@_);
553              
554 0         0 $self->rv('NULL');
555 0         0 $self->rs('NULL');
556 0         0 $self->ra('NULL');
557              
558 0         0 $self->_beep($args->{'beepbefore'});
559              
560 0         0 my $cwd = abs_path();
561 0   0     0 $args->{'path'} ||= abs_path();
562 0         0 my $pre_selection = $args->{'path'};
563 0         0 my $path = $args->{'path'};
564 0 0       0 if (-f $pre_selection) {
565 0         0 $path = dirname($path);
566             }
567 0 0 0     0 if (!$path || $path =~ /^(\.|\.\/)$/) {
568 0         0 $path = $cwd;
569             }
570 0         0 my $user_selection = $pre_selection;
571 0         0 my ($menu,$list) = ([],[]);
572 0   0     0 FSEL: while ($self->state() ne "ESC" && $self->state() ne "CANCEL") {
573 0 0       0 my $entries = ($args->{'dselect'}) ? ['[new directory]'] : ['[new file]'];
574 0         0 ($menu, $list) = $self->_list_dir($path, $entries, $args->{'dselect'});
575             $user_selection = $self->menu
576             ( height=>$args->{'height'},
577             width=>$args->{'width'},
578             listheight=>($args->{'listheight'}||$args->{'menuheight'}),
579             title=>$args->{'title'},
580             backtitle=>$args->{'backtitle'},
581 0 0 0     0 text=>"Select a ".($args->{'dselect'}?'path':'file').": ".$path,
582             list=>$menu
583             );
584 0 0       0 if ($self->state() eq "CANCEL") {
    0          
585 0         0 $self->rv(1);
586 0         0 $self->rs('NULL');
587 0         0 $self->ra('NULL');
588 0         0 last FSEL;
589             }
590             elsif ($user_selection ne "") {
591 0 0 0     0 if ($list->[($user_selection - 1 || 0)] =~ /^\[(new\sdirectory|new\sfile)\]$/) {
    0 0        
    0 0        
    0 0        
    0 0        
592 0         0 my $nfn;
593 0   0     0 while (!$nfn || -e $path."/".$nfn) {
594             $nfn = $self->inputbox
595             ( height=>$args->{'height'},
596             width=>$args->{'width'},
597 0         0 title=>$args->{'title'},
598             text=>'Enter a name (will have a base directory of: '.$path.')'
599             );
600 0 0 0     0 next FSEL if $self->state() eq "ESC" or $self->state() eq "CANCEL";
601 0 0       0 if (-e $path."/".$nfn) {
602 0         0 $self->msgbox
603             ( title=>'error',
604             text=>$path."/".$nfn.' exists. Choose another name please.');
605             }
606             }
607 0         0 $user_selection = $path."/".$nfn;
608 0 0       0 $user_selection =~ s!/$!! unless $user_selection =~ m!^/$!;
609 0         0 $user_selection =~ s!/\./!/!g; $user_selection =~ s!/+!/!g;
  0         0  
610 0         0 last FSEL;
611             }
612             elsif ($list->[($user_selection - 1 || 0)] eq "../") {
613 0         0 $path = dirname($path);
614             }
615             elsif ($list->[($user_selection - 1 || 0)] eq "./") {
616 0         0 $user_selection = $path;
617 0 0       0 $user_selection =~ s!/$!! unless $user_selection =~ m!^/$!;
618 0         0 $user_selection =~ s!/\./!/!g; $user_selection =~ s!/+!/!g;
  0         0  
619 0         0 last FSEL;
620             }
621             elsif (-d $path."/".$list->[($user_selection - 1 || 0)]) {
622 0   0     0 $path = $path."/".$list->[($user_selection - 1 || 0)];
623             }
624             elsif (-e $path."/".$list->[($user_selection - 1 || 0)]) {
625 0   0     0 $user_selection = $path."/".$list->[($user_selection - 1 || 0)];
626 0 0       0 $user_selection =~ s!/$!! unless $user_selection =~ m!^/$!;
627 0         0 $user_selection =~ s!/\./!/!g; $user_selection =~ s!/+!/!g;
  0         0  
628 0         0 last FSEL;
629             }
630             }
631 0         0 $user_selection = undef();
632 0         0 $path =~ s!(/*)!/!; $path =~ s!/\./!/!g;
  0         0  
633             }
634 0         0 $self->_beep($args->{'beepafter'});
635 0         0 my $rv = $self->rv();
636 0         0 $self->ra('NULL');
637 0 0 0     0 if ($rv && $rv >= 1) {
638 0         0 $self->rs('NULL');
639 0         0 return(0);
640             }
641             else {
642 0         0 $self->rs($user_selection);
643 0         0 return($user_selection);
644             }
645             }
646              
647             #:+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
648             #: directory selection
649             sub dselect {
650 0     0 1 0 my $self = shift();
651 0   0     0 my $caller = (caller(1))[3] || 'main';
652 0 0 0     0 $caller = ($caller =~ /^UI\:\:Dialog\:\:Backend\:\:/) ? ((caller(2))[3]||'main') : $caller;
653 0 0 0     0 if ($_[0] && $_[0] eq 'caller') {
654 0         0 shift(); $caller = shift();
  0         0  
655             }
656 0         0 my $args = $self->_pre($caller,@_);
657 0         0 my $dirname;
658 0         0 $self->rv('NULL');
659 0         0 $self->rs('NULL');
660 0         0 $self->ra('NULL');
661 0   0     0 while (not $dirname && $self->state() !~ /^(CANCEL|ESC|ERROR)$/) {
662 0         0 $dirname = $self->fselect(@_,'dselect',1);
663 0 0       0 if ($self->state() =~ /^(CANCEL|ESC|ERROR)$/) {
664 0         0 return(0);
665             }
666 0 0       0 unless (not $dirname) {
667             # if it's a directory or not exist (assume new dir)
668 0 0 0     0 unless (-d $dirname || not -e $dirname) {
669 0         0 $self->msgbox( text => $dirname . " is not a directory.\nPlease select a directory." );
670 0         0 $dirname = undef();
671             }
672             }
673             }
674 0   0     0 return($dirname||'');
675             }
676              
677              
678             #::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
679             #: Backend Methods
680             #:
681              
682             sub _pre {
683 11     11   13 my $self = shift();
684 11         9 my $caller = shift();
685 11         20 my $args = $self->_merge_attrs(@_);
686 11         11 $args->{'caller'} = $caller;
687 11         13 my $class = ref($self);
688              
689 11         11 my $CODEREFS = $args->{'callbacks'};
690 11 50       18 if (ref($CODEREFS) eq "HASH") {
691 0         0 my $PRECODE = $CODEREFS->{'PRE'};
692 0 0       0 if (ref($PRECODE) eq "CODE") {
693 0         0 &$PRECODE($args,$self->state());
694             }
695             }
696              
697 11         22 $self->_beep($args->{'beepbefore'});
698 11         20 $self->_clear($args->{'clearbefore'});
699 11         21 return($args);
700             }
701              
702             sub _post {
703 9     9   10 my $self = shift();
704 9   50     16 my $args = shift() || {};
705 9         10 my $class = ref($self);
706              
707 9         18 $self->_beep($args->{'beepafter'});
708 9         13 $self->_clear($args->{'clearafter'});
709              
710 9         7 my $CODEREFS = $args->{'callbacks'};
711 9 50       16 if (ref($CODEREFS) eq "HASH") {
712 0         0 my $state = $self->state();
713 0 0       0 if ($state eq "OK") {
    0          
    0          
714 0         0 my $OKCODE = $CODEREFS->{'OK'};
715 0 0       0 if (ref($OKCODE) eq "CODE") {
716 0         0 &$OKCODE($args);
717             }
718             }
719             elsif ($state eq "ESC") {
720 0         0 my $ESCCODE = $CODEREFS->{'ESC'};
721 0 0       0 if (ref($ESCCODE) eq "CODE") {
722 0         0 &$ESCCODE($args);
723             }
724             }
725             elsif ($state eq "CANCEL") {
726 0         0 my $CANCELCODE = $CODEREFS->{'CANCEL'};
727 0 0       0 if (ref($CANCELCODE) eq "CODE") {
728 0         0 &$CANCELCODE($args);
729             }
730             }
731 0         0 my $POSTCODE = $CODEREFS->{'POST'};
732 0 0       0 if (ref($POSTCODE) eq "CODE") {
733 0         0 &$POSTCODE($args,$state);
734             }
735             }
736              
737 9         12 return(1);
738             }
739              
740              
741             #: indent and organize the text argument
742             sub _organize_text {
743 9     9   58 my $self = $_[0];
744 9         9 my $text = $_[1];
745 9   50     19 my $width = $_[2] || 65;
746 9 50 33     32 my $trust = (exists $_[3] && defined $_[3]) ? $_[3] : '0';
747 9         9 $width -= 4; # take account of borders?
748 9         6 my @array;
749              
750 9 50       20 if (ref($text) eq "ARRAY") {
    50          
751 0         0 push(@array,@{$text});
  0         0  
752             }
753             elsif ($text =~ /\\n/) {
754 0         0 @array = split(/\\n/,$text);
755             }
756             else {
757 9         18 @array = split(/\n/,$text);
758             }
759 9         9 $text = undef;
760              
761 9         14 @array = $self->word_wrap($width,"","",@array);
762              
763 9 50       22 if ($self->{'scale'}) {
764 0         0 foreach my $line (@array) {
765 0         0 my $s_line = $line;#$self->__TRANSLATE_CLEAN($line);
766 0         0 $s_line =~ s!\[A\=\w+\]!!gi;
767             $self->{'width'} = length($s_line) + 5
768             if ($self->{'width'} - 5) < length($s_line)
769 0 0 0     0 && (length($s_line) <= $self->{'max-scale'});
770             }
771             }
772              
773 9         10 foreach my $line (@array) {
774 9         5 my $pad;
775 9         19 $self->clean_format( $trust, \$line );
776 9         24 my $s_line = $self->_strip_text($line);
777 9 50       17 if ($line =~ /\[A\=(\w+)\]/i) {
778 0         0 my $align = $1;
779 0         0 $line =~ s!\[A\=\w+\]!!gi;
780 0 0 0     0 if (uc($align) eq "CENTER" || uc($align) eq "C") {
    0 0        
    0 0        
781 0         0 $pad = ((($self->{'_opts'}->{'width'} - 5) - length($s_line)) / 2);
782             }
783             elsif (uc($align) eq "LEFT" || uc($align) eq "L") {
784 0         0 $pad = 0;
785             }
786             elsif (uc($align) eq "RIGHT" || uc($align) eq "R") {
787 0         0 $pad = (($self->{'_opts'}->{'width'} - 5) - length($s_line));
788             }
789             }
790 9 50       12 if ($pad) {
791 0         0 $text .= (" " x $pad).$line."\n";
792             }
793             else {
794 9         12 $text .= $line."\n";
795             }
796             }
797 9         12 $text = $self->_strip_text($text);
798 9 50       15 chomp($text) if $text;
799 9         14 return($text);
800             }
801              
802             #: merge the arguments with the default attributes, and arguments override defaults.
803             sub _merge_attrs {
804 11     11   11 my $self = shift();
805 11 50       39 my $args = (@_ % 2) ? { @_, '_odd' } : { @_ };
806 11         13 my $defs = $self->{'_opts'};
807              
808 11         39 foreach my $def (keys(%$defs)) {
809             # default unless exists
810 209 100       291 $args->{$def} = $defs->{$def} unless exists $args->{$def};
811             }
812              
813             # alias 'filename' and 'file' to path
814             $args->{'path'} = (($args->{'filename'}) ? $args->{'filename'} :
815             ($args->{'file'}) ? $args->{'file'} :
816 11 100       39 ($args->{'path'}) ? $args->{'path'} : "");
    50          
    50          
817              
818 11   50     51 $args->{'clear'} = $args->{'clearbefore'} || $args->{'clearafter'} || $args->{'autoclear'} || 0;
819 11   50     46 $args->{'beep'} = $args->{'beepbefore'} || $args->{'beepafter'} || $args->{'autobeep'} || 0;
820 11         14 return($args);
821             }
822              
823             #: search through the given paths for a specific variant
824             sub _find_bin {
825 43     43   48 my $self = $_[0];
826 43         44 my $variant = $_[1];
827             $self->{'PATHS'} = ((ref($self->{'PATHS'}) eq "ARRAY") ? $self->{'PATHS'} :
828 43 50       111 ($self->{'PATHS'}) ? [ $self->{'PATHS'} ] :
    100          
829             [ '/bin', '/usr/bin', '/usr/local/bin', '/opt/bin' ]);
830 43         45 foreach my $PATH (@{$self->{'PATHS'}}) {
  43         79  
831 270 100       6943790 return($PATH . '/' . $variant)
832             unless not -x $PATH . '/' . $variant;
833             }
834 26         152 return(0);
835             }
836              
837             #: clean the text arguments of all colour codes, alignments and attributes.
838             sub _strip_text {
839 18     18   13 my $self = $_[0];
840 18         302 my $text = $_[1];
841 18   50     23 $text ||= '';
842 18         27 $text =~ s!\\Z[0-7bBuUrRn]!!gmi;
843 18         6 $text =~ s!\[[AC]=\w+\]!!gmi;
844 18         14 $text =~ s!\[/?[BURN]\]!!gmi;
845 18         22 return($text);
846             }
847              
848             #: is this a BSD system?
849             sub _is_bsd {
850 0     0   0 my $self = shift();
851 0 0       0 return(1) if $^O =~ /bsd/i;
852 0         0 return(0);
853             }
854              
855             #: gather a list of the contents of a directory and return it in
856             #: two forms, one is the "simple" list of all the filenames and the
857             #: other is a 'menu' list corresponding to the simple list.
858             sub _list_dir {
859 0     0   0 my $self = shift();
860 0   0     0 my $path = shift() || return();
861 0         0 my $pref = shift();
862 0 0 0     0 my $paths_only = (@_ == 1 && $_[0] == 1) ? 1 : 0;
863 0         0 my (@listing,@list);
864 0 0       0 if (opendir(GETDIR,$path)) {
865 0         0 my @dir_data = readdir(GETDIR);
866 0         0 closedir(GETDIR);
867 0 0       0 if ($pref) {
868 0         0 push(@listing,@{$pref});
  0         0  
869             }
870 0         0 foreach my $dir (sort(grep { -d $path."/".$_ } @dir_data)) {
  0         0  
871 0         0 push(@listing,$dir."/");
872             }
873 0 0       0 unless ($paths_only) {
874 0         0 foreach my $item (sort(grep { !-d $path."/".$_ } @dir_data)) {
  0         0  
875 0         0 push(@listing,$item);
876             }
877             }
878 0         0 my $c = 1;
879 0         0 foreach my $item (@listing) {
880 0         0 push(@list,"$c",$item); $c++;
  0         0  
881             }
882 0         0 return(\@list,\@listing);
883             }
884             else {
885 0         0 return("failed to read directory: ".$path);
886             }
887             }
888              
889             sub _debug {
890 0     0   0 my $self = $_[0];
891 0   0     0 my $mesg = $_[1] || 'null debug message given!';
892 0   0     0 my $rate = $_[2] || 1;
893 0 0 0     0 return() unless $self->{'_opts'}->{'debug'} and $self->{'_opts'}->{'debug'} >= $rate;
894 0         0 chomp($mesg);
895 0         0 print STDERR "Debug: ".$mesg."\n";
896             }
897             sub _error {
898 0     0   0 my $self = $_[0];
899 0   0     0 my $mesg = $_[1] || 'null error message given!';
900 0         0 chomp($mesg);
901 0         0 print STDERR "Error: ".$mesg."\n";
902             }
903              
904             #: really make some noise
905             sub _beep {
906 20     20   19 my $self = $_[0];
907 20         13 my $beep = $_[1];
908 20 50       31 unless (not $beep) {
909 0 0       0 if (-x $self->{'_opts'}->{'beepbin'}) {
910 0         0 return(eval { system($self->{'_opts'}->{'beepbin'}); 1; });
  0         0  
  0         0  
911             }
912             else {
913 0 0 0     0 return (1) unless $ENV{'TERM'} && $ENV{'TERM'} ne "dumb";
914 0         0 print STDERR "\a";
915             }
916             }
917 20         20 return(1);
918             }
919              
920             #: The actual clear action.
921             sub _clear {
922 20     20   16 my $self = $_[0];
923 20   50     43 my $clear = $_[1] || 0;
924             # Useless with GUI based variants so we return here.
925             # Is the use of the "dumb" TERM appropriate? need feedback.
926 20 50 33     67 return (1) unless $ENV{'TERM'} && $ENV{'TERM'} ne "dumb";
927 20 50 33     62 unless (not $clear and not $self->{'_opts'}->{'autoclear'}) {
928 0   0     0 $self->{'_clear'} ||= `clear`;
929 0         0 print STDOUT $self->{'_clear'};
930             }
931 20         17 return(1);
932             }
933              
934              
935              
936             1;