I have a C++ application where I am reading pcap file format and processing usb packets. For each packet, I would like to create a QListWidgetItem to which I am storing some data and then adding it to the QListWidget. Here is where the trouble begins. According to QListWidget documentation, after inserting item :
The list widget will take ownership of the item.
So what I thought is that its on QListWidget to delete all QListWidgetItems. The items are added fine, but when I close my app(I suppose thats when desctructor od QListWidget is called so he is calling desctructor of each QListWidgetItem) I get delete_scalar exception. According to call stack, deletion of some QListWidgetItem triggers it :
Snippet of my code (this function is being called for each packet in pcap file and is responsible for creating and adding items):
void ItemManager::ProcessPacket(QByteArray packetData)
{
const unsigned char* packet = (unsigned char*)packetData.data();
PUSBPCAP_BUFFER_PACKET_HEADER usbh = (PUSBPCAP_BUFFER_PACKET_HEADER)packet;
QListWidgetItem* item = new QListWidgetItem;
//set USBPCAP header to item
QByteArray usbhArray((const char*)packet, sizeof(USBPCAP_BUFFER_PACKET_HEADER));
item->setData(dataHolder->USBPCAP_HEADER_DATA, QVariant(usbhArray));
packet += sizeof(USBPCAP_BUFFER_PACKET_HEADER);
if (usbh->transfer == USBPCAP_TRANSFER_ISOCHRONOUS || usbh->transfer == USBPCAP_TRANSFER_CONTROL) //check for optional header data
{
int additionalDataSize = usbh->headerLen - sizeof(USBPCAP_BUFFER_PACKET_HEADER);
if (additionalDataSize > 0)
{
//set additional header data to item
QByteArray additionalDataArray((const char*)(packet), additionalDataSize);
item->setData(dataHolder->TRANSFER_OPTIONAL_HEADER, QVariant(additionalDataArray));
packet += additionalDataSize;
}
else
{
item->setData(dataHolder->TRANSFER_OPTIONAL_HEADER, QVariant()); //QVariant creates invalid QVariant, later i just need to check with QVariant::isValid()
}
}
else
{
item->setData(dataHolder->TRANSFER_OPTIONAL_HEADER, QVariant());
}
//set leftover data to item
QByteArray leftoverDataArray((const char*)packet, usbh->dataLength);
item->setData(dataHolder->TRANSFER_LEFTOVER_DATA, QVariant(leftoverDataArray));
listWidget->insertItem(listWidget->count(), item);
}
Calling of ProcessPacket function :
void ItemManager::ProcessFile(QString filename, bool liveReading)
{
if (fileReader.OpenNewFile(filename))
{
if (fileReader.ReadFileHeader())
{
while (!stopButtonClicked)
{
while (!fileReader.EndOfFile())
{
QByteArray packetData = fileReader.GetPacket();
if (!pauseButtonClicked)
{
ProcessPacket(packetData);
}
}
parent->Refresh(); //this is just calling QCoreApplication::processEvents();
if (!atBottomOfList)
{
listWidget->scrollToBottom();
}
if (liveReading)
{
Sleep(50);
}
else
{
return;
}
}
}
}
}
EDIT
I found out that this problem is happening only when appending to QListWidget through ItemManager class. In my main Q_OBJECT class USB_Packet_Analyzer(which holds QlistWidget that I am appending to) i have slot on_OpenButton_clicked() which looks like this :
void USB_Packet_Analyzer::on_OpenButton_clicked()
{
QString fil = QFileDialog::getOpenFileName(this, "Select source file", ".", "Pcap files (*.pcap)");
ItemManager manager(ui.listWidget,this);
manager.ProcessFile(fil, ui.liveCaptureRadioButton->isChecked());
}
where in ItemManager class constructor looks like this :
ItemManager::ItemManager(QListWidget* listWidget, USB_Packet_Analyzer* parent)
{
this->stopButtonClicked = false;
this->pauseButtonClicked = false;
this->atBottomOfList = false;
this->listWidget = listWidget;
this->parent = parent;
this->dataHolder = DataHolder::GetDataHolder();
}
Now, if I add items in on_OpenButton_clicked() slot and close the app, everything is fine. But when I create ItemManager instance and append items in that class, the error occurs. Could it be that I am not allowed to pass QListWidget* as parameter ?
You have to create a new item inside your loop. You must not reuse the pointer because no content is copied, only the pointer is stored within the QListWidget
To this day I am not completely sure what caused the error, but it was fixed after I modified USBPCAP structs which I were using. They use all kind of stuff like #pragma pack(1) , #pragma pack(push) and so on. It looks like I was missing some of those, which might result in some undefined behaviour while using them.
Related
Sorry if the code is sloppy, I've only been working with QT for the past couple of weeks. I'm working on a small game while studying, and right now I'm working on when the player buys an item from the shop it will be placed it into the character bag.
The Problem is when I buy the item once, it works fine. But if I delete the item after purchase then repurchase the same item again it will put two of the same item into the array even though I purchased one.
I have 3 files used for this. Inventory.cpp, GameScreen.cpp, Shops.cpp.
GameScreen.cpp is the main file. Here I just initialize everything.
void GameScreen::initGame(QString &characterName, QString &characterProfession){
//PASS CHARACTERNAME AND CHARACTERPROFESSION INTO INVENTORY
inv.initCharacter(characterName, characterProfession);
//INIT BAG
inv.initBag();
//INIT MONEY
inv.initMoney();
//SHOPS INITS
mos.initShop();
mos.passMoneyToShop(inv.gold, inv.silver, inv.copper);
}
This will open the shop window. The Item connect is apart of my problem.
void GameScreen::on_mapOneShopB_clicked()
{
mos.setModal(true);
//SEND THE ITEM INTO BAG
connect(&mos, SIGNAL(getItemFromMapOneShop(const QString&)), &inv, SLOT(bagAddElement(const QString&)));
mos.show();
mos.exec();
}
Moving into Shops.cpp. I use polymorphism in this file and that's the reason why you see MapOneShop:: instead of Shops::. Sorry about the confusion. But moving on, I hit the button and it subtracts the item price from my amount of gold.
void MapOneShop::on_buyB_clicked()
{
//ONLY WORKS IF I HIT THE ITEM NAME COLUMN THEN HIT BUY
gold -= itemPrice[ui->treeWidget->currentColumn()];
//UPDATE INFORMATION
updateInformationVAndMoneyAfterBuy();
}
It then moves into updateInformationVAndMoneyAfterBuy(); which checks that I spent money, and If I did it will emit the item I need. I just emit back to GameScreen.cpp inside of void GameScreen::on_mapOneShopB_clicked() and pass them into Inventory.cpp.
void MapOneShop::updateInformationVAndMoneyAfterBuy()
{
//UPDATE INFORMATIONV FIRST TO CHECK MONEY CHANGES
if(goldCheck != gold)
{
emit getUpdatedMoneyFromShop(gold, silver, copper);
//ITEM NAME IS WHERE I STORE THE NAMES OF THE ITEMS
emit getItemFromMapOneShop(this->itemName[ui->treeWidget->currentColumn()]);
}
}
Now moving into Inventory.cpp. When the item gets passed into bagAddElement after deleting then repurchasing, I get two of the same items even though only 1 should be passed in. I am using a dynamic array for this. Below I will also show the void Inventory::on_deleteB_clicked, void Inventory::bagDeleteAt and also void Inventory::bagLWPrint functions. The items value is my array holding the shopItem strings.
void Inventory::bagAddElement(const QString& shopItem)
{
//I USE THIS TO CHECK THE VALUE shopItem.
qDebug()<<"bagAddElement: " << shopItem;
//IF THE CURRENT POSITION IN THE BAG
//IS BIGGER THAN THE CURRENT SIZE
//IT WILL INCREASE THE BAG FOR US
if(nrOfEl >= bagSize)
{
bagExpand();
}
//CHECK FOR VALUES INSIDE ARRAY AND CHECK IF THEY ARE NULL
//IF SO, IT WILL ADD THE ITEM INTO THE NULL POSITION
for(int i = 0; i < bagSize; i++)
{
if(items[i] == nullptr)
{
nrOfEl = i;
items[nrOfEl++] = shopItem;
break;
}
}
//UPDATE WIDGET LIST
bagLWPrint();
}
Here I pass the currently selected row value inside bagLW and pass it into void Inventory::bagDeleteAt.
void Inventory::on_deleteB_clicked()
{
//BagLW IS A LIST WIDGET
bagDeleteAt(bagLW->currentRow());
}
Now we look for the what's inside items[row] and set it to nullptr. Then we go into void Inventory::bagLWPrint.
void Inventory::bagDeleteAt(int row)
{
if(items[row] == nullptr)
{
//IF THE ITEM IS ALREADY NULL WILL PRINT A MESSAGE
QMessageBox::information(this,"Bag","No item in that slot");
}
else
{
//SET CURRENTLY SELECTED ITEM TO NULL
items[row] = nullptr;
bagLWPrint();
}
}
I clear bagLW and refill it with the updated array.
void Inventory::bagLWPrint()
{
bagLW->clear();
for(int i = 0; i < bagSize; i++)
{
if(items[i] != nullptr)
{
bagLW->addItem(items[i]);
}
if(items[i] == nullptr)
{
bagLW->addItem(items[i]);
}
}
}
Other notes.
-The item only gets doubled when inserted into void Inventory::bagAddElement.
-I tried adding another item inside of void GameScreen::initGame and it did not double after deleting and re-entering.
Sorry for such a long question. And any help would be much, much appreciated.
void Inventory::bagExpand()
{
//1: INCREASE BAGSPACE
bagSize *= 2;
//2: CREATE TEMP ARRAY
QString *tempItems = new QString[bagSize];
//3: COPY OVER VALID VALUES FROM OLD ARRAY
for(int i = 0; i < nrOfEl; i++)
{
tempItems[i] = items[i];
}
//4: DELETE OLD ARRAY MEMORY
delete[] items;
//5: POINT OLD ARRAY POINTER TO NEW ARRAY LOCATION
items = tempItems;
//PRINT BAGLW - (UDPATE)
bagLWPrint();
qDebug()<<"Bag has increased";
}
The problem is that you connect and re-connect the getItemFromMapOneShop signal on every button click. That means that on first click, you add it once (signal triggered, 1 slot connected). On the second click, you add it twice (signal triggered, 2 slots connected). And so on.
QObjects manage a list of all connected slots per signal, and call each of them. Connecting a slot multiple times will call it that many times
You should connect the singal only once, e.g. in the constructor; or disconnect the signal when no longer used
As #Andéon Evain pointed out, you could also use Qt::UniqueConnection. This will not add a duplicate connection if it already exists (considering sender, signal, receiver, slot). That might be useful for cases where it's unknown if already connected; not in your simple case
I have an overloaded QTreeWidget class, with my SIGNALS: I have promoted it in my UI and when I listen promoted QTreeWidget object with a lambda syntax I have an error.
QObject::connect: signal not found in CustomTreeWidget.
MY CustomTreeWidget looks like:
.h
class CustomTreeWidget : public QTreeWidget
{
Q_OBJECT
public:
explicit CustomTreeWidget(QWidget *parent = 0);
~CustomTreeWidget() {
}
signals:
void currentNodeChanged(QSet<int> uids);
void deleteRequest(QVector<int> uids);
}
.cpp
CustomTreeWidget::CustomTreeWidget(QWidget *parent) : QTreeWidget(parent)
{
setAnimated(true);
connect(this, &CustomTreeWidget::customContextMenuRequested, this, [=](const QPoint &pos) {
this->m_bCustomMenuOpen = true;
const auto &&item = this->itemAt(pos);
QMenu myMenu;
bool ok = !(item) ? false : true;
if (ok) {
//თუ topLevelItem -ია მხოლოდ დამატების action -ი უნდა იყოს ჩართული.
if (item == this->topLevelItem(0) || item == this->topLevelItem(0)->child(0)) {
ok = false;
}
}
QAction *Removecnt = myMenu.addAction(tr("&წაშლა"), this, SLOT(DeleteNode()));
Removecnt->setIcon(QIcon(":/global_res/delete.png"));
Removecnt->setEnabled(ok);
myMenu.exec(this->mapToGlobal(pos));
});
}
void CustomTreeWidget::BFS(QTreeWidgetItem *item, QSet<int> &out)
{
std::queue<QTreeWidgetItem *> Q;
Q.push(item);
while (!Q.empty()) {
QTreeWidgetItem *now = Q.front(); Q.pop();
out.insert(this->m_mapUids[now]);
for (int i = 0; i < now->childCount(); i++) {
Q.push(now->child(i));
}
}
}
QSet<int> CustomTreeWidget::GetCurrentNodeUids()
{
QSet<int> uids;
if (!this->currentItem())
return uids;
this->BFS(this->currentItem(), uids);
return uids;
}
void CustomTreeWidget::DeleteNode()
{
QSet<int> nodes = this->GetCurrentNodeUids();
QVector<int> uids;
for (auto it : nodes) {
uids.push_back(it);
}
emit deleteRequest(uids);
}
My lambda looks like:
connect(ui->productTree, &CustomTreeWidget::deleteRequest, this, [=](QVector<int> uids) {
//logic
});
But this signal works with old syntax.
connect(ui->productTree, SIGNAL(deleteRequest(QVector<int>)), this, SLOT(checkSlot(QVector<int>)));
And this slot is.
void ProductForm::checkSlot(QVector<int> uids)
{
qDebug() << uids.size();
}
So what is problem lambda syntax?
This smells like the violation of the one definition rule (ODR) - perhaps due to a stale build folder. The problem is that the address of deleteRequest passed to the connect method is not the same as the address of the method visible from moc_CustomTreeWidget.cpp. Remove the build folder and try again. If it still doesn't work, start reducing your problem:
Create a minimization branch in the repository (if you're not using version control, you won't go anywhere with minimization).
Copy-paste the contents of the ui_CustomTreeWidget.h file into the CustomTreeWidget.cpp file, remove the #include line.
Inspect the pasted contents for instantiation of the productTree object with the correct type.
If that's correct, then remove everything else from the code, step-by-step, rebuilding and committing to the repository at each step that still reproduces. You should end up with a test case that is 20-30 lines long at most. And it'll be either obvious what's wrong, or you can modify the question with the test case.
I'm writing an app to create a file browser of the FS of a MTP device. I have done a first version but I think I can make it better designed.
Current, I have a wrote a mtp_wrapper.cpp which provide a class mtp with different method inside to get files, get folders, delete files...
For example, My mainwindow class contain:
void MainWindow::DisplayTree(uint32_t index, uint32_t parent)
{
LIBMTP_file_t *files;
LIBMTP_file_t *file;
MyTree->clear();
// files = m_device.GetFileAndFolder(index);
if(files == NULL)
return;
file = files;
while(file != NULL && file->parent_id == parent) {
LIBMTP_file_t *oldfile;
QTreeWidgetItem* item = new QTreeWidgetItem();
if(strcmp(file->filename,"Android") == 0)
item->setText(0,"AneedA");
else
item->setText(0,file->filename);
if (file->filetype == LIBMTP_FILETYPE_FOLDER) {
item->setText(1, "--");
item->setText(2, "--");
DisplaySubTree(item, file->item_id);
}
else {
const QDateTime dt = QDateTime::fromTime_t(file->modificationdate);
const QString textdate = dt.toString( Qt::TextDate );
item->setText(1,textdate);
item->setText(2, QString::number(file->filesize));
}
MyTree->addTopLevelItem(item);
oldfile = file;
file = file->next;
#if 0
m_device.DestroyFile(oldfile);
#endif
}
}
The first call of this, is using (0,0) as argument to display the whole tree. but I think it's not smart enough. The DisplayTree method should only called a list and there is no need to have a call to mtp_api.
I know that gtk have some method to store_list, set_list and reuse it but I'm currently under Qt and C++
Any idea, I can disconnect the mainwindow from the mtp ? The best should be to have the list/model filed by the mtp part. I have created an method used to scan the mtp device and the mainwindows through the tree method will reuse this list/model to display data.
I have a custom QWidget class called VideoWidget. Its source file looks something like this:
VideoWidget::VideoWidget(QWidget *parent, string test) :
QWidget(parent)
{
pathname=test;
QLabel *label= new QLabel(pathname.c_str(), this);
//...
}
string VideoWidget::getFilePath(){
return pathname;
}
In my MainWindow class I add the VideoWidget to a QListWidget through looping through a xml file and getting the string argument from that file like this:
QDomNode node = rootXML.firstChild();
while( !node.isNull() )
{
if( node.isElement() )
{
QDomElement element = node.toElement();
VideoWidget* mytest = new VideoWidget(this, element.attribute( "Pathname", "not set").toStdString());
QListWidgetItem* item = new QListWidgetItem;
item->setSizeHint(QSize(150,100));
ui->myList->addItem(item);
ui->myList->setItemWidget(item,mytest);
}
node = node.nextSibling();
}
This correctly fills my QListWidget with the VideoWidget where all the labels have a different value.
Now I'd like to get the pathname variable everytime I doubleclick on a item in the QListWidget like this:
connect(ui->myList,SIGNAL(doubleClicked(QModelIndex)),this,SLOT(playClip(QModelIndex)));
void MainWindow::playClip(QModelIndex index){
QListWidgetItem* item = ui->myList->itemAt(0,index.row());
VideoWidget* widget = dynamic_cast<VideoWidget*>(ui->myList->itemWidget(item));
cout << widget->getFilePath() << endl;
}
My problem is that widget->getFilePath() always returns the same value for every clicked widget. It is the value of the first time I set pathname=test;. What am I missing here?
This is probably mistake:
QListWidgetItem* item = ui->myList->itemAt(0,index.row());
Method "itemAt" takes x and y coordinates, not indexes. Use "takeItem" instead.
Next thing I want to say is that this part:
ui->myList->itemWidget(item)
is useless. You can convert "item" directly.
And last - use qobject_cast since you use Qt. And never use dynamic_case (especially when you anyway do not check result against NULL).
Im trying to make the code which reads xml files and deserialize various qt controls from this xml, and im doing this using QDomDocument, and i want to get the QLlist from my deserealization method. And im having a bit of troubles, here is some code of the template class (.h) file:
QList<T*> deserialize(QIODevice *input)
{
QList<T*> objects = QList<T*>();
if(_deserializeObject(input, objects))
return objects;
}
bool _deserializeObjects(QIODevice* input, QList<QObject*>& list);
and my .cpp file with deserialize method, here im reading the control tags from file:
bool Serializer::_deserializeObjects(QIODevice* input, QList<QObject *> &objects)
{
QDomDocument doc;
if (!doc.setContent(input))
return false;
QDomElement root= doc.documentElement();
for(int j = 0; j < root.childNodes().length();j++)
{
QObject* object;
qDebug() << root.tagName();
if(root.tagName().contains("QGroupBox")) // <------- Here i need to determine which control i need to process.
{
????
}
qDebug () << object->metaObject()->className();
qDebug() << object->metaObject()->propertyCount();
for(int i = 0; i < object->metaObject()->propertyCount(); i++)
{
object->metaObject()->cast()
QMetaProperty prop = object->metaObject()->property(i);
QString propName = prop.name();
if(propName == "objectName")
continue;
QDomNodeList nodeList = root.elementsByTagName(propName);
if(nodeList.length() < 1)
continue;
QDomNode node = nodeList.at(0);
QVariant value = object->property(propName.toLatin1().data());
QString v = node.toElement().text();
if(propName == "x")
{
x = v.toInt();
}
else if(propName == "y")
{
y = v.toInt();
}
else if(propName == "width")
{
width = v.toInt();
}
else if(propName == "height")
{
height = v.toInt();
}
if(propName == "geometry")
{
continue;
}
object->setProperty(propName.toLatin1().data(), QVariant(v));
}
object->setProperty("geometry",QVariant(QRect(x,y,width,height)));
objects.push_back(object);
}
return true;
}
In this part
if(root.tagName().contains("QGroupBox")) // <------- Here i need to determine which control i need to process.
{
????
}
qDebug () << object->metaObject()->className();
qDebug() << object->metaObject()->propertyCount();
for(int i = 0; i < object->metaObject()->propertyCount(); i++)
{
...
}
I want to actually somehow get the type of the control by name, so the question is, can i cast QGroupBox to QObject saving the QGroupBox properties so QObject metaObject class name would be QGroupBox, so i can pass all this properties? Because i don't want to make the loops for each control type. Also i when i got the result like so:
QList<QObject *> ds = s.deserialize<Object>((QIODevice*)&f);
Can i then just pass all QObjects in a loop and using QMetaObject class name and using qobject_cast cast each object to QPushButton,QLabel etc.?
QGroupBox is a subclass of QObject; therefore every QGroupBox is also a QObject, so you can treat it as one whenever you like. An explicit cast isn't necessary.
Iterating over all the diffent objects-derived-from-QObject in a loop will do what you want, provided that the methods you call on them are virtual methods (which they presumably will be -- in particular, QObject::metaObject() is a virtual method, so your loop will get the appropriate QMetaObject returned even if it is calling them method through a QObject pointer).
(As an aside, the annoying part of the process will probably be the part where you have read the name of the object's type from the XML and now need to instantiate an object of that type. AFAIK there is no good automatic way to do that in C++, so the best you can do is a factory function containing a giant switch statement with a separate case for every type you might want to instantiate)
Alternatively, use a right tool for a right job. Chances are that what you are really building here is some XML thing for defining widget layouts etc. Qt already has a tool for that, the Qt Designer which uses an XML format for the UI definitions and a C++ code generator for actually producing a C++ code during compile time.