source: src/mlx/gui/common.py@ 850:daa89a700477

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

Added RESPONSETYPE_NONE.

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