File Coverage

blib/lib/Curses/UI/Container.pm
Criterion Covered Total %
statement 89 233 38.2
branch 26 96 27.0
condition 10 36 27.7
subroutine 14 27 51.8
pod 11 22 50.0
total 150 414 36.2


line stmt bran cond sub pod time code
1             # ----------------------------------------------------------------------
2             # Curses::UI::Container
3             #
4             # (c) 2001-2002 by Maurice Makaay. All rights reserved.
5             # This file is part of Curses::UI. Curses::UI is free software.
6             # You can redistribute it and/or modify it under the same terms
7             # as perl itself.
8             #
9             # Currently maintained by Marcus Thiesen
10             # e-mail: marcus@cpan.thiesenweb.de
11             # ----------------------------------------------------------------------
12              
13             # TODO: update dox
14              
15             package Curses::UI::Container;
16              
17 8     8   126 use Curses;
  8         15  
  8         26361  
18 8     8   7426 use Curses::UI::Widget;
  8         28  
  8         725  
19 8     8   47 use Curses::UI::Common;
  8         15  
  8         908  
20              
21 8         23246 use vars qw(
22             @ISA
23             $VERSION
24 8     8   39 );
  8         11  
25              
26             $VERSION = "1.11";
27              
28             @ISA = qw(
29             Curses::UI::Widget
30             Curses::UI::Common
31             );
32              
33             # ----------------------------------------------------------------------
34             # Public interface
35             # ----------------------------------------------------------------------
36              
37             # Create a new Container object.
38             sub new()
39             {
40 15     15 1 27 my $class = shift;
41              
42 15         100 my %userargs = @_;
43 15         47 keys_to_lowercase(\%userargs);
44              
45 15         170 my %args = (
46             -releasefocus => 0, # Allows the focus to be released to parent on end
47              
48             %userargs,
49              
50             -id2object => undef, # Id to object mapping
51             -object2id => undef, # Object to id mapping
52             -focusorder => [], # The order in which objects get focused
53             -draworder => [], # The order in which objects get drawn
54             -focus => 0, # Value init
55             );
56              
57 15         140 my $this = $class->SUPER::new(%args);
58             }
59              
60             DESTROY()
61             {
62 4     4   18 my $this = shift;
63 4         23 $this->SUPER::delete_subwindows();
64             }
65              
66             # Add an object to the container
67             sub add($@)
68             {
69 19     19 1 2502 my $this = shift;
70 19         31 my $id = shift;
71 19         27 my $class = shift;
72 19         71 my %args = @_;
73            
74 19 50 66     174 $this->root->fatalerror(
75             "The object id \"$id\" is already in use!"
76             ) if defined $id and
77             defined $this->{-id2object}->{$id};
78              
79             # If $id is not defined, create an auto-id.
80 19 100       49 if (not defined $id)
81             {
82 3         4 my $i = 0;
83 3         4 my $id_pre = "__container_auto_id_";
84 3         3 do { $id = $id_pre . $i++ }
  4         19  
85             until (not defined $this->{-id2object}->{$id});
86             }
87              
88             # Make it possible to specify WidgetType instead of
89             # Curses::UI::WidgetType.
90 19 100 66     110 $class = "Curses::UI::$class"
91             if $class !~ /\:\:/ or
92             $class =~ /^Dialog\:\:[^\:]+$/;
93              
94             # Create a new object of the wanted class.
95 19         129 $this->root->usemodule($class);
96 19         114 my $object = $class->new(
97             %args,
98             -parent => $this
99             );
100              
101             # Store the object.
102 19         68 $this->{-id2object}->{$id} = $object;
103 19         82 $this->{-object2id}->{$object} = $id;
104              
105             # begin by AGX: inherith parent background color!
106 19 100       57 if (defined( $object->{-bg} )) {
107 12 50       37 if ($object->{-bg} eq "-1" ) {
108 12 100       37 if (defined( $this->{-bg} )) {
109 7         16 $object->{-bg} = $this->{-bg};
110             }
111             }
112             }
113             # end by AGX
114             # begin by AGX: inherith parent foreground color!
115 19 100       50 if (defined( $object->{-fg} )) {
116 12 50       33 if ($object->{-fg} eq "-1" ) {
117 12 100       44 if (defined( $this->{-fg} )) {
118 7         14 $object->{-fg} = $this->{-fg};
119             }
120             }
121             }
122             # end by AGX
123              
124             # Automatically create a focus- and draworder (last added =
125             # last focus/draw). This can be overriden by the
126             # set_focusorder() and set_draworder() functions.
127 19         25 push @{$this->{-focusorder}}, $id;
  19         55  
128 19         28 unshift @{$this->{-draworder}}, $id;
  19         52  
129              
130             # Return the created object.
131 19         77 return $object;
132             }
133              
134             # Delete the contained object with id=$id from the Container.
135             sub delete(;$)
136             {
137 0     0 1 0 my $this = shift;
138 0         0 my $id = shift;
139              
140 0 0       0 return $this unless defined $this->{-id2object}->{$id};
141              
142             # Delete curses subwindows.
143 0         0 $this->{-id2object}->{$id}->delete_subwindows();
144            
145             # Destroy object.
146 0         0 undef $this->{-object2id}->{$this->{-id2object}->{$id}};
147 0         0 delete $this->{-object2id}->{$this->{-id2object}->{$id}};
148 0         0 undef $this->{-id2object}->{$id};
149 0         0 delete $this->{-id2object}->{$id};
150              
151 0         0 foreach my $param (qw(-focusorder -draworder))
152             {
153 0         0 my ($current_focused_id, $new_focused_id, $new_focused_obj);
154 0         0 $current_focused_id = $this->{-draworder}->[-1];
155 0         0 my $idx = $this->base_id2idx($param, $id);
156 0 0       0 splice(@{$this->{$param}}, $idx, 1)
  0         0  
157             if defined $idx;
158              
159             #did the deleted id had the focus?
160 0 0       0 if ($current_focused_id eq $id)
161             {
162 0         0 $new_focused_id = $this->{-draworder}->[-1];
163 0 0       0 $new_focused_obj = $this->{-id2object}->{$new_focused_id}
164             if $new_focused_id;
165 0 0       0 $new_focused_obj->event_onfocus
166             if $new_focused_obj;
167             }
168             }
169              
170 0         0 return $this;
171             }
172              
173             sub delete_subwindows()
174             {
175 0     0 0 0 my $this = shift;
176 0         0 while (my ($id, $object) = each %{$this->{-id2object}}) {
  0         0  
177 0         0 $object->delete_subwindows();
178             }
179 0         0 $this->SUPER::delete_subwindows();
180 0         0 return $this;
181             }
182              
183              
184             # Draw the container and it's contained objects.
185             sub draw(;$)
186             {
187 12     12 1 24 my $this = shift;
188 12   50     48 my $no_doupdate = shift || 0;
189            
190             # Draw the Widget.
191 12 50       107 $this->SUPER::draw(1) or return $this;
192            
193             # Draw all contained object.
194 12         28 foreach my $id (@{$this->{-draworder}}) {
  12         45  
195 3         24 $this->{-id2object}->{$id}->draw(1);
196             }
197              
198             # Update the screen unless suppressed.
199 12 50       46 doupdate() unless $no_doupdate;
200              
201 12         40 return $this;
202             }
203              
204             sub layout()
205             {
206 13     13 1 21 my $this = shift;
207 13 100       67 $this->SUPER::layout() or return;
208 9         59 $this->layout_contained_objects();
209 9         30 return $this;
210             }
211              
212             sub layout_contained_objects()
213             {
214 18     18 0 44 my $this = shift;
215              
216             # Layout all contained objects.
217 18         60 foreach my $id (@{$this->{-draworder}})
  18         70  
218             {
219 0         0 my $obj = $this->{-id2object}->{$id};
220 0         0 $obj->{-parent} = $this;
221 0         0 $obj->layout();
222 0         0 $obj->draw();
223             }
224              
225 18         34 return $this;
226             }
227              
228             # Look if there are objects of a certain kind in the container.
229             sub hasa($;)
230             {
231 0     0 1 0 my $this = shift;
232 0         0 my $class = shift;
233              
234 0         0 my $count = 0;
235 0         0 while (my ($id,$obj) = each %{$this->{-id2object}}) {
  0         0  
236 0 0       0 $count++ if ref $obj eq $class;
237             }
238 0         0 return $count;
239             }
240              
241             sub window_is_ontop($;)
242             {
243 0     0 0 0 my $this = shift;
244 0         0 my $win = shift;
245              
246             # If we have a stack of no windows, return immediately.
247 0 0       0 return undef if @{$this->{-draworder}} == 0;
  0         0  
248              
249 0         0 my $topwin = $this->{-draworder}->[-1];
250 0 0       0 if (ref $win) { $topwin = $this->getobj($topwin) }
  0         0  
251              
252 0         0 return $topwin eq $win;
253             }
254              
255             sub event_keypress($;)
256             {
257 0     0 0 0 my $this = shift;
258 0         0 my $key = shift;
259              
260             # Try to run the event on this widget. Return
261             # unless the binding returns 'DELEGATE' which
262             # means that this widget should try to delegate
263             # the event to its contained object which has
264             # the focus.
265             #
266 0         0 my $return = $this->process_bindings($key);
267 0 0 0     0 return $return
268             unless defined $return and
269             $return eq 'DELEGATE';
270              
271             # Get the current focused object and send the
272             # keypress to it.
273 0         0 $obj = $this->getfocusobj;
274 0 0       0 if (defined $obj) {
275 0         0 return $obj->event_keypress($key);
276             } else {
277 0         0 return 'DELEGATE';
278             }
279             }
280              
281             sub focus_prev()
282             {
283 0     0 0 0 my $this = shift;
284              
285             # Return without doing anything if we do not
286             # have a focuslist.
287 0 0       0 return $this unless @{$this->{-focusorder}};
  0         0  
288            
289             # Find the current focused object id.
290 0         0 my $id = $this->{-draworder}->[-1];
291              
292             # Find the current focusorder index.
293 0         0 my $idx = $this->focusorder_id2idx($id);
294              
295 0         0 my $circle_flag = 0;
296              
297             # Go to the previous object or wraparound.
298 0         0 until ($circle_flag) {
299 0         0 $idx--;
300 0 0       0 if ($idx < 0) {
301 0         0 $idx = @{$this->{-focusorder}} - 1;
  0         0  
302 0         0 $circle_flag = 1;
303             }
304 0         0 my $new_obj = $this->getobj($this->{-focusorder}[$idx]);
305 0 0 0     0 last if (defined $new_obj && $new_obj->focusable);
306             }
307              
308             # Focus the previous object.
309 0         0 $this->focus($this->{-focusorder}->[$idx], undef, -1);
310 0 0 0     0 if ( $circle_flag && $this->{-releasefocus} ) {
311 0         0 $this->{-parent}->focus_prev;
312             }
313             }
314              
315             sub focus_next()
316             {
317 0     0 0 0 my $this = shift;
318              
319             # Return without doing anything if we do not
320             # have a focuslist.
321 0 0       0 return $this unless @{$this->{-focusorder}};
  0         0  
322            
323             # Find the current focused object id.
324 0         0 my $id = $this->{-draworder}->[-1];
325              
326             # Find the current focusorder index.
327 0         0 my $idx = $this->focusorder_id2idx($id);
328              
329             # Go to the next object or wraparound.
330 0         0 my $circle_flag = 0;
331 0         0 until ($circle_flag) {
332 0         0 $idx++;
333 0 0       0 if ($idx >= scalar (@{$this->{-focusorder}}) ) {
  0         0  
334 0         0 $idx = 0;
335 0         0 $circle_flag = 1;
336             }
337 0         0 my $new_obj = $this->getobj($this->{-focusorder}[$idx]);
338 0 0 0     0 last if (defined $new_obj && $new_obj->focusable);
339             }
340            
341             # Focus the next object.
342 0         0 $this->focus($this->{-focusorder}->[$idx], undef, +1);
343             #check if we have to release the focus
344 0 0 0     0 if ( $circle_flag && $this->{-releasefocus} ) {
345 0         0 $this->{-parent}->focus_next;
346             }
347             }
348              
349             sub focus(;$$$)
350             {
351 2     2 1 3 my $this = shift;
352 2         3 my $focus_to = shift;
353 2   50     10 my $forced = shift || 0;
354 2   50     9 my $direction = shift || 1;
355              
356             # The direction in which to look for other objects
357             # if this object is not focusable.
358 2 50       6 $direction = ($direction < 0 ? -1 : 1);
359              
360             # Find the id for a object if the argument
361             # is an object.
362 2 50       20 my $new_id = ref $focus_to
363             ? $this->{-object2id}->{$focus_to}
364             : $focus_to;
365              
366 2 50 33     7 if ($forced and not defined $new_id) {
367 0         0 $new_id = $this->{-draworder}->[-1];
368             }
369              
370             # Do we need to change the focus inside the container?
371 2 50       7 if (defined $new_id)
372             {
373             # Find the currently focused object.
374 2         7 my $cur_id = $this->{-draworder}->[-1];
375 2         6 my $cur_obj = $this->{-id2object}->{$cur_id};
376            
377             # Find the new focused object.
378 2         7 my $new_obj = $this->{-id2object}->{$new_id};
379 2 50       5 $this->root->fatalerror(
380             "focus(): $this has no element with id='$new_id'"
381             ) unless defined $new_obj;
382              
383             # Can the new object be focused? If not, then
384             # try to find the first next object that can
385             # be focused.
386 2 50       22 unless ($new_obj->focusable)
387             {
388 0         0 my $idx = $start_idx = $this->focusorder_id2idx($cur_id);
389              
390 0         0 undef $new_obj;
391 0         0 undef $new_id;
392              
393 0         0 OBJECT: for(;;)
394             {
395 0         0 $idx += $direction;
396 0 0       0 $idx = 0 if $idx > @{$this->{-focusorder}}-1;
  0         0  
397 0 0       0 $idx = @{$this->{-focusorder}}-1 if $idx < 0;
  0         0  
398 0 0       0 last if $idx == $start_idx;
399              
400 0         0 my $test_id = $this->{-focusorder}->[$idx];
401 0         0 my $test_obj = $this->{-id2object}->{$test_id};
402            
403 0 0       0 if ($test_obj->focusable)
404             {
405 0         0 $new_id = $test_id;
406 0         0 $new_obj = $test_obj;
407             last OBJECT
408 0         0 }
409              
410             }
411             }
412              
413             # Change the draworder if a focusable objects was found.
414 2 50 33     20 if ($forced or defined $new_obj and $new_obj ne $cur_obj)
      33        
415             {
416 0         0 my $idx = $this->draworder_id2idx($new_id);
417 0         0 my $move = splice(@{$this->{-draworder}}, $idx, 1);
  0         0  
418 0         0 push @{$this->{-draworder}}, $move;
  0         0  
419              
420 0 0       0 unless ($new_obj->{-has_modal_focus}) {
421 0         0 $cur_obj->event_onblur;
422             }
423 0         0 $new_obj->event_onfocus;
424             }
425             }
426            
427 2         15 $this->SUPER::focus();
428             }
429              
430             sub event_onfocus()
431             {
432 0     0 0 0 my $this = shift;
433              
434             # Do an onfocus event for this object.
435 0         0 $this->SUPER::event_onfocus;
436              
437             # If there is a focused object within this
438             # container and this container is not a
439             # container widget, then send an onfocus event to it.
440 0 0       0 unless ($this->isa('Curses::UI::ContainerWidget')) {
441 0         0 my $focused_object = $this->getfocusobj;
442 0 0       0 if (defined $focused_object) {
443 0         0 $focused_object->event_onfocus;
444             }
445             }
446              
447 0         0 return $this;
448             }
449              
450             sub event_onblur()
451             {
452 0     0 0 0 my $this = shift;
453              
454             #If the Container loose it focus
455             #the current focused child must be unfocused
456              
457             #get the id
458 0         0 my $id = $this->{-draworder}->[-1];
459 0 0       0 return unless $id;
460              
461             #get the object
462 0         0 my $obj = $this->{-id2object}->{$id};
463 0 0       0 return unless $obj;
464              
465             #draw the widget without the focus
466 0         0 $obj->{-focus} = 0;
467 0         0 $obj->draw;
468              
469 0         0 $this->SUPER::event_onblur();
470              
471 0         0 return $this;
472             }
473              
474              
475             sub set_focusorder(@)
476             {
477 8     8 1 9 my $this = shift;
478 8         14 my @order = @_;
479 8         15 $this->{-focusorder} = \@order;
480 8         19 return $this;
481             }
482              
483             sub set_draworder(@)
484             {
485 8     8 1 9 my $this = shift;
486 8         16 my @order = @_;
487 8         14 $this->{-draworder} = \@order;
488 8         22 return $this;
489             }
490              
491             sub getobj($;)
492             {
493 7     7 1 9 my $this = shift;
494 7         8 my $id = shift;
495 7         26 return $this->{-id2object}->{$id};
496             }
497              
498             sub getfocusobj()
499             {
500 0     0 1   my $this = shift;
501 0           my $id = $this->{-draworder}->[-1];
502 0 0         return (defined $id ? $this->getobj($id) : undef);
503             }
504              
505             # ----------------------------------------------------------------------
506             # Private functions
507             # ----------------------------------------------------------------------
508              
509 0     0 0   sub draworder_id2idx($;) {shift()->base_id2idx('-draworder' , shift())}
510 0     0 0   sub focusorder_id2idx($;) {shift()->base_id2idx('-focusorder', shift())}
511              
512             sub base_id2idx($;)
513             {
514 0     0 0   my $this = shift;
515 0           my $param = shift;
516 0           my $id = shift;
517            
518 0           my $idx;
519 0           my $i = 0;
520 0           foreach my $win_id (@{$this->{$param}})
  0            
521             {
522 0 0         if ($win_id eq $id) {
523 0           $idx = $i;
524 0           last;
525             }
526 0           $i++;
527             }
528 0           return $idx;
529             }
530              
531             =pod
532              
533             =head1 NAME
534              
535             Curses::UI::Container - Create and manipulate container widgets
536              
537             =head1 CLASS HIERARCHY
538              
539             Curses::UI::Widget
540             |
541             +----Curses::UI::Container
542              
543              
544             =head1 SYNOPSIS
545              
546             use Curses::UI;
547             my $cui = new Curses::UI;
548             my $win = $cui->add('window_id', 'Window');
549              
550             my $container = $win->add(
551             'mycontainer', 'Container'
552             );
553              
554             $container->add(
555             'contained', 'SomeWidget',
556             .....
557             );
558              
559             $container->focus();
560              
561              
562             =head1 DESCRIPTION
563              
564             A container provides an easy way of managing multiple widgets
565             in a single "form". A lot of Curses::UI functionality is
566             built around containers. The main class L
567             itself is a container. A L
568             is a container. Some of the widgets are implemented as
569             containers.
570              
571              
572              
573             =head1 STANDARD OPTIONS
574              
575             B<-parent>, B<-x>, B<-y>, B<-width>, B<-height>,
576             B<-pad>, B<-padleft>, B<-padright>, B<-padtop>, B<-padbottom>,
577             B<-ipad>, B<-ipadleft>, B<-ipadright>, B<-ipadtop>, B<-ipadbottom>,
578             B<-title>, B<-titlefullwidth>, B<-titlereverse>, B<-onfocus>,
579             B<-onblur>
580              
581             For an explanation of these standard options, see
582             L.
583              
584              
585              
586              
587             =head1 WIDGET-SPECIFIC OPTIONS
588              
589             =over 4
590              
591             =item * B<-releasefocus>
592              
593             If this option is set, the widgets inside this Container will be
594             part of the focus ordering of the parent widget.
595             This means that when this Container gets the focus, its first widget
596             will be focused. When the focus leaves the last widget inside the
597             Container it will give the focus back to the parent instead
598             of cycling back to the first widget in this Container.
599             This option is useful to create a sub-class packed with common used
600             widgets, making the reuse easier.
601              
602             =back
603              
604              
605              
606             =head1 METHODS
607              
608             =over 4
609              
610             =item * B ( )
611              
612             Create a new instance of the Curses::UI::Container class.
613              
614             =item * B ( ID, CLASS, OPTIONS )
615              
616             This is the main method for this class. Using this method
617             it is easy to add widgets to the container.
618              
619             The ID is an identifier that you want to use for the
620             added widget. This may be any string you want. If you
621             do not need an ID, you may also us an undefined
622             value. The container will automatically create
623             an ID for you.
624              
625             The CLASS is the class which you want to add to the
626             container. If CLASS does not contain '::' or CLASS
627             matches 'Dialog::...' then 'Curses::UI' will be prepended
628             to it. This way you do not have to specifiy the full
629             class name for widgets that are in the Curses::UI
630             hierarchy. It is not necessary to call "use CLASS"
631             yourself. The B method will call the B
632             method from Curses::UI to automatically load the module.
633              
634             The hash OPTIONS contains the options that you want to pass
635             on to the new instance of CLASS.
636              
637             Example:
638              
639             $container->add(
640             'myid', # ID
641             'Label', # CLASS
642             -text => 'Hello, world!', # OPTIONS
643             -x => 10,
644             -y => 5,
645             );
646              
647             =item * B ( ID )
648              
649             This method deletes the contained widget with the given ID
650             from the container.
651              
652             =item * B ( CLASS )
653              
654             This method returns true if the container contains one or
655             more widgets of the class CLASS.
656              
657             =item * B ( )
658              
659             Layout the Container and all its contained widgets.
660              
661             =item * B ( BOOLEAN )
662              
663             Draw the Container and all its contained widgets.
664             If BOOLEAN is true, the screen will not update after
665             drawing. By default this argument is false, so the
666             screen will update after drawing the container.
667              
668             =item * B ( )
669              
670             See L for an
671             explanation of this method.
672              
673             =item * B ( )
674              
675             If the container contains no widgets, this routine will
676             return immediately. Else the container will get focus.
677              
678             If the container gets focus, one of the contained widgets
679             will get the focus. The returnvalue of this widget determines
680             what has to be done next. Here are the possible cases:
681              
682             * The returnvalue is B
683              
684             As soon as a widget returns this value, the container
685             will loose its focus and return the returnvalue and the
686             last pressed key to the caller.
687              
688             * The returnvalue is B
689              
690             The container will not loose focus and the focus will stay
691             at the same widget of the container.
692              
693             * Any other returnvalue
694              
695             The focus will go to the next widget in the container.
696              
697             =item * B ( ID )
698              
699             This method returns the object reference of the contained
700             widget with the given ID.
701              
702             =item * B ( )
703              
704             This method returns the object reference of the contained
705             widget which currently has the focus.
706              
707             =item * B ( IDLIST )
708              
709             Normally the order in which widgets get focused in a
710             container is determined by the order in which they
711             are added to the container. Use B if you
712             want a different focus order. IDLIST contains a list
713             of id's.
714              
715             =item * B ( IDLIST )
716              
717             Normally the order in which widgets are drawn in a
718             container is determined by the order in which they
719             are added to the container. Use B if you
720             want a different draw order. IDLIST contains a list
721             of id's.
722              
723             =item * B ( CLASS )
724              
725             This will load the module for the CLASS. If loading
726             fails, the program will die.
727              
728             =item * B ( CODEREF )
729              
730             This method can be used to set the B<-onfocus> event handler
731             (see above) after initialization of the widget.
732              
733             =item * B ( CODEREF )
734              
735             This method can be used to set the B<-onblur> event handler
736             (see above) after initialization of the widget.
737              
738              
739             =back
740              
741              
742              
743              
744             =head1 DEFAULT BINDINGS
745              
746             Since interacting is not handled by the container itself, but
747             by the contained widgets, this class does not have any key
748             bindings.
749              
750              
751              
752              
753             =head1 SEE ALSO
754              
755             L,
756              
757              
758              
759             =head1 AUTHOR
760              
761             Copyright (c) 2001-2002 Maurice Makaay. All rights reserved.
762              
763             Maintained by Marcus Thiesen (marcus@cpan.thiesenweb.de)
764              
765              
766             This package is free software and is provided "as is" without express
767             or implied warranty. It may be used, redistributed and/or modified
768             under the same terms as perl itself.
769