diff options
-rwxr-xr-x | src/ivymon | 1620 |
1 files changed, 942 insertions, 678 deletions
@@ -2,22 +2,29 @@ use Tk; use Tk::Font; +use Tk::LabFrame; +use Tk::Balloon; +use Tk::FBox; +use Tk::ErrorDialog; +use Tk::Dialog; use Ivy; +use Carp; use strict 'vars'; use Getopt::Long; +use Tk::CmdLine; use vars qw/$opt_help $opt_b $opt_history @opt_bind @opt_send/; -# geometrie -my $minW = 950; +# geometry +my $minW = 1050; my $minH = 768; my $ivy_port; # undef:=> default value is treated by ivy-perl -my $history = 10000; +my $history = 100000; my $casesensitiveflag = 1; -# divers +# misc my %connectedClients; -my %clientsIndex; my %bindings; +my %effectivebindings; my %bindingsIndex; my $selectedClient; my %msgToSend; @@ -38,7 +45,9 @@ my @bindHistory; my $bindHistoryIndex = -1; my @messagesbuffer; - + +my $markers_cnt = 0; + my $duration = 0; my $time = 0; my $fmtduration = '00:00'; @@ -46,62 +55,68 @@ my $bytes = 0 ; my $messagesNumber = 0; my $bindingsFlag = 0; my $stopFlag = 0; +my $noMessageYet = 1; +my $jump_cnt = 0; my $appname = 'IvyMon'; + +# Rejeu messages array my @send_def = ('ClockStop', 'ClockStart', 'ClockBackward', 'ClockForward', - 'SetClock Time=[]:[]:[]', - 'SetClock Rate=[]', + 'SetClock Time=', + 'SetClock Rate=', 'GetClockDatas', 'GetRadarRefresh', - 'GetTrajectory MsgName=[] Flight=[] From=[]', - 'GetPln MsgName=[] Flight=[] From=[]', - 'GetSectorsInfos MsgName=[] Flight=[]', - 'GetPosition MsgName=[] Flight=[] Time=[]:[]:[]', - 'GetOldPositions MsgName=[] Flight=[] Nb=[] ', - 'GetStrip MsgName=[] Flight=[] Sector=[]', - 'GetRange MsgName=[] Flight=[]', - 'GetDuplicate MsgName=[] Flight=[]', - 'GetDataBaseInfos MsgName=[] Cond=[]', - 'GetFlightsWithStrip MsgName=[] Sector=[]', - 'SetStripTime Flight=[] Sector=[] Time=[]:[]', - 'SetSectorIn Flight=[] Sector=[] Time=[]:[]', - 'SetSectorOut Flight=[] Sector=[] Time=[]:[]', - 'SetSectorsInfos Flight=[] List=[]', - 'SetCallSign Flight=[] CallSign=[]', - 'SetTfl Flight=[] Sector=[] Tfl=[]', - 'SetBeforeTrack Flight=[] List=[]', - 'SetAfterTrack Flight=[] List=[]', - 'TranslateTime Flight=[] Shift=[]:[]:[]', - 'TranslateFl Flight=[] Delta=[]', - 'AircraftDirect Flight=[] Beacon=[]', - 'AircraftHeading Flight=[] To=[]', - 'AircraftTurn Flight=[] Angle=[]', - 'AircraftLevel Flight=[] Fl=[]', - 'SetUserEventList Flight=[] EventName=[] List=[]', - 'MergeUserEventList Flight=[] EventName=[] List=[]', - 'GetUserEventList Flight=[] Name=[]', - 'GetUserEventNames MsgName=[] Flight=[]', - 'CancelLastOrder Flight=[]', - 'Discard Flight=[]', - 'Enable Flight=[]', - 'Dump Flight=[]', + 'GetTrajectory MsgName= Flight= From=', + 'GetPln MsgName= Flight= From=', + 'GetSectorsInfos MsgName= Flight=', + 'GetPosition MsgName= Flight= Time=', + 'GetOldPositions MsgName= Flight= Nb= ', + 'GetStrip MsgName= Flight= Sector=', + 'GetRange MsgName= Flight=', + 'GetDuplicate MsgName= Flight=', + 'GetDataBaseInfos MsgName= Cond=', + 'GetFlightsWithStrip MsgName= Sector=', + 'SetStripTime Flight= Sector= Time=', + 'SetSectorIn Flight= Sector= Time=', + 'SetSectorOut Flight= Sector= Time=', + 'SetSectorsInfos Flight= List=', + 'SetCallSign Flight= CallSign=', + 'SetMiniPln Flight= Ssr= Dep= Arr=', + 'SetTfl Flight= Sector= Tfl=', + 'SetBeforeTrack Flight= List=', + 'SetAfterTrack Flight= List=', + 'TranslateTime Flight= Shift=', + 'TranslateFl Flight= Delta=', + 'AircraftDirect Flight= Beacon=', + 'AircraftHeading Flight= To=', + 'AircraftTurn Flight= Angle=', + 'AircraftLevel Flight= Fl=', + 'SetUserEventList Flight= EventName= List=', + 'MergeUserEventList Flight= EventName= List=', + 'GetUserEventList Flight= Name=', + 'GetUserEventNames MsgName= Flight=', + 'CancelLastOrder Flight=', + 'Discard Flight=', + 'Enable Flight=', + 'Dump Flight=', 'StripsOnOff Print=yes', 'StripsOnOff Print=no', - 'TagStrip Flight=[] Sector=[]', + 'TagStrip Flight= Sector=', 'PrintTaggedStrips', - 'Debug Flight=[]', - 'FileRead Type=rejeu Name=[]', - 'FileRead Type=simu Name=[]', - 'FileWrite Type=rejeu Name=[]', - 'FileWrite Type=simu Name=[]', - 'FileWrite Type=dump Name=[]', + 'Debug Flight=', + 'FileRead Type=rejeu Name=', + 'FileRead Type=simu Name=', + 'FileWrite Type=rejeu Name=', + 'FileWrite Type=simu Name=', + 'FileWrite Type=dump Name=', ); +# Rejeu available (not effective) bindings array my @bind_def = ('(.*)', '(^ClockEvent .*)', '(^RadarEndEvent)', @@ -148,6 +163,7 @@ my @bind_def = ('(.*)', '(^SetSectorOut .*)', '(^SetSectorsInfos .*)', '(^SetCallSign .*)', + '(^SetMiniPln .*)', '(^SetTfl .*)', '(^SetBeforeTrack .*)', '(^SetAfterTrack .*)', @@ -163,7 +179,6 @@ my @bind_def = ('(.*)', '(^MergeUserEventList .*)', '(^GetUserEventList .*)', '(^UserEventList .*)', - '(^etUserEventNames .*)', '(^SetUserEventList .*)', '(^UserEventNames .*)', '(^CancelLastOrder .*)', @@ -178,33 +193,30 @@ my @bind_def = ('(.*)', '(^FileWrite .*)', ); -my @send = @send_def; -my @bind = @bind_def; +# Effective bindings array +my @effectivebind; -my $mw = MainWindow->new; -$mw->geometry($minW.'x'.$minH); -$mw->minsize($minW, $minH); +#---------------------------------------------------------------------------------- +# command line options management +#---------------------------------------------------------------------------------- + +Tk::CmdLine::SetArguments(-font => '7x14'); +Tk::CmdLine::SetArguments(); if (not GetOptions('-help', '-history=s', '-b=s', '-bind=s@', '-send=s@') or $opt_help) { print "Usage: ivymon [-b bus] [-help] [-history length]\n"; print " [-bind regexp1] ... [-bind regexpN] \n"; print " [-send message1] ... [-send messageN] \n"; + print " [standard X11 options...]\n"; print "\n"; print "Options :\n"; - print " -b <[addr]:port> Ivy bus (default is \$IVYBUS or $ivy_port)\n"; - print " -history <lenght> Messages list history length (default is $history)\n"; + print " -b <[addr]:port> Ivy bus (default is \$IVYBUS)\n"; + print " -history <length> Messages list history length ". + "(default $history messages)\n"; print " -bind <regexp> Ivy binding regular expression\n"; print " -send <string> Ivy message to send\n"; print "\n"; - print "Default bindings are : '", $bind_def[0], "'\n"; - for (my $i=1; $i < @bind_def; $i++) { - print " '", $bind_def[$i], "'\n"; - } - print "Default messages are : '", $send_def[0], "'\n"; - for (my $i=1; $i < @send_def; $i++) { - print " '", $send_def[$i], "'\n"; - } exit ; } @@ -212,156 +224,173 @@ if (not GetOptions('-help', '-history=s', '-b=s', '-bind=s@', '-send=s@') or $op $ivy_port = $opt_b if $opt_b and $opt_b =~ /^(\d+\.\d+\.\d+\.\d+)?(,\d+\.\d+\.\d+\.\d+)*:\d+/; $history = $opt_history if $opt_history; -$mw->title("IvyMon ($ivy_port)"); - -push(@bind, @opt_bind); -push(@send, @opt_send); -#---------------------------------------------------------------------------------- -# La frame du haut $frame1top prend toute la largeur de l'appli et contient un w -# idget Text ou sont affiches les messages -#---------------------------------------------------------------------------------- +my $title; +if ($ivy_port) { + $title = "Ivymon ($ivy_port)"; +} else { + $title = "Ivymon (default port)"; +} -my $frame1top = +push(@effectivebind, @opt_bind); +@effectivebind = ('(.*)') unless @effectivebind > 0; +push(@bind_def, @opt_bind); +push(@send_def, @opt_send); + +#================================================================================= +# +# H M I +# +#================================================================================= +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('<Key-Tab>', sub {Tk->break}); + + +# create balloon help widget +my $balloonhelp = $mw->Balloon(-balloonposition => 'mouse', + -state => 'none', + ); +# create base frames +my $top_fm = $mw->Frame()->pack(-fill => 'both', -side => 'top', -expand => 1, -padx => 5, -pady => 5); - -my $frame11messages = $frame1top; - - -#---------------------------------------------------------------------------------- -# La frame du bas $frame2bottom est composee d'une frame Commandes a droite, d'une -# frame Clients au centre et d'une frame regroupant Abonnements, Messages a emettre -# et Recherche -#---------------------------------------------------------------------------------- -my $frame2bottom = + +my $bottom_fm = $mw->Frame()->pack(-fill => 'y', -side => 'bottom', -padx => 5, -pady => 5, - -ipadx => 3, -ipady => 3); -my $frame21left = - $frame2bottom->Frame()->pack(-fill => 'both', - -side => 'left', - -expand => 1, - ); -my $frame22center = - $frame2bottom->Frame()->pack(-fill => 'both', - -side => 'left', - -expand => 1, + ); +my $bindings_fm = + $bottom_fm->LabFrame(-label => 'Bindings : ', + -labelside => 'acrosstop', + -borderwidth => 3)->pack(-fill => 'both', + -side => 'left', + -padx => 5, + -expand => 1, ); -my $frame23right = - $frame2bottom->Frame()->pack(-fill => 'both', - -side => 'right', - -expand => 1, - ); - -my $frame21appli = $frame22center; - -my $frame21control = $frame23right; - -my $frame212bottom = $frame21left->Frame()->pack(-fill => 'both', - -side => 'bottom', - ); -my $frame13send = $frame212bottom; - -my $frame2111left = $frame21left->Frame()->pack(-fill => 'both', - -side => 'left', - ); -my $frame2112right = $frame21left->Frame()->pack(-fill => 'both', - -side => 'right', - ); - -my $frame121bindings = $frame2111left; -my $frame122search = $frame2112right; +my $clients_fm = + $bottom_fm->LabFrame(-label => 'Applications : ', + -labelside => 'acrosstop', + -borderwidth => 3)->pack(-fill => 'both', + -side => 'left', + -expand => 1, + -padx => 5, + ); +my $send_fm = + $bottom_fm->LabFrame(-label => 'Messages to send : ', + -labelside => 'acrosstop', + -borderwidth => 3)->pack(-fill => 'both', + -side => 'left', + -expand => 1, + -padx => 5, + ); +my $searchandcontrol_fm = $bottom_fm->Frame()->pack(-side => 'right'); +my $search_fm = + $searchandcontrol_fm->LabFrame(-label => 'Search : ', + -labelside => 'acrosstop', + -borderwidth => 3)->pack(-fill => 'none', + -side => 'top', + -padx => 5, + -expand => 1); +my $control_fm = + $searchandcontrol_fm->LabFrame(-label => 'Control : ', + -labelside => 'acrosstop', + -borderwidth => 3)->pack(-fill => 'none', + -side => 'bottom', + -padx => 5, + -expand => 1); +my $progressbar = + $bottom_fm->ProgressBar(-from => 0, + -length => 200, + -borderwidth => 2, + -colors => [ 0 => 'yellow'], + -relief => 'sunken', + -resolution => 0, + -anchor => 'n', + )->pack(#-fill => 'both', + -side => 'right', + #-expand => 1, + -padx => 5, + ); #---------------------------------------------------------------------------------- -# Description de la zone Messages +# Messages display area #---------------------------------------------------------------------------------- +my $top2_fm = $top_fm->Frame()->pack(-side => 'top', + -fill => 'x', + ); my $messagesLabel = - $frame11messages->Label(-text => "Messages :")->pack(-side => 'top', - -anchor => 'w'); - -my $textFont = $mw->fontCreate('H_Normal', - -family => 'Helvetica', - -size => 11); -my $textItalicFont = $mw->fontCreate('H_Italic', - -family => 'Helvetica', - -size => 11, - -slant => 'italic'); -my $textBoldFont = $mw->fontCreate('H_Bold', - -family => 'Helvetica', - -size => 11, - -weight => 'bold'); + $top2_fm->Label(-text => "Messages :")->pack(-side => 'left'); + +my $messagesCounterLabel = + $top2_fm->Label(-textvariable => \$messagesNumber)->pack(-side => 'right'); + +# my $textFont = $mw->fontCreate('H_Normal', +# -family => 'Helvetica', +# -size => 11); +# my $textItalicFont = $mw->fontCreate('H_Italic', +# -family => 'Helvetica', +# -size => 11, +# -slant => 'italic'); +# my $textBoldFont = $mw->fontCreate('H_Bold', +# -family => 'Helvetica', +# -size => 11, +# -weight => 'bold'); my $messagesText = - $frame11messages->Scrolled(Text, - -scrollbars => 'e', - -height => 18, - -font => 'H_Bold', - -spacing1 => 2, - -spacing2 => 0, - -spacing3 => 2, - -state => 'disabled')->pack(-fill => 'both', - -expand => 1, - -side => 'bottom'); + $top_fm->Scrolled(Text, + -scrollbars => 'e', + -height => 18, + #-font => 'H_Bold', + -spacing1 => 2, + -spacing2 => 0, + -spacing3 => 2, + -state => 'disabled', + )->pack(-fill => 'both', + -expand => 1, + -side => 'bottom'); &wheelmousebindings($messagesText); +$messagesText->bind('<1>', sub {$messagesText->focus;}); +$messagesText->bind('<Double-1>', \&marker); + +my $bgcolor = $messagesText->cget(-background); -my $messagesBg = $messagesText->cget(-background); +# text tag creation $messagesText->tagConfigure('sender', -background => 'gray50', -foreground => 'gray90'); -$messagesText->tagConfigure($appname, -foreground => 'gray40'); +$messagesText->tagConfigure($appname, -foreground => 'gray30'); +$messagesText->tagConfigure('marker0', -background => 'lightcoral', + -foreground => 'lightcoral'); +$messagesText->tagConfigure('marker1', -background => 'LightGoldenrod', + -foreground => 'LightGoldenrod'); +$messagesText->tagConfigure('marker2', -background => 'skyblue', + -foreground => 'skyblue'); +$messagesText->tagConfigure('marker3', -background => 'darkseagreen', + -foreground => 'darkseagreen'); +$messagesText->tagConfigure('marker4', + -background => 'ivory', -foreground => 'ivory'); -#---------------------------------------------------------------------------------- -# Description de la zone Clients -#---------------------------------------------------------------------------------- -my $clientsLabel = - $frame21appli->Label(-text => "Applications :")->pack(-side => 'top', - -anchor => 'w'); -my $clientsListbox = - $frame21appli->Scrolled(Listbox, - -scrollbars => 's')->pack(-fill => 'both', - -expand => 1, - -side => 'top'); -$clientsListbox->bind('<1>', [\&selectClient]); -&wheelmousebindings($clientsListbox); #---------------------------------------------------------------------------------- -# Description de la zone Abonnements +# Bindings area #---------------------------------------------------------------------------------- -# la frame Abonnements -$frame121bindings = - $frame121bindings->LabFrame(-label => 'Bindings : ', - -labelside => 'acrosstop', - -borderwidth => 3)->pack(-fill => 'none', - -side => 'left', - -expand => 1); -# la frame de gauche contenant le champ de saisie et la listbox -my $frame1211 = - $frame121bindings->Frame()->pack(-fill => 'none', - -padx => 10, -pady => 5, - -side => 'left', - -expand => 1); -# la frame de droite contenant les boutons de gestion de la listbox -my $frame1212 = - $frame121bindings->Frame()->pack(-fill => 'none', - -padx => 10, -pady => 5, - -ipady => 13, - -side => 'right', - -expand => 1); - -# creation du champ de saisie + bindings associes my $bindingsEntry = - $frame1211->Entry(-width => 40)->pack(-fill => 'x', - -side => 'top', - -anchor => 'w', - -ipady => 3, - -expand => 0, - -pady => 5); + $bindings_fm->Entry(-width => 30)->pack(-fill => 'x', + -side => 'top', + -anchor => 'w', + -ipady => 3, + -expand => 0, + -pady => 5); $bindingsEntry->bind('<Escape>' => [\&addBindingExpression]); -$bindingsEntry->bind('<Return>' => [\&addBinding, 1]); +$bindingsEntry->bind('<Return>' => [\&addBinding]); $bindingsEntry->bind('<Down>' => [\&bindingNextExpression]); $bindingsEntry->bind('<Control-n>' => [\&bindingNextExpression]); @@ -370,57 +399,80 @@ $bindingsEntry->bind('<Control-p>' => [\&bindingPrevExpression]); $bindingsEntry->focus; -# creation de la liste + bindings associes my $bindingsList = - $frame1211->Scrolled(Listbox, - -scrollbars => 'e', - -height => 4, - -width => 40)->pack(-fill => 'y', - -side => 'bottom', - -anchor => 'w', - -expand => 1); + $bindings_fm->Scrolled(Listbox, + -scrollbars => 'e', + -height => 4, + -width => 30)->pack(-fill => 'y', + -side => 'top', + -anchor => 'w', + -expand => 1); $bindingsList->bind('<1>', [\&selectBinding]); +$bindingsList->bind('<Double-1>', [\&addBinding]); +$bindingsEntry->bind('<Key>' => [\&findExprInList, $bindingsList]); + +my $effectivebindingsList = + $bindings_fm->Scrolled(Listbox, + -scrollbars => 'e', + -height => 4, + -width => 30)->pack(-fill => 'y', + -side => 'bottom', + -anchor => 'w', + -expand => 1); +$effectivebindingsList->bind('<Double-1>', [\&removeBinding]); + &wheelmousebindings($bindingsList); -# creation des boutons -my $bindingsClear = - $frame1212->Button(-text => 'Clear', - -height => 1, - -command => [\&clearBinding])->pack(-fill => 'both', - -side => 'top', - -expand => 1); -my $bindingsAdd = - $frame1212->Button(-text => 'Add', - -height => 1, - -command => [\&addBinding, 1])->pack(-fill => 'both', - -side => 'top', - -expand => 1); -my $bindingsRemove = - $frame1212->Button(-text => 'Remove', - -height => 1, - -state => 'disabled', - -command => [\&removeBinding])->pack(-fill => 'both', - -side => 'top', - -expand => 1); +#---------------------------------------------------------------------------------- +# Connected applications area +#---------------------------------------------------------------------------------- +my $clientsListbox = + $clients_fm->Scrolled(Listbox, + -scrollbars => 's')->pack(-fill => 'both', + -expand => 1, + -side => 'top'); +$clientsListbox->bind('<1>', [\&selectClient]); +&wheelmousebindings($clientsListbox); + +#---------------------------------------------------------------------------------- +# Messages to send area +#---------------------------------------------------------------------------------- + +my $sendEntry = + $send_fm->Entry(-width => 40)->pack(-fill => 'x', + -side => 'top', + -anchor => 'w', + -ipady => 3, + -expand => 0, + -pady => 5); + +my $sendList = + $send_fm->Scrolled(Listbox, + -scrollbars => 'e', + -width => 40)->pack(-fill => 'y', + -anchor => 'w', + -side => 'bottom', + -expand => 1); +$sendEntry->bind('<Key>' => [\&findExprInList, $sendList]); +$sendEntry->bind('<Return>' => [\&addMsgToSend, undef, 1]); + +$sendEntry->bind('<Down>' => [\&sendNextExpression]); +$sendEntry->bind('<Control-n>' => [\&sendNextExpression]); +$sendEntry->bind('<Up>' => [\&sendPrevExpression]); +$sendEntry->bind('<Control-p>' => [\&sendPrevExpression]); + +$sendList->bind('<1>', [\&selectMsgToSend]); +$sendList->bind('<Double-1>', [\&addMsgToSend, undef, 1]); +&wheelmousebindings($sendList); + #---------------------------------------------------------------------------------- -# Description de la zone Recherche +# Search area #---------------------------------------------------------------------------------- -# la frame Recherche -$frame122search = - $frame122search->LabFrame(-label => 'Search : ', - -labelside => 'acrosstop', - -borderwidth => 3)->pack(-fill => 'none', - -padx => 15, - -side => 'left', - -expand => 1); - -# le champ de saisie + bindings associes my $searchEntry = - $frame122search->Entry(-width => 20)->pack(-fill => 'none', - -ipady => 3, - -expand => 1, - -padx => 10, -pady => 3); + $search_fm->Entry(-width => 24)->grid(-column => 1, + -row => 1, + -columnspan => 3); $searchEntry->bind('<Return>' => [\&searchNext, 1]); $searchEntry->bind('<Next>' => [\&searchNext, 1]); $searchEntry->bind('<Control-s>' => [\&searchNext, 1]); @@ -434,202 +486,188 @@ $searchEntry->bind('<Control-n>' => [\&searchNextExpression]); $searchEntry->bind('<Up>' => [\&searchPrevExpression]); $searchEntry->bind('<Control-p>' => [\&searchPrevExpression]); -# la frame comprenant les boutons associes et le Min/Maj checkbutton -my $frame1220 = - $frame122search->Frame()->pack(-fill => 'both', - -side => 'top', - -expand => 0, - -padx => 10, -pady => 5); -my $casesensitivecb = $frame1220->Checkbutton(-text => 'Case sensitive', - -variable => \$casesensitiveflag - )->pack(-fill => 'x', - -side => 'top', - -expand => 1); -# la frame comprenant la rangee superieure de bouton Previous et Next -my $frame1221 = - $frame1220->Frame()->pack(-fill => 'x', - -side => 'top', - -expand => 0); -# la frame comprenant la rangee inferieure de bouton Clear et All -my $frame1222 = - $frame1220->Frame()->pack(-fill => 'x', - -side => 'top', - -expand => 0); +my $casesensitive_cb = + $search_fm->Checkbutton(-text => 'Case sensitive', + -variable => \$casesensitiveflag + )->grid(-column => 1, + -row => 2, + -columnspan => 3); my $searchPrev = - $frame1221->Button(-text => 'Previous', + $search_fm->Button(-text => 'Prev', -height => 1, -width => 4, - -command => [\&searchPrev])->pack(-fill => 'x', - -side => 'left', - -expand => 1); + -command => [\&searchPrev])->grid(-column => 1, + -row => 3); my $searchNext = - $frame1221->Button(-text => 'Next', + $search_fm->Button(-text => 'Next', -width => 4, -height => 1, - -command => [\&searchNext])->pack(-fill => 'x', - -side => 'right', - -expand => 1); -my $searchClear = - $frame1222->Button(-text => 'Clear', - -width => 4, - -height => 1, - -command => [\&clearSearch])->pack(-fill => 'x', - -side => 'left', - -expand => 1); + -command => [\&searchNext])->grid(-column => 2, + -row => 3); my $searchAll = - $frame1222->Button(-text => 'All', + $search_fm->Button(-text => 'All', -width => 4, -height => 1, - -command => [\&searchAll])->pack(-fill => 'x', - -side => 'right', - -expand => 1); - -#---------------------------------------------------------------------------------- -# Description de la zone Emission -#---------------------------------------------------------------------------------- -# la frame Emission -$frame13send = - $frame13send->LabFrame(-label => 'Messages to send : ', - -labelside => 'acrosstop', - -borderwidth => 3)->pack(-fill => 'none', - -side => 'left', - -expand => 1); - - -# la frame de gauche contenant le champ de saisie et la listbox -my $frame131 = - $frame13send->Frame()->pack(-fill => 'none', - -padx => 9, -pady => 5, - -side => 'left', - -expand => 1); -# la frame de droite contenant les boutons de gestion de la listbox -my $frame132 = - $frame13send->Frame()->pack(-fill => 'none', - -padx => 10, -pady => 5, - -ipady => 13, - -side => 'right', - -expand => 1); - -# creation du champ de saisie + bindings associes -my $sendEntry = - $frame131->Entry(-width => 69)->pack(-fill => 'x', - -side => 'top', - -anchor => 'w', - -ipady => 3, - -expand => 0, - -pady => 5); -$sendEntry->bind('<Return>' => [\&addMsgToSend, 1]); - -$sendEntry->bind('<Down>' => [\&sendNextExpression]); -$sendEntry->bind('<Control-n>' => [\&sendNextExpression]); -$sendEntry->bind('<Up>' => [\&sendPrevExpression]); -$sendEntry->bind('<Control-p>' => [\&sendPrevExpression]); - -# creation de la liste + bindings associes -my $sendList = - $frame131->Scrolled(Listbox, - -scrollbars => 'e', - -height => 4, - -width => 69)->pack(-fill => 'y', - -anchor => 'w', - -side => 'bottom', - -expand => 1); -$sendList->bind('<1>', [\&selectMsgToSend]); -$sendList->bind('<Double-1>', [\&addMsgToSend, 1]); -&wheelmousebindings($sendList); - -# creation des boutons -my $sendClear = - $frame132->Button(-text => 'Clear', - -height => 1, - -command => [\&clearMsgToSend])->pack(-fill => 'both', - -side => 'top', - -expand => 1); -my $sendAdd = - $frame132->Button(-text => 'Send', - -height => 1, - -command => [\&addMsgToSend, 1])->pack(-fill => 'both', - -side => 'top', - -expand => 1); -my $sendRemove = - $frame132->Button(-text => 'Remove', - -height => 1, - -state => 'disabled', - -command => [\&removeMsgToSend])->pack(-fill => 'both', - -side => 'top', - -expand => 1); - + -command => [\&searchAll])->grid(-column => 3, + -row => 3); #---------------------------------------------------------------------------------- -# Description de a la zone Commande +# Control panel #---------------------------------------------------------------------------------- -my $frame211control = - $frame21control->LabFrame(-label => "Control :", - -labelside => "acrosstop", - -borderwidth => 3)->pack(-fill => 'none', - -ipadx => 10, - -ipady => 10, - -side => 'left', - -expand => 1); -# utilisation d'un grid -$frame211control->gridBbox(2, 3); -my $helpButton = - $frame211control->Button(-height => 3, - -width => 5, - -command => [\&loadfile, 1], - -state => 'disabled', - -text => 'Help')->grid(-column => 1, - -row => 1); +my $balloonflag = 0; +my $balloon_cb = +$control_fm->Checkbutton(-text => 'Balloon help', + -variable => \$balloonflag, + -command => sub { + if ($balloonflag == 0) { + $balloonhelp->configure(-state => 'none'); + } else { + $balloonhelp->configure(-state => 'balloon'); + } + } + ); +$balloon_cb->grid(-column => 1, + -columnspan => 3, + -row => 1); my $loadButton = - $frame211control->Button(-height => 3, - -width => 5, - -command => [\&loadfile, 1], - -state => 'disabled', - -text => 'Load')->grid(-column => 1, - -row => 2); + $control_fm->Button(-height => 2, + -width => 4, + -command => [\&loadfile, 1], + -state => 'normal', + -text => 'Load')->grid(-column => 1, + -row => 2); my $saveButton = - $frame211control->Button(-height => 3, - -width => 5, - -command => [\&savefile, 1], - -state => 'disabled', - -text => 'Save')->grid(-column => 2, - -row => 2); + $control_fm->Button(-height => 2, + -width => 4, + -command => [\&savefile, 1], + -state => 'normal', + -text => 'Save')->grid(-column => 2, + -row => 2); + +my $jumpButton = + $control_fm->Button(-height => 2, + -width => 4, + -command => [\&jump], + -state => 'disabled', + -text => 'Jump')->grid(-column => 3, + -row => 2); my $startButton = - $frame211control->Button(-height => 3, - -width => 5, - -command => [\&start, 1], - -state => 'disabled', - -text => 'Continue')->grid(-column => 1, - -row => 3); + $control_fm->Button(-height => 2, + -width => 4, + -command => [\&start, 1], + -state => 'disabled', + -text => 'Scroll')->grid(-column => 1, + -row => 3); my $stopButton = - $frame211control->Button(-height => 3, - -width => 5, - -command => [\&stop, 1], - -text => "Pause")->grid(-column => 2, - -row => 3); -my $clearButton = - $frame211control->Button(-height => 3, - -width => 5, - -command => [\&clearMessages], - -text => "Clear")->grid(-column => 1, - -row => 4); - + $control_fm->Button(-height => 2, + -width => 4, + -command => [\&stop, 1], + -text => "Scroll\nLock")->grid(-column => 2, + -row => 3); my $exitButton = - $frame211control->Button(-height => 3, - -width => 5, - -command => [\&bye], - -text => 'Exit')->grid(-column => 2, - -row => 4); - + $control_fm->Button(-height => 2, + -width => 4, + -command => [\&bye], + -text => 'Exit')->grid(-column => 3, + -row => 3); +#---------------------------------------------------------------------------------- +# Update default bindings and messages lists +#---------------------------------------------------------------------------------- +# add default bindings in available bindings list +for my $bind (sort(@bind_def)) { + &addBindingInList($bind); +} + +# add default messages in available messages list +for my $msg (sort(@send_def)) { + &addMsgToSend(undef, $msg); +} #---------------------------------------------------------------------------------- -# Demarrage d'Ivy et abonnements +# Balloon help messages #---------------------------------------------------------------------------------- +$balloonhelp->attach($bindingsEntry, -balloonmsg => + "This input field is used to enter new bindings or\n". + "edit default one from list above. In both case, hit\n". + "[Enter] to apply. Provides completion and history\n". + "functionalities : type part of a word and hit the\n". + "[Tab] key to activate completion, use [Up] and\n". + "[Down] keys to access previous inputs." + ); +$balloonhelp->attach($bindingsList, -balloonmsg => + "Available bindings list. Select an item for edition\n". + "or double-click on a predefined binding to activate it." + ); + +$balloonhelp->attach($effectivebindingsList, -balloonmsg => + "Effective bindings list. Double-click on binding to unset it." + ); +$balloonhelp->attach($clientsListbox,-balloonmsg => + "Select an application name to highlight\n". + "related Ivy messages in the Messages area"); +$balloonhelp->attach($sendEntry, -balloonmsg => + "This input field is used to enter new messages or\n". + "edit default one from list above. In both case, hit\n". + "[Enter] to send it. Provides completion and history\n". + "functionalities : type part of a word and hit the\n". + "[Tab] key to activate completion, use [Up] and\n". + "[Down] keys to access previous inputs." + ); +$balloonhelp->attach($sendList, -balloonmsg => + "Available messages list. Select an item for edition.\n". + "or double click on predefined message to send it." + ); +$balloonhelp->attach($messagesText, -balloonmsg => + "You can insert colored marker by double-clicking between\n". + "two messages. Then, you can quickly access markers\n". + "using the [Jump] button in control panel. To remove a\n". + "marker, just double-click on it." + ); +$balloonhelp->attach($searchEntry, -balloonmsg => + "This input field is used to search expressions\n". + "in messages list. Hit [Return] key to search\n". + "forward and [Shift-Return] key to search backward.\n". + "Provides history functionality : use [Up] and [Down]\n". + "keys to access previous inputs." + ); +$balloonhelp->attach($stopButton, -balloonmsg => + "Stop scrolling in Messages area" + ); +$balloonhelp->attach($startButton, -balloonmsg => + "Restart scrolling in Messages area" + ); +$balloonhelp->attach($jumpButton, -balloonmsg => + "Access next marker in Messages area" + ); + +# alarm on balloon help +my $repeatid = $balloon_cb->repeat(200, sub { + if ($balloon_cb->cget(-foreground) eq 'ivory') { + $balloon_cb->configure(-foreground => 'black'); + } else { + $balloon_cb->configure(-foreground => 'ivory'); + } +}); +$balloon_cb->after(2000, sub {$balloon_cb->configure(-foreground => 'black'); + $balloon_cb->afterCancel($repeatid) + }); + + +#================================================================================= +# +# Ivy initialisation and Ivy bindings +# +#================================================================================= +# add Ivymon in connected applications list +$connectedClients{$appname}++; +&addClient($appname, 'localhost', 1); + +# init Ivy bus and start it. Ivy->init(-loopMode => 'TK', -appName => $appname, -ivyBus => $ivy_port, @@ -637,95 +675,117 @@ Ivy->init(-loopMode => 'TK', my $ivy = Ivy->new(-statusFunc => \&checkClientsStatus); $ivy->start; -# par defaut on cree les abonnements suivants : -for my $bind (sort(@bind)) { - $bindingsEntry->delete(0, 'end'); - $bindingsEntry->insert(0, $bind); - &addBinding; -} -$bindingsEntry->delete(0, 'end'); - -# par defaut on cree les messages suivants : -for my $msg (sort(@send)) { - $sendEntry->delete(0, 'end'); - $sendEntry->insert(0, $msg); - &addMsgToSend; +# add Ivy bindings +for my $bind (@effectivebind) { + &addBinding(undef, $bind); } -$sendEntry->delete(0, 'end'); -# ajout de Ivymon dans la liste des applications connectees -$connectedClients{$appname} = 1; -&addClient($appname); MainLoop; #================================================================================== -# FONCTIONS IVY +# +# Functions +# #================================================================================== + +#---------------------------------------------------------------------------------- +# Ivy functions +#---------------------------------------------------------------------------------- sub addIvyBinding { my $binding = shift; #print "in addIvyBinding $binding\n"; return if $stopFlag; - $ivy->bindRegexp($binding, [\&updateMessages]); -} + $ivy->bindRegexp($binding, [sub { + $messagesNumber++; + if ($stopFlag) { + &bufferizeMessages(@_); + } else { + &updateMessages(@_); + } + }]); + + + +} # end addIvyBinding sub removeIvyBinding { my $binding = shift; $ivy->bindRegexp($binding); -} -#================================================================================== -# CALLBACK TK -#================================================================================== +} # end removeIvyBinding + #---------------------------------------------------------------------------------- -# Fonctions associees a la liste des applications connectees +# Functions related to connected applications management #---------------------------------------------------------------------------------- sub checkClientsStatus { - my ($present, $absent) = @_; - #print "in checkClientsStatus\n"; - my %present; - for (@$present) { - $present{$_} = 1; - unless ($connectedClients{$_}) { - $connectedClients{$_} = 1; - &addClient($_); - } + my $appname = $_[3]; + my $status = $_[4]; + my $host = $_[5]; + my $message; + if ($status eq 'died') { + $connectedClients{$appname}--; + } elsif ($status eq 'new') { + $connectedClients{$appname}++; + } else { + carp "In Ivymon, checkClientsStatus function, unknown status <$status>\n"; } - for (keys(%connectedClients)) { - unless ($present{$_} or $_ eq $appname) { - delete $connectedClients{$_}; - &removeClient($_); - } + if ($connectedClients{$appname} == 0) { + &removeClient($appname, $host); + } elsif ($connectedClients{$appname} >= 1) { + &addClient($appname, $host, $connectedClients{$appname}); + } else { + carp "In Ivymon, checkClientsStatus function, found negative ". + "<$connectedClients{$appname}> instances of application <$appname>!!!\n"; } -} + +} # end checkClientsStatus + + sub addClient { my $client = shift; - $clientsListbox->insert('end', $client); - &clientsGenList; + my $host = shift; + my $num = shift; + my $i = 0; + for ($clientsListbox->get(0, 'end')) { + if ($_ eq "$client on $host" or $_ =~ /^$client\(\d+\)$/) { + $clientsListbox->delete($i); + } + $i++; + } + if ($num == 1) { + $clientsListbox->insert($i, "$client on $host"); + } else { + $clientsListbox->insert($i, "$client($num)"); + } + +} # end addClient -} sub removeClient { my $client = shift; - my $index = $clientsIndex{$client}; - $clientsListbox->delete($index) if $index >= 0; - &clientsGenList; -} - -sub clientsGenList { + my $host = shift; my $i = 0; for ($clientsListbox->get(0, 'end')) { - $clientsIndex{$_} = $i++; + if ($_ eq "$client on $host") { + $clientsListbox->delete($i); + last; + } + $i++; } -} + +} # end removeClient + sub selectClient { - $messagesText->tagConfigure($selectedClient, -background => $messagesBg) + $messagesText->tagConfigure($selectedClient, -background => $bgcolor) if $selectedClient; my $selindex = $clientsListbox->curselection; my $client = $clientsListbox->get($selindex); + $client =~ s/\(\d+\)$//; + $client =~ s/ on .*//; if ($selectedClient eq $client) { $selectedClient = undef; $clientsListbox->selectionClear($selindex); @@ -734,105 +794,162 @@ sub selectClient { $messagesText->tagConfigure($client, -background => 'gray70'); $selectedClient = $client; -} +} # end selectClient #---------------------------------------------------------------------------------- -# Fonctions associees a la gestion de l'affichage des messages +# Functions related to messages display management #---------------------------------------------------------------------------------- +sub marker { + return if $noMessageYet; + my @markers = $messagesText->markNames; + my $foundmarker; + my @markers2; + for (@markers) { + push(@markers2, $_) if (/^ivymon/); + } + $jumpButton->configure(-state => 'disabled') if @markers2 == 0; + for(@markers2) { + my $comp = $messagesText->compare('current linestart', '==', $_); + if ($comp) { + $foundmarker = $_; + $jumpButton->configure(-state => 'disabled') if @markers2 == 1; + last; + } + } + if ($foundmarker) { + &deletemarker($foundmarker); + } else { + my $index = $messagesText->index('current linestart'); + my $nb = $markers_cnt % 5; + &addmarker($index, 'marker'.$nb); + $markers_cnt++; + } + +} # end marker + +sub deletemarker { + my $marker = shift; + $messagesText->configure(-state => 'normal'); + $messagesText->delete($marker, "$marker lineend + 1 chars"); + $messagesText->markUnset($marker); + $messagesText->configure(-state => 'disabled'); + +} # end deletemarker + +sub addmarker { + my $index = shift; + my $marker = shift; + print "index=$index marker=$marker\n"; + $messagesText->configure(-state => 'normal'); + $messagesText->insert($index, "$marker\n"); + $messagesText->tagAdd($marker, $index, "$index + ".(length($marker)+1)." chars"); + $messagesText->markSet('ivymon'.$markers_cnt, $index); + $jumpButton->configure(-state => 'normal'); + $messagesText->configure(-state => 'disabled'); + +} # ens addmarker + +sub bufferizeMessages { + my ($sender, $message) = @_; + # if scrolling is locked, don't force display, just put message in buffer + push(@messagesbuffer, [$sender, $message]); + splice(@messagesbuffer, 0, 1) if @messagesbuffer > $history; + +} + +sub dumpBuffMessages { + my (@messages) = @_; + for my $msg (@messages) { + &updateMessages($msg->[0], $msg->[1]); + } + +} # end dumpBuffMessages + sub updateMessages { - my ($sender, @messages) = @_; - my $message = '"' . join ( '" "', @messages) . '"' ; + my ($sender, $message, $noUpdateFlag) = @_; + $noMessageYet = 0; chomp($message); - # cas ou l'on se trouve dans le mode pause, et ou l'on ne force pas l'affichage - # des messages => on bufferise - if ($stopFlag) { - push(@messagesbuffer, [$sender, $message]); - splice(@messagesbuffer, 0, 1) if @messagesbuffer > $history; - return; - } - # sinon, on formate le message et on l'affiche + $message = '"' . $message . '"' ; + # is scrolling is available, format and then display message my $text = "$sender $message\n"; $messagesText->configure(-state => 'normal'); - # on regarde si l'historique n'est pas depasse : on compare le nombre de lignes - # contenues dans le widget text avec la taille de l'historique. + # look at history : compare widget text lines number with history size my ($linesNb) = split(/\./, $messagesText->index('end')); + $linesNb--; if ($linesNb > $history) { - my $dl = $linesNb - $history + 1; + my $dl = $linesNb - $history; $messagesText->delete('1.0', "1.0 + $dl lines"); - $messagesText->update; + $messagesText->update unless $noUpdateFlag; } - - # on extrait les index de debut et de fin de l'application emettrice + + # to comment... my $index1 = $messagesText->index('end'); $index1 = "$index1 - 1 lines"; my $senderlen = length($sender); my $index2 = "$index1 + $senderlen chars"; $messagesText->insert('end', $text); $messagesText->tagAdd('sender', $index1, $index2); - # on extrait les index de debut et de fin du message my $msglen = length($message); $index1 = "$index2 + 1 chars"; $index2 = "$index1 + $msglen chars"; $messagesText->tagAdd($sender, $index1, $index2); $messagesText->configure(-state => 'disabled'); $messagesText->see('end'); - $messagesNumber++; $bytes += length($message); - $messagesText->update; -} + $messagesText->update unless $noUpdateFlag; -sub addBindingExpression { - $bindingsEntry->insert('insert', '(.*)'); - $bindingsEntry->xview('end'); -} +} # end updateMessages -sub clearMessages { - &stop; - $messagesText->configure(-state => 'normal'); - $messagesText->delete('0.0', 'end'); - $messagesText->configure(-state => 'disabled'); - &start; -} -sub searchLoopEvt { - $messagesText->configure(-background => 'gray90'); - $messagesText->after(100, sub {$messagesText->configure(-background => - $messagesBg); - }); - $messagesText->bell; -} +sub highlightString { + my ($i1, $i2) = @_; + $messagesText->tagConfigure('found', + -background => 'sienna', + -foreground => 'ivory'); + $messagesText->tagAdd('found', $i1, $i2); + +} # end highlightString + + +sub highlightStringOff { + $messagesText->tagDelete('found'); + $messagesText->tagConfigure('found', + -background => 'sienna', + -foreground => 'ivory'); +} # end highlightStringOff -sub searchNotFoundEvt { - $messagesText->configure(-background => 'gray30'); - $messagesText->after(100, sub {$messagesText->configure(-background => - $messagesBg); - }); - $messagesText->bell; -} #---------------------------------------------------------------------------------- -# Fonctions associees a la gestion des abonnements +#Functions related to Ivy bindings management #---------------------------------------------------------------------------------- +sub addBindingExpression { + $bindingsEntry->insert('insert', '(.*)'); + $bindingsEntry->xview('end'); + +} # end addBindingExpression + sub bindingNextExpression { my $cursorIndex = $bindingsEntry->index('insert'); $cursorIndex = -1 if $bindingsEntry->index('end') == $cursorIndex; if ($bindHistoryIndex == -1 or $bindHistoryIndex == @bindHistory - 1 or @bindHistory <= 1) { - &flashBindingEntry; + &warning1($bindingsEntry); } else { $bindHistoryIndex++; $bindingsEntry->delete(0, 'end'); $bindingsEntry->insert(0, $bindHistory[$bindHistoryIndex]); $bindingsEntry->icursor($cursorIndex) if $cursorIndex >= 0; } -} + +} # end bindingNextExpression + sub bindingPrevExpression { my $cursorIndex = $bindingsEntry->index('insert'); $cursorIndex = -1 if $bindingsEntry->index('end') == $cursorIndex; if ($bindHistoryIndex == 0 or @bindHistory <= 1) { - &flashBindingEntry; + &warning1($bindingsEntry); } else { if ($bindHistoryIndex == -1) { $bindHistoryIndex = @bindHistory - 2; @@ -843,31 +960,40 @@ sub bindingPrevExpression { $bindingsEntry->insert(0, $bindHistory[$bindHistoryIndex]); $bindingsEntry->icursor($cursorIndex) if $cursorIndex >= 0; } -} -sub flashBindingEntry { - $bindingsEntry->configure(-background => 'gray90'); - $bindingsEntry->after(100, sub {$bindingsEntry->configure(-background => - $messagesBg); - }); -} +} # end bindingPrevExpression + sub clearBinding { - # on vide la le champ de saisie $bindingsEntry->delete(0, 'end'); - # on enleve la selection dans la listbox $bindingsList->selectionClear(0, 'end'); - # on desactive le bouton Remove - $bindingsRemove->configure(-state => 'disabled'); -} + +} # end clearBinding + sub addBinding { - my $bindFlag = shift; - my $entry = $bindingsEntry->get; - return unless $entry; - # on met a jour l'historique - &bindHistoryGenList($entry) if $bindFlag; - # on insere le message dans la liste si celui-ci n'y est pas + my ($sender, $entry) = @_; + unless ($entry) { + $entry = $bindingsEntry->get; + return unless $entry; + } + &bindHistoryGenList($entry); + if ($effectivebindings{$entry}) { + &warning2($bindingsEntry); + return ; + } + $effectivebindingsList->insert('end', "bound to ".$entry); + $effectivebindings{$entry} = 1; + &addBindingInList($entry); + &warning3($bindingsEntry); + &addIvyBinding($entry); + &bindingsGenList; + +} # end addBinding + + +sub addBindingInList { + my $entry = shift; return if $bindings{$entry}; my @content = $bindingsList->get(0, 'end'); push (@content, $entry); @@ -878,38 +1004,32 @@ sub addBinding { $i++; } $bindingsList->insert($index, $entry); - # on ajoute l'abonnement ivy - &addIvyBinding($entry); - # on remet a jour la liste des bindings &bindingsGenList; -} + +} # end addBindingInList + sub selectBinding { + $bindingsEntry->focus; my $selindex = $bindingsList->curselection; my $selected = $bindingsList->get($selindex); - # on active le bouton Remove - $bindingsRemove->configure(-state => 'normal'); - # on met a jour le champ de saisie $bindingsEntry->delete(0, 'end'); $bindingsEntry->insert(0, $selected); -} + +} # end selectBinding + sub removeBinding { - my $selindex = $bindingsList->curselection; - return if $selindex eq ""; - my $selected = $bindingsList->get($selindex); - # on supprime l'abonnement ivy + my $selindex = $effectivebindingsList->curselection; + return unless defined($selindex); + my $selected = $effectivebindingsList->get($selindex); + $selected =~ s/^bound to //; &removeIvyBinding($selected); - # on enleve l'item de la liste - $bindingsList->delete($selindex); - # on remet a jour la liste des bindings - &bindingsGenList; - # on desactive le bouton Remove - $bindingsRemove->configure(-state => 'disabled'); - # on vide le champ de saisie - $bindingsEntry->delete(0, 'end'); + $effectivebindingsList->delete($selindex); + $effectivebindings{$selected} = undef; -} +} # end removeBinding + sub bindingsGenList { my $i = 0; @@ -924,8 +1044,9 @@ sub bindingsGenList { $bindings{$_} = 1 } $bindingsFlag = $found; - #print "bindingsFlag = $bindingsFlag\n"; -} + +} # end bindingsGenList + sub bindHistoryGenList { my $string = shift; @@ -933,30 +1054,33 @@ sub bindHistoryGenList { $bindHistory{$string} = 1; push(@bindHistory, $string); -} +} # end bindHistoryGenList #---------------------------------------------------------------------------------- -# Fonctions associees a la gestion des envois de messages +# Fucntions related to messages emission. #---------------------------------------------------------------------------------- + sub sendNextExpression { my $cursorIndex = $sendEntry->index('insert'); $cursorIndex = -1 if $sendEntry->index('end') == $cursorIndex; if ($sendHistoryIndex == -1 or $sendHistoryIndex == @sendHistory - 1 or @sendHistory <= 1) { - &flashSendEntry; + &warning1($sendEntry); } else { $sendHistoryIndex++; $sendEntry->delete(0, 'end'); $sendEntry->insert(0, $sendHistory[$sendHistoryIndex]); $sendEntry->icursor($cursorIndex) if $cursorIndex >= 0; } -} + +} # end sendNextExpression + sub sendPrevExpression { my $cursorIndex = $sendEntry->index('insert'); $cursorIndex = -1 if $sendEntry->index('end') == $cursorIndex; if ($sendHistoryIndex == 0 or @sendHistory <= 1) { - &flashSendEntry; + &warning1($sendEntry); } else { if ($sendHistoryIndex == -1) { $sendHistoryIndex = @sendHistory - 2; @@ -967,33 +1091,27 @@ sub sendPrevExpression { $sendEntry->insert(0, $sendHistory[$sendHistoryIndex]); $sendEntry->icursor($cursorIndex) if $cursorIndex >= 0; } -} -sub flashSendEntry { - $sendEntry->configure(-background => 'gray90'); - $sendEntry->after(100, sub {$sendEntry->configure(-background => - $messagesBg); - }); -} +} # end sendPrevExpression + sub clearMsgToSend { - # on vide la le champ de saisie $sendEntry->delete(0, 'end'); - # on enleve la selection dans la listbox $sendList->selectionClear(0, 'end'); - # on desactive le bouton Remove - $sendRemove->configure(-state => 'disabled'); -} + +} # end clearMsgToSend + + sub addMsgToSend { - my $sendFlag = shift; - my $entry = $sendEntry->get; - return unless $entry; - # on emet le messages + my ($sender, $entry, $sendFlag) = @_; + unless ($entry) { + $entry = $sendEntry->get; + return unless $entry; + } &sendMsg($entry) if $sendFlag; - # on met a jour l'historique + &warning3($sendEntry); &sendHistoryGenList($entry) if $sendFlag; - # on insere le message dans la liste si celui-ci n'y est pas return if $msgToSend{$entry}; my @content = $sendList->get(0, 'end'); push (@content, $entry); @@ -1004,42 +1122,28 @@ sub addMsgToSend { $i++; } $sendList->insert($index, $entry); - # on remet a jour la liste des bindings &sendGenList; -} + +} # end addMsgToSend + sub sendMsg { my $entry = shift; - # emission sur le bus $ivy->sendMsgs($entry); - # mise a jour de la fenetre des messages + $messagesNumber++; &updateMessages($appname, $entry); +} # end sendMsg + -} sub selectMsgToSend { + $sendEntry->focus; my $selindex = $sendList->curselection; my $selected = $sendList->get($selindex); - # on active le bouton Remove - $sendRemove->configure(-state => 'normal'); - # on met a jour le champ de saisie $sendEntry->delete(0, 'end'); $sendEntry->insert(0, $selected); -} - -sub removeMsgToSend { - my $selindex = $sendList->curselection; - return if $selindex eq ""; - my $selected = $sendList->get($selindex); - # on enleve l'item de la liste - $sendList->delete($selindex); - # on desactive le bouton Remove - $sendRemove->configure(-state => 'disabled'); - # on vide le champ de saisie - $sendEntry->delete(0, 'end'); - -} +} # end selectMsgToSend sub sendGenList { my $i = 0; @@ -1054,7 +1158,9 @@ sub sendGenList { $msgToSend{$_} = 1 } $msgToSendFlag = $found; -} + +} # end sendGenList + sub sendHistoryGenList { my $string = shift; @@ -1062,37 +1168,32 @@ sub sendHistoryGenList { $sendHistory{$string} = 1; push(@sendHistory, $string); -} +} # end sendHistoryGenList #---------------------------------------------------------------------------------- -# Fonctions associees au panneau de recherche +# Functions related to search panel #---------------------------------------------------------------------------------- -sub flashSearchEntry { - $searchEntry->configure(-background => 'gray90'); - $searchEntry->after(100, sub {$searchEntry->configure(-background => - $messagesBg); - }); -} - sub searchNextExpression { my $cursorIndex = $searchEntry->index('insert'); $cursorIndex = -1 if $searchEntry->index('end') == $cursorIndex; if ($searchHistoryIndex == -1 or $searchHistoryIndex == @searchHistory - 1 or @searchHistory <= 1) { - &flashSearchEntry; + &warning1($searchEntry); } else { $searchHistoryIndex++; $searchEntry->delete(0, 'end'); $searchEntry->insert(0, $searchHistory[$searchHistoryIndex]); $searchEntry->icursor($cursorIndex) if $cursorIndex >= 0; } -} + +} # end searchNextExpression + sub searchPrevExpression { my $cursorIndex = $searchEntry->index('insert'); $cursorIndex = -1 if $searchEntry->index('end') == $cursorIndex; if ($searchHistoryIndex == 0 or @searchHistory <= 1) { - &flashSearchEntry; + &warning1($searchEntry); } else { if ($searchHistoryIndex == -1) { $searchHistoryIndex = @searchHistory - 2; @@ -1103,21 +1204,27 @@ sub searchPrevExpression { $searchEntry->insert(0, $searchHistory[$searchHistoryIndex]); $searchEntry->icursor($cursorIndex) if $cursorIndex >= 0; } -} + +} # end searchPrevExpression + sub clearSearch { # on vide la le champ de saisie $searchEntry->delete(0, 'end'); &highlightStringOff; $searchIndex = undef; -} + +} # end clearSearch + sub searchHistoryGenList { my $string = shift; return if $searchHistory{$string}; $searchHistory{$string} = 1; push(@searchHistory, $string); -} + +} # end searchHistoryGenList + sub searchNext { my $string = $searchEntry->get; @@ -1132,15 +1239,17 @@ sub searchNext { if ($index) { &highlightString($index, "$index + $strlen chars"); $messagesText->see($index); - &searchLoopEvt if defined($searchIndex) and + &warning1($messagesText) if defined($searchIndex) and $messagesText->compare($index, "<=" ,$searchIndex); } else { - &searchNotFoundEvt; + &warning2($messagesText); } $searchIndex = $index; $searchHistoryIndex = -1; &searchHistoryGenList($string); -} + +} # end searchNext + sub searchPrev { my $string = $searchEntry->get; @@ -1155,15 +1264,16 @@ sub searchPrev { if ($index) { &highlightString($index, "$index + $strlen chars"); $messagesText->see($index); - &searchLoopEvt if defined($searchIndex) and + &warning1($messagesText) if defined($searchIndex) and $messagesText->compare($index, ">=" ,$searchIndex); } else { - &searchNotFoundEvt; + &warning2($messagesText); } $searchIndex = $index; $searchHistoryIndex = -1; &searchHistoryGenList($string); -} + +} # end searchPrev sub searchAll { @@ -1186,92 +1296,152 @@ sub searchAll { $index = "$index + 1 chars"; $found++; } - &searchNotFoundEvt unless ($found); + &warning2($messagesText) unless ($found); $searchHistoryIndex = -1; &searchHistoryGenList($string); -} - -sub highlightString { - my ($i1, $i2) = @_; - $messagesText->tagConfigure('found', - -background => 'sienna', - -foreground => 'ivory'); - $messagesText->tagAdd('found', $i1, $i2); -} - -sub highlightStringOff { - $messagesText->tagDelete('found'); - $messagesText->tagConfigure('found', - -background => 'sienna', - -foreground => 'ivory'); -} +} # end searchAll #---------------------------------------------------------------------------------- -# Fonctions associees au panneau de commande +# Functions related to control panel #---------------------------------------------------------------------------------- sub bye { #$ivy->stop; exit; -} +} + +sub jump { + my @markers = $messagesText->markNames; + my @markers2; + for (@markers) { + push(@markers2, $_) if (/^ivymon/); + } + if (@markers2 < $jump_cnt + 1) { + $jump_cnt = 0; + } + $messagesText->yview($markers2[$jump_cnt]); + $jump_cnt++; + +} # end jump + sub stop { my $flag = shift; - # on reinitialise le buffer de messages @messagesbuffer = (); $stopFlag = 1; $startButton->configure(-state => 'normal'); $stopButton->configure(-state => 'disabled'); -} +} # end stop + sub start { my $flag = shift; - # si le curseur n'est pas au debut d'une ligne (car une edition a ete faite, - # sans RC final), on passe a la ligne suivante pour assurer une meilleure - # lisibilite if ($messagesText->index('insert') !~ /\.0$/) { $messagesText->insert('insert', "\n"); } - # puis, on affiche les messages bufferises - for (@messagesbuffer) { - &updateMessages(@$_, 1); + $startButton->configure(-state => 'disabled'); + for my $msg (@messagesbuffer) { + &updateMessages($msg->[0], $msg->[1]); } - # puis on re-active l'affichage des messages recus $stopFlag = 0; $stopButton->configure(-state => 'normal'); - $startButton->configure(-state => 'disabled'); -} - -sub formattime { - my $duration = shift; - my $hour = sprintf("%02d", int($duration/3600)); - my $sec = $duration % 3600; - my $min = sprintf("%02d", int($sec/60)); - $sec = sprintf("%02d", $sec % 60); - if ($hour eq '00') { - return "$min:$sec"; - } else { - return "$hour:$min:$sec"; - } -} +} # end start -sub setEditMode { - #print "setEditMode\n"; - $messagesText->configure(-state => 'normal'); - $messagesText->focus; -} +sub loadfile { + 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; + } + } + my $file = $mw->getOpenFile(-filetypes => [['Ivy Files', '.ivy'], + ['All Files', '*']], + ); + return unless $file; + # open file + open(IN, "$file") or $mw->Tk::Error("$!\n"); + my %client; + my $step = 0; + my $line = 0; + while(<IN>) { + chomp; + if (/^applications=/) { + next; + } elsif (/^messages_number=(.*)/) { + $progressbar->configure(-to => $1); + $step = int($1/10); + next; + } elsif (/^\s*$/) { + next; + $line++; + } elsif (/^(marker\d+)$/) { + my $index = $messagesText->index('current linestart'); + &addmarker($index, $1); + } else { + my ($sender, $message) = split(/ /, $_, 2); + unless ($client{$sender}) { + $client{$sender} = 1; + $clientsListbox->insert('end', $sender); + } + $messagesNumber++; + $line++; + &updateMessages($sender, $message, 1); + #print "sender=$sender message=$message\n"; + $progressbar->value($line); + $progressbar->update if ($line % $step == 0); + } + } + close(IN); + $progressbar->value(0); + +} # end loadfile + + +sub savefile { + my ($d, $m, $y, $h, $M) = (localtime(time))[3,4,5,2,1]; + $y =~ s/^\d// if $y >= 100; + $m++; + my $default = 'ivylog'.$d.$m.$y."_".$h.":".$M.".ivy"; + my $file = $mw->getSaveFile(-initialfile => $default, + -filetypes => [['Ivy Files', '.ivy'], + ['All Files', '*']], + ); + return unless $file; + # open file + open(OUT, ">$file") or $mw->Tk::Error("$!\n"); + + # save connected applications name + my @clients = $clientsListbox->get(0, 'end'); + print OUT "applications=", join(',', @clients),"\n" or $mw->Tk::Error("$!\n"); -sub unsetEditMode { - #print "unsetEditMode\n"; - $messagesText->configure(-state => 'disabled'); - $bindingsEntry->focus; -} + + my $nblines = $messagesText->index("end"); + $nblines =~ s/\.\d+$//; + print OUT "messages_number=$nblines\n" or $mw->Tk::Error("$!\n"); + # save messages 100 by 100, in order to reduce memory usage + my $index = "1.0"; + while(1) { + my $messages = $messagesText->get($index, "$index + 100 lines"); + $index = "$index + 100 lines"; + last unless $messages; + print OUT $messages or $mw->Tk::Error("$!\n"); + } + close(OUT); + +} # end savefile + #---------------------------------------------------------------------------------- -# Fonctionsgenerales +# General functions #---------------------------------------------------------------------------------- sub wheelmousebindings { my $w = shift; @@ -1284,7 +1454,107 @@ sub wheelmousebindings { $w->bind('<Control-ButtonPress-5>', sub {$w->yview('scroll', 1, 'page')}); $w->bind('<Shift-ButtonPress-5>', sub {$w->yview('scroll', 1, 'unit')}); $w->bind('<ButtonPress-5>', sub {$w->yview('scroll', $count, 'unit')}); -} + +} # end wheelmousebindings + +sub findExprInList { + my ($entry, $list) = @_; + my $key = $entry->XEvent->K; + my $expr = $entry->get; + #print "expr=$expr\n"; + my @elems = $list->get(0, "end"); + if ($key eq 'Tab') { + my $index = 0; + my @found; + my $elem; + my $qmexpr = quotemeta($expr); + for $elem (sort @elems) { + #print "qmexpr=$qmexpr qmelem=",quotemeta($elem),"\n"; + if ($expr and $elem =~ /^$qmexpr/) { + push (@found, $index); + } + $index++; + } + if (@found == 0) { + &warning2($entry); + my @cursel = $list->curselection; + $list->selectionClear(@cursel) if @cursel > 0; + } elsif (@found == 1) { + my @cursel = $list->curselection; + $list->selectionClear(@cursel) if @cursel > 0; + $list->selectionSet($found[0]); + $list->see($found[0]); + $entry->delete(0, 'end'); + $entry->insert(0, scalar $list->get($found[0])); + } else { + &warning1($entry); + my $i = 0; + my $str = $list->get($found[0]); + my $commonstr; + for ($i=0; $i < length($str); $i++) { + my $char = substr($str, length($expr)+$i, 1); + my $j = 0; + while ($j < @found) { + last if substr(scalar $list->get($found[$j]), length($expr)+$i, 1) + ne $char; + $j++; + } + if ($j == @found) { + $commonstr = $commonstr.$char; + } else { + last; + } + } + $entry->insert('end', $commonstr); + } + + } elsif (not $expr) { + my @cursel = $list->curselection; + $list->selectionClear(@cursel) if @cursel > 0; + } else { + my $index = 0; + for my $elem (sort @elems) { + my $qwexpr = quotemeta($expr); + if ($expr and $elem =~ /^$qwexpr/) { + my @cursel = $list->curselection; + $list->selectionClear(@cursel) if @cursel > 0; + $list->selectionSet($index); + $list->see($index); + #print "$elem\n"; + last; + } + $index++; + } + } + +} # end findExprInList + + +sub warning1 { + my $widget = shift; + $widget->configure(-background => 'gray90'); + $widget->after(100, sub {$widget->configure(-background => $bgcolor); }); + $widget->bell; + +} # end warning1 + + +sub warning2 { + my $widget = shift; + $widget->configure(-background => 'gray30'); + $widget->after(100, sub {$widget->configure(-background => $bgcolor); }); + $widget->bell; + +} # end warning2 + +sub warning3 { + my $widget = shift; + $widget->configure(-foreground => 'red'); + $widget->after(100, sub {$widget->configure(-foreground => 'black')}); + +} # end warning3 + + __END__ @@ -1298,26 +1568,23 @@ ivymon - a graphical application for monitoring Ivy B<ivymon> [B<-b> ivybus] [B<-help>] [B<-history> size] [B<-bind> messageB<1>] ... [B<-bind> messageB<N>] [B<-send> regexpB<1>] ... [B<-send> regexpB<N>] ... + [standard X11 options...] =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. -The main area is the window labeled B<Messages> where are printed messages that forward on bus. Each message is preceded by sender application's name. +The main area is the window labeled B<Messages> 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<Applications> area lists the connected applications names. When you select an item in the listbox, the messages sent by this application are highlighted. -The B<Bindings> area lets you manage regular expressions used to subscribe to ivy messages. It provides an entry field to enter new regexp, a listbox which contains an alphabetical list of effective bindings, and buttons to manage this list. To validate an entry, press the I<Add> button or the I<Return> key. To remove subscription, select the corresponding item in the listbox, and use the I<Remove> button. Pressing the I<Escape> key inserts I<(.*)> string in entry field. - -The B<Messages to send> area lets you manage a list of predefined messages ready to be sent. It provides an entry field to enter new message, a listbox which contains an alphabetical list of messages, and buttons to manage this list. When you validate an entry (by pressing the I<Send> button or the I<Return> key), the new message is added to the listbox and sent on ivy bus. To send predefined messages, simply I<double-click> on corresponding item in the listbox. +The B<Bindings> 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<Return> key to validate. To remove subscription, double-click on the corresponding item in the second listbox. Pressing the I<Escape> key inserts I<(.*)> string in entry field. Default bindings match Rejeu messages V2.20. This field provides completion and history functionalities. -The B<Search> area provides an interface for searching (non-regexp) pattern in messages window. It provides an entry field to enter new pattern, and control buttons. To highlight all matches, press the I<All> button. To make incremental search, press the I<Next> button, the I<Return>, I<Next> or I<Ctrl-s> keys to proceed forward, and press the I<Previous> button the I<Shift-Return>, I<Prior> or I<Ctrl-r> keys to proceed backward. +The B<Messages to send> 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<Return> key), the new message is added to the listbox and sent on ivy bus. To send predefined messages, simply I<double-click> on corresponding item in the listbox. Default messages match Rejeu messages V2.20. This field provides completion and history functionalities. -The last area contains statistics data and general control buttons. The I<Clear> button clears out the messages window. The I<Pause> one stops the printing of messages (but messages are stored). When you are in this mode, the messages text area becomes editable. You are able to insert free coloured text (particular tags for example), anywhere in the messages window. Then, you can restart monitoring by pressing the I<Start> button. At last, the I<Exit> button closes the application without asking confirmation. +The B<Search> area provides an interface for searching (non-regexp) pattern in messages window. It provides an input field to enter new pattern, and control buttons. To highlight all matches, press the I<All> button. To make incremental search, press the I<Next> button or the I<Return> key to proceed forward, and press the I<Previous> button or the I<Shift-Return> key to proceed backward. This field provides an history functionality. -=head2 ENTRIES HISTORY - -Each entry field use an history mechanism. To scroll history list, use the I<UpArrow> (or I<Ctrl-p>) and I<DownArrow> (or I<Ctrl-n>) keys. +The B<Control> panel contains some general control buttons. Set ou unset balloon help functionality with the I<Balloon help> checkbutton. Stop the messages scrolling with the I<Scroll lock> button. You must do it if you want to insert a marker in the messages window. Restart scrolling with the I<Scroll> button. To access markers sequentially, press the I<Jump> button. At last, the I<Exit> button closes the application without asking confirmation. =head1 OPTIONS @@ -1356,6 +1623,3 @@ ivymon -b 10.192.36.255:3456 -history 20000 -bind 'PLN:(.*) SectorActivation sec Daniel Etienne <etienne@cena.fr> -=head1 ENHANCEMENT - -Ivymon should provides 'Save in file' and 'Load file' functions. |