#!/usr/bin/perl # Contest UI for the MO Submitter # (c) 2007 Martin Mares use strict; use warnings; BEGIN { defined $ENV{"MO_ROOT"} or die "Please set MO_ROOT to the contest root directory first.\n"; } use lib $ENV{"MO_ROOT"} . "/submit"; use lib $ENV{"MO_ROOT"} . "/submit/lib/perl5"; use MO::Submit; use Sherlock::Object; use POSIX; use Gtk2 -init; my $conn = new MO::Submit; ### GUI INITIALIZATION ### sub init_refresh(); sub timed_refresh(); sub refresh($); sub submit(); my $busy_cursor = Gtk2::Gdk::Cursor->new('watch'); # The main window my $window = Gtk2::Window->new('toplevel'); $window->signal_connect("delete-event" => sub { Gtk2->main_quit }); $window->set_title($conn->{"Contest"} . " Submitter"); $window->set_wmclass("submitter", "Submitter"); $window->set_default_size(640, 480); # The title label my $title_lab = Gtk2::Label->new; $title_lab->set_markup("" . $conn->{"Contest"} . ""); # The row of buttons my $b_submit = Gtk2::Button->new('Submit'); $b_submit->signal_connect(clicked => \&submit); $b_submit->set_sensitive(0); my $b_check = Gtk2::Button->new('Check'); $b_check->signal_connect(clicked => sub { Gtk2->main_quit }); $b_check->set_sensitive(0); my $b_refresh = Gtk2::Button->new('Refresh'); $b_refresh->signal_connect(clicked => sub { refresh(0) }); my $button_box = Gtk2::HBox->new; $button_box->pack_start_defaults($b_submit); $button_box->pack_start_defaults($b_check); $button_box->pack_start_defaults($b_refresh); $button_box->set_border_width(5); # The list of tasks my $task_store = Gtk2::ListStore->new('Glib::String', 'Glib::String'); my $task_view = Gtk2::TreeView->new($task_store); my $task_renderer = Gtk2::CellRendererText->new; my $task_col1 = Gtk2::TreeViewColumn->new_with_attributes("Task", $task_renderer, "text", 0); $task_view->append_column($task_col1); my $task_col2 = Gtk2::TreeViewColumn->new_with_attributes("Status", $task_renderer, "text", 1); $task_view->append_column($task_col2); $task_view->set_headers_visible(0); my $task_scroll = Gtk2::ScrolledWindow->new; $task_scroll->set_policy("automatic", "automatic"); $task_scroll->add($task_view); $task_scroll->set_border_width(5); my $task_frame = Gtk2::Frame->new("Tasks"); $task_frame->add($task_scroll); my $selected_task; my $task_sel = $task_view->get_selection; $task_sel->set_mode('single'); $task_sel->signal_connect(changed => sub { my $iter = $_[0]->get_selected; if ($iter) { $selected_task = $task_store->get($iter, 0); $b_submit->set_sensitive(1); $b_check->set_sensitive(1); } else { $selected_task = undef; print "Deselected task\n"; $b_submit->set_sensitive(0); $b_check->set_sensitive(0); } }); my $status_bar = Gtk2::Statusbar->new; my $bar_ctx = $status_bar->get_context_id('xyzzy'); my $vbox = Gtk2::VBox->new; $vbox->pack_start($title_lab, 0, 0, 10); $vbox->pack_start($task_frame, 1, 1, 0); $vbox->pack_start($button_box, 0, 0, 0); $vbox->pack_start($status_bar, 0, 0, 0); $window->add($vbox); $window->signal_connect("expose-event" => sub { init_refresh(); return 0; }); $window->show_all; ### REFRESHING ### my $last_status_id; sub status($) { print "GUI: ", $_[0], "\n" if $conn->{"Trace"}; defined $last_status_id and $status_bar->remove($bar_ctx, $last_status_id); $last_status_id = $status_bar->push($bar_ctx, shift @_); } sub busy($) { status($_[0]); $window->window->set_cursor($busy_cursor); $window->Gtk2::Gdk::flush; } sub ready($) { status($_[0]); $window->window->set_cursor(undef); $window->Gtk2::Gdk::flush; } my $window_inited = 0; sub init_refresh() { if (!$window_inited) { refresh(1); $window_inited = 1; } return 1; } sub timed_refresh() { refresh(0); # FIXME: If-modified-since version? return 1; # We wish to re-run the timer } my $task_status_object; my @task_parts = (); my @task_stat = (); sub recalc_task_list() { my @new_tp = (); my @new_stat = (); foreach my $t ($task_status_object->getarray("(T")) { my $task = $t->get("T"); foreach my $p ($t->getarray("(P")) { my $part = $p->get("P"); my $taskpart = ($task eq $part) ? $task : "$task/$part"; push @new_tp, $taskpart; my $status = "---"; my $current_ver = $p->get("V"); foreach my $v ($p->getarray("(V")) { if ($v->get("V") == $current_ver) { my $time = strftime("%H:%M:%S", localtime $v->get("T")); $status = "OK (" . "$part." . $v->get("X") . ", " . $v->get("L") . " bytes, " . $v->get("S") . " $time)"; last; } } push @new_stat, $status; } } if (join("\n", @new_tp) ne join("\n", @task_parts)) { # The tasks have changed, repopulate the whole structure $task_store->clear; my @s = @new_stat; foreach my $taskpart (@new_tp) { my $iter = $task_store->append; $task_store->set($iter, 0, $taskpart, 1, shift @s); } } else { # Update the task status my @s = @task_stat; my @ns = @new_stat; $task_store->foreach(sub { my ($obj, $path, $iter) = @_; if ($s[0] ne $ns[0]) { $task_store->set($iter, 1, $ns[0]); } shift @s; shift @ns; return 0; }); } @task_parts = @new_tp; @task_stat = @new_stat; } sub refresh($) { my $force = shift @_; if (!$conn->is_connected || $force) { busy("Connecting to server..."); if ($conn->connect) { ready("Connected successfully"); } else { ready($conn->{"error"}); } } if ($conn->is_connected) { busy("Updating status..."); my $r = new Sherlock::Object("!" => "STATUS"); $r = $conn->request($r); if (!defined $r) { ready($conn->{"error"}); } elsif ($r->get("-")) { ready($r->get("-")); } else { $task_status_object = $r; recalc_task_list; ready("Ready"); } } if (!$conn->is_connected && !$force) { # Retry $conn->log("Retrying"); refresh(1); } } ### SUBMITTING ### my $subwin; my $chooser; my $subwin_vbox; my $subwin_label; my $bbutton_box; my $submitting_label; my $status_label; my $submit_filename; sub end_submit($) { my ($close) = @_; $subwin->destroy if $close; ### FIXME: ... and refresh status } sub finish_submit() { my $button = Gtk2::Button->new('Return'); $button->signal_connect(clicked => sub { end_submit(1) }); $bbutton_box = Gtk2::HButtonBox->new; $bbutton_box->pack_start_defaults($button); $subwin_vbox->pack_start($bbutton_box, 0, 0, 10); $subwin->show_all; } sub submit_ok() { $status_label->set_markup("Submitted OK"); $submitting_label->set_markup("The task has been successfully submitted."); finish_submit(); } sub submit_failed($) { my ($msg) = @_; $status_label->set_markup("Submit failed"); $submitting_label->set_markup("$msg"); finish_submit(); } sub run_submit() { sleep 1; submit_failed("Xyzzy"); #submit_ok(); } sub checks_failed($) { my ($msg) = @_; $status_label->set_markup("Check failed"); my $text_buffer = Gtk2::TextBuffer->new; $text_buffer->set_text($msg); my $text_view = Gtk2::TextView->new_with_buffer($text_buffer); $text_view->set_editable(0); $text_view->set_cursor_visible(0); $submitting_label->destroy; $subwin_vbox->pack_start_defaults($text_view); finish_submit(); } sub checks_ok() { ### FIXME: Record to local history here $status_label->set_markup("Submitting"); $subwin->show_all; # Continue when everything is displayed Glib::Idle->add(sub { $window->Gtk2::Gdk::flush; run_submit(); return 0; }); } sub run_checks() { sleep 1; #checks_failed("One\nTwo\nThree...\n"); checks_ok(); } sub do_submit() { my $submit_filename = $chooser->get_filename; defined $submit_filename or return; -f $submit_filename or return; $conn->log("Selected $submit_filename for submit"); $chooser->destroy; $bbutton_box->destroy; $status_label->set_markup("Checking"); $submitting_label = Gtk2::Label->new("Please wait..."); $subwin_vbox->pack_start_defaults($submitting_label); $subwin->show_all; # Continue when everything is displayed Glib::Idle->add(sub { $window->Gtk2::Gdk::flush; run_checks(); return 0; }); } sub submit() { ## FIXME: Stop the status timer? $subwin = Gtk2::Window->new('toplevel'); $subwin->set_default_size(640, 480); $subwin->set_modal(1); $subwin->set_transient_for($window); $subwin->set_destroy_with_parent(1); $subwin->set_title("Submit task $selected_task"); $subwin->set_wmclass("submitter", "Submitter"); $subwin->signal_connect("delete-event" => sub { end_submit(0); return 0; }); my $bb_submit = Gtk2::Button->new('Submit'); $bb_submit->signal_connect(clicked => \&do_submit); #$bb_submit->set_sensitive(0); my $bb_cancel = Gtk2::Button->new('Cancel'); $bb_cancel->signal_connect(clicked => sub { end_submit(1) }); $bbutton_box = Gtk2::HButtonBox->new; $bbutton_box->pack_start_defaults($bb_submit); $bbutton_box->pack_start_defaults($bb_cancel); $bbutton_box->set_border_width(5); my $subwin_label = Gtk2::Label->new; $subwin_label->set_markup("Submitting $selected_task"); $status_label = Gtk2::Label->new; $status_label->set_markup("Please select file to submit"); $chooser = Gtk2::FileChooserWidget->new("open"); $chooser->set_local_only(1); $chooser->signal_connect("file-activated" => \&do_submit); $subwin_vbox = Gtk2::VBox->new; $subwin_vbox->pack_start($subwin_label, 0, 0, 10); $subwin_vbox->pack_start($status_label, 0, 0, 10); $subwin_vbox->pack_start_defaults($chooser); $subwin_vbox->pack_start($bbutton_box, 0, 0, 0); $subwin->add($subwin_vbox); $subwin->show_all; } ### MAIN ### Glib::Timeout->add(5000, \&timed_refresh); Gtk2->main; exit 0;