added funcs for version handling
[clawsker.git] / clawsker
1 #!/usr/bin/perl -w
2 #
3 # $Id$
4 #
5
6 =pod
7
8 =head1 NAME
9
10 Clawsker - A Claws Mail Tweaker
11
12 =head1 SYNOPSIS
13
14 clawsker [options]
15
16 =head1 DESCRIPTION
17
18 Clawsker is an applet to edit the so called Claws Mail hidden preferences.
19
20 Claws Mail is a fast, lightweight and feature-rich MUA with a high number 
21 of configurable options. To keep the binary small and fast some of these 
22 preferences which are not widely used are not provided with a graphical
23 interface for inspection and/or modification.
24
25 Users wanting to edit such preferences had to face raw editing of their 
26 configuration files, now you can do it with a convenient GTK2 interface
27 using Clawsker.
28
29 =head1 OPTIONS
30
31 --help
32         Shows a brief help screen.
33
34 --verbose
35         Tells more on the standard output.
36
37 --alternate-config-dir <dir>
38         Uses <dir> as Claws Mail configuration dir.
39
40 --clawsrc <file>
41         Uses <file> as Claws Mail resource configuration file. This sets
42         the full file name overriding any previous setting.
43
44 Multiple options are allowed, although only the last one has effect. Weird
45 option specifications may produce weird results (but otherwise correct).
46         
47 =head1 LIMITATIONS
48
49 A running Claws Mail cannot be detected if using the --clawsrc option because
50 the directory is not assumed to be a Claws Mail configuration dir. If that is 
51 the case use the --alternate-config-dir option instead.
52
53 =head1 AUTHOR
54
55 Ricardo Mones E<lt>ricardo@mones.orgE<gt>
56
57 =head1 LICENSE
58
59 Copyright (c) 2007-2008 by Ricardo Mones Lastra
60
61 This program is free software: you can redistribute it and/or modify
62 it under the terms of the GNU General Public License as published by
63 the Free Software Foundation, either version 3 of the License, or
64 (at your option) any later version.
65
66 This program is distributed in the hope that it will be useful,
67 but WITHOUT ANY WARRANTY; without even the implied warranty of
68 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
69 GNU General Public License for more details.
70
71 You should have received a copy of the GNU General Public License
72 along with this program.  If not, see E<lt>http://www.gnu.org/licenses/E<gt>.
73
74 =cut
75
76 use strict;
77 use encoding 'utf8';
78 use Glib qw(TRUE FALSE);
79 use Gtk2 -init;
80 use POSIX qw(setlocale);
81 use Locale::gettext;
82
83 my $NAME = 'clawsker';
84 my $PREFIX = '@PREFIX@';
85 my $LIBDIR = '@LIBDIR@';
86 my $VERSION = '@VERSION@';
87 my $VERBOSE = FALSE;
88 my $main_window = undef;
89
90 my $locale = (defined($ENV{LC_MESSAGES}) ? $ENV{LC_MESSAGES} : $ENV{LANG});
91 $locale = "C" unless defined($locale);
92 setlocale (LC_ALL, $locale);
93 bindtextdomain ($NAME, sprintf ('%s/share/locale', $PREFIX));
94 textdomain ($NAME);
95
96 my $SHOWHINTS = FALSE;
97 $SHOWHINTS = TRUE if ($Gtk2::VERSION >= 1.040 and Gtk2->CHECK_VERSION (2, 12, 0));
98
99 sub _ {
100     my $str = shift;
101     my %par = @_;
102     my $xla = gettext ($str);
103     if (scalar(keys(%par)) > 0) {
104         foreach my $key (keys %par) {
105             $xla =~ s/\{$key\}/$par{$key}/g;
106         }
107     }
108     return $xla;
109 }
110
111 # default messages
112 %xl::s = (
113     win_title => _('Claws Mail Hidden Preferences'),
114     about => _('About...'),
115     about_title => _('Clawsker :: A Claws Mail Tweaker'),
116     about_license => _('License:'),
117     about_version => _('Version:'),
118
119     tab_colours => _('Colours'),
120     tab_behaviour => _('Behaviour'),
121     tab_gui => _('GUI'),
122     tab_other => _('Other'),
123
124     ab_frame => _('Addressbook'),
125     mem_frame => _('Memory'),
126     msgview_frame => _('Message view'),
127     log_frame => _('Log window'),
128     dnd_frame => _('Drag \'n\' drop'),
129     ssl_frame => _('Secure Sockets Layer'),
130     msgs_frame => _('Messages'),
131     stripes_frame => _('Coloured stripes'),
132     sbar_frame => _('Scroll bars'),
133     mlist_frame => _('Message List'),
134     netm_frame => _('NetworkManager'),
135
136     l_oth_use_dlg => _('Use detached address book edit dialogue'),
137     h_oth_use_dlg => _('If true use a separate dialogue to edit a person\'s details. Otherwise will use a form embedded in the address book\'s main window.'),
138     l_oth_max_use => _('Maximum memory for message cache (kB)'),
139     h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kilobytes.'),
140     l_oth_min_time => _('Minimun time for cache elements (minutes)'),
141     h_oth_min_time => _('The minimum time in minutes to keep a cache in memory. Caches more recent than this time will not be freed, even if the memory usage is too high.'),
142     l_oth_use_netm => _('Use NetworkManager'),
143     h_oth_use_netm => _('Use NetworkManager to switch offline automatically.'),
144     
145     l_gui_b_unread => _('Show unread messages with bold font'),
146     h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'),
147     l_gui_no_markup => _('Don\'t use markup in compose window'),
148     h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'),
149     l_gui_dot_lines => _('Use dotted lines in tree view components'),
150     h_gui_dot_lines => _('Use the old dotted line look in the main window tree views (Folder, Message and other lists) instead of the modern lineless look.'),
151     l_gui_h_scroll => _('Enable horizontal scrollbar'),
152     h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'),
153     l_gui_swp_from => _('Dislay To column instead From column in Sent folder'),
154     h_gui_swp_from => _('Display the recipient\'s email address in a To column of the Sent folder instead of the originator\'s one in a From column.'),
155     l_gui_v_scroll => _('Folder List scrollbar behaviour'),
156     h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'),
157     l_gui_v_scroll_show => _('Show always'),
158     l_gui_v_scroll_auto => _('Automatic'),
159     l_gui_v_scroll_hide => _('Hide always'),
160     l_gui_strip_off => _('Coloured lines contrast'),
161     h_gui_strip_off => _('Specify the value to use when creating alternately coloured lines in tree view components. The smaller the value, the less visible the difference in the alternating colours of the lines.'),
162     l_gui_cursor_v => _('Show Cursor in message view'),
163     h_gui_cursor_v => _('Display the cursor in the message view.'),
164     l_gui_toolbar_d => _('Detachable toolbars'),
165     h_gui_toolbar_d => _('Show handles in the toolbars.'),
166     l_gui_strip_all => _('Use stripes in all tree view components'),
167     h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'),
168     l_gui_strip_sum => _('Use stripes in Folder List and Message List'),
169     h_gui_strip_sum => _('Enable alternately coloured lines in Message list and Folder list.'),
170     l_gui_two_line_v => _('2 lines per Message List item in 3-column layout'),
171     h_gui_two_line_v => _('Spread Message List information over two lines when using the three column mode.'),
172
173     l_beh_hover_t => _('Drag \'n\' drop hover timeout (ms)'),
174     h_beh_hover_t => _('Time in milliseconds that will cause a folder tree to expand when the mouse cursor is held over it during drag and drop.'),
175     l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
176     h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
177     l_beh_flowed => _('Respect format=flowed in messages'),
178     h_beh_flowed => _('Respect format=flowed on text/plain message parts. This will cause some mails to have long lines, but will fix some URLs that would otherwise be wrapped.'),
179     l_beh_parts_rw => _('Allow writable temporary files'),
180     h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
181     l_beh_skip_ssl => _('Don\'t check SSL certificates'),
182     h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
183     l_beh_up_step => _('Progress bar update step (items)'),
184     h_beh_up_step => _('Update stepping in progress bars.'),
185     l_beh_thread_a => _('Maximum age when threading by subject (days)'),
186     h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
187     l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
188     h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
189     l_beh_use_utf8 => _('Force UTF-8 for broken mails'),
190     h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'),
191     l_beh_warn_dnd => _('Warn on drag \'n\' drop'),
192     h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'),
193     l_beh_out_ascii => _('Ougoing messages fallback to ASCII'),
194     h_beh_out_ascii => _('If content allows, ASCII will be used to encode outgoing messages, otherwise the user-defined encoding is enforced always.'),
195
196     l_col_emphasis => _('X-Mailer header'),
197     h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'),
198     l_col_log_err => _('Error messages'),
199     h_col_log_err => _('Colour for error messages in log window.'),
200     l_col_log_in => _('Server messages'),
201     h_col_log_in => _('Colour for messages received from servers in log window.'),
202     l_col_log_msg => _('Standard messages'),
203     h_col_log_msg => _('Colour for messages in log window.'),
204     l_col_log_out => _('Client messages'),
205     h_col_log_out => _('Colour for messages sent to servers in log window.'),
206     l_col_log_warn => _('Warnings'),
207     h_col_log_warn => _('Colour for warning messages in log window.'),
208
209     e_error => _('Error: '),
210     e_noclawsrc => _('resource file for Claws Mail was not found.'),
211     e_running => _('seems Claws Mail is currently running, close it first.'),
212     e_requireddir => _('option requires a directory name.'),
213     e_requiredfile => _('option requires a file name.'),
214     e_notadir => _('specified name is not a directory or does not exist.'),
215     e_notafile => _('specified name is not a file or does not exist.'),
216 );
217
218 # all preferences read by load_preferences
219 my %PREFS = ();
220 # values of all preferences handled by clawsker
221 my %HPVALUE = ();
222 # default config dir and file name
223 my $ALTCONFIGDIR = FALSE;
224 my $CONFIGDIR = $ENV{HOME} . '/.claws-mail/';
225 my $CONFIGRC = 'clawsrc';
226
227 # index constants for preference arrays
228 use constant NAME  => 0; # the name on the rc file
229 use constant LABEL => 1; # the label on the GUI
230 use constant DESC  => 2; # the description for the hint/help
231 use constant TYPE  => 3; # data type: bool, int, float, string, color
232 use constant CMVER => 4; # lowest Claws Mail version the feature exists
233 use constant CMDEF => 5; # default value for the preference in Claws Mail
234 use constant GUI   => 6; # GUI element
235
236 # constants for GUI spacing
237 use constant HBOX_SPC => 5;
238 use constant FRAME_SPC => 2;
239 use constant PAGE_SPC => 5;
240
241 # version functions
242
243 sub version_greater() {
244   my ($version, $refvers) = @_;
245   my @version = split (/\./, $version);
246   my @refvers = split (/\./, $refvers);
247   while ($#version < $#refvers) {
248     push (@version, '0');
249   }
250   my $idx = 0;
251   while (($idx <= $#refvers) 
252          and (int ($version[$idx]) == int ($refvers[$idx]))) {
253     ++$idx;
254   }
255   return TRUE if (($idx > $#refvers) 
256                or (int ($version[$idx]) > int ($refvers[$idx])));
257   return FALSE;
258 }
259
260 sub get_claws_version() {
261     my @cmbin = (
262         'claws-mail',
263     );
264     my $res = "";
265     foreach my $bin (@cmbin) {
266         $_ = qx/which $bin/;
267         chomp;
268         last if ($_ ne "");
269     }
270     return "" unless ($_); # not installed
271     $_ = qx/$_ -v/;
272     chomp;
273     my @fver = split (/ /);
274     die "Invalid version string" unless ($fver[2] eq "version");
275     my @ver = split (/\./, $fver[3]);
276     $res .= "$ver[0].";
277     $res .= "$ver[1].";
278     if ($ver[2] =~ /(\d+)cvs(\d+)/) {
279         $res .= "$1.$2";
280     }
281     else {
282         $res .= "$ver[2].0";
283     }
284     return $res;
285 }
286
287 # data handlers and auxiliar functions
288
289 sub handle_bool_value {
290     my ($widget, $event, $dataref) = @_;
291     $$dataref = ($widget->get_active ())? '1': '0';
292 }
293
294 sub handle_int_value {
295     my ($widget, $event, $dataref) = @_;
296     $_ = $widget->get_text ();
297     s/^\s+//;
298     s/\s+$//;
299     if (/^[0-9]+$/) {
300         $$dataref = $_;
301         $widget->set_text ($_);
302     }
303     else {
304         $widget->set_text ($$dataref);
305     }
306 }
307
308 sub handle_string_value {
309     my ($widget, $event, $dataref) = @_;
310     $$dataref = $widget->get_text ();
311 }
312
313 sub gdk_color_from_str {
314     my ($str) = @_;
315     my ($rr, $gg, $bb) = (0, 0 ,0);
316     $_ = uc ($str);
317     if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) {
318         $rr = hex($1) * 256;
319         $gg = hex($2) * 256; 
320         $bb = hex($3) * 256;
321     }
322     my $color = Gtk2::Gdk::Color->new ($rr, $gg, $bb);
323     return $color;
324 }
325
326 sub str_from_gdk_color {
327     my ($color) = @_;
328     my $rr = $color->red / 256;
329     my $gg = $color->green / 256;
330     my $bb = $color->blue / 256;
331     my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb);
332     return $str;
333 }
334
335 sub handle_color_value {
336     my ($widget, $event, $dataref) = @_;
337     my $newcol = $widget->get_color;
338     $$dataref = &str_from_gdk_color ($newcol);
339 }
340
341 sub handle_selection_value {
342     my ($widget, $event, $dataref) = @_;
343     $$dataref = $widget->get_active;
344 }
345
346 sub get_rc_filename {
347     return $CONFIGDIR . $CONFIGRC;
348 }
349
350 sub set_rc_filename {
351     my ($fullname) = @_;
352     my @parts = split ('/', $fullname);
353     $CONFIGRC = $parts[$#parts];
354     $parts[$#parts] = '';
355     $CONFIGDIR = join ('/', @parts);
356 }
357
358 sub log_message {
359     my ($mesg, $fatal) = @_;
360     if (defined($fatal) && $fatal eq 'die') {
361         die "$NAME: $mesg\n";
362     }
363     if ($VERBOSE) {
364         print "$NAME: $mesg\n";
365     }
366 }
367
368 sub error_dialog {
369     my ($emsg) = @_;
370     my $markup = "<span weight=\"bold\" size=\"large\">" . $emsg . "</span>";
371     my $errordlg = Gtk2::MessageDialog->new_with_markup ($main_window, 'modal', 'error', 'cancel', $markup);
372     $errordlg->set_title (_('Error message'));
373     $errordlg->run;
374     $errordlg->destroy;
375 }
376
377 sub check_claws_not_running() {
378     my $socket = (not $ALTCONFIGDIR)? "/tmp/": $CONFIGDIR;
379     $socket .= "claws-mail-$<";
380     -S $socket and do {
381         my $emsg = "$xl::s{e_error}$xl::s{e_running}";
382         log_message ($emsg);
383         error_dialog ($emsg);
384         return FALSE;
385      };
386
387      return TRUE;
388 }
389
390 sub check_rc_file() {
391     my ($rcfile) = @_;
392     (defined($rcfile) && -f $rcfile) or do {
393        my $emsg = "$xl::s{e_error}$xl::s{e_noclawsrc}\n";
394         log_message ($emsg);
395         error_dialog ($emsg);
396         return FALSE;
397     };
398
399     return TRUE;
400 }
401
402 sub set_widget_hint() {
403     if ($SHOWHINTS) {
404         my ($wdgt, $hint) = @_;    
405         $wdgt->set_tooltip_text ($hint);
406         $wdgt->set_has_tooltip (TRUE);
407     }
408 }
409
410 # graphic element creation 
411
412 sub new_check_button_for {
413     my ($hash, $key) = @_;
414     my $name = $$hash{$key}[NAME];
415     my $label = $$hash{$key}[LABEL];
416     #
417     my $hbox = Gtk2::HBox->new (FALSE, 5);
418     my $cb = Gtk2::CheckButton->new ($label);
419     $$hash{$key}[GUI] = $cb;
420     if (defined ($HPVALUE{$name})) {
421         $cb->set_active ($HPVALUE{$name} eq '1');
422     }
423     $cb->signal_connect (clicked => sub {
424             my ($w, $e) = @_;
425             &handle_bool_value($w, $e, \$HPVALUE{$name});
426         });
427     &set_widget_hint ($cb, $$hash{$key}[DESC]);
428     $hbox->pack_start ($cb, FALSE, FALSE, HBOX_SPC);
429     #
430     return $hbox;
431 }
432
433 sub new_text_box_for_int {
434     my ($hash, $key) = @_;
435     my $name = $$hash{$key}[NAME];
436     my $label = $$hash{$key}[LABEL];
437     my @type = split (/,/, $$hash{$key}[TYPE]);
438     push (@type, 0), push (@type, 10000) unless ($#type > 0); 
439     #
440     my $hbox = Gtk2::HBox->new (FALSE, 5);
441     my $glabel = Gtk2::Label->new ($label);
442     my $pagei = int (($type[2] - $type[1]) / 10);
443     my $adjust = Gtk2::Adjustment->new (
444             $HPVALUE{$name}, $type[1], $type[2], 1, $pagei, 10
445         );
446     my $gentry = Gtk2::SpinButton->new ($adjust, 1, 0);
447     $gentry->set_numeric (TRUE);
448     $$hash{$key}[GUI] = $gentry;
449     $gentry->signal_connect('value-changed' => sub {
450             my ($w, $e) = @_;
451             &handle_int_value($w, $e, \$HPVALUE{$name});
452         });
453     &set_widget_hint ($gentry, $$hash{$key}[DESC]);
454     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
455     $hbox->pack_start ($gentry, FALSE, FALSE, HBOX_SPC);
456     #
457     return $hbox;
458 }
459
460 sub new_color_button_for {
461     my ($hash, $key) = @_;
462     my $name = $$hash{$key}[NAME];
463     my $label = $$hash{$key}[LABEL];
464     #
465     my $col = &gdk_color_from_str ($HPVALUE{$name});
466     my $hbox = Gtk2::HBox->new (FALSE, 5);
467     my $glabel = Gtk2::Label->new ($label);
468     my $button = Gtk2::ColorButton->new_with_color ($col);
469     $$hash{$key}[GUI] = $button;
470     $button->set_title ($label);
471     $button->set_relief ('none');
472     $button->signal_connect ('color-set' => sub {
473             my ($w, $e) = @_;
474             &handle_color_value($w, $e, \$HPVALUE{$name}); 
475         });
476     &set_widget_hint ($button, $$hash{$key}[DESC]);
477     $hbox->pack_start ($button, FALSE, FALSE, HBOX_SPC);
478     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
479     #
480     return $hbox;
481 }
482
483 sub new_selection_box_for {
484     my ($hash, $key) = @_;
485     my $name = $$hash{$key}[NAME];
486     my $label = $$hash{$key}[LABEL];
487     #
488     my $hbox = Gtk2::HBox->new (FALSE, 5);
489     my $glabel = Gtk2::Label->new ($label);
490     my $combo = Gtk2::ComboBox->new_text;
491     $$hash{$key}[GUI] = $combo;
492     my @options = split (';', $$hash{$key}[TYPE]);
493     foreach my $opt (@options) {
494         my ($index, $textkey) = split ('=', $opt);
495         $combo->insert_text ($index, $xl::s{$textkey});
496     }
497     $combo->signal_connect ('changed' => sub {
498             my ($w, $e) = @_;
499             &handle_selection_value($w, $e, \$HPVALUE{$name});
500         });
501     $combo->set_active ($HPVALUE{$name});
502     &set_widget_hint ($combo, $$hash{$key}[DESC]);
503     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
504     $hbox->pack_start ($combo, FALSE, FALSE, HBOX_SPC);
505     #
506     return $hbox;
507 }
508
509 # preference maps and corresponding page creation subs
510
511 %pr::oth = ( # other preferences
512     use_dlg => [ 
513         'addressbook_use_editaddress_dialog',
514         $xl::s{l_oth_use_dlg},
515         $xl::s{h_oth_use_dlg},
516         'bool',
517         '2.7.0',
518         '0',
519         undef,
520     ],
521     max_use => [
522         'cache_max_mem_usage',
523         $xl::s{l_oth_max_use},
524         $xl::s{h_oth_max_use},
525         'int,0,262144', # 0 Kb - 256 Mb
526         '0.0.0',
527         '4096',
528         undef,
529     ],
530     min_time => [
531         'cache_min_keep_time',
532         $xl::s{l_oth_min_time},
533         $xl::s{h_oth_min_time},
534         'int,0,120', # 0 minutes - 2 hours
535         '0.0.0',
536         '15',
537         undef,
538     ],
539     use_netm => [
540         'use_networkmanager',
541         $xl::s{l_oth_use_netm},
542         $xl::s{h_oth_use_netm},
543         'bool',
544         '3.3.1',
545         '1',
546         undef,
547     ],
548 );
549
550 sub new_other_page() {
551     my $of = Gtk2::VBox->new (FALSE, 5);
552     $of->set_border_width (PAGE_SPC);
553
554     my $ab_frame = Gtk2::Frame->new ($xl::s{ab_frame}); 
555     my $cb_use_dlg = &new_check_button_for(\%pr::oth, 'use_dlg');
556     my $vb1 = Gtk2::VBox->new (FALSE, 5);
557     $vb1->set_border_width (PAGE_SPC); 
558     $vb1->pack_start ($cb_use_dlg, FALSE, FALSE, 0);
559     $ab_frame->add ($vb1);
560
561     my $mem_frame = Gtk2::Frame->new ($xl::s{mem_frame}); 
562     my $tb_max_use = &new_text_box_for_int(\%pr::oth, 'max_use');
563     my $tb_min_time = &new_text_box_for_int(\%pr::oth, 'min_time');
564     my $vb2 = Gtk2::VBox->new (FALSE, 5);
565     $vb2->set_border_width (PAGE_SPC); 
566     $vb2->pack_start ($tb_max_use, FALSE, FALSE, 0);
567     $vb2->pack_start ($tb_min_time, FALSE, FALSE, 0);
568     $mem_frame->add ($vb2);
569
570     my $netm_frame = Gtk2::Frame->new ($xl::s{netm_frame});
571     my $cb_use_netm = &new_check_button_for(\%pr::oth, 'use_netm');
572     my $vb3 = Gtk2::VBox->new (FALSE, 5);
573     $vb3->set_border_width (PAGE_SPC);
574     $vb3->pack_start ($cb_use_netm, FALSE, FALSE, 0);
575     $netm_frame->add ($vb3);
576
577     $of->pack_start ($ab_frame, FALSE, FALSE, FRAME_SPC);
578     $of->pack_start ($mem_frame, FALSE, FALSE, FRAME_SPC);
579     $of->pack_start ($netm_frame, FALSE, FALSE, FRAME_SPC);
580
581     return $of;
582 }
583
584 %pr::gui = ( # gui bells and whistles
585     b_unread => [ 
586         'bold_unread',
587         $xl::s{l_gui_b_unread},
588         $xl::s{h_gui_b_unread},
589         'bool',
590         '0.0.0',
591         '1',
592         undef,
593     ],
594     no_markup => [
595         'compose_no_markup',
596         $xl::s{l_gui_no_markup},
597         $xl::s{h_gui_no_markup},
598         'bool',
599         '0.0.0',
600         '0',
601         undef,
602     ],
603     dot_lines => [
604         'enable_dotted_lines',
605         $xl::s{l_gui_dot_lines},
606         $xl::s{h_gui_dot_lines},
607         'bool',
608         '0.0.0',
609         '0',
610         undef,
611     ],
612     h_scroll => [
613         'enable_hscrollbar',
614         $xl::s{l_gui_h_scroll},
615         $xl::s{h_gui_h_scroll},
616         'bool',
617         '0.0.0',
618         '1',
619         undef,
620     ],
621     swp_from => [
622         'enable_swap_from',
623         $xl::s{l_gui_swp_from},
624         $xl::s{h_gui_swp_from},
625         'bool',
626         '0.0.0',
627         '0',
628         undef,
629     ],
630     v_scroll => [
631         'folderview_vscrollbar_policy',
632         $xl::s{l_gui_v_scroll},
633         $xl::s{h_gui_v_scroll},
634         '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
635         '0.0.0',
636         '0',
637         undef,
638     ],
639     strip_off => [
640         'stripes_color_offset',
641         $xl::s{l_gui_strip_off},
642         $xl::s{h_gui_strip_off},
643         'int,0,10000', # no idea what this number means
644         '0.0.0',
645         '4000',
646         undef,
647     ],
648     cursor_v => [
649         'textview_cursor_visible',
650         $xl::s{l_gui_cursor_v},
651         $xl::s{h_gui_cursor_v},
652         'bool',
653         '0.0.0',
654         '0',
655         undef,
656     ],
657     toolbar_d => [
658         'toolbar_detachable',
659         $xl::s{l_gui_toolbar_d},
660         $xl::s{h_gui_toolbar_d},
661         'bool',
662         '0.0.0',
663         '0',
664         undef,
665     ],
666     strip_all => [
667         'use_stripes_everywhere',
668         $xl::s{l_gui_strip_all},
669         $xl::s{h_gui_strip_all},
670         'bool',
671         '0.0.0',
672         '1',
673         undef,
674     ],
675     strip_sum => [
676         'use_stripes_in_summaries',
677         $xl::s{l_gui_strip_sum},
678         $xl::s{h_gui_strip_sum},
679         'bool',
680         '0.0.0',
681         '1',
682         undef,
683     ],
684     two_linev => [
685         'two_line_vertical',
686         $xl::s{l_gui_two_line_v},
687         $xl::s{h_gui_two_line_v},
688         'bool',
689         '3.4.0.7',
690         '0',
691         undef,
692     ],
693 );
694
695 sub new_gui_page() {
696     my $gf = Gtk2::VBox->new (FALSE, 5);
697     $gf->set_border_width (PAGE_SPC);
698
699     my $stripes_frame = Gtk2::Frame->new ($xl::s{stripes_frame});
700     my $cb_strip_all = &new_check_button_for (\%pr::gui, 'strip_all');
701     my $cb_strip_sum = &new_check_button_for (\%pr::gui, 'strip_sum');
702     my $tb_strip_off = &new_text_box_for_int (\%pr::gui, 'strip_off');
703     my $vb1 = Gtk2::VBox->new (FALSE, 5);
704     $vb1->set_border_width (PAGE_SPC);
705     $vb1->pack_start ($cb_strip_all, FALSE, FALSE, 0);
706     $vb1->pack_start ($cb_strip_sum, FALSE, FALSE, 0);
707     $vb1->pack_start ($tb_strip_off, FALSE, FALSE, 0);
708     $stripes_frame->add ($vb1);
709
710     my $mlist_frame = Gtk2::Frame->new ($xl::s{mlist_frame});
711     my $cb_b_unread = &new_check_button_for (\%pr::gui, 'b_unread');
712     my $cb_swp_from = &new_check_button_for (\%pr::gui, 'swp_from');
713     my $vb3 = Gtk2::VBox->new (FALSE, 5);
714     $vb3->set_border_width (PAGE_SPC);
715     $vb3->pack_start ($cb_b_unread, FALSE, FALSE, 0);
716     $vb3->pack_start ($cb_swp_from, FALSE, FALSE, 0);
717     $mlist_frame->add ($vb3);
718
719     my $sbar_frame = Gtk2::Frame->new ($xl::s{sbar_frame});
720     my $cb_h_scroll = &new_check_button_for (\%pr::gui, 'h_scroll');
721     my $sb_v_scroll = &new_selection_box_for (\%pr::gui, 'v_scroll');
722     my $vb2 = Gtk2::VBox->new (FALSE, 5);
723     $vb2->set_border_width (PAGE_SPC);
724     $vb2->pack_start ($cb_h_scroll, FALSE, FALSE, 0);
725     $vb2->pack_start ($sb_v_scroll, FALSE, FALSE, 0);
726     $sbar_frame->add ($vb2);
727
728     my $cb_no_markup = &new_check_button_for (\%pr::gui, 'no_markup'); 
729     my $cb_dot_lines = &new_check_button_for (\%pr::gui, 'dot_lines'); 
730     my $cb_cursor_v = &new_check_button_for (\%pr::gui, 'cursor_v');
731     my $cb_toolbar_d = &new_check_button_for (\%pr::gui, 'toolbar_d');
732     my $cb_two_linev = &new_check_button_for (\%pr::gui, 'two_linev');
733
734     $gf->pack_start ($stripes_frame, FALSE, FALSE, FRAME_SPC);
735     $gf->pack_start ($mlist_frame, FALSE, FALSE, FRAME_SPC);
736     $gf->pack_start ($cb_no_markup, FALSE, FALSE, 0);
737     $gf->pack_start ($cb_dot_lines, FALSE, FALSE, 0);
738     $gf->pack_start ($cb_cursor_v, FALSE, FALSE, 0);
739     $gf->pack_start ($cb_toolbar_d, FALSE, FALSE, 0);
740     $gf->pack_start ($cb_two_linev, FALSE, FALSE, 0);
741     $gf->pack_start ($sbar_frame, FALSE, FALSE, FRAME_SPC);
742
743     return $gf;
744 }
745
746 %pr::beh = ( # tweak some behaviour
747     hover_t => [
748         'hover_timeout',
749         $xl::s{l_beh_hover_t},
750         $xl::s{h_beh_hover_t},
751         'int,100,3000', # 0.1 seconds - 3 seconds
752         '0.0.0',
753         '500',
754         undef,
755     ],
756     dangerous => [
757         'live_dangerously',
758         $xl::s{l_beh_dangerous},
759         $xl::s{h_beh_dangerous},
760         'bool',
761         '0.0.0',
762         '0',
763         undef,
764     ],
765     flowed => [
766         'respect_flowed_format',
767         $xl::s{l_beh_flowed},
768         $xl::s{h_beh_flowed},
769         'bool',
770         '0.0.0',
771         '0',
772         undef,
773     ],
774     parts_rw => [
775         'save_parts_readwrite',
776         $xl::s{l_beh_parts_rw},
777         $xl::s{h_beh_parts_rw},
778         'bool',
779         '0.0.0',
780         '0',
781         undef,
782     ],
783     skip_ssl => [
784         'skip_ssl_cert_check',
785         $xl::s{l_beh_skip_ssl},
786         $xl::s{h_beh_skip_ssl},
787         'bool',
788         '0.0.0',
789         '0',
790         undef,
791     ],
792     up_step => [
793         'statusbar_update_step',
794         $xl::s{l_beh_up_step},
795         $xl::s{h_beh_up_step},
796         'int,1,200', # 1 item - 200 items
797         '0.0.0',
798         '10',
799         undef,
800     ],
801     thread_a => [
802         'thread_by_subject_max_age',
803         $xl::s{l_beh_thread_a},
804         $xl::s{h_beh_thread_a},
805         'int,1,30', # 1 day - 30 days
806         '0.0.0',
807         '10',
808         undef,
809     ],
810     unsafe_ssl => [
811         'unsafe_ssl_certs',
812         $xl::s{l_beh_unsafe_ssl},
813         $xl::s{h_beh_unsafe_ssl},
814         'bool',
815         '0.0.0',
816         '0',
817         undef,
818     ],
819     use_utf8 => [
820         'utf8_instead_of_locale_for_broken_mail',
821         $xl::s{l_beh_use_utf8},
822         $xl::s{h_beh_use_utf8},
823         'bool',
824         '0.0.0',
825         '0',
826         undef,
827     ],
828     warn_dnd => [
829         'warn_dnd',
830         $xl::s{l_beh_warn_dnd},
831         $xl::s{h_beh_warn_dnd},
832         'bool',
833         '0.0.0',
834         '1',
835         undef,
836     ],
837     out_ascii => [
838         'outgoing_fallback_to_ascii',
839         $xl::s{l_beh_out_ascii},
840         $xl::s{h_beh_out_ascii},
841         'bool',
842         '3.4.0.37',
843         '1',
844         undef,
845     ],
846 );
847
848 sub new_behaviour_page() {
849     my $bf = Gtk2::VBox->new (FALSE, 5);
850     $bf->set_border_width (PAGE_SPC);
851
852     my $dnd_frame = Gtk2::Frame->new ($xl::s{dnd_frame});
853     my $tb_hoover_t = &new_text_box_for_int (\%pr::beh, 'hover_t');
854     my $cb_warn_dnd = &new_check_button_for (\%pr::beh, 'warn_dnd');
855     my $vb1 = Gtk2::VBox->new (FALSE, 5);
856     $vb1->set_border_width (PAGE_SPC);
857     $vb1->pack_start ($cb_warn_dnd, FALSE, FALSE, 0);
858     $vb1->pack_start ($tb_hoover_t, FALSE, FALSE, 0);
859     $dnd_frame->add ($vb1);
860
861     my $ssl_frame = Gtk2::Frame->new ($xl::s{ssl_frame});
862     my $cb_skip_ssl = &new_check_button_for (\%pr::beh, 'skip_ssl');
863     my $cb_unsafe_ssl = &new_check_button_for (\%pr::beh, 'unsafe_ssl');
864     my $hb1 = Gtk2::HBox->new (FALSE, 5);
865     $hb1->set_border_width (PAGE_SPC);
866     $hb1->pack_start ($cb_skip_ssl, FALSE, FALSE, 0);
867     $hb1->pack_start ($cb_unsafe_ssl, FALSE, FALSE, 0);
868     $ssl_frame->add ($hb1);
869
870     my $tb_up_step = &new_text_box_for_int (\%pr::beh, 'up_step');
871     my $tb_thread_a = &new_text_box_for_int (\%pr::beh, 'thread_a');
872
873     my $msgs_frame = Gtk2::Frame->new ($xl::s{msgs_frame});
874     my $cb_flowed = &new_check_button_for (\%pr::beh, 'flowed');
875     my $cb_parts_rw = &new_check_button_for (\%pr::beh, 'parts_rw');
876     my $cb_use_utf8 = &new_check_button_for (\%pr::beh, 'use_utf8');
877     my $cb_dangerous = &new_check_button_for (\%pr::beh, 'dangerous');
878     my $cb_out_ascii = &new_check_button_for (\%pr::beh, 'out_ascii');
879     my $vb2 = Gtk2::VBox->new (FALSE, 5);    
880     $vb2->set_border_width (PAGE_SPC);
881     $vb2->pack_start ($cb_flowed, FALSE, FALSE, 0);
882     $vb2->pack_start ($cb_parts_rw, FALSE, FALSE, 0);
883     $vb2->pack_start ($cb_use_utf8, FALSE, FALSE, 0);
884     $vb2->pack_start ($cb_dangerous, FALSE, FALSE, 0);
885     $vb2->pack_start ($cb_out_ascii, FALSE, FALSE, 0);
886     $msgs_frame->add ($vb2);
887
888     $bf->pack_start ($dnd_frame, FALSE, FALSE, FRAME_SPC);
889     $bf->pack_start ($ssl_frame, FALSE, FALSE, FRAME_SPC);
890     $bf->pack_start ($tb_up_step, FALSE, FALSE, 0);
891     $bf->pack_start ($tb_thread_a, FALSE, FALSE, 0);
892     $bf->pack_start ($msgs_frame, FALSE, FALSE, FRAME_SPC);
893
894     return $bf;
895 }
896
897 %pr::col = ( # a variety of colours
898     emphasis => [
899         'emphasis_color',
900         $xl::s{l_col_emphasis},
901         $xl::s{h_col_emphasis},
902         'color',
903         '0.0.0',
904         '#0000cf',
905         undef,
906     ],
907     log_err => [
908         'log_error_color',
909         $xl::s{l_col_log_err},
910         $xl::s{h_col_log_err},
911         'color',
912         '0.0.0',
913         '#af0000',
914         undef,
915     ],
916     log_in => [
917         'log_in_color',
918         $xl::s{l_col_log_in},
919         $xl::s{h_col_log_in},
920         'color',
921         '0.0.0',
922         '#000000',
923         undef,
924     ],
925     log_msg => [
926         'log_msg_color',
927         $xl::s{l_col_log_msg},
928         $xl::s{h_col_log_msg},
929         'color',
930         '0.0.0',
931         '#00af00',
932         undef,
933     ],
934     log_out => [
935         'log_out_color',
936         $xl::s{l_col_log_out},
937         $xl::s{h_col_log_out},
938         'color',
939         '0.0.0',
940         '#0000ef',
941         undef,
942     ],
943     log_warn => [
944         'log_warn_color',
945         $xl::s{l_col_log_warn},
946         $xl::s{h_col_log_warn},
947         'color',
948         '0.0.0',
949         '#af0000',
950         undef,
951     ],
952 );
953
954 sub new_colours_page() {
955     my $cf = Gtk2::VBox->new (FALSE, 5);
956     $cf->set_border_width (PAGE_SPC);
957
958     my $msgview_frame = Gtk2::Frame->new ($xl::s{msgview_frame}); 
959     my $cb_emphasis = &new_color_button_for (\%pr::col, 'emphasis');
960     my $vb1 = Gtk2::VBox->new (FALSE, 5);
961     $vb1->set_border_width (PAGE_SPC);
962     $vb1->pack_start ($cb_emphasis, FALSE, FALSE, 0);
963     $msgview_frame->add ($vb1);
964     
965     my $log_frame = Gtk2::Frame->new ($xl::s{log_frame}); 
966     my $cb_log_err = &new_color_button_for (\%pr::col, 'log_err');
967     my $cb_log_in = &new_color_button_for (\%pr::col, 'log_in');
968     my $cb_log_msg = &new_color_button_for (\%pr::col, 'log_msg');
969     my $cb_log_out = &new_color_button_for (\%pr::col, 'log_out');
970     my $cb_log_warn = &new_color_button_for (\%pr::col, 'log_warn');
971     my $vb2 = Gtk2::VBox->new (FALSE, 5);
972     $vb2->set_border_width (PAGE_SPC);
973     $vb2->pack_start ($cb_log_err, FALSE, FALSE, 0);
974     $vb2->pack_start ($cb_log_in, FALSE, FALSE, 0);
975     $vb2->pack_start ($cb_log_msg, FALSE, FALSE, 0);
976     $vb2->pack_start ($cb_log_out, FALSE, FALSE, 0);
977     $vb2->pack_start ($cb_log_warn, FALSE, FALSE, 0);
978     $log_frame->add ($vb2);
979
980     $cf->pack_start ($msgview_frame, FALSE, FALSE, 0);
981     $cf->pack_start ($log_frame, FALSE, FALSE, 0);
982
983     return $cf;
984 }
985
986 # the command line help
987 sub print_help() {
988     my $line = '-' x length ($xl::s{about_title}) . "\n";
989     print $line;
990     print $xl::s{about_title} . "\n";
991     print $xl::s{about_version} . " $VERSION\n";
992     print "Perl-GLib " . $Glib::VERSION;
993     # version info stuff appeared in 1.040
994     if ($Glib::VERSION >= 1.040) {
995         print _(", built for ") . join(".", Glib->GET_VERSION_INFO) 
996               . _(", running with ") . join(".", &Glib::major_version, 
997                   &Glib::minor_version, &Glib::micro_version);
998     }
999     print "\n";
1000     print "Perl-GTK2 " . $Gtk2::VERSION;
1001     if ($Gtk2::VERSION >= 1.040) {
1002         print _(", built for ") . join(".", Gtk2->GET_VERSION_INFO)
1003               . _(", running with ") . join(".", &Gtk2::major_version, 
1004                   &Gtk2::minor_version, &Gtk2::micro_version);
1005     }
1006     print "\n";
1007     print $line;
1008     print _("Syntax:\n");
1009     print _("    clawsker [options]\n");
1010     print _("Options:\n");
1011     print _("    --help                         Prints this help screen.\n");
1012     print _("    --verbose                      More messages on standard output.\n");
1013     print _("    --alternate-config-dir <dir>   Uses <dir> as Claws Mail config dir.\n");
1014     print _("    --clawsrc <file>               Uses <file> as full resource name.\n");
1015 }
1016
1017 # handle errors which don't allow to run
1018 sub command_line_fatal() {
1019     my $reason = shift;
1020     my $emsg = $xl::s{e_error} . $reason;
1021     error_dialog ($emsg);
1022     log_message ("$emsg", 'die');
1023 }
1024
1025 # parse the command line
1026 sub parse_command_line() {
1027     my $arg = 0;
1028     while (defined($ARGV[$arg])) {
1029         for ($ARGV[$arg]) {
1030             /--help/ && do { 
1031                 &print_help; 
1032                 return FALSE;
1033             };
1034             /--verbose/ && do {
1035                 $VERBOSE = TRUE;
1036                 last;
1037             };
1038             /--alternate-config-dir/ && do {
1039                 ++$arg;
1040                 &command_line_fatal ($xl::s{e_requireddir})
1041                     unless defined($ARGV[$arg]);
1042                 &command_line_fatal ($xl::s{e_notadir})
1043                     unless -d $ARGV[$arg];
1044                 $CONFIGDIR = $ARGV[$arg];
1045                 $CONFIGDIR .= "/" 
1046                     unless ($CONFIGDIR =~ /.*\/$/);
1047                 $ALTCONFIGDIR = TRUE;
1048                 last;
1049             };
1050             /--clawsrc/ && do {
1051                 ++$arg;
1052                 &command_line_fatal($xl::s{e_requiredfile}) 
1053                     unless defined($ARGV[$arg]);
1054                 &command_line_fatal($xl::s{e_notafile}) 
1055                     unless -f $ARGV[$arg];
1056                 &set_rc_filename ($ARGV[$arg]);
1057                 last;
1058             };
1059             /.*/ && &command_line_fatal (
1060                         _("unknown option '{opt}'.\n", opt => $ARGV[$arg]));
1061         }
1062         ++$arg;
1063     }
1064     # eveything continues...
1065     return TRUE;
1066 }
1067
1068 # update the hidden preferences status from loaded values
1069 sub init_hidden_preferences() {
1070     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth) {
1071         foreach my $key (keys %$hash) { 
1072             $HPVALUE{${$hash}{$key}[NAME]} = $PREFS{${$hash}{$key}[NAME]};
1073         }
1074     }
1075     return TRUE;
1076 }
1077
1078 # load current status from disc
1079 sub load_preferences() {
1080     my $rc = &get_rc_filename;
1081     &log_message ("Loading preferences from $rc\n");
1082     return FALSE unless &check_rc_file ($rc);
1083     return FALSE unless &check_claws_not_running;
1084     open (RCF, "<$rc");
1085     while (<RCF>) {
1086         chomp;
1087         if (/^([8a-z_]+)=(.*)$/) {
1088             $PREFS{$1} = "$2";
1089         }
1090     }
1091     close (RCF);
1092     return TRUE;
1093 }
1094
1095 # save current preferences to disc
1096 sub save_preferences() {
1097     my $rc = &get_rc_filename;
1098     &log_message ("Saving preferences to $rc\n");
1099     return FALSE unless &check_rc_file ($rc);
1100     return FALSE unless &check_claws_not_running;
1101     my $rcbak = "$rc.backup";
1102     rename ($rc, $rcbak);
1103     open (RCF, ">$rc");
1104     open (RCB, "<$rcbak");
1105     while (<RCB>) {
1106         chomp;
1107         if (/^([8a-z_]+)=(.*)$/) {
1108             if (defined($HPVALUE{$1})) {
1109                 print RCF $1 . "=" . $HPVALUE{$1} . "\n";
1110             }
1111             else {
1112                 print RCF $_ . "\n";
1113             }
1114         }
1115         else {
1116             print RCF $_ . "\n";
1117         }
1118     }
1119     close (RCB);
1120     close (RCF);
1121     return TRUE;
1122 }
1123
1124 # create notebook
1125 sub new_notebook() {
1126     my $nb = Gtk2::Notebook->new;
1127     # 
1128     $nb->append_page (&new_behaviour_page, $xl::s{tab_behaviour});
1129     $nb->append_page (&new_colours_page, $xl::s{tab_colours});
1130     $nb->append_page (&new_gui_page, $xl::s{tab_gui});
1131     $nb->append_page (&new_other_page, $xl::s{tab_other});
1132
1133     return $nb;
1134 }
1135
1136 # create an about dialog
1137 sub new_about_dialog() {
1138     my ($parent) = @_;
1139     my $title = $xl::s{about_title};
1140     my $lic = $xl::s{about_license};
1141     my $vers = $xl::s{about_version} . " $VERSION";
1142     my $license = 
1143 "This program is free software: you can redistribute it and/or modify
1144 it under the terms of the GNU General Public License as published by
1145 the Free Software Foundation, either version 3 of the License, or
1146 (at your option) any later version.
1147
1148 This program is distributed in the hope that it will be useful,
1149 but WITHOUT ANY WARRANTY; without even the implied warranty of
1150 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1151 GNU General Public License for more details.
1152
1153 You should have received a copy of the GNU General Public License
1154 along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.";
1155     my $year = "2007";
1156     my $holder = "Ricardo Mones &lt;ricardo\@mones.org&gt;";
1157
1158     my $dialog = Gtk2::MessageDialog->new_with_markup ($parent, 
1159                     [qw/modal destroy-with-parent/], 
1160                     'info', 'close', 
1161                     "<span size=\"x-large\" weight=\"bold\">$title</span>\n"
1162                     . "<span size=\"large\">$vers</span>\n"
1163                     . "<span>Copyright $year by $holder</span>\n\n"
1164                     . "<span size=\"large\">$lic</span>\n\n"
1165                     . "<span size=\"small\">$license</span>");
1166     $dialog->set_title ($xl::s{about});
1167     #
1168     return $dialog;
1169 }
1170
1171 # create buttons box
1172 sub new_button_box() {
1173     my ($parent, $adlg) = @_;
1174     my $b_about = Gtk2::Button->new_from_stock ('gtk-about');
1175     my $b_exit = Gtk2::Button->new_from_stock ('gtk-quit');
1176     my $b_apply = Gtk2::Button->new_from_stock ('gtk-apply');
1177     # disable button until is really implemented
1178     # my $b_undo = Gtk2::Button->new_from_stock ('gtk-undo');
1179     my $hbox = Gtk2::HBox->new (FALSE, 5);
1180     # signal handlers 
1181     $b_exit->signal_connect (clicked => sub { Gtk2->main_quit });
1182     $b_apply->signal_connect (clicked => sub { &save_preferences ($parent) });
1183     # $b_undo->signal_connect (clicked => sub { &undo_current_changes });
1184     $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
1185     # package them
1186     $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
1187     $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
1188     # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
1189     $hbox->pack_start ($b_about, FALSE, FALSE, 0);
1190     #
1191     return $hbox;
1192 }
1193
1194 # initialise
1195 $main_window = Gtk2::Window->new ('toplevel');
1196 exit unless &parse_command_line;
1197 exit unless &load_preferences;
1198 exit unless &init_hidden_preferences;
1199 # create main GUI
1200 my $box = Gtk2::VBox->new (FALSE, 5);
1201 $box->set_border_width(3);
1202 my $about = &new_about_dialog;
1203 $box->pack_start (&new_notebook, FALSE, FALSE, 0);
1204 $box->pack_end (&new_button_box ($main_window, $about), FALSE, FALSE, 0);
1205 $main_window->signal_connect (delete_event => sub { Gtk2->main_quit });
1206 $main_window->set_title ($xl::s{win_title});
1207 $main_window->add ($box);
1208 $main_window->show_all;
1209 Gtk2->main;
1210