File Coverage

blib/lib/POE/XUL/Node.pm
Criterion Covered Total %
statement 304 354 85.8
branch 110 162 67.9
condition 32 46 69.5
subroutine 59 67 88.0
pod 27 42 64.2
total 532 671 79.2


line stmt bran cond sub pod time code
1             package POE::XUL::Node;
2             # $Id: Node.pm 1566 2010-11-03 03:13:32Z fil $
3             # Copyright Philip Gwyn 2007-2010. All rights reserved.
4             # Based on code Copyright 2003-2004 Ran Eilam. All rights reserved.
5              
6              
7              
8 21     21   20251 use strict;
  21         28  
  21         508  
9 21     21   76 use warnings;
  21         25  
  21         444  
10 21     21   68 use Carp;
  21         22  
  21         1060  
11 21     21   78 use Scalar::Util qw( blessed );
  21         22  
  21         1053  
12 21     21   7952 use POE::XUL::Constants;
  21         37  
  21         2078  
13 21     21   7429 use POE::XUL::TextNode;
  21         30  
  21         457  
14 21     21   7618 use POE::XUL::CDATA;
  21         29  
  21         423  
15 21     21   7298 use POE::XUL::Style;
  21         35  
  21         521  
16 21     21   7269 use POE::XUL::Window;
  21         37  
  21         66  
17 21     21   4808 use Storable qw( dclone );
  21         18793  
  21         1371  
18 21     21   96 use HTML::Entities qw( encode_entities_numeric );
  21         23  
  21         847  
19              
20 21     21   82 use constant DEBUG => 0;
  21         16  
  21         3432  
21              
22             our $VERSION = '0.0601';
23             our $CM;
24              
25             my $ID = 0;
26              
27             my @XUL_ELEMENTS = qw(
28             ArrowScrollBox Box Button Caption CheckBox ColorPicker Column Columns
29             Deck Description Grid Grippy GroupBox HBox Image Label ListBox
30             ListCell ListCol ListCols ListHead ListHeader ListItem Menu MenuBar
31             MenuItem MenuList MenuPopup MenuSeparator ProgressMeter Radio
32             RadioGroup Row Rows ScrollBar Seperator Spacer Splitter Stack StatusBar
33             StatusBarPanel Tab TabBox TabPanel TabPanels Tabs TextBox ToolBar
34             ToolBarButton ToolBarSeperator ToolBox VBox Window
35              
36             Tree TreeChildren TreeItem TreeRow TreeCols TreeCol TreeCell
37             TreeSeparator Template Rule
38             );
39              
40             # my %XUL_ELEMENTS = map { $_ => 1 } @XUL_ELEMENTS;
41              
42             my @HTML_ELEMENTS = qw(
43             HTML_Pre HTML_H1 HTML_H2 HTML_H3 HTML_H4 HTML_A HTML_Div HTML_Br HTML_Span
44             );
45              
46             my @DEFAULT_LABEL =
47              
48             my %DEFAULT_ATTRIBUTE = map { $_ => 'label' } qw(
49             caption button menuitem radio listitem
50             );
51            
52              
53             my @OTHER_ELEMENTS = qw(
54             Script Boot RawCmd pxInstructions
55             );
56              
57             my %LOGICAL_ATTS = (
58             selected => 1,
59             disabled => 1,
60             autoFill => 1,
61             autocheck => 1,
62             editable => 1,
63             # checked => 1
64             );
65              
66             # creating --------------------------------------------------------------------
67              
68             ##############################################################
69             sub import
70             {
71 22     22   3497 my( $package ) = @_;
72 22         44 my $caller = caller();
73 21     21   84 no strict 'refs';
  21         21  
  21         65628  
74             # export factory methods for each xul element type
75 22         49 foreach my $sub ( @XUL_ELEMENTS, @HTML_ELEMENTS ) {
76 1584         1425 my $tag = lc $sub;
77 1584         1305 $tag =~ s/^html_/html:/;
78             # delete ${"${caller}::$other"};
79 1584         4274 *{"${caller}::$sub"} = sub
80 1584     70   2442 { return scalar $package->new(tag => $tag, @_) };
  70         28629  
81             }
82 22         37 foreach my $other (@OTHER_ELEMENTS) {
83             # delete ${"${caller}::$other"}
84 88         220 *{"${caller}::$other"} = sub
85 88     8   172 { return scalar $package->can("$other")->( $package, @_ ) };
  8         3743  
86             }
87              
88             # export the xul element constants
89 22         537 foreach my $constant_name (@POE::XUL::Node::Constants::EXPORT) {
90 0         0 *{"${$caller}::$constant_name"} = *{"$constant_name"}
  0         0  
  0         0  
  0         0  
91             }
92             }
93              
94             ##############################################################
95             sub new
96             {
97 77     77 0 1389 my ($class, @params) = @_;
98              
99 77         67 my $self;
100 77 100 50     448 if( ($params[0]||'') eq 'tag' and lc($params[1]||'') eq 'window' ) {
      50        
      100        
101 7         46 $self = bless {attributes => {}, children => [], events => {}},
102             'POE::XUL::Window';
103             } else {
104 70         236 $self = bless {attributes => {}, children => [], events => {}}, $class;
105             }
106              
107 77         75 my $id;
108 77         177 ( $id, @params ) = $self->__find_id( @params );
109              
110 77         164 $id = $self->__auto_id( $id );
111 77 100       188 $CM->before_creation( $self ) if $CM;
112              
113 77         54 if( DEBUG and not $CM and $INC{'POE/XUL/ChangeManager.pm'} ) {
114             Carp::cluck "Building a POE::XUL::Node, but no ChangeManager avaiable";
115             }
116              
117 77         152 while (my $param = shift @params) {
118 195 100 100     817 if( ref $param ) {
    100          
    100          
    100          
    50          
119 42         65 $self->appendChild( $param );
120             }
121             elsif( $param =~ /\s/ or 0==@params ) {
122 22         43 $self->defaultChild( $param );
123             }
124             elsif ($param eq 'textNode' ) {
125 6         15 $self->appendChild( shift @params );
126             }
127             elsif ($param =~ /^[a-z]/) {
128 120         223 $self->setAttribute( $param => shift @params );
129             }
130             elsif ($param =~ /^[A-Z]/) {
131 5         23 $self->attach($param => shift @params );
132             }
133             else {
134 0         0 croak "unrecognized param: [$param]"
135             }
136             }
137              
138 77         197 return $self;
139             }
140              
141             ##############################################################
142             # Scan ->new()'s parameters, trying to pull out an ID
143             sub __find_id
144             {
145 77     77   118 my( $self, @params ) = @_;
146 77         67 my( $id, @out );
147 77         149 while (my $param = shift @params) {
148 208 100 100     853 if( ref $param or $param =~ /\s/ or 0==@params ) {
      100        
149 64         121 push @out, $param;
150             }
151             else {
152 144 100       200 if( $param eq 'id' ) {
153 13         19 $id = shift @params;
154 13         44 next;
155             }
156 131         280 push @out, $param, shift @params;
157             }
158             }
159 77         214 return ( $id, @out );
160             }
161              
162             ##############################################################
163             sub Script {
164 2     2 1 4 my $class = shift;
165             # warn "class=$class";
166             # warn "script=", join "\n", @_;
167 2         19 my $cdata = POE::XUL::CDATA->new( join "\n", @_ );
168 2         8 return $class->new( tag=>'script', type=>'text/javascript', $cdata );
169             }
170              
171             ##############################################################
172             # Boot message
173             sub Boot
174             {
175 0     0 1 0 my( $class, $msg ) = @_;
176 0 0       0 if( $CM ) {
177 0         0 $CM->Boot( $msg );
178             }
179 0         0 my $server = $POE::XUL::Application::server;
180 0 0       0 if( $server ) {
181 0         0 $server->Boot( $msg );
182             }
183 0         0 return;
184             }
185              
186             ##############################################################
187             # Send a raw command to Runner.js
188             sub RawCmd
189             {
190 0     0 1 0 my( $class, $cmd ) = @_;
191 0 0       0 if( $CM ) {
192 0         0 $CM->Prepend( $cmd );
193             }
194 0         0 return;
195             }
196              
197             ##############################################################
198             # Instructions to Runner.js, via ChangeManager
199             sub pxInstructions
200             {
201 6     6 1 8 my( $self, @inst ) = @_;
202 6 50       12 unless( $CM ) {
203 0 0       0 unless( $INC{ 'Test/More.pm' } ) {
204             # carp "There is no ChangeManager. Instructions ignored.";
205             }
206 0         0 return;
207             }
208              
209 6         6 my $rv;
210 6         7 foreach my $inst ( @inst ) {
211 9         23 $rv = $CM->instruction( $inst );
212             }
213 6         12 return $rv;
214             }
215              
216              
217             ##############################################################
218             ## Assign an ID as soon as possible, so that the CM and State
219             ## will see it
220             sub __auto_id
221             {
222 77     77   79 my( $self, $id ) = @_;
223 77 100       115 unless( $id ) {
224 64         74 $id = "PXN$ID";
225 64         53 $ID++;
226 64         117 $self->{default_id} = $id;
227             }
228 77         118 $self->{attributes}{id} = $id;
229 77         92 return $id;
230             }
231              
232             ##############################################################
233             sub build_text_node
234             {
235 21     21 0 24 my( $self, $text ) = @_;
236 21         57 my $textnode = POE::XUL::TextNode->new;
237              
238 21         44 $textnode->nodeValue( $text );
239 21         23 return $textnode;
240             }
241             *createTextNode = \&build_text_node;
242              
243              
244             ##############################################################
245             sub textNode
246             {
247 3     3 1 1692 my( $self, $text ) = @_;
248              
249             # Find the last text node
250 3         5 my $old;
251 3         6 foreach my $C ( $self->children ) {
252 3 50       23 next unless $C->isa( 'POE::XUL::TextNode' );
253 3         6 $old = $C;
254             }
255              
256 3 50       13 unless( 2==@_ ) {
257 0 0       0 return unless $old;
258 0         0 return $old->nodeValue;
259             }
260              
261 3 100 66     24 if( $old and ref $text ) {
    50          
262 1         7 $self->replaceChild( $text, $old );
263 1 50       5 return $text->nodeValue if blessed $text;
264 0         0 return $text;
265             }
266             elsif( $old ) {
267 2         8 return $old->nodeValue( $text );
268             }
269             else {
270 0         0 return $self->appendChild( $text )->nodeValue;
271             }
272             }
273              
274              
275             ##############################################################
276             sub getItemAtIndex
277             {
278 4     4 1 921 my( $self, $index ) = @_;
279 4 100 66     22 return if not defined $index or $index < 0;
280              
281 3 50       13 if( $self->tag eq 'menulist' ) {
282 0         0 $self = $self->firstChild;
283             }
284              
285 3         4 my $N = 0;
286 3         5 foreach my $I ( $self->children ) {
287 13         25 my $t = $I->tag;
288 13 100 66     39 next unless $t eq 'listitem' or $t eq 'menuitem';
289 7 100       14 return $I if $N == $index;
290 5         5 $N++;
291             }
292 1         2 return;
293             }
294             *get_item = \&getItemAtIndex;
295              
296             # attribute-like method invocation --------------------------------------------
297             sub mk_method
298             {
299 126     126 0 110 my( $name ) = @_;
300             return sub {
301 1     1   2 my $self = shift;
302 1 50       3 return unless $CM;
303 1         6 $CM->after_method_call( $self, $name, [@_] );
304 126         286 };
305             }
306             *scrollTo = mk_method( 'scrollTo' );
307             *scrollBy = mk_method( 'scrollBy' );
308             *scrollToLine = mk_method( 'scrollToLine' );
309             *scrollByLine = mk_method( 'scrollByLine' );
310             *scrollByPage = mk_method( 'scrollByPage' );
311             *scrollByIndex = mk_method( 'scrollByIndex' );
312              
313              
314             # attribute handling ----------------------------------------------------------
315              
316             ##############################################################
317             sub attributes
318             {
319 22     22 1 22 my( $self ) = @_;
320 22         432 my $ret = dclone $self->{attributes};
321 22 50       143 return %$ret if wantarray;
322 0         0 return $ret;
323             }
324              
325             ##############################################################
326             sub get_attribute
327             {
328 139     139 1 392 my( $self, $key ) = @_;
329 139 100       347 if( $LOGICAL_ATTS{ $key } ) {
330 6 100       16 return unless $self->{attributes}{$key};
331             # 'false' is still true, in Perl
332 3 50       7 return if $self->{attributes}{$key} eq 'false';
333             }
334              
335 136 50       268 return $self->style if $key eq 'style';
336 136         364 return $self->{attributes}{$key};
337             }
338             *getAttribute = \&get_attribute;
339              
340              
341             ##############################################################
342             sub set_attribute
343             {
344 141     141 1 4074 my( $self, $key, $value ) = @_;
345 141 100       201 return $self->style( $value ) if $key eq 'style';
346 137 100       186 if( $key eq 'tag' ) {
347 75         84 $value = lc $value;
348 75         83 $value =~ s/^html_/html:/;
349 75         68 $value =~ s/^xul://;
350             }
351              
352 137 100       212 if( $LOGICAL_ATTS{ $key } ) {
353 9 100 100     43 if( ! $value or $value eq 'false' ) {
354 3         8 $self->remove_attribute( $key );
355 3         5 return;
356             # remove_attribute() informs the CM, we don't have to
357             }
358             # 2008-09 : the following is a tad silly...
359 6 50       18 $value = $value ? 'true' : 'false';
360             }
361              
362 134         85 if( DEBUG and $key eq 'id' ) {
363             carp $self->id, ".$key=$value";
364             }
365              
366 134 100       191 if( $key eq 'value' ) { # and $self->tag eq 'menulist' ) {
367             # Carp::cluck( $self->tag . ".value=$value" );
368             }
369              
370 134         172 $self->{attributes}{$key} = $value;
371 134 100       262 $CM->after_set_attribute( $self, $key, $value ) if $CM;
372 134         245 return $value;
373             }
374             *setAttribute = \&set_attribute;
375              
376             ##############################################################
377             sub remove_attribute
378             {
379 5     5 1 17 my( $self, $key ) = @_;
380             # if( $key eq 'value' and $self->tag eq 'menulist' ) {
381             # Carp::cluck( $self->tag . ".removeAttribute('value')" );
382             # }
383 5 50       16 croak "You may not remove the tag attribute" if $key eq 'tag';
384 5 100       21 $CM->after_remove_attribute( $self, $key ) if $CM;
385 5         14 delete $self->{attributes}{ $key };
386             }
387             *removeAttribute = \&remove_attribute;
388              
389             ##############################################################
390 22     22 0 69 sub is_window { 0 }
391              
392             ##############################################################
393             *id = __mk_accessor( 'id' );
394             *tagName = __mk_accessor( 'tag' );
395             #*textNode = __mk_accessor( 'textNode' );
396              
397             sub __mk_accessor
398             {
399 42     42   49 my( $tag ) = @_;
400             return sub {
401 17     17   21 my( $self, $value ) = @_;
402 17 100       40 if( @_ == 2 ) {
403 1         3 return $self->setAttribute( $tag, $value );
404             }
405             else {
406 16         81 return $self->{attributes}{$tag};
407             }
408             }
409 42         112 }
410              
411             ##############################################################
412             sub style {
413 46     46 0 2969 my( $self, $value ) = @_;
414 46 100       81 if( 1==@_ ) {
415 35         50 return $self->get_style;
416             }
417             else {
418 11         24 return $self->set_style( $value );
419             }
420             }
421              
422             sub get_style
423             {
424 49     49 0 40 my( $self ) = @_;
425 49 100       119 return $self->{style_obj} if $self->{style_obj};
426 12         52 $self->{style_obj} = POE::XUL::Style->new( $self->{attributes}{style} );
427 12 100       31 $CM->after_new_style( $self ) if $CM;
428 12         24 return $self->{style_obj};
429             }
430              
431             sub set_style
432             {
433 11     11 0 13 my( $self, $value ) = @_;
434 11         23 $self->{attributes}{style} = "$value";
435 11 50       39 if( blessed $value ) {
436 0         0 $self->{style_obj} = $value;
437 0 0       0 $CM->after_new_style( $self ) if $CM;
438             }
439             else {
440 11         29 delete $self->{style_obj};
441             # do the following to provoke a ->after_new_style();
442 11         20 $self->get_style;
443             }
444 11         23 return;
445             }
446              
447             ##############################################################
448             sub AUTOLOAD {
449 58     58   432 my( $self, $value ) = @_;
450 58         45 my $key = our $AUTOLOAD;
451 58 50       101 return if $key =~ /DESTROY$/;
452 58         167 $key =~ s/^.*:://;
453             # Carp::confess $key;
454 58 50       102 if( $key =~ /^[a-z]/ ) {
    0          
455 58 100       72 if( @_ == 1 ) {
456 52         80 return $self->getAttribute( $key );
457             }
458             else {
459 6         12 return $self->setAttribute( $key, $value );
460             }
461             }
462             elsif( $key =~ /^[A-Z]/ ) {
463 0         0 $self->add_child( __PACKAGE__->new(tag => $key, @_[ 1..$#_ ] ) );
464             }
465 0         0 croak __PACKAGE__. "::AUTOLOAD cannot find method $key";
466             }
467              
468             ##############################################################
469             sub hide
470             {
471 7     7 1 2156 my( $self ) = @_;
472 7         13 $self->style->display( 'none' );
473             }
474              
475             ##############################################################
476             sub show
477             {
478 5     5 1 1096 my( $self ) = @_;
479 5         12 $self->style->display( '' );
480             }
481              
482             sub hidden
483             {
484 4     4 0 9 my( $self ) = @_;
485 4         7 return $self->style->display eq 'none';
486             }
487              
488             # compositing -----------------------------------------------------------------
489              
490 153         414 sub children { wantarray? @{shift->{children}}:
491 153 50   153 1 173 [@{shift->{children}}] }
  0         0  
492 78     78 1 53 sub child_count { scalar @{shift->{children}} }
  78         95  
493 0     0 1 0 sub hasChildNodes { return 0!= scalar @{shift->{children}} }
  0         0  
494 6     6 1 2049 sub first_child { shift->{children}->[0] }
495             *firstChild = \&first_child;
496 4     4 1 10 sub get_child { shift->{children}->[pop] }
497 1     1 1 5 sub last_child { shift->{children}->[-1] }
498             *lastChild = \&last_child;
499              
500             ##############################################################
501             sub add_child {
502 2     2 1 12 my ($self, $child, $index) = @_;
503             # This is a huge speed up, but breaks the Aspect stuff
504             # unless( defined $index ) {
505             # push @{$self->{children}}, $child;
506             # return $child;
507             # }
508 2         4 my $child_count = $self->child_count;
509 2 100       5 $index = $child_count unless defined $index;
510 2 50 33     15 croak "index out of bounds: [$index:$child_count]"
511             if ($index < 0 || $index > $child_count);
512              
513 2 100       4 if( $self->{children}[$index] ) {
514 1         6 $self->remove_child( $index );
515             }
516              
517 2         5 $self->_add_child_at_index($child, $index);
518 2         3 return $child;
519             }
520             sub appendChild
521             {
522 63     63 1 1066 my( $self, $child ) = @_;
523 63 100       110 $child = $self->createTextNode( $child ) unless ref $child;
524 63         92 my $index = $self->child_count;
525 63         105 $self->_add_child_at_index( $child, $index );
526             }
527              
528             sub defaultChild
529             {
530 22     22 0 43 my( $self, $text ) = @_;
531 22   50     67 my $d_att = $DEFAULT_ATTRIBUTE{ lc $self->{attributes}{tag} || '' };
532 22 100       38 if( $d_att ) {
533 9         16 $self->setAttribute( $d_att => $text );
534 9         44 return;
535             }
536            
537 13         22 my $child = $self->createTextNode( $text );
538 13         24 my $index = $self->child_count;
539 13         24 $self->_add_child_at_index( $child, $index );
540             }
541              
542             ##############################################################
543             sub replaceChild {
544 1     1 1 3 my ($self, $new, $old) = @_;
545              
546 1         6 my ($oldNode, $index) = $self->_compute_child_and_index($old);
547 1 50       3 $CM->before_remove_child( $self, $oldNode, $index ) if $CM;
548 1         1 splice @{$self->{children}}, $index, 1, $new;
  1         4  
549 1 50       3 $CM->before__add_child_at_index( $self, $new, $index ) if $CM;
550 1         4 $old->dispose;
551 1         1 return $self;
552             }
553              
554             ##############################################################
555             sub remove_child {
556 4     4 1 2543 my ($self, $something) = @_;
557              
558 4         12 my ($child, $index) = $self->_compute_child_and_index($something);
559              
560 4 100 66     10 unless( $child and $index < @{ $self->{children} } ) {
  3         12  
561 1 50       5 Carp::carp "Attempt to remove an unknown child node" unless $ENV{AUTOMATED_TESTING};
562 1         2 return;
563             }
564              
565             # warn "remove_child id=", $child->{attributes}{id};
566 3 50       14 $CM->before_remove_child( $self, $child, $index ) if $CM;
567 3         4 splice @{$self->{children}}, $index, 1;
  3         6  
568 3 50       17 $child->dispose if blessed $child;
569 3         9 return $self;
570             }
571              
572             *removeChild = \&remove_child;
573              
574             ##############################################################
575             sub get_child_index
576             {
577 1     1 0 2 my ($self, $child) = @_;
578 1         1 my $index = 0;
579 1         2 foreach my $C ( @{ $self->{children} } ) {
  1         59  
580 1 50       7 return $index if $child eq $C;
581 0         0 $index++;
582             }
583 0         0 confess 'child not in parent';
584             }
585              
586             ##############################################################
587             # computes child and index from child or index
588             sub _compute_child_and_index
589             {
590 5     5   7 my ($self, $something) = @_;
591 5         7 my $is_node = ref $something;
592 5 100       28 my $child = $is_node? $something: $self->get_child($something);
593 5 100       14 my $index = $is_node? $self->get_child_index($something): $something;
594 5 50       14 return wantarray? ($child, $index): $child;
595             }
596              
597             sub _add_child_at_index {
598 78     78   69 my ($self, $child, $index) = @_;
599 78         49 my $N = $#{ $self->{children} };
  78         81  
600 78         52 my $trueindex;
601 78 50       113 if( $index > $N ) {
602 78         57 $index = -1;
603 78         49 push @{ $self->{children} }, $child;
  78         78  
604 78         56 $trueindex = $#{ $self->{children} };
  78         78  
605             }
606             else {
607 0         0 splice @{$self->{children}}, $index, 0, $child;
  0         0  
608             }
609 78 100       128 if( $CM ) {
610 35         82 $CM->after__add_child_at_index( $self, $child, $index );
611             # after__add_child needs $index to be -1 for appends, so that they
612             # work in Runner. However, the state needs to remember the real, true
613             # index, so we set it afterwards.
614             # 2009-02: the problem with this is that the index might differ from
615             # what is happening in the client. Client should send the index back
616             # to us. TODO when we implement AJAX
617 35 50       58 if( defined $trueindex ) {
618 35         66 $CM->set_trueindex( $self, $child, $trueindex );
619             }
620             }
621 78         144 return $child;
622             }
623              
624             ##############################################################
625             sub getElementById
626             {
627 0     0 0 0 my( $self, $id ) = @_;
628 0 0       0 return $id if blessed $id; # act like prototype's $()
629 0 0       0 croak "getElementById may only be invoked on a Window"
630             unless $self->is_window;
631 0         0 return $CM->getElementById( $id );
632             }
633              
634             # event handling --------------------------------------------------------------
635              
636             sub attach {
637 5     5 1 10 my( $self, $name, $listener ) = @_;
638              
639 5         4 my $state;
640              
641 5         9 my $server = $POE::XUL::Application::server;
642 5 50       14 if( $server ) {
643             # auto-create the handler in the application
644 0         0 $state = $server->attach_handler( $self, $name, $listener );
645             }
646             else {
647 5   33     16 $state = $listener||$name;
648             }
649 5         5 DEBUG and warn $self->id, ".$name = $state";
650 5 50       14 return unless $state;
651 5         15 $self->{events}->{ $name } = $state;
652 5         16 return 1;
653             }
654             *addEventListener = \&attach;
655              
656             sub detach {
657 0     0 1 0 my ($self, $name) = @_;
658 0         0 my $listener = delete $self->{events}->{$name};
659 0 0       0 croak "no listener to detach: $name" unless $listener;
660             # TODO: remove the POE state if we auto-created it?
661             }
662             *removeEventListener = \&detach;
663              
664             sub event {
665 0     0 1 0 my ($self, $name) = @_;
666 0         0 my $listener = $self->{events}->{ $name };
667 0         0 return $listener;
668             }
669              
670             # disposing ------------------------------------------------------------------
671              
672             # protected, used by sessions and by parent nodes to free node memory
673             # event handlers could cause reference cycles, so we free them manually
674             sub dispose {
675 2     2 1 3 my $self = shift;
676 2         3 $self->{disposed} = 1;
677 2         3 delete $self->{style_obj};
678 2         5 $_->dispose for grep { blessed $_ } $self->children;
  1         7  
679 2         3 delete $self->{events};
680 2         3 $self->{children} = [];
681             # TODO: remove any events that auto-created handler states
682             }
683             *destroy = \&dispose;
684              
685 0     0 0 0 sub is_destroyed { !shift->{events} }
686              
687             sub DESTROY
688             {
689 58     58   2521 my( $self ) = @_;
690             # carp "DESTROY ", ($self->id||$self);
691 58 100       312 $CM->after_destroy( $self ) if $CM;
692             }
693              
694              
695              
696             #######################################################################
697             sub as_xml {
698 22     22 1 30 my $self = shift;
699 22   100     48 my $level = shift || 0;
700 22         72 my $tag = lc $self->tag;
701 22         28 $tag =~ s/_/:/;
702 22         31 my $attributes = $self->attributes_as_xml;
703 22         55 my $children = $self->children_as_xml($level + 1);
704             # my $indent = $self->get_indent($level);
705 22 100       78 my $nl = ( $tag =~ /^((h|v|group)box)|(grid|row|(field-(name|value)))$/ ? "\n" : "" );
706 22 100       28 return qq[<$tag$attributes${\( $children? ">$nl$children$nl];
  22         96  
707             }
708              
709             sub attributes_as_xml {
710 22     22 0 18 my $self = shift;
711 22         29 my %attributes = $self->attributes;
712 22         20 my $xml = '';
713              
714             delete $attributes{id} if $self->{default_id} and
715 22 50 33     93 $attributes{id} eq $self->{default_id};
716            
717 22         35 foreach my $k ( keys %attributes ) {
718 24 50       47 next if defined $attributes{ $k };
719 0         0 warn $self->id."/$k is undef";
720 0         0 $attributes{ $k } = '';
721             }
722 2         12 $xml .= qq[ $_='${\( encode_entities_numeric( $self->$_, "\x00-\x1f<>&\'\x80-\xff" ) )}']
723 22 100       31 for grep { $_ ne 'tag' and $_ ne 'textNode' } keys %attributes;
  24         77  
724             # die $xml if $xml =~ /\n/;
725 22         242 return $xml;
726             }
727              
728             sub children_as_xml {
729 22     22 0 20 my $self = shift;
730 22   50     36 my $level = shift || 0;
731             # my $indent = $self->get_indent($level);
732 22         16 my $xml = '';
733             # $xml .= qq[\n$indent${\( $_->as_xml($level) )}] for $self->children;
734 22 50       33 $xml .= qq[${\( blessed $_ ? $_->as_xml($level) : $_ )}] for $self->children;
  31         372  
735 22         600 return $xml;
736             }
737              
738 0     0 0   sub get_indent { ' ' x (3 * pop) }
739              
740             1;
741              
742             __END__