release 0.2.0
[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 not widely used are not provided with a graphical interface 
23 for inspection and/or modification.
24
25 Users wanting to edit such preferences had to face raw edition 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 --alternate-config-dir <dir>
35         Uses <dir> as Claws Mail configuration dir.
36
37 --clawsrc <file>
38         Uses <file> as Claws Mail resource configuration file. This sets
39         the full file name overriding any previous setting.
40
41 Multiple options are allowed, although only the last one has effect. Weird
42 option specifications may produce weird results (but otherwise correct).
43         
44 =head1 LIMITATIONS
45
46 Alternate configuration directories are not (yet) supported.
47
48 =head1 AUTHOR
49
50 Ricardo Mones E<lt>ricardo@mones.orgE<gt>
51
52 =head1 LICENSE
53
54 Copyright (c) 2007 by Ricardo Mones
55
56 This program is free software: you can redistribute it and/or modify
57 it under the terms of the GNU General Public License as published by
58 the Free Software Foundation, either version 3 of the License, or
59 (at your option) any later version.
60
61 This program is distributed in the hope that it will be useful,
62 but WITHOUT ANY WARRANTY; without even the implied warranty of
63 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
64 GNU General Public License for more details.
65
66 You should have received a copy of the GNU General Public License
67 along with this program.  If not, see E<lt>http://www.gnu.org/licenses/E<gt>.
68
69 =cut
70
71 use strict;
72 use POSIX qw(setlocale);
73 use Locale::gettext;
74 use Glib qw(TRUE FALSE);
75 use Gtk2 -init;
76
77 my $NAME = 'clawsker';
78 my $PREFIX = '@PREFIX@';
79 my $LIBDIR = '@LIBDIR@';
80 my $VERSION = '@VERSION@';
81
82 my $locale = (defined($ENV{LC_MESSAGES}) ? $ENV{LC_MESSAGES} : $ENV{LANG});
83 setlocale (LC_ALL, $locale);
84 bindtextdomain ($NAME, sprintf ('%s/share/locale', $PREFIX));
85 textdomain ($NAME);
86
87 sub _ {
88     my $str = shift;
89     my %par = @_;
90     my $xla = gettext ($str);
91     if (scalar(keys(%par)) > 0) {
92         foreach my $key (keys %par) {
93             $xla =~ s/\{$key\}/$par{$key}/g;
94         }
95     }
96     return $xla;
97 }
98
99 # default messages
100 %xl::s = (
101     win_title => _('Claws Mail Hidden Preferences'),
102     about => _('About...'),
103     about_title => _('Clawsker :: A Claws Mail Tweaker'),
104     about_license => _('License:'),
105     about_version => _('Version:'),
106
107     tab_colours => _('Colours'),
108     tab_behaviour => _('Behaviour'),
109     tab_gui => _('GUI'),
110     tab_other => _('Other'),
111
112     ab_frame => _('Addressbook'),
113     mem_frame => _('Memory'),
114     msgview_frame => _('Message view'),
115     log_frame => _('Log window'),
116     dnd_frame => _('Drag and drop'),
117     ssl_frame => _('Secure Sockets Layer'),
118     msgs_frame => _('Messages'),
119     stripes_frame => _('Coloured stripes'),
120     sbar_frame => _('Scroll bars'),
121     mlist_frame => _('Message list'),
122
123     l_oth_use_dlg => _('Use address book dialog'),
124     h_oth_use_dlg => _('If true use a separate dialogue to edit a person\'s details. Otherwise will use a form embedded in the addressbook\'s main window.'),
125     l_oth_max_use => _('Maximum memory for message cache (Kb)'),
126     h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kB.'),
127     l_oth_min_time => _('Minimun time for cache elements (minutes)'),
128     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.'),
129     
130     l_gui_b_unread => _('Show unread messages with bold font'),
131     h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'),
132     l_gui_no_markup => _('Don\'t use markup in compose window'),
133     h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'),
134     l_gui_dot_lines => _('Use dotted lines in tree views'),
135     h_gui_dot_lines => _('Use the old dotted line look in the main window tree views (folder and message lists) instead of the modern lineless look.'),
136     l_gui_h_scroll => _('Enable horizontal scrollbar'),
137     h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'),
138     l_gui_swp_from => _('Swap From field in Sent folder'),
139     h_gui_swp_from => _('Display the sender\'s email address in the To column of the Sent folder instead of the recipient\'s.'),
140     l_gui_v_scroll => _('Folder list scrollbar behaviour'),
141     h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'),
142     l_gui_v_scroll_show => _('Show always'),
143     l_gui_v_scroll_auto => _('Automatic'),
144     l_gui_v_scroll_hide => _('Hide always'),
145     l_gui_strip_off => _('Striped lines contrast'),
146     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.'),
147     l_gui_cursor_v => _('Cursor visible in message view'),
148     h_gui_cursor_v => _('Display the cursor in the message view.'),
149     l_gui_toolbar_d => _('Detachable toolbars'),
150     h_gui_toolbar_d => _('Show handles in the toolbars.'),
151     l_gui_strip_all => _('Use stripes in tree view components'),
152     h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'),
153     l_gui_strip_sum => _('Use stripes in summary view'),
154     h_gui_strip_sum => _('Enable alternately coloured lines in summary view and folder list'),
155
156     l_beh_hover_t => _('Drag and drop hover timeout (ms)'),
157     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.'),
158     l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
159     h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
160     l_beh_flowed => _('Respect flowed format in messages'),
161     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.'),
162     l_beh_parts_rw => _('Allow writable temporary files'),
163     h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
164     l_beh_skip_ssl => _('Don\'t check SSL certificates'),
165     h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
166     l_beh_up_step => _('Progress update step (items)'),
167     h_beh_up_step => _('Update stepping in progress bars.'),
168     l_beh_thread_a => _('Maximum age in threading by subject (days)'),
169     h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
170     l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
171     h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
172     l_beh_use_utf8 => _('Use UTF-8 in broken mails'),
173     h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'),
174     l_beh_warn_dnd => _('Warning on drag and drop'),
175     h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'),
176
177     l_col_emphasis => _('Emphasised X-Mailer colour'),
178     h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'),
179     l_col_log_err => _('Errors colour'),
180     h_col_log_err => _('Colour for error messages in log window.'),
181     l_col_log_in => _('Input colour'),
182     h_col_log_in => _('Colour for input messages in log window.'),
183     l_col_log_msg => _('Messages colour'),
184     h_col_log_msg => _('Colour for messages in log window.'),
185     l_col_log_out => _('Output colour'),
186     h_col_log_out => _('Colour for output messages in log window.'),
187     l_col_log_warn => _('Warnings colour'),
188     h_col_log_warn => _('Colour for warning messages in log window.'),
189
190     e_error => _('Error: '),
191     e_noclawsrc => _('resource file for Claws Mail was not found.'),
192     e_running => _('seems Claws Mail is currently running, close it first.'),
193     e_requireddir => _('option requires a directory name.'),
194     e_requiredfile => _('option requires a file name.'),
195     e_notadir => _('specified name is not a directory or does not exist.'),
196     e_notafile => _('specified name is not a file or does not exist.'),
197 );
198
199 # all preferences read by load_preferences
200 my %PREFS = ();
201 # values of all preferences handled by clawsker
202 my %HPVALUE = ();
203 # default config dir and file name
204 my $CONFIGDIR = $ENV{HOME} . '/.claws-mail/';
205 my $CONFIGRC = 'clawsrc';
206
207 # index constants for preference arrays
208 use constant NAME  => 0; # the name on the rc file
209 use constant LABEL => 1; # the label on the GUI
210 use constant DESC  => 2; # the description for the hint/help
211 use constant TYPE  => 3; # data type: bool, int, float, string, color
212 use constant GUI   => 4; # GUI element
213
214 # constants for GUI spacing
215 use constant HBOX_SPC => 5;
216 use constant FRAME_SPC => 2;
217 use constant PAGE_SPC => 5;
218
219 # data handlers and auxiliar functions
220
221 sub handle_bool_value {
222     my ($widget, $event, $dataref) = @_;
223     $$dataref = ($widget->get_active ())? '1': '0';
224 }
225
226 sub handle_int_value {
227     my ($widget, $event, $dataref) = @_;
228     $_ = $widget->get_text ();
229     s/^\s+//;
230     s/\s+$//;
231     if (/^[0-9]+$/) {
232         $$dataref = $_;
233         $widget->set_text ($_);
234     }
235     else {
236         $widget->set_text ($$dataref);
237     }
238 }
239
240 sub handle_string_value {
241     my ($widget, $event, $dataref) = @_;
242     $$dataref = $widget->get_text ();
243 }
244
245 sub gdk_color_from_str {
246     my ($str) = @_;
247     my ($rr, $gg, $bb) = (0, 0 ,0);
248     $_ = uc ($str);
249     if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) {
250         $rr = hex($1) * 256;
251         $gg = hex($2) * 256; 
252         $bb = hex($3) * 256;
253     }
254     my $color = Gtk2::Gdk::Color->new ($rr, $gg, $bb);
255     return $color;
256 }
257
258 sub str_from_gdk_color {
259     my ($color) = @_;
260     my $rr = $color->red / 256;
261     my $gg = $color->green / 256;
262     my $bb = $color->blue / 256;
263     my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb);
264     return $str;
265 }
266
267 sub handle_color_value {
268     my ($widget, $event, $dataref) = @_;
269     my $newcol = $widget->get_color;
270     $$dataref = &str_from_gdk_color ($newcol);
271 }
272
273 sub handle_selection_value {
274     my ($widget, $event, $dataref) = @_;
275     $$dataref = $widget->get_active;
276 }
277
278 sub get_rc_filename {
279     return $CONFIGDIR . $CONFIGRC;
280 }
281
282 sub set_rc_filename {
283     my ($fullname) = @_;
284     my @parts = split ('/', $fullname);
285     $CONFIGRC = $parts[$#parts];
286     $parts[$#parts] = '';
287     $CONFIGDIR = join ('/', @parts);
288 }
289
290 sub check_claws_not_running() {
291     my $socket = "/tmp/claws-mail-$<";
292     -S $socket and die "$xl::s{e_error}$xl::s{e_running}\n";
293 }
294
295 # graphic element creation 
296
297 sub new_check_button_for {
298     my ($hash, $key) = @_;
299     my $name = $$hash{$key}[NAME];
300     my $label = $$hash{$key}[LABEL];
301     #
302     my $hbox = Gtk2::HBox->new (FALSE, 5);
303     my $cb = Gtk2::CheckButton->new ($label);
304     $$hash{$key}[GUI] = $cb;
305     $cb->set_active ($HPVALUE{$name} eq '1');
306     $cb->signal_connect (clicked => sub {
307             my ($w, $e) = @_;
308             &handle_bool_value($w, $e, \$HPVALUE{$name});
309         });
310     $hbox->pack_start ($cb, FALSE, FALSE, HBOX_SPC);
311     #
312     return $hbox;
313 }
314
315 sub new_text_box_for {
316     my ($hash, $key) = @_;
317     my $name = $$hash{$key}[NAME];
318     my $label = $$hash{$key}[LABEL];
319     #
320     my $hbox = Gtk2::HBox->new (FALSE, 5);
321     my $glabel = Gtk2::Label->new ($label);
322     my $gentry = Gtk2::Entry->new;
323     $gentry->set_width_chars (8);
324     $$hash{$key}[GUI] = $gentry;
325     $gentry->set_text($HPVALUE{$name});
326     $gentry->signal_connect(changed => sub {
327             my ($w, $e) = @_;
328             &handle_int_value($w, $e, \$HPVALUE{$name}); # FIXME int only
329         });
330     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
331     $hbox->pack_start ($gentry, FALSE, FALSE, HBOX_SPC);
332     #
333     return $hbox;
334 }
335
336 sub new_color_button_for {
337     my ($hash, $key) = @_;
338     my $name = $$hash{$key}[NAME];
339     my $label = $$hash{$key}[LABEL];
340     #
341     my $col = &gdk_color_from_str ($HPVALUE{$name});
342     my $hbox = Gtk2::HBox->new (FALSE, 5);
343     my $glabel = Gtk2::Label->new ($label);
344     my $button = Gtk2::ColorButton->new_with_color ($col);
345     $$hash{$key}[GUI] = $button;
346     $button->set_title ($label);
347     $button->set_relief ('none');
348     $button->signal_connect ('color-set' => sub {
349             my ($w, $e) = @_;
350             &handle_color_value($w, $e, \$HPVALUE{$name}); 
351         });
352     $hbox->pack_start ($button, FALSE, FALSE, HBOX_SPC);
353     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
354     #
355     return $hbox;
356 }
357
358 sub new_selection_box_for {
359     my ($hash, $key) = @_;
360     my $name = $$hash{$key}[NAME];
361     my $label = $$hash{$key}[LABEL];
362     #
363     my $hbox = Gtk2::HBox->new (FALSE, 5);
364     my $glabel = Gtk2::Label->new ($label);
365     my $combo = Gtk2::ComboBox->new_text;
366     $$hash{$key}[GUI] = $combo;
367     my @options = split (';', $$hash{$key}[TYPE]);
368     foreach my $opt (@options) {
369         my ($index, $textkey) = split ('=', $opt);
370         $combo->insert_text ($index, $xl::s{$textkey});
371     }
372     $combo->signal_connect ('changed' => sub {
373             my ($w, $e) = @_;
374             &handle_selection_value($w, $e, \$HPVALUE{$name});
375         });
376     $combo->set_active ($HPVALUE{$name});
377     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
378     $hbox->pack_start ($combo, FALSE, FALSE, HBOX_SPC);
379     #
380     return $hbox;
381 }
382
383 # preference maps and corresponding page creation subs
384
385 %pr::oth = ( # other preferences
386     use_dlg => [ 
387         'addressbook_use_editaddress_dialog',
388         $xl::s{l_oth_use_dlg},
389         $xl::s{h_oth_use_dlg},
390         'bool',
391         undef,
392     ],
393     max_use => [
394         'cache_max_mem_usage',
395         $xl::s{l_oth_max_use},
396         $xl::s{h_oth_max_use},
397         'int',
398         undef,
399     ],
400     min_time => [
401         'cache_min_keep_time',
402         $xl::s{l_oth_min_time},
403         $xl::s{h_oth_min_time},
404         'int',
405         undef,
406     ],
407 );
408
409 sub new_other_page() {
410     my $of = Gtk2::VBox->new (FALSE, 5);
411     $of->set_border_width (PAGE_SPC);
412
413     my $ab_frame = Gtk2::Frame->new ($xl::s{ab_frame}); 
414     my $cb_use_dlg = &new_check_button_for(\%pr::oth, 'use_dlg');
415     my $vb1 = Gtk2::VBox->new (FALSE, 5);
416     $vb1->set_border_width (PAGE_SPC); 
417     $vb1->pack_start ($cb_use_dlg, FALSE, FALSE, 0);
418     $ab_frame->add ($vb1);
419
420     my $mem_frame = Gtk2::Frame->new ($xl::s{mem_frame}); 
421     my $tb_max_use = &new_text_box_for(\%pr::oth, 'max_use');
422     my $tb_min_time = &new_text_box_for(\%pr::oth, 'min_time');
423     my $vb2 = Gtk2::VBox->new (FALSE, 5);
424     $vb2->set_border_width (PAGE_SPC); 
425     $vb2->pack_start ($tb_max_use, FALSE, FALSE, 0);
426     $vb2->pack_start ($tb_min_time, FALSE, FALSE, 0);
427     $mem_frame->add ($vb2);
428
429     $of->pack_start ($ab_frame, FALSE, FALSE, FRAME_SPC);
430     $of->pack_start ($mem_frame, FALSE, FALSE, FRAME_SPC);
431
432     return $of;
433 }
434
435 %pr::gui = ( # gui bells and whistles
436     b_unread => [ 
437         'bold_unread',
438         $xl::s{l_gui_b_unread},
439         $xl::s{h_gui_b_unread},
440         'bool',
441         undef,
442     ],
443     no_markup => [
444         'compose_no_markup',
445         $xl::s{l_gui_no_markup},
446         $xl::s{h_gui_no_markup},
447         'bool',
448         undef,
449     ],
450     dot_lines => [
451         'enable_dotted_lines',
452         $xl::s{l_gui_dot_lines},
453         $xl::s{h_gui_dot_lines},
454         'bool',
455         undef,
456     ],
457     h_scroll => [
458         'enable_hscrollbar',
459         $xl::s{l_gui_h_scroll},
460         $xl::s{h_gui_h_scroll},
461         'bool',
462         undef,
463     ],
464     swp_from => [
465         'enable_swap_from',
466         $xl::s{l_gui_swp_from},
467         $xl::s{h_gui_swp_from},
468         'bool',
469         undef,
470     ],
471     v_scroll => [
472         'folderview_vscrollbar_policy',
473         $xl::s{l_gui_v_scroll},
474         $xl::s{h_gui_v_scroll},
475         '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
476         undef,
477     ],
478     strip_off => [
479         'stripes_color_offset',
480         $xl::s{l_gui_strip_off},
481         $xl::s{h_gui_strip_off},
482         'int',
483         undef,
484     ],
485     cursor_v => [
486         'textview_cursor_visible',
487         $xl::s{l_gui_cursor_v},
488         $xl::s{h_gui_cursor_v},
489         'bool',
490         undef,
491     ],
492     toolbar_d => [
493         'toolbar_detachable',
494         $xl::s{l_gui_toolbar_d},
495         $xl::s{h_gui_toolbar_d},
496         'bool',
497         undef,
498     ],
499     strip_all => [
500         'use_stripes_everywhere',
501         $xl::s{l_gui_strip_all},
502         $xl::s{h_gui_strip_all},
503         'bool',
504         undef,
505     ],
506     strip_sum => [
507         'use_stripes_in_summaries',
508         $xl::s{l_gui_strip_sum},
509         $xl::s{h_gui_strip_sum},
510         'bool',
511         undef,
512     ],
513 );
514
515 sub new_gui_page() {
516     my $gf = Gtk2::VBox->new (FALSE, 5);
517     $gf->set_border_width (PAGE_SPC);
518
519     my $stripes_frame = Gtk2::Frame->new ($xl::s{stripes_frame});
520     my $cb_strip_all = &new_check_button_for (\%pr::gui, 'strip_all');
521     my $cb_strip_sum = &new_check_button_for (\%pr::gui, 'strip_sum');
522     my $tb_strip_off = &new_text_box_for (\%pr::gui, 'strip_off');
523     my $vb1 = Gtk2::VBox->new (FALSE, 5);
524     $vb1->set_border_width (PAGE_SPC);
525     $vb1->pack_start ($cb_strip_all, FALSE, FALSE, 0);
526     $vb1->pack_start ($cb_strip_sum, FALSE, FALSE, 0);
527     $vb1->pack_start ($tb_strip_off, FALSE, FALSE, 0);
528     $stripes_frame->add ($vb1);
529
530     my $mlist_frame = Gtk2::Frame->new ($xl::s{mlist_frame});
531     my $cb_b_unread = &new_check_button_for (\%pr::gui, 'b_unread');
532     my $cb_swp_from = &new_check_button_for (\%pr::gui, 'swp_from');
533     my $vb3 = Gtk2::VBox->new (FALSE, 5);
534     $vb3->set_border_width (PAGE_SPC);
535     $vb3->pack_start ($cb_b_unread, FALSE, FALSE, 0);
536     $vb3->pack_start ($cb_swp_from, FALSE, FALSE, 0);
537     $mlist_frame->add ($vb3);
538
539     my $sbar_frame = Gtk2::Frame->new ($xl::s{sbar_frame});
540     my $cb_h_scroll = &new_check_button_for (\%pr::gui, 'h_scroll');
541     my $sb_v_scroll = &new_selection_box_for (\%pr::gui, 'v_scroll');
542     my $vb2 = Gtk2::VBox->new (FALSE, 5);
543     $vb2->set_border_width (PAGE_SPC);
544     $vb2->pack_start ($cb_h_scroll, FALSE, FALSE, 0);
545     $vb2->pack_start ($sb_v_scroll, FALSE, FALSE, 0);
546     $sbar_frame->add ($vb2);
547
548     my $cb_no_markup = &new_check_button_for (\%pr::gui, 'no_markup'); 
549     my $cb_dot_lines = &new_check_button_for (\%pr::gui, 'dot_lines'); 
550     my $cb_cursor_v = &new_check_button_for (\%pr::gui, 'cursor_v');
551     my $cb_toolbar_d = &new_check_button_for (\%pr::gui, 'toolbar_d');
552
553     $gf->pack_start ($stripes_frame, FALSE, FALSE, FRAME_SPC);
554     $gf->pack_start ($mlist_frame, FALSE, FALSE, FRAME_SPC);
555     $gf->pack_start ($cb_no_markup, FALSE, FALSE, 0);
556     $gf->pack_start ($cb_dot_lines, FALSE, FALSE, 0);
557     $gf->pack_start ($cb_cursor_v, FALSE, FALSE, 0);
558     $gf->pack_start ($cb_toolbar_d, FALSE, FALSE, 0);
559     $gf->pack_start ($sbar_frame, FALSE, FALSE, FRAME_SPC);
560
561     return $gf;
562 }
563
564 %pr::beh = ( # tweak some behaviour
565     hover_t => [
566         'hover_timeout',
567         $xl::s{l_beh_hover_t},
568         $xl::s{h_beh_hover_t},
569         'int',
570         undef,
571     ],
572     dangerous => [
573         'live_dangerously',
574         $xl::s{l_beh_dangerous},
575         $xl::s{h_beh_dangerous},
576         'bool',
577         undef,
578     ],
579     flowed => [
580         'respect_flowed_format',
581         $xl::s{l_beh_flowed},
582         $xl::s{h_beh_flowed},
583         'bool',
584         undef,
585     ],
586     parts_rw => [
587         'save_parts_readwrite',
588         $xl::s{l_beh_parts_rw},
589         $xl::s{h_beh_parts_rw},
590         'bool',
591         undef,
592     ],
593     skip_ssl => [
594         'skip_ssl_cert_check',
595         $xl::s{l_beh_skip_ssl},
596         $xl::s{h_beh_skip_ssl},
597         'bool',
598         undef,
599     ],
600     up_step => [
601         'statusbar_update_step',
602         $xl::s{l_beh_up_step},
603         $xl::s{h_beh_up_step},
604         'int',
605         undef,
606     ],
607     thread_a => [
608         'thread_by_subject_max_age',
609         $xl::s{l_beh_thread_a},
610         $xl::s{h_beh_thread_a},
611         'int',
612         undef,
613     ],
614     unsafe_ssl => [
615         'unsafe_ssl_certs',
616         $xl::s{l_beh_unsafe_ssl},
617         $xl::s{h_beh_unsafe_ssl},
618         'bool',
619         undef,
620     ],
621     use_utf8 => [
622         'utf8_instead_of_locale_for_broken_mail',
623         $xl::s{l_beh_use_utf8},
624         $xl::s{h_beh_use_utf8},
625         'bool',
626         undef,
627     ],
628     warn_dnd => [
629         'warn_dnd',
630         $xl::s{l_beh_warn_dnd},
631         $xl::s{h_beh_warn_dnd},
632         'bool',
633         undef,
634     ],
635 );
636
637 sub new_behaviour_page() {
638     my $bf = Gtk2::VBox->new (FALSE, 5);
639     $bf->set_border_width (PAGE_SPC);
640
641     my $dnd_frame = Gtk2::Frame->new ($xl::s{dnd_frame});
642     my $tb_hoover_t = &new_text_box_for (\%pr::beh, 'hover_t');
643     my $cb_warn_dnd = &new_check_button_for (\%pr::beh, 'warn_dnd');
644     my $vb1 = Gtk2::VBox->new (FALSE, 5);
645     $vb1->set_border_width (PAGE_SPC);
646     $vb1->pack_start ($cb_warn_dnd, FALSE, FALSE, 0);
647     $vb1->pack_start ($tb_hoover_t, FALSE, FALSE, 0);
648     $dnd_frame->add ($vb1);
649
650     my $ssl_frame = Gtk2::Frame->new ($xl::s{ssl_frame});
651     my $cb_skip_ssl = &new_check_button_for (\%pr::beh, 'skip_ssl');
652     my $cb_unsafe_ssl = &new_check_button_for (\%pr::beh, 'unsafe_ssl');
653     my $hb1 = Gtk2::HBox->new (FALSE, 5);
654     $hb1->set_border_width (PAGE_SPC);
655     $hb1->pack_start ($cb_skip_ssl, FALSE, FALSE, 0);
656     $hb1->pack_start ($cb_unsafe_ssl, FALSE, FALSE, 0);
657     $ssl_frame->add ($hb1);
658
659     my $tb_up_step = &new_text_box_for (\%pr::beh, 'up_step');
660     my $tb_thread_a = &new_text_box_for (\%pr::beh, 'thread_a');
661
662     my $msgs_frame = Gtk2::Frame->new ($xl::s{msgs_frame});
663     my $cb_flowed = &new_check_button_for (\%pr::beh, 'flowed');
664     my $cb_parts_rw = &new_check_button_for (\%pr::beh, 'parts_rw');
665     my $cb_use_utf8 = &new_check_button_for (\%pr::beh, 'use_utf8');
666     my $cb_dangerous = &new_check_button_for (\%pr::beh, 'dangerous');
667     my $vb2 = Gtk2::VBox->new (FALSE, 5);    
668     $vb2->set_border_width (PAGE_SPC);
669     $vb2->pack_start ($cb_flowed, FALSE, FALSE, 0);
670     $vb2->pack_start ($cb_parts_rw, FALSE, FALSE, 0);
671     $vb2->pack_start ($cb_use_utf8, FALSE, FALSE, 0);
672     $vb2->pack_start ($cb_dangerous, FALSE, FALSE, 0);
673     $msgs_frame->add ($vb2);
674
675     $bf->pack_start ($dnd_frame, FALSE, FALSE, FRAME_SPC);
676     $bf->pack_start ($ssl_frame, FALSE, FALSE, FRAME_SPC);
677     $bf->pack_start ($tb_up_step, FALSE, FALSE, 0);
678     $bf->pack_start ($tb_thread_a, FALSE, FALSE, 0);
679     $bf->pack_start ($msgs_frame, FALSE, FALSE, FRAME_SPC);
680
681     return $bf;
682 }
683
684 %pr::col = ( # a variety of colours
685     emphasis => [
686         'emphasis_color',
687         $xl::s{l_col_emphasis},
688         $xl::s{h_col_emphasis},
689         'color',
690         undef,
691     ],
692     log_err => [
693         'log_error_color',
694         $xl::s{l_col_log_err},
695         $xl::s{h_col_log_err},
696         'color',
697         undef,
698     ],
699     log_in => [
700         'log_in_color',
701         $xl::s{l_col_log_in},
702         $xl::s{h_col_log_in},
703         'color',
704         undef,
705     ],
706     log_msg => [
707         'log_msg_color',
708         $xl::s{l_col_log_msg},
709         $xl::s{h_col_log_msg},
710         'color',
711         undef,
712     ],
713     log_out => [
714         'log_out_color',
715         $xl::s{l_col_log_out},
716         $xl::s{h_col_log_out},
717         'color',
718         undef,
719     ],
720     log_warn => [
721         'log_warn_color',
722         $xl::s{l_col_log_warn},
723         $xl::s{h_col_log_warn},
724         'color',
725         undef,
726     ],
727 );
728
729 sub new_colours_page() {
730     my $cf = Gtk2::VBox->new (FALSE, 5);
731     $cf->set_border_width (PAGE_SPC);
732
733     my $msgview_frame = Gtk2::Frame->new ($xl::s{msgview_frame}); 
734     my $cb_emphasis = &new_color_button_for (\%pr::col, 'emphasis');
735     my $vb1 = Gtk2::VBox->new (FALSE, 5);
736     $vb1->set_border_width (PAGE_SPC);
737     $vb1->pack_start ($cb_emphasis, FALSE, FALSE, 0);
738     $msgview_frame->add ($vb1);
739     
740     my $log_frame = Gtk2::Frame->new ($xl::s{log_frame}); 
741     my $cb_log_err = &new_color_button_for (\%pr::col, 'log_err');
742     my $cb_log_in = &new_color_button_for (\%pr::col, 'log_in');
743     my $cb_log_msg = &new_color_button_for (\%pr::col, 'log_msg');
744     my $cb_log_out = &new_color_button_for (\%pr::col, 'log_out');
745     my $cb_log_warn = &new_color_button_for (\%pr::col, 'log_warn');
746     my $vb2 = Gtk2::VBox->new (FALSE, 5);
747     $vb2->set_border_width (PAGE_SPC);
748     $vb2->pack_start ($cb_log_err, FALSE, FALSE, 0);
749     $vb2->pack_start ($cb_log_in, FALSE, FALSE, 0);
750     $vb2->pack_start ($cb_log_msg, FALSE, FALSE, 0);
751     $vb2->pack_start ($cb_log_out, FALSE, FALSE, 0);
752     $vb2->pack_start ($cb_log_warn, FALSE, FALSE, 0);
753     $log_frame->add ($vb2);
754
755     $cf->pack_start ($msgview_frame, FALSE, FALSE, 0);
756     $cf->pack_start ($log_frame, FALSE, FALSE, 0);
757
758     return $cf;
759 }
760
761 # the command line help
762 sub print_help() {
763     my $line = '-' x length ($xl::s{about_title}) . "\n";
764     print $line;
765     print $xl::s{about_title} . "\n";
766     print $xl::s{about_version} . " $VERSION\n";
767     print $line;
768     print _("Syntax:\n");
769     print _("    clawsker [options]\n");
770     print _("Options:\n");
771     print _("    --help                         Prints this help screen.\n");
772     print _("    --alternate-config-dir <dir>   Uses <dir> as Claws Mail config dir.\n");
773     print _("    --clawsrc <file>               Uses <file> as full resource name.\n");
774 }
775
776 # parse the command line
777 sub parse_command_line() {
778     my $arg = 0;
779     while (defined($ARGV[$arg])) {
780         for ($ARGV[$arg]) {
781             /--help/ && do { 
782                 &print_help; 
783                 exit 0;
784             };
785             /--alternate-config-dir/ && do {
786                 ++$arg;
787                 die "$xl::s{e_error}$xl::s{e_requireddir}\n" 
788                     unless defined($ARGV[$arg]);
789                 die "$xl::s{e_error}$xl::s{e_notadir}\n" 
790                     unless -d $ARGV[$arg];
791                 $CONFIGDIR = $ARGV[$arg];
792                 last;
793             };
794             /--clawsrc/ && do {
795                 ++$arg;
796                 die "$xl::s{e_error}$xl::s{e_requiredfile}\n" 
797                     unless defined($ARGV[$arg]);
798                 die "$xl::s{e_error}$xl::s{e_notafile}\n" 
799                     unless -f $ARGV[$arg];
800                 &set_rc_filename ($ARGV[$arg]);
801                 last;
802             };
803             /.*/ && die $xl::s{e_error} 
804                         . _("unknown option '{opt}'.\n", opt => $ARGV[$arg]);
805         }
806         ++$arg;
807     }
808 }
809
810 # update the hidden preferences status from loaded values
811 sub init_hidden_preferences() {
812     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth) {
813         foreach my $key (keys %$hash) { 
814             $HPVALUE{${$hash}{$key}[NAME]} = $PREFS{${$hash}{$key}[NAME]};
815         }
816     }
817 }
818
819 # load current status from disc
820 sub load_preferences() {
821     my $rc = &get_rc_filename;
822     -f $rc or die "$xl::s{e_error}$xl::s{e_noclawsrc}\n";
823     &check_claws_not_running;
824     open (RCF, "<$rc");
825     while (<RCF>) {
826         chomp;
827         if (/^([8a-z_]+)=(.*)$/) {
828             $PREFS{$1} = "$2";
829         }
830     }
831     close (RCF);
832 }
833
834 # save current preferences to disc
835 sub save_preferences() {
836     my $rc = &get_rc_filename;
837     -f $rc or die "$xl::s{e_error}$xl::s{e_noclawsrc}\n";
838     &check_claws_not_running;
839     my $rcbak = "$rc.backup";
840     rename ($rc, $rcbak);
841     open (RCF, ">$rc");
842     open (RCB, "<$rcbak");
843     while (<RCB>) {
844         chomp;
845         if (/^([8a-z_]+)=(.*)$/) {
846             if (defined($HPVALUE{$1})) {
847                 print RCF $1 . "=" . $HPVALUE{$1} . "\n";
848             }
849             else {
850                 print RCF $_ . "\n";
851             }
852         }
853         else {
854             print RCF $_ . "\n";
855         }
856     }
857     close (RCB);
858     close (RCF);
859 }
860
861 # create notebook
862 sub new_notebook() {
863     my $nb = Gtk2::Notebook->new;
864     # 
865     $nb->append_page (&new_behaviour_page, $xl::s{tab_behaviour});
866     $nb->append_page (&new_colours_page, $xl::s{tab_colours});
867     $nb->append_page (&new_gui_page, $xl::s{tab_gui});
868     $nb->append_page (&new_other_page, $xl::s{tab_other});
869
870     return $nb;
871 }
872
873 # create an about dialog
874 sub new_about_dialog() {
875     my ($parent) = @_;
876     my $title = $xl::s{about_title};
877     my $lic = $xl::s{about_license};
878     my $vers = $xl::s{about_version} . " $VERSION";
879     my $license = 
880 "This program is free software: you can redistribute it and/or modify
881 it under the terms of the GNU General Public License as published by
882 the Free Software Foundation, either version 3 of the License, or
883 (at your option) any later version.
884
885 This program is distributed in the hope that it will be useful,
886 but WITHOUT ANY WARRANTY; without even the implied warranty of
887 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
888 GNU General Public License for more details.
889
890 You should have received a copy of the GNU General Public License
891 along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.";
892     my $year = "2007";
893     my $holder = "Ricardo Mones &lt;ricardo\@mones.org&gt;";
894
895     my $dialog = Gtk2::MessageDialog->new_with_markup ($parent, 
896                     [qw/modal destroy-with-parent/], 
897                     'info', 'close', 
898                     "<span size=\"x-large\" weight=\"bold\">$title</span>\n"
899                     . "<span size=\"large\">$vers</span>\n"
900                     . "<span>Copyright $year by $holder</span>\n\n"
901                     . "<span size=\"large\">$lic</span>\n\n"
902                     . "<span size=\"small\">$license</span>");
903     $dialog->set_title ($xl::s{about});
904     #
905     return $dialog;
906 }
907
908 # create buttons box
909 sub new_button_box() {
910     my ($adlg) = @_;
911     my $b_about = Gtk2::Button->new_from_stock ('gtk-about');
912     my $b_exit = Gtk2::Button->new_from_stock ('gtk-quit');
913     my $b_apply = Gtk2::Button->new_from_stock ('gtk-apply');
914     # disable button until is really implemented
915     # my $b_undo = Gtk2::Button->new_from_stock ('gtk-undo');
916     my $hbox = Gtk2::HBox->new (FALSE, 5);
917     # signal handlers 
918     $b_exit->signal_connect (clicked => sub { Gtk2->main_quit });
919     $b_apply->signal_connect (clicked => sub { &save_preferences });
920     # $b_undo->signal_connect (clicked => sub { &undo_current_changes });
921     $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
922     # package them
923     $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
924     $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
925     # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
926     $hbox->pack_start ($b_about, FALSE, FALSE, 0);
927     #
928     return $hbox;
929 }
930
931 # initialise
932 &parse_command_line;
933 &load_preferences;
934 &init_hidden_preferences;
935 # create main GUI
936 my $window = Gtk2::Window->new ('toplevel');
937 my $box = Gtk2::VBox->new (FALSE, 5);
938 $box->set_border_width(3);
939 my $about = &new_about_dialog;
940 $box->pack_start (&new_notebook, FALSE, FALSE, 0);
941 $box->pack_end (&new_button_box ($about), FALSE, FALSE, 0);
942 $window->signal_connect (delete_event => sub { Gtk2->main_quit });
943 $window->set_title ($xl::s{win_title});
944 $window->add ($box);
945 $window->show_all;
946 Gtk2->main;
947