File Coverage

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


line stmt bran cond sub pod time code
1             package Padre::Plugin::FormBuilder::Dialog;
2            
3 1     1   1360 use 5.008;
  1         3  
  1         39  
4 1     1   6 use strict;
  1         1  
  1         28  
5 1     1   5 use warnings;
  1         2  
  1         32  
6 1     1   6 use Class::Inspector ();
  1         1  
  1         15  
7 1     1   560 use Padre::Unload ();
  0            
  0            
8             use Padre::Plugin::FormBuilder::FBP ();
9             use Padre::Plugin::FormBuilder::Preview ();
10            
11             our $VERSION = '0.04';
12             our @ISA = 'Padre::Plugin::FormBuilder::FBP';
13            
14             # Temporary namespace counter
15             my $COUNT = 0;
16            
17             use constant OPTIONS => qw{
18             translate
19             encapsulation
20             version
21             padre
22             };
23            
24             use constant SINGLE => qw{
25             select
26             preview
27             translate
28             encapsulation
29             version
30             associate
31             generate
32             };
33            
34             use constant COMPLETE => qw{
35             complete_fbp
36             complete_shim
37             };
38            
39             use constant FRAME => qw{
40             complete_app
41             complete_script
42             };
43            
44            
45            
46            
47            
48             ######################################################################
49             # Customisation
50            
51             sub new {
52             my $class = shift;
53             my $main = shift;
54            
55             # Create the dialog
56             my $self = $class->SUPER::new($main);
57             $self->disable( OPTIONS, SINGLE, COMPLETE, FRAME );
58             $self->CenterOnParent;
59            
60             # If we don't have a current project, disable the checkbox
61             my $project = $main->current->project;
62             unless ( $project and $project->isa('Padre::Project::Perl') ) {
63             $self->associate->Disable;
64             }
65            
66             return $self;
67             }
68            
69             sub path {
70             $_[0]->browse->GetPath;
71             }
72            
73             sub selected {
74             $_[0]->select->GetStringSelection;
75             }
76            
77             sub padre_code {
78             !! $_[0]->padre->IsChecked;
79             }
80            
81             sub i18n {
82             $_[0]->translate->GetSelection > 0;
83             }
84            
85             sub i18n_trim {
86             $_[0]->translate->GetSelection > 1;
87             }
88            
89             sub encapsulate {
90             $_[0]->encapsulation->GetSelection == 1;
91             }
92            
93             sub project {
94             my $self = shift;
95             my $path = $self->path or return;
96             $self->ide->project_manager->from_file($path);
97             }
98            
99            
100            
101            
102            
103             ######################################################################
104             # Event Handlers
105            
106             sub browse_changed {
107             my $self = shift;
108             my $path = $self->path;
109            
110             # Flush any existing state
111             $self->{xml} = undef;
112             SCOPE: {
113             my $lock = $self->lock_update;
114             $self->select->Clear;
115             $self->disable( OPTIONS, SINGLE, COMPLETE, FRAME );
116             }
117            
118             # Attempt to load the file and parse out the dialog list
119             local $@;
120             eval {
121             # Load the file
122             require FBP;
123             $self->{xml} = FBP->new;
124             my $ok = $self->{xml}->parse_file($path);
125             die "Failed to load the file" unless $ok;
126            
127             # Extract the dialog list
128             my $list = [
129             sort
130             grep { defined $_ and length $_ }
131             map { $_->name }
132             $self->{xml}->project->forms
133             ];
134             die "No dialogs found" unless @$list;
135            
136             # Find the project for the fbp file
137             my $project = $self->project;
138             if ( $project->isa('Padre::Project::Perl') ) {
139             my $version = $project->version;
140             $self->version->SetValue($version) if $version;
141             }
142            
143             # Populate the dialog list
144             my $lock = $self->lock_update;
145             $self->select->Append($list);
146             $self->select->SetSelection(0);
147            
148             # If any of the dialogs are under Padre:: default the
149             # Padre-compatible code generation to true.
150             if ( grep { /^Padre::/ } @$list ) {
151             $self->padre->SetValue(1);
152             $self->encapsulation->SetSelection(0);
153             $self->translate->SetSelection(1);
154             } else {
155             $self->padre->SetValue(0);
156             $self->encapsulation->SetSelection(0);
157             $self->translate->SetSelection(0);
158             }
159            
160             # Enable the dialog list and buttons
161             $self->enable( OPTIONS, SINGLE, COMPLETE );
162            
163             # We need at least one frame to build a complete application
164             if ( $self->{xml}->project->find_first( isa => 'FBP::Frame' ) ) {
165             $self->enable( FRAME );
166             } else {
167             $self->disable( FRAME );
168             }
169            
170             # Indicate the FBP file is ok
171             if ( $self->browse->HasTextCtrl ) {
172             my $ctrl = $self->browse->GetTextCtrl;
173             $ctrl->SetBackgroundColour(
174             Wx::Colour->new('#CCFFCC')
175             );
176             }
177             };
178             if ( $@ ) {
179             # Indicate the FBP file is not ok
180             if ( $self->browse->HasTextCtrl ) {
181             $self->browse->GetTextCtrl->SetBackgroundColour(
182             Wx::Colour->new('#FFCCCC')
183             );
184             }
185            
186             # Inform the user directly
187             $self->error("Missing, invalid or empty file '$path': $@");
188             }
189            
190             return;
191             }
192            
193             sub generate_clicked {
194             my $self = shift;
195             my $dialog = $self->selected or return;
196             my $fbp = $self->{xml} or return;
197             my $form = $fbp->form($dialog);
198             unless ( $form ) {
199             $self->error("Failed to find form $dialog");
200             return;
201             }
202            
203             # Generate the dialog code
204             my $code = $self->generate_form(
205             fbp => $fbp,
206             form => $form,
207             package => $dialog,
208             padre => $self->padre_code,
209             version => $self->version->GetValue || '0.01',
210             i18n => $self->i18n,
211             i18n_trim => $self->i18n_trim,
212             ) or return;
213            
214             # Open the generated code as a new file
215             $self->show($code);
216            
217             return;
218             }
219            
220             sub preview_clicked {
221             my $self = shift;
222             my $dialog = $self->selected or return;
223             my $fbp = $self->{xml} or return;
224             my $form = $fbp->form($dialog);
225             unless ( $form ) {
226             $self->error("Failed to find form $dialog");
227             return;
228             }
229            
230             # Close any previous frame
231             $self->clear_preview;
232            
233             # Generate the dialog code
234             my $name = "Padre::Plugin::FormBuilder::Temp::Dialog" . ++$COUNT;
235             SCOPE: {
236             local $@ = '';
237             my $code = eval {
238             $self->generate_form(
239             fbp => $fbp,
240             form => $form,
241             package => $name,
242             padre => $self->padre_code,
243             version => $self->version->GetValue || '0.01',
244             i18n => 0,
245             i18n_trim => 0,
246             )
247             };
248             if ( $@ or not $code ) {
249             $self->error("Error generating dialog: $@");
250             $self->unload($name);
251             return;
252             }
253            
254             # Load the dialog
255             eval "$code";
256             if ( $@ ) {
257             $self->error("Error loading dialog: $@");
258             $self->unload($name);
259             return;
260             }
261             }
262            
263             # Create the form
264             local $@;
265             my $preview = eval {
266             $form->isa('FBP::FormPanel')
267             ? Padre::Plugin::FormBuilder::Preview->new( $self->main, $name )
268             : $name->new( $self->main )
269             };
270             if ( $@ ) {
271             $self->error("Error constructing dialog: $@");
272             $self->unload($name);
273             return;
274             }
275            
276             # Handle the ones we can show modally
277             if ( $preview->can('ShowModal') ) {
278             # Show the dialog
279             my $rv = eval {
280             $preview->ShowModal;
281             };
282             $preview->Destroy;
283             if ( $@ ) {
284             $self->error("Dialog crashed while in use: $@");
285             }
286             $self->unload($name);
287             return;
288             }
289            
290             # Show the long way
291             $preview->Show;
292             $self->{frame} = $preview->GetId;
293            
294             return 1;
295             }
296            
297             sub clear_preview {
298             my $self = shift;
299             if ( $self->{frame} ) {
300             my $old = Wx::Window::FindWindowById( delete $self->{frame} );
301             $old->Destroy if $old;
302             }
303             return 1;
304             }
305            
306             sub complete_refresh {
307             my $self = shift;
308            
309             # Show the complete button if any box is ticked
310             foreach my $name ( COMPLETE ) {
311             my $checkbox = $self->$name();
312             next unless $checkbox->IsEnabled;
313             next unless $checkbox->IsChecked;
314             return $self->enable('complete');
315             }
316            
317             # None of the tick boxes are enabled
318             return $self->disable('complete');
319             }
320            
321             sub complete_clicked {
322             my $self = shift;
323             my $fbp = $self->{xml} or return;
324            
325             # This could change lots of files, so lets wrap some
326             # relatively course locking to prevent background task
327             # storms and unneeded database operations.
328             # Also ensure all notebook titles are updated when we are done.
329             my $lock = $self->main->lock('DB', 'REFRESH', 'refresh_notebook');
330            
331             # Prepare the common generation options
332             my @files = ();
333             my %common = (
334             fbp => $fbp,
335             padre => $self->padre_code,
336             version => $self->version->GetValue || '0.01',
337             i18n => $self->i18n,
338             i18n_trim => $self->i18n_trim,
339             shim => $self->complete_shim->IsChecked ? 1 : 0,
340             );
341            
342             # Generate the launch script for the app
343             if ( $self->complete_script->IsChecked ) {
344             my $code = $self->generate_script(%common) or return;
345            
346             # Make a guess at a sensible default name for the script
347             my $file = lc $self->generator(%common)->app_package;
348             $file =~ s/:://g;
349            
350             push @files, $self->show(
351             code => $code,
352             file => File::Spec->catfile( 'script', $file ),
353             );
354             }
355            
356             # Generate the Wx::App root class
357             if ( $self->complete_app->IsChecked ) {
358             my $code = $self->generate_app(%common) or return;
359             push @files, $self->show($code);
360             }
361            
362             # Generate all of the shim dialogs
363             if ( $self->complete_shim->IsChecked ) {
364             foreach my $form ( $fbp->project->forms ) {
365             my $name = $form->name or next;
366            
367             # Generate the class
368             my $code = $self->generate_shim(
369             form => $form,
370             name => $name,
371             %common,
372             ) or next;
373            
374             # Open the generated code as a new file
375             push @files, $self->show($code);
376             }
377             }
378            
379             # Generate all of the FBP dialogs
380             if ( $self->complete_fbp->IsChecked ) {
381             foreach my $form ( $fbp->project->forms ) {
382             my $name = $form->name or next;
383            
384             # Generate the class
385             my $code = $self->generate_form(
386             form => $form,
387             name => $name,
388             %common,
389             ) or next;
390            
391             # Open the generated code as a new file
392             push @files, $self->show($code);
393             }
394             }
395            
396             # Focus on the first document we touched
397             @files = grep { !! $_ } @files;
398             if ( @files ) {
399             my $editor = $files[0]->editor or return;
400             my $notebook = $editor->notebook or return;
401             my $id = $notebook->GetPageIndex($editor);
402             $notebook->SetSelection($id);
403             }
404            
405             return;
406             }
407            
408            
409            
410            
411            
412             ######################################################################
413             # Code Generation Methods
414            
415             # Generate a launch script
416             sub generate_script {
417             my $self = shift;
418             my $perl = $self->generator(@_);
419            
420             # Generate the script code
421             local $@;
422             my $string = eval {
423             $perl->flatten(
424             $perl->script_app
425             );
426             };
427             if ( $@ ) {
428             $self->error("Code Generator Error: $@");
429             return;
430             }
431            
432             return $string;
433             }
434            
435             # Generate the root Wx app class
436             sub generate_app {
437             my $self = shift;
438             my $perl = $self->generator(@_);
439            
440             # Generate the app code
441             local $@;
442             my $string = eval {
443             $perl->flatten(
444             $perl->app_class
445             );
446             };
447             if ( $@ ) {
448             $self->error("Code Generator Error: $@");
449             return;
450             }
451            
452             return $string;
453             }
454            
455             # Generate the class code
456             sub generate_form {
457             my $self = shift;
458             my $perl = $self->generator(@_);
459             my %param = @_;
460            
461             # Generate the class code
462             local $@;
463             my $string = eval {
464             $perl->flatten(
465             $perl->form_class( $param{form} )
466             );
467             };
468             if ( $@ ) {
469             $self->error("Code Generator Error: $@");
470             return;
471             }
472            
473             # Customise the package name if requested
474             if ( $param{package} ) {
475             $string =~ s/^package [\w:]+/package $param{package}/;
476             }
477            
478             return $string;
479             }
480            
481             # Generate the shim code
482             sub generate_shim {
483             my $self = shift;
484             my $perl = $self->generator(@_);
485             my %param = @_;
486            
487             # Generate the class code
488             local $@;
489             my $string = eval {
490             $perl->flatten(
491             $perl->shim_class($param{form})
492             );
493             };
494             if ( $@ ) {
495             $self->error("Code Generator Error: $@");
496             return;
497             }
498            
499             return $string;
500             }
501            
502             # NOTE: Not in use yet, intended for arbitrary class entry later
503             sub dialog_class {
504             my $self = shift;
505             my $name = shift || '';
506             my $main = $self->main;
507            
508             # What class name?
509             my $dialog = Wx::TextEntryDialog->new(
510             $main,
511             Wx::gettext("Enter Class Name"),
512             $self->plugin_name,
513             $name,
514             );
515             while ( $dialog->ShowModal != Wx::wxID_CANCEL ) {
516             my $package = $dialog->GetValue;
517             unless ( defined $package and length $package ) {
518             $self->error("Did not provide a class name");
519             next;
520             }
521             unless ( Params::Util::_CLASS($package) ) {
522             $self->error("Not a valid class name");
523             next;
524             }
525            
526             return $package;
527             }
528            
529             return;
530             }
531            
532            
533            
534            
535            
536             ######################################################################
537             # Support Methods
538            
539             # Display a generated document
540             sub show {
541             my $self = shift;
542             my %param = (@_ == 1) ? ( code => shift ) : @_;
543             my $code = $param{code};
544             my $file = $param{file};
545             my $main = $self->main;
546             my $project = $self->project;
547            
548             # Auto-detect the file name if we can
549             unless ( defined Params::Util::_STRING($file) ) {
550             # Is this a module?
551             if ( $code =~ /^package\s+([\w:]+)/ ) {
552             # Where should the module be on the filesystem
553             my $module = $1;
554             $file = File::Spec->catfile(
555             'lib',
556             split( /::/, $1 )
557             ) . '.pm';
558             }
559             }
560            
561             # If we have a file name and it exists, overwrite the
562             # content in an existing editor rather than making a new
563             # document.
564             if ( defined Params::Util::_STRING($file) ) {
565             my $path = File::Spec->catfile( $project->root, $file );
566            
567             # Do we have the module open
568             my $id = $main->editor_of_file($path);
569             unless ( defined $id ) {
570             # Open the file if it exists on disk
571             if ( -f $path and -r $path ) {
572             # Always use the plural "setup_editors" as
573             # it clears the unused current document and
574             # does update and refresh locking.
575             $main->setup_editors($path);
576             $id = $main->editor_of_file($path);
577             unless ( defined $id ) {
578             warn "Failed to open '$path'";
579             return;
580             }
581             }
582             }
583             if ( defined $id ) {
584             # Apply to the existing file by delta
585             my $editor = $main->notebook->GetPage($id);
586             my $document = $editor->{Document} or return;
587             $document->text_replace($code);
588             return $document;
589             }
590             }
591            
592             # Not open, does not exist, or no special handling
593             my $lock = $main->lock('REFRESH');
594             my $document = $main->new_document_from_string(
595             $code => 'application/x-perl',
596             );
597            
598             # If we have a file name for the new file, set it early.
599             if ( defined Params::Util::_STRING($file) ) {
600             $document->set_filename(
601             File::Spec->catfile( $project->root, $file )
602             );
603             }
604            
605             return $document;
606             }
607            
608             sub generator {
609             my $self = shift;
610             my %param = @_;
611            
612             # Use the version tweaked for Padre?
613             if ( $param{padre} ) {
614             require Padre::Plugin::FormBuilder::Perl;
615             return Padre::Plugin::FormBuilder::Perl->new(
616             project => $param{fbp}->project,
617             version => $param{version},
618             encapsulate => $self->encapsulate,
619             prefix => 2,
620             nocritic => 1,
621             i18n => $param{i18n},
622             i18n_trim => $param{i18n_trim},
623             shim => $param{shim},
624             shim_deep => $param{shim},
625             );
626             }
627            
628             # Just use the normal version
629             require FBP::Perl;
630             return FBP::Perl->new(
631             project => $param{fbp}->project,
632             version => $param{version},
633             nocritic => 1,
634             i18n => $param{i18n},
635             i18n_trim => $param{i18n_trim},
636             shim => $param{shim},
637             shim_deep => $param{shim},
638             );
639             }
640            
641             # Enable a set of controls
642             sub enable {
643             my $self = shift;
644             foreach my $name ( @_ ) {
645             $self->$name()->Enable(1);
646             }
647             return;
648             }
649            
650             # Disable a set of controls
651             sub disable {
652             my $self = shift;
653             foreach my $name ( @_ ) {
654             $self->$name()->Disable;
655             }
656             return;
657             }
658            
659             # Convenience integration with Class::Unload
660             sub unload {
661             my $either = shift;
662             foreach my $package (@_) {
663             Padre::Unload::unload($package);
664             }
665             return 1;
666             }
667            
668             # Convenience
669             sub error {
670             shift->main->error(@_);
671             }
672            
673             1;
674            
675             # Copyright 2008-2012 The Padre development team as listed in Padre.pm.
676             # LICENSE
677             # This program is free software; you can redistribute it and/or
678             # modify it under the same terms as Perl 5 itself.