root/trunk/Padre/lib/Padre/Wx/Dialog/RegexEditor.pm @ 10473

Revision 10473, 11.0 KB (checked in by azawawi, 7 months ago)

More refactoring to the Regex Editor dialog UI & code. You can now see four non-functioning regex snippet buttons alongside the regex text control.

Line 
1package Padre::Wx::Dialog::RegexEditor;
2
3# The Regex Editor for Padre
4
5use 5.008;
6use strict;
7use warnings;
8use Padre::Wx                  ();
9use Padre::Wx::Icon            ();
10use Padre::Wx::Role::MainChild ();
11
12our $VERSION = '0.56';
13our @ISA     = qw{
14        Padre::Wx::Role::MainChild
15        Wx::Dialog
16};
17
18
19######################################################################
20# Constructor
21
22sub new {
23        my $class  = shift;
24        my $parent = shift;
25
26        # Create the basic object
27        my $self = $class->SUPER::new(
28                $parent,
29                -1,
30                Wx::gettext('Regex Editor'),
31                Wx::wxDefaultPosition,
32                Wx::wxDefaultSize,
33                Wx::wxDEFAULT_FRAME_STYLE,
34        );
35
36        # Set basic dialog properties
37        $self->SetIcon(Padre::Wx::Icon::PADRE);
38        $self->SetMinSize( [ 350, 550 ] );
39
40        # create sizer that will host all controls
41        my $sizer = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
42
43        # Create the controls
44        $self->_create_controls($sizer);
45
46        # Bind the control events
47        $self->_bind_events;
48
49        # Tune the size and position it appears
50        $self->SetSizer($sizer);
51        $self->Fit;
52        $self->CentreOnParent;
53
54        return $self;
55}
56
57
58#
59# A private method that returns a hash of regex groups along with their meaning
60#
61sub _regex_groups {
62        my $self = shift;
63
64        return (
65                '01' => {
66                        label => Wx::gettext('Character Classes'),
67                        value => {
68                                '.'  => Wx::gettext('Any character except a newline'),
69                                '\d' => Wx::gettext('Any decimal digit'),
70                                '\D' => Wx::gettext('Any non-digit'),
71                                '\s' => Wx::gettext('Any whitespace character'),
72                                '\S' => Wx::gettext('Any non-whitespace character'),
73                                '\w' => Wx::gettext('Any word character'),
74                                '\W' => Wx::gettext('Any non-word character'),
75                        }
76                },
77                '02' => {
78                        label => Wx::gettext('Quantifiers'),
79                        value => {
80                                '*'     => Wx::gettext('Zero or more of the preceding block'),
81                                '+'     => Wx::gettext('One or more of the preceding block'),
82                                '?'     => Wx::gettext('Zero or one of the preceding block'),
83                                '{m}'   => Wx::gettext('Exactly m of the preceding block'),
84                                '{m,n}' => Wx::gettext('m to n of the preceding block'),
85                        }
86                },
87                '03' => {
88                        label => Wx::gettext('Miscellaneous'),
89                        value => {
90                                '|'   => Wx::gettext('Alternation'),
91                                '[ ]' => Wx::gettext('Character set'),
92                                '^'   => Wx::gettext('Beginning of line'),
93                                '$'   => Wx::gettext('End of line'),
94                                '\b'  => Wx::gettext('A word boundary'),
95                                '\B'  => Wx::gettext('Not a word boundary'),
96                        }
97                },
98                '04' => {
99                        label => Wx::gettext('Grouping constructs'),
100                        value => {
101                                '( )'   => Wx::gettext('A group'),
102                                '(?: )' => Wx::gettext('Non-capturing group'),
103                                '(?= )' => Wx::gettext('Positive lookahead assertion'),
104                                '(?! )' => Wx::gettext('Negative lookahead assertion'),
105                                '\n'    => Wx::gettext('Backreference to the nth group'),
106                        }
107                }
108        );
109}
110
111sub _create_controls {
112        my ( $self, $sizer ) = @_;
113
114        # Dialog Controls
115
116        my $regex_label = Wx::StaticText->new( $self, -1, Wx::gettext('&Regular Expression:') );
117
118        $self->{regex} = Wx::TextCtrl->new(
119                $self, -1, '', Wx::wxDefaultPosition, Wx::wxDefaultSize,
120                Wx::wxTE_MULTILINE | Wx::wxNO_FULL_REPAINT_ON_RESIZE
121        );
122
123        my $substitute_label = Wx::StaticText->new( $self, -1, Wx::gettext('&Substitute text with:') );
124        $self->{substitute} = Wx::TextCtrl->new(
125                $self, -1, '', Wx::wxDefaultPosition, Wx::wxDefaultSize,
126                Wx::wxTE_MULTILINE | Wx::wxNO_FULL_REPAINT_ON_RESIZE
127        );
128
129        my $original_label = Wx::StaticText->new( $self, -1, Wx::gettext('&Original text:') );
130        $self->{original_text} = Wx::TextCtrl->new(
131                $self, -1, '', Wx::wxDefaultPosition, Wx::wxDefaultSize,
132                Wx::wxTE_MULTILINE | Wx::wxNO_FULL_REPAINT_ON_RESIZE
133        );
134
135        my $matched_label = Wx::StaticText->new( $self, -1, Wx::gettext('&Matched text:') );
136        $self->{matched_text} = Wx::TextCtrl->new(
137                $self, -1, '', Wx::wxDefaultPosition, Wx::wxDefaultSize,
138                Wx::wxTE_MULTILINE | Wx::wxNO_FULL_REPAINT_ON_RESIZE
139        );
140
141        # Modifiers
142        my %m = $self->_modifiers();
143        foreach my $name ( keys %m ) {
144                $self->{$name} = Wx::CheckBox->new(
145                        $self,
146                        -1,
147                        $m{$name}{name},
148                );
149        }
150
151        $self->{menu} = Wx::Menu->new;
152        my %regex_groups = $self->_regex_groups;
153
154        foreach my $code (keys %regex_groups) {
155                my %sub_group = %{$regex_groups{$code}};
156                $self->{$code} = Wx::Button->new(
157                        $self, -1, $sub_group{label},
158                );
159#               $self->{menu}->Append( -1, $subgroup{label} );
160#               $self->{menu}->AppendSeparator();
161#               my $subgroup_value = $subgroup{value};
162#               foreach my $name (keys %subgroup_value) {
163#                       my $menu_item = $self->{menu}->Append( -1, $subgroup_value{$name} );
164#                       $self->{menu}->AppendSeparator();
165#               }
166        }
167#       Wx::Event::EVT_MENU(
168#               $self,
169#               $self->{menu},
170#               sub {
171#               },
172#       );
173
174        # Matching radio button
175        $self->{matching} = Wx::RadioButton->new(
176                $self, -1, Wx::gettext('Matching'),
177        );
178       
179        # Substitution radio button
180        $self->{substituting} = Wx::RadioButton->new(
181                $self, -1, Wx::gettext('Substituting'),
182        );
183
184        # Close button
185        $self->{close_button} = Wx::Button->new(
186                $self, Wx::wxID_CANCEL, Wx::gettext('&Close'),
187        );
188
189        # Dialog Layout
190
191        my $modifiers = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
192        $modifiers->AddStretchSpacer;
193        $modifiers->Add( $self->{ignore_case}, 0, Wx::wxALL, 1 );
194        $modifiers->Add( $self->{single_line}, 0, Wx::wxALL, 1 );
195        $modifiers->Add( $self->{multi_line},  0, Wx::wxALL, 1 );
196        $modifiers->Add( $self->{extended},    0, Wx::wxALL, 1 );
197
198        my $operation = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
199        $operation->AddStretchSpacer;
200        $operation->Add( $self->{matching},     0, Wx::wxALL, 1 );
201        $operation->Add( $self->{substituting}, 0, Wx::wxALL, 1 );
202        $operation->AddStretchSpacer;
203
204        my $regex = Wx::BoxSizer->new(Wx::wxVERTICAL);
205        $regex->Add(
206                $self->{regex},
207                1,
208                Wx::wxALL | Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxEXPAND,
209                1
210        );
211
212        my $regex_groups = Wx::BoxSizer->new(Wx::wxVERTICAL);
213        foreach my $code (keys %regex_groups) {
214                $regex_groups->Add( $self->{$code}, 0, Wx::wxEXPAND | Wx::wxALIGN_CENTER_HORIZONTAL, 1 );
215        }
216       
217        my $combined = Wx::BoxSizer->new(Wx::wxHORIZONTAL);
218        $combined->Add( $regex, 2, Wx::wxALL | Wx::wxEXPAND, 0 );
219        $combined->Add( $regex_groups, 0, Wx::wxALL | Wx::wxEXPAND, 0 );
220
221        # Vertical layout of the left hand side
222        my $left = Wx::BoxSizer->new(Wx::wxVERTICAL);
223        $left->Add( $operation,   0, Wx::wxALL | Wx::wxEXPAND, 2 );
224        $left->Add( $modifiers,   0, Wx::wxALL | Wx::wxEXPAND, 2 );
225        $left->AddSpacer(5);
226        $left->Add( $regex_label, 0, Wx::wxALL | Wx::wxEXPAND, 1 );     
227        $left->Add( $combined,   0, Wx::wxALL | Wx::wxEXPAND, 2 );
228       
229        $left->Add( $substitute_label, 0, Wx::wxALL | Wx::wxEXPAND, 1 );
230        $left->Add(
231                $self->{substitute},
232                1,
233                Wx::wxALL | Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxEXPAND,
234                1
235        );
236
237
238        $left->Add( $original_label, 0, Wx::wxALL | Wx::wxEXPAND, 1 );
239        $left->Add(
240                $self->{original_text},
241                1,
242                Wx::wxALL | Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxEXPAND,
243                1
244        );
245        $left->Add( $matched_label, 0, Wx::wxALL | Wx::wxEXPAND, 1 );
246        $left->Add(
247                $self->{matched_text},
248                1,
249                Wx::wxALL | Wx::wxALIGN_CENTER_HORIZONTAL | Wx::wxEXPAND,
250                1
251        );
252        $left->AddSpacer(5);
253        $left->Add( $self->{close_button}, 0, Wx::wxALIGN_CENTER_HORIZONTAL, 1 );
254
255        # Main sizer
256        $sizer->Add( $left, 1, Wx::wxALL | Wx::wxEXPAND, 5 );
257}
258
259sub _bind_events {
260        my $self = shift;
261
262        Wx::Event::EVT_TEXT(
263                $self,
264                $self->{regex},
265                sub { $_[0]->run; },
266        );
267        Wx::Event::EVT_TEXT(
268                $self,
269                $self->{substitute},
270                sub { $_[0]->run; },
271        );
272        Wx::Event::EVT_TEXT(
273                $self,
274                $self->{original_text},
275                sub { $_[0]->run; },
276        );
277
278        # Modifiers
279        my %m = $self->_modifiers();
280        foreach my $name ( keys %m ) {
281                Wx::Event::EVT_CHECKBOX(
282                        $self,
283                        $self->{$name},
284                        sub {
285                                $_[0]->box_clicked($name);
286                        },
287                );
288        }
289
290        Wx::Event::EVT_RADIOBUTTON(
291                $self,
292                $self->{matching},
293                sub { $_[0]->run; },
294        );
295        Wx::Event::EVT_RADIOBUTTON(
296                $self,
297                $self->{substituting},
298                sub { $_[0]->run; },
299        );
300}
301
302
303#
304# A private method that returns a hash of regex modifiers
305#
306sub _modifiers {
307        my $self = shift;
308        return (
309                ignore_case => { mod => 'i', name => sprintf( Wx::gettext('Ignore case (%s)'), 'i' ) },
310                single_line => { mod => 's', name => sprintf( Wx::gettext('Single-line (%s)'), 's' ) },
311                multi_line  => { mod => 'm', name => sprintf( Wx::gettext('Multi-line (%s)'),  'm' ) },
312                extended    => { mod => 'x', name => sprintf( Wx::gettext('Extended (%s)'),    'x' ) },
313        );
314}
315
316
317# -- public methods
318
319sub show {
320        my $self = shift;
321
322        $self->{regex}->AppendText("regex");
323        $self->{substitute}->AppendText("substitute");
324        $self->{original_text}->AppendText("Original text");
325        $self->{matching}->SetValue(1);
326
327        $self->Show;
328}
329
330sub run {
331        my $self = shift;
332
333        my $regex = $self->{regex}->GetRange( 0, $self->{regex}->GetLastPosition );
334        my $original_text = $self->{original_text}->GetRange( 0, $self->{original_text}->GetLastPosition );
335        my $substitute = $self->{substitute}->GetRange( 0, $self->{substitute}->GetLastPosition );
336
337
338        my $start = '';
339        my $end   = '';
340        my %m     = $self->_modifiers();
341        foreach my $name ( keys %m ) {
342                if ( $self->{$name}->IsChecked ) {
343                        $start .= $m{$name}{mod};
344                } else {
345                        $end .= $m{$name}{mod};
346                }
347        }
348        my $xism = "$start-$end";
349
350        $self->{matched_text}->Clear;
351
352        if ( $self->{matching}->GetValue ) {
353                my $match;
354                eval {
355                        if ( $original_text =~ /(?$xism:$regex)/ )
356                        {
357                                $match = substr( $original_text, $-[0], $+[0] - $-[0] );
358                        }
359                };
360                if ($@) {
361                        $self->{matched_text}->AppendText("Match failure in $regex:  $@");
362                        return;
363                }
364
365                if ( defined $match ) {
366                        $self->{matched_text}->AppendText("Matched '$match'");
367                } else {
368                        $self->{matched_text}->AppendText("No match");
369                }
370        } else {
371                $self->{matched_text}->AppendText("Substitute not yet implemented");
372        }
373
374        return;
375}
376
377sub box_clicked {
378        my $self = shift;
379        $self->run();
380        return;
381}
382
3831;
384
385__END__
386
387=pod
388
389=head1 NAME
390
391Padre::Wx::Dialog::RegexEditor - dialog to make it easy to create a regular expression
392
393=head1 DESCRIPTION
394
395
396The C<Regex Editor> provides an interface to easily create regular
397expressions used in Perl.
398
399The user can insert a regular expression (the surrounding C</> characters are not
400needed) and a text. The C<Regex Editor> will automatically display the matching
401text in the bottom right window.
402
403
404At the top of the window the user can select any of the four
405regular expression modifiers:
406
407=over
408
409=item Ignore case (i)
410
411=item Single-line (s)
412
413=item Multi-line (m)
414
415=item Extended (x)
416
417=back
418
419=head1 TO DO
420
421Implement substitute as well
422
423Global match
424
425Allow the change/replacement of the // around the regular expression
426
427Highlight the match in the source text instead of in
428a separate window
429
430Display the captured groups in a tree hierarchy similar to Rx ?
431
432  Group                  Span (character) Value
433  Match 0 (Group 0)      4-7              the actual match
434
435Display the various Perl variable containing the relevant values
436e.g. the C<@-> and C<@+> arrays, the C<%+> hash
437C<$1>, C<$2>...
438
439point out what to use instead of C<$@> and C<$'> and C<$`>
440
441English explanation of the regular expression
442
443=head1 COPYRIGHT & LICENSE
444
445Copyright 2008-2010 The Padre development team as listed in Padre.pm.
446
447This program is free software; you can redistribute it and/or modify it
448under the same terms as Perl 5 itself.
449
450=cut
451
452# Copyright 2008-2010 The Padre development team as listed in Padre.pm.
453# LICENSE
454# This program is free software; you can redistribute it and/or
455# modify it under the same terms as Perl 5 itself.
Note: See TracBrowser for help on using the browser.