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