Subversion Repositories Projects

Rev

Blame | Last modification | View Log | RSS feed

#!/usr/bin/perl
#!/usr/bin/perl -d:ptkdb

###############################################################################
#
# mkcockpit.pl -  MK Mission Cockpit - GUI
#
# 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-02-20 0.0.1 rw created
# 2009-04-01 0.1.0 rw RC1
# 2009-04-16 0.1.1 rw Bugfix, ALT= average of airsensor and Sat
# 2009-05-14 0.2.0 rw Waypoint Player
# 2009-05-17 0.2.1 rw Cursor-Steuerung fuer WP-Player. Cmdline-Parameter "-geometry"
# 2009-07-18 0.2.2 rw DE/EN multinational
#                     Target-Balloon with Distance, Tolerance and Holdtime
#                     Fix footprint "Ausreiser"
#                     JPEG and PNG maps supported
#                     Player for KML Files
# 2009-07-26 0.2.3 rw System Messages Balloon
# 2009-07-31 0.2.4 rw ODO Kilometerzähler
#                     Enter WP-Number from Keyboard
#                     Random WP-Player (Waypoint and Map)
#                     Check Airfield Border
#                     Draw Calibration points on map
#
###############################################################################

$Version = "0.2.4 - 2009-07-31";
 
use threads;            # http://search.cpan.org/~jdhedden/threads-1.72/threads.pm
                        # http://perldoc.perl.org/threads.html
use threads::shared;    # http://search.cpan.org/~jdhedden/threads-shared-1.28/shared.pm
use Thread::Queue;      # http://search.cpan.org/dist/Thread-Queue-2.11/lib/Thread/Queue.pm
use Tk;
use Tk::Balloon;
use Tk::Dialog;
use Tk::Notebook;
use Tk::JPEG;           # http://search.cpan.org/~srezic/Tk-804.028/JPEG/JPEG.pm
use Tk::PNG;            # http://search.cpan.org/~srezic/Tk-804.028/PNG/PNG.pm
use Math::Trig;
use Time::HiRes qw(usleep);  # http://search.cpan.org/~jhi/Time-HiRes-1.9719/HiRes.pm
use XML::Simple;             # http://search.cpan.org/dist/XML-Simple-2.18/lib/XML/Simple.pm

# change working directory to program path
my $Cwd = substr ($0, 0, rindex ($0, "mkcockpit.pl"));
chdir $Cwd;

# set path for local Perl libs
push @INC, $Cwd . "perl/lib", $Cwd . "perl/site/lib";

# Version setting
share (%Version);
$Version{'mkcockpit.pl'}  = $Version;

# Read configuration

$XmlConfigFile = "mkcockpit.xml";
$Cfg = XMLin($XmlConfigFile);

require "track.pl";     # Tracking antenna
require "mkcomm.pl";    # MK communication
require "logging.pl";   # CSV and GPX Logging
require "geserver.pl";  # Google Earth Server
require "$Cfg->{'map'}->{'MapDir'}/map.pl";   # Landkarte
require "libmap.pl";    # map subs
require "translate.pl"; # Übersetzungstable

# Commandline parameter
my %CmdLine = @ARGV;

# Thread fuer Kommunikation mit MK starten
# Output: %MkOsd, %MkTarget, %MkNcDebug, %Mk
# Input:  Thread-Queue: $MkSendQueue
$mk_thr = threads->create (\&MkCommLoop) -> detach();

# Start Logging Thread
$log_thr = threads->create (\&MkLogLoop) -> detach();

# Start GoogleEarth Thread
$ge_thr = threads->create (\&GeServer) -> detach();

# Aktuell gültige Karte
my %Map = %{$Maps{'Current'}};
       
# Hauptfenster
my $main = new MainWindow;
$main->title ("MK Mission Cockpit - Version $Version");

if ( $CmdLine{'-geometry'} ne "" )
    {
    $main->geometry( "$CmdLine{'-geometry'}" );
    }

#-----------------------------------------------------------------
# Menu
#-----------------------------------------------------------------

# Menu bar
my $menu_bar = $main->Menu;
$main->optionAdd("*tearOff", "false");
$main->configure ('-menu' => $menu_bar);

my $menu_file = $menu_bar->cascade('-label' => $Translate{'File'});
    $menu_file->command('-label' => $Translate{'Preferences'},
                        '-command' => [\&Configure],
                       );
    $menu_file->separator;                                     
    $menu_file->command('-label' => $Translate{'Exit'},
                        '-command' => sub{exit(0)},
                        );

my $menu_debug = $menu_bar->cascade(-label => $Translate{'Debug'});
    $menu_debug->command('-label' => $Translate{'NcOsdDataset'},
                         '-command' => [\&DisplayHash, \%MkOsd, $Translate{'NcOsdDataset'}, "Display Refresh Heartbeat"],
                        );
    $menu_debug->command('-label' => $Translate{'NcTargetDataset'},
                         '-command' => [\&DisplayHash, \%MkTarget, $Translate{'NcTargetDataset'}, "Display Refresh Heartbeat"],
                        );
    $menu_debug->command('-label' => $Translate{'NcDebugDataset'},
                         '-command' => [\&DisplayHash, \%MkNcDebug, $Translate{'NcDebugDataset'}, "Display Refresh Heartbeat"],
                                        );             
    $menu_debug->command('-label' => $Translate{'NcOther'},
                         '-command' => [\&DisplayHash, \%Mk, $Translate{'NcOther'}, "Display Refresh Heartbeat"],
                                        );
    $menu_debug->separator;                                    
    $menu_debug->command('-label' => $Translate{'TrackingDebugDataset'},
                         '-command' => [\&DisplayHash, \%MkTrack, $Translate{'TrackingDebugDataset'}, "Display Refresh Heartbeat"],
                        );

my $menu_help = $menu_bar->cascade(-label => $Translate{'Help'});
    $menu_help->command('-label' => 'Version',
                        '-command' => [\&DisplayHash, \%Version, $Translate{'Version'}, "Display"],
                       );
    $menu_help->separator;
    $menu_help->command('-label' => $Translate{'About'},
                        '-command' => sub
        {
        my $License = <<EOF;
Copyright (C) 2009  Rainer Walther (rainerwalther-mail\@web.de)

Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)

See LICENSE.TXT
EOF


        my $DlgAbout = $frame_map->Dialog('-title' => $Translate{'AboutMissionCockpit'},
                                          '-text' => "$License",
                                          '-buttons' => ['OK'],
                                          '-bitmap' => 'info',
                                         );
        $DlgAbout->Show;
        });  


# Hauptfenster Statuszeile
$frame_status = $main->Frame( '-background' => 'lightgray',
                         ) -> pack('-side' => 'bottom',
                                   '-anchor' => 'w',
                                   '-fill' => 'none',
                                   '-expand' => 'y',
                                    );
$status_line = $frame_status->Label ('-text' => $Translate{'StatusLine'},
                                    ) -> pack ('-side' => 'bottom',
                                               );

                                                                                           
#-----------------------------------------------------------------
# Frames
#-----------------------------------------------------------------                       

#
# Frame: Map
#
                                 
$frame_map = $main->Frame( '-background' => 'lightgray',
                           '-relief' => 'sunken',
                           '-borderwidth' => 5,
                          ) -> pack('-side' => 'top',
                                    '-fill' => 'x',
                                    );

# Map Überschrift
$frame_map_top = $frame_map->Frame() -> pack( '-side' => 'top',
                                              '-expand' => 'x',
                                              '-anchor' => 'w',
                                            );

$frame_map_top->Label ('-text' => "$Translate{'Map'}: $Map{'Name'} ($Map{'File'})",
                       '-background' => 'lightgray',
                       '-relief' => 'flat',
                       ) -> pack( '-side' => 'left' );

# Map Statuszeile
$map_status = $frame_map->Frame( '-background' => 'lightgray',
                               ) -> pack('-side' => 'bottom',
                                         '-anchor' => 'w',
                                         '-fill' => 'none',
                                         '-expand' => 'y',
                                        );
$map_status_line = $map_status->Label ( '-text' => $Translate{'StatusLine'},
                                        '-background' => 'lightgray',
                                       ) -> pack ('-side' => 'bottom',);

# Map Canvas

# Canvas size
$MapSizeX  = $Map{'Size_X'};
$MapSizeY  = $Map{'Size_Y'};

$map_canvas = $frame_map->Canvas( '-width'  => $MapSizeX,
                                  '-height' => $MapSizeY,
                                  '-cursor' => 'cross',
                                ) -> pack();

# Images and Icons on canvas
my @Icons = (
            # Image             Tag             File                                       Pos_x            Pos_y
            'Map',              'Map',          "$Cfg->{'map'}->{'MapDir'}/$Map{'File'}",  0,               0,
            'HeartbeatSmall',   'Heartbeat',    "$Cfg->{'mkcockpit'}->{'IconHeartSmall'}", $MapSizeX/4,     10,
            'HeartbeatLarge',   'Heartbeat',    "$Cfg->{'mkcockpit'}->{'IconHeartLarge'}", $MapSizeX/4,     -100,
            'Satellite-Photo',  'Satellite',    "$Cfg->{'mkcockpit'}->{'IconSatellite'}",  $MapSizeX-300,   -100,
            'Waypoint-Photo',   'Waypoint',     "$Cfg->{'mkcockpit'}->{'IconWaypoint'}",   0,               -150,
            'Target-Photo',     'Target',       "$Cfg->{'mkcockpit'}->{'IconTarget'}",     0,               -100,
            'Fox-Photo',        'Fox',          "$Cfg->{'mkcockpit'}->{'IconFox'}",        $MapSizeX/2-100, $MapSizeY/2,
            'WpPlay-Foto',      'Wp-PlayPause', "$Cfg->{'waypoint'}->{'IconPlay'}",        $MapSizeX/2+100, $MapSizeY-48,
            'WpPause-Foto',     'Wp-PlayPause', "$Cfg->{'waypoint'}->{'IconPause'}",       $MapSizeX/2+100, -100,
            'WpStop-Foto',      'Wp-Stop',      "$Cfg->{'waypoint'}->{'IconStop'}",        $MapSizeX/2+150, $MapSizeY-48,
            'WpNext-Foto',      'Wp-Next',      "$Cfg->{'waypoint'}->{'IconNext'}",        $MapSizeX/2,     $MapSizeY-48,
            'WpPrev-Foto',      'Wp-Prev',      "$Cfg->{'waypoint'}->{'IconPrev'}",        $MapSizeX/2-50,  $MapSizeY-48,
            'WpFirst-Foto',     'Wp-First',     "$Cfg->{'waypoint'}->{'IconFirst'}",       $MapSizeX/2-100, $MapSizeY-48,
            'WpLast-Foto',      'Wp-Last',      "$Cfg->{'waypoint'}->{'IconLast'}",        $MapSizeX/2+50,  $MapSizeY-48,
            'WpHome-Foto',      'Wp-Home',      "$Cfg->{'waypoint'}->{'IconHome'}",        $MapSizeX/2-150, $MapSizeY-48,
            'WpWpt-Foto',       'Wp-WptKml',    "$Cfg->{'waypoint'}->{'IconWpt'}",         $MapSizeX/2-250, $MapSizeY-48,
            'WpKml-Foto',       'Wp-WptKml',    "$Cfg->{'waypoint'}->{'IconKml'}",         $MapSizeX/2-250, -100 ,
            'WpRandomOff-Foto', 'Wp-WptRandom', "$Cfg->{'waypoint'}->{'IconRandomOff'}",   $MapSizeX/2-200, -100,
            'WpRandomOn-Foto',  'Wp-WptRandom', "$Cfg->{'waypoint'}->{'IconRandomOn'}",    $MapSizeX/2-200, $MapSizeY-48,
            'WpRandomMap-Foto', 'Wp-WptRandom', "$Cfg->{'waypoint'}->{'IconRandomMap'}",   $MapSizeX/2-200, -100,
            );

my $i = 0;
for $Icon (0 .. $#Icons/5)
    {
    my $Image =  $Icons[$i++];
    my $Tag =    $Icons[$i++];
    my $File =   $Icons[$i++];
    my $Pos_x =  $Icons[$i++];
    my $Pos_y =  $Icons[$i++];

    $map_canvas->Photo( $Image,
                        '-file' => $File,
                      );
    $map_canvas->createImage( $Pos_x, $Pos_y,
                              '-tags'   => $Tag,
                              '-anchor' => 'nw',
                              '-image'  => $Image,
                            );
    }

# Calibration Points
$map_canvas->createLine ( $Map{'P1_x'}-8, $Map{'P1_y'},
                          $Map{'P1_x'}+8, $Map{'P1_y'},
                          $Map{'P1_x'},   $Map{'P1_y'},
                          $Map{'P1_x'},   $Map{'P1_y'}-8,
                          $Map{'P1_x'},   $Map{'P1_y'}+8,
                          '-tags'  => 'Calibration',
                          '-arrow' => 'none',
                          '-fill'  => 'red',
                          '-width' => 1,
                         );
$map_canvas->createLine ( $Map{'P2_x'}-8, $Map{'P2_y'},
                          $Map{'P2_x'}+8, $Map{'P2_y'},
                          $Map{'P2_x'},   $Map{'P2_y'},
                          $Map{'P2_x'},   $Map{'P2_y'}-8,
                          $Map{'P2_x'},   $Map{'P2_y'}+8,
                          '-tags'  => 'Calibration',
                          '-arrow' => 'none',
                          '-fill'  => 'red',
                          '-width' => 1,
                         );

# border polygon
$map_canvas->createPolygon( @Map{'Border'},
                           '-tags' => 'Map-Border',
                           '-fill' => '',
                           '-outline' => $Cfg->{'mkcockpit'}->{'ColorAirfield'}, '-width' => 2,
                          );
$map_canvas->raise('Map-Border', 'Map');  # Border above Map


# Balloon attached to Canvas
$map_balloon = $frame_map->Balloon('-statusbar' => $status_line, );
$map_balloon->attach($map_canvas,
                     '-balloonposition' => 'mouse',
                     '-state' => 'balloon',
                     '-msg' => { 'MK-Arrow'               => $Translate{'Balloon-MK-Arrow'},
                                 'MK-Home-Line'           => $Translate{'Balloon-MK-Home-Line'},
                                 'MK-Home-Dist'           => $Translate{'Balloon-MK-Home-Dist'},
                                 'MK-Target-Line'         => $Translate{'Balloon-MK-Target-Line' },
                                 'MK-Target-Dist'         => $Translate{'Balloon-MK-Target-Dist'},
                                 'MK-Speed'               => $Translate{'Balloon-MK-Speed'},
                                 'Map-Variometer'         => $Translate{'Balloon-Map-Variometer' },
                                 'Map-Variometer-Pointer' => $Translate{'Balloon-Map-Variometer-Pointer'},
                                 'Map-Variometer-Skala'   => $Translate{'Balloon-Map-Variometer-Pointer'},
                                 'Fox'                    => $Translate{'Balloon-Fox'},
                                 'Heartbeat'              => $Translate{'Balloon-Heartbeat'},
                                 'Satellite'              => $Translate{'Balloon-Satellite'},
                                 'Waypoint'               => $Translate{'Balloon-Waypoint'},
                                 'Map-Border'             => $Translate{'Balloon-Map-Border'},
                                 'Waypoint-Connector'     => $Translate{'Balloon-Waypoint-Connector'},
                                 'Wp-PlayPause'           => $Translate{'Balloon-Wp-PlayPause'},
                                 'Wp-Stop'                => $Translate{'Balloon-Wp-Stop'},
                                 'Wp-First'               => $Translate{'Balloon-Wp-First'},
                                 'Wp-Last'                => $Translate{'Balloon-Wp-Last'},
                                 'Wp-Next'                => $Translate{'Balloon-Wp-Next'},
                                 'Wp-Prev'                => $Translate{'Balloon-Wp-Prev'},
                                 'Wp-Home'                => $Translate{'Balloon-Wp-Home'},
                                 'Wp-WptKml'              => $Translate{'Balloon-Wp-WptKml'},
                                 'Wp-WptRandom'           => $Translate{'Balloon-Wp-WptRandom'},
                               },
                    );

#
# Mouse buttons
#
                                       
# general Mouse button 1
$map_canvas->CanvasBind("<Button-1>", sub
    {
    # print coords in status line
    my ($x, $y) = ($Tk::event->x, $Tk::event->y);
    my ($Lat, $Lon) = &MapXY2Gps($x, $y);
    $map_status_line->configure ('-text' => "Lat: $Lat  Lon: $Lon     x: $x  y: $y");
    });
       
# Mouse button 1 for Fox
my $FoxOldx = 0;
my $FoxOldy = 0;

# Pick Fox
$map_canvas->bind('Fox' => '<Button-1>' => sub
    {
    # prepare to move Fox
    my ($x, $y) = ($Tk::event->x, $Tk::event->y);
    $FoxOldx = $x;
    $FoxOldy = $y;
    $FoxTime = time;
    });

# Move Fox
$map_canvas->bind('Fox' => '<Button1-Motion>' => sub
    {
    my ($x, $y) = ($Tk::event->x, $Tk::event->y);
    my $id      = $map_canvas->find('withtag', 'current');

    $map_canvas->move($id => $x - $FoxOldx, $y - $FoxOldy);
    $FoxOldx = $x;
    $FoxOldy = $y;
       
    if ( time > $FoxTime )
        {
          # wenn in Bewegung Koordinaten nur 1/s senden
        my ($x0, $y0, $x1, $y1) = $map_canvas->bbox ($id);
        $x = $x0 + ($x1 - $x0)/2;
        $y = $y1;

        my ($Lat, $Lon) = &MapXY2Gps($x, $y);
        &MkFlyTo ( '-lat' => $Lat,
                   '-lon' => $Lon,
                   '-mode' => "Target",
                 );
        $FoxTime = time;

        $map_status_line->configure ('-text' => "$Translate{'TargetCoordSent'} -> Lat: $Lat  Lon: $Lon     x: $x  y: $y");
        }
    });

# Release Fox
$map_canvas->bind('Fox' => '<Button1-ButtonRelease>' => sub
    {
    my ($x, $y) = ($Tk::event->x, $Tk::event->y);
    my $id      = $map_canvas->find('withtag', 'current');

    my ($x0, $y0, $x1, $y1) = $map_canvas->bbox ($id);
    $x = $x0 + ($x1 - $x0)/2;
    $y = $y1;
       
    my ($Lat, $Lon) = &MapXY2Gps($x, $y);
    &MkFlyTo ( '-lat' => $Lat,
               '-lon' => $Lon,
               '-mode' => "Target",
             );

    # Show user that Waypoints in MK are cleared
    $WaypointsModified = 1;
    &WpRedrawLines();

    $map_status_line->configure ('-text' => "$Translate{'TargetCoordSent'} -> Lat: $Lat  Lon: $Lon     x: $x  y: $y");
    });

# Pick Waypoint
$map_canvas->bind('Waypoint' => '<Button-1>' => sub
    {
    # prepare to move
    my ($x, $y) = ($Tk::event->x, $Tk::event->y);
    $WpOldx = $x;
    $WpOldy = $y;
    });
       
# Move Waypoint
$map_canvas->bind('Waypoint' => '<Button1-Motion>' => sub
    {
    my ($x, $y) = ($Tk::event->x, $Tk::event->y);
    my $id      = $map_canvas->find('withtag', 'current');

    # move icon and Wp-Number
    my $WpIndex = &WpGetIndexFromId($id);
    if ( $WpIndex >= 0 )
        {
        my $Tag = $Waypoints[$WpIndex]{'Tag'};
        $map_canvas->move($Tag => $x - $WpOldx, $y - $WpOldy);
        }

    $WpOldx = $x;
    $WpOldy = $y;
    });

# Release Wp
$map_canvas->bind('Waypoint' => '<Button1-ButtonRelease>' => sub
    {
    my ($x, $y) = ($Tk::event->x, $Tk::event->y);
    my $id      = $map_canvas->find('withtag', 'current');

    # take coords from lower/middle icon position
    my ($x0, $y0, $x1, $y1) = $map_canvas->bbox ($id);
    $x = $x0 + ($x1 - $x0)/2;
    $y = $y1;

    # update Waypoint-Array
    my $WpIndex = &WpGetIndexFromId($id);
    if ( $WpIndex >= 0 )
            {
        # got it: set new coords

        my ($Lat, $Lon) = &MapXY2Gps($x, $y);
        my $Wp = $Waypoints[$WpIndex];
        $Wp->{'MapX'} = $x;
        $Wp->{'MapY'} = $y;
        $Wp->{'Pos_Lat'} = $Lat;
        $Wp->{'Pos_Lon'} = $Lon;
               
        # redraw connector-lines
        &WpRedrawLines();

        # red connectors: Wp still have to be sent to MK
        $map_canvas->itemconfigure('Waypoint-Connector',
                                           '-fill' => $Cfg->{'mkcockpit'}->{'ColorWpResend'},
                                  );
        $WaypointsModified = 1;
                       
        my $WpNum = $WpIndex + 1;
        $map_status_line->configure ('-text' => "$Translate{'WpMoved'}: $WpNum -> Lat: $Lat  Lon: $Lon     x: $x  y: $y");
        }
    });


#
# Player:
#    Waypoint-List:   @Waypoints
#    KML-Target-List: @KmlTargets
#

# Player state machine
$PlayerMode = 'Stop';     # Start, Stop, Pause, Home ...
$PlayerWptKmlMode = 'WPT';  # WPT, KML
$PlayerRandomMode = 'STD';  # STD, RND, MAP
$WpPlayerIndex = 0;
$WpPlayerHoldtime = -1;
$KmlPlayerIndex = 0;
$PlayerPause_Lat = "";
$PlayerPause_Lon = "";

# Mouse bindings
$map_canvas->bind('Wp-PlayPause' => '<Button-1>' => \&CbPlayerPlayPause );
$map_canvas->bind('Wp-Next'      => '<Button-1>' => \&CbPlayerNext );
$map_canvas->bind('Wp-Prev'      => '<Button-1>' => \&CbPlayerPrev );
$map_canvas->bind('Wp-First'     => '<Button-1>' => \&CbPlayerFirst );
$map_canvas->bind('Wp-Last'      => '<Button-1>' => \&CbPlayerLast );
$map_canvas->bind('Wp-Home'      => '<Button-1>' => \&CbPlayerHome );
$map_canvas->bind('Wp-Stop'      => '<Button-1>' => \&CbPlayerStop );
$map_canvas->bind('Wp-WptKml'    => '<Button-1>' => \&CbPlayerWptKml );
$map_canvas->bind('Wp-WptRandom' => '<Button-1>' => \&CbPlayerWptRandom );


# Focus Canvas, if any key pressed. Needed for the following key-bindings
$main->bind('<Any-Enter>' => sub { $map_canvas->Tk::focus });

# Disable default arrow-key bindings on canvas
$main->bind('Tk::Canvas',"<$_>",undef)for qw /Left Right Up Down/;

# keyboard bindings
$map_canvas->Tk::bind( '<Key-space>' , \&CbPlayerPlayPause );
$map_canvas->Tk::bind( '<Key-n>'     , \&CbPlayerNext );
$map_canvas->Tk::bind( '<Key-p>'     , \&CbPlayerPrev );
$map_canvas->Tk::bind( '<Key-f>'     , \&CbPlayerFirst );
$map_canvas->Tk::bind( '<Key-l>'     , \&CbPlayerLast );
$map_canvas->Tk::bind( '<Key-h>'     , \&CbPlayerHome );
$map_canvas->Tk::bind( '<Key-s>'     , \&CbPlayerStop );
$map_canvas->Tk::bind( '<Key-w>'     , \&CbPlayerWptKml );
$map_canvas->Tk::bind( '<Key-k>'     , \&CbPlayerWptKml );
$map_canvas->Tk::bind( '<Key-r>'     , \&CbPlayerWptRandom );
$map_canvas->Tk::bind( '<Key-0>'     , [\&CbPlayerNum, "0"] );
$map_canvas->Tk::bind( '<Key-1>'     , [\&CbPlayerNum, "1"] );
$map_canvas->Tk::bind( '<Key-2>'     , [\&CbPlayerNum, "2"] );
$map_canvas->Tk::bind( '<Key-3>'     , [\&CbPlayerNum, "3"] );
$map_canvas->Tk::bind( '<Key-4>'     , [\&CbPlayerNum, "4"] );
$map_canvas->Tk::bind( '<Key-5>'     , [\&CbPlayerNum, "5"] );
$map_canvas->Tk::bind( '<Key-6>'     , [\&CbPlayerNum, "6"] );
$map_canvas->Tk::bind( '<Key-7>'     , [\&CbPlayerNum, "7"] );
$map_canvas->Tk::bind( '<Key-8>'     , [\&CbPlayerNum, "8"] );
$map_canvas->Tk::bind( '<Key-9>'     , [\&CbPlayerNum, "9"] );
$map_canvas->Tk::bind( '<Key-Left>'  , [\&CbPlayerMove, -1,  0] );
$map_canvas->Tk::bind( '<Key-Right>' , [\&CbPlayerMove,  1,  0] );
$map_canvas->Tk::bind( '<Key-Up>'    , [\&CbPlayerMove,  0,  1] );
$map_canvas->Tk::bind( '<Key-Down>'  , [\&CbPlayerMove,  0, -1] );
$map_canvas->Tk::bind( '<Key-Escape>', sub { exit; } );



# Mouse button 3 context menu
my $map_menu = $map_canvas->Menu('-tearoff' => 0,
                                 '-title' =>'None',
                                 '-menuitems' =>
    [
     [Button => $Translate{'WpAddAndSend'},  -command => sub
        {
        # send Wp to MK        
        ($Lat, $Lon) = &MapXY2Gps($Wp_x, $Wp_y);
        &MkFlyTo ( '-lat' => $Lat,
                   '-lon' => $Lon,
                   '-mode' => "Waypoint"
                 );

        # Add Wp to Waypoints list
        &WpAdd ($MapCanvasX, $MapCanvasY);

        # switch player to Wp mode and redraw waypoints
        &WptKmlSwitch ('WPT');  

        $map_status_line->configure ('-text' => "$Translate{'WpSavedAndSent'} -> Lat: $Lat Lon: $Lon");
        }],


     [Button => $Translate{'WpProperties'},  -command => sub
        {
        # find Wp-Hash for selected icon/tag
        my $WpIndex = &WpGetIndexFromId($MapCanvasId);
        if ( $WpIndex >= 0 )
            {
            my $Wp = $Waypoints[$WpIndex];
            my $WpNum = $WpIndex + 1;

            &DisplayHash ($Wp, "$Translate{'WpProperties'} $WpNum", "Edit Waypoint Refresh");

            $map_status_line->configure ('-text' => "$Translate{'WpProperties'} $WpNum");
            }
        }],
               
     [Button => $Translate{'WpResendAll'},  -command => sub
        {
        &WpSendAll();
               
        $map_status_line->configure ('-text' => $Translate{'WpAllSent'});
        }],

      '',   # Separator
       
     [Button => $Translate{'WpLoadAndSend'},  -command => sub
        {
        my $WpFile = $main->getOpenFile('-defaultextension' => ".xml",
                                        '-filetypes'        =>
                                            [['Waypoints',     '.xml' ],
                                             ['All Files',     '*', ],
                                            ],
                                        '-initialdir' => $Cfg->{'waypoint'}->{'WpDir'},
                                        '-title' => $Translate{'WpLoad'},
                                       );
        if ( -f $WpFile )
            {
            &WpLoadFile ($WpFile);

            # send all Wp to MK
            &WpSendAll();

            # switch player to Wp mode and redraw waypoints
            $PlayerRandomMode  = 'STD';
            &WptKmlSwitch ('WPT');
       
            $map_status_line->configure ('-text' => "$Translate{'WpLoadedAndSent'}: $WpFile");
            }
        }],    
       
     [Button => $Translate{'WpSave'},  -command => sub
        {
        my $WpFile = $main->getSaveFile('-defaultextension' => ".xml",
                                        '-filetypes'        =>
                                          [['Waypoints',     '.xml' ],
                                           ['All Files',     '*', ],
                                          ],
                                        '-initialdir' => $Cfg->{'waypoint'}->{'WpDir'},
                                        '-title' => $Translate{'WpSave'},
                                       );

        &WpSaveFile ($WpFile);
               
        $map_status_line->configure ('-text' => "$Translate{'WpSaved'}: $WpFile");
        }],
               
     '',   # Separator

     [Button => $Translate{'WpDelete'},  -command => sub
        {
        # find Wp-Hash for selected icon/tag
        my $WpIndex = &WpGetIndexFromId($MapCanvasId);
        if ( $WpIndex >= 0 )
            {
            &WpDelete ($WpIndex);

            # redraw connector-lines
            $WaypointsModified = 1;
            &WpRedrawLines();  
            &WpRedrawIcons();  # wg. Wp-Nummern

            my $WpNum = $WpIndex + 1;
            $map_status_line->configure ('-text' => "$Translate{'WpDeleted'}: $WpNum");
            }
        }],

     [Button => $Translate{'WpAllDeleteAndSend'},  -command => sub
        {
        undef @Waypoints;
        $WpPlayerIndex = 0;
        $WpPlayerHoldtime = -1;

        # remove all Wp-Icons and Wp-Number on canvas
        &WpHide();

        &WpSendAll();

        $map_status_line->configure ('-text' => "$Translate{'WpAllDeleted'}: $WpIndex");
        }],

    '',   # Separator

     [Button => $Translate{'KmlLoadAndPlay'},  -command => sub
        {
        $KmlFile = $main->getOpenFile('-defaultextension' => ".kml",
                                     '-filetypes'        =>
                                         [['KML',           '.kml' ],
                                          ['All Files',     '*', ],
                                         ],
                                     '-initialdir' => $Cfg->{'waypoint'}->{'KmlDir'},
                                     '-title' => $Translate{'KmlLoad'},
                                    );
        if ( -f $KmlFile )
            {
            &KmlLoadFile($KmlFile);

            # switch player to KML mode and redraw track
            &WptKmlSwitch ('KML');

            $map_status_line->configure ('-text' => "$Translate{'KmlLoaded'}: $KmlFile" );
            }

        }],

    '',   # Separator
         
     [Button => $Translate{'WpFlyImmediately'},  -command => sub
        {
        &MkFlyTo ( '-x' => $MapCanvasX,
                   '-y' => $MapCanvasY,
                   '-mode' => "Target"
                 );

        # redraw connector-lines
        $WaypointsModified = 1;
        &WpRedrawLines();  

        $map_status_line->configure ('-text' => "$Translate{'TargetCoordSent'} -> Lat: $Lat  Lon: $Lon     x: $MapCanvasX  y: $MapCanvasY");
        }],
    ]
                                    );
$map_canvas->CanvasBind("<Button-3>" => [ sub
    {
    $map_canvas->focus;
    my($w, $x, $y) = @_;
    ($MapCanvasX, $MapCanvasY) = ($Tk::event->x, $Tk::event->y);
    $MapCanvasId = $map_canvas->find('withtag', 'current');
    $map_menu->post($x, $y);
    }, Ev('X'), Ev('Y') ] );



# Line from MK to Home
$map_canvas->createLine ( $MapSizeX/2, $MapSizeY/2, $MapSizeX/2, $MapSizeY/2,
                          '-tags' => 'MK-Home-Line',
                          '-arrow' => 'none',
                          '-fill' => $Cfg->{'mkcockpit'}->{'ColorHomeLine'},
                          '-width' => 3,
                         );
                                                                                 
# Text Entfernung positioniert an der Home-Linie
$map_canvas->createText ( $MapSizeX/2 + 8, $MapSizeY/2 - 8,
                          '-tags' => 'MK-Home-Dist',
                          '-text' => '0 m',
                          '-anchor' => 'w',
                          '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
                          '-fill' => $Cfg->{'mkcockpit'}->{'ColorHomeDist'},
                          );

# Line from MK to Target, draw invisible out of sight
$map_canvas->createLine ( 0, -100, 0, -100,
                          '-tags' => 'MK-Target-Line',
                          '-arrow' => 'none',
                          '-fill' => $Cfg->{'mkcockpit'}->{'ColorTargetLine'},
                          '-width' => 3,
                         );
                                                                                 
# Text Entfernung positioniert an der Target-Linie
$map_canvas->createText ( 0, -100,
                          '-tags' => 'MK-Target-Dist',
                          '-text' => '0 m',
                          '-anchor' => 'w',
                          '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
                          '-fill' => $Cfg->{'mkcockpit'}->{'ColorTargetDist'},
                          );
                                                 
# MK Geschwindigkeits-Vektor
$MapMkSpeedLen = 60;    # Länge Speed-Zeiger
my $x0 = $MapSizeX/2;
my $y0 = $MapSizeY/2;
my $x1 = $MapSizeX/2;
my $y1 = $MapSizeY/2 - $MapMkSpeedLen;
$map_canvas->createLine ( $x0, $y0, $x1, $y1,
                          '-tags' => 'MK-Speed',
                          '-arrow' => 'last',
                          '-arrowshape' => [10, 10, 3 ],
                          '-fill' => $Cfg->{'mkcockpit'}->{'ColorSpeedVector'},
                          '-width' => 4,
                         );

# MK als Pfeilspitze einer Linie darstellen
$MapMkLen = 25;
my $x0 = $MapSizeX/2;
my $y0 = $MapSizeY/2 + $MapMkLen/2;
my $x1 = $MapSizeX/2;
my $y1 = $MapSizeY/2 - $MapMkLen/2;
$map_canvas->createLine ( $x0, $y0, $x1, $y1,
                          '-tags' => 'MK-Arrow',
                          '-arrow' => 'last',
                          '-arrowshape' => [25, 30, 10 ],
                          '-fill' => $Cfg->{'mkcockpit'}->{'ColorMkSatNo'},
                          '-width' => 1
                         );
$MkPos_x = $MapSizeX/2;
$MkPos_y = $MapSizeY/2;


                                                 
# OSD Daten auf Karte anzeigen

my @Texts = (
            # Tag                 Text        Pos_x          Pos_y  Font
            'MK-OSD-Tim-Label',   "TIM",      $MapSizeX/2 - 40, 20, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Tim-Value',   "00:00",    $MapSizeX/2,      20, '-*-Arial-Bold-R-Normal--*-270-*',
            'MK-OSD-Bat-Label',   "BAT",      $MapSizeX/2 - 40, 50, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Bat-Value',   "0.0 V",    $MapSizeX/2,      50, '-*-Arial-Bold-R-Normal--*-270-*',
            'MK-OSD-Spd-Label',   "SPD",      10,               20, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Spd-Value',   "0.0 km/h", 60,               20, '-*-Arial-Bold-R-Normal--*-270-*',
            'MK-OSD-Alt-Label',   "ALT",      10,               50, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Alt-Value',   "0 m",      60,               50, '-*-Arial-Bold-R-Normal--*-270-*',
            'MK-OSD-Odo-Label',   "ODO",      10,               80, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Odo-Value',   "0.000 km", 60,               80, '-*-Arial-Bold-R-Normal--*-270-*',
            'MK-OSD-Sat-Label',   "SAT",      $MapSizeX - 230,  20, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Sat-Value',   "0",        $MapSizeX - 180,  20, '-*-Arial-Bold-R-Normal--*-270-*',
            'MK-OSD-Wp-Label',    "WPT",      $MapSizeX - 230,  50, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Wp-Value',    "0 / 0",    $MapSizeX - 180,  50, '-*-Arial-Bold-R-Normal--*-270-*',
            'MK-OSD-Mode-Label',  "MOD",      $MapSizeX - 230,  80, '-*-Arial-Bold-R-Normal--*-150-*',
            'MK-OSD-Mode-Value',  "",         $MapSizeX - 180,  80, '-*-Arial-Bold-R-Normal--*-270-*',
            );

my $i = 0;
for $Text (0 .. $#Texts/5)
    {
    my $Tag =   $Texts[$i++];
    my $Text =  $Texts[$i++];
    my $Pos_x = $Texts[$i++];
    my $Pos_y = $Texts[$i++];
    my $Font =  $Texts[$i++];

    $map_canvas->createText ( $Pos_x, $Pos_y,
                              '-tags' => $Tag,
                              '-text' => $Text,
                              '-font' => $Font,
                              '-fill' => $Cfg->{'mkcockpit'}->{'ColorOsd'},
                              '-anchor' => 'w',
                             );

    }


# Variometer on canvas
my @Polygon;
for ( $y = -100; $y <= 100; $y += 10)
    {
    my $Len = 5;
    if ( ($y % 50) == 0 )
        {
        $Len = 10;
        $map_canvas->createText ( $Len+5, $MapSizeY/2 + $y,
                                  '-tags' => 'Map-Variometer-Skala',
                                  '-text' => sprintf ("%3d", -$y / 10),
                                  '-anchor' => 'w',
                                  '-font' => '-*-Arial-Normal-R-Normal--*-150-*',
                          '-fill' => $Cfg->{'mkcockpit'}->{'ColorVariometer'},
                          );
        }
    push @Polygon, (   0, $MapSizeY/2 + $y);
    push @Polygon, ($Len, $MapSizeY/2 + $y);
    push @Polygon, (   0, $MapSizeY/2 + $y);
    }

$map_canvas->createLine(@Polygon,
                        '-tags' => 'Map-Variometer',
                        '-fill' => $Cfg->{'mkcockpit'}->{'ColorVariometer'},
                        '-width' => 2,
                        '-arrow' => 'none',
                       );
# Vario Pointer
$map_canvas->createPolygon( 5, $MapSizeY/2, 20, $MapSizeY/2+10, 20, $MapSizeY/2-10,
                           '-tags' => 'Map-Variometer-Pointer',
                           '-fill' => $Cfg->{'mkcockpit'}->{'ColorVariometerPointer'},
                           '-outline' => 'black', '-width' => 1,
                          );

# Tracking Canvas

if ( $Cfg->{'track'}->{'Active'} =~ /y/i )
    {
    # Canvas size
    $TrackSizeX  = 125;
    $TrackSizeY  = 100;
    $TrackOffY   = $TrackSizeY - $MapSizeY + 20;
    $TrackPtrLen = 50;    # Länge Zeiger

    # draw in map-canvas
    $track_canvas = $map_canvas;

    # Ziffernblatt
    my $x0 = $TrackSizeX/2 - $TrackPtrLen;
    my $y0 = $TrackSizeY + $TrackPtrLen - $TrackOffY;
    my $x1 = $TrackSizeX/2 + $TrackPtrLen;
    my $y1 = $TrackSizeY   - $TrackPtrLen - $TrackOffY;
    $track_canvas->createArc ( $x0, $y0, $x1, $y1,
                               '-extent' => '200',
                               '-start' => '-10',
                               '-style' => 'chord',
                               '-outline' => 'gray', '-width' => '1',
                             );

    # Skala Ziffernblatt
    for ($i=0; $i<=180; $i+=15)
        {
        my $pi = 3.14159265358979;
        my $x0 = $TrackSizeX/2 - ($TrackPtrLen - 20) * cos($i / 180 * $pi);
        my $y0 = $TrackSizeY   - ($TrackPtrLen - 20) * sin($i / 180 * $pi) - $TrackOffY;
        my $x1 = $TrackSizeX/2 - ($TrackPtrLen - 28) * cos($i / 180 * $pi);
        my $y1 = $TrackSizeY   - ($TrackPtrLen - 28) * sin($i / 180 * $pi) - $TrackOffY;
        $track_canvas->createLine ( $x0, $y0, $x1, $y1,
                                   '-fill' => 'white',
                                   '-width' => 1,
                                  );
        }

    # Skala Beschriftung Ziffernblatt
    for ($i=0; $i<=180; $i+=45)
        {
        my $pi = 3.14159265358979;
        my $x0 = $TrackSizeX/2 - ($TrackPtrLen - 12) * cos($i / 180 * $pi);
        my $y0 = $TrackSizeY   - ($TrackPtrLen - 12) * sin($i / 180 * $pi) - $TrackOffY;
        $track_canvas->createText ( $x0, $y0,
                                   '-text' => $i - 90,
                                   '-fill' => 'white',
                                  );
        }

    # Ziffernblatt Beschriftung Einheit
    my $x0 = $TrackSizeX/2;
    my $y0 = $MapSizeY -6;
    $track_canvas->createText ( $x0, $y0,
                                '-text' => "Antenne Winkel",
                                '-justify' => 'center',
                                '-fill' => 'white',
                                );
                                         
    # Zeiger
    my $x0 = $TrackSizeX/2;
    my $y0 = $TrackSizeY - 0 - $TrackOffY;
    my $x1 = $TrackSizeX/2;
    my $y1 = $TrackSizeY - ($TrackPtrLen - 22) - $TrackOffY;
    $track_ptr_id= $track_canvas->createLine ( $x0, $y0, $x1, $y1,
                                               '-tags' => 'Track-Ptr',
                                               '-arrow' => 'last',
                                               '-arrowshape' => [20, 30, 5 ],
                                               '-fill' => 'red',
                                               '-width' => 8,
                                              );
    # Zeiger Center
    my $Dia = 7;
    my $x0 = $TrackSizeX/2 - $Dia;
    my $y0 = $TrackSizeY + $Dia - $TrackOffY;
    my $x1 = $TrackSizeX/2 + $Dia;
    my $y1 = $TrackSizeY   - $Dia - $TrackOffY;
    $track_canvas->createArc ( $x0, $y0, $x1, $y1,
                               '-extent' => '359',
                               '-outline' => 'gray', '-width' => 1,
                               '-fill' => 'gray',
                             );
    }
                                                                                 
#-----------------------------------------------------------------
# Timer
#-----------------------------------------------------------------                       

#
# Timer: 5s
#
$main->repeat (5000, sub
    {
    if ( ! $MkSendWp )
        {
        # Abfragefrequenz OSD und Debug regelmäßig neu einstellen, falls Übertragungsfehler
        $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 10) );   # Frequenz OSD Datensatz, * 10ms
        $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 10) );   # Frequenz MK Debug Datensatz, * 10ms
        $MkSendQueue->enqueue( "v", "$AddrNC", "");   # Version
        $MkSendQueue->enqueue( "e", "$AddrNC", "");   # Error Text Request
        }
    });


#      
# Timer: 0.1s - Map Overlay aktualisieren
#
$frame_map_top->repeat (100, sub
    {

    # Clear old messages from this timer
    &MkMessageInit ("Timer-MapOverlay");

    lock (%MkOsd);              # until end of block

    # Aktuell gültige Karte
    my %Map = %{$Maps{'Current'}};

    if ( $MkOsd{'_Timestamp'} >= time-2 )
        {
        # Gueltige OSD Daten

        my $SatsInUse = $MkOsd{'SatsInUse'};
        if ( $SatsInUse > 0  and  $MkOsd{'CurPos_Stat'} == 1 and $MkOsd{'HomePos_Stat'} == 1 )
            {
            # ausreichender GPS Empfang

            # get x,y map coords of current position
            my ($C_x, $C_y, $C_Angel) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $MkOsd{'CompassHeading'});
            $MkPos_x = $C_x;
            $MkPos_y = $C_y;
               
            # rotate MK arrow
            my $dy = sin (deg2rad $C_Angel) * ($MapMkLen/2);
            my $dx = cos (deg2rad $C_Angel) * ($MapMkLen/2);
            my $x0 = $C_x - $dx;
            my $y0 = $C_y - $dy;
            my $x1 = $C_x + $dx;
            my $y1 = $C_y + $dy;
            $map_canvas->coords ('MK-Arrow', $x0, $y0, $x1, $y1);

            # Update speed vector
            my $MapAngel = &MapAngel();   # Norh to Map-Horizont
            my $GpsSpeedNorth = $MkNcDebug{'Analog_21'};
            my $GpsSpeedEast  = $MkNcDebug{'Analog_22'};
            my $PhiGpsSpeed = rad2deg atan2 ( $GpsSpeedEast, $GpsSpeedNorth );
            $PhiMapSpeed = $PhiGpsSpeed - $MapAngel;

            # 555 cm/s ~ 20 km/h -> Zeigerlänge = $MkSpeedLen bei 20 km/h
            my $dy = sin (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
            my $dx = cos (deg2rad $PhiMapSpeed) * $MapMkSpeedLen * $MkOsd{'GroundSpeed'} / 555;
            my $x0 = $C_x;
            my $y0 = $C_y;
            my $x1 = $C_x + $dx;
            my $y1 = $C_y + $dy;
            $map_canvas->coords ('MK-Speed', $x0, $y0, $x1, $y1);
 
            # Update Line between Home and MK
            my ($H_x, $H_y) = &MapGps2XY($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
            $map_canvas->coords ('MK-Home-Line', $H_x, $H_y, $C_x, $C_y);

            # Update Distance between Home and MK
            my ($Dist, $Bearing) = MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
                                                   $MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'} );
            my $x = ($C_x - $H_x) / 2 + $H_x + 8;
            my $y = ($C_y - $H_y) / 2 + $H_y + 8;
            $map_canvas->coords ('MK-Home-Dist', $x, $y);
            $map_canvas->itemconfigure ('MK-Home-Dist',
                                        '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
                                       );

            # Update OSD - Sat dependent values
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", $MkOsd{'GroundSpeed'} * 0.036) );

            # Alt = average Luftdruck und Sat
            my $Alt = int ( ($MkOsd{'Altimeter'} / $Cfg->{'mkcockpit'}->{'AltFactor'} +
                             $MkOsd{'CurPos_Alt'} - $MkOsd{'HomePos_Alt'} ) / 2 + 0.5 );
            $map_canvas->itemconfigure ('MK-OSD-Alt-Value', '-text' => sprintf ("%3d m", $Alt) );

            if ( $MkOsd{'TargetPos_Stat'} == 1 )
                {
                # Valid Target
                # Update Line between Target and MK
                my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
                $map_canvas->coords ('MK-Target-Line', $C_x, $C_y, $T_x, $T_y);

                # Update Distance between Target and MK
                my ($Dist, $Bearing) = MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'},
                                                        $MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'} );

                if ( $Dist >= 25 )  
                    {
                    my $x = ($C_x - $T_x) / 2 + $T_x - 8;
                    my $y = ($C_y - $T_y) / 2 + $T_y + 8;
                    $map_canvas->coords ('MK-Target-Dist', $x, $y);
                    $map_canvas->itemconfigure ('MK-Target-Dist',
                                                '-text' => sprintf ("%4d m", int ($Dist + 0.5) ),
                                               );
                    }
                else
                    {
                    # Don't show distance < 25m
                    $map_canvas->coords ('MK-Target-Dist', 0, -100);
                    }

                # show target icon
                my $IconHeight = 48;
                my $IconWidth = 48;
                $map_canvas->coords('Target', $T_x - $IconWidth/2, $T_y - $IconHeight );

                if ( &IsCrossingBorder($MkPos_x, $MkPos_y, $T_x, $T_y) )
                    {
                    &MkMessage ($Translate{'MsgCrossingBorder'}, "Timer-MapOverlay");
                    }
                }
            else
                {
                # No valid Target, move target line out of sight/canvas
                $map_canvas->coords ('MK-Target-Line', 0, -100, 0, -100);
                $map_canvas->coords ('MK-Target-Dist', 0, -100);

                # hide target icon
                $map_canvas->coords('Target', 0, -100, );
                }
            }
        else
            {
            # kein ausreichender Sat-Empfang
            $map_canvas->itemconfigure ('MK-OSD-Spd-Value', '-text' => sprintf ("%3d km/h", 0 ) );
            }

        # Update OSD - non Sat dependent values
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-text' => sprintf ("%3.1f V", $MkOsd{'UBat'}) );
        $map_canvas->itemconfigure ('MK-OSD-Vsi-Value', '-text' => sprintf ("%3d", $MkOsd{'Variometer'}) );
        $map_canvas->itemconfigure ('MK-OSD-Odo-Value', '-text' => sprintf ("%3.3f km", $OdoMeter / 1000) );
        $map_canvas->itemconfigure ('MK-OSD-Tim-Value', '-text' => sprintf ("%02d:%02d", $MkFlyingTime / 60, $MkFlyingTime % 60) );
        $map_canvas->itemconfigure ('MK-OSD-Sat-Value', '-text' => $MkOsd{'SatsInUse'} );

        # blink battery warning
        $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => $Cfg->{'mkcockpit'}->{'ColorOsd'});
        if ( $MkOsd{'UBat'}  <  $Cfg->{'mkcockpit'}->{'UBatWarning'} )
            {
            if ( time %2 )
                {
                $map_canvas->itemconfigure ('MK-OSD-Bat-Value', '-fill' => 'red');
                }

            &MkMessage ($Translate{'MsgBatWarning'}, "Timer-MapOverlay");
            }


        # Operation Mode
        my $Mode = "";
        if ($MkOsd{'NCFlags'} & 0x04) { $Mode = "WPT"; }
        if ($PlayerMode eq "Play")    { $Mode = "Play"; }
        if ($PlayerMode eq "Pause")   { $Mode = "Paus"; }
        if ($PlayerMode eq "Home")    { $Mode = "Home"; }
        if ($MkOsd{'NCFlags'} & 0x02) { $Mode = "PH"; }
        if ($MkOsd{'NCFlags'} & 0x01) { $Mode = "Free"; }

        # Display Operation Mode
        my $DisplayMode = $Mode;
        if ( ($MkOsd{'NCFlags'} & 0x04)  and  $Mode eq "Play" )
            {
            my %ModeMatrix =
               (
               "KML-STD" => "Play KML",
               "KML-RND" => "Play KML",
               "KML-MAP" => "Play KML",
               "WPT-STD" => "Play WPT",
               "WPT-RND" => "Rand WPT",
               "WPT-MAP" => "Rand MAP",
               );
            my $Key = "${PlayerWptKmlMode}-${PlayerRandomMode}";
            $DisplayMode = $ModeMatrix{$Key};
            }
        if ($MkOsd{'NCFlags'} & 0x08)
            {
            $DisplayMode  = "$DisplayMode" . " !!";   # Range Warning
            }
        $map_canvas->itemconfigure ('MK-OSD-Mode-Value', '-text' => $DisplayMode );


        # Waypoints abhaengig vom Modus NC/Player
        my $WpValue = "--/--";
        if ( $MkOsd{'WaypointNumber'} > 0)
            {
            $WpValue = sprintf ("%d / %d", $MkOsd{'WaypointIndex'} + 1, $MkOsd{'WaypointNumber'});
            }
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "WPT")
            {
            $WpValue = sprintf ("%d / %d", $WpPlayerIndex +1, scalar @Waypoints);
            }
        if ($PlayerMode ne "Stop" and $PlayerWptKmlMode eq "KML" )
            {
            my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
            my $CurrTime = int ($KmlPlayerIndex * $KmlTimeBase + 0.5);
            my $TotTime = int (scalar @KmlTargets * $KmlTimeBase + 0.5);
            $WpValue = sprintf ("%02d:%02d / %02d:%02d", $CurrTime / 60, $CurrTime % 60, $TotTime / 60, $TotTime % 60);
            }
        $map_canvas->itemconfigure ('MK-OSD-Wp-Value',  '-text' => "$WpValue");

        # Farbe MK-Zeiger abhängig vom GPS Empfang
        my $MkCol= $Cfg->{'mkcockpit'}->{'ColorMkSatNo'};
        if ( $SatsInUse >= 1 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatLow'} ; }
        if ( $SatsInUse >= 6 ) { $MkCol = $Cfg->{'mkcockpit'}->{'ColorMkSatGood'}; }
        $map_canvas->itemconfigure ('MK-Arrow', '-fill' => $MkCol);


        # Show/Hide SatFix Icon
        if ($SatsInUse >= 6 )
            {
            $map_canvas->coords('Satellite', $MapSizeX-300, 10, );
            }
        else
            {
            # move icon out of sight
            $map_canvas->coords('Satellite', 0, -100, );
            }


        # Variometer Pointer
        my $dy = -$MkOsd{'Variometer'} * 10;
        $map_canvas->coords('Map-Variometer-Pointer', 5, $MapSizeY/2+$dy, 20, $MapSizeY/2+10+$dy, 20, $MapSizeY/2-10+$dy);

        #
        # System checks
        #

        if (($MkOsd{'MKFlags'} & 0x01) == 0) { &MkMessage ($Translate{'MsgMotorOff'}, "Timer-MapOverlay"); }
        if (($MkOsd{'MKFlags'} & 0x02) == 0) { &MkMessage ($Translate{'MsgNotFlying'}, "Timer-MapOverlay"); }
        if ($MkOsd{'MKFlags'} & 0x04 )       { &MkMessage ($Translate{'MsgCalibrate'}, "Timer-MapOverlay"); }
        if ($MkOsd{'MKFlags'} & 0x08 )       { &MkMessage ($Translate{'MsgStart'}, "Timer-MapOverlay") }
        if ($MkOsd{'MKFlags'} & 0x10 )       { &MkMessage ($Translate{'MsgEmergencyLanding'}, "Timer-MapOverlay"); }
        if ($MkOsd{'NCFlags'} & 0x08)        { &MkMessage ($Translate{'MsgRangeLimit'}, "Timer-MapOverlay"); }

        # RC range check
        my $RcQuality = $MkOsd{'RC_Quality'};
        if ( $RcQuality < 100 )
            {
            &MkMessage ($Translate{'MsgRcError'}, "Timer-MapOverlay");
            }
        elsif ( $RcQuality < 150 )
            {
            &MkMessage ($Translate{'MsgRcWarning'}, "Timer-MapOverlay");
            }

        # Sat reception quality
        if ( $SatsInUse == 0 )
            {
            &MkMessage ($Translate{'MsgNoSatReception'}, "Timer-MapOverlay");
            }
        elsif ( $SatsInUse > 0  and $SatsInUse < 6 )
            {
            &MkMessage ($Translate{'MsgWeakSatReception'}, "Timer-MapOverlay");
            }

        # MK Border check
        if ( ! &IsInsideBorder($MkPos_x, $MkPos_y) )
            {
            &MkMessage ($Translate{'MsgOutsideBorder'}, "Timer-MapOverlay");
            }


        #
        # Show Balloon, when aproaching Target
        #

        $map_canvas->delete('Target-Balloon');  # delete old Balloon

 
        if ( $Mode ne "Free" and $MkOsd{'TargetPos_Stat'} == 1  and $MkOsd{'TargetPosDev_Dist'} /10 < 25 )
            {
            my $BalloonLines = 1;
            $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 ( $MkOsd{'NCFlags'} & 0x20  and  $Mode 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 ( $MkOsd{'NCFlags'} & 0x04  and  $Mode 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 ( $MkOsd{'NCFlags'} & 0x04  and  $Mode 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',
                                    );

            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');
            }
        }
    else
        {
        # keine aktuellen OSD Daten vom MK verfügbar
        &MkMessage ($Translate{'MsgNoData'}, "Timer-MapOverlay");
        }


    # Wp-Number input from keyboard
    $KbTimer++;
    if ( $CbPlayerKey ne "" )
        {
        # Key pressed
        $KbNum = "$KbNum" . "$CbPlayerKey";

        $CbPlayerKey = "";
        $KbTimer = 0;
        }
    if ( $KbTimer > 7  and $KbNum ne "" )
        {
        # number complete, set target
        my $WpIndex = sprintf ("%d", $KbNum);
        &WpTargetSet ($WpIndex - 1);

        # prepare for next number
        $KbNum = "";
        }
   

    # Show System Messages
    &MkMessageShow();

    });

#      
# Timer: 0.1s - Tracking Anzeige aktualisieren
#
if ( $Cfg->{'track'}->{'Active'} =~ /y/i )
    {
    $frame_map_top->repeat (100, sub
        {
        # Clear old messages from this timer
        &MkMessageInit ("Timer-Tracking");    

        lock (%MkOsd);              # until end of block

        # Aktuell gültige Karte
        my %Map = %{$Maps{'Current'}};

        # Zeiger neu zeichnen
        my $ServoPan = @ServoPos[$MkTrack{'ServoPan'}];
        if ( $ServoPan ne ""  )
            {
            my $x0 = $TrackSizeX/2;    
            my $y0 = $TrackSizeY - 0 - $TrackOffY;
            my $x1 = $TrackSizeX/2 - ($TrackPtrLen-22) * cos( deg2rad $ServoPan);
            my $y1 = $TrackSizeY   - ($TrackPtrLen-22) * sin (deg2rad $ServoPan) - $TrackOffY;
            $track_canvas->coords ('Track-Ptr', $x0, $y0, $x1, $y1);
            }

        # Farbe Zeiger abhängig vom GPS Empfang
        my $SatsInUse = $MkOsd{'SatsInUse'};
        my $TrackPtrCol= 'red';
        if ( $SatsInUse >= 1 ) { $TrackPtrCol = 'orange'; }
        if ( $SatsInUse >= 6 ) { $TrackPtrCol = 'green'; }
        $track_canvas->itemconfigure ('Track-Ptr', '-fill' => $TrackPtrCol);
        });
    }


#      
# Timer: 0.5s - Waypoint Player
#
$frame_map_top->repeat (500, sub
    {
    # Clear old messages from this timer
    &MkMessageInit ("Timer-Player");        

    lock (%MkOsd);              # until end of block

    if ($MkOsd{'NCFlags'} & 0x04)
        {
        # NC is in WPT Mode

        if ( $PlayerMode eq "Pause" )
            {
            if ( $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
                {
                # Gespeicherte Pausen-Pos senden
                &MkFlyTo ( '-lat'  => $PlayerPause_Lat,
                           '-lon'  => $PlayerPause_Lon,
                           '-holdtime' => "60",
                           '-mode' => "Target",
                         );
                }
            }

        if ( $PlayerMode eq "Home" )
            {
            if ( $MkOsd{'HomePos_Stat'} == 1 )
                {
                # Gespeicherte Home-Pos senden
                &MkFlyTo ( '-lat'  => $MkOsd{'HomePos_Lat'},
                           '-lon'  => $MkOsd{'HomePos_Lon'},
                           '-holdtime' => "60",
                           '-mode' => "Target",
                         );
                }
            }


        if ( $PlayerWptKmlMode ne 'WPT' )
            {
            # not in Wp mode
            return;
            }


        if ( $PlayerMode eq "Play"  )
            {

            if ( $PlayerRandomMode =~ /RND/i  or $PlayerRandomMode =~ /STD/i )
                {
                my $WpCnt = scalar @Waypoints;
                if ( $WpCnt > 0  and  $WpPlayerIndex < $WpCnt )
                    {
                    # Target WP-Pos senden
                    my $Wp = $Waypoints[$WpPlayerIndex];
                    my $Wp_Lon = $Wp->{'Pos_Lon'};
                    my $Wp_Lat = $Wp->{'Pos_Lat'};
                    if ( $Wp_Lat ne ""  and  $Wp_Lon ne "" )
                        {
                        &MkFlyTo ( '-lat'  => $Wp_Lat,
                                   '-lon'  => $Wp_Lon,
                                   '-holdtime' => "60",
                                   '-mode' => "Target",
                                 );
                        }
                    }
                }

            if ( $PlayerRandomMode =~ /MAP/i )
                {
                # Target Map-Pos senden
                &MkFlyTo ( '-x'  => $RandomTarget_x ,
                           '-y'  => $RandomTarget_y ,
                           '-holdtime' => "60",
                           '-mode' => "Target",
                         );
                }
 
            # Ziel erreicht?
            if ( &WpCheckTargetReached() )
                {
                &WpTargetNext();
                }
            }
        }

    # WP Player Holdtime count down
    if ( $WpPlayerHoldtime > 0  )
        {
        $WpPlayerHoldtime --;
        }
    });


#      
# Timer: variabel - KML Player
#
my $KmlTimeBase = $Cfg->{'waypoint'}->{'KmlTimeBase'} || 1.0;
$KmlTimeBase *= 1000;

$frame_map_top->repeat ($KmlTimeBase, sub
    {

    # Clear old messages from this timer
    &MkMessageInit ("Timer-KMLPlayer");    

    if ( $PlayerWptKmlMode ne 'KML' )
        {
        # not in KML mode
        return;
        }

    lock (%MkOsd);              # until end of block

    if ($MkOsd{'NCFlags'} & 0x04)
        {
        # NC is in WPT Mode

        # Pause, Home is handled in WPT-Timer

        my $KmlCnt = scalar @KmlTargets;
        if ( $PlayerMode eq "Play"  and  $KmlCnt > 0  and  $KmlPlayerIndex < $KmlCnt )
            {

            my $Lat = $KmlTargets[$KmlPlayerIndex]->{'Lat'};
            my $Lon = $KmlTargets[$KmlPlayerIndex]->{'Lon'};
            my $Alt = $KmlTargets[$KmlPlayerIndex]->{'Alt'};

            &MkFlyTo ( '-lat'             => $Lat,
                       '-lon'             => $Lon,
                       '-alt'             => $Alt,
                       '-holdtime'        => "60",
                       '-mode'            => "Target",
                     );

            # proceed to next Target
            $KmlPlayerIndex ++;
            if ( $KmlPlayerIndex >= scalar @KmlTargets )
                {
                $KmlPlayerIndex = 0;
                }
            }
        }
    });


#      
# Timer: 1s
#
$frame_map_top->repeat (1000, sub
    {
    # Clear old messages from this timer
    &MkMessageInit ("Timer-Misc-1s");    

    lock (%MkOsd);              # until end of block

    # Aktuell gültige Karte
    my %Map = %{$Maps{'Current'}};

    if ( $MkOsd{'_Timestamp'} >= time -2 )
        {

        # Heartbeat MK Datenübertragung
        if ( time %2 )
            {
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatLarge', );
            }
        else
            {
            $map_canvas->itemconfigure('Heartbeat', '-image' => 'HeartbeatSmall', );
            }

        # Flugzeit aktualisieren
        # Flugzeit selber mitzählen, da $MkOsd{'FlyingTime'} immer 0 (0.14b)
        if ( $MkOsd{'MKFlags'} & 0x02 )
            {
            $MkFlyingTime += 1;
            }

        # Update ODO-Meter
        if ( $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'CurPos_Stat'} == 1 )
            {
            my $C_Lat = $MkOsd{'CurPos_Lat'};
            my $C_Lon = $MkOsd{'CurPos_Lon'};

            if ( $OdoFirst ne "" )
                {
                my ($Dist, $Bearing) = MapGpsTo($C_Lat, $C_Lon, $OdoPos_Lat, $OdoPos_Lon );
                $OdoMeter += $Dist;
                }
            $OdoPos_Lat = $C_Lat;
            $OdoPos_Lon = $C_Lon;
            $OdoFirst = "1";
            }

        # Footprint
        if ( $Cfg->{'mkcockpit'}->{'FootprintLength'} > 0 )
            {
            if ( $MkOsd{'SatsInUse'} > 4  and  $MkOsd{'CurPos_Stat'} == 1 )
                {
                # neuen Footprint hinten anhaengen
                my ($x, $y) = &MapGps2XY($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'});
                push @Footprint, $x, $y;
                }

            while ( $#Footprint / 2  >  $Cfg->{'mkcockpit'}->{'FootprintLength'} )
                {
                # alte Footprints entfernen
                splice @Footprint, 0, 2;
                }

            &FootprintRedraw();
            }

 
        # tracking antenne
        if ( $MkOsd{'MKFlags'} & 0x01  and  ! $MkTrack{'IsRunning'} and
             $Cfg->{'track'}->{'Active'} =~ /y/i )
            {
            # start track at 1st motor start
            $track_thr = threads->create (\&TrackAntennaGps)->detach();
            $MkTrack{'IsRunning'} = "Running";
            }
        }

    });


MainLoop();   # should never end


#-----------------------------------------------------------------
# Subroutines
#-----------------------------------------------------------------                       


# 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 on Canvas
sub WpHide()
   {
   $map_canvas->delete('Waypoint');
   $map_canvas->delete('WaypointNumber');
   $map_canvas->delete('Waypoint-Connector');
   }
   


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



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


# Hide Kml-Track on Canvas
sub KmlHide()
   {
   $map_canvas->delete('KML-Track');
   }


# Switch player between WPT and KML
sub WptKmlSwitch()
    {
    my ($Mode) = @_;

    # Wpt/Kml-Player-Icon loeschen und neu anzeigen
    $map_canvas->delete('Wp-WptKml');

    if ( $Mode =~ /KML/i )
        {
        $PlayerWptKmlMode = 'KML';

        # set player button to KML
        $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();
        }

    if ( $Mode =~ /WPT/i )
        {
        $PlayerWptKmlMode = 'WPT';

        # set player button to WPT
        $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
                                 '-tags' => 'Wp-WptKml',
                                 '-anchor' => 'nw',
                                 '-image'  => 'WpWpt-Foto',
                                 );

        # delete Kml-Track from canvas
        &KmlHide();

        # Show waypoints
        if ( $PlayerRandomMode ne 'MAP' )
            {
            &WpRedrawIcons()
            }
        if ( $PlayerRandomMode eq 'STD' )
            {
            &WpRedrawLines()
            }
        }
    }


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

    $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 ( $MkOsd{'_Timestamp'} >= time-2  and   # Gueltige OSD Daten
             $MkOsd{'NCFlags'} & 0x04  and         # WPT-Mode
             $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'CurPos_Stat'} == 1 and  $MkOsd{'HomePos_Stat'} == 1)
            {
            # 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
                }
            }
        }

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


#
# GUI Call Back
#

# Player CallBack: Play/Pause button
sub CbPlayerPlayPause()
    {
    # Play/Pause-Icon loeschen und neu anzeigen
    $map_canvas->delete('Wp-PlayPause');

    if ( ($PlayerMode eq "Pause") or  ($PlayerMode eq "Stop") or  ($PlayerMode eq "Home") )
        {
        $PlayerMode = 'Play';
        $WpPlayerHoldtime = -1;
        $map_canvas->createImage($MapSizeX/2+100, $MapSizeY-48,
                                 '-tags' => 'Wp-PlayPause',
                                 '-anchor' => 'nw',
                                 '-image'  => 'WpPause-Foto',
                                 );
        }
    else
        {
        $PlayerMode = 'Pause';
        $WpPlayerHoldtime = -1;
        $map_canvas->createImage($MapSizeX/2+100, $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 ( $MkOsd{'_Timestamp'} >= time-2 )
            {
            # Gueltige OSD Daten
            if ( $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'CurPos_Stat'} == 1 )
                {
                $PlayerPause_Lon = $MkOsd{'CurPos_Lon'};
                $PlayerPause_Lat = $MkOsd{'CurPos_Lat'};
                }
            }
        }
    }


# Player CallBack: Next
sub CbPlayerNext()
    {
    if ( $PlayerMode ne 'Stop' )
        {

        if ( $PlayerWptKmlMode eq 'WPT' )
           {
           &WpTargetNext();
           }

        if ( $PlayerWptKmlMode eq 'KML' )
           {
           &KmlTargetNext();
           }

        }
    }


# Player CallBack: Prev
sub CbPlayerPrev()
    {
    if ( $PlayerMode ne 'Stop' )
        {

        if ( $PlayerWptKmlMode eq 'WPT' )
           {
           &WpTargetPrev();
           }

        if ( $PlayerWptKmlMode eq 'KML' )
           {
           &KmlTargetPrev();
           }

        }
    }


# Player CallBack: First
sub CbPlayerFirst()
    {
    if ( $PlayerMode ne 'Stop' )
        {

        if ( $PlayerWptKmlMode eq 'WPT' )
           {
           &WpTargetFirst();
           }

        if ( $PlayerWptKmlMode eq 'KML' )
           {
           &KmlTargetFirst();
           }

        }
    }

# Player CallBack: Last
sub CbPlayerLast()
    {
    if ( $PlayerMode ne 'Stop' )
        {

        if ( $PlayerWptKmlMode eq 'WPT' )
           {
           &WpTargetLast();
           }

        if ( $PlayerWptKmlMode eq 'KML' )
           {
           &KmlTargetLast();
           }

        }
    }


# Player CallBack: Home
sub CbPlayerHome()
    {
    if ( $PlayerMode ne 'Stop' )
        {
        $PlayerMode = 'Home';
        &WpTargetFirst();

        $map_canvas->delete('Wp-PlayPause');
        $map_canvas->createImage($MapSizeX/2+100, $MapSizeY-48,
                                 '-tags'   => 'Wp-PlayPause',
                                 '-anchor' => 'nw',
                                 '-image'  => 'WpPlay-Foto',
                                 );
        }
    }


# Player CallBack: Stop
sub CbPlayerStop()
    {
    if ( $PlayerMode ne 'Stop' )
        {
        $PlayerMode = 'Stop';
        &WpTargetFirst();

        # set Play/Pause Icon to "Play
        $map_canvas->delete('Wp-PlayPause');
        $map_canvas->createImage($MapSizeX/2+100, $MapSizeY-48,
                                 '-tags'   => 'Wp-PlayPause',
                                 '-anchor' => 'nw',
                                 '-image'  => 'WpPlay-Foto',
                                 );


        # WP resend required
        $WaypointsModified = 1;

        # switch player to Wp Mode
        &WptKmlSwitch ('WPT');
        }
    }


# Player CallBack: Move MK in Pause-Mode
sub CbPlayerMove()
    {
    my ($Id, $DirX, $DirY) = @_;

    if ( $PlayerMode eq 'Pause'  and
         $PlayerPause_Lat ne ""  and  $PlayerPause_Lon ne "" )
        {
        my $Dist = $Cfg->{'waypoint'}->{'PauseMoveDist'} || 1;  # 1m default

        my $BearingTop = &MapAngel() - 90.0;
        my $BearingKey = rad2deg atan2($DirX, $DirY);
        my $Bearing = $BearingTop + $BearingKey;

        ($PlayerPause_Lat, $PlayerPause_Lon) = &MapGpsAt($PlayerPause_Lat, $PlayerPause_Lon, $Dist, $Bearing)
        }
    }


# Player CallBack: Toggle WPT/KML button
sub CbPlayerWptKml()
    {

    if ( $PlayerWptKmlMode =~ /WPT/i )
        {
        # switch player to KML Mode
        &WptKmlSwitch ('KML');
        }

    elsif ( $PlayerWptKmlMode =~ /KML/i )
        {
        # WP resend required
        $WaypointsModified = 1;

        # switch player to Wp Mode
        &WptKmlSwitch ('WPT');
        }

    }


# Player CallBack: Toggle Random modes
sub CbPlayerWptRandom()
    {
    # Hide old Icon
    $map_canvas->delete('Wp-WptRandom');

    if ( $PlayerRandomMode eq "STD" )
        {
        $PlayerRandomMode = "RND";
        $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
                                 '-tags' => 'Wp-WptRandom',
                                 '-anchor' => 'nw',
                                 '-image'  => 'WpRandomMap-Foto',
                                );

        # delete old connectors from canvas
        $map_canvas->delete('Waypoint-Connector');  
        }

    elsif ( $PlayerRandomMode eq "RND" )
        {
        $PlayerRandomMode = "MAP";
        $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();
        }

    else
        {
        $PlayerRandomMode = "STD";
        $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
                                 '-tags' => 'Wp-WptRandom',
                                 '-anchor' => 'nw',
                                 '-image'  => 'WpRandomOn-Foto',
                                );

        # redraw connectors and Icons on canvas
        &WpRedrawLines();
        &WpRedrawIcons();
        }
    }

# Player CallBack: Number Keys
sub CbPlayerNum()
    {
    my ($Id, $Num) = @_;

    $CbPlayerKey = "$CbPlayerKey" . "$Num";
    }

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

    }


#
# 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 ( $MkOsd{'_Timestamp'} >= time-2 )
                    {
                    # 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' => 30,
                                 '-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__