I am using this code in QGIS's built-in python-editor.
How can I make the script wait while I mark the necessary lines, and only then continue execution? So far, I cannot do this: the window starts, but the script continues to execute and therefore there is no way to use the select list. For example, can I somehow add the "Ok" button there (as in a standard dialog box)?
from qgis.PyQt import *
from qgis.core import *
class CheckableComboBox(QComboBox):
# Subclass Delegate to increase item height
class Delegate(QStyledItemDelegate):
def sizeHint(self, option, index):
size = super().sizeHint(option, index)
size.setHeight(20)
return size
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
# Make the combo editable to set a custom text, but readonly
self.setEditable(True)
self.lineEdit().setReadOnly(True)
# Make the lineedit the same color as QPushButton
palette = qApp.palette()
palette.setBrush(QPalette.Base, palette.button())
self.lineEdit().setPalette(palette)
# Use custom delegate
self.setItemDelegate(CheckableComboBox.Delegate())
# Update the text when an item is toggled
self.model().dataChanged.connect(self.updateText)
# Hide and show popup when clicking the line edit
self.lineEdit().installEventFilter(self)
self.closeOnLineEditClick = False
# Prevent popup from closing when clicking on an item
self.view().viewport().installEventFilter(self)
def resizeEvent(self, event):
# Recompute text to elide as needed
self.updateText()
super().resizeEvent(event)
def eventFilter(self, object, event):
if object == self.lineEdit():
if event.type() == QEvent.MouseButtonRelease:
if self.closeOnLineEditClick:
self.hidePopup()
else:
self.showPopup()
return True
return False
if object == self.view().viewport():
if event.type() == QEvent.MouseButtonRelease:
index = self.view().indexAt(event.pos())
item = self.model().item(index.row())
if item.checkState() == Qt.Checked:
item.setCheckState(Qt.Unchecked)
else:
item.setCheckState(Qt.Checked)
return True
return False
def showPopup(self):
super().showPopup()
# When the popup is displayed, a click on the lineedit should close it
self.closeOnLineEditClick = True
def hidePopup(self):
super().hidePopup()
# Used to prevent immediate reopening when clicking on the lineEdit
self.startTimer(100)
# Refresh the display text when closing
self.updateText()
def timerEvent(self, event):
# After timeout, kill timer, and reenable click on line edit
self.killTimer(event.timerId())
self.closeOnLineEditClick = False
def updateText(self):
texts = []
for i in range(self.model().rowCount()):
if self.model().item(i).checkState() == Qt.Checked:
texts.append(self.model().item(i).text())
text = ", ".join(texts)
# Compute elided text (with "...")
metrics = QFontMetrics(self.lineEdit().font())
elidedText = metrics.elidedText(text, Qt.ElideRight, self.lineEdit().width())
self.lineEdit().setText(elidedText)
def addItem(self, text, data=None):
item = QStandardItem()
item.setText(text)
if data is None:
item.setData(text)
else:
item.setData(data)
item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable)
item.setData(Qt.Unchecked, Qt.CheckStateRole)
self.model().appendRow(item)
def addItems(self, texts, datalist=None):
for i, text in enumerate(texts):
try:
data = datalist[i]
except (TypeError, IndexError):
data = None
self.addItem(text, data)
def currentData(self):
# Return the list of selected items data
res = []
for i in range(self.model().rowCount()):
if self.model().item(i).checkState() == Qt.Checked:
res.append(self.model().item(i).data())
return res
print("Starting...")
comunes = ['Ameglia', 'Arcola', 'Bagnone', 'Bolano', 'Carrara', 'Casola', 'Barnaul', 'London']
combo = CheckableComboBox()
combo.resize(350,300)
combo.setWindowTitle('CheckableComboBox')
combo.setWindowFlags(Qt.WindowStaysOnTopHint)
combo.show()
combo.addItems(comunes)
print(combo.currentData())
The problem solved by adding checkable_combobox to the QDialog. Here is the code using QgsCheckableComboBox():
https://gis.stackexchange.com/questions/376901/how-to-put-qgscheckablecombobox-into-standby
from qgis.PyQt import QtGui
from qgis.core import *
planet_list = ["Venus", "Earth", "Mars", "Jupiter", "Pluto"]
items = QgsCheckableComboBox()
items.addItems(planet_list)
dlg = QDialog()
layout = QVBoxLayout()
layout.addWidget(items)
dlg.setLayout(layout)
dlg.exec_()
print('\n\n-----------CheckedItems: ', items.checkedItems())
Related
I have the following code to create a QTreeWidget and a contextmenu with 2 actions.
import sys
from PyQt5 import QtCore, QtWidgets
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__()
self.tw = QtWidgets.QTreeWidget()
self.tw.setHeaderLabels(['Name', 'Cost ($)'])
cg = QtWidgets.QTreeWidgetItem(['carrots', '0.99'])
c1 = QtWidgets.QTreeWidgetItem(['carrot', '0.33'])
self.tw.addTopLevelItem(cg)
self.tw.addTopLevelItem(c1)
self.tw.installEventFilter(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tw)
def eventFilter(self, source: QtWidgets.QTreeWidget, event):
if (event.type() == QtCore.QEvent.ContextMenu and
source is self.tw):
menu = QtWidgets.QMenu()
AAction = QtWidgets.QAction("AAAAA")
AAction.triggered.connect(lambda :self.a(source.itemAt(event.pos())))
BAction = QtWidgets.QAction("BBBBBB")
BAction.triggered.connect(lambda :self.b(source, event))
menu.addAction(AAction)
menu.addAction(BAction)
menu.exec_(event.globalPos())
return True
return super(Dialog, self).eventFilter(source, event)
def a(self, item):
if item is None:
return
print("A: {}".format([item.text(i) for i in range(self.tw.columnCount())]))
def b(self, source, event):
item = source.itemAt(event.pos())
if item is None:
return
print("B: {}".format([item.text(i) for i in range(source.columnCount())]))
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Dialog()
window.setGeometry(600, 100, 300, 200)
window.show()
sys.exit(app.exec_())
When opening the contextmenu in the header and clicking on one of the actions it prints either carrot or carrots, depending on where in the contextmenu I click. But I give the position of right click event to the functions.
So why is this happening and what can I do to stop it?
Your code has 2 errors:
The main error is that the itemAt() method uses the coordinates with respect to the viewport() and not with respect to the view (the QTreeWidget) so you will get incorrect items (the header occupies a space making the positions with respect to the QTreeWidget and the viewport() have an offset).
You should not block the eventloop, for example blocking the eventFilter you may be blocking other events that would cause errors that are difficult to debug, in this case it is better to use a signal.
class Dialog(QtWidgets.QDialog):
rightClicked = QtCore.pyqtSignal(QtCore.QPoint)
def __init__(self, parent=None):
super(Dialog, self).__init__()
self.tw = QtWidgets.QTreeWidget()
self.tw.setHeaderLabels(["Name", "Cost ($)"])
cg = QtWidgets.QTreeWidgetItem(["carrots", "0.99"])
c1 = QtWidgets.QTreeWidgetItem(["carrot", "0.33"])
self.tw.addTopLevelItem(cg)
self.tw.addTopLevelItem(c1)
self.tw.viewport().installEventFilter(self)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tw)
self.rightClicked.connect(self.handle_rightClicked)
def eventFilter(self, source: QtWidgets.QTreeWidget, event):
if event.type() == QtCore.QEvent.ContextMenu and source is self.tw.viewport():
self.rightClicked.emit(event.pos())
return True
return super(Dialog, self).eventFilter(source, event)
def handle_rightClicked(self, pos):
item = self.tw.itemAt(pos)
if item is None:
return
menu = QtWidgets.QMenu()
print_action = QtWidgets.QAction("Print")
print_action.triggered.connect(lambda checked, item=item: self.print_item(item))
menu.addAction(print_action)
menu.exec_(self.tw.viewport().mapToGlobal(pos))
def print_item(self, item):
if item is None:
return
texts = []
for i in range(item.columnCount()):
text = item.text(i)
texts.append(text)
print("B: {}".format(",".join(texts)))
Although it is unnecessary that you use an eventFilter to handle the contextmenu since a simpler solution is to set the contextMenuPolicy of the QTreeWidget to Qt::CustomContextMenu and use the customContextMenuRequested signal:
class Dialog(QtWidgets.QDialog):
def __init__(self, parent=None):
super(Dialog, self).__init__()
self.tw = QtWidgets.QTreeWidget()
self.tw.setHeaderLabels(["Name", "Cost ($)"])
cg = QtWidgets.QTreeWidgetItem(["carrots", "0.99"])
c1 = QtWidgets.QTreeWidgetItem(["carrot", "0.33"])
self.tw.addTopLevelItem(cg)
self.tw.addTopLevelItem(c1)
self.tw.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
self.tw.customContextMenuRequested.connect(self.handle_rightClicked)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.tw)
def handle_rightClicked(self, pos):
item = self.tw.itemAt(pos)
if item is None:
return
menu = QtWidgets.QMenu()
print_action = QtWidgets.QAction("Print")
print_action.triggered.connect(lambda checked, item=item: self.print_item(item))
menu.addAction(print_action)
menu.exec_(self.tw.viewport().mapToGlobal(pos))
def print_item(self, item):
if item is None:
return
texts = []
for i in range(item.columnCount()):
text = item.text(i)
texts.append(text)
print("B: {}".format(",".join(texts)))
I know how to drag rows
QTableView.verticalHeader().setSectionsMovable(True)
QTableView.verticalHeader().setDragEnabled(True)
QTableView.verticalHeader().setDragDropMode(qtw.QAbstractItemView.InternalMove)
but I want to be able to drag and drop just a single (or a pair of) cell.
Can anyone point me in the right direction?
PS: my current idea would be to intercept the clicked() -> dragEnter() -> dragLeave() or fork to dragDrop() -> dragIndicatorPosition()
events, but it sounds kinda convoluted
I did read this but I am confused on how to implement it, especially the section "Enabling drag and drop for items" seems to address exactly what I need. I'll see if I can post a "slim" example.
EDIT:
here an example with some other stuff. in MyStandardItemModel I try to do the trick:
from PyQt5 import QtCore, QtWidgets, QtSql
from PyQt5.QtCore import QModelIndex
from PyQt5.QtGui import QStandardItemModel
from PyQt5.QtWidgets import QApplication, QTableView, QTableWidget
class MyStandardItemModel(QStandardItemModel):
def __init__(self, parent=None, *arg, **kwargs):
super().__init__(parent, *arg, **kwargs)
self.__readonly_cols = []
def flags(self, index: QtCore.QModelIndex) -> QtCore.Qt.ItemFlags:
try:
default_Flags = QStandardItemModel.flags(self,index)
if (index.column() in self.__readonly_cols):
return QtCore.Qt.ItemIsEnabled | QtCore.Qt.ItemIsSelectable
else:
if (index.isValid()):
return default_Flags | QtCore.Qt.ItemIsDragEnabled | QtCore.Qt.ItemIsDropEnabled
else:
return default_Flags
except Exception as ex:
print(ex)
def setReadOnly(self, columns: [int]):
for i in columns:
if i <= (self.columnCount() - 1) and i not in self.__readonly_cols:
self.__readonly_cols.append(i)
def resetReadOnly(self):
self.__readonly_cols = []
class MyTableView(QtWidgets.QTableView):
def __init__(self, parent=None, *args, **kwargs):
super().__init__(parent, *args, **kwargs)
class CheckBoxDelegate(QtWidgets.QItemDelegate):
"""
A delegate that places a fully functioning QCheckBox cell of the column to which it's applied.
"""
# signal to inform clicking
user_click = QtCore.pyqtSignal(int, int, bool)
def __init__(self, parent):
QtWidgets.QItemDelegate.__init__(self, parent)
def createEditor(self, parent, option, index):
"""
Important, otherwise an editor is created if the user clicks in this cell.
"""
return None
def paint(self, painter, option, index):
"""
Paint a checkbox without the label.
"""
self.drawCheck(painter, option, option.rect, QtCore.Qt.Unchecked if int(index.data()) == 0 else QtCore.Qt.Checked)
def editorEvent(self, event, model, option, index):
'''
Change the data in the model and the state of the checkbox
if the user presses the left mousebutton and this cell is editable. Otherwise do nothing.
'''
if not int(index.flags() & QtCore.Qt.ItemIsEditable) > 0:
return False
if event.type() == QtCore.QEvent.MouseButtonRelease and event.button() == QtCore.Qt.LeftButton:
# Change the checkbox-state
self.setModelData(None, model, index)
return True
# if event.type() == QtCore.QEvent.MouseButtonPress or event.type() == QtCore.QEvent.MouseMove:
# return False
return False
def setModelData (self, editor, model, index):
'''
The user wanted to change the old state in the opposite.
'''
try:
if int(index.data()) == 0:
model.setData(index, 1, QtCore.Qt.EditRole)
ret = True
else:
model.setData(index, 0, QtCore.Qt.EditRole)
ret = False
# emit signal with row, col, and the status
self.user_click.emit(index.row(), index.column(), ret)
except Exception as ex:
print(ex)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
model = MyStandardItemModel(5, 5)
header_labels = ['', 'Signal', 'Type', 'Routing', 'Input']
model.setHorizontalHeaderLabels(header_labels)
tableView = MyTableView()
tableView.setModel(model)
delegate = CheckBoxDelegate(None)
tableView.setItemDelegateForColumn(0, delegate)
for row in range(5):
for column in range(4):
index = model.index(row, column, QModelIndex())
model.setData(index, 0)
model.setReadOnly([1,2])
tableView.setWindowTitle("CheckBox, readonly and drag&drop example")
tableView.show()
sys.exit(app.exec_())
I have a custom combo box placed on a QDialog widget, and I can not catch any of the mouse signals. I sub classed QComboBox to intercept two signals not provided by QComboBox: LostFocusEvent and mouseDobleClickEvent. LostFocusEvent works well but the combo is not firing the mouse events. I need three signals on the combo box and only one suitable is provided.
I tried to set combo.grabMouse(), disregarding the documentation warnings, and the combo.doubleClicked start working, but all other widgets connected through signals start behaving erratically.
Also tried combo.view().doubleClick.connect with similar results. Also I tried other mouse events with similar results (Press- Release- etc)
Finally, I tried to to use event instead of QMouseEvent in the comboBox sub class, but it's intercepted by the focusOutEvent slot.
Mouse event work on QPushButtons including double click on QTableView widgets
Using Windows 8 Python 3.7 PyQt5.
`class Agreement(QDialog):
def __init__(self,db, address, parent=None):
super().__init__(parent= None)
self.parent = parent
.......................................
def setUi(self):
.....................................
self.comboSupplier = ComboFocus.FocusCombo(self)
self.comboSupplier.setMaximumSize(220,30)
self.comboSupplier.setEditable(True)
#self.comboSupplier.grabMouse()
self.comboSupplier.activated.connect(self.supplierChange)
self.comboSupplier.focusLost.connect(self.supplierFocusLost)
self.comboSupplier.doubleClicked.connect(self.editContact)
...........................................
def supplierChange(self):
try:
row = self.comboSupplier.currentIndex()
idx = self.comboSupplier.model().index(row,0)
self.supplierId = self.comboSupplier.model().data(idx)
self.agreementTitle[0] = self.comboSupplier.currentText()
self.setAgreementTitle()
self.okToSave[2] = int(self.supplierId)
self.okSaving()
except TypeError as err:
print('supplierChange' + type(err).__name__ + ' ' + err.args[0])
#pyqtSlot()
def editContact(self):
try:
c = Contacts(self.db,self.comboSupplier.currentText(),
APM.OPEN_EDIT_ONE, self.supplierId,parent=self)
c.show()
c.exec()
except Exception as err:
print(type(err).__name__, err-args)
#pyqtSlot(ComboFocus.FocusCombo)
def supplierFocusLost(self, combo):
try:
self.setFocusPolicy(Qt.NoFocus)
name = combo.currentText()
if combo.findText(name) > -1:
return
........................................
class FocusCombo(QComboBox):
focusLost = pyqtSignal(QComboBox)
focusGot = pyqtSignal(QComboBox)
doubleClicked = pyqtSignal(QComboBox)
def __init__(self, parent = None):
super().__init__(parent)
self.parent = parent
def mouseDoubleClickEvent(self,event=QMouseEvent.MouseButtonDblClick):
print("double click detected")
self.doubleClicked.emit(self)
def focusOutEvent(self, event):
if event.gotFocus():
self.focusGot.emit(self)
elif event.lostFocus():
self.focusLost.emit(self)
if __name__ == '__main__':
app = QApplication(sys.argv)
cb = FocusCombo()
cb.show()
app.exec_()
sys.exit(app.exec_())
I'd like to double click on the comboBox to open a widget to edit the contact attributes on the fly.
When you set QLineEdit to editable, a QLineEdit is added, so to track your events you must use an eventFilter:
from PyQt5 import QtCore, QtGui, QtWidgets
class FocusCombo(QtWidgets.QComboBox):
focusLost = QtCore.pyqtSignal(QtWidgets.QComboBox)
focusGot = QtCore.pyqtSignal(QtWidgets.QComboBox)
doubleClicked = QtCore.pyqtSignal(QtWidgets.QComboBox)
def setEditable(self, editable):
super(FocusCombo, self).setEditable(editable)
if self.lineEdit() is not None:
self.lineEdit().installEventFilter(self)
def eventFilter(self, obj, event):
if obj is self.lineEdit():
if event.type() == QtCore.QEvent.MouseButtonDblClick:
self.doubleClicked.emit(self)
"""elif event.type() == QtCore.QEvent.MouseButtonPress:
print("press")
elif event.type() == QtCore.QEvent.MouseButtonRelease:
print("release")"""
return super(FocusCombo, self).eventFilter(obj, event)
def mouseDoubleClickEvent(self,event):
print("double click detected")
self.doubleClicked.emit(self)
super(FocusCombo, self).mouseDoubleClickEvent(event)
def focusOutEvent(self, event):
if event.gotFocus():
self.focusGot.emit(self)
elif event.lostFocus():
self.focusLost.emit(self)
super(FocusCombo, self).focusOutEvent(event)
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
cb = FocusCombo()
cb.addItems(list("abcdef"))
cb.setEditable(True)
cb.doubleClicked.connect(print)
cb.show()
sys.exit(app.exec_())
Can I set my eventFilter to ignore the 'default' events of a widget eg. mousePressEvents etc? Or can these two be mixed in the first place?
In my code, I have a mousePressEvent and a custom event that I have created for a right-click menu for the menu items within a QMenu.
And from time to time, as I execute my code/ or when doing the right mouse click on the tab, I am seeing either:
AttributeError: 'PySide2.QtCore.QEvent' object has no attribute 'pos'
or sometimes:
RuntimeWarning: Invalid return value in function QTabBar.eventFilter, expected bool, got NoneType.
As such, is there a better way to get around it, or should I re-factor of how I proceed with the Rename Item and Delete Item as denoted in show_adv_qmenu()?
class MyWin(QtGui.QMainWindow):
def __init__(self, parent=None):
super(MyWin, self).__init__()
self.rename_menu_allowed = False
central_widget = QtGui.QWidget()
self.setCentralWidget(central_widget)
vlay = QtGui.QVBoxLayout(central_widget)
hlay = QtGui.QHBoxLayout()
vlay.addLayout(hlay)
vlay.addStretch()
self.add_button = QtGui.QToolButton()
self.tab_bar = QtGui.QTabBar(self)
self.add_button.setIcon(QtGui.QIcon('add.png'))
self.add_button.setMenu(self.set_menu())
self.add_button.setPopupMode(QtGui.QToolButton.InstantPopup)
self.tab_bar.setTabButton(
0,
QtGui.QTabBar.ButtonPosition.RightSide,
self.add_button
)
hlay.addWidget(self.add_button)
hlay.addWidget(self.tab_bar)
self.my_extra_menus = defaultdict(list)
def set_menu(self):
menu_options = ['food', 'drinks', 'snacks']
qmenu = QtGui.QMenu(self.add_button)
for opt in menu_options:
qmenu.addAction(opt, partial(self.set_new_tab, opt))
return qmenu
def set_new_tab(self, opt):
self.tab_bar.addTab(opt)
def mousePressEvent(self, event):
index = self.tab_bar.tabAt(event.pos())
if event.button() == QtCore.Qt.RightButton:
self._showContextMenu(event.pos(), index)
else:
super(MyWin, self).mousePressEvent(event)
def eventFilter(self, obj, event):
# Custom event only for right mouse click within the qmenu
if obj == self.qmenu:
if event.type() == QtCore.QEvent.MouseButtonPress and event.button() == QtCore.Qt.RightButton:
index = self.tab_bar.tabAt(event.pos())
action = obj.actionAt(event.pos())
self.show_adv_qmenu(obj, action, index)
def _showContextMenu(self, position, index):
self.qmenu = QtGui.QMenu(self)
self.qmenu.setTearOffEnabled(True) # New item is not shown in tearoff mode
self.qmenu.setTitle(self.tab_bar.tabText(index))
self.qmenu.installEventFilter(self)
add_item_action = QtGui.QAction('Add Menu Item', self)
slot = partial(self._addMenuItem, index)
add_item_action.triggered.connect(slot)
self.qmenu.addAction(add_item_action)
self.qmenu.addSeparator()
if self.my_extra_menus.get(index):
for menuItem in self.my_extra_menus[index]:
self.qmenu.addMenu(menuItem)
self.qmenu.addSeparator()
global_position = self.mapToGlobal(self.pos())
self.qmenu.exec_(QtCore.QPoint(
global_position.x() - self.pos().x() + position.x(),
global_position.y() - self.pos().y() + position.y()
))
def _addMenuItem(self, index):
# For first tier menu
first_tier_menu = []
for i in self.qmenu.actions():
first_tier_menu.append(i.text())
new_menu_name, ok = QtGui.QInputDialog.getText(
self,
"Name of Menu",
"Name of new Menu Item:"
)
if ok:
if new_menu_name in list(filter(None, first_tier_menu)):
self.err_popup()
else:
menu = QtGui.QMenu(new_menu_name, self)
menu.setTearOffEnabled(True) # New item is shown in tearoff mode, unless I close and re-tearoff
add_item_action = QtGui.QAction('Add sub Item', menu)
slot = partial(self._addActionItem, menu)
add_item_action.triggered.connect(slot)
menu.addAction(add_item_action)
menu.addSeparator()
self.my_extra_menus[index].append(menu)
def _addActionItem(self, menu):
# For second tier menu
new_item_name, ok = QtGui.QInputDialog.getText(
self,
"Name of Menu Item",
"Name of new Menu Item:"
)
second_tier_menu = []
for i in menu.actions():
second_tier_menu.append(i.text())
if ok:
if new_item_name in list(filter(None, second_tier_menu)):
self.err_popup()
else:
action = QtGui.QAction(new_item_name, self)
slot = partial(self._callActionItem, new_item_name)
action.setCheckable(True)
action.toggled.connect(slot)
menu.addAction(action)
def _callActionItem(self, name, flag):
# Function for the checked items..
print name
print flag
def show_adv_qmenu(self, obj, action, index):
self.adv_qmenu = QtGui.QMenu()
rename_menu_action = QtGui.QAction('Rename Item', self)
rename_slot = partial(self.rename_menu_item, obj, action)
rename_menu_action.triggered.connect(rename_slot)
self.adv_qmenu.addAction(rename_menu_action)
delete_menu_action = QtGui.QAction('Delete Item', self)
delete_slot = partial(self.delete_menu_item, obj, action, index)
delete_menu_action.triggered.connect(delete_slot)
self.adv_qmenu.addAction(delete_menu_action)
# global_position = self.mapToGlobal(self.pos())
# self.adv_qmenu.exec_(QtCore.QPoint(global_position))
self.adv_qmenu.exec_(QtGui.QCursor().pos())
def rename_menu_item(self, obj, selected_action):
rename_name, ok = QtGui.QInputDialog.getText(
self,
"renaming",
"New name:"
)
if ok:
for i in obj.actions():
if selected_action.text() == i.text():
i.setText(rename_name)
print "Name changed : {0} --> {1}".format(selected_action.text(), i.text())
def delete_menu_item(self, obj, selected_action, index):
obj.removeAction(selected_action)
def err_popup(self):
msg = QtGui.QMessageBox()
msg.setIcon(QtGui.QMessageBox.Critical)
msg.setText("Input name already exists. Please check.")
msg.setWindowTitle('Unable to add item')
msg.setStandardButtons(QtGui.QMessageBox.Ok)
msg.exec_()
Sorry for the long code..
eventFilter must return a boolean, but in your case it does not return anything so the second error is throwing you. Also I have improved your logic:
def eventFilter(self, obj, event):
# Custom event only for right mouse click within the qmenu
if obj is self.qmenu and event.type() == QtCore.QEvent.MouseButtonPress:
if event.button() == QtCore.Qt.RightButton:
index = self.tab_bar.tabAt(event.pos())
action = self.qmenu.actionAt(event.pos())
if index != -1 and action is not None:
self.show_adv_qmenu(obj, action, index)
return super(MyWin, self).eventFilter(obj, event)
I'd like to modify QTreeWidget to make the selected cell editable when the enter key is hit, but keep the selection to full rows.
I've done a hacky implementation of figuring out where the last click was and saving the value, then sending those values to my edit_item function on the key press (also used for the itemDoubleClicked signal). It's not great though and I'm wondering if there's a much easier way to do it.
For the record, clicking on an item still selects the whole row. It's probably hidden behaviour by default, but in Maya there's a visible selection thing of the last cell that was moved over while the mouse button was held. If I could somehow get access to that, I could also add in behaviour to control it with the arrow keys.
This is an example of the selected cell:
This is my code so far:
class QTreeWidget(QtWidgets.QTreeWidget):
returnPressed = QtCore.Signal(QTreeWidget, int)
def __init__(self, *args, **kwargs):
QtWidgets.QTreeWidget.__init__(self, *args, **kwargs)
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
self.returnPressed.emit(self._selected_item, self._selected_column)
else:
QtWidgets.QTreeWidget.keyPressEvent(self, event)
def _mouse_pos_calculate(self, x_pos):
"""Find the currently selected column."""
try:
item = self.selectedItems()[0]
except IndexError:
item = None
header = self.header()
total_width = 0
for i in range(self.columnCount()):
total_width += header.sectionSize(i)
if total_width > x_pos:
return (item, i)
def mousePressEvent(self, event):
QtWidgets.QTreeWidget.mousePressEvent(self, event)
self._selected_item, self._selected_column = self._mouse_pos_calculate(event.pos().x())
def mouseReleaseEvent(self, event):
QtWidgets.QTreeWidget.mouseReleaseEvent(self, event)
self._selected_item, self._selected_column = self._mouse_pos_calculate(event.pos().x())
Edit: Improved function thanks to eyllanesc
class QTreeWidget(QtWidgets.QTreeWidget):
"""Add ability to edit cells when pressing return."""
itemEdit = QtCore.Signal(QtWidgets.QTreeWidgetItem, int)
def __init__(self, *args, **kwargs):
QtWidgets.QTreeWidget.__init__(self, *args, **kwargs)
self._last_item = None
self._last_column = 0
self.itemDoubleClicked.connect(self._edit_item_intercept)
def _edit_item_intercept(self, item=None, column=None):
if item is None:
item = self._last_item
if column is None:
column = self._last_column
self.itemEdit.emit(item, column)
def _store_last_cell(self, pos):
selected_item = self.itemAt(pos)
if selected_item is None:
return
self._last_item = selected_item
self._last_column = self.header().logicalIndexAt(pos.x())
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
return self._edit_item_intercept()
QtWidgets.QTreeWidget.keyPressEvent(self, event)
def mouseMoveEvent(self, event):
QtWidgets.QTreeWidget.mouseMoveEvent(self, event)
self._store_last_cell(event.pos())
You are doing a lot of calculation unnecessarily, in the next part I show a cleaner solution:
from PySide2 import QtCore, QtGui, QtWidgets
class QTreeWidget(QtWidgets.QTreeWidget):
def __init__(self, *args, **kwargs):
super(TreeWidget, self).__init__(*args, **kwargs)
self.special_item = None
self.special_col = 0
def keyPressEvent(self, event):
if event.key() == QtCore.Qt.Key_Return:
self.editItem(self.special_item, self.special_col)
QtWidgets.QTreeWidget.keyPressEvent(self, event)
def editEnable(self, pos):
press_item = self.itemAt(pos)
if press_item is None:
return
if press_item is self.selectedItems()[0]:
col = self.header().logicalIndexAt(pos.x())
self.special_item = press_item
self.special_col = col
def mousePressEvent(self, event):
QtWidgets.QTreeWidget.mousePressEvent(self, event)
self.editEnable(event.pos())