I have following code using pyqt, which displays two QComboBox's and I want to use the selection values in my applications:
class TACRG(QtGui.QMainWindow, design.Ui_MainWindow):
def __init__(self, parent=None):
super(TACRG, self).__init__(parent)
self.setupUi(self)
self.CList.addItems(["A", "B", "C", "D"])
self.connect(self.CList, QtCore.SIGNAL('activated(QString)'), self.c_chosen)
self.RList.addItems(["Q1", "Q2", "Q3", "Annual"])
self.connect(self.RList, QtCore.SIGNAL('activated(QString)'), self.r_chosen)
def r_chosen(self, text):
report_start, report_end = report_period(text)
def c_chosen(self, text):
accs = get_ucs(text)
def report_period(r_period):
year=date.today().year
if r_period == 'Q1':
return (str(year)+'0101',str(year)+'0331')
elif r_period == 'Q2':
return (str(year)+'0401',str(year)+'0630')
elif r_period == 'Q3':
return (str(year)+'0701',str(year)+'0930')
elif r_period == 'Annual':
return (str(year-1)+'0101',str(year-1)+'1231')
def get_ucs(c_name):
"""DO something""
return """some string"""
Now I wan't to use the values returned from the report_period and get_ucs functions (report_start, report_end, accs) in another funtion, which must be called after these two executed.
How can I achieve this?
Store values in class data members using self, call your other_method in both r_chosen and c_chosen and check for valid values in other_method before use.
class TACRG(QtGui.QMainWindow, design.Ui_MainWindow):
def __init__(self, parent=None):
# Your init code
self.report_start, self.report_end, self.accs = [None] * 3
def r_chosen(self, text):
self.report_start, self.report_end = report_period(text)
self.other_method()
def c_chosen(self, text):
self.accs = get_ucs(text)
self.other_method()
def other_method(self):
#validate if your fields have valid values
if self.report_start and self.report_end and self.accs:
print self.report_start, self.report_end, self.accs
Edit (Other method outside class):
class TACRG(QtGui.QMainWindow, design.Ui_MainWindow):
def __init__(self, parent=None):
# Your init code
self.report_start, self.report_end, self.accs = [None] * 3
def r_chosen(self, text):
self.report_start, self.report_end = report_period(text)
other_method(self.report_start, self.report_end, self.accs)
def c_chosen(self, text):
self.accs = get_ucs(text)
other_method(self.report_start, self.report_end, self.accs)
def other_method(report_start, report_end, accs):
#validate if your fields have valid values
if report_start and report_end and accs:
print report_start, report_end, accs
Related
I am learning how to use proxy models recently, And I want to create a custom proxy model that can flat nodes in a source tree model to a list model, I have found a good solution to this:
How to create a proxy model that would flatten nodes of a QAbstractItemModel into a list in PySide?
However, when I try to removeRows() (remove tree node) from the source tree model, the proxy model crashes, I guess it's because the source model didn't emit layoutChanged signal to the proxy model to refresh the self.m_rowMap and self.m_indexMap?
【question1】: How to fix the crash?
【question2】:For QSortFilterProxyModel, removeRows() from the source model won't crash the proxy model, so I also want to know the underlying mechanism of QSortFilterProxyModel, especially the implementation of the following methods:
setSourceModel(),
mapFromSource(),
mapToSource(),
mapSelectionFromSource(),
mapSelectionToSource()
Especially how it emits signals between the sourceModel and the QSortFilterProxyModel?
Reproduce example:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import sys
from PyQt5 import QtGui, QtWidgets
from PyQt5.QtCore import (QAbstractProxyModel, QModelIndex, pyqtSlot)
class FlatProxyModel(QAbstractProxyModel):
def __init__(self, parent=None):
# super(FlatProxyModel, self).__init__(parent)
super().__init__(parent)
def buildMap(self, model, parent=QModelIndex(), row=0):
"""
Usage:
* to build the rowMap and indexMap of the treeModel
"""
if row == 0:
self.m_rowMap = {}
self.m_indexMap = {}
rows = model.rowCount(parent)
for r in range(rows):
index = model.index(r, 0, parent)
print('row', row, 'item', model.data(index))
self.m_rowMap[index] = row
self.m_indexMap[row] = index
row = row + 1
if model.hasChildren(index):
row = self.buildMap(model, index, row)
return row
def setSourceModel(self, model):
QAbstractProxyModel.setSourceModel(self, model)
self.buildMap(model)
print(flush=True)
model.dataChanged.connect(self.sourceDataChanged)
def mapFromSource(self, index):
if index not in self.m_rowMap:
return QModelIndex()
# print('mapping to row', self.m_rowMap[index], flush = True)
return self.createIndex(self.m_rowMap[index], index.column())
def mapToSource(self, index):
if not index.isValid() or (index.row() not in self.m_indexMap):
return QModelIndex()
# print('mapping from row', index.row(), flush = True)
return self.m_indexMap[index.row()]
def columnCount(self, parent):
return QAbstractProxyModel.sourceModel(self).columnCount(self.mapToSource(parent))
def rowCount(self, parent):
# print('rows:', len(self.m_rowMap), flush=True)
return len(self.m_rowMap) if not parent.isValid() else 0
def index(self, row, column, parent):
# print('index for:', row, column, flush=True)
if parent.isValid():
return QModelIndex()
return self.createIndex(row, column)
def parent(self, index):
return QModelIndex()
#pyqtSlot(QModelIndex, QModelIndex)
def sourceDataChanged(self, topLeft, bottomRight):
self.dataChanged.emit(self.mapFromSource(topLeft),
self.mapFromSource(bottomRight))
class myWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
super().__init__(parent)
self.model = QtGui.QStandardItemModel()
names = ['Foo', 'Bar', 'Baz']
for first in names:
row = QtGui.QStandardItem(first)
for second in names:
row.appendRow(QtGui.QStandardItem(first + second))
self.model.appendRow(row)
self.proxy = FlatProxyModel()
self.proxy.setSourceModel(self.model)
self.nestedProxy = FlatProxyModel()
self.nestedProxy.setSourceModel(self.proxy)
vLayout = QtWidgets.QVBoxLayout(self)
hLayout = QtWidgets.QHBoxLayout()
self.treeView = QtWidgets.QTreeView()
self.treeView.setModel(self.model)
self.treeView.expandAll()
self.treeView.header().hide()
hLayout.addWidget(self.treeView)
self.listView1 = QtWidgets.QListView()
self.listView1.setModel(self.proxy)
hLayout.addWidget(self.listView1)
self.listView2 = QtWidgets.QListView()
self.listView2.setModel(self.nestedProxy)
hLayout.addWidget(self.listView2)
vLayout.addLayout(hLayout)
removeButton = QtWidgets.QPushButton('Remove')
removeButton.clicked.connect(self.removeItems)
vLayout.addWidget(removeButton)
def removeItems(self):
index = self.treeView.currentIndex()
model = index.model()
model.removeRows(index.row(), 1, index.parent())
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
w = myWidget()
w.show()
sys.exit(app.exec_())
The problem is that when removing an item from the source model the proxy is not notified and the "map" is not updated. One possible solution is to connect the rowsRemoved signal to the buildMap.
class FlatProxyModel(QAbstractProxyModel):
def __init__(self, parent=None):
super().__init__(parent)
self.connections = []
def buildMap(self, model, parent=QModelIndex(), row=0):
# ...
def setSourceModel(self, model):
if self.sourceModel() is not None:
for connection in self.connections:
self.sourceModel().disconnect(connection)
QAbstractProxyModel.setSourceModel(self, model)
if self.sourceModel() is None:
self.connections = []
return
self.connections = [
self.sourceModel().dataChanged.connect(self.sourceDataChanged),
self.sourceModel().rowsRemoved.connect(self.reload_model),
self.sourceModel().modelReset.connect(self.reload_model),
self.sourceModel().rowsInserted.connect(self.reload_model)
]
self.reload_model()
def reload_model(self):
self.beginResetModel()
self.buildMap(self.sourceModel())
self.endResetModel()
# ...
def removeItems(self):
index = self.treeView.currentIndex()
if not index.isValid():
return
model = index.model()
model.removeRows(index.row(), 1, index.parent())
I would like to use a DataViewCtrl with a PyDataViewModel as tree-like control with drag-and-drop support. I am almost there but I cannot figure out how to distinguish between dropping an item between the lines or on top of the parent item. The two screenshots illustrate the issue. In both cases the item identified as the drop target is the parent item.
This is the code:
import wx
import wx.dataview as dv
print(wx.__version__)
class Container(list):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Container {}'.format(self.name)
class Element(object):
def __init__(self, name):
self.name = name
def __str__(self):
return 'Element {}'.format(self.name)
#property
def len(self):
return str(len(self.name))
class MyTreeListModel(dv.PyDataViewModel):
def __init__(self, data):
dv.PyDataViewModel.__init__(self)
self.data = data
def GetColumnCount(self):
return 2
def GetColumnType(self, col):
mapper = { 0 : 'string',
1 : 'string'
}
return mapper[col]
def GetChildren(self, parent, children):
if not parent:
for cont in self.data:
children.append(self.ObjectToItem(cont))
return len(self.data)
node = self.ItemToObject(parent)
if isinstance(node, Container):
for ds in node:
children.append(self.ObjectToItem(ds))
return len(node)
return 0
def IsContainer(self, item):
if not item:
return True
node = self.ItemToObject(item)
if isinstance(node, Container):
return True
return False
def GetParent(self, item):
if not item:
return dv.NullDataViewItem
node = self.ItemToObject(item)
if isinstance(node, Container):
return dv.NullDataViewItem
elif isinstance(node, Element):
for g in self.data:
try:
g.index(node)
except ValueError:
continue
else:
return self.ObjectToItem(g)
def GetValue(self, item, col):
node = self.ItemToObject(item)
if isinstance(node, Container):
mapper = { 0 : node.name,
1 : '',
}
return mapper[col]
elif isinstance(node, Element):
mapper = { 0 : node.name,
1 : node.len
}
return mapper[col]
else:
raise RuntimeError("unknown node type")
class MyFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, -1, "Table")
panel = wx.Panel(self)
dvcTree = dv.DataViewCtrl(panel,style=wx.BORDER_THEME|dv.DV_MULTIPLE)
self.model = MyTreeListModel(data)
dvcTree.AssociateModel(self.model)
dvcTree.AppendTextColumn("Container", 0, width=80)
dvcTree.AppendTextColumn("Element", 1, width=80)
dvcTree.Bind(dv.EVT_DATAVIEW_ITEM_BEGIN_DRAG, self._onDrag)
dvcTree.Bind(dv.EVT_DATAVIEW_ITEM_DROP, self._onEndDrag)
dvcTree.Bind(dv.EVT_DATAVIEW_ITEM_DROP_POSSIBLE, self._onDropPossible)
self.dvcTree = dvcTree
dvcTree.EnableDragSource(wx.DataFormat(wx.DF_UNICODETEXT))
dvcTree.EnableDropTarget(wx.DataFormat(wx.DF_UNICODETEXT))
box = wx.BoxSizer(wx.VERTICAL)
box.Add(dvcTree, 1, wx.EXPAND)
panel.SetSizer(box)
self.Layout()
def _onDropPossible(self, evt):
item = evt.GetItem()
mod = evt.GetModel()
if not evt.GetItem().IsOk():
return
def _onEndDrag(self, evt):
if not evt.GetItem().IsOk():
evt.Veto()
return
mod = evt.GetModel()
print('dropped at', mod.ItemToObject(evt.GetItem()))
try:
print('parent:',mod.ItemToObject(mod.GetParent(evt.GetItem())))
except TypeError:
print('parent: None')
def _onDrag(self, evt):
evt.Allow()
mod = evt.GetModel()
print('from', mod.GetValue(evt.GetItem(),0))
evt.SetDataObject(wx.TextDataObject('don\'t know how to retrieve that information in the drop handler'))
evt.SetDragFlags(wx.Drag_AllowMove)
data = [Container('eins'),Container('zwei'),Container('drei')]
for d in data:
d[:] = [Element('element {}'.format('X'*q)) for q in range(5)]
if __name__ == "__main__":
app = wx.App()
f = MyFrame(None)
f.Show()
app.MainLoop()
EDIT:
This is mostly a problem on OSX since the MSW implementation does not offer to drop between lines. I was not able to test it on linux though.
I have two questions:
I was wondering if this is the proper way to do a search/filter on a single column treeview. I feel like a lot of my copying/pasting could contain unnecessary stuff. Is all the code in the subclass of QSortFilterProxyModel and the code in the search_text_changed method needed? I don't feel like the regex is needed, since I set the filter-proxy to ignore case-sensitivity.
How can I make it so that when a user double-clicks a treeview item a signal emits a string list containing the string of the item clicked and all of its ancestors recursively? For example, if I double-clicked "Birds", it would return ['Birds','Animals']; and if I double-clicked "Animals", it would just return ['Animals'].
import os, sys
from PySide import QtCore, QtGui
tags = {
"Animals": [
"Birds",
"Various"
],
"Brick": [
"Blocks",
"Special"
],
"Manmade": [
"Air Conditioners",
"Audio Equipment"
],
"Food": [
"Fruit",
"Grains and Seeds"
]
}
class SearchProxyModel(QtGui.QSortFilterProxyModel):
def __init__(self, parent=None):
super(SearchProxyModel, self).__init__(parent)
self.text = ''
# Recursive search
def _accept_index(self, idx):
if idx.isValid():
text = idx.data(role=QtCore.Qt.DisplayRole).lower()
condition = text.find(self.text) >= 0
if condition:
return True
for childnum in range(idx.model().rowCount(parent=idx)):
if self._accept_index(idx.model().index(childnum, 0, parent=idx)):
return True
return False
def filterAcceptsRow(self, sourceRow, sourceParent):
# Only first column in model for search
idx = self.sourceModel().index(sourceRow, 0, sourceParent)
return self._accept_index(idx)
def lessThan(self, left, right):
leftData = self.sourceModel().data(left)
rightData = self.sourceModel().data(right)
return leftData < rightData
class TagsBrowserWidget(QtGui.QWidget):
clickedTag = QtCore.Signal(list)
def __init__(self, parent=None):
super(TagsBrowserWidget, self).__init__(parent)
self.resize(300,500)
# controls
self.ui_search = QtGui.QLineEdit()
self.ui_search.setPlaceholderText('Search...')
self.tags_model = SearchProxyModel()
self.tags_model.setSourceModel(QtGui.QStandardItemModel())
self.tags_model.setDynamicSortFilter(True)
self.tags_model.setFilterCaseSensitivity(QtCore.Qt.CaseInsensitive)
self.ui_tags = QtGui.QTreeView()
self.ui_tags.setSortingEnabled(True)
self.ui_tags.sortByColumn(0, QtCore.Qt.AscendingOrder)
self.ui_tags.setEditTriggers(QtGui.QAbstractItemView.NoEditTriggers)
self.ui_tags.setHeaderHidden(True)
self.ui_tags.setRootIsDecorated(True)
self.ui_tags.setUniformRowHeights(True)
self.ui_tags.setModel(self.tags_model)
# layout
main_layout = QtGui.QVBoxLayout()
main_layout.addWidget(self.ui_search)
main_layout.addWidget(self.ui_tags)
self.setLayout(main_layout)
# signals
self.ui_tags.doubleClicked.connect(self.tag_double_clicked)
self.ui_search.textChanged.connect(self.search_text_changed)
# init
self.create_model()
def create_model(self):
model = self.ui_tags.model().sourceModel()
self.populate_tree(tags, model.invisibleRootItem())
self.ui_tags.sortByColumn(0, QtCore.Qt.AscendingOrder)
def populate_tree(self, children, parent):
for child in sorted(children):
node = QtGui.QStandardItem(child)
parent.appendRow(node)
if isinstance(children, dict):
self.populate_tree(children[child], node)
def tag_double_clicked(self, item):
text = item.data(role=QtCore.Qt.DisplayRole)
print [text]
self.clickedTag.emit([text])
def search_text_changed(self, text=None):
regExp = QtCore.QRegExp(self.ui_search.text(), QtCore.Qt.CaseInsensitive, QtCore.QRegExp.FixedString)
self.tags_model.text = self.ui_search.text().lower()
self.tags_model.setFilterRegExp(regExp)
if len(self.ui_search.text()) >= 1 and self.tags_model.rowCount() > 0:
self.ui_tags.expandAll()
else:
self.ui_tags.collapseAll()
def main():
app = QtGui.QApplication(sys.argv)
ex = TagsBrowserWidget()
ex.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
There's no point in setting the case-sensivity of the filter-proxy at all, because you are by-passing the built-in filtering by overriding filterAcceptsRow. And even if you weren't doing that, setFilterRegExp ignores the current case sensitiviy settings anyway.
I would simplify the filter-proxy to this:
class SearchProxyModel(QtGui.QSortFilterProxyModel):
def setFilterRegExp(self, pattern):
if isinstance(pattern, str):
pattern = QtCore.QRegExp(
pattern, QtCore.Qt.CaseInsensitive,
QtCore.QRegExp.FixedString)
super(SearchProxyModel, self).setFilterRegExp(pattern)
def _accept_index(self, idx):
if idx.isValid():
text = idx.data(QtCore.Qt.DisplayRole)
if self.filterRegExp().indexIn(text) >= 0:
return True
for row in range(idx.model().rowCount(idx)):
if self._accept_index(idx.model().index(row, 0, idx)):
return True
return False
def filterAcceptsRow(self, sourceRow, sourceParent):
idx = self.sourceModel().index(sourceRow, 0, sourceParent)
return self._accept_index(idx)
and change the search method to this:
def search_text_changed(self, text=None):
self.tags_model.setFilterRegExp(self.ui_search.text())
if len(self.ui_search.text()) >= 1 and self.tags_model.rowCount() > 0:
self.ui_tags.expandAll()
else:
self.ui_tags.collapseAll()
So now the SearchProxyModel has sole responsibilty for deciding how searches are performed via its setFilterRegExp method. The case-sensitivity is handled transparently, so there is no need to pre-process the input.
The method for getting a list of descendants, can be written like this:
def tag_double_clicked(self, idx):
text = []
while idx.isValid():
text.append(idx.data(QtCore.Qt.DisplayRole))
idx = idx.parent()
text.reverse()
self.clickedTag.emit(text)
My Code:
class SUCHER(EventHandler):
def __init__(self):
self.vokabel = VOKABEL()
EventHandler.__init__(self)
self.button = Button("Suchen!", Point(250,200))
self.button.setFontSize(25)
self.button.setFillColor("lightgrey")
self.button.addHandler(self)
self.lateinischesWort = TextBox(200,50,Point(120,100))
self.übersetzungsText = Rectangle(200,50,Point(380,100))
self.textD = Text("Deutsches Wort", 15, Point(380,50))
self.textL = Text("Lateinisches Wort", 15, Point(120,50))
self.textU = Text(self.vokabel, 25, Point(380,100))
def anzeigenIn(self, Fenster):
Fenster.add(self.button)
Fenster.add(self.übersetzungsText)
Fenster.add(self.lateinischesWort)
Fenster.add(self.textD)
Fenster.add(self.textL)
Fenster.add(self.textU)
def handle(self, Event):
if Event.getDescription() == "mouse click":
self.textL = self.wort
self.textU = self.übersetzung
self.textU.setMessage(self.vokabel)
class BINÄRBAUM:
def __init__(self):
self.wurzel = ABSCHLUSS()
def einfügen(self, Datum):
self.wurzel = self.wurzel.einfügen(Datum)
def inorderAusgeben(self):
self.wurzel.inorderAusgeben()
def preorderAusgeben(self):
self.wurzel.preorderAusgeben()
def postorderAusgeben(self):
self.wurzel.postorderAusgeben()
def suchen(self, Schlüssel):
self.wurzel.suchen(Schlüssel)
class KNOTEN:
def __init__(self, Datum):
self.datum = Datum
self.links = ABSCHLUSS()
self.rechts = ABSCHLUSS()
def einfügen(self, Datum):
if Datum.schlüsselGeben() < self.datum.schlüsselGeben():
self.links = self.links.einfügen(Datum)
else:
self.rechts = self.rechts.einfügen(Datum)
return self
def inorderAusgeben(self):
self.links.inorderAusgeben()
self.datum.informationAusgeben()
self.rechts.inorderAusgeben()
def preorderAusgeben(self):
self.datum.informationAusgeben()
self.links.preorderAusgeben()
self.rechts.preorderAusgeben()
def postorderAusgeben(self):
self.links.postorderAusgeben()
self.rechts.postorderAusgeben()
self.datum.informationAusgeben()
def suchen(self, Schlüssel):
if self.datum.schlüsselGeben() == Schlüssel.casefold():
self.datum.informationAusgeben()
elif self.datum.schlüsselGeben() > Schlüssel.casefold():
self.links.suchen(Schlüssel)
else:
self.rechts.suchen(Schlüssel)
class ABSCHLUSS:
def __init__(self):
pass
def einfügen(self, Datum):
return KNOTEN(Datum)
def inorderAusgeben(self):
pass
def preorderAusgeben(self):
pass
def postorderAusgeben(self):
pass
def suchen(self, Schlüssel):
pass
class VOKABEL:
def __init__(self, Wort, Übersetzung):
self.wort = Wort
self.übersetzung = Übersetzung
def informationAusgeben(self):
print("Das Wort",self.wort,"hat die Bedeutung",self.übersetzung,".")
def schlüsselGeben(self):
return self.wort.casefold()
v = VOKABEL("Nebel", "fog")
s = SUCHER()
b = BINÄRBAUM()
b.einfügen(v)
b.inorderAusgeben()
b.preorderAusgeben()
b.postorderAusgeben()
b.suchen("Nebel")
fenster = Canvas(500,250)
s.anzeigenIn(fenster)
I'm a programmer from Germany and I have a problem.By the way I use the module cs1graphics. I get the Error : builtins.TypeError: init() missing 2 required positional arguments: 'Wort' and 'Übersetzung'
so what can I do to solve this?
class SUCHER(EventHandler):
def __init__(self):
self.vokabel = VOKABEL()
You are instanciating a new VOKABEL class without giving it the 2 arguments it needs. See here the constructor :
class VOKABEL:
def __init__(self, Wort, Übersetzung):
I make a UI with PyQt4. It has a treeView and I want to deal with it.
The treeView is made up with model-base. I create a data in .py file and import it.
So, I can see the data tree in my treeView.
But I can't drag and drop it, so can't change the order.
I referred some articles so add it in my script, but they couldn't work.
I plant some "print", so I chased my problem.
I found that when drag a item, it transferred to MIME data.
But when it is dropped, I can't find any outputs.
It seems that the script doesn't call "dropMimeData" method.
How can I fix my script?
from PyQt4 import QtCore, QtGui
from setting import *
from copy import deepcopy
from cPickle import dumps, load, loads
from cStringIO import StringIO
class PyMimeData(QtCore.QMimeData):
MIME_TYPE = QtCore.QString('text/plain')
def __init__(self, data=None):
QtCore.QMimeData.__init__(self)
self._local_instance = data
if data is not None:
try:
pdata = dumps(data)
except:
return
self.setData(self.MIME_TYPE, dumps(data.__class__) + pdata)
#classmethod
def coerce(cls, md):
if isinstance(md, cls):
return md
if not md.hasFormat(cls.MIME_TYPE):
return None
nmd = cls()
nmd.setData(cls.MIME_TYPE, md.data())
return nmd
def instance(self):
if self._local_instance is not None:
return self._local_instance
io = StringIO(str(self.data(self.MIME_TYPE)))
try:
load(io)
return load(io)
except:
pass
return None
def instanceType(self):
if self._local_instance is not None:
return self._local_instance.__class__
try:
return loads(str(self.data(self.MIME_TYPE)))
except:
pass
return None
class treeItem(QtGui.QStandardItem):
def __init__(self, data, parent=None):
super(treeItem, self).__init__(data)
self.parentItem = parent
self.itemData = data
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def parent(self):
return self.parentItem
def childAtRow(self, row):
return self.childItems[row]
def rowOfChild(self, child):
for i, item in enumerate(self.childItems):
if item == child:
return i
return -1
class treeModel(QtGui.QStandardItemModel):
def __init__(self, name, parent=None):
super(treeModel, self).__init__(parent)
self.headerName = name
self.childItems = []
def appendChild(self, item):
self.childItems.append(item)
def removeRowAll(self):
pass
def addItemList(self, parent, elements):
for text, children in elements:
item = treeItem(text, parent)
self.addItems(parent, item)
if children:
self.addItemList(item, children)
def addItems(self, parent, inputItem):
parent.appendRow(inputItem)
parent.appendChild(inputItem)
def headerData(self, section, orientation, role):
if orientation == QtCore.Qt.Horizontal and role == QtCore.Qt.DisplayRole:
return self.headerName
def supportedDropActions(self):
return QtCore.Qt.MoveAction | QtCore.Qt.CopyAction
def flags(self, index):
defaultFlags = QtCore.QAbstractItemModel.flags(self, index)
if index.isValid():
return QtCore.Qt.ItemIsEditable | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled | defaultFlags
else:
return QtCore.Qt.ItemIsDropEnabled | defaultFlags
def mimeTypes(self):
types = QtCore.QStringList()
types.append('text/plain')
return types
def mimeData(self, index):
node = self.nodeFromIndex(index[0])
mimeData = PyMimeData(node)
return mimeData
def dropMimeData(self, mimedata, action, row, column, parentIndex):
print mimedata, action, row, column, parentIndex
if action == QtCore.Qt.IgnoreAction:
return True
dragNode = mimedata.instance()
print dragNode
parentNode = self.nodeFromIndex(parentIndex)
# copy of node being moved
newNode = deepcopy(dragNode)
print newNode
newNode.setParent(parentNode)
self.insertRow(len(parentNode)-1, parentIndex)
self.emit(QtCore.SIGNAL("dataChanged(QtCore.QModelIndex,QtCore.QModelIndex)"), parentIndex, parentIndex)
return True
def nodeFromIndex(self, index):
##return index.internalPointer() if index.isValid() else self.root
return index.model() if index.isValid() else self.parent()
def insertRow(self, row, parent):
return self.insertRows(row, 1, parent)
def insertRows(self, row, count, parent):
self.beginInsertRows(parent, row, (row + (count - 1)))
self.endInsertRows()
return True
def removeRow(self, row, parentIndex):
return self.removeRows(row, 1, parentIndex)
def removeRows(self, row, count, parentIndex):
self.beginRemoveRows(parentIndex, row, row)
node = self.nodeFromIndex(parentIndex)
node.removeChild(row)
self.endRemoveRows()
return True
added script
here is ui creation (the script above is imported in this script)
class RigControlWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self, parent = getMayaWindow()):
super(RigControlWindow, self).__init__(parent)
self.setupUi(self)
self.bodyrig_treelist.setDragEnabled(1)
self.bodyrig_treelist.setAcceptDrops(1)
self.bodyrig_treelist.setDropIndicatorShown(1)
self.bodyrig_treelist.setDragDropMode(QtGui.QAbstractItemView.InternalMove)
QtCore.QObject.connect(self.finalize_button, QtCore.SIGNAL("clicked()"), self.AddData_treeList)
def AddData_treeList(self):
self.localtreeModel = treeModel("objects")
self.bodyrig_treelist.setModel(self.localtreeModel)
self.localtreeModel.addItemList(self.localtreeModel, data)
and data is
data = [("root",[("upper",[("hand",[]),
("head",[])
]),
("lower",[("leg",[]),
("foot",[])
])
])
]
The QTreeView.dragMoveEvent and QTreeView.dragEnterEvent methods both check the object returned by event.mimeData() to see if it can return data for any of the formats supported by the model (i.e. those returned by model.mimeTypes()).
But your PyMimeData subclass doesn't support any formats, because it never successfully sets the data passed to its constructor.
The problem is located in PyMimeData.__init__:
...
try:
pdata = dumps(data)
except:
return
self.setData(self.MIME_TYPE, dumps(data.__class__) + pdata)
The data is passed in from the treeModel.mimeData method:
def mimeData(self, index):
node = self.nodeFromIndex(index[0])
mimeData = PyMimeData(node)
return mimeData
But if you check the type of data/node you'll see that it is a treeModel instance, and so dumps(data) will fail because data can't be pickled. As a result of this, the PyMimeData object is not initialized properly, and so it is ignored by drag events.