Gtkmm: Using RefPtr with widgets kept in std::vector - c++

I am trying to keep a std::vector of Gtk::Widgets that I am showing (and will potentially be) moving around between Gtk::Containers.
At the moment I keep a Gtk::Notebook which is basically a one-to-one map to the std::vector, but if I use Glib::RefPtr around the widgets I get problems when removing the widget from the notebook. I already have to use a 'hack' to get a pointer to the underlying Gtk object when adding it to the notebook and I suspect that the Notebook container frees/deletes the object when I remove it from the container.
I have defined my vector of widgets something like this:
std::vector<Glib::RefPtr<Gtk::Widget>> widgets;
When I add a widget to the vector and the notebook I do:
Glib::RefPtr<Gtk::Widget> w (new Gtk::Widget());
widgets.push_back (w);
Gtk::Widget *wptr = w.operator->(); // hack
notebook.append_page (*wptr);
when I try to remove it I do:
int c = 1; // widget no. to remove
notebook.remove_page (c);
auto it = widgets.begin() + c;
widgets.erase (it);
but this results in a G_IS_OBJECT fail assertion when (I think) the element in the std::vector is cleaned up at the end of the iterator (end of function), since possibly notebook.remove_page() already freed the object. How can I do this? Is it possible with RefPtr's?
Related (same assertion failure): Destructing Glib::RefPtr causes failed assertions in the GTK 3 core

Glib::RefPtr<> should not be used with widgets. It is not a general purpose smartpointer. It should only be used with classes that force you to use it - by having no public constructor but having public create*() methods.

Unfortunately you can't do this because the Gtk::Notebook takes ownership of the child objects. You have to refactor your code to use the Gtk::Notebook itself to access the widgets instead of the vector, for example with Gtk::Notebook::get_nth_page().

Related

add a QListWidgetItem to a QListWidget using a std::shared_ptr to fix fortify issue

Fortify doesn't like QListWidget::addItem(new QListWidgetItem) and reports a false memory leak, even though QT manages the memory properly.
I'm trying to figure out a work-around.
I was told to use a std::shared_ptr, but I haven't figured out the syntax yet.
Here's what I've got so far, but it reports an error about the type.
These 2 lines of code are all I need to fix, there is no further context. Just looking for the syntax for a shared pointer to QListWidgetItem, adding the item to the list widget with addItem().
Any syntax that works is fine. MUST create a QListWidgetItem and THEN add it. Cannot use additem("string") syntax.
In a header file, declare member variable item:
...
class Class1{
...
std::shared_ptr<QListWidgetItem> item;
...
};
In a source file:
...
Class1::ClassFunction1()
{
std::make_shared<QListWidgetItem> item("AStringToAdd");
ui->qlw->addItem(item);
}
As per my comment you might be able to utilize std::unique_ptr to silence fortify...
Class1::ClassFunction1 ()
{
auto item = std::make_unique<QListWidgetItem>("AStringToAdd");
/*
* Use std::unique_ptr::release() to transfer ownership of the
* QListWidgetItem to the QListWidget.
*/
ui->qlw->addItem(item.release());
}
The solution provided in the answer by #hyde is certainly the more robust. Having said that the original post is essentially seeking ways of trying to fix a problem with the fortify tool. So the real solution is "fix the tool" or find other, better analysis tools.
This might do the trick, based on code you show in your question:
class Class1{
...
std::unique_ptr<QListWidgetItem> item; // no need to use shared ptr
std::unique_ptr<...whatever you need here...> ui; // change ui to unique_ptr and put it after the item!
// remember to change construction of `ui` accordingly, and remove deleting it in destructor
...
};
Class1::ClassFunction1()
{
// reset member variable, don't create a new local variable
item.reset(new QListWidgetItem>("AStringToAdd"));
ui->qlw->addItem(item.get()); // pass naked pointer
}
That way, item will go out of scope before ui, and will be deleted by the unique_ptr. When the item is deleted, it will notify the view, and view will remove the item.
If you do it the other way around, view will delete the item, but it has no way to notify the unique_ptr. Therefore unique_ptr will delete it again, resulting in Undefined Behavior, with luck just a crash.

QGraphicsItem is not visible after adding to scene

I'm working on a diagram visualisation tool and I ran into an issue where my QGraphicsScene does not display a shared_ptr<DiagramItem> when a raw pointer obtained via .get() is passed to scene->addItem().
Subsequent check via scene->items() shows that my DiagramItem is not a part of the scene. My guess is that it got freed as the refcounter on the shared_ptr will be zero after leaving the scope of the testing function...
But that was the testing case. In my actual code I'm using a shared_ptr that I got from elsewhere and is definitely present in memory with a non-zero refcounter. I get the raw pointer of that and pass it to scene->addItem(). It is also not displayed, but this time it is present in scene->items(). So why is it not being drawn?
If I switch from using shared_ptr<DiagramItem> to DiagramItem* then the issue disappears and everything is displayed properly. But due to limitations from the rest of the project, I cannot easily abandon smart pointers here, nor do I want to.
Did I run into some kind of memory limitation or am I doing something wrong?
I already tried calling show() and update() on the item and increasing the scene size in case the item doesn't fit (it does). I also tried breakpointing the paint() method, but that one doesn't get called at all.
I found a possibly related question here where similar behaviour occurs due to the object going out of scope and being deallocated, but that doesn't seem to be the case with my actual DiagramItem.
class DiagramItem : public QGraphicsItem
{
...
}
//Create scene
auto scene = new QGraphicsScene(nullptr);
//Item is created OR obtained from elsewhere
auto item1 = std::make_shared<DiagramItem>(nullptr, QString("aaa"), true);
auto item2 = GetDiagramItem(...);
//Raw pointers get passed to addItem
scene->addItem(item1.get());
scene->addItem(item2.get());
//Item1 is not present at all (directly created DiagramItem)
//Item2 is present but invisible (DiagramItem passed from elsewhere)
//myItem gets Item2
auto myItem = scene->items()[0];
...

Qt/C++ : Is "static_cast" ok for casting in this snippet of code?

I am using Qt5 on Windows 7. In my current app I have the following piece of code that changes the background color of some push-buttons:
...
for(int i = 0; i < layout()->count(); i++)
{
QPushButton * button = static_cast<QPushButton*>(layout()->itemAt(i)->widget());
button->setStyleSheet(backgroundColor);
}
Well, I have 2 questions about the above code:
Is it ok/correct to use static_cast or should I use some other type of casting?
Is it possible to use foreach instead of the for loop above?
You should use qobject_cast so you can check if the cast was successful. It returns 0 if the cast failed.
QPushButton * button = qobject_cast<QPushButton*>(layout()->itemAt(i)->widget());
if(button)
// cast ok
else
// cast failed
You can't use foreach as you would need a container for that.
It is technically acceptable to use static_cast only if you're sure that the layout only contains widget items and they all contain a QPushButton. Since this is error prone in face of code modifications, I don't suggest doing it.
Instead, it is desirable to use range-for in C++11 or foreach/Q_FOREACH by using a layout iterator adapter. The iterator adapter also solves the problem of iterating only the elements of a type you desire and makes your code safe in face of modifications.
Your can then use range-for and this code is safe even if no QPushButtons are in the layout, and will cope with any kind of layout item gracefully by ignoring it as it should:
for (auto button : IterableLayoutAdapter<QPushButton>(layout()))
button->setStyleSheet(backgroundColor);
If you are sure that all widgets are QPushButtons, then yes, static_cast is the best option (most efficient)
Regarding the foreach, I'm not sure you can get the QLayoutItems as some standard container, so I'm not sure you can do it.

QTabWidget - how to "include" a pointer to every tab?

I'm trying to make a simple communicator, with UI based on tabs (QTabWidget). I want tabs to be closeable and movable. Still, for every tab I would like to remember a pointer to my class (where I keep socket etc.), so I could manage closing tabs and disconnecting sockets.
One way is to keep them(pointers) in array / any container, analyze any move that was done by a user, and change indexes or swap pointers dependently on index of tabs, that were moved, but this involves a lot of work, and even more bugs. Is there any other and simpler way I could get it?
Use myTabWidget->widget(index).
There is one for each tab.
Doc
You can set the widget as the parent of your class if your class inherits from QObject, or connect its signals (like destroyed()) with that of your class.
Or you can even do
QVariant prop = QVariant::fromValue<intptr_t>((intptr_t)workerObject);
myTabWidget->widget(index)->setProperty("workerObject", prop);
to really store the pointer, and
QVariant prop = myTabWidget->widget(index)->getProperty("workerObject");
WorkerClass *ptr = (WorkerClass*) prop.value<intptr_t>();
to get it back.

QT add widgets to UI anywhere

The application that I'm building is supposed to create, destroy, and manipluate widgets that I've created
The problem is I'm not making a simple program with nice buttons where everything is symmetrical and needs to be evenly spaced and handled via a layout that will automatically move everything around and space it.
And yet, the only way I know of is to manually instance a layout and add the widgets to it, but then I can't set the coordinates of them
How can I simply instance my widget, and add it to the project generated frame?
This is how I'm instantiating my class, in which case I then set my own parameters:
Tile *tile = new Tile;
tile->setImg("://Images/placeholderTile.png");
tile->setCol(true);
tile->setGeometry(retX(line),retY(line),50,50);
To reiterate, I want to add my own widgets to a frame outside of the editor (only by code), and be able to manually move them around the frame by code.
I don't see an ".addWidget() as a method accessible from the QFrame, and yet they can be children within the designer, why can't I do this by code? Whenever I try to do it manually and add them to any layout, any attempt I make to manually set the widgets location doesn't do anything. I haven't overridden the setGeometry
I fixed my problem
After 2 hours of continual searching I finally came across my answer
I never thought that you could set the parent of a widget by code, as I thought you strictly had to add it in as a child of something else, not the reverse and declare that it should have a parent
So, by simply adding the line:
tile->setParent(ui->frame);
completely fixed my problem.
I will change this post back and submit the answer tomorrow when I'm allowed to by this site.
Thank you to those who actually came though. I'm just glad I managed to fix it before that.
All you need is to pass the parent to the widget's constructor:
Tile *tile = new Tile(ui->frame); // <-- here
tile->setImg("://Images/placeholderTile.png");
tile->setCol(true);
tile->setGeometry(retX(line),retY(line),50,50);
Since Tile is your own class, you should definitely have a Qt-style, parent-taking explicit constructor for it:
class Tile : public QWidget {
...
public:
explicit Tile(QWidget * parent = 0) : QWidget(parent) { ... }
};
Another approach is to write your own layout that would know about the relationships that are to be held between your objects. After you do it once, writing custom layouts isn't that hard.