File Coverage

blib/lib/POE/XUL/Session.pm
Criterion Covered Total %
statement 54 232 23.2
branch 0 64 0.0
condition 0 3 0.0
subroutine 18 44 40.9
pod 1 1 100.0
total 73 344 21.2


line stmt bran cond sub pod time code
1             package # Hide from the CPAN indexer
2             POE::XUL::Session;
3             # $Id$
4             # Copyright Philip Gwyn 2007-2010. All rights reserved.
5              
6 1     1   6 use strict;
  1         2  
  1         32  
7 1     1   4 use warnings;
  1         1  
  1         21  
8 1     1   4 use Carp;
  1         1  
  1         48  
9              
10             our $VERSION = '0.0601';
11              
12 1     1   4 use POE;
  1         1  
  1         5  
13 1     1   237 use base qw(POE::Session);
  1         1  
  1         92  
14              
15 1     1   4 use POE::XUL::Logging;
  1         1  
  1         50  
16              
17 1     1   4 use POSIX qw( ENOSYS );
  1         2  
  1         8  
18 1     1   46 use Carp;
  1         5  
  1         33  
19              
20 1     1   4 use constant DEBUG => 0;
  1         1  
  1         644  
21              
22             ################################################################
23             my %SELVES;
24             sub create
25             {
26 0     0 1   my( $package, $application ) = @_;
27              
28 0           return $package->SUPER::create(
29             inline_states => {
30             _start => \&_never,
31             _stop => \&_never,
32             },
33             args => [ $application ]
34             );
35             }
36              
37 0     0     sub _never { confess "Never invoke me" }
38              
39              
40             sub _get_self
41             {
42 0     0     my( $session ) = @_;
43 0 0         return $session unless $session->isa( 'POE::Session' );
44 0           return $SELVES{ $session->ID };
45             }
46              
47             ################################################################
48             sub _invoke_state {
49 0     0     my( $session, $source_session, $state, $etc, $file, $line,
50             $source_state ) = @_;
51              
52 0 0         if( $state eq '_stop' ) {
    0          
53 0           delete $SELVES{ $session->ID };
54 0           DEBUG and xwarn "_stop";
55 0           return;
56             }
57             elsif( $state eq '_start' ) {
58 0           my $self = $SELVES{ $session->ID } =
59             POE::XUL::Session::Delegate->new( $session, $etc->[0] );
60 0           return $self->_start();
61             }
62              
63 0           my $self = $session->_get_self;
64              
65 0           DEBUG and xwarn "Invoking $state\n";
66              
67              
68 0 0         if ($session->[POE::Session::SE_OPTIONS]->{+POE::Session::OPT_TRACE}) {
69 0           xwarn( $POE::Kernel::poe_kernel->ID_session_to_id($session),
70             " -> $state (from $file at $line)\n"
71             );
72             }
73              
74 0           my $handler = $session->[POE::Session::SE_STATES]->{$state};
75              
76             # The desired destination state doesn't exist in this session.
77             # Attempt to redirect the state transition to _default.
78 0 0         unless ( $handler ) {
79 0           $handler = $session->[POE::Session::SE_STATES]->{+POE::Session::EN_DEFAULT};
80 0 0         unless( $handler ) {
81 0           $! = ENOSYS;
82 0 0         if ($session->[POE::Session::SE_OPTIONS]->{+POE::Session::OPT_DEFAULT}) {
83 0           xwarn( "a '$state' state was sent from $file at $line to session ",
84             $POE::Kernel::poe_kernel->ID_session_to_id($session),
85             ", but session ",
86             $POE::Kernel::poe_kernel->ID_session_to_id($session),
87             " has neither that state nor a _default state to handle it\n"
88             );
89             }
90 0           DEBUG and xwarn "No handler for $state";
91 0           return undef;
92             }
93             }
94              
95 0           local $POE::XUL::Application::window = $self->{main_window};
96 0           local $POE::XUL::Application::server = $self;
97 0           local $POE::XUL::Node::CM = $self->{CM};
98 0           local $self->{source_session} = $source_session;
99 0           local $self->{source_state} = $source_state;
100 0           local $self->{source_file} = $file;
101 0           local $self->{source_line} = $line;
102 0           local $self->{current_state} = $state;
103              
104             # warn "P::X::App:window=$POE::XUL::Application::window";
105             # warn "M::App::window=".My::Application::window();
106              
107 0           my $wa = wantarray;
108             # DEBUG and xwarn "${state}'s handler is $handler, wantarray=", (defined $wa ? $wa : '' );
109              
110 0           my( $OK, @ret );
111 0 0         if( $wa ) {
112 0           eval {
113 0 0         if( 'CODE' eq ref $handler ) {
114 0           @ret = $handler->( @$etc );
115             }
116             else {
117 0           my( $object, $method ) = @$handler;
118 0           @ret = $object->$method( @$etc );
119             }
120 0           $OK = 1;
121             };
122             }
123             else {
124 0           my $ret;
125 0           eval {
126 0 0         if( 'CODE' eq ref $handler ) {
127 0           $ret[0] = $handler->( @$etc );
128             }
129             else {
130 0           my( $object, $method ) = @$handler;
131 0           $ret[0] = $object->$method( @$etc );
132             }
133 0           $OK = 1;
134             };
135             }
136              
137 0 0         if( $OK ) {
138 0 0         return @ret if wantarray;
139 0           return $ret[0];
140             }
141              
142 0           $self->event_error( "PERL ERROR: $@" );
143             }
144              
145             #############################################################################
146             package POE::XUL::Session::Delegate;
147              
148 1     1   5 use strict;
  1         1  
  1         18  
149 1     1   3 use warnings;
  1         1  
  1         38  
150              
151 1     1   6 use POE::Kernel;
  1         2  
  1         7  
152 1     1   44 use POE::XUL::Logging;
  1         1  
  1         51  
153              
154 1     1   3 use constant DEBUG => 0;
  1         2  
  1         46  
155 1     1   3 use Carp;
  1         1  
  1         40  
156 1     1   1369 use Devel::Peek;
  1         438  
  1         5  
157 1     1   100 use Data::Dumper;
  1         1  
  1         329  
158              
159             ################################################################
160             sub new
161             {
162 0     0     my( $package, $session, $application ) = @_;
163 0           return bless {
164             session => $session->ID,
165             application => $application
166             }, $package;
167             }
168              
169             ################################################################
170             sub _start
171             {
172 0     0     my( $self ) = @_;
173 0           DEBUG and xwarn "$$: _start ", $self->SID;
174 0           $poe_kernel->alias_set( $self->SID );
175 0           $poe_kernel->alias_set( $self );
176 0           $poe_kernel->state( boot => $self );
177 0           $poe_kernel->state( timeout => $self );
178 0           $poe_kernel->state( shutdown => $self );
179 0           $poe_kernel->state( connect => $self );
180 0           $poe_kernel->state( disconnect => $self );
181 0           return;
182             }
183              
184             ################################################################
185             sub ID
186             {
187 0     0     my( $self ) = @_;
188 0           return $self->{session};
189             }
190              
191             sub SID
192             {
193 0     0     my( $self ) = @_;
194 0           return $self->{application}->SID;
195             }
196              
197             sub session
198             {
199 0     0     my( $self ) = @_;
200 0 0         return unless $self->{session};
201 0           return $poe_kernel->ID_id_to_session( $self->{session} );
202             }
203              
204 0     0     sub sender_file { $_[0]->{source_file} }
205 0     0     sub sender_line { $_[0]->{source_line} }
206 0     0     sub sender_state { $_[0]->{source_state} }
207 0     0     sub sender_session { $_[0]->{source_session} }
208 0     0     sub current_state { $_[0]->{current_state} }
209              
210             ################################################################
211             # Initial boot request
212             sub boot
213             {
214 0     0     my( $self, $event ) = @_;
215 0           $self->{name} = $event->app;
216 0           $self->{CM} = $event->CM;
217 1     1   4 use Data::Dumper;
  1         1  
  1         997  
218 0 0         $self->{name} or die Dumper $event;
219              
220             # we didn't have a CM until now, so _invoke_state didn't set it
221 0           local $POE::XUL::Node::CM = $self->{CM};
222              
223 0           xlog "Boot $self->{name}";
224              
225 0           $self->{application}->boot( $event );
226              
227 0 0         unless( $self->{booted} ) {
228 0           xlog "Application didn't Boot(), using $self->{name}";
229 0           POE::XUL::Node::Boot( $self->{name} );
230             }
231              
232             croak "You must create a Window during $self->{SID}/boot"
233 0 0         unless $self->{main_window};
234 0           $event->handled;
235 0           return;
236             }
237              
238             ################################################################
239             # POE::XUL::Node::Boot telling us the boot message
240             sub Boot
241             {
242 0     0     my( $self, $msg ) = @_;
243 0           $self->{booted} = 1;
244             }
245              
246              
247              
248             ################################################################
249             ## window->open creates a temporary window (TWindow)
250             ## It, in turn, tells us so
251             sub attach_subwindow
252             {
253 0     0     my( $self, $twindow ) = @_;
254              
255             # save the twindow until we get the 'connect' event
256 0           $self->{subwindows}{ $twindow->id } = $twindow;
257 0           return;
258             }
259              
260              
261             ################################################################
262             ## New sub-window connect request
263             sub connect
264             {
265 0     0     my( $self, $event ) = @_;
266 0           my $winID = $event->window;
267 0           xlog "Connect $winID";
268              
269 0           my $twindow = delete $self->{subwindows}{ $winID };
270 0 0         die "Connect $winID, but we don't have that TWindow." unless $twindow;
271              
272 0           $twindow->create_window();
273 0           $twindow->dispose();
274              
275 0           $self->window_call( $event, 'Connect' );
276              
277 0           $event->handled;
278 0           return;
279             }
280              
281             ################################################################
282             ## Sub-window disconnect request
283             sub disconnect
284             {
285 0     0     my( $self, $event ) = @_;
286 0           my $winID = $event->window->id;
287 0           xlog "Disconnect $winID";
288              
289 0           $self->window_call( $event, 'Disconnect' );
290              
291             # delete the window, and all sub-elements
292 0           $event->window->dispose;
293 0           $event->set( window => undef );
294              
295 0           $event->handled;
296 0           return;
297             }
298              
299             ################################################################
300             ## Call a handler for a sub-window
301             sub window_call
302             {
303 0     0     my( $self, $event, $name ) = @_;
304 0           my $listener = $event->window->event( $name );
305 0 0         if( $listener ) {
306             # it's up to the handler to do ->defer if it needs it
307 0           $event->done( 1 );
308 0 0         if( ref $listener ) {
309 0           $listener->( $event );
310             }
311             else {
312 0           DEBUG and xwarn "$name -> $listener";
313             # we don't use ->yield because we want the event to go
314             # through before we return
315 0           $poe_kernel->call( $self->SID, $listener, $event );
316             }
317 0           return;
318             }
319              
320 0           $name = lc $name;
321 0 0         if( $self->{application}->can( $name ) ) {
322 0           DEBUG and xwarn "->$name";
323 0           $self->{application}->$name( $event );
324             }
325              
326 0           return;
327             }
328              
329              
330             ################################################################
331             ## Application shutdown (close or timeout)
332             sub shutdown
333             {
334 0     0     my( $self ) = @_;
335 0           DEBUG and xwarn "Application $self->{name} shutdown";
336 0 0         if( $self->{application}->can( 'shutdown' ) ) {
337 0           $self->{application}->shutdown();
338             }
339 0           $poe_kernel->alias_remove( $self );
340 0           $poe_kernel->alias_remove( $self->SID );
341             }
342              
343             ################################################################
344             ## Application timeout
345             sub timeout
346             {
347 0     0     my( $self ) = @_;
348 0           DEBUG and xwarn "Application $self->{name} timeout";
349 0 0         if( $self->{application}->can( 'timeout' ) ) {
350 0           $self->{application}->timeout();
351             }
352             }
353              
354             ################################################################
355             ## Error from _invoke_state
356             sub event_error
357             {
358 0     0     my( $self, $msg ) = @_;
359 0           $self->{CM}->wrapped_error( $msg );
360             }
361              
362             ################################################################
363             ## Reflection
364             sub has_handler
365             {
366 0     0     my( $self, $state ) = @_;
367 0           $! = 0;
368 0           my $handler = $self->session->[POE::Session::SE_STATES]->{$state};
369 0 0         return 1 if $handler;
370              
371             # The desired destination state doesn't exist in this session.
372             # Attempt to redirect the state transition to _default.
373 0           $handler = $self->session->[POE::Session::SE_STATES]->{+POE::Session::EN_DEFAULT};
374 0 0         return 1 if $handler;
375              
376             # No dice
377 0           $! = POSIX::ENOSYS();
378 0           return;
379             }
380              
381             ################################################################
382             ## POE::XUL::ChangeManager telling us about a new window
383             sub register_window
384             {
385 0     0     my( $self, $node ) = @_;
386 0 0 0       unless( $self->{main_window} ) {
    0          
387 0           $self->{main_window} = $node;
388 0           $POE::XUL::Application::window = $node;
389             }
390             elsif( $self->{CM} and $self->{CM}{current_event} ) {
391 0           $self->{CM}{current_event}->window( $node );
392 0           $self->{CM}{current_event}{window_id} = $node->id;
393             }
394             }
395              
396             ################################################################
397             ## Create a handler if needs be
398             sub attach_handler
399             {
400 0     0     my( $self, $node, $name, $listener ) = @_;
401              
402 0           my $app = $self->{application};
403              
404 0 0         if( ref $listener ) {
405             # CODEREF -> create an event for that coderef
406 0           my $state = join '-', "poe-xul", $name, $node->id;
407 0           $app->createHandler( $state, $listener );
408 0           return $state;
409             }
410            
411             # other -> create an event for that event
412 0           my @check = ( $listener );
413 0 0         if( $node->id ) {
414 0           my $id = $node->id;
415 0           $id =~ s/\W/_/g;
416 0           push @check, join '_', 'xul', $name, $id;
417             }
418 0           push @check, $name;
419 0           DEBUG and xwarn $node->id, ".$name one of ", join ', ', @check;
420 0           foreach my $state ( @check ) {
421 0 0         next unless defined $state;
422 0 0         return $state if $self->has_handler( $state ); # already have one?
423              
424 0 0         next unless $app->can( $state ); # couldn't object handle it?
425            
426 0           DEBUG and xwarn "Creating handler for ", $node->id, ".$name event ($state)";
427 0           $app->createHandler( $state );
428 0           return $state;
429             }
430 0 0         if( $listener ) {
431 0           xcarp "Can't handle $name event $listener via package ", ref $app;
432             }
433             else {
434 0           xcarp "Can't handle event $name via package ", ref $app;
435             }
436 0           return;
437             }
438              
439              
440             1;
441              
442             __DATA__