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