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