catapult.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632
  1. #!/usr/bin/env python
  2. # $Id$
  3. from PyQt4 import QtCore, QtGui
  4. import os.path, sys
  5. from openmsx_utils import tclEscape, EscapedStr
  6. #Is this a version for the openMSX-CD ?
  7. openmsxcd = False
  8. for i in sys.argv:
  9. if i == '--cd':
  10. openmsxcd = True
  11. # Application info must be set before the "preferences" module is imported.
  12. # Since many different modules might import "preferences", we perform the
  13. # setup before any Catapult modules are imported.
  14. app = QtGui.QApplication(sys.argv)
  15. app.setOrganizationName('openMSX Team')
  16. app.setOrganizationDomain('openmsx.org')
  17. app.setApplicationName('openMSX Catapult')
  18. if sys.platform == 'darwin':
  19. # Determine app folder location.
  20. appPath = os.path.abspath(sys.argv[0]).split('/')
  21. while appPath:
  22. pathElem = appPath.pop()
  23. if pathElem == 'Contents':
  24. break
  25. if appPath:
  26. appDir = '/'.join(appPath)
  27. # Change working dir to resource dir, so icons are loaded correctly.
  28. success = QtCore.QDir.setCurrent(appDir + '/Contents/Resources/')
  29. assert success
  30. from editconfig import configDialog
  31. from custom import docDir
  32. from machine import MachineManager
  33. from extension import ExtensionManager
  34. from mediamodel import MediaModel
  35. from connectormodel import ConnectorModel
  36. from media import MediaSwitcher
  37. from connectors import ConnectorPlugger
  38. from audio import AudioMixer
  39. from diskmanipulator import Diskmanipulator
  40. from cheatfinder import Cheatfinder
  41. from trainerselect import TrainerSelect
  42. from softwaredb import SoftwareDB
  43. from autorun import Autorun
  44. from savestatemanager import SaveStateManager
  45. from openmsx_control import ControlBridge, NotConfiguredException
  46. from paletteeditor import PaletteEditor
  47. from inputtext import InputText
  48. from player import PlayState
  49. from qt_utils import connect
  50. import settings
  51. from ui_main import Ui_MainWindow
  52. class MainWindow(QtGui.QMainWindow):
  53. # Colors used for different types of log messages:
  54. logStyle = {
  55. 'info': 0x000000,
  56. 'warning': 0xFF0000,
  57. 'command': 0x000080,
  58. 'default': 0x00FFFF, # selected for unknown levels
  59. }
  60. def __init__(self, bridge):
  61. QtGui.QMainWindow.__init__(self)
  62. self.__bridge = bridge
  63. self.__ui = ui = Ui_MainWindow()
  64. self.__connectorModel = connectorModel = ConnectorModel(bridge)
  65. self.openmsxcd = openmsxcd
  66. ui.setupUi(self)
  67. # Added stuff that at the moment will be exclusive to
  68. # the openMSX-CD
  69. if openmsxcd:
  70. ui.action_SoftwareDB = QtGui.QAction(self)
  71. ui.action_SoftwareDB.setObjectName("action_SoftwareDB")
  72. ui.action_SoftwareDB.setText(QtGui.QApplication.translate("MainWindow",
  73. "Software DB", None, QtGui.QApplication.UnicodeUTF8))
  74. ui.menuTools.addAction(ui.action_SoftwareDB)
  75. ui.action_Autorun = QtGui.QAction(self)
  76. ui.action_Autorun.setObjectName("action_Autorun")
  77. ui.action_Autorun.setText(QtGui.QApplication.translate("MainWindow",
  78. "Autorun dialog", None, QtGui.QApplication.UnicodeUTF8))
  79. ui.menuTools.addAction(ui.action_Autorun)
  80. # Resources that are loaded on demand.
  81. self.__machineDialog = None
  82. self.__extensionDialog = None
  83. self.__aboutDialog = None
  84. self.__assistentClient = None
  85. self.__logColours = dict(
  86. ( level, QtGui.QColor(
  87. (color >> 16) & 0xFF, (color >> 8) & 0xFF, color & 0xFF ) )
  88. for level, color in self.logStyle.iteritems()
  89. )
  90. connect(QtGui.qApp, 'lastWindowClosed()', self.closeConnection)
  91. # We have to let openMSX quit gracefully before quitting Catapult.
  92. QtGui.qApp.setQuitOnLastWindowClosed(False)
  93. # Register Tcl commands to intercept openMSX exit.
  94. # This should happen before SettingsManager is instantiated, since that
  95. # will register "unset renderer" and the exit interception should be
  96. # in place before the output window is opened.
  97. bridge.registerInitial(self.__interceptExit)
  98. self.__settingsManager = settingsManager = settings.SettingsManager(bridge)
  99. self.__extensionManager = extensionManager = ExtensionManager(
  100. self, ui, bridge
  101. )
  102. self.__machineManager = machineManager = MachineManager(
  103. self, ui, bridge
  104. )
  105. self.__mediaModel = mediaModel = MediaModel(bridge, machineManager)
  106. self.__diskmanipulator = Diskmanipulator(
  107. self, mediaModel, machineManager, bridge
  108. )
  109. self.__cheatfinder = Cheatfinder(bridge)
  110. self.__trainerselect = TrainerSelect(bridge)
  111. if openmsxcd:
  112. self.__softwaredb = SoftwareDB(bridge)
  113. self.__autorun = Autorun(self, settingsManager, bridge)
  114. self.__paletteeditor = PaletteEditor(bridge)
  115. self.__inputtext = InputText(bridge)
  116. self.__playState = PlayState(settingsManager, ui)
  117. self.__saveStateManager = SaveStateManager(bridge, self.__playState)
  118. self.__connectMenuActions(ui)
  119. bridge.logLine.connect(self.logLine)
  120. bridge.registerInitial(self.__setUpSettings)
  121. # full screen is a special setting, because we want to pop up a dialog
  122. # before letting the change take effect.
  123. settingsManager.registerSpecialSetting(
  124. 'fullscreen', self.__updateSpecialSettings
  125. )
  126. connect(ui.fullscreen, 'clicked(bool)', self.__goFullscreen)
  127. connect(ui.extensionButton, 'clicked()',
  128. extensionManager.chooseExtension)
  129. connect(ui.machineButton, 'clicked()', machineManager.chooseMachine)
  130. self.__mediaSwitcher = MediaSwitcher(
  131. ui, mediaModel, settingsManager, machineManager
  132. )
  133. self.__connectorPlugger = ConnectorPlugger(ui, connectorModel,
  134. settingsManager
  135. )
  136. self.__audioMixer = AudioMixer(ui, settingsManager, bridge)
  137. self.__frameRateTimer = QtCore.QTimer()
  138. self.__frameRateTimer.setInterval(2000)
  139. self.__frameRateLabel = QtGui.QLabel('')
  140. ui.statusbar.addWidget(self.__frameRateLabel)
  141. def __updateSpecialSettings(self, name, message):
  142. if name == 'fullscreen':
  143. self.__ui.fullscreen.setChecked(message in ('on', 'true', 'yes'))
  144. def __goFullscreen(self, value):
  145. if value:
  146. reply = QtGui.QMessageBox.warning(self,
  147. self.tr("Going fullscreen"),
  148. self.tr(
  149. "<p>Do you really want to go fullscreen?</p>"
  150. "<p>This will hide Catapult, so make sure"
  151. " that you know how to disable fullscreen"
  152. " later on!</p>"
  153. ),
  154. self.tr("&Cancel"),
  155. self.tr("Continue"))
  156. if reply == 0:
  157. self.__ui.fullscreen.setChecked(False)
  158. #TODO find out why we need to activate the
  159. #checbox twice before we see this dialog again
  160. #if we respond with 'Cancel'
  161. else:
  162. self.__bridge.sendCommandRaw('set fullscreen on')
  163. else:
  164. self.__bridge.sendCommandRaw('set fullscreen off')
  165. def __setUpSettings(self):
  166. '''Things that should be done after the connection is established
  167. This is mostly registering and connecting settings.
  168. '''
  169. # Some complex settings that need their UI elements to be configured...
  170. # we need to register and connect them here since we need to
  171. # have e.g. the sliders set to the correct minimum/maximum by
  172. # the openmsx_info command first (that's what I mean with
  173. # configuring the UI elements). Otherwise it is possible that
  174. # the setting will try to set the slider to a value not yet
  175. # allowed
  176. # Triggering an valuechanged signal that sets the openmsx to
  177. # the wrong value. This was the case when the noise was set to
  178. # 3.0 and the slider only went up until 0.99 since the
  179. # slider was not yet configured...
  180. # The same goes for practically all settings.
  181. settingsManager = self.__settingsManager
  182. ui = self.__ui
  183. # video settings
  184. settingsManager.registerSetting('gamma', settings.FloatSetting)
  185. settingsManager.connectSetting('gamma', ui.gammaSlider)
  186. settingsManager.connectSetting('gamma', ui.gammaSpinBox)
  187. settingsManager.registerSetting('brightness', settings.FloatSetting)
  188. settingsManager.connectSetting('brightness', ui.brightnessSlider)
  189. settingsManager.connectSetting('brightness', ui.brightnessSpinBox)
  190. settingsManager.registerSetting('contrast', settings.FloatSetting)
  191. settingsManager.connectSetting('contrast', ui.contrastSlider)
  192. settingsManager.connectSetting('contrast', ui.contrastSpinBox)
  193. settingsManager.registerSetting('noise', settings.FloatSetting)
  194. settingsManager.connectSetting('noise', ui.noiseSlider)
  195. settingsManager.connectSetting('noise', ui.noiseSpinBox)
  196. settingsManager.registerSetting('scanline', settings.IntegerSetting)
  197. settingsManager.connectSetting('scanline', ui.scanlineSlider)
  198. settingsManager.connectSetting('scanline', ui.scanlineSpinBox)
  199. settingsManager.registerSetting('blur', settings.IntegerSetting)
  200. settingsManager.connectSetting('blur', ui.blurSlider)
  201. settingsManager.connectSetting('blur', ui.blurSpinBox)
  202. settingsManager.registerSetting('glow', settings.IntegerSetting)
  203. settingsManager.connectSetting('glow', ui.glowSlider)
  204. settingsManager.connectSetting('glow', ui.glowSpinBox)
  205. settingsManager.registerSetting('scale_factor', settings.IntegerSetting)
  206. settingsManager.connectSetting('scale_factor', ui.scaleFactorSpinBox)
  207. settingsManager.registerSetting('deinterlace', settings.BooleanSetting)
  208. settingsManager.connectSetting('deinterlace', ui.deinterlace)
  209. settingsManager.registerSetting('limitsprites', settings.BooleanSetting)
  210. settingsManager.connectSetting('limitsprites', ui.limitsprites)
  211. settingsManager.registerSetting('scale_algorithm', settings.EnumSetting)
  212. settingsManager.connectSetting('scale_algorithm', ui.scalealgorithmComboBox)
  213. settingsManager.registerSetting('videosource', settings.EnumSetting)
  214. settingsManager.connectSetting('videosource', ui.videosourceComboBox)
  215. settingsManager.registerSetting('renderer', settings.EnumSetting)
  216. settingsManager.connectSetting('renderer', ui.rendererComboBox)
  217. settingsManager.registerSetting('display_deform', settings.EnumSetting)
  218. settingsManager.connectSetting('display_deform', ui.displaydeformComboBox)
  219. # misc settings
  220. settingsManager.registerSetting('speed', settings.IntegerSetting)
  221. settingsManager.connectSetting('speed', ui.speedSlider)
  222. settingsManager.connectSetting('speed', ui.speedSpinBox)
  223. connect(ui.normalSpeedButton, 'clicked()',
  224. lambda: settingsManager.restoreToDefault('speed'))
  225. settingsManager.registerSetting('throttle', settings.BooleanSetting)
  226. settingsManager.connectSetting('throttle', ui.limitSpeedCheckBox)
  227. settingsManager.registerSetting('fullspeedwhenloading',
  228. settings.BooleanSetting)
  229. settingsManager.connectSetting('fullspeedwhenloading',
  230. ui.fullSpeedWhenLoadingCheckBox)
  231. settingsManager.registerSetting('minframeskip', settings.IntegerSetting)
  232. settingsManager.connectSetting('minframeskip', ui.minFrameSkipSpinBox)
  233. connect(ui.resetMinFrameSkipButton, 'clicked()',
  234. lambda: settingsManager.restoreToDefault('minframeskip'))
  235. settingsManager.registerSetting('maxframeskip', settings.IntegerSetting)
  236. settingsManager.connectSetting('maxframeskip', ui.maxFrameSkipSpinBox)
  237. connect(ui.resetMaxFrameSkipButton, 'clicked()',
  238. lambda: settingsManager.restoreToDefault('maxframeskip'))
  239. settingsManager.registerSetting('z80_freq', settings.IntegerSetting)
  240. # TODO: Z80 frequency can change due to MSX software (when it's locked)
  241. # display the actual frequency when it's locked, not the setting
  242. # (but may require openMSX changes to notify about frequency change,
  243. # otherwise we would have to poll for "machine_info z80_freq")
  244. settingsManager.connectSetting('z80_freq', ui.Z80FrequencySpinBox)
  245. settingsManager.connectSetting('z80_freq', ui.Z80FrequencySlider)
  246. settingsManager.registerSetting('z80_freq_locked', settings.BooleanSetting)
  247. settingsManager.connectSetting('z80_freq_locked', ui.Z80FrequencyLockCheckBox)
  248. connect(ui.resetZ80FrequencyButton, 'clicked()',
  249. lambda: settingsManager.restoreToDefault('z80_freq'))
  250. settingsManager.registerSetting('r800_freq', settings.IntegerSetting)
  251. settingsManager.connectSetting('r800_freq', ui.R800FrequencySpinBox)
  252. settingsManager.connectSetting('r800_freq', ui.R800FrequencySlider)
  253. settingsManager.registerSetting('r800_freq_locked', settings.BooleanSetting)
  254. settingsManager.connectSetting('r800_freq_locked',
  255. ui.R800FrequencyLockCheckBox)
  256. connect(ui.resetR800FrequencyButton, 'clicked()',
  257. lambda: settingsManager.restoreToDefault('r800_freq'))
  258. # menu setting(s)
  259. settingsManager.registerSetting('save_settings_on_exit',
  260. settings.BooleanSetting)
  261. settingsManager.connectSetting('save_settings_on_exit',
  262. ui.action_AutoSaveSettings)
  263. ###### non standard settings
  264. # monitor type
  265. # TODO: settings implemented in TCL don't have a way to sync
  266. # back...
  267. def monitorTypeListReply(*words):
  268. combo = self.__ui.monitorTypeComboBox
  269. for word in sorted(words):
  270. combo.addItem(QtCore.QString(word.replace('_', ' ')))
  271. # hardcoding to start on normal, because this setting
  272. # cannot be saved anyway
  273. index = combo.findText('normal')
  274. combo.setCurrentIndex(index)
  275. self.__bridge.command('monitor_type', '-list')(
  276. monitorTypeListReply
  277. )
  278. def monitorTypeChanged(newType):
  279. self.__bridge.command(
  280. 'monitor_type', str(newType.replace(' ', '_'))
  281. )()
  282. connect(self.__ui.monitorTypeComboBox, 'activated(QString)',
  283. monitorTypeChanged
  284. )
  285. ###### other stuff
  286. connect(self.__frameRateTimer, 'timeout()',
  287. lambda: self.__bridge.command('openmsx_info', 'fps')(
  288. self.__updateFrameRateLabel, None
  289. )
  290. )
  291. self.__playState.getVisibleSetting().valueChanged.connect(
  292. self.__visibilityChanged
  293. )
  294. def __connectMenuActions(self, ui):
  295. '''Connect actions to methods.
  296. For some reason, on_*_triggered methods are called twice unless
  297. they have an @QtCore.pyqtSignature decoration. Unfortunately,
  298. we have to support Python 2.3, which does not have decoration.
  299. '''
  300. for action, func in (
  301. # The action is only triggered when Quit is selected from the menu,
  302. # not when the main application window is closed. Therefore we
  303. # unify both flows by closing the windows, which will indirectly
  304. # lead to a quit.
  305. ( ui.action_Quit, QtGui.qApp.closeAllWindows ),
  306. ( ui.action_SaveSettings, self.__saveSettings ),
  307. ( ui.action_SaveSettingsAs, self.__saveSettingsAs ),
  308. ( ui.action_LoadSettings, self.__loadSettings ),
  309. ( ui.action_QuickLoadState, self.__quickLoadState ),
  310. ( ui.action_QuickSaveState, self.__quickSaveState ),
  311. ( ui.action_LoadState, self.__loadState ),
  312. ( ui.action_SaveState, self.__saveState ),
  313. ( ui.action_EditConfiguration, configDialog.show ),
  314. ( ui.action_Diskmanipulator, self.__diskmanipulator.show ),
  315. ( ui.action_CheatFinder, self.__cheatfinder.show ),
  316. ( ui.action_TrainerSelect, self.__trainerselect.show ),
  317. ( ui.action_PaletteEditor, self.__paletteeditor.show ),
  318. ( ui.action_InputText, self.__inputtext.show ),
  319. ( ui.action_HelpSetup, self.showHelpSetup ),
  320. ( ui.action_HelpUser, self.showHelpUser ),
  321. ( ui.action_AboutCatapult, self.showAboutDialog ),
  322. ( ui.action_AboutQt, QtGui.qApp.aboutQt ),
  323. ):
  324. connect(action, 'triggered(bool)', func)
  325. if openmsxcd:
  326. for action, func in (
  327. ( ui.action_Autorun, self.__autorun.show ),
  328. ( ui.action_SoftwareDB, self.__softwaredb.show ),
  329. ):
  330. connect(action, 'triggered(bool)', func)
  331. def __interceptExit(self):
  332. '''Redefines the "exit" command so openMSX will stop instead of exit
  333. when the window is closed or the quit hotkey is used.
  334. '''
  335. # TODO: On Mac OS X, if the user selects Quit from the dock menu,
  336. # openMSX will be marked as not responding.
  337. # TODO: If the user quits openMSX with a hotkey, should that just
  338. # close the window or quit Catapult as well?
  339. # For example on Mac, we might use Cmd-Q to quit openMSX and
  340. # Catapult, while Cmd-W only closes the openMSX window.
  341. self.__bridge.sendCommandRaw('rename exit exit_process')
  342. self.__bridge.sendCommandRaw(
  343. 'proc exit {} { set ::renderer none ; set ::power off }'
  344. )
  345. def __updateFrameRateLabel(self, value):
  346. self.__frameRateLabel.setText(str(round(float(value), 1)) + " fps")
  347. def __visibilityChanged(self, value):
  348. if value:
  349. self.__frameRateTimer.start()
  350. # self.__frameRateTimer.stop() # uncomment to disable fps polling
  351. else:
  352. self.__frameRateTimer.stop()
  353. self.__frameRateLabel.setText('')
  354. def consoleReply(self, reply):
  355. if reply.endswith('\n'):
  356. reply = reply[ : -1]
  357. self.logLine('command', reply)
  358. def getPlayState(self):
  359. return self.__playState
  360. # Slots:
  361. #@QtCore.pyqtSignature('')
  362. def closeEvent(self, event):
  363. print " QtGui.QMainWindow.closeEvent(self, event)"
  364. QtGui.QMainWindow.closeEvent(self, event)
  365. @QtCore.pyqtSignature('')
  366. def close(self):
  367. # [Manuel] is the following log line correct??
  368. print " QtGui.QMainWindow.closeEvent(self, event)"
  369. QtGui.QMainWindow.close(self)
  370. @QtCore.pyqtSignature('')
  371. def on_playButton_clicked(self):
  372. self.__playState.setState(PlayState.play)
  373. @QtCore.pyqtSignature('')
  374. def on_pauseButton_clicked(self):
  375. self.__playState.setState(PlayState.pause)
  376. @QtCore.pyqtSignature('')
  377. def on_stopButton_clicked(self):
  378. self.__playState.setState(PlayState.stop)
  379. @QtCore.pyqtSignature('')
  380. def on_forwardButton_clicked(self):
  381. self.__playState.setState(PlayState.forward)
  382. @QtCore.pyqtSignature('')
  383. def on_resetButton_clicked(self):
  384. self.__bridge.command('reset')()
  385. @QtCore.pyqtSignature('')
  386. def on_consoleLineEdit_returnPressed(self):
  387. line = self.__ui.consoleLineEdit.text()
  388. self.logLine('command', '> %s' % line)
  389. self.__ui.consoleLineEdit.clear()
  390. self.__bridge.sendCommandRaw(line, self.consoleReply)
  391. def chooseMachine(self):
  392. dialog = self.__machineDialog
  393. if dialog is None:
  394. self.__machineDialog = dialog = QtGui.QDialog(
  395. self, QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint
  396. )
  397. # Setup UI made in Qt Designer.
  398. from ui_machine import Ui_Dialog
  399. ui = Ui_Dialog()
  400. ui.setupUi(dialog)
  401. dialog.show()
  402. dialog.raise_()
  403. dialog.activateWindow()
  404. def chooseExtension(self):
  405. dialog = self.__extensionDialog
  406. if dialog is None:
  407. self.__extensionDialog = dialog = QtGui.QDialog(
  408. self, QtCore.Qt.Dialog | QtCore.Qt.WindowTitleHint
  409. )
  410. # Setup UI made in Qt Designer.
  411. from ui_extension import Ui_Dialog
  412. ui = Ui_Dialog()
  413. ui.setupUi(dialog)
  414. dialog.show()
  415. dialog.raise_()
  416. dialog.activateWindow()
  417. @QtCore.pyqtSignature('QString, QString')
  418. def logLine(self, level, message):
  419. text = self.__ui.logText
  420. text.setTextColor(
  421. self.__logColours.get(str(level), self.__logColours['default'])
  422. )
  423. text.append(message)
  424. @QtCore.pyqtSignature('')
  425. def closeConnection(self):
  426. # wrap in lambda to avoid setting a builtin func as callback
  427. # which is not appreciated by the command method of the bridge
  428. self.__bridge.closeConnection(lambda: QtGui.qApp.quit())
  429. def __saveSettings(self):
  430. self.__bridge.command('save_settings')()
  431. def __saveSettingsAs(self):
  432. settingsFile = QtGui.QFileDialog.getSaveFileName(
  433. self.__ui.centralwidget, 'Select openMSX Settings File',
  434. QtCore.QDir.homePath(),
  435. 'openMSX Settings Files (*.xml);;All Files (*)',
  436. None #, 0
  437. )
  438. if settingsFile != '':
  439. self.__bridge.command('save_settings',
  440. EscapedStr(tclEscape(settingsFile)))(
  441. None,
  442. lambda message: self.__generalFailHandler(
  443. message, 'Problem Saving Settings'
  444. )
  445. )
  446. def __loadSettings(self):
  447. settingsFile = QtGui.QFileDialog.getOpenFileName(
  448. self.__ui.centralwidget, 'Select openMSX Settings File',
  449. QtCore.QDir.homePath(),
  450. 'openMSX Settings Files (*.xml);;All Files (*)',
  451. None #, 0
  452. )
  453. if settingsFile != '':
  454. self.__bridge.command('set', '__tmp', '$renderer;',
  455. 'load_settings',
  456. EscapedStr(tclEscape(settingsFile)) + ';',
  457. 'set', 'renderer', '$__tmp'
  458. )(
  459. None,
  460. lambda message: self.__generalFailHandler(
  461. message, 'Problem Loading Settings'
  462. )
  463. )
  464. def __loadSettingsFailedHandler(self, message):
  465. messageBox = QtGui.QMessageBox('Problem Loading Settings', message,
  466. QtGui.QMessageBox.Warning, 0, 0, 0,
  467. self.__ui.centralwidget
  468. )
  469. messageBox.show()
  470. def __quickLoadState(self):
  471. # TODO: when loading fails, while state was STOP,
  472. # you see a nasty flicker (openMSX window becomes
  473. # visible for a short amount of time). Fix this!
  474. # save old play state
  475. state = self.__playState.getState()
  476. # set to play *before* loading the state
  477. self.__playState.setState(PlayState.play)
  478. self.__bridge.command('loadstate')(
  479. None,
  480. lambda message: failHelper(message)
  481. )
  482. def failHelper(message):
  483. # failed, restore play state
  484. self.__playState.setState(state)
  485. self.__generalFailHandler(
  486. message, 'Problem quick-loading state'
  487. )
  488. def __quickSaveState(self):
  489. self.__bridge.command('savestate')(
  490. None,
  491. lambda message: self.__generalFailHandler(
  492. message, 'Problem quick-saving state'
  493. )
  494. )
  495. def __loadState(self):
  496. self.__saveStateManager.exec_('load')
  497. def __saveState(self):
  498. self.__saveStateManager.exec_('save')
  499. def __generalFailHandler(self, message, title):
  500. messageBox = QtGui.QMessageBox(title, message,
  501. QtGui.QMessageBox.Warning, 0, 0, 0,
  502. self.__ui.centralwidget
  503. )
  504. messageBox.show()
  505. def __getAssistentClient(self):
  506. if self.__assistentClient is None:
  507. from PyQt4.QtAssistant import QAssistantClient
  508. # Note: The string parameter is the path to look for the
  509. # Qt Assistent executable.
  510. # Empty string means use OS search path.
  511. # TODO: Is it safe to assume Qt Assistent is always in the path?
  512. # What happens if it is not?
  513. self.__assistentClient = QAssistantClient('')
  514. return self.__assistentClient
  515. @QtCore.pyqtSignature('')
  516. def showHelpSetup(self):
  517. print 'show Setup Guide'
  518. client = self.__getAssistentClient()
  519. # TODO: Make metadata documents to customize Qt Assistant for openMSX.
  520. # TODO: Get a reliable path (by guessing? from openMSX?).
  521. client.showPage(docDir + '/manual/setup.html')
  522. @QtCore.pyqtSignature('')
  523. def showHelpUser(self):
  524. print 'show User\'s Manual'
  525. client = self.__getAssistentClient()
  526. # TODO: Get a reliable path (by guessing? from openMSX?).
  527. client.showPage(docDir + '/manual/user.html')
  528. @QtCore.pyqtSignature('')
  529. def showAboutDialog(self):
  530. dialog = self.__aboutDialog
  531. if dialog is None:
  532. # TODO: An about dialog should not have minimize and maximize
  533. # buttons. Although I'm not asking Qt to show those, I still
  534. # get them. Maybe a misunderstanding between Qt and the
  535. # window manager (KWin)?
  536. self.__aboutDialog = dialog = QtGui.QDialog(
  537. self,
  538. QtCore.Qt.Dialog
  539. | QtCore.Qt.WindowTitleHint | QtCore.Qt.WindowSystemMenuHint
  540. )
  541. # Do not keep openMSX running just because of the About dialog.
  542. dialog.setAttribute(QtCore.Qt.WA_QuitOnClose, False)
  543. # Setup UI made in Qt Designer.
  544. from ui_about import Ui_Dialog
  545. ui = Ui_Dialog()
  546. ui.setupUi(dialog)
  547. dialog.show()
  548. dialog.raise_()
  549. dialog.activateWindow()
  550. #print '%X' % int(dialog.windowFlags())
  551. if __name__ == '__main__':
  552. controlBridge = ControlBridge()
  553. mainWindow = MainWindow(controlBridge)
  554. done = False
  555. while not done:
  556. try:
  557. controlBridge.openConnection()
  558. done = True
  559. except NotConfiguredException:
  560. configDialog.show(True) # block
  561. mainWindow.show()
  562. sys.exit(app.exec_())