summaryrefslogtreecommitdiff
path: root/src/ivymon
diff options
context:
space:
mode:
Diffstat (limited to 'src/ivymon')
-rwxr-xr-xsrc/ivymon755
1 files changed, 755 insertions, 0 deletions
diff --git a/src/ivymon b/src/ivymon
new file mode 100755
index 0000000..e6f3ceb
--- /dev/null
+++ b/src/ivymon
@@ -0,0 +1,755 @@
+#!/usr/bin/perl
+
+use Tk;
+use Tk::Font;
+use Ivy;
+use strict 'vars';
+use Getopt::Long;
+use vars qw/$opt_help $opt_b $opt_history/;
+
+# geometrie
+my $appliFrameWidth = 150;
+my $appliFrameHeight = 200;
+my $bindingsFrameWidth = 400;
+
+
+my $ivy_port = "127.255.255.255:2010";
+my $history = 10000;
+
+# divers
+my %connectedClients;
+my %clientsIndex;
+my %bindings;
+my %bindingsIndex;
+my $selectedClient;
+
+my $duration = 0;
+my $time = 0;
+my $fmtduration = '00:00';
+my $bytes = 0 ;
+my $messagesNumber = 0;
+my $bindingsFlag = 0;
+my $stopFlag = 0;
+
+my $mw = MainWindow->new;
+$mw->geometry('850x768');
+$mw->minsize(850, 768);
+
+
+if (not GetOptions('-help', '-history=s', '-b=s') or $opt_help) {
+ print "Usage: ivymon [options]\n";
+ print "Options : \n";
+ print " -s <[addr]:port> bus port(default $ivy_port)\n";
+ print " -history <number> messages history length (default $history)\n";
+ print " -help help message \n";
+ exit ;
+}
+
+$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)");
+
+#----------------------------------------------------------------------------------
+# La Frame de gauche prend toute la hauteur de l'appli et est composee d'une frame
+# ou sont affiches les Messages et une frame ou sont saisis les Abonnements et les
+# instructions de Recherche
+#----------------------------------------------------------------------------------
+my $frame1left =
+ $mw->Frame()->pack(-fill => 'both',
+ -expand => 1,
+ -side => 'left');
+
+my $frame11messages =
+ $frame1left->Frame()->pack(-fill => 'both',
+ -expand => 1,
+ -padx => 5, -pady => 5);
+my $frame12 =
+ $frame1left->Frame(-height => $appliFrameHeight)->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 0);
+my $frame121bindings =
+ $frame12->Frame(-height => $appliFrameHeight,
+ -relief => 'groove',
+ -borderwidth => 3,
+ -width => $bindingsFrameWidth)->pack(-fill => 'both',
+ -ipady => 20, -ipadx => 20,
+ -padx => 5, -pady => 5,
+ -side => 'left',
+ -expand => 0);
+my $frame122search =
+ $frame12->Frame(-relief => 'groove',
+ -borderwidth => 3,
+ -height => $appliFrameHeight)->pack(-fill => 'both',
+ -padx => 5, -pady => 5,
+ -side => 'left',
+ -expand => 0);
+
+
+#----------------------------------------------------------------------------------
+# La Frame de droite prend toute la hauteur de l'appli et est composee d'une frame
+# ou sont affiches les Applications connectees et une frame contenant les boutons
+# de commande
+#----------------------------------------------------------------------------------
+my $frame2right =
+ $mw->Frame(-width => $appliFrameWidth)->pack(-fill => 'both',
+ -expand => 0,
+ -side => 'right');
+my $frame21appli =
+ $frame2right->Frame(-width => $appliFrameWidth)->pack(-fill => 'both',
+ -expand => 1,
+ -padx => 5, -pady => 5);
+
+my $frame21control =
+ $frame2right->Frame(-width => $appliFrameWidth,
+ -height => $appliFrameHeight)->pack(-fill => 'both',
+ -expand => 0,
+ -padx => 5, -pady => 5);
+#----------------------------------------------------------------------------------
+# Description de la zone Messages
+#----------------------------------------------------------------------------------
+my $messagesLabel =
+ $frame11messages->Label(-text => "Messages :")->pack(-side => 'top',
+ -anchor => 'w');
+
+my $textFont = $mw->fontCreate('H_Normal',
+ -family => 'Helvetica',
+ -size => 10);
+my $textItalicFont = $mw->fontCreate('H_Italic',
+ -family => 'Helvetica',
+ -size => 10,
+ -slant => 'italic');
+my $textBoldFont = $mw->fontCreate('H_Bold',
+ -family => 'Helvetica',
+ -size => 10,
+ -weight => 'bold');
+
+my $messagesText =
+ $frame11messages->Scrolled(Text,
+ -scrollbars => 'e',
+ -font => 'H_Bold',
+ -spacing1 => 2,
+ -spacing2 => 0,
+ -spacing3 => 2,
+ -state => 'disabled')->pack(-fill => 'both',
+ -expand => 1,
+ -side => 'bottom');
+my $messagesBg = $messagesText->cget(-background);
+$messagesText->tagConfigure('sender',
+ -background => 'gray50',
+ -foreground => 'gray90');
+$messagesText->tagConfigure('info', -foreground => 'sienna');
+$messagesText->bind('<KeyPress>', sub {
+ my $index = $messagesText->index('insert');
+ #print "index = $index\n";
+ $messagesText->tagAdd('info', "$index - 1 chars", $index);
+});
+
+#----------------------------------------------------------------------------------
+# Description de la zone Clients
+#----------------------------------------------------------------------------------
+my $clientsLabel =
+ $frame21appli->Label(-text => "Applications :")->pack(-side => 'top',
+ -anchor => 'w');
+my $clientsListbox =
+ $frame21appli->Listbox()->pack(-fill => 'both',
+ -expand => 1,
+ -side => 'bottom');
+$clientsListbox->bind('<1>', [\&selectClient]);
+
+#----------------------------------------------------------------------------------
+# Description de la zone Abonnements
+#----------------------------------------------------------------------------------
+my $bindingsLabel =
+ $frame121bindings->Label(-text => 'Bindings : ')->pack(-side => 'top',
+ -anchor => 'w');
+
+my $bindingsEntry =
+ $frame121bindings->Entry(-width => 50)->pack(-fill => 'x',
+ -ipady => 3,
+ -expand => 0,
+ -padx => 5, -pady => 5);
+$bindingsEntry->bind('<Escape>' => [\&addBindingExpression]);
+$bindingsEntry->bind('<Return>' => [\&addBinding, 1]);
+$bindingsEntry->focus;
+
+my $frame1211 =
+ $frame121bindings->Frame()->pack(-fill => 'x',
+ -side => 'bottom',
+ -expand => 1);
+my $bindingsList =
+ $frame1211->Listbox(-width => 40)->pack(-fill => 'y',
+ -side => 'left',
+ -expand => 1);
+$bindingsList->bind('<1>', [\&selectBinding]);
+
+my $frame12111 =
+ $frame1211->Frame()->pack(-fill => 'y',
+ -side => 'right',
+ -expand => 1);
+my $bindingsClear =
+ $frame12111->Button(-text => 'Clear',
+ -command => [\&clearBinding])->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 1);
+my $bindingsAdd =
+ $frame12111->Button(-text => 'Add',
+ -command => [\&addBinding])->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 1);
+my $bindingsChange =
+ $frame12111->Button(-text => 'Change',
+ -state => 'disabled',
+ -command => [\&changeBinding])->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 1);
+my $bindingsRemove =
+ $frame12111->Button(-text => 'Remove',
+ -state => 'disabled',
+ -command => [\&removeBinding])->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 1);
+
+#----------------------------------------------------------------------------------
+# Description de la zone Recherche
+#----------------------------------------------------------------------------------
+my $searchLabel =
+ $frame122search->Label(-text => 'Search : ')->pack(-side => 'top',
+ -anchor => 'w');
+my $searchEntry =
+ $frame122search->Entry(-width => 20)->pack(-fill => 'x',
+ -ipady => 3,
+ -expand => 0,
+ -padx => 5, -pady => 5);
+$searchEntry->bind('<Return>' => [\&searchNext, 1]);
+
+my $frame1222 =
+ $frame122search->Frame()->pack(-fill => 'x',
+ -padx => 10, -pady => 10,
+ -side => 'bottom',
+ -expand => 0);
+my $frame1221 =
+ $frame122search->Frame()->pack(-fill => 'x',
+ -padx => 10,
+ -side => 'bottom',
+ -expand => 0);
+my $searchPrev =
+ $frame1221->Button(-text => 'Previous',
+ -height => 3,
+ -width => 4,
+ -command => [\&searchPrev])->pack(-fill => 'x',
+ -side => 'left',
+ -expand => 1);
+my $searchNext =
+ $frame1221->Button(-text => 'Next',
+ -width => 4,
+ -height => 3,
+ -command => [\&searchNext])->pack(-fill => 'x',
+ -side => 'right',
+ -expand => 1);
+my $searchClear =
+ $frame1222->Button(-text => 'Clear',
+ -width => 4,
+ -height => 3,
+ -command => [\&clearSearch])->pack(-fill => 'x',
+ -side => 'left',
+ -expand => 1);
+my $searchAll =
+ $frame1222->Button(-text => 'All',
+ -width => 4,
+ -height => 3,
+ -command => [\&searchAll])->pack(-fill => 'x',
+ -side => 'right',
+ -expand => 1);
+
+
+#----------------------------------------------------------------------------------
+# Description de a la zone Commande
+#----------------------------------------------------------------------------------
+# les champs de statistiques
+my $frame211control =
+ $frame21control->Frame()->pack(-side => 'top',
+ -fill => 'both',
+ -expand => 1);
+my $frame2111control =
+ $frame211control->Frame()->pack(-side => 'left',
+ -padx => 5,
+ -fill => 'both',
+ -expand => 1);
+my $frame2112control =
+ $frame211control->Frame()->pack(-side => 'right',
+ -fill => 'both',
+ -expand => 1);
+
+
+$frame2111control->Label(-text => "Duration :")->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w',
+ );
+$frame2111control->Label(-text => "Messages :")->pack(-side => 'top',
+ -pady => 2,
+ -anchor => 'w');
+$frame2111control->Label(-text => "Bytes :")->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+$frame2112control->Label(-textvariable => \$fmtduration)->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+$frame2112control->Label(-textvariable => \$messagesNumber)->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+$frame2112control->Label(-textvariable => \$bytes)->pack(-side => 'top',
+ -pady => 3,
+ -anchor => 'w');
+
+# les boutons
+my $frame212control =
+ $frame21control->Frame()->pack(-side => 'bottom',
+ -pady => 8,
+ -fill => 'both',
+ -expand => 1);
+my $frame2121control =
+ $frame212control->Frame()->pack(-side => 'left',
+ -fill => 'both',
+ -ipady => 10,
+ -padx => 5, -pady => 5,
+ -expand => 1);
+my $frame2122control =
+ $frame212control->Frame()->pack(-side => 'right',
+ -padx => 5, -pady => 5,
+ -ipady => 10,
+ -fill => 'both',
+ -expand => 1);
+my $startButton =
+ $frame2121control->Button(-height => 3,
+ -width => 6,
+ -command => [\&start, 1],
+ -state => 'disabled',
+ -text => 'Start')->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 0);
+my $clearButton =
+ $frame2121control->Button(-height => 3,
+ -width => 6,
+ -command => [\&clearMessages],
+ -text => "Clear")->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 0);
+
+my $stopButton =
+ $frame2122control->Button(-height => 3,
+ -width => 6,
+ -command => [\&stop, 1],
+ -text => 'Stop')->pack(-fill => 'both',
+ -side => 'top',
+ -expand => 0);
+my $exitButton =
+ $frame2122control->Button(-height => 3,
+ -width => 6,
+ -command => [\&bye],
+ -text => 'Exit')->pack(-fill => 'both',
+ -side => 'bottom',
+ -expand => 0);
+
+
+
+
+#----------------------------------------------------------------------------------
+# Demarrage d'Ivy et abonnements
+#----------------------------------------------------------------------------------
+
+Ivy->init(-loopMode => 'TK',
+ -appName => "IVYMON",
+ -ivyBus => $ivy_port,
+ );
+my $ivy = Ivy->new(-statusFunc => \&checkClientsStatus);
+$ivy->start;
+
+$mw->repeat(1000, \&updateDuration);
+
+MainLoop;
+
+#==================================================================================
+# FONCTIONS IVY
+#==================================================================================
+sub addIvyBinding {
+ my $binding = shift;
+ #print "in addIvyBinding $binding\n";
+ return if $stopFlag;
+ $ivy->bindRegexp($binding, [\&updateMessages]);
+}
+
+sub removeIvyBinding {
+ my $binding = shift;
+ $ivy->bindRegexp($binding);
+}
+
+sub stopIvyBindings {
+ for (keys(%bindings)) {
+ $ivy->bindRegexp($_);
+ }
+}
+
+sub startIvyBindings {
+ for (keys(%bindings)) {
+ $ivy->bindRegexp($_, [\&updateMessages]);
+ }
+}
+
+#==================================================================================
+# CALLBACK TK
+#==================================================================================
+
+#----------------------------------------------------------------------------------
+# Fonctions associees a la liste des applications connectees
+#----------------------------------------------------------------------------------
+sub checkClientsStatus {
+ my ($present, $absent) = @_;
+ #print "in checkClientsStatus\n";
+ my %present;
+ for (@$present) {
+ $present{$_} = 1;
+ unless ($connectedClients{$_}) {
+ $connectedClients{$_} = 1;
+ &addClient($_);
+ }
+ }
+ for (keys(%connectedClients)) {
+ unless ($present{$_}) {
+ delete $connectedClients{$_};
+ &removeClient($_);
+ }
+ }
+}
+
+sub addClient {
+ my $client = shift;
+ $clientsListbox->insert('end', $client);
+ &clientsGenList;
+
+}
+
+sub removeClient {
+ my $client = shift;
+ my $index = $clientsIndex{$client};
+ $clientsListbox->delete($index) if $index >= 0;
+ &clientsGenList;
+}
+
+sub clientsGenList {
+ my $i = 0;
+ for ($clientsListbox->get(0, 'end')) {
+ $clientsIndex{$_} = $i++;
+ }
+}
+
+sub selectClient {
+ $messagesText->tagConfigure($selectedClient, -background => $messagesBg)
+ if $selectedClient;
+ my $selindex = $clientsListbox->curselection;
+ my $client = $clientsListbox->get($selindex);
+ if ($selectedClient eq $client) {
+ $selectedClient = undef;
+ $clientsListbox->selectionClear($selindex);
+ return;
+ }
+ $messagesText->tagConfigure($client, -background => 'gray70');
+ $selectedClient = $client;
+}
+#----------------------------------------------------------------------------------
+# Fonctions associees a la gestion de l'affichage des messages
+#----------------------------------------------------------------------------------
+sub updateMessages {
+ my ($sender, $message) = @_;
+ return unless $message;
+ return if ($message =~ /READY$/);
+ chomp($message);
+ my $text = "$sender $message\n";
+ $messagesText->configure(-state => 'normal');
+ # on teste la taille de la fenetre de messages
+ my ($linesNb) = split(/\./, $messagesText->index('end'));
+ #print "linesNb=$linesNb history=$history\n";
+ if ($linesNb > $history) {
+ my $dl = $linesNb - $history;
+ $messagesText->delete('1.0', "1.0 + $dl lines");
+ }
+ # on extrait les index de debut et de fin de l'application emettrice
+ 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);
+ $mw->update;
+}
+
+sub printInfo {
+ my $info = shift;
+ $messagesText->insert('end', "<< $info >>\n");
+ my $index = $messagesText->index('end');
+ my $len = length($info) + 7;
+ my $index1 = "$index - 1 lines";
+ my $index0 = "$index1 - $len chars";
+ $messagesText->tagAdd('info', $index0, $index1);
+ $messagesText->see('end');
+ $messagesText->markSet('insert', 'end');
+}
+
+sub addBindingExpression {
+ $bindingsEntry->insert('insert', '(.*)');
+ $bindingsEntry->xview('end');
+}
+
+sub clearMessages {
+ &stop;
+ $messagesText->configure(-state => 'normal');
+ $messagesText->delete('0.0', 'end');
+ $messagesText->configure(-state => 'disabled');
+ &start;
+}
+
+#----------------------------------------------------------------------------------
+# Fonctions associees a la gestion des abonnements
+#----------------------------------------------------------------------------------
+
+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 les boutons Change et Remove
+ $bindingsChange->configure(-state => 'disabled');
+ $bindingsRemove->configure(-state => 'disabled');
+}
+
+sub addBinding {
+ my $entry = $bindingsEntry->get;
+ return unless $entry;
+ return if $bindings{$entry};
+ $bindingsList->insert('end', $entry);
+ # on ajoute l'abonnement ivy
+ &addIvyBinding($entry);
+ # on remet a jour la liste des bindings
+ &bindingsGenList;
+}
+
+sub selectBinding {
+ my $selindex = $bindingsList->curselection;
+ my $selected = $bindingsList->get($selindex);
+ # on active les boutons Change et Remove
+ $bindingsChange->configure(-state => 'normal');
+ $bindingsRemove->configure(-state => 'normal');
+ # on met a jour le champ de saisie
+ $bindingsEntry->delete(0, 'end');
+ $bindingsEntry->insert(0, $selected);
+}
+
+sub removeBinding {
+ my $selindex = $bindingsList->curselection;
+ return if $selindex eq "";
+ my $selected = $bindingsList->get($selindex);
+ # on supprime l'abonnement ivy
+ &removeIvyBinding($selected);
+ # on enleve l'item de la liste
+ $bindingsList->delete($selindex);
+ # on remet a jour la liste des bindings
+ &bindingsGenList;
+ # on desactive les boutons Change et Remove
+ $bindingsChange->configure(-state => 'disabled');
+ $bindingsRemove->configure(-state => 'disabled');
+ # on vide le champ de saisie
+ $bindingsEntry->delete(0, 'end');
+
+}
+
+sub changeBinding {
+ my $selindex = $bindingsList->curselection;
+ return if $selindex eq "";
+ my $selected = $bindingsList->get($selindex);
+ my $newbinding = $bindingsEntry->get;
+ return unless $newbinding;
+ return if $bindings{$newbinding};
+ # on supprime l'abonnement ivy
+ &removeIvyBinding($selected);
+ # on ajoute le nouvel abonnement ivy
+ &addIvyBinding($selected);
+ # on enleve la selection de la liste
+ $bindingsList->selectionClear(0, 'end');
+ # on met a jour l'item de la liste
+ $bindingsList->delete($selindex);
+ $bindingsList->insert($selindex, $newbinding);
+ # on remet a jour la liste des bindings
+ &bindingsGenList;
+ # on desactive les boutons Change et Remove
+ $bindingsChange->configure(-state => 'disabled');
+ $bindingsRemove->configure(-state => 'disabled');
+
+}
+
+sub bindingsGenList {
+ my $i = 0;
+ my $found = 0;
+ for (keys %bindingsIndex) {
+ delete $bindingsIndex{$_};
+ delete $bindings{$_};
+ }
+ for ($bindingsList->get(0, 'end')) {
+ $found = 1;
+ $bindingsIndex{$_} = $i++;
+ $bindings{$_} = 1
+ }
+ $bindingsFlag = $found;
+ #print "bindingsFlag = $bindingsFlag\n";
+}
+
+#----------------------------------------------------------------------------------
+# Fonctions associees au panneau de recherche
+#----------------------------------------------------------------------------------
+my $searchIndex;
+sub clearSearch {
+ # on vide la le champ de saisie
+ $searchEntry->delete(0, 'end');
+ &highlightStringOff;
+}
+
+sub searchNext {
+ my $string = $searchEntry->get;
+ my $strlen = length($string);
+ return unless $string;
+ my $index0 = ($searchIndex) ? "$searchIndex + 1 chars": '0.0';
+ my $index = $messagesText->search(-forwards, $string, $index0);
+ $searchIndex = $index;
+ return unless $index;
+ &highlightStringOff;
+ &highlightString($index, "$index + $strlen chars");
+ $messagesText->see($index);
+
+}
+
+sub searchPrev {
+ my $string = $searchEntry->get;
+ my $strlen = length($string);
+ return unless $string;
+ #my $index0 = ($searchIndex) ? "$searchIndex - 1 chars": '0.0';
+ my $index0 = ($searchIndex) ? $searchIndex : '0.0';
+ my $index = $messagesText->search(-backwards, $string, $index0);
+ $searchIndex = $index;
+ return unless $index;
+ &highlightStringOff;
+ &highlightString($index, "$index + $strlen chars");
+ $messagesText->see($index);
+
+}
+
+sub searchAll {
+ my $string = $searchEntry->get;
+ my $strlen = length($string);
+ return unless $string;
+ $messagesText->tagConfigure('found',
+ -background => 'sienna',
+ -foreground => 'ivory');
+ my $index = '0.0';
+ while ($index) {
+ $index = $messagesText->search(-forwards, $string, $index, 'end');
+ return unless $index;
+ $messagesText->tagAdd('found', $index, "$index + $strlen chars");
+ $index = "$index + 1 chars";
+ }
+
+}
+
+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');
+}
+
+#----------------------------------------------------------------------------------
+# Fonctions associees au panneau de commande
+#----------------------------------------------------------------------------------
+sub bye {
+ #$ivy->stop;
+ exit;
+}
+
+sub stop {
+ my $flag = shift;
+ $stopFlag = 1;
+ $startButton->configure(-state => 'normal');
+ $stopButton->configure(-state => 'disabled');
+ &stopIvyBindings;
+ if ($flag) {
+ &setEditMode;
+ my $fmttime = &formattime($time);
+ &printInfo("Stopped at $fmttime");
+ }
+
+}
+
+sub start {
+ my $flag = shift;
+ $stopFlag = 0;
+ $stopButton->configure(-state => 'normal');
+ $startButton->configure(-state => 'disabled');
+ &startIvyBindings;
+ if ($flag) {
+ my $fmttime = &formattime($time);
+ &printInfo("Restarted at $fmttime");
+ &unsetEditMode;
+ }
+}
+
+sub updateDuration {
+ return unless $bindingsFlag;
+ $time++;
+ return if $stopFlag;
+ $duration++;
+ $fmtduration = &formattime($duration);
+}
+
+sub formattime {
+ my $duration = shift;
+ my $hour = sprintf("%02d", int($duration/3600));
+ my $sec = $duration % 3600;
+ my $min = sprintf("%02d", int($sec/60));
+ my $sec = sprintf("%02d", $sec % 60);
+ if ($hour eq '00') {
+ return "$min:$sec";
+ } else {
+ return "$hour:$min:$sec";
+ }
+}
+
+# sub Ivy::exit {
+# print "exit\n";
+# }
+
+sub setEditMode {
+ #print "setEditMode\n";
+ $messagesText->configure(-state => 'normal');
+ $messagesText->focus;
+}
+
+sub unsetEditMode {
+ #print "unsetEditMode\n";
+ $messagesText->configure(-state => 'disabled');
+ $bindingsEntry->focus;
+}