File Coverage

blib/lib/Term/Choose.pm
Criterion Covered Total %
statement 45 1036 4.3
branch 8 630 1.2
condition 0 246 0.0
subroutine 14 43 32.5
pod 2 2 100.0
total 69 1957 3.5


line stmt bran cond sub pod time code
1             package Term::Choose;
2              
3 3     3   317707 use warnings;
  3         6  
  3         179  
4 3     3   21 use strict;
  3         5  
  3         60  
5 3     3   35 use 5.10.1;
  3         21  
6              
7             our $VERSION = '1.782';
8 3     3   15 use Exporter 'import';
  3         5  
  3         128  
9             our @EXPORT_OK = qw( choose );
10              
11 3     3   10 use Carp qw( croak carp );
  3         10  
  3         219  
12              
13 3     3   1189 use Term::Choose::Constants qw( :all );
  3         10  
  3         1013  
14 3     3   1483 use Term::Choose::LineFold qw( line_fold print_columns cut_to_printwidth );
  3         15  
  3         482  
15 3     3   28 use Term::Choose::Screen qw( :all );
  3         6  
  3         893  
16 3     3   1639 use Term::Choose::ValidateOptions qw( validate_options );
  3         10  
  3         340  
17              
18             my $Plugin;
19              
20             BEGIN {
21 3 50   3   15 if ( $^O eq 'MSWin32' ) {
22 0         0 require Win32::Console::ANSI;
23 0         0 require Term::Choose::Win32;
24 0         0 $Plugin = 'Term::Choose::Win32';
25             }
26             else {
27 3         1395 require Term::Choose::Linux;
28 3         35133 $Plugin = 'Term::Choose::Linux';
29             }
30             }
31              
32             END {
33 3 50   3   188597 if ( $? == 255 ) {
34 0 0       0 if( $^O eq 'MSWin32' ) {
    0          
35 0         0 my $input = Win32::Console->new( Win32::Console::constant( "STD_INPUT_HANDLE", 0 ) );
36 0         0 $input->Mode( 0x0001|0x0002|0x0004 );
37 0         0 $input->Flush;
38             }
39             elsif ( TERM_READKEY ) {
40 0         0 Term::ReadKey::ReadMode( 'restore' );
41             }
42             else {
43 0         0 system( "stty sane" );
44             }
45 0         0 print "\n", clear_to_end_of_screen;
46 0         0 print show_cursor;
47             }
48             }
49              
50              
51             sub new {
52 113     113 1 451604 my $class = shift;
53 113         292 my ( $opt ) = @_;
54 113 50       349 croak "new: called with " . @_ . " arguments - 0 or 1 arguments expected" if @_ > 1;
55 113         251 my $instance_defaults = _defaults();
56 113 100       318 if ( defined $opt ) {
57 111 50       338 croak "new: the (optional) argument must be a HASH reference" if ref $opt ne 'HASH';
58 111         222 validate_options( _valid_options(), $opt, 'new' );
59 111         840 for my $key ( keys %$opt ) {
60 150 100       481 $instance_defaults->{$key} = $opt->{$key} if defined $opt->{$key};
61             }
62             }
63 113         304 my $self = bless $instance_defaults, $class;
64 113         939 $self->{backup_instance_defaults} = { %$instance_defaults };
65 113         563 $self->{plugin} = $Plugin->new();
66 113         580 return $self;
67             }
68              
69              
70             sub _valid_options {
71             return {
72 111     111   2982 beep => '[ 0 1 ]',
73             clear_screen => '[ 0 1 ]',
74             codepage_mapping => '[ 0 1 ]',
75             hide_cursor => '[ 0 1 ]',
76             index => '[ 0 1 ]',
77             mouse => '[ 0 1 ]',
78             order => '[ 0 1 ]',
79             alignment => '[ 0 1 2 ]',
80             color => '[ 0 1 2 ]',
81             include_highlighted => '[ 0 1 2 ]',
82             layout => '[ 0 1 2 ]',
83             page => '[ 0 1 2 ]',
84             search => '[ 0 1 2 ]',
85             keep => '[ 1-9 ][ 0-9 ]*',
86             ll => '[ 1-9 ][ 0-9 ]*',
87             max_cols => '[ 1-9 ][ 0-9 ]*',
88             max_height => '(?:[2-9]|[ 1-9 ][ 0-9 ]+)', # rename to max_rows
89             max_width => '(?:[2-9]|[ 1-9 ][ 0-9 ]+)',
90             default => '[ 0-9 ]+',
91             pad => '[ 0-9 ]+',
92             margin => 'Array_Int',
93             mark => 'Array_Int_Idx',
94             meta_items => 'Array_Int_Idx',
95             no_spacebar => 'Array_Int_Idx',
96             tabs_bottom_text => 'Array_Int',
97             tabs_info => 'Array_Int',
98             tabs_prompt => 'Array_Int',
99             skip_items => 'Regexp',
100             bottom_text => 'Str',
101             empty => 'Str',
102             footer => 'Str',
103             info => 'Str',
104             prompt => 'Str',
105             undef => 'Str',
106             busy_string => 'Str',
107             };
108             };
109              
110              
111             sub _defaults {
112             return {
113 113     113   1114 alignment => 0,
114             beep => 0,
115             #bottom_text => undef,
116             clear_screen => 0,
117             codepage_mapping => 0,
118             color => 0,
119             #default => undef,
120             empty => '',
121             #footer => undef,
122             hide_cursor => 1,
123             include_highlighted => 0,
124             index => 0,
125             #info => undef,
126             keep => 5,
127             layout => 1,
128             #ll => undef,
129             #margin => undef,
130             #mark => undef,
131             #max_cols => undef,
132             #max_height => undef,
133             #max_width => undef,
134             #meta_items => undef,
135             mouse => 0,
136             #no_spacebar => undef,
137             order => 1, ##
138             pad => 2,
139             page => 1,
140             #prompt => undef,
141             search => 1,
142             #skip_items => undef,
143             #tabs_bottom_text => undef,
144             #tabs_info => undef,
145             #tabs_prompt => undef,
146             undef => '',
147             #busy_string => undef,
148             };
149             }
150              
151              
152             sub __copy_orig_list {
153 0     0     my ( $self, $orig_list_ref ) = @_;
154 0 0         if ( $self->{ll} ) {
155 0           $self->{list} = $orig_list_ref;
156             }
157             else {
158 0           $self->{list} = [ @$orig_list_ref ];
159 0 0         if ( $self->{color} ) {
160 0           $self->{orig_list} = $orig_list_ref;
161             }
162 0           for ( @{$self->{list}} ) {
  0            
163 0 0         if ( ! $_ ) {
164 0 0         $_ = $self->{undef} if ! defined $_;
165 0 0         $_ = $self->{empty} if ! length $_;
166             }
167 0 0         if ( $self->{color} ) {
168 0           s/${\PH}//g;
  0            
169 0           s/${\SGR_ES}/${\PH}/g;
  0            
  0            
170             }
171 0           s/\t/ /g;
172 0           s/\v+/\ \ /g;
173             # \p{Cn} might not be up to date and remove assigned codepoints
174             # therefore only \p{Noncharacter_Code_Point}
175 0           s/[\p{Cc}\p{Noncharacter_Code_Point}\p{Cs}]//g;
176             }
177             }
178             }
179              
180              
181             sub __length_list_elements {
182 0     0     my ( $self ) = @_;
183 0           my $list = $self->{list};
184 0 0         if ( $self->{ll} ) {
185 0           $self->{col_width} = $self->{ll};
186             }
187             else {
188 0           my $length_elements = [];
189 0           my $longest = 0;
190 0           for my $i ( 0 .. $#$list ) {
191 0           $length_elements->[$i] = print_columns( $list->[$i] );
192 0 0         $longest = $length_elements->[$i] if $length_elements->[$i] > $longest;
193             }
194 0           $self->{width_elements} = $length_elements;
195 0           $self->{col_width} = $longest;
196             }
197 0           $self->{bu_col_width} = $self->{col_width};
198             }
199              
200              
201             sub __init_term {
202 0     0     my ( $self ) = @_;
203             my $config = {
204             mode => 'ultra-raw',
205             mouse => $self->{mouse},
206             hide_cursor => $self->{hide_cursor},
207 0           };
208 0           $self->{mouse} = $self->{plugin}->__set_mode( $config );
209             }
210              
211              
212             sub __reset_term {
213 0     0     my ( $self, $clear_choose ) = @_;
214 0 0         if ( defined $self->{plugin} ) {
215 0           $self->{plugin}->__reset_mode( { mouse => $self->{mouse}, hide_cursor => $self->{hide_cursor} } );
216             }
217 0 0         if ( $clear_choose ) {
218 0           my $up = $self->{i_row} + $self->{count_pre_rows};
219 0 0         print up( $up ) if $up;
220 0           print "\r" . clear_to_end_of_screen();
221             }
222 0 0         if ( exists $self->{backup_instance_defaults} ) { # backup_instance_defaults exists if ObjectOriented
223 0           my $instance_defaults = $self->{backup_instance_defaults};
224 0           for my $key ( keys %$self ) {
225 0 0 0       if ( $key eq 'plugin' || $key eq 'backup_instance_defaults' ) {
    0          
226 0           next;
227             }
228             elsif ( exists $instance_defaults->{$key} ) {
229 0           $self->{$key} = $instance_defaults->{$key};
230             }
231             else {
232 0           delete $self->{$key};
233             }
234             }
235             }
236             }
237              
238              
239             sub __invalid_term_size {
240 0     0     my ( $self, $min_term_width, $min_term_height, $clear_choose ) = @_;
241 0           $self->__reset_term( $clear_choose );
242 0           warn "Minimum terminal size: " . ++$min_term_width . " x $min_term_height";
243 0           exit;
244             }
245              
246              
247             sub __get_key {
248 0     0     my ( $self ) = @_;
249 0           my $key;
250 0 0         if ( defined $self->{skip_items} ) {
251 0           my $idx = $self->{rc2idx}[$self->{pos}[ROW]][$self->{pos}[COL]];
252 0 0         if ( $self->{list}[$idx] =~ $self->{skip_items} ) {
253 0           $key = $self->Term::Choose::Opt::SkipItems::__key_skipped();
254             }
255             }
256 0 0         if ( ! defined $key ) {
257 0           $key = $self->{plugin}->__get_key_OS( $self->{mouse} );
258             }
259 0 0         return $key if ref $key ne 'ARRAY';
260 0           return $self->Term::Choose::Opt::Mouse::__mouse_info_to_key( @$key );
261             }
262              
263              
264             sub __modify_options {
265 0     0     my ( $self ) = @_;
266 0 0 0       if ( defined $self->{max_cols} && $self->{max_cols} == 1 ) {
267 0           $self->{layout} = 2;
268             }
269 0 0 0       if ( $self->{max_height} && $self->{max_height} < $self->{keep} ) {
270 0           $self->{keep} = $self->{max_height};
271             }
272 0 0 0       if ( length $self->{footer} && $self->{page} != 2 ) {
273 0           $self->{page} = 2;
274             }
275 0 0 0       if ( $self->{page} == 2 && ! $self->{clear_screen} ) {
276 0           $self->{clear_screen} = 1;
277             }
278 0 0 0       if ( $self->{max_cols} && $self->{layout} == 1 ) {
279 0           $self->{layout} = 0;
280             }
281 0 0         if ( ! defined $self->{prompt} ) {
282 0 0         $self->{prompt} = defined $self->{wantarray} ? 'Your choice:' : 'Close with ENTER';
283             }
284 0 0         if ( defined $self->{margin} ) {
285 0           ( $self->{margin_top}, $self->{margin_right}, $self->{margin_bottom}, $self->{margin_left} ) = @{$self->{margin}};
  0            
286             }
287 0   0       $self->{margin_top} //= 0;
288 0   0       $self->{margin_right} //= 0;
289 0   0       $self->{margin_bottom} //= 0;
290 0   0       $self->{margin_left} //= 0;
291             }
292              
293              
294             sub choose {
295 0 0   0 1   if ( ref $_[0] ne __PACKAGE__ ) {
296 0           my $ob = __PACKAGE__->new();
297 0           delete $ob->{backup_instance_defaults};
298 0           return $ob->__choose( @_ );
299             }
300 0           my $self = shift;
301 0           return $self->__choose( @_ );
302             }
303              
304              
305             sub __choose {
306 0     0     my $self = shift;
307 0           my ( $orig_list_ref, $opt ) = @_;
308 0 0 0       croak "choose: called with " . @_ . " arguments - 1 or 2 arguments expected" if @_ < 1 || @_ > 2;
309 0 0         croak "choose: the first argument must be an ARRAY reference" if ref $orig_list_ref ne 'ARRAY';
310 0 0         if ( defined $opt ) {
311 0 0         croak "choose: the (optional) second argument must be a HASH reference" if ref $opt ne 'HASH';
312 0           validate_options( _valid_options(), $opt, 'choose' );
313 0           for my $key ( keys %$opt ) {
314 0 0         $self->{$key} = $opt->{$key} if defined $opt->{$key};
315             }
316             }
317 0 0         if ( ! @$orig_list_ref ) {
318 0           return;
319             }
320 0           local $\ = undef;
321 0           local $, = undef;
322 0           local $| = 1;
323 0 0         if ( defined $self->{busy_string} ) {
324 0           print "\r" . clear_to_end_of_line();
325 0           print $self->{busy_string};
326             }
327 0           $self->{wantarray} = wantarray;
328 0           $self->__modify_options();
329 0 0         if ( $self->{mouse} ) {
330 0           require Term::Choose::Opt::Mouse;
331             }
332 0 0         if ( $^O eq 'MSWin32' ) {
333 0 0         print $opt->{codepage_mapping} ? "\e(K" : "\e(U";
334             }
335 0           $self->__copy_orig_list( $orig_list_ref );
336 0           $self->__length_list_elements();
337 0 0         if ( defined $self->{skip_items} ) {
338 0           require Term::Choose::Opt::SkipItems;
339 0           $self->Term::Choose::Opt::SkipItems::__prepare_default();
340             }
341 0 0         if ( exists $ENV{TC_RESET_AUTO_UP} ) {
342 0           $ENV{TC_RESET_AUTO_UP} = 0;
343             }
344             local $SIG{INT} = sub {
345 0     0     $self->__reset_term();
346 0           exit;
347 0           };
348 0           $self->__init_term();
349 0           ( $self->{term_width}, $self->{term_height} ) = get_term_size();
350 0           my $min_term_width = 3; # + 1
351 0           my $min_term_height = 2;
352 0 0 0       if ( $self->{term_width} < $min_term_width || $self->{term_height} < $min_term_height ) {
353 0           $self->__invalid_term_size( $min_term_width, $min_term_height, 0 );
354             }
355 0           $self->__wr_first_screen();
356 0           my $fast_page = 10;
357 0 0         if ( $self->{pp_count} > 10_000 ) {
358 0           $fast_page = 20;
359             }
360 0           my $saved_pos;
361              
362 0           GET_KEY: while ( 1 ) {
363 0           my $key = $self->__get_key();
364 0 0         if ( ! defined $key ) {
365 0           $self->__reset_term( 1 );
366 0           carp "EOT: $!";
367 0           return;
368             }
369 0           $self->{pressed_key} = $key;
370 0           my ( $new_term_width, $new_term_height ) = get_term_size();
371 0 0 0       if ( $new_term_width < $min_term_width || $new_term_height < $min_term_height ) {
372 0           $self->__invalid_term_size( $min_term_width, $min_term_height, 1 );
373             }
374 0 0 0       if ( $new_term_width != $self->{term_width} || $new_term_height != $self->{term_height} ) {
375 0 0         if ( $self->{ll} ) {
376 0           $self->__reset_term( 0 );
377 0           return -1;
378             }
379 0 0         if ( $new_term_width < $self->{term_width} ) {
380 0   0       my $up = $self->{i_row} + ( $self->{margin_top} // 0 );
381 0           for my $opt ( qw( info prompt ) ) {
382 0 0         next if ! $self->{$opt};
383 0           for my $row ( @{$self->{$opt . '_rows'}} ) {
  0            
384 0 0 0       $up++ and next if ! length $row;
385 0 0         $row =~ s/${\SGR_ES}//g if $self->{color}; # __modify_options() resets the rows
  0            
386 0           my $w = print_columns( $row );
387 0           $up += int( $w / ( $new_term_width + EXTRA_W ) );
388 0 0         $up++ if $w % ( $new_term_width + EXTRA_W );
389             }
390             }
391 0 0         $up++ if length $self->{search_info};
392 0           $self->{count_pre_rows} = $up;
393             }
394              
395 0           ( $self->{term_width}, $self->{term_height} ) = ( $new_term_width, $new_term_height );
396 0           $self->{col_width} = $self->{bu_col_width};
397 0           $self->__modify_options();
398 0           $self->{default} = $self->{rc2idx}[$self->{pos}[ROW]][$self->{pos}[COL]];
399 0 0 0       if ( $self->{wantarray} && @{$self->{marked}} ) {
  0            
400 0           $self->{mark} = $self->__marked_rc2idx();
401             }
402 0           my $up = $self->{i_row} + $self->{count_pre_rows};
403 0 0         if ( $up ) {
404 0           print up( $up );
405             }
406             # print "\r" . clear_to_end_of_screen();
407 0           $self->__wr_first_screen();
408 0           next GET_KEY;
409             }
410 0 0         next GET_KEY if $key == NEXT_get_key;
411 0 0         next GET_KEY if $key == KEY_Tilde;
412 0 0 0       if ( exists $ENV{TC_RESET_AUTO_UP} && $ENV{TC_RESET_AUTO_UP} == 0 ) {
413 0 0 0       if ( $key != LINE_FEED && $key != CARRIAGE_RETURN ) {
414 0           $ENV{TC_RESET_AUTO_UP} = 1;
415             }
416             }
417 0           my $page_step = 1;
418 0 0         if ( $key == VK_INSERT ) {
    0          
419 0 0         $page_step = $fast_page if $self->{first_page_row} - $fast_page * $self->{avail_height} >= 0;
420 0           $key = VK_PAGE_UP;
421             }
422             elsif ( $key == VK_DELETE ) {
423 0 0         $page_step = $fast_page if $self->{last_page_row} + $fast_page * $self->{avail_height} <= $#{$self->{rc2idx}};
  0            
424 0           $key = VK_PAGE_DOWN;
425             }
426 0 0 0       if ( $saved_pos && $key != VK_PAGE_UP && $key != CONTROL_B && $key != VK_PAGE_DOWN && $key != CONTROL_F ) {
      0        
      0        
      0        
427 0           $saved_pos = undef;
428             }
429             # $self->{rc2idx} holds the new list (AoA) formatted in "__list_idx2rc" appropriate to the chosen layout.
430             # $self->{rc2idx} does not hold the values directly but the respective list indexes from the original list.
431             # If the original list would be ( 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h' ) and the new formatted list should be
432             # a d g
433             # b e h
434             # c f
435             # then the $self->{rc2idx} would look like this
436             # 0 3 6
437             # 1 4 7
438             # 2 5
439             # So e.g. the second value in the second row of the new list would be $self->{list}[ $self->{rc2idx}[1][1] ].
440             # On the other hand the index of the last row of the new list would be $#{$self->{rc2idx}}
441             # or the index of the last column in the first row would be $#{$self->{rc2idx}[0]}.
442              
443 0 0 0       if ( $key == VK_DOWN || $key == KEY_j ) {
    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 0        
    0          
    0          
444 0 0 0       if ( ! $self->{rc2idx}[$self->{pos}[ROW]+1]
445             || ! $self->{rc2idx}[$self->{pos}[ROW]+1][$self->{pos}[COL]]
446             ) {
447 0           $self->__beep();
448             }
449             else {
450 0           $self->{pos}[ROW]++;
451 0 0         if ( $self->{pos}[ROW] <= $self->{last_page_row} ) {
452 0           $self->__wr_cell( $self->{pos}[ROW] - 1, $self->{pos}[COL] );
453 0           $self->__wr_cell( $self->{pos}[ROW] , $self->{pos}[COL] );
454             }
455             else {
456 0           $self->{first_page_row} = $self->{last_page_row} + 1;
457 0           $self->{last_page_row} = $self->{last_page_row} + $self->{avail_height};
458 0 0         $self->{last_page_row} = $#{$self->{rc2idx}} if $self->{last_page_row} > $#{$self->{rc2idx}};
  0            
  0            
459 0           $self->__wr_screen();
460             }
461             }
462             }
463             elsif ( $key == VK_UP || $key == KEY_k ) {
464 0 0         if ( $self->{pos}[ROW] == 0 ) {
465 0           $self->__beep();
466             }
467             else {
468 0           $self->{pos}[ROW]--;
469 0 0         if ( $self->{pos}[ROW] >= $self->{first_page_row} ) {
470 0           $self->__wr_cell( $self->{pos}[ROW] + 1, $self->{pos}[COL] );
471 0           $self->__wr_cell( $self->{pos}[ROW] , $self->{pos}[COL] );
472             }
473             else {
474 0           $self->{last_page_row} = $self->{first_page_row} - 1;
475 0           $self->{first_page_row} = $self->{first_page_row} - $self->{avail_height};
476 0 0         $self->{first_page_row} = 0 if $self->{first_page_row} < 0;
477 0           $self->__wr_screen();
478             }
479             }
480             }
481             elsif ( $key == KEY_TAB || $key == CONTROL_I ) { # KEY_TAB == CONTROL_I
482 0 0 0       if ( $self->{pos}[ROW] == $#{$self->{rc2idx}}
  0            
483 0           && $self->{pos}[COL] == $#{$self->{rc2idx}[$self->{pos}[ROW]]}
484             ) {
485 0           $self->__beep();
486             }
487             else {
488 0 0         if ( $self->{pos}[COL] < $#{$self->{rc2idx}[$self->{pos}[ROW]]} ) {
  0            
489 0           $self->{pos}[COL]++;
490 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] - 1 );
491 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] );
492             }
493             else {
494 0           $self->{pos}[ROW]++;
495 0 0         if ( $self->{pos}[ROW] <= $self->{last_page_row} ) {
496 0           $self->{pos}[COL] = 0;
497 0           $self->__wr_cell( $self->{pos}[ROW] - 1, $#{$self->{rc2idx}[$self->{pos}[ROW] - 1]} );
  0            
498 0           $self->__wr_cell( $self->{pos}[ROW] , $self->{pos}[COL] );
499             }
500             else {
501 0           $self->{first_page_row} = $self->{last_page_row} + 1;
502 0           $self->{last_page_row} = $self->{last_page_row} + $self->{avail_height};
503 0 0         $self->{last_page_row} = $#{$self->{rc2idx}} if $self->{last_page_row} > $#{$self->{rc2idx}};
  0            
  0            
504 0           $self->{pos}[COL] = 0;
505 0           $self->__wr_screen();
506             }
507             }
508             }
509             }
510             elsif ( $key == KEY_BSPACE || $key == KEY_BTAB || $key == CONTROL_H ) { # KEY_BTAB == CONTROL_H
511 0 0 0       if ( $self->{pos}[COL] == 0 && $self->{pos}[ROW] == 0 ) {
512 0           $self->__beep();
513             }
514             else {
515 0 0         if ( $self->{pos}[COL] > 0 ) {
516 0           $self->{pos}[COL]--;
517 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] + 1 );
518 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] );
519             }
520             else {
521 0           $self->{pos}[ROW]--;
522 0 0         if ( $self->{pos}[ROW] >= $self->{first_page_row} ) {
523 0           $self->{pos}[COL] = $#{$self->{rc2idx}[$self->{pos}[ROW]]};
  0            
524 0           $self->__wr_cell( $self->{pos}[ROW] + 1, 0 );
525 0           $self->__wr_cell( $self->{pos}[ROW] , $self->{pos}[COL] );
526             }
527             else {
528 0           $self->{last_page_row} = $self->{first_page_row} - 1;
529 0           $self->{first_page_row} = $self->{first_page_row} - $self->{avail_height};
530 0 0         $self->{first_page_row} = 0 if $self->{first_page_row} < 0;
531 0           $self->{pos}[COL] = $#{$self->{rc2idx}[$self->{pos}[ROW]]};
  0            
532 0           $self->__wr_screen();
533             }
534             }
535             }
536             }
537             elsif ( $key == VK_RIGHT || $key == KEY_l ) {
538 0 0         if ( $self->{pos}[COL] == $#{$self->{rc2idx}[$self->{pos}[ROW]]} ) {
  0            
539 0           $self->__beep();
540             }
541             else {
542 0           $self->{pos}[COL]++;
543 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] - 1 );
544 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] );
545             }
546             }
547             elsif ( $key == VK_LEFT || $key == KEY_h ) {
548 0 0         if ( $self->{pos}[COL] == 0 ) {
549 0           $self->__beep();
550             }
551             else {
552 0           $self->{pos}[COL]--;
553 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] + 1 );
554 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] );
555             }
556             }
557             elsif ( $key == VK_PAGE_UP || $key == CONTROL_P ) {
558 0 0         if ( $self->{first_page_row} <= 0 ) {
559 0           $self->__beep();
560             }
561             else {
562 0           $self->{first_page_row} = $self->{avail_height} * ( int( $self->{pos}[ROW] / $self->{avail_height} ) - $page_step );
563 0           $self->{last_page_row} = $self->{first_page_row} + $self->{avail_height} - 1;
564 0 0         if ( $saved_pos ) {
565 0           $self->{pos}[ROW] = $saved_pos->[ROW] + $self->{first_page_row};
566 0           $self->{pos}[COL] = $saved_pos->[COL];
567 0           $saved_pos = undef;
568             }
569             else {
570 0           $self->{pos}[ROW] -= $self->{avail_height} * $page_step;
571             }
572 0           $self->__wr_screen();
573             }
574             }
575             elsif ( $key == VK_PAGE_DOWN || $key == CONTROL_N ) {
576 0 0         if ( $self->{last_page_row} >= $#{$self->{rc2idx}} ) {
  0            
577 0           $self->__beep();
578             }
579             else {
580 0           my $backup_p_begin = $self->{first_page_row};
581 0           $self->{first_page_row} = $self->{avail_height} * ( int( $self->{pos}[ROW] / $self->{avail_height} ) + $page_step );
582 0           $self->{last_page_row} = $self->{first_page_row} + $self->{avail_height} - 1;
583 0 0         $self->{last_page_row} = $#{$self->{rc2idx}} if $self->{last_page_row} > $#{$self->{rc2idx}};
  0            
  0            
584 0 0 0       if ( $self->{pos}[ROW] + $self->{avail_height} > $#{$self->{rc2idx}}
  0            
585 0           || $self->{pos}[COL] > $#{$self->{rc2idx}[$self->{pos}[ROW] + $self->{avail_height}]}
586             ) {
587 0           $saved_pos = [ $self->{pos}[ROW] - $backup_p_begin, $self->{pos}[COL] ];
588 0           $self->{pos}[ROW] = $#{$self->{rc2idx}};
  0            
589 0 0         if ( $self->{pos}[COL] > $#{$self->{rc2idx}[$self->{pos}[ROW]]} ) {
  0            
590 0           $self->{pos}[COL] = $#{$self->{rc2idx}[$self->{pos}[ROW]]};
  0            
591             }
592             }
593             else {
594 0           $self->{pos}[ROW] += $self->{avail_height} * $page_step;
595             }
596 0           $self->__wr_screen();
597             }
598             }
599             elsif ( $key == VK_HOME || $key == CONTROL_A ) {
600 0 0 0       if ( $self->{pos}[COL] == 0 && $self->{pos}[ROW] == 0 ) {
601 0           $self->__beep();
602             }
603             else {
604 0           $self->{pos}[ROW] = 0;
605 0           $self->{pos}[COL] = 0;
606 0           $self->{first_page_row} = 0;
607 0           $self->{last_page_row} = $self->{first_page_row} + $self->{avail_height} - 1;
608 0 0         $self->{last_page_row} = $#{$self->{rc2idx}} if $self->{last_page_row} > $#{$self->{rc2idx}};
  0            
  0            
609 0           $self->__wr_screen();
610             }
611             }
612             elsif ( $key == VK_END || $key == CONTROL_E ) {
613 0 0 0       if ( $self->{order} == 1 && $self->{idx_of_last_col_in_last_row} < $#{$self->{rc2idx}[0]} ) {
  0            
614 0 0 0       if ( $self->{pos}[ROW] == $#{$self->{rc2idx}} - 1
  0            
615 0           && $self->{pos}[COL] == $#{$self->{rc2idx}[$self->{pos}[ROW]]}
616             ) {
617 0           $self->__beep();
618             }
619             else {
620 0   0       $self->{first_page_row} = @{$self->{rc2idx}} - ( @{$self->{rc2idx}} % $self->{avail_height} || $self->{avail_height} );
  0            
621 0           $self->{pos}[ROW] = $#{$self->{rc2idx}} - 1;
  0            
622 0           $self->{pos}[COL] = $#{$self->{rc2idx}[$self->{pos}[ROW]]};
  0            
623 0 0         if ( $self->{first_page_row} == $#{$self->{rc2idx}} ) {
  0            
624 0           $self->{first_page_row} = $self->{first_page_row} - $self->{avail_height};
625 0           $self->{last_page_row} = $self->{first_page_row} + $self->{avail_height} - 1;
626             }
627             else {
628 0           $self->{last_page_row} = $#{$self->{rc2idx}};
  0            
629             }
630 0           $self->__wr_screen();
631             }
632             }
633             else {
634 0 0 0       if ( $self->{pos}[ROW] == $#{$self->{rc2idx}}
  0            
635 0           && $self->{pos}[COL] == $#{$self->{rc2idx}[$self->{pos}[ROW]]}
636             ) {
637 0           $self->__beep();
638             }
639             else {
640 0   0       $self->{first_page_row} = @{$self->{rc2idx}} - ( @{$self->{rc2idx}} % $self->{avail_height} || $self->{avail_height} );
  0            
641 0           $self->{last_page_row} = $#{$self->{rc2idx}};
  0            
642 0           $self->{pos}[ROW] = $#{$self->{rc2idx}};
  0            
643 0           $self->{pos}[COL] = $#{$self->{rc2idx}[$self->{pos}[ROW]]};
  0            
644 0           $self->__wr_screen();
645             }
646             }
647             }
648             elsif ( $key == KEY_q || $key == CONTROL_Q ) {
649 0           $self->__reset_term( 1 );
650 0           return;
651             }
652             elsif ( $key == CONTROL_C ) {
653 0           $self->__reset_term( 1 );
654 0           print STDERR "^C\n";
655 0           exit 1;
656             }
657             elsif ( $key == LINE_FEED || $key == CARRIAGE_RETURN ) { # LINE_FEED == CONTROL_J, CARRIAGE_RETURN == CONTROL_M # ENTER key
658 0 0         if ( length $self->{search_info} ) {
659 0           require Term::Choose::Opt::Search;
660 0           $self->Term::Choose::Opt::Search::__search_end();
661 0           next GET_KEY;
662             }
663 0   0       my $opt_index = $self->{index} || $self->{ll};
664 0           my $list_idx = $self->{rc2idx}[$self->{pos}[ROW]][$self->{pos}[COL]];
665 0 0         if ( ! defined $self->{wantarray} ) {
    0          
666 0           $self->__reset_term( 1 );
667 0           return;
668             }
669             elsif ( $self->{wantarray} ) {
670 0 0         if ( $self->{include_highlighted} == 1 ) {
    0          
671 0           $self->{marked}[$self->{pos}[ROW]][$self->{pos}[COL]] = 1;
672             }
673             elsif ( $self->{include_highlighted} == 2 ) {
674 0           my $chosen = $self->__marked_rc2idx();
675 0 0         if ( ! @$chosen ) {
676 0           $self->{marked}[$self->{pos}[ROW]][$self->{pos}[COL]] = 1;
677             }
678             }
679 0 0 0       if ( defined $self->{meta_items} && ! $self->{marked}[$self->{pos}[ROW]][$self->{pos}[COL]] ) {
680 0           for my $meta_item ( @{$self->{meta_items}} ) {
  0            
681 0 0         if ( $meta_item == $list_idx ) {
682 0           $self->{marked}[$self->{pos}[ROW]][$self->{pos}[COL]] = 1;
683 0           last;
684             }
685             }
686             }
687 0           my $chosen = $self->__marked_rc2idx();
688 0           $self->__reset_term( 1 );
689 0 0         return $opt_index ? @$chosen : @{$orig_list_ref}[@$chosen];
  0            
690             }
691             else {
692 0 0         my $chosen = $opt_index ? $list_idx : $orig_list_ref->[$list_idx];
693 0           $self->__reset_term( 1 );
694 0           return $chosen;
695             }
696             }
697             elsif ( $key == KEY_SPACE ) {
698 0 0         if ( $self->{wantarray} ) {
699 0           my $list_idx = $self->{rc2idx}[$self->{pos}[ROW]][$self->{pos}[COL]];
700 0           my $locked = 0;
701 0 0 0       if ( defined $self->{no_spacebar} || defined $self->{meta_items} ) {
702 0 0         for my $no_spacebar ( @{$self->{no_spacebar}||[]}, @{$self->{meta_items}||[]} ) {
  0 0          
  0            
703 0 0         if ( $list_idx == $no_spacebar ) {
704 0           ++$locked;
705 0           last;
706             }
707             }
708             }
709 0 0         if ( $locked ) {
710 0           $self->__beep();
711             }
712             else {
713 0           $self->{marked}[$self->{pos}[ROW]][$self->{pos}[COL]] = ! $self->{marked}[$self->{pos}[ROW]][$self->{pos}[COL]];
714 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] );
715             }
716             }
717             else {
718 0           $self->__beep();
719             }
720             }
721             elsif ( $key == CONTROL_SPACE ) {
722 0 0         if ( $self->{wantarray} ) {
723 0           for my $i ( 0 .. $#{$self->{rc2idx}} ) {
  0            
724 0           for my $j ( 0 .. $#{$self->{rc2idx}[$i]} ) {
  0            
725 0           $self->{marked}[$i][$j] = ! $self->{marked}[$i][$j];
726             }
727             }
728 0 0         if ( $self->{skip_items} ) {
729 0           $self->Term::Choose::Opt::SkipItems::__unmark_skip_items();
730             }
731 0 0         if ( defined $self->{no_spacebar} ) {
732 0           $self->__marked_idx2rc( $self->{no_spacebar}, 0 );
733             }
734 0 0         if ( defined $self->{meta_items} ) {
735 0           $self->__marked_idx2rc( $self->{meta_items}, 0 );
736             }
737              
738 0           $self->__wr_screen();
739             }
740             else {
741 0           $self->__beep();
742             }
743             }
744             elsif ( $key == CONTROL_F && $self->{search} ) {
745 0           require Term::Choose::Opt::Search;
746 0 0         if ( $self->{ll} ) {
747 0           $ENV{TC_POS_AT_SEARCH} = $self->{rc2idx}[$self->{pos}[ROW]][$self->{pos}[COL]];
748 0           $self->__reset_term( 0 );
749 0           return -13;
750             }
751 0 0         if ( length $self->{search_info} ) {
752 0           $self->Term::Choose::Opt::Search::__search_end();
753             }
754 0           $self->Term::Choose::Opt::Search::__search_begin();
755             }
756             else {
757 0           $self->__beep();
758             }
759             }
760             }
761              
762              
763             sub __beep {
764 0     0     my ( $self, $beep ) = @_;
765 0 0         if ( $beep ) {
766 0           print bell();
767             }
768             }
769              
770              
771             sub __wr_first_screen {
772 0     0     my ( $self ) = @_;
773 0           $self->__avail_screen_size();
774 0           $self->__current_layout();
775 0           $self->__list_idx2rc();
776 0           $self->__prepare_footer_line();
777 0           $self->{first_page_row} = 0;
778 0 0         if ( $#{$self->{rc2idx}} > $self->{avail_height} - 1 ) {
  0            
779 0           $self->{last_page_row} = $self->{avail_height} - 1;
780             }
781             else {
782 0           $self->{last_page_row} = $#{$self->{rc2idx}};
  0            
783             }
784 0           $self->{i_row} = 0;
785 0           $self->{i_col} = 0;
786 0           $self->{pos} = [ 0, 0 ];
787 0           $self->{marked} = [];
788 0 0 0       if ( $self->{wantarray} && defined $self->{mark} ) {
789 0           $self->__marked_idx2rc( $self->{mark}, 1 );
790             }
791 0 0 0       if ( defined $self->{default} && $self->{default} <= $#{$self->{list}} ) {
  0            
792 0           $self->__set_cell( $self->{default} );
793             }
794 0 0         if ( $self->{clear_screen} ) {
795 0           print clear_screen();
796             }
797             else {
798 0           print "\r" . clear_to_end_of_screen();
799             }
800 0           my $pre_string;
801 0 0         $pre_string .= "\n" x $self->{margin_top} if $self->{margin_top};
802 0 0         $pre_string .= join( "\n\r", @{$self->{info_rows}} ) . "\n\r" if $self->{info_rows};
  0            
803 0 0         $pre_string .= join( "\n\r", @{$self->{prompt_rows}} ) . "\n\r" if $self->{prompt_rows};
  0            
804 0 0         if ( length $self->{search_info} ) {
805 0           my $search_info = ( ' ' x $self->{margin_left} ) . $self->{search_info};
806 0 0         if ( print_columns( $search_info ) > $self->{term_width} + EXTRA_W ) {
807 0           $pre_string .= cut_to_printwidth( $search_info, $self->{term_width} + EXTRA_W - 3 ) . "...\n\r";
808             }
809             else {
810 0           $pre_string .= $search_info . "\n\r";
811             }
812             }
813             # \n\r -> stty 'raw' mode and Term::Readkey 'ultra-raw' mode don't translate newline to carriage_return/newline
814 0 0         if ( length $pre_string ) {
815 0           print $pre_string;
816             }
817 0           $self->__wr_screen();
818 0 0         if ( $self->{mouse} ) {
819 0           my $abs_cursor_y = $self->{plugin}->__get_cursor_row();
820 0           $self->{offset_rows} = $abs_cursor_y - 1 - $self->{i_row};
821             }
822             }
823              
824              
825             sub __avail_screen_size {
826 0     0     my ( $self ) = @_;
827 0           $self->{avail_width} = $self->{term_width};
828 0           $self->{extra_w} = 0;
829 0 0 0       if ( $self->{margin_right} || $self->{col_width} > $self->{avail_width} ) {
830             # When reducing the right margin, ensure that it does not go below 1 if it was originally
831             # set to 1 or greater, so that EXTRA_W does not have to be removed.
832 0           $self->{extra_w} = EXTRA_W;
833 0           $self->{avail_width} += $self->{extra_w};
834             # + EXTRA_W: use also the last terminal column if there is only one item-column;
835             # with only one item-column the output doesn't get messed up if an item
836             # reaches the right edge of the terminal on a non-MSWin32-OS (EXTRA_W is 0 if OS is MSWin32)
837             }
838 0           my $reduce = $self->__check_horinzontal_margins();
839 0 0         if ( defined $reduce ) {
840 0 0 0       $self->{margin_left} = int( $self->{margin_left} * $reduce ) || 1 if $self->{margin_left};
841 0 0 0       $self->{margin_right} = int( $self->{margin_right} * $reduce ) || 1 if $self->{margin_right};
842             }
843 0 0         $self->{avail_width} -= $self->{margin_left} if $self->{margin_left};
844 0 0         $self->{avail_width} -= $self->{margin_right} if $self->{margin_right};
845 0           my $bu_max_width = $self->{max_width};
846 0 0 0       if ( $self->{max_width} && $self->{avail_width} > $self->{max_width} ) {
847 0           $self->{avail_width} = $self->{max_width};
848             }
849             else {
850 0           delete $self->{max_width};
851             }
852 0           $self->__fold_text( $reduce, 1 );
853 0           $self->{avail_height} = $self->{term_height};
854 0 0         $self->{avail_height} -= $self->{margin_top} if $self->{margin_top};
855 0 0         $self->{avail_height} -= @{$self->{info_rows}} if $self->{info_rows};
  0            
856 0 0         $self->{avail_height} -= @{$self->{prompt_rows}} if $self->{prompt_rows};
  0            
857 0 0         $self->{avail_height}-- if length $self->{search_info};
858 0 0         $self->{avail_height}-- if $self->{page};
859 0 0         $self->{avail_height} -= @{$self->{bottom_text_rows}} if $self->{bottom_text_rows};
  0            
860 0 0         $self->{avail_height} -= $self->{margin_bottom} if $self->{margin_bottom};
861 0 0         if ( $self->{avail_height} < $self->{keep} ) {
862 0           $self->__avail_height_to_keep();
863             }
864 0           $self->{max_width} = $bu_max_width;
865 0 0 0       if ( $self->{max_height} && $self->{avail_height} > $self->{max_height} ) {
866 0           $self->{avail_height} = $self->{max_height};
867             }
868 0   0       $self->{count_pre_rows} = $self->{margin_top} + @{$self->{info_rows}//[]} + @{$self->{prompt_rows}//[]};
  0   0        
  0            
869 0 0         if ( length $self->{search_info} ) {
870 0           ++$self->{count_pre_rows};
871             }
872             }
873              
874              
875             sub __reduce_horizontal_margin {
876 0     0     my ( $self, $avail_width ) = @_;
877 0 0         if ( $avail_width < 4 ) { return 0 }
  0 0          
    0          
    0          
    0          
    0          
    0          
    0          
    0          
878 0           elsif ( $avail_width < 9 ) { return 0.10 }
879 0           elsif ( $avail_width < 16 ) { return 0.20 }
880 0           elsif ( $avail_width < 23 ) { return 0.30 }
881 0           elsif ( $avail_width < 30 ) { return 0.40 }
882 0           elsif ( $avail_width < 37 ) { return 0.50 }
883 0           elsif ( $avail_width < 44 ) { return 0.60 }
884 0           elsif ( $avail_width < 51 ) { return 0.70 }
885 0           elsif ( $avail_width < 58 ) { return 0.80 }
886 0           return 0.90;
887             }
888              
889              
890             sub __check_horinzontal_margins {
891 0     0     my ( $self ) = @_;
892 0           my $threshold = 65;
893 0           my $reduce = 1;
894 0 0 0       if ( $self->{margin_left} || $self->{margin_right} ) {
895 0           my $tmp_avail_width = $self->{avail_width} - ( $self->{margin_left} + $self->{margin_right} );
896 0 0         if ( $tmp_avail_width < $threshold ) {
897 0           $reduce = $self->__reduce_horizontal_margin( $tmp_avail_width );
898             }
899             }
900              
901 0           for my $opt ( qw( info prompt bottom_text ) ) {
902 0 0 0       if ( length $self->{$opt} && defined $self->{'tabs_' . $opt} ) {
903 0           my $orig_tabs = 'tabs_' . $opt;
904 0           my $tmp_tabs = 'tmp_tabs_' . $opt; ##
905             $self->{$tmp_tabs} = [
906             $self->{$orig_tabs}[0] // 0, $self->{$orig_tabs}[1] // 0,
907             $self->{$orig_tabs}[2] // 0, $self->{$orig_tabs}[3] // 0,
908 0   0       $self->{$orig_tabs}[4]
      0        
      0        
      0        
909             ];
910 0 0         if ( $self->{$tmp_tabs}[4] ) {
911 0           ++$self->{tabs_with_max_width};
912             }
913 0 0 0       if ( $self->{$tmp_tabs}[2] || $self->{$tmp_tabs}[3] ) {
914 0           my $avail_text_width = ( $self->{term_width} + EXTRA_W ) - ( $self->{$tmp_tabs}[2] + $self->{$tmp_tabs}[3] );
915 0 0         if ( $avail_text_width < $threshold ) {
916 0           my $new_factor = $self->__reduce_horizontal_margin( $avail_text_width );
917 0 0         if ( $new_factor < $reduce ) {
918 0           $reduce = $new_factor;
919             }
920             }
921 0           ++$self->{tabs_width_horizontal_margin};
922             }
923             }
924             }
925 0 0         if ( $reduce < 1 ) {
926 0           return $reduce;
927             }
928 0           return;
929             }
930              
931              
932             sub __fold_text {
933 0     0     my ( $self, $reduce, $initialize_text_rows ) = @_;
934              
935 0           for my $opt ( qw( info prompt bottom_text ) ) {
936 0 0         if ( length $self->{$opt} ) {
937 0           my ( $init, $subseq, $l_margin, $r_margin ) = ( 0, 0, 0, 0 );
938 0           my $max_width;
939 0           my $tmp_tabs = 'tmp_tabs_' . $opt;
940 0 0         if ( defined $self->{$tmp_tabs} ) {
941 0 0         $init = $self->{$tmp_tabs}[0] if $self->{$tmp_tabs}[0];
942 0 0         $subseq = $self->{$tmp_tabs}[1] if $self->{$tmp_tabs}[1];
943 0 0         if ( ! defined $reduce ) {
    0          
944 0           $l_margin = $self->{$tmp_tabs}[2];
945 0           $r_margin = $self->{$tmp_tabs}[3];
946             }
947             elsif ( $reduce == 0 ) {
948 0 0         $l_margin = $self->{$tmp_tabs}[2] = 1 if $self->{$tmp_tabs}[2];
949 0 0         $r_margin = $self->{$tmp_tabs}[3] = 1 if $self->{$tmp_tabs}[3];
950             }
951             else {
952 0 0 0       $l_margin = $self->{$tmp_tabs}[2] = int( $self->{$tmp_tabs}[2] * $reduce ) || 1 if $self->{$tmp_tabs}[2];
953 0 0 0       $r_margin = $self->{$tmp_tabs}[3] = int( $self->{$tmp_tabs}[3] * $reduce ) || 1 if $self->{$tmp_tabs}[3];
954             }
955 0 0 0       if ( $self->{$tmp_tabs}[4] && $self->{$tmp_tabs}[4] < ( $self->{term_width} + EXTRA_W ) - ( $l_margin + $r_margin ) ) {
956 0           $max_width = $self->{$tmp_tabs}[4];
957             }
958             #if ( $self->{$tmp_tabs}[4] ) {
959             # if ( $self->{$tmp_tabs}[4] < ( $self->{term_width} + EXTRA_W ) - ( $l_margin + $r_margin ) ) {
960             # $max_width = $self->{$tmp_tabs}[4];
961             # }
962             # else {
963             # $self->{$tmp_tabs}[4] = undef;
964             # # It should be OK because subsequent calls to '__fold_text' (in '__avail_height_to_keep')
965             # # do not reduce margins or call it once all max_width values have already been removed.
966             # }
967             #}
968             }
969             else {
970 0           $l_margin = $self->{margin_left};
971 0           $r_margin = $self->{margin_right};
972 0           $max_width = $self->{max_width};
973             }
974 0           my $width;
975 0 0         if ( $max_width ) {
976 0           $width = $l_margin + $max_width;
977             }
978             else {
979 0           $width = $self->{term_width} + EXTRA_W - $r_margin;
980             }
981 0           $init += $l_margin;
982 0           $subseq += $l_margin;
983 0 0 0       if ( $init > $width - 5 || $subseq > $width - 5 ) {
984 0           ( $init, $subseq ) = ( $l_margin, $l_margin );
985             }
986             my $fold_opt = {
987 0           width => $width, init_tab => $init, subseq_tab => $subseq, color => $self->{color}, join => 0, truncate_long_tabs => 0
988             };
989 0           my $text_opt_rows = $opt . '_rows';
990 0 0         if ( $initialize_text_rows ) {
991 0           $self->{$text_opt_rows} = [ line_fold( $self->{$opt}, $fold_opt ) ];
992             }
993             else {
994 0           my $prev_row_count = @{$self->{$text_opt_rows}};
  0            
995 0           $self->{$text_opt_rows} = [ line_fold( $self->{$opt}, $fold_opt ) ];
996 0           $self->{avail_height} += $prev_row_count - @{$self->{$text_opt_rows}};
  0            
997             }
998              
999             }
1000             }
1001             }
1002              
1003              
1004             sub __avail_height_to_keep {
1005 0     0     my ( $self ) = @_;
1006 0           my $keep = $self->{keep};
1007 0 0         if ( $self->{layout} == 2 ) {
1008 0 0         if ( $keep > @{$self->{list}} ) {
  0            
1009 0           $keep = @{$self->{list}};
  0            
1010             }
1011             }
1012             else {
1013 0           $keep = $self->__keep_to_row_count( $keep );
1014             }
1015 0 0         if ( $keep <= $self->{avail_height} ) {
1016 0           return;
1017             }
1018 0 0 0       if ( $self->{margin_top} || $self->{margin_bottom} ) {
1019              
1020 0           REDUCE: while ( 1 ) {
1021 0           my $prev_avail_height = $self->{avail_height};
1022              
1023 0           for my $margin_type ( qw( margin_top margin_bottom ) ) {
1024 0 0         if ( $self->{$margin_type} ) {
1025 0           --$self->{$margin_type};
1026 0           ++$self->{avail_height};
1027 0 0         last REDUCE if $self->{avail_height} == $keep;
1028             }
1029             }
1030 0 0         last REDUCE if $prev_avail_height == $self->{avail_height};
1031             }
1032             }
1033 0 0         if ( $keep <= $self->{avail_height} ) {
1034 0           return;
1035             }
1036 0 0 0       if ( $self->{max_width} || $self->{tabs_with_max_width} ) {
1037 0           my $avail_width = {};
1038 0           my $increase = { main => 0, info => 0, prompt => 0, bottom_text => 0 };
1039 0           my $steps = 10;
1040 0 0         if ( $self->{max_width} ) {
1041 0           $avail_width->{main} = ( $self->{term_width} + $self->{extra_w} ) - ( $self->{margin_left} + $self->{margin_right} );
1042 0           $increase->{main} = ( $avail_width->{main} - $self->{max_width} ) / $steps;
1043             }
1044              
1045 0           for my $opt ( qw( info prompt bottom_text ) ) {
1046 0           my $tmp_tabs = 'tmp_tabs_' . $opt;
1047 0 0 0       if ( defined $self->{$tmp_tabs} && $self->{$tmp_tabs}[4] ) {
1048 0           $avail_width->{$opt} = ( $self->{term_width} + EXTRA_W ) - ( $self->{$tmp_tabs}[2] + $self->{$tmp_tabs}[3] );
1049 0           $increase->{$opt} = ( $avail_width->{$opt} - $self->{$tmp_tabs}[4] ) / $steps;
1050             }
1051             }
1052              
1053 0           for my $s ( 1 .. $steps ) {
1054 0 0         if ( $increase->{main} > 0 ) { # increase < 0 if max_width > avail_width
1055 0 0         if ( $s == $steps ) {
1056 0           $self->{max_width} = $avail_width->{main};
1057             }
1058             else {
1059 0           $self->{max_width} += int $increase->{main};
1060             }
1061 0           $self->{avail_width} = $self->{max_width};
1062              
1063             }
1064 0           for my $opt ( qw( info prompt bottom_text ) ) {
1065 0 0         if ( $increase->{$opt} > 0 ) {
1066 0 0         if ( $s == $steps ) {
1067 0           $self->{'tmp_tabs_' . $opt}[4] = $avail_width->{$opt};
1068             }
1069             else {
1070 0           $self->{'tmp_tabs_' . $opt}[4] += int $increase->{$opt};
1071             }
1072             }
1073             }
1074 0           $self->__fold_text();
1075 0           $keep = $self->__keep_to_row_count( $keep );
1076 0 0         last if $keep <= $self->{avail_height};
1077             }
1078 0           $self->{max_width} = undef;
1079 0           for my $opt ( qw( info prompt bottom_text ) ) {
1080 0           my $tmp_tabs = 'tmp_tabs_' . $opt;
1081 0 0 0       if ( defined $self->{$tmp_tabs} && $self->{$tmp_tabs}[4] ) {
1082 0           $self->{$tmp_tabs}[4] = undef; ##
1083             }
1084             }
1085             }
1086 0 0         if ( $keep <= $self->{avail_height} ) {
1087 0           return;
1088             }
1089 0 0 0       if ( $self->{margin_right} || $self->{margin_left} || $self->{tabs_width_horizontal_margin} ) {
      0        
1090 0           my $orig_margin_right = $self->{margin_right};
1091 0           my $orig_margin_left = $self->{margin_left};
1092 0           my $orig_avail_width = $self->{avail_width};
1093              
1094 0           for my $reduce ( 0.80, 0.75, 0.6667, 0.5, 0 ) { # 4/5, 3/4, 2/3, 1/2, 0
1095 0 0         if ( $reduce == 0 ) {
1096 0 0         $self->{margin_right} = $self->{margin_right} ? 1 : 0;
1097 0 0         $self->{margin_left} = $self->{margin_left} ? 1 : 0;
1098             }
1099             else {
1100 0 0 0       $self->{margin_right} = int( $self->{margin_right} * $reduce ) || 1 if $self->{margin_right};
1101 0 0 0       $self->{margin_left} = int( $self->{margin_left} * $reduce ) || 1 if $self->{margin_left};
1102             }
1103             $self->{avail_width} = $orig_avail_width + ( $orig_margin_right - $self->{margin_right} )
1104 0           + ( $orig_margin_left - $self->{margin_left} );
1105 0           $self->__fold_text( $reduce );
1106 0           $keep = $self->__keep_to_row_count( $keep );
1107 0 0         last if $keep <= $self->{avail_height}; ##
1108             }
1109             }
1110 0 0         if ( $keep <= $self->{avail_height} ) {
1111 0           return;
1112             }
1113 0 0         if ( $self->{info_rows} ) {
1114             # Text rows above the menu (info_rows, prompt_rows, search_info) are not deleted. They are pushed upwards
1115             # out of the terminal when the space reserved for them is deleted.
1116 0 0         if ( @{$self->{info_rows}} >= $keep - $self->{avail_height} ) {
  0            
1117 0           $self->{avail_height} = $keep;
1118             }
1119             else {
1120 0           $self->{avail_height} += @{$self->{info_rows}};
  0            
1121             }
1122             }
1123 0 0         if ( $keep <= $self->{avail_height} ) {
1124 0           return;
1125             }
1126 0 0         if ( $self->{bottom_text_rows} ) {
1127             # bottom_text_rows are deleted because they are below the menu.
1128 0 0         if ( @{$self->{bottom_text_rows}} > $keep - $self->{avail_height} ) {
  0            
1129 0           splice( @{$self->{bottom_text_rows}}, -( $keep - $self->{avail_height} ) );
  0            
1130 0           $self->{avail_height} = $keep;
1131 0           my $avail_w;
1132 0 0         if ( defined $self->{tmp_tabs_bottom_text} ) {
1133 0           $avail_w = $self->{term_width} + EXTRA_W - $self->{tmp_tabs_bottom_text}[3];
1134             }
1135             else {
1136 0           $avail_w = $self->{margin_left} + $self->{avail_width};
1137             }
1138 0           my $ellipsis = '...';
1139 0           my $ellipsis_w = length $ellipsis;
1140 0 0         if ( $avail_w >= $ellipsis_w ) {
1141 0           while ( print_columns( $self->{bottom_text_rows}[-1] ) + $ellipsis_w > $avail_w ) {
1142 0           $self->{bottom_text_rows}[-1] =~ s/.\z//;
1143             }
1144 0           $self->{bottom_text_rows}[-1] .= $ellipsis;
1145             }
1146             }
1147             else {
1148 0           $self->{avail_height} += @{$self->{bottom_text_rows}};
  0            
1149 0           delete $self->{bottom_text_rows};
1150             }
1151             }
1152 0 0         if ( $keep <= $self->{avail_height} ) {
1153 0           return;
1154             }
1155 0 0         if ( $self->{prompt_rows} ) {
1156 0 0         if ( @{$self->{prompt_rows}} > $keep - $self->{avail_height} ) {
  0            
1157 0           $self->{avail_height} = $keep;
1158             }
1159             else {
1160 0 0         if ( $self->{avail_height} + @{$self->{prompt_rows}} > 4 ) {
  0            
1161             # keep the last prompt line
1162 0           $self->{avail_height} += @{$self->{prompt_rows}} - 1;
  0            
1163 0           --$keep;
1164             }
1165             else {
1166 0           $self->{avail_height} += @{$self->{prompt_rows}};
  0            
1167             }
1168             }
1169             }
1170 0 0         if ( $keep <= $self->{avail_height} ) {
1171 0           return;
1172             }
1173 0 0         if ( length $self->{search_info} ) {
1174 0 0         if ( $self->{avail_height} > 2 ) {
1175 0           --$keep;
1176             }
1177             else {
1178 0           ++$self->{avail_height};
1179             }
1180             }
1181 0 0         if ( $keep <= $self->{avail_height} ) {
1182 0           return;
1183             }
1184 0 0         if ( $self->{page} ) {
1185 0           --$keep;
1186             }
1187             #if ( $self->{page} ) {
1188             # if ( $self->{avail_height} > 1 ) {
1189             # --$keep;
1190             # }
1191             # else {
1192             # ++$self->{avail_height};
1193             # delete $self->{footer_fmt};
1194             # }
1195             #}
1196             }
1197              
1198              
1199             sub __keep_to_row_count {
1200 0     0     my ( $self, $keep ) = @_;
1201 0           my $row_w = $self->{col_width};
1202 0           my $count = 1;
1203              
1204 0           while ( 1 ) { ##
1205 0           $row_w += $self->{pad} + $self->{col_width};
1206 0 0         if ( $row_w >= $self->{avail_width} ) {
1207 0           last;
1208             }
1209 0           ++$count;
1210             }
1211 0           my $rows = int( @{$self->{list}} / $count );
  0            
1212 0 0         if ( @{$self->{list}} % $count ) {
  0            
1213 0           ++$rows;
1214             }
1215 0 0         if ( $keep > $rows ) {
1216 0           $keep = $rows;
1217             }
1218 0           return $keep;
1219             }
1220              
1221              
1222             sub __current_layout {
1223 0     0     my ( $self ) = @_;
1224 0           my $all_in_first_row;
1225 0 0 0       if ( $self->{layout} <= 1 && ! $self->{ll} && ! $self->{max_cols} ) {
      0        
1226 0           my $firstrow_width = 0;
1227 0           for my $list_idx ( 0 .. $#{$self->{list}} ) {
  0            
1228 0           $firstrow_width += $self->{width_elements}[$list_idx] + $self->{pad};
1229 0 0         if ( $firstrow_width - $self->{pad} > $self->{avail_width} ) {
1230 0           $firstrow_width = 0;
1231 0           last;
1232             }
1233             }
1234 0           $all_in_first_row = $firstrow_width;
1235             }
1236 0 0         if ( $all_in_first_row ) {
    0          
1237 0           $self->{current_layout} = -1;
1238             }
1239             elsif ( $self->{col_width} >= $self->{avail_width} ) {
1240 0           $self->{current_layout} = 2;
1241 0           $self->{col_width} = $self->{avail_width};
1242             }
1243             else {
1244 0           $self->{current_layout} = $self->{layout};
1245             }
1246 0           $self->{col_width_plus} = $self->{col_width} + $self->{pad};
1247             # 'col_width_plus' no effects if layout == 2
1248             }
1249              
1250              
1251             sub __list_idx2rc {
1252 0     0     my ( $self ) = @_;
1253 0           $self->{rc2idx} = [];
1254 0 0         if ( $self->{current_layout} == -1 ) {
    0          
1255 0           $self->{rc2idx}[0] = [ 0 .. $#{$self->{list}} ];
  0            
1256 0           $self->{idx_of_last_col_in_last_row} = $#{$self->{list}};
  0            
1257             }
1258             elsif ( $self->{current_layout} == 2 ) {
1259 0           for my $list_idx ( 0 .. $#{$self->{list}} ) {
  0            
1260 0           $self->{rc2idx}[$list_idx][0] = $list_idx;
1261 0           $self->{idx_of_last_col_in_last_row} = 0;
1262             }
1263             }
1264             else {
1265 0           my $tmp_avail_width = $self->{avail_width} + $self->{pad};
1266             # auto_format
1267 0 0         if ( $self->{current_layout} == 1 ) {
1268 0           my $tmc = int( @{$self->{list}} / $self->{avail_height} );
  0            
1269 0 0         $tmc++ if @{$self->{list}} % $self->{avail_height};
  0            
1270 0           $tmc *= $self->{col_width_plus};
1271 0 0         if ( $tmc < $tmp_avail_width ) {
1272 0           $tmc = int( $tmc + ( ( $tmp_avail_width - $tmc ) / 1.5 ) );
1273 0           $tmp_avail_width = $tmc;
1274             }
1275             }
1276             # order
1277 0           my $cols_per_row = int( $tmp_avail_width / $self->{col_width_plus} );
1278 0 0 0       if ( $self->{max_cols} && $cols_per_row > $self->{max_cols} ) {
1279 0           $cols_per_row = $self->{max_cols};
1280             }
1281 0 0         $cols_per_row = 1 if $cols_per_row < 1;
1282 0   0       $self->{idx_of_last_col_in_last_row} = ( @{$self->{list}} % $cols_per_row || $cols_per_row ) - 1;
1283 0 0         if ( $self->{order} == 1 ) {
1284 0           my $rows = int( ( @{$self->{list}} - 1 + $cols_per_row ) / $cols_per_row );
  0            
1285 0           my @rearranged_idx;
1286 0           my $begin = 0;
1287 0           my $end = $rows - 1 ;
1288 0           for my $c ( 0 .. $cols_per_row - 1 ) {
1289 0 0         --$end if $c > $self->{idx_of_last_col_in_last_row};
1290 0           $rearranged_idx[$c] = [ $begin .. $end ];
1291 0           $begin = $end + 1;
1292 0           $end = $begin + $rows - 1;
1293             }
1294 0           for my $r ( 0 .. $rows - 1 ) {
1295 0           my @temp_idx;
1296 0           for my $c ( 0 .. $cols_per_row - 1 ) {
1297 0 0 0       next if $r == $rows - 1 && $c > $self->{idx_of_last_col_in_last_row};
1298 0           push @temp_idx, $rearranged_idx[$c][$r];
1299             }
1300 0           push @{$self->{rc2idx}}, \@temp_idx;
  0            
1301             }
1302             }
1303             else {
1304 0           my $begin = 0;
1305 0           my $end = $cols_per_row - 1;
1306 0 0         $end = $#{$self->{list}} if $end > $#{$self->{list}};
  0            
  0            
1307 0           push @{$self->{rc2idx}}, [ $begin .. $end ];
  0            
1308 0           while ( $end < $#{$self->{list}} ) {
  0            
1309 0           $begin += $cols_per_row;
1310 0           $end += $cols_per_row;
1311 0 0         $end = $#{$self->{list}} if $end > $#{$self->{list}};
  0            
  0            
1312 0           push @{$self->{rc2idx}}, [ $begin .. $end ];
  0            
1313             }
1314             }
1315             }
1316             }
1317              
1318              
1319             sub __prepare_footer_line {
1320 0     0     my ( $self ) = @_;
1321 0 0         if ( exists $self->{footer_fmt} ) {
1322 0           delete $self->{footer_fmt};
1323             }
1324 0           my $pp_total = int( $#{$self->{rc2idx}} / $self->{avail_height} ) + 1; ##
  0            
1325 0 0 0       if ( $self->{page} == 0 ) {
    0          
1326             # nothing to do
1327             }
1328             elsif ( $self->{page} == 1 && $pp_total == 1 ) {
1329 0           $self->{avail_height}++;
1330             }
1331             else {
1332 0           my $pp_total_width = length $pp_total;
1333 0           $self->{footer_fmt} = '--- %0' . $pp_total_width . 'd/' . $pp_total . ' --- ';
1334 0 0         if ( defined $self->{footer} ) {
1335 0           $self->{footer_fmt} .= $self->{footer};
1336             }
1337 0 0         if ( print_columns( sprintf $self->{footer_fmt}, $pp_total ) > $self->{avail_width} ) { # color
1338 0           $self->{footer_fmt} = '%0' . $pp_total_width . 'd/' . $pp_total;
1339 0 0         if ( length( sprintf $self->{footer_fmt}, $pp_total ) > $self->{avail_width} ) {
1340 0 0         $pp_total_width = $self->{avail_width} if $pp_total_width > $self->{avail_width};
1341 0           $self->{footer_fmt} = '%0' . $pp_total_width . '.' . $pp_total_width . 's';
1342             }
1343             }
1344 0 0         if ( $self->{margin_left} ) {
1345 0           $self->{footer_fmt} = ( ' ' x $self->{margin_left} ) . $self->{footer_fmt};
1346             }
1347             }
1348 0           $self->{pp_count} = $pp_total;
1349             }
1350              
1351              
1352             sub __marked_idx2rc {
1353 0     0     my ( $self, $indexes, $boolean ) = @_;
1354 0           my $last_list_idx = $#{$self->{list}};
  0            
1355 0 0         if ( $self->{current_layout} == 2 ) {
1356 0           for my $list_idx ( @$indexes ) {
1357 0 0         if ( $list_idx > $last_list_idx ) {
1358 0           next;
1359             }
1360 0           $self->{marked}[$list_idx][0] = $boolean;
1361             }
1362 0           return;
1363             }
1364 0           my ( $row, $col );
1365 0           my $cols_per_row = @{$self->{rc2idx}[0]};
  0            
1366 0 0         if ( $self->{order} == 0 ) {
    0          
1367 0           for my $list_idx ( @$indexes ) {
1368 0 0         if ( $list_idx > $last_list_idx ) {
1369 0           next;
1370             }
1371 0           $row = int( $list_idx / $cols_per_row );
1372 0           $col = $list_idx % $cols_per_row;
1373 0           $self->{marked}[$row][$col] = $boolean;
1374             }
1375             }
1376             elsif ( $self->{order} == 1 ) {
1377 0           my $rows_per_col = @{$self->{rc2idx}};
  0            
1378 0           my $col_count_last_row = $self->{idx_of_last_col_in_last_row} + 1;
1379 0           my $last_list_idx_in_cols_full = $rows_per_col * $col_count_last_row - 1;
1380 0           my $first_list_idx_in_cols_short = $last_list_idx_in_cols_full + 1;
1381              
1382 0           for my $list_idx ( @$indexes ) {
1383 0 0         if ( $list_idx > $last_list_idx ) {
1384 0           next;
1385             }
1386 0 0         if ( $list_idx <= $last_list_idx_in_cols_full ) {
1387 0           $row = $list_idx % $rows_per_col;
1388 0           $col = int( $list_idx / $rows_per_col );
1389             }
1390             else {
1391 0           my $rows_per_col_short = $rows_per_col - 1;
1392 0           $row = ( $list_idx - $first_list_idx_in_cols_short ) % $rows_per_col_short;
1393 0           $col = int( ( $list_idx - $col_count_last_row ) / $rows_per_col_short );
1394             }
1395 0           $self->{marked}[$row][$col] = $boolean;
1396             }
1397             }
1398             }
1399              
1400              
1401             sub __set_cell {
1402 0     0     my ( $self, $list_idx ) = @_;
1403 0 0         if ( $self->{current_layout} == 2 ) {
1404 0           $self->{pos} = [ $list_idx, 0 ];
1405             }
1406             else {
1407 0           LOOP: for my $i ( 0 .. $#{$self->{rc2idx}} ) {
  0            
1408 0           for my $j ( 0 .. $#{$self->{rc2idx}[$i]} ) {
  0            
1409 0 0         if ( $list_idx == $self->{rc2idx}[$i][$j] ) {
1410 0           $self->{pos} = [ $i, $j ];
1411 0           last LOOP;
1412             }
1413             }
1414             }
1415             }
1416 0           $self->{first_page_row} = $self->{avail_height} * int( $self->{pos}[ROW] / $self->{avail_height} );
1417 0           $self->{last_page_row} = $self->{first_page_row} + $self->{avail_height} - 1;
1418 0 0         $self->{last_page_row} = $#{$self->{rc2idx}} if $self->{last_page_row} > $#{$self->{rc2idx}};
  0            
  0            
1419             }
1420              
1421              
1422             sub __marked_rc2idx {
1423 0     0     my ( $self ) = @_;
1424 0           my $list_idx = [];
1425 0 0         if ( $self->{order} == 1 ) {
1426 0           for my $col ( 0 .. $#{$self->{rc2idx}[0]} ) {
  0            
1427 0           for my $row ( 0 .. $#{$self->{rc2idx}} ) {
  0            
1428 0 0         if ( $self->{marked}[$row][$col] ) {
1429 0           push @$list_idx, $self->{rc2idx}[$row][$col];
1430             }
1431             }
1432             }
1433             }
1434             else {
1435 0           for my $row ( 0 .. $#{$self->{rc2idx}} ) {
  0            
1436 0           for my $col ( 0 .. $#{$self->{rc2idx}[$row]} ) {
  0            
1437 0 0         if ( $self->{marked}[$row][$col] ) {
1438 0           push @$list_idx, $self->{rc2idx}[$row][$col];
1439             }
1440             }
1441             }
1442             }
1443 0           return $list_idx;
1444             }
1445              
1446              
1447             sub __wr_screen {
1448 0     0     my ( $self ) = @_;
1449 0           $self->__goto( 0, 0 );
1450 0           print "\r" . clear_to_end_of_screen();
1451 0           my $line_feeds;
1452             #if ( $self->{footer_fmt} ) {
1453 0 0 0       if ( $self->{footer_fmt} || $self->{pp_count} > 1 ) { ##
1454 0           $line_feeds = $self->{avail_height};
1455             }
1456             else {
1457 0           $line_feeds = $self->{last_page_row} - $self->{first_page_row} + 1;
1458             }
1459 0           my $up = $line_feeds;
1460 0           my @post_rows;
1461 0 0         if ( $self->{footer_fmt} ) {
1462 0           @post_rows = ( sprintf $self->{footer_fmt}, int( $self->{first_page_row} / $self->{avail_height} ) + 1 );
1463 0           $up += 1;
1464             }
1465 0 0         if ( $self->{bottom_text_rows} ) {
1466 0           push @post_rows, @{$self->{bottom_text_rows}};
  0            
1467 0           $up += @{$self->{bottom_text_rows}};
  0            
1468             }
1469 0 0         if ( $self->{margin_bottom} ) {
1470 0           push @post_rows, ( '' ) x $self->{margin_bottom};
1471 0           $up += $self->{margin_bottom};
1472             }
1473 0           print "\n" x $line_feeds;
1474 0 0         if ( @post_rows ) {
1475             # no leading line-feed because the menu has a trailing line-feed.
1476 0           print join( "\n\r", @post_rows ) . "\r";
1477 0           --$up; # last @post_rows row has no trailing line-feed.
1478             }
1479 0           print up( $up );
1480 0           my $pad_str = ' ' x $self->{pad};
1481 0           my $left_margin = ' ' x $self->{margin_left};
1482              
1483 0           for my $row ( $self->{first_page_row} .. $self->{last_page_row} ) {
1484 0           my $line = $self->__prepare_cell( $row, 0 );
1485 0 0         if ( $#{$self->{rc2idx}[$row]} ) { #
  0            
1486 0           for my $col ( 1 .. $#{$self->{rc2idx}[$row]} ) {
  0            
1487 0           $line = $line . $pad_str . $self->__prepare_cell( $row, $col );
1488             }
1489             }
1490 0 0         if ( $left_margin ) {
1491 0           print $left_margin . $line . "\n\r";
1492             }
1493             else {
1494 0           print $line . "\n\r";
1495             }
1496             }
1497 0           print up( $self->{last_page_row} - $self->{first_page_row} + 1 );
1498             # relativ cursor pos: 0, 0
1499 0 0         if ( $self->{margin_left} ) {
1500 0           print right( $self->{margin_left} ); # set left margin after "\r"
1501             }
1502 0           $self->__wr_cell( $self->{pos}[ROW], $self->{pos}[COL] );
1503             }
1504              
1505              
1506             sub __prepare_cell {
1507 0     0     my( $self, $row, $col ) = @_;
1508 0   0       my $is_current_pos = $row == $self->{pos}[ROW] && $col == $self->{pos}[COL];
1509 0 0         my $emphasised = ( $self->{marked}[$row][$col] ? bold_underline() : '' ) . ( $is_current_pos ? reverse_video() : '' );
    0          
1510 0           my $idx = $self->{rc2idx}[$row][$col];
1511 0 0         if ( $self->{ll} ) {
1512 0 0         if ( $self->{color} ) {
1513 0           my $str = $self->{list}[$idx];
1514 0 0         if ( $emphasised ) {
1515 0 0 0       if ( $is_current_pos && $self->{color} == 1 ) {
1516             # no color for the selected cell if color == 1
1517 0           $str =~ s/${\SGR_ES}//g;
  0            
1518             }
1519             else {
1520             # keep marked cells marked after color escapes
1521 0           $str =~ s/(${\SGR_ES})/${1}$emphasised/g;
  0            
1522             }
1523 0           $str = $emphasised . $str;
1524             }
1525 0           return $str . normal();
1526             }
1527             else {
1528 0 0         if ( $emphasised ) {
1529 0           return $emphasised . $self->{list}[$idx] . normal();
1530             }
1531             else {
1532 0           return $self->{list}[$idx];
1533             }
1534             }
1535             }
1536             else {
1537 0 0         my $str = $self->{current_layout} == -1 ? $self->{list}[$idx] : $self->__pad_str_to_colwidth( $idx );
1538 0 0         if ( $self->{color} ) {
1539 0           my @color;
1540 0 0         if ( ! $self->{orig_list}[$idx] ) {
1541 0 0         if ( ! defined $self->{orig_list}[$idx] ) {
    0          
1542 0           @color = $self->{undef} =~ /(${\SGR_ES})/g;
  0            
1543             }
1544             elsif ( ! length $self->{orig_list}[$idx] ) {
1545 0           @color = $self->{empty} =~ /(${\SGR_ES})/g;
  0            
1546             }
1547             }
1548             else {
1549 0           @color = $self->{orig_list}[$idx] =~ /(${\SGR_ES})/g;
  0            
1550             }
1551 0 0         if ( $emphasised ) {
1552 0           for ( @color ) {
1553             # keep marked cells marked after color escapes
1554 0           $_ .= $emphasised;
1555             }
1556 0           $str = $emphasised . $str . normal();
1557 0 0 0       if ( $is_current_pos && $self->{color} == 1 ) {
1558             # no color for the selected cell if color == 1
1559 0           @color = ();
1560 0           $str =~ s/${\PH}//g;
  0            
1561             }
1562             }
1563 0 0         if ( @color ) {
1564 0           $str =~ s/${\PH}/shift @color/ge;
  0            
  0            
1565 0 0         if ( ! $emphasised ) {
1566 0           $str .= normal();
1567             }
1568             }
1569 0           return $str;
1570             }
1571             else {
1572 0 0         if ( $emphasised ) {
1573 0           $str = $emphasised . $str . normal();
1574             }
1575 0           return $str;
1576             }
1577             }
1578             }
1579              
1580              
1581             sub __wr_cell {
1582 0     0     my( $self, $row, $col ) = @_;
1583 0           my $idx = $self->{rc2idx}[$row][$col];
1584 0 0         if ( $self->{current_layout} == -1 ) {
1585 0           my $x = 0;
1586 0 0         if ( $col > 0 ) {
1587 0           for my $cl ( 0 .. $col - 1 ) {
1588 0           my $i = $self->{rc2idx}[$row][$cl];
1589 0           $x += $self->{width_elements}[$i] + $self->{pad};
1590             }
1591             }
1592 0           $self->__goto( $row - $self->{first_page_row}, $x );
1593 0           $self->{i_col} = $self->{i_col} + $self->{width_elements}[$idx];
1594             }
1595             else {
1596 0           $self->__goto( $row - $self->{first_page_row}, $col * $self->{col_width_plus} );
1597 0           $self->{i_col} = $self->{i_col} + $self->{col_width};
1598             }
1599 0           print $self->__prepare_cell( $row, $col );
1600             }
1601              
1602              
1603             sub __pad_str_to_colwidth {
1604 0     0     my ( $self, $idx ) = @_;
1605 0 0         if ( $self->{width_elements}[$idx] < $self->{col_width} ) {
    0          
1606 0 0         if ( $self->{alignment} == 0 ) {
    0          
    0          
1607 0           return $self->{list}[$idx] . ( " " x ( $self->{col_width} - $self->{width_elements}[$idx] ) );
1608             }
1609             elsif ( $self->{alignment} == 1 ) {
1610 0           return " " x ( $self->{col_width} - $self->{width_elements}[$idx] ) . $self->{list}[$idx];
1611             }
1612             elsif ( $self->{alignment} == 2 ) {
1613 0           my $all = $self->{col_width} - $self->{width_elements}[$idx];
1614 0           my $half = int( $all / 2 );
1615 0           return ( " " x $half ) . $self->{list}[$idx] . ( " " x ( $all - $half ) );
1616             }
1617             }
1618             elsif ( $self->{width_elements}[$idx] > $self->{col_width} ) {
1619 0 0         if ( $self->{col_width} > 6 ) {
1620 0           return cut_to_printwidth( $self->{list}[$idx], $self->{col_width} - 3 ) . '...';
1621             }
1622             else {
1623 0           return cut_to_printwidth( $self->{list}[$idx], $self->{col_width} );
1624             }
1625             }
1626             else {
1627 0           return $self->{list}[$idx];
1628             }
1629             }
1630              
1631              
1632             sub __goto {
1633 0     0     my ( $self, $newrow, $newcol ) = @_;
1634             # requires up, down, left or right to be 1 or greater
1635 0 0         if ( $newrow > $self->{i_row} ) {
    0          
1636 0           print down( $newrow - $self->{i_row} );
1637 0           $self->{i_row} = $newrow;
1638             }
1639             elsif ( $newrow < $self->{i_row} ) {
1640 0           print up( $self->{i_row} - $newrow );
1641 0           $self->{i_row} = $newrow;
1642             }
1643 0 0         if ( $newcol > $self->{i_col} ) {
    0          
1644 0           print right( $newcol - $self->{i_col} );
1645 0           $self->{i_col} = $newcol;
1646             }
1647             elsif ( $newcol < $self->{i_col} ) {
1648 0           print left( $self->{i_col} - $newcol );
1649 0           $self->{i_col} = $newcol;
1650             }
1651             }
1652              
1653              
1654              
1655              
1656             1;
1657              
1658              
1659             __END__