File Coverage

blib/lib/Gtk2/Ex/DBI.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


line stmt bran cond sub pod time code
1             # (C) Daniel Kasak: d.j.kasak.dk@gmail.com
2             # See COPYRIGHT file for full license
3              
4             package Gtk2::Ex::DBI;
5              
6 1     1   53947 use strict;
  1         2  
  1         34  
7 1     1   5 use warnings;
  1         1  
  1         28  
8             #no warnings;
9              
10 1     1   6 use Carp;
  1         5  
  1         82  
11 1     1   8991 use Data::Dumper;
  1         21929  
  1         77  
12              
13 1     1   1787 use POSIX;
  1         12200  
  1         18  
14 1     1   6668 use XML::Simple;
  0            
  0            
15             use Time::HiRes;
16             use Glib qw/TRUE FALSE/;
17              
18             use Gtk2::Ex::Dialogs (
19             destroy_with_parent => TRUE,
20             modal => TRUE,
21             no_separator => FALSE
22             );
23              
24             BEGIN {
25             $Gtk2::Ex::DBI::VERSION = '2.30';
26             }
27              
28             sub new {
29            
30             my ( $class, $req, $xml_options ) = @_;
31            
32             my $self;
33            
34             if ( ref $req eq "HASH" ) {
35            
36             # Assemble object from request
37             $self = {
38             dbh => $$req{dbh} # A database handle
39             , primary_keys => $$req{primary_keys} # An array of primary keys
40             , sql => $$req{sql} # A hash of SQL related stuff
41             , widgets => $$req{widgets} # A hash of field definitions and stuff
42             , schema => $$req{schema} # The 'schema' to use to get column info from
43             , builder => $$req{builder} # The Gtk2-Builder object ... use either this or 'form', below
44             , form => $$req{form} # The Gtk2-GladeXML *object* we're using
45             , formname => $$req{formname} # The *name* of the window ( needed for dialogs to work properly )
46             , read_only => $$req{read_only} || FALSE # Whether changes to the table are allowed
47             , apeture => $$req{apeture} || 100 # The number of records to select at a time
48             , on_current => $$req{on_current} # A reference to code that is run when we move to a new record
49             , before_query => $$req{before_query} # A reference to code that is run *before* a query is executed ( can abort the query )
50             , before_apply => $$req{before_apply} # A reference to code that is run *before* the 'apply' method is called
51             , on_apply => $$req{on_apply} # A reference to code that is run *after* the 'apply' method is called
52             , on_undo => $$req{on_undo} # A reference to code that is run *after* teh 'undo' method is called
53             , on_changed => $$req{on_changed} # A reference to code that is run *every* time a managed field is changed
54             , on_initial_changed => $$req{on_initial_changed} # A reference to code that is run when the recordset status *initially* changes to CHANGED
55             , auto_apply => $$req{auto_apply} # Boolean to force all records to be applied automatically when querying, closing, etc
56             , calc_fields => $$req{calc_fields} # Calculated field definitions
57             , defaults => $$req{defaults} # Default values
58             , disable_find => $$req{disable_find} || FALSE # Do we build the right-click 'find' item on GtkEntrys?
59             , disable_full_table_find => $$req{disable_full_table_find} || FALSE # Can the user search the whole table ( sql=>{from} ) or only the current recordset?
60             , combos => $$req{combos} # Definitions to set up combos
61             , autocompletions => $$req{autocompletions} # Definitions to set up autocompletions
62             , data_lock_field => $$req{data_lock_field} || undef # A field to use as a data-driven lock ( positive values will lock the record )
63             , status_label => $$req{status_label} || "lbl_RecordStatus" # The name of a field to use as the record status indicator
64             , record_spinner => $$req{record_spinner} || "RecordSpinner" # The name of a GtkSpinButton to use as the RecordSpinner
65             , quiet => $$req{quiet} || FALSE # A flag to silence warnings such as missing widgets
66             , friendly_table_name => $$req{friendly_table_name} # Table name to use when issuing GUI errors
67             , custom_changed_text => $$req{custom_changed_text} # Text ( including markup ) to use in GUI questions when changes need to be applied
68             , changed => FALSE # A flag indicating that the current record has been changed
69             , changelock => FALSE # Prevents the 'changed' flag from being set when we're moving records
70             , constructor_done => FALSE # A flag that indicates whether the new() method has completed yet
71             , debug => $$req{debug} || FALSE # Dump info to terminal
72             , skip_query => $$req{skip_query} # Don't call query() in the constructor
73             , dont_update_keys => $$req{dont_update_keys} # Don't include primary keys in update statements
74             , widget_prefix => $$req{widget_prefix} # A string to prefix ( glade ) widget names with when searching for them
75             , auto_incrementing => $$req{auto_incrementing} # A flag to indicate whether we should try to poll the last inserted ID after an insert
76             };
77            
78             } else {
79            
80             # Assume we're loading an XML
81             my $xml_cfg = XML::Simple->new(
82             AttrIndent => TRUE, # XML formatting option - doesn't affect performance
83             OutputFile => $self->{xml_file},
84             KeyAttr => [ ] # Stops XML::Simple from squishing some data structures
85             );
86            
87             $self = $xml_cfg->XMLin( $req );
88            
89             # Attach to the libglade / builder object
90             if ( exists $xml_options->{glade_xml} ) {
91             $self->{form} = $xml_options->{glade_xml};
92             } elsif ( exists $xml_options->{gtk_builder} ) {
93             $self->{builder} = $xml_options->{gtk_builder};
94             }
95            
96            
97             # Link DBI connections
98             $self->{dbh} = $xml_options->{connections}->{ $self->{Connection} };
99            
100             foreach my $combo ( keys %{$self->{combos}} ) {
101             $self->{combos}->{$combo}->{alternate_dbh} = $xml_options->{connections}->{ $self->{combos}->{$combo}->{connection_name} };
102             }
103            
104             }
105            
106             my $legacy_warnings;
107            
108             if ( $self->{debug} ) {
109             print "\nGtk2::Ex::DBI version $Gtk2::Ex::DBI::VERSION initialising ...\n\n";
110             }
111            
112             # Check we've been passed enough stuff to continue ...
113            
114             if ( ! $self->{dbh} ) {
115             croak( "Gtk2::Ex::DBI constructor missing a dbh!\n" );
116             }
117            
118             if ( ! $self->{form} && ! $self->{builder} ) {
119             croak( "Gtk2::Ex::DBI constructor missing a 'form' ( Gtk2::GladeXML ) and a 'builder' ( Gtk2::Builder ) ..."
120             . " You need one or the other" );
121             }
122            
123             # Set window object for later ( optionally based on legacy 'formname' string )
124             if ( ! $self->{formname} ) {
125             if ( exists $self->{form} && ref $self->{form} eq "Gtk2::GladeXML" ) {
126             foreach my $item ( $self->{form}->get_widget_prefix( "" ) ) {
127             if ( ref $item eq "Gtk2::Window" || ref $item eq "Gtk2::Dialog") {
128             $self->{window} = $item;
129             last;
130             }
131             }
132             } elsif ( exists $self->{builder} && ref $self->{builder} eq "Gtk2::Builder" ) {
133             foreach my $item ( $self->{builder}->get_objects() ) {
134             if ( ref $item eq "Gtk2::Window" || ref $item eq "Gtk2::Dialog") {
135             $self->{window} = $item;
136             last;
137             }
138             }
139             }
140             # Now check that we have a window
141             if ( ! $self->{window} ) {
142             croak( "Gtk2::Ex::DBI wasn't passed a formname,"
143             . " AND failed to find a Gtk2::Window or Gtk2::Dialog to manage!\n" );
144             }
145             } else {
146             # This doens't really warrant a 'legacy warnings' type thing, but a warning anyway ...
147             carp( "\nThe formname key in now depreciated. Gtk2::Ex::DBI can now find"
148             . " the Gtk2::Window object to manage without being passed a formname ... but make"
149             . " sure you only have ONE GtkWindow object per GladeXML file ( for many reasons ).\n" );
150             $self->{window} = $self->get_widget( $self->{formname} );
151             }
152            
153             if ( $self->{sql} ) {
154             if ( exists $self->{sql}->{pass_through} ) {
155             # pass_throughs are read-only at the moment ... it's all a bit hackish
156             $self->{read_only} = TRUE;
157             } elsif ( ! ( exists $self->{sql}->{select} && exists $self->{sql}->{from} ) ) {
158             croak( "Gtk2::Ex::DBI constructor missing a complete sql definition!\n"
159             . "You either need to specify a pass_through key ( 'pass_through' )\n"
160             . "or BOTH a 'select' AND and a 'from' key\n" );
161             }
162             }
163            
164             if ( exists $self->{readonly} ) {
165             carp( "\n\n Gtk2::Ex::DBI option 'readonly' renamed to 'read_only' ...\n"
166             . "... Sorry about that ... done for consistancy.\n\n" );
167             $self->{read_only} = $self->{readonly};
168             }
169            
170             bless $self, $class;
171            
172             if ( $self->{data_lock_field} && ! $self->get_widget($self->{data_lock_field}) ) {
173             carp( "\n\n Gtk2::Ex::DBI created with a data_lock_field,\n"
174             . " but couldn't find a matching widget!\n"
175             . " You *need* a matching widget.\n"
176             . " Make it invisible if you don't want to see it.\n"
177             . " Patches to remove this requirement gladly accepted :)\n"
178             . " * * * DATA DRIVEN LOCKING DISABLED * * *\n\n" );
179             delete $self->{data_lock_field};
180             }
181            
182             # Set up combo box models
183             foreach my $combo ( keys %{$self->{combos}} ) {
184             $self->setup_combo( $combo );
185             }
186            
187             # ... and autocompletions
188             foreach my $autocompletion ( keys %{$self->{autocompletions}} ) {
189             $self->setup_autocompletion( $autocompletion );
190             }
191            
192             # Reconstruct sql object if needed
193             if ( $self->{sql_select} || $self->{table} || $self->{sql_where} || $self->{sql_order_by} ) {
194            
195             # Strip out SQL directives
196             if ( $self->{sql_select} ) {
197             $self->{sql_select} =~ s/^select //i;
198             }
199             if ( $self->{sql_table} ) {
200             $self->{sql_table} =~ s/^from //i;
201             }
202             if ( $self->{sql_where} ) {
203             $self->{sql_where} =~ s/^where //i;
204             }
205             if ( $self->{sql_order_by} ) {
206             $self->{sql_order_by} =~ s/^order by //i;
207             }
208            
209             # Assemble things
210             my $sql = {
211             select => $self->{sql_select},
212             from => $self->{table},
213             where => $self->{sql_where},
214             order_by => $self->{sql_order_by}
215             };
216            
217             $self->{sql} = $sql;
218            
219             $legacy_warnings .= " - use the new sql object for the SQL string\n";
220            
221             }
222            
223             # Set the table name to use for GUI errors
224             if ( ! $self->{friendly_table_name} ) {
225             $self->{friendly_table_name} = $self->{sql}->{from};
226             }
227            
228             # Primary Key - oldest
229             if ( ref $req eq 'HASH' ) {
230             if ( exists $$req{primarykey} ) {
231             push @{$self->{primary_keys}}, $$req{primarykey};
232             $legacy_warnings .= " - primarykey pushed onto primary_keys array\n";
233             }
234            
235             # Primary Key - 2nd oldest
236             if ( exists $$req{primary_key} ) {
237             push @{$self->{primary_keys}}, $$req{primary_key};
238             $legacy_warnings .= " - primary_key pushed onto primary_keys array\n";
239             }
240            
241             }
242            
243             if ( $legacy_warnings || $self->{legacy_mode} ) {
244             carp ( "**** Gtk2::Ex::DBI starting in legacy mode ***\n"
245             . "While quite some effort has gone into supporting this, it would be wise to take action now.\n"
246             . "Warnings triggered by your request:\n$legacy_warnings\n" );
247             }
248            
249             # NOTE The code below that constructs the widgets hash has been copied into axis::form_editor
250             # Please make sure any changes here also get reflected there
251            
252             # TODO Should we move this code to a central location instead of having 2 copies?
253            
254             $self->{server} = $self->{dbh}->get_info( 17 );
255            
256             # Some PostGreSQL stuff - DLB
257             if ( $self->{server} =~ /postgres/i ) {
258             if ( ! $self->{search_path} ) {
259             if ( $self->{schema} ) {
260             $self->{search_path} = $self->{schema} . ",public";
261             } else {
262             $self->{search_path} = "public";
263             }
264             }
265             my $sth = $self->{dbh}->prepare ( "SET search_path to " . $self->{search_path} );
266             eval {
267             $sth->execute or die $self->{dbh}->errstr;
268             };
269             if ( $@ ) {
270             carp( "Failed to set search_path to " . $self->{search_path}
271             . " for a Postgres database. I'm not sure what the implications of this are. Postgres users, please report ...\n" );
272             }
273             }
274            
275             if ( $self->{widgets} && ! $self->{sql}->{select} && ! $self->{sql}->{pass_through} ) {
276            
277             # Reconstruct SQL select string if we've got a 'widgets' hash but no SQL select
278            
279             $self->{sql}->{select} = "";
280            
281             foreach my $fieldname ( keys %{$self->{widgets}} ) {
282             if ( $self->{widgets}->{$fieldname}->{sql_fieldname} ) {
283             # Support for aliases
284             $self->{sql}->{select} .= " $self->{widgets}->{$fieldname}->{sql_fieldname} as $fieldname";
285             } else {
286             # Otherwise just use the default fieldname
287             $self->{sql}->{select} .= " $fieldname";
288             }
289             $self->{sql}->{select} .= ",";
290             }
291            
292             chop( $self->{sql}->{select} );
293            
294             } elsif ( $self->{sql}->{select} && $self->{sql}->{select} !~ /[\*|%]/ ) {
295            
296             # Construct a widgets hash from the select string
297            
298             # TODO: Breaks with new-line characters OR ( excess )leading spaces
299            
300             foreach my $fieldname ( split( / *, */, $self->{sql}->{select} ) ) {
301             if ( $fieldname =~ m/ as /i ) {
302             my ( $sql_fieldname, $alias ) = split( / as /i, $fieldname );
303             $self->{widgets}->{$alias} = { sql_fieldname => $sql_fieldname };
304             } else {
305             if ( ! exists $self->{widgets}->{$fieldname} ) {
306             $self->{widgets}->{$fieldname} = {};
307             }
308             }
309             }
310            
311             } else {
312            
313             # If we're using a wildcard SQL select or a pass-through, then we use the fieldlist
314             # to construct the widgets hash
315            
316             my $sth;
317            
318             eval {
319             if ( exists $self->{sql}->{pass_through} ) {
320             $sth = $self->{dbh}->prepare( $self->{sql}->{pass_through} )
321             || croak( $self->{dbh}->errstr );
322             } else {
323             $sth = $self->{dbh}->prepare(
324             "select " . $self->{sql}->{select} . " from " . $self->{sql}->{from} . " where 0=1")
325             || croak( $self->{dbh}->errstr );
326             }
327             };
328            
329             if ( $@ ) {
330             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
331             title => "Error in Query!",
332             icon => "error",
333             text => "Database Server Says:\n\n$@"
334             );
335             return FALSE;
336             }
337            
338             eval {
339             $sth->execute || croak( $self->{dbh}->errstr );
340             };
341            
342             if ( $@ ) {
343             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
344             title => "Error in Query!",
345             icon => "error",
346             text => "Database Server Says:\n\n$@"
347             );
348             return FALSE;
349             }
350            
351             foreach my $fieldname ( @{$sth->{'NAME'}} ) {
352             if ( ! $self->{widgets}->{$fieldname} ) {
353             $self->{widgets}->{$fieldname} = {};
354             }
355             }
356            
357             $sth->finish;
358            
359             }
360            
361             # Construct a hash to map SQL fieldnames to widgets
362             foreach my $widget ( keys %{$self->{widgets}} ) {
363             $self->{sql_to_widget_map}->{$self->{widgets}->{$widget}->{sql_fieldname} || $widget} = $widget;
364             }
365            
366             my $sth;
367            
368             # Set the auto_incrementing flag if this value is not already defined.
369             if ( ! exists $self->{auto_incrementing} ) {
370             $self->{auto_incrementing} = 1;
371             }
372            
373             # Fetch primary key(s), but only if we haven't been passed one in the constructor
374             if ( ! $self->{primary_keys} ) {
375             eval {
376             $sth = $self->{dbh}->primary_key_info( undef, undef, $self->{sql}->{from} )
377             || die $self->{dbh}->errstr;
378             };
379             if ( ! $@ ) {
380             while ( my $row = $sth->fetchrow_hashref ) {
381             if ( $self->{debug} ) {
382             print "Gtk2::Ex::DBI bound to $self->{friendly_table_name} detected primary key item: $row->{COLUMN_NAME}\n";
383             }
384             push @{$self->{primary_keys}}, $row->{COLUMN_NAME};
385             if ( exists $row->{KEY_SEQ} ) {
386             if ( ! $row->{KEY_SEQ} ) {
387             if ( $self->{debug} ) {
388             print "This primary key is NOT auto-incrementing. I hope you know what you're doing ...\n";
389             }
390             $self->{auto_incrementing} = 0;
391             }
392             }
393             }
394             }
395             }
396            
397             # Fetch column_info for current table
398             eval {
399             if ( $self->{sql}->{pass_through} ) {
400             $sth = $self->{dbh}->column_info( undef, $self->{schema}, $self->{sql}->{pass_through}, '%' )
401             || die $self->{dbh}->errstr;
402             } else {
403             $sth = $self->{dbh}->column_info( undef, $self->{schema}, $self->{sql}->{from}, '%' )
404             || die $self->{dbh}->errstr;
405             }
406             };
407            
408             if ( $@ && ! $self->{primary_keys} ) {
409            
410             # Old versions of SQLite don't support column_info, but do support primary_key_info
411             # This might be the case for other database servers, so we'll try primary_key_info and
412             # see what happens ...
413            
414             eval {
415             $sth = $self->{dbh}->primary_key_info( undef, undef, $self->{sql}->{from} )
416             || die $self->{dbh}->errstr;
417             };
418            
419             if ( ! $@ ) {
420             # It works!
421             while ( my $row = $sth->fetchrow_hashref ) {
422             print "Gtk2::Ex::DBI bound to $self->{friendly_table_name} detected primary key item: $row->{COLUMN_NAME}\n";
423             push @{$self->{primary_keys}}, $row->{COLUMN_NAME};
424             }
425             }
426            
427             if ( ! $self->{primary_keys} ) {
428             # That's it. I give up. No read-write access for you!
429             if ( ! $self->{quiet} ) {
430             carp( "\nAll known methods of fetching the primary key from " . $self->{server} . " failed :("
431             . " ... If column_info fails ( eg multi-table queries ), then you MUST ...\n"
432             . " ... provide a primary_keys array in the constructor ...\n"
433             . " ... if you want to be able to update the recordset ...\n"
434             . " ... Defaulting to READ-ONLY mode ...\n" );
435             }
436             $self->{read_only} = TRUE;
437             }
438            
439             } else {
440            
441             while ( my $column_info_row = $sth->fetchrow_hashref ) {
442             # Loop through the list of columns from the database, and
443             # add only columns that we're actually dealing with
444             for my $fieldname ( keys %{$self->{sql_to_widget_map}} ) {
445             if ( $column_info_row->{COLUMN_NAME} eq ( $fieldname ) ) {
446             # Note that we want to store this against the fieldname and NOT the sql_fieldname
447             $self->{column_info}->{ $self->{sql_to_widget_map}->{$fieldname} } = $column_info_row;
448             last;
449             }
450             }
451             }
452            
453             }
454            
455             # Make sure we've got the primary key in the widgets hash and the sql_to_widget_map hash
456             # It will NOT be here unless it's been specified in the SQL select string or the widgets hash already
457             # Note that we test the sql_to_widget_map, and NOT the widgets hash, as we don't know what
458             # the widget might be called, but we DO know what the name of the key in the sql_to_widget_map
459             # should be
460             foreach my $primary_key ( @{$self->{primary_keys}} ) {
461             if ( ! exists $self->{sql_to_widget_map}->{ $primary_key } ) {
462             $self->{widgets}->{ $primary_key } = {};
463             $self->{sql_to_widget_map}->{ $primary_key } = $primary_key;
464             }
465             if ( $self->{dont_update_keys} ) {
466             $self->{widgets}->{ $primary_key }->{dont_update} = 1;
467             }
468             }
469            
470             # If things failed above, we mightn't have a $sth to finish, so
471             # check we do first ...
472             if ( $sth ) {
473             $sth->finish;
474             }
475            
476             if ( ! $self->{skip_query} ) {
477             $self->query;
478             }
479            
480             # We connect a few little goodies to various widgets ...
481            
482             # - Connect our 'changed' method to whatever signal each widget emits when it's 'changed'
483            
484             # - Gtk's ComboBoxEntry has a bug where it only registers a change and set's the currect iter if
485             # the combo box functionality is used. If the Entry functionality is used ( ie someone types a
486             # string that matches one in the list ), NOTHING is registered, and the active iter is not set.
487             # We *NEED* to work around this until the bug is fixed, otherwise ComboBoxEntrys are horribly broken.
488             # Therefore we connect the sub set_active_iter_for_broken_combo_box to the on_focus_out event.
489            
490             # See http://bugzilla.gnome.org/show_bug.cgi?id=156017
491             # Note that while the above bug page shows this bug as being 'FIXED', I've yet to see this
492             # fix materialise in Gtk2 - when it does I will limit our work-around to those affected
493             # versions of Gtk2.
494            
495             # - Use the populate-popup signal of Gtk2::Entry widgets to add the 'find' menu item
496            
497             # - Connect to the 'key-press-event' signal of various widgets to move the focus along
498             # ( ie as if the TAB key was pressed )
499            
500             # We also keep an array of widgets and signal ids so that we can disconnect all signal handlers
501             # and cleanly destroy ourselves when requested
502            
503             # We also set up input / output formatters based on widget ( ie $self->{widgets} )
504            
505             # Set up some defaults for different widget types
506             foreach my $fieldname ( keys %{$self->{widgets}} ) {
507            
508             # Get hold of the widget def ...
509             my $widget_def = $self->{widgets}->{$fieldname};
510            
511             if ( exists $widget_def->{number} ) {
512            
513             # Properties of the number format:
514             # - decimals - number of decimal places
515             # - decimal_fill - whether to pad decimals out to the number of decimals
516             # - separate_thousands - whether to separate thousands groups with a comma
517             # - currency - whether to apply currency formatting
518            
519             # Set some defaults for properties that haven't been specified ...
520            
521             if ( ! exists $widget_def->{number}->{decimal_fill} ) {
522             $widget_def->{number}->{decimal_fill} = TRUE;
523             }
524            
525             if ( ! exists $widget_def->{number}->{separate_thousands} ) {
526             $widget_def->{number}->{separate_thousands} = TRUE;
527             }
528            
529             # If this is a currency widget, default to 2 decial places
530             if ( exists $widget_def->{currency} && $widget_def->{currency} && ! exists $widget_def->{number}->{decimals} ) {
531             $widget_def->{number}->{decimals} = 2;
532             }
533            
534             }
535            
536             my @widgets;
537             my $this_widget = $self->get_widget( $fieldname );
538            
539             if ( $this_widget ) {
540            
541             push @widgets, $this_widget;
542            
543             } else {
544            
545             # TODO Remove dodgy split-widget support and replace with custom widgets
546             # Check for split-widget widgets ... at present, TimeSpinners
547             foreach my $type qw / hh mm ss / {
548             $this_widget = $self->get_widget( $fieldname . "_" . $type );
549             if ( $this_widget ) {
550             push @widgets, $this_widget;
551             }
552             }
553            
554             }
555            
556             # Now we've either got nothing, or 1 widget in an array, or a number of widgets in an array
557             foreach my $widget ( @widgets ) {
558            
559             my @signals;
560             my $type = (ref $widget);
561            
562             # To aid in debugging, I first push these onto a temporary array ...
563             if ( $type eq "Gtk2::Calendar" ) {
564            
565             push @signals, $widget->signal_connect_after(
566             day_selected => sub { $self->changed( $fieldname ) } );
567            
568             } elsif ( $type eq "Gtk2::ToggleButton" ) {
569            
570             push @signals, $widget->signal_connect_after(
571             toggled => sub { $self->changed( $fieldname ) } );
572            
573             } elsif ( $type eq "Gtk2::TextView" ) {
574            
575             # In this case, we don't connect to the widget, but to the widget's buffer ...
576             # ... so we swap the buffer into $widget, so we can disconnect our signal later
577             $widget = $widget->get_buffer;
578             push @signals, $widget->signal_connect_after(
579             changed => sub { $self->changed( $fieldname ) } );
580            
581             } elsif ( $type eq "Gtk2::ComboBoxEntry" ) {
582            
583             push @signals, $widget->signal_connect_after(
584             changed => sub { $self->changed( $fieldname ) } );
585            
586             # Append our work-around for broken combo directly to the objects_and_signals array ...
587             # ... We can't use the code below to append more than 1 widget at a time
588             my $child_widget = $widget->get_child;
589            
590             my $signal = $child_widget->signal_connect_after(
591             changed => sub { $self->set_active_iter_for_broken_combo_box($widget) } );
592            
593             if ( $self->{debug} ) {
594             warn "Remembering object / signal pair for later disconnection ...\n"
595             . " Field: $fieldname\n"
596             . " Widget: $child_widget\n"
597             . " Signal: $signal\n\n";
598             }
599            
600             push @{$self->{objects_and_signals}},
601             [
602             $child_widget,
603             $signal
604             ];
605            
606             # Trigger 2 tab-forward events;
607             # 1 to get to the combo part ( ie the child's parent widget )
608             # and 1 to get to the next widget
609             $signal = $child_widget->signal_connect(
610             'activate' => sub {
611             $self->{window}->child_focus('tab-forward');
612             $self->{window}->child_focus('tab-forward');
613             } );
614            
615             if ( $self->{debug} ) {
616             warn "Remembering object / signal pair for later disconnection ...\n"
617             . " Field: $fieldname\n"
618             . " Widget: $child_widget\n"
619             . " Signal: $signal\n\n";
620             }
621            
622             push @{$self->{objects_and_signals}},
623             [
624             $child_widget,
625             $signal
626             ];
627            
628             # We also want a right-click menu for a Combo's child ( entry )
629             $signal = $child_widget->signal_connect_after(
630             'populate-popup' => sub { $self->build_right_click_menu(@_) } );
631            
632             if ( $self->{debug} ) {
633             warn "Remembering object / signal pair for later disconnection ...\n"
634             . " Field: $fieldname\n"
635             . " Widget: $child_widget\n"
636             . " Signal: $signal\n\n";
637             }
638             push @{$self->{objects_and_signals}},
639             [
640             $child_widget,
641             $signal
642             ];
643            
644             } elsif ( $type eq "Gtk2::CheckButton" ) {
645            
646             push @signals, $widget->signal_connect_after(
647             toggled => sub { $self->changed( $fieldname ) } );
648            
649             } elsif ( $type eq "Gtk2::Entry" ) {
650            
651             push @signals, $widget->signal_connect_after(
652             changed => sub { $self->changed( $fieldname ) } );
653             push @signals, $widget->signal_connect_after(
654             'populate-popup' => sub { $self->build_right_click_menu(@_) } );
655             push @signals, $widget->signal_connect(
656             'activate' => sub { $self->{window}->child_focus('tab-forward') } );
657            
658             } elsif ( $type eq "Gtk2::SpinButton" ) {
659            
660             push @signals, $widget->signal_connect_after(
661             changed => sub { $self->changed( $fieldname ) } );
662             push @signals, $widget->signal_connect_after(
663             'populate-popup' => sub { $self->build_right_click_menu(@_) } );
664             push @signals, $widget->signal_connect(
665             'key-press-event' => sub { $self->process_entry_keypress(@_) } );
666            
667             } else {
668            
669             push @signals, $widget->signal_connect_after(
670             changed => sub { $self->changed( $fieldname ) } );
671            
672             }
673             # ... and then warn() some info about what we're doing, and also append
674             # the objects and signals to the *real* list
675             foreach my $signal ( @signals ) {
676             if ( $self->{debug} ) {
677             warn "Remembering object / signal pair for later disconnection ...\n"
678             . " Field: $fieldname\n"
679             . " Widget: $widget\n"
680             . " Signal: $signal\n\n";
681             }
682             push @{$self->{objects_and_signals}},
683             [
684             $widget,
685             $signal
686             ];
687             }
688             }
689             }
690            
691             $self->{spinner} = $self->get_widget( $self->{record_spinner} );
692            
693             if ( $self->{spinner} ) {
694            
695             $self->{record_spinner_value_changed_signal}
696             = $self->{spinner}->signal_connect_after( value_changed => sub {
697             $self->{spinner}->signal_handler_block( $self->{record_spinner_value_changed_signal} );
698             $self->move( undef, $self->{spinner}->get_text - 1 );
699             $self->{spinner}->signal_handler_unblock( $self->{record_spinner_value_changed_signal} );
700             return TRUE;
701             }
702             );
703            
704             push @{$self->{objects_and_signals}},
705             [
706             $self->{spinner},
707             $self->{record_spinner_value_changed_signal}
708             ];
709            
710             }
711            
712             # Check recordset status when window is destroyed
713             push @{$self->{objects_and_signals}},
714             [
715             $self->{window},
716             $self->{window}->signal_connect( delete_event => sub {
717             if ( $self->{changed} ) {
718             my $answer = TRUE;
719             if ( ! $self->{auto_apply} ) {
720             my $answer = Gtk2::Ex::Dialogs::Question->new_and_run(
721             title => "Apply changes to " . $self->{friendly_table_name} . " before closing?",
722             icon => "question",
723             text => $self->{custom_changed_text} ? $self->{custom_changed_text} :
724             "There are changes to the current record ( " . $self->{friendly_table_name} . " )\n"
725             . "that haven't yet been applied. Would you like to apply them before closing the form?"
726             );
727             }
728             # We return FALSE to allow the default signal handler to
729             # continue with destroying the window - all we wanted to do was check
730             # whether to apply records or not
731             if ( $answer ) {
732             if ( $self->apply ) {
733             return FALSE;
734             } else {
735             # ie don't allow the form to close if there was an error applying
736             return TRUE;
737             }
738             } else {
739             return FALSE;
740             }
741             }
742             } )
743             ];
744            
745             $self->{constructor_done} = TRUE;
746            
747             $self->set_record_spinner_range;
748            
749             if ( $self->{debug} ) {
750             print " ... Gtk2::Ex::DBI version $Gtk2::Ex::DBI::VERSION successfully initialised.\n\n";
751             }
752            
753             return $self;
754            
755             }
756              
757             sub destroy_signal_handlers {
758            
759             my $self = shift;
760            
761             foreach my $set ( @{$self->{objects_and_signals}} ) {
762             if ( $self->{debug} ) {
763             warn "Disconnecting object / signal pair:\n"
764             . " Object: $$set[0]\n"
765             . " Signal: $$set[1]\n";
766             }
767             $$set[0]->signal_handler_disconnect( $$set[1] );
768             if ( $self->{debug} ) {
769             warn "\n\n";
770             }
771             }
772            
773             }
774              
775             sub destroy_self {
776            
777             undef $_[0];
778            
779             }
780              
781             sub destroy {
782            
783             my $self = shift;
784            
785             $self->destroy_signal_handlers;
786             $self->destroy_self;
787            
788             }
789              
790             sub fieldlist {
791            
792             # Provide legacy fieldlist method
793            
794             my $self = shift;
795            
796             return keys %{$self->{widgets}};
797            
798             }
799              
800             sub query {
801            
802             # Query / Re-query
803            
804             my ( $self, $where_object ) = @_;
805            
806             # In version 2.x, $where_object *should* be a hash, containing the keys:
807             # - where
808             # - bind_values
809            
810             # Update database from current hash if necessary
811             if ( $self->{changed} ) {
812            
813             my $answer = TRUE;
814            
815             if ( ! $self->{auto_apply} ) {
816             $answer = Gtk2::Ex::Dialogs::Question->ask(
817             title => "Apply changes to " . $self->{friendly_table_name} . " before querying?",
818             icon => "question",
819             text => "There are outstanding changes to the current record ( "
820             . $self->{friendly_table_name} . " )."
821             . " Do you want to apply them before running a new query?"
822             );
823             }
824            
825             if ( $answer ) {
826             if ( ! $self->apply ) {
827             return FALSE; # Apply method will already give a dialog explaining error
828             }
829             }
830            
831             }
832            
833             # Execute any before_query code
834             if ( $self->{before_query} ) {
835             if ( ! $self->{before_query}( $where_object ) ) {
836             return FALSE;
837             }
838             }
839            
840             # If we're using a stored procedure, we don't keep a keyset - there's not much point.
841             # We simply pull all records at once ... which we do now.
842             # We can't wait for move() to call fetch_new_slice() because move() wants to know
843             # how many records there are ( which usually comes from the keyset, which we're not fetching
844             # here ). So anyway, we need to do the query here.
845            
846             my ( $query_start_time, $query_end_time );
847            
848             $query_start_time = Time::HiRes::gettimeofday;
849            
850             if ( exists $self->{sql}->{pass_through} ) {
851            
852             eval {
853             $self->{records} = $self->{dbh}->selectall_arrayref (
854             $self->{sql}->{pass_through}, {Slice=>{}}
855             ) || croak( "Error in SQL:\n\n" . $self->{sql}->{pass_through} );
856             };
857            
858             if ( $@ ) {
859             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
860             title => "Error in Query!",
861             icon => "error",
862             text => "Database Server Says:\n\n$@"
863             );
864             return FALSE;
865             }
866            
867             } else {
868            
869             # Deal with legacy mode - the query method used to accept an optional where clause
870             if ( $where_object ) {
871             if ( ref( $where_object ) ne "HASH" ) {
872             # Legacy mode
873             # Strip 'where ' out of clause
874             if ( $where_object ) {
875             $where_object =~ s/^where //i;
876             }
877             # Transfer new sql_where clause if defined
878             $self->{sql}->{where} = $where_object;
879             # Also remove any bound values if called in legacy mode
880             $self->{sql}->{bind_values} = undef;
881             } else {
882             # NOT legacy mode
883             if ( $where_object->{where} ) {
884             $self->{sql}->{where} = $where_object->{where};
885             }
886             if ( $where_object->{bind_values} ) {
887             $self->{sql}->{bind_values} = $where_object->{bind_values};
888             }
889             }
890             }
891            
892             $self->{keyset_group} = undef;
893             $self->{slice_position} = undef;
894            
895             # Get an array of primary keys
896             my $sth;
897            
898             my $local_sql = "select " . join( ", ", @{$self->{primary_keys}} )
899             . " from " . $self->{sql}->{from};
900            
901             # Add where clause if defined
902             if ( $self->{sql}->{where} ) {
903             $local_sql .= " where " . $self->{sql}->{where};
904             }
905            
906             # Add order by clause of defined
907             if ( $self->{sql}->{order_by} ) {
908             $local_sql .= " order by " . $self->{sql}->{order_by};
909             }
910            
911             eval {
912             $sth = $self->{dbh}->prepare( $local_sql )
913             || croak( $self->{dbh}->errstr );
914             };
915            
916             if ( $@ ) {
917             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
918             title => "Error in Query!",
919             icon => "error",
920             text => "Database Server Says:\n\n$@"
921             );
922             if ( $self->{debug} ) {
923             croak( "Gtk2::Ex::DBI::query died with the SQL:\n\n$local_sql\n" );
924             }
925             return FALSE;
926             }
927            
928             eval {
929             if ( $self->{sql}->{bind_values} ) {
930             $sth->execute( @{$self->{sql}->{bind_values}} ) || croak( $self->{dbh}->errstr );
931             } else {
932             $sth->execute || croak( $self->{dbh}->errstr );
933             }
934             };
935            
936             if ( $@ ) {
937             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
938             title => "Error in Query!",
939             icon => "error",
940             text => "Database Server Says:\n\n$@"
941             );
942             if ( $self->{debug} ) {
943             $sth->finish;
944             croak( "Gtk2::Ex::DBI::query died with the SQL:\n\n$local_sql\n" );
945             } else {
946             return FALSE;
947             }
948             }
949            
950             $self->{keyset} = ();
951             $self->{records} = ();
952            
953             while ( my @row = $sth->fetchrow_array ) {
954             my $key_no = 0;
955             my @keys;
956             foreach my $primary_key ( @{$self->{primary_keys}} ) {
957             push @keys, $row[$key_no];
958             $key_no ++;
959             }
960             push @{$self->{keyset}}, @keys;
961             }
962            
963             $sth->finish;
964            
965             }
966            
967             $query_end_time = Time::HiRes::gettimeofday;
968            
969             $self->{query_execution_time} = $query_end_time - $query_start_time;
970            
971             $self->move( 0, 0 );
972            
973             $self->set_record_spinner_range;
974            
975             return TRUE;
976            
977             }
978              
979             sub insert {
980            
981             # Inserts a record at the end of the *in-memory* recordset.
982            
983             my $self = shift;
984             my $newposition = $self->count; # No need to add one, as the array starts at zero.
985            
986             # Open RecordSpinner range
987             if ( $self->{spinner} ) {
988             $self->{spinner}->signal_handler_block( $self->{record_spinner_value_changed_signal} );
989             $self->{spinner}->set_range( 1, $self->count + 1 );
990             $self->{spinner}->signal_handler_unblock( $self->{record_spinner_value_changed_signal} );
991             }
992            
993             if ( ! $self->move( 0, $newposition ) ) {
994             warn "Insert failed ... probably because the current record couldn't be applied\n";
995             return FALSE;
996             }
997            
998             # Assemble new record and put it in place
999             $self->{records}[$self->{slice_position}] = $self->assemble_new_record;
1000            
1001             # Finally, paint the current recordset onto the widgets
1002             # This is the 2nd time this is called in this sub ( 1st from $self->move ) but we need to do it again to paint the default values
1003             $self->paint;
1004            
1005             return TRUE;
1006            
1007             }
1008              
1009             sub assemble_new_record {
1010            
1011             # This sub assembles a new hash record and sets default values
1012            
1013             my $self = shift;
1014            
1015             my $new_record;
1016            
1017             # First, we create fields with default values from the database ...
1018             foreach my $fieldname ( keys %{$self->{column_info}} ) {
1019             # COLUMN_DEF is DBI speak for 'column default'
1020             my $default = $self->{column_info}->{$fieldname}->{COLUMN_DEF};
1021             if ( $default && $self->{server} =~ /microsoft/i ) {
1022             $default = $self->parse_sql_server_default( $default );
1023             }
1024             $new_record->{$fieldname} = $default;
1025             }
1026            
1027             # ... and then we set user-defined defaults
1028             foreach my $fieldname ( keys %{$self->{defaults}} ) {
1029             $new_record->{$fieldname} = $self->{defaults}->{$fieldname};
1030             }
1031            
1032             # Finally, set the 'inserting' flag ( but don't set the changed flag until the user actually changes something )
1033             $self->{inserting} = 1;
1034            
1035             return $new_record;
1036            
1037             }
1038              
1039             sub count {
1040            
1041             # Counts the records ( items in the keyset array ).
1042             # Note that this returns the REAL record count, and keep in mind that the first record is at position 0.
1043            
1044             my $self = shift;
1045            
1046             my $count_this;
1047            
1048             if ( exists $self->{sql}->{pass_through} ) {
1049             $count_this = "records";
1050             } else {
1051             $count_this = "keyset";
1052             }
1053            
1054             if ( ref($self->{$count_this}) eq "ARRAY" ) {
1055             return scalar @{$self->{$count_this}};
1056             } else {
1057             return 0;
1058             }
1059            
1060             }
1061              
1062             sub paint {
1063            
1064             my $self = shift;
1065            
1066             #carp( $self->{friendly_table_name} . " painting ..." );
1067            
1068             # Set the changelock so we don't trigger more changes
1069             $self->{changelock} = TRUE;
1070            
1071             foreach my $fieldname ( keys %{$self->{widgets}} ) {
1072             my $data = $self->{records}[$self->{slice_position}]->{$fieldname};
1073             $self->set_widget_value(
1074             $fieldname,
1075             $data
1076             );
1077             }
1078            
1079             # Paint calculated fields
1080             $self->paint_calculated;
1081            
1082             # Execute external on_current code
1083             # ( only if we have been constructed AND returned to calling code 1st - otherwise references to us won't work )
1084             if ( $self->{on_current} && $self->{constructor_done} ) {
1085             $self->{on_current}();
1086             }
1087            
1088             # Unlock the changelock
1089             $self->{changelock} = FALSE;
1090            
1091             }
1092              
1093             sub move {
1094            
1095             # Moves to the requested position, either as an offset from the current position,
1096             # or as an absolute value. If an absolute value is given, it overrides the offset.
1097             # If there are changes to the current record, these are applied to the Database Server first.
1098             # Returns TRUE ( 1 ) if successful, FALSE ( 0 ) if unsuccessful.
1099            
1100             my ( $self, $offset, $absolute ) = @_;
1101            
1102             # Update database from current hash if necessary
1103             if ( $self->{changed} ) {
1104             my $answer = TRUE;
1105             if ( ! $self->{auto_apply} ) {
1106             $answer = Gtk2::Ex::Dialogs::Question->ask(
1107             title => "Apply changes to " . $self->{friendly_table_name} . " before moving?",
1108             icon => "question",
1109             text => "There are outstanding changes to the current record ( "
1110             . $self->{friendly_table_name} . " )."
1111             . " Do you want to apply them before moving to a different record?"
1112             );
1113             }
1114             if ( $answer ) {
1115             if ( ! $self->apply ) {
1116             # Update failed. If RecordSpinner exists, set it to the current position PLUS ONE.
1117             if ( $self->{spinner} ) {
1118             $self->{spinner}->signal_handler_block( $self->{record_spinner_value_changed_signal});
1119             $self->{spinner}->set_value( $self->position + 1 );
1120             $self->{spinner}->signal_handler_block( $self->{record_spinner_value_changed_signal});
1121             }
1122             return FALSE;
1123             }
1124             } else {
1125             $self->{changed} = FALSE;
1126             }
1127             }
1128            
1129             my ( $new_keyset_group, $new_position);
1130            
1131             if ( defined $absolute ) {
1132             $new_position = $absolute;
1133             } else {
1134             $new_position = ( $self->position || 0 ) + $offset;
1135             # Make sure we loop around the recordset if we go out of bounds.
1136             if ( $new_position < 0 ) {
1137             $new_position = $self->count - 1;
1138             } elsif ( $new_position > $self->count - 1 ) {
1139             $new_position = 0;
1140             }
1141             }
1142            
1143             # Check if we now have a sane $new_position.
1144             # Some operations ( insert, then revert part-way through ... or move backwards when there are no records ) can cause this.
1145             if ( $new_position < 0 ) {
1146             $new_position = 0;
1147             }
1148            
1149             # Skip this bit for sps
1150             if ( ! exists $self->{sql}->{pass_through} ) {
1151            
1152             # Check if we need to roll to another slice of our recordset
1153             $new_keyset_group = int($new_position / $self->{apeture} );
1154            
1155             if (defined $self->{slice_position}) {
1156             if ( $self->{keyset_group} != $new_keyset_group ) {
1157             $self->{keyset_group} = $new_keyset_group;
1158             $self->fetch_new_slice;
1159             };
1160             } else {
1161             $self->{keyset_group} = $new_keyset_group;
1162             $self->fetch_new_slice;
1163             }
1164            
1165             $self->{slice_position} = $new_position - ( $new_keyset_group * $self->{apeture} );
1166            
1167             } else {
1168            
1169             $self->{slice_position} = $new_position;
1170            
1171             }
1172            
1173             if ( $self->{data_lock_field} ) {
1174             if ( $self->{records}[$self->{slice_position}]->{$self->{data_lock_field}} ) {
1175             $self->{data_lock} = TRUE;
1176             } else {
1177             $self->{data_lock} = FALSE;
1178             }
1179             }
1180            
1181             $self->record_status_label_set;
1182            
1183             $self->paint;
1184            
1185             # Set the RecordSpinner
1186             if ( $self->{spinner} ) {
1187             $self->{spinner}->signal_handler_block( $self->{record_spinner_value_changed_signal} );
1188             $self->{spinner}->set_value( $self->position + 1 );
1189             $self->{spinner}->signal_handler_unblock( $self->{record_spinner_value_changed_signal} );
1190             }
1191            
1192             return TRUE;
1193            
1194             }
1195              
1196             sub fetch_new_slice {
1197            
1198             # Fetches a new 'slice' of records ( based on the aperture size )
1199            
1200             my $self = shift;
1201            
1202             # Get max value for the loop ( not sure if putting a calculation inside the loop def slows it down or not )
1203             my $lower = $self->{keyset_group} * $self->{apeture};
1204             my $upper = ( ($self->{keyset_group} + 1) * $self->{apeture} ) - 1;
1205            
1206             # Don't try to fetch records that aren't there ( at the end of the recordset )
1207             my $keyset_count = $self->count; # So we don't keep running $self->count...
1208            
1209             if ( ( $keyset_count == 0 ) || ( $keyset_count == $lower ) ) {
1210            
1211             # If $keyset_count == 0 , then we don't have any records.
1212            
1213             # If $keyset_count == $lower, then the 1st position ( lower ) is actually out of bounds
1214             # because our keyset STARTS AT ZERO.
1215            
1216             # Either way, there are no records, so we're inserting ...
1217            
1218             # First, we have to delete anything in $self->{records}
1219             # This would *usually* just be overwritten if we actually got a keyset above,
1220             # but since we didn't, we have to make sure there's nothing left
1221             $self->{records} = ();
1222            
1223             # Now create a new record ( with defaults and insertion marker )
1224            
1225             # Note that we don't set the changed marker at this point, so if the user starts entering data,
1226             # this is treated as an inserted record. However if the user doesn't enter data, and does something else
1227             # ( eg another query ), this record will simply be discarded ( changed marker = 0 )
1228            
1229             # Keep in mind that this doens't take into account other requirements for a valid record ( eg foreign keys )
1230             push @{$self->{records}}, $self->assemble_new_record;
1231            
1232             } else {
1233            
1234             # Reset 'inserting' flag
1235             $self->{inserting} = 0;
1236            
1237             if ( $upper > $keyset_count - 1 ) {
1238             $upper = $keyset_count - 1;
1239             }
1240            
1241             my $key_list;
1242            
1243             # Assemble query
1244             my $local_sql = "select " . $self->{sql}->{select};
1245            
1246             # Do we have an SQL wildcard ( * or % ) in the select string?
1247             if ( $self->{sql}->{select} !~ /[\*|%]/ ){
1248             # No? In that case, check we have the primary keys; append them if we don't - we need them
1249             $local_sql .= ", " . join( ', ', @{$self->{primary_keys}} );
1250             }
1251            
1252             $local_sql .= " from " . $self->{sql}->{from}
1253             . " where ( " . join( ', ', @{$self->{primary_keys}} ) . " ) in ( ";
1254            
1255             # The where clause we're trying to build should look like:
1256             #
1257             # where ( key_1, key_2, key_3 ) in
1258             # (
1259             # ( 1, 5, 8 ),
1260             # ( 2, 4, 9 )
1261             # )
1262             # ... etc ... assuming we have a primary key spanning 3 columns
1263            
1264             for ( my $counter = $lower; $counter < $upper+1; $counter++ ) {
1265             $local_sql .= " ( " . join( ",", $self->{keyset}[$counter] ) . " ),";
1266             #$key_list .= " " . $self->{keyset}[$counter] . ",";
1267             }
1268            
1269             # Chop off trailing comma
1270             chop( $local_sql );
1271            
1272             $local_sql .= " )";
1273            
1274             if ( $self->{sql}->{order_by} ) {
1275             $local_sql .= " order by " . $self->{sql}->{order_by};
1276             }
1277            
1278             eval {
1279             $self->{records} = $self->{dbh}->selectall_arrayref (
1280             $local_sql, {Slice=>{}}
1281             ) || croak( $self->{dbh}->errstr . "\n\nLocal SQL was:\n$local_sql" );
1282             };
1283            
1284             if ( $@ ) {
1285             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1286             title => "Error fetching record slice!",
1287             icon => "error",
1288             text => "Database server says:\n\n" . $@
1289             );
1290             return FALSE;
1291             }
1292            
1293             return TRUE;
1294            
1295             }
1296            
1297             }
1298              
1299             sub apply {
1300            
1301             # Applys the data from the current form back to the Database Server.
1302             # Returns TRUE ( 1 ) if successful, FALSE ( 0 ) if unsuccessful.
1303            
1304             my $self = shift;
1305            
1306             if ( $self->{read_only} ) {
1307             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1308             title => "Read Only!",
1309             icon => "authentication",
1310             text => "Sorry. This form is open\nin read-only mode!"
1311             );
1312             return FALSE;
1313             }
1314            
1315             if ( $self->{data_lock} && $self->{data_lock} == TRUE ) {
1316             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1317             title => "Data Lock!",
1318             icon => "authentication",
1319             text => "Sorry. This record has been locked to prevent further changes!\n"
1320             . "This usually occurs to ensure data integrity,\n"
1321             . "or prevent unwanted editing"
1322             );
1323             return FALSE;
1324             }
1325            
1326             if ( $self->{before_apply} ) {
1327             if ( ! $self->{before_apply}() ) {
1328             return FALSE;
1329             }
1330             }
1331            
1332             my @fieldlist = ();
1333             my @bind_values = ();
1334            
1335             my $placeholders; # We need to append to the placeholders while we're looping through fields, so we know how many fields we actually have
1336            
1337             foreach my $fieldname ( keys %{$self->{widgets}} ) {
1338            
1339             my $widget_definition = $self->{widgets}->{$fieldname};
1340            
1341             if ( $self->{debug} ) {
1342             print "Processing field $fieldname ...\n"
1343             . Dumper( $widget_definition ) . "\n";
1344             }
1345            
1346             # Support for aliases
1347             my $sql_fieldname = $widget_definition->{sql_fieldname} || $fieldname;
1348            
1349             if ( $self->{inserting} && $widget_definition->{dont_insert} ) {
1350            
1351             # ie if we're inserting and the current widget definition says we
1352             # shouldn't insert values into this field
1353             next;
1354            
1355             } elsif ( ! $self->{inserting} && $widget_definition->{dont_update} ) {
1356            
1357             # ie if we're updating and the current widget definition says we
1358             # shouldn't update values in this field
1359             next;
1360            
1361             }
1362            
1363             # # TODO Remove dodged-up multi-widget support
1364             # my $widget = $self->get_widget( $fieldname ) || $self->get_widget( $fieldname . "_" . "hh" );
1365             #
1366             # # TODO Document read-only labels
1367             # if ( defined $widget && ref $widget ne "Gtk2::Label" ) { # Labels are read-only
1368             push @fieldlist, $sql_fieldname;
1369             push @bind_values, $self->get_widget_value( $fieldname );
1370             # }
1371            
1372             }
1373            
1374             my $update_sql;
1375            
1376             if ( $self->{inserting} ) {
1377            
1378             $update_sql = "insert into " . $self->{sql}->{from} . " ( " . join( ",", @fieldlist, ) . " )"
1379             . " values ( " . "?," x ( @fieldlist - 1 ) . "? )";
1380            
1381             } else {
1382            
1383             $update_sql = "update " . $self->{sql}->{from} . " set " . join( "=?, ", @fieldlist ) . "=? where "
1384             . join( "=? and", @{$self->{primary_keys}} ) . "=?";
1385            
1386             foreach my $primary_key ( @{$self->{primary_keys}} ) {
1387             push @bind_values, $self->{records}[$self->{slice_position}]->{ $self->{sql_to_widget_map}->{$primary_key} };
1388             }
1389            
1390             }
1391            
1392             if ( $self->{debug} ) {
1393             print "Final SQL:\n\n$update_sql\n\n";
1394            
1395             my $counter = 0;
1396            
1397             for my $value ( @bind_values ) {
1398             no warnings 'uninitialized';
1399             print " " x ( 20 - length( $fieldlist[$counter] ) ) . $fieldlist[$counter] . ": $value\n";
1400             $counter ++;
1401             }
1402             }
1403            
1404             my $sth;
1405            
1406             # Evaluate the results of attempting to prepare the statement
1407             eval {
1408             $sth = $self->{dbh}->prepare( $update_sql )
1409             || die $self->{dbh}->errstr;
1410             };
1411            
1412             if ( $@ ) {
1413             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1414             title => "Error preparing statement to update recordset!",
1415             icon => "error",
1416             text => "Database server says:\n\n$@"
1417             );
1418             carp( "Error preparing statement to update recordset:\n\n$update_sql\n\n@bind_values\n" . $@ );
1419             }
1420            
1421             # Evaluate the results of the update.
1422             my $affected_rows;
1423             eval {
1424             $affected_rows = $sth->execute( @bind_values ) || die $self->{dbh}->errstr;
1425             };
1426            
1427             if ( $self->{debug} ) {
1428             no warnings 'uninitialized';
1429             print "apply() affected [$affected_rows] rows";
1430             }
1431            
1432             if ( $@ ) {
1433             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1434             title => "Error updating recordset!",
1435             icon => "error",
1436             text => "Database server says:\n\n" . $@
1437             );
1438             carp( "Error updating recordset:\n\n$update_sql\n\n@bind_values\n" . $@ . "\n" );
1439             }
1440            
1441             eval {
1442             $sth->finish;
1443             };
1444            
1445             # If this was an INSERT, we need to fetch the primary key value and apply it to the local slice,
1446             # and also append the primary key to the keyset
1447            
1448             if ( $self->{inserting} ) {
1449            
1450             if ( $self->{auto_incrementing} ) {
1451            
1452             # We only support a *single* primary key in the case of
1453             # an auto-incrementing setup. There shouldn't really
1454             # be any other options to cover ...
1455            
1456             my $new_key = $self->last_insert_id;
1457             my $primary_key = $self->{primary_keys}[0];
1458            
1459             $self->{records}[$self->{slice_position}]->{ $self->{sql_to_widget_map}->{$primary_key} } = $new_key;
1460            
1461             # Apply primary key to form ( if field exists )
1462             my $widget = $self->get_widget( $self->{sql_to_widget_map}->{$primary_key} );
1463             if ( $widget ) {
1464             $widget->set_text( $primary_key ); # Assuming the widget has a set_text method of course ... can't see when this wouldn't be the case
1465             }
1466            
1467             }
1468            
1469             }
1470            
1471             # SQL update successfull. Now apply update to local array.
1472             foreach my $fieldname ( keys %{$self->{widgets}} ) {
1473             my $widget = $self->get_widget( $fieldname );
1474             if ( defined $widget ) {
1475             $self->{records}[$self->{slice_position}]->{$fieldname} = $self->get_widget_value( $fieldname );
1476             }
1477             }
1478            
1479             if ( $self->{inserting} ) {
1480            
1481             # Note: For non-auto-incrementing primary keys, this MUST happen after we've copied values
1482             # from the GUI back to our in-memory slice ( ie immediately above )
1483            
1484             my @keys;
1485            
1486             foreach my $primary_key ( @{$self->{primary_keys}} ) {
1487             push @keys, $self->{records}[$self->{slice_position}]->{ $self->{sql_to_widget_map}->{$primary_key} };
1488             }
1489            
1490             push @{$self->{keyset}}, @keys;
1491            
1492             $self->{changelock} = FALSE;
1493             $self->set_record_spinner_range;
1494             $self->{changelock} = FALSE;
1495            
1496             }
1497            
1498             $self->{changed} = FALSE;
1499            
1500             $self->paint;
1501            
1502             # Execute external an_apply code
1503             if ( $self->{on_apply} ) {
1504            
1505             my $apply_info;
1506            
1507             foreach my $primary_key ( @{$self->{primary_keys}} ) {
1508             $apply_info->{primary_key} = $primary_key; # Legacy support. Single primary-key setups. DON'T break existing sane setups.
1509             $apply_info->{primary_keys}->{$primary_key} = $self->{records}[$self->{slice_position}]->{ $self->{sql_to_widget_map}->{$primary_key} };
1510             }
1511            
1512             if ( $self->{inserting} ) {
1513             $apply_info->{status} = "inserted";
1514             } else {
1515             $apply_info->{status} = "changed";
1516             }
1517            
1518             $self->{on_apply}($apply_info);
1519            
1520             }
1521            
1522             $self->record_status_label_set;
1523            
1524             $self->{inserting} = 0; # Reset this flag ( doesn't matter if we were inserting or not )
1525            
1526             return TRUE;
1527            
1528             }
1529              
1530             sub changed {
1531            
1532             # Sets the 'changed' flag, and update the RecordStatus indicator ( if there is one ).
1533            
1534             my ( $self, $fieldname ) = @_;
1535            
1536             if ( ! $self->{changelock} ) {
1537            
1538             if ( ! $self->{read_only} && ! $self->{data_lock} ) {
1539            
1540             if ( $self->{debug} ) {
1541             warn "Gtk2::Ex::DBI::changed triggered in object [" . $self->{friendly_table_name} . "] by field [$fieldname]\n";
1542             }
1543            
1544             my $recordstatus = $self->get_widget( $self->{status_label} );
1545            
1546             if ( $recordstatus ) {
1547             $recordstatus->set_markup( "Changed" );
1550             }
1551            
1552             if ( ! $self->{changed} ) {
1553            
1554             # If our changed flag is not already set, this is the 1st edit for this record
1555             # ( whether it's a new one or not )
1556            
1557             $self->{changed} = TRUE; # We need to set this now, otherwise recursion occurs
1558            
1559             # If we're inserting, scan for sequences
1560             if ( $self->{inserting} ) {
1561             foreach my $widget_name ( keys %{$self->{widgets}} ) {
1562             my $widget = $self->{widgets}->{$widget_name};
1563             if ( exists $widget->{sequence_sql} ) {
1564             my $sequence_dbh = exists $widget->{sequence_dbh} ? $widget->{sequence_dbh} : $self->{dbh};
1565             my $sth;
1566             eval {
1567             $sth = $sequence_dbh->prepare( $widget->{sequence_sql} )
1568             || die $sequence_dbh->errstr;
1569             $sth->execute()
1570             || die $sequence_dbh->errstr;
1571             };
1572             if ( $@ ) {
1573             carp( "Failed to select the next sequence:\n$@" );
1574             return FALSE;
1575             }
1576             if ( my @row = $sth->fetchrow_array ) {
1577             $self->set_widget_value( $widget_name, $row[0] );
1578             }
1579             }
1580             }
1581             }
1582            
1583             if ( $self->{on_initial_changed} ) {
1584             # Execute on_initial_changed code ( only for the *initial* change of recordset status )
1585            
1586             if ( ! $self->{on_initial_changed}() ) {
1587             $self->undo;
1588             return FALSE;
1589             }
1590             }
1591            
1592             }
1593            
1594             if ( $self->{on_changed} ) {
1595             # ... and also any on_changed code, which gets executed for EVERY change in data
1596             # ... ( ie not recordset status )
1597             $self->{on_changed}();
1598             }
1599            
1600             }
1601            
1602             $self->paint_calculated;
1603            
1604             }
1605            
1606             return FALSE; # Have to do this otherwise other signal handlers won't be fired
1607            
1608             }
1609              
1610             sub record_status_label_set {
1611            
1612             # This function is called from move() and apply()
1613             # It will set the record status label to either:
1614             # - Synchronized, or
1615             # - Locked
1616            
1617             my $self = shift;
1618            
1619             my $recordstatus = $self->get_widget( $self->{status_label} );
1620            
1621             if ( $recordstatus ) {
1622             if ( $self->{data_lock} ) {
1623             $recordstatus->set_markup( "Locked" );
1626             } else {
1627             $recordstatus->set_markup( "Synchronized" );
1630             }
1631             }
1632            
1633             }
1634              
1635             sub paint_calculated {
1636            
1637             # Paints calculated fields. If a field is passed, only that one gets calculated. Otherwise they all do.
1638            
1639             my ( $self, $field_to_paint ) = @_;
1640            
1641             foreach my $fieldname ( $field_to_paint || keys %{$self->{calc_fields}} ) {
1642             my $widget = $self->get_widget($fieldname);
1643             my $calc_value = eval $self->{calc_fields}->{$fieldname};
1644             if ( ! defined $widget ) {
1645             if ( ! $self->{quiet} ) {
1646             warn "*** Calculated Field $fieldname is missing a widget! ***\n";
1647             }
1648             } else {
1649             if ( ref $widget eq "Gtk2::Entry" || ref $widget eq "Gtk2::Label" ) {
1650             $self->{changelock} = TRUE;
1651             $widget->set_text( $calc_value || 0 );
1652             $self->{changelock} = FALSE;
1653             } else {
1654             warn "FIXME: Unknown widget type in Gtk2::Ex::DBI::paint_calculated: " . ref $widget . "\n";
1655             }
1656             }
1657             }
1658            
1659             }
1660              
1661             sub revert {
1662            
1663             # Reverts the form to the state of the in-memory recordset ( or deletes the in-memory record if we're adding a record )
1664            
1665             my $self = shift;
1666            
1667             if ( $self->{inserting} ) {
1668             # We're insering. Drop record and roll back one
1669             my $garbage_record = pop @{$self->{records}};
1670             $self->{changed} = FALSE;
1671             $self->{inserting} = 0;
1672             # Force a new slice to be fetched when we move(), which in turn deals with possible problems
1673             # if there are no records ( ie we want to set the 'inserting' flag if there are no records )
1674             $self->{keyset_group} = -1;
1675             $self->move( -1 );
1676             } else {
1677             # Existing record
1678             $self->{changed} = FALSE;
1679             $self->move( 0 );
1680             }
1681            
1682             $self->set_record_spinner_range;
1683            
1684             if ( $self->{on_undo} ) {
1685             $self->{on_undo}();
1686             }
1687            
1688             }
1689              
1690             sub undo {
1691            
1692             # undo is a synonym of revert
1693            
1694             my $self = shift;
1695            
1696             $self->revert;
1697            
1698             }
1699              
1700             sub delete {
1701            
1702             # Deletes the current record from the Database Server and from memory
1703            
1704             my $self = shift;
1705            
1706             my $sth = $self->{dbh}->prepare( "delete from " . $self->{sql}->{from} . " where "
1707             . join( "=? and", @{$self->{primary_keys}} ) . "=?" );
1708            
1709             my @bind_values;
1710            
1711             foreach my $primary_key ( @{$self->{primary_keys}} ) {
1712             push @bind_values, $self->{records}[$self->{slice_position}]->{ $self->{sql_to_widget_map}->{$primary_key} };
1713             }
1714            
1715             eval {
1716             $sth->execute( @bind_values )
1717             || die $self->{dbh}->errstr;
1718             };
1719            
1720             if ( $@ ) {
1721             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1722             title => "Error Deleting Record!",
1723             icon => "error",
1724             text => "Database Server Says:\n\n$@"
1725             );
1726             $sth->finish;
1727             return FALSE;
1728             }
1729            
1730             $sth->finish;
1731            
1732             # Cancel any updates ( if the user changed something before pressing delete )
1733             $self->{changed} = FALSE;
1734            
1735             # First remove the record from the keyset
1736             splice( @{$self->{keyset}}, $self->position, 1) ;
1737            
1738             # Force a new slice to be fetched when we move(), which in turn handles with possible problems
1739             # if there are no records ( ie we want to set the 'inserting' flag if there are no records )
1740             $self->{keyset_group} = -1;
1741            
1742             # Moving forwards will give problems if we're at the end of the keyset, so we move backwards instead
1743             # If we're already at the start, move() will deal with this gracefully
1744             $self->move( -1 );
1745            
1746             $self->set_record_spinner_range;
1747            
1748             }
1749              
1750             sub lock {
1751            
1752             # Locks the current record from further edits
1753            
1754             my $self = shift;
1755            
1756             if ( ! $self->{data_lock_field} ) {
1757             croak( "\nGtk2::Ex::DBI::lock called without having a data_lock_field defined!\n" );
1758             }
1759            
1760             # Apply the current record first
1761             if ( ! $self->apply ) {
1762             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1763             title => "Failed to lock record!",
1764             icon => "authentication",
1765             text => "There was an error applying the current record.\n"
1766             . "The lock operation has been aborted."
1767             );
1768             return FALSE;
1769             }
1770            
1771             # Set the lock field
1772             $self->set_widget_value( $self->{data_lock_field}, 1 );
1773            
1774             # Apply it ( which will implement the lock )
1775             if ( ! $self->apply ) {
1776             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1777             title => "Failed to lock record!",
1778             icon => "authentication",
1779             text => "There was an error applying the current record.\n"
1780             . "The lock operation has been aborted."
1781             );
1782             $self->revert; # Removes our changes to the lock field
1783             return FALSE;
1784             }
1785            
1786             $self->{data_lock} = TRUE;
1787            
1788             return TRUE;
1789            
1790             }
1791              
1792             sub unlock {
1793            
1794             # Unlocks the current record
1795            
1796             my $self = shift;
1797            
1798             if ( ! $self->{data_lock_field} ) {
1799             croak( "\nGtk2::Ex::DBI::unlock called without having a data_lock_field defined!\n" );
1800             }
1801            
1802             # Have to force this off, otherwise apply() method will fail
1803             $self->{data_lock} = FALSE;
1804            
1805             # Unset the lock field
1806             $self->set_widget_value( $self->{data_lock_field}, 0 );
1807            
1808             if ( ! $self->apply ) {
1809             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1810             title => "Failed to unlock record!",
1811             icon => "authentication",
1812             text => "There was an error applying the current record.\n"
1813             . "The unlock operation has been aborted."
1814             );
1815             $self->revert; # Removes our changes to the lock field
1816             return FALSE;
1817             }
1818            
1819             return TRUE;
1820            
1821             }
1822              
1823             sub position {
1824            
1825             # Returns the absolute position ( starting at 0 ) in the recordset ( taking into account the keyset and slice positions )
1826            
1827             my $self = shift;
1828             return ( $self->{keyset_group} * $self->{apeture} ) + $self->{slice_position};
1829            
1830             }
1831              
1832             sub set_record_spinner_range {
1833            
1834             # Convenience function that sets the min / max value of the record spinner
1835            
1836             my $self = shift;
1837            
1838             if ( $self->{spinner} ) {
1839             $self->{spinner}->signal_handler_block( $self->{record_spinner_value_changed_signal} );
1840             $self->{spinner}->set_range( 1, $self->count );
1841             $self->{spinner}->signal_handler_unblock( $self->{record_spinner_value_changed_signal} );
1842             }
1843            
1844             return TRUE;
1845            
1846             }
1847              
1848             sub setup_combo {
1849            
1850             # Convenience function that creates / refreshes a combo's model & sets up autocompletion
1851            
1852             my ( $self, $combo_name, $new_where_object ) = @_;
1853            
1854             my $combo = $self->{combos}->{$combo_name};
1855            
1856             # Remember the current value.
1857             # After setting up the model, we then try to select this value in the new model
1858             my $previous_value;
1859            
1860             if ( $self->{constructor_done} ) {
1861             $previous_value = $self->get_widget_value( $combo_name );
1862             }
1863            
1864             # Transfer new where object if one is passed
1865             if ( $new_where_object ) {
1866             $combo->{sql}->{where_object} = $new_where_object;
1867             }
1868            
1869             # Deal with legacy bind_variables key
1870             if ( exists $combo->{sql}->{where_object} && exists $combo->{sql}->{where_object}->{bind_variables} ) {
1871             if ( $self->{debug} ) {
1872             carp( "Gtk2::Ex::DBI::setup_combo called with a legacy bind_variables key!\n" );
1873             }
1874             $combo->{sql}->{where_object}->{bind_values} = $combo->{sql}->{where_object}->{bind_variables};
1875             delete $combo->{sql}->{where_object}->{bind_variables};
1876             }
1877            
1878             # First we clone a database connection - in case we're dealing with SQL Server here ...
1879             # ... SQL Server doesn't like it if you do too many things ( > 1 ) with one connection :)
1880             my $local_dbh;
1881            
1882             if ( exists $combo->{alternate_dbh} ) {
1883             $local_dbh = $combo->{alternate_dbh}->clone;
1884             } else {
1885             $local_dbh = $self->{dbh}->clone;
1886             }
1887            
1888             my $widget = $self->get_widget( $combo_name ) || 0;
1889            
1890             if ( ! $widget ) {
1891             warn "\n" . $self->{friendly_table_name} . " missing combo widget: $combo_name\n";
1892             return FALSE;
1893             }
1894            
1895             if ( ! $combo->{sql} ) {
1896             warn "\nMissing an SQL object in the combo definition for $combo_name!\n\n";
1897             return FALSE;
1898             } elsif ( ! $combo->{sql}->{from} ) {
1899             warn "\nMissing the 'from' key in the sql object in the combo definition for $combo_name!\n\n";
1900             return FALSE;
1901             }
1902            
1903             # Support using an SQL select string *instead* of an array of field definitions
1904             if ( exists $combo->{sql}->{select} && ! exists $combo->{fields} ) {
1905            
1906             # In this case, we clobber whatever was in the array of field definitions
1907             # ( if there was anything ) and construct our own ...
1908            
1909             $combo->{fields} = ();
1910            
1911             # ... and screw supporing aliases and other stuff
1912             my @fields = split /\,/, $combo->{sql}->{select};
1913            
1914             foreach my $field ( @fields ) {
1915             push @{$combo->{fields}},
1916             {
1917             name => $field,
1918             type => "Glib::String" # TODO Detect type from database? Factor out detection from constructor?
1919             };
1920             }
1921            
1922             }
1923            
1924             # Assemble items for liststore and SQL to get the data
1925             my ( @liststore_def, $sql );
1926            
1927             $sql = "select";
1928            
1929             my $column_no = 0;
1930            
1931             foreach my $field ( @{$combo->{fields}} ) {
1932            
1933             push @liststore_def, $field->{type}; # TODO automatically select type based on column_info from DB server
1934             $sql .= " $field->{name},";
1935            
1936             # Add additional renderers for columns if defined
1937             # We only want to do this the 1st time ( renderers_setup flag ), otherwise we get lots of renderers
1938             if ( $column_no > 1 && ! $combo->{renderers_setup} ) {
1939            
1940             my $renderer = Gtk2::CellRendererText->new;
1941             $widget->pack_start( $renderer, FALSE );
1942             $widget->set_attributes( $renderer, text => $column_no );
1943            
1944             # Set up custom cell data func if defined
1945             if ( exists $field->{cell_data_func} ) {
1946             $widget->set_cell_data_func( $renderer, sub { $field->{cell_data_func}( @_ ) } );
1947             }
1948            
1949             }
1950            
1951             $column_no ++;
1952            
1953             }
1954            
1955             $combo->{renderers_setup} = TRUE;
1956            
1957             chop( $sql );
1958            
1959             $sql .= " from $combo->{sql}->{from}";
1960            
1961             if ( $combo->{sql}->{where_object} ) {
1962             if ( ! $combo->{sql}->{where_object}->{bind_values} && ! $self->{quiet} ) {
1963             warn "\n* * * Gtk2::Ex::DBI::setup_combo called with a where clause but *WITHOUT* an array of values to bind!\n"
1964             . "* * * While this method is supported, it is a security hazard. *PLEASE* take advantage of our support of bind values\n\n";
1965             }
1966             $sql .= " where $combo->{sql}->{where_object}->{where}";
1967             }
1968            
1969             if ( $combo->{sql}->{group_by} ) {
1970             $sql .= " group by $combo->{sql}->{group_by}";
1971             }
1972            
1973             if ( $combo->{sql}->{order_by} ) {
1974             $sql .= " order by $combo->{sql}->{order_by}";
1975             }
1976            
1977             my $sth;
1978            
1979             eval {
1980             $sth = $local_dbh->prepare( $sql )
1981             || die $local_dbh->errstr;
1982             };
1983            
1984             if ( $@ ) {
1985             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
1986             title => "Error setting up combo box: $combo_name",
1987             icon => "error",
1988             text => "Database Server Says:\n\n$@"
1989             );
1990             if ( $self->{debug} ) {
1991             warn "\n$sql\n";
1992             }
1993             return FALSE;
1994             }
1995            
1996             # We have to use 'exists' here, otherwise we inadvertently create the where_object hash,
1997             # just by testing for it ... ( or by testing for bind_variables anyway )
1998             if ( exists $combo->{sql}->{where_object} && exists $combo->{sql}->{where_object}->{bind_values} ) {
1999             eval {
2000             $sth->execute( @{$combo->{sql}->{where_object}->{bind_values}} )
2001             || die $local_dbh->errstr;
2002             };
2003             } else {
2004             eval {
2005             $sth->execute || die $local_dbh->errstr;
2006             };
2007             }
2008            
2009             if ( $@ ) {
2010             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
2011             title => "Error setting up combo box: $combo_name",
2012             icon => "error",
2013             text => "Database Server Says:\n\n$@\n\n"
2014             . "Check the definintion of the table: $combo->{sql}->{from}"
2015             );
2016             return FALSE;
2017             }
2018            
2019             # Create the model
2020             my $model = Gtk2::ListStore->new( @liststore_def );
2021            
2022             while ( my @row = $sth->fetchrow_array ) {
2023            
2024             # We use fetchrow_array instead of fetchrow_hashref so
2025             # we can support the use of aliases in the fields
2026            
2027             my @model_row;
2028             my $column = 0;
2029             push @model_row, $model->append;
2030            
2031             foreach my $field ( @{$combo->{fields}} ) {
2032             push @model_row, $column, $row[$column];
2033             $column ++;
2034             }
2035            
2036             $model->set( @model_row );
2037            
2038             }
2039            
2040             $sth->finish;
2041             $local_dbh->disconnect;
2042            
2043             # Connect the model to the widget
2044             $widget->set_model( $model );
2045            
2046             if ( ref $widget eq "Gtk2::ComboBoxEntry" ) {
2047            
2048             # We can only call $combo->set_text_column *once* per combo, so we
2049             # have to remember when we've set it up
2050             if ( ! $self->{combos_set}->{$combo_name} ) {
2051             $widget->set_text_column( 1 );
2052             $self->{combos_set}->{$combo_name} = TRUE;
2053             }
2054            
2055             # Set up autocompletion in the Combo's entry
2056             my $entrycompletion = Gtk2::EntryCompletion->new;
2057             $entrycompletion->set_minimum_key_length( 1 );
2058             $entrycompletion->set_model( $model );
2059             $entrycompletion->set_text_column( 1 );
2060             $widget->get_child->set_completion( $entrycompletion );
2061            
2062             }
2063            
2064             # Finally select the previous value in the new model
2065             if ( $self->{constructor_done} ) {
2066             my $changelock_status = $self->{changelock}; # Remember the state of the changelock
2067             $self->{changelock} = TRUE;
2068             $self->set_widget_value( $combo_name, $previous_value );
2069             $self->{changelock} = $changelock_status;
2070             }
2071            
2072             return TRUE;
2073            
2074             }
2075              
2076             sub setup_autocompletion {
2077            
2078             # Convenience function that creates / refreshes a combo's model & sets up autocompletion
2079            
2080             my ( $self, $autocompletion_name, $new_where_object ) = @_;
2081            
2082             my $autocompletion = $self->{autocompletions}->{$autocompletion_name};
2083            
2084             # Transfer new where object if one is passed
2085             if ( $new_where_object ) {
2086             $autocompletion->{sql}->{where_object} = $new_where_object;
2087             }
2088            
2089             # First we clone a database connection - in case we're dealing with SQL Server here ...
2090             # ... SQL Server doesn't like it if you do too many things ( > 1 ) with one connection :)
2091             my $local_dbh;
2092            
2093             if ( exists $autocompletion->{alternate_dbh} ) {
2094             $local_dbh = $autocompletion->{alternate_dbh}->clone;
2095             } else {
2096             $local_dbh = $self->{dbh}->clone;
2097             }
2098            
2099             my $widget = $self->get_widget( $autocompletion_name ) || 0;
2100            
2101             if ( ! $widget ) {
2102             warn "\n" . $self->{friendly_table_name} . " missing autocompletion widget: $autocompletion_name\n";
2103             return FALSE;
2104             }
2105            
2106             if ( ! $autocompletion->{sql} ) {
2107             warn "\nMissing an SQL object in the combo definition for $autocompletion_name!\n\n";
2108             return FALSE;
2109             } elsif ( ! $autocompletion->{sql}->{from} ) {
2110             warn "\nMissing the 'from' key in the sql object in the autocompletion definition for $autocompletion_name!\n\n";
2111             return FALSE;
2112             }
2113            
2114             # Support using an SQL select string *instead* of an array of field definitions
2115             if ( exists $autocompletion->{sql}->{select} && ! exists $autocompletion->{fields} ) {
2116            
2117             # In this case, we clobber whatever was in the array of field definitions
2118             # ( if there was anything ) and construct our own ...
2119            
2120             $autocompletion->{fields} = ();
2121            
2122             # ... and screw supporing aliases and other stuff
2123             my @fields = split /\,/, $autocompletion->{sql}->{select};
2124            
2125             foreach my $field ( @fields ) {
2126             push @{$autocompletion->{fields}},
2127             {
2128             name => $field,
2129             type => "Glib::String" # TODO Detect type from database? Factor out detection from constructor?
2130             };
2131             }
2132            
2133             }
2134            
2135             # Assemble items for liststore and SQL to get the data
2136             my ( @liststore_def, $sql );
2137            
2138             $sql = "select";
2139            
2140             my $column_no = 0;
2141            
2142             foreach my $field ( @{$autocompletion->{fields}} ) {
2143            
2144             push @liststore_def, $field->{type}; # TODO automatically select type based on column_info from DB server
2145             $sql .= " $field->{name},";
2146            
2147             $column_no ++;
2148            
2149             }
2150            
2151             chop( $sql );
2152            
2153             $sql .= " from " . $autocompletion->{sql}->{from};
2154            
2155             if ( $autocompletion->{sql}->{where_object} ) {
2156             if ( ! $autocompletion->{sql}->{where_object}->{bind_values} && ! $self->{quiet} ) {
2157             warn "\n* * * Gtk2::Ex::DBI::setup_combo called with a where clause but *WITHOUT* an array of values to bind!\n"
2158             . "* * * While this method is supported, it is a security hazard. *PLEASE* take advantage of our support of bind values\n\n";
2159             }
2160             $sql .= " where $autocompletion->{sql}->{where_object}->{where}";
2161             }
2162            
2163             if ( $autocompletion->{sql}->{group_by} ) {
2164             $sql .= " group by " . $autocompletion->{sql}->{group_by};
2165             }
2166            
2167             if ( $autocompletion->{sql}->{order_by} ) {
2168             $sql .= " order by " . $autocompletion->{sql}->{order_by};
2169             }
2170            
2171             my $sth;
2172            
2173             eval {
2174             $sth = $local_dbh->prepare( $sql )
2175             || die $local_dbh->errstr;
2176             };
2177            
2178             if ( $@ ) {
2179             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
2180             title => "Error setting up combo box: $autocompletion_name",
2181             icon => "error",
2182             text => "Database Server Says:\n\n$@"
2183             );
2184             if ( $self->{debug} ) {
2185             warn "\n$sql\n";
2186             }
2187             return FALSE;
2188             }
2189            
2190             # We have to use 'exists' here, otherwise we inadvertently create the where_object hash,
2191             # just by testing for it ... ( or by testing for bind_variables anyway )
2192             if ( exists $autocompletion->{sql}->{where_object} && exists $autocompletion->{sql}->{where_object}->{bind_values} ) {
2193             eval {
2194             $sth->execute( @{$autocompletion->{sql}->{where_object}->{bind_values}} )
2195             || die $local_dbh->errstr;
2196             };
2197             } else {
2198             eval {
2199             $sth->execute || die $local_dbh->errstr;
2200             };
2201             }
2202            
2203             if ( $@ ) {
2204             Gtk2::Ex::Dialogs::ErrorMsg->new_and_run(
2205             title => "Error setting up combo box: $autocompletion_name",
2206             icon => "error",
2207             text => "Database Server Says:\n\n$@\n\n"
2208             . "Check the definintion of the table: " . $autocompletion->{sql}->{from}
2209             );
2210             return FALSE;
2211             }
2212            
2213             # Create the model
2214             my $model = Gtk2::ListStore->new( @liststore_def );
2215            
2216             while ( my @row = $sth->fetchrow_array ) {
2217            
2218             # We use fetchrow_array instead of fetchrow_hashref so
2219             # we can support the use of aliases in the fields
2220            
2221             my @model_row;
2222             my $column = 0;
2223             push @model_row, $model->append;
2224            
2225             foreach my $field ( @{$autocompletion->{fields}} ) {
2226             push @model_row, $column, $row[$column];
2227             $column ++;
2228             }
2229            
2230             $model->set( @model_row );
2231            
2232             }
2233            
2234             $sth->finish;
2235             $local_dbh->disconnect;
2236            
2237             # Set up autocompletion in the Combo's entry
2238             my $entrycompletion = Gtk2::EntryCompletion->new;
2239             $entrycompletion->set_minimum_key_length( 0 );
2240             $entrycompletion->set_model( $model );
2241             $entrycompletion->set_text_column( 1 );
2242             $widget->set_completion( $entrycompletion );
2243            
2244             return TRUE;
2245            
2246             }
2247              
2248             sub get_widget {
2249            
2250             # Returns a given widget, whether from a Gtk2::GladeXML object,
2251             # or a Gtk2::Builder object
2252            
2253             my ( $self, $widget_name ) = @_;
2254            
2255             if ( exists $self->{form} && ref $self->{form} eq "Gtk2::GladeXML" ) {
2256            
2257             my $widget = $self->{form}->get_widget( $widget_name );
2258            
2259             if ( ! $widget && $self->{widget_prefix} ) {
2260             $widget = $self->{form}->get_widget( $self->{widget_prefix} . $widget_name );
2261             }
2262            
2263             return $widget;
2264            
2265             } elsif ( exists $self->{builder} && ref $self->{builder} eq "Gtk2::Builder" ) {
2266            
2267             my $widget = $self->{form}->get_object( $widget_name );
2268            
2269             if ( ! $widget && $self->{widget_prefix} ) {
2270             $widget = $self->{form}->get_object( $self->{widget_prefix} . $widget_name );
2271             }
2272            
2273             return $widget;
2274            
2275             } else {
2276            
2277             carp( "get_widget() called but we don't have a form ( Gtk2::GladeXML ) or builder ( Gtk2::Builder ) object available" );
2278             return undef;
2279            
2280             }
2281            
2282             }
2283              
2284             sub get_widget_value {
2285            
2286             # Returns the *current* value of the given *widget*
2287            
2288             my ( $self, $fieldname ) = @_;
2289            
2290             my $widget = $self->get_widget( $fieldname );
2291            
2292             if ( ! $widget ) {
2293            
2294             # No widget by this name. Check for split-widget widgets ( currently TimeSpinners )
2295             # At the moment, we only check for the presence of a $field_hh - named field
2296            
2297             # TODO Remove this bullshit. We need to create a custom widget for Time
2298            
2299             my $hh_test = $self->get_widget( $fieldname . "_hh" );
2300             my $time_value;
2301             if ( $hh_test ) {
2302             foreach my $type qw / hh mm ss / {
2303             $time_value .= sprintf( "%02d", $self->get_widget_value( $fieldname . "_" . $type ) || 0 ) . ":";
2304             }
2305             chop ( $time_value );
2306             if ( $time_value eq "00:00:00" ) {
2307             $time_value = undef;
2308             }
2309             return $time_value;
2310             } else {
2311             carp( "\nGtk2::Ex::DBI::get_widget_value called on non-existant field: $fieldname!" );
2312             return undef;
2313             }
2314            
2315             }
2316            
2317             my $type = ref $widget;
2318            
2319             my $value;
2320            
2321             if ( $type eq "Gtk2::Calendar" ) {
2322            
2323             my ( $year, $month, $day ) = $widget->get_date;
2324             my $date;
2325            
2326             if ( $day > 0 ) {
2327            
2328             # NOTE! NOTE! Apparently GtkCalendar has the months starting at ZERO!
2329             # Therefore, add one to the month...
2330             $month ++;
2331            
2332             # Pad the $month and $day values
2333             $month = sprintf( "%02d", $month );
2334             $day = sprintf( "%02d", $day );
2335            
2336             $date = $year . "-" . $month . "-" . $day;
2337            
2338             } else {
2339             $date = undef;
2340             }
2341            
2342             $value = $date;
2343            
2344            
2345             } elsif ( $type eq "Gtk2::ToggleButton" ) {
2346            
2347             if ( $widget->get_active ) {
2348             $value = 1;
2349             } else {
2350             $value = 0;
2351             }
2352            
2353             } elsif ( $type eq "Gtk2::ComboBoxEntry" || $type eq "Gtk2::ComboBox" ) {
2354            
2355             my $iter = $widget->get_active_iter;
2356            
2357             # If $iter is defined ( ie something is selected ), push the ID of the selected row
2358             # onto @bind_values, otherwise test the column type.
2359             # If we find a "Glib::Int" column type, we push a zero onto @bind_values otherwise 'undef'
2360            
2361             if ( defined $iter ) {
2362             $value = $widget->get_model->get( $iter, 0 );
2363             } else {
2364             my $columntype = $widget->get_model->get_column_type( 0 );
2365             if ( $columntype eq "Glib::Int" ) {
2366             $value = 0;
2367             } else {
2368             $value = undef;
2369             }
2370             }
2371            
2372             } elsif ( $type eq "Gtk2::TextView" ) {
2373            
2374             my $textbuffer = $widget->get_buffer;
2375             my ( $start_iter, $end_iter ) = $textbuffer->get_bounds;
2376             $value = $textbuffer->get_text( $start_iter, $end_iter, 1 );
2377            
2378             } elsif ( $type eq "Gtk2::CheckButton" ) {
2379            
2380             if ( $widget->get_active ) {
2381             $value = 1;
2382             } else {
2383             $value = 0;
2384             }
2385            
2386             } else {
2387            
2388             my $txt_value = $widget->get_text;
2389            
2390             if ( $txt_value || $txt_value eq "0" ) { # Don't push an undef value just because our field has a zero in it
2391             $value = $txt_value;
2392             } else {
2393             $value = undef;
2394             }
2395            
2396             }
2397            
2398             # To allow us to use get_widget_value on non-managed fields, we have to be careful we don't
2399             # accidentally add the widget name to our widgets hash, or the widget will end up being included
2400             # in SQL commands
2401            
2402             if ( exists $self->{widgets}->{ $fieldname } ) {
2403            
2404             my $widget_def = $self->{widgets}->{$fieldname};
2405            
2406             foreach my $item ( keys %{$widget_def}) {
2407            
2408             # Possible values are:
2409             # - sql_fieldname - ( not related to formatting )
2410             # - number - a hash describing numeric formatting
2411             # - date - a hash describing date formatting
2412            
2413             if ( $item eq "number" ) {
2414             $value = $self->formatter_number_from_widget( $value, $widget_def->{number} );
2415             } elsif ( $item eq "date" ) {
2416             $value = $self->formatter_date_from_widget( $value, $widget_def->{date} );
2417             }
2418            
2419             }
2420            
2421             }
2422            
2423             return $value;
2424            
2425             }
2426              
2427             sub set_widget_value {
2428            
2429             # Sets a widget to a given value
2430            
2431             my ( $self, $fieldname, $value ) = @_;
2432            
2433             my $widget = $self->get_widget( $fieldname );
2434            
2435             my $local_value = $value;
2436            
2437             # To allow us to use set_widget_value on non-managed fields, we have to be careful we don't
2438             # accidentally add the widget name to our widgets hash, or the widget will end up being included
2439             # in SQL commands
2440            
2441             if ( exists $self->{widgets}->{$fieldname} ) {
2442            
2443             my $widget_def = $self->{widgets}->{$fieldname};
2444            
2445             foreach my $item ( keys %{$widget_def}) {
2446            
2447             if ( $item eq "number" ) {
2448             $local_value = $self->formatter_number_to_widget( $local_value, $widget_def->{number} );
2449             } elsif ( $item eq "date" ) {
2450             $local_value = $self->formatter_date_to_widget( $local_value, $widget_def->{date} );
2451             }
2452            
2453             }
2454            
2455             }
2456            
2457             if ( ! $widget ) {
2458            
2459             # Check for split-widget widgets ( currently TimeSpinners )
2460             # At the moment, we only check for the presence of a $field_hh - named field
2461            
2462             # TODO Remove this bullshit. We need to create a custom widget for Time
2463            
2464             my $hh_test = $self->get_widget( $fieldname . "_hh" );
2465             my $time_value;
2466             if ( $hh_test ) {
2467             # Found an hour widget. Split time into 3 values and apply
2468             my @hhmmss;
2469             if ( $local_value ) {
2470             @hhmmss = split /:/, $local_value;
2471             } else {
2472             @hhmmss = ( 0, 0, 0 );
2473             }
2474             my $counter = 0;
2475             foreach my $type qw / hh mm ss / {
2476             $self->set_widget_value( $fieldname . "_" . $type, $hhmmss[$counter] || 0 );
2477             $counter ++;
2478             }
2479             } elsif ( ! $self->{quiet} && $fieldname ne $self->{primary_key} ) {
2480             # We don't warn on a missing primary key *widget*, as this is perfectly safe
2481             # ( updates use the in-memory primary key )
2482             carp( "Field $fieldname is missing a widget" );
2483             return FALSE;
2484             }
2485            
2486             }
2487            
2488            
2489             my $type = ( ref $widget );
2490            
2491             if ( $type eq "Gtk2::Calendar" ) {
2492            
2493             if ( $local_value ) {
2494            
2495             my $year = substr( $local_value, 0, 4 );
2496             my $month = substr( $local_value, 5, 2 );
2497             my $day = substr( $local_value, 8, 2 );
2498            
2499             # Months start at zero ...
2500             $month --;
2501            
2502             if ( $month != -1 ) {
2503             if ( $self->{debug} ) {
2504             warn "Setting Day: $day, Month: $month, Year: $year"
2505             }
2506             $widget->select_month( $month, $year );
2507             $widget->select_day( $day );
2508             } else {
2509             # Select the current month / year
2510             ( $month, $year ) = (localtime())[4, 5];
2511             $year += 1900;
2512             $widget->select_month( $month, $year );
2513             # But de-select the day
2514             $widget->select_day( 0 );
2515             }
2516            
2517             } else {
2518             # Select the current month / year
2519             my ( $month, $year ) = (localtime())[4, 5];
2520             $year += 1900;
2521             $widget->select_month( $month, $year );
2522             $widget->select_day( 0 );
2523             }
2524            
2525             } elsif ( $type eq "Gtk2::ToggleButton" ) {
2526            
2527             $widget->set_active( $local_value );
2528            
2529             } elsif ( $type eq "Gtk2::ComboBoxEntry" || $type eq "Gtk2::ComboBox" || $type eq "Gtk2::Combo" ) {
2530            
2531             # This is some ugly stuff. Gtk2 doesn't support selecting an iter in a model based on the string
2532            
2533             # See http://bugzilla.gnome.org/show_bug.cgi?id=149248
2534            
2535             # TODO Broken Gtk2 combo box entry workaround
2536             # If we can't get above bug resolved, perhaps load the ID / value pairs into something
2537             # that supports rapid searching so we don't have to loop through the entire list, which
2538             # could be *very* slow if the list is large
2539            
2540             # Check to see whether this combo has a model
2541             my $model = $widget->get_model;
2542            
2543             if ( ! $model ) {
2544             carp( "Field $fieldname has a matching combo, but there is no model attached!\n"
2545             . " You MUST set up all your combo's models before creating a Gtk2::Ex::DBI object" );
2546             return FALSE;
2547             }
2548            
2549             my $iter = $model->get_iter_first;
2550            
2551             if ( $type eq "Gtk2::ComboBoxEntry" ) {
2552             $widget->get_child->set_text( "" );
2553             }
2554            
2555             my $match_found = FALSE;
2556            
2557             while ( $iter ) {
2558             if ( ( defined $local_value ) &&
2559             ( $local_value eq $model->get( $iter, 0 ) ) ) {
2560             $match_found = TRUE;
2561             $widget->set_active_iter( $iter );
2562             last;
2563             }
2564             $iter = $model->iter_next( $iter );
2565             }
2566            
2567             # There's only really a need to warn if we've actually been passed a value, because
2568             # if we've been passed a 0 or undef, we de-select the combo, and this will return a zero
2569             # when we call $self->get_widget_value() anyway, so this is as good as finding a match
2570            
2571             if ( ! $match_found && $value ) {
2572             carp( "Failed to set $fieldname to value $value\n ( wasn't in the model )" );
2573             }
2574            
2575             } elsif ( $type eq "Gtk2::TextView" ) {
2576            
2577             $widget->get_buffer->set_text( $local_value || "" );
2578            
2579             } elsif ( $type eq "Gtk2::CheckButton" ) {
2580            
2581             $widget->set_active( $local_value );
2582            
2583             } elsif ( $type eq "Gtk2::SpinButton" ) {
2584            
2585             $widget->set_value( $local_value || 0 );
2586            
2587             } else {
2588            
2589             # Assume everything else has a 'set_text' method. Add more types if necessary...
2590             $widget->set_text( defined $local_value ? $local_value : "" );
2591            
2592             }
2593            
2594             return TRUE;
2595            
2596             }
2597              
2598             sub sum_widgets {
2599            
2600             # Return the sum of all given fields ( they don't have to be fields we manage; just on the same form )
2601            
2602             my ( $self, $fields ) = @_;
2603            
2604             my $total;
2605            
2606             if ( $self->{debug} ) {
2607             print "\n\nGtk2::Ex::DBI::sum_widgets calling Gtk2::Ex::DBI::get_widget_value ... ... ...\n";
2608             }
2609            
2610             foreach my $fieldname ( @{$fields} ) {
2611             $total += $self->get_widget_value( $fieldname ) || 0;
2612             }
2613            
2614             if ( $self->{debug} ) {
2615             print "\nGtk2::Ex::DBI::sum_widgets final value: $total\n\n";
2616             }
2617            
2618             return $total;
2619            
2620             }
2621              
2622             sub original_value {
2623            
2624             my ( $self, $fieldname ) = @_;
2625            
2626             return $self->{records}[$self->{slice_position}]->{$fieldname};
2627            
2628             }
2629              
2630             sub set_active_iter_for_broken_combo_box {
2631            
2632             # This function is called when a ComboBoxEntry's value is changed
2633             # See http://bugzilla.gnome.org/show_bug.cgi?id=156017
2634            
2635             my ( $self, $widget ) = @_;
2636            
2637             my $string = $widget->get_child->get_text;
2638             my $model = $widget->get_model;
2639            
2640             if ( ! $model ) {
2641             return FALSE;
2642             }
2643            
2644             my $current_iter = $widget->get_active_iter;
2645             my $iter = $model->get_iter_first;
2646            
2647             while ( $iter ) {
2648             if ( $string eq $model->get( $iter, 1 ) ) {
2649             $widget->set_active_iter( $iter );
2650             if ( ! $current_iter || $iter != $current_iter ) {
2651             $self->changed;
2652             }
2653             last;
2654             }
2655             $iter = $model->iter_next( $iter );
2656             }
2657            
2658             return FALSE; # Apparently we must return FALSE so the entry gets the event as well
2659            
2660             }
2661              
2662             sub last_insert_id {
2663            
2664             my $self = shift;
2665            
2666             my $primary_key;
2667            
2668             if ( $self->{server} =~ /postgres/i ) {
2669            
2670             # Postgres drivers support DBI's last_insert_id()
2671            
2672             $primary_key = $self->{dbh}->last_insert_id (
2673             undef,
2674             $self->{schema},
2675             $self->{sql}->{from},
2676             undef
2677             );
2678            
2679             } elsif ( lc($self->{server}) eq "sqlite" ) {
2680            
2681             $primary_key = $self->{dbh}->last_insert_id(
2682             undef,
2683             undef,
2684             $self->{sql}->{from},
2685             undef
2686             );
2687            
2688             } else {
2689            
2690             # MySQL drivers ( recent ones ) claim to support last_insert_id(), but I'll be
2691             # damned if I can get it to work. Older drivers don't support it anyway, so for
2692             # maximum compatibility, we do something they can all deal with.
2693            
2694             # The below works for MySQL and SQL Server, and possibly others ( Sybase ? )
2695            
2696             my $sth = $self->{dbh}->prepare( 'select @@IDENTITY' );
2697             $sth->execute;
2698            
2699             if ( my $row = $sth->fetchrow_array ) {
2700             $primary_key = $row;
2701             } else {
2702             $primary_key = undef;
2703             }
2704            
2705             }
2706            
2707             return $primary_key;
2708            
2709             }
2710              
2711             sub process_entry_keypress {
2712            
2713             my ( $self, $widget, $event ) = @_;
2714            
2715             if (
2716             $event->keyval == $Gtk2::Gdk::Keysyms{ Return } ||
2717             $event->keyval == $Gtk2::Gdk::Keysyms{ KP_Enter }
2718             ) {
2719             $self->{window}->child_focus('tab-forward');
2720             # If this was a combo ( keep in mind this event occurs in the Combo's child entry ),
2721             # then tab-forward again, otherwise our focus has only moved along
2722             # to the drop-down button thingy ...
2723             if ( ref $widget->get_parent eq "Gtk2::ComboBoxEntry" ) {
2724             $self->{window}->child_focus('tab-forward');
2725             }
2726             }
2727            
2728             return FALSE; # This return value is required otherwise the event doesn't propogate and the keypress has no effect
2729            
2730             }
2731              
2732             sub reset_record_status {
2733            
2734             # This sub resets the record status ( changed flag ) so that Gtk2::Ex::DBI considers the record SYNCHRONISED
2735            
2736             my $self = shift;
2737            
2738             $self->{changed} = FALSE;
2739             $self->record_status_label_set;
2740            
2741             }
2742              
2743             sub formatter_number_to_widget {
2744            
2745             my ( $self, $value, $options ) = @_;
2746            
2747             # $options can contain:
2748             # - currency
2749             # - percentage
2750             # - decimals
2751             # - decimal_fill
2752             # - separate_thousands
2753            
2754             # Avoid stoopid warnings - this will replace NULL values zeros, which is OK by me ...
2755             if ( ! $value ) {
2756             $value = 0;
2757             }
2758            
2759             # Strip out dollar signs
2760             $value =~ s/\$//g;
2761            
2762             # Allow for our number of decimal places
2763             if ( $options->{decimals} ) {
2764             $value *= 10 ** $options->{decimals};
2765             }
2766            
2767             # For percentages, multiply by 100
2768             if ( $options->{percentage} ) {
2769             $value *= 100;
2770             }
2771            
2772             # Round
2773             $value = int( $value + .5 * ( $value <=> 0 ) );
2774            
2775             # Get decimals back
2776             if ( $options->{decimals} ) {
2777             $value /= 10 ** $options->{decimals};
2778             }
2779            
2780             # Split whole and decimal parts
2781             my ( $whole, $decimal ) = split /\./, $value;
2782            
2783             # Pad decimals
2784             if ( $options->{decimal_fill} && $options->{decimals} ) {
2785             if ( defined $decimal ) {
2786             $decimal = $decimal . "0" x ( $options->{decimals} - length( $decimal ) );
2787             } else {
2788             $decimal = "0" x $options->{decimals};
2789             }
2790             }
2791            
2792             # Separate thousands if specified, OR make it the default to separate them if we're dealing with currency
2793             if ( $options->{separate_thousands} || ( $options->{currency} && ! exists $options->{separate_thousands} ) ) {
2794             # This BS comes from 'perldoc -q numbers'
2795             $whole =~ s/(^[-+]?\d+?(?=(?>(?:\d{3})+)(?!\d))|\G\d{3}(?=\d))/$1,/g;
2796             }
2797            
2798             if ( $options->{decimals} ) {
2799             $value = "$whole.$decimal";
2800             } else {
2801             $value = $whole;
2802             }
2803            
2804             # Prepend a dollar sign for currency
2805             if ( $options->{currency} ) {
2806             $value = '$' . $value;
2807             # If this is a negative value, we want to force the negative sign to the left of the dollar sign ...
2808             $value =~ s/\$-/-\$/;
2809             }
2810            
2811             # Append a percentage sign for percentages
2812             if ( $options->{percentage} ) {
2813             $value .= '%';
2814             }
2815            
2816             return $value;
2817            
2818             }
2819              
2820             sub formatter_date_to_widget {
2821            
2822             my ( $self, $value, $options ) = @_;
2823            
2824             # $options can contain:
2825             # - strip_time - useful for cases where you have a datetime field, but are only storing / viewing dates
2826             # - format - format for date display ( currently only ddmmyyyy supported )
2827            
2828             if ( $value ) { # Don't bother with any formatting if we haven't been passed anything
2829            
2830             if ( $options->{strip_time} ) {
2831             $value = substr( $value, 0, 10 ); # Assuming 10 characters of course ( dd-mm-yyyy )
2832             }
2833            
2834             if ( $options->{format} eq "ddmmyyyy" ) {
2835             my ( $yyyy, $mm, $dd ) = split /[-\/]/, $value;
2836             $value = $dd . "-" . $mm . "-" . $yyyy;
2837             }
2838            
2839             }
2840            
2841             return $value;
2842            
2843             }
2844              
2845             sub formatter_number_from_widget {
2846            
2847             # This sub strips dollar signs and commas from values, so they can
2848             # be passed back to the database as numeric values
2849            
2850             my ( $self, $value ) = @_;
2851            
2852             if ( $value ) {
2853             # Strip dollar signs
2854             $value =~ s/\$//g;
2855            
2856             # Strip commas
2857             $value =~ s/\,//g;
2858             }
2859            
2860             if ( $value =~ /%/ ) {
2861             $value =~ s/%//g;
2862             $value /= 100;
2863             }
2864            
2865             return $value;
2866            
2867             }
2868              
2869             sub formatter_date_from_widget {
2870            
2871             # Converts date values from a widget ( which may be formatted ) back to
2872             # a DB-friendly format if required
2873            
2874             my ( $self, $value, $options ) = @_;
2875            
2876             if ( $value ) {
2877             if ( $options->{format} eq "ddmmyyyy" ) {
2878            
2879             my ( $dd, $mm, $yyyy ) = split /[-\/]/, $value;
2880            
2881             if ( length( $yyyy) == 2 ) {
2882             $yyyy = '20' . $yyyy;
2883             }
2884            
2885             $value = $yyyy . "-"
2886             . sprintf( "%02d", $mm ) . "-"
2887             . sprintf( "%02d", $dd );
2888            
2889             }
2890             } else {
2891             $value = undef;
2892             }
2893            
2894             return $value;
2895            
2896             }
2897              
2898             sub parse_sql_server_default {
2899            
2900             # This sub parses the string returned by SQL Server as the DEFAULT value for a given field
2901            
2902             my ( $self, $sqlserver_default ) = @_;
2903            
2904             # Find the last space in the string
2905             my $final_space_position = rindex( $sqlserver_default, " " );
2906            
2907             if ( ! $final_space_position || $final_space_position == -1 ) {
2908             # Bail out, returning undef.
2909             # We can't use the current default value ( as it's a string definition ), so we might as well just drop it completely
2910             warn "Gtk2::Ex::DBI::parse_sql_server_default failed to find the last space character in the DEFAULT definition:\n$sqlserver_default\n";
2911             return undef;
2912             } else {
2913             # We've got the final space character. Now get everything to the right of it ...
2914             my $default_value = substr( $sqlserver_default, $final_space_position + 1, length( $sqlserver_default ) - $final_space_position - 1 );
2915             # ... and strip off any quotes
2916             $default_value =~ s/'//g;
2917             return $default_value;
2918             }
2919            
2920             }
2921              
2922             sub build_right_click_menu {
2923            
2924             # This sub appends menu items to the right-click menu of widgets
2925            
2926             # TODO Add some custom icons, particularly for the calculator ... find is OK
2927            
2928             my ( $self, $widget, $menu ) = @_;
2929            
2930             my $menu_item;
2931            
2932             # Get the parent widget so we know if we're an entry in a combo box or not
2933             my $parent_widget = $widget->get_parent;
2934            
2935             # The 'find' menu item
2936             if ( ! $self->{disable_find} && ! exists $self->{sql}->{pass_through} ) {
2937             $menu_item = Gtk2::ImageMenuItem->new_from_stock("gtk-find");
2938             if ( ref $parent_widget eq "Gtk2::ComboBoxEntry" ) {
2939             $menu_item->signal_connect_after( activate => sub { $self->find_dialog($parent_widget); } );
2940             } else {
2941             $menu_item->signal_connect_after( activate => sub { $self->find_dialog($widget); } );
2942             }
2943             $menu->append($menu_item);
2944             $menu_item->show;
2945             }
2946            
2947             # The 'calculator' menu item
2948             $menu_item = Gtk2::ImageMenuItem->new("Calculator");
2949             my $pixbuf = $widget->render_icon( "gtk-index", "menu" );
2950             my $image = Gtk2::Image->new_from_pixbuf($pixbuf);
2951             $menu_item->set_image($image);
2952             $menu_item->signal_connect_after( activate => sub { $self->calculator($widget); } );
2953             $menu->append($menu_item);
2954             $menu_item->show;
2955            
2956             # The 'refresh combo' menu item
2957             # ( but only if we've got a definition handy to rebuild it with )
2958             if ( ref $parent_widget eq "Gtk2::ComboBoxEntry"
2959             && $self->{combos}
2960             && $self->{combos}->{$parent_widget->get_name}
2961             ) {
2962             $menu_item = Gtk2::ImageMenuItem->new("Refresh Combo");
2963             $pixbuf = $widget->render_icon( "gtk-refresh", "menu" );
2964             $image = Gtk2::Image->new_from_pixbuf($pixbuf);
2965             $menu_item->set_image($image);
2966             $menu_item->signal_connect_after( activate => sub { $self->setup_combo($parent_widget->get_name); } );
2967             $menu->append($menu_item);
2968             $menu_item->show;
2969             }
2970            
2971             return FALSE;
2972            
2973             }
2974              
2975             sub find_dialog {
2976            
2977             # Pops up a find dialog for the user to search the *existing* recordset
2978             my ( $self, $widget ) = @_;
2979            
2980             # TODO This needs a rewrite, but I've never used it anyway ...
2981             warn "find_dialog() functionality currently broken ... needs a rewrite ...";
2982            
2983             $self->{find}->{window} = Gtk2::Window->new ( "toplevel" );
2984             $self->{find}->{window}->set_title( "Gtk2::Ex::DBI advanced query" );
2985             $self->{find}->{window}->set_default_size( 300, 480 );
2986             $self->{find}->{window}->set_position( "center-always" );
2987            
2988             $self->{find}->{criteria_vbox} = Gtk2::VBox->new( 0, 5 );
2989            
2990             $self->{find}->{criteria} = ();
2991            
2992             # Construct a model to use for the 'operator' combo box in the criteria builder
2993             $self->{find}->{operator_model} = Gtk2::ListStore->new(
2994             "Glib::String",
2995             "Glib::String"
2996             );
2997            
2998             foreach my $operator(
2999             [ "=", "equals" ],
3000             [ "!=", "does not equal" ],
3001             [ "<", "less than" ],
3002             [ ">", "greater than" ],
3003             [ "like", "like" ]
3004             ) {
3005             $self->{find}->{operator_model}->set(
3006             $self->{find}->{operator_model}->append,
3007             0, $$operator[0],
3008             1, $$operator[1]
3009             );
3010             }
3011            
3012             # Construct a model to use for the 'field' combo box in the criteria builder
3013             $self->{find}->{field_model} = Gtk2::ListStore->new( "Glib::String" );
3014            
3015             foreach my $field ( $self->fieldlist ) {
3016             $self->{find}->{field_model}->set(
3017             $self->{find}->{field_model}->append,
3018             0, $field
3019             );
3020             }
3021            
3022             # Add a blank row ( and set the field of the first criteria row )
3023             $self->find_dialog_add_criteria( $widget->get_name );
3024            
3025             # A scrolled window to put the criteria selectors in
3026             my $sw = Gtk2::ScrolledWindow->new( undef, undef );
3027             $sw->set_shadow_type( "etched-in" );
3028             $sw->set_policy( "never", "always" );
3029             $sw->add_with_viewport( $self->{find}->{criteria_vbox} );
3030            
3031             # A button to add more criteria
3032             my $add_criteria_button = Gtk2::Button->new_from_stock( 'gtk-add' );
3033             $add_criteria_button->signal_connect_after( clicked => sub { $self->find_dialog_add_criteria } );
3034            
3035             # The find button
3036             my $find_button = Gtk2::Button->new_from_stock( 'gtk-find' );
3037             $find_button->signal_connect_after( clicked => sub { $self->find_do_search } );
3038            
3039             # An hbox to hold the buttons
3040             my $hbox = Gtk2::HBox->new( 0, 5 );
3041             $hbox->pack_start( $add_criteria_button, TRUE, TRUE, 5 );
3042             $hbox->pack_start( $find_button, TRUE, TRUE, 5 );
3043            
3044             # Another hbox to hold the headings
3045             my $headings_hbox = Gtk2::HBox->new( 0, 5 );
3046            
3047             # The headings
3048             my $field_heading = Gtk2::Label->new;
3049             $field_heading->set_markup( "Field" );
3050            
3051             my $operator_heading = Gtk2::Label->new;
3052             $operator_heading->set_markup( "Operator" );
3053            
3054             my $criteria_heading = Gtk2::Label->new;
3055             $criteria_heading->set_markup( "Criteria" );
3056            
3057             $headings_hbox->pack_start( $field_heading, TRUE, TRUE, 0 );
3058             $headings_hbox->pack_start( $operator_heading, TRUE, TRUE, 0 );
3059             $headings_hbox->pack_start( $criteria_heading, TRUE, TRUE, 0 );
3060            
3061             # Add everything to the dialog
3062             my $vbox = Gtk2::VBox->new( 0, 5 );
3063            
3064             my $title = Gtk2::Label->new;
3065             $title->set_markup( "Enter criteria for the search ..." );
3066            
3067             $vbox->pack_start( $title, FALSE, FALSE, 0 );
3068             $vbox->pack_start( $headings_hbox, FALSE, FALSE, 0 );
3069             $vbox->pack_start( $sw, TRUE, TRUE, 0 );
3070             $vbox->pack_start( $hbox, FALSE, FALSE, 0 );
3071            
3072             $self->{find}->{window}->add( $vbox );
3073            
3074             # Show everything
3075             $self->{find}->{window}->show_all;
3076            
3077             }
3078              
3079             sub find_do_search {
3080            
3081             my $self = shift;
3082            
3083             my ( $where_clause, $bind_values );
3084            
3085             # Limit to current recordset?
3086             if ( $self->{disable_full_table_find} ) {
3087             $where_clause = $self->{sql}->{where};
3088             $bind_values = $self->{sql}->{bind_values};
3089             }
3090            
3091             # Loop through criteria array and assemble where clause
3092             for my $criteria ( @{$self->{find}->{criteria}} ) {
3093            
3094             my $operator;
3095             my $iter = $criteria->{operator_combo}->get_active_iter;
3096            
3097             if ( $iter ) {
3098            
3099             my $field_name = $criteria->{field_combo}->get_child->get_text;
3100            
3101             $operator = $criteria->{operator_combo}->get_model->get( $iter, 0 );
3102            
3103             if ( $where_clause ) {
3104             $where_clause .= " and ";
3105             }
3106            
3107             $where_clause .= $field_name . " " . $operator . " " . "?";
3108            
3109             my ( $criteria_text, $criteria_value );
3110            
3111             # We need to put wildcards around a 'like' search
3112             if ( $operator eq "like" ) {
3113             $criteria_text = "%" . $criteria->{criteria_widget}->get_text . "%";
3114             } else {
3115             $criteria_text = $criteria->{criteria_widget}->get_text;
3116             }
3117            
3118             # Check if this field's widget is a combo. If it is, we can't search the table
3119             # on the text entered. We have to look up the text in the combo's model, and
3120             # search on that instead
3121            
3122             my $widget = $self->get_widget( $field_name );
3123            
3124             if ( $widget && ref( $widget ) eq 'Gtk2::ComboBoxEntry' ) {
3125             my $model = $widget->get_model;
3126             my $iter = $model->get_iter_first;
3127             while ( $iter ) {
3128             if ( $criteria_text eq $model->get( $iter, 1 ) ) {
3129             $criteria_value = $model->get( $iter, 0 );
3130             last;
3131             }
3132             $iter = $model->iter_next( $iter );
3133             }
3134             if ( ! defined $criteria_value ) {
3135             # We haven't found a match in the combo's model. Not much we can do
3136             # other than use the text we've been given.
3137             # In cases where the key text is the display text, this would work.
3138             # In other cases, it probably won't
3139             $criteria_value = $criteria_text;
3140             }
3141             } else {
3142             $criteria_value = $criteria_text;
3143             }
3144            
3145             push @{$bind_values}, $criteria_value;
3146            
3147             }
3148            
3149             }
3150            
3151             print "Find Dialog querying with:\n$where_clause\n" . Dumper( $bind_values );
3152            
3153             $self->query(
3154             {
3155             where => $where_clause,
3156             bind_values => $bind_values
3157             }
3158             );
3159            
3160             $self->{find}->{window}->destroy;
3161            
3162             }
3163              
3164             sub find_dialog_add_criteria {
3165            
3166             # Creates a new row of widgets for more criteria for our search operation, and store them
3167             # in an array ( $self->{find}->{criteria} )
3168            
3169             my ( $self, $widget ) = @_;
3170            
3171             # Create 3 widgets for the row
3172             my $field_combo = Gtk2::ComboBoxEntry->new( $self->{find}->{field_model}, 0 );
3173             my $operator_combo = Gtk2::ComboBoxEntry->new( $self->{find}->{operator_model}, 1 );
3174             my $criteria_widget = Gtk2::Entry->new;
3175            
3176             # Set the field if we've been passed one
3177             if ( $widget ) {
3178             $field_combo->get_child->set_text( $widget );
3179             }
3180            
3181             # Create an hbox to hold the 3 widgets
3182             my $hbox = Gtk2::HBox->new( TRUE, 5 );
3183            
3184             # Put widgets into hbox
3185             $hbox->pack_start( $field_combo, TRUE, TRUE, 5 );
3186             $hbox->pack_start( $operator_combo, TRUE, TRUE, 5 );
3187             $hbox->pack_start( $criteria_widget, TRUE, TRUE, 5 );
3188            
3189             # Make a hash of the current criteria widgets
3190             my $new_criteria = {
3191             field_combo => $field_combo,
3192             operator_combo => $operator_combo,
3193             criteria_widget => $criteria_widget
3194             };
3195            
3196             # Append this hash onto our list of all criteria widgets
3197             push @{$self->{find}->{criteria}}, $new_criteria;
3198            
3199             # Add the hbox to the main vbox
3200             $self->{find}->{criteria_vbox}->pack_start( $hbox, FALSE, FALSE, 5 );
3201            
3202             # Show them
3203             $hbox->show_all;
3204            
3205             }
3206              
3207             sub calculator {
3208            
3209             # This pops up a simple addition-only calculator, and returns the calculated value to the calling widget
3210            
3211             my ( $self, $widget ) = @_;
3212            
3213             my $dialog = Gtk2::Dialog->new (
3214             "Gtk2::Ex::DBI calculator",
3215             undef,
3216             "modal",
3217             "gtk-ok" => "ok",
3218             "gtk-cancel" => "reject"
3219             );
3220            
3221             $dialog->set_default_size( 300, 480 );
3222            
3223             # The model
3224             my $model = Gtk2::ListStore->new( "Glib::Double" );
3225            
3226             # Add an initial row data to the model
3227             my $iter = $model->append;
3228             $model->set( $iter, 0, 0 );
3229            
3230             # A renderer
3231             my $renderer = Gtk2::CellRendererText->new;
3232             $renderer->set( editable => TRUE );
3233            
3234             # A column
3235             my $column = Gtk2::TreeViewColumn->new_with_attributes(
3236             "Values",
3237             $renderer,
3238             'text' => 0
3239             );
3240            
3241             # The TreeView
3242             my $treeview = Gtk2::TreeView->new( $model );
3243             $treeview->set_rules_hint( TRUE );
3244             $treeview->append_column($column);
3245            
3246             # A scrolled window to put the TreeView in
3247             my $sw = Gtk2::ScrolledWindow->new( undef, undef );
3248             $sw->set_shadow_type( "etched-in" );
3249             $sw->set_policy( "never", "always" );
3250            
3251             # Add treeview to scrolled window
3252             $sw->add( $treeview );
3253            
3254             # Add scrolled window to the dialog
3255             $dialog->vbox->pack_start( $sw, TRUE, TRUE, 0 );
3256            
3257             # Add a Gtk2::Entry to show the current total
3258             my $total_widget = Gtk2::Entry->new;
3259             $dialog->vbox->pack_start( $total_widget, FALSE, FALSE, 0 );
3260            
3261             # Handle editing in the renderer
3262             $renderer->signal_connect_after( edited => sub {
3263             $self->calculator_process_editing( @_, $treeview, $model, $column, $total_widget );
3264             } );
3265            
3266             # Show everything
3267             $dialog->show_all;
3268            
3269             # Start editing in the 1st row
3270             $treeview->set_cursor( $model->get_path( $iter ), $column, TRUE );
3271            
3272             my $response = $dialog->run;
3273            
3274             if ( $response eq "ok" ) {
3275             # Transfer value back to calling widget and exit
3276             $widget->set_text( $total_widget->get_text );
3277             $dialog->destroy;
3278             } else {
3279             $dialog->destroy;
3280             }
3281            
3282             }
3283              
3284             sub calculator_process_editing {
3285            
3286             my ( $self, $renderer, $text_path, $new_text, $treeview, $model, $column, $total_widget ) = @_;
3287            
3288             my $path = Gtk2::TreePath->new_from_string ($text_path);
3289             my $iter = $model->get_iter ($path);
3290            
3291             # Only do something if we get a numeric value that isn't zero
3292             if ( $new_text !~ /\d/ || $new_text == 0 ) {
3293             return FALSE;
3294             }
3295            
3296             $model->set( $iter, 0, $new_text);
3297             my $new_iter = $model->append;
3298            
3299             $treeview->set_cursor(
3300             $model->get_path( $new_iter ),
3301             $column,
3302             TRUE
3303             );
3304            
3305             # Calculate total and display
3306             $iter = $model->get_iter_first;
3307             my $current_total;
3308            
3309             while ( $iter ) {
3310             $current_total += $model->get( $iter, 0 );
3311             $iter = $model->iter_next( $iter );
3312             }
3313            
3314             $total_widget->set_text( $current_total );
3315            
3316             }
3317              
3318             1;
3319              
3320              
3321             ########################################################################################################
3322              
3323             package Gtk2::Ex::DBI::CalendarButton;
3324              
3325             use Gtk2;
3326              
3327             # this big hairy statement registers our Glib::Object-derived class
3328             # and sets up all the signals and properties for it.
3329              
3330             # TODO Complete CalendarButton functionality
3331             # We want the same as the gnome date combo selector thing
3332             # ( but we can't use it because it doesn't work on Windows / OSX )
3333              
3334             use Glib::Object::Subclass
3335             Gtk2::Button::,
3336             signals => {
3337             clicked => \&on_clicked
3338             },
3339             properties => [
3340             Glib::ParamSpec->string(
3341             "date",
3342             "Date",
3343             "What's the date again?",
3344             "",
3345             [qw(readable writable)]
3346             ),
3347             Glib::ParamSpec->string(
3348             "format",
3349             "Format",
3350             "What format should the date be displayed in?",
3351             "",
3352             [qw(readable writable)]
3353             )
3354             ];
3355              
3356              
3357             1;
3358              
3359             =head1 NAME
3360              
3361             Gtk2::Ex::DBI - Bind a Gtk2::GladeXML - generated window to a DBI data source
3362              
3363             =head1 SYNOPSIS
3364              
3365             use DBI;
3366             use Gtk2 -init;
3367             use Gtk2::GladeXML;
3368             use Gtk2::Ex::DBI;
3369              
3370             my $dbh = DBI->connect (
3371             "dbi:mysql:dbname=sales;host=screamer;port=3306",
3372             "some_username",
3373             "salespass", {
3374             PrintError => 0,
3375             RaiseError => 0,
3376             AutoCommit => 1,
3377             }
3378             );
3379              
3380             my $prospects_form = Gtk2::GladeXML->new("/path/to/glade/file/my_form.glade", 'Prospects');
3381              
3382             my $prospects_form = Gtk2::Ex::DBI->new( {
3383             dbh => $dbh
3384             , sql => {
3385             select => "*"
3386             , from => "Prospects"
3387             }
3388             , form => $prospects
3389             }
3390             );
3391              
3392             =head1 DESCRIPTION
3393              
3394             This class ties data from a DBI datasource to widgets in a Gtk2+ window ( generated by Glade ).
3395             All that is required is that you name your widgets the same as the fields in your data source.
3396             You have to set up combo boxes ( ie create your Gtk2::ListStore and
3397             attach it to your combo box ) *before* creating your Gtk2::Ex::DBI object.
3398              
3399             Steps for use:
3400              
3401             * Open a DBI connection
3402              
3403             * Create a window from a Gtk2::GladeXML object
3404              
3405             * Create a Gtk2::Ex::DBI object and link it to your window
3406              
3407             You would then typically create some buttons ( in your Glade object )
3408             and connect them to the methods below to handle common actions
3409             such as inserting, moving, deleting, etc.
3410              
3411             =head1 METHODS
3412              
3413             =head2 new
3414              
3415             =over 4
3416              
3417             Object constructor. For more info, see section on CONSTRUCTION below.
3418              
3419             =back
3420              
3421             =head2 query ( [ where_object ] )
3422              
3423             =over 4
3424              
3425             Requeries the Database Server, either with the current where clause, or with a new one ( if passed ).
3426              
3427             Version 2.x expects a where_object hash, containing the following keys:
3428              
3429             =back
3430              
3431             =head3 where
3432              
3433             =over 4
3434              
3435             The where key should contain the where clause, with placeholders ( ? ) for each value.
3436             Using placeholders is particularly important if you're assembling a query based on
3437             values taken from a form, as users can initiate an SQL injection attack if you
3438             insert values directly into your where clause.
3439              
3440             =back
3441              
3442             =head3 bind_values
3443              
3444             =over 4
3445              
3446             bind_values should be an array of values, one for each placeholder in your where clause.
3447              
3448             Version 1.x expected to be passed an optional string as a new where clause.
3449             This behaviour is still supported for backwards compatibility. If a version 1.x call
3450             is detected ( ie if where_object isn't a hash ), any existing bind_values will be deleted
3451              
3452             =back
3453              
3454             =head2 insert
3455              
3456             =over 4
3457              
3458             Inserts a new record in the *in-memory* recordset and sets up default values,
3459             either from database defaults, or optionally overridden with values from the
3460             default_values hash. If you're using sequences, the next sequence value will not
3461             be fetched yet; this will happen when the user first starts entering data in the
3462             new record.
3463              
3464             =back
3465              
3466             =head2 count
3467              
3468             =over 4
3469              
3470             Returns the number of records in the current recordset.
3471              
3472             =back
3473              
3474             =head2 move ( offset, [ absolute_position ] )
3475              
3476             =over 4
3477              
3478             Moves to a specified position in the recordset - either an offset, or an absolute position.
3479             If an absolute position is given, the offset is ignored.
3480             If there are changes to the current record, these are applied to the Database Server first.
3481             Returns TRUE if successful, FALSE if unsuccessful.
3482              
3483             =back
3484              
3485             =head2 apply
3486              
3487             =over 4
3488              
3489             Apply changes to the current record back to the Database Server.
3490             Returns TRUE if successful, FALSE if unsuccessful.
3491              
3492             =back
3493              
3494             =head2 revert
3495              
3496             =over 4
3497              
3498             Reverts the current record back to its original state.
3499             Deletes the in-memory recordset if we were inserting a new record.
3500              
3501             =back
3502              
3503             =head2 undo
3504              
3505             =over 4
3506              
3507             Synonym of revert
3508              
3509             =back
3510              
3511             =head2 delete
3512              
3513             =over 4
3514              
3515             Deletes the current record.
3516              
3517             =back
3518              
3519             =head2 set_widget_value( fieldname, value )
3520              
3521             =over 4
3522              
3523             A convenience function to set a widget ( via it's fieldname ) with a given value.
3524             This function will automatically set up data formatting for you ( eg numeric, date ),
3525             based on the assumption that you are giving it data in the format that the database
3526             server likes ( for example, yyyymmdd format dates ).
3527              
3528             =back
3529              
3530             =head2 get_widget( widget_name )
3531              
3532             =over 4
3533              
3534             A convenience function to get a widget. Works with both Glade and GTK Builder.
3535             If no object with the given name exists, we also check if an object with the given name,
3536             starting with $self->{widget_prefix} exists. widget_prefix can be passed in the constructor.
3537              
3538             =back
3539              
3540             =head2 get_widget_value( widget_name )
3541              
3542             =over 4
3543              
3544             Complimentary to the set_widget_value, this will return the value that data in a current
3545             widget REPRESENTS from the database's point of view, ie with formatting stripped. You can
3546             call get_widget_value() on non-managed widgets as well as managed ones.
3547              
3548             =back
3549              
3550             =head2 original_value( fieldname )
3551              
3552             =over 4
3553              
3554             A convenience function that returns the original value of the given field
3555             ( at the current position in the recordset ), since the recordset was last applied.
3556             This is also the ONLY way of fetching the value of a field that is IN the recordset,
3557             but NOT represented by a widget.
3558              
3559             =back
3560              
3561             =head2 sum_widgets( @widget_names )
3562              
3563             =over 4
3564              
3565             Convenience function that returns the sum of all given widgets. get_widget_value() is used
3566             to retrieve each value, which will stips formatting from managed widgets, but you can include
3567             non-managed widgets as well - they just have to be in the same window.
3568              
3569             =back
3570              
3571             =head2 lock
3572              
3573             =over 4
3574              
3575             Locks the current record to prevent editing. This is implemented by setting a value in a field
3576             that you specify as a value of data_lock_field in your constructor. The apply() method is automatically
3577             called when locking, and if apply() fails, lock() also fails.
3578              
3579             =back
3580              
3581             =head2 unlock
3582              
3583             =over 4
3584              
3585             Unlocks a locked record so the user can edit it again.
3586              
3587             =back
3588              
3589             =head2 setup_combo ( widget_name, [ new_where_object ] )
3590              
3591             =over 4
3592              
3593             Creates a new model for the combo of widget_name.
3594             You can use this to refresh the items in a combo's list.
3595             You can optionally pass a hash containing a new where_object
3596             ( where clause and bind_values ).
3597              
3598             You can call this method on widgets that aren't being managed
3599             ( ie aren't in the field list ).
3600              
3601             =back
3602              
3603             =head2 setup_autocompletion ( widget_name, [ new_where_object ] )
3604              
3605             =over 4
3606              
3607             Creates an autocompletion object for a text entry. Autocompletions
3608             are similar to combo boxes, but don't have a button thing. They're
3609             invisible additions to a text entry that magically pop up a list
3610             of values to select from.
3611              
3612             =back
3613              
3614             =head2 calculator ( Gtk2::Widget )
3615              
3616             =over 4
3617              
3618             Opens up a simple calculator dialog that allows the user to enter a list of values
3619             to be added. The result will be applied to the given widget ( which assumes a
3620             set_text() method ... ie a Gtk2::Entry would be a good choice ).
3621              
3622             =back
3623              
3624             =head2 find_dialog ( [ field ] )
3625              
3626             =over 4
3627              
3628             Opens a find 'dialog' ( a window in GTK speak ) that allows the user to query the active
3629             table ( whatever's in the sql->{from} clause ). This will allow them to *alter* the where
3630             clause. If you only want them to be able to *append* to the existing where clause, then
3631             set the disable_full_table_find key to TRUE ( see 'new' method ).
3632              
3633             If an optional field is passed, this will be inserted into the dialog as the first field
3634             in the criteria list.
3635              
3636             Note that the user can currently activate the find_dialog by right-clicking in a text field.
3637             To disable this behaviour, set the disable_find key to TRUE ( see 'new' method ).
3638              
3639             =back
3640              
3641             =head2 position
3642              
3643             =over 4
3644              
3645             Returns the current position in the recordset ( starting at zero ).
3646              
3647             =back
3648              
3649             =head2 fieldlist
3650              
3651             =over 4
3652              
3653             Returns a fieldlist as an array, based on the current query.
3654             Mainly for internal Gtk2::Ex::DBI use
3655              
3656             =back
3657              
3658             =head2 find_dialog
3659              
3660             =over 4
3661              
3662             Pops up a rudimentary 'find' dialog for searching. FIXME :)
3663              
3664             =back
3665              
3666             =head1 INTERNAL METHODS
3667              
3668             =head2 assemble_new_record
3669              
3670             =over 4
3671              
3672             This method is called by the insert method. It actually creates the hash that
3673             represents the new record. It sets default values using either database defaults
3674             or from the default_values hash.
3675              
3676             =back
3677              
3678             =head2 paint
3679              
3680             =over 4
3681              
3682             Paints the form with current data.
3683              
3684             =back
3685              
3686             =head2 fetch_new_slice
3687              
3688             =over 4
3689              
3690             Fetches a new slice of records based on the apeture size.
3691              
3692             =back
3693              
3694             =head2 changed
3695              
3696             =over 4
3697              
3698             Called each time a widget value that Gtk2::Ex::DBI is managing changes.
3699              
3700             =back
3701              
3702             =head2 last_insert_id
3703              
3704             =over 4
3705              
3706             Wrapper around DBI's last_insert_id(), with some smarts for various
3707             broken bits in different drivers.
3708              
3709             =back
3710              
3711             =head2 record_status_label_set
3712              
3713             =over 4
3714              
3715             Sets the record status label.
3716              
3717             =back
3718              
3719             =head2 paint_calculated
3720              
3721             =over 4
3722              
3723             Sets the values of all calculated fields. Called from the changed method.
3724              
3725             =back
3726              
3727             =head2 set_record_spinner_range
3728              
3729             =over 4
3730              
3731             Sets the record spinner range. Nothing to see here.
3732              
3733             =back
3734              
3735             =head2 process_entry_keypress
3736              
3737             =over 4
3738              
3739             Called on every keypress. Injects a 'tab' keypress when the user hits
3740             'enter'.
3741              
3742             =back
3743              
3744             =head2 reset_record_status
3745              
3746             =over 4
3747              
3748             Resets the record status
3749              
3750             =back
3751              
3752             =head2 formatter_number_to_widget
3753              
3754             =over 4
3755              
3756             Performs numeric formatting
3757              
3758             =back
3759              
3760             =head2 formatter_date_to_widget
3761              
3762             =over 4
3763              
3764             Performs date formatting
3765              
3766             =back
3767              
3768             =head2 formatter_number_from_widget
3769              
3770             =over 4
3771              
3772             Opposite of formatter_number_to_widget
3773              
3774             =back
3775              
3776             =head2 formatter_date_from_widget
3777              
3778             =over 4
3779              
3780             Opposite of formatter_date_to_widget
3781              
3782             =back
3783              
3784             =head2 parse_sql_server_default
3785              
3786             =over 4
3787              
3788             Parses default definitions from SQL Server ( v7 )
3789              
3790             =back
3791              
3792             =head2 build_right_click_menu
3793              
3794             =over 4
3795              
3796             Builds the menu that pops up when you right-click most widgets that we manage
3797              
3798             =back
3799              
3800             =head2 set_active_iter_for_broken_combo_box
3801              
3802             =over 4
3803              
3804             This method is called when a ComboBoxEntry's value is changed
3805             See http://bugzilla.gnome.org/show_bug.cgi?id=156017
3806              
3807             Wow ... a 10-year-old bug. They're not going to fix it :(
3808              
3809             =back
3810              
3811             =head2 find_do_search
3812              
3813             =over 4
3814              
3815             Called when user activates search functionality in the find dialog
3816              
3817             =back
3818              
3819             =head2 find_dialog_add_criteria
3820              
3821             =over 4
3822              
3823             Creates a new row of widgets for entering more criteria in the find dialog
3824              
3825             =back
3826              
3827             =head2 calculator_process_editing
3828              
3829             =over 4
3830              
3831             Calculates totals in our calculator
3832              
3833             =back
3834              
3835             =head2 destroy_signal_handlers
3836              
3837             =over 4
3838              
3839             Destroys all signal handlers Gtk2::Ex::DBI has created.
3840              
3841             =back
3842              
3843             =head2 destroy_self
3844              
3845             =over 4
3846              
3847             Internal use only.
3848              
3849             =back
3850              
3851             =head2 destroy
3852              
3853             =over 4
3854              
3855             Destroys itself
3856              
3857             =back
3858              
3859             =head1 CONSTRUCTION
3860              
3861             The new() method expects a hash of key / value pairs.
3862              
3863             =head2 dbh
3864              
3865             =over 4
3866              
3867             A DBI database handle
3868              
3869             =back
3870              
3871             =head2 form
3872              
3873             =over 4
3874              
3875             The Gtk2::GladeXML object to bind to. You need to set either
3876             the 'form' or 'builder' key.
3877              
3878             =back
3879              
3880             =head2 builder
3881              
3882             =over 4
3883              
3884             A Gtk2::Builder object to bind to. You need to set either
3885             the'form' or 'builder' key.
3886              
3887             =back
3888              
3889             =head2 sql
3890              
3891             =over 4
3892              
3893             The sql object describes the query to be executed to fetch your records. Note that in contrast to
3894             version 1.x, all the keywords ( select, from, where, order by, etc ) are *OMMITTED* ... see above
3895             example. This is for consistency and ease of manipulating things. Trust me.
3896              
3897             Minimum requirements for the sql object are the 'select' and 'from' keys, or alternatively a 'pass_through'.
3898             All others are optional.
3899              
3900             Details:
3901              
3902             =back
3903              
3904             =head2 select
3905              
3906             =over 4
3907              
3908             The SELECT clause
3909              
3910             =back
3911              
3912             =head2 from
3913              
3914             =over 4
3915              
3916             The FROM clause
3917              
3918             =back
3919              
3920             =head2 where
3921              
3922             =over 4
3923              
3924             The WHERE clause ( try '0=1' for inserting records )
3925              
3926             =back
3927              
3928             =head2 bind_values
3929              
3930             =over 4
3931              
3932             An array of values to bind to placeholders ... you ARE using placeholders, right?
3933              
3934             =back
3935              
3936             =head2 order_by
3937              
3938             =over 4
3939              
3940             The ORDER BY clause
3941              
3942             =back
3943              
3944             =head2 pass_through
3945              
3946             =over 4
3947              
3948             A command which is passsed directly to the Database Server ( that hopefully returns a recordset ).
3949             If a pass_through key is specified, then this will be used as the SQL command, and all the other keys will be ignored.
3950             You can use this feature to either construct your own SQL directly, which can include executing a stored procedure that
3951             returns a recordset. Recordsets based on a pass_through query will be forced to read_only mode, as updates require that
3952             column_info is available. I'm only currently using this feature for executing stored procedures, and column_info doesn't
3953             work for these. If you want to enable updates for pass_through queries, you'll have to work on getting column_info working ...
3954              
3955             =back
3956              
3957             That's it for essential keys. All the rest are optional.
3958              
3959             =head2 widgets
3960              
3961             =over 4
3962              
3963             The widgets hash contains information particular to each widget, including formatting information and SQL fieldname to widget
3964             name mapping.
3965             See the WIDGETS section for more information.
3966              
3967             =back
3968              
3969             =head2 combos
3970              
3971             =over 4
3972              
3973             The combos hash describes how to set up GtkComboBoxEntry widgets.
3974             See COMBOS section for more informaton.
3975              
3976             =back
3977              
3978             =head2 primary_key
3979              
3980             =over 4
3981              
3982             The PRIMARY KEY of the table you are querying.
3983              
3984             As of version 2.0, the primary key is automatically selected for you if you use MySQL. Note, however,
3985             that this will only work if the FROM clause contains a single table. If you have a multi-table query,
3986             you must specify the primary_key, otherwise the last primary_key encountered will be used. I recommend
3987             against using multi-table queries anyway.
3988              
3989             =back
3990              
3991             =head2 on_current
3992              
3993             =over 4
3994              
3995             A reference to some Perl code to run when moving to a new record
3996              
3997             =back
3998              
3999             =head2 before_query
4000              
4001             =over 4
4002              
4003             A reference to some Perl code to run *before* executing a query.
4004             Your code will be passed the 'where' object. Keep in mind this could either be a scalar or a hash,
4005             depending on how you're using it.
4006             Return TRUE to allow the query method to continue, or FALSE to prevent the query method from continuing.
4007              
4008             =back
4009              
4010             =head2 before_apply
4011              
4012             =over 4
4013              
4014             A reference to some Perl code to run *before* applying the current record.
4015             Return TRUE to allow the apply method to continue, or FALSE to prevent the apply method from continuing.
4016              
4017             =back
4018              
4019             =head2 on_apply
4020              
4021             =over 4
4022              
4023             A reference to some Perl code to run *after* applying the current record.
4024             Your code will be passed a reference to a hash of info about the current record:
4025              
4026             {
4027             status => a string, with possible values: 'inserted', 'changed'
4028             primary_key => the primary key of the record in question
4029             }
4030              
4031             =back
4032              
4033             =head2 on_undo
4034              
4035             =over 4
4036              
4037             A reference to some Perl code to run *after* undo() is called.
4038             This can either be called by your code directly, or could be called if the
4039             user makes changes to a recordset, and then wants to close the form / requery
4040             without applying changes, which will call undo()
4041              
4042             =back
4043              
4044             =head2 on_changed
4045              
4046             =over 4
4047              
4048             A reference to some Perl code that runs *every* time the changed signal is fired.
4049             Be careful - it's fired a LOT, eg every keypress event in entry widgets, etc
4050              
4051             =back
4052              
4053             =head2 on_initial_changed
4054              
4055             =over 4
4056              
4057             A reference to some Perl code that runs *only* when the record status initially changes
4058             for each record ( subsequent changes to the same record won't trigger this code )
4059              
4060             =back
4061              
4062             =head2 auto_apply
4063              
4064             =over 4
4065              
4066             A boolean that will cause datasheets to *automatically* apply changes if a new query is run
4067             while outstanding changes exist, or if the user tries to close a form with outstanding changes
4068             ... ie NO question dialog will appear
4069            
4070             =back
4071              
4072             =head2 calc_fields
4073              
4074             =over 4
4075              
4076             A hash of fieldnames / Perl expressions to provide calculated fields
4077              
4078             =back
4079              
4080             =head2 apeture
4081              
4082             =over 4
4083              
4084             The size of the recordset slice ( in records ) to fetch into memory.
4085             ONLY change this BEFORE querying
4086              
4087             =back
4088              
4089             =head2 record_spinner
4090              
4091             =over 4
4092              
4093             The name of a GtkSpinButton to use as the record spinner. The default is to use a
4094             widget called RecordSpinner. However there are a number of reasons why you may want to
4095             override this. You can simply pass the name of a widget that *doesn't* exist ( ie NONE )
4096             to disable the use of a record spinner. Otherwise you may want to use a widget with a
4097             different name, for example if you have a number of Gtk2::Ex::DBI objects connected to
4098             the same Glade XML project.
4099              
4100             =back
4101              
4102             =head2 friendly_table_name
4103              
4104             =over 4
4105              
4106             This is a string you can use to override the default table name ( ie $self->{sql}->{from} )
4107             in GUI error messages.
4108              
4109             =back
4110              
4111             =head2 manual_spinner
4112              
4113             =over 4
4114              
4115             Disable automatic move() operations when the RecordSpinner is clicked
4116              
4117             =back
4118              
4119             =head2 read_only
4120              
4121             =over 4
4122              
4123             Whether we allow updates to the recordset ( default = FALSE ; updates allowed )
4124              
4125             =back
4126              
4127             =head2 data_lock_field
4128              
4129             =over 4
4130              
4131             The name of a field that controls record locking. If this field contains a non-zero or not null value
4132             at the point of the on_current event, the record will be LOCKED from edits and deletes.
4133             See also the lock() and unlock() methods
4134              
4135             =back
4136              
4137             =head2 defaults
4138              
4139             =over 4
4140              
4141             A HOH of default values to use when a new record is inserted
4142              
4143             =back
4144              
4145             =head2 quiet
4146              
4147             =over 4
4148              
4149             A flag to silence warnings such as missing widgets
4150              
4151             =back
4152              
4153             =head2 status_label
4154              
4155             =over 4
4156              
4157             The name of a label to use to indicate the record status. This is especially useful if you have
4158             more than 1 Gtk2::Ex::DBI object bound to a single Gtk2::GladeXML object
4159              
4160             =back
4161              
4162             =head2 schema
4163              
4164             =over 4
4165              
4166             The schema to query to get field details ( defaults, column types ) ... not required for MySQL
4167              
4168             =back
4169              
4170             =head2 disable_full_table_find
4171              
4172             =over 4
4173              
4174             Don't allow the user to replace the where clause; only append to the existing one
4175              
4176             =back
4177              
4178             =head2 disable_find
4179              
4180             =over 4
4181              
4182             Disable the 'find' item in the right-click menu of GtkText widgets ( ie disable user-initiated searches )
4183              
4184             =back
4185              
4186             =head2 dont_update_keys
4187              
4188             =over 4
4189              
4190             Don't include primary keys in update statements. Some databases don't like that.
4191              
4192             =back
4193              
4194             =head2 widget_prefix
4195              
4196             =over 4
4197              
4198             A string to prefix to widget names when getting hold of them ( ie via Glade or Builder ).
4199             NOTE: we FIRST try without the widget prefix.
4200              
4201             =back
4202              
4203             =head2 auto_incrementing
4204              
4205             =over 4
4206              
4207             A flag ( default ON ) to indicate whether we should try to poll the last inserted ID after
4208             an insert.
4209              
4210             =back
4211              
4212             =head1 WIDGETS
4213              
4214             The widgets hash contains information particular to each managed widget. Each hash item in the widgets hash
4215             should be named after a widget in your Glade XML file. The following are possible keys for each widget:
4216              
4217             =head2 sequence_sql
4218              
4219             =over 4
4220              
4221             If you set this, it can be any SQL that can be executed and then fetched to return a value
4222             that will be used as a sequence. The value will be applied to the current widget. This will happen
4223             each time editing begins in a new record.
4224              
4225             =back
4226              
4227             =head2 sequence_dbh
4228              
4229             =over 4
4230              
4231             If you want your sequence_sql to be executed against a different connection to your main dbh,
4232             pass a sequence_dbh.
4233              
4234             =back
4235              
4236             =head2 sql_fieldname
4237              
4238             =over 4
4239              
4240             The sql_fieldname is, as expected the SQL fieldname. This is the name used in selects, updates, deletes and inserts.
4241             The most common use ( for me ) is to support SQL aliases. For example, if you have a complex window that has a number
4242             of Gtk2::Ex::DBI objects attached to it, you may encounter the situation where you have name clashes. In this case, Gtk2::Ex::DBI
4243             will use the sql_fieldname when talking to the database, but will bind to the widget which matches this widget hash's name.
4244             Another ( perhaps more natural ) way of generating this behaviour is to simply create an alias in your SQL select string.
4245             Gtk2::Ex::DBI parses the select string and populates the sql_fieldname key of the widgets hash where appropriate for you.
4246              
4247             =back
4248              
4249             =head2 number
4250              
4251             =over 4
4252              
4253             This is a HASH of options to control numeric formatting. Possible keys are:
4254              
4255             =back
4256              
4257             =head2 decimal_places
4258              
4259             =over 4
4260              
4261             You can specify the number of decimal places values are rounded to when being displayed. Keep in mind that if a user edits
4262             data, when they apply, the value displayed in the widget will be the one applied. This default to 2 if you set the 'currency'
4263             field.
4264              
4265             =back
4266              
4267             =head2 decimal_fill
4268              
4269             =over 4
4270              
4271             Whether to fill numbers out to the specified number of decimal places. This is automatically selected if you set
4272             the 'currency' field.
4273              
4274             =back
4275              
4276             =head2 currency
4277              
4278             =over 4
4279              
4280             Whether to apply currency formatting to data. It adds a dollar sign before values. It also sets the following options
4281             if they aren't already specified:
4282             - decimal_places - 2
4283             - decimal_fill - TRUE
4284             - separate_thousands - TRUE
4285              
4286             =back
4287              
4288             =head2 percentage
4289              
4290             =over 4
4291              
4292             Whether to convert values to percentage when rendering to a widget. Note that if the number hash exists for a widget,
4293             then Gtk2::Ex::DBI will *always* check for percentages, and convert when necessary, when fetching a value *from* a widget
4294             ( ie whether the percentage key is set or not )
4295              
4296             =back
4297              
4298             =head2 separate_thousands
4299              
4300             =over 4
4301              
4302             Whether to separate each group of 3 digits with a comma before rendering to a widget. If the number hash exists, values
4303             will *always* have commas stripped from them when fetching a value *from* a widget ( ie whether the separate_thousands
4304             key is set or not )
4305              
4306             =back
4307              
4308             =head2 date
4309              
4310             =over 4
4311              
4312             This is a HASH of options controlling date formatting. Possible options are:
4313              
4314             =back
4315              
4316             =head2 format
4317              
4318             =over 4
4319              
4320             This formatter converts dates from the international standard ( yyyy-mm-dd ) to the Australian ( and maybe others )
4321             fomat ( dd-mm-yyyy ). If you use this formatter, you should also use the complementary output_formatter, also called
4322             date_dd-mm-yyyy ... but in the output_formatter array.
4323              
4324             =back
4325              
4326             =head2 strip_time
4327              
4328             =over 4
4329              
4330             This formatter strips off the end of date values. It is useful in cases where the database server returns a DATETIME
4331             value and you only want the DATE portion. Keep in mind that when you apply data, you will only be passing a DATE value
4332             back to the database.
4333              
4334             =back
4335              
4336             =head1 COMBOS
4337              
4338             Gtk2::Ex::DBI uses the GtkComboBoxEntry widget, which is available in gtk 2.4 and above.
4339             To populate the list of options, a model ( Gtk2::ListStore ) is attached to the combo.
4340             Gtk::Ex::DBI expects this model to have the ID in the 1st column, and the String column 2nd column.
4341             You can pack as many other columns in as you like ... at least for now :)
4342              
4343             If you choose to set up each combo's model yourself, you *must* do this before constructing your
4344             Gtk2::Ex::DBI object.
4345              
4346             Alternatively you can pass a hash of combo definitions to the constructor, and they will be set up for you.
4347             If you choose this method, you get a couple of other features for free. You will be able to refresh the combo's
4348             model with the setup_combo() method ( see above ). Users will also be able to trigger this action by right-clicking
4349             in the combo's entry and selecting 'refresh'. You will also get autocompletion set up in the combo's entry widget
4350             ( this is triggered after typing the 1st character in the combo's entry ).
4351              
4352             To make use of the automated combo setup functionality, create a key in the combos hash, with a name that matches
4353             the GtkComboBoxEntry's widget name in your glade xml file. Inside this key, create a hash with the following keys:
4354              
4355             =head2 sql
4356              
4357             =over 4
4358              
4359             A hash of SQL related stuff. Possible keys are:
4360              
4361             =back
4362              
4363             =head2 select
4364              
4365             =over 4
4366              
4367             The select clause that defines the fields you want in your combo
4368              
4369             =back
4370              
4371             =head2 from
4372              
4373             =over 4
4374              
4375             The from clause
4376              
4377             =back
4378              
4379             =head2 where_object
4380              
4381             =over 4
4382              
4383             This can either be a where clause, or a hash with the following keys:
4384              
4385             =back
4386              
4387             =head2 where
4388              
4389             =over 4
4390              
4391             The where key should contain the where clause, with placeholders ( ? ) for each value.
4392             Using placeholders is particularly important if you're assembling a query based on
4393             values taken from a form, as users can initiate an SQL injection attack if you
4394             insert values directly into your where clause.
4395              
4396             =back
4397              
4398             =head2 bind_values
4399              
4400             =over 4
4401              
4402             bind_values should be an array of values, one for each placeholder in your where clause.
4403              
4404             =back
4405              
4406             =head2 order_by
4407              
4408             =over 4
4409              
4410             An 'order by' clause
4411              
4412             =back
4413              
4414             =head2 alternate_dbh
4415              
4416             =over 4
4417              
4418             A DBI handle to use instead of the current Gtk2::Ex::DBI DBI handle
4419              
4420             =back
4421              
4422             =head2 fields ( optional )
4423              
4424             =over 4
4425              
4426             An array of field definitions. Note that the fields array is now optional.
4427             If you ommit it, simply define a 'select' key in the SQL hash, and the
4428             fields will be automatically set up ( with Glib::String types for each column ).
4429             The only reason you'd really want to define a 'fields' array now is to set a
4430             cell_data_func.
4431              
4432             Each field definition is a hash with the following keys:
4433              
4434             =back
4435              
4436             =head2 name
4437              
4438             =over 4
4439              
4440             The SQL fieldname / expression
4441              
4442             =back
4443              
4444             =head2 type
4445              
4446             =over 4
4447              
4448             The ( Glib ) type of column to create for this field in the Gtk2::ListStore. Possible values are
4449             Glib::Int and Glib::String.
4450              
4451             =back
4452              
4453             =head2 cell_data_func ( optional )
4454              
4455             =over 4
4456              
4457             A reference to some perl code to use as this columns's renderer's custom cell_data_func.
4458             You can use this to perform formatting on the column ( or cell, whatever ) based on the
4459             current data. Your function will be passed ( $column, $cell, $model, $iter ), as well as anything
4460             else you pass in yourself.
4461              
4462             =back
4463              
4464             =head1 Class behaviour flags
4465              
4466             =head2 $Gtk2::Ex::DBI::highlight_ok_colour
4467              
4468             =over 4
4469              
4470             The colour to use in various places for 'ok' type highlighting. Currently this only affects
4471             the record status label, when the record is synchronised
4472              
4473             =back
4474              
4475             =head2 $Gtk2::Ex::DBI::highlight_colour
4476              
4477             =over 4
4478              
4479             The colour to use in various places for 'warning' type highlighting. Currently this only affects
4480             the record status label, when the record is locked or changed.
4481              
4482             =back
4483              
4484             =head1 ISSUES
4485              
4486             =head2 SQL Server compatibility
4487              
4488             =over 4
4489              
4490             To use SQL Server, you should use FreeTDS ==> UnixODBC ==> DBD::ODBC. Only this combination supports
4491             the use of bind values in SQL statements, which is a requirement of Gtk2::Ex::DBI. Please
4492             make sure you have the *very* *latest* versions of each.
4493              
4494             The only problem I've ( recently ) encountered with SQL Server is with the 'money' column type.
4495             Avoid using this type, and you should have flawless SQL Server action.
4496              
4497             =back
4498              
4499             =head1 BUGS
4500              
4501             =head2 'destroy' method doesn't currently work
4502              
4503             I don't know what the problem with this is.
4504             I attach a *lot* of signals to widgets. I also go to great lengths to remember them all
4505             and disconnect them later. Perhaps I'm missing one of them? Perhaps it's something else.
4506             Patches gladly accepted :)
4507              
4508             =head1 AUTHORS
4509              
4510             Daniel Kasak - d.j.kasak.dk@gmail.com
4511              
4512             =head1 CREDITS
4513              
4514             Muppet
4515              
4516             - tirelessly offered help and suggestions in response to my endless list of questions
4517              
4518             Gtk2-Perl Authors
4519              
4520             - obviously without them, I wouldn't have gotten very far ...
4521              
4522             Gtk2-Perl list
4523              
4524             - yet more help, suggestions, and general words of encouragement
4525              
4526             =head1 Other cool things you should know about:
4527              
4528             This module is part of an umbrella project, which aims to make
4529             Rapid Application Development of database apps using open-source tools a reality.
4530             The project includes:
4531              
4532             Gtk2::Ex::DBI - forms
4533             Gtk2::Ex::Datasheet::DBI - datasheets
4534             PDF::ReportWriter - reports
4535              
4536             =cut