Subversion Repositories Projects

Compare Revisions

Ignore whitespace Rev 731 → Rev 732

/MissionCockpit/tags/V0.4.0/libmktimer.pl
File deleted
/MissionCockpit/tags/V0.4.0/libmkcockpit.pl
0,0 → 1,2720
 
#!/usr/bin/perl
#!/usr/bin/perl -d:ptkdb
 
###############################################################################
#
# libmkcockpit.pl - MK Mission Cockpit - Subroutined for GUI Frontend
#
# Copyright (C) 2009 Rainer Walther (rainerwalther-mail@web.de)
#
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
#
# Es ist Ihnen gestattet:
# * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
# * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
#
# Zu den folgenden Bedingungen:
# * Namensnennung.
# Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
# * Keine kommerzielle Nutzung.
# Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
# * Weitergabe unter gleichen Bedingungen.
# Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
# verändern oder als Grundlage für einen anderen Inhalt verwenden,
# dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
# weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
#
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
#
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
# des Rechteinhabers dazu erhalten.
#
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
#
# Weitere Details zur Lizenzbestimmung gibt es hier:
# Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
# Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
#
###############################################################################
# 2009-08-09 0.2.5 rw subroutines moved from mkcockpit.pl
# 2009-09-05 0.2.6 rw POI heading control added
# 2009-10-10 0.2.7 rw Layout Config-dialog
# Fix Message-Balloon in KML-Mode
# 2009-10-25 0.3.0 rw NC 0.17
# Read/Write KopterTool WPL Waypoint list
# configuration Combo Box
# 2010-02-10 0.4.0 rw Show Grid on map
# Show crosshair in player pause mode
# joystick and 3D-Mouse support
# Cfg file selection dialog
# serial channel
# Event engine
# External control - Limit, expo
# Resize WP-Icon to 24x48 pixel
#
###############################################################################
 
$Version{'libmkcockpit.pl'} = "0.4.0 - 2010-02-10";
 
 
# check, if %MkOsd is valid
sub MkOsdIsValid()
{
return ( $MkOsd{'_Timestamp'} >= time-2 );
}
 
# check, if current GPS position is valid
sub CurPosIsValid()
{
return ( &MkOsdIsValid() and $MkOsd{'SatsInUse'} >= 6 and $MkOsd{'CurPos_Stat'} == 1 );
}
 
# check, if home GPS position is valid
sub HomePosIsValid()
{
return ( &MkOsdIsValid() and $MkOsd{'SatsInUse'} >= 6 and $MkOsd{'HomePos_Stat'} == 1 );
}
 
# check, if target GPS position is valid
sub TargetIsValid()
{
return ( &MkOsdIsValid() and $MkOsd{'SatsInUse'} >= 6 and $MkOsd{'TargetPos_Stat'} == 1 );
}
 
# check, if motor are on
sub MkIsMotorOn()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x01 );
}
 
# check, if MK is flying
sub MkIsFlying()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x02 );
}
 
# check, if MK is calibrating
sub MkIsCalibrating()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x04 );
}
# check, if Motor is starting
sub MkIsMotorStarting()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x08 );
}
 
# check, Emergency Landing
sub MkEmergencyLanding()
{
return ( &MkOsdIsValid() and $MkOsd{'MKFlags'} & 0x10 );
}
 
# check, if MK is FREE Mode
sub MkIsFreeMode()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x01 );
}
 
# check, if MK is in PH Mode
sub MkIsPhMode()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x02 );
}
 
# check, if MK is in WPT Mode
sub MkIsWptMode()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x04 );
}
 
# check, Range Limit
sub MkRangeLimit()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x08 );
}
 
# check, Serial Link
sub MkSerialLink()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x10 );
}
 
# check, Target reached
sub MkTargetReached()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x20 );
}
 
# check, Manual Control
sub MkManualControl()
{
return ( &MkOsdIsValid() and $MkOsd{'NCFlags'} & 0x40 );
}
 
 
# Get altitude (hoehensensor)
sub AltitudeAir ()
{
return ( $MkOsd{'Altimeter'} / $Cfg->{'mkcockpit'}->{'AltFactor'} );
}
 
# Get altitude (GPS)
sub AltitudeGPS ()
{
return ( $MkOsd{'CurPos_Alt'} - $MkOsd{'HomePos_Alt'} );
}
 
# Get altitude (average hoehensensor , GPS)
sub Altitude ()
{
my $Alt = ( 4 * &AltitudeAir + &AltitudeGPS ) / 5;
return ($Alt);
}
 
# range 0 .. 255
sub CheckUnsignedChar()
{
my ($U8) = @_;
 
if ( $U8 < 0) { $U8 = 0; };
if ( $U8 > 255) { $U8 = 255; };
return $U8;
}
 
# range -128 .. 127
sub CheckSignedChar()
{
my ($S8) = @_;
 
if ( $S8 < -128) { $S8 = -128; };
if ( $S8 > 127) { $S8 = 127; };
return $S8;
}
 
# Set serial Channel value. Num: 0..11, Val: -128..0..127
sub SerialChannel()
{
my ($Num, $Val) = @_;
 
my $Key = sprintf ("SerialChannel%02d", $Num + 1);
 
lock (%MkSerialChannel); # until end of block
 
$MkSerialChannel{$Key} = &CheckSignedChar($Val);
 
# timestamp, when channel value was set
$MkSerialChannel{'_Timestamp'} = time;
}
 
 
# Limit: 0% .. 100%
# Expo : -100% .. 0 .. 100%
sub ExpoLimit ()
{
my ($StickMin, $StickMax, $Stick, $Expo, $Limit) = @_;
 
if ( $Expo ne "" )
{
# neg. Expo: 1..0.2 (0% .. -100%)
# pos. Expo: 1..5 (0% .. 100%)
 
if ( $Expo >= 0 )
{
$Expo = 1 + $Expo / 100 * 5;
}
else
{
$Expo = 1 - $Expo / 100 / 5;
}
 
if( $Stick >= 0 )
{
$Stick = $StickMax * ( $Stick ** $Expo ) / ( $StickMax ** $Expo);
}
else
{
$Stick = $StickMin * ( (- $Stick) ** $Expo ) / ( (- $StickMin) ** $Expo);
}
}
 
if ( $Limit ne "" )
{
$Stick = $Stick * $Limit / 100;
}
 
return ($Stick);
}
 
 
# get battery capacity in %
sub BatCapacity()
{
my ($UBat) = @_;
 
my $CfgVal = $Cfg->{'mkcockpit'}->{'BatCharacteristics'};
my @Voltage = split ' ', $CfgVal;
 
my $Capacity = 0;
if ( $UBat >= $Voltage[0] )
{
$Capacity = 100;
}
 
$Cnt = $#Voltage;
for ($i=0; $i < $Cnt; $i++)
{
my $V1 = $Voltage[$i];
my $V2 = $Voltage[$i+1];
 
if ( $UBat >= $V1 and $UBat < $V2 or
$UBat <= $V1 and $UBat > $V2 )
{
# linear interpolation
my $x = $i + ($UBat - $V1 ) / ($V2 - $V1);
$Capacity = 100 - $x * 100 / $Cnt;
last;
}
}
 
return $Capacity;
}
 
 
#
# Waypoint handling
#
 
# Add a Waypoint to @Waypoints List
sub WpAdd()
{
my %Param = @_;
my $Wp_x = $Param{'-x'};
my $Wp_y = $Param{'-y'};
my $Lat = $Param{'-lat'};
my $Lon = $Param{'-lon'};
 
# x/y and/or Lat/Lon must be passed
if ( $Wp_x eq "" and $Wp_y eq "" )
{
($Wp_x, $Wp_y) = &MapGps2XY($Lat, $Lon);
}
if ( $Lat eq "" and $Lon eq "" )
{
($Lat, $Lon) = &MapXY2Gps($Wp_x, $Wp_y);
}
 
# save Wp-Hash in Waypoint-Array
my $Wp = {};
 
# kind of unique Tag for this Wp
my ($t0_s, $t0_us) = gettimeofday;
my $Tag = sprintf "WP-%d.%d", $t0_s, $t0_us;
 
$Wp->{'Tag'} = $Tag;
$Wp->{'MapX'} = $Wp_x;
$Wp->{'MapY'} = $Wp_y;
$Wp->{'Pos_Lat'} = $Lat;
$Wp->{'Pos_Lon'} = $Lon;
$Wp->{'Pos_Alt'} = $MkOsd{'CurPos_Alt'};
$Wp->{'Heading'} = $Cfg->{'waypoint'}->{'DefaultHeading'};
$Wp->{'ToleranceRadius'} = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
$Wp->{'Holdtime'} = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
$Wp->{'Event_Flag'} = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
push @Waypoints, $Wp;
}
 
 
# Delete Waypoint from @Waypoints List
sub WpDelete ()
{
my ($WpIndex) = @_;
 
# delete Wp in Waypoint-Array
splice @Waypoints, $WpIndex, 1;
}
 
 
# Delete all Waypoints
sub WpDeleteAll ()
{
undef @Waypoints;
$WpPlayerIndex = 0;
$WpPlayerHoldtime = -1;
 
# remove all Wp-Icons and Wp-Number on canvas
&WpHide();
}
 
 
# Load @Waypoints from file
sub WpLoadFile ()
{
my ($WpFile) = @_;
 
if ( $WpFile =~ /.wpl$/i )
{
# load Mikrokopter Tool WP List *.wpl
 
my $WpCnt = 0;
my $WpIndex = 0;
my @WpWpl;
 
open WPL, "<$WpFile";
my @Wpl = <WPL>;
close WPL;
foreach my $Line (@Wpl)
{
chomp $Line;
if ( $Line =~ /NumberOfWaypoints\s*=\s*(\d*)/i )
{
$WpCnt = $1;
}
elsif ( $Line =~ /\[Waypoint(\d*)\]/i )
{
$WpIndex = $1;
}
elsif ( $Line =~ /(\S*)\s*=\s*(\S*)/i )
{
my $Key = $1;
my $Value = $2;
$WpWpl[$WpIndex]{$Key} = $Value;
}
}
 
# WPL Array in Waypoints-Array umkopieren
undef @Waypoints;
 
for ( $Index=0; $Index < $WpCnt; $Index++)
{
my $Wp = {};
my $Tag = sprintf "Waypoint-%d.%d", time, $Index + 1; # kind of unique Tag for this Wp
my $Lat = $WpWpl[$Index]{'Latitude'};
my $Lon = $WpWpl[$Index]{'Longitude'};
($MapX, $MapY) = &MapGps2XY($Lat, $Lon);
$Wp->{'Tag'} = $Tag;
$Wp->{'MapX'} = $MapX;
$Wp->{'MapY'} = $MapY;
$Wp->{'Pos_Lat'} = $Lat;
$Wp->{'Pos_Lon'} = $Lon;
$Wp->{'Pos_Alt'} = $MkOsd{'CurPos_Alt'};
$Wp->{'Heading'} = $Cfg->{'waypoint'}->{'DefaultHeading'};
$Wp->{'ToleranceRadius'} = $WpWpl[$Index]{'Radius'};
$Wp->{'Holdtime'} = $WpWpl[$Index]{'DelayTime'};
$Wp->{'Event_Flag'} = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
push @Waypoints, $Wp;
}
}
else
{
# load Mission Cockpit XML
 
# XML in Hash-Ref lesen
my $Wp = XMLin($WpFile, ForceArray => 1);
 
# XML Hash-Ref in Wp-Array umkopieren
undef @Waypoints;
 
foreach $key (sort keys %$Wp)
{
my $Point = $Wp->{$key}->[0];
 
# relative Pixelkoordinaten auf Bildgroesse umrechnen
if ( $Point->{'MapX'} <= 1 and $Point->{'MapY'} <= 1 )
{
$Point->{'MapX'} = int ( $Point->{'MapX'} * $MapSizeX + 0.5 );
$Point->{'MapY'} = int ( $Point->{'MapY'} * $MapSizeY + 0.5 );
}
 
# abs. pixel koordinates not needed
delete $Point->{'MapX_Pixel'};
delete $Point->{'MapY_Pixel'};
 
# GPS Koordinaten für die aktuelle Karte neu aus Map x/y berechnen
my ($Lat, $Lon) = &MapXY2Gps($Point->{'MapX'}, $Point->{'MapY'});
$Point->{'Pos_Lat'} = $Lat;
$Point->{'Pos_Lon'} = $Lon;
push @Waypoints, $Point;
}
}
# Start with 1st WP
&WpTargetFirst();
}
 
 
# Save @Waypoints to file
sub WpSaveFile()
{
my ($WpFile) = @_;
 
if ( $WpFile =~ /.wpl$/i )
{
# save Mikrokopter Tool WP List *.wpl
 
open WPL, ">$WpFile";
 
my $WpCnt = scalar @Waypoints;
 
print WPL "[General\]\n";
print WPL "FileVersion=1\n";
print WPL "NumberOfWaypoints=$WpCnt\n";
 
for $i ( 0 .. $#Waypoints )
{
print WPL "\[Waypoint${i}\]\n";
print WPL "Latitude=$Waypoints[$i]{'Pos_Lat'}\n";
print WPL "Longitude=$Waypoints[$i]{'Pos_Lon'}\n";
print WPL "Radius=$Waypoints[$i]{'ToleranceRadius'}\n";
print WPL "DelayTime=$Waypoints[$i]{'Holdtime'}\n";
}
close WPL;
}
else
{
# save Mission Cockpit XML
 
# Waypoint-Array in Hash umkopieren
for $i ( 0 .. $#Waypoints )
{
my $key = sprintf ("WP-%04d", $i);
my $Wp = {%{$Waypoints[$i]}}; # copy of Hash-content
$WpOut{$key} = $Wp;
 
# Pixelkoordinaten relativ zur Bildgroesse speichern
$WpOut{$key}{'MapX_Pixel'} = $WpOut{$key}{'MapX'};
$WpOut{$key}{'MapY_Pixel'} = $WpOut{$key}{'MapY'};
$WpOut{$key}{'MapX'} /= $MapSizeX;
$WpOut{$key}{'MapY'} /= $MapSizeY;
}
 
# WP-Hash als XML speichern
&XMLout (\%WpOut,
'OutputFile' => $WpFile,
'AttrIndent' => '1',
'RootName' => 'Waypoints',
);
}
}
 
 
# Get Wp Index from Canvas Id
sub WpGetIndexFromId()
{
my ($id) = @_;
 
my @Tags = $map_canvas->gettags($id);
my $WpTag = $Tags[1];
 
for $i (0 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
if ( $Wp->{'Tag'} eq $WpTag )
{
# got it
return $i;
}
}
return -1;
}
 
# Resend all Waypoints to MK
sub WpSendAll()
{
# OSD/Debug Abfragefrequenz verringern, sonst kommen nicht alle Wp im MK an
# Sicherheitshalber doppelt senden
$MkSendWp = 1; # verhindert ueberschreiben im Timer
 
$MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) ); # Frequenz OSD Datensatz, * 10ms
$MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) ); # Frequenz MK Debug Datensatz, * 10ms
usleep (200000);
$MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) ); # Frequenz OSD Datensatz, * 10ms
$MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) ); # Frequenz MK Debug Datensatz, * 10ms
usleep (200000);
 
# Alte WP-Liste im MK löschen
my $Wp = $Waypoints[0];
&MkFlyTo ( '-lat' => $Wp->{'Pos_Lat'},
'-lon' => $Wp->{'Pos_Lon'},
'-mode' => "Waypoint Delete"
);
for $i (0 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
&MkFlyTo ( '-lat' => $Wp->{'Pos_Lat'},
'-lon' => $Wp->{'Pos_Lon'},
'-alt' => $Wp->{'Pos_Alt'},
'-heading' => $Wp->{'Heading'},
'-toleranceradius' => $Wp->{'ToleranceRadius'},
'-holdtime' => $Wp->{'Holdtime'},
'-eventflag' => $Wp->{'Event_Flag'},
'-mode' => "Waypoint",
'-index' => $i,
);
usleep (150000) # NC Zeit zum Verarbeiten geben
}
 
$MkSendWp = 0; # normale OSD/Debug Abfragefrequenz wird automatisch im 5s Timer wieder eingestellt
 
# grey connectors: Wp are sent to MK
$map_canvas->itemconfigure('Waypoint-Connector',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorWpConnector'},
);
 
# MK ist nun synchron mit @Waypoints
$WaypointsModified = 0;
}
 
# Redraw Waypoint Icons
sub WpRedrawIcons()
{
if ( $PlayerWptKmlMode =~ /WPT/i )
{
 
# delete old icons and Wp-Number from canvas
$map_canvas->delete('Waypoint');
$map_canvas->delete('WaypointNumber');
 
# create new icons
for $i (0 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
my $x = $Wp->{'MapX'};
my $y = $Wp->{'MapY'};
my $Tag = $Wp->{'Tag'};
 
# Waypoint Icon
my $IconHeight = 48;
my $IconWidth = 24;
$map_canvas->createImage($x-$IconWidth/2, $y-$IconHeight,
'-tags' => ['Waypoint', $Tag],
'-anchor' => 'nw',
'-image' => 'Waypoint-Photo',
);
# Waypoint Number
my $WpNumber = $i + 1;
$map_canvas->createText ( $x+3, $y-$IconHeight/2+12,
'-tags' => ['WaypointNumber', $Tag],
'-text' => $WpNumber,
'-font' => '-*-Arial-Bold-R-Normal--*-100-*',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorWpNumber'},
'-anchor' => 'w',
);
 
}
$map_canvas->lower('Waypoint', 'Target'); # waypoint below Target
$map_canvas->lower('WaypointNumber', 'Waypoint'); # waypoint-number below waypoint
}
}
 
# Redraw Waypoint connectors
sub WpRedrawLines()
{
if ( $PlayerWptKmlMode eq 'WPT' and $PlayerRandomMode eq 'STD' )
{
# delete old connectors from canvas
$map_canvas->delete('Waypoint-Connector');
 
my $Color = $Cfg->{'mkcockpit'}->{'ColorWpConnector'};
if ( $WaypointsModified )
{
$Color = $Cfg->{'mkcockpit'}->{'ColorWpResend'};
}
 
my $Wp = $Waypoints[0];
my $x_last = $Wp->{'MapX'};
my $y_last = $Wp->{'MapY'};
for $i (1 .. $#Waypoints)
{
my $Wp = $Waypoints[$i];
my $x = $Wp->{'MapX'};
my $y = $Wp->{'MapY'};
 
$map_canvas->createLine ( $x_last, $y_last, $x, $y,
'-tags' => 'Waypoint-Connector',
'-arrow' => 'last',
'-arrowshape' => [10, 10, 3 ],
'-fill' => $Color,
'-width' => 1,
);
$x_last = $x;
$y_last = $y;
}
$map_canvas->raise('Waypoint-Connector', 'Map'); # connector above map
 
}
}
 
 
# Hide Waypoints and connectors on Canvas
sub WpHide()
{
$map_canvas->delete('Waypoint');
$map_canvas->delete('WaypointNumber');
$map_canvas->delete('Waypoint-Connector');
}
 
# Hide Kml-Track on Canvas
sub KmlHide()
{
$map_canvas->delete('KML-Track');
}
 
 
# Load @KmlTargets from file
sub KmlLoadFile()
{
my ($File) = @_;
 
# XML in Hash-Ref lesen
my $Kml = XMLin($File);
 
# init state maschine
undef @KmlTargets;
$KmlPlayerIndex = 0;
 
my $Coordinates = $Kml->{Document}->{Placemark}->{LineString}->{coordinates};
foreach $Line (split "\n", $Coordinates)
{
chomp $Line;
$Line =~ s/\s//g; # remove white space
if ( $Line ne "" )
{
my ($Lon, $Lat, $Alt) = split ",", $Line;
$Lon = sprintf ("%f", $Lon);
$Lat = sprintf ("%f", $Lat);
$Alt = sprintf ("%f", $Alt);
 
push @KmlTargets, {'Lat' => $Lat,
'Lon' => $Lon,
'Alt' => $Alt,
};
}
}
}
 
# Redraw KML track
sub KmlRedraw()
{
 
# delete old Track from canvas
$map_canvas->delete('KML-Track');
 
my @Track;
 
foreach $Target ( @KmlTargets )
{
my $Lat = $Target->{'Lat'};
my $Lon = $Target->{'Lon'};
my $Alt = $Target->{'Alt'};
my ($x, $y) = &MapGps2XY($Lat, $Lon);
push @Track, $x, $y;
}
 
if ( scalar @Track >= 4 ) # at least 2 Koordinaten-Paare
{
$map_canvas->createLine ( @Track,
'-tags' => 'KML-Track',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorKmlTrack'},
'-width' => 1,
);
 
$map_canvas->lower('KML-Track', 'Target'); # Track below Target
}
}
 
 
# Redraw Footprint
sub FootprintRedraw()
{
# delete old Footprint from canvas
$map_canvas->delete('Footprint');
 
if ( scalar @Footprint >= 4 ) # at least 2 Koordinaten-Paare
{
$map_canvas->createLine ( @Footprint,
'-tags' => 'Footprint',
'-fill' => $Cfg->{'mkcockpit'}->{'ColorFootprint'},
'-width' => 1,
);
}
$map_canvas->lower('Footprint', 'Target');
}
 
 
# Waypoint Player: Set Waypoint - sequence or random
sub WpTargetSet()
{
my ($Index) = @_;
 
my $WpCnt = scalar @Waypoints;
if ( $Index < 0 or $Index >= $WpCnt )
{
# invalid WP number
return 1;
}
 
my $Wp = $Waypoints[$Index];
my $Wp_x = $Wp->{'MapX'};
my $Wp_y = $Wp->{'MapY'};
 
# is Wp reachable?
if ( ! &IsTargetReachable($Wp_x, $Wp_y) )
{
# new Wp-Target is not reachable
return 1;
}
 
# set new Wp-Target
$WpPlayerIndex = $Index;
$WpPlayerHoldtime = -1;
 
return 0;
}
 
 
# Waypoint Player: Goto next Waypoint - sequence or random
sub WpTargetNext()
{
 
my $WpCnt = scalar @Waypoints;
 
# Std- or Random Waypoint sequence
if ( $PlayerRandomMode =~ /STD/i or
$PlayerRandomMode =~ /RND/i )
{
$NewIndex = $WpPlayerIndex;
 
# get next Wp
for ( $i=0; $i<5; $i++) # avoid deadlock, if no WP reachable
{
for ( $j=0; $j<5; $j++ ) # avoid deadlock, if only 1 WP
{
if ( $PlayerRandomMode =~ /STD/i )
{
$NewIndex ++;
if ( $NewIndex >= $WpCnt )
{
# Restart with 1st Wp
$NewIndex = 0;
}
}
if ( $PlayerRandomMode =~ /RND/i )
{
$NewIndex = int (rand($WpCnt));
}
 
# want to have different Wp
if ( $NewIndex ne $WpPlayerIndex )
{
last;
}
}
 
# Set new Target
if ( &WpTargetSet ($NewIndex) == 0 )
{
# new Wp-Target set
last;
}
}
}
 
# Random Map sequence
if ( $PlayerRandomMode =~ /MAP/i )
{
$RandomTarget_x = $MkPos_x;
$RandomTarget_y = $MkPos_y;
 
for ( $i=0; $i<50; $i++) # avoid deadlock, if target not reachable
{
# don't use 10% around the map
my $New_x = int (rand($MapSizeX - 2 * $MapSizeX/10));
my $New_y = int (rand($MapSizeY - 2 * $MapSizeY/10));
$New_x += $MapSizeX/10;
$New_y += $MapSizeY/10;
 
# is Target reachable?
if ( &IsTargetReachable($New_x, $New_y) )
{
# new Target found
$RandomTarget_x = $New_x;
$RandomTarget_y = $New_y;
last;
}
}
}
&TtsSpeak ('MEDIUM', $Translate{'TtsNextTarget'});
 
$WpPlayerHoldtime = -1;
}
 
 
# Waypoint Player: Goto previous Waypoint
sub WpTargetPrev()
{
if ( $PlayerRandomMode =~ /STD/i )
{
$WpPlayerIndex --;
if ( $WpPlayerIndex < 0 )
{
# Restart with last Wp
$WpPlayerIndex = $#Waypoints;
}
}
else
{
# Next Random Target
&WpTargetNext();
}
 
$WpPlayerHoldtime = -1;
}
 
 
# Waypoint Player: Goto first Waypoint
sub WpTargetFirst()
{
$WpPlayerIndex = 0;
$WpPlayerHoldtime = -1;
}
 
# Waypoint Player: Goto last Waypoint
sub WpTargetLast()
{
$WpPlayerIndex = $#Waypoints;
$WpPlayerHoldtime = -1;
}
 
 
# Waypoint Player: Waypoint Target reached?
sub WpCheckTargetReached()
{
if ( $WpPlayerHoldtime == -1 )
{
lock (%MkOsd); # until end of block
 
if ( &CurPosIsValid() and &HomePosIsValid() and &MkIsWptMode() )
{
# Gueltige SAT Daten
 
# for Wp mode
my $Wp = $Waypoints[$WpPlayerIndex];
my $WpTarget_Lat = $Wp->{'Pos_Lat'};
my $WpTarget_Lon = $Wp->{'Pos_Lon'};
my $WpTolerance = $Wp->{'ToleranceRadius'};
my $WpHoldtime = $Wp->{'Holdtime'};
 
# Random-Map Mode
if ( $PlayerRandomMode =~ /MAP/i )
{
($WpTarget_Lat, $WpTarget_Lon) = &MapXY2Gps ($RandomTarget_x, $RandomTarget_y);
$WpTolerance = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
$WpHoldtime = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
}
 
# Operation Radius pruefen
my ($HomeDist, $HomeBearing) = &MapGpsTo($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
if ( $HomeDist > $MkOsd{'OperatingRadius'} )
{
# Target entsprechend Operation Radius neu berechnen
$HomeDist = $MkOsd{'OperatingRadius'};
($WpTarget_Lat, $WpTarget_Lon) = &MapGpsAt($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $HomeDist, $HomeBearing);
}
 
# Abstand zum Ziel pruefen
my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
$Dist = int ($Dist + 0.5);
if ( $Dist <= $WpTolerance )
{
# Target reached - count down Holdtime
$WpPlayerHoldtime = 2 * $WpHoldtime; # 0..2n - decrement im 0.5s timer
 
&TtsSpeak ('MEDIUM', $Translate{'TtsTargetReached'});
}
}
}
 
if ( $WpPlayerHoldtime == 0 ) # wird im 0.5s timer runtergezaehlt
{
# Target reached - Holdtime is over
$WpPlayerHoldtime = -1;
 
return 1;
}
 
# Target NOT reached
return 0;
}
 
 
# KML Player: 10s forward
sub KmlTargetNext()
{
$KmlPlayerIndex += int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
if ( $KmlPlayerIndex > $#KmlTargets )
{
# Next loop
$KmlPlayerIndex -= $#KmlTargets;
}
}
 
# KML Player: 10s backward
sub KmlTargetPrev()
{
$KmlPlayerIndex -= int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
if ( $KmlPlayerIndex < 0 )
{
# Next loop
$KmlPlayerIndex += $#KmlTargets;
}
}
 
# KML Player: Goto first Target
sub KmlTargetFirst()
{
$KmlPlayerIndex = 0;
}
 
# KML Player: Goto last Target
sub KmlTargetLast()
{
$KmlPlayerIndex = $#KmlTargets;
}
 
 
#
# Set Player modes
#
 
# set Player mode
sub PlayerModeSet()
{
my ($Mode) = @_;
 
if ( $Mode =~ /play/i ) { &PlayerPlay(); }
elsif ( $Mode =~ /pause/i ) { &PlayerPause(); }
elsif ( $Mode =~ /home/i ) { &PlayerHome(); }
elsif ( $Mode =~ /stop/i ) { &PlayerStop(); }
}
 
 
# set player to "Play" mode
sub PlayerPlay()
{
$PlayerMode = 'Play';
$WpPlayerHoldtime = -1;
 
# Play/Pause-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPause-Foto',
);
&FoxHide();
&CrosshairHide();
}
 
 
# set player to "Pause" mode
sub PlayerPause()
{
$PlayerMode = 'Pause';
$WpPlayerHoldtime = -1;
 
# Play/Pause-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPlay-Foto',
);
 
# momentane Position merken und im Player-Timer senden
$PlayerPause_Lon = "";
$PlayerPause_Lat = "";
 
lock (%MkOsd); # until end of block
if ( &CurPosIsValid() )
{
$PlayerPause_Lon = $MkOsd{'CurPos_Lon'};
$PlayerPause_Lat = $MkOsd{'CurPos_Lat'};
}
 
&FoxShow();
 
# restart crosshair timer
$CrosshairTimerCnt = 0;
}
 
 
# set player to "Home" mode
sub PlayerHome()
{
 
$PlayerMode = 'Home';
&WpTargetFirst();
 
# Play/Pause-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPlay-Foto',
);
&FoxHide();
&CrosshairHide();
}
 
 
# set player to "Stop" mode
sub PlayerStop()
{
$PlayerMode = 'Stop';
&WpTargetFirst();
 
# set Play/Pause Icon to "Play
$map_canvas->delete('Wp-PlayPause');
$map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
'-tags' => 'Wp-PlayPause',
'-anchor' => 'nw',
'-image' => 'WpPlay-Foto',
);
 
# switch player to Wp Mode
&PlayerWpt();
 
&FoxHide();
&CrosshairHide();
}
 
 
# set player Random Mode to "STD"
sub PlayerRandomStd()
{
$PlayerRandomMode = "STD";
 
# Set Icon
$map_canvas->delete('Wp-WptRandom');
$map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
'-tags' => 'Wp-WptRandom',
'-anchor' => 'nw',
'-image' => 'WpRandomOn-Foto',
);
 
# redraw connectors and Icons on canvas
&WpRedrawLines();
&WpRedrawIcons();
}
 
 
# set player Random Mode to "RND"
sub PlayerRandomRnd()
{
$PlayerRandomMode = "RND";
 
# Set Icon
$map_canvas->delete('Wp-WptRandom');
$map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
'-tags' => 'Wp-WptRandom',
'-anchor' => 'nw',
'-image' => 'WpRandomMap-Foto',
);
 
# delete Wp-connectors from canvas
$map_canvas->delete('Waypoint-Connector');
}
 
 
# set player Random Mode to "MAP"
sub PlayerRandomMap()
{
$PlayerRandomMode = "MAP";
 
# Set Icon
$map_canvas->delete('Wp-WptRandom');
$map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
'-tags' => 'Wp-WptRandom',
'-anchor' => 'nw',
'-image' => 'WpRandomOff-Foto',
);
 
# Get 1st Target
&WpTargetNext();
 
# hide WP and connectors on canvas
&WpHide();
}
 
 
# set player Pause Mode to "MAP", "MK"
sub PlayerPauseMode()
{
($PlayerPauseMode) = @_;
}
 
 
# set player to KML mode
sub PlayerKml()
{
$PlayerWptKmlMode = 'KML';
 
# Wpt/Kml-Player-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-WptKml');
$map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
'-tags' => 'Wp-WptKml',
'-anchor' => 'nw',
'-image' => 'WpKml-Foto',
);
 
# delete Waypoints from canvas
&WpHide();
 
# show KML Track
&KmlRedraw();
}
 
 
# set player to WPT mode
sub PlayerWpt()
{
$PlayerWptKmlMode = 'WPT';
 
# Wpt/Kml-Player-Icon loeschen und neu anzeigen
$map_canvas->delete('Wp-WptKml');
$map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
'-tags' => 'Wp-WptKml',
'-anchor' => 'nw',
'-image' => 'WpWpt-Foto',
);
 
# delete Kml-Track from canvas
&KmlHide();
 
# Show waypoints, WP resend required
$WaypointsModified = 1;
 
if ( $PlayerRandomMode ne 'MAP' )
{
&WpRedrawIcons()
}
if ( $PlayerRandomMode eq 'STD' )
{
&WpRedrawLines()
}
 
}
 
 
# Activate Recording mode
sub PlayerRecordOn
{
$PlayerRecordMode = "REC";
$map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "Recording" );
 
# Record new KML-Track
undef @KmlTargets;
$KmlPlayerIndex = 0;
 
# delete Kml-Track from canvas
&KmlHide();
}
 
# Deactivate Recording mode
sub PlayerRecordOff
{
$PlayerRecordMode = "";
$map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "" );
}
 
 
# Hide Fox icon on canvas
sub FoxHide()
{
$map_canvas->lower('Fox', 'Map');
}
 
# Show Fox icon on canvas
sub FoxShow()
{
$map_canvas->raise('Fox', 'Target');
}
 
# Hide POI icon on canvas
sub PoiHide()
{
$map_canvas->lower('POI', 'Map');
}
 
# Show POI icon on canvas
sub PoiShow()
{
$map_canvas->raise('POI', 'Track-Antenna');
}
 
# Show Grid on canvas
sub GridShow()
{
my $Dist = $Cfg->{map}->{'GridDist'} || 50;
my $Color = $Cfg->{map}->{'GridColor'} || "#909090";
 
my $xmin = 0;
my $ymin = 0;
my $xmax = $MapSizeX;
my $ymax = $MapSizeY;
+
+ my $PhiRef = &MapAngel();
+ my ($Lat1, $Lon1) = &MapXY2Gps($xmin, $ymin);
+ my ($Lat2, $Lon2) = &MapGpsAt($Lat1, $Lon1, $Dist, $PhiRef);
+ my ($x, $y) = &MapGps2XY($Lat2, $Lon2);
+ my $dpix = int ($x - $xmin + 0.5);
+
+ lock (%MkOsd); # until end of block
+ my $x0 = $MapSizeX / 2;
+ my $y0 = $MapSizeY / 2;
+ if ( &HomePosIsValid() )
+ {
+ ($x0, $y0) = &MapGps2XY ($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
+ }
+
+ for ($x = $xmin + $x0 % $dpix; $x < $xmax; $x +=$dpix)
+ {
+ $map_canvas->createLine ( $x, $ymin, $x, $ymax,
+ '-tags' => 'Map-Grid',
+ '-arrow' => 'none',
+ '-fill' => $Color,
+ '-width' => 1,
+ );
+ }
+
+ for ($y = $ymin + $y0 % $dpix; $y < $ymax; $y +=$dpix)
+ {
+ $map_canvas->createLine ( $xmin, $y, $xmax, $y,
+ '-tags' => 'Map-Grid',
+ '-arrow' => 'none',
+ '-fill' => $Color,
+ '-width' => 1,
+ );
+ }
+
+ # Beschriftung x
+ for ( $x = xmin + $x0 % $dpix; $x < $xmax; $x += $dpix)
+ {
+ my $ScaleX = int (($x - $x0) / $dpix * $Dist + 0.5);
+ if ( $ScaleX < 0 )
+ {
+ $ScaleX = int (($x - $x0) / $dpix * $Dist - 0.5);
+ }
+ $map_canvas->createText ( $x - 2, $y0 - 8,
+ '-tags' => 'Map-Grid',
+ '-text' => sprintf ("%d", $ScaleX),
+ '-font' => '-*-Arial-Bold-R-Normal--*-150-*',
+ '-fill' => $Color,
+ '-anchor' => 'e',
+ );
+ }
+ # Beschriftung y
+ for ( $y = ymin + $y0 % $dpix; $y < $ymax; $y += $dpix)
+ {
+ my $ScaleY = int (($y - $y0) / $dpix * $Dist + 0.5);
+ if ( $ScaleY < 0 )
+ {
+ $ScaleY = int (($y - $y0) / $dpix * $Dist - 0.5);
+ }
+ $map_canvas->createText ( $x0 + 4, $y - 8,
+ '-tags' => 'Map-Grid',
+ '-text' => sprintf ("%d", $ScaleY * -1),
+ '-font' => '-*-Arial-Bold-R-Normal--*-150-*',
+ '-fill' => $Color,
+ '-anchor' => 'w',
+ );
+ }
+
+ $map_canvas->raise('Map-Grid', 'Map');
+ }
+
+
+# Hide Grid on canvas
+sub GridHide()
+ {
+ $map_canvas->delete('Map-Grid');
+ }
+
+
+# Show Crosshair for Pause Position on canvas
+sub CrosshairShow()
+ {
+ my ($Lat, $Lon) = @_;
+
+ my ($x, $y) = &MapGps2XY ($Lat, $Lon);
+ if ( $x != $LastCrosshairX and $y != $LastCroshairY )
+ {
+ # Only update, if coords changed - CPU consuming!
+ $map_canvas->coords ('Map-Crosshair-X', 0, $y, $MapSizeX, $y);
+ $map_canvas->coords ('Map-Crosshair-Y', $x, 0, $x, $MapSizeY);
+
+ $map_canvas->raise('Map-Crosshair', 'Target');
+ }
+
+ $LastCrosshairX = $x;
+ $LastCrosshairY = $y;
+ }
+
+
+# Hide Crosshair on canvas
+sub CrosshairHide()
+ {
+ $map_canvas->lower('Map-Crosshair', 'Map'); # hide below map
+
+ $LastCrosshairX = -1;
+ $LastCrosshairY = -1;
+ }
+
+
+#
+# System Messages
+#
+
+# Init Messages for a Subsystem/timer
+sub MkMessageInit ()
+ {
+ my ($Id) = @_;
+
+ $MkMessages{$Id} = [];
+ }
+
+
+# Register message
+sub MkMessage ()
+ {
+ my ($Message, $Id) = @_;
+
+ push @{$MkMessages{$Id}}, $Message;
+ }
+
+
+# show registered messages
+sub MkMessageShow()
+ {
+ my @Messages;
+ my $MsgLines = 0;
+ my $MaxMsgLen = 0;
+
+ # Collect Messages of each category
+ foreach my $Id (keys %MkMessages)
+ {
+ foreach $i ( 0 .. $#{$MkMessages{$Id}} )
+ {
+ my $Msg = $MkMessages{$Id}[$i];
+ push @Messages, $Msg;
+
+ $MsgLines ++;
+
+ my $Len = length $Msg;
+ if ( $Len > $MaxMsgLen )
+ {
+ $MaxMsgLen = $Len;
+ }
+ }
+ }
+
+ $map_canvas->delete('Message-Balloon'); # delete old Balloon
+
+ if ( $MsgLines > 0 )
+ {
+ # draw Balloon
+ my @MsgBalloon = ( $MkPos_x , $MkPos_y,
+ $MkPos_x + 30 , $MkPos_y + 40,
+ $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 40,
+ $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 44 + $MsgLines * 20,
+ $MkPos_x + 20, $MkPos_y + 44 + $MsgLines * 20,
+ $MkPos_x + 20, $MkPos_y + 40,
+ $MkPos_x, $MkPos_y,
+ );
+
+ $map_canvas->createPolygon( @MsgBalloon,
+ '-tags' => ['Message-Balloon', 'Message-BalloonBubble'],
+ '-fill' => 'yellow',
+ '-outline' => 'yellow',
+ '-width' => 1,
+ );
+ # draw Messages
+ my $MsgLine = 1;
+ foreach my $Msg (@Messages)
+ {
+ $map_canvas->createText ( $MkPos_x + 25, $MkPos_y + 32 + $MsgLine * 20 ,
+ '-tags' => ['Message-Balloon', 'Message-BalloonText'],
+ '-text' => $Msg,
+ '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
+ '-fill' => 'blue',
+ '-anchor' => 'w',
+ );
+ $MsgLine ++;
+ }
+
+
+ $map_canvas->lower('Message-Balloon', 'MK-Arrow');
+ }
+
+ }
+
+
+# Show Balloon, when arproaching Target
+sub TargetMessageShow ()
+ {
+ $map_canvas->delete('Target-Balloon'); # delete old Balloon
+
+ if ( $OperationMode ne "Free" and $MkOsd{'TargetPos_Stat'} == 1 and $MkOsd{'TargetPosDev_Dist'} /10 < 25 )
+ {
+ my $BalloonLines = 0;
+ $ColorBalloon = "blue";
+ my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
+ my $Wp = $Waypoints[$MkOsd{'WaypointIndex'}];
+
+ # Holdtime Wp-Player Mode
+ if ( $WpPlayerHoldtime >= 0 and $PlayerWptKmlMode eq "WPT" )
+ {
+ # Holdtime
+ $ColorBalloon = 'red';
+ my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($WpPlayerHoldtime / 2 + 0.5) );
+ $map_canvas->createText ( $T_x + 25, $T_y - 40,
+ '-tags' => ['Target-Balloon', 'Target-BalloonText'],
+ '-text' => $HoldTime,
+ '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
+ '-fill' => $ColorBalloon,
+ '-anchor' => 'w',
+ );
+ $BalloonLines ++;
+ }
+
+ # Holdtime WPT-Mode
+ if ( &MkTargetReached() and $OperationMode eq "WPT" )
+ {
+ # Holdtime from MK
+ $ColorBalloon = 'red';
+ my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($MkOsd{'TargetHoldTime'} + 0.5) );
+ $map_canvas->createText ( $T_x + 25, $T_y - 40,
+ '-tags' => ['Target-Balloon', 'Target-BalloonText'],
+ '-text' => $HoldTime,
+ '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
+ '-fill' => $ColorBalloon,
+ '-anchor' => 'w',
+ );
+ $BalloonLines ++;
+ }
+
+ # Tolerance Radius Player Mode
+ if ( &MkIsWptMode() and $OperationMode eq "Play" and $PlayerWptKmlMode eq "WPT" )
+ {
+ my $WpTolerance = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'});
+ $map_canvas->createText ( $T_x + 25, $T_y - 60,
+ '-tags' => ['Target-Balloon', 'Target-BalloonText'],
+ '-text' => $WpTolerance,
+ '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
+ '-fill' => $ColorBalloon,
+ '-anchor' => 'w',
+ );
+ $BalloonLines ++;
+ }
+
+ # Tolerance WPT-Mode
+ if ( &MkIsWptMode and $OperationMode eq "WPT" )
+ {
+ my $WpTolerance = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'} );
+ $map_canvas->createText ( $T_x + 25, $T_y - 60,
+ '-tags' => ['Target-Balloon', 'Target-BalloonText'],
+ '-text' => $WpTolerance,
+ '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
+ '-fill' => $ColorBalloon,
+ '-anchor' => 'w',
+ );
+ $BalloonLines ++;
+ }
+
+ # Distance to Target
+ my $Dist = int ($MkOsd{'TargetPosDev_Dist'} /10 + 0.5);
+ $map_canvas->createText ( $T_x + 25, $T_y - 80,
+ '-tags' => ['Target-Balloon', 'Target-BalloonText'],
+ '-text' => sprintf ("%5s %3d m", "DST:", $Dist) ,
+ '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
+ '-fill' => $ColorBalloon,
+ '-anchor' => 'w',
+ );
+ $BalloonLines ++;
+
+ if ( $BalloonLines >= 1 )
+ {
+ # draw Balloon
+ my @TargetBalloon = ( $T_x , $T_y,
+ $T_x + 30, $T_y - (3 - $BalloonLines) * 20 -27,
+ $T_x + 150, $T_y - (3 - $BalloonLines) * 20 -27 ,
+ $T_x + 150, $T_y - 93,
+ $T_x + 20, $T_y - 93,
+ $T_x + 20, $T_y - (3 - $BalloonLines) * 20 -27,
+ $T_x, $T_y,
+ );
+
+ $map_canvas->createPolygon( @TargetBalloon,
+ '-tags' => ['Target-Balloon', 'Target-BalloonBubble'],
+ '-fill' => 'lightgray',
+ '-outline' => 'yellow',
+ '-width' => 1,
+ );
+ }
+
+
+ $map_canvas->lower('Target-Balloon', 'MK-Home-Line');
+ $map_canvas->lower('Target-BalloonBubble', 'Target-BalloonText');
+ }
+ }
+
+
+#
+# Airfield border
+#
+
+# Are two segments A(a1/a2), B(b1/b2) and C(c1/c2), D(d1/d2) crossing ?
+sub SegmentCross()
+ {
+ my ( $a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) = @_;
+
+ # segment C/D ist vertical, avoid div/0
+ if ( $c1 == $d1 )
+ {
+ $d1 += 0.00001;
+ }
+
+ my $n = ($b1 - $a1) * ($d2 - $c2) - ($b2 - $a2) * ($d1 - $c1);
+ if ( $n == 0.0 )
+ {
+ # AB und CD sind parallel
+ return 0;
+ }
+
+ my $s = ( ($c1 - $a1) * ($d2 - $c2) - ($c2 - $a2) * ($d1 - $c1) ) / $n;
+ my $t = ( $a1 - $c1 + $s * ($b1 - $a1) ) / ( $d1 - $c1 );
+ if ( $s >= 0.0 and $s <= 1.0 and $t >= 0.0 and $t <= 1.0 )
+ {
+ # beide Strecken kreuzen sich
+
+ # Schnittpunkt: s_x, s_y
+ my $s_x = $a1 + $s * ( $b1 - $a1 );
+ my $s_y = $a2 + $s * ( $b2 - $a2 );
+
+ return 1;
+ }
+
+ # beide Strecken kreuzen sich nicht
+ return 0;
+ }
+
+
+# How often does a segment A(a1,a2), B(b1,b2) cross the polygon?
+sub SegmentPolygonCross()
+ {
+ my ( $a1, $a2, $b1, $b2, $Polygon) = @_;
+
+ my $Cross = 0;
+ my $PolyCnt = scalar @{$Polygon};
+ my $PolyPointCnt = $PolyCnt / 2;
+
+ my $i = 0;
+ for ( $p=0; $p < $PolyPointCnt; $p++ )
+ {
+ my $c1 = ${$Polygon}[$i++];
+ my $c2 = ${$Polygon}[$i++];
+
+ if ( $i >= $PolyCnt ) { $i = 0; }
+
+ my $d1 = ${$Polygon}[$i];
+ my $d2 = ${$Polygon}[$i+1];
+
+ # map calibration offsets
+ $c1 -= $Map{'Offset_x'};
+ $c2 += $Map{'Offset_y'};
+ $d1 -= $Map{'Offset_x'};
+ $d2 += $Map{'Offset_y'};
+
+ if ( &SegmentCross($a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) )
+ {
+ $Cross ++;
+ }
+ }
+
+ return $Cross;
+ }
+
+
+# Is point A inside airfield border?
+sub IsInsideBorder()
+ {
+ my ($a1, $a2) = @_;
+
+ if ( scalar @Map{'Border'} == 0 )
+ {
+ # no border defined, always inside
+ return 1;
+ }
+
+ my $Cross = &SegmentPolygonCross (-10, -10, $a1, $a2, @Map{'Border'} );
+
+ # Ungerade Anzahl Kreuzungen: Inside
+ return ( $Cross % 2 );
+ }
+
+
+
+# Is segment A, B crossing the airfield border?
+sub IsCrossingBorder()
+ {
+ my ($a1, $a2, $b1, $b2) = @_;
+
+ if ( scalar @Map{'Border'} == 0 )
+ {
+ # no border defined, always not crossing
+ return 0;
+ }
+
+ my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
+
+ return ( $Cross > 0 );
+ }
+
+
+# How often is segment A, B crossing the airfield border?
+sub CrossingBorderCount()
+ {
+ my ($a1, $a2, $b1, $b2) = @_;
+
+ if ( scalar @Map{'Border'} == 0 )
+ {
+ # no border defined, not crossing
+ return 0;
+ }
+
+ my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
+
+ return ( $Cross );
+ }
+
+
+# check, if Target is reachable my MK
+sub IsTargetReachable()
+ {
+ my ($T_x, $T_y) = @_;
+
+ my $MkIsInside = &IsInsideBorder($MkPos_x, $MkPos_y);
+ my $TargetIsInside = &IsInsideBorder($T_x, $T_y);
+ my $MkTargetCrossingCount = &CrossingBorderCount($MkPos_x, $MkPos_y, $T_x, $T_y);
+
+ if ( ($MkIsInside and $MkTargetCrossingCount == 0 ) or
+ (! $MkIsInside and $TargetIsInside and $MkTargetCrossingCount == 1) )
+ {
+ # Target is reachable
+ return 1;
+ }
+
+ # Target is not reachable
+ return 0;
+ }
+
+
+#
+# Configuration and data-visualisation
+#
+
+# Display or Modify Hash
+sub DisplayHash()
+ {
+ my ($hrefData, $Titel, $Mode) = @_;
+
+ # $Mode: Display, Edit, Waypoint, Refresh, Heartbeat, SerialChannel, ExternControl
+
+ my $MaxRow = 20; # number or Rows in multi column view
+ my %Id;
+ my $Label;
+ my $Value;
+
+ # Neues Fenster aufmachen
+ my $popup = $main->Toplevel();
+ $popup->title($Titel);
+
+ # Frame mit den Buttons
+ my $popup_button = $popup->Frame() -> pack('-side' => 'bottom',
+ '-expand' => 'y',
+ '-anchor' => 's',
+ '-padx' => 5,
+ '-pady' => 5,
+ );
+ $popup_button->Button('-text' => 'Schließen',
+ '-command' => sub
+ {
+ if ( $Mode =~ /edit/i and $Mode =~ /waypoint/i )
+ {
+ $WaypointsModified = 1;
+ &WpRedrawLines();
+ &WpRedrawIcons();
+ }
+
+ $popup->destroy()
+ })->pack;
+
+ # Frame mit den Labels und Daten
+ my $popup_data = $popup->Frame() -> pack('-side' => 'left',
+ '-expand' => 'y',
+ '-anchor' => 'w',
+ '-padx' => 10,
+ '-pady' => 10,
+ );
+ # Labels und Daten anzeigen
+ my $Row = 0;
+ my $Col = 0;
+
+ foreach $Label ( sort keys %{$hrefData})
+ {
+ $LabelView = $Label;
+ if ( $Translate{$LabelView} ne "" )
+ {
+ $LabelView = $Translate{$LabelView};
+ }
+
+ # Label
+ $popup_data->Label ('-text' => $LabelView,
+ '-width' => 25,
+ '-anchor' => 'w',
+ ) -> grid( -row => $Row,
+ -column => $Col,
+ -padx => 10,
+ );
+ # Daten
+ if ( $Mode =~ /display/i )
+ {
+ # Display
+ if ( ref ${$hrefData}{$Label} )
+ {
+ $Text = "- can't display references -";
+ }
+ else
+ {
+ $Text = ${$hrefData}{$Label};
+ }
+
+ $Id{$Label} = $popup_data->Label ('-text' => $Text,
+ '-width' => 20,
+ '-anchor' => 'e',
+ '-relief' => 'sunken',
+ ) -> grid( -row => $Row,
+ -column => $Col + 1,
+ -padx => 10,
+ );
+ }
+ if ( $Mode =~ /edit/i )
+ {
+ # Edit
+ $Id{$Label} = $popup_data->Entry ('-textvariable' => \${$hrefData}{$Label},
+ '-exportselection' => '1',
+ '-width' => 20,
+ '-relief' => 'sunken',
+ ) -> grid( -row => $Row,
+ -column => $Col + 1,
+ -padx => 10,
+ );
+ if ( $Mode =~ /waypoint/i )
+ {
+ # einige Waypoint-Felder nicht aenderbar einstellen
+ if ( "MapX MapY Pos_Lat Pos_Lon Tag" =~ /$Label/i )
+ {
+ $Id{$Label}->configure('-state' => 'disabled', );
+ }
+ }
+ }
+
+ # multi Column wrap
+ $Row++;
+ if ( $Row > $MaxRow )
+ {
+ $Row = 0;
+ $Col += 2;
+ }
+ }
+
+
+ if ( $Mode =~ /refresh/i )
+ {
+ # Timer: 0.1s
+ $popup_data->repeat (100, sub
+ {
+ # Datenfelder alle 100ms aktualisieren
+
+ my $BgColor = 'white';
+ if ( $Mode =~ /heartbeat/i )
+ {
+ $BgColor = 'red';
+ if ( &MkOsdIsValid() )
+ {
+ # gültige daten vom MK
+ $BgColor = 'white';
+ }
+ }
+
+ if ( $Mode =~ /serialchannel/i )
+ {
+ $BgColor = 'red';
+ if ( $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i )
+ {
+ # senden aktiv
+ $BgColor = 'white';
+ }
+ }
+
+ if ( $Mode =~ /externcontrol/i )
+ {
+ $BgColor = 'red';
+ if ( $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i )
+ {
+ # senden aktiv
+ $BgColor = 'white';
+ }
+ }
+
+ foreach $Label ( sort keys %{$hrefData} )
+ {
+ # Eingebbare Waypoint-Felder nicht aktualisieren
+ if ( ! ($Mode =~ /waypoint/i and
+ "Event_Flag Heading ToleranceRadius HoldTime Pos_Alt" =~ /$Label/i) )
+ {
+ $Id{$Label}->configure('-text' => ${$hrefData}{$Label},
+ '-background' => "$BgColor",
+ );
+ }
+ }
+ });
+ }
+
+ return 0;
+ }
+
+
+# Konfigurations-Hash (aus XML-Datei) im Popup-Fenster editieren
+sub Configure()
+ {
+ my ($CfgFile, $hrefCfg, $Mode) = @_;
+
+ # get a copy of Cfg-Hash for editing
+ my $CfgEdit = &CopyHash($hrefCfg);
+
+ # Neues Fenster aufmachen
+ my $popup = $main->Toplevel();
+ $popup->title("Einstellungen - $CfgFile");
+
+ # Display data in a notebook widget
+ my $book = $popup->NoteBook()->grid(-row => 0,
+ -column => 0,
+ -columnspan => 4,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+ # Show data
+ &ConfigureShow($book, $CfgEdit);
+
+ # Button: OK
+ $popup->Button('-text' => 'OK',
+ '-width' => '10',
+ '-command' => sub
+ {
+ # Save and activate config
+ &ConfigureSave( $CfgFile, $hrefCfg, $CfgEdit);
+
+ $popup->destroy();
+
+ } )->grid(-row => 1,
+ -column => 0,
+ -sticky => 'w',
+ -padx => 15,
+ -pady => 5,
+ );
+
+ # Button: Apply
+ $popup->Button('-text' => $Translate{'Apply'},
+ '-width' => '10',
+ '-command' => sub
+ {
+ # Save and activate config
+ &ConfigureSave( $CfgFile, $hrefCfg, $CfgEdit);
+
+ } )->grid(-row => 1,
+ -column => 1,
+ -sticky => 'w',
+ -padx => 15,
+ -pady => 5,
+ );
+
+
+ # Button: Abort
+ $popup->Button('-text' => $Translate{'Abort'},
+ '-width' => '10',
+ '-command' => sub { $popup->destroy() },
+ )->grid(-row => 1,
+ -column => 2,
+ -sticky => 'w',
+ -padx => 15,
+ -pady => 5,
+ );
+
+ #
+ # special handling for "Config" configuration
+ #
+ if ( $Mode =~ /CONFIG/i )
+ {
+ $popup->Label ('-text' => $Translate{'RestartRequired'},
+ '-anchor' => 'w',
+ '-foreground' => 'red',
+ )->grid(-row => 1,
+ -column => 3,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+ }
+
+
+ #
+ # special handling for "Event" configuration
+ #
+ if ( $Mode =~ /EVENT/i )
+ {
+
+ # notebook must have at least one tab
+ if (scalar $book->pages() == 0 )
+ {
+ # create new record in hash
+ my $NewEvent = sprintf ("Event%d", scalar $book->pages() + 1);
+ &EventInit($NewEvent, $CfgEdit);
+
+ # Display new event
+ &ConfigureShow($book, $CfgEdit);
+ $book->raise($NewEvent);
+ }
+
+
+ # Menu bar (New, Delete, Rename)
+
+ my $menu_bar = $popup->Menu;
+ $popup->optionAdd("*tearOff", "false");
+ $popup->configure ('-menu' => $menu_bar);
+
+ my $menu_event = $menu_bar->cascade('-label' => $Translate{'Event'});
+
+ #
+ # New Event
+ #
+ $menu_event->command('-label' => $Translate{'EventNew'},
+ '-command' => sub
+ {
+ # Event Name in neuem Fenster abfragen
+ my $popup_new = $popup->Toplevel();
+ $popup_new->title("Event - $Translate{'EventNew'}");
+
+ $popup_new->Label (-text => $Translate{'EventNewName'},
+ -width => 20,
+ -anchor => 'w',
+ )->grid (-row => 0,
+ -column => 0,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+
+ my $NewEvent = sprintf ("Event%d", scalar $book->pages() + 1);
+ $popup_new->Entry ( -textvariable => \$NewEvent,
+ -exportselection => '1',
+ -width => 40,
+ -relief => 'sunken',
+ )->grid (-row => 0,
+ -column => 1,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+ # Button: OK
+ $popup_new->Button('-text' => "OK",
+ '-width' => '10',
+ '-command' => sub
+ {
+ # create new record in hash
+ $NewEvent = &EventnameAdjust($NewEvent);
+ &EventInit($NewEvent, $CfgEdit);
+
+ # Display new event
+ &ConfigureShow($book, $CfgEdit);
+ $book->raise($NewEvent);
+
+ $popup_new->destroy();
+ } )->grid (-row => 1,
+ -column => 0,
+ -sticky => 'w',
+ -padx => 20,
+ -pady => 5,
+ );
+
+ # Button: Abort
+ $popup_new->Button('-text' => $Translate{'Abort'},
+ '-width' => '10',
+ '-command' => sub
+ {
+ $popup_new->destroy()
+ } )->grid (-row => 1,
+ -column => 1,
+ -sticky => 'e',
+ -padx => 20,
+ -pady => 5,
+ );
+ });
+
+ #
+ # Rename Event
+ #
+ $menu_event->command('-label' => $Translate{'EventRename'},
+ '-command' => sub
+ {
+ # Event Name in neuem Fenster abfragen
+ my $popup_rename = $popup->Toplevel();
+ $popup_rename->title("Event - $Translate{'EventRename'}");
+
+ $popup_rename->Label (-text => $Translate{'EventName'},
+ -width => 20,
+ -anchor => 'w',
+ )->grid (-row => 0,
+ -column => 0,
+ -sticky => 'e',
+ -padx => 5,
+ -pady => 5,
+ );
+
+ my $CurrentEvent = $book->raised;
+ $popup_rename->Entry ( -textvariable => \$CurrentEvent,
+ -exportselection => '1',
+ -width => 40,
+ -relief => 'sunken',
+ -state => 'disabled',
+ )->grid (-row => 0,
+ -column => 1,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+
+ $popup_rename->Label (-text => $Translate{'EventNewName'},
+ -width => 20,
+ -anchor => 'w',
+ )->grid (-row => 1,
+ -column => 0,
+ -sticky => 'e',
+ -padx => 5,
+ -pady => 5,
+ );
+
+ my $NewEvent = sprintf ("Event%d", scalar $book->pages() + 1);
+ $popup_rename->Entry ( -textvariable => \$NewEvent,
+ -exportselection => '1',
+ -width => 40,
+ -relief => 'sunken',
+ )->grid (-row => 1,
+ -column => 1,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+
+ # Button: OK
+ $popup_rename->Button('-text' => "OK",
+ '-width' => '10',
+ '-command' => sub
+ {
+ $NewEvent = &EventnameAdjust($NewEvent);
+
+ # create new record in hash
+ $CfgEdit->{$NewEvent} = $CfgEdit->{$CurrentEvent};
+ delete $CfgEdit->{$CurrentEvent}
+
+ # Display events again
+ &ConfigureShow($book, $CfgEdit);
+
+ $book->raise($NewEvent);
+
+ $popup_rename->destroy();
+ } )->grid (-row => 2,
+ -column => 0,
+ -sticky => 'e',
+ -padx => 20,
+ -pady => 5,
+ );
+
+ # Button: Abort
+ $popup_rename->Button('-text' => $Translate{'Abort'},
+ '-width' => '10',
+ '-command' => sub
+ {
+ $popup_rename->destroy()
+ } )->grid (-row => 2,
+ -column => 1,
+ -sticky => 'w',
+ -padx => 20,
+ -pady => 5,
+ );
+
+ });
+
+ #
+ # Copy Event
+ #
+ $menu_event->command('-label' => $Translate{'EventCopy'},
+ '-command' => sub
+ {
+ # Event Name in neuem Fenster abfragen
+ my $popup_copy = $popup->Toplevel();
+ $popup_copy->title("Event - $Translate{'EventCopy'}");
+
+ my $CurrentEvent = $book->raised;
+ my $CopyEvent = sprintf ("Event%d", scalar $book->pages() + 1);
+
+ $popup_copy->Label (-text => $Translate{'EventName'},
+ -width => 20,
+ -anchor => 'w',
+ )->grid (-row => 0,
+ -column => 0,
+ -sticky => 'e',
+ -padx => 5,
+ -pady => 5,
+ );
+
+ $popup_copy->Entry ( -textvariable => \$CurrentEvent,
+ -exportselection => '1',
+ -width => 40,
+ -relief => 'sunken',
+ -state => 'disabled',
+ )->grid (-row => 0,
+ -column => 1,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+
+ $popup_copy->Label (-text => $Translate{'EventNewName'},
+ -width => 20,
+ -anchor => 'w',
+ )->grid (-row => 1,
+ -column => 0,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+
+
+ $popup_copy->Entry ( -textvariable => \$CopyEvent,
+ -exportselection => '1',
+ -width => 40,
+ -relief => 'sunken',
+ )->grid (-row => 1,
+ -column => 1,
+ -sticky => 'w',
+ -padx => 5,
+ -pady => 5,
+ );
+ # Button: OK
+ $popup_copy->Button('-text' => "OK",
+ '-width' => '10',
+ '-command' => sub
+ {
+ $CopyEvent = &EventnameAdjust($CopyEvent);
+
+ # copy hash
+ $CfgEdit->{$CopyEvent} = {%{$hrefCfg->{$CurrentEvent}}};
+
+ # Display new event
+ &ConfigureShow($book, $CfgEdit);
+ $book->raise($CopyEvent);
+
+ $popup_copy->destroy();
+ } )->grid (-row => 2,
+ -column => 0,
+ -sticky => 'w',
+ -padx => 20,
+ -pady => 5,
+ );
+
+ # Button: Abort
+ $popup_copy->Button('-text' => $Translate{'Abort'},
+ '-width' => '10',
+ '-command' => sub
+ {
+ $popup_copy->destroy()
+ } )->grid (-row => 2,
+ -column => 1,
+ -sticky => 'e',
+ -padx => 20,
+ -pady => 5,
+ );
+ });
+
+ #
+ # Delete event
+ #
+ $menu_event->command('-label' => $Translate{'EventDelete'},
+ '-command' => sub
+ {
+ my $CurrentBook = $book->raised;
+
+ # delet event in Cfg-Hash
+ delete $CfgEdit->{$CurrentBook};
+
+ # Display events again
+ &ConfigureShow($book, $CfgEdit);
+ });
+
+ #
+ # Export current Event
+ #
+ $menu_event->command('-label' => $Translate{'EventExport'},
+ '-command' => sub
+ {
+ my $XmlFile = $popup->getSaveFile('-defaultextension' => ".xml",
+ '-filetypes' =>
+ [['Event', '.xml' ],
+ ['All Files', '*', ],
+ ],
+ '-initialdir' => "event",
+ '-title' => $Translate{'EventExport'},
+ );
+ if ( $XmlFile ne "" )
+ {
+ my %ExportCfg;
+ my $CurrentEvent = $book->raised;
+
+ # copy and quote event
+ foreach $key (keys %{$CfgEdit->{$CurrentEvent}})
+ {
+ my $Line = $CfgEdit->{$CurrentEvent}->{$key};
+ $ExportCfg->{$key} = &QuoteXML($Line);
+ }
+
+ # Event in XML-Datei speichern
+ &XMLout ($ExportCfg, # save quoted hash
+ 'OutputFile' => $XmlFile,
+ 'AttrIndent' => '1',
+ 'RootName' => 'mkcockpit-Event',
+ 'NoEscape' => '1',
+ );
+ }
+
+ });
+
+ #
+ # Import XML to current Event
+ #
+ $menu_event->command('-label' => $Translate{'EventImport'},
+ '-command' => sub
+ {
+ my $XmlFile = $popup->getOpenFile(-defaultextension => ".xml",
+ -filetypes =>
+ [['Event', '.xml' ],
+ ['All Files', '*', ],
+ ],
+ -initialdir => "event",
+ -title => $Translate{'EventImport'},
+ );
+ if ( -f $XmlFile )
+ {
+ my $CurrentEvent = $book->raised;
+ my $ImportCfg = XMLin($XmlFile);
+
+ # copy event
+ foreach $key (keys %{$ImportCfg})
+ {
+ $CfgEdit->{$CurrentEvent}->{$key} = $ImportCfg->{$key};
+ }
+
+ # deactivate Event
+ $CfgEdit->{$CurrentEvent}->{'Active'} = "NO";
+ }
+ });
+ }
+ }
+
+
+# Copy a Cfg-Hash including real copy of hash-references
+sub CopyHash()
+ {
+ my ($hrefCfg) = @_;
+
+ my $CfgCopy = {%{$hrefCfg}};
+ foreach $key (keys %{$hrefCfg})
+ {
+ if ( ref $hrefCfg->{$key} )
+ {
+ $CfgCopy->{$key} = {%{$hrefCfg->{$key}}};
+ }
+ }
+ return $CfgCopy;
+ }
+
+
+# Initialize a new event
+sub EventInit()
+ {
+ my ($EventName, $Cfg) = @_;
+
+ $Cfg->{$EventName}->{'Active'} = "no";
+ $Cfg->{$EventName}->{'Action'} = "";
+ $Cfg->{$EventName}->{'ActionElse'} = "";
+ $Cfg->{$EventName}->{'Condition'} = "";
+ $Cfg->{$EventName}->{'Delay'} = "";
+ $Cfg->{$EventName}->{'Repeat'} = "";
+ $Cfg->{$EventName}->{'RepeatElse'} = "";
+ $Cfg->{$EventName}->{'Description'} = "";
+ $Cfg->{$EventName}->{'Trigger'} = "TRUE";
+ }
+
+
+# Event-Name XML konform anpassen
+sub EventnameAdjust()
+ {
+ my ($Name) = @_;
+
+ $Name =~ s/\W/_/g;
+ if ( substr ($Name, 0, 1) =~ /\d/ )
+ {
+ substr ($Name, 0, 1) = "_";
+ }
+
+ return $Name;
+ }
+
+
+# Reiter mit Konfigurationsdaten anzeigen
+sub ConfigureShow()
+ {
+ my ($book, $CfgEdit) = @_;
+
+ # delete all existing tabs in notebook
+ foreach my $Tab ($book->pages)
+ {
+ $book->delete($Tab);
+ }
+
+ # jede Sektion in eigenem Tab anzeigen
+ foreach $key (sort keys %{$CfgEdit})
+ {
+ if ( ! ref $CfgEdit->{$key} )
+ {
+ next;
+ }
+
+ my $TabLabel = "$key";
+ if ( $Translate{$key} ne "" )
+ {
+ $TabLabel = $Translate{$key};
+ }
+
+ my $Tab = $book->add( "$key",
+ -label => "$TabLabel",
+ -wraplength => "75",
+ );
+
+ # Frame for label and data
+ my $popup_cfg = $Tab->Frame() -> pack('-anchor' => 'w',
+ '-padx' => 5,
+ '-pady' => 5,
+ );
+
+ # Eingabefelder/Optionmenu/Fileselection mit Daten anzeigen
+ $Row = 0;
+ foreach $Entry ( sort keys %{$CfgEdit->{$key}})
+ {
+ # Label
+ my $Label = $Entry;
+ if ( $Translate{$Label} ne "" )
+ {
+ $Label = $Translate{$Label};
+ }
+
+ $popup_cfg->Label (-text => $Label,
+ -width => 35,
+ -anchor => 'w',
+ )->grid (-row => $Row,
+ -column => 0,
+ );
+ #
+ # Combo Box with optiones defined in libcfgopt.pl
+ #
+ if ( defined $CfgOpt{$Entry}[0] )
+ {
+ my $cbo = $popup_cfg->BrowseEntry( -label => "",
+ -variable => \$CfgEdit->{$key}->{$Entry},
+ -width => 37,
+ -relief => 'sunken'
+ )->grid (-row => $Row,
+ -column => 1,
+ -columnspan => 2,
+ -sticky => 'w',
+ );
+ # add options
+ $cbo->insert("end", @{ $CfgOpt{$Entry} });
+ }
+
+ #
+ # File selection, if defined in libcfgopt.pl
+ #
+ elsif ( defined $CfgFile{$Entry} )
+ {
+ # a) Text entry
+ my $TextEntry = $popup_cfg->Entry ( -textvariable => \$CfgEdit->{$key}->{$Entry},
+ -exportselection => '1',
+ -width => 37,
+ -relief => 'sunken',
+ )->grid (-row => $Row,
+ -column => 1,
+ -columnspan => 1,
+ -sticky => 'e',
+ );
+ # b) button with file selection dialog
+ my $Mode = $CfgFile{$Entry};
+ $popup_cfg->Button('-text' => '>',
+ '-width' => 1,
+ '-command' => sub
+ {
+ my $File = $popup_cfg->getOpenFile( '-title' => $Label );
+ if ($File ne "" )
+ {
+ if ( $Mode =~ /Filename/i )
+ {
+ $File = substr ($File, rindex ($File, '/') +1 );
+ }
+ elsif ( $Mode =~ /Path/i )
+ {
+ # nothing to do
+ }
+
+ # show selection in text entry
+ $TextEntry->delete (0, length $TextEntry->get );
+ $TextEntry->insert (0, $File);
+ }
+ } )->grid (-row => $Row,
+ -column => 2,
+ -sticky => 'e',
+ );
+ }
+
+ #
+ # Multiline Text widget, if defined in libcfgopt.pl
+ #
+ elsif ( defined $CfgText{$Entry} )
+ {
+ # a) Text entry
+
+ my $State = 'normal';
+ my $NumLines = grep /\n/, $CfgEdit->{$key}->{$Entry};
+ if ( $NumLines > 0 )
+ {
+ $State = 'disabled';
+ }
+ my $TextEntry = $popup_cfg->Entry ( -textvariable => \$CfgEdit->{$key}->{$Entry},
+ -exportselection => '1',
+ -state => $State,
+ -width => 37,
+ -relief => 'sunken',
+ )->grid (-row => $Row,
+ -column => 1,
+ -columnspan => 1,
+ -sticky => 'e',
+ );
+
+ # b) button with multiline Text-Edit dialog
+ my ($Width, $Height) = split /;/, $CfgText{$Entry};
+ my $Title = "Edit: $key -> $Entry";
+ my $refVariable = \$CfgEdit->{$key}->{$Entry};
+
+ $popup_cfg->Button('-text' => '>',
+ '-width' => 1,
+ '-command' => sub
+ {
+ # popup mit Text-Widget
+
+ my $popup_text = $popup_cfg->Toplevel();
+ $popup_text->title($Title);
+
+ my $text = $popup_text->Scrolled( "Text",
+ -height => $Height,
+ -width => $Width,
+ -wrap => 'none',
+ )->pack();
+
+ $text->insert('end', $$refVariable);
+
+ # OK Button
+ $popup_text->Button('-text' => 'OK',
+ '-width' => 10,
+ '-command' => sub
+ {
+ # Text uebernehmen
+ $$refVariable = $text->get("1.0", "end");
+ chomp $$refVariable;
+
+ $popup_text->destroy()
+
+ } )->pack ('-side' => 'left',
+ '-expand' => 'y',
+ '-anchor' => 's',
+ '-padx' => 5,
+ '-pady' => 5,
+ );
+
+ # Anwenden Button
+ $popup_text->Button('-text' => $Translate{'Apply'},
+ '-width' => 10,
+ '-command' => sub
+ {
+ # Text uebernehmen
+ $$refVariable = $text->get("1.0", "end");
+ chomp $$refVariable;
+
+ } )->pack ('-side' => 'left',
+ '-expand' => 'y',
+ '-anchor' => 's',
+ '-padx' => 5,
+ '-pady' => 5,
+ );
+
+ # Abort Button
+ $popup_text->Button('-text' => $Translate{'Abort'},
+ '-width' => '10',
+ '-command' => sub
+ {
+ $popup_text->destroy();
+
+ } )->pack ('-side' => 'left',
+ '-expand' => 'y',
+ '-anchor' => 's',
+ '-padx' => 5,
+ '-pady' => 5,
+ );
+ } )->grid (-row => $Row,
+ -column => 2,
+ -sticky => 'e',
+ );
+ }
+
+ #
+ # Text entry
+ #
+ else
+ {
+ $popup_cfg->Entry ( -textvariable => \$CfgEdit->{$key}->{$Entry},
+ -exportselection => '1',
+ -width => 40,
+ -relief => 'sunken',
+ )->grid (-row => $Row,
+ -column => 1,
+ -columnspan => 2,
+ -sticky => 'e',
+ );
+ }
+
+ # next Variable in next row
+ $Row ++;
+ }
+ }
+ }
+
+
+# Quote for output to XML-file
+sub QuoteXML()
+ {
+ my ($Line) = @_;
+
+ $Line =~ s/\&/\&amp;/g;
+ $Line =~ s/'/\&apos;/g;
+ $Line =~ s/</\&lt;/g;
+ $Line =~ s/>/\&gt;/g;
+ $Line =~ s/"/\&quot;/g;
+ $Line =~ s/\n/\&#10;/g;
+
+ return $Line;
+ }
+
+
+# Activate and save Config to file
+sub ConfigureSave()
+ {
+ my ($CfgFile, $hrefCfg, $CfgEdit) = @_;
+
+ # set new timestamp
+ my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
+ my $TimeStamp = sprintf ("%04d%02d%02d-%02d%02d%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
+ $CfgEdit->{'CreationDate'} = $TimeStamp;
+
+ # set MkCockpit Version
+ $CfgEdit->{'Version'} = $Version{'mkcockpit.pl'};
+
+ # empty original hash
+ foreach $key (keys %{$hrefCfg})
+ {
+ delete $hrefCfg->{$key};
+ }
+
+ # Build %CfgXml with XML-quoting
+ my $CfgXml = {};
+
+ # then copy %CfgEdit back to $hrefCfg.
+ foreach $key (keys %{$CfgEdit})
+ {
+ if ( ref $CfgEdit->{$key} )
+ {
+ # Reference
+ foreach $val (keys %{$CfgEdit->{$key}})
+ {
+ my $Line = $CfgEdit->{$key}->{$val};
+ $hrefCfg->{$key}->{$val} = $Line;
+ $CfgXml->{$key}->{$val} = &QuoteXML($Line);
+ }
+ }
+ else
+ {
+ # Scalar
+ my $Line = $CfgEdit->{$key};
+ $hrefCfg->{$key} = $Line;
+ $CfgXml->{$key} = &QuoteXML($Line);
+ }
+ }
+
+ # Cfg in XML-Datei speichern
+ &XMLout ($CfgXml, # save quoted hash
+ 'OutputFile' => $CfgFile,
+ 'AttrIndent' => '1',
+ 'RootName' => 'mkcockpit-Config',
+ 'NoEscape' => '1',
+ );
+ }
+
+1;
+
+__END__