39a662e1e39ab109832b89e0343130b3f3d54230
[clawsker.git] / clawsker
1 #!/usr/bin/perl -w
2 #
3 # Clawsker :: A Claws Mail Tweaker
4 # Copyright 2007-2018 Ricardo Mones <ricardo@mones.org>
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # See COPYING file for license details.
12 # See AUTHORS file for a complete list of contributors.
13 #
14
15 binmode STDOUT, ":encoding(utf8)";
16
17 use 5.010_000;
18 use strict;
19 use utf8;
20 use version 0.77;
21 use Glib qw(TRUE FALSE);
22 use Gtk3;
23 use POSIX qw(setlocale);
24 use Locale::gettext;
25 use Encode;
26 use Digest::MD5 qw(md5_hex);
27 use Getopt::Long;
28
29 my $NAME = 'clawsker';
30 my $PREFIX = '@PREFIX@';
31 my $LIBDIR = '@LIBDIR@';
32 my $DATADIR = '@DATADIR@';
33 my $VERSION = '@VERSION@';
34 my $VERBOSE = FALSE;
35 my $READONLY = FALSE;
36 my $CLAWSV = undef;
37 my $main_window = undef;
38
39 my $locale = (defined($ENV{LC_MESSAGES}) ? $ENV{LC_MESSAGES} : $ENV{LANG});
40 $locale = "C" unless defined($locale);
41 setlocale (LC_ALL, $locale);
42 bindtextdomain ($NAME, sprintf ('%s/share/locale', $PREFIX));
43 textdomain ($NAME);
44
45 sub _ {
46     my $str = shift;
47     my %par = @_;
48     my $xla = gettext ($str);
49     if (scalar(keys(%par)) > 0) {
50         foreach my $key (keys %par) {
51             $xla =~ s/\{$key\}/$par{$key}/g;
52         }
53     }
54     return decode_utf8($xla);
55 }
56
57 # default messages
58 %xl::s = (
59     win_title => _('Claws Mail Hidden Preferences'),
60     about_title => _('Clawsker :: A Claws Mail Tweaker'),
61     about_web_label => _("Visit Clawsker's web page"),
62
63     error_title => _('Clawsker error'),
64
65     exit_title => _('Clawsker warning'),
66     exit_fact => _('There are unapplied modifications.'),
67     exit_question => _('Do you really want to quit?'),
68
69     tab_colours => _('Colours'),
70     tab_behaviour => _('Behaviour'),
71     tab_gui => _('GUI'),
72     tab_other => _('Other'),
73     tab_winpos => _('Windows'),
74     tab_accounts => _('Accounts'),
75     tab_plugins => _('Plugins'),
76     tab_hotkeys => _('Hotkeys'),
77     tab_info => _('Info'),
78
79     dnd_frame => _('Drag \'n\' drop'),
80     ssl_frame => _('Secure Sockets Layer'),
81     msgs_frame => _('Messages'),
82     swc_frame => _('Completion'),
83
84     l_oth_use_dlg => _('Use detached address book edit dialogue'),
85     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.'),
86     l_oth_max_use => _('Maximum memory for message cache'),
87     l_oth_max_use_units => _('kilobytes'),
88     h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kilobytes.'),
89     l_oth_min_time => _('Minimun time for cache elements'),
90     l_oth_min_time_units => _('minutes'),
91     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.'),
92     l_oth_use_netm => _('Use NetworkManager'),
93     h_oth_use_netm => _('Use NetworkManager to switch offline automatically.'),
94     l_oth_mp_rounds => _('Rounds for PBKDF2 function'),
95     h_oth_mp_rounds => _('Specify the number of iterations the key derivation function will be applied on master passphrase computation. Does not modify currently stored passphrase, only master passphrases computed after changing this value are affected.'),
96
97     l_gui_b_unread => _('Show unread messages with bold font'),
98     h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'),
99     l_gui_no_markup => _('Don\'t use markup'),
100     h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'),
101     l_gui_dot_lines => _('Use dotted lines in tree view components'),
102     h_gui_dot_lines => _('Use the old dotted line look in the main window tree views (Folder, Message and other lists) instead of the modern lineless look.'),
103     l_gui_h_scroll => _('Enable horizontal scrollbar'),
104     h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'),
105     l_gui_swp_from => _('Display To column instead From column in Sent folder'),
106     h_gui_swp_from => _('Display the recipient\'s email address in a To column of the Sent folder instead of the originator\'s one in a From column.'),
107     l_gui_v_scroll => _('Folder List scrollbar behaviour'),
108     h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'),
109     l_gui_v_scroll_show => _('Show always'),
110     l_gui_v_scroll_auto => _('Automatic'),
111     l_gui_v_scroll_hide => _('Hide always'),
112     l_gui_from_show => _('From column displays'),
113     h_gui_from_show => _('Selects the data displayed in the From column of the Message List: name, address or both.'),
114     l_gui_from_show_name => _('Name only'),
115     l_gui_from_show_addr => _('Address only'),
116     l_gui_from_show_both => _('Both name and address'),
117     l_gui_strip_off => _('Coloured lines contrast'),
118     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.'),
119     l_gui_cursor_v => _('Show cursor'),
120     h_gui_cursor_v => _('Display the cursor in the Message View.'),
121     l_gui_toolbar_d => _('Detachable toolbars'),
122     h_gui_toolbar_d => _('Show handles in the toolbars.'),
123     l_gui_strip_all => _('Use stripes in all tree view components'),
124     h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'),
125     l_gui_strip_sum => _('Use stripes in Folder List and Message List'),
126     h_gui_strip_sum => _('Enable alternately coloured lines in Message List and Folder List.'),
127     l_gui_two_line_v => _('2 lines per Message List item in 3-column layout'),
128     h_gui_two_line_v => _('Spread Message List information over two lines when using the three column mode.'),
129     l_gui_margin_co => _('Show margin'),
130     h_gui_margin_co => _('Shows a small margin in the Compose View.'),
131     l_gui_mview_date => _('Don\'t display localized date'),
132     h_gui_mview_date => _('Toggles localization of date format in Message View.'),
133     l_gui_zero_char => _('Zero replacement character'),
134     h_gui_zero_char => _('Replaces \'0\' with the given character in Folder List.'),
135     l_gui_type_any => _('Editable headers'),
136     h_gui_type_any => _('Allows to manually type any value in Compose Window header entries or just select from the available choices in the associated dropdown list.'),
137     l_gui_warn_send_multi => _('Warn when sending to more than'),
138     l_gui_warn_send_multi_units => _('recipients'),
139     h_gui_warn_send_multi => _('Show a warning dialogue when sending to more recipients than specified. Use 0 to disable this check.'),
140     l_gui_next_del => _('Select next message on delete'),
141     h_gui_next_del => _('When deleting a message, toggles between selecting the next one (newer message) or the previous one (older message).'),
142
143     l_beh_hover_t => _('Drag \'n\' drop hover timeout'),
144     l_beh_hover_t_units => _('milliseconds'),
145     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.'),
146     l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
147     h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
148     l_beh_flowed => _('Respect format=flowed in messages'),
149     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.'),
150     l_beh_parts_rw => _('Allow writable temporary files'),
151     h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
152     l_beh_skip_ssl => _('Don\'t check SSL certificates'),
153     h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
154     l_beh_up_step => _('Progress bar update step every'),
155     l_beh_up_step_units => _('items'),
156     h_beh_up_step => _('Update stepping in progress bars.'),
157     l_beh_thread_a => _('Maximum age when threading by subject'),
158     l_beh_thread_a_units => _('days'),
159     h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
160     l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
161     h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
162     l_beh_use_utf8 => _('Force UTF-8 for broken mails'),
163     h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'),
164     l_beh_warn_dnd => _('Warn on drag \'n\' drop'),
165     h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'),
166     l_beh_out_ascii => _('Outgoing messages fallback to ASCII'),
167     h_beh_out_ascii => _('If allowed by content, ASCII will be used to encode outgoing messages, otherwise the user-defined encoding is always enforced.'),
168     l_beh_pp_unsel => _('Primary paste unselects selection'),
169     h_beh_pp_unsel => _('Controls how pasting using middle-click changes the selected text and insertion point.'),
170     l_beh_inline_at => _('Show inline attachments'),
171     h_beh_inline_at => _('Allows to hide inline attachments already shown in mail structure view.'),
172     l_beh_addr_swc => _('Address search in compose window matches any'),
173     h_beh_addr_swc => _('On Tab-key completion, address text will match any part of the string or only from the start.'),
174     l_beh_fold_swc => _('Folder search in folder selector matches any'),
175     h_beh_fold_swc => _('On folder name completion text will match any part of the string or only from the start.'),
176     l_beh_rewrite_ff => _('Rewrite first \'From\' using QP encoding'),
177     h_beh_rewrite_ff => _('Workaround some servers which convert first \'From\' to \'>From\' by using Quoted-Printable transfer encoding instead of 7bit/8bit encoding.'),
178
179     l_col_emphasis => _('X-Mailer header'),
180     h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'),
181     l_col_log_err => _('Error messages'),
182     h_col_log_err => _('Colour for error messages in log window.'),
183     l_col_log_in => _('Server messages'),
184     h_col_log_in => _('Colour for messages received from servers in log window.'),
185     l_col_log_msg => _('Standard messages'),
186     h_col_log_msg => _('Colour for messages in log window.'),
187     l_col_log_out => _('Client messages'),
188     h_col_log_out => _('Colour for messages sent to servers in log window.'),
189     l_col_log_warn => _('Warning messages'),
190     h_col_log_warn => _('Colour for warning messages in log window.'),
191
192     l_col_tags_bg => _('Tags background'),
193     h_col_tags_bg => _('Background colour for tags in message view.'),
194     l_col_tags_text => _('Tags text'),
195     h_col_tags_text => _('Text colour for tags in message view.'),
196
197     l_col_default_header_bg => _('Default headers background'),
198     h_col_default_header_bg => _('Background colour for default headers in compose window.'),
199     l_col_default_header_text => _('Default headers text'),
200     h_col_default_header_text => _('Text colour for default headers in compose window.'),
201
202     l_col_qs_active_bg => _('Active quick search background'),
203     h_col_qs_active_bg => _('Background colour for active quick search.'),
204     l_col_qs_active_text => _('Active quick search text'),
205     h_col_qs_active_text => _('Text colour for active quick search.'),
206     l_col_qs_error_bg => _('Quick search error background'),
207     h_col_qs_error_bg => _('Background colour for quick search error.'),
208     l_col_qs_error_text => _('Quick search error text'),
209     h_col_qs_error_text => _('Text colour for quick search error.'),
210
211     l_col_diff_add => _('Added lines'),
212     h_col_diff_add => _('Colour for added lines in patches.'),
213     l_col_diff_del => _('Deleted lines'),
214     h_col_diff_del => _('Colour for deleted lines in patches.'),
215     l_col_diff_hunk => _('Hunk lines'),
216     h_col_diff_hunk => _('Colour for hunk headers in patches.'),
217
218     l_win_x => _('X position'),
219     h_win_x => _('X coordinate for window\'s top-left corner.'),
220     l_win_y => _('Y position'),
221     h_win_y => _('Y coordinate for window\'s top-left corner.'),
222     l_win_w => _('Width'),
223     h_win_w => _('Window\'s width in pixels.'),
224     l_win_h => _('Height'),
225     h_win_h => _('Window\'s height in pixels.'),
226
227     l_win_main_mx => _('Maximized'),
228     h_win_main_mx => _('Changes window maximized status.'),
229     l_win_main_fs => _('Full-screen'),
230     h_win_main_fs => _('Changes full screen status.'),
231
232     l_acc_gtls_set => _('Use custom GnuTLS priority'),
233     h_acc_gtls_set => _('Enables using user provided GnuTLS priority string.'),
234     l_acc_gtls_pri => _('GnuTLS priority'),
235     h_acc_gtls_pri => _('Value to use as GnuTLS priority string if custom priority check is enabled. Otherwise this value is ignored.'),
236
237     l_plu_gpg_alimit => _('Autocompletion limit'),
238     h_plu_gpg_alimit => _('Limits the number of addresses obtained from keyring through autocompletion. Use 0 to get all matches.'),
239     l_plu_lav_burl => _('Base URL'),
240     h_plu_lav_burl => _('This is the URL where avatar requests are sent. You can use the one of your own libravatar server, if available.'),
241     l_plu_prl_flvb => _('Log level'),
242     h_plu_prl_flvb => _('Verbosity level of log, accumulative.'),
243     l_plu_prl_none => _('None'),
244     l_plu_prl_manual => _('Manual'),
245     l_plu_prl_action => _('Actions'),
246     l_plu_prl_match => _('Matches'),
247 );
248
249 # data and metadata of resource files
250 my $CONFIGDATA;
251 my $CONFIGMETA;
252 my $ACCOUNTDATA;
253 my $ACCOUNTMETA;
254 # all preferences read by load_preferences
255 my %PREFS = ();
256 my %ACPREFS = ();
257 my %PLPREFS = ();
258 # values of all preferences handled by clawsker
259 my %HPVALUE = ();
260 my %ACHPVALUE = ();
261 my %PLHPVALUE = ();
262 # default config dir and file name
263 my $ALTCONFIGDIR = FALSE;
264 my $CONFIGDIR = $ENV{HOME} . '/.claws-mail/';
265 my $CONFIGRC = 'clawsrc';
266 my $ACCOUNTRC = 'accountrc';
267 # supported and available plugins lists
268 my @PLUGINS = qw(AttRemover GPG ManageSieve Libravatar PerlPlugin);
269 my @AVPLUGINS = ();
270 # loaded hotkeys from load_menurc
271 my $HOTKEYS;
272 # current tree selection
273 my $SELHOTKEY;
274 # loaded icons
275 my @APPICONS = ();
276 # modification flag
277 my $MODIFIED = 0;
278
279 # index constants for preference arrays
280 use constant NAME  => 0; # the name on the rc file
281 use constant LABEL => 1; # the label on the GUI
282 use constant DESC  => 2; # the description for the hint/help
283 use constant TYPE  => 3; # data type: bool, int, float, string, color
284 use constant CMVER => 4; # lowest[,highest] Claws Mail version(s) the feature exists
285 use constant CMDEF => 5; # default value for the preference in Claws Mail
286 use constant PLUGIN => 6; # plugin section (only in plugin preferences)
287
288 # constants for GUI spacing
289 use constant HBOX_PAD => 5;
290 use constant FRAME_SPC => 2;
291 use constant PAGE_SPC => 5;
292
293 # for data references indexing
294 use constant VALUE => 0;
295 use constant IVALUE => 1;
296
297 # version functions
298
299 sub version_greater_or_equal {
300     my ($version, $refvers) = @_;
301     return TRUE if (length($version) == 0 and length($refvers) >= 0);
302     return FALSE if (length($version) >= 0 and length($refvers) == 0);
303     return TRUE if (version->parse($version) >= version->parse($refvers));
304     return FALSE;
305 }
306
307 sub get_claws_version {
308     $_ = qx/which claws-mail/;
309     chomp;
310     return "" unless ($_); # not installed
311     my $res = "";
312     $_ = qx/$_ -v/;
313     chomp;
314     my @fver = split (/ /);
315     die "Invalid version string" unless ($fver[2] eq "version");
316     my @ver = split (/\./, $fver[3]);
317     $res .= "$ver[0].";
318     $res .= "$ver[1].";
319     if ($ver[2] =~ /(\d+)git(\d+)/) {
320         $res .= "$1.$2";
321     }
322     else {
323         $res .= "$ver[2].0";
324     }
325     return $res;
326 }
327
328 # data handlers and auxiliar functions
329
330 sub handle_bool_value {
331     my ($widget, $event, $dataref) = @_;
332     $$dataref->[VALUE] = ($widget->get_active ())? '1': '0';
333     $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
334         if defined $$dataref->[IVALUE];
335 }
336
337 sub handle_int_value {
338     my ($widget, $event, $dataref) = @_;
339     $_ = $widget->get_text ();
340     s/^\s+//;
341     s/\s+$//;
342     if (/^[0-9]+$/) {
343         $$dataref->[VALUE] = $_;
344         $widget->set_text ($_);
345     }
346     else {
347         $widget->set_text ($$dataref->[VALUE]);
348     }
349     $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
350         if defined $$dataref->[IVALUE];
351 }
352
353 sub handle_string_value {
354     my ($widget, $event, $dataref) = @_;
355     $$dataref->[VALUE] = $widget->get_text ();
356     $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
357         if defined $$dataref->[IVALUE];
358 }
359
360 sub handle_nchar_value {
361     my ($widget, $event, $dataref, $minlen, $maxlen) = @_;
362     $_ = substr ($widget->get_text (), 0, $maxlen);
363     $widget->set_text ($_);
364     $$dataref->[VALUE] = $_;
365     $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
366         if defined $$dataref->[IVALUE];
367 }
368
369 sub gdk_rgba_from_str {
370     my ($str) = @_;
371     my ($rr, $gg, $bb) = (0, 0 ,0);
372     $_ = uc ($str);
373     if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) {
374         $rr = hex($1) / 256;
375         $gg = hex($2) / 256;
376         $bb = hex($3) / 256;
377     }
378     my $color = Gtk3::Gdk::RGBA->new ($rr, $gg, $bb, 1.0);
379     return $color;
380 }
381
382 sub str_from_gdk_rgba {
383     my ($color) = @_;
384     my $rr = $color->red * 256;
385     my $gg = $color->green * 256;
386     my $bb = $color->blue * 256;
387     my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb);
388     return $str;
389 }
390
391 sub handle_color_value {
392     my ($widget, $event, $dataref) = @_;
393     my $newcol = $widget->get_rgba;
394     $$dataref->[VALUE] = str_from_gdk_rgba ($newcol);
395     $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
396         if defined $$dataref->[IVALUE];
397 }
398
399 sub handle_selection_value {
400     my ($widget, $event, $dataref) = @_;
401     $$dataref->[VALUE] = $widget->get_active;
402     $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
403         if defined $$dataref->[IVALUE];
404 }
405
406 sub get_rc_filename {
407     return $CONFIGDIR . $CONFIGRC;
408 }
409
410 sub get_ac_rc_filename {
411     return $CONFIGDIR . $ACCOUNTRC;
412 }
413
414 sub get_menurc_filename {
415     return $CONFIGDIR . "menurc";
416 }
417
418 sub set_rc_filename {
419     my ($fullname) = @_;
420     my @parts = split ('/', $fullname);
421     $CONFIGRC = $parts[$#parts];
422     $parts[$#parts] = '';
423     $CONFIGDIR = join ('/', @parts);
424 }
425
426 sub log_message {
427     my ($mesg, $fatal) = @_;
428     if (defined($fatal) && $fatal eq 'die') {
429         die "$NAME: $mesg\n";
430     }
431     if ($VERBOSE) {
432         print "$NAME: $mesg\n";
433     }
434 }
435
436 sub message_dialog {
437     my ($parent, $title, $markup, $type, $buttons) = @_;
438     my $flags = [qw/modal destroy-with-parent/];
439     my $dialog = Gtk3::Dialog->new_with_buttons (
440         $title, $parent, $flags, @$buttons
441     );
442     my $label = Gtk3::Label->new;
443     $label->set_markup ($markup);
444     my $icon = undef;
445     if ($type eq 'error') {
446       $icon = Gtk3::Image->new_from_icon_name('dialog-error', 'GTK_ICON_SIZE_DIALOG');
447     } elsif ($type eq 'warning') {
448       $icon = Gtk3::Image->new_from_icon_name('dialog-warning', 'GTK_ICON_SIZE_DIALOG');
449     } elsif ($type eq 'question') {
450       $icon = Gtk3::Image->new_from_icon_name('dialog-question', 'GTK_ICON_SIZE_DIALOG');
451     }
452     my $hbox = Gtk3::Box->new ('horizontal', 5);
453     $hbox->pack_start ($icon, FALSE, FALSE, 5) if defined $icon;
454     $hbox->pack_start ($label, FALSE, FALSE, 5);
455     my $dialogbox = $dialog->get_content_area;
456     $dialogbox->add ($hbox);
457     $dialogbox->show_all;
458     return $dialog;
459 }
460
461 sub error_dialog {
462     my ($emsg) = @_;
463     my $markup = "<span weight=\"bold\" size=\"large\">" . $emsg . "</span>";
464     my $errordlg = message_dialog (
465         $main_window, $xl::s{error_title}, $markup, 'error', [ 'gtk-cancel', 0 ]
466     );
467     $errordlg->run;
468     $errordlg->destroy;
469 }
470
471 sub claws_is_running {
472     my $emsg = _('Error: seems Claws Mail is currently running, close it first.');
473     log_message ($emsg);
474     error_dialog ($emsg);
475     return FALSE;
476 }
477
478 sub check_claws_not_running {
479     return TRUE if $READONLY;
480     my $tmpdir = (defined $ENV{TMPDIR})? $ENV{TMPDIR}: '/tmp';
481     $tmpdir = '/tmp' if ($tmpdir eq '');
482     my $lockdir = "$tmpdir/claws-mail-$<";
483     -d $lockdir and do {
484         $_ = $CONFIGDIR;
485         s/\/$//;
486         my $socket = "$lockdir/" . md5_hex ($_);
487         -S $socket and return claws_is_running ();
488     };
489     return TRUE;
490 }
491
492 sub check_rc_file {
493     my ($rcfile) = @_;
494     (defined($rcfile) && -f $rcfile) or do {
495         my $emsg = _('Error: resource file for Claws Mail was not found.');
496         log_message ($emsg);
497         error_dialog ($emsg);
498         return FALSE;
499     };
500     return TRUE;
501 }
502
503 sub set_widget_hint {
504     my ($wdgt, $hint) = @_;
505     $wdgt->set_tooltip_text ($hint);
506     $wdgt->set_has_tooltip (TRUE);
507 }
508
509 sub set_widget_sens {
510     my ($wdgt, $versions) = @_;
511     my @ver = split(/,/, $versions);
512     if ($#ver == 1) {
513       $wdgt->set_sensitive (
514         version_greater_or_equal ($CLAWSV, $ver[0])
515         and version_greater_or_equal ($ver[1], $CLAWSV)
516       );
517     } else {
518         $wdgt->set_sensitive (version_greater_or_equal ($CLAWSV, $ver[0]));
519     }
520 }
521
522 # graphic element creation
523
524 sub new_hbox_spaced_pack {
525     my $hbox = Gtk3::HBox->new (FALSE);
526     foreach (@_) {
527         $hbox->pack_start ($_, FALSE, FALSE, HBOX_PAD);
528     }
529     return $hbox;
530 }
531
532 sub new_check_button_for($$$) {
533     my ($hash, $key, $vhash) = @_;
534     my $name = $$hash{$key}[NAME];
535     my $label = $$hash{$key}[LABEL];
536     #
537     my $cb = Gtk3::CheckButton->new ($label);
538     my $value = $$vhash{$name}[VALUE];
539     $value //= $$hash{$key}[CMDEF];
540     $cb->set_active ($value eq '1');
541     $cb->signal_connect (clicked => sub {
542             my ($w, $e) = @_;
543             handle_bool_value ($w, $e, \$$vhash{$name});
544         });
545     set_widget_hint ($cb, $$hash{$key}[DESC]);
546     set_widget_sens ($cb, $$hash{$key}[CMVER]);
547     #
548     return new_hbox_spaced_pack ($cb);
549 }
550
551 sub new_text_box_for_int($$$) {
552     my ($hash, $key, $vhash) = @_;
553     my $name = $$hash{$key}[NAME];
554     my $label = $$hash{$key}[LABEL];
555     my @type = split (/,/, $$hash{$key}[TYPE]);
556     push (@type, 0), push (@type, 10000) unless ($#type > 0);
557     #
558     my $gunits = undef;
559     if (ref $label eq 'ARRAY') {
560         $gunits = Gtk3::Label->new ($label->[1]);
561         $label = $label->[0];
562     }
563     my $glabel = Gtk3::Label->new ($label);
564     my $pagei = int (($type[2] - $type[1]) / 10);
565     my $gentry = Gtk3::SpinButton->new_with_range ($type[1], $type[2], $pagei);
566     my $value = $$vhash{$name}[VALUE];
567     $value //= $$hash{$key}[CMDEF];
568     $gentry->set_numeric (TRUE);
569     $gentry->set_value ($value);
570     $gentry->signal_connect('value-changed' => sub {
571             my ($w, $e) = @_;
572             handle_int_value ($w, $e, \$$vhash{$name});
573         });
574     set_widget_hint ($gentry, $$hash{$key}[DESC]);
575     set_widget_sens ($gentry, $$hash{$key}[CMVER]);
576     $glabel->set_sensitive ($gentry->get_sensitive);
577     $gunits->set_sensitive ($gentry->get_sensitive) if ($gunits);
578     #
579     return new_hbox_spaced_pack ($glabel, $gentry, $gunits) if ($gunits);
580     return new_hbox_spaced_pack ($glabel, $gentry);
581 }
582
583 sub new_text_box_for_nchar($$$) {
584     my ($hash, $key, $vhash) = @_;
585     my $name = $$hash{$key}[NAME];
586     my $label = $$hash{$key}[LABEL];
587     my @type = split (/,/, $$hash{$key}[TYPE]); # char,minlen,maxlen,width
588     my $glabel = Gtk3::Label->new ($label);
589     my $gentry = Gtk3::Entry->new ();
590     $gentry->set_max_length($type[2]) if defined ($type[2]);
591     my $width = $type[3];
592     $width //= $type[2];
593     $gentry->set_width_chars(int ($width) + 2) if defined ($width);
594     my $value = $$vhash{$name}[VALUE];
595     $value //= $$hash{$key}[CMDEF];
596     $gentry->set_text ($value);
597     $gentry->signal_connect('key-release-event' => sub {
598             my ($w, $e) = @_;
599             handle_nchar_value ($w, $e, \$$vhash{$name}, $type[1], $type[2]);
600         });
601     set_widget_hint ($gentry, $$hash{$key}[DESC]);
602     set_widget_sens ($gentry, $$hash{$key}[CMVER]);
603     $glabel->set_sensitive ($gentry->get_sensitive);
604     #
605     return new_hbox_spaced_pack ($glabel, $gentry);
606 }
607
608 sub new_color_button_for($$$) {
609     my ($hash, $key, $vhash) = @_;
610     my $name = $$hash{$key}[NAME];
611     my $label = $$hash{$key}[LABEL];
612     #
613     my $value = $$vhash{$name}[VALUE];
614     $value //= $$hash{$key}[CMDEF];
615     my $col = gdk_rgba_from_str ($value);
616     my $glabel = Gtk3::Label->new ($label);
617     my $button = Gtk3::ColorButton->new_with_rgba ($col);
618     $button->set_title ($label);
619     $button->set_relief ('none');
620     $button->signal_connect ('color-set' => sub {
621             my ($w, $e) = @_;
622             handle_color_value ($w, $e, \$$vhash{$name});
623         });
624     set_widget_hint ($button, $$hash{$key}[DESC]);
625     set_widget_sens ($button, $$hash{$key}[CMVER]);
626     $glabel->set_sensitive ($button->get_sensitive);
627     #
628     return new_hbox_spaced_pack ($button, $glabel);
629 }
630
631 sub new_selection_box_for($$$) {
632     my ($hash, $key, $vhash) = @_;
633     my $name = $$hash{$key}[NAME];
634     my $label = $$hash{$key}[LABEL];
635     #
636     my $glabel = Gtk3::Label->new ($label);
637     my $combo = Gtk3::ComboBoxText->new;
638     my @options = split (';', $$hash{$key}[TYPE]);
639     foreach my $opt (@options) {
640         my ($index, $textkey) = split ('=', $opt);
641         $combo->insert (-1, $index, $xl::s{$textkey});
642     }
643     $combo->signal_connect ('changed' => sub {
644             my ($w, $e) = @_;
645             handle_selection_value ($w, $e, \$$vhash{$name});
646         });
647     my $value = $$vhash{$name}[VALUE];
648     $value //= $$hash{$key}[CMDEF];
649     $combo->set_active ($value);
650     set_widget_hint ($combo, $$hash{$key}[DESC]);
651     set_widget_sens ($combo, $$hash{$key}[CMVER]);
652     $glabel->set_sensitive ($combo->get_sensitive);
653     #
654     return new_hbox_spaced_pack ($glabel, $combo);
655 }
656
657 # more graphic helpers
658
659 sub new_hbox_pack {
660     my $hbox = Gtk3::HBox->new (FALSE);
661     $hbox->set_border_width (PAGE_SPC);
662     foreach (@_) {
663         $hbox->pack_start ($_, FALSE, FALSE, 0);
664     }
665     return $hbox;
666 }
667
668 sub new_hbox_pack_compact {
669     my $hbox = Gtk3::HBox->new (FALSE);
670     $hbox->set_border_width (0);
671     foreach (@_) {
672         $hbox->pack_start ($_, FALSE, FALSE, 0);
673     }
674     return $hbox;
675 }
676
677 sub new_vbox_pack {
678     my $vbox = Gtk3::VBox->new (FALSE, 5);
679     $vbox->set_border_width (PAGE_SPC);
680     foreach (@_) {
681         $vbox->pack_start ($_, FALSE, FALSE, 0);
682     }
683     return $vbox;
684 }
685
686 sub new_vbox_pack_compact {
687     my $vbox = Gtk3::VBox->new (FALSE, 0);
688     $vbox->set_border_width (0);
689     foreach (@_) {
690         $vbox->pack_start ($_, FALSE, FALSE, 0);
691     }
692     return $vbox;
693 }
694
695 sub new_subpage_frame {
696     my ($box, $title, $notpacked) = @_;
697     my $frame = Gtk3::Frame->new ($title);
698     $frame->add ($box);
699     return new_vbox_pack ($frame) unless defined ($notpacked);
700     return $frame;
701 }
702
703 sub new_grid {
704     my ($border_w, $row_s, $col_s) = @_;
705     $border_w //= 10;
706     $row_s //= 10;
707     $col_s //= 10;
708     my $grid = Gtk3::Grid->new;
709     $grid->set_border_width ($border_w);
710     $grid->set_row_spacing ($row_s);
711     $grid->set_column_spacing ($col_s);
712     return $grid;
713 }
714
715 sub new_label {
716     my $text = shift;
717     $text //= '';
718     my $label = Gtk3::Label->new ($text);
719     $label->set_alignment (0, 0.5);
720     return $label;
721 }
722
723 sub new_title {
724     my $text = shift;
725     $text //= '';
726     my $label = Gtk3::Label->new ('<b>' . $text . '</b>');
727     $label->set_use_markup (TRUE);
728     $label->set_alignment (0, 0.5);
729     return $label;
730 }
731
732 sub new_grid_pack {
733     my ($width, $height, $widget) = @_;
734     my $grid = new_grid ();
735     $grid->set_column_homogeneous (TRUE);
736     for (my $i = 0; $i < $width; ++$i) {
737         for (my $j = 0; $j < $height; ++$j) {
738             my $wid = $widget->[$j]->[$i];
739             next unless defined $wid;
740             my $ww = (($i + 1 < $width) and (defined $widget->[$j]->[$i + 1]))
741                 ? 1
742                 : $width - $i;
743             if (ref $wid) {
744                 $grid->attach ($wid, $i, $j, $ww, 1);
745             } else { # not a widget
746                 if ('--' eq $wid) { # a separator
747                     $grid->attach (Gtk3::Separator->new ('horizontal'),
748                         $i, $j, $ww, 1);
749                 } else { # or a title
750                     $grid->attach (new_title ($wid),
751                         $i, $j, $ww, 1);
752                 }
753             }
754         }
755     }
756     return $grid;
757 }
758
759 # preference maps and corresponding page creation subs
760
761 %pr::oth = ( # other preferences
762     use_dlg => [
763         'addressbook_use_editaddress_dialog',
764         $xl::s{l_oth_use_dlg},
765         $xl::s{h_oth_use_dlg},
766         'bool',
767         '2.7.0',
768         '0',
769     ],
770     max_use => [
771         'cache_max_mem_usage',
772         [ $xl::s{l_oth_max_use}, $xl::s{l_oth_max_use_units} ],
773         $xl::s{h_oth_max_use},
774         'int,0,524288', # 0 Kb - 512 Mb
775         '0.7.8.36',
776         '4096',
777     ],
778     min_time => [
779         'cache_min_keep_time',
780         [ $xl::s{l_oth_min_time}, $xl::s{l_oth_min_time_units} ],
781         $xl::s{h_oth_min_time},
782         'int,0,120', # 0 minutes - 2 hours
783         '0.7.8.36',
784         '15',
785     ],
786     use_netm => [
787         'use_networkmanager',
788         $xl::s{l_oth_use_netm},
789         $xl::s{h_oth_use_netm},
790         'bool',
791         '3.3.1',
792         '1',
793     ],
794     mp_rounds => [
795         'master_passphrase_pbkdf2_rounds',
796         $xl::s{l_oth_mp_rounds},
797         $xl::s{h_oth_mp_rounds},
798         'int,50000,1000000',
799         '3.13.2.110',
800         '50000',
801     ],
802 );
803
804 sub new_other_page() {
805     return new_grid_pack (1, 12, [
806         [ _('Addressbook') ],
807         [ new_check_button_for(\%pr::oth, 'use_dlg', \%HPVALUE) ],
808         [ '--' ],
809         [ _('Memory') ],
810         [ new_text_box_for_int(\%pr::oth, 'max_use', \%HPVALUE) ],
811         [ new_text_box_for_int(\%pr::oth, 'min_time', \%HPVALUE) ],
812         [ '--' ],
813         [ _('NetworkManager') ],
814         [ new_check_button_for(\%pr::oth, 'use_netm', \%HPVALUE) ],
815         [ '--' ],
816         [ _('Master passphrase') ],
817         [ new_text_box_for_int(\%pr::oth, 'mp_rounds', \%HPVALUE) ]
818     ]);
819 }
820
821 %pr::gui = ( # gui bells and whistles
822     b_unread => [
823         'bold_unread',
824         $xl::s{l_gui_b_unread},
825         $xl::s{h_gui_b_unread},
826         'bool',
827         '0.5.3',
828         '1',
829     ],
830     no_markup => [
831         'compose_no_markup',
832         $xl::s{l_gui_no_markup},
833         $xl::s{h_gui_no_markup},
834         'bool',
835         '2.1.0.16',
836         '0',
837     ],
838     dot_lines => [
839         'enable_dotted_lines',
840         $xl::s{l_gui_dot_lines},
841         $xl::s{h_gui_dot_lines},
842         'bool',
843         '2.4.0.115,3.7.10.44',
844         '0',
845     ],
846     h_scroll => [
847         'enable_hscrollbar',
848         $xl::s{l_gui_h_scroll},
849         $xl::s{h_gui_h_scroll},
850         'bool',
851         '0.8.6.18',
852         '1',
853     ],
854     swp_from => [
855         'enable_swap_from',
856         $xl::s{l_gui_swp_from},
857         $xl::s{h_gui_swp_from},
858         'bool',
859         '1.9.13.40',
860         '0',
861     ],
862     v_scroll => [
863         'folderview_vscrollbar_policy',
864         $xl::s{l_gui_v_scroll},
865         $xl::s{h_gui_v_scroll},
866         '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
867         '0.7.8.14',
868         '0',
869     ],
870     from_show => [
871         'summary_from_show',
872         $xl::s{l_gui_from_show},
873         $xl::s{h_gui_from_show},
874         '0=l_gui_from_show_name;1=l_gui_from_show_addr;2=l_gui_from_show_both',
875         '3.7.10',
876         '0',
877     ],
878     strip_off => [
879         'stripes_color_offset',
880         $xl::s{l_gui_strip_off},
881         $xl::s{h_gui_strip_off},
882         'int,0,40000', # no idea what this number means
883         '2.4.0.186',
884         '4000',
885     ],
886     cursor_v => [
887         'textview_cursor_visible',
888         $xl::s{l_gui_cursor_v},
889         $xl::s{h_gui_cursor_v},
890         'bool',
891         '0.0.0',
892         '0',
893     ],
894     toolbar_d => [
895         'toolbar_detachable',
896         $xl::s{l_gui_toolbar_d},
897         $xl::s{h_gui_toolbar_d},
898         'bool',
899         '0.0.0',
900         '0',
901     ],
902     strip_all => [
903         'use_stripes_everywhere',
904         $xl::s{l_gui_strip_all},
905         $xl::s{h_gui_strip_all},
906         'bool',
907         '0.0.0',
908         '1',
909     ],
910     strip_sum => [
911         'use_stripes_in_summaries',
912         $xl::s{l_gui_strip_sum},
913         $xl::s{h_gui_strip_sum},
914         'bool',
915         '0.0.0',
916         '1',
917     ],
918     two_linev => [
919         'two_line_vertical',
920         $xl::s{l_gui_two_line_v},
921         $xl::s{h_gui_two_line_v},
922         'bool',
923         '3.4.0.7',
924         '0',
925     ],
926     margin_co => [
927         'show_compose_margin',
928         $xl::s{l_gui_margin_co},
929         $xl::s{h_gui_margin_co},
930         'bool',
931         '3.7.6.7',
932         '0',
933     ],
934     mview_date => [
935         'msgview_date_format',
936         $xl::s{l_gui_mview_date},
937         $xl::s{h_gui_mview_date},
938         'bool',
939         '3.7.8.42',
940         '0',
941     ],
942     zero_char => [
943         'zero_replacement_char',
944         $xl::s{l_gui_zero_char},
945         $xl::s{h_gui_zero_char},
946         'char,1,1',
947         '2.8.1.77',
948         '0',
949     ],
950     type_any => [
951         'type_any_header',
952         $xl::s{l_gui_type_any},
953         $xl::s{h_gui_type_any},
954         'bool',
955         '3.12.0.44',
956         '0',
957     ],
958     warn_send_multi => [
959         'warn_sending_many_recipients_num',
960         [ $xl::s{l_gui_warn_send_multi}, $xl::s{l_gui_warn_send_multi_units} ],
961         $xl::s{h_gui_warn_send_multi},
962         'int,0,1000',
963         '3.14.1.125',
964         '3.15.0.28',
965     ],
966     next_del => [
967         'next_on_delete',
968         $xl::s{l_gui_next_del},
969         $xl::s{h_gui_next_del},
970         'bool',
971         '3.13.0.5',
972         '0',
973     ],
974 );
975
976 sub new_gui_page() {
977     return new_grid_pack (2, 24, [
978         [ _('Coloured stripes') ],
979         [ new_check_button_for (\%pr::gui, 'strip_all', \%HPVALUE),
980             new_check_button_for (\%pr::gui, 'strip_sum', \%HPVALUE) ],
981         [ new_text_box_for_int (\%pr::gui, 'strip_off', \%HPVALUE) ],
982         [ '--' ],
983         [ _('Message List') ],
984         [ new_check_button_for (\%pr::gui, 'b_unread', \%HPVALUE),
985             new_check_button_for (\%pr::gui, 'swp_from', \%HPVALUE) ],
986         [ new_check_button_for (\%pr::gui, 'two_linev', \%HPVALUE),
987             new_check_button_for (\%pr::gui, 'next_del', \%HPVALUE) ],
988         [ new_selection_box_for (\%pr::gui, 'from_show', \%HPVALUE) ],
989         [ '--' ],
990         [ _('Message View') ],
991         [ new_check_button_for (\%pr::gui, 'cursor_v', \%HPVALUE),
992             new_check_button_for (\%pr::gui, 'mview_date', \%HPVALUE) ],
993         [ '--' ],
994         [ _('Compose window') ],
995         [ new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
996             new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE) ],
997         [ new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE) ],
998         [ new_text_box_for_int (\%pr::gui, 'warn_send_multi', \%HPVALUE) ],
999         [ '--' ],
1000         [ _('Scroll bars') ],
1001         [ new_check_button_for (\%pr::gui, 'h_scroll', \%HPVALUE) ],
1002         [ new_selection_box_for (\%pr::gui, 'v_scroll', \%HPVALUE) ],
1003         [ '--' ],
1004         [ _('Other') ],
1005         [ new_check_button_for (\%pr::gui, 'dot_lines', \%HPVALUE),
1006             new_check_button_for (\%pr::gui, 'toolbar_d', \%HPVALUE) ],
1007         [ new_text_box_for_nchar (\%pr::gui, 'zero_char', \%HPVALUE) ]
1008     ]);
1009 }
1010
1011 %pr::beh = ( # tweak some behaviour
1012     hover_t => [
1013         'hover_timeout',
1014         [ $xl::s{l_beh_hover_t}, $xl::s{l_beh_hover_t_units} ],
1015         $xl::s{h_beh_hover_t},
1016         'int,100,3000', # 0.1 seconds - 3 seconds
1017         '0.0.0',
1018         '500',
1019     ],
1020     dangerous => [
1021         'live_dangerously',
1022         $xl::s{l_beh_dangerous},
1023         $xl::s{h_beh_dangerous},
1024         'bool',
1025         '0.0.0',
1026         '0',
1027     ],
1028     flowed => [
1029         'respect_flowed_format',
1030         $xl::s{l_beh_flowed},
1031         $xl::s{h_beh_flowed},
1032         'bool',
1033         '0.0.0',
1034         '0',
1035     ],
1036     parts_rw => [
1037         'save_parts_readwrite',
1038         $xl::s{l_beh_parts_rw},
1039         $xl::s{h_beh_parts_rw},
1040         'bool',
1041         '0.0.0',
1042         '0',
1043     ],
1044     skip_ssl => [
1045         'skip_ssl_cert_check',
1046         $xl::s{l_beh_skip_ssl},
1047         $xl::s{h_beh_skip_ssl},
1048         'bool',
1049         '0.0.0',
1050         '0',
1051     ],
1052     up_step => [
1053         'statusbar_update_step',
1054         [ $xl::s{l_beh_up_step}, $xl::s{l_beh_up_step_units} ],
1055         $xl::s{h_beh_up_step},
1056         'int,1,200', # 1 item - 200 items
1057         '0.0.0',
1058         '10',
1059     ],
1060     thread_a => [
1061         'thread_by_subject_max_age',
1062         [ $xl::s{l_beh_thread_a}, $xl::s{l_beh_thread_a_units} ],
1063         $xl::s{h_beh_thread_a},
1064         'int,1,30', # 1 day - 30 days
1065         '0.0.0',
1066         '10',
1067     ],
1068     unsafe_ssl => [
1069         'unsafe_ssl_certs',
1070         $xl::s{l_beh_unsafe_ssl},
1071         $xl::s{h_beh_unsafe_ssl},
1072         'bool',
1073         '0.0.0',
1074         '0',
1075     ],
1076     use_utf8 => [
1077         'utf8_instead_of_locale_for_broken_mail',
1078         $xl::s{l_beh_use_utf8},
1079         $xl::s{h_beh_use_utf8},
1080         'bool',
1081         '1.9.14.49',
1082         '0',
1083     ],
1084     warn_dnd => [
1085         'warn_dnd',
1086         $xl::s{l_beh_warn_dnd},
1087         $xl::s{h_beh_warn_dnd},
1088         'bool',
1089         '0.0.0',
1090         '1',
1091     ],
1092     out_ascii => [
1093         'outgoing_fallback_to_ascii',
1094         $xl::s{l_beh_out_ascii},
1095         $xl::s{h_beh_out_ascii},
1096         'bool',
1097         '3.4.0.37',
1098         '1',
1099     ],
1100     pp_unsel => [
1101         'primary_paste_unselects',
1102         $xl::s{l_beh_pp_unsel},
1103         $xl::s{h_beh_pp_unsel},
1104         'bool',
1105         '3.6.1.35',
1106         '0',
1107     ],
1108     inline_at => [
1109         'show_inline_attachments',
1110         $xl::s{l_beh_inline_at},
1111         $xl::s{h_beh_inline_at},
1112         'bool',
1113         '3.7.8.48',
1114         '1',
1115     ],
1116     addr_swc => [
1117         'address_search_wildcard',
1118         $xl::s{l_beh_addr_swc},
1119         $xl::s{h_beh_addr_swc},
1120         'bool',
1121         '3.9.3.18',
1122         '0',
1123     ],
1124     fold_swc => [
1125         'folder_search_wildcard',
1126         $xl::s{l_beh_fold_swc},
1127         $xl::s{h_beh_fold_swc},
1128         'bool',
1129         '3.9.3.18',
1130         '0',
1131     ],
1132     rewrite_ff => [
1133         'rewrite_first_from',
1134         $xl::s{l_beh_rewrite_ff},
1135         $xl::s{h_beh_rewrite_ff},
1136         'bool',
1137         '3.14.0.94',
1138         '0',
1139     ],
1140 );
1141
1142 sub new_behaviour_page() {
1143     my $bf = Gtk3::VBox->new (FALSE, 5);
1144     $bf->set_border_width (PAGE_SPC);
1145
1146     my $tb_up_step = new_text_box_for_int (\%pr::beh, 'up_step', \%HPVALUE);
1147     my $tb_thread_a = new_text_box_for_int (\%pr::beh, 'thread_a', \%HPVALUE);
1148
1149     $bf->pack_start (new_subpage_frame (
1150                          new_vbox_pack (
1151                              new_text_box_for_int (\%pr::beh, 'hover_t', \%HPVALUE),
1152                              new_check_button_for (\%pr::beh, 'warn_dnd', \%HPVALUE)),
1153                          $xl::s{dnd_frame}, 'not-packed'),
1154                      FALSE, FALSE, FRAME_SPC);
1155     $bf->pack_start (new_subpage_frame (
1156                          new_hbox_pack (
1157                              new_check_button_for (\%pr::beh, 'skip_ssl', \%HPVALUE),
1158                              new_check_button_for (\%pr::beh, 'unsafe_ssl', \%HPVALUE)),
1159                          $xl::s{ssl_frame}, 'not-packed'),
1160                      FALSE, FALSE, FRAME_SPC);
1161     $bf->pack_start ($tb_up_step, FALSE, FALSE, 0);
1162     $bf->pack_start ($tb_thread_a, FALSE, FALSE, 0);
1163     $bf->pack_start (new_subpage_frame (
1164                          new_vbox_pack (
1165                              new_check_button_for (\%pr::beh, 'flowed', \%HPVALUE),
1166                              new_check_button_for (\%pr::beh, 'parts_rw', \%HPVALUE),
1167                              new_check_button_for (\%pr::beh, 'use_utf8', \%HPVALUE),
1168                              new_check_button_for (\%pr::beh, 'dangerous', \%HPVALUE),
1169                              new_check_button_for (\%pr::beh, 'out_ascii', \%HPVALUE),
1170                              new_check_button_for (\%pr::beh, 'pp_unsel', \%HPVALUE),
1171                              new_check_button_for (\%pr::beh, 'inline_at', \%HPVALUE),
1172                              new_check_button_for (\%pr::beh, 'rewrite_ff', \%HPVALUE)),
1173                          $xl::s{msgs_frame}, 'not-packed'),
1174                      FALSE, FALSE, FRAME_SPC);
1175     $bf->pack_start (new_subpage_frame (
1176                          new_vbox_pack (
1177                              new_check_button_for (\%pr::beh, 'addr_swc', \%HPVALUE),
1178                              new_check_button_for (\%pr::beh, 'fold_swc', \%HPVALUE)),
1179                          $xl::s{swc_frame}, 'not-packed'),
1180                      FALSE, FALSE, FRAME_SPC);
1181     return $bf;
1182 }
1183
1184 %pr::col = ( # a variety of colours
1185     emphasis => [
1186         'emphasis_color',
1187         $xl::s{l_col_emphasis},
1188         $xl::s{h_col_emphasis},
1189         'color',
1190         '0.0.0',
1191         '#0000cf',
1192     ],
1193     log_err => [
1194         'log_error_color',
1195         $xl::s{l_col_log_err},
1196         $xl::s{h_col_log_err},
1197         'color',
1198         '0.0.0',
1199         '#af0000',
1200     ],
1201     log_in => [
1202         'log_in_color',
1203         $xl::s{l_col_log_in},
1204         $xl::s{h_col_log_in},
1205         'color',
1206         '0.0.0',
1207         '#000000',
1208     ],
1209     log_msg => [
1210         'log_msg_color',
1211         $xl::s{l_col_log_msg},
1212         $xl::s{h_col_log_msg},
1213         'color',
1214         '0.0.0',
1215         '#00af00',
1216     ],
1217     log_out => [
1218         'log_out_color',
1219         $xl::s{l_col_log_out},
1220         $xl::s{h_col_log_out},
1221         'color',
1222         '0.0.0',
1223         '#0000ef',
1224     ],
1225     log_warn => [
1226         'log_warn_color',
1227         $xl::s{l_col_log_warn},
1228         $xl::s{h_col_log_warn},
1229         'color',
1230         '0.0.0',
1231         '#af0000',
1232     ],
1233     diff_add => [
1234         'diff_added_color',
1235         $xl::s{l_col_diff_add},
1236         $xl::s{h_col_diff_add},
1237         'color',
1238         '3.8.0.54',
1239         '#008b8b',
1240     ],
1241     diff_del => [
1242         'diff_deleted_color',
1243         $xl::s{l_col_diff_del},
1244         $xl::s{h_col_diff_del},
1245         'color',
1246         '3.8.0.54',
1247         '#6a5acd',
1248     ],
1249     diff_hunk => [
1250         'diff_hunk_color',
1251         $xl::s{l_col_diff_hunk},
1252         $xl::s{h_col_diff_hunk},
1253         'color',
1254         '3.8.0.54',
1255         '#a52a2a',
1256     ],
1257     tags_bg => [
1258         'tags_bgcolor',
1259         $xl::s{l_col_tags_bg},
1260         $xl::s{h_col_tags_bg},
1261         'color',
1262         '3.14.1.31',
1263         '#f5f6be',
1264     ],
1265     tags_text => [
1266         'tags_color',
1267         $xl::s{l_col_tags_text},
1268         $xl::s{h_col_tags_text},
1269         'color',
1270         '3.14.1.31',
1271         '#000000',
1272     ],
1273     default_header_bg => [
1274         'default_header_bgcolor',
1275         $xl::s{l_col_default_header_bg},
1276         $xl::s{h_col_default_header_bg},
1277         'color',
1278         '3.14.1.31',
1279         '#f5f6be',
1280     ],
1281     default_header_text => [
1282         'default_header_color',
1283         $xl::s{l_col_default_header_text},
1284         $xl::s{h_col_default_header_text},
1285         'color',
1286         '3.14.1.31',
1287         '#000000',
1288     ],
1289     qs_active_bg => [
1290         'qs_active_bgcolor',
1291         $xl::s{l_col_qs_active_bg},
1292         $xl::s{h_col_qs_active_bg},
1293         'color',
1294         '3.14.1.31',
1295         '#f5f6be',
1296     ],
1297     qs_active_text => [
1298         'qs_active_color',
1299         $xl::s{l_col_qs_active_text},
1300         $xl::s{h_col_qs_active_text},
1301         'color',
1302         '3.14.1.31',
1303         '#000000',
1304     ],
1305     qs_error_bg => [
1306         'qs_error_bgcolor',
1307         $xl::s{l_col_qs_error_bg},
1308         $xl::s{h_col_qs_error_bg},
1309         'color',
1310         '3.14.1.31',
1311         '#ff7070',
1312     ],
1313     qs_error_text => [
1314         'qs_error_color',
1315         $xl::s{l_col_qs_error_text},
1316         $xl::s{h_col_qs_error_text},
1317         'color',
1318         '3.14.1.31',
1319         '#000000',
1320     ],
1321 );
1322
1323 sub new_colours_page() {
1324     return new_grid_pack (3, 18, [
1325         [ _('Message View') ],
1326         [ new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE) ],
1327         [ new_color_button_for (\%pr::col, 'tags_text', \%HPVALUE) ,
1328             new_color_button_for (\%pr::col, 'tags_bg', \%HPVALUE) ],
1329         [ '--' ],
1330         [ _('Log window') ],
1331         [ new_color_button_for (\%pr::col, 'log_err', \%HPVALUE) ,
1332             new_color_button_for (\%pr::col, 'log_in', \%HPVALUE) ],
1333         [ new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE) ,
1334             new_color_button_for (\%pr::col, 'log_out', \%HPVALUE) ],
1335         [ new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE) ],
1336         [ '--' ],
1337         [ _('Viewing patches') ],
1338         [ new_color_button_for (\%pr::col, 'diff_add', \%HPVALUE) ,
1339             new_color_button_for (\%pr::col, 'diff_del', \%HPVALUE) ,
1340             new_color_button_for (\%pr::col, 'diff_hunk', \%HPVALUE) ],
1341         [ '--' ],
1342         [ _('Compose window') ],
1343         [ new_color_button_for (\%pr::col, 'default_header_text', \%HPVALUE) ,
1344             new_color_button_for (\%pr::col, 'default_header_bg', \%HPVALUE) ],
1345         [ '--' ],
1346         [ _('Quick search') ],
1347         [ new_color_button_for (\%pr::col, 'qs_active_text', \%HPVALUE) ,
1348             new_color_button_for (\%pr::col, 'qs_active_bg', \%HPVALUE) ],
1349         [ new_color_button_for (\%pr::col, 'qs_error_text', \%HPVALUE) ,
1350             new_color_button_for (\%pr::col, 'qs_error_bg', \%HPVALUE) ]
1351     ]);
1352 }
1353
1354 %pr::win = ( # tweak window positions and/or sizes
1355     main_x => [
1356         'mainwin_x',
1357         $xl::s{l_win_x},
1358         $xl::s{h_win_x},
1359         'int,0,3000', # 0 pixels - 3000 pixels
1360         '0.0.0',
1361         '16',
1362     ],
1363     main_y => [
1364         'mainwin_y',
1365         $xl::s{l_win_y},
1366         $xl::s{h_win_y},
1367         'int,0,3000', # 0 pixels - 3000 pixels
1368         '0.0.0',
1369         '16',
1370     ],
1371     main_w => [
1372         'mainwin_width',
1373         $xl::s{l_win_w},
1374         $xl::s{h_win_w},
1375         'int,0,3000', # 0 pixels - 3000 pixels
1376         '0.0.0',
1377         '779',
1378     ],
1379     main_h => [
1380         'mainwin_height',
1381         $xl::s{l_win_h},
1382         $xl::s{h_win_h},
1383         'int,0,3000', # 0 pixels - 3000 pixels
1384         '0.0.0',
1385         '568',
1386     ],
1387     main_mx => [
1388         'mainwin_maximised',
1389         $xl::s{l_win_main_mx},
1390         $xl::s{h_win_main_mx},
1391         'bool',
1392         '0.0.0',
1393         '0',
1394     ],
1395     main_fs => [
1396         'mainwin_fullscreen',
1397         $xl::s{l_win_main_fs},
1398         $xl::s{h_win_main_fs},
1399         'bool',
1400         '0.0.0',
1401         '0',
1402     ],
1403     msgs_x => [
1404         'main_messagewin_x',
1405         $xl::s{l_win_x},
1406         $xl::s{h_win_x},
1407         'int,0,3000', # 0 pixels - 3000 pixels
1408         '0.0.0',
1409         '256',
1410     ],
1411     msgs_y => [
1412         'main_messagewin_y',
1413         $xl::s{l_win_y},
1414         $xl::s{h_win_y},
1415         'int,0,3000', # 0 pixels - 3000 pixels
1416         '0.0.0',
1417         '210',
1418     ],
1419     msgs_w => [
1420         'messagewin_width',
1421         $xl::s{l_win_w},
1422         $xl::s{h_win_w},
1423         'int,0,3000', # 0 pixels - 3000 pixels
1424         '0.0.0',
1425         '600',
1426     ],
1427     msgs_h => [
1428         'messagewin_height',
1429         $xl::s{l_win_h},
1430         $xl::s{h_win_h},
1431         'int,0,3000', # 0 pixels - 3000 pixels
1432         '0.0.0',
1433         '540',
1434     ],
1435     send_w => [
1436         'sendwin_width',
1437         $xl::s{l_win_w},
1438         $xl::s{h_win_w},
1439         'int,0,3000', # 0 pixels - 3000 pixels
1440         '0.0.0',
1441         '460',
1442     ],
1443     send_h => [
1444         'sendwin_height',
1445         $xl::s{l_win_h},
1446         $xl::s{h_win_h},
1447         'int,0,3000', # 0 pixels - 3000 pixels
1448         '0.0.0',
1449         '-1',
1450     ],
1451     recv_w => [
1452         'receivewin_width',
1453         $xl::s{l_win_w},
1454         $xl::s{h_win_w},
1455         'int,0,3000', # 0 pixels - 3000 pixels
1456         '0.0.0',
1457         '460',
1458     ],
1459     recv_h => [
1460         'receivewin_height',
1461         $xl::s{l_win_h},
1462         $xl::s{h_win_h},
1463         'int,0,3000', # 0 pixels - 3000 pixels
1464         '0.0.0',
1465         '-1',
1466     ],
1467     fold_x => [
1468         'folderwin_x',
1469         $xl::s{l_win_x},
1470         $xl::s{h_win_x},
1471         'int,0,3000', # 0 pixels - 3000 pixels
1472         '0.0.0',
1473         '16',
1474     ],
1475     fold_y => [
1476         'folderwin_y',
1477         $xl::s{l_win_y},
1478         $xl::s{h_win_y},
1479         'int,0,3000', # 0 pixels - 3000 pixels
1480         '0.0.0',
1481         '16',
1482     ],
1483     fold_w => [
1484         'folderitemwin_width',
1485         $xl::s{l_win_w},
1486         $xl::s{h_win_w},
1487         'int,0,3000', # 0 pixels - 3000 pixels
1488         '0.0.0',
1489         '500',
1490     ],
1491     fold_h => [
1492         'folderitemwin_height',
1493         $xl::s{l_win_h},
1494         $xl::s{h_win_h},
1495         'int,0,3000', # 0 pixels - 3000 pixels
1496         '0.0.0',
1497         '-1',
1498     ],
1499     fsel_w => [
1500         'folderselwin_width',
1501         $xl::s{l_win_w},
1502         $xl::s{h_win_w},
1503         'int,0,3000', # 0 pixels - 3000 pixels
1504         '0.0.0',
1505         '300',
1506     ],
1507     fsel_h => [
1508         'folderselwin_height',
1509         $xl::s{l_win_h},
1510         $xl::s{h_win_h},
1511         'int,0,3000', # 0 pixels - 3000 pixels
1512         '0.0.0',
1513         '-1',
1514     ],
1515     sour_w => [
1516         'sourcewin_width',
1517         $xl::s{l_win_w},
1518         $xl::s{h_win_w},
1519         'int,0,3000', # 0 pixels - 3000 pixels
1520         '0.0.0',
1521         '600',
1522     ],
1523     sour_h => [
1524         'sourcewin_height',
1525         $xl::s{l_win_h},
1526         $xl::s{h_win_h},
1527         'int,0,3000', # 0 pixels - 3000 pixels
1528         '0.0.0',
1529         '500',
1530     ],
1531     addr_w => [
1532         'addressbookwin_width',
1533         $xl::s{l_win_w},
1534         $xl::s{h_win_w},
1535         'int,0,3000', # 0 pixels - 3000 pixels
1536         '0.0.0',
1537         '520',
1538     ],
1539     addr_h => [
1540         'addressbookwin_height',
1541         $xl::s{l_win_h},
1542         $xl::s{h_win_h},
1543         'int,0,3000', # 0 pixels - 3000 pixels
1544         '0.0.0',
1545         '-1',
1546     ],
1547     adep_w => [
1548         'addressbookeditpersonwin_width',
1549         $xl::s{l_win_w},
1550         $xl::s{h_win_w},
1551         'int,0,3000', # 0 pixels - 3000 pixels
1552         '0.0.0',
1553         '640',
1554     ],
1555     adep_h => [
1556         'addressbookeditpersonwin_height',
1557         $xl::s{l_win_h},
1558         $xl::s{h_win_h},
1559         'int,0,3000', # 0 pixels - 3000 pixels
1560         '0.0.0',
1561         '320',
1562     ],
1563     adeg_w => [
1564         'addressbookeditgroupwin_width',
1565         $xl::s{l_win_w},
1566         $xl::s{h_win_w},
1567         'int,0,3000', # 0 pixels - 3000 pixels
1568         '0.0.0',
1569         '580',
1570     ],
1571     adeg_h => [
1572         'addressbookeditgroupwin_height',
1573         $xl::s{l_win_h},
1574         $xl::s{h_win_h},
1575         'int,0,3000', # 0 pixels - 3000 pixels
1576         '0.0.0',
1577         '340',
1578     ],
1579     adda_w => [
1580         'addressaddwin_width',
1581         $xl::s{l_win_w},
1582         $xl::s{h_win_w},
1583         'int,0,3000', # 0 pixels - 3000 pixels
1584         '0.0.0',
1585         '300',
1586     ],
1587     adda_h => [
1588         'addressaddwin_height',
1589         $xl::s{l_win_h},
1590         $xl::s{h_win_h},
1591         'int,0,3000', # 0 pixels - 3000 pixels
1592         '0.0.0',
1593         '-1',
1594     ],
1595     addf_w => [
1596         'addressbook_folderselwin_width',
1597         $xl::s{l_win_w},
1598         $xl::s{h_win_w},
1599         'int,0,3000', # 0 pixels - 3000 pixels
1600         '0.0.0',
1601         '300',
1602     ],
1603     addf_h => [
1604         'addressbook_folderselwin_height',
1605         $xl::s{l_win_h},
1606         $xl::s{h_win_h},
1607         'int,0,3000', # 0 pixels - 3000 pixels
1608         '0.0.0',
1609         '-1',
1610     ],
1611     acce_w => [
1612         'editaccountwin_width',
1613         $xl::s{l_win_w},
1614         $xl::s{h_win_w},
1615         'int,0,3000', # 0 pixels - 3000 pixels
1616         '0.0.0',
1617         '500',
1618     ],
1619     acce_h => [
1620         'editaccountwin_height',
1621         $xl::s{l_win_h},
1622         $xl::s{h_win_h},
1623         'int,0,3000', # 0 pixels - 3000 pixels
1624         '0.0.0',
1625         '-1',
1626     ],
1627     acco_w => [
1628         'accountswin_width',
1629         $xl::s{l_win_w},
1630         $xl::s{h_win_w},
1631         'int,0,3000', # 0 pixels - 3000 pixels
1632         '0.0.0',
1633         '500',
1634     ],
1635     acco_h => [
1636         'accountswin_height',
1637         $xl::s{l_win_h},
1638         $xl::s{h_win_h},
1639         'int,0,3000', # 0 pixels - 3000 pixels
1640         '0.0.0',
1641         '-1',
1642     ],
1643     filt_w => [
1644         'filteringwin_width',
1645         $xl::s{l_win_w},
1646         $xl::s{h_win_w},
1647         'int,0,3000', # 0 pixels - 3000 pixels
1648         '0.0.0',
1649         '500',
1650     ],
1651     filt_h => [
1652         'filteringwin_height',
1653         $xl::s{l_win_h},
1654         $xl::s{h_win_h},
1655         'int,0,3000', # 0 pixels - 3000 pixels
1656         '0.0.0',
1657         '-1',
1658     ],
1659     fila_w => [
1660         'filteringactionwin_width',
1661         $xl::s{l_win_w},
1662         $xl::s{h_win_w},
1663         'int,0,3000', # 0 pixels - 3000 pixels
1664         '0.0.0',
1665         '490',
1666     ],
1667     fila_h => [
1668         'filteringactionwin_height',
1669         $xl::s{l_win_h},
1670         $xl::s{h_win_h},
1671         'int,0,3000', # 0 pixels - 3000 pixels
1672         '0.0.0',
1673         '-1',
1674     ],
1675     fild_w => [
1676         'filtering_debugwin_width',
1677         $xl::s{l_win_w},
1678         $xl::s{h_win_w},
1679         'int,0,3000', # 0 pixels - 3000 pixels
1680         '0.0.0',
1681         '600',
1682     ],
1683     fild_h => [
1684         'filtering_debugwin_height',
1685         $xl::s{l_win_h},
1686         $xl::s{h_win_h},
1687         'int,0,3000', # 0 pixels - 3000 pixels
1688         '0.0.0',
1689         '-1',
1690     ],
1691     matc_w => [
1692         'matcherwin_width',
1693         $xl::s{l_win_w},
1694         $xl::s{h_win_w},
1695         'int,0,3000', # 0 pixels - 3000 pixels
1696         '0.0.0',
1697         '520',
1698     ],
1699     matc_h => [
1700         'matcherwin_height',
1701         $xl::s{l_win_h},
1702         $xl::s{h_win_h},
1703         'int,0,3000', # 0 pixels - 3000 pixels
1704         '0.0.0',
1705         '-1',
1706     ],
1707     pref_w => [
1708         'prefswin_width',
1709         $xl::s{l_win_w},
1710         $xl::s{h_win_w},
1711         'int,0,3000', # 0 pixels - 3000 pixels
1712         '0.0.0',
1713         '600',
1714     ],
1715     pref_h => [
1716         'prefswin_height',
1717         $xl::s{l_win_h},
1718         $xl::s{h_win_h},
1719         'int,0,3000', # 0 pixels - 3000 pixels
1720         '0.0.0',
1721         '-1',
1722     ],
1723     temp_w => [
1724         'templateswin_width',
1725         $xl::s{l_win_w},
1726         $xl::s{h_win_w},
1727         'int,0,3000', # 0 pixels - 3000 pixels
1728         '0.0.0',
1729         '480',
1730     ],
1731     temp_h => [
1732         'templateswin_height',
1733         $xl::s{l_win_h},
1734         $xl::s{h_win_h},
1735         'int,0,3000', # 0 pixels - 3000 pixels
1736         '0.0.0',
1737         '-1',
1738     ],
1739     acti_w => [
1740         'actionswin_width',
1741         $xl::s{l_win_w},
1742         $xl::s{h_win_w},
1743         'int,0,3000', # 0 pixels - 3000 pixels
1744         '0.0.0',
1745         '486',
1746     ],
1747     acti_h => [
1748         'actionswin_height',
1749         $xl::s{l_win_h},
1750         $xl::s{h_win_h},
1751         'int,0,3000', # 0 pixels - 3000 pixels
1752         '0.0.0',
1753         '-1',
1754     ],
1755     acio_w => [
1756         'actionsiodialog_width',
1757         $xl::s{l_win_w},
1758         $xl::s{h_win_w},
1759         'int,0,3000', # 0 pixels - 3000 pixels
1760         '0.0.0',
1761         '582',
1762     ],
1763     acio_h => [
1764         'actionsiodialog_height',
1765         $xl::s{l_win_h},
1766         $xl::s{h_win_h},
1767         'int,0,3000', # 0 pixels - 3000 pixels
1768         '0.0.0',
1769         '310',
1770     ],
1771     tags_w => [
1772         'tagswin_width',
1773         $xl::s{l_win_w},
1774         $xl::s{h_win_w},
1775         'int,0,3000', # 0 pixels - 3000 pixels
1776         '0.0.0',
1777         '486',
1778     ],
1779     tags_h => [
1780         'tagswin_height',
1781         $xl::s{l_win_h},
1782         $xl::s{h_win_h},
1783         'int,0,3000', # 0 pixels - 3000 pixels
1784         '0.0.0',
1785         '-1',
1786     ],
1787     plug_w => [
1788         'pluginswin_width',
1789         $xl::s{l_win_w},
1790         $xl::s{h_win_w},
1791         'int,0,3000', # 0 pixels - 3000 pixels
1792         '0.0.0',
1793         '-1',
1794     ],
1795     plug_h => [
1796         'pluginswin_height',
1797         $xl::s{l_win_h},
1798         $xl::s{h_win_h},
1799         'int,0,3000', # 0 pixels - 3000 pixels
1800         '0.0.0',
1801         '-1',
1802     ],
1803     logw_w => [
1804         'logwin_width',
1805         $xl::s{l_win_w},
1806         $xl::s{h_win_w},
1807         'int,0,3000', # 0 pixels - 3000 pixels
1808         '0.0.0',
1809         '520',
1810     ],
1811     logw_h => [
1812         'logwin_height',
1813         $xl::s{l_win_h},
1814         $xl::s{h_win_h},
1815         'int,0,3000', # 0 pixels - 3000 pixels
1816         '0.0.0',
1817         '-1',
1818     ],
1819     prin_w => [
1820         'print_previewwin_width',
1821         $xl::s{l_win_w},
1822         $xl::s{h_win_w},
1823         'int,0,3000', # 0 pixels - 3000 pixels
1824         '0.0.0',
1825         '600',
1826     ],
1827     prin_h => [
1828         'print_previewwin_height',
1829         $xl::s{l_win_h},
1830         $xl::s{h_win_h},
1831         'int,0,3000', # 0 pixels - 3000 pixels
1832         '0.0.0',
1833         '-1',
1834     ],
1835 );
1836
1837 sub new_winpos_subpage_main() {
1838     return new_grid_pack (3, 7, [
1839         [ _('Main window'), undef ],
1840         [ new_text_box_for_int (\%pr::win, 'main_x', \%HPVALUE), undef ],
1841         [ new_text_box_for_int (\%pr::win, 'main_y', \%HPVALUE), undef ],
1842         [ new_text_box_for_int (\%pr::win, 'main_w', \%HPVALUE),
1843             new_text_box_for_int (\%pr::win, 'main_h', \%HPVALUE) ],
1844         [ new_check_button_for (\%pr::win, 'main_fs', \%HPVALUE), undef ],
1845         [ new_check_button_for (\%pr::win, 'main_mx', \%HPVALUE), undef ]
1846     ]);
1847 }
1848
1849 sub new_winpos_subpage_msgs() {
1850     return new_grid_pack (3, 4, [
1851         [ _('Message window') ],
1852         [ new_text_box_for_int (\%pr::win, 'msgs_x', \%HPVALUE) ],
1853         [ new_text_box_for_int (\%pr::win, 'msgs_y', \%HPVALUE) ],
1854         [ new_text_box_for_int (\%pr::win, 'msgs_w', \%HPVALUE),
1855             new_text_box_for_int (\%pr::win, 'msgs_h', \%HPVALUE) ]
1856     ]);
1857 }
1858
1859 sub new_winpos_subpage_sendrecv() {
1860     return new_grid_pack (3, 5, [
1861         [ _('Send window') ],
1862         [ new_text_box_for_int (\%pr::win, 'send_w', \%HPVALUE),
1863             new_text_box_for_int (\%pr::win, 'send_h', \%HPVALUE) ],
1864         [ '--' ],
1865         [ _('Receive window') ],
1866         [ new_text_box_for_int (\%pr::win, 'recv_w', \%HPVALUE),
1867             new_text_box_for_int (\%pr::win, 'recv_h', \%HPVALUE) ]
1868     ]);
1869 }
1870
1871 sub new_winpos_subpage_fold() {
1872     return new_grid_pack (3, 7, [
1873         [ _('Folder window') ],
1874         [ new_text_box_for_int (\%pr::win, 'fold_x', \%HPVALUE) ],
1875         [ new_text_box_for_int (\%pr::win, 'fold_y', \%HPVALUE) ],
1876         [ new_text_box_for_int (\%pr::win, 'fold_w', \%HPVALUE),
1877             new_text_box_for_int (\%pr::win, 'fold_h', \%HPVALUE) ],
1878         [ '--' ],
1879         [ _('Folder selection window') ],
1880         [ new_text_box_for_int (\%pr::win, 'fsel_w', \%HPVALUE),
1881             new_text_box_for_int (\%pr::win, 'fsel_h', \%HPVALUE) ]
1882     ]);
1883 }
1884
1885 sub new_winpos_subpage_addrbook() {
1886     return new_grid_pack (3, 14, [
1887         [ _('Addressbook main window') ],
1888         [ new_text_box_for_int (\%pr::win, 'addr_w', \%HPVALUE),
1889             new_text_box_for_int (\%pr::win, 'addr_h', \%HPVALUE) ],
1890         [ '--' ],
1891         [ _('Edit person window') ],
1892         [ new_text_box_for_int (\%pr::win, 'adep_w', \%HPVALUE),
1893             new_text_box_for_int (\%pr::win, 'adep_h', \%HPVALUE) ],
1894         [ '--' ],
1895         [ _('Edit group window') ],
1896         [ new_text_box_for_int (\%pr::win, 'adeg_w', \%HPVALUE),
1897             new_text_box_for_int (\%pr::win, 'adeg_h', \%HPVALUE) ],
1898         [ '--' ],
1899         [ _('Add address window') ],
1900         [ new_text_box_for_int (\%pr::win, 'adda_w', \%HPVALUE),
1901             new_text_box_for_int (\%pr::win, 'adda_h', \%HPVALUE) ],
1902         [ '--' ],
1903         [ _('Folder select window') ],
1904         [ new_text_box_for_int (\%pr::win, 'addf_w', \%HPVALUE),
1905             new_text_box_for_int (\%pr::win, 'addf_h', \%HPVALUE) ]
1906     ]);
1907 }
1908
1909 sub new_winpos_subpage_accounts() {
1910     return new_grid_pack (3, 5, [
1911         [ _('Accounts window') ],
1912         [ new_text_box_for_int (\%pr::win, 'acco_w', \%HPVALUE),
1913             new_text_box_for_int (\%pr::win, 'acco_h', \%HPVALUE) ],
1914         [ '--' ],
1915         [ _('Edit account window') ],
1916         [ new_text_box_for_int (\%pr::win, 'acce_w', \%HPVALUE),
1917             new_text_box_for_int (\%pr::win, 'acce_h', \%HPVALUE) ]
1918     ]);
1919 }
1920
1921 sub new_winpos_subpage_filtering() {
1922     return new_grid_pack (3, 11, [
1923         [ _('Filtering window') ],
1924         [ new_text_box_for_int (\%pr::win, 'filt_w', \%HPVALUE),
1925             new_text_box_for_int (\%pr::win, 'filt_h', \%HPVALUE) ],
1926         [ '--' ],
1927         [ _('Filtering actions window') ],
1928         [ new_text_box_for_int (\%pr::win, 'fila_w', \%HPVALUE),
1929             new_text_box_for_int (\%pr::win, 'fila_h', \%HPVALUE) ],
1930         [ '--' ],
1931         [ _('Filtering debug window') ],
1932         [ new_text_box_for_int (\%pr::win, 'fild_w', \%HPVALUE),
1933             new_text_box_for_int (\%pr::win, 'fild_h', \%HPVALUE) ],
1934         [ '--' ],
1935         [ ('Matcher window') ],
1936         [ new_text_box_for_int (\%pr::win, 'matc_w', \%HPVALUE),
1937             new_text_box_for_int (\%pr::win, 'matc_h', \%HPVALUE) ]
1938
1939     ]);
1940 }
1941
1942 sub new_winpos_subpage_useractions() {
1943     return new_grid_pack (3, 5, [
1944         [ _('User Actions prefs window') ],
1945         [ new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
1946             new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE) ],
1947         [ '--' ],
1948         [ _('User Actions I/O window') ],
1949         [ new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
1950             new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE) ]
1951     ]);
1952 }
1953
1954 sub new_winpos_subpage_prefs() {
1955     return new_grid_pack (3, 11, [
1956         [ _('Preferences window') ],
1957         [ new_text_box_for_int (\%pr::win, 'pref_w', \%HPVALUE),
1958             new_text_box_for_int (\%pr::win, 'pref_h', \%HPVALUE) ],
1959         [ '--' ],
1960         [ _('Templates window') ],
1961         [ new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
1962             new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE) ],
1963         [ '--' ],
1964         [ _('Tags window') ],
1965         [ new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
1966             new_text_box_for_int (\%pr::win, 'tags_h', \%HPVALUE) ],
1967         [ '--' ],
1968         [ _('Plugins window') ],
1969         [ new_text_box_for_int (\%pr::win, 'plug_w', \%HPVALUE),
1970             new_text_box_for_int (\%pr::win, 'plug_h', \%HPVALUE) ]
1971     ]);
1972 }
1973
1974 sub new_winpos_subpage_misc() {
1975     return new_grid_pack (3, 8, [
1976         [ _('Log window') ],
1977         [ new_text_box_for_int (\%pr::win, 'logw_w', \%HPVALUE),
1978             new_text_box_for_int (\%pr::win, 'logw_h', \%HPVALUE) ],
1979         [ '--' ],
1980         [ _('Print preview window') ],
1981         [ new_text_box_for_int (\%pr::win, 'prin_w', \%HPVALUE),
1982             new_text_box_for_int (\%pr::win, 'prin_h', \%HPVALUE) ],
1983         [ '--' ],
1984         [ _('View source window') ],
1985         [ new_text_box_for_int (\%pr::win, 'sour_w', \%HPVALUE),
1986             new_text_box_for_int (\%pr::win, 'sour_h', \%HPVALUE) ]
1987     ]);
1988 }
1989
1990 sub new_winpos_page() {
1991     my $winbook = Gtk3::Notebook->new;
1992     $winbook->set_tab_pos ('right');
1993     $winbook->append_page (new_winpos_subpage_main,
1994         Gtk3::Label->new (_('Main')));
1995     $winbook->append_page (new_winpos_subpage_msgs,
1996         Gtk3::Label->new (_('Message')));
1997     $winbook->append_page (new_winpos_subpage_sendrecv,
1998         Gtk3::Label->new (_('Send/Receive')));
1999     $winbook->append_page (new_winpos_subpage_fold,
2000         Gtk3::Label->new (_('Folder')));
2001     $winbook->append_page (new_winpos_subpage_addrbook,
2002         Gtk3::Label->new (_('Addressbook')));
2003     $winbook->append_page (new_winpos_subpage_accounts,
2004         Gtk3::Label->new (_('Accounts')));
2005     $winbook->append_page (new_winpos_subpage_filtering,
2006         Gtk3::Label->new (_('Filtering')));
2007     $winbook->append_page (new_winpos_subpage_useractions,
2008         Gtk3::Label->new (_('User Actions')));
2009     $winbook->append_page (new_winpos_subpage_prefs,
2010         Gtk3::Label->new (_('Preferences')));
2011     $winbook->append_page (new_winpos_subpage_misc,
2012         Gtk3::Label->new (_('Other')));
2013     return $winbook;
2014 }
2015
2016 %pr::acc = ( # per account hidden preferences
2017     tls_set => [
2018         'gnutls_set_priority',
2019         $xl::s{l_acc_gtls_set},
2020         $xl::s{h_acc_gtls_set},
2021         'bool',
2022         '3.9.0.181',
2023         '0',
2024     ],
2025     tls_pri => [
2026         'gnutls_priority',
2027         $xl::s{l_acc_gtls_pri},
2028         $xl::s{h_acc_gtls_pri},
2029         'char,0,256,32',
2030         '3.9.0.181',
2031         '0',
2032     ],
2033 );
2034
2035 sub new_account_subpage($) {
2036     my ($akey) = @_;
2037     return new_grid_pack (1, 3, [
2038         [ _('GnuTLS priority') ],
2039         [ new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}) ],
2040         [ new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey}) ]
2041     ]);
2042 }
2043
2044 sub new_accounts_page() {
2045     my $accbook = Gtk3::Notebook->new;
2046     $accbook->set_tab_pos ('right');
2047     my @akeys = sort {
2048         $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
2049     } keys %ACPREFS;
2050     foreach (@akeys) {
2051         my $name = $ACPREFS{$_}{'account_name'};
2052         my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
2053         my $page = new_account_subpage ($_);
2054         my $label = new_label ($isdef? '<u>' . $name . '</u>': $name);
2055         $label->set_use_markup (TRUE);
2056         $accbook->append_page ($page, $label);
2057     }
2058     $accbook->set_scrollable (TRUE);
2059     return $accbook;
2060 }
2061
2062 %pr::plu = ( # plugins hidden preferences
2063     # att_remover
2064     arm_winw => [
2065         'win_width',
2066         $xl::s{l_win_w},
2067         $xl::s{h_win_w},
2068         'int,0,3000', # 0 pixels - 3000 pixels
2069         '3.9.0.74',
2070         '-1',
2071         'AttRemover',
2072     ],
2073     arm_winh => [
2074         'win_height',
2075         $xl::s{l_win_h},
2076         $xl::s{h_win_h},
2077         'int,0,3000', # 0 pixels - 3000 pixels
2078         '3.9.0.74',
2079         '-1',
2080         'AttRemover',
2081     ],
2082     # GPG
2083     gpg_alimit => [
2084         'autocompletion_limit',
2085         $xl::s{l_plu_gpg_alimit},
2086         $xl::s{h_plu_gpg_alimit},
2087         'int,0,100',
2088         '3.12.0.75',
2089         '0',
2090         'GPG',
2091     ],
2092     # managesieve
2093     msv_winw => [
2094         'manager_win_width',
2095         $xl::s{l_win_w},
2096         $xl::s{h_win_w},
2097         'int,0,3000', # 0 pixels - 3000 pixels
2098         '3.11.1.210',
2099         '-1',
2100         'ManageSieve',
2101     ],
2102     msv_winh => [
2103         'manager_win_height',
2104         $xl::s{l_win_h},
2105         $xl::s{h_win_h},
2106         'int,0,3000', # 0 pixels - 3000 pixels
2107         '3.11.1.210',
2108         '-1',
2109         'ManageSieve',
2110     ],
2111     # libravatar
2112     lav_burl => [
2113         'base_url',
2114         $xl::s{l_plu_lav_burl},
2115         $xl::s{h_plu_lav_burl},
2116         'char,0,1024,32',
2117         '3.9.3.32',
2118         'http://cdn.libravatar.org/avatar',
2119         'Libravatar',
2120     ],
2121     # perl
2122     prl_flvb => [
2123         'filter_log_verbosity',
2124         $xl::s{l_plu_prl_flvb},
2125         $xl::s{h_plu_prl_flvb},
2126         '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2127         '3.9.0.75',
2128         '2',
2129         'PerlPlugin',
2130     ],
2131 );
2132
2133 sub new_plugins_page() {
2134     my %widget = (
2135         'AttRemover' => [
2136             new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2137             new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})
2138         ],
2139         'GPG' => [
2140             new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})
2141         ],
2142         'ManageSieve' => [
2143             new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2144             new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})
2145         ],
2146         'Libravatar' => [
2147             new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'})
2148         ],
2149         'PerlPlugin' => [
2150             new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})
2151         ]
2152     );
2153     foreach my $pk (@PLUGINS) {
2154         foreach my $wg (@{$widget{$pk}}) {
2155             $wg->set_sensitive (defined $PLHPVALUE{$pk});
2156         }
2157     }
2158     return new_grid_pack (3, 14, [
2159         [ _('Attachment remover') ], $widget{'AttRemover'}, [ '--' ],
2160         [ _('GPG') ], $widget{'GPG'}, [ '--' ],
2161         [ _('Sieve manager') ], $widget{'ManageSieve'}, [ '--' ],
2162         [ _('Libravatar') ], $widget{'Libravatar'}, [ '--' ],
2163         [ _('Perl') ], $widget{'PerlPlugin'}
2164     ]);
2165 }
2166
2167 use constant {
2168     C_LABEL => 0,
2169     C_HOTKEY => 1,
2170     C_GROUP => 2,
2171     C_ACCEL => 3,
2172     C_ODDITY => 4,
2173 };
2174
2175 sub new_hotkeys_list_label {
2176     my $renderer = Gtk3::CellRendererText->new ();
2177     $renderer->set_property('alignment' => 'left');
2178     $renderer->set_property('editable' => FALSE);
2179     return $renderer;
2180 }
2181
2182 sub new_hotkeys_list_hotkey {
2183     my $renderer = Gtk3::CellRendererAccel->new ();
2184     $renderer->set_property ('accel-mode' => 'gtk');
2185     $renderer->set_property ('editable' => TRUE);
2186     $renderer->signal_connect ('accel-edited' => sub {
2187         my ($w, $path, $key, $mods, $keycode) = @_;
2188         my $accel = Gtk3::Accelerator->name ($key, $mods);
2189         my ($model, $iter) = $SELHOTKEY->get_selected ();
2190         $model->set($iter, C_HOTKEY, "\"$accel\"");
2191         my $gkey = $model->get_value ($iter, C_GROUP);
2192         my $akey = $model->get_value ($iter, C_ACCEL);
2193         my $data = $HOTKEYS->{$gkey}->{$akey};
2194         $data->{'key'} = "\"$accel\"";
2195         $data->{'enabled'} = TRUE;
2196     });
2197     $renderer->signal_connect ('accel-cleared' => sub {
2198         my ($w, $path) = @_;
2199         my ($model, $iter) = $SELHOTKEY->get_selected ();
2200         $model->set($iter, C_HOTKEY, "\"\"");
2201         my $gkey = $model->get_value ($iter, C_GROUP);
2202         my $akey = $model->get_value ($iter, C_ACCEL);
2203         my $data = $HOTKEYS->{$gkey}->{$akey};
2204         $data->{'key'} = "\"\"";
2205         $data->{'enabled'} = FALSE;
2206     });
2207     return $renderer;
2208 }
2209
2210 sub row_background_color {
2211     my ($column, $isodd) = @_;
2212     my $treeview = $column->get_tree_view;
2213     my $stylectx = $treeview->get_style_context;
2214     return $isodd
2215         ? $stylectx->get_background_color ('normal')
2216         : $stylectx->get_background_color ('insensitive');
2217 }
2218
2219 sub new_hotkeys_list {
2220     my ($gkey, $group) = @_;
2221     my $store = Gtk3::ListStore->new(
2222         qw/Glib::String Glib::String Glib::String Glib::String Glib::Boolean/);
2223     my $even = TRUE;
2224     foreach my $akey (sort keys %$group) {
2225         my $iter = $store->append ();
2226         my $hotkey = $group->{$akey}->{'key'};
2227         my $label = $akey;
2228         $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2229         $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2230             C_GROUP, $gkey, C_ACCEL, $akey, C_ODDITY, $even);
2231         $even = not $even;
2232     }
2233     my $treeview = Gtk3::TreeView->new_with_model ($store);
2234     # labels column
2235     $treeview->insert_column_with_data_func (
2236         0, _("Menu path"), new_hotkeys_list_label (),
2237         sub {
2238             my ($col, $renderer, $model, $iter, $data) = @_;
2239             $renderer->set_property (
2240                 'markup' => '<span size="smaller">'
2241                             . $model->get_value ($iter, C_LABEL)
2242                             . '</span>');
2243             my $bgcol = row_background_color (
2244                 $col, $model->get_value ($iter, C_ODDITY));
2245             $renderer->set_property ('cell-background-rgba' => $bgcol);
2246         }
2247     );
2248     # hotkeys column
2249     $treeview->insert_column_with_data_func (
2250         1, _('Hotkey'), new_hotkeys_list_hotkey (),
2251         sub {
2252             my ($col, $renderer, $model, $iter, $data) = @_;
2253             my $hkey = $model->get_value ($iter, C_HOTKEY);
2254             $hkey =~ s/\"//g;
2255             my ($acckey, $accmod) = Gtk3::accelerator_parse ($hkey);
2256             $renderer->set_property ('accel-key' => $acckey);
2257             $renderer->set_property ('accel-mods' => $accmod);
2258             my $bgcol = row_background_color (
2259                 $col, $model->get_value ($iter, C_ODDITY));
2260             $renderer->set_property ('cell-background-rgba' => $bgcol);
2261         }
2262     );
2263     # callback for saving current selection
2264     my $selection = $treeview->get_selection ();
2265     $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2266     return $treeview;
2267 }
2268
2269 sub new_hotkeys_page() {
2270     my $swin = Gtk3::ScrolledWindow->new ();
2271     my $vbox = Gtk3::VBox->new (FALSE, 5);
2272     foreach my $gkey (sort keys %$HOTKEYS) {
2273         my $group = $HOTKEYS->{$gkey};
2274         # group title
2275         my $glabel = Gtk3::Label->new ('<b>' . $gkey . '</b>');
2276         $glabel->set_use_markup (TRUE);
2277         $glabel->set_alignment (0, 0.5);
2278         $glabel->set_padding (5, 1);
2279         $vbox->pack_start ($glabel, FALSE, FALSE, 0);
2280         # group key list
2281         my $keylist = new_hotkeys_list ($gkey, $group);
2282         $vbox->pack_start ($keylist, FALSE, FALSE, 0);
2283     }
2284     $swin->set_border_width (5);
2285     $swin->set_shadow_type ('none');
2286     $swin->set_policy ('automatic', 'always');
2287     $swin->add_with_viewport ($vbox);
2288     return $swin;
2289 }
2290
2291 sub new_info_page() {
2292     my $v = get_toolkit_versions ();
2293     my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2294     $cfgv //= '';
2295     return new_grid_pack (4, 11, [
2296         [ _('Library versions') ],
2297         [ new_label ('Perl-GLib'), new_title ($v->{'glib'}) ],
2298         [ new_label (_('GLib runtime')), new_title ($v->{'glib-r'}) ],
2299         [ new_label (_('GLib built')), new_title ($v->{'glib-b'}) ],
2300         [ new_label ('Perl-GTK3'), new_title ($v->{'gtk'}) ],
2301         [ new_label (_('GTK3 runtime')), new_title ($v->{'gtk-r'}) ],
2302         [ new_label (_('GTK3 built')), new_title ($v->{'gtk-b'}) ],
2303         [ '--' ],
2304         [ _('Claws Mail versions') ],
2305         [ new_label (_('Binary')), new_title ($CLAWSV) ],
2306         [ new_label (_('Configuration')), new_title ($cfgv) ]
2307     ]);
2308 }
2309
2310 # version info
2311 sub get_toolkit_versions {
2312     my %versions = ();
2313     $versions{'glib'} = $Glib::VERSION;
2314     # version info stuff appeared in 1.040
2315     if ($Glib::VERSION >= 1.040) {
2316         $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2317         $versions{'glib-r'} = join('.',
2318             &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2319     }
2320     $versions{'gtk'} = $Gtk3::VERSION;
2321     if ($Gtk3::VERSION >= 0.034) {
2322         $versions{'gtk-b'} = &Gtk3::GET_VERSION_INFO
2323     } else {
2324         $versions{'gtk-b'} = _('Not available')
2325     }
2326     $versions{'gtk-r'} = join('.',
2327         &Gtk3::get_major_version, &Gtk3::get_minor_version, &Gtk3::get_micro_version);
2328     return \%versions;
2329 }
2330
2331 sub print_version() {
2332     print $xl::s{about_title} . "\n";
2333     print $xl::s{about_version} . " $VERSION\n";
2334     my $v = get_toolkit_versions ();
2335     if ($v->{'glib-b'}) {
2336         print _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2337                 glibv => $v->{'glib'},
2338                 glibb => $v->{'glib-b'},
2339                 glibr => $v->{'glib-r'});
2340     } else {
2341         print _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2342     }
2343     print "\n";
2344     if ($v->{'gtk-b'}) {
2345         print _("Perl-GTK3 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2346                 gtkv => $v->{'gtk'},
2347                 gtkb => $v->{'gtk-b'},
2348                 gtkr => $v->{'gtk-r'});
2349     } else {
2350         print _("Perl-GTK3 version {gtkv}.", gtkv => $v->{'gtk'});
2351     }
2352     print "\n";
2353     my $clawsver = ($CLAWSV eq "") ?
2354                 _("Claws Mail was not found!") :
2355                 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2356     print $clawsver . "\n";
2357 }
2358
2359 # the command line help
2360 sub print_help() {
2361     my $line = '-' x length ($xl::s{about_title});
2362     say $line;
2363     say $xl::s{about_title};
2364     say $line;
2365     my @help = (
2366         _("Syntax:"),
2367         _("  clawsker [options]"),
2368         _("Options:"),
2369         _("  -a|--alternate-config-dir <dir>  Uses <dir> as Claws Mail configuration."),
2370         _("  -b|--verbose                     More messages on standard output."),
2371         _("  -c|--clawsrc <file>              Uses <file> as full resource name."),
2372         _("  -h|--help                        Prints this help screen and exits."),
2373         _("  -r|--read-only                   Disables writing changes to disk."),
2374         _("  -v|--version                     Prints version information and exits.")
2375     );
2376     foreach (@help) { say $_ }
2377 }
2378
2379 sub parse_command_line {
2380     my $cont = TRUE;
2381     $CLAWSV = get_claws_version ();
2382     eval {
2383         GetOptions('h|help' => sub { print_help (); $cont = FALSE },
2384             'v|version' => sub { print_version (); $cont = FALSE },
2385             'b|verbose' => sub { $VERBOSE = TRUE },
2386             'r|read-only' => sub { $READONLY = TRUE },
2387             'u|use-claws-version=s' => \&opt_use_claws_version,
2388             'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2389             'c|clawsrc=s' => \&opt_clawsrc)
2390         or die _("try -h or --help for syntax.\n");
2391     };
2392     if ($@) {
2393         my $msg = _("Error in options: {msg}\n", msg => $@);
2394         if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2395             eval { Gtk3->init };
2396             error_dialog ($msg) unless $@;
2397         }
2398         die $msg;
2399     }
2400     return $cont;
2401 }
2402
2403 sub opt_use_claws_version {
2404     my ($name, $value) = @_;
2405     die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2406         unless ($value =~ /^[\d\.]+$/);
2407     $CLAWSV = $value;
2408 }
2409
2410 sub opt_alternate_config_dir {
2411     my ($name, $value) = @_;
2412     die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2413         unless -d $value;
2414     $CONFIGDIR = $value;
2415     $CONFIGDIR .= "/" unless ($CONFIGDIR =~ /.*\/$/);
2416     $ALTCONFIGDIR = TRUE;
2417 }
2418
2419 sub opt_clawsrc {
2420     my ($name, $value) = @_;
2421     die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2422         unless -f $value;
2423     set_rc_filename ($value);
2424 }
2425
2426 # update the hidden preferences status from loaded values
2427 sub init_hidden_preferences {
2428     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2429         foreach my $key (keys %$hash) {
2430             $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2431             $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2432         }
2433     }
2434     foreach my $akey (keys %ACPREFS) {
2435         foreach my $key (keys %pr::acc) {
2436             my $pname = $pr::acc{$key}[NAME];
2437             $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2438             $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2439         }
2440     }
2441     foreach my $key (keys %pr::plu) {
2442         my $plugin = $pr::plu{$key}[PLUGIN];
2443         my $pname = $pr::plu{$key}[NAME];
2444         if (defined $PLPREFS{$plugin}) {
2445             $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2446             $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2447         }
2448     }
2449     return TRUE;
2450 }
2451
2452 # generic load/save resource files
2453 sub load_resource {
2454     my $rc = shift;
2455     my %data = ();
2456     my %meta = ();
2457     my $line = 0;
2458     open (RCF, '<:encoding(utf8)', $rc)
2459         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2460     my $section = '_'; # default unnamed section
2461     while (<RCF>) {
2462         chomp;
2463         ++$line;
2464         next if (/^\s*$/);
2465         if (/^\[([^\]]+)\]$/) { # new section
2466             $section = $1;
2467             die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2468                 sect => $section, file => $rc) if ($data{$section});
2469             $data{$section} = {};
2470             $meta{$section}{'#'} = $line;
2471         }
2472         elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2473             $data{$section}{$1} = $2;
2474             $meta{$section}{$1} = $line;
2475         }
2476         elsif (/^(.*)$/) { # lone value
2477             push (@{$data{$section}{'_'}}, $1);
2478         }
2479     }
2480     close (RCF);
2481     return (\%data, \%meta);
2482 }
2483
2484 sub save_resource {
2485     my ($rc, $data, $meta) = @_;
2486     open (RCF, '>:utf8', $rc)
2487         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2488     my @sections = keys %$data;
2489     if (defined $meta) {
2490         @sections = sort {
2491             $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2492         } @sections
2493     }
2494     foreach my $section (@sections) {
2495         say RCF "[$section]";
2496         if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2497             foreach my $val (@{$data->{$section}{'_'}}) {
2498                 say RCF $val;
2499             }
2500         } else {
2501             my @keys = keys %{$data->{$section}};
2502             if (defined $meta) {
2503                 @keys = sort {
2504                     $meta->{$section}{$a} <=> $meta->{$section}{$b}
2505                 } @keys
2506             }
2507             foreach my $key (@keys) {
2508                 my $val = $data->{$section}{$key};
2509                 say RCF "$key=$val";
2510             }
2511         }
2512         say RCF "";
2513     }
2514     close (RCF);
2515 }
2516
2517 sub backup_resource {
2518     my $rc = shift;
2519     my $rcbak = "$rc.backup";
2520     do {
2521         my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2522         log_message ($emsg);
2523         error_dialog ($emsg);
2524         return FALSE;
2525     } unless rename ($rc, $rcbak);
2526     return TRUE;
2527 }
2528
2529 # specific loaders
2530 sub load_menurc {
2531     my $rc = shift;
2532     open (RCF, '<:encoding(utf8)', $rc)
2533         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2534     my %groups = ();
2535     my $line = 0;
2536     while (<RCF>) {
2537         chomp;
2538         if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2539             my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2540             $groups{$1}{$2} = \%data;
2541             # say "group -> $1 | path -> $2 | key -> $3";
2542         } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2543             my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2544             $groups{$1}{$2} = \%data;
2545             # say "group -> $1 | path -> $2 | key -> $3";
2546         }
2547         ++$line;
2548     }
2549     close (RCF);
2550     return \%groups;
2551 }
2552
2553 sub save_menurc {
2554     my ($rc, $groups) = @_;
2555     my @lines = ();
2556     foreach my $gkey (keys %$groups) {
2557         my $group = $groups->{$gkey};
2558         foreach my $akey (keys %$group) {
2559             my $data = $group->{$akey};
2560             my $key = $data->{'key'};
2561             my $line = $data->{'line'};
2562             $lines[$line] = ($data->{'enabled'})? '': '; ';
2563             $lines[$line] .= '(gtk_accel_path "<'
2564                     . $gkey . '>' . $akey . '" ' . $key . ')';
2565         }
2566     }
2567     open (RCF, '>:utf8', $rc)
2568         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2569     say RCF '; claws-mail GtkAccelMap rc-file         -*- scheme -*-';
2570     say RCF '; this file is an automated accelerator map dump';
2571     say RCF ';';
2572     foreach (@lines) { say RCF $_ if $_ }
2573     close (RCF);
2574 }
2575
2576 # load current status from disc
2577 sub load_rc_preferences {
2578     my $rc = get_rc_filename ();
2579     log_message ("Loading preferences from $rc\n");
2580     return FALSE unless check_rc_file ($rc);
2581     ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2582     foreach (keys %{$CONFIGDATA->{'Common'}}) {
2583         $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2584     }
2585     foreach my $plugin (@PLUGINS) {
2586         if (defined $CONFIGDATA->{$plugin}) {
2587             push (@AVPLUGINS, $plugin);
2588             foreach (keys %{$CONFIGDATA->{$plugin}}) {
2589                 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2590             }
2591         }
2592     }
2593     return TRUE;
2594 }
2595
2596 sub load_ac_preferences {
2597     my $rc = get_ac_rc_filename ();
2598     log_message ("Loading account preferences from $rc\n");
2599     return FALSE unless check_rc_file ($rc);
2600     ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2601     foreach my $asect (keys %$ACCOUNTDATA) {
2602         if ($asect =~ /^Account: (\d+)$/) {
2603             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2604                 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2605             }
2606         }
2607     }
2608     return TRUE;
2609 }
2610
2611 sub load_hk_preferences {
2612     my $rc = get_menurc_filename ();
2613     return FALSE unless check_rc_file ($rc);
2614     $HOTKEYS = load_menurc ($rc);
2615     return TRUE;
2616 }
2617
2618 sub load_preferences {
2619     return FALSE unless check_claws_not_running ();
2620     return (load_rc_preferences ()
2621         and load_ac_preferences ()
2622         and load_hk_preferences ()
2623     );
2624 }
2625
2626 # save current preferences to disc
2627 sub save_rc_preferences {
2628     my $rc = get_rc_filename ();
2629     log_message ("Saving preferences to $rc\n");
2630     return FALSE unless check_rc_file ($rc);
2631     return FALSE unless check_claws_not_running ();
2632     return FALSE unless backup_resource ($rc);
2633     foreach (keys %PREFS) {
2634         if (defined $HPVALUE{$_}) {
2635             $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2636         }
2637     }
2638     foreach my $plugin (@AVPLUGINS) {
2639         foreach (keys %{$CONFIGDATA->{$plugin}}) {
2640             if (defined $PLHPVALUE{$plugin}{$_}) {
2641                 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2642             }
2643         }
2644     }
2645     save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2646     return TRUE;
2647 }
2648
2649 sub save_ac_preferences {
2650     my $rc = get_ac_rc_filename ();
2651     log_message ("Saving account preferences to $rc\n");
2652     return FALSE unless check_rc_file ($rc);
2653     return FALSE unless check_claws_not_running ();
2654     return FALSE unless backup_resource ($rc);
2655     foreach my $asect (keys %$ACCOUNTDATA) {
2656         if ($asect =~ /^Account: (\d+)$/) {
2657             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2658                 if (defined $ACHPVALUE{$1}{$_}) {
2659                     $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2660                 }
2661             }
2662         }
2663     }
2664     save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2665     return TRUE;
2666 }
2667
2668 sub save_hk_preferences {
2669     my $rc = get_menurc_filename ();
2670     log_message ("Saving hotkey preferences to $rc\n");
2671     return FALSE unless check_rc_file ($rc);
2672     return FALSE unless check_claws_not_running ();
2673     return FALSE unless backup_resource ($rc);
2674     save_menurc ($rc, $HOTKEYS);
2675     return TRUE;
2676 }
2677
2678 sub save_preferences {
2679     my $result = save_rc_preferences ()
2680         and save_ac_preferences ()
2681         and save_hk_preferences ();
2682     $MODIFIED = 0 if $result;
2683     return $result;
2684 }
2685
2686 # create notebook
2687 sub new_notebook {
2688     my $nb = Gtk3::Notebook->new;
2689     #
2690     $nb->append_page (new_behaviour_page (),
2691         Gtk3::Label->new ($xl::s{tab_behaviour}));
2692     $nb->append_page (new_colours_page (),
2693         Gtk3::Label->new ($xl::s{tab_colours}));
2694     $nb->append_page (new_gui_page (),
2695         Gtk3::Label->new ($xl::s{tab_gui}));
2696     $nb->append_page (new_other_page (),
2697         Gtk3::Label->new ($xl::s{tab_other}));
2698     $nb->append_page (new_winpos_page (),
2699         Gtk3::Label->new ($xl::s{tab_winpos}));
2700     $nb->append_page (new_accounts_page (),
2701         Gtk3::Label->new ($xl::s{tab_accounts}));
2702     $nb->append_page (new_plugins_page (),
2703         Gtk3::Label->new ($xl::s{tab_plugins}));
2704     $nb->append_page (new_hotkeys_page (),
2705         Gtk3::Label->new ($xl::s{tab_hotkeys}));
2706     $nb->append_page (new_info_page (),
2707         Gtk3::Label->new ($xl::s{tab_info}));
2708
2709     return $nb;
2710 }
2711
2712 # create an about dialog
2713 sub new_about_dialog {
2714     my ($parent) = @_;
2715     my $year = '2007-2018';
2716     my $holder = 'Ricardo Mones <ricardo@mones.org>';
2717     my $url = 'http://www.claws-mail.org/clawsker.php';
2718     my $icons = &get_app_icons;
2719
2720     my $dialog = Gtk3::AboutDialog->new;
2721     $dialog->set_transient_for ($parent);
2722     $dialog->set_program_name ('Clawsker');
2723     $dialog->set_version ($VERSION);
2724     $dialog->set_copyright ("Copyright © $year $holder");
2725     $dialog->set_license_type ('gpl-3-0');
2726     $dialog->set_website ($url);
2727     $dialog->set_website_label ($xl::s{about_web_label});
2728     # committers, by number of commits
2729     $dialog->set_authors ([
2730         $holder,
2731         'Tristan Chabredier (wwp) <subscript@free.fr>',
2732         'Andreas Rönnquist <andreas@ronnquist.net>',
2733         'Christian Hesse <mail@eworm.de>',
2734     ]);
2735     $dialog->set_artists ([
2736         'Jesper Schultz <jesper@schultz-net.dk>',
2737         $holder,
2738     ]);
2739     $dialog->set_documenters ([
2740         $holder,
2741         'Paul Mangan <paul@claws-mail.org>',
2742     ]);
2743     # active translators, in alphabetical order
2744     $dialog->set_translator_credits (join ("\n",
2745         'Andreas Rönnquist <andreas@ronnquist.net>',
2746         'Axel Köllhofer <AxelKoellhofer@web.de>',
2747         'David Medina <opensusecatala@gmail.com>',
2748         'Erik P. Olsen <erik@epo.dk>',
2749         'Frederico Goncalves Guimaraes <frederico@teia.bio.br>',
2750         'Marcel Pol <marcel@timelord.nl>',
2751         'Mark Chang <mark.cyj@gmail.com>',
2752         'M. Sulchan Darmawan <bleketux@gmail.com>',
2753         'Numan Demirdöğen <if.gnu.linux@gmail.com>',
2754         'Petter Adsen <petter@synth.no>',
2755         $holder,
2756         'Tristan Chabredier (wwp) <subscript@free.fr>',
2757     ));
2758     $dialog->set_title ($xl::s{about_title});
2759     $dialog->set_logo ($icons->[-1]);
2760
2761     return $dialog;
2762 }
2763
2764 sub exit_handler {
2765   my ($parent) = @_;
2766   if ($MODIFIED != 0 and not $READONLY) {
2767     my $markup = "<span>" . $xl::s{exit_fact} . "</span>\n\n"
2768         . "<span weight=\"bold\">" . $xl::s{exit_question} . "</span>\n";
2769     my $dialog = message_dialog (
2770         $parent, $xl::s{exit_title}, $markup, 'question',
2771         [ 'gtk-no', 1, 'gtk-yes', 0 ]
2772     );
2773     my $resp = $dialog->run;
2774     $dialog->hide;
2775     return TRUE if ($resp == 1);
2776   }
2777   Gtk3->main_quit;
2778 }
2779
2780 # create buttons box
2781 sub new_button_box {
2782     my ($parent, $adlg) = @_;
2783     my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2784     my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2785     my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2786     # disable button until is really implemented
2787     # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2788     my $hbox = Gtk3::HBox->new (FALSE, 5);
2789     # signal handlers
2790     $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2791     $b_apply->set_sensitive (not $READONLY);
2792     $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2793     # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2794     $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2795     # package them
2796     $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2797     $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2798     # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2799     $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2800     #
2801     return $hbox;
2802 }
2803
2804 sub get_app_icons {
2805     return \@APPICONS if (@APPICONS);
2806     my @names;
2807     if (-d $DATADIR) { # installed
2808         my $dir = $DATADIR . '/icons/hicolor';
2809         @names = map {
2810             join ('/', ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png'))
2811         } (48, 64, 128);
2812     } else { # unpacked tarball or git clone
2813         @names = map {
2814             join ('/', ('./icons', $NAME . '-' . $_ . '.png'));
2815         } (48, 64, 128);
2816     }
2817     foreach (@names) {
2818         my $icon = undef;
2819         $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2820         push @APPICONS, $icon if ($icon);
2821     }
2822     return \@APPICONS;
2823 }
2824
2825 sub escape_key_handler {
2826     my ($widget, $event) = @_;
2827     if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {
2828         exit_handler($widget);
2829     }
2830 }
2831
2832 # initialise
2833 exit unless parse_command_line ();
2834 Gtk3->init;
2835 $main_window = Gtk3::Window->new ('toplevel');
2836 exit unless load_preferences ();
2837 exit unless init_hidden_preferences ();
2838 # create main GUI
2839 my $box = Gtk3::VBox->new (FALSE, 5);
2840 $box->set_border_width(3);
2841 my $about = new_about_dialog ($main_window);
2842 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2843 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2844 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2845 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2846 $main_window->set_title ($xl::s{win_title});
2847 $main_window->set_icon_list (get_app_icons ());
2848 $main_window->add ($box);
2849 $main_window->show_all;
2850 $MODIFIED = 0;
2851 Gtk3->main;
2852