PageRenderTime 68ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/examples/itemviews/puzzle/puzzle.py

https://github.com/lucianowolf/Examples
Python | 410 lines | 307 code | 80 blank | 23 comment | 43 complexity | 38c01688901377cc98f4f3c3b43d4ef4 MD5 | raw file
  1. #!/usr/bin/env python
  2. ############################################################################
  3. #
  4. # Copyright (C) 2004-2005 Trolltech AS. All rights reserved.
  5. #
  6. # This file is part of the example classes of the Qt Toolkit.
  7. #
  8. # This file may be used under the terms of the GNU General Public
  9. # License version 2.0 as published by the Free Software Foundation
  10. # and appearing in the file LICENSE.GPL included in the packaging of
  11. # self file. Please review the following information to ensure GNU
  12. # General Public Licensing requirements will be met:
  13. # http://www.trolltech.com/products/qt/opensource.html
  14. #
  15. # If you are unsure which license is appropriate for your use, please
  16. # review the following information:
  17. # http://www.trolltech.com/products/qt/licensing.html or contact the
  18. # sales department at sales@trolltech.com.
  19. #
  20. # This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
  21. # WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
  22. #
  23. ############################################################################
  24. import random
  25. from PySide import QtCore, QtGui
  26. import puzzle_rc
  27. class PuzzleWidget(QtGui.QWidget):
  28. puzzleCompleted = QtCore.Signal()
  29. def __init__(self, parent=None):
  30. super(PuzzleWidget, self).__init__(parent)
  31. self.piecePixmaps = []
  32. self.pieceRects = []
  33. self.pieceLocations = []
  34. self.highlightedRect = QtCore.QRect()
  35. self.inPlace = 0
  36. self.setAcceptDrops(True)
  37. self.setMinimumSize(400, 400)
  38. self.setMaximumSize(400, 400)
  39. def clear(self):
  40. self.pieceLocations = []
  41. self.piecePixmaps = []
  42. self.pieceRects = []
  43. self.highlightedRect = QtCore.QRect()
  44. self.inPlace = 0
  45. self.update()
  46. def dragEnterEvent(self, event):
  47. if event.mimeData().hasFormat('image/x-puzzle-piece'):
  48. event.accept()
  49. else:
  50. event.ignore()
  51. def dragLeaveEvent(self, event):
  52. updateRect = self.highlightedRect
  53. self.highlightedRect = QtCore.QRect()
  54. self.update(updateRect)
  55. event.accept()
  56. def dragMoveEvent(self, event):
  57. updateRect = self.highlightedRect.unite(self.targetSquare(event.pos()))
  58. if event.mimeData().hasFormat('image/x-puzzle-piece') and self.findPiece(self.targetSquare(event.pos())) == -1:
  59. self.highlightedRect = self.targetSquare(event.pos())
  60. event.setDropAction(QtCore.Qt.MoveAction)
  61. event.accept()
  62. else:
  63. self.highlightedRect = QtCore.QRect()
  64. event.ignore()
  65. self.update(updateRect)
  66. def dropEvent(self, event):
  67. if event.mimeData().hasFormat('image/x-puzzle-piece') and self.findPiece(self.targetSquare(event.pos())) == -1:
  68. pieceData = event.mimeData().data('image/x-puzzle-piece')
  69. stream = QtCore.QDataStream(pieceData, QtCore.QIODevice.ReadOnly)
  70. square = self.targetSquare(event.pos())
  71. pixmap = QtGui.QPixmap()
  72. location = QtCore.QPoint()
  73. stream >> pixmap >> location
  74. self.pieceLocations.append(location)
  75. self.piecePixmaps.append(pixmap)
  76. self.pieceRects.append(square)
  77. self.hightlightedRect = QtCore.QRect()
  78. self.update(square)
  79. event.setDropAction(QtCore.Qt.MoveAction)
  80. event.accept()
  81. if location == QtCore.QPoint(square.x() / 80, square.y() / 80):
  82. self.inPlace += 1
  83. if self.inPlace == 25:
  84. self.puzzleCompleted.emit()
  85. else:
  86. self.highlightedRect = QtCore.QRect()
  87. event.ignore()
  88. def findPiece(self, pieceRect):
  89. try:
  90. return self.pieceRects.index(pieceRect)
  91. except ValueError:
  92. return -1
  93. def mousePressEvent(self, event):
  94. square = self.targetSquare(event.pos())
  95. found = self.findPiece(square)
  96. if found == -1:
  97. return
  98. location = self.pieceLocations[found]
  99. pixmap = self.piecePixmaps[found]
  100. del self.pieceLocations[found]
  101. del self.piecePixmaps[found]
  102. del self.pieceRects[found]
  103. if location == QtCore.QPoint(square.x() + 80, square.y() + 80):
  104. self.inPlace -= 1
  105. self.update(square)
  106. itemData = QtCore.QByteArray()
  107. dataStream = QtCore.QDataStream(itemData, QtCore.QIODevice.WriteOnly)
  108. dataStream << pixmap << location
  109. mimeData = QtCore.QMimeData()
  110. mimeData.setData('image/x-puzzle-piece', itemData)
  111. drag = QtGui.QDrag(self)
  112. drag.setMimeData(mimeData)
  113. drag.setHotSpot(event.pos() - square.topLeft())
  114. drag.setPixmap(pixmap)
  115. if drag.start(QtCore.Qt.MoveAction) == 0:
  116. self.pieceLocations.insert(found, location)
  117. self.piecePixmaps.insert(found, pixmap)
  118. self.pieceRects.insert(found, square)
  119. self.update(self.targetSquare(event.pos()))
  120. if location == QtCore.QPoint(square.x() / 80, square.y() / 80):
  121. self.inPlace += 1
  122. def paintEvent(self, event):
  123. painter = QtGui.QPainter()
  124. painter.begin(self)
  125. painter.fillRect(event.rect(), QtCore.Qt.white)
  126. if self.highlightedRect.isValid():
  127. painter.setBrush(QtGui.QColor("#ffcccc"))
  128. painter.setPen(QtCore.Qt.NoPen)
  129. painter.drawRect(self.highlightedRect.adjusted(0, 0, -1, -1))
  130. for i, pieceRect in enumerate(self.pieceRects):
  131. painter.drawPixmap(pieceRect, self.piecePixmaps[i])
  132. painter.end()
  133. def targetSquare(self, position):
  134. return QtCore.QRect(position.x() // 80 * 80, position.y() // 80 * 80, 80, 80)
  135. class PiecesModel(QtCore.QAbstractListModel):
  136. def __init__(self, parent=None):
  137. super(PiecesModel, self).__init__(parent)
  138. self.locations = []
  139. self.pixmaps = []
  140. def data(self, index, role=QtCore.Qt.DisplayRole):
  141. if not index.isValid():
  142. return None
  143. if role == QtCore.Qt.DecorationRole:
  144. return QtGui.QIcon(self.pixmaps[index.row()].scaled(
  145. 60, 60, QtCore.Qt.KeepAspectRatio,
  146. QtCore.Qt.SmoothTransformation))
  147. if role == QtCore.Qt.UserRole:
  148. return self.pixmaps[index.row()]
  149. if role == QtCore.Qt.UserRole + 1:
  150. return self.locations[index.row()]
  151. return None
  152. def addPiece(self, pixmap, location):
  153. if random.random() < 0.5:
  154. row = 0
  155. else:
  156. row = len(self.pixmaps)
  157. self.beginInsertRows(QtCore.QModelIndex(), row, row)
  158. self.pixmaps.insert(row, pixmap)
  159. self.locations.insert(row, location)
  160. self.endInsertRows()
  161. def flags(self,index):
  162. if index.isValid():
  163. return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable |
  164. QtCore.Qt.ItemIsDragEnabled)
  165. return QtCore.Qt.ItemIsDropEnabled
  166. def removeRows(self,row, count, parent):
  167. if parent.isValid():
  168. return False
  169. if row >= len(self.pixmaps) or row + count <= 0:
  170. return False
  171. beginRow = max(0, row)
  172. endRow = min(row + count - 1, len(self.pixmaps) - 1)
  173. self.beginRemoveRows(parent, beginRow, endRow)
  174. del self.pixmaps[beginRow:endRow + 1]
  175. del self.locations[beginRow:endRow + 1]
  176. self.endRemoveRows()
  177. return True
  178. def mimeTypes(self):
  179. return ['image/x-puzzle-piece']
  180. def mimeData(self, indexes):
  181. mimeData = QtCore.QMimeData()
  182. encodedData = QtCore.QByteArray()
  183. stream = QtCore.QDataStream(encodedData, QtCore.QIODevice.WriteOnly)
  184. for index in indexes:
  185. if index.isValid():
  186. pixmap = QtGui.QPixmap(self.data(index, QtCore.Qt.UserRole))
  187. location = self.data(index, QtCore.Qt.UserRole + 1)
  188. stream << pixmap << location
  189. mimeData.setData('image/x-puzzle-piece', encodedData)
  190. return mimeData
  191. def dropMimeData(self, data, action, row, column, parent):
  192. if not data.hasFormat('image/x-puzzle-piece'):
  193. return False
  194. if action == QtCore.Qt.IgnoreAction:
  195. return True
  196. if column > 0:
  197. return False
  198. if not parent.isValid():
  199. if row < 0:
  200. endRow = len(self.pixmaps)
  201. else:
  202. endRow = min(row, len(self.pixmaps))
  203. else:
  204. endRow = parent.row()
  205. encodedData = data.data('image/x-puzzle-piece')
  206. stream = QtCore.QDataStream(encodedData, QtCore.QIODevice.ReadOnly)
  207. while not stream.atEnd():
  208. pixmap = QtGui.QPixmap()
  209. location = QtGui.QPoint()
  210. stream >> pixmap >> location
  211. self.beginInsertRows(QtCore.QModelIndex(), endRow, endRow)
  212. self.pixmaps.insert(endRow, pixmap)
  213. self.locations.insert(endRow, location)
  214. self.endInsertRows()
  215. endRow += 1
  216. return True
  217. def rowCount(self, parent):
  218. if parent.isValid():
  219. return 0
  220. else:
  221. return len(self.pixmaps)
  222. def supportedDropActions(self):
  223. return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction
  224. def addPieces(self, pixmap):
  225. self.beginRemoveRows(QtCore.QModelIndex(), 0, 24)
  226. self.pixmaps = []
  227. self.locations = []
  228. self.endRemoveRows()
  229. for y in range(5):
  230. for x in range(5):
  231. pieceImage = pixmap.copy(x*80, y*80, 80, 80)
  232. self.addPiece(pieceImage, QtCore.QPoint(x, y))
  233. class MainWindow(QtGui.QMainWindow):
  234. def __init__(self, parent=None):
  235. super(MainWindow, self).__init__(parent)
  236. self.puzzleImage = QtGui.QPixmap()
  237. self.setupMenus()
  238. self.setupWidgets()
  239. self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed,
  240. QtGui.QSizePolicy.Fixed))
  241. self.setWindowTitle("Puzzle")
  242. def openImage(self, path=None):
  243. if not path:
  244. path = QtGui.QFileDialog.getOpenFileName(self, "Open Image", '',
  245. "Image Files (*.png *.jpg *.bmp)")
  246. if path:
  247. newImage = QtGui.QPixmap()
  248. if not newImage.load(path):
  249. QtGui.QMessageBox.warning(self, "Open Image",
  250. "The image file could not be loaded.",
  251. QtGui.QMessageBox.Cancel)
  252. return
  253. self.puzzleImage = newImage
  254. self.setupPuzzle()
  255. def setCompleted(self):
  256. QtGui.QMessageBox.information(self, "Puzzle Completed",
  257. "Congratulations! You have completed the puzzle!\nClick OK "
  258. "to start again.",
  259. QtGui.QMessageBox.Ok)
  260. self.setupPuzzle()
  261. def setupPuzzle(self):
  262. size = min(self.puzzleImage.width(), self.puzzleImage.height())
  263. self.puzzleImage = self.puzzleImage.copy((self.puzzleImage.width()-size)/2,
  264. (self.puzzleImage.height() - size)/2, size, size).scaled(400,
  265. 400, QtCore.Qt.IgnoreAspectRatio,
  266. QtCore.Qt.SmoothTransformation)
  267. random.seed(QtGui.QCursor.pos().x() ^ QtGui.QCursor.pos().y())
  268. self.model.addPieces(self.puzzleImage)
  269. self.puzzleWidget.clear()
  270. def setupMenus(self):
  271. fileMenu = self.menuBar().addMenu("&File")
  272. openAction = fileMenu.addAction("&Open...")
  273. openAction.setShortcut("Ctrl+O")
  274. exitAction = fileMenu.addAction("E&xit")
  275. exitAction.setShortcut("Ctrl+Q")
  276. gameMenu = self.menuBar().addMenu("&Game")
  277. restartAction = gameMenu.addAction("&Restart")
  278. openAction.triggered.connect(self.openImage)
  279. exitAction.triggered.connect(QtGui.qApp.quit)
  280. restartAction.triggered.connect(self.setupPuzzle)
  281. def setupWidgets(self):
  282. frame = QtGui.QFrame()
  283. frameLayout = QtGui.QHBoxLayout(frame)
  284. self.piecesList = QtGui.QListView()
  285. self.piecesList.setDragEnabled(True)
  286. self.piecesList.setViewMode(QtGui.QListView.IconMode)
  287. self.piecesList.setIconSize(QtCore.QSize(60,60))
  288. self.piecesList.setGridSize(QtCore.QSize(80,80))
  289. self.piecesList.setSpacing(10)
  290. self.piecesList.setMovement(QtGui.QListView.Snap)
  291. self.piecesList.setAcceptDrops(True)
  292. self.piecesList.setDropIndicatorShown(True)
  293. self.model = PiecesModel(self)
  294. self.piecesList.setModel(self.model)
  295. self.puzzleWidget = PuzzleWidget()
  296. self.puzzleWidget.puzzleCompleted.connect(self.setCompleted,
  297. QtCore.Qt.QueuedConnection)
  298. frameLayout.addWidget(self.piecesList)
  299. frameLayout.addWidget(self.puzzleWidget)
  300. self.setCentralWidget(frame)
  301. if __name__ == '__main__':
  302. import sys
  303. app = QtGui.QApplication(sys.argv)
  304. window = MainWindow()
  305. window.openImage(':/images/example.jpg')
  306. window.show()
  307. sys.exit(app.exec_())