#!/usr/bin/env python

############################################################################
# 
#  Copyright (C) 2004-2005 Trolltech AS. All rights reserved.
# 
#  This file is part of the example classes of the Qt Toolkit.
# 
#  This file may be used under the terms of the GNU General Public
#  License version 2.0 as published by the Free Software Foundation
#  and appearing in the file LICENSE.GPL included in the packaging of
#  self file.  Please review the following information to ensure GNU
#  General Public Licensing requirements will be met:
#  http://www.trolltech.com/products/qt/opensource.html
# 
#  If you are unsure which license is appropriate for your use, please
#  review the following information:
#  http://www.trolltech.com/products/qt/licensing.html or contact the
#  sales department at sales@trolltech.com.
# 
#  This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
#  WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
# 
############################################################################

import sys
import random
from PyQt4 import QtCore, QtGui

import puzzle_rc


class PuzzleWidget(QtGui.QWidget):
    def __init__(self, parent=None):
        QtGui.QWidget.__init__(self, parent)

        self.piecePixmaps = []
        self.pieceRects = []
        self.pieceLocations = []
        self.highlightedRect = QtCore.QRect()
        self.inPlace = 0
        
        self.setAcceptDrops(True)
        self.setMinimumSize(400, 400)
        self.setMaximumSize(400, 400)
        
    def clear(self):
        self.pieceLocations = []
        self.piecePixmaps = []
        self.pieceRects = []
        self.highlightedRect = QtCore.QRect()
        self.inPlace = 0
        self.update()
        
    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat("image/x-puzzle-piece"):
            event.accept()
        else:
            event.ignore()
            
    def dragLeaveEvent(self, event):
        updateRect = self.highlightedRect
        self.highlightedRect = QtCore.QRect()
        self.update(updateRect)
        event.accept()
        
    def dragMoveEvent(self, event):
        updateRect = self.highlightedRect.unite(self.targetSquare(event.pos()))

        if event.mimeData().hasFormat("image/x-puzzle-piece") and self.findPiece(self.targetSquare(event.pos())) == -1:
            self.highlightedRect = self.targetSquare(event.pos())
            event.setDropAction(QtCore.Qt.MoveAction)
            event.accept()
        else:
            self.highlightedRect = QtCore.QRect()
            event.ignore()
        
        self.update(updateRect)

    def dropEvent(self, event):
        if event.mimeData().hasFormat("image/x-puzzle-piece") and self.findPiece(self.targetSquare(event.pos())) == -1:
            pieceData = event.mimeData().data("image/x-puzzle-piece")
            stream = QtCore.QDataStream(pieceData, QtCore.QIODevice.ReadOnly)
            square = self.targetSquare(event.pos())
            pixmap = QtGui.QPixmap()
            location = QtCore.QPoint()
            stream >> pixmap >> location
            
            self.pieceLocations.append(location)
            self.piecePixmaps.append(pixmap)
            self.pieceRects.append(square)
            
            self.hightlightedRect = QtCore.QRect()
            self.update(square)
            
            event.setDropAction(QtCore.Qt.MoveAction)
            event.accept()
            
            if location == QtCore.QPoint(square.x() / 80, square.y() / 80):
                self.inPlace += 1
                if self.inPlace == 25:
                    self.emit(QtCore.SIGNAL("puzzleCompleted()"))
        else:
            self.highlightedRect = QtCore.QRect()
            event.ignore()
            
    def findPiece(self, pieceRect):
        for i in range(len(self.pieceRects)):
            if pieceRect == self.pieceRects[i]:
                return i

        return -1
    
    def mousePressEvent(self, event):
        square = self.targetSquare(event.pos())
        found = self.findPiece(square)

        if found == -1:
            return
        
        location = self.pieceLocations[found]
        pixmap = self.piecePixmaps[found]
        
        del self.pieceLocations[found]
        del self.piecePixmaps[found]
        del self.pieceRects[found]
        
        if location == QtCore.QPoint(square.x() + 80, square.y() + 80):
            self.inPlace -= 1
        
        self.update(square)
        
        itemData = QtCore.QByteArray()
        dataStream = QtCore.QDataStream(itemData, QtCore.QIODevice.WriteOnly)
        
        dataStream << pixmap << location
        
        mimeData = QtCore.QMimeData()
        mimeData.setData("image/x-puzzle-piece", itemData)
        
        drag = QtGui.QDrag(self)
        drag.setMimeData(mimeData)
        drag.setHotSpot(event.pos() - square.topLeft())
        drag.setPixmap(pixmap)
        
        if drag.start(QtCore.Qt.MoveAction) == 0:
            self.pieceLocations.insert(found, location)
            self.piecePixmaps.insert(found, pixmap)
            self.pieceRects.insert(found, square)
            self.update(self.targetSquare(event.pos()))
            
            if location == QtCore.QPoint(square.x() / 80, square.y() / 80):
                self.inPlace += 1
        
    def paintEvent(self, event):
        painter = QtGui.QPainter()
        painter.begin(self)
        painter.fillRect(event.rect(), QtCore.Qt.white)
        
        if self.highlightedRect.isValid():
            painter.setBrush(QtGui.QColor("#ffcccc"))
            painter.setPen(QtCore.Qt.NoPen)
            painter.drawRect(self.highlightedRect.adjusted(0, 0, -1, -1))
            
        for i in range(len(self.pieceRects)):
            painter.drawPixmap(self.pieceRects[i], self.piecePixmaps[i])

        painter.end()

    def targetSquare(self, position):
        return QtCore.QRect(position.x() / 80 * 80, position.y() / 80 * 80, 80, 80)


class PiecesModel(QtCore.QAbstractListModel):
    def __init__(self, parent=None):
        QtCore.QAbstractListModel.__init__(self, parent)

        self.locations = []
        self.pixmaps = []
        
    def data(self, index, role=QtCore.Qt.DisplayRole):
        if not index.isValid():
            return QtCore.QVariant()
        
        if role == QtCore.Qt.DecorationRole:
            return QtCore.QVariant(QtGui.QIcon(self.pixmaps[index.row()].scaled(
                                               60, 60, QtCore.Qt.KeepAspectRatio,
                                               QtCore.Qt.SmoothTransformation)))
        elif role == QtCore.Qt.UserRole:
            return QtCore.QVariant(self.pixmaps[index.row()])
        elif role == QtCore.Qt.UserRole + 1:
            return QtCore.QVariant(self.locations[index.row()])
        
        return QtCore.QVariant()
    
    def addPiece(self, pixmap, location):
        if random.random() < 0.5:
            row = 0
        else:
            row = len(self.pixmaps)
        
        self.beginInsertRows(QtCore.QModelIndex(), row, row)
        self.pixmaps.insert(row, pixmap)
        self.locations.insert(row, location)
        self.endInsertRows()

    def flags(self,index):
        if index.isValid():
            return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
                  | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled)

        return (QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsDropEnabled)
    
    def removeRows(self,row, count, parent):
        if parent.isValid():
            return False
        
        if row >= len(self.pixmaps) or row + count <= 0:
            return False
        
        beginRow = max(0, row)
        endRow = min(row + count - 1, len(self.pixmaps) - 1)
        
        self.beginRemoveRows(parent, beginRow, endRow)
        
        del self.pixmaps[beginRow:endRow + 1]
        del self.locations[beginRow:endRow + 1]
        
        self.endRemoveRows()
        return True
        
    def mimeTypes(self):
        types = QtCore.QStringList()
        types << "image/x-puzzle-piece"
        return types
    
    def mimeData(self, indexes):
        mimeData = QtCore.QMimeData()
        encodedData = QtCore.QByteArray()
        
        stream = QtCore.QDataStream(encodedData, QtCore.QIODevice.WriteOnly)
        
        for index in indexes:
            if index.isValid():
                pixmap = QtGui.QPixmap(self.data(index, QtCore.Qt.UserRole))
                location = self.data(index, QtCore.Qt.UserRole + 1).toPoint()
                stream << pixmap << location

        mimeData.setData("image/x-puzzle-piece", encodedData)
        return mimeData
            
    def dropMimeData(self, data, action, row, column, parent):
        if not data.hasFormat("image/x-puzzle-piece"):
            return False

        if action == QtCore.Qt.IgnoreAction:
            return True
        
        if column > 0:
            return False
        
        if not parent.isValid() and row < 0:
            endRow = len(self.pixmaps)
        elif not parent.isValid():
            endRow = min(row, len(self.pixmaps))
        else:
            endRow = parent.row()
        
        encodedData = data.data("image/x-puzzle-piece")
        stream = QtCore.QDataStream(encodedData, QtCore.QIODevice.ReadOnly)
        
        while not stream.atEnd():
            pixmap = QtGui.QPixmap()
            location = QtGui.QPoint()
            stream >> pixmap >> location
            
            self.beginInsertRows(QtCore.QModelIndex(), endRow, endRow)
            self.pixmaps.insert(endRow, pixmap)
            self.locations.insert(endRow, location)
            self.endInsertRows()

            endRow += 1

        return True
        
    def rowCount(self, parent):
        if parent.isValid():
            return 0
        else:
            return len(self.pixmaps)
        
    def supportedDropActions(self):
        return QtCore.Qt.CopyAction | QtCore.Qt.MoveAction


class MainWindow(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)

        self.puzzleImage = QtGui.QPixmap()
        
        self.setupMenus()
        self.setupWidgets()
        
        self.setSizePolicy(QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed))
        self.setWindowTitle(self.tr("Puzzle"))
    
    def openImage(self, path=QtCore.QString()):
        fileName = path

        if fileName.isNull():
            fileName = QtGui.QFileDialog.getOpenFileName(self,
                            self.tr("Open Image"), "", "Image Files (*.png *.jpg *.bmp)")

        if not fileName.isEmpty():
            newImage = QtGui.QPixmap()
            if not newImage.load(fileName):
                QtGui.QMessageBox.warning(self, self.tr("Open Image"),
                                          self.tr("The image file could not be loaded."),
                                          QtGui.QMessageBox.Cancel,
                                          QtGui.QMessageBox.NoButton)
                return

            self.puzzleImage = newImage
            self.setupPuzzle()
    
    def setCompleted(self):
        QtGui.QMessageBox.information(self, self.tr("Puzzle Completed"),
                                      self.tr("Congratulations! You have completed "
                                      "the puzzle!\nClick OK to start again."),
                                      QtGui.QMessageBox.Ok)

        self.setupPuzzle()

    def setupPuzzle(self):
        size = min(self.puzzleImage.width(), self.puzzleImage.height())
        self.puzzleImage = self.puzzleImage.copy((self.puzzleImage.width()-size)/2,
                                                 (self.puzzleImage.height() - size)/2, size,
                                                 size).scaled(400, 400, 
                                                 QtCore.Qt.IgnoreAspectRatio,
                                                 QtCore.Qt.SmoothTransformation)

        self.model = PiecesModel(self)
        self.piecesList.setModel(self.model)

        random.seed(QtGui.QCursor.pos().x() ^ QtGui.QCursor.pos().y())
        
        for y in range(5):
            for x in range(5):
                pieceImage = self.puzzleImage.copy(x*80, y*80, 80, 80)
                self.model.addPiece(pieceImage, QtCore.QPoint(x,y))

        self.puzzleWidget.clear()

    def setupMenus(self):
        fileMenu = self.menuBar().addMenu(self.tr("&File"))
        
        openAction = fileMenu.addAction(self.tr("&Open..."))
        openAction.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+O")))
        
        exitAction = fileMenu.addAction(self.tr("E&xit"))
        exitAction.setShortcut(QtGui.QKeySequence(self.tr("Ctrl+Q")))
        
        gameMenu = self.menuBar().addMenu(self.tr("&Game"))
        
        restartAction = gameMenu.addAction(self.tr("&Restart"))
        
        self.connect(openAction, QtCore.SIGNAL("triggered()"), self.openImage)
        self.connect(exitAction, QtCore.SIGNAL("triggered()"), QtGui.qApp, QtCore.SLOT("quit()"))
        self.connect(restartAction, QtCore.SIGNAL("triggered()"), self.setupPuzzle)

    def setupWidgets(self):
        frame = QtGui.QFrame()
        frameLayout = QtGui.QHBoxLayout(frame)
        
        self.piecesList = QtGui.QListView()
        self.piecesList.setDragEnabled(True)
        self.piecesList.setViewMode(QtGui.QListView.IconMode)
        self.piecesList.setIconSize(QtCore.QSize(60,60))
        self.piecesList.setGridSize(QtCore.QSize(80,80))
        self.piecesList.setSpacing(10)
        self.piecesList.setMovement(QtGui.QListView.Snap)
        self.piecesList.setAcceptDrops(True)
        self.piecesList.setDropIndicatorShown(True)

        model = PiecesModel(self)
        self.piecesList.setModel(model)

        self.puzzleWidget = PuzzleWidget()

        self.connect(self.puzzleWidget, QtCore.SIGNAL("puzzleCompleted()"),
                     self.setCompleted, QtCore.Qt.QueuedConnection)
        
        frameLayout.addWidget(self.piecesList)
        frameLayout.addWidget(self.puzzleWidget)
        self.setCentralWidget(frame)


if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MainWindow()
    window.openImage(QtCore.QString(":/images/example.jpg"))
    window.show()
    sys.exit(app.exec_())
