Subversion Repositories Projects

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
834 - 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
# 2010-11-01 0.6.0 rw Minor change SPD-Player
63
#                     NC 0.21
64
#
65
###############################################################################
66
 
67
$Version{'libmkcockpit.pl'} = "0.6.0 - 2010-11-01";
68
 
69
 
70
# check, if %MkOsd is valid
71
sub MkOsdIsValid()
72
    {
73
    return ( $MkOsd{'_Timestamp'} >= time-2 );
74
    }
75
 
76
# check, if current GPS position is valid
77
sub CurPosIsValid()
78
    {
79
    return ( &MkOsdIsValid()  and  &MkGpsOk()  and  $MkOsd{'CurPos_Stat'} == 1 );
80
    }
81
 
82
# check, if home GPS position is valid
83
sub HomePosIsValid()
84
    {
85
    return ( &MkOsdIsValid()  and  &MkGpsOk()  and  $MkOsd{'HomePos_Stat'} == 1 );
86
    }
87
 
88
# check, if target GPS position is valid
89
sub TargetIsValid()
90
    {
91
    return ( &MkOsdIsValid()  and  &MkGpsOk()  and  $MkOsd{'TargetPos_Stat'} == 1  );
92
    }
93
 
94
# check, if motor are on
95
sub MkIsMotorOn()
96
    {
97
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x01  );
98
    }
99
 
100
# check, if MK is flying
101
sub MkIsFlying()
102
    {
103
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x02  );
104
    }
105
 
106
# check, if MK is calibrating
107
sub MkIsCalibrating()
108
    {
109
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x04  );
110
    }
111
 
112
# check, if Motor is starting
113
sub MkIsMotorStarting()
114
    {
115
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x08  );
116
    }
117
 
118
# check, Emergency Landing
119
sub MkEmergencyLanding()
120
    {
121
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x10  );
122
    }
123
# check, if Battery Low voltage
124
sub MkIsLowVoltage()
125
    {
126
    return ( &MkOsdIsValid()  and  $MkOsd{'MKFlags'} & 0x20  );
127
    }
128
 
129
# check, if MK is FREE Mode
130
sub MkIsFreeMode()
131
    {
132
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x01  );
133
    }
134
 
135
# check, if MK is in PH Mode
136
sub MkIsPhMode()
137
    {
138
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x02  );
139
    }
140
 
141
# check, if MK is in WPT Mode
142
sub MkIsWptMode()
143
    {
144
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x04  );
145
    }
146
 
147
# check, Range Limit
148
sub MkRangeLimit()
149
    {
150
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x08  );
151
    }
152
 
153
# check, Serial Link
154
sub MkSerialLink()
155
    {
156
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x10  );
157
    }
158
 
159
# check, Target reached
160
sub MkTargetReached()
161
    {
162
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x20  );
163
    }
164
 
165
# check, Manual Control
166
sub MkManualControl()
167
    {
168
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x40  );
169
    }
170
 
171
# check, GPS OK
172
sub MkGpsOk()
173
    {
174
    return ( &MkOsdIsValid()  and  $MkOsd{'NCFlags'} & 0x80  );
175
    }
176
 
177
 
178
# Get altitude (hoehensensor)
179
sub AltitudeAir ()
180
    {
181
    return ( $MkOsd{'Altimeter'} / $Cfg->{'map'}->{'AltFactor'} );
182
    }
183
 
184
# Get altitude (GPS)
185
sub AltitudeGPS ()
186
    {
187
    return ( $MkOsd{'CurPos_Alt'} - $MkOsd{'HomePos_Alt'} );
188
    }
189
 
190
# Get altitude (average hoehensensor , GPS)
191
sub Altitude ()
192
    {
193
    my $Alt =  ( 4 * &AltitudeAir + &AltitudeGPS ) / 5;
194
    return ($Alt);
195
    }
196
 
197
 
198
# check, if Fct-Key "Num" pressed, Num = 1..12
199
sub FctKey()
200
    {
201
    my ($Num) = @_;
202
 
203
    $Num--;
204
    return (($Stick{'FctKey'} >> $Num) & 1) == 1;
205
    }
206
 
207
# check, if Fct-Key "Num" pressed (Toggle), Num = 1..12
208
sub FctKeyToggle()
209
    {
210
    my ($Num) = @_;
211
 
212
    $Num--;
213
    return (($Stick{'FctKeyToggle'} >> $Num) & 1) == 1;
214
    }
215
 
216
# Get RcPoti value. Poti = 1..8
217
sub RcPoti()
218
    {
219
    my ($Poti) = @_;
220
 
221
    return $Stick{"RcPoti" . "$Poti"};
222
    }
223
 
224
 
225
# Get Rc Stick value. Stick = Nick, Roll, Gas, Gier
226
sub RcStick()
227
    {
228
    my ($Stick) = @_;
229
 
230
    return $Stick{"RcStick" . "$Stick"};
231
    }
232
 
233
 
234
# range 0 .. 255
235
sub CheckUnsignedChar()
236
    {
237
    my ($U8) = @_;
238
 
239
    if ( $U8 < 0)   { $U8 = 0; };
240
    if ( $U8 > 255) { $U8 = 255; };
241
    return $U8;
242
    }
243
 
244
# range -128 .. 127
245
sub CheckSignedChar()
246
    {
247
    my ($S8) = @_;
248
 
249
    if ( $S8 < -128) { $S8 = -128; };
250
    if ( $S8 > 127)  { $S8 = 127; };
251
    return $S8;
252
    }
253
 
254
# Set serial Channel value. Num: 0..11, Val: -128..0..127
255
sub SerialChannel()
256
    {
257
    my ($Num, $Val) = @_;
258
 
259
    my $Key = sprintf ("SerialChannel%02d", $Num + 1);
260
 
261
    lock (%MkSerialChannel);    # until end of block
262
 
263
    $MkSerialChannel{$Key} = &CheckSignedChar($Val);  
264
 
265
    # timestamp, when channel value was set
266
    $MkSerialChannel{'_Timestamp'} = time;  
267
    }
268
 
269
 
270
# Limit: 0% .. 100%
271
# Expo : -100% .. 0 .. 100%
272
sub ExpoLimit ()
273
    {
274
    my ($StickMin, $StickMax, $Stick, $Expo, $LimitMin, $LimitMax) = @_;
275
 
276
    if ( $Expo ne "" )
277
        {
278
        # neg. Expo: 1..0.2  (0% .. -100%)
279
        # pos. Expo: 1..5    (0% ..  100%)
280
 
281
        if ( $Expo >= 0 )
282
            {
283
            $Expo = 1 + $Expo / 100 * 4;
284
            }
285
        else
286
            {
287
            $Expo = 1 + $Expo / 100 * 0.8;
288
            }
289
 
290
        if( $Stick >= 0 )
291
            {
292
            $Stick = $StickMax * ( $Stick ** $Expo ) / ( $StickMax ** $Expo);
293
            }
294
        else
295
            {
296
            $Stick = $StickMin * ( (- $Stick) ** $Expo ) / ( (- $StickMin) ** $Expo);
297
            }
298
        }
299
 
300
    # Travel limiter
301
    if ( $Stick >= 0  and  $LimitMax ne "" )
302
        {
303
        $Stick = $Stick * $LimitMax / 100;
304
        }
305
    elsif ( $Stick < 0  and  $LimitMin ne "" )
306
        {
307
        $Stick = $Stick * $LimitMin / 100;
308
        }
309
 
310
    return ($Stick);
311
    }
312
 
313
 
314
# Multipoint Curve
315
sub Curve ()
316
    {
317
    my ($Min, $Max, $Resolution, $Stick, @Curve) = @_;
318
 
319
    my $Points = scalar @Curve;
320
    if ( $Points < 2 )
321
        {
322
        # need at least 2 points
323
        return $Stick;
324
        }
325
 
326
    my $Val;
327
    if ( $Stick < $Min )
328
        {
329
        $Val= $Curve[0];
330
        }
331
    elsif ( $Stick > $Max )
332
        {
333
        $Val = $Curve[$Points -1];
334
        }
335
    else
336
        {
337
        my $i = 0;
338
        my $dx = ($Max - $Min) / ($Points - 1);
339
        for (my $x = $Min; $x < $Max; $x += $dx)
340
            {
341
            if ( $Stick >= $x  and  $Stick <= $x + $dx)
342
                {
343
                my $y1 = $Curve[$i];
344
                my $y2 = $Curve[$i+1];
345
                my $dy = $y2 - $y1;
346
 
347
                $Val = $y1 + $dy / $dx * ($Stick - $x);
348
                last;
349
                }
350
 
351
            $i ++;
352
            }
353
        }
354
 
355
    # Prozent in steps umrechnen
356
    $Stick = $Val / 100 * $Resolution;
357
 
358
    return $Stick;
359
    }
360
 
361
 
362
# parse input controls for one output channel
363
# Syntax: [ChannelOption,par,par] + Control1[_Reverse],par,par,par + Control2 ...  + Control3 ...
364
sub ParseControls
365
    {
366
    my ($Channel, $ControlVal, $Expo, $Limit, $Timing) = @_;
367
 
368
    if ( $Expo  eq "" ) { $Expo  = 0; };
369
    if ( $Limit eq "" ) { $Limit = 100; };
370
 
371
    my $ChannelRes = 125;             # Channel Resolution pos+neg = 250 Steps
372
    my $ChannelInc = 0;               # Channel Incremental Mode: 0, 1, 2
373
    my $ChannelReverse = 1;           # Channel reverse factor, 1 , -1
374
    my $ChannelOffset = 0;            # Channel Offset in %
375
    my $ChannelTravelNeg = $Limit;    # Channel travel in %
376
    my $ChannelTravelPos = $Limit;    # Channel travel in %
377
    my $ChannelLimitNeg =  $Limit;    # Channel limit in %
378
    my $ChannelLimitPos =  $Limit;    # Channel limit in %
379
    my $ChannelExpo = $Expo;          # Channel expo in %
380
    my $ChannelSwitchVal = "OFF";     # Channel switch value in % or "OFF"
381
    my $ChannelSwitchMin = 100;       # Channel switch min output in %
382
    my $ChannelSwitchMax = 100;       # Channel switch max output in %
383
    my @ChannelCurve;                 # Channel curve
384
 
385
    my $IsAsymChannel = 0;
386
    if ( "ExternControlGas ExternControlHeight" =~ /$Channel/i )
387
        {
388
        # special handling for asymmetric channels
389
        $IsAsymChannel = 1;
390
 
391
        $ChannelLimitNeg = 0;
392
        $ChannelTravelNeg = 0;
393
        }
394
 
395
    # Channel output is sum of multiple input controls
396
    my $ChannelVal = 0;
397
    $ControlVal =~ s/ //g;
398
    my @Controls = split('\+', $ControlVal);    # controls separated by "+"
399
    foreach $ControlVal (@Controls)
400
        {
401
        # Control params separated by ","
402
        my @Par = split(',', $ControlVal);
403
        my $Control = $Par[0];
404
        my $ControlTravelNeg = $Par[1];   # in %
405
        my $ControlTravelPos = $Par[2];   # in %
406
        my $ControlExpo      = $Par[3];   # in %
407
        my $ControlOffset    = $Par[4];   # in %
408
 
409
        # take Pos as Neg, if only Neg is given
410
        if ( $ControlTravelNeg eq "" ) { $ControlTravelNeg = 100; }
411
        if ( $ControlTravelPos eq "" ) { $ControlTravelPos = $ControlTravelNeg; }
412
 
413
        if ( $ControlExpo eq "" )   { $ControlExpo = 0; }
414
        if ( $ControlOffset eq "" ) { $ControlOffset = 0; }
415
 
416
        my $Val = 0;               # control value
417
        my $ControlReverse = 1;    # control reverse factor: 1, -1
418
 
419
        my ($Control, $Option) = split('_', $Control, 2);      # Control options separeted by "_"
420
        if ( $Control ne "" )
421
            {
422
 
423
            #
424
            # Output Channel Options
425
            #
426
 
427
            if ( $Control =~ /Rev/i )
428
                {
429
                $ChannelReverse *= -1;
430
                next;
431
                }
432
 
433
            if ( $Control =~ /IncStop/i )
434
                {
435
                # stop at neutral point
436
                $ChannelInc = 2;
437
                next;
438
                }
439
            elsif ( $Control =~ /Inc/i )
440
                {
441
                # don't stop at neutral point
442
                $ChannelInc = 1;
443
                next;
444
                }
445
 
446
            if ( $Control =~ /Offset/i )
447
                {
448
                $ChannelOffset = $Par[1];
449
                if ( $ChannelOffset eq "" ) { $ChannelOffset = 0; };
450
                next;
451
                }
452
 
453
            if ( $Control =~ /Travel/i )
454
                {
455
                $ChannelTravelNeg = $Par[1];
456
                $ChannelTravelPos = $Par[2];
457
 
458
                # take symmetrical Pos as Neg, if only Neg is given
459
                if ( $ChannelTravelNeg eq "" ) { $ChannelTravelNeg = 100; }
460
                if ( $ChannelTravelPos eq "" ) { $ChannelTravelPos = $ChannelTravelNeg; }
461
                next;
462
                }
463
 
464
            if ( $Control =~ /Limit/i )
465
                {
466
                $ChannelLimitNeg = $Par[1];
467
                $ChannelLimitPos = $Par[2];
468
 
469
                if ( $IsAsymChannel  and  $ChannelLimitNeg ne ""  and $ChannelLimitPos eq "" )
470
                    {
471
                    # only Neg given. Take Neg as Pos.
472
                    $ChannelLimitPos = $ChannelLimitNeg;
473
                    $ChannelLimitNeg = 0;
474
                    }
475
 
476
                # take symmetrical Pos as Neg, if only Neg is given
477
                if ( $ChannelLimitNeg eq "" ) { $ChannelLimitNeg = 100; }
478
                if ( $ChannelLimitPos eq "" ) { $ChannelLimitPos = $ChannelLimitNeg; }
479
                next;
480
                }
481
 
482
            if ( $Control =~ /Expo/i )
483
                {
484
                $ChannelExpo = $Par[1];
485
                if ( $ChannelExpo eq "" ) { $ChannelExpo = 0; };
486
                next;
487
                }
488
 
489
            if ( $Control =~ /Switch/i )
490
                {
491
                $ChannelSwitchVal = $Par[1];
492
                $ChannelSwitchMin = $Par[2];
493
                $ChannelSwitchMax = $Par[3];
494
                if ( $ChannelSwitchVal eq "" ) { $ChannelSwitchVal = "OFF"; };
495
                if ( $ChannelSwitchMin eq "" ) { $ChannelSwitchMin = 100; };
496
                if ( $ChannelSwitchMax eq "" ) { $ChannelSwitchMax = 100; };
497
                next;
498
                }
499
 
500
            if ( $Control =~ /Curve/i )
501
                {
502
                @ChannelCurve = @Par;
503
                splice @ChannelCurve, 0, 1;
504
                next;
505
                }
506
 
507
            #
508
            # Input Control Options
509
            #
510
 
511
            if ( $Option =~ /Rev/i )
512
                {
513
                $ControlReverse *= -1;
514
                }
515
 
516
            #
517
            # Input controls
518
            #
519
 
520
            # Joystick Button
521
            if ( $Control =~ /^JoystickButton(\d+)/i )
522
                {
523
                my $Button = $1 - 1;
524
                $Val = &JoystickButton($Button) ? $ChannelRes : -$ChannelRes;
525
                }
526
 
527
            # Joystick POV Button
528
            elsif ( $Control =~ /^JoystickPov(\d+)/i )
529
                {
530
                my $Angle = $1;
531
                my $Pov = $Stick{'JoystickPov'} / 100;
532
                $Val = ($Pov == $Angle) ? $ChannelRes : -$ChannelRes;
533
                }
534
 
535
            # Mouse Button
536
            elsif ( $Control =~ /^MouseButton(\d+)/i )
537
                {
538
                my $Button = $1 - 1;
539
                $Val = &MouseButton($Button) ? $ChannelRes : -$ChannelRes;
540
                }
541
 
542
            # Function Key
543
            elsif ( $Control =~ /^FctKey(\d+)/i )
544
                {
545
                my $Key = $1 - 1;
546
                $Val = &FctKey($Key) ? $ChannelRes : -$ChannelRes;
547
                }
548
 
549
            # Function Key (Toggle Mode)
550
            elsif ( $Control =~ /^FctKeyToggle(\d+)/i )
551
                {
552
                my $Key = $1 - 1;
553
                $Val = &FctKeyToggle($Key) ? $ChannelRes : -$ChannelRes;
554
                }
555
 
556
            # Serial Channel
557
            elsif ( $Control =~ /^SerialChannel/i )
558
                {
559
                $Val = $MkSerialChannel{$Control};
560
                }
561
 
562
            # fixed value
563
            elsif ( $Control =~ /^(-*\d+)/i )
564
                {
565
                $Val = $1;
566
                if ( $IsAsymChannel )
567
                    {
568
                    $Val = $Val - $ChannelRes;
569
                    }
570
                }
571
 
572
            # Joystick
573
            elsif ( $Control =~ /^Joystick/i )
574
                {
575
                # Scale Stick 0..StickRange to -125..0..125
576
                $Val = $Stick{$Control} / $Stick{'StickRange'} * 2 * $ChannelRes - $ChannelRes;
577
                }
578
 
579
            # 3D-Maus
580
            elsif ( $Control =~ /^Mouse/i )
581
                {
582
                # Scale Stick 0..StickRange to -125..0..125
583
                $Val = $Stick{$Control} / $Stick{'StickRange'} * 2 * $ChannelRes - $ChannelRes;
584
                }
585
 
586
            # Rc Poti
587
            elsif ( $Control =~ /^RcPoti/i )
588
                {
589
                $Val = $Stick{$Control};
590
                }
591
 
592
            # Rc Stick
593
            elsif ( $Control =~ /^RcStick/i )
594
                {
595
                $Val = $Stick{$Control};
596
                }
597
            else
598
                {
599
                # unknown, fall through
600
                print "ParseControls: $Channel : Unknown Control \"$Control\"\n";
601
                next;
602
                }
603
 
604
            # Control Reverse
605
            $Val *= $ControlReverse;
606
 
607
            # Expo/Limit for each input control
608
            if ( $IsAsymChannel  and  $ChannelInc == 0 )
609
                {
610
                # asymmetric channel 0..250, if not in INC mode
611
                $Val += $ChannelRes;
612
                $Val = &ExpoLimit (0.001, 2 * $ChannelRes, $Val, $ControlExpo, $ControlTravelNeg, $ControlTravelPos);
613
                }
614
            else
615
                {
616
                # symmetric channel -125..0..125
617
                $Val = &ExpoLimit (- $ChannelRes, $ChannelRes, $Val, $ControlExpo, $ControlTravelNeg, $ControlTravelPos);
618
                }
619
 
620
            # Control Offset
621
            $Val = $Val + $ControlOffset / 100 * $ChannelRes;
622
 
623
            # sum controls
624
            $ChannelVal += $Val;
625
            }
626
        }
627
 
628
    # Incremental Channel Mode. Control must be neg ..0..pos
629
    if ( $ChannelInc > 0 )
630
        {
631
        # Channel travel time is 1s for 125 steps at control full speed
632
 
633
        # Channel neutral point crossing detection 
634
        my $Neutral = $ChannelVal;
635
        my $LastNeutral = $Controls{$Channel}{'Neutral'};
636
        $Controls{$Channel}{'Neutral'} = $Neutral;
637
 
638
        my $LastVal = $Controls{$Channel}{'Value'};
639
        $ChannelVal = $LastVal + $ChannelVal * $Timing / 1000;
640
 
641
        if ( $ChannelInc > 1 )
642
            {
643
            # stop at neutral position
644
            if ( $LastVal <= 0  and  $ChannelVal >= 0  and  $LastNeutral > 5  and $Neutral > 5 )
645
                {
646
                # coming from left
647
                $ChannelVal = 0;
648
                }
649
            elsif ( $LastVal >= 0  and  $ChannelVal <= 0  and  $LastNeutral < -5  and $Neutral < -5 )
650
                {
651
                # comimg from right
652
                $ChannelVal = 0;
653
                }
654
            }
655
 
656
        if ( $IsAsymChannel )
657
            {
658
            # asymmetric channel 0..250
659
            if ( $ChannelVal > 2 * $ChannelRes ) { $ChannelVal = 2 * $ChannelRes };
660
            if ( $ChannelVal < 0 )               { $ChannelVal = 0 };
661
            }
662
        else
663
            {
664
            # symmetric channel -125..0..125
665
            if ( $ChannelVal >  $ChannelRes ) { $ChannelVal =  $ChannelRes };
666
            if ( $ChannelVal < -$ChannelRes ) { $ChannelVal = -$ChannelRes };
667
            }
668
 
669
        $Controls{$Channel}{'Value'} = $ChannelVal;
670
        }
671
 
672
 
673
    # channel output processing
674
    if ( $IsAsymChannel )
675
        {
676
        # asymmetric channel 0..250
677
 
678
        # Channel Multipoint curve
679
        $ChannelVal = &Curve (0.001, 2 * $ChannelRes, 2 * $ChannelRes, $ChannelVal, @ChannelCurve);
680
 
681
        # Channel Expo, Travel
682
        $ChannelVal = &ExpoLimit (0.001, 2 * $ChannelRes, $ChannelVal, $ChannelExpo, $ChannelTravelNeg, $ChannelTravelPos);
683
 
684
        # Channel Switch
685
        if ( $ChannelSwitchVal ne "OFF" )
686
            {
687
            if ( $ChannelVal < 2 * $ChannelRes * $ChannelSwitchVal / 100 )
688
                {
689
                $ChannelVal = 2 * $ChannelRes * $ChannelSwitchMin / 100;
690
                }
691
            else
692
                {
693
                $ChannelVal = 2 * $ChannelRes * $ChannelSwitchMax / 100;
694
                }
695
            }
696
 
697
        # Channel Reverse
698
        if ( $ChannelReverse == -1 )
699
            {
700
            $ChannelVal = 2 * $ChannelRes - $ChannelVal;
701
            }
702
 
703
        # Channel offset
704
        $ChannelVal = $ChannelVal + $ChannelOffset / 100 * 2 * $ChannelRes;
705
 
706
        # Channel Limiter
707
        my $Pos = 2 * $ChannelRes * $ChannelLimitPos / 100;
708
        if ( $ChannelVal > $Pos )
709
            {
710
            $ChannelVal = $Pos;
711
            }
712
        my $Neg = 2 * $ChannelRes * $ChannelLimitNeg / 100;
713
        if ( $ChannelVal < $Neg )
714
            {
715
            $ChannelVal = $Neg;
716
            }
717
 
718
        # round to integer
719
        if ( $ChannelVal >= 0 )
720
            {
721
            $ChannelVal = int ($ChannelVal + 0.5);
722
            }
723
        else
724
            {
725
            $ChannelVal = int ($ChannelVal - 0.5);
726
            }
727
        $ChannelVal = &CheckUnsignedChar($ChannelVal);
728
        }
729
    else
730
        {
731
        # symmetric channel -125..0..125
732
 
733
        # Channel Multipoint curve
734
        $ChannelVal = &Curve (-$ChannelRes, $ChannelRes, $ChannelRes, $ChannelVal, @ChannelCurve);
735
 
736
        # Channel Expo, Travel
737
        $ChannelVal = &ExpoLimit (-$ChannelRes, $ChannelRes, $ChannelVal, $ChannelExpo, $ChannelTravelNeg, $ChannelTravelPos);
738
 
739
        # Channel Switch
740
        if ( $ChannelSwitchVal ne "OFF" )
741
            {
742
            if ( $ChannelVal < $ChannelRes * $ChannelSwitchVal / 100 )
743
                {
744
                $ChannelVal = $ChannelRes * $ChannelSwitchMin / 100;
745
                }
746
            else
747
                {
748
                $ChannelVal = $ChannelRes * $ChannelSwitchMax / 100;
749
                }
750
            }
751
 
752
        # Channel Reverse
753
        $ChannelVal *= $ChannelReverse;
754
 
755
        # Channel offset
756
        $ChannelVal = $ChannelVal + $ChannelOffset / 100 * $ChannelRes;
757
 
758
        # Channel Limiter
759
        my $Pos = $ChannelRes * $ChannelLimitPos / 100;
760
        if ( $ChannelVal > $Pos )
761
            {
762
            $ChannelVal = $Pos;
763
            }
764
        my $Neg = - $ChannelRes * $ChannelLimitNeg / 100;
765
        if ( $ChannelVal < $Neg )
766
            {
767
            $ChannelVal = $Neg;
768
            }
769
 
770
        # round to integer
771
        if ( $ChannelVal >= 0 )
772
            {
773
            $ChannelVal = int ($ChannelVal + 0.5);
774
            }
775
        else
776
            {
777
            $ChannelVal = int ($ChannelVal - 0.5);
778
            }
779
        $ChannelVal = &CheckSignedChar($ChannelVal);
780
        }
781
 
782
    return $ChannelVal;
783
    }
784
 
785
 
786
#
787
# Waypoint handling
788
#
789
 
790
# Add a Waypoint to @Waypoints List
791
sub WpAdd()
792
    {
793
    my %Param = @_;
794
    my $Wp_x = $Param{'-x'};
795
    my $Wp_y = $Param{'-y'};
796
    my $Lat  = $Param{'-lat'};
797
    my $Lon  = $Param{'-lon'};
798
    my $Alt  = $Param{'-alt'};
799
 
800
    # x/y and/or Lat/Lon must be passed
801
    if ( $Wp_x eq ""  and  $Wp_y eq "" )
802
        {
803
        ($Wp_x, $Wp_y) = &MapGps2XY($Lat, $Lon);
804
        }
805
    if ( $Lat eq ""  and  $Lon eq "" )
806
        {
807
        ($Lat, $Lon) = &MapXY2Gps($Wp_x, $Wp_y);
808
        }
809
    if ( $Alt eq "" )
810
        {
811
        $Alt = &Altitude();
812
        }
813
 
814
    # kind of unique Tag for this Wp    
815
    my ($t0_s, $t0_us) = gettimeofday;
816
    my $Tag = sprintf "WP-%d.%d", $t0_s, $t0_us;
817
 
818
    # save Wp-Hash in Waypoint-Array
819
    my $Wp = {};        
820
    $Wp->{'Tag'} = $Tag;
821
    $Wp->{'MapX'} = $Wp_x;
822
    $Wp->{'MapY'} = $Wp_y;
823
    $Wp->{'_MapX_Rel'} = $Wp_x / $MapSizeX;
824
    $Wp->{'_MapY_Rel'} = $Wp_y / $MapSizeY;
825
    $Wp->{'Pos_Lat'} = $Lat;
826
    $Wp->{'Pos_Lon'} = $Lon;
827
    $Wp->{'Pos_Alt'} = $Alt;
828
    $Wp->{'Heading'}         = $Cfg->{'waypoint'}->{'DefaultHeading'};
829
    $Wp->{'ToleranceRadius'} = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
830
    $Wp->{'Holdtime'}        = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
831
    $Wp->{'Event_Flag'}      = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
832
    $Wp->{'Speed'}           = $Cfg->{'waypoint'}->{'Speed'} || 10;
833
    push @Waypoints, $Wp;
834
    }
835
 
836
 
837
# Delete Waypoint from @Waypoints List
838
sub WpDelete ()
839
    {
840
    my ($WpIndex) = @_;
841
 
842
    # delete Wp in Waypoint-Array
843
    splice @Waypoints, $WpIndex, 1;
844
    }
845
 
846
 
847
# Delete all Waypoints
848
sub WpDeleteAll ()
849
    {
850
    undef @Waypoints;
851
    $WpPlayerIndex = 0;
852
    $WpPlayerHoldtime = -1;
853
    $SpdPlayerTime = 0;
854
    $SpdPlayerIndex = 0;
855
 
856
    # remove all Wp-Icons and Wp-Number on canvas
857
    &WpHide();
858
    }
859
 
860
# Add current MK-Position to @Waypoints
861
sub WpCapture()
862
    {
863
    my $Lat = 0;
864
    my $Lon = 0;
865
    my $Alt = 0;
866
    for my $i ( 1..8)       # average over n cycles
867
        {
868
        $Lat += $MkOsd{'CurPos_Lat'};
869
        $Lon += $MkOsd{'CurPos_Lon'};
870
        $Alt += &Altitude();
871
        usleep (250000);  # 0.25s
872
        }
873
    $Lat /= 8;
874
    $Lon /= 8;
875
    $Alt /= 8;
876
 
877
    &WpAdd (-lat => $Lat,
878
            -lon => $Lon,
879
            -alt => $Alt,
880
           );
881
    }
882
 
883
# Load @Waypoints from file
884
sub WpLoadFile ()
885
    {
886
    my ($WpFile) = @_;
887
 
888
    if ( $WpFile =~ /.wpl$/i )
889
        {
890
        # load Mikrokopter Tool WP List *.wpl
891
 
892
        my $WpCnt = 0;
893
        my $WpIndex = 0;
894
        my @WpWpl;
895
 
896
        open WPL, "<$WpFile";
897
        my @Wpl = <WPL>;
898
        close WPL;
899
        foreach my $Line (@Wpl)
900
            {
901
            chomp $Line;
902
            if ( $Line =~ /NumberOfWaypoints\s*=\s*(\d*)/i )
903
                {
904
                $WpCnt = $1;
905
                }
906
            elsif ( $Line =~ /\[Waypoint(\d*)\]/i )
907
                {
908
                $WpIndex = $1;
909
                }
910
            elsif ( $Line =~ /(\S*)\s*=\s*(\S*)/i )
911
                {
912
                my $Key = $1;
913
                my $Value = $2;
914
                $WpWpl[$WpIndex]{$Key} = $Value;
915
                }
916
            }
917
 
918
        # WPL Array in Waypoints-Array umkopieren
919
        undef @Waypoints;
920
 
921
        for ( $Index=0; $Index < $WpCnt; $Index++)
922
            {
923
            my $Wp = {};        
924
            my $Tag = sprintf "Waypoint-%d.%d", time, $Index + 1;   # kind of unique Tag for this Wp
925
 
926
            my $Lat = $WpWpl[$Index]{'Latitude'};
927
            my $Lon = $WpWpl[$Index]{'Longitude'};
928
 
929
            ($MapX, $MapY) = &MapGps2XY($Lat, $Lon);
930
            $Wp->{'Tag'}  = $Tag;
931
            $Wp->{'MapX'} = $MapX;
932
            $Wp->{'MapY'} = $MapY;
933
            $Wp->{'_MapX_Rel'} = $MapX / $MapSizeX;
934
            $Wp->{'_MapY_Rel'} = $MapY / $MapSizeY;
935
            $Wp->{'Pos_Lat'} = $Lat;
936
            $Wp->{'Pos_Lon'} = $Lon;
937
            $Wp->{'Pos_Alt'} = &Altitude();
938
            $Wp->{'Heading'}         = $Cfg->{'waypoint'}->{'DefaultHeading'};
939
            $Wp->{'ToleranceRadius'} = $WpWpl[$Index]{'Radius'};
940
            $Wp->{'Holdtime'}        = $WpWpl[$Index]{'DelayTime'};
941
            $Wp->{'Event_Flag'}      = $Cfg->{'waypoint'}->{'DefaultEventFlag'};
942
            $Wp->{'Speed'}           = $Cfg->{'waypoint'}->{'Speed'} || 10;
943
 
944
            push @Waypoints, $Wp;
945
            }
946
        }
947
    else
948
        {
949
        # load Mission Cockpit XML
950
 
951
        # XML in Hash-Ref lesen
952
        my $Wp = XMLin($WpFile, ForceArray => 1);
953
 
954
        # XML Hash-Ref in Wp-Array umkopieren
955
        undef @Waypoints;
956
 
957
        foreach $key (sort keys %$Wp)
958
            {
959
            my $Point = $Wp->{$key}->[0];
960
 
961
            # relative Pixelkoordinaten auf Bildgroesse umrechnen
962
            if ( $Point->{'MapX'} <= 1  and  $Point->{'MapY'} <= 1 )
963
                {
964
                $Point->{'MapX'} = int ( $Point->{'MapX'} * $MapSizeX + 0.5 );
965
                $Point->{'MapY'} = int ( $Point->{'MapY'} * $MapSizeY + 0.5 );
966
                }
967
 
968
            # abs. pixel koordinates not needed
969
            delete $Point->{'MapX_Pixel'};
970
            delete $Point->{'MapY_Pixel'};
971
 
972
            $Point->{'_MapX_Rel'} = $Point->{'MapX'} / $MapSizeX;
973
            $Point->{'_MapY_Rel'} = $Point->{'MapY'} / $MapSizeY;
974
 
975
            if ( $Point->{'Speed'} eq "" )     { $Point->{'Speed'} = $Cfg->{'waypoint'}->{'Speed'} || 10; }
976
            if ( $Point->{'Pos_Alt'}   eq "" ) { $Point->{'Pos_Alt'}   = 5; }
977
 
978
            # GPS Koordinaten für die aktuelle Karte neu aus Map x/y berechnen
979
            my ($Lat, $Lon) = &MapXY2Gps($Point->{'MapX'}, $Point->{'MapY'});
980
            $Point->{'Pos_Lat'} = $Lat;
981
            $Point->{'Pos_Lon'} = $Lon;
982
            push @Waypoints, $Point;
983
            }
984
        }
985
 
986
    # Start with 1st WP
987
    &WpTargetFirst();
988
    }
989
 
990
 
991
# Save @Waypoints to file
992
sub WpSaveFile()
993
    {
994
    my ($WpFile) = @_;
995
 
996
    if ( $WpFile =~ /.wpl$/i )
997
        {
998
        # save Mikrokopter Tool WP List *.wpl
999
 
1000
        open WPL, ">$WpFile";
1001
 
1002
        my $WpCnt = scalar @Waypoints;
1003
 
1004
        print WPL "[General\]\n";
1005
        print WPL "FileVersion=1\n";
1006
        print WPL "NumberOfWaypoints=$WpCnt\n";
1007
 
1008
        for  $i ( 0 .. $#Waypoints )
1009
            {
1010
            print WPL "\[Waypoint${i}\]\n";
1011
            print WPL "Latitude=$Waypoints[$i]{'Pos_Lat'}\n";
1012
            print WPL "Longitude=$Waypoints[$i]{'Pos_Lon'}\n";
1013
            print WPL "Radius=$Waypoints[$i]{'ToleranceRadius'}\n";
1014
            print WPL "DelayTime=$Waypoints[$i]{'Holdtime'}\n";
1015
            }
1016
        close WPL;
1017
        }
1018
    else
1019
        {
1020
        # save Mission Cockpit XML
1021
 
1022
        # Waypoint-Array in Hash umkopieren
1023
        for  $i ( 0 .. $#Waypoints )
1024
            {
1025
            my $key = sprintf ("WP-%04d", $i);
1026
            my $Wp = {%{$Waypoints[$i]}};        # copy of Hash-content
1027
            $WpOut{$key} = $Wp;
1028
 
1029
            # Pixelkoordinaten relativ zur Bildgroesse speichern
1030
            $WpOut{$key}{'MapX'} /= $MapSizeX;
1031
            $WpOut{$key}{'MapY'} /= $MapSizeY;
1032
            }
1033
 
1034
        # WP-Hash als XML speichern
1035
        &XMLout (\%WpOut,
1036
                 'OutputFile' => $WpFile,
1037
                 'AttrIndent' => '1',
1038
                 'RootName' => 'Waypoints',
1039
                );
1040
        }
1041
    }
1042
 
1043
# Recalc  WP coordinates from relative coordinates
1044
sub WpRecalc()
1045
    {
1046
    for $i (0 .. $#Waypoints)
1047
        {
1048
        my $Wp = $Waypoints[$i];
1049
 
1050
        # new pixel coordinates from relative coordinates
1051
        $Wp->{'MapX'} = int ( $Wp->{'_MapX_Rel'} * $MapSizeX + 0.5 );
1052
        $Wp->{'MapY'} = int ( $Wp->{'_MapY_Rel'} * $MapSizeY + 0.5 );
1053
 
1054
        # new GPS coordinates from pixel coordinates
1055
        my ($Lat, $Lon) = &MapXY2Gps($Wp->{'MapX'}, $Wp->{'MapY'});
1056
        $Wp->{'Pos_Lat'} = $Lat;
1057
        $Wp->{'Pos_Lon'} = $Lon;
1058
        }
1059
    }
1060
 
1061
 
1062
# Get Wp Index from Canvas Id
1063
sub WpGetIndexFromId()
1064
    {
1065
    my ($id) = @_;
1066
 
1067
    my @Tags = $map_canvas->gettags($id);
1068
    my $WpTag = $Tags[1];
1069
 
1070
    for $i (0 .. $#Waypoints)
1071
        {
1072
        my $Wp = $Waypoints[$i];
1073
        if ( $Wp->{'Tag'} eq $WpTag )
1074
            {
1075
            # got it
1076
            return $i;
1077
            }
1078
        }
1079
    return -1;
1080
    }
1081
 
1082
 
1083
# Resend all Waypoints to MK
1084
sub WpSendAll()
1085
    {
1086
    # OSD/Debug Abfragefrequenz verringern, sonst kommen nicht alle Wp im MK an
1087
    # Sicherheitshalber doppelt senden
1088
    $MkSendWp = 1;       # verhindert ueberschreiben im Timer
1089
 
1090
    $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) );   # Frequenz OSD Datensatz, * 10ms
1091
    $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) );   # Frequenz MK Debug Datensatz, * 10ms
1092
    usleep (200000);
1093
    $MkSendQueue->enqueue( "o", "$AddrNC", pack ("C", 1000) );   # Frequenz OSD Datensatz, * 10ms
1094
    $MkSendQueue->enqueue( "d", "$AddrNC", pack ("C", 1000) );   # Frequenz MK Debug Datensatz, * 10ms
1095
    usleep (200000);
1096
 
1097
    # Alte WP-Liste im MK löschen
1098
    my $Wp = $Waypoints[0];
1099
    &MkFlyTo ( '-lat'  => $Wp->{'Pos_Lat'},
1100
               '-lon'  => $Wp->{'Pos_Lon'},
1101
               '-mode' => "Waypoint Delete"
1102
             );
1103
 
1104
    for $i (0 .. $#Waypoints)
1105
        {
1106
        my $Wp = $Waypoints[$i];
1107
        &MkFlyTo ( '-lat'             => $Wp->{'Pos_Lat'},
1108
                   '-lon'             => $Wp->{'Pos_Lon'},
1109
                   '-alt'             => $Wp->{'Pos_Alt'},
1110
                   '-heading'         => $Wp->{'Heading'},
1111
                   '-toleranceradius' => $Wp->{'ToleranceRadius'},
1112
                   '-holdtime'        => $Wp->{'Holdtime'},
1113
                   '-eventflag'       => $Wp->{'Event_Flag'},
1114
                   '-mode'            => "Waypoint",
1115
                   '-index'           => $i,
1116
                 );
1117
 
1118
        usleep (150000)  # NC Zeit zum Verarbeiten geben
1119
        }
1120
 
1121
    $MkSendWp = 0;  # normale OSD/Debug Abfragefrequenz wird automatisch im 5s Timer wieder eingestellt
1122
 
1123
    # grey connectors: Wp are sent to MK
1124
    $map_canvas->itemconfigure('Waypoint-Connector',
1125
                               '-fill' => $Cfg->{'mkcockpit'}->{'ColorWpConnector'},
1126
                              );
1127
 
1128
    # MK ist nun synchron mit @Waypoints
1129
    $WaypointsModified = 0;
1130
    }          
1131
 
1132
 
1133
# Redraw Waypoint Icons
1134
sub WpRedrawIcons()
1135
    {
1136
    if ( $PlayerWptKmlMode eq 'WPT' and ($PlayerRandomMode eq 'STD' or $PlayerRandomMode eq 'RND')  or
1137
         $PlayerWptKmlMode eq 'SPD' )
1138
        {
1139
        # delete old icons and Wp-Number from canvas
1140
        $map_canvas->delete('Waypoint');
1141
        $map_canvas->delete('WaypointNumber');
1142
 
1143
        # create new icons
1144
        for $i (0 .. $#Waypoints)
1145
           {
1146
            my $Wp = $Waypoints[$i];
1147
            my $x = $Wp->{'MapX'};
1148
            my $y = $Wp->{'MapY'};
1149
            my $Tag = $Wp->{'Tag'};
1150
 
1151
            # Waypoint Icon
1152
            my $IconHeight = 48;
1153
            my $IconWidth = 24;
1154
            $map_canvas->createImage($x-$IconWidth/2, $y-$IconHeight,
1155
                                     '-tags' => ['Waypoint', $Tag],
1156
                                     '-anchor' => 'nw',
1157
                                     '-image'  => 'Waypoint-Photo',
1158
                                    );
1159
            # Waypoint Number
1160
            my $WpNumber = $i + 1;
1161
            $map_canvas->createText ( $x+3, $y-$IconHeight/2+12,
1162
                                      '-tags' => ['WaypointNumber', $Tag],
1163
                                      '-text' => $WpNumber,
1164
                                      '-font' => '-*-Arial-Bold-R-Normal--*-100-*',
1165
                                      '-fill' => $Cfg->{'mkcockpit'}->{'ColorWpNumber'},
1166
                                      '-anchor' => 'w',
1167
                                     );
1168
 
1169
            }  
1170
        $map_canvas->lower('Waypoint', 'Target');           # waypoint below Target
1171
        $map_canvas->lower('WaypointNumber', 'Waypoint');   # waypoint-number below waypoint
1172
        }
1173
    }
1174
 
1175
 
1176
# Redraw Waypoint+spline connectors, calc Spline coefficients
1177
sub WpRedrawLines()
1178
    {
1179
    # delete old connectors from canvas
1180
    $map_canvas->delete('Waypoint-Connector');  
1181
 
1182
    # Delete old spline
1183
    $map_canvas->delete('Waypoint-Spline');
1184
 
1185
    if ( scalar(@Waypoints) < 2 )
1186
        {
1187
        # need at least 2 WPs
1188
        return;
1189
        }
1190
 
1191
    if ( $PlayerWptKmlMode eq 'SPD')
1192
        {
1193
        #
1194
        # draw spline polygon
1195
        #
1196
 
1197
        my @WpX;
1198
        my @WpY;
1199
        for $i (0 .. $#Waypoints)
1200
            {
1201
            my $Wp = $Waypoints[$i];
1202
            push @WpX, $Wp->{'MapX'};
1203
            push @WpY, $Wp->{'MapY'};
1204
            }
1205
 
1206
        # Calc spline coefficients
1207
        # global - also unsed in SPD-Timer
1208
        @SplineCoeffX = &SplineCalcNaturalCubic(@WpX);
1209
        @SplineCoeffY = &SplineCalcNaturalCubic(@WpY);
1210
 
1211
        # calc spline polygon
1212
        # very crude technique - just break each segment up into Steps lines
1213
        my $Steps = 16;
1214
 
1215
        my @Polygon;
1216
        push @Polygon, &SplineEval(0, %{$SplineCoeffX[0]});
1217
        push @Polygon, &SplineEval(0, %{$SplineCoeffY[0]});
1218
 
1219
        for $i (0 .. $#SplineCoeffX)
1220
            {
1221
            for ($j = 1; $j <= $Steps; $j++)
1222
                {
1223
                $u = $j / $Steps;
1224
                push @Polygon, &SplineEval($u, %{$SplineCoeffX[$i]});
1225
                push @Polygon, &SplineEval($u, %{$SplineCoeffY[$i]});
1226
                }
1227
            }
1228
 
1229
        # draw polygon
1230
        $map_canvas->createLine ( @Polygon,
1231
                                  -tags => 'Waypoint-Spline',
1232
                                  -fill => $Cfg->{'mkcockpit'}->{'ColorWpConnector'} || 'white',
1233
                                  -width => 1,
1234
                                );
1235
 
1236
        $map_canvas->raise('Waypoint-Spline', 'Map');      # Spline above map
1237
        }
1238
 
1239
    elsif ( $PlayerWptKmlMode eq 'WPT'  and  $PlayerRandomMode eq 'STD' )
1240
        {
1241
        #
1242
        # draw WP-connectors
1243
        #
1244
        my $Color = $Cfg->{'mkcockpit'}->{'ColorWpConnector'};
1245
        if ( $WaypointsModified )
1246
            {
1247
            $Color = $Cfg->{'mkcockpit'}->{'ColorWpResend'};
1248
            }
1249
 
1250
        my $Wp = $Waypoints[0];
1251
        my $x_0 = $Wp->{'MapX'};
1252
        my $y_0 = $Wp->{'MapY'};
1253
        my $x_last = $x_0;
1254
        my $y_last = $y_0;
1255
 
1256
        for $i (1 .. $#Waypoints)
1257
            {
1258
            my $Wp = $Waypoints[$i];
1259
            my $x = $Wp->{'MapX'};
1260
            my $y = $Wp->{'MapY'};
1261
 
1262
            $map_canvas->createLine ( $x_last, $y_last, $x, $y,
1263
                                      '-tags' => 'Waypoint-Connector',
1264
                                      '-arrow' => 'last',
1265
                                      '-arrowshape' => [10, 10, 3 ],
1266
                                      '-fill' => $Color,
1267
                                      '-width' => 1,
1268
                                    );
1269
            $x_last = $x;
1270
            $y_last = $y;
1271
            }
1272
        # Connector last WP to 1st WP
1273
        $map_canvas->createLine ( $x_last, $y_last, $x_0, $y_0,
1274
                                  '-tags' => 'Waypoint-Connector',
1275
                                  '-arrow' => 'last',
1276
                                  '-arrowshape' => [10, 10, 3 ],
1277
                                  '-fill' => $Color,
1278
                                  '-width' => 1,
1279
                                );
1280
 
1281
        $map_canvas->raise('Waypoint-Connector', 'Map');   # connector above map
1282
        }
1283
    }
1284
 
1285
 
1286
# Hide Waypoints and connectors on Canvas
1287
sub WpHide()
1288
   {
1289
   $map_canvas->delete('Waypoint');
1290
   $map_canvas->delete('WaypointNumber');
1291
   $map_canvas->delete('Waypoint-Connector');
1292
   $map_canvas->delete('Waypoint-Spline');
1293
   }
1294
 
1295
 
1296
# Hide Kml-Track on Canvas
1297
sub KmlHide()
1298
   {
1299
   $map_canvas->delete('KML-Track');
1300
   }
1301
 
1302
 
1303
# Load @KmlTargets from file
1304
sub KmlLoadFile()
1305
    {
1306
    my ($File) = @_;
1307
 
1308
    # XML in Hash-Ref lesen
1309
    my $Kml = XMLin($File);
1310
 
1311
    # init state maschine
1312
    undef @KmlTargets;
1313
    $KmlPlayerIndex = 0;
1314
 
1315
    my $Coordinates = $Kml->{Document}->{Placemark}->{LineString}->{coordinates};
1316
    foreach $Line (split "\n", $Coordinates)
1317
        {
1318
        chomp $Line;
1319
        $Line =~ s/\s//g;       # remove white space
1320
        if ( $Line ne "" )
1321
            {
1322
            my ($Lon, $Lat, $Alt) = split ",", $Line;
1323
            $Lon = sprintf ("%f", $Lon);
1324
            $Lat = sprintf ("%f", $Lat);
1325
            $Alt = sprintf ("%f", $Alt);
1326
 
1327
            push @KmlTargets, {'Lat' => $Lat,
1328
                               'Lon' => $Lon,
1329
                               'Alt' => $Alt,
1330
                              };
1331
            }
1332
        }
1333
    }
1334
 
1335
# Redraw KML track
1336
sub KmlRedraw()
1337
    {
1338
 
1339
    # delete old Track from canvas
1340
    $map_canvas->delete('KML-Track');
1341
 
1342
    my @Track;
1343
 
1344
    foreach $Target ( @KmlTargets )
1345
        {
1346
        my $Lat = $Target->{'Lat'};
1347
        my $Lon = $Target->{'Lon'};
1348
        my $Alt = $Target->{'Alt'};
1349
        my ($x, $y) = &MapGps2XY($Lat, $Lon);
1350
        push @Track, $x, $y;
1351
        }
1352
 
1353
    if ( scalar @Track >= 4 )  # at least 2 Koordinaten-Paare
1354
        {
1355
        $map_canvas->createLine ( @Track,
1356
                                  '-tags' => 'KML-Track',
1357
                                  '-fill' => $Cfg->{'mkcockpit'}->{'ColorKmlTrack'},
1358
                                  '-width' => 1,
1359
                                );       
1360
 
1361
        $map_canvas->lower('KML-Track', 'Target');        # Track below Target
1362
        }
1363
    }
1364
 
1365
 
1366
# Redraw Footprint
1367
sub FootprintRedraw()
1368
    {
1369
    # delete old Footprint from canvas
1370
    $map_canvas->delete('Footprint');  
1371
 
1372
    if ( scalar @Footprint >= 4 )  # at least 2 Koordinaten-Paare
1373
        {
1374
        $map_canvas->createLine ( @Footprint,
1375
                                  '-tags' => 'Footprint',
1376
                                  '-fill' => $Cfg->{'mkcockpit'}->{'ColorFootprint'},
1377
                                  '-width' => 1,
1378
                                );       
1379
        }
1380
 
1381
    $map_canvas->lower('Footprint', 'Target');
1382
    }
1383
 
1384
 
1385
# Waypoint Player: Set Waypoint - sequence or random
1386
sub WpTargetSet()
1387
    {
1388
    my ($Index) = @_;
1389
 
1390
    my $WpCnt = scalar @Waypoints;
1391
    if ( $Index < 0  or  $Index >= $WpCnt )
1392
        {
1393
        # invalid WP number
1394
        return 1;
1395
        }
1396
 
1397
    my $Wp = $Waypoints[$Index];
1398
    my $Wp_x = $Wp->{'MapX'};
1399
    my $Wp_y = $Wp->{'MapY'};
1400
 
1401
    # is Wp reachable?
1402
    if ( ! &IsTargetReachable($Wp_x, $Wp_y) )
1403
        {
1404
        # new Wp-Target is not reachable
1405
        return 1;
1406
        }
1407
 
1408
    # set new Wp-Target 
1409
    $WpPlayerIndex = $Index;
1410
    $WpPlayerHoldtime = -1;
1411
 
1412
    return 0;
1413
    }
1414
 
1415
 
1416
# Waypoint Player: Goto next Waypoint - sequence or random
1417
sub WpTargetNext()
1418
    {
1419
    my $WpCnt = scalar @Waypoints;
1420
 
1421
    # Std- or Random Waypoint sequence
1422
    if ( $PlayerRandomMode =~ /STD/i  or
1423
         $PlayerRandomMode =~ /RND/i )
1424
        {
1425
        $NewIndex = $WpPlayerIndex;
1426
 
1427
        # get next Wp
1428
        for ( $i=0; $i<10; $i++)        # avoid deadlock, if no WP reachable
1429
            {
1430
            for ( $j=0; $j<10; $j++ )   # avoid deadlock, if only 1 WP
1431
                {
1432
 
1433
                if ( $PlayerRandomMode =~ /STD/i )
1434
                    {
1435
                    $NewIndex ++;
1436
                    if ( $NewIndex >= $WpCnt )
1437
                        {
1438
                        # Restart with 1st Wp
1439
                        $NewIndex = 0;
1440
                        }
1441
                    }
1442
 
1443
                if ( $PlayerRandomMode =~ /RND/i )
1444
                    {
1445
                    $NewIndex = int (rand($WpCnt));
1446
                    }
1447
 
1448
                # want to have different Wp 
1449
                if ( $NewIndex ne $WpPlayerIndex )
1450
                    {
1451
                    last;
1452
                    }
1453
                }
1454
 
1455
            # Set new Target 
1456
            if ( &WpTargetSet ($NewIndex) == 0 )
1457
                {
1458
                # new Wp-Target set
1459
                last;
1460
                }
1461
            }
1462
        }
1463
 
1464
    # Random Map sequence
1465
    if ( $PlayerRandomMode =~ /MAP/i )
1466
        {
1467
        $RandomTarget_x = $MkPos_x;
1468
        $RandomTarget_y = $MkPos_y;
1469
 
1470
        for ( $i=0; $i<50; $i++)        # avoid deadlock, if target not reachable
1471
            {
1472
            # don't use 10% around the map
1473
            my $New_x = int (rand($MapSizeX - 2 * $MapSizeX/10));
1474
            my $New_y = int (rand($MapSizeY - 2 * $MapSizeY/10));
1475
            $New_x += $MapSizeX/10;
1476
            $New_y += $MapSizeY/10;
1477
 
1478
            # is Target reachable?
1479
            if ( &IsTargetReachable($New_x, $New_y) )
1480
                {
1481
                # new Target found
1482
                $RandomTarget_x = $New_x;
1483
                $RandomTarget_y = $New_y;
1484
                last;
1485
                }
1486
            }
1487
        }
1488
 
1489
    &TtsSpeak ('MEDIUM', $Translate{'TtsNextTarget'});
1490
 
1491
    $WpPlayerHoldtime = -1;
1492
 
1493
    # Altitude Control
1494
    &AltCurPosSave()
1495
    }
1496
 
1497
 
1498
# Waypoint Player: Goto previous Waypoint
1499
sub WpTargetPrev()
1500
    {
1501
    if ( $PlayerRandomMode =~ /STD/i )
1502
        {
1503
        $WpPlayerIndex --;
1504
        if ( $WpPlayerIndex < 0 )
1505
            {
1506
            # Restart with last Wp
1507
            $WpPlayerIndex = $#Waypoints;
1508
            }
1509
        }
1510
    else
1511
        {
1512
        # Next Random Target
1513
        &WpTargetNext();
1514
        }
1515
 
1516
    $WpPlayerHoldtime = -1;
1517
 
1518
    # Altitude Control
1519
    &AltCurPosSave()
1520
    }
1521
 
1522
 
1523
# Waypoint Player: Goto first Waypoint
1524
sub WpTargetFirst()
1525
    {
1526
    $WpPlayerIndex = 0;
1527
    $WpPlayerHoldtime = -1;
1528
 
1529
    # Altitude Control
1530
    &AltCurPosSave()
1531
    }
1532
 
1533
# Waypoint Player: Goto last Waypoint
1534
sub WpTargetLast()
1535
    {
1536
    $WpPlayerIndex = $#Waypoints;
1537
    $WpPlayerHoldtime = -1;
1538
 
1539
    # Altitude Control
1540
    &AltCurPosSave()
1541
    }
1542
 
1543
 
1544
# Waypoint Player: Waypoint Target reached?
1545
sub WpCheckTargetReached()
1546
    {
1547
    if ( $WpPlayerHoldtime == -1 )
1548
        {
1549
        lock (%MkOsd);              # until end of block
1550
 
1551
        if ( &CurPosIsValid() and  &HomePosIsValid() and  &MkIsWptMode() )
1552
            {
1553
            # Gueltige SAT Daten
1554
 
1555
            # for Wp mode
1556
            my $Wp = $Waypoints[$WpPlayerIndex];
1557
            my $WpTarget_Lat = $Wp->{'Pos_Lat'};
1558
            my $WpTarget_Lon = $Wp->{'Pos_Lon'};
1559
            my $WpTolerance  = $Wp->{'ToleranceRadius'};
1560
            my $WpHoldtime   = $Wp->{'Holdtime'};
1561
 
1562
            # Random-Map Mode
1563
            if ( $PlayerRandomMode =~ /MAP/i )
1564
                {
1565
                ($WpTarget_Lat, $WpTarget_Lon) = &MapXY2Gps ($RandomTarget_x, $RandomTarget_y);
1566
                $WpTolerance = $Cfg->{'waypoint'}->{'DefaultToleranceRadius'};
1567
                $WpHoldtime  = $Cfg->{'waypoint'}->{'DefaultHoldtime'};
1568
                }
1569
 
1570
            # Operation Radius pruefen
1571
            my ($HomeDist, $HomeBearing) = &MapGpsTo($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
1572
            if ( $HomeDist > $MkOsd{'OperatingRadius'} )
1573
                {
1574
                # Target entsprechend Operation Radius neu berechnen
1575
                $HomeDist = $MkOsd{'OperatingRadius'};
1576
                ($WpTarget_Lat, $WpTarget_Lon) = &MapGpsAt($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'}, $HomeDist, $HomeBearing);
1577
                }
1578
 
1579
            # Abstand zum Ziel pruefen
1580
            my ($Dist, $Bearing) = &MapGpsTo($MkOsd{'CurPos_Lat'}, $MkOsd{'CurPos_Lon'}, $WpTarget_Lat, $WpTarget_Lon );
1581
            $Dist = int ($Dist + 0.5);
1582
            if ( $Dist <= $WpTolerance )
1583
                {
1584
                # Target reached - count down Holdtime
1585
                $WpPlayerHoldtime = 2 * $WpHoldtime;      # 0..2n - decrement im 0.5s timer
1586
 
1587
                &TtsSpeak ('MEDIUM', $Translate{'TtsTargetReached'});
1588
                }
1589
            }
1590
        }
1591
 
1592
    if ( $WpPlayerHoldtime == 0 )  # wird im 0.5s timer runtergezaehlt
1593
        {
1594
        # Target reached - Holdtime is over
1595
        $WpPlayerHoldtime = -1;
1596
 
1597
        return 1;
1598
        }
1599
 
1600
    # Target NOT reached
1601
    return 0;
1602
    }
1603
 
1604
 
1605
# KML Player: 10s forward
1606
sub KmlTargetNext()
1607
    {
1608
    $KmlPlayerIndex += int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
1609
    if ( $KmlPlayerIndex > $#KmlTargets )
1610
        {
1611
        # Next loop
1612
        $KmlPlayerIndex -= $#KmlTargets;
1613
        }
1614
    }
1615
 
1616
# KML Player: 10s backward
1617
sub KmlTargetPrev()
1618
    {
1619
    $KmlPlayerIndex -= int (10 / $Cfg->{waypoint}->{'KmlTimeBase'} + 0.5);
1620
    if ( $KmlPlayerIndex < 0 )
1621
        {
1622
        # Next loop
1623
        $KmlPlayerIndex += $#KmlTargets;
1624
        }
1625
    }
1626
 
1627
# KML Player: Goto first Target
1628
sub KmlTargetFirst()
1629
    {
1630
    $KmlPlayerIndex = 0;
1631
    }
1632
 
1633
# KML Player: Goto last Target
1634
sub KmlTargetLast()
1635
    {
1636
    $KmlPlayerIndex = $#KmlTargets;
1637
    }
1638
 
1639
 
1640
# SPD Player: 10s forward
1641
sub SpdTargetNext()
1642
    {
1643
    $SpdPlayerTime += 10;
1644
 
1645
    # Altitude Control
1646
    &AltCurPosSave()
1647
    }
1648
 
1649
# SPD Player: 10s backward
1650
sub SpdTargetPrev()
1651
    {
1652
    $SpdPlayerTime -= 10;
1653
    if ( $SpdPlayerTime < 0 )
1654
        {
1655
        $SpdPlayerTime = 0;
1656
        $System{'AltWpIndex'} = 0;
1657
        }
1658
 
1659
    # Altitude Control
1660
    &AltCurPosSave()
1661
    }
1662
 
1663
# SPD Player: Goto first Target
1664
sub SpdTargetFirst()
1665
    {
1666
    $SpdPlayerTime = 0;
1667
    $SpdPlayerIndex = 0;
1668
    $System{'AltWpIndex'} = 0;
1669
 
1670
    # Altitude Control
1671
    &AltCurPosSave();
1672
    }
1673
 
1674
# SPD Player: Goto last Target
1675
sub SpdTargetLast()
1676
    {
1677
    $SpdPlayerTime = 0;
1678
    $SpdPlayerIndex = 0;
1679
    $System{'AltWpIndex'} = 0;
1680
 
1681
    # Altitude Control
1682
    &AltCurPosSave();
1683
    }
1684
 
1685
 
1686
#
1687
# Set Player modes
1688
#
1689
 
1690
# set Player mode
1691
sub PlayerModeSet()
1692
    {
1693
    my ($Mode) = @_;
1694
 
1695
    if    ( $Mode =~ /play/i )  { &PlayerPlay(); }
1696
    elsif ( $Mode =~ /pause/i ) { &PlayerPause(); }
1697
    elsif ( $Mode =~ /home/i )  { &PlayerHome(); }
1698
    elsif ( $Mode =~ /stop/i )  { &PlayerStop(); }
1699
    }
1700
 
1701
 
1702
# set player to "Play" mode
1703
sub PlayerPlay()
1704
    {
1705
    $PlayerMode = 'Play';
1706
    $WpPlayerHoldtime = -1;
1707
 
1708
    # Play/Pause-Icon loeschen und neu anzeigen
1709
    $map_canvas->delete('Wp-PlayPause');
1710
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1711
                             '-tags' => 'Wp-PlayPause',
1712
                             '-anchor' => 'nw',
1713
                             '-image'  => 'WpPause-Foto',
1714
                             );
1715
    &FoxHide();
1716
    &CrosshairHide();
1717
 
1718
    # Altitude control
1719
    &AltCurPosSave();
1720
    }
1721
 
1722
 
1723
# set player to "Pause" mode
1724
sub PlayerPause()
1725
    {
1726
    $PlayerMode = 'Pause';
1727
    $WpPlayerHoldtime = -1;
1728
 
1729
    # Play/Pause-Icon loeschen und neu anzeigen
1730
    $map_canvas->delete('Wp-PlayPause');
1731
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1732
                             '-tags' => 'Wp-PlayPause',
1733
                             '-anchor' => 'nw',
1734
                             '-image'  => 'WpPlay-Foto',
1735
                             );
1736
 
1737
    # momentane Position merken und im Player-Timer senden
1738
    $PlayerPause_Lon = "";
1739
    $PlayerPause_Lat = "";
1740
 
1741
    lock (%MkOsd);              # until end of block
1742
    if ( &CurPosIsValid() )
1743
        {
1744
        $PlayerPause_Lon = $MkOsd{'CurPos_Lon'};
1745
        $PlayerPause_Lat = $MkOsd{'CurPos_Lat'};
1746
        }
1747
 
1748
    &FoxShow();
1749
 
1750
    # restart crosshair timer
1751
    $CrosshairTimerCnt = 0;
1752
 
1753
    # Altitude Control
1754
    &AltCurPosSave()
1755
    }
1756
 
1757
 
1758
# set player to "Home" mode
1759
sub PlayerHome()
1760
    {
1761
 
1762
    $PlayerMode = 'Home';
1763
    &WpTargetFirst();
1764
 
1765
    # Play/Pause-Icon loeschen und neu anzeigen
1766
    $map_canvas->delete('Wp-PlayPause');
1767
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1768
                             '-tags'   => 'Wp-PlayPause',
1769
                             '-anchor' => 'nw',
1770
                             '-image'  => 'WpPlay-Foto',
1771
                             );
1772
    &FoxHide();
1773
    &CrosshairHide();
1774
 
1775
    # Altitude control
1776
    &AltCurPosSave();
1777
    }
1778
 
1779
 
1780
# set player to "Stop" mode
1781
sub PlayerStop()
1782
    {
1783
    $PlayerMode = 'Stop';
1784
    &WpTargetFirst();
1785
 
1786
    # set Play/Pause Icon to "Play
1787
    $map_canvas->delete('Wp-PlayPause');
1788
    $map_canvas->createImage($MapSizeX/2+150, $MapSizeY-48,
1789
                             '-tags'   => 'Wp-PlayPause',
1790
                             '-anchor' => 'nw',
1791
                             '-image'  => 'WpPlay-Foto',
1792
                             );
1793
 
1794
    # switch player to Wp Mode
1795
    &PlayerWpt();
1796
 
1797
    &FoxHide();
1798
    &CrosshairHide();
1799
 
1800
    # Altitude control
1801
    &AltCurPosSave();
1802
    }
1803
 
1804
 
1805
# set player Random Mode to "STD"
1806
sub PlayerRandomStd()
1807
    {
1808
    if ( $PlayerWptKmlMode ne 'WPT' )
1809
        {
1810
        # not in WPT Mode
1811
        return;
1812
        }
1813
 
1814
    $PlayerRandomMode = "STD";
1815
 
1816
    # Set Icon
1817
    $map_canvas->delete('Wp-WptRandom');
1818
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
1819
                             '-tags' => 'Wp-WptRandom',
1820
                             '-anchor' => 'nw',
1821
                             '-image'  => 'WpRandomOn-Foto',
1822
                            );
1823
 
1824
    # redraw connectors and Icons on canvas
1825
    &WpRedrawLines();
1826
    &WpRedrawIcons();
1827
 
1828
    # Altitude control
1829
    &AltCurPosSave();
1830
    }
1831
 
1832
 
1833
# set player Random Mode to "RND"
1834
sub PlayerRandomRnd()
1835
    {
1836
    if ( $PlayerWptKmlMode ne 'WPT' )
1837
        {
1838
        # not in WPT Mode
1839
        return;
1840
        }
1841
 
1842
    $PlayerRandomMode = "RND";
1843
 
1844
    # Set Icon
1845
    $map_canvas->delete('Wp-WptRandom');
1846
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
1847
                             '-tags' => 'Wp-WptRandom',
1848
                             '-anchor' => 'nw',
1849
                             '-image'  => 'WpRandomMap-Foto',
1850
                            );
1851
 
1852
    # delete Wp-connectors from canvas
1853
    $map_canvas->delete('Waypoint-Connector');
1854
    $map_canvas->delete('Waypoint-Spline');
1855
 
1856
    # Altitude control
1857
    &AltCurPosSave();
1858
    }
1859
 
1860
 
1861
# set player Random Mode to "MAP"
1862
sub PlayerRandomMap()
1863
    {
1864
    if ( $PlayerWptKmlMode ne 'WPT' )
1865
        {
1866
        # not in WPT Mode
1867
        return;
1868
        }
1869
 
1870
    $PlayerRandomMode = "MAP";
1871
 
1872
    # Set Icon
1873
    $map_canvas->delete('Wp-WptRandom');
1874
    $map_canvas->createImage($MapSizeX/2-200, $MapSizeY-48,
1875
                             '-tags' => 'Wp-WptRandom',
1876
                             '-anchor' => 'nw',
1877
                             '-image'  => 'WpRandomOff-Foto',
1878
                            );
1879
 
1880
    # Get 1st Target
1881
    &WpTargetNext();
1882
 
1883
    # hide WP and connectors on canvas
1884
    &WpHide();
1885
 
1886
    # Altitude control
1887
    &AltCurPosSave();
1888
    $WpPlayerMapAlt = &Altitude();
1889
    }
1890
 
1891
 
1892
# set player Pause Mode to "MAP", "MK"
1893
sub PlayerPauseMode()
1894
    {
1895
    ($PlayerPauseMode) = @_;
1896
    }
1897
 
1898
 
1899
# set player to KML mode
1900
sub PlayerKml()
1901
    {
1902
    $PlayerWptKmlMode = 'KML';
1903
 
1904
    # Wpt/Kml-Player-Icon loeschen und neu anzeigen
1905
    $map_canvas->delete('Wp-WptKml');
1906
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1907
                             '-tags' => 'Wp-WptKml',
1908
                             '-anchor' => 'nw',
1909
                             '-image'  => 'WpKml-Foto',
1910
                             );
1911
 
1912
    # delete Waypoints from canvas
1913
    &WpHide();
1914
 
1915
    # show KML Track
1916
    &KmlRedraw();
1917
 
1918
    # Altitude control
1919
    &AltCurPosSave();
1920
    }
1921
 
1922
 
1923
# set player to WPT mode
1924
sub PlayerWpt()
1925
    {
1926
    $PlayerWptKmlMode = 'WPT';
1927
 
1928
    # Wpt/Kml-Player-Icon loeschen und neu anzeigen
1929
    $map_canvas->delete('Wp-WptKml');
1930
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1931
                             '-tags' => 'Wp-WptKml',
1932
                             '-anchor' => 'nw',
1933
                             '-image'  => 'WpWpt-Foto',
1934
                             );
1935
 
1936
    # delete Kml-Track from canvas
1937
    &KmlHide();
1938
 
1939
    # remove all Wp-Icons and Wp-Number on canvas
1940
    &WpHide();
1941
 
1942
    # Show waypoints, WP resend required
1943
    $WaypointsModified = 1;
1944
 
1945
    if ( $PlayerRandomMode ne 'MAP' )
1946
        {
1947
        &WpRedrawIcons()
1948
        }
1949
    if ( $PlayerRandomMode eq 'STD' )
1950
        {
1951
        &WpRedrawLines()
1952
        }
1953
 
1954
    # Altitude control
1955
    &AltCurPosSave();
1956
    }
1957
 
1958
 
1959
# set player to SPD mode
1960
sub PlayerSpd()
1961
    {
1962
    $PlayerWptKmlMode = 'SPD';
1963
 
1964
    # Wpt/Spd/Kml-Player-Icon loeschen und neu anzeigen
1965
    $map_canvas->delete('Wp-WptKml');
1966
    $map_canvas->createImage($MapSizeX/2-250, $MapSizeY-48,
1967
                             '-tags' => 'Wp-WptKml',
1968
                             '-anchor' => 'nw',
1969
                             '-image'  => 'WpSpd-Foto',
1970
                             );
1971
 
1972
    # delete Kml-Track from canvas
1973
    &KmlHide();
1974
 
1975
    # remove all Wp-Icons and Wp-Number on canvas
1976
    &WpHide();
1977
 
1978
    # Show waypoints, WP resend required
1979
    $WaypointsModified = 1;
1980
 
1981
    # show icon and spline
1982
    &WpRedrawIcons();
1983
    &WpRedrawLines();
1984
 
1985
    # Altitude control
1986
    &AltCurPosSave();
1987
    }
1988
 
1989
 
1990
# Activate Recording mode
1991
sub PlayerRecordOn
1992
    {
1993
    $PlayerRecordMode = "REC";
1994
    $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "Recording" );
1995
 
1996
    # Record new KML-Track
1997
    undef @KmlTargets;
1998
    $KmlPlayerIndex = 0;
1999
 
2000
    # delete Kml-Track from canvas
2001
    &KmlHide();
2002
    }
2003
 
2004
# Deactivate Recording mode
2005
sub PlayerRecordOff
2006
    {
2007
    $PlayerRecordMode = "";
2008
    $map_canvas->itemconfigure ('MK-OSD-Rec-Value', '-text' => "" );
2009
    }
2010
 
2011
 
2012
# Hide Fox icon on canvas
2013
sub FoxHide()
2014
    {
2015
    $map_canvas->lower('Fox', 'Map');
2016
    }
2017
 
2018
# Show Fox icon on canvas
2019
sub FoxShow()
2020
    {
2021
    $map_canvas->raise('Fox', 'Target');
2022
    }
2023
 
2024
# Hide POI icon on canvas
2025
sub PoiHide()
2026
    {
2027
    $map_canvas->lower('POI', 'Map');
2028
    }
2029
 
2030
# Show POI icon on canvas
2031
sub PoiShow()
2032
    {
2033
    $map_canvas->raise('POI', 'Track-Antenna');
2034
    }
2035
 
2036
# Show Grid on canvas
2037
sub GridShow()
2038
    {
2039
    my $Dist  = $Cfg->{map}->{'GridDist'}  || 50;
2040
    my $Color = $Cfg->{map}->{'GridColor'} || "#909090";
2041
 
2042
    my $xmin = 0;
2043
    my $ymin = 0;
2044
    my $xmax = $MapSizeX;
2045
    my $ymax = $MapSizeY;
2046
 
2047
 
2048
    my $PhiRef = &MapAngel();  
2049
    my ($Lat1, $Lon1) = &MapXY2Gps($xmin, $ymin);
2050
    my ($Lat2, $Lon2) = &MapGpsAt($Lat1, $Lon1, $Dist, $PhiRef);
2051
    my ($x, $y) = &MapGps2XY($Lat2, $Lon2);
2052
    my $dpix = int ($x - $xmin + 0.5);
2053
 
2054
    lock (%MkOsd);              # until end of block
2055
    my $x0 = $MapSizeX / 2;
2056
    my $y0 = $MapSizeY / 2;
2057
    if ( &HomePosIsValid() )
2058
        {
2059
        ($x0, $y0) = &MapGps2XY ($MkOsd{'HomePos_Lat'}, $MkOsd{'HomePos_Lon'});
2060
        }
2061
 
2062
    for ($x = $xmin + $x0 % $dpix; $x < $xmax; $x +=$dpix)
2063
        {
2064
        $map_canvas->createLine ( $x, $ymin, $x, $ymax,
2065
                                  '-tags' => 'Map-Grid',
2066
                                  '-arrow' => 'none',
2067
                                  '-fill' => $Color,
2068
                                  '-width' => 1,
2069
                                );
2070
        }
2071
 
2072
    for ($y = $ymin + $y0 % $dpix; $y < $ymax; $y +=$dpix)
2073
        {
2074
        $map_canvas->createLine ( $xmin, $y, $xmax, $y,
2075
                                  '-tags' => 'Map-Grid',
2076
                                  '-arrow' => 'none',
2077
                                  '-fill' => $Color,
2078
                                  '-width' => 1,
2079
                                );
2080
        }
2081
 
2082
    # Beschriftung x
2083
    for ( $x = xmin + $x0 % $dpix; $x < $xmax; $x += $dpix)
2084
        {
2085
        my $ScaleX = int (($x - $x0) / $dpix * $Dist + 0.5);
2086
        if ( $ScaleX < 0 )
2087
            {
2088
            $ScaleX = int (($x - $x0) / $dpix * $Dist - 0.5);
2089
            }
2090
        $map_canvas->createText ( $x - 2, $y0 - 8,
2091
                                  '-tags' => 'Map-Grid',
2092
                                  '-text' => sprintf ("%d", $ScaleX),
2093
                                  '-font' => '-*-Arial-Bold-R-Normal--*-150-*',
2094
                                  '-fill' => $Color,
2095
                                  '-anchor' => 'e',
2096
                                );
2097
        }
2098
    # Beschriftung y
2099
    for ( $y = ymin + $y0 % $dpix; $y < $ymax; $y += $dpix)
2100
        {
2101
        my $ScaleY = int (($y - $y0) / $dpix * $Dist + 0.5);
2102
        if ( $ScaleY < 0 )
2103
            {
2104
            $ScaleY = int (($y - $y0) / $dpix * $Dist - 0.5);
2105
            }
2106
        $map_canvas->createText ( $x0 + 4, $y - 8,
2107
                                  '-tags' => 'Map-Grid',
2108
                                  '-text' => sprintf ("%d", $ScaleY * -1),
2109
                                  '-font' => '-*-Arial-Bold-R-Normal--*-150-*',
2110
                                  '-fill' => $Color,
2111
                                  '-anchor' => 'w',
2112
                                );
2113
        }
2114
 
2115
    $map_canvas->raise('Map-Grid', 'Map');
2116
    }
2117
 
2118
 
2119
# Hide Grid on canvas
2120
sub GridHide()
2121
    {
2122
    $map_canvas->delete('Map-Grid');
2123
    }
2124
 
2125
 
2126
# Show Crosshair for Pause Position on canvas
2127
sub CrosshairShow()
2128
    {
2129
    my ($Lat, $Lon) = @_;
2130
 
2131
    my ($x, $y) = &MapGps2XY ($Lat, $Lon);
2132
    if ( $x != $LastCrosshairX  and  $y != $LastCroshairY )
2133
        {
2134
        # Only update, if coords changed - CPU consuming!
2135
        $map_canvas->coords ('Map-Crosshair-X', 0, $y, $MapSizeX, $y);
2136
        $map_canvas->coords ('Map-Crosshair-Y', $x, 0, $x, $MapSizeY);
2137
 
2138
        $map_canvas->raise('Map-Crosshair', 'Target');
2139
        }
2140
 
2141
    $LastCrosshairX = $x;
2142
    $LastCrosshairY = $y;
2143
    }
2144
 
2145
 
2146
# Hide Crosshair on canvas
2147
sub CrosshairHide()
2148
    {
2149
    $map_canvas->lower('Map-Crosshair', 'Map');  # hide below map
2150
 
2151
    $LastCrosshairX = -1;
2152
    $LastCrosshairY = -1;
2153
    }
2154
 
2155
 
2156
#
2157
# System Messages
2158
#
2159
 
2160
# Init Messages for a Subsystem/timer
2161
sub MkMessageInit ()
2162
    {
2163
    my ($Id) = @_;
2164
 
2165
    $MkMessages{$Id} = [];
2166
    }
2167
 
2168
 
2169
# Register message
2170
sub MkMessage ()
2171
    {
2172
    my ($Message, $Id) = @_;
2173
 
2174
    push @{$MkMessages{$Id}}, $Message;
2175
    }
2176
 
2177
 
2178
# show registered messages
2179
sub MkMessageShow()
2180
    {
2181
    my @Messages;
2182
    my $MsgLines = 0;
2183
    my $MaxMsgLen = 0;
2184
 
2185
    # Collect Messages of each category
2186
    foreach my $Id (keys %MkMessages)
2187
        {
2188
        foreach $i ( 0 .. $#{$MkMessages{$Id}} )
2189
            {
2190
            my $Msg = $MkMessages{$Id}[$i];
2191
            push @Messages, $Msg;
2192
 
2193
            $MsgLines ++;
2194
 
2195
            my $Len = length $Msg;
2196
            if ( $Len > $MaxMsgLen )
2197
                {
2198
                $MaxMsgLen = $Len;
2199
                }
2200
            }
2201
        }
2202
 
2203
    $map_canvas->delete('Message-Balloon');  # delete old Balloon
2204
 
2205
    if ( $MsgLines > 0 )
2206
        {
2207
        # draw Balloon
2208
        my @MsgBalloon = ( $MkPos_x ,                       $MkPos_y,
2209
                           $MkPos_x + 30 ,                  $MkPos_y + 40,
2210
                           $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 40,
2211
                           $MkPos_x + 30 + $MaxMsgLen * 11, $MkPos_y + 44 + $MsgLines * 20,
2212
                           $MkPos_x + 20,                   $MkPos_y + 44 + $MsgLines * 20,
2213
                           $MkPos_x + 20,                   $MkPos_y + 40,
2214
                           $MkPos_x,                        $MkPos_y,
2215
                          );
2216
 
2217
        $map_canvas->createPolygon( @MsgBalloon,
2218
                                    '-tags' => ['Message-Balloon', 'Message-BalloonBubble'],
2219
                                    '-fill' => 'yellow',
2220
                                    '-outline' => 'yellow',
2221
                                    '-width' => 1,
2222
                                  );
2223
        # draw Messages
2224
        my $MsgLine = 1;
2225
        foreach my $Msg (@Messages)
2226
            {
2227
            $map_canvas->createText ( $MkPos_x + 25, $MkPos_y + 32 + $MsgLine * 20 ,
2228
                                      '-tags' => ['Message-Balloon', 'Message-BalloonText'],
2229
                                      '-text' => $Msg,
2230
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2231
                                      '-fill' => 'blue',
2232
                                      '-anchor' => 'w',
2233
                                        );
2234
            $MsgLine ++;
2235
            }
2236
 
2237
 
2238
        $map_canvas->lower('Message-Balloon', 'MK-Arrow');
2239
        }
2240
 
2241
    }
2242
 
2243
 
2244
# Show Balloon, when aproaching Target
2245
sub TargetMessageShow ()
2246
    {
2247
    $map_canvas->delete('Target-Balloon');  # delete old Balloon
2248
 
2249
    if ( $OperationMode ne "Free" and $MkOsd{'TargetPos_Stat'} == 1  and $MkOsd{'TargetPosDev_Dist'} /10 < 25 )
2250
        {
2251
        my $BalloonLines = 0;
2252
        $ColorBalloon = "blue";
2253
        my ($T_x, $T_y) = &MapGps2XY($MkOsd{'TargetPos_Lat'}, $MkOsd{'TargetPos_Lon'});
2254
        my $Wp = $Waypoints[$MkOsd{'WaypointIndex'}];
2255
 
2256
        # Holdtime Wp-Player Mode
2257
        if ( $WpPlayerHoldtime >= 0  and  $PlayerWptKmlMode  eq "WPT" )
2258
            {
2259
            # Holdtime
2260
            $ColorBalloon = 'red';
2261
            my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($WpPlayerHoldtime / 2  + 0.5) );
2262
            $map_canvas->createText ( $T_x + 25, $T_y - 40,
2263
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2264
                                      '-text' => $HoldTime,
2265
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2266
                                      '-fill' => $ColorBalloon,
2267
                                      '-anchor' => 'w',
2268
                                    );
2269
            $BalloonLines ++;
2270
            }
2271
 
2272
        # Holdtime WPT-Mode
2273
        if ( &MkTargetReached()  and  $OperationMode eq "WPT" )
2274
            {
2275
            # Holdtime from MK
2276
            $ColorBalloon = 'red';
2277
            my $HoldTime = sprintf ("%5s %3d s", "HLD:", int ($MkOsd{'TargetHoldTime'} + 0.5) );
2278
            $map_canvas->createText ( $T_x + 25, $T_y - 40,
2279
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2280
                                      '-text' => $HoldTime,
2281
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2282
                                      '-fill' => $ColorBalloon,
2283
                                      '-anchor' => 'w',
2284
                                    );
2285
            $BalloonLines ++;
2286
            }
2287
 
2288
        # Tolerance Radius Player Mode
2289
        if ( &MkIsWptMode()  and  $OperationMode eq "Play" and $PlayerWptKmlMode eq "WPT" )
2290
            {
2291
            my $WpTolerance  = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'});
2292
            $map_canvas->createText ( $T_x + 25, $T_y - 60,
2293
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2294
                                      '-text' => $WpTolerance,
2295
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2296
                                      '-fill' => $ColorBalloon,
2297
                                      '-anchor' => 'w',
2298
                                    );
2299
            $BalloonLines ++;
2300
            }
2301
 
2302
        # Tolerance WPT-Mode
2303
        if ( &MkIsWptMode  and  $OperationMode eq "WPT" )
2304
            {
2305
            my $WpTolerance  = sprintf ("%5s %3d m", "TOL:", $Wp->{'ToleranceRadius'} );
2306
            $map_canvas->createText ( $T_x + 25, $T_y - 60,
2307
                                      '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2308
                                      '-text' => $WpTolerance,
2309
                                      '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2310
                                      '-fill' => $ColorBalloon,
2311
                                      '-anchor' => 'w',
2312
                                    );
2313
            $BalloonLines ++;
2314
            }
2315
 
2316
        # Distance to Target
2317
        my $Dist = int ($MkOsd{'TargetPosDev_Dist'} /10 + 0.5);
2318
        $map_canvas->createText ( $T_x + 25, $T_y - 80,
2319
                                  '-tags' => ['Target-Balloon', 'Target-BalloonText'],
2320
                                  '-text' => sprintf ("%5s %3d m", "DST:", $Dist) ,
2321
                                  '-font' => '-*-Arial-Bold-R-Normal--*-200-*',
2322
                                  '-fill' => $ColorBalloon,
2323
                                  '-anchor' => 'w',
2324
                                );
2325
        $BalloonLines ++;
2326
 
2327
        if ( $BalloonLines >= 1 )
2328
            {
2329
            # draw Balloon
2330
            my @TargetBalloon = ( $T_x ,      $T_y,
2331
                                  $T_x + 30,  $T_y - (3 - $BalloonLines) * 20 -27,
2332
                                  $T_x + 150, $T_y - (3 - $BalloonLines) * 20 -27 ,
2333
                                  $T_x + 150, $T_y - 93,
2334
                                  $T_x + 20,  $T_y - 93,
2335
                                  $T_x + 20,  $T_y - (3 - $BalloonLines) * 20 -27,
2336
                                  $T_x,       $T_y,
2337
                                );
2338
 
2339
            $map_canvas->createPolygon( @TargetBalloon,
2340
                                        '-tags' => ['Target-Balloon', 'Target-BalloonBubble'],
2341
                                        '-fill' => 'lightgray',
2342
                                        '-outline' => 'yellow',
2343
                                        '-width' => 1,
2344
                                       );
2345
            }
2346
 
2347
 
2348
        $map_canvas->lower('Target-Balloon', 'MK-Home-Line');
2349
        $map_canvas->lower('Target-BalloonBubble', 'Target-BalloonText');
2350
        }
2351
    }
2352
 
2353
 
2354
#
2355
# Airfield border
2356
#
2357
 
2358
# Are two segments A(a1/a2), B(b1/b2) and C(c1/c2), D(d1/d2) crossing ?
2359
sub SegmentCross()
2360
    {
2361
    my ( $a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) = @_;
2362
 
2363
    # segment C/D ist vertical, avoid div/0
2364
    if ( $c1 == $d1 )
2365
        {
2366
        $d1 += 0.00001;
2367
        }
2368
 
2369
    my $n = ($b1 - $a1) * ($d2 - $c2) - ($b2 - $a2) * ($d1 - $c1);
2370
    if ( $n == 0.0 )
2371
        {
2372
        # AB und CD sind parallel
2373
        return 0;
2374
        }
2375
 
2376
    my $s = ( ($c1 - $a1) * ($d2 - $c2) - ($c2 - $a2) * ($d1 - $c1) ) / $n;
2377
    my $t = ( $a1 - $c1 + $s * ($b1 - $a1) ) / ( $d1 - $c1 );
2378
    if ( $s >= 0.0  and  $s <= 1.0  and  $t >= 0.0  and  $t <= 1.0 )
2379
        {
2380
        # beide Strecken kreuzen sich
2381
 
2382
        # Schnittpunkt: s_x, s_y
2383
        my $s_x = $a1 + $s * ( $b1 - $a1 );
2384
        my $s_y = $a2 + $s * ( $b2 - $a2 );
2385
 
2386
        return 1;
2387
        }
2388
 
2389
    # beide Strecken kreuzen sich nicht
2390
    return 0;
2391
    }
2392
 
2393
 
2394
# How often does a segment A(a1,a2), B(b1,b2) cross the polygon?
2395
sub SegmentPolygonCross()
2396
    {
2397
    my ( $a1, $a2, $b1, $b2, $Polygon) = @_;
2398
 
2399
    my $Cross = 0;
2400
    my $PolyCnt = scalar @{$Polygon};
2401
    my $PolyPointCnt = $PolyCnt / 2;
2402
 
2403
    my $i = 0;
2404
    for ( $p=0; $p < $PolyPointCnt; $p++ )
2405
        {
2406
        my $c1 = ${$Polygon}[$i++];
2407
        my $c2 = ${$Polygon}[$i++];
2408
 
2409
        if ( $i >= $PolyCnt ) { $i = 0; }
2410
 
2411
        my $d1 = ${$Polygon}[$i];
2412
        my $d2 = ${$Polygon}[$i+1];
2413
 
2414
        # map calibration offsets
2415
        $c1 -= $Map{'Offset_x'};
2416
        $c2 += $Map{'Offset_y'};
2417
        $d1 -= $Map{'Offset_x'};
2418
        $d2 += $Map{'Offset_y'};    
2419
 
2420
        if ( &SegmentCross($a1, $a2, $b1, $b2, $c1, $c2, $d1, $d2) )
2421
            {
2422
            $Cross ++;
2423
            }
2424
        }
2425
 
2426
    return $Cross;
2427
    }
2428
 
2429
 
2430
# Is point A inside airfield border?
2431
sub IsInsideBorder()
2432
    {
2433
    my ($a1, $a2) = @_;
2434
 
2435
    if ( scalar @Map{'Border'} == 0 )
2436
        {
2437
        # no border defined, always inside
2438
        return 1;
2439
        }
2440
 
2441
    my $Cross = &SegmentPolygonCross (-10, -10, $a1, $a2, @Map{'Border'} );
2442
 
2443
    # Ungerade Anzahl Kreuzungen: Inside
2444
    return ( $Cross % 2 );
2445
    }
2446
 
2447
 
2448
 
2449
# Is segment A, B crossing the airfield border?
2450
sub IsCrossingBorder()
2451
    {
2452
    my ($a1, $a2, $b1, $b2) = @_;
2453
 
2454
    if ( scalar @Map{'Border'} == 0 )
2455
        {
2456
        # no border defined, always not crossing
2457
        return 0;
2458
        }
2459
 
2460
    my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
2461
 
2462
    return ( $Cross > 0 );
2463
    }
2464
 
2465
 
2466
# How often is segment A, B crossing the airfield border?
2467
sub CrossingBorderCount()
2468
    {
2469
    my ($a1, $a2, $b1, $b2) = @_;
2470
 
2471
    if ( scalar @Map{'Border'} == 0 )
2472
        {
2473
        # no border defined, not crossing
2474
        return 0;
2475
        }
2476
 
2477
    my $Cross = &SegmentPolygonCross ($a1, $a2, $b1, $b2, @Map{'Border'} );
2478
 
2479
    return ( $Cross );
2480
    }
2481
 
2482
 
2483
# check, if Target is reachable my MK
2484
sub IsTargetReachable()
2485
    {
2486
    my ($T_x, $T_y) = @_;
2487
 
2488
    my $MkIsInside = &IsInsideBorder($MkPos_x, $MkPos_y);
2489
    my $TargetIsInside = &IsInsideBorder($T_x, $T_y);
2490
    my $MkTargetCrossingCount = &CrossingBorderCount($MkPos_x, $MkPos_y, $T_x, $T_y);
2491
 
2492
    if ( ($MkIsInside  and  $MkTargetCrossingCount == 0 )  or
2493
         (! $MkIsInside  and  $TargetIsInside  and  $MkTargetCrossingCount == 1) )
2494
        {
2495
        # Target is reachable
2496
        return 1;
2497
        }
2498
 
2499
    # Target is not reachable
2500
    return 0;
2501
    }
2502
 
2503
 
2504
#
2505
# Closed natural cubic spline
2506
#
2507
# Derived from: http://www.cse.unsw.edu.au/~lambert/splines
2508
# @author Tim Lambert
2509
#
2510
# calculates the closed natural cubic spline that interpolates x[0], x[1], ... x[n]
2511
# The first segment is returned as
2512
# C[0].a + C[0].b*u + C[0].c*u^2 + C[0].d*u^3 0<=u<1
2513
# the other segments are in C[1], C[2], ...  C[n]
2514
 
2515
sub SplineCalcNaturalCubic()
2516
    {
2517
    my (@x) = @_;
2518
 
2519
    # We solve the equation
2520
    #   [4 1      1] [D[0]]   [3(x[1] - x[n])  ]
2521
    #   |1 4 1     | |D[1]|   |3(x[2] - x[0])  |
2522
    #   |  1 4 1   | | .  | = |      .         |
2523
    #   |    ..... | | .  |   |      .         |
2524
    #   |     1 4 1| | .  |   |3(x[n] - x[n-2])|
2525
    #   [1      1 4] [D[n]]   [3(x[0] - x[n-1])]
2526
 
2527
    #   by decomposing the matrix into upper triangular and lower matrices
2528
    #   and then back sustitution.  See Spath "Spline Algorithms for Curves
2529
    #   and Surfaces" pp 19--21. The D[i] are the derivatives at the knots.
2530
 
2531
    my @C, @D, @v, @w, @y, $F, $G, $H, $k, $z;
2532
    my $n = $#x;
2533
 
2534
    $w[1] = $v[1] = $z = 1.0/4.0;
2535
    $y[0] = $z * 3 * ($x[1] - $x[$n]);
2536
    $H = 4;
2537
    $F = 3 * ($x[0] - $x[$n-1]);
2538
    $G = 1;
2539
    for ( $k = 1; $k < $n; $k++)
2540
        {
2541
        $v[$k+1] = $z = 1/(4 - $v[$k]);
2542
        $w[$k+1] = -$z * $w[$k];
2543
        $y[$k] = $z * (3*($x[$k+1]-$x[$k-1]) - $y[$k-1]);
2544
        $H = $H - $G * $w[$k];
2545
        $F = $F - $G * $y[$k-1];
2546
        $G = -$v[$k] * $G;
2547
        }
2548
    $H = $H - ($G+1)*($v[$n]+$w[$n]);
2549
    $y[$n] = $F - ($G+1)*$y[$n-1];
2550
 
2551
    $D[$n] = $y[$n]/$H;
2552
    $D[$n-1] = $y[$n-1] - ($v[$n]+$w[$n])*$D[$n];
2553
    for ( $k = $n-2; $k >= 0; $k--)
2554
        {
2555
        $D[$k] = $y[$k] - $v[$k+1]*$D[$k+1] - $w[$k+1]*$D[$n];
2556
        }
2557
 
2558
    # now compute the coefficients of the cubics
2559
    for ( $k = 0; $k < $n; $k++)
2560
        {
2561
        $C[$k]{'a'} = $x[$k];
2562
        $C[$k]{'b'} = $D[$k];
2563
        $C[$k]{'c'} = 3*($x[$k+1] - $x[$k]) - 2*$D[$k] - $D[$k+1];
2564
        $C[$k]{'d'} = 2*($x[$k] - $x[$k+1]) + $D[$k] + $D[$k+1];
2565
        }
2566
 
2567
    $C[$n]{'a'} = $x[$n];
2568
    $C[$n]{'b'} = $D[$n];
2569
    $C[$n]{'c'} = 3*($x[0] - $x[$n]) - 2*$D[$n] - $D[0];
2570
    $C[$n]{'d'} = 2*($x[$n] - $x[0]) + $D[$n] + $D[0];
2571
 
2572
    return @C;
2573
    }
2574
 
2575
# Eval interpolated spline value
2576
sub SplineEval()
2577
    {
2578
    my ($u, %C) = @_;
2579
 
2580
    $a = $C{'a'};
2581
    $b = $C{'b'};
2582
    $c = $C{'c'};
2583
    $d = $C{'d'};
2584
 
2585
    return ((($d*$u) + $c)*$u + $b)*$u + $a;
2586
    }
2587
 
2588
# Get pixel position on WP-Spline after elapesd time
2589
sub SplinePosFromTime()
2590
    {
2591
    my ($Time) = @_;
2592
 
2593
    if ( scalar(@Waypoints) >= 2 )
2594
        {
2595
        # eval x/y target according to Time and Speed
2596
        my $TrackTime = 0;      # time/s needed to fly spline-track
2597
        my $WpIndex = 0;
2598
        my $Steps = 16;
2599
 
2600
        while (1)
2601
            {
2602
            my $Wp = $Waypoints[$WpIndex];
2603
            my $x0 = $Wp->{'MapX'};
2604
            my $y0 = $Wp->{'MapY'};
2605
 
2606
            my $WpIndex1 = $WpIndex + 1;
2607
            if ( $WpIndex1 >= scalar (@Waypoints) )
2608
                {
2609
                $WpIndex1 = 0;
2610
                }
2611
            my $Wp = $Waypoints[$WpIndex1];
2612
            my $Wp1_Speed = $Wp->{'Speed'} || 10;  # km/h
2613
            $Wp1_Speed /= 3.6;                     # m/s
2614
 
2615
            for (my $j = 1; $j <= $Steps; $j++)
2616
                {
2617
                my $u = $j / $Steps;
2618
                my $x = &SplineEval($u, %{$SplineCoeffX[$WpIndex]});
2619
                my $y = &SplineEval($u, %{$SplineCoeffY[$WpIndex]});
2620
 
2621
                my $SegmentLen = &MapXYTo($x0, $y0, $x, $y);
2622
                my $SegmentTime = $SegmentLen / $Wp1_Speed;
2623
                $TrackTime += $SegmentTime;
2624
 
2625
                if ( $TrackTime >= $SpdPlayerTime )
2626
                    {
2627
                    # position found
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, $WpIndex1);
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{'AltStartPos_XX'} 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__