9d09671d1c553053657365c8d48671d52b8ec763
[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->sensitive);
571     $gunits->set_sensitive ($gentry->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->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->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::ComboBox->new_text;
632     my @options = split (';', $$hash{$key}[TYPE]);
633     foreach my $opt (@options) {
634         my ($index, $textkey) = split ('=', $opt);
635         $combo->insert_text ($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->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, _('Main'));
2005     $winbook->append_page (new_winpos_subpage_msgs, _('Message'));
2006     $winbook->append_page (new_winpos_subpage_sendrecv, _('Send/Receive'));
2007     $winbook->append_page (new_winpos_subpage_fold, _('Folder'));
2008     $winbook->append_page (new_winpos_subpage_addrbook, _('Addressbook'));
2009     $winbook->append_page (new_winpos_subpage_accounts, _('Accounts'));
2010     $winbook->append_page (new_winpos_subpage_filtering, _('Filtering'));
2011     $winbook->append_page (new_winpos_subpage_useractions, _('User Actions'));
2012     $winbook->append_page (new_winpos_subpage_prefs, _('Preferences'));
2013     $winbook->append_page (new_winpos_subpage_misc, _('Other'));
2014     return $winbook;
2015 }
2016
2017 %pr::acc = ( # per account hidden preferences
2018     tls_set => [
2019         'gnutls_set_priority',
2020         $xl::s{l_acc_gtls_set},
2021         $xl::s{h_acc_gtls_set},
2022         'bool',
2023         '3.9.0.181',
2024         '0',
2025     ],
2026     tls_pri => [
2027         'gnutls_priority',
2028         $xl::s{l_acc_gtls_pri},
2029         $xl::s{h_acc_gtls_pri},
2030         'char,0,256,32',
2031         '3.9.0.181',
2032         '0',
2033     ],
2034 );
2035
2036 sub new_account_subpage($) {
2037     my ($akey) = @_;
2038     return new_vbox_pack (
2039                 new_subpage_frame (
2040                     new_vbox_pack (
2041                         new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}),
2042                         new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey})),
2043                     _('GnuTLS priority'), 'not-packed')
2044            );
2045 }
2046
2047 sub new_accounts_page() {
2048     my $accbook = Gtk3::Notebook->new;
2049     $accbook->set_tab_pos ('right');
2050     my @akeys = sort {
2051         $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
2052     } keys %ACPREFS;
2053     foreach (@akeys) {
2054         my $name = $ACPREFS{$_}{'account_name'};
2055         my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
2056         my $page = new_account_subpage ($_);
2057         $accbook->append_page ($page, $isdef? '<b>' . $name . '</b>': $name);
2058         if ($isdef) {
2059             my $label = $accbook->get_tab_label ($page);
2060             $label->set_use_markup (TRUE);
2061         }
2062     }
2063     $accbook->set_scrollable (TRUE);
2064     return $accbook;
2065 }
2066
2067 %pr::plu = ( # plugins hidden preferences
2068     # att_remover
2069     arm_winw => [
2070         'win_width',
2071         $xl::s{l_win_w},
2072         $xl::s{h_win_w},
2073         'int,0,3000', # 0 pixels - 3000 pixels
2074         '3.9.0.74',
2075         '-1',
2076         'AttRemover',
2077     ],
2078     arm_winh => [
2079         'win_height',
2080         $xl::s{l_win_h},
2081         $xl::s{h_win_h},
2082         'int,0,3000', # 0 pixels - 3000 pixels
2083         '3.9.0.74',
2084         '-1',
2085         'AttRemover',
2086     ],
2087     # GPG
2088     gpg_alimit => [
2089         'autocompletion_limit',
2090         $xl::s{l_plu_gpg_alimit},
2091         $xl::s{h_plu_gpg_alimit},
2092         'int,0,100',
2093         '3.12.0.75',
2094         '0',
2095         'GPG',
2096     ],
2097     # managesieve
2098     msv_winw => [
2099         'manager_win_width',
2100         $xl::s{l_win_w},
2101         $xl::s{h_win_w},
2102         'int,0,3000', # 0 pixels - 3000 pixels
2103         '3.11.1.210',
2104         '-1',
2105         'ManageSieve',
2106     ],
2107     msv_winh => [
2108         'manager_win_height',
2109         $xl::s{l_win_h},
2110         $xl::s{h_win_h},
2111         'int,0,3000', # 0 pixels - 3000 pixels
2112         '3.11.1.210',
2113         '-1',
2114         'ManageSieve',
2115     ],
2116     # libravatar
2117     lav_burl => [
2118         'base_url',
2119         $xl::s{l_plu_lav_burl},
2120         $xl::s{h_plu_lav_burl},
2121         'char,0,1024,32',
2122         '3.9.3.32',
2123         'http://cdn.libravatar.org/avatar',
2124         'Libravatar',
2125     ],
2126     # perl
2127     prl_flvb => [
2128         'filter_log_verbosity',
2129         $xl::s{l_plu_prl_flvb},
2130         $xl::s{h_plu_prl_flvb},
2131         '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2132         '3.9.0.75',
2133         '2',
2134         'PerlPlugin',
2135     ],
2136 );
2137
2138 sub new_plugins_page() {
2139     my %frame = ();
2140     $frame{'AttRemover'} =
2141                 new_subpage_frame (
2142                      new_hbox_pack (
2143                           new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2144                           new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})),
2145                      _('Attachment remover'), 'not-packed');
2146     $frame{'GPG'} =
2147                 new_subpage_frame (
2148                      new_hbox_pack (
2149                           new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})),
2150                      _('GPG'), 'not-packed');
2151     $frame{'ManageSieve'} =
2152                 new_subpage_frame (
2153                      new_hbox_pack (
2154                           new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2155                           new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})),
2156                      _('Sieve manager'), 'not-packed');
2157     $frame{'Libravatar'} =
2158                 new_subpage_frame (
2159                      new_hbox_pack (
2160                           new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'})),
2161                      _('Libravatar'), 'not-packed');
2162     $frame{'PerlPlugin'} =
2163                 new_subpage_frame (
2164                      new_hbox_pack (
2165                           new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})),
2166                      _('Perl'), 'not-packed');
2167     foreach (@PLUGINS) {
2168         $frame{$_}->set_sensitive (defined $PLHPVALUE{$_});
2169     }
2170     return new_vbox_pack (
2171                 $frame{'AttRemover'},
2172                 $frame{'GPG'},
2173                 $frame{'ManageSieve'},
2174                 $frame{'Libravatar'},
2175                 $frame{'PerlPlugin'});
2176 }
2177
2178 use constant {
2179     C_LABEL => 0,
2180     C_HOTKEY => 1,
2181     C_GROUP => 2,
2182     C_ACCEL => 3,
2183     C_BCOLOR => 4,
2184     # cell backgrounds
2185     BG_LIGHTER => '#ffffff',
2186     BG_DARKER => '#eeeeee'
2187 };
2188
2189 sub new_hotkeys_list_label {
2190     my $renderer = Gtk3::CellRendererText->new ();
2191     $renderer->set_property('alignment' => 'left');
2192     $renderer->set_property('editable' => FALSE);
2193     return $renderer;
2194 }
2195
2196 sub new_hotkeys_list_hotkey {
2197     my $renderer = Gtk3::CellRendererAccel->new ();
2198     $renderer->set_property ('accel-mode' => 'gtk');
2199     $renderer->set_property ('editable' => TRUE);
2200     $renderer->signal_connect ('accel-edited' => sub {
2201         my ($w, $path, $key, $mods, $keycode) = @_;
2202         my $accel = Gtk3::Accelerator->name ($key, $mods);
2203         my ($model, $iter) = $SELHOTKEY->get_selected ();
2204         $model->set($iter, C_HOTKEY, "\"$accel\"");
2205         my $gkey = $model->get_value ($iter, C_GROUP);
2206         my $akey = $model->get_value ($iter, C_ACCEL);
2207         my $data = $HOTKEYS->{$gkey}->{$akey};
2208         $data->{'key'} = "\"$accel\"";
2209         $data->{'enabled'} = TRUE;
2210     });
2211     $renderer->signal_connect ('accel-cleared' => sub {
2212         my ($w, $path) = @_;
2213         my ($model, $iter) = $SELHOTKEY->get_selected ();
2214         $model->set($iter, C_HOTKEY, "\"\"");
2215         my $gkey = $model->get_value ($iter, C_GROUP);
2216         my $akey = $model->get_value ($iter, C_ACCEL);
2217         my $data = $HOTKEYS->{$gkey}->{$akey};
2218         $data->{'key'} = "\"\"";
2219         $data->{'enabled'} = FALSE;
2220     });
2221     return $renderer;
2222 }
2223
2224 sub new_hotkeys_list {
2225     my ($gkey, $group) = @_;
2226     my $store = Gtk3::ListStore->new(
2227         qw/Glib::String Glib::String Glib::String Glib::String Glib::String/);
2228     my $even = FALSE;
2229     foreach my $akey (sort keys %$group) {
2230         my $iter = $store->append ();
2231         my $hotkey = $group->{$akey}->{'key'};
2232         my $label = $akey;
2233         $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2234         my $bgcol = $even ? BG_DARKER: BG_LIGHTER;
2235         $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2236             C_GROUP, $gkey, C_ACCEL, $akey, C_BCOLOR, $bgcol);
2237         $even = not $even;
2238     }
2239     my $treeview = Gtk3::TreeView->new_with_model ($store);
2240     # labels column
2241     $treeview->insert_column_with_data_func (
2242         0, _("Menu path"), new_hotkeys_list_label (),
2243         sub {
2244             my ($col, $renderer, $model, $iter, $data) = @_;
2245             $renderer->set_property (
2246                 'markup' => '<span size="smaller">'
2247                             . $model->get_value ($iter, C_LABEL)
2248                             . '</span>');
2249             $renderer->set_property (
2250                 'background' => $model->get_value ($iter, C_BCOLOR));
2251         }
2252     );
2253     # hotkeys column
2254     $treeview->insert_column_with_data_func (
2255         1, _('Hotkey'), new_hotkeys_list_hotkey (),
2256         sub {
2257             my ($col, $renderer, $model, $iter, $data) = @_;
2258             my $hkey = $model->get_value ($iter, C_HOTKEY);
2259             $hkey =~ s/\"//g;
2260             my ($acckey, $accmod) = Gtk3::Accelerator->parse ($hkey);
2261             $renderer->set_property ('accel-key' => $acckey);
2262             $renderer->set_property ('accel-mods' => $accmod);
2263             $renderer->set_property (
2264                 'background' => $model->get_value ($iter, C_BCOLOR));
2265         }
2266     );
2267     # callback for saving current selection
2268     my $selection = $treeview->get_selection ();
2269     $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2270     return $treeview;
2271 }
2272
2273 sub new_hotkeys_page() {
2274     my $swin = Gtk3::ScrolledWindow->new ();
2275     my $vbox = Gtk3::VBox->new (FALSE, 5);
2276     foreach my $gkey (sort keys %$HOTKEYS) {
2277         my $group = $HOTKEYS->{$gkey};
2278         # group title
2279         my $glabel = Gtk3::Label->new ('<b>' . $gkey . '</b>');
2280         $glabel->set_use_markup (TRUE);
2281         $glabel->set_alignment (0, 0.5);
2282         $glabel->set_padding (5, 1);
2283         $vbox->pack_start ($glabel, FALSE, FALSE, 0);
2284         # group key list
2285         my $keylist = new_hotkeys_list ($gkey, $group);
2286         $vbox->pack_start ($keylist, FALSE, FALSE, 0);
2287     }
2288     $swin->set_border_width (5);
2289     $swin->set_shadow_type ('none');
2290     $swin->set_policy ('automatic', 'always');
2291     $swin->add_with_viewport ($vbox);
2292     return $swin;
2293 }
2294
2295 sub new_info_page() {
2296     my $t0 = Gtk3::Table->new (7, 2, FALSE);
2297     my $v = get_toolkit_versions ();
2298     my %labels = (
2299         'glib' => 'Perl-GLib',
2300         'glib-r' => _('GLib runtime'),
2301         'glib-b' => _('GLib built'),
2302         'gtk2' => 'Perl-GTK2',
2303         'gtk2-r' => _('GTK2 runtime'),
2304         'gtk2-b' => _('GTK2 built'),
2305     );
2306     my $row = 0;
2307     foreach (sort keys %$v) {
2308         if (defined $v->{$_}) {
2309             my $label = Gtk3::Label->new ($labels{$_});
2310             my $value = Gtk3::Label->new ('<b>' . $v->{$_} . '</b>');
2311             $label->set_alignment (0, 0.5);
2312             $value->set_alignment (0, 0.5);
2313             $value->set_use_markup (TRUE);
2314             $t0->attach ($label, 0, 1, $row, $row + 1, 'fill', 'shrink', 8, 6);
2315             $t0->attach ($value, 1, 2, $row, $row + 1, 'fill', 'shrink', 8, 6);
2316             ++$row;
2317         }
2318     }
2319     my $t1 = Gtk3::Table->new (2, 2, FALSE);
2320     my @lbl = map { $_->set_alignment (0, 0.5); $_ } (
2321         Gtk3::Label->new (_('Binary')),
2322         Gtk3::Label->new (_('Configuration'))
2323     );
2324     my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2325     $cfgv //= '';
2326     my @val = map { $_->set_alignment (0, 0.5); $_->set_use_markup (TRUE); $_ } (
2327         Gtk3::Label->new ('<b>' . $CLAWSV . '</b>'),
2328         Gtk3::Label->new ('<b>' . $cfgv . '</b>')
2329     );
2330     for (my $i = 0; $i <= $#lbl; ++$i) {
2331         $t1->attach ($lbl[$i], 0, 1, $i, $i + 1, 'fill', 'shrink', 8, 6);
2332         $t1->attach ($val[$i], 1, 2, $i, $i + 1, 'fill', 'shrink', 8, 6);
2333     }
2334     return new_vbox_pack (
2335                 new_subpage_frame ($t0, _('Library versions'), 'not-packed'),
2336                 new_subpage_frame ($t1, _('Claws Mail versions'), 'not-packed'));
2337 }
2338
2339 # version info
2340 sub get_toolkit_versions {
2341     my %versions = ();
2342     $versions{'glib'} = $Glib::VERSION;
2343     # version info stuff appeared in 1.040
2344     if ($Glib::VERSION >= 1.040) {
2345         $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2346         $versions{'glib-r'} = join('.',
2347             &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2348     }
2349     $versions{'gtk2'} = $Gtk3::VERSION;
2350     if ($Gtk3::VERSION >= 1.040) {
2351         $versions{'gtk2-b'} = join('.', Gtk3->GET_VERSION_INFO);
2352         $versions{'gtk2-r'} = join('.',
2353             &Gtk3::major_version, &Gtk3::minor_version, &Gtk3::micro_version);
2354     }
2355     return \%versions;
2356 }
2357
2358 sub print_version() {
2359     print $xl::s{about_title} . "\n";
2360     print $xl::s{about_version} . " $VERSION\n";
2361     my $v = get_toolkit_versions ();
2362     if ($v->{'glib-b'}) {
2363         print _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2364                 glibv => $v->{'glib'},
2365                 glibb => $v->{'glib-b'},
2366                 glibr => $v->{'glib-r'});
2367     } else {
2368         print _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2369     }
2370     print "\n";
2371     if ($v->{'gtk2-b'}) {
2372         print _("Perl-GTK2 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2373                 gtkv => $v->{'gtk2'},
2374                 gtkb => $v->{'gtk2-b'},
2375                 gtkr => $v->{'gtk2-r'});
2376     } else {
2377         print _("Perl-GTK2 version {gtkv}.", gtkv => $v->{'gtk2'});
2378     }
2379     print "\n";
2380     my $clawsver = ($CLAWSV eq "") ?
2381                 _("Claws Mail was not found!") :
2382                 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2383     print $clawsver . "\n";
2384 }
2385
2386 # the command line help
2387 sub print_help() {
2388     my $line = '-' x length ($xl::s{about_title});
2389     say $line;
2390     say $xl::s{about_title};
2391     say $line;
2392     my @help = (
2393         _("Syntax:"),
2394         _("  clawsker [options]"),
2395         _("Options:"),
2396         _("  -a|--alternate-config-dir <dir>  Uses <dir> as Claws Mail configuration."),
2397         _("  -b|--verbose                     More messages on standard output."),
2398         _("  -c|--clawsrc <file>              Uses <file> as full resource name."),
2399         _("  -h|--help                        Prints this help screen and exits."),
2400         _("  -r|--read-only                   Disables writing changes to disk."),
2401         _("  -v|--version                     Prints version information and exits.")
2402     );
2403     foreach (@help) { say $_ }
2404 }
2405
2406 sub parse_command_line {
2407     my $cont = TRUE;
2408     $CLAWSV = get_claws_version ();
2409     eval {
2410         GetOptions('h|help' => sub { print_help (); $cont = FALSE },
2411             'v|version' => sub { print_version (); $cont = FALSE },
2412             'b|verbose' => sub { $VERBOSE = TRUE },
2413             'r|read-only' => sub { $READONLY = TRUE },
2414             'u|use-claws-version=s' => \&opt_use_claws_version,
2415             'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2416             'c|clawsrc=s' => \&opt_clawsrc)
2417         or die _("try -h or --help for syntax.\n");
2418     };
2419     if ($@) {
2420         my $msg = _("Error in options: {msg}\n", msg => $@);
2421         if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2422             eval { Gtk3->init };
2423             error_dialog ($msg) unless $@;
2424         }
2425         die $msg;
2426     }
2427     return $cont;
2428 }
2429
2430 sub opt_use_claws_version {
2431     my ($name, $value) = @_;
2432     die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2433         unless ($value =~ /^[\d\.]+$/);
2434     $CLAWSV = $value;
2435 }
2436
2437 sub opt_alternate_config_dir {
2438     my ($name, $value) = @_;
2439     die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2440         unless -d $value;
2441     $CONFIGDIR = $value;
2442     $CONFIGDIR .= "/" unless ($CONFIGDIR =~ /.*\/$/);
2443     $ALTCONFIGDIR = TRUE;
2444 }
2445
2446 sub opt_clawsrc {
2447     my ($name, $value) = @_;
2448     die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2449         unless -f $value;
2450     set_rc_filename ($value);
2451 }
2452
2453 # update the hidden preferences status from loaded values
2454 sub init_hidden_preferences {
2455     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2456         foreach my $key (keys %$hash) {
2457             $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2458             $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2459         }
2460     }
2461     foreach my $akey (keys %ACPREFS) {
2462         foreach my $key (keys %pr::acc) {
2463             my $pname = $pr::acc{$key}[NAME];
2464             $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2465             $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2466         }
2467     }
2468     foreach my $key (keys %pr::plu) {
2469         my $plugin = $pr::plu{$key}[PLUGIN];
2470         my $pname = $pr::plu{$key}[NAME];
2471         if (defined $PLPREFS{$plugin}) {
2472             $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2473             $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2474         }
2475     }
2476     return TRUE;
2477 }
2478
2479 # generic load/save resource files
2480 sub load_resource {
2481     my $rc = shift;
2482     my %data = ();
2483     my %meta = ();
2484     my $line = 0;
2485     open (RCF, '<:encoding(utf8)', $rc)
2486         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2487     my $section = '_'; # default unnamed section
2488     while (<RCF>) {
2489         chomp;
2490         ++$line;
2491         next if (/^\s*$/);
2492         if (/^\[([^\]]+)\]$/) { # new section
2493             $section = $1;
2494             die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2495                 sect => $section, file => $rc) if ($data{$section});
2496             $data{$section} = {};
2497             $meta{$section}{'#'} = $line;
2498         }
2499         elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2500             $data{$section}{$1} = $2;
2501             $meta{$section}{$1} = $line;
2502         }
2503         elsif (/^(.*)$/) { # lone value
2504             push (@{$data{$section}{'_'}}, $1);
2505         }
2506     }
2507     close (RCF);
2508     return (\%data, \%meta);
2509 }
2510
2511 sub save_resource {
2512     my ($rc, $data, $meta) = @_;
2513     open (RCF, '>:utf8', $rc)
2514         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2515     my @sections = keys %$data;
2516     if (defined $meta) {
2517         @sections = sort {
2518             $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2519         } @sections
2520     }
2521     foreach my $section (@sections) {
2522         say RCF "[$section]";
2523         if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2524             foreach my $val (@{$data->{$section}{'_'}}) {
2525                 say RCF $val;
2526             }
2527         } else {
2528             my @keys = keys %{$data->{$section}};
2529             if (defined $meta) {
2530                 @keys = sort {
2531                     $meta->{$section}{$a} <=> $meta->{$section}{$b}
2532                 } @keys
2533             }
2534             foreach my $key (@keys) {
2535                 my $val = $data->{$section}{$key};
2536                 say RCF "$key=$val";
2537             }
2538         }
2539         say RCF "";
2540     }
2541     close (RCF);
2542 }
2543
2544 sub backup_resource {
2545     my $rc = shift;
2546     my $rcbak = "$rc.backup";
2547     do {
2548         my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2549         log_message ($emsg);
2550         error_dialog ($emsg);
2551         return FALSE;
2552     } unless rename ($rc, $rcbak);
2553     return TRUE;
2554 }
2555
2556 # specific loaders
2557 sub load_menurc {
2558     my $rc = shift;
2559     open (RCF, '<:encoding(utf8)', $rc)
2560         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2561     my %groups = ();
2562     my $line = 0;
2563     while (<RCF>) {
2564         chomp;
2565         if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2566             my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2567             $groups{$1}{$2} = \%data;
2568             # say "group -> $1 | path -> $2 | key -> $3";
2569         } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2570             my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2571             $groups{$1}{$2} = \%data;
2572             # say "group -> $1 | path -> $2 | key -> $3";
2573         }
2574         ++$line;
2575     }
2576     close (RCF);
2577     return \%groups;
2578 }
2579
2580 sub save_menurc {
2581     my ($rc, $groups) = @_;
2582     my @lines = ();
2583     foreach my $gkey (keys %$groups) {
2584         my $group = $groups->{$gkey};
2585         foreach my $akey (keys %$group) {
2586             my $data = $group->{$akey};
2587             my $key = $data->{'key'};
2588             my $line = $data->{'line'};
2589             $lines[$line] = ($data->{'enabled'})? '': '; ';
2590             $lines[$line] .= '(gtk_accel_path "<'
2591                     . $gkey . '>' . $akey . '" ' . $key . ')';
2592         }
2593     }
2594     open (RCF, '>:utf8', $rc)
2595         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2596     say RCF '; claws-mail GtkAccelMap rc-file         -*- scheme -*-';
2597     say RCF '; this file is an automated accelerator map dump';
2598     say RCF ';';
2599     foreach (@lines) { say RCF $_ if $_ }
2600     close (RCF);
2601 }
2602
2603 # load current status from disc
2604 sub load_rc_preferences {
2605     my $rc = get_rc_filename ();
2606     log_message ("Loading preferences from $rc\n");
2607     return FALSE unless check_rc_file ($rc);
2608     ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2609     foreach (keys %{$CONFIGDATA->{'Common'}}) {
2610         $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2611     }
2612     foreach my $plugin (@PLUGINS) {
2613         if (defined $CONFIGDATA->{$plugin}) {
2614             push (@AVPLUGINS, $plugin);
2615             foreach (keys %{$CONFIGDATA->{$plugin}}) {
2616                 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2617             }
2618         }
2619     }
2620     return TRUE;
2621 }
2622
2623 sub load_ac_preferences {
2624     my $rc = get_ac_rc_filename ();
2625     log_message ("Loading account preferences from $rc\n");
2626     return FALSE unless check_rc_file ($rc);
2627     ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2628     foreach my $asect (keys %$ACCOUNTDATA) {
2629         if ($asect =~ /^Account: (\d+)$/) {
2630             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2631                 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2632             }
2633         }
2634     }
2635     return TRUE;
2636 }
2637
2638 sub load_hk_preferences {
2639     my $rc = get_menurc_filename ();
2640     return FALSE unless check_rc_file ($rc);
2641     $HOTKEYS = load_menurc ($rc);
2642     return TRUE;
2643 }
2644
2645 sub load_preferences {
2646     return FALSE unless check_claws_not_running ();
2647     return (load_rc_preferences ()
2648         and load_ac_preferences ()
2649         and load_hk_preferences ()
2650     );
2651 }
2652
2653 # save current preferences to disc
2654 sub save_rc_preferences {
2655     my $rc = get_rc_filename ();
2656     log_message ("Saving preferences to $rc\n");
2657     return FALSE unless check_rc_file ($rc);
2658     return FALSE unless check_claws_not_running ();
2659     return FALSE unless backup_resource ($rc);
2660     foreach (keys %PREFS) {
2661         if (defined $HPVALUE{$_}) {
2662             $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2663         }
2664     }
2665     foreach my $plugin (@AVPLUGINS) {
2666         foreach (keys %{$CONFIGDATA->{$plugin}}) {
2667             if (defined $PLHPVALUE{$plugin}{$_}) {
2668                 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2669             }
2670         }
2671     }
2672     save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2673     return TRUE;
2674 }
2675
2676 sub save_ac_preferences {
2677     my $rc = get_ac_rc_filename ();
2678     log_message ("Saving account preferences to $rc\n");
2679     return FALSE unless check_rc_file ($rc);
2680     return FALSE unless check_claws_not_running ();
2681     return FALSE unless backup_resource ($rc);
2682     foreach my $asect (keys %$ACCOUNTDATA) {
2683         if ($asect =~ /^Account: (\d+)$/) {
2684             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2685                 if (defined $ACHPVALUE{$1}{$_}) {
2686                     $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2687                 }
2688             }
2689         }
2690     }
2691     save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2692     return TRUE;
2693 }
2694
2695 sub save_hk_preferences {
2696     my $rc = get_menurc_filename ();
2697     log_message ("Saving hotkey preferences to $rc\n");
2698     return FALSE unless check_rc_file ($rc);
2699     return FALSE unless check_claws_not_running ();
2700     return FALSE unless backup_resource ($rc);
2701     save_menurc ($rc, $HOTKEYS);
2702     return TRUE;
2703 }
2704
2705 sub save_preferences {
2706     my $result = save_rc_preferences ()
2707         and save_ac_preferences ()
2708         and save_hk_preferences ();
2709     $MODIFIED = 0 if $result;
2710     return $result;
2711 }
2712
2713 # create notebook
2714 sub new_notebook {
2715     my $nb = Gtk3::Notebook->new;
2716     #
2717     $nb->append_page (new_behaviour_page (), $xl::s{tab_behaviour});
2718     $nb->append_page (new_colours_page (), $xl::s{tab_colours});
2719     $nb->append_page (new_gui_page (), $xl::s{tab_gui});
2720     $nb->append_page (new_other_page (), $xl::s{tab_other});
2721     $nb->append_page (new_winpos_page (), $xl::s{tab_winpos});
2722     $nb->append_page (new_accounts_page (), $xl::s{tab_accounts});
2723     $nb->append_page (new_plugins_page (), $xl::s{tab_plugins});
2724     $nb->append_page (new_hotkeys_page (), $xl::s{tab_hotkeys});
2725     $nb->append_page (new_info_page (), $xl::s{tab_info});
2726
2727     return $nb;
2728 }
2729
2730 # create an about dialog
2731 sub new_about_dialog {
2732     my ($parent) = @_;
2733     my $title = $xl::s{about_title};
2734     my $lic = $xl::s{about_license};
2735     my $vers = $xl::s{about_version} . " $VERSION";
2736     my $license =
2737 "This program is free software: you can redistribute it and/or modify
2738 it under the terms of the GNU General Public License as published by
2739 the Free Software Foundation, either version 3 of the License, or
2740 (at your option) any later version.
2741
2742 This program is distributed in the hope that it will be useful,
2743 but WITHOUT ANY WARRANTY; without even the implied warranty of
2744 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2745 GNU General Public License for more details.
2746
2747 You should have received a copy of the GNU General Public License
2748 along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.";
2749     my $year = "2007-2018";
2750     my $holder = "Ricardo Mones &lt;ricardo\@mones.org&gt;";
2751     my $url = "http://www.claws-mail.org/clawsker.php";
2752
2753     my $dialog = Gtk3::MessageDialog->new ($parent,
2754         [qw/modal destroy-with-parent/], 'info', 'close');
2755     $dialog->set_markup (
2756         "<span size=\"x-large\" weight=\"bold\">$title</span>\n"
2757         . "<span size=\"large\">$vers</span>\n\n"
2758         . "<span color=\"blue\" size=\"large\">$url</span>\n\n"
2759         . "<span>Copyright $year by $holder</span>\n\n"
2760         . "<span size=\"large\">$lic</span>\n\n"
2761         . "<span size=\"small\">$license</span>");
2762     $dialog->set_title ($xl::s{about});
2763     if (Gtk3->CHECK_VERSION (2, 10, 0)) {
2764         my @icons = get_app_icons ();
2765         my $image = Gtk3::Image->new_from_pixbuf ($icons[-1]);
2766         $image->show ();
2767         $image->set_alignment (0, 0);
2768         $dialog->set_image ($image);
2769     }
2770     #
2771     return $dialog;
2772 }
2773
2774 sub exit_handler {
2775   my ($parent) = @_;
2776   if ($MODIFIED != 0 and not $READONLY) {
2777     my $fact = $xl::s{exit_fact};
2778     my $question = $xl::s{exit_question};
2779     my $dialog = Gtk3::MessageDialog->new ($parent,
2780         [qw/modal destroy-with-parent/], 'warning', 'yes-no');
2781     $dialog->set_markup ("<span>$fact</span>\n\n"
2782         . "<span weight=\"bold\">$question</span>");
2783     $dialog->set_title ($xl::s{exit_title});
2784     my $resp = $dialog->run;
2785     $dialog->hide;
2786     return TRUE if ($resp eq 'no');
2787   }
2788   Gtk3->main_quit;
2789 }
2790
2791 # create buttons box
2792 sub new_button_box {
2793     my ($parent, $adlg) = @_;
2794     my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2795     my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2796     my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2797     # disable button until is really implemented
2798     # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2799     my $hbox = Gtk3::HBox->new (FALSE, 5);
2800     # signal handlers
2801     $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2802     $b_apply->set_sensitive (not $READONLY);
2803     $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2804     # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2805     $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2806     # package them
2807     $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2808     $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2809     # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2810     $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2811     #
2812     return $hbox;
2813 }
2814
2815 sub get_app_icons {
2816     return @APPICONS if (@APPICONS);
2817     my @names;
2818     if (-d $DATADIR) { # installed
2819         my $dir = $DATADIR . '/icons/hicolor';
2820         @names = map {
2821             join ('/', ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png'))
2822         } (48, 64, 128);
2823     } else { # unpacked tarball or git clone
2824         @names = map {
2825             join ('/', ('./icons', $NAME . '-' . $_ . '.png'));
2826         } (48, 64, 128);
2827     }
2828     foreach (@names) {
2829         my $icon = undef;
2830         $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2831         push @APPICONS, $icon if ($icon);
2832     }
2833     return @APPICONS;
2834 }
2835
2836 sub escape_key_handler {
2837     my ($widget, $event) = @_;
2838     if ($event->keyval == Gtk3::Gdk->keyval_from_name('Escape')) {
2839         exit_handler($widget);
2840     }
2841 }
2842
2843 # initialise
2844 exit unless parse_command_line ();
2845 Gtk3->init;
2846 $main_window = Gtk3::Window->new ('toplevel');
2847 exit unless load_preferences ();
2848 exit unless init_hidden_preferences ();
2849 # create main GUI
2850 my $box = Gtk3::VBox->new (FALSE, 5);
2851 $box->set_border_width(3);
2852 my $about = new_about_dialog ();
2853 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2854 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2855 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2856 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2857 $main_window->set_title ($xl::s{win_title});
2858 $main_window->set_icon_list (get_app_icons ());
2859 $main_window->add ($box);
2860 $main_window->show_all;
2861 $MODIFIED = 0;
2862 Gtk3->main;
2863