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