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