#!/usr/bin/perl -w # # $Id$ # =pod =head1 NAME Clawsker - A Claws Mail Tweaker =head1 SYNOPSIS clawsker [options] =head1 DESCRIPTION Clawsker is an applet to edit the so called Claws Mail hidden preferences. Claws Mail is a fast, lightweight and feature-rich MUA with a high number of configurable options. To keep the binary small and fast some of these preferences which are not widely used are not provided with a graphical interface for inspection and/or modification. Users wanting to edit such preferences had to face raw editing of their configuration files, now you can do it with a convenient GTK2 interface using Clawsker. =head1 OPTIONS --help Shows a brief help screen. --version Show information about program, Claws Mail and Perl-GTK versions. --verbose Tells more on the standard output. --alternate-config-dir Uses as Claws Mail configuration dir. --clawsrc Uses as Claws Mail resource configuration file. This sets the full file name overriding any previous setting. Multiple options are allowed, although only the last one has effect. Weird option specifications may produce weird results (but otherwise correct). =head1 LIMITATIONS A running Claws Mail cannot be detected if using the --clawsrc option because the directory is not assumed to be a Claws Mail configuration dir. If that is the case use the --alternate-config-dir option instead. =head1 AUTHOR Ricardo Mones Ericardo@mones.orgE =head1 LICENSE Copyright (c) 2007-2009 by Ricardo Mones Lastra 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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see Ehttp://www.gnu.org/licenses/E. =cut use strict; use encoding 'utf8'; use Glib qw(TRUE FALSE); use Gtk2 -init; use POSIX qw(setlocale); use Locale::gettext; my $NAME = 'clawsker'; my $PREFIX = '@PREFIX@'; my $LIBDIR = '@LIBDIR@'; my $VERSION = '@VERSION@'; my $VERBOSE = FALSE; my $CLAWSV = undef; my $main_window = undef; my $locale = (defined($ENV{LC_MESSAGES}) ? $ENV{LC_MESSAGES} : $ENV{LANG}); $locale = "C" unless defined($locale); setlocale (LC_ALL, $locale); bindtextdomain ($NAME, sprintf ('%s/share/locale', $PREFIX)); textdomain ($NAME); my $SHOWHINTS = FALSE; $SHOWHINTS = TRUE if ($Gtk2::VERSION >= 1.040 and Gtk2->CHECK_VERSION (2, 12, 0)); sub _ { my $str = shift; my %par = @_; my $xla = gettext ($str); if (scalar(keys(%par)) > 0) { foreach my $key (keys %par) { $xla =~ s/\{$key\}/$par{$key}/g; } } return $xla; } # default messages %xl::s = ( win_title => _('Claws Mail Hidden Preferences'), about => _('About...'), about_title => _('Clawsker :: A Claws Mail Tweaker'), about_license => _('License:'), about_version => _('Version:'), tab_colours => _('Colours'), tab_behaviour => _('Behaviour'), tab_gui => _('GUI'), tab_other => _('Other'), ab_frame => _('Addressbook'), mem_frame => _('Memory'), msgview_frame => _('Message view'), log_frame => _('Log window'), dnd_frame => _('Drag \'n\' drop'), ssl_frame => _('Secure Sockets Layer'), msgs_frame => _('Messages'), stripes_frame => _('Coloured stripes'), sbar_frame => _('Scroll bars'), mlist_frame => _('Message List'), netm_frame => _('NetworkManager'), 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)'), 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)'), 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.'), l_gui_b_unread => _('Show unread messages with bold font'), h_gui_b_unread => _('Show unread messages in the Message List using a bold font.'), l_gui_no_markup => _('Don\'t use markup in compose window'), h_gui_no_markup => _('Don\'t use bold and italic text in Compose dialogue\'s account selector.'), l_gui_dot_lines => _('Use dotted lines in tree view components'), 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.'), l_gui_h_scroll => _('Enable horizontal scrollbar'), h_gui_h_scroll => _('Enable the horizontal scrollbar in the Message List.'), l_gui_swp_from => _('Display To column instead From column in Sent folder'), 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.'), l_gui_v_scroll => _('Folder List scrollbar behaviour'), h_gui_v_scroll => _('Specify the policy of vertical scrollbar of Folder List: show always, automatic or hide always.'), l_gui_v_scroll_show => _('Show always'), l_gui_v_scroll_auto => _('Automatic'), l_gui_v_scroll_hide => _('Hide always'), l_gui_strip_off => _('Coloured lines contrast'), 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.'), l_gui_cursor_v => _('Show Cursor in message view'), h_gui_cursor_v => _('Display the cursor in the message view.'), l_gui_toolbar_d => _('Detachable toolbars'), h_gui_toolbar_d => _('Show handles in the toolbars.'), l_gui_strip_all => _('Use stripes in all tree view components'), h_gui_strip_all => _('Enable alternately coloured lines in all tree view components.'), l_gui_strip_sum => _('Use stripes in Folder List and Message List'), h_gui_strip_sum => _('Enable alternately coloured lines in Message list and Folder list.'), l_gui_two_line_v => _('2 lines per Message List item in 3-column layout'), h_gui_two_line_v => _('Spread Message List information over two lines when using the three column mode.'), l_beh_hover_t => _('Drag \'n\' drop hover timeout (ms)'), 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.'), l_beh_flowed => _('Respect format=flowed in messages'), 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.'), l_beh_parts_rw => _('Allow writable temporary files'), 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)'), h_beh_up_step => _('Update stepping in progress bars.'), l_beh_thread_a => _('Maximum age when threading by subject (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.'), l_beh_use_utf8 => _('Force UTF-8 for broken mails'), h_beh_use_utf8 => _('Use UTF-8 encoding for broken mails instead of current locale.'), l_beh_warn_dnd => _('Warn on drag \'n\' drop'), h_beh_warn_dnd => _('Display a confirmation dialogue on drag \'n\' drop of folders.'), l_beh_out_ascii => _('Ougoing messages fallback to ASCII'), h_beh_out_ascii => _('If content allows, ASCII will be used to encode outgoing messages, otherwise the user-defined encoding is enforced always.'), l_beh_pp_unsel => _('Primary paste unselects selection'), h_beh_pp_unsel => _('Controls how pasting using middle-click changes the selected text and insertion point.'), l_col_emphasis => _('X-Mailer header'), h_col_emphasis => _('The colour used for the X-Mailer line when its value is Claws Mail.'), l_col_log_err => _('Error messages'), h_col_log_err => _('Colour for error messages in log window.'), l_col_log_in => _('Server messages'), h_col_log_in => _('Colour for messages received from servers in log window.'), l_col_log_msg => _('Standard messages'), 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'), h_col_log_warn => _('Colour for warning messages in log window.'), e_error => _('Error: '), e_noclawsrc => _('resource file for Claws Mail was not found.'), e_running => _('seems Claws Mail is currently running, close it first.'), e_requireddir => _('option requires a directory name.'), e_requiredfile => _('option requires a file name.'), e_notadir => _('specified name is not a directory or does not exist.'), e_notafile => _('specified name is not a file or does not exist.'), ); # all preferences read by load_preferences my %PREFS = (); # values of all preferences handled by clawsker my %HPVALUE = (); # default config dir and file name my $ALTCONFIGDIR = FALSE; my $CONFIGDIR = $ENV{HOME} . '/.claws-mail/'; my $CONFIGRC = 'clawsrc'; # index constants for preference arrays use constant NAME => 0; # the name on the rc file use constant LABEL => 1; # the label on the GUI use constant DESC => 2; # the description for the hint/help use constant TYPE => 3; # data type: bool, int, float, string, color use constant CMVER => 4; # lowest Claws Mail version the feature exists use constant CMDEF => 5; # default value for the preference in Claws Mail use constant GUI => 6; # GUI element # constants for GUI spacing use constant HBOX_SPC => 5; use constant FRAME_SPC => 2; use constant PAGE_SPC => 5; # version functions sub version_greater() { 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 FALSE; } sub get_claws_version() { my @cmbin = ( 'claws-mail', ); my $res = ""; foreach my $bin (@cmbin) { $_ = qx/which $bin/; chomp; last if ($_ ne ""); } return "" unless ($_); # not installed $_ = qx/$_ -v/; chomp; my @fver = split (/ /); die "Invalid version string" unless ($fver[2] eq "version"); my @ver = split (/\./, $fver[3]); $res .= "$ver[0]."; $res .= "$ver[1]."; if ($ver[2] =~ /(\d+)cvs(\d+)/) { $res .= "$1.$2"; } else { $res .= "$ver[2].0"; } return $res; } # data handlers and auxiliar functions sub handle_bool_value { my ($widget, $event, $dataref) = @_; $$dataref = ($widget->get_active ())? '1': '0'; } sub handle_int_value { my ($widget, $event, $dataref) = @_; $_ = $widget->get_text (); s/^\s+//; s/\s+$//; if (/^[0-9]+$/) { $$dataref = $_; $widget->set_text ($_); } else { $widget->set_text ($$dataref); } } sub handle_string_value { my ($widget, $event, $dataref) = @_; $$dataref = $widget->get_text (); } sub gdk_color_from_str { my ($str) = @_; my ($rr, $gg, $bb) = (0, 0 ,0); $_ = uc ($str); if (/\#([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])([A-F0-9][A-F0-9])/) { $rr = hex($1) * 256; $gg = hex($2) * 256; $bb = hex($3) * 256; } my $color = Gtk2::Gdk::Color->new ($rr, $gg, $bb); return $color; } sub str_from_gdk_color { my ($color) = @_; my $rr = $color->red / 256; my $gg = $color->green / 256; my $bb = $color->blue / 256; my $str = sprintf ("#%.2x%.2x%.2x", $rr, $gg, $bb); return $str; } sub handle_color_value { my ($widget, $event, $dataref) = @_; my $newcol = $widget->get_color; $$dataref = &str_from_gdk_color ($newcol); } sub handle_selection_value { my ($widget, $event, $dataref) = @_; $$dataref = $widget->get_active; } sub get_rc_filename { return $CONFIGDIR . $CONFIGRC; } sub set_rc_filename { my ($fullname) = @_; my @parts = split ('/', $fullname); $CONFIGRC = $parts[$#parts]; $parts[$#parts] = ''; $CONFIGDIR = join ('/', @parts); } sub log_message { my ($mesg, $fatal) = @_; if (defined($fatal) && $fatal eq 'die') { die "$NAME: $mesg\n"; } if ($VERBOSE) { print "$NAME: $mesg\n"; } } sub error_dialog { my ($emsg) = @_; my $markup = "" . $emsg . ""; my $errordlg = Gtk2::MessageDialog->new_with_markup ($main_window, 'modal', 'error', 'cancel', $markup); $errordlg->set_title (_('Error message')); $errordlg->run; $errordlg->destroy; } sub check_claws_not_running() { my $socket = (not $ALTCONFIGDIR)? "/tmp/": $CONFIGDIR; $socket .= "claws-mail-$<"; -S $socket and do { my $emsg = "$xl::s{e_error}$xl::s{e_running}"; log_message ($emsg); error_dialog ($emsg); return FALSE; }; return TRUE; } sub check_rc_file() { my ($rcfile) = @_; (defined($rcfile) && -f $rcfile) or do { my $emsg = "$xl::s{e_error}$xl::s{e_noclawsrc}\n"; log_message ($emsg); error_dialog ($emsg); return FALSE; }; return TRUE; } sub set_widget_hint() { if ($SHOWHINTS) { my ($wdgt, $hint) = @_; $wdgt->set_tooltip_text ($hint); $wdgt->set_has_tooltip (TRUE); } } # graphic element creation sub new_check_button_for { my ($hash, $key) = @_; my $name = $$hash{$key}[NAME]; my $label = $$hash{$key}[LABEL]; # my $hbox = Gtk2::HBox->new (FALSE, 5); my $cb = Gtk2::CheckButton->new ($label); $$hash{$key}[GUI] = $cb; if (defined ($HPVALUE{$name})) { $cb->set_active ($HPVALUE{$name} eq '1'); } $cb->signal_connect (clicked => sub { my ($w, $e) = @_; &handle_bool_value($w, $e, \$HPVALUE{$name}); }); &set_widget_hint ($cb, $$hash{$key}[DESC]); $hbox->pack_start ($cb, FALSE, FALSE, HBOX_SPC); # return $hbox; } sub new_text_box_for_int { my ($hash, $key) = @_; my $name = $$hash{$key}[NAME]; my $label = $$hash{$key}[LABEL]; my @type = split (/,/, $$hash{$key}[TYPE]); push (@type, 0), push (@type, 10000) unless ($#type > 0); # my $hbox = Gtk2::HBox->new (FALSE, 5); my $glabel = Gtk2::Label->new ($label); my $pagei = int (($type[2] - $type[1]) / 10); my $adjust = Gtk2::Adjustment->new ( $HPVALUE{$name}, $type[1], $type[2], 1, $pagei, 10 ); my $gentry = Gtk2::SpinButton->new ($adjust, 1, 0); $gentry->set_numeric (TRUE); $$hash{$key}[GUI] = $gentry; $gentry->signal_connect('value-changed' => sub { my ($w, $e) = @_; &handle_int_value($w, $e, \$HPVALUE{$name}); }); &set_widget_hint ($gentry, $$hash{$key}[DESC]); $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC); $hbox->pack_start ($gentry, FALSE, FALSE, HBOX_SPC); # return $hbox; } sub new_color_button_for { my ($hash, $key) = @_; my $name = $$hash{$key}[NAME]; my $label = $$hash{$key}[LABEL]; # my $col = &gdk_color_from_str ($HPVALUE{$name}); my $hbox = Gtk2::HBox->new (FALSE, 5); my $glabel = Gtk2::Label->new ($label); my $button = Gtk2::ColorButton->new_with_color ($col); $$hash{$key}[GUI] = $button; $button->set_title ($label); $button->set_relief ('none'); $button->signal_connect ('color-set' => sub { my ($w, $e) = @_; &handle_color_value($w, $e, \$HPVALUE{$name}); }); &set_widget_hint ($button, $$hash{$key}[DESC]); $hbox->pack_start ($button, FALSE, FALSE, HBOX_SPC); $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC); # return $hbox; } sub new_selection_box_for { my ($hash, $key) = @_; 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; $$hash{$key}[GUI] = $combo; my @options = split (';', $$hash{$key}[TYPE]); foreach my $opt (@options) { my ($index, $textkey) = split ('=', $opt); $combo->insert_text ($index, $xl::s{$textkey}); } $combo->signal_connect ('changed' => sub { my ($w, $e) = @_; &handle_selection_value($w, $e, \$HPVALUE{$name}); }); $combo->set_active ($HPVALUE{$name}); &set_widget_hint ($combo, $$hash{$key}[DESC]); $hbox->pack_start ($glabel, FALSE, FALSE, HBOX_SPC); $hbox->pack_start ($combo, FALSE, FALSE, HBOX_SPC); # return $hbox; } # preference maps and corresponding page creation subs %pr::oth = ( # other preferences use_dlg => [ 'addressbook_use_editaddress_dialog', $xl::s{l_oth_use_dlg}, $xl::s{h_oth_use_dlg}, 'bool', '2.7.0', '0', undef, ], max_use => [ 'cache_max_mem_usage', $xl::s{l_oth_max_use}, $xl::s{h_oth_max_use}, 'int,0,262144', # 0 Kb - 256 Mb '0.0.0', '4096', undef, ], min_time => [ 'cache_min_keep_time', $xl::s{l_oth_min_time}, $xl::s{h_oth_min_time}, 'int,0,120', # 0 minutes - 2 hours '0.0.0', '15', undef, ], use_netm => [ 'use_networkmanager', $xl::s{l_oth_use_netm}, $xl::s{h_oth_use_netm}, 'bool', '3.3.1', '1', undef, ], ); sub new_other_page() { my $of = Gtk2::VBox->new (FALSE, 5); $of->set_border_width (PAGE_SPC); my $ab_frame = Gtk2::Frame->new ($xl::s{ab_frame}); my $cb_use_dlg = &new_check_button_for(\%pr::oth, 'use_dlg'); my $vb1 = Gtk2::VBox->new (FALSE, 5); $vb1->set_border_width (PAGE_SPC); $vb1->pack_start ($cb_use_dlg, FALSE, FALSE, 0); $ab_frame->add ($vb1); my $mem_frame = Gtk2::Frame->new ($xl::s{mem_frame}); my $tb_max_use = &new_text_box_for_int(\%pr::oth, 'max_use'); my $tb_min_time = &new_text_box_for_int(\%pr::oth, 'min_time'); my $vb2 = Gtk2::VBox->new (FALSE, 5); $vb2->set_border_width (PAGE_SPC); $vb2->pack_start ($tb_max_use, FALSE, FALSE, 0); $vb2->pack_start ($tb_min_time, FALSE, FALSE, 0); $mem_frame->add ($vb2); my $netm_frame = Gtk2::Frame->new ($xl::s{netm_frame}); my $cb_use_netm = &new_check_button_for(\%pr::oth, 'use_netm'); my $vb3 = Gtk2::VBox->new (FALSE, 5); $vb3->set_border_width (PAGE_SPC); $vb3->pack_start ($cb_use_netm, FALSE, FALSE, 0); $netm_frame->add ($vb3); $of->pack_start ($ab_frame, FALSE, FALSE, FRAME_SPC); $of->pack_start ($mem_frame, FALSE, FALSE, FRAME_SPC); $of->pack_start ($netm_frame, FALSE, FALSE, FRAME_SPC); return $of; } %pr::gui = ( # gui bells and whistles b_unread => [ 'bold_unread', $xl::s{l_gui_b_unread}, $xl::s{h_gui_b_unread}, 'bool', '0.0.0', '1', undef, ], no_markup => [ 'compose_no_markup', $xl::s{l_gui_no_markup}, $xl::s{h_gui_no_markup}, 'bool', '0.0.0', '0', undef, ], dot_lines => [ 'enable_dotted_lines', $xl::s{l_gui_dot_lines}, $xl::s{h_gui_dot_lines}, 'bool', '0.0.0', '0', undef, ], h_scroll => [ 'enable_hscrollbar', $xl::s{l_gui_h_scroll}, $xl::s{h_gui_h_scroll}, 'bool', '0.0.0', '1', undef, ], swp_from => [ 'enable_swap_from', $xl::s{l_gui_swp_from}, $xl::s{h_gui_swp_from}, 'bool', '0.0.0', '0', undef, ], v_scroll => [ 'folderview_vscrollbar_policy', $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', undef, ], strip_off => [ 'stripes_color_offset', $xl::s{l_gui_strip_off}, $xl::s{h_gui_strip_off}, 'int,0,10000', # no idea what this number means '0.0.0', '4000', undef, ], cursor_v => [ 'textview_cursor_visible', $xl::s{l_gui_cursor_v}, $xl::s{h_gui_cursor_v}, 'bool', '0.0.0', '0', undef, ], toolbar_d => [ 'toolbar_detachable', $xl::s{l_gui_toolbar_d}, $xl::s{h_gui_toolbar_d}, 'bool', '0.0.0', '0', undef, ], strip_all => [ 'use_stripes_everywhere', $xl::s{l_gui_strip_all}, $xl::s{h_gui_strip_all}, 'bool', '0.0.0', '1', undef, ], strip_sum => [ 'use_stripes_in_summaries', $xl::s{l_gui_strip_sum}, $xl::s{h_gui_strip_sum}, 'bool', '0.0.0', '1', undef, ], two_linev => [ 'two_line_vertical', $xl::s{l_gui_two_line_v}, $xl::s{h_gui_two_line_v}, 'bool', '3.4.0.7', '0', undef, ], ); sub new_gui_page() { my $gf = Gtk2::VBox->new (FALSE, 5); $gf->set_border_width (PAGE_SPC); my $stripes_frame = Gtk2::Frame->new ($xl::s{stripes_frame}); my $cb_strip_all = &new_check_button_for (\%pr::gui, 'strip_all'); my $cb_strip_sum = &new_check_button_for (\%pr::gui, 'strip_sum'); my $tb_strip_off = &new_text_box_for_int (\%pr::gui, 'strip_off'); my $vb1 = Gtk2::VBox->new (FALSE, 5); $vb1->set_border_width (PAGE_SPC); $vb1->pack_start ($cb_strip_all, FALSE, FALSE, 0); $vb1->pack_start ($cb_strip_sum, FALSE, FALSE, 0); $vb1->pack_start ($tb_strip_off, FALSE, FALSE, 0); $stripes_frame->add ($vb1); my $mlist_frame = Gtk2::Frame->new ($xl::s{mlist_frame}); my $cb_b_unread = &new_check_button_for (\%pr::gui, 'b_unread'); my $cb_swp_from = &new_check_button_for (\%pr::gui, 'swp_from'); my $vb3 = Gtk2::VBox->new (FALSE, 5); $vb3->set_border_width (PAGE_SPC); $vb3->pack_start ($cb_b_unread, FALSE, FALSE, 0); $vb3->pack_start ($cb_swp_from, FALSE, FALSE, 0); $mlist_frame->add ($vb3); my $sbar_frame = Gtk2::Frame->new ($xl::s{sbar_frame}); my $cb_h_scroll = &new_check_button_for (\%pr::gui, 'h_scroll'); my $sb_v_scroll = &new_selection_box_for (\%pr::gui, 'v_scroll'); my $vb2 = Gtk2::VBox->new (FALSE, 5); $vb2->set_border_width (PAGE_SPC); $vb2->pack_start ($cb_h_scroll, FALSE, FALSE, 0); $vb2->pack_start ($sb_v_scroll, FALSE, FALSE, 0); $sbar_frame->add ($vb2); my $cb_no_markup = &new_check_button_for (\%pr::gui, 'no_markup'); my $cb_dot_lines = &new_check_button_for (\%pr::gui, 'dot_lines'); my $cb_cursor_v = &new_check_button_for (\%pr::gui, 'cursor_v'); my $cb_toolbar_d = &new_check_button_for (\%pr::gui, 'toolbar_d'); my $cb_two_linev = &new_check_button_for (\%pr::gui, 'two_linev'); $gf->pack_start ($stripes_frame, FALSE, FALSE, FRAME_SPC); $gf->pack_start ($mlist_frame, FALSE, FALSE, FRAME_SPC); $gf->pack_start ($cb_no_markup, FALSE, FALSE, 0); $gf->pack_start ($cb_dot_lines, FALSE, FALSE, 0); $gf->pack_start ($cb_cursor_v, FALSE, FALSE, 0); $gf->pack_start ($cb_toolbar_d, FALSE, FALSE, 0); $gf->pack_start ($cb_two_linev, FALSE, FALSE, 0); $gf->pack_start ($sbar_frame, FALSE, FALSE, FRAME_SPC); return $gf; } %pr::beh = ( # tweak some behaviour hover_t => [ 'hover_timeout', $xl::s{l_beh_hover_t}, $xl::s{h_beh_hover_t}, 'int,100,3000', # 0.1 seconds - 3 seconds '0.0.0', '500', undef, ], dangerous => [ 'live_dangerously', $xl::s{l_beh_dangerous}, $xl::s{h_beh_dangerous}, 'bool', '0.0.0', '0', undef, ], flowed => [ 'respect_flowed_format', $xl::s{l_beh_flowed}, $xl::s{h_beh_flowed}, 'bool', '0.0.0', '0', undef, ], parts_rw => [ 'save_parts_readwrite', $xl::s{l_beh_parts_rw}, $xl::s{h_beh_parts_rw}, 'bool', '0.0.0', '0', undef, ], skip_ssl => [ 'skip_ssl_cert_check', $xl::s{l_beh_skip_ssl}, $xl::s{h_beh_skip_ssl}, 'bool', '0.0.0', '0', undef, ], up_step => [ 'statusbar_update_step', $xl::s{l_beh_up_step}, $xl::s{h_beh_up_step}, 'int,1,200', # 1 item - 200 items '0.0.0', '10', undef, ], thread_a => [ 'thread_by_subject_max_age', $xl::s{l_beh_thread_a}, $xl::s{h_beh_thread_a}, 'int,1,30', # 1 day - 30 days '0.0.0', '10', undef, ], unsafe_ssl => [ 'unsafe_ssl_certs', $xl::s{l_beh_unsafe_ssl}, $xl::s{h_beh_unsafe_ssl}, 'bool', '0.0.0', '0', undef, ], use_utf8 => [ 'utf8_instead_of_locale_for_broken_mail', $xl::s{l_beh_use_utf8}, $xl::s{h_beh_use_utf8}, 'bool', '0.0.0', '0', undef, ], warn_dnd => [ 'warn_dnd', $xl::s{l_beh_warn_dnd}, $xl::s{h_beh_warn_dnd}, 'bool', '0.0.0', '1', undef, ], out_ascii => [ 'outgoing_fallback_to_ascii', $xl::s{l_beh_out_ascii}, $xl::s{h_beh_out_ascii}, 'bool', '3.4.0.37', '1', undef, ], pp_unsel => [ 'primary_paste_unselects', $xl::s{l_beh_pp_unsel}, $xl::s{h_beh_pp_unsel}, 'bool', '3.6.1.35', '0', undef, ], ); sub new_behaviour_page() { my $bf = Gtk2::VBox->new (FALSE, 5); $bf->set_border_width (PAGE_SPC); my $dnd_frame = Gtk2::Frame->new ($xl::s{dnd_frame}); my $tb_hoover_t = &new_text_box_for_int (\%pr::beh, 'hover_t'); my $cb_warn_dnd = &new_check_button_for (\%pr::beh, 'warn_dnd'); my $vb1 = Gtk2::VBox->new (FALSE, 5); $vb1->set_border_width (PAGE_SPC); $vb1->pack_start ($cb_warn_dnd, FALSE, FALSE, 0); $vb1->pack_start ($tb_hoover_t, FALSE, FALSE, 0); $dnd_frame->add ($vb1); my $ssl_frame = Gtk2::Frame->new ($xl::s{ssl_frame}); my $cb_skip_ssl = &new_check_button_for (\%pr::beh, 'skip_ssl'); my $cb_unsafe_ssl = &new_check_button_for (\%pr::beh, 'unsafe_ssl'); my $hb1 = Gtk2::HBox->new (FALSE, 5); $hb1->set_border_width (PAGE_SPC); $hb1->pack_start ($cb_skip_ssl, FALSE, FALSE, 0); $hb1->pack_start ($cb_unsafe_ssl, FALSE, FALSE, 0); $ssl_frame->add ($hb1); my $tb_up_step = &new_text_box_for_int (\%pr::beh, 'up_step'); my $tb_thread_a = &new_text_box_for_int (\%pr::beh, 'thread_a'); my $msgs_frame = Gtk2::Frame->new ($xl::s{msgs_frame}); my $cb_flowed = &new_check_button_for (\%pr::beh, 'flowed'); my $cb_parts_rw = &new_check_button_for (\%pr::beh, 'parts_rw'); my $cb_use_utf8 = &new_check_button_for (\%pr::beh, 'use_utf8'); my $cb_dangerous = &new_check_button_for (\%pr::beh, 'dangerous'); my $cb_out_ascii = &new_check_button_for (\%pr::beh, 'out_ascii'); my $cb_pp_unsel = &new_check_button_for (\%pr::beh, 'pp_unsel'); my $vb2 = Gtk2::VBox->new (FALSE, 5); $vb2->set_border_width (PAGE_SPC); $vb2->pack_start ($cb_flowed, FALSE, FALSE, 0); $vb2->pack_start ($cb_parts_rw, FALSE, FALSE, 0); $vb2->pack_start ($cb_use_utf8, FALSE, FALSE, 0); $vb2->pack_start ($cb_dangerous, FALSE, FALSE, 0); $vb2->pack_start ($cb_out_ascii, FALSE, FALSE, 0); $vb2->pack_start ($cb_pp_unsel, FALSE, FALSE, 0); $msgs_frame->add ($vb2); $bf->pack_start ($dnd_frame, FALSE, FALSE, FRAME_SPC); $bf->pack_start ($ssl_frame, FALSE, FALSE, FRAME_SPC); $bf->pack_start ($tb_up_step, FALSE, FALSE, 0); $bf->pack_start ($tb_thread_a, FALSE, FALSE, 0); $bf->pack_start ($msgs_frame, FALSE, FALSE, FRAME_SPC); return $bf; } %pr::col = ( # a variety of colours emphasis => [ 'emphasis_color', $xl::s{l_col_emphasis}, $xl::s{h_col_emphasis}, 'color', '0.0.0', '#0000cf', undef, ], log_err => [ 'log_error_color', $xl::s{l_col_log_err}, $xl::s{h_col_log_err}, 'color', '0.0.0', '#af0000', undef, ], log_in => [ 'log_in_color', $xl::s{l_col_log_in}, $xl::s{h_col_log_in}, 'color', '0.0.0', '#000000', undef, ], log_msg => [ 'log_msg_color', $xl::s{l_col_log_msg}, $xl::s{h_col_log_msg}, 'color', '0.0.0', '#00af00', undef, ], log_out => [ 'log_out_color', $xl::s{l_col_log_out}, $xl::s{h_col_log_out}, 'color', '0.0.0', '#0000ef', undef, ], log_warn => [ 'log_warn_color', $xl::s{l_col_log_warn}, $xl::s{h_col_log_warn}, 'color', '0.0.0', '#af0000', undef, ], ); sub new_colours_page() { my $cf = Gtk2::VBox->new (FALSE, 5); $cf->set_border_width (PAGE_SPC); my $msgview_frame = Gtk2::Frame->new ($xl::s{msgview_frame}); my $cb_emphasis = &new_color_button_for (\%pr::col, 'emphasis'); my $vb1 = Gtk2::VBox->new (FALSE, 5); $vb1->set_border_width (PAGE_SPC); $vb1->pack_start ($cb_emphasis, FALSE, FALSE, 0); $msgview_frame->add ($vb1); my $log_frame = Gtk2::Frame->new ($xl::s{log_frame}); my $cb_log_err = &new_color_button_for (\%pr::col, 'log_err'); my $cb_log_in = &new_color_button_for (\%pr::col, 'log_in'); my $cb_log_msg = &new_color_button_for (\%pr::col, 'log_msg'); my $cb_log_out = &new_color_button_for (\%pr::col, 'log_out'); my $cb_log_warn = &new_color_button_for (\%pr::col, 'log_warn'); my $vb2 = Gtk2::VBox->new (FALSE, 5); $vb2->set_border_width (PAGE_SPC); $vb2->pack_start ($cb_log_err, FALSE, FALSE, 0); $vb2->pack_start ($cb_log_in, FALSE, FALSE, 0); $vb2->pack_start ($cb_log_msg, FALSE, FALSE, 0); $vb2->pack_start ($cb_log_out, FALSE, FALSE, 0); $vb2->pack_start ($cb_log_warn, FALSE, FALSE, 0); $log_frame->add ($vb2); $cf->pack_start ($msgview_frame, FALSE, FALSE, 0); $cf->pack_start ($log_frame, FALSE, FALSE, 0); return $cf; } # version info sub print_version() { print $xl::s{about_title} . "\n"; print $xl::s{about_version} . " $VERSION\n"; print "Perl-GLib " . $Glib::VERSION; # version info stuff appeared in 1.040 if ($Glib::VERSION >= 1.040) { print _(", built for ") . join(".", Glib->GET_VERSION_INFO) . _(", running with ") . join(".", &Glib::major_version, &Glib::minor_version, &Glib::micro_version); } print "\n"; print "Perl-GTK2 " . $Gtk2::VERSION; if ($Gtk2::VERSION >= 1.040) { print _(", built for ") . join(".", Gtk2->GET_VERSION_INFO) . _(", running with ") . join(".", &Gtk2::major_version, &Gtk2::minor_version, &Gtk2::micro_version); } print "\n"; my $clawsver = ($CLAWSV eq "") ? _("was not found!") : (_("returned version ") . $CLAWSV); print "Claws Mail " . $clawsver . "\n"; } # the command line help sub print_help() { my $line = '-' x length ($xl::s{about_title}) . "\n"; print $line; print $xl::s{about_title} . "\n"; print $line; print _("Syntax:\n"); print _(" clawsker [options]\n"); print _("Options:\n"); print _(" --help Prints this help screen.\n"); print _(" --version Prints version infos.\n"); print _(" --verbose More messages on standard output.\n"); print _(" --alternate-config-dir Uses as Claws Mail config dir.\n"); print _(" --clawsrc Uses as full resource name.\n"); } # handle errors which don't allow to run sub command_line_fatal() { my $reason = shift; my $emsg = $xl::s{e_error} . $reason; error_dialog ($emsg); log_message ("$emsg", 'die'); } # parse the command line sub parse_command_line() { $CLAWSV = &get_claws_version; my $arg = 0; while (defined($ARGV[$arg])) { for ($ARGV[$arg]) { /--help/ && do { &print_help; return FALSE; }; /--version/ && do { &print_version; return FALSE; }; /--verbose/ && do { $VERBOSE = TRUE; last; }; /--use-claws-version/ && do { ++$arg; &command_line_fatal ("required version") unless defined($ARGV[$arg]); &command_line_fatal ("required a dotted numeric value") unless ($ARGV[$arg] =~ /[\d\.]+/); $CLAWSV = $ARGV[$arg]; }; /--alternate-config-dir/ && do { ++$arg; &command_line_fatal ($xl::s{e_requireddir}) unless defined($ARGV[$arg]); &command_line_fatal ($xl::s{e_notadir}) unless -d $ARGV[$arg]; $CONFIGDIR = $ARGV[$arg]; $CONFIGDIR .= "/" unless ($CONFIGDIR =~ /.*\/$/); $ALTCONFIGDIR = TRUE; last; }; /--clawsrc/ && do { ++$arg; &command_line_fatal($xl::s{e_requiredfile}) unless defined($ARGV[$arg]); &command_line_fatal($xl::s{e_notafile}) unless -f $ARGV[$arg]; &set_rc_filename ($ARGV[$arg]); last; }; /.*/ && &command_line_fatal ( _("unknown option '{opt}'.\n", opt => $ARGV[$arg])); } ++$arg; } # eveything continues... return TRUE; } # update the hidden preferences status from loaded values sub init_hidden_preferences() { foreach my $hash (\%pr::beh, \%pr::col, \%pr::gui, \%pr::oth) { foreach my $key (keys %$hash) { $HPVALUE{${$hash}{$key}[NAME]} = $PREFS{${$hash}{$key}[NAME]}; } } return TRUE; } # load current status from disc sub load_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; open (RCF, "<$rc"); while () { chomp; if (/^([8a-z_]+)=(.*)$/) { $PREFS{$1} = "$2"; } } close (RCF); return TRUE; } # save current preferences to disc sub save_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); open (RCF, ">$rc"); open (RCB, "<$rcbak"); while () { chomp; if (/^([8a-z_]+)=(.*)$/) { if (defined($HPVALUE{$1})) { print RCF $1 . "=" . $HPVALUE{$1} . "\n"; } else { print RCF $_ . "\n"; } } else { print RCF $_ . "\n"; } } close (RCB); close (RCF); return TRUE; } # create notebook sub new_notebook() { my $nb = Gtk2::Notebook->new; # $nb->append_page (&new_behaviour_page, $xl::s{tab_behaviour}); $nb->append_page (&new_colours_page, $xl::s{tab_colours}); $nb->append_page (&new_gui_page, $xl::s{tab_gui}); $nb->append_page (&new_other_page, $xl::s{tab_other}); return $nb; } # create an about dialog sub new_about_dialog() { my ($parent) = @_; my $title = $xl::s{about_title}; my $lic = $xl::s{about_license}; my $vers = $xl::s{about_version} . " $VERSION"; my $license = "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 the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. 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-2009"; my $holder = "Ricardo Mones <ricardo\@mones.org>"; my $dialog = Gtk2::MessageDialog->new_with_markup ($parent, [qw/modal destroy-with-parent/], 'info', 'close', "$title\n" . "$vers\n" . "Copyright $year by $holder\n\n" . "$lic\n\n" . "$license"); $dialog->set_title ($xl::s{about}); # return $dialog; } # create buttons box sub new_button_box() { my ($parent, $adlg) = @_; my $b_about = Gtk2::Button->new_from_stock ('gtk-about'); my $b_exit = Gtk2::Button->new_from_stock ('gtk-quit'); my $b_apply = Gtk2::Button->new_from_stock ('gtk-apply'); # disable button until is really implemented # 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_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 $hbox->pack_end ($b_apply, FALSE, FALSE, 0); $hbox->pack_end ($b_exit, FALSE, FALSE, 0); # $hbox->pack_end ($b_undo, FALSE, FALSE, 0); $hbox->pack_start ($b_about, FALSE, FALSE, 0); # return $hbox; } # initialise $main_window = Gtk2::Window->new ('toplevel'); exit unless &parse_command_line; exit unless &load_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_end (&new_button_box ($main_window, $about), FALSE, FALSE, 0); $main_window->signal_connect (delete_event => sub { Gtk2->main_quit }); $main_window->set_title ($xl::s{win_title}); $main_window->add ($box); $main_window->show_all; Gtk2->main;