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