File Coverage

blib/lib/CLI/Gwrapper/wxGrid.pm
Criterion Covered Total %
statement 22 24 91.6
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 30 32 93.7


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # PODNAME: CLI::Gwrapper::wxGrid
4             # ABSTRACT: CLI::Gwrap graphical wrapper in a wxGrid
5             #
6             # AUTHOR: Reid Augustin
7             # EMAIL: reid@LucidPort.com
8             # CREATED: 07/08/2013 12:08:30 PM
9             #===============================================================================
10              
11 1     1   19 use 5.008;
  1         3  
  1         36  
12 1     1   6 use strict;
  1         1  
  1         33  
13 1     1   5 use warnings;
  1         2  
  1         33  
14              
15             package CLI::Gwrapper::wxGrid;
16              
17 1     1   3 use Moo;
  1         2  
  1         5  
18             extends 'CLI::Gwrapper::Wx::App';
19             with 'CLI::Gwrapper'; # this module must satisfy the Gwrapper role
20 1     1   346 use Types::Standard qw( Str Int Bool ArrayRef CodeRef InstanceOf );
  1         3  
  1         20  
21              
22 1     1   1225 use Carp;
  1         2  
  1         112  
23 1     1   6 use Scalar::Util(qw( looks_like_number ));
  1         2  
  1         55  
24 0           use Wx qw(
25             :sizer
26             :combobox
27             :textctrl
28             wxNOT_FOUND wxID_EXIT
29             wxTELETYPE
30 1     1   457 );
  0            
31             use Wx::Event qw(
32             EVT_BUTTON
33             EVT_COLLAPSIBLEPANE_CHANGED
34             );
35              
36             has 'sizer' => (is => 'rw', isa => InstanceOf['Wx::BoxSizer']);
37             has 'notebook' => (is => 'rw', isa => InstanceOf['Wx::Notebook']);
38             has 'Command_page' => (is => 'rw', isa => InstanceOf['Wx::Panel']);
39             has 'STDOUT_page' => (is => 'rw', isa => InstanceOf['Wx::Panel']);
40             has 'STDERR_page' => (is => 'rw', isa => InstanceOf['Wx::Panel']);
41             has 'STDOUT_text_ctrl' => (is => 'rw', isa => InstanceOf['Wx::TextCtrl']);
42             has 'STDERR_text_ctrl' => (is => 'rw', isa => InstanceOf['Wx::TextCtrl']);
43              
44             our $VERSION = '0.030'; # VERSION
45              
46             sub BUILD {
47             my ($self, $params) = @_;
48              
49             $self->_populate_window($self->panel); # fill in all the parts
50              
51             $self->frame->Show; # put it up on the screen
52             }
53              
54             sub title { # required by Gwrapper role
55             my ($self, $new) = @_;
56              
57             if (@_ > 1) {
58             $self->frame->SetTitle($new);
59             $self->{title} = $new;
60             }
61             return $self->{title};
62             }
63              
64             sub run { # required by Gwrapper role
65             my ($self) = @_;
66              
67             $self->MainLoop; # run the main event loop
68             }
69              
70             sub _populate_window {
71             my ($self, $parent) = @_;
72              
73             $self->notebook(Wx::Notebook->new($parent));
74              
75             my $sizer = Wx::BoxSizer->new(wxVERTICAL);
76             $self->sizer($sizer);
77             $sizer->Add(
78             $self->notebook,
79             1, # proportion
80             wxEXPAND | wxALL, # flags
81             0, # border
82             );
83              
84             $self->_populate_command_page;
85             # add the control buttons at the bottom
86             $sizer->Add(
87             $self->_populate_control_h_boxsizer($parent),
88             0, # proportion
89             wxEXPAND | wxALL, # flags
90             8, # border
91             );
92              
93             $sizer->SetSizeHints($self->frame);
94             $parent->SetSizer( $sizer );
95              
96             $parent->Layout;
97             }
98              
99             sub _populate_command_page {
100             my ($self) = @_;
101              
102             my $parent = $self->_build_page('Command');
103             my $sizer = Wx::BoxSizer->new(wxVERTICAL);
104              
105             $sizer->Add(
106             $self->_populate_cmd_h_boxsizer($parent),
107             0, # proportion
108             wxEXPAND | wxALL, # flags
109             4, # border
110             );
111             if (my $opt_sizer = $self->_populate_args_grid($parent, 'opts')) {
112             $sizer->Add(
113             $opt_sizer,
114             0, # proportion
115             wxEXPAND | wxALL, # flags
116             0, # border
117             );
118             }
119             if ($self->advanced
120             and @{$self->advanced}) {
121             my $collapser = Wx::CollapsiblePane->new(
122             $parent, # parent
123             -1, # window ID
124             'Advanced', # collapser button label
125             );
126             my $pane = $collapser->GetPane;
127             if (my $opt_sizer = $self->_populate_args_grid($pane, 'advanced')) {
128             $pane->SetSizer($opt_sizer);
129             $sizer->Add(
130             $collapser,
131             0, # proportion of 0 recommended for CollapsablePane
132             wxEXPAND | wxALL, # flags
133             0, # border
134             );
135             EVT_COLLAPSIBLEPANE_CHANGED(
136             $parent,
137             $collapser,
138             sub {
139             $self->notebook->InvalidateBestSize;
140             $self->sizer->SetSizeHints($self->frame);
141             # $self->panel->Layout;
142             },
143             );
144             }
145             }
146             $parent->SetSizer($sizer);
147             }
148              
149             # several opt types need a TextCtrl style widget
150             sub _text_ctrl {
151             my ($self, $opt, $parent, $sizer) = @_;
152              
153             my @opts;
154             if (defined $opt->width) {
155             push @opts, [$opt->width, -1];
156             }
157             my $text_ctrl = Wx::TextCtrl->new(
158             $parent, # parent window
159             -1, # window ID
160             $opt->state || '', # label
161             [-1, -1],
162             @opts,
163             );
164              
165             my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
166             $opt->widget($hsizer);
167              
168             my $vsizer = Wx::BoxSizer->new(wxVERTICAL); # this prevents TextCtrl from expanding vertically
169             $hsizer->Add(
170             $vsizer,
171             defined $opt->width ? 0 : 1, # proportion
172             wxALIGN_CENTER_VERTICAL,
173             0, # border
174             );
175              
176             my $vsizer_opts = wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxLEFT;
177             if (not defined $opt->width) {
178             $vsizer_opts |= wxEXPAND;
179             }
180             $vsizer->Add(
181             $text_ctrl,
182             0, # proportion
183             $vsizer_opts,
184             0, # border
185             );
186             my $label = Wx::StaticText->new(
187             $parent, # parent window
188             -1, # window ID
189             $opt->name_for_display($self->verbatim), # label
190             );
191             $hsizer->Add(
192             $label,
193             0, # proportion
194             wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxRIGHT | wxLEFT,
195             4, # border
196             );
197             #$label->SetForegroundColour(Wx::Colour->new(0,255,0));
198              
199             $sizer->Add(
200             $hsizer,
201             0,
202             wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxLEFT | wxRIGHT,
203             4,
204             );
205             $label->SetToolTip($opt->description);
206             $text_ctrl->SetToolTip($opt->description);
207              
208             $opt->retriever(
209             sub {
210             my $last_line = $text_ctrl->GetNumberOfLines - 1;
211             my $text = '';
212             for my $ii (0 .. $last_line) {
213             $text .= $text_ctrl->GetLineText($ii);
214             }
215             return '' if not $text;
216             my $name_for_CLI = $opt->name_for_CLI;
217             my $joiner = $opt->joiner;
218             if ($opt->type eq 'hash') {
219             my @opts;
220             for my $token (split (/\s+/, $text)) {
221             if (defined $token and $token ne '') {
222             if ($name_for_CLI) {
223             $token = qq[$name_for_CLI$joiner$token];
224             }
225             push @opts, $token;
226             }
227             }
228             return join ' ', @opts;
229             }
230             elsif ($opt->type eq 'incremental') {
231             if (looks_like_number($text)) {
232             my $count = $text;
233             $text = "$name_for_CLI " x $count;
234             }
235             else {
236             $text = $name_for_CLI;
237             }
238             }
239             if ($name_for_CLI) {
240             #$text =~ s/"/\\"/g; # escape quote (TODO may need more escapes here?)
241             $text = qq[$name_for_CLI$joiner$text];
242             }
243             return $text;
244             }
245             );
246             }
247              
248             # subs to build widget and attach a retriever function to the opt
249             my %widget_builders = (
250             check => sub {
251             my ($self, $opt, $parent, $sizer) = @_;
252              
253             my $widget = Wx::CheckBox->new(
254             $parent,
255             -1,
256             $opt->name_for_display($self->verbatim), # label
257             [-1, -1],
258             [-1, -1],
259             #wxALIGN_RIGHT, # label to the left
260             );
261             $opt->widget($widget);
262             $widget->SetValue($opt->state);
263             $sizer->Add(
264             $widget,
265             0,
266             wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxLEFT | wxRIGHT,
267             4,
268             );
269             $widget->SetToolTip($opt->description);
270             $opt->retriever(
271             sub {
272             if ($widget->GetValue) {
273             return $opt->name_for_CLI;
274             }
275             return '';
276             }
277             );
278             },
279             radio => sub {
280             my ($self, $opt, $parent, $sizer) = @_;
281              
282             my $hsizer = Wx::BoxSizer->new(wxHORIZONTAL);
283             $opt->widget($hsizer);
284              
285             my $choice = Wx::Choice->new(
286             $parent, # parent
287             -1, # window ID
288             [-1, -1], # position
289             [-1, -1], # size
290             $opt->choices, # the choices
291             0, # style (sorted or not)
292             );
293             $hsizer->Add(
294             $choice,
295             0, # proportion
296             wxEXPAND, # flags
297             0, # border
298             );
299             if (my $state = $opt->state) {
300             for my $ii (0 .. $#{$opt->choices}) {
301             if ($state eq $opt->choices->[$ii]) {
302             $choice->SetSelection($ii);
303             last;
304             }
305             }
306             }
307              
308             my $label = Wx::StaticText->new(
309             $parent, # parent
310             -1, # window ID
311             $opt->name_for_display($self->verbatim), # label
312             );
313             $hsizer->Add(
314             $label,
315             0, # proportion
316             wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxLEFT,
317             10, # border
318             );
319             #$label->SetForegroundColour(Wx::Colour->new(0,0,255));
320             $sizer->Add(
321             $hsizer,
322             0,
323             wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxLEFT | wxRIGHT,
324             4,
325             );
326             $label->SetToolTip($opt->description);
327             $choice->SetToolTip($opt->description);
328              
329             $opt->retriever(
330             sub {
331             my $idx = $choice->GetSelection;
332             return '' if ($idx eq wxNOT_FOUND or $idx == 0);
333             my $choice = $opt->choices->[$idx];
334             if ((my $name_for_CLI = $opt->name_for_CLI) ne '') {
335             return "$name_for_CLI=$choice";
336             }
337             return $choice;
338             }
339             );
340             },
341             string => sub {
342             _text_ctrl(@_);
343             },
344             hash => sub {
345             _text_ctrl(@_);
346             },
347             integer => sub {
348             _text_ctrl(@_);
349             },
350             float => sub {
351             _text_ctrl(@_);
352             },
353             incremental => sub {
354             _text_ctrl(@_);
355             },
356             label => sub {
357             my ($self, $opt, $parent, $sizer) = @_;
358              
359             my $label = Wx::StaticText->new(
360             $parent, # parent window
361             -1, # window ID
362             $opt->name_for_display($self->verbatim), # label
363             );
364              
365             $sizer->Add(
366             $label,
367             0,
368             wxEXPAND | wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT | wxLEFT | wxRIGHT,
369             4,
370             );
371             $label->SetToolTip($opt->description);
372              
373             $opt->retriever(
374             sub {
375             return '';
376             }
377             );
378             }
379             );
380              
381             sub _build_opt_widget {
382             my ($self, $opt, $parent, $sizer) = @_;
383              
384             my $type = $opt->type;
385             my $builder = $widget_builders{$type}
386             || carp("Unknown option type: $type\n");
387             $self->$builder($opt, $parent, $sizer);
388             }
389              
390             # the CLI command, and unnamed opts (if any)
391             sub _populate_cmd_h_boxsizer {
392             my ($self, $parent) = @_;
393              
394             my $grid = Wx::GridSizer->new(1, 2, 0, 0);
395              
396             my $cmd = $self->command->[0]; # label
397             my $long_cmd = $self->command->[1]; # label
398             if ($long_cmd and
399             $long_cmd ne $cmd) {
400             $cmd = "$cmd ($long_cmd)";
401             }
402             my $label = Wx::StaticText->new(
403             $parent, # parent
404             -1, # window ID
405             $cmd, # label
406             );
407             $grid->Add(
408             $label,
409             0, # proportion
410             wxALIGN_CENTRE_VERTICAL | wxALIGN_CENTER | wxLEFT | wxRIGHT,
411             10, # border
412             );
413             $label->SetToolTip($self->description);
414             #$label->SetForegroundColour(Wx::Colour->new(255,0,0));
415              
416             if ($self->main_opt) {
417             my $v = $self->{verbatim} || 0;
418             $self->{verbatim} = 1; # turn verbatim on for this
419             $self->_build_opt_widget($self->main_opt, $parent, $grid);
420             $self->{verbatim} = $v;
421             }
422              
423             return $grid;
424             }
425              
426             sub _populate_args_grid {
427             my ($self, $parent, $name) = @_;
428              
429             return if (not my $opts = @{$self->$name});
430              
431             my $opts_rows = ($opts + $self->columns - 1) / $self->columns;
432             my $grid = Wx::GridSizer->new($opts_rows, $self->columns, 0, 0);
433              
434             for my $opt (@{$self->{$name}}) {
435             $self->_build_opt_widget($opt, $parent, $grid);
436             }
437              
438             return $grid;
439             }
440              
441             my @buttons = (
442             { name => 'Execute', cb => \&onClick_Execute, },
443             { name => 'Help', cb => \&onClick_Help, },
444             { name => 'Close', cb => \&onClick_Close, flags => wxID_EXIT, },
445             );
446              
447             # add Execute, Help, and Done buttons
448             sub _populate_control_h_boxsizer {
449             my ($self, $parent) = @_;
450              
451             my $num_buttons = scalar @buttons;
452             $num_buttons -- if (not $self->help);
453             my $grid = Wx::GridSizer->new(1, , 0, 0);
454              
455             for my $b (@buttons) {
456             next if ($b->{name} eq 'Help' and
457             not $self->help);
458             my $button = Wx::Button->new(
459             $parent, # parent
460             -1, # ID
461             $b->{name}, # button label
462             );
463             # attach callback to button press
464             EVT_BUTTON(
465             $parent, # parent
466             $button, # the button
467             sub { # callback funtion
468             $b->{cb}->($self, @_);
469             },
470             );
471             $grid->Add(
472             $button,
473             1, # proportion
474             wxALIGN_CENTER | wxALIGN_CENTER_HORIZONTAL | wxALL, # flags
475             0, # border
476             );
477             }
478              
479             return $grid;
480             }
481              
482             # Button callbacks
483             sub onClick_Execute {
484             my ($self, $button, $event) = @_;
485              
486             my @cmd_line = (
487             $self->command->[0], # the CLI command
488             );
489             if ($self->main_opt) {
490             my $opt_string = $self->main_opt->retriever->();
491             push @cmd_line, $opt_string if (defined $opt_string and $opt_string ne '');
492             }
493             for my $opt (@{$self->opts}, @{$self->advanced}) {
494             my $opt_string = $opt->retriever->();
495             push @cmd_line, $opt_string if (defined $opt_string and $opt_string ne '');
496             }
497             # printf "Execute: %s\n", join(' ', @cmd_line);
498             $self->_execute_and_show(\@cmd_line, $self->persist);
499              
500             if (not $self->persist) {
501             $self->frame->Close(1);
502             }
503             }
504              
505             sub onClick_Help {
506             my ($self, $button, $event) = @_;
507              
508             my @cmd_line = (
509             $self->command->[0], # the CLI command
510             $self->help, # the option that invokes help
511             );
512             # printf "Help: %s\n", join(' ', @cmd_line);
513             $self->_execute_and_show(\@cmd_line, 'persist');
514             }
515              
516             sub onClick_Close {
517             my ($self, $button, $event) = @_;
518              
519             # print "Close\n";
520             $self->frame->Close(1);
521             }
522              
523             sub _build_page {
524             my ($self, $type) = @_;
525              
526             my $name = "${type}_page";
527             return $self->$name if ($self->$name);
528              
529             my $panel = Wx::Panel->new($self->notebook);
530             $self->$name($panel);
531             $self->notebook->AddPage($panel, $type);
532              
533             return $panel;
534             }
535              
536             sub _build_text_ctrl {
537             my ($self, $type) = @_;
538              
539             my $name = "${type}_text_ctrl";
540             return $self->$name if ($self->$name);
541              
542             my $panel = $self->_build_page($type);
543             my $text_ctrl = Wx::TextCtrl->new(
544             $panel,
545             -1, # window ID
546             '', # text
547             [-1, -1],
548             [-1, -1],
549             wxTE_READONLY | wxTE_PROCESS_TAB | wxTE_MULTILINE | wxHSCROLL | wxTE_RICH,
550             );
551             $self->$name($text_ctrl);
552             my $font = $text_ctrl->GetFont();
553             $font = Wx::Font->new(
554             $font->GetPointSize,
555             wxTELETYPE,
556             $font->GetStyle,
557             $font->GetWeight,
558             $font->GetUnderlined,
559             );
560             $text_ctrl->SetFont($font);
561              
562             my $sizer = Wx::BoxSizer->new(wxHORIZONTAL);
563             $sizer->Add(
564             $text_ctrl,
565             1, # proportion
566             wxEXPAND | wxALL, # flags
567             0, # border
568             );
569              
570             $panel->SetSizer( $sizer );
571              
572             return $text_ctrl;
573             }
574              
575             sub _show_output {
576             my ($self, $type, $text) = @_;
577              
578             if (my $text_ctrl = $self->{"${type}_text_ctrl"}) {
579             $text_ctrl->Remove(0, $text_ctrl->GetLastPosition);
580             }
581              
582             if ($text) {
583              
584             my $page = $self->_build_page($type);
585             my $text_ctrl = $self->_build_text_ctrl($type);
586             if ($type eq 'STDERR') {
587             $text_ctrl->SetDefaultStyle(Wx::TextAttr->new(Wx::Colour->new(255,0,0)));
588             }
589             $text_ctrl->AppendText($text);
590             $self->{"${type}_page"}->Show;
591             # print $text;
592             }
593             elsif ($self->{"${type}_page"}) {
594             $self->{"${type}_page"}->Hide;
595             }
596             }
597              
598             sub _execute_and_show {
599             my ($self, $cmd_line, $persist) = @_;
600              
601             my ($status, $output, $errors) = $self->execute_callback($cmd_line);
602              
603             if (ref $cmd_line eq 'ARRAY') {
604             $cmd_line = join(' ', @{$cmd_line});
605             }
606             $self->frame->SetTitle(sprintf("%s (exit value => $status)", join(' ', $cmd_line)));
607              
608             $self->_show_output('STDOUT', $output);
609             $self->_show_output('STDERR', $errors);
610             }
611              
612             1;
613              
614              
615              
616             =pod
617              
618             =head1 NAME
619              
620             CLI::Gwrapper::wxGrid - CLI::Gwrap graphical wrapper in a wxGrid
621              
622             =head1 VERSION
623              
624             version 0.030
625              
626             =head1 DESCRIPTION
627              
628             CLI::Gwrapper::wxGrid provides a CLI::Gwrapper role using wxperl as the
629             graphics engine. The top level is a Wx::Notebook, beneath which is a row
630             of control buttons (Execute, Help, and Close). Options are presented
631             inside a Wx::Grid on the first page of the Notebook. STDOUT and STDERR are
632             written to a second and third page of the Notebook.
633              
634             =head1 SEE ALSO
635              
636             CLI::Gwrap
637              
638             =head1 AUTHOR
639              
640             Reid Augustin
641              
642             =head1 COPYRIGHT AND LICENSE
643              
644             This software is copyright (c) 2013 by Reid Augustin.
645              
646             This is free software; you can redistribute it and/or modify it under
647             the same terms as the Perl 5 programming language system itself.
648              
649             =cut
650              
651              
652             __END__