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; |