Finding / removing a row from a QStandardItemModel by item data - c++

I have a QStandardItemModel with a single column (represents a list). Each item in the list has a unique integer ID stored as the QStandardItem's data (via QStandardItem::setData which I guess is into Qt::UserRole+1 by default).
Given one of these IDs, I'd like to find and remove the corresponding row from the model. Right now I'm doing this:
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId) {
foreach (const QStandardItem *item, model->findItems("*", Qt::MatchWildcard)) {
if (item->data() == sessionId) {
model->removeRow(item->index().row());
break;
}
}
}
It works fine, but every single line of that function makes me cringe. Is there a cleaner way to do any of this?

How about traversing the QStandardItemModel directly? Something like this:
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId)
{
for (int i = 0; i < model->rowCount(); ++i)
{
if (model->item(i)->data() == sessionId)
{
model->removeRow(i);
break;
}
}
}
Not sure how QStandardItemModel behaves with random access, maybe your method is more efficient.
Edit:
Actually, there is a function to do what you want: QAbstractItemModel::match
It returns a QModelIndexList with all entries that have matching data in a given role.
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId)
{
QModelIndexList list = model->match(model->index(0, 0), Qt::UserRole + 1, sessionId);
if (!list.empty())
model->removeRow(list .first().row());
}
Setting data to a specific role can be done as follows:
model->setData(model->index(row, col), QVariant(13), Qt::UserRole + 1);

You need to get the row index from your item id.
A more effective way could be to use a QMap with the row index as value and the item id as a key.
In this case, you also need to maintain the map values every time you add/remove a row.
If you don't have 3 millions items in your list, just keep it simple and use your code.
By optimize this code, you probably also add complexity and reduce maintainability, and you get is 0,05 ms instead of 0,06 ms.
In GUI code, I often have code like this : it's simple, everyone get it immediatly and it does the job. It' also fast enough.

You're using findItems wrong, it can already return the item you want just by passing the value you're searching for. If you call it like you're doing right now you're looping through your items at least two times, since findItems must iterate through all the items to find those that match your pattern, in your case all items match, then you iterate the returned items again to find the sessionId.
void NetworkManager::removeSessionFromModel (QStandardItemModel *model, int sessionId) {
auto items = model->findItems(QString::number(sessionId));
if (!items.empty()) {
auto row = items.first()->index().row();
model->removeRow(row);
}
}
Alternatively you can use the match method since findItems uses that internally, so you avoid allocating the StandardItem just to get its index. Also match returns right after the number of items matching the pattern, in this case the value of sessionId, are found so it doesn't always iterate all the items; that's more efficient. Obviously if the value is not found after iterating all the items it returns an empty list.
auto start = model->index(0, 0);
auto indexes = model->match(start, Qt::UserRole + 1, QString::number(sessionId), 1, Qt::MatchExactly);
if (!indexes.empty()) {
auto row = indexes.first().row();
model->removeRow(row);
}

Related

Accessing a Lua table within a table from C++ side

I'm trying to transfer a table where there may be nested tables from the lua and write to a .ini file. But I just can not how I need to go on the stack to get data from nested tables. This code does not work as it should. Function setData work not correctly. What could be the problem?
C++ code
int sasl::LuaUtilities::luaWriteIni(LuaState inOutState)
{
string path;
boost::property_tree::ptree iniTree;
LUA_CHECK_NARG_EQ(inOutState, 2);
LUA_GET_PARAM(inOutState, 1, path);
int nargs = lua_gettop(inOutState);
for (int i = 1; i <= nargs; i++) {
if (lua_istable(inOutState, nargs)) {
setData(inOutState, nargs, iniTree);
}
}
return 0;
}
void sasl::LuaUtilities::setData(LuaState inOutState, int index, boost::property_tree::ptree & inIniTree)
{
// Push an initial nil to init lua_next
lua_pushnil(inOutState);
// Parse the table at index
while (lua_next(inOutState, index))
{
if (lua_istable(inOutState, index))
{
setData(inOutState, index, inIniTree);
}
else
{
string key = lua_tostring(inOutState, -2);
string value = lua_tostring(inOutState, -1);
}
// Pop value, keep key
lua_pop(inOutState, 1);
}
return;
}
Lua code
t = {}
local fileName = findResourceFile("test.ini")
t = readINI(fileName)
writeINI(fileName, t) --this not work in c++ side
There are two problems. lua_istable(inOutState, index) is wrong, because index is not the value of the key retrieved by next. That index is always the table you're iterating over. So you'll infinitely recurse over the same table when you call setData with that index.
In fact, passing index to setData itself is almost certainly wrong. Or at least, it's probably not right. You want to use relative indices here, but calling next pushes an extra value onto the stack.
What you probably want to do is have setData assume that the table to iterate over is at index -1 (ie: the top of the stack). That way, you're just calling lua_next(state, -2) (this is -2 because the key to get the next one for is at the top). And when you recursively call setData for a table value, you don't need to provide an index, because the table value is already at the top of the stack.
The second problem is that you never write the key/value pairs. You also never check to see if the value is something which can be converted to a string. It could be a Lua userdata.

How to delete selected row of a List Control in MFC?

I want to delete selected row of list control in MFC.
I have created a Delete Button, So If any row (it could be one or more than one row) is/are selected and I press delete button that/those rows should be deleted.
If lets say there are 100 rows and I select rows from 50-60, all the rows in this range should be deleted and rest of rows should have indexes from 1 to 90. means indexing should be proper after deletion also.
Adapted from this MSDN article:
UINT i, uSelectedCount = m_myListCtrl.GetSelectedCount();
int nItem;
if (uSelectedCount > 0)
for (i=0; i < uSelectedCount; i++)
{ nItem = m_myListCtrl.GetNextItem(-1, LVNI_SELECTED);
ASSERT(nItem != -1);
m_myListCtrl.DeleteItem(nItem);
}
When deleting a multiple selection having several items I prefer to do it like this:
int nItem = -1;
while ((nItem = m_list.GetNextItem(nItem, LVNI_SELECTED)) != -1)
{
if (m_list.DeleteItem(nItem))
nItem--;
}
Notice the important nItem--; line
UPDATE
I had to give up from this approach as the ItemData of an element gots fucked up. If I remove the nth element then the n+1 element will be my new nth. That element has a completely screwed up Itemdata.
UPDATE 2
I also tried with
int nItem = -1;
while ((nItem = m_list.GetNextItem(-1, LVNI_SELECTED)) != -1)
{
m_list.DeleteItem(nItem);
}
This approach also has the problem of screwing the Itemdata I reported before.
The following approach worked perfecly for me:
std::stack< int > items;
int nItem = -1;
while ((nItem = myListCtrl.GetNextItem(nItem, LVNI_SELECTED)) != -1)
{
items.push(nItem);
}
bool removed = false;
while (!items.empty())
{
nItem = items.top();
if (myListCtrl.DeleItem(nItem))
removed = true;
items.pop();
}
if (removed)
// update some application state;
Explanation:
When you remove things from the end to the start, you do not have to worry about the validity of positions. As the CListCtrl does not provide a GetPrevItem or any other way to get items in the reverse order, you need to store them in a collection where you can have that reverse order.
The most practical way to do it is to use a stack. Due to the way it works, you will put things in there in the normal order, and when you retrieve things they are automatically in reverse order.

QComboBox::findData() always returns -1

I am trying to get the id of the record in the model from a QCombobox with findData(index), but when select a item, it retunrs -1. It has been working in another project, but this is the second one that doesn't work. Here's my code:
modAnfi = new QSqlTableModel(this);
modAnfi->setQuery("SELECT id, (nombres || ' ' || apellidos) as Nombre, nombres, apellidos FROM tbPersonas WHERE activo=1");
comboAnfitrion->setModel(modAnfi);
comboAnfitrion->setModelColumn(1);
comboAnfitrion->setEditable(true);
comboAnfitrion->completer()->setCompletionMode(QCompleter::PopupCompletion);
connect(comboAnfitrion, SIGNAL(currentIndexChanged(int)), this, SLOT(currentIndexChangeAnfitrion(int)));
and:
void controlReg::currentIndexChangeAnfitrion(int index)
{
qDebug() << comboAnfitrion->findData(index); // -1
qDebug()<< comboAnfitrion->itemData(1); // QVariant(Invalid)
}
Thanks for your time, any help will be appreciated.
You have to use the model you assign to the comboBox, use the index to look for it:
modAnfi->data(modAnfi->index( index, 0));
Check the QComboBox documentation; from the findData description, quoting:
Returns the index of the item containing the given data
Where you are passing index as the "given data". However, index is already an index in the combobox. But you're obviously not looking for an index (since you already have one).
I suspect you actually want to call the itemData method instead? That would retrieve the data associated with an element for a given index.

Find out which column is selected in a QTableWidget

I have a QTableWidget with SelectionMode set to SingleSelection, and SelectionBehavior set to SelectColumns. This means that only a single column can be selected.
But I later need to find out which column is selected, and the only functions I can use are selectedIndexes() or selectedItems(), both of which return entire lists, which is wasteful.
Is there a way to do this more efficiently?
Your approach with the selectedItems() was correct.
As QT cant know that you've set your widget to singlerow/column selection it offers those functions to return a QList<>.
in your case you can work on those by using .first().
Evne though I suggest to use the signals currentColumnChanged() to react in your application
( http://harmattan-dev.nokia.com/docs/library/html/qt4/qitemselectionmodel.html#currentColumnChanged )
you could always iterate over all columns of the selected row via selectionModel()->isColumnSelected()
( http://qt-project.org/doc/qt-4.8/qitemselectionmodel.html#isColumnSelected )
connect(tableWidget, SIGNAL(currentCellChanged(int,int,int,int), this, SLOT(onCellChanged(int,int,int,int)));
void Class::onCellChanged(int curRow, int curCol, int preRow, int preCol)
{
current_Col = curCol;
// curRow, preRow and preCol are unused
}
connect(tableWidget->selectionModel()
, SIGNAL(currentColumnChanged(QModelIndex,QModelIndex))
, SLOT(onColumnChanged(QModelIndex)));
...
void Class::onColumnChanged(const QModelIndex &index)
{
int col = index.column();
}
It seems that the function selectedRanges() does what I need. It returns a list of the selected ranges, but since it's a single column, this list would have only one item (so it's efficient, no big list need to be created).
int column = ui->tableWidget->selectedRanges().front().leftColumn();
currentColumn() returns an int of the current selected column.

How to delete arbitrary objects in repeated field? (protobuf)

I have some entries in the repeated field in my proto. Now I want delete some of them. How can I accomplish this? There is a function to delete the last element, but I want to delete arbitrary elements. I cant just swap them because the order is important.
I could swap with next until end, but isn't there a nicer solution?
For Protobuf v3
iterator RepeatedField::erase(const_iterator position) can delete at arbitrary position.
For Protobuf v2
You can use the DeleteSubrange(int start, int num) in RepeatedPtrField class.
If you want to delete a single element then you have to call this method as DeleteSubrange(index_to_be_del, 1). It will remove the element at that index.
According to the API docs, there isn't a way to arbitrarily remove an element from within a repeated field, just a way to remove the last one.
...
We don't provide a way to remove any element other than the last
because it invites inefficient use, such as O(n^2) filtering loops
that should have been O(n). If you want to remove an element other
than the last, the best way to do it is to re-arrange the elements so
that the one you want removed is at the end, then call RemoveLast()
...
What I usually do in these cases is to create a new Protobuf (PB) message. I iterate the repeated fields of the existing message and add them (except the ones you don't want anymore) to the new PB message.
Here is example:
message GuiChild
{
optional string widgetName = 1;
//..
}
message GuiLayout
{
repeated ChildGuiElement children = 1;
//..
}
typedef google_public::protobuf::RepeatedPtrField<GuiChild> RepeatedField;
typedef google_public::protobuf::Message Msg;
GuiLayout guiLayout;
//Init children as necessary..
GuiChild child;
//Set child fileds..
DeleteElementsFromRepeatedField(*child, guiLayout->mutable_children());
void DeleteElementsFromRepeatedField(const Msg& msg, RepeatedField* repeatedField)
{
for (RepeatedField::iterator it = repeatedField->begin(); it != repeatedField->end(); it++)
{
if (google_public::protobuf::util::MessageDifferencer::Equals(*it, msg))
{
repeatedField->erase(it);
break;
}
}
}
Although there's no straight-forward method you still can do this (for custom message using reflection). Code below removes count repeated field items starting from row index.
void RemoveFromRepeatedField(
const google::protobuf::Reflection *reflection,
const google::protobuf::FieldDescriptor *field,
google::protobuf::Message *message,
int row,
int count)
{
int size = reflection->FieldSize(*message, field);
// shift all remaining elements
for (int i = row; i < size - count; ++i)
reflection->SwapElements(message, field, i, i + count);
// delete elements from reflection
for (int i = 0; i < count; ++i)
reflection->RemoveLast(message, field);
}