media.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781
  1. # $Id$
  2. from PyQt4 import QtCore, QtGui
  3. from preferences import preferences
  4. from qt_utils import connect
  5. import settings
  6. from ipsselector import ipsDialog
  7. from mediamodel import Medium
  8. def parseMediaSlot(mediaSlot):
  9. '''Returns a tuple ( mediumType, identifier) that corresponds to the given
  10. media slot.
  11. '''
  12. assert mediaSlot is not None, 'Invalid media slot! (None)'
  13. assert mediaSlot != '', 'Invalid media slot! (emtpy)'
  14. if mediaSlot == 'cassetteplayer':
  15. return 'cassette', None
  16. else:
  17. return mediaSlot[ : -1], mediaSlot[-1]
  18. class MediaSwitcher(QtCore.QObject):
  19. def __init__(self, ui, mediaModel, settingsManager, machineManager):
  20. QtCore.QObject.__init__(self)
  21. self.__mediaModel = mediaModel
  22. self.__settingsManager = settingsManager
  23. self.__machineManager = machineManager
  24. self.__ui = ui
  25. self.__mediaSlot = None
  26. self.__cartPageInited = False
  27. # Connect to media model:
  28. ui.mediaList.setModel(mediaModel)
  29. mediaModel.dataChanged.connect(self.mediaPathChanged)
  30. mediaModel.mediaSlotRemoved.connect(self.setInfoPage)
  31. mediaModel.connected.connect(self.__connectSettings)
  32. # Connect view:
  33. connect(
  34. ui.mediaList.selectionModel(),
  35. 'currentChanged(QModelIndex, QModelIndex)',
  36. self.updateMedia
  37. )
  38. connect(
  39. ui.mediaList, 'doubleClicked(QModelIndex)',
  40. self.browseMedia
  41. )
  42. connect(
  43. ui.mediaList, 'entered(QModelIndex)',
  44. self.showMediaToolTip
  45. )
  46. # Connect signals of media panels:
  47. # It is essential to keep the references, otherwise the classes are
  48. # garbage collected even though they have signal-slot connections
  49. # attached to them.
  50. self.__handlers = [
  51. handler(ui, self)
  52. for handler in ( DiskHandler, CartHandler,
  53. CassetteHandler, HarddiskHandler, CDROMHandler )
  54. ]
  55. def __connectSettings(self):
  56. settingsManager = self.__settingsManager
  57. ui = self.__ui
  58. settingsManager.registerSetting('autoruncassettes', settings.BooleanSetting)
  59. settingsManager.connectSetting('autoruncassettes',
  60. ui.autoRunCassettesCheckBox)
  61. def __getHandlerByMediumType(self, mediumType):
  62. for handler in self.__handlers:
  63. if handler.mediumType == mediumType:
  64. return handler
  65. assert False, 'No handler found for mediumType "%s"' % mediumType
  66. def __getPageBySlot(self, mediaSlot):
  67. mediumType, identifier_ = parseMediaSlot(mediaSlot.getName())
  68. # Look up page widget for this mediumType.
  69. return getattr(self.__ui, mediumType + 'Page')
  70. def __getHandlerBySlot(self, mediaSlot):
  71. mediumType, identifier_ = parseMediaSlot(mediaSlot.getName())
  72. return self.__getHandlerByMediumType(mediumType)
  73. def __updateMediaPage(self, mediaSlot):
  74. mediumType, identifier = parseMediaSlot(mediaSlot.getName())
  75. handler = self.__getHandlerByMediumType(mediumType)
  76. # Initialise the UI page for this mediumType.
  77. handler.updatePage(identifier)
  78. @QtCore.pyqtSignature('QModelIndex')
  79. def updateMedia(self, index):
  80. oldMediaSlot = self.__mediaSlot
  81. # Find out which media entry has become active.
  82. mediaSlotName = str(index.data(QtCore.Qt.UserRole).toString())
  83. # prevent problems due to race conditions when removing slots:
  84. if mediaSlotName == '':
  85. return
  86. slot = self.__mediaModel.getMediaSlotByName(
  87. mediaSlotName, self.__machineManager.getCurrentMachineId()
  88. )
  89. #print '***********'
  90. #print 'mediaslot has currently become active: ', slot
  91. if oldMediaSlot is not None and oldMediaSlot.getName() == slot.getName():
  92. return
  93. if oldMediaSlot is not None:
  94. self.__getHandlerBySlot(oldMediaSlot).signalSetInvisible()
  95. self.__mediaSlot = slot
  96. self.__updateMediaPage(slot)
  97. # Switch page.
  98. self.__ui.mediaStack.setCurrentWidget(self.__getPageBySlot(slot))
  99. self.__getHandlerBySlot(slot).signalSetVisible()
  100. @QtCore.pyqtSignature('QModelIndex')
  101. def showMediaToolTip(self, index):
  102. # Find out which media entry has become active.
  103. mediaSlotName = str(index.data(QtCore.Qt.UserRole).toString())
  104. text = ''
  105. if mediaSlotName != '':
  106. slot = self.__mediaModel.getMediaSlotByName(
  107. mediaSlotName, self.__machineManager.getCurrentMachineId()
  108. )
  109. if slot.getMedium() != None:
  110. text = slot.getMedium().getPath()
  111. self.__ui.mediaList.setToolTip(text)
  112. @QtCore.pyqtSignature('QModelIndex')
  113. def browseMedia(self, index):
  114. # Find out which media entry has become active.
  115. mediaSlotName = str(index.data(QtCore.Qt.UserRole).toString())
  116. mediumType, identifier_ = parseMediaSlot(mediaSlotName)
  117. handler = self.__getHandlerByMediumType(mediumType)
  118. handler.browseImage()
  119. @QtCore.pyqtSignature('QModelIndex, QModelIndex')
  120. def mediaPathChanged(
  121. self, topLeft, bottomRight
  122. # pylint: disable-msg=W0613
  123. # TODO: We use the fact that we know MediaModel will only mark
  124. # one item changed at a time. This is not correct in general.
  125. ):
  126. index = topLeft
  127. mediaSlotName = str(index.data(QtCore.Qt.UserRole).toString())
  128. if self.__mediaSlot is not None and \
  129. self.__mediaSlot.getName() == mediaSlotName and \
  130. mediaSlotName != '':
  131. self.__updateMediaPage(self.__mediaSlot)
  132. def setInfoPage(self):
  133. # TODO: this is called for each media hardware that is added or removed,
  134. # since switching machines will sent this event for each and
  135. # every drive/hd/cd/... this will be called several times in a row
  136. # do we need to handle this in a better way?
  137. if self.__mediaSlot is not None:
  138. self.__getHandlerBySlot(self.__mediaSlot).signalSetInvisible()
  139. self.__ui.mediaStack.setCurrentWidget(self.__ui.infoPage)
  140. self.__ui.mediaList.selectionModel().clear()
  141. self.__mediaSlot = None
  142. def insertMedium(self, medium):
  143. '''Sets a new medium for the currently selected slot.
  144. '''
  145. self.__mediaSlot.setMedium(medium,
  146. self.__mediaChangeErrorHandler
  147. )
  148. def __mediaChangeErrorHandler(self, message):
  149. messageBox = QtGui.QMessageBox('Problem changing media', message,
  150. QtGui.QMessageBox.Warning, 0, 0, 0,
  151. self.__ui.centralwidget
  152. )
  153. messageBox.show()
  154. def getMedium(self):
  155. return self.__mediaSlot.getMedium()
  156. def getSlot(self):
  157. return self.__mediaSlot
  158. def getRomTypes(self):
  159. return self.__mediaModel.getRomTypes()
  160. class MediaHandler(QtCore.QObject):
  161. '''Base class for handling media stuff.
  162. The purpose is to make it easy to add a new media type, by
  163. only implementing/overriding what is specific for that new type
  164. in a specialized class.
  165. '''
  166. mediumType = None
  167. browseTitle = None
  168. imageSpec = None
  169. emptyPathDesc = None
  170. def __init__(self, ui, switcher):
  171. QtCore.QObject.__init__(self)
  172. self._ui = ui
  173. self._switcher = switcher
  174. # Look up UI elements.
  175. self._ejectButton = getattr(ui, self.mediumType + 'EjectButton', None)
  176. self._browseButton = getattr(ui, self.mediumType + 'BrowseImageButton')
  177. self._historyBox = getattr(ui, self.mediumType + 'HistoryBox')
  178. self._mediaLabel = getattr(ui, self.mediumType + 'Label')
  179. self._descriptionLabel = getattr(ui, self.mediumType + 'DescriptionLabel')
  180. # Load history.
  181. history = preferences.getList(self.mediumType + '/history')
  182. self._historyBox.addItems(history)
  183. # On OS X, the top item of the history is automatically put into
  184. # the edit box; this is not what we want, so we clear it.
  185. self._historyBox.clearEditText()
  186. # Connect signals.
  187. if (self._ejectButton):
  188. connect(self._ejectButton, 'clicked()', self.eject)
  189. connect(self._browseButton, 'clicked()', self.browseImage)
  190. connect(self._historyBox, 'activated(QString)', self._pathSelected)
  191. connect(self._historyBox.lineEdit(), 'editingFinished()', self.edited)
  192. def _pathSelected(self, path):
  193. print 'selected:', path or '<nothing>'
  194. if not path:
  195. return
  196. # Make sure the passed path is the current path in the UI (e.g.
  197. # after browse)
  198. self._historyBox.lineEdit().setText(path)
  199. self._insertMediumFromCurrentValues()
  200. def _createMediumFromCurrentDialog(self):
  201. '''Reads out the values from the current controls and composes the \
  202. proper media object from it.
  203. '''
  204. path = unicode(self._historyBox.currentText())
  205. medium = Medium.create(self.mediumType, path)
  206. return medium
  207. def _insertMediumFromCurrentValues(self):
  208. '''Tells the model to insert the medium defined by the current controls.
  209. '''
  210. medium = self._createMediumFromCurrentDialog()
  211. self._addToHistory(medium)
  212. # Update the model.
  213. self._switcher.insertMedium(medium)
  214. def _addToHistory(self, medium):
  215. path = medium.getPath()
  216. historyBox = self._historyBox
  217. # Insert path at the top of the list.
  218. historyBox.insertItem(0, path)
  219. historyBox.setCurrentIndex(0)
  220. # Remove duplicates of the path from the history.
  221. index = 1
  222. while index < historyBox.count():
  223. if historyBox.itemText(index) == path:
  224. historyBox.removeItem(index)
  225. else:
  226. index += 1
  227. # Persist history.
  228. history = QtCore.QStringList()
  229. for index in range(historyBox.count()):
  230. history.append(historyBox.itemText(index))
  231. preferences[self.mediumType + '/history'] = history
  232. def eject(self):
  233. '''Removes the currently inserted medium.
  234. '''
  235. self._historyBox.clearEditText()
  236. self._switcher.insertMedium(None)
  237. def edited(self):
  238. '''Inserts the medium specified in the combobox line edit.
  239. '''
  240. self._pathSelected(self._historyBox.lineEdit().text())
  241. def browseImage(self):
  242. self._pathSelected(QtGui.QFileDialog.getOpenFileName(
  243. self._ui.mediaStack, self.browseTitle,
  244. self._historyBox.itemText(0) or QtCore.QDir.homePath(),
  245. self.imageSpec, None #, 0
  246. ))
  247. def updatePage(self, identifier):
  248. medium = self._switcher.getMedium()
  249. if (self._ejectButton):
  250. self._ejectButton.setDisabled(medium is None)
  251. self._mediaLabel.setText(self._getLabelText(identifier))
  252. if medium:
  253. path = medium.getPath()
  254. else:
  255. path = ''
  256. fileInfo = QtCore.QFileInfo(path)
  257. if path == '':
  258. description = self.emptyPathDesc
  259. elif fileInfo.isDir():
  260. description = self._getDirDesc(fileInfo)
  261. elif fileInfo.isFile():
  262. lastDot = path.rfind('.')
  263. if lastDot == -1:
  264. ext = None
  265. else:
  266. ext = path[lastDot + 1 : ].lower()
  267. description = self._getFileDesc(fileInfo, ext)
  268. elif fileInfo.exists():
  269. description = 'Special file node'
  270. else:
  271. description = 'Not found'
  272. # TODO: Display "(read only)" somewhere if the media is
  273. # read only for some reason:
  274. # - image type that openMSX cannot write (XSA)
  275. # - image file that is read-only on host file system
  276. # I guess it's best if openMSX detects and reports this.
  277. # The "mediaX" commands return a flag "readonly", but updates
  278. # do not include flags.
  279. self._descriptionLabel.setText(description)
  280. self._historyBox.lineEdit().setText(path)
  281. self._historyBox.lineEdit().setToolTip(path)
  282. def _getLabelText(self, identifier):
  283. raise NotImplementedError
  284. def _getFileDesc(self, fileInfo, ext):
  285. raise NotImplementedError
  286. def _getDirDesc(self, dummy):
  287. # there's a default implementation in case
  288. # dirs are not supported
  289. return 'Not found'
  290. def signalSetVisible(self):
  291. '''Called when this page has become visible.
  292. '''
  293. # default implementation does nothing
  294. return
  295. def signalSetInvisible(self):
  296. '''Called when this page has become invisible
  297. '''
  298. # default implementation does nothing
  299. return
  300. class PatchableMediaHandler(MediaHandler):
  301. # pylint: disable-msg=W0223
  302. '''Baseclass of a Mediahandler that supports IPS patches which should not
  303. be instantiated. (Hence we do not implement abstract methods of the baseclass.)
  304. '''
  305. def __init__(self, ui, switcher):
  306. MediaHandler.__init__(self, ui, switcher)
  307. # Look up UI elements.
  308. self._IPSButton = getattr(ui, self.mediumType + 'IPSButton', None)
  309. # Connect signals.
  310. connect(self._IPSButton, 'clicked()', self._IPSButtonClicked)
  311. def _createMediumFromCurrentDialog(self):
  312. baseMedium = MediaHandler._createMediumFromCurrentDialog(self)
  313. return baseMedium.copyWithNewPatchList(ipsDialog.getIPSList())
  314. def updatePage(self, identifier):
  315. MediaHandler.updatePage(self, identifier)
  316. medium = self._switcher.getMedium()
  317. if medium:
  318. patchList = medium.getIpsPatchList()
  319. else:
  320. patchList = []
  321. ipsDialog.fill(patchList)
  322. def _IPSButtonClicked(self):
  323. medium = self._switcher.getMedium()
  324. assert medium is not None, 'Click on IPS button without medium'
  325. if ipsDialog.exec_(self._IPSButton) == QtGui.QDialog.Accepted:
  326. self._insertMediumFromCurrentValues()
  327. class DiskHandler(PatchableMediaHandler):
  328. mediumType = 'disk'
  329. browseTitle = 'Select Disk Image'
  330. imageSpec = 'Disk Images (*.dsk *.di? *.xsa *.zip *.gz);;All Files (*)'
  331. emptyPathDesc = 'No disk in drive'
  332. def __init__(self, ui, switcher):
  333. PatchableMediaHandler.__init__(self, ui, switcher)
  334. # Look up UI elements.
  335. self._browseDirButton = ui.diskBrowseDirectoryButton
  336. # Connect signals.
  337. connect(self._browseDirButton, 'clicked()', self.browseDirectory)
  338. def updatePage(self, identifier):
  339. PatchableMediaHandler.updatePage(self, identifier)
  340. medium = self._switcher.getMedium()
  341. if medium is None:
  342. self._ui.diskIPSLabel.setDisabled(True)
  343. self._IPSButton.setDisabled(True)
  344. amount = 0
  345. else:
  346. self._ui.diskIPSLabel.setEnabled(True)
  347. self._IPSButton.setEnabled(True)
  348. amount = len(medium.getIpsPatchList())
  349. self._ui.diskIPSLabel.setText('(' + str(amount) + ' selected)')
  350. def browseDirectory(self):
  351. self._pathSelected(QtGui.QFileDialog.getExistingDirectory(
  352. self._ui.mediaStack, 'Select Directory',
  353. self._historyBox.itemText(0) or QtCore.QDir.homePath()
  354. ))
  355. def _getLabelText(self, identifier):
  356. return 'Disk Drive %s' % identifier.upper()
  357. def _getFileDesc(self, fileInfo, ext):
  358. if ext in ('dsk', 'di1', 'di2'):
  359. description = 'Raw disk image'
  360. size = fileInfo.size()
  361. if size != 0:
  362. description += ' of %dkB' % (size / 1024)
  363. elif ext in ('xsa', 'zip', 'gz'):
  364. description = 'Compressed disk image'
  365. else:
  366. description = 'Disk image of unknown type'
  367. return description
  368. def _getDirDesc(self, fileInfo):
  369. return 'Directory as disk (%d entries)' % (
  370. fileInfo.dir().count()
  371. )
  372. class CartHandler(PatchableMediaHandler):
  373. mediumType = 'cart'
  374. browseTitle = 'Select ROM Image'
  375. imageSpec = 'ROM Images (*.rom *.ri *.zip *.gz);;All Files (*)'
  376. emptyPathDesc = 'No cartridge in slot'
  377. def __init__(self, ui, switcher):
  378. PatchableMediaHandler.__init__(self, ui, switcher)
  379. self.__cartPageInited = False
  380. historyBox = self._historyBox
  381. mapperTypeHistory = preferences.getList(
  382. self.mediumType + 'mappertype/history'
  383. )
  384. # some backwards compatibility code:
  385. tooLittleItems = historyBox.count() - mapperTypeHistory.count()
  386. for dummy in xrange(tooLittleItems):
  387. mapperTypeHistory.append('Auto Detect')
  388. # fill our mapper type data dict
  389. self.__mapperTypeData = {}
  390. index = 0
  391. while index < historyBox.count():
  392. self.__mapperTypeData[
  393. unicode(historyBox.itemText(index))
  394. ] = mapperTypeHistory[index]
  395. index += 1
  396. # Look up UI elements.
  397. self._mapperTypeCombo = ui.mapperTypeCombo
  398. # Connect signals.
  399. connect(self._mapperTypeCombo, 'activated(QString)',
  400. self.__mapperTypeSelected)
  401. def _createMediumFromCurrentDialog(self):
  402. baseMedium = PatchableMediaHandler._createMediumFromCurrentDialog(self)
  403. medium = Medium.create(
  404. self.mediumType, baseMedium.getPath(),
  405. baseMedium.getIpsPatchList(),
  406. str(self._mapperTypeCombo.currentText())
  407. )
  408. return medium
  409. def _pathSelected(self, path):
  410. print 'selected:', path or '<nothing>'
  411. if not path:
  412. return
  413. path = unicode(path)
  414. if path in self.__mapperTypeData:
  415. historyMapperType = self.__mapperTypeData[path]
  416. # restore mapper type from previous entry
  417. index = self._ui.mapperTypeCombo.findText(historyMapperType)
  418. self._ui.mapperTypeCombo.setCurrentIndex(index)
  419. PatchableMediaHandler._pathSelected(self, path)
  420. def _addToHistory(self, medium):
  421. PatchableMediaHandler._addToHistory(self, medium)
  422. path = medium.getPath()
  423. historyBox = self._historyBox
  424. self.__mapperTypeData[path] = medium.getMapperType()
  425. # Persist history (of mapper type).
  426. mapperTypeHistory = QtCore.QStringList()
  427. for index in range(historyBox.count()):
  428. mapperTypeHistory.append(self.__mapperTypeData[
  429. unicode(historyBox.itemText(index))
  430. ])
  431. preferences[self.mediumType + 'mappertype/history'] = mapperTypeHistory
  432. def updatePage(self, identifier):
  433. PatchableMediaHandler.updatePage(self, identifier)
  434. if not self.__cartPageInited:
  435. # the next query might be empty, if it happens too soon
  436. mapperTypes = self._switcher.getRomTypes()
  437. if len(mapperTypes) != 0:
  438. self.__cartPageInited = True
  439. for item in mapperTypes:
  440. self._ui.mapperTypeCombo.addItem(QtCore.QString(item))
  441. else:
  442. print 'Interesting! We are preventing a race\
  443. condition here!'
  444. # set the mappertype combo to the proper value
  445. medium = self._switcher.getMedium()
  446. if medium is None:
  447. # mapper
  448. self._ui.mapperTypeCombo.setDisabled(True)
  449. index = self._ui.mapperTypeCombo.findText('Auto Detect')
  450. self._ui.mapperTypeCombo.setCurrentIndex(index)
  451. # patchlist
  452. self._ui.cartIPSLabel.setDisabled(True)
  453. self._IPSButton.setDisabled(True)
  454. amount = 0
  455. else:
  456. # mapper
  457. self._ui.mapperTypeCombo.setEnabled(True)
  458. mapperType = medium.getMapperType()
  459. index = self._ui.mapperTypeCombo.findText(mapperType)
  460. self._ui.mapperTypeCombo.setCurrentIndex(index)
  461. # patchlist
  462. self._ui.cartIPSLabel.setEnabled(True)
  463. self._IPSButton.setEnabled(True)
  464. amount = len(medium.getIpsPatchList())
  465. self._ui.cartIPSLabel.setText('(' + str(amount) + ' selected)')
  466. def __mapperTypeSelected(self, dummy):
  467. # We read it back from the combobox, so we don't need the
  468. # mapperType param here
  469. self._insertMediumFromCurrentValues()
  470. def _getLabelText(self, identifier):
  471. return 'Cartridge Slot %s' % identifier.upper()
  472. def _getFileDesc(self, fileInfo, ext):
  473. if ext in ('rom', 'ri'):
  474. description = 'ROM image'
  475. size = fileInfo.size()
  476. if size != 0:
  477. description += ' of %dkB' % (size / 1024)
  478. megabits = size / 1024 / 128
  479. if megabits == 1:
  480. description += ' (MegaROM)'
  481. elif megabits > 1:
  482. description += ' (%d MegaROM)' % megabits
  483. elif ext in ('zip', 'gz'):
  484. description = 'Compressed ROM image'
  485. else:
  486. description = 'ROM image of unknown type'
  487. return description
  488. class CassetteHandler(MediaHandler):
  489. mediumType = 'cassette'
  490. browseTitle = 'Select Cassette Image'
  491. imageSpec = 'Cassette Images (*.cas *.wav *.zip *.gz);;All Files (*)'
  492. emptyPathDesc = 'No cassette in deck'
  493. play = 'play'
  494. rewind = 'rewind'
  495. stop = 'stop'
  496. record = 'record'
  497. def __init__(self, ui, switcher):
  498. MediaHandler.__init__(self, ui, switcher)
  499. # Look up UI elements.
  500. self.__pollTimer = QtCore.QTimer()
  501. self.__pollTimer.setInterval(500)
  502. self.__isVisible = False
  503. # Connect signals.
  504. connect(ui.tapePlayButton, 'clicked()', self.__playButtonClicked)
  505. connect(ui.tapeRewindButton, 'clicked()', self.__rewindButtonClicked)
  506. connect(ui.tapeStopButton, 'clicked()', self.__stopButtonClicked)
  507. connect(ui.tapeRecordButton, 'clicked()', self.__recordButtonClicked)
  508. connect(self.__pollTimer, 'timeout()', self.__queryTimes)
  509. self.__buttonMap = {
  510. self.play: ui.tapePlayButton,
  511. self.rewind: ui.tapeRewindButton,
  512. self.stop: ui.tapeStopButton,
  513. self.record: ui.tapeRecordButton,
  514. }
  515. def updatePage(self, identifier):
  516. MediaHandler.updatePage(self, identifier)
  517. medium = self._switcher.getMedium()
  518. if medium:
  519. length = medium.getLength()
  520. else:
  521. self.__updateTapePosition(0)
  522. length = 0
  523. self.__updateTapeLength(length)
  524. self._ui.tapeTime.setDisabled(medium is None)
  525. self._ui.tapeLength.setDisabled(medium is None)
  526. deck = self._switcher.getSlot()
  527. self.__updateButtonState(deck.getState())
  528. def __updateButtonState(self, newState):
  529. for state, button in self.__buttonMap.iteritems():
  530. button.setChecked(newState == state)
  531. if newState in ['play', 'record'] and self.__isVisible:
  532. self.__pollTimer.start()
  533. if newState == 'stop':
  534. self.__pollTimer.stop()
  535. if self.__isVisible:
  536. self.__queryTimes() # make sure end time is correct
  537. def __playButtonClicked(self):
  538. path = self._historyBox.currentText()
  539. if path == '':
  540. self.browseImage()
  541. else:
  542. deck = self._switcher.getSlot()
  543. deck.play(self.__errorHandler)
  544. # prevent toggling behaviour of play button:
  545. self.__updateButtonState(deck.getState())
  546. def __rewindButtonClicked(self):
  547. deck = self._switcher.getSlot()
  548. deck.rewind(self.__errorHandler)
  549. def __stopButtonClicked(self):
  550. # restore button state (this is actually a 'readonly' button)
  551. deck = self._switcher.getSlot()
  552. self.__updateButtonState(deck.getState())
  553. def __recordButtonClicked(self):
  554. filename = QtGui.QFileDialog.getSaveFileName(
  555. None, 'Enter New File for Cassette Image',
  556. QtCore.QDir.homePath(),
  557. 'Cassette Images (*.wav);;All Files (*)',
  558. None #, 0
  559. )
  560. deck = self._switcher.getSlot()
  561. if filename == '':
  562. self.__updateButtonState(deck.getState())
  563. else:
  564. self.__updateTapeLength(0)
  565. deck.record(filename, self.__errorHandler)
  566. def __errorHandler(self, message):
  567. messageBox = QtGui.QMessageBox('Cassette deck problem', message,
  568. QtGui.QMessageBox.Warning, 0, 0, 0,
  569. self._ui.tapeStopButton
  570. )
  571. messageBox.show()
  572. deck = self._switcher.getSlot()
  573. self.__updateButtonState(deck.getState())
  574. def __updateTapeLength(self, length):
  575. zeroTime = QtCore.QTime(0, 0, 0)
  576. time = zeroTime.addSecs(round(float(length)))
  577. self._ui.tapeLength.setTime(time)
  578. def __updateTapePosition(self, position):
  579. deck = self._switcher.getSlot()
  580. if not deck: # can happen due to race conditions
  581. return
  582. zeroTime = QtCore.QTime(0, 0, 0)
  583. time = zeroTime.addSecs(round(float(position)))
  584. self._ui.tapeTime.setTime(time)
  585. # for now, we can have this optimization:
  586. if (deck.getState() == 'record'):
  587. self.__updateTapeLength(position)
  588. def __queryTimes(self):
  589. #medium = self._switcher.getMedium()
  590. # don't do something like this for now, but use the optimization
  591. # that length == position when recording, see above
  592. # deck = self._switcher.getSlot()
  593. # if (deck.getState() == 'record'):
  594. # medium.getTapeLength(self.__updateTapeLength,
  595. # self.__errorHandler
  596. # )
  597. self._switcher.getSlot().getPosition(self.__updateTapePosition,
  598. # errors can occur if cassetteplayer got removed
  599. lambda message: self.__updateTapePosition(0)
  600. )
  601. def signalSetVisible(self):
  602. assert self.__isVisible == False, 'Um, we already were visible!?'
  603. self.__isVisible = True
  604. # start timer in case we are in play or record mode
  605. deck = self._switcher.getSlot()
  606. state = deck.getState()
  607. if state in ['play', 'record']:
  608. self.__pollTimer.start()
  609. self._switcher.getSlot().stateChanged.connect(self.__updateButtonState)
  610. def signalSetInvisible(self):
  611. assert self.__isVisible == True, 'Um, we were not even visible!?'
  612. self.__isVisible = False
  613. # always stop timer
  614. self.__pollTimer.stop()
  615. self._switcher.getSlot().stateChanged.disconnect(self.__updateButtonState)
  616. def _getLabelText(self, dummy):
  617. return 'Cassette Deck'
  618. def _getFileDesc(self, dummy, ext):
  619. if ext == 'cas':
  620. description = 'Cassette image in CAS format'
  621. elif ext == 'wav':
  622. description = 'Raw cassette image'
  623. elif ext in ('zip', 'gz'):
  624. description = 'Compressed cassette image'
  625. else:
  626. description = 'Cassette image of unknown type'
  627. return description
  628. class HarddiskHandler(MediaHandler):
  629. mediumType = 'hd'
  630. browseTitle = 'Select Hard Disk Image'
  631. imageSpec = 'Hard Disk Images (*.dsk *.zip *.gz);;All Files (*)'
  632. emptyPathDesc = 'No hard disk in drive'
  633. def _getLabelText(self, identifier):
  634. return 'Hard Disk Drive %s' % identifier.upper()
  635. def _getFileDesc(self, fileInfo, ext):
  636. if ext == 'dsk':
  637. description = 'Raw hard disk image'
  638. size = fileInfo.size()
  639. if size != 0:
  640. description += ' of %dMB' % (size / 1024 / 1024)
  641. elif ext in ('zip', 'gz'):
  642. description = 'Compressed hard disk image'
  643. else:
  644. description = 'Hard disk image of unknown type'
  645. return description
  646. class CDROMHandler(MediaHandler):
  647. mediumType = 'cd'
  648. browseTitle = 'Select CD-ROM Image'
  649. imageSpec = 'CD-ROM Images (*.iso *.zip *.gz);;All Files (*)'
  650. emptyPathDesc = 'No CD-ROM in drive'
  651. def _getLabelText(self, identifier):
  652. return 'CD-ROM Drive %s' % identifier.upper()
  653. def _getFileDesc(self, fileInfo, ext):
  654. if ext == 'iso':
  655. description = 'ISO CD-ROM image'
  656. size = fileInfo.size()
  657. if size != 0:
  658. description += ' of %dMB' % (size / 1024 / 1024)
  659. elif ext in ('zip', 'gz'):
  660. description = 'Compressed CD-ROM image'
  661. else:
  662. description = 'CD-ROM image of unknown type'
  663. return description