Subversion Repositories Projects

Rev

Details | Last modification | View Log | RSS feed

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