Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
827 - 1
#!/usr/bin/perl
2
#!/usr/bin/perl -d:ptkdb
3
 
4
###############################################################################
5
#
6
# libmkcockpit.pl -  MK Mission Cockpit - Subroutined for GUI Frontend
7
#
8
# Copyright (C) 2009  Rainer Walther  (rainerwalther-mail@web.de)
9
#
10
# Creative Commons Lizenz mit den Zusaetzen (by, nc, sa)
11
#
12
# Es ist Ihnen gestattet: 
13
#     * das Werk vervielfältigen, verbreiten und öffentlich zugänglich machen
14
#     * Abwandlungen bzw. Bearbeitungen des Inhaltes anfertigen
15
# 
16
# Zu den folgenden Bedingungen:
17
#     * Namensnennung.
18
#       Sie müssen den Namen des Autors/Rechteinhabers in der von ihm festgelegten Weise nennen.
19
#     * Keine kommerzielle Nutzung.
20
#       Dieses Werk darf nicht für kommerzielle Zwecke verwendet werden.
21
#     * Weitergabe unter gleichen Bedingungen.
22
#       Wenn Sie den lizenzierten Inhalt bearbeiten oder in anderer Weise umgestalten,
23
#       verändern oder als Grundlage für einen anderen Inhalt verwenden,
24
#       dürfen Sie den neu entstandenen Inhalt nur unter Verwendung von Lizenzbedingungen
25
#       weitergeben, die mit denen dieses Lizenzvertrages identisch oder vergleichbar sind.
26
# 
27
# Im Falle einer Verbreitung müssen Sie anderen die Lizenzbedingungen, unter welche dieses
28
# Werk fällt, mitteilen. Am Einfachsten ist es, einen Link auf diese Seite einzubinden.
29
# 
30
# Jede der vorgenannten Bedingungen kann aufgehoben werden, sofern Sie die Einwilligung
31
# des Rechteinhabers dazu erhalten.
32
# 
33
# Diese Lizenz lässt die Urheberpersönlichkeitsrechte unberührt.
34
# 
35
# Weitere Details zur Lizenzbestimmung gibt es hier:
36
#   Kurzform: http://creativecommons.org/licenses/by-nc-sa/3.0/de/
37
#   Komplett: http://creativecommons.org/licenses/by-nc-sa/3.0/de/legalcode
38
#
39
###############################################################################
40
# 2009-08-09 0.2.5 rw subroutines moved from mkcockpit.pl
41
# 2009-09-05 0.2.6 rw POI heading control added
42
# 2009-10-10 0.2.7 rw Layout Config-dialog
43
#                     Fix Message-Balloon in KML-Mode
44
# 2009-10-25 0.3.0 rw NC 0.17
45
#                     Read/Write KopterTool WPL Waypoint list
46
#                     configuration Combo Box
47
# 2010-02-10 0.4.0 rw Show Grid on map
48
#                     Show crosshair in player pause mode
49
#                     joystick and 3D-Mouse support
50
#                     Cfg file selection dialog
51
#                     serial channel
52
#                     Event engine
53
#                     External control - Limit, expo
54
#                     Resize WP-Icon to 24x48 pixel
55
# 2010-02-14 0.4.1 rw ExpoLimit
56
# 2010-03-07 0.4.2 rw Input control parser added
57
#                     FctKey, RcStick, RcPoti input device
58
#                     MkIsLowBat     
59
# 2010-09-10 0.5.2 rw Redraw canvas after configuration change 
60
# 2010-10-16 0.5.4 rw Spline speed controlled mode
61
#                     FctKeyToggle() added
62
#
63
###############################################################################
64
 
65
$Version{'libmkcockpit.pl'} = "0.5.4 - 2010-10-16";
66
 
67
 
68
# check, if %MkOsd is valid
69
sub MkOsdIsValid()
70
    {
71
    return ( $MkOsd{'_Timestamp'} >= time-2 );
72
    }
73
 
74
# check, if current GPS position is valid
75
sub CurPosIsValid()
76
    {
77
    return ( &MkOsdIsValid()  and  $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'CurPos_Stat'} == 1 );
78
    }
79
 
80
# check, if home GPS position is valid
81
sub HomePosIsValid()
82
    {
83
    return ( &MkOsdIsValid()  and  $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'HomePos_Stat'} == 1 );
84
    }
85
 
86
# check, if target GPS position is valid
87
sub TargetIsValid()
88
    {
89
    return ( &MkOsdIsValid()  and  $MkOsd{'SatsInUse'} >= 6  and  $MkOsd{'TargetPos_Stat'} == 1  );
90
    }
91
 
92
# check, if motor are on
93
sub MkIsMotorOn()
94
    {
95
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x01  );
96
    }
97
 
98
# check, if MK is flying
99
sub MkIsFlying()
100
    {
101
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x02  );
102
    }
103
 
104
# check, if MK is calibrating
105
sub MkIsCalibrating()
106
    {
107
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x04  );
108
    }
109
 
110
# check, if Motor is starting
111
sub MkIsMotorStarting()
112
    {
113
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x08  );
114
    }
115
 
116
# check, Emergency Landing
117
sub MkEmergencyLanding()
118
    {
119
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x10  );
120
    }
121
# check, if Battery Low voltage
122
sub MkIsLowVoltage()
123
    {
124
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x20  );
125
    }
126
 
127
# check, if MK is FREE Mode
128
sub MkIsFreeMode()
129
    {
130
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x01  );
131
    }
132
 
133
# check, if MK is in PH Mode
134
sub MkIsPhMode()
135
    {
136
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x02  );
137
    }
138
 
139
# check, if MK is in WPT Mode
140
sub MkIsWptMode()
141
    {
142
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x04  );
143
    }
144
 
145
# check, Range Limit
146
sub MkRangeLimit()
147
    {
148
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x08  );
149
    }
150
 
151
# check, Serial Link
152
sub MkSerialLink()
153
    {
154
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x10  );
155
    }
156
 
157
# check, Target reached
158
sub MkTargetReached()
159
    {
160
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x20  );
161
    }
162
 
163
# check, Manual Control
164
sub MkManualControl()
165
    {
166
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x40  );
167
    }
168
 
169
 
170
# Get altitude (hoehensensor)
171
sub AltitudeAir ()
172
    {
173
    return ( $MkOsd{'Altimeter'} / $Cfg->{'map'}->{'AltFactor'} );
174
    }
175
 
176
# Get altitude (GPS)
177
sub AltitudeGPS ()
178
    {
179
    return ( $MkOsd{'CurPos_Alt'} - $MkOsd{'HomePos_Alt'} );
180
    }
181
 
182
# Get altitude (average hoehensensor , GPS)
183
sub Altitude ()
184
    {
185
    my $Alt =  ( 4 * &AltitudeAir + &AltitudeGPS ) / 5;
186
    return ($Alt);
187
    }
188
 
189
 
190
# check, if Fct-Key "Num" pressed, Num = 1..12
191
sub FctKey()
192
    {
193
    my ($Num) = @_;
194
 
195
    $Num--;
196
    return (($Stick{'FctKey'} >> $Num) & 1) == 1;
197
    }
198
 
199
# check, if Fct-Key "Num" pressed (Toggle), Num = 1..12
200
sub FctKeyToggle()
201
    {
202
    my ($Num) = @_;
203
 
204
    $Num--;
205
    return (($Stick{'FctKeyToggle'} >> $Num) & 1) == 1;
206
    }
207
 
208
# Get RcPoti value. Poti = 1..8
209
sub RcPoti()
210
    {
211
    my ($Poti) = @_;
212
 
213
    return $Stick{"RcPoti" . "$Poti"};
214
    }
215
 
216
 
217
# Get Rc Stick value. Stick = Nick, Roll, Gas, Gier
218
sub RcStick()
219
    {
220
    my ($Stick) = @_;
221
 
222
    return $Stick{"RcStick" . "$Stick"};
223
    }
224
 
225
 
226
# range 0 .. 255
227
sub CheckUnsignedChar()
228
    {
229
    my ($U8) = @_;
230
 
231
    if ( $U8 < 0)   { $U8 = 0; };
232
    if ( $U8 > 255) { $U8 = 255; };
233
    return $U8;
234
    }
235
 
236
# range -128 .. 127
237
sub CheckSignedChar()
238
    {
239
    my ($S8) = @_;
240
 
241
    if ( $S8 < -128) { $S8 = -128; };
242
    if ( $S8 > 127)  { $S8 = 127; };
243
    return $S8;
244
    }
245
 
246
# Set serial Channel value. Num: 0..11, Val: -128..0..127
247
sub SerialChannel()
248
    {
249
    my ($Num, $Val) = @_;
250
 
251
    my $Key = sprintf ("SerialChannel%02d", $Num + 1);
252
 
253
    lock (%MkSerialChannel);    # until end of block
254
 
255
    $MkSerialChannel{$Key} = &CheckSignedChar($Val);  
256
 
257
    # timestamp, when channel value was set
258
    $MkSerialChannel{'_Timestamp'} = time;  
259
    }
260
 
261
 
262
# Limit: 0% .. 100%
263
# Expo : -100% .. 0 .. 100%
264
sub ExpoLimit ()
265
    {
266
    my ($StickMin, $StickMax, $Stick, $Expo, $LimitMin, $LimitMax) = @_;
267
 
268
    if ( $Expo ne "" )
269
        {
270
        # neg. Expo: 1..0.2  (0% .. -100%)
271
        # pos. Expo: 1..5    (0% ..  100%)
272
 
273
        if ( $Expo >= 0 )
274
            {
275
            $Expo = 1 + $Expo / 100 * 4;
276
            }
277
        else
278
            {
279
            $Expo = 1 + $Expo / 100 * 0.8;
280
            }
281
 
282
        if( $Stick >= 0 )
283
            {
284
            $Stick = $StickMax * ( $Stick ** $Expo ) / ( $StickMax ** $Expo);
285
            }
286
        else
287
            {
288
            $Stick = $StickMin * ( (- $Stick) ** $Expo ) / ( (- $StickMin) ** $Expo);
289
            }
290
        }
291
 
292
    # Travel limiter
293
    if ( $Stick >= 0  and  $LimitMax ne "" )
294
        {
295
        $Stick = $Stick * $LimitMax / 100;
296
        }
297
    elsif ( $Stick < 0  and  $LimitMin ne "" )
298
        {
299
        $Stick = $Stick * $LimitMin / 100;
300
        }
301
 
302
    return ($Stick);
303
    }
304
 
305
 
306
# Multipoint Curve
307
sub Curve ()
308
    {
309
    my ($Min, $Max, $Resolution, $Stick, @Curve) = @_;
310
 
311
    my $Points = scalar @Curve;
312
    if ( $Points < 2 )
313
        {
314
        # need at least 2 points
315
        return $Stick;
316
        }
317
 
318
    my $Val;
319
    if ( $Stick < $Min )
320
        {
321
        $Val= $Curve[0];
322
        }
323
    elsif ( $Stick > $Max )
324
        {
325
        $Val = $Curve[$Points -1];
326
        }
327
    else
328
        {
329
        my $i = 0;
330
        my $dx = ($Max - $Min) / ($Points - 1);
331
        for (my $x = $Min; $x < $Max; $x += $dx)
332
            {
333
            if ( $Stick >= $x  and  $Stick <= $x + $dx)
334
                {
335
                my $y1 = $Curve[$i];
336
                my $y2 = $Curve[$i+1];
337
                my $dy = $y2 - $y1;
338
 
339
                $Val = $y1 + $dy / $dx * ($Stick - $x);
340
                last;
341
                }
342
 
343
            $i ++;
344
            }
345
        }
346
 
347
    # Prozent in steps umrechnen
348
    $Stick = $Val / 100 * $Resolution;
349
 
350
    return $Stick;
351
    }
352
 
353
 
354
# parse input controls for one output channel
355
# Syntax: [ChannelOption,par,par] + Control1[_Reverse],par,par,par + Control2 ...  + Control3 ...
356
sub ParseControls
357
    {
358
    my ($Channel, $ControlVal, $Expo, $Limit, $Timing) = @_;
359
 
360
    if ( $Expo  eq "" ) { $Expo  = 0; };
361
    if ( $Limit eq "" ) { $Limit = 100; };
362
 
363
    my $ChannelRes = 125;             # Channel Resolution pos+neg = 250 Steps
364
    my $ChannelInc = 0;               # Channel Incremental Mode: 0, 1, 2
365
    my $ChannelReverse = 1;           # Channel reverse factor, 1 , -1
366
    my $ChannelOffset = 0;            # Channel Offset in %
367
    my $ChannelTravelNeg = $Limit;    # Channel travel in %
368
    my $ChannelTravelPos = $Limit;    # Channel travel in %
369
    my $ChannelLimitNeg =  $Limit;    # Channel limit in %
370
    my $ChannelLimitPos =  $Limit;    # Channel limit in %
371
    my $ChannelExpo = $Expo;          # Channel expo in %
372
    my $ChannelSwitchVal = "OFF";     # Channel switch value in % or "OFF"
373
    my $ChannelSwitchMin = 100;       # Channel switch min output in %
374
    my $ChannelSwitchMax = 100;       # Channel switch max output in %
375
    my @ChannelCurve;                 # Channel curve
376
 
377
    my $IsAsymChannel = 0;
378
    if ( "ExternControlGas ExternControlHeight" =~ /$Channel/i )
379
        {
380
        # special handling for asymmetric channels
381
        $IsAsymChannel = 1;
382
 
383
        $ChannelLimitNeg = 0;
384
        $ChannelTravelNeg = 0;
385
        }
386
 
387
    # Channel output is sum of multiple input controls
388
    my $ChannelVal = 0;
389
    $ControlVal =~ s/ //g;
390
    my @Controls = split('\+', $ControlVal);    # controls separated by "+"
391
    foreach $ControlVal (@Controls)
392
        {
393
        # Control params separated by ","
394
        my @Par = split(',', $ControlVal);
395
        my $Control = $Par[0];
396
        my $ControlTravelNeg = $Par[1];   # in %
397
        my $ControlTravelPos = $Par[2];   # in %
398
        my $ControlExpo      = $Par[3];   # in %
399
        my $ControlOffset    = $Par[4];   # in %
400
 
401
        # take Pos as Neg, if only Neg is given
402
        if ( $ControlTravelNeg eq "" ) { $ControlTravelNeg = 100; }
403
        if ( $ControlTravelPos eq "" ) { $ControlTravelPos = $ControlTravelNeg; }
404
 
405
        if ( $ControlExpo eq "" )   { $ControlExpo = 0; }
406
        if ( $ControlOffset eq "" ) { $ControlOffset = 0; }
407
 
408
        my $Val = 0;               # control value
409
        my $ControlReverse = 1;    # control reverse factor: 1, -1
410
 
411
        my ($Control, $Option) = split('_', $Control, 2);      # Control options separeted by "_"
412
        if ( $Control ne "" )
413
            {
414
 
415
            #
416
            # Output Channel Options
417
            #
418
 
419
            if ( $Control =~ /Rev/i )
420
                {
421
                $ChannelReverse *= -1;
422
                next;
423
                }
424
 
425
            if ( $Control =~ /IncStop/i )
426
                {
427
                # stop at neutral point
428
                $ChannelInc = 2;
429
                next;
430
                }
431
            elsif ( $Control =~ /Inc/i )
432
                {
433
                # don't stop at neutral point
434
                $ChannelInc = 1;
435
                next;
436
                }
437
 
438
            if ( $Control =~ /Offset/i )
439
                {
440
                $ChannelOffset = $Par[1];
441
                if ( $ChannelOffset eq "" ) { $ChannelOffset = 0; };
442
                next;
443
                }
444
 
445
            if ( $Control =~ /Travel/i )
446
                {
447
                $ChannelTravelNeg = $Par[1];
448
                $ChannelTravelPos = $Par[2];
449
 
450
                # take symmetrical Pos as Neg, if only Neg is given
451
                if ( $ChannelTravelNeg eq "" ) { $ChannelTravelNeg = 100; }
452
                if ( $ChannelTravelPos eq "" ) { $ChannelTravelPos = $ChannelTravelNeg; }
453
                next;
454
                }
455
 
456
            if ( $Control =~ /Limit/i )
457
                {
458
                $ChannelLimitNeg = $Par[1];
459
                $ChannelLimitPos = $Par[2];
460
 
461
                if ( $IsAsymChannel  and  $ChannelLimitNeg ne ""  and $ChannelLimitPos eq "" )
462
                    {
463
                    # only Neg given. Take Neg as Pos.
464
                    $ChannelLimitPos = $ChannelLimitNeg;
465
                    $ChannelLimitNeg = 0;
466
                    }
467
 
468
                # take symmetrical Pos as Neg, if only Neg is given
469
                if ( $ChannelLimitNeg eq "" ) { $ChannelLimitNeg = 100; }
470
                if ( $ChannelLimitPos eq "" ) { $ChannelLimitPos = $ChannelLimitNeg; }
471
                next;
472
                }
473
 
474
            if ( $Control =~ /Expo/i )
475
                {
476
                $ChannelExpo = $Par[1];
477
                if ( $ChannelExpo eq "" ) { $ChannelExpo = 0; };
478
                next;
479
                }
480
 
481
            if ( $Control =~ /Switch/i )
482
                {
483
                $ChannelSwitchVal = $Par[1];
484
                $ChannelSwitchMin = $Par[2];
485
                $ChannelSwitchMax = $Par[3];
486
                if ( $ChannelSwitchVal eq "" ) { $ChannelSwitchVal = "OFF"; };
487
                if ( $ChannelSwitchMin eq "" ) { $ChannelSwitchMin = 100; };
488
                if ( $ChannelSwitchMax eq "" ) { $ChannelSwitchMax = 100; };
489
                next;
490
                }
491
 
492
            if ( $Control =~ /Curve/i )
493
                {
494
                @ChannelCurve = @Par;
495
                splice @ChannelCurve, 0, 1;
496
                next;
497
                }
498
 
499
            #
500
            # Input Control Options
501
            #
502
 
503
            if ( $Option =~ /Rev/i )
504
                {
505
                $ControlReverse *= -1;
506
                }
507
 
508
            #
509
            # Input controls
510
            #
511
 
512
            # Joystick Button
513
            if ( $Control =~ /^JoystickButton(\d+)/i )
514
                {
515
                my $Button = $1 - 1;
516
                $Val = &JoystickButton($Button) ? $ChannelRes : -$ChannelRes;
517
                }
518
 
519
            # Joystick POV Button
520
            elsif ( $Control =~ /^JoystickPov(\d+)/i )
521
                {
522
                my $Angle = $1;
523
                my $Pov = $Stick{'JoystickPov'} / 100;
524
                $Val = ($Pov == $Angle) ? $ChannelRes : -$ChannelRes;
525
                }
526
 
527
            # Mouse Button
528
            elsif ( $Control =~ /^MouseButton(\d+)/i )
529
                {
530
                my $Button = $1 - 1;
531
                $Val = &MouseButton($Button) ? $ChannelRes : -$ChannelRes;
532
                }
533
 
534
            # Function Key
535
            elsif ( $Control =~ /^FctKey(\d+)/i )
536
                {
537
                my $Key = $1 - 1;
538
                $Val = &FctKey($Key) ? $ChannelRes : -$ChannelRes;
539
                }
540
 
541
            # Function Key (Toggle Mode)
542
            elsif ( $Control =~ /^FctKeyToggle(\d+)/i )
543
                {
544
                my $Key = $1 - 1;
545
                $Val = &FctKeyToggle($Key) ? $ChannelRes : -$ChannelRes;
546
                }
547
 
548
            # Serial Channel
549
            elsif ( $Control =~ /^SerialChannel/i )
550
                {
551
                $Val = $MkSerialChannel{$Control};
552
                }
553
 
554
            # fixed value
555
            elsif ( $Control =~ /^(-*\d+)/i )
556
                {
557
                $Val = $1;
558
                if ( $IsAsymChannel )
559
                    {
560
                    $Val = $Val - $ChannelRes;
561
                    }
562
                }
563
 
564
            # Joystick
565
            elsif ( $Control =~ /^Joystick/i )
566
                {
567
                # Scale Stick 0..StickRange to -125..0..125
568
                $Val = $Stick{$Control} / $Stick{'StickRange'} * 2 * $ChannelRes - $ChannelRes;
569
                }
570
 
571
            # 3D-Maus
572
            elsif ( $Control =~ /^Mouse/i )
573
                {
574
                # Scale Stick 0..StickRange to -125..0..125
575
                $Val = $Stick{$Control} / $Stick{'StickRange'} * 2 * $ChannelRes - $ChannelRes;
576
                }
577
 
578
            # Rc Poti
579
            elsif ( $Control =~ /^RcPoti/i )
580
                {
581
                $Val = $Stick{$Control};
582
                }
583
 
584
            # Rc Stick
585
            elsif ( $Control =~ /^RcStick/i )
586
                {
587
                $Val = $Stick{$Control};
588
                }
589
            else
590
                {
591
                # unknown, fall through
592
                print "ParseControls: $Channel : Unknown Control \"$Control\"\n";
593
                next;
594
                }
595
 
596
            # Control Reverse
597
            $Val *= $ControlReverse;
598
 
599
            # Expo/Limit for each input control
600
            if ( $IsAsymChannel  and  $ChannelInc == 0 )
601
                {
602
                # asymmetric channel 0..250, if not in INC mode
603
                $Val += $ChannelRes;
604
                $Val = &ExpoLimit (0.001, 2 * $ChannelRes, $Val, $ControlExpo, $ControlTravelNeg, $ControlTravelPos);
605
                }
606
            else
607
                {
608
                # symmetric channel -125..0..125
609
                $Val = &ExpoLimit (- $ChannelRes, $ChannelRes, $Val, $ControlExpo, $ControlTravelNeg, $ControlTravelPos);
610
                }
611
 
612
            # Control Offset
613
            $Val = $Val + $ControlOffset / 100 * $ChannelRes;
614
 
615
            # sum controls
616
            $ChannelVal += $Val;
617
            }
618
        }
619
 
620
    # Incremental Channel Mode. Control must be neg ..0..pos
621
    if ( $ChannelInc > 0 )
622
        {
623
        # Channel travel time is 1s for 125 steps at control full speed
624
 
625
        # Channel neutral point crossing detection 
626
        my $Neutral = $ChannelVal;
627
        my $LastNeutral = $Controls{$Channel}{'Neutral'};
628
        $Controls{$Channel}{'Neutral'} = $Neutral;
629
 
630
        my $LastVal = $Controls{$Channel}{'Value'};
631
        $ChannelVal = $LastVal + $ChannelVal * $Timing / 1000;
632
 
633
        if ( $ChannelInc > 1 )
634
            {
635
            # stop at neutral position
636
            if ( $LastVal <= 0  and  $ChannelVal >= 0  and  $LastNeutral > 5  and $Neutral > 5 )
637
                {
638
                # coming from left
639
                $ChannelVal = 0;
640
                }
641
            elsif ( $LastVal >= 0  and  $ChannelVal <= 0  and  $LastNeutral < -5  and $Neutral < -5 )
642
                {
643
                # comimg from right
644
                $ChannelVal = 0;
645
                }
646
            }
647
 
648
        if ( $IsAsymChannel )
649
            {
650
            # asymmetric channel 0..250
651
            if ( $ChannelVal > 2 * $ChannelRes ) { $ChannelVal = 2 * $ChannelRes };
652
            if ( $ChannelVal < 0 )               { $ChannelVal = 0 };
653
            }
654
        else
655
            {
656
            # symmetric channel -125..0..125
657
            if ( $ChannelVal >  $ChannelRes ) { $ChannelVal =  $ChannelRes };
658
            if ( $ChannelVal < -$ChannelRes ) { $ChannelVal = -$ChannelRes };
659
            }
660
 
661
        $Controls{$Channel}{'Value'} = $ChannelVal;
662
        }
663
 
664
 
665
    # channel output processing
666
    if ( $IsAsymChannel )
667
        {
668
        # asymmetric channel 0..250
669
 
670
        # Channel Multipoint curve
671
        $ChannelVal = &Curve (0.001, 2 * $ChannelRes, 2 * $ChannelRes, $ChannelVal, @ChannelCurve);
672
 
673
        # Channel Expo, Travel
674
        $ChannelVal = &ExpoLimit (0.001, 2 * $ChannelRes, $ChannelVal, $ChannelExpo, $ChannelTravelNeg, $ChannelTravelPos);
675
 
676
        # Channel Switch
677
        if ( $ChannelSwitchVal ne "OFF" )
678
            {
679
            if ( $ChannelVal < 2 * $ChannelRes * $ChannelSwitchVal / 100 )
680
                {
681
                $ChannelVal = 2 * $ChannelRes * $ChannelSwitchMin / 100;
682
                }
683
            else
684
                {
685
                $ChannelVal = 2 * $ChannelRes * $ChannelSwitchMax / 100;
686
                }
687
            }
688
 
689
        # Channel Reverse
690
        if ( $ChannelReverse == -1 )
691
            {
692
            $ChannelVal = 2 * $ChannelRes - $ChannelVal;
693
            }
694
 
695
        # Channel offset
696
        $ChannelVal = $ChannelVal + $ChannelOffset / 100 * 2 * $ChannelRes;
697
 
698
        # Channel Limiter
699
        my $Pos = 2 * $ChannelRes * $ChannelLimitPos / 100;
700
        if ( $ChannelVal > $Pos )
701
            {
702
            $ChannelVal = $Pos;
703
            }
704
        my $Neg = 2 * $ChannelRes * $ChannelLimitNeg / 100;
705
        if ( $ChannelVal < $Neg )
706
            {
707
            $ChannelVal = $Neg;
708
            }
709
 
710
        # round to integer
711
        if ( $ChannelVal >= 0 )
712
            {
713
            $ChannelVal = int ($ChannelVal + 0.5);
714
            }
715
        else
716
            {
717
            $ChannelVal = int ($ChannelVal - 0.5);
718
            }
719
        $ChannelVal = &CheckUnsignedChar($ChannelVal);
720
        }
721
    else
722
        {
723
        # symmetric channel -125..0..125
724
 
725
        # Channel Multipoint curve
726
        $ChannelVal = &Curve (-$ChannelRes, $ChannelRes, $ChannelRes, $ChannelVal, @ChannelCurve);
727
 
728
        # Channel Expo, Travel
729
        $ChannelVal = &ExpoLimit (-$ChannelRes, $ChannelRes, $ChannelVal, $ChannelExpo, $ChannelTravelNeg, $ChannelTravelPos);
730
 
731
        # Channel Switch
732
        if ( $ChannelSwitchVal ne "OFF" )
733
            {
734
            if ( $ChannelVal < $ChannelRes * $ChannelSwitchVal / 100 )
735
                {
736
                $ChannelVal = $ChannelRes * $ChannelSwitchMin / 100;
737
                }
738
            else
739
                {
740
                $ChannelVal = $ChannelRes * $ChannelSwitchMax / 100;
741
                }
742
            }
743
 
744
        # Channel Reverse
745
        $ChannelVal *= $ChannelReverse;
746
 
747
        # Channel offset
748
        $ChannelVal = $ChannelVal + $ChannelOffset / 100 * $ChannelRes;
749
 
750
        # Channel Limiter
751
        my $Pos = $ChannelRes * $ChannelLimitPos / 100;
752
        if ( $ChannelVal > $Pos )
753
            {
754
            $ChannelVal = $Pos;
755
            }
756
        my $Neg = - $ChannelRes * $ChannelLimitNeg / 100;
757
        if ( $ChannelVal < $Neg )
758
            {
759
            $ChannelVal = $Neg;
760
            }
761
 
762
        # round to integer
763
        if ( $ChannelVal >= 0 )
764
            {
765
            $ChannelVal = int ($ChannelVal + 0.5);
766
            }
767
        else
768
            {
769
            $ChannelVal = int ($ChannelVal - 0.5);
770
            }
771
        $ChannelVal = &CheckSignedChar($ChannelVal);
772
        }
773
 
774
    return $ChannelVal;
775
    }
776
 
777
 
778
#
779
# Waypoint handling
780
#
781
 
782
# Add a Waypoint to @Waypoints List
783
sub WpAdd()
784
    {
785
    my %Param = @_;
786
    my $Wp_x = $Param{'-x'};
787
    my $Wp_y = $Param{'-y'};
788
    my $Lat  = $Param{'-lat'};
789
    my $Lon  = $Param{'-lon'};
790
    my $Alt  = $Param{'-alt'};
791
 
792
    # x/y and/or Lat/Lon must be passed
793
    if ( $Wp_x eq ""  and  $Wp_y eq "" )
794
        {
795
        ($Wp_x, $Wp_y) = &MapGps2XY($Lat, $Lon);
796
        }
797
    if ( $Lat eq ""  and  $Lon eq "" )
798
        {
799
        ($Lat, $Lon) = &MapXY2Gps($Wp_x, $Wp_y);
800
        }
801
    if ( $Alt eq "" )
802
        {
803
        $Alt = &Altitude();
804
        }
805
 
806
    # kind of unique Tag for this Wp    
807
    my ($t0_s, $t0_us) = gettimeofday;
808
    my $Tag = sprintf "WP-%d.%d", $t0_s, $t0_us;
809
 
810
    # save Wp-Hash in Waypoint-Array
811
    my $Wp = {};        
812
    $Wp->{'Tag'} = $Tag;
813
    $Wp->{'MapX'} = $Wp_x;
814
    $Wp->{'MapY'} = $Wp_y;
815
    $Wp->{'_MapX_Rel'} = $Wp_x / $MapSizeX;
816
    $Wp->{'_MapY_Rel'} = $Wp_y / $MapSizeY;
817
    $Wp->{'Pos_Lat'} = $Lat;
818
    $Wp->{'Pos_Lon'} = $Lon;
819
    $Wp->{'Pos_Alt'} = $Alt;
820
    $Wp->{'Heading'}         = $Cfg->{'waypoint'}->{'DefaultHeading'};
821
    $Wp->{'ToleranceRadius'} = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
822
    $Wp->{'Holdtime'}        = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
823
    $Wp->{'Event_Flag'}      = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
824
    $Wp->{'Speed'}           = $Cfg->{'waypoint'}->{'Speed'} || 10;
825
    push @Waypoints, $Wp;
826
    }
827
 
828
 
829
# Delete Waypoint from @Waypoints List
830
sub WpDelete ()
831
    {
832
    my ($WpIndex) = @_;
833
 
834
    # delete Wp in Waypoint-Array
835
    splice @Waypoints, $WpIndex, 1;
836
    }
837
 
838
 
839
# Delete all Waypoints
840
sub WpDeleteAll ()
841
    {
842
    undef @Waypoints;
843
    $WpPlayerIndex = 0;
844
    $WpPlayerHoldtime = -1;
845
    $SpdPlayerTime = 0;
846
    $SpdPlayerIndex = 0;
847
 
848
    # remove all Wp-Icons and Wp-Number on canvas
849
    &WpHide();
850
    }
851
 
852
# Add current MK-Position to @Waypoints
853
sub WpCapture()
854
    {
855
    my $Lat = 0;
856
    my $Lon = 0;
857
    my $Alt = 0;
858
    for my $i ( 1..8)       # average over n cycles
859
        {
860
        $Lat += $MkOsd{'CurPos_Lat'};
861
        $Lon += $MkOsd{'CurPos_Lon'};
862
        $Alt += &Altitude();
863
        usleep (250000);  # 0.25s
864
        }
865
    $Lat /= 8;
866
    $Lon /= 8;
867
    $Alt /= 8;
868
 
869
    &WpAdd (-lat => $Lat,
870
            -lon => $Lon,
871
            -alt => $Alt,
872
           );
873
    }
874
 
875
# Load @Waypoints from file
876
sub WpLoadFile ()
877
    {
878
    my ($WpFile) = @_;
879
 
880
    if ( $WpFile =~ /.wpl$/i )
881
        {
882
        # load Mikrokopter Tool WP List *.wpl
883
 
884
        my $WpCnt = 0;
885
        my $WpIndex = 0;
886
        my @WpWpl;
887
 
888
        open WPL, "<$WpFile";
889
        my @Wpl = <WPL>;
890
        close WPL;
891
        foreach my $Line (@Wpl)
892
            {
893
            chomp $Line;
894
            if ( $Line =~ /NumberOfWaypoints\s*=\s*(\d*)/i )
895
                {
896
                $WpCnt = $1;
897
                }
898
            elsif ( $Line =~ /\[Waypoint(\d*)\]/i )
899
                {
900
                $WpIndex = $1;
901
                }
902
            elsif ( $Line =~ /(\S*)\s*=\s*(\S*)/i )
903
                {
904
                my $Key = $1;
905
                my $Value = $2;
906
                $WpWpl[$WpIndex]{$Key} = $Value;
907
                }
908
            }
909
 
910
        # WPL Array in Waypoints-Array umkopieren
911
        undef @Waypoints;
912
 
913
        for ( $Index=0; $Index < $WpCnt; $Index++)
914
            {
915
            my $Wp = {};        
916
            my $Tag = sprintf "Waypoint-%d.%d", time, $Index + 1;   # kind of unique Tag for this Wp
917
 
918
            my $Lat = $WpWpl[$Index]{'Latitude'};
919
            my $Lon = $WpWpl[$Index]{'Longitude'};
920
 
921
            ($MapX, $MapY) = &MapGps2XY($Lat, $Lon);
922
            $Wp->{'Tag'}  = $Tag;
923
            $Wp->{'MapX'} = $MapX;
924
            $Wp->{'MapY'} = $MapY;
925
            $Wp->{'_MapX_Rel'} = $MapX / $MapSizeX;
926
            $Wp->{'_MapY_Rel'} = $MapY / $MapSizeY;
927
            $Wp->{'Pos_Lat'} = $Lat;
928
            $Wp->{'Pos_Lon'} = $Lon;
929
            $Wp->{'Pos_Alt'} = &Altitude();
930
            $Wp->{'Heading'}         = $Cfg->{'waypoint'}->{'DefaultHeading'};
931
            $Wp->{'ToleranceRadius'} = $WpWpl[$Index]{'Radius'};
932
            $Wp->{'Holdtime'}        = $WpWpl[$Index]{'DelayTime'};
933
            $Wp->{'Event_Flag'}      = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
934
            $Wp->{'Speed'}           = $Cfg->{'waypoint'}->{'Speed'} || 10;
935
 
936
            push @Waypoints, $Wp;
937
            }
938
        }
939
    else
940
        {
941
        # load Mission Cockpit XML
942
 
943
        # XML in Hash-Ref lesen
944
        my $Wp = XMLin($WpFile, ForceArray => 1);
945
 
946
        # XML Hash-Ref in Wp-Array umkopieren
947
        undef @Waypoints;
948
 
949
        foreach $key (sort keys %$Wp)
950
            {
951
            my $Point = $Wp->{$key}->[0];
952
 
953
            # relative Pixelkoordinaten auf Bildgroesse umrechnen
954
            if ( $Point->{'MapX'} <= 1  and  $Point->{'MapY'} <= 1 )
955
                {
956
                $Point->{'MapX'} = int ( $Point->{'MapX'} * $MapSizeX + 0.5 );
957
                $Point->{'MapY'} = int ( $Point->{'MapY'} * $MapSizeY + 0.5 );
958
                }
959
 
960
            # abs. pixel koordinates not needed
961
            delete $Point->{'MapX_Pixel'};
962
            delete $Point->{'MapY_Pixel'};
963
 
964
            $Point->{'_MapX_Rel'} = $Point->{'MapX'} / $MapSizeX;
965
            $Point->{'_MapY_Rel'} = $Point->{'MapY'} / $MapSizeY;
966
 
967
            if ( $Point->{'Speed'} eq "" )     { $Point->{'Speed'} = $Cfg->{'waypoint'}->{'Speed'} || 10; }
968
            if ( $Point->{'Pos_Alt'}   eq "" ) { $Point->{'Pos_Alt'}   = 5; }
969
 
970
            # GPS Koordinaten für die aktuelle Karte neu aus Map x/y berechnen
971
            my ($Lat, $Lon) = &MapXY2Gps($Point->{'MapX'}, $Point->{'MapY'});
972
            $Point->{'Pos_Lat'} = $Lat;
973
            $Point->{'Pos_Lon'} = $Lon;
974
            push @Waypoints, $Point;
975
            }
976
        }
977
 
978
    # Start with 1st WP
979
    &WpTargetFirst();
980
    }
981
 
982
 
983
# Save @Waypoints to file
984
sub WpSaveFile()
985
    {
986
    my ($WpFile) = @_;
987
 
988
    if ( $WpFile =~ /.wpl$/i )
989
        {
990
        # save Mikrokopter Tool WP List *.wpl
991
 
992
        open WPL, ">$WpFile";
993
 
994
        my $WpCnt = scalar @Waypoints;
995
 
996
        print WPL "[General\]\n";
997
        print WPL "FileVersion=1\n";
998
        print WPL "NumberOfWaypoints=$WpCnt\n";
999
 
1000
        for  $i ( 0 .. $#Waypoints )
1001
            {
1002
            print WPL "\[Waypoint${i}\]\n";
1003
            print WPL "Latitude=$Waypoints[$i]{'Pos_Lat'}\n";
1004
            print WPL "Longitude=$Waypoints[$i]{'Pos_Lon'}\n";
1005
            print WPL "Radius=$Waypoints[$i]{'ToleranceRadius'}\n";
1006
            print WPL "DelayTime=$Waypoints[$i]{'Holdtime'}\n";
1007
            }
1008
        close WPL;
1009
        }
1010
    else
1011
        {
1012
        # save Mission Cockpit XML
1013
 
1014
        # Waypoint-Array in Hash umkopieren
1015
        for  $i ( 0 .. $#Waypoints )
1016
            {
1017
            my $key = sprintf ("WP-%04d", $i);
1018
            my $Wp = {%{$Waypoints[$i]}};        # copy of Hash-content
1019
            $WpOut{$key} = $Wp;
1020
 
1021
            # Pixelkoordinaten relativ zur Bildgroesse speichern
1022
            $WpOut{$key}{'MapX'} /= $MapSizeX;
1023
            $WpOut{$key}{'MapY'} /= $MapSizeY;
1024
            }
1025
 
1026
        # WP-Hash als XML speichern
1027
        &XMLout (\%WpOut,
1028
                 'OutputFile' => $WpFile,
1029
                 'AttrIndent' => '1',
1030
                 'RootName' => 'Waypoints',
1031
                );
1032
        }
1033
    }
1034
 
1035
# Recalc  WP coordinates from relative coordinates
1036
sub WpRecalc()
1037
    {
1038
    for $i (0 .. $#Waypoints)
1039
        {
1040
        my $Wp = $Waypoints[$i];
1041
 
1042
        # new pixel coordinates from relative coordinates
1043
        $Wp->{'MapX'} = int ( $Wp->{'_MapX_Rel'} * $MapSizeX + 0.5 );
1044
        $Wp->{'MapY'} = int ( $Wp->{'_MapY_Rel'} * $MapSizeY + 0.5 );
1045
 
1046
        # new GPS coordinates from pixel coordinates
1047
        my ($Lat, $Lon) = &MapXY2Gps($Wp->{'MapX'}, $Wp->{'MapY'});
1048
        $Wp->{'Pos_Lat'} = $Lat;
1049
        $Wp->{'Pos_Lon'} = $Lon;
1050
        }
1051
    }
1052
 
1053
 
1054
# Get Wp Index from Canvas Id
1055
sub WpGetIndexFromId()
1056
    {
1057
    my ($id) = @_;
1058
 
1059
    my @Tags = $map_canvas->gettags($id);
1060
    my $WpTag = $Tags[1];
1061
 
1062
    for $i (0 .. $#Waypoints)
1063
        {
1064
        my $Wp = $Waypoints[$i];
1065
        if ( $Wp->{'Tag'} eq $WpTag )
1066
            {
1067
            # got it
1068
            return $i;
1069
            }
1070
        }
1071
    return -1;
1072
    }
1073
 
1074
 
1075
# Resend all Waypoints to MK
1076
sub WpSendAll()
1077
    {
1078
    # OSD/Debug Abfragefrequenz verringern, sonst kommen nicht alle Wp im MK an
1079
    # Sicherheitshalber doppelt senden
1080
    $MkSendWp = 1;       # verhindert ueberschreiben im Timer
1081
 
1082
    $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) );   # Frequenz OSD Datensatz, * 10ms
1083
    $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) );   # Frequenz MK Debug Datensatz, * 10ms
1084
    usleep (200000);
1085
    $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) );   # Frequenz OSD Datensatz, * 10ms
1086
    $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) );   # Frequenz MK Debug Datensatz, * 10ms
1087
    usleep (200000);
1088
 
1089
    # Alte WP-Liste im MK löschen
1090
    my $Wp = $Waypoints[0];
1091
    &MkFlyTo ( '-lat'  => $Wp->{'Pos_Lat'},
1092
               '-lon'  => $Wp->{'Pos_Lon'},
1093
               '-mode' => "Waypoint Delete"
1094
             );
1095
 
1096
    for $i (0 .. $#Waypoints)
1097
        {
1098
        my $Wp = $Waypoints[$i];
1099
        &MkFlyTo ( '-lat'             => $Wp->{'Pos_Lat'},
1100
                   '-lon'             => $Wp->{'Pos_Lon'},
1101
                   '-alt'             => $Wp->{'Pos_Alt'},
1102
                   '-heading'         => $Wp->{'Heading'},
1103
                   '-toleranceradius' => $Wp->{'ToleranceRadius'},
1104
                   '-holdtime'        => $Wp->{'Holdtime'},
1105
                   '-eventflag'       => $Wp->{'Event_Flag'},
1106
                   '-mode'            => "Waypoint",
1107
                   '-index'           => $i,
1108
                 );
1109
 
1110
        usleep (150000)  # NC Zeit zum Verarbeiten geben
1111
        }
1112
 
1113
    $MkSendWp = 0;  # normale OSD/Debug Abfragefrequenz wird automatisch im 5s Timer wieder eingestellt
1114
 
1115
    # grey connectors: Wp are sent to MK
1116
    $map_canvas->itemconfigure('Waypoint-Connector',
1117
                               '-fill' => $Cfg->{'mkcockpit'}->{'ColorWpConnector'},
1118
                              );
1119
 
1120
    # MK ist nun synchron mit @Waypoints
1121
    $WaypointsModified = 0;
1122
    }          
1123
 
1124
 
1125
# Redraw Waypoint Icons
1126
sub WpRedrawIcons()
1127
    {
1128
    if ( $PlayerWptKmlMode eq 'WPT' and ($PlayerRandomMode eq 'STD' or $PlayerRandomMode eq 'RND')  or
1129
         $PlayerWptKmlMode eq 'SPD' )
1130
        {
1131
        # delete old icons and Wp-Number from canvas
1132
        $map_canvas->delete('Waypoint');
1133
        $map_canvas->delete('WaypointNumber');
1134
 
1135
        # create new icons
1136
        for $i (0 .. $#Waypoints)
1137
           {
1138
            my $Wp = $Waypoints[$i];
1139
            my $x = $Wp->{'MapX'};
1140
            my $y = $Wp->{'MapY'};
1141
            my $Tag = $Wp->{'Tag'};
1142
 
1143
            # Waypoint Icon
1144
            my $IconHeight = 48;
1145
            my $IconWidth = 24;
1146
            $map_canvas->createImage($x-$IconWidth/2, $y-$IconHeight,
1147
                                     '-tags' => ['Waypoint', $Tag],
1148
                                     '-anchor' => 'nw',
1149
                                     '-image'  => 'Waypoint-Photo',
1150
                                    );
1151
            # Waypoint Number
1152
            my $WpNumber = $i + 1;
1153
            $map_canvas->createText ( $x+3, $y-$IconHeight/2+12,
1154
                                      '-tags' => ['WaypointNumber', $Tag],
1155
                                      '-text' => $WpNumber,
1156
                                      '-font' => '-*-Arial-Bold-R-Normal--*-100-*',
1157
                                      '-fill' => $Cfg->{'mkcockpit'}->{'ColorWpNumber'},
1158
                                      '-anchor' => 'w',
1159
                                     );
1160
 
1161
            }  
1162
        $map_canvas->lower('Waypoint', 'Target');           # waypoint below Target
1163
        $map_canvas->lower('WaypointNumber', 'Waypoint');   # waypoint-number below waypoint
1164
        }
1165
    }
1166
 
1167
 
1168
# Redraw Waypoint+spline connectors, calc Spline coefficients
1169
sub WpRedrawLines()
1170
    {
1171
    # delete old connectors from canvas
1172
    $map_canvas->delete('Waypoint-Connector');  
1173
 
1174
    # Delete old spline
1175
    $map_canvas->delete('Waypoint-Spline');
1176
 
1177
    if ( scalar(@Waypoints) < 2 )
1178
        {
1179
        # need at least 2 WPs
1180
        return;
1181
        }
1182
 
1183
    if ( $PlayerWptKmlMode eq 'SPD')
1184
        {
1185
        #
1186
        # draw spline polygon
1187
        #
1188
 
1189
        my @WpX;
1190
        my @WpY;
1191
        for $i (0 .. $#Waypoints)
1192
            {
1193
            my $Wp = $Waypoints[$i];
1194
            push @WpX, $Wp->{'MapX'};
1195
            push @WpY, $Wp->{'MapY'};
1196
            }
1197
 
1198
        # Calc spline coefficients
1199
        # global - also unsed in SPD-Timer
1200
        @SplineCoeffX = &SplineCalcNaturalCubic(@WpX);
1201
        @SplineCoeffY = &SplineCalcNaturalCubic(@WpY);
1202
 
1203
        # calc spline polygon
1204
        # very crude technique - just break each segment up into Steps lines
1205
        my $Steps = 16;
1206
 
1207
        my @Polygon;
1208
        push @Polygon, &SplineEval(0, %{$SplineCoeffX[0]});
1209
        push @Polygon, &SplineEval(0, %{$SplineCoeffY[0]});
1210
 
1211
        for $i (0 .. $#SplineCoeffX)
1212
            {
1213
            for ($j = 1; $j <= $Steps; $j++)
1214
                {
1215
                $u = $j / $Steps;
1216
                push @Polygon, &SplineEval($u, %{$SplineCoeffX[$i]});
1217
                push @Polygon, &SplineEval($u, %{$SplineCoeffY[$i]});
1218
                }
1219
            }
1220
 
1221
        # draw polygon
1222
        $map_canvas->createLine ( @Polygon,
1223
                                  -tags => 'Waypoint-Spline',
1224
                                  -fill => $Cfg->{'mkcockpit'}->{'ColorWpConnector'} || 'white',
1225
                                  -width => 1,
1226
                                );
1227
 
1228
        $map_canvas->raise('Waypoint-Spline', 'Map');      # Spline above map
1229
        }
1230
 
1231
    elsif ( $PlayerWptKmlMode eq 'WPT'  and  $PlayerRandomMode eq 'STD' )
1232
        {
1233
        #
1234
        # draw WP-connectors
1235
        #
1236
        my $Color = $Cfg->{'mkcockpit'}->{'ColorWpConnector'};
1237
        if ( $WaypointsModified )
1238
            {
1239
            $Color = $Cfg->{'mkcockpit'}->{'ColorWpResend'};
1240
            }
1241
 
1242
        my $Wp = $Waypoints[0];
1243
        my $x_0 = $Wp->{'MapX'};
1244
        my $y_0 = $Wp->{'MapY'};
1245
        my $x_last = $x_0;
1246
        my $y_last = $y_0;
1247
 
1248
        for $i (1 .. $#Waypoints)
1249
            {
1250
            my $Wp = $Waypoints[$i];
1251
            my $x = $Wp->{'MapX'};
1252
            my $y = $Wp->{'MapY'};
1253
 
1254
            $map_canvas->createLine ( $x_last, $y_last, $x, $y,
1255
                                      '-tags' => 'Waypoint-Connector',
1256
                                      '-arrow' => 'last',
1257
                                      '-arrowshape' => [10, 10, 3 ],
1258
                                      '-fill' => $Color,
1259
                                      '-width' => 1,
1260
                                    );
1261
            $x_last = $x;
1262
            $y_last = $y;
1263
            }
1264
        # Connector last WP to 1st WP
1265
        $map_canvas->createLine ( $x_last, $y_last, $x_0, $y_0,
1266
                                  '-tags' => 'Waypoint-Connector',
1267
                                  '-arrow' => 'last',
1268
                                  '-arrowshape' => [10, 10, 3 ],
1269
                                  '-fill' => $Color,
1270
                                  '-width' => 1,
1271
                                );
1272
 
1273
        $map_canvas->raise('Waypoint-Connector', 'Map');   # connector above map
1274
        }
1275
    }
1276
 
1277
 
1278
# Hide Waypoints and connectors on Canvas
1279
sub WpHide()
1280
   {
1281
   $map_canvas->delete('Waypoint');
1282
   $map_canvas->delete('WaypointNumber');
1283
   $map_canvas->delete('Waypoint-Connector');
1284
   $map_canvas->delete('Waypoint-Spline');
1285
   }
1286
 
1287
 
1288
# Hide Kml-Track on Canvas
1289
sub KmlHide()
1290
   {
1291
   $map_canvas->delete('KML-Track');
1292
   }
1293
 
1294
 
1295
# Load @KmlTargets from file
1296
sub KmlLoadFile()
1297
    {
1298
    my ($File) = @_;
1299
 
1300
    # XML in Hash-Ref lesen
1301
    my $Kml = XMLin($File);
1302
 
1303
    # init state maschine
1304
    undef @KmlTargets;
1305
    $KmlPlayerIndex = 0;
1306
 
1307
    my $Coordinates = $Kml->{Document}->{Placemark}->{LineString}->{coordinates};
1308
    foreach $Line (split "\n", $Coordinates)
1309
        {
1310
        chomp $Line;
1311
        $Line =~ s/\s//g;       # remove white space
1312
        if ( $Line ne "" )
1313
            {
1314
            my ($Lon, $Lat, $Alt) = split ",", $Line;
1315
            $Lon = sprintf ("%f", $Lon);
1316
            $Lat = sprintf ("%f", $Lat);
1317
            $Alt = sprintf ("%f", $Alt);
1318
 
1319
            push @KmlTargets, {'Lat' => $Lat,
1320
                               'Lon' => $Lon,
1321
                               'Alt' => $Alt,
1322
                              };
1323
            }
1324
        }
1325
    }
1326
 
1327
# Redraw KML track
1328
sub KmlRedraw()
1329
    {
1330
 
1331
    # delete old Track from canvas
1332
    $map_canvas->delete('KML-Track');
1333
 
1334
    my @Track;
1335
 
1336
    foreach $Target ( @KmlTargets )
1337
        {
1338
        my $Lat = $Target->{'Lat'};
1339
        my $Lon = $Target->{'Lon'};
1340
        my $Alt = $Target->{'Alt'};
1341
        my ($x, $y) = &MapGps2XY($Lat, $Lon);
1342
        push @Track, $x, $y;
1343
        }
1344
 
1345
    if ( scalar @Track >= 4 )  # at least 2 Koordinaten-Paare
1346
        {
1347
        $map_canvas->createLine ( @Track,
1348
                                  '-tags' => 'KML-Track',
1349
                                  '-fill' => $Cfg->{'mkcockpit'}->{'ColorKmlTrack'},
1350
                                  '-width' => 1,
1351
                                );       
1352
 
1353
        $map_canvas->lower('KML-Track', 'Target');        # Track below Target
1354
        }
1355
    }
1356
 
1357
 
1358
# Redraw Footprint
1359
sub FootprintRedraw()
1360
    {
1361
    # delete old Footprint from canvas
1362
    $map_canvas->delete('Footprint');  
1363
 
1364
    if ( scalar @Footprint >= 4 )  # at least 2 Koordinaten-Paare
1365
        {
1366
        $map_canvas->createLine ( @Footprint,
1367
                                  '-tags' => 'Footprint',
1368
                                  '-fill' => $Cfg->{'mkcockpit'}->{'ColorFootprint'},
1369
                                  '-width' => 1,
1370
                                );       
1371
        }
1372
 
1373
    $map_canvas->lower('Footprint', 'Target');
1374
    }
1375
 
1376
 
1377
# Waypoint Player: Set Waypoint - sequence or random
1378
sub WpTargetSet()
1379
    {
1380
    my ($Index) = @_;
1381
 
1382
    my $WpCnt = scalar @Waypoints;
1383
    if ( $Index < 0  or  $Index >= $WpCnt )
1384
        {
1385
        # invalid WP number
1386
        return 1;
1387
        }
1388
 
1389
    my $Wp = $Waypoints[$Index];
1390
    my $Wp_x = $Wp->{'MapX'};
1391
    my $Wp_y = $Wp->{'MapY'};
1392
 
1393
    # is Wp reachable?
1394
    if ( ! &IsTargetReachable($Wp_x, $Wp_y) )
1395
        {
1396
        # new Wp-Target is not reachable
1397
        return 1;
1398
        }
1399
 
1400
    # set new Wp-Target 
1401
    $WpPlayerIndex = $Index;
1402
    $WpPlayerHoldtime = -1;
1403
 
1404
    return 0;
1405
    }
1406
 
1407
 
1408
# Waypoint Player: Goto next Waypoint - sequence or random
1409
sub WpTargetNext()
1410
    {
1411
    my $WpCnt = scalar @Waypoints;
1412
 
1413
    # Std- or Random Waypoint sequence
1414
    if ( $PlayerRandomMode =~ /STD/i  or
1415
         $PlayerRandomMode =~ /RND/i )
1416
        {
1417
        $NewIndex = $WpPlayerIndex;
1418
 
1419
        # get next Wp
1420
        for ( $i=0; $i<10; $i++)        # avoid deadlock, if no WP reachable
1421
            {
1422
            for ( $j=0; $j<10; $j++ )   # avoid deadlock, if only 1 WP
1423
                {
1424
 
1425
                if ( $PlayerRandomMode =~ /STD/i )
1426
                    {
1427
                    $NewIndex ++;
1428
                    if ( $NewIndex >= $WpCnt )
1429
                        {
1430
                        # Restart with 1st Wp
1431
                        $NewIndex = 0;
1432
                        }
1433
                    }
1434
 
1435
                if ( $PlayerRandomMode =~ /RND/i )
1436
                    {
1437
                    $NewIndex = int (rand($WpCnt));
1438
                    }
1439
 
1440
                # want to have different Wp 
1441
                if ( $NewIndex ne $WpPlayerIndex )
1442
                    {
1443
                    last;
1444
                    }
1445
                }
1446
 
1447
            # Set new Target 
1448
            if ( &WpTargetSet ($NewIndex) == 0 )
1449
                {
1450
                # new Wp-Target set
1451
                last;
1452
                }
1453
            }
1454
        }
1455
 
1456
    # Random Map sequence
1457
    if ( $PlayerRandomMode =~ /MAP/i )
1458
        {
1459
        $RandomTarget_x = $MkPos_x;
1460
        $RandomTarget_y = $MkPos_y;
1461
 
1462
        for ( $i=0; $i<50; $i++)        # avoid deadlock, if target not reachable
1463
            {
1464
            # don't use 10% around the map
1465
            my $New_x = int (rand($MapSizeX - 2 * $MapSizeX/10));
1466
            my $New_y = int (rand($MapSizeY - 2 * $MapSizeY/10));
1467
            $New_x += $MapSizeX/10;
1468
            $New_y += $MapSizeY/10;
1469
 
1470
            # is Target reachable?
1471
            if ( &IsTargetReachable($New_x, $New_y) )
1472
                {
1473
                # new Target found
1474
                $RandomTarget_x = $New_x;
1475
                $RandomTarget_y = $New_y;
1476
                last;
1477
                }
1478
            }
1479
        }
1480
 
1481
    &TtsSpeak ('MEDIUM', $Translate{'TtsNextTarget'});
1482
 
1483
    $WpPlayerHoldtime = -1;
1484
 
1485
    # Altitude Control
1486
    &AltCurPosSave()
1487
    }
1488
 
1489
 
1490
# Waypoint Player: Goto previous Waypoint
1491
sub WpTargetPrev()
1492
    {
1493
    if ( $PlayerRandomMode =~ /STD/i )
1494
        {
1495
        $WpPlayerIndex --;
1496
        if ( $WpPlayerIndex < 0 )
1497
            {
1498
            # Restart with last Wp
1499
            $WpPlayerIndex = $#Waypoints;
1500
            }
1501
        }
1502
    else
1503
        {
1504
        # Next Random Target
1505
        &WpTargetNext();
1506
        }
1507
 
1508
    $WpPlayerHoldtime = -1;
1509
 
1510
    # Altitude Control
1511
    &AltCurPosSave()
1512
    }
1513
 
1514
 
1515
# Waypoint Player: Goto first Waypoint
1516
sub WpTargetFirst()
1517
    {
1518
    $WpPlayerIndex = 0;
1519
    $WpPlayerHoldtime = -1;
1520
 
1521
    # Altitude Control
1522
    &AltCurPosSave()
1523
    }
1524
 
1525
# Waypoint Player: Goto last Waypoint
1526
sub WpTargetLast()
1527
    {
1528
    $WpPlayerIndex = $#Waypoints;
1529
    $WpPlayerHoldtime = -1;
1530
 
1531
    # Altitude Control
1532
    &AltCurPosSave()
1533
    }
1534
 
1535
 
1536
# Waypoint Player: Waypoint Target reached?
1537
sub WpCheckTargetReached()
1538
    {
1539
    if ( $WpPlayerHoldtime == -1 )
1540
        {
1541
        lock (%MkOsd);              # until end of block
1542
 
1543
        if ( &CurPosIsValid() and  &HomePosIsValid() and  &MkIsWptMode() )
1544
            {
1545
            # Gueltige SAT Daten
1546
 
1547
            # for Wp mode
1548
            my $Wp = $Waypoints[$WpPlayerIndex];
1549
            my $WpTarget_Lat = $Wp->{'Pos_Lat'};
1550
            my $WpTarget_Lon = $Wp->{'Pos_Lon'};
1551
            my $WpTolerance  = $Wp->{'ToleranceRadius'};
1552
            my $WpHoldtime   = $Wp->{'Holdtime'};
1553
 
1554
            # Random-Map Mode
1555
            if ( $PlayerRandomMode =~ /MAP/i )
1556
                {
1557
                ($WpTarget_Lat, $WpTarget_Lon) = &MapXY2Gps ($RandomTarget_x, $RandomTarget_y);
1558
                $WpTolerance = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
1559
                $WpHoldtime  = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
1560
                }
1561
 
1562
            # Operation Radius pruefen
1563
            my ($HomeDist, $HomeBearing) = &MapGpsTo($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
1564
            if ( $HomeDist > $MkOsd{'OperatingRadius'} )
1565
                {
1566
                # Target entsprechend Operation Radius neu berechnen
1567
                $HomeDist = $MkOsd{'OperatingRadius'};
1568
                ($WpTarget_Lat, $WpTarget_Lon) = &MapGpsAt($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $HomeDist, $HomeBearing);
1569
                }
1570
 
1571
            # Abstand zum Ziel pruefen
1572
            my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
1573
            $Dist = int ($Dist + 0.5);
1574
            if ( $Dist <= $WpTolerance )
1575
                {
1576
                # Target reached - count down Holdtime
1577
                $WpPlayerHoldtime = 2 * $WpHoldtime;      # 0..2n - decrement im 0.5s timer
1578
 
1579
                &TtsSpeak ('MEDIUM', $Translate{'TtsTargetReached'});
1580
                }
1581
            }
1582
        }
1583
 
1584
    if ( $WpPlayerHoldtime == 0 )  # wird im 0.5s timer runtergezaehlt
1585
        {
1586
        # Target reached - Holdtime is over
1587
        $WpPlayerHoldtime = -1;
1588
 
1589
        return 1;
1590
        }
1591
 
1592
    # Target NOT reached
1593
    return 0;
1594
    }
1595
 
1596
 
1597
# KML Player: 10s forward
1598
sub KmlTargetNext()
1599
    {
1600
    $KmlPlayerIndex += int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
1601
    if ( $KmlPlayerIndex > $#KmlTargets )
1602
        {
1603
        # Next loop
1604
        $KmlPlayerIndex -= $#KmlTargets;
1605
        }
1606
    }
1607
 
1608
# KML Player: 10s backward
1609
sub KmlTargetPrev()
1610
    {
1611
    $KmlPlayerIndex -= int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
1612
    if ( $KmlPlayerIndex < 0 )
1613
        {
1614
        # Next loop
1615
        $KmlPlayerIndex += $#KmlTargets;
1616
        }
1617
    }
1618
 
1619
# KML Player: Goto first Target
1620
sub KmlTargetFirst()
1621
    {
1622
    $KmlPlayerIndex = 0;
1623
    }
1624
 
1625
# KML Player: Goto last Target
1626
sub KmlTargetLast()
1627
    {
1628
    $KmlPlayerIndex = $#KmlTargets;
1629
    }
1630
 
1631
 
1632
# SPD Player: 10s forward
1633
sub SpdTargetNext()
1634
    {
1635
    $SpdPlayerTime += 10;
1636
 
1637
    # Altitude Control
1638
    &AltCurPosSave()
1639
    }
1640
 
1641
# SPD Player: 10s backward
1642
sub SpdTargetPrev()
1643
    {
1644
    $SpdPlayerTime -= 10;
1645
    if ( $SpdPlayerTime < 0 )
1646
        {
1647
        $SpdPlayerTime = 0;
1648
        $System{'AltWpIndex'} = 0;
1649
        }
1650
 
1651
    # Altitude Control
1652
    &AltCurPosSave()
1653
    }
1654
 
1655
# SPD Player: Goto first Target
1656
sub SpdTargetFirst()
1657
    {
1658
    $SpdPlayerTime = 0;
1659
    $SpdPlayerIndex = 0;
1660
    $System{'AltWpIndex'} = 0;
1661
 
1662
    # Altitude Control
1663
    &AltCurPosSave();
1664
    }
1665
 
1666
# SPD Player: Goto last Target
1667
sub SpdTargetLast()
1668
    {
1669
    $SpdPlayerTime = 0;
1670
    $SpdPlayerIndex = 0;
1671
    $System{'AltWpIndex'} = 0;
1672
 
1673
    # Altitude Control
1674
    &AltCurPosSave();
1675
    }
1676
 
1677
 
1678
#
1679
# Set Player modes
1680
#
1681
 
1682
# set Player mode
1683
sub PlayerModeSet()
1684
    {
1685
    my ($Mode) = @_;
1686
 
1687
    if    ( $Mode =~ /play/i )  { &PlayerPlay(); }
1688
    elsif ( $Mode =~ /pause/i ) { &PlayerPause(); }
1689
    elsif ( $Mode =~ /home/i )  { &PlayerHome(); }
1690
    elsif ( $Mode =~ /stop/i )  { &PlayerStop(); }
1691
    }
1692
 
1693
 
1694
# set player to "Play" mode
1695
sub PlayerPlay()
1696
    {
1697
    $PlayerMode = 'Play';
1698
    $WpPlayerHoldtime = -1;
1699
 
1700
    # Play/Pause-Icon loeschen und neu anzeigen
1701
    $map_canvas->delete('Wp-PlayPause');
1702
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1703
                             '-tags' => 'Wp-PlayPause',
1704
                             '-anchor' => 'nw',
1705
                             '-image'  => 'WpPause-Foto',
1706
                             );
1707
    &FoxHide();
1708
    &CrosshairHide();
1709
 
1710
    # Altitude control
1711
    &AltCurPosSave();
1712
    }
1713
 
1714
 
1715
# set player to "Pause" mode
1716
sub PlayerPause()
1717
    {
1718
    $PlayerMode = 'Pause';
1719
    $WpPlayerHoldtime = -1;
1720
 
1721
    # Play/Pause-Icon loeschen und neu anzeigen
1722
    $map_canvas->delete('Wp-PlayPause');
1723
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1724
                             '-tags' => 'Wp-PlayPause',
1725
                             '-anchor' => 'nw',
1726
                             '-image'  => 'WpPlay-Foto',
1727
                             );
1728
 
1729
    # momentane Position merken und im Player-Timer senden
1730
    $PlayerPause_Lon = "";
1731
    $PlayerPause_Lat = "";
1732
 
1733
    lock (%MkOsd);              # until end of block
1734
    if ( &CurPosIsValid() )
1735
        {
1736
        $PlayerPause_Lon = $MkOsd{'CurPos_Lon'};
1737
        $PlayerPause_Lat = $MkOsd{'CurPos_Lat'};
1738
        }
1739
 
1740
    &FoxShow();
1741
 
1742
    # restart crosshair timer
1743
    $CrosshairTimerCnt = 0;
1744
 
1745
    # Altitude Control
1746
    &AltCurPosSave()
1747
    }
1748
 
1749
 
1750
# set player to "Home" mode
1751
sub PlayerHome()
1752
    {
1753
 
1754
    $PlayerMode = 'Home';
1755
    &WpTargetFirst();
1756
 
1757
    # Play/Pause-Icon loeschen und neu anzeigen
1758
    $map_canvas->delete('Wp-PlayPause');
1759
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1760
                             '-tags'   => 'Wp-PlayPause',
1761
                             '-anchor' => 'nw',
1762
                             '-image'  => 'WpPlay-Foto',
1763
                             );
1764
    &FoxHide();
1765
    &CrosshairHide();
1766
 
1767
    # Altitude control
1768
    &AltCurPosSave();
1769
    }
1770
 
1771
 
1772
# set player to "Stop" mode
1773
sub PlayerStop()
1774
    {
1775
    $PlayerMode = 'Stop';
1776
    &WpTargetFirst();
1777
 
1778
    # set Play/Pause Icon to "Play
1779
    $map_canvas->delete('Wp-PlayPause');
1780
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1781
                             '-tags'   => 'Wp-PlayPause',
1782
                             '-anchor' => 'nw',
1783
                             '-image'  => 'WpPlay-Foto',
1784
                             );
1785
 
1786
    # switch player to Wp Mode
1787
    &PlayerWpt();
1788
 
1789
    &FoxHide();
1790
    &CrosshairHide();
1791
 
1792
    # Altitude control
1793
    &AltCurPosSave();
1794
    }
1795
 
1796
 
1797
# set player Random Mode to "STD"
1798
sub PlayerRandomStd()
1799
    {
1800
    if ( $PlayerWptKmlMode ne 'WPT' )
1801
        {
1802
        # not in WPT Mode
1803
        return;
1804
        }
1805
 
1806
    $PlayerRandomMode = "STD";
1807
 
1808
    # Set Icon
1809
    $map_canvas->delete('Wp-WptRandom');
1810
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
1811
                             '-tags' => 'Wp-WptRandom',
1812
                             '-anchor' => 'nw',
1813
                             '-image'  => 'WpRandomOn-Foto',
1814
                            );
1815
 
1816
    # redraw connectors and Icons on canvas
1817
    &WpRedrawLines();
1818
    &WpRedrawIcons();
1819
 
1820
    # Altitude control
1821
    &AltCurPosSave();
1822
    }
1823
 
1824
 
1825
# set player Random Mode to "RND"
1826
sub PlayerRandomRnd()
1827
    {
1828
    if ( $PlayerWptKmlMode ne 'WPT' )
1829
        {
1830
        # not in WPT Mode
1831
        return;
1832
        }
1833
 
1834
    $PlayerRandomMode = "RND";
1835
 
1836
    # Set Icon
1837
    $map_canvas->delete('Wp-WptRandom');
1838
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
1839
                             '-tags' => 'Wp-WptRandom',
1840
                             '-anchor' => 'nw',
1841
                             '-image'  => 'WpRandomMap-Foto',
1842
                            );
1843
 
1844
    # delete Wp-connectors from canvas
1845
    $map_canvas->delete('Waypoint-Connector');
1846
    $map_canvas->delete('Waypoint-Spline');
1847
 
1848
    # Altitude control
1849
    &AltCurPosSave();
1850
    }
1851
 
1852
 
1853
# set player Random Mode to "MAP"
1854
sub PlayerRandomMap()
1855
    {
1856
    if ( $PlayerWptKmlMode ne 'WPT' )
1857
        {
1858
        # not in WPT Mode
1859
        return;
1860
        }
1861
 
1862
    $PlayerRandomMode = "MAP";
1863
 
1864
    # Set Icon
1865
    $map_canvas->delete('Wp-WptRandom');
1866
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
1867
                             '-tags' => 'Wp-WptRandom',
1868
                             '-anchor' => 'nw',
1869
                             '-image'  => 'WpRandomOff-Foto',
1870
                            );
1871
 
1872
    # Get 1st Target
1873
    &WpTargetNext();
1874
 
1875
    # hide WP and connectors on canvas
1876
    &WpHide();
1877
 
1878
    # Altitude control
1879
    &AltCurPosSave();
1880
    $WpPlayerMapAlt = &Altitude();
1881
    }
1882
 
1883
 
1884
# set player Pause Mode to "MAP", "MK"
1885
sub PlayerPauseMode()
1886
    {
1887
    ($PlayerPauseMode) = @_;
1888
    }
1889
 
1890
 
1891
# set player to KML mode
1892
sub PlayerKml()
1893
    {
1894
    $PlayerWptKmlMode = 'KML';
1895
 
1896
    # Wpt/Kml-Player-Icon loeschen und neu anzeigen
1897
    $map_canvas->delete('Wp-WptKml');
1898
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1899
                             '-tags' => 'Wp-WptKml',
1900
                             '-anchor' => 'nw',
1901
                             '-image'  => 'WpKml-Foto',
1902
                             );
1903
 
1904
    # delete Waypoints from canvas
1905
    &WpHide();
1906
 
1907
    # show KML Track
1908
    &KmlRedraw();
1909
 
1910
    # Altitude control
1911
    &AltCurPosSave();
1912
    }
1913
 
1914
 
1915
# set player to WPT mode
1916
sub PlayerWpt()
1917
    {
1918
    $PlayerWptKmlMode = 'WPT';
1919
 
1920
    # Wpt/Kml-Player-Icon loeschen und neu anzeigen
1921
    $map_canvas->delete('Wp-WptKml');
1922
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1923
                             '-tags' => 'Wp-WptKml',
1924
                             '-anchor' => 'nw',
1925
                             '-image'  => 'WpWpt-Foto',
1926
                             );
1927
 
1928
    # delete Kml-Track from canvas
1929
    &KmlHide();
1930
 
1931
    # remove all Wp-Icons and Wp-Number on canvas
1932
    &WpHide();
1933
 
1934
    # Show waypoints, WP resend required
1935
    $WaypointsModified = 1;
1936
 
1937
    if ( $PlayerRandomMode ne 'MAP' )
1938
        {
1939
        &WpRedrawIcons()
1940
        }
1941
    if ( $PlayerRandomMode eq 'STD' )
1942
        {
1943
        &WpRedrawLines()
1944
        }
1945
 
1946
    # Altitude control
1947
    &AltCurPosSave();
1948
    }
1949
 
1950
 
1951
# set player to SPD mode
1952
sub PlayerSpd()
1953
    {
1954
    $PlayerWptKmlMode = 'SPD';
1955
 
1956
    # Wpt/Spd/Kml-Player-Icon loeschen und neu anzeigen
1957
    $map_canvas->delete('Wp-WptKml');
1958
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1959
                             '-tags' => 'Wp-WptKml',
1960
                             '-anchor' => 'nw',
1961
                             '-image'  => 'WpSpd-Foto',
1962
                             );
1963
 
1964
    # delete Kml-Track from canvas
1965
    &KmlHide();
1966
 
1967
    # remove all Wp-Icons and Wp-Number on canvas
1968
    &WpHide();
1969
 
1970
    # Show waypoints, WP resend required
1971
    $WaypointsModified = 1;
1972
 
1973
    # show icon and spline
1974
    &WpRedrawIcons();
1975
    &WpRedrawLines();
1976
 
1977
    # Altitude control
1978
    &AltCurPosSave();
1979
    }
1980
 
1981
 
1982
# Activate Recording mode
1983
sub PlayerRecordOn
1984
    {
1985
    $PlayerRecordMode = "REC";
1986
    $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "Recording" );
1987
 
1988
    # Record new KML-Track
1989
    undef @KmlTargets;
1990
    $KmlPlayerIndex = 0;
1991
 
1992
    # delete Kml-Track from canvas
1993
    &KmlHide();
1994
    }
1995
 
1996
# Deactivate Recording mode
1997
sub PlayerRecordOff
1998
    {
1999
    $PlayerRecordMode = "";
2000
    $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "" );
2001
    }
2002
 
2003
 
2004
# Hide Fox icon on canvas
2005
sub FoxHide()
2006
    {
2007
    $map_canvas->lower('Fox', 'Map');
2008
    }
2009
 
2010
# Show Fox icon on canvas
2011
sub FoxShow()
2012
    {
2013
    $map_canvas->raise('Fox', 'Target');
2014
    }
2015
 
2016
# Hide POI icon on canvas
2017
sub PoiHide()
2018
    {
2019
    $map_canvas->lower('POI', 'Map');
2020
    }
2021
 
2022
# Show POI icon on canvas
2023
sub PoiShow()
2024
    {
2025
    $map_canvas->raise('POI', 'Track-Antenna');
2026
    }
2027
 
2028
# Show Grid on canvas
2029
sub GridShow()
2030
    {
2031
    my $Dist  = $Cfg->{map}->{'GridDist'}  || 50;
2032
    my $Color = $Cfg->{map}->{'GridColor'} || "#909090";
2033
 
2034
    my $xmin = 0;
2035
    my $ymin = 0;
2036
    my $xmax = $MapSizeX;
2037
    my $ymax = $MapSizeY;
2038
 
2039
 
2040
    my $PhiRef = &MapAngel();  
2041
    my ($Lat1, $Lon1) = &MapXY2Gps($xmin, $ymin);
2042
    my ($Lat2, $Lon2) = &MapGpsAt($Lat1, $Lon1, $Dist, $PhiRef);
2043
    my ($x, $y) = &MapGps2XY($Lat2, $Lon2);
2044
    my $dpix = int ($x - $xmin + 0.5);
2045
 
2046
    lock (%MkOsd);              # until end of block
2047
    my $x0 = $MapSizeX / 2;
2048
    my $y0 = $MapSizeY / 2;
2049
    if ( &HomePosIsValid() )
2050
        {
2051
        ($x0, $y0) = &MapGps2XY ($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
2052
        }
2053
 
2054
    for ($x = $xmin + $x0 % $dpix; $x < $xmax; $x +=$dpix)
2055
        {
2056
        $map_canvas->createLine ( $x, $ymin, $x, $ymax,
2057
                                  '-tags' => 'Map-Grid',
2058
                                  '-arrow' => 'none',
2059
                                  '-fill' => $Color,
2060
                                  '-width' => 1,
2061
                                );
2062
        }
2063
 
2064
    for ($y = $ymin + $y0 % $dpix; $y < $ymax; $y +=$dpix)
2065
        {
2066
        $map_canvas->createLine ( $xmin, $y, $xmax, $y,
2067
                                  '-tags' => 'Map-Grid',
2068
                                  '-arrow' => 'none',
2069
                                  '-fill' => $Color,
2070
                                  '-width' => 1,
2071
                                );
2072
        }
2073
 
2074
    # Beschriftung x
2075
    for ( $x = xmin + $x0 % $dpix; $x < $xmax; $x += $dpix)
2076
        {
2077
        my $ScaleX = int (($x - $x0) / $dpix * $Dist + 0.5);
2078
        if ( $ScaleX < 0 )
2079
            {
2080
            $ScaleX = int (($x - $x0) / $dpix * $Dist - 0.5);
2081
            }
2082
        $map_canvas->createText ( $x - 2, $y0 - 8,
2083
                                  '-tags' => 'Map-Grid',
2084
                                  '-text' => sprintf ("%d", $ScaleX),
2085
                                  '-font' => '-*-Arial-Bold-R-Normal--*-150-*',
2086
                                  '-fill' => $Color,
2087
                                  '-anchor' => 'e',
2088
                                );
2089
        }
2090
    # Beschriftung y
2091
    for ( $y = ymin + $y0 % $dpix; $y < $ymax; $y += $dpix)
2092
        {
2093
        my $ScaleY = int (($y - $y0) / $dpix * $Dist + 0.5);
2094
        if ( $ScaleY < 0 )
2095
            {
2096
            $ScaleY = int (($y - $y0) / $dpix * $Dist - 0.5);
2097
            }
2098
        $map_canvas->createText ( $x0 + 4, $y - 8,
2099
                                  '-tags' => 'Map-Grid',
2100
                                  '-text' => sprintf ("%d", $ScaleY * -1),
2101
                                  '-font' => '-*-Arial-Bold-R-Normal--*-150-*',
2102
                                  '-fill' => $Color,
2103
                                  '-anchor' => 'w',
2104
                                );
2105
        }
2106
 
2107
    $map_canvas->raise('Map-Grid', 'Map');
2108
    }
2109
 
2110
 
2111
# Hide Grid on canvas
2112
sub GridHide()
2113
    {
2114
    $map_canvas->delete('Map-Grid');
2115
    }
2116
 
2117
 
2118
# Show Crosshair for Pause Position on canvas
2119
sub CrosshairShow()
2120
    {
2121
    my ($Lat, $Lon) = @_;
2122
 
2123
    my ($x, $y) = &MapGps2XY ($Lat, $Lon);
2124
    if ( $x != $LastCrosshairX  and  $y != $LastCroshairY )
2125
        {
2126
        # Only update, if coords changed - CPU consuming!
2127
        $map_canvas->coords ('Map-Crosshair-X', 0, $y, $MapSizeX, $y);
2128
        $map_canvas->coords ('Map-Crosshair-Y', $x, 0, $x, $MapSizeY);
2129
 
2130
        $map_canvas->raise('Map-Crosshair', 'Target');
2131
        }
2132
 
2133
    $LastCrosshairX = $x;
2134
    $LastCrosshairY = $y;
2135
    }
2136
 
2137
 
2138
# Hide Crosshair on canvas
2139
sub CrosshairHide()
2140
    {
2141
    $map_canvas->lower('Map-Crosshair', 'Map');  # hide below map
2142
 
2143
    $LastCrosshairX = -1;
2144
    $LastCrosshairY = -1;
2145
    }
2146
 
2147
 
2148
#
2149
# System Messages
2150
#
2151
 
2152
# Init Messages for a Subsystem/timer
2153
sub MkMessageInit ()
2154
    {
2155
    my ($Id) = @_;
2156
 
2157
    $MkMessages{$Id} = [];
2158
    }
2159
 
2160
 
2161
# Register message
2162
sub MkMessage ()
2163
    {
2164
    my ($Message, $Id) = @_;
2165
 
2166
    push @{$MkMessages{$Id}}, $Message;
2167
    }
2168
 
2169
 
2170
# show registered messages
2171
sub MkMessageShow()
2172
    {
2173
    my @Messages;
2174
    my $MsgLines = 0;
2175
    my $MaxMsgLen = 0;
2176
 
2177
    # Collect Messages of each category
2178
    foreach my $Id (keys %MkMessages)
2179
        {
2180
        foreach $i ( 0 .. $#{$MkMessages{$Id}} )
2181
            {
2182
            my $Msg = $MkMessages{$Id}[$i];
2183
            push @Messages, $Msg;
2184
 
2185
            $MsgLines ++;
2186
 
2187
            my $Len = length $Msg;
2188
            if ( $Len > $MaxMsgLen )
2189
                {
2190
                $MaxMsgLen = $Len;
2191
                }
2192
            }
2193
        }
2194
 
2195
    $map_canvas->delete('Message-Balloon');  # delete old Balloon
2196
 
2197
    if ( $MsgLines > 0 )
2198
        {
2199
        # draw Balloon
2200
        my @MsgBalloon = ( $MkPos_x ,                       $MkPos_y,
2201
                           $MkPos_x + 30 ,                  $MkPos_y + 40,
2202
                           $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 40,
2203
                           $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 44 + $MsgLines * 20,
2204
                           $MkPos_x + 20,                   $MkPos_y + 44 + $MsgLines * 20,
2205
                           $MkPos_x + 20,                   $MkPos_y + 40,
2206
                           $MkPos_x,                        $MkPos_y,
2207
                          );
2208
 
2209
        $map_canvas->createPolygon( @MsgBalloon,
2210
                                    '-tags' => ['Message-Balloon', 'Message-BalloonBubble'],
2211
                                    '-fill' => 'yellow',
2212
                                    '-outline' => 'yellow',
2213
                                    '-width' => 1,
2214
                                  );
2215
        # draw Messages
2216
        my $MsgLine = 1;
2217
        foreach my $Msg (@Messages)
2218
            {
2219
            $map_canvas->createText ( $MkPos_x + 25, $MkPos_y + 32 + $MsgLine * 20 ,
2220
                                      '-tags' => ['Message-Balloon', 'Message-BalloonText'],
2221
                                      '-text' => $Msg,
2222
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2223
                                      '-fill' => 'blue',
2224
                                      '-anchor' => 'w',
2225
                                        );
2226
            $MsgLine ++;
2227
            }
2228
 
2229
 
2230
        $map_canvas->lower('Message-Balloon', 'MK-Arrow');
2231
        }
2232
 
2233
    }
2234
 
2235
 
2236
# Show Balloon, when aproaching Target
2237
sub TargetMessageShow ()
2238
    {
2239
    $map_canvas->delete('Target-Balloon');  # delete old Balloon
2240
 
2241
    if ( $OperationMode ne "Free" and $MkOsd{'TargetPos_Stat'} == 1  and $MkOsd{'TargetPosDev_Dist'} /10 < 25 )
2242
        {
2243
        my $BalloonLines = 0;
2244
        $ColorBalloon = "blue";
2245
        my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
2246
        my $Wp = $Waypoints[$MkOsd{'WaypointIndex'}];
2247
 
2248
        # Holdtime Wp-Player Mode
2249
        if ( $WpPlayerHoldtime >= 0  and  $PlayerWptKmlMode  eq "WPT" )
2250
            {
2251
            # Holdtime
2252
            $ColorBalloon = 'red';
2253
            my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($WpPlayerHoldtime / 2  + 0.5) );
2254
            $map_canvas->createText ( $T_x + 25, $T_y - 40,
2255
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2256
                                      '-text' => $HoldTime,
2257
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2258
                                      '-fill' => $ColorBalloon,
2259
                                      '-anchor' => 'w',
2260
                                    );
2261
            $BalloonLines ++;
2262
            }
2263
 
2264
        # Holdtime WPT-Mode
2265
        if ( &MkTargetReached()  and  $OperationMode eq "WPT" )
2266
            {
2267
            # Holdtime from MK
2268
            $ColorBalloon = 'red';
2269
            my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($MkOsd{'TargetHoldTime'} + 0.5) );
2270
            $map_canvas->createText ( $T_x + 25, $T_y - 40,
2271
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2272
                                      '-text' => $HoldTime,
2273
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2274
                                      '-fill' => $ColorBalloon,
2275
                                      '-anchor' => 'w',
2276
                                    );
2277
            $BalloonLines ++;
2278
            }
2279
 
2280
        # Tolerance Radius Player Mode
2281
        if ( &MkIsWptMode()  and  $OperationMode eq "Play" and $PlayerWptKmlMode eq "WPT" )
2282
            {
2283
            my $WpTolerance  = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'});
2284
            $map_canvas->createText ( $T_x + 25, $T_y - 60,
2285
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2286
                                      '-text' => $WpTolerance,
2287
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2288
                                      '-fill' => $ColorBalloon,
2289
                                      '-anchor' => 'w',
2290
                                    );
2291
            $BalloonLines ++;
2292
            }
2293
 
2294
        # Tolerance WPT-Mode
2295
        if ( &MkIsWptMode  and  $OperationMode eq "WPT" )
2296
            {
2297
            my $WpTolerance  = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'} );
2298
            $map_canvas->createText ( $T_x + 25, $T_y - 60,
2299
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2300
                                      '-text' => $WpTolerance,
2301
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2302
                                      '-fill' => $ColorBalloon,
2303
                                      '-anchor' => 'w',
2304
                                    );
2305
            $BalloonLines ++;
2306
            }
2307
 
2308
        # Distance to Target
2309
        my $Dist = int ($MkOsd{'TargetPosDev_Dist'} /10 + 0.5);
2310
        $map_canvas->createText ( $T_x + 25, $T_y - 80,
2311
                                  '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2312
                                  '-text' => sprintf ("%5s %3d m", "DST:", $Dist) ,
2313
                                  '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2314
                                  '-fill' => $ColorBalloon,
2315
                                  '-anchor' => 'w',
2316
                                );
2317
        $BalloonLines ++;
2318
 
2319
        if ( $BalloonLines >= 1 )
2320
            {
2321
            # draw Balloon
2322
            my @TargetBalloon = ( $T_x ,      $T_y,
2323
                                  $T_x + 30,  $T_y - (3 - $BalloonLines) * 20 -27,
2324
                                  $T_x + 150, $T_y - (3 - $BalloonLines) * 20 -27 ,
2325
                                  $T_x + 150, $T_y - 93,
2326
                                  $T_x + 20,  $T_y - 93,
2327
                                  $T_x + 20,  $T_y - (3 - $BalloonLines) * 20 -27,
2328
                                  $T_x,       $T_y,
2329
                                );
2330
 
2331
            $map_canvas->createPolygon( @TargetBalloon,
2332
                                        '-tags' => ['Target-Balloon', 'Target-BalloonBubble'],
2333
                                        '-fill' => 'lightgray',
2334
                                        '-outline' => 'yellow',
2335
                                        '-width' => 1,
2336
                                       );
2337
            }
2338
 
2339
 
2340
        $map_canvas->lower('Target-Balloon', 'MK-Home-Line');
2341
        $map_canvas->lower('Target-BalloonBubble', 'Target-BalloonText');
2342
        }
2343
    }
2344
 
2345
 
2346
#
2347
# Airfield border
2348
#
2349
 
2350
# Are two segments A(a1/a2), B(b1/b2) and C(c1/c2), D(d1/d2) crossing ?
2351
sub SegmentCross()
2352
    {
2353
    my ( $a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) = @_;
2354
 
2355
    # segment C/D ist vertical, avoid div/0
2356
    if ( $c1 == $d1 )
2357
        {
2358
        $d1 += 0.00001;
2359
        }
2360
 
2361
    my $n = ($b1 - $a1) * ($d2 - $c2) - ($b2 - $a2) * ($d1 - $c1);
2362
    if ( $n == 0.0 )
2363
        {
2364
        # AB und CD sind parallel
2365
        return 0;
2366
        }
2367
 
2368
    my $s = ( ($c1 - $a1) * ($d2 - $c2) - ($c2 - $a2) * ($d1 - $c1) ) / $n;
2369
    my $t = ( $a1 - $c1 + $s * ($b1 - $a1) ) / ( $d1 - $c1 );
2370
    if ( $s >= 0.0  and  $s <= 1.0  and  $t >= 0.0  and  $t <= 1.0 )
2371
        {
2372
        # beide Strecken kreuzen sich
2373
 
2374
        # Schnittpunkt: s_x, s_y
2375
        my $s_x = $a1 + $s * ( $b1 - $a1 );
2376
        my $s_y = $a2 + $s * ( $b2 - $a2 );
2377
 
2378
        return 1;
2379
        }
2380
 
2381
    # beide Strecken kreuzen sich nicht
2382
    return 0;
2383
    }
2384
 
2385
 
2386
# How often does a segment A(a1,a2), B(b1,b2) cross the polygon?
2387
sub SegmentPolygonCross()
2388
    {
2389
    my ( $a1, $a2, $b1, $b2, $Polygon) = @_;
2390
 
2391
    my $Cross = 0;
2392
    my $PolyCnt = scalar @{$Polygon};
2393
    my $PolyPointCnt = $PolyCnt / 2;
2394
 
2395
    my $i = 0;
2396
    for ( $p=0; $p < $PolyPointCnt; $p++ )
2397
        {
2398
        my $c1 = ${$Polygon}[$i++];
2399
        my $c2 = ${$Polygon}[$i++];
2400
 
2401
        if ( $i >= $PolyCnt ) { $i = 0; }
2402
 
2403
        my $d1 = ${$Polygon}[$i];
2404
        my $d2 = ${$Polygon}[$i+1];
2405
 
2406
        # map calibration offsets
2407
        $c1 -= $Map{'Offset_x'};
2408
        $c2 += $Map{'Offset_y'};
2409
        $d1 -= $Map{'Offset_x'};
2410
        $d2 += $Map{'Offset_y'};    
2411
 
2412
        if ( &SegmentCross($a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) )
2413
            {
2414
            $Cross ++;
2415
            }
2416
        }
2417
 
2418
    return $Cross;
2419
    }
2420
 
2421
 
2422
# Is point A inside airfield border?
2423
sub IsInsideBorder()
2424
    {
2425
    my ($a1, $a2) = @_;
2426
 
2427
    if ( scalar @Map{'Border'} == 0 )
2428
        {
2429
        # no border defined, always inside
2430
        return 1;
2431
        }
2432
 
2433
    my $Cross = &SegmentPolygonCross (-10, -10, $a1, $a2, @Map{'Border'} );
2434
 
2435
    # Ungerade Anzahl Kreuzungen: Inside
2436
    return ( $Cross % 2 );
2437
    }
2438
 
2439
 
2440
 
2441
# Is segment A, B crossing the airfield border?
2442
sub IsCrossingBorder()
2443
    {
2444
    my ($a1, $a2, $b1, $b2) = @_;
2445
 
2446
    if ( scalar @Map{'Border'} == 0 )
2447
        {
2448
        # no border defined, always not crossing
2449
        return 0;
2450
        }
2451
 
2452
    my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
2453
 
2454
    return ( $Cross > 0 );
2455
    }
2456
 
2457
 
2458
# How often is segment A, B crossing the airfield border?
2459
sub CrossingBorderCount()
2460
    {
2461
    my ($a1, $a2, $b1, $b2) = @_;
2462
 
2463
    if ( scalar @Map{'Border'} == 0 )
2464
        {
2465
        # no border defined, not crossing
2466
        return 0;
2467
        }
2468
 
2469
    my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
2470
 
2471
    return ( $Cross );
2472
    }
2473
 
2474
 
2475
# check, if Target is reachable my MK
2476
sub IsTargetReachable()
2477
    {
2478
    my ($T_x, $T_y) = @_;
2479
 
2480
    my $MkIsInside = &IsInsideBorder($MkPos_x, $MkPos_y);
2481
    my $TargetIsInside = &IsInsideBorder($T_x, $T_y);
2482
    my $MkTargetCrossingCount = &CrossingBorderCount($MkPos_x, $MkPos_y, $T_x, $T_y);
2483
 
2484
    if ( ($MkIsInside  and  $MkTargetCrossingCount == 0 )  or
2485
         (! $MkIsInside  and  $TargetIsInside  and  $MkTargetCrossingCount == 1) )
2486
        {
2487
        # Target is reachable
2488
        return 1;
2489
        }
2490
 
2491
    # Target is not reachable
2492
    return 0;
2493
    }
2494
 
2495
 
2496
#
2497
# Closed natural cubic spline
2498
#
2499
# Derived from: http://www.cse.unsw.edu.au/~lambert/splines
2500
# @author Tim Lambert
2501
#
2502
# calculates the closed natural cubic spline that interpolates x[0], x[1], ... x[n]
2503
# The first segment is returned as
2504
# C[0].a + C[0].b*u + C[0].c*u^2 + C[0].d*u^3 0<=u<1
2505
# the other segments are in C[1], C[2], ...  C[n]
2506
 
2507
sub SplineCalcNaturalCubic()
2508
    {
2509
    my (@x) = @_;
2510
 
2511
    # We solve the equation
2512
    #   [4 1      1] [D[0]]   [3(x[1] - x[n])  ]
2513
    #   |1 4 1     | |D[1]|   |3(x[2] - x[0])  |
2514
    #   |  1 4 1   | | .  | = |      .         |
2515
    #   |    ..... | | .  |   |      .         |
2516
    #   |     1 4 1| | .  |   |3(x[n] - x[n-2])|
2517
    #   [1      1 4] [D[n]]   [3(x[0] - x[n-1])]
2518
 
2519
    #   by decomposing the matrix into upper triangular and lower matrices
2520
    #   and then back sustitution.  See Spath "Spline Algorithms for Curves
2521
    #   and Surfaces" pp 19--21. The D[i] are the derivatives at the knots.
2522
 
2523
    my @C, @D, @v, @w, @y, $F, $G, $H, $k, $z;
2524
    my $n = $#x;
2525
 
2526
    $w[1] = $v[1] = $z = 1.0/4.0;
2527
    $y[0] = $z * 3 * ($x[1] - $x[$n]);
2528
    $H = 4;
2529
    $F = 3 * ($x[0] - $x[$n-1]);
2530
    $G = 1;
2531
    for ( $k = 1; $k < $n; $k++)
2532
        {
2533
        $v[$k+1] = $z = 1/(4 - $v[$k]);
2534
        $w[$k+1] = -$z * $w[$k];
2535
        $y[$k] = $z * (3*($x[$k+1]-$x[$k-1]) - $y[$k-1]);
2536
        $H = $H - $G * $w[$k];
2537
        $F = $F - $G * $y[$k-1];
2538
        $G = -$v[$k] * $G;
2539
        }
2540
    $H = $H - ($G+1)*($v[$n]+$w[$n]);
2541
    $y[$n] = $F - ($G+1)*$y[$n-1];
2542
 
2543
    $D[$n] = $y[$n]/$H;
2544
    $D[$n-1] = $y[$n-1] - ($v[$n]+$w[$n])*$D[$n];
2545
    for ( $k = $n-2; $k >= 0; $k--)
2546
        {
2547
        $D[$k] = $y[$k] - $v[$k+1]*$D[$k+1] - $w[$k+1]*$D[$n];
2548
        }
2549
 
2550
    # now compute the coefficients of the cubics
2551
    for ( $k = 0; $k < $n; $k++)
2552
        {
2553
        $C[$k]{'a'} = $x[$k];
2554
        $C[$k]{'b'} = $D[$k];
2555
        $C[$k]{'c'} = 3*($x[$k+1] - $x[$k]) - 2*$D[$k] - $D[$k+1];
2556
        $C[$k]{'d'} = 2*($x[$k] - $x[$k+1]) + $D[$k] + $D[$k+1];
2557
        }
2558
 
2559
    $C[$n]{'a'} = $x[$n];
2560
    $C[$n]{'b'} = $D[$n];
2561
    $C[$n]{'c'} = 3*($x[0] - $x[$n]) - 2*$D[$n] - $D[0];
2562
    $C[$n]{'d'} = 2*($x[$n] - $x[0]) + $D[$n] + $D[0];
2563
 
2564
    return @C;
2565
    }
2566
 
2567
# Eval interpolated spline value
2568
sub SplineEval()
2569
    {
2570
    my ($u, %C) = @_;
2571
 
2572
    $a = $C{'a'};
2573
    $b = $C{'b'};
2574
    $c = $C{'c'};
2575
    $d = $C{'d'};
2576
 
2577
    return ((($d*$u) + $c)*$u + $b)*$u + $a;
2578
    }
2579
 
2580
# Get pixel position on WP-Spline after elapesd time
2581
sub SplinePosFromTime()
2582
    {
2583
    my ($Time) = @_;
2584
 
2585
    if ( scalar(@Waypoints) >= 2 )
2586
        {
2587
        # eval x/y target according to Time and Speed
2588
        my $TrackTime = 0;      # time/s needed to fly spline-track
2589
        my $WpIndex = 0;
2590
        my $Steps = 16;
2591
 
2592
        while (1)
2593
            {
2594
            my $Wp = $Waypoints[$WpIndex];
2595
            my $x0 = $Wp->{'MapX'};
2596
            my $y0 = $Wp->{'MapY'};
2597
 
2598
            my $WpIndex1 = $WpIndex + 1;
2599
            if ( $WpIndex1 >= scalar (@Waypoints) )
2600
                {
2601
                $WpIndex1 = 0;
2602
                }
2603
            my $Wp = $Waypoints[$WpIndex1];
2604
            my $Wp1_Speed = $Wp->{'Speed'} || 10;  # km/h
2605
            $Wp1_Speed /= 3.6;                     # m/s
2606
 
2607
            for (my $j = 1; $j <= $Steps; $j++)
2608
                {
2609
                my $u = $j / $Steps;
2610
                my $x = &SplineEval($u, %{$SplineCoeffX[$WpIndex]});
2611
                my $y = &SplineEval($u, %{$SplineCoeffY[$WpIndex]});
2612
 
2613
                my $SegmentLen = &MapXYTo($x0, $y0, $x, $y);
2614
                my $SegmentTime = $SegmentLen / $Wp1_Speed;
2615
                $TrackTime += $SegmentTime;
2616
 
2617
                if ( $TrackTime >= $SpdPlayerTime )
2618
                    {
2619
                    # position found
2620
 
2621
                    # set info for player control
2622
                    if ( $SpdPlayerIndex != $WpIndex1  and  $SpdPlayerTime > 0 )
2623
                        {
2624
                        $SpdPlayerTargetReached = 1;
2625
                        $SpdPlayerIndex = $WpIndex1;
2626
                        }
2627
 
2628
                    # linear interpolation between spline polygon points
2629
                    my $DiffTime = $TrackTime - $SpdPlayerTime;
2630
                    my $Ratio = 1 - $DiffTime / $SegmentTime;
2631
                    my $P_x = $x0 + ($x - $x0) * $Ratio;
2632
                    my $P_y = $y0 + ($y - $y0) * $Ratio;
2633
 
2634
                    return ($P_x, $P_y);
2635
                    }
2636
 
2637
                $x0 = $x;
2638
                $y0 = $y;
2639
                }
2640
 
2641
            # next WPT
2642
            $WpIndex ++;
2643
            if ( $WpIndex >= scalar (@Waypoints) )
2644
                {
2645
                # next loop: reset counter
2646
                $WpIndex = 0;
2647
                $TrackTime = 0;
2648
                $SpdPlayerTime = 0;
2649
                }
2650
            }
2651
        }
2652
 
2653
    # "position not found" return
2654
    return ("", "");
2655
    }
2656
 
2657
# save current position as starting point for Altitude control
2658
sub AltCurPosSave()
2659
   {
2660
   $System{'AltStartPos_x'} = $System{'CurrPos_x'};
2661
   $System{'AltStartPos_y'} = $System{'CurrPos_y'};
2662
   $System{'AltStartPos_Alt'} = &Altitude();
2663
   }
2664
 
2665
# Altitude of way from Startpoint $System{'AltStartXX'} to destination WP
2666
sub AltCalc()
2667
    {
2668
    # A: Start Point; B: Dest Point; C: Current Position
2669
    my $Ax = $System{'AltStartPos_x'};
2670
    my $Ay = $System{'AltStartPos_y'};
2671
 
2672
    my $DestWp = $System{'AltWpIndex'};
2673
    my $Wp = $Waypoints[$DestWp];
2674
    my $Bx = $Wp->{'MapX'};
2675
    my $By = $Wp->{'MapY'};
2676
 
2677
    my $Cx = $System{'CurrPos_x'};
2678
    my $Cy = $System{'CurrPos_y'};
2679
 
2680
    my $c = sqrt( ($Cx - $Ax)**2 + ($Cy - $Ay)**2 );
2681
    my $d = sqrt( ($Cx - $Bx)**2 + ($Cy - $By)**2 );
2682
    my $e = sqrt( ($Bx - $Ax)**2 + ($By - $Ay)**2 );
2683
 
2684
    my $a = ($c*$c - $d*$d + $e*$e) / (2 * $e);
2685
 
2686
    my $AltA = $System{'AltStartPos_Alt'};
2687
    my $AltB = $Wp->{'Pos_Alt'} || 0;
2688
    my $Alt = $a/$e * ($AltB - $AltA) + $AltA;
2689
 
2690
    # Alt Limits between A and B
2691
    if ($AltA < $AltB  and  $Alt < $AltA ) { $Alt = $AltA; };
2692
    if ($AltA > $AltB  and  $Alt > $AltA ) { $Alt = $AltA; };
2693
    if ($AltB < $AltA  and  $Alt < $AltB ) { $Alt = $AltB; };
2694
    if ($AltB > $AltA  and  $Alt > $AltB ) { $Alt = $AltB; };
2695
 
2696
    return $Alt;
2697
    }
2698
 
2699
#
2700
# Configuration and data-visualisation
2701
#
2702
 
2703
# Display or Modify Hash
2704
sub DisplayHash()
2705
    {
2706
    my ($hrefData, $Titel, $Mode) = @_;
2707
 
2708
    # $Mode: Display, Edit, Waypoint, Refresh, Heartbeat, SerialChannel, ExternControl
2709
 
2710
    my $MaxRow = 22;   # number or Rows in multi column view
2711
    my %Id;
2712
    my $Label;
2713
    my $Value;
2714
 
2715
    # Neues Fenster aufmachen
2716
    my $popup = $main->Toplevel();
2717
    $popup->title($Titel);
2718
 
2719
    # Frame mit den Buttons
2720
    my $popup_button = $popup->Frame() -> pack('-side' => 'bottom',
2721
                                               '-expand' => 'y',
2722
                                               '-anchor' => 's',
2723
                                               '-padx' => 5,
2724
                                               '-pady' => 5,
2725
                                               );
2726
    $popup_button->Button('-text'    => 'Schließen',
2727
                          '-command' => sub
2728
        {
2729
        if ( $Mode =~ /edit/i  and  $Mode =~ /waypoint/i )
2730
            {
2731
            $WaypointsModified = 1;            
2732
            &WpRedrawLines();
2733
            &WpRedrawIcons();
2734
            }
2735
 
2736
        $popup->destroy()
2737
        })->pack;
2738
 
2739
    # Frame mit den Labels und Daten
2740
    my $popup_data = $popup->Frame() -> pack('-side' => 'left',
2741
                                             '-expand' => 'y',
2742
                                             '-anchor' => 'w',
2743
                                             '-padx' => 10,
2744
                                             '-pady' => 10,
2745
                                             );
2746
    # Labels und Daten anzeigen
2747
    my $Row = 0;
2748
    my $Col = 0;
2749
 
2750
    foreach $Label ( sort keys %{$hrefData})
2751
        {
2752
        $LabelView = $Label;
2753
        if ( $Translate{$LabelView} ne "" )
2754
            {
2755
            $LabelView = $Translate{$LabelView};
2756
            }
2757
 
2758
        # Label
2759
        $popup_data->Label ('-text' => $LabelView,
2760
                            '-width' => 25,
2761
                            '-anchor' => 'w',
2762
                            ) -> grid( -row => $Row,
2763
                                       -column => $Col,
2764
                                       -padx => 10,
2765
                                     );
2766
        # Daten
2767
        if ( $Mode =~ /display/i )
2768
            {
2769
            # Display
2770
            if ( ref ${$hrefData}{$Label} )
2771
                {
2772
                $Text = "- can't display references -";
2773
                }
2774
            else
2775
                {
2776
                $Text = ${$hrefData}{$Label};
2777
                }
2778
 
2779
            $Id{$Label} = $popup_data->Label ('-text' => $Text,
2780
                                              '-width' => 20,
2781
                                              '-anchor' => 'e',
2782
                                              '-relief' => 'sunken',
2783
                                             ) -> grid( -row => $Row,
2784
                                                        -column => $Col + 1,
2785
                                                        -padx => 10,
2786
                                                      );
2787
            }
2788
        if ( $Mode =~ /edit/i )
2789
            {
2790
            # Edit
2791
            $Id{$Label} = $popup_data->Entry ('-textvariable' => \${$hrefData}{$Label},
2792
                                              '-exportselection' => '1',
2793
                                              '-width' => 20,
2794
                                              '-relief' => 'sunken',
2795
                                             ) -> grid( -row => $Row,
2796
                                                        -column => $Col + 1,
2797
                                                        -padx => 10,
2798
                                                      );
2799
            if ( $Mode =~ /waypoint/i )
2800
                {
2801
                # einige Waypoint-Felder nicht aenderbar einstellen
2802
                if ( "MapX MapY _MapX_Rel _MapY_Rel Pos_Lat Pos_Lon Tag" =~ /$Label/i )
2803
                    {
2804
                    $Id{$Label}->configure('-state' => 'disabled', );
2805
                    }
2806
                }
2807
            }
2808
 
2809
        # multi Column wrap
2810
        $Row++;
2811
        if ( $Row > $MaxRow )
2812
            {
2813
            $Row = 0;
2814
            $Col += 2;
2815
            }
2816
        }      
2817
 
2818
 
2819
    if ( $Mode =~ /refresh/i )
2820
        {
2821
        # Timer: 0.1s
2822
        $popup_data->repeat (100, sub
2823
            {
2824
            # Datenfelder alle 100ms aktualisieren
2825
 
2826
            my $BgColor = 'white';
2827
            if ( $Mode =~ /heartbeat/i )
2828
                {
2829
                $BgColor = 'red';
2830
                if ( &MkOsdIsValid() )
2831
                    {
2832
                    # gültige daten vom MK
2833
                    $BgColor = 'white';
2834
                    }
2835
                }
2836
 
2837
            if ( $Mode =~ /serialchannel/i )
2838
                {
2839
                $BgColor = 'red';
2840
                if ( $Cfg->{'serialchannel'}->{'SerialChannelSend'} =~ /y/i )
2841
                    {
2842
                    # senden aktiv
2843
                    $BgColor = 'white';
2844
                    }
2845
                }
2846
 
2847
            if ( $Mode =~ /externcontrol/i )
2848
                {
2849
                $BgColor = 'red';
2850
                if ( $Cfg->{'externcontrol'}->{'ExternControlSend'} =~ /y/i )
2851
                    {
2852
                    # senden aktiv
2853
                    $BgColor = 'white';
2854
                    }
2855
                }
2856
 
2857
            foreach $Label ( sort keys %{$hrefData} )
2858
                {
2859
                # Eingebbare Waypoint-Felder nicht aktualisieren
2860
                if ( ! ($Mode =~ /waypoint/i  and
2861
                        "Event_Flag Heading ToleranceRadius HoldTime Pos_Alt Speed" =~ /$Label/i) )
2862
                    {                
2863
                    $Id{$Label}->configure('-text' => ${$hrefData}{$Label},
2864
                                           '-background' => "$BgColor",
2865
                                          );
2866
                    }
2867
                }
2868
            });
2869
        }
2870
 
2871
    return 0;
2872
    }
2873
 
2874
 
2875
# Konfigurations-Hash (aus XML-Datei) im Popup-Fenster editieren
2876
sub Configure()
2877
    {
2878
    my ($CfgFile, $hrefCfg, $Mode) = @_;
2879
 
2880
    # get a copy of Cfg-Hash for editing
2881
    my $CfgEdit = &CopyHash($hrefCfg);
2882
 
2883
    # Neues Fenster aufmachen
2884
    my $popup = $main->Toplevel();
2885
    $popup->title("Einstellungen - $CfgFile");
2886
 
2887
    # Display data in a notebook widget
2888
    my $book = $popup->NoteBook(-dynamicgeometry => 1,
2889
                               )->grid(-row        => 0,
2890
                                       -column     => 0,
2891
                                       -columnspan => 4,
2892
                                       -sticky     => 'w',
2893
                                       -padx       => 5,
2894
                                       -pady       => 5,
2895
                                      );
2896
    # Show data
2897
    &ConfigureShow($book, $CfgEdit);
2898
 
2899
    # Button: OK
2900
    $popup->Button('-text'    => 'OK',
2901
                   '-width' => '10',
2902
                   '-command' => sub
2903
        {
2904
        # Save and activate config
2905
        &ConfigureSave( $CfgFile, $hrefCfg, $CfgEdit);
2906
        &CanvasRedraw();
2907
 
2908
        $popup->destroy();
2909
 
2910
        } )->grid(-row    => 1,
2911
                  -column => 0,
2912
                  -sticky => 'w',
2913
                  -padx   => 15,
2914
                  -pady   => 5,
2915
                 );
2916
 
2917
    # Button: Apply
2918
    $popup->Button('-text'    => $Translate{'Apply'},
2919
                   '-width' => '10',
2920
                   '-command' => sub
2921
        {
2922
        # Save and activate config
2923
        &ConfigureSave( $CfgFile, $hrefCfg, $CfgEdit);
2924
        &CanvasRedraw();
2925
 
2926
        } )->grid(-row    => 1,
2927
                  -column => 1,
2928
                  -sticky => 'w',
2929
                  -padx   => 15,
2930
                  -pady   => 5,
2931
                 );
2932
 
2933
 
2934
    # Button: Abort
2935
    $popup->Button('-text'    => $Translate{'Abort'},
2936
                   '-width' => '10',
2937
                   '-command' => sub { $popup->destroy() },
2938
                  )->grid(-row    => 1,
2939
                          -column => 2,
2940
                          -sticky => 'w',
2941
                          -padx   => 15,
2942
                          -pady   => 5,
2943
                         );
2944
 
2945
    #
2946
    # special handling for "Config" configuration
2947
    #
2948
    if ( $Mode =~ /CONFIG/i )
2949
        {
2950
        $popup->Label ('-text' => $Translate{'RestartRequired'},
2951
                       '-anchor' => 'w',
2952
                       '-foreground' => 'red',
2953
                      )->grid(-row    => 1,
2954
                              -column => 3,
2955
                              -sticky => 'w',
2956
                              -padx   => 5,
2957
                              -pady   => 5,
2958
                             );
2959
        }
2960
 
2961
 
2962
    #
2963
    # special handling for "Event" configuration
2964
    #
2965
    if ( $Mode =~ /EVENT/i )
2966
        {
2967
 
2968
        # notebook must have at least one tab
2969
        if (scalar $book->pages() == 0 )
2970
            {
2971
            # create new record in hash
2972
            my $NewEvent = sprintf ("Event%d", scalar $book->pages() + 1);
2973
            &EventInit($NewEvent, $CfgEdit);
2974
 
2975
            # Display new event
2976
            &ConfigureShow($book, $CfgEdit);
2977
            $book->raise($NewEvent);
2978
            }
2979
 
2980
 
2981
        # Menu bar (New, Delete, Rename)
2982
 
2983
        my $menu_bar = $popup->Menu;
2984
        $popup->optionAdd("*tearOff", "false");
2985
        $popup->configure ('-menu' => $menu_bar);
2986
 
2987
        my $menu_event = $menu_bar->cascade('-label' => $Translate{'Event'});
2988
 
2989
        #
2990
        # New Event
2991
        #
2992
        $menu_event->command('-label' => $Translate{'EventNew'},
2993
                             '-command' => sub
2994
            {
2995
            # Event Name in neuem Fenster abfragen
2996
            my $popup_new = $popup->Toplevel();
2997
            $popup_new->title("Event - $Translate{'EventNew'}");
2998
 
2999
            $popup_new->Label (-text => $Translate{'EventNewName'},
3000
                               -width => 20,
3001
                               -anchor => 'w',
3002
                               )->grid (-row    => 0,
3003
                                        -column => 0,
3004
                                        -sticky => 'w',
3005
                                        -padx   => 5,
3006
                                        -pady   => 5,
3007
                                       );
3008
 
3009
            my $NewEvent = sprintf ("Event%d", scalar $book->pages() + 1);
3010
            $popup_new->Entry ( -textvariable => \$NewEvent,
3011
                                -exportselection => '1',
3012
                                -width => 40,
3013
                                -relief => 'sunken',
3014
                               )->grid (-row    => 0,
3015
                                        -column => 1,
3016
                                        -sticky => 'w',
3017
                                        -padx   => 5,
3018
                                        -pady   => 5,
3019
                                        );
3020
            # Button: OK
3021
            $popup_new->Button('-text'    => "OK",
3022
                               '-width' => '10',
3023
                               '-command' => sub
3024
                {
3025
                # create new record in hash
3026
                $NewEvent = &EventnameAdjust($NewEvent);
3027
                &EventInit($NewEvent, $CfgEdit);
3028
 
3029
                # Display new event
3030
                &ConfigureShow($book, $CfgEdit);
3031
                $book->raise($NewEvent);
3032
 
3033
                $popup_new->destroy();
3034
                } )->grid (-row    => 1,
3035
                           -column => 0,
3036
                           -sticky => 'w',
3037
                           -padx   => 20,
3038
                           -pady   => 5,
3039
                          );
3040
 
3041
            # Button: Abort
3042
            $popup_new->Button('-text'    => $Translate{'Abort'},
3043
                               '-width' => '10',
3044
                               '-command' => sub
3045
                {
3046
                $popup_new->destroy()
3047
                } )->grid (-row    => 1,
3048
                           -column => 1,
3049
                           -sticky => 'e',
3050
                           -padx   => 20,
3051
                           -pady   => 5,
3052
                          );
3053
            });
3054
 
3055
        #
3056
        # Rename Event
3057
        #
3058
        $menu_event->command('-label' => $Translate{'EventRename'},
3059
                             '-command' => sub
3060
            {
3061
            # Event Name in neuem Fenster abfragen
3062
            my $popup_rename = $popup->Toplevel();
3063
            $popup_rename->title("Event - $Translate{'EventRename'}");
3064
 
3065
            $popup_rename->Label (-text => $Translate{'EventName'},
3066
                                  -width => 20,
3067
                                  -anchor => 'w',
3068
                                  )->grid (-row    => 0,
3069
                                           -column => 0,
3070
                                           -sticky => 'e',
3071
                                           -padx   => 5,
3072
                                           -pady   => 5,
3073
                                          );
3074
 
3075
            my $CurrentEvent = $book->raised;
3076
            $popup_rename->Entry ( -textvariable => \$CurrentEvent,
3077
                                   -exportselection => '1',
3078
                                   -width => 40,
3079
                                   -relief => 'sunken',
3080
                                   -state => 'disabled',
3081
                                  )->grid (-row    => 0,
3082
                                           -column => 1,
3083
                                           -sticky => 'w',
3084
                                           -padx   => 5,
3085
                                           -pady   => 5,
3086
                                           );
3087
 
3088
            $popup_rename->Label (-text => $Translate{'EventNewName'},
3089
                                  -width => 20,
3090
                                  -anchor => 'w',
3091
                                  )->grid (-row    => 1,
3092
                                           -column => 0,
3093
                                           -sticky => 'e',
3094
                                           -padx   => 5,
3095
                                           -pady   => 5,
3096
                                          );
3097
 
3098
            my $NewEvent = sprintf ("Event%d", scalar $book->pages() + 1);
3099
            $popup_rename->Entry ( -textvariable => \$NewEvent,
3100
                                   -exportselection => '1',
3101
                                   -width => 40,
3102
                                   -relief => 'sunken',
3103
                                  )->grid (-row    => 1,
3104
                                           -column => 1,
3105
                                           -sticky => 'w',
3106
                                           -padx   => 5,
3107
                                           -pady   => 5,
3108
                                           );
3109
 
3110
            # Button: OK
3111
            $popup_rename->Button('-text'    => "OK",
3112
                                  '-width' => '10',
3113
                                  '-command' => sub
3114
                {
3115
                $NewEvent = &EventnameAdjust($NewEvent);
3116
 
3117
                # create new record in hash
3118
                $CfgEdit->{$NewEvent} = $CfgEdit->{$CurrentEvent};
3119
                delete $CfgEdit->{$CurrentEvent}
3120
 
3121
                # Display events again
3122
                &ConfigureShow($book, $CfgEdit);
3123
 
3124
                $book->raise($NewEvent);
3125
 
3126
                $popup_rename->destroy();
3127
                } )->grid (-row    => 2,
3128
                           -column => 0,
3129
                           -sticky => 'e',
3130
                           -padx   => 20,
3131
                           -pady   => 5,
3132
                          );
3133
 
3134
            # Button: Abort
3135
            $popup_rename->Button('-text'    => $Translate{'Abort'},
3136
                                  '-width' => '10',
3137
                                  '-command' => sub
3138
                {
3139
                $popup_rename->destroy()
3140
                } )->grid (-row    => 2,
3141
                           -column => 1,
3142
                           -sticky => 'w',
3143
                           -padx   => 20,
3144
                           -pady   => 5,
3145
                          );
3146
 
3147
            });
3148
 
3149
        #
3150
        # Copy Event
3151
        #
3152
        $menu_event->command('-label' => $Translate{'EventCopy'},
3153
                             '-command' => sub
3154
            {
3155
            # Event Name in neuem Fenster abfragen
3156
            my $popup_copy = $popup->Toplevel();
3157
            $popup_copy->title("Event - $Translate{'EventCopy'}");
3158
 
3159
            my $CurrentEvent = $book->raised;
3160
            my $CopyEvent = sprintf ("Event%d", scalar $book->pages() + 1);
3161
 
3162
            $popup_copy->Label (-text => $Translate{'EventName'},
3163
                                -width => 20,
3164
                                -anchor => 'w',
3165
                                )->grid (-row    => 0,
3166
                                         -column => 0,
3167
                                         -sticky => 'e',
3168
                                         -padx   => 5,
3169
                                         -pady   => 5,
3170
                                        );
3171
 
3172
            $popup_copy->Entry ( -textvariable => \$CurrentEvent,
3173
                                 -exportselection => '1',
3174
                                 -width => 40,
3175
                                 -relief => 'sunken',
3176
                                 -state => 'disabled',
3177
                                )->grid (-row    => 0,
3178
                                         -column => 1,
3179
                                         -sticky => 'w',
3180
                                         -padx   => 5,
3181
                                         -pady   => 5,
3182
                                         );
3183
 
3184
            $popup_copy->Label (-text => $Translate{'EventNewName'},
3185
                                -width => 20,
3186
                                -anchor => 'w',
3187
                                )->grid (-row    => 1,
3188
                                         -column => 0,
3189
                                         -sticky => 'w',
3190
                                         -padx   => 5,
3191
                                         -pady   => 5,
3192
                                        );
3193
 
3194
 
3195
            $popup_copy->Entry ( -textvariable => \$CopyEvent,
3196
                                 -exportselection => '1',
3197
                                 -width => 40,
3198
                                 -relief => 'sunken',
3199
                                )->grid (-row    => 1,
3200
                                         -column => 1,
3201
                                         -sticky => 'w',
3202
                                         -padx   => 5,
3203
                                         -pady   => 5,
3204
                                         );
3205
            # Button: OK
3206
            $popup_copy->Button('-text'    => "OK",
3207
                                '-width' => '10',
3208
                                '-command' => sub
3209
                {
3210
                $CopyEvent = &EventnameAdjust($CopyEvent);
3211
 
3212
                # copy hash
3213
                $CfgEdit->{$CopyEvent} = {%{$hrefCfg->{$CurrentEvent}}};
3214
 
3215
                # Display new event
3216
                &ConfigureShow($book, $CfgEdit);
3217
                $book->raise($CopyEvent);
3218
 
3219
                $popup_copy->destroy();
3220
                } )->grid (-row    => 2,
3221
                           -column => 0,
3222
                           -sticky => 'w',
3223
                           -padx   => 20,
3224
                           -pady   => 5,
3225
                          );
3226
 
3227
            # Button: Abort
3228
            $popup_copy->Button('-text'    => $Translate{'Abort'},
3229
                               '-width' => '10',
3230
                               '-command' => sub
3231
                {
3232
                $popup_copy->destroy()
3233
                } )->grid (-row    => 2,
3234
                           -column => 1,
3235
                           -sticky => 'e',
3236
                           -padx   => 20,
3237
                           -pady   => 5,
3238
                          );
3239
            });
3240
 
3241
        #
3242
        # Delete event
3243
        #
3244
        $menu_event->command('-label' => $Translate{'EventDelete'},
3245
                             '-command' => sub
3246
            {
3247
            my $CurrentBook = $book->raised;
3248
 
3249
            # delet event in Cfg-Hash
3250
            delete $CfgEdit->{$CurrentBook};
3251
 
3252
            # Display events again
3253
            &ConfigureShow($book, $CfgEdit);
3254
            });
3255
 
3256
        #
3257
        # Export current Event
3258
        #
3259
        $menu_event->command('-label' => $Translate{'EventExport'},
3260
                             '-command' => sub
3261
            {
3262
            my $XmlFile = $popup->getSaveFile('-defaultextension' => ".xml",
3263
                                              '-filetypes'        =>
3264
                                               [['Event',     '.xml' ],
3265
                                                ['All Files', '*', ],
3266
                                                ],
3267
                                              '-initialdir' => "event",
3268
                                              '-title' => $Translate{'EventExport'},
3269
                                             );
3270
            if ( $XmlFile ne "" )
3271
                {
3272
                my %ExportCfg;
3273
                my $CurrentEvent = $book->raised;
3274
 
3275
                # copy and quote event
3276
                foreach $key (keys %{$CfgEdit->{$CurrentEvent}})
3277
                    {
3278
                    my $Line = $CfgEdit->{$CurrentEvent}->{$key};
3279
                    $ExportCfg->{$key} = &QuoteXML($Line);
3280
                    }
3281
 
3282
            # Event in XML-Datei speichern
3283
            &XMLout ($ExportCfg,                 # save quoted hash
3284
                     'OutputFile' => $XmlFile,
3285
                     'AttrIndent' => '1',
3286
                     'RootName'   => 'mkcockpit-Event',
3287
                     'NoEscape'   => '1',
3288
                    );
3289
                }
3290
 
3291
            });
3292
 
3293
        #
3294
        # Import XML to current Event
3295
        #
3296
        $menu_event->command('-label' => $Translate{'EventImport'},
3297
                             '-command' => sub
3298
            {
3299
            my $XmlFile = $popup->getOpenFile(-defaultextension => ".xml",
3300
                                              -filetypes        =>
3301
                                               [['Event',     '.xml' ],
3302
                                                ['All Files', '*', ],
3303
                                                ],
3304
                                              -initialdir => "event",
3305
                                              -title => $Translate{'EventImport'},
3306
                                             );
3307
            if ( -f $XmlFile )
3308
                {
3309
                my $CurrentEvent = $book->raised;
3310
                my $ImportCfg = XMLin($XmlFile);
3311
 
3312
                # copy event
3313
                foreach $key (keys %{$ImportCfg})
3314
                    {
3315
                    $CfgEdit->{$CurrentEvent}->{$key} = $ImportCfg->{$key};
3316
                    }
3317
 
3318
                # deactivate Event
3319
                $CfgEdit->{$CurrentEvent}->{'Active'} = "NO";
3320
                }
3321
            });
3322
        }
3323
    }
3324
 
3325
 
3326
# Copy a Cfg-Hash including real copy of hash-references
3327
sub CopyHash()
3328
    {
3329
    my ($hrefCfg) = @_;
3330
 
3331
    my $CfgCopy = {%{$hrefCfg}};
3332
    foreach $key (keys %{$hrefCfg})
3333
        {
3334
        if ( ref $hrefCfg->{$key} )
3335
            {
3336
            $CfgCopy->{$key} = {%{$hrefCfg->{$key}}};
3337
            }
3338
        }
3339
    return $CfgCopy;
3340
    }
3341
 
3342
 
3343
# Initialize a new event
3344
sub EventInit()
3345
    {
3346
    my ($EventName, $Cfg) = @_;
3347
 
3348
    $Cfg->{$EventName}->{'Active'} = "no";
3349
    $Cfg->{$EventName}->{'Action'} = "";
3350
    $Cfg->{$EventName}->{'ActionElse'} = "";
3351
    $Cfg->{$EventName}->{'Condition'} = "";
3352
    $Cfg->{$EventName}->{'Delay'} = "";
3353
    $Cfg->{$EventName}->{'Repeat'} = "";
3354
    $Cfg->{$EventName}->{'RepeatElse'} = "";
3355
    $Cfg->{$EventName}->{'Description'} = "";
3356
    $Cfg->{$EventName}->{'Trigger'} = "TRUE";
3357
    }
3358
 
3359
 
3360
# Event-Name XML konform anpassen
3361
sub EventnameAdjust()
3362
    {
3363
    my ($Name) = @_;
3364
 
3365
    $Name =~ s/\W/_/g;
3366
    if ( substr ($Name, 0, 1) =~ /\d/ )
3367
        {
3368
        substr ($Name, 0, 1) = "_";
3369
        }
3370
 
3371
    return $Name;
3372
    }
3373
 
3374
 
3375
# Reiter mit Konfigurationsdaten anzeigen
3376
sub ConfigureShow()
3377
    {
3378
    my ($book, $CfgEdit) = @_;
3379
 
3380
    # delete all existing tabs in notebook
3381
    foreach my $Tab ($book->pages)
3382
        {
3383
        $book->delete($Tab);
3384
        }
3385
 
3386
    # jede Sektion in eigenem Tab anzeigen
3387
    foreach $key (sort keys %{$CfgEdit})
3388
        {    
3389
        if ( ! ref $CfgEdit->{$key} )
3390
            {
3391
            next;
3392
            }
3393
 
3394
        my $TabLabel = "$key";
3395
        if ( $Translate{$key} ne "" )
3396
                {
3397
                $TabLabel = $Translate{$key};
3398
                }
3399
 
3400
        my $Tab = $book->add( "$key",
3401
                              -label      => "$TabLabel",
3402
                              -wraplength => "75",
3403
                            );
3404
 
3405
        # Frame for label and data
3406
        my $popup_cfg = $Tab->Frame() -> pack('-anchor' => 'w',
3407
                                              '-padx' => 5,
3408
                                              '-pady' => 5,
3409
                                              );
3410
 
3411
        # Eingabefelder/Optionmenu/Fileselection mit Daten anzeigen
3412
        $Row = 0;
3413
        foreach $Entry ( sort keys %{$CfgEdit->{$key}})
3414
            {                          
3415
            # Label
3416
            my $Label = $Entry;
3417
            if ( $Translate{$Label} ne "" )
3418
                {
3419
                $Label = $Translate{$Label};
3420
                }
3421
 
3422
            $popup_cfg->Label (-text => $Label,
3423
                               -width => 35,
3424
                               -anchor => 'w',
3425
                               )->grid (-row    => $Row,
3426
                                        -column => 0,
3427
                                       );
3428
            #
3429
            # Combo Box with optiones defined in libcfgopt.pl
3430
            #
3431
            if ( defined $CfgOpt{$Entry}[0] )
3432
                {
3433
                my $cbo = $popup_cfg->BrowseEntry( -label => "",
3434
                                                   -variable => \$CfgEdit->{$key}->{$Entry},
3435
                                                   -width => 37,
3436
                                                   -relief => 'sunken',
3437
                                                 )->grid (-row    => $Row,
3438
                                                          -column => 1,
3439
                                                          -columnspan => 2,
3440
                                                          -sticky => 'w',
3441
                                                         );
3442
                # add options
3443
                $cbo->insert("end", @{ $CfgOpt{$Entry} });
3444
                }
3445
 
3446
            #
3447
            # File selection, if defined in libcfgopt.pl
3448
            #
3449
            elsif ( defined $CfgFile{$Entry} )
3450
                {
3451
                # a) Text entry
3452
                my $TextEntry = $popup_cfg->Entry ( -textvariable => \$CfgEdit->{$key}->{$Entry},
3453
                                                    -exportselection => '1',
3454
                                                    -width => 37,
3455
                                                    -relief => 'sunken',
3456
                                                  )->grid (-row    => $Row,
3457
                                                           -column => 1,
3458
                                                           -columnspan => 1,
3459
                                                           -sticky => 'e',
3460
                                                          );
3461
                # b) button with file selection dialog
3462
                my $Mode = $CfgFile{$Entry};
3463
                $popup_cfg->Button('-text'  => '>',
3464
                                   '-width' => 1,
3465
                                   '-command' => sub
3466
                    {
3467
                    my $File = $popup_cfg->getOpenFile( '-title' => $Label );
3468
                    if ($File ne "" )
3469
                        {
3470
                        if ( $Mode =~ /Filename/i )
3471
                            {
3472
                            $File = substr ($File, rindex ($File, '/') +1 );
3473
                            }
3474
                        elsif ( $Mode =~ /Path/i )
3475
                            {
3476
                            # nothing to do
3477
                            }
3478
 
3479
                        # show selection in text entry
3480
                        $TextEntry->delete (0, length $TextEntry->get );
3481
                        $TextEntry->insert (0, $File);
3482
                        }
3483
                    } )->grid (-row    => $Row,
3484
                               -column => 2,
3485
                               -sticky => 'e',
3486
                              );
3487
                }
3488
 
3489
            #
3490
            # Multiline Text widget, if defined in libcfgopt.pl
3491
            #
3492
            elsif ( defined $CfgText{$Entry} )
3493
                {
3494
                # a) Text entry
3495
 
3496
                my $State = 'normal';
3497
                my $NumLines = grep /\n/, $CfgEdit->{$key}->{$Entry};
3498
                if ( $NumLines > 0 )
3499
                    {
3500
                    $State = 'disabled';
3501
                    }
3502
                my $TextEntry = $popup_cfg->Entry ( -textvariable => \$CfgEdit->{$key}->{$Entry},
3503
                                                    -exportselection => '1',
3504
                                                    -state => $State,
3505
                                                    -width => 37,
3506
                                                    -relief => 'sunken',
3507
                                                  )->grid (-row    => $Row,
3508
                                                           -column => 1,
3509
                                                           -columnspan => 1,
3510
                                                           -sticky => 'e',
3511
                                                          );
3512
 
3513
                # b) button with multiline Text-Edit dialog
3514
                my ($Width, $Height) = split /;/, $CfgText{$Entry};
3515
                my $Title = "Edit: $key -> $Entry";
3516
                my $refVariable = \$CfgEdit->{$key}->{$Entry};
3517
 
3518
                $popup_cfg->Button('-text'  => '>',
3519
                                   '-width' => 1,
3520
                                   '-command' => sub
3521
                    {
3522
                    # popup mit Text-Widget
3523
 
3524
                    my $popup_text = $popup_cfg->Toplevel();
3525
                    $popup_text->title($Title);
3526
 
3527
                    my $text = $popup_text->Scrolled( "Text",
3528
                                                      -height => $Height,
3529
                                                      -width  => $Width,
3530
                                                      -wrap   => 'none',
3531
                                                      -scrollbars => 'se',
3532
                                                    )->pack();
3533
 
3534
                    $text->insert('end', $$refVariable);
3535
 
3536
                    # OK Button
3537
                    $popup_text->Button('-text'    => 'OK',
3538
                                        '-width'   => 10,
3539
                                        '-command' => sub
3540
                        {
3541
                        # Text uebernehmen
3542
                        $$refVariable = $text->get("1.0", "end");
3543
                        chomp $$refVariable;
3544
 
3545
                        $popup_text->destroy()
3546
 
3547
                        } )->pack ('-side' => 'left',
3548
                                   '-expand' => 'y',
3549
                                   '-anchor' => 's',
3550
                                   '-padx' => 5,
3551
                                   '-pady' => 5,
3552
                                  );
3553
 
3554
                    # Anwenden Button
3555
                    $popup_text->Button('-text'    => $Translate{'Apply'},
3556
                                        '-width'   => 10,
3557
                                        '-command' => sub
3558
                        {
3559
                        # Text uebernehmen
3560
                        $$refVariable = $text->get("1.0", "end");
3561
                        chomp $$refVariable;
3562
 
3563
                        } )->pack ('-side' => 'left',
3564
                                   '-expand' => 'y',
3565
                                   '-anchor' => 's',
3566
                                   '-padx' => 5,
3567
                                   '-pady' => 5,
3568
                                  );
3569
 
3570
                    # Abort Button
3571
                    $popup_text->Button('-text'    => $Translate{'Abort'},
3572
                                        '-width'   => '10',
3573
                                        '-command' => sub
3574
                        {
3575
                        $popup_text->destroy();
3576
 
3577
                        } )->pack ('-side' => 'left',
3578
                                   '-expand' => 'y',
3579
                                   '-anchor' => 's',
3580
                                   '-padx' => 5,
3581
                                   '-pady' => 5,
3582
                                   );
3583
                    } )->grid (-row    => $Row,
3584
                               -column => 2,
3585
                               -sticky => 'e',
3586
                              );
3587
                }
3588
 
3589
            #
3590
            # Text entry
3591
            #
3592
            else
3593
                {
3594
                $popup_cfg->Entry ( -textvariable => \$CfgEdit->{$key}->{$Entry},
3595
                                    -exportselection => '1',
3596
                                    -width => 40,
3597
                                    -relief => 'sunken',
3598
                                   )->grid (-row    => $Row,
3599
                                            -column => 1,
3600
                                            -columnspan => 2,
3601
                                            -sticky => 'e',
3602
                                           );
3603
                }
3604
 
3605
            # next Variable in next row
3606
            $Row ++;
3607
            }
3608
        }
3609
    }
3610
 
3611
 
3612
# Quote for output to XML-file
3613
sub QuoteXML()
3614
    {
3615
    my ($Line) = @_;
3616
 
3617
    $Line =~ s/\&/\&amp;/g;
3618
    $Line =~ s/'/\&apos;/g;
3619
    $Line =~ s/</\&lt;/g;
3620
    $Line =~ s/>/\&gt;/g;
3621
    $Line =~ s/"/\&quot;/g;
3622
    $Line =~ s/\n/\&#10;/g;
3623
 
3624
    return $Line;
3625
    }
3626
 
3627
 
3628
# Activate and save Config to file
3629
sub ConfigureSave()
3630
    {
3631
    my ($CfgFile, $hrefCfg, $CfgEdit) = @_;
3632
 
3633
    # set new timestamp
3634
    my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
3635
    my $TimeStamp = sprintf ("%04d%02d%02d-%02d%02d%02d", $year+1900, $mon+1, $mday, $hour, $min, $sec);
3636
    $CfgEdit->{'CreationDate'} = $TimeStamp;
3637
 
3638
    # set MkCockpit Version
3639
    $CfgEdit->{'Version'} =  $Version{'mkcockpit.pl'};
3640
 
3641
    # empty original hash
3642
    foreach $key (keys %{$hrefCfg})
3643
        {
3644
        delete $hrefCfg->{$key};
3645
        }
3646
 
3647
    # Build %CfgXml with XML-quoting
3648
    my $CfgXml = {};
3649
 
3650
    # then copy %CfgEdit back to $hrefCfg.
3651
    foreach $key (keys %{$CfgEdit})
3652
        {
3653
        if ( ref $CfgEdit->{$key} )
3654
            {
3655
            # Reference
3656
            foreach $val (keys %{$CfgEdit->{$key}})
3657
                {
3658
                my $Line = $CfgEdit->{$key}->{$val};
3659
                $hrefCfg->{$key}->{$val} = $Line;
3660
                $CfgXml->{$key}->{$val} = &QuoteXML($Line);
3661
                }
3662
            }
3663
        else
3664
            {
3665
            # Scalar
3666
            my $Line = $CfgEdit->{$key};
3667
            $hrefCfg->{$key} = $Line;
3668
            $CfgXml->{$key} = &QuoteXML($Line);
3669
            }
3670
        }
3671
 
3672
    # Cfg in XML-Datei speichern
3673
    &XMLout ($CfgXml,                 # save quoted hash
3674
             'OutputFile' => $CfgFile,
3675
             'AttrIndent' => '1',
3676
             'RootName'   => 'mkcockpit-Config',
3677
             'NoEscape'   => '1',
3678
            );
3679
    }
3680
 
3681
 
3682
1;
3683
 
3684
__END__