3 # Clawsker :: A Claws Mail Tweaker
4 # Copyright 2007-2022 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.
20 use Glib qw(TRUE FALSE);
23 use File::Spec::Functions;
24 use POSIX qw(setlocale);
27 use Digest::MD5 qw(md5_hex);
28 use Getopt::Long qw(GetOptionsFromArray);
30 my $NAME = 'clawsker';
31 my $PREFIX = '@PREFIX@';
32 my $LIBDIR = '@LIBDIR@';
33 my $DATADIR = '@DATADIR@';
34 my $VERSION = '@VERSION@';
40 my $main_window = undef;
43 binmode STDOUT, ":encoding(utf8)";
44 my $locale = (defined($ENV{LC_MESSAGES}) ? $ENV{LC_MESSAGES} : $ENV{LANG});
45 $locale = "C" unless defined($locale);
46 setlocale (LC_ALL, $locale);
47 bindtextdomain ($NAME, catdir ($PREFIX, 'share', 'locale'));
55 my $xla = gettext ($str);
56 if (scalar(keys(%par)) > 0) {
57 foreach my $key (keys %par) {
58 $xla =~ s/\{$key\}/$par{$key}/g;
61 return decode_utf8($xla);
66 about_title => _('Clawsker :: A Claws Mail Tweaker'),
68 l_oth_use_dlg => _('Use detached address book edit dialogue'),
69 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.'),
70 l_oth_max_use => _('Maximum memory for message cache'),
71 l_oth_max_use_units => _('kilobytes'),
72 h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kilobytes.'),
73 l_oth_min_time => _('Minimun time for cache elements'),
74 l_oth_min_time_units => _('minutes'),
75 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.'),
76 l_oth_use_netm => _('Use NetworkManager'),
77 h_oth_use_netm => _('Use NetworkManager to switch offline automatically.'),
78 l_oth_mp_rounds => _('Rounds for PBKDF2 function'),
79 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.'),
80 l_oth_imap_recl => _('Maximum depth scanning folders'),
81 h_oth_imap_recl => _('Maximum number of nested folders to be scanned on your configured IMAP servers.'),
83 l_gui_b_unread => _('Show unread messages with bold font'),
84 h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'),
85 l_gui_no_markup => _('Don\'t use markup'),
86 h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'),
87 l_gui_dot_lines => _('Use dotted lines in tree view components'),
88 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.'),
89 l_gui_h_scroll => _('Enable horizontal scrollbar'),
90 h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'),
91 l_gui_swp_from => _('Display To column instead From column in Sent folder'),
92 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.'),
93 l_gui_v_scroll => _('Folder List scrollbar behaviour'),
94 h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'),
95 l_gui_v_scroll_show => _('Show always'),
96 l_gui_v_scroll_auto => _('Automatic'),
97 l_gui_v_scroll_hide => _('Hide always'),
98 l_gui_from_show => _('From column displays'),
99 h_gui_from_show => _('Selects the data displayed in the From column of the Message List: name, address or both.'),
100 l_gui_from_show_name => _('Name only'),
101 l_gui_from_show_addr => _('Address only'),
102 l_gui_from_show_both => _('Both name and address'),
103 l_gui_strip_off => _('Coloured lines contrast'),
104 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.'),
105 l_gui_cursor_v => _('Show cursor'),
106 h_gui_cursor_v => _('Display the cursor in the Message View.'),
107 l_gui_toolbar_d => _('Detachable toolbars'),
108 h_gui_toolbar_d => _('Show handles in the toolbars.'),
109 l_gui_strip_all => _('Use stripes in all tree view components'),
110 h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'),
111 l_gui_strip_sum => _('Use stripes in Folder List and Message List'),
112 h_gui_strip_sum => _('Enable alternately coloured lines in Message List and Folder List.'),
113 l_gui_two_line_v => _('2 lines per Message List item in 3-column layout'),
114 h_gui_two_line_v => _('Spread Message List information over two lines when using the three column mode.'),
115 l_gui_margin_co => _('Show margin'),
116 h_gui_margin_co => _('Shows a small margin in the Compose View.'),
117 l_gui_mview_date => _('Don\'t display localized date'),
118 h_gui_mview_date => _('Toggles localization of date format in Message View.'),
119 l_gui_zero_char => _('Zero replacement character'),
120 h_gui_zero_char => _('Replaces \'0\' with the given character in Folder List.'),
121 l_gui_type_any => _('Editable headers'),
122 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.'),
123 l_gui_warn_send_multi => _('Warn when sending to more than'),
124 l_gui_warn_send_multi_units => _('recipients'),
125 h_gui_warn_send_multi => _('Show a warning dialogue when sending to more recipients than specified. Use 0 to disable this check.'),
126 l_gui_next_del => _('Select next message on delete'),
127 h_gui_next_del => _('When deleting a message, toggles between selecting the next one (newer message) or the previous one (older message).'),
129 l_beh_hover_t => _('Drag \'n\' drop hover timeout'),
130 l_beh_hover_t_units => _('milliseconds'),
131 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.'),
132 l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
133 h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
134 l_beh_flowed => _('Respect format=flowed in messages'),
135 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.'),
136 l_beh_parts_rw => _('Allow writable temporary files'),
137 h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
138 l_beh_skip_ssl => _('Don\'t check SSL certificates'),
139 h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
140 l_beh_up_step => _('Progress bar update step every'),
141 l_beh_up_step_units => _('items'),
142 h_beh_up_step => _('Update stepping in progress bars.'),
143 l_beh_thread_a => _('Maximum age when threading by subject'),
144 l_beh_thread_a_units => _('days'),
145 h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
146 l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
147 h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
148 l_beh_use_utf8 => _('Force UTF-8 for broken mails'),
149 h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'),
150 l_beh_warn_dnd => _('Warn on drag \'n\' drop'),
151 h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'),
152 l_beh_out_ascii => _('Outgoing messages fallback to ASCII'),
153 h_beh_out_ascii => _('If allowed by content, ASCII will be used to encode outgoing messages, otherwise the user-defined encoding is always enforced.'),
154 l_beh_pp_unsel => _('Primary paste unselects selection'),
155 h_beh_pp_unsel => _('Controls how pasting using middle-click changes the selected text and insertion point.'),
156 l_beh_inline_at => _('Show inline attachments'),
157 h_beh_inline_at => _('Allows to hide inline attachments already shown in mail structure view.'),
158 l_beh_addr_swc => _('Address search in compose window matches any'),
159 h_beh_addr_swc => _('On Tab-key completion, address text will match any part of the string or only from the start.'),
160 l_beh_fold_swc => _('Folder search in folder selector matches any'),
161 h_beh_fold_swc => _('On folder name completion text will match any part of the string or only from the start.'),
162 l_beh_rewrite_ff => _('Rewrite first \'From\' using QP encoding'),
163 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_beh_hide_tz => _('Hide time zone information'),
165 h_beh_hide_tz => _('On outgoing messages, sets time zone of date headers to the unknown timezone value "-0000", as specified by RFC 5322'),
167 l_col_emphasis => _('X-Mailer header'),
168 h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'),
169 l_col_log_err => _('Error messages'),
170 h_col_log_err => _('Colour for error messages in log window.'),
171 l_col_log_in => _('Server messages'),
172 h_col_log_in => _('Colour for messages received from servers in log window.'),
173 l_col_log_msg => _('Standard messages'),
174 h_col_log_msg => _('Colour for messages in log window.'),
175 l_col_log_out => _('Client messages'),
176 h_col_log_out => _('Colour for messages sent to servers in log window.'),
177 l_col_log_warn => _('Warning messages'),
178 h_col_log_warn => _('Colour for warning messages in log window.'),
180 l_col_tags_bg => _('Tags background'),
181 h_col_tags_bg => _('Background colour for tags in message view.'),
182 l_col_tags_text => _('Tags text'),
183 h_col_tags_text => _('Text colour for tags in message view.'),
185 l_col_default_header_bg => _('Default headers background'),
186 h_col_default_header_bg => _('Background colour for default headers in compose window.'),
187 l_col_default_header_text => _('Default headers text'),
188 h_col_default_header_text => _('Text colour for default headers in compose window.'),
190 l_col_qs_active_bg => _('Active quick search background'),
191 h_col_qs_active_bg => _('Background colour for active quick search.'),
192 l_col_qs_active_text => _('Active quick search text'),
193 h_col_qs_active_text => _('Text colour for active quick search.'),
194 l_col_qs_error_bg => _('Quick search error background'),
195 h_col_qs_error_bg => _('Background colour for quick search error.'),
196 l_col_qs_error_text => _('Quick search error text'),
197 h_col_qs_error_text => _('Text colour for quick search error.'),
199 l_col_diff_add => _('Added lines'),
200 h_col_diff_add => _('Colour for added lines in patches.'),
201 l_col_diff_del => _('Deleted lines'),
202 h_col_diff_del => _('Colour for deleted lines in patches.'),
203 l_col_diff_hunk => _('Hunk lines'),
204 h_col_diff_hunk => _('Colour for hunk headers in patches.'),
206 l_win_x => _('X position'),
207 h_win_x => _('X coordinate for window\'s top-left corner.'),
208 l_win_y => _('Y position'),
209 h_win_y => _('Y coordinate for window\'s top-left corner.'),
210 l_win_w => _('Width'),
211 h_win_w => _('Window\'s width in pixels.'),
212 l_win_h => _('Height'),
213 h_win_h => _('Window\'s height in pixels.'),
215 l_win_main_mx => _('Maximized'),
216 h_win_main_mx => _('Changes window maximized status.'),
217 l_win_main_fs => _('Full-screen'),
218 h_win_main_fs => _('Changes full screen status.'),
220 l_acc_gtls_set => _('Use custom GnuTLS priority'),
221 h_acc_gtls_set => _('Enables using user provided GnuTLS priority string.'),
222 l_acc_gtls_pri => _('GnuTLS priority'),
223 h_acc_gtls_pri => _('Value to use as GnuTLS priority string if custom priority check is enabled. Otherwise this value is ignored.'),
224 l_acc_tls_sni => _('Use TLS SNI extension'),
225 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.'),
227 l_plu_gpg_alimit => _('Autocompletion limit'),
228 h_plu_gpg_alimit => _('Limits the number of addresses obtained from keyring through autocompletion. Use 0 to get all matches.'),
229 l_plu_lav_burl => _('Base URL'),
230 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.'),
231 l_plu_lav_maxr => _('Default redirects'),
232 h_plu_lav_maxr => _('Maximum default value for HTTP redirects, if enabled'),
233 l_plu_lav_maxrmm => _('Mistery man redirects'),
234 h_plu_lav_maxrmm => _('Maximum value for HTTP redirects when using "Mistery man" mode.'),
235 l_plu_lav_maxrurl => _('Custom URL redirects'),
236 h_plu_lav_maxrurl => _('Maximum value for HTTP redirects when using a custom URL'),
238 l_plu_prl_flvb => _('Log level'),
239 h_plu_prl_flvb => _('Verbosity level of log, accumulative.'),
240 l_plu_prl_none => _('None'),
241 l_plu_prl_manual => _('Manual'),
242 l_plu_prl_action => _('Actions'),
243 l_plu_prl_match => _('Matches'),
246 # data and metadata of resource files
251 # all preferences read by load_preferences
255 # values of all preferences handled by clawsker
259 # default config dir and file name
260 my $ALTCONFIGDIR = FALSE;
261 my $CONFIGDIR = catdir ($ENV{HOME}, '.claws-mail');
262 my $CONFIGRC = 'clawsrc';
263 my $ACCOUNTRC = 'accountrc';
264 # supported and available plugins lists
265 my @PLUGINS = qw(AttRemover GPG ManageSieve Libravatar PerlPlugin);
267 # loaded hotkeys from load_menurc
269 # current tree selection
277 # index constants for preference arrays
278 NAME => 0, # the name on the rc file
279 LABEL => 1, # the label on the GUI
280 DESC => 2, # the description for the hint/help
281 TYPE => 3, # data type: bool, int, float, string, color
282 CMVER => 4, # lowest[,highest] Claws Mail version(s) the feature exists
283 CMDEF => 5, # default value for the preference in Claws Mail
284 PLUGIN => 6, # plugin section (only in plugin preferences)
285 # constants for GUI spacing
288 # for data references indexing
291 # hotkey list store columns
301 sub version_greater_or_equal {
302 my ($version, $refvers) = @_;
303 return TRUE if (length($version) == 0 and length($refvers) >= 0);
304 return FALSE if (length($version) >= 0 and length($refvers) == 0);
305 return TRUE if (version->parse($version) >= version->parse($refvers));
309 sub get_claws_version {
310 my $cm_path = which ('claws-mail') or return ""; # not found
311 open my $ph, "-|", $cm_path, "-v" or return ""; # no pipe
312 chomp (my $v = <$ph>);
314 # Claws Mail version 3.17.2git17
315 $v =~ m/\bversion\s+(\d[\w.]+)/ or die "Invalid version string: '$v'";
317 my @ver = split m/(?:\.|git)/, $cmv;
318 @ver < 4 and push @ver, 0;
319 return join ".", @ver;
322 # data handlers and auxiliar functions
324 sub handle_bool_value {
325 my ($widget, $event, $dataref) = @_;
326 $$dataref->[VALUE] = ($widget->get_active ())? '1': '0';
327 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
328 if defined $$dataref->[IVALUE];
331 sub handle_int_value {
332 my ($widget, $event, $dataref) = @_;
333 $_ = $widget->get_text ();
337 $$dataref->[VALUE] = $_;
338 $widget->set_text ($_);
341 $widget->set_text ($$dataref->[VALUE]);
343 $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
344 if defined $$dataref->[IVALUE];
347 sub handle_nchar_value {
348 my ($widget, $event, $dataref, $minlen, $maxlen) = @_;
349 $_ = substr ($widget->get_text (), 0, $maxlen);
350 $widget->set_text ($_);
351 $$dataref->[VALUE] = $_;
352 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
353 if defined $$dataref->[IVALUE];
356 sub gdk_rgba_from_str {
358 my ($rr, $gg, $bb) = (0, 0 ,0);
360 if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) {
365 my $color = Gtk3::Gdk::RGBA->new ($rr, $gg, $bb, 1.0);
369 sub str_from_gdk_rgba {
371 my $rr = $color->red * 256;
372 my $gg = $color->green * 256;
373 my $bb = $color->blue * 256;
374 my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb);
378 sub handle_color_value {
379 my ($widget, $event, $dataref) = @_;
380 my $newcol = $widget->get_rgba;
381 $$dataref->[VALUE] = str_from_gdk_rgba ($newcol);
382 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
383 if defined $$dataref->[IVALUE];
386 sub handle_selection_value {
387 my ($widget, $event, $dataref) = @_;
388 $$dataref->[VALUE] = $widget->get_active;
389 $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
390 if defined $$dataref->[IVALUE];
393 sub get_rc_filename {
394 return catfile ($CONFIGDIR, $CONFIGRC);
397 sub get_ac_rc_filename {
398 return catfile ($CONFIGDIR, $ACCOUNTRC);
401 sub get_menurc_filename {
402 return catfile ($CONFIGDIR, "menurc");
405 sub set_rc_filename {
407 my @parts = splitpath ($fullname);
408 $CONFIGRC = $parts[$#parts];
409 $parts[$#parts] = '';
410 $CONFIGDIR = catpath (@parts);
414 my ($mesg, $fatal) = @_;
415 if (defined($fatal) && $fatal eq 'die') {
416 die "$NAME: $mesg\n";
419 print "$NAME: $mesg\n";
424 my ($parent, $title, $markup, $type, $buttons) = @_;
425 my $flags = [qw/modal destroy-with-parent/];
426 my $dialog = Gtk3::Dialog->new_with_buttons (
427 $title, $parent, $flags, @$buttons
429 my $label = Gtk3::Label->new;
430 $label->set_markup ($markup);
432 if ($type eq 'error') {
433 $icon = Gtk3::Image->new_from_icon_name('dialog-error', 'GTK_ICON_SIZE_DIALOG');
434 } elsif ($type eq 'warning') {
435 $icon = Gtk3::Image->new_from_icon_name('dialog-warning', 'GTK_ICON_SIZE_DIALOG');
436 } elsif ($type eq 'question') {
437 $icon = Gtk3::Image->new_from_icon_name('dialog-question', 'GTK_ICON_SIZE_DIALOG');
439 my $hbox = Gtk3::Box->new ('horizontal', 5);
440 $hbox->pack_start ($icon, FALSE, FALSE, 5) if defined $icon;
441 $hbox->pack_start ($label, FALSE, FALSE, 5);
442 my $dialogbox = $dialog->get_content_area;
443 $dialogbox->add ($hbox);
444 $dialogbox->show_all;
450 my $markup = "<span weight=\"bold\" size=\"large\">" . $emsg . "</span>";
451 my $errordlg = message_dialog (
452 $main_window, _('Clawsker error'), $markup, 'error', [ 'gtk-cancel', 0 ]
458 sub claws_is_running {
459 my $emsg = _('Error: seems Claws Mail is currently running, close it first.');
461 error_dialog ($emsg);
465 sub has_claws_socket {
470 my $socket = catfile ($lockdir, md5_hex ($_));
471 -S $socket and return TRUE;
476 sub check_claws_not_running {
477 return TRUE if $READONLY;
478 my $rundir = $ENV{XDG_RUNTIME_DIR} // $ENV{XDG_CACHE_HOME};
479 defined $rundir and do {
480 my $lockdir = catfile ($rundir, "claws-mail");
481 return claws_is_running () if has_claws_socket ($lockdir);
483 my $tmpdir = File::Spec->tmpdir ();
484 my $lockdir = catfile ($tmpdir, "claws-mail-$<");
485 return claws_is_running () if has_claws_socket ($lockdir);
491 (defined($rcfile) && -f $rcfile) or do {
492 my $emsg = _('Error: resource file for Claws Mail was not found.');
494 error_dialog ($emsg);
500 sub set_widget_hint {
501 my ($wdgt, $hint) = @_;
502 $wdgt->set_tooltip_text ($hint);
503 $wdgt->set_has_tooltip (TRUE);
506 sub set_widget_sens {
507 my ($wdgt, $versions) = @_;
509 my @ver = split(/,/, $versions);
511 $wdgt->set_sensitive (
512 version_greater_or_equal ($CLAWSV, $ver[0])
513 and version_greater_or_equal ($ver[1], $CLAWSV)
516 $wdgt->set_sensitive (version_greater_or_equal ($CLAWSV, $ver[0]));
520 # graphic element creation
522 sub new_hbox_spaced_pack {
523 my $hbox = Gtk3::HBox->new (FALSE);
525 $hbox->pack_start ($_, FALSE, FALSE, HBOX_PAD);
530 sub new_check_button_for($$$) {
531 my ($hash, $key, $vhash) = @_;
532 my $name = $$hash{$key}[NAME];
533 my $label = $$hash{$key}[LABEL];
535 my $cb = Gtk3::CheckButton->new ($label);
536 my $value = $$vhash{$name}[VALUE];
537 $value //= $$hash{$key}[CMDEF];
538 $cb->set_active ($value eq '1');
539 $cb->signal_connect (clicked => sub {
541 handle_bool_value ($w, $e, \$$vhash{$name});
543 set_widget_hint ($cb, $$hash{$key}[DESC]);
544 set_widget_sens ($cb, $$hash{$key}[CMVER]);
546 return new_hbox_spaced_pack ($cb);
549 sub new_text_box_for_int($$$) {
550 my ($hash, $key, $vhash) = @_;
551 my $name = $$hash{$key}[NAME];
552 my $label = $$hash{$key}[LABEL];
553 my @type = split (/,/, $$hash{$key}[TYPE]);
554 push (@type, 0), push (@type, 10000) unless ($#type > 0);
557 if (ref $label eq 'ARRAY') {
558 $gunits = Gtk3::Label->new ($label->[1]);
559 $label = $label->[0];
561 my $glabel = Gtk3::Label->new ($label);
562 my $pagei = int (($type[2] - $type[1]) / 10);
563 my $gentry = Gtk3::SpinButton->new_with_range ($type[1], $type[2], $pagei);
564 my $value = $$vhash{$name}[VALUE];
565 $value //= $$hash{$key}[CMDEF];
566 $gentry->set_numeric (TRUE);
567 $gentry->set_value ($value);
568 $gentry->signal_connect('value-changed' => sub {
570 handle_int_value ($w, $e, \$$vhash{$name});
572 set_widget_hint ($gentry, $$hash{$key}[DESC]);
573 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
574 $glabel->set_sensitive ($gentry->get_sensitive);
575 $gunits->set_sensitive ($gentry->get_sensitive) if ($gunits);
577 return new_hbox_spaced_pack ($glabel, $gentry, $gunits) if ($gunits);
578 return new_hbox_spaced_pack ($glabel, $gentry);
581 sub new_text_box_for_nchar($$$) {
582 my ($hash, $key, $vhash) = @_;
583 my $name = $$hash{$key}[NAME];
584 my $label = $$hash{$key}[LABEL];
585 my @type = split (/,/, $$hash{$key}[TYPE]); # char,minlen,maxlen,width
586 my $glabel = Gtk3::Label->new ($label);
587 my $gentry = Gtk3::Entry->new;
588 $gentry->set_max_length($type[2]) if defined ($type[2]);
589 my $width = $type[3];
591 $gentry->set_width_chars(int ($width) + 2) if defined ($width);
592 my $value = $$vhash{$name}[VALUE];
593 $value //= $$hash{$key}[CMDEF];
594 $gentry->set_text ($value);
595 $gentry->signal_connect('key-release-event' => sub {
597 handle_nchar_value ($w, $e, \$$vhash{$name}, $type[1], $type[2]);
599 set_widget_hint ($gentry, $$hash{$key}[DESC]);
600 set_widget_sens ($gentry, $$hash{$key}[CMVER]);
601 $glabel->set_sensitive ($gentry->get_sensitive);
603 return new_hbox_spaced_pack ($glabel, $gentry);
606 sub new_color_button_for($$$) {
607 my ($hash, $key, $vhash) = @_;
608 my $name = $$hash{$key}[NAME];
609 my $label = $$hash{$key}[LABEL];
611 my $value = $$vhash{$name}[VALUE];
612 $value //= $$hash{$key}[CMDEF];
613 my $col = gdk_rgba_from_str ($value);
614 my $glabel = Gtk3::Label->new ($label);
615 my $button = Gtk3::ColorButton->new_with_rgba ($col);
616 $button->set_title ($label);
617 $button->set_relief ('none');
618 $button->signal_connect ('color-set' => sub {
620 handle_color_value ($w, $e, \$$vhash{$name});
622 set_widget_hint ($button, $$hash{$key}[DESC]);
623 set_widget_sens ($button, $$hash{$key}[CMVER]);
624 $glabel->set_sensitive ($button->get_sensitive);
626 return new_hbox_spaced_pack ($button, $glabel);
629 sub new_selection_box_for($$$) {
630 my ($hash, $key, $vhash) = @_;
631 my $name = $$hash{$key}[NAME];
632 my $label = $$hash{$key}[LABEL];
634 my $glabel = Gtk3::Label->new ($label);
635 my $combo = Gtk3::ComboBoxText->new;
636 my @options = split (';', $$hash{$key}[TYPE]);
637 foreach my $opt (@options) {
638 my ($index, $textkey) = split ('=', $opt);
639 $combo->insert (-1, $index, $xl::s{$textkey});
641 $combo->signal_connect ('changed' => sub {
643 handle_selection_value ($w, $e, \$$vhash{$name});
645 my $value = $$vhash{$name}[VALUE];
646 $value //= $$hash{$key}[CMDEF];
647 $combo->set_active ($value);
648 set_widget_hint ($combo, $$hash{$key}[DESC]);
649 set_widget_sens ($combo, $$hash{$key}[CMVER]);
650 $glabel->set_sensitive ($combo->get_sensitive);
652 return new_hbox_spaced_pack ($glabel, $combo);
655 # more graphic helpers
658 my ($border_w, $row_s, $col_s) = @_;
659 $border_w //= GRID_SPC;
662 my $grid = Gtk3::Grid->new;
663 $grid->set_border_width ($border_w);
664 $grid->set_row_spacing ($row_s);
665 $grid->set_column_spacing ($col_s);
672 my $label = Gtk3::Label->new ($text);
673 $label->set_alignment (0, 0.5);
680 my $label = Gtk3::Label->new ('<b>' . $text . '</b>');
681 $label->set_use_markup (TRUE);
682 $label->set_alignment (0, 0.5);
687 my ($width, $height, $widget) = @_;
688 my $grid = new_grid ();
689 $grid->set_column_homogeneous (TRUE);
690 for (my $i = 0; $i < $width; ++$i) {
691 for (my $j = 0; $j < $height; ++$j) {
692 my $wid = $widget->[$j]->[$i];
693 next unless defined $wid;
694 my $ww = (($i + 1 < $width) and (defined $widget->[$j]->[$i + 1]))
698 $grid->attach ($wid, $i, $j, $ww, 1);
699 } else { # not a widget
700 if ('--' eq $wid) { # a separator
701 $grid->attach (Gtk3::Separator->new ('horizontal'),
703 } else { # or a title
704 $grid->attach (new_title ($wid),
711 my $swin = Gtk3::ScrolledWindow->new;
712 $swin->set_border_width (0);
713 $swin->set_shadow_type ('none');
714 $swin->set_policy ('automatic', 'automatic');
721 # preference maps and corresponding page creation subs
723 %pr::oth = ( # other preferences
725 'addressbook_use_editaddress_dialog',
726 $xl::s{l_oth_use_dlg},
727 $xl::s{h_oth_use_dlg},
733 'cache_max_mem_usage',
734 [ $xl::s{l_oth_max_use}, $xl::s{l_oth_max_use_units} ],
735 $xl::s{h_oth_max_use},
736 'int,0,524288', # 0 Kb - 512 Mb
741 'cache_min_keep_time',
742 [ $xl::s{l_oth_min_time}, $xl::s{l_oth_min_time_units} ],
743 $xl::s{h_oth_min_time},
744 'int,0,120', # 0 minutes - 2 hours
749 'use_networkmanager',
750 $xl::s{l_oth_use_netm},
751 $xl::s{h_oth_use_netm},
757 'master_passphrase_pbkdf2_rounds',
758 $xl::s{l_oth_mp_rounds},
759 $xl::s{h_oth_mp_rounds},
765 'imap_scan_tree_recurs_limit',
766 $xl::s{l_oth_imap_recl},
767 $xl::s{h_oth_imap_recl},
774 sub new_other_page() {
775 return new_grid_pack (1, 15, [
776 [ _('Addressbook') ],
777 [ new_check_button_for(\%pr::oth, 'use_dlg', \%HPVALUE) ],
780 [ new_text_box_for_int(\%pr::oth, 'max_use', \%HPVALUE) ],
781 [ new_text_box_for_int(\%pr::oth, 'min_time', \%HPVALUE) ],
783 [ _('NetworkManager') ],
784 [ new_check_button_for(\%pr::oth, 'use_netm', \%HPVALUE) ],
786 [ _('Master passphrase') ],
787 [ new_text_box_for_int(\%pr::oth, 'mp_rounds', \%HPVALUE) ],
790 [ new_text_box_for_int(\%pr::oth, 'imap_recl', \%HPVALUE) ]
794 %pr::gui = ( # gui bells and whistles
797 $xl::s{l_gui_b_unread},
798 $xl::s{h_gui_b_unread},
805 $xl::s{l_gui_no_markup},
806 $xl::s{h_gui_no_markup},
812 'enable_dotted_lines',
813 $xl::s{l_gui_dot_lines},
814 $xl::s{h_gui_dot_lines},
816 '2.4.0.115,3.7.10.44',
821 $xl::s{l_gui_h_scroll},
822 $xl::s{h_gui_h_scroll},
829 $xl::s{l_gui_swp_from},
830 $xl::s{h_gui_swp_from},
836 'folderview_vscrollbar_policy',
837 $xl::s{l_gui_v_scroll},
838 $xl::s{h_gui_v_scroll},
839 '0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
845 $xl::s{l_gui_from_show},
846 $xl::s{h_gui_from_show},
847 '0=l_gui_from_show_name;1=l_gui_from_show_addr;2=l_gui_from_show_both',
852 'stripes_color_offset',
853 $xl::s{l_gui_strip_off},
854 $xl::s{h_gui_strip_off},
855 'int,0,40000', # no idea what this number means
860 'textview_cursor_visible',
861 $xl::s{l_gui_cursor_v},
862 $xl::s{h_gui_cursor_v},
868 'toolbar_detachable',
869 $xl::s{l_gui_toolbar_d},
870 $xl::s{h_gui_toolbar_d},
876 'use_stripes_everywhere',
877 $xl::s{l_gui_strip_all},
878 $xl::s{h_gui_strip_all},
884 'use_stripes_in_summaries',
885 $xl::s{l_gui_strip_sum},
886 $xl::s{h_gui_strip_sum},
893 $xl::s{l_gui_two_line_v},
894 $xl::s{h_gui_two_line_v},
900 'show_compose_margin',
901 $xl::s{l_gui_margin_co},
902 $xl::s{h_gui_margin_co},
908 'msgview_date_format',
909 $xl::s{l_gui_mview_date},
910 $xl::s{h_gui_mview_date},
916 'zero_replacement_char',
917 $xl::s{l_gui_zero_char},
918 $xl::s{h_gui_zero_char},
925 $xl::s{l_gui_type_any},
926 $xl::s{h_gui_type_any},
932 'warn_sending_many_recipients_num',
933 [ $xl::s{l_gui_warn_send_multi}, $xl::s{l_gui_warn_send_multi_units} ],
934 $xl::s{h_gui_warn_send_multi},
941 $xl::s{l_gui_next_del},
942 $xl::s{h_gui_next_del},
950 return new_grid_pack (2, 24, [
951 [ _('Coloured stripes') ],
952 [ new_check_button_for (\%pr::gui, 'strip_all', \%HPVALUE),
953 new_check_button_for (\%pr::gui, 'strip_sum', \%HPVALUE) ],
954 [ new_text_box_for_int (\%pr::gui, 'strip_off', \%HPVALUE) ],
956 [ _('Message List') ],
957 [ new_check_button_for (\%pr::gui, 'b_unread', \%HPVALUE),
958 new_check_button_for (\%pr::gui, 'swp_from', \%HPVALUE) ],
959 [ new_check_button_for (\%pr::gui, 'two_linev', \%HPVALUE),
960 new_check_button_for (\%pr::gui, 'next_del', \%HPVALUE) ],
961 [ new_selection_box_for (\%pr::gui, 'from_show', \%HPVALUE) ],
963 [ _('Message View') ],
964 [ new_check_button_for (\%pr::gui, 'cursor_v', \%HPVALUE),
965 new_check_button_for (\%pr::gui, 'mview_date', \%HPVALUE) ],
967 [ _('Compose window') ],
968 [ new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
969 new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE) ],
970 [ new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE),
971 new_text_box_for_int (\%pr::gui, 'warn_send_multi', \%HPVALUE) ],
973 [ _('Scroll bars') ],
974 [ new_check_button_for (\%pr::gui, 'h_scroll', \%HPVALUE),
975 new_selection_box_for (\%pr::gui, 'v_scroll', \%HPVALUE) ],
978 [ new_check_button_for (\%pr::gui, 'dot_lines', \%HPVALUE),
979 new_check_button_for (\%pr::gui, 'toolbar_d', \%HPVALUE) ],
980 [ new_text_box_for_nchar (\%pr::gui, 'zero_char', \%HPVALUE) ]
984 %pr::beh = ( # tweak some behaviour
987 [ $xl::s{l_beh_hover_t}, $xl::s{l_beh_hover_t_units} ],
988 $xl::s{h_beh_hover_t},
989 'int,100,3000', # 0.1 seconds - 3 seconds
995 $xl::s{l_beh_dangerous},
996 $xl::s{h_beh_dangerous},
1002 'respect_flowed_format',
1003 $xl::s{l_beh_flowed},
1004 $xl::s{h_beh_flowed},
1010 'save_parts_readwrite',
1011 $xl::s{l_beh_parts_rw},
1012 $xl::s{h_beh_parts_rw},
1018 'skip_ssl_cert_check',
1019 $xl::s{l_beh_skip_ssl},
1020 $xl::s{h_beh_skip_ssl},
1026 'statusbar_update_step',
1027 [ $xl::s{l_beh_up_step}, $xl::s{l_beh_up_step_units} ],
1028 $xl::s{h_beh_up_step},
1029 'int,1,200', # 1 item - 200 items
1034 'thread_by_subject_max_age',
1035 [ $xl::s{l_beh_thread_a}, $xl::s{l_beh_thread_a_units} ],
1036 $xl::s{h_beh_thread_a},
1037 'int,1,30', # 1 day - 30 days
1043 $xl::s{l_beh_unsafe_ssl},
1044 $xl::s{h_beh_unsafe_ssl},
1050 'utf8_instead_of_locale_for_broken_mail',
1051 $xl::s{l_beh_use_utf8},
1052 $xl::s{h_beh_use_utf8},
1059 $xl::s{l_beh_warn_dnd},
1060 $xl::s{h_beh_warn_dnd},
1066 'outgoing_fallback_to_ascii',
1067 $xl::s{l_beh_out_ascii},
1068 $xl::s{h_beh_out_ascii},
1074 'primary_paste_unselects',
1075 $xl::s{l_beh_pp_unsel},
1076 $xl::s{h_beh_pp_unsel},
1082 'show_inline_attachments',
1083 $xl::s{l_beh_inline_at},
1084 $xl::s{h_beh_inline_at},
1090 'address_search_wildcard',
1091 $xl::s{l_beh_addr_swc},
1092 $xl::s{h_beh_addr_swc},
1098 'folder_search_wildcard',
1099 $xl::s{l_beh_fold_swc},
1100 $xl::s{h_beh_fold_swc},
1106 'rewrite_first_from',
1107 $xl::s{l_beh_rewrite_ff},
1108 $xl::s{h_beh_rewrite_ff},
1115 $xl::s{l_beh_hide_tz},
1116 $xl::s{h_beh_hide_tz},
1123 sub new_behaviour_page() {
1124 return new_grid_pack (2, 21, [
1125 [ _('Drag \'n\' drop') ],
1126 [ new_text_box_for_int (\%pr::beh, 'hover_t', \%HPVALUE) ],
1127 [ new_check_button_for (\%pr::beh, 'warn_dnd', \%HPVALUE) ],
1129 [ _('Secure Sockets Layer') ],
1130 [ new_check_button_for (\%pr::beh, 'skip_ssl', \%HPVALUE),
1131 new_check_button_for (\%pr::beh, 'unsafe_ssl', \%HPVALUE) ],
1134 [ new_check_button_for (\%pr::beh, 'flowed', \%HPVALUE),
1135 new_check_button_for (\%pr::beh, 'out_ascii', \%HPVALUE) ],
1136 [ new_check_button_for (\%pr::beh, 'parts_rw', \%HPVALUE),
1137 new_check_button_for (\%pr::beh, 'pp_unsel', \%HPVALUE) ],
1138 [ new_check_button_for (\%pr::beh, 'use_utf8', \%HPVALUE),
1139 new_check_button_for (\%pr::beh, 'inline_at', \%HPVALUE) ],
1140 [ new_check_button_for (\%pr::beh, 'dangerous', \%HPVALUE),
1141 new_check_button_for (\%pr::beh, 'rewrite_ff', \%HPVALUE) ],
1142 [ new_check_button_for (\%pr::beh, 'hide_tz', \%HPVALUE) ],
1144 [ _('Completion') ],
1145 [ new_check_button_for (\%pr::beh, 'addr_swc', \%HPVALUE) ],
1146 [ new_check_button_for (\%pr::beh, 'fold_swc', \%HPVALUE) ],
1149 [ new_text_box_for_int (\%pr::beh, 'up_step', \%HPVALUE) ],
1150 [ new_text_box_for_int (\%pr::beh, 'thread_a', \%HPVALUE) ]
1154 %pr::col = ( # a variety of colours
1157 $xl::s{l_col_emphasis},
1158 $xl::s{h_col_emphasis},
1165 $xl::s{l_col_log_err},
1166 $xl::s{h_col_log_err},
1173 $xl::s{l_col_log_in},
1174 $xl::s{h_col_log_in},
1181 $xl::s{l_col_log_msg},
1182 $xl::s{h_col_log_msg},
1189 $xl::s{l_col_log_out},
1190 $xl::s{h_col_log_out},
1197 $xl::s{l_col_log_warn},
1198 $xl::s{h_col_log_warn},
1205 $xl::s{l_col_diff_add},
1206 $xl::s{h_col_diff_add},
1208 '3.8.0.54,3.17.4.35',
1212 'diff_deleted_color',
1213 $xl::s{l_col_diff_del},
1214 $xl::s{h_col_diff_del},
1216 '3.8.0.54,3.17.4.35',
1221 $xl::s{l_col_diff_hunk},
1222 $xl::s{h_col_diff_hunk},
1224 '3.8.0.54,3.17.4.35',
1229 $xl::s{l_col_tags_bg},
1230 $xl::s{h_col_tags_bg},
1237 $xl::s{l_col_tags_text},
1238 $xl::s{h_col_tags_text},
1243 default_header_bg => [
1244 'default_header_bgcolor',
1245 $xl::s{l_col_default_header_bg},
1246 $xl::s{h_col_default_header_bg},
1251 default_header_text => [
1252 'default_header_color',
1253 $xl::s{l_col_default_header_text},
1254 $xl::s{h_col_default_header_text},
1260 'qs_active_bgcolor',
1261 $xl::s{l_col_qs_active_bg},
1262 $xl::s{h_col_qs_active_bg},
1269 $xl::s{l_col_qs_active_text},
1270 $xl::s{h_col_qs_active_text},
1277 $xl::s{l_col_qs_error_bg},
1278 $xl::s{h_col_qs_error_bg},
1285 $xl::s{l_col_qs_error_text},
1286 $xl::s{h_col_qs_error_text},
1293 sub new_colours_page() {
1294 return new_grid_pack (3, 18, [
1295 [ _('Message View') ],
1296 [ new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE) ],
1297 [ new_color_button_for (\%pr::col, 'tags_text', \%HPVALUE) ,
1298 new_color_button_for (\%pr::col, 'tags_bg', \%HPVALUE) ],
1300 [ _('Log window') ],
1301 [ new_color_button_for (\%pr::col, 'log_err', \%HPVALUE) ,
1302 new_color_button_for (\%pr::col, 'log_in', \%HPVALUE) ],
1303 [ new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE) ,
1304 new_color_button_for (\%pr::col, 'log_out', \%HPVALUE) ],
1305 [ new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE) ],
1307 [ _('Viewing patches') ],
1308 [ new_color_button_for (\%pr::col, 'diff_add', \%HPVALUE) ,
1309 new_color_button_for (\%pr::col, 'diff_del', \%HPVALUE) ,
1310 new_color_button_for (\%pr::col, 'diff_hunk', \%HPVALUE) ],
1312 [ _('Compose window') ],
1313 [ new_color_button_for (\%pr::col, 'default_header_text', \%HPVALUE) ,
1314 new_color_button_for (\%pr::col, 'default_header_bg', \%HPVALUE) ],
1316 [ _('Quick search') ],
1317 [ new_color_button_for (\%pr::col, 'qs_active_text', \%HPVALUE) ,
1318 new_color_button_for (\%pr::col, 'qs_active_bg', \%HPVALUE) ],
1319 [ new_color_button_for (\%pr::col, 'qs_error_text', \%HPVALUE) ,
1320 new_color_button_for (\%pr::col, 'qs_error_bg', \%HPVALUE) ]
1324 %pr::win = ( # tweak window positions and/or sizes
1329 'int,0,3000', # 0 pixels - 3000 pixels
1337 'int,0,3000', # 0 pixels - 3000 pixels
1345 'int,0,3000', # 0 pixels - 3000 pixels
1353 'int,0,3000', # 0 pixels - 3000 pixels
1358 'mainwin_maximised',
1359 $xl::s{l_win_main_mx},
1360 $xl::s{h_win_main_mx},
1366 'mainwin_fullscreen',
1367 $xl::s{l_win_main_fs},
1368 $xl::s{h_win_main_fs},
1374 'main_messagewin_x',
1377 'int,0,3000', # 0 pixels - 3000 pixels
1382 'main_messagewin_y',
1385 'int,0,3000', # 0 pixels - 3000 pixels
1393 'int,0,3000', # 0 pixels - 3000 pixels
1398 'messagewin_height',
1401 'int,0,3000', # 0 pixels - 3000 pixels
1406 'uriopenerwin_width',
1409 'int,0,3000', # 0 pixels - 3000 pixels
1414 'uriopenerwin_height',
1417 'int,0,3000', # 0 pixels - 3000 pixels
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 'receivewin_height',
1450 'int,0,3000', # 0 pixels - 3000 pixels
1458 'int,0,3000', # 0 pixels - 3000 pixels
1466 'int,0,3000', # 0 pixels - 3000 pixels
1471 'folderitemwin_width',
1474 'int,0,3000', # 0 pixels - 3000 pixels
1479 'folderitemwin_height',
1482 'int,0,3000', # 0 pixels - 3000 pixels
1487 'folderselwin_width',
1490 'int,0,3000', # 0 pixels - 3000 pixels
1495 'folderselwin_height',
1498 'int,0,3000', # 0 pixels - 3000 pixels
1503 'foldersortwin_width',
1506 'int,0,3000', # 0 pixels - 3000 pixels
1511 'foldersortwin_height',
1514 'int,0,3000', # 0 pixels - 3000 pixels
1522 'int,0,3000', # 0 pixels - 3000 pixels
1530 'int,0,3000', # 0 pixels - 3000 pixels
1535 'addressbookwin_width',
1538 'int,0,3000', # 0 pixels - 3000 pixels
1543 'addressbookwin_height',
1546 'int,0,3000', # 0 pixels - 3000 pixels
1551 'addressbookeditpersonwin_width',
1554 'int,0,3000', # 0 pixels - 3000 pixels
1559 'addressbookeditpersonwin_height',
1562 'int,0,3000', # 0 pixels - 3000 pixels
1567 'addressbookeditgroupwin_width',
1570 'int,0,3000', # 0 pixels - 3000 pixels
1575 'addressbookeditgroupwin_height',
1578 'int,0,3000', # 0 pixels - 3000 pixels
1583 'addressaddwin_width',
1586 'int,0,3000', # 0 pixels - 3000 pixels
1591 'addressaddwin_height',
1594 'int,0,3000', # 0 pixels - 3000 pixels
1599 'addressbook_folderselwin_width',
1602 'int,0,3000', # 0 pixels - 3000 pixels
1607 'addressbook_folderselwin_height',
1610 'int,0,3000', # 0 pixels - 3000 pixels
1615 'editaccountwin_width',
1618 'int,0,3000', # 0 pixels - 3000 pixels
1623 'editaccountwin_height',
1626 'int,0,3000', # 0 pixels - 3000 pixels
1631 'accountswin_width',
1634 'int,0,3000', # 0 pixels - 3000 pixels
1639 'accountswin_height',
1642 'int,0,3000', # 0 pixels - 3000 pixels
1647 'filteringwin_width',
1650 'int,0,3000', # 0 pixels - 3000 pixels
1655 'filteringwin_height',
1658 'int,0,3000', # 0 pixels - 3000 pixels
1663 'filteringactionwin_width',
1666 'int,0,3000', # 0 pixels - 3000 pixels
1671 'filteringactionwin_height',
1674 'int,0,3000', # 0 pixels - 3000 pixels
1679 'filtering_debugwin_width',
1682 'int,0,3000', # 0 pixels - 3000 pixels
1687 'filtering_debugwin_height',
1690 'int,0,3000', # 0 pixels - 3000 pixels
1698 'int,0,3000', # 0 pixels - 3000 pixels
1703 'matcherwin_height',
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 'templateswin_width',
1730 'int,0,3000', # 0 pixels - 3000 pixels
1735 'templateswin_height',
1738 'int,0,3000', # 0 pixels - 3000 pixels
1746 'int,0,3000', # 0 pixels - 3000 pixels
1751 'actionswin_height',
1754 'int,0,3000', # 0 pixels - 3000 pixels
1759 'actionsiodialog_width',
1762 'int,0,3000', # 0 pixels - 3000 pixels
1767 'actionsiodialog_height',
1770 'int,0,3000', # 0 pixels - 3000 pixels
1778 'int,0,3000', # 0 pixels - 3000 pixels
1786 'int,0,3000', # 0 pixels - 3000 pixels
1794 'int,0,3000', # 0 pixels - 3000 pixels
1802 'int,0,3000', # 0 pixels - 3000 pixels
1810 'int,0,3000', # 0 pixels - 3000 pixels
1815 'pluginswin_height',
1818 'int,0,3000', # 0 pixels - 3000 pixels
1826 'int,0,3000', # 0 pixels - 3000 pixels
1834 'int,0,3000', # 0 pixels - 3000 pixels
1839 'print_previewwin_width',
1842 'int,0,3000', # 0 pixels - 3000 pixels
1847 'print_previewwin_height',
1850 'int,0,3000', # 0 pixels - 3000 pixels
1858 'int,0,3000', # 0 pixels - 3000 pixels
1866 'int,0,3000', # 0 pixels - 3000 pixels
1873 sub new_winpos_subpage_main() {
1874 return new_grid_pack (3, 7, [
1875 [ _('Main window'), undef ],
1876 [ new_text_box_for_int (\%pr::win, 'main_x', \%HPVALUE), undef ],
1877 [ new_text_box_for_int (\%pr::win, 'main_y', \%HPVALUE), undef ],
1878 [ new_text_box_for_int (\%pr::win, 'main_w', \%HPVALUE),
1879 new_text_box_for_int (\%pr::win, 'main_h', \%HPVALUE) ],
1880 [ new_check_button_for (\%pr::win, 'main_fs', \%HPVALUE), undef ],
1881 [ new_check_button_for (\%pr::win, 'main_mx', \%HPVALUE), undef ]
1885 sub new_winpos_subpage_msgs() {
1886 return new_grid_pack (3, 7, [
1887 [ _('Message window') ],
1888 [ new_text_box_for_int (\%pr::win, 'msgs_x', \%HPVALUE) ],
1889 [ new_text_box_for_int (\%pr::win, 'msgs_y', \%HPVALUE) ],
1890 [ new_text_box_for_int (\%pr::win, 'msgs_w', \%HPVALUE),
1891 new_text_box_for_int (\%pr::win, 'msgs_h', \%HPVALUE) ],
1893 [ _('Open URLs window') ],
1894 [ new_text_box_for_int (\%pr::win, 'urio_w', \%HPVALUE),
1895 new_text_box_for_int (\%pr::win, 'urio_h', \%HPVALUE) ]
1899 sub new_winpos_subpage_sendrecv() {
1900 return new_grid_pack (3, 5, [
1901 [ _('Send window') ],
1902 [ new_text_box_for_int (\%pr::win, 'send_w', \%HPVALUE),
1903 new_text_box_for_int (\%pr::win, 'send_h', \%HPVALUE) ],
1905 [ _('Receive window') ],
1906 [ new_text_box_for_int (\%pr::win, 'recv_w', \%HPVALUE),
1907 new_text_box_for_int (\%pr::win, 'recv_h', \%HPVALUE) ]
1911 sub new_winpos_subpage_fold() {
1912 return new_grid_pack (3, 10, [
1913 [ _('Folder window') ],
1914 [ new_text_box_for_int (\%pr::win, 'fold_x', \%HPVALUE) ],
1915 [ new_text_box_for_int (\%pr::win, 'fold_y', \%HPVALUE) ],
1916 [ new_text_box_for_int (\%pr::win, 'fold_w', \%HPVALUE),
1917 new_text_box_for_int (\%pr::win, 'fold_h', \%HPVALUE) ],
1919 [ _('Folder selection window') ],
1920 [ new_text_box_for_int (\%pr::win, 'fsel_w', \%HPVALUE),
1921 new_text_box_for_int (\%pr::win, 'fsel_h', \%HPVALUE) ],
1923 [ _('Folder sorting window') ],
1924 [ new_text_box_for_int (\%pr::win, 'fsor_w', \%HPVALUE),
1925 new_text_box_for_int (\%pr::win, 'fsor_h', \%HPVALUE) ],
1929 sub new_winpos_subpage_addrbook() {
1930 return new_grid_pack (3, 14, [
1931 [ _('Addressbook main window') ],
1932 [ new_text_box_for_int (\%pr::win, 'addr_w', \%HPVALUE),
1933 new_text_box_for_int (\%pr::win, 'addr_h', \%HPVALUE) ],
1935 [ _('Edit person window') ],
1936 [ new_text_box_for_int (\%pr::win, 'adep_w', \%HPVALUE),
1937 new_text_box_for_int (\%pr::win, 'adep_h', \%HPVALUE) ],
1939 [ _('Edit group window') ],
1940 [ new_text_box_for_int (\%pr::win, 'adeg_w', \%HPVALUE),
1941 new_text_box_for_int (\%pr::win, 'adeg_h', \%HPVALUE) ],
1943 [ _('Add address window') ],
1944 [ new_text_box_for_int (\%pr::win, 'adda_w', \%HPVALUE),
1945 new_text_box_for_int (\%pr::win, 'adda_h', \%HPVALUE) ],
1947 [ _('Folder select window') ],
1948 [ new_text_box_for_int (\%pr::win, 'addf_w', \%HPVALUE),
1949 new_text_box_for_int (\%pr::win, 'addf_h', \%HPVALUE) ]
1953 sub new_winpos_subpage_accounts() {
1954 return new_grid_pack (3, 5, [
1955 [ _('Accounts window') ],
1956 [ new_text_box_for_int (\%pr::win, 'acco_w', \%HPVALUE),
1957 new_text_box_for_int (\%pr::win, 'acco_h', \%HPVALUE) ],
1959 [ _('Edit account window') ],
1960 [ new_text_box_for_int (\%pr::win, 'acce_w', \%HPVALUE),
1961 new_text_box_for_int (\%pr::win, 'acce_h', \%HPVALUE) ]
1965 sub new_winpos_subpage_filtering() {
1966 return new_grid_pack (3, 11, [
1967 [ _('Filtering window') ],
1968 [ new_text_box_for_int (\%pr::win, 'filt_w', \%HPVALUE),
1969 new_text_box_for_int (\%pr::win, 'filt_h', \%HPVALUE) ],
1971 [ _('Filtering actions window') ],
1972 [ new_text_box_for_int (\%pr::win, 'fila_w', \%HPVALUE),
1973 new_text_box_for_int (\%pr::win, 'fila_h', \%HPVALUE) ],
1975 [ _('Filtering debug window') ],
1976 [ new_text_box_for_int (\%pr::win, 'fild_w', \%HPVALUE),
1977 new_text_box_for_int (\%pr::win, 'fild_h', \%HPVALUE) ],
1979 [ _('Matcher window') ],
1980 [ new_text_box_for_int (\%pr::win, 'matc_w', \%HPVALUE),
1981 new_text_box_for_int (\%pr::win, 'matc_h', \%HPVALUE) ]
1986 sub new_winpos_subpage_useractions() {
1987 return new_grid_pack (3, 5, [
1988 [ _('User Actions prefs window') ],
1989 [ new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
1990 new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE) ],
1992 [ _('User Actions I/O window') ],
1993 [ new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
1994 new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE) ]
1998 sub new_winpos_subpage_prefs() {
1999 return new_grid_pack (3, 11, [
2000 [ _('Preferences window') ],
2001 [ new_text_box_for_int (\%pr::win, 'pref_w', \%HPVALUE),
2002 new_text_box_for_int (\%pr::win, 'pref_h', \%HPVALUE) ],
2004 [ _('Templates window') ],
2005 [ new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
2006 new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE) ],
2008 [ _('Tags window') ],
2009 [ new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
2010 new_text_box_for_int (\%pr::win, 'tags_h', \%HPVALUE) ],
2012 [ _('Plugins window') ],
2013 [ new_text_box_for_int (\%pr::win, 'plug_w', \%HPVALUE),
2014 new_text_box_for_int (\%pr::win, 'plug_h', \%HPVALUE) ]
2018 sub new_winpos_subpage_misc() {
2019 return new_grid_pack (4, 14, [
2020 [ _('About window') ],
2021 [ new_text_box_for_int (\%pr::win, 'about_w', \%HPVALUE),
2022 new_text_box_for_int (\%pr::win, 'about_h', \%HPVALUE) ],
2024 [ _('Log window') ],
2025 [ new_text_box_for_int (\%pr::win, 'logw_w', \%HPVALUE),
2026 new_text_box_for_int (\%pr::win, 'logw_h', \%HPVALUE) ],
2028 [ _('SSL manager') ],
2029 [ new_text_box_for_int (\%pr::win, 'sslman_w', \%HPVALUE),
2030 new_text_box_for_int (\%pr::win, 'sslman_h', \%HPVALUE) ],
2032 [ _('Print preview window') ],
2033 [ new_text_box_for_int (\%pr::win, 'prin_w', \%HPVALUE),
2034 new_text_box_for_int (\%pr::win, 'prin_h', \%HPVALUE) ],
2036 [ _('View source window') ],
2037 [ new_text_box_for_int (\%pr::win, 'sour_w', \%HPVALUE),
2038 new_text_box_for_int (\%pr::win, 'sour_h', \%HPVALUE) ]
2042 sub new_winpos_page() {
2043 my $winbook = Gtk3::Notebook->new;
2044 $winbook->set_tab_pos ('right');
2045 $winbook->append_page (&new_winpos_subpage_main, new_label (_('Main')));
2046 $winbook->append_page (&new_winpos_subpage_msgs, new_label (_('Message')));
2047 $winbook->append_page (&new_winpos_subpage_sendrecv, new_label (_('Send/Receive')));
2048 $winbook->append_page (&new_winpos_subpage_fold, new_label (_('Folder')));
2049 $winbook->append_page (&new_winpos_subpage_addrbook, new_label (_('Addressbook')));
2050 $winbook->append_page (&new_winpos_subpage_accounts, new_label (_('Accounts')));
2051 $winbook->append_page (&new_winpos_subpage_filtering, new_label (_('Filtering')));
2052 $winbook->append_page (&new_winpos_subpage_useractions, new_label (_('User Actions')));
2053 $winbook->append_page (&new_winpos_subpage_prefs, new_label (_('Preferences')));
2054 $winbook->append_page (&new_winpos_subpage_misc, new_label (_('Other')));
2058 %pr::acc = ( # per account hidden preferences
2060 'gnutls_set_priority',
2061 $xl::s{l_acc_gtls_set},
2062 $xl::s{h_acc_gtls_set},
2069 $xl::s{l_acc_gtls_pri},
2070 $xl::s{h_acc_gtls_pri},
2077 $xl::s{l_acc_tls_sni},
2078 $xl::s{h_acc_tls_sni},
2085 sub new_account_subpage($) {
2087 return new_grid_pack (1, 5, [
2088 [ _('GnuTLS priority') ],
2089 [ new_check_button_for (\%pr::acc, 'tls_set', $ACHPVALUE{$akey}) ],
2090 [ new_text_box_for_nchar (\%pr::acc, 'tls_pri', $ACHPVALUE{$akey}) ],
2091 [ _('Server Name Indication') ],
2092 [ new_check_button_for (\%pr::acc, 'tls_sni', $ACHPVALUE{$akey}) ],
2096 sub new_accounts_page() {
2097 my $accbook = Gtk3::Notebook->new;
2098 $accbook->set_tab_pos ('right');
2100 $ACPREFS{$a}{'account_name'} cmp $ACPREFS{$b}{'account_name'}
2103 my $name = $ACPREFS{$_}{'account_name'};
2104 my $isdef = ($ACPREFS{$_}{'is_default'} eq '1');
2105 my $page = new_account_subpage ($_);
2106 my $label = new_label ($isdef? '<u>' . $name . '</u>': $name);
2107 $label->set_use_markup (TRUE);
2108 $accbook->append_page ($page, $label);
2110 $accbook->set_scrollable (TRUE);
2114 %pr::plu = ( # plugins hidden preferences
2120 'int,0,3000', # 0 pixels - 3000 pixels
2129 'int,0,3000', # 0 pixels - 3000 pixels
2136 'autocompletion_limit',
2137 $xl::s{l_plu_gpg_alimit},
2138 $xl::s{h_plu_gpg_alimit},
2146 'manager_win_width',
2149 'int,0,3000', # 0 pixels - 3000 pixels
2155 'manager_win_height',
2158 'int,0,3000', # 0 pixels - 3000 pixels
2166 $xl::s{l_plu_lav_burl},
2167 $xl::s{h_plu_lav_burl},
2170 'http://cdn.libravatar.org/avatar',
2175 $xl::s{l_plu_lav_maxr},
2176 $xl::s{h_plu_lav_maxr},
2184 $xl::s{l_plu_lav_maxrmm},
2185 $xl::s{h_plu_lav_maxrmm},
2192 'max_redirects_url',
2193 $xl::s{l_plu_lav_maxrurl},
2194 $xl::s{h_plu_lav_maxrurl},
2202 'filter_log_verbosity',
2203 $xl::s{l_plu_prl_flvb},
2204 $xl::s{h_plu_prl_flvb},
2205 '0=l_plu_prl_none;1=l_plu_prl_manual;2=l_plu_prl_action;3=l_plu_prl_match',
2212 'console_win_width',
2215 'int,0,3000', # 0 pixels - 3000 pixels
2221 'console_win_height',
2224 'int,0,3000', # 0 pixels - 3000 pixels
2231 sub new_plugins_page() {
2234 new_text_box_for_int (\%pr::plu, 'arm_winw', $PLHPVALUE{'AttRemover'}),
2235 new_text_box_for_int (\%pr::plu, 'arm_winh', $PLHPVALUE{'AttRemover'})
2238 new_text_box_for_int (\%pr::plu, 'gpg_alimit', $PLHPVALUE{'GPG'})
2241 new_text_box_for_int (\%pr::plu, 'msv_winw', $PLHPVALUE{'ManageSieve'}),
2242 new_text_box_for_int (\%pr::plu, 'msv_winh', $PLHPVALUE{'ManageSieve'})
2244 'Libravatar' => [new_grid_pack (2, 4, [
2245 [ new_text_box_for_nchar (\%pr::plu, 'lav_burl', $PLHPVALUE{'Libravatar'}) ],
2246 [ new_text_box_for_int (\%pr::plu, 'lav_maxr', $PLHPVALUE{'Libravatar'}) ],
2247 [ new_text_box_for_int (\%pr::plu, 'lav_maxrmm', $PLHPVALUE{'Libravatar'}) ],
2248 [ new_text_box_for_int (\%pr::plu, 'lav_maxrurl', $PLHPVALUE{'Libravatar'}) ]
2251 new_selection_box_for (\%pr::plu, 'prl_flvb', $PLHPVALUE{'PerlPlugin'})
2254 new_text_box_for_int (\%pr::plu, 'py_consw', $PLHPVALUE{'Python'}),
2255 new_text_box_for_int (\%pr::plu, 'py_consh', $PLHPVALUE{'Python'})
2258 foreach my $pk (@PLUGINS) {
2259 foreach my $wg (@{$widget{$pk}}) {
2260 $wg->set_sensitive (defined $PLHPVALUE{$pk});
2263 return new_grid_pack (3, 18, [
2264 [ _('Attachment remover') ], $widget{'AttRemover'}, [ '--' ],
2265 [ _('GPG') ], $widget{'GPG'}, [ '--' ],
2266 [ _('Sieve manager') ], $widget{'ManageSieve'}, [ '--' ],
2267 [ _('Libravatar') ], $widget{'Libravatar'}, [ '--' ],
2268 [ _('Perl') ], $widget{'PerlPlugin'}, [ '--' ],
2269 [ _('Python console') ], $widget{'Python'}
2273 sub new_hotkeys_list_label {
2274 my $renderer = Gtk3::CellRendererText->new;
2275 $renderer->set_property('alignment' => 'left');
2276 $renderer->set_property('editable' => FALSE);
2280 sub new_hotkeys_list_hotkey {
2281 my $renderer = Gtk3::CellRendererAccel->new;
2282 $renderer->set_property ('accel-mode' => 'gtk');
2283 $renderer->set_property ('editable' => TRUE);
2284 $renderer->signal_connect ('accel-edited' => sub {
2285 my ($w, $path, $key, $mods, $keycode) = @_;
2286 my $accel = Gtk3::accelerator_name ($key, $mods);
2287 my ($model, $iter) = $SELHOTKEY->get_selected ();
2288 $model->set($iter, C_HOTKEY, "\"$accel\"");
2289 my $gkey = $model->get_value ($iter, C_GROUP);
2290 my $akey = $model->get_value ($iter, C_ACCEL);
2291 my $data = $HOTKEYS->{$gkey}->{$akey};
2292 $data->{'key'} = "\"$accel\"";
2293 $data->{'enabled'} = TRUE;
2295 $renderer->signal_connect ('accel-cleared' => sub {
2296 my ($w, $path) = @_;
2297 my ($model, $iter) = $SELHOTKEY->get_selected ();
2298 $model->set($iter, C_HOTKEY, "\"\"");
2299 my $gkey = $model->get_value ($iter, C_GROUP);
2300 my $akey = $model->get_value ($iter, C_ACCEL);
2301 my $data = $HOTKEYS->{$gkey}->{$akey};
2302 $data->{'key'} = "\"\"";
2303 $data->{'enabled'} = FALSE;
2308 sub row_background_color {
2309 my ($column, $isodd) = @_;
2310 my $treeview = $column->get_tree_view;
2311 my $stylectx = $treeview->get_style_context;
2313 ? $stylectx->get_background_color ('normal')
2314 : $stylectx->get_background_color ('insensitive');
2317 sub new_hotkeys_list {
2318 my ($gkey, $group) = @_;
2319 my $store = Gtk3::ListStore->new(
2320 qw/Glib::String Glib::String Glib::String Glib::String Glib::Boolean/);
2322 foreach my $akey (sort keys %$group) {
2323 my $iter = $store->append ();
2324 my $hotkey = $group->{$akey}->{'key'};
2326 $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
2327 $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
2328 C_GROUP, $gkey, C_ACCEL, $akey, C_ODDITY, $even);
2331 my $treeview = Gtk3::TreeView->new_with_model ($store);
2333 $treeview->insert_column_with_data_func (
2334 0, _("Menu path"), new_hotkeys_list_label (),
2336 my ($col, $renderer, $model, $iter, $data) = @_;
2337 my $hkey = $model->get_value ($iter, C_HOTKEY);
2338 my $weight = ($hkey ne '""')? 'weight="bold"': '';
2339 $renderer->set_property (
2340 'markup' => "<span size=\"smaller\" $weight>"
2341 . $model->get_value ($iter, C_LABEL)
2343 my $bgcol = row_background_color (
2344 $col, $model->get_value ($iter, C_ODDITY));
2345 $renderer->set_property ('cell-background-rgba' => $bgcol);
2349 $treeview->insert_column_with_data_func (
2350 1, _('Hotkey'), new_hotkeys_list_hotkey (),
2352 my ($col, $renderer, $model, $iter, $data) = @_;
2353 my $hkey = $model->get_value ($iter, C_HOTKEY);
2355 my ($acckey, $accmod) = Gtk3::accelerator_parse ($hkey);
2356 $renderer->set_property ('accel-key' => $acckey);
2357 $renderer->set_property ('accel-mods' => $accmod);
2358 my $bgcol = row_background_color (
2359 $col, $model->get_value ($iter, C_ODDITY));
2360 $renderer->set_property ('cell-background-rgba' => $bgcol);
2363 # callback for saving current selection
2364 my $selection = $treeview->get_selection ();
2365 $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
2369 sub new_hotkeys_page() {
2370 my $hkbook = Gtk3::Notebook->new;
2371 $hkbook->set_tab_pos ('right');
2372 foreach my $gkey (sort keys %$HOTKEYS) {
2373 my $group = $HOTKEYS->{$gkey};
2374 my $keylist = new_hotkeys_list ($gkey, $group);
2375 # prepare scrolled window
2376 my $swin = Gtk3::ScrolledWindow->new;
2377 $swin->set_border_width (5);
2378 $swin->set_shadow_type ('none');
2379 $swin->set_policy ('automatic', 'automatic');
2381 $swin->add ($keylist);
2382 $hkbook->append_page ($swin, new_label ($gkey));
2387 sub new_info_page() {
2388 my $v = get_toolkit_versions ();
2389 my $cfgv = $CONFIGDATA->{'Common'}{'config_version'};
2391 return new_grid_pack (4, 11, [
2392 [ _('Library versions') ],
2393 [ new_label ('Perl-GLib'), new_title ($v->{'glib'}) ],
2394 [ new_label (_('GLib runtime')), new_title ($v->{'glib-r'}) ],
2395 [ new_label (_('GLib built')), new_title ($v->{'glib-b'}) ],
2396 [ new_label ('Perl-GTK3'), new_title ($v->{'gtk'}) ],
2397 [ new_label (_('GTK3 runtime')), new_title ($v->{'gtk-r'}) ],
2398 [ new_label (_('GTK3 built')), new_title ($v->{'gtk-b'}) ],
2400 [ _('Claws Mail versions') ],
2401 [ new_label (_('Binary')), new_title ($CLAWSV) ],
2402 [ new_label (_('Configuration')), new_title ($cfgv) ]
2407 sub get_toolkit_versions {
2409 $versions{'glib'} = $Glib::VERSION;
2410 # version info stuff appeared in 1.040
2411 if ($Glib::VERSION >= 1.040) {
2412 $versions{'glib-b'} = join('.', Glib->GET_VERSION_INFO);
2413 $versions{'glib-r'} = join('.',
2414 &Glib::major_version, &Glib::minor_version, &Glib::micro_version);
2416 $versions{'gtk'} = $Gtk3::VERSION;
2417 if ($Gtk3::VERSION >= 0.034) {
2418 $versions{'gtk-b'} = join('.', &Gtk3::GET_VERSION_INFO);
2420 $versions{'gtk-b'} = _('Not available')
2422 $versions{'gtk-r'} = join('.', &Gtk3::get_version_info);
2426 sub print_version() {
2427 say $xl::s{about_title};
2428 say _('Version:') . " $VERSION";
2429 my $v = get_toolkit_versions ();
2430 if ($v->{'glib-b'}) {
2431 say _("Perl-GLib version {glibv}, built for {glibb}, running with {glibr}.",
2432 glibv => $v->{'glib'},
2433 glibb => $v->{'glib-b'},
2434 glibr => $v->{'glib-r'});
2436 say _("Perl-GLib version {glibv}.", glibv => $v->{'glib'});
2438 if ($v->{'gtk-b'}) {
2439 say _("Perl-GTK3 version {gtkv}, built for {gtkb}, running with {gtkr}.",
2440 gtkv => $v->{'gtk'},
2441 gtkb => $v->{'gtk-b'},
2442 gtkr => $v->{'gtk-r'});
2444 say _("Perl-GTK3 version {gtkv}.", gtkv => $v->{'gtk'});
2446 my $clawsver = ($CLAWSV eq "") ?
2447 _("Claws Mail was not found!") :
2448 _("Claws Mail returned version {cmv}.", cmv => $CLAWSV);
2452 # the command line help
2454 my $line = '-' x length ($xl::s{about_title});
2456 say $xl::s{about_title};
2460 _(" clawsker [options]"),
2462 _(" -a|--alternate-config-dir <dir> Uses <dir> as Claws Mail configuration."),
2463 _(" -b|--verbose More messages on standard output."),
2464 _(" -c|--clawsrc <file> Uses <file> as full resource name."),
2465 _(" -h|--help Prints this help screen and exits."),
2466 _(" -i|--ignore-versions Allows setting almost everything."),
2467 _(" -r|--read-only Disables writing changes to disk."),
2468 _(" -s|--small-screen Forces low resolution UI adjustments."),
2469 _(" -u|--use-claws-version <ver> Uses <ver> instead of detected version."),
2470 _(" -v|--version Prints version information and exits.")
2472 foreach (@help) { say $_ }
2475 sub parse_command_line {
2478 $CLAWSV = get_claws_version ();
2480 GetOptionsFromArray($argv,
2481 'h|help' => sub { print_help (); $cont = FALSE },
2482 'v|version' => sub { print_version (); $cont = FALSE },
2483 'b|verbose' => sub { $VERBOSE = TRUE },
2484 'r|read-only' => sub { $READONLY = TRUE },
2485 's|small-screen' => sub { $LOWRES = TRUE },
2486 'i|ignore-versions' => sub { $IGNOREV = TRUE },
2487 'u|use-claws-version=s' => \&opt_use_claws_version,
2488 'a|alternate-config-dir=s' => \&opt_alternate_config_dir,
2489 'c|clawsrc=s' => \&opt_clawsrc)
2490 or die _("try -h or --help for syntax.\n");
2493 my $msg = _("Error in options: {msg}\n", msg => $@);
2494 if (defined $ENV{'DISPLAY'} and $ENV{'DISPLAY'} ne '') {
2495 eval { Gtk3->init };
2496 error_dialog ($msg) unless $@;
2503 sub opt_use_claws_version {
2504 my ($name, $value) = @_;
2505 die _("Error: {opt} requires a dotted numeric value argument\n", opt => $name)
2506 unless ($value =~ /^[\d\.]+$/);
2510 sub opt_alternate_config_dir {
2511 my ($name, $value) = @_;
2512 die _("Error: '{dir}' is not a directory or does not exist\n", dir => $value)
2514 $CONFIGDIR = $value;
2515 $ALTCONFIGDIR = TRUE;
2519 my ($name, $value) = @_;
2520 die _("Error: '{value}' is not a file or does not exist\n", value => $value)
2522 set_rc_filename ($value);
2525 # update the hidden preferences status from loaded values
2526 sub init_hidden_preferences {
2527 foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
2528 foreach my $key (keys %$hash) {
2529 $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
2530 $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
2533 foreach my $akey (keys %ACPREFS) {
2534 foreach my $key (keys %pr::acc) {
2535 my $pname = $pr::acc{$key}[NAME];
2536 $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
2537 $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
2540 foreach my $key (keys %pr::plu) {
2541 my $plugin = $pr::plu{$key}[PLUGIN];
2542 my $pname = $pr::plu{$key}[NAME];
2543 if (defined $PLPREFS{$plugin}) {
2544 $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
2545 $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
2551 # generic load/save resource files
2557 open (RCF, '<:encoding(utf8)', $rc)
2558 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2559 my $section = '_'; # default unnamed section
2564 if (/^\[([^\]]+)\]$/) { # new section
2566 die _("Error: duplicate section '{sect}' in resource file '{file}'\n",
2567 sect => $section, file => $rc) if ($data{$section});
2568 $data{$section} = {};
2569 $meta{$section}{'#'} = $line;
2571 elsif (/^([0-9a-z_]+)=(.*)$/) { # key=value
2572 $data{$section}{$1} = $2;
2573 $meta{$section}{$1} = $line;
2575 elsif (/^(.*)$/) { # lone value
2576 push (@{$data{$section}{'_'}}, $1);
2580 return (\%data, \%meta);
2584 my ($rc, $data, $meta) = @_;
2585 open (RCF, '>:utf8', $rc)
2586 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2587 my @sections = keys %$data;
2588 if (defined $meta) {
2590 $meta->{$a}{'#'} <=> $meta->{$b}{'#'}
2593 foreach my $section (@sections) {
2594 say RCF "[$section]";
2595 if (ref ($data->{$section}{'_'}) eq 'ARRAY') {
2596 foreach my $val (@{$data->{$section}{'_'}}) {
2599 delete $data->{$section}{'_'};
2601 my @keys = keys %{$data->{$section}};
2602 if (defined $meta) {
2604 $meta->{$section}{$a} <=> $meta->{$section}{$b}
2607 foreach my $key (@keys) {
2608 my $val = $data->{$section}{$key};
2609 say RCF "$key=$val";
2616 sub backup_resource {
2618 my $rcbak = "$rc.backup";
2620 my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
2621 log_message ($emsg);
2622 error_dialog ($emsg);
2624 } unless rename ($rc, $rcbak);
2631 open (RCF, '<:encoding(utf8)', $rc)
2632 or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
2637 if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2638 my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
2639 $groups{$1}{$2} = \%data;
2640 # say "group -> $1 | path -> $2 | key -> $3";
2641 } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
2642 my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
2643 $groups{$1}{$2} = \%data;
2644 # say "group -> $1 | path -> $2 | key -> $3";
2653 my ($rc, $groups) = @_;
2655 foreach my $gkey (keys %$groups) {
2656 my $group = $groups->{$gkey};
2657 foreach my $akey (keys %$group) {
2658 my $data = $group->{$akey};
2659 my $key = $data->{'key'};
2660 my $line = $data->{'line'};
2661 $lines[$line] = ($data->{'enabled'})? '': '; ';
2662 $lines[$line] .= '(gtk_accel_path "<'
2663 . $gkey . '>' . $akey . '" ' . $key . ')';
2666 open (RCF, '>:utf8', $rc)
2667 or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
2668 say RCF '; claws-mail GtkAccelMap rc-file -*- scheme -*-';
2669 say RCF '; this file is an automated accelerator map dump';
2671 foreach (@lines) { say RCF $_ if $_ }
2675 # load current status from disc
2676 sub load_rc_preferences {
2677 my $rc = get_rc_filename ();
2678 log_message ("Loading preferences from $rc\n");
2679 return FALSE unless check_rc_file ($rc);
2680 ($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
2681 foreach (keys %{$CONFIGDATA->{'Common'}}) {
2682 $PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
2684 foreach my $plugin (@PLUGINS) {
2685 if (defined $CONFIGDATA->{$plugin}) {
2686 push (@AVPLUGINS, $plugin);
2687 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2688 $PLPREFS{$plugin}{$_} = $CONFIGDATA->{$plugin}{$_};
2695 sub load_ac_preferences {
2696 my $rc = get_ac_rc_filename ();
2697 log_message ("Loading account preferences from $rc\n");
2698 return FALSE unless check_rc_file ($rc);
2699 ($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
2700 foreach my $asect (keys %$ACCOUNTDATA) {
2701 if ($asect =~ /^Account: (\d+)$/) {
2702 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2703 $ACPREFS{$1}{$_} = $ACCOUNTDATA->{$asect}{$_};
2710 sub load_hk_preferences {
2711 my $rc = get_menurc_filename ();
2712 return FALSE unless check_rc_file ($rc);
2713 $HOTKEYS = load_menurc ($rc);
2717 sub load_preferences {
2718 return FALSE unless check_claws_not_running ();
2719 return (load_rc_preferences ()
2720 and load_ac_preferences ()
2721 and load_hk_preferences ()
2725 # save current preferences to disc
2726 sub save_rc_preferences {
2727 my $rc = get_rc_filename ();
2728 log_message ("Saving preferences to $rc\n");
2729 return FALSE unless check_rc_file ($rc);
2730 return FALSE unless backup_resource ($rc);
2731 foreach (keys %PREFS) {
2732 if (defined $HPVALUE{$_}) {
2733 $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
2736 foreach my $plugin (@AVPLUGINS) {
2737 foreach (keys %{$CONFIGDATA->{$plugin}}) {
2738 if (defined $PLHPVALUE{$plugin}{$_}) {
2739 $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
2743 save_resource ($rc, $CONFIGDATA, $CONFIGMETA);
2747 sub save_ac_preferences {
2748 my $rc = get_ac_rc_filename ();
2749 log_message ("Saving account preferences to $rc\n");
2750 return FALSE unless check_rc_file ($rc);
2751 return FALSE unless backup_resource ($rc);
2752 foreach my $asect (keys %$ACCOUNTDATA) {
2753 if ($asect =~ /^Account: (\d+)$/) {
2754 foreach (keys %{$ACCOUNTDATA->{$asect}}) {
2755 if (defined $ACHPVALUE{$1}{$_}) {
2756 $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
2761 save_resource ($rc, $ACCOUNTDATA, $ACCOUNTMETA);
2765 sub save_hk_preferences {
2766 my $rc = get_menurc_filename ();
2767 log_message ("Saving hotkey preferences to $rc\n");
2768 return FALSE unless check_rc_file ($rc);
2769 return FALSE unless backup_resource ($rc);
2770 save_menurc ($rc, $HOTKEYS);
2774 sub save_preferences {
2775 return FALSE unless check_claws_not_running ();
2776 my $result = save_rc_preferences ()
2777 and save_ac_preferences ()
2778 and save_hk_preferences ();
2779 $MODIFIED = 0 if $result;
2785 my $nb = Gtk3::Notebook->new;
2787 $nb->append_page (&new_behaviour_page, Gtk3::Label->new (_('Behaviour')));
2788 $nb->append_page (&new_colours_page, Gtk3::Label->new (_('Colours')));
2789 $nb->append_page (&new_gui_page, Gtk3::Label->new (_('GUI')));
2790 $nb->append_page (&new_other_page, Gtk3::Label->new (_('Other')));
2791 $nb->append_page (&new_winpos_page, Gtk3::Label->new (_('Windows')));
2792 $nb->append_page (&new_accounts_page, Gtk3::Label->new (_('Accounts')));
2793 $nb->append_page (&new_plugins_page, Gtk3::Label->new (_('Plugins')));
2794 $nb->append_page (&new_hotkeys_page, Gtk3::Label->new (_('Hotkeys')));
2795 $nb->append_page (&new_info_page, Gtk3::Label->new (_('Info')));
2800 # create an about dialog
2801 sub new_about_dialog {
2803 my $year = '2007-2022';
2804 my $holder = 'Ricardo Mones <ricardo@mones.org>';
2805 my $url = 'http://www.claws-mail.org/clawsker.php';
2806 my $icons = &get_app_icons;
2808 my $dialog = Gtk3::AboutDialog->new;
2809 $dialog->set_transient_for ($parent);
2810 $dialog->set_program_name ('Clawsker');
2811 $dialog->set_version ($VERSION);
2812 $dialog->set_copyright ("Copyright © $year $holder");
2813 $dialog->set_license_type ('gpl-3-0');
2814 $dialog->set_website ($url);
2815 $dialog->set_website_label (_("Visit Clawsker's web page"));
2816 # committers, by number of commits
2817 $dialog->set_authors ([
2819 'Tristan Chabredier (wwp) <subscript@free.fr>',
2820 'Andreas Rönnquist <andreas@ronnquist.net>',
2821 'Christian Hesse <mail@eworm.de>',
2823 $dialog->set_artists ([
2824 'Jesper Schultz <jesper@schultz-net.dk>',
2827 $dialog->set_documenters ([
2829 'Paul Mangan <paul@claws-mail.org>',
2831 # active translators, in alphabetical order
2832 $dialog->set_translator_credits (join ("\n",
2833 'Andreas Rönnquist <andreas@ronnquist.net>',
2834 'Axel Köllhofer <AxelKoellhofer@web.de>',
2835 'David Medina <opensusecatala@gmail.com>',
2836 'Erik P. Olsen <erik@epo.dk>',
2837 'Frederico Goncalves Guimaraes <frederico@teia.bio.br>',
2838 'Marcel Pol <marcel@timelord.nl>',
2839 'Mark Chang <mark.cyj@gmail.com>',
2840 'M. Sulchan Darmawan <bleketux@gmail.com>',
2841 'Numan Demirdöğen <if.gnu.linux@gmail.com>',
2842 'Pedro Albuquerque <pmra@gmx.com>',
2843 'Petter Adsen <petter@synth.no>',
2845 'Tristan Chabredier (wwp) <subscript@free.fr>',
2847 $dialog->set_title ($xl::s{about_title});
2848 $dialog->set_logo ($icons->[-1]);
2855 if ($MODIFIED != 0 and not $READONLY) {
2856 my $markup = "<span>" . _('There are unapplied modifications.')
2857 . "</span>\n\n<span weight=\"bold\">"
2858 . _('Do you really want to quit?') . "</span>\n";
2859 my $dialog = message_dialog (
2860 $parent, _('Clawsker warning'), $markup, 'question',
2861 [ 'gtk-no', 1, 'gtk-yes', 0 ]
2863 my $resp = $dialog->run;
2865 return TRUE if $resp;
2870 # create buttons box
2871 sub new_button_box {
2872 my ($parent, $adlg) = @_;
2873 my $b_about = Gtk3::Button->new_from_stock ('gtk-about');
2874 my $b_exit = Gtk3::Button->new_from_stock ('gtk-quit');
2875 my $b_apply = Gtk3::Button->new_from_stock ('gtk-apply');
2876 # disable button until is really implemented
2877 # my $b_undo = Gtk3::Button->new_from_stock ('gtk-undo');
2878 my $hbox = Gtk3::HBox->new (FALSE, 5);
2880 $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
2881 $b_apply->set_sensitive (not $READONLY);
2882 $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
2883 # $b_undo->signal_connect (clicked => sub { undo_current_changes });
2884 $b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
2886 $hbox->pack_end ($b_apply, FALSE, FALSE, 0);
2887 $hbox->pack_end ($b_exit, FALSE, FALSE, 0);
2888 # $hbox->pack_end ($b_undo, FALSE, FALSE, 0);
2889 $hbox->pack_start ($b_about, FALSE, FALSE, 0);
2895 return \@APPICONS if (@APPICONS);
2897 if (-d $DATADIR) { # installed
2898 my $dir = catdir ($DATADIR, 'icons', 'hicolor');
2900 catfile ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png')
2902 } else { # unpacked tarball or git clone
2904 catfile ('.', 'icons', $NAME . '-' . $_ . '.png');
2909 $icon = Gtk3::Gdk::Pixbuf->new_from_file($_) if (-f $_);
2910 push @APPICONS, $icon if ($icon);
2915 sub escape_key_handler {
2916 my ($widget, $event) = @_;
2917 if ($event->keyval == Gtk3::Gdk::keyval_from_name('Escape')) {
2918 exit_handler($widget);
2922 sub get_screen_height {
2923 my $display = Gtk3::Gdk::Display::get_default();
2924 my $monitor = Gtk3::Gdk::Display::get_primary_monitor($display);
2925 my $area = Gtk3::Gdk::Monitor::get_workarea($monitor);
2926 return $area->{'height'};
2931 exit unless parse_command_line ($args);
2933 $main_window = Gtk3::Window->new ('toplevel');
2934 exit unless load_preferences ();
2935 exit unless init_hidden_preferences ();
2937 $LOWRES = TRUE unless get_screen_height() > 720;
2938 my $box = Gtk3::VBox->new (FALSE, 5);
2939 $box->set_border_width(3);
2940 my $about = new_about_dialog ($main_window);
2941 $box->pack_start (new_notebook (), TRUE, TRUE, 0);
2942 $box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
2943 $main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
2944 $main_window->signal_connect (key_press_event => \&escape_key_handler);
2945 $main_window->set_title (_('Claws Mail Hidden Preferences'));
2946 $main_window->set_icon_list (get_app_icons ());
2947 $main_window->add ($box);
2948 $main_window->show_all;
2954 exit Clawsker::main(\@ARGV) unless caller;