source: src/mlx/gui/gui.py@ 105:d1c3dd71da77

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

The dialogs now have a proper parent window and title

File size: 23.7 KB
RevLine 
[28]1# The main file for the GUI
2
[29]3from statusicon import StatusIcon
[32]4from statusbar import Statusbar
[90]5from info import FlightInfo
[36]6from update import Updater
[29]7from mlx.gui.common import *
[42]8from mlx.gui.flight import Wizard
[77]9from mlx.gui.monitor import MonitorWindow
[28]10
11import mlx.const as const
12import mlx.fs as fs
13import mlx.flight as flight
14import mlx.logger as logger
15import mlx.acft as acft
[41]16import mlx.web as web
[28]17
18import time
[38]19import threading
20import sys
[28]21
[77]22#------------------------------------------------------------------------------
23
[28]24class GUI(fs.ConnectionListener):
25 """The main GUI class."""
[96]26 @staticmethod
27 def _formatFlightLogLine(timeStr, line):
28 """Format the given line for flight logging."""
29 if timeStr is not None:
30 line = timeStr + ": " + line
31 return line + "\n"
32
[36]33 def __init__(self, programDirectory, config):
[28]34 """Construct the GUI."""
35 gobject.threads_init()
36
[36]37 self._programDirectory = programDirectory
[42]38 self.config = config
[28]39 self._connecting = False
[59]40 self._reconnecting = False
[28]41 self._connected = False
[96]42 self._logger = logger.Logger(self)
[28]43 self._flight = None
44 self._simulator = None
[59]45 self._monitoring = False
[38]46
47 self._stdioLock = threading.Lock()
48 self._stdioText = ""
[28]49
[41]50 self.webHandler = web.Handler()
51 self.webHandler.start()
52
[38]53 self.toRestart = False
54
[29]55 def build(self, iconDirectory):
[28]56 """Build the GUI."""
[29]57
[36]58 window = gtk.Window()
[105]59 window.set_title(WINDOW_TITLE_BASE)
[36]60 window.set_icon_from_file(os.path.join(iconDirectory, "logo.ico"))
61 window.connect("delete-event",
62 lambda a, b: self.hideMainWindow())
63 window.connect("window-state-event", self._handleMainWindowState)
[93]64 accelGroup = gtk.AccelGroup()
65 window.add_accel_group(accelGroup)
[28]66
67 mainVBox = gtk.VBox()
[36]68 window.add(mainVBox)
[28]69
[93]70 menuBar = self._buildMenuBar(accelGroup)
71 mainVBox.pack_start(menuBar, False, False, 0)
72
[92]73 self._notebook = gtk.Notebook()
[93]74 mainVBox.pack_start(self._notebook, True, True, 4)
[42]75
[46]76 self._wizard = Wizard(self)
[90]77 label = gtk.Label("Fligh_t")
[46]78 label.set_use_underline(True)
79 label.set_tooltip_text("Flight wizard")
[92]80 self._notebook.append_page(self._wizard, label)
[42]81
[90]82 self._flightInfo = FlightInfo(self)
83 label = gtk.Label("Flight _info")
84 label.set_use_underline(True)
85 label.set_tooltip_text("Flight information")
[92]86 self._notebook.append_page(self._flightInfo, label)
[93]87 self._flightInfo.disable()
[90]88
[93]89 (logWidget, self._logView) = self._buildLogWidget()
[46]90 label = gtk.Label("_Log")
91 label.set_use_underline(True)
[93]92 label.set_tooltip_text("The log of your flight that will be sent to the MAVA website")
93 self._notebook.append_page(logWidget, label)
94
95 (self._debugLogWidget, self._debugLogView) = self._buildLogWidget()
96 self._debugLogWidget.show_all()
[32]97
98 mainVBox.pack_start(gtk.HSeparator(), False, False, 0)
99
100 self._statusbar = Statusbar()
101 mainVBox.pack_start(self._statusbar, False, False, 0)
102
[92]103 self._notebook.connect("switch-page", self._notebookPageSwitch)
[46]104
[77]105 self._monitorWindow = MonitorWindow(self, iconDirectory)
[93]106 self._monitorWindow.add_accel_group(accelGroup)
[77]107 self._monitorWindowX = None
108 self._monitorWindowY = None
[93]109 self._selfToggling = False
[77]110
[46]111 window.show_all()
112 self._wizard.grabDefault()
[28]113
[36]114 self._mainWindow = window
[29]115
116 self._statusIcon = StatusIcon(iconDirectory, self)
[28]117
[49]118 self._busyCursor = gdk.Cursor(gdk.CursorType.WATCH if pygobject
119 else gdk.WATCH)
120
[59]121 @property
[105]122 def mainWindow(self):
123 """Get the main window of the GUI."""
124 return self._mainWindow
125
126 @property
[97]127 def logger(self):
128 """Get the logger used by us."""
129 return self._logger
130
131 @property
[59]132 def simulator(self):
133 """Get the simulator used by us."""
134 return self._simulator
135
[71]136 @property
137 def flight(self):
138 """Get the flight being performed."""
139 return self._flight
[84]140
141 @property
[97]142 def bookedFlight(self):
143 """Get the booked flight selected, if any."""
144 return self._wizard.bookedFlight
145
146 @property
147 def cargoWeight(self):
148 """Get the cargo weight."""
149 return self._wizard.cargoWeight
150
151 @property
[84]152 def zfw(self):
153 """Get Zero-Fuel Weight calculated for the current flight."""
154 return self._wizard.zfw
155
156 @property
[97]157 def filedCruiseAltitude(self):
158 """Get cruise altitude filed for the current flight."""
159 return self._wizard.filedCruiseAltitude
160
161 @property
[84]162 def cruiseAltitude(self):
[97]163 """Get cruise altitude set for the current flight."""
[84]164 return self._wizard.cruiseAltitude
[97]165
166 @property
167 def route(self):
168 """Get the flight route."""
169 return self._wizard.route
170
171 @property
172 def departureMETAR(self):
173 """Get the METAR of the deprature airport."""
174 return self._wizard.departureMETAR
[84]175
176 @property
[97]177 def arrivalMETAR(self):
178 """Get the METAR of the deprature airport."""
179 return self._wizard.arrivalMETAR
180
181 @property
182 def departureRunway(self):
183 """Get the name of the departure runway."""
184 return self._wizard.departureRunway
185
186 @property
187 def sid(self):
188 """Get the SID."""
189 return self._wizard.sid
190
191 @property
[84]192 def v1(self):
193 """Get the V1 speed calculated for the flight."""
194 return self._wizard.v1
195
196 @property
197 def vr(self):
198 """Get the Vr speed calculated for the flight."""
199 return self._wizard.vr
200
201 @property
202 def v2(self):
203 """Get the V2 speed calculated for the flight."""
204 return self._wizard.v2
[71]205
[86]206 @property
[97]207 def arrivalRunway(self):
208 """Get the arrival runway."""
209 return self._wizard.arrivalRunway
210
211 @property
212 def star(self):
213 """Get the STAR."""
214 return self._wizard.star
215
216 @property
217 def transition(self):
218 """Get the transition."""
219 return self._wizard.transition
220
221 @property
222 def approachType(self):
223 """Get the approach type."""
224 return self._wizard.approachType
225
226 @property
[86]227 def vref(self):
228 """Get the Vref speed calculated for the flight."""
229 return self._wizard.vref
230
[97]231 @property
232 def flightType(self):
233 """Get the flight type."""
234 return self._wizard.flightType
235
236 @property
237 def online(self):
238 """Get whether the flight was online or not."""
239 return self._wizard.online
240
241 @property
242 def comments(self):
243 """Get the comments."""
244 return self._flightInfo.comments
245
246 @property
247 def flightDefects(self):
248 """Get the flight defects."""
249 return self._flightInfo.flightDefects
250
[99]251 @property
252 def delayCodes(self):
253 """Get the delay codes."""
254 return self._flightInfo.delayCodes
255
[28]256 def run(self):
257 """Run the GUI."""
[42]258 if self.config.autoUpdate:
[38]259 self._updater = Updater(self,
260 self._programDirectory,
[42]261 self.config.updateURL,
[36]262 self._mainWindow)
263 self._updater.start()
264
[28]265 gtk.main()
[36]266
[91]267 self._disconnect()
[28]268
269 def connected(self, fsType, descriptor):
270 """Called when we have connected to the simulator."""
271 self._connected = True
272 self._logger.untimedMessage("Connected to the simulator %s" % (descriptor,))
[59]273 gobject.idle_add(self._handleConnected, fsType, descriptor)
274
275 def _handleConnected(self, fsType, descriptor):
276 """Called when the connection to the simulator has succeeded."""
277 self._statusbar.updateConnection(self._connecting, self._connected)
278 self.endBusy()
279 if not self._reconnecting:
280 self._wizard.connected(fsType, descriptor)
281 self._reconnecting = False
282
283 def connectionFailed(self):
284 """Called when the connection failed."""
285 self._logger.untimedMessage("Connection to the simulator failed")
286 gobject.idle_add(self._connectionFailed)
287
288 def _connectionFailed(self):
289 """Called when the connection failed."""
290 self.endBusy()
291 self._statusbar.updateConnection(self._connecting, self._connected)
292
[105]293 dialog = gtk.MessageDialog(parent = self._mainWindow,
294 type = MESSAGETYPE_ERROR,
[59]295 message_format =
[105]296 "Cannot connect to the simulator.")
297 dialog.set_title(WINDOW_TITLE_BASE)
[59]298 dialog.format_secondary_markup("Rectify the situation, and press <b>Try again</b> "
299 "to try the connection again, "
300 "or <b>Cancel</b> to cancel the flight.")
301
302 dialog.add_button("_Cancel", 0)
303 dialog.add_button("_Try again", 1)
304 dialog.set_default_response(1)
305
306 result = dialog.run()
307 dialog.hide()
308 if result == 1:
309 self.beginBusy("Connecting to the simulator.")
310 self._simulator.reconnect()
311 else:
[91]312 self.reset()
[28]313
314 def disconnected(self):
315 """Called when we have disconnected from the simulator."""
316 self._connected = False
317 self._logger.untimedMessage("Disconnected from the simulator")
[59]318
319 gobject.idle_add(self._disconnected)
320
321 def _disconnected(self):
322 """Called when we have disconnected from the simulator unexpectedly."""
323 self._statusbar.updateConnection(self._connecting, self._connected)
324
325 dialog = gtk.MessageDialog(type = MESSAGETYPE_ERROR,
326 message_format =
327 "The connection to the simulator failed unexpectedly.",
328 parent = self._mainWindow)
[105]329 dialog.set_title(WINDOW_TITLE_BASE)
[59]330 dialog.format_secondary_markup("If the simulator has crashed, restart it "
331 "and restore your flight as much as possible "
[105]332 "to the state it was in before the crash. "
[59]333 "Then press <b>Reconnect</b> to reconnect.\n\n"
334 "If you want to cancel the flight, press <b>Cancel</b>.")
335
336 dialog.add_button("_Cancel", 0)
337 dialog.add_button("_Reconnect", 1)
338 dialog.set_default_response(1)
339
340 result = dialog.run()
341 dialog.hide()
342 if result == 1:
343 self.beginBusy("Connecting to the simulator.")
344 self._reconnecting = True
345 self._simulator.reconnect()
346 else:
[91]347 self.reset()
348
[93]349 def enableFlightInfo(self):
350 """Enable the flight info tab."""
351 self._flightInfo.enable()
352
[91]353 def reset(self):
354 """Reset the GUI."""
355 self._disconnect()
[92]356
[91]357 self._flightInfo.reset()
[93]358 self._flightInfo.disable()
[91]359 self.resetFlightStatus()
[28]360
[92]361 self._wizard.reset()
362 self._notebook.set_current_page(0)
363
364 self._logView.get_buffer().set_text("")
365
[91]366 def _disconnect(self):
367 """Disconnect from the simulator if connected."""
[92]368 self.stopMonitoring()
369
[91]370 if self._connected:
371 self._flight.simulator.disconnect()
372 self._connected = False
373
374 self._connecting = False
375 self._reconnecting = False
376 self._statusbar.updateConnection(False, False)
377
[96]378 def addFlightLogLine(self, timeStr, line):
379 """Write the given message line to the log."""
380 gobject.idle_add(self._writeLog,
381 GUI._formatFlightLogLine(timeStr, line),
382 self._logView)
383
384 def updateFlightLogLine(self, index, timeStr, line):
385 """Update the line with the given index."""
386 gobject.idle_add(self._updateFlightLogLine, index,
387 GUI._formatFlightLogLine(timeStr, line))
388
389 def _updateFlightLogLine(self, index, line):
390 """Replace the contents of the given line in the log."""
391 buffer = self._logView.get_buffer()
392 startIter = buffer.get_iter_at_line(index)
393 endIter = buffer.get_iter_at_line(index + 1)
394 buffer.delete(startIter, endIter)
395 buffer.insert(startIter, line)
396 self._logView.scroll_mark_onscreen(buffer.get_insert())
397
[28]398 def check(self, flight, aircraft, logger, oldState, state):
399 """Update the data."""
[77]400 gobject.idle_add(self._monitorWindow.setData, state)
[80]401 gobject.idle_add(self._statusbar.updateTime, state.timestamp)
[28]402
[31]403 def resetFlightStatus(self):
404 """Reset the status of the flight."""
[32]405 self._statusbar.resetFlightStatus()
[80]406 self._statusbar.updateTime()
[31]407 self._statusIcon.resetFlightStatus()
408
409 def setStage(self, stage):
410 """Set the stage of the flight."""
411 gobject.idle_add(self._setStage, stage)
412
413 def _setStage(self, stage):
414 """Set the stage of the flight."""
[32]415 self._statusbar.setStage(stage)
[31]416 self._statusIcon.setStage(stage)
[84]417 self._wizard.setStage(stage)
[88]418 if stage==const.STAGE_END:
[91]419 self._disconnect()
[31]420
421 def setRating(self, rating):
422 """Set the rating of the flight."""
423 gobject.idle_add(self._setRating, rating)
424
425 def _setRating(self, rating):
426 """Set the rating of the flight."""
[32]427 self._statusbar.setRating(rating)
[31]428 self._statusIcon.setRating(rating)
429
430 def setNoGo(self, reason):
431 """Set the rating of the flight to No-Go with the given reason."""
432 gobject.idle_add(self._setNoGo, reason)
433
434 def _setNoGo(self, reason):
435 """Set the rating of the flight."""
[32]436 self._statusbar.setNoGo(reason)
[31]437 self._statusIcon.setNoGo(reason)
438
[29]439 def _handleMainWindowState(self, window, event):
440 """Hande a change in the state of the window"""
441 iconified = gdk.WindowState.ICONIFIED if pygobject \
442 else gdk.WINDOW_STATE_ICONIFIED
443 if (event.changed_mask&iconified)!=0 and (event.new_window_state&iconified)!=0:
[35]444 self.hideMainWindow(savePosition = False)
[29]445
[35]446 def hideMainWindow(self, savePosition = True):
[29]447 """Hide the main window and save its position."""
[35]448 if savePosition:
449 (self._mainWindowX, self._mainWindowY) = \
450 self._mainWindow.get_window().get_root_origin()
451 else:
452 self._mainWindowX = self._mainWindowY = None
[29]453 self._mainWindow.hide()
454 self._statusIcon.mainWindowHidden()
455 return True
456
457 def showMainWindow(self):
458 """Show the main window at its former position."""
[35]459 if self._mainWindowX is not None and self._mainWindowY is not None:
460 self._mainWindow.move(self._mainWindowX, self._mainWindowY)
461
462 self._mainWindow.show()
[29]463 self._mainWindow.deiconify()
[35]464
[29]465 self._statusIcon.mainWindowShown()
466
467 def toggleMainWindow(self):
468 """Toggle the main window."""
469 if self._mainWindow.get_visible():
470 self.hideMainWindow()
471 else:
472 self.showMainWindow()
[36]473
[77]474 def hideMonitorWindow(self, savePosition = True):
475 """Hide the monitor window."""
476 if savePosition:
477 (self._monitorWindowX, self._monitorWindowY) = \
478 self._monitorWindow.get_window().get_root_origin()
479 else:
480 self._monitorWindowX = self._monitorWindowY = None
481 self._monitorWindow.hide()
482 self._statusIcon.monitorWindowHidden()
[93]483 if self._showMonitorMenuItem.get_active():
484 self._selfToggling = True
485 self._showMonitorMenuItem.set_active(False)
[77]486 return True
487
488 def showMonitorWindow(self):
489 """Show the monitor window."""
490 if self._monitorWindowX is not None and self._monitorWindowY is not None:
491 self._monitorWindow.move(self._monitorWindowX, self._monitorWindowY)
492 self._monitorWindow.show_all()
493 self._statusIcon.monitorWindowShown()
[93]494 if not self._showMonitorMenuItem.get_active():
495 self._selfToggling = True
496 self._showMonitorMenuItem.set_active(True)
497
498 def _toggleMonitorWindow(self, menuItem):
499 if self._selfToggling:
500 self._selfToggling = False
501 elif self._monitorWindow.get_visible():
502 self.hideMonitorWindow()
503 else:
504 self.showMonitorWindow()
[77]505
[38]506 def restart(self):
507 """Quit and restart the application."""
508 self.toRestart = True
[81]509 self._quit(force = True)
[38]510
511 def flushStdIO(self):
512 """Flush any text to the standard error that could not be logged."""
513 if self._stdioText:
514 sys.__stderr__.write(self._stdioText)
515
[36]516 def writeStdIO(self, text):
517 """Write the given text into standard I/O log."""
[38]518 with self._stdioLock:
519 self._stdioText += text
520
521 gobject.idle_add(self._writeStdIO)
[36]522
[49]523 def beginBusy(self, message):
524 """Begin a period of background processing."""
[93]525 self._wizard.set_sensitive(False)
[49]526 self._mainWindow.get_window().set_cursor(self._busyCursor)
527 self._statusbar.updateBusyState(message)
528
529 def endBusy(self):
530 """End a period of background processing."""
531 self._mainWindow.get_window().set_cursor(None)
[93]532 self._wizard.set_sensitive(True)
[49]533 self._statusbar.updateBusyState(None)
534
[38]535 def _writeStdIO(self):
[36]536 """Perform the real writing."""
[38]537 with self._stdioLock:
538 text = self._stdioText
539 self._stdioText = ""
540 if not text: return
541
[36]542 lines = text.splitlines()
543 if text[-1]=="\n":
544 text = ""
545 else:
546 text = lines[-1]
547 lines = lines[:-1]
548
549 for line in lines:
[96]550 #print >> sys.__stdout__, line
[93]551 self._writeLog(line + "\n", self._debugLogView)
[36]552
553 if text:
[96]554 #print >> sys.__stdout__, text,
[93]555 self._writeLog(text, self._debugLogView)
[51]556
[59]557 def connectSimulator(self, aircraftType):
558 """Connect to the simulator for the first time."""
559 self._logger.reset()
560
561 self._flight = flight.Flight(self._logger, self)
562 self._flight.aircraftType = aircraftType
563 self._flight.aircraft = acft.Aircraft.create(self._flight)
564 self._flight.aircraft._checkers.append(self)
565
566 if self._simulator is None:
567 self._simulator = fs.createSimulator(const.SIM_MSFS9, self)
568
569 self._flight.simulator = self._simulator
570
571 self.beginBusy("Connecting to the simulator...")
572 self._statusbar.updateConnection(self._connecting, self._connected)
573
574 self._connecting = True
575 self._simulator.connect(self._flight.aircraft)
576
[70]577 def startMonitoring(self):
578 """Start monitoring."""
[88]579 if not self._monitoring:
580 self.simulator.startMonitoring()
581 self._monitoring = True
[70]582
583 def stopMonitoring(self):
584 """Stop monitoring."""
[88]585 if self._monitoring:
586 self.simulator.stopMonitoring()
587 self._monitoring = False
[70]588
[93]589 def _buildMenuBar(self, accelGroup):
590 """Build the main menu bar."""
591 menuBar = gtk.MenuBar()
592
593 fileMenuItem = gtk.MenuItem("File")
594 fileMenu = gtk.Menu()
595 fileMenuItem.set_submenu(fileMenu)
596 menuBar.append(fileMenuItem)
597
598 quitMenuItem = gtk.ImageMenuItem(gtk.STOCK_QUIT)
599 quitMenuItem.set_use_stock(True)
600 quitMenuItem.add_accelerator("activate", accelGroup,
601 ord("q"), CONTROL_MASK,
602 ACCEL_VISIBLE)
603 quitMenuItem.connect("activate", self._quit)
604 fileMenu.append(quitMenuItem)
605
606
607 viewMenuItem = gtk.MenuItem("View")
608 viewMenu = gtk.Menu()
609 viewMenuItem.set_submenu(viewMenu)
610 menuBar.append(viewMenuItem)
[28]611
[93]612 self._showMonitorMenuItem = gtk.CheckMenuItem()
613 self._showMonitorMenuItem.set_label("Show _monitor window")
614 self._showMonitorMenuItem.set_use_underline(True)
615 self._showMonitorMenuItem.set_active(False)
616 self._showMonitorMenuItem.add_accelerator("activate", accelGroup,
617 ord("m"), CONTROL_MASK,
618 ACCEL_VISIBLE)
619 self._showMonitorMenuItem.connect("toggled", self._toggleMonitorWindow)
620 viewMenu.append(self._showMonitorMenuItem)
621
622 showDebugMenuItem = gtk.CheckMenuItem()
623 showDebugMenuItem.set_label("Show _debug log")
624 showDebugMenuItem.set_use_underline(True)
625 showDebugMenuItem.set_active(False)
626 showDebugMenuItem.add_accelerator("activate", accelGroup,
627 ord("d"), CONTROL_MASK,
628 ACCEL_VISIBLE)
629 showDebugMenuItem.connect("toggled", self._toggleDebugLog)
630 viewMenu.append(showDebugMenuItem)
[28]631
[93]632 return menuBar
[28]633
[93]634 def _toggleDebugLog(self, menuItem):
635 """Toggle the debug log."""
636 if menuItem.get_active():
637 label = gtk.Label("_Debug log")
638 label.set_use_underline(True)
639 label.set_tooltip_text("Log with debugging information.")
640 self._debugLogPage = self._notebook.append_page(self._debugLogWidget, label)
641 self._notebook.set_current_page(self._debugLogPage)
642 else:
643 self._notebook.remove_page(self._debugLogPage)
644
645 def _buildLogWidget(self):
646 """Build the widget for the log."""
647 alignment = gtk.Alignment(xscale = 1.0, yscale = 1.0)
648
649 alignment.set_padding(padding_top = 8, padding_bottom = 8,
650 padding_left = 16, padding_right = 16)
[28]651
652 logScroller = gtk.ScrolledWindow()
[93]653 # FIXME: these should be constants in common
654 logScroller.set_policy(gtk.PolicyType.AUTOMATIC if pygobject
655 else gtk.POLICY_AUTOMATIC,
656 gtk.PolicyType.AUTOMATIC if pygobject
657 else gtk.POLICY_AUTOMATIC)
658 logScroller.set_shadow_type(gtk.ShadowType.IN if pygobject
659 else gtk.SHADOW_IN)
660 logView = gtk.TextView()
661 logView.set_editable(False)
662 logScroller.add(logView)
[28]663
664 logBox = gtk.VBox()
665 logBox.pack_start(logScroller, True, True, 0)
666 logBox.set_size_request(-1, 200)
667
[93]668 alignment.add(logBox)
[28]669
[93]670 return (alignment, logView)
[28]671
[93]672 def _writeLog(self, msg, logView):
[28]673 """Write the given message to the log."""
[93]674 buffer = logView.get_buffer()
[28]675 buffer.insert(buffer.get_end_iter(), msg)
[93]676 logView.scroll_mark_onscreen(buffer.get_insert())
[28]677
[81]678 def _quit(self, what = None, force = False):
[38]679 """Quit from the application."""
[81]680 if force:
681 result=RESPONSETYPE_YES
682 else:
[105]683 dialog = gtk.MessageDialog(parent = self._mainWindow,
684 type = MESSAGETYPE_QUESTION,
[81]685 buttons = BUTTONSTYPE_YES_NO,
686 message_format =
687 "Are you sure to quit the logger?")
[105]688 dialog.set_title(WINDOW_TITLE_BASE)
[81]689 result = dialog.run()
690 dialog.hide()
[76]691
692 if result==RESPONSETYPE_YES:
693 self._statusIcon.destroy()
694 return gtk.main_quit()
[38]695
[46]696 def _notebookPageSwitch(self, notebook, page, page_num):
697 """Called when the current page of the notebook has changed."""
698 if page_num==0:
[48]699 gobject.idle_add(self._wizard.grabDefault)
[46]700 else:
701 self._mainWindow.set_default(None)
Note: See TracBrowser for help on using the repository browser.