source: src/mlx/gui/common.py@ 851:b98050aeebf9

Last change on this file since 851:b98050aeebf9 was 851:b98050aeebf9, checked in by István Váradi <ivaradi@…>, 7 years ago

The time editor can return a timestamp.

File size: 21.9 KB
Line 
1
2from mlx.common import *
3
4import mlx.const as _const
5from mlx.i18n import xstr
6
7from mlx.util import secondaryInstallation
8
9import os
10import time
11import calendar
12
13#-----------------------------------------------------------------------------
14
15## @package mlx.gui.common
16#
17# Common definitions and utilities for the GUI
18#
19# The main purpose of this module is to provide common definitions for things
20# that are named differently in Gtk+ 2 and 3. This way the other parts of the
21# GUI have to check the version in use very rarely. The variable \c pygobject
22# tells which version is being used. If it is \c True, Gtk+ 3 is used via the
23# PyGObject interface. Otherwise Gtk+ 2 is used, which is the default on
24# Windows or when the \c FORCE_PYGTK environment variable is set.
25#
26# Besides this there are some common utility classes and functions.
27
28#-----------------------------------------------------------------------------
29
30appIndicator = False
31
32if not pygobject:
33 print "Using PyGTK"
34 pygobject = False
35 import pygtk
36 pygtk.require("2.0")
37 import gtk.gdk as gdk
38 import gtk
39 import pango
40
41 try:
42 import appindicator
43 appIndicator = True
44 except Exception, e:
45 pass
46
47 MESSAGETYPE_ERROR = gtk.MESSAGE_ERROR
48 MESSAGETYPE_QUESTION = gtk.MESSAGE_QUESTION
49 MESSAGETYPE_INFO = gtk.MESSAGE_INFO
50
51 RESPONSETYPE_NONE = gtk.RESPONSE_NONE
52 RESPONSETYPE_OK = gtk.RESPONSE_OK
53 RESPONSETYPE_YES = gtk.RESPONSE_YES
54 RESPONSETYPE_NO = gtk.RESPONSE_NO
55 RESPONSETYPE_ACCEPT = gtk.RESPONSE_ACCEPT
56 RESPONSETYPE_REJECT = gtk.RESPONSE_REJECT
57 RESPONSETYPE_CANCEL = gtk.RESPONSE_CANCEL
58
59 ACCEL_VISIBLE = gtk.ACCEL_VISIBLE
60 CONTROL_MASK = gdk.CONTROL_MASK
61 DIALOG_MODAL = gtk.DIALOG_MODAL
62 WRAP_WORD = gtk.WRAP_WORD
63
64 JUSTIFY_CENTER = gtk.JUSTIFY_CENTER
65 JUSTIFY_LEFT = gtk.JUSTIFY_LEFT
66
67 CONTROL_MASK = gdk.CONTROL_MASK
68 SHIFT_MASK = gdk.SHIFT_MASK
69 BUTTON1_MASK = gdk.BUTTON1_MASK
70
71 SCROLL_UP = gdk.SCROLL_UP
72 SCROLL_DOWN = gdk.SCROLL_DOWN
73
74 SPIN_USER_DEFINED = gtk.SPIN_USER_DEFINED
75
76 FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
77 FILE_CHOOSER_ACTION_OPEN = gtk.FILE_CHOOSER_ACTION_OPEN
78 FILE_CHOOSER_ACTION_SAVE = gtk.FILE_CHOOSER_ACTION_SAVE
79
80 SELECTION_MULTIPLE = gtk.SELECTION_MULTIPLE
81
82 SHADOW_IN = gtk.SHADOW_IN
83 SHADOW_NONE = gtk.SHADOW_NONE
84
85 POLICY_AUTOMATIC = gtk.POLICY_AUTOMATIC
86 POLICY_NEVER = gtk.POLICY_NEVER
87 POLICY_ALWAYS = gtk.POLICY_ALWAYS
88
89 WEIGHT_NORMAL = pango.WEIGHT_NORMAL
90 WEIGHT_BOLD = pango.WEIGHT_BOLD
91
92 WINDOW_STATE_ICONIFIED = gdk.WINDOW_STATE_ICONIFIED
93 WINDOW_STATE_WITHDRAWN = gdk.WINDOW_STATE_WITHDRAWN
94
95 SORT_ASCENDING = gtk.SORT_ASCENDING
96 SORT_DESCENDING = gtk.SORT_DESCENDING
97
98 EVENT_BUTTON_PRESS = gdk.BUTTON_PRESS
99
100 TREE_VIEW_COLUMN_FIXED = gtk.TREE_VIEW_COLUMN_FIXED
101
102 FILL = gtk.FILL
103 EXPAND = gtk.EXPAND
104
105 UPDATE_IF_VALID = gtk.UPDATE_IF_VALID
106
107 SELECTION_MULTIPLE = gtk.SELECTION_MULTIPLE
108
109 pixbuf_new_from_file = gdk.pixbuf_new_from_file
110
111 def text2unicode(text):
112 """Convert the given text, returned by a Gtk widget, to Unicode."""
113 return unicode(text)
114
115 def text2str(text):
116 """Convert the given text, returned by xstr to a string."""
117 return str(text)
118
119else: # pygobject
120 from gi.repository import Gdk as gdk
121 from gi.repository import GdkPixbuf as gdkPixbuf
122 from gi.repository import Gtk as gtk
123 from gi.repository import AppIndicator3 as appindicator
124 from gi.repository import Pango as pango
125
126 appIndicator = True
127
128
129 MESSAGETYPE_ERROR = gtk.MessageType.ERROR
130 MESSAGETYPE_QUESTION = gtk.MessageType.QUESTION
131 MESSAGETYPE_INFO = gtk.MessageType.INFO
132 RESPONSETYPE_NONE = gtk.ResponseType.NONE
133 RESPONSETYPE_OK = gtk.ResponseType.OK
134 RESPONSETYPE_YES = gtk.ResponseType.YES
135 RESPONSETYPE_NO = gtk.ResponseType.NO
136 RESPONSETYPE_ACCEPT = gtk.ResponseType.ACCEPT
137 RESPONSETYPE_REJECT = gtk.ResponseType.REJECT
138 RESPONSETYPE_CANCEL = gtk.ResponseType.CANCEL
139 ACCEL_VISIBLE = gtk.AccelFlags.VISIBLE
140 CONTROL_MASK = gdk.ModifierType.CONTROL_MASK
141 DIALOG_MODAL = gtk.DialogFlags.MODAL
142 WRAP_WORD = gtk.WrapMode.WORD
143 JUSTIFY_CENTER = gtk.Justification.CENTER
144 JUSTIFY_LEFT = gtk.Justification.LEFT
145
146 CONTROL_MASK = gdk.ModifierType.CONTROL_MASK
147 SHIFT_MASK = gdk.ModifierType.SHIFT_MASK
148 BUTTON1_MASK = gdk.ModifierType.BUTTON1_MASK
149
150 SCROLL_UP = gdk.ScrollDirection.UP
151 SCROLL_DOWN = gdk.ScrollDirection.DOWN
152
153 SPIN_USER_DEFINED = gtk.SpinType.USER_DEFINED
154
155 FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FileChooserAction.SELECT_FOLDER
156 FILE_CHOOSER_ACTION_OPEN = gtk.FileChooserAction.OPEN
157 FILE_CHOOSER_ACTION_SAVE = gtk.FileChooserAction.SAVE
158
159 SELECTION_MULTIPLE = gtk.SelectionMode.MULTIPLE
160
161 SHADOW_IN = gtk.ShadowType.IN
162 SHADOW_NONE = gtk.ShadowType.NONE
163
164 POLICY_AUTOMATIC = gtk.PolicyType.AUTOMATIC
165 POLICY_NEVER = gtk.PolicyType.NEVER
166 POLICY_ALWAYS = gtk.PolicyType.ALWAYS
167
168 WEIGHT_NORMAL = pango.Weight.NORMAL
169 WEIGHT_BOLD = pango.Weight.BOLD
170
171 WINDOW_STATE_ICONIFIED = gdk.WindowState.ICONIFIED
172 WINDOW_STATE_WITHDRAWN = gdk.WindowState.WITHDRAWN
173
174 SORT_ASCENDING = gtk.SortType.ASCENDING
175 SORT_DESCENDING = gtk.SortType.DESCENDING
176
177 EVENT_BUTTON_PRESS = gdk.EventType.BUTTON_PRESS
178
179 TREE_VIEW_COLUMN_FIXED = gtk.TreeViewColumnSizing.FIXED
180
181 FILL = gtk.AttachOptions.FILL
182 EXPAND = gtk.AttachOptions.EXPAND
183
184 UPDATE_IF_VALID = gtk.SpinButtonUpdatePolicy.IF_VALID
185
186 SELECTION_MULTIPLE = gtk.SelectionMode.MULTIPLE
187
188 pixbuf_new_from_file = gdkPixbuf.Pixbuf.new_from_file
189
190 import codecs
191 _utf8Decoder = codecs.getdecoder("utf-8")
192
193 def text2unicode(str):
194 """Convert the given text, returned by a Gtk widget, to Unicode."""
195 return _utf8Decoder(str)[0]
196
197 def text2str(text):
198 """Convert the given text, returned by xstr to a string."""
199 return _utf8Decoder(text)[0]
200
201import cairo
202
203#------------------------------------------------------------------------------
204
205class FlightStatusHandler(object):
206 """Base class for objects that handle the flight status in some way."""
207 def __init__(self):
208 self._stage = None
209 self._rating = 100
210 self._noGoReason = None
211
212 def resetFlightStatus(self):
213 """Reset the flight status."""
214 self._stage = None
215 self._rating = 100
216 self._noGoReason = None
217 self._updateFlightStatus()
218
219 def setStage(self, stage):
220 """Set the stage of the flight."""
221 if stage!=self._stage:
222 self._stage = stage
223 self._updateFlightStatus()
224
225 def setRating(self, rating):
226 """Set the rating to the given value."""
227 if rating!=self._rating:
228 self._rating = rating
229 if self._noGoReason is None:
230 self._updateFlightStatus()
231
232 def setNoGo(self, reason):
233 """Set a No-Go condition with the given reason."""
234 if self._noGoReason is None:
235 self._noGoReason = reason
236 self._updateFlightStatus()
237
238#------------------------------------------------------------------------------
239
240class IntegerEntry(gtk.Entry):
241 """An entry that allows only either an empty value, or an integer."""
242 def __init__(self, defaultValue = None):
243 """Construct the entry."""
244 gtk.Entry.__init__(self)
245
246 self.set_alignment(1.0)
247
248 self._defaultValue = defaultValue
249 self._currentInteger = defaultValue
250 self._selfSetting = False
251 self._set_text()
252
253 self.connect("changed", self._handle_changed)
254
255 def get_int(self):
256 """Get the integer."""
257 return self._currentInteger
258
259 def reset(self):
260 """Reset the integer."""
261 self.set_int(None)
262
263 def set_int(self, value):
264 """Set the integer."""
265 if value!=self._currentInteger:
266 self._currentInteger = value
267 self.emit("integer-changed", self._currentInteger)
268 self._set_text()
269
270 def _handle_changed(self, widget):
271 """Handle the changed signal."""
272 if self._selfSetting:
273 return
274 text = self.get_text()
275 if text=="":
276 self.set_int(self._defaultValue)
277 else:
278 try:
279 self.set_int(int(text))
280 except:
281 self._set_text()
282
283 def _set_text(self):
284 """Set the text value from the current integer."""
285 self._selfSetting = True
286 self.set_text("" if self._currentInteger is None
287 else str(self._currentInteger))
288 self._selfSetting = False
289
290#------------------------------------------------------------------------------
291
292class TimeEntry(gtk.Entry):
293 """Widget to display and edit a time value in HH:MM format."""
294 def __init__(self):
295 """Construct the entry"""
296 super(TimeEntry, self).__init__(max = 5)
297
298 self.connect("insert-text", self._insertText)
299 self.connect("delete-text", self._deleteText)
300 self.connect("focus-out-event", self._focusOutEvent)
301
302 @property
303 def hour(self):
304 """Get the hour from the current text"""
305 text = self.get_text()
306 if not text or text==":":
307 return 0
308
309 words = text.split(":")
310 if len(words)==1:
311 return 0
312 elif len(words)>=2:
313 return 0 if len(words[0])==0 else int(words[0])
314 else:
315 return 0
316
317 @property
318 def minute(self):
319 """Get the hour from the current text"""
320 text = self.get_text()
321 if not text or text==":":
322 return 0
323
324 words = text.split(":")
325 if len(words)==1:
326 return 0 if len(words[0])==0 else int(words[0])
327 elif len(words)>=2:
328 return 0 if len(words[1])==0 else int(words[1])
329 else:
330 return 0
331
332 @property
333 def minutes(self):
334 """Get the time in minutes, i.e. hour*60+minute."""
335 return self.hour * 60 + self.minute
336
337 def setTimestamp(self, timestamp):
338 """Set the hour and minute from the given timestamp in UTC."""
339 tm = time.gmtime(timestamp)
340 self.set_text("%02d:%02d" % (tm.tm_hour, tm.tm_min))
341
342 def getTimestampFrom(self, timestamp):
343 """Get the timestamp by replacing the hour and minute from the given
344 timestamp with what is set in this widget."""
345 tm = time.gmtime(timestamp)
346 ts = calendar.timegm((tm.tm_year, tm.tm_mon, tm.tm_mday,
347 self.hour, self.minute, 0,
348 tm.tm_wday, tm.tm_yday, tm.tm_isdst))
349
350 if ts > (timestamp + (16*60*60)):
351 ts -= 24*60*60
352 elif (ts + 16*60*60) < timestamp:
353 ts += 24*60*60
354
355 return ts
356
357 def _focusOutEvent(self, widget, event):
358 """Reformat the text to match pattern HH:MM"""
359 text = "%02d:%02d" % (self.hour, self.minute)
360 if text!=self.get_text():
361 self.set_text(text)
362
363 def _insertText(self, entry, text, length, position):
364 """Called when some text is inserted into the entry."""
365 text=text[:length]
366 currentText = self.get_text()
367 position = self.get_position()
368 newText = currentText[:position] + text + currentText[position:]
369 self._checkText(newText, "insert-text")
370
371 def _deleteText(self, entry, start, end):
372 """Called when some text is erased from the entry."""
373 currentText = self.get_text()
374 newText = currentText[:start] + currentText[end:]
375 self._checkText(newText, "delete-text")
376
377 def _checkText(self, newText, signal):
378 """Check the given text.
379
380 If it is not suitable, stop the emission of the signal to prevent the
381 change from appearing."""
382 if not newText or newText==":":
383 return
384
385 words = newText.split(":")
386 if (len(words)==1 and
387 len(words[0])<=2 and (len(words[0])==0 or
388 (words[0].isdigit() and int(words[0])<60))) or \
389 (len(words)==2 and
390 len(words[0])<=2 and (len(words[0])==0 or
391 (words[0].isdigit() and int(words[0])<24)) and
392 len(words[1])<=2 and (len(words[1])==0 or
393 (words[1].isdigit() and int(words[1])<60))):
394 pass
395 else:
396 gtk.gdk.display_get_default().beep()
397 self.stop_emission(signal)
398
399#------------------------------------------------------------------------------
400
401class CredentialsDialog(gtk.Dialog):
402 """A dialog window to ask for a user name and a password."""
403 def __init__(self, gui, userName, password,
404 titleLabel, cancelButtonLabel, okButtonLabel,
405 userNameLabel, userNameTooltip,
406 passwordLabel, passwordTooltip,
407 infoText = None,
408 rememberPassword = None,
409 rememberLabel = None, rememberTooltip = None):
410 """Construct the dialog."""
411 super(CredentialsDialog, self).__init__(WINDOW_TITLE_BASE + " - " +
412 titleLabel,
413 gui.mainWindow,
414 DIALOG_MODAL)
415 self.add_button(cancelButtonLabel, RESPONSETYPE_CANCEL)
416 self.add_button(okButtonLabel, RESPONSETYPE_OK)
417
418 contentArea = self.get_content_area()
419
420 contentAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
421 xscale = 0.0, yscale = 0.0)
422 contentAlignment.set_padding(padding_top = 4, padding_bottom = 16,
423 padding_left = 8, padding_right = 8)
424
425 contentArea.pack_start(contentAlignment, False, False, 0)
426
427 contentVBox = gtk.VBox()
428 contentAlignment.add(contentVBox)
429
430 if infoText is not None:
431 label = gtk.Label(infoText)
432 label.set_alignment(0.0, 0.0)
433
434 contentVBox.pack_start(label, False, False, 0)
435
436 tableAlignment = gtk.Alignment(xalign = 0.5, yalign = 0.5,
437 xscale = 0.0, yscale = 0.0)
438 tableAlignment.set_padding(padding_top = 24, padding_bottom = 0,
439 padding_left = 0, padding_right = 0)
440
441 table = gtk.Table(3, 2)
442 table.set_row_spacings(4)
443 table.set_col_spacings(16)
444 table.set_homogeneous(False)
445
446 tableAlignment.add(table)
447 contentVBox.pack_start(tableAlignment, True, True, 0)
448
449 label = gtk.Label(userNameLabel)
450 label.set_use_underline(True)
451 label.set_alignment(0.0, 0.5)
452 table.attach(label, 0, 1, 0, 1)
453
454 self._userName = gtk.Entry()
455 self._userName.set_width_chars(16)
456 # FIXME: enabled the OK button only when there is something in thr
457 # user name and password fields
458 #self._userName.connect("changed",
459 # lambda button: self._updateForwardButton())
460 self._userName.set_tooltip_text(userNameTooltip)
461 self._userName.set_text(userName)
462 table.attach(self._userName, 1, 2, 0, 1)
463 label.set_mnemonic_widget(self._userName)
464
465 label = gtk.Label(passwordLabel)
466 label.set_use_underline(True)
467 label.set_alignment(0.0, 0.5)
468 table.attach(label, 0, 1, 1, 2)
469
470 self._password = gtk.Entry()
471 self._password.set_visibility(False)
472 #self._password.connect("changed",
473 # lambda button: self._updateForwardButton())
474 self._password.set_tooltip_text(passwordTooltip)
475 self._password.set_text(password)
476 table.attach(self._password, 1, 2, 1, 2)
477 label.set_mnemonic_widget(self._password)
478
479 if rememberPassword is not None:
480 self._rememberButton = gtk.CheckButton(rememberLabel)
481 self._rememberButton.set_use_underline(True)
482 self._rememberButton.set_tooltip_text(rememberTooltip)
483 self._rememberButton.set_active(rememberPassword)
484 table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
485 else:
486 self._rememberButton = None
487
488 @property
489 def userName(self):
490 """Get the user name entered."""
491 return self._userName.get_text()
492
493 @property
494 def password(self):
495 """Get the password entered."""
496 return self._password.get_text()
497
498 @property
499 def rememberPassword(self):
500 """Get whether the password is to be remembered."""
501 return None if self._rememberButton is None \
502 else self._rememberButton.get_active()
503
504 def run(self):
505 """Run the dialog."""
506 self.show_all()
507
508 response = super(CredentialsDialog, self).run()
509
510 self.hide()
511
512 return response
513
514#------------------------------------------------------------------------------
515
516gobject.signal_new("integer-changed", IntegerEntry, gobject.SIGNAL_RUN_FIRST,
517 None, (object,))
518
519#------------------------------------------------------------------------------
520
521PROGRAM_NAME = "MAVA Logger X"
522
523WINDOW_TITLE_BASE = PROGRAM_NAME + " " + _const.VERSION
524if secondaryInstallation:
525 WINDOW_TITLE_BASE += " (" + xstr("secondary") + ")"
526
527#------------------------------------------------------------------------------
528
529# A mapping of aircraft types to their screen names
530aircraftNames = { _const.AIRCRAFT_B736 : xstr("aircraft_b736"),
531 _const.AIRCRAFT_B737 : xstr("aircraft_b737"),
532 _const.AIRCRAFT_B738 : xstr("aircraft_b738"),
533 _const.AIRCRAFT_B738C : xstr("aircraft_b738c"),
534 _const.AIRCRAFT_B732 : xstr("aircraft_b732"),
535 _const.AIRCRAFT_B733 : xstr("aircraft_b733"),
536 _const.AIRCRAFT_B734 : xstr("aircraft_b734"),
537 _const.AIRCRAFT_B735 : xstr("aircraft_b735"),
538 _const.AIRCRAFT_DH8D : xstr("aircraft_dh8d"),
539 _const.AIRCRAFT_B762 : xstr("aircraft_b762"),
540 _const.AIRCRAFT_B763 : xstr("aircraft_b763"),
541 _const.AIRCRAFT_CRJ2 : xstr("aircraft_crj2"),
542 _const.AIRCRAFT_F70 : xstr("aircraft_f70"),
543 _const.AIRCRAFT_DC3 : xstr("aircraft_dc3"),
544 _const.AIRCRAFT_T134 : xstr("aircraft_t134"),
545 _const.AIRCRAFT_T154 : xstr("aircraft_t154"),
546 _const.AIRCRAFT_YK40 : xstr("aircraft_yk40"),
547 _const.AIRCRAFT_B462 : xstr("aircraft_b462") }
548
549#------------------------------------------------------------------------------
550
551def formatFlightLogLine(timeStr, line):
552 """Format the given flight log line."""
553 """Format the given line for flight logging."""
554 if timeStr is not None:
555 line = timeStr + ": " + line
556 return line + "\n"
557
558#------------------------------------------------------------------------------
559
560def addFaultTag(buffer):
561 """Add a tag named 'fault' to the given buffer."""
562 buffer.create_tag("fault", foreground="red", weight=WEIGHT_BOLD)
563
564#------------------------------------------------------------------------------
565
566def appendTextBuffer(buffer, text, isFault = False):
567 """Append the given line at the end of the given text buffer.
568
569 If isFault is set, use the tag named 'fault'."""
570 insertTextBuffer(buffer, buffer.get_end_iter(), text, isFault)
571
572#------------------------------------------------------------------------------
573
574def insertTextBuffer(buffer, iter, text, isFault = False):
575 """Insert the given line into the given text buffer at the given iterator.
576
577 If isFault is set, use the tag named 'fault' else use the tag named
578 'normal'."""
579 line = iter.get_line()
580
581 buffer.insert(iter, text)
582
583 iter0 = buffer.get_iter_at_line(line)
584 iter1 = buffer.get_iter_at_line(line+1)
585 if isFault:
586 buffer.apply_tag_by_name("fault", iter0, iter1)
587 else:
588 buffer.remove_all_tags(iter0, iter1)
589
590#------------------------------------------------------------------------------
591
592def askYesNo(question, parent = None, title = WINDOW_TITLE_BASE):
593 """Ask a Yes/No question.
594
595 Return a boolean indicating the answer."""
596 dialog = gtk.MessageDialog(parent = parent,
597 type = MESSAGETYPE_QUESTION,
598 message_format = question)
599
600 dialog.add_button(xstr("button_no"), RESPONSETYPE_NO)
601 dialog.add_button(xstr("button_yes"), RESPONSETYPE_YES)
602
603 dialog.set_title(title)
604 result = dialog.run()
605 dialog.hide()
606
607 return result==RESPONSETYPE_YES
608
609#------------------------------------------------------------------------------
610
611def errroDialog(message, parent = None, secondary = None,
612 title = WINDOW_TITLE_BASE):
613 """Display an error dialog box with the given message."""
614 dialog = gtk.MessageDialog(parent = parent,
615 type = MESSAGETYPE_ERROR,
616 message_format = message)
617 dialog.add_button(xstr("button_ok"), RESPONSETYPE_OK)
618 dialog.set_title(title)
619 if secondary is not None:
620 dialog.format_secondary_markup(secondary)
621
622 dialog.run()
623 dialog.hide()
624
625#------------------------------------------------------------------------------
626
627def communicationErrorDialog(parent = None, title = WINDOW_TITLE_BASE):
628 """Display a communication error dialog."""
629 errroDialog(xstr("error_communication"), parent = parent,
630 secondary = xstr("error_communication_secondary"),
631 title = title)
632
633#------------------------------------------------------------------------------
634
635def createFlightTypeComboBox():
636 flightTypeModel = gtk.ListStore(str, int)
637 for type in _const.flightTypes:
638 name = "flighttype_" + _const.flightType2string(type)
639 flightTypeModel.append([xstr(name), type])
640
641 flightType = gtk.ComboBox(model = flightTypeModel)
642 renderer = gtk.CellRendererText()
643 flightType.pack_start(renderer, True)
644 flightType.add_attribute(renderer, "text", 0)
645
646 return flightType
647
648#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.