sed -i 's,Gtk2,Gtk3,' clawsker
[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_with_markup ($main_window, 'modal', 'error', 'cancel', $markup);
457     $errordlg->set_title (_('Clawsker error'));
458     $errordlg->run;
459     $errordlg->destroy;
460 }
461
462 sub claws_is_running {
463     my $emsg = _('Error: seems Claws Mail is currently running, close it first.');
464     log_message ($emsg);
465     error_dialog ($emsg);
466     return FALSE;
467 }
468
469 sub check_claws_not_running {
470     return TRUE if $READONLY;
471     my $tmpdir = (defined $ENV{TMPDIR})? $ENV{TMPDIR}: '/tmp';
472     $tmpdir = '/tmp' if ($tmpdir eq '');
473     my $lockdir = "$tmpdir/claws-mail-$<";
474     -d $lockdir and do {
475         $_ = $CONFIGDIR;
476         s/\/$//;
477         my $socket = "$lockdir/" . md5_hex ($_);
478         -S $socket and return claws_is_running ();
479     };
480     return TRUE;
481 }
482
483 sub check_rc_file {
484     my ($rcfile) = @_;
485     (defined($rcfile) && -f $rcfile) or do {
486         my $emsg = _('Error: resource file for Claws Mail was not found.');
487         log_message ($emsg);
488         error_dialog ($emsg);
489         return FALSE;
490     };
491     return TRUE;
492 }
493
494 sub set_widget_hint {
495     if ($SHOWHINTS) {
496         my ($wdgt, $hint) = @_;
497         $wdgt->set_tooltip_text ($hint);
498         $wdgt->set_has_tooltip (TRUE);
499     }
500 }
501
502 sub set_widget_sens {
503     my ($wdgt, $versions) = @_;
504     my @ver = split(/,/, $versions);
505     if ($#ver == 1) {
506       $wdgt->set_sensitive (
507         version_greater_or_equal ($CLAWSV, $ver[0])
508         and version_greater_or_equal ($ver[1], $CLAWSV)
509       );
510     } else {
511         $wdgt->set_sensitive (version_greater_or_equal ($CLAWSV, $ver[0]));
512     }
513 }
514
515 # graphic element creation
516
517 sub new_hbox_spaced_pack {
518     my $hbox = Gtk3::HBox->new (FALSE);
519     foreach (@_) {
520         $hbox->pack_start ($_, FALSE, FALSE, HBOX_PAD);
521     }
522     return $hbox;
523 }
524
525 sub new_check_button_for($$$) {
526     my ($hash, $key, $vhash) = @_;
527     my $name = $$hash{$key}[NAME];
528     my $label = $$hash{$key}[LABEL];
529     #
530     my $cb = Gtk3::CheckButton->new ($label);
531     my $value = $$vhash{$name}[VALUE];
532     $value //= $$hash{$key}[CMDEF];
533     $cb->set_active ($value eq '1');
534     $cb->signal_connect (clicked => sub {
535             my ($w, $e) = @_;
536             handle_bool_value ($w, $e, \$$vhash{$name});
537         });
538     set_widget_hint ($cb, $$hash{$key}[DESC]);
539     set_widget_sens ($cb, $$hash{$key}[CMVER]);
540     #
541     return new_hbox_spaced_pack ($cb);
542 }
543
544 sub new_text_box_for_int($$$) {
545     my ($hash, $key, $vhash) = @_;
546     my $name = $$hash{$key}[NAME];
547     my $label = $$hash{$key}[LABEL];
548     my @type = split (/,/, $$hash{$key}[TYPE]);
549     push (@type, 0), push (@type, 10000) unless ($#type > 0);
550     #
551     my $gunits = undef;
552     if (ref $label eq 'ARRAY') {
553         $gunits = Gtk3::Label->new ($label->[1]);
554         $label = $label->[0];
555     }
556     my $glabel = Gtk3::Label->new ($label);
557     my $pagei = int (($type[2] - $type[1]) / 10);
558     my $gentry = Gtk3::SpinButton->new_with_range ($type[1], $type[2], $pagei);
559     my $value = $$vhash{$name}[VALUE];
560     $value //= $$hash{$key}[CMDEF];
561     $gentry->set_numeric (TRUE);
562     $gentry->set_value ($value);
563     $gentry->signal_connect('value-changed' => sub {
564             my ($w, $e) = @_;
565             handle_int_value ($w, $e, \$$vhash{$name});
566         });
567     set_widget_hint ($gentry, $$hash{$key}[DESC]);
568     set_widget_sens ($gentry, $$hash{$key}[CMVER]);
569     $glabel->set_sensitive ($gentry->sensitive);
570     $gunits->set_sensitive ($gentry->sensitive) if ($gunits);
571     #
572     return new_hbox_spaced_pack ($glabel, $gentry, $gunits) if ($gunits);
573     return new_hbox_spaced_pack ($glabel, $gentry);
574 }
575
576 sub new_text_box_for_nchar($$$) {
577     my ($hash, $key, $vhash) = @_;
578     my $name = $$hash{$key}[NAME];
579     my $label = $$hash{$key}[LABEL];
580     my @type = split (/,/, $$hash{$key}[TYPE]); # char,minlen,maxlen,width
581     my $glabel = Gtk3::Label->new ($label);
582     my $gentry = Gtk3::Entry->new ();
583     $gentry->set_max_length($type[2]) if defined ($type[2]);
584     my $width = $type[3];
585     $width //= $type[2];
586     $gentry->set_width_chars(int ($width) + 2) if defined ($width);
587     my $value = $$vhash{$name}[VALUE];
588     $value //= $$hash{$key}[CMDEF];
589     $gentry->set_text ($value);
590     $gentry->signal_connect('key-release-event' => sub {
591             my ($w, $e) = @_;
592             handle_nchar_value ($w, $e, \$$vhash{$name}, $type[1], $type[2]);
593         });
594     set_widget_hint ($gentry, $$hash{$key}[DESC]);
595     set_widget_sens ($gentry, $$hash{$key}[CMVER]);
596     $glabel->set_sensitive ($gentry->sensitive);
597     #
598     return new_hbox_spaced_pack ($glabel, $gentry);
599 }
600
601 sub new_color_button_for($$$) {
602     my ($hash, $key, $vhash) = @_;
603     my $name = $$hash{$key}[NAME];
604     my $label = $$hash{$key}[LABEL];
605     #
606     my $value = $$vhash{$name}[VALUE];
607     $value //= $$hash{$key}[CMDEF];
608     my $col = gdk_color_from_str ($value);
609     my $glabel = Gtk3::Label->new ($label);
610     my $button = Gtk3::ColorButton->new_with_color ($col);
611     $button->set_title ($label);
612     $button->set_relief ('none');
613     $button->signal_connect ('color-set' => sub {
614             my ($w, $e) = @_;
615             handle_color_value ($w, $e, \$$vhash{$name});
616         });
617     set_widget_hint ($button, $$hash{$key}[DESC]);
618     set_widget_sens ($button, $$hash{$key}[CMVER]);
619     $glabel->set_sensitive ($button->sensitive);
620     #
621     return new_hbox_spaced_pack ($button, $glabel);
622 }
623
624 sub new_selection_box_for($$$) {
625     my ($hash, $key, $vhash) = @_;
626     my $name = $$hash{$key}[NAME];
627     my $label = $$hash{$key}[LABEL];
628     #
629     my $glabel = Gtk3::Label->new ($label);
630     my $combo = Gtk3::ComboBox->new_text;
631     my @options = split (';', $$hash{$key}[TYPE]);
632     foreach my $opt (@options) {
633         my ($index, $textkey) = split ('=', $opt);
634         $combo->insert_text ($index, $xl::s{$textkey});
635     }
636     $combo->signal_connect ('changed' => sub {
637             my ($w, $e) = @_;
638             handle_selection_value ($w, $e, \$$vhash{$name});
639         });
640     my $value = $$vhash{$name}[VALUE];
641     $value //= $$hash{$key}[CMDEF];
642     $combo->set_active ($value);
643     set_widget_hint ($combo, $$hash{$key}[DESC]);
644     set_widget_sens ($combo, $$hash{$key}[CMVER]);
645     $glabel->set_sensitive ($combo->sensitive);
646     #
647     return new_hbox_spaced_pack ($glabel, $combo);
648 }
649
650 # more graphic helpers
651
652 sub new_hbox_pack {
653     my $hbox = Gtk3::HBox->new (FALSE);
654     $hbox->set_border_width (PAGE_SPC);
655     foreach (@_) {
656         $hbox->pack_start ($_, FALSE, FALSE, 0);
657     }
658     return $hbox;
659 }
660
661 sub new_hbox_pack_compact {
662     my $hbox = Gtk3::HBox->new (FALSE);
663     $hbox->set_border_width (0);
664     foreach (@_) {
665         $hbox->pack_start ($_, FALSE, FALSE, 0);
666     }
667     return $hbox;
668 }
669
670 sub new_vbox_pack {
671     my $vbox = Gtk3::VBox->new (FALSE, 5);
672     $vbox->set_border_width (PAGE_SPC);
673     foreach (@_) {
674         $vbox->pack_start ($_, FALSE, FALSE, 0);
675     }
676     return $vbox;
677 }
678
679 sub new_vbox_pack_compact {
680     my $vbox = Gtk3::VBox->new (FALSE, 0);
681     $vbox->set_border_width (0);
682     foreach (@_) {
683         $vbox->pack_start ($_, FALSE, FALSE, 0);
684     }
685     return $vbox;
686 }
687
688 sub new_subpage_frame {
689     my ($box, $title, $notpacked) = @_;
690     my $frame = Gtk3::Frame->new ($title);
691     $frame->add ($box);
692     return new_vbox_pack ($frame) unless defined ($notpacked);
693     return $frame;
694 }
695
696 # preference maps and corresponding page creation subs
697
698 %pr::oth = ( # other preferences
699     use_dlg => [
700         'addressbook_use_editaddress_dialog',
701         $xl::s{l_oth_use_dlg},
702         $xl::s{h_oth_use_dlg},
703         'bool',
704         '2.7.0',
705         '0',
706     ],
707     max_use => [
708         'cache_max_mem_usage',
709         [ $xl::s{l_oth_max_use}, $xl::s{l_oth_max_use_units} ],
710         $xl::s{h_oth_max_use},
711         'int,0,524288', # 0 Kb - 512 Mb
712         '0.7.8.36',
713         '4096',
714     ],
715     min_time => [
716         'cache_min_keep_time',
717         [ $xl::s{l_oth_min_time}, $xl::s{l_oth_min_time_units} ],
718         $xl::s{h_oth_min_time},
719         'int,0,120', # 0 minutes - 2 hours
720         '0.7.8.36',
721         '15',
722     ],
723     use_netm => [
724         'use_networkmanager',
725         $xl::s{l_oth_use_netm},
726         $xl::s{h_oth_use_netm},
727         'bool',
728         '3.3.1',
729         '1',
730     ],
731     mp_rounds => [
732         'master_passphrase_pbkdf2_rounds',
733         $xl::s{l_oth_mp_rounds},
734         $xl::s{h_oth_mp_rounds},
735         'int,50000,1000000',
736         '3.13.2.110',
737         '50000',
738     ],
739 );
740
741 sub new_other_page() {
742     return new_vbox_pack (
743                new_subpage_frame (
744                    new_vbox_pack (
745                        new_check_button_for(\%pr::oth, 'use_dlg', \%HPVALUE)),
746                    $xl::s{ab_frame}, 'not-packed'),
747                new_subpage_frame (
748                    new_vbox_pack (
749                        new_text_box_for_int(\%pr::oth, 'max_use', \%HPVALUE),
750                        new_text_box_for_int(\%pr::oth, 'min_time', \%HPVALUE)),
751                    $xl::s{mem_frame}, 'not-packed'),
752                new_subpage_frame (
753                    new_vbox_pack (
754                        new_check_button_for(\%pr::oth, 'use_netm', \%HPVALUE)),
755                    $xl::s{netm_frame}, 'not-packed'),
756                new_subpage_frame (
757                    new_vbox_pack (
758                        new_text_box_for_int(\%pr::oth, 'mp_rounds', \%HPVALUE)),
759                    $xl::s{mpass_frame}, 'not-packed')
760            );
761 }
762
763 %pr::gui = ( # gui bells and whistles
764     b_unread => [
765         'bold_unread',
766         $xl::s{l_gui_b_unread},
767         $xl::s{h_gui_b_unread},
768         'bool',
769         '0.5.3',
770         '1',
771     ],
772     no_markup => [
773         'compose_no_markup',
774         $xl::s{l_gui_no_markup},
775         $xl::s{h_gui_no_markup},
776         'bool',
777         '2.1.0.16',
778         '0',
779     ],
780     dot_lines => [
781         'enable_dotted_lines',
782         $xl::s{l_gui_dot_lines},
783         $xl::s{h_gui_dot_lines},
784         'bool',
785         '2.4.0.115,3.7.10.44',
786         '0',
787     ],
788     h_scroll => [
789         'enable_hscrollbar',
790         $xl::s{l_gui_h_scroll},
791         $xl::s{h_gui_h_scroll},
792         'bool',
793         '0.8.6.18',
794         '1',
795     ],
796     swp_from => [
797         'enable_swap_from',
798         $xl::s{l_gui_swp_from},
799         $xl::s{h_gui_swp_from},
800         'bool',
801         '1.9.13.40',
802         '0',
803     ],
804     v_scroll => [
805         'folderview_vscrollbar_policy',
806         $xl::s{l_gui_v_scroll},
807         $xl::s{h_gui_v_scroll},
808         '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
809         '0.7.8.14',
810         '0',
811     ],
812     from_show => [
813         'summary_from_show',
814         $xl::s{l_gui_from_show},
815         $xl::s{h_gui_from_show},
816         '0=l_gui_from_show_name;1=l_gui_from_show_addr;2=l_gui_from_show_both',
817         '3.7.10',
818         '0',
819     ],
820     strip_off => [
821         'stripes_color_offset',
822         $xl::s{l_gui_strip_off},
823         $xl::s{h_gui_strip_off},
824         'int,0,40000', # no idea what this number means
825         '2.4.0.186',
826         '4000',
827     ],
828     cursor_v => [
829         'textview_cursor_visible',
830         $xl::s{l_gui_cursor_v},
831         $xl::s{h_gui_cursor_v},
832         'bool',
833         '0.0.0',
834         '0',
835     ],
836     toolbar_d => [
837         'toolbar_detachable',
838         $xl::s{l_gui_toolbar_d},
839         $xl::s{h_gui_toolbar_d},
840         'bool',
841         '0.0.0',
842         '0',
843     ],
844     strip_all => [
845         'use_stripes_everywhere',
846         $xl::s{l_gui_strip_all},
847         $xl::s{h_gui_strip_all},
848         'bool',
849         '0.0.0',
850         '1',
851     ],
852     strip_sum => [
853         'use_stripes_in_summaries',
854         $xl::s{l_gui_strip_sum},
855         $xl::s{h_gui_strip_sum},
856         'bool',
857         '0.0.0',
858         '1',
859     ],
860     two_linev => [
861         'two_line_vertical',
862         $xl::s{l_gui_two_line_v},
863         $xl::s{h_gui_two_line_v},
864         'bool',
865         '3.4.0.7',
866         '0',
867     ],
868     margin_co => [
869         'show_compose_margin',
870         $xl::s{l_gui_margin_co},
871         $xl::s{h_gui_margin_co},
872         'bool',
873         '3.7.6.7',
874         '0',
875     ],
876     mview_date => [
877         'msgview_date_format',
878         $xl::s{l_gui_mview_date},
879         $xl::s{h_gui_mview_date},
880         'bool',
881         '3.7.8.42',
882         '0',
883     ],
884     zero_char => [
885         'zero_replacement_char',
886         $xl::s{l_gui_zero_char},
887         $xl::s{h_gui_zero_char},
888         'char,1,1',
889         '2.8.1.77',
890         '0',
891     ],
892     type_any => [
893         'type_any_header',
894         $xl::s{l_gui_type_any},
895         $xl::s{h_gui_type_any},
896         'bool',
897         '3.12.0.44',
898         '0',
899     ],
900     warn_send_multi => [
901         'warn_sending_many_recipients_num',
902         [ $xl::s{l_gui_warn_send_multi}, $xl::s{l_gui_warn_send_multi_units} ],
903         $xl::s{h_gui_warn_send_multi},
904         'int,0,1000',
905         '3.14.1.125',
906         '3.15.0.28',
907     ],
908     next_del => [
909         'next_on_delete',
910         $xl::s{l_gui_next_del},
911         $xl::s{h_gui_next_del},
912         'bool',
913         '3.13.0.5',
914         '0',
915     ],
916 );
917
918 sub new_gui_page() {
919     my $gf = Gtk3::VBox->new (FALSE, 5);
920     $gf->set_border_width (PAGE_SPC);
921
922     my $cb_dot_lines = new_check_button_for (\%pr::gui, 'dot_lines', \%HPVALUE);
923     my $cb_toolbar_d = new_check_button_for (\%pr::gui, 'toolbar_d', \%HPVALUE);
924     my $tb_zero_char = new_text_box_for_nchar (\%pr::gui, 'zero_char', \%HPVALUE);
925
926     $gf->pack_start (new_subpage_frame (
927                          new_vbox_pack (
928                              new_check_button_for (\%pr::gui, 'strip_all', \%HPVALUE),
929                              new_check_button_for (\%pr::gui, 'strip_sum', \%HPVALUE),
930                              new_text_box_for_int (\%pr::gui, 'strip_off', \%HPVALUE)),
931                          $xl::s{stripes_frame}, 'not-packed'),
932                      FALSE, FALSE, FRAME_SPC);
933     $gf->pack_start (new_subpage_frame (
934                          new_vbox_pack (
935                              new_check_button_for (\%pr::gui, 'b_unread', \%HPVALUE),
936                              new_check_button_for (\%pr::gui, 'swp_from', \%HPVALUE),
937                              new_check_button_for (\%pr::gui, 'two_linev', \%HPVALUE),
938                              new_check_button_for (\%pr::gui, 'next_del', \%HPVALUE),
939                              new_selection_box_for (\%pr::gui, 'from_show', \%HPVALUE)),
940                          $xl::s{mlist_frame}, 'not-packed'),
941                      FALSE, FALSE, FRAME_SPC);
942     $gf->pack_start (new_subpage_frame (
943                          new_hbox_pack (
944                              new_check_button_for (\%pr::gui, 'cursor_v', \%HPVALUE),
945                              new_check_button_for (\%pr::gui, 'mview_date', \%HPVALUE)),
946                          $xl::s{mview_frame}, 'not-packed'),
947                      FALSE, FALSE, FRAME_SPC);
948     $gf->pack_start (new_subpage_frame (
949                          new_vbox_pack (
950                              new_hbox_pack_compact (
951                                  new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
952                                  new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE),
953                                  new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE)),
954                              new_text_box_for_int (\%pr::gui, 'warn_send_multi', \%HPVALUE)),
955                          $xl::s{compo_frame}, 'not-packed'),
956                      FALSE, FALSE, FRAME_SPC);
957     $gf->pack_start ($cb_dot_lines, FALSE, FALSE, 0);
958     $gf->pack_start ($cb_toolbar_d, FALSE, FALSE, 0);
959     $gf->pack_start (new_subpage_frame (
960                          new_vbox_pack (
961                              new_check_button_for (\%pr::gui, 'h_scroll', \%HPVALUE),
962                              new_selection_box_for (\%pr::gui, 'v_scroll', \%HPVALUE)),
963                          $xl::s{sbar_frame}, 'not-packed'),
964                      FALSE, FALSE, FRAME_SPC);
965     $gf->pack_start ($tb_zero_char, FALSE, FALSE, 0);
966
967     return $gf;
968 }
969
970 %pr::beh = ( # tweak some behaviour
971     hover_t => [
972         'hover_timeout',
973         [ $xl::s{l_beh_hover_t}, $xl::s{l_beh_hover_t_units} ],
974         $xl::s{h_beh_hover_t},
975         'int,100,3000', # 0.1 seconds - 3 seconds
976         '0.0.0',
977         '500',
978     ],
979     dangerous => [
980         'live_dangerously',
981         $xl::s{l_beh_dangerous},
982         $xl::s{h_beh_dangerous},
983         'bool',
984         '0.0.0',
985         '0',
986     ],
987     flowed => [
988         'respect_flowed_format',
989         $xl::s{l_beh_flowed},
990         $xl::s{h_beh_flowed},
991         'bool',
992         '0.0.0',
993         '0',
994     ],
995     parts_rw => [
996         'save_parts_readwrite',
997         $xl::s{l_beh_parts_rw},
998         $xl::s{h_beh_parts_rw},
999         'bool',
1000         '0.0.0',
1001         '0',
1002     ],
1003     skip_ssl => [
1004         'skip_ssl_cert_check',
1005         $xl::s{l_beh_skip_ssl},
1006         $xl::s{h_beh_skip_ssl},
1007         'bool',
1008         '0.0.0',
1009         '0',
1010     ],
1011     up_step => [
1012         'statusbar_update_step',
1013         [ $xl::s{l_beh_up_step}, $xl::s{l_beh_up_step_units} ],
1014         $xl::s{h_beh_up_step},
1015         'int,1,200', # 1 item - 200 items
1016         '0.0.0',
1017         '10',
1018     ],
1019     thread_a => [
1020         'thread_by_subject_max_age',
1021         [ $xl::s{l_beh_thread_a}, $xl::s{l_beh_thread_a_units} ],
1022         $xl::s{h_beh_thread_a},
1023         'int,1,30', # 1 day - 30 days
1024         '0.0.0',
1025         '10',
1026     ],
1027     unsafe_ssl => [
1028         'unsafe_ssl_certs',
1029         $xl::s{l_beh_unsafe_ssl},
1030         $xl::s{h_beh_unsafe_ssl},
1031         'bool',
1032         '0.0.0',
1033         '0',
1034     ],
1035     use_utf8 => [
1036         'utf8_instead_of_locale_for_broken_mail',
1037         $xl::s{l_beh_use_utf8},
1038         $xl::s{h_beh_use_utf8},
1039         'bool',
1040         '1.9.14.49',
1041         '0',
1042     ],
1043     warn_dnd => [
1044         'warn_dnd',
1045         $xl::s{l_beh_warn_dnd},
1046         $xl::s{h_beh_warn_dnd},
1047         'bool',
1048         '0.0.0',
1049         '1',
1050     ],
1051     out_ascii => [
1052         'outgoing_fallback_to_ascii',
1053         $xl::s{l_beh_out_ascii},
1054         $xl::s{h_beh_out_ascii},
1055         'bool',
1056         '3.4.0.37',
1057         '1',
1058     ],
1059     pp_unsel => [
1060         'primary_paste_unselects',
1061         $xl::s{l_beh_pp_unsel},
1062         $xl::s{h_beh_pp_unsel},
1063         'bool',
1064         '3.6.1.35',
1065         '0',
1066     ],
1067     inline_at => [
1068         'show_inline_attachments',
1069         $xl::s{l_beh_inline_at},
1070         $xl::s{h_beh_inline_at},
1071         'bool',
1072         '3.7.8.48',
1073         '1',
1074     ],
1075     addr_swc => [
1076         'address_search_wildcard',
1077         $xl::s{l_beh_addr_swc},
1078         $xl::s{h_beh_addr_swc},
1079         'bool',
1080         '3.9.3.18',
1081         '0',
1082     ],
1083     fold_swc => [
1084         'folder_search_wildcard',
1085         $xl::s{l_beh_fold_swc},
1086         $xl::s{h_beh_fold_swc},
1087         'bool',
1088         '3.9.3.18',
1089         '0',
1090     ],
1091     rewrite_ff => [
1092         'rewrite_first_from',
1093         $xl::s{l_beh_rewrite_ff},
1094         $xl::s{h_beh_rewrite_ff},
1095         'bool',
1096         '3.14.0.94',
1097         '0',
1098     ],
1099 );
1100
1101 sub new_behaviour_page() {
1102     my $bf = Gtk3::VBox->new (FALSE, 5);
1103     $bf->set_border_width (PAGE_SPC);
1104
1105     my $tb_up_step = new_text_box_for_int (\%pr::beh, 'up_step', \%HPVALUE);
1106     my $tb_thread_a = new_text_box_for_int (\%pr::beh, 'thread_a', \%HPVALUE);
1107
1108     $bf->pack_start (new_subpage_frame (
1109                          new_vbox_pack (
1110                              new_text_box_for_int (\%pr::beh, 'hover_t', \%HPVALUE),
1111                              new_check_button_for (\%pr::beh, 'warn_dnd', \%HPVALUE)),
1112                          $xl::s{dnd_frame}, 'not-packed'),
1113                      FALSE, FALSE, FRAME_SPC);
1114     $bf->pack_start (new_subpage_frame (
1115                          new_hbox_pack (
1116                              new_check_button_for (\%pr::beh, 'skip_ssl', \%HPVALUE),
1117                              new_check_button_for (\%pr::beh, 'unsafe_ssl', \%HPVALUE)),
1118                          $xl::s{ssl_frame}, 'not-packed'),
1119                      FALSE, FALSE, FRAME_SPC);
1120     $bf->pack_start ($tb_up_step, FALSE, FALSE, 0);
1121     $bf->pack_start ($tb_thread_a, FALSE, FALSE, 0);
1122     $bf->pack_start (new_subpage_frame (
1123                          new_vbox_pack (
1124                              new_check_button_for (\%pr::beh, 'flowed', \%HPVALUE),
1125                              new_check_button_for (\%pr::beh, 'parts_rw', \%HPVALUE),
1126                              new_check_button_for (\%pr::beh, 'use_utf8', \%HPVALUE),
1127                              new_check_button_for (\%pr::beh, 'dangerous', \%HPVALUE),
1128                              new_check_button_for (\%pr::beh, 'out_ascii', \%HPVALUE),
1129                              new_check_button_for (\%pr::beh, 'pp_unsel', \%HPVALUE),
1130                              new_check_button_for (\%pr::beh, 'inline_at', \%HPVALUE),
1131                              new_check_button_for (\%pr::beh, 'rewrite_ff', \%HPVALUE)),
1132                          $xl::s{msgs_frame}, 'not-packed'),
1133                      FALSE, FALSE, FRAME_SPC);
1134     $bf->pack_start (new_subpage_frame (
1135                          new_vbox_pack (
1136                              new_check_button_for (\%pr::beh, 'addr_swc', \%HPVALUE),
1137                              new_check_button_for (\%pr::beh, 'fold_swc', \%HPVALUE)),
1138                          $xl::s{swc_frame}, 'not-packed'),
1139                      FALSE, FALSE, FRAME_SPC);
1140     return $bf;
1141 }
1142
1143 %pr::col = ( # a variety of colours
1144     emphasis => [
1145         'emphasis_color',
1146         $xl::s{l_col_emphasis},
1147         $xl::s{h_col_emphasis},
1148         'color',
1149         '0.0.0',
1150         '#0000cf',
1151     ],
1152     log_err => [
1153         'log_error_color',
1154         $xl::s{l_col_log_err},
1155         $xl::s{h_col_log_err},
1156         'color',
1157         '0.0.0',
1158         '#af0000',
1159     ],
1160     log_in => [
1161         'log_in_color',
1162         $xl::s{l_col_log_in},
1163         $xl::s{h_col_log_in},
1164         'color',
1165         '0.0.0',
1166         '#000000',
1167     ],
1168     log_msg => [
1169         'log_msg_color',
1170         $xl::s{l_col_log_msg},
1171         $xl::s{h_col_log_msg},
1172         'color',
1173         '0.0.0',
1174         '#00af00',
1175     ],
1176     log_out => [
1177         'log_out_color',
1178         $xl::s{l_col_log_out},
1179         $xl::s{h_col_log_out},
1180         'color',
1181         '0.0.0',
1182         '#0000ef',
1183     ],
1184     log_warn => [
1185         'log_warn_color',
1186         $xl::s{l_col_log_warn},
1187         $xl::s{h_col_log_warn},
1188         'color',
1189         '0.0.0',
1190         '#af0000',
1191     ],
1192     diff_add => [
1193         'diff_added_color',
1194         $xl::s{l_col_diff_add},
1195         $xl::s{h_col_diff_add},
1196         'color',
1197         '3.8.0.54',
1198         '#008b8b',
1199     ],
1200     diff_del => [
1201         'diff_deleted_color',
1202         $xl::s{l_col_diff_del},
1203         $xl::s{h_col_diff_del},
1204         'color',
1205         '3.8.0.54',
1206         '#6a5acd',
1207     ],
1208     diff_hunk => [
1209         'diff_hunk_color',
1210         $xl::s{l_col_diff_hunk},
1211         $xl::s{h_col_diff_hunk},
1212         'color',
1213         '3.8.0.54',
1214         '#a52a2a',
1215     ],
1216     tags_bg => [
1217         'tags_bgcolor',
1218         $xl::s{l_col_tags_bg},
1219         $xl::s{h_col_tags_bg},
1220         'color',
1221         '3.14.1.31',
1222         '#f5f6be',
1223     ],
1224     tags_text => [
1225         'tags_color',
1226         $xl::s{l_col_tags_text},
1227         $xl::s{h_col_tags_text},
1228         'color',
1229         '3.14.1.31',
1230         '#000000',
1231     ],
1232     default_header_bg => [
1233         'default_header_bgcolor',
1234         $xl::s{l_col_default_header_bg},
1235         $xl::s{h_col_default_header_bg},
1236         'color',
1237         '3.14.1.31',
1238         '#f5f6be',
1239     ],
1240     default_header_text => [
1241         'default_header_color',
1242         $xl::s{l_col_default_header_text},
1243         $xl::s{h_col_default_header_text},
1244         'color',
1245         '3.14.1.31',
1246         '#000000',
1247     ],
1248     qs_active_bg => [
1249         'qs_active_bgcolor',
1250         $xl::s{l_col_qs_active_bg},
1251         $xl::s{h_col_qs_active_bg},
1252         'color',
1253         '3.14.1.31',
1254         '#f5f6be',
1255     ],
1256     qs_active_text => [
1257         'qs_active_color',
1258         $xl::s{l_col_qs_active_text},
1259         $xl::s{h_col_qs_active_text},
1260         'color',
1261         '3.14.1.31',
1262         '#000000',
1263     ],
1264     qs_error_bg => [
1265         'qs_error_bgcolor',
1266         $xl::s{l_col_qs_error_bg},
1267         $xl::s{h_col_qs_error_bg},
1268         'color',
1269         '3.14.1.31',
1270         '#ff7070',
1271     ],
1272     qs_error_text => [
1273         'qs_error_color',
1274         $xl::s{l_col_qs_error_text},
1275         $xl::s{h_col_qs_error_text},
1276         'color',
1277         '3.14.1.31',
1278         '#000000',
1279     ],
1280 );
1281
1282 sub new_colours_page() {
1283     return new_vbox_pack (
1284                new_subpage_frame (
1285                    new_vbox_pack (
1286                        new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE),
1287                        new_hbox_pack_compact (
1288                            new_color_button_for (\%pr::col, 'tags_text', \%HPVALUE),
1289                            new_color_button_for (\%pr::col, 'tags_bg', \%HPVALUE))),
1290                    $xl::s{msgview_frame}, 'not-packed'),
1291                new_subpage_frame (
1292                    new_hbox_pack (
1293                        new_vbox_pack_compact (
1294                            new_color_button_for (\%pr::col, 'log_err', \%HPVALUE),
1295                            new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE),
1296                            new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE)),
1297                        new_vbox_pack_compact (
1298                            new_color_button_for (\%pr::col, 'log_in', \%HPVALUE),
1299                            new_color_button_for (\%pr::col, 'log_out', \%HPVALUE))),
1300                    $xl::s{log_frame}, 'not-packed'),
1301                new_subpage_frame (
1302                    new_vbox_pack (
1303                        new_color_button_for (\%pr::col, 'diff_add', \%HPVALUE),
1304                        new_color_button_for (\%pr::col, 'diff_del', \%HPVALUE),
1305                        new_color_button_for (\%pr::col, 'diff_hunk', \%HPVALUE)),
1306                    $xl::s{diff_frame}, 'not-packed'),
1307                new_subpage_frame (
1308                    new_hbox_pack (
1309                        new_color_button_for (\%pr::col, 'default_header_text', \%HPVALUE),
1310                        new_color_button_for (\%pr::col, 'default_header_bg', \%HPVALUE)),
1311                    $xl::s{compose_frame}, 'not-packed'),
1312                new_subpage_frame (
1313                    new_hbox_pack (
1314                        new_vbox_pack_compact (
1315                            new_color_button_for (\%pr::col, 'qs_active_text', \%HPVALUE),
1316                            new_color_button_for (\%pr::col, 'qs_error_text', \%HPVALUE)),
1317                        new_vbox_pack_compact (
1318                            new_color_button_for (\%pr::col, 'qs_active_bg', \%HPVALUE),
1319                            new_color_button_for (\%pr::col, 'qs_error_bg', \%HPVALUE))),
1320                    $xl::s{qs_frame}, 'not-packed')
1321            );
1322 }
1323
1324 %pr::win = ( # tweak window positions and/or sizes
1325     main_x => [
1326         'mainwin_x',
1327         $xl::s{l_win_x},
1328         $xl::s{h_win_x},
1329         'int,0,3000', # 0 pixels - 3000 pixels
1330         '0.0.0',
1331         '16',
1332     ],
1333     main_y => [
1334         'mainwin_y',
1335         $xl::s{l_win_y},
1336         $xl::s{h_win_y},
1337         'int,0,3000', # 0 pixels - 3000 pixels
1338         '0.0.0',
1339         '16',
1340     ],
1341     main_w => [
1342         'mainwin_width',
1343         $xl::s{l_win_w},
1344         $xl::s{h_win_w},
1345         'int,0,3000', # 0 pixels - 3000 pixels
1346         '0.0.0',
1347         '779',
1348     ],
1349     main_h => [
1350         'mainwin_height',
1351         $xl::s{l_win_h},
1352         $xl::s{h_win_h},
1353         'int,0,3000', # 0 pixels - 3000 pixels
1354         '0.0.0',
1355         '568',
1356     ],
1357     main_mx => [
1358         'mainwin_maximised',
1359         $xl::s{l_win_main_mx},
1360         $xl::s{h_win_main_mx},
1361         'bool',
1362         '0.0.0',
1363         '0',
1364     ],
1365     main_fs => [
1366         'mainwin_fullscreen',
1367         $xl::s{l_win_main_fs},
1368         $xl::s{h_win_main_fs},
1369         'bool',
1370         '0.0.0',
1371         '0',
1372     ],
1373     msgs_x => [
1374         'main_messagewin_x',
1375         $xl::s{l_win_x},
1376         $xl::s{h_win_x},
1377         'int,0,3000', # 0 pixels - 3000 pixels
1378         '0.0.0',
1379         '256',
1380     ],
1381     msgs_y => [
1382         'main_messagewin_y',
1383         $xl::s{l_win_y},
1384         $xl::s{h_win_y},
1385         'int,0,3000', # 0 pixels - 3000 pixels
1386         '0.0.0',
1387         '210',
1388     ],
1389     msgs_w => [
1390         'messagewin_width',
1391         $xl::s{l_win_w},
1392         $xl::s{h_win_w},
1393         'int,0,3000', # 0 pixels - 3000 pixels
1394         '0.0.0',
1395         '600',
1396     ],
1397     msgs_h => [
1398         'messagewin_height',
1399         $xl::s{l_win_h},
1400         $xl::s{h_win_h},
1401         'int,0,3000', # 0 pixels - 3000 pixels
1402         '0.0.0',
1403         '540',
1404     ],
1405     send_w => [
1406         'sendwin_width',
1407         $xl::s{l_win_w},
1408         $xl::s{h_win_w},
1409         'int,0,3000', # 0 pixels - 3000 pixels
1410         '0.0.0',
1411         '460',
1412     ],
1413     send_h => [
1414         'sendwin_height',
1415         $xl::s{l_win_h},
1416         $xl::s{h_win_h},
1417         'int,0,3000', # 0 pixels - 3000 pixels
1418         '0.0.0',
1419         '-1',
1420     ],
1421     recv_w => [
1422         'receivewin_width',
1423         $xl::s{l_win_w},
1424         $xl::s{h_win_w},
1425         'int,0,3000', # 0 pixels - 3000 pixels
1426         '0.0.0',
1427         '460',
1428     ],
1429     recv_h => [
1430         'receivewin_height',
1431         $xl::s{l_win_h},
1432         $xl::s{h_win_h},
1433         'int,0,3000', # 0 pixels - 3000 pixels
1434         '0.0.0',
1435         '-1',
1436     ],
1437     fold_x => [
1438         'folderwin_x',
1439         $xl::s{l_win_x},
1440         $xl::s{h_win_x},
1441         'int,0,3000', # 0 pixels - 3000 pixels
1442         '0.0.0',
1443         '16',
1444     ],
1445     fold_y => [
1446         'folderwin_y',
1447         $xl::s{l_win_y},
1448         $xl::s{h_win_y},
1449         'int,0,3000', # 0 pixels - 3000 pixels
1450         '0.0.0',
1451         '16',
1452     ],
1453     fold_w => [
1454         'folderitemwin_width',
1455         $xl::s{l_win_w},
1456         $xl::s{h_win_w},
1457         'int,0,3000', # 0 pixels - 3000 pixels
1458         '0.0.0',
1459         '500',
1460     ],
1461     fold_h => [
1462         'folderitemwin_height',
1463         $xl::s{l_win_h},
1464         $xl::s{h_win_h},
1465         'int,0,3000', # 0 pixels - 3000 pixels
1466         '0.0.0',
1467         '-1',
1468     ],
1469     fsel_w => [
1470         'folderselwin_width',
1471         $xl::s{l_win_w},
1472         $xl::s{h_win_w},
1473         'int,0,3000', # 0 pixels - 3000 pixels
1474         '0.0.0',
1475         '300',
1476     ],
1477     fsel_h => [
1478         'folderselwin_height',
1479         $xl::s{l_win_h},
1480         $xl::s{h_win_h},
1481         'int,0,3000', # 0 pixels - 3000 pixels
1482         '0.0.0',
1483         '-1',
1484     ],
1485     sour_w => [
1486         'sourcewin_width',
1487         $xl::s{l_win_w},
1488         $xl::s{h_win_w},
1489         'int,0,3000', # 0 pixels - 3000 pixels
1490         '0.0.0',
1491         '600',
1492     ],
1493     sour_h => [
1494         'sourcewin_height',
1495         $xl::s{l_win_h},
1496         $xl::s{h_win_h},
1497         'int,0,3000', # 0 pixels - 3000 pixels
1498         '0.0.0',
1499         '500',
1500     ],
1501     addr_w => [
1502         'addressbookwin_width',
1503         $xl::s{l_win_w},
1504         $xl::s{h_win_w},
1505         'int,0,3000', # 0 pixels - 3000 pixels
1506         '0.0.0',
1507         '520',
1508     ],
1509     addr_h => [
1510         'addressbookwin_height',
1511         $xl::s{l_win_h},
1512         $xl::s{h_win_h},
1513         'int,0,3000', # 0 pixels - 3000 pixels
1514         '0.0.0',
1515         '-1',
1516     ],
1517     adep_w => [
1518         'addressbookeditpersonwin_width',
1519         $xl::s{l_win_w},
1520         $xl::s{h_win_w},
1521         'int,0,3000', # 0 pixels - 3000 pixels
1522         '0.0.0',
1523         '640',
1524     ],
1525     adep_h => [
1526         'addressbookeditpersonwin_height',
1527         $xl::s{l_win_h},
1528         $xl::s{h_win_h},
1529         'int,0,3000', # 0 pixels - 3000 pixels
1530         '0.0.0',
1531         '320',
1532     ],
1533     adeg_w => [
1534         'addressbookeditgroupwin_width',
1535         $xl::s{l_win_w},
1536         $xl::s{h_win_w},
1537         'int,0,3000', # 0 pixels - 3000 pixels
1538         '0.0.0',
1539         '580',
1540     ],
1541     adeg_h => [
1542         'addressbookeditgroupwin_height',
1543         $xl::s{l_win_h},
1544         $xl::s{h_win_h},
1545         'int,0,3000', # 0 pixels - 3000 pixels
1546         '0.0.0',
1547         '340',
1548     ],
1549     adda_w => [
1550         'addressaddwin_width',
1551         $xl::s{l_win_w},
1552         $xl::s{h_win_w},
1553         'int,0,3000', # 0 pixels - 3000 pixels
1554         '0.0.0',
1555         '300',
1556     ],
1557     adda_h => [
1558         'addressaddwin_height',
1559         $xl::s{l_win_h},
1560         $xl::s{h_win_h},
1561         'int,0,3000', # 0 pixels - 3000 pixels
1562         '0.0.0',
1563         '-1',
1564     ],
1565     addf_w => [
1566         'addressbook_folderselwin_width',
1567         $xl::s{l_win_w},
1568         $xl::s{h_win_w},
1569         'int,0,3000', # 0 pixels - 3000 pixels
1570         '0.0.0',
1571         '300',
1572     ],
1573     addf_h => [
1574         'addressbook_folderselwin_height',
1575         $xl::s{l_win_h},
1576         $xl::s{h_win_h},
1577         'int,0,3000', # 0 pixels - 3000 pixels
1578         '0.0.0',
1579         '-1',
1580     ],
1581     acce_w => [
1582         'editaccountwin_width',
1583         $xl::s{l_win_w},
1584         $xl::s{h_win_w},
1585         'int,0,3000', # 0 pixels - 3000 pixels
1586         '0.0.0',
1587         '500',
1588     ],
1589     acce_h => [
1590         'editaccountwin_height',
1591         $xl::s{l_win_h},
1592         $xl::s{h_win_h},
1593         'int,0,3000', # 0 pixels - 3000 pixels
1594         '0.0.0',
1595         '-1',
1596     ],
1597     acco_w => [
1598         'accountswin_width',
1599         $xl::s{l_win_w},
1600         $xl::s{h_win_w},
1601         'int,0,3000', # 0 pixels - 3000 pixels
1602         '0.0.0',
1603         '500',
1604     ],
1605     acco_h => [
1606         'accountswin_height',
1607         $xl::s{l_win_h},
1608         $xl::s{h_win_h},
1609         'int,0,3000', # 0 pixels - 3000 pixels
1610         '0.0.0',
1611         '-1',
1612     ],
1613     filt_w => [
1614         'filteringwin_width',
1615         $xl::s{l_win_w},
1616         $xl::s{h_win_w},
1617         'int,0,3000', # 0 pixels - 3000 pixels
1618         '0.0.0',
1619         '500',
1620     ],
1621     filt_h => [
1622         'filteringwin_height',
1623         $xl::s{l_win_h},
1624         $xl::s{h_win_h},
1625         'int,0,3000', # 0 pixels - 3000 pixels
1626         '0.0.0',
1627         '-1',
1628     ],
1629     fila_w => [
1630         'filteringactionwin_width',
1631         $xl::s{l_win_w},
1632         $xl::s{h_win_w},
1633         'int,0,3000', # 0 pixels - 3000 pixels
1634         '0.0.0',
1635         '490',
1636     ],
1637     fila_h => [
1638         'filteringactionwin_height',
1639         $xl::s{l_win_h},
1640         $xl::s{h_win_h},
1641         'int,0,3000', # 0 pixels - 3000 pixels
1642         '0.0.0',
1643         '-1',
1644     ],
1645     fild_w => [
1646         'filtering_debugwin_width',
1647         $xl::s{l_win_w},
1648         $xl::s{h_win_w},
1649         'int,0,3000', # 0 pixels - 3000 pixels
1650         '0.0.0',
1651         '600',
1652     ],
1653     fild_h => [
1654         'filtering_debugwin_height',
1655         $xl::s{l_win_h},
1656         $xl::s{h_win_h},
1657         'int,0,3000', # 0 pixels - 3000 pixels
1658         '0.0.0',
1659         '-1',
1660     ],
1661     matc_w => [
1662         'matcherwin_width',
1663         $xl::s{l_win_w},
1664         $xl::s{h_win_w},
1665         'int,0,3000', # 0 pixels - 3000 pixels
1666         '0.0.0',
1667         '520',
1668     ],
1669     matc_h => [
1670         'matcherwin_height',
1671         $xl::s{l_win_h},
1672         $xl::s{h_win_h},
1673         'int,0,3000', # 0 pixels - 3000 pixels
1674         '0.0.0',
1675         '-1',
1676     ],
1677     pref_w => [
1678         'prefswin_width',
1679         $xl::s{l_win_w},
1680         $xl::s{h_win_w},
1681         'int,0,3000', # 0 pixels - 3000 pixels
1682         '0.0.0',
1683         '600',
1684     ],
1685     pref_h => [
1686         'prefswin_height',
1687         $xl::s{l_win_h},
1688         $xl::s{h_win_h},
1689         'int,0,3000', # 0 pixels - 3000 pixels
1690         '0.0.0',
1691         '-1',
1692     ],
1693     temp_w => [
1694         'templateswin_width',
1695         $xl::s{l_win_w},
1696         $xl::s{h_win_w},
1697         'int,0,3000', # 0 pixels - 3000 pixels
1698         '0.0.0',
1699         '480',
1700     ],
1701     temp_h => [
1702         'templateswin_height',
1703         $xl::s{l_win_h},
1704         $xl::s{h_win_h},
1705         'int,0,3000', # 0 pixels - 3000 pixels
1706         '0.0.0',
1707         '-1',
1708     ],
1709     acti_w => [
1710         'actionswin_width',
1711         $xl::s{l_win_w},
1712         $xl::s{h_win_w},
1713         'int,0,3000', # 0 pixels - 3000 pixels
1714         '0.0.0',
1715         '486',
1716     ],
1717     acti_h => [
1718         'actionswin_height',
1719         $xl::s{l_win_h},
1720         $xl::s{h_win_h},
1721         'int,0,3000', # 0 pixels - 3000 pixels
1722         '0.0.0',
1723         '-1',
1724     ],
1725     acio_w => [
1726         'actionsiodialog_width',
1727         $xl::s{l_win_w},
1728         $xl::s{h_win_w},
1729         'int,0,3000', # 0 pixels - 3000 pixels
1730         '0.0.0',
1731         '582',
1732     ],
1733     acio_h => [
1734         'actionsiodialog_height',
1735         $xl::s{l_win_h},
1736         $xl::s{h_win_h},
1737         'int,0,3000', # 0 pixels - 3000 pixels
1738         '0.0.0',
1739         '310',
1740     ],
1741     tags_w => [
1742         'tagswin_width',
1743         $xl::s{l_win_w},
1744         $xl::s{h_win_w},
1745         'int,0,3000', # 0 pixels - 3000 pixels
1746         '0.0.0',
1747         '486',
1748     ],
1749     tags_h => [
1750         'tagswin_height',
1751         $xl::s{l_win_h},
1752         $xl::s{h_win_h},
1753         'int,0,3000', # 0 pixels - 3000 pixels
1754         '0.0.0',
1755         '-1',
1756     ],
1757     plug_w => [
1758         'pluginswin_width',
1759         $xl::s{l_win_w},
1760         $xl::s{h_win_w},
1761         'int,0,3000', # 0 pixels - 3000 pixels
1762         '0.0.0',
1763         '-1',
1764     ],
1765     plug_h => [
1766         'pluginswin_height',
1767         $xl::s{l_win_h},
1768         $xl::s{h_win_h},
1769         'int,0,3000', # 0 pixels - 3000 pixels
1770         '0.0.0',
1771         '-1',
1772     ],
1773     logw_w => [
1774         'logwin_width',
1775         $xl::s{l_win_w},
1776         $xl::s{h_win_w},
1777         'int,0,3000', # 0 pixels - 3000 pixels
1778         '0.0.0',
1779         '520',
1780     ],
1781     logw_h => [
1782         'logwin_height',
1783         $xl::s{l_win_h},
1784         $xl::s{h_win_h},
1785         'int,0,3000', # 0 pixels - 3000 pixels
1786         '0.0.0',
1787         '-1',
1788     ],
1789     prin_w => [
1790         'print_previewwin_width',
1791         $xl::s{l_win_w},
1792         $xl::s{h_win_w},
1793         'int,0,3000', # 0 pixels - 3000 pixels
1794         '0.0.0',
1795         '600',
1796     ],
1797     prin_h => [
1798         'print_previewwin_height',
1799         $xl::s{l_win_h},
1800         $xl::s{h_win_h},
1801         'int,0,3000', # 0 pixels - 3000 pixels
1802         '0.0.0',
1803         '-1',
1804     ],
1805 );
1806
1807 sub new_winpos_subpage_main() {
1808     return new_subpage_frame (
1809                new_vbox_pack_compact (
1810                    new_vbox_pack (
1811                        new_text_box_for_int (\%pr::win, 'main_x', \%HPVALUE),
1812                        new_text_box_for_int (\%pr::win, 'main_y', \%HPVALUE)),
1813                    new_hbox_pack (
1814                        new_text_box_for_int (\%pr::win, 'main_w', \%HPVALUE),
1815                        new_text_box_for_int (\%pr::win, 'main_h', \%HPVALUE)),
1816                    new_vbox_pack (
1817                        new_check_button_for (\%pr::win, 'main_fs', \%HPVALUE),
1818                        new_check_button_for (\%pr::win, 'main_mx', \%HPVALUE))),
1819                _('Main window')
1820            );
1821 }
1822
1823 sub new_winpos_subpage_msgs() {
1824     return new_subpage_frame (
1825                new_vbox_pack_compact (
1826                    new_vbox_pack (
1827                        new_text_box_for_int (\%pr::win, 'msgs_x', \%HPVALUE),
1828                        new_text_box_for_int (\%pr::win, 'msgs_y', \%HPVALUE)),
1829                    new_hbox_pack (
1830                        new_text_box_for_int (\%pr::win, 'msgs_w', \%HPVALUE),
1831                        new_text_box_for_int (\%pr::win, 'msgs_h', \%HPVALUE))),
1832                _('Message window')
1833            );
1834 }
1835
1836 sub new_winpos_subpage_sendrecv() {
1837     return new_vbox_pack (
1838                new_subpage_frame (
1839                    new_hbox_pack (
1840                        new_text_box_for_int (\%pr::win, 'send_w', \%HPVALUE),
1841                        new_text_box_for_int (\%pr::win, 'send_h', \%HPVALUE)),
1842                    _('Send window'), 'not-packed'),
1843                new_subpage_frame (
1844                    new_hbox_pack (
1845                        new_text_box_for_int (\%pr::win, 'recv_w', \%HPVALUE),
1846                        new_text_box_for_int (\%pr::win, 'recv_h', \%HPVALUE)),
1847                    _('Receive window'), 'not-packed')
1848            );
1849 }
1850
1851 sub new_winpos_subpage_fold() {
1852     return new_vbox_pack (
1853                new_subpage_frame (
1854                    new_vbox_pack_compact (
1855                        new_vbox_pack (
1856                             new_text_box_for_int (\%pr::win, 'fold_x', \%HPVALUE),
1857                             new_text_box_for_int (\%pr::win, 'fold_y', \%HPVALUE)),
1858                        new_hbox_pack (
1859                            new_text_box_for_int (\%pr::win, 'fold_w', \%HPVALUE),
1860                            new_text_box_for_int (\%pr::win, 'fold_h', \%HPVALUE))),
1861                    _('Folder window'), 'not-packed'),
1862                new_subpage_frame (
1863                    new_hbox_pack (
1864                        new_text_box_for_int (\%pr::win, 'fsel_w', \%HPVALUE),
1865                        new_text_box_for_int (\%pr::win, 'fsel_h', \%HPVALUE)),
1866                    _('Folder selection window'), 'not-packed')
1867            );
1868 }
1869
1870 sub new_winpos_subpage_addrbook() {
1871     return new_vbox_pack (
1872                 new_subpage_frame (
1873                      new_hbox_pack (
1874                           new_text_box_for_int (\%pr::win, 'addr_w', \%HPVALUE),
1875                           new_text_box_for_int (\%pr::win, 'addr_h', \%HPVALUE)),
1876                      _('Addressbook main window'), 'not-packed'),
1877                 new_subpage_frame (
1878                      new_hbox_pack (
1879                           new_text_box_for_int (\%pr::win, 'adep_w', \%HPVALUE),
1880                           new_text_box_for_int (\%pr::win, 'adep_h', \%HPVALUE)),
1881                      _('Edit person window'), 'not-packed'),
1882                 new_subpage_frame (
1883                      new_hbox_pack (
1884                           new_text_box_for_int (\%pr::win, 'adeg_w', \%HPVALUE),
1885                           new_text_box_for_int (\%pr::win, 'adeg_h', \%HPVALUE)),
1886                      _('Edit group window'), 'not-packed'),
1887                 new_subpage_frame (
1888                      new_hbox_pack (
1889                           new_text_box_for_int (\%pr::win, 'adda_w', \%HPVALUE),
1890                           new_text_box_for_int (\%pr::win, 'adda_h', \%HPVALUE)),
1891                      _('Add address window'), 'not-packed'),
1892                 new_subpage_frame (
1893                      new_hbox_pack (
1894                           new_text_box_for_int (\%pr::win, 'addf_w', \%HPVALUE),
1895                           new_text_box_for_int (\%pr::win, 'addf_h', \%HPVALUE)),
1896                      _('Folder select window'), 'not-packed')
1897            );
1898 }
1899
1900 sub new_winpos_subpage_accounts() {
1901     return new_vbox_pack (
1902                 new_subpage_frame (
1903                      new_hbox_pack (
1904                           new_text_box_for_int (\%pr::win, 'acco_w', \%HPVALUE),
1905                           new_text_box_for_int (\%pr::win, 'acco_h', \%HPVALUE)),
1906                      _('Accounts window'), 'not-packed'),
1907                 new_subpage_frame (
1908                      new_hbox_pack (
1909                           new_text_box_for_int (\%pr::win, 'acce_w', \%HPVALUE),
1910                           new_text_box_for_int (\%pr::win, 'acce_h', \%HPVALUE)),
1911                      _('Edit account window'), 'not-packed')
1912            );
1913 }
1914
1915 sub new_winpos_subpage_filtering() {
1916     return new_vbox_pack (
1917                 new_subpage_frame (
1918                      new_hbox_pack (
1919                           new_text_box_for_int (\%pr::win, 'filt_w', \%HPVALUE),
1920                           new_text_box_for_int (\%pr::win, 'filt_h', \%HPVALUE)),
1921                      _('Filtering window'), 'not-packed'),
1922                 new_subpage_frame (
1923                      new_hbox_pack (
1924                           new_text_box_for_int (\%pr::win, 'fila_w', \%HPVALUE),
1925                           new_text_box_for_int (\%pr::win, 'fila_h', \%HPVALUE)),
1926                      _('Filtering actions window'), 'not-packed'),
1927                 new_subpage_frame (
1928                      new_hbox_pack (
1929                           new_text_box_for_int (\%pr::win, 'fild_w', \%HPVALUE),
1930                           new_text_box_for_int (\%pr::win, 'fild_h', \%HPVALUE)),
1931                      _('Filtering debug window'), 'not-packed'),
1932                 new_subpage_frame (
1933                      new_hbox_pack (
1934                           new_text_box_for_int (\%pr::win, 'matc_w', \%HPVALUE),
1935                           new_text_box_for_int (\%pr::win, 'matc_h', \%HPVALUE)),
1936                      _('Matcher window'), 'not-packed')
1937            );
1938 }
1939
1940 sub new_winpos_subpage_useractions() {
1941     return new_vbox_pack (
1942                 new_subpage_frame (
1943                      new_hbox_pack (
1944                           new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
1945                           new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE)),
1946                      _('User Actions prefs window'), 'not-packed'),
1947                 new_subpage_frame (
1948                      new_hbox_pack (
1949                           new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
1950                           new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE)),
1951                      _('User Actions I/O window'), 'not-packed')
1952            );
1953 }
1954
1955 sub new_winpos_subpage_prefs() {
1956     return new_vbox_pack (
1957                 new_subpage_frame (
1958                      new_hbox_pack (
1959                           new_text_box_for_int (\%pr::win, 'pref_w', \%HPVALUE),
1960                           new_text_box_for_int (\%pr::win, 'pref_h', \%HPVALUE)),
1961                      _('Preferences window'), 'not-packed'),
1962                 new_subpage_frame (
1963                      new_hbox_pack (
1964                           new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
1965                           new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE)),
1966                      _('Templates window'), 'not-packed'),
1967                 new_subpage_frame (
1968                      new_hbox_pack (
1969                           new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
1970                           new_text_box_for_int (\%pr::win, 'tags_h', \%HPVALUE)),
1971                      _('Tags window'), 'not-packed'),
1972                 new_subpage_frame (
1973                      new_hbox_pack (
1974                           new_text_box_for_int (\%pr::win, 'plug_w', \%HPVALUE),
1975                           new_text_box_for_int (\%pr::win, 'plug_h', \%HPVALUE)),
1976                      _('Plugins window'), 'not-packed')
1977            );
1978 }
1979
1980 sub new_winpos_subpage_misc() {
1981     return new_vbox_pack (
1982                 new_subpage_frame (
1983                      new_hbox_pack (
1984                           new_text_box_for_int (\%pr::win, 'logw_w', \%HPVALUE),
1985                           new_text_box_for_int (\%pr::win, 'logw_h', \%HPVALUE)),
1986                      _('Log window'), 'not-packed'),
1987                 new_subpage_frame (
1988                      new_hbox_pack (
1989                           new_text_box_for_int (\%pr::win, 'prin_w', \%HPVALUE),
1990                           new_text_box_for_int (\%pr::win, 'prin_h', \%HPVALUE)),
1991                      _('Print preview window'), 'not-packed'),
1992                 new_subpage_frame (
1993                      new_hbox_pack (
1994                           new_text_box_for_int (\%pr::win, 'sour_w', \%HPVALUE),
1995                           new_text_box_for_int (\%pr::win, 'sour_h', \%HPVALUE)),
1996                      _('View source window'), 'not-packed')
1997            );
1998 }
1999
2000 sub new_winpos_page() {
2001     my $winbook = Gtk3::Notebook->new;
2002     $winbook->set_tab_pos ('right');
2003     $winbook->append_page (new_winpos_subpage_main, _('Main'));
2004     $winbook->append_page (new_winpos_subpage_msgs, _('Message'));
2005     $winbook->append_page (new_winpos_subpage_sendrecv, _('Send/Receive'));
2006     $winbook->append_page (new_winpos_subpage_fold, _('Folder'));
2007     $winbook->append_page (new_winpos_subpage_addrbook, _('Addressbook'));
2008     $winbook->append_page (new_winpos_subpage_accounts, _('Accounts'));
2009     $winbook->append_page (new_winpos_subpage_filtering, _('Filtering'));
2010     $winbook->append_page (new_winpos_subpage_useractions, _('User Actions'));
2011     $winbook->append_page (new_winpos_subpage_prefs, _('Preferences'));
2012     $winbook->append_page (new_winpos_subpage_misc, _('Other'));
2013     return $winbook;
2014 }
2015
2016 %pr::acc = ( # per account hidden preferences
2017     tls_set => [
2018         'gnutls_set_priority',
2019         $xl::s{l_acc_gtls_set},
2020         $xl::s{h_acc_gtls_set},
2021         'bool',
2022         '3.9.0.181',
2023         '0',
2024     ],
2025     tls_pri => [
2026         'gnutls_priority',
2027         $xl::s{l_acc_gtls_pri},
2028         $xl::s{h_acc_gtls_pri},
2029         'char,0,256,32',
2030         '3.9.0.181',
2031         '0',
2032     ],
2033 );
2034
2035 sub new_account_subpage($) {
2036     my ($akey) = @_;
2037     return new_vbox_pack (
2038                 new_subpage_frame (
2039                     new_vbox_pack (
2040                         new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}),
2041                         new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey})),
2042                     _('GnuTLS priority'), 'not-packed')
2043            );
2044 }
2045
2046 sub new_accounts_page() {
2047     my $accbook = Gtk3::Notebook->new;
2048     $accbook->set_tab_pos ('right');
2049     my @akeys = sort {
2050         $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
2051     } keys %ACPREFS;
2052     foreach (@akeys) {
2053         my $name = $ACPREFS{$_}{'account_name'};
2054         my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
2055         my $page = new_account_subpage ($_);
2056         $accbook->append_page ($page, $isdef? '<b>' . $name . '</b>': $name);
2057         if ($isdef) {
2058             my $label = $accbook->get_tab_label ($page);
2059             $label->set_use_markup (TRUE);
2060         }
2061     }
2062     $accbook->set_scrollable (TRUE);
2063     return $accbook;
2064 }
2065
2066 %pr::plu = ( # plugins hidden preferences
2067     # att_remover
2068     arm_winw => [
2069         'win_width',
2070         $xl::s{l_win_w},
2071         $xl::s{h_win_w},
2072         'int,0,3000', # 0 pixels - 3000 pixels
2073         '3.9.0.74',
2074         '-1',
2075         'AttRemover',
2076     ],
2077     arm_winh => [
2078         'win_height',
2079         $xl::s{l_win_h},
2080         $xl::s{h_win_h},
2081         'int,0,3000', # 0 pixels - 3000 pixels
2082         '3.9.0.74',
2083         '-1',
2084         'AttRemover',
2085     ],
2086     # GPG
2087     gpg_alimit => [
2088         'autocompletion_limit',
2089         $xl::s{l_plu_gpg_alimit},
2090         $xl::s{h_plu_gpg_alimit},
2091         'int,0,100',
2092         '3.12.0.75',
2093         '0',
2094         'GPG',
2095     ],
2096     # managesieve
2097     msv_winw => [
2098         'manager_win_width',
2099         $xl::s{l_win_w},
2100         $xl::s{h_win_w},
2101         'int,0,3000', # 0 pixels - 3000 pixels
2102         '3.11.1.210',
2103         '-1',
2104         'ManageSieve',
2105     ],
2106     msv_winh => [
2107         'manager_win_height',
2108         $xl::s{l_win_h},
2109         $xl::s{h_win_h},
2110         'int,0,3000', # 0 pixels - 3000 pixels
2111         '3.11.1.210',
2112         '-1',
2113         'ManageSieve',
2114     ],
2115     # libravatar
2116     lav_burl => [
2117         'base_url',
2118         $xl::s{l_plu_lav_burl},
2119         $xl::s{h_plu_lav_burl},
2120         'char,0,1024,32',
2121         '3.9.3.32',
2122         'http://cdn.libravatar.org/avatar',
2123         'Libravatar',
2124     ],
2125     # perl
2126     prl_flvb => [
2127         'filter_log_verbosity',
2128         $xl::s{l_plu_prl_flvb},
2129         $xl::s{h_plu_prl_flvb},
2130         '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2131         '3.9.0.75',
2132         '2',
2133         'PerlPlugin',
2134     ],
2135 );
2136
2137 sub new_plugins_page() {
2138     my %frame = ();
2139     $frame{'AttRemover'} =
2140                 new_subpage_frame (
2141                      new_hbox_pack (
2142                           new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2143                           new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})),
2144                      _('Attachment remover'), 'not-packed');
2145     $frame{'GPG'} =
2146                 new_subpage_frame (
2147                      new_hbox_pack (
2148                           new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})),
2149                      _('GPG'), 'not-packed');
2150     $frame{'ManageSieve'} =
2151                 new_subpage_frame (
2152                      new_hbox_pack (
2153                           new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2154                           new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})),
2155                      _('Sieve manager'), 'not-packed');
2156     $frame{'Libravatar'} =
2157                 new_subpage_frame (
2158                      new_hbox_pack (
2159                           new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'})),
2160                      _('Libravatar'), 'not-packed');
2161     $frame{'PerlPlugin'} =
2162                 new_subpage_frame (
2163                      new_hbox_pack (
2164                           new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})),
2165                      _('Perl'), 'not-packed');
2166     foreach (@PLUGINS) {
2167         $frame{$_}->set_sensitive (defined $PLHPVALUE{$_});
2168     }
2169     return new_vbox_pack (
2170                 $frame{'AttRemover'},
2171                 $frame{'GPG'},
2172                 $frame{'ManageSieve'},
2173                 $frame{'Libravatar'},
2174                 $frame{'PerlPlugin'});
2175 }
2176
2177 use constant {
2178     C_LABEL => 0,
2179     C_HOTKEY => 1,
2180     C_GROUP => 2,
2181     C_ACCEL => 3,
2182     C_BCOLOR => 4,
2183     # cell backgrounds
2184     BG_LIGHTER => '#ffffff',
2185     BG_DARKER => '#eeeeee'
2186 };
2187
2188 sub new_hotkeys_list_label {
2189     my $renderer = Gtk3::CellRendererText->new ();
2190     $renderer->set_property('alignment' => 'left');
2191     $renderer->set_property('editable' => FALSE);
2192     return $renderer;
2193 }
2194
2195 sub new_hotkeys_list_hotkey {
2196     my $renderer = Gtk3::CellRendererAccel->new ();
2197     $renderer->set_property ('accel-mode' => 'gtk');
2198     $renderer->set_property ('editable' => TRUE);
2199     $renderer->signal_connect ('accel-edited' => sub {
2200         my ($w, $path, $key, $mods, $keycode) = @_;
2201         my $accel = Gtk3::Accelerator->name ($key, $mods);
2202         my ($model, $iter) = $SELHOTKEY->get_selected ();
2203         $model->set($iter, C_HOTKEY, "\"$accel\"");
2204         my $gkey = $model->get_value ($iter, C_GROUP);
2205         my $akey = $model->get_value ($iter, C_ACCEL);
2206         my $data = $HOTKEYS->{$gkey}->{$akey};
2207         $data->{'key'} = "\"$accel\"";
2208         $data->{'enabled'} = TRUE;
2209     });
2210     $renderer->signal_connect ('accel-cleared' => sub {
2211         my ($w, $path) = @_;
2212         my ($model, $iter) = $SELHOTKEY->get_selected ();
2213         $model->set($iter, C_HOTKEY, "\"\"");
2214         my $gkey = $model->get_value ($iter, C_GROUP);
2215         my $akey = $model->get_value ($iter, C_ACCEL);
2216         my $data = $HOTKEYS->{$gkey}->{$akey};
2217         $data->{'key'} = "\"\"";
2218         $data->{'enabled'} = FALSE;
2219     });
2220     return $renderer;
2221 }
2222
2223 sub new_hotkeys_list {
2224     my ($gkey, $group) = @_;
2225     my $store = Gtk3::ListStore->new(
2226         qw/Glib::String Glib::String Glib::String Glib::String Glib::String/);
2227     my $even = FALSE;
2228     foreach my $akey (sort keys %$group) {
2229         my $iter = $store->append ();
2230         my $hotkey = $group->{$akey}->{'key'};
2231         my $label = $akey;
2232         $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2233         my $bgcol = $even ? BG_DARKER: BG_LIGHTER;
2234         $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2235             C_GROUP, $gkey, C_ACCEL, $akey, C_BCOLOR, $bgcol);
2236         $even = not $even;
2237     }
2238     my $treeview = Gtk3::TreeView->new_with_model ($store);
2239     # labels column
2240     $treeview->insert_column_with_data_func (
2241         0, _("Menu path"), new_hotkeys_list_label (),
2242         sub {
2243             my ($col, $renderer, $model, $iter, $data) = @_;
2244             $renderer->set_property (
2245                 'markup' => '<span size="smaller">'
2246                             . $model->get_value ($iter, C_LABEL)
2247                             . '</span>');
2248             $renderer->set_property (
2249                 'background' => $model->get_value ($iter, C_BCOLOR));
2250         }
2251     );
2252     # hotkeys column
2253     $treeview->insert_column_with_data_func (
2254         1, _('Hotkey'), new_hotkeys_list_hotkey (),
2255         sub {
2256             my ($col, $renderer, $model, $iter, $data) = @_;
2257             my $hkey = $model->get_value ($iter, C_HOTKEY);
2258             $hkey =~ s/\"//g;
2259             my ($acckey, $accmod) = Gtk3::Accelerator->parse ($hkey);
2260             $renderer->set_property ('accel-key' => $acckey);
2261             $renderer->set_property ('accel-mods' => $accmod);
2262             $renderer->set_property (
2263                 'background' => $model->get_value ($iter, C_BCOLOR));
2264         }
2265     );
2266     # callback for saving current selection
2267     my $selection = $treeview->get_selection ();
2268     $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2269     return $treeview;
2270 }
2271
2272 sub new_hotkeys_page() {
2273     my $swin = Gtk3::ScrolledWindow->new ();
2274     my $vbox = Gtk3::VBox->new (FALSE, 5);
2275     foreach my $gkey (sort keys %$HOTKEYS) {
2276         my $group = $HOTKEYS->{$gkey};
2277         # group title
2278         my $glabel = Gtk3::Label->new ('<b>' . $gkey . '</b>');
2279         $glabel->set_use_markup (TRUE);
2280         $glabel->set_alignment (0, 0.5);
2281         $glabel->set_padding (5, 1);
2282         $vbox->pack_start ($glabel, FALSE, FALSE, 0);
2283         # group key list
2284         my $keylist = new_hotkeys_list ($gkey, $group);
2285         $vbox->pack_start ($keylist, FALSE, FALSE, 0);
2286     }
2287     $swin->set_border_width (5);
2288     $swin->set_shadow_type ('none');
2289     $swin->set_policy ('automatic', 'always');
2290     $swin->add_with_viewport ($vbox);
2291     return $swin;
2292 }
2293
2294 sub new_info_page() {
2295     my $t0 = Gtk3::Table->new (7, 2, FALSE);
2296     my $v = get_toolkit_versions ();
2297     my %labels = (
2298         'glib' => 'Perl-GLib',
2299         'glib-r' => _('GLib runtime'),
2300         'glib-b' => _('GLib built'),
2301         'gtk2' => 'Perl-GTK2',
2302         'gtk2-r' => _('GTK2 runtime'),
2303         'gtk2-b' => _('GTK2 built'),
2304     );
2305     my $row = 0;
2306     foreach (sort keys %$v) {
2307         if (defined $v->{$_}) {
2308             my $label = Gtk3::Label->new ($labels{$_});
2309             my $value = Gtk3::Label->new ('<b>' . $v->{$_} . '</b>');
2310             $label->set_alignment (0, 0.5);
2311             $value->set_alignment (0, 0.5);
2312             $value->set_use_markup (TRUE);
2313             $t0->attach ($label, 0, 1, $row, $row + 1, 'fill', 'shrink', 8, 6);
2314             $t0->attach ($value, 1, 2, $row, $row + 1, 'fill', 'shrink', 8, 6);
2315             ++$row;
2316         }
2317     }
2318     my $t1 = Gtk3::Table->new (2, 2, FALSE);
2319     my @lbl = map { $_->set_alignment (0, 0.5); $_ } (
2320         Gtk3::Label->new (_('Binary')),
2321         Gtk3::Label->new (_('Configuration'))
2322     );
2323     my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2324     $cfgv //= '';
2325     my @val = map { $_->set_alignment (0, 0.5); $_->set_use_markup (TRUE); $_ } (
2326         Gtk3::Label->new ('<b>' . $CLAWSV . '</b>'),
2327         Gtk3::Label->new ('<b>' . $cfgv . '</b>')
2328     );
2329     for (my $i = 0; $i <= $#lbl; ++$i) {
2330         $t1->attach ($lbl[$i], 0, 1, $i, $i + 1, 'fill', 'shrink', 8, 6);
2331         $t1->attach ($val[$i], 1, 2, $i, $i + 1, 'fill', 'shrink', 8, 6);
2332     }
2333     return new_vbox_pack (
2334                 new_subpage_frame ($t0, _('Library versions'), 'not-packed'),
2335                 new_subpage_frame ($t1, _('Claws Mail versions'), 'not-packed'));
2336 }
2337
2338 # version info
2339 sub get_toolkit_versions {
2340     my %versions = ();
2341     $versions{'glib'} = $Glib::VERSION;
2342     # version info stuff appeared in 1.040
2343     if ($Glib::VERSION >= 1.040) {
2344         $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2345         $versions{'glib-r'} = join('.',
2346             &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2347     }
2348     $versions{'gtk2'} = $Gtk3::VERSION;
2349     if ($Gtk3::VERSION >= 1.040) {
2350         $versions{'gtk2-b'} = join('.', Gtk3->GET_VERSION_INFO);
2351         $versions{'gtk2-r'} = join('.',
2352             &Gtk3::major_version, &Gtk3::minor_version, &Gtk3::micro_version);
2353     }
2354     return \%versions;
2355 }
2356
2357 sub print_version() {
2358     print $xl::s{about_title} . "\n";
2359     print $xl::s{about_version} . " $VERSION\n";
2360     my $v = get_toolkit_versions ();
2361     if ($v->{'glib-b'}) {
2362         print _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2363                 glibv => $v->{'glib'},
2364                 glibb => $v->{'glib-b'},
2365                 glibr => $v->{'glib-r'});
2366     } else {
2367         print _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2368     }
2369     print "\n";
2370     if ($v->{'gtk2-b'}) {
2371         print _("Perl-GTK2 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2372                 gtkv => $v->{'gtk2'},
2373                 gtkb => $v->{'gtk2-b'},
2374                 gtkr => $v->{'gtk2-r'});
2375     } else {
2376         print _("Perl-GTK2 version {gtkv}.", gtkv => $v->{'gtk2'});
2377     }
2378     print "\n";
2379     my $clawsver = ($CLAWSV eq "") ?
2380                 _("Claws Mail was not found!") :
2381                 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2382     print $clawsver . "\n";
2383 }
2384
2385 # the command line help
2386 sub print_help() {
2387     my $line = '-' x length ($xl::s{about_title});
2388     say $line;
2389     say $xl::s{about_title};
2390     say $line;
2391     my @help = (
2392         _("Syntax:"),
2393         _("  clawsker [options]"),
2394         _("Options:"),
2395         _("  -a|--alternate-config-dir <dir>  Uses <dir> as Claws Mail configuration."),
2396         _("  -b|--verbose                     More messages on standard output."),
2397         _("  -c|--clawsrc <file>              Uses <file> as full resource name."),
2398         _("  -h|--help                        Prints this help screen and exits."),
2399         _("  -r|--read-only                   Disables writing changes to disk."),
2400         _("  -v|--version                     Prints version information and exits.")
2401     );
2402     foreach (@help) { say $_ }
2403 }
2404
2405 sub parse_command_line {
2406     my $cont = TRUE;
2407     $CLAWSV = get_claws_version ();
2408     eval {
2409         GetOptions('h|help' => sub { print_help (); $cont = FALSE },
2410             'v|version' => sub { print_version (); $cont = FALSE },
2411             'b|verbose' => sub { $VERBOSE = TRUE },
2412             'r|read-only' => sub { $READONLY = TRUE },
2413             'u|use-claws-version=s' => \&opt_use_claws_version,
2414             'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2415             'c|clawsrc=s' => \&opt_clawsrc)
2416         or die _("try -h or --help for syntax.\n");
2417     };
2418     if ($@) {
2419         my $msg = _("Error in options: {msg}\n", msg => $@);
2420         if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2421             eval { Gtk3->init };
2422             error_dialog ($msg) unless $@;
2423         }
2424         die $msg;
2425     }
2426     return $cont;
2427 }
2428
2429 sub opt_use_claws_version {
2430     my ($name, $value) = @_;
2431     die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2432         unless ($value =~ /^[\d\.]+$/);
2433     $CLAWSV = $value;
2434 }
2435
2436 sub opt_alternate_config_dir {
2437     my ($name, $value) = @_;
2438     die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2439         unless -d $value;
2440     $CONFIGDIR = $value;
2441     $CONFIGDIR .= "/" unless ($CONFIGDIR =~ /.*\/$/);
2442     $ALTCONFIGDIR = TRUE;
2443 }
2444
2445 sub opt_clawsrc {
2446     my ($name, $value) = @_;
2447     die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2448         unless -f $value;
2449     set_rc_filename ($value);
2450 }
2451
2452 # update the hidden preferences status from loaded values
2453 sub init_hidden_preferences {
2454     foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2455         foreach my $key (keys %$hash) {
2456             $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2457             $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2458         }
2459     }
2460     foreach my $akey (keys %ACPREFS) {
2461         foreach my $key (keys %pr::acc) {
2462             my $pname = $pr::acc{$key}[NAME];
2463             $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2464             $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2465         }
2466     }
2467     foreach my $key (keys %pr::plu) {
2468         my $plugin = $pr::plu{$key}[PLUGIN];
2469         my $pname = $pr::plu{$key}[NAME];
2470         if (defined $PLPREFS{$plugin}) {
2471             $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2472             $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2473         }
2474     }
2475     return TRUE;
2476 }
2477
2478 # generic load/save resource files
2479 sub load_resource {
2480     my $rc = shift;
2481     my %data = ();
2482     my %meta = ();
2483     my $line = 0;
2484     open (RCF, '<:encoding(utf8)', $rc)
2485         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2486     my $section = '_'; # default unnamed section
2487     while (<RCF>) {
2488         chomp;
2489         ++$line;
2490         next if (/^\s*$/);
2491         if (/^\[([^\]]+)\]$/) { # new section
2492             $section = $1;
2493             die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2494                 sect => $section, file => $rc) if ($data{$section});
2495             $data{$section} = {};
2496             $meta{$section}{'#'} = $line;
2497         }
2498         elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2499             $data{$section}{$1} = $2;
2500             $meta{$section}{$1} = $line;
2501         }
2502         elsif (/^(.*)$/) { # lone value
2503             push (@{$data{$section}{'_'}}, $1);
2504         }
2505     }
2506     close (RCF);
2507     return (\%data, \%meta);
2508 }
2509
2510 sub save_resource {
2511     my ($rc, $data, $meta) = @_;
2512     open (RCF, '>:utf8', $rc)
2513         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2514     my @sections = keys %$data;
2515     if (defined $meta) {
2516         @sections = sort {
2517             $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2518         } @sections
2519     }
2520     foreach my $section (@sections) {
2521         say RCF "[$section]";
2522         if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2523             foreach my $val (@{$data->{$section}{'_'}}) {
2524                 say RCF $val;
2525             }
2526         } else {
2527             my @keys = keys %{$data->{$section}};
2528             if (defined $meta) {
2529                 @keys = sort {
2530                     $meta->{$section}{$a} <=> $meta->{$section}{$b}
2531                 } @keys
2532             }
2533             foreach my $key (@keys) {
2534                 my $val = $data->{$section}{$key};
2535                 say RCF "$key=$val";
2536             }
2537         }
2538         say RCF "";
2539     }
2540     close (RCF);
2541 }
2542
2543 sub backup_resource {
2544     my $rc = shift;
2545     my $rcbak = "$rc.backup";
2546     do {
2547         my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2548         log_message ($emsg);
2549         error_dialog ($emsg);
2550         return FALSE;
2551     } unless rename ($rc, $rcbak);
2552     return TRUE;
2553 }
2554
2555 # specific loaders
2556 sub load_menurc {
2557     my $rc = shift;
2558     open (RCF, '<:encoding(utf8)', $rc)
2559         or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2560     my %groups = ();
2561     my $line = 0;
2562     while (<RCF>) {
2563         chomp;
2564         if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2565             my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2566             $groups{$1}{$2} = \%data;
2567             # say "group -> $1 | path -> $2 | key -> $3";
2568         } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2569             my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2570             $groups{$1}{$2} = \%data;
2571             # say "group -> $1 | path -> $2 | key -> $3";
2572         }
2573         ++$line;
2574     }
2575     close (RCF);
2576     return \%groups;
2577 }
2578
2579 sub save_menurc {
2580     my ($rc, $groups) = @_;
2581     my @lines = ();
2582     foreach my $gkey (keys %$groups) {
2583         my $group = $groups->{$gkey};
2584         foreach my $akey (keys %$group) {
2585             my $data = $group->{$akey};
2586             my $key = $data->{'key'};
2587             my $line = $data->{'line'};
2588             $lines[$line] = ($data->{'enabled'})? '': '; ';
2589             $lines[$line] .= '(gtk_accel_path "<'
2590                     . $gkey . '>' . $akey . '" ' . $key . ')';
2591         }
2592     }
2593     open (RCF, '>:utf8', $rc)
2594         or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2595     say RCF '; claws-mail GtkAccelMap rc-file         -*- scheme -*-';
2596     say RCF '; this file is an automated accelerator map dump';
2597     say RCF ';';
2598     foreach (@lines) { say RCF $_ if $_ }
2599     close (RCF);
2600 }
2601
2602 # load current status from disc
2603 sub load_rc_preferences {
2604     my $rc = get_rc_filename ();
2605     log_message ("Loading preferences from $rc\n");
2606     return FALSE unless check_rc_file ($rc);
2607     ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2608     foreach (keys %{$CONFIGDATA->{'Common'}}) {
2609         $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2610     }
2611     foreach my $plugin (@PLUGINS) {
2612         if (defined $CONFIGDATA->{$plugin}) {
2613             push (@AVPLUGINS, $plugin);
2614             foreach (keys %{$CONFIGDATA->{$plugin}}) {
2615                 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2616             }
2617         }
2618     }
2619     return TRUE;
2620 }
2621
2622 sub load_ac_preferences {
2623     my $rc = get_ac_rc_filename ();
2624     log_message ("Loading account preferences from $rc\n");
2625     return FALSE unless check_rc_file ($rc);
2626     ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2627     foreach my $asect (keys %$ACCOUNTDATA) {
2628         if ($asect =~ /^Account: (\d+)$/) {
2629             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2630                 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2631             }
2632         }
2633     }
2634     return TRUE;
2635 }
2636
2637 sub load_hk_preferences {
2638     my $rc = get_menurc_filename ();
2639     return FALSE unless check_rc_file ($rc);
2640     $HOTKEYS = load_menurc ($rc);
2641     return TRUE;
2642 }
2643
2644 sub load_preferences {
2645     return FALSE unless check_claws_not_running ();
2646     return (load_rc_preferences ()
2647         and load_ac_preferences ()
2648         and load_hk_preferences ()
2649     );
2650 }
2651
2652 # save current preferences to disc
2653 sub save_rc_preferences {
2654     my $rc = get_rc_filename ();
2655     log_message ("Saving preferences to $rc\n");
2656     return FALSE unless check_rc_file ($rc);
2657     return FALSE unless check_claws_not_running ();
2658     return FALSE unless backup_resource ($rc);
2659     foreach (keys %PREFS) {
2660         if (defined $HPVALUE{$_}) {
2661             $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2662         }
2663     }
2664     foreach my $plugin (@AVPLUGINS) {
2665         foreach (keys %{$CONFIGDATA->{$plugin}}) {
2666             if (defined $PLHPVALUE{$plugin}{$_}) {
2667                 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2668             }
2669         }
2670     }
2671     save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2672     return TRUE;
2673 }
2674
2675 sub save_ac_preferences {
2676     my $rc = get_ac_rc_filename ();
2677     log_message ("Saving account preferences to $rc\n");
2678     return FALSE unless check_rc_file ($rc);
2679     return FALSE unless check_claws_not_running ();
2680     return FALSE unless backup_resource ($rc);
2681     foreach my $asect (keys %$ACCOUNTDATA) {
2682         if ($asect =~ /^Account: (\d+)$/) {
2683             foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2684                 if (defined $ACHPVALUE{$1}{$_}) {
2685                     $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2686                 }
2687             }
2688         }
2689     }
2690     save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2691     return TRUE;
2692 }
2693
2694 sub save_hk_preferences {
2695     my $rc = get_menurc_filename ();
2696     log_message ("Saving hotkey preferences to $rc\n");
2697     return FALSE unless check_rc_file ($rc);
2698     return FALSE unless check_claws_not_running ();
2699     return FALSE unless backup_resource ($rc);
2700     save_menurc ($rc, $HOTKEYS);
2701     return TRUE;
2702 }
2703
2704 sub save_preferences {
2705     my $result = save_rc_preferences ()
2706         and save_ac_preferences ()
2707         and save_hk_preferences ();
2708     $MODIFIED = 0 if $result;
2709     return $result;
2710 }
2711
2712 # create notebook
2713 sub new_notebook {
2714     my $nb = Gtk3::Notebook->new;
2715     #
2716     $nb->append_page (new_behaviour_page (), $xl::s{tab_behaviour});
2717     $nb->append_page (new_colours_page (), $xl::s{tab_colours});
2718     $nb->append_page (new_gui_page (), $xl::s{tab_gui});
2719     $nb->append_page (new_other_page (), $xl::s{tab_other});
2720     $nb->append_page (new_winpos_page (), $xl::s{tab_winpos});
2721     $nb->append_page (new_accounts_page (), $xl::s{tab_accounts});
2722     $nb->append_page (new_plugins_page (), $xl::s{tab_plugins});
2723     $nb->append_page (new_hotkeys_page (), $xl::s{tab_hotkeys});
2724     $nb->append_page (new_info_page (), $xl::s{tab_info});
2725
2726     return $nb;
2727 }
2728
2729 # create an about dialog
2730 sub new_about_dialog {
2731     my ($parent) = @_;
2732     my $title = $xl::s{about_title};
2733     my $lic = $xl::s{about_license};
2734     my $vers = $xl::s{about_version} . " $VERSION";
2735     my $license =
2736 "This program is free software: you can redistribute it and/or modify
2737 it under the terms of the GNU General Public License as published by
2738 the Free Software Foundation, either version 3 of the License, or
2739 (at your option) any later version.
2740
2741 This program is distributed in the hope that it will be useful,
2742 but WITHOUT ANY WARRANTY; without even the implied warranty of
2743 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2744 GNU General Public License for more details.
2745
2746 You should have received a copy of the GNU General Public License
2747 along with this program.  If not, see &lt;http://www.gnu.org/licenses/&gt;.";
2748     my $year = "2007-2018";
2749     my $holder = "Ricardo Mones &lt;ricardo\@mones.org&gt;";
2750     my $url = "http://www.claws-mail.org/clawsker.php";
2751
2752     my $dialog = Gtk3::MessageDialog->new_with_markup ($parent,
2753                     [qw/modal destroy-with-parent/],
2754                     'info', 'close',
2755                     "<span size=\"x-large\" weight=\"bold\">$title</span>\n"
2756                     . "<span size=\"large\">$vers</span>\n\n"
2757                     . "<span color=\"blue\" size=\"large\">$url</span>\n\n"
2758                     . "<span>Copyright $year by $holder</span>\n\n"
2759                     . "<span size=\"large\">$lic</span>\n\n"
2760                     . "<span size=\"small\">$license</span>");
2761     $dialog->set_title ($xl::s{about});
2762     if (Gtk3->CHECK_VERSION (2, 10, 0)) {
2763         my @icons = get_app_icons ();
2764         my $image = Gtk3::Image->new_from_pixbuf ($icons[-1]);
2765         $image->show ();
2766         $image->set_alignment (0, 0);
2767         $dialog->set_image ($image);
2768     }
2769     #
2770     return $dialog;
2771 }
2772
2773 sub exit_handler {
2774   my ($parent) = @_;
2775   if ($MODIFIED != 0 and not $READONLY) {
2776     my $fact = $xl::s{exit_fact};
2777     my $question = $xl::s{exit_question};
2778     my $dialog = Gtk3::MessageDialog->new_with_markup ($parent,
2779                     [qw/modal destroy-with-parent/],
2780                     'warning', 'yes-no',
2781                     "<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