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