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