File Coverage

blib/lib/Term/ANSIMenu.pm
Criterion Covered Total %
statement 752 1410 53.3
branch 294 842 34.9
condition 10 99 10.1
subroutine 145 181 80.1
pod 0 101 0.0
total 1201 2633 45.6


line stmt bran cond sub pod time code
1             package Term::ANSIMenu;
2              
3 1     1   781324 use 5.006;
  1         5  
  1         51  
4 1     1   5 use strict;
  1         2  
  1         34  
5 1     1   6 use warnings;
  1         7  
  1         33  
6 1     1   5 use Carp;
  1         2  
  1         591  
7 1     1   6 use Term::ReadKey;
  1         2  
  1         329  
8              
9             our $VERSION = '0.02';
10              
11             #===============================================================================
12             #Constants
13             #===============================================================================
14              
15             #Screen control
16 1     1   6 use constant VT100 => "\x1B[61\"p";
  1         2  
  1         95  
17 1     1   6 use constant LINES => "\x1B(0";
  1         2  
  1         54  
18 1     1   5 use constant ASCII => "\x1B(B";
  1         1  
  1         47  
19 1     1   6 use constant WRAP_ON => "\x1B[?7h";
  1         2  
  1         74  
20 1     1   6 use constant WRAP_OFF => "\x1B[?7l";
  1         1  
  1         299  
21 1     1   6 use constant REGION_ON => "\x1B[?6h";
  1         2  
  1         61  
22 1     1   5 use constant REGION_OFF => "\x1B[?6l";
  1         2  
  1         51  
23              
24             #Deleting
25 1     1   5 use constant DEL_TO_END => "\x1B[0K";
  1         2  
  1         50  
26 1     1   59 use constant DEL_FROM_BEGIN => "\x1B[1K";
  1         36  
  1         49  
27 1     1   5 use constant DEL_LINE => "\x1B[2K";
  1         2  
  1         53  
28 1     1   5 use constant DEL_TO_EOS => "\x1B[0J";
  1         1  
  1         49  
29 1     1   5 use constant DEL_FROM_BOS => "\x1B[1J";
  1         2  
  1         42  
30 1     1   5 use constant DEL_SCREEN => "\x1B[2J";
  1         2  
  1         47  
31 1     1   5 use constant CLS => "\x1B[2J";
  1         2  
  1         50  
32              
33             #Cursor control
34 1     1   5 use constant CURSOR_OFF => "\x1B[?25l";
  1         2  
  1         41  
35 1     1   5 use constant CURSOR_ON => "\x1B[?25h";
  1         1  
  1         50  
36 1     1   5 use constant CURSOR_SAV => "\x1B7";
  1         2  
  1         50  
37 1     1   4 use constant CURSOR_RST => "\x1B8";
  1         1  
  1         39  
38 1     1   4 use constant REGION_UP => "\x1BM";
  1         2  
  1         132  
39 1     1   5 use constant REGION_DOWN => "\x1BD";
  1         2  
  1         47  
40 1     1   4 use constant NEXT_LINE => "\x1BE";
  1         2  
  1         43  
41 1     1   4 use constant HOME => "\x1B[H";
  1         2  
  1         83  
42              
43             #Line drawing
44 1     1   5 use constant HOR => LINES . "q" . ASCII;
  1         8  
  1         98  
45 1     1   5 use constant VER => LINES . "x" . ASCII;
  1         1  
  1         114  
46 1     1   5 use constant ULC => LINES . "l" . ASCII;
  1         2  
  1         62  
47 1     1   5 use constant URC => LINES . "k" . ASCII;
  1         2  
  1         59  
48 1     1   4 use constant LRC => LINES . "j" . ASCII;
  1         2  
  1         67  
49 1     1   5 use constant LLC => LINES . "m" . ASCII;
  1         2  
  1         95  
50 1     1   5 use constant LTE => LINES . "t" . ASCII;
  1         2  
  1         73  
51 1     1   5 use constant RTE => LINES . "u" . ASCII;
  1         16  
  1         59  
52 1     1   5 use constant TTE => LINES . "w" . ASCII;
  1         1  
  1         147  
53 1     1   5 use constant BTE => LINES . "v" . ASCII;
  1         1  
  1         60  
54 1     1   5 use constant CTE => LINES . "n" . ASCII;
  1         2  
  1         54  
55              
56             #Attributes
57 1     1   4 use constant CLEAR => "\x1B[0m";
  1         3  
  1         51  
58 1     1   4 use constant RESET => "\x1B[0m";
  1         2  
  1         96  
59 1     1   40 use constant BOLD => "\x1B[1m";
  1         2  
  1         3621  
60 1     1   14 use constant DIM => "\x1B[2m";
  1         1  
  1         134  
61 1     1   5 use constant UNDERLINE => "\x1B[4m";
  1         2  
  1         55  
62 1     1   5 use constant BLINK => "\x1B[5m";
  1         3  
  1         102  
63 1     1   6 use constant REVERSE => "\x1B[7m";
  1         2  
  1         1006  
64 1     1   8 use constant HIDDEN => "\x1B[8m";
  1         3  
  1         78  
65              
66             #Colors
67 1     1   5 use constant BLACK => "\x1B[30m";
  1         9  
  1         45  
68 1     1   6 use constant RED => "\x1B[31m";
  1         1  
  1         104  
69 1     1   6 use constant GREEN => "\x1B[32m";
  1         2  
  1         57  
70 1     1   5 use constant YELLOW => "\x1B[33m";
  1         2  
  1         43  
71 1     1   5 use constant BLUE => "\x1B[34m";
  1         2  
  1         49  
72 1     1   4 use constant MAGENTA => "\x1B[35m";
  1         3  
  1         50  
73 1     1   5 use constant CYAN => "\x1B[36m";
  1         1  
  1         54  
74 1     1   5 use constant WHITE => "\x1B[37m";
  1         2  
  1         46  
75              
76 1     1   4 use constant ON_BLACK => "\x1B[40m";
  1         2  
  1         48  
77 1     1   4 use constant ON_RED => "\x1B[41m";
  1         2  
  1         685  
78 1     1   6 use constant ON_GREEN => "\x1B[42m";
  1         2  
  1         109  
79 1     1   7 use constant ON_YELLOW => "\x1B[43m";
  1         2  
  1         46  
80 1     1   5 use constant ON_BLUE => "\x1B[44m";
  1         1  
  1         65  
81 1     1   11 use constant ON_MAGENTA => "\x1B[45m";
  1         2  
  1         46  
82 1     1   4 use constant ON_CYAN => "\x1B[46m";
  1         2  
  1         40  
83 1     1   6 use constant ON_WHITE => "\x1B[47m";
  1         2  
  1         39785  
84              
85             #===============================================================================
86             #Encapsulated data
87             #===============================================================================
88              
89             {
90             my %_attribs = ( #Default value Mode #Comment
91             _term_width => [0, 'r' ], #INT
92             _term_height => [0, 'r' ], #INT
93             _width => [0, 'rw'], #INT, clip if < length title or > $term_width
94             _height => [0, 'rw'], #INT, clip if > $height
95             _space_after_title => [1, 'rw'], #BOOL
96             _space_after_items => [1, 'rw'], #BOOL
97             _space_after_status => [0, 'rw'], #BOOL
98             _spacious_items => [0, 'rw'], #BOOL
99             _cursor => [1, 'rw'], #BOOL
100             _cursor_char => ['?', 'rw'], #CHAR
101             _up_keys => [['UP', 'PGUP', 'LEFT'], 'rw'], #ARRAY of keys
102             _down_keys => [['DOWN', 'PGDN', 'RIGHT'], 'rw'], #ARRAY of keys
103             _exit_keys => [['q', 'Q', 'CTRL-c'], 'rw'], #ARRAY of keys
104             _help_keys => [['F1', '?'] , 'rw'], #ARRAY of keys
105             _help => [[], 'rw'], #ARRAY of [status_msg, CODE reference]
106             _selection => [0, 'rw'], #INT, > 0 and <= number of items
107             _selection_keys => [['SPACE', 'ENTER'], 'rw'], #ARRAY of keys
108             _selection_wrap => [1, 'rw'], #BOOL
109             _selection_style => [['REVERSE'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD, UNDERLINE or CLEAR
110             _selection_fgcolor => ['', 'rw'], #FGCOLOR
111             _selection_bgcolor => ['', 'rw'], #BGCOLOR
112             _leader => [0, 'rw'], #BOOL
113             _leader_delimiter => ['', 'rw'], #STR or LINE CHAR
114             _trailer => [0, 'rw'], #BOOL
115             _trailer_delimiter => ['', 'rw'], #STR or LINE CHAR
116             _shortcut_prefix => ['', 'rw'], #STR or LINE CHAR
117             _shortcut_postfix => ['', 'rw'], #STR or LINE CHAR
118             _delimiter => ['', 'rw'], #STR or LINE CHAR
119             _label_prefix => ['', 'rw'], #STR or LINE CHAR
120             _label_postfix => ['', 'rw'], #STR or LINE CHAR
121             _title => ['', 'rw'], #STR
122             _title_style => [['BOLD'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD, UNDERLINE or CLEAR
123             _title_fgcolor => ['', 'rw'], #FGCOLOR
124             _title_bgcolor => ['', 'rw'], #BGCOLOR
125             _title_align => ['CENTER', 'rw'], #LEFT|RIGHT|CENTER
126             _title_fill => [1, 'rw'], #BOOL
127             _title_frame => [1, 'rw'], #BOOL
128             _title_frame_style => [['BOLD'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD or CLEAR
129             _title_frame_fgcolor => ['', 'rw'], #FGCOLOR
130             _title_frame_bgcolor => ['', 'rw'], #BGCOLOR
131             _items => [[], 'rw'], #ARRAY of ['shortcut', 'description', \&code ]
132             _item_style => [['CLEAR'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD, UNDERLINE or CLEAR
133             _item_fgcolor => ['', 'rw'], #FGCOLOR
134             _item_bgcolor => ['', 'rw'], #BGCOLOR
135             _item_align => ['LEFT', 'rw'], #LEFT|RIGHT|CENTER
136             _item_fill => [1, 'rw'], #BOOL
137             _item_frame => [1, 'rw'], #BOOL
138             _item_frame_style => [['CLEAR'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD or CLEAR
139             _item_frame_fgcolor => ['', 'rw'], #FGCOLOR
140             _item_frame_bgcolor => ['', 'rw'], #BGCOLOR
141             _status => ['', 'rw'], #STR
142             _status_style => [['CLEAR'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD, UNDERLINE or CLEAR
143             _status_fgcolor => ['', 'rw'], #FGCOLOR
144             _status_bgcolor => ['', 'rw'], #BGCOLOR
145             _status_align => ['LEFT', 'rw'], #LEFT|RIGHT|CENTER
146             _status_fill => [1, 'rw'], #BOOL
147             _status_frame => [0, 'rw'], #BOOL
148             _status_frame_style => [['CLEAR'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD or CLEAR
149             _status_frame_fgcolor => ['', 'rw'], #FGCOLOR
150             _status_frame_bgcolor => ['', 'rw'], #BGCOLOR
151             _prompt => ['', 'rw'], #STR
152             _prompt_style => [['BOLD'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD, UNDERLINE or CLEAR
153             _prompt_fgcolor => ['', 'rw'], #FGCOLOR
154             _prompt_bgcolor => ['', 'rw'], #BGCOLOR
155             _prompt_align => ['LEFT', 'rw'], #LEFT|RIGHT|CENTER
156             _prompt_fill => [1, 'rw'], #BOOL
157             _prompt_frame => [0, 'rw'], #BOOL
158             _prompt_frame_style => [['BOLD'], 'rw'], #ARRAY of attributes BLINK, REVERSE, BOLD or CLEAR
159             _prompt_frame_fgcolor => ['', 'rw'], #FGCOLOR
160             _prompt_frame_bgcolor => ['', 'rw']);#BGCOLOR
161              
162             my %_keynames = ( "\e[1~" => "HOME", #Linux console
163             "\e[2~" => "INSERT", #VT100
164             "\e[3~" => "DEL", #VT100
165             "\e[4~" => "END", #Linux console
166             "\e[5~" => "PGUP", #VT100
167             "\e[6~" => "PGDN", #VT100
168             "\e[11~" => "F1", #VT100
169             "\e[12~" => "F2", #VT100
170             "\e[13~" => "F3", #VT100
171             "\e[14~" => "F4", #VT100
172             "\e[15~" => "F5", #VT100
173             "\e[17~" => "F6", #VT100
174             "\e[18~" => "F7", #VT100
175             "\e[19~" => "F8", #VT100
176             "\e[20~" => "F9", #VT100
177             "\e[21~" => "F10", #VT100
178             "\e[23~" => "F11", #VT100
179             "\e[24~" => "F12", #VT100
180             "\e[[A" => "F1", #Linux console
181             "\e[[B" => "F2", #Linux console
182             "\e[[C" => "F3", #Linux console
183             "\e[[D" => "F4", #Linux console
184             "\e[[E" => "F5", #Linux console
185             "\e[A" => "UP", #VT100
186             "\e[B" => "DOWN", #VT100
187             "\e[C" => "RIGHT", #VT100
188             "\e[D" => "LEFT", #VT100
189             "\e[F" => "END", #VT100
190             "\e[H" => "HOME", #VT100
191             "\eOA" => "UP", #XTerm
192             "\eOB" => "DOWN", #XTerm
193             "\eOC" => "RIGHT", #XTerm
194             "\eOD" => "LEFT", #XTerm
195             "\eOF" => "END", #XTerm
196             "\eOH" => "HOME", #XTerm
197             "\eOP" => "F1", #XTerm
198             "\eOQ" => "F2", #XTerm
199             "\eOR" => "F3", #XTerm
200             "\eOS" => "F4", #XTerm
201             "\ea" => "META-a",
202             "\eb" => "META-b",
203             "\ec" => "META-c",
204             "\ed" => "META-d",
205             "\ee" => "META-e",
206             "\ef" => "META-f",
207             "\eg" => "META-g",
208             "\eh" => "META-h",
209             "\ei" => "META-i",
210             "\ej" => "META-j",
211             "\ek" => "META-k",
212             "\el" => "META-l",
213             "\em" => "META-m",
214             "\en" => "META-n",
215             "\eo" => "META-o",
216             "\ep" => "META-p",
217             "\eq" => "META-q",
218             "\er" => "META-r",
219             "\es" => "META-s",
220             "\et" => "META-t",
221             "\eu" => "META-u",
222             "\ev" => "META-v",
223             "\ew" => "META-w",
224             "\ex" => "META-x",
225             "\ey" => "META-y",
226             "\ez" => "META-z",
227             "\x01" => "CTRL-a",
228             "\x02" => "CTRL-b",
229             "\x03" => "CTRL-c",
230             "\x04" => "CTRL-d",
231             "\x05" => "CTRL-e",
232             "\x06" => "CTRL-f",
233             "\x07" => "CTRL-g",
234             "\x08" => "CTRL-h",
235             "\x09" => "TAB", #Also CRTL-i
236             "\x0A" => "ENTER", #Also CTRL-j
237             "\x0B" => "CTRL-k",
238             "\x0C" => "CTRL-l",
239             "\x0D" => "CTRL-m", #Apparently CTRL-m gives \x0A
240             "\x0E" => "CTRL-n",
241             "\x0F" => "CTRL-o",
242             "\x10" => "CTRL-p",
243             "\x11" => "CTRL-q",
244             "\x12" => "CTRL-r",
245             "\x13" => "CTRL-s",
246             "\x14" => "CTRL-t",
247             "\x15" => "CTRL-u",
248             "\x16" => "CTRL-v",
249             "\x17" => "CTRL-w",
250             "\x18" => "CTRL-x",
251             "\x19" => "CTRL-y",
252             "\x1A" => "CTRL-z",
253             "\x20" => "SPACE",
254             "\x7F" => "BS");
255              
256             #Get the name of a key or return undef
257             sub _get_keyname {
258 0     0   0 my ($self, $sequence) = @_;
259 0         0 my $keyname = undef;
260 0 0       0 $keyname = $_keynames{$sequence} if exists $_keynames{$sequence};
261 0         0 return $keyname;
262             }
263              
264             #Is argument a valid key name?
265             sub _is_keyname {
266 28     28   45 my ($self, $name) = @_;
267 28 100 66     258 return 1 if length($name) == 1 and $name =~ /^[[:graph:] ]$/;
268 16         1039 my %keynames = reverse %_keynames;
269 16 50       421 return 1 if exists $keynames{$name};
270 0         0 return 0;
271             }
272              
273             #Get default value for an attribute
274             sub _get_default {
275 68     68   77 my ($self, $attrib) = @_;
276 68         315 return $_attribs{$attrib}[0];
277             }
278              
279             #Get a list of all attributes
280             sub _list_attribs {
281 1     1   179 return keys %_attribs;
282             }
283              
284             #Verify the access mode for an attribute
285             sub _check_mode {
286 70     70   88 my ($self, $attrib, $mode) = @_;
287 70         483 return $_attribs{$attrib}[1] =~ /$mode/i;
288             }
289              
290             #Verify existence of an attribute
291             sub _check_attrib {
292 0     0   0 my ($self, $attrib) = @_;
293 0         0 return exists $_attribs{$attrib};
294             }
295              
296             #Verify validity of an attribute value
297             sub _check_value {
298 76     76   6228 my ($self, $attrib, $value) = @_;
299 76         95 my $ok = 0;
300             #Make sure a value was given
301 76 50       164 return $ok unless defined $value;
302             #Now check if the given value(s) is/are appropriate
303             SWITCH: {
304 76 100       84 if ($attrib eq '_width') {
  76         168  
305 2 50 66     55 $ok++ if $value > 0 and $value < $self->{_term_width};
306 2         8 last SWITCH;
307             }
308 74 100       150 if ($attrib eq '_height') {
309 2 50 66     45 $ok++ if $value > 0 and $value < $self->{_term_height};
310 2         5 last SWITCH;
311             }
312 72 100       163 if ($attrib =~ /^_space_after_/) {
313 3 50       15 $ok++ if $value =~ /^(?:\-|\+|0|1|NO|N|YES|Y|FALSE|F|TRUE|T)$/i;
314 3         5 last SWITCH;
315             }
316 69 100       194 if ($attrib eq '_spacious_items') {
317 1 50       23 $ok++ if $value =~ /^(?:\-|\+|0|1|NO|N|YES|Y|FALSE|F|TRUE|T)$/i;
318 1         3 last SWITCH;
319             }
320 68 100       122 if ($attrib eq '_cursor') {
321 1 50       14 $ok++ if $value =~ /^(?:\-|\+|0|1|NO|N|YES|Y|FALSE|F|TRUE|T)$/i;
322 1         2 last SWITCH;
323             }
324 67 100       200 if ($attrib eq '_cursor_char') {
325 1 50       5 $ok++ if $value =~ /^[[:graph:] ]$/;
326 1         2 last SWITCH;
327             }
328 66 100       115 if ($attrib eq '_selection') {
329 1 50 33     11 $ok++ if $value =~ /^\d+$/ and $value <= scalar(@{$self->{_items}});
  1         6  
330 1         2 last SWITCH;
331             }
332 65 100       115 if ($attrib eq '_selection_wrap') {
333 1 50       7 $ok++ if $value =~ /^(?:\-|\+|0|1|NO|N|YES|Y|FALSE|F|TRUE|T)$/i;
334 1         2 last SWITCH;
335             }
336 64 100       104 if ($attrib eq '_help') {
337 1 50       4 last SWITCH unless ref($value) eq 'ARRAY';
338 1         2 foreach my $help (@{$value}) {
  1         3  
339 2 50       6 if (defined $help) {
340 2 50       6 last SWITCH unless ref($help) eq 'ARRAY';
341 2 50       6 if (defined $help->[0]) {
342 2 50       10 last SWITCH unless $help->[0] =~ /^[[:graph:] ]*$/;
343             }
344 2 50       5 if (defined $help->[1]) {
345 2 50       7 last SWITCH unless ref($help->[1]) eq 'CODE';
346             }
347             }
348             }
349 1         4 $ok++;
350 1         2 last SWITCH;
351             }
352 63 100       168 if ($attrib =~ /_keys$/) {
353 10 50       24 last SWITCH unless ref($value) eq 'ARRAY';
354 10         16 foreach my $arg (@{$value}) {
  10         26  
355 22 50       121 last SWITCH unless $self->_is_keyname($arg);
356             }
357 10         14 $ok++;
358 10         20 last SWITCH;
359             }
360 53 100       101 if ($attrib eq '_items') {
361 2 50       8 last SWITCH unless ref($value) eq 'ARRAY';
362 2         3 foreach my $item (@{$value}) {
  2         6  
363 6 50       26 last SWITCH unless ref($item) eq 'ARRAY';
364 6 50 33     33 last SWITCH unless defined($item->[0]) and $self->_is_keyname($item->[0]);
365 6 50 33     50 last SWITCH unless defined($item->[1]) and $item->[1] =~ /^[[:graph:] ]*$/;
366 6 100       14 if (defined $item->[2]) {
367 4 50       19 last SWITCH unless ref($item->[2]) eq 'CODE';
368             }
369             }
370 2         3 $ok++;
371 2         4 last SWITCH;
372             }
373 51 100       106 if ($attrib =~ /_fill$/) {
374 4 50       20 $ok++ if $value =~ /^(?:\-|\+|0|1|NO|N|YES|Y|FALSE|F|TRUE|T)$/i;
375 4         8 last SWITCH;
376             }
377 47 100       179 if ($attrib =~ /_(?:leader|trailer)$/) {
378 2 50       13 $ok++ if $value =~ /^(?:\-|\+|0|1|NO|N|YES|Y|FALSE|F|TRUE|T)$/i;
379 2         2 last SWITCH;
380             }
381 45 100       98 if ($attrib =~ /_(?:pre|post)fix$/) {
382 4 100       24 if ($value =~ /^ *(?:HOR|VER|ULC|URC|LRC|LLC|LTE|RTE|TTE|BTE|CTE) *$/) {
    50          
383 2         3 $ok++;
384             }
385             elsif ($value =~ /^[[:graph:] ]*$/) {
386 2         6 $ok++;
387             }
388 4         6 last SWITCH;
389             }
390 41 100       86 if ($attrib =~ /_delimiter$/) {
391 3 50       12 if ($value =~ /^(?:HOR|VER|ULC|URC|LRC|LLC|LTE|RTE|TTE|BTE|CTE)$/) {
    0          
392 3         3 $ok++;
393             }
394             elsif ($value =~ /^[[:graph:] ]?$/) {
395 0         0 $ok++;
396             }
397 3         4 last SWITCH;
398             }
399 38 100       118 if ($attrib =~ /^_(?:prompt|status|title)$/) {
400 3 50       14 $ok++ if $value =~ /^[[:graph:] ]*$/;
401 3         6 last SWITCH;
402             }
403 35 100       78 if ($attrib =~ /_align$/) {
404 4 50       18 $ok++ if $value =~ /^(?:LEFT|RIGHT|CENTER)$/i;
405 4         7 last SWITCH;
406             }
407 31 100       68 if ($attrib =~ /_frame$/) {
408 4 50       23 $ok++ if $value =~ /^(?:\-|\+|0|1|NO|N|YES|Y|FALSE|F|TRUE|T)$/i;
409 4         5 last SWITCH;
410             }
411 27 100       66 if ($attrib =~ /_frame_style$/) {
412 4 50       13 last SWITCH unless ref($value) eq 'ARRAY';
413 4         5 foreach my $arg (@{$value}) {
  4         9  
414 4 50       22 last SWITCH unless $arg =~ /^(?:BLINK|REVERSE|BOLD|CLEAR)$/i;
415             }
416 4         6 $ok++;
417 4         6 last SWITCH;
418             }
419 23 100       57 if ($attrib =~ /_style$/) {
420 5 50       18 last SWITCH unless ref($value) eq 'ARRAY';
421 5         7 foreach my $arg (@{$value}) {
  5         15  
422 5 50       42 last SWITCH unless $arg =~ /^(?:BLINK|REVERSE|BOLD|UNDERLINE|CLEAR)$/i;
423             }
424 5         9 $ok++;
425 5         84 last SWITCH;
426             }
427 18 50       242 if ($attrib =~ /_[fb]gcolor$/) {
428 18 50       68 $ok++ if $value =~ /^(?:BLACK|RED|GREEN|YELLOW|BLUE|MAGENTA|CYAN|WHITE)$/i;
429 18         33 last SWITCH;
430             }
431             else {
432 0         0 croak "No such attribute: $attrib";
433             }
434             }
435 76         330 return $ok;
436             }
437              
438             sub _linestr_length {
439 0     0   0 my ($self, $str) = @_;
440              
441 0         0 my $length = 0;
442 0 0       0 if ($str =~ /^( *)(?:HOR|VER|ULC|URC|LRC|LLC|LTE|RTE|TTE|BTE|CTE)( *)$/) {
443 0         0 $length = length($1) + 1 + length($2);
444             }
445             else {
446 0         0 $length = length $str;
447             }
448 0         0 return $length;
449             }
450              
451             sub _print_linestr {
452 0     0   0 my ($self, $str) = @_;
453              
454 0 0       0 if ($str =~ /^( *)(HOR|VER|ULC|URC|LRC|LLC|LTE|RTE|TTE|BTE|CTE)( *)$/) {
455 0         0 print $1;
456 0         0 print &{\&$2};
  0         0  
457 0         0 print $3;
458             }
459             else {
460 0         0 print $str;
461             }
462             }
463              
464             sub _print_color {
465 0     0   0 my ($self, $fgcolor, $bgcolor) = @_;
466              
467 0 0       0 print &{\&$fgcolor} if $fgcolor;
  0         0  
468 0 0       0 if ($bgcolor) {
469 0         0 $bgcolor = "ON_" . $bgcolor;
470 0         0 print &{\&$bgcolor};
  0         0  
471             }
472             }
473              
474             sub _print_style {
475 0     0   0 my ($self, @styles) = @_;
476              
477 0         0 foreach my $style (@styles) {
478 0 0       0 print &{\&$style} if $style;
  0         0  
479             }
480             }
481              
482             sub _items_start {
483 0     0   0 my $self = shift;
484              
485 0         0 my $line = 1;
486 0 0       0 if (length($self->title()) > 0) {
487 0         0 $line++;
488 0 0       0 $line += 2 if $self->title_frame();
489 0 0       0 $line++ if $self->space_after_title();
490             }
491 0         0 return $line;
492             }
493              
494             sub _status_start {
495 0     0   0 my $self = shift;
496              
497 0         0 my $line = $self->_items_start();
498 0 0       0 if ($self->item_count() > 0) {
499 0 0 0     0 $line++ if $self->leader() and not $self->item_frame();
500 0 0       0 $line += 2 if $self->item_frame();
501 0         0 $line += $self->item_count();
502 0 0 0     0 $line += $self->item_count() - 1 if $self->item_frame() and $self->spacious_items() and $self->item_count() > 1;
      0        
503 0 0 0     0 $line++ if $self->trailer() and not $self->item_frame();
504 0 0       0 $line++ if $self->space_after_items();
505             }
506 0         0 return $line;
507             }
508              
509             sub _prompt_start {
510 0     0   0 my $self = shift;
511              
512 0         0 my $line = $self->_status_start();
513 0 0       0 if (length($self->status()) > 0) {
514 0         0 $line++;
515 0 0       0 $line += 2 if $self->status_frame();
516 0 0       0 $line++ if $self->space_after_status();
517             }
518 0         0 return $line;
519             }
520              
521             sub _cursor_pos {
522 0     0   0 my $self = shift;
523              
524 0         0 my $line = $self->_prompt_start();
525 0 0       0 $line++ if $self->prompt_frame();
526 0         0 my $max_length = $self->width() - 1;
527 0 0       0 $max_length -= 2 if $self->prompt_frame();
528 0         0 my $prompt_length = length $self->prompt();
529 0 0       0 $prompt_length = $max_length if $prompt_length > $max_length;
530 0         0 my $padding = $max_length - $prompt_length;
531 0 0       0 $padding = 0 if $padding < 0;
532 0         0 my $col = 1;
533 0 0       0 if ($self->prompt_fill()) {
534 0 0       0 if ($self->prompt_align() eq 'CENTER') {
    0          
535 0         0 $padding = int ($padding / 2);
536 0         0 $col += $padding + $prompt_length;
537 0 0       0 $col++ if $self->prompt_frame();
538             }
539             elsif ($self->prompt_align() eq 'RIGHT') {
540 0         0 $col += $padding + $prompt_length;
541 0 0       0 $col++ if $self->prompt_frame();
542             }
543             else {
544 0         0 $col += $prompt_length;
545 0 0       0 $col++ if $self->prompt_frame();
546             }
547             }
548             else {
549 0         0 $col += $prompt_length;
550 0 0       0 $col++ if $self->prompt_frame();
551             }
552 0         0 return $line, $col;
553             }
554              
555             sub _clear_after_items {
556 0     0   0 my $self = shift;
557              
558 0         0 $self->pos($self->_status_start(), 1);
559 0         0 print DEL_TO_EOS;
560             }
561              
562             sub _update_hint {
563 0     0   0 my ($self, $hint) = @_;
564              
565 0 0 0     0 if (defined $hint and $self->_check_value('_status', $hint)) {
566 0         0 $self->_clear_after_items();
567 0 0       0 $self->print_status($hint) if $self->status();
568 0 0       0 $self->print_prompt() if $self->prompt();
569 0         0 $self->print_cursor();
570             }
571             }
572              
573             }
574              
575             #===============================================================================
576             #Constructor and destructor
577             #===============================================================================
578              
579             sub new {
580 1     1 0 158 my ($caller, %args) = @_;
581              
582 1         4 my $caller_is_obj = ref($caller);
583 1   33     9 my $class = $caller_is_obj || $caller;
584 1         3 my $self = bless {}, $class;
585              
586             #Set attributes
587 1         7 my ($w, $h) = GetTerminalSize;
588 1         19144 $self->{_term_width} = $w;
589 1         17 $self->{_term_height} = $h;
590 1         130 foreach my $attrib ($self->_list_attribs()) {
591 70 100       129 next unless $self->_check_mode($attrib, 'w');
592 68         223 my ($arg) = ($attrib =~ /^_(\w+)/);
593 68 50       170 if (exists $args{$arg}) {
    50          
594 0 0       0 if ($self->_check_value($attrib, $args{$arg})) {
595 0         0 $self->{$attrib} = $self->$arg($args{$arg});
596             }
597             else {
598 0         0 croak "Invalid value for $arg: $args{$arg}";
599             }
600             }
601             elsif ($caller_is_obj) {
602 0         0 $self->{$attrib} = $caller->{$attrib};
603             }
604             else {
605 68         122 $self->{$attrib} = $self->_get_default($attrib);
606             }
607             }
608 1 50       15 $self->{_width} = $w unless $self->_check_value('_width', $self->{_width});
609 1 50       4 $self->{_height} = $h unless $self->_check_value('_height', $self->{_height});
610             #Initialize terminal
611 1         10 $| = 1; #Set flush mode
612 1         103 print "\e[61\"p"; #Set VT100 mode
613 1         5 print "\e[2J"; #Clear screen
614 1         5 print "\e[1;1H"; #Position cursor at top left
615 1         15 return $self;
616             }
617              
618             sub DESTROY {
619 1     1   57 Term::ReadKey::ReadMode(0); #Restore propper readmode
620             #print "\e[?6l"; #Remove region
621 1         90 print "\e(B"; #Restore charset
622             #print "\e8"; #Restore cursor
623 1         5 print "\e[?25h"; #Turn cursor on
624 1         162 print "\e[0m"; #Restore all attributes
625             #print "\e[2J"; #Clear screen
626             #print "\e[1;1H"; #Position cursor at top left
627             }
628              
629             #===============================================================================
630             #Accessors and mutators
631             #===============================================================================
632              
633             #Sorry, I just don't like AUTOLOAD and yes I do know about affordances but
634             #separate read/write methods feel awkward to me. Consequently this is more code
635             #than strictly needed, but that's only a one-time investment. A small one if
636             #you're using vim `;-)
637              
638             sub width {
639 1     1 0 13 my ($self, $width) = @_;
640              
641 1 50       4 if ($width) {
642 1 50       4 if ($self->_check_value('_width', $width)) {
643 0         0 $self->{_width} = $width;
644             }
645             else {
646 1         569 carp "width must be larger than 0 and smaller than the terminal width";
647             }
648             }
649 1         130 return $self->{_width};
650             }
651              
652             sub height {
653 1     1 0 3 my ($self, $height) = @_;
654              
655 1 50       6 if ($height) {
656 1 50       4 if ($self->_check_value('_height', $height)) {
657 0         0 $self->{_height} = $height;
658             }
659             else {
660 1         111 carp "height must be larger than 0 and smaller than the terminal height";
661             }
662             }
663 1         67 return $self->{_height};
664             }
665              
666             sub space_after_title {
667 1     1 0 3 my ($self, $space) = @_;
668              
669 1 50       4 if (defined $space) {
670 1 50       4 if ($self->_check_value('_space_after_title', $space)) {
671 1 50       7 if ($space =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
672 1         4 $self->{_space_after_title} = 1;
673             }
674             else {
675 0         0 $self->{_space_after_title} = 0;
676             }
677             }
678             else {
679 0         0 carp "space_after_title must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
680             }
681             }
682 1         6 return $self->{_space_after_title};
683             }
684              
685             sub space_after_items {
686 1     1 0 4 my ($self, $space) = @_;
687              
688 1 50       4 if (defined $space) {
689 1 50       4 if ($self->_check_value('_space_after_items', $space)) {
690 1 50       6 if ($space =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
691 1         3 $self->{_space_after_items} = 1;
692             }
693             else {
694 0         0 $self->{_space_after_items} = 0;
695             }
696             }
697             else {
698 0         0 carp "space_after_items must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
699             }
700             }
701 1         5 return $self->{_space_after_items};
702             }
703              
704             sub space_after_status {
705 1     1 0 3 my ($self, $space) = @_;
706              
707 1 50       4 if (defined $space) {
708 1 50       4 if ($self->_check_value('_space_after_status', $space)) {
709 1 50       6 if ($space =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
710 1         3 $self->{_space_after_status} = 1;
711             }
712             else {
713 0         0 $self->{_space_after_status} = 0;
714             }
715             }
716             else {
717 0         0 carp "space_after_status must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
718             }
719             }
720 1         4 return $self->{_space_after_status};
721             }
722              
723             sub spacious_items {
724 1     1 0 3 my ($self, $space) = @_;
725              
726 1 50       5 if (defined $space) {
727 1 50       4 if ($self->_check_value('_spacious_items', $space)) {
728 1 50       9 if ($space =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
729 1         4 $self->{_spacious_items} = 1;
730             }
731             else {
732 0         0 $self->{_spacious_items} = 0;
733             }
734             }
735             else {
736 0         0 carp "spacious_items must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
737             }
738             }
739 1         5 return $self->{_spacious_items};
740             }
741              
742             sub cursor {
743 1     1 0 3 my ($self, $cursor) = @_;
744              
745 1 50       5 if (defined $cursor) {
746 1 50       4 if ($self->_check_value('_cursor', $cursor)) {
747 1 50       7 if ($cursor =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
748 1         3 $self->{_cursor} = 1;
749             }
750             else {
751 0         0 $self->{_cursor} = 0;
752             }
753             }
754             else {
755 0         0 carp "cursor must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
756             }
757             }
758 1         10 return $self->{_cursor};
759             }
760              
761             sub cursor_char {
762 1     1 0 3 my ($self, $char) = @_;
763              
764 1 50       5 if ($char) {
765 1 50       3 if ($self->_check_value('_cursor_char', $char)) {
766 1         3 $self->{_cursor_char} = $char;
767             }
768             else {
769 0         0 carp "cursor_char must be a printable character";
770             }
771             }
772 1         6 return $self->{_cursor_char};
773             }
774              
775             sub up_keys {
776 3     3 0 9 my ($self, $keys) = @_;
777              
778 3 100       10 if ($keys) {
779 2 50       10 if (ref $keys eq 'ARRAY') {
780 2 50       6 if ($self->_check_value('_up_keys', $keys)) {
781 2         7 $self->{_up_keys} = $keys;
782             }
783             else {
784 0         0 carp "up_keys must be one or more keynames";
785             }
786             }
787             else {
788 0         0 carp "up_keys must be given as a reference to an array";
789             }
790             }
791 3 100       19 return wantarray ? @{$self->{_up_keys}} : $self->{_up_keys};
  1         9  
792             }
793              
794             sub down_keys {
795 3     3 0 5 my ($self, $keys) = @_;
796              
797 3 100       9 if ($keys) {
798 2 50       7 if (ref $keys eq 'ARRAY') {
799 2 50       5 if ($self->_check_value('_down_keys', $keys)) {
800 2         6 $self->{_down_keys} = $keys;
801             }
802             else {
803 0         0 carp "down_keys must be one or more keynames";
804             }
805             }
806             else {
807 0         0 carp "down_keys must be given as a reference to an array";
808             }
809             }
810 3 100       12 return wantarray ? @{$self->{_down_keys}} : $self->{_down_keys};
  1         9  
811             }
812              
813             sub help {
814 1     1 0 5 my ($self, $help) = @_;
815              
816 1 50       4 if ($help) {
817 1 50       4 if (ref $help eq 'ARRAY') {
818 1 50       3 if ($self->_check_value('_help', $help)) {
819 1         3 $self->{_help} = $help;
820             }
821             else {
822 0         0 carp "help must an array of arrays containing strings and code references";
823             }
824             }
825             else {
826 0         0 carp "help must be given as a reference to an array";
827             }
828             }
829 1 50       7 return wantarray ? @{$self->{_help}} : $self->{_help};
  0         0  
830             }
831              
832             sub help_keys {
833 3     3 0 6 my ($self, $keys) = @_;
834              
835 3 100       10 if ($keys) {
836 2 50       11 if (ref $keys eq 'ARRAY') {
837 2 50       5 if ($self->_check_value('_help_keys', $keys)) {
838 2         6 $self->{_help_keys} = $keys;
839             }
840             else {
841 0         0 carp "help_keys must be one or more keynames";
842             }
843             }
844             else {
845 0         0 carp "help_keys must be given as a reference to an array";
846             }
847             }
848 3 100       13 return wantarray ? @{$self->{_help_keys}} : $self->{_help_keys};
  1         9  
849             }
850              
851             sub exit_keys {
852 3     3 0 9 my ($self, $keys) = @_;
853              
854 3 100       9 if ($keys) {
855 2 50       9 if (ref $keys eq 'ARRAY') {
856 2 50       6 if ($self->_check_value('_exit_keys', $keys)) {
857 2         6 $self->{_exit_keys} = $keys;
858             }
859             else {
860 0         0 carp "exit_keys must be one or more keynames";
861             }
862             }
863             else {
864 0         0 carp "exit_keys must be given as a reference to an array";
865             }
866             }
867 3 100       12 return wantarray ? @{$self->{_exit_keys}} : $self->{_exit_keys};
  1         7  
868             }
869              
870             sub selection {
871 1     1 0 3 my ($self, $sel) = @_;
872              
873 1 50       4 if (defined $sel) {
874 1 50       4 if ($self->_check_value('_selection', $sel)) {
875 1         3 $self->{_selection} = $sel;
876             }
877             else {
878 0         0 carp "selection must be larger than or equal to 0 and smaller than or equal to the number of items";
879             }
880             }
881 1         6 return $self->{_selection};
882             }
883              
884             sub selection_wrap {
885 1     1 0 3 my ($self, $wrap) = @_;
886              
887 1 50       4 if (defined $wrap) {
888 1 50       3 if ($self->_check_value('_selection_wrap', $wrap)) {
889 1 50       5 if ($wrap =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
890 1         3 $self->{_selection_wrap} = 1;
891             }
892             else {
893 0         0 $self->{_selection_wrap} = 0;
894             }
895             }
896             else {
897 0         0 carp "selection_wrap must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
898             }
899             }
900 1         5 return $self->{_selection_wrap};
901             }
902              
903             sub selection_keys {
904 3     3 0 7 my ($self, $keys) = @_;
905              
906 3 100       8 if ($keys) {
907 2 50       6 if (ref $keys eq 'ARRAY') {
908 2 50       7 if ($self->_check_value('_selection_keys', $keys)) {
909 2         6 $self->{_selection_keys} = $keys;
910             }
911             else {
912 0         0 carp "selection_keys must be one or more keynames";
913             }
914             }
915             else {
916 0         0 carp "selection_keys must be given as a reference to an array";
917             }
918             }
919 3 100       29 return wantarray ? @{$self->{_selection_keys}} : $self->{_selection_keys};
  1         8  
920             }
921              
922             sub selection_style {
923 1     1 0 2 my ($self, $styles) = @_;
924              
925 1 50       4 if ($styles) {
926 1 50       5 if (ref $styles eq 'ARRAY') {
927 1 50       3 if ($self->_check_value('_selection_style', $styles)) {
928 1         2 foreach my $style (@{$styles}) {
  1         2  
929 1         4 $style = uc $style;
930             }
931 1         3 $self->{_selection_style} = $styles;
932             }
933             else {
934 0         0 carp "selection_style must be BLINK, REVERSE, BOLD, UNDERLINE and/or CLEAR";
935             }
936             }
937             else {
938 0         0 carp "selection_style must be given as a reference to an array";
939             }
940             }
941 1 50       13 return wantarray ? @{$self->{_selection_style}} : $self->{_selection_style};
  0         0  
942             }
943              
944             sub selection_fgcolor {
945 1     1 0 2 my ($self, $fgcolor) = @_;
946              
947 1 50       5 if ($fgcolor) {
948 1 50       2 if ($self->_check_value('_selection_fgcolor', $fgcolor)) {
949 1         4 $self->{_selection_fgcolor} = uc $fgcolor;
950             }
951             else {
952 0         0 carp "selection_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
953             }
954             }
955 1         4 return $self->{_selection_fgcolor};
956             }
957              
958             sub selection_bgcolor {
959 1     1 0 3 my ($self, $bgcolor) = @_;
960              
961 1 50       3 if ($bgcolor) {
962 1 50       3 if ($self->_check_value('_selection_bgcolor', $bgcolor)) {
963 1         3 $self->{_selection_bgcolor} = uc $bgcolor;
964             }
965             else {
966 0         0 carp "selection_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
967             }
968             }
969 1         5 return $self->{_selection_bgcolor};
970             }
971              
972             sub leader {
973 1     1 0 2 my ($self, $leader) = @_;
974              
975 1 50       4 if (defined $leader) {
976 1 50       3 if ($self->_check_value('_leader', $leader)) {
977 1 50       8 if ($leader =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
978 0         0 $self->{_leader} = 1;
979             }
980             else {
981 1         3 $self->{_leader} = 0;
982             }
983             }
984             else {
985 0         0 carp "leader must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
986             }
987             }
988 1         5 return $self->{_leader};
989             }
990              
991             sub trailer {
992 1     1 0 2 my ($self, $trailer) = @_;
993              
994 1 50       4 if (defined $trailer) {
995 1 50       4 if ($self->_check_value('_trailer', $trailer)) {
996 1 50       6 if ($trailer =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
997 0         0 $self->{_trailer} = 1;
998             }
999             else {
1000 1         3 $self->{_trailer} = 0;
1001             }
1002             }
1003             else {
1004 0         0 carp "trailer must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1005             }
1006             }
1007 1         4 return $self->{_trailer};
1008             }
1009              
1010             sub shortcut_prefix {
1011 1     1 0 3 my ($self, $prefix) = @_;
1012              
1013 1 50       4 if (defined $prefix) {
1014 1 50       4 if ($self->_check_value('_shortcut_prefix', $prefix)) {
1015 1         3 $self->{_shortcut_prefix} = $prefix;
1016             }
1017             else {
1018 0         0 carp "shortcut_prefix must be a string of printable characters or a line-drawing character";
1019             }
1020             }
1021 1         70 return $self->{_shortcut_prefix};
1022             }
1023              
1024             sub shortcut_postfix {
1025 1     1 0 3 my ($self, $postfix) = @_;
1026              
1027 1 50       6 if (defined $postfix) {
1028 1 50       3 if ($self->_check_value('_shortcut_postfix', $postfix)) {
1029 1         3 $self->{_shortcut_postfix} = $postfix;
1030             }
1031             else {
1032 0         0 carp "shortcut_postfix must be a string of printable characters or a line-drawing character";
1033             }
1034             }
1035 1         4 return $self->{_shortcut_postfix};
1036             }
1037              
1038             sub delimiter {
1039 1     1 0 3 my ($self, $del) = @_;
1040              
1041 1 50       5 if (defined $del) {
1042 1 50       3 if ($self->_check_value('_delimiter', $del)) {
1043 1         3 $self->{_delimiter} = $del;
1044             }
1045             else {
1046 0         0 carp "delimiter must be a string of printable characters or a line-drawing character";
1047             }
1048             }
1049 1         4 return $self->{_delimiter};
1050             }
1051              
1052             sub leader_delimiter {
1053 1     1 0 2 my ($self, $del) = @_;
1054              
1055 1 50       4 if (defined $del) {
1056 1 50       3 if ($self->_check_value('_leader_delimiter', $del)) {
1057 1         3 $self->{_leader_delimiter} = $del;
1058             }
1059             else {
1060 0         0 carp "leader_delimiter must be a string of printable characters or a line-drawing character";
1061             }
1062             }
1063 1         5 return $self->{_leader_delimiter};
1064             }
1065              
1066             sub trailer_delimiter {
1067 1     1 0 4 my ($self, $del) = @_;
1068              
1069 1 50       4 if (defined $del) {
1070 1 50       3 if ($self->_check_value('_trailer_delimiter', $del)) {
1071 1         3 $self->{_trailer_delimiter} = $del;
1072             }
1073             else {
1074 0         0 carp "trailer_delimiter must be a string of printable characters or a line-drawing character";
1075             }
1076             }
1077 1         4 return $self->{_trailer_delimiter};
1078             }
1079              
1080             sub label_prefix {
1081 1     1 0 5 my ($self, $prefix) = @_;
1082              
1083 1 50       4 if (defined $prefix) {
1084 1 50       4 if ($self->_check_value('_label_prefix', $prefix)) {
1085 1         3 $self->{_label_prefix} = $prefix;
1086             }
1087             else {
1088 0         0 carp "label_prefix must be a string of printable characters or a line-drawing character";
1089             }
1090             }
1091 1         7 return $self->{_label_prefix};
1092             }
1093              
1094             sub label_postfix {
1095 1     1 0 3 my ($self, $postfix) = @_;
1096              
1097 1 50       10 if (defined $postfix) {
1098 1 50       4 if ($self->_check_value('_label_postfix', $postfix)) {
1099 1         3 $self->{_label_postfix} = $postfix;
1100             }
1101             else {
1102 0         0 carp "label_postfix must be a string of printable characters or a line-drawing character";
1103             }
1104             }
1105 1         5 return $self->{_label_postfix};
1106             }
1107              
1108             sub title {
1109 1     1 0 3 my ($self, $title) = @_;
1110              
1111 1 50       5 if (defined $title) {
1112 1 50       4 if ($self->_check_value('_title', $title)) {
1113 1         3 $self->{_title} = $title;
1114             }
1115             else {
1116 0         0 carp "title must be a string of printable characters";
1117             }
1118             }
1119 1         5 return $self->{_title};
1120             }
1121              
1122             sub title_style {
1123 1     1 0 3 my ($self, $styles) = @_;
1124              
1125 1 50       8 if ($styles) {
1126 1 50       3 if (ref $styles eq 'ARRAY') {
1127 1 50       4 if ($self->_check_value('_title_style', $styles)) {
1128 1         2 foreach my $style (@{$styles}) {
  1         3  
1129 1         3 $style = uc $style;
1130             }
1131 1         3 $self->{_title_style} = $styles;
1132             }
1133             else {
1134 0         0 carp "title_style must be BLINK, REVERSE, BOLD, UNDERLINE and/or CLEAR";
1135             }
1136             }
1137             else {
1138 0         0 carp "title_style must be given as a reference to an array";
1139             }
1140             }
1141 1 50       7 return wantarray ? @{$self->{_title_style}} : $self->{_title_style};
  0         0  
1142             }
1143              
1144             sub title_fgcolor {
1145 1     1 0 2 my ($self, $fgcolor) = @_;
1146              
1147 1 50       4 if ($fgcolor) {
1148 1 50       9 if ($self->_check_value('_title_fgcolor', $fgcolor)) {
1149 1         4 $self->{_title_fgcolor} = uc $fgcolor;
1150             }
1151             else {
1152 0         0 carp "title_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1153             }
1154             }
1155 1         5 return $self->{_title_fgcolor};
1156             }
1157              
1158             sub title_bgcolor {
1159 1     1 0 3 my ($self, $bgcolor) = @_;
1160              
1161 1 50       4 if ($bgcolor) {
1162 1 50       3 if ($self->_check_value('_title_bgcolor', $bgcolor)) {
1163 1         4 $self->{_title_bgcolor} = uc $bgcolor;
1164             }
1165             else {
1166 0         0 carp "title_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1167             }
1168             }
1169 1         5 return $self->{_title_bgcolor};
1170             }
1171              
1172             sub title_align {
1173 1     1 0 3 my ($self, $align) = @_;
1174              
1175 1 50       7 if ($align) {
1176 1 50       3 if ($self->_check_value('_title_align', $align)) {
1177 1         14 $self->{_title_align} = uc $align;
1178             }
1179             else {
1180 0         0 carp "title_align must be LEFT, RIGHT or CENTER";
1181             }
1182             }
1183 1         5 return $self->{_title_align};
1184             }
1185              
1186             sub title_fill {
1187 1     1 0 4 my ($self, $fill) = @_;
1188              
1189 1 50       3 if (defined $fill) {
1190 1 50       5 if ($self->_check_value('_title_fill', $fill)) {
1191 1 50       7 if ($fill =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1192 1         3 $self->{_title_fill} = 1;
1193             }
1194             else {
1195 0         0 $self->{_title_fill} = 0;
1196             }
1197             }
1198             else {
1199 0         0 carp "title_fill must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1200             }
1201             }
1202 1         5 return $self->{_title_fill};
1203             }
1204              
1205             sub title_frame {
1206 1     1 0 3 my ($self, $frame) = @_;
1207              
1208 1 50       4 if (defined $frame) {
1209 1 50       3 if ($self->_check_value('_title_frame', $frame)) {
1210 1 50       7 if ($frame =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1211 1         3 $self->{_title_frame} = 1;
1212             }
1213             else {
1214 0         0 $self->{_title_frame} = 0;
1215             }
1216             }
1217             else {
1218 0         0 carp "title_frame must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1219             }
1220             }
1221 1         5 return $self->{_title_frame};
1222             }
1223              
1224             sub title_frame_style {
1225 1     1 0 9 my ($self, $styles) = @_;
1226              
1227 1 50       5 if ($styles) {
1228 1 50       5 if (ref $styles eq 'ARRAY') {
1229 1 50       4 if ($self->_check_value('_title_frame_style', $styles)) {
1230 1         2 foreach my $style (@{$styles}) {
  1         2  
1231 1         4 $style = uc $style;
1232             }
1233 1         3 $self->{_title_frame_style} = $styles;
1234             }
1235             else {
1236 0         0 carp "title_frame_style must be BLINK, REVERSE, BOLD and/or CLEAR";
1237             }
1238             }
1239             else {
1240 0         0 carp "title_frame_style must be given as a reference to an array";
1241             }
1242             }
1243 1 50       8 return wantarray ? @{$self->{_title_frame_style}} : $self->{_title_frame_style};
  0         0  
1244             }
1245              
1246             sub title_frame_fgcolor {
1247 1     1 0 4 my ($self, $fgcolor) = @_;
1248              
1249 1 50       5 if ($fgcolor) {
1250 1 50       3 if ($self->_check_value('_title_frame_fgcolor', $fgcolor)) {
1251 1         4 $self->{_title_frame_fgcolor} = uc $fgcolor;
1252             }
1253             else {
1254 0         0 carp "title_frame_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1255             }
1256             }
1257 1         5 return $self->{_title_frame_fgcolor};
1258             }
1259              
1260             sub title_frame_bgcolor {
1261 1     1 0 8 my ($self, $bgcolor) = @_;
1262              
1263 1 50       5 if ($bgcolor) {
1264 1 50       4 if ($self->_check_value('_title_frame_bgcolor', $bgcolor)) {
1265 1         4 $self->{_title_frame_bgcolor} = uc $bgcolor;
1266             }
1267             else {
1268 0         0 carp "title_frame_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1269             }
1270             }
1271 1         4 return $self->{_title_frame_bgcolor};
1272             }
1273              
1274             sub items {
1275 8     8 0 14 my ($self, $items) = @_;
1276              
1277 8 100       22 if ($items) {
1278 2 50       8 if (ref $items eq 'ARRAY') {
1279 2 50       7 if ($self->_check_value('_items', $items)) {
1280 2         7 $self->{_items} = $items;
1281             }
1282             else {
1283 0         0 carp "items must be an array of arrays containing keynames, descriptions and code references";
1284             }
1285             }
1286             else {
1287 0         0 carp "items must be given as a reference to an array";
1288             }
1289             }
1290 8 50       42 return wantarray ? @{$self->{_items}} : $self->{_items};
  0         0  
1291             }
1292              
1293             sub item_style {
1294 1     1 0 3 my ($self, $styles) = @_;
1295              
1296 1 50       4 if ($styles) {
1297 1 50       5 if (ref $styles eq 'ARRAY') {
1298 1 50       4 if ($self->_check_value('_item_style', $styles)) {
1299 1         4 foreach my $style (@{$styles}) {
  1         4  
1300 1         4 $style = uc $style;
1301             }
1302 1         5 $self->{_item_style} = $styles;
1303             }
1304             else {
1305 0         0 carp "item_style must be BLINK, REVERSE, BOLD, UNDERLINE and/or CLEAR";
1306             }
1307             }
1308             else {
1309 0         0 carp "item_style must be given as a reference to an array";
1310             }
1311             }
1312 1 50       19 return wantarray ? @{$self->{_item_style}} : $self->{_item_style};
  0         0  
1313             }
1314              
1315             sub item_fgcolor {
1316 1     1 0 3 my ($self, $fgcolor) = @_;
1317              
1318 1 50       5 if ($fgcolor) {
1319 1 50       5 if ($self->_check_value('_item_fgcolor', $fgcolor)) {
1320 1         4 $self->{_item_fgcolor} = uc $fgcolor;
1321             }
1322             else {
1323 0         0 carp "item_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1324             }
1325             }
1326 1         6 return $self->{_item_fgcolor};
1327             }
1328              
1329             sub item_bgcolor {
1330 1     1 0 3 my ($self, $bgcolor) = @_;
1331              
1332 1 50       4 if ($bgcolor) {
1333 1 50       4 if ($self->_check_value('_item_bgcolor', $bgcolor)) {
1334 1         4 $self->{_item_bgcolor} = uc $bgcolor;
1335             }
1336             else {
1337 0         0 carp "item_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1338             }
1339             }
1340 1         5 return $self->{_item_bgcolor};
1341             }
1342              
1343             sub item_align {
1344 1     1 0 4 my ($self, $align) = @_;
1345              
1346 1 50       13 if ($align) {
1347 1 50       4 if ($self->_check_value('_item_align', $align)) {
1348 1         4 $self->{_item_align} = uc $align;
1349             }
1350             else {
1351 0         0 carp "item_align must be LEFT, RIGHT or CENTER";
1352             }
1353             }
1354 1         5 return $self->{_item_align};
1355             }
1356              
1357             sub item_fill {
1358 1     1 0 2 my ($self, $fill) = @_;
1359              
1360 1 50       6 if (defined $fill) {
1361 1 50       8 if ($self->_check_value('_item_fill', $fill)) {
1362 1 50       14 if ($fill =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1363 1         4 $self->{_item_fill} = 1;
1364             }
1365             else {
1366 0         0 $self->{_item_fill} = 0;
1367             }
1368             }
1369             else {
1370 0         0 carp "item_fill must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1371             }
1372             }
1373 1         5 return $self->{_item_fill};
1374             }
1375              
1376             sub item_frame {
1377 1     1 0 3 my ($self, $frame) = @_;
1378              
1379 1 50       5 if (defined $frame) {
1380 1 50       4 if ($self->_check_value('_item_frame', $frame)) {
1381 1 50       6 if ($frame =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1382 1         108 $self->{_item_frame} = 1;
1383             }
1384             else {
1385 0         0 $self->{_item_frame} = 0;
1386             }
1387             }
1388             else {
1389 0         0 carp "item_frame must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1390             }
1391             }
1392 1         8 return $self->{_item_frame};
1393             }
1394              
1395             sub item_frame_style {
1396 1     1 0 2 my ($self, $styles) = @_;
1397              
1398 1 50       5 if ($styles) {
1399 1 50       6 if (ref $styles eq 'ARRAY') {
1400 1 50       4 if ($self->_check_value('_item_frame_style', $styles)) {
1401 1         2 foreach my $style (@{$styles}) {
  1         3  
1402 1         3 $style = uc $style;
1403             }
1404 1         4 $self->{_item_frame_style} = $styles;
1405             }
1406             else {
1407 0         0 carp "item_frame_style must be BLINK, REVERSE, BOLD and/or CLEAR";
1408             }
1409             }
1410             else {
1411 0         0 carp "item_frame_style must be given as a reference to an array";
1412             }
1413             }
1414 1 50       9 return wantarray ? @{$self->{_item_frame_style}} : $self->{_item_frame_style};
  0         0  
1415             }
1416              
1417             sub item_frame_fgcolor {
1418 1     1 0 3 my ($self, $fgcolor) = @_;
1419              
1420 1 50       5 if ($fgcolor) {
1421 1 50       4 if ($self->_check_value('_item_frame_fgcolor', $fgcolor)) {
1422 1         4 $self->{_item_frame_fgcolor} = uc $fgcolor;
1423             }
1424             else {
1425 0         0 carp "item_frame_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1426             }
1427             }
1428 1         6 return $self->{_item_frame_fgcolor};
1429             }
1430              
1431             sub item_frame_bgcolor {
1432 1     1 0 4 my ($self, $bgcolor) = @_;
1433              
1434 1 50       4 if ($bgcolor) {
1435 1 50       4 if ($self->_check_value('_item_frame_bgcolor', $bgcolor)) {
1436 1         5 $self->{_item_frame_bgcolor} = uc $bgcolor;
1437             }
1438             else {
1439 0         0 carp "item_frame_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1440             }
1441             }
1442 1         5 return $self->{_item_frame_bgcolor};
1443             }
1444              
1445             sub status {
1446 1     1 0 4 my ($self, $status) = @_;
1447              
1448 1 50       4 if (defined $status) {
1449 1 50       4 if ($self->_check_value('_status', $status)) {
1450 1         3 $self->{_status} = $status;
1451             }
1452             else {
1453 0         0 carp "status must be a string of printable characters";
1454             }
1455             }
1456 1         5 return $self->{_status};
1457             }
1458              
1459             sub status_style {
1460 1     1 0 2 my ($self, $styles) = @_;
1461              
1462 1 50       3 if ($styles) {
1463 1 50       9 if (ref $styles eq 'ARRAY') {
1464 1 50       5 if ($self->_check_value('_status_style', $styles)) {
1465 1         2 foreach my $style (@{$styles}) {
  1         3  
1466 1         4 $style = uc $style;
1467             }
1468 1         3 $self->{_status_style} = $styles;
1469             }
1470             else {
1471 0         0 carp "status_style must be BLINK, REVERSE, BOLD, UNDERLINE and/or CLEAR";
1472             }
1473             }
1474             else {
1475 0         0 carp "status_style must be given as a reference to an array";
1476             }
1477             }
1478 1 50       8 return wantarray ? @{$self->{_status_style}} : $self->{_status_style};
  0         0  
1479             }
1480              
1481             sub status_fgcolor {
1482 1     1 0 3 my ($self, $fgcolor) = @_;
1483              
1484 1 50       4 if ($fgcolor) {
1485 1 50       5 if ($self->_check_value('_status_fgcolor', $fgcolor)) {
1486 1         5 $self->{_status_fgcolor} = uc $fgcolor;
1487             }
1488             else {
1489 0         0 carp "status_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1490             }
1491             }
1492 1         6 return $self->{_status_fgcolor};
1493             }
1494              
1495             sub status_bgcolor {
1496 1     1 0 5 my ($self, $bgcolor) = @_;
1497              
1498 1 50       19 if ($bgcolor) {
1499 1 50       4 if ($self->_check_value('_status_bgcolor', $bgcolor)) {
1500 1         4 $self->{_status_bgcolor} = uc $bgcolor;
1501             }
1502             else {
1503 0         0 carp "status_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1504             }
1505             }
1506 1         4 return $self->{_status_bgcolor};
1507             }
1508              
1509             sub status_align {
1510 1     1 0 3 my ($self, $align) = @_;
1511              
1512 1 50       20 if ($align) {
1513 1 50       5 if ($self->_check_value('_status_align', $align)) {
1514 1         5 $self->{_status_align} = uc $align;
1515             }
1516             else {
1517 0         0 carp "status_align must be LEFT, RIGHT or CENTER";
1518             }
1519             }
1520 1         6 return $self->{_status_align};
1521             }
1522              
1523             sub status_fill {
1524 1     1 0 3 my ($self, $fill) = @_;
1525              
1526 1 50       4 if (defined $fill) {
1527 1 50       5 if ($self->_check_value('_status_fill', $fill)) {
1528 1 50       6 if ($fill =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1529 1         4 $self->{_status_fill} = 1;
1530             }
1531             else {
1532 0         0 $self->{_status_fill} = 0;
1533             }
1534             }
1535             else {
1536 0         0 carp "status_fill must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1537             }
1538             }
1539 1         4 return $self->{_status_fill};
1540             }
1541              
1542             sub status_frame {
1543 1     1 0 3 my ($self, $frame) = @_;
1544              
1545 1 50       5 if (defined $frame) {
1546 1 50       4 if ($self->_check_value('_status_frame', $frame)) {
1547 1 50       6 if ($frame =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1548 0         0 $self->{_status_frame} = 1;
1549             }
1550             else {
1551 1         5 $self->{_status_frame} = 0;
1552             }
1553             }
1554             else {
1555 0         0 carp "status_frame must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1556             }
1557             }
1558 1         8 return $self->{_status_frame};
1559             }
1560              
1561             sub status_frame_style {
1562 1     1 0 2 my ($self, $styles) = @_;
1563              
1564 1 50       4 if ($styles) {
1565 1 50       5 if (ref $styles eq 'ARRAY') {
1566 1 50       3 if ($self->_check_value('_status_frame_style', $styles)) {
1567 1         2 foreach my $style (@{$styles}) {
  1         3  
1568 1         3 $style = uc $style;
1569             }
1570 1         4 $self->{_status_frame_style} = $styles;
1571             }
1572             else {
1573 0         0 carp "status_frame_style must be BLINK, REVERSE, BOLD and/or CLEAR";
1574             }
1575             }
1576             else {
1577 0         0 carp "status_frame_style must be given as a reference to an array";
1578             }
1579             }
1580 1 50       8 return wantarray ? @{$self->{_status_frame_style}} : $self->{_status_frame_style};
  0         0  
1581             }
1582              
1583             sub status_frame_fgcolor {
1584 1     1 0 4 my ($self, $fgcolor) = @_;
1585              
1586 1 50       4 if ($fgcolor) {
1587 1 50       3 if ($self->_check_value('_status_frame_fgcolor', $fgcolor)) {
1588 1         4 $self->{_status_frame_fgcolor} = uc $fgcolor;
1589             }
1590             else {
1591 0         0 carp "status_frame_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1592             }
1593             }
1594 1         5 return $self->{_status_frame_fgcolor};
1595             }
1596              
1597             sub status_frame_bgcolor {
1598 1     1 0 2 my ($self, $bgcolor) = @_;
1599              
1600 1 50       4 if ($bgcolor) {
1601 1 50       4 if ($self->_check_value('_status_frame_bgcolor', $bgcolor)) {
1602 1         4 $self->{_status_frame_bgcolor} = uc $bgcolor;
1603             }
1604             else {
1605 0         0 carp "status_frame_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1606             }
1607             }
1608 1         8 return $self->{_status_frame_bgcolor};
1609             }
1610              
1611             sub prompt {
1612 1     1 0 3 my ($self, $prompt) = @_;
1613              
1614 1 50       4 if (defined $prompt) {
1615 1 50       3 if ($self->_check_value('_prompt', $prompt)) {
1616 1         3 $self->{_prompt} = $prompt;
1617             }
1618             else {
1619 0         0 carp "prompt must be a string of printable characters";
1620             }
1621             }
1622 1         5 return $self->{_prompt};
1623             }
1624              
1625             sub prompt_style {
1626 1     1 0 3 my ($self, $styles) = @_;
1627              
1628 1 50       9 if ($styles) {
1629 1 50       5 if (ref $styles eq 'ARRAY') {
1630 1 50       3 if ($self->_check_value('_prompt_style', $styles)) {
1631 1         3 foreach my $style (@{$styles}) {
  1         2  
1632 1         4 $style = uc $style;
1633             }
1634 1         4 $self->{_prompt_style} = $styles;
1635             }
1636             else {
1637 0         0 carp "prompt_style must be BLINK, REVERSE, BOLD, UNDERLINE and/or CLEAR";
1638             }
1639             }
1640             else {
1641 0         0 carp "prompt_style must be given as a reference to an array";
1642             }
1643             }
1644 1 50       7 return wantarray ? @{$self->{_prompt_style}} : $self->{_prompt_style};
  0         0  
1645             }
1646              
1647             sub prompt_fgcolor {
1648 1     1 0 3 my ($self, $fgcolor) = @_;
1649              
1650 1 50       4 if ($fgcolor) {
1651 1 50       5 if ($self->_check_value('_prompt_fgcolor', $fgcolor)) {
1652 1         3 $self->{_prompt_fgcolor} = uc $fgcolor;
1653             }
1654             else {
1655 0         0 carp "prompt_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1656             }
1657             }
1658 1         5 return $self->{_prompt_fgcolor};
1659             }
1660              
1661             sub prompt_bgcolor {
1662 1     1 0 3 my ($self, $bgcolor) = @_;
1663              
1664 1 50       4 if ($bgcolor) {
1665 1 50       3 if ($self->_check_value('_prompt_bgcolor', $bgcolor)) {
1666 1         4 $self->{_prompt_bgcolor} = uc $bgcolor;
1667             }
1668             else {
1669 0         0 carp "prompt_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1670             }
1671             }
1672 1         8 return $self->{_prompt_bgcolor};
1673             }
1674              
1675             sub prompt_align {
1676 1     1 0 3 my ($self, $align) = @_;
1677              
1678 1 50       4 if ($align) {
1679 1 50       3 if ($self->_check_value('_prompt_align', $align)) {
1680 1         5 $self->{_prompt_align} = uc $align;
1681             }
1682             else {
1683 0         0 carp "prompt_align must be LEFT, RIGHT or CENTER";
1684             }
1685             }
1686 1         5 return $self->{_prompt_align};
1687             }
1688              
1689             sub prompt_fill {
1690 1     1 0 111 my ($self, $fill) = @_;
1691              
1692 1 50       43 if (defined $fill) {
1693 1 50       4 if ($self->_check_value('_prompt_fill', $fill)) {
1694 1 50       11 if ($fill =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1695 1         4 $self->{_prompt_fill} = 1;
1696             }
1697             else {
1698 0         0 $self->{_prompt_fill} = 0;
1699             }
1700             }
1701             else {
1702 0         0 carp "prompt_fill must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1703             }
1704             }
1705 1         5 return $self->{_prompt_fill};
1706             }
1707              
1708             sub prompt_frame {
1709 1     1 0 2 my ($self, $frame) = @_;
1710              
1711 1 50       4 if (defined $frame) {
1712 1 50       4 if ($self->_check_value('_prompt_frame', $frame)) {
1713 1 50       13 if ($frame =~ /^(?:\+|1|YES|Y|TRUE|T)$/i) {
1714 1         3 $self->{_prompt_frame} = 1;
1715             }
1716             else {
1717 0         0 $self->{_prompt_frame} = 0;
1718             }
1719             }
1720             else {
1721 0         0 carp "prompt_frame must be -, +, 0, 1, NO, N, YES, Y, FALSE, F, TRUE or T";
1722             }
1723             }
1724 1         5 return $self->{_prompt_frame};
1725             }
1726              
1727             sub prompt_frame_style {
1728 1     1 0 2 my ($self, $styles) = @_;
1729              
1730 1 50       4 if ($styles) {
1731 1 50       3 if (ref $styles eq 'ARRAY') {
1732 1 50       4 if ($self->_check_value('_prompt_frame_style', $styles)) {
1733 1         1 foreach my $style (@{$styles}) {
  1         2  
1734 1         4 $style = uc $style;
1735             }
1736 1         4 $self->{_prompt_frame_style} = $styles;
1737             }
1738             else {
1739 0         0 carp "prompt_frame_style must be BLINK, REVERSE, BOLD and/or CLEAR";
1740             }
1741             }
1742             else {
1743 0         0 carp "prompt_frame_style must be given as a reference to an array";
1744             }
1745             }
1746 1 50       6 return wantarray ? @{$self->{_prompt_frame_style}} : $self->{_prompt_frame_style};
  0         0  
1747             }
1748              
1749             sub prompt_frame_fgcolor {
1750 1     1 0 3 my ($self, $fgcolor) = @_;
1751              
1752 1 50       3 if ($fgcolor) {
1753 1 50       5 if ($self->_check_value('_prompt_frame_fgcolor', $fgcolor)) {
1754 1         4 $self->{_prompt_frame_fgcolor} = uc $fgcolor;
1755             }
1756             else {
1757 0         0 carp "prompt_frame_fgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1758             }
1759             }
1760 1         4 return $self->{_prompt_frame_fgcolor};
1761             }
1762              
1763             sub prompt_frame_bgcolor {
1764 1     1 0 12 my ($self, $bgcolor) = @_;
1765              
1766 1 50       3 if ($bgcolor) {
1767 1 50       4 if ($self->_check_value('_prompt_frame_bgcolor', $bgcolor)) {
1768 1         4 $self->{_prompt_frame_bgcolor} = uc $bgcolor;
1769             }
1770             else {
1771 0         0 carp "prompt_frame_bgcolor must be BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN or WHITE";
1772             }
1773             }
1774 1         6 return $self->{_prompt_frame_bgcolor};
1775             }
1776              
1777             #===============================================================================
1778             #Methods
1779             #===============================================================================
1780              
1781             sub read_key {
1782 0     0 0 0 my $self = shift;
1783              
1784 0         0 my $key = undef;
1785 0         0 ReadMode(4);
1786 0         0 my $char = ReadKey(0);
1787 0 0       0 if ($char eq "\e") {
    0          
    0          
1788             #Escape sequences
1789 0         0 $char = ReadKey(0);
1790 0 0       0 if ($char eq "[") {
    0          
    0          
1791 0         0 $char = ReadKey(0);
1792 0 0       0 if ($char =~ /[ABCDFH]/) {
    0          
    0          
1793             #VT100 specific sequences
1794 0         0 $key = $self->_get_keyname("\e[" . $char);
1795             }
1796             elsif ($char eq "[") {
1797 0         0 $char = ReadKey(0);
1798 0 0       0 if ($char =~ /[ABCDE]/) {
1799             #Linux console specific sequences
1800 0         0 $key = $self->_get_keyname("\e[[" . $char);
1801             }
1802             }
1803             elsif ($char =~ /^\d$/) {
1804 0         0 my $num = $char;
1805 0         0 $char = ReadKey(0);
1806 0         0 while ($char ne '~') {
1807 0         0 $num = $num * 10 + $char;
1808 0         0 $char = ReadKey(0);
1809             }
1810             #VT100 and Linux console sequences
1811 0         0 $key = $self->_get_keyname("\e[" . $num . "~");
1812             }
1813             }
1814             elsif ($char eq "O") {
1815 0         0 $char = ReadKey(0);
1816 0 0       0 if ($char =~ /[ABCDFHPQRS]/) {
1817             #Xterm specific sequences
1818 0         0 $key = $self->_get_keyname("\eO" . $char);
1819             }
1820             }
1821             elsif ($char =~ /[a-z]/) {
1822             #Meta a-z
1823 0         0 $key = $self->_get_keyname("\e" . $char);
1824             }
1825             }
1826             elsif ($self->_get_keyname($char)) {
1827             #Keys with special names, including CTRL a-z
1828 0         0 $key = $self->_get_keyname($char);
1829             }
1830             elsif ($char =~ /^[[:graph:]]$/) {
1831             #Plain keys
1832 0         0 $key = $char;
1833             }
1834 0         0 ReadMode(0);
1835 0         0 return $key;
1836             }
1837              
1838             sub up {
1839 0     0 0 0 my ($self, $n) = @_;
1840              
1841 0 0       0 $n = 0 unless defined $n;
1842 0 0       0 if ($n =~ /^\d+$/) {
1843 0         0 print "\x1B[" . $n . "A";
1844 0         0 return 1;
1845             }
1846 0         0 return 0;
1847             }
1848              
1849             sub down {
1850 0     0 0 0 my ($self, $n) = @_;
1851              
1852 0 0       0 $n = 0 unless defined $n;
1853 0 0       0 if ($n =~ /^\d+$/) {
1854 0         0 print "\x1B[" . $n . "B";
1855 0         0 return 1;
1856             }
1857 0         0 return 0;
1858             }
1859              
1860             sub right {
1861 0     0 0 0 my ($self, $n) = @_;
1862              
1863 0 0       0 $n = 0 unless defined $n;
1864 0 0       0 if ($n =~ /^\d+$/) {
1865 0         0 print "\x1B[" . $n . "C";
1866 0         0 return 1;
1867             }
1868 0         0 return 0;
1869             }
1870              
1871             sub left {
1872 0     0 0 0 my ($self, $n) = @_;
1873              
1874 0 0       0 $n = 0 unless defined $n;
1875 0 0       0 if ($n =~ /^\d+$/) {
1876 0         0 print "\x1B[" . $n . "D";
1877 0         0 return 1;
1878             }
1879 0         0 return 0;
1880             }
1881              
1882             sub region {
1883 0     0 0 0 my ($self, $t, $b) = @_;
1884              
1885 0 0 0     0 $t = 1 unless defined $t and $t <= $self->height();
1886 0 0 0     0 $b = $self->height() unless defined $b and $b <= $self->height();
1887 0 0       0 if ($b >= $t) {
1888 0         0 print "\x1B[" . $t . ";" . $b . "r";
1889 0         0 return 1;
1890             }
1891 0         0 return 0;
1892             }
1893              
1894             sub pos {
1895 0     0 0 0 my ($self, $l, $c) = @_;
1896              
1897 0 0 0     0 $l = 1 unless defined $l and $l > 0 and $l <= $self->height();
      0        
1898 0 0 0     0 $c = 1 unless defined $c and $c > 0 and $c <= $self->width();
      0        
1899 0 0 0     0 if ($l =~ /^\d+$/ and $c =~ /^\d+$/) {
1900 0         0 print "\x1B[" . $l . ";" . $c ."H";
1901 0         0 return 1;
1902             }
1903 0         0 return 0;
1904             }
1905              
1906             sub print_title {
1907 0     0 0 0 my $self = shift;
1908              
1909 0         0 my $width = $self->width();
1910 0         0 my $title = $self->title();
1911 0         0 my $max_length = $width;
1912 0 0       0 $max_length -= 2 if $self->title_frame();
1913 0         0 my $padding = $max_length - length $title;
1914 0 0       0 $title = substr($title, 0, $max_length) if length($title) > $max_length;
1915 0 0       0 if ($self->title_fill()) {
1916 0 0       0 if ($self->title_align() eq 'CENTER') {
    0          
1917 0         0 my $lpadding = int ($padding / 2);
1918 0         0 my $rpadding = $padding - $lpadding;
1919 0         0 $title = " " x $lpadding . $title . " " x $rpadding;
1920             }
1921             elsif ($self->title_align() eq 'RIGHT') {
1922 0         0 $title = " " x $padding . $title;
1923             }
1924             else {
1925 0         0 $title .= " " x $padding;
1926             }
1927             }
1928 0         0 $self->pos(1,1);
1929 0 0       0 if ($self->title_frame()) {
1930 0         0 print RESET;
1931 0         0 $self->_print_color($self->title_frame_fgcolor(), $self->title_frame_bgcolor());
1932 0         0 $self->_print_style($self->title_frame_style());
1933 0         0 print ULC;
1934 0         0 print HOR x length $title;
1935 0         0 print URC;
1936 0         0 print "\n";
1937 0         0 print VER;
1938             }
1939 0         0 print RESET;
1940 0         0 $self->_print_color($self->title_fgcolor(), $self->title_bgcolor());
1941 0         0 $self->_print_style($self->title_style());
1942 0         0 print $title;
1943 0 0       0 if ($self->title_frame()) {
1944 0         0 print RESET;
1945 0         0 $self->_print_color($self->title_frame_fgcolor(), $self->title_frame_bgcolor());
1946 0         0 $self->_print_style($self->title_frame_style());
1947 0         0 print VER;
1948 0         0 print "\n";
1949 0         0 print LLC;
1950 0         0 print HOR x length $title;
1951 0         0 print LRC;
1952             }
1953 0         0 print RESET;
1954 0         0 print "\n";
1955             }
1956              
1957             sub print_items {
1958 0     0 0 0 my ($self, $selected) = @_;
1959              
1960 0 0       0 if (defined $selected) {
1961 0 0       0 $self->selection($selected) if $self->_check_value('_selection', $selected);
1962             }
1963 0         0 my $width = $self->width();
1964 0         0 my $max_length = $width;
1965 0 0       0 if ($self->item_frame()) {
    0          
1966 0         0 $max_length -= 3;
1967             }
1968             elsif ($self->delimiter()) {
1969 0         0 $max_length--;
1970             }
1971 0         0 my $key_length = 0;
1972 0         0 my $desc_length = 0;
1973 0         0 foreach my $item (@{$self->items()}) {
  0         0  
1974 0 0       0 $key_length = length($item->[0]) if length($item->[0]) > $key_length;
1975 0 0       0 $desc_length = length($item->[1]) if length($item->[1]) > $desc_length;
1976             }
1977 0         0 $key_length += $self->_linestr_length($self->shortcut_prefix());
1978 0         0 $key_length += $self->_linestr_length($self->shortcut_postfix());
1979 0         0 my $label_length = $desc_length;
1980 0         0 $label_length += $self->_linestr_length($self->label_prefix());
1981 0         0 $label_length += $self->_linestr_length($self->label_postfix());
1982 0 0 0     0 if ($key_length + $label_length > $max_length or $self->item_fill()) {
1983 0         0 $label_length = $max_length - $key_length;
1984 0         0 $desc_length = $label_length - $self->_linestr_length($self->shortcut_prefix());
1985 0         0 $desc_length = $desc_length - $self->_linestr_length($self->shortcut_postfix());
1986             }
1987 0         0 my $last_item = $self->item_count() - 1;
1988 0         0 my $highlight = 0;
1989 0 0 0     0 $highlight++ if $self->selection() > 0 and $self->selection() <= $last_item + 1;
1990 0         0 my $i = 0;
1991 0         0 $self->pos($self->_items_start(),1);
1992 0         0 foreach my $item (@{$self->items()}) {
  0         0  
1993 0 0       0 if ($i == 0) {
1994 0 0       0 if ($self->item_frame()) {
    0          
1995 0         0 print RESET;
1996 0         0 $self->_print_color($self->item_frame_fgcolor(), $self->item_frame_bgcolor());
1997 0         0 $self->_print_style($self->item_frame_style());
1998 0         0 print ULC;
1999 0         0 print HOR x $key_length;
2000 0         0 print TTE;
2001 0         0 print HOR x $label_length;
2002 0         0 print URC;
2003 0         0 print "\n";
2004             }
2005             elsif ($self->leader()) {
2006 0         0 print RESET;
2007 0         0 $self->_print_color($self->item_fgcolor(), $self->item_bgcolor());
2008 0         0 $self->_print_style($self->item_style());
2009 0         0 print ULC;
2010 0         0 print HOR x ($key_length - 1);
2011 0 0       0 $self->_print_linestr($self->leader_delimiter()) if $self->delimiter();
2012 0         0 print HOR x ($label_length - 1);
2013 0         0 print URC;
2014 0         0 print "\n";
2015             }
2016             }
2017 0 0       0 if ($self->item_frame()) {
2018 0         0 print RESET;
2019 0         0 $self->_print_color($self->item_frame_fgcolor(), $self->item_frame_bgcolor());
2020 0         0 $self->_print_style($self->item_frame_style());
2021 0         0 print VER;
2022             }
2023 0         0 print RESET;
2024 0 0 0     0 if ($highlight and $i == $self->selection() - 1) {
2025 0         0 $self->_print_color($self->selection_fgcolor(), $self->selection_bgcolor());
2026 0         0 $self->_print_style($self->selection_style());
2027             }
2028             else {
2029 0         0 $self->_print_color($self->item_fgcolor(), $self->item_bgcolor());
2030 0         0 $self->_print_style($self->item_style());
2031             }
2032 0 0       0 $self->_print_linestr($self->shortcut_prefix()) if $self->shortcut_prefix();
2033 0         0 print $item->[0];
2034 0 0       0 if (length($item->[0]) < $key_length) {
2035 0         0 print ' ' x ($key_length - length($item->[0]));
2036             }
2037 0 0       0 $self->_print_linestr($self->shortcut_postfix()) if $self->shortcut_postfix();
2038 0 0       0 if ($self->item_frame()) {
    0          
2039 0         0 print RESET;
2040 0         0 $self->_print_color($self->item_frame_fgcolor(), $self->item_frame_bgcolor());
2041 0         0 $self->_print_style($self->item_frame_style());
2042 0 0       0 if ($self->spacious_items()) {
2043 0         0 print VER;
2044             }
2045             else {
2046 0 0       0 if ($self->delimiter()) {
2047 0         0 $self->_print_linestr($self->delimiter());
2048             }
2049             else {
2050 0         0 print VER;
2051             }
2052             }
2053             }
2054             elsif ($self->delimiter()) {
2055 0         0 $self->_print_linestr($self->delimiter());
2056             }
2057 0         0 print RESET;
2058 0 0 0     0 if ($highlight and $i == $self->selection() - 1) {
2059 0         0 $self->_print_color($self->selection_fgcolor(), $self->selection_bgcolor());
2060 0         0 $self->_print_style($self->selection_style());
2061             }
2062             else {
2063 0         0 $self->_print_color($self->item_fgcolor(), $self->item_bgcolor());
2064 0         0 $self->_print_style($self->item_style());
2065             }
2066 0 0       0 if ($self->label_prefix()) {
2067 0         0 $self->_print_linestr($self->label_prefix());
2068             }
2069 0         0 my $desc = '';
2070 0 0       0 if (length($item->[1]) > $desc_length) {
    0          
2071 0         0 $desc = substr($item->[1], 0, $desc_length);
2072             }
2073             elsif (length($item->[1]) < $desc_length) {
2074 0 0       0 if ($self->item_fill()) {
2075 0 0       0 if ($self->item_align() eq 'CENTER') {
    0          
2076 0         0 my $lpad = int (($desc_length - length($item->[1])) / 2);
2077 0         0 my $rpad = $desc_length - length($item->[1]) - $lpad;
2078 0         0 $desc = ' ' x $lpad . $item->[1] . ' ' x $rpad;
2079             }
2080             elsif ($self->item_align() eq 'RIGHT') {
2081 0         0 $desc = ' ' x ($desc_length - length($item->[1])) . $item->[1];
2082             }
2083             else {
2084 0         0 $desc = $item->[1] . ' ' x ($desc_length - length($item->[1]));
2085             }
2086             }
2087             else {
2088 0         0 $desc = $item->[1];
2089             }
2090             }
2091             else {
2092 0         0 $desc = $item->[1];
2093             }
2094 0         0 print $desc;
2095 0 0       0 if ($self->label_postfix()) {
2096 0         0 $self->_print_linestr($self->label_postfix());
2097             }
2098 0 0       0 if ($self->item_frame()) {
2099 0         0 print RESET;
2100 0         0 $self->_print_color($self->item_frame_fgcolor(), $self->item_frame_bgcolor());
2101 0         0 $self->_print_style($self->item_frame_style());
2102 0         0 print VER;
2103             }
2104 0         0 print "\n";
2105 0 0 0     0 if ($i < $last_item and $self->spacious_items()) {
2106 0 0       0 if ($self->item_frame()) {
2107 0         0 print LTE;
2108 0         0 print HOR x $key_length;
2109 0         0 print CTE;
2110 0         0 print HOR x $label_length;
2111 0         0 print RTE;
2112 0         0 print "\n";
2113             }
2114             }
2115 0 0       0 if ($i == $last_item) {
2116 0 0       0 if ($self->item_frame()) {
    0          
2117 0         0 print RESET;
2118 0         0 $self->_print_color($self->item_frame_fgcolor(), $self->item_frame_bgcolor());
2119 0         0 $self->_print_style($self->item_frame_style());
2120 0         0 print LLC;
2121 0         0 print HOR x $key_length;
2122 0         0 print BTE;
2123 0         0 print HOR x $label_length;
2124 0         0 print LRC;
2125 0         0 print "\n";
2126             }
2127             elsif ($self->trailer()) {
2128 0         0 print RESET;
2129 0         0 $self->_print_color($self->item_fgcolor(), $self->item_bgcolor());
2130 0         0 $self->_print_style($self->item_style());
2131 0         0 print LLC;
2132 0         0 print HOR x ($key_length - 1);
2133 0 0       0 $self->_print_linestr($self->trailer_delimiter()) if $self->delimiter();
2134 0         0 print HOR x ($label_length - 1);
2135 0         0 print LRC;
2136 0         0 print "\n";
2137             }
2138             }
2139 0         0 $i++;
2140             }
2141 0         0 print RESET;
2142             }
2143              
2144             sub print_status {
2145 0     0 0 0 my ($self, $text) = @_;
2146              
2147 0         0 my $width = $self->width();
2148 0         0 my $status;
2149 0 0 0     0 if (defined $text and length $text > 0) {
2150 0         0 $status = $text;
2151             }
2152             else {
2153 0         0 $status = $self->status();
2154             }
2155 0         0 my $max_length = $width;
2156 0 0       0 $max_length -= 2 if $self->status_frame();
2157 0         0 my $padding = $max_length - length $status;
2158 0 0       0 $status = substr($status, 0, $max_length) if length($status) > $max_length;
2159 0 0       0 if ($self->status_fill()) {
2160 0 0       0 if ($self->status_align() eq 'CENTER') {
    0          
2161 0         0 my $lpadding = int ($padding / 2);
2162 0         0 my $rpadding = $padding - $lpadding;
2163 0         0 $status = " " x $lpadding . $status . " " x $rpadding;
2164             }
2165             elsif ($self->status_align() eq 'RIGHT') {
2166 0         0 $status = " " x $padding . $status;
2167             }
2168             else {
2169 0         0 $status .= " " x $padding;
2170             }
2171             }
2172 0         0 $self->pos($self->_status_start(),1);
2173 0 0       0 if ($self->status_frame()) {
2174 0         0 print RESET;
2175 0         0 $self->_print_color($self->status_frame_fgcolor(), $self->status_frame_bgcolor());
2176 0         0 $self->_print_style($self->status_frame_style());
2177 0         0 print ULC;
2178 0         0 print HOR x length $status;
2179 0         0 print URC;
2180 0         0 print "\n";
2181 0         0 print VER;
2182             }
2183 0         0 print RESET;
2184 0         0 $self->_print_color($self->status_fgcolor(), $self->status_bgcolor());
2185 0         0 $self->_print_style($self->status_style());
2186 0         0 print $status;
2187 0 0       0 if ($self->status_frame()) {
2188 0         0 print RESET;
2189 0         0 $self->_print_color($self->status_frame_fgcolor(), $self->status_frame_bgcolor());
2190 0         0 $self->_print_style($self->status_frame_style());
2191 0         0 print VER;
2192 0         0 print "\n";
2193 0         0 print LLC;
2194 0         0 print HOR x length $status;
2195 0         0 print LRC;
2196             }
2197 0         0 print RESET;
2198 0         0 print "\n";
2199             }
2200              
2201             sub print_prompt {
2202 0     0 0 0 my $self = shift;
2203              
2204 0         0 my $width = $self->width();
2205 0         0 my $max_length = $width - 1; #Allocate space for cursor
2206 0 0       0 $max_length -= 2 if $self->prompt_frame();
2207 0         0 my $padding = $max_length - length $self->prompt();
2208 0 0       0 $padding = 0 if $padding < 0;
2209 0         0 my $lpadding = 0;
2210 0         0 my $rpadding = 0;
2211 0         0 my $prompt = $self->prompt();
2212 0 0       0 $prompt = substr($self->prompt(), 0, $max_length) if length($self->prompt()) > $max_length;
2213 0 0       0 if ($self->prompt_fill()) {
2214 0 0       0 if ($self->prompt_align() eq 'CENTER') {
2215 0         0 $lpadding = int ($padding / 2);
2216 0         0 $rpadding = $padding - $lpadding;
2217             }
2218             }
2219 0         0 $self->pos($self->_prompt_start(),1);
2220 0 0       0 if ($self->prompt_frame()) {
2221 0         0 print RESET;
2222 0         0 $self->_print_color($self->prompt_frame_fgcolor(), $self->prompt_frame_bgcolor());
2223 0         0 $self->_print_style($self->prompt_frame_style());
2224 0         0 print ULC;
2225 0         0 print HOR x (length($prompt) + 1);
2226 0 0       0 if ($self->prompt_fill()) {
2227 0         0 print HOR x $padding;
2228             }
2229 0         0 print URC;
2230 0         0 print "\n";
2231 0         0 print VER;
2232             }
2233 0         0 print RESET;
2234 0         0 $self->_print_color($self->prompt_fgcolor(), $self->prompt_bgcolor());
2235 0         0 $self->_print_style($self->prompt_style());
2236 0 0       0 if ($self->prompt_fill()) {
2237 0 0       0 if ($self->prompt_align() eq 'CENTER') {
    0          
2238 0         0 print ' ' x $lpadding;
2239 0         0 print $prompt, ' ';
2240 0         0 print ' ' x $rpadding;
2241             }
2242             elsif ($self->prompt_align() eq 'RIGHT') {
2243 0         0 print ' ' x $padding;
2244 0         0 print $prompt, ' ';
2245             }
2246             else {
2247 0         0 print $prompt, ' ';
2248 0         0 print ' ' x $padding;
2249             }
2250             }
2251             else {
2252 0         0 print $prompt, ' ';
2253             }
2254 0 0       0 if ($self->prompt_frame()) {
2255 0         0 print RESET;
2256 0         0 $self->_print_color($self->prompt_frame_fgcolor(), $self->prompt_frame_bgcolor());
2257 0         0 $self->_print_style($self->prompt_frame_style());
2258 0         0 print VER;
2259 0         0 print "\n";
2260 0         0 print LLC;
2261 0         0 print HOR x (length($prompt) + 1);
2262 0 0       0 if ($self->prompt_fill()) {
2263 0         0 print HOR x $padding;
2264             }
2265 0         0 print LRC;
2266             }
2267 0         0 print RESET;
2268 0         0 print "\n";
2269 0         0 $self->print_cursor();
2270             }
2271              
2272             #Position the cursor and print cursor_char
2273             sub print_cursor {
2274 0     0 0 0 my $self = shift;
2275              
2276 0         0 my ($l, $c) = $self->_cursor_pos();
2277 0         0 $self->pos($l, $c);
2278 0 0 0     0 if ($self->cursor() and $self->prompt()) {
2279 0         0 print $self->cursor_char();
2280 0         0 $self->left(1);
2281 0         0 $self->cursor_on();
2282             }
2283             else {
2284 0         0 $self->cursor_off();
2285             }
2286             }
2287              
2288             #Turn off the cursor
2289             sub cursor_off {
2290 0     0 0 0 my $self = shift;
2291              
2292 0         0 print CURSOR_OFF;
2293             }
2294              
2295             #Turn on the cursor
2296             sub cursor_on {
2297 0     0 0 0 my $self = shift;
2298              
2299 0         0 print CURSOR_ON;
2300             }
2301              
2302             #Clear the screen
2303             sub clearscreen {
2304 0     0 0 0 my $self = shift;
2305              
2306 0         0 $self->pos(1,1);
2307 0         0 print CLS;
2308             }
2309              
2310             #Is argument a UP key?
2311             sub is_up_key {
2312 1     1 0 3 my ($self, $key) = @_;
2313 1         3 foreach my $up_key ($self->up_keys()) {
2314 1 50       8 return 1 if $key eq $up_key;
2315             }
2316 0         0 return 0;
2317             }
2318              
2319             #Is argument a DOWN key?
2320             sub is_down_key {
2321 1     1 0 2 my ($self, $key) = @_;
2322 1         4 foreach my $down_key ($self->down_keys()) {
2323 1 50       7 return 1 if $key eq $down_key;
2324             }
2325 0         0 return 0;
2326             }
2327              
2328             #Is argument a HELP key?
2329             sub is_help_key {
2330 1     1 0 3 my ($self, $key) = @_;
2331 1         3 foreach my $help_key ($self->help_keys()) {
2332 1 50       8 return 1 if $key eq $help_key;
2333             }
2334 0         0 return 0;
2335             }
2336              
2337             #Is argument a DOWN key?
2338             sub is_exit_key {
2339 1     1 0 3 my ($self, $key) = @_;
2340 1         4 foreach my $exit_key ($self->exit_keys()) {
2341 3 100       12 return 1 if $key eq $exit_key;
2342             }
2343 0         0 return 0;
2344             }
2345              
2346             #Is argument a SELECTION key?
2347             sub is_selection_key {
2348 1     1 0 3 my ($self, $key) = @_;
2349 1         4 foreach my $selection_key ($self->selection_keys()) {
2350 2 100       11 return 1 if $key eq $selection_key;
2351             }
2352 0         0 return 0;
2353             }
2354              
2355             #Is argument a shortcut key and if so which item does it refer to?
2356             sub is_shortcut {
2357 1     1 0 4 my ($self, $key) = @_;
2358              
2359 1         2 my @items = @{$self->items()};
  1         2  
2360 1         6 for (my $i = 1; $i <= $self->item_count(); $i++) {
2361 3 100       17 return $i if $key eq $items[$i - 1]->[0];
2362             }
2363 0         0 return 0;
2364             }
2365              
2366             #Get a list of all shortcuts that directly select an item
2367             sub shortcuts {
2368 1     1 0 3 my $self = shift;
2369              
2370 1         3 my @shortcuts = ();
2371 1         2 foreach my $item (@{$self->items()}) {
  1         3  
2372 3         84 push @shortcuts, $item->[0];
2373             }
2374 1 50       10 return wantarray ? @shortcuts : \@shortcuts;
2375             }
2376              
2377             #Get the number of items
2378             sub item_count {
2379 4     4 0 7 my $self = shift;
2380              
2381 4         4 return scalar(@{$self->items()});
  4         9  
2382             }
2383              
2384             #Move selection
2385             sub move_selection {
2386 0     0 0   my ($self, $offset) = @_;
2387              
2388 0           my $new_sel = 0;
2389 0 0 0       if (defined $offset and $offset =~ /^[+-]?\d+/) {
2390 0 0         if (abs($offset) > $self->item_count()) {
2391 0           $offset = $offset % $self->item_count();
2392             }
2393 0           $new_sel = $self->selection() + $offset;
2394 0 0         if ($new_sel > $self->item_count()) {
    0          
    0          
2395 0 0         $new_sel = $self->selection_wrap() ? $new_sel - $self->item_count() : $self->item_count();
2396             }
2397             elsif ($new_sel == 0) {
2398 0 0         $new_sel = $self->selection_wrap() ? $self->item_count() : 1;
2399             }
2400             elsif ($new_sel < 0) {
2401 0 0         $new_sel = $self->selection_wrap() ? $self->item_count() + $new_sel + 1 : 1;
2402             }
2403             }
2404 0           $self->selection($new_sel);
2405 0           $self->print_items();
2406 0           my @help = $self->help();
2407 0 0 0       if (defined $help[$new_sel]->[0] and length($help[$new_sel]->[0]) > 0) {
2408 0           $self->_update_hint($help[$new_sel]->[0]);
2409             }
2410             else {
2411 0           $self->update_status();
2412             }
2413 0           $self->print_cursor();
2414             }
2415              
2416             #Return code reference associated with a shortcut
2417             sub get_code_ref {
2418 0     0 0   my ($self, $shortcut) = @_;
2419              
2420 0           foreach my $item (@{$self->items()}) {
  0            
2421 0 0         return $item->[2] if $item->[0] eq $shortcut;
2422             }
2423 0           return undef;
2424             }
2425              
2426             #Perform action associated with a key
2427             #Return 0 for noop, 1 for success and undef for exit
2428             sub do_key {
2429 0     0 0   my ($self, $key, @args) = @_;
2430              
2431 0 0         return 0 unless defined $key;
2432 0 0         if ($self->is_exit_key($key)) {
2433 0           return undef;
2434             }
2435 0 0         if ($self->is_up_key($key)) {
2436 0           $self->move_selection(-1);
2437 0           return 1;
2438             }
2439 0 0         if ($self->is_down_key($key)) {
2440 0           $self->move_selection(1);
2441 0           return 1;
2442             }
2443 0 0 0       if ($self->is_selection_key($key) and $self->selection() > 0) {
2444 0 0         if (defined $self->items()->[$self->selection() - 1]->[2]) {
2445 0           $self->do_item($self->items()->[$self->selection() - 1]->[2], $self->selection());
2446             }
2447 0           return 1;
2448             }
2449 0 0         if ($self->is_help_key($key)) {
2450 0           my @help = $self->help();
2451 0 0         if ($self->help()->[$self->selection()]) {
    0          
2452 0           $self->do_help($self->help()->[$self->selection()], $self->selection());
2453             }
2454             elsif ($help[0]) {
2455 0           $self->do_help($self->help()->[0], $self->selection());
2456             }
2457 0           return 1;
2458             }
2459 0 0         if (my $sel = $self->is_shortcut($key)) {
2460 0           $self->print_items($self->selection($sel));
2461 0 0         if (defined $self->items()->[$sel - 1]->[2]) {
2462 0           $self->do_item($self->items()->[$sel - 1]->[2], $sel);
2463             }
2464 0 0 0       if (defined $self->help()->[$sel]->[0] and length($self->help()->[$sel]->[0]) > 0) {
2465 0           $self->_update_hint($self->help()->[$sel]->[0]);
2466             }
2467             else {
2468 0           $self->update_status();
2469 0           $self->print_cursor();
2470             }
2471 0           return 1;
2472             }
2473 0           return 0;
2474             }
2475              
2476             sub do_item {
2477 0     0 0   my ($self, $code_ref, @args) = @_;
2478              
2479 0           $self->clearscreen();
2480 0           $code_ref->(@args);
2481 0           $self->print_menu();
2482             }
2483              
2484             sub do_help {
2485 0     0 0   my ($self, $ref, @args) = @_;
2486              
2487 0 0 0       if (defined $ref->[1]) {
    0          
2488 0           $self->clearscreen();
2489 0           $ref->[1]->(@args);
2490 0           $self->print_menu();
2491             }
2492             elsif (defined $ref->[0] and length($ref->[0]) > 0) {
2493 0           $self->_update_hint($ref->[0]);
2494             }
2495             }
2496              
2497             sub print_menu {
2498 0     0 0   my $self = shift;
2499              
2500 0           $self->clearscreen();
2501 0 0         if (length $self->title() > 0) {
2502 0           $self->print_title();
2503 0 0         print "\n" if $self->space_after_title();
2504             }
2505 0 0         if ($self->item_count() > 0) {
2506 0           $self->print_items();
2507 0 0         print "\n" if $self->space_after_items();
2508             }
2509 0 0         if (length $self->status() > 0) {
2510 0           $self->print_status();
2511 0 0         print "\n" if $self->space_after_status();
2512             }
2513 0 0         if (length $self->prompt() > 0) {
2514 0           $self->print_prompt();
2515             }
2516             else {
2517 0           $self->print_cursor()
2518             }
2519             }
2520              
2521             sub update_status {
2522 0     0 0   my ($self, $status) = @_;
2523              
2524 0 0 0       if (defined $status and $self->_check_value('_status', $status)) {
2525 0           $self->status($status);
2526             }
2527 0           $self->_clear_after_items();
2528 0 0         $self->print_status() if $self->status();
2529 0 0         $self->print_prompt() if $self->prompt();
2530 0           $self->print_cursor();
2531             }
2532              
2533             sub update_prompt {
2534 0     0 0   my ($self, $prompt) = @_;
2535              
2536 0 0 0       if (defined $prompt and $self->_check_value('_prompt', $prompt)) {
2537 0           $self->prompt($prompt);
2538             }
2539 0           $self->_clear_after_items();
2540 0 0         $self->print_status() if $self->status();
2541 0 0         $self->print_prompt() if $self->prompt();
2542 0           $self->print_cursor();
2543             }
2544              
2545             sub line_after_menu {
2546 0     0 0   my $self = shift;
2547              
2548 0           my $line = $self->_status_start();
2549 0 0         if (length($self->status()) > 0) {
2550 0           $line++;
2551 0 0         $line += 2 if $self->status_frame();
2552 0 0         $line++ if $self->space_after_status();
2553             }
2554 0 0         if (length($self->prompt()) > 0) {
2555 0           $line++;
2556 0 0         $line += 2 if $self->prompt_frame();
2557             }
2558 0           return $line;
2559             }
2560              
2561             #===============================================================================
2562             #Make sure this modules ends with a true value
2563             #===============================================================================
2564              
2565             "That's all folks!";
2566              
2567             __END__