4ba365c9f8a40be2428e824cb5c2afa0e1d17336
[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     sslman_w => [
1707         'sslmanwin_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         '486',
1713     ],
1714     sslman_h => [
1715         'sslmanwin_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     plug_w => [
1723         'pluginswin_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         '-1',
1729     ],
1730     plug_h => [
1731         'pluginswin_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     logw_w => [
1739         'logwin_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         '520',
1745     ],
1746     logw_h => [
1747         'logwin_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     prin_w => [
1755         'print_previewwin_width',
1756         $xl::s{l_win_w},
1757         $xl::s{h_win_w},
1758         'int,0,3000', # 0 pixels - 3000 pixels
1759         '0.0.0',
1760         '600',
1761     ],
1762     prin_h => [
1763         'print_previewwin_height',
1764         $xl::s{l_win_h},
1765         $xl::s{h_win_h},
1766         'int,0,3000', # 0 pixels - 3000 pixels
1767         '0.0.0',
1768         '-1',
1769     ],
1770 );
1771
1772 sub new_winpos_subpage_main() {
1773     return new_grid_pack (3, 7, [
1774         [ _('Main window'), undef ],
1775         [ new_text_box_for_int (\%pr::win, 'main_x', \%HPVALUE), undef ],
1776         [ new_text_box_for_int (\%pr::win, 'main_y', \%HPVALUE), undef ],
1777         [ new_text_box_for_int (\%pr::win, 'main_w', \%HPVALUE),
1778             new_text_box_for_int (\%pr::win, 'main_h', \%HPVALUE) ],
1779         [ new_check_button_for (\%pr::win, 'main_fs', \%HPVALUE), undef ],
1780         [ new_check_button_for (\%pr::win, 'main_mx', \%HPVALUE), undef ]
1781     ]);
1782 }
1783
1784 sub new_winpos_subpage_msgs() {
1785     return new_grid_pack (3, 4, [
1786         [ _('Message window') ],
1787         [ new_text_box_for_int (\%pr::win, 'msgs_x', \%HPVALUE) ],
1788         [ new_text_box_for_int (\%pr::win, 'msgs_y', \%HPVALUE) ],
1789         [ new_text_box_for_int (\%pr::win, 'msgs_w', \%HPVALUE),
1790             new_text_box_for_int (\%pr::win, 'msgs_h', \%HPVALUE) ]
1791     ]);
1792 }
1793
1794 sub new_winpos_subpage_sendrecv() {
1795     return new_grid_pack (3, 5, [
1796         [ _('Send window') ],
1797         [ new_text_box_for_int (\%pr::win, 'send_w', \%HPVALUE),
1798             new_text_box_for_int (\%pr::win, 'send_h', \%HPVALUE) ],
1799         [ '--' ],
1800         [ _('Receive window') ],
1801         [ new_text_box_for_int (\%pr::win, 'recv_w', \%HPVALUE),
1802             new_text_box_for_int (\%pr::win, 'recv_h', \%HPVALUE) ]
1803     ]);
1804 }
1805
1806 sub new_winpos_subpage_fold() {
1807     return new_grid_pack (3, 7, [
1808         [ _('Folder window') ],
1809         [ new_text_box_for_int (\%pr::win, 'fold_x', \%HPVALUE) ],
1810         [ new_text_box_for_int (\%pr::win, 'fold_y', \%HPVALUE) ],
1811         [ new_text_box_for_int (\%pr::win, 'fold_w', \%HPVALUE),
1812             new_text_box_for_int (\%pr::win, 'fold_h', \%HPVALUE) ],
1813         [ '--' ],
1814         [ _('Folder selection window') ],
1815         [ new_text_box_for_int (\%pr::win, 'fsel_w', \%HPVALUE),
1816             new_text_box_for_int (\%pr::win, 'fsel_h', \%HPVALUE) ]
1817     ]);
1818 }
1819
1820 sub new_winpos_subpage_addrbook() {
1821     return new_grid_pack (3, 14, [
1822         [ _('Addressbook main window') ],
1823         [ new_text_box_for_int (\%pr::win, 'addr_w', \%HPVALUE),
1824             new_text_box_for_int (\%pr::win, 'addr_h', \%HPVALUE) ],
1825         [ '--' ],
1826         [ _('Edit person window') ],
1827         [ new_text_box_for_int (\%pr::win, 'adep_w', \%HPVALUE),
1828             new_text_box_for_int (\%pr::win, 'adep_h', \%HPVALUE) ],
1829         [ '--' ],
1830         [ _('Edit group window') ],
1831         [ new_text_box_for_int (\%pr::win, 'adeg_w', \%HPVALUE),
1832             new_text_box_for_int (\%pr::win, 'adeg_h', \%HPVALUE) ],
1833         [ '--' ],
1834         [ _('Add address window') ],
1835         [ new_text_box_for_int (\%pr::win, 'adda_w', \%HPVALUE),
1836             new_text_box_for_int (\%pr::win, 'adda_h', \%HPVALUE) ],
1837         [ '--' ],
1838         [ _('Folder select window') ],
1839         [ new_text_box_for_int (\%pr::win, 'addf_w', \%HPVALUE),
1840             new_text_box_for_int (\%pr::win, 'addf_h', \%HPVALUE) ]
1841     ]);
1842 }
1843
1844 sub new_winpos_subpage_accounts() {
1845     return new_grid_pack (3, 5, [
1846         [ _('Accounts window') ],
1847         [ new_text_box_for_int (\%pr::win, 'acco_w', \%HPVALUE),
1848             new_text_box_for_int (\%pr::win, 'acco_h', \%HPVALUE) ],
1849         [ '--' ],
1850         [ _('Edit account window') ],
1851         [ new_text_box_for_int (\%pr::win, 'acce_w', \%HPVALUE),
1852             new_text_box_for_int (\%pr::win, 'acce_h', \%HPVALUE) ]
1853     ]);
1854 }
1855
1856 sub new_winpos_subpage_filtering() {
1857     return new_grid_pack (3, 11, [
1858         [ _('Filtering window') ],
1859         [ new_text_box_for_int (\%pr::win, 'filt_w', \%HPVALUE),
1860             new_text_box_for_int (\%pr::win, 'filt_h', \%HPVALUE) ],
1861         [ '--' ],
1862         [ _('Filtering actions window') ],
1863         [ new_text_box_for_int (\%pr::win, 'fila_w', \%HPVALUE),
1864             new_text_box_for_int (\%pr::win, 'fila_h', \%HPVALUE) ],
1865         [ '--' ],
1866         [ _('Filtering debug window') ],
1867         [ new_text_box_for_int (\%pr::win, 'fild_w', \%HPVALUE),
1868             new_text_box_for_int (\%pr::win, 'fild_h', \%HPVALUE) ],
1869         [ '--' ],
1870         [ ('Matcher window') ],
1871         [ new_text_box_for_int (\%pr::win, 'matc_w', \%HPVALUE),
1872             new_text_box_for_int (\%pr::win, 'matc_h', \%HPVALUE) ]
1873
1874     ]);
1875 }
1876
1877 sub new_winpos_subpage_useractions() {
1878     return new_grid_pack (3, 5, [
1879         [ _('User Actions prefs window') ],
1880         [ new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
1881             new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE) ],
1882         [ '--' ],
1883         [ _('User Actions I/O window') ],
1884         [ new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
1885             new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE) ]
1886     ]);
1887 }
1888
1889 sub new_winpos_subpage_prefs() {
1890     return new_grid_pack (3, 11, [
1891         [ _('Preferences window') ],
1892         [ new_text_box_for_int (\%pr::win, 'pref_w', \%HPVALUE),
1893             new_text_box_for_int (\%pr::win, 'pref_h', \%HPVALUE) ],
1894         [ '--' ],
1895         [ _('Templates window') ],
1896         [ new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
1897             new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE) ],
1898         [ '--' ],
1899         [ _('Tags window') ],
1900         [ new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
1901             new_text_box_for_int (\%pr::win, 'tags_h', \%HPVALUE) ],
1902         [ '--' ],
1903         [ _('Plugins window') ],
1904         [ new_text_box_for_int (\%pr::win, 'plug_w', \%HPVALUE),
1905             new_text_box_for_int (\%pr::win, 'plug_h', \%HPVALUE) ]
1906     ]);
1907 }
1908
1909 sub new_winpos_subpage_misc() {
1910     return new_grid_pack (4, 11, [
1911         [ _('Log window') ],
1912         [ new_text_box_for_int (\%pr::win, 'logw_w', \%HPVALUE),
1913             new_text_box_for_int (\%pr::win, 'logw_h', \%HPVALUE) ],
1914         [ '--' ],
1915         [ _('SSL manager') ],
1916         [ new_text_box_for_int (\%pr::win, 'sslman_w', \%HPVALUE),
1917             new_text_box_for_int (\%pr::win, 'sslman_h', \%HPVALUE) ],
1918         [ '--' ],
1919         [ _('Print preview window') ],
1920         [ new_text_box_for_int (\%pr::win, 'prin_w', \%HPVALUE),
1921             new_text_box_for_int (\%pr::win, 'prin_h', \%HPVALUE) ],
1922         [ '--' ],
1923         [ _('View source window') ],
1924         [ new_text_box_for_int (\%pr::win, 'sour_w', \%HPVALUE),
1925             new_text_box_for_int (\%pr::win, 'sour_h', \%HPVALUE) ]
1926     ]);
1927 }
1928
1929 sub new_winpos_page() {
1930     my $winbook = Gtk3::Notebook->new;
1931     $winbook->set_tab_pos ('right');
1932     $winbook->append_page (&new_winpos_subpage_main, new_label (_('Main')));
1933     $winbook->append_page (&new_winpos_subpage_msgs, new_label (_('Message')));
1934     $winbook->append_page (&new_winpos_subpage_sendrecv, new_label (_('Send/Receive')));
1935     $winbook->append_page (&new_winpos_subpage_fold, new_label (_('Folder')));
1936     $winbook->append_page (&new_winpos_subpage_addrbook, new_label (_('Addressbook')));
1937     $winbook->append_page (&new_winpos_subpage_accounts, new_label (_('Accounts')));
1938     $winbook->append_page (&new_winpos_subpage_filtering, new_label (_('Filtering')));
1939     $winbook->append_page (&new_winpos_subpage_useractions, new_label (_('User Actions')));
1940     $winbook->append_page (&new_winpos_subpage_prefs, new_label (_('Preferences')));
1941     $winbook->append_page (&new_winpos_subpage_misc, new_label (_('Other')));
1942     return $winbook;
1943 }
1944
1945 %pr::acc = ( # per account hidden preferences
1946     tls_set => [
1947         'gnutls_set_priority',
1948         $xl::s{l_acc_gtls_set},
1949         $xl::s{h_acc_gtls_set},
1950         'bool',
1951         '3.9.0.181',
1952         '0',
1953     ],
1954     tls_pri => [
1955         'gnutls_priority',
1956         $xl::s{l_acc_gtls_pri},
1957         $xl::s{h_acc_gtls_pri},
1958         'char,0,256,32',
1959         '3.9.0.181',
1960         '0',
1961     ],
1962 );
1963
1964 sub new_account_subpage($) {
1965     my ($akey) = @_;
1966     return new_grid_pack (1, 3, [
1967         [ _('GnuTLS priority') ],
1968         [ new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}) ],
1969         [ new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey}) ]
1970     ]);
1971 }
1972
1973 sub new_accounts_page() {
1974     my $accbook = Gtk3::Notebook->new;
1975     $accbook->set_tab_pos ('right');
1976     my @akeys = sort {
1977         $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
1978     } keys %ACPREFS;
1979     foreach (@akeys) {
1980         my $name = $ACPREFS{$_}{'account_name'};
1981         my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
1982         my $page = new_account_subpage ($_);
1983         my $label = new_label ($isdef? '<u>' . $name . '</u>': $name);
1984         $label->set_use_markup (TRUE);
1985         $accbook->append_page ($page, $label);
1986     }
1987     $accbook->set_scrollable (TRUE);
1988     return $accbook;
1989 }
1990
1991 %pr::plu = ( # plugins hidden preferences
1992     # att_remover
1993     arm_winw => [
1994         'win_width',
1995         $xl::s{l_win_w},
1996         $xl::s{h_win_w},
1997         'int,0,3000', # 0 pixels - 3000 pixels
1998         '3.9.0.74',
1999         '-1',
2000         'AttRemover',
2001     ],
2002     arm_winh => [
2003         'win_height',
2004         $xl::s{l_win_h},
2005         $xl::s{h_win_h},
2006         'int,0,3000', # 0 pixels - 3000 pixels
2007         '3.9.0.74',
2008         '-1',
2009         'AttRemover',
2010     ],
2011     # GPG
2012     gpg_alimit => [
2013         'autocompletion_limit',
2014         $xl::s{l_plu_gpg_alimit},
2015         $xl::s{h_plu_gpg_alimit},
2016         'int,0,100',
2017         '3.12.0.75',
2018         '0',
2019         'GPG',
2020     ],
2021     # managesieve
2022     msv_winw => [
2023         'manager_win_width',
2024         $xl::s{l_win_w},
2025         $xl::s{h_win_w},
2026         'int,0,3000', # 0 pixels - 3000 pixels
2027         '3.11.1.210',
2028         '-1',
2029         'ManageSieve',
2030     ],
2031     msv_winh => [
2032         'manager_win_height',
2033         $xl::s{l_win_h},
2034         $xl::s{h_win_h},
2035         'int,0,3000', # 0 pixels - 3000 pixels
2036         '3.11.1.210',
2037         '-1',
2038         'ManageSieve',
2039     ],
2040     # libravatar
2041     lav_burl => [
2042         'base_url',
2043         $xl::s{l_plu_lav_burl},
2044         $xl::s{h_plu_lav_burl},
2045         'char,0,1024,32',
2046         '3.9.3.32',
2047         'http://cdn.libravatar.org/avatar',
2048         'Libravatar',
2049     ],
2050     # perl
2051     prl_flvb => [
2052         'filter_log_verbosity',
2053         $xl::s{l_plu_prl_flvb},
2054         $xl::s{h_plu_prl_flvb},
2055         '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2056         '3.9.0.75',
2057         '2',
2058         'PerlPlugin',
2059     ],
2060 );
2061
2062 sub new_plugins_page() {
2063     my %widget = (
2064         'AttRemover' => [
2065             new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2066             new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})
2067         ],
2068         'GPG' => [
2069             new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})
2070         ],
2071         'ManageSieve' => [
2072             new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2073             new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})
2074         ],
2075         'Libravatar' => [
2076             new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'})
2077         ],
2078         'PerlPlugin' => [
2079             new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})
2080         ]
2081     );
2082     foreach my $pk (@PLUGINS) {
2083         foreach my $wg (@{$widget{$pk}}) {
2084             $wg->set_sensitive (defined $PLHPVALUE{$pk});
2085         }
2086     }
2087     return new_grid_pack (3, 14, [
2088         [ _('Attachment remover') ], $widget{'AttRemover'}, [ '--' ],
2089         [ _('GPG') ], $widget{'GPG'}, [ '--' ],
2090         [ _('Sieve manager') ], $widget{'ManageSieve'}, [ '--' ],
2091         [ _('Libravatar') ], $widget{'Libravatar'}, [ '--' ],
2092         [ _('Perl') ], $widget{'PerlPlugin'}
2093     ]);
2094 }
2095
2096 sub new_hotkeys_list_label {
2097     my $renderer = Gtk3::CellRendererText->new ();
2098     $renderer->set_property('alignment' => 'left');
2099     $renderer->set_property('editable' => FALSE);
2100     return $renderer;
2101 }
2102
2103 sub new_hotkeys_list_hotkey {
2104     my $renderer = Gtk3::CellRendererAccel->new ();
2105     $renderer->set_property ('accel-mode' => 'gtk');
2106     $renderer->set_property ('editable' => TRUE);
2107     $renderer->signal_connect ('accel-edited' => sub {
2108         my ($w, $path, $key, $mods, $keycode) = @_;
2109         my $accel = Gtk3::accelerator_name ($key, $mods);
2110         my ($model, $iter) = $SELHOTKEY->get_selected ();
2111         $model->set($iter, C_HOTKEY, "\"$accel\"");
2112         my $gkey = $model->get_value ($iter, C_GROUP);
2113         my $akey = $model->get_value ($iter, C_ACCEL);
2114         my $data = $HOTKEYS->{$gkey}->{$akey};
2115         $data->{'key'} = "\"$accel\"";
2116         $data->{'enabled'} = TRUE;
2117     });
2118     $renderer->signal_connect ('accel-cleared' => sub {
2119         my ($w, $path) = @_;
2120         my ($model, $iter) = $SELHOTKEY->get_selected ();
2121         $model->set($iter, C_HOTKEY, "\"\"");
2122         my $gkey = $model->get_value ($iter, C_GROUP);
2123         my $akey = $model->get_value ($iter, C_ACCEL);
2124         my $data = $HOTKEYS->{$gkey}->{$akey};
2125         $data->{'key'} = "\"\"";
2126         $data->{'enabled'} = FALSE;
2127     });
2128     return $renderer;
2129 }
2130
2131 sub row_background_color {
2132     my ($column, $isodd) = @_;
2133     my $treeview = $column->get_tree_view;
2134     my $stylectx = $treeview->get_style_context;
2135     return $isodd
2136         ? $stylectx->get_background_color ('normal')
2137         : $stylectx->get_background_color ('insensitive');
2138 }
2139
2140 sub new_hotkeys_list {
2141     my ($gkey, $group) = @_;
2142     my $store = Gtk3::ListStore->new(
2143         qw/Glib::String Glib::String Glib::String Glib::String Glib::Boolean/);
2144     my $even = TRUE;
2145     foreach my $akey (sort keys %$group) {
2146         my $iter = $store->append ();
2147         my $hotkey = $group->{$akey}->{'key'};
2148         my $label = $akey;
2149         $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2150         $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2151             C_GROUP, $gkey, C_ACCEL, $akey, C_ODDITY, $even);
2152         $even = not $even;
2153     }
2154     my $treeview = Gtk3::TreeView->new_with_model ($store);
2155     # labels column
2156     $treeview->insert_column_with_data_func (
2157         0, _("Menu path"), new_hotkeys_list_label (),
2158         sub {
2159             my ($col, $renderer, $model, $iter, $data) = @_;
2160             $renderer->set_property (
2161                 'markup' => '<span size="smaller">'
2162                             . $model->get_value ($iter, C_LABEL)
2163                             . '</span>');
2164             my $bgcol = row_background_color (
2165                 $col, $model->get_value ($iter, C_ODDITY));
2166             $renderer->set_property ('cell-background-rgba' => $bgcol);
2167         }
2168     );
2169     # hotkeys column
2170     $treeview->insert_column_with_data_func (
2171         1, _('Hotkey'), new_hotkeys_list_hotkey (),
2172         sub {
2173             my ($col, $renderer, $model, $iter, $data) = @_;
2174             my $hkey = $model->get_value ($iter, C_HOTKEY);
2175             $hkey =~ s/\"//g;
2176             my ($acckey, $accmod) = Gtk3::accelerator_parse ($hkey);
2177             $renderer->set_property ('accel-key' => $acckey);
2178             $renderer->set_property ('accel-mods' => $accmod);
2179             my $bgcol = row_background_color (
2180                 $col, $model->get_value ($iter, C_ODDITY));
2181             $renderer->set_property ('cell-background-rgba' => $bgcol);
2182         }
2183     );
2184     # callback for saving current selection
2185     my $selection = $treeview->get_selection ();
2186     $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2187     return $treeview;
2188 }
2189
2190 sub new_hotkeys_page() {
2191     my $swin = Gtk3::ScrolledWindow->new ();
2192     my $vbox = Gtk3::VBox->new (FALSE, 5);
2193     foreach my $gkey (sort keys %$HOTKEYS) {
2194         my $group = $HOTKEYS->{$gkey};
2195         # group title
2196         my $glabel = new_title ($gkey);
2197         $glabel->set_padding (5, 1);
2198         $vbox->pack_start ($glabel, FALSE, FALSE, 0);
2199         # group key list
2200         my $keylist = new_hotkeys_list ($gkey, $group);
2201         $vbox->pack_start ($keylist, FALSE, FALSE, 0);
2202     }
2203     $swin->set_border_width (5);
2204     $swin->set_shadow_type ('none');
2205     $swin->set_policy ('automatic', 'always');
2206     $swin->add_with_viewport ($vbox);
2207     return $swin;
2208 }
2209
2210 sub new_info_page() {
2211     my $v = get_toolkit_versions ();
2212     my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2213     $cfgv //= '';
2214     return new_grid_pack (4, 11, [
2215         [ _('Library versions') ],
2216         [ new_label ('Perl-GLib'), new_title ($v->{'glib'}) ],
2217         [ new_label (_('GLib runtime')), new_title ($v->{'glib-r'}) ],
2218         [ new_label (_('GLib built')), new_title ($v->{'glib-b'}) ],
2219         [ new_label ('Perl-GTK3'), new_title ($v->{'gtk'}) ],
2220         [ new_label (_('GTK3 runtime')), new_title ($v->{'gtk-r'}) ],
2221         [ new_label (_('GTK3 built')), new_title ($v->{'gtk-b'}) ],
2222         [ '--' ],
2223         [ _('Claws Mail versions') ],
2224         [ new_label (_('Binary')), new_title ($CLAWSV) ],
2225         [ new_label (_('Configuration')), new_title ($cfgv) ]
2226     ]);
2227 }
2228
2229 # version info
2230 sub get_toolkit_versions {
2231     my %versions = ();
2232     $versions{'glib'} = $Glib::VERSION;
2233     # version info stuff appeared in 1.040
2234     if ($Glib::VERSION >= 1.040) {
2235         $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2236         $versions{'glib-r'} = join('.',
2237             &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2238     }
2239     $versions{'gtk'} = $Gtk3::VERSION;
2240     if ($Gtk3::VERSION >= 0.034) {
2241         $versions{'gtk-b'} = &Gtk3::GET_VERSION_INFO
2242     } else {
2243         $versions{'gtk-b'} = _('Not available')
2244     }
2245     $versions{'gtk-r'} = join('.',
2246         &Gtk3::get_major_version, &Gtk3::get_minor_version, &Gtk3::get_micro_version);
2247     return \%versions;
2248 }
2249
2250 sub print_version() {
2251     print $xl::s{about_title} . "\n";
2252     print $xl::s{about_version} . " $VERSION\n";
2253     my $v = get_toolkit_versions ();
2254     if ($v->{'glib-b'}) {
2255         print _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2256                 glibv => $v->{'glib'},
2257                 glibb => $v->{'glib-b'},
2258                 glibr => $v->{'glib-r'});
2259     } else {
2260         print _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2261     }
2262     print "\n";
2263     if ($v->{'gtk-b'}) {
2264         print _("Perl-GTK3 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2265                 gtkv => $v->{'gtk'},
2266                 gtkb => $v->{'gtk-b'},
2267                 gtkr => $v->{'gtk-r'});
2268     } else {
2269         print _("Perl-GTK3 version {gtkv}.", gtkv => $v->{'gtk'});
2270     }
2271     print "\n";
2272     my $clawsver = ($CLAWSV eq "") ?
2273                 _("Claws Mail was not found!") :
2274                 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2275     print $clawsver . "\n";
2276 }
2277
2278 # the command line help
2279 sub print_help() {
2280     my $line = '-' x length ($xl::s{about_title});
2281     say $line;
2282     say $xl::s{about_title};
2283     say $line;
2284     my @help = (
2285         _("Syntax:"),
2286         _("  clawsker [options]"),
2287         _("Options:"),
2288         _("  -a|--alternate-config-dir <dir>  Uses <dir> as Claws Mail configuration."),
2289         _("  -b|--verbose                     More messages on standard output."),
2290         _("  -c|--clawsrc <file>              Uses <file> as full resource name."),
2291         _("  -h|--help                        Prints this help screen and exits."),
2292         _("  -r|--read-only                   Disables writing changes to disk."),
2293         _("  -v|--version                     Prints version information and exits.")
2294     );
2295     foreach (@help) { say $_ }
2296 }
2297
2298 sub parse_command_line {
2299     my $cont = TRUE;
2300     $CLAWSV = get_claws_version ();
2301     eval {
2302         GetOptions('h|help' => sub { print_help (); $cont = FALSE },
2303             'v|version' => sub { print_version (); $cont = FALSE },
2304             'b|verbose' => sub { $VERBOSE = TRUE },
2305             'r|read-only' => sub { $READONLY = TRUE },
2306             'u|use-claws-version=s' => \&opt_use_claws_version,
2307             'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2308             'c|clawsrc=s' => \&opt_clawsrc)
2309         or die _("try -h or --help for syntax.\n");
2310     };
2311     if ($@) {
2312         my $msg = _("Error in options: {msg}\n", msg => $@);
2313         if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2314             eval { Gtk3->init };
2315             error_dialog ($msg) unless $@;
2316         }
2317         die $msg;
2318     }
2319     return $cont;
2320 }
2321
2322 sub opt_use_claws_version {
2323     my ($name, $value) = @_;
2324     die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2325         unless ($value =~ /^[\d\.]+$/);
2326     $CLAWSV = $value;
2327 }
2328
2329 sub opt_alternate_config_dir {
2330     my ($name, $value) = @_;
2331     die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2332         unless -d $value;
2333     $CONFIGDIR = $value;
2334     $ALTCONFIGDIR = TRUE;
2335 }
2336
2337 sub opt_clawsrc {
2338     my ($name, $value) = @_;
2339     die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2340         unless -f $value;
2341     set_rc_filename ($value);
2342 }
2343
2344 # update the hidden preferences status from loaded values
2345 sub init_hidden_preferences {
2346     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2347         foreach my $key (keys %$hash) {
2348             $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2349             $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2350         }
2351     }
2352     foreach my $akey (keys %ACPREFS) {
2353         foreach my $key (keys %pr::acc) {
2354             my $pname = $pr::acc{$key}[NAME];
2355             $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2356             $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2357         }
2358     }
2359     foreach my $key (keys %pr::plu) {
2360         my $plugin = $pr::plu{$key}[PLUGIN];
2361         my $pname = $pr::plu{$key}[NAME];
2362         if (defined $PLPREFS{$plugin}) {
2363             $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2364             $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2365         }
2366     }
2367     return TRUE;
2368 }
2369
2370 # generic load/save resource files
2371 sub load_resource {
2372     my $rc = shift;
2373     my %data = ();
2374     my %meta = ();
2375     my $line = 0;
2376     open (RCF, '<:encoding(utf8)', $rc)
2377         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2378     my $section = '_'; # default unnamed section
2379     while (<RCF>) {
2380         chomp;
2381         ++$line;
2382         next if (/^\s*$/);
2383         if (/^\[([^\]]+)\]$/) { # new section
2384             $section = $1;
2385             die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2386                 sect => $section, file => $rc) if ($data{$section});
2387             $data{$section} = {};
2388             $meta{$section}{'#'} = $line;
2389         }
2390         elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2391             $data{$section}{$1} = $2;
2392             $meta{$section}{$1} = $line;
2393         }
2394         elsif (/^(.*)$/) { # lone value
2395             push (@{$data{$section}{'_'}}, $1);
2396         }
2397     }
2398     close (RCF);
2399     return (\%data, \%meta);
2400 }
2401
2402 sub save_resource {
2403     my ($rc, $data, $meta) = @_;
2404     open (RCF, '>:utf8', $rc)
2405         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2406     my @sections = keys %$data;
2407     if (defined $meta) {
2408         @sections = sort {
2409             $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2410         } @sections
2411     }
2412     foreach my $section (@sections) {
2413         say RCF "[$section]";
2414         if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2415             foreach my $val (@{$data->{$section}{'_'}}) {
2416                 say RCF $val;
2417             }
2418         } else {
2419             my @keys = keys %{$data->{$section}};
2420             if (defined $meta) {
2421                 @keys = sort {
2422                     $meta->{$section}{$a} <=> $meta->{$section}{$b}
2423                 } @keys
2424             }
2425             foreach my $key (@keys) {
2426                 my $val = $data->{$section}{$key};
2427                 say RCF "$key=$val";
2428             }
2429         }
2430         say RCF "";
2431     }
2432     close (RCF);
2433 }
2434
2435 sub backup_resource {
2436     my $rc = shift;
2437     my $rcbak = "$rc.backup";
2438     do {
2439         my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2440         log_message ($emsg);
2441         error_dialog ($emsg);
2442         return FALSE;
2443     } unless rename ($rc, $rcbak);
2444     return TRUE;
2445 }
2446
2447 # specific loaders
2448 sub load_menurc {
2449     my $rc = shift;
2450     open (RCF, '<:encoding(utf8)', $rc)
2451         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2452     my %groups = ();
2453     my $line = 0;
2454     while (<RCF>) {
2455         chomp;
2456         if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2457             my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2458             $groups{$1}{$2} = \%data;
2459             # say "group -> $1 | path -> $2 | key -> $3";
2460         } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2461             my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2462             $groups{$1}{$2} = \%data;
2463             # say "group -> $1 | path -> $2 | key -> $3";
2464         }
2465         ++$line;
2466     }
2467     close (RCF);
2468     return \%groups;
2469 }
2470
2471 sub save_menurc {
2472     my ($rc, $groups) = @_;
2473     my @lines = ();
2474     foreach my $gkey (keys %$groups) {
2475         my $group = $groups->{$gkey};
2476         foreach my $akey (keys %$group) {
2477             my $data = $group->{$akey};
2478             my $key = $data->{'key'};
2479             my $line = $data->{'line'};
2480             $lines[$line] = ($data->{'enabled'})? '': '; ';
2481             $lines[$line] .= '(gtk_accel_path "<'
2482                     . $gkey . '>' . $akey . '" ' . $key . ')';
2483         }
2484     }
2485     open (RCF, '>:utf8', $rc)
2486         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2487     say RCF '; claws-mail GtkAccelMap rc-file         -*- scheme -*-';
2488     say RCF '; this file is an automated accelerator map dump';
2489     say RCF ';';
2490     foreach (@lines) { say RCF $_ if $_ }
2491     close (RCF);
2492 }
2493
2494 # load current status from disc
2495 sub load_rc_preferences {
2496     my $rc = get_rc_filename ();
2497     log_message ("Loading preferences from $rc\n");
2498     return FALSE unless check_rc_file ($rc);
2499     ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2500     foreach (keys %{$CONFIGDATA->{'Common'}}) {
2501         $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2502     }
2503     foreach my $plugin (@PLUGINS) {
2504         if (defined $CONFIGDATA->{$plugin}) {
2505             push (@AVPLUGINS, $plugin);
2506             foreach (keys %{$CONFIGDATA->{$plugin}}) {
2507                 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2508             }
2509         }
2510     }
2511     return TRUE;
2512 }
2513
2514 sub load_ac_preferences {
2515     my $rc = get_ac_rc_filename ();
2516     log_message ("Loading account preferences from $rc\n");
2517     return FALSE unless check_rc_file ($rc);
2518     ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2519     foreach my $asect (keys %$ACCOUNTDATA) {
2520         if ($asect =~ /^Account: (\d+)$/) {
2521             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2522                 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2523             }
2524         }
2525     }
2526     return TRUE;
2527 }
2528
2529 sub load_hk_preferences {
2530     my $rc = get_menurc_filename ();
2531     return FALSE unless check_rc_file ($rc);
2532     $HOTKEYS = load_menurc ($rc);
2533     return TRUE;
2534 }
2535
2536 sub load_preferences {
2537     return FALSE unless check_claws_not_running ();
2538     return (load_rc_preferences ()
2539         and load_ac_preferences ()
2540         and load_hk_preferences ()
2541     );
2542 }
2543
2544 # save current preferences to disc
2545 sub save_rc_preferences {
2546     my $rc = get_rc_filename ();
2547     log_message ("Saving preferences to $rc\n");
2548     return FALSE unless check_rc_file ($rc);
2549     return FALSE unless backup_resource ($rc);
2550     foreach (keys %PREFS) {
2551         if (defined $HPVALUE{$_}) {
2552             $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2553         }
2554     }
2555     foreach my $plugin (@AVPLUGINS) {
2556         foreach (keys %{$CONFIGDATA->{$plugin}}) {
2557             if (defined $PLHPVALUE{$plugin}{$_}) {
2558                 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2559             }
2560         }
2561     }
2562     save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2563     return TRUE;
2564 }
2565
2566 sub save_ac_preferences {
2567     my $rc = get_ac_rc_filename ();
2568     log_message ("Saving account preferences to $rc\n");
2569     return FALSE unless check_rc_file ($rc);
2570     return FALSE unless backup_resource ($rc);
2571     foreach my $asect (keys %$ACCOUNTDATA) {
2572         if ($asect =~ /^Account: (\d+)$/) {
2573             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2574                 if (defined $ACHPVALUE{$1}{$_}) {
2575                     $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2576                 }
2577             }
2578         }
2579     }
2580     save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2581     return TRUE;
2582 }
2583
2584 sub save_hk_preferences {
2585     my $rc = get_menurc_filename ();
2586     log_message ("Saving hotkey preferences to $rc\n");
2587     return FALSE unless check_rc_file ($rc);
2588     return FALSE unless backup_resource ($rc);
2589     save_menurc ($rc, $HOTKEYS);
2590     return TRUE;
2591 }
2592
2593 sub save_preferences {
2594     return FALSE unless check_claws_not_running ();
2595     my $result = save_rc_preferences ()
2596         and save_ac_preferences ()
2597         and save_hk_preferences ();
2598     $MODIFIED = 0 if $result;
2599     return $result;
2600 }
2601
2602 # create notebook
2603 sub new_notebook {
2604     my $nb = Gtk3::Notebook->new;
2605
2606     $nb->append_page (&new_behaviour_page, Gtk3::Label->new (_('Behaviour')));
2607     $nb->append_page (&new_colours_page, Gtk3::Label->new (_('Colours')));
2608     $nb->append_page (&new_gui_page, Gtk3::Label->new (_('GUI')));
2609     $nb->append_page (&new_other_page, Gtk3::Label->new (_('Other')));
2610     $nb->append_page (&new_winpos_page, Gtk3::Label->new (_('Windows')));
2611     $nb->append_page (&new_accounts_page, Gtk3::Label->new (_('Accounts')));
2612     $nb->append_page (&new_plugins_page, Gtk3::Label->new (_('Plugins')));
2613     $nb->append_page (&new_hotkeys_page, Gtk3::Label->new (_('Hotkeys')));
2614     $nb->append_page (&new_info_page, Gtk3::Label->new (_('Info')));
2615
2616     return $nb;
2617 }
2618
2619 # create an about dialog
2620 sub new_about_dialog {
2621     my ($parent) = @_;
2622     my $year = '2007-2018';
2623     my $holder = 'Ricardo Mones <ricardo@mones.org>';
2624     my $url = 'http://www.claws-mail.org/clawsker.php';
2625     my $icons = &get_app_icons;
2626
2627     my $dialog = Gtk3::AboutDialog->new;
2628     $dialog->set_transient_for ($parent);
2629     $dialog->set_program_name ('Clawsker');
2630     $dialog->set_version ($VERSION);
2631     $dialog->set_copyright ("Copyright © $year $holder");
2632     $dialog->set_license_type ('gpl-3-0');
2633     $dialog->set_website ($url);
2634     $dialog->set_website_label (_("Visit Clawsker's web page"));
2635     # committers, by number of commits
2636     $dialog->set_authors ([
2637         $holder,
2638         'Tristan Chabredier (wwp) <subscript@free.fr>',
2639         'Andreas Rönnquist <andreas@ronnquist.net>',
2640         'Christian Hesse <mail@eworm.de>',
2641     ]);
2642     $dialog->set_artists ([
2643         'Jesper Schultz <jesper@schultz-net.dk>',
2644         $holder,
2645     ]);
2646     $dialog->set_documenters ([
2647         $holder,
2648         'Paul Mangan <paul@claws-mail.org>',
2649     ]);
2650     # active translators, in alphabetical order
2651     $dialog->set_translator_credits (join ("\n",
2652         'Andreas Rönnquist <andreas@ronnquist.net>',
2653         'Axel Köllhofer <AxelKoellhofer@web.de>',
2654         'David Medina <opensusecatala@gmail.com>',
2655         'Erik P. Olsen <erik@epo.dk>',
2656         'Frederico Goncalves Guimaraes <frederico@teia.bio.br>',
2657         'Marcel Pol <marcel@timelord.nl>',
2658         'Mark Chang <mark.cyj@gmail.com>',
2659         'M. Sulchan Darmawan <bleketux@gmail.com>',
2660         'Numan Demirdöğen <if.gnu.linux@gmail.com>',
2661         'Pedro Albuquerque <pmra@gmx.com>',
2662         'Petter Adsen <petter@synth.no>',
2663         $holder,
2664         'Tristan Chabredier (wwp) <subscript@free.fr>',
2665     ));
2666     $dialog->set_title ($xl::s{about_title});
2667     $dialog->set_logo ($icons->[-1]);
2668
2669     return $dialog;
2670 }
2671
2672 sub exit_handler {
2673   my ($parent) = @_;
2674   if ($MODIFIED != 0 and not $READONLY) {
2675     my $markup = "<span>" . _('There are unapplied modifications.')
2676         . "</span>\n\n<span weight=\"bold\">"
2677         . _('Do you really want to quit?') . "</span>\n";
2678     my $dialog = message_dialog (
2679         $parent, _('Clawsker warning'), $markup, 'question',
2680         [ 'gtk-no', 1, 'gtk-yes', 0 ]
2681     );
2682     my $resp = $dialog->run;
2683     $dialog->hide;
2684     return TRUE if $resp;
2685   }
2686   Gtk3->main_quit;
2687 }
2688
2689 # create buttons box
2690 sub new_button_box {
2691     my ($parent, $adlg) = @_;
2692     my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2693     my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2694     my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2695     # disable button until is really implemented
2696     # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2697     my $hbox = Gtk3::HBox->new (FALSE, 5);
2698     # signal handlers
2699     $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2700     $b_apply->set_sensitive (not $READONLY);
2701     $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2702     # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2703     $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2704     # package them
2705     $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2706     $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2707     # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2708     $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2709     #
2710     return $hbox;
2711 }
2712
2713 sub get_app_icons {
2714     return \@APPICONS if (@APPICONS);
2715     my @names;
2716     if (-d $DATADIR) { # installed
2717         my $dir = catdir ($DATADIR, 'icons', 'hicolor');
2718         @names = map {
2719             catfile ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png')
2720         } (48, 64, 128);
2721     } else { # unpacked tarball or git clone
2722         @names = map {
2723             catfile ('.', 'icons', $NAME . '-' . $_ . '.png');
2724         } (48, 64, 128);
2725     }
2726     foreach (@names) {
2727         my $icon = undef;
2728         $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2729         push @APPICONS, $icon if ($icon);
2730     }
2731     return \@APPICONS;
2732 }
2733
2734 sub escape_key_handler {
2735     my ($widget, $event) = @_;
2736     if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {
2737         exit_handler($widget);
2738     }
2739 }
2740
2741 # initialise
2742 exit unless parse_command_line ();
2743 Gtk3->init;
2744 $main_window = Gtk3::Window->new ('toplevel');
2745 exit unless load_preferences ();
2746 exit unless init_hidden_preferences ();
2747 # create main GUI
2748 my $box = Gtk3::VBox->new (FALSE, 5);
2749 $box->set_border_width(3);
2750 my $about = new_about_dialog ($main_window);
2751 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2752 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2753 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2754 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2755 $main_window->set_title (_('Claws Mail Hidden Preferences'));
2756 $main_window->set_icon_list (get_app_icons ());
2757 $main_window->add ($box);
2758 $main_window->show_all;
2759 $MODIFIED = 0;
2760 Gtk3->main;
2761