package App::FQStat::Drawing;
# App::FQStat is (c) 2007-2009 Steffen Mueller
#
# This program is free software; you can redistribute it and/or
# modify it under the same terms as Perl itself.
use strict;
use warnings;
use Time::HiRes qw/sleep time/;
use Term::ANSIScreen qw/RESET locate clline cls/;
use App::FQStat::Debug;
use App::FQStat::Colors qw(get_color);
use base 'Exporter';
our %EXPORT_TAGS = (
'all' => [qw(
printline
update_display
)],
);
our @EXPORT_OK = @{$EXPORT_TAGS{'all'}};
# print line and buffer with space to line length
sub printline {
warnenter if ::DEBUG > 2;
my $line = shift;
my $offset = shift || 0;
$line .= ' ' x ($::Termsize[0]-length($line)-$offset);
print $line, "\n";
}
# draws the first, title line
sub draw_title_line {
warnenter if ::DEBUG > 1;
locate(1,1);
my $line;
my $summary_mode = $::SummaryMode;
if ($::MenuMode) {
print get_color("menu_normal");
$line = App::FQStat::Menu::get_menu_title_line();
}
elsif ($summary_mode) {
lock($::Interval);
my $progress = ::PROGRESS_INDICATORS()->[$::ProgressIndicator];
$progress = ' ' if not defined $progress;
$line = sprintf(
'fqstat v%.1f %s Jobs:%i Upd:%.1fs [S]witch [F10] Menu ' . get_color('header_highlight') . 'Summary Mode' . RESET . ', Nodes:%i',
$App::FQStat::VERSION||0,
$progress,
scalar(@{$::Records})||0,
$::Interval||0,
$::NoActiveNodes||0,
);
}
else {
lock($::RecordsReversed);
lock($::Interval);
lock($::User);
# reversed list indicator
my $status = get_color('reverse_indicator').'[';
if ($::RecordsReversed) { $status .= 'R' }
if ($status =~ /\[$/) { $status = '' }
else { $status .= ']'.RESET.' ' }
my $progress = ::PROGRESS_INDICATORS()->[$::ProgressIndicator];
$progress = ' ' if not defined $progress;
$line = sprintf(
'fqstat v%.1f %s %s%sJobs:%i Upd:%.1fs [h]elp [F10] Menu (c) S. Mueller, Nodes:%i',
$App::FQStat::VERSION||0,
$progress,
$status||'',
(defined($::User) ? "User:$::User " : ""),
scalar(@{$::Records})||0,
$::Interval||0,
$::NoActiveNodes||0,
);
}
::GetTermSize();
$line = substr($line, 0, $::Termsize[0]) if $::Termsize[0] < length($line);
printline($line);
print RESET if $::MenuMode;
}
# draws the column header line
sub draw_header_line {
warnenter if ::DEBUG > 1;
my @highlight = @_;
my %highlight = map {($_ => 1)} @highlight;
# Header Line
locate(2,1);
my ($line, $width);
my $high = get_color("header_highlight");
my $norm = get_color("header_normal");
my $summary_mode = $::SummaryMode;
my $summary_clustering = App::FQStat::Config::get("summary_clustering");
print $norm;
if (not $summary_mode) {
if (exists $highlight{1}) { $line = $high.'Stat'.$norm }
else { $line = 'Stat' }
}
$width = 4;
my $colno = 1;
my $columns_list = $summary_mode ? \@::SummaryColumns : \@::Columns;
my $columns_hash = $summary_mode ? \%::SummaryColumns : \%::Columns;
foreach my $col (@$columns_list) {
my $c = $columns_hash->{$col};
$colno++;
next if $summary_mode and $c->{key} eq 'name' and not $summary_clustering;
$line .= (' 'x($::Termsize[0]-$width-1)).'>', last if $width+2+$c->{width} > $::Termsize[0];
$width += 1+$c->{width};
$line .= ' ' unless $colno == 2 and $summary_mode;
if (exists $highlight{$colno}) { $line .= $high.sprintf("\%-".$c->{width}."s", $c->{name}).$norm }
else { $line .= sprintf("\%-".$c->{width}."s", $c->{name}) }
}
printline($line);
print RESET;
}
my $last_update;
sub update_display {
warnenter if ::DEBUG;
my $force = shift;
$last_update ||= time();
my $time = time();
if ($force or $time-$last_update > $::Interval) {
$last_update = $time;
$::ProgressIndicator++;
$::ProgressIndicator = 0
if $::ProgressIndicator >= scalar(@{::PROGRESS_INDICATORS()});
App::FQStat::Scanner::run_qstat($force);
}
cls();
::GetTermSize();
draw_title_line(); # first line
draw_header_line(); # second line
my $summary_mode = $::SummaryMode;
if ($summary_mode) {
draw_summary();
} else {
draw_job_display(); # list of jobs
}
if ($::MenuMode) {
App::FQStat::Menu::draw_menu();
}
locate(1,1);
}
# Draws the job summary
sub draw_summary {
warnenter if ::DEBUG;
# before there's any jobs, warn the user that the
# queue isn't actually empty.
if (not $::Initialized) {
draw_initializing_sign();
locate(1,1);
return;
}
App::FQStat::Scanner::calculate_summary()
if not defined $::Summary or @$::Summary == 0;
my $summary = $::Summary;
my $summary_clustering = App::FQStat::Config::get("summary_clustering");
my $maxno_lines = space_for_jobs();
my %status_color = (
nrun => get_color("status_running"),
nerr => get_color("status_error"),
nhold => get_color("status_hold"),
nwait => get_color("status_queued"),
);
locate(3,1);
my $summary_color = get_color("summary");
my $no = 0;
foreach my $summaryLine (@$summary) {
$no++;
last if $no >= $maxno_lines;
clline();
print $summary_color;
my $width = 0;
my $first = 1;
my $too_short = 0;
foreach my $col (@::SummaryColumns) {
my $c = $::SummaryColumns{$col};
# skip name column if not defined
next if $c->{key} eq 'name' and not $summary_clustering;
$too_short = 1, last if $width+3+$c->{width} > $::Termsize[0];
$width += 1+$c->{width};
print ' ' unless $first;
$first = 0;
print $status_color{ $c->{key} } if exists $status_color{ $c->{key} };
printf( $c->{format}, $summaryLine->[ $c->{index} ] );
print $summary_color if exists $status_color{ $c->{key} };
}
# padding for "position bar" of scroll bar
print ' 'x($::Termsize[0]-$width-1);
if ($too_short) {
# not enough space
print '>';
}
print RESET;
print "\n";
}
locate(1,1);
}
# Draws the list of jobs
# Optional argument: hash reference of items to highlight
sub draw_job_display {
warnenter if ::DEBUG;
my $highlight = shift || {};
my $mark = shift || {};
# before there's any jobs, warn the user that the
# queue isn't actually empty.
if (not $::Initialized) {
draw_initializing_sign();
locate(1,1);
return;
}
my $jobs = $::Records;
check_display_offset();
lock($::DisplayOffset);
my $drawUpperScrollbar = need_upper_scrollbar();
draw_upper_scrollbar() if $drawUpperScrollbar;
my $drawLowerScrollbar = need_lower_scrollbar();
my $max_job_index = space_for_jobs() - $drawUpperScrollbar - $drawLowerScrollbar;
locate(3+$drawUpperScrollbar,1);
my $last = (@$jobs-$::DisplayOffset <= $max_job_index ? @$jobs-1 : $::DisplayOffset + $max_job_index);
# Calculate "scroll bar" marker
# XXX this is only necessary if there is enough space.
my $marker_start = 0;
my $marker_end = 1e99;
if (@$jobs) {
my $jobs_per_display_line = int(@$jobs / $max_job_index)||1;
my $display_lines_before_marker = int($::DisplayOffset / $jobs_per_display_line);
my $display_lines_of_marker = int(($last-$::DisplayOffset) / $jobs_per_display_line);
my $display_lines_after_marker = (@$jobs-$last) / $jobs_per_display_line;
$marker_start = int $display_lines_before_marker;
$marker_end = int($display_lines_before_marker+$display_lines_of_marker);
$marker_end += 1 if $marker_start == $marker_end;
}
my $no = 0;
my %status_color = (
running => get_color("status_running"),
hold => get_color("status_hold"),
error => get_color("status_error"),
queued => get_color("status_queued"),
fallback => get_color("status_fallback"),
);
foreach my $jobIndex ($::DisplayOffset .. $last) {
clline();
my $job = $jobs->[$jobIndex];
last if $no >= $max_job_index;
# band-aid fix for uninit... bug
if (not defined $job) {
next;
}
$no++;
my $highlightcolor = $highlight->{$jobIndex};
# highlight selected user name
$highlightcolor = get_color("user_highlight")
if not defined $highlightcolor
and defined $::HighlightUser
and $job->[::F_user] =~ $::HighlightUser;
# Print STATUS
my $status = $job->[::F_status];
if ($status =~ /^[rt]$/) { print $status_color{running} }
elsif ($status =~ /(?:^d|E)/) { print $status_color{error} }
elsif ($status =~ /h(?:qw|r)/) { print $status_color{hold} }
elsif ($status =~ /w/) { print $status_color{queued} }
else { print $status_color{fallback} }
printf('%-4s', $status);
print RESET;
print $highlightcolor if defined $highlightcolor;
# print marking if applicable
if (exists($mark->{$jobIndex})) {
my $markstring = $mark->{$jobIndex};
$markstring = "*" if not defined $markstring;
printf("%1s", $markstring);
}
else { print " " }
my $width = 4;
my $first = 1;
my $too_short = 0;
foreach my $col (@::Columns) {
my $c = $::Columns{$col};
$too_short = 1, last if $width+3+$c->{width} > $::Termsize[0];
$width += 1+$c->{width};
print ' ' unless $first;
$first = 0;
printf( $c->{format}, $job->[ $c->{index} ] );
}
# padding for "position bar" of scroll bar
print ' 'x($::Termsize[0]-$width-1);
if ($too_short) {
# not enough space
print '>';
}
else {
# draw "position bar" if there is enough space
my $marker;
my $bgcolor = get_color("scrollbar_bg");
my $fgcolor = get_color("scrollbar_fg");
if ($no >= $marker_start and $no <= $marker_end) {
$marker = $fgcolor . " " . RESET();
}
else {
$marker = $bgcolor . " " . RESET();
}
print $marker;
}
print RESET;
print "\n";
}
draw_lower_scrollbar() if $drawLowerScrollbar;
locate(1,1);
}
sub draw_upper_scrollbar {
warnenter if ::DEBUG > 1;
locate(3,1);
printline("^" x ($::Termsize[0]-2));
}
sub draw_lower_scrollbar {
warnenter if ::DEBUG > 1;
locate($::Termsize[1]-1, 1);
printline("v" x ($::Termsize[0]-2));
}
sub need_upper_scrollbar {
warnenter if ::DEBUG > 2;
return 1 if $::DisplayOffset;
return 0;
}
sub need_lower_scrollbar {
warnenter if ::DEBUG > 2;
my $jobs = $::Records;
my $drawLowerScrollbar = (@$jobs-$::DisplayOffset > space_for_jobs()-need_upper_scrollbar() ? 1 : 0);
return $drawLowerScrollbar;
}
sub space_for_jobs {
warnenter if ::DEBUG > 1;
my $space = $::Termsize[1]-3;
return $space;
}
sub show_warning {
warnenter if ::DEBUG > 1;
my $text = shift;
# Header Line
locate(2,1);
my $color = get_color('header_warning');
printline($color.$text);
print RESET;
sleep 2;
return;
}
sub check_display_offset {
lock($::DisplayOffset);
return if not $::DisplayOffset;
if (@$::Records <= $::DisplayOffset) {
$::DisplayOffset = $#{$::Records};
}
$::DisplayOffset = 0 if $::DisplayOffset < 0;
}
sub draw_initializing_sign {
warnenter if ::DEBUG > 1;
locate(3,1);
my $color = get_color('initializing');
print $color, <<'HERE';
Initial Job Scan...
HERE
print RESET;
}
1;