File Coverage

blib/lib/Curses/Application.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             # Curses::Application.pm -- Curses Application Framework
2             #
3             # (c) 2001, Arthur Corliss
4             #
5             # $Id: Application.pm,v 0.2 2002/11/14 19:40:42 corliss Exp corliss $
6             #
7             # This program is free software; you can redistribute it and/or modify
8             # it under the terms of the GNU General Public License as published by
9             # the Free Software Foundation; either version 2 of the License, or
10             # any later version.
11             #
12             # This program is distributed in the hope that it will be useful,
13             # but WITHOUT ANY WARRANTY; without even the implied warranty of
14             # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15             # GNU General Public License for more details.
16             #
17             # You should have received a copy of the GNU General Public License
18             # along with this program; if not, write to the Free Software
19             # Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20             #
21             #####################################################################
22              
23             =head1 NAME
24              
25             Curses::Application - Curses Application Framework
26              
27             =head1 MODULE VERSION
28              
29             $Id: Application.pm,v 0.2 2002/11/14 19:40:42 corliss Exp corliss $
30              
31             =head1 SYNOPSIS
32              
33             use Curses::Application;
34              
35             $app = Curses::Application->new({
36             FOREGROUND => 'white',
37             BACKGROUND => 'blue',
38             TITLEBAR => 1,
39             STATUSBAR => 1,
40             CAPTION => 'My Application',
41             MAINFORM => { name => defname },
42             MINY => 20,
43             MINX => 60,
44             ALTFBASE => 'MyCompany::Forms',
45             ALTBASE => 'MyCompany::Widgets',
46             });
47              
48             ($y, $x) = $app->maxyx;
49             $mwh = $app->mwh;
50              
51             $app->titlebar($caption);
52             $app->statusbar($message);
53              
54             $app->draw;
55             $app->redraw;
56              
57             $app->addFormDef('MyForm', { %formopts });
58             $app->createForm($name, $def);
59             $form = $app->getForm('MainFrm');
60             $app->delForm('Main');
61             $app->execForm('Main');
62              
63             $app->execute;
64              
65             =head1 REQUIREMENTS
66              
67             Curses
68             Curses::Widgets
69             Curses::Forms
70              
71             =head1 DESCRIPTION
72              
73             Curses::Application attempts to relieve the programmer of having to deal
74             directly with Curses at all. Based upon Curses::Widgets and Curses::Forms,
75             all one should have to do is define the application forms and contents in the
76             DATA block of a script. Curses::Application will take care of the rest.
77              
78             =cut
79              
80             #####################################################################
81             #
82             # Environment definitions
83             #
84             #####################################################################
85              
86             package Curses::Application;
87              
88 1     1   922 use strict;
  1         2  
  1         39  
89 1     1   6 use vars qw($VERSION @ISA @EXPORT);
  1         1  
  1         59  
90 1     1   6 use Exporter;
  1         13  
  1         42  
91 1     1   4 use Carp;
  1         2  
  1         205  
92 1     1   2063 use Curses;
  0            
  0            
93             use Curses::Widgets;
94             use Curses::Forms;
95             use Curses::Forms::Dialog;
96             use Curses::Forms::Dialog::Input;
97             use Curses::Forms::Dialog::Logon;
98              
99             ($VERSION) = (q$Revision: 0.2 $ =~ /(\d+(?:\.(\d+))+)/);
100              
101             @ISA = qw(Curses::Widgets);
102             @EXPORT = qw(dialog input logon BTN_OK BTN_YES BTN_NO BTN_CANCEL
103             BTN_HELP scankey);
104              
105             my @events = qw(OnEnter OnExit);
106             my @colitems = qw(FOREGROUND BACKGROUND BORDERCOL CAPTIONCOL);
107              
108             #####################################################################
109             #
110             # Module code follows
111             #
112             #####################################################################
113              
114             =head1 INTRODUCTION
115              
116             This module follows many of the conventions established by the Curses::Widgets
117             and Curses::Forms modules, being built upon that framework. One area of
118             special note, however, is the declaration of forms used within the
119             application.
120              
121             B differentiates between forms and form definitions.
122             A form is an instance of any particular form definition. Keeping that line of
123             separation simplifies the development of MDI (Multiple Document Interface)
124             applications.
125              
126             Form definitions can be provided in two ways: as a list of definitions in the
127             main::DATA block, or individually by using the B method. The
128             former would normally be the simplest way to do so.
129              
130             At the end of your script, declare a DATA block using Perl's B<__DATA__>
131             token. In that DATA block place a hash declaration (%forms) which contains
132             a key/value pair for each form definition. The key being the name of the
133             definition, and the value being a hash reference to the form declarations
134             (see the B pod for directives available to that module). The
135             only extra key that should be in each form's hash reference should be a
136             B directive, which would point to a module name relative to the base
137             Curses::Forms class. If you omit this key, then it will be assumed that the
138             form is a Curses::Forms object, or some custom derivative as specified in
139             B.
140              
141             Example:
142             ========
143              
144             __DATA__
145            
146             %forms = (
147             Main => {
148             TYPE => 'Custom',
149             ALTBASE => 'MyCompany::Forms',
150             LINES => 10,
151             COLUMNS => 80,
152             DERIVED => 0,
153             WIDGETS => {
154             ...
155             },
156             ...
157             },
158             );
159              
160             Just as Curses::Forms allows you to use custom derivatives of Curses::Widgets,
161             this module also allows you to use custom derivatives of Curses::Forms using
162             the B directive. Similarly, the OnEnter and OnExit events are also
163             supported on per-form basis. Instead of passing the form reference as an
164             argument to the call it passes the application object reference.
165              
166             B: The main form (as declared with B) will always be a
167             derived form and the size of the screen minus any title or status bars used.
168             This is overridden in the object constructor, so expect those options to be
169             set as such.
170              
171             =head1 FUNCTIONS
172              
173             This module exports the functions and constants provided by
174             Curses::Forms::Dialog and child modules:
175              
176             Functions
177             ---------
178             dialog, input, logon, scankey
179              
180             Constants
181             ---------
182             BTN_OK, BTN_YES, BTN_NO, BTN_CANCEL, BTN_HELP
183              
184             This should provide all of the functionality needed within your main
185             application code. The intent of this module is to prevent you from having to
186             know and/or use the entire Curses family of modules directly. The only thing
187             you will need to be aware of is the appropriate configuration syntax for both
188             forms and widgets.
189              
190             If you need access to the B, etc., functions, you'll need to add:
191              
192             use Curses::Forms;
193              
194             to your main script body, and they'll be imported directly.
195              
196             =head1 METHODS
197              
198             =head2 new
199              
200             $app = Curses::Application->new({
201             FOREGROUND => 'white',
202             BACKGROUND => 'blue',
203             TITLEBAR => 1,
204             STATUSBAR => 1,
205             CAPTION => 'My Application',
206             MAINFORM => { name => defname },
207             MINY => 20,
208             MINX => 60,
209             ALTFBASE => 'MyCompany::Forms',
210             ALTBASE => 'MyCompany::Widgets',
211             });
212              
213             The B class method returns a Curses::Application object. All
214             arguments are optional, provided you're happy with the defaults, with the
215             exception of B. That directive is a key/value pair consisting of
216             the form name and the name of the form definition.
217              
218             Argument Default Description
219             ============================================================
220             FOREGROUND undef Default foreground colour
221             BACKGROUND undef Default background colour
222             CAPTIONCOL undef Default caption colour
223             TITLEBAR 0 Whether or not to show a title bar
224             STATUSBAR 0 Whether or not to show a status bar
225             CAPTION $0 Default caption to show in the titlebar
226             MINY 24 Minimum lines needed for application
227             MINX 80 Minimum columns needed for application
228             ALTFBASE undef Alternate namespace to search for forms
229             ALTBASE undef Alternate namespace to search for widgets
230             FORMDEFS {} Form definitions
231             INPUTFUNC \&scankey Default input routine
232              
233             B is the form first display by the application when executed.
234              
235             If either B or B is not satisfied, this method will return undef
236             instead of an object reference to Curses::Application.
237              
238             Like Curses::Forms, all colour choices are passed to each form that doesn't
239             explicitly declare their own. Alternate namespaces are also passed.
240              
241             =cut
242              
243             sub _conf {
244             # This method creates the initial curses object and initialises
245             # both the curses and application configurable space.
246             #
247             # Usage: $self->_conf(%conf);
248              
249             my $self = shift;
250             my %conf = (
251             TITLEBAR => 0,
252             STATUSBAR => 0,
253             FORMDEFS => {},
254             CAPTION => $0,
255             MINY => 24,
256             MINX => 80,
257             @_ );
258             my $mwh = new Curses;
259             my @required = qw(MAINFORM);
260             my ($y, $x, %forms, $code);
261             my ($my, $ml) = (0, 0);
262             my $err = 0;
263             my $main;
264              
265             # Set some defaults
266             $self->{CONF} = {%conf};
267             $self->{FORMS} = {};
268             $self->{FORMDEFS} = {};
269              
270             # Check for required arguments
271             foreach (@required) { $err = 1 unless exists $conf{$_} };
272             unless ($err == 0) {
273             carp ref($self), ": Required fields not passed";
274             return 0;
275             }
276              
277             # Save the handle to stdscr
278             $self->{MWH} = $mwh;
279             pushwh($mwh);
280              
281             # Get and store the max X and Y
282             $mwh->getmaxyx($y, $x);
283             $self->{MAX} = [$y, $x];
284              
285             # Return an error if MINY and MINX aren't met
286             unless ($y >= $conf{MINY} && $x >= $conf{MINX}) {
287             carp ref($self), ": Minimum screen size not satisfied!";
288             return 0;
289             }
290              
291             # Set up the session
292             noecho(); # Turn off input echoing
293             halfdelay(1); # Turn on partial blocking uncooked input
294             curs_set(0); # Turn off visible cursor
295             $mwh->keypad(1); # Turn on keypad support for special keys
296             $mwh->syncok(1); # Sync sub/derwins up to mainwin
297              
298             # Read the forms from main
299             $code = join('', );
300             close(main::DATA);
301             unless (eval $code) {
302             carp ref($self), ": Eval of main::DATA failed!";
303             return 0;
304             }
305              
306             # Get geometry for the main form
307             $ml = $y;
308             $my = 0;
309             if ($conf{TITLEBAR}) {
310             --$ml;
311             ++$my;
312             }
313             --$ml if $conf{STATUSBAR};
314              
315             # Set size of MAINFORM
316             $main = (keys %{$conf{MAINFORM}})[0];
317             $forms{$conf{MAINFORM}{$main}} = {
318             %{$forms{$conf{MAINFORM}{$main}}},
319             Y => $my,
320             X => 0,
321             LINES => $ml,
322             COLUMNS => $x,
323             DERIVED => 1,
324             };
325              
326             # Save the form defs, adjusting the colours, if neccessary
327             foreach (keys %forms) { $self->addFormDef($_, $forms{$_}) };
328              
329             # Set the window foreground/background colours if specified
330             if ($conf{FOREGROUND} && $conf{BACKGROUND}) {
331             $mwh->bkgdset(COLOR_PAIR(
332             select_colour($conf{FOREGROUND}, $conf{BACKGROUND})));
333             }
334              
335             # Make sure no errors are returned by the parent method
336             $err = 1 unless $self->SUPER::_conf(%conf);
337              
338             # Initialise window
339             $self->_init($mwh);
340              
341             return $err == 1 ? 0 : 1;
342             }
343              
344             =head2 maxyx
345              
346             ($y, $x) = $app->maxyx;
347              
348             Returns the maximum Y and X coordinates for the screen.
349              
350             =cut
351              
352             sub maxyx {
353             my $self = shift;
354              
355             return @{$self->{MAX}};
356             }
357              
358             =head2 mwh
359              
360             $mwh = $app->mwh;
361              
362             Returns a handle to the curses window handle.
363              
364             =cut
365              
366             sub mwh {
367             my $self = shift;
368              
369             return $self->{MWH};
370             }
371              
372             =head2 titlebar
373              
374             $app->titlebar($newcaption);
375              
376             This method updates the application caption used in the titlebar and
377             immediately updates screen with a refresh. If you'd prefer to have it updated
378             at the next application refresh (such as the next B method call) you
379             should use the B method instead, and update the B
field.
380              
381             =cut
382              
383             sub titlebar {
384             my $self = shift;
385             my $caption = shift;
386             my $conf = $self->{CONF};
387              
388             $$conf{CAPTION} = $caption;
389             $self->_titlebar;
390             $self->{MWH}->refresh;
391             }
392              
393             sub _titlebar {
394             my $self = shift;
395             my $mwh = $self->{MWH};
396             my $enabled = $self->{CONF}->{TITLEBAR};
397             my $caption = $self->{CONF}->{CAPTION};
398              
399             if ($enabled) {
400             $mwh->standout;
401             $mwh->addstr(0, 0, $caption . ' ' x ($COLS - length($caption)));
402             $mwh->standend;
403             }
404             }
405              
406             =head2 statusbar
407              
408             $app->statusbar($message);
409              
410             This method updates the statusbar message and immediately updates screen with
411             a refresh. If you'd prefer to have it updated at the next application
412             refresh (such as the next B method call) you should use the
413             B method instead, and update the B field.
414              
415             =cut
416              
417             sub statusbar {
418             my $self = shift;
419             my $message = shift;
420             my $conf = $self->{CONF};
421              
422             $$conf{MESSAGE} = $message;
423             $self->_statusbar;
424             $self->{MWH}->refresh;
425             }
426              
427             sub _statusbar {
428             my $self = shift;
429             my $mwh = $self->{MWH};
430             my $enabled = $self->{CONF}->{STATUSBAR};
431             my $message = $self->{CONF}->{MESSAGE};
432             my ($y, $x);
433              
434             if ($enabled) {
435             $mwh->getmaxyx($y, $x);
436             $mwh->standout;
437             $mwh->addstr($y - 1, 0, $message . ' ' x ($COLS - length($message)));
438             $mwh->standend;
439             }
440             }
441              
442             =head2 draw
443              
444             $app->draw;
445              
446             Flushes all screen changes to the terminal.
447              
448             =cut
449              
450             sub draw {
451             my $self = shift;
452             my $mwh = $self->{MWH};
453             my $conf = $self->{CONF};
454              
455             $self->_titlebar;
456             $self->_statusbar;
457             $mwh->refresh;
458             }
459              
460             =head2 redraw
461              
462             $app->redraw;
463              
464             Redraws the entire screen.
465              
466             =cut
467              
468             sub redraw {
469             my $self = shift;
470             my $mwh = $self->{MWH};
471              
472             $mwh->touchwin;
473             $mwh->refresh;
474             }
475              
476             =head2 addFormDef
477              
478             $app->addFormDef('MyForm', { %formopts });
479              
480             Adds another form definition to the current library. Returns a true if
481             successful, and a false if not (such as if the form type requested is provided
482             by an unavailable module).
483              
484             =cut
485              
486             sub addFormDef {
487             my $self = shift;
488             my $name = shift;
489             my $options = shift;
490             my $type = $$options{TYPE} || '';
491             my $forms = $self->{FORMDEFS};
492             my @try = ('Curses::Forms');
493             my $conf = $self->{CONF};
494             my ($altf, $altw) = @$conf{qw(ALTFBASE ALTBASE)};
495             my $success = 0;
496             my ($base, $mod);
497              
498             # Get the alt forms base class, if specified
499             if (defined $altf) {
500             if (ref($altf) eq 'ARRAY') {
501             unshift @try, @$altf;
502             } else {
503             unshift @try, $self->{CONF}->{ALTBASE};
504             }
505             }
506              
507             # Load the applicable module
508             foreach $base (@try) {
509             $mod = $type eq '' ? $base : "${base}::$type";
510             if (eval "require $mod") {
511             $success = 1;
512             $type = $mod;
513             last;
514             }
515             }
516             unless ($success) {
517             carp ref($self), ": Loading module $type (in @try) failed!";
518             return 0;
519             }
520              
521             # Avoid name collisions
522             if (exists $$forms{$name}) {
523             carp ref($_), ": A form def named $name is already in the hash!";
524             return 0;
525             }
526              
527             # Store the form def after updating few options
528             $$options{INPUTFUNC} = $$conf{INPUTFUNC};
529             $$options{MODULE} = $mod;
530             foreach (@colitems) {
531             $$options{$_} = $$conf{$_} if
532             (exists $$conf{$_} && ! exists $$options{$_});
533             }
534             $$forms{$name} = { ALTFBASE => $altf, ALTBASE => $altw, %$options };
535              
536             return 1;
537             }
538              
539             =head2 createForm
540              
541             $app->createForm($name, $def);
542              
543             Creates a form object based on the named definition. Returns a handle to the
544             form if successful, and a false if not.
545              
546             =cut
547              
548             sub createForm {
549             my $self = shift;
550             my $name = shift;
551             my $def = shift;
552             my $forms = $self->{FORMS};
553             my $defs = $self->{FORMDEFS};
554             my ($type, $options);
555              
556             # Saftey checks
557             unless (exists $$defs{$def}) {
558             carp ref($self), ": No form def exists by that name ($name)!";
559             return 0;
560             }
561             if (exists $$forms{$name}) {
562             carp ref($self), ": A form by the name of $name already exists!";
563             return 0;
564             }
565              
566             # Create and store the form
567             {
568             no strict 'refs';
569             $type = $$defs{$def}{MODULE};
570             $options = $$defs{$def};
571             unless ($$forms{$name} = "$type"->new($options)) {
572             carp ref($self), ": $type creation failed!";
573             return 0;
574             }
575              
576             # Reference event subs under form space
577             foreach (@events) {
578             $$forms{$name}->{$_} = $$options{$_} if exists $$options{$_};
579             }
580             }
581              
582             return $$forms{$name};
583             }
584              
585             =head2 getForm
586              
587             $form = $app->getForm('MainFrm');
588              
589             Returns a handle to the specified form. If that form does not exist, the
590             object generates a warning and returns undef.
591              
592             =cut
593              
594             sub getForm {
595             my $self = shift;
596             my $name = shift;
597             my $forms = $self->{FORMS};
598              
599             if (exists $$forms{$name}) {
600             return $$forms{$name};
601             } else {
602             carp ref($self), ": No form by the name $name exists!";
603             return undef;
604             }
605             }
606              
607             =head2 delForm
608              
609             $app->delForm('Main');
610              
611             Deletes the form object by that name.
612              
613             =cut
614              
615             sub delForm {
616             my $self = shift;
617             my $name = shift;
618             my $forms = $self->{FORMS};
619              
620             unless (exists $$forms{$name}) {
621             carp ref($self), ": No form by that name ($name) exists to be deleted!";
622             return 0;
623             }
624              
625             delete $$forms{$name};
626             return 1;
627             }
628              
629             =head2 execForm
630              
631             $app->execForm('Main');
632              
633             Executes the form specified by name. This form must be created beforehand via
634             the B method. Returns the return value of the form's B
635             method.
636              
637             =cut
638              
639             sub execForm {
640             my $self = shift;
641             my $name = shift;
642             my $forms = $self->{FORMS};
643             my ($f, $rv);
644              
645             unless (exists $$forms{$name}) {
646             carp ref($self), ": No form ($name) available to execute!";
647             return 0;
648             }
649              
650             $f = $$forms{$name};
651              
652             # Call the OnEnter routine if present
653             &{$f->{OnEnter}}($self) if defined $f->{OnEnter};
654              
655             # Execute the form
656             $rv = $f->execute($self->mwh);
657              
658             # Call the OnExit routine if present
659             &{$f->{OnExit}}($self) if defined $f->{OnExit};
660              
661             return $rv;
662             }
663              
664             =head2 execute
665              
666             $app->execute;
667              
668             Causes the main form to execute. Once the main form exits, this call will
669             exit as well.
670              
671             =cut
672              
673             sub execute {
674             my $self = shift;
675             my $conf = $self->{CONF};
676             my $forms = $self->{FORMS};
677             my $main;
678              
679             # Get the main form name
680             $main = (keys %{$$conf{MAINFORM}})[0];
681              
682             # Create it if necessary
683             unless (exists $$forms{$main}) {
684             $self->createForm($main, $$conf{MAINFORM}{$main});
685             }
686              
687             # Execute it
688             $self->execForm($main);
689             }
690              
691             sub DESTROY {
692             # This routines resets the console to the previous sane state
693             # before the application began.
694             #
695             # Internal use only.
696              
697             my $self = shift;
698              
699             popwh();
700             endwin();
701             }
702              
703             1;
704              
705             =head1 HISTORY
706              
707             =over
708              
709             =item 2002/11/12 - Initial release.
710              
711             =back
712              
713             =head1 AUTHOR/COPYRIGHT
714              
715             (c) 2001 Arthur Corliss (corliss@digitalmages.com)
716              
717             =cut
718