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.
19 use Glib qw(TRUE FALSE);
22 use File::Spec::Functions;
23 use POSIX qw(setlocale);
26 use Digest::MD5 qw(md5_hex);
27 use Getopt::Long qw(GetOptionsFromArray);
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;
40 binmode STDOUT, ":encoding(utf8)";
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'));
51 my $xla = gettext ($str);
52 if (scalar(keys(%par)) > 0) {
53 foreach my $key (keys %par) {
54 $xla =~ s/\{$key\}/$par{$key}/g;
57 return decode_utf8($xla);
62 about_title => _('Clawsker :: A Claws Mail Tweaker'),
64 l_oth_use_dlg => _('Use detached address book edit dialogue'),
65 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.'),
66 l_oth_max_use => _('Maximum memory for message cache'),
67 l_oth_max_use_units => _('kilobytes'),
68 h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kilobytes.'),
69 l_oth_min_time => _('Minimun time for cache elements'),
70 l_oth_min_time_units => _('minutes'),
71 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.'),
72 l_oth_use_netm => _('Use NetworkManager'),
73 h_oth_use_netm => _('Use NetworkManager to switch offline automatically.'),
74 l_oth_mp_rounds => _('Rounds for PBKDF2 function'),
75 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.'),
77 l_gui_b_unread => _('Show unread messages with bold font'),
78 h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'),
79 l_gui_no_markup => _('Don\'t use markup'),
80 h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'),
81 l_gui_dot_lines => _('Use dotted lines in tree view components'),
82 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.'),
83 l_gui_h_scroll => _('Enable horizontal scrollbar'),
84 h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'),
85 l_gui_swp_from => _('Display To column instead From column in Sent folder'),
86 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.'),
87 l_gui_v_scroll => _('Folder List scrollbar behaviour'),
88 h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'),
89 l_gui_v_scroll_show => _('Show always'),
90 l_gui_v_scroll_auto => _('Automatic'),
91 l_gui_v_scroll_hide => _('Hide always'),
92 l_gui_from_show => _('From column displays'),
93 h_gui_from_show => _('Selects the data displayed in the From column of the Message List: name, address or both.'),
94 l_gui_from_show_name => _('Name only'),
95 l_gui_from_show_addr => _('Address only'),
96 l_gui_from_show_both => _('Both name and address'),
97 l_gui_strip_off => _('Coloured lines contrast'),
98 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.'),
99 l_gui_cursor_v => _('Show cursor'),
100 h_gui_cursor_v => _('Display the cursor in the Message View.'),
101 l_gui_toolbar_d => _('Detachable toolbars'),
102 h_gui_toolbar_d => _('Show handles in the toolbars.'),
103 l_gui_strip_all => _('Use stripes in all tree view components'),
104 h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'),
105 l_gui_strip_sum => _('Use stripes in Folder List and Message List'),
106 h_gui_strip_sum => _('Enable alternately coloured lines in Message List and Folder List.'),
107 l_gui_two_line_v => _('2 lines per Message List item in 3-column layout'),
108 h_gui_two_line_v => _('Spread Message List information over two lines when using the three column mode.'),
109 l_gui_margin_co => _('Show margin'),
110 h_gui_margin_co => _('Shows a small margin in the Compose View.'),
111 l_gui_mview_date => _('Don\'t display localized date'),
112 h_gui_mview_date => _('Toggles localization of date format in Message View.'),
113 l_gui_zero_char => _('Zero replacement character'),
114 h_gui_zero_char => _('Replaces \'0\' with the given character in Folder List.'),
115 l_gui_type_any => _('Editable headers'),
116 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.'),
117 l_gui_warn_send_multi => _('Warn when sending to more than'),
118 l_gui_warn_send_multi_units => _('recipients'),
119 h_gui_warn_send_multi => _('Show a warning dialogue when sending to more recipients than specified. Use 0 to disable this check.'),
120 l_gui_next_del => _('Select next message on delete'),
121 h_gui_next_del => _('When deleting a message, toggles between selecting the next one (newer message) or the previous one (older message).'),
123 l_beh_hover_t => _('Drag \'n\' drop hover timeout'),
124 l_beh_hover_t_units => _('milliseconds'),
125 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.'),
126 l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
127 h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
128 l_beh_flowed => _('Respect format=flowed in messages'),
129 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.'),
130 l_beh_parts_rw => _('Allow writable temporary files'),
131 h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
132 l_beh_skip_ssl => _('Don\'t check SSL certificates'),
133 h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
134 l_beh_up_step => _('Progress bar update step every'),
135 l_beh_up_step_units => _('items'),
136 h_beh_up_step => _('Update stepping in progress bars.'),
137 l_beh_thread_a => _('Maximum age when threading by subject'),
138 l_beh_thread_a_units => _('days'),
139 h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
140 l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
141 h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
142 l_beh_use_utf8 => _('Force UTF-8 for broken mails'),
143 h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'),
144 l_beh_warn_dnd => _('Warn on drag \'n\' drop'),
145 h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'),
146 l_beh_out_ascii => _('Outgoing messages fallback to ASCII'),
147 h_beh_out_ascii => _('If allowed by content, ASCII will be used to encode outgoing messages, otherwise the user-defined encoding is always enforced.'),
148 l_beh_pp_unsel => _('Primary paste unselects selection'),
149 h_beh_pp_unsel => _('Controls how pasting using middle-click changes the selected text and insertion point.'),
150 l_beh_inline_at => _('Show inline attachments'),
151 h_beh_inline_at => _('Allows to hide inline attachments already shown in mail structure view.'),
152 l_beh_addr_swc => _('Address search in compose window matches any'),
153 h_beh_addr_swc => _('On Tab-key completion, address text will match any part of the string or only from the start.'),
154 l_beh_fold_swc => _('Folder search in folder selector matches any'),
155 h_beh_fold_swc => _('On folder name completion text will match any part of the string or only from the start.'),
156 l_beh_rewrite_ff => _('Rewrite first \'From\' using QP encoding'),
157 h_beh_rewrite_ff => _('Workaround some servers which convert first \'From\' to \'>From\' by using Quoted-Printable transfer encoding instead of 7bit/8bit encoding.'),
159 l_col_emphasis => _('X-Mailer header'),
160 h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'),
161 l_col_log_err => _('Error messages'),
162 h_col_log_err => _('Colour for error messages in log window.'),
163 l_col_log_in => _('Server messages'),
164 h_col_log_in => _('Colour for messages received from servers in log window.'),
165 l_col_log_msg => _('Standard messages'),
166 h_col_log_msg => _('Colour for messages in log window.'),
167 l_col_log_out => _('Client messages'),
168 h_col_log_out => _('Colour for messages sent to servers in log window.'),
169 l_col_log_warn => _('Warning messages'),
170 h_col_log_warn => _('Colour for warning messages in log window.'),
172 l_col_tags_bg => _('Tags background'),
173 h_col_tags_bg => _('Background colour for tags in message view.'),
174 l_col_tags_text => _('Tags text'),
175 h_col_tags_text => _('Text colour for tags in message view.'),
177 l_col_default_header_bg => _('Default headers background'),
178 h_col_default_header_bg => _('Background colour for default headers in compose window.'),
179 l_col_default_header_text => _('Default headers text'),
180 h_col_default_header_text => _('Text colour for default headers in compose window.'),
182 l_col_qs_active_bg => _('Active quick search background'),
183 h_col_qs_active_bg => _('Background colour for active quick search.'),
184 l_col_qs_active_text => _('Active quick search text'),
185 h_col_qs_active_text => _('Text colour for active quick search.'),
186 l_col_qs_error_bg => _('Quick search error background'),
187 h_col_qs_error_bg => _('Background colour for quick search error.'),
188 l_col_qs_error_text => _('Quick search error text'),
189 h_col_qs_error_text => _('Text colour for quick search error.'),
191 l_col_diff_add => _('Added lines'),
192 h_col_diff_add => _('Colour for added lines in patches.'),
193 l_col_diff_del => _('Deleted lines'),
194 h_col_diff_del => _('Colour for deleted lines in patches.'),
195 l_col_diff_hunk => _('Hunk lines'),
196 h_col_diff_hunk => _('Colour for hunk headers in patches.'),
198 l_win_x => _('X position'),
199 h_win_x => _('X coordinate for window\'s top-left corner.'),
200 l_win_y => _('Y position'),
201 h_win_y => _('Y coordinate for window\'s top-left corner.'),
202 l_win_w => _('Width'),
203 h_win_w => _('Window\'s width in pixels.'),
204 l_win_h => _('Height'),
205 h_win_h => _('Window\'s height in pixels.'),
207 l_win_main_mx => _('Maximized'),
208 h_win_main_mx => _('Changes window maximized status.'),
209 l_win_main_fs => _('Full-screen'),
210 h_win_main_fs => _('Changes full screen status.'),
212 l_acc_gtls_set => _('Use custom GnuTLS priority'),
213 h_acc_gtls_set => _('Enables using user provided GnuTLS priority string.'),
214 l_acc_gtls_pri => _('GnuTLS priority'),
215 h_acc_gtls_pri => _('Value to use as GnuTLS priority string if custom priority check is enabled. Otherwise this value is ignored.'),
216 l_acc_tls_sni => _('Use TLS SNI extension'),
217 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.'),
219 l_plu_gpg_alimit => _('Autocompletion limit'),
220 h_plu_gpg_alimit => _('Limits the number of addresses obtained from keyring through autocompletion. Use 0 to get all matches.'),
221 l_plu_lav_burl => _('Base URL'),
222 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.'),
223 l_plu_prl_flvb => _('Log level'),
224 h_plu_prl_flvb => _('Verbosity level of log, accumulative.'),
225 l_plu_prl_none => _('None'),
226 l_plu_prl_manual => _('Manual'),
227 l_plu_prl_action => _('Actions'),
228 l_plu_prl_match => _('Matches'),
231 # data and metadata of resource files
236 # all preferences read by load_preferences
240 # values of all preferences handled by clawsker
244 # default config dir and file name
245 my $ALTCONFIGDIR = FALSE;
246 my $CONFIGDIR = catdir ($ENV{HOME}, '.claws-mail');
247 my $CONFIGRC = 'clawsrc';
248 my $ACCOUNTRC = 'accountrc';
249 # supported and available plugins lists
250 my @PLUGINS = qw(AttRemover GPG ManageSieve Libravatar PerlPlugin);
252 # loaded hotkeys from load_menurc
254 # current tree selection
262 # index constants for preference arrays
263 NAME => 0, # the name on the rc file
264 LABEL => 1, # the label on the GUI
265 DESC => 2, # the description for the hint/help
266 TYPE => 3, # data type: bool, int, float, string, color
267 CMVER => 4, # lowest[,highest] Claws Mail version(s) the feature exists
268 CMDEF => 5, # default value for the preference in Claws Mail
269 PLUGIN => 6, # plugin section (only in plugin preferences)
270 # constants for GUI spacing
273 # for data references indexing
276 # hotkey list store columns
286 sub version_greater_or_equal {
287 my ($version, $refvers) = @_;
288 return TRUE if (length($version) == 0 and length($refvers) >= 0);
289 return FALSE if (length($version) >= 0 and length($refvers) == 0);
290 return TRUE if (version->parse($version) >= version->parse($refvers));
294 sub get_claws_version {
295 my $cm_path = which ('claws-mail') or return ""; # not found
296 open my $ph, "-|", $cm_path, "-v" or return ""; # no pipe
297 chomp (my $v = <$ph>);
299 # Claws Mail version 3.17.2git17
300 $v =~ m/\bversion\s+(\d[\w.]+)/ or die "Invalid version string: '$v'";
302 my @ver = split m/(?:\.|git)/, $cmv;
303 @ver < 4 and push @ver, 0;
304 return join ".", @ver;
307 # data handlers and auxiliar functions
309 sub handle_bool_value {
310 my ($widget, $event, $dataref) = @_;
311 $$dataref->[VALUE] = ($widget->get_active ())? '1': '0';
312 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
313 if defined $$dataref->[IVALUE];
316 sub handle_int_value {
317 my ($widget, $event, $dataref) = @_;
318 $_ = $widget->get_text ();
322 $$dataref->[VALUE] = $_;
323 $widget->set_text ($_);
326 $widget->set_text ($$dataref->[VALUE]);
328 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
329 if defined $$dataref->[IVALUE];
332 sub handle_nchar_value {
333 my ($widget, $event, $dataref, $minlen, $maxlen) = @_;
334 $_ = substr ($widget->get_text (), 0, $maxlen);
335 $widget->set_text ($_);
336 $$dataref->[VALUE] = $_;
337 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
338 if defined $$dataref->[IVALUE];
341 sub gdk_rgba_from_str {
343 my ($rr, $gg, $bb) = (0, 0 ,0);
345 if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) {
350 my $color = Gtk3::Gdk::RGBA->new ($rr, $gg, $bb, 1.0);
354 sub str_from_gdk_rgba {
356 my $rr = $color->red * 256;
357 my $gg = $color->green * 256;
358 my $bb = $color->blue * 256;
359 my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb);
363 sub handle_color_value {
364 my ($widget, $event, $dataref) = @_;
365 my $newcol = $widget->get_rgba;
366 $$dataref->[VALUE] = str_from_gdk_rgba ($newcol);
367 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
368 if defined $$dataref->[IVALUE];
371 sub handle_selection_value {
372 my ($widget, $event, $dataref) = @_;
373 $$dataref->[VALUE] = $widget->get_active;
374 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
375 if defined $$dataref->[IVALUE];
378 sub get_rc_filename {
379 return catfile ($CONFIGDIR, $CONFIGRC);
382 sub get_ac_rc_filename {
383 return catfile ($CONFIGDIR, $ACCOUNTRC);
386 sub get_menurc_filename {
387 return catfile ($CONFIGDIR, "menurc");
390 sub set_rc_filename {
392 my @parts = splitpath ($fullname);
393 $CONFIGRC = $parts[$#parts];
394 $parts[$#parts] = '';
395 $CONFIGDIR = catpath (@parts);
399 my ($mesg, $fatal) = @_;
400 if (defined($fatal) && $fatal eq 'die') {
401 die "$NAME: $mesg\n";
404 print "$NAME: $mesg\n";
409 my ($parent, $title, $markup, $type, $buttons) = @_;
410 my $flags = [qw/modal destroy-with-parent/];
411 my $dialog = Gtk3::Dialog->new_with_buttons (
412 $title, $parent, $flags, @$buttons
414 my $label = Gtk3::Label->new;
415 $label->set_markup ($markup);
417 if ($type eq 'error') {
418 $icon = Gtk3::Image->new_from_icon_name('dialog-error', 'GTK_ICON_SIZE_DIALOG');
419 } elsif ($type eq 'warning') {
420 $icon = Gtk3::Image->new_from_icon_name('dialog-warning', 'GTK_ICON_SIZE_DIALOG');
421 } elsif ($type eq 'question') {
422 $icon = Gtk3::Image->new_from_icon_name('dialog-question', 'GTK_ICON_SIZE_DIALOG');
424 my $hbox = Gtk3::Box->new ('horizontal', 5);
425 $hbox->pack_start ($icon, FALSE, FALSE, 5) if defined $icon;
426 $hbox->pack_start ($label, FALSE, FALSE, 5);
427 my $dialogbox = $dialog->get_content_area;
428 $dialogbox->add ($hbox);
429 $dialogbox->show_all;
435 my $markup = "<span weight=\"bold\" size=\"large\">" . $emsg . "</span>";
436 my $errordlg = message_dialog (
437 $main_window, _('Clawsker error'), $markup, 'error', [ 'gtk-cancel', 0 ]
443 sub claws_is_running {
444 my $emsg = _('Error: seems Claws Mail is currently running, close it first.');
446 error_dialog ($emsg);
450 sub check_claws_not_running {
451 return TRUE if $READONLY;
452 my $tmpdir = File::Spec->tmpdir ();
453 my $lockdir = catfile ($tmpdir, "claws-mail-$<");
457 my $socket = catfile ($lockdir, md5_hex ($_));
458 -S $socket and return claws_is_running ();
465 (defined($rcfile) && -f $rcfile) or do {
466 my $emsg = _('Error: resource file for Claws Mail was not found.');
468 error_dialog ($emsg);
474 sub set_widget_hint {
475 my ($wdgt, $hint) = @_;
476 $wdgt->set_tooltip_text ($hint);
477 $wdgt->set_has_tooltip (TRUE);
480 sub set_widget_sens {
481 my ($wdgt, $versions) = @_;
482 my @ver = split(/,/, $versions);
484 $wdgt->set_sensitive (
485 version_greater_or_equal ($CLAWSV, $ver[0])
486 and version_greater_or_equal ($ver[1], $CLAWSV)
489 $wdgt->set_sensitive (version_greater_or_equal ($CLAWSV, $ver[0]));
493 # graphic element creation
495 sub new_hbox_spaced_pack {
496 my $hbox = Gtk3::HBox->new (FALSE);
498 $hbox->pack_start ($_, FALSE, FALSE, HBOX_PAD);
503 sub new_check_button_for($$$) {
504 my ($hash, $key, $vhash) = @_;
505 my $name = $$hash{$key}[NAME];
506 my $label = $$hash{$key}[LABEL];
508 my $cb = Gtk3::CheckButton->new ($label);
509 my $value = $$vhash{$name}[VALUE];
510 $value //= $$hash{$key}[CMDEF];
511 $cb->set_active ($value eq '1');
512 $cb->signal_connect (clicked => sub {
514 handle_bool_value ($w, $e, \$$vhash{$name});
516 set_widget_hint ($cb, $$hash{$key}[DESC]);
517 set_widget_sens ($cb, $$hash{$key}[CMVER]);
519 return new_hbox_spaced_pack ($cb);
522 sub new_text_box_for_int($$$) {
523 my ($hash, $key, $vhash) = @_;
524 my $name = $$hash{$key}[NAME];
525 my $label = $$hash{$key}[LABEL];
526 my @type = split (/,/, $$hash{$key}[TYPE]);
527 push (@type, 0), push (@type, 10000) unless ($#type > 0);
530 if (ref $label eq 'ARRAY') {
531 $gunits = Gtk3::Label->new ($label->[1]);
532 $label = $label->[0];
534 my $glabel = Gtk3::Label->new ($label);
535 my $pagei = int (($type[2] - $type[1]) / 10);
536 my $gentry = Gtk3::SpinButton->new_with_range ($type[1], $type[2], $pagei);
537 my $value = $$vhash{$name}[VALUE];
538 $value //= $$hash{$key}[CMDEF];
539 $gentry->set_numeric (TRUE);
540 $gentry->set_value ($value);
541 $gentry->signal_connect('value-changed' => sub {
543 handle_int_value ($w, $e, \$$vhash{$name});
545 set_widget_hint ($gentry, $$hash{$key}[DESC]);
546 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
547 $glabel->set_sensitive ($gentry->get_sensitive);
548 $gunits->set_sensitive ($gentry->get_sensitive) if ($gunits);
550 return new_hbox_spaced_pack ($glabel, $gentry, $gunits) if ($gunits);
551 return new_hbox_spaced_pack ($glabel, $gentry);
554 sub new_text_box_for_nchar($$$) {
555 my ($hash, $key, $vhash) = @_;
556 my $name = $$hash{$key}[NAME];
557 my $label = $$hash{$key}[LABEL];
558 my @type = split (/,/, $$hash{$key}[TYPE]); # char,minlen,maxlen,width
559 my $glabel = Gtk3::Label->new ($label);
560 my $gentry = Gtk3::Entry->new;
561 $gentry->set_max_length($type[2]) if defined ($type[2]);
562 my $width = $type[3];
564 $gentry->set_width_chars(int ($width) + 2) if defined ($width);
565 my $value = $$vhash{$name}[VALUE];
566 $value //= $$hash{$key}[CMDEF];
567 $gentry->set_text ($value);
568 $gentry->signal_connect('key-release-event' => sub {
570 handle_nchar_value ($w, $e, \$$vhash{$name}, $type[1], $type[2]);
572 set_widget_hint ($gentry, $$hash{$key}[DESC]);
573 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
574 $glabel->set_sensitive ($gentry->get_sensitive);
576 return new_hbox_spaced_pack ($glabel, $gentry);
579 sub new_color_button_for($$$) {
580 my ($hash, $key, $vhash) = @_;
581 my $name = $$hash{$key}[NAME];
582 my $label = $$hash{$key}[LABEL];
584 my $value = $$vhash{$name}[VALUE];
585 $value //= $$hash{$key}[CMDEF];
586 my $col = gdk_rgba_from_str ($value);
587 my $glabel = Gtk3::Label->new ($label);
588 my $button = Gtk3::ColorButton->new_with_rgba ($col);
589 $button->set_title ($label);
590 $button->set_relief ('none');
591 $button->signal_connect ('color-set' => sub {
593 handle_color_value ($w, $e, \$$vhash{$name});
595 set_widget_hint ($button, $$hash{$key}[DESC]);
596 set_widget_sens ($button, $$hash{$key}[CMVER]);
597 $glabel->set_sensitive ($button->get_sensitive);
599 return new_hbox_spaced_pack ($button, $glabel);
602 sub new_selection_box_for($$$) {
603 my ($hash, $key, $vhash) = @_;
604 my $name = $$hash{$key}[NAME];
605 my $label = $$hash{$key}[LABEL];
607 my $glabel = Gtk3::Label->new ($label);
608 my $combo = Gtk3::ComboBoxText->new;
609 my @options = split (';', $$hash{$key}[TYPE]);
610 foreach my $opt (@options) {
611 my ($index, $textkey) = split ('=', $opt);
612 $combo->insert (-1, $index, $xl::s{$textkey});
614 $combo->signal_connect ('changed' => sub {
616 handle_selection_value ($w, $e, \$$vhash{$name});
618 my $value = $$vhash{$name}[VALUE];
619 $value //= $$hash{$key}[CMDEF];
620 $combo->set_active ($value);
621 set_widget_hint ($combo, $$hash{$key}[DESC]);
622 set_widget_sens ($combo, $$hash{$key}[CMVER]);
623 $glabel->set_sensitive ($combo->get_sensitive);
625 return new_hbox_spaced_pack ($glabel, $combo);
628 # more graphic helpers
631 my ($border_w, $row_s, $col_s) = @_;
632 $border_w //= GRID_SPC;
635 my $grid = Gtk3::Grid->new;
636 $grid->set_border_width ($border_w);
637 $grid->set_row_spacing ($row_s);
638 $grid->set_column_spacing ($col_s);
645 my $label = Gtk3::Label->new ($text);
646 $label->set_alignment (0, 0.5);
653 my $label = Gtk3::Label->new ('<b>' . $text . '</b>');
654 $label->set_use_markup (TRUE);
655 $label->set_alignment (0, 0.5);
660 my ($width, $height, $widget) = @_;
661 my $grid = new_grid ();
662 $grid->set_column_homogeneous (TRUE);
663 for (my $i = 0; $i < $width; ++$i) {
664 for (my $j = 0; $j < $height; ++$j) {
665 my $wid = $widget->[$j]->[$i];
666 next unless defined $wid;
667 my $ww = (($i + 1 < $width) and (defined $widget->[$j]->[$i + 1]))
671 $grid->attach ($wid, $i, $j, $ww, 1);
672 } else { # not a widget
673 if ('--' eq $wid) { # a separator
674 $grid->attach (Gtk3::Separator->new ('horizontal'),
676 } else { # or a title
677 $grid->attach (new_title ($wid),
686 # preference maps and corresponding page creation subs
688 %pr::oth = ( # other preferences
690 'addressbook_use_editaddress_dialog',
691 $xl::s{l_oth_use_dlg},
692 $xl::s{h_oth_use_dlg},
698 'cache_max_mem_usage',
699 [ $xl::s{l_oth_max_use}, $xl::s{l_oth_max_use_units} ],
700 $xl::s{h_oth_max_use},
701 'int,0,524288', # 0 Kb - 512 Mb
706 'cache_min_keep_time',
707 [ $xl::s{l_oth_min_time}, $xl::s{l_oth_min_time_units} ],
708 $xl::s{h_oth_min_time},
709 'int,0,120', # 0 minutes - 2 hours
714 'use_networkmanager',
715 $xl::s{l_oth_use_netm},
716 $xl::s{h_oth_use_netm},
722 'master_passphrase_pbkdf2_rounds',
723 $xl::s{l_oth_mp_rounds},
724 $xl::s{h_oth_mp_rounds},
731 sub new_other_page() {
732 return new_grid_pack (1, 12, [
733 [ _('Addressbook') ],
734 [ new_check_button_for(\%pr::oth, 'use_dlg', \%HPVALUE) ],
737 [ new_text_box_for_int(\%pr::oth, 'max_use', \%HPVALUE) ],
738 [ new_text_box_for_int(\%pr::oth, 'min_time', \%HPVALUE) ],
740 [ _('NetworkManager') ],
741 [ new_check_button_for(\%pr::oth, 'use_netm', \%HPVALUE) ],
743 [ _('Master passphrase') ],
744 [ new_text_box_for_int(\%pr::oth, 'mp_rounds', \%HPVALUE) ]
748 %pr::gui = ( # gui bells and whistles
751 $xl::s{l_gui_b_unread},
752 $xl::s{h_gui_b_unread},
759 $xl::s{l_gui_no_markup},
760 $xl::s{h_gui_no_markup},
766 'enable_dotted_lines',
767 $xl::s{l_gui_dot_lines},
768 $xl::s{h_gui_dot_lines},
770 '2.4.0.115,3.7.10.44',
775 $xl::s{l_gui_h_scroll},
776 $xl::s{h_gui_h_scroll},
783 $xl::s{l_gui_swp_from},
784 $xl::s{h_gui_swp_from},
790 'folderview_vscrollbar_policy',
791 $xl::s{l_gui_v_scroll},
792 $xl::s{h_gui_v_scroll},
793 '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
799 $xl::s{l_gui_from_show},
800 $xl::s{h_gui_from_show},
801 '0=l_gui_from_show_name;1=l_gui_from_show_addr;2=l_gui_from_show_both',
806 'stripes_color_offset',
807 $xl::s{l_gui_strip_off},
808 $xl::s{h_gui_strip_off},
809 'int,0,40000', # no idea what this number means
814 'textview_cursor_visible',
815 $xl::s{l_gui_cursor_v},
816 $xl::s{h_gui_cursor_v},
822 'toolbar_detachable',
823 $xl::s{l_gui_toolbar_d},
824 $xl::s{h_gui_toolbar_d},
830 'use_stripes_everywhere',
831 $xl::s{l_gui_strip_all},
832 $xl::s{h_gui_strip_all},
838 'use_stripes_in_summaries',
839 $xl::s{l_gui_strip_sum},
840 $xl::s{h_gui_strip_sum},
847 $xl::s{l_gui_two_line_v},
848 $xl::s{h_gui_two_line_v},
854 'show_compose_margin',
855 $xl::s{l_gui_margin_co},
856 $xl::s{h_gui_margin_co},
862 'msgview_date_format',
863 $xl::s{l_gui_mview_date},
864 $xl::s{h_gui_mview_date},
870 'zero_replacement_char',
871 $xl::s{l_gui_zero_char},
872 $xl::s{h_gui_zero_char},
879 $xl::s{l_gui_type_any},
880 $xl::s{h_gui_type_any},
886 'warn_sending_many_recipients_num',
887 [ $xl::s{l_gui_warn_send_multi}, $xl::s{l_gui_warn_send_multi_units} ],
888 $xl::s{h_gui_warn_send_multi},
895 $xl::s{l_gui_next_del},
896 $xl::s{h_gui_next_del},
904 return new_grid_pack (2, 24, [
905 [ _('Coloured stripes') ],
906 [ new_check_button_for (\%pr::gui, 'strip_all', \%HPVALUE),
907 new_check_button_for (\%pr::gui, 'strip_sum', \%HPVALUE) ],
908 [ new_text_box_for_int (\%pr::gui, 'strip_off', \%HPVALUE) ],
910 [ _('Message List') ],
911 [ new_check_button_for (\%pr::gui, 'b_unread', \%HPVALUE),
912 new_check_button_for (\%pr::gui, 'swp_from', \%HPVALUE) ],
913 [ new_check_button_for (\%pr::gui, 'two_linev', \%HPVALUE),
914 new_check_button_for (\%pr::gui, 'next_del', \%HPVALUE) ],
915 [ new_selection_box_for (\%pr::gui, 'from_show', \%HPVALUE) ],
917 [ _('Message View') ],
918 [ new_check_button_for (\%pr::gui, 'cursor_v', \%HPVALUE),
919 new_check_button_for (\%pr::gui, 'mview_date', \%HPVALUE) ],
921 [ _('Compose window') ],
922 [ new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
923 new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE) ],
924 [ new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE) ],
925 [ new_text_box_for_int (\%pr::gui, 'warn_send_multi', \%HPVALUE) ],
927 [ _('Scroll bars') ],
928 [ new_check_button_for (\%pr::gui, 'h_scroll', \%HPVALUE),
929 new_selection_box_for (\%pr::gui, 'v_scroll', \%HPVALUE) ],
932 [ new_check_button_for (\%pr::gui, 'dot_lines', \%HPVALUE),
933 new_check_button_for (\%pr::gui, 'toolbar_d', \%HPVALUE) ],
934 [ new_text_box_for_nchar (\%pr::gui, 'zero_char', \%HPVALUE) ]
938 %pr::beh = ( # tweak some behaviour
941 [ $xl::s{l_beh_hover_t}, $xl::s{l_beh_hover_t_units} ],
942 $xl::s{h_beh_hover_t},
943 'int,100,3000', # 0.1 seconds - 3 seconds
949 $xl::s{l_beh_dangerous},
950 $xl::s{h_beh_dangerous},
956 'respect_flowed_format',
957 $xl::s{l_beh_flowed},
958 $xl::s{h_beh_flowed},
964 'save_parts_readwrite',
965 $xl::s{l_beh_parts_rw},
966 $xl::s{h_beh_parts_rw},
972 'skip_ssl_cert_check',
973 $xl::s{l_beh_skip_ssl},
974 $xl::s{h_beh_skip_ssl},
980 'statusbar_update_step',
981 [ $xl::s{l_beh_up_step}, $xl::s{l_beh_up_step_units} ],
982 $xl::s{h_beh_up_step},
983 'int,1,200', # 1 item - 200 items
988 'thread_by_subject_max_age',
989 [ $xl::s{l_beh_thread_a}, $xl::s{l_beh_thread_a_units} ],
990 $xl::s{h_beh_thread_a},
991 'int,1,30', # 1 day - 30 days
997 $xl::s{l_beh_unsafe_ssl},
998 $xl::s{h_beh_unsafe_ssl},
1004 'utf8_instead_of_locale_for_broken_mail',
1005 $xl::s{l_beh_use_utf8},
1006 $xl::s{h_beh_use_utf8},
1013 $xl::s{l_beh_warn_dnd},
1014 $xl::s{h_beh_warn_dnd},
1020 'outgoing_fallback_to_ascii',
1021 $xl::s{l_beh_out_ascii},
1022 $xl::s{h_beh_out_ascii},
1028 'primary_paste_unselects',
1029 $xl::s{l_beh_pp_unsel},
1030 $xl::s{h_beh_pp_unsel},
1036 'show_inline_attachments',
1037 $xl::s{l_beh_inline_at},
1038 $xl::s{h_beh_inline_at},
1044 'address_search_wildcard',
1045 $xl::s{l_beh_addr_swc},
1046 $xl::s{h_beh_addr_swc},
1052 'folder_search_wildcard',
1053 $xl::s{l_beh_fold_swc},
1054 $xl::s{h_beh_fold_swc},
1060 'rewrite_first_from',
1061 $xl::s{l_beh_rewrite_ff},
1062 $xl::s{h_beh_rewrite_ff},
1069 sub new_behaviour_page() {
1070 return new_grid_pack (2, 20, [
1071 [ _('Drag \'n\' drop') ],
1072 [ new_text_box_for_int (\%pr::beh, 'hover_t', \%HPVALUE) ],
1073 [ new_check_button_for (\%pr::beh, 'warn_dnd', \%HPVALUE) ],
1075 [ _('Secure Sockets Layer') ],
1076 [ new_check_button_for (\%pr::beh, 'skip_ssl', \%HPVALUE),
1077 new_check_button_for (\%pr::beh, 'unsafe_ssl', \%HPVALUE) ],
1080 [ new_check_button_for (\%pr::beh, 'flowed', \%HPVALUE),
1081 new_check_button_for (\%pr::beh, 'out_ascii', \%HPVALUE) ],
1082 [ new_check_button_for (\%pr::beh, 'parts_rw', \%HPVALUE),
1083 new_check_button_for (\%pr::beh, 'pp_unsel', \%HPVALUE) ],
1084 [ new_check_button_for (\%pr::beh, 'use_utf8', \%HPVALUE),
1085 new_check_button_for (\%pr::beh, 'inline_at', \%HPVALUE) ],
1086 [ new_check_button_for (\%pr::beh, 'dangerous', \%HPVALUE),
1087 new_check_button_for (\%pr::beh, 'rewrite_ff', \%HPVALUE) ],
1089 [ _('Completion') ],
1090 [ new_check_button_for (\%pr::beh, 'addr_swc', \%HPVALUE) ],
1091 [ new_check_button_for (\%pr::beh, 'fold_swc', \%HPVALUE) ],
1094 [ new_text_box_for_int (\%pr::beh, 'up_step', \%HPVALUE) ],
1095 [ new_text_box_for_int (\%pr::beh, 'thread_a', \%HPVALUE) ]
1099 %pr::col = ( # a variety of colours
1102 $xl::s{l_col_emphasis},
1103 $xl::s{h_col_emphasis},
1110 $xl::s{l_col_log_err},
1111 $xl::s{h_col_log_err},
1118 $xl::s{l_col_log_in},
1119 $xl::s{h_col_log_in},
1126 $xl::s{l_col_log_msg},
1127 $xl::s{h_col_log_msg},
1134 $xl::s{l_col_log_out},
1135 $xl::s{h_col_log_out},
1142 $xl::s{l_col_log_warn},
1143 $xl::s{h_col_log_warn},
1150 $xl::s{l_col_diff_add},
1151 $xl::s{h_col_diff_add},
1157 'diff_deleted_color',
1158 $xl::s{l_col_diff_del},
1159 $xl::s{h_col_diff_del},
1166 $xl::s{l_col_diff_hunk},
1167 $xl::s{h_col_diff_hunk},
1174 $xl::s{l_col_tags_bg},
1175 $xl::s{h_col_tags_bg},
1182 $xl::s{l_col_tags_text},
1183 $xl::s{h_col_tags_text},
1188 default_header_bg => [
1189 'default_header_bgcolor',
1190 $xl::s{l_col_default_header_bg},
1191 $xl::s{h_col_default_header_bg},
1196 default_header_text => [
1197 'default_header_color',
1198 $xl::s{l_col_default_header_text},
1199 $xl::s{h_col_default_header_text},
1205 'qs_active_bgcolor',
1206 $xl::s{l_col_qs_active_bg},
1207 $xl::s{h_col_qs_active_bg},
1214 $xl::s{l_col_qs_active_text},
1215 $xl::s{h_col_qs_active_text},
1222 $xl::s{l_col_qs_error_bg},
1223 $xl::s{h_col_qs_error_bg},
1230 $xl::s{l_col_qs_error_text},
1231 $xl::s{h_col_qs_error_text},
1238 sub new_colours_page() {
1239 return new_grid_pack (3, 18, [
1240 [ _('Message View') ],
1241 [ new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE) ],
1242 [ new_color_button_for (\%pr::col, 'tags_text', \%HPVALUE) ,
1243 new_color_button_for (\%pr::col, 'tags_bg', \%HPVALUE) ],
1245 [ _('Log window') ],
1246 [ new_color_button_for (\%pr::col, 'log_err', \%HPVALUE) ,
1247 new_color_button_for (\%pr::col, 'log_in', \%HPVALUE) ],
1248 [ new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE) ,
1249 new_color_button_for (\%pr::col, 'log_out', \%HPVALUE) ],
1250 [ new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE) ],
1252 [ _('Viewing patches') ],
1253 [ new_color_button_for (\%pr::col, 'diff_add', \%HPVALUE) ,
1254 new_color_button_for (\%pr::col, 'diff_del', \%HPVALUE) ,
1255 new_color_button_for (\%pr::col, 'diff_hunk', \%HPVALUE) ],
1257 [ _('Compose window') ],
1258 [ new_color_button_for (\%pr::col, 'default_header_text', \%HPVALUE) ,
1259 new_color_button_for (\%pr::col, 'default_header_bg', \%HPVALUE) ],
1261 [ _('Quick search') ],
1262 [ new_color_button_for (\%pr::col, 'qs_active_text', \%HPVALUE) ,
1263 new_color_button_for (\%pr::col, 'qs_active_bg', \%HPVALUE) ],
1264 [ new_color_button_for (\%pr::col, 'qs_error_text', \%HPVALUE) ,
1265 new_color_button_for (\%pr::col, 'qs_error_bg', \%HPVALUE) ]
1269 %pr::win = ( # tweak window positions and/or sizes
1274 'int,0,3000', # 0 pixels - 3000 pixels
1282 'int,0,3000', # 0 pixels - 3000 pixels
1290 'int,0,3000', # 0 pixels - 3000 pixels
1298 'int,0,3000', # 0 pixels - 3000 pixels
1303 'mainwin_maximised',
1304 $xl::s{l_win_main_mx},
1305 $xl::s{h_win_main_mx},
1311 'mainwin_fullscreen',
1312 $xl::s{l_win_main_fs},
1313 $xl::s{h_win_main_fs},
1319 'main_messagewin_x',
1322 'int,0,3000', # 0 pixels - 3000 pixels
1327 'main_messagewin_y',
1330 'int,0,3000', # 0 pixels - 3000 pixels
1338 'int,0,3000', # 0 pixels - 3000 pixels
1343 'messagewin_height',
1346 'int,0,3000', # 0 pixels - 3000 pixels
1354 'int,0,3000', # 0 pixels - 3000 pixels
1362 'int,0,3000', # 0 pixels - 3000 pixels
1370 'int,0,3000', # 0 pixels - 3000 pixels
1375 'receivewin_height',
1378 'int,0,3000', # 0 pixels - 3000 pixels
1386 'int,0,3000', # 0 pixels - 3000 pixels
1394 'int,0,3000', # 0 pixels - 3000 pixels
1399 'folderitemwin_width',
1402 'int,0,3000', # 0 pixels - 3000 pixels
1407 'folderitemwin_height',
1410 'int,0,3000', # 0 pixels - 3000 pixels
1415 'folderselwin_width',
1418 'int,0,3000', # 0 pixels - 3000 pixels
1423 'folderselwin_height',
1426 'int,0,3000', # 0 pixels - 3000 pixels
1434 'int,0,3000', # 0 pixels - 3000 pixels
1442 'int,0,3000', # 0 pixels - 3000 pixels
1447 'addressbookwin_width',
1450 'int,0,3000', # 0 pixels - 3000 pixels
1455 'addressbookwin_height',
1458 'int,0,3000', # 0 pixels - 3000 pixels
1463 'addressbookeditpersonwin_width',
1466 'int,0,3000', # 0 pixels - 3000 pixels
1471 'addressbookeditpersonwin_height',
1474 'int,0,3000', # 0 pixels - 3000 pixels
1479 'addressbookeditgroupwin_width',
1482 'int,0,3000', # 0 pixels - 3000 pixels
1487 'addressbookeditgroupwin_height',
1490 'int,0,3000', # 0 pixels - 3000 pixels
1495 'addressaddwin_width',
1498 'int,0,3000', # 0 pixels - 3000 pixels
1503 'addressaddwin_height',
1506 'int,0,3000', # 0 pixels - 3000 pixels
1511 'addressbook_folderselwin_width',
1514 'int,0,3000', # 0 pixels - 3000 pixels
1519 'addressbook_folderselwin_height',
1522 'int,0,3000', # 0 pixels - 3000 pixels
1527 'editaccountwin_width',
1530 'int,0,3000', # 0 pixels - 3000 pixels
1535 'editaccountwin_height',
1538 'int,0,3000', # 0 pixels - 3000 pixels
1543 'accountswin_width',
1546 'int,0,3000', # 0 pixels - 3000 pixels
1551 'accountswin_height',
1554 'int,0,3000', # 0 pixels - 3000 pixels
1559 'filteringwin_width',
1562 'int,0,3000', # 0 pixels - 3000 pixels
1567 'filteringwin_height',
1570 'int,0,3000', # 0 pixels - 3000 pixels
1575 'filteringactionwin_width',
1578 'int,0,3000', # 0 pixels - 3000 pixels
1583 'filteringactionwin_height',
1586 'int,0,3000', # 0 pixels - 3000 pixels
1591 'filtering_debugwin_width',
1594 'int,0,3000', # 0 pixels - 3000 pixels
1599 'filtering_debugwin_height',
1602 'int,0,3000', # 0 pixels - 3000 pixels
1610 'int,0,3000', # 0 pixels - 3000 pixels
1615 'matcherwin_height',
1618 'int,0,3000', # 0 pixels - 3000 pixels
1626 'int,0,3000', # 0 pixels - 3000 pixels
1634 'int,0,3000', # 0 pixels - 3000 pixels
1639 'templateswin_width',
1642 'int,0,3000', # 0 pixels - 3000 pixels
1647 'templateswin_height',
1650 'int,0,3000', # 0 pixels - 3000 pixels
1658 'int,0,3000', # 0 pixels - 3000 pixels
1663 'actionswin_height',
1666 'int,0,3000', # 0 pixels - 3000 pixels
1671 'actionsiodialog_width',
1674 'int,0,3000', # 0 pixels - 3000 pixels
1679 'actionsiodialog_height',
1682 'int,0,3000', # 0 pixels - 3000 pixels
1690 'int,0,3000', # 0 pixels - 3000 pixels
1698 'int,0,3000', # 0 pixels - 3000 pixels
1706 'int,0,3000', # 0 pixels - 3000 pixels
1714 'int,0,3000', # 0 pixels - 3000 pixels
1722 'int,0,3000', # 0 pixels - 3000 pixels
1727 'pluginswin_height',
1730 'int,0,3000', # 0 pixels - 3000 pixels
1738 'int,0,3000', # 0 pixels - 3000 pixels
1746 'int,0,3000', # 0 pixels - 3000 pixels
1751 'print_previewwin_width',
1754 'int,0,3000', # 0 pixels - 3000 pixels
1759 'print_previewwin_height',
1762 'int,0,3000', # 0 pixels - 3000 pixels
1768 sub new_winpos_subpage_main() {
1769 return new_grid_pack (3, 7, [
1770 [ _('Main window'), undef ],
1771 [ new_text_box_for_int (\%pr::win, 'main_x', \%HPVALUE), undef ],
1772 [ new_text_box_for_int (\%pr::win, 'main_y', \%HPVALUE), undef ],
1773 [ new_text_box_for_int (\%pr::win, 'main_w', \%HPVALUE),
1774 new_text_box_for_int (\%pr::win, 'main_h', \%HPVALUE) ],
1775 [ new_check_button_for (\%pr::win, 'main_fs', \%HPVALUE), undef ],
1776 [ new_check_button_for (\%pr::win, 'main_mx', \%HPVALUE), undef ]
1780 sub new_winpos_subpage_msgs() {
1781 return new_grid_pack (3, 4, [
1782 [ _('Message window') ],
1783 [ new_text_box_for_int (\%pr::win, 'msgs_x', \%HPVALUE) ],
1784 [ new_text_box_for_int (\%pr::win, 'msgs_y', \%HPVALUE) ],
1785 [ new_text_box_for_int (\%pr::win, 'msgs_w', \%HPVALUE),
1786 new_text_box_for_int (\%pr::win, 'msgs_h', \%HPVALUE) ]
1790 sub new_winpos_subpage_sendrecv() {
1791 return new_grid_pack (3, 5, [
1792 [ _('Send window') ],
1793 [ new_text_box_for_int (\%pr::win, 'send_w', \%HPVALUE),
1794 new_text_box_for_int (\%pr::win, 'send_h', \%HPVALUE) ],
1796 [ _('Receive window') ],
1797 [ new_text_box_for_int (\%pr::win, 'recv_w', \%HPVALUE),
1798 new_text_box_for_int (\%pr::win, 'recv_h', \%HPVALUE) ]
1802 sub new_winpos_subpage_fold() {
1803 return new_grid_pack (3, 7, [
1804 [ _('Folder window') ],
1805 [ new_text_box_for_int (\%pr::win, 'fold_x', \%HPVALUE) ],
1806 [ new_text_box_for_int (\%pr::win, 'fold_y', \%HPVALUE) ],
1807 [ new_text_box_for_int (\%pr::win, 'fold_w', \%HPVALUE),
1808 new_text_box_for_int (\%pr::win, 'fold_h', \%HPVALUE) ],
1810 [ _('Folder selection window') ],
1811 [ new_text_box_for_int (\%pr::win, 'fsel_w', \%HPVALUE),
1812 new_text_box_for_int (\%pr::win, 'fsel_h', \%HPVALUE) ]
1816 sub new_winpos_subpage_addrbook() {
1817 return new_grid_pack (3, 14, [
1818 [ _('Addressbook main window') ],
1819 [ new_text_box_for_int (\%pr::win, 'addr_w', \%HPVALUE),
1820 new_text_box_for_int (\%pr::win, 'addr_h', \%HPVALUE) ],
1822 [ _('Edit person window') ],
1823 [ new_text_box_for_int (\%pr::win, 'adep_w', \%HPVALUE),
1824 new_text_box_for_int (\%pr::win, 'adep_h', \%HPVALUE) ],
1826 [ _('Edit group window') ],
1827 [ new_text_box_for_int (\%pr::win, 'adeg_w', \%HPVALUE),
1828 new_text_box_for_int (\%pr::win, 'adeg_h', \%HPVALUE) ],
1830 [ _('Add address window') ],
1831 [ new_text_box_for_int (\%pr::win, 'adda_w', \%HPVALUE),
1832 new_text_box_for_int (\%pr::win, 'adda_h', \%HPVALUE) ],
1834 [ _('Folder select window') ],
1835 [ new_text_box_for_int (\%pr::win, 'addf_w', \%HPVALUE),
1836 new_text_box_for_int (\%pr::win, 'addf_h', \%HPVALUE) ]
1840 sub new_winpos_subpage_accounts() {
1841 return new_grid_pack (3, 5, [
1842 [ _('Accounts window') ],
1843 [ new_text_box_for_int (\%pr::win, 'acco_w', \%HPVALUE),
1844 new_text_box_for_int (\%pr::win, 'acco_h', \%HPVALUE) ],
1846 [ _('Edit account window') ],
1847 [ new_text_box_for_int (\%pr::win, 'acce_w', \%HPVALUE),
1848 new_text_box_for_int (\%pr::win, 'acce_h', \%HPVALUE) ]
1852 sub new_winpos_subpage_filtering() {
1853 return new_grid_pack (3, 11, [
1854 [ _('Filtering window') ],
1855 [ new_text_box_for_int (\%pr::win, 'filt_w', \%HPVALUE),
1856 new_text_box_for_int (\%pr::win, 'filt_h', \%HPVALUE) ],
1858 [ _('Filtering actions window') ],
1859 [ new_text_box_for_int (\%pr::win, 'fila_w', \%HPVALUE),
1860 new_text_box_for_int (\%pr::win, 'fila_h', \%HPVALUE) ],
1862 [ _('Filtering debug window') ],
1863 [ new_text_box_for_int (\%pr::win, 'fild_w', \%HPVALUE),
1864 new_text_box_for_int (\%pr::win, 'fild_h', \%HPVALUE) ],
1866 [ ('Matcher window') ],
1867 [ new_text_box_for_int (\%pr::win, 'matc_w', \%HPVALUE),
1868 new_text_box_for_int (\%pr::win, 'matc_h', \%HPVALUE) ]
1873 sub new_winpos_subpage_useractions() {
1874 return new_grid_pack (3, 5, [
1875 [ _('User Actions prefs window') ],
1876 [ new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
1877 new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE) ],
1879 [ _('User Actions I/O window') ],
1880 [ new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
1881 new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE) ]
1885 sub new_winpos_subpage_prefs() {
1886 return new_grid_pack (3, 11, [
1887 [ _('Preferences window') ],
1888 [ new_text_box_for_int (\%pr::win, 'pref_w', \%HPVALUE),
1889 new_text_box_for_int (\%pr::win, 'pref_h', \%HPVALUE) ],
1891 [ _('Templates window') ],
1892 [ new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
1893 new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE) ],
1895 [ _('Tags window') ],
1896 [ new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
1897 new_text_box_for_int (\%pr::win, 'tags_h', \%HPVALUE) ],
1899 [ _('Plugins window') ],
1900 [ new_text_box_for_int (\%pr::win, 'plug_w', \%HPVALUE),
1901 new_text_box_for_int (\%pr::win, 'plug_h', \%HPVALUE) ]
1905 sub new_winpos_subpage_misc() {
1906 return new_grid_pack (4, 11, [
1907 [ _('Log window') ],
1908 [ new_text_box_for_int (\%pr::win, 'logw_w', \%HPVALUE),
1909 new_text_box_for_int (\%pr::win, 'logw_h', \%HPVALUE) ],
1911 [ _('SSL manager') ],
1912 [ new_text_box_for_int (\%pr::win, 'sslman_w', \%HPVALUE),
1913 new_text_box_for_int (\%pr::win, 'sslman_h', \%HPVALUE) ],
1915 [ _('Print preview window') ],
1916 [ new_text_box_for_int (\%pr::win, 'prin_w', \%HPVALUE),
1917 new_text_box_for_int (\%pr::win, 'prin_h', \%HPVALUE) ],
1919 [ _('View source window') ],
1920 [ new_text_box_for_int (\%pr::win, 'sour_w', \%HPVALUE),
1921 new_text_box_for_int (\%pr::win, 'sour_h', \%HPVALUE) ]
1925 sub new_winpos_page() {
1926 my $winbook = Gtk3::Notebook->new;
1927 $winbook->set_tab_pos ('right');
1928 $winbook->append_page (&new_winpos_subpage_main, new_label (_('Main')));
1929 $winbook->append_page (&new_winpos_subpage_msgs, new_label (_('Message')));
1930 $winbook->append_page (&new_winpos_subpage_sendrecv, new_label (_('Send/Receive')));
1931 $winbook->append_page (&new_winpos_subpage_fold, new_label (_('Folder')));
1932 $winbook->append_page (&new_winpos_subpage_addrbook, new_label (_('Addressbook')));
1933 $winbook->append_page (&new_winpos_subpage_accounts, new_label (_('Accounts')));
1934 $winbook->append_page (&new_winpos_subpage_filtering, new_label (_('Filtering')));
1935 $winbook->append_page (&new_winpos_subpage_useractions, new_label (_('User Actions')));
1936 $winbook->append_page (&new_winpos_subpage_prefs, new_label (_('Preferences')));
1937 $winbook->append_page (&new_winpos_subpage_misc, new_label (_('Other')));
1941 %pr::acc = ( # per account hidden preferences
1943 'gnutls_set_priority',
1944 $xl::s{l_acc_gtls_set},
1945 $xl::s{h_acc_gtls_set},
1952 $xl::s{l_acc_gtls_pri},
1953 $xl::s{h_acc_gtls_pri},
1960 $xl::s{l_acc_tls_sni},
1961 $xl::s{h_acc_tls_sni},
1968 sub new_account_subpage($) {
1970 return new_grid_pack (1, 5, [
1971 [ _('GnuTLS priority') ],
1972 [ new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}) ],
1973 [ new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey}) ],
1974 [ _('Server Name Indication') ],
1975 [ new_check_button_for (\%pr::acc, 'tls_sni', $ACHPVALUE{$akey}) ],
1979 sub new_accounts_page() {
1980 my $accbook = Gtk3::Notebook->new;
1981 $accbook->set_tab_pos ('right');
1983 $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
1986 my $name = $ACPREFS{$_}{'account_name'};
1987 my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
1988 my $page = new_account_subpage ($_);
1989 my $label = new_label ($isdef? '<u>' . $name . '</u>': $name);
1990 $label->set_use_markup (TRUE);
1991 $accbook->append_page ($page, $label);
1993 $accbook->set_scrollable (TRUE);
1997 %pr::plu = ( # plugins hidden preferences
2003 'int,0,3000', # 0 pixels - 3000 pixels
2012 'int,0,3000', # 0 pixels - 3000 pixels
2019 'autocompletion_limit',
2020 $xl::s{l_plu_gpg_alimit},
2021 $xl::s{h_plu_gpg_alimit},
2029 'manager_win_width',
2032 'int,0,3000', # 0 pixels - 3000 pixels
2038 'manager_win_height',
2041 'int,0,3000', # 0 pixels - 3000 pixels
2049 $xl::s{l_plu_lav_burl},
2050 $xl::s{h_plu_lav_burl},
2053 'http://cdn.libravatar.org/avatar',
2058 'filter_log_verbosity',
2059 $xl::s{l_plu_prl_flvb},
2060 $xl::s{h_plu_prl_flvb},
2061 '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2068 sub new_plugins_page() {
2071 new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2072 new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})
2075 new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})
2078 new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2079 new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})
2082 new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'})
2085 new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})
2088 foreach my $pk (@PLUGINS) {
2089 foreach my $wg (@{$widget{$pk}}) {
2090 $wg->set_sensitive (defined $PLHPVALUE{$pk});
2093 return new_grid_pack (3, 14, [
2094 [ _('Attachment remover') ], $widget{'AttRemover'}, [ '--' ],
2095 [ _('GPG') ], $widget{'GPG'}, [ '--' ],
2096 [ _('Sieve manager') ], $widget{'ManageSieve'}, [ '--' ],
2097 [ _('Libravatar') ], $widget{'Libravatar'}, [ '--' ],
2098 [ _('Perl') ], $widget{'PerlPlugin'}
2102 sub new_hotkeys_list_label {
2103 my $renderer = Gtk3::CellRendererText->new;
2104 $renderer->set_property('alignment' => 'left');
2105 $renderer->set_property('editable' => FALSE);
2109 sub new_hotkeys_list_hotkey {
2110 my $renderer = Gtk3::CellRendererAccel->new;
2111 $renderer->set_property ('accel-mode' => 'gtk');
2112 $renderer->set_property ('editable' => TRUE);
2113 $renderer->signal_connect ('accel-edited' => sub {
2114 my ($w, $path, $key, $mods, $keycode) = @_;
2115 my $accel = Gtk3::accelerator_name ($key, $mods);
2116 my ($model, $iter) = $SELHOTKEY->get_selected ();
2117 $model->set($iter, C_HOTKEY, "\"$accel\"");
2118 my $gkey = $model->get_value ($iter, C_GROUP);
2119 my $akey = $model->get_value ($iter, C_ACCEL);
2120 my $data = $HOTKEYS->{$gkey}->{$akey};
2121 $data->{'key'} = "\"$accel\"";
2122 $data->{'enabled'} = TRUE;
2124 $renderer->signal_connect ('accel-cleared' => sub {
2125 my ($w, $path) = @_;
2126 my ($model, $iter) = $SELHOTKEY->get_selected ();
2127 $model->set($iter, C_HOTKEY, "\"\"");
2128 my $gkey = $model->get_value ($iter, C_GROUP);
2129 my $akey = $model->get_value ($iter, C_ACCEL);
2130 my $data = $HOTKEYS->{$gkey}->{$akey};
2131 $data->{'key'} = "\"\"";
2132 $data->{'enabled'} = FALSE;
2137 sub row_background_color {
2138 my ($column, $isodd) = @_;
2139 my $treeview = $column->get_tree_view;
2140 my $stylectx = $treeview->get_style_context;
2142 ? $stylectx->get_background_color ('normal')
2143 : $stylectx->get_background_color ('insensitive');
2146 sub new_hotkeys_list {
2147 my ($gkey, $group) = @_;
2148 my $store = Gtk3::ListStore->new(
2149 qw/Glib::String Glib::String Glib::String Glib::String Glib::Boolean/);
2151 foreach my $akey (sort keys %$group) {
2152 my $iter = $store->append ();
2153 my $hotkey = $group->{$akey}->{'key'};
2155 $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2156 $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2157 C_GROUP, $gkey, C_ACCEL, $akey, C_ODDITY, $even);
2160 my $treeview = Gtk3::TreeView->new_with_model ($store);
2162 $treeview->insert_column_with_data_func (
2163 0, _("Menu path"), new_hotkeys_list_label (),
2165 my ($col, $renderer, $model, $iter, $data) = @_;
2166 $renderer->set_property (
2167 'markup' => '<span size="smaller">'
2168 . $model->get_value ($iter, C_LABEL)
2170 my $bgcol = row_background_color (
2171 $col, $model->get_value ($iter, C_ODDITY));
2172 $renderer->set_property ('cell-background-rgba' => $bgcol);
2176 $treeview->insert_column_with_data_func (
2177 1, _('Hotkey'), new_hotkeys_list_hotkey (),
2179 my ($col, $renderer, $model, $iter, $data) = @_;
2180 my $hkey = $model->get_value ($iter, C_HOTKEY);
2182 my ($acckey, $accmod) = Gtk3::accelerator_parse ($hkey);
2183 $renderer->set_property ('accel-key' => $acckey);
2184 $renderer->set_property ('accel-mods' => $accmod);
2185 my $bgcol = row_background_color (
2186 $col, $model->get_value ($iter, C_ODDITY));
2187 $renderer->set_property ('cell-background-rgba' => $bgcol);
2190 # callback for saving current selection
2191 my $selection = $treeview->get_selection ();
2192 $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2196 sub new_hotkeys_page() {
2197 my $hkbook = Gtk3::Notebook->new;
2198 $hkbook->set_tab_pos ('right');
2199 foreach my $gkey (sort keys %$HOTKEYS) {
2200 my $group = $HOTKEYS->{$gkey};
2201 my $keylist = new_hotkeys_list ($gkey, $group);
2202 # prepare scrolled window
2203 my $swin = Gtk3::ScrolledWindow->new;
2204 $swin->set_border_width (5);
2205 $swin->set_shadow_type ('none');
2206 $swin->set_policy ('automatic', 'automatic');
2208 $swin->add ($keylist);
2209 $hkbook->append_page ($swin, new_label ($gkey));
2214 sub new_info_page() {
2215 my $v = get_toolkit_versions ();
2216 my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2218 return new_grid_pack (4, 11, [
2219 [ _('Library versions') ],
2220 [ new_label ('Perl-GLib'), new_title ($v->{'glib'}) ],
2221 [ new_label (_('GLib runtime')), new_title ($v->{'glib-r'}) ],
2222 [ new_label (_('GLib built')), new_title ($v->{'glib-b'}) ],
2223 [ new_label ('Perl-GTK3'), new_title ($v->{'gtk'}) ],
2224 [ new_label (_('GTK3 runtime')), new_title ($v->{'gtk-r'}) ],
2225 [ new_label (_('GTK3 built')), new_title ($v->{'gtk-b'}) ],
2227 [ _('Claws Mail versions') ],
2228 [ new_label (_('Binary')), new_title ($CLAWSV) ],
2229 [ new_label (_('Configuration')), new_title ($cfgv) ]
2234 sub get_toolkit_versions {
2236 $versions{'glib'} = $Glib::VERSION;
2237 # version info stuff appeared in 1.040
2238 if ($Glib::VERSION >= 1.040) {
2239 $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2240 $versions{'glib-r'} = join('.',
2241 &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2243 $versions{'gtk'} = $Gtk3::VERSION;
2244 if ($Gtk3::VERSION >= 0.034) {
2245 $versions{'gtk-b'} = &Gtk3::GET_VERSION_INFO
2247 $versions{'gtk-b'} = _('Not available')
2249 $versions{'gtk-r'} = join('.',
2250 &Gtk3::get_major_version, &Gtk3::get_minor_version, &Gtk3::get_micro_version);
2254 sub print_version() {
2255 say $xl::s{about_title};
2256 say _('Version:') . " $VERSION";
2257 my $v = get_toolkit_versions ();
2258 if ($v->{'glib-b'}) {
2259 say _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2260 glibv => $v->{'glib'},
2261 glibb => $v->{'glib-b'},
2262 glibr => $v->{'glib-r'});
2264 say _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2266 if ($v->{'gtk-b'}) {
2267 say _("Perl-GTK3 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2268 gtkv => $v->{'gtk'},
2269 gtkb => $v->{'gtk-b'},
2270 gtkr => $v->{'gtk-r'});
2272 say _("Perl-GTK3 version {gtkv}.", gtkv => $v->{'gtk'});
2274 my $clawsver = ($CLAWSV eq "") ?
2275 _("Claws Mail was not found!") :
2276 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2280 # the command line help
2282 my $line = '-' x length ($xl::s{about_title});
2284 say $xl::s{about_title};
2288 _(" clawsker [options]"),
2290 _(" -a|--alternate-config-dir <dir> Uses <dir> as Claws Mail configuration."),
2291 _(" -b|--verbose More messages on standard output."),
2292 _(" -c|--clawsrc <file> Uses <file> as full resource name."),
2293 _(" -h|--help Prints this help screen and exits."),
2294 _(" -r|--read-only Disables writing changes to disk."),
2295 _(" -v|--version Prints version information and exits.")
2297 foreach (@help) { say $_ }
2300 sub parse_command_line {
2303 $CLAWSV = get_claws_version ();
2305 GetOptionsFromArray($argv,
2306 'h|help' => sub { print_help (); $cont = FALSE },
2307 'v|version' => sub { print_version (); $cont = FALSE },
2308 'b|verbose' => sub { $VERBOSE = TRUE },
2309 'r|read-only' => sub { $READONLY = TRUE },
2310 'u|use-claws-version=s' => \&opt_use_claws_version,
2311 'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2312 'c|clawsrc=s' => \&opt_clawsrc)
2313 or die _("try -h or --help for syntax.\n");
2316 my $msg = _("Error in options: {msg}\n", msg => $@);
2317 if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2318 eval { Gtk3->init };
2319 error_dialog ($msg) unless $@;
2326 sub opt_use_claws_version {
2327 my ($name, $value) = @_;
2328 die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2329 unless ($value =~ /^[\d\.]+$/);
2333 sub opt_alternate_config_dir {
2334 my ($name, $value) = @_;
2335 die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2337 $CONFIGDIR = $value;
2338 $ALTCONFIGDIR = TRUE;
2342 my ($name, $value) = @_;
2343 die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2345 set_rc_filename ($value);
2348 # update the hidden preferences status from loaded values
2349 sub init_hidden_preferences {
2350 foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2351 foreach my $key (keys %$hash) {
2352 $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2353 $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2356 foreach my $akey (keys %ACPREFS) {
2357 foreach my $key (keys %pr::acc) {
2358 my $pname = $pr::acc{$key}[NAME];
2359 $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2360 $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2363 foreach my $key (keys %pr::plu) {
2364 my $plugin = $pr::plu{$key}[PLUGIN];
2365 my $pname = $pr::plu{$key}[NAME];
2366 if (defined $PLPREFS{$plugin}) {
2367 $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2368 $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2374 # generic load/save resource files
2380 open (RCF, '<:encoding(utf8)', $rc)
2381 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2382 my $section = '_'; # default unnamed section
2387 if (/^\[([^\]]+)\]$/) { # new section
2389 die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2390 sect => $section, file => $rc) if ($data{$section});
2391 $data{$section} = {};
2392 $meta{$section}{'#'} = $line;
2394 elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2395 $data{$section}{$1} = $2;
2396 $meta{$section}{$1} = $line;
2398 elsif (/^(.*)$/) { # lone value
2399 push (@{$data{$section}{'_'}}, $1);
2403 return (\%data, \%meta);
2407 my ($rc, $data, $meta) = @_;
2408 open (RCF, '>:utf8', $rc)
2409 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2410 my @sections = keys %$data;
2411 if (defined $meta) {
2413 $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2416 foreach my $section (@sections) {
2417 say RCF "[$section]";
2418 if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2419 foreach my $val (@{$data->{$section}{'_'}}) {
2423 my @keys = keys %{$data->{$section}};
2424 if (defined $meta) {
2426 $meta->{$section}{$a} <=> $meta->{$section}{$b}
2429 foreach my $key (@keys) {
2430 my $val = $data->{$section}{$key};
2431 say RCF "$key=$val";
2439 sub backup_resource {
2441 my $rcbak = "$rc.backup";
2443 my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2444 log_message ($emsg);
2445 error_dialog ($emsg);
2447 } unless rename ($rc, $rcbak);
2454 open (RCF, '<:encoding(utf8)', $rc)
2455 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2460 if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2461 my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2462 $groups{$1}{$2} = \%data;
2463 # say "group -> $1 | path -> $2 | key -> $3";
2464 } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2465 my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2466 $groups{$1}{$2} = \%data;
2467 # say "group -> $1 | path -> $2 | key -> $3";
2476 my ($rc, $groups) = @_;
2478 foreach my $gkey (keys %$groups) {
2479 my $group = $groups->{$gkey};
2480 foreach my $akey (keys %$group) {
2481 my $data = $group->{$akey};
2482 my $key = $data->{'key'};
2483 my $line = $data->{'line'};
2484 $lines[$line] = ($data->{'enabled'})? '': '; ';
2485 $lines[$line] .= '(gtk_accel_path "<'
2486 . $gkey . '>' . $akey . '" ' . $key . ')';
2489 open (RCF, '>:utf8', $rc)
2490 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2491 say RCF '; claws-mail GtkAccelMap rc-file -*- scheme -*-';
2492 say RCF '; this file is an automated accelerator map dump';
2494 foreach (@lines) { say RCF $_ if $_ }
2498 # load current status from disc
2499 sub load_rc_preferences {
2500 my $rc = get_rc_filename ();
2501 log_message ("Loading preferences from $rc\n");
2502 return FALSE unless check_rc_file ($rc);
2503 ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2504 foreach (keys %{$CONFIGDATA->{'Common'}}) {
2505 $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2507 foreach my $plugin (@PLUGINS) {
2508 if (defined $CONFIGDATA->{$plugin}) {
2509 push (@AVPLUGINS, $plugin);
2510 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2511 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2518 sub load_ac_preferences {
2519 my $rc = get_ac_rc_filename ();
2520 log_message ("Loading account preferences from $rc\n");
2521 return FALSE unless check_rc_file ($rc);
2522 ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2523 foreach my $asect (keys %$ACCOUNTDATA) {
2524 if ($asect =~ /^Account: (\d+)$/) {
2525 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2526 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2533 sub load_hk_preferences {
2534 my $rc = get_menurc_filename ();
2535 return FALSE unless check_rc_file ($rc);
2536 $HOTKEYS = load_menurc ($rc);
2540 sub load_preferences {
2541 return FALSE unless check_claws_not_running ();
2542 return (load_rc_preferences ()
2543 and load_ac_preferences ()
2544 and load_hk_preferences ()
2548 # save current preferences to disc
2549 sub save_rc_preferences {
2550 my $rc = get_rc_filename ();
2551 log_message ("Saving preferences to $rc\n");
2552 return FALSE unless check_rc_file ($rc);
2553 return FALSE unless backup_resource ($rc);
2554 foreach (keys %PREFS) {
2555 if (defined $HPVALUE{$_}) {
2556 $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2559 foreach my $plugin (@AVPLUGINS) {
2560 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2561 if (defined $PLHPVALUE{$plugin}{$_}) {
2562 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2566 save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2570 sub save_ac_preferences {
2571 my $rc = get_ac_rc_filename ();
2572 log_message ("Saving account preferences to $rc\n");
2573 return FALSE unless check_rc_file ($rc);
2574 return FALSE unless backup_resource ($rc);
2575 foreach my $asect (keys %$ACCOUNTDATA) {
2576 if ($asect =~ /^Account: (\d+)$/) {
2577 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2578 if (defined $ACHPVALUE{$1}{$_}) {
2579 $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2584 save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2588 sub save_hk_preferences {
2589 my $rc = get_menurc_filename ();
2590 log_message ("Saving hotkey preferences to $rc\n");
2591 return FALSE unless check_rc_file ($rc);
2592 return FALSE unless backup_resource ($rc);
2593 save_menurc ($rc, $HOTKEYS);
2597 sub save_preferences {
2598 return FALSE unless check_claws_not_running ();
2599 my $result = save_rc_preferences ()
2600 and save_ac_preferences ()
2601 and save_hk_preferences ();
2602 $MODIFIED = 0 if $result;
2608 my $nb = Gtk3::Notebook->new;
2610 $nb->append_page (&new_behaviour_page, Gtk3::Label->new (_('Behaviour')));
2611 $nb->append_page (&new_colours_page, Gtk3::Label->new (_('Colours')));
2612 $nb->append_page (&new_gui_page, Gtk3::Label->new (_('GUI')));
2613 $nb->append_page (&new_other_page, Gtk3::Label->new (_('Other')));
2614 $nb->append_page (&new_winpos_page, Gtk3::Label->new (_('Windows')));
2615 $nb->append_page (&new_accounts_page, Gtk3::Label->new (_('Accounts')));
2616 $nb->append_page (&new_plugins_page, Gtk3::Label->new (_('Plugins')));
2617 $nb->append_page (&new_hotkeys_page, Gtk3::Label->new (_('Hotkeys')));
2618 $nb->append_page (&new_info_page, Gtk3::Label->new (_('Info')));
2623 # create an about dialog
2624 sub new_about_dialog {
2626 my $year = '2007-2018';
2627 my $holder = 'Ricardo Mones <ricardo@mones.org>';
2628 my $url = 'http://www.claws-mail.org/clawsker.php';
2629 my $icons = &get_app_icons;
2631 my $dialog = Gtk3::AboutDialog->new;
2632 $dialog->set_transient_for ($parent);
2633 $dialog->set_program_name ('Clawsker');
2634 $dialog->set_version ($VERSION);
2635 $dialog->set_copyright ("Copyright © $year $holder");
2636 $dialog->set_license_type ('gpl-3-0');
2637 $dialog->set_website ($url);
2638 $dialog->set_website_label (_("Visit Clawsker's web page"));
2639 # committers, by number of commits
2640 $dialog->set_authors ([
2642 'Tristan Chabredier (wwp) <subscript@free.fr>',
2643 'Andreas Rönnquist <andreas@ronnquist.net>',
2644 'Christian Hesse <mail@eworm.de>',
2646 $dialog->set_artists ([
2647 'Jesper Schultz <jesper@schultz-net.dk>',
2650 $dialog->set_documenters ([
2652 'Paul Mangan <paul@claws-mail.org>',
2654 # active translators, in alphabetical order
2655 $dialog->set_translator_credits (join ("\n",
2656 'Andreas Rönnquist <andreas@ronnquist.net>',
2657 'Axel Köllhofer <AxelKoellhofer@web.de>',
2658 'David Medina <opensusecatala@gmail.com>',
2659 'Erik P. Olsen <erik@epo.dk>',
2660 'Frederico Goncalves Guimaraes <frederico@teia.bio.br>',
2661 'Marcel Pol <marcel@timelord.nl>',
2662 'Mark Chang <mark.cyj@gmail.com>',
2663 'M. Sulchan Darmawan <bleketux@gmail.com>',
2664 'Numan Demirdöğen <if.gnu.linux@gmail.com>',
2665 'Pedro Albuquerque <pmra@gmx.com>',
2666 'Petter Adsen <petter@synth.no>',
2668 'Tristan Chabredier (wwp) <subscript@free.fr>',
2670 $dialog->set_title ($xl::s{about_title});
2671 $dialog->set_logo ($icons->[-1]);
2678 if ($MODIFIED != 0 and not $READONLY) {
2679 my $markup = "<span>" . _('There are unapplied modifications.')
2680 . "</span>\n\n<span weight=\"bold\">"
2681 . _('Do you really want to quit?') . "</span>\n";
2682 my $dialog = message_dialog (
2683 $parent, _('Clawsker warning'), $markup, 'question',
2684 [ 'gtk-no', 1, 'gtk-yes', 0 ]
2686 my $resp = $dialog->run;
2688 return TRUE if $resp;
2693 # create buttons box
2694 sub new_button_box {
2695 my ($parent, $adlg) = @_;
2696 my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2697 my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2698 my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2699 # disable button until is really implemented
2700 # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2701 my $hbox = Gtk3::HBox->new (FALSE, 5);
2703 $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2704 $b_apply->set_sensitive (not $READONLY);
2705 $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2706 # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2707 $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2709 $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2710 $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2711 # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2712 $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2718 return \@APPICONS if (@APPICONS);
2720 if (-d $DATADIR) { # installed
2721 my $dir = catdir ($DATADIR, 'icons', 'hicolor');
2723 catfile ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png')
2725 } else { # unpacked tarball or git clone
2727 catfile ('.', 'icons', $NAME . '-' . $_ . '.png');
2732 $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2733 push @APPICONS, $icon if ($icon);
2738 sub escape_key_handler {
2739 my ($widget, $event) = @_;
2740 if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {
2741 exit_handler($widget);
2748 exit unless parse_command_line ($args);
2750 $main_window = Gtk3::Window->new ('toplevel');
2751 exit unless load_preferences ();
2752 exit unless init_hidden_preferences ();
2754 my $box = Gtk3::VBox->new (FALSE, 5);
2755 $box->set_border_width(3);
2756 my $about = new_about_dialog ($main_window);
2757 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2758 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2759 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2760 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2761 $main_window->set_title (_('Claws Mail Hidden Preferences'));
2762 $main_window->set_icon_list (get_app_icons ());
2763 $main_window->add ($box);
2764 $main_window->show_all;
2770 exit Clawsker::main(\@ARGV) unless caller;