source: src/mlx/config.py@ 1139:9754005a7e70

python3
Last change on this file since 1139:9754005a7e70 was 1123:f0334593281d, checked in by István Váradi <ivaradi@…>, 12 months ago

Support for an alternative sound set

File size: 43.7 KB
Line 
1# -*- encoding: utf-8 -*-
2
3from . import const
4from .util import secondaryInstallation, utf2unicode
5
6import os
7import sys
8import traceback
9import configparser
10
11## @package mlx.config
12#
13# The handling of the configuration.
14#
15# The \ref Config class contains the main configuration and is capable of
16# loading and saving the configuration. It contains getters and setters for the
17# configuration options.
18#
19# Some parts of the configuration are not simple data items, like strings or
20# booleans, but more complicated data. These have their own class, like \ref
21# ApproachCallouts or \ref Checklist.
22
23#-------------------------------------------------------------------------------
24
25configPath = os.path.join(os.path.expanduser("~"),
26 "mlx.config" if os.name=="nt" else ".mlxrc") + \
27 ("-secondary" if secondaryInstallation else "")
28
29#-------------------------------------------------------------------------------
30
31if os.name=="nt":
32 _languageMap = { "en_GB" : "eng",
33 "hu_HU" : "hun" }
34
35#-------------------------------------------------------------------------------
36
37class Hotkey(object):
38 """A hotkey."""
39 def __init__(self, ctrl = False, shift = False, key = "0"):
40 """Construct the hotkey."""
41 self.ctrl = ctrl
42 self.shift = shift
43 self.key = key
44
45 def set(self, s):
46 """Set the hotkey from the given string."""
47 self.ctrl = "C" in s[:-1]
48 self.shift = "S" in s[:-1]
49 self.key = s[-1]
50
51 def __eq__(self, other):
52 """Check if the given hotkey is equal to the other one."""
53 return self.ctrl == other.ctrl and self.shift == other.shift and \
54 self.key == other.key
55
56 def __ne__(self, other):
57 """Check if the given hotkey is not equal to the other one."""
58 return not self==other
59
60 def __str__(self):
61 """Construct the hotkey to a string."""
62 s = ""
63 if self.ctrl: s += "C"
64 if self.shift: s += "S"
65 s += self.key
66 return s
67
68#-------------------------------------------------------------------------------
69
70class Checklist(object):
71 """A checklist for a certain aircraft type."""
72 # The name of the section of the checklists
73 SECTION="checklists"
74
75 @staticmethod
76 def fromConfig(config, aircraftType):
77 """Create a checklist for the given aircraft type from the given
78 config."""
79 baseName = "checklist." + const.icaoCodes[aircraftType] + "."
80 fileList = []
81 while True:
82 option = baseName + str(len(fileList))
83 if config.has_option(Checklist.SECTION, option):
84 fileList.append(config.get(Checklist.SECTION, option))
85 else:
86 break
87
88 return Checklist(fileList)
89
90 def __init__(self, fileList = None):
91 """Construct the check list with the given file list."""
92 self._fileList = [] if fileList is None else fileList[:]
93
94 def clone(self):
95 """Clone the checklist."""
96 return Checklist(self._fileList)
97
98 def toConfig(self, config, aircraftType):
99 """Add this checklist to the given config."""
100 baseName = "checklist." + const.icaoCodes[aircraftType] + "."
101 for index in range(0, len(self._fileList)):
102 option = baseName + str(index)
103 config.set(Checklist.SECTION, option,
104 self._fileList[index])
105
106 def __eq__(self, other):
107 """Determine if the checklist is equal to the given other one."""
108 return self._fileList == other._fileList
109
110 def __ne__(self, other):
111 """Determine if the checklist is not equal to the given other one."""
112 return not self==other
113
114 def __len__(self):
115 """Get the length of the file list."""
116 return len(self._fileList)
117
118 def __getitem__(self, index):
119 """Get the file with the given index."""
120 return self._fileList[index]
121
122 def __iter__(self):
123 """Iterate over the files."""
124 return iter(self._fileList)
125
126#-------------------------------------------------------------------------------
127
128class ApproachCallouts(object):
129 """The approach callouts for a certain aircraft type."""
130 # The name of the section of the approach callouts
131 SECTION="callouts"
132
133 @staticmethod
134 def fromConfig(config, aircraftType):
135 """Create a checklist for the given aircraft type from the given
136 config."""
137 baseName = "callouts." + const.icaoCodes[aircraftType] + "."
138 mapping = {}
139 while True:
140 option = baseName + str(len(mapping))
141 if config.has_option(ApproachCallouts.SECTION, option):
142 value = config.get(ApproachCallouts.SECTION, option)
143 (altitude, path) = value.split(",")
144 altitude = int(altitude.strip())
145 path = path.strip()
146 mapping[altitude] = path
147 else:
148 break
149
150 return ApproachCallouts(mapping)
151
152 def __init__(self, mapping = None):
153 """Construct the check list with the given mapping of altitudes to
154 files."""
155 self._mapping = {} if mapping is None else mapping.copy()
156
157 def clone(self):
158 """Clone the callout information."""
159 return ApproachCallouts(self._mapping)
160
161 def toConfig(self, config, aircraftType):
162 """Add this checklist to the given config."""
163 baseName = "callouts." + const.icaoCodes[aircraftType] + "."
164 index = 0
165 for (altitude, path) in self._mapping.items():
166 option = baseName + str(index)
167 config.set(ApproachCallouts.SECTION, option,
168 "%d, %s" % (altitude, path))
169 index += 1
170
171 def getAltitudes(self, descending = True):
172 """Get the altitudes in decreasing order by default."""
173 altitudes = list(self._mapping.keys())
174 altitudes.sort(reverse = descending)
175 return altitudes
176
177 def __bool__(self):
178 """Return if there is anything in the mapping."""
179 return not not self._mapping
180
181 def __eq__(self, other):
182 """Determine if the approach callout mapping is equal to the given
183 other one."""
184 return self._mapping == other._mapping
185
186 def __ne__(self, other):
187 """Determine if the approach callout mapping is not equal to the given
188 other one."""
189 return not self==other
190
191 def __len__(self):
192 """Get the number of elements in the mapping."""
193 return len(self._mapping)
194
195 def __getitem__(self, altitude):
196 """Get the file that is associated with the given altitude.
197
198 If no such file found, return None."""
199 return self._mapping[altitude] if altitude in self._mapping else None
200
201 def __iter__(self):
202 """Iterate over the pairs of altitudes and paths in decreasing order of
203 the altitude."""
204 altitudes = self.getAltitudes()
205
206 for altitude in altitudes:
207 yield (altitude, self._mapping[altitude])
208
209#-------------------------------------------------------------------------------
210
211class Config(object):
212 """Our configuration."""
213 DEFAULT_UPDATE_URL = "https://mlx.varadiistvan.hu/update"
214
215 _messageTypesSection = "messageTypes"
216
217 def __init__(self):
218 """Construct the configuration with default values."""
219
220 self._pilotID = ""
221 self._password = ""
222 self._rememberPassword = False
223
224 self._clearBrowserCacheOnStart = False
225 self._language = ""
226 self._mainWindowResizable = False
227 self._hideMinimizedWindow = True
228 self._quitOnClose = False
229 self._onlineGateSystem = not secondaryInstallation
230 self._onlineACARS = not secondaryInstallation
231 self._flareTimeFromFS = False
232 self._syncFSTime = False
233 self._usingFS2Crew = False
234 self._iasSmoothingLength = -2
235 self._vsSmoothingLength = -2
236
237 self._useSimBrief = False
238 self._useInternalBrowserForSimBrief = False
239 self._simBriefUserName = ""
240 self._simBriefPassword = ""
241 self._rememberSimBriefPassword = False
242
243 self._pirepDirectory = None
244 self._pirepAutoSave = False
245
246 self._defaultMSFS = os.name=="nt"
247
248 self._enableSounds = not secondaryInstallation
249
250 self._pilotControlsSounds = True
251 self._pilotHotkey = Hotkey(ctrl = True, shift = False, key = "0")
252
253 self._taxiSoundOnPushback = False
254
255 self._enableApproachCallouts = False
256 self._speedbrakeAtTD = True
257 self._soundSet = 0
258
259 self._enableChecklists = False
260 self._checklistHotkey = Hotkey(ctrl = True, shift = True, key = "0")
261
262 self._autoUpdate = True
263 self._updateURL = Config.DEFAULT_UPDATE_URL
264
265 self._xplaneRemote = False
266 self._xplaneAddress = ""
267
268 self._messageTypeLevels = {}
269
270 self._checklists = {}
271 self._approachCallouts = {}
272 for aircraftType in const.aircraftTypes:
273 self._checklists[aircraftType] = Checklist()
274 self._approachCallouts[aircraftType] = ApproachCallouts()
275
276 self._modified = False
277
278 @property
279 def pilotID(self):
280 """Get the pilot ID."""
281 return self._pilotID
282
283 @pilotID.setter
284 def pilotID(self, pilotID):
285 """Set the pilot ID."""
286 if pilotID!=self._pilotID:
287 self._pilotID = pilotID
288 self._modified = True
289
290 @property
291 def password(self):
292 """Get the password."""
293 return self._password
294
295 @password.setter
296 def password(self, password):
297 """Set the password."""
298 if password!=self._password:
299 self._password = password
300 self._modified = True
301
302 @property
303 def rememberPassword(self):
304 """Get if we should remember the password."""
305 return self._rememberPassword
306
307 @rememberPassword.setter
308 def rememberPassword(self, rememberPassword):
309 """Set if we should remember the password."""
310 if rememberPassword!=self._rememberPassword:
311 self._rememberPassword = rememberPassword
312 self._modified = True
313
314 @property
315 def clearBrowseCacheOnStart(self):
316 """Get whether the browser cache should be cleared on start."""
317 return self._clearBrowserCacheOnStart
318
319 @clearBrowseCacheOnStart.setter
320 def clearBrowseCacheOnStart(self, clearBrowseCacheOnStart):
321 """Set whether the browser cache should be cleared on start."""
322 if clearBrowseCacheOnStart!=self._clearBrowserCacheOnStart:
323 self._clearBrowserCacheOnStart = clearBrowseCacheOnStart
324 self._modified = True
325
326 @property
327 def language(self):
328 """Get the language to use."""
329 return self._language
330
331 @language.setter
332 def language(self, language):
333 """Set the language to use."""
334 if language!=self._language:
335 self._language = language
336 self._modified = True
337
338 @property
339 def mainWindowResizable(self):
340 """Get whether a the main window should be resizable."""
341 return self._mainWindowResizable
342
343 @mainWindowResizable.setter
344 def mainWindowResizable(self, mainWindowResizable):
345 """Set whether a minimized window should be hidden."""
346 if mainWindowResizable!=self._mainWindowResizable:
347 self._mainWindowResizable = mainWindowResizable
348 self._modified = True
349
350 @property
351 def hideMinimizedWindow(self):
352 """Get whether a minimized window should be hidden."""
353 return self._hideMinimizedWindow
354
355 @hideMinimizedWindow.setter
356 def hideMinimizedWindow(self, hideMinimizedWindow):
357 """Set whether a minimized window should be hidden."""
358 if hideMinimizedWindow!=self._hideMinimizedWindow:
359 self._hideMinimizedWindow = hideMinimizedWindow
360 self._modified = True
361
362 @property
363 def quitOnClose(self):
364 """Get whether the application should quit when the close button is
365 clicked."""
366 return self._quitOnClose
367
368 @quitOnClose.setter
369 def quitOnClose(self, quitOnClose):
370 """Set whether the application should quit when the close button is
371 clicked."""
372 if quitOnClose!=self._quitOnClose:
373 self._quitOnClose = quitOnClose
374 self._modified = True
375
376 @property
377 def onlineGateSystem(self):
378 """Get whether the online gate system should be used."""
379 return self._onlineGateSystem
380
381 @onlineGateSystem.setter
382 def onlineGateSystem(self, onlineGateSystem):
383 """Set whether the online gate system should be used."""
384 if onlineGateSystem!=self._onlineGateSystem:
385 self._onlineGateSystem = onlineGateSystem
386 self._modified = True
387
388 @property
389 def onlineACARS(self):
390 """Get whether the online ACARS system should be used."""
391 return self._onlineACARS
392
393 @onlineACARS.setter
394 def onlineACARS(self, onlineACARS):
395 """Set whether the online ACARS system should be used."""
396 if onlineACARS!=self._onlineACARS:
397 self._onlineACARS = onlineACARS
398 self._modified = True
399
400 @property
401 def flareTimeFromFS(self):
402 """Get whether the flare time should be calculated from the time values
403 returned by the simulator."""
404 return self._flareTimeFromFS
405
406 @flareTimeFromFS.setter
407 def flareTimeFromFS(self, flareTimeFromFS):
408 """Set whether the flare time should be calculated from the time values
409 returned by the simulator."""
410 if flareTimeFromFS!=self._flareTimeFromFS:
411 self._flareTimeFromFS = flareTimeFromFS
412 self._modified = True
413
414 @property
415 def syncFSTime(self):
416 """Get whether the simulator's time should be synchronized with the
417 machine's clock."""
418 return self._syncFSTime
419
420 @syncFSTime.setter
421 def syncFSTime(self, syncFSTime):
422 """Set whether the simulator's time should be synchronized with the
423 machine's clock."""
424 if syncFSTime!=self._syncFSTime:
425 self._syncFSTime = syncFSTime
426 self._modified = True
427
428 @property
429 def usingFS2Crew(self):
430 """Get whether the FS2Crew addon is being used."""
431 return self._usingFS2Crew
432
433 @usingFS2Crew.setter
434 def usingFS2Crew(self, usingFS2Crew):
435 """Set whether the FS2Crew addon is being used."""
436 if usingFS2Crew!=self._usingFS2Crew:
437 self._usingFS2Crew = usingFS2Crew
438 self._modified = True
439
440 @property
441 def iasSmoothingLength(self):
442 """Get the number of samples over which the IAS is averaged for the
443 smoothed IAS calculation. It may be negative, in which case smoothing
444 is disabled, but we nevertheless store the number of seconds in case it
445 may become useful later."""
446 return self._iasSmoothingLength
447
448 @property
449 def realIASSmoothingLength(self):
450 """Get the real smoothing length of IAS."""
451 return max(self._iasSmoothingLength, 1)
452
453 @iasSmoothingLength.setter
454 def iasSmoothingLength(self, iasSmoothingLength):
455 """Set the number of samples over which the IAS is averaged for the
456 smoothed IAS calculation."""
457 if iasSmoothingLength!=self._iasSmoothingLength:
458 self._iasSmoothingLength = iasSmoothingLength
459 self._modified = True
460
461 @property
462 def vsSmoothingLength(self):
463 """Get the number of samples over which the VS is averaged for the
464 smoothed VS calculation. It may be negative, in which case smoothing
465 is disabled, but we nevertheless store the number of seconds in case it
466 may become useful later."""
467 return self._vsSmoothingLength
468
469 @property
470 def realVSSmoothingLength(self):
471 """Get the real smoothing length of VS."""
472 return max(self._vsSmoothingLength, 1)
473
474 @vsSmoothingLength.setter
475 def vsSmoothingLength(self, vsSmoothingLength):
476 """Set the number of samples over which the VS is averaged for the
477 smoothed VS calculation."""
478 if vsSmoothingLength!=self._vsSmoothingLength:
479 self._vsSmoothingLength = vsSmoothingLength
480 self._modified = True
481
482 @property
483 def useSimBrief(self):
484 """Check if SimBrief should be used."""
485 return self._useSimBrief
486
487 @useSimBrief.setter
488 def useSimBrief(self, useSimBrief):
489 """Check if SimBrief should be used."""
490 if self._useSimBrief != useSimBrief:
491 self._useSimBrief = useSimBrief
492 self._modified = True
493
494 @property
495 def useInternalBrowserForSimBrief(self):
496 """Get if we should use the internal browser to handle SimBrief."""
497 return self._useInternalBrowserForSimBrief
498
499 @useInternalBrowserForSimBrief.setter
500 def useInternalBrowserForSimBrief(self, useInternalBrowserForSimBrief):
501 """Set if we should use the internal browser to handle SimBrief."""
502 if useInternalBrowserForSimBrief!=self._useInternalBrowserForSimBrief:
503 self._useInternalBrowserForSimBrief = useInternalBrowserForSimBrief
504 self._modified = True
505
506 @property
507 def simBriefUserName(self):
508 """Get the SimBrief user name last used"""
509 return self._simBriefUserName
510
511 @simBriefUserName.setter
512 def simBriefUserName(self, simBriefUserName):
513 """Set the SimBrief user name to be used next."""
514 if self._simBriefUserName != simBriefUserName:
515 self._simBriefUserName = simBriefUserName
516 self._modified = True
517
518 @property
519 def simBriefPassword(self):
520 """Get the SimBrief password last used"""
521 return self._simBriefPassword
522
523 @simBriefPassword.setter
524 def simBriefPassword(self, simBriefPassword):
525 """Set the SimBrief password to be used next."""
526 if self._simBriefPassword != simBriefPassword:
527 self._simBriefPassword = simBriefPassword
528 self._modified = True
529
530 @property
531 def rememberSimBriefPassword(self):
532 """Get if we should remember the SimBrief password."""
533 return self._rememberSimBriefPassword
534
535 @rememberSimBriefPassword.setter
536 def rememberSimBriefPassword(self, rememberSimBriefPassword):
537 """Set if we should remember the SimBrief password."""
538 if rememberSimBriefPassword!=self._rememberSimBriefPassword:
539 self._rememberSimBriefPassword = rememberSimBriefPassword
540 self._modified = True
541
542 @property
543 def pirepDirectory(self):
544 """Get the directory offered by default when saving a PIREP."""
545 return self._pirepDirectory
546
547 @pirepDirectory.setter
548 def pirepDirectory(self, pirepDirectory):
549 """Get the directory offered by default when saving a PIREP."""
550 if pirepDirectory!=self._pirepDirectory and \
551 (pirepDirectory!="" or self._pirepDirectory is not None):
552 self._pirepDirectory = None if pirepDirectory=="" \
553 else pirepDirectory
554 if self._pirepDirectory is None:
555 self._pirepAutoSave = False
556 self._modified = True
557
558 @property
559 def pirepAutoSave(self):
560 """Get whether the PIREP should be saved automatically when it becomes
561 saveable."""
562 return self._pirepAutoSave
563
564 @pirepAutoSave.setter
565 def pirepAutoSave(self, pirepAutoSave):
566 """Set whether the PIREP should be saved automatically when it becomes
567 saveable."""
568 pirepAutoSave = pirepAutoSave and self._pirepDirectory is not None
569 if pirepAutoSave!=self._pirepAutoSave:
570 self._pirepAutoSave = pirepAutoSave
571 self._modified = True
572
573 @property
574 def defaultMSFS(self):
575 """Get if the default simulator type is MS FS."""
576 return self._defaultMSFS
577
578 @defaultMSFS.setter
579 def defaultMSFS(self, defaultMSFS):
580 """Set if the default simulator type is MS FS."""
581 if defaultMSFS!=self._defaultMSFS:
582 self._defaultMSFS = defaultMSFS
583 self._modified = True
584
585 def getMessageTypeLevel(self, messageType):
586 """Get the level for the given message type."""
587 return self._messageTypeLevels[messageType] \
588 if messageType in self._messageTypeLevels \
589 else const.MESSAGELEVEL_NONE
590
591 def isMessageTypeFS(self, messageType):
592 """Determine if the given message type is displayed in the
593 simulator."""
594 level = self.getMessageTypeLevel(messageType)
595 return level==const.MESSAGELEVEL_FS or \
596 level==const.MESSAGELEVEL_BOTH
597
598 def setMessageTypeLevel(self, messageType, level):
599 """Set the level of the given message type."""
600 if messageType not in self._messageTypeLevels or \
601 self._messageTypeLevels[messageType]!=level:
602 self._messageTypeLevels[messageType] = level
603 self._modified = True
604
605 @property
606 def enableSounds(self):
607 """Get whether background sounds are enabled."""
608 return self._enableSounds
609
610 @enableSounds.setter
611 def enableSounds(self, enableSounds):
612 """Set whether background sounds are enabled."""
613 if enableSounds!=self._enableSounds:
614 self._enableSounds = enableSounds
615 self._modified = True
616
617 @property
618 def pilotControlsSounds(self):
619 """Get whether the pilot controls the background sounds."""
620 return self._pilotControlsSounds
621
622 @pilotControlsSounds.setter
623 def pilotControlsSounds(self, pilotControlsSounds):
624 """Set whether the pilot controls the background sounds."""
625 if pilotControlsSounds!=self._pilotControlsSounds:
626 self._pilotControlsSounds = pilotControlsSounds
627 self._modified = True
628
629 @property
630 def pilotHotkey(self):
631 """Get the pilot's hotkey."""
632 return self._pilotHotkey
633
634 @pilotHotkey.setter
635 def pilotHotkey(self, pilotHotkey):
636 """Set the pilot's hotkey."""
637 if pilotHotkey!=self._pilotHotkey:
638 self._pilotHotkey = pilotHotkey
639 self._modified = True
640
641 @property
642 def taxiSoundOnPushback(self):
643 """Get whether the taxi sound should be played as soon as pushback starts."""
644 return self._taxiSoundOnPushback
645
646 @taxiSoundOnPushback.setter
647 def taxiSoundOnPushback(self, taxiSoundOnPushback):
648 """Set whether the taxi sound should be played as soon as pushback starts."""
649 if taxiSoundOnPushback!=self._taxiSoundOnPushback:
650 self._taxiSoundOnPushback = taxiSoundOnPushback
651 self._modified = True
652
653 @property
654 def enableApproachCallouts(self):
655 """Get whether the approach callouts should be played."""
656 return self._enableApproachCallouts
657
658 @enableApproachCallouts.setter
659 def enableApproachCallouts(self, enableApproachCallouts):
660 """Set whether the approach callouts should be played."""
661 if enableApproachCallouts!=self._enableApproachCallouts:
662 self._enableApproachCallouts = enableApproachCallouts
663 self._modified = True
664
665 @property
666 def speedbrakeAtTD(self):
667 """Get whether the speedbrake sounds should be played at touchdown."""
668 return self._speedbrakeAtTD
669
670 @speedbrakeAtTD.setter
671 def speedbrakeAtTD(self, speedbrakeAtTD):
672 """Set whether the speedbrake sounds should be played at touchdown."""
673 if speedbrakeAtTD!=self._speedbrakeAtTD:
674 self._speedbrakeAtTD = speedbrakeAtTD
675 self._modified = True
676
677 @property
678 def soundSet(self):
679 """Get the number of the sound set to use."""
680 return self._soundSet
681
682 @soundSet.setter
683 def soundSet(self, soundSet):
684 """Set the number of the sound set."""
685 if soundSet!=self._soundSet:
686 self._soundSet = soundSet
687 self._modified = True
688
689 @property
690 def enableChecklists(self):
691 """Get whether aircraft-specific checklists should be played."""
692 return self._enableChecklists
693
694 @enableChecklists.setter
695 def enableChecklists(self, enableChecklists):
696 """Get whether aircraft-specific checklists should be played."""
697 if enableChecklists!=self._enableChecklists:
698 self._enableChecklists = enableChecklists
699 self._modified = True
700
701 @property
702 def checklistHotkey(self):
703 """Get the checklist hotkey."""
704 return self._checklistHotkey
705
706 @checklistHotkey.setter
707 def checklistHotkey(self, checklistHotkey):
708 """Set the checklist hotkey."""
709 if checklistHotkey!=self._checklistHotkey:
710 self._checklistHotkey = checklistHotkey
711 self._modified = True
712
713 @property
714 def autoUpdate(self):
715 """Get if an automatic update is needed."""
716 return self._autoUpdate
717
718 @autoUpdate.setter
719 def autoUpdate(self, autoUpdate):
720 """Set if an automatic update is needed."""
721 if autoUpdate!=self._autoUpdate:
722 self._autoUpdate = autoUpdate
723 self._modified = True
724
725 @property
726 def updateURL(self):
727 """Get the update URL."""
728 return self._updateURL
729
730 @updateURL.setter
731 def updateURL(self, updateURL):
732 """Set the update URL."""
733 if updateURL!=self._updateURL:
734 self._updateURL = updateURL
735 self._modified = True
736
737 @property
738 def xplaneRemote(self):
739 """Indicate if X-Plane should be accessed remotely."""
740 return self._xplaneRemote
741
742 @xplaneRemote.setter
743 def xplaneRemote(self, xplaneRemote):
744 """Set if X-Plane should be accessed remotely."""
745 if xplaneRemote!=self._xplaneRemote:
746 self._xplaneRemote = xplaneRemote
747 self._modified = True
748
749 @property
750 def xplaneAddress(self):
751 """Get the address of the machine running X-Plane"""
752 return self._xplaneAddress
753
754 @xplaneAddress.setter
755 def xplaneAddress(self, xplaneAddress):
756 """Set the address of the machine running X-Plane."""
757 if xplaneAddress!=self._xplaneAddress:
758 self._xplaneAddress = xplaneAddress
759 self._modified = True
760
761 def getChecklist(self, aircraftType):
762 """Get the checklist for the given aircraft type."""
763 return self._checklists[aircraftType]
764
765 def setChecklist(self, aircraftType, checklist):
766 """Set the checklist for the given aircraft type."""
767 if checklist!=self._checklists[aircraftType]:
768 self._checklists[aircraftType] = checklist.clone()
769 self._modified = True
770
771 def getApproachCallouts(self, aircraftType):
772 """Get the approach callouts for the given aircraft type."""
773 return self._approachCallouts[aircraftType]
774
775 def setApproachCallouts(self, aircraftType, approachCallouts):
776 """Set the approach callouts for the given aircraft type."""
777 if not approachCallouts==self._approachCallouts[aircraftType]:
778 self._approachCallouts[aircraftType] = approachCallouts.clone()
779 self._modified = True
780
781 def load(self):
782 """Load the configuration from its default location."""
783 try:
784 config = configparser.RawConfigParser()
785 config.read(configPath)
786 except:
787 traceback.print_exc()
788 return
789
790 self._pilotID = self._get(config, "login", "id", "")
791 self._password = self._get(config, "login", "password", "")
792 self._rememberPassword = self._getBoolean(config, "login",
793 "rememberPassword", False)
794
795 self._clearBrowserCacheOnStart = \
796 self._getBoolean(config, "general",
797 "clearBrowseCacheOnStart", False)
798
799 self._language = self._get(config, "general", "language", "")
800
801 self._mainWindowResizable = self._getBoolean(config, "general",
802 "mainWindowResizable",
803 False)
804 self._hideMinimizedWindow = self._getBoolean(config, "general",
805 "hideMinimizedWindow",
806 True)
807 self._quitOnClose = self._getBoolean(config, "general",
808 "quitOnClose", False)
809
810 self._onlineGateSystem = self._getBoolean(config, "general",
811 "onlineGateSystem",
812 not secondaryInstallation)
813 self._onlineACARS = self._getBoolean(config, "general",
814 "onlineACARS",
815 not secondaryInstallation)
816 self._flareTimeFromFS = self._getBoolean(config, "general",
817 "flareTimeFromFS",
818 False)
819 self._syncFSTime = self._getBoolean(config, "general",
820 "syncFSTime",
821 False)
822 self._usingFS2Crew = self._getBoolean(config, "general",
823 "usingFS2Crew",
824 False)
825 self._iasSmoothingLength = int(self._get(config, "general",
826 "iasSmoothingLength",
827 -2))
828 self._vsSmoothingLength = int(self._get(config, "general",
829 "vsSmoothingLength",
830 -2))
831
832 self._useSimBrief = self._getBoolean(config, "simbrief",
833 "use", False)
834 self._useInternalBrowserForSimBrief = self._getBoolean(config, "simbrief",
835 "useInternalBrowser",
836 False)
837 self._simBriefUserName = self._get(config, "simbrief",
838 "username", "")
839 self._simBriefPassword = self._get(config, "simbrief",
840 "password", "")
841 self._rememberSimBriefPassword = self._getBoolean(config, "simbrief",
842 "rememberPassword",
843 False)
844
845 self._pirepDirectory = self._get(config, "general",
846 "pirepDirectory", None)
847
848 self._pirepAutoSave = self._getBoolean(config, "general",
849 "pirepAutoSave", False)
850 if self._pirepDirectory is None:
851 self._pirepAutoSave = False
852
853 self._messageTypeLevels = {}
854 for messageType in const.messageTypes:
855 self._messageTypeLevels[messageType] = \
856 self._getMessageTypeLevel(config, messageType)
857
858 self._enableSounds = self._getBoolean(config, "sounds", "enable",
859 not secondaryInstallation)
860 self._pilotControlsSounds = self._getBoolean(config, "sounds",
861 "pilotControls", True)
862 self._pilotHotkey.set(self._get(config, "sounds",
863 "pilotHotkey", "C0"))
864
865 self._taxiSoundOnPushback = \
866 self._getBoolean(config, "sounds", "taxiSoundOnPushback", False)
867 self._enableApproachCallouts = \
868 self._getBoolean(config, "sounds", "enableApproachCallouts", False)
869 self._speedbrakeAtTD = self._getBoolean(config, "sounds",
870 "speedbrakeAtTD", True)
871 self._soundSet = self._getInteger(config, "sounds",
872 "soundSet", 0)
873
874 self._enableChecklists = self._getBoolean(config, "sounds",
875 "enableChecklists", False)
876 self._checklistHotkey.set(self._get(config, "sounds",
877 "checklistHotkey", "CS0"))
878
879 self._autoUpdate = self._getBoolean(config, "update", "auto", True)
880 self._updateURL = self._get(config, "update", "url",
881 Config.DEFAULT_UPDATE_URL)
882
883 for aircraftType in const.aircraftTypes:
884 self._checklists[aircraftType] = \
885 Checklist.fromConfig(config, aircraftType)
886 self._approachCallouts[aircraftType] = \
887 ApproachCallouts.fromConfig(config, aircraftType)
888
889 self._defaultMSFS = self._getBoolean(config, "general",
890 "defaultMSFS", os.name=="nt")
891
892 self._xplaneRemote = self._getBoolean(config, "general",
893 "xplaneRemote", False)
894 self._xplaneAddress = self._get(config, "general",
895 "xplaneAddress", "")
896
897 self._modified = False
898
899 def save(self):
900 """Save the configuration file if it has been modified."""
901 if not self._modified:
902 return
903
904 config = configparser.RawConfigParser()
905
906 config.add_section("login")
907 config.set("login", "id", self._pilotID)
908 config.set("login", "password", self._password)
909 config.set("login", "rememberPassword",
910 "yes" if self._rememberPassword else "no")
911
912 config.add_section("general")
913 config.set("general", "clearBrowseCacheOnStart",
914 "yes" if self._clearBrowserCacheOnStart else "no")
915 if self._language:
916 config.set("general", "language", self._language)
917 config.set("general", "mainWindowResizable",
918 "yes" if self._mainWindowResizable else "no")
919 config.set("general", "hideMinimizedWindow",
920 "yes" if self._hideMinimizedWindow else "no")
921 config.set("general", "quitOnClose",
922 "yes" if self._quitOnClose else "no")
923 config.set("general", "onlineGateSystem",
924 "yes" if self._onlineGateSystem else "no")
925 config.set("general", "onlineACARS",
926 "yes" if self._onlineACARS else "no")
927 config.set("general", "flareTimeFromFS",
928 "yes" if self._flareTimeFromFS else "no")
929 config.set("general", "syncFSTime",
930 "yes" if self._syncFSTime else "no")
931 config.set("general", "usingFS2Crew",
932 "yes" if self._usingFS2Crew else "no")
933 config.set("general", "iasSmoothingLength",
934 str(self._iasSmoothingLength))
935 config.set("general", "vsSmoothingLength",
936 str(self._vsSmoothingLength))
937
938 config.add_section("simbrief")
939 config.set("simbrief", "use",
940 "yes" if self._useSimBrief else "no")
941 config.set("simbrief", "useInternalBrowser",
942 "yes" if self._useInternalBrowserForSimBrief else "no")
943 config.set("simbrief", "username", self._simBriefUserName)
944 config.set("simbrief", "password", self._simBriefPassword)
945 config.set("simbrief", "rememberPassword",
946 "yes" if self._rememberSimBriefPassword else "no")
947
948 if self._pirepDirectory is not None:
949 config.set("general", "pirepDirectory", self._pirepDirectory)
950 config.set("general", "pirepAutoSave",
951 "yes" if self._pirepAutoSave else "no")
952
953 config.set("general", "defaultMSFS",
954 "yes" if self._defaultMSFS else "no")
955
956 config.set("general", "xplaneRemote",
957 "yes" if self._xplaneRemote else "no")
958 config.set("general", "xplaneAddress", self._xplaneAddress)
959
960 config.add_section(Config._messageTypesSection)
961 for messageType in const.messageTypes:
962 if messageType in self._messageTypeLevels:
963 option = self._getMessageTypeLevelOptionName(messageType)
964 level = self._messageTypeLevels[messageType]
965 config.set(Config._messageTypesSection, option,
966 const.messageLevel2string(level))
967
968 config.add_section("sounds")
969 config.set("sounds", "enable",
970 "yes" if self._enableSounds else "no")
971 config.set("sounds", "pilotControls",
972 "yes" if self._pilotControlsSounds else "no")
973 config.set("sounds", "pilotHotkey", str(self._pilotHotkey))
974 config.set("sounds", "taxiSoundOnPushback",
975 "yes" if self._taxiSoundOnPushback else "no")
976 config.set("sounds", "enableApproachCallouts",
977 "yes" if self._enableApproachCallouts else "no")
978 config.set("sounds", "speedbrakeAtTD",
979 "yes" if self._speedbrakeAtTD else "no")
980 config.set("sounds", "soundSet", str(self._soundSet))
981
982 config.set("sounds", "enableChecklists",
983 "yes" if self._enableChecklists else "no")
984 config.set("sounds", "checklistHotkey",
985 str(self._checklistHotkey))
986
987 config.add_section("update")
988 config.set("update", "auto",
989 "yes" if self._autoUpdate else "no")
990 config.set("update", "url", self._updateURL)
991
992 config.add_section(Checklist.SECTION)
993 config.add_section(ApproachCallouts.SECTION)
994 for aircraftType in const.aircraftTypes:
995 self._checklists[aircraftType].toConfig(config, aircraftType)
996 self._approachCallouts[aircraftType].toConfig(config, aircraftType)
997
998 try:
999 fd = os.open(configPath, os.O_CREAT|os.O_TRUNC|os.O_WRONLY,
1000 0o600)
1001 with os.fdopen(fd, "wt") as f:
1002 config.write(f)
1003 self._modified = False
1004
1005 print("Configuration saved:")
1006 self.log()
1007
1008 except Exception as e:
1009 print("Failed to update config: " + \
1010 utf2unicode(str(e)), file=sys.stderr)
1011
1012 def _getBoolean(self, config, section, option, default):
1013 """Get the given option as a boolean, if found in the given config,
1014 otherwise the default."""
1015 return config.getboolean(section, option) \
1016 if config.has_option(section, option) \
1017 else default
1018
1019 def _getInteger(self, config, section, option, default):
1020 """Get the given option as an integer, if found in the given config,
1021 otherwise the default."""
1022 return config.getint(section, option) \
1023 if config.has_option(section, option) \
1024 else default
1025
1026 def _get(self, config, section, option, default):
1027 """Get the given option as a string, if found in the given config,
1028 otherwise the default."""
1029 return config.get(section, option) \
1030 if config.has_option(section, option) \
1031 else default
1032
1033 def _getMessageTypeLevel(self, config, messageType):
1034 """Get the message type level for the given message type."""
1035 option = self._getMessageTypeLevelOptionName(messageType)
1036 if config.has_option(Config._messageTypesSection, option):
1037 value = config.get(Config._messageTypesSection, option)
1038 return const.string2messageLevel(value)
1039 elif secondaryInstallation:
1040 return const.MESSAGELEVEL_NONE
1041 elif messageType in [const.MESSAGETYPE_LOGGER_ERROR,
1042 const.MESSAGETYPE_FAULT,
1043 const.MESSAGETYPE_NOGO,
1044 const.MESSAGETYPE_GATE_SYSTEM,
1045 const.MESSAGETYPE_HELP]:
1046 return const.MESSAGELEVEL_BOTH
1047 else:
1048 return const.MESSAGELEVEL_FS
1049
1050 def _getMessageTypeLevelOptionName(self, messageType):
1051 """Get the option name for the given message type level."""
1052 return const.messageType2string(messageType)
1053
1054 def setupLocale(self):
1055 """Setup the locale based on the language set.
1056
1057 Return True if a specific language was set, False otherwise."""
1058 import locale
1059 if self._language:
1060 print("Setting up locale for", self._language)
1061 os.environ["LANGUAGE"] = self._language
1062 langAndEncoding = self._language + "." + locale.getpreferredencoding()
1063 os.environ["LANG"] = langAndEncoding
1064 os.environ["LC_MESSAGES"] = langAndEncoding
1065 os.environ["LC_COLLATE"] = langAndEncoding
1066 os.environ["LC_CTYPE"] = langAndEncoding
1067 os.environ["LC_MONETARY"] = langAndEncoding
1068 os.environ["LC_NUMERIC"] = langAndEncoding
1069 os.environ["LC_TIME"] = langAndEncoding
1070 return True
1071 else:
1072 return False
1073
1074 def getLanguage(self):
1075 """Get the language to be used."""
1076 import locale
1077 if self._language:
1078 if os.name=="nt":
1079 if self._language in _languageMap:
1080 locale.setlocale(locale.LC_ALL, _languageMap[self._language])
1081 else:
1082 locale.setlocale(locale.LC_ALL, "")
1083 else:
1084 locale.setlocale(locale.LC_ALL, (self._language,
1085 locale.getpreferredencoding()))
1086 return self._language
1087 else:
1088 locale.setlocale(locale.LC_ALL, "")
1089 return locale.getdefaultlocale()[0]
1090
1091 def log(self):
1092 """Log the configuration by printing the values"""
1093 print(" pilot ID:", self._pilotID)
1094 print(" rememberPassword:", self._rememberPassword)
1095
1096 print(" clearBrowseCacheOnStart:", self._clearBrowserCacheOnStart)
1097 print(" language:", self._language)
1098
1099 print(" mainWindowResizable:", self._mainWindowResizable)
1100 print(" hideMinimizedWindow:", self._hideMinimizedWindow)
1101 print(" quitOnClose:", self._quitOnClose)
1102
1103 print(" onlineGateSystem:", self._onlineGateSystem)
1104 print(" onlineACARS:", self._onlineACARS)
1105
1106 print(" flareTimeFromFS:", self._flareTimeFromFS)
1107 print(" syncFSTime:", self._syncFSTime)
1108 print(" usingFS2Crew:", self._usingFS2Crew)
1109
1110 print(" iasSmoothingLength:", self._iasSmoothingLength)
1111 print(" vsSmoothingLength:", self._vsSmoothingLength)
1112
1113 print(" useSimBrief:", self._useSimBrief)
1114 print(" useInternalBrowserForSimBrief:", self._useInternalBrowserForSimBrief)
1115 print(" simBriefUserName:", self._simBriefUserName)
1116 print(" rememberSimBriefPassword:", self._rememberSimBriefPassword)
1117
1118 print(" pirepDirectory:", self._pirepDirectory)
1119 print(" pirepAutoSave:", self._pirepAutoSave)
1120
1121 print(" defaultMSFS:", self._defaultMSFS)
1122 print(" xplaneRemote:", self._xplaneRemote)
1123 print(" xplaneAddress:", self._xplaneAddress)
1124
1125 print(" enableSounds:", self._enableSounds)
1126
1127 print(" pilotControlsSounds:", self._pilotControlsSounds)
1128 print(" pilotHotkey:", str(self._pilotHotkey))
1129
1130 print(" taxiSoundOnPushback:", self._taxiSoundOnPushback)
1131
1132 print(" enableApproachCallouts:", self._enableApproachCallouts)
1133 print(" speedbrakeAtTD:", self._speedbrakeAtTD)
1134 print(" soundSet:", self._soundSet)
1135
1136 print(" enableChecklists:", self._enableChecklists)
1137 print(" checklistHotkey:", str(self._checklistHotkey))
1138
1139 print(" autoUpdate:", self._autoUpdate)
1140 print(" updateURL:", self._updateURL)
1141
1142 print(" messageTypeLevels:")
1143 for (type, level) in self._messageTypeLevels.items():
1144 print(" %s: %s" % (const.messageType2string(type),
1145 const.messageLevel2string(level)))
1146
1147 print(" checklists:")
1148 for (type, checklist) in self._checklists.items():
1149 print(" %s:" % (const.icaoCodes[type],))
1150 for path in checklist:
1151 print(" " + path)
1152
1153 print(" approachCallouts:")
1154 for (type, approachCallouts) in self._approachCallouts.items():
1155 print(" %s:" % (const.icaoCodes[type],))
1156 for (altitude, path) in approachCallouts:
1157 print(" %d: %s" % (altitude, path))
1158
1159#-------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.