source: src/mlx/gui/common.py@ 828:b5013b5bffb1

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

The pending flights window gets closed automatically if no pending flights remain (re #307).

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