#!/usr/bin/perl -w
#
# Clawsker :: A Claws Mail Tweaker
-# Copyright 2007-2016 Ricardo Mones <ricardo@mones.org>
+# Copyright 2007-2018 Ricardo Mones <ricardo@mones.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
use 5.010_000;
use strict;
use utf8;
+use version 0.77;
use Glib qw(TRUE FALSE);
use Gtk2;
use POSIX qw(setlocale);
about_license => _('License:'),
about_version => _('Version:'),
+ exit_title => _('Clawsker warning'),
+ exit_fact => _('There are unapplied modifications.'),
+ exit_question => _('Do you really want to quit?'),
+
tab_colours => _('Colours'),
tab_behaviour => _('Behaviour'),
tab_gui => _('GUI'),
tab_winpos => _('Windows'),
tab_accounts => _('Accounts'),
tab_plugins => _('Plugins'),
+ tab_hotkeys => _('Hotkeys'),
tab_info => _('Info'),
ab_frame => _('Addressbook'),
netm_frame => _('NetworkManager'),
diff_frame => _('Viewing patches'),
mpass_frame => _('Master passphrase'),
+ compose_frame => _('Compose window'),
+ qs_frame => _('Quick search'),
l_oth_use_dlg => _('Use detached address book edit dialogue'),
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.'),
- l_oth_max_use => _('Maximum memory for message cache (kB)'),
+ l_oth_max_use => _('Maximum memory for message cache'),
+ l_oth_max_use_units => _('kilobytes'),
h_oth_max_use => _('The maximum amount of memory to use to cache messages, in kilobytes.'),
- l_oth_min_time => _('Minimun time for cache elements (minutes)'),
+ l_oth_min_time => _('Minimun time for cache elements'),
+ l_oth_min_time_units => _('minutes'),
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.'),
l_oth_use_netm => _('Use NetworkManager'),
h_oth_use_netm => _('Use NetworkManager to switch offline automatically.'),
h_gui_zero_char => _('Replaces \'0\' with the given character in Folder List.'),
l_gui_type_any => _('Editable headers'),
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.'),
+ l_gui_warn_send_multi => _('Warn when sending to more than'),
+ l_gui_warn_send_multi_units => _('recipients'),
+ h_gui_warn_send_multi => _('Show a warning dialogue when sending to more recipients than specified. Use 0 to disable this check.'),
l_gui_next_del => _('Select next message on delete'),
h_gui_next_del => _('When deleting a message, toggles between selecting the next one (newer message) or the previous one (older message).'),
- l_beh_hover_t => _('Drag \'n\' drop hover timeout (ms)'),
+ l_beh_hover_t => _('Drag \'n\' drop hover timeout'),
+ l_beh_hover_t_units => _('milliseconds'),
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.'),
l_beh_dangerous => _('Don\'t confirm deletions (dangerous!)'),
h_beh_dangerous => _('Don\'t ask for confirmation before definitive deletion of emails.'),
h_beh_parts_rw => _('Saves temporary files when opening attachment with write bit set.'),
l_beh_skip_ssl => _('Don\'t check SSL certificates'),
h_beh_skip_ssl => _('Disables the verification of SSL certificates.'),
- l_beh_up_step => _('Progress bar update step (items)'),
+ l_beh_up_step => _('Progress bar update step every'),
+ l_beh_up_step_units => _('items'),
h_beh_up_step => _('Update stepping in progress bars.'),
- l_beh_thread_a => _('Maximum age when threading by subject (days)'),
+ l_beh_thread_a => _('Maximum age when threading by subject'),
+ l_beh_thread_a_units => _('days'),
h_beh_thread_a => _('Number of days to include a message in a thread when using "Thread using subject in addition to standard headers".'),
l_beh_unsafe_ssl => _('Allow unsafe SSL certificates'),
h_beh_unsafe_ssl => _('Allows Claws Mail to remember multiple SSL certificates for a given server/port.'),
h_col_log_msg => _('Colour for messages in log window.'),
l_col_log_out => _('Client messages'),
h_col_log_out => _('Colour for messages sent to servers in log window.'),
- l_col_log_warn => _('Warnings'),
+ l_col_log_warn => _('Warning messages'),
h_col_log_warn => _('Colour for warning messages in log window.'),
+ l_col_tags_bg => _('Tags background'),
+ h_col_tags_bg => _('Background colour for tags in message view.'),
+ l_col_tags_text => _('Tags text'),
+ h_col_tags_text => _('Text colour for tags in message view.'),
+
+ l_col_default_header_bg => _('Default headers background'),
+ h_col_default_header_bg => _('Background colour for default headers in compose window.'),
+ l_col_default_header_text => _('Default headers text'),
+ h_col_default_header_text => _('Text colour for default headers in compose window.'),
+
+ l_col_qs_active_bg => _('Active quick search background'),
+ h_col_qs_active_bg => _('Background colour for active quick search.'),
+ l_col_qs_active_text => _('Active quick search text'),
+ h_col_qs_active_text => _('Text colour for active quick search.'),
+ l_col_qs_error_bg => _('Quick search error background'),
+ h_col_qs_error_bg => _('Background colour for quick search error.'),
+ l_col_qs_error_text => _('Quick search error text'),
+ h_col_qs_error_text => _('Text colour for quick search error.'),
+
l_col_diff_add => _('Added lines'),
h_col_diff_add => _('Colour for added lines in patches.'),
l_col_diff_del => _('Deleted lines'),
h_col_diff_del => _('Colour for deleted lines in patches.'),
l_col_diff_hunk => _('Hunk lines'),
- h_col_diff_hunk => _('Color for hunk headers in patches.'),
+ h_col_diff_hunk => _('Colour for hunk headers in patches.'),
l_win_x => _('X position'),
h_win_x => _('X coordinate for window\'s top-left corner.'),
l_plu_lav_burl => _('Base URL'),
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.'),
l_plu_prl_flvb => _('Log level'),
- h_plu_prl_flvb => _('Verbosity level of log, acumulative.'),
+ h_plu_prl_flvb => _('Verbosity level of log, accumulative.'),
l_plu_prl_none => _('None'),
l_plu_prl_manual => _('Manual'),
l_plu_prl_action => _('Actions'),
# supported and available plugins lists
my @PLUGINS = qw(AttRemover GPG ManageSieve Libravatar PerlPlugin);
my @AVPLUGINS = ();
+# loaded hotkeys from load_menurc
+my $HOTKEYS;
+# current tree selection
+my $SELHOTKEY;
# loaded icons
my @APPICONS = ();
+# modification flag
+my $MODIFIED = 0;
# index constants for preference arrays
use constant NAME => 0; # the name on the rc file
use constant PLUGIN => 6; # plugin section (only in plugin preferences)
# constants for GUI spacing
-use constant HBOX_SPC => 5;
+use constant HBOX_PAD => 5;
use constant FRAME_SPC => 2;
use constant PAGE_SPC => 5;
+# for data references indexing
+use constant VALUE => 0;
+use constant IVALUE => 1;
+
# version functions
sub version_greater_or_equal {
my ($version, $refvers) = @_;
- my @version = split (/\./, $version);
- my @refvers = split (/\./, $refvers);
- while ($#version < $#refvers) {
- push (@version, '0');
- }
- my $idx = 0;
- while (($idx <= $#refvers)
- and (int ($version[$idx]) == int ($refvers[$idx]))) {
- ++$idx;
- }
- return TRUE if (($idx > $#refvers)
- or (int ($version[$idx]) >= int ($refvers[$idx])));
+ return TRUE if (length($version) == 0 and length($refvers) >= 0);
+ return FALSE if (length($version) >= 0 and length($refvers) == 0);
+ return TRUE if (version->parse($version) >= version->parse($refvers));
return FALSE;
}
sub handle_bool_value {
my ($widget, $event, $dataref) = @_;
- $$dataref = ($widget->get_active ())? '1': '0';
+ $$dataref->[VALUE] = ($widget->get_active ())? '1': '0';
+ $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
+ if $$dataref->[IVALUE];
}
sub handle_int_value {
s/^\s+//;
s/\s+$//;
if (/^[0-9]+$/) {
- $$dataref = $_;
+ $$dataref->[VALUE] = $_;
$widget->set_text ($_);
}
else {
- $widget->set_text ($$dataref);
+ $widget->set_text ($$dataref->[VALUE]);
}
+ $MODIFIED += $$dataref->[VALUE] != $$dataref->[IVALUE]? 1: -1
+ if $$dataref->[IVALUE];
}
sub handle_string_value {
my ($widget, $event, $dataref) = @_;
- $$dataref = $widget->get_text ();
+ $$dataref->[VALUE] = $widget->get_text ();
+ $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
+ if $$dataref->[IVALUE];
}
sub handle_nchar_value {
my ($widget, $event, $dataref, $minlen, $maxlen) = @_;
$_ = substr ($widget->get_text (), 0, $maxlen);
$widget->set_text ($_);
- $$dataref = $_;
+ $$dataref->[VALUE] = $_;
+ $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
+ if $$dataref->[IVALUE];
}
sub gdk_color_from_str {
sub handle_color_value {
my ($widget, $event, $dataref) = @_;
my $newcol = $widget->get_color;
- $$dataref = str_from_gdk_color ($newcol);
+ $$dataref->[VALUE] = str_from_gdk_color ($newcol);
+ $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
+ if $$dataref->[IVALUE];
}
sub handle_selection_value {
my ($widget, $event, $dataref) = @_;
- $$dataref = $widget->get_active;
+ $$dataref->[VALUE] = $widget->get_active;
+ $MODIFIED += $$dataref->[VALUE] ne $$dataref->[IVALUE]? 1: -1
+ if $$dataref->[IVALUE];
}
sub get_rc_filename {
return $CONFIGDIR . $ACCOUNTRC;
}
+sub get_menurc_filename {
+ return $CONFIGDIR . "menurc";
+}
+
sub set_rc_filename {
my ($fullname) = @_;
my @parts = split ('/', $fullname);
# graphic element creation
+sub new_hbox_spaced_pack {
+ my $hbox = Gtk2::HBox->new (FALSE);
+ foreach (@_) {
+ $hbox->pack_start ($_, FALSE, FALSE, HBOX_PAD);
+ }
+ return $hbox;
+}
+
sub new_check_button_for($$$) {
my ($hash, $key, $vhash) = @_;
my $name = $$hash{$key}[NAME];
my $label = $$hash{$key}[LABEL];
#
- my $hbox = Gtk2::HBox->new (FALSE, 5);
my $cb = Gtk2::CheckButton->new ($label);
- my $value = $$vhash{$name};
+ my $value = $$vhash{$name}[VALUE];
$value //= $$hash{$key}[CMDEF];
$cb->set_active ($value eq '1');
$cb->signal_connect (clicked => sub {
});
set_widget_hint ($cb, $$hash{$key}[DESC]);
set_widget_sens ($cb, $$hash{$key}[CMVER]);
- $hbox->pack_start ($cb, FALSE, FALSE, HBOX_SPC);
#
- return $hbox;
+ return new_hbox_spaced_pack ($cb);
}
sub new_text_box_for_int($$$) {
my @type = split (/,/, $$hash{$key}[TYPE]);
push (@type, 0), push (@type, 10000) unless ($#type > 0);
#
- my $hbox = Gtk2::HBox->new (FALSE, 5);
+ my $gunits = undef;
+ if (ref $label eq 'ARRAY') {
+ $gunits = Gtk2::Label->new ($label->[1]);
+ $label = $label->[0];
+ }
my $glabel = Gtk2::Label->new ($label);
my $pagei = int (($type[2] - $type[1]) / 10);
my $gentry = Gtk2::SpinButton->new_with_range ($type[1], $type[2], $pagei);
- my $value = $$vhash{$name};
+ my $value = $$vhash{$name}[VALUE];
$value //= $$hash{$key}[CMDEF];
$gentry->set_numeric (TRUE);
$gentry->set_value ($value);
set_widget_hint ($gentry, $$hash{$key}[DESC]);
set_widget_sens ($gentry, $$hash{$key}[CMVER]);
$glabel->set_sensitive ($gentry->sensitive);
- $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
- $hbox->pack_start ($gentry, FALSE, FALSE, HBOX_SPC);
+ $gunits->set_sensitive ($gentry->sensitive) if ($gunits);
#
- return $hbox;
+ return new_hbox_spaced_pack ($glabel, $gentry, $gunits) if ($gunits);
+ return new_hbox_spaced_pack ($glabel, $gentry);
}
sub new_text_box_for_nchar($$$) {
my $name = $$hash{$key}[NAME];
my $label = $$hash{$key}[LABEL];
my @type = split (/,/, $$hash{$key}[TYPE]); # char,minlen,maxlen,width
- my $hbox = Gtk2::HBox->new (FALSE, 5);
my $glabel = Gtk2::Label->new ($label);
my $gentry = Gtk2::Entry->new ();
$gentry->set_max_length($type[2]) if defined ($type[2]);
my $width = $type[3];
$width //= $type[2];
$gentry->set_width_chars(int ($width) + 2) if defined ($width);
- my $value = $$vhash{$name};
+ my $value = $$vhash{$name}[VALUE];
$value //= $$hash{$key}[CMDEF];
$gentry->set_text ($value);
$gentry->signal_connect('key-release-event' => sub {
set_widget_hint ($gentry, $$hash{$key}[DESC]);
set_widget_sens ($gentry, $$hash{$key}[CMVER]);
$glabel->set_sensitive ($gentry->sensitive);
- $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
- $hbox->pack_start ($gentry, FALSE, FALSE, HBOX_SPC);
#
- return $hbox;
+ return new_hbox_spaced_pack ($glabel, $gentry);
}
sub new_color_button_for($$$) {
my $name = $$hash{$key}[NAME];
my $label = $$hash{$key}[LABEL];
#
- my $value = $$vhash{$name};
+ my $value = $$vhash{$name}[VALUE];
$value //= $$hash{$key}[CMDEF];
my $col = gdk_color_from_str ($value);
- my $hbox = Gtk2::HBox->new (FALSE, 5);
my $glabel = Gtk2::Label->new ($label);
my $button = Gtk2::ColorButton->new_with_color ($col);
$button->set_title ($label);
set_widget_hint ($button, $$hash{$key}[DESC]);
set_widget_sens ($button, $$hash{$key}[CMVER]);
$glabel->set_sensitive ($button->sensitive);
- $hbox->pack_start ($button, FALSE, FALSE, HBOX_SPC);
- $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
#
- return $hbox;
+ return new_hbox_spaced_pack ($button, $glabel);
}
sub new_selection_box_for($$$) {
my $name = $$hash{$key}[NAME];
my $label = $$hash{$key}[LABEL];
#
- my $hbox = Gtk2::HBox->new (FALSE, 5);
my $glabel = Gtk2::Label->new ($label);
my $combo = Gtk2::ComboBox->new_text;
my @options = split (';', $$hash{$key}[TYPE]);
my ($w, $e) = @_;
handle_selection_value ($w, $e, \$$vhash{$name});
});
- my $value = $$vhash{$name};
+ my $value = $$vhash{$name}[VALUE];
$value //= $$hash{$key}[CMDEF];
$combo->set_active ($value);
set_widget_hint ($combo, $$hash{$key}[DESC]);
set_widget_sens ($combo, $$hash{$key}[CMVER]);
$glabel->set_sensitive ($combo->sensitive);
- $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC);
- $hbox->pack_start ($combo, FALSE, FALSE, HBOX_SPC);
#
- return $hbox;
+ return new_hbox_spaced_pack ($glabel, $combo);
}
# more graphic helpers
sub new_hbox_pack {
- my $hbox = Gtk2::HBox->new (FALSE, 5);
+ my $hbox = Gtk2::HBox->new (FALSE);
$hbox->set_border_width (PAGE_SPC);
foreach (@_) {
$hbox->pack_start ($_, FALSE, FALSE, 0);
return $hbox;
}
+sub new_hbox_pack_compact {
+ my $hbox = Gtk2::HBox->new (FALSE);
+ $hbox->set_border_width (0);
+ foreach (@_) {
+ $hbox->pack_start ($_, FALSE, FALSE, 0);
+ }
+ return $hbox;
+}
+
sub new_vbox_pack {
my $vbox = Gtk2::VBox->new (FALSE, 5);
$vbox->set_border_width (PAGE_SPC);
],
max_use => [
'cache_max_mem_usage',
- $xl::s{l_oth_max_use},
+ [ $xl::s{l_oth_max_use}, $xl::s{l_oth_max_use_units} ],
$xl::s{h_oth_max_use},
- 'int,0,262144', # 0 Kb - 256 Mb
- '0.0.0',
+ 'int,0,524288', # 0 Kb - 512 Mb
+ '0.7.8.36',
'4096',
],
min_time => [
'cache_min_keep_time',
- $xl::s{l_oth_min_time},
+ [ $xl::s{l_oth_min_time}, $xl::s{l_oth_min_time_units} ],
$xl::s{h_oth_min_time},
'int,0,120', # 0 minutes - 2 hours
- '0.0.0',
+ '0.7.8.36',
'15',
],
use_netm => [
$xl::s{l_gui_b_unread},
$xl::s{h_gui_b_unread},
'bool',
- '0.0.0',
+ '0.5.3',
'1',
],
no_markup => [
$xl::s{l_gui_no_markup},
$xl::s{h_gui_no_markup},
'bool',
- '0.0.0',
+ '2.1.0.16',
'0',
],
dot_lines => [
$xl::s{l_gui_dot_lines},
$xl::s{h_gui_dot_lines},
'bool',
- '0.0.0,3.7.10.44',
+ '2.4.0.115,3.7.10.44',
'0',
],
h_scroll => [
$xl::s{l_gui_h_scroll},
$xl::s{h_gui_h_scroll},
'bool',
- '0.0.0',
+ '0.8.6.18',
'1',
],
swp_from => [
$xl::s{l_gui_swp_from},
$xl::s{h_gui_swp_from},
'bool',
- '0.0.0',
+ '1.9.13.40',
'0',
],
v_scroll => [
$xl::s{l_gui_v_scroll},
$xl::s{h_gui_v_scroll},
'0=l_gui_v_scroll_show;1=l_gui_v_scroll_auto;2=l_gui_v_scroll_hide',
- '0.0.0',
+ '0.7.8.14',
'0',
],
from_show => [
$xl::s{l_gui_strip_off},
$xl::s{h_gui_strip_off},
'int,0,40000', # no idea what this number means
- '0.0.0',
+ '2.4.0.186',
'4000',
],
cursor_v => [
'3.12.0.44',
'0',
],
+ warn_send_multi => [
+ 'warn_sending_many_recipients_num',
+ [ $xl::s{l_gui_warn_send_multi}, $xl::s{l_gui_warn_send_multi_units} ],
+ $xl::s{h_gui_warn_send_multi},
+ 'int,0,1000',
+ '3.14.1.125',
+ '3.15.0.28',
+ ],
next_del => [
'next_on_delete',
$xl::s{l_gui_next_del},
$xl::s{mview_frame}, 'not-packed'),
FALSE, FALSE, FRAME_SPC);
$gf->pack_start (new_subpage_frame (
- new_hbox_pack (
- new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
- new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE),
- new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE)),
+ new_vbox_pack (
+ new_hbox_pack_compact (
+ new_check_button_for (\%pr::gui, 'no_markup', \%HPVALUE),
+ new_check_button_for (\%pr::gui, 'margin_co', \%HPVALUE),
+ new_check_button_for (\%pr::gui, 'type_any', \%HPVALUE)),
+ new_text_box_for_int (\%pr::gui, 'warn_send_multi', \%HPVALUE)),
$xl::s{compo_frame}, 'not-packed'),
FALSE, FALSE, FRAME_SPC);
$gf->pack_start ($cb_dot_lines, FALSE, FALSE, 0);
%pr::beh = ( # tweak some behaviour
hover_t => [
'hover_timeout',
- $xl::s{l_beh_hover_t},
+ [ $xl::s{l_beh_hover_t}, $xl::s{l_beh_hover_t_units} ],
$xl::s{h_beh_hover_t},
'int,100,3000', # 0.1 seconds - 3 seconds
'0.0.0',
],
up_step => [
'statusbar_update_step',
- $xl::s{l_beh_up_step},
+ [ $xl::s{l_beh_up_step}, $xl::s{l_beh_up_step_units} ],
$xl::s{h_beh_up_step},
'int,1,200', # 1 item - 200 items
'0.0.0',
],
thread_a => [
'thread_by_subject_max_age',
- $xl::s{l_beh_thread_a},
+ [ $xl::s{l_beh_thread_a}, $xl::s{l_beh_thread_a_units} ],
$xl::s{h_beh_thread_a},
'int,1,30', # 1 day - 30 days
'0.0.0',
$xl::s{l_beh_use_utf8},
$xl::s{h_beh_use_utf8},
'bool',
- '0.0.0',
+ '1.9.14.49',
'0',
],
warn_dnd => [
'3.8.0.54',
'#a52a2a',
],
+ tags_bg => [
+ 'tags_bgcolor',
+ $xl::s{l_col_tags_bg},
+ $xl::s{h_col_tags_bg},
+ 'color',
+ '3.14.1.31',
+ '#f5f6be',
+ ],
+ tags_text => [
+ 'tags_color',
+ $xl::s{l_col_tags_text},
+ $xl::s{h_col_tags_text},
+ 'color',
+ '3.14.1.31',
+ '#000000',
+ ],
+ default_header_bg => [
+ 'default_header_bgcolor',
+ $xl::s{l_col_default_header_bg},
+ $xl::s{h_col_default_header_bg},
+ 'color',
+ '3.14.1.31',
+ '#f5f6be',
+ ],
+ default_header_text => [
+ 'default_header_color',
+ $xl::s{l_col_default_header_text},
+ $xl::s{h_col_default_header_text},
+ 'color',
+ '3.14.1.31',
+ '#000000',
+ ],
+ qs_active_bg => [
+ 'qs_active_bgcolor',
+ $xl::s{l_col_qs_active_bg},
+ $xl::s{h_col_qs_active_bg},
+ 'color',
+ '3.14.1.31',
+ '#f5f6be',
+ ],
+ qs_active_text => [
+ 'qs_active_color',
+ $xl::s{l_col_qs_active_text},
+ $xl::s{h_col_qs_active_text},
+ 'color',
+ '3.14.1.31',
+ '#000000',
+ ],
+ qs_error_bg => [
+ 'qs_error_bgcolor',
+ $xl::s{l_col_qs_error_bg},
+ $xl::s{h_col_qs_error_bg},
+ 'color',
+ '3.14.1.31',
+ '#ff7070',
+ ],
+ qs_error_text => [
+ 'qs_error_color',
+ $xl::s{l_col_qs_error_text},
+ $xl::s{h_col_qs_error_text},
+ 'color',
+ '3.14.1.31',
+ '#000000',
+ ],
);
sub new_colours_page() {
return new_vbox_pack (
new_subpage_frame (
new_vbox_pack (
- new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE)),
+ new_color_button_for (\%pr::col, 'emphasis', \%HPVALUE),
+ new_hbox_pack_compact (
+ new_color_button_for (\%pr::col, 'tags_text', \%HPVALUE),
+ new_color_button_for (\%pr::col, 'tags_bg', \%HPVALUE))),
$xl::s{msgview_frame}, 'not-packed'),
new_subpage_frame (
- new_vbox_pack (
- new_color_button_for (\%pr::col, 'log_err', \%HPVALUE),
- new_color_button_for (\%pr::col, 'log_in', \%HPVALUE),
- new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE),
- new_color_button_for (\%pr::col, 'log_out', \%HPVALUE),
- new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE)),
+ new_hbox_pack (
+ new_vbox_pack_compact (
+ new_color_button_for (\%pr::col, 'log_err', \%HPVALUE),
+ new_color_button_for (\%pr::col, 'log_warn', \%HPVALUE),
+ new_color_button_for (\%pr::col, 'log_msg', \%HPVALUE)),
+ new_vbox_pack_compact (
+ new_color_button_for (\%pr::col, 'log_in', \%HPVALUE),
+ new_color_button_for (\%pr::col, 'log_out', \%HPVALUE))),
$xl::s{log_frame}, 'not-packed'),
new_subpage_frame (
new_vbox_pack (
new_color_button_for (\%pr::col, 'diff_add', \%HPVALUE),
new_color_button_for (\%pr::col, 'diff_del', \%HPVALUE),
new_color_button_for (\%pr::col, 'diff_hunk', \%HPVALUE)),
- $xl::s{diff_frame}, 'not-packed')
+ $xl::s{diff_frame}, 'not-packed'),
+ new_subpage_frame (
+ new_hbox_pack (
+ new_color_button_for (\%pr::col, 'default_header_text', \%HPVALUE),
+ new_color_button_for (\%pr::col, 'default_header_bg', \%HPVALUE)),
+ $xl::s{compose_frame}, 'not-packed'),
+ new_subpage_frame (
+ new_hbox_pack (
+ new_vbox_pack_compact (
+ new_color_button_for (\%pr::col, 'qs_active_text', \%HPVALUE),
+ new_color_button_for (\%pr::col, 'qs_error_text', \%HPVALUE)),
+ new_vbox_pack_compact (
+ new_color_button_for (\%pr::col, 'qs_active_bg', \%HPVALUE),
+ new_color_button_for (\%pr::col, 'qs_error_bg', \%HPVALUE))),
+ $xl::s{qs_frame}, 'not-packed')
);
}
'0.0.0',
'-1',
],
+ acio_w => [
+ 'actionsiodialog_width',
+ $xl::s{l_win_w},
+ $xl::s{h_win_w},
+ 'int,0,3000', # 0 pixels - 3000 pixels
+ '0.0.0',
+ '582',
+ ],
+ acio_h => [
+ 'actionsiodialog_height',
+ $xl::s{l_win_h},
+ $xl::s{h_win_h},
+ 'int,0,3000', # 0 pixels - 3000 pixels
+ '0.0.0',
+ '310',
+ ],
tags_w => [
'tagswin_width',
$xl::s{l_win_w},
);
}
+sub new_winpos_subpage_useractions() {
+ return new_vbox_pack (
+ new_subpage_frame (
+ new_hbox_pack (
+ new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
+ new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE)),
+ _('User Actions prefs window'), 'not-packed'),
+ new_subpage_frame (
+ new_hbox_pack (
+ new_text_box_for_int (\%pr::win, 'acio_w', \%HPVALUE),
+ new_text_box_for_int (\%pr::win, 'acio_h', \%HPVALUE)),
+ _('User Actions I/O window'), 'not-packed')
+ );
+}
+
sub new_winpos_subpage_prefs() {
return new_vbox_pack (
new_subpage_frame (
new_text_box_for_int (\%pr::win, 'temp_w', \%HPVALUE),
new_text_box_for_int (\%pr::win, 'temp_h', \%HPVALUE)),
_('Templates window'), 'not-packed'),
- new_subpage_frame (
- new_hbox_pack (
- new_text_box_for_int (\%pr::win, 'acti_w', \%HPVALUE),
- new_text_box_for_int (\%pr::win, 'acti_h', \%HPVALUE)),
- _('Actions window'), 'not-packed'),
new_subpage_frame (
new_hbox_pack (
new_text_box_for_int (\%pr::win, 'tags_w', \%HPVALUE),
$winbook->append_page (new_winpos_subpage_addrbook, _('Addressbook'));
$winbook->append_page (new_winpos_subpage_accounts, _('Accounts'));
$winbook->append_page (new_winpos_subpage_filtering, _('Filtering'));
+ $winbook->append_page (new_winpos_subpage_useractions, _('User Actions'));
$winbook->append_page (new_winpos_subpage_prefs, _('Preferences'));
$winbook->append_page (new_winpos_subpage_misc, _('Other'));
return $winbook;
$label->set_use_markup (TRUE);
}
}
+ $accbook->set_scrollable (TRUE);
return $accbook;
}
$frame{'PerlPlugin'});
}
+use constant {
+ C_LABEL => 0,
+ C_HOTKEY => 1,
+ C_GROUP => 2,
+ C_ACCEL => 3,
+ C_BCOLOR => 4,
+ # cell backgrounds
+ BG_LIGHTER => '#ffffff',
+ BG_DARKER => '#eeeeee'
+};
+
+sub new_hotkeys_list_label {
+ my $renderer = Gtk2::CellRendererText->new ();
+ $renderer->set_property('alignment' => 'left');
+ $renderer->set_property('editable' => FALSE);
+ return $renderer;
+}
+
+sub new_hotkeys_list_hotkey {
+ my $renderer = Gtk2::CellRendererAccel->new ();
+ $renderer->set_property ('accel-mode' => 'gtk');
+ $renderer->set_property ('editable' => TRUE);
+ $renderer->signal_connect ('accel-edited' => sub {
+ my ($w, $path, $key, $mods, $keycode) = @_;
+ my $accel = Gtk2::Accelerator->name ($key, $mods);
+ my ($model, $iter) = $SELHOTKEY->get_selected ();
+ $model->set($iter, C_HOTKEY, "\"$accel\"");
+ my $gkey = $model->get_value ($iter, C_GROUP);
+ my $akey = $model->get_value ($iter, C_ACCEL);
+ my $data = $HOTKEYS->{$gkey}->{$akey};
+ $data->{'key'} = "\"$accel\"";
+ $data->{'enabled'} = TRUE;
+ });
+ $renderer->signal_connect ('accel-cleared' => sub {
+ my ($w, $path) = @_;
+ my ($model, $iter) = $SELHOTKEY->get_selected ();
+ $model->set($iter, C_HOTKEY, "\"\"");
+ my $gkey = $model->get_value ($iter, C_GROUP);
+ my $akey = $model->get_value ($iter, C_ACCEL);
+ my $data = $HOTKEYS->{$gkey}->{$akey};
+ $data->{'key'} = "\"\"";
+ $data->{'enabled'} = FALSE;
+ });
+ return $renderer;
+}
+
+sub new_hotkeys_list {
+ my ($gkey, $group) = @_;
+ my $store = Gtk2::ListStore->new(
+ qw/Glib::String Glib::String Glib::String Glib::String Glib::String/);
+ my $even = FALSE;
+ foreach my $akey (sort keys %$group) {
+ my $iter = $store->append ();
+ my $hotkey = $group->{$akey}->{'key'};
+ my $label = $akey;
+ $label =~ s/[<>]//g; # <rrsyl> and <IMAPFolder> !?
+ my $bgcol = $even ? BG_DARKER: BG_LIGHTER;
+ $store->set ($iter, C_LABEL, $label, C_HOTKEY, $hotkey,
+ C_GROUP, $gkey, C_ACCEL, $akey, C_BCOLOR, $bgcol);
+ $even = not $even;
+ }
+ my $treeview = Gtk2::TreeView->new_with_model ($store);
+ # labels column
+ $treeview->insert_column_with_data_func (
+ 0, _("Menu path"), new_hotkeys_list_label (),
+ sub {
+ my ($col, $renderer, $model, $iter, $data) = @_;
+ $renderer->set_property (
+ 'markup' => '<span size="smaller">'
+ . $model->get_value ($iter, C_LABEL)
+ . '</span>');
+ $renderer->set_property (
+ 'background' => $model->get_value ($iter, C_BCOLOR));
+ }
+ );
+ # hotkeys column
+ $treeview->insert_column_with_data_func (
+ 1, _('Hotkey'), new_hotkeys_list_hotkey (),
+ sub {
+ my ($col, $renderer, $model, $iter, $data) = @_;
+ my $hkey = $model->get_value ($iter, C_HOTKEY);
+ $hkey =~ s/\"//g;
+ my ($acckey, $accmod) = Gtk2::Accelerator->parse ($hkey);
+ $renderer->set_property ('accel-key' => $acckey);
+ $renderer->set_property ('accel-mods' => $accmod);
+ $renderer->set_property (
+ 'background' => $model->get_value ($iter, C_BCOLOR));
+ }
+ );
+ # callback for saving current selection
+ my $selection = $treeview->get_selection ();
+ $selection->signal_connect ('changed' => sub { $SELHOTKEY = shift });
+ return $treeview;
+}
+
+sub new_hotkeys_page() {
+ my $swin = Gtk2::ScrolledWindow->new ();
+ my $vbox = Gtk2::VBox->new (FALSE, 5);
+ foreach my $gkey (sort keys %$HOTKEYS) {
+ my $group = $HOTKEYS->{$gkey};
+ # group title
+ my $glabel = Gtk2::Label->new ('<b>' . $gkey . '</b>');
+ $glabel->set_use_markup (TRUE);
+ $glabel->set_alignment (0, 0.5);
+ $glabel->set_padding (5, 1);
+ $vbox->pack_start ($glabel, FALSE, FALSE, 0);
+ # group key list
+ my $keylist = new_hotkeys_list ($gkey, $group);
+ $vbox->pack_start ($keylist, FALSE, FALSE, 0);
+ }
+ $swin->set_border_width (5);
+ $swin->set_shadow_type ('none');
+ $swin->set_policy ('automatic', 'always');
+ $swin->add_with_viewport ($vbox);
+ return $swin;
+}
+
sub new_info_page() {
my $t0 = Gtk2::Table->new (7, 2, FALSE);
my $v = get_toolkit_versions ();
sub init_hidden_preferences {
foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth, \%pr::win) {
foreach my $key (keys %$hash) {
- $HPVALUE{${$hash}{$key}[NAME]} = $PREFS{${$hash}{$key}[NAME]};
+ $HPVALUE{${$hash}{$key}[NAME]}[VALUE] = $PREFS{${$hash}{$key}[NAME]};
+ $HPVALUE{${$hash}{$key}[NAME]}[IVALUE] = $PREFS{${$hash}{$key}[NAME]};
}
}
foreach my $akey (keys %ACPREFS) {
foreach my $key (keys %pr::acc) {
my $pname = $pr::acc{$key}[NAME];
- $ACHPVALUE{$akey}{$pname} = $ACPREFS{$akey}{$pname};
+ $ACHPVALUE{$akey}{$pname}[VALUE] = $ACPREFS{$akey}{$pname};
+ $ACHPVALUE{$akey}{$pname}[IVALUE] = $ACPREFS{$akey}{$pname};
}
}
foreach my $key (keys %pr::plu) {
my $plugin = $pr::plu{$key}[PLUGIN];
my $pname = $pr::plu{$key}[NAME];
if (defined $PLPREFS{$plugin}) {
- $PLHPVALUE{$plugin}{$pname} = $PLPREFS{$plugin}{$pname};
+ $PLHPVALUE{$plugin}{$pname}[VALUE] = $PLPREFS{$plugin}{$pname};
+ $PLHPVALUE{$plugin}{$pname}[IVALUE] = $PLPREFS{$plugin}{$pname};
}
}
return TRUE;
close (RCF);
}
+sub backup_resource {
+ my $rc = shift;
+ my $rcbak = "$rc.backup";
+ do {
+ my $emsg = _("Unable to create backup file '{name}'\n", name => $rcbak);
+ log_message ($emsg);
+ error_dialog ($emsg);
+ return FALSE;
+ } unless rename ($rc, $rcbak);
+ return TRUE;
+}
+
+# specific loaders
+sub load_menurc {
+ my $rc = shift;
+ open (RCF, '<:encoding(utf8)', $rc)
+ or die _("Error: opening '{file}' for reading", file => $rc) . ": $!\n";
+ my %groups = ();
+ my $line = 0;
+ while (<RCF>) {
+ chomp;
+ if (/^; \(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
+ my %data = ('key' => $3, 'enabled' => FALSE, 'line' => $line);
+ $groups{$1}{$2} = \%data;
+ # say "group -> $1 | path -> $2 | key -> $3";
+ } elsif (/^\(gtk_accel_path "<([A-Za-z]+)>([^"]+)" ([^\)]+)\)$/) {
+ my %data = ('key' => $3, 'enabled' => TRUE, 'line' => $line);
+ $groups{$1}{$2} = \%data;
+ # say "group -> $1 | path -> $2 | key -> $3";
+ }
+ ++$line;
+ }
+ close (RCF);
+ return \%groups;
+}
+
+sub save_menurc {
+ my ($rc, $groups) = @_;
+ my @lines = ();
+ foreach my $gkey (keys %$groups) {
+ my $group = $groups->{$gkey};
+ foreach my $akey (keys %$group) {
+ my $data = $group->{$akey};
+ my $key = $data->{'key'};
+ my $line = $data->{'line'};
+ $lines[$line] = ($data->{'enabled'})? '': '; ';
+ $lines[$line] .= '(gtk_accel_path "<'
+ . $gkey . '>' . $akey . '" ' . $key . ')';
+ }
+ }
+ open (RCF, '>:utf8', $rc)
+ or die _("Error: opening '{file}' for writing", file => $rc) . ": $!\n";
+ say RCF '; claws-mail GtkAccelMap rc-file -*- scheme -*-';
+ say RCF '; this file is an automated accelerator map dump';
+ say RCF ';';
+ foreach (@lines) { say RCF $_ if $_ }
+ close (RCF);
+}
+
# load current status from disc
-sub load_preferences {
+sub load_rc_preferences {
my $rc = get_rc_filename ();
log_message ("Loading preferences from $rc\n");
return FALSE unless check_rc_file ($rc);
- return FALSE unless check_claws_not_running ();
($CONFIGDATA, $CONFIGMETA) = load_resource ($rc);
foreach (keys %{$CONFIGDATA->{'Common'}}) {
$PREFS{$_} = $CONFIGDATA->{'Common'}{$_};
my $rc = get_ac_rc_filename ();
log_message ("Loading account preferences from $rc\n");
return FALSE unless check_rc_file ($rc);
- return FALSE unless check_claws_not_running ();
($ACCOUNTDATA, $ACCOUNTMETA) = load_resource ($rc);
foreach my $asect (keys %$ACCOUNTDATA) {
if ($asect =~ /^Account: (\d+)$/) {
return TRUE;
}
+sub load_hk_preferences {
+ my $rc = get_menurc_filename ();
+ return FALSE unless check_rc_file ($rc);
+ $HOTKEYS = load_menurc ($rc);
+ return TRUE;
+}
+
+sub load_preferences {
+ return FALSE unless check_claws_not_running ();
+ return (load_rc_preferences ()
+ and load_ac_preferences ()
+ and load_hk_preferences ()
+ );
+}
+
# save current preferences to disc
-sub save_preferences {
+sub save_rc_preferences {
my $rc = get_rc_filename ();
log_message ("Saving preferences to $rc\n");
return FALSE unless check_rc_file ($rc);
return FALSE unless check_claws_not_running ();
- my $rcbak = "$rc.backup";
- rename ($rc, $rcbak);
+ return FALSE unless backup_resource ($rc);
foreach (keys %PREFS) {
if (defined $HPVALUE{$_}) {
- $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_};
+ $CONFIGDATA->{'Common'}{$_} = $HPVALUE{$_}[VALUE];
}
}
foreach my $plugin (@AVPLUGINS) {
foreach (keys %{$CONFIGDATA->{$plugin}}) {
if (defined $PLHPVALUE{$plugin}{$_}) {
- $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_};
+ $CONFIGDATA->{$plugin}{$_} = $PLHPVALUE{$plugin}{$_}[VALUE];
}
}
}
log_message ("Saving account preferences to $rc\n");
return FALSE unless check_rc_file ($rc);
return FALSE unless check_claws_not_running ();
- my $rcbak = "$rc.backup";
- rename ($rc, $rcbak);
+ return FALSE unless backup_resource ($rc);
foreach my $asect (keys %$ACCOUNTDATA) {
if ($asect =~ /^Account: (\d+)$/) {
foreach (keys %{$ACCOUNTDATA->{$asect}}) {
if (defined $ACHPVALUE{$1}{$_}) {
- $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_};
+ $ACCOUNTDATA->{$asect}{$_} = $ACHPVALUE{$1}{$_}[VALUE];
}
}
}
return TRUE;
}
+sub save_hk_preferences {
+ my $rc = get_menurc_filename ();
+ log_message ("Saving hotkey preferences to $rc\n");
+ return FALSE unless check_rc_file ($rc);
+ return FALSE unless check_claws_not_running ();
+ return FALSE unless backup_resource ($rc);
+ save_menurc ($rc, $HOTKEYS);
+ return TRUE;
+}
+
+sub save_preferences {
+ my $result = save_rc_preferences ()
+ and save_ac_preferences ()
+ and save_hk_preferences ();
+ $MODIFIED = 0 if $result;
+ return $result;
+}
+
# create notebook
sub new_notebook {
my $nb = Gtk2::Notebook->new;
$nb->append_page (new_winpos_page (), $xl::s{tab_winpos});
$nb->append_page (new_accounts_page (), $xl::s{tab_accounts});
$nb->append_page (new_plugins_page (), $xl::s{tab_plugins});
+ $nb->append_page (new_hotkeys_page (), $xl::s{tab_hotkeys});
$nb->append_page (new_info_page (), $xl::s{tab_info});
return $nb;
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.";
- my $year = "2007-2016";
+ my $year = "2007-2018";
my $holder = "Ricardo Mones <ricardo\@mones.org>";
my $url = "http://www.claws-mail.org/clawsker.php";
$dialog->set_title ($xl::s{about});
if (Gtk2->CHECK_VERSION (2, 10, 0)) {
my @icons = get_app_icons ();
- my $image = Gtk2::Image->new_from_pixbuf ($icons[1]);
+ my $image = Gtk2::Image->new_from_pixbuf ($icons[-1]);
$image->show ();
$image->set_alignment (0, 0);
$dialog->set_image ($image);
return $dialog;
}
+sub exit_handler {
+ my ($parent) = @_;
+ if ($MODIFIED != 0 and not $READONLY) {
+ my $fact = $xl::s{exit_fact};
+ my $question = $xl::s{exit_question};
+ my $dialog = Gtk2::MessageDialog->new_with_markup ($parent,
+ [qw/modal destroy-with-parent/],
+ 'warning', 'yes-no',
+ "<span>$fact</span>\n\n"
+ . "<span weight=\"bold\">$question</span>");
+ $dialog->set_title ($xl::s{exit_title});
+ my $resp = $dialog->run;
+ $dialog->hide;
+ return TRUE if ($resp eq 'no');
+ }
+ Gtk2->main_quit;
+}
+
# create buttons box
sub new_button_box {
my ($parent, $adlg) = @_;
# my $b_undo = Gtk2::Button->new_from_stock ('gtk-undo');
my $hbox = Gtk2::HBox->new (FALSE, 5);
# signal handlers
- $b_exit->signal_connect (clicked => sub { Gtk2->main_quit });
+ $b_exit->signal_connect (clicked => sub { exit_handler($parent) });
$b_apply->set_sensitive (not $READONLY);
- $b_apply->signal_connect (clicked => sub {
- save_preferences ($parent);
- save_ac_preferences ($parent);
- });
+ $b_apply->signal_connect (clicked => sub { save_preferences($parent) });
# $b_undo->signal_connect (clicked => sub { undo_current_changes });
$b_about->signal_connect (clicked => sub { $adlg->run; $adlg->hide });
# package them
my $dir = $DATADIR . '/icons/hicolor';
@names = map {
join ('/', ($dir, $_ . 'x' . $_, 'apps', $NAME . '.png'))
- } (64, 128);
+ } (48, 64, 128);
} else { # unpacked tarball or git clone
@names = map {
join ('/', ('./icons', $NAME . '-' . $_ . '.png'));
- } (64, 128);
+ } (48, 64, 128);
}
foreach (@names) {
my $icon = undef;
return @APPICONS;
}
+sub escape_key_handler {
+ my ($widget, $event) = @_;
+ if ($event->keyval == Gtk2::Gdk->keyval_from_name('Escape')) {
+ exit_handler($widget);
+ }
+}
+
# initialise
exit unless parse_command_line ();
Gtk2->init;
$main_window = Gtk2::Window->new ('toplevel');
exit unless load_preferences ();
-exit unless load_ac_preferences ();
exit unless init_hidden_preferences ();
# create main GUI
my $box = Gtk2::VBox->new (FALSE, 5);
$box->set_border_width(3);
my $about = new_about_dialog ();
-$box->pack_start (new_notebook (), FALSE, FALSE, 0);
+$box->pack_start (new_notebook (), TRUE, TRUE, 0);
$box->pack_end (new_button_box ($main_window, $about), FALSE, FALSE, 0);
-$main_window->signal_connect (delete_event => sub { Gtk2->main_quit });
+$main_window->signal_connect (delete_event => sub { exit_handler($main_window) });
+$main_window->signal_connect (key_press_event => \&escape_key_handler);
$main_window->set_title ($xl::s{win_title});
$main_window->set_icon_list (get_app_icons ());
$main_window->add ($box);