File Coverage

blib/lib/Tickit/Console.pm
Criterion Covered Total %
statement 9 9 100.0
branch n/a
condition n/a
subroutine 3 3 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             # You may distribute under the terms of either the GNU General Public License
2             # or the Artistic License (the same terms as Perl itself)
3             #
4             # (C) Paul Evans, 2011-2014 -- leonerd@leonerd.org.uk
5              
6             package Tickit::Console;
7              
8 1     1   692 use strict;
  1         2  
  1         31  
9 1     1   5 use warnings;
  1         2  
  1         31  
10 1     1   13 use base qw( Tickit::Widget::VBox );
  1         2  
  1         709  
11              
12             our $VERSION = '0.06';
13              
14             use Tickit::Widget::Entry;
15             use Tickit::Widget::Scroller 0.04;
16             use Tickit::Widget::Tabbed 0.003;
17              
18             use Scalar::Util qw( weaken );
19              
20             =head1 NAME
21              
22             C - build full-screen console-style applications
23              
24             =head1 SYNOPSIS
25              
26             my $console = Tickit::Console->new;
27              
28             Tickit->new( root => $console )->run;
29              
30             =head1 DESCRIPTION
31              
32             A C instance is a subclass of L
33             intended to help building a full-screen console-style application which
34             presents the user with one or more scrollable text areas, selectable as tabs
35             on a ribbon, with a text entry area at the bottom of the screen for entering
36             commands or other data. As a L subclass it can be added
37             anywhere within a widget tree, though normally it would be used as the root
38             widget for a L instance.
39              
40             =cut
41              
42             =head1 CONSTRUCTOR
43              
44             =cut
45              
46             =head2 $console = Tickit::Console->new( %args )
47              
48             Returns a new instance of a C. Takes the following named
49             arguments:
50              
51             =over 8
52              
53             =item on_line => CODE
54              
55             Callback to invoke when a line of text is entered in the entry widget.
56              
57             $on_line->( $active_tab, $text )
58              
59             =back
60              
61             =cut
62              
63             sub new
64             {
65             my $class = shift;
66             my %args = @_;
67              
68             my $on_line = delete $args{on_line};
69              
70             my $self = $class->SUPER::new( %args );
71              
72             $self->add(
73             $self->{tabbed} = Tickit::Widget::Tabbed->new(
74             tab_position => "bottom",
75             tab_class => "Tickit::Console::Tab",
76             ),
77             expand => 1,
78             );
79              
80             $self->add(
81             $self->{entry} = Tickit::Widget::Entry->new
82             );
83              
84             weaken( my $weakself = $self );
85             $self->{entry}->set_on_enter( sub {
86             return unless $weakself;
87             my ( $entry ) = @_;
88             my $line = $entry->text;
89             $entry->set_text( "" );
90              
91             my $tab = $weakself->active_tab;
92             if( $tab->{on_line} ) {
93             $tab->{on_line}->( $tab, $line );
94             }
95             else {
96             $on_line->( $tab, $line );
97             }
98             } );
99              
100             return $self;
101             }
102              
103             =head1 METHODS
104              
105             =cut
106              
107             =head2 $tab = $console->add_tab( %args )
108              
109             Adds a new tab to the console, and returns an object representing it.
110              
111             Takes the following named arguments:
112              
113             =over 8
114              
115             =item name => STRING
116              
117             Name for the tab.
118              
119             =item on_line => CODE
120              
121             Optional. Provides a different callback to invoke when a line of text is
122             entered while this tab is active. Invoked the same way as above.
123              
124             =item make_widget => CODE
125              
126             Optional. Gives a piece of code used to construct the actual L
127             used as this tab's child in the ribbon. A C to hold
128             the tab's content will be passed in to this code, which should construct some
129             sort of widget tree with that inside it, and return it. This can be used to
130             apply a decorative frame, place the scroller in a split box or other layout
131             along with other widgets, or various other effects.
132              
133             $tab_widget = $make_widget->( $scroller )
134              
135             =back
136              
137             See L below for more information about the returned object.
138              
139             =cut
140              
141             sub add_tab
142             {
143             my $self = shift;
144             my %args = @_;
145              
146             my $make_widget = $args{make_widget};
147              
148             my $scroller = Tickit::Widget::Scroller->new( gravity => "bottom" );
149              
150             my $widget = $make_widget ? $make_widget->( $scroller ) : $scroller;
151              
152             my $tab = $self->{tabbed}->add_tab(
153             $widget,
154             label => $args{name}
155             );
156              
157             $tab->{on_line} = delete $args{on_line};
158              
159             # Cheating
160             $tab->{scroller} = $scroller;
161             weaken( $tab->{console} = $self );
162              
163             return $tab;
164             }
165              
166             =head2 $index = $console->active_tab_index
167              
168             =head2 $tab = $console->active_tab
169              
170             =head2 $console->remove_tab( $tab_or_index )
171              
172             =head2 $console->move_tab( $tab_or_index, $delta )
173              
174             =head2 $console->activate_tab( $tab_or_index )
175              
176             =head2 $console->next_tab
177              
178             =head2 $console->prev_tab
179              
180             These methods are all passed through to the underlying
181             L object.
182              
183             =cut
184              
185             foreach my $method (qw( active_tab_index active_tab
186             remove_tab move_tab activate_tab next_tab prev_tab )) {
187             no strict 'refs';
188             *$method = sub {
189             my $self = shift;
190             $self->{tabbed}->$method( @_ );
191             };
192             }
193              
194             =head2 $console->bind_key( $key, $code )
195              
196             Installs a callback to invoke if the given key is pressed, overwriting any
197             previous callback for the same key. The code block is invoked as
198              
199             $code->( $console, $key )
200              
201             If C<$code> is missing or C, any existing callback is removed.
202              
203             =cut
204              
205             sub bind_key
206             {
207             my $self = shift;
208             my ( $key, $code ) = @_;
209              
210             $self->{keybindings}{$key}[0] = $code;
211              
212             $self->_update_key_binding( $key );
213             }
214              
215             sub _update_key_binding
216             {
217             my $self = shift;
218             my ( $key ) = @_;
219              
220             my $bindings = $self->{keybindings}{$key};
221              
222             if( $bindings->[0] or $bindings->[1] ) {
223             $self->{entry}->bind_keys( $key => sub {
224             my ( $entry, $key ) = @_;
225             $entry->parent->_on_key( $key );
226             });
227             }
228             else {
229             $self->{entry}->bind_key( $key => undef );
230             }
231             }
232              
233             sub _on_key
234             {
235             my $self = shift;
236             my ( $key ) = @_;
237              
238             if( my $tab = $self->active_tab ) {
239             return 1 if $tab->{keybindings}{$key} and
240             $tab->{keybindings}{$key}->( $tab, $key );
241             }
242              
243             my $code = $self->{keybindings}{$key}[0] or return 0;
244             return $code->( $self, $key );
245             }
246              
247             =head1 TAB OBJECTS
248              
249             =cut
250              
251             package Tickit::Console::Tab;
252             use base qw( Tickit::Widget::Tabbed::Tab );
253              
254             our $VERSION = '0.06';
255              
256             use Tickit::Widget::Scroller::Item::Text;
257             use Tickit::Widget::Scroller::Item::RichText;
258              
259             =head2 $name = $tab->name
260              
261             =head2 $tab->set_name( $name )
262              
263             Returns or sets the tab name text
264              
265             =cut
266              
267             sub name
268             {
269             my $self = shift;
270             return $self->label;
271             }
272              
273             sub set_name
274             {
275             my $self = shift;
276             my ( $name ) = @_;
277             $self->set_label( $name );
278             }
279              
280             =head2 $tab->add_line( $string, %opts )
281              
282             Appends a line of text to the tab. C<$string> may either be a plain perl
283             string, or an instance of L containing formatting tags, as
284             specified by L. Options will be passed to the
285             L used to contain the string.
286              
287             =cut
288              
289             sub add_line
290             {
291             my $self = shift;
292             my ( $string, %opts ) = @_;
293              
294             my $item;
295             if( eval { $string->isa( "String::Tagged" ) } ) {
296             $item = Tickit::Widget::Scroller::Item::RichText->new( $string, %opts );
297             }
298             else {
299             $item = Tickit::Widget::Scroller::Item::Text->new( $string, %opts );
300             }
301              
302             $self->{scroller}->push( $item );
303             }
304              
305             =head2 $tab->bind_key( $key, $code )
306              
307             Installs a callback to invoke if the given key is pressed while this tab has
308             focus, overwriting any previous callback for the same key. The code block is
309             invoked as
310              
311             $result = $code->( $tab, $key )
312              
313             If C<$code> is missing or C, any existing callback is removed.
314              
315             This callback will be invoked before one defined on the console object itself,
316             if present. If it returns a false value, then the one on the console will be
317             invoked instead.
318              
319             =cut
320              
321             sub bind_key
322             {
323             my $self = shift;
324             my ( $key, $code ) = @_;
325              
326             my $console = $self->{console};
327              
328             if( not $self->{keybindings}{$key} and $code ) {
329             $console->{keybindings}{$key}[1]++;
330             $console->_update_key_binding( $key );
331             }
332             elsif( $self->{keybindings}{$key} and not $code ) {
333             $console->{keybindings}{$key}[1]--;
334             $console->_update_key_binding( $key );
335             }
336              
337             $self->{keybindings}{$key} = $code;
338             }
339              
340             =head1 AUTHOR
341              
342             Paul Evans
343              
344             =cut
345              
346             0x55AA;