connectormodel.py 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  1. # $Id$
  2. from PyQt4 import QtCore
  3. from bisect import bisect
  4. from qt_utils import QtSignal, Signal
  5. # This is a utility class that can be used to emit a signal
  6. # when the internal counter reaches zero. Use it to track
  7. # if the last callback has been received
  8. # Maybe it should be moved to openmsx_utils.py or so.
  9. class ReadyCounter(object):
  10. def __init__(self, signal):
  11. self.__counter = 0
  12. self.__signal = signal
  13. def incr(self):
  14. self.__counter = self.__counter + 1
  15. def decr(self):
  16. self.__counter = self.__counter - 1
  17. assert self.__counter >= 0
  18. if (self.__counter == 0):
  19. self.__signal.emit()
  20. class ConnectorModel(QtCore.QAbstractListModel):
  21. dataChanged = QtSignal('QModelIndex', 'QModelIndex')
  22. initialized = Signal()
  23. def __init__(self, bridge):
  24. QtCore.QAbstractListModel.__init__(self)
  25. self.__bridge = bridge
  26. self.__connectors = []
  27. self.__connectorClasses = {}
  28. self.__connectorDescriptions = {}
  29. self.__pluggableClasses = {}
  30. self.__pluggableDescriptions = {'--empty--': 'Unplugged'}
  31. bridge.registerInitial(self.__updateAll)
  32. bridge.registerUpdate('plug', self.__connectorPlugged)
  33. bridge.registerUpdate('connector', self.__updateConnectorList)
  34. self.__readyCounter = ReadyCounter(self.initialized)
  35. def __updateAll(self):
  36. # TODO: The idea of the name "updateAll" was to be able to deal with
  37. # openMSX crashes. So, we should go back to knowing nothing about
  38. # the openMSX state.
  39. #self.__connectors = []
  40. # Query connectors.
  41. self.__bridge.command('machine_info', 'connector')(
  42. self.__connectorListReply
  43. )
  44. self.__bridge.command('machine_info', 'pluggable')(
  45. self.__pluggableListReply
  46. )
  47. def __connectorListReply(self, *connectors):
  48. '''Callback to list the initial connectors of a particular type.
  49. '''
  50. if len(connectors) == 0:
  51. return
  52. for connector in connectors:
  53. self.__connectorAdded(connector)
  54. self.__readyCounter.incr()
  55. self.__bridge.command('machine_info', 'connectionclass', connector)(
  56. lambda connectorClass, connector = connector:
  57. self.__connectorClassReply(connector, connectorClass)
  58. )
  59. def __pluggableListReply(self, *pluggables):
  60. '''Callback to list the initial pluggables of a particular type.
  61. '''
  62. if len(pluggables) == 0:
  63. return
  64. for pluggable in pluggables:
  65. self.__bridge.command('machine_info', 'pluggable', pluggable)(
  66. lambda description, pluggable = pluggable:
  67. self.__pluggableDescriptionReply(pluggable, description)
  68. )
  69. self.__readyCounter.incr()
  70. for pluggable in pluggables:
  71. self.__bridge.command('machine_info', 'connectionclass', pluggable)(
  72. lambda pluggableClass, pluggable = pluggable:
  73. self.__pluggableClassReply(pluggable, pluggableClass)
  74. )
  75. self.__readyCounter.incr()
  76. def __pluggableDescriptionReply(self, pluggable, description):
  77. self.__readyCounter.decr()
  78. self.__pluggableDescriptions[pluggable] = description
  79. def __connectorDescriptionReply(self, connector, description):
  80. self.__readyCounter.decr()
  81. self.__connectorDescriptions[connector] = description
  82. def __connectorClassReply(self, connector, connectorClass):
  83. self.__readyCounter.decr()
  84. self.__connectorClasses[connector] = connectorClass
  85. def __pluggableClassReply(self, connector, pluggableClass):
  86. self.__readyCounter.decr()
  87. self.__pluggableClasses[connector] = pluggableClass
  88. def getPluggables(self, connectorClass):
  89. retval = []
  90. for pluggable in self.__pluggableClasses:
  91. if self.__pluggableClasses[pluggable] == connectorClass:
  92. retval.append(pluggable)
  93. return retval
  94. def getPluggableDescription(self, pluggable):
  95. try:
  96. desc = self.__pluggableDescriptions[pluggable]
  97. except KeyError:
  98. desc = ''
  99. print 'No description available yet for pluggable %s, '\
  100. 'fix race conditions!' % pluggable
  101. return desc
  102. def getConnectorDescription(self, connector):
  103. try:
  104. desc = self.__connectorDescriptions[connector]
  105. except KeyError:
  106. desc = ''
  107. print 'No description available yet for connector %s, '\
  108. 'fix race conditions!' % connector
  109. return desc
  110. def getClass(self, connector):
  111. return self.__connectorClasses[connector]
  112. def __connectorPlugged(self, connector, machineId, pluggable):
  113. if pluggable:
  114. print 'Connector %s got plugged with a %s on machine %s' % (connector, \
  115. pluggable, machineId)
  116. else:
  117. print 'Connector %s got unplugged on machine %s' % (connector, machineId)
  118. # TODO: shouldn't we do something with the machineId?
  119. self.__setConnector(connector, pluggable)
  120. def queryConnector(self, connector):
  121. '''Queries the connector info of the specified connector'''
  122. self.__bridge.command('plug', connector)(self.__connectorReply)
  123. def __connectorAdded(self, connector):
  124. # First update the list of connector descriptions, if necessary
  125. if connector not in self.__connectorClasses.keys():
  126. self.__bridge.command('machine_info', 'connectionclass', connector)(
  127. lambda connectorClass, connector = connector:
  128. self.__connectorClassReply(connector, connectorClass)
  129. )
  130. self.__readyCounter.incr()
  131. if connector not in self.__connectorDescriptions.keys():
  132. self.__bridge.command('machine_info', 'connector',
  133. connector)(lambda description, connector = connector:
  134. self.__connectorDescriptionReply(connector, description))
  135. self.__readyCounter.incr()
  136. newEntry = ( connector, None )
  137. index = bisect(self.__connectors, newEntry)
  138. parent = QtCore.QModelIndex() # invalid model index
  139. self.beginInsertRows(parent, index, index)
  140. self.__connectors.insert(index, newEntry)
  141. self.endInsertRows()
  142. self.queryConnector(connector)
  143. def __connectorRemoved(self, connector):
  144. index = bisect(self.__connectors, ( connector, ))
  145. if 0 <= index < len(self.__connectors) \
  146. and self.__connectors[index][0] == connector:
  147. parent = QtCore.QModelIndex() # invalid model index
  148. self.beginRemoveRows(parent, index, index)
  149. del self.__connectors[index]
  150. self.endRemoveRows()
  151. else:
  152. print 'removed connector "%s" did not exist' % connector
  153. def __setConnector(self, connector, pluggable):
  154. index = 0
  155. for name, oldPluggable in self.__connectors:
  156. if name == connector:
  157. if oldPluggable == pluggable:
  158. return False
  159. else:
  160. if pluggable == '':
  161. pluggable = '--empty--'
  162. print 'unplug %s' % name
  163. else:
  164. print 'plug into %s: %s' % (name, pluggable or '<empty>')
  165. self.__connectors[index] = name, str(pluggable)
  166. modelIndex = self.createIndex(index, 0)
  167. self.dataChanged.emit(modelIndex, modelIndex)
  168. return True
  169. index += 1
  170. else:
  171. raise KeyError(connector)
  172. def __updateConnector(self, connector, pluggable):
  173. try:
  174. self.__setConnector(connector, pluggable)
  175. except KeyError:
  176. # This can happen if we don't monitor the creation of new
  177. # connectors.
  178. # TODO: Is that a temporary situation?
  179. print 'received update for non-existing connector "%s"' % connector
  180. def __updateConnectorList(self, connector, machineId, action):
  181. # TODO: shouldn't we do something with the machineId?
  182. if action == 'add':
  183. self.__connectorAdded(connector)
  184. elif action == 'remove':
  185. self.__connectorRemoved(connector)
  186. else:
  187. print 'received update for unsupported action "%s" for ' \
  188. 'connector "%s" and machine "%s".'\
  189. % ( action, connector, machineId )
  190. def __connectorReply(self, connector, pluggable = '', flags = ''):
  191. print 'connector update %s to "%s" flags "%s"'\
  192. % ( connector, pluggable, flags )
  193. if connector[-1] == ':':
  194. connector = connector[ : -1]
  195. else:
  196. print 'connector query reply does not start with "<connector>:", '\
  197. 'but with "%s"' % connector
  198. return
  199. # TODO: Do something with the flags.
  200. self.__updateConnector(connector, pluggable)
  201. def getInserted(self, connector):
  202. '''Returns the pluggable which is currently plugged in the
  203. given connector.
  204. If the pluggable is not yet known, None is returned.
  205. Raises KeyError if no connector exists by the given name.
  206. '''
  207. for name, pluggable in self.__connectors:
  208. if name == connector:
  209. return pluggable
  210. else:
  211. raise KeyError(connector)
  212. def setInserted(self, connector, pluggable, errorHandler):
  213. '''Sets the pluggable of the given connector.
  214. Raises KeyError if no connector exists by the given name.
  215. '''
  216. changed = self.__setConnector(connector, pluggable)
  217. if changed:
  218. if pluggable == '--empty--' or pluggable == '':
  219. self.__bridge.command('unplug', connector)(
  220. None, errorHandler
  221. )
  222. else:
  223. self.__bridge.command('plug', connector,
  224. pluggable)(None, errorHandler)
  225. def rowCount(self, parent):
  226. # TODO: What does this mean?
  227. if parent.isValid():
  228. return 0
  229. else:
  230. return len(self.__connectors)
  231. def data(self, index, role = QtCore.Qt.DisplayRole):
  232. if not index.isValid():
  233. return QtCore.QVariant()
  234. name, pluggable = self.__connectors[index.row()]
  235. if role == QtCore.Qt.DisplayRole:
  236. description = self.getConnectorDescription(name)
  237. return QtCore.QVariant(
  238. '%s: %s' % ( description, pluggable )
  239. )
  240. elif role == QtCore.Qt.UserRole:
  241. return QtCore.QVariant(name)
  242. return QtCore.QVariant()