qt QGraphicsScene additem - c++

http://qt-project.org/doc/qt-4.8/qgraphicsscene.html#addItem
said
If the item is already in a different scene, it will first be removed from its old scene, and then added to this scene as a
top-level.
I want to keep the item in old scene.
how can I do this?
myscene1.addItem(item);
myscene2.addItem(item);// I don't want to remove item from myscene1

You can make a copy of the item :
myscene1.addItem(item);
myscene2.addItem(item->clone());

An item cannot occupy two scenes at the same time, in the way that you cannot be in two places at the same time.
The only way to do this is to make a copy of the item and place it in the second scene.

What you could do is to create a new class. e.g.
class Position
{
...
QPoinfF pos;
...
}
Then you can add that class to your item.
class Item : public QGraphicsItem
{
...
public:
void setSharedPos(Position *pos)
{
sharedPosition = pos;
}
//implement the paint(...) function
//its beeing called by the scene
void paint(...)
{
//set the shared position here
setPos(sharedPos);
//paint the item
...
}
protected:
void QGraphicsItem::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event )
{
//get the position from the item that could have been moved
//you could also check if the position actually changed
sharedPosition->pos = pos();
}
private
Position *sharedPostion;
...
}
You would no have to create two items and give them both the same pointer to a Position object.
Item *item1 = new Item;
Item *item2 = new Item;
Position *sharedPos = new Position;
item1->setSharedPos(sharedPos);
item2->setSharedPos(sharedPos);
myScene1->addItem(item1);
myScene2->addItem(item2);
They should no at least share their position in the scenes.
If this works then you'll have to change the Position class to fit your needs and it should be working.
I am not quite shure if setting the position in the paint() function works. But thats how I would try to synchronise the items. If it does not work then you'll have to look for another place to update the settings of the items.
Or you could give the items a Pointer to each other and let them change the positions/settings directly.
e.g.
class Item : public QGraphicsItem
{
...
void QGraphicsItem::mouseReleaseEvent ( QGraphicsSceneMouseEvent * event )
{
otherItem->setPos(pos());
}
...
void setOtherItem(Item *item)
{
otherItem = item;
}
private:
Item *otherItem;
}
Item *item1 = new Item;
Item *item2 = new Item;
item1->setOtherItem(item2);
item2->setOtherItem(item1);
myScene1->addItem(item1);
myScene2->addItem(item2);

Related

UE4 C++ Multiple widgets created for the same actor in UI

I am trying to set up a system that displays the ground loot near the player (i.e., actors that are overlapping the player's UBoxComponent). This needs to change as and when the player moves and the ground loot actors are no longer being overlapped.
I have sort of got it working - when the actors are on their own the function seems to work. However, when two ground loot actors are close to one another, the inventory displays multiple widgets for the same overlapping actor on the floor and sometimes only recognises one of the actors on the floor. It seems to be having trouble when the Player's UBoxComponent overlaps more than one actor at a time. I'll link a YouTube video to help show the issue I'm having because it's quite hard to describe: https://youtu.be/a_zMl1zOUDc
This is the function for updating the ground loot widgets. It is called by a dynamic multicast delegate that is broadcasted from the player class:
// Iterates through the ground loot around the player and displays a widget for each item
void UInventory::UpdateGroundLoot()
{
MyPlayer = Cast<AMainCharacter>(GetOwningPlayer()->GetCharacter());
if(!MyPlayer) { return; } // Null check
// Get a ref to the Player's inventory component
UInventoryComponent* PlayerInventoryComp = MyPlayer->PlayerInventory;
// Clear any current widgets in the ground loot list
GroundLootScrollBox->ClearChildren();
// GroundLootActorsArray is an array of AActors that gets added to/emptied based on AActors that overlap with the Player's UBoxComponent
if(!PlayerInventoryComp && !PlayerInventoryComp->GroundLootActorsArray.IsValidIndex(0)) { return; } // Null check
for(int32 i = 0; i < PlayerInventoryComp->GroundLootActorsArray.Num(); i++)
{
// Cast each element of the GroundLootItems array to AItem* GroundItem
AItem* GroundItem = Cast<AItem>(PlayerInventoryComp->GroundLootActorsArray[i]);
if(!GroundItem) { return; } // Null check
// Add GroundItem to a new array (GroundLootItemsArray). This is an array specifically of AItems (rather than AActors).
GroundLootItemsArray.Add(GroundItem);
GroundLootWidget = CreateWidget<UInventoryItemWidget>(GetOwningPlayer(), InventoryItemWidgetClass);
// Add each created widget to an array of widgets called GroundLootWidgetsArray
GroundLootWidgetsArray.Add(GroundLootWidget);
if(!GroundLootWidgetsArray.IsValidIndex(0) && !GroundLootItemsArray.IsValidIndex(0)) { return; } // Null check
// Display item specific names/thumbnails for each widget created
GroundLootWidgetsArray[i]->ItemName->SetText(GroundLootItemsArray[i]->ItemDisplayName);
GroundLootWidgetsArray[i]->Thumbnail->SetBrushFromTexture(GroundLootItemsArray[i]->Thumbnail);
// Add each array element of the GroundLootWidgetsArray to the scroll box in the widget blueprint
GroundLootScrollBox->AddChild(GroundLootWidgetsArray[i]);
}
}
DevilsD in the comments pointed me in the right direction here. There were two main issues I had.
The delegate that was designed to update the ground loot widgets was being broadcasted in a for loop for every element of the OverlappingActor array. I think this resulted in multiple widgets being displayed for the same actor. The code in the Player's class now looks like this (TriggerEnter and TriggerExit are called by the OnComponentBeginOverlap and OnComponentEndOverlap delegates in the constructor respectively) :
void AMainCharacter::TriggerEnter(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex, bool bFromSweep, const FHitResult& SweepResult)
{
bItemIsWithinRange = true;
// I think the TSubclassOf ensures that we only pickup AItem classes and it's children - rather than all actors on the ground
OverlappedComponent->GetOverlappingActors(OverlappingActors, TSubclassOf<AItem>(AItem::StaticClass()));
if(PlayerInventory && OverlappingActors.IsValidIndex(0))
{
for(auto& GroundItems : OverlappingActors)
{
PlayerInventory->AddItemToGroundLoot(GroundItems);
}
OnUpdateGroundLoot.Broadcast();
}
}
void AMainCharacter::TriggerExit(UPrimitiveComponent* OverlappedComponent, AActor* OtherActor,
UPrimitiveComponent* OtherComp, int32 OtherBodyIndex)
{
bItemIsWithinRange = false;
if(PlayerInventory && OverlappingActors.IsValidIndex(0))
{
for(auto& GroundItems : OverlappingActors)
{
PlayerInventory->GroundLootActorsArray.Remove(GroundItems);
}
OnUpdateGroundLoot.Broadcast();
OverlappingActors.Empty();
}
}
I wasn't emptying the GroundLootItemsArray every time I was updating the ground loot and so previous ground loot was being stored in this array on each new update. This also resulted in showing the same widget for two different actors. I have now set it to empty along with clearing the children of the scroll box before any widget creation takes place in the UInventory.cpp (which is a child of UUserWidget):
// Iterates through the ground loot around the player and displays a widget for each item
void UInventory::UpdateGroundLoot()
{
MyPlayer = Cast<AMainCharacter>(GetOwningPlayer()->GetCharacter());
if(!MyPlayer) { return; } // Null check
// Get a ref to the Player's inventory component
UInventoryComponent* PlayerInventoryComp = MyPlayer->PlayerInventory;
// Clear any current widgets in the ground loot list
GroundLootScrollBox->ClearChildren();
// Empties this array to prevent replication of Item specific information that gets sent to the widget (i.e., the thumbnail or the name of the item)
GroundLootItemsArray.Empty();
// GroundLootActorsArray is an array of AActors that gets added to/emptied based on AActors that overlap with the Player's UBoxComponent
if(!PlayerInventoryComp && !PlayerInventoryComp->GroundLootActorsArray.IsValidIndex(0)) { return; } // Null check
for(int32 i = 0; i < PlayerInventoryComp->GroundLootActorsArray.Num(); i++)
{
// Cast each element of the GroundLootItems array to AItem* GroundItem
AItem* GroundItem = Cast<AItem>(PlayerInventoryComp->GroundLootActorsArray[i]);
if(!GroundItem) { return; } // Null check
// Add GroundItem to a new array (GroundLootItemsArray). This is an array specifically of AItems (rather than AActors).
GroundLootItemsArray.AddUnique(GroundItem);
GroundLootWidget = CreateWidget<UInventoryItemWidget>(GetOwningPlayer(), InventoryItemWidgetClass);
// Add each created widget to an array of widgets called GroundLootWidgetsArray
GroundLootWidgetsArray.AddUnique(GroundLootWidget);
if(!GroundLootWidgetsArray.IsValidIndex(0) && !GroundLootItemsArray.IsValidIndex(0)) { return; } // Null check
// Display item specific names/thumbnails for each widget created
GroundLootWidgetsArray[i]->ItemName->SetText(GroundLootItemsArray[i]->ItemDisplayName);
GroundLootWidgetsArray[i]->Thumbnail->SetBrushFromTexture(GroundLootItemsArray[i]->Thumbnail);
// Add each array element of the GroundLootWidgetsArray to the scroll box in the widget blueprint
GroundLootScrollBox->AddChild(GroundLootWidgetsArray[i]);
}
}
I also changed the adding of actors/items to the arrays from Array.Add(Actor) to Array.AddUnique(Actor). I'm not sure if this was necessary but it made sense to me because I didn't want more than one of any given item accidently being added to the array.

QListWidget deleting QListWidgetItem causes segmentaion fault

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.

Why do I lose my QList items when accessing it in another function?

GraphWidget Timeline Image.
I have a list called node1List which stores a list of nodes that are to be displayed in a QGraphicsScene. GraphWidget.cpp is a timeline where the user can click to add nodes. EffectsWidget is another timeline which has the option to add three QLabels to the scene. When these labels are added, I need to add a node to the graphWidget timeline in the same respective place.
list declaration in graphWidget.h:
private:
QList<Node *> node1List;
In graphWidget.cpp, on Mouse Click I create a node and add it to the list:
void GraphWidget::mousePressEvent(QMouseEvent *event)
{
switch (event->button()) {
case Qt::MouseButton::RightButton: {
//... create node
addNode1(newNode, location);
//...
}
break;
}
}
Here is the addNode1 function which is declared in the same graphWidget.cpp:
void GraphWidget::addNode1(Node *newNode,int loc)
{
node1List.insert(loc, newNode);
}
This all works fine and I can access the list of nodes WITHIN the mouse press event.
However now I need to allow these nodes to be also added when a label is added to another class, effectsWidget.cpp.
void EffectsWidget::addEffectsItemStart(){
graphWidget = new GraphWidget();
graphWidget->addLabelsToListAt(sceneStartLabel, 1);
//...
}
and then
void GraphWidget::addLabelsToListAt(QLabel *label, int location)
{
labelListGraphWidget.insert(location, label);
generateNodes(label);
}
So finally, now when I call generateNodes(label), I output the size of the node1List only to find that it is empty?
void GraphWidget::generateNodes(QLabel* label)
{
//The following outputs zero
DBOUT("List size = " << node1List.size() << "\n");
//...create new node
addNode1(newNode, location);
}
If anyone could please explain to me how to retain all the items in a list it would be great thanks! First question here so my apologies if I've messed it up!

QT - Can I dynamically point to GUI's labels using a variable passed into a method?

I'm creating an inventory and want to reference my inventory Forms QLabels with the name of the item passed into my method.
The QLabels and the names of the items are the same so I wanted to reduce if statements by referencing a QLabel depending on the name of the item passed in.
void Zork::on_btnTake_clicked()
{
Item *item = new Item(ui->takeItem->currentText().toStdString());
Colin->addItemToInventory(item);
inventory_screen->addItem(item->getDescription()); //Enables the Item in the Inventory
currentRoom->deleteItem(item);
ui->takeItem->removeItem(ui->takeItem->currentIndex()); //Only remove the item in the foreground of combobox at the time take button was clicked
}
Calls this Method addItemToInventory(item):
void Inventory_Ui:: addItem(string itemName){
myUi->itemName->setText(QString::fromStdString(itemName));
}
I am unable to do so as itemName is not a member of Ui, although itemName does contain members of Ui.
Example:
In my Inventory_Ui Form I have 6 labels, one named broken_bottle. I want to pass broken_bottle into the method's parameter itemName and them use that as a reference so at run-time it would look like myUi->broken_bottle->setText...
I think I follow what you are asking to do. I would access it with findChild or findChildren.
http://doc.qt.io/qt-5/qobject.html#findChild
http://doc.qt.io/qt-5/qobject.html#findChildren
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
QLabel* targetLabel = 0;
foreach(QLabel * label, labels)
{
if(label->text() == ui->takeItem->currentText())
{
targetLabel = label;
break;
}
}
if(targetLabel != 0)
{
// ... do something with targetLabel
}
or...
QList <QLabel *> labels = ui->myForm()->findChildren<QLabel*>();
foreach(QLabel * label, labels)
label->setObjectName(label->text());
// later
QLabel * targetLabel = ui->myForm()->findChild<QLabel*>("text of label");
if(targetLabel)
// use it
Or if you are connecting all these labels to the same slot, you can figure some of it out using the sender static method.
http://doc.qt.io/qt-5/qobject.html#sender
void MainWindow::on_takeItem_currentTextChanged()
{
QLabel* targetLabel = qobject_cast<QLabel*>QObject::sender();
if(targetLabel)
// do something with it
}
Hope that helps.

Getting variable from widget in a QListWidget

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).