In a QTableWidget, how to determine if an empty cell is editable? - python

I'm working on a QAction to paste structured text from the clipboard into a QTableWidget. This is my current code:
class PasteCellsAction(qt.QAction):
def __init__(self, table):
if not isinstance(table, qt.QTableWidget):
raise ValueError('CopySelectedCellsAction must be initialised ' +
'with a QTableWidget.')
super(PasteCellsAction, self).__init__(table)
self.table = table
self.setText("Paste")
self.setShortcut(qt.QKeySequence('Ctrl+V'))
self.triggered.connect(self.pasteCellFromClipboard)
def pasteCellFromClipboard(self):
"""Paste text from cipboard into the table.
If the text contains tabulations and
newlines, they are interpreted as column and row separators.
In such a case, the text is split into multiple texts to be paste
into multiple cells.
:return: *True* in case of success, *False* if pasting data failed.
"""
selected_idx = self.table.selectedIndexes()
if len(selected_idx) != 1:
msgBox = qt.QMessageBox(parent=self.table)
msgBox.setText("A single cell must be selected to paste data")
msgBox.exec_()
return False
selected_row = selected_idx[0].row()
selected_col = selected_idx[0].column()
qapp = qt.QApplication.instance()
clipboard_text = qapp.clipboard().text()
table_data = _parseTextAsTable(clipboard_text)
protected_cells = 0
out_of_range_cells = 0
# paste table data into cells, using selected cell as origin
for row in range(len(table_data)):
for col in range(len(table_data[row])):
if selected_row + row >= self.table.rowCount() or\
selected_col + col >= self.table.columnCount():
out_of_range_cells += 1
continue
item = self.table.item(selected_row + row,
selected_col + col)
# ignore empty strings
if table_data[row][col] != "":
if not item.flags() & qt.Qt.ItemIsEditable:
protected_cells += 1
continue
item.setText(table_data[row][col])
if protected_cells or out_of_range_cells:
msgBox = qt.QMessageBox(parent=self.table)
msg = "Some data could not be inserted, "
msg += "due to out-of-range or write-protected cells."
msgBox.setText(msg)
msgBox.exec_()
return False
return True
I want to test if a cell is editable before pasting data in it, and for this I get the item using QTableWidget.item(row, col) and then I checks the flags of the item.
My problem is that the .item method returns None for empty cells, so I can't check the flags of empty cells. My code currently only works when there is no empty cell in the paste area.
The error is in lines 46 (None returned) and 50 (AttributeError: 'NoneType' object has no attribute 'flags'):
item = self.table.item(selected_row + row,
selected_col + col)
# ignore empty strings
if table_data[row][col] != "":
if not item.flags() & qt.Qt.ItemIsEditable:
...
Is there another way of finding out if the cell is editable, other than checking flags of the item?

The dimensions of QTableWidget a can specified without explicitly adding any items. In which case, the cells will be completely empty - i.e. both the data and the item will be None. If the user edits the cell, the data will be added to the table's model, and an item will be added. This will happen even if the entered value is an empty string. By default, all cells will be editable unless you take explicit steps to make them read-only.
There are numerous ways of making cells read-only - for instance, setting the edit triggers, or overriding the tables's edit method. But if your only method is explicitly setting the flags on individual table-widget items, you can safely assume that a cell with no item is both editable and empty. (Note that if you directly set the data via the table's model rather than by using e.g. setItem, the cell will still automatically have an item).

I found a solution that seems to work: create a new item and add it to the table when the item() method returns None.
I still have some doubts abouts whether there is a risk that this might modify the flags of a write-protected cell. I currently just assume that if a cell is write-protected, this means that it necessarily already contains an item.
item = self.table.item(target_row,
target_col)
# item may not exist for empty cells
if item is None:
item = qt.QTableWidgetItem()
self.table.setItem(target_row,
target_col,
item)
# ignore empty strings
if table_data[row_offset][col_offset] != "":
if not item.flags() & qt.Qt.ItemIsEditable:
protected_cells += 1
continue
item.setText(table_data[row_offset][col_offset])
EDIT: target_row = selected_row + row_offset ...

Related

How to update Predecessor or PredecessorList Object in Smartsheet Python SDK

I have a loop that creates a parent row and a variable number of child rows. I would like to update the Predecessor field of the child row with the prior child row number (when more than one child row is added). I am having difficulty adding the 2 required arguments to the 'cell.object_value' object (object_type & predecessors).
for key, value in record.items():
new_cell = ss.models.Cell()
if k == 'Predecessors':
pred = []
pred_update = {}
pred_update['rowId'] = predecessor_row_id
pred_update['type'] = 'FS'
pred.append(pred_update)
row = ss.Sheets.get_row(sheet_id, predecessor_row_id)
new_cell.object_value = str(row.row_number)
new_cell.object_value.predecessors = pred
new_cell.object_type = "PREDECESSOR_LIST"
EDIT:
The code runs without error however the information contained in the new_cell.object_value.predecessors argument is not being transferred into SmartSheet.
Setting predecessors is little tricky. You were on the right track. However, when you set the value on the cell, you just need to set the object_value. Like this:
# init the object_value for predecessor cell
pred_cell.object_value = ss_client.models.ObjectValue()
# set the object_type to PREDECESSOR_LIST
pred_cell.object_value.object_type = "PREDECESSOR_LIST"
# set the object_value for the cell with the PredecessorList
pred_cell.object_value = pred_list
A full Gist of the operation can be found here.
predecessors is a property of the object value, not the cell directly.
new_cell.objectValue.predecessors = pred

Can't select entire row of data from QTableWidget

Problem Statement
I'm trying to select rows of data from my QtableWidget and print them out to my console just so I can test some things, with the end goal being able to plot the data. However I can never grab the whole row of data.
Background
I have made a GUI that can embed several QTableWidgets by importing a specifically formatted CSV file. The goal is to be able to pull data from multiple rows from the same or different tables and then plot them in a side by side fashion. Where each row of data will be its own dataset and have its own plot, but there will be multiple plots on the same figure.
To complete this task I have made a window called CompareWindow that opens when a Qpushbutton called "Compare" is pressed. The window prompts the user to type in the names of the tables and the respective rows from that table they wish to plot.
After this information is submitted I have dictionary that I can reference which has saved all the QTableObjects that have been instantiated. Where the keys are the names given to the tables which are connected to their corresponding Table Object.
Problem
The two main methods I have tried to grab the row data areā€¦
The first idea was using TableObject.selectRow() command I would iterate through the rows I wanted, but whenever I did this to it would return a nonetype.
The second method I tried was to iterate a given rows columns so it would fill a list one by one by appending the item values. However when I did this it only filled the list with the same number repeatedly, which was the first cell in my Qtable.
Even when I explicitly called a certain row or column I would get the same output. The output being pulled is .12, the number from the first cell in my CSV file.
Here is the code in question I'm having problems with.
def initiateMultiPlot(self, tableV, rowV, PlotV):
"""
1. Match TableName values with the key values in our TableDB
2. When we find a match look at that key's corresponding Table Object, and iterate
through that objects rows and select the rows specified by rowV
3.Call plot for those values
"""
#calls my class and creates a blank figure where eventually we will plot data on
f = CreateFigure.FigureAssembly()
print("")
for i in tableV:
"""
tableV: is list of strings that represent assigned tablenames [Table1, Table2, Table3]
rowV: is a list, containing lists representing rows from corresponding Tables the user wishes to plot.
for example [[1,2],[3,4],[1]] means rows 1,2 from table1, rows 3,4 from table2... so on
PlotV: is a string that is ethier "box" or "whisker" to tell what method to plot. Default right now
is to do a simple boxplot
"""
print("Creating table instance")
#Table Dictionary is setup so the names of the Tables (tableV) are the keys of the dictionary
#and the actual table objects are referenced by these keys
self.TableOBJ = self.TableDictionary[i]
print("Data Type for the table object is..................{}".format(type(self.TableOBJ)))
#empty list that will store our row data
self.Elements = []
try:
for rows in rowV:
for i in rows:
print("rowV value is... {}".format(rowV))
print("current row list{}".format(rows))
print("i value is {}".format(i))
print("itterating")
for j in range(self.TableOBJ.columnCount()):
print("i value is ...{}".format(i))
print("j value is .... {}".format(j))
#FIRST idea try selecting entire row of data
print("i value is ...{}".format(i))
print("j value is .... {}".format(j))
#entire row returns none-type
EntireRow = self.TableOBJ.selectRow(i)
print(EntireRow)
#selecteditems
#SECOND idea try using for loop and iterating through every value in a row
item = self.TableOBJ.itemAt(i,j)
#explicit call for (row 1, col 1) and (row 3, col 3), both which output .12
print(self.TableOBJ.itemAt(1,1).text())
print(self.TableOBJ.itemAt(3,3).text())
print("printing item...... {}".format(item))
element = item.text()
print(element)
#list of .12
self.Elements.append(element)
#elements = [self.TableOBJ.item(i, j).text() for j in range(self.TableOBJ.columnCount()) if
# self.TableOBJ.item(i, j).text() != ""]
#print(elements)
except Exception as e:
print(e)
print(self.Elements)
Here is my GitHub link containing all my files: https://github.com/Silvuurleaf/Data-Visualize-Project
The problem occurs in my file Perspective.py in the method initiateMultiPlot. The file CompareWindow.py sends a signal to my Perspective.py and is connected to initateMultiPlot. Please inquire if anything requires more in depth explanation.
According to the documentation:
QTableWidgetItem *QTableWidget::itemAt(int ax, int ay) const
Returns the item at the position equivalent to QPoint(ax, ay) in the
table widget's coordinate system, or returns 0 if the specified point
is not covered by an item in the table widget.
That is, returns the given item x and y which are graphical coordinates with respect to QTableWidget, and clearly is not what you are looking for.
You must use the item():
QTableWidgetItem *QTableWidget::item(int row, int column) const
Returns the item for the given row and column if one has been set;
otherwise returns 0.
But in your case will not work unless you do the following change:
class CreateTable(QTableWidget):
....
for j in range(0, m):
self.item = QTableWidgetItem(str(round(ValList[j], 6)))
# print("{}, {}".format(i, j))
self.setItem(i, j, self.item)
to:
class CreateTable(QTableWidget):
....
for j in range(0, m):
item = QTableWidgetItem(str(round(ValList[j], 6)))
# print("{}, {}".format(i, j))
self.setItem(i, j, item)
That is, you change your self.item to item.
The problem is that at first glance the error is rather difficult, the QTableWidget class has an item() function, but when you use the self.item statement you are replacing that call, ie when python reads that statement it will use the attribute and not the function , So you get the error:
TypeError 'xxx' object is not callable

PyQt treeview edit text on doubleclick

So I've been trying to implement a proper TreeView that displays directories and files depending on user input - I am allowing the user to add directories and files to his "project" either recursively or otherwise, after which I create my own tree view of the contents of said project.
Now, my problem is that even though most of the documentation and other questions I've found on this subject seem to want to disable the editability of treeview items, I am trying ( and failing ) to find a way to enable it. What I would like is for the user to be able to double click on any cell in any column of my treeview and then edit its contents. Does anyone know how to do this?
Below is the code I am using to generate a tab in a tabView Widget, after which i then add the TreeView. The items of the TreeView are later added through the AddParent and AddChild methods.
class treeTab(QtWidgets.QWidget):
def __init__(self,core,main,label):
super (treeTab,self).__init__()
self.label = label
self.core = core
self.sizes = core.UISizes
self.tab_sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding,QtWidgets.QSizePolicy.Expanding)
self.tree = QtWidgets.QTreeWidget(self)
self.tree.setColumnCount(len(self.sizes.projectTreeColumnLabels))
self.tree.setHeaderLabels(self.sizes.projectTreeColumnLabels)
self.tree.setSizePolicy(self.tab_sizePolicy)
self.tree_layout = QtWidgets.QGridLayout()
self.tree_layout.objectName = self.label + "TreeGridLayout"
self.tree.setLayout(self.tree_layout)
self.treeroot = self.tree.invisibleRootItem()
self.tree.setSelectionMode(Qt.QAbstractItemView.ContiguousSelection)
def addParent(self, parent, column, title, data):
item = QtWidgets.QTreeWidgetItem(parent, [title])
item.setData(column, QtCore.Qt.UserRole, data)
item.setChildIndicatorPolicy(QtWidgets.QTreeWidgetItem.ShowIndicator)
item.setExpanded (True)
return item
def addChild(self, parent, column, title, data):
item = QtWidgets.QTreeWidgetItem(parent, [title])
item.setData(column, QtCore.Qt.UserRole, data)
item.setText(1,data.print_tags())
item.setText(2,data.category.name)
item.setText(3,data.format)
item.setCheckState (column, QtCore.Qt.Unchecked)
item.setFlags(item.flags() or QtCore.Qt.ItemIsEditable)
return item
You have confused binary operators and Boolean operators. Boolean operators (such as and and or) are used with Boolean values (such as True and False) to produce a single True or False once the expression is evaluated.
However, the flags and not Boolean values. They are integers which are powers of 2 (or binary numbers with only one bit set), such that they can be combined into a single integer that represents whether each flag is enabled or disabled. For instance, 2 is represented in binary as 0b010. 4 is represented as 0b100. If you bitwise or these together, you get 0b110, indicating that both the flag equal to 2 and the flag equal to 4 is set. However the flag equal to 1 is not set (the 0 in the 0b110).
In short, you should set the flags using the bitwise or operator (|):
item.setFlags(item.flags() | QtCore.Qt.ItemIsEditable)
So I managed to figure this one out - it turned out to be quite simple in the end :)
In order to create an 'editable' treeView item where you can double click on the text of a particular item to edit it, you simply need to change the widget contained in that particular column of the item to a QLineEdit Widget that deletes itself upon pressing enter. The code for this looks as follows:
This is the code to connect the double click event to a method that gets the currently selected item and replaces it with a QLineEdit Widget:
self.tree.itemDoubleClicked.connect(self.editItem)
def editItem(self,*args):
itm = self.tree.itemFromIndex(self.tree.selectedIndexes()[0])
column = self.tree.currentColumn()
edit = QtWidgets.QLineEdit()
edit.returnPressed.connect(lambda*_:self.project.setData(column,edit.text(),itm,column,self.tree))
edit.returnPressed.connect(lambda*_:self.update())
self.tree.setItemWidget(itm,column,edit)
Note especially the combination of the following code:
itm = self.tree.itemFromIndex(self.tree.selectedIndexes()[0])
column = self.tree.currentColumn()
This code in effect gives you both the row and the column of the currently selected item, which is useful if you want to edit column items separately.
Now You'll be asking yourself why I'm passing so many parameters to the 'setData' method: this is purely for the purposes of my particular project, so don't worry about it. the 'returnPressed' event simply needs to connect to the proper method to handle whatever data it contains, and then delete itself. in my code, this looks like this:
def setData(self,dataTypeIndex,data,item,column,tree):
if dataTypeIndex == 0:
# filename
self.name = data
elif dataTypeIndex == 1:
# tags
data = data.split(",")
self.tags = []
for tag in data:
self.tags.append(Tag(tag))
elif dataTypeIndex == 2:
# category
self.category.name = data
tree.setItemWidget(item,column,None)
This last line of code ( tree.setItemWidget(item,column,None) ) is where the QlineEdit is unparented and therefore effectively removed.

How to search through a gtk.ListStore in pyGTK and remove elements?

I have the following code (where store is a gtk.ListStore and titer is a gtk.TreeIter. The docs say that if there is no next row, iter_next() will return None, hence the break when that is found. It is supposed to search through the ListStore of (int, str) and remove the one item whose int component matches item_id.
while True:
if store.get_path(titer)[0] == item_id:
store.remove(titer)
break
else:
titer = store.iter_next(titer)
if titer is None:
break
However, if an element in the middle has previously been deleted, instead of titer.iter_next() pointing to the next valid element, it points to None. This means that if the element with the right int value is after the previously deleted item, it never gets found. Is there a correct way to search through a gtk.ListStore to remove items?
The only mistake I notice is store.get_path(titer)[0], which will just get the row number of the list model. It should be store.get_value(titer, 0).
By the way, your code can be expressed in a simpler style using the (PyGTK-only) TreeModelRow:
for row in store:
if row[0] == item_id:
store.remove(row.iter)
break
sanity check: make sure there is data in your list store when trying to search it
column_number = 0
search_term = 'foo'
iter_child = tree_model.get_iter_first()
tree_path = None
while iter_child:
if (tree_model.get_value(iter_child, column_number) == search_term):
tree_path = tree_model.get_path(iter_child)
iter_child = tree_model.iter_next(iter_child)
view.row_activated(tree_path, column_number)
view.set_cursor(tree_path, column_number, True)

python: getting rid of values from a list

drug_input=['MORPHINE','CODEINE']
def some_function(drug_input)
generic_drugs_mapping={'MORPHINE':0,
'something':1,
'OXYCODONE':2,
'OXYMORPHONE':3,
'METHADONE':4,
'BUPRENORPHINE':5,
'HYDROMORPHONE':6,
'CODEINE':7,
'HYDROCODONE':8}
row is a list.
I would like to set all the members of row[..]='' EXCEPT for those that drug_input defines, in this case it is 0, and 7.
So row[1,2,3,4,5,6,8]=''
If row is initially:
row[0]='blah'
row[1]='bla1'
...
...
row[8]='bla8'
I need:
row[0]='blah' (same as before)
row[1]=''
row[2]=''
row[3]=''
...
...
row[7]='bla7'
row[8]=''
How do I do this?
You could first create a set of all the indexes that should be kept, and then set all the other ones to '':
keep = set(generic_drugs_mapping[drug] for drug in drug_input)
for i in range(len(row)):
if i not in keep:
row[i] = ''
I'd set up a defaultdict unless you really need it to be a list:
from collections import defaultdict # put this at the top of the file
class EmptyStringDict(defaultdict):
__missing__ = lambda self, key: ''
newrow = EmptyStringDict()
for drug in drug_input:
keep = generic_drugs_mapping[drug]
newrow[keep] = row[keep]
saved_len = len(row) # use this later if you need the old row length
row = newrow
Having a list that's mostly empty strings is wasteful. This will build an object that returns '' for every value except the ones actually inserted. However, you'd need to change any iterating code to use xrange(saved_len). Ideally, though, you would just modify the code that uses the list so as not to need such a thing.
If you really want to build the list:
newrow = [''] * len(row) # build a list of empty strings
for drug in drug_input:
keep = generic_drugs_mapping[drug]
newrow[keep] = row[keep] # fill it in where we need to
row = newrow # throw the rest away

Categories

Resources