summaryrefslogtreecommitdiff
path: root/src/Agent.pm
diff options
context:
space:
mode:
Diffstat (limited to 'src/Agent.pm')
-rw-r--r--src/Agent.pm405
1 files changed, 405 insertions, 0 deletions
diff --git a/src/Agent.pm b/src/Agent.pm
new file mode 100644
index 0000000..75cf616
--- /dev/null
+++ b/src/Agent.pm
@@ -0,0 +1,405 @@
+package Agent;
+
+use IvyIO;
+use FugueConfig;
+
+use strict;
+
+
+
+#----------------------------------------------------------------------
+#
+# class variables and methods
+#
+#----------------------------------------------------------------------
+my ($x0, $y0, $wmax, $dx, $dy, $cmin, $rmax, @matrix, @hosts, $tempo_id,
+ $mw, $bg, $fg, $selcolor, $hlbg, $darkbg, $fontspec, @fontspec,
+ $on_img, $off_img, %instances, @instances, $bus,
+ $hosts_tl, $selected_host, $preselected_host);
+
+
+# configure the class : graphic parameters, geometry and ivy bus
+sub configure {
+ shift;
+ ($mw, $bg, $fg, $selcolor, $hlbg, $darkbg,
+ $fontspec, $x0, $y0, $wmax, $dy, $cmin, $rmax, $bus) = @_;
+ @fontspec = @$fontspec if $fontspec;
+ $off_img = $mw->Bitmap('off', -file => Tk::findINC('led.bmp'),
+ -foreground => $bg);
+ $on_img = $mw->Bitmap('on', -file => Tk::findINC('led.bmp'),
+ -foreground => $selcolor);
+ &_resetmatrix;
+
+} # end configure
+
+
+# kill an named agent
+sub kill {
+ my ($class, $appname) = @_;
+ IvyIO::kill($appname);
+
+} # end kill;
+
+
+# kill all agents
+sub killall {
+ my ($class) = @_;
+ my $nb = 0;
+ for(@instances) {
+ if ($_->{status} > 0) {
+ IvyIO::kill($_->{appname});
+ $nb++;
+ }
+ }
+ return $nb;
+
+} # end killall;
+
+# set usable hosts list for executing agents
+sub hosts {
+ shift;
+ @hosts = @_;
+
+} # end hosts
+
+# callback called when an agent connects
+sub connect {
+ my ($class, $appname, $host) = @_;
+ if ($instances{$appname}) {
+ if ($instances{$appname}->{status} == 1) {
+ $instances{$appname}->addtwin;
+ } else {
+ $instances{$appname}->on;
+ }
+ } else {
+ $class->new($appname, $host, undef, undef, 1);
+ }
+
+} # end connect
+
+
+# callback called when an agent disconnects
+sub disconnect {
+ my ($class, $appname, $host) = @_;
+ if ($instances{$appname}) {
+ if ($instances{$appname}->{number} > 1) {
+ $instances{$appname}->removetwin;
+ } else {
+ $instances{$appname}->off;
+ }
+ } else {
+ $class->new($appname, $host, undef, undef, 0);
+ }
+
+} # end disconnect
+
+# class constructor
+sub new {
+ my ($class, $appname, $host, $command, $params, $status) = @_;
+ if ($instances{$appname}) {
+ $instances{$appname}->addtwin;
+ return;
+ }
+ my $self = {};
+ bless $self;
+
+ # set object attributes
+ $self->{appname} = $appname;
+ $self->{status} = $status;
+ $self->{host} = $host;
+ $self->{command} = $command;
+ $self->{params} = $params;
+ $self->{number} = 1;
+ $self->{led} = undef;
+ $self->{label} = undef;
+
+ # reset positions before adding new instance
+ if (@instances >= ($rmax*$cmin)) {
+ $cmin++;
+ $class->_updatepositions;
+ }
+ # set class variables
+ $instances{$appname} = $self;
+ push(@instances, $self);
+
+ # graphical updates
+ $self->createlabel;
+ $self->on if $status;
+
+ $class->_alphabeticsort();
+
+ return $self;
+
+} # end new
+
+
+
+#----------------------------------------------------------------------
+#
+# instance methods
+#
+#----------------------------------------------------------------------
+
+# called when several agent instances are detected
+sub addtwin {
+ my $self = shift;
+ $self->{number}++;
+ $self->{label}->configure(-text => $self->formatlabel);
+
+} # end addtwin
+
+# called when an instance of a not single agent dies
+sub removetwin {
+ my $self = shift;
+ return if $self->{number} == 1;
+ $self->{number}--;
+ $self->{label}->configure(-text => $self->formatlabel);
+
+} # end addtwin
+
+# graphical effect when a known agent connects
+sub on {
+ my $self = shift;
+ $self->{status} = 1;
+ $self->{led}->configure(-image => $on_img);
+ $self->{label}->raise;
+ $self->{label}->configure(-highlightthicknes => 2);
+ $tempo_id = $mw->after(3000, sub {
+ $self->{label}->configure(highlightthicknes => 0);
+ });
+
+} # end on
+
+# graphical effect when a known agent disconnects
+
+sub off {
+ my $self = shift;
+ $self->{status} = 0;
+ $self->{led}->configure(-image => $off_img);
+
+} # end off
+
+# label placement
+sub setposition {
+ my $self = shift;
+ my ($r, $c);
+ for (my $i=0; $i<@matrix; $i++) {
+ for (my $j=0; $j<=$#{$matrix[$i]}; $j++) {
+ unless ($matrix[$i][$j]->[0]) {
+ ($r, $c) = ($i, $j);
+ last;
+ }
+ }
+ last if defined $r;
+ }
+ $matrix[$r][$c]->[0] = $self;
+ my ($x, $y) = ($matrix[$r][$c]->[1], $matrix[$r][$c]->[2]);
+ my $y2 = $y - 5;
+ $y2 -= 12 if ($self->{label}->cget(-text) =~ /\n/);
+
+ if ($self->{command}) {
+ $self->{led}->configure(-highlightthickness => 6);
+ $self->{led}->place(-x => $x+3, -y => $y+3);
+ } else {
+ $self->{led}->configure(-highlightthickness => 2);
+ $self->{led}->place(-x => $x+6, -y => $y+6);
+ }
+ $self->{label}->place(-x => $x + 38, -y => $y2);
+
+} # end setposition
+
+
+# label creation
+sub createlabel {
+ my ($self) = @_;
+ $self->{led} =
+ $mw->Label(-image => $off_img,
+ -highlightbackground => $hlbg,
+ -borderwidth => 0,
+ );
+ $self->{label} =
+ $mw->Label(-text => $self->formatlabel,
+ -pady => 10,
+ -justify => 'left',
+ -relief => 'flat',
+ -highlightthickness => 0,
+ -background => $darkbg,
+ -foreground => $fg,
+ @fontspec,
+ );
+ $self->{led}->bind('<1>', [\&_cbOnPress, $self]);
+ $self->{label}->bind('<1>', [\&_cbOnPress, $self]);
+
+} # end createlabel
+
+# label format : affects too long labels and not single agents
+sub formatlabel {
+ my ($self) = @_;
+ my $im_width = 38;
+ my $dx = $dx - $im_width;
+ my $appname = $self->{appname};
+ # for not single agents
+ $appname = '['.$self->{number}.'] '.$appname if $self->{number} > 1;
+ my $width = $mw->fontMeasure($fontspec[1], $appname);
+ my $appnametext;
+ # for too long names
+ while ($width > 2*$dx) {
+ $appname = substr($appname, 0, -1);
+ $width = $mw->fontMeasure($fontspec[1], $appname);
+ }
+ if ($width > $dx) {
+ my $hlen = int(length($appname)/2);
+ my @fields = split(/:/, $appname);
+ if (@fields > 1) {
+ my $len = 0;
+ my $imax = -1;
+ for (my $i=0; $i<@fields; $i++) {
+ $len += length($fields[$i]);
+ if ($len > $hlen) {
+ $imax = $i - 1;
+ last;
+ }
+ }
+ if ($imax >= 0) {
+ $appnametext = join(':', (@fields)[0..$imax])."\n".
+ join(':', (@fields)[$imax+1..$#fields]);
+ }
+ }
+ $appnametext = substr($appname, 0, $hlen)."\n".substr($appname, $hlen)
+ unless $appnametext;
+ } else {
+ my @fields = split(/:/, $appname);
+ if (@fields > 1) {
+ $appnametext = $fields[0].":\n ".join(':', (@fields)[1..$#fields]);
+ }
+ }
+ $appnametext = $appname unless $appnametext;
+ return $appnametext;
+
+} # end formatlabel
+
+
+#----------------------------------------------------------------------
+#
+# private methods or functions
+#
+#----------------------------------------------------------------------
+sub _resetmatrix {
+ $dx = $wmax/$cmin;
+ for(my $r=0; $r<$rmax; $r++) {
+ for(my $c=0; $c<$cmin; $c++) {
+ $matrix[$r][$c] = [undef, $x0 + $c*$dx, $y0 + $r*$dy];
+ }
+ }
+
+} # end _resetmatrix
+
+
+sub _updatepositions {
+ &_resetmatrix;
+ for (@instances) {
+ $_->setposition();
+ $_->{label}->configure(-text => $_->formatlabel);
+ }
+
+} # end _updatepositions
+
+
+sub _alphabeticsort {
+ &_resetmatrix;
+ for (sort {uc($a->{appname}) cmp uc($b->{appname})} @instances) {
+ $_->setposition();
+ }
+
+} # end _alphabeticsort
+
+
+# callback invoked when user press on label
+sub _cbOnPress {
+ shift;
+ my $self = shift;
+ $self->{label}->configure(-foreground => $hlbg);
+ if ($self->{status} == 1) {
+ IvyIO::kill($self->{appname});
+ $mw->after(400, sub {$self->{label}->configure(-foreground => $fg);});
+ } elsif ($self->{command}) {
+ my ($x, $y) = ($self->{label}->rootx, $self->{label}->rooty);
+ my $host = &_hostsmenu($x, $y, $self->{host});
+ $self->{label}->configure(-foreground => $fg);
+ return unless $host;
+ FugueConfig::launchAgent($self->{appname}, $host,
+ $self->{command}, $self->{params}, $bus);
+ print "$self->{appname} launched\n";
+ } else {
+ $mw->bell;
+ $mw->after(400, sub {$self->{label}->configure(-foreground => $fg);});
+ }
+
+} # end _cbOnPress
+
+
+# create and show hosts menu
+sub _hostsmenu {
+ my $x = shift;
+ my $y = shift;
+ $x += 50;
+
+ my $preselected_host = shift;
+ return $preselected_host if @hosts <= 1;
+ $hosts_tl->destroy if Tk::Exists($hosts_tl);
+ $hosts_tl = $mw->Toplevel(-background => $darkbg);
+ $hosts_tl->resizable(0,0);
+ $hosts_tl->title('ivycontrolpanel');
+ my $hosts_fm = $hosts_tl->Frame(-background => $darkbg,
+ -highlightthickness => 3,
+ -highlightbackground => $hlbg,
+ )->pack(-side => 'top', -padx => 0, -pady => 0);
+ my @lattr = (-padx => 10,
+ -pady => 10,
+ -relief => 'flat',
+ -highlightthickness => 0,
+ -background => $darkbg,
+ -foreground => $fg,
+ -borderwidth => 0,
+ @fontspec);
+ my @battr = (@lattr,
+ -width => 4, -height => 1,
+ -highlightthickness => 3,
+ -highlightbackground => $hlbg,
+ -activebackground => $darkbg,
+ -activeforeground => $fg,
+ );
+ my @rattr = (@lattr,
+ -activebackground => $darkbg,
+ -activeforeground => $fg,
+ -selectcolor => $selcolor);
+
+ $hosts_fm->Label(@lattr, -text => "restart on :"
+ )->pack(-side => 'top', -padx => 20, -pady => 20);
+ for(@hosts) {
+ $hosts_fm->Radiobutton(@rattr,
+ -variable => \$preselected_host,
+ -value => $_,
+ -text => $_,
+ )->pack(-side => 'top');
+ }
+ my $fm = $hosts_fm->Frame(-background => $darkbg)->pack(-side => 'bottom');
+ $fm->Button(@battr,
+ -command => sub {$hosts_tl->destroy},
+ -text => 'ok')->pack(-side => 'left', -padx => 10, -pady => 20);
+ $fm->Button(@battr,
+ -command => sub {$preselected_host = undef; $hosts_tl->destroy},
+ -text => 'cancel')->pack(-side => 'left', -padx => 10, -pady => 20);
+ $hosts_tl->update;
+ my ($X, $Y) = ($mw->rootx, $mw->rooty);
+ my ($w, $h) = ($hosts_tl->width, $hosts_tl->height);
+ my ($W, $H) = ($mw->width, $mw->height);
+ $x = $X + $W - $w if ($x + $w) > $X + $W;
+ $y = $Y + $H - $h if ($y + $h) > $Y + $H;
+ $hosts_tl->geometry('+'.$x.'+'.$y);
+ $hosts_tl->waitWindow();
+ return $preselected_host;
+
+} # end hostsmenu
+
+1;