Subversion Repositories Projects

Rev

Blame | Last modification | View Log | RSS feed

#!/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
#
###############################################################################

$Version{'libmkcockpit.pl'} = "0.2.5 - 2009-08-09";


# 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 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 ($Wp_x, $Wp_y) = @_;

    # save Wp-Hash in Waypoint-Array
    my $Wp = {};        
    my $Tag = sprintf "Waypoint-%d.%d", time, int (rand(9)) ;   # kind of unique Tag for this Wp
    ($Lat, $Lon) = &MapXY2Gps($Wp_x, $Wp_y);
    $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;
    }


# Load @Waypoints from file
sub WpLoadFile ()
    {
    my ($WpFile) = @_;

    # 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 );
            }

        # 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;
        }
    }


# Safe @Waypoints to file
sub WpSaveFile()
    {
    my ($WpFile) = @_;

    # 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"
                 );
               
        usleep (150000)  # NC Zeit zum Verarbeiten geben
        }

    $MkSendWp = 0;  # normale OSD/Debug Abfragefrequenz wird automatisch im 5s Timer wieder eingestellt

    # gray 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 = 48;
            $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', 'Fox');              # waypoint below Fox
        $map_canvas->lower('WaypointNumber', 'Waypoint');   # waypoint-number below waypoint
        }
    }

       
# Redraw Waypoint connectors
sub WpRedrawLines()
    {
    if ( $PlayerWptKmlMode =~ /WPT/i )
        {
        # 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->lower('Waypoint-Connector', 'Waypoint');   # connector below waypoint
        }
    }


# 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', 'Fox');
    }


# 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 ($ParIndex) = @_;

    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 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',
                             );
    }


# 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 ( &MkOsdIsValid() )
        {
        # Gueltige OSD Daten
        if ( $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'CurPos_Stat'} == 1 )
            {
            $PlayerPause_Lon = $MkOsd{'CurPos_Lon'};
            $PlayerPause_Lat = $MkOsd{'CurPos_Lat'};
            }
        }
    }


# 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',
                             );
    }


# 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();
    }


# 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 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' => "" );
    }


#
# 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 )
            {
            # 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

    my %Id;
    my $Label;
    my $Value;

    # Neues Fenster aufmachen
    my $popup = $main->Toplevel();
    $popup->title($Titel);
       
    # 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
    my $popup_label = $popup->Frame() -> pack('-side' => 'left',
                                              '-expand' => 'y',
                                              '-anchor' => 'w',
                                              '-padx' => 10,
                                              '-pady' => 10,
                                              );
    # Labels anzeigen                    
    foreach $Label ( sort keys %{$hrefData})
        {
        if ( $Translate{$Label} ne "" )
            {
            $Label = $Translate{$Label};
            }
                       
        $popup_label->Label ('-text' => $Label,
                             '-width' => 25,
                             '-anchor' => 'w',
                             ) -> pack();
        }
       
    # Frame mit den Daten
    my $popup_values = $popup->Frame() -> pack('-side' => 'left',
                                               '-expand' => 'y',
                                               '-anchor' => 'w',
                                               '-padx' => 10,
                                               '-pady' => 10,
                                               );
    # Daten anzeigen
    foreach $Value ( sort keys %{$hrefData})
        {                              
        if ( $Mode =~ /display/i )
            {
            # Display
            $Id{$Value} = $popup_values->Label ('-text' => ${$hrefData}{$Value},
                                                '-width' => 20,
                                                '-anchor' => 'e',
                                                '-relief' => 'sunken',
                                                ) -> pack();
            }
        if ( $Mode =~ /edit/i )
            {
            # Edit
            $Id{$Value} = $popup_values->Entry ('-textvariable' => \${$hrefData}{$Value},
                                                '-exportselection' => '1',
                                                '-width' => 20,
                                                '-relief' => 'sunken',
                                               ) -> pack();
            if ( $Mode =~ /waypoint/i )
                {
                # einige Waypoint-Felder nicht aenderbar einstellen
                if ( "MapX MapY Pos_Lat Pos_Lon Tag" =~ /$Value/i )
                    {
                    $Id{$Value}->configure('-state' => 'disabled', );
                    }
                }      
            }
        }

    if ( $Mode =~ /refresh/i )
        {
        # Timer: 0.1s
        $popup_values->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';
                    }
                }
               
            foreach $Value ( sort keys %{$hrefData} )
                {
                # Eingebbare Waypoint-Felder nicht aktualisieren
                if ( ! ($Mode =~ /waypoint/i  and
                        "Event_Flag Heading ToleranceRadius HoldTime Pos_Alt" =~ /$Value/i) )
                    {                
                    $Id{$Value}->configure('-text' => ${$hrefData}{$Value},
                                           '-background' => "$BgColor",
                                          );
                    }
                }
            });
        }

    return 0;
    }



# Konfigurationsdatei mkcockpit.xml im Popup-Fenster editieren
sub Configure()
    {

    # Copy Cfg-Hash for editing
    my $CfgEdit = {%{$Cfg}};
    foreach $key (keys %{$Cfg})
        {
        if ( ref $Cfg->{$key} )
            {
            $CfgEdit->{$key} = {%{$Cfg->{$key}}};
            }
        }

    # Neues Fenster aufmachen
    my $popup = $main->Toplevel();
    $popup->title("Einstellungen - $XmlConfigFile");

    # jede Sektion in einem Tab anzeigen
    my $book = $popup->NoteBook()->pack( -fill=>'both', -expand=>1 );
    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", );

        # Frame fuer Buttons
        my $book_button = $Tab->Frame() -> pack('-side' => 'bottom',
                                                '-expand' => 'y',
                                                '-anchor' => 's',
                                                '-padx' => 5,
                                                '-pady' => 5,
                                                );

        $book_button->Button('-text'    => 'OK',
                             '-width' => '10',
                             '-command' => sub
            {
            # Copy back CfgEdit-Hash
            $Cfg = {%{$CfgEdit}};
            foreach $key (keys %{$CfgEdit})
                {
                if ( ref $CfgEdit->{$key} )
                    {
                    $Cfg->{$key} = {%{$CfgEdit->{$key}}};
                    }
                }

            # 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);
            $Cfg->{'CreationDate'} = $TimeStamp;

            # Cfg in mkcockpit.xml speichern
            &XMLout ($Cfg,
                     'OutputFile' => $XmlConfigFile,
                     'AttrIndent' => '1',
                     'RootName' => 'mkcockpit-Config',
                    );

            $popup->destroy();
            } )->pack ('-side' => 'left',
                       '-expand' => 'y',
                       '-anchor' => 's',
                       '-padx' => 5,
                       '-pady' => 5,
                      );
        $book_button->Button('-text'    => $Translate{'Abort'},
                             '-width' => '10',
                             '-command' => sub { $popup->destroy() },
                            )->pack ('-side' => 'left',
                                     '-expand' => 'y',
                                     '-anchor' => 's',
                                     '-padx' => 5,
                                     '-pady' => 5,
                                     );
        $book_button->Label ('-text' => $Translate{'RestartRequired'},
                             '-anchor' => 'w',
                             '-foreground' => 'red',
                            ) ->pack ('-side' => 'left',
                                      '-expand' => 'y',
                                      '-anchor' => 's',
                                      '-padx' => 10,
                                      '-pady' => 5,
                                      );

        # Frame mit den Labels
        my $popup_label = $Tab->Frame() -> pack('-side' => 'left',
                                                '-expand' => 'y',
                                                '-anchor' => 'w',
                                                '-padx' => 10,
                                                '-pady' => 10,
                                                );
        # Labels anzeigen                        
        foreach $Label ( sort keys %{$CfgEdit->{$key}})
            {
            if ( $Translate{$Label} ne "" )
                {
                $Label = $Translate{$Label};
                }
               
            $popup_label->Label ('-text' => $Label,
                                 '-width' => 35,
                                 '-anchor' => 'w',
                                ) -> pack();
            }
 
        # Frame mit den Daten
        my $popup_values = $Tab->Frame() -> pack('-side' => 'left',
                                                 '-expand' => 'y',
                                                 '-anchor' => 'w',
                                                 '-padx' => 10,
                                                 '-pady' => 10,
                                                 );
        # Eingabefelder mit Daten anzeigen
        foreach $Value ( sort keys %{$CfgEdit->{$key}})
            {                          
            $popup_values->Entry ('-textvariable' => \$CfgEdit->{$key}->{$Value},
                                  '-exportselection' => '1',
                                  '-width' => 30,
                                  '-relief' => 'sunken',
                                 ) -> pack();  
            }
        }
    }


1;

__END__