File Coverage

blib/lib/Term/Menu/Hierarchical.pm
Criterion Covered Total %
statement 15 93 16.1
branch 0 36 0.0
condition 0 6 0.0
subroutine 5 8 62.5
pod 0 1 0.0
total 20 144 13.8


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