added verbose, more error dialogs
[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 $errordlg = Gtk2::MessageDialog->new ($main_window, 'modal', 'error', 'cancel', $emsg);
311     $errordlg->set_title (_('Error'));
312     $errordlg->run;
313     $errordlg->destroy;
314 }
315
316 sub check_claws_not_running() {
317     my $socket = (not $ALTCONFIGDIR)? "/tmp/": $CONFIGDIR;
318     $socket .= "claws-mail-$<";
319     -S $socket and do {
320         my $emsg = "$xl::s{e_error}$xl::s{e_running}";
321         log_message ($emsg);
322         error_dialog ($emsg);
323         return FALSE;
324      };
325
326      return TRUE;
327 }
328
329 sub check_rc_file() {
330     my ($rcfile) = @_;
331     (defined($rcfile) && -f $rcfile) or do {
332        my $emsg = "$xl::s{e_error}$xl::s{e_noclawsrc}\n";
333         log_message ($emsg);
334         error_dialog ($emsg);
335         return FALSE;
336     };
337
338     return TRUE;
339 }
340
341 # graphic element creation 
342
343 sub new_check_button_for {
344     my ($hash, $key) = @_;
345     my $name = $$hash{$key}[NAME];
346     my $label = $$hash{$key}[LABEL];
347     #
348     my $hbox = Gtk2::HBox->new (FALSE, 5);
349     my $cb = Gtk2::CheckButton->new ($label);
350     $$hash{$key}[GUI] = $cb;
351     $cb->set_active ($HPVALUE{$name} eq '1');
352     $cb->signal_connect (clicked => sub {
353             my ($w, $e) = @_;
354             &handle_bool_value($w, $e, \$HPVALUE{$name});
355         });
356     $hbox->pack_start ($cb, FALSE, FALSE, HBOX_SPC);
357     #
358     return $hbox;
359 }
360
361 sub new_text_box_for {
362     my ($hash, $key) = @_;
363     my $name = $$hash{$key}[NAME];
364     my $label = $$hash{$key}[LABEL];
365     #
366     my $hbox = Gtk2::HBox->new (FALSE, 5);
367     my $glabel = Gtk2::Label->new ($label);
368     my $gentry = Gtk2::Entry->new;
369     $gentry->set_width_chars (8);
370     $$hash{$key}[GUI] = $gentry;
371     $gentry->set_text($HPVALUE{$name});
372     $gentry->signal_connect(changed => sub {
373             my ($w, $e) = @_;
374             &handle_int_value($w, $e, \$HPVALUE{$name}); # FIXME int only
375         });
376     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
377     $hbox->pack_start ($gentry, FALSE, FALSE, HBOX_SPC);
378     #
379     return $hbox;
380 }
381
382 sub new_color_button_for {
383     my ($hash, $key) = @_;
384     my $name = $$hash{$key}[NAME];
385     my $label = $$hash{$key}[LABEL];
386     #
387     my $col = &gdk_color_from_str ($HPVALUE{$name});
388     my $hbox = Gtk2::HBox->new (FALSE, 5);
389     my $glabel = Gtk2::Label->new ($label);
390     my $button = Gtk2::ColorButton->new_with_color ($col);
391     $$hash{$key}[GUI] = $button;
392     $button->set_title ($label);
393     $button->set_relief ('none');
394     $button->signal_connect ('color-set' => sub {
395             my ($w, $e) = @_;
396             &handle_color_value($w, $e, \$HPVALUE{$name}); 
397         });
398     $hbox->pack_start ($button, FALSE, FALSE, HBOX_SPC);
399     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
400     #
401     return $hbox;
402 }
403
404 sub new_selection_box_for {
405     my ($hash, $key) = @_;
406     my $name = $$hash{$key}[NAME];
407     my $label = $$hash{$key}[LABEL];
408     #
409     my $hbox = Gtk2::HBox->new (FALSE, 5);
410     my $glabel = Gtk2::Label->new ($label);
411     my $combo = Gtk2::ComboBox->new_text;
412     $$hash{$key}[GUI] = $combo;
413     my @options = split (';', $$hash{$key}[TYPE]);
414     foreach my $opt (@options) {
415         my ($index, $textkey) = split ('=', $opt);
416         $combo->insert_text ($index, $xl::s{$textkey});
417     }
418     $combo->signal_connect ('changed' => sub {
419             my ($w, $e) = @_;
420             &handle_selection_value($w, $e, \$HPVALUE{$name});
421         });
422     $combo->set_active ($HPVALUE{$name});
423     $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
424     $hbox->pack_start ($combo, FALSE, FALSE, HBOX_SPC);
425     #
426     return $hbox;
427 }
428
429 # preference maps and corresponding page creation subs
430
431 %pr::oth = ( # other preferences
432     use_dlg => [ 
433         'addressbook_use_editaddress_dialog',
434         $xl::s{l_oth_use_dlg},
435         $xl::s{h_oth_use_dlg},
436         'bool',
437         undef,
438     ],
439     max_use => [
440         'cache_max_mem_usage',
441         $xl::s{l_oth_max_use},
442         $xl::s{h_oth_max_use},
443         'int',
444         undef,
445     ],
446     min_time => [
447         'cache_min_keep_time',
448         $xl::s{l_oth_min_time},
449         $xl::s{h_oth_min_time},
450         'int',
451         undef,
452     ],
453 );
454
455 sub new_other_page() {
456     my $of = Gtk2::VBox->new (FALSE, 5);
457     $of->set_border_width (PAGE_SPC);
458
459     my $ab_frame = Gtk2::Frame->new ($xl::s{ab_frame}); 
460     my $cb_use_dlg = &new_check_button_for(\%pr::oth, 'use_dlg');
461     my $vb1 = Gtk2::VBox->new (FALSE, 5);
462     $vb1->set_border_width (PAGE_SPC); 
463     $vb1->pack_start ($cb_use_dlg, FALSE, FALSE, 0);
464     $ab_frame->add ($vb1);
465
466     my $mem_frame = Gtk2::Frame->new ($xl::s{mem_frame}); 
467     my $tb_max_use = &new_text_box_for(\%pr::oth, 'max_use');
468     my $tb_min_time = &new_text_box_for(\%pr::oth, 'min_time');
469     my $vb2 = Gtk2::VBox->new (FALSE, 5);
470     $vb2->set_border_width (PAGE_SPC); 
471     $vb2->pack_start ($tb_max_use, FALSE, FALSE, 0);
472     $vb2->pack_start ($tb_min_time, FALSE, FALSE, 0);
473     $mem_frame->add ($vb2);
474
475     $of->pack_start ($ab_frame, FALSE, FALSE, FRAME_SPC);
476     $of->pack_start ($mem_frame, FALSE, FALSE, FRAME_SPC);
477
478     return $of;
479 }
480
481 %pr::gui = ( # gui bells and whistles
482     b_unread => [ 
483         'bold_unread',
484         $xl::s{l_gui_b_unread},
485         $xl::s{h_gui_b_unread},
486         'bool',
487         undef,
488     ],
489     no_markup => [
490         'compose_no_markup',
491         $xl::s{l_gui_no_markup},
492         $xl::s{h_gui_no_markup},
493         'bool',
494         undef,
495     ],
496     dot_lines => [
497         'enable_dotted_lines',
498         $xl::s{l_gui_dot_lines},
499         $xl::s{h_gui_dot_lines},
500         'bool',
501         undef,
502     ],
503     h_scroll => [
504         'enable_hscrollbar',
505         $xl::s{l_gui_h_scroll},
506         $xl::s{h_gui_h_scroll},
507         'bool',
508         undef,
509     ],
510     swp_from => [
511         'enable_swap_from',
512         $xl::s{l_gui_swp_from},
513         $xl::s{h_gui_swp_from},
514         'bool',
515         undef,
516     ],
517     v_scroll => [
518         'folderview_vscrollbar_policy',
519         $xl::s{l_gui_v_scroll},
520         $xl::s{h_gui_v_scroll},
521         '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
522         undef,
523     ],
524     strip_off => [
525         'stripes_color_offset',
526         $xl::s{l_gui_strip_off},
527         $xl::s{h_gui_strip_off},
528         'int',
529         undef,
530     ],
531     cursor_v => [
532         'textview_cursor_visible',
533         $xl::s{l_gui_cursor_v},
534         $xl::s{h_gui_cursor_v},
535         'bool',
536         undef,
537     ],
538     toolbar_d => [
539         'toolbar_detachable',
540         $xl::s{l_gui_toolbar_d},
541         $xl::s{h_gui_toolbar_d},
542         'bool',
543         undef,
544     ],
545     strip_all => [
546         'use_stripes_everywhere',
547         $xl::s{l_gui_strip_all},
548         $xl::s{h_gui_strip_all},
549         'bool',
550         undef,
551     ],
552     strip_sum => [
553         'use_stripes_in_summaries',
554         $xl::s{l_gui_strip_sum},
555         $xl::s{h_gui_strip_sum},
556         'bool',
557         undef,
558     ],
559 );
560
561 sub new_gui_page() {
562     my $gf = Gtk2::VBox->new (FALSE, 5);
563     $gf->set_border_width (PAGE_SPC);
564
565     my $stripes_frame = Gtk2::Frame->new ($xl::s{stripes_frame});
566     my $cb_strip_all = &new_check_button_for (\%pr::gui, 'strip_all');
567     my $cb_strip_sum = &new_check_button_for (\%pr::gui, 'strip_sum');
568     my $tb_strip_off = &new_text_box_for (\%pr::gui, 'strip_off');
569     my $vb1 = Gtk2::VBox->new (FALSE, 5);
570     $vb1->set_border_width (PAGE_SPC);
571     $vb1->pack_start ($cb_strip_all, FALSE, FALSE, 0);
572     $vb1->pack_start ($cb_strip_sum, FALSE, FALSE, 0);
573     $vb1->pack_start ($tb_strip_off, FALSE, FALSE, 0);
574     $stripes_frame->add ($vb1);
575
576     my $mlist_frame = Gtk2::Frame->new ($xl::s{mlist_frame});
577     my $cb_b_unread = &new_check_button_for (\%pr::gui, 'b_unread');
578     my $cb_swp_from = &new_check_button_for (\%pr::gui, 'swp_from');
579     my $vb3 = Gtk2::VBox->new (FALSE, 5);
580     $vb3->set_border_width (PAGE_SPC);
581     $vb3->pack_start ($cb_b_unread, FALSE, FALSE, 0);
582     $vb3->pack_start ($cb_swp_from, FALSE, FALSE, 0);
583     $mlist_frame->add ($vb3);
584
585     my $sbar_frame = Gtk2::Frame->new ($xl::s{sbar_frame});
586     my $cb_h_scroll = &new_check_button_for (\%pr::gui, 'h_scroll');
587     my $sb_v_scroll = &new_selection_box_for (\%pr::gui, 'v_scroll');
588     my $vb2 = Gtk2::VBox->new (FALSE, 5);
589     $vb2->set_border_width (PAGE_SPC);
590     $vb2->pack_start ($cb_h_scroll, FALSE, FALSE, 0);
591     $vb2->pack_start ($sb_v_scroll, FALSE, FALSE, 0);
592     $sbar_frame->add ($vb2);
593
594     my $cb_no_markup = &new_check_button_for (\%pr::gui, 'no_markup'); 
595     my $cb_dot_lines = &new_check_button_for (\%pr::gui, 'dot_lines'); 
596     my $cb_cursor_v = &new_check_button_for (\%pr::gui, 'cursor_v');
597     my $cb_toolbar_d = &new_check_button_for (\%pr::gui, 'toolbar_d');
598
599     $gf->pack_start ($stripes_frame, FALSE, FALSE, FRAME_SPC);
600     $gf->pack_start ($mlist_frame, FALSE, FALSE, FRAME_SPC);
601     $gf->pack_start ($cb_no_markup, FALSE, FALSE, 0);
602     $gf->pack_start ($cb_dot_lines, FALSE, FALSE, 0);
603     $gf->pack_start ($cb_cursor_v, FALSE, FALSE, 0);
604     $gf->pack_start ($cb_toolbar_d, FALSE, FALSE, 0);
605     $gf->pack_start ($sbar_frame, FALSE, FALSE, FRAME_SPC);
606
607     return $gf;
608 }
609
610 %pr::beh = ( # tweak some behaviour
611     hover_t => [
612         'hover_timeout',
613         $xl::s{l_beh_hover_t},
614         $xl::s{h_beh_hover_t},
615         'int',
616         undef,
617     ],
618     dangerous => [
619         'live_dangerously',
620         $xl::s{l_beh_dangerous},
621         $xl::s{h_beh_dangerous},
622         'bool',
623         undef,
624     ],
625     flowed => [
626         'respect_flowed_format',
627         $xl::s{l_beh_flowed},
628         $xl::s{h_beh_flowed},
629         'bool',
630         undef,
631     ],
632     parts_rw => [
633         'save_parts_readwrite',
634         $xl::s{l_beh_parts_rw},
635         $xl::s{h_beh_parts_rw},
636         'bool',
637         undef,
638     ],
639     skip_ssl => [
640         'skip_ssl_cert_check',
641         $xl::s{l_beh_skip_ssl},
642         $xl::s{h_beh_skip_ssl},
643         'bool',
644         undef,
645     ],
646     up_step => [
647         'statusbar_update_step',
648         $xl::s{l_beh_up_step},
649         $xl::s{h_beh_up_step},
650         'int',
651         undef,
652     ],
653     thread_a => [
654         'thread_by_subject_max_age',
655         $xl::s{l_beh_thread_a},
656         $xl::s{h_beh_thread_a},
657         'int',
658         undef,
659     ],
660     unsafe_ssl => [
661         'unsafe_ssl_certs',
662         $xl::s{l_beh_unsafe_ssl},
663         $xl::s{h_beh_unsafe_ssl},
664         'bool',
665         undef,
666     ],
667     use_utf8 => [
668         'utf8_instead_of_locale_for_broken_mail',
669         $xl::s{l_beh_use_utf8},
670         $xl::s{h_beh_use_utf8},
671         'bool',
672         undef,
673     ],
674     warn_dnd => [
675         'warn_dnd',
676         $xl::s{l_beh_warn_dnd},
677         $xl::s{h_beh_warn_dnd},
678         'bool',
679         undef,
680     ],
681 );
682
683 sub new_behaviour_page() {
684     my $bf = Gtk2::VBox->new (FALSE, 5);
685     $bf->set_border_width (PAGE_SPC);
686
687     my $dnd_frame = Gtk2::Frame->new ($xl::s{dnd_frame});
688     my $tb_hoover_t = &new_text_box_for (\%pr::beh, 'hover_t');
689     my $cb_warn_dnd = &new_check_button_for (\%pr::beh, 'warn_dnd');
690     my $vb1 = Gtk2::VBox->new (FALSE, 5);
691     $vb1->set_border_width (PAGE_SPC);
692     $vb1->pack_start ($cb_warn_dnd, FALSE, FALSE, 0);
693     $vb1->pack_start ($tb_hoover_t, FALSE, FALSE, 0);
694     $dnd_frame->add ($vb1);
695
696     my $ssl_frame = Gtk2::Frame->new ($xl::s{ssl_frame});
697     my $cb_skip_ssl = &new_check_button_for (\%pr::beh, 'skip_ssl');
698     my $cb_unsafe_ssl = &new_check_button_for (\%pr::beh, 'unsafe_ssl');
699     my $hb1 = Gtk2::HBox->new (FALSE, 5);
700     $hb1->set_border_width (PAGE_SPC);
701     $hb1->pack_start ($cb_skip_ssl, FALSE, FALSE, 0);
702     $hb1->pack_start ($cb_unsafe_ssl, FALSE, FALSE, 0);
703     $ssl_frame->add ($hb1);
704
705     my $tb_up_step = &new_text_box_for (\%pr::beh, 'up_step');
706     my $tb_thread_a = &new_text_box_for (\%pr::beh, 'thread_a');
707
708     my $msgs_frame = Gtk2::Frame->new ($xl::s{msgs_frame});
709     my $cb_flowed = &new_check_button_for (\%pr::beh, 'flowed');
710     my $cb_parts_rw = &new_check_button_for (\%pr::beh, 'parts_rw');
711     my $cb_use_utf8 = &new_check_button_for (\%pr::beh, 'use_utf8');
712     my $cb_dangerous = &new_check_button_for (\%pr::beh, 'dangerous');
713     my $vb2 = Gtk2::VBox->new (FALSE, 5);    
714     $vb2->set_border_width (PAGE_SPC);
715     $vb2->pack_start ($cb_flowed, FALSE, FALSE, 0);
716     $vb2->pack_start ($cb_parts_rw, FALSE, FALSE, 0);
717     $vb2->pack_start ($cb_use_utf8, FALSE, FALSE, 0);
718     $vb2->pack_start ($cb_dangerous, FALSE, FALSE, 0);
719     $msgs_frame->add ($vb2);
720
721     $bf->pack_start ($dnd_frame, FALSE, FALSE, FRAME_SPC);
722     $bf->pack_start ($ssl_frame, FALSE, FALSE, FRAME_SPC);
723     $bf->pack_start ($tb_up_step, FALSE, FALSE, 0);
724     $bf->pack_start ($tb_thread_a, FALSE, FALSE, 0);
725     $bf->pack_start ($msgs_frame, FALSE, FALSE, FRAME_SPC);
726
727     return $bf;
728 }
729
730 %pr::col = ( # a variety of colours
731     emphasis => [
732         'emphasis_color',
733         $xl::s{l_col_emphasis},
734         $xl::s{h_col_emphasis},
735         'color',
736         undef,
737     ],
738     log_err => [
739         'log_error_color',
740         $xl::s{l_col_log_err},
741         $xl::s{h_col_log_err},
742         'color',
743         undef,
744     ],
745     log_in => [
746         'log_in_color',
747         $xl::s{l_col_log_in},
748         $xl::s{h_col_log_in},
749         'color',
750         undef,
751     ],
752     log_msg => [
753         'log_msg_color',
754         $xl::s{l_col_log_msg},
755         $xl::s{h_col_log_msg},
756         'color',
757         undef,
758     ],
759     log_out => [
760         'log_out_color',
761         $xl::s{l_col_log_out},
762         $xl::s{h_col_log_out},
763         'color',
764         undef,
765     ],
766     log_warn => [
767         'log_warn_color',
768         $xl::s{l_col_log_warn},
769         $xl::s{h_col_log_warn},
770         'color',
771         undef,
772     ],
773 );
774
775 sub new_colours_page() {
776     my $cf = Gtk2::VBox->new (FALSE, 5);
777     $cf->set_border_width (PAGE_SPC);
778
779     my $msgview_frame = Gtk2::Frame->new ($xl::s{msgview_frame}); 
780     my $cb_emphasis = &new_color_button_for (\%pr::col, 'emphasis');
781     my $vb1 = Gtk2::VBox->new (FALSE, 5);
782     $vb1->set_border_width (PAGE_SPC);
783     $vb1->pack_start ($cb_emphasis, FALSE, FALSE, 0);
784     $msgview_frame->add ($vb1);
785     
786     my $log_frame = Gtk2::Frame->new ($xl::s{log_frame}); 
787     my $cb_log_err = &new_color_button_for (\%pr::col, 'log_err');
788     my $cb_log_in = &new_color_button_for (\%pr::col, 'log_in');
789     my $cb_log_msg = &new_color_button_for (\%pr::col, 'log_msg');
790     my $cb_log_out = &new_color_button_for (\%pr::col, 'log_out');
791     my $cb_log_warn = &new_color_button_for (\%pr::col, 'log_warn');
792     my $vb2 = Gtk2::VBox->new (FALSE, 5);
793     $vb2->set_border_width (PAGE_SPC);
794     $vb2->pack_start ($cb_log_err, FALSE, FALSE, 0);
795     $vb2->pack_start ($cb_log_in, FALSE, FALSE, 0);
796     $vb2->pack_start ($cb_log_msg, FALSE, FALSE, 0);
797     $vb2->pack_start ($cb_log_out, FALSE, FALSE, 0);
798     $vb2->pack_start ($cb_log_warn, FALSE, FALSE, 0);
799     $log_frame->add ($vb2);
800
801     $cf->pack_start ($msgview_frame, FALSE, FALSE, 0);
802     $cf->pack_start ($log_frame, FALSE, FALSE, 0);
803
804     return $cf;
805 }
806
807 # the command line help
808 sub print_help() {
809     my $line = '-' x length ($xl::s{about_title}) . "\n";
810     print $line;
811     print $xl::s{about_title} . "\n";
812     print $xl::s{about_version} . " $VERSION\n";
813     print $line;
814     print _("Syntax:\n");
815     print _("    clawsker [options]\n");
816     print _("Options:\n");
817     print _("    --help                         Prints this help screen.\n");
818     print _("    --verbose                      More messages on standard output.\n");
819     print _("    --alternate-config-dir <dir>   Uses <dir> as Claws Mail config dir.\n");
820     print _("    --clawsrc <file>               Uses <file> as full resource name.\n");
821 }
822
823 # parse the command line
824 sub parse_command_line() {
825     my $arg = 0;
826     while (defined($ARGV[$arg])) {
827         for ($ARGV[$arg]) {
828             /--help/ && do { 
829                 &print_help; 
830                 exit 0;
831             };
832             /--verbose/ && do {
833                 $VERBOSE = TRUE;
834             };
835             /--alternate-config-dir/ && do {
836                 ++$arg;
837                 die "$xl::s{e_error}$xl::s{e_requireddir}\n" 
838                     unless defined($ARGV[$arg]);
839                 die "$xl::s{e_error}$xl::s{e_notadir}\n" 
840                     unless -d $ARGV[$arg];
841                 $CONFIGDIR = $ARGV[$arg];
842                 $ALTCONFIGDIR = TRUE;
843                 last;
844             };
845             /--clawsrc/ && do {
846                 ++$arg;
847                 die "$xl::s{e_error}$xl::s{e_requiredfile}\n" 
848                     unless defined($ARGV[$arg]);
849                 die "$xl::s{e_error}$xl::s{e_notafile}\n" 
850                     unless -f $ARGV[$arg];
851                 &set_rc_filename ($ARGV[$arg]);
852                 last;
853             };
854             /.*/ && die $xl::s{e_error} 
855                         . _("unknown option '{opt}'.\n", opt => $ARGV[$arg]);
856         }
857         ++$arg;
858     }
859 }
860
861 # update the hidden preferences status from loaded values
862 sub init_hidden_preferences() {
863     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth) {
864         foreach my $key (keys %$hash) { 
865             $HPVALUE{${$hash}{$key}[NAME]} = $PREFS{${$hash}{$key}[NAME]};
866         }
867     }
868 }
869
870 # load current status from disc
871 sub load_preferences() {
872     my $rc = &get_rc_filename;
873     &check_rc_file ($rc) or return;
874     &check_claws_not_running or return;
875     open (RCF, "<$rc");
876     while (<RCF>) {
877         chomp;
878         if (/^([8a-z_]+)=(.*)$/) {
879             $PREFS{$1} = "$2";
880         }
881     }
882     close (RCF);
883 }
884
885 # save current preferences to disc
886 sub save_preferences() {
887     my $rc = &get_rc_filename;
888     &check_rc_file ($rc) or return;
889     &check_claws_not_running or return;
890     my $rcbak = "$rc.backup";
891     rename ($rc, $rcbak);
892     open (RCF, ">$rc");
893     open (RCB, "<$rcbak");
894     while (<RCB>) {
895         chomp;
896         if (/^([8a-z_]+)=(.*)$/) {
897             if (defined($HPVALUE{$1})) {
898                 print RCF $1 . "=" . $HPVALUE{$1} . "\n";
899             }
900             else {
901                 print RCF $_ . "\n";
902             }
903         }
904         else {
905             print RCF $_ . "\n";
906         }
907     }
908     close (RCB);
909     close (RCF);
910 }
911
912 # create notebook
913 sub new_notebook() {
914     my $nb = Gtk2::Notebook->new;
915     # 
916     $nb->append_page (&new_behaviour_page, $xl::s{tab_behaviour});
917     $nb->append_page (&new_colours_page, $xl::s{tab_colours});
918     $nb->append_page (&new_gui_page, $xl::s{tab_gui});
919     $nb->append_page (&new_other_page, $xl::s{tab_other});
920
921     return $nb;
922 }
923
924 # create an about dialog
925 sub new_about_dialog() {
926     my ($parent) = @_;
927     my $title = $xl::s{about_title};
928     my $lic = $xl::s{about_license};
929     my $vers = $xl::s{about_version} . " $VERSION";
930     my $license = 
931 "This program is free software: you can redistribute it and/or modify
932 it under the terms of the GNU General Public License as published by
933 the Free Software Foundation, either version 3 of the License, or
934 (at your option) any later version.
935
936 This program is distributed in the hope that it will be useful,
937 but WITHOUT ANY WARRANTY; without even the implied warranty of
938 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
939 GNU General Public License for more details.
940
941 You should have received a copy of the GNU General Public License
942 along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.";
943     my $year = "2007";
944     my $holder = "Ricardo Mones &lt;ricardo\@mones.org&gt;";
945
946     my $dialog = Gtk2::MessageDialog->new_with_markup ($parent, 
947                     [qw/modal destroy-with-parent/], 
948                     'info', 'close', 
949                     "<span size=\"x-large\" weight=\"bold\">$title</span>\n"
950                     . "<span size=\"large\">$vers</span>\n"
951                     . "<span>Copyright $year by $holder</span>\n\n"
952                     . "<span size=\"large\">$lic</span>\n\n"
953                     . "<span size=\"small\">$license</span>");
954     $dialog->set_title ($xl::s{about});
955     #
956     return $dialog;
957 }
958
959 # create buttons box
960 sub new_button_box() {
961     my ($parent, $adlg) = @_;
962     my $b_about = Gtk2::Button->new_from_stock ('gtk-about');
963     my $b_exit = Gtk2::Button->new_from_stock ('gtk-quit');
964     my $b_apply = Gtk2::Button->new_from_stock ('gtk-apply');
965     # disable button until is really implemented
966     # my $b_undo = Gtk2::Button->new_from_stock ('gtk-undo');
967     my $hbox = Gtk2::HBox->new (FALSE, 5);
968     # signal handlers 
969     $b_exit->signal_connect (clicked => sub { Gtk2->main_quit });
970     $b_apply->signal_connect (clicked => sub { &save_preferences ($parent) });
971     # $b_undo->signal_connect (clicked => sub { &undo_current_changes });
972     $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
973     # package them
974     $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
975     $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
976     # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
977     $hbox->pack_start ($b_about, FALSE, FALSE, 0);
978     #
979     return $hbox;
980 }
981
982 # initialise
983 $main_window = Gtk2::Window->new ('toplevel');
984 &parse_command_line;
985 &load_preferences;
986 &init_hidden_preferences;
987 # create main GUI
988 my $box = Gtk2::VBox->new (FALSE, 5);
989 $box->set_border_width(3);
990 my $about = &new_about_dialog;
991 $box->pack_start (&new_notebook, FALSE, FALSE, 0);
992 $box->pack_end (&new_button_box ($main_window, $about), FALSE, FALSE, 0);
993 $main_window->signal_connect (delete_event => sub { Gtk2->main_quit });
994 $main_window->set_title ($xl::s{win_title});
995 $main_window->add ($box);
996 $main_window->show_all;
997 Gtk2->main;
998