Subversion Repositories Projects

Rev

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

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