3 # Clawsker :: A Claws Mail Tweaker
4 # Copyright 2007-2018 Ricardo Mones <ricardo@mones.org>
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.
11 # See COPYING file for license details.
12 # See AUTHORS file for a complete list of contributors.
15 binmode STDOUT, ":encoding(utf8)";
21 use Glib qw(TRUE FALSE);
24 use File::Spec::Functions;
25 use POSIX qw(setlocale);
28 use Digest::MD5 qw(md5_hex);
31 my $NAME = 'clawsker';
32 my $PREFIX = '@PREFIX@';
33 my $LIBDIR = '@LIBDIR@';
34 my $DATADIR = '@DATADIR@';
35 my $VERSION = '@VERSION@';
39 my $main_window = undef;
41 my $locale = (defined($ENV{LC_MESSAGES}) ? $ENV{LC_MESSAGES} : $ENV{LANG});
42 $locale = "C" unless defined($locale);
43 setlocale (LC_ALL, $locale);
44 bindtextdomain ($NAME, catdir ($PREFIX, 'share', 'locale'));
50 my $xla = gettext ($str);
51 if (scalar(keys(%par)) > 0) {
52 foreach my $key (keys %par) {
53 $xla =~ s/\{$key\}/$par{$key}/g;
56 return decode_utf8($xla);
61 about_title => _('Clawsker :: A Claws Mail Tweaker'),
63 l_oth_use_dlg => _('Use detached address book edit dialogue'),
64 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.'),
65 l_oth_max_use => _('Maximum memory for message cache'),
66 l_oth_max_use_units => _('kilobytes'),
67 h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kilobytes.'),
68 l_oth_min_time => _('Minimun time for cache elements'),
69 l_oth_min_time_units => _('minutes'),
70 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.'),
71 l_oth_use_netm => _('Use NetworkManager'),
72 h_oth_use_netm => _('Use NetworkManager to switch offline automatically.'),
73 l_oth_mp_rounds => _('Rounds for PBKDF2 function'),
74 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.'),
76 l_gui_b_unread => _('Show unread messages with bold font'),
77 h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'),
78 l_gui_no_markup => _('Don\'t use markup'),
79 h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'),
80 l_gui_dot_lines => _('Use dotted lines in tree view components'),
81 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.'),
82 l_gui_h_scroll => _('Enable horizontal scrollbar'),
83 h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'),
84 l_gui_swp_from => _('Display To column instead From column in Sent folder'),
85 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.'),
86 l_gui_v_scroll => _('Folder List scrollbar behaviour'),
87 h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'),
88 l_gui_v_scroll_show => _('Show always'),
89 l_gui_v_scroll_auto => _('Automatic'),
90 l_gui_v_scroll_hide => _('Hide always'),
91 l_gui_from_show => _('From column displays'),
92 h_gui_from_show => _('Selects the data displayed in the From column of the Message List: name, address or both.'),
93 l_gui_from_show_name => _('Name only'),
94 l_gui_from_show_addr => _('Address only'),
95 l_gui_from_show_both => _('Both name and address'),
96 l_gui_strip_off => _('Coloured lines contrast'),
97 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.'),
98 l_gui_cursor_v => _('Show cursor'),
99 h_gui_cursor_v => _('Display the cursor in the Message View.'),
100 l_gui_toolbar_d => _('Detachable toolbars'),
101 h_gui_toolbar_d => _('Show handles in the toolbars.'),
102 l_gui_strip_all => _('Use stripes in all tree view components'),
103 h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'),
104 l_gui_strip_sum => _('Use stripes in Folder List and Message List'),
105 h_gui_strip_sum => _('Enable alternately coloured lines in Message List and Folder List.'),
106 l_gui_two_line_v => _('2 lines per Message List item in 3-column layout'),
107 h_gui_two_line_v => _('Spread Message List information over two lines when using the three column mode.'),
108 l_gui_margin_co => _('Show margin'),
109 h_gui_margin_co => _('Shows a small margin in the Compose View.'),
110 l_gui_mview_date => _('Don\'t display localized date'),
111 h_gui_mview_date => _('Toggles localization of date format in Message View.'),
112 l_gui_zero_char => _('Zero replacement character'),
113 h_gui_zero_char => _('Replaces \'0\' with the given character in Folder List.'),
114 l_gui_type_any => _('Editable headers'),
115 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.'),
116 l_gui_warn_send_multi => _('Warn when sending to more than'),
117 l_gui_warn_send_multi_units => _('recipients'),
118 h_gui_warn_send_multi => _('Show a warning dialogue when sending to more recipients than specified. Use 0 to disable this check.'),
119 l_gui_next_del => _('Select next message on delete'),
120 h_gui_next_del => _('When deleting a message, toggles between selecting the next one (newer message) or the previous one (older message).'),
122 l_beh_hover_t => _('Drag \'n\' drop hover timeout'),
123 l_beh_hover_t_units => _('milliseconds'),
124 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.'),
125 l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
126 h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
127 l_beh_flowed => _('Respect format=flowed in messages'),
128 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.'),
129 l_beh_parts_rw => _('Allow writable temporary files'),
130 h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
131 l_beh_skip_ssl => _('Don\'t check SSL certificates'),
132 h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
133 l_beh_up_step => _('Progress bar update step every'),
134 l_beh_up_step_units => _('items'),
135 h_beh_up_step => _('Update stepping in progress bars.'),
136 l_beh_thread_a => _('Maximum age when threading by subject'),
137 l_beh_thread_a_units => _('days'),
138 h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
139 l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
140 h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
141 l_beh_use_utf8 => _('Force UTF-8 for broken mails'),
142 h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'),
143 l_beh_warn_dnd => _('Warn on drag \'n\' drop'),
144 h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'),
145 l_beh_out_ascii => _('Outgoing messages fallback to ASCII'),
146 h_beh_out_ascii => _('If allowed by content, ASCII will be used to encode outgoing messages, otherwise the user-defined encoding is always enforced.'),
147 l_beh_pp_unsel => _('Primary paste unselects selection'),
148 h_beh_pp_unsel => _('Controls how pasting using middle-click changes the selected text and insertion point.'),
149 l_beh_inline_at => _('Show inline attachments'),
150 h_beh_inline_at => _('Allows to hide inline attachments already shown in mail structure view.'),
151 l_beh_addr_swc => _('Address search in compose window matches any'),
152 h_beh_addr_swc => _('On Tab-key completion, address text will match any part of the string or only from the start.'),
153 l_beh_fold_swc => _('Folder search in folder selector matches any'),
154 h_beh_fold_swc => _('On folder name completion text will match any part of the string or only from the start.'),
155 l_beh_rewrite_ff => _('Rewrite first \'From\' using QP encoding'),
156 h_beh_rewrite_ff => _('Workaround some servers which convert first \'From\' to \'>From\' by using Quoted-Printable transfer encoding instead of 7bit/8bit encoding.'),
158 l_col_emphasis => _('X-Mailer header'),
159 h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'),
160 l_col_log_err => _('Error messages'),
161 h_col_log_err => _('Colour for error messages in log window.'),
162 l_col_log_in => _('Server messages'),
163 h_col_log_in => _('Colour for messages received from servers in log window.'),
164 l_col_log_msg => _('Standard messages'),
165 h_col_log_msg => _('Colour for messages in log window.'),
166 l_col_log_out => _('Client messages'),
167 h_col_log_out => _('Colour for messages sent to servers in log window.'),
168 l_col_log_warn => _('Warning messages'),
169 h_col_log_warn => _('Colour for warning messages in log window.'),
171 l_col_tags_bg => _('Tags background'),
172 h_col_tags_bg => _('Background colour for tags in message view.'),
173 l_col_tags_text => _('Tags text'),
174 h_col_tags_text => _('Text colour for tags in message view.'),
176 l_col_default_header_bg => _('Default headers background'),
177 h_col_default_header_bg => _('Background colour for default headers in compose window.'),
178 l_col_default_header_text => _('Default headers text'),
179 h_col_default_header_text => _('Text colour for default headers in compose window.'),
181 l_col_qs_active_bg => _('Active quick search background'),
182 h_col_qs_active_bg => _('Background colour for active quick search.'),
183 l_col_qs_active_text => _('Active quick search text'),
184 h_col_qs_active_text => _('Text colour for active quick search.'),
185 l_col_qs_error_bg => _('Quick search error background'),
186 h_col_qs_error_bg => _('Background colour for quick search error.'),
187 l_col_qs_error_text => _('Quick search error text'),
188 h_col_qs_error_text => _('Text colour for quick search error.'),
190 l_col_diff_add => _('Added lines'),
191 h_col_diff_add => _('Colour for added lines in patches.'),
192 l_col_diff_del => _('Deleted lines'),
193 h_col_diff_del => _('Colour for deleted lines in patches.'),
194 l_col_diff_hunk => _('Hunk lines'),
195 h_col_diff_hunk => _('Colour for hunk headers in patches.'),
197 l_win_x => _('X position'),
198 h_win_x => _('X coordinate for window\'s top-left corner.'),
199 l_win_y => _('Y position'),
200 h_win_y => _('Y coordinate for window\'s top-left corner.'),
201 l_win_w => _('Width'),
202 h_win_w => _('Window\'s width in pixels.'),
203 l_win_h => _('Height'),
204 h_win_h => _('Window\'s height in pixels.'),
206 l_win_main_mx => _('Maximized'),
207 h_win_main_mx => _('Changes window maximized status.'),
208 l_win_main_fs => _('Full-screen'),
209 h_win_main_fs => _('Changes full screen status.'),
211 l_acc_gtls_set => _('Use custom GnuTLS priority'),
212 h_acc_gtls_set => _('Enables using user provided GnuTLS priority string.'),
213 l_acc_gtls_pri => _('GnuTLS priority'),
214 h_acc_gtls_pri => _('Value to use as GnuTLS priority string if custom priority check is enabled. Otherwise this value is ignored.'),
215 l_acc_tls_sni => _('Use TLS SNI extension'),
216 h_acc_tls_sni => _('Enables sending your hostname, if available, so the server can select the appropriate certificate for your domain. Useful for servers which host multiple domains on the same IP address.'),
218 l_plu_gpg_alimit => _('Autocompletion limit'),
219 h_plu_gpg_alimit => _('Limits the number of addresses obtained from keyring through autocompletion. Use 0 to get all matches.'),
220 l_plu_lav_burl => _('Base URL'),
221 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.'),
222 l_plu_prl_flvb => _('Log level'),
223 h_plu_prl_flvb => _('Verbosity level of log, accumulative.'),
224 l_plu_prl_none => _('None'),
225 l_plu_prl_manual => _('Manual'),
226 l_plu_prl_action => _('Actions'),
227 l_plu_prl_match => _('Matches'),
230 # data and metadata of resource files
235 # all preferences read by load_preferences
239 # values of all preferences handled by clawsker
243 # default config dir and file name
244 my $ALTCONFIGDIR = FALSE;
245 my $CONFIGDIR = catdir ($ENV{HOME}, '.claws-mail');
246 my $CONFIGRC = 'clawsrc';
247 my $ACCOUNTRC = 'accountrc';
248 # supported and available plugins lists
249 my @PLUGINS = qw(AttRemover GPG ManageSieve Libravatar PerlPlugin);
251 # loaded hotkeys from load_menurc
253 # current tree selection
261 # index constants for preference arrays
262 NAME => 0, # the name on the rc file
263 LABEL => 1, # the label on the GUI
264 DESC => 2, # the description for the hint/help
265 TYPE => 3, # data type: bool, int, float, string, color
266 CMVER => 4, # lowest[,highest] Claws Mail version(s) the feature exists
267 CMDEF => 5, # default value for the preference in Claws Mail
268 PLUGIN => 6, # plugin section (only in plugin preferences)
269 # constants for GUI spacing
272 # for data references indexing
275 # hotkey list store columns
285 sub version_greater_or_equal {
286 my ($version, $refvers) = @_;
287 return TRUE if (length($version) == 0 and length($refvers) >= 0);
288 return FALSE if (length($version) >= 0 and length($refvers) == 0);
289 return TRUE if (version->parse($version) >= version->parse($refvers));
293 sub get_claws_version {
294 my $cm_path = which ('claws-mail') or return ""; # not found
295 open my $ph, "-|", $cm_path, "-v" or return ""; # no pipe
296 chomp (my $v = <$ph>);
298 # Claws Mail version 3.17.2git17
299 $v =~ m/\bversion\s+(\d[\w.]+)/ or die "Invalid version string: '$v'";
301 my @ver = split m/(?:\.|git)/, $cmv;
302 @ver < 4 and push @ver, 0;
303 return join ".", @ver;
306 # data handlers and auxiliar functions
308 sub handle_bool_value {
309 my ($widget, $event, $dataref) = @_;
310 $$dataref->[VALUE] = ($widget->get_active ())? '1': '0';
311 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
312 if defined $$dataref->[IVALUE];
315 sub handle_int_value {
316 my ($widget, $event, $dataref) = @_;
317 $_ = $widget->get_text ();
321 $$dataref->[VALUE] = $_;
322 $widget->set_text ($_);
325 $widget->set_text ($$dataref->[VALUE]);
327 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
328 if defined $$dataref->[IVALUE];
331 sub handle_nchar_value {
332 my ($widget, $event, $dataref, $minlen, $maxlen) = @_;
333 $_ = substr ($widget->get_text (), 0, $maxlen);
334 $widget->set_text ($_);
335 $$dataref->[VALUE] = $_;
336 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
337 if defined $$dataref->[IVALUE];
340 sub gdk_rgba_from_str {
342 my ($rr, $gg, $bb) = (0, 0 ,0);
344 if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) {
349 my $color = Gtk3::Gdk::RGBA->new ($rr, $gg, $bb, 1.0);
353 sub str_from_gdk_rgba {
355 my $rr = $color->red * 256;
356 my $gg = $color->green * 256;
357 my $bb = $color->blue * 256;
358 my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb);
362 sub handle_color_value {
363 my ($widget, $event, $dataref) = @_;
364 my $newcol = $widget->get_rgba;
365 $$dataref->[VALUE] = str_from_gdk_rgba ($newcol);
366 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
367 if defined $$dataref->[IVALUE];
370 sub handle_selection_value {
371 my ($widget, $event, $dataref) = @_;
372 $$dataref->[VALUE] = $widget->get_active;
373 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
374 if defined $$dataref->[IVALUE];
377 sub get_rc_filename {
378 return catfile ($CONFIGDIR, $CONFIGRC);
381 sub get_ac_rc_filename {
382 return catfile ($CONFIGDIR, $ACCOUNTRC);
385 sub get_menurc_filename {
386 return catfile ($CONFIGDIR, "menurc");
389 sub set_rc_filename {
391 my @parts = splitpath ($fullname);
392 $CONFIGRC = $parts[$#parts];
393 $parts[$#parts] = '';
394 $CONFIGDIR = catpath (@parts);
398 my ($mesg, $fatal) = @_;
399 if (defined($fatal) && $fatal eq 'die') {
400 die "$NAME: $mesg\n";
403 print "$NAME: $mesg\n";
408 my ($parent, $title, $markup, $type, $buttons) = @_;
409 my $flags = [qw/modal destroy-with-parent/];
410 my $dialog = Gtk3::Dialog->new_with_buttons (
411 $title, $parent, $flags, @$buttons
413 my $label = Gtk3::Label->new;
414 $label->set_markup ($markup);
416 if ($type eq 'error') {
417 $icon = Gtk3::Image->new_from_icon_name('dialog-error', 'GTK_ICON_SIZE_DIALOG');
418 } elsif ($type eq 'warning') {
419 $icon = Gtk3::Image->new_from_icon_name('dialog-warning', 'GTK_ICON_SIZE_DIALOG');
420 } elsif ($type eq 'question') {
421 $icon = Gtk3::Image->new_from_icon_name('dialog-question', 'GTK_ICON_SIZE_DIALOG');
423 my $hbox = Gtk3::Box->new ('horizontal', 5);
424 $hbox->pack_start ($icon, FALSE, FALSE, 5) if defined $icon;
425 $hbox->pack_start ($label, FALSE, FALSE, 5);
426 my $dialogbox = $dialog->get_content_area;
427 $dialogbox->add ($hbox);
428 $dialogbox->show_all;
434 my $markup = "<span weight=\"bold\" size=\"large\">" . $emsg . "</span>";
435 my $errordlg = message_dialog (
436 $main_window, _('Clawsker error'), $markup, 'error', [ 'gtk-cancel', 0 ]
442 sub claws_is_running {
443 my $emsg = _('Error: seems Claws Mail is currently running, close it first.');
445 error_dialog ($emsg);
449 sub check_claws_not_running {
450 return TRUE if $READONLY;
451 my $tmpdir = File::Spec->tmpdir ();
452 my $lockdir = catfile ($tmpdir, "claws-mail-$<");
456 my $socket = catfile ($lockdir, md5_hex ($_));
457 -S $socket and return claws_is_running ();
464 (defined($rcfile) && -f $rcfile) or do {
465 my $emsg = _('Error: resource file for Claws Mail was not found.');
467 error_dialog ($emsg);
473 sub set_widget_hint {
474 my ($wdgt, $hint) = @_;
475 $wdgt->set_tooltip_text ($hint);
476 $wdgt->set_has_tooltip (TRUE);
479 sub set_widget_sens {
480 my ($wdgt, $versions) = @_;
481 my @ver = split(/,/, $versions);
483 $wdgt->set_sensitive (
484 version_greater_or_equal ($CLAWSV, $ver[0])
485 and version_greater_or_equal ($ver[1], $CLAWSV)
488 $wdgt->set_sensitive (version_greater_or_equal ($CLAWSV, $ver[0]));
492 # graphic element creation
494 sub new_hbox_spaced_pack {
495 my $hbox = Gtk3::HBox->new (FALSE);
497 $hbox->pack_start ($_, FALSE, FALSE, HBOX_PAD);
502 sub new_check_button_for($$$) {
503 my ($hash, $key, $vhash) = @_;
504 my $name = $$hash{$key}[NAME];
505 my $label = $$hash{$key}[LABEL];
507 my $cb = Gtk3::CheckButton->new ($label);
508 my $value = $$vhash{$name}[VALUE];
509 $value //= $$hash{$key}[CMDEF];
510 $cb->set_active ($value eq '1');
511 $cb->signal_connect (clicked => sub {
513 handle_bool_value ($w, $e, \$$vhash{$name});
515 set_widget_hint ($cb, $$hash{$key}[DESC]);
516 set_widget_sens ($cb, $$hash{$key}[CMVER]);
518 return new_hbox_spaced_pack ($cb);
521 sub new_text_box_for_int($$$) {
522 my ($hash, $key, $vhash) = @_;
523 my $name = $$hash{$key}[NAME];
524 my $label = $$hash{$key}[LABEL];
525 my @type = split (/,/, $$hash{$key}[TYPE]);
526 push (@type, 0), push (@type, 10000) unless ($#type > 0);
529 if (ref $label eq 'ARRAY') {
530 $gunits = Gtk3::Label->new ($label->[1]);
531 $label = $label->[0];
533 my $glabel = Gtk3::Label->new ($label);
534 my $pagei = int (($type[2] - $type[1]) / 10);
535 my $gentry = Gtk3::SpinButton->new_with_range ($type[1], $type[2], $pagei);
536 my $value = $$vhash{$name}[VALUE];
537 $value //= $$hash{$key}[CMDEF];
538 $gentry->set_numeric (TRUE);
539 $gentry->set_value ($value);
540 $gentry->signal_connect('value-changed' => sub {
542 handle_int_value ($w, $e, \$$vhash{$name});
544 set_widget_hint ($gentry, $$hash{$key}[DESC]);
545 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
546 $glabel->set_sensitive ($gentry->get_sensitive);
547 $gunits->set_sensitive ($gentry->get_sensitive) if ($gunits);
549 return new_hbox_spaced_pack ($glabel, $gentry, $gunits) if ($gunits);
550 return new_hbox_spaced_pack ($glabel, $gentry);
553 sub new_text_box_for_nchar($$$) {
554 my ($hash, $key, $vhash) = @_;
555 my $name = $$hash{$key}[NAME];
556 my $label = $$hash{$key}[LABEL];
557 my @type = split (/,/, $$hash{$key}[TYPE]); # char,minlen,maxlen,width
558 my $glabel = Gtk3::Label->new ($label);
559 my $gentry = Gtk3::Entry->new;
560 $gentry->set_max_length($type[2]) if defined ($type[2]);
561 my $width = $type[3];
563 $gentry->set_width_chars(int ($width) + 2) if defined ($width);
564 my $value = $$vhash{$name}[VALUE];
565 $value //= $$hash{$key}[CMDEF];
566 $gentry->set_text ($value);
567 $gentry->signal_connect('key-release-event' => sub {
569 handle_nchar_value ($w, $e, \$$vhash{$name}, $type[1], $type[2]);
571 set_widget_hint ($gentry, $$hash{$key}[DESC]);
572 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
573 $glabel->set_sensitive ($gentry->get_sensitive);
575 return new_hbox_spaced_pack ($glabel, $gentry);
578 sub new_color_button_for($$$) {
579 my ($hash, $key, $vhash) = @_;
580 my $name = $$hash{$key}[NAME];
581 my $label = $$hash{$key}[LABEL];
583 my $value = $$vhash{$name}[VALUE];
584 $value //= $$hash{$key}[CMDEF];
585 my $col = gdk_rgba_from_str ($value);
586 my $glabel = Gtk3::Label->new ($label);
587 my $button = Gtk3::ColorButton->new_with_rgba ($col);
588 $button->set_title ($label);
589 $button->set_relief ('none');
590 $button->signal_connect ('color-set' => sub {
592 handle_color_value ($w, $e, \$$vhash{$name});
594 set_widget_hint ($button, $$hash{$key}[DESC]);
595 set_widget_sens ($button, $$hash{$key}[CMVER]);
596 $glabel->set_sensitive ($button->get_sensitive);
598 return new_hbox_spaced_pack ($button, $glabel);
601 sub new_selection_box_for($$$) {
602 my ($hash, $key, $vhash) = @_;
603 my $name = $$hash{$key}[NAME];
604 my $label = $$hash{$key}[LABEL];
606 my $glabel = Gtk3::Label->new ($label);
607 my $combo = Gtk3::ComboBoxText->new;
608 my @options = split (';', $$hash{$key}[TYPE]);
609 foreach my $opt (@options) {
610 my ($index, $textkey) = split ('=', $opt);
611 $combo->insert (-1, $index, $xl::s{$textkey});
613 $combo->signal_connect ('changed' => sub {
615 handle_selection_value ($w, $e, \$$vhash{$name});
617 my $value = $$vhash{$name}[VALUE];
618 $value //= $$hash{$key}[CMDEF];
619 $combo->set_active ($value);
620 set_widget_hint ($combo, $$hash{$key}[DESC]);
621 set_widget_sens ($combo, $$hash{$key}[CMVER]);
622 $glabel->set_sensitive ($combo->get_sensitive);
624 return new_hbox_spaced_pack ($glabel, $combo);
627 # more graphic helpers
630 my ($border_w, $row_s, $col_s) = @_;
631 $border_w //= GRID_SPC;
634 my $grid = Gtk3::Grid->new;
635 $grid->set_border_width ($border_w);
636 $grid->set_row_spacing ($row_s);
637 $grid->set_column_spacing ($col_s);
644 my $label = Gtk3::Label->new ($text);
645 $label->set_alignment (0, 0.5);
652 my $label = Gtk3::Label->new ('<b>' . $text . '</b>');
653 $label->set_use_markup (TRUE);
654 $label->set_alignment (0, 0.5);
659 my ($width, $height, $widget) = @_;
660 my $grid = new_grid ();
661 $grid->set_column_homogeneous (TRUE);
662 for (my $i = 0; $i < $width; ++$i) {
663 for (my $j = 0; $j < $height; ++$j) {
664 my $wid = $widget->[$j]->[$i];
665 next unless defined $wid;
666 my $ww = (($i + 1 < $width) and (defined $widget->[$j]->[$i + 1]))
670 $grid->attach ($wid, $i, $j, $ww, 1);
671 } else { # not a widget
672 if ('--' eq $wid) { # a separator
673 $grid->attach (Gtk3::Separator->new ('horizontal'),
675 } else { # or a title
676 $grid->attach (new_title ($wid),
685 # preference maps and corresponding page creation subs
687 %pr::oth = ( # other preferences
689 'addressbook_use_editaddress_dialog',
690 $xl::s{l_oth_use_dlg},
691 $xl::s{h_oth_use_dlg},
697 'cache_max_mem_usage',
698 [ $xl::s{l_oth_max_use}, $xl::s{l_oth_max_use_units} ],
699 $xl::s{h_oth_max_use},
700 'int,0,524288', # 0 Kb - 512 Mb
705 'cache_min_keep_time',
706 [ $xl::s{l_oth_min_time}, $xl::s{l_oth_min_time_units} ],
707 $xl::s{h_oth_min_time},
708 'int,0,120', # 0 minutes - 2 hours
713 'use_networkmanager',
714 $xl::s{l_oth_use_netm},
715 $xl::s{h_oth_use_netm},
721 'master_passphrase_pbkdf2_rounds',
722 $xl::s{l_oth_mp_rounds},
723 $xl::s{h_oth_mp_rounds},
730 sub new_other_page() {
731 return new_grid_pack (1, 12, [
732 [ _('Addressbook') ],
733 [ new_check_button_for(\%pr::oth, 'use_dlg', \%HPVALUE) ],
736 [ new_text_box_for_int(\%pr::oth, 'max_use', \%HPVALUE) ],
737 [ new_text_box_for_int(\%pr::oth, 'min_time', \%HPVALUE) ],
739 [ _('NetworkManager') ],
740 [ new_check_button_for(\%pr::oth, 'use_netm', \%HPVALUE) ],
742 [ _('Master passphrase') ],
743 [ new_text_box_for_int(\%pr::oth, 'mp_rounds', \%HPVALUE) ]
747 %pr::gui = ( # gui bells and whistles
750 $xl::s{l_gui_b_unread},
751 $xl::s{h_gui_b_unread},
758 $xl::s{l_gui_no_markup},
759 $xl::s{h_gui_no_markup},
765 'enable_dotted_lines',
766 $xl::s{l_gui_dot_lines},
767 $xl::s{h_gui_dot_lines},
769 '2.4.0.115,3.7.10.44',
774 $xl::s{l_gui_h_scroll},
775 $xl::s{h_gui_h_scroll},
782 $xl::s{l_gui_swp_from},
783 $xl::s{h_gui_swp_from},
789 'folderview_vscrollbar_policy',
790 $xl::s{l_gui_v_scroll},
791 $xl::s{h_gui_v_scroll},
792 '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
798 $xl::s{l_gui_from_show},
799 $xl::s{h_gui_from_show},
800 '0=l_gui_from_show_name;1=l_gui_from_show_addr;2=l_gui_from_show_both',
805 'stripes_color_offset',
806 $xl::s{l_gui_strip_off},
807 $xl::s{h_gui_strip_off},
808 'int,0,40000', # no idea what this number means
813 'textview_cursor_visible',
814 $xl::s{l_gui_cursor_v},
815 $xl::s{h_gui_cursor_v},
821 'toolbar_detachable',
822 $xl::s{l_gui_toolbar_d},
823 $xl::s{h_gui_toolbar_d},
829 'use_stripes_everywhere',
830 $xl::s{l_gui_strip_all},
831 $xl::s{h_gui_strip_all},
837 'use_stripes_in_summaries',
838 $xl::s{l_gui_strip_sum},
839 $xl::s{h_gui_strip_sum},
846 $xl::s{l_gui_two_line_v},
847 $xl::s{h_gui_two_line_v},
853 'show_compose_margin',
854 $xl::s{l_gui_margin_co},
855 $xl::s{h_gui_margin_co},
861 'msgview_date_format',
862 $xl::s{l_gui_mview_date},
863 $xl::s{h_gui_mview_date},
869 'zero_replacement_char',
870 $xl::s{l_gui_zero_char},
871 $xl::s{h_gui_zero_char},
878 $xl::s{l_gui_type_any},
879 $xl::s{h_gui_type_any},
885 'warn_sending_many_recipients_num',
886 [ $xl::s{l_gui_warn_send_multi}, $xl::s{l_gui_warn_send_multi_units} ],
887 $xl::s{h_gui_warn_send_multi},
894 $xl::s{l_gui_next_del},
895 $xl::s{h_gui_next_del},
903 return new_grid_pack (2, 24, [
904 [ _('Coloured stripes') ],
905 [ new_check_button_for (\%pr::gui, 'strip_all', \%HPVALUE),
906 new_check_button_for (\%pr::gui, 'strip_sum', \%HPVALUE) ],
907 [ new_text_box_for_int (\%pr::gui, 'strip_off', \%HPVALUE) ],
909 [ _('Message List') ],
910 [ new_check_button_for (\%pr::gui, 'b_unread', \%HPVALUE),
911 new_check_button_for (\%pr::gui, 'swp_from', \%HPVALUE) ],
912 [ new_check_button_for (\%pr::gui, 'two_linev', \%HPVALUE),
913 new_check_button_for (\%pr::gui, 'next_del', \%HPVALUE) ],
914 [ new_selection_box_for (\%pr::gui, 'from_show', \%HPVALUE) ],
916 [ _('Message View') ],
917 [ new_check_button_for (\%pr::gui, 'cursor_v', \%HPVALUE),
918 new_check_button_for (\%pr::gui, 'mview_date', \%HPVALUE) ],
920 [ _('Compose window') ],
921 [ new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
922 new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE) ],
923 [ new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE) ],
924 [ new_text_box_for_int (\%pr::gui, 'warn_send_multi', \%HPVALUE) ],
926 [ _('Scroll bars') ],
927 [ new_check_button_for (\%pr::gui, 'h_scroll', \%HPVALUE),
928 new_selection_box_for (\%pr::gui, 'v_scroll', \%HPVALUE) ],
931 [ new_check_button_for (\%pr::gui, 'dot_lines', \%HPVALUE),
932 new_check_button_for (\%pr::gui, 'toolbar_d', \%HPVALUE) ],
933 [ new_text_box_for_nchar (\%pr::gui, 'zero_char', \%HPVALUE) ]
937 %pr::beh = ( # tweak some behaviour
940 [ $xl::s{l_beh_hover_t}, $xl::s{l_beh_hover_t_units} ],
941 $xl::s{h_beh_hover_t},
942 'int,100,3000', # 0.1 seconds - 3 seconds
948 $xl::s{l_beh_dangerous},
949 $xl::s{h_beh_dangerous},
955 'respect_flowed_format',
956 $xl::s{l_beh_flowed},
957 $xl::s{h_beh_flowed},
963 'save_parts_readwrite',
964 $xl::s{l_beh_parts_rw},
965 $xl::s{h_beh_parts_rw},
971 'skip_ssl_cert_check',
972 $xl::s{l_beh_skip_ssl},
973 $xl::s{h_beh_skip_ssl},
979 'statusbar_update_step',
980 [ $xl::s{l_beh_up_step}, $xl::s{l_beh_up_step_units} ],
981 $xl::s{h_beh_up_step},
982 'int,1,200', # 1 item - 200 items
987 'thread_by_subject_max_age',
988 [ $xl::s{l_beh_thread_a}, $xl::s{l_beh_thread_a_units} ],
989 $xl::s{h_beh_thread_a},
990 'int,1,30', # 1 day - 30 days
996 $xl::s{l_beh_unsafe_ssl},
997 $xl::s{h_beh_unsafe_ssl},
1003 'utf8_instead_of_locale_for_broken_mail',
1004 $xl::s{l_beh_use_utf8},
1005 $xl::s{h_beh_use_utf8},
1012 $xl::s{l_beh_warn_dnd},
1013 $xl::s{h_beh_warn_dnd},
1019 'outgoing_fallback_to_ascii',
1020 $xl::s{l_beh_out_ascii},
1021 $xl::s{h_beh_out_ascii},
1027 'primary_paste_unselects',
1028 $xl::s{l_beh_pp_unsel},
1029 $xl::s{h_beh_pp_unsel},
1035 'show_inline_attachments',
1036 $xl::s{l_beh_inline_at},
1037 $xl::s{h_beh_inline_at},
1043 'address_search_wildcard',
1044 $xl::s{l_beh_addr_swc},
1045 $xl::s{h_beh_addr_swc},
1051 'folder_search_wildcard',
1052 $xl::s{l_beh_fold_swc},
1053 $xl::s{h_beh_fold_swc},
1059 'rewrite_first_from',
1060 $xl::s{l_beh_rewrite_ff},
1061 $xl::s{h_beh_rewrite_ff},
1068 sub new_behaviour_page() {
1069 return new_grid_pack (2, 20, [
1070 [ _('Drag \'n\' drop') ],
1071 [ new_text_box_for_int (\%pr::beh, 'hover_t', \%HPVALUE) ],
1072 [ new_check_button_for (\%pr::beh, 'warn_dnd', \%HPVALUE) ],
1074 [ _('Secure Sockets Layer') ],
1075 [ new_check_button_for (\%pr::beh, 'skip_ssl', \%HPVALUE),
1076 new_check_button_for (\%pr::beh, 'unsafe_ssl', \%HPVALUE) ],
1079 [ new_check_button_for (\%pr::beh, 'flowed', \%HPVALUE),
1080 new_check_button_for (\%pr::beh, 'out_ascii', \%HPVALUE) ],
1081 [ new_check_button_for (\%pr::beh, 'parts_rw', \%HPVALUE),
1082 new_check_button_for (\%pr::beh, 'pp_unsel', \%HPVALUE) ],
1083 [ new_check_button_for (\%pr::beh, 'use_utf8', \%HPVALUE),
1084 new_check_button_for (\%pr::beh, 'inline_at', \%HPVALUE) ],
1085 [ new_check_button_for (\%pr::beh, 'dangerous', \%HPVALUE),
1086 new_check_button_for (\%pr::beh, 'rewrite_ff', \%HPVALUE) ],
1088 [ _('Completion') ],
1089 [ new_check_button_for (\%pr::beh, 'addr_swc', \%HPVALUE) ],
1090 [ new_check_button_for (\%pr::beh, 'fold_swc', \%HPVALUE) ],
1093 [ new_text_box_for_int (\%pr::beh, 'up_step', \%HPVALUE) ],
1094 [ new_text_box_for_int (\%pr::beh, 'thread_a', \%HPVALUE) ]
1098 %pr::col = ( # a variety of colours
1101 $xl::s{l_col_emphasis},
1102 $xl::s{h_col_emphasis},
1109 $xl::s{l_col_log_err},
1110 $xl::s{h_col_log_err},
1117 $xl::s{l_col_log_in},
1118 $xl::s{h_col_log_in},
1125 $xl::s{l_col_log_msg},
1126 $xl::s{h_col_log_msg},
1133 $xl::s{l_col_log_out},
1134 $xl::s{h_col_log_out},
1141 $xl::s{l_col_log_warn},
1142 $xl::s{h_col_log_warn},
1149 $xl::s{l_col_diff_add},
1150 $xl::s{h_col_diff_add},
1156 'diff_deleted_color',
1157 $xl::s{l_col_diff_del},
1158 $xl::s{h_col_diff_del},
1165 $xl::s{l_col_diff_hunk},
1166 $xl::s{h_col_diff_hunk},
1173 $xl::s{l_col_tags_bg},
1174 $xl::s{h_col_tags_bg},
1181 $xl::s{l_col_tags_text},
1182 $xl::s{h_col_tags_text},
1187 default_header_bg => [
1188 'default_header_bgcolor',
1189 $xl::s{l_col_default_header_bg},
1190 $xl::s{h_col_default_header_bg},
1195 default_header_text => [
1196 'default_header_color',
1197 $xl::s{l_col_default_header_text},
1198 $xl::s{h_col_default_header_text},
1204 'qs_active_bgcolor',
1205 $xl::s{l_col_qs_active_bg},
1206 $xl::s{h_col_qs_active_bg},
1213 $xl::s{l_col_qs_active_text},
1214 $xl::s{h_col_qs_active_text},
1221 $xl::s{l_col_qs_error_bg},
1222 $xl::s{h_col_qs_error_bg},
1229 $xl::s{l_col_qs_error_text},
1230 $xl::s{h_col_qs_error_text},
1237 sub new_colours_page() {
1238 return new_grid_pack (3, 18, [
1239 [ _('Message View') ],
1240 [ new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE) ],
1241 [ new_color_button_for (\%pr::col, 'tags_text', \%HPVALUE) ,
1242 new_color_button_for (\%pr::col, 'tags_bg', \%HPVALUE) ],
1244 [ _('Log window') ],
1245 [ new_color_button_for (\%pr::col, 'log_err', \%HPVALUE) ,
1246 new_color_button_for (\%pr::col, 'log_in', \%HPVALUE) ],
1247 [ new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE) ,
1248 new_color_button_for (\%pr::col, 'log_out', \%HPVALUE) ],
1249 [ new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE) ],
1251 [ _('Viewing patches') ],
1252 [ new_color_button_for (\%pr::col, 'diff_add', \%HPVALUE) ,
1253 new_color_button_for (\%pr::col, 'diff_del', \%HPVALUE) ,
1254 new_color_button_for (\%pr::col, 'diff_hunk', \%HPVALUE) ],
1256 [ _('Compose window') ],
1257 [ new_color_button_for (\%pr::col, 'default_header_text', \%HPVALUE) ,
1258 new_color_button_for (\%pr::col, 'default_header_bg', \%HPVALUE) ],
1260 [ _('Quick search') ],
1261 [ new_color_button_for (\%pr::col, 'qs_active_text', \%HPVALUE) ,
1262 new_color_button_for (\%pr::col, 'qs_active_bg', \%HPVALUE) ],
1263 [ new_color_button_for (\%pr::col, 'qs_error_text', \%HPVALUE) ,
1264 new_color_button_for (\%pr::col, 'qs_error_bg', \%HPVALUE) ]
1268 %pr::win = ( # tweak window positions and/or sizes
1273 'int,0,3000', # 0 pixels - 3000 pixels
1281 'int,0,3000', # 0 pixels - 3000 pixels
1289 'int,0,3000', # 0 pixels - 3000 pixels
1297 'int,0,3000', # 0 pixels - 3000 pixels
1302 'mainwin_maximised',
1303 $xl::s{l_win_main_mx},
1304 $xl::s{h_win_main_mx},
1310 'mainwin_fullscreen',
1311 $xl::s{l_win_main_fs},
1312 $xl::s{h_win_main_fs},
1318 'main_messagewin_x',
1321 'int,0,3000', # 0 pixels - 3000 pixels
1326 'main_messagewin_y',
1329 'int,0,3000', # 0 pixels - 3000 pixels
1337 'int,0,3000', # 0 pixels - 3000 pixels
1342 'messagewin_height',
1345 'int,0,3000', # 0 pixels - 3000 pixels
1353 'int,0,3000', # 0 pixels - 3000 pixels
1361 'int,0,3000', # 0 pixels - 3000 pixels
1369 'int,0,3000', # 0 pixels - 3000 pixels
1374 'receivewin_height',
1377 'int,0,3000', # 0 pixels - 3000 pixels
1385 'int,0,3000', # 0 pixels - 3000 pixels
1393 'int,0,3000', # 0 pixels - 3000 pixels
1398 'folderitemwin_width',
1401 'int,0,3000', # 0 pixels - 3000 pixels
1406 'folderitemwin_height',
1409 'int,0,3000', # 0 pixels - 3000 pixels
1414 'folderselwin_width',
1417 'int,0,3000', # 0 pixels - 3000 pixels
1422 'folderselwin_height',
1425 'int,0,3000', # 0 pixels - 3000 pixels
1433 'int,0,3000', # 0 pixels - 3000 pixels
1441 'int,0,3000', # 0 pixels - 3000 pixels
1446 'addressbookwin_width',
1449 'int,0,3000', # 0 pixels - 3000 pixels
1454 'addressbookwin_height',
1457 'int,0,3000', # 0 pixels - 3000 pixels
1462 'addressbookeditpersonwin_width',
1465 'int,0,3000', # 0 pixels - 3000 pixels
1470 'addressbookeditpersonwin_height',
1473 'int,0,3000', # 0 pixels - 3000 pixels
1478 'addressbookeditgroupwin_width',
1481 'int,0,3000', # 0 pixels - 3000 pixels
1486 'addressbookeditgroupwin_height',
1489 'int,0,3000', # 0 pixels - 3000 pixels
1494 'addressaddwin_width',
1497 'int,0,3000', # 0 pixels - 3000 pixels
1502 'addressaddwin_height',
1505 'int,0,3000', # 0 pixels - 3000 pixels
1510 'addressbook_folderselwin_width',
1513 'int,0,3000', # 0 pixels - 3000 pixels
1518 'addressbook_folderselwin_height',
1521 'int,0,3000', # 0 pixels - 3000 pixels
1526 'editaccountwin_width',
1529 'int,0,3000', # 0 pixels - 3000 pixels
1534 'editaccountwin_height',
1537 'int,0,3000', # 0 pixels - 3000 pixels
1542 'accountswin_width',
1545 'int,0,3000', # 0 pixels - 3000 pixels
1550 'accountswin_height',
1553 'int,0,3000', # 0 pixels - 3000 pixels
1558 'filteringwin_width',
1561 'int,0,3000', # 0 pixels - 3000 pixels
1566 'filteringwin_height',
1569 'int,0,3000', # 0 pixels - 3000 pixels
1574 'filteringactionwin_width',
1577 'int,0,3000', # 0 pixels - 3000 pixels
1582 'filteringactionwin_height',
1585 'int,0,3000', # 0 pixels - 3000 pixels
1590 'filtering_debugwin_width',
1593 'int,0,3000', # 0 pixels - 3000 pixels
1598 'filtering_debugwin_height',
1601 'int,0,3000', # 0 pixels - 3000 pixels
1609 'int,0,3000', # 0 pixels - 3000 pixels
1614 'matcherwin_height',
1617 'int,0,3000', # 0 pixels - 3000 pixels
1625 'int,0,3000', # 0 pixels - 3000 pixels
1633 'int,0,3000', # 0 pixels - 3000 pixels
1638 'templateswin_width',
1641 'int,0,3000', # 0 pixels - 3000 pixels
1646 'templateswin_height',
1649 'int,0,3000', # 0 pixels - 3000 pixels
1657 'int,0,3000', # 0 pixels - 3000 pixels
1662 'actionswin_height',
1665 'int,0,3000', # 0 pixels - 3000 pixels
1670 'actionsiodialog_width',
1673 'int,0,3000', # 0 pixels - 3000 pixels
1678 'actionsiodialog_height',
1681 'int,0,3000', # 0 pixels - 3000 pixels
1689 'int,0,3000', # 0 pixels - 3000 pixels
1697 'int,0,3000', # 0 pixels - 3000 pixels
1705 'int,0,3000', # 0 pixels - 3000 pixels
1713 'int,0,3000', # 0 pixels - 3000 pixels
1721 'int,0,3000', # 0 pixels - 3000 pixels
1726 'pluginswin_height',
1729 'int,0,3000', # 0 pixels - 3000 pixels
1737 'int,0,3000', # 0 pixels - 3000 pixels
1745 'int,0,3000', # 0 pixels - 3000 pixels
1750 'print_previewwin_width',
1753 'int,0,3000', # 0 pixels - 3000 pixels
1758 'print_previewwin_height',
1761 'int,0,3000', # 0 pixels - 3000 pixels
1767 sub new_winpos_subpage_main() {
1768 return new_grid_pack (3, 7, [
1769 [ _('Main window'), undef ],
1770 [ new_text_box_for_int (\%pr::win, 'main_x', \%HPVALUE), undef ],
1771 [ new_text_box_for_int (\%pr::win, 'main_y', \%HPVALUE), undef ],
1772 [ new_text_box_for_int (\%pr::win, 'main_w', \%HPVALUE),
1773 new_text_box_for_int (\%pr::win, 'main_h', \%HPVALUE) ],
1774 [ new_check_button_for (\%pr::win, 'main_fs', \%HPVALUE), undef ],
1775 [ new_check_button_for (\%pr::win, 'main_mx', \%HPVALUE), undef ]
1779 sub new_winpos_subpage_msgs() {
1780 return new_grid_pack (3, 4, [
1781 [ _('Message window') ],
1782 [ new_text_box_for_int (\%pr::win, 'msgs_x', \%HPVALUE) ],
1783 [ new_text_box_for_int (\%pr::win, 'msgs_y', \%HPVALUE) ],
1784 [ new_text_box_for_int (\%pr::win, 'msgs_w', \%HPVALUE),
1785 new_text_box_for_int (\%pr::win, 'msgs_h', \%HPVALUE) ]
1789 sub new_winpos_subpage_sendrecv() {
1790 return new_grid_pack (3, 5, [
1791 [ _('Send window') ],
1792 [ new_text_box_for_int (\%pr::win, 'send_w', \%HPVALUE),
1793 new_text_box_for_int (\%pr::win, 'send_h', \%HPVALUE) ],
1795 [ _('Receive window') ],
1796 [ new_text_box_for_int (\%pr::win, 'recv_w', \%HPVALUE),
1797 new_text_box_for_int (\%pr::win, 'recv_h', \%HPVALUE) ]
1801 sub new_winpos_subpage_fold() {
1802 return new_grid_pack (3, 7, [
1803 [ _('Folder window') ],
1804 [ new_text_box_for_int (\%pr::win, 'fold_x', \%HPVALUE) ],
1805 [ new_text_box_for_int (\%pr::win, 'fold_y', \%HPVALUE) ],
1806 [ new_text_box_for_int (\%pr::win, 'fold_w', \%HPVALUE),
1807 new_text_box_for_int (\%pr::win, 'fold_h', \%HPVALUE) ],
1809 [ _('Folder selection window') ],
1810 [ new_text_box_for_int (\%pr::win, 'fsel_w', \%HPVALUE),
1811 new_text_box_for_int (\%pr::win, 'fsel_h', \%HPVALUE) ]
1815 sub new_winpos_subpage_addrbook() {
1816 return new_grid_pack (3, 14, [
1817 [ _('Addressbook main window') ],
1818 [ new_text_box_for_int (\%pr::win, 'addr_w', \%HPVALUE),
1819 new_text_box_for_int (\%pr::win, 'addr_h', \%HPVALUE) ],
1821 [ _('Edit person window') ],
1822 [ new_text_box_for_int (\%pr::win, 'adep_w', \%HPVALUE),
1823 new_text_box_for_int (\%pr::win, 'adep_h', \%HPVALUE) ],
1825 [ _('Edit group window') ],
1826 [ new_text_box_for_int (\%pr::win, 'adeg_w', \%HPVALUE),
1827 new_text_box_for_int (\%pr::win, 'adeg_h', \%HPVALUE) ],
1829 [ _('Add address window') ],
1830 [ new_text_box_for_int (\%pr::win, 'adda_w', \%HPVALUE),
1831 new_text_box_for_int (\%pr::win, 'adda_h', \%HPVALUE) ],
1833 [ _('Folder select window') ],
1834 [ new_text_box_for_int (\%pr::win, 'addf_w', \%HPVALUE),
1835 new_text_box_for_int (\%pr::win, 'addf_h', \%HPVALUE) ]
1839 sub new_winpos_subpage_accounts() {
1840 return new_grid_pack (3, 5, [
1841 [ _('Accounts window') ],
1842 [ new_text_box_for_int (\%pr::win, 'acco_w', \%HPVALUE),
1843 new_text_box_for_int (\%pr::win, 'acco_h', \%HPVALUE) ],
1845 [ _('Edit account window') ],
1846 [ new_text_box_for_int (\%pr::win, 'acce_w', \%HPVALUE),
1847 new_text_box_for_int (\%pr::win, 'acce_h', \%HPVALUE) ]
1851 sub new_winpos_subpage_filtering() {
1852 return new_grid_pack (3, 11, [
1853 [ _('Filtering window') ],
1854 [ new_text_box_for_int (\%pr::win, 'filt_w', \%HPVALUE),
1855 new_text_box_for_int (\%pr::win, 'filt_h', \%HPVALUE) ],
1857 [ _('Filtering actions window') ],
1858 [ new_text_box_for_int (\%pr::win, 'fila_w', \%HPVALUE),
1859 new_text_box_for_int (\%pr::win, 'fila_h', \%HPVALUE) ],
1861 [ _('Filtering debug window') ],
1862 [ new_text_box_for_int (\%pr::win, 'fild_w', \%HPVALUE),
1863 new_text_box_for_int (\%pr::win, 'fild_h', \%HPVALUE) ],
1865 [ ('Matcher window') ],
1866 [ new_text_box_for_int (\%pr::win, 'matc_w', \%HPVALUE),
1867 new_text_box_for_int (\%pr::win, 'matc_h', \%HPVALUE) ]
1872 sub new_winpos_subpage_useractions() {
1873 return new_grid_pack (3, 5, [
1874 [ _('User Actions prefs window') ],
1875 [ new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
1876 new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE) ],
1878 [ _('User Actions I/O window') ],
1879 [ new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
1880 new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE) ]
1884 sub new_winpos_subpage_prefs() {
1885 return new_grid_pack (3, 11, [
1886 [ _('Preferences window') ],
1887 [ new_text_box_for_int (\%pr::win, 'pref_w', \%HPVALUE),
1888 new_text_box_for_int (\%pr::win, 'pref_h', \%HPVALUE) ],
1890 [ _('Templates window') ],
1891 [ new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
1892 new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE) ],
1894 [ _('Tags window') ],
1895 [ new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
1896 new_text_box_for_int (\%pr::win, 'tags_h', \%HPVALUE) ],
1898 [ _('Plugins window') ],
1899 [ new_text_box_for_int (\%pr::win, 'plug_w', \%HPVALUE),
1900 new_text_box_for_int (\%pr::win, 'plug_h', \%HPVALUE) ]
1904 sub new_winpos_subpage_misc() {
1905 return new_grid_pack (4, 11, [
1906 [ _('Log window') ],
1907 [ new_text_box_for_int (\%pr::win, 'logw_w', \%HPVALUE),
1908 new_text_box_for_int (\%pr::win, 'logw_h', \%HPVALUE) ],
1910 [ _('SSL manager') ],
1911 [ new_text_box_for_int (\%pr::win, 'sslman_w', \%HPVALUE),
1912 new_text_box_for_int (\%pr::win, 'sslman_h', \%HPVALUE) ],
1914 [ _('Print preview window') ],
1915 [ new_text_box_for_int (\%pr::win, 'prin_w', \%HPVALUE),
1916 new_text_box_for_int (\%pr::win, 'prin_h', \%HPVALUE) ],
1918 [ _('View source window') ],
1919 [ new_text_box_for_int (\%pr::win, 'sour_w', \%HPVALUE),
1920 new_text_box_for_int (\%pr::win, 'sour_h', \%HPVALUE) ]
1924 sub new_winpos_page() {
1925 my $winbook = Gtk3::Notebook->new;
1926 $winbook->set_tab_pos ('right');
1927 $winbook->append_page (&new_winpos_subpage_main, new_label (_('Main')));
1928 $winbook->append_page (&new_winpos_subpage_msgs, new_label (_('Message')));
1929 $winbook->append_page (&new_winpos_subpage_sendrecv, new_label (_('Send/Receive')));
1930 $winbook->append_page (&new_winpos_subpage_fold, new_label (_('Folder')));
1931 $winbook->append_page (&new_winpos_subpage_addrbook, new_label (_('Addressbook')));
1932 $winbook->append_page (&new_winpos_subpage_accounts, new_label (_('Accounts')));
1933 $winbook->append_page (&new_winpos_subpage_filtering, new_label (_('Filtering')));
1934 $winbook->append_page (&new_winpos_subpage_useractions, new_label (_('User Actions')));
1935 $winbook->append_page (&new_winpos_subpage_prefs, new_label (_('Preferences')));
1936 $winbook->append_page (&new_winpos_subpage_misc, new_label (_('Other')));
1940 %pr::acc = ( # per account hidden preferences
1942 'gnutls_set_priority',
1943 $xl::s{l_acc_gtls_set},
1944 $xl::s{h_acc_gtls_set},
1951 $xl::s{l_acc_gtls_pri},
1952 $xl::s{h_acc_gtls_pri},
1959 $xl::s{l_acc_tls_sni},
1960 $xl::s{h_acc_tls_sni},
1967 sub new_account_subpage($) {
1969 return new_grid_pack (1, 5, [
1970 [ _('GnuTLS priority') ],
1971 [ new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}) ],
1972 [ new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey}) ],
1973 [ _('Server Name Indication') ],
1974 [ new_check_button_for (\%pr::acc, 'tls_sni', $ACHPVALUE{$akey}) ],
1978 sub new_accounts_page() {
1979 my $accbook = Gtk3::Notebook->new;
1980 $accbook->set_tab_pos ('right');
1982 $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
1985 my $name = $ACPREFS{$_}{'account_name'};
1986 my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
1987 my $page = new_account_subpage ($_);
1988 my $label = new_label ($isdef? '<u>' . $name . '</u>': $name);
1989 $label->set_use_markup (TRUE);
1990 $accbook->append_page ($page, $label);
1992 $accbook->set_scrollable (TRUE);
1996 %pr::plu = ( # plugins hidden preferences
2002 'int,0,3000', # 0 pixels - 3000 pixels
2011 'int,0,3000', # 0 pixels - 3000 pixels
2018 'autocompletion_limit',
2019 $xl::s{l_plu_gpg_alimit},
2020 $xl::s{h_plu_gpg_alimit},
2028 'manager_win_width',
2031 'int,0,3000', # 0 pixels - 3000 pixels
2037 'manager_win_height',
2040 'int,0,3000', # 0 pixels - 3000 pixels
2048 $xl::s{l_plu_lav_burl},
2049 $xl::s{h_plu_lav_burl},
2052 'http://cdn.libravatar.org/avatar',
2057 'filter_log_verbosity',
2058 $xl::s{l_plu_prl_flvb},
2059 $xl::s{h_plu_prl_flvb},
2060 '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2067 sub new_plugins_page() {
2070 new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2071 new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})
2074 new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})
2077 new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2078 new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})
2081 new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'})
2084 new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})
2087 foreach my $pk (@PLUGINS) {
2088 foreach my $wg (@{$widget{$pk}}) {
2089 $wg->set_sensitive (defined $PLHPVALUE{$pk});
2092 return new_grid_pack (3, 14, [
2093 [ _('Attachment remover') ], $widget{'AttRemover'}, [ '--' ],
2094 [ _('GPG') ], $widget{'GPG'}, [ '--' ],
2095 [ _('Sieve manager') ], $widget{'ManageSieve'}, [ '--' ],
2096 [ _('Libravatar') ], $widget{'Libravatar'}, [ '--' ],
2097 [ _('Perl') ], $widget{'PerlPlugin'}
2101 sub new_hotkeys_list_label {
2102 my $renderer = Gtk3::CellRendererText->new;
2103 $renderer->set_property('alignment' => 'left');
2104 $renderer->set_property('editable' => FALSE);
2108 sub new_hotkeys_list_hotkey {
2109 my $renderer = Gtk3::CellRendererAccel->new;
2110 $renderer->set_property ('accel-mode' => 'gtk');
2111 $renderer->set_property ('editable' => TRUE);
2112 $renderer->signal_connect ('accel-edited' => sub {
2113 my ($w, $path, $key, $mods, $keycode) = @_;
2114 my $accel = Gtk3::accelerator_name ($key, $mods);
2115 my ($model, $iter) = $SELHOTKEY->get_selected ();
2116 $model->set($iter, C_HOTKEY, "\"$accel\"");
2117 my $gkey = $model->get_value ($iter, C_GROUP);
2118 my $akey = $model->get_value ($iter, C_ACCEL);
2119 my $data = $HOTKEYS->{$gkey}->{$akey};
2120 $data->{'key'} = "\"$accel\"";
2121 $data->{'enabled'} = TRUE;
2123 $renderer->signal_connect ('accel-cleared' => sub {
2124 my ($w, $path) = @_;
2125 my ($model, $iter) = $SELHOTKEY->get_selected ();
2126 $model->set($iter, C_HOTKEY, "\"\"");
2127 my $gkey = $model->get_value ($iter, C_GROUP);
2128 my $akey = $model->get_value ($iter, C_ACCEL);
2129 my $data = $HOTKEYS->{$gkey}->{$akey};
2130 $data->{'key'} = "\"\"";
2131 $data->{'enabled'} = FALSE;
2136 sub row_background_color {
2137 my ($column, $isodd) = @_;
2138 my $treeview = $column->get_tree_view;
2139 my $stylectx = $treeview->get_style_context;
2141 ? $stylectx->get_background_color ('normal')
2142 : $stylectx->get_background_color ('insensitive');
2145 sub new_hotkeys_list {
2146 my ($gkey, $group) = @_;
2147 my $store = Gtk3::ListStore->new(
2148 qw/Glib::String Glib::String Glib::String Glib::String Glib::Boolean/);
2150 foreach my $akey (sort keys %$group) {
2151 my $iter = $store->append ();
2152 my $hotkey = $group->{$akey}->{'key'};
2154 $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2155 $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2156 C_GROUP, $gkey, C_ACCEL, $akey, C_ODDITY, $even);
2159 my $treeview = Gtk3::TreeView->new_with_model ($store);
2161 $treeview->insert_column_with_data_func (
2162 0, _("Menu path"), new_hotkeys_list_label (),
2164 my ($col, $renderer, $model, $iter, $data) = @_;
2165 $renderer->set_property (
2166 'markup' => '<span size="smaller">'
2167 . $model->get_value ($iter, C_LABEL)
2169 my $bgcol = row_background_color (
2170 $col, $model->get_value ($iter, C_ODDITY));
2171 $renderer->set_property ('cell-background-rgba' => $bgcol);
2175 $treeview->insert_column_with_data_func (
2176 1, _('Hotkey'), new_hotkeys_list_hotkey (),
2178 my ($col, $renderer, $model, $iter, $data) = @_;
2179 my $hkey = $model->get_value ($iter, C_HOTKEY);
2181 my ($acckey, $accmod) = Gtk3::accelerator_parse ($hkey);
2182 $renderer->set_property ('accel-key' => $acckey);
2183 $renderer->set_property ('accel-mods' => $accmod);
2184 my $bgcol = row_background_color (
2185 $col, $model->get_value ($iter, C_ODDITY));
2186 $renderer->set_property ('cell-background-rgba' => $bgcol);
2189 # callback for saving current selection
2190 my $selection = $treeview->get_selection ();
2191 $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2195 sub new_hotkeys_page() {
2196 my $hkbook = Gtk3::Notebook->new;
2197 $hkbook->set_tab_pos ('right');
2198 foreach my $gkey (sort keys %$HOTKEYS) {
2199 my $group = $HOTKEYS->{$gkey};
2200 my $keylist = new_hotkeys_list ($gkey, $group);
2201 # prepare scrolled window
2202 my $swin = Gtk3::ScrolledWindow->new;
2203 $swin->set_border_width (5);
2204 $swin->set_shadow_type ('none');
2205 $swin->set_policy ('automatic', 'automatic');
2207 $swin->add ($keylist);
2208 $hkbook->append_page ($swin, new_label ($gkey));
2213 sub new_info_page() {
2214 my $v = get_toolkit_versions ();
2215 my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2217 return new_grid_pack (4, 11, [
2218 [ _('Library versions') ],
2219 [ new_label ('Perl-GLib'), new_title ($v->{'glib'}) ],
2220 [ new_label (_('GLib runtime')), new_title ($v->{'glib-r'}) ],
2221 [ new_label (_('GLib built')), new_title ($v->{'glib-b'}) ],
2222 [ new_label ('Perl-GTK3'), new_title ($v->{'gtk'}) ],
2223 [ new_label (_('GTK3 runtime')), new_title ($v->{'gtk-r'}) ],
2224 [ new_label (_('GTK3 built')), new_title ($v->{'gtk-b'}) ],
2226 [ _('Claws Mail versions') ],
2227 [ new_label (_('Binary')), new_title ($CLAWSV) ],
2228 [ new_label (_('Configuration')), new_title ($cfgv) ]
2233 sub get_toolkit_versions {
2235 $versions{'glib'} = $Glib::VERSION;
2236 # version info stuff appeared in 1.040
2237 if ($Glib::VERSION >= 1.040) {
2238 $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2239 $versions{'glib-r'} = join('.',
2240 &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2242 $versions{'gtk'} = $Gtk3::VERSION;
2243 if ($Gtk3::VERSION >= 0.034) {
2244 $versions{'gtk-b'} = &Gtk3::GET_VERSION_INFO
2246 $versions{'gtk-b'} = _('Not available')
2248 $versions{'gtk-r'} = join('.',
2249 &Gtk3::get_major_version, &Gtk3::get_minor_version, &Gtk3::get_micro_version);
2253 sub print_version() {
2254 say $xl::s{about_title};
2255 say _('Version:') . " $VERSION";
2256 my $v = get_toolkit_versions ();
2257 if ($v->{'glib-b'}) {
2258 say _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2259 glibv => $v->{'glib'},
2260 glibb => $v->{'glib-b'},
2261 glibr => $v->{'glib-r'});
2263 say _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2265 if ($v->{'gtk-b'}) {
2266 say _("Perl-GTK3 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2267 gtkv => $v->{'gtk'},
2268 gtkb => $v->{'gtk-b'},
2269 gtkr => $v->{'gtk-r'});
2271 say _("Perl-GTK3 version {gtkv}.", gtkv => $v->{'gtk'});
2273 my $clawsver = ($CLAWSV eq "") ?
2274 _("Claws Mail was not found!") :
2275 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2279 # the command line help
2281 my $line = '-' x length ($xl::s{about_title});
2283 say $xl::s{about_title};
2287 _(" clawsker [options]"),
2289 _(" -a|--alternate-config-dir <dir> Uses <dir> as Claws Mail configuration."),
2290 _(" -b|--verbose More messages on standard output."),
2291 _(" -c|--clawsrc <file> Uses <file> as full resource name."),
2292 _(" -h|--help Prints this help screen and exits."),
2293 _(" -r|--read-only Disables writing changes to disk."),
2294 _(" -v|--version Prints version information and exits.")
2296 foreach (@help) { say $_ }
2299 sub parse_command_line {
2301 $CLAWSV = get_claws_version ();
2303 GetOptions('h|help' => sub { print_help (); $cont = FALSE },
2304 'v|version' => sub { print_version (); $cont = FALSE },
2305 'b|verbose' => sub { $VERBOSE = TRUE },
2306 'r|read-only' => sub { $READONLY = TRUE },
2307 'u|use-claws-version=s' => \&opt_use_claws_version,
2308 'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2309 'c|clawsrc=s' => \&opt_clawsrc)
2310 or die _("try -h or --help for syntax.\n");
2313 my $msg = _("Error in options: {msg}\n", msg => $@);
2314 if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2315 eval { Gtk3->init };
2316 error_dialog ($msg) unless $@;
2323 sub opt_use_claws_version {
2324 my ($name, $value) = @_;
2325 die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2326 unless ($value =~ /^[\d\.]+$/);
2330 sub opt_alternate_config_dir {
2331 my ($name, $value) = @_;
2332 die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2334 $CONFIGDIR = $value;
2335 $ALTCONFIGDIR = TRUE;
2339 my ($name, $value) = @_;
2340 die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2342 set_rc_filename ($value);
2345 # update the hidden preferences status from loaded values
2346 sub init_hidden_preferences {
2347 foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2348 foreach my $key (keys %$hash) {
2349 $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2350 $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2353 foreach my $akey (keys %ACPREFS) {
2354 foreach my $key (keys %pr::acc) {
2355 my $pname = $pr::acc{$key}[NAME];
2356 $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2357 $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2360 foreach my $key (keys %pr::plu) {
2361 my $plugin = $pr::plu{$key}[PLUGIN];
2362 my $pname = $pr::plu{$key}[NAME];
2363 if (defined $PLPREFS{$plugin}) {
2364 $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2365 $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2371 # generic load/save resource files
2377 open (RCF, '<:encoding(utf8)', $rc)
2378 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2379 my $section = '_'; # default unnamed section
2384 if (/^\[([^\]]+)\]$/) { # new section
2386 die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2387 sect => $section, file => $rc) if ($data{$section});
2388 $data{$section} = {};
2389 $meta{$section}{'#'} = $line;
2391 elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2392 $data{$section}{$1} = $2;
2393 $meta{$section}{$1} = $line;
2395 elsif (/^(.*)$/) { # lone value
2396 push (@{$data{$section}{'_'}}, $1);
2400 return (\%data, \%meta);
2404 my ($rc, $data, $meta) = @_;
2405 open (RCF, '>:utf8', $rc)
2406 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2407 my @sections = keys %$data;
2408 if (defined $meta) {
2410 $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2413 foreach my $section (@sections) {
2414 say RCF "[$section]";
2415 if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2416 foreach my $val (@{$data->{$section}{'_'}}) {
2420 my @keys = keys %{$data->{$section}};
2421 if (defined $meta) {
2423 $meta->{$section}{$a} <=> $meta->{$section}{$b}
2426 foreach my $key (@keys) {
2427 my $val = $data->{$section}{$key};
2428 say RCF "$key=$val";
2436 sub backup_resource {
2438 my $rcbak = "$rc.backup";
2440 my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2441 log_message ($emsg);
2442 error_dialog ($emsg);
2444 } unless rename ($rc, $rcbak);
2451 open (RCF, '<:encoding(utf8)', $rc)
2452 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2457 if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2458 my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2459 $groups{$1}{$2} = \%data;
2460 # say "group -> $1 | path -> $2 | key -> $3";
2461 } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2462 my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2463 $groups{$1}{$2} = \%data;
2464 # say "group -> $1 | path -> $2 | key -> $3";
2473 my ($rc, $groups) = @_;
2475 foreach my $gkey (keys %$groups) {
2476 my $group = $groups->{$gkey};
2477 foreach my $akey (keys %$group) {
2478 my $data = $group->{$akey};
2479 my $key = $data->{'key'};
2480 my $line = $data->{'line'};
2481 $lines[$line] = ($data->{'enabled'})? '': '; ';
2482 $lines[$line] .= '(gtk_accel_path "<'
2483 . $gkey . '>' . $akey . '" ' . $key . ')';
2486 open (RCF, '>:utf8', $rc)
2487 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2488 say RCF '; claws-mail GtkAccelMap rc-file -*- scheme -*-';
2489 say RCF '; this file is an automated accelerator map dump';
2491 foreach (@lines) { say RCF $_ if $_ }
2495 # load current status from disc
2496 sub load_rc_preferences {
2497 my $rc = get_rc_filename ();
2498 log_message ("Loading preferences from $rc\n");
2499 return FALSE unless check_rc_file ($rc);
2500 ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2501 foreach (keys %{$CONFIGDATA->{'Common'}}) {
2502 $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2504 foreach my $plugin (@PLUGINS) {
2505 if (defined $CONFIGDATA->{$plugin}) {
2506 push (@AVPLUGINS, $plugin);
2507 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2508 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2515 sub load_ac_preferences {
2516 my $rc = get_ac_rc_filename ();
2517 log_message ("Loading account preferences from $rc\n");
2518 return FALSE unless check_rc_file ($rc);
2519 ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2520 foreach my $asect (keys %$ACCOUNTDATA) {
2521 if ($asect =~ /^Account: (\d+)$/) {
2522 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2523 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2530 sub load_hk_preferences {
2531 my $rc = get_menurc_filename ();
2532 return FALSE unless check_rc_file ($rc);
2533 $HOTKEYS = load_menurc ($rc);
2537 sub load_preferences {
2538 return FALSE unless check_claws_not_running ();
2539 return (load_rc_preferences ()
2540 and load_ac_preferences ()
2541 and load_hk_preferences ()
2545 # save current preferences to disc
2546 sub save_rc_preferences {
2547 my $rc = get_rc_filename ();
2548 log_message ("Saving preferences to $rc\n");
2549 return FALSE unless check_rc_file ($rc);
2550 return FALSE unless backup_resource ($rc);
2551 foreach (keys %PREFS) {
2552 if (defined $HPVALUE{$_}) {
2553 $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2556 foreach my $plugin (@AVPLUGINS) {
2557 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2558 if (defined $PLHPVALUE{$plugin}{$_}) {
2559 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2563 save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2567 sub save_ac_preferences {
2568 my $rc = get_ac_rc_filename ();
2569 log_message ("Saving account preferences to $rc\n");
2570 return FALSE unless check_rc_file ($rc);
2571 return FALSE unless backup_resource ($rc);
2572 foreach my $asect (keys %$ACCOUNTDATA) {
2573 if ($asect =~ /^Account: (\d+)$/) {
2574 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2575 if (defined $ACHPVALUE{$1}{$_}) {
2576 $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2581 save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2585 sub save_hk_preferences {
2586 my $rc = get_menurc_filename ();
2587 log_message ("Saving hotkey preferences to $rc\n");
2588 return FALSE unless check_rc_file ($rc);
2589 return FALSE unless backup_resource ($rc);
2590 save_menurc ($rc, $HOTKEYS);
2594 sub save_preferences {
2595 return FALSE unless check_claws_not_running ();
2596 my $result = save_rc_preferences ()
2597 and save_ac_preferences ()
2598 and save_hk_preferences ();
2599 $MODIFIED = 0 if $result;
2605 my $nb = Gtk3::Notebook->new;
2607 $nb->append_page (&new_behaviour_page, Gtk3::Label->new (_('Behaviour')));
2608 $nb->append_page (&new_colours_page, Gtk3::Label->new (_('Colours')));
2609 $nb->append_page (&new_gui_page, Gtk3::Label->new (_('GUI')));
2610 $nb->append_page (&new_other_page, Gtk3::Label->new (_('Other')));
2611 $nb->append_page (&new_winpos_page, Gtk3::Label->new (_('Windows')));
2612 $nb->append_page (&new_accounts_page, Gtk3::Label->new (_('Accounts')));
2613 $nb->append_page (&new_plugins_page, Gtk3::Label->new (_('Plugins')));
2614 $nb->append_page (&new_hotkeys_page, Gtk3::Label->new (_('Hotkeys')));
2615 $nb->append_page (&new_info_page, Gtk3::Label->new (_('Info')));
2620 # create an about dialog
2621 sub new_about_dialog {
2623 my $year = '2007-2018';
2624 my $holder = 'Ricardo Mones <ricardo@mones.org>';
2625 my $url = 'http://www.claws-mail.org/clawsker.php';
2626 my $icons = &get_app_icons;
2628 my $dialog = Gtk3::AboutDialog->new;
2629 $dialog->set_transient_for ($parent);
2630 $dialog->set_program_name ('Clawsker');
2631 $dialog->set_version ($VERSION);
2632 $dialog->set_copyright ("Copyright © $year $holder");
2633 $dialog->set_license_type ('gpl-3-0');
2634 $dialog->set_website ($url);
2635 $dialog->set_website_label (_("Visit Clawsker's web page"));
2636 # committers, by number of commits
2637 $dialog->set_authors ([
2639 'Tristan Chabredier (wwp) <subscript@free.fr>',
2640 'Andreas Rönnquist <andreas@ronnquist.net>',
2641 'Christian Hesse <mail@eworm.de>',
2643 $dialog->set_artists ([
2644 'Jesper Schultz <jesper@schultz-net.dk>',
2647 $dialog->set_documenters ([
2649 'Paul Mangan <paul@claws-mail.org>',
2651 # active translators, in alphabetical order
2652 $dialog->set_translator_credits (join ("\n",
2653 'Andreas Rönnquist <andreas@ronnquist.net>',
2654 'Axel Köllhofer <AxelKoellhofer@web.de>',
2655 'David Medina <opensusecatala@gmail.com>',
2656 'Erik P. Olsen <erik@epo.dk>',
2657 'Frederico Goncalves Guimaraes <frederico@teia.bio.br>',
2658 'Marcel Pol <marcel@timelord.nl>',
2659 'Mark Chang <mark.cyj@gmail.com>',
2660 'M. Sulchan Darmawan <bleketux@gmail.com>',
2661 'Numan Demirdöğen <if.gnu.linux@gmail.com>',
2662 'Pedro Albuquerque <pmra@gmx.com>',
2663 'Petter Adsen <petter@synth.no>',
2665 'Tristan Chabredier (wwp) <subscript@free.fr>',
2667 $dialog->set_title ($xl::s{about_title});
2668 $dialog->set_logo ($icons->[-1]);
2675 if ($MODIFIED != 0 and not $READONLY) {
2676 my $markup = "<span>" . _('There are unapplied modifications.')
2677 . "</span>\n\n<span weight=\"bold\">"
2678 . _('Do you really want to quit?') . "</span>\n";
2679 my $dialog = message_dialog (
2680 $parent, _('Clawsker warning'), $markup, 'question',
2681 [ 'gtk-no', 1, 'gtk-yes', 0 ]
2683 my $resp = $dialog->run;
2685 return TRUE if $resp;
2690 # create buttons box
2691 sub new_button_box {
2692 my ($parent, $adlg) = @_;
2693 my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2694 my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2695 my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2696 # disable button until is really implemented
2697 # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2698 my $hbox = Gtk3::HBox->new (FALSE, 5);
2700 $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2701 $b_apply->set_sensitive (not $READONLY);
2702 $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2703 # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2704 $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2706 $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2707 $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2708 # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2709 $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2715 return \@APPICONS if (@APPICONS);
2717 if (-d $DATADIR) { # installed
2718 my $dir = catdir ($DATADIR, 'icons', 'hicolor');
2720 catfile ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png')
2722 } else { # unpacked tarball or git clone
2724 catfile ('.', 'icons', $NAME . '-' . $_ . '.png');
2729 $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2730 push @APPICONS, $icon if ($icon);
2735 sub escape_key_handler {
2736 my ($widget, $event) = @_;
2737 if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {
2738 exit_handler($widget);
2743 exit unless parse_command_line ();
2745 $main_window = Gtk3::Window->new ('toplevel');
2746 exit unless load_preferences ();
2747 exit unless init_hidden_preferences ();
2749 my $box = Gtk3::VBox->new (FALSE, 5);
2750 $box->set_border_width(3);
2751 my $about = new_about_dialog ($main_window);
2752 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2753 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2754 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2755 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2756 $main_window->set_title (_('Claws Mail Hidden Preferences'));
2757 $main_window->set_icon_list (get_app_icons ());
2758 $main_window->add ($box);
2759 $main_window->show_all;