File Coverage

blib/lib/DBIx/Informix/Perform.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 1     1   47535 use 5.6.0;
  1         5  
  1         73  
2             package DBIx::Informix::Perform;
3              
4 1     1   6 use strict;
  1         3  
  1         38  
5 1     1   7 use warnings;
  1         9  
  1         43  
6 1     1   6 use Carp;
  1         2  
  1         86  
7 1     1   4838 use Curses; # to get KEY_*
  0            
  0            
8             use Curses::Application;
9             use DBI;
10             use POSIX;
11             use DBIx::Informix::Perform::DButils;
12              
13             # Apparently it's necessary to directly "use" the derived form types one wants...
14              
15             use DBIx::Informix::Perform::Forms;
16             # use DBIx::Informix::Perform::Widgets;
17             use DBIx::Informix::Perform::Widgets::TextField;
18             use DBIx::Informix::Perform::Widgets::ButtonSet;
19              
20             use base 'Exporter';
21              
22             our $VERSION = 0.01;
23              
24             use vars qw(@EXPORT_OK %FORM %APP
25             $APP $FORM $DB $NO_MORE_ROWS);
26              
27             $NO_MORE_ROWS = "No more rows in the direction you are going.";
28              
29             @EXPORT_OK = qw(run);
30              
31             %FORM =
32             (TABORDER => ['Buttons', 'DBForm'],
33             TYPE => '', # but from DBIx::Informix::Perform::Forms
34             # There is some bug in the alt[f]base stuff in Curses::Forms.
35             ALTFBASE => ['DBIx::Informix::Perform::Forms', 'DBIx::Informix::Perform::Widgets'],
36             ALTBASE => ['DBIx::Informix::Perform::Forms', 'DBIx::Informix::Perform::Widgets'],
37             FOCUSED => 'Buttons',
38             WIDGETS => {
39             Buttons => {
40             TYPE => 'ButtonSet',
41             BORDER => 0,
42             X => 1,
43             Y => 0,
44             LABELS => [qw(Query Next Prev. Add Update Remove Exit)],
45             LENGTH => 6,
46             FOCUSSWITCH=> "\t\nmd",
47             OnExit => \&ButtonPush,
48             },
49             },
50             );
51              
52             %APP =
53             (
54             FOREGROUND => 'white',
55             BACKGROUND => 'black',
56             MAINFORM => { Dummy => 'DummyDef' }, # changed at runtime
57             STATUSBAR => 1,
58             EXIT => 0,
59             form_name => 'Run0',
60             form_names => ['Run0'], # set later
61             form_name_indexes => { Run0 => 0 }, # also set later
62             md_mode => 'm', # master/detail mode, "m" or "d".
63             resume_command => undef, # do the specified command after switching
64             # master/detail context or screens.
65             );
66              
67              
68            
69             sub run
70             {
71             my $arg = shift;
72              
73             my $form = load($arg);
74             $DB = DBIx::Informix::Perform::DButils::open_db($form->{'db'});
75             run_form($form);
76             }
77              
78              
79             sub run_form
80             {
81             my $form = shift;
82              
83             my %appdef = %APP;
84             if (defined (my $minsize = $form->{'screen'}{'MINSIZE'})) {
85             @appdef{'MINX', 'MINY'} = @$minsize;
86             }
87             my $instrs = $appdef{'instrs'} = $form->{'instrs'};
88             my $masters = $instrs && $$instrs{'MASTERS'};
89             $appdef{'MASTERS'} = $masters; # n.b. add it even if undef'd.
90             $appdef{'BACKGROUND'} = $ENV{'BGCOLOR'}
91             if $ENV{'BGCOLOR'};
92             $APP = new Curses::Application (\%appdef)
93             or die "Unable to create application object";
94             my $mwh = $APP->mwh(); # main window handle.
95             my ($maxy, $maxx) = $APP->maxyx();
96             my $i = 0;
97             my @subformdefs = curses_formdefs($form, $maxy-2, $maxx, \%appdef);
98             my @formnames;
99             foreach my $sfd (@subformdefs) {
100             my %runformdef = %FORM;
101             my $defname = "RunDef$i";
102             my $formname = "Run$i";
103             @runformdef{qw(X Y LINES COLUMNS DERIVED SUBFORMS)} =
104             (0, 0, $maxy-1, $maxx, 1, { 'DBForm' => $sfd });
105             push (@formnames, $formname);
106             $APP->addFormDef($defname, { %runformdef });
107             $APP->createForm($formname, $defname);
108             $i++;
109             }
110             $APP->setField(MAINFORM => {Run0 => 'RunDef0'});
111             $APP->setField(form_names => [ @formnames ]);
112             $APP->setField(form_name_indexes =>
113             +{ map {($formnames[$_], $_)} 0..$#formnames });
114             $APP->draw();
115             while (! $APP->getField('EXIT')) { # run until user exits.
116             my $fname = $APP->getField('form_name');
117             local $FORM = $APP->getForm($fname);
118             my $resumecmd = $APP->getField('resume_command');
119             if ($resumecmd) {
120             &$resumecmd($FORM);
121             $APP->setField('resume_command', undef);
122             }
123             $APP->execForm($fname);
124             }
125             }
126              
127              
128             # returns a (spread) array of form defs.
129             # In a master/detail arrangement, the master form is presumed to be first.
130             sub curses_formdefs
131             {
132             my $formspec = shift; # parsed from a .per file...
133             my $maxy = shift;
134             my $maxx = shift;
135             my $appdef = shift;
136              
137             my @screens = @{$formspec->{'screens'}};
138             my $attrs = $formspec->{'attrs'};
139             my $lineoffset = 0; # used for combining screens
140             my @formdefs = ();
141             my $i = 0;
142             my $appbg = $$appdef{'BACKGROUND'};
143             my $deffldbg = $ENV{'FIELDBGCOLOR'} || 'blue';
144             foreach my $screen (@screens) {
145             my $widgets = $$screen{'WIDGETS'};
146             my $fields = $$screen{'FIELDS'};
147             my $subformfields =
148             # [ grep { $$widgets{$_} } @$fields ] ;
149             $fields; # may not need this, really.
150             my $add_taborder = # fields without NOENTRY attribute
151             [ grep { ! $$attrs{$_}[1]{NOENTRY} } @$subformfields ];
152             my $update_taborder = # fields without NOUPDATE attribute
153             [ grep { ! $$attrs{$_}[1]{NOUPDATE} } @$subformfields ];
154             my $defaults =
155             +{map { my $d = $$attrs{$_}[1]{DEFAULT};
156             defined($d) ? ($_, $d) : (); } @$subformfields };
157             my %def = (
158             X => 0, Y => 1,
159             COLUMNS => $maxx,
160             LINES => $maxy,
161             DERIVED => 1,
162             ALTFBASE => 'DBIx::Informix::Perform::Forms',
163             ALTBASE => 'DBIx::Informix::Perform::Widgets',
164             TABORDER => $fields,
165             tables => $formspec->{'tables'}, # pass these two straight on
166             attrs => $attrs, # to the runtime system.
167             fields => $subformfields, # this is taborder for query mode, too.
168             add_taborder => $add_taborder,
169             update_taborder => $update_taborder,
170             defaults => $defaults,
171             md_mode => 'm', # master/detail mode.
172             editmode => '',
173             # query_save => {},
174             );
175             # Now install OnExit trampolines and more for each field...
176             foreach my $f (@$fields) {
177             my $w = $widgets->{$f};
178             my ($cols, $fattrs) = @{$$attrs{$f}};
179             # This "trampoline" function gives field name to the real OnExit fcn.
180             $w->{'OnExit'} = sub{ &OnFieldExit($f, @_); };
181             my $comments = $$fattrs{'COMMENTS'};
182             $w->{'OnEnter'} = sub{ $APP->statusbar($comments); }
183             if ($comments);
184             $w->{'FOCUSSWITCH'} = "\t\n\cp\cw\cc\ck\c[";
185             $w->{'FOCUSSWITCH_MACROKEYS'} = [KEY_UP, KEY_DOWN, KEY_DC];
186             my $color = $fattrs->{'COLOR'} || $deffldbg;
187             $w->{'BACKGROUND'} = $color;
188             if ($color eq $appbg) {
189             # Need the open/close brackets if no color difference.
190             $$widgets{"$f.openbracket"} =
191             +{ TYPE => 'Label', COLUMNS => 1, ROWS => 1,
192             Y => $w->{'Y'}, X => $w->{'X'}-1, VALUE => "[" };
193             $$widgets{"$f.closebracket"} =
194             +{ TYPE => 'Label', COLUMNS => 1, ROWS => 1,
195             Y => $w->{'Y'}, X => $w->{'X'} + $w->{'COLUMNS'},
196             VALUE => "]" };
197             }
198             # Copy the attributes & columns out in the widgets where
199             # they may be handier.
200             $w->{'columns'} = $cols; # [ [ tbl, col ], ...]
201             $w->{'attrs'} = $fattrs; # { NOENTRY => 1, DEFAULT => '"33"',... }
202             $w->{'savevalue'} = '';
203             }
204             $def{'WIDGETS'} = { %$widgets },
205             push (@formdefs, { %def });
206             }
207             return @formdefs;
208             }
209              
210              
211             sub load
212             {
213             my $arg = shift;
214              
215             if (length($arg) < 500) {
216             # Assume filename.
217             if ($arg =~ /\.per$/) {
218             return load_per($arg);
219             }
220             elsif ($arg =~ /\.pps/) {
221             return load_file($arg);
222             }
223             else {
224             die "Unknown file extension on '$arg'";
225             }
226             }
227             else {
228             if (ref($arg) =~ /HASH/) {
229             return $arg;
230             }
231             elsif ($arg =~ /^\s*database\s/m) {
232             require "DBIx/Informix/Perform/DigestPer.pm";
233             return DBIx::Informix::Perform::DigestPer::digest_string($arg);
234             }
235             elsif ($arg =~ /^\$form\s*=/) {
236             return load_string($arg);
237             }
238             die "Unrecognized string arg.";
239             }
240             }
241              
242             # Digest it on the fly.
243             sub load_per
244             {
245             my $file = shift;
246              
247             open (PER_IN, "< $file")
248             || die "Unable to open '$file' for reading: $!";
249             require "DBIx/Informix/Perform/DigestPer.pm";
250             my $digest = DBIx::Informix::Perform::DigestPer::digest(\*PER_IN);
251             die "File did not digest to a Perl Perform Spec"
252             unless $digest =~ /\$form\s*=/;
253             return load_string($digest);
254             }
255            
256            
257              
258             sub load_file
259             {
260             my $file = shift;
261             load_internal(sub { require $file });
262             }
263              
264             sub load_string
265             {
266             my $string = shift;
267             load_internal(sub { eval $string });
268             }
269              
270              
271             sub load_internal
272             {
273             my $sub = shift;
274              
275             our $form;
276             local ($form);
277             &$sub();
278             return $form;
279             }
280              
281              
282             # Run-time functions...
283              
284             use vars '%BUTTONSUBS';
285             %BUTTONSUBS =
286             (query => \&querymode,
287             next => \&do_next,
288             'prev.' => \&do_prev,
289             add => \&addmode,
290             update => \&updatemode,
291             remove => \&do_remove,
292             exit => \&doquit,
293             );
294              
295             sub ButtonPush
296             {
297             my $form = shift;
298             my $key = shift;
299              
300             if (lc($key) =~ /[md]/) {
301             do_master_detail(lc($key), $form);
302             return;
303             }
304             my $wid = $form->getWidget('Buttons');
305             my $val = $wid->getField('VALUE');
306             my $labels = $wid->getField('LABELS');
307             my $thislabel = lc($$labels[$val]);
308             my $btnsub = $BUTTONSUBS{$thislabel};
309             if ($btnsub && ref($btnsub) eq 'CODE') {
310             &$btnsub($form);
311             }
312             else {
313             print STDERR "No button sub for '$thislabel'\n";
314             $form->setField('DONTSWITCH', 1);
315             }
316             }
317              
318             sub clear_textfields
319             {
320             my $subform = shift;
321              
322             my $fields = $subform->getField('fields');
323             return unless $fields;
324             foreach my $f (@$fields) {
325             $subform->getWidget($f)->setField('VALUE', '');
326             }
327             }
328              
329             # Hope this suffices to switch forms.
330             sub setSubform
331             {
332             my $form = shift; # top-level form.
333             my $n = shift;
334              
335             my $forms = $APP->getField('form_names');
336             my $fname = $$forms[$n];
337             if ($fname) {
338             $APP->setField('form_name', $fname);
339             $form->setField('EXIT', 1);
340             }
341             }
342              
343             use vars qw($STH @ROWS $ROWNUM $STHDONE
344             $MASTER_STH @MASTER_ROWS $MASTER_ROWNUM $MASTER_STHDONE);
345              
346              
347             sub clear_STH
348             {
349             if ($STH) {
350             eval { $STH->finish() }; # ignore errors from this.
351             undef $STH;
352             @ROWS = ();
353             $ROWNUM = -1;
354             undef $STHDONE;
355             }
356             }
357              
358             # If there are no rows, it sets DONTSWITCH and statusbars a message.
359             # Returns true if no rows.
360             sub check_rows_and_advise
361             {
362             my $form = shift;
363              
364             if ($#ROWS < 0 || !defined($ROWNUM)) {
365             $APP->statusbar("There are no rows in the current list.");
366             $form->setField('DONTSWITCH', 1);
367             return 1;
368             }
369             return undef;
370             }
371              
372             # called from button_push with the top-level form.
373             sub querymode
374             {
375             my $form = shift;
376              
377             # Shift forcibly back to master mode for query.
378             # FIX_ME? This is an incompatibility, no querying in detail mode.
379             if ($APP->getField('md_mode') ne 'm') {
380             do_master_detail('m', $form);
381             $APP->setField('resume_command', \&querymode_resume);
382             }
383             my $subform =
384             $form->getSubform('DBForm') || $form;
385             clear_textfields($subform);
386             my $to = $subform->getField('fields');
387             $subform->setField('TABORDER', $to);
388             $subform->setField('FOCUSED', $to->[0]); # first field.
389             $subform->setField('editmode', 'query');
390             $APP->statusbar("Enter fields to query. ESC queries, DEL cancels.");
391             # go ahead and switch to the form.
392             }
393              
394             # Called as a resume entry, 'cause we have to force the form into
395             # the subform since we can't rely on lack of DONTSWITCH to switch there.
396             sub querymode_resume
397             {
398             my ($form) = @_;
399             querymode(@_);
400             $form->setField('FOCUSED', 'DBForm');
401             }
402            
403              
404             sub do_master_detail
405             {
406             my $m_or_d = shift;
407             my $form = shift;
408              
409             my $masters = $APP->getField('MASTERS');
410             return ($form->setField('DONTSWITCH', 1) ,
411             $APP->statusbar('No detail table for this form.'))
412             unless $masters; # if not in a m/d form, skip it.
413             return undef
414             if ($APP->getField('md_mode') eq $m_or_d);
415             # Switch modes.
416             my $subform = $form->getSubform('DBForm');
417             $APP->setField('md_mode', $m_or_d);
418             my (@wheres, @vals);
419             if ($m_or_d eq 'd') {
420             if (@ROWS && $ROWNUM >= 0 && $ROWS[$ROWNUM]) {
421             # Do detail query...
422             # Save state of master query...
423             $MASTER_STH = $STH;
424             @MASTER_ROWS = @ROWS;
425             $MASTER_ROWNUM = $ROWNUM;
426             $MASTER_STHDONE = $STHDONE;
427             $STH = {}; # so the object doesn't get finish()'ed.
428             my $mrow = $MASTER_ROWS[$MASTER_ROWNUM];
429             my $mtable = $masters->[0][0];
430             my $dtable = $masters->[0][1];
431             my $attrs = $subform->getField('attrs');
432             # Get all the join columns...
433             my @keys = grep { scalar @{$$attrs{$_}->[0]} > 1 } keys %$attrs;
434             foreach my $k (@keys) {
435             my $f = $$attrs{$k};
436             my ($mcol) = grep { $_ ->[0] eq $mtable } @{$f->[0]};
437             my ($dcol) = grep { $_ ->[0] eq $dtable } @{$f->[0]};
438             push (@wheres, "$dcol->[1] = ?");
439             push (@vals, $mrow->{$mcol->[1]});
440             }
441             my $n = do_query_internal($dtable, \@wheres, \@vals);
442             setSubform($form, 1);
443             $APP->setField('resume_command',
444             \&do_next);
445             $n = 0 + $n; # numericize it.
446             my $p = ($n == 1 ? '' : 's');
447             $APP->statusbar("Detail: $n row$p found; row 0")
448             if $n;
449             }
450             else {
451             $APP->statusbar("No active query; not switching to detail mode.");
452             }
453             }
454             else {
455             clear_STH(); # mostly to finish the statement handle.
456             $STH = $MASTER_STH;
457             @ROWS = @MASTER_ROWS;
458             $ROWNUM = $MASTER_ROWNUM;
459             $STHDONE = $MASTER_STHDONE;
460             setSubform($form, 0);
461             display_row_fields($form, $ROWS[$ROWNUM], $ROWNUM);
462             }
463            
464             }
465              
466             # called from button_push with the top-level form.
467             sub do_next
468             {
469             my $form = shift;
470             my $switch = shift;
471              
472             $form->setField('DONTSWITCH', 1)
473             unless $switch;
474             unless ($STH) {
475             $APP->statusbar("No query is active.");
476             return;
477             }
478             my ($row, $msg);
479             if (!defined($ROWNUM) || $ROWNUM >= $#ROWS) {
480             # We're at the end of the fetched rows...
481             $row = $STH->fetchrow_hashref()
482             if !$STHDONE;
483             if ($row) {
484             push (@ROWS, $row);
485             $ROWNUM = $#ROWS;
486             }
487             else {
488             # No row was fetched...
489             $msg = $#ROWS < 0 ? "No rows found" : $NO_MORE_ROWS;
490             $APP->statusbar($msg);
491             my $newbtn = @ROWS ? 2 : 0; # FIX_ME use constants
492             $form->getWidget('Buttons')->setField('VALUE', $newbtn);
493             $STHDONE = 1;
494             if (@ROWS) {
495             # Redisplay current row
496             $row = $ROWS[$ROWNUM];
497             # display_row_fields($form, , $ROWNUM);
498             } else {
499             # Punt on the whole thing.
500             my $subform = $form->getSubform('DBForm');
501             clear_textfields($subform);
502             return;
503             }
504             }
505             }
506             else {
507             # we are marching forward through already-fetched rows...
508             $row = $ROWS[++$ROWNUM];
509             }
510             display_row_fields($form, $row, $msg ? undef : $ROWNUM);
511             }
512              
513             # called from button_push with the top-level form.
514             sub do_prev
515             {
516             my $form = shift;
517              
518             my $display_rownum = $ROWNUM;
519             $form->setField('DONTSWITCH', 1);
520             if ($ROWNUM <= 0) {
521             $APP->statusbar($NO_MORE_ROWS);
522             undef $display_rownum;
523             my $newbtn = @ROWS ? 1 : 0; # FIX_ME use constants
524             $form->getWidget('Buttons')->setField('VALUE', $newbtn);
525             }
526             else {
527             --$ROWNUM;
528             }
529             display_row_fields($form, $ROWS[$ROWNUM], $display_rownum);
530             }
531              
532             # called from button_push with the top-level form.
533             sub addmode
534             {
535             my $form = shift;
536              
537             my $subform = $form->getSubform('DBForm');
538             clear_textfields($subform);
539             # go ahead and switch to form.
540            
541             my $to = $subform->getField('add_taborder');
542             $subform->setField('TABORDER', $to);
543             $subform->setField('FOCUSED', $to->[0]); # first field.
544             $subform->setField('editmode', 'add');
545             my $defs = $subform->getField('defaults');
546             foreach my $f (keys %{ $defs || {} }) {
547             my $v = $$defs{$f};
548             $v = POSIX::strftime("%Y-%m-%d", localtime())
549             if uc($v) eq 'TODAY';
550             $subform->getWidget($f)->setField('VALUE', $v);
551             }
552             $APP->statusbar("Enter row to add. ESC stores; DEL cancels the add.");
553             }
554              
555             # called from button_push with the top-level form.
556             sub updatemode
557             {
558             my $form = shift;
559              
560             return if check_rows_and_advise($form);
561             my $subform = $form->getSubform('DBForm');
562             my $fields = $subform->getField('fields');
563             my $row = $ROWS[$ROWNUM];
564             my $attrs = $subform->getField('attrs');
565             foreach my $f (@$fields) {
566             my $w = $subform->getWidget($f);
567             my $col = $attrs->{$f}[0][0][1];
568             $w->setField('savevalue', $row->{$col});
569             }
570             # go ahead and switch to form.
571             my $to = $subform->getField('update_taborder');
572             $subform->setField('TABORDER', $to);
573             $subform->setField('FOCUSED', $to->[0]); # first field.
574             $subform->setField('editmode', 'update');
575             $APP->statusbar("Update the row. ESC stores; DEL cancels the update.");
576             }
577              
578             sub edit_control # Needs to be generalized to more events.
579             {
580             my $field = shift;
581             my $subform = shift;
582             my $when = lc(shift); # before or after
583              
584             my $instrs = $APP->getField('instrs');
585             my $controls = $instrs->{'CONTROLS'};
586             my $attrs = $subform->getField('attrs');
587             my ($fldtblcols, $fldattrs) = @{$attrs->{$field}};
588             my @cols = map { $_->[1] } @$fldtblcols;
589             my $emode = $subform->getField('editmode');
590             my $event = "edit$emode";
591             my @actions = map {$controls->{$_}{$event}{$when}} @cols;
592             @actions = map {$_ ? @$_ : () } @actions; # spread the arrayrefs.
593             foreach my $ac (@actions) {
594             my ($ac, $field, $opd1, $op, $opd2) = @$ac;
595             if ($ac eq 'nextfield'){
596             if (grep { $field eq $_ } @{$subform->getField('TABORDER')}) {
597             $subform->setField('FOCUSED', $field);
598             }
599             }
600             elsif ($ac eq 'let') {
601             ## FIX_ME *extremely* limited functionality here.
602             my $widget = $subform->getWidget($field);
603             $APP->statusbar("No field '$field' in control block."),
604             return ()
605             unless $widget;
606             $APP->statusbar("Unrecognized operator '$op' in control block."),
607             return()
608             unless $op =~ /^[-+*\/]$/;
609            
610             my $val1 = field_value_or_require_quotes($opd1, $subform);
611             my $val2 = field_value_or_require_quotes($opd2, $subform);
612             my $result = eval "$val1 $op $val2";
613             if ($@) {
614             $APP->statusbar("In control block: $@");
615             return;
616             }
617             $widget->setField('VALUE', $result);
618             $subform->setField('REDRAW', 1);
619             # $APP->redraw();
620             }
621             }
622             }
623            
624             sub field_value_or_require_quotes # single-quote value.
625             {
626             my $opd = shift;
627             my $subform = shift;
628              
629             my $w1 = $subform->getWidget($opd);
630             if ($w1) {
631             my $val = $w1->getField('VALUE');
632             $val =~ s/\'/\\\'/;
633             return "'$val'";
634             }
635             unless ($opd =~ /^\"(.*)\"$|^(\d+(\.\d_)?)$/) {
636             $APP->statusbar("Neither field, number nor quoted string: '$opd' in control block");
637             return "''";
638             }
639             my $val = defined($1) ? $1 : $2;
640             $val =~ s/\'/\\\'/;
641             return "'$val'"; # hard-quote it lest any monkey biz happen.
642             }
643            
644              
645             # called from button_push with the top-level form.
646             sub do_remove
647             {
648             my $form = shift;
649              
650             return if check_rows_and_advise($form);
651             my $subform = $form->getSubform('DBForm');
652             my $fields = $subform->getField('fields');
653             my @wheres = ();
654             my @values = ();
655             my $tables = $subform->getField('tables');
656             my ($table) = @$tables; # only one table for now.
657             my $row = $ROWS[$ROWNUM];
658             ## FIX_ME! Do a two-table remove if necessary.
659             { # this block to be a loop someday
660             foreach my $f (@$fields) {
661             my $fieldspec = $subform->getField('attrs')->{$f}[0];
662             my ($tbl, $col) = @$fieldspec[0,1];
663             next if $tbl ne $table;
664             # my $v = $subform->getWidget($f)->getField('VALUE');
665             my $v = $$row{$col}; # get value straight from source.
666             push (@wheres, defined($v) ? "$col = ?" : "$col is null");
667             push (@values, $v) if defined($v);
668             }
669             my $wheres = join ' and ', @wheres;
670             my $cmd = "delete from $table where $wheres";
671             my $rc = $DB->do($cmd, {}, @values);
672             if (!defined $rc) {
673             $APP->statusbar("Database error: $DBI::errstr");
674             }
675             else {
676             my $msg = "Row removed.";
677             splice(@ROWS, $ROWNUM, 1);
678             $ROWNUM = $#ROWS if $ROWNUM > $#ROWS;
679             clear_textfields($subform);
680             }
681             }
682             $form->setField('DONTSWITCH', 1); # in all cases.
683             }
684              
685             # called from button_push with the top-level form.
686             sub doquit
687             {
688             my $form = shift;
689             # This assumes the form is the top-level one.
690             $form->setField('EXIT', 1);
691             $APP->setField('EXIT', 1);
692             }
693              
694             # When the user hits ESC from the subform, run one of the following
695             # based on the value of the button set.
696             use vars '%MODESUBS';
697             %MODESUBS =
698             ( query => \&do_query,
699             add => \&do_add,
700             update => \&do_update,
701             );
702              
703             sub OnFieldExit
704             {
705             my ($field, $form, $key) = @_; # leaving @_ for back-patching
706              
707             my $widget = $form->getWidget($field);
708             edit_control($field, $form, 'after'); # do any AFTER control blocks.
709             if ($key eq "\t" || $key eq "\n"
710             || $key eq KEY_DOWN) { # shift to next field
711             $APP->statusbar("") # erase our comments
712             if ($widget->getField('attrs')->{COMMENTS});
713             return;
714             }
715              
716             my $dontswitch = 1;
717             # printf STDERR ("Field Exit: Field = $field; Widget = $widget; Key = %o\n", $key);
718             if ($key eq "\c[") { # Do The Mode
719             my $btns = $FORM->getWidget('Buttons');
720             my $mode = lc(($btns->getField('LABELS'))->[$btns->getField('VALUE')]);
721             my $sub = $MODESUBS{$mode};
722             if ($sub && ref($sub) eq 'CODE') {
723             $dontswitch = 0; # let the sub decide.
724             &$sub($field, $widget, $form);
725             }
726             else {
727             beep();
728             }
729             }
730             elsif ($key eq "\cw") {
731             my $msg = $widget->getField('HELPMSG');
732             $APP->statusbar($msg) if ($msg);
733             }
734             elsif ($key eq "\cp") {
735             $APP->statusbar("Current-Value-Of-This-Row not working yet");
736             }
737             elsif ($key eq "\cc") {
738             # FIX_ME not working?!
739             clear_textfields($form);
740             }
741             elsif ($key eq KEY_DC) { # DEL
742             # Bailing out of Query, Update or Modify.
743             # Re-display the row as it was, if any.
744             if ($#ROWS >= 0) {
745             display_row_fields($form, $ROWS[$ROWNUM], $ROWNUM);
746             }
747             else {
748             clear_textfields($form);
749             }
750             # Back to top menu
751             $APP->statusbar("") # erase our comments
752             if ($widget->getField('attrs')->{COMMENTS});
753             $form->setField('EXIT', 1);
754             }
755             elsif ($key eq "\cK" || $key eq KEY_UP || $key eq KEY_STAB) {
756             my $taborder = $form->getField('TABORDER');
757             my %taborder = map { ($$taborder[$_], $_) } (0..$#$taborder);
758             my $i = $taborder{$form->getField('FOCUSED')};
759             $i = ($i <= 0) ? $#$taborder : $i - 1;
760             $form->setField('FOCUSED', $$taborder[$i]);
761             $APP->statusbar("") # erase our comments
762             if ($widget->getField('attrs')->{COMMENTS});
763             $dontswitch = 0;
764             }
765              
766             if ($dontswitch) {
767             $form->setField('DONTSWITCH', 1);
768             }
769             }
770              
771             # Validates a field value against applicable field attributes.
772             # If valid, returns true. If invalid, does statusbar, sets focus
773             # to the field and sets DONTSWITCH and then returns false.
774             sub validate_contents
775             {
776             my $subform = shift;
777             my $f = shift; # vield name
778             my $attrs = shift; # field's attributes hash
779             my $v = shift; # value from widget.
780              
781             my $msg;
782             $msg = "This field requires a value"
783             if ($$attrs{'REQUIRED'} && !defined($v)) ;
784             my $inc = $$attrs{'INCLUDE'};
785             my $inchash = $$attrs{'INCLUDEHASH'}; # made in curses_formdef
786             $msg ||= "Field permissible values: $inc"
787             if ($inchash && !$$inchash{$v});
788             return 1 unless $msg; # Value is OK.
789             $APP->statusbar($msg);
790             $subform->setField('FOCUSED', $f);
791             $subform->setField('DONTSWITCH', 1);
792             return undef;
793             }
794              
795              
796              
797             sub do_query
798             {
799             my $field = shift;
800             my $widget = shift;
801             my $subform = shift;
802              
803             my $masters = $APP->getField('MASTERS');
804             my $attrs = $subform->getField('attrs');
805             my @tables = @{$subform->getField('tables')};
806             my ($table, $detail);
807             if ($masters) {
808             my $mdpair = $$masters[0];
809             my $indexes = $APP->getField('form_name_indexes');
810             my $formindex = $$indexes{$APP->getField('form_name')};
811             my $mdmode = $APP->getField('md_mode');
812             my $mdindex = $mdmode eq 'm' ? 0 : $mdmode eq 'd' ? 1 : undef;
813             die "Masters exist in instructions but md_mode is '$mdmode'"
814             unless defined($mdindex);
815             $table = $$mdpair[$mdindex];
816             $detail = $mdindex != 0;
817             }
818             my @wheres = ();
819             my @vals = ();
820             foreach my $f (@{$subform->getField('fields')}) {
821             my ($fldtblcols, $fldattrs) = @{$attrs->{$f}};
822             my @fldtblcols = @$fldtblcols;
823             next if $masters &&
824             ! grep { $fldtblcols[$_]->[0] eq $table } 0..$#fldtblcols;
825             my ($tbl, $col) = @{$fldtblcols[0]};
826             if (! $masters && $#fldtblcols > 0) {
827             my ($tbl2, $col2) = @{$fldtblcols[1]}; # FIX_ME two tables only
828             push (@wheres, "$tbl.$col = $tbl2.$col2");
829             }
830             my $val = $subform->getWidget($f)->getField('VALUE');
831             next if ($val eq '');
832             # Non-empty field; decide what kind of comparison...
833             my $op = '=';
834             my $cval = $val;
835             if ($val =~ /[*%?]$/) {
836             $cval =~ tr/*?/%_/; # SQL wildcard characters
837             $op = 'like';
838             }
839             if ($val eq '=') {
840             $op = 'is null';
841             $cval = undef;
842             }
843             elsif ($val =~ /^(<<|>>)\s*(.*)$/) {
844             $APP->statusbar("The $1 operator is not supported yet.");
845             }
846             elsif ($val =~ /^([<>][<=>]?)\s*(.*)$/) {
847             $op = $1;
848             $cval = $2;
849             }
850             elsif ($val =~ /^(.+?):(.+)$/) {
851             $op = "between ? and ";
852             push (@vals, $1);
853             $cval = $2;
854             }
855             elsif ($val =~ /^(.+?)\|(.+)$/) {
856             $op = "= ? or $col = "; # might should use in ($1,$2)
857             push (@vals, $1);
858             $cval = $2;
859             }
860             my $where = "$tbl.$col $op" . (defined($cval) ? ' ?' : '');
861             push (@wheres, $where);
862             push (@vals, $cval) if defined($cval);
863             }
864             my $tables = $masters ? $table : join ', ', @tables;
865             my $n = do_query_internal($tables, \@wheres, \@vals);
866             $subform->setField('EXIT', 1); # Focus back to menu if we got this far.
867             unless (defined($n)) {
868             $APP->statusbar("DB Error on execute: $DBI::errstr");
869             return;
870             }
871             $n = 0 + $n; # coerce to number.
872             do_next($FORM, 'switch');
873             $APP->statusbar("$n row" . ($n == 1 ? '' : 's') . " found; Row 0")
874             if $n > 0;
875             }
876              
877             sub do_query_internal
878             {
879             my $tables = shift;
880             my $wheres_ref = shift;
881             my $vals_ref = shift;
882            
883             my $wheres = join ' and ', @$wheres_ref;
884             my $query = "select * from $tables " . ($wheres ? "where $wheres" : '');
885             clear_STH();
886             $STH = $DB->prepare_cached($query);
887             unless($STH) {
888             $APP->statusbar("DB Error on prepare: $DBI::errstr");
889             return;
890             }
891             return $STH->execute(@$vals_ref);
892             }
893              
894             sub do_add
895             {
896             my $field = shift;
897             my $widget = shift;
898             my $subform = shift;
899              
900             my $fields = $subform->getField('fields');
901             my @cols = ();
902             my @values = ();
903             my $tables = $subform->getField('tables');
904             my ($table) = @$tables; # only one table for now.
905             my $row = {};
906             ## FIX_ME! Do a two-table add if necessary.
907             { # this block to be a loop someday
908             foreach my $f (@$fields) {
909             my $fieldattrs = $subform->getField('attrs')->{$f};
910             my ($fieldspecs, $attrs) = @$fieldattrs;
911             next if $$attrs{'NOENTRY'}; # don't include in cols/vals.
912             my $fieldspec = $$fieldspecs[0];
913             my ($tbl, $col) = @$fieldspec[0,1];
914             next if $tbl ne $table;
915             my $v = $subform->getWidget($f)->getField('VALUE');
916             undef $v if $v eq ''; # give NULL for empty fields.
917             return # function below has side-effects on form.
918             unless validate_contents($subform, $f, $attrs, $v);
919             push (@cols, $col);
920             push (@values, $v);
921             $$row{$col} = $v;
922             }
923             my $holders = join ', ', map { "?" } @cols;
924             my $cols = join ', ', @cols;
925             my $cmd = "insert into $table ($cols) values ($holders)";
926             my $rc = $DB->do($cmd, {}, @values);
927             if (!defined $rc) {
928             $APP->statusbar("Database error: $DBI::errstr");
929             return;
930             }
931             else {
932             $APP->statusbar("Row Added.");
933             }
934             }
935             $subform->setField('EXIT', 1); # back to menu
936             # Pretend it's a result of one row, so it can be removed / modified.
937             clear_STH();
938             @ROWS = ( $row );
939             $ROWNUM = 0;
940             $STH = {};
941             $STHDONE = 1;
942             }
943              
944             sub do_update
945             {
946             my $field = shift;
947             my $widget = shift;
948             my $subform = shift;
949              
950             my $fields = $subform->getField('fields');
951             my %wheres = ();
952             my %upds = ();
953             my $tables = $subform->getField('tables');
954             my ($table) = @$tables; # only one table for now.
955             my $row = {};
956             my $attrs = $subform->getField('attrs');
957             ## FIX_ME! Do a two-table add if necessary.
958             { # this block to be a loop someday
959             foreach my $f (@$fields) {
960             my ($fieldspec, $attrs) = @{$attrs->{$f}};
961             my ($tbl, $col) = @{$$fieldspec[0]};
962             next if $tbl ne $table;
963             my $w = $subform->getWidget($f);
964             my $v = $w->getField('VALUE');
965             undef $v if $v eq ''; # empty field means NULL.
966             return
967             unless validate_contents($subform, $f, $attrs, $v);
968             my $sv = $w->getField('savevalue');
969             $$row{$col} = $v;
970             $upds{$col} = $v
971             if ($v ne $sv && !$$attrs{'NOUPDATE'});
972             $wheres{$col} = $sv;
973             }
974             my @updcols = keys (%upds);
975             my @updvals = map { $upds{$_} } @updcols;
976             my $sets = join(', ', map { "$_ = ?" } @updcols);
977             my @wherecols = keys (%wheres);
978             my @wherevals = map { my $w = $wheres{$_}; defined($w) ?
979             ($w) : () } @wherecols;
980             # my %whereinds = map { ($wherecols[$_], $_) } 0..$#wherecols;
981             my %updinds = map { ($updcols[$_], $_) } 0..$#updcols;
982             my $wheres = join(' and ', map { defined($wheres{$_}) ?
983             "$_ = ?" :
984             "$_ is null"
985             } @wherecols);
986             my $cmd = "update $table set $sets where $wheres";
987             my $rc = $DB->do($cmd, {}, @updvals, @wherevals);
988             if (!defined $rc) {
989             $APP->statusbar("Database error: $DBI::errstr");
990             return;
991             }
992             else {
993             $APP->statusbar((0+$rc) . " rows affected");
994             my $query = "select * from $table where $wheres";
995             # Since the new value is now in, change the where value...
996             grep {$ROWS[$ROWNUM]->{$_} = $row->{$_} = $updvals[$updinds{$_}];}
997             @updcols;
998             display_row_fields($subform, $ROWS[$ROWNUM]);
999             }
1000             }
1001             $subform->setField('EXIT', 1); # back to menu
1002             }
1003              
1004              
1005             sub display_row_fields
1006             {
1007             my $form = shift;
1008             my $row = shift;
1009             my $n = shift;
1010              
1011             my $subform =
1012             $form->getSubform('DBForm') || $form;
1013              
1014             my $fields = $subform->getField('fields');
1015             my $attrs = $subform->getField('attrs');
1016             foreach my $f (@$fields) {
1017             my $attr = $attrs->{$f}[0][0];
1018             my ($tbl, $col, $stuff) = @$attr;
1019             $subform->getWidget($f)->setField('VALUE', $row->{$col});
1020             }
1021             $APP->statusbar("Row number $n")
1022             if (defined($n));
1023             # $subform->draw(); ??
1024             }
1025              
1026              
1027             # What a kludge... required by Curses::Application
1028             package main;
1029             __DATA__