Saturday, 17 April 2010

openmolar - preparing for roll out

i am thinking of breaking openmolar into bits

ie. separate packages namely openmolar-server, openmolar-client and openmolar-help multiple reasons for this.
A slimmer version of the existing package will become the client, and this will be the cross platform bit (ie. I will make a windows executable)

openmolar-server will be the package which sets up and configures the mysql server and allows for practice customisation. This will only run on unix-like OSs.

openmolar-help will simply put a set of html docs and videos into a pre-destined location.

 if anyone has any thoughts, please let me know.

Monday, 12 April 2010

PyQt4 model/view drag & drop example

In PyQt4, drag and drop with QListWidget works really, really well.

However, for real life application, I believe it's best to leave the convenience widgets (like QListWidget) behind, and head for the model/view pyqt4 classes. This gives a lot more flexibility than the "one-size fits all" model which comes with QListwidget et al.

This enables easy referencing of the true python objects that underlie the model, using Qt.UserRole to refer to the object, and pickle (or cPickle) to convert to a bytestream which the drag/drop can handle.

here is a little example using a few tricks to get drag and drop of native python objects in PyQt4.




import datetime
import cPickle
import pickle
import sys

from PyQt4 import QtGui, QtCore

class person(object):
    '''
    a custom data structure, for example purposes
    '''
    def __init__(self, name, dob, house_no):
        self.name = name
        self.dob = dob
        self.addr = "%d Rue de la Soleil"% house_no
    def __repr__(self):
        return "%s\n%s\n%s"% (self.name, self.dob, self.addr)

class simple_model(QtCore.QAbstractListModel):
    def __init__(self, parent=None):
        super(simple_model, self).__init__(parent)
        self.list = []
        for name, dob, house_no in (
        ("Neil", datetime.date(1969,12,9), 23),
        ("John", datetime.date(1952,5,3), 2543),
        ("Ilona", datetime.date(1975,4,6), 1)):
            self.list.append(person(name, dob, house_no))
        self.setSupportedDragActions(QtCore.Qt.MoveAction)

    def rowCount(self, parent=QtCore.QModelIndex()):
        return len(self.list)

    def data(self, index, role):
        if role == QtCore.Qt.DisplayRole: #show just the name
            person = self.list[index.row()]
            return QtCore.QVariant(person.name)
        elif role == QtCore.Qt.UserRole:  #return the whole python object
            person = self.list[index.row()]
            return person
        return QtCore.QVariant()

    def removeRow(self, position):
        self.list = self.list[:position] + self.list[position+1:]
        self.reset()

class dropZone(QtGui.QLabel):
    def __init__(self, parent=None):
        super(dropZone, self).__init__(parent)
        self.setMinimumSize(200,200)
        self.set_bg()
        self.setText("Drop Here")
        self.setAlignment(QtCore.Qt.AlignCenter)
        self.setAcceptDrops(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat("application/x-person"):
            self.set_bg(True)
            event.accept()
        else:
            event.ignore()

    def dragMoveEvent(self, event):
        if event.mimeData().hasFormat("application/x-person"):
            event.setDropAction(QtCore.Qt.MoveAction)
            event.accept()
        else:
            event.ignore()

    def dragLeaveEvent(self, event):
        self.set_bg()

    def dropEvent(self, event):
        data = event.mimeData()
        bstream = data.retrieveData("application/x-person",
            QtCore.QVariant.ByteArray)
        selected = pickle.loads(bstream.toByteArray())
        self.setText(str(selected))
        self.set_bg()
        event.accept()

    def set_bg(self, active=False):
        if active:
            val = "background:yellow;"
        else:
            val = "background:green;"
        self.setStyleSheet(val)


class draggableList(QtGui.QListView):
    '''
    a listView whose items can be moved
    '''
    def ___init__(self, parent=None):
        super(draggableList, self).__init__(parent)
        self.setDragEnabled(True)

    def dragEnterEvent(self, event):
        if event.mimeData().hasFormat("application/x-person"):
            event.setDropAction(QtCore.Qt.QMoveAction)
            event.accept()
        else:
            event.ignore()

    def startDrag(self, event):
        index = self.indexAt(event.pos())
        if not index.isValid():
            return

        ## selected is the relevant person object
        selected = self.model().data(index,QtCore.Qt.UserRole)

        ## convert to  a bytestream
        bstream = cPickle.dumps(selected)
        mimeData = QtCore.QMimeData()
        mimeData.setData("application/x-person", bstream)

        drag = QtGui.QDrag(self)
        drag.setMimeData(mimeData)

        # example 1 - the object itself

        pixmap = QtGui.QPixmap()
        pixmap = pixmap.grabWidget(self, self.rectForIndex(index))

        # example 2 -  a plain pixmap
        #pixmap = QtGui.QPixmap(100, self.height()/2)
        #pixmap.fill(QtGui.QColor("orange"))
        drag.setPixmap(pixmap)

        drag.setHotSpot(QtCore.QPoint(pixmap.width()/2, pixmap.height()/2))
        drag.setPixmap(pixmap)
        result = drag.start(QtCore.Qt.MoveAction)
        if result: # == QtCore.Qt.MoveAction:
            self.model().removeRow(index.row())

    def mouseMoveEvent(self, event):
        self.startDrag(event)

class testDialog(QtGui.QDialog):
    def __init__(self, parent=None):
        super(testDialog, self).__init__(parent)
        self.setWindowTitle("Drag Drop Test")
        layout = QtGui.QGridLayout(self)

        label = QtGui.QLabel("Drag Name From This List")

        self.model = simple_model()
        self.listView = draggableList()
        self.listView.setModel(self.model)
        self.dz = dropZone()

        layout.addWidget(label,0,0)
        layout.addWidget(self.listView,1,0)
        layout.addWidget(self.dz,0,1,2,2)

if __name__ == "__main__":
    '''
    the try catch here is to ensure that the app exits cleanly no matter what
    makes life better for SPE
    '''
    try:
        app = QtGui.QApplication([])
        dl = testDialog()
        dl.exec_()
    except Exception, e:  #could use as e for python 2.6...
        print e
    sys.exit(app.closeAllWindows())