/usr/local/CPAN/POE-Component-XUL/POE/XUL/ChangeManager.pm
package POE::XUL::ChangeManager;
use strict;
use warnings;
use Carp;
use Aspect;
use XUL::Node;
use XUL::Node::State;
# creating --------------------------------------------------------------------
# windows is list of all top level nodes
# destroyed is buffer of all states scheduled for destruction on next flush
# next_node_id is next available node ID - 1
sub new {
my $class = shift;
bless { windows => [], destroyed => [], next_node_id => 0 }, $class
}
# public interface for sessions -----------------------------------------------
sub run_and_flush {
my ($self, $code) = (shift,shift);
local $_;
$code->($self,@_);
my $out = (join '', map { $self->flush_node($_) } @{$self->windows}).
(join '', map { $_->flush } @{$self->{destroyed}});
$self->{destroyed} = [];
return $out;
}
sub destroy {
my $self = shift;
$_->destroy for @{$self->{windows}};
delete $self->{windows};
}
# advice ----------------------------------------------------------------------
my $Self_Flow = cflow source => __PACKAGE__.'::run_and_flush';
# when node changed register change on state
# if it has no state, give it one, give it an id, register the node, and
# register the node as a window if node is_window
after {
my $context = shift;
my $self = $context->source->self;
my $node = $context->self;
my $key = $context->params->[1];
my $value = $context->params->[2];
my $state = $self->node_state($node);
unless ($state) {
push @{$self->windows}, $node if $node->is_window;
$state = XUL::Node::State->new;
my $id = 'E'. ++$self->{next_node_id};
$state->set_id($id);
$self->node_state($node, $state);
$self->event_manager->register_node($id, $node)
if $self->event_manager;
}
if ($key eq 'tag') { $state->set_tag($value) }
else { $state->set_attribute($key, $value) }
} call 'XUL::Node::set_attribute' & $Self_Flow;
# when node added, set parent node state id on child node state
before {
my $context = shift;
my $self = $context->source->self;
my $parent = $context->self;
my $child = $context->params->[1];
my $index = $context->params->[2];
my $child_state = $self->node_state($child);
$child_state->set_parent_id($self->node_state($parent)->get_id);
$child_state->set_index($index);
} call 'XUL::Node::_add_child_at_index' & $Self_Flow;
# when node destroyed, update state using set_destoyed
before {
my $context = shift;
my $self = $context->source->self;
my $parent = $context->self;
my $child = $parent->_compute_child_and_index($context->params->[1]);
my $child_state = $self->node_state($child);
$child_state->set_destroyed;
push @{$self->{destroyed}}, $child_state;
} call 'XUL::Node::remove_child' & $Self_Flow;
# private ---------------------------------------------------------------------
sub flush_node {
my ($self, $node) = @_;
my $out = $self->node_state($node)->flush;
$out .= $self->flush_node($_) for $node->children;
return $out;
}
sub node_state {
my ($self, $node, $state) = @_;
croak "not a node: [$node]" unless UNIVERSAL::isa($node, 'XUL::Node');
return $node->{state} unless $state;
$node->{state} = $state;
}
sub event_manager {
my ($self, $event_manager) = @_;
return $self->{event_manager} unless $event_manager;
$self->{event_manager} = $event_manager;
}
# testing ---------------------------------------------------------------------
sub windows { shift->{windows} }
1;