source: src/mlx/gui/common.py@ 823:7a9ce7d89ece

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

Multiple flights can be selected in a flight list (re #307).

File size: 15.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
10
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
19# GUI have to check the version in use very rarely. The variable \c pygobject
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#
24# Besides this there are some common utility classes and functions.
25
26#-----------------------------------------------------------------------------
27
28appIndicator = False
29
30if not pygobject:
31 print "Using PyGTK"
32 pygobject = False
33 import pygtk
34 pygtk.require("2.0")
35 import gtk.gdk as gdk
36 import gtk
37 import pango
38
39 try:
40 import appindicator
41 appIndicator = True
42 except Exception, e:
43 pass
44
45 MESSAGETYPE_ERROR = gtk.MESSAGE_ERROR
46 MESSAGETYPE_QUESTION = gtk.MESSAGE_QUESTION
47 MESSAGETYPE_INFO = gtk.MESSAGE_INFO
48
49 RESPONSETYPE_OK = gtk.RESPONSE_OK
50 RESPONSETYPE_YES = gtk.RESPONSE_YES
51 RESPONSETYPE_NO = gtk.RESPONSE_NO
52 RESPONSETYPE_ACCEPT = gtk.RESPONSE_ACCEPT
53 RESPONSETYPE_REJECT = gtk.RESPONSE_REJECT
54 RESPONSETYPE_CANCEL = gtk.RESPONSE_CANCEL
55
56 ACCEL_VISIBLE = gtk.ACCEL_VISIBLE
57 CONTROL_MASK = gdk.CONTROL_MASK
58 DIALOG_MODAL = gtk.DIALOG_MODAL
59 WRAP_WORD = gtk.WRAP_WORD
60
61 JUSTIFY_CENTER = gtk.JUSTIFY_CENTER
62 JUSTIFY_LEFT = gtk.JUSTIFY_LEFT
63
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
73 FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER
74 FILE_CHOOSER_ACTION_OPEN = gtk.FILE_CHOOSER_ACTION_OPEN
75 FILE_CHOOSER_ACTION_SAVE = gtk.FILE_CHOOSER_ACTION_SAVE
76
77 SELECTION_MULTIPLE = gtk.SELECTION_MULTIPLE
78
79 SHADOW_IN = gtk.SHADOW_IN
80 SHADOW_NONE = gtk.SHADOW_NONE
81
82 POLICY_AUTOMATIC = gtk.POLICY_AUTOMATIC
83 POLICY_NEVER = gtk.POLICY_NEVER
84 POLICY_ALWAYS = gtk.POLICY_ALWAYS
85
86 WEIGHT_NORMAL = pango.WEIGHT_NORMAL
87 WEIGHT_BOLD = pango.WEIGHT_BOLD
88
89 WINDOW_STATE_ICONIFIED = gdk.WINDOW_STATE_ICONIFIED
90 WINDOW_STATE_WITHDRAWN = gdk.WINDOW_STATE_WITHDRAWN
91
92 SORT_ASCENDING = gtk.SORT_ASCENDING
93 SORT_DESCENDING = gtk.SORT_DESCENDING
94
95 EVENT_BUTTON_PRESS = gdk.BUTTON_PRESS
96
97 TREE_VIEW_COLUMN_FIXED = gtk.TREE_VIEW_COLUMN_FIXED
98
99 FILL = gtk.FILL
100 EXPAND = gtk.EXPAND
101
102 UPDATE_IF_VALID = gtk.UPDATE_IF_VALID
103
104 SELECTION_MULTIPLE = gtk.SELECTION_MULTIPLE
105
106 pixbuf_new_from_file = gdk.pixbuf_new_from_file
107
108 def text2unicode(text):
109 """Convert the given text, returned by a Gtk widget, to Unicode."""
110 return unicode(text)
111
112 def text2str(text):
113 """Convert the given text, returned by xstr to a string."""
114 return str(text)
115
116else: # pygobject
117 from gi.repository import Gdk as gdk
118 from gi.repository import GdkPixbuf as gdkPixbuf
119 from gi.repository import Gtk as gtk
120 from gi.repository import AppIndicator3 as appindicator
121 from gi.repository import Pango as pango
122
123 appIndicator = True
124
125
126 MESSAGETYPE_ERROR = gtk.MessageType.ERROR
127 MESSAGETYPE_QUESTION = gtk.MessageType.QUESTION
128 MESSAGETYPE_INFO = gtk.MessageType.INFO
129 RESPONSETYPE_OK = gtk.ResponseType.OK
130 RESPONSETYPE_YES = gtk.ResponseType.YES
131 RESPONSETYPE_NO = gtk.ResponseType.NO
132 RESPONSETYPE_ACCEPT = gtk.ResponseType.ACCEPT
133 RESPONSETYPE_REJECT = gtk.ResponseType.REJECT
134 RESPONSETYPE_CANCEL = gtk.ResponseType.CANCEL
135 ACCEL_VISIBLE = gtk.AccelFlags.VISIBLE
136 CONTROL_MASK = gdk.ModifierType.CONTROL_MASK
137 DIALOG_MODAL = gtk.DialogFlags.MODAL
138 WRAP_WORD = gtk.WrapMode.WORD
139 JUSTIFY_CENTER = gtk.Justification.CENTER
140 JUSTIFY_LEFT = gtk.Justification.LEFT
141
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
151 FILE_CHOOSER_ACTION_SELECT_FOLDER = gtk.FileChooserAction.SELECT_FOLDER
152 FILE_CHOOSER_ACTION_OPEN = gtk.FileChooserAction.OPEN
153 FILE_CHOOSER_ACTION_SAVE = gtk.FileChooserAction.SAVE
154
155 SELECTION_MULTIPLE = gtk.SelectionMode.MULTIPLE
156
157 SHADOW_IN = gtk.ShadowType.IN
158 SHADOW_NONE = gtk.ShadowType.NONE
159
160 POLICY_AUTOMATIC = gtk.PolicyType.AUTOMATIC
161 POLICY_NEVER = gtk.PolicyType.NEVER
162 POLICY_ALWAYS = gtk.PolicyType.ALWAYS
163
164 WEIGHT_NORMAL = pango.Weight.NORMAL
165 WEIGHT_BOLD = pango.Weight.BOLD
166
167 WINDOW_STATE_ICONIFIED = gdk.WindowState.ICONIFIED
168 WINDOW_STATE_WITHDRAWN = gdk.WindowState.WITHDRAWN
169
170 SORT_ASCENDING = gtk.SortType.ASCENDING
171 SORT_DESCENDING = gtk.SortType.DESCENDING
172
173 EVENT_BUTTON_PRESS = gdk.EventType.BUTTON_PRESS
174
175 TREE_VIEW_COLUMN_FIXED = gtk.TreeViewColumnSizing.FIXED
176
177 FILL = gtk.AttachOptions.FILL
178 EXPAND = gtk.AttachOptions.EXPAND
179
180 UPDATE_IF_VALID = gtk.SpinButtonUpdatePolicy.IF_VALID
181
182 SELECTION_MULTIPLE = gtk.SelectionMode.MULTIPLE
183
184 pixbuf_new_from_file = gdkPixbuf.Pixbuf.new_from_file
185
186 import codecs
187 _utf8Decoder = codecs.getdecoder("utf-8")
188
189 def text2unicode(str):
190 """Convert the given text, returned by a Gtk widget, to Unicode."""
191 return _utf8Decoder(str)[0]
192
193 def text2str(text):
194 """Convert the given text, returned by xstr to a string."""
195 return _utf8Decoder(text)[0]
196
197import cairo
198
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()
214
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#------------------------------------------------------------------------------
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
242 self.set_alignment(1.0)
243
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
255 def reset(self):
256 """Reset the integer."""
257 self.set_int(None)
258
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()
265
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
285
286#------------------------------------------------------------------------------
287
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
403gobject.signal_new("integer-changed", IntegerEntry, gobject.SIGNAL_RUN_FIRST,
404 None, (object,))
405
406#------------------------------------------------------------------------------
407
408PROGRAM_NAME = "MAVA Logger X"
409
410WINDOW_TITLE_BASE = PROGRAM_NAME + " " + _const.VERSION
411if secondaryInstallation:
412 WINDOW_TITLE_BASE += " (" + xstr("secondary") + ")"
413
414#------------------------------------------------------------------------------
415
416# A mapping of aircraft types to their screen names
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"),
421 _const.AIRCRAFT_B732 : xstr("aircraft_b732"),
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"),
433 _const.AIRCRAFT_YK40 : xstr("aircraft_yk40"),
434 _const.AIRCRAFT_B462 : xstr("aircraft_b462") }
435
436#------------------------------------------------------------------------------
437
438def formatFlightLogLine(timeStr, line):
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#------------------------------------------------------------------------------
446
447def addFaultTag(buffer):
448 """Add a tag named 'fault' to the given buffer."""
449 buffer.create_tag("fault", foreground="red", weight=WEIGHT_BOLD)
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'."""
457 insertTextBuffer(buffer, buffer.get_end_iter(), text, isFault)
458
459#------------------------------------------------------------------------------
460
461def insertTextBuffer(buffer, iter, text, isFault = False):
462 """Insert the given line into the given text buffer at the given iterator.
463
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)
472 if isFault:
473 buffer.apply_tag_by_name("fault", iter0, iter1)
474 else:
475 buffer.remove_all_tags(iter0, iter1)
476
477#------------------------------------------------------------------------------
Note: See TracBrowser for help on using the repository browser.