source: src/mlx/gui/common.py@ 1001:e5cde583f829

python3
Last change on this file since 1001:e5cde583f829 was 999:e096a5638b87, checked in by István Váradi <ivaradi@…>, 5 years ago

Removed Gtk 2/3 constant definitions (re #347)

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