Remove QGraphicsPixMapItem (by setParentItem) - c++

So I'm having trouble with removing a QGraphicsPixMapItem. I add it to my view by setting it's parent so I expected that if I would change the parent to nullptr it would be removed but it didn't work. I read online that I could also use functions like hide but when I use these the program crashes. Whats the best way to fix this?
Btw I'm sure the code crashes when I call the hide function and the rest of my code is good.
A left click should add a 'stamlid' (QGraphicsPixMapItem) to the QGraphicsEllipsItem (this). And It should be removed with a right click.
void Vakje::mousePressEvent(QGraphicsSceneMouseEvent *event) {
Speler *speler = m_spel->getAanDeBeurt();
if (event->button() == Qt::LeftButton && m_stamlid == nullptr) {
m_stamlid = speler->getVrijeStamleden()[0]; //stamlid van speler dat niet op bord staat
m_stamlid->getStamlidView()->setParentItem(this);
m_stamlid->setOpBord(true);
m_stamlid->getStamlidView()->setVisible(true);
speler->getSpelerView()->updateMembers();
} else if (event->button() == Qt::RightButton && m_stamlid != nullptr) { //verwijder pion
m_stamlid->setOpBord(false);
m_stamlid->getStamlidView()->setParentItem(nullptr);
m_stamlid = nullptr;
speler->getSpelerView()->updateMembers();
}
}

The Qt documentation for the base class QGraphicsItem explains that setting the parent item to 0 does not remove the item, but makes it a top-level item instead.
Perhaps what you should do is get the item's scene and use it to remove the item.
if (m_stamlid->scene())
m_stamlid->scene()->removeItem(m_stamlid);
delete m_stamlid;
m_stamlid = nullptr;

Related

Mouse right click option using eventFilter in Qt

I have QGraphicsView, which has many QGraphicsItem. I am trying to create a right click menu on these QGraphicsItem. Right click menu has multiple options. But only 1st option works. It means, if I click on 2nd option, it does not work. If I change the sequence ( means 1st one will go to 2nd position, and 2nd one will come to 1st position ) then still 2nd one will not work.
bool myClass::eventFilter(QObject *watched, QEvent *event)
{
switch(event->type())
{
case QEvent::ContextMenu:
{
foreach(QGraphicsItem* pItem, _scene->items())
{
if(pItem->isUnderMouse())
{
QMouseEvent *mouseEvent = static_cast<QMouseEvent*> (event);
menu = new QMenu(this);
myMenu = menu->addMenu("Copy");
myMenu ->addAction(Name);
myMenu ->addAction(Address);
if(Name == menu->exec(mouseEvent->globalPos()))
{
// logic
}
if(Address == menu->exec(mouseEvent->globalPos()))
{
// logic
}
}
}
}
}
Always works only 1st mouse right click option. Why is so ?
The usual way to do something like this is to override the QGraphicsItem::mouseReleaseEvent() or QGraphicsItem::mousePressEvent() function of your item class.
This way, you won't have to do anything (no looping, etc...), it is already handled by the event loop.
Here you can find a simple example:
void MyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
if(event->button() == Qt::RightButton)
{
QMenu my_menu;
// Build your QMenu the way you want
my_menu.addAction(my_first_action);
my_menu.addAction(my_second_action);
//...
my_menu.exec(event->globalPos());
}
}
From the Qt documentation:
Note that all signals are emitted as usual. If you connect a QAction to a slot and call the menu's exec(), you get the result both via the signal-slot connection and in the return value of exec().
You just need to QObject::connect() the QActions you added to the context menu to the proper slots (here goes the "logic") and the job is done.
If you prefer to check the returned value by yourself, you just have to get the returned QAction* once and for all (only one call to QMenu::exec()) and branch on it.
For example:
void MyItem::mouseReleaseEvent(QGraphicsSceneMouseEvent * event)
{
if(event->button() == Qt::RightButton)
{
QMenu my_menu;
// Build your QMenu the way you want
my_menu.addAction(my_first_action);
my_menu.addAction(my_second_action);
//...
QAction * triggered = my_menu.exec(event->globalPos());
if(triggered == my_first_action)
{
// Do something
}
else if(triggered == my_second_action)
{
// Do some other thing
}
//...
}
}
I would personnally prefer to stick with the signal-slot connections instead that manually handling the returned value, especially since each QAction is most likely to be already connected to its corresponding slot.

Blocking signal one time in Qt doesn't work correctly

Hi i have following code:
void MainWindow::on_listWidgetNotes_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)//Test!
{
if(current != NULL)
{
ui->plainTextEditContent->setEnabled(true);
change = false;
if(isModified)
{
auto reply = QMessageBox::question(this, "Test", "Do you want save changes?", QMessageBox::Yes|QMessageBox::No|QMessageBox::Cancel);
if (reply == QMessageBox::Yes) on_pushButtonSave_clicked();
else if(reply == QMessageBox::No) notes.closeFile();
else
{
//ui->listWidgetNotes->blockSignals(true);
ui->listWidgetNotes->setCurrentItem(previous);
//ui->listWidgetNotes->blockSignals(false);
return;
}
}
isModified = false;
this->setWindowTitle(current->text()+" - VfNotes 1.0");
ui->plainTextEditContent->setPlainText(notes.openFile(current->text()));
}
}
In specified case code have to show message box and set focus on previous item, after select cancel button.
But setCurrentItem calls on_listWidgetNotes_currentItemChanged again with this message box. After use blockSignals focus doesn't come back on previous element. What to do to set focus on previous item after click cancel, that on_listWidgetNotes_currentItemChanged wasn't call again?
So, if this is the slot that gets called when the selection changes, then instead this slot getting called , create another slot and from there, you call this function..
Now this new slot will have the previous item and from there if the function returns a book instead of void signifying that a cancel is pressed, then you call setCurrentItem again...

Qt remove Layout from other Layout

How can I remove an layout_newInfo from layout_main in runtime (pressing button)?
code I tried:
QLayout *layout = this->layout();
QLayoutItem *item;
while ((item = layout->takeAt(0)) != 0)
layout->removeItem (item);
delete layout_newInfo;
layout_main->update();
What exactly do you want to achieve?
If you want to show/hide the widgets that are now in layout_newInfo, then
don't use a layout. Use a widget that you put in a layout_main (vertical layout), which itself has the newInfo items and layout, then just use setVisible(true/false) on the widget as you need.
How can I remove layout_newInfo from layout_main in runtime given layout_newInfo is nested to layout_main?
The semantically clearer method:
layout_main->removeItem(layout_newInfo); // make sure layout_newInfo object deleted
// after either by parent or somehow else
BTW, usually this should also do the same removing of nested layout:
delete layout_newInfo; // also removes it from upper layout
layout_main->update(); // triggers update on the screen
So, just 2 bottom lines of your code example should be sufficient where layout_main->update() call is needed only sometimes if no other update triggered.
The example from here shows that deleting QLayoutItem which is parent for QLayout does remove it from upper layout structure as well (its destructor does it).
Finally find an answer best way is making void method like void showNewInfo(QString action);
In class cpp file
void MainWind::showNewInfo(QString action)
{
if(action == "true")
{
bt_search->setEnabled(false);
bt_production->setEnabled(false);
bt_drying->setEnabled(false);
bt_storage->setEnabled(false);
ln_spent->show();
cb_thickness1->show();
cb_thickness2->show();
cb_thickness3->show();
cb_EFL1->show();
cb_EFL2->show();
bt_newItem->show();
}
else if(action == "false")
{
bt_search->setEnabled(true);
bt_production->setEnabled(true);
bt_drying->setEnabled(true);
bt_storage->setEnabled(true);
ln_spent->hide();
cb_thickness1->hide();
cb_thickness2->hide();
cb_thickness3->hide();
cb_EFL1->hide();
cb_EFL2->hide();
bt_newItem->hide();
}
}
Also there is possible to use setText(""), so next time showing fragment, it will be clear;

Cocos2d: Check if a CCNode/CCSprite is on the display list

In ActionScript, I can just check the .stage property of a DisplayObject, and if it's null, then the DisplayObject isn't on the display list. Is there a cocos2d equivalent?
I'm controlling my own touch system for buttons etc, and I want a quick way to ignore buttons that are registered but not actually on the screen. I'm currently checking against visible and parent, but that doesn't go all the way up the chain, so if I have a popup in memory that's not visible/attached to anything, and a button as a child inside that popup, the button check will pass (as it's visible and has a parent).
Aside from looping all the up until the scene, is there an easy way to check if a CCNode/CCSprite is on the display list?
Edit
Working on #HariKrishna's answer, this was the code I came up with as the cocos2d-x implementation wasn't exactly what I was looking for (e.g. if the parent of the node was nil, then it would return YES as it would never enter the check):
- (BOOL) hasVisibleParents
{
CCNode * p = self.parent;
while( true )
{
if( p == nil || !p.visible )
return NO;
if( [p isKindOfClass:[CCScene class]] )
return YES;
p = p.parent;
}
return YES;
}
You can use CCNode::isVisible() and CCControl::hasVisibleParents() which will internally go up to all the node hierarchy...
Thats the closest you can get for the same.
Example:
bool presentInDisplayList() {
if(isVisible() && hasVisibleParents())
return true;
else
return false;
}
Where hasVisibleParents() is, (Copied from Cocos2d-X Library)
bool CCControl::hasVisibleParents()
{
CCNode* pParent = this->getParent();
for( CCNode *c = pParent; c != NULL; c = c->getParent() )
{
if( !c->isVisible() )
{
return false;
}
}
return true;
}
This can be easily translated to Objective-C

QTableView: When selecting cells, how do I make the first cell selected the current index?

I have a simple class which inherits QTableView and I want the following behavior: when the user selects a few cells, I want the first cell selected to be set as the current index.
So for example if I select from (0, 0) towards (2, 2), when I start typing the text would show up in (0, 0), not (2, 2) which seems to be the default.
I have tried overriding the setSelection function with the following:
void SampleTable::setSelection(const QRect &rect, QItemSelectionModel::SelectionFlags command)
{
if((command & QItemSelectionModel::Current) != 0)
{
QModelIndex curr = indexAt(rect.topLeft());
selectionModel()->select(curr, QItemSelectionModel::Current);
command ^= QItemSelectionModel::Current;
}
QTableView::setSelection(rect, command);
}
but to no avail. It seems to have something to do with the mouse events, but I can't quite locate the problem in the source code and I'm hoping there's an easier way anyway.
What are you trying to achieve? If you'd like the user to edit/select a single cell only, use setSelectionBehaviour to force this. Otherwise you could try chinfoo's idea but make sure to communicate the behavior in a way the user is able to understand it (i.e. he's able to see that his edit will change the first cell/row).
I figured out the problem and how to fix it, but it's not pretty. The problem is in the mouse moved event for a QAbstractItemView. After much debugging and searching through the source code I found this in qabstractitemview.cpp:
void QAbstractItemView::mouseMoveEvent(QMouseEvent *event)
...
if (index.isValid()
&& (index != d->selectionModel->currentIndex())
&& d->isIndexEnabled(index))
d->selectionModel->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
}
I fixed it by giving my class a QModelIndex member which stores the last location of the top left QModelIndex (set in my overriden version of setSelection like above) and then I overrode the mouseMoveEvent with this:
void SampleTable::mouseMoveEvent(QMouseEvent *event)
{
QTableView::mouseMoveEvent(event);
if (state() == ExpandingState || state() == CollapsingState || state() == DraggingState || state() == EditingState)
return;
if ((event->buttons() & Qt::LeftButton)) {
if (m_topLeft.isValid())
{
selectionModel()->setCurrentIndex(m_topLeft, QItemSelectionModel::NoUpdate);
}
}
}
Not a pretty solution, but it works.
The QtableWidget class has a signal itemSelectionChanged(), connect it to your custom slot. In that slot, use selectedIndexes() to get all indexes, then use setCurrentIndex() to set the cell which you want to be the current index.