root/Curses-UI-POE/trunk/POE.pm @ 206

Revision 206, 10.9 KB (checked in by scott, 8 months ago)

Fixed POD

Line 
1# Copyright 2003 by Scott McCoy.  All rights reserved.  Released under
2# the same terms as Perl itself.
3#
4# Portions Copyright 2003 by Rocco Caputo.  All rights reserved.  Released
5# under the same terms as Perl itself.
6#
7# Portions Copyright 2001-2003 by Maurice Makaay and/or Marcus
8# Thiesen.  Released under the same terms as Perl itself.
9
10# Good luck.  Send the author feedback.  Thanks for trying it.  :)
11package Curses::UI::POE; 
12
13use warnings FATAL => "all";
14use strict;
15
16use POE;
17use POSIX qw( fcntl_h );
18use base qw( Curses::UI );
19use Curses::UI::Widget;
20
21# Force POE::Kernel to have ran...stops my warnings...
22# We do it in a BEGIN so there can be no sessions prior
23# to our calling this unless somebody is being really, really bad.
24BEGIN { run POE::Kernel }
25
26*VERSION = \0.040;
27our $VERSION;
28
29use constant TOP => -1;
30
31sub import {
32    my $caller = caller;
33
34    no strict "refs";
35
36    *{ $caller . "::MainLoop" } = \&MainLoop;
37    eval "package $caller; use POE;";
38}
39
40# XXX We assume that there will never be two Curses::UI::POE sessions.
41my @modal_objects;
42my @modal_callbacks;
43
44# The session needed to make curses run in POE.
45sub new {
46    my ($type, %options) = @_;
47    my $self = &Curses::UI::new(@_);
48#   my $self = bless Curses::UI->new, $type;
49#   my $self = bless &Curses::UI::new(@_), $type;
50
51    # I have to do this here, because if our first order of business is a
52    # dialog then the _start event will be too late.  This self reference is
53    # just so we can stack and peel onto the list of modal objects, and get to
54    # ourselves when we reach the top.
55    push @modal_objects, $self;
56
57    $self->{options}            = \%options;
58    $self->{__start_callback}   = delete $options{inline_states}{_start};
59
60    # Default so we don't get a warning about using undef
61    $options{package_states}  ||= [];
62    $options{object_states}   ||= [];
63    $options{inline_states}   ||= {};
64    $options{options}         ||= {};
65
66    POE::Session->create
67        ( options        => $options{options},
68          args           => $options{args},
69          inline_states  => $options{inline_states},
70          package_states => $options{package_states},
71
72          object_states  => [
73            @{ $options{object_states} },
74            $self, [ qw( _start init keyin timer shutdown ) ]
75          ],
76         
77          # This is to maintain backward compatibility.
78          heap => $self );
79
80    # Copy the no-output option
81    $self->{-no_output} = $options{-no_output} || 0;
82
83    return $self;
84}
85
86# Wait until the kernel actually starts before we muck with things.
87sub _start { $_[KERNEL]->yield("init") }
88
89sub init {
90    my ($self, $kernel) = @_[ OBJECT, KERNEL ];
91
92    $kernel->select(\*STDIN, "keyin");
93
94    # Turn blocking back on for STDIN.  Some Curses
95    # implementations don't deal well with non-blocking STDIN.
96    my $flags = fcntl STDIN, F_GETFL, 0 or die $!;
97    fcntl STDIN, F_SETFL, $flags & ~O_NONBLOCK or die $!;
98
99    # If we're in a dialog, then the TOP modal object is more appropriate than
100    # $self, although if we're not in a dialog $self is what this actually is.
101    set_read_timeout($modal_objects[TOP]);
102
103    # When gpm_mouse isn't enabled, sometimes there is extra garbage during
104    # startup.  We ignore that garbage during construction, assuming that since
105    # the UI isn't rendered yet (we're still creating the root object!) the
106    # input must not matter.
107    $self->flushkeys;
108
109    # Unmask...
110    $self->{__start_callback}(@_)
111        if defined $self->{__start_callback};
112}
113
114sub _clear_modal_callback {
115    my ($self) = @_;
116
117    my $top     = pop @modal_objects;
118
119    # Reset focus
120    $top->{-focus} = 0;
121
122    # Dispatch callback.
123    my $args    = pop @modal_callbacks;
124    my $sub     = shift @$args;
125    &{$sub}(@$args);
126}
127
128sub keyin {
129    my ($self, $kernel) = @_[ OBJECT, KERNEL ];
130
131
132    until ((my $key = $self->get_key(0)) eq -1) {
133        $self->feedkey($key);
134
135        unless ($#modal_objects) {
136            $self->do_one_event;
137        }
138        else {
139            # dispatch the event to the top-most modal object, or the root.
140            $self->do_one_event($modal_objects[TOP]);
141        }
142    }
143
144    # Set the root cursor mode
145    unless ($self->{-no_output}) {
146        Curses::curs_set($self->{-cursor_mode});
147    }
148}
149
150sub timer {
151    my ($self) = @_;
152
153    # dispatch the event to the top-most modal object, or the root.
154    my $top_object = $modal_objects[TOP];
155
156    $top_object->do_timer;
157
158    # Set the root cursor mode.
159    unless ($self->{-no_output}) {
160        Curses::curs_set($self->{-cursor_mode});
161    }
162
163    set_read_timeout($top_object);
164}
165
166sub shutdown {
167    my ($kernel) = $_[ KERNEL ];
168
169    # Unselect stdin
170    $kernel->select(\*STDIN);
171}
172
173sub mainloop {
174    my ($this) = @_;
175
176    unless ($this->{-no_output}) {
177        $this->focus(undef, 1);
178        $this->draw;
179
180        Curses::doupdate;
181    }
182
183
184
185    no warnings "redefine";
186
187    my $modalfocus = \&Curses::UI::Widget::modalfocus;
188
189    # Let modalfocus() be a reentrant into the POE Kernel.  This is stackable,
190    # so it should not impact other behaviors, and POE keeps chugging along
191    # uneffected.  This is a modal focus without a callback, this method does
192    # not return until the modal widget get's cleared out.
193    #
194    # This is done here so that ->dailog will still work as it did previously.
195    # until this is run.  And just in case, we save the old modalfocus
196    # definition and redefine it later.
197    sub Curses::UI::Widget::modalfocus () {
198        my ($this) = @_;
199
200        # "Fake" focus for this object.
201        $this->{-has_modal_focus} = 1;
202        $this->focus;
203        $this->draw;
204
205        push @modal_objects, $this;
206        push @modal_callbacks, undef;
207
208        # This is reentrant into the POE::Kernel
209        while ( $this->{-has_modal_focus} ) {
210            $poe_kernel->loop_do_timeslice;
211        }
212
213        $this->{-focus} = 0;
214
215        pop @modal_callbacks;
216        pop @modal_objects;
217
218        return $this;
219    }
220
221    POE::Kernel->run;
222
223    # Replace previously defined method into the symbol table.
224    *{"Curses::UI::Widget::modalfocus"} = $modalfocus;
225}
226
227sub set_read_timeout {
228    my $this = shift; 
229
230    my $new_timeout = -1;
231
232    while (my ($id, $config) = each %{$this->{-timers}}) {
233        next unless $config->{-enabled};
234
235        $new_timeout = $config->{-time}
236        unless $new_timeout != -1 and
237            $new_timeout < $config->{-time};
238    }
239
240    $poe_kernel->delay(timer => $new_timeout) if $new_timeout >= 0;
241
242    # Force the read timeout to be 0, so Curses::UI polls.
243    $this->{-read_timeout} = 0;
244
245    return $this;
246}
247
248{
249    no warnings "redefine";
250    # None of this work's if POE isn't running...
251    # Redefine the callbackmodalfocus to ensure that callbacks and objects make
252    # it on to our own private stack.
253    sub Curses::UI::Widget::callbackmodalfocus {
254        my ($this, $cb) = @_;
255
256        # "Fake" focus for this object.
257        $this->{-has_modal_focus} = 1;
258        $this->focus;
259        $this->draw;
260
261        push @modal_objects, $this;
262
263        if (defined $cb) {
264            # They need a callback, so register it.
265            push @modal_callbacks, $cb;
266        } else {
267            # Push a null callback.
268            push @modal_callbacks, [sub { }];
269        }
270
271        # We assume our callers are going to return immediately back to the
272        # main event loop, so we don't need a recursive call.       
273        return;
274    }
275
276}
277
278=head1 NAME
279
280Curses::UI::POE - A subclass makes Curses::UI POE Friendly.
281
282=head1 SYNOPSIS
283
284 use Curses::UI::POE;
285
286 my $cui = new Curses::UI::POE inline_states => {
287     _start => sub {
288         $_[HEAP]->dialog("Hello!");
289     },
290
291     _stop => sub {
292         $_[HEAP]->dialog("Good bye!");
293     },
294 };
295
296 $cui->mainloop
297
298=head1 INTRODUCTION
299
300This is a subclass for Curses::UI that enables it to work with POE.
301It is designed to simply slide over Curses::UI.  Keeping the API the
302same and simply forcing Curses::UI to do all of its event handling
303via POE, instead of internal to itself.  This allows you to use POE
304behind the scenes for things like networking clients, without Curses::UI
305breaking your programs' functionality.
306
307=head1 ADDITIONS
308
309This is a list of distinct changes between the Curses::UI API, and the
310Curses::UI::POE API.  They should all be non-obstructive additions only,
311keeping Curses::UI::POE a drop-in replacement for Curses::UI.
312
313=head2 Constructor Options
314
315=over 2
316
317=item inline_states
318
319The inline_states constructor option allows insertion of inline states
320into the Curses::UI::POE controlling session.  Since Curses::UI::POE is
321implimented with a small session I figured it may be useful provide the
322ability to the controlling session for all POE to Interface interaction.
323
324While Curses::UI events are still seamlessly forced to use POE, this allows
325you to use it for a little bit more, such as catching responses from another
326POE component that should be directly connected with output.  (See the IRC
327client example).
328
329In this controlling session, however, the heap is predefined as the root
330Curses::UI object, which is a hash reference.  In the Curses::UI object,
331all private data is indexed by a key begining with "-".  So if you wish
332to use the heap to store other data, simply dont use the "-" hash index
333prefix to avoid conflicts.
334
335=back
336
337=head1 TIMERS
338
339The undocumented Curses::UI timers ($cui->timer) will still work, and
340they will be translated into POE delays.  I would suggest not using them,
341however, as POE's internal alarms and delays are far more robust.
342
343=head1 DIALOGS
344
345The Curses::UI::POE dialog methods contain thier own miniature event loop,
346similar to the way Curses::UI's dialog methods worked.  However instead
347of blocking and polling on readkeys, it incites its own custom miniature
348POE Event loop until the dialog has completed, and then its result is
349returned as per the Curses::UI specifications.
350
351=head1 MODALITY
352
353Curses::UI::POE builds its own internal modality structure.  This allows
354Curses::UI to manage it, and POE to issue the (hopefully correct) events.
355To do this it uses its own custom (smaller) event loop, which is reentrant
356into the POE::Loop in use (In this case, usually POE::Loop::Select).  This
357way there can be several recursed layers of event loops, forcing focus on
358the current modal widget, without stopping other POE::Sessions from running.
359
360=head1 SEE ALSO
361
362L<POE>, L<Curses::UI>.  Use of this module requires understanding of both
363the Curses::UI widget set and the POE Framework.
364
365=head1 BUGS
366
367=over 2
368
369=item Dialogs before ->mainloop()
370
371Dialogs before Curses::UI::Mainloop
372
373=back
374
375Find more?  Send them to me!  tag@cpan.org
376
377=head1 AUTHOR
378
379=over 2
380
381=item Rocco Caputo (rcaputo@cpan.org)
382
383Rocco has helped in an astronomical number of ways.  He helped me work out
384a number of issues (including how to do this in the first place) and atleast
385half the code if not more came from his fingertips.
386
387=back
388
389=head1 MAINTAINER
390
391=over 2
392
393=item Scott McCoy (tag@cpan.org)
394
395This was my stupid idea.  I also got to maintain it, although the original
396code (some of which may or may not still exist) came from Rocco.
397
398=back
399
400=cut
401
4021;
Note: See TracBrowser for help on using the browser.