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