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