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