File Coverage

blib/lib/Term/Clui.pm
Criterion Covered Total %
statement 9 1110 0.8
branch 2 594 0.3
condition 0 183 0.0
subroutine 4 59 6.7
pod 14 55 25.4
total 29 2001 1.4


line stmt bran cond sub pod time code
1             # Term::Clui.pm
2             #########################################################################
3             # This Perl module is Copyright (c) 2002, Peter J Billam #
4             # c/o P J B Computing, www.pjb.com.au #
5             # #
6             # This module is free software; you can redistribute it and/or #
7             # modify it under the same terms as Perl itself. #
8             #########################################################################
9              
10             package Term::Clui;
11             our $VERSION = '1.74_01';
12             $VERSION =~ tr/_//d;
13             my $stupid_bloody_warning = $VERSION; # circumvent -w warning
14             require Exporter;
15             @ISA = qw(Exporter);
16             @EXPORT = qw(ask ask_password ask_filename confirm
17             choose help_text edit sorry view inform);
18             @EXPORT_OK = qw(beep tiview back_up get_default set_default timestamp);
19             %EXPORT_TAGS = (ALL => [@EXPORT,@EXPORT_OK]);
20              
21 1     1   662 use 5.006;
  1         3  
22 1     1   4 no strict; no warnings;
  1     1   1  
  1         17  
  1         4  
  1         1  
  1         9607  
23              
24             my $have_Term_ReadKey = 1;
25             my $have_Term_Size = 0;
26             eval 'require "Term/ReadKey.pm"';
27             if ($@) {
28             $have_Term_ReadKey = 0;
29             $have_Term_Size = 1;
30             eval 'require "Term/Size.pm"';
31             if ($@) { $have_Term_Size = 0; }
32             }
33              
34             my $Eflite;
35             my $Eflite_FH; # open here at top-level so one sub can silence the previous
36             my $Espeak;
37             my $Espeak_PID; # defined at top-level so one espeak can kill the previous
38             my $SpeakUpSilentFile; # 1.62
39             if ($ENV{'CLUI_SPEAK'}) { # 1.62 emacspeak not very relevant as a criterion
40             for my $d ('/sys/accessibility', '/proc') {
41             if (-w "$d/speakup/silent") {
42             $SpeakUpSilentFile = "$d/speakup/silent"; break;
43             }
44             }
45             $Eflite = &which('eflite');
46             $Espeak = &which('espeak');
47             if ($Eflite && !$Espeak) { # 1.68 Espeak should be the default
48             if (open($Eflite_FH,'|-',$Eflite)) {
49             select((select($Eflite_FH), $| = 1)[0]); print $Eflite_FH q{};
50             } else {
51             warn "can't run $Eflite: $!\n";
52             }
53             } elsif (! $Espeak) {
54             warn("Term::Clui warning: CLUI_SPEAK set; "
55             . "but can't find eflite or espeak\n");
56             }
57             }
58              
59              
60             # use open ':locale'; # the open pragma was introduced in 5.8.6
61             my $EncodingString = q{};
62             if (($ENV{LANG} =~ /utf-?8/i) || ($ENV{LC_TYPE} =~ /utf-?8/i)) {
63             $EncodingString = ':encoding(utf8)';
64             }
65              
66             # ------------------------ vt100 stuff -------------------------
67              
68             $A_NORMAL = 0;
69             $A_BOLD = 1;
70             $A_UNDERLINE = 2;
71             $A_REVERSE = 4;
72             $KEY_UP = 0403;
73             $KEY_LEFT = 0404;
74             $KEY_RIGHT = 0405;
75             $KEY_DOWN = 0402;
76             $KEY_ENTER = "\r";
77             $KEY_INSERT = 0525;
78             $KEY_DELETE = 0524;
79             $KEY_HOME = 0523;
80             $KEY_END = 0522;
81             $KEY_PPAGE = 0521;
82             $KEY_NPAGE = 0520;
83             $KEY_BTAB = 0541;
84             my $AbsCursX = 0; my $AbsCursY = 0; my $TopRow = 0; my $CursorRow;
85             my $LastEventWasPress = 0; # in order to ignore left-over button-ups
86             my %SpecialKey = map { $_, 1 } ( # 1.51, used by ask to ignore these
87             $KEY_UP, $KEY_LEFT, $KEY_RIGHT, $KEY_DOWN, $KEY_HOME, $KEY_END,
88             $KEY_PPAGE, $KEY_NPAGE, $KEY_BTAB, $KEY_INSERT, $KEY_DELETE
89             );
90              
91             my $irow; my $icol; # maintained by &puts, &up, &down, &left and &right
92 0     0 0   sub puts { my $s = join q{}, @_;
93 0           $irow += ($s =~ tr/\n/\n/);
94 0 0         if ($s =~ /\r\n?$/) { $icol = 0;
  0            
95 0           } else { $icol += length($s);
96             }
97 0           print TTY $s;
98             }
99             # could terminfo sgr0, bold, rev, cub1, cuu1, cuf1, cud1 ...
100 0     0 0   sub attrset { my $attr = $_[0];
101 0 0         if (! $attr) {
102 0           print TTY "\e[0m";
103             } else {
104 0 0         if ($attr & $A_BOLD) { print TTY "\e[1m" };
  0            
105 0 0         if ($attr & $A_REVERSE) { print TTY "\e[7m" };
  0            
106 0 0         if ($attr & $A_UNDERLINE) { print TTY "\e[4m" };
  0            
107             }
108             }
109 0     0 1   sub beep { print TTY "\07"; }
110 0     0 0   sub clear { print TTY "\e[H\e[J"; }
111 0     0 0   sub clrtoeol { print TTY "\e[K"; }
112 0     0 0   sub black { print TTY "\e[30m"; }
113 0     0 0   sub red { print TTY "\e[31m"; }
114 0     0 0   sub green { print TTY "\e[32m"; }
115 0     0 0   sub blue { print TTY "\e[34m"; }
116 0     0 0   sub violet { print TTY "\e[35m"; }
117              
118 0     0 0   sub getc_wrapper { my $timeout = 0 + $_[0];
119 0 0         if ($have_Term_ReadKey) {
120 0           return Term::ReadKey::ReadKey($timeout, *TTYIN);
121             } else {
122             #if ($timeout > 0.00001) { # doesn't seem to work on openbsd...
123             # my $rin = q{};
124             # vec($rin,fileno(TTYIN),1) = 1;
125             # my $nfound = select($rin, undef, undef, $timeout);
126             # if (!$nfound) { return undef; }
127             #}
128 0           return getc(TTYIN);
129             }
130             }
131              
132             sub getch {
133 0     0 0   my $c = getc_wrapper(0);
134 0 0         if ($c eq "\e") {
135 0           $c = getc_wrapper(0.10);
136              
137 0 0         if (! defined $c) { return("\e"); }
  0            
138 0 0         if ($c eq 'A') { return($KEY_UP); }
  0            
139 0 0         if ($c eq 'B') { return($KEY_DOWN); }
  0            
140 0 0         if ($c eq 'C') { return($KEY_RIGHT); }
  0            
141 0 0         if ($c eq 'D') { return($KEY_LEFT); }
  0            
142 0 0         if ($c eq '2') { getc_wrapper(0); return($KEY_INSERT); }
  0            
  0            
143 0 0         if ($c eq '3') { getc_wrapper(0); return($KEY_DELETE); } # 1.54
  0            
  0            
144 0 0         if ($c eq '5') { getc_wrapper(0); return($KEY_PPAGE); }
  0            
  0            
145 0 0         if ($c eq '6') { getc_wrapper(0); return($KEY_NPAGE); }
  0            
  0            
146 0 0         if ($c eq 'Z') { return($KEY_BTAB); }
  0            
147 0 0         if ($c eq 'O') { # 1.68 Haiku wierdness, inherited from an old Suse
148 0           $c = getc_wrapper(0);
149 0 0         if ($c eq 'A') { return($KEY_UP); } # 1.68
  0            
150 0 0         if ($c eq 'B') { return($KEY_DOWN); } # 1.68
  0            
151 0 0         if ($c eq 'C') { return($KEY_RIGHT); } # 1.68
  0            
152 0 0         if ($c eq 'D') { return($KEY_LEFT); } # 1.68
  0            
153 0 0         if ($c eq 'F') { return($KEY_END); } # 1.68
  0            
154 0 0         if ($c eq 'H') { return($KEY_HOME); } # 1.68
  0            
155 0           return($c);
156             }
157 0 0         if ($c eq '[') {
158 0           $c = getc_wrapper(0);
159 0 0         if ($c eq 'A') { return($KEY_UP); }
  0            
160 0 0         if ($c eq 'B') { return($KEY_DOWN); }
  0            
161 0 0         if ($c eq 'C') { return($KEY_RIGHT); }
  0            
162 0 0         if ($c eq 'D') { return($KEY_LEFT); }
  0            
163 0 0         if ($c eq 'F') { return($KEY_END); } # 1.67
  0            
164 0 0         if ($c eq 'H') { return($KEY_HOME); } # 1.67
  0            
165 0 0         if ($c eq 'M') { # mouse report - we must be in BYTES !
166             # http://invisible-island.net/xterm/ctlseqs/ctlseqs.html
167 0           my $event_type = ord(getc_wrapper(0))-32;
168 0           my $x = ord(getc_wrapper(0))-32;
169 0           my $y = ord(getc_wrapper(0))-32;
170             # my $shift = $event_type & 0x04; # used by wm
171             # my $meta = $event_type & 0x08; # used by wm
172             # my $control = $event_type & 0x10; # used by xterm
173 0           my $button_drag = ($event_type & 0x20) >> 5;
174 0           my $button_pressed;
175 0           my $low3bits = $event_type & 0x03;
176 0 0         if ($low3bits == 0x03) {
177 0           $button_pressed = 0;
178             } else { # button 4 means wheel-up, button 5 means wheel-down
179 0 0         if ($event_type & 0x40) { $button_pressed = $low3bits + 4;
  0            
180 0           } else { $button_pressed = $low3bits + 1;
181             }
182             }
183 0   0       return handle_mouse($x,$y,$button_pressed,$button_drag)
184             || getch();
185             }
186 0 0         if ($c =~ /\d/) { my $c1 = getc_wrapper(0);
  0            
187 0 0         if ($c1 eq '~') {
188 0 0         if ($c eq '2') { return($KEY_INSERT);
  0 0          
    0          
    0          
189 0           } elsif ($c eq '3') { return($KEY_DELETE);
190 0           } elsif ($c eq '5') { return($KEY_PPAGE);
191 0           } elsif ($c eq '6') { return($KEY_NPAGE);
192             }
193             } else { # cursor-position report, response to \e[6n
194 0           $AbsCursY = 0 + $c;
195 0           while (1) {
196 0 0         last if $c1 eq ';';
197 0           $AbsCursY = 10*$AbsCursY + $c1;
198             # debug("c1=$c1 AbsCursY=$AbsCursY");
199 0           $c1 = getc(TTYIN);
200             }
201 0           $AbsCursX = 0;
202 0           while (1) {
203 0           $c1 = getc(TTYIN);
204 0 0         last if $c1 eq 'R';
205 0           $AbsCursX = 10*$AbsCursX + $c1;
206             }
207 0           return getch();
208             }
209             }
210 0 0         if ($c eq 'Z') { return($KEY_BTAB); }
  0            
211 0           return($c);
212             }
213 0           return($c);
214             #} elsif ($c eq ord(0217)) { # 1.50 BUG what?? never gets here...
215             # $c = getc_wrapper(0);
216             # if ($c eq 'A') { return($KEY_UP); }
217             # if ($c eq 'B') { return($KEY_DOWN); }
218             # if ($c eq 'C') { return($KEY_RIGHT); }
219             # if ($c eq 'D') { return($KEY_LEFT); }
220             # return($c);
221             #} elsif ($c eq ord(0233)) { # 1.50 BUG what?? never gets here...
222             # $c = getc_wrapper(0);
223             # if ($c eq 'A') { return($KEY_UP); }
224             # if ($c eq 'B') { return($KEY_DOWN); }
225             # if ($c eq 'C') { return($KEY_RIGHT); }
226             # if ($c eq 'D') { return($KEY_LEFT); }
227             # if ($c eq '5') { getc_wrapper(0); return($KEY_PPAGE); }
228             # if ($c eq '6') { getc_wrapper(0); return($KEY_NPAGE); }
229             # if ($c eq 'Z') { return($KEY_BTAB); }
230             # return($c);
231             } else {
232 0           return($c);
233             }
234             }
235             sub up {
236             # if ($_[0] < 0) { &down(0 - $_[0]); return; }
237 0     0 0   print TTY "\e[A" x $_[0]; $irow -= $_[0];
  0            
238             }
239             sub down {
240             # if ($_[0] < 0) { &up(0 - $_[0]); return; }
241 0     0 0   print TTY "\n" x $_[0]; $irow += $_[0];
  0            
242             }
243             sub right {
244             # if ($_[0] < 0) { &left(0 - $_[0]); return; }
245 0     0 0   print TTY "\e[C" x $_[0]; $icol += $_[0];
  0            
246             }
247             sub left {
248             # if ($_[0] < 0) { &right(0 - $_[0]); return; }
249 0     0 0   print TTY "\e[D" x $_[0]; $icol -= $_[0];
  0            
250             }
251 0     0 0   sub goto { my $newcol = shift; my $newrow = shift;
  0            
252 0 0         if ($newcol == 0) { print TTY "\r" ; $icol = 0;
  0 0          
  0 0          
253 0           } elsif ($newcol > $icol) { &right($newcol-$icol);
254 0           } elsif ($newcol < $icol) { &left($icol-$newcol);
255             }
256 0 0         if ($newrow > $irow) { &down($newrow-$irow);
  0 0          
257 0           } elsif ($newrow < $irow) { &up($irow-$newrow);
258             }
259             }
260             # sub move { my ($ix,$iy) = @_; printf TTY "\e[%d;%dH",$iy+1,$ix+1; }
261              
262             my $InitscrAlreadyRun = 0;
263             my $IsMouseMode = 0;
264             my $WasMouseMode = 0;
265             my $IsSpeakUpSilent = 0; # 1.62
266             my $WasSpeakUpSilent = 0; # 1.62
267             my $Stty = q{};
268              
269             sub enter_mouse_mode { # 1.50
270 0 0   0 0   if ($ENV{'CLUI_MOUSE'} eq 'OFF') { return 0; } # 1.62
  0            
271 0 0         if ($IsMouseMode) {
272 0           warn "enter_mouse_mode but already IsMouseMode\r\n"; return 1 ;
  0            
273             }
274 0 0         if ($EncodingString) {
275 0           close TTYIN;
276 0 0         open(TTYIN, "<:bytes", '/dev/tty')
277             || (warn "Can't read /dev/tty: $!\n", return 0);
278             }
279 0           print TTY "\e[?1003h"; # sets SET_ANY_EVENT_MOUSE mode
280 0           $IsMouseMode = 1;
281 0           return 1;
282             }
283             sub leave_mouse_mode { # 1.50
284             # if ($ENV{'CLUI_MOUSE'} =~ /off/i) { return 0; } # 1.62
285 0 0   0 0   if (!$IsMouseMode) {
286 0           warn "leave_mouse_mode but not IsMouseMode\r\n"; return 1 ;
  0            
287             }
288 0 0         if ($EncodingString) {
289 0           close TTYIN;
290 0 0         open(TTYIN, "<$EncodingString", '/dev/tty')
291             || (warn "Can't read /dev/tty: $!\n", return 0);
292             }
293 0           print TTY "\e[?1003l"; # cancels SET_ANY_EVENT_MOUSE mode
294 0           $IsMouseMode = 0;
295 0           return 1;
296             }
297              
298             sub enter_speakup_silent { # 1.62
299             # echo 7 > /sys/accessibility/speakup/silent if it exists
300 0 0   0 0   if (!$SpeakUpSilentFile) { return 0; }
  0            
301 0 0         if ($IsSpeakUpSilent) {
302 0           warn "enter_speakup_silent but already IsSpeakUpSilent\r\n"; return 1 ;
  0            
303             }
304 0 0         if (open(S, '>', $SpeakUpSilentFile)) { print S "7\n"; close S; }
  0            
  0            
305 0           $IsSpeakUpSilent = 1;
306 0           return 1;
307             }
308             sub leave_speakup_silent { # 1.62
309             # echo 4 > /sys/accessibility/speakup/silent if it exists
310 0 0   0 0   if (!$SpeakUpSilentFile) { return 0; }
  0            
311 0 0         if (!$IsSpeakUpSilent) {
312 0           warn "leave_speakup_silent but not IsSpeakUpSilent\r\n"; return 1 ;
  0            
313             }
314 0 0         if (open(S, '>', $SpeakUpSilentFile)) { print S "4\n"; close S; }
  0            
  0            
315 0           $IsSpeakUpSilent = 0;
316 0           return 1;
317             }
318              
319 0     0 0   sub initscr { my %args = @_;
320 0           my $mouse_mode = $args{'mouse_mode'}; # for mouse-handling
321 0 0         if ($ENV{'CLUI_MOUSE'} eq 'OFF') { $mouse_mode = undef; } # 1.62
  0            
322 0           my $speakup_silent = $args{'speakup_silent'}; # to silence SpeakUp
323 0 0         if ($InitscrAlreadyRun) {
324 0           $InitscrAlreadyRun++;
325 0 0 0       if (!$mouse_mode and $IsMouseMode) {
    0 0        
326 0 0         leave_mouse_mode() or return 0;
327             } elsif ($mouse_mode and !$IsMouseMode) {
328 0 0         enter_mouse_mode() or return 0;
329             }
330 0           $WasMouseMode = $IsMouseMode;
331 0 0 0       if (!$speakup_silent and $IsSpeakUpSilent) { # 1.62
    0 0        
332 0 0         leave_speakup_silent() or return 0;
333             } elsif ($speakup_silent and !$IsSpeakUpSilent) {
334 0 0         enter_speakup_silent() or return 0;
335             }
336 0           $WasSpeakUpSilent = $IsSpeakUpSilent;
337 0           $icol = 0; $irow = 0;
  0            
338 0           return;
339             }
340 0 0         open(TTY, ">$EncodingString", '/dev/tty') # 1.43
341             || (warn "Can't write /dev/tty: $!\n", return 0);
342 0 0         if (!$have_Term_ReadKey) { $Stty = `stty -g`; chop $Stty; }
  0            
  0            
343 0           my $encoding_string;
344 0 0         if ($mouse_mode) {
345 0           $IsMouseMode = 1; $encoding_string = ':bytes';
  0            
346 0           print TTY "\e[?1003h"; # sets SET_ANY_EVENT_MOUSE mode
347             } else {
348 0           $IsMouseMode = 0; $encoding_string = $EncodingString;
  0            
349             }
350 0 0 0       if ($speakup_silent and !$IsSpeakUpSilent) { enter_speakup_silent(); }
  0            
351 0 0         open(TTYIN, "<$encoding_string", '/dev/tty')
352             || (warn "Can't read /dev/tty: $!\n", return 0);
353              
354 0 0         if ($have_Term_ReadKey) {
355 0           Term::ReadKey::ReadMode('ultra-raw', *TTYIN);
356             } else {
357 0 0         if ($^O =~ /^FreeBSD$/i) { system("stty -echo -icrnl raw
  0            
358 0           } else { system("stty -echo -icrnl raw /dev/tty");
359             }
360             }
361 0           select((select(TTY), $| = 1)[0]); print TTY q{};
  0            
362 0           $rin = q{}; vec($rin, fileno(TTYIN), 1) = 1;
  0            
363 0           $icol = 0; $irow = 0; $InitscrAlreadyRun = 1;
  0            
  0            
364             }
365              
366             sub endwin {
367 0     0 0   print TTY "\e[0m";
368 0 0         if ($InitscrAlreadyRun > 1) {
369 0 0 0       if ($IsMouseMode and !$WasMouseMode) { leave_mouse_mode();
  0 0 0        
370 0           } elsif (!$IsMouseMode and $WasMouseMode) { enter_mouse_mode();
371             }
372 0 0 0       if ($IsSpeakUpSilent and !$WasSpeakUpSilent) { # 1.62
    0 0        
373 0           leave_speakup_silent();
374             } elsif (!$IsSpeakUpSilent and $WasSpeakUpSilent) {
375 0           enter_speakup_silent();
376             }
377 0           $InitscrAlreadyRun--; return;
  0            
378             }
379 0           print TTY "\e[?1003l"; $IsMouseMode = 0;
  0            
380 0 0         if ($IsSpeakUpSilent) { leave_speakup_silent(); }
  0            
381 0 0         if ($have_Term_ReadKey) {
382 0           Term::ReadKey::ReadMode('restore', *TTYIN);
383 0           close TTY; close TTYIN;
  0            
384             } else {
385 0           close TTY; close TTYIN;
  0            
386 0 0         if ($^O =~ /^FreeBSD$/i) { system("stty $Stty
  0 0          
387 0 0         } else { system("stty $Stty /dev/tty") if $Stty;
388             }
389             }
390 0           $InitscrAlreadyRun = 0;
391             }
392              
393             # ----------------------- size handling ----------------------
394              
395             my ($maxcols, $maxrows); my $size_changed = 1;
396             my @OtherLines; # 20131002 $otherlines, $notherlines no longer global
397              
398             sub check_size {
399 0 0   0 0   if (! $size_changed) { return; }
  0            
400 0 0         if ($have_Term_ReadKey) {
    0          
401 0           ($maxcols, $maxrows) = Term::ReadKey::GetTerminalSize(*STDERR);
402             } elsif ($have_Term_Size) {
403 0           ($maxcols, $maxrows) = Term::Size::chars(*STDERR);
404             } else {
405 0           $maxcols = `tput cols`;
406 0   0       $maxrows = (`tput lines` + 0) || (`tput rows` + 0);
407             }
408 0   0       $maxcols = $maxcols || 80; $maxcols--;
  0            
409 0   0       $maxrows = $maxrows || 24;
410 0 0         if (@OtherLines) {
411 0           @OtherLines = &fmt(join("\n",@OtherLines));
412             }
413 0           $size_changed = 0;
414             }
415             $SIG{'WINCH'} = sub { $size_changed = 1; };
416              
417             # ------------------------ ask stuff -------------------------
418              
419             # Options such as integer, real, positive, >x, >=x,
420             # non-null, max-length, min-length, silent ...
421             # default could be just one more option, and backward compatibilty
422             # could be preserved by checking whether the 2nd arg is a hashref ...
423              
424 0     0 1   sub ask_filename { my ($question, $default) = @_; # 1.65 tab-completion
425 0 0         eval 'require Term::ReadLine'; if ($@) {
  0            
426 0           sorry("you should install Term::ReadLine::Gnu from www.cpan.org");
427 0           return ask($question, $default);
428             }
429 0           initscr(speakup_silent=>1);
430 0           endwin();
431 0           $term = Term::ReadLine->new('ProgramName');
432 0           my $filename = $term->readline($question.' '); # 1.70
433 0           print STDERR "\e[J";
434 0           $filename =~ s/ $//; # 1.66
435 0           return $filename;
436             }
437             sub ask_password { # no echo - use for passwords
438 0     0 1   local ($silent) = 'yes'; &ask($_[0]);
  0            
439             }
440 0     0 1   sub ask { my ($question, $default) = @_;
441 0 0         return q{} unless $question;
442 0           &initscr(speakup_silent=>1);
443 0           my $nol = &display_question($question);
444              
445 0           my $i = 0; my $n = 0; my @s = (); # cursor position, length, string
  0            
  0            
446 0 0         if (defined $default) { # 1.69 defined, to include 0
447 0           &speak("$question, default is $default");
448 0           $default =~ s/\t/ /g;
449 0           @s = split(q{}, $default); $n = scalar @s; $i = 0;
  0            
  0            
450 0           foreach $j (0 .. $#s) { &puts($s[$j]); }
  0            
451 0           &left($n);
452             } else {
453 0           &speak($question);
454             }
455              
456 0           while (1) {
457 0           my $c = &getch();
458 0 0         if ($c eq "\r") { &erase_lines(1); last; }
  0            
  0            
459 0 0         if ($size_changed) {
460 0           &erase_lines(0); $nol = &display_question($question);
  0            
461             }
462 0 0 0       if ($c == $KEY_LEFT) {
    0 0        
    0 0        
    0 0        
    0          
    0          
    0          
    0          
    0          
    0          
    0          
463 0 0         if ($i > 0) { $i--; &left(1); } # 1.44
  0            
  0            
464             } elsif ($c == $KEY_RIGHT) {
465 0 0         if ($i < $n) { &puts($silent ? "x" : $s[$i]); $i++; }
  0 0          
  0            
466             } elsif ($c == $KEY_DELETE) { # 1.54
467 0 0         if ($i < $n) {
468 0           $n--; splice(@s, $i, 1);
  0            
469 0 0         foreach $j ($i..$#s) { &puts($silent ? "x" : $s[$j]); } # 1.67
  0            
470 0           &clrtoeol(); &left($n-$i);
  0            
471             }
472             } elsif (($c eq "\cH") || ($c eq "\c?")) {
473 0 0         if ($i > 0) {
474 0           $n--; $i--;
  0            
475 0 0         if (! $silent) { &speak($s[$i]); } # 1.63
  0            
476 0           splice(@s, $i, 1); &left(1);
  0            
477 0 0         foreach $j ($i..$#s) { &puts($silent ? "x" : $s[$j]); } # 1.67
  0            
478 0           &clrtoeol(); &left($n-$i);
  0            
479             }
480             } elsif ($c eq "\cC") { # 1.56
481 0           &erase_lines(1); &endwin();
  0            
482 0           warn "^C\n"; kill('INT', $$); return undef;
  0            
  0            
483             } elsif ($c eq "\cX" || $c eq "\cD") { # clear ...
484 0           &left($i); $i = 0; $n = 0; &clrtoeol(); @s = ();
  0            
  0            
  0            
  0            
485 0           } elsif ($c eq "\cA" || $c == $KEY_HOME) { &left($i); $i = 0;
  0            
486 0           } elsif ($c eq "\cE" || $c == $KEY_END) { &right($n-$i); $i = $n;
  0            
487 0           } elsif ($c eq "\cL") { &speak(join("", @s)); # redraw ...
488 0           } elsif ($SpecialKey{$c}) { &beep();
489             } elsif (ord($c) >= 32) { # 1.51
490 0           splice(@s, $i, 0, $c);
491 0 0         &puts($silent ? "x" : $c);
492 0 0         if (! $silent) { &speak($c); }
  0            
493 0           $n++; $i++;
  0            
494 0 0         foreach $j ($i..$#s) { &puts($silent ? "x" : $s[$j]); } # 1.67
  0            
495 0           &clrtoeol(); &left($n-$i);
  0            
496 0           } else { &beep();
497             }
498             }
499 0           &speak(join("", @s), 'wait');
500 0           &endwin(); $silent = q{}; return join("", @s);
  0            
  0            
501             }
502              
503             # ----------------------- choose stuff -------------------------
504             sub debug {
505 0 0   0 0   if (! open (DEBUG, '>>/tmp/clui.log')) {
506 0           warn "can't open /tmp/clui.log: $!\n"; return;
  0            
507             }
508 0           print DEBUG "$_[0]\n"; close DEBUG;
  0            
509             }
510              
511             my (%irow, %icol, $nrows, $clue_has_been_given, $choice, $this_cell);
512             my @marked;
513             my $HOME = $ENV{'HOME'} || $ENV{'LOGDIR'} || (getpwuid($<))[7];
514             srand(time() ^ ($$+($$<15)));
515              
516 0     0 1   sub choose { my $question = shift; local @list = @_; # @list must be local
  0            
517             # As from 1.22, allows multiple choice if called in array context
518              
519 0 0         return unless @list;
520 0   0       grep (($_ =~ s/[\r\n]+$//) && 0, @list); # chop final newlines
521 0           my @biglist = @list; my $icell; @marked = ();
  0            
  0            
522              
523 0           $question =~ s/^[\n\r]+//; # strip initial newline(s)
524 0           $question =~ s/[\n\r]+$//; # strip final newline(s)
525 0           my ($firstline,$otherlines) = split(/\r?\n/, $question, 2);
526 0           my $firstlinelength = length $firstline;
527              
528 0           $choice = &get_default($firstline);
529             # If wantarray ? Is remembering multiple choices safe ?
530              
531 0           &initscr(mouse_mode=>1, speakup_silent=>1);
532 0           &size_and_layout(0);
533 0           @OtherLines = &fmt($otherlines);
534 0           my $speaktext = join(' ',$list[$this_cell],'. ',@OtherLines);
535 0 0         if (wantarray) {
536 0           $#marked = $#list;
537 0 0         if ($firstlinelength < $maxcols-30) {
    0          
    0          
538 0           &puts("$firstline (multiple choice with spacebar)\n\r");
539             } elsif ($firstlinelength < $maxcols-16) {
540 0           &puts("$firstline (multiple choice)\n\r");
541             } elsif ($firstlinelength < $maxcols-9) {
542 0           &puts("$firstline (multiple)\n\r");
543             } else {
544 0           &puts("$firstline\n\r");
545             }
546 0 0         if ($nrows >= $maxrows) { &speak("$firstline, ", 'wait');
  0            
547 0           } else { &speak("$firstline, multiple choice, $speaktext");
548             }
549             } else {
550 0           &puts("$firstline\n\r");
551 0 0         if ($nrows >= $maxrows) { &speak("$firstline, ", 'wait');
  0            
552 0           } else { &speak("$firstline, choose, $speaktext");
553             }
554             }
555 0 0         if ($nrows >= $maxrows) {
556 0           @list = &narrow_the_search(@list);
557 0 0         if (! @list) {
558 0           &up(1); &clrtoeol(); &endwin(); $clue_has_been_given = 0;
  0            
  0            
  0            
559 0 0         return wantarray ? () : undef;
560             }
561 0           my $speaktext = join(' ',$list[$this_cell],'. ',@OtherLines);
562 0           &speak("choose, $speaktext");
563             }
564 0           &wr_screen();
565             # the cursor is now on this_cell, not on the question
566 0           print TTY "\e[6n"; # terminfo u7, will set $AbsCursX,$AbsCursY
567 0           $CursorRow = $irow[$this_cell]; # global, needed by handle_mouse
568              
569 0           while (1) {
570 0           $c = &getch();
571 0 0         if ($size_changed) {
572 0           &size_and_layout($nrows);
573 0 0         if ($nrows >= $maxrows) {
574 0           @list = &narrow_the_search(@list);
575 0 0         if (! @list) {
576 0           &up(1); &clrtoeol(); &endwin(); $clue_has_been_given = 0;
  0            
  0            
  0            
577 0 0         return wantarray ? () : undef;
578             }
579             }
580 0           &wr_screen();
581 0           &speak("choose, $list[$this_cell]");
582             }
583 0 0 0       if ($c eq "q" || $c eq "\cD" || $c eq "\cX") {
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
    0 0        
      0        
      0        
      0        
584 0           &erase_lines(1);
585 0 0         if ($clue_has_been_given) {
586 0           my $re_clue = &confirm("Do you want to change your clue ?");
587 0           &up(1); &clrtoeol(); # erase the confirm
  0            
588 0 0         if ($re_clue) {
589 0           $irow = 1;
590 0           @list = &narrow_the_search(@biglist); &wr_screen();
  0            
591 0           &speak("choose, $list[$this_cell]");
592 0           next;
593             } else {
594 0           &up(1); &clrtoeol(); &endwin(); $clue_has_been_given = 0;
  0            
  0            
  0            
595 0 0         return wantarray ? () : undef;
596             }
597             }
598 0           &goto(0,0); &clrtoeol(); &endwin(); $clue_has_been_given = 0;
  0            
  0            
  0            
599 0 0         return wantarray ? () : undef;
600             } elsif (($c eq "\t") && ($this_cell < $#list)) {
601 0           $this_cell++; &wr_cell($this_cell-1); &wr_cell($this_cell);
  0            
  0            
602 0           &speak($list[$this_cell]);
603             } elsif ((($c eq "l") || ($c == $KEY_RIGHT)) && ($this_cell < $#list)
604             && ($irow[$this_cell] == $irow[$this_cell+1])) {
605 0           $this_cell++; &wr_cell($this_cell-1); &wr_cell($this_cell);
  0            
  0            
606 0           &speak($list[$this_cell]);
607             } elsif ((($c eq "\cH") || ($c == $KEY_BTAB)) && ($this_cell > 0)) {
608 0           $this_cell--; &wr_cell($this_cell+1); &wr_cell($this_cell);
  0            
  0            
609 0           &speak($list[$this_cell]);
610             } elsif ((($c eq "h") || ($c == $KEY_LEFT)) && ($this_cell > 0)
611             && ($irow[$this_cell] == $irow[$this_cell-1])) {
612 0           $this_cell--; &wr_cell($this_cell+1); &wr_cell($this_cell);
  0            
  0            
613 0           &speak($list[$this_cell]);
614             } elsif ((($c eq "j") || ($c == $KEY_DOWN)) && ($irow < $nrows)) {
615 0           my $mid_col = $icol[$this_cell] + 0.5 * length($list[$this_cell]);
616 0           my $left_of_target = 1000;
617 0           for ($inew=$this_cell+1; $inew < $#list; $inew++) {
618 0 0         last if $icol[$inew] < $mid_col; # skip rest of row
619             }
620 0           my $new_mid_col = 0;
621 0           for (; $inew < $#list; $inew++) {
622 0           $new_mid_col = $icol[$inew] + 0.5*length($list[$inew]);
623 0 0         last if $new_mid_col >= $mid_col; # we've reached it
624 0 0         last if $icol[$inew+1] <= $icol[$inew]; # we're at EOL
625 0           $left_of_target = $mid_col - $new_mid_col;
626             }
627 0 0         if (($new_mid_col - $mid_col) > $left_of_target) { $inew--; }
  0            
628 0           $iold = $this_cell; $this_cell = $inew;
  0            
629 0           &wr_cell($iold); &wr_cell($this_cell);
  0            
630 0           &speak($list[$this_cell]);
631             } elsif ((($c eq "k") || ($c == $KEY_UP)) && ($irow > 1)) {
632 0           my $mid_col = $icol[$this_cell] + 0.5*length($list[$this_cell]);
633 0           my $right_of_target = 1000;
634 0           for ($inew=$this_cell-1; $inew > 0; $inew--) {
635 0 0         last if $irow[$inew] < $irow[$this_cell]; # skip rest of row
636             }
637 0           my $new_mid_col = 0;
638 0           for (; $inew > 0; $inew--) {
639 0 0         last unless $icol[$inew];
640 0           $new_mid_col = $icol[$inew] + 0.5*length($list[$inew]);
641 0 0         last if $new_mid_col <= $mid_col; # we're past it
642 0           $right_of_target = $new_mid_col - $mid_col;
643             }
644 0 0         if (($mid_col - $new_mid_col) > $right_of_target) { $inew++; }
  0            
645 0           $iold = $this_cell; $this_cell = $inew;
  0            
646 0           &wr_cell($iold); &wr_cell($this_cell);
  0            
647 0           &speak($list[$this_cell]);
648             } elsif ($c eq "\cL") {
649 0 0         if ($size_changed) {
650 0           &size_and_layout($nrows);
651 0 0         if ($nrows >= $maxrows) {
652 0           @list = &narrow_the_search(@list);
653 0 0         if (! @list) {
654 0           &up(1); &clrtoeol(); &endwin();
  0            
  0            
655 0           $clue_has_been_given = 0;
656 0 0         return wantarray ? () : undef;
657             }
658             }
659             }
660 0           &wr_screen();
661             } elsif ($c eq "\cC") { # 1.56
662 0           &erase_lines(1); &endwin();
  0            
663 0           warn "^C\n"; kill('INT', $$); return undef;
  0            
  0            
664             } elsif ($c eq "\r") {
665 0           &erase_lines(1); &goto($firstlinelength+1, 0);
  0            
666 0           my @chosen;
667 0 0         if (wantarray) {
668 0           my $i; for ($i=0; $i<=$#list; $i++) {
  0            
669 0 0 0       if ($marked[$i] || $i==$this_cell) {
670 0           push @chosen, $list[$i];
671             }
672             }
673 0           &clrtoeol();
674 0           my $remaining = $maxcols-$firstlinelength;
675 0           my $last = pop @chosen;
676 0           my $dotsprinted;
677 0           foreach (@chosen) {
678 0 0         if (($remaining - length $_) < 4) {
679 0           $dotsprinted=1; &puts("..."); $remaining -= 3; last;
  0            
  0            
  0            
680             } else {
681 0           &puts("$_, "); $remaining -= (2 + length $_);
  0            
682             }
683             }
684 0 0         if (!$dotsprinted) {
685 0 0         if (($remaining - length $last)>0) { &puts($last);
  0 0          
686 0           } elsif ($remaining > 2) { &puts('...');
687             }
688             }
689 0           &puts("\n\r");
690 0           push @chosen, $last;
691             } else {
692 0           &puts($list[$this_cell]."\n\r");
693             }
694 0           &endwin();
695 0           &set_default($firstline, $list[$this_cell]); # join($,,@chosen) ?
696 0           $clue_has_been_given = 0;
697 0 0         if (wantarray) {
698 0           &speak(join(' and ',@chosen), 'wait'); return @chosen;
  0            
699             } else {
700 0           &speak($list[$this_cell], 'wait'); return $list[$this_cell];
  0            
701             }
702             } elsif ($c eq " ") {
703 0 0         if (wantarray) {
704 0           $marked[$this_cell] = !$marked[$this_cell];
705             #if ($this_cell < $#list) {
706             # $this_cell++; &wr_cell($this_cell-1); # 1.50
707 0           &wr_cell($this_cell);
708 0           &speak('marked');
709             #}
710             #} elsif ($this_cell < $#list) {
711             # $this_cell++; &wr_cell($this_cell-1); &wr_cell($this_cell);
712             }
713             } elsif ($c eq "?") {
714 0           warn "help\r\n";
715             }
716             }
717 0           &endwin();
718 0           warn "choose: shouldn't reach here ...\n";
719             }
720 0     0 0   sub layout { my @list = @_;
721 0           $this_cell = 0; my $irow = 1; my $icol = 0; my $i;
  0            
  0            
  0            
722 0           for ($i=0; $i<=$#list; $i++) {
723 0           $l[$i] = length($list[$i]) + 2;
724 0 0         if ($l[$i] > $maxcols-1) { $l[$i] = $maxcols-1; } # 1.42
  0            
725 0 0         if (($icol + $l[$i]) >= $maxcols ) { $irow++; $icol = 0; }
  0            
  0            
726 0 0         if ($irow > $maxrows) { return $irow; } # save time
  0            
727 0           $irow[$i] = $irow; $icol[$i] = $icol;
  0            
728 0           $icol += $l[$i];
729 0 0         if ($list[$i] eq $choice) { $this_cell = $i; }
  0            
730             }
731 0           return $irow;
732             }
733             sub wr_screen {
734 0     0 0   for (my $i=0; $i<=$#list; $i++) {
735 0 0         &wr_cell($i) unless $i==$this_cell;
736             }
737 0           my $notherlines = scalar @OtherLines;
738 0 0 0       if ($notherlines && ($nrows+$notherlines) < $maxrows) {
739 0           &puts("\r\n", join("\r\n", @OtherLines), "\r");
740             }
741 0           &wr_cell($this_cell);
742             }
743 0     0 0   sub wr_cell { my $i = shift;
744 0           my $no_tabs = $list[$i];
745 0           $no_tabs =~ s/\t/ /g;
746 0           &goto($icol[$i], $irow[$i]);
747 0 0         if ($marked[$i]) { &attrset($A_BOLD | $A_UNDERLINE); }
  0            
748 0 0         if ($i == $this_cell) { &attrset($A_REVERSE); }
  0            
749 0           &puts(substr " $no_tabs ", 0, $maxcols); # 1.42, 1.54
750 0 0 0       if ($marked[$i] || $i == $this_cell) { &attrset($A_NORMAL); }
  0            
751             }
752             sub size_and_layout {
753 0     0 0   my $erase_rows = shift;
754 0           &check_size();
755 0 0         if ($erase_rows) {
756 0 0         if ($erase_rows > $maxrows) { $erase_rows = $maxrows; } # XXX?
  0            
757 0           &erase_lines(1);
758             }
759 0           $nrows = &layout(@list);
760             }
761 0     0 0   sub narrow_the_search { my @biglist = @_;
762             # replaces the old ... require 'complete.pl';
763             # return &Complete("$firstline (TAB to complete, ^D to list) ", @list);
764 0           my $nchoices = scalar @_;
765 0           my $n; my $i; my @s; my $s; my @list = @biglist;
  0            
  0            
  0            
766 0           $clue_has_been_given = 1;
767 0 0         if ($IsMouseMode) { leave_mouse_mode(); }
  0            
768 0           &ask_for_clue($nchoices, $i, $s);
769 0           while (1) {
770 0           $c = &getch();
771 0 0         if ($size_changed) {
772 0           &size_and_layout(0);
773 0 0         if ($nrows < $maxrows) {
774 0           &erase_lines(1); enter_mouse_mode(); return @list;
  0            
  0            
775             }
776             }
777 0 0 0       if ($c == $KEY_LEFT && $i > 0) { $i--; &left(1); next;
  0 0 0        
  0 0 0        
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
778             } elsif ($c == $KEY_RIGHT) {
779 0 0         if ($i < $n) { &puts($s[$i]); $i++; next; }
  0            
  0            
  0            
780             } elsif (($c eq "\cH") || ($c eq "\c?")) {
781 0 0         if ($i > 0) {
782 0           $n--; $i--;
  0            
783 0           &speak($s[$i], 'wait'); # 1.63
784 0           splice(@s, $i, 1); &left(1);
  0            
785 0           foreach $j ($i..$n) { &puts($s[$j]); }
  0            
786 0           &clrtoeol(); &left($n-$i);
  0            
787             }
788             } elsif ($c eq "\cC") { # 1.56
789 0           &erase_lines(1); &endwin();
  0            
790 0           warn "^C\n"; kill('INT', $$); return undef;
  0            
  0            
791             } elsif ($c eq "\cX" || $c eq "\cD") { # clear ...
792 0 0         if (! @s) { # 20070305 ?
793 0           $clue_has_been_given = 0; &erase_lines(1);
  0            
794 0           enter_mouse_mode(); return ();
  0            
795             }
796 0           &left($i); $i = 0; $n = 0; @s = (); &clrtoeol();
  0            
  0            
  0            
  0            
797 0           } elsif ($c eq "\cA") { &left($i); $i = 0; next;
  0            
  0            
798 0           } elsif ($c eq "\cE") { &right($n-$i); $i = $n; next;
  0            
  0            
799             } elsif ($c eq "\cL") {
800 0           } elsif ($SpecialKey{$c}) { &beep();
801             } elsif (ord($c) >= 32) { # 1.51
802 0           splice(@s, $i, 0, $c);
803 0           $n++; $i++; &puts($c);
  0            
  0            
804 0           foreach $j ($i..$n) { &puts($s[$j]); } &clrtoeol(); &left($n-$i);
  0            
  0            
  0            
805 0           &speak($c, 'wait'); # 1.63
806 0           } else { &beep();
807             }
808             # grep, and if $nchoices=1 return
809 0           $s = join("", @s);
810 0           @list = grep(0 <= index($_,$s), @biglist);
811 0           $nchoices = scalar @list;
812 0           $nrows = &layout(@list);
813 0 0 0       if ($nchoices==1 || ($nchoices && ($nrows<$maxrows))) {
      0        
814 0           &puts("\r"); &clrtoeol(); &up(1); &clrtoeol();
  0            
  0            
  0            
815 0           enter_mouse_mode(); return @list;
  0            
816             }
817 0           &ask_for_clue($nchoices, $i, $s);
818             }
819 0           warn "narrow_the_search: shouldn't reach here ...\n";
820             }
821 0     0 0   sub ask_for_clue { my ($nchoices, $i, $s) = @_;
822 0 0         if ($nchoices) {
823 0 0         if ($s) {
824 0           my $headstr = "the choices won't fit; there are still";
825 0           &goto(0,1); &puts("$headstr $nchoices of them"); &clrtoeol();
  0            
  0            
826 0           &goto(0,2); &puts("lengthen the clue : "); &right($i);
  0            
  0            
827 0           &speak("still $nchoices choices, lengthen the clue");
828             } else {
829 0           my $headstr = "the choices won't fit; there are";
830 0           &goto(0,1); &puts("$headstr $nchoices of them"); &clrtoeol();
  0            
  0            
831 0           &goto(0,2);
832 0           &puts(" give me a clue : (or ctrl-X to quit)");
833 0           &left(31); # 1.62
834 0           &speak("$nchoices choices, give me a clue, or control-X to quit");
835             }
836             } else {
837 0           &goto(0,1); &puts("No choices fit this clue !"); &clrtoeol();
  0            
  0            
838 0           &goto(0,2); &puts(" shorten the clue : "); &right($i);
  0            
  0            
839 0           &speak("no choices fit, shorten the clue");
840             }
841             }
842 0     0 1   sub get_default { my ($question) = @_;
843 0 0         if ($ENV{CLUI_DIR} =~ /off/i) { return undef; }
  0            
844 0 0         if (! $question) { return undef; }
  0            
845 0           my @choices;
846 0           my $n_tries = 5;
847 0           while ($n_tries--) {
848 0 0         if (dbmopen (%CHOICES, &dbm_file(), 0600)) {
849 0           last;
850             } else {
851 0 0         if ($! eq 'Resource temporarily unavailable') {
852 0           my $wait = rand 0.45; select undef, undef, undef, $wait;
  0            
853 0           } else { return undef;
854             }
855             }
856             }
857 0           @choices = split ($; ,$CHOICES{$question}); dbmclose %CHOICES;
  0            
858 0 0         if (wantarray) { return @choices;
  0            
859 0           } else { return $choices[0];
860             }
861             }
862 0     0 1   sub set_default { my $question = shift; my $s = join($; , @_);
  0            
863 0 0         if ($ENV{CLUI_DIR} =~ /off/i) { return undef; }
  0            
864 0 0         if (! $question) { return undef; }
  0            
865 0           my $n_tries = 5;
866 0           while ($n_tries--) {
867 0 0         if (dbmopen(%CHOICES, &dbm_file(), 0600)) {
868 0           last;
869             } else {
870 0 0         if ($! eq 'Resource temporarily unavailable') {
871 0           my $wait = rand 0.50; select undef, undef, undef, $wait;
  0            
872 0           } else { return undef;
873             }
874             }
875             }
876 0           $CHOICES{$question} = $s; dbmclose %CHOICES;
  0            
877 0           return $s;
878             }
879             sub dbm_file {
880 0 0   0 0   if ($ENV{CLUI_DIR} =~ /off/i) { return undef; }
  0            
881 0           my $db_dir;
882 0 0         if ($ENV{CLUI_DIR}) {
883 0           $db_dir = $ENV{CLUI_DIR};
884 0           $db_dir =~ s#^~/#$HOME/#;
885 0           } else { $db_dir = "$HOME/.clui_dir";
886             }
887 0           mkdir ($db_dir,0750);
888 0           return "$db_dir/choices";
889             }
890 0     0 0   sub handle_mouse { my ($x, $y, $button_pressed, $button_drag) = @_; # 1.50
891 0           $TopRow = $AbsCursY - $CursorRow;
892 0 0         if ($LastEventWasPress) { $LastEventWasPress = 0; return(''); }
  0            
  0            
893 0 0         return('') unless $y >= $TopRow;
894 0           my $mouse_row = $y - $TopRow;
895 0           my $mouse_col = $x - 1;
896             # debug("x=$x y=$y TopRow=$TopRow mouse_row=$mouse_row");
897             # debug("button_pressed=$button_pressed button_drag=$button_drag");
898 0           my $found = 0;
899 0           my $i = 0; while ($i < @irow) {
  0            
900 0 0         if ($irow[$i] == $mouse_row) {
901             # debug("list[$i]=$list[$i] is the right row");
902 0 0 0       if ($icol[$i] < $mouse_col
903             and ($icol[$i]+length($list[$i]) >= $mouse_col)) {
904 0           $found = 1; last;
  0            
905             }
906 0 0         last if $irow[$i] > $mouse_row;
907             }
908 0           $i += 1;
909             }
910 0 0         return unless $found;
911             # if xterm doesn't receive a button-up event it thinks it's dragging
912 0           my $return_char = q{};
913 0 0 0       if ($button_pressed == 1 and !$button_drag) {
    0 0        
914 0           $LastEventWasPress = 1;
915 0           $return_char = $KEY_ENTER;
916             } elsif ($button_pressed == 3 and !$button_drag) {
917 0           $LastEventWasPress = 1;
918 0           $return_char = q{ };
919             }
920 0 0         if ($i != $this_cell) {
921 0           my $t = $this_cell; $this_cell = $i;
  0            
922 0           &wr_cell($t); &wr_cell($this_cell);
  0            
923             }
924 0           return $return_char;
925             }
926             sub help_text { # 1.54
927 0     0 1   my $text;
928 0 0         if ($_[0] eq 'ask') {
929 0           return "\nLeft and Right arrowkeys, Backspace, Delete; control-A = "
930             . " beginning; control-E = end; control-X = clear; then Return.";
931             }
932 0 0         if ($ENV{'CLUI_MOUSE'} eq 'OFF') {
933 0           $text = "\nmove around with Arrowkeys (or hjkl);";
934             } else {
935 0           $text = "\nmove around with Mouse or Arrowkeys (or hjkl);";
936             }
937 0 0         if ($_[0] =~ /^mult/) {
938 0           $text .= " multiselect with Rightclick or Spacebar;";
939             }
940 0           $text .= " then either q or ctrl-X for quit,";
941 0 0         if ($ENV{'CLUI_MOUSE'} eq 'OFF') {
942 0           $text .= " or Return to choose.";
943             } else {
944 0           $text .= " or choose with Leftclick or Return.";
945             }
946             }
947              
948             # ----------------------- confirm stuff -------------------------
949              
950 0     0 1   sub confirm { my $question = shift; # asks user Yes|No, returns 1|0
951 0 0         return(0) unless $question; return(0) unless -t STDERR;
  0 0          
952 0           &initscr(speakup_silent=>1);
953 0           my $nol = &display_question($question); &puts(" (y/n) ");
  0            
954 0           &speak($question . ', y or n');
955 0           while (1) {
956 0           $response=&getch();
957 0 0         if ($response eq "\cC") { # 1.56
958 0           &erase_lines(1); &endwin();
  0            
959 0           warn "^C\n"; kill('INT', $$); return undef;
  0            
  0            
960             }
961 0 0         last if ($response=~/[yYnN]/);
962 0           &beep();
963             }
964 0           &left(6); &clrtoeol();
  0            
965 0 0         if ($response=~/^[yY]/) {
966 0           &puts("Yes");
967 0           &speak('yess', 'wait');
968             } else {
969 0           &puts("No");
970 0           &speak('know', 'wait');
971             }
972 0           &erase_lines(1); &endwin();
  0            
973 0 0         if ($response =~ /^[yY]/) { return 1; } else { return 0 ; }
  0            
  0            
974             }
975              
976             # ----------------------- edit stuff -------------------------
977              
978 0     0 1   sub edit { my ($title, $text) = @_;
979 0           my $argc = $#_ - 0 +1;
980 0           my ($dirname, $basename, $rcsdir, $rcsfile, $rcs_ok);
981            
982 0 0         if ($argc == 0) { # start editor session with no preloaded file
    0          
    0          
983 0   0       system $ENV{EDITOR} || "vi"; # should also look in ~/db/choices.db
984             } elsif ($argc == 2) {
985             # must create tmp file with title embedded in name
986 0           my $tmpdir = '/tmp';
987 0           my $safename = $title;
988 0           $safename =~ s/[\W_]+/_/g;
989 0           my $file = "$tmpdir/$safename.$$";
990 0 0         if (!open(F,">$file")) {&sorry("can't open $file: $!\n");return q{};}
  0            
  0            
991 0           print F $text; close F;
  0            
992 0   0       $editor = $ENV{EDITOR} || "vi"; # should also look in ~/db/choices.db
993 0           system "$editor $file";
994 0 0         if (!open(F,"< $file")) {&sorry("can't open $file: $!\n");return 0;}
  0            
  0            
995 0           undef $/; $text = ; $/ = "\n";
  0            
  0            
996 0           close F; unlink $file; return $text;
  0            
  0            
997             } elsif ($argc == 1) { # its a file, we will try RCS ...
998 0           my $file = $title;
999              
1000             # weed out no-go situations
1001 0 0         if (-d $file) {&sorry("$file is already a directory\n"); return 0;}
  0            
  0            
1002 0 0 0       if (-B _ && -s _) {&sorry("$file is not a text file\n"); return 0;}
  0            
  0            
1003 0 0 0       if (-T _ && !-w _) { &view($file); return 1; }
  0            
  0            
1004            
1005             # it's a writeable text file, so work out the locations
1006 0 0         if ($file =~ /\//) {
1007 0           ($dirname, $basename) = $file =~ /^(.*)\/([^\/]+)$/;
1008 0           $rcsdir = "$dirname/RCS";
1009 0           $rcsfile = "$rcsdir/$basename,v";
1010             } else {
1011 0           $basename = $file;
1012 0           $rcsdir = "RCS";
1013 0           $rcsfile = "$rcsdir/$basename,v";
1014             }
1015 0           $rcslog = "$rcsdir/log";
1016            
1017             # we no longer create the RCS directory if it doesn't exist,
1018             # so `mkdir RCS' to enable rcs in a directory ...
1019 0 0         $rcs_ok = 1; if (!-d $rcsdir) { $rcs_ok = 0; }
  0            
  0            
1020 0 0 0       if (-d _ && ! -w _) { $rcs_ok = 0; warn "can't write in $rcsdir\n"; }
  0            
  0            
1021            
1022             # if the file doesn't exist, but the RCS does, then check it out
1023 0 0 0       if ($rcs_ok && -f $rcsfile && !-f $file) {
      0        
1024 0           system "co -l $file $rcsfile";
1025             }
1026              
1027 0           my $starttime = time;
1028 0   0       $editor = $ENV{EDITOR} || "vi"; # should also look in ~/db/choices.db
1029 0           system "$editor $file";
1030 0           my $elapsedtime = time - $starttime;
1031             # could be output or logged, for worktime accounting
1032            
1033 0 0 0       if ($rcs_ok && -T $file) { # check it in
1034 0 0         if (!-f $rcsfile) {
1035 0           my $msg = &ask("$file is new. Please describe it:");
1036 0           my $quotedmsg = $msg; $quotedmsg =~ s/'/'"'"'/g;
  0            
1037 0 0         if ($msg) {
1038 0           system "ci -q -l -t-'$quotedmsg' -i $file $rcsfile";
1039 0           &logit($basename, $msg);
1040             }
1041             } else {
1042 0           my $msg = &ask("What changes have you made to $file ?");
1043 0           my $quotedmsg = $msg; $quotedmsg =~ s/'/'"'"'/g;
  0            
1044 0 0         if ($msg) {
1045 0           system "ci -q -l -m'$quotedmsg' $file $rcsfile";
1046 0           &logit($basename, $msg);
1047             }
1048             }
1049             }
1050             }
1051             }
1052 0     0 0   sub logit { my ($file, $msg) = @_;
1053 0 0         if (! open(LOG, ">> $rcslog")) { warn "can't open $rcslog: $!\n";
  0            
1054             } else {
1055 0           $pid = fork; # log in background for better response time
1056 0 0         if (! $pid) {
1057 0           ($user) = getpwuid($>);
1058 0           print LOG ×tamp, " $file $user $msg\n"; close LOG;
  0            
1059 0 0         if ($pid == 0) { exit 0; } # the child's end, if a fork occurred
  0            
1060             }
1061             }
1062             }
1063             sub timestamp {
1064             # returns current date and time in "199403011 113520" format
1065 0     0 1   my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;
1066 0           $wday += 0; $yday += 0; $isdst += 0; # avoid bloody -w warning
  0            
  0            
1067 0           return sprintf("%4.4d%2.2d%2.2d %2.2d%2.2d%2.2d",
1068             $year+1900, $mon+1, $mday, $hour, $min, $sec);
1069             }
1070              
1071             # ----------------------- sorry stuff -------------------------
1072              
1073             sub sorry { # warns user of an error condition
1074 0     0 1   print STDERR "Sorry, $_[0]\n";
1075 0           &speak("Sorry, $_[0]", 'wait');
1076             }
1077 0     0 1   sub inform { my $text = $_[0];
1078 0           $text =~ s/([^\n])$/$1\n/s;
1079 0 0         if (open(TTY, ">$EncodingString", '/dev/tty')) { # 1.43
1080 0           print TTY $text; close TTY;
  0            
1081 0           } else { warn $text;
1082             }
1083 0           &speak($text, 'wait');
1084             }
1085              
1086             # ----------------------- view stuff -------------------------
1087              
1088             foreach $f ("/usr/bin/less", "/usr/bin/more") {
1089             if (-x $f) { $default_pager = $f; }
1090             }
1091 0     0 1   sub view { my ($title, $text) = @_; # or ($filename) =
1092 0   0       my $pager = $ENV{PAGER} || $default_pager;
1093 0 0 0       if (! $text and ($title =~ /\.doc$/i) and -r $title) { # 1.65
    0 0        
      0        
      0        
1094 0 0         my $wvText = which('wvText'); if ($wvText) {
  0            
1095 0           my $tmpf = "/tmp/wv$$";
1096 0           system "$wvText '$title' $tmpf"; system "$pager $tmpf";
  0            
1097 0           unlink $tmpf; return 1;
  0            
1098             }
1099 0 0         my $antiword = which('antiword'); if ($antiword) {
  0            
1100 0           system "$antiword -i 1 '$title' | $pager"; return 1;
  0            
1101             }
1102 0 0         my $catdoc = which('catdoc'); if ($catdoc) {
  0            
1103 0           system "$catdoc '$title' | $pager"; return 1;
  0            
1104             }
1105 0           sorry("it's a .doc file; you need to install wv, antiword or catdoc");
1106 0           return 0;
1107             } elsif (! $text && -T $title && open(F,"< $title")) {
1108 0           $nlines = 0;
1109 0 0         while () { last if ($nlines++ > $maxrows); } close F;
  0            
  0            
1110 0 0         if ($nlines > (0.6*$maxrows)) {
1111 0           system "$pager \'$title\'";
1112             } else {
1113 0           open(F,"< $title"); undef $/; $text=; $/="\n"; close F;
  0            
  0            
  0            
  0            
1114 0           &tiview($title, $text);
1115             }
1116             } else {
1117 0           local (@lines) = split(/\r?\n/, $text, $maxrows);
1118 0 0         if (($#lines) < 21) {
1119 0           &tiview($title, $text);
1120             } else {
1121 0           local ($safetitle); ($safetitle = $title) =~ s/[^a-zA-Z0-9]+/_/g;
  0            
1122 0           local ($tmp) = "/tmp/$safetitle.$$";
1123 0 0         if (!open(TMP, ">$tmp")) {warn "can't open $tmp: $!\n"; return;}
  0            
  0            
1124 0           print TMP $text; close TMP;
  0            
1125 0           system "$pager \'$tmp\'";
1126 0           unlink $tmp;
1127 0           return 1;
1128             }
1129             }
1130             }
1131 0     0 0   sub tiview { my ($title, $text) = @_;
1132 0 0         return unless $text;
1133 0           $title =~ s/\t/ /g; my $titlelength = length $title;
  0            
1134            
1135 0           &check_size();
1136 0           my @rows = &fmt($text, nofill=>1);
1137 0           &initscr();
1138 0 0         if (3 > scalar @rows) {
1139 0           &puts("$title\r\n".join("\r\n",@rows), "\r\n");
1140 0           &speak("$title, ".join(" ",@rows), 'wait');
1141 0           &endwin(); return 1;
  0            
1142             }
1143 0 0         if ($titlelength > ($maxcols-35)) { &puts("$title\r\n");
  0            
1144 0           } else { &puts("$title ( to continue, q to clear)\r\n");
1145             }
1146 0           &puts("\r", join("\e[K\r\n",@rows), "\r");
1147 0           &speak("$title, enter to continue, ".join(" ",@rows));
1148 0           $icol = 0; $irow = scalar @rows; &goto($titlelength+1, 0);
  0            
  0            
1149            
1150 0           while (1) {
1151 0           $c = &getch();
1152 0 0 0       if ($c eq 'q' || $c eq "\cX" || $c eq "\cW" || $c eq "\cZ"
    0 0        
    0 0        
      0        
      0        
1153             || $c eq "\cC" || $c eq "\c\\") {
1154 0           &erase_lines(0); &endwin(); return 1;
  0            
  0            
1155             } elsif ($c eq "\r") { # retains text on screen
1156 0           &clrtoeol(); &goto(0, @rows+1); &endwin(); return 1;
  0            
  0            
  0            
1157             } elsif ($c eq "\cL") {
1158 0           &puts("\r"); &endwin(); &tiview($title,$text); return 1;
  0            
  0            
  0            
1159             }
1160             }
1161 0           warn "tiview: shouldn't reach here\n";
1162             }
1163              
1164             # -------------------------- infrastructure -------------------------
1165              
1166             sub which {
1167 0     0 0   my $f;
1168 0 0         foreach $d (split(":",$ENV{'PATH'})) {$f="$d/$_[0]"; return $f if -x $f;}
  0            
  0            
1169             }
1170             %SpeakMode = ();
1171             sub END {
1172 1 50   1   2937 if ($Eflite_FH) { print $Eflite_FH "s\nq { }\n"; close $Eflite_FH;
  0 50          
  0            
1173 0           } elsif ($Espeak_PID) { kill SIGHUP, $Espeak_PID; wait;
  0            
1174             }
1175             }
1176 0     0 0   sub speak { my ($text, $wait) = @_;
1177 0           $text="$text";
1178 0 0         return unless length($text); # should clean up for exit: kill or wait
1179             # could replace the punctuation chars with descriptive words...
1180 0 0         if ($SpeakMode{'dot'}) {
1181 0           $text =~ s/\s*\.\s*/ dot /g;
1182 0           $text =~ s/\s*\.(\w)/ dot $1/g;
1183             }
1184 0 0         if ($Eflite_FH) {
    0          
1185 0 0         if (length($text) == 1) {
1186 0 0         if ($text eq '.') { print $Eflite_FH "s\nq { dot }\nd\n";
  0            
1187 0           } else { print $Eflite_FH "s\nl {$text}\n";
1188             }
1189 0 0         if ($wait) { select(undef,undef,undef,0.5); }
  0            
1190             } else {
1191 0           print $Eflite_FH "s\nq {$text}\nd\n";
1192             # useless emacspeak op: tts_sy nc_state all 0 0 1 225\nq {[:np ]}
1193 0 0         if ($wait) { select(undef,undef,undef,0.3+0.07*length($text)); }
  0            
1194             }
1195             } elsif ($Espeak) { # 1.68 should be using Speech::eSpeak !
1196 0 0         if ($Espeak_PID) { kill SIGHUP, $Espeak_PID; wait; $Espeak_PID = 0; }
  0            
  0            
  0            
1197 0           $Espeak_PID = fork();
1198 0 0         if ($Espeak_PID) {
1199 0 0         if ($wait) {
1200 0 0         if (length($text) == 1) { select(undef,undef,undef,0.5);
  0            
1201 0           } else { select(undef,undef,undef,0.3+0.07*length($text));
1202             }
1203             }
1204 0           return 1;
1205             } else {
1206 0           my $espeak_FH;
1207             my $espeak_PID;
1208 0 0         if ($espeak_PID = open($espeak_FH,'|-',$Espeak)) {
1209 0           select((select($espeak_FH), $| = 1)[0]); print $espeak_FH q{};
  0            
1210             } else {
1211 0           warn "can't run $Espeak: $!\n"; return;
  0            
1212             }
1213             # binmode($espeak_FH, ':unix');
1214 0     0 0   sub huphandler { kill 'KILL', $espeak_PID; }
1215 0           $SIG{HUP} = \&huphandler;
1216 0 0         if ($text eq '.') { print $espeak_FH "dot\n";
  0            
1217 0           } else { print $espeak_FH "$text\n";
1218             }
1219             # close $espeak_FH; # Must Not Close! close Hangs, unkillable !
1220 0           wait;
1221 0           exit 0;
1222             }
1223             }
1224             }
1225              
1226 0     0 0   sub display_question { my $question = shift; my %options = @_;
  0            
1227             # used by &ask and &confirm, but not by &choose ...
1228 0           &check_size();
1229 0           my ($firstline, $otherlines); # 20131002 @otherlines => $otherlines
1230 0 0         if ($options{nofirstline}) {
1231 0           @OtherLines = &fmt($question);
1232             } else {
1233 0           ($firstline,$otherlines) = split(/\r?\n/, $question, 2);
1234 0           @OtherLines = &fmt($otherlines);
1235 0 0         if ($firstline) { &puts("$firstline "); }
  0            
1236             }
1237 0 0         if (@OtherLines) {
1238 0           &puts("\r\n", join("\r\n", @OtherLines), "\r");
1239 0           &goto(1 + length $firstline, 0);
1240             }
1241 0           return scalar @OtherLines;
1242             }
1243             sub erase_lines { # leaves cursor at beginning of line $_[0]
1244 0     0 0   &goto(0, $_[0]); print TTY "\e[J";
  0            
1245             }
1246 0     0 0   sub fmt { my $text = shift; my %options = @_;
  0            
1247             # Used by tiview, ask and confirm; formats the text within $maxcols cols
1248 0           my (@i_words, $o_line, @o_lines, $o_length, $last_line_empty, $w_length);
1249 0           my (@i_lines, $initial_space);
1250 0           @i_lines = split(/\r?\n/, $text);
1251 0           foreach $i_line (@i_lines) {
1252 0 0         if ($i_line =~ /^\s*$/) { # blank line ?
1253 0 0         if ($o_line) { push @o_lines, $o_line; $o_line=q{}; $o_length=0; }
  0            
  0            
  0            
1254 0 0         if (! $last_line_empty) { push @o_lines,""; $last_line_empty=1; }
  0            
  0            
1255 0           next;
1256             }
1257 0           $last_line_empty = 0;
1258              
1259 0 0         if ($options{nofill}) {
1260 0           push @o_lines, substr($i_line, 0, $maxcols-1); next;
  0            
1261             }
1262 0 0         if ($i_line =~ s/^(\s+)//) { # line begins with space ?
1263 0           $initial_space = $1; $initial_space =~ s/\t/ /g;
  0            
1264 0 0         if ($o_line) { push @o_lines, $o_line; }
  0            
1265 0           $o_line = $initial_space; $o_length = length $initial_space;
  0            
1266             } else {
1267 0           $initial_space = q{};
1268             }
1269              
1270 0           @i_words = split(' ', $i_line);
1271 0           foreach $i_word (@i_words) {
1272 0           $w_length = length $i_word;
1273 0 0         if (($o_length + $w_length) >= $maxcols) {
1274 0           push @o_lines, $o_line;
1275 0           $o_line = $initial_space; $o_length = length $initial_space;
  0            
1276             }
1277 0 0         if ($w_length >= $maxcols) { # chop it !
1278 0           push @o_lines, substr($i_word,0,$maxcols-1); next;
  0            
1279             }
1280 0 0         if ($o_line) { $o_line .= ' '; $o_length += 1; }
  0            
  0            
1281 0           $o_line .= $i_word; $o_length += $w_length;
  0            
1282             }
1283             }
1284 0 0         if ($o_line) { push @o_lines, $o_line; }
  0            
1285 0 0         if ((scalar @o_lines) < $maxrows-2) { return(@o_lines);
  0            
1286 0           } else { return splice (@o_lines, 0, $maxrows-2);
1287             }
1288             }
1289             sub back_up {
1290 0 0   0 0   open(TTY, '>', '/dev/tty') # 1.43
1291             || (warn "Can't write /dev/tty: $!\n", return 0);
1292 0           print TTY "\r\e[K\e[A\e[K";
1293 0           close TTY;
1294             }
1295             1;
1296              
1297             __END__