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);
23 use POSIX qw(setlocale);
26 use Digest::MD5 qw(md5_hex);
29 my $NAME = 'clawsker';
30 my $PREFIX = '@PREFIX@';
31 my $LIBDIR = '@LIBDIR@';
32 my $DATADIR = '@DATADIR@';
33 my $VERSION = '@VERSION@';
37 my $main_window = undef;
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));
48 my $xla = gettext ($str);
49 if (scalar(keys(%par)) > 0) {
50 foreach my $key (keys %par) {
51 $xla =~ s/\{$key\}/$par{$key}/g;
54 return decode_utf8($xla);
59 win_title => _('Claws Mail Hidden Preferences'),
60 about_title => _('Clawsker :: A Claws Mail Tweaker'),
61 about_web_label => _("Visit Clawsker's web page"),
63 error_title => _('Clawsker error'),
65 exit_title => _('Clawsker warning'),
66 exit_fact => _('There are unapplied modifications.'),
67 exit_question => _('Do you really want to quit?'),
69 l_oth_use_dlg => _('Use detached address book edit dialogue'),
70 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.'),
71 l_oth_max_use => _('Maximum memory for message cache'),
72 l_oth_max_use_units => _('kilobytes'),
73 h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kilobytes.'),
74 l_oth_min_time => _('Minimun time for cache elements'),
75 l_oth_min_time_units => _('minutes'),
76 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.'),
77 l_oth_use_netm => _('Use NetworkManager'),
78 h_oth_use_netm => _('Use NetworkManager to switch offline automatically.'),
79 l_oth_mp_rounds => _('Rounds for PBKDF2 function'),
80 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.'),
82 l_gui_b_unread => _('Show unread messages with bold font'),
83 h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'),
84 l_gui_no_markup => _('Don\'t use markup'),
85 h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'),
86 l_gui_dot_lines => _('Use dotted lines in tree view components'),
87 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.'),
88 l_gui_h_scroll => _('Enable horizontal scrollbar'),
89 h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'),
90 l_gui_swp_from => _('Display To column instead From column in Sent folder'),
91 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.'),
92 l_gui_v_scroll => _('Folder List scrollbar behaviour'),
93 h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'),
94 l_gui_v_scroll_show => _('Show always'),
95 l_gui_v_scroll_auto => _('Automatic'),
96 l_gui_v_scroll_hide => _('Hide always'),
97 l_gui_from_show => _('From column displays'),
98 h_gui_from_show => _('Selects the data displayed in the From column of the Message List: name, address or both.'),
99 l_gui_from_show_name => _('Name only'),
100 l_gui_from_show_addr => _('Address only'),
101 l_gui_from_show_both => _('Both name and address'),
102 l_gui_strip_off => _('Coloured lines contrast'),
103 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.'),
104 l_gui_cursor_v => _('Show cursor'),
105 h_gui_cursor_v => _('Display the cursor in the Message View.'),
106 l_gui_toolbar_d => _('Detachable toolbars'),
107 h_gui_toolbar_d => _('Show handles in the toolbars.'),
108 l_gui_strip_all => _('Use stripes in all tree view components'),
109 h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'),
110 l_gui_strip_sum => _('Use stripes in Folder List and Message List'),
111 h_gui_strip_sum => _('Enable alternately coloured lines in Message List and Folder List.'),
112 l_gui_two_line_v => _('2 lines per Message List item in 3-column layout'),
113 h_gui_two_line_v => _('Spread Message List information over two lines when using the three column mode.'),
114 l_gui_margin_co => _('Show margin'),
115 h_gui_margin_co => _('Shows a small margin in the Compose View.'),
116 l_gui_mview_date => _('Don\'t display localized date'),
117 h_gui_mview_date => _('Toggles localization of date format in Message View.'),
118 l_gui_zero_char => _('Zero replacement character'),
119 h_gui_zero_char => _('Replaces \'0\' with the given character in Folder List.'),
120 l_gui_type_any => _('Editable headers'),
121 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.'),
122 l_gui_warn_send_multi => _('Warn when sending to more than'),
123 l_gui_warn_send_multi_units => _('recipients'),
124 h_gui_warn_send_multi => _('Show a warning dialogue when sending to more recipients than specified. Use 0 to disable this check.'),
125 l_gui_next_del => _('Select next message on delete'),
126 h_gui_next_del => _('When deleting a message, toggles between selecting the next one (newer message) or the previous one (older message).'),
128 l_beh_hover_t => _('Drag \'n\' drop hover timeout'),
129 l_beh_hover_t_units => _('milliseconds'),
130 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.'),
131 l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
132 h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
133 l_beh_flowed => _('Respect format=flowed in messages'),
134 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.'),
135 l_beh_parts_rw => _('Allow writable temporary files'),
136 h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
137 l_beh_skip_ssl => _('Don\'t check SSL certificates'),
138 h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
139 l_beh_up_step => _('Progress bar update step every'),
140 l_beh_up_step_units => _('items'),
141 h_beh_up_step => _('Update stepping in progress bars.'),
142 l_beh_thread_a => _('Maximum age when threading by subject'),
143 l_beh_thread_a_units => _('days'),
144 h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
145 l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
146 h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
147 l_beh_use_utf8 => _('Force UTF-8 for broken mails'),
148 h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'),
149 l_beh_warn_dnd => _('Warn on drag \'n\' drop'),
150 h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'),
151 l_beh_out_ascii => _('Outgoing messages fallback to ASCII'),
152 h_beh_out_ascii => _('If allowed by content, ASCII will be used to encode outgoing messages, otherwise the user-defined encoding is always enforced.'),
153 l_beh_pp_unsel => _('Primary paste unselects selection'),
154 h_beh_pp_unsel => _('Controls how pasting using middle-click changes the selected text and insertion point.'),
155 l_beh_inline_at => _('Show inline attachments'),
156 h_beh_inline_at => _('Allows to hide inline attachments already shown in mail structure view.'),
157 l_beh_addr_swc => _('Address search in compose window matches any'),
158 h_beh_addr_swc => _('On Tab-key completion, address text will match any part of the string or only from the start.'),
159 l_beh_fold_swc => _('Folder search in folder selector matches any'),
160 h_beh_fold_swc => _('On folder name completion text will match any part of the string or only from the start.'),
161 l_beh_rewrite_ff => _('Rewrite first \'From\' using QP encoding'),
162 h_beh_rewrite_ff => _('Workaround some servers which convert first \'From\' to \'>From\' by using Quoted-Printable transfer encoding instead of 7bit/8bit encoding.'),
164 l_col_emphasis => _('X-Mailer header'),
165 h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'),
166 l_col_log_err => _('Error messages'),
167 h_col_log_err => _('Colour for error messages in log window.'),
168 l_col_log_in => _('Server messages'),
169 h_col_log_in => _('Colour for messages received from servers in log window.'),
170 l_col_log_msg => _('Standard messages'),
171 h_col_log_msg => _('Colour for messages in log window.'),
172 l_col_log_out => _('Client messages'),
173 h_col_log_out => _('Colour for messages sent to servers in log window.'),
174 l_col_log_warn => _('Warning messages'),
175 h_col_log_warn => _('Colour for warning messages in log window.'),
177 l_col_tags_bg => _('Tags background'),
178 h_col_tags_bg => _('Background colour for tags in message view.'),
179 l_col_tags_text => _('Tags text'),
180 h_col_tags_text => _('Text colour for tags in message view.'),
182 l_col_default_header_bg => _('Default headers background'),
183 h_col_default_header_bg => _('Background colour for default headers in compose window.'),
184 l_col_default_header_text => _('Default headers text'),
185 h_col_default_header_text => _('Text colour for default headers in compose window.'),
187 l_col_qs_active_bg => _('Active quick search background'),
188 h_col_qs_active_bg => _('Background colour for active quick search.'),
189 l_col_qs_active_text => _('Active quick search text'),
190 h_col_qs_active_text => _('Text colour for active quick search.'),
191 l_col_qs_error_bg => _('Quick search error background'),
192 h_col_qs_error_bg => _('Background colour for quick search error.'),
193 l_col_qs_error_text => _('Quick search error text'),
194 h_col_qs_error_text => _('Text colour for quick search error.'),
196 l_col_diff_add => _('Added lines'),
197 h_col_diff_add => _('Colour for added lines in patches.'),
198 l_col_diff_del => _('Deleted lines'),
199 h_col_diff_del => _('Colour for deleted lines in patches.'),
200 l_col_diff_hunk => _('Hunk lines'),
201 h_col_diff_hunk => _('Colour for hunk headers in patches.'),
203 l_win_x => _('X position'),
204 h_win_x => _('X coordinate for window\'s top-left corner.'),
205 l_win_y => _('Y position'),
206 h_win_y => _('Y coordinate for window\'s top-left corner.'),
207 l_win_w => _('Width'),
208 h_win_w => _('Window\'s width in pixels.'),
209 l_win_h => _('Height'),
210 h_win_h => _('Window\'s height in pixels.'),
212 l_win_main_mx => _('Maximized'),
213 h_win_main_mx => _('Changes window maximized status.'),
214 l_win_main_fs => _('Full-screen'),
215 h_win_main_fs => _('Changes full screen status.'),
217 l_acc_gtls_set => _('Use custom GnuTLS priority'),
218 h_acc_gtls_set => _('Enables using user provided GnuTLS priority string.'),
219 l_acc_gtls_pri => _('GnuTLS priority'),
220 h_acc_gtls_pri => _('Value to use as GnuTLS priority string if custom priority check is enabled. Otherwise this value is ignored.'),
222 l_plu_gpg_alimit => _('Autocompletion limit'),
223 h_plu_gpg_alimit => _('Limits the number of addresses obtained from keyring through autocompletion. Use 0 to get all matches.'),
224 l_plu_lav_burl => _('Base URL'),
225 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.'),
226 l_plu_prl_flvb => _('Log level'),
227 h_plu_prl_flvb => _('Verbosity level of log, accumulative.'),
228 l_plu_prl_none => _('None'),
229 l_plu_prl_manual => _('Manual'),
230 l_plu_prl_action => _('Actions'),
231 l_plu_prl_match => _('Matches'),
234 # data and metadata of resource files
239 # all preferences read by load_preferences
243 # values of all preferences handled by clawsker
247 # default config dir and file name
248 my $ALTCONFIGDIR = FALSE;
249 my $CONFIGDIR = $ENV{HOME} . '/.claws-mail/';
250 my $CONFIGRC = 'clawsrc';
251 my $ACCOUNTRC = 'accountrc';
252 # supported and available plugins lists
253 my @PLUGINS = qw(AttRemover GPG ManageSieve Libravatar PerlPlugin);
255 # loaded hotkeys from load_menurc
257 # current tree selection
265 # index constants for preference arrays
266 NAME => 0, # the name on the rc file
267 LABEL => 1, # the label on the GUI
268 DESC => 2, # the description for the hint/help
269 TYPE => 3, # data type: bool, int, float, string, color
270 CMVER => 4, # lowest[,highest] Claws Mail version(s) the feature exists
271 CMDEF => 5, # default value for the preference in Claws Mail
272 PLUGIN => 6, # plugin section (only in plugin preferences)
273 # constants for GUI spacing
278 # for data references indexing
279 use constant VALUE => 0;
280 use constant IVALUE => 1;
284 sub version_greater_or_equal {
285 my ($version, $refvers) = @_;
286 return TRUE if (length($version) == 0 and length($refvers) >= 0);
287 return FALSE if (length($version) >= 0 and length($refvers) == 0);
288 return TRUE if (version->parse($version) >= version->parse($refvers));
292 sub get_claws_version {
293 $_ = qx/which claws-mail/;
295 return "" unless ($_); # not installed
299 my @fver = split (/ /);
300 die "Invalid version string" unless ($fver[2] eq "version");
301 my @ver = split (/\./, $fver[3]);
304 if ($ver[2] =~ /(\d+)git(\d+)/) {
313 # data handlers and auxiliar functions
315 sub handle_bool_value {
316 my ($widget, $event, $dataref) = @_;
317 $$dataref->[VALUE] = ($widget->get_active ())? '1': '0';
318 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
319 if defined $$dataref->[IVALUE];
322 sub handle_int_value {
323 my ($widget, $event, $dataref) = @_;
324 $_ = $widget->get_text ();
328 $$dataref->[VALUE] = $_;
329 $widget->set_text ($_);
332 $widget->set_text ($$dataref->[VALUE]);
334 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
335 if defined $$dataref->[IVALUE];
338 sub handle_nchar_value {
339 my ($widget, $event, $dataref, $minlen, $maxlen) = @_;
340 $_ = substr ($widget->get_text (), 0, $maxlen);
341 $widget->set_text ($_);
342 $$dataref->[VALUE] = $_;
343 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
344 if defined $$dataref->[IVALUE];
347 sub gdk_rgba_from_str {
349 my ($rr, $gg, $bb) = (0, 0 ,0);
351 if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) {
356 my $color = Gtk3::Gdk::RGBA->new ($rr, $gg, $bb, 1.0);
360 sub str_from_gdk_rgba {
362 my $rr = $color->red * 256;
363 my $gg = $color->green * 256;
364 my $bb = $color->blue * 256;
365 my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb);
369 sub handle_color_value {
370 my ($widget, $event, $dataref) = @_;
371 my $newcol = $widget->get_rgba;
372 $$dataref->[VALUE] = str_from_gdk_rgba ($newcol);
373 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
374 if defined $$dataref->[IVALUE];
377 sub handle_selection_value {
378 my ($widget, $event, $dataref) = @_;
379 $$dataref->[VALUE] = $widget->get_active;
380 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
381 if defined $$dataref->[IVALUE];
384 sub get_rc_filename {
385 return $CONFIGDIR . $CONFIGRC;
388 sub get_ac_rc_filename {
389 return $CONFIGDIR . $ACCOUNTRC;
392 sub get_menurc_filename {
393 return $CONFIGDIR . "menurc";
396 sub set_rc_filename {
398 my @parts = split ('/', $fullname);
399 $CONFIGRC = $parts[$#parts];
400 $parts[$#parts] = '';
401 $CONFIGDIR = join ('/', @parts);
405 my ($mesg, $fatal) = @_;
406 if (defined($fatal) && $fatal eq 'die') {
407 die "$NAME: $mesg\n";
410 print "$NAME: $mesg\n";
415 my ($parent, $title, $markup, $type, $buttons) = @_;
416 my $flags = [qw/modal destroy-with-parent/];
417 my $dialog = Gtk3::Dialog->new_with_buttons (
418 $title, $parent, $flags, @$buttons
420 my $label = Gtk3::Label->new;
421 $label->set_markup ($markup);
423 if ($type eq 'error') {
424 $icon = Gtk3::Image->new_from_icon_name('dialog-error', 'GTK_ICON_SIZE_DIALOG');
425 } elsif ($type eq 'warning') {
426 $icon = Gtk3::Image->new_from_icon_name('dialog-warning', 'GTK_ICON_SIZE_DIALOG');
427 } elsif ($type eq 'question') {
428 $icon = Gtk3::Image->new_from_icon_name('dialog-question', 'GTK_ICON_SIZE_DIALOG');
430 my $hbox = Gtk3::Box->new ('horizontal', 5);
431 $hbox->pack_start ($icon, FALSE, FALSE, 5) if defined $icon;
432 $hbox->pack_start ($label, FALSE, FALSE, 5);
433 my $dialogbox = $dialog->get_content_area;
434 $dialogbox->add ($hbox);
435 $dialogbox->show_all;
441 my $markup = "<span weight=\"bold\" size=\"large\">" . $emsg . "</span>";
442 my $errordlg = message_dialog (
443 $main_window, $xl::s{error_title}, $markup, 'error', [ 'gtk-cancel', 0 ]
449 sub claws_is_running {
450 my $emsg = _('Error: seems Claws Mail is currently running, close it first.');
452 error_dialog ($emsg);
456 sub check_claws_not_running {
457 return TRUE if $READONLY;
458 my $tmpdir = (defined $ENV{TMPDIR})? $ENV{TMPDIR}: '/tmp';
459 $tmpdir = '/tmp' if ($tmpdir eq '');
460 my $lockdir = "$tmpdir/claws-mail-$<";
464 my $socket = "$lockdir/" . md5_hex ($_);
465 -S $socket and return claws_is_running ();
472 (defined($rcfile) && -f $rcfile) or do {
473 my $emsg = _('Error: resource file for Claws Mail was not found.');
475 error_dialog ($emsg);
481 sub set_widget_hint {
482 my ($wdgt, $hint) = @_;
483 $wdgt->set_tooltip_text ($hint);
484 $wdgt->set_has_tooltip (TRUE);
487 sub set_widget_sens {
488 my ($wdgt, $versions) = @_;
489 my @ver = split(/,/, $versions);
491 $wdgt->set_sensitive (
492 version_greater_or_equal ($CLAWSV, $ver[0])
493 and version_greater_or_equal ($ver[1], $CLAWSV)
496 $wdgt->set_sensitive (version_greater_or_equal ($CLAWSV, $ver[0]));
500 # graphic element creation
502 sub new_hbox_spaced_pack {
503 my $hbox = Gtk3::HBox->new (FALSE);
505 $hbox->pack_start ($_, FALSE, FALSE, HBOX_PAD);
510 sub new_check_button_for($$$) {
511 my ($hash, $key, $vhash) = @_;
512 my $name = $$hash{$key}[NAME];
513 my $label = $$hash{$key}[LABEL];
515 my $cb = Gtk3::CheckButton->new ($label);
516 my $value = $$vhash{$name}[VALUE];
517 $value //= $$hash{$key}[CMDEF];
518 $cb->set_active ($value eq '1');
519 $cb->signal_connect (clicked => sub {
521 handle_bool_value ($w, $e, \$$vhash{$name});
523 set_widget_hint ($cb, $$hash{$key}[DESC]);
524 set_widget_sens ($cb, $$hash{$key}[CMVER]);
526 return new_hbox_spaced_pack ($cb);
529 sub new_text_box_for_int($$$) {
530 my ($hash, $key, $vhash) = @_;
531 my $name = $$hash{$key}[NAME];
532 my $label = $$hash{$key}[LABEL];
533 my @type = split (/,/, $$hash{$key}[TYPE]);
534 push (@type, 0), push (@type, 10000) unless ($#type > 0);
537 if (ref $label eq 'ARRAY') {
538 $gunits = Gtk3::Label->new ($label->[1]);
539 $label = $label->[0];
541 my $glabel = Gtk3::Label->new ($label);
542 my $pagei = int (($type[2] - $type[1]) / 10);
543 my $gentry = Gtk3::SpinButton->new_with_range ($type[1], $type[2], $pagei);
544 my $value = $$vhash{$name}[VALUE];
545 $value //= $$hash{$key}[CMDEF];
546 $gentry->set_numeric (TRUE);
547 $gentry->set_value ($value);
548 $gentry->signal_connect('value-changed' => sub {
550 handle_int_value ($w, $e, \$$vhash{$name});
552 set_widget_hint ($gentry, $$hash{$key}[DESC]);
553 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
554 $glabel->set_sensitive ($gentry->get_sensitive);
555 $gunits->set_sensitive ($gentry->get_sensitive) if ($gunits);
557 return new_hbox_spaced_pack ($glabel, $gentry, $gunits) if ($gunits);
558 return new_hbox_spaced_pack ($glabel, $gentry);
561 sub new_text_box_for_nchar($$$) {
562 my ($hash, $key, $vhash) = @_;
563 my $name = $$hash{$key}[NAME];
564 my $label = $$hash{$key}[LABEL];
565 my @type = split (/,/, $$hash{$key}[TYPE]); # char,minlen,maxlen,width
566 my $glabel = Gtk3::Label->new ($label);
567 my $gentry = Gtk3::Entry->new ();
568 $gentry->set_max_length($type[2]) if defined ($type[2]);
569 my $width = $type[3];
571 $gentry->set_width_chars(int ($width) + 2) if defined ($width);
572 my $value = $$vhash{$name}[VALUE];
573 $value //= $$hash{$key}[CMDEF];
574 $gentry->set_text ($value);
575 $gentry->signal_connect('key-release-event' => sub {
577 handle_nchar_value ($w, $e, \$$vhash{$name}, $type[1], $type[2]);
579 set_widget_hint ($gentry, $$hash{$key}[DESC]);
580 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
581 $glabel->set_sensitive ($gentry->get_sensitive);
583 return new_hbox_spaced_pack ($glabel, $gentry);
586 sub new_color_button_for($$$) {
587 my ($hash, $key, $vhash) = @_;
588 my $name = $$hash{$key}[NAME];
589 my $label = $$hash{$key}[LABEL];
591 my $value = $$vhash{$name}[VALUE];
592 $value //= $$hash{$key}[CMDEF];
593 my $col = gdk_rgba_from_str ($value);
594 my $glabel = Gtk3::Label->new ($label);
595 my $button = Gtk3::ColorButton->new_with_rgba ($col);
596 $button->set_title ($label);
597 $button->set_relief ('none');
598 $button->signal_connect ('color-set' => sub {
600 handle_color_value ($w, $e, \$$vhash{$name});
602 set_widget_hint ($button, $$hash{$key}[DESC]);
603 set_widget_sens ($button, $$hash{$key}[CMVER]);
604 $glabel->set_sensitive ($button->get_sensitive);
606 return new_hbox_spaced_pack ($button, $glabel);
609 sub new_selection_box_for($$$) {
610 my ($hash, $key, $vhash) = @_;
611 my $name = $$hash{$key}[NAME];
612 my $label = $$hash{$key}[LABEL];
614 my $glabel = Gtk3::Label->new ($label);
615 my $combo = Gtk3::ComboBoxText->new;
616 my @options = split (';', $$hash{$key}[TYPE]);
617 foreach my $opt (@options) {
618 my ($index, $textkey) = split ('=', $opt);
619 $combo->insert (-1, $index, $xl::s{$textkey});
621 $combo->signal_connect ('changed' => sub {
623 handle_selection_value ($w, $e, \$$vhash{$name});
625 my $value = $$vhash{$name}[VALUE];
626 $value //= $$hash{$key}[CMDEF];
627 $combo->set_active ($value);
628 set_widget_hint ($combo, $$hash{$key}[DESC]);
629 set_widget_sens ($combo, $$hash{$key}[CMVER]);
630 $glabel->set_sensitive ($combo->get_sensitive);
632 return new_hbox_spaced_pack ($glabel, $combo);
635 # more graphic helpers
638 my ($border_w, $row_s, $col_s) = @_;
639 $border_w //= GRID_SPC;
642 my $grid = Gtk3::Grid->new;
643 $grid->set_border_width ($border_w);
644 $grid->set_row_spacing ($row_s);
645 $grid->set_column_spacing ($col_s);
652 my $label = Gtk3::Label->new ($text);
653 $label->set_alignment (0, 0.5);
660 my $label = Gtk3::Label->new ('<b>' . $text . '</b>');
661 $label->set_use_markup (TRUE);
662 $label->set_alignment (0, 0.5);
667 my ($width, $height, $widget) = @_;
668 my $grid = new_grid ();
669 $grid->set_column_homogeneous (TRUE);
670 for (my $i = 0; $i < $width; ++$i) {
671 for (my $j = 0; $j < $height; ++$j) {
672 my $wid = $widget->[$j]->[$i];
673 next unless defined $wid;
674 my $ww = (($i + 1 < $width) and (defined $widget->[$j]->[$i + 1]))
678 $grid->attach ($wid, $i, $j, $ww, 1);
679 } else { # not a widget
680 if ('--' eq $wid) { # a separator
681 $grid->attach (Gtk3::Separator->new ('horizontal'),
683 } else { # or a title
684 $grid->attach (new_title ($wid),
693 # preference maps and corresponding page creation subs
695 %pr::oth = ( # other preferences
697 'addressbook_use_editaddress_dialog',
698 $xl::s{l_oth_use_dlg},
699 $xl::s{h_oth_use_dlg},
705 'cache_max_mem_usage',
706 [ $xl::s{l_oth_max_use}, $xl::s{l_oth_max_use_units} ],
707 $xl::s{h_oth_max_use},
708 'int,0,524288', # 0 Kb - 512 Mb
713 'cache_min_keep_time',
714 [ $xl::s{l_oth_min_time}, $xl::s{l_oth_min_time_units} ],
715 $xl::s{h_oth_min_time},
716 'int,0,120', # 0 minutes - 2 hours
721 'use_networkmanager',
722 $xl::s{l_oth_use_netm},
723 $xl::s{h_oth_use_netm},
729 'master_passphrase_pbkdf2_rounds',
730 $xl::s{l_oth_mp_rounds},
731 $xl::s{h_oth_mp_rounds},
738 sub new_other_page() {
739 return new_grid_pack (1, 12, [
740 [ _('Addressbook') ],
741 [ new_check_button_for(\%pr::oth, 'use_dlg', \%HPVALUE) ],
744 [ new_text_box_for_int(\%pr::oth, 'max_use', \%HPVALUE) ],
745 [ new_text_box_for_int(\%pr::oth, 'min_time', \%HPVALUE) ],
747 [ _('NetworkManager') ],
748 [ new_check_button_for(\%pr::oth, 'use_netm', \%HPVALUE) ],
750 [ _('Master passphrase') ],
751 [ new_text_box_for_int(\%pr::oth, 'mp_rounds', \%HPVALUE) ]
755 %pr::gui = ( # gui bells and whistles
758 $xl::s{l_gui_b_unread},
759 $xl::s{h_gui_b_unread},
766 $xl::s{l_gui_no_markup},
767 $xl::s{h_gui_no_markup},
773 'enable_dotted_lines',
774 $xl::s{l_gui_dot_lines},
775 $xl::s{h_gui_dot_lines},
777 '2.4.0.115,3.7.10.44',
782 $xl::s{l_gui_h_scroll},
783 $xl::s{h_gui_h_scroll},
790 $xl::s{l_gui_swp_from},
791 $xl::s{h_gui_swp_from},
797 'folderview_vscrollbar_policy',
798 $xl::s{l_gui_v_scroll},
799 $xl::s{h_gui_v_scroll},
800 '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
806 $xl::s{l_gui_from_show},
807 $xl::s{h_gui_from_show},
808 '0=l_gui_from_show_name;1=l_gui_from_show_addr;2=l_gui_from_show_both',
813 'stripes_color_offset',
814 $xl::s{l_gui_strip_off},
815 $xl::s{h_gui_strip_off},
816 'int,0,40000', # no idea what this number means
821 'textview_cursor_visible',
822 $xl::s{l_gui_cursor_v},
823 $xl::s{h_gui_cursor_v},
829 'toolbar_detachable',
830 $xl::s{l_gui_toolbar_d},
831 $xl::s{h_gui_toolbar_d},
837 'use_stripes_everywhere',
838 $xl::s{l_gui_strip_all},
839 $xl::s{h_gui_strip_all},
845 'use_stripes_in_summaries',
846 $xl::s{l_gui_strip_sum},
847 $xl::s{h_gui_strip_sum},
854 $xl::s{l_gui_two_line_v},
855 $xl::s{h_gui_two_line_v},
861 'show_compose_margin',
862 $xl::s{l_gui_margin_co},
863 $xl::s{h_gui_margin_co},
869 'msgview_date_format',
870 $xl::s{l_gui_mview_date},
871 $xl::s{h_gui_mview_date},
877 'zero_replacement_char',
878 $xl::s{l_gui_zero_char},
879 $xl::s{h_gui_zero_char},
886 $xl::s{l_gui_type_any},
887 $xl::s{h_gui_type_any},
893 'warn_sending_many_recipients_num',
894 [ $xl::s{l_gui_warn_send_multi}, $xl::s{l_gui_warn_send_multi_units} ],
895 $xl::s{h_gui_warn_send_multi},
902 $xl::s{l_gui_next_del},
903 $xl::s{h_gui_next_del},
911 return new_grid_pack (2, 24, [
912 [ _('Coloured stripes') ],
913 [ new_check_button_for (\%pr::gui, 'strip_all', \%HPVALUE),
914 new_check_button_for (\%pr::gui, 'strip_sum', \%HPVALUE) ],
915 [ new_text_box_for_int (\%pr::gui, 'strip_off', \%HPVALUE) ],
917 [ _('Message List') ],
918 [ new_check_button_for (\%pr::gui, 'b_unread', \%HPVALUE),
919 new_check_button_for (\%pr::gui, 'swp_from', \%HPVALUE) ],
920 [ new_check_button_for (\%pr::gui, 'two_linev', \%HPVALUE),
921 new_check_button_for (\%pr::gui, 'next_del', \%HPVALUE) ],
922 [ new_selection_box_for (\%pr::gui, 'from_show', \%HPVALUE) ],
924 [ _('Message View') ],
925 [ new_check_button_for (\%pr::gui, 'cursor_v', \%HPVALUE),
926 new_check_button_for (\%pr::gui, 'mview_date', \%HPVALUE) ],
928 [ _('Compose window') ],
929 [ new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
930 new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE) ],
931 [ new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE) ],
932 [ new_text_box_for_int (\%pr::gui, 'warn_send_multi', \%HPVALUE) ],
934 [ _('Scroll bars') ],
935 [ new_check_button_for (\%pr::gui, 'h_scroll', \%HPVALUE) ],
936 [ new_selection_box_for (\%pr::gui, 'v_scroll', \%HPVALUE) ],
939 [ new_check_button_for (\%pr::gui, 'dot_lines', \%HPVALUE),
940 new_check_button_for (\%pr::gui, 'toolbar_d', \%HPVALUE) ],
941 [ new_text_box_for_nchar (\%pr::gui, 'zero_char', \%HPVALUE) ]
945 %pr::beh = ( # tweak some behaviour
948 [ $xl::s{l_beh_hover_t}, $xl::s{l_beh_hover_t_units} ],
949 $xl::s{h_beh_hover_t},
950 'int,100,3000', # 0.1 seconds - 3 seconds
956 $xl::s{l_beh_dangerous},
957 $xl::s{h_beh_dangerous},
963 'respect_flowed_format',
964 $xl::s{l_beh_flowed},
965 $xl::s{h_beh_flowed},
971 'save_parts_readwrite',
972 $xl::s{l_beh_parts_rw},
973 $xl::s{h_beh_parts_rw},
979 'skip_ssl_cert_check',
980 $xl::s{l_beh_skip_ssl},
981 $xl::s{h_beh_skip_ssl},
987 'statusbar_update_step',
988 [ $xl::s{l_beh_up_step}, $xl::s{l_beh_up_step_units} ],
989 $xl::s{h_beh_up_step},
990 'int,1,200', # 1 item - 200 items
995 'thread_by_subject_max_age',
996 [ $xl::s{l_beh_thread_a}, $xl::s{l_beh_thread_a_units} ],
997 $xl::s{h_beh_thread_a},
998 'int,1,30', # 1 day - 30 days
1004 $xl::s{l_beh_unsafe_ssl},
1005 $xl::s{h_beh_unsafe_ssl},
1011 'utf8_instead_of_locale_for_broken_mail',
1012 $xl::s{l_beh_use_utf8},
1013 $xl::s{h_beh_use_utf8},
1020 $xl::s{l_beh_warn_dnd},
1021 $xl::s{h_beh_warn_dnd},
1027 'outgoing_fallback_to_ascii',
1028 $xl::s{l_beh_out_ascii},
1029 $xl::s{h_beh_out_ascii},
1035 'primary_paste_unselects',
1036 $xl::s{l_beh_pp_unsel},
1037 $xl::s{h_beh_pp_unsel},
1043 'show_inline_attachments',
1044 $xl::s{l_beh_inline_at},
1045 $xl::s{h_beh_inline_at},
1051 'address_search_wildcard',
1052 $xl::s{l_beh_addr_swc},
1053 $xl::s{h_beh_addr_swc},
1059 'folder_search_wildcard',
1060 $xl::s{l_beh_fold_swc},
1061 $xl::s{h_beh_fold_swc},
1067 'rewrite_first_from',
1068 $xl::s{l_beh_rewrite_ff},
1069 $xl::s{h_beh_rewrite_ff},
1076 sub new_behaviour_page() {
1077 return new_grid_pack (2, 20, [
1078 [ _('Drag \'n\' drop') ],
1079 [ new_text_box_for_int (\%pr::beh, 'hover_t', \%HPVALUE) ],
1080 [ new_check_button_for (\%pr::beh, 'warn_dnd', \%HPVALUE) ],
1082 [ _('Secure Sockets Layer') ],
1083 [ new_check_button_for (\%pr::beh, 'skip_ssl', \%HPVALUE),
1084 new_check_button_for (\%pr::beh, 'unsafe_ssl', \%HPVALUE) ],
1087 [ new_check_button_for (\%pr::beh, 'flowed', \%HPVALUE),
1088 new_check_button_for (\%pr::beh, 'out_ascii', \%HPVALUE) ],
1089 [ new_check_button_for (\%pr::beh, 'parts_rw', \%HPVALUE),
1090 new_check_button_for (\%pr::beh, 'pp_unsel', \%HPVALUE) ],
1091 [ new_check_button_for (\%pr::beh, 'use_utf8', \%HPVALUE),
1092 new_check_button_for (\%pr::beh, 'inline_at', \%HPVALUE) ],
1093 [ new_check_button_for (\%pr::beh, 'dangerous', \%HPVALUE),
1094 new_check_button_for (\%pr::beh, 'rewrite_ff', \%HPVALUE) ],
1096 [ _('Completion') ],
1097 [ new_check_button_for (\%pr::beh, 'addr_swc', \%HPVALUE) ],
1098 [ new_check_button_for (\%pr::beh, 'fold_swc', \%HPVALUE) ],
1101 [ new_text_box_for_int (\%pr::beh, 'up_step', \%HPVALUE) ],
1102 [ new_text_box_for_int (\%pr::beh, 'thread_a', \%HPVALUE) ]
1106 %pr::col = ( # a variety of colours
1109 $xl::s{l_col_emphasis},
1110 $xl::s{h_col_emphasis},
1117 $xl::s{l_col_log_err},
1118 $xl::s{h_col_log_err},
1125 $xl::s{l_col_log_in},
1126 $xl::s{h_col_log_in},
1133 $xl::s{l_col_log_msg},
1134 $xl::s{h_col_log_msg},
1141 $xl::s{l_col_log_out},
1142 $xl::s{h_col_log_out},
1149 $xl::s{l_col_log_warn},
1150 $xl::s{h_col_log_warn},
1157 $xl::s{l_col_diff_add},
1158 $xl::s{h_col_diff_add},
1164 'diff_deleted_color',
1165 $xl::s{l_col_diff_del},
1166 $xl::s{h_col_diff_del},
1173 $xl::s{l_col_diff_hunk},
1174 $xl::s{h_col_diff_hunk},
1181 $xl::s{l_col_tags_bg},
1182 $xl::s{h_col_tags_bg},
1189 $xl::s{l_col_tags_text},
1190 $xl::s{h_col_tags_text},
1195 default_header_bg => [
1196 'default_header_bgcolor',
1197 $xl::s{l_col_default_header_bg},
1198 $xl::s{h_col_default_header_bg},
1203 default_header_text => [
1204 'default_header_color',
1205 $xl::s{l_col_default_header_text},
1206 $xl::s{h_col_default_header_text},
1212 'qs_active_bgcolor',
1213 $xl::s{l_col_qs_active_bg},
1214 $xl::s{h_col_qs_active_bg},
1221 $xl::s{l_col_qs_active_text},
1222 $xl::s{h_col_qs_active_text},
1229 $xl::s{l_col_qs_error_bg},
1230 $xl::s{h_col_qs_error_bg},
1237 $xl::s{l_col_qs_error_text},
1238 $xl::s{h_col_qs_error_text},
1245 sub new_colours_page() {
1246 return new_grid_pack (3, 18, [
1247 [ _('Message View') ],
1248 [ new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE) ],
1249 [ new_color_button_for (\%pr::col, 'tags_text', \%HPVALUE) ,
1250 new_color_button_for (\%pr::col, 'tags_bg', \%HPVALUE) ],
1252 [ _('Log window') ],
1253 [ new_color_button_for (\%pr::col, 'log_err', \%HPVALUE) ,
1254 new_color_button_for (\%pr::col, 'log_in', \%HPVALUE) ],
1255 [ new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE) ,
1256 new_color_button_for (\%pr::col, 'log_out', \%HPVALUE) ],
1257 [ new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE) ],
1259 [ _('Viewing patches') ],
1260 [ new_color_button_for (\%pr::col, 'diff_add', \%HPVALUE) ,
1261 new_color_button_for (\%pr::col, 'diff_del', \%HPVALUE) ,
1262 new_color_button_for (\%pr::col, 'diff_hunk', \%HPVALUE) ],
1264 [ _('Compose window') ],
1265 [ new_color_button_for (\%pr::col, 'default_header_text', \%HPVALUE) ,
1266 new_color_button_for (\%pr::col, 'default_header_bg', \%HPVALUE) ],
1268 [ _('Quick search') ],
1269 [ new_color_button_for (\%pr::col, 'qs_active_text', \%HPVALUE) ,
1270 new_color_button_for (\%pr::col, 'qs_active_bg', \%HPVALUE) ],
1271 [ new_color_button_for (\%pr::col, 'qs_error_text', \%HPVALUE) ,
1272 new_color_button_for (\%pr::col, 'qs_error_bg', \%HPVALUE) ]
1276 %pr::win = ( # tweak window positions and/or sizes
1281 'int,0,3000', # 0 pixels - 3000 pixels
1289 'int,0,3000', # 0 pixels - 3000 pixels
1297 'int,0,3000', # 0 pixels - 3000 pixels
1305 'int,0,3000', # 0 pixels - 3000 pixels
1310 'mainwin_maximised',
1311 $xl::s{l_win_main_mx},
1312 $xl::s{h_win_main_mx},
1318 'mainwin_fullscreen',
1319 $xl::s{l_win_main_fs},
1320 $xl::s{h_win_main_fs},
1326 'main_messagewin_x',
1329 'int,0,3000', # 0 pixels - 3000 pixels
1334 'main_messagewin_y',
1337 'int,0,3000', # 0 pixels - 3000 pixels
1345 'int,0,3000', # 0 pixels - 3000 pixels
1350 'messagewin_height',
1353 'int,0,3000', # 0 pixels - 3000 pixels
1361 'int,0,3000', # 0 pixels - 3000 pixels
1369 'int,0,3000', # 0 pixels - 3000 pixels
1377 'int,0,3000', # 0 pixels - 3000 pixels
1382 'receivewin_height',
1385 'int,0,3000', # 0 pixels - 3000 pixels
1393 'int,0,3000', # 0 pixels - 3000 pixels
1401 'int,0,3000', # 0 pixels - 3000 pixels
1406 'folderitemwin_width',
1409 'int,0,3000', # 0 pixels - 3000 pixels
1414 'folderitemwin_height',
1417 'int,0,3000', # 0 pixels - 3000 pixels
1422 'folderselwin_width',
1425 'int,0,3000', # 0 pixels - 3000 pixels
1430 'folderselwin_height',
1433 'int,0,3000', # 0 pixels - 3000 pixels
1441 'int,0,3000', # 0 pixels - 3000 pixels
1449 'int,0,3000', # 0 pixels - 3000 pixels
1454 'addressbookwin_width',
1457 'int,0,3000', # 0 pixels - 3000 pixels
1462 'addressbookwin_height',
1465 'int,0,3000', # 0 pixels - 3000 pixels
1470 'addressbookeditpersonwin_width',
1473 'int,0,3000', # 0 pixels - 3000 pixels
1478 'addressbookeditpersonwin_height',
1481 'int,0,3000', # 0 pixels - 3000 pixels
1486 'addressbookeditgroupwin_width',
1489 'int,0,3000', # 0 pixels - 3000 pixels
1494 'addressbookeditgroupwin_height',
1497 'int,0,3000', # 0 pixels - 3000 pixels
1502 'addressaddwin_width',
1505 'int,0,3000', # 0 pixels - 3000 pixels
1510 'addressaddwin_height',
1513 'int,0,3000', # 0 pixels - 3000 pixels
1518 'addressbook_folderselwin_width',
1521 'int,0,3000', # 0 pixels - 3000 pixels
1526 'addressbook_folderselwin_height',
1529 'int,0,3000', # 0 pixels - 3000 pixels
1534 'editaccountwin_width',
1537 'int,0,3000', # 0 pixels - 3000 pixels
1542 'editaccountwin_height',
1545 'int,0,3000', # 0 pixels - 3000 pixels
1550 'accountswin_width',
1553 'int,0,3000', # 0 pixels - 3000 pixels
1558 'accountswin_height',
1561 'int,0,3000', # 0 pixels - 3000 pixels
1566 'filteringwin_width',
1569 'int,0,3000', # 0 pixels - 3000 pixels
1574 'filteringwin_height',
1577 'int,0,3000', # 0 pixels - 3000 pixels
1582 'filteringactionwin_width',
1585 'int,0,3000', # 0 pixels - 3000 pixels
1590 'filteringactionwin_height',
1593 'int,0,3000', # 0 pixels - 3000 pixels
1598 'filtering_debugwin_width',
1601 'int,0,3000', # 0 pixels - 3000 pixels
1606 'filtering_debugwin_height',
1609 'int,0,3000', # 0 pixels - 3000 pixels
1617 'int,0,3000', # 0 pixels - 3000 pixels
1622 'matcherwin_height',
1625 'int,0,3000', # 0 pixels - 3000 pixels
1633 'int,0,3000', # 0 pixels - 3000 pixels
1641 'int,0,3000', # 0 pixels - 3000 pixels
1646 'templateswin_width',
1649 'int,0,3000', # 0 pixels - 3000 pixels
1654 'templateswin_height',
1657 'int,0,3000', # 0 pixels - 3000 pixels
1665 'int,0,3000', # 0 pixels - 3000 pixels
1670 'actionswin_height',
1673 'int,0,3000', # 0 pixels - 3000 pixels
1678 'actionsiodialog_width',
1681 'int,0,3000', # 0 pixels - 3000 pixels
1686 'actionsiodialog_height',
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
1718 'pluginswin_height',
1721 'int,0,3000', # 0 pixels - 3000 pixels
1729 'int,0,3000', # 0 pixels - 3000 pixels
1737 'int,0,3000', # 0 pixels - 3000 pixels
1742 'print_previewwin_width',
1745 'int,0,3000', # 0 pixels - 3000 pixels
1750 'print_previewwin_height',
1753 'int,0,3000', # 0 pixels - 3000 pixels
1759 sub new_winpos_subpage_main() {
1760 return new_grid_pack (3, 7, [
1761 [ _('Main window'), undef ],
1762 [ new_text_box_for_int (\%pr::win, 'main_x', \%HPVALUE), undef ],
1763 [ new_text_box_for_int (\%pr::win, 'main_y', \%HPVALUE), undef ],
1764 [ new_text_box_for_int (\%pr::win, 'main_w', \%HPVALUE),
1765 new_text_box_for_int (\%pr::win, 'main_h', \%HPVALUE) ],
1766 [ new_check_button_for (\%pr::win, 'main_fs', \%HPVALUE), undef ],
1767 [ new_check_button_for (\%pr::win, 'main_mx', \%HPVALUE), undef ]
1771 sub new_winpos_subpage_msgs() {
1772 return new_grid_pack (3, 4, [
1773 [ _('Message window') ],
1774 [ new_text_box_for_int (\%pr::win, 'msgs_x', \%HPVALUE) ],
1775 [ new_text_box_for_int (\%pr::win, 'msgs_y', \%HPVALUE) ],
1776 [ new_text_box_for_int (\%pr::win, 'msgs_w', \%HPVALUE),
1777 new_text_box_for_int (\%pr::win, 'msgs_h', \%HPVALUE) ]
1781 sub new_winpos_subpage_sendrecv() {
1782 return new_grid_pack (3, 5, [
1783 [ _('Send window') ],
1784 [ new_text_box_for_int (\%pr::win, 'send_w', \%HPVALUE),
1785 new_text_box_for_int (\%pr::win, 'send_h', \%HPVALUE) ],
1787 [ _('Receive window') ],
1788 [ new_text_box_for_int (\%pr::win, 'recv_w', \%HPVALUE),
1789 new_text_box_for_int (\%pr::win, 'recv_h', \%HPVALUE) ]
1793 sub new_winpos_subpage_fold() {
1794 return new_grid_pack (3, 7, [
1795 [ _('Folder window') ],
1796 [ new_text_box_for_int (\%pr::win, 'fold_x', \%HPVALUE) ],
1797 [ new_text_box_for_int (\%pr::win, 'fold_y', \%HPVALUE) ],
1798 [ new_text_box_for_int (\%pr::win, 'fold_w', \%HPVALUE),
1799 new_text_box_for_int (\%pr::win, 'fold_h', \%HPVALUE) ],
1801 [ _('Folder selection window') ],
1802 [ new_text_box_for_int (\%pr::win, 'fsel_w', \%HPVALUE),
1803 new_text_box_for_int (\%pr::win, 'fsel_h', \%HPVALUE) ]
1807 sub new_winpos_subpage_addrbook() {
1808 return new_grid_pack (3, 14, [
1809 [ _('Addressbook main window') ],
1810 [ new_text_box_for_int (\%pr::win, 'addr_w', \%HPVALUE),
1811 new_text_box_for_int (\%pr::win, 'addr_h', \%HPVALUE) ],
1813 [ _('Edit person window') ],
1814 [ new_text_box_for_int (\%pr::win, 'adep_w', \%HPVALUE),
1815 new_text_box_for_int (\%pr::win, 'adep_h', \%HPVALUE) ],
1817 [ _('Edit group window') ],
1818 [ new_text_box_for_int (\%pr::win, 'adeg_w', \%HPVALUE),
1819 new_text_box_for_int (\%pr::win, 'adeg_h', \%HPVALUE) ],
1821 [ _('Add address window') ],
1822 [ new_text_box_for_int (\%pr::win, 'adda_w', \%HPVALUE),
1823 new_text_box_for_int (\%pr::win, 'adda_h', \%HPVALUE) ],
1825 [ _('Folder select window') ],
1826 [ new_text_box_for_int (\%pr::win, 'addf_w', \%HPVALUE),
1827 new_text_box_for_int (\%pr::win, 'addf_h', \%HPVALUE) ]
1831 sub new_winpos_subpage_accounts() {
1832 return new_grid_pack (3, 5, [
1833 [ _('Accounts window') ],
1834 [ new_text_box_for_int (\%pr::win, 'acco_w', \%HPVALUE),
1835 new_text_box_for_int (\%pr::win, 'acco_h', \%HPVALUE) ],
1837 [ _('Edit account window') ],
1838 [ new_text_box_for_int (\%pr::win, 'acce_w', \%HPVALUE),
1839 new_text_box_for_int (\%pr::win, 'acce_h', \%HPVALUE) ]
1843 sub new_winpos_subpage_filtering() {
1844 return new_grid_pack (3, 11, [
1845 [ _('Filtering window') ],
1846 [ new_text_box_for_int (\%pr::win, 'filt_w', \%HPVALUE),
1847 new_text_box_for_int (\%pr::win, 'filt_h', \%HPVALUE) ],
1849 [ _('Filtering actions window') ],
1850 [ new_text_box_for_int (\%pr::win, 'fila_w', \%HPVALUE),
1851 new_text_box_for_int (\%pr::win, 'fila_h', \%HPVALUE) ],
1853 [ _('Filtering debug window') ],
1854 [ new_text_box_for_int (\%pr::win, 'fild_w', \%HPVALUE),
1855 new_text_box_for_int (\%pr::win, 'fild_h', \%HPVALUE) ],
1857 [ ('Matcher window') ],
1858 [ new_text_box_for_int (\%pr::win, 'matc_w', \%HPVALUE),
1859 new_text_box_for_int (\%pr::win, 'matc_h', \%HPVALUE) ]
1864 sub new_winpos_subpage_useractions() {
1865 return new_grid_pack (3, 5, [
1866 [ _('User Actions prefs window') ],
1867 [ new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
1868 new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE) ],
1870 [ _('User Actions I/O window') ],
1871 [ new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
1872 new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE) ]
1876 sub new_winpos_subpage_prefs() {
1877 return new_grid_pack (3, 11, [
1878 [ _('Preferences window') ],
1879 [ new_text_box_for_int (\%pr::win, 'pref_w', \%HPVALUE),
1880 new_text_box_for_int (\%pr::win, 'pref_h', \%HPVALUE) ],
1882 [ _('Templates window') ],
1883 [ new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
1884 new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE) ],
1886 [ _('Tags window') ],
1887 [ new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
1888 new_text_box_for_int (\%pr::win, 'tags_h', \%HPVALUE) ],
1890 [ _('Plugins window') ],
1891 [ new_text_box_for_int (\%pr::win, 'plug_w', \%HPVALUE),
1892 new_text_box_for_int (\%pr::win, 'plug_h', \%HPVALUE) ]
1896 sub new_winpos_subpage_misc() {
1897 return new_grid_pack (3, 8, [
1898 [ _('Log window') ],
1899 [ new_text_box_for_int (\%pr::win, 'logw_w', \%HPVALUE),
1900 new_text_box_for_int (\%pr::win, 'logw_h', \%HPVALUE) ],
1902 [ _('Print preview window') ],
1903 [ new_text_box_for_int (\%pr::win, 'prin_w', \%HPVALUE),
1904 new_text_box_for_int (\%pr::win, 'prin_h', \%HPVALUE) ],
1906 [ _('View source window') ],
1907 [ new_text_box_for_int (\%pr::win, 'sour_w', \%HPVALUE),
1908 new_text_box_for_int (\%pr::win, 'sour_h', \%HPVALUE) ]
1912 sub new_winpos_page() {
1913 my $winbook = Gtk3::Notebook->new;
1914 $winbook->set_tab_pos ('right');
1915 $winbook->append_page (&new_winpos_subpage_main, new_label (_('Main')));
1916 $winbook->append_page (&new_winpos_subpage_msgs, new_label (_('Message')));
1917 $winbook->append_page (&new_winpos_subpage_sendrecv, new_label (_('Send/Receive')));
1918 $winbook->append_page (&new_winpos_subpage_fold, new_label (_('Folder')));
1919 $winbook->append_page (&new_winpos_subpage_addrbook, new_label (_('Addressbook')));
1920 $winbook->append_page (&new_winpos_subpage_accounts, new_label (_('Accounts')));
1921 $winbook->append_page (&new_winpos_subpage_filtering, new_label (_('Filtering')));
1922 $winbook->append_page (&new_winpos_subpage_useractions, new_label (_('User Actions')));
1923 $winbook->append_page (&new_winpos_subpage_prefs, new_label (_('Preferences')));
1924 $winbook->append_page (&new_winpos_subpage_misc, new_label (_('Other')));
1928 %pr::acc = ( # per account hidden preferences
1930 'gnutls_set_priority',
1931 $xl::s{l_acc_gtls_set},
1932 $xl::s{h_acc_gtls_set},
1939 $xl::s{l_acc_gtls_pri},
1940 $xl::s{h_acc_gtls_pri},
1947 sub new_account_subpage($) {
1949 return new_grid_pack (1, 3, [
1950 [ _('GnuTLS priority') ],
1951 [ new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}) ],
1952 [ new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey}) ]
1956 sub new_accounts_page() {
1957 my $accbook = Gtk3::Notebook->new;
1958 $accbook->set_tab_pos ('right');
1960 $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
1963 my $name = $ACPREFS{$_}{'account_name'};
1964 my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
1965 my $page = new_account_subpage ($_);
1966 my $label = new_label ($isdef? '<u>' . $name . '</u>': $name);
1967 $label->set_use_markup (TRUE);
1968 $accbook->append_page ($page, $label);
1970 $accbook->set_scrollable (TRUE);
1974 %pr::plu = ( # plugins hidden preferences
1980 'int,0,3000', # 0 pixels - 3000 pixels
1989 'int,0,3000', # 0 pixels - 3000 pixels
1996 'autocompletion_limit',
1997 $xl::s{l_plu_gpg_alimit},
1998 $xl::s{h_plu_gpg_alimit},
2006 'manager_win_width',
2009 'int,0,3000', # 0 pixels - 3000 pixels
2015 'manager_win_height',
2018 'int,0,3000', # 0 pixels - 3000 pixels
2026 $xl::s{l_plu_lav_burl},
2027 $xl::s{h_plu_lav_burl},
2030 'http://cdn.libravatar.org/avatar',
2035 'filter_log_verbosity',
2036 $xl::s{l_plu_prl_flvb},
2037 $xl::s{h_plu_prl_flvb},
2038 '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2045 sub new_plugins_page() {
2048 new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2049 new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})
2052 new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})
2055 new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2056 new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})
2059 new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'})
2062 new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})
2065 foreach my $pk (@PLUGINS) {
2066 foreach my $wg (@{$widget{$pk}}) {
2067 $wg->set_sensitive (defined $PLHPVALUE{$pk});
2070 return new_grid_pack (3, 14, [
2071 [ _('Attachment remover') ], $widget{'AttRemover'}, [ '--' ],
2072 [ _('GPG') ], $widget{'GPG'}, [ '--' ],
2073 [ _('Sieve manager') ], $widget{'ManageSieve'}, [ '--' ],
2074 [ _('Libravatar') ], $widget{'Libravatar'}, [ '--' ],
2075 [ _('Perl') ], $widget{'PerlPlugin'}
2087 sub new_hotkeys_list_label {
2088 my $renderer = Gtk3::CellRendererText->new ();
2089 $renderer->set_property('alignment' => 'left');
2090 $renderer->set_property('editable' => FALSE);
2094 sub new_hotkeys_list_hotkey {
2095 my $renderer = Gtk3::CellRendererAccel->new ();
2096 $renderer->set_property ('accel-mode' => 'gtk');
2097 $renderer->set_property ('editable' => TRUE);
2098 $renderer->signal_connect ('accel-edited' => sub {
2099 my ($w, $path, $key, $mods, $keycode) = @_;
2100 my $accel = Gtk3::Accelerator->name ($key, $mods);
2101 my ($model, $iter) = $SELHOTKEY->get_selected ();
2102 $model->set($iter, C_HOTKEY, "\"$accel\"");
2103 my $gkey = $model->get_value ($iter, C_GROUP);
2104 my $akey = $model->get_value ($iter, C_ACCEL);
2105 my $data = $HOTKEYS->{$gkey}->{$akey};
2106 $data->{'key'} = "\"$accel\"";
2107 $data->{'enabled'} = TRUE;
2109 $renderer->signal_connect ('accel-cleared' => sub {
2110 my ($w, $path) = @_;
2111 my ($model, $iter) = $SELHOTKEY->get_selected ();
2112 $model->set($iter, C_HOTKEY, "\"\"");
2113 my $gkey = $model->get_value ($iter, C_GROUP);
2114 my $akey = $model->get_value ($iter, C_ACCEL);
2115 my $data = $HOTKEYS->{$gkey}->{$akey};
2116 $data->{'key'} = "\"\"";
2117 $data->{'enabled'} = FALSE;
2122 sub row_background_color {
2123 my ($column, $isodd) = @_;
2124 my $treeview = $column->get_tree_view;
2125 my $stylectx = $treeview->get_style_context;
2127 ? $stylectx->get_background_color ('normal')
2128 : $stylectx->get_background_color ('insensitive');
2131 sub new_hotkeys_list {
2132 my ($gkey, $group) = @_;
2133 my $store = Gtk3::ListStore->new(
2134 qw/Glib::String Glib::String Glib::String Glib::String Glib::Boolean/);
2136 foreach my $akey (sort keys %$group) {
2137 my $iter = $store->append ();
2138 my $hotkey = $group->{$akey}->{'key'};
2140 $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2141 $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2142 C_GROUP, $gkey, C_ACCEL, $akey, C_ODDITY, $even);
2145 my $treeview = Gtk3::TreeView->new_with_model ($store);
2147 $treeview->insert_column_with_data_func (
2148 0, _("Menu path"), new_hotkeys_list_label (),
2150 my ($col, $renderer, $model, $iter, $data) = @_;
2151 $renderer->set_property (
2152 'markup' => '<span size="smaller">'
2153 . $model->get_value ($iter, C_LABEL)
2155 my $bgcol = row_background_color (
2156 $col, $model->get_value ($iter, C_ODDITY));
2157 $renderer->set_property ('cell-background-rgba' => $bgcol);
2161 $treeview->insert_column_with_data_func (
2162 1, _('Hotkey'), new_hotkeys_list_hotkey (),
2164 my ($col, $renderer, $model, $iter, $data) = @_;
2165 my $hkey = $model->get_value ($iter, C_HOTKEY);
2167 my ($acckey, $accmod) = Gtk3::accelerator_parse ($hkey);
2168 $renderer->set_property ('accel-key' => $acckey);
2169 $renderer->set_property ('accel-mods' => $accmod);
2170 my $bgcol = row_background_color (
2171 $col, $model->get_value ($iter, C_ODDITY));
2172 $renderer->set_property ('cell-background-rgba' => $bgcol);
2175 # callback for saving current selection
2176 my $selection = $treeview->get_selection ();
2177 $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2181 sub new_hotkeys_page() {
2182 my $swin = Gtk3::ScrolledWindow->new ();
2183 my $vbox = Gtk3::VBox->new (FALSE, 5);
2184 foreach my $gkey (sort keys %$HOTKEYS) {
2185 my $group = $HOTKEYS->{$gkey};
2187 my $glabel = Gtk3::Label->new ('<b>' . $gkey . '</b>');
2188 $glabel->set_use_markup (TRUE);
2189 $glabel->set_alignment (0, 0.5);
2190 $glabel->set_padding (5, 1);
2191 $vbox->pack_start ($glabel, FALSE, FALSE, 0);
2193 my $keylist = new_hotkeys_list ($gkey, $group);
2194 $vbox->pack_start ($keylist, FALSE, FALSE, 0);
2196 $swin->set_border_width (5);
2197 $swin->set_shadow_type ('none');
2198 $swin->set_policy ('automatic', 'always');
2199 $swin->add_with_viewport ($vbox);
2203 sub new_info_page() {
2204 my $v = get_toolkit_versions ();
2205 my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2207 return new_grid_pack (4, 11, [
2208 [ _('Library versions') ],
2209 [ new_label ('Perl-GLib'), new_title ($v->{'glib'}) ],
2210 [ new_label (_('GLib runtime')), new_title ($v->{'glib-r'}) ],
2211 [ new_label (_('GLib built')), new_title ($v->{'glib-b'}) ],
2212 [ new_label ('Perl-GTK3'), new_title ($v->{'gtk'}) ],
2213 [ new_label (_('GTK3 runtime')), new_title ($v->{'gtk-r'}) ],
2214 [ new_label (_('GTK3 built')), new_title ($v->{'gtk-b'}) ],
2216 [ _('Claws Mail versions') ],
2217 [ new_label (_('Binary')), new_title ($CLAWSV) ],
2218 [ new_label (_('Configuration')), new_title ($cfgv) ]
2223 sub get_toolkit_versions {
2225 $versions{'glib'} = $Glib::VERSION;
2226 # version info stuff appeared in 1.040
2227 if ($Glib::VERSION >= 1.040) {
2228 $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2229 $versions{'glib-r'} = join('.',
2230 &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2232 $versions{'gtk'} = $Gtk3::VERSION;
2233 if ($Gtk3::VERSION >= 0.034) {
2234 $versions{'gtk-b'} = &Gtk3::GET_VERSION_INFO
2236 $versions{'gtk-b'} = _('Not available')
2238 $versions{'gtk-r'} = join('.',
2239 &Gtk3::get_major_version, &Gtk3::get_minor_version, &Gtk3::get_micro_version);
2243 sub print_version() {
2244 print $xl::s{about_title} . "\n";
2245 print $xl::s{about_version} . " $VERSION\n";
2246 my $v = get_toolkit_versions ();
2247 if ($v->{'glib-b'}) {
2248 print _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2249 glibv => $v->{'glib'},
2250 glibb => $v->{'glib-b'},
2251 glibr => $v->{'glib-r'});
2253 print _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2256 if ($v->{'gtk-b'}) {
2257 print _("Perl-GTK3 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2258 gtkv => $v->{'gtk'},
2259 gtkb => $v->{'gtk-b'},
2260 gtkr => $v->{'gtk-r'});
2262 print _("Perl-GTK3 version {gtkv}.", gtkv => $v->{'gtk'});
2265 my $clawsver = ($CLAWSV eq "") ?
2266 _("Claws Mail was not found!") :
2267 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2268 print $clawsver . "\n";
2271 # the command line help
2273 my $line = '-' x length ($xl::s{about_title});
2275 say $xl::s{about_title};
2279 _(" clawsker [options]"),
2281 _(" -a|--alternate-config-dir <dir> Uses <dir> as Claws Mail configuration."),
2282 _(" -b|--verbose More messages on standard output."),
2283 _(" -c|--clawsrc <file> Uses <file> as full resource name."),
2284 _(" -h|--help Prints this help screen and exits."),
2285 _(" -r|--read-only Disables writing changes to disk."),
2286 _(" -v|--version Prints version information and exits.")
2288 foreach (@help) { say $_ }
2291 sub parse_command_line {
2293 $CLAWSV = get_claws_version ();
2295 GetOptions('h|help' => sub { print_help (); $cont = FALSE },
2296 'v|version' => sub { print_version (); $cont = FALSE },
2297 'b|verbose' => sub { $VERBOSE = TRUE },
2298 'r|read-only' => sub { $READONLY = TRUE },
2299 'u|use-claws-version=s' => \&opt_use_claws_version,
2300 'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2301 'c|clawsrc=s' => \&opt_clawsrc)
2302 or die _("try -h or --help for syntax.\n");
2305 my $msg = _("Error in options: {msg}\n", msg => $@);
2306 if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2307 eval { Gtk3->init };
2308 error_dialog ($msg) unless $@;
2315 sub opt_use_claws_version {
2316 my ($name, $value) = @_;
2317 die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2318 unless ($value =~ /^[\d\.]+$/);
2322 sub opt_alternate_config_dir {
2323 my ($name, $value) = @_;
2324 die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2326 $CONFIGDIR = $value;
2327 $CONFIGDIR .= "/" unless ($CONFIGDIR =~ /.*\/$/);
2328 $ALTCONFIGDIR = TRUE;
2332 my ($name, $value) = @_;
2333 die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2335 set_rc_filename ($value);
2338 # update the hidden preferences status from loaded values
2339 sub init_hidden_preferences {
2340 foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2341 foreach my $key (keys %$hash) {
2342 $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2343 $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2346 foreach my $akey (keys %ACPREFS) {
2347 foreach my $key (keys %pr::acc) {
2348 my $pname = $pr::acc{$key}[NAME];
2349 $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2350 $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2353 foreach my $key (keys %pr::plu) {
2354 my $plugin = $pr::plu{$key}[PLUGIN];
2355 my $pname = $pr::plu{$key}[NAME];
2356 if (defined $PLPREFS{$plugin}) {
2357 $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2358 $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2364 # generic load/save resource files
2370 open (RCF, '<:encoding(utf8)', $rc)
2371 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2372 my $section = '_'; # default unnamed section
2377 if (/^\[([^\]]+)\]$/) { # new section
2379 die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2380 sect => $section, file => $rc) if ($data{$section});
2381 $data{$section} = {};
2382 $meta{$section}{'#'} = $line;
2384 elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2385 $data{$section}{$1} = $2;
2386 $meta{$section}{$1} = $line;
2388 elsif (/^(.*)$/) { # lone value
2389 push (@{$data{$section}{'_'}}, $1);
2393 return (\%data, \%meta);
2397 my ($rc, $data, $meta) = @_;
2398 open (RCF, '>:utf8', $rc)
2399 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2400 my @sections = keys %$data;
2401 if (defined $meta) {
2403 $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2406 foreach my $section (@sections) {
2407 say RCF "[$section]";
2408 if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2409 foreach my $val (@{$data->{$section}{'_'}}) {
2413 my @keys = keys %{$data->{$section}};
2414 if (defined $meta) {
2416 $meta->{$section}{$a} <=> $meta->{$section}{$b}
2419 foreach my $key (@keys) {
2420 my $val = $data->{$section}{$key};
2421 say RCF "$key=$val";
2429 sub backup_resource {
2431 my $rcbak = "$rc.backup";
2433 my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2434 log_message ($emsg);
2435 error_dialog ($emsg);
2437 } unless rename ($rc, $rcbak);
2444 open (RCF, '<:encoding(utf8)', $rc)
2445 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2450 if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2451 my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2452 $groups{$1}{$2} = \%data;
2453 # say "group -> $1 | path -> $2 | key -> $3";
2454 } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2455 my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2456 $groups{$1}{$2} = \%data;
2457 # say "group -> $1 | path -> $2 | key -> $3";
2466 my ($rc, $groups) = @_;
2468 foreach my $gkey (keys %$groups) {
2469 my $group = $groups->{$gkey};
2470 foreach my $akey (keys %$group) {
2471 my $data = $group->{$akey};
2472 my $key = $data->{'key'};
2473 my $line = $data->{'line'};
2474 $lines[$line] = ($data->{'enabled'})? '': '; ';
2475 $lines[$line] .= '(gtk_accel_path "<'
2476 . $gkey . '>' . $akey . '" ' . $key . ')';
2479 open (RCF, '>:utf8', $rc)
2480 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2481 say RCF '; claws-mail GtkAccelMap rc-file -*- scheme -*-';
2482 say RCF '; this file is an automated accelerator map dump';
2484 foreach (@lines) { say RCF $_ if $_ }
2488 # load current status from disc
2489 sub load_rc_preferences {
2490 my $rc = get_rc_filename ();
2491 log_message ("Loading preferences from $rc\n");
2492 return FALSE unless check_rc_file ($rc);
2493 ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2494 foreach (keys %{$CONFIGDATA->{'Common'}}) {
2495 $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2497 foreach my $plugin (@PLUGINS) {
2498 if (defined $CONFIGDATA->{$plugin}) {
2499 push (@AVPLUGINS, $plugin);
2500 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2501 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2508 sub load_ac_preferences {
2509 my $rc = get_ac_rc_filename ();
2510 log_message ("Loading account preferences from $rc\n");
2511 return FALSE unless check_rc_file ($rc);
2512 ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2513 foreach my $asect (keys %$ACCOUNTDATA) {
2514 if ($asect =~ /^Account: (\d+)$/) {
2515 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2516 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2523 sub load_hk_preferences {
2524 my $rc = get_menurc_filename ();
2525 return FALSE unless check_rc_file ($rc);
2526 $HOTKEYS = load_menurc ($rc);
2530 sub load_preferences {
2531 return FALSE unless check_claws_not_running ();
2532 return (load_rc_preferences ()
2533 and load_ac_preferences ()
2534 and load_hk_preferences ()
2538 # save current preferences to disc
2539 sub save_rc_preferences {
2540 my $rc = get_rc_filename ();
2541 log_message ("Saving preferences to $rc\n");
2542 return FALSE unless check_rc_file ($rc);
2543 return FALSE unless check_claws_not_running ();
2544 return FALSE unless backup_resource ($rc);
2545 foreach (keys %PREFS) {
2546 if (defined $HPVALUE{$_}) {
2547 $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2550 foreach my $plugin (@AVPLUGINS) {
2551 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2552 if (defined $PLHPVALUE{$plugin}{$_}) {
2553 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2557 save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2561 sub save_ac_preferences {
2562 my $rc = get_ac_rc_filename ();
2563 log_message ("Saving account preferences to $rc\n");
2564 return FALSE unless check_rc_file ($rc);
2565 return FALSE unless check_claws_not_running ();
2566 return FALSE unless backup_resource ($rc);
2567 foreach my $asect (keys %$ACCOUNTDATA) {
2568 if ($asect =~ /^Account: (\d+)$/) {
2569 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2570 if (defined $ACHPVALUE{$1}{$_}) {
2571 $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2576 save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2580 sub save_hk_preferences {
2581 my $rc = get_menurc_filename ();
2582 log_message ("Saving hotkey preferences to $rc\n");
2583 return FALSE unless check_rc_file ($rc);
2584 return FALSE unless check_claws_not_running ();
2585 return FALSE unless backup_resource ($rc);
2586 save_menurc ($rc, $HOTKEYS);
2590 sub save_preferences {
2591 my $result = save_rc_preferences ()
2592 and save_ac_preferences ()
2593 and save_hk_preferences ();
2594 $MODIFIED = 0 if $result;
2600 my $nb = Gtk3::Notebook->new;
2602 $nb->append_page (&new_behaviour_page, Gtk3::Label->new (_('Behaviour')));
2603 $nb->append_page (&new_colours_page, Gtk3::Label->new (_('Colours')));
2604 $nb->append_page (&new_gui_page, Gtk3::Label->new (_('GUI')));
2605 $nb->append_page (&new_other_page, Gtk3::Label->new (_('Other')));
2606 $nb->append_page (&new_winpos_page, Gtk3::Label->new (_('Windows')));
2607 $nb->append_page (&new_accounts_page, Gtk3::Label->new (_('Accounts')));
2608 $nb->append_page (&new_plugins_page, Gtk3::Label->new (_('Plugins')));
2609 $nb->append_page (&new_hotkeys_page, Gtk3::Label->new (_('Hotkeys')));
2610 $nb->append_page (&new_info_page, Gtk3::Label->new (_('Info')));
2615 # create an about dialog
2616 sub new_about_dialog {
2618 my $year = '2007-2018';
2619 my $holder = 'Ricardo Mones <ricardo@mones.org>';
2620 my $url = 'http://www.claws-mail.org/clawsker.php';
2621 my $icons = &get_app_icons;
2623 my $dialog = Gtk3::AboutDialog->new;
2624 $dialog->set_transient_for ($parent);
2625 $dialog->set_program_name ('Clawsker');
2626 $dialog->set_version ($VERSION);
2627 $dialog->set_copyright ("Copyright © $year $holder");
2628 $dialog->set_license_type ('gpl-3-0');
2629 $dialog->set_website ($url);
2630 $dialog->set_website_label ($xl::s{about_web_label});
2631 # committers, by number of commits
2632 $dialog->set_authors ([
2634 'Tristan Chabredier (wwp) <subscript@free.fr>',
2635 'Andreas Rönnquist <andreas@ronnquist.net>',
2636 'Christian Hesse <mail@eworm.de>',
2638 $dialog->set_artists ([
2639 'Jesper Schultz <jesper@schultz-net.dk>',
2642 $dialog->set_documenters ([
2644 'Paul Mangan <paul@claws-mail.org>',
2646 # active translators, in alphabetical order
2647 $dialog->set_translator_credits (join ("\n",
2648 'Andreas Rönnquist <andreas@ronnquist.net>',
2649 'Axel Köllhofer <AxelKoellhofer@web.de>',
2650 'David Medina <opensusecatala@gmail.com>',
2651 'Erik P. Olsen <erik@epo.dk>',
2652 'Frederico Goncalves Guimaraes <frederico@teia.bio.br>',
2653 'Marcel Pol <marcel@timelord.nl>',
2654 'Mark Chang <mark.cyj@gmail.com>',
2655 'M. Sulchan Darmawan <bleketux@gmail.com>',
2656 'Numan Demirdöğen <if.gnu.linux@gmail.com>',
2657 'Petter Adsen <petter@synth.no>',
2659 'Tristan Chabredier (wwp) <subscript@free.fr>',
2661 $dialog->set_title ($xl::s{about_title});
2662 $dialog->set_logo ($icons->[-1]);
2669 if ($MODIFIED != 0 and not $READONLY) {
2670 my $markup = "<span>" . $xl::s{exit_fact} . "</span>\n\n"
2671 . "<span weight=\"bold\">" . $xl::s{exit_question} . "</span>\n";
2672 my $dialog = message_dialog (
2673 $parent, $xl::s{exit_title}, $markup, 'question',
2674 [ 'gtk-no', 1, 'gtk-yes', 0 ]
2676 my $resp = $dialog->run;
2678 return TRUE if ($resp == 1);
2683 # create buttons box
2684 sub new_button_box {
2685 my ($parent, $adlg) = @_;
2686 my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2687 my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2688 my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2689 # disable button until is really implemented
2690 # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2691 my $hbox = Gtk3::HBox->new (FALSE, 5);
2693 $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2694 $b_apply->set_sensitive (not $READONLY);
2695 $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2696 # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2697 $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2699 $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2700 $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2701 # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2702 $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2708 return \@APPICONS if (@APPICONS);
2710 if (-d $DATADIR) { # installed
2711 my $dir = $DATADIR . '/icons/hicolor';
2713 join ('/', ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png'))
2715 } else { # unpacked tarball or git clone
2717 join ('/', ('./icons', $NAME . '-' . $_ . '.png'));
2722 $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2723 push @APPICONS, $icon if ($icon);
2728 sub escape_key_handler {
2729 my ($widget, $event) = @_;
2730 if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {
2731 exit_handler($widget);
2736 exit unless parse_command_line ();
2738 $main_window = Gtk3::Window->new ('toplevel');
2739 exit unless load_preferences ();
2740 exit unless init_hidden_preferences ();
2742 my $box = Gtk3::VBox->new (FALSE, 5);
2743 $box->set_border_width(3);
2744 my $about = new_about_dialog ($main_window);
2745 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2746 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2747 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2748 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2749 $main_window->set_title ($xl::s{win_title});
2750 $main_window->set_icon_list (get_app_icons ());
2751 $main_window->add ($box);
2752 $main_window->show_all;