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