source: src/mlx/gui/common.py@ 1083:a5f219c25f2a

python3
Last change on this file since 1083:a5f219c25f2a was 1083:a5f219c25f2a, checked in by István Váradi <ivaradi@…>, 14 months ago

Updates for CEF 108 and the corresponding CEFPython.

File size: 18.0 KB
RevLine 
[575]1
2from mlx.common import *
[28]3
[105]4import mlx.const as _const
[175]5from mlx.i18n import xstr
[105]6
[373]7from mlx.util import secondaryInstallation
8
[28]9import os
[837]10import time
[851]11import calendar
[28]12
[300]13#-----------------------------------------------------------------------------
14
15## @package mlx.gui.common
16#
17# Common definitions and utilities for the GUI
18
19#-----------------------------------------------------------------------------
20
[28]21appIndicator = False
22
[994]23import gi
24gi.require_version("Gdk", "3.0")
[997]25from gi.repository import Gdk
[998]26from gi.repository import GdkPixbuf
[994]27gi.require_version("Gtk", "3.0")
[996]28from gi.repository import Gtk
[994]29try:
30 gi.require_version("AppIndicator3", "0.1")
[998]31 from gi.repository import AppIndicator3
[994]32 appIndicator = True
33except:
34 pass
[998]35from gi.repository import Pango
[1002]36from gi.repository import PangoCairo
[1083]37from gi.repository import GLib
[575]38
[994]39import codecs
40_utf8Decoder = codecs.getdecoder("utf-8")
[401]41
[28]42import cairo
43
[32]44#------------------------------------------------------------------------------
45
46class FlightStatusHandler(object):
47 """Base class for objects that handle the flight status in some way."""
48 def __init__(self):
49 self._stage = None
50 self._rating = 100
51 self._noGoReason = None
52
53 def resetFlightStatus(self):
54 """Reset the flight status."""
55 self._stage = None
56 self._rating = 100
57 self._noGoReason = None
58 self._updateFlightStatus()
[401]59
[32]60 def setStage(self, stage):
61 """Set the stage of the flight."""
62 if stage!=self._stage:
63 self._stage = stage
64 self._updateFlightStatus()
65
66 def setRating(self, rating):
67 """Set the rating to the given value."""
68 if rating!=self._rating:
69 self._rating = rating
70 if self._noGoReason is None:
71 self._updateFlightStatus()
72
73 def setNoGo(self, reason):
74 """Set a No-Go condition with the given reason."""
75 if self._noGoReason is None:
76 self._noGoReason = reason
77 self._updateFlightStatus()
78
79#------------------------------------------------------------------------------
[84]80
[996]81class IntegerEntry(Gtk.Entry):
[84]82 """An entry that allows only either an empty value, or an integer."""
83 def __init__(self, defaultValue = None):
84 """Construct the entry."""
[996]85 Gtk.Entry.__init__(self)
[84]86
[86]87 self.set_alignment(1.0)
88
[84]89 self._defaultValue = defaultValue
90 self._currentInteger = defaultValue
91 self._selfSetting = False
92 self._set_text()
93
94 self.connect("changed", self._handle_changed)
95
96 def get_int(self):
97 """Get the integer."""
98 return self._currentInteger
99
[241]100 def reset(self):
101 """Reset the integer."""
102 self.set_int(None)
103
[84]104 def set_int(self, value):
105 """Set the integer."""
106 if value!=self._currentInteger:
107 self._currentInteger = value
108 self.emit("integer-changed", self._currentInteger)
109 self._set_text()
[401]110
[84]111 def _handle_changed(self, widget):
112 """Handle the changed signal."""
113 if self._selfSetting:
114 return
115 text = self.get_text()
116 if text=="":
117 self.set_int(self._defaultValue)
118 else:
119 try:
120 self.set_int(int(text))
121 except:
122 self._set_text()
123
124 def _set_text(self):
125 """Set the text value from the current integer."""
126 self._selfSetting = True
127 self.set_text("" if self._currentInteger is None
128 else str(self._currentInteger))
129 self._selfSetting = False
[401]130
[84]131#------------------------------------------------------------------------------
132
[996]133class TimeEntry(Gtk.Entry):
[837]134 """Widget to display and edit a time value in HH:MM format."""
135 def __init__(self):
136 """Construct the entry"""
[926]137 super(TimeEntry, self).__init__()
138 self.set_max_width_chars(5)
[837]139
140 self.connect("insert-text", self._insertText)
141 self.connect("delete-text", self._deleteText)
142 self.connect("focus-out-event", self._focusOutEvent)
143
144 @property
145 def hour(self):
146 """Get the hour from the current text"""
147 text = self.get_text()
148 if not text or text==":":
149 return 0
150
151 words = text.split(":")
152 if len(words)==1:
153 return 0
154 elif len(words)>=2:
155 return 0 if len(words[0])==0 else int(words[0])
156 else:
157 return 0
158
159 @property
160 def minute(self):
161 """Get the hour from the current text"""
162 text = self.get_text()
163 if not text or text==":":
164 return 0
165
166 words = text.split(":")
167 if len(words)==1:
168 return 0 if len(words[0])==0 else int(words[0])
169 elif len(words)>=2:
170 return 0 if len(words[1])==0 else int(words[1])
171 else:
172 return 0
173
[846]174 @property
175 def minutes(self):
176 """Get the time in minutes, i.e. hour*60+minute."""
177 return self.hour * 60 + self.minute
178
[837]179 def setTimestamp(self, timestamp):
180 """Set the hour and minute from the given timestamp in UTC."""
181 tm = time.gmtime(timestamp)
182 self.set_text("%02d:%02d" % (tm.tm_hour, tm.tm_min))
183
[851]184 def getTimestampFrom(self, timestamp):
185 """Get the timestamp by replacing the hour and minute from the given
186 timestamp with what is set in this widget."""
187 tm = time.gmtime(timestamp)
188 ts = calendar.timegm((tm.tm_year, tm.tm_mon, tm.tm_mday,
189 self.hour, self.minute, 0,
190 tm.tm_wday, tm.tm_yday, tm.tm_isdst))
191
192 if ts > (timestamp + (16*60*60)):
193 ts -= 24*60*60
194 elif (ts + 16*60*60) < timestamp:
195 ts += 24*60*60
196
197 return ts
198
[837]199 def _focusOutEvent(self, widget, event):
200 """Reformat the text to match pattern HH:MM"""
201 text = "%02d:%02d" % (self.hour, self.minute)
202 if text!=self.get_text():
203 self.set_text(text)
204
205 def _insertText(self, entry, text, length, position):
206 """Called when some text is inserted into the entry."""
207 text=text[:length]
208 currentText = self.get_text()
209 position = self.get_position()
210 newText = currentText[:position] + text + currentText[position:]
211 self._checkText(newText, "insert-text")
212
213 def _deleteText(self, entry, start, end):
214 """Called when some text is erased from the entry."""
215 currentText = self.get_text()
216 newText = currentText[:start] + currentText[end:]
217 self._checkText(newText, "delete-text")
218
219 def _checkText(self, newText, signal):
220 """Check the given text.
221
222 If it is not suitable, stop the emission of the signal to prevent the
223 change from appearing."""
224 if not newText or newText==":":
225 return
226
227 words = newText.split(":")
228 if (len(words)==1 and
229 len(words[0])<=2 and (len(words[0])==0 or
230 (words[0].isdigit() and int(words[0])<60))) or \
231 (len(words)==2 and
232 len(words[0])<=2 and (len(words[0])==0 or
233 (words[0].isdigit() and int(words[0])<24)) and
234 len(words[1])<=2 and (len(words[1])==0 or
235 (words[1].isdigit() and int(words[1])<60))):
236 pass
237 else:
[996]238 Gtk.gdk.display_get_default().beep()
[837]239 self.stop_emission(signal)
240
241#------------------------------------------------------------------------------
242
[996]243class CredentialsDialog(Gtk.Dialog):
[741]244 """A dialog window to ask for a user name and a password."""
245 def __init__(self, gui, userName, password,
246 titleLabel, cancelButtonLabel, okButtonLabel,
247 userNameLabel, userNameTooltip,
248 passwordLabel, passwordTooltip,
249 infoText = None,
250 rememberPassword = None,
251 rememberLabel = None, rememberTooltip = None):
252 """Construct the dialog."""
253 super(CredentialsDialog, self).__init__(WINDOW_TITLE_BASE + " - " +
254 titleLabel,
255 gui.mainWindow,
[999]256 Gtk.DialogFlags.MODAL)
257 self.add_button(cancelButtonLabel, Gtk.ResponseType.CANCEL)
258 self.add_button(okButtonLabel, Gtk.ResponseType.OK)
[741]259
260 contentArea = self.get_content_area()
261
[996]262 contentAlignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
[741]263 xscale = 0.0, yscale = 0.0)
264 contentAlignment.set_padding(padding_top = 4, padding_bottom = 16,
265 padding_left = 8, padding_right = 8)
266
267 contentArea.pack_start(contentAlignment, False, False, 0)
268
[996]269 contentVBox = Gtk.VBox()
[741]270 contentAlignment.add(contentVBox)
271
272 if infoText is not None:
[996]273 label = Gtk.Label(infoText)
[741]274 label.set_alignment(0.0, 0.0)
275
276 contentVBox.pack_start(label, False, False, 0)
277
[996]278 tableAlignment = Gtk.Alignment(xalign = 0.5, yalign = 0.5,
[741]279 xscale = 0.0, yscale = 0.0)
280 tableAlignment.set_padding(padding_top = 24, padding_bottom = 0,
281 padding_left = 0, padding_right = 0)
282
[996]283 table = Gtk.Table(3, 2)
[741]284 table.set_row_spacings(4)
285 table.set_col_spacings(16)
286 table.set_homogeneous(False)
287
288 tableAlignment.add(table)
289 contentVBox.pack_start(tableAlignment, True, True, 0)
290
[996]291 label = Gtk.Label(userNameLabel)
[741]292 label.set_use_underline(True)
293 label.set_alignment(0.0, 0.5)
294 table.attach(label, 0, 1, 0, 1)
295
[996]296 self._userName = Gtk.Entry()
[741]297 self._userName.set_width_chars(16)
298 # FIXME: enabled the OK button only when there is something in thr
299 # user name and password fields
300 #self._userName.connect("changed",
301 # lambda button: self._updateForwardButton())
302 self._userName.set_tooltip_text(userNameTooltip)
303 self._userName.set_text(userName)
304 table.attach(self._userName, 1, 2, 0, 1)
305 label.set_mnemonic_widget(self._userName)
306
[996]307 label = Gtk.Label(passwordLabel)
[741]308 label.set_use_underline(True)
309 label.set_alignment(0.0, 0.5)
310 table.attach(label, 0, 1, 1, 2)
311
[996]312 self._password = Gtk.Entry()
[741]313 self._password.set_visibility(False)
314 #self._password.connect("changed",
315 # lambda button: self._updateForwardButton())
316 self._password.set_tooltip_text(passwordTooltip)
317 self._password.set_text(password)
318 table.attach(self._password, 1, 2, 1, 2)
319 label.set_mnemonic_widget(self._password)
320
321 if rememberPassword is not None:
[996]322 self._rememberButton = Gtk.CheckButton(rememberLabel)
[741]323 self._rememberButton.set_use_underline(True)
324 self._rememberButton.set_tooltip_text(rememberTooltip)
325 self._rememberButton.set_active(rememberPassword)
326 table.attach(self._rememberButton, 1, 2, 2, 3, ypadding = 8)
327 else:
328 self._rememberButton = None
329
330 @property
331 def userName(self):
332 """Get the user name entered."""
333 return self._userName.get_text()
334
335 @property
336 def password(self):
337 """Get the password entered."""
338 return self._password.get_text()
339
340 @property
341 def rememberPassword(self):
342 """Get whether the password is to be remembered."""
343 return None if self._rememberButton is None \
344 else self._rememberButton.get_active()
345
346 def run(self):
347 """Run the dialog."""
348 self.show_all()
349
350 response = super(CredentialsDialog, self).run()
351
352 self.hide()
353
354 return response
355
356#------------------------------------------------------------------------------
357
[995]358GObject.signal_new("integer-changed", IntegerEntry, GObject.SIGNAL_RUN_FIRST,
[84]359 None, (object,))
360
361#------------------------------------------------------------------------------
[105]362
[227]363PROGRAM_NAME = "MAVA Logger X"
364
[732]365WINDOW_TITLE_BASE = PROGRAM_NAME + " " + _const.VERSION
[373]366if secondaryInstallation:
367 WINDOW_TITLE_BASE += " (" + xstr("secondary") + ")"
[105]368
369#------------------------------------------------------------------------------
[175]370
371# A mapping of aircraft types to their screen names
[191]372aircraftNames = { _const.AIRCRAFT_B736 : xstr("aircraft_b736"),
373 _const.AIRCRAFT_B737 : xstr("aircraft_b737"),
374 _const.AIRCRAFT_B738 : xstr("aircraft_b738"),
375 _const.AIRCRAFT_B738C : xstr("aircraft_b738c"),
[790]376 _const.AIRCRAFT_B732 : xstr("aircraft_b732"),
[191]377 _const.AIRCRAFT_B733 : xstr("aircraft_b733"),
378 _const.AIRCRAFT_B734 : xstr("aircraft_b734"),
379 _const.AIRCRAFT_B735 : xstr("aircraft_b735"),
380 _const.AIRCRAFT_DH8D : xstr("aircraft_dh8d"),
381 _const.AIRCRAFT_B762 : xstr("aircraft_b762"),
382 _const.AIRCRAFT_B763 : xstr("aircraft_b763"),
383 _const.AIRCRAFT_CRJ2 : xstr("aircraft_crj2"),
384 _const.AIRCRAFT_F70 : xstr("aircraft_f70"),
385 _const.AIRCRAFT_DC3 : xstr("aircraft_dc3"),
386 _const.AIRCRAFT_T134 : xstr("aircraft_t134"),
387 _const.AIRCRAFT_T154 : xstr("aircraft_t154"),
[443]388 _const.AIRCRAFT_YK40 : xstr("aircraft_yk40"),
389 _const.AIRCRAFT_B462 : xstr("aircraft_b462") }
[175]390
391#------------------------------------------------------------------------------
[221]392
[858]393aircraftFamilyNames = {
394 _const.AIRCRAFT_FAMILY_B737NG: xstr("aircraft_family_b737ng"),
395
396 _const.AIRCRAFT_FAMILY_B737CL: xstr("aircraft_family_b737cl"),
397
398 _const.AIRCRAFT_FAMILY_DH8D: xstr("aircraft_family_dh8d"),
399
400 _const.AIRCRAFT_FAMILY_B767: xstr("aircraft_family_b767"),
401
402 _const.AIRCRAFT_FAMILY_CRJ2: xstr("aircraft_family_crj2"),
403
404 _const.AIRCRAFT_FAMILY_F70: xstr("aircraft_family_f70"),
405
406 _const.AIRCRAFT_FAMILY_DC3: xstr("aircraft_family_dc3"),
407
408 _const.AIRCRAFT_FAMILY_T134: xstr("aircraft_family_t134"),
409
410 _const.AIRCRAFT_FAMILY_T154: xstr("aircraft_family_t154"),
411
412 _const.AIRCRAFT_FAMILY_YK40: xstr("aircraft_family_yk40"),
413
414 _const.AIRCRAFT_FAMILY_B462: xstr("aircraft_family_b462")
415}
416
417#------------------------------------------------------------------------------
418
[226]419def formatFlightLogLine(timeStr, line):
[221]420 """Format the given flight log line."""
421 """Format the given line for flight logging."""
422 if timeStr is not None:
423 line = timeStr + ": " + line
424 return line + "\n"
425
426#------------------------------------------------------------------------------
[226]427
428def addFaultTag(buffer):
429 """Add a tag named 'fault' to the given buffer."""
[999]430 buffer.create_tag("fault", foreground="red", weight=Pango.Weight.BOLD)
[226]431
432#------------------------------------------------------------------------------
433
434def appendTextBuffer(buffer, text, isFault = False):
435 """Append the given line at the end of the given text buffer.
436
437 If isFault is set, use the tag named 'fault'."""
[345]438 insertTextBuffer(buffer, buffer.get_end_iter(), text, isFault)
[226]439
440#------------------------------------------------------------------------------
[345]441
442def insertTextBuffer(buffer, iter, text, isFault = False):
443 """Insert the given line into the given text buffer at the given iterator.
444
[362]445 If isFault is set, use the tag named 'fault' else use the tag named
446 'normal'."""
447 line = iter.get_line()
448
449 buffer.insert(iter, text)
450
451 iter0 = buffer.get_iter_at_line(line)
452 iter1 = buffer.get_iter_at_line(line+1)
[345]453 if isFault:
[362]454 buffer.apply_tag_by_name("fault", iter0, iter1)
[345]455 else:
[362]456 buffer.remove_all_tags(iter0, iter1)
[345]457
458#------------------------------------------------------------------------------
[826]459
460def askYesNo(question, parent = None, title = WINDOW_TITLE_BASE):
461 """Ask a Yes/No question.
462
463 Return a boolean indicating the answer."""
[996]464 dialog = Gtk.MessageDialog(parent = parent,
[999]465 type = Gtk.MessageType.QUESTION,
[826]466 message_format = question)
467
[999]468 dialog.add_button(xstr("button_no"), Gtk.ResponseType.NO)
469 dialog.add_button(xstr("button_yes"), Gtk.ResponseType.YES)
[826]470
471 dialog.set_title(title)
472 result = dialog.run()
473 dialog.hide()
474
[999]475 return result==Gtk.ResponseType.YES
[826]476
477#------------------------------------------------------------------------------
[828]478
[863]479def errorDialog(message, parent = None, secondary = None,
[828]480 title = WINDOW_TITLE_BASE):
481 """Display an error dialog box with the given message."""
[996]482 dialog = Gtk.MessageDialog(parent = parent,
[999]483 type = Gtk.MessageType.ERROR,
[828]484 message_format = message)
[999]485 dialog.add_button(xstr("button_ok"), Gtk.ResponseType.OK)
[828]486 dialog.set_title(title)
487 if secondary is not None:
488 dialog.format_secondary_markup(secondary)
489
490 dialog.run()
491 dialog.hide()
492
493#------------------------------------------------------------------------------
494
495def communicationErrorDialog(parent = None, title = WINDOW_TITLE_BASE):
496 """Display a communication error dialog."""
[863]497 errorDialog(xstr("error_communication"), parent = parent,
[828]498 secondary = xstr("error_communication_secondary"),
499 title = title)
500
501#------------------------------------------------------------------------------
[836]502
503def createFlightTypeComboBox():
[996]504 flightTypeModel = Gtk.ListStore(str, int)
[836]505 for type in _const.flightTypes:
506 name = "flighttype_" + _const.flightType2string(type)
507 flightTypeModel.append([xstr(name), type])
508
[996]509 flightType = Gtk.ComboBox(model = flightTypeModel)
510 renderer = Gtk.CellRendererText()
[836]511 flightType.pack_start(renderer, True)
512 flightType.add_attribute(renderer, "text", 0)
513
514 return flightType
515
516#------------------------------------------------------------------------------
[852]517
518def getTextViewText(textView):
519 """Get the text from the given text view."""
520 buffer = textView.get_buffer()
521 return buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter(), True)
522
523#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.