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