File Coverage

blib/lib/XUL/Gui.pm
Criterion Covered Total %
statement 200 853 23.4
branch 44 426 10.3
condition 27 229 11.7
subroutine 52 167 31.1
pod 29 42 69.0
total 352 1717 20.5


line stmt bran cond sub pod time code
1             package XUL::Gui;
2 2     2   68564 use warnings;
  2         6  
  2         72  
3 2     2   12 use strict;
  2         4  
  2         69  
4 2     2   12 use Carp;
  2         6  
  2         168  
5 2     2   2627 use Storable 'dclone';
  2         8644  
  2         157  
6 2     2   16 use List::Util 'max';
  2         4  
  2         207  
7 2     2   1897 use MIME::Base64 'encode_base64';
  2         1694  
  2         132  
8 2     2   2053 use Encode 'encode';
  2         26009  
  2         186  
9 2     2   17 use Scalar::Util qw/weaken isweak reftype blessed/;
  2         4  
  2         397  
10             BEGIN {
11 2     2   4 local $@;
12             eval {
13 2         1950 require Hash::Util;
14 2         5844 Hash::Util->import('hv_store'); 1
  2         8627  
15 2 50       5 } or do {
16             *hv_store = sub (\%$$) {
17 0         0 $_[0]{$_[1]} = $_[2];
18 0 0       0 weaken $_[0]{$_[1]} if ref $_[2]
19 0         0 };
20 0         0 warn "XUL::Gui> Hash::Util::hv_store not found, memory usage will be higher\n"
21             }
22             }
23             our $VERSION = '0.63';
24             our $THREADS = $INC{'threads.pm'}; # disables a number of optimizations that break under threads
25             our $TESTING;
26             our $DEBUG = 0; # verbosity from 0 - 6
27             our $MOZILLA = 1; # enables mozilla specific XUL, otherwise only HTML tags will work (Web::Gui mode)
28             our $AUTOBUFFER = 1; # enable autobuffering of SET messages
29             our $EXTENDED_OBJECTS = 1; # enable inheritance for normal objects (does not apply to widgets)
30             our $TIED_WIDGETS = 0; # enable data inheritance instead of copying in widgets (10-20% slower, potentially less memory usage)
31             our $FILL_GENID_OBJECTS = 1; # include objects without a user set ID in the widget namespace
32             # MUST BE SET BEFORE WIDGETS ARE CREATED (ideally, right after "use XUL::Gui;")
33             # disabling this option will save some time and memory, but will
34             # require accessing the object's {W} key to get to widget members for unnamed objects
35            
36             $Carp::Internal{"XUL::Gui$_"}++ for '', '::Object', '::Server';
37            
38             sub import {
39 3 50 66 3   855 splice @_ => 1, 1, ':all'
40             if @_ == 2 and $_[1] =~ /^(\*|all|)$/;
41            
42             require Exporter and
43 2   66     5192 goto &{ Exporter->can('import') }
  4         65  
44             if @_ == 1
45 3 100 50     41 or 1 < (@_ = grep {not
      100        
46             /^(?: ([\w:!]*) -> \*? ([\w:!]*)
47             | ([\w:!]+::!*)
48             )$/x && XUL::Gui->oo( $3 or $2 or $1 )
49             } @_)
50             }
51            
52             =head1 NAME
53            
54             XUL::Gui - render cross platform gui applications with firefox from perl
55            
56             =head1 VERSION
57            
58             version 0.63
59            
60             this module is under active development, interfaces may change.
61            
62             this code is currently in beta, use in production environments at your own risk
63            
64             =head1 SYNOPSIS
65            
66             use XUL::Gui;
67             display Label 'hello, world!';
68            
69             # short enough? remove "Label" for bonus points
70            
71             use XUL::Gui;
72             display Window title => "XUL::Gui's long hello",
73             GroupBox(
74             Caption('XUL'),
75             Button(
76             label => 'click me',
77             oncommand => sub {$_->label = 'ouch'}
78             ),
79             Button(
80             id => 'btn',
81             label =>'automatic id registration',
82             oncommand => sub {
83             ID(btn)->label = 'means no more variable clutter';
84             ID(txt)->value = 'and makes cross tag updates easy';
85             }),
86             Button(
87             type => 'menu',
88             label => 'menu button',
89             MenuPopup map
90             {MenuItem label => $_} qw/first second third/
91             ),
92             TextBox( id => 'txt', width => 300 ),
93             ProgressMeter( mode => 'undetermined' ),
94             ),
95             GroupBox(
96             Caption('HTML too'),
97             TABLE( width => '100%',
98             TR map {TD $_}
99             'one', I('two'), B('three'), U('four'), SUP('five')
100             ),
101             BR, HR,
102             P('all the HTML tags are in CAPS'),
103             );
104            
105             =head1 DESCRIPTION
106            
107             this module exposes the entire functionality of mozilla firefox's rendering
108             engine to perl by providing all of the C< XUL > and C< HTML > tags as functions
109             and allowing you to interact with those objects directly from perl. gui
110             applications created with this toolkit are cross platform, fully support CSS
111             styling, inherit firefox's rich assortment of web technologies (browser, canvas
112             and video tags, flash and other plugins), and are even easier to write than
113             C< HTML >.
114            
115             =head2 how things work
116            
117             gui's created with this module are event driven. an arbitrarily complex (and
118             runtime mutable) object tree is passed to C< display >, which then creates the
119             gui in firefox and starts the event loop. C< display > will wait for and respond
120             to events until the C< quit > function is called, or the user closes the window.
121            
122             all of javascript's event handlers are available, and can be written in perl
123             (normally) or javascript (for handlers that need to be very fast such as image
124             rollovers with onmouseover or the like). this is not to say that perl side
125             handlers are slow, but with rollovers and fast mouse movements, sometimes there
126             is mild lag due to protocol overhead.
127            
128             this module is written in pure perl, and only depends upon core modules, making
129             it easy to distribute your application. the goal of this module is to make all
130             steps of gui development as easy as possible. XUL's widgets and nested design
131             structure gets us most of the way there, and this module with its light weight
132             syntax, and 'do what i mean' nature hopefully finishes the job. everything has
133             sensible defaults with minimal boilerplate, and nested design means a logical
134             code flow that isn't littered with variables. please send feedback if you think
135             anything could be improved.
136            
137             =head2 building blocks
138            
139             just like in C< HTML>, you build up your gui using tags. all tags (C< XUL >
140             tags, C< HTML > tags, user defined widgets, and the C< display > function) are
141             parsed the same way, and can fit into one of four templates:
142            
143             =over 8
144            
145             =item * no arguments
146            
147             HR()
148            
149            
150             =item * one simple argument
151            
152             B('some bold text')
153             some bold text
154            
155             in the special case of a tag with one argument, which is not another tag, that
156             argument is added to that tag as a text node. this is mostly useful for HTML
157             tags, but works with XUL as well. once parsed, the line C< B('...') > becomes
158             C<< B( TEXT => '...' ) >>. the special C< TEXT > attribute can be used directly
159             if other attributes need to be set: C<< FONT( color=>'blue', TEXT=>'...' ) >>.
160            
161             =item * multiple attributes
162            
163             Label( value=>'some text', style=>'color: red' )
164            
165            
166             =item * attributes and children
167            
168             Hbox( id => 'mybox', pack => 'center',
169             Label( value => 'hello' ),
170             BR,
171             B('world')
172             )
173            
174            
175            
176            
177             world
178            
179            
180             =back
181            
182             as you can see, the tag functions in perl nest and behave the same way as their
183             counterpart element constructors in C< HTML/XUL >. just like in C< HTML >, you
184             access the elements in your gui by C< id >. but rather than using
185             C< document.getElementById(...) > all the time, setting the C< id > attribute
186             names an element in the global C< %ID > hash. the same hash can be accessed
187             using the C< ID(some_id) > function.
188            
189             my $object = Button( id => 'btn', label => 'OK' );
190            
191             # $ID{btn} == ID(btn) == $object
192            
193             the ID hash also exists in javascript:
194            
195             ID.btn == document.getElementById('btn')
196            
197             due to the way this module works, every element needs an C< id >, so if you
198             don't set one yourself, an auto generated C< id > matching C< /^xul_\d+$/ > is
199             used. you can use any C< id > that matches C< /\w+/ >
200            
201             Tk's attribute style with a leading dash is supported.
202             this is useful for readability when collapsing attribute lists with C< qw// >
203            
204             TextBox id=>'txt', width=>75, height=>20, type=>'number', decimalplaces=>4;
205             TextBox qw/-id txt -width 75 -height 20 -type number -decimalplaces 4/;
206            
207             multiple 'style' attributes are joined with ';' into a single attribute
208            
209            
210            
211             =head3 xul documentation links
212            
213             all C< XUL > and C< HTML > objects in perl are exact mirrors of their javascript
214             counterparts and can be acted on as such. for anything not written in this
215             document or L, developer.mozilla.com is the official source of
216             documentation:
217            
218             =over
219            
220             =item * L
221            
222             =item * L
223            
224             =item * L
225            
226             =item * L - XUL periodic table
227            
228             =back
229            
230             =head2 event handlers
231            
232             any tag attribute name that matches C< /^on/ > is an event handler (onclick,
233             onfocus, ...), and expects a C< sub {...} > (perl event handler) or
234             C< function q{...} > (javascript event handler).
235            
236             perl event handlers get passed a reference to their object and an event object
237            
238             Button( label=>'click me', oncommand=> sub {
239             my ($self, $event) = @_;
240             $self->label = $event->type;
241             })
242            
243             in the event handler, C< $_ == $_[0] > so a shorter version would be:
244            
245             oncommand => sub {$_->label = pop->type}
246            
247             javascript event handlers have C< event > and C< this > set for you
248            
249             Button( label=>'click me', oncommand=> function q{
250             this.label = event.type;
251             })
252            
253             any attribute with a name that doesn't match C< /^on/ > that has a code ref
254             value is added to the object as a method. methods are explained in more detail
255             later on.
256            
257             =head1 EXPORT
258            
259             use XUL::Gui; # is the same as
260             use XUL::Gui qw/:base :util :pragma :xul :html :const :image/;
261            
262             the following export tags are available:
263            
264             :base %ID ID alert display quit widget
265             :tools function gui interval serve timeout toggle XUL
266             :pragma buffered cached delay doevents flush noevents now
267             :const BLUR FILL FIT FLEX MIDDLE SCROLL
268             :widgets ComboBox filepicker prompt
269             :image bitmap bitmap2src
270             :util apply mapn trace zip
271             :internal genid object realid tag
272            
273             :all (all exports)
274             :default (same as with 'use XUL::Gui;')
275            
276             :xul (also exported as Titlecase)
277             Action ArrowScrollBox Assign BBox Binding Bindings Box Broadcaster
278             BroadcasterSet Browser Button Caption CheckBox ColorPicker Column Columns
279             Command CommandSet Conditions Content DatePicker Deck Description Dialog
280             DialogHeader DropMarker Editor Grid Grippy GroupBox HBox IFrame Image Key
281             KeySet Label ListBox ListCell ListCol ListCols ListHead ListHeader
282             ListItem Member Menu MenuBar MenuItem MenuList MenuPopup MenuSeparator
283             Notification NotificationBox Observes Overlay Page Panel Param PopupSet
284             PrefPane PrefWindow Preference Preferences ProgressMeter Query QuerySet
285             Radio RadioGroup Resizer RichListBox RichListItem Row Rows Rule Scale
286             Script ScrollBar ScrollBox ScrollCorner Separator Spacer SpinButtons
287             Splitter Stack StatusBar StatusBarPanel StringBundle StringBundleSet Tab
288             TabBox TabPanel TabPanels Tabs Template TextBox TextNode TimePicker
289             TitleBar ToolBar ToolBarButton ToolBarGrippy ToolBarItem ToolBarPalette
290             ToolBarSeparator ToolBarSet ToolBarSpacer ToolBarSpring ToolBox ToolTip
291             Tree TreeCell TreeChildren TreeCol TreeCols TreeItem TreeRow TreeSeparator
292             Triple VBox Where Window Wizard WizardPage
293            
294             :html (also exported as html_lowercase)
295             A ABBR ACRONYM ADDRESS APPLET AREA AUDIO B BASE BASEFONT BDO BGSOUND BIG
296             BLINK BLOCKQUOTE BODY BR BUTTON CANVAS CAPTION CENTER CITE CODE COL
297             COLGROUP COMMENT DD DEL DFN DIR DIV DL DT EM EMBED FIELDSET FONT FORM
298             FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME ILAYER IMG INPUT
299             INS ISINDEX KBD LABEL LAYER LEGEND LI LINK LISTING MAP MARQUEE MENU META
300             MULTICOL NOBR NOEMBED NOFRAMES NOLAYER NOSCRIPT OBJECT OL OPTGROUP OPTION
301             P PARAM PLAINTEXT PRE Q RB RBC RP RT RTC RUBY S SAMP SCRIPT SELECT SMALL
302             SOURCE SPACER SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD TEXTAREA
303             TFOOT TH THEAD TITLE TR TT U UL VAR VIDEO WBR XML XMP
304            
305             constants:
306            
307             FLEX flex => 1
308             FILL flex => 1, align =>'stretch'
309             FIT sizeToContent => 1
310             SCROLL style => 'overflow: auto'
311             MIDDLE align => 'center', pack => 'center'
312             BLUR onfocus => 'this.blur()'
313            
314             each is a function that returns its constant, prepended to its arguments,
315             thus the following are both valid:
316            
317             Box FILL pack=>'end';
318             Box FILL, pack=>'end';
319            
320             =cut
321            
322 0     0 0 0 sub FLEX {flex => 1, @_}
323 0     0 0 0 sub FILL {qw/-flex 1 -align stretch/, @_}
324 0     0 0 0 sub FIT {sizeToContent => 1, @_}
325 0     0 0 0 sub SCROLL {style => 'overflow: auto', @_}
326 0     0 0 0 sub MIDDLE {qw/-align center -pack center/, @_}
327 0     0 0 0 sub BLUR {qw/-onfocus this.blur()/, @_}
328            
329             our @Xul = map {$_, (ucfirst lc) x /.[A-Z]/} qw {
330             Action ArrowScrollBox Assign BBox Binding Bindings Box Broadcaster
331             BroadcasterSet Browser Button Caption CheckBox ColorPicker Column
332             Columns Command CommandSet Conditions Content DatePicker Deck
333             Description Dialog DialogHeader DropMarker Editor Grid Grippy GroupBox
334             HBox IFrame Image Key KeySet Label ListBox ListCell ListCol ListCols
335             ListHead ListHeader ListItem Member Menu MenuBar MenuItem MenuList
336             MenuPopup MenuSeparator Notification NotificationBox Observes Overlay
337             Page Panel Param PopupSet PrefPane PrefWindow Preference Preferences
338             ProgressMeter Query QuerySet Radio RadioGroup Resizer RichListBox
339             RichListItem Row Rows Rule Scale Script ScrollBar ScrollBox ScrollCorner
340             Separator Spacer SpinButtons Splitter Stack StatusBar StatusBarPanel
341             StringBundle StringBundleSet Tab TabBox TabPanel TabPanels Tabs Template
342             TextBox TextNode TimePicker TitleBar ToolBar ToolBarButton ToolBarGrippy
343             ToolBarItem ToolBarPalette ToolBarSeparator ToolBarSet ToolBarSpacer
344             ToolBarSpring ToolBox ToolTip Tree TreeCell TreeChildren TreeCol
345             TreeCols TreeItem TreeRow TreeSeparator Triple VBox Where Window Wizard
346             WizardPage
347             };
348             our %HTML = map {("html_$_" => "html:$_", uc $_ => "html:$_")} qw {
349             a abbr acronym address applet area audio b base basefont bdo bgsound big
350             blink blockquote body br button canvas caption center cite code col
351             colgroup comment dd del dfn dir div dl dt em embed fieldset font form
352             frame frameset h1 h2 h3 h4 h5 h6 head hr html i iframe ilayer img input
353             ins isindex kbd label layer legend li link listing map marquee menu meta
354             multicol nobr noembed noframes nolayer noscript object ol optgroup
355             option p param plaintext pre q rb rbc rp rt rtc ruby s samp script
356             select small source spacer span strike strong style sub sup table tbody
357             td textarea tfoot th thead title tr tt u ul var video wbr xml xmp
358             };
359             our %EXPORT_TAGS = (
360             util => [qw/zip mapn apply trace/],
361             base => [qw/%ID ID display quit alert widget/],
362             widgets => [qw/filepicker ComboBox prompt/],
363             tools => [qw/gui interval timeout toggle function serve XUL/],
364             pragma => [qw/buffered now cached noevents delay doevents flush/],
365             xul => [@Xul],
366             html => [keys %HTML],
367             const => [qw/FLEX FIT FILL SCROLL MIDDLE BLUR/],
368             image => [qw/bitmap bitmap2src/],
369             internal => [qw/tag object genid realid/],
370             );
371             our @EXPORT_OK = map @$_ => values %EXPORT_TAGS;
372             our @EXPORT = map @{ $EXPORT_TAGS{$_} } =>
373             qw/util base tools pragma xul html const image/;
374             @EXPORT_TAGS{qw/default all/} = (\@EXPORT, \@EXPORT_OK);
375            
376             #for (qw/base tools pragma const widgets image util internal/) {
377             # printf " :%-10s %s\n", $_, join ' '=> sort {
378             # lc $a cmp lc $b
379             # } @{ $EXPORT_TAGS{$_} }
380             #}
381            
382             our %defaults = (
383             window => ['xmlns:html' => 'http://www.w3.org/1999/xhtml',
384             xmlns => 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul',
385             onclose => sub {quit(); 0},
386             resizeTo => sub {gui("window.resizeTo($_[1],$_[2]);")},
387             ],
388             textbox => [ value => sub :lvalue {tie my $ret, 'XUL::Gui::Scalar', shift, '_value'; $ret},
389             _value => sub :lvalue {tie my $ret, 'XUL::Gui::Scalar', shift, 'value'; $ret}
390             ],
391             );
392             our $server = XUL::Gui::Server->new;
393             our (%ID, %dialogs);
394             my ($preprocess, $toJS, $toXML);
395            
396             {*ID = my $id = {};
397             sub realid :lvalue {
398 4 50   4 0 40 @_ ? $$id{$_[0]} : (my $id = $id)
399             }
400             }
401            
402 4     4 0 23 {my $id; sub genid () {'xul_' . ++$id}}
403            
404             sub isa_object {
405 2     2   25 no warnings;
  2         4  
  2         1641  
406 11 50   11 0 99 blessed(@_ ? $_[0] : $_) eq 'XUL::Gui::Object'
407             }
408            
409             my $weaken = sub {weaken $_[0] if ref $_[0] and not isweak $_[0]};
410            
411             my $weak_set = sub {
412             my ($obj, $key) = @_;
413             my $type = reftype $obj or return warn "weak_set @_";
414             my $strong = defined $key
415             ? $type eq 'HASH'
416             ? do {
417             if ($TIED_WIDGETS and my $tied = tied %$obj) {
418             $obj = $tied->hash
419             }
420             \$$obj{$key}
421             }
422             : $type eq 'ARRAY'
423             ? \$$obj[$key]
424             : return warn "weak_set @_"
425             : $type eq 'SCALAR'
426             ? $obj
427             : return warn "weak_set @_";
428            
429             $$strong = $_[2] if @_ > 2;
430            
431             weaken $$strong if ref $$strong
432             and not isweak $$strong;
433             return
434             };
435            
436             sub mapn (&$@);
437 0     0   0 sub CLONE_SKIP {1}
438            
439             sub parse {
440 7     7 0 11 my (@C, %A, %M);
441 7         18 while (@_) {
442 11         16 my $x = shift;
443 11 50       78 if (isa_object $x) {push @C, $x; next}
  0         0  
  0         0  
444 11 50       27 grep {not defined and $_ = '???'} $x, $_[0]
  22 50       106  
445             and croak "parse failure: [ $x => $_[0] ] @_[1..$#_],";
446 11         22 $x =~ s/^-//;
447 11 100 100     68 if ($x =~ /^_?on/ or ref $_[0] ne 'CODE') {
  1         5  
448 10 100 66     75 $x eq 'style' and $A{$x} .= (shift).';'
449             or $A{$x} = shift}
450             else {$M{$x} = shift}
451             }
452 7         131 C => \@C, A => \%A, M => \%M
453             }
454            
455            
456             =head2 object oriented interface
457            
458             if you prefer an OO interface, there are a few ways to get one:
459            
460             use XUL::Gui 'g->*'; # DYOI: draw your own interface
461            
462             C< g > (which could be any empty package name) now has all of XUL::Gui's
463             functions as methods. since draw your own interface does what you mean
464             (C< dyoidwym > ), each of the following graphic styles are equivalent:
465             C<< g->*, g->, ->g, install_into->g >>.
466            
467             normally, installing methods into an existing package will cause a fatal error,
468             however you can add C to force installation into an existing package
469            
470             no functions are imported into your namespace by default, but you can request
471             any you do want as usual:
472            
473             use XUL::Gui qw( g->* :base :pragma );
474            
475             to use the OO interface:
476            
477             g->display( g->Label('hello world') );
478             # is the same as
479             XUL::Gui::display( XUL::Gui::Label('hello world') );
480            
481             use g->id('someid') or g->ID('someid') to access the %ID hash
482            
483             the XUL tags are also available in lc and lcfirst:
484             g->label == XUI::Gui::Label
485             g->colorpicker == XUL::Gui::ColorPicker
486             g->colorPicker == XUL::Gui::ColorPicker
487            
488             the HTML tags are also available in lc, unless an XUL tag
489             of the same name exists
490            
491             if you prefer an object (which behaves exactly the same as the package 'g'):
492            
493             use XUL::Gui (); # or anything you do want
494             my $g = XUL::Gui->oo; # $g now has XUL::Gui's functions as methods
495            
496             if you like B the OO lowercase names, but want functions, draw that:
497            
498             use XUL::Gui qw( ->main:: ); # ->:: will also export to main::
499             # '::' implies '!'
500             display label 'hello, world';
501            
502            
503             =cut
504             {my %loaded;
505             sub oo {
506 2     2   13 no strict 'refs';
  2         4  
  2         4834  
507 3   50 3 0 12 my $target = $_[1] || 'XUL::Gui::OO';
508 3         9 my $force = $target =~ s/!//g;
509 3         10 my $methods = not $target =~ s/::$//;
510            
511 3   50     10 $target ||= 'main';
512 3   33     15 $force ||= !$methods;
513 3         52 my $pkg = "$target\::";
514            
515 3 100 66     27 if (%$pkg and not $force)
  1   33     201  
516             {return $loaded{$pkg} || croak "package '$pkg' not empty"}
517             mapn {
518 1546     1546   1740 my $sub = \&{$_[1]};
  1546         4442  
519 1546 50   2   7458 *{$pkg.$_} = $methods ? sub {shift; goto &$sub} : $sub;
  1546         10929  
  2         20  
  2         9  
520 2         15 } 2 => %{{
  248         518  
521 484         1010 (map {lc, $_} grep {not /_/} keys %HTML, @{$EXPORT_TAGS{const}}),
  2         10  
  382         903  
522 930         1693 $MOZILLA ? (map {lcfirst, $_} @Xul) : (),
523 932         3688 (map {$_, $_} grep {not /\W|^self$/} @EXPORT_OK),
  6         1440  
524 2 50       93 (map {lc, $_, lcfirst, $_} @{$EXPORT_TAGS{widgets}})
  2         30  
525             }};
526 2     0   14 *{$pkg.'id'} = $methods ? sub :lvalue {$XUL::Gui::ID{$_[1]}}
  0         0  
527 2 50       633 : \&ID;
528 2         141 bless $loaded{$pkg} = {} => substr $pkg, 0, -2
529             }}
530            
531            
532             =head1 FUNCTIONS
533            
534             =head2 gui functions
535            
536             =over 8
537            
538             =item C< display LIST >
539            
540             C< display > starts the http server, launches firefox, and waits for events.
541            
542             it takes a list of gui objects, and several optional parameters:
543            
544             debug (0) .. 6 adjust verbosity to stderr
545             silent (0) 1 disables all stderr status messages
546             trusted 0 (1) starts firefox with '-app' (requires firefox 3+)
547             launch 0 (1) launches firefox, if 0 connect to http://localhost:port
548             skin 0 (1) use the default 'chrome://global/skin' skin
549             chrome 0 (1) chrome mode disables all normal firefox gui elements,
550             setting this to 0 will turn those elements back on.
551             xml (0) 1 returns the object tree as xml, the gui is not launched
552             perl includes deparsed perl event handlers
553             delay milliseconds delays each gui update cycle (for debugging)
554             port first port to start the server on, port++ after that
555             otherwise a random 5 digit port is used
556             mozilla 0 (1) setting this to 0 disables all mozilla specific features
557             including all XUL tags, the filepicker, and any
558             trusted mode features. (used to implement Web::Gui)
559            
560             if the first object is a C< Window >, that window is created, otherwise a
561             default one is added. the remaining objects are then added to the window.
562            
563             C< display > will not return until the the gui quits
564            
565             see C< SYNOPSIS >, L, L, and the
566             C< examples > folder in this distribution for more details
567            
568             =cut
569             sub display {
570 0     0 1 0 for (my $i = 0; $i < @_; $i++) {
571 0 0       0 next if isa_object $_[$i];
572 0 0       0 ref $_[$i] eq 'CODE'
573             ? delay ((splice @_, $i--, 1), $$server{root})
574             : $i++
575             }
576 0 0 0     0 if (@_ == 1 and not isa_object $_[0]) {
577 0         0 @_ = PRE(shift)
578             }
579 0         0 my $args = { &parse };
580 0 0       0 if ($$args{A}{xml}) {
581 0         0 return join "\n" =>
582             map $_->$toXML( 0, $$args{A}{xml} )
583 0         0 => @{$$args{C}}
584             }
585 0         0 $server->start( $args )
586             }
587            
588            
589             =item C< quit >
590            
591             shuts down the server (causes a call to C< display > to return at the end of the
592             current event cycle)
593            
594             C< quit > will shut down the server, but it can only shut down the client in
595             trusted mode.
596            
597             =cut
598             sub quit {
599 0     0 1 0 gui('setTimeout("quit()", 5); 0');
600 0         0 $$server{run} = 0;
601             }
602            
603            
604             =item C< serve PATH MIMETYPE DATA >
605            
606             add a virtual file to the server
607            
608             serve '/myfile.jpg', 'text/jpeg', $jpegdata;
609            
610             the paths C< qw( / /client.js /event /ping /exit /perl ) > are reserved
611            
612             =cut
613 0     0 1 0 sub serve {$server->serve(@_)}
614            
615            
616             =item C< object TAGNAME LIST >
617            
618             creates a gui proxy object, allows run time addition of custom tags
619            
620             object('Label', value=>'hello') is the same as Label( value=>'hello' )
621            
622             the C< object > function is the constructor of all proxied gui objects, and all
623             these objects inherit from C< [object] > which provides the following methods.
624            
625             =cut
626             bless my $object = {
627             WIDGET => 0,
628             NOPROXY => 1,
629             ISA => [],
630             ID => '[object]',
631             M => {
632             attr => sub :lvalue {$_[0]{A}{ $_[1] }},
633             child => sub :lvalue {$_[0]{C}[ $_[1] ]},
634             can => sub :lvalue {$_[0]{M}{ $_[1] }},
635             attributes => sub {%{ $_[0]{A} }},
636             children => sub {@{ $_[0]{C} }},
637             methods => sub {%{ $_[0]{M} }},
638             has => sub {
639             my $self = shift;
640             my ($A, $M) = @$self{qw/A M/};
641             my @found = map {
642             my $required = index($_, '!' ) == -1 ? 0 : s/!//g;
643             my ($key, $as) = index($_, '->') == -1 ? ($_, $_) : /(.+)->(.+)/;
644            
645             exists $$A{ $key }
646             ? ($as => $$A{ $key }) :
647             exists $$M{ $key }
648             ? ($as => $$M{ $key }) :
649             $required ? do {
650             local $Carp::CarpLevel = 1;
651             croak "widget requires attribute/method '$key'";
652             } : ()
653             } split /\s+/ => @_ > 1 ? "@_" : $_[0];
654             wantarray ? @found
655             : @found == 2 ? $found[1] : @found / 2
656             },
657             id => sub {$_[0]{ID}},
658             parent => sub {$_[0]{P }},
659             widget => sub {$_[0]{W }},
660             super => sub {$_[0]{ISA}[$_[1] or 0]},
661             proto => sub :lvalue {$_[0]{ISA}},
662             extends => sub {
663             my $self = shift;
664             my $target = (\%ID == realid) ? $self : \%ID;
665             my $base = $_[0]{W} or croak 'extends takes a widget';
666             if ($TIED_WIDGETS) {
667             XUL::Gui::Hash->new($target, $base)
668             } else {
669             $$target{$_} = $$base{$_} for grep {/[a-z]/} keys %$base;
670             }
671             unshift @{$$self{ISA}}, $base;
672             @_
673             },
674             }
675             } => 'XUL::Gui::Object';
676            
677             my $setup_object = sub {
678             my $self = shift;
679             for (@{$$self{C}}) {
680             $_->$weak_set(P => $self)
681             }
682             };
683             my $install_widget = sub {
684             my ($self, $widget) = @_;
685            
686             my $w = \$$self{W};
687             if ($$w) {
688             if ($$w != $widget and not $$$w{W}) {
689             $$$w{W} = $widget;
690             }
691             } else {
692             $$w = $widget;
693             }
694             };
695            
696             =item object introspection
697            
698             objects and widgets inherit from a base class C< [object] > that provides the
699             following object inspection / extension methods. these methods operate on the
700             current data that XUL::Gui is holding in perl, none of them will ever call out
701             to the gui
702            
703             ->has('item!') returns attributes or methods (see widget for details)
704             ->attr('rows') lvalue access to $$self{A} attributes
705             ->child(2) lvalue access to $$self{C} children
706             it only makes sense to use attr or child to set
707             values on objects before they are written to the gui
708             ->can('update') lvalue access to $$self{M} methods
709             ->attributes returns %{ $$self{A} }
710             ->children returns @{ $$self{C} }
711             ->methods returns %{ $$self{M} }
712             ->widget returns $$self{W}
713             ->id returns $$self{ID}
714             ->parent returns $$self{P}
715             ->super returns $$self{ISA}[0]
716             ->super(2) returns $$self{ISA}[2]
717             ->extends(...) sets inheritance (see widget for details)
718            
719             these methods are always available for widgets, and if they end up getting in
720             the way of any javascript methods you want to call for gui objects:
721            
722             $object->extends(...) # calls the perl introspection function
723             $object->extends_(...) # calls 'object.extends(...)' in the gui
724             $x = $object->_extends; # fetches the 'object.extends' property
725             $object->setAtribute('extends', ...); # and setting an attribute
726            
727             or at runtime:
728            
729             local $XUL::Gui::EXTENDED_OBJECTS = 0; # which prevents object inheritance
730             # in the current lexical scope
731             $object->extends(...);
732             # calls the real javascript 'extends' method assuming that it exists
733            
734             =cut
735            
736             sub object {
737 7   50 7 1 30 my $tag = lc (shift or '');
738 7 50       26 if (my $defaults = $defaults{$tag}) {
739 0         0 unshift @_, @$defaults
740             }
741 7 50       33 bless my $self = {
742             ISA => [$object],
743             $tag ? (
744             TAG => $tag,
745             DIRTY => $tag,
746             ) : (),
747             &parse
748             } => 'XUL::Gui::Object';
749            
750 7 100       379 if (my $id = $$self{A}{id}) {
751             ($$self{ID} = $id)
752 3 100       18 =~ /\W/ and do {
753 1         2 $$self{ID} = 'invalid'; # for DESTROY
754 1         321 croak "id '$id' contains non-word character"
755             }
756             } else {
757 4         14 $$self{ID} = $$self{A}{id} = genid
758             }
759 6 50       22 if ($tag) {
760 6         18 $self->$setup_object;
761 6         23 $ID{$$self{ID}} = $self;
762             }
763             $self
764 6         23 }
765            
766            
767             =item C< tag NAME >
768            
769             returns a code ref that generates proxy objects, allows for user defined tag
770             functions
771            
772             *mylabel = tag 'label';
773             \&mylabel == \&Label
774            
775             =cut
776            
777             sub tag {
778 854     854 1 1607 my @args = @_;
779             sub {
780 6 50 33 6   2257 object @args,
781             (@_ == 1 and not isa_object $_[0])
782             ? 'TEXT' : (),
783             @_
784             }
785 854         5341 }
786 2     2   13 {no strict 'refs';
  2         4  
  2         1440  
787             *$_ = tag $_ for @Xul;
788             *$_ = tag $HTML{$_} for keys %HTML;
789             }
790            
791            
792             =item C< ID OBJECTID >
793            
794             returns the gui object with the id C< OBJECTID >.
795             it is exactly the same as C< $ID{OBJECTID} > and has C< (*) > glob context so
796             you don't need to quote the id.
797            
798             Label( id => 'myid' )
799             ...
800             $ID{myid}->value = 5;
801             ID(myid)->value = 5; # same
802            
803             =cut
804 0     0 1 0 sub ID (*):lvalue {$ID{$_[0]}}
805            
806            
807             =item C< widget {CODE} HASH >
808            
809             widgets are containers used to group tags together into common patterns.
810             in addition to grouping, widgets can have methods, attached data, and can
811             inherit from other widgets
812            
813             *MyWidget = widget {
814             Hbox(
815             Label( $_->has('label->value') ),
816             Button( label => 'OK', $_->has('oncommand') ),
817             $_->children
818             )
819             } method => sub{ ... },
820             method2 => sub{ ... },
821             some_data => [ ... ]; # unless the value is a CODE ref, each widget
822             # instance gets a new deep copy of the data
823            
824             $ID{someobject}->appendChild(
825             MyWidget( label=>'widget', oncommand=>\&event_handler )
826             );
827            
828             inside the widget's code block, several variables are defined:
829            
830             variable contains the passed in
831             $_{A} = { attributes }
832             $_{C} = [ children ]
833             $_{M} = { methods }
834             $_ = a reference to the current widget (also as $_{W})
835             @_ = the unchanged runtime argument list
836            
837             widgets have the following predefined (and overridable) methods that are
838             synonyms / syntactic sugar for the widget variables:
839            
840             $_->has('label') ~~ exists $_{A}{label} ? (label=>$_{A}{label}) : ()
841             $_->has('label->value') ~~ exists $_{A}{label} ? (value=>$_{A}{label}) : ()
842            
843             $_->has('!label !command->oncommand style')
844            
845             ->has(...) splits its arguments on whitespace and will search $_{A}, then
846             $_{M} for the attribute. if an ! is attached (anywhere) to an attribute,
847             it is required, and the widget will croak without it.
848             in scalar context, if only one key => value pair is found, ->has() will
849             return the value. otherwise, the number of found pairs is returned
850            
851             $_->attr( STRING ) $_{A}{STRING} # lvalue
852             $_->attributes %{ $_{A} }
853             $_->child( NUMBER ) $_{C}[NUMBER] # lvalue
854             $_->children @{ $_{C} }
855             $_->can( STRING ) $_{M}{STRING} # lvalue
856             $_->methods %{ $_{M} }
857            
858             most everything that you would want to access is available as a method of the
859             widget (attributes, children, instance data, methods). since there may be
860             namespace collisions, here is the namespace construction order:
861            
862             %widget_methods = (
863             passed in attributes,
864             predefined widget methods,
865             widget methods and instance data,
866             passed in methods
867             );
868            
869             widgets can inherit from other widgets using the ->extends() method:
870            
871             *MySubWidget = widget {$_->extends( &MyWidget )}
872             submethod => sub {...};
873            
874             more detail in L
875            
876             =cut
877            
878             sub widget (&%) {
879 2     2 1 7 my ($code, %methods, $sub) = @_;
880 2         6 my $caller = caller;
881             $sub = sub {
882 0     0   0 my %data;
883 0         0 my $id = realid;
884 0         0 my $inner = \%ID != $id;
885 0 0       0 my $self = $TIED_WIDGETS
886             ? XUL::Gui::Hash->new({parse @_})
887             : {parse @_};
888 0 0 0     0 my $wid = $inner ? genid : $$self{A}{id} || genid;
889            
890 0 0       0 my $_self = $TIED_WIDGETS ? tied(%$self)->hash : $self;
891            
892 0         0 @$_self{qw/ID WIDGET CALLER NOPROXY/} = ($wid, $sub, $caller, 1);
893            
894 0         0 for (keys %methods) {
895 0         0 my ($k, $v) = ($_, $methods{$_});
896 0 0       0 if (ref $v ne 'CODE') {
897 0 0       0 $data{$k} = ref $v ? dclone $v : $v;
898 0     0   0 $v = sub :lvalue {$data{$k}};
  0         0  
899             }
900 0   0     0 $$_self{M}{$k} ||= $v
901             }
902            
903 0         0 hv_store %$_self, $_ => $data{$_} for keys %data;
904            
905 0         0 $$id{$wid} = bless $self => 'XUL::Gui::Object';
906            
907 0 0       0 weaken $$id{$wid} unless $THREADS; # crashes with threads
908            
909 0 0 0     0 $ID{$$_self{A}{id} or genid} = $self if $inner;
910            
911 2     2   12 no strict 'refs';
  2         20  
  2         185  
912 0         0 my $callid = "$caller\::ID";
913 0   0     0 my $setcid = %$callid && \%$callid == \%ID;
914 0         0 local %ID;
915 0 0       0 local *$callid = \%ID if $setcid;
916 2     2   11 use strict 'refs';
  2         3  
  2         3079  
917            
918 0         0 local ($_, *_) = ($self) x 2;
919 0         0 local $_{W} = $self;
920            
921 0         0 $$_self{ISA} = [ $object ];
922 0         0 my $objects = [ &$code ];
923            
924 0         0 my @named_objects;
925             mapn {
926 0 0   0   0 isa_object my $obj = $_[1]
927             or return warn "not an object: $_";
928            
929 0 0       0 if ($TIED_WIDGETS) {
930 0 0       0 if (my $tied = tied %{$_[1]}) {
  0         0  
931 0         0 $tied->unshift($self, $$_self{A});
932             } else {
933 0         0 XUL::Gui::Hash->new($_[1], $self, $$_self{A});
934             }
935             }
936 0         0 $$id{ my $gid = genid } = $obj;
937            
938 0 0       0 if (exists $$obj{WIDGET}) {
939 0         0 weaken $$id{$gid}
940             }
941            
942 0 0 0     0 if ($FILL_GENID_OBJECTS
      0        
943             or $$obj{A}{id} && $$obj{A}{id} !~ /^xul_\d+$/) {
944            
945 0 0       0 isweak $obj or weaken $obj;
946 0         0 hv_store %$_self, $_ => $obj;
947            
948 0 0       0 if (exists $$obj{W}) {
949 0         0 $$obj{EXTENDED_FROM} = $$obj{W}
950             }
951 0         0 $$obj{NAME} = $$obj{A}{id};
952            
953 0         0 push @named_objects, $obj;
954             }
955 0         0 $$obj{W} = $self;
956 0         0 $$obj{ID} = $$obj{A}{id} = $gid;
957            
958 0         0 } 2 => %ID;
959            
960 0 0       0 unless ($TIED_WIDGETS) {
961 0         0 my @keys_self = grep /[a-z]/ => keys %$_self;
962 0         0 my @keys_A = keys %{$$_self{A}};
  0         0  
963            
964 0         0 for my $obj (@named_objects) {
965 0   0     0 exists $$obj{$_} or hv_store %$obj, $_, $$_self{$_} for @keys_self;
966 0   0     0 exists $$obj{$_} or hv_store %$obj, $_, $$_self{A}{$_} for @keys_A;
967             }
968             }
969 0         0 @$objects[0 .. $#$objects]
970             }
971 2         19 }
972            
973            
974             =item C< alert STRING >
975            
976             open an alert message box
977            
978             =cut
979             sub alert {
980 0     0 1 0 gui( "alert('".&escape."')" );
981 0 0       0 wantarray ? @_ : pop
982             }
983            
984            
985             =item C< prompt STRING >
986            
987             open an prompt message box
988            
989             =cut
990             sub prompt {
991 0     0 1 0 gui( "prompt('".&escape."')" )
992             }
993            
994            
995             =item C< filepicker MODE FILTER_PAIRS >
996            
997             opens a filepicker dialog. modes are 'open', 'dir', or 'save'. returns the path
998             or undef on failure. if mode is 'open' and C< filepicker > is called in list
999             context, the picker can select multiple files. the filepicker is only available
1000             when the gui is running in 'trusted' mode.
1001            
1002             my @files = filepicker open =>
1003             Text => '*.txt; *.rtf',
1004             Images => '*.jpg; *.gif; *.png';
1005            
1006             =cut
1007             sub filepicker {
1008 0 0   0 1 0 $MOZILLA or croak "filepicker not available (XUL disabled)";
1009 0   0     0 my $type = shift || 'open';
1010 0 0       0 my $mode = {
1011             open => wantarray
1012             ? [modeOpenMultiple => 'Select Files' ]
1013             : [modeOpen => 'Select a File' ],
1014             save => [modeSave => 'Save as' ],
1015             dir => [modeGetFolder => 'Select a Folder'],
1016             }->{$type};
1017            
1018 0 0 0     0 my $res = gui(qq ~
1019             (function () {
1020             xul_gui.deadman_pause();
1021             var nsIFilePicker = Components.interfaces.nsIFilePicker;
1022             var fp = Components.classes["\@mozilla.org/filepicker;1"]
1023             .createInstance(nsIFilePicker);
1024 0     0   0 fp.init(window, "$$mode[1]", nsIFilePicker.$$mode[0]);
1025             @{[mapn {qq{
1026             fp.appendFilter("$_[0]", "$_[1]");
1027 0         0 }} 2 => @_ ]}
1028             var res = fp.show();
1029             xul_gui.deadman_resume();
1030             if (res == nsIFilePicker.returnCancel) return;~ .
1031             ($type eq 'open' && wantarray ? q {
1032             var files = fp.files;
1033             var paths = [];
1034             while (files.hasMoreElements()) {
1035             var arg = files.getNext().QueryInterface(
1036             Components.interfaces.nsILocalFile ).path;
1037             paths.push(arg);
1038             }
1039             return paths.join("\n")
1040             } : q {return fp.file.path;}
1041             ) . '})()');
1042 0 0       0 defined $res
    0          
1043             ? wantarray
1044             ? split /\n/ => $res
1045             : $res
1046             : ()
1047             }
1048            
1049            
1050             =item C< trace LIST >
1051            
1052             carps C< LIST > with object details, and then returns C< LIST > unchanged
1053            
1054             =cut
1055             sub trace {
1056 0     0 1 0 my $caller = caller;
1057 0 0       0 carp 'trace: ', join ', ' => map {
1058 0         0 (isa_object) ? lookup($_, $caller) : $_
1059             } @_;
1060 0 0       0 wantarray ? @_ : pop
1061             }
1062            
1063             {my %cache;
1064             my $last_caller;
1065             sub lookup {
1066 2     2   13 no strict 'refs';
  2         5  
  2         400  
1067 0     0 0 0 my $self = shift;
1068 0 0 0     0 my $proto = $$self{WIDGET} || $$self{W}{WIDGET}
      0        
1069             or return $$self{ID} || $self;
1070            
1071 0 0 0     0 if (@_) {$last_caller = $_[0]}
  0         0  
  0         0  
1072             else {@_ = $last_caller ||= caller}
1073            
1074 0         0 my $name = $cache{$proto};
1075 0 0       0 unless ($name) {
1076 0         0 our %space;
1077 0         0 local *space = \%{"$_[0]\::"};
  0         0  
1078 0         0 local $@;
1079 0         0 keys %space;
1080 0         0 while (my ($key, $glob) = each %space) {
1081 2     2   13 no warnings;
  2         5  
  2         6339  
1082 0 0       0 if (eval {*$glob{CODE} == $proto}) {
  0         0  
1083 0         0 $cache{$proto} = $name = $key;
1084             last
1085 0         0 }
1086             }
1087             }
1088             $name and return
1089 0 0 0     0 $name . ($$self{WIDGET} ? '{'
    0 0        
1090             : '{'.($$self{W}{A}{id} or $$self{W}{ID}).'}->{')
1091             . ($$self{NAME} or $$self{ID}).'}';
1092            
1093 0 0       0 $$self{ID} or $self
1094             }}
1095            
1096            
1097             =item C< function JAVASCRIPT >
1098            
1099             create a javascript event handler, useful for mouse events that need to be very
1100             fast, such as onmousemove or onmouseover
1101            
1102             Button( label=>'click me', oncommand=> function q{
1103             this.label = 'ouch';
1104             alert('hello from javascript');
1105             if (some_condition) {
1106             perl("print 'hello from perl'");
1107             }
1108             })
1109            
1110             $ID{myid} in perl is ID.myid in javascript
1111            
1112             to access widget siblings by id, wrap the id with C< W{...} >
1113            
1114             =cut
1115             sub function ($) {
1116 0     0 1 0 my $js = shift;
1117             bless [sub {
1118 0     0   0 my $self = shift;
1119 0         0 my $func = 'ID.' . genid;
1120             delay( sub {
1121 0         0 $js =~ s[\$?W{\s*(\w+)\s*}] [ID.$$self{W}{$1}{ID}]g;
1122 0         0 gui(
1123             qq{SET;$func = function (event) {
1124             try {return (function(){ $js }).call( ID.$$self{ID} )}
1125             catch (e) {alert( e.name + "\\n" + e.message )}
1126             }})
1127 0         0 });
1128 0         0 "$func(event)"
1129 0         0 }] => 'XUL::Gui::Function'
1130             }
1131            
1132            
1133             =item C< interval {CODE} TIME LIST >
1134            
1135             perl interface to javascript's C< setInterval() >. interval returns a code ref
1136             which when called will cancel the interval. C< TIME > is in milliseconds.
1137             C< @_ > will be set to C< LIST > when the code block is executed.
1138            
1139             =cut
1140             sub interval (&$@) {
1141 0     0 1 0 my ($code, $time) = splice @_, 0, 2;
1142 0         0 my $list = \@_;
1143 0         0 my $id = genid;
1144 0     0   0 realid($id)= sub {$code->(@$list)};
  0         0  
1145             # = sub {local *_ = $list; goto &$code};
1146 0         0 gui( qq{SET;ID.$id = setInterval( "pevt('XUL::Gui::realid(q|$id|)->()')", $time)} );
1147 0     0   0 sub {gui(qq{SET;clearInterval(ID.$id)})}
1148 0         0 }
1149            
1150            
1151             =item C< timeout {CODE} TIME LIST >
1152            
1153             perl interface to javascript's C< setTimeout() >. timeout returns a code ref
1154             which when called will cancel the timeout. C< TIME > is in milliseconds. C< @_ >
1155             will be set to C< LIST > when the code block is executed.
1156            
1157             =cut
1158             sub timeout (&$@) {
1159 0     0 1 0 my ($code, $time) = splice @_, 0, 2;
1160 0         0 my $list = \@_;
1161 0         0 my $id = genid;
1162 0     0   0 realid($id) = sub {$code->(@$list)};
  0         0  
1163 0         0 gui( qq{SET;ID.$id = setTimeout( "pevt('XUL::Gui::realid(q|$id|)->()')", $time)} );
1164 0     0   0 sub {gui(qq{SET;cancelTimeout(ID.$id)})}
1165 0         0 }
1166            
1167             sub escape {
1168 0     0 0 0 my $str = $_[0];
1169            
1170 0 0       0 return $str if $str !~ /[\\\n\r']|[^[:ascii:]]/;
1171            
1172 0         0 $str =~ s/\\/\\\\/g;
1173 0         0 $str =~ s/\n/\\n/g;
1174 0         0 $str =~ s/\r/\\r/g;
1175 0         0 $str =~ s/'/\\'/g;
1176 0     0   0 $str =~ /[^[:ascii:]]/
1177             ? encode ascii => $str
1178             => sub {sprintf '\u%04X', $_[0]}
1179 0 0       0 : $str
1180             }
1181            
1182            
1183             =item C< XUL STRING >
1184            
1185             converts an XML XUL string to C< XUL::Gui > objects. experimental.
1186            
1187             this function is provided to facilitate drag and drop of XML based XUL from
1188             tutorials for testing. the perl functional syntax for tags should be used in all
1189             other cases
1190            
1191             =cut
1192             {my %xul; @xul{map lc, @Xul} = @Xul;
1193             sub XUL {
1194 0 0   0 1 0 $MOZILLA or croak "XUL disabled";
1195 0         0 local $@;
1196 0         0 for ("@_") {
1197 0         0 s {<(\w+)(.+?)} "XUL::Gui::$xul{lc $1}($2"g;
1198 0         0 s {/>} '),'g;
1199 0         0 s {} '),'g;
1200 0         0 s {>} ''g;
1201 0         0 s {(\w+)\s*=\s*(\S+)} "'$1'=>$2"g;
1202 0         0 s <([^\\](}|"|'))\s+> "$1,"g;
1203 0 0       0 return eval 'package '.caller().";$_"
1204             or carp "content skipped due to parse failure: $@\n\n$_"
1205             }
1206             }}
1207            
1208            
1209             =item C< gui JAVASCRIPT >
1210            
1211             executes C< JAVASCRIPT > in the gui, returns the result
1212            
1213             =back
1214            
1215             =cut
1216             {my ($buffered, @buffer, @setbuf, $cached, %cache, $now);
1217             sub gui :lvalue {
1218 1     1 1 4 my $msg = "@_\n";
1219 1         2 my $type = '';
1220 1 50       5 if (substr($msg, 1, 2) eq 'ET') {
1221 0         0 my $first = substr $msg, 0, 1;
1222 0 0 0     0 if ($first eq 'S' or $first eq 'G') {
1223 0 0       0 if ((my $check = substr $msg, 3, 1) eq '(') {
    0          
1224 0         0 $type = $first . 'ET';
1225             }
1226             elsif ($check eq ';') {
1227 0         0 $type = $first . 'ET';
1228 0         0 $msg = substr $msg, 4;
1229             }
1230             }
1231             }
1232 1 50       4 unless ($now) {
1233 1 50 0     79 push @buffer, $msg and return if $buffered;
1234 1 50 0     12 push @setbuf, $msg and return if $AUTOBUFFER
      33        
      33        
1235             and $type eq 'SET'
1236             and not $cached;
1237 1 50       5 return $cache{$msg} if exists $cache{$msg};
1238             }
1239 1 50       4 if (@setbuf) {
1240 0         0 $msg = join '' => @setbuf, $msg;
1241 0         0 @setbuf = ();
1242             }
1243 1 50 33     7 defined wantarray or $msg .= ';true'
1244             unless $cached;
1245            
1246 1         7 $server->write('text/plain', $msg);
1247 0         0 my $res = $server->read_until('/res');
1248            
1249 0 0 0     0 if (defined wantarray or $cached) {
1250 0 0       0 ($res = $$res{CONTENT}) =~ /^(...) (.*)/s
1251             or croak "invalid response: $res";
1252            
1253 0 0 0     0 $res = $1 eq 'OBJ'
    0          
1254             ? ($ID{$2} || object undef, id=>$2)
1255             : $1 eq 'UND'
1256             ? undef
1257             : $2;
1258 0 0       0 if ($cached) {
1259 0 0       0 if ($type eq 'SET') {
1260 0         0 $type = 'GET';
1261 0         0 $msg =~ s/.[^,]+(?=\).*?$)//;
1262 0         0 substr $msg, 0, 3, 'GET';
1263             }
1264 0 0       0 $cache{$msg} = $res if $type eq 'GET'
1265             }
1266             }
1267             $res
1268 0         0 }
1269            
1270             =head2 data binding
1271            
1272             =over 8
1273            
1274             passing a reference to a scalar or coderef as a value in an object constructor
1275             will create a data binding between the perl variable and its corresponding
1276             value in the gui.
1277            
1278             use XUL::Gui;
1279            
1280             my $title = 'initial title';
1281            
1282             display Window title => \$title,
1283             Button(
1284             label => 'update title',
1285             oncommand => sub {
1286             $title = 'title updated via data binding';
1287             }
1288             );
1289            
1290             a property on a previously declared object can also be bound by taking a
1291             reference to it:
1292            
1293             display
1294             Label( id => 'lbl', value => 'initial value'),
1295             Button(
1296             label => 'update',
1297             oncommand => sub {
1298             my $label = \ID(lbl)->value;
1299            
1300             $$label = 'new value';
1301             }
1302             )
1303            
1304             this is just an application of the normal bidirectional behavior of gui
1305             accessors:
1306            
1307             for (ID(lbl)->value) {
1308             print "$_\n"; # gets the current value from the gui
1309            
1310             $_ = 'new'; # sets the value in the gui
1311            
1312             print "$_\n"; # gets the value from the gui again
1313             }
1314            
1315             =back
1316            
1317             =head2 pragmatic blocks
1318            
1319             the following functions all apply pragmas to their CODE blocks. in some cases,
1320             they also take a list. this list will be C< @_ > when the CODE block executes.
1321             this is useful for sending in values from the gui, if you don't want to use a
1322             C< now {block} >
1323            
1324             =head3 autobuffering
1325            
1326             this module will automatically buffer certain actions within event handlers.
1327             autobuffering will queue setting of values in the gui until there is a get, the
1328             event handler ends, or C< doevents > is called. this eliminates the need for
1329             many common applications of the C< buffered > pragma.
1330            
1331             =over 8
1332            
1333             =item C< flush >
1334            
1335             flush the autobuffer
1336            
1337             =cut
1338             sub flush {
1339 0 0   0 1 0 if (@setbuf) {
1340 0         0 $server->write('text/plain', join '' => @setbuf);
1341 0         0 @setbuf = ();
1342 0         0 $server->read_until('/res');
1343             }
1344             }
1345            
1346             =item C< buffered {CODE} LIST >
1347            
1348             delays sending all messages to the gui. partially deprecated (see autobuffering)
1349            
1350             buffered {
1351             $ID{$_}->value = '' for qw/a bunch of labels/
1352             }; # all labels are cleared at once
1353            
1354             =cut
1355             sub buffered (&@) {
1356 0     0 1 0 $buffered++;
1357 0         0 &{+shift};
  0         0  
1358 0 0       0 unless (--$buffered) {
1359 0         0 gui "SET;@buffer";
1360 0         0 @buffer = ();
1361             }
1362             return
1363 0         0 }
1364            
1365            
1366             =item C< cached {CODE} >
1367            
1368             turns on caching of gets from the gui
1369            
1370             =cut
1371             sub cached (&) {
1372 0     0 1 0 $cached++;
1373 0         0 my $ret = shift->();
1374 0 0       0 %cache = () unless --$cached;
1375 0         0 $ret
1376             }
1377            
1378            
1379             =item C< now {CODE} >
1380            
1381             execute immediately, from inside a buffered or cached block, without causing a
1382             buffer flush or cache reset. buffered and cached will not work inside a now
1383             block.
1384            
1385             =cut
1386             sub now (&) {
1387 0     0 1 0 my ($want, @ret) = wantarray;
1388 0         0 $now++;
1389 0 0       0 $want ? @ret = shift->()
1390             : $ret[0] = shift->();
1391 0         0 $now--;
1392 0 0       0 $want ? @ret : $ret[0]
1393             }
1394             }
1395            
1396            
1397             =item C< delay {CODE} LIST >
1398            
1399             delays executing its CODE until the next gui refresh
1400            
1401             useful for triggering widget initialization code that needs to run after the gui
1402             objects are rendered. the first element of C< LIST > will be in C< $_ > when
1403             the code block is executed
1404            
1405             =cut
1406             sub delay (&@) {
1407 1     1 1 4 my $code = shift;
1408 1         4 my $args = \@_;
1409 1 0   0   3 push @{$$server{queue}}, sub {@$args and local *_ = \$$args[0]; $code->(@$args)};
  1         24  
  0         0  
  0         0  
1410             return
1411 1         4 }
1412            
1413            
1414             =item C< noevents {CODE} LIST >
1415            
1416             disable event handling
1417            
1418             =cut
1419             sub noevents (&@) {
1420 0     0 1 0 gui 'xul_gui.cacheEvents(false);';
1421 0         0 my @ret = &{+shift};
  0         0  
1422 0         0 gui 'xul_gui.cacheEvents(true);';
1423             @ret
1424 0         0 }
1425            
1426            
1427             =item C< doevents >
1428            
1429             force a gui update cycle before an event handler finishes
1430            
1431             =cut
1432             sub doevents () {
1433 0     0 1 0 $server->{dispatch}{'/ping'}();
1434 0         0 $server->read_until('/ping');
1435             return
1436 0         0 }
1437            
1438            
1439             =back
1440            
1441             =head2 utility functions
1442            
1443             =over 8
1444            
1445             =item C< mapn {CODE} NUMBER LIST >
1446            
1447             map over n elements at a time in C< @_ > with C< $_ == $_[0] >
1448            
1449             print mapn {$_ % 2 ? "@_" : " [@_] "} 3 => 1..20;
1450             > 1 2 3 [4 5 6] 7 8 9 [10 11 12] 13 14 15 [16 17 18] 19 20
1451            
1452             =cut
1453             sub mapn (&$@) {
1454 3     3 1 654 my ($sub, $n, @ret) = splice @_, 0, 2;
1455 3 50       19 croak '$_[1] must be >= 1' unless $n >= 1;
1456            
1457 3 50       11 return map $sub->($_) => @_ if $n == 1;
1458            
1459 3         8 my $want = defined wantarray;
1460 3         12 while (@_) {
1461 1550         2885 local *_ = \$_[0];
1462 1550 100       2830 if ($want) {push @ret =>
  4         14  
  1546         3577  
1463             $sub->(splice @_, 0, $n)}
1464             else {$sub->(splice @_, 0, $n)}
1465             }
1466             @ret
1467 3         952 }
1468            
1469            
1470             =item C< zip LIST of ARRAYREF >
1471            
1472             %hash = zip [qw/a b c/], [1..3];
1473            
1474             =cut
1475             sub zip {
1476 1     1 1 18 map {my $i = $_;
  3         4  
1477 3         16 map $$_[$i] => @_
1478             } 0 .. max map $#$_ => @_
1479             }
1480            
1481            
1482             =item C< apply {CODE} LIST >
1483            
1484             apply a function to a copy of C< LIST > and return the copy
1485            
1486             print join ", " => apply {s/$/ one/} "this", "and that";
1487             > this one, and that one
1488            
1489             =cut
1490             sub apply (&@) {
1491 1     1 1 4 my ($sub, @ret) = @_;
1492 1         6 $sub->() for @ret;
1493 1 50       19 wantarray ? @ret : pop @ret
1494             }
1495            
1496            
1497             =item C< toggle TARGET OPT1 OPT2 >
1498            
1499             alternate a variable between two states
1500            
1501             toggle $state; # opts default to 0, 1
1502             toggle $state => 'red', 'blue';
1503            
1504             =cut
1505             sub toggle {
1506 2     2   15 no warnings;
  2         353  
  2         1956  
1507 0     0 1 0 my @opt = (splice(@_, 1), 0, 1);
1508 0   0     0 $_[0] = $opt[ $_[0] eq $opt[0] or $_[0] ne $opt[1] ]
1509             }
1510            
1511            
1512             =item C< bitmap WIDTH HEIGHT OCTETS >
1513            
1514             returns a binary .bmp bitmap image. C< OCTETS > is a list of C< BGR > values
1515            
1516             bitmap 2, 2, qw(255 0 0 255 0 0 255 0 0 255 0 0); # 2px blue square
1517            
1518             for efficiency, rather than a list of C< OCTETS >, you can send in a single
1519             array reference. each element of the array reference can either be an array
1520             reference of octets, or a packed string C<< pack "C*" => OCTETS >>
1521            
1522             =cut
1523             sub bitmap {
1524 1     1 1 4 my ($width, $height) = splice @_, 0, 2;
1525            
1526 1   33     5 my @pad = map {(0) x ($_ and 4 - $_)} ($width*3) % 4;
  1         11  
1527            
1528 1         4 my $size = $height * ($width * 3 + @pad);
1529            
1530 2         39 pack 'n V n n (N)2 (V)2 n n N V (N)4 (a*)*' =>
1531             0x42_4D, # "BM" # file format thanks to Wikipedia
1532             (54 + $size), # file size
1533             0x00_00, # not used
1534             0x00_00, # not used
1535             0x36_00_00_00, # offset of bitmap data
1536             0x28_00_00_00, # remaining bytes in header
1537             $width,
1538             $height,
1539             0x01_00, # color planes
1540             0x18_00, # bits/pixel (24)
1541             0x00_00_00_00, # no compression
1542             $size, # size of raw BMP data (after header)
1543             0x13_0B_00_00, # horizontal res
1544             0x13_0B_00_00, # vertical res
1545             0x00_00_00_00, # not used
1546             0x00_00_00_00, # not used
1547             reverse
1548             @_ > 1
1549 0 0       0 ? map {pack 'C*' => splice(@_, 0, $width*3), @pad} 1 .. $height
1550 0         0 : map {ref $_
1551             ? pack 'C*' => @$_, @pad
1552             : pack 'a* C*' => $_, @pad
1553 1 50       8 } @{$_[0]}
1554             }
1555            
1556            
1557             =item C< bitmap2src WIDTH HEIGHT OCTETS >
1558            
1559             returns a packaged bitmap image that can be directly assigned to an image tag's
1560             src attribute. arguments are the same as C< bitmap() >
1561            
1562             $ID{myimage}->src = bitmap2src 320, 180, @image_data;
1563            
1564             =back
1565            
1566             =cut
1567 1     1 1 583 sub bitmap2src {'data:image/bitmap;base64,' . encode_base64 &bitmap}
1568            
1569            
1570             =head1 METHODS
1571            
1572             # access attributes and properties
1573            
1574             $object->value = 5; # sets the value in the gui
1575             print $object->value; # gets the value from the gui
1576            
1577             # the attribute is set if it exists, otherwise the property is set
1578            
1579             $object->_value = 7; # sets the property directly
1580            
1581             # method calls
1582            
1583             $object->focus; # void context or
1584             $object->appendChild( H2('title') ); # any arguments are always methods
1585             print $object->someAccessorMethod_; # append _ to force interpretation
1586             # as a JS method call
1587            
1588             in addition to mirroring all of an object's existing javascript methods /
1589             attributes / and properties to perl (with identical spelling / capitalization),
1590             several default methods have been added to all objects
1591            
1592             =over 8
1593            
1594             =cut
1595            
1596             package
1597             XUL::Gui::Object;
1598             my $can; $can = sub {
1599             my ($self, $method) = @_;
1600            
1601             $server->status(' ' . XUL::Gui::lookup($self) . "->can( $method ) ?")
1602             if ($DEBUG > 4
1603             or $DEBUG > 3 and $method !~ /^~/)
1604             and $self != $object;
1605            
1606             $$self{M}{$method}
1607             or do {
1608             return if $self == $object;
1609             if ($$self{WIDGET}) {
1610             if (exists $$self{$method}) {
1611             return ref $$self{$method} eq 'CODE'
1612             ? $$self{$method}
1613             : sub:lvalue {$$self{$method}}
1614             }
1615             if (exists $$self{A}{$method}) {
1616             return sub:lvalue {$$self{A}{$method}}
1617             }
1618             } else {
1619             return unless $EXTENDED_OBJECTS
1620             }
1621             for (@{$$self{ISA}})
1622             {return $_->$can($method) || next}
1623             }
1624             };
1625             sub can :lvalue;
1626             sub attr :lvalue;
1627             sub child :lvalue;
1628            
1629             use overload fallback => 1, '@{}' => sub {
1630 0     0   0 tie my @ret => 'XUL::Gui::Array', shift;
1631             \@ret
1632 2     2   1873 };
  2         1348  
  2         27  
  0         0  
1633            
1634             {
1635             my $debug_perl_method_call = sub {
1636             my ($self, $name) = splice @_, 0, 3;
1637             my $caller = caller 1;
1638             $server->status('perl: '. XUL::Gui::lookup($self, $caller) . "->$name(" .
1639             (join ', ' => map {(XUL::Gui::isa_object)
1640             ? XUL::Gui::lookup($_, $caller) : "'$_'"} @_). ")")
1641             };
1642            
1643             my $debug_js_method_call = sub {
1644             my ($self, $name) = splice @_, 0, 2;
1645             $server->status( "gui: ID.$$self{ID}.$name(" .
1646             (join ', ' => map {(XUL::Gui::isa_object)
1647             ? "ID.$$_{ID}" : "'$_'"} @_). ")" )
1648             };
1649            
1650             sub AUTOLOAD :lvalue {
1651 0     0   0 my $self = $_[0];
1652 0 0       0 my $name = substr our $AUTOLOAD, 1 + rindex $AUTOLOAD, ':'
1653             or Carp::croak "invalid autoload: $AUTOLOAD";
1654            
1655 0 0       0 if (my $method = $self->$can($name)) {
1656 0 0       0 $debug_perl_method_call->($self, $name, @_) if $DEBUG;
1657 0         0 goto &$method
1658             }
1659 0 0 0     0 if ($$self{NOPROXY} or not shift->{ID}) {
1660 0         0 Carp::croak "no method '$name' on ". XUL::Gui::lookup($self, scalar caller)
1661             }
1662 0         0 my $void = not defined wantarray;
1663            
1664 0 0 0     0 if (substr($name, -1) eq '_' && chop $name or @_ or $void) {
      0        
      0        
1665            
1666 0 0       0 $debug_js_method_call->($self, $name, @_) if $DEBUG > 1;
1667            
1668 0   0     0 {($$self{uc $name} or next)
  0         0  
1669             -> (local $_ = $self)
1670             => return}
1671 0         0 my @pre;
1672 0         0 my $arg = join ',' => map {not defined and 'null' or
1673 0 0 0     0 XUL::Gui::isa_object and do {
      0        
      0        
1674 0 0       0 push @pre, $_->$toJS(undef, $self) if $$_{DIRTY};
1675 0         0 "ID.$$_{ID}"
1676             } or "'" . XUL::Gui::escape($_) . "'"
1677             } @_;
1678 0         0 local $" = '';
1679 0         0 return XUL::Gui::gui 'SET;' x $void, "@pre; ID.$$self{ID}.$name($arg);"
1680             }
1681 0 0       0 $server->status("proxy: ID.$$self{ID}.$name") if $DEBUG > 2;
1682            
1683 0         0 tie my $ret, 'XUL::Gui::Scalar', $self, $name; # proxy
1684 0         0 $ret
1685             }
1686             }
1687            
1688             {my @queue;
1689             my $rid = XUL::Gui::realid;
1690             sub DESTROY {
1691             return unless @_
1692 1 50 33 1   23 and Scalar::Util::reftype $_[0] eq 'HASH'
      33        
1693             and $_[0]{ID};
1694 1         4 delete $rid->{$_[0]{ID}};
1695 1         6 push @queue, "delete ID.$_[0]{ID};";
1696 1 50       6 if (@queue == 1) {
1697             XUL::Gui::delay {
1698 0     0   0 local $" = '';
1699 0         0 XUL::Gui::gui "SET;@queue";
1700 0         0 @queue = ();
1701 0         0 for (keys %$rid) {
1702 0 0       0 unless (defined $$rid{$_}) {
1703 0         0 delete $$rid{$_}
1704             }
1705             }
1706             }
1707 1         62 }
1708 1 50       3 untie %{$_[0]} if tied %{$_[0]};
  0         0  
  1         21  
1709             }}
1710 0     0   0 sub CLONE_SKIP {1}
1711            
1712             {my $deparser;
1713             $toXML = sub {
1714             my $self = shift;
1715             my $tab = shift || 0;
1716             my (@xml, @perl);
1717             my $text = '';
1718            
1719             my $deparse = (shift||'') eq 'perl' ? do {
1720             $deparser ||= do {
1721             require B::Deparse;
1722             my $d = B::Deparse->new('-sC');
1723             $d->ambient_pragmas(qw/strict all warnings all/);
1724             $d
1725             }} : 0;
1726            
1727             $self->$preprocess unless $deparse;
1728             for ($$self{CODE}) {
1729             if (defined) {
1730             my $tabs = "\t" x $tab;
1731             s/^/$tabs/mg;
1732             return substr $_, $tab;
1733             }
1734             }
1735             my $tag = $$self{TAG};
1736            
1737             $MOZILLA or $tag =~ s/^html://;
1738            
1739             push @xml, "<$tag ";
1740             for (keys %{$$self{A}}) {
1741             if ($deparse and ref (my $code = $$self{A}{$_}) eq 'CODE') {
1742             push @xml, qq{$_="alert('handled by perl')" };
1743             push @perl, bless {CODE => "\n"};
1745             next
1746             }
1747             my $val = XUL::Gui::escape $$self{A}{$_};
1748             if ($_ eq 'TEXT') {
1749             $val =~ s/\\n/\n/g;
1750             $text = $val;
1751             next
1752             }
1753             push @xml, qq{$_="$val" };
1754             }
1755             if (@{$$self{C}} or $text or @perl) {
1756             push @xml, ">$text\n";
1757             push @xml, "\t" x ($tab+1), $_->$toXML($tab+1, $deparse ? 'perl' : ())
1758             for @perl, @{$$self{C}};
1759             push @xml, "\t" x $tab, "\n";
1760             } else {
1761             if ($MOZILLA) {
1762             push @xml, "/>\n"
1763             } else {
1764             push @xml, ">\n"
1765             }
1766             }
1767             join '' => @xml
1768             }}
1769            
1770             {my $id = XUL::Gui::realid;
1771             $preprocess = sub {
1772             my $self = $_[0];
1773             die 'processed again' unless $$self{DIRTY};
1774             $$self{DIRTY} = 0;
1775             my $attr = $$self{A};
1776             for my $key (keys %$attr) {
1777             my $val = \$$attr{$key};
1778             if (ref $$val eq 'XUL::Gui::Function') {
1779             $$val = $$$val[0]( $self )
1780             }
1781             my $ref = ref $$val;
1782             if ($ref eq 'SCALAR' or $ref eq 'REF') {
1783             my $bound = $$val;
1784             $$val = $$bound;
1785             tie $$bound => 'XUL::Gui::Scalar', $self, $key;
1786             }
1787             if (substr($key, 0, 1) eq '_') {
1788             substr $key, 0, 1, '';
1789             }
1790             next unless index($key, 'on') == 0 and ref $$val eq 'CODE';
1791             $$self{uc $key} = $$val;
1792             $$val = "EVT(event,'$$self{ID}');";
1793             }
1794             }}
1795            
1796             $toJS = sub {
1797             my ($root, $final, $parent) = @_;
1798             my @queue = $root;
1799             my (@pre, @post);
1800             my $realid = XUL::Gui::realid;
1801            
1802             if ($parent) {
1803             $root->$weak_set( P => $parent );
1804             push @{$$parent{C}}, $root;
1805            
1806             if (my $widget = $$parent{W}) {
1807             $root->$install_widget($widget)
1808             }
1809             }
1810             while (my $node = shift @queue) {
1811             $node->$preprocess;
1812             if (my $code = $$node{CODE}) {
1813             push @pre, $code;
1814             next
1815             }
1816             my $id = "ID.$$node{ID}";
1817             my ($attribute, $children, $tag) = @$node{qw/A C TAG/};
1818            
1819             my $widget = $$node{W};
1820             for my $child (@$children) {
1821             push @queue, $child;
1822             push @post, qq{$id.appendChild(ID.$$child{ID});} if $$child{TAG};
1823             $child->$weak_set( P => $node );
1824             $child->$install_widget($widget) if $widget;
1825             }
1826             $weaken->($$realid{$$node{ID}}) unless $THREADS;
1827            
1828             push @pre, qq{$id=document.createElement} .
1829             ($MOZILLA
1830             ? index ($tag, ':') == -1
1831             ? qq{('$tag');}
1832             : qq{NS('http://www.w3.org/1999/xhtml','$tag');}
1833             : $tag =~ /^html:(.+)/
1834             ? qq{('$1');}
1835             : Carp::croak "$tag is not an HTML tag");
1836            
1837             keys %$attribute;
1838             while (my ($key, $val) = each %$attribute) {
1839             my $clean = XUL::Gui::escape $val;
1840             if ($key eq 'TEXT') {
1841             push @pre, qq{$id.appendChild(document.createTextNode('$clean'));}
1842             } elsif (substr($key, 0, 1) eq '_') {
1843             if (substr($key, 1, 2) eq 'on') {
1844             push @post, qq{$id.} . (substr $key, 1) . qq{=function(event){if(!event){event=window.event}$val};}
1845             } else {
1846             push @post, qq{$id\['}. (substr $key, 1). qq{']='$clean';}
1847             }
1848             } else {
1849             push @pre, qq{$id.setAttribute('\L$key\E','$clean');}
1850             }
1851             }
1852             }
1853             push @post, "$final.appendChild(ID.$$root{ID});" if $final;
1854            
1855             local $" = $DEBUG ? "\n" : '';
1856             "@pre@post"
1857             };
1858            
1859            
1860             =item C<< ->removeChildren( LIST ) >>
1861            
1862             removes the children in C< LIST >, or all children if none are given
1863            
1864             =cut
1865            
1866             my $remove_children = sub {
1867             my $self = shift;
1868             if (@_) {
1869             my %remove = map {$_ => 1} @_;
1870             @{$$self{C}} = grep {not $remove{$_}} @{$$self{C}};
1871             } else {
1872             @{$$self{C}} = ()
1873             }
1874             };
1875            
1876            
1877             sub removeChildren {
1878 0     0   0 my $self = shift;
1879 0     0   0 @_ ? XUL::Gui::buffered {$self->removeChild_($_) for @_} @_
1880 0 0       0 : XUL::Gui::gui "SET;ID.$$self{ID}.removeChildren();";
1881            
1882 0         0 $self->$remove_children(@_);
1883 0         0 $self
1884             }
1885            
1886            
1887             =item C<< ->removeItems( LIST ) >>
1888            
1889             removes the items in C< LIST >, or all items if none are given
1890            
1891             =cut
1892             sub removeItems {
1893 0     0   0 my $self = shift;
1894 0     0   0 @_ ? XUL::Gui::buffered {$self->removeItem_($_) for @_} @_
1895 0 0       0 : XUL::Gui::gui "SET;ID.$$self{ID}.removeItems();";
1896            
1897 0 0       0 $self->$remove_children(@_ ? @_ : grep {$$_{TAG} =~ /item/i} @{ $$self{C} });
  0         0  
  0         0  
1898 0         0 $self
1899             }
1900            
1901            
1902             =item C<< ->appendChildren( LIST ) >>
1903            
1904             appends the children in C< LIST >
1905            
1906             =cut
1907            
1908             sub appendChild {
1909 0     0   0 my ($self, $child) = @_;
1910 0         0 push @{ $$self{C} }, $child;
  0         0  
1911 0         0 $self->appendChild_( $child );
1912             }
1913            
1914             sub removeChild {
1915 0     0   0 my ($self, $child) = @_;
1916 0         0 $self->removeChild_($child);
1917 0         0 my $children = $$self{C};
1918 0         0 for (0 .. $#$children) {
1919 0 0       0 if ($$children[$_] == $child) {
1920 0         0 return splice @$children, $_, 1
1921             }
1922             }
1923             }
1924            
1925             sub appendChildren {
1926 0     0   0 my $self = shift;
1927 0     0   0 XUL::Gui::buffered {$self->appendChild($_) for @_} @_;
  0         0  
1928 0         0 $self
1929             }
1930            
1931            
1932             =item C<< ->prependChild( CHILD, [INDEX] ) >>
1933            
1934             inserts C< CHILD > at C< INDEX > (defaults to 0) in the parent's child list
1935            
1936             =cut
1937             sub prependChild {
1938 0     0   0 my ($self, $child, $count, $first) = @_;
1939 0 0       0 if ($$self{TAG} eq 'tabs') {
1940 0   0     0 $first = $self->getItemAtIndex( $count || 0 )
1941             } else {
1942 0         0 $first = $self->firstChild;
1943 0         0 while ($count-- > 0) {
1944 0 0       0 last unless $first;
1945 0         0 $first = $first->nextSibling;
1946             }
1947             }
1948 0 0       0 $first ? $self->insertBefore( $child, $first )
1949             : $self->appendChild ( $child );
1950 0         0 push @{$$self{C}}, $child;
  0         0  
1951 0         0 $self
1952             }
1953            
1954             =item C<< ->replaceChildren( LIST ) >>
1955            
1956             removes all children, then appends C< LIST>
1957            
1958             =cut
1959             sub replaceChildren {
1960 0     0   0 my ($self, @children) = @_;
1961             XUL::Gui::buffered {
1962             XUL::Gui::noevents {
1963 0         0 $self->removeChildren
1964             ->appendChildren( @children )
1965 0     0   0 }};
  0         0  
1966 0         0 $self
1967             }
1968            
1969             =item C<< ->appendItems( LIST ) >>
1970            
1971             append a list of items
1972            
1973             =cut
1974             sub appendItems {
1975 0     0   0 my ($self, @items) = @_;
1976             XUL::Gui::buffered {
1977             (XUL::Gui::isa_object)
1978             ? $self->appendChild($_)
1979             : $self->appendItem( ref eq 'ARRAY' ? @$_ : $_ )
1980 0 0   0   0 for @items
    0          
1981 0         0 };
1982 0         0 $self
1983             }
1984            
1985            
1986             =item C<< ->replaceItems( LIST ) >>
1987            
1988             removes all items, then appends C< LIST>
1989            
1990             =back
1991            
1992             =cut
1993             sub replaceItems {
1994 0     0   0 my ($self, @items) = @_;
1995             XUL::Gui::buffered {
1996             XUL::Gui::noevents {
1997 0         0 $self->removeItems
1998             ->appendItems( @items )
1999 0     0   0 }};
  0         0  
2000 0         0 $self
2001             }
2002            
2003            
2004             package
2005             XUL::Gui::Scalar;
2006 2     2   9026 use Carp;
  2         5  
  2         2224  
2007            
2008 0     0   0 sub TIESCALAR {bless [ @_[1..$#_] ] => $_[0]}
2009 0     0   0 sub DESTROY { }
2010 0     0   0 sub CLONE_SKIP {1}
2011            
2012             sub FETCH {
2013 0     0   0 my ($self, $AL) = @{+shift};
  0         0  
2014 0 0       0 return $$self{uc $AL} if $AL =~ /^on/;
2015 0 0       0 XUL::Gui::gui $AL =~ /^_(.+)/
2016             ? "GET;ID.$$self{ID}\['$1'];"
2017             : "GET(ID.$$self{ID}, '$AL');"
2018             }
2019            
2020             sub STORE {
2021 0     0   0 my ($self, $AL, $new) = (@{+shift}, @_);
  0         0  
2022 0 0       0 if ($AL =~ /^on/) {
2023 0 0       0 if (ref $new eq 'XUL::Gui::Function') {
2024 0         0 $new = $$new[0]($self);
2025             } else {
2026 0 0 0     0 not defined $new or ref $new eq 'CODE'
2027             or croak "assignment to event handler must be CODE ref, 'function q{...}', or undef";
2028 0 0       0 $new = $new ? do {$$self{uc $AL} = $new; "EVT(event, '$$self{ID}')"} : '';
  0         0  
  0         0  
2029             }
2030             }
2031 0 0       0 $new = defined $new ? "'" . XUL::Gui::escape($new) . "'" : 'null';
2032            
2033 0 0       0 XUL::Gui::gui $AL =~ /^_(.+)/
2034             ? "SET;ID.$$self{ID}\['$1'] = $new;"
2035             : "SET(ID.$$self{ID}, '$AL', $new);"
2036             }
2037            
2038            
2039             {my ($fetch, $store) = (\&FETCH, \&STORE);
2040             package
2041             XUL::Gui::Array;
2042 0     0   0 sub TIEARRAY {bless \pop}
2043 0     0   0 sub FETCH {@_ = [${$_[0]}, '_'.$_[1]]; goto &$fetch}
  0         0  
  0         0  
2044 0     0   0 sub FETCHSIZE {@_ = [${$_[0]}, '_length']; goto &$fetch}
  0         0  
  0         0  
2045 0     0   0 sub STORE {@_ = ([${$_[0]}, '_'.$_[1]], $_[2]); goto &$store}
  0         0  
  0         0  
2046 0     0   0 sub STORESIZE {@_ = ([${$_[0]}, '_length'], $_[1]); goto &$store}
  0         0  
  0         0  
2047 2     2   938 BEGIN {*EXTEND = \&STORESIZE}
2048 0     0   0 sub EXISTS {${$_[0] }->hasOwnProperty($_[1])}
  0         0  
2049 0     0   0 sub POP {${$_[0] }->pop }
  0         0  
2050 0     0   0 sub SHIFT {${$_[0] }->shift }
  0         0  
2051 0     0   0 sub CLEAR {${$_[0] }->splice (0 )}
  0         0  
2052 0     0   0 sub PUSH {${shift;}->push (@_)}
  0         0  
2053 0     0   0 sub UNSHIFT {${shift;}->unshift(@_)}
  0         0  
2054 0     0   0 sub SPLICE {@{${shift;}->splice (@_)}}
  0         0  
  0         0  
2055 0     0   0 sub DELETE {XUL::Gui::gui "delete ID.$${$_[0]}{ID}\[$_[1]]"}
  0         0  
2056             }
2057            
2058             package
2059             XUL::Gui::Server;
2060 2     2   14 use Carp;
  2         4  
  2         232  
2061 2     2   2338 use IO::Socket;
  2         69396  
  2         10  
2062 2     2   1531 use File::Find;
  2         4  
  2         137  
2063 2     2   13 use Scalar::Util qw/openhandle/;
  2         5  
  2         9724  
2064             our ($req, $active, @cleanup);
2065            
2066 2     2   24 sub new {bless {}}
2067            
2068 0 0   0   0 sub status {print STDERR "XUL::Gui> @_\n" unless shift->{silent}; 1}
  0         0  
2069            
2070             sub start {
2071 0     0   0 my $self = shift;
2072 0         0 $$self{args} = shift;
2073 0         0 $$self{content} = $$self{args}{C};
2074 0 0       0 $$self{content} = [XUL::Gui::META()] unless @{$$self{content}};
  0         0  
2075 0         0 $weaken->($$self{args}{C});
2076 0         0 $$self{caller} = caller 1;
2077 0         0 $active = $self;
2078             $$self{$_} = $$self{args}{A}{$_}
2079 0         0 for qw(debug silent trusted launch skin chrome port delay mozilla default_browser serve_files);
2080            
2081             defined $$self{$_} or $$self{$_} = 1
2082 0   0     0 for qw(launch chrome skin);
2083            
2084 0 0 0     0 $self->status("version $VERSION") if
2085             local $DEBUG = $$self{debug} || $DEBUG;
2086            
2087 0         0 push @cleanup, $self;
2088            
2089 0 0 0     0 local $MOZILLA = defined $$self{mozilla} ? $$self{mozilla} : $MOZILLA
    0          
2090             or $DEBUG && $self->status('XUL enhancements disabled. using HTML only mode');
2091            
2092 0 0       0 $$self{silent}++ if $TESTING;
2093            
2094 0 0       0 local $| = 1 if $DEBUG;
2095            
2096 0 0       0 require Time::HiRes if $$self{delay};
2097            
2098 0   0     0 $$self{port} ||= int (10000 + rand 45000);
2099 0         0 $$self{port}++ until
2100             $$self{server} = IO::Socket::INET->new(
2101             Proto => 'tcp',
2102             PeerAddr => 'localhost',
2103             LocalAddr => "localhost:$$self{port}",
2104             Listen => 1,
2105             );
2106            
2107 0         0 $self->build_dispatch;
2108 0         0 $$self{run} = 1;
2109 0         0 $self->status("display server started on http://localhost:$$self{port}");
2110            
2111 0 0 0     0 $self->launch if $$self{launch} or $$self{trusted};
2112 0         0 $$self{client} = $$self{server}->accept;
2113 0         0 $$self{client}->autoflush(1);
2114            
2115 0         0 $self->status('opening window');
2116            
2117 0         0 local $@;
2118 0 0 0     0 my $error = eval {$self->read_until('main loop:'); 1}
  0         0  
  0         0  
2119             ? 0 : $@ || 'something bad happened'; #?
2120            
2121 0 0       0 if ($$self{firefox}) {
2122 0         0 kill HUP => -$$self{ffpid};
2123 0         0 kill HUP => $$self{ffpid};
2124 0         0 close $$self{firefox};
2125             }
2126            
2127 0   0     0 {($$self{dir} or last)->unlink_on_destroy(1)}
  0         0  
2128            
2129 0 0 0     0 die $error if $error
2130             and ref $error ne 'XUL::Gui server stopped';
2131            
2132 0         0 $self->stop('display stopped');
2133 0         0 $self->cleanup;
2134             }
2135            
2136 0     0   0 sub abort {die bless [] => 'XUL::Gui server stopped'}
2137            
2138             sub read_until {
2139 0     0   0 my ($self, $stop) = @_;
2140 0         0 my $run = \$$self{run};
2141 0         0 my $dispatch = $$self{dispatch};
2142            
2143 0         0 while (local $req = $self->read) {
2144 0         0 my $url = $$req{URL};
2145            
2146 0 0 0     0 $self->status(($stop =~ /:/ ? '' : 'read until ')."$stop got $url")
    0          
2147             if $DEBUG > 4 and $url ne '/ping';
2148            
2149 0 0       0 return $req if $url eq $stop;
2150            
2151 0 0       0 if (my $handler = $$dispatch{$url}) {
    0          
2152 0         0 $handler->();
2153             } elsif (my $prefix = $$self{serve_files}) {
2154 0 0       0 $url = ($prefix =~ m{ [\\\/] $ }x ? $prefix : '.') . $url;
2155            
2156 0 0       0 if (open my $file, '<', $url) {
2157 0         0 $self->write('text/plain', do {local $/; <$file>})
  0         0  
  0         0  
2158             } else {
2159 0         0 $self->status("file: $url not found");
2160 0         0 $self->write('text/plain', '')
2161             }
2162             }
2163 0 0       0 $$run or abort;
2164             }
2165             }
2166            
2167             sub assert {
2168 1 50   1   214 return if openhandle pop;
2169 1 50       21 my $name = ((caller 2)[3] =~ /([^:]+)$/ ? "$1 " : '') . shift;
2170 1         204 croak "XUL::Gui> $name error: client not connected,"
2171             }
2172            
2173             sub read {
2174 0     0   0 my ($self, $client) = ($_[0], $_[0]{client});
2175 0         0 my ($length, %req);
2176 0         0 local $/ = "\015\012";
2177 0         0 local *_;
2178 0         0 assert read=> $client;
2179 0         0 my $header = <$client>;
2180             $header and ($req{URL}) = $header =~ /^\s*\w+\s*(\S+)\s*HTTP/
2181 0 0 0     0 or do {
2182 0 0       0 $self->status(
2183             $header
2184             ? "broken message received: $header"
2185             : 'firefox seems to be closed'
2186             );
2187 0         0 abort
2188             };
2189            
2190 0         0 {chomp ($_ = <$client>);
  0         0  
2191 0 0 0     0 $length ||= /^\s*content-length\D+(\d+)/i ? $1 : 0;
2192 0 0       0 $_ and redo}
2193            
2194 0         0 read $client => $req{CONTENT}, $length;
2195            
2196 0 0 0     0 $self->status( "read: $req{URL} $req{CONTENT}" )
2197             if $DEBUG > 3 and $req{URL} ne '/ping';
2198 0 0 0     0 if ($$self{delay} and $req{URL} ne '/ping') {
2199 0         0 Time::HiRes::usleep(1000*$$self{delay})
2200             }
2201 0         0 \%req
2202             }
2203            
2204             sub write {
2205 1     1   3 my ($self, $type, $msg) = @_;
2206 1         7 assert write => my $client = $$self{client};
2207            
2208 0 0         XUL::Gui::flush if $msg eq 'NOOP';
2209 0 0         if ($DEBUG > 3) {
2210 0           (my $msg = "$type $msg") =~ s/[\x80-\xFF]+/ ... /g;
2211 0 0         $self->status(
2212             $DEBUG > 4
2213             ? "write $msg"
2214             : (substr "write $msg", 0, 115)
2215             . (' ...' x (length $msg > 115))
2216             )
2217             }
2218 0           print $client join "\015\012" =>
2219             'HTTP/1.1 200 OK',
2220             'Expires: -1',
2221             'Keep-Alive: 300',
2222             'Content-type: ' . $type,
2223             'Content-length: ' . length $msg,
2224             '',
2225             $msg
2226             }
2227            
2228             sub stop {
2229 0     0     my $self = shift;
2230 0           local $SIG{HUP} = 'IGNORE';
2231 0           kill HUP => -$$;
2232 0           $self->status(@_);
2233             }
2234            
2235             sub serve {
2236 0     0     my ($self, $path, $type, $data) = @_;
2237 0 0         $path =~ m[^/(?:client.js|event|ping|exit|perl)?$]
2238             and croak "reserved path: $path";
2239 0 0         $self->status("serve $path $type") if $DEBUG;
2240             $$self{dispatch}{$path} = sub {
2241 0     0     $self->write($type, $data);
2242 0           };
2243 0           $path
2244             }
2245            
2246             sub build_dispatch {
2247 0     0     my $self = shift;
2248 0           my $root;
2249 0           $$self{dispatch} = {
2250             exists $$self{dispatch} ? %{$$self{dispatch}} : (),
2251             '/' => sub {
2252 0     0     my ($meta, $html);
2253 0 0         if ($MOZILLA) {
2254 0           $meta = qq{\n} .
2255             (qq{\n} x!! $$self{skin});
2256 0           $root = $$self{content}[0]{TAG} eq 'window'
2257 0 0         ? shift @{$$self{content}}
2258             : XUL::Gui::Window()
2259             } else {
2260 0           $meta = qq{\n};
2261 0           $html = $$self{content}[0]{TAG} eq 'html:html'
2262 0 0         ? shift @{$$self{content}}
2263             : XUL::Gui::HTML();
2264            
2265 0           for (@{$$html{C}}) {
  0            
2266 0 0         if ($$_{TAG} eq 'html:body') {
2267 0           $root = $_;
2268 0           last;
2269             }
2270             }
2271 0 0         unless ($root) {
2272 0           for (0 .. $#{$$self{content}}) {
  0            
2273 0 0         if ($$self{content}[$_]{TAG} eq 'html:body') {
2274 0           $root = splice @{$$self{content}}, $_, 1;
  0            
2275 0           last;
2276             }
2277             }
2278 0   0       push @{$$html{C}}, $root ||= XUL::Gui::BODY();
  0            
2279             }
2280             }
2281 0           for (qw/onunload onclose/) {
2282 0   0       $$self{$_} ||= $$root{A}{$_};
2283 0           $$root{A}{$_} = 'return xul_gui.shutdown();';
2284             }
2285 0           unshift @{$$self{content}}, @{ $$root{C} };
  0            
  0            
2286 0           $$self{root} = $root;
2287 0           $$root{C} = [ XUL::Gui::Script(src=>"http://localhost:$$self{port}/client.js") ];
2288 0 0         $self->write(
    0          
2289             $MOZILLA ? 'application/vnd.mozilla.xul+xml'
2290             : 'text/html',
2291             $meta . (
2292             $MOZILLA ? $root->$toXML
2293             : $html->$toXML
2294             )
2295             )
2296             },
2297             '/client.js' => sub {
2298 0           $self->write( 'text/javascript',
2299             join ";\n" => $self->client_js,
2300             qq {xul_gui.root = ID.$$root{ID} = document.getElementById('$$root{ID}')},
2301 0     0     (map {$_->$toJS("ID.$$root{ID}")} @{$$self{content}}),
  0            
2302             'xul_gui.start()'
2303             );
2304 0           push @{$$root{C}}, splice @{$$self{content}};
  0            
  0            
2305             },
2306             '/event' => sub {
2307 0 0   0     $self->status("event $$req{CONTENT}") if $DEBUG > 1;
2308 0           my ($code, $id, $evt, $obj) = split ' ', $$req{CONTENT};
2309 0           for ($ID{$id}) {
2310 0           my $handler = $$_{"ON\U$evt"};
2311 0 0         if (ref $handler eq 'CODE') {
  0            
2312 0           $handler->( $_, XUL::Gui::object(undef, id=>$obj) );
2313             } else {$self->status("no event handler found: $$req{CONTENT}")}
2314             }
2315 0           $self->write('text/plain', 'NOOP');
2316             },
2317             '/perl' => sub {
2318 0 0   0     $self->status("perl $$req{CONTENT}") if $DEBUG > 1;
2319 0           local $@;
2320 0           my $return;
2321 0 0         eval "no strict; package $$self{caller}; \$return = do {$$req{CONTENT}}; 1"
2322             or warn "perl( $$req{CONTENT} ) error: $@\n";
2323 0   0       $self->write( 'text/plain', "RETURN " . ($return || ''));
2324             },
2325             '/ping' => sub {
2326 0 0   0     if (my @delay = splice @{$$self{queue}}) {
  0            
2327 0 0         $self->status('/ping clearing delay queue') if $DEBUG > 1;
2328 0           $_->() for @delay;
2329 0           XUL::Gui::flush;
2330             }
2331 0           local $DEBUG = 0;
2332 0           $self->write('text/plain', 'NOOP');
2333             },
2334             '/favicon.ico' => sub {
2335 0     0     $self->write('text/plain', '');
2336             },
2337             '/close' => sub {
2338 0     0     my $shutdown = 1;
2339 0           for (grep defined, @$self{qw/onclose onunload/}) {
2340 0 0         $shutdown = ref eq 'CODE' ? $_->() : XUL::Gui::gui $_;
2341             }
2342 0 0         $self->write('text/plain', 'RETURN ' . ($shutdown ? 'true' : 'false'));
2343 0 0         $$self{run} = ! $shutdown if $$self{run};
2344             }
2345             }
2346 0 0         }
2347            
2348             {my @firefox;
2349             sub launch {
2350 0     0     my $self = shift;
2351            
2352 0 0 0       if ($$self{default_browser} or not $MOZILLA) {
2353 0 0         my $cmd = ($^O =~ /MSWin/ ? 'start' :
    0          
2354             $^O =~ /darwin/ ? 'open' : 'xdg-open')
2355             . qq{ http://localhost:$$self{port}};
2356            
2357 0 0         $self->status('launching default browser' . ($DEBUG ? ": $cmd" : ''));
2358 0 0         system $cmd and die $!;
2359             return
2360 0           }
2361 0 0         unless (@firefox) {
2362 0 0 0 0     find sub {push @firefox, [length, $File::Find::name]
2363 0           if /^(:?firefox|iceweasel|xulrunner.*)(?:-bin|\.exe)?$/i and -f} => $_
2364 0 0         for grep {/mozilla|firefox|iceweasel|xulrunner/i }
  0 0          
2365             map {
2366 0 0         if (opendir my $dir => my $path = $_)
2367             {map "$path/$_" => readdir $dir} else {}
2368             }
2369             $^O =~ /MSWin/ ? @ENV{qw/ProgramFiles ProgramFiles(x86)/} :
2370             $^O =~ /darwin/ ? '/Applications' :
2371             split /[:;]/ => $ENV{PATH};
2372 0           @firefox = sort {$$a[0] < $$b[0]} @firefox
  0            
2373             }
2374 0 0         if (@firefox) {
  0            
2375 0           my $app;
2376 0           for ($$self{trusted}) {
2377 0 0 0       defined and !$_ or $_ =
2378             `"$firefox[0][1]" -v 2>&1` =~
2379             / (?: firefox | iceweasel ) \s+ [34]
2380             | xulrunner \s+ (?: 1\.[5-9] | 2\.[0-3] )
2381             /ix
2382             }
2383 0 0         if ($$self{trusted}) {
2384 0           local $@;
2385             eval {
2386 0           require File::Spec;
2387 0           require File::Temp;
2388 0           $$self{dir} = File::Temp->newdir('xulgui_XXXXXX', TMPDIR => 1);
2389            
2390 0           $$self{dir}->unlink_on_destroy(0); # for threads
2391 0           my $dirname = $$self{dir}->dirname;
2392 0           my $base = (File::Spec->splitdir($dirname))[-1];
2393            
2394 0           my ($file, $dir) = map {my $method = $_;
  0            
2395 0     0     sub {File::Spec->$method( $dirname, split /\s+/ => "@_" )}
2396 0           } qw( catfile catdir );
2397            
2398             mkdir $dir->($_) or die $!
2399 0   0       for qw(chrome defaults), "chrome $base", 'defaults preferences';
2400            
2401 0 0         open my $manifest, '>', $file->('chrome chrome.manifest') or die $!;
2402 0           print $manifest "content $base file:$base/";
2403            
2404 0 0         open my $boot, '>', $file->('chrome', $base, 'boot.xul') or die $!; {
2405 2     2   47 no warnings 'redefine';
  2         4  
  2         1988  
  0            
2406             local *write = sub {
2407 0     0     my $self = shift;
2408 0           my $code = pop;
2409 0 0         $self->status("write \n\t". join "\n\t", split /\n/, $code) if $DEBUG > 3;
2410 0           $code
2411 0           };
2412 0           print $boot $$self{dispatch}{'/'}();
2413             }
2414            
2415 0 0         open my $prefs, '>', $file->('defaults preferences prefs.js') or die $!;
2416 0           print $prefs qq {pref("toolkit.defaultChromeURI", "chrome://$base/content/boot.xul");};
2417            
2418 0 0         open my $ini, '>', $app = $file->('application.ini') or die $!;
2419 0           print $ini split /[\t ]+/ => qq {
2420             [App]
2421             Name=$base
2422             Version=$XUL::Gui::VERSION
2423             BuildID=$base
2424            
2425             [Gecko]
2426             MinVersion=1.6
2427             MaxVersion=2.3
2428             };
2429 0 0         $self->status("trusted: $app") if $DEBUG > 2;
2430 0           1
2431 0 0         } or do {
2432 0   0       chomp (my $err = ($@ or $!));
2433 0           $self->status("trusted mode failed: $err");
2434 0           $$self{trusted} = 0;
2435 0           undef $app;
2436             }
2437             } else {
2438 0           while ($firefox[0][1] =~ /xulrunner[^\/\\]$/i) {
2439 0           shift @firefox;
2440 0 0         unless (@firefox) {
2441 0           status {}, 'firefox not found: xulrunner was found but trusted mode is disabled';
2442             return
2443 0           }
2444             }
2445             }
2446            
2447 0           my $firefox = $firefox[0][1];
2448 0 0         $firefox =~ tr./.\\. if $^O =~ /MSWin/;
2449 0 0         my $cmd = qq{"$firefox" }
    0          
2450             . ($app
2451             ? "-app $app"
2452             : ($$self{chrome} ? '-chrome ' : '')
2453             . qq{"http://localhost:$$self{port}"}
2454             ) . (q{ 1>&2 2>/dev/null} x ($^O !~ /MSWin/));
2455 0 0         if ($$self{launch}) {
2456 0 0         $self->status('launching firefox' . ($DEBUG ? ": $cmd" : ''));
2457            
2458 0 0 0       if (not $$self{trusted} and $^O =~ /darwin/) {
2459 0           system qq[osascript -e 'tell application "Firefox" to OpenURL "http://localhost:$$self{port}"']
2460             } else {
2461 0           $$self{ffpid} = open $$self{firefox} => "$cmd |";
2462             }
2463             } else {
2464 0           status {}, "launch gui with:\n\t$cmd"
2465             }
2466             }
2467             else {status {}, 'firefox not found: start manually'}
2468             }}
2469            
2470             sub CLONE {
2471 0     0     local $@;
2472 0           eval {$$active{client}->close};
  0            
2473 0           eval {$$active{server}->close};
  0            
2474             }
2475 2     2   776 BEGIN {*cleanup = \&CLONE}
2476             END {
2477 2     2   11273 local $@;
2478 2         10 for (@cleanup) {
2479 0         0 eval {$_->cleanup};
  0         0  
2480 0         0 eval {
2481 0         0 $$_{dir}->unlink_on_destroy(1);
2482 0         0 $$_{dir}->DESTROY;
2483             };
2484             }
2485 2         5 eval {File::Temp::cleanup()};
  2         816  
2486             }
2487            
2488             sub client_js {
2489 0     0     my $self = shift;
2490             XUL::Gui::apply {
2491 0     0     s//$$self{port}/g;
2492 0 0         unless ($MOZILLA) {
2493 0           s/\bconst\b/var/g;
2494 0           s/^/if (!window.Element) window.Element = function(){};/;
2495             }
2496 0           } <<'' }
2497            
2498             const xul_gui = (function () {
2499             var $jsid = 0;
2500             var $ID = {};
2501             var $noEvents = {};
2502             var $cacheEvents = true;
2503             var $ping = 50;
2504             var $host = 'http://localhost:/';
2505             var $port = ;
2506             var $queue = [];
2507             var $mutex = false;
2508             var $delayqueue = [];
2509             var $server = new XMLHttpRequest();
2510             var $lives = 5;
2511             var $interval;
2512             var $deadman;
2513             function deadman () {
2514             if (--$lives <= 0) quit();
2515             $deadman = setTimeout(deadman, 50);
2516             }
2517             function deadman_pause () {clearTimeout($deadman)}
2518             function deadman_resume () {$lives++; deadman()}
2519            
2520             function pinger () {
2521             if ($mutex || !$cacheEvents) return;
2522             while ($delayqueue.length > 0)
2523             $delayqueue.shift().call();
2524             EVT( null, null );
2525             }
2526            
2527             function start () {
2528             $interval = setInterval( pinger, $ping );
2529             deadman();
2530             }
2531            
2532             function shutdown () {return send('close','')}
2533            
2534             function send ($to, $val) {
2535             var $url = $host + $to;
2536             var $resurl = $host + 'res';
2537             var $type;
2538             var $realval;
2539             while (1) {
2540             deadman_pause();
2541             $server.open( 'POST', $url, false );
2542             $server.send( $val );
2543             $lives = 5;
2544             deadman_resume();
2545             $val = $server.responseText;
2546            
2547             if ($val == 'NOOP') return $realval;
2548             if ($val.substr(0, 7) == 'RETURN ') return eval( $val.substr(7) );
2549            
2550             try {$realval = eval( $val )}
2551             catch ($err) {
2552             if ($err == 'quit') return $server = null;
2553             alert (
2554             typeof $err == 'object'
2555             ? [$err.name, $val, $err.message].join("\n\n")
2556             : $err
2557             );
2558             $realval = null;
2559             }
2560             $url = $resurl;
2561             $val = $realval;
2562             $type = typeof $val;
2563             if ($val === true ) $val = 'RES 1'
2564             else if ($val === false || $val === 0 ) $val = 'RES 0'
2565             else if ($val === null || $val === undefined) $val = 'UND EF'
2566             else if ($type == 'object')
2567             if ($val.hasAttribute && $val.hasAttribute('id'))
2568             $val = 'OBJ ' + $val.getAttribute('id')
2569             else
2570             xul_gui.ID[ 'xul_js_' + $jsid ] = $val,
2571             $val = 'OBJ xul_js_' + $jsid++
2572             else $val = 'RES ' + $val
2573             }
2574             }
2575            
2576             function EVT ($event, $id) {
2577             if ($noEvents.__count__ > 0
2578             && $id in $noEvents) return;
2579             if ($mutex) {
2580             if($cacheEvents && $event)
2581             $queue.push([$event, $id]);
2582             return
2583             }
2584             $mutex = true;
2585             var $ret;
2586             var $evt;
2587             do {
2588             if ($evt) {
2589             $event = $evt[0];
2590             $id = $evt[1];
2591             }
2592             if ($event) {
2593             if ($event.type == 'perl') {
2594             $ret = send('perl', $event.code);
2595             break;
2596             } else {
2597             $ID['xul_js_' + $jsid] = $event;
2598             send('event', 'EVT ' + $id +
2599             ' ' + $event.type + ' ' + ('xul_js_' + $jsid++));
2600             }
2601             } else {
2602             send('ping', null)
2603             }
2604             } while ($evt = $queue.shift());
2605             $mutex = false;
2606             if ($event) setTimeout(pinger, 10);
2607             return $ret;
2608             }
2609            
2610             function GET ($self, $k) {
2611             if (typeof $self.hasAttribute == 'function' && $self.hasAttribute($k))
2612             return $self.getAttribute($k);
2613            
2614             if (typeof $self[$k] == 'function')
2615             return $self[$k]();
2616            
2617             return $self[$k];
2618             }
2619            
2620             function SET ($self, $k, $v) {
2621             if (typeof $self.hasAttribute == 'function'
2622             && $self.hasAttribute($k) ) {
2623             $self.setAttribute($k, $v);
2624             return $v;
2625             }
2626             return $self[$k] = $v;
2627             }
2628            
2629             function quit () {
2630             clearInterval($interval);
2631             EVT = function(){};
2632             try {
2633             var $appStartup = Components.classes[
2634             '@mozilla.org/toolkit/app-startup;1'
2635             ].getService(Components.interfaces.nsIAppStartup);
2636             $appStartup.quit(Components.interfaces.nsIAppStartup.eForceQuit);
2637             } catch ($e) {}
2638             try {
2639             window.close();
2640             } catch ($e) {}
2641             throw 'quit';
2642             }
2643            
2644             function pevt ($code) {
2645             EVT({ type: 'perl', code: $code }, null)
2646             }
2647            
2648             function perl ($code) {
2649             return ($mutex ? send('perl', $code) : pevt($code))
2650             }
2651            
2652             function delay ($code) {
2653             $delayqueue.push(
2654             typeof $code == 'function'
2655             ? $code
2656             : function(){eval($code)}
2657             )
2658             }
2659            
2660             Element.prototype.noEvents = function ($value) {
2661             return $value
2662             ? $noEvents[this] = true
2663             : delete $noEvents[this]
2664             };
2665            
2666             return {
2667             ID: $ID,
2668             noEvents: $noEvents,
2669             start: start,
2670             shutdown: shutdown,
2671             send: send,
2672             EVT: EVT,
2673             GET: GET,
2674             SET: SET,
2675             quit: quit,
2676             pevt: pevt,
2677             perl: perl,
2678             delay: delay,
2679             cacheEvents: function ($val) {$cacheEvents = $val},
2680             deadman_pause: deadman_pause,
2681             deadman_resume: deadman_resume
2682             }
2683             })();
2684            
2685             for (var $name in xul_gui)
2686             window[$name] = xul_gui[$name];
2687            
2688             const ID = xul_gui.ID;
2689            
2690             (function ($proto) {
2691             for (var $name in $proto)
2692             Element.prototype[$name] = $proto[$name]
2693             })({
2694             removeChildren: function () {
2695             while (this.firstChild)
2696             this.removeChild( this.firstChild )
2697             },
2698             removeItems: function () {
2699             while (this.lastChild
2700             && this.lastChild.nodeName == 'listitem')
2701             this.removeChild( this.lastChild )
2702             },
2703             computed: function ($style) {
2704             return document.defaultView
2705             .getComputedStyle( this, null )
2706             .getPropertyValue( $style )
2707             },
2708             scrollTo: function ($x, $y) {
2709             try {
2710             this.boxObject
2711             .QueryInterface( Components.interfaces.nsIScrollBoxObject )
2712             .scrollTo($x, $y)
2713             } catch ($e)
2714             { alert('error: ' + this.tagName + ' does not scroll') }
2715             }
2716             });
2717            
2718            
2719            
2720            
2721             package
2722             XUL::Gui;
2723 2     2   15 no warnings 'once';
  2         5  
  2         1095  
2724            
2725             =head2 widgets
2726            
2727             =over 4
2728            
2729             =item ComboBox
2730            
2731             create dropdown list boxes
2732            
2733             items => [
2734             ['displayed label' => 'value'],
2735             'label is same as value'
2736             ...
2737             ]
2738             default => 'item selected if this matches its value'
2739            
2740             also takes: label, oncommand, editable, flex
2741             styles: liststyle, popupstyle, itemstyle
2742             getter: value
2743            
2744             =cut
2745            
2746             *ComboBox = widget {
2747             my $sel = $_->has('default') || '';
2748             my $in = grep /^$sel/ =>
2749             map {ref $_ ? $$_[1] : $_}
2750             @{ $_->has('items!') };
2751            
2752             my $menu = MenuList(
2753             id => 'list',
2754             $_ -> has('oncommand editable flex liststyle->style'),
2755             MenuPopup(
2756             id => 'popup',
2757             $_ -> has('popupstyle->style'),
2758             map {MenuItem(
2759             $_{W}->has('itemstyle->style'),
2760             zip [qw/label tooltiptext value selected/] =>
2761             apply {$$_[3] = ($sel and $$_[2] =~ /^$sel/) ? 'true' : 'false'}
2762             ref $_ eq 'ARRAY'
2763             ? [@$_[0, 0, 1]]
2764             : [($_) x 3]
2765             )} ($_{A}{editable} && $sel && !$in ? $sel : ()),
2766             @{ $_->has('items!') }
2767             )
2768             );
2769             $_->has('label')
2770             ? Hbox( align => 'center', Label( $_->has('label->value') ), $menu )
2771             : $menu
2772             }
2773             value => sub {
2774             my $self = shift;
2775             my $item = $$self{list}->selectedItem;
2776            
2777             $item ? $item->value
2778             : $$self{list}->inputField->_value
2779             };
2780            
2781             =back
2782            
2783             =head1 CAVEATS
2784            
2785             too many changes to count. if anything is broken, please send in a bug report.
2786            
2787             some options for display have been reworked from 0.36 to remove double negatives
2788            
2789             widgets have changed quite a bit from version 0.36. they are the same under the
2790             covers, but the external interface is cleaner. for the most part, the following
2791             substitutions are all you need:
2792            
2793             $W --> $_ or $_{W}
2794             $A{...} --> $_{A}{...} or $_->attr(...)
2795             $C[...] --> $_{C}[...] or $_->child(...)
2796             $M{...} --> $_{M}{...} or $_->can(...)
2797            
2798             attribute 'label onclick' --> $_->has('label onclick')
2799             widget {extends ...} --> widget {$_->extends(...)}
2800            
2801             export tags were changed a little bit from 0.36
2802            
2803             thread safety should be better than in 0.36
2804            
2805             currently it is not possible to open more than one window, hopefully this will
2806             be fixed soon
2807            
2808             the code that attempts to find firefox may not work in all cases, patches
2809             welcome
2810            
2811             for the TextBox object, the behaviors of the "value" and "_value" methods are
2812             reversed. it works better that way and is more consistent with the behavior of
2813             other tags.
2814            
2815             =head1 AUTHOR
2816            
2817             Eric Strom, C<< >>
2818            
2819             =head1 BUGS
2820            
2821             please report any bugs or feature requests to C< bug-xul-gui at rt.cpan.org >,
2822             or through the web interface at
2823             L. I will be notified,
2824             and then you'll automatically be notified of progress on your bug as I make
2825             changes.
2826            
2827             =head1 ACKNOWLEDGMENTS
2828            
2829             the mozilla development team
2830            
2831             =head1 COPYRIGHT & LICENSE
2832            
2833             copyright 2009-2010 Eric Strom.
2834            
2835             this program is free software; you can redistribute it and/or modify it under
2836             the terms of either: the GNU General Public License as published by the Free
2837             Software Foundation; or the Artistic License.
2838            
2839             see http://dev.perl.org/licenses/ for more information.
2840            
2841             =cut
2842            
2843            
2844             {package
2845             XUL::Gui::Hash;
2846 2     2   12 use Scalar::Util qw/blessed weaken isweak/;
  2         5  
  2         3013  
2847             sub new {
2848 0 0   0     if (not defined wantarray) {
2849 0           my %base = %{$_[1]};
  0            
2850 0           tie %{$_[1]} => $_[0], \%base, @_[2 .. $#_];
  0            
2851 0           return;
2852             }
2853 0           my ($class, $self) = splice @_, 0, 2;
2854 0           tie my %hash => $class, $self, @_;
2855 0 0         blessed ($self)
2856             ? bless \%hash => ref $self
2857             : \%hash
2858             }
2859             sub unshift {
2860 0     0     my $self = shift;
2861 0           unshift @{$$self{isa}}, @_;
  0            
2862            
2863 0   0       isweak $_ or weaken $_ for @{$$self{isa}};
  0            
2864             }
2865 0     0     sub hasOwn {exists $_[0]{hash}{$_[1]}}
2866 0     0     sub hash :lvalue {$_[0]{hash}}
2867             sub TIEHASH {
2868 0     0     my $class = shift;
2869 0   0       bless my $self = {
2870             hash => shift || {},
2871             isa => [ @_ ]
2872             } => $class;
2873            
2874 0           weaken $_ for @{$$self{isa}};
  0            
2875 0           $self
2876             }
2877             sub FETCH {
2878 0     0     my ($self, $key) = @_;
2879            
2880 0 0         if (exists $$self{hash}{$key}) {
2881 0           return $$self{hash}{$key}
2882             }
2883 0 0         return if $key eq uc $key;
2884            
2885 0           for (@{$$self{isa}}) {
  0            
2886 0 0 0       return $$_{$key} if $_ and %$_ and exists $$_{$key}
      0        
2887             }
2888             return
2889 0           }
2890 0     0     sub STORE {$_[0]{hash}{$_[1]} = $_[2]}
2891 0     0     sub DELETE {delete $_[0]{hash}{$_[1]}}
2892 0     0     sub CLEAR {$_[0]{hash} = {}}
2893             sub EXISTS {
2894 0     0     my ($self, $key) = @_;
2895 0 0         return 1 if exists $$self{hash}{$key};
2896 0 0         return if $key eq uc $key;
2897 0           for (@{$$self{isa}}) {
  0            
2898 0 0 0       return 1 if $_ and %$_ and exists $$_{$key}
      0        
2899             }
2900             return
2901 0           }
2902             sub FIRSTKEY {
2903 0     0     my ($self) = @_;
2904 0           my @each = ($$self{hash}, @{$$self{isa}});
  0            
2905 0           keys %$_ for @each;
2906 0           my %seen;
2907 0           my $count = @each;
2908            
2909             goto &{
2910 0           $$self{nextkey} = sub {
2911 0     0     my $want = wantarray;
2912 0           while (@each) {
2913 0 0         if ($want) {
2914 0 0         if (my ($k, $v) = each %{$each[0]}) {
  0            
2915 0 0         redo if $seen{$k}++;
2916 0 0 0       redo if $k eq uc $k and $count != @each;
2917 0           return $k, $v
2918             }
2919             } else {
2920 0 0         if (defined (my $k = each %{$each[0]})) {
  0            
2921 0 0         redo if $seen{$k}++;
2922 0 0 0       redo if $k eq uc $k and $count != @each;
2923 0           return $k;
2924             }
2925             }
2926             shift @each
2927 0           }
2928             return
2929 0           }
2930 0           }
2931             }
2932 0     0     sub NEXTKEY {$_[0]{nextkey}()}
2933            
2934             sub SCALAR {
2935 0     0     my $self = shift;
2936 0           for ($$self{hash}, @{$$self{isa}}) {
  0            
2937 0   0       return scalar (%$_) || next
2938             }
2939             return
2940 0           }
2941             sub UNTIE {
2942 0     0     my $self = shift;
2943 0           delete $$self{$_} for keys %$self;
2944             }
2945             }
2946            
2947            
2948             __PACKAGE__ if 'first require'