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