From 8948ee0376d2aeba319ef33bcff4746741362af3 Mon Sep 17 00:00:00 2001 From: etienne Date: Fri, 23 Jul 2004 15:08:34 +0000 Subject: Add a Replay mode that can be configured by command options and controlled by ivy messages. Messages storage can be activated at launching. An input file can be loaded at launching by command argument. --- src/ivymon | 751 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 594 insertions(+), 157 deletions(-) (limited to 'src') diff --git a/src/ivymon b/src/ivymon index a12fcbe..297433d 100755 --- a/src/ivymon +++ b/src/ivymon @@ -1,5 +1,7 @@ #!/usr/bin/perl +$SIG{INT} = \&quit; +$SIG{QUIT} = \&quit; use Tk; use Tk::Font; use Tk::LabFrame; @@ -11,11 +13,18 @@ use Tk::ProgressBar; use Sys::Hostname; use Ivy; use Carp; -use strict 'vars'; +use strict; use Getopt::Long; use Tk::CmdLine; -use vars qw/$opt_help $opt_b $opt_bus $opt_history @opt_bind @opt_send $opt_size $opt_undersize/; - +use vars qw/$VERSION $opt_help $opt_b $opt_bus $opt_history @opt_bind @opt_send $opt_size + $opt_undersize $opt_out $opt_loadingmode $opt_replayrepeat $opt_replaystartregexp + $opt_replaystopregexp/; +$VERSION = '1.6'; +# options initialisation +$opt_loadingmode = 'replay-pause'; +$opt_replayrepeat = 0; +$opt_replaystartregexp = '^ClockStart'; +$opt_replaystopregexp = '^ClockStop'; # geometry my $minW = 1024; my $minH = 768; @@ -59,10 +68,6 @@ my @messagesbuffer; my $markers_cnt = 0; -my $duration = 0; -my $time = 0; -my $fmtduration = '00:00'; - # displayed messages counters my $messagesNumber = 0; my $messagesLastNumber = 0; @@ -83,16 +88,34 @@ my $loadingFlag = 0; my $appname = 'IvyMon'; $appname =~ s/ /_/g; -# Rejeu messages array +# messages array my @send_def; -# Rejeu available (not effective) bindings array +# available (not effective) bindings array my @bind_def; # Effective bindings array my @effectivebind; - +# replay +my $replay_speed = 1; +my $replay_runnable = 1; +my $replay_running = 0; +my $replay_timer; +my $replay_tpl; +my $replay_text; +my $replay_repeat; +my %replay_msg; +my $replay_time; +my $replay_min_time; +my $replay_max_time; +my $replay_last_time; +my $replay_stepbystep; +my $replay_bg = 'gray75'; +my $replay_fg = 'black'; +my $replay_bg_orig; +my $replay_fg_orig; +my $replay_hour = &replayTime();; #---------------------------------------------------------------------------------- # command line options management #---------------------------------------------------------------------------------- @@ -101,25 +124,42 @@ Tk::CmdLine::SetArguments(-font => '7x14'); Tk::CmdLine::SetArguments(); if (not GetOptions('-help', '-history=s', '-b=s', '-bus=s', '-bind=s@', - '-send=s@', '-size=s', '-undersize') or $opt_help) { + '-send=s@', '-size=s', '-undersize', '-loadingmode=s', + '-replayrepeat', '-replaystartregexp=s', + '-replaystopregexp=s', '-out=s') or $opt_help) { print "Usage: ivymon [-b[us] bus] [-help] [-history length]\n"; - print " [-size VGA|SVGA|XGA|SXGA|UXGA]\n"; + print " [-size size] [-undersize]\n"; print " [-bind regexp1] ... [-bind regexpN] \n"; print " [-send message1] ... [-send messageN] \n"; - print " [standard X11 options...]\n"; + print " [-loadingmode mode] [-replayrepeat]\n"; + print " [-replaystartregexp regexp] [-replaystopregexp regexp]\n"; + print " [-out outputfile] [standard X11 options...]\n"; + print " [inputfile]\n"; print "\n"; print "Options :\n"; - print " -b <[addr]:port> Ivy bus (default is \$IVYBUS)\n"; - print " -history Messages list history length ". - "(default $history messages)\n"; - print " -bind Ivy binding regular expression\n"; - print " -send Ivy message to send\n"; - print " -size Set the size of the IvyMon window, see man (default=XGA)\n"; - print " -undersize Slightly reduce the IvyMon window.\n"; + print " -b <[addr]:port> Ivy bus (\$IVYBUS by default)\n"; + print " -history Messages list history length\n"; + print " ($history messages by default)\n"; + print " -size Size of the main window\n"; + print " -undersize If set, slightly reduces the main window\n"; + print " -bind Ivy binding regular expression\n"; + print " ((.*) by default)\n"; + print " -send Ivy message to send\n"; + print " -loadingmode \n"; + print " Loading mode of input files\n"; + print " ('replay-pause' by default)\n"; + print " NOTE: The following options are usable if at least one input file is\n"; + print " given and if -loadingmode is set to 'replay'\n"; + print " -replayrepeat If set, repeat infinitely the replay sequence\n"; + print " -replaystartregexp Regexp to match for starting replay\n"; + print " ('^ClockStart\$'' by default)\n"; + print " -replaystopregexp Regexp to match for stopping replay\n"; + print " ('^ClockStop\$'' by default)\n"; print "\n"; exit ; } +$replay_repeat = $opt_replayrepeat; if ($opt_bus) { $ivy_port = $opt_bus; } elsif ($opt_b) { @@ -128,9 +168,9 @@ if ($opt_bus) { $history = $opt_history if $opt_history; my $title; if ($ivy_port) { - $title = "Ivymon ($ivy_port)"; + $title = "Ivymon v$VERSION ($ivy_port)"; } else { - $title = "Ivymon (default port)"; + $title = "Ivymon v$VERSION (default port)"; } push(@effectivebind, @opt_bind); @@ -191,9 +231,11 @@ if($opt_undersize) # #================================================================================= my $mw = MainWindow->new(); + # Set default min size $mw->minsize($minW, $minH); $mw->title($title); + # Key-Tab binding is deactivated, because this event will be used by entries # for completion functionality $mw->bind('', sub {Tk->break}); @@ -314,7 +356,7 @@ my $messagesMaxLabel = my $messagesText = - $top_fm->Scrolled(Text, + $top_fm->Scrolled('Text', -scrollbars => 'e', -height => 18*$coef, -spacing1 => 2, @@ -334,6 +376,8 @@ my $bgcolor = $messagesText->cget(-background); $messagesText->tagConfigure('sender', -background => 'gray50', -foreground => 'gray90'); +$messagesText->tagConfigure('time', + -state => 'hidden'); $messagesText->tagConfigure($appname, -foreground => 'gray30'); $messagesText->tagConfigure('marker0', -background => 'lightcoral', -foreground => 'lightcoral'); @@ -392,7 +436,7 @@ $bindingsEntry->bind('' => [\&bindingPrevExpression]); $bindingsEntry->focus; my $bindingsList = - $bindings_fm->Scrolled(Listbox, + $bindings_fm->Scrolled('Listbox', -scrollbars => 'osoe', -height => 4, -width => 30*$coef)->pack(-fill => 'y', @@ -404,7 +448,7 @@ $bindingsList->bind('', [\&addBinding]); $bindingsEntry->bind('' => [\&findExprInList, $bindingsList]); my $effectivebindingsList = - $bindings_fm->Scrolled(Listbox, + $bindings_fm->Scrolled('Listbox', -scrollbars => 'osoe', -height => 4, -width => 30*$coef)->pack(-fill => 'y', @@ -419,7 +463,7 @@ $effectivebindingsList->bind('', [\&removeBinding]); # Connected applications area #---------------------------------------------------------------------------------- my $clientsListbox = - $clients_fm->Scrolled(Listbox, + $clients_fm->Scrolled('Listbox', -scrollbars => 'osoe')->pack(-fill => 'both', -expand => 1, -side => 'top'); @@ -497,7 +541,7 @@ my $clockbackCheckbutton = -ipadx => 10*$coef, -sticky => 'nsew'); my $sendList = - $send_fm->Scrolled(Listbox, + $send_fm->Scrolled('Listbox', -scrollbars => 'osoe', -width => 40*$coef)->pack(-fill => 'y', -anchor => 'w', @@ -772,6 +816,7 @@ $balloonhelp->attach($saveButton, -balloonmsg => Ivy->init(-loopMode => 'TK', -appName => $appname, -ivyBus => $ivy_port, + -onDieFunc => [\&quit], ); my $ivy = Ivy->new(-statusFunc => \&checkClientsStatus); $ivy->start; @@ -781,6 +826,47 @@ for my $bind (@effectivebind) { &addBinding(undef, $bind); } +#================================================================================= +# +# Options and arguments test +# +#================================================================================= +# output file +if ($opt_out) { + unless (open(OUT, ">$opt_out")) { + $mw->Tk::Error("Can't write to file '$opt_out' ($!)"); + $opt_out = undef; + } +} +# loading mode +$mw->Tk::Error("syntax error : -loadingmode accepts 'replay', 'replay-pause' ". + "or 'display' value") + if ($opt_loadingmode ne 'replay' and $opt_loadingmode ne 'replay-pause' + and $opt_loadingmode ne 'display'); + +# load input files +if (@ARGV > 0) { + my $file = $ARGV[0]; + if ( not open(IN, $file)) { + $mw->Tk::Error("Can't open file '$file' ($!)"); + } else { + my ($step, $timefound) = &stepsnumber; + if ($opt_loadingmode eq 'replay-pause' or $opt_loadingmode eq 'replay') { + if ($timefound) { + &loadfileForReplay($step); + } else { + $mw->Tk::Error("No time information in file '$file'. ". + "Can't be replayed."); + } + &replayStart() if $opt_loadingmode eq 'replay'; + } elsif ($opt_loadingmode eq 'display') { + &loadfileForDisplay($step); + } + $progressbar->value(0); + close(IN); + } +} + MainLoop; @@ -799,24 +885,28 @@ sub addIvyBinding { $ivy->bindRegexp($binding, [sub { my $sender = shift; my $message; - for (my $i = 0; $i < @_; $i++) { - $_[$i] = '"'.$_[$i].'"'; + if (@_ > 1) { + for (my $i = 0; $i < @_; $i++) { + $_[$i] = '"'.$_[$i].'"'; + } + $message = join(' ', @_); + } else { + $message = shift; } - $message = join(' ', @_); $messagesNumber++; + my $time = time(); if ($stopFlag) { - &bufferizeMessages($sender, $message); + &bufferizeMessages($sender, $message, $time); } else { &beforeUpdatingMessages; - &updateMessages($sender, $message); + &updateMessages($sender, $message, $time); &afterUpdatingMessages; } }]); - - } # end addIvyBinding + sub removeIvyBinding { my $binding = shift; $ivy->bindRegexp($binding); @@ -1094,10 +1184,10 @@ sub addmarker { sub bufferizeMessages { - my ($sender, $message) = @_; + my ($sender, $message, $time) = @_; $bufNumber++; # if scrolling is locked, don't force display, just put message in buffer - push(@messagesbuffer, [$sender, $message]); + push(@messagesbuffer, [$sender, $message, $time]); splice(@messagesbuffer, 0, 1) if @messagesbuffer > $history; } # end bufferizeMessages @@ -1110,13 +1200,14 @@ sub beforeUpdatingMessages { sub updateMessages { - my ($sender, $message) = @_; + my ($sender, $message, $time) = @_; chomp($message); + $message = '"' . $message . '"' ; # If scrolling is available, format and then display message. # Unless file has been loaded, look at history : compare widget text lines # number with history size if ($loadedFileFlag) { - &loadMessage($sender, $message); + &loadMessage($sender, $message, $time); $messagesText->update unless $noUpdateFlag; } else { @@ -1130,7 +1221,7 @@ sub updateMessages { $messagesDeletedValue->configure(-foreground => 'red'); } } else { - &loadMessage($sender, $message); + &loadMessage($sender, $message, $time); $messagesText->update unless $noUpdateFlag; } } @@ -1153,17 +1244,20 @@ sub updateClientMessages { sub loadMessage { - my ($sender, $message) = @_; + my ($sender, $message, $time) = @_; $recordedNumber++; $noMessageYet = 0; - my $text = "$sender $message\n"; + my $text = "$sender $time $message\n"; my $index1 = $messagesText->index('end')." - 1 lines"; my $index2 = "$index1 + ".length($sender)." chars"; $messagesText->insert('end', $text); $messagesText->tagAdd('sender', $index1, $index2); my $index3 = "$index2 + 1 chars"; - my $index4 = "$index3 + ".length($message)." chars"; - $messagesText->tagAdd($sender, $index3, $index4); + my $index4 = "$index3 + ".length($time)." chars"; + $messagesText->tagAdd('time', $index3, $index4); + my $index5 = "$index4 + 1 chars"; + my $index6 = "$index5 + ".length($message)." chars"; + $messagesText->tagAdd($sender, $index5, $index6); &updateClientMessages($sender, $message) if $clientsMessagesTpl{$sender} and Tk::Exists $clientsMessagesTpl{$sender}; @@ -1288,25 +1382,6 @@ sub addBindingInList { } # end addBindingInList -sub newBinding2 { - my $regexp = shift; - return if $regexp eq '(.*)'; - # skip ^ and $ characters in regexp - $regexp =~ s/^\^//; - $regexp =~ s/\$$//; - my $msg = $regexp; - $msg =~ s/=\(.*\)\s/= /g; - #$msg =~ s/\(.*\)//g; - # skip parenthesis in regexp - $regexp =~ s/\(//g; - $regexp =~ s/\)//g; - $regexp = "^(".$regexp.")"; - &addBindingInList($regexp); - &addMsgToSend(undef, $msg); - -} # end newBinding2 - - # add a binding in the bindings list and, after some modifications, # add a new message in the list of messages to send. sub newBinding { @@ -1488,7 +1563,7 @@ sub sendMsg { my $entry = shift; $messagesNumber++; &beforeUpdatingMessages; - &updateMessages($appname, $entry); + &updateMessages($appname, $entry, scalar time()); &afterUpdatingMessages; $ivy->sendMsgs($entry); @@ -1747,7 +1822,7 @@ sub bye { return; } } - exit; + &quit; } sub jump { @@ -1794,7 +1869,7 @@ sub start { $noUpdateFlag = 1; &beforeUpdatingMessages; for my $msg (@messagesbuffer) { - &updateMessages($msg->[0], $msg->[1]); + &updateMessages($msg->[0], $msg->[1], $msg->[2]); $bufNumber--; } &afterUpdatingMessages; @@ -1811,30 +1886,120 @@ sub start { } # end start -sub loadfile { +sub clear { if ($messagesNumber > 0) { my $diag = $mw->Dialog(-text => - "Some Ivy messages are already displayed. ". - "If you continue, loaded messages will ". - "be appended without distinction.\n", - -default_button => 'Continue', - -buttons => [qw(Continue Cancel)]); + "Do you really want to remove displayed messages ?", + -default_button => 'OK', + -buttons => [qw(OK Cancel)]); my $answer = $diag->Show; if ($answer eq 'Cancel') { return; } } + + # disable other buttons + my $jumpstate = $jumpButton->cget(-state); + my $startstate = $startButton->cget(-state); + my $stopstate = $stopButton->cget(-state); + $loadButton->configure(-state => 'disabled'); + $saveButton->configure(-state => 'disabled'); + $jumpButton->configure(-state => 'disabled'); + $clearButton->configure(-state => 'disabled'); + $startButton->configure(-state => 'disabled'); + $stopButton->configure(-state => 'disabled'); + $mw->update; + + $messagesText->configure(-state => 'normal'); + $messagesNumber = 0; + $recordedNumber = 0; + #$messagesText->delete('1.0', 'end'); + while ($messagesText->compare($messagesText->index('end'), ">", "2.0")) { + $messagesText->delete('1.0', '1000.0'); + my $index = $messagesText->index('end'); + } + $messagesText->configure(-state => 'disabled'); + + # restore other buttons state + $loadButton->configure(-state => 'normal'); + $saveButton->configure(-state => 'normal'); + $jumpButton->configure(-state => $jumpstate); + $clearButton->configure(-state => 'normal'); + $startButton->configure(-state => $startstate); + $stopButton->configure(-state => $stopstate); + +} # end clear + +#---------------------------------------------------------------------------------- +# Functions related to input/output files +#---------------------------------------------------------------------------------- +sub loadfile { + my $file = $mw->getOpenFile(-filetypes => [['Ivy Files', '.ivy'], ['All Files', '*']], ); return unless $file; - # open file unless (open(IN, "$file")) { $mw->Tk::Error("$!\n"); return; } + my $loadingmode = 'display'; + my ($step, $timefound) = &stepsnumber(); + $loadingmode = &selectLoadingMode(); + if ($loadingmode eq 'replay') { + &loadfileForReplay($step); + } else { + &loadfileForDisplay($step); + } + $progressbar->value(0); + close(IN); + +} # end loadfile + +sub stepsnumber { + + my $step = 0; + my $timefound; + # select loading mode + # if the 1rst message contains a time field, a dialog window + # will be displayed in order to select replay mode or simple + # display mode. + while() { + chomp; + next if (/^applications=/ or /^(marker\d+)$/); + if (/^messages_number=(.*)/) { + $progressbar->configure(-to => $1); + $step = int($1/10); + next; + } + my ($sender, $message) = split(/ /, $_, 2); + $timefound = 1 if $message =~ /^(\d+)\s+.*/; + last; + } + seek(IN, 0, 0); + return ($step, $timefound); + +} # end stepsnumber + + +sub loadfileForDisplay { + + my $step = shift; + if ($messagesNumber > 0) { + my $diag = $mw->Dialog(-text => + "Some Ivy messages are already displayed. ". + "If you continue, loaded messages will ". + "be appended without distinction.\n", + -default_button => 'Continue', + -buttons => [qw(Continue Cancel)]); + my $answer = $diag->Show; + if ($answer eq 'Cancel') { + return; + } + } + # set load flags... $loadedFileFlag = 1; $loadingFlag = 1; @@ -1859,17 +2024,12 @@ sub loadfile { $mw->update; my %client; - my $step = 0; my $line = 0; $noUpdateFlag = 1; &beforeUpdatingMessages; while() { chomp; - if (/^applications=/) { - next; - } elsif (/^messages_number=(.*)/) { - $progressbar->configure(-to => $1); - $step = int($1/10); + if (/^applications=/ or /^messages_number=/) { next; } elsif (/^\s*$/) { next; @@ -1879,6 +2039,11 @@ sub loadfile { &addmarker($index, $1); } else { my ($sender, $message) = split(/ /, $_, 2); + my $time = undef; + if ($message =~ /^(\d+)\s+(.*)/) { + $time = $1; + $message = $2; + } unless ($client{$sender}) { $client{$sender} = 1; $clientsListbox->insert('end', $sender); @@ -1886,16 +2051,14 @@ sub loadfile { $messagesNumber++; $line++; #$message =~ s/^\"(.*)\"$/$1/; - &loadMessage($sender, $message); + &loadMessage($sender, $message, $time); #print "sender=$sender message=$message step=$step line=$line\n"; $progressbar->value($line); $progressbar->update if ($step == 0 or $line % $step == 0); } } - $progressbar->value(0); &afterUpdatingMessages; $noUpdateFlag = 0; - close(IN); # restore other buttons state $loadButton->configure(-state => 'normal'); @@ -1905,10 +2068,186 @@ sub loadfile { $startButton->configure(-state => $startstate); $stopButton->configure(-state => $stopstate); $loadingFlag = 0; + +} # end loadfileForDisplay + +sub loadfileForReplay { + + my $step = shift; + my $line = 0; + %replay_msg = (); + $replay_time = undef; + # build replay window + $replay_tpl->destroy if defined $replay_tpl and Tk::Exists($replay_tpl); + $replay_tpl = $mw->Toplevel; + $replay_tpl->title("Replay mode"); + my $ctrl_fm = $replay_tpl->Frame()->pack(-side => 'bottom', -pady => 5); + $replay_text = $replay_tpl->Scrolled('Text', + -scrollbars => 'e', + -spacing1 => 2, + -spacing2 => 0, + -spacing3 => 2, + )->pack(-fill => 'both', + -expand => 1, + -side => 'bottom'); + # colors + $replay_bg_orig = $replay_text->cget(-background); + $replay_fg_orig = $replay_text->cget(-foreground); + &wheelmousebindings($replay_text); + # build speed control buttons + my $ctrl_fm1 = $ctrl_fm->Frame()->pack(-side => 'left', -padx => 10); + $ctrl_fm1->Checkbutton(-text => "x0.5", -indicatoron => 0, + -height => 2, + -width => 4, + -onvalue => 0.5, + -variable => \$replay_speed, + -selectcolor => 'white')->pack(-side => 'left'); + $ctrl_fm1->Checkbutton(-text => "x1", -indicatoron => 0, + -height => 2, + -width => 4, + -onvalue => 1, + -variable => \$replay_speed, + -selectcolor => 'white')->pack(-side => 'left'); + $ctrl_fm1->Checkbutton(-text => "x2", -indicatoron => 0, + -height => 2, + -width => 4, + -onvalue => 2, + -variable => \$replay_speed, + -selectcolor => 'white')->pack(-side => 'left'); + $ctrl_fm1->Checkbutton(-text => "x5", -indicatoron => 0, + -height => 2, + -width => 4, + -onvalue => 5, + -variable => \$replay_speed, + -selectcolor => 'white')->pack(-side => 'left'); + $ctrl_fm1->Checkbutton(-text => "x9", -indicatoron => 0, + -height => 2, + -width => 4, + -onvalue => 9, + -variable => \$replay_speed, + -selectcolor => 'white')->pack(-side => 'left'); + # build hour label + my $ctrl_fm2 = $ctrl_fm->Frame()->pack(-side => 'left', -padx => 10); + my $hour_lab = $ctrl_fm2->Label(-borderwidth => 1, -relief => 'ridge', + -height => 2, -width => 9, + -textvariable => \$replay_hour, + )->pack(-side => 'left'); + + # build control buttons + my $ctrl_fm3 = $ctrl_fm->Frame()->pack(-side => 'left', -padx => 10); + $ctrl_fm3->Radiobutton(-text => "Play", -indicatoron => 0, + -width => 6, + -height => 2, + -value => 0, + -command => \&replayStart, + -variable => \$replay_runnable, + -selectcolor => 'white')->pack(-side => 'left'); + $ctrl_fm3->Radiobutton(-text => "Pause", -indicatoron => 0, + -width => 6, + -height => 2, + -value => 1, + -command => \&replayStop, + -variable => \$replay_runnable, + -selectcolor => 'white')->pack(-side => 'left'); + # build loop and step by step checkbuttons + my $ctrl_fm4 = $ctrl_fm->Frame(-relief => 'ridge', -borderwidth => 1 + )->pack(-side => 'left', -padx => 10, -fill => 'y', + -expand => 1); + $ctrl_fm4->Checkbutton(-text => "Repeat", + -variable => \$replay_repeat, + )->pack(-side => 'left'); -} # end loadfile - + $ctrl_fm4->Checkbutton(-text => "Step by\nstep", + -variable => \$replay_stepbystep, + -command => \&replayStop, + )->pack(-side => 'left', -padx => 10); + + # build close button + $ctrl_fm->Button(-text => "Close", + -command => \&replayClose, + -height => 1)->pack(-side => 'left', -padx => 10, -fill => 'y', + -expand => 1); + $replay_tpl->update; + $replay_tpl->minsize($replay_tpl->width, $replay_tpl->height); + + + # display messsages to replay + my ($sender, $time, $message); + while() { + chomp; + next if /^applications=/ or /^messages_number=/ or /^\s*$/ or /^(marker\d+)$/; + ($sender, $time, $message) = split(/ /, $_, 3); + if (defined $replay_max_time) { + $replay_max_time = $time if $time > $replay_max_time; + } else { + $replay_max_time = $time; + } + if (defined $replay_min_time) { + $replay_min_time = $time if $time < $replay_min_time; + } else { + $replay_min_time = $time; + } + $line++; + push(@{$replay_msg{$time}}, $message); + $replay_text->insert('end', &replayTime($time)." ".$message."\n", $time); + # when user click on a message, the begin time changes. + $replay_text->tagBind($time, '<1>', [sub { + my $ti = $_[1]; + my $replay_was_running; + if ($replay_running) { + $replay_was_running = 1; + &replayStop; + } + my $fg = $replay_text->tagCget($ti, -foreground); + my $bg = $replay_text->tagCget($ti, -background); + $replay_text->tagConfigure($_[1], -foreground => 'white', + -background => 'gray50'); + $replay_hour = &replayTime($ti); + $replay_time = $ti; + $replay_text->after(100, [sub { + $replay_text->tagConfigure($_[0], + -foreground => $fg, + -background => $bg); + }, $ti]); + &replayStart if $replay_was_running; + }, $time]); + $progressbar->value($line); + $progressbar->update if ($step == 0 or $line % $step == 0); + } + $replay_tpl->raise; + $replay_time = $replay_min_time; + $replay_text->configure(-state => 'disabled'); + # ivy bindings + $ivy->bindRegexp($opt_replaystartregexp, [\&replayStart]); + $ivy->bindRegexp($opt_replaystopregexp, [\&replayStop]); + + # baloon help + $balloonhelp->attach($replay_text, -balloonmsg => + "Select an item in the list to\n". + "modify the begin time of replay" + ); + +} # end loadfileForReplay + + +sub selectLoadingMode { + + my $diag = $mw->Dialog(-text => + "The selected file contains time informations. ". + "Do you want to replay the recorded messages or ". + "just display them?", + -default_button => 'Just display', + -buttons => ['Replay', 'Just display']); + my $answer = $diag->Show; + if ($answer eq 'Replay') { + return 'replay'; + } else { + return 'display'; + } + +} # end selectLoadingMode + sub savefile { my ($d, $m, $y, $h, $M) = (localtime(time))[3,4,5,2,1]; @@ -1950,26 +2289,37 @@ sub savefile { &$restorestate; return; } - # save connected applications name - my @clients = $clientsListbox->get(0, 'end'); - unless (print OUT "applications=", join(',', @clients),"\n") { + unless (&save) { $mw->Tk::Error("$!\n"); close(OUT); &$restorestate; return; } + close(OUT); + &$restorestate; + +} # end savefile + - my $nblines = $messagesText->index("end"); +sub save { + + my $tpl = $mw->Toplevel; + $tpl->Popup; + $tpl->title("Save"); + $tpl->geometry("200x50"); + $tpl->Label(-text => "Saving data...")->pack(-expand => 1, -fill => 'both'); + # save connected applications name + my @clients = $clientsListbox->get(0, 'end'); + print OUT "applications=", join(',', @clients),"\n" or return; + + my $nblines = $messagesText->index('end'); + #print "nblines=$nblines\n"; $nblines =~ s/\.\d+$//; - $nblines--; + $nblines -= 2; $progressbar->configure(-to => $nblines); my $step = int($nblines/10); #print "step=$step\n"; - unless (print OUT "messages_number=$nblines\n") { - $mw->Tk::Error("$!\n"); - close(OUT); - return; - } + print OUT "messages_number=$nblines\n" or return; # save messages 100 by 100, in order to reduce memory usage my $index = "1.0"; my $counter = 0; @@ -1981,66 +2331,107 @@ sub savefile { $progressbar->update; last; } - unless (print OUT $messages) { - $mw->Tk::Error("$!\n"); - close(OUT); - return; - } + print OUT $messages or return; $progressbar->value($counter); $counter += 100; $progressbar->update if ($step == 0 or $counter % $step == 0); } - close(OUT); $progressbar->value(0); + $mw->after(300, sub {$tpl->destroy;}); + $tpl->waitWindow; + return 1; + +} # end save - &$restorestate; - -} # end savefile - -sub clear { - if ($messagesNumber > 0) { - my $diag = $mw->Dialog(-text => - "Do you really want to remove displayed messages ?", - -default_button => 'OK', - -buttons => [qw(OK Cancel)]); - my $answer = $diag->Show; - if ($answer eq 'Cancel') { +#---------------------------------------------------------------------------------- +# Functions related to replay +#---------------------------------------------------------------------------------- +sub replayStart { + + my $loopflag = ($_[0] == 1) ? 1 : undef; + my $t = $replay_text; + print "abort 1 replayStart\n" if $replay_running and not $loopflag; + return if $replay_running and not $loopflag; + print "abort 2 replayStart\n" if $loopflag and not $replay_running ; + return if $loopflag and not $replay_running; + $replay_runnable = 0; + $replay_running = 1; + if ($replay_time > $replay_max_time) { + if ($replay_repeat == 1) { + $replay_time = $replay_min_time; + } else { + $t->tagConfigure($replay_last_time, + -foreground => $replay_fg_orig, + -background => $replay_bg_orig) + if defined $replay_last_time; + $replay_running = 0; + $replay_runnable = 1; return; } } - - # disable other buttons - my $jumpstate = $jumpButton->cget(-state); - my $startstate = $startButton->cget(-state); - my $stopstate = $stopButton->cget(-state); - $loadButton->configure(-state => 'disabled'); - $saveButton->configure(-state => 'disabled'); - $jumpButton->configure(-state => 'disabled'); - $clearButton->configure(-state => 'disabled'); - $startButton->configure(-state => 'disabled'); - $stopButton->configure(-state => 'disabled'); - $mw->update; - - $messagesText->configure(-state => 'normal'); - $messagesNumber = 0; - $recordedNumber = 0; - #$messagesText->delete('1.0', 'end'); - while ($messagesText->compare($messagesText->index('end'), ">", "2.0")) { - $messagesText->delete('1.0', '1000.0'); - my $index = $messagesText->index('end'); + $replay_hour = &replayTime($replay_time); + if (defined $replay_msg{$replay_time}) { + for my $msg (@{$replay_msg{$replay_time}}) { + &sendMsg($msg); + $t->tagConfigure($replay_last_time, + -foreground => $replay_fg_orig, + -background => $replay_bg_orig) + if defined $replay_last_time; + $t->tagConfigure($replay_time, + -foreground => $replay_fg, + -background => $replay_bg); + } + $replay_last_time = $replay_time; + my $i = $t->tagRanges($replay_time); + $t->see($i) if defined $i; } - $messagesText->configure(-state => 'disabled'); + $replay_time++; + # step by step mode + if ($replay_stepbystep) { + while (not defined $replay_msg{$replay_time}) { + $replay_time++; + last if $replay_time > $replay_max_time; + } + $replay_running = 0; + $replay_runnable = 1; + } else { + $replay_timer = $mw->after(1000/$replay_speed, [\&replayStart, 1]); + } + +} # end replayStart - # restore other buttons state - $loadButton->configure(-state => 'normal'); - $saveButton->configure(-state => 'normal'); - $jumpButton->configure(-state => $jumpstate); - $clearButton->configure(-state => 'normal'); - $startButton->configure(-state => $startstate); - $stopButton->configure(-state => $stopstate); +sub replayStop { -} # end clear + $replay_running = 0; + $replay_runnable = 1; + $replay_text->afterCancel($replay_timer) if defined $replay_timer; + +} # end replayStop + +sub replayClose { + + $ivy->bindRegexp($opt_replaystartregexp); + $ivy->bindRegexp($opt_replaystopregexp); + &replayStop(); + $replay_text->after(200, sub {$replay_tpl->destroy}); + $replay_speed = 1; + $replay_repeat = $opt_replayrepeat; + $replay_stepbystep = undef; + $replay_hour = '--:--:--'; + +} # end replayClose +sub replayTime { + + my $time = shift; + if (defined $time) { + my ($s, $m, $h) = localtime($time); + return sprintf("%02d:%02d:%02d", $h, $m, $s); + } else { + return "--:--:--"; + } + +} # end replayTime #---------------------------------------------------------------------------------- # General functions @@ -2196,6 +2587,17 @@ sub warning3 { } # end warning3 +sub quit { + + print "Quit\n"; + if (defined $opt_out) { + $mw->Tk::Error("$!\n") unless (&save); + close(OUT); + } + exit; + +} # end quit + __END__ =head1 NAME @@ -2206,26 +2608,32 @@ ivymon - a graphical application for monitoring Ivy =head1 SYNOPSIS B [B<-b> ivybus] [B<-help>] [B<-history> size] - [B<-size> window size] [B<-undersize|noundersize>] - [B<-bind> messageB<1>] ... [B<-bind> messageB] - [B<-send> regexpB<1>] ... [B<-send> regexpB] ... - [standard X11 options...] + [B<-size> window size] [B<-undersize>] + [B<-bind> regexpB<1>] ... [B<-bind> regexpB] + [B<-send> messageB<1>] ... [B<-send> messageB] + [B<-loadingmode> mode] [B<-replayrepeat>] + [B<-replaystartregexp> regexp] + [B<-replaystopregexp> regexp] + [B<-out> outputfile] [standard X11 options...] + [INPUT-FILE] =head1 DESCRIPTION -IvyMon is dedicated to monitor an Ivy bus. It prints out messages that match regular expressions, lets you send messages on bus, and provides a keywords search interface. +IvyMon is dedicated to monitor an Ivy bus. It prints out messages that match regular expressions, lets you send messages on bus, provides a keywords search interface, and can replay recorded messages. The main area is the window labeled B where are printed messages that forward on bus. Each message is preceded by sender application's name. You can insert a colored marker by double-clicking between two messages. -The B area lists the connected applications names. When you select an item in the listbox, the messages sent by this application are highlighted. You can display the agent's bindings or kill an agent by using the dedicated buttons below. +The B area lists the connected applications names. When you select an item in the listbox, the messages sent by this application are highlighted. You can display the agent's bindings or kill an agent by using the dedicated buttons. -The B area is used to manage regular expressions to subscribe to ivy messages. It provides an input field to enter new regexp, a first listbox which contains an alphabetical list of available bindings, and a second listbox of effective bindings. To bind a new regular expression, double-click on the corresponding item in the first list or select one, edit it in the input field and then hit the I key to validate. To remove subscription, double-click on the corresponding item in the second listbox. Pressing the I key inserts I<(.*)> string in entry field. Default bindings match Rejeu messages V2.40. This field provides completion and history functionalities. +The B area is used to manage regular expressions to subscribe to ivy messages. It provides an input field to enter new regexp, a first listbox which contains an alphabetical list of available bindings, and a second listbox of effective bindings. To bind a new regular expression, double-click on the corresponding item in the first list or select one, edit it in the input field and then hit the I key to validate. To remove subscription, double-click on the corresponding item in the second listbox. Pressing the I key inserts I<(.*)> string in entry field. This field provides completion and history functionalities. The bindings of connected ivy agents are automaticalley added to the list. You can add your own bindings using the I<-bind> option. -The B area is used to manage a list of predefined messages ready to be sent. It provides an input field to enter new message or edit existing one, and a listbox which contains an alphabetical list of available messages. When you validate an input (by pressing the I key), the new message is added to the listbox and sent on ivy bus. To send predefined messages, simply I on corresponding item in the listbox. Default messages match Rejeu messages V2.40. This field provides completion and history functionalities, and assistance for entering values. +The B area is used to manage a list of predefined messages ready to be sent. It provides an input field to enter new message or edit existing one, and a listbox which contains an alphabetical list of available messages. When you validate an input (by pressing the I key), the new message is added to the listbox and sent on ivy bus. To send predefined messages, simply I on corresponding item in the listbox. This field provides completion and history functionalities, and assistance for entering values. The application fits the bindings of connected ivy agents to build this list. You can add your own messages using the I<-send> option. The B area provides an interface for searching pattern in messages window. It provides an input field to enter new pattern, and control buttons. To highlight all matches, press the I button. To make incremental search, press the I button or the I key to proceed forward, and press the I button or the I key to proceed backward. This field provides an history functionality. -The B panel contains some general control buttons. Set ou unset balloon help functionality with the I checkbutton. Stop the messages scrolling with the I button. You must do it if you want to insert a marker in the messages window. Restart scrolling with the I button. To access markers sequentially, press the I button. At last, the I button closes the application without asking confirmation. +The B panel contains some general control buttons. Set or unset balloon help functionality with the I checkbutton. Stop the messages scrolling with the I button. You must do it if you want to insert a marker in the messages window. Restart scrolling with the I button. To access markers sequentially, press the I button. With the I and I buttons, you can select input and output files. The I button removes all displayed messages. At last, the I button closes the application. + +You can activate the B mode when the selected input file contains time information (file typically created with ivymon release 1.6 or later). In this case, you are asked to choose between replaying messages or just displaying them. If first mode is choosen, messages are loaded in a Replay separate window with control buttons for playing, pausing replay, changing its speed, selecting repetitive and step by step modes. The Replay mode can be also activated using command options and replay sequences can be partially controled by ivy messages. =head1 OPTIONS @@ -2234,31 +2642,51 @@ The B panel contains some general control buttons. Set ou unset balloon =item B<-b> ip:port -Sets the bus domain and port number to be used. Use $IVYBUS variable if defined. Default is 127.255.255.255:2010. +Set the bus domain and port number to be used. Use $IVYBUS variable if defined. Default is 127.255.255.255:2010. =item B<-help> -Gets some help +Get some help =item B<-history> size -Sets the history size of messages window (default: 200000). If ivymon receives more messages, they won't be displayed neither stored. +Set the history size of messages window (default: 200000). If ivymon receives more messages, they won't be displayed neither stored. =item B<-size> window size -Sets the size of the IvyMon window. Can be VGA or 640, SVGA or 800, XGA or 1024, SXGA or 1280, UXGA or 1600. Default is XGA (1024x768). +Set the size of the IvyMon window. Can be VGA or 640, SVGA or 800, XGA or 1024, SXGA or 1280, UXGA or 1600. Default is XGA (1024x768). -=item B<-undersize|noundersize> +=item B<-undersize> -Slightly reduce the IvyMon window to fit it in screen with borders of the window manager. Default: noundersize. +Slightly reduce the IvyMon window to fit it in screen with borders of the window manager. Option not set by default. =item B<-bind> regular_expression -Subscribes to ivy messages, by using regular expression. This option may be used several times. +Subscribe to ivy messages, by using regular expression. This option may be used several times. =item B<-send> ivy_message -Adds an ivy message in the list of potential messages to send. This option may be used several times. +Add an ivy message in the list of potential messages to send. This option may be used several times. + +=item B<-loadingmode> display|replay|replay-pause + +Specify how to manage the messages loaded at launching. If set to 'display', they are just displayed in the Messages area for analysis. If set to 'replay-pause' (the default value), they are loaded to a new Replay window and if set to 'replay', the replay is started in addition. This option is ignored unless input file command argument exists. + +=item B<-replayrepeat> + +If set, the replay sequence will be repetitive. + +=item B<-replaystartregexp> regexp + +Specify the ivy regular expression to match in order to start the replay sequence. Default is '^ClockStart'. You can do manually the same using the B button of the Replay window. + +=item B<-replaystopregexp> regexp + +Specify the ivy regular expression to match in order to stop the replay sequence. Default is '^ClockStop'. You can do manually the same using the B button of the Replay window. + +=item B<-out> output file + +Specify the output filename and pass in storage mode : the content of the Messages area will be written to file when you exit the application. You can do manually the same using the B button. =back @@ -2267,6 +2695,15 @@ Adds an ivy message in the list of potential messages to send. This option may b ivymon -b 10.192.36.255:3456 -history 20000 -bind 'PLN:(.*) SectorActivation sector=(.*)' -bind 'CLOCK start' -send 'TRAFFIC RadarEnd' -send 'AIRCRAFT: Activated' -size SVGA -undersize +ivymon -b 10.192.36.255:3456 -out /tmp/backup.ivy -loadingmode replay -replayrepeat -replaystartregexp '^ReplayOn' -replaystopregexp '^ReplayOff' /my/dir/log.ivy + +=head1 HISTORY + +B a Replay mode is added with new command options to parameter it. Input file can be loaded by command argument. Messages storage can be activated at launching using the -out command option. + +B Add a 'Filter' button to display the messages sent by an agent in a separate window. + +B Size management, contributed by Cedric Mariot. =head1 AUTHORS -- cgit v1.1