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