# $Id: GUI.pm,v 1.34 2001/09/02 16:17:32 joern Exp $
package JaM::GUI;
@ISA = qw ( JaM::GUI::Component );
use strict;
use Gtk;
sub DESTROY {
Gtk->exit(0);
}
sub gtk_box { my $s = shift; $s->{gtk_box}
= shift if @_; $s->{gtk_box} }
sub gtk_menubar { my $s = shift; $s->{gtk_menubar}
= shift if @_; $s->{gtk_menubar} }
sub gtk_toolbar { my $s = shift; $s->{gtk_toolbar}
= shift if @_; $s->{gtk_toolbar} }
sub gtk_show_all_radio { my $s = shift; $s->{gtk_show_all_radio}
= shift if @_; $s->{gtk_show_all_radio} }
sub gtk_show_limit_radio{ my $s = shift; $s->{gtk_show_limit_radio}
= shift if @_; $s->{gtk_show_limit_radio} }
sub gtk_limit_entry { my $s = shift; $s->{gtk_limit_entry}
= shift if @_; $s->{gtk_limit_entry} }
sub getting_messages_state { my $s = shift; $s->{getting_messages_state}
= shift if @_; $s->{getting_messages_state} }
sub no_subjects_update { my $s = shift; $s->{no_subjects_update}
= shift if @_; $s->{no_subjects_update} }
sub start {
my $self = shift; $self->trace_in;
Gtk->init;
Gtk::Widget->set_default_colormap(Gtk::Gdk::Rgb->get_cmap());
Gtk::Widget->set_default_visual(Gtk::Gdk::Rgb->get_visual());
$self->load_fonts;
$self->build;
while (1) {
eval { Gtk->main };
if ( $@ ) {
my $error =
"An internal exception was thrown!\n".
"The error message was:\n\n$@";
$self->message_window (
message => $error
);
next;
} else {
last;
}
}
}
sub load_fonts {
my $self = shift; $self->trace_in;
my $fonts = $self->config_object->entries_by_type ( 'font' );
my ($font_name, $value, $font);
while ( ($font_name,$value) = each %{$fonts} ) {
$font = $font_name;
$font =~ s/^font_name/font/;
$self->config_object->set_temporary(
$font,
Gtk::Gdk::Font->load ($value->{value})
);
}
1;
}
sub build {
my $self = shift; $self->trace_in;
my $dbh = $self->dbh;
# create GTK widgets for main application window
my $win = $self->create_window;
my $box = $self->create_window_box;
my $menubar = $self->create_menubar;
my $toolbar = $self->create_toolbar;
# store component
$self->comp ( gui => $self );
# create objects for our main GUI components
my $folders =
JaM::GUI::Folders->new ( dbh => $dbh, gtk_win => $win )->build;
$self->comp ( folders => $folders );
my $subjects =
JaM::GUI::Subjects->new ( dbh => $dbh, gtk_win => $win )->build;
$self->comp ( subjects => $subjects );
my $mail =
JaM::GUI::Mail->new ( dbh => $dbh, gtk_win => $win )->build;
$self->comp ( mail => $mail );
# arrange components inside the application window
my $vpane = new Gtk::VPaned();
$vpane->set_handle_size( 10 );
$vpane->set_gutter_size( 15 );
$vpane->add1 ($subjects->widget);
$vpane->add2 ($mail->widget);
$vpane->show();
my $hpane = new Gtk::HPaned();
$hpane->set_handle_size( 10 );
$hpane->set_gutter_size( 15 );
if ( $self->config('folder_tree_left') ) {
$hpane->add1 ($folders->widget);
$hpane->add2 ($vpane);
} else {
$hpane->add1 ($vpane);
$hpane->add2 ($folders->widget);
}
$hpane->show();
my $sep = Gtk::HSeparator->new;
$sep->show;
$box->pack_start($menubar, 0, 1, 0);
$box->pack_start($toolbar, 0, 1, 0);
$box->pack_start($sep, 0, 1, 0);
$box->pack_start($hpane, 1, 1, 0);
$win->add($box);
$mail->clear;
$win->show;
$self->widget($win);
$folders->gtk_folders_tree->select_row(0,0);
return;
}
sub create_window {
my $self = shift; $self->trace_in;
my $win = new Gtk::Window -toplevel;
$win->set_title($self->config('program_name'));
$win->signal_connect("destroy" => \&Gtk::main_quit);
$win->border_width(0);
$win->set_uposition (10,10);
$win->set_default_size (
$self->config('main_window_width'),
$self->config('main_window_height'),
);
$win->realize;
$win->signal_connect("size-allocate",
sub {
$self->config('main_window_width', $_[1]->[2]);
$self->config('main_window_height', $_[1]->[3]);
}
);
$self->gtk_win ($win);
return $win;
}
sub create_window_box {
my $self = shift; $self->trace_in;
my $box = new Gtk::VBox (0, 2);
$box->show;
$self->gtk_box ($box);
return $box;
}
sub create_menubar {
my $self = shift; $self->trace_in;
my $win = $self->gtk_win;
my @menu_items = (
{ path => '/_File',
type => '<Branch>' },
{ path => '/File/_Get Messages',
accelerator => '<control>G',
callback => sub { $self->cb_get_button } },
{ path => '/File/sep1',
type => '<Separator>' },
{ path => '/File/_Netscape Import...',
callback => sub { $self->netscape_import_window } },
{ path => '/File/sep2',
type => '<Separator>' },
{ path => '/File/Empty _Trash...',
callback => sub { $self->ask_empty_trash_folder } },
{ path => '/File/sep5',
type => '<Separator>' },
{ path => '/File/_Exit',
accelerator => '<control>Q',
callback => sub { Gtk->exit( 0 ); } },
{ path => '/_Edit',
type => '<Branch>' },
{ path => '/Edit/Input, Output _Filter',
accelerator => '<control>I',
callback => sub {
my $filter = JaM::GUI::IO_Filter->new (
dbh => $self->dbh
);
$filter->open_window;
} },
{ path => '/Edit/Mail _Account',
accelerator => '<control>M',
callback => sub { $self->account_window } },
{ path => '/Edit/Address _Book',
accelerator => '<control>B',
callback => sub {
my $address = JaM::GUI::Address->new (
dbh => $self->dbh
);
$address->open_window;
} },
{ path => '/Edit/_Database Configuration',
callback => sub {
my $db = JaM::GUI::Database->new (
dbh => $self->dbh
);
$db->open_window;
} },
{ path => '/Edit/_User Configuration',
accelerator => '<control>U',
callback => sub {
my $conf = JaM::GUI::Config->new (
dbh => $self->dbh
);
$conf->open_window;
} },
{ path => '/_Message',
type => '<Branch>' },
{ path => '/Message/_New Message',
accelerator => '<control>N',
callback => sub { $self->cb_new_button } },
{ path => '/Message/_Reply Message',
accelerator => '<control>R',
callback => sub { $self->cb_reply_button } },
{ path => '/Message/Reply _All Message',
accelerator => '<control>A',
callback => sub { $self->cb_reply_all_button } },
{ path => '/Message/_Forward Message',
accelerator => '<control>O',
callback => sub { $self->cb_forward_button } },
{ path => '/Message/sep2',
type => '<Separator>' },
{ path => '/Message/_Print Message',
accelerator => '<control>P',
callback => sub { $self->cb_print_button } },
{ path => '/Message/sep3',
type => '<Separator>' },
{ path => '/Message/_Delete Message',
accelerator => '<control>D',
callback => sub { $self->cb_delete_button } },
{ path => '/Message/sep4',
type => '<Separator>' },
{ path => '/Message/Advanced _Search...',
accelerator => '<control>F',
callback => sub {
my $search = JaM::GUI::Search->new (
dbh => $self->dbh
);
$search->open_window;
} },
{ path => '/_Help',
type => '<LastBranch>' },
{ path => '/Help/_About',
callback => sub { $self->about_window } },
);
my $accel_group = Gtk::AccelGroup->new;
my $item_factory = Gtk::ItemFactory->new (
'Gtk::MenuBar',
'<main>',
$accel_group
);
$item_factory->create_items ( @menu_items );
$win->add_accel_group ( $accel_group );
my $menubar = $self->gtk_menubar ( $item_factory->get_widget( '<main>' ) );
$menubar->show;
return $menubar;
}
sub create_toolbar {
my $self = shift; $self->trace_in;
my $toolbar = Gtk::Toolbar->new ( 'horizontal', 'text' );
$toolbar->set_space_size( 3 );
$toolbar->set_space_style( 'empty' );
$toolbar->set_button_relief( 'none' );
$toolbar->border_width( 0 );
$toolbar->append_space;
my $get_button = $toolbar->append_item (
"Get Messages", "Fetch new messages", undef, undef
);
$toolbar->append_space;
my $new_button = $toolbar->append_item (
"New Message", "Compose a new message", undef, undef
);
$toolbar->append_space;
my $reply_button = $toolbar->append_item (
"Reply", "Reply to this message", undef, undef
);
$toolbar->append_space;
my $reply_all_button = $toolbar->append_item (
"Reply All", "Reply to all recipients", undef, undef
);
$toolbar->append_space;
my $forward_button = $toolbar->append_item (
"Forward", "Forward selected message", undef, undef
);
$toolbar->append_space;
my $print_button = $toolbar->append_item (
"Print", "Print selected message", undef, undef
);
$toolbar->append_space;
my $delete_button = $toolbar->append_item (
"Delete", "Delete selected message", undef, undef
);
$toolbar->append_space;
my $mark_all_read_button = $toolbar->append_item (
"Mark All Read", "Mark all messages in this folder as read", undef, undef
);
my $hbox = Gtk::HBox->new (0,0);
$hbox->show;
my $show_all_radio = Gtk::RadioButton->new ("Show all");
$hbox->pack_start ($show_all_radio, 0, 0, 1);
$show_all_radio->show;
my $show_limit_radio = Gtk::RadioButton->new ("Limit to", $show_all_radio);
$hbox->pack_start ($show_limit_radio, 0, 0, 1);
$show_limit_radio->show;
my $limit_entry = Gtk::Entry->new (8);
$limit_entry->set_usize(50, undef);
$hbox->pack_start ($limit_entry, 0, 0, 1);
$limit_entry->show;
$toolbar->append_space;
$toolbar->append_space;
$toolbar->append_widget ( $hbox, "Adjust folder display limit",, "");
$self->gtk_show_all_radio ( $show_all_radio );
$self->gtk_show_limit_radio ( $show_limit_radio );
$self->gtk_limit_entry ( $limit_entry );
$get_button->signal_connect ("clicked", sub { $self->cb_get_button (@_) } );
$new_button->signal_connect ("clicked", sub { $self->cb_new_button (@_) } );
$reply_button->signal_connect ("clicked", sub { $self->cb_reply_button (@_) } );
$reply_all_button->signal_connect ("clicked", sub { $self->cb_reply_all_button (@_) } );
$forward_button->signal_connect ("clicked", sub { $self->cb_forward_button (@_) } );
$print_button->signal_connect ("clicked", sub { $self->cb_print_button (@_) } );
$delete_button->signal_connect ("clicked", sub { $self->cb_delete_button (@_) } );
$mark_all_read_button->signal_connect ("clicked", sub { $self->cb_mark_all_read (@_) } );
$show_all_radio->signal_connect ("clicked", sub { $self->cb_show_all (1) } );
$show_limit_radio->signal_connect ("clicked", sub { $self->cb_show_all (0) } );
$limit_entry->signal_connect_after("activate", sub { $self->cb_limit_entry_activate (@_) });
$toolbar->show();
$self->gtk_toolbar ($toolbar);
return $toolbar;
}
sub cb_get_button {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
return 1 if $self->getting_messages_state;
my $dropper = JaM::Drop->new (
dbh => $self->dbh,
type => 'input',
);
if ( $dropper->filter_error ) {
$self->message_window (
title => "Error initializing Input Filter",
message =>
"Can't initialize Input Filter\n".
"Error: ".$dropper->filter_error
);
return;
}
my $account = JaM::Account->load_default ( dbh => $self->dbh )
or return;
if ( not $account->pop3_server or
not $account->pop3_login ) {
$self->account_window;
return 1;
}
my $pop = Net::POP3->new (
$account->pop3_server,
Timeout => 60
);
$self->debug ("pw=".$account->pop3_password);
my $ok = $pop->login ( $account->pop3_login, $account->pop3_password );
if ( not $ok ) {
$self->message_window (
message => "Error logging into pop3 account."
);
return 1;
}
my $last_nr = $pop->last;
my ($max_nr) = $pop->popstat;
# $last_nr = 0; # TESTING !!!
if ( $max_nr <= $last_nr ) {
$self->debug ("no new messages");
$self->message_window (message => "No new messages.");
$pop->quit;
return 1;
}
my $progress_win = Gtk::Window->new ("toplevel");
$progress_win->set_title ("Fetching mail...");
$progress_win->set_policy (0, 0, 1);
$progress_win->border_width (10);
$progress_win->position ("center");
my $vbox = Gtk::VBox->new (0, 5);
$vbox->border_width(1);
$progress_win->add($vbox);
$vbox->show;
my $align = Gtk::Alignment->new (0.5, 0.5, 0, 0);
$vbox->pack_start( $align, 0, 0, 5 );
$align->show;
my $adj = Gtk::Adjustment->new ( 0, 1, $max_nr - $last_nr, 0, 0, 0);
my $progress = Gtk::ProgressBar->new_with_adjustment ($adj);
$progress->set_format_string ("%v/%u (%p%%)");
$progress->set_show_text (1);
$align->add( $progress );
$progress->show;
my $cancel = Gtk::Button->new ("Cancel");
$vbox->pack_start ( $cancel, 0, 0, 0 );
$cancel->signal_connect( "clicked", sub { $self->cb_cancel_get_messages (@_) } );
$cancel->can_default( 1 );
$cancel->grab_default;
$cancel->show;
$progress_win->show;
$self->getting_messages_state ({
progress => $progress,
progress_win => $progress_win,
dropper => $dropper,
pop => $pop,
nr => $last_nr + 1,
max => $max_nr,
delete => $account->pop3_delete,
});
$self->debug ("start time=".time);
$self->getting_messages_state->{idle} = Gtk->idle_add (
sub { $self->fetch_next_message }
);
1;
}
sub cb_cancel_get_messages {
my $self = shift;
my $state = $self->getting_messages_state;
my $folders = $self->comp('folders');
my $pop = $state->{pop};
Gtk->idle_remove ($state->{idle});
$pop->reset;
$pop->quit;
$state->{progress_win}->destroy;
$self->getting_messages_state(undef);
$folders->update_folder_stati;
return 1;
}
my $idle_nr = 0;
sub fetch_next_message {
my $self = shift;
return 1 if ++$idle_nr % 1000;
my $state = $self->getting_messages_state;
my ($data, $mail_id, $folder_id);
my $subjects = $self->comp('subjects');
my $folders = $self->comp('folders');
my $selected_folder_object = $folders->selected_folder_object;
$self->debug ("getting message no. $state->{nr}");
my $pop = $state->{pop};
$state->{progress}->set_value ($state->{progress}->get_value + 1);
$data = $pop->get($state->{nr});
#! todo: error window
$self->cb_cancel_get_messages if not $data;
$pop->delete($state->{nr}) if $state->{delete};
$state->{nr}++;
($mail_id, $folder_id) = $state->{dropper}->drop_mail ( data => $data );
$subjects->prepend_new_mail ( mail_id => $mail_id )
if $selected_folder_object and
$folder_id == $selected_folder_object->id;
my $folder_object = JaM::Folder->by_id($folder_id);
$folders->update_folder_item (
folder_object => $folder_object,
no_folder_stati => 1,
);
if ( $state->{nr} > $state->{max} ) {
$pop->quit;
$state->{progress_win}->destroy;
$self->getting_messages_state(undef);
$folders->update_folder_stati;
$self->debug ("end time=".time);
return;
}
1;
}
sub cb_new_button {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
my $account = JaM::Account->load_default ( dbh => $self->dbh );
if ( not $account->smtp_server or
not $account->from_name or
not $account->from_adress ) {
$self->account_window;
return 1;
}
my $compose = JaM::GUI::Compose->new (
dbh => $self->dbh
);
$compose->build;
return $compose;
}
sub cb_reply_button {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
my $account = JaM::Account->load_default ( dbh => $self->dbh );
if ( not $account->smtp_server or
not $account->from_name or
not $account->from_adress ) {
$self->account_window;
return 1;
}
return 1 if not $self->comp('mail')->mail;
my $compose = JaM::GUI::Compose->new (
dbh => $self->dbh
);
$compose->build;
$compose->insert_reply_message (
mail => $self->comp('mail')->mail
);
1;
}
sub cb_reply_all_button {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
my $account = JaM::Account->load_default ( dbh => $self->dbh );
if ( not $account->smtp_server or
not $account->from_name or
not $account->from_adress ) {
$self->account_window;
return 1;
}
return 1 if not $self->comp('mail')->mail;
my $compose = JaM::GUI::Compose->new (
dbh => $self->dbh
);
$compose->build;
$compose->insert_reply_message (
mail => $self->comp('mail')->mail,
reply_all => 1,
);
1;
}
sub cb_forward_button {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
my $account = JaM::Account->load_default ( dbh => $self->dbh );
if ( not $account->smtp_server or
not $account->from_name or
not $account->from_adress ) {
$self->account_window;
return 1;
}
my $selected_mail_ids = $self->comp('subjects')->selected_mail_ids;
return if not @{$selected_mail_ids};
my $compose = JaM::GUI::Compose->new (
dbh => $self->dbh
);
my $first_mail = JaM::Mail->load (
mail_id => shift @{$selected_mail_ids},
dbh => $self->dbh
);
$compose->build;
$compose->forwarded_message (
mail => $first_mail
);
$compose->add_attachment (
mail => $first_mail
);
foreach my $mail_id ( @{$selected_mail_ids} ) {
$compose->add_attachment (
mail => JaM::Mail->load (
mail_id => $mail_id,
dbh => $self->dbh
)
);
}
1;
}
sub cb_print_button {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
my $selected_mail_ids = $self->comp('subjects')->selected_mail_ids;
return if not @{$selected_mail_ids};
my $mail;
my $mail_comp = $self->comp('mail');
foreach my $mail_id ( @{$selected_mail_ids} ) {
$self->debug ("printing mail id=$mail_id...");
$mail = JaM::Mail->load (
mail_id => $mail_id,
dbh => $self->dbh
);
my $mail_as_html = JaM::GUI::MailAsHTML->new;
$mail_as_html->begin;
$mail_comp->print_entity_head (
widget => $mail_as_html,
entity => $mail
);
if ( $mail->body ) {
$mail_comp->put_mail_text (
widget => $mail_as_html,
data => $mail->body->as_string,
no_table => 1,
);
}
$mail_comp->print_child_entities (
first_time => 1,
widget => $mail_as_html,
entity => $mail
);
$mail_as_html->end;
my $html2ps = $self->config('html2ps_prog');
my $lpr = $self->config('lpr_prog');
my $lp = $self->config('printer_name');
$self->debug ("execute $html2ps | $lpr -P$lp");
if ( not open (PRINT, "| $html2ps | $lpr -P$lp") ) {
warn ("can't fork $html2ps | $lpr -P$lp");
return 1;
}
print PRINT $mail_as_html->html;
close PRINT or
warn ("can't execute $html2ps | $lpr -P$lp");
}
1;
}
sub cb_delete_button {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
my $trash_folder_id = $self->config('trash_folder_id');
$self->comp('subjects')->move_selected_mails (
folder_id => $trash_folder_id
);
1;
}
sub cb_mark_all_read {
my $self = shift; $self->trace_in;
my ($widget, $event) = @_;
my $folder_object = $self->comp('subjects')->folder_object;
return 1 if not $folder_object;
$folder_object->mark_all_read;
$self->comp('subjects')->show,
$self->comp('folders')->update_folder_item (
folder_object => $folder_object
);
1;
}
sub update_folder_limit {
my $self = shift; $self->trace_in;
my %par = @_;
my ($folder_object) = @par{'folder_object'};
if ( not $folder_object ) {
$self->gtk_show_all_radio->set_active (0);
$self->gtk_show_limit_radio->set_active (0);
$self->gtk_limit_entry->set_text("");
return 1;
}
if ( $folder_object->show_all ) {
$self->debug ("show all activated");
$self->gtk_show_all_radio->set_active (1);
$self->gtk_show_limit_radio->set_active (0);
} else {
$self->debug ("show all deactivated");
$self->gtk_show_all_radio->set_active (0);
$self->gtk_show_limit_radio->set_active (1);
}
$self->gtk_limit_entry->set_text ( $folder_object->show_max );
1;
}
sub cb_show_all {
my $self = shift;
my ($show_all) = @_;
my $folder_object = $self->comp('folders')->selected_folder_object;
return 1 if not $folder_object;
return 1 if $folder_object->show_all eq $show_all;
$folder_object->show_all($show_all);
$folder_object->save;
$self->comp('subjects')->show if not $self->no_subjects_update;
1;
}
sub cb_limit_entry_activate {
my $self = shift;
my ($widget, $event) = @_;
my $folder_object = $self->comp('folders')->selected_folder_object;
return 1 if not $folder_object;
$folder_object->show_max($widget->get_text);
$folder_object->show_all(0);
$folder_object->save;
$self->update_folder_limit;
$self->comp('subjects')->show if not $self->no_subjects_update;
$widget->set_text($folder_object->show_max);
return 1;
}
sub about_window {
my $self = shift;
my $win = new Gtk::Window;
$win->set_title( "About: ".$self->config('program_name') );
$win->set_usize ( 420, 350 );
$win->set_policy ( 0, 0, 1);
$win->border_width(0);
$win->position ('center');
$win->signal_connect("destroy", sub { $win->destroy } );
my $vbox = Gtk::VBox->new (0,0);
$vbox->show;
my $sw = new Gtk::ScrolledWindow(undef, undef);
$sw->set_policy('automatic', 'automatic');
my $html = JaM::GUI::HTMLSurface->new (
image_dir => $self->htdocs_dir,
);
$html->show_eval (
file => "about.html"
);
my $widget = $html->widget;
$sw->show;
$sw->add($widget);
$vbox->pack_start($sw, 1, 1, 0);
$win->add ($vbox);
$win->show;
1;
}
sub account_window {
my $self = shift;
my $account = JaM::GUI::Account->new (
dbh => $self->dbh
);
$account->open_window;
1;
}
sub netscape_import_window{
my $self = shift;
my $import = JaM::GUI::NetscapeImport->new (
dbh => $self->dbh
);
$import->build;
1;
}
sub ask_empty_trash_folder {
my $self = shift;
$self->confirm_window (
message => "Do you want to empty the trash folder?",
position => 'center',
yes_callback => sub { $self->comp('folders')->empty_trash_folder }
);
}
1;