C++ How to enumerate QGraphicsItem's? - c++

Using this function, I can delete selected QGraphicsItem's from a QGraphicsView.
How can I get my ellipses enumerated in order to receive a notification like "Deleted ellipse n°...".
void MainWindow::deleteItem()
{
foreach (QGraphicsItem *item, scene->selectedItems()) {
if (item->type() == ellipse->Type) {
scene->removeItem(item);
delete item;
QMessageBox::information(this,"Notification", "Deleted");
}
}
}

A few ways
Assuming you only care about that iteration:
int ix = 0; // add this
foreach (QGraphicsItem *item, scene->selectedItems()) {
if (item->type() == ellipse->Type) {
scene->removeItem(item);
delete item;
std::cout << "Deleted ellipse number " << ix++ << std::endl; // and add this
QMessageBox::information(this,"Notification", "Deleted");
}
}
The above only works if your ordering only corresponds to that foreach() loop. If your items are in some arbitrary order:
std::unordered_map<QGraphicsItem*, int> mGraphicsItems;
That's assuming you can populate it, of course. If you can, do a lookup before calling delete(), to get the value which is the enumeration. Not very elegant though, and adds space.
Other way is to subclass QGraphicsItem [Untested code but you get the idea]
class MyGraphicsItem : public QGraphicsItem
{
Q_OBJECT
public:
// snip
int index() const { return mIndex; }
void setIndex( int i ) { mIndex = i; }
private:
int mIndex;
};
Just set the index in whatever way you want when the QGraphicsItem is created, and before calling delete, print out (or do whatever) with item->index();

Related

No instance of overloaded function "std::vector<_Ty, _Alloc>::erase [with _Ty=Enemy *, _Alloc=std::allocator<Enemy *>]" matches the argument list

for (auto enemy : this->enemies)
{
if (enemy->getHP() <= 0)
{
enemies.erase(enemy);
}
}
I have a vector enemies containing multiple of Enemy* elements and i want to erase an enemy if their hp is 0 or below
I write the code above and it gave me this error message:
No instance of overloaded function "std::vector<_Ty, _Alloc>::erase [with _Ty=Enemy *, _Alloc=std::allocator<Enemy *>]" matches the argument list
argument types are: (Enemy*)
object type is: std::vector<Enemy*,std::allocator<Enemy*>>
I assume that is not the right way to do it, so how?
Im new in stackoverflow and im still learning english so sorry if i made mistakes
EDIT:
It's my almost complete code:
struct enemyType
{
public:
int type;
sf::Vector2f pos;
}
std::vector<std::vector<enemyType>> enemyList = {
{
{ trashMonster, sf::Vector2f(5.f * 16, 18.f * 16) }
}
}
std::vector<Enemy*> enemies;
std::vector<Enemy*>* GetEnemy(int level)
{
for (int i = 0; i < enemyList[level].size(); i++)
{
switch (enemyList[level][i].type)
{
case trashMonster:
n_TrashMonster->setPosition(enemyList[level][i].pos);
enemies.emplace_back(n_TrashMonster);
break;
default:
std::cout << "Error to get an enemy\n";
break;
}
}
return &enemies;
}
//Code in different file
std::vector<Enemy*> enemies;
this->enemies = *GetEnemy(lvl);
for (auto enemy : this->enemies)
{
enemy->update(player->getCollisionBox());
//collision enemies to tilemap
collision::MapCollision(*this->map.getTilesCol(), *enemy);
if (enemy->getHP() <= 0)
{
enemies.erase(enemy);
}
}
Didn't include that because my code is a complete mess so I was afraid people won't get the point of my question and it's my first question here
The std::vector<T>::erase function does not have a erase(T a) overload. And if you want to remove elemennts from a vector you can't iterate over them like that. I suggest a convencional loop.
for (size_t i=0; i<this->enemies.size();++i){
if (this->enemies[i]->getHP()){
std::swap(enemies[i],enemies::back());
delete enemies::back();//Only if you don't free the space elsewere
enemies.pop_back();
}
}
Edit:
This will mess up the order of the vector. If you don't want that you can use erase(enemies.begin()+i) iinstead of swaping it back and removeing it
When using STL containers usually you don't even need an (explicit) for loop. Use std::remove_if like this
#include <vector>
#include <algorithm>
#include <iostream>
class Enemy
{
public:
// not explicit on purpose, so I can initalize vector more quickly (in real code you should have explicit constructors if they have one argument of a different type)
Enemy(int hp) :
m_hp{ hp }
{
};
int getHP() const noexcept
{
return m_hp;
}
int m_hp;
};
int main()
{
// create a test vector with enemies with given hitpoints
std::vector<Enemy> enemies{ 1,2,0,4,5,6,0,7,8 };
// boolean lambda function that determines if an enemy is dead.
auto enemy_is_dead = [](const Enemy& enemy) { return enemy.getHP() <= 0; };
// use remove_if (this will move all the items to be removed to the end
auto remove_from = std::remove_if(enemies.begin(), enemies.end(), enemy_is_dead );
// then shrink the vector
enemies.erase(remove_from, enemies.end());
for (const auto& enemy : enemies)
{
std::cout << enemy.getHP() << " ";
}
return 0;
}

C++ No matching member function for call to 'push_back' error for vector

I have been working with a vector and I receive this error:
No matching member function for call to 'push back'
Can anyone take a look at my code to detect my mistakes?
bubble_jar.h
#include "abstract_bubble.h"
#include "bubbles.h"
#include <vector>
#include "cpputils/graphics/image.h"
#ifndef BUBBLE_JAR_H
#define BUBBLE_JAR_H
class BubbleJar : public graphics::MouseEventListener,
public graphics::AnimationEventListener {
public:
~BubbleJar();
void Initialize(int max_age);
void Start();
void OnMouseEvent(const graphics::MouseEvent& event) override;
void OnAnimationStep() override;
graphics::Image* GetImageForTesting() { return &image_; }
private:
graphics::Image image_;
int max;
std::vector<std::unique_ptr<AbstractBubble>> ab;
};
#endif // BUBBLE_JAR_H
bubble_jar.cc
void BubbleJar::OnMouseEvent(const graphics::MouseEvent& event) {
std::cout << "BubbleJar got a MouseEvent" << std::endl;
// Create a BigBubble on mouse press and a SmallBubble on mouse release
// and add them to the vector.
if (event.GetMouseAction() == graphics::MouseAction::kPressed) {
BigBubble b(event.GetX(), event.GetY(), &image_);
ab.push_back(b);
b.Draw();
}
else if ( event.GetMouseAction() == graphics::MouseAction::kReleased ) {
SmallBubble s(event.GetX(), event.GetY(), &image_);
ab.push_back(s);
s.Draw();
}
image_.Flush();
}
Your std::vector holds std::unique_ptr smart pointers to AbstractBubble-derived objects, but you are trying to push actual derived objects, not smart pointers to derived objects, hence the push_back() error due to a type mismatch.
Try this instead:
void BubbleJar::OnMouseEvent(const graphics::MouseEvent& event) {
std::cout << "BubbleJar got a MouseEvent" << std::endl;
// Create a BigBubble on mouse press and a SmallBubble on mouse release
// and add them to the vector.
if (event.GetMouseAction() == graphics::MouseAction::kPressed) {
auto b = std::make_unique<BigBubble>(event.GetX(), event.GetY(), &image_);
ab.push_back(std::move(b));
b->Draw();
}
else if ( event.GetMouseAction() == graphics::MouseAction::kReleased ) {
auto s = std::make_unique<SmallBubble>(event.GetX(), event.GetY(), &image_);
ab.push_back(std::move(s));
s->Draw();
}
image_.Flush();
}

QTableWidget, Cellwidget, QLabel

I've created a QTableWidget. Some of the cells are filled with a cell widget (a modified QLabel, sending a clicked signal).
Now, I want to react on a click on this label. I add some debugging functions.
Clicking on an empty cell writes the correct row and column to console.
Clicking on the label is reckognized as a click, but with the wrong row and column (the previous cell data is used).
Question: How can I get the correct row and column for clicking on the label.
Greetings and thanks, Michael
MtTimeTable::MtTimeTable( QWidget* parent )
: QTableWidget { parent }, mIdArray { MtIdArray() },
mDate { QDate::currentDate() }
{
this->fillLesssonWidgets();
connect( this, &MtTimeTable::cellClicked,
this, &MtTimeTable::slotCellActivated );
}
void MtTimeTable::fillLessonWidgets()
{
MtLessonVec lessonVec { true }; // Data to show, loaded from file
auto cit { lessonVec.begin() };
while( cit != lessonVec.end() ) {
// Class MtLessonWidget derived from QLabel
MtLessonWidget* lessonWidget { new MtLessonWidget };
lessonWidget->setLesson( *cit );
// Using member functions of MtLessonwidget, working correct
this->setCellWidget( lessonWidget->startingRow(),
lessonWidget->startingCol(),
lessonWidget );
}
connect( lessonWidget, &MtLessonWidget::sigClicked,
this, &MtTimeTable::slotLessonWidgetClicked );
++cit;
}
}
I've tried to reduce the code to a minimum.
void MtTimeTable::slotLessonWidgetClicked()
{
std::cerr << "Table Cell: [" << this->currentRow() << ","
<< this->currentColumn() << "]" << std::endl;
}
According to the docs:
int QTableWidget::currentColumn() const
Returns the column of the current item.
int QTableWidget::currentRow() const
Returns the row of the current item.
That is, it is the position of the item, and it refers to a QTableWidgetItem, but if we use setCellWidget an item is not created, so those positions are not suitable, we must look for another means to obtain the row and column associated with the widget.
One way is to use the indexAt() method that returns a QModelIndex associated with the cell given its position relative to the viewport() of QTableWidget, and that is the one that should be used:
void MtTimeTable::slotLessonWidgetClicked(){
auto lbl = qobject_cast<MtLessonWidget *>(sender());
if(lbl){
auto ix = indexAt(lbl->pos());
qDebug()<<"Table Cell: [" <<ix.row()<< "," <<ix.column()<< "]";
}
}
Complete Example:
#include <QApplication>
#include <QLabel>
#include <QTableWidget>
#include <QDebug>
class CustomLabel: public QLabel{
Q_OBJECT
protected:
void mousePressEvent(QMouseEvent *){
emit clicked();
}
signals:
void clicked();
};
class TableWidget: public QTableWidget{
Q_OBJECT
public:
TableWidget(QWidget* parent=Q_NULLPTR):QTableWidget(parent){
setRowCount(10);
setColumnCount(10);
for(int i=0; i<rowCount(); i++){
for(int j=0; j<columnCount(); j++){
auto lbl = new CustomLabel;
setCellWidget(i, j, lbl);
connect(lbl, &CustomLabel::clicked, this, &TableWidget::onClicked);
}
}
}
private slots:
void onClicked(){
auto lbl = qobject_cast<CustomLabel *>(sender());
if(lbl){
auto ix = indexAt(lbl->pos());
qDebug()<<"Table Cell: [" <<ix.row()<< "," <<ix.column()<< "]";
}
}
};
#include "main.moc"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
TableWidget w;
w.show();
return a.exec();
}
In the following link I show the complete example.

Vector does not accept new element properly

I see some odd behaviour in the code below. My console is printing
0lo1lo
when in reality I am expecting
0Hel1lo
Node.cpp
std::vector<Node> Node::getChildren() {
return children;
}
void Node::setChildren(std::vector<Node> childrenNodes) {
children = childrenNodes;
}
void Node::addChild(Node child) {
children.push_back(child);
std::cout << child.getTitle();
}
std::string Node::getTitle() {
return title;
}
From Main function
Node root = Node("root");
root.addChild(Node("Hel"));
root.addChild(Node("lo"));
std::cout << "\n";
std::vector<Node> children = root.getChildren();
for (int i = 0; i < children.size(); i++) {
Node menuItem = children[i];
std::cout << i;
std::cout << menuItem.getTitle();
}
std::cout << "\n";
Does anybody have an idea why getChildren() appears to be getting a vector that is not accurately listing the first element I inserted?
You're using global variables to store instance data:
std::string title;
That means there's only one title in your program and if you ever change it, it changes for every class, function, etc. that accesses it.
Make it a non-static member variable of Node and your problem will go away.

deleting user define qGraphicItems after detecting collision

I have a bullet class that I want to delete upon hitting anything but if it hits an enemy, I want it to delete the enemy as well. It was working on my desktop but upon switching to my laptop, it starts crashing whenever it removes. Currently my scene is outside in a different class in Dialog.cpp
here's my code:
bullet.cpp
void bullet::DoCollision()
{
QList<QGraphicsItem *> list = collidingItems() ;
foreach(QGraphicsItem * i , list)
{
if (i->type() == 3)
{
QGraphicsItem * item= i;
delete item;
qDebug() << "bye";
}
}
m_FireTimer->stop();
delete this;
}
You should use QGraphicsScene::removeItem(QGraphicsItem * item) which removes the item and all its children from the scene. You should hold a pointer to your scene in a variable like myScene.
Your code should be like:
void bullet::DoCollision()
{
QList<QGraphicsItem *> list = collidingItems() ;
foreach(QGraphicsItem * i , list)
{
if (i->type() == 3)
{
QGraphicsItem * item= i;
myScene->removeItem(item);
delete item;
qDebug() << "bye";
}
}
m_FireTimer->stop();
delete this;
}