Ticket #824: UI.pm

File UI.pm, 6.2 KB (added by Corion, 4 years ago)

Todo/UI.pm - the meat of the todo list

Line 
1package Padre::Plugin::Todo::UI;
2
3use 5.008;
4use strict;
5use warnings;
6use Params::Util qw{ _STRING };
7use Padre::Wx ();
8use Padre::Current ('_CURRENT');
9
10our $VERSION = '0.54';
11our @ISA     = 'Wx::Panel';
12
13#####################################################################
14# Constructor
15
16sub new {
17    my $class = shift;
18    my $main  = shift;
19
20    # Create the parent panel, which will contain the search and tree
21    my $self = $class->SUPER::new(
22        $main->right,
23        -1,
24        Wx::wxDefaultPosition,
25        Wx::wxDefaultSize,
26    );
27
28    # Store main for other methods
29    $self->{main} = $main;
30
31    # Temporary store for the todo list.
32    $self->{_items} = [];
33
34    # Create the search control
35    $self->{search} = Wx::TextCtrl->new(
36        $self, -1, '',
37        Wx::wxDefaultPosition,
38        Wx::wxDefaultSize,
39        Wx::wxTE_PROCESS_ENTER | Wx::wxSIMPLE_BORDER,
40    );
41
42    # Create the Todo list
43    $self->{items} = Wx::ListBox->new(
44        $self,
45        -1,
46        Wx::wxDefaultPosition,
47        Wx::wxDefaultSize,
48        [],
49        Wx::wxLB_SINGLE | Wx::wxBORDER_NONE
50    );
51
52    # Create a sizer
53    my $sizer = Wx::BoxSizer->new(Wx::wxVERTICAL);
54    $sizer->Add( $self->{search},    0, Wx::wxALL | Wx::wxEXPAND );
55    $sizer->Add( $self->{items},     1, Wx::wxALL | Wx::wxEXPAND );
56
57    # Fits panel layout
58    $self->SetSizerAndFit($sizer);
59    $sizer->SetSizeHints($self);
60
61    # Grab the kill focus to prevent deselection
62    Wx::Event::EVT_KILL_FOCUS(
63        $self->{items},
64        sub {
65            return;
66        },
67    );
68
69    # Double-click a function name
70    Wx::Event::EVT_LISTBOX_DCLICK(
71        $self,
72        $self->{items},
73        sub {
74            $self->on_list_item_activated( $_[0], $_[1] );
75        }
76    );
77
78    # Handle key events
79    Wx::Event::EVT_KEY_UP(
80        $self->{items},
81        sub {
82            my ( $this, $event ) = @_;
83            if ( $event->GetKeyCode == Wx::WXK_RETURN ) {
84                $self->on_list_item_activated($event);
85            }
86            $event->Skip(1);
87        }
88    );
89
90    # Handle key events
91    Wx::Event::EVT_CHAR(
92        $self->{search},
93        sub {
94            my ( $this, $event ) = @_;
95
96            my $code = $event->GetKeyCode;
97            if ( $code == Wx::WXK_DOWN || $code == Wx::WXK_UP || $code == Wx::WXK_RETURN ) {
98
99                # Up/Down and return keys focus on the functions lists
100                $self->{items}->SetFocus;
101                my $selection = $self->{items}->GetSelection;
102                if ( $selection == -1 && $self->{items}->GetCount > 0 ) {
103                    $selection = 0;
104                }
105                $self->{items}->Select($selection);
106
107            } elsif ( $code == Wx::WXK_ESCAPE ) {
108
109                # Escape key clears search and returns focus
110                # to the editor
111                $self->{search}->SetValue('');
112                my $current  = _CURRENT( $self->{main}->current );
113                my $document = $current->document;
114                if ($document) {
115                    $document->editor->SetFocus;
116                }
117            }
118
119            $event->Skip(1);
120        }
121    );
122
123    # React to user search
124    Wx::Event::EVT_TEXT(
125        $self,
126        $self->{search},
127        sub {
128            $self->_update_list;
129        }
130    );
131
132    return $self;
133}
134
135sub gettext_label {
136    Wx::gettext('To-do');
137}
138
139
140
141
142
143#####################################################################
144# Event Handlers
145
146sub on_list_item_activated {
147    my ( $self, $event ) = @_;
148
149    # Which sub did they click
150    my $item = $self->{items}->GetSelection;
151
152    my $current  = _CURRENT( $self->{main}->current );
153    my $document = $current->document or return;
154    my $editor   = $document->editor;
155   
156    my $start = $self->{_items}->[$item];
157   
158    unless ( defined $start ) {
159
160        # Couldn't find it
161        return;
162    }
163
164    # Move the selection to the location
165    $editor->goto_pos_centerize($start->{pos});
166
167    return;
168}
169
170#
171# Sets the focus on the search field
172#
173sub focus_on_search {
174    my $self = shift;
175    $self->{search}->SetFocus;
176}
177
178#
179# Refresh the functions list
180#
181sub refresh {
182    my ( $self, $current ) = @_;
183
184    # Flush the list if there is no active document
185    return unless $current;
186    my $document  = $current->document;
187    my $items = $self->{items};
188
189    # Hide the widgets when no files are open
190    if ($document) {
191        $self->{search}->Show(1);
192        $self->{items}->Show(1);
193    } else {
194        $items->Clear;
195        $self->{search}->Hide;
196        $self->{items}->Hide;
197        $self->{_items} = [];
198        return;
199    }
200
201    # Clear search when it is a different document
202    if ( $self->{_document} && $document != $self->{_document} ) {
203        $self->{search}->ChangeValue('');
204    }
205    $self->{_document} = $document;
206
207    my $config  = $self->{main}->config;
208    #my @methods = $document->get_functions; # XXX
209    #my @items = $document->get_todo; # XXX
210    my $text = $document->text_get();
211    my @items;
212    # XXX The
213    while ($text =~ /#\s*(?:TO\s*DO:?|XXX)(?:[ \t]*)(.*?)\s*$/gim) {
214        push @items, { text => $1 || '<no text>', 'pos' => pos($text) }
215    };
216    while ($text =~ /#\s*(Ticket #\d+.*?)/gim) {
217        push @items, { text => $1 || '<no text>', 'pos' => pos($text) }
218    };
219
220    if ( scalar @items == 0 ) {
221        $items->Clear;
222        $self->{_items} = [];
223        return;
224    }
225
226    #if ( $config->main_functions_order eq 'original' ) {
227
228        # That should be the one we got from get_functions
229    #} elsif ( $config->main_functions_order eq 'alphabetical_private_last' ) {
230        #
231    #   # ~ comes after \w
232    #   tr/_/~/ foreach @methods;
233    #   @methods = sort @methods;
234    #   tr/~/_/ foreach @methods;
235    #} else {
236
237        # Alphabetical (aka 'abc')
238        #@items = sort { $a->{text} cmp $b->{text} } @items;
239    #}
240
241    if ( scalar(@items) == scalar( @{ $self->{_items} } ) ) {
242        my $new = join ';', @items;
243        my $old = join ';', @{ $self->{_items} };
244        return if $old eq $new;
245    }
246
247    $self->{_items} = \@items;
248
249    # Show them again
250    $self->{search}->Show;
251    $self->{items}->Show;
252
253    $self->_update_list;
254}
255
256#
257# Populate the list with search results
258#
259sub _update_list {
260    my $self = shift;
261
262    my $items = $self->{items};
263
264    #quote the search string to make it safer
265    my $search_expr = $self->{search}->GetValue();
266    if ( $search_expr eq '' ) {
267        $search_expr = '.*';
268    } else {
269        $search_expr = quotemeta $search_expr;
270    }
271
272    #populate the function list with matching items
273    $items->Clear;
274    foreach my $item ( reverse @{ $self->{_items} } ) {
275        if ( $item->{text} =~ /$search_expr/i ) {
276            $items->Insert( $item->{text}, 0 );
277            # XXX keep track of the data?
278        }
279    }
280}
281
2821;
283
284# Copyright 2008-2010 The Padre development team as listed in Padre.pm.
285# LICENSE
286# This program is free software; you can redistribute it and/or
287# modify it under the same terms as Perl 5 itself.