markong has asked for the wisdom of the Perl Monks concerning the following question:
Hello Monks,
I need some help with an issue related with GUI construction: I need a very simple GUI to show
the output of a series of commands.
To give you an idea of what I'm after, it's a thing very similar to the GUI I've seen often on
many Debian based distros when system updates are performed (if I remember well):
a text pane/view which scrolls following whatever gets out from apt/dpkg in real-time.
My researches on CPAN have suggested that there exists something similar which could be reused in
the form of Gtk2::Ex::TextView::FollowAppend.
I'm posting a relatively simple thing I've came up with, but I've never touched GTK2 and I'm in a
bit of a rush, so I thought I could use some help here on how to "send" the output I'm collecting
with IPC::Run to the Gtk2::TextBuffer.
I'm reading the API doc/details here http://gtk2-perl.sourceforge.net/doc/pod/Gtk2/TextBuffer.html and here http://gtk2-perl.sourceforge.net/doc/gtk2-perl-study-guide/x2662.html
but to repeat I'm not really
familiar with the GTK2 API, so I couldn't find the "right" signal to update the
Gtk2::Ex::TextView::FollowAppend Gtk2::TextBuffer when something gets "pump-ed" into $out.
I've also tried inspecting the source of the main distribution module (Gtk2::Ex::ErrorTextDialog) to see
how the Gtk2::Ex::TextView::FollowAppend is updated but it's pretty convoluted.
I should also add that the update of the buffer should take into account the fact that the
commands print some textual "progress bar" which re-set the cursor very often and I fear it could
cause problems if the text is just appended to the Gtk2::TextBuffer.
Please ignore the rawness of the GUI, I'm just interested in the mechanics of the update for now.
Also, I'm open to suggestion on alternative GUIs (on GNU/Linux) but I feel like that I miss only
some pieces here.
Thanks for the attention.
#!/usr/bin/perl
use strict;
use warnings FATAL => 'all';
use Gtk2 '-init';
use Gtk2::Ex::TextView::FollowAppend;
use IPC::Run qw( start pump finish timeout );
use FindBin qw($Bin);
my $window = Gtk2::Window->new;
$window->set_title('Diabytes meters downloader');
$window->signal_connect (destroy => sub { Gtk2->main_quit; });
$window->set_border_width(3);
my $vbox = Gtk2::VBox->new(0, 6);
$window->add($vbox);
my $frame = Gtk2::Frame->new('Buttons');
$vbox->pack_start($frame, 1, 1, 0);
$frame->set_border_width(3);
my $hbox = Gtk2::HBox->new(0 ,6);
$frame->add($hbox);
$hbox->set_border_width(3);
my $incButton = Gtk2::Button->new('_Click Me');
$hbox->pack_start($incButton, 0 , 0, 0);
my $quitButton = Gtk2::Button->new('_Quit');
$hbox->pack_start($quitButton, 0, 0 , 0);
$quitButton->signal_connect( clicked => sub {
Gtk2::main_quit();
});
my $scrolled = Gtk2::ScrolledWindow->new;
$scrolled->set_policy('never', 'always');
$vbox->pack_start($scrolled, 1,1,0);
my $textBuffer = Gtk2::TextBuffer->new();
$incButton->signal_connect( clicked => sub {
&makeSomeNoise($textBuffer);
});
my $textView = Gtk2::Ex::TextView::FollowAppend->new_with_buffer($text
+Buffer);
$textView->set(wrap_mode => 'char', editable => 0);
$scrolled->add($textView);
$window->set_default_size(500, 250);
$window->set_position('GTK_WIN_POS_CENTER');
$window->show_all;
Gtk2->main;
sub makeSomeNoise {
my $textBuff = shift;
# IPC::Run setup
my $file = File::Spec->catfile($Bin, 'somefile.txt');
my $timeFile = File::Spec->catfile($Bin, 'downloadsession.tm');
my $scriptFile = File::Spec->catfile($Bin, 'downloadsession.out')
+;
my @cat = ('cat', $file);
#my @command = qw(scriptreplay -t downloadsession.tm -s downloadse
+ssion.out);
my @command = ('scriptreplay', '-t', $timeFile, '-s', $scriptFile)
+;
my ($in, $out, $err, $t);
#my $h = start( \@cat, \$in, \$out, $t = timeout( 180 ) );
my $h = start( \@command, \$in, \$out, $t = timeout( 180 ) );
while ($h->pump) {
#&refreshTextBuffer($textBuffer, $out);
#$textBuff->set_text($out);
#print($out);
}
$h->finish();
}
Re: GUI to dynamically show program output
by Discipulus (Canon) on Feb 04, 2021 at 07:31 UTC
|
| [reply] [d/l] |
|
| [reply] |
Re: GUI to dynamically show program output
by kcott (Archbishop) on Feb 04, 2021 at 07:55 UTC
|
G'day markong,
"I'm open to suggestion on alternative GUIs"
That's lucky, because I don't know Gtk2. :-)
Here's a Tk solution which, I believe, does the sort of thing you want.
#!/usr/bin/env perl
use strict;
use warnings;
use Tk;
use Tk::ROText;
my $mw = MainWindow::->new();
$mw->geometry('512x288+100+150');
my $ctrl_F = $mw->Frame(
)->pack(-side => 'left', -fill => 'y');
my $text_F = $mw->Frame(
)->pack(-side => 'left', -fill => 'both', -expand => 1);
my $fill_x = $ctrl_F->Button(-text => 'Fill X')->pack();
my $fill_y = $ctrl_F->Button(-text => 'Fill Y')->pack();
my $text = $text_F->Scrolled('ROText',
-scrollbars => 'osoe', -wrap => 'none',
)->pack(-fill => 'both', -expand => 1);
$fill_x->configure(-command => sub {
$_->configure(-state => 'disabled') for $fill_x, $fill_y;
my $ms = 50 * (1 + int rand 10);
my $iters = 1 + int rand 100;
my $iter = 0;
my $id;
$id = $mw->repeat($ms, sub {
if (++$iter < $iters) {
text_show($text, 'X');
}
else {
text_show($text, "\n");
$id->cancel;
$_->configure(-state => 'normal') for $fill_x, $fill_y;
}
});
});
$fill_y->configure(-command => sub {
$_->configure(-state => 'disabled') for $fill_x, $fill_y;
my $ms = 50 * (1 + int rand 10);
my $iters = 1 + int rand 100;
my $iter = 0;
my $id;
$id = $mw->repeat($ms, sub {
if (++$iter < $iters) {
my $str = 'Y' x (1 + int rand 100) . "\n";
text_show($text, $str);
}
else {
$id->cancel;
$_->configure(-state => 'normal') for $fill_x, $fill_y;
}
});
});
MainLoop;
{
my ($line, $char); BEGIN { ($line, $char) = (1, 0) }
sub text_show {
my ($text, $str) = @_;
$text->insert("$line.$char" => $str);
$text->see("$line.$char");
if (-1 < index $str, "\n") {
$line += $str =~ y/\n//;
$char = length(substr $str, rindex $str, "\n") - 1;
}
else {
$char += length $str;
}
}
}
Notes:
-
The "Fill X" button simulates the textual "progress bar" you mentioned.
-
The "Fill Y" button simulates lines of output from your commands.
-
The anonymous block at the end is where the output display occurs.
You'll want to feed you command/progressbar output to text_show().
-
I've allowed the text window to scroll horizontally as well vertically to show you that option.
You may want to change -wrap => 'none' to -wrap => 'char'
(which, I'd guess, is what wrap_mode => 'char' does in your Gtk2 code).
-
The Tk::ROText widget
does, I assume, the same as the editable => 0 does in your Gtk2 code.
Take a look at Tk::Text for all the non-read-only features.
-
The automatic scrolling is handled by $text->see("$line.$char");.
-
You may also want to look at Tk::fileevent.
That's probably what I'd use; with the call to text_show() in the callback.
| [reply] [d/l] [select] |
|
kcott, thank you very much!
The example really does what I need but unfortunately it doesn't show correctly the progress bar of the command.
This is probably due to the fact that the textual progress bar includes a counter
which is implemented by printing "\r" char (below the entire C function).
void doProgress(char* label, size_t step, size_t total) {
//progress width
const int pwidth = 60;
//minus label len
size_t width = pwidth - strlen(label);
size_t pos = (step * width) / total;
size_t percent = (step * 100) / total;
printf("\r%s%4d/%4d [", label, step, total);
int i;
//fill progress bar with =
for (i = 0; i < pos; i++) {
printf("%c", '=');
}
//fill progress bar with spaces
printf("% *c", width - pos + 1, ']');
printf("%3d%%", percent);
fflush(stdout);
}
The net result is that I get a mess displayed when I add the text to the TextView,
because TextView doesn't interpret char escape sequences.
I get the same result using Tk::DoCommand.
After a little bit of searching I think that I could probably solve the thing by
using pseudo-ttys
in Tk or even embedding Xterm(1) inside Tk !
The two examples are all in python, so I'd just ask if the same
can be done with Perl Tk (maybe playing with IPC::Run module pseudo-ttys support) ?
Comments and further suggestions are always welcome! | [reply] [d/l] |
|
"... really does what I need but ... doesn't show correctly ... probably due to ... printing "\r" char ..."
Well, I obviously had to make an initial guess about how the textual progressbar was implemented; however,
you'll be glad to know that can be very easily fixed. :-)
I rewrote the "Fill X" callback so that it gives a rough approximation of what your doProgress() is printing.
I left in the all-important \r but didn't bother adding the percentage calculation.
$fill_x->configure(-command => sub {
$_->configure(-state => 'disabled') for $fill_x, $fill_y;
my $ms = 50 * (1 + int rand 10);
my $iters = 1 + int rand 100;
my $iter = 0;
my $id;
my $label = 'LABEL';
$id = $mw->repeat($ms, sub {
if (++$iter <= $iters) {
text_show($text,
sprintf "\r%s%4d/%4d [%s]",
$label, $iter, $iters, '=' x $iter
);
}
else {
text_show($text, "\nDone!\n");
$id->cancel;
$_->configure(-state => 'normal') for $fill_x, $fill_y;
}
});
});
I then added some code to text_show() to handle the \r.
{
my ($line, $char); BEGIN { ($line, $char) = (1, 0) }
sub text_show {
my ($text, $str) = @_;
if (0 == index $str, "\r") {
$str = substr $str, 1;
$text->delete("$line.0", "$line.$char");
$char = 0;
}
$text->insert("$line.$char" => $str);
if (-1 < index $str, "\n") {
$line += $str =~ y/\n//;
$char = length(substr $str, rindex $str, "\n") - 1;
}
else {
$char += length $str;
}
$text->see("$line.$char");
}
}
You may notice that I also moved the $text->see("$line.$char") statement.
It logically makes more sense for this to occur after $line and $char have been recalculated;
although, visually I couldn't see any difference.
It probably depends on the size of the GUI window, how you've implemented scrollbars, and the value of -wrap.
I'll leave you to play around with that part.
I didn't change anything else in the original code I posted.
So, just change those two blocks of code; give it a whirl; and see how you go.
Minor update:
With the numbers being displayed, I noticed an off-by-one error in the "Fill X" callback:
I changed ++$iter < $iters to ++$iter <= $iters.
The same error exists in the "Fill Y" callback: fix it if you want;
it would only be noticeable if $iters randomised to 1,
in which case you'd get no output.
| [reply] [d/l] [select] |
|
|