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
Related
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.
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.
My Debugger (gdb) Is Saying That I Have a Segfault Error, But Cannot Tell Me Where It Is, Says ?? () For the Function.
I started Getting an Error in a very specific circumstance where I click on a weapon type item, then click on another usable item where my program segfaults. I removed most pointers from my code because at first I thought that was the problem, but it didn't change anything. I Could Not Find a Similar Bug online And Debugging Wasn't Very Helpful Because It Can't Tell Me Where Exactly The Error Is Coming From.
Relavant Player.h
class Player{
private:
int HP; //player's Health Points
int SP; //player's Special Points
int maxHP; //maximum value for the player's health points
int maxSP; //maximum value for the player's special points
uint32_t money; //how much money the player has
float speed; //speed the player will move at (pixels per frame)
bool menuOpen; //boolean which is true when the inventory Menu is open
sf::Vector2f pos; //player's position
//tests if the player is colliding with any items
//in the room, accepts the memory address of the room
bool checkForItems(Room* r);
std::vector<int> ItemInventory; //the player's inventory of items.
std::vector<TextButton> itemButtons; //vector of TextButtons that correspond with each unique item in the player's inventory
std::map<std::string, int> ItemsList; //map that stores each unique item's name and it's quantity
Control openMenu; //control to open the menu
void ItemAttributes(int id); //makes the items do something depending on what was passed
bool removeItem(int id); //removes an item from the player's inventory
void useItem(Item it); //takes an Item* and passes it's ID to ItemAttributes
void updateButtons(); //updates the buttons based on the player's inventory
Item* EquipedWeapon; //stores the player's currently equipped weapon
//enum to store the player's direction for rotations
enum direction {
up,
down,
left,
right
} currentDirection;
void setRotation();
public:
//constructor for the player, initializes maxHP, maxSP, and their position:
Player(int maxhp, int maxsp, sf::Vector2f position);
//getters:
int ItemButtonsTextSearchC(const std::wstring& text); //returns the index in itemButtons for a TextButton which *contains* the text parameter
int ItemButtonsTextSearch(const std::wstring& text); //returns the index in itemButtons for a TextButton which equals the text parameter
//setters:
void setMoney(int amount); //sets the money variable
void setHP(int hp); //sets the HP variable
void setSP(int sp); //sets the SP variable
void menu(); //inventory menu, handles all things related to the player's inventory
void draw(); //draws the player to the screen
};
Methods In Player.cpp That Cause The Problem:
void Player::menu(){
//iterate through all the items listed in itemsList:
for(auto& p : ItemsList){
//get a pointer to the item by it's name:
Item item = *Materials::getItemFromName(p.first);
//convert the item's name to a wstring for easier use in parameters:
std::wstring wItemName = std::wstring(p.first.begin(), p.first.end());
//get the index of the item's corresponding button
//by searching the itemButtons vector for a button
//whose text matches this item's name:
int bIndex = ItemButtonsTextSearch(wItemName);
if(bIndex != -1){
//store the button's position:
sf::Vector2f buttonPosition = itemButtons.at(bIndex).getPosition();
//default the Y values to 150, we need a separate Y for each one
//because there are 4 columns of buttons for each type of item
float weaponY = 150, usableY = 150, collectibleY = 150, moneyY = 150;
//switch statement to determine the position of the button:
switch(item.getType()){
case Item::Weapon:
buttonPosition.x = 100;
buttonPosition.y = weaponY;
//increment by 20 to give space between buttons:
weaponY += 20.0f;
break;
case Item::Usable:
buttonPosition.x = 375;
buttonPosition.y = usableY;
//increment by 20 to give space between buttons:
usableY += 20.0f;
break;
case Item::Collectible:
buttonPosition.x = 650;
buttonPosition.y = collectibleY;
//increment by 20 to give space between buttons:
collectibleY += 20.0f;
break;
case Item::Money:
buttonPosition.x = 925;
buttonPosition.y = moneyY;
//increment by 20 to give space between buttons:
moneyY += 20.0f;
break;
}
//set the button's position now that it's X has been determined:
itemButtons.at(bIndex).setPosition(buttonPosition);
/*
* below we will set the button's text to represent
* it's corresponding item's name as well as it's
* quantity then draw the button to the screen so
* that the client can see how many of each item
* they have, but then we change it back so that it
* doesn't break any comparisons with the button's
* Text (ItemButtonsTextSearch for example):
*/
//text representing item's quantity to append to the end of the the item's name:
std::string QuantityText = "\tx" + std::to_string(p.second);
//wide string that will be the button's text:
std::wstring wQText = wItemName + std::wstring(QuantityText.begin(), QuantityText.end());
//set the button's text (it takes wchar_t* so we call .c_str() on wQText):
itemButtons.at(bIndex).setText(wQText.c_str());
//draw the button with the temporary text to the screen:
itemButtons.at(bIndex).draw();
//poll if the button was clicked, and if it was,
//we will call useItem on it's corresponding Item:
if(itemButtons.at(bIndex).pollClicked()){
useItem(item);
}
//change the button's text back to what it was, note: there
//is a possibility of the button being removed after calling
//useItem() because when an item's quantity hits 0, the
//button corresponding with that item is removed, therefore
//we need a check after the useItem() call to make sure that
//we don't get an index out of bounds error:
if(ItemButtonsTextSearchC(wQText) != -1)
itemButtons.at(bIndex).setText(wItemName.c_str());
}
}
}
void Player::useItem(Item it){
int itemID = it.getItemID();
ItemAttributes(itemID);
}
void Player::ItemAttributes(int id){
switch(id){
case 0: //sword
//EquipedWeapon = Materials::getItem(id);
break;
case 1: //ultra potion of healing
healHP(50);
removeItem(id);
break;
}
}
bool Player::removeItem(int id){
//this will be set to true as soon as we find the item:
bool found = false;
//loop through ItemInventory and remove the first occurance of id
//if it exists, otherwise found will remain false:
for(int i = 0; i < ItemInventory.size(); i++){
if(ItemInventory.at(i) == id){
ItemInventory.erase(ItemInventory.begin() + i);
found = true;
break;
}
}
//if the item was not found in the inventory, there is no need to
//continue, we can just return false because we know that it isn't
//in the player's inventory so it can't be removed in the first place:
if(!found)
return false;
//get an iterator for the item ID's corresponding name in itemsList:
auto itr = ItemsList.find(Materials::itemNames[id]);
//check to make sure the item is actually listed; it will be
//but this is a safeguard in case something breaks:
if(itr != ItemsList.end()){
//decrement the item's quantity:
itr->second--;
//if there are none remaining, we remove it from the itemsList entirely:
if(itr->second <= 0)
ItemsList.erase(itr);
}
//update the buttons based on the new changes:
updateButtons();
//return true because if it got to this point,
//the item was found and removed:
return true;
}
int Player::ItemButtonsTextSearchC(const std::wstring& text){
if(itemButtons.size() > 0){
for(int i = 0; i < itemButtons.size(); i++){
std::wstring bTxt = itemButtons.at(i).getText();
if(bTxt.find(text) != std::string::npos)
return i;
}
}
return -1;
}
int Player::ItemButtonsTextSearch(const std::wstring& text){
if(itemButtons.size() > 0){
for(int i = 0; i < itemButtons.size(); i++){
std::wstring bTxt = itemButtons.at(i).getText();
if(bTxt == text)
return i;
}
}
return -1;
}
void Player::updateButtons(){
//first we clear the vector of itemButton:
itemButtons.clear();
//loop to go through each unique item in ItemsList map
//and make a button for each one:
for(auto& p : ItemsList){
//convert the item's name into a wstring (textbutton constructor takes wchar_t*, wstring is easier to work with):
std::wstring wName = std::wstring(p.first.begin(), p.first.end());
//add the new button to itemButtons
TextButton btn(sf::Vector2f(0, 0), sf::Color::Magenta, sf::Color::White, wName.c_str(), 18);
//make sure button presses only register once
btn.setWasClicked(true);
//add the button to the itemButtons vector:
itemButtons.push_back(btn);
}
}
bool Player::checkForItems(Room* r){
//itemIndex will == the ID of any item we collided with, if there
//was no item it returns -1
int itemIndex = r->checkForItemCollision(player.getGlobalBounds());
if(itemIndex >= 0){
//get item ID from the item we just collided with:
int itemID = r->getItem(itemIndex).collect();
//remove the item from the room and add it's ID to ItemInventory:
r->removeItem(itemIndex);
ItemInventory.push_back(itemID);
//get the item's name and add it to itemsList if it doesn't exist.
std::string itemName = Materials::itemNames[itemID];
//if the item's name is listed in itemsList, we increment it's
//quantity, else we add it and initialize it's quantity to 1:
if(ItemsList.count(itemName) != 0){
ItemsList.at(itemName)++;
} else {
ItemsList.insert(std::make_pair(itemName, 1));
}
//update the buttons in case a new item was obtained:
updateButtons();
//return true because item was found:
return true;
}
//return false, item wasn't found:
return false;
}
When I click a button, it should delete the first occurance of that item's numeric ID from itemInventory, then in itemsList it should decrement the quantity (the map's value) and if that's <= 0, it should remove that entirely. UpdateButtons clears the entire vector of buttons and makes new ones from itemsList, one button for each key. The Item class has an enum for what type of item it is, the sword (item ID 0) is a weapon, the potion (item ID 1) is a usable item, when i click on the sword (does nothing currently), then add a usable item (the potion) and click the new button that was created for usable, it has an error. This doesn't happen unless I click the sword's button first, in any other circumstance it doesn't error when I use all of the item, then add more to my inventory and use them all again. I suspect it's an error with how i'm updating buttons and removing the keys, but I can't find it. All Item Types are in the same vector of buttons, and the item type pretty much only determines where the button will be positioned.
In Player::menu, you have a loop, for(auto& p : ItemsList). Within that loop, you call a sequence (useItem(item) -> ItemAttributes(itemID) -> removeItem(id) -> ItemsList.erase(itr)) that can modify the ItemsList map you are iterating thru. This invalidates the iterators currently referring to p, so when you try access the next element in the map you get Undefined Behavior because the (internally used) iterator is no longer valid.
One possible remedy is to change your for loop to use your own iterators, and increment the iterator at the top of the loop body (before you modify the map):
for (auto it = ItemsList.begin(); it != ItemsList.end(); ) {
auto &p = *it;
++it;
// rest of for loop
}
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!
I have created a QTableWidget in which I've used setCellWidget(QWidget*). I've set QLineEdit in the cell widget. I've also created a delete button and clicking that button sends a signal to the function deleteRow. I've also used a function currentRow() to get the current row, but it returns -1 because of the QLineEdit. The code snippet is below.
void createTable() {
m_table = new QTableWidget(QDialog); //member variable
for (int i = 0; i < 3; i++)
{
QLineEdit *lineEdit = new QLineEdit(m_table);
m_table->setCellWidget(i, 0, lineEdit);
}
QPushButton *deleteBut = new QPushButton(QDiaolg);
connect(deleteBut, SIGNAL(clicked()), QDialog, SLOT(editRow()));
}
editRow() {
int row = m_table->currentRow(); // This gives -1
m_table->remove(row);
}
In above scenario I click in the QLineEdit and then click on the button delete. Please help me out with a solution.
Just tried it here, it seems that currentRow of the table returns -1 when clicking the button right after program start, and when first selecting a cell, then selecting the QLineEdit and then clicking the button, the correct row is returned.
I would do the following as a workaround: Save the row number in the QLineEdit, e.g. by using QObject::setProperty:
QLineEdit *lineEdit = new QLineEdit(m_table);
lineEdit->setProperty("row", i);
m_table->setCellWidget(i, 0, lineEdit);
Then, in the editRow handler, retrieve the property by asking the QTableWidget for its focused child:
int row = m_table->currentRow();
if (row == -1) {
if (QWidget* focused = m_table->focusWidget()) {
row = focused->property("row").toInt();
}
}
The accepted solution, as is, would not work if rows might get deleted while the program runs. Thus the approach would require to update all the properties. Can be done, if this is a rare operation.
I got away with an iteration approach:
for(unsigned int i = 0; i < table->rowCount(); ++i)
{
if(table->cellWidget(i, relevantColumn) == QObject::sender())
{
return i;
}
}
return -1;
Quick, dirty, but worked, and in my case more suitable, as rows got deleted often or changed their positions, only buttons in the widget were connected to the slot and the slot was never called directly. If these conditions are not met, further checks might get necessary (if(QObject::sender()) { /* */ }, ...).
Karsten's answer will work correctly only if QLineEdit's property is recalculated each time a row is deleted, which might be a lot of work. And Aconcagua's answer works only if the method is invoked via signal/slot mechanism. In my solution, I just calculate the position of the QlineEdit which has focus (assuming all table items were set with setCellWidget):
int getCurrentRow() {
for (int i=0; i<myTable->rowCount(); i++)
for (int j=0; j<myTable->columnCount(); j++) {
if (myTable->cellWidget(i,j) == myTable->focusWidget()) {
return i;
}
}
return -1;
}