File Coverage

blib/lib/UI/Dialog/Util/MenuControl.pm
Criterion Covered Total %
statement 6 102 5.8
branch 0 44 0.0
condition 0 28 0.0
subroutine 2 7 28.5
pod 4 4 100.0
total 12 185 6.4


line stmt bran cond sub pod time code
1             package UI::Dialog::Util::MenuControl; ## A menu maker for dialog
2              
3              
4 1     1   561 use strict;
  1         1  
  1         26  
5 1     1   2 use vars qw($VERSION);
  1         2  
  1         819  
6              
7             our $VERSION='0.10';
8              
9              
10              
11             # It is an OO class to render a Dialog menu by a tree of array and hashes
12             # with specific form.
13             # a shell. It does not use curses and has no large dependencies.
14             #
15             #
16             # SYNOPSIS
17             # ========
18             #
19             #
20             # use UI::Dialog::Util::MenuControl;
21             #
22             # my $tree = {
23             # title => 'Conditinal behaviour',
24             # entries => [
25             # {
26             # title => 'entry A (prework for B)',
27             # function => \&doA,
28             # condition => undef,
29             # },
30             # {
31             # title => 'entry B',
32             # function => \&doB,
33             # condition => \&aWasCalled,
34             # },
35             # {
36             # title => 'reset A (undo prework)',
37             # function => \&resetA,
38             # condition => \&aWasCalled,
39             # },
40             # {
41             # title => 'has also submenus',
42             # entries => [
43             # {
44             # title => 'sub b 1',
45             # },
46             # {
47             # title => 'sub b 2',
48             # },
49             # ]
50             # },
51             #
52             # ],
53             # };
54             #
55             #
56             #
57             # my $menu_control = UI::Dialog::Util::MenuControl->new( menu => $tree );
58             #
59             # $menu_control->run();
60             #
61             # To build a menu, you can nest nodes with the attributes
62             #
63             # title
64             # function a reference to a function.
65             # condition a reference to a function given a boolean result whether to display the item or not
66             # entries array ref to further nodes
67             # context a 'self" for the called function
68             #
69             # Context
70             # =======
71             #
72             # The context you can use globaly (via constructor) or in a node, can be used in different ways.
73             # It is an important feature to keep object oriented features, because the function call from a menu
74             # normaly does not know which object you want to use and usually you want to separate the menu from the
75             # working object.
76             #
77             # ...
78             #
79             # our $objA = Local::UsecaseA->new();
80             #
81             #
82             # my $tree = {
83             # title => 'Conditinal behaviour',
84             # entries => [
85             # {
86             # title => 'entry B',
87             # function => \&doB,
88             # condition => \&Local::UsecaseA::check,
89             # context => $objA,
90             # },
91             #
92             # ],
93             # };
94             #
95             # In this example an object objA has been loaded before and provides a check() method.
96             # To run this check method in $objA context, you can tell a context to the node.
97             #
98             # What does the absolute same:
99             #
100             # my $tree = {
101             # title => 'Conditinal behaviour',
102             # entries => [
103             # {
104             # title => 'entry B',
105             # function => \&doB,
106             # condition => sub{ $objA->check() },
107             # },
108             #
109             # ],
110             # };
111             #
112             #
113             # But here a more elegant way:
114             #
115             # ...
116             #
117             # our $objA = Local::UsecaseA->new();
118             #
119             #
120             # my $tree = {
121             # title => 'Conditinal behaviour',
122             # entries => [
123             # {
124             # title => 'entry B',
125             # function => 'doB( "hello" )', # it is a simple string. Also parameters possible.
126             # condition => 'check', # called as method on $objA
127             # },
128             #
129             # ],
130             # };
131             #
132             #
133             # my $menu_control = UI::Dialog::Util::MenuControl->new(
134             # menu => $tree,
135             # context => $objA, # Set the context for methods
136             # );
137             #
138             # $menu_control->run();
139             #
140             #
141             # Try a function
142             # ==============
143             # Normaly the application dies if inside a function call a die() will happen. But you can try a function
144             # if it dies, it wont leave the menu.
145             # Therefore you have to add the magic work "try " before the function. As with dialogs the user may hit "cancel",
146             # I recomment to throw an exception (die) if that happens to make a difference to just "not entering a value".
147             # But if this menu call that function directly, the menu might also die then.
148             #
149             # ...
150             # function => 'try askForValue',
151             # ...
152             #
153             # As a try will eat all errors, you can handle them; Use 'catch' as parameter to point to an error handler function.
154             # This function will get the thrown error as first parameter.
155             #
156             #
157             # ...
158             # function => 'try askForValue',
159             # catch => 'showErrorWithDialog',
160             # ...
161             #
162             # The catch can also be globally set via constructor. So far catch can only take scalars describing a function in the same context
163             # as the rest. A coderef won't work. Errors in the catcher can't be handled and the menu will realy die.
164             #
165             #
166             #
167             # Negative conditions
168             # ===================
169             # It is quite simple. Just add the magic word "not " or "!" in front of a condition.
170             #
171             # ...
172             # function => 'prepareFolder',
173             # condition => 'not isFolderPrepared',
174             # ...
175             #
176             #
177             #
178             # LICENSE
179             # =======
180             # You can redistribute it and/or modify it under the conditions of LGPL.
181             #
182             # AUTHOR
183             # ======
184             # Andreas Hernitscheck ahernit(AT)cpan.org
185              
186              
187              
188              
189              
190             # parameters
191             #
192             # context context object wich can be used for all called procedures (self)
193             # backend UI::Dialog Backend engine. E.g. CDialog (default), GDialog, KDialog, ...
194             # backend_settings Values as hash transfered to backend constructor
195             # menu Tree structure (see example above)
196             # catch An error catching function, which retrieves the error if first param (only if 'try' used)
197             #
198             sub new {
199 0     0 1   my $pkg = shift;
200 0           my $self = bless {}, $pkg;
201 0           my $param = { @_ };
202              
203 0 0         if ( not $param->{'menu'} ){ die "needs menu structure as key \'menu\'" };
  0            
204 0           my $menu = $param->{'menu'};
205              
206 0           %{ $self } = %{ $param };
  0            
  0            
207            
208 0   0       my $bset = $param->{'backend_settings'} || {};
209              
210 0   0       $bset->{'listheight'} ||= 10;
211 0   0       $bset->{'height'} ||= 20;
212              
213             # if no dialog is given assume console and init now
214 0   0       my $use_backend = $param->{'backend'} || 'CDialog';
215 0           my $backend_module = "UI::Dialog::Backend::$use_backend";
216              
217             #require $backend_module;
218 0           eval("require $backend_module"); ## no critic
219 0 0         if ( $@ ){ die $@ };
  0            
220              
221 0           my $backend = $backend_module->new( %{ $bset } );
  0            
222 0           $self->dialog( $backend );
223              
224              
225             # set first node as default
226 0           $self->_currentNode( $menu );
227              
228 0           return $self;
229             }
230              
231              
232             # Main loop method. Will return when the user selected the last exit field.
233             sub run{
234 0     0 1   my $self = shift;
235              
236 0           while (1){
237 0 0         last if not $self->showMenu();
238             }
239              
240 0           return;
241             }
242              
243              
244             # Main control unit, but usually called by run().
245             # If you call it by yourself, you have to build your own loop around.
246             sub showMenu {
247 0     0 1   my $self = shift;
248 0           my $dialog = $self->dialog();
249 0           my $pos = $self->_currentNode();
250              
251 0           my $title = $pos->{'title'};
252            
253              
254 0           my $retval = 1;
255              
256              
257             # node context or global or undef
258 0   0       my $context = $pos->{'context'} || $self->{'context'} || undef;
259 0   0       my $catch = $pos->{'catch'} || $self->{'catch'} || undef;
260              
261             # prepare entries and remember further refs by
262             # the selected number
263 0           my @list;
264 0           my $c = 0;
265 0           my $entries = {};
266 0           menubuild: foreach my $e ( @{ $pos->{'entries'} } ) {
  0            
267            
268             # context per element entry?
269 0           my $context_elem = $e->{'context'};
270              
271 0           my $condition = $e->{'condition'};
272              
273             # magic prefix "not" or "!" to negate condition?
274 0           my $negative = 0;
275 0 0         if ( $condition =~ s/^(not |\!)//i ){
276 0           $negative = 1;
277             }
278              
279              
280             # you can skip menu entries if a condition is false.
281             # it is a boolean return of a function. So you can
282             # use moose's attributes.
283 0 0         if ( defined($condition) ){
284              
285 0           my $cond_result;
286 0   0       my $used_context = $context_elem || $context;
287              
288 0 0         if ( ref($condition) eq 'CODE' ){ # use a code ref like \& or sub{}
    0          
289 0           $cond_result = &{ $condition }( $used_context );
  0            
290             }elsif( not ref($condition) ){ # assume a name of a function in context
291 0           eval( "\$cond_result = \$used_context->$condition"); ## no critic
292 0 0         if ( $@ ){
293 0           die $@;
294             }
295             }
296            
297             # show menu entry or skip to next
298             # negative negates the condition
299 0 0 0       if ( $cond_result xor $negative ) {
300             # positive means to render menu point
301             }else{
302             # that is negative and means skip
303 0           next menubuild;
304             }
305             }
306            
307 0           $c++; # is the entry number
308 0           push @list, $c, $e->{'title'}; # title shown in the menu
309            
310 0           $entries->{ $c } = $e;
311             }
312            
313            
314 0           my $sel = $dialog->menu(
315             text => $title,
316             list => \@list,
317             );
318            
319             # selection in the menu?
320 0 0         if ( $sel ) {
321            
322              
323 0           my $function = $entries->{ $sel }->{'function'};
324 0   0       my $catchn = $catch || $entries->{ $sel }->{'catch'};
325 0           my $context_elem = $entries->{ $sel }->{'context'};
326 0   0       my $used_context = $context_elem || $context;
327              
328             # does the selected item has a submenu?
329 0 0         if ( $entries->{ $sel }->{'entries'} ){
    0          
330            
331 0           $self->_currentNode( $entries->{ $sel } );
332 0           $self->_currentNode()->{'parent'} = $pos;
333 0           $self->showMenu();
334            
335             }elsif( $function ){ # or is it a function call?
336              
337             # avoid to die if the function fails
338 0           my $dontdie;
339 0 0         if ( $function =~ s/^try //i ){
340 0           $dontdie = 1;
341             }
342              
343 0 0         if ( ref($function) eq 'CODE' ){ # use a code ref like \& or sub{}
    0          
344 0           &{ $entries->{ $sel }->{'function'} }( $used_context );
  0            
345             }elsif( not ref($function) ){ # assume a name of a function in context
346 0           eval( "\$used_context->$function" ); ## no critic
347 0 0         if ( $@ ){
348 0 0         die $@ if not $dontdie;
349              
350             # if a catch function is given (in context), forward the error
351 0 0 0       if ( $dontdie && $catchn ){
352 0           my $err = $@;
353 0 0         if (not ref($catchn) ){
354 0           eval( "\$used_context->$catchn( \$err )" ); ## no critic
355             }
356             }
357             }
358             }
359             }
360            
361             }else{
362             # selected 'cancel' means go to partent if exists or exit app
363 0 0         if ( $pos->{ 'parent' } ) {
364 0           $self->_currentNode( $pos->{ 'parent' } );
365 0           $self->showMenu();
366             }else{
367 0           $retval = 0;
368 0           exit; ## top menu cancel, does an exit
369             }
370            
371              
372             }
373            
374 0           return $retval;
375             }
376              
377              
378              
379             # Points to the current displayed node in the menu tree.
380             sub _currentNode{
381 0     0     my $self = shift;
382 0           my $node = shift;
383              
384 0 0         if ( $node ){
385 0           $self->{'current_node'} = $node;
386             }
387              
388 0           return $self->{'current_node'};
389             }
390              
391              
392             # Holds the backend dialog system.
393             sub dialog{
394 0     0 1   my $self = shift;
395 0           my $backend = shift;
396              
397 0 0         if ( $backend ){
398 0           $self->{'backend'} = $backend;
399             }
400              
401 0           return $self->{'backend'};
402             }
403              
404              
405              
406             1;
407              
408              
409              
410             #################### pod generated by Pod::Autopod - keep this line to make pod updates possible ####################
411              
412             =head1 NAME
413              
414             UI::Dialog::Util::MenuControl - A menu maker for dialog
415              
416              
417             =head1 SYNOPSIS
418              
419              
420              
421             use UI::Dialog::Util::MenuControl;
422            
423             my $tree = {
424             title => 'Conditinal behaviour',
425             entries => [
426             {
427             title => 'entry A (prework for B)',
428             function => \&doA,
429             condition => undef,
430             },
431             {
432             title => 'entry B',
433             function => \&doB,
434             condition => \&aWasCalled,
435             },
436             {
437             title => 'reset A (undo prework)',
438             function => \&resetA,
439             condition => \&aWasCalled,
440             },
441             {
442             title => 'has also submenus',
443             entries => [
444             {
445             title => 'sub b 1',
446             },
447             {
448             title => 'sub b 2',
449             },
450             ]
451             },
452            
453             ],
454             };
455            
456            
457            
458             my $menu_control = UI::Dialog::Util::MenuControl->new( menu => $tree );
459            
460             $menu_control->run();
461              
462             To build a menu, you can nest nodes with the attributes
463            
464             title
465             function a reference to a function.
466             condition a reference to a function given a boolean result whether to display the item or not
467             entries array ref to further nodes
468             context a 'self" for the called function
469              
470              
471              
472             =head1 DESCRIPTION
473              
474             It is an OO class to render a Dialog menu by a tree of array and hashes
475             with specific form.
476             a shell. It does not use curses and has no large dependencies.
477              
478              
479              
480              
481             =head1 REQUIRES
482              
483              
484             =head1 METHODS
485              
486             =head2 new
487              
488             $self->new();
489              
490             parameters
491              
492             context context object wich can be used for all called procedures (self)
493             backend UI::Dialog Backend engine. E.g. CDialog (default), GDialog, KDialog, ...
494             backend_settings Values as hash transfered to backend constructor
495             menu Tree structure (see example above)
496             catch An error catching function, which retrieves the error if first param (only if 'try' used)
497              
498              
499              
500             =head2 dialog
501              
502             $self->dialog();
503              
504             Holds the backend dialog system.
505              
506              
507             =head2 run
508              
509             $self->run();
510              
511             Main loop method. Will return when the user selected the last exit field.
512              
513              
514             =head2 showMenu
515              
516             $self->showMenu();
517              
518             Main control unit, but usually called by run().
519             If you call it by yourself, you have to build your own loop around.
520              
521              
522              
523             =head1 Try a function
524              
525             Normaly the application dies if inside a function call a die() will happen. But you can try a function
526             if it dies, it wont leave the menu.
527             Therefore you have to add the magic work "try " before the function. As with dialogs the user may hit "cancel",
528             I recomment to throw an exception (die) if that happens to make a difference to just "not entering a value".
529             But if this menu call that function directly, the menu might also die then.
530              
531             ...
532             function => 'try askForValue',
533             ...
534              
535             As a try will eat all errors, you can handle them; Use 'catch' as parameter to point to an error handler function.
536             This function will get the thrown error as first parameter.
537              
538              
539             ...
540             function => 'try askForValue',
541             catch => 'showErrorWithDialog',
542             ...
543              
544             The catch can also be globally set via constructor. So far catch can only take scalars describing a function in the same context
545             as the rest. A coderef won't work. Errors in the catcher can't be handled and the menu will realy die.
546              
547              
548              
549              
550              
551             =head1 Context
552              
553              
554             The context you can use globaly (via constructor) or in a node, can be used in different ways.
555             It is an important feature to keep object oriented features, because the function call from a menu
556             normaly does not know which object you want to use and usually you want to separate the menu from the
557             working object.
558            
559             ...
560            
561             our $objA = Local::UsecaseA->new();
562            
563            
564             my $tree = {
565             title => 'Conditinal behaviour',
566             entries => [
567             {
568             title => 'entry B',
569             function => \&doB,
570             condition => \&Local::UsecaseA::check,
571             context => $objA,
572             },
573            
574             ],
575             };
576              
577             In this example an object objA has been loaded before and provides a check() method.
578             To run this check method in $objA context, you can tell a context to the node.
579              
580             What does the absolute same:
581              
582             my $tree = {
583             title => 'Conditinal behaviour',
584             entries => [
585             {
586             title => 'entry B',
587             function => \&doB,
588             condition => sub{ $objA->check() },
589             },
590            
591             ],
592             };
593              
594              
595             But here a more elegant way:
596              
597             ...
598            
599             our $objA = Local::UsecaseA->new();
600            
601            
602             my $tree = {
603             title => 'Conditinal behaviour',
604             entries => [
605             {
606             title => 'entry B',
607             function => 'doB( "hello" )', # it is a simple string. Also parameters possible.
608             condition => 'check', # called as method on $objA
609             },
610            
611             ],
612             };
613              
614              
615             my $menu_control = UI::Dialog::Util::MenuControl->new(
616             menu => $tree,
617             context => $objA, # Set the context for methods
618             );
619            
620             $menu_control->run();
621              
622              
623              
624              
625             =head1 Negative conditions
626              
627             It is quite simple. Just add the magic word "not " or "!" in front of a condition.
628              
629             ...
630             function => 'prepareFolder',
631             condition => 'not isFolderPrepared',
632             ...
633              
634              
635              
636              
637              
638             =head1 AUTHOR
639              
640             Andreas Hernitscheck ahernit(AT)cpan.org
641              
642              
643             =head1 LICENSE
644              
645             You can redistribute it and/or modify it under the conditions of LGPL.
646              
647              
648              
649             =cut