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