File Coverage

blib/lib/Term/Menu/Hierarchical.pm
Criterion Covered Total %
statement 15 100 15.0
branch 0 46 0.0
condition 0 6 0.0
subroutine 5 9 55.5
pod 0 2 0.0
total 20 163 12.2


line stmt bran cond sub pod time code
1             package Term::Menu::Hierarchical;
2 1     1   89866 use strict;
  1         13  
  1         39  
3 1     1   9 use warnings;
  1         2  
  1         39  
4 1     1   627 use POSIX;
  1         8372  
  1         8  
5 1     1   4913 use Term::Cap;
  1         3841  
  1         45  
6 1     1   572 use Term::ReadKey;
  1         2489  
  1         1771  
7             require Exporter;
8             require 5.007_001;
9             binmode STDOUT, ":utf8";
10             $|++;
11              
12             our @ISA = qw(Exporter);
13             our @EXPORT = qw(menu);
14              
15             our $VERSION = '1.00';
16              
17             # Set up the terminal handling
18             my $plain_mode = 0;
19             $ENV{TERM} = 'linux' if !defined $ENV{TERM} or $ENV{TERM} eq '';
20             my $ti = POSIX::Termios->new();
21             $ti->getattr;
22             my $t = eval { Term::Cap->Tgetent({ TERM => undef, OSPEED => $ti->getospeed||38400 }); };
23             $plain_mode = 1 if $@;
24             $t->Trequire(qw/cl md me so se/) unless $plain_mode;
25              
26             sub tput {
27 0     0 0   my $arg = shift;
28 0 0         if ($arg eq 'cl'){
29 0 0         if ($plain_mode){
30 0 0         my $clear = $^O =~ /darwin|linux|bsd/ ? qx/clear/ :
    0          
31             $^O =~ /MSWin32/ ? qx/cls/ :
32             "\033[2J"; # That's a Hail Mary pass if there ever was one...
33 0           print $clear;
34             }
35             else {
36 0           $t->Tputs("cl", 1, *STDOUT);
37             }
38             }
39             else {
40 0 0         $t->Tputs($arg, 1, *STDOUT) unless $plain_mode;
41             }
42             }
43              
44             my($max_width, $max_height);
45              
46             ########################################################################################
47              
48             sub menu {
49              
50 0     0 0   my ($all, $data) = shift;
51 0 0         die "The argument must be a hashref (arbitrary depth); exiting.\n"
52             unless ref($all) eq 'HASH';
53              
54             {
55             # Refresh size info to catch term resize events
56 0           ($max_width, $max_height) = GetTerminalSize \*STDOUT;
  0            
57             # $t->Tputs("cl", 1, *STDOUT);
58 0           tput("cl");
59 0 0         if (ref($data->{content}) eq 'HASH'){
60 0           $data = _display($data);
61             }
62             else {
63 0 0         if (defined $data->{content}){
64 0           _more("$data->{label}\n\n$data->{content}\n");
65             }
66 0           $data->{content} = $all;
67 0           $data->{label} = 'Top';
68             }
69 0           redo;
70             }
71             }
72              
73             sub _more {
74 0 0   0     return unless my @txt = split /\n/, shift;
75             # Fill @txt so we have full 'pages'
76 0 0         if (@txt % ($max_height - 2)){
77 0           push @txt, '~' for 3 .. ($max_height - @txt % ($max_height - 2));
78             }
79 0           my ($pos, @pages) = 0;
80 0           push @pages, [ splice @txt, 0, ($max_height - 2) ] while @txt;
81              
82 0           my $prompt = ' [ =page down =back =quit ] ';
83             {
84             # $t->Tputs("cl", 1, *STDOUT);
85 0           tput("cl");
  0            
86 0           for (@{$pages[$pos]}){
  0            
87             # (Crude) long line handling. You should format your data...
88 0 0         if (length($_) > $max_width){
89 0           print substr($_, 0, $max_width - 1);
90             # $t->Tputs("so", 1, *STDOUT);
91 0           tput("so");
92 0           print ">\n";
93             # $t->Tputs("se", 1, *STDOUT);
94 0           tput("se");
95             }
96             else {
97 0           print "$_\n";
98             }
99             }
100              
101             # $t->Tputs("so", 1, *STDOUT);
102             # $t->Tputs("md", 1, *STDOUT);
103 0           tput("so");
104 0           tput("md");
105 0           print "\n", $prompt, ' ' x ($max_width - length($prompt));
106             # $t->Tputs("me", 1, *STDOUT);
107             # $t->Tputs("se", 1, *STDOUT);
108 0           tput("me");
109 0           tput("se");
110              
111 0           ReadMode 4;
112 0           my $key;
113 0           1 while not defined ($key = ReadKey(-1));
114 0           ReadMode 0;
115 0 0         if ($key =~ /q/i){
    0          
    0          
116 0           return;
117             }
118             elsif ($key =~ /b/i){
119 0 0         $pos-- if $pos > 0;
120             }
121             elsif ($key =~ /\s/){
122 0 0         $pos++ if $pos < $#pages;
123             }
124 0           redo;
125             }
126             }
127              
128             sub _display {
129 0     0     my $ref = shift;
130             # reverse-sort the lengths of all the item names, count them...
131 0           my $num_items = my @lengths = sort {$b<=>$a} map {length($_)} keys %{$ref->{content}};
  0            
  0            
  0            
132             # ...and grab the first number in the list to get the display width.
133 0           my $max_len = $lengths[0];
134 0 0         die "Your display is too narrow for these items.\n"
135             if $max_len + 7 > $max_width;
136            
137             # How many digits will we need for the index?
138 0           my $count_width = $num_items =~ tr/0-9//;
139             # '5' covers the formatting bits (separator, parens, three spaces)
140 0           my $span_width = $max_len + $count_width + 5;
141             # Max number of items that will fit in the display width *or*
142             # the total number of items if it's less than that.
143 0 0         my $items_per_line = int($max_width/$span_width) < $num_items ?
144             int($max_width/$span_width) : $num_items;
145             # Figure out total width for printing; '-1' adjusts for box corners
146 0           my $width = $items_per_line * $span_width - 1;
147              
148             # Display the menu, get the answer, and validate it
149 0           my($answer, %list);
150             {
151 0           my $count;
  0            
152             # $t->Tputs("cl", 1, *STDOUT);
153 0           tput("cl");
154 0           print "." . "-" x $width . ".\n";
155 0           for my $item (keys %{$ref->{content}}){
  0            
156             # Create a number-to-entry lookup table
157 0           $list{++$count} = $item;
158             # Print formatted box body
159 0           printf "| %${count_width}s) %-${max_len}s ", $count, $item;
160 0 0         print "|\n" unless $count % $items_per_line;
161             }
162             # If we don't have enough items to fill the last line, pad with empty cells
163 0 0         if ($count % $items_per_line){
164 0           my $pad = "|" . " " x ($span_width - 1);
165 0           print $pad x ($items_per_line - $count % $items_per_line);
166 0           print "|\n";
167             }
168 0           print "'" . "-" x $width . "'\n";
169              
170 0           print "Item number (1-$count, 0 to restart, 'q' to quit)? ";
171 0           chomp($answer = );
172 0 0         exit if $answer =~ /^q/i;
173 0 0 0       redo unless $answer =~ /^\d+$/ && $answer >= 0 && $answer <= $count;
      0        
174             }
175 0           my $retval;
176 0 0         if ($answer == 0){
177 0           $retval->{content} = undef;
178             }
179             else {
180 0           $retval->{label} = "$ref->{label} >> $list{$answer}";
181 0           $retval->{content} = $ref->{content}->{$list{$answer}};
182             }
183 0           return $retval;
184             }
185              
186             ########################################################################################
187              
188             1;
189              
190             __END__