Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

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