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