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