Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

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