379116590532051c8068dcd3f2ba2d38087cb432
[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 $hkbook = Gtk3::Notebook->new;
2192     $hkbook->set_tab_pos ('right');
2193     foreach my $gkey (sort keys %$HOTKEYS) {
2194         my $group = $HOTKEYS->{$gkey};
2195         my $keylist = new_hotkeys_list ($gkey, $group);
2196         # prepare scrolled window
2197         my $swin = Gtk3::ScrolledWindow->new;
2198         $swin->set_border_width (5);
2199         $swin->set_shadow_type ('none');
2200         $swin->set_policy ('automatic', 'automatic');
2201         # add list of keys
2202         $swin->add ($keylist);
2203         $hkbook->append_page ($swin, new_label ($gkey));
2204     }
2205     return $hkbook;
2206 }
2207
2208 sub new_info_page() {
2209     my $v = get_toolkit_versions ();
2210     my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2211     $cfgv //= '';
2212     return new_grid_pack (4, 11, [
2213         [ _('Library versions') ],
2214         [ new_label ('Perl-GLib'), new_title ($v->{'glib'}) ],
2215         [ new_label (_('GLib runtime')), new_title ($v->{'glib-r'}) ],
2216         [ new_label (_('GLib built')), new_title ($v->{'glib-b'}) ],
2217         [ new_label ('Perl-GTK3'), new_title ($v->{'gtk'}) ],
2218         [ new_label (_('GTK3 runtime')), new_title ($v->{'gtk-r'}) ],
2219         [ new_label (_('GTK3 built')), new_title ($v->{'gtk-b'}) ],
2220         [ '--' ],
2221         [ _('Claws Mail versions') ],
2222         [ new_label (_('Binary')), new_title ($CLAWSV) ],
2223         [ new_label (_('Configuration')), new_title ($cfgv) ]
2224     ]);
2225 }
2226
2227 # version info
2228 sub get_toolkit_versions {
2229     my %versions = ();
2230     $versions{'glib'} = $Glib::VERSION;
2231     # version info stuff appeared in 1.040
2232     if ($Glib::VERSION >= 1.040) {
2233         $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2234         $versions{'glib-r'} = join('.',
2235             &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2236     }
2237     $versions{'gtk'} = $Gtk3::VERSION;
2238     if ($Gtk3::VERSION >= 0.034) {
2239         $versions{'gtk-b'} = &Gtk3::GET_VERSION_INFO
2240     } else {
2241         $versions{'gtk-b'} = _('Not available')
2242     }
2243     $versions{'gtk-r'} = join('.',
2244         &Gtk3::get_major_version, &Gtk3::get_minor_version, &Gtk3::get_micro_version);
2245     return \%versions;
2246 }
2247
2248 sub print_version() {
2249     say $xl::s{about_title};
2250     say _('Version:') . " $VERSION";
2251     my $v = get_toolkit_versions ();
2252     if ($v->{'glib-b'}) {
2253         say _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2254                 glibv => $v->{'glib'},
2255                 glibb => $v->{'glib-b'},
2256                 glibr => $v->{'glib-r'});
2257     } else {
2258         say _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2259     }
2260     if ($v->{'gtk-b'}) {
2261         say _("Perl-GTK3 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2262                 gtkv => $v->{'gtk'},
2263                 gtkb => $v->{'gtk-b'},
2264                 gtkr => $v->{'gtk-r'});
2265     } else {
2266         say _("Perl-GTK3 version {gtkv}.", gtkv => $v->{'gtk'});
2267     }
2268     my $clawsver = ($CLAWSV eq "") ?
2269                 _("Claws Mail was not found!") :
2270                 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2271     say $clawsver;
2272 }
2273
2274 # the command line help
2275 sub print_help() {
2276     my $line = '-' x length ($xl::s{about_title});
2277     say $line;
2278     say $xl::s{about_title};
2279     say $line;
2280     my @help = (
2281         _("Syntax:"),
2282         _("  clawsker [options]"),
2283         _("Options:"),
2284         _("  -a|--alternate-config-dir <dir>  Uses <dir> as Claws Mail configuration."),
2285         _("  -b|--verbose                     More messages on standard output."),
2286         _("  -c|--clawsrc <file>              Uses <file> as full resource name."),
2287         _("  -h|--help                        Prints this help screen and exits."),
2288         _("  -r|--read-only                   Disables writing changes to disk."),
2289         _("  -v|--version                     Prints version information and exits.")
2290     );
2291     foreach (@help) { say $_ }
2292 }
2293
2294 sub parse_command_line {
2295     my $cont = TRUE;
2296     $CLAWSV = get_claws_version ();
2297     eval {
2298         GetOptions('h|help' => sub { print_help (); $cont = FALSE },
2299             'v|version' => sub { print_version (); $cont = FALSE },
2300             'b|verbose' => sub { $VERBOSE = TRUE },
2301             'r|read-only' => sub { $READONLY = TRUE },
2302             'u|use-claws-version=s' => \&opt_use_claws_version,
2303             'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2304             'c|clawsrc=s' => \&opt_clawsrc)
2305         or die _("try -h or --help for syntax.\n");
2306     };
2307     if ($@) {
2308         my $msg = _("Error in options: {msg}\n", msg => $@);
2309         if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2310             eval { Gtk3->init };
2311             error_dialog ($msg) unless $@;
2312         }
2313         die $msg;
2314     }
2315     return $cont;
2316 }
2317
2318 sub opt_use_claws_version {
2319     my ($name, $value) = @_;
2320     die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2321         unless ($value =~ /^[\d\.]+$/);
2322     $CLAWSV = $value;
2323 }
2324
2325 sub opt_alternate_config_dir {
2326     my ($name, $value) = @_;
2327     die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2328         unless -d $value;
2329     $CONFIGDIR = $value;
2330     $ALTCONFIGDIR = TRUE;
2331 }
2332
2333 sub opt_clawsrc {
2334     my ($name, $value) = @_;
2335     die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2336         unless -f $value;
2337     set_rc_filename ($value);
2338 }
2339
2340 # update the hidden preferences status from loaded values
2341 sub init_hidden_preferences {
2342     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2343         foreach my $key (keys %$hash) {
2344             $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2345             $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2346         }
2347     }
2348     foreach my $akey (keys %ACPREFS) {
2349         foreach my $key (keys %pr::acc) {
2350             my $pname = $pr::acc{$key}[NAME];
2351             $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2352             $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2353         }
2354     }
2355     foreach my $key (keys %pr::plu) {
2356         my $plugin = $pr::plu{$key}[PLUGIN];
2357         my $pname = $pr::plu{$key}[NAME];
2358         if (defined $PLPREFS{$plugin}) {
2359             $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2360             $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2361         }
2362     }
2363     return TRUE;
2364 }
2365
2366 # generic load/save resource files
2367 sub load_resource {
2368     my $rc = shift;
2369     my %data = ();
2370     my %meta = ();
2371     my $line = 0;
2372     open (RCF, '<:encoding(utf8)', $rc)
2373         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2374     my $section = '_'; # default unnamed section
2375     while (<RCF>) {
2376         chomp;
2377         ++$line;
2378         next if (/^\s*$/);
2379         if (/^\[([^\]]+)\]$/) { # new section
2380             $section = $1;
2381             die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2382                 sect => $section, file => $rc) if ($data{$section});
2383             $data{$section} = {};
2384             $meta{$section}{'#'} = $line;
2385         }
2386         elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2387             $data{$section}{$1} = $2;
2388             $meta{$section}{$1} = $line;
2389         }
2390         elsif (/^(.*)$/) { # lone value
2391             push (@{$data{$section}{'_'}}, $1);
2392         }
2393     }
2394     close (RCF);
2395     return (\%data, \%meta);
2396 }
2397
2398 sub save_resource {
2399     my ($rc, $data, $meta) = @_;
2400     open (RCF, '>:utf8', $rc)
2401         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2402     my @sections = keys %$data;
2403     if (defined $meta) {
2404         @sections = sort {
2405             $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2406         } @sections
2407     }
2408     foreach my $section (@sections) {
2409         say RCF "[$section]";
2410         if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2411             foreach my $val (@{$data->{$section}{'_'}}) {
2412                 say RCF $val;
2413             }
2414         } else {
2415             my @keys = keys %{$data->{$section}};
2416             if (defined $meta) {
2417                 @keys = sort {
2418                     $meta->{$section}{$a} <=> $meta->{$section}{$b}
2419                 } @keys
2420             }
2421             foreach my $key (@keys) {
2422                 my $val = $data->{$section}{$key};
2423                 say RCF "$key=$val";
2424             }
2425         }
2426         say RCF "";
2427     }
2428     close (RCF);
2429 }
2430
2431 sub backup_resource {
2432     my $rc = shift;
2433     my $rcbak = "$rc.backup";
2434     do {
2435         my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2436         log_message ($emsg);
2437         error_dialog ($emsg);
2438         return FALSE;
2439     } unless rename ($rc, $rcbak);
2440     return TRUE;
2441 }
2442
2443 # specific loaders
2444 sub load_menurc {
2445     my $rc = shift;
2446     open (RCF, '<:encoding(utf8)', $rc)
2447         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2448     my %groups = ();
2449     my $line = 0;
2450     while (<RCF>) {
2451         chomp;
2452         if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2453             my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2454             $groups{$1}{$2} = \%data;
2455             # say "group -> $1 | path -> $2 | key -> $3";
2456         } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2457             my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2458             $groups{$1}{$2} = \%data;
2459             # say "group -> $1 | path -> $2 | key -> $3";
2460         }
2461         ++$line;
2462     }
2463     close (RCF);
2464     return \%groups;
2465 }
2466
2467 sub save_menurc {
2468     my ($rc, $groups) = @_;
2469     my @lines = ();
2470     foreach my $gkey (keys %$groups) {
2471         my $group = $groups->{$gkey};
2472         foreach my $akey (keys %$group) {
2473             my $data = $group->{$akey};
2474             my $key = $data->{'key'};
2475             my $line = $data->{'line'};
2476             $lines[$line] = ($data->{'enabled'})? '': '; ';
2477             $lines[$line] .= '(gtk_accel_path "<'
2478                     . $gkey . '>' . $akey . '" ' . $key . ')';
2479         }
2480     }
2481     open (RCF, '>:utf8', $rc)
2482         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2483     say RCF '; claws-mail GtkAccelMap rc-file         -*- scheme -*-';
2484     say RCF '; this file is an automated accelerator map dump';
2485     say RCF ';';
2486     foreach (@lines) { say RCF $_ if $_ }
2487     close (RCF);
2488 }
2489
2490 # load current status from disc
2491 sub load_rc_preferences {
2492     my $rc = get_rc_filename ();
2493     log_message ("Loading preferences from $rc\n");
2494     return FALSE unless check_rc_file ($rc);
2495     ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2496     foreach (keys %{$CONFIGDATA->{'Common'}}) {
2497         $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2498     }
2499     foreach my $plugin (@PLUGINS) {
2500         if (defined $CONFIGDATA->{$plugin}) {
2501             push (@AVPLUGINS, $plugin);
2502             foreach (keys %{$CONFIGDATA->{$plugin}}) {
2503                 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2504             }
2505         }
2506     }
2507     return TRUE;
2508 }
2509
2510 sub load_ac_preferences {
2511     my $rc = get_ac_rc_filename ();
2512     log_message ("Loading account preferences from $rc\n");
2513     return FALSE unless check_rc_file ($rc);
2514     ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2515     foreach my $asect (keys %$ACCOUNTDATA) {
2516         if ($asect =~ /^Account: (\d+)$/) {
2517             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2518                 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2519             }
2520         }
2521     }
2522     return TRUE;
2523 }
2524
2525 sub load_hk_preferences {
2526     my $rc = get_menurc_filename ();
2527     return FALSE unless check_rc_file ($rc);
2528     $HOTKEYS = load_menurc ($rc);
2529     return TRUE;
2530 }
2531
2532 sub load_preferences {
2533     return FALSE unless check_claws_not_running ();
2534     return (load_rc_preferences ()
2535         and load_ac_preferences ()
2536         and load_hk_preferences ()
2537     );
2538 }
2539
2540 # save current preferences to disc
2541 sub save_rc_preferences {
2542     my $rc = get_rc_filename ();
2543     log_message ("Saving preferences to $rc\n");
2544     return FALSE unless check_rc_file ($rc);
2545     return FALSE unless backup_resource ($rc);
2546     foreach (keys %PREFS) {
2547         if (defined $HPVALUE{$_}) {
2548             $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2549         }
2550     }
2551     foreach my $plugin (@AVPLUGINS) {
2552         foreach (keys %{$CONFIGDATA->{$plugin}}) {
2553             if (defined $PLHPVALUE{$plugin}{$_}) {
2554                 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2555             }
2556         }
2557     }
2558     save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2559     return TRUE;
2560 }
2561
2562 sub save_ac_preferences {
2563     my $rc = get_ac_rc_filename ();
2564     log_message ("Saving account preferences to $rc\n");
2565     return FALSE unless check_rc_file ($rc);
2566     return FALSE unless backup_resource ($rc);
2567     foreach my $asect (keys %$ACCOUNTDATA) {
2568         if ($asect =~ /^Account: (\d+)$/) {
2569             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2570                 if (defined $ACHPVALUE{$1}{$_}) {
2571                     $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2572                 }
2573             }
2574         }
2575     }
2576     save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2577     return TRUE;
2578 }
2579
2580 sub save_hk_preferences {
2581     my $rc = get_menurc_filename ();
2582     log_message ("Saving hotkey preferences to $rc\n");
2583     return FALSE unless check_rc_file ($rc);
2584     return FALSE unless backup_resource ($rc);
2585     save_menurc ($rc, $HOTKEYS);
2586     return TRUE;
2587 }
2588
2589 sub save_preferences {
2590     return FALSE unless check_claws_not_running ();
2591     my $result = save_rc_preferences ()
2592         and save_ac_preferences ()
2593         and save_hk_preferences ();
2594     $MODIFIED = 0 if $result;
2595     return $result;
2596 }
2597
2598 # create notebook
2599 sub new_notebook {
2600     my $nb = Gtk3::Notebook->new;
2601
2602     $nb->append_page (&new_behaviour_page, Gtk3::Label->new (_('Behaviour')));
2603     $nb->append_page (&new_colours_page, Gtk3::Label->new (_('Colours')));
2604     $nb->append_page (&new_gui_page, Gtk3::Label->new (_('GUI')));
2605     $nb->append_page (&new_other_page, Gtk3::Label->new (_('Other')));
2606     $nb->append_page (&new_winpos_page, Gtk3::Label->new (_('Windows')));
2607     $nb->append_page (&new_accounts_page, Gtk3::Label->new (_('Accounts')));
2608     $nb->append_page (&new_plugins_page, Gtk3::Label->new (_('Plugins')));
2609     $nb->append_page (&new_hotkeys_page, Gtk3::Label->new (_('Hotkeys')));
2610     $nb->append_page (&new_info_page, Gtk3::Label->new (_('Info')));
2611
2612     return $nb;
2613 }
2614
2615 # create an about dialog
2616 sub new_about_dialog {
2617     my ($parent) = @_;
2618     my $year = '2007-2018';
2619     my $holder = 'Ricardo Mones <ricardo@mones.org>';
2620     my $url = 'http://www.claws-mail.org/clawsker.php';
2621     my $icons = &get_app_icons;
2622
2623     my $dialog = Gtk3::AboutDialog->new;
2624     $dialog->set_transient_for ($parent);
2625     $dialog->set_program_name ('Clawsker');
2626     $dialog->set_version ($VERSION);
2627     $dialog->set_copyright ("Copyright © $year $holder");
2628     $dialog->set_license_type ('gpl-3-0');
2629     $dialog->set_website ($url);
2630     $dialog->set_website_label (_("Visit Clawsker's web page"));
2631     # committers, by number of commits
2632     $dialog->set_authors ([
2633         $holder,
2634         'Tristan Chabredier (wwp) <subscript@free.fr>',
2635         'Andreas Rönnquist <andreas@ronnquist.net>',
2636         'Christian Hesse <mail@eworm.de>',
2637     ]);
2638     $dialog->set_artists ([
2639         'Jesper Schultz <jesper@schultz-net.dk>',
2640         $holder,
2641     ]);
2642     $dialog->set_documenters ([
2643         $holder,
2644         'Paul Mangan <paul@claws-mail.org>',
2645     ]);
2646     # active translators, in alphabetical order
2647     $dialog->set_translator_credits (join ("\n",
2648         'Andreas Rönnquist <andreas@ronnquist.net>',
2649         'Axel Köllhofer <AxelKoellhofer@web.de>',
2650         'David Medina <opensusecatala@gmail.com>',
2651         'Erik P. Olsen <erik@epo.dk>',
2652         'Frederico Goncalves Guimaraes <frederico@teia.bio.br>',
2653         'Marcel Pol <marcel@timelord.nl>',
2654         'Mark Chang <mark.cyj@gmail.com>',
2655         'M. Sulchan Darmawan <bleketux@gmail.com>',
2656         'Numan Demirdöğen <if.gnu.linux@gmail.com>',
2657         'Pedro Albuquerque <pmra@gmx.com>',
2658         'Petter Adsen <petter@synth.no>',
2659         $holder,
2660         'Tristan Chabredier (wwp) <subscript@free.fr>',
2661     ));
2662     $dialog->set_title ($xl::s{about_title});
2663     $dialog->set_logo ($icons->[-1]);
2664
2665     return $dialog;
2666 }
2667
2668 sub exit_handler {
2669   my ($parent) = @_;
2670   if ($MODIFIED != 0 and not $READONLY) {
2671     my $markup = "<span>" . _('There are unapplied modifications.')
2672         . "</span>\n\n<span weight=\"bold\">"
2673         . _('Do you really want to quit?') . "</span>\n";
2674     my $dialog = message_dialog (
2675         $parent, _('Clawsker warning'), $markup, 'question',
2676         [ 'gtk-no', 1, 'gtk-yes', 0 ]
2677     );
2678     my $resp = $dialog->run;
2679     $dialog->hide;
2680     return TRUE if $resp;
2681   }
2682   Gtk3->main_quit;
2683 }
2684
2685 # create buttons box
2686 sub new_button_box {
2687     my ($parent, $adlg) = @_;
2688     my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2689     my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2690     my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2691     # disable button until is really implemented
2692     # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2693     my $hbox = Gtk3::HBox->new (FALSE, 5);
2694     # signal handlers
2695     $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2696     $b_apply->set_sensitive (not $READONLY);
2697     $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2698     # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2699     $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2700     # package them
2701     $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2702     $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2703     # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2704     $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2705     #
2706     return $hbox;
2707 }
2708
2709 sub get_app_icons {
2710     return \@APPICONS if (@APPICONS);
2711     my @names;
2712     if (-d $DATADIR) { # installed
2713         my $dir = catdir ($DATADIR, 'icons', 'hicolor');
2714         @names = map {
2715             catfile ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png')
2716         } (48, 64, 128);
2717     } else { # unpacked tarball or git clone
2718         @names = map {
2719             catfile ('.', 'icons', $NAME . '-' . $_ . '.png');
2720         } (48, 64, 128);
2721     }
2722     foreach (@names) {
2723         my $icon = undef;
2724         $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2725         push @APPICONS, $icon if ($icon);
2726     }
2727     return \@APPICONS;
2728 }
2729
2730 sub escape_key_handler {
2731     my ($widget, $event) = @_;
2732     if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {
2733         exit_handler($widget);
2734     }
2735 }
2736
2737 # initialise
2738 exit unless parse_command_line ();
2739 Gtk3->init;
2740 $main_window = Gtk3::Window->new ('toplevel');
2741 exit unless load_preferences ();
2742 exit unless init_hidden_preferences ();
2743 # create main GUI
2744 my $box = Gtk3::VBox->new (FALSE, 5);
2745 $box->set_border_width(3);
2746 my $about = new_about_dialog ($main_window);
2747 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2748 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2749 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2750 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2751 $main_window->set_title (_('Claws Mail Hidden Preferences'));
2752 $main_window->set_icon_list (get_app_icons ());
2753 $main_window->add ($box);
2754 $main_window->show_all;
2755 $MODIFIED = 0;
2756 Gtk3->main;
2757