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