The basic problem is this:
I have a bunch of widgets that the user can move around. This is achieved by using a GtkOverlay as the outer container, and inside this, is a bunch of GtkFixed, each containing the corresponding widget:
GtkOverlay
GtkFixed
Widget A
GtkFixed
Widget B
...
The reason for the outer GtkOverlay is that GtkFixed lacks support for z-index. Now each GtkFixed covers the entire area. Thus, to make it possible to activate a widget that is currently below, the pass-through property has to be set on each child of the GtkOverlay. The system works for the leaf widgets GtkScale and GtkDrawingArea. However, If I put something inside a GtkScrolledWindow in GTK assumes that the GtkScrolledWindow is located in the upper left corner of the GtkFixed, which in general is wrong. Unsetting the pass-through property solves the problem, but then other widgets become inaccessible. Is there any workaround for this.
I think the proper solution would be to have each child of the GtkOverlay not to occupy the whole area. Then pass-through would not be needed, but I think it is not possible to do this with GTK (or maybe you could if you write your own layout container, which behaves like a web page with free floating layers.
Minimal non-working example:
//# {
//# "targets":[{"name":"minimal-nonworking-example-so-64090741","type":"application", "pkgconfig_libs":["gtk+-3.0"]}]
//# }
#include <gtk/gtk.h>
#include <string>
#include <vector>
#include <algorithm>
// To be able to feed the list box
template<class Callback>
void readlines(FILE* src, Callback&& cb)
{
std::string buffer;
while(true)
{
auto ch_in = getc(src);
if(ch_in == -1)
{
if(buffer.size() != 0)
{
cb(buffer);
}
return;
}
if(ch_in=='\n')
{
cb(buffer);
buffer.clear();
}
else
{
buffer+=ch_in;
}
}
}
int main(int argc, char** argv)
{
gtk_init(&argc, &argv);
auto mainwin = GTK_WINDOW(gtk_window_new(GTK_WINDOW_TOPLEVEL));
gtk_window_set_title(mainwin, "ListBoxTest");
gtk_window_set_default_size(mainwin, 800, 500);
auto overlay = GTK_OVERLAY(gtk_overlay_new());
gtk_container_add(GTK_CONTAINER(mainwin), GTK_WIDGET(overlay));
gtk_widget_set_size_request(GTK_WIDGET(overlay), 500, 300);
auto frame = GTK_FRAME(gtk_frame_new(nullptr));
auto fixed = GTK_FIXED(gtk_fixed_new());
gtk_fixed_put(fixed, GTK_WIDGET(frame), 60, 60);
gtk_overlay_add_overlay(overlay, GTK_WIDGET(fixed));
// {
// Without this line, GtkScrolledWindow works as expected, but if there were more layers
// other layers would become inaccessible
gtk_overlay_set_overlay_pass_through(overlay, GTK_WIDGET(fixed), TRUE);
// }
gtk_overlay_reorder_overlay(overlay, GTK_WIDGET(fixed), -1);
auto scrolled_window = GTK_SCROLLED_WINDOW(gtk_scrolled_window_new(nullptr, nullptr));
gtk_container_add(GTK_CONTAINER(frame), GTK_WIDGET(scrolled_window));
gtk_widget_set_size_request(GTK_WIDGET(frame), 128, 128);
auto listbox = GTK_LIST_BOX(gtk_list_box_new());
gtk_container_add(GTK_CONTAINER(scrolled_window), GTK_WIDGET(listbox));
gtk_widget_show_all(GTK_WIDGET(mainwin));
readlines(stdin, [listbox](auto&& item) {
auto label = GTK_LABEL(gtk_label_new(item.c_str()));
gtk_label_set_justify(label, GTK_JUSTIFY_LEFT);
gtk_widget_set_halign(GTK_WIDGET(label), GTK_ALIGN_START);
gtk_list_box_insert(listbox, GTK_WIDGET(label), -1);
});
gtk_widget_show_all(GTK_WIDGET(fixed));
gtk_main();
gtk_widget_destroy(GTK_WIDGET(mainwin));
return 0;
}
Related
The motivating scenario: I'm working on a Qt/QWidgets C++ app whose GUI is largely arranged into tabs, via a hierarchy of QTabWidgets. Many of these tabs are various agglomerations of the same content, (e.g. there's a tab with widget A and B, a tab with widget B and C, and so on).
I have one particular widget class W which is a fairly heavyweight GUI object and it appears in many (but not all) of the tabs. Currently I handle that by simply creating a separate object of the W class for each tab I want it to appear in, and that basically works, but it's not 100% satisfactory for a couple of reasons:
Since the widget is heavy, creating a number of instances of it slows down the GUI's creation on startup, and uses more system resources than I would like.
Every time the user changes the layout/state of the widget in one tab, I have to manually echo that change to all of its "clones" in the other tabs; otherwise the user will notice the different states of the different W widgets as he moves back and forth from one tab to another. This is doable, but it's a maintenance and testing headache.
So what I'd like to do is create just a single instance of W and have it magically appear in its expected location within whichever tab is currently visible. Since only one tab with W should ever be visible at one time, it seems like a single W instance ought to be enough to accomplish that.
I thought about making a lightweight proxy/container-widget of some sort, and overriding its showEvent() method to setParent() the real W object to be its child as necessary; I think that might work, or it might turn out to be full of gotchas, so I thought I'd ask first if anyone else knows of a more elegant or better-supported way to accomplish the same result.
With a little help I was able to make this technique work, as shown in the example code below. Note that the green label "Shared Widget!" is only created once, but it appears in all 5 tabs (along with various normal QLabels):
#include <QApplication>
#include <QLabel>
#include <QMap>
#include <QSet>
#include <QStackedLayout>
#include <QTabWidget>
#include <QWidget>
/** This is a special container-class that holds a single target widget so that the target widget can be placed
* into more than one QTabWidget at a time. This widget will handle moving the target widget around from proxy
* to proxy as tabs are shown, so that instead of having to create N identical widgets, we can just create one
* target-widget and have it jump from tab to tab as necessary.
*/
class TabProxyWidget : public QWidget
{
public:
/** Constructor
* #param optTargetWidget if non-NULL, this will be passed to SetTargetWidget(). Defaults to NULL.
*/
TabProxyWidget(QWidget * optTargetWidget = NULL)
: _layout(new QStackedLayout(this))
, _targetWidget(NULL)
{
SetTargetWidget(optTargetWidget);
}
virtual ~TabProxyWidget() {SetTargetWidget(NULL);}
/** Set the widget that we want to be a proxy for
* #param optTargetWidget the widget we will proxy for, or NULL to disassociate us from any target widget
* #note the same pointer for (optTargetWidget) can (and should!) be passed to multiple TabProxyWidget objects
*/
void SetTargetWidget(QWidget * optTargetWidget);
virtual void showEvent(QShowEvent *);
virtual bool eventFilter(QObject * o, QEvent * e);
private:
void AdoptTargetWidget();
void UpdateSizeConstraints();
QStackedLayout * _layout;
QWidget * _targetWidget;
};
static QMap<QWidget *, QSet<TabProxyWidget *> > _targetWidgetToProxies;
void TabProxyWidget :: SetTargetWidget(QWidget * targetWidget)
{
if (targetWidget != _targetWidget)
{
if (_targetWidget)
{
_targetWidget->removeEventFilter(this);
QSet<TabProxyWidget *> * proxiesForTargetWidget = _targetWidgetToProxies.contains(_targetWidget) ? &_targetWidgetToProxies[_targetWidget] : NULL;
if ((proxiesForTargetWidget == NULL)||(proxiesForTargetWidget->isEmpty()))
{
printf("TabProxyWidget::SetTargetWidget(NULL): can't proxies-table for target widget %p is %s!\n", targetWidget, proxiesForTargetWidget?"empty":"missing");
exit(10);
}
(void) proxiesForTargetWidget->remove(this);
if (proxiesForTargetWidget->isEmpty())
{
(void) _targetWidgetToProxies.remove(_targetWidget);
delete _targetWidget;
}
else if (dynamic_cast<TabProxyWidget *>(_targetWidget->parentWidget()) == this)
{
proxiesForTargetWidget->values()[0]->AdoptTargetWidget(); // hand him off to another proxy to for safekeeping
}
}
_targetWidget = targetWidget;
if (_targetWidget)
{
if (_targetWidgetToProxies.contains(_targetWidget) == false) _targetWidgetToProxies[_targetWidget] = QSet<TabProxyWidget *>();
_targetWidgetToProxies[_targetWidget].insert(this);
if ((isHidden() == false)||(_targetWidget->parentWidget() == NULL)||(dynamic_cast<TabProxyWidget *>(_targetWidget->parentWidget()) == NULL)) AdoptTargetWidget();
UpdateSizeConstraints();
_targetWidget->installEventFilter(this);
}
}
}
bool TabProxyWidget :: eventFilter(QObject * o, QEvent * e)
{
if ((o == _targetWidget)&&(e->type() == QEvent::Resize)) UpdateSizeConstraints();
return QWidget::eventFilter(o, e);
}
void TabProxyWidget :: UpdateSizeConstraints()
{
if (_targetWidget)
{
setMinimumSize(_targetWidget->minimumSize());
setMaximumSize(_targetWidget->maximumSize());
setSizePolicy (_primaryWidget->sizePolicy());
}
else
{
setMinimumSize(QSize(0,0));
setMaximumSize(QSize(QWIDGETSIZE_MAX, QWIDGETSIZE_MAX));
setSizePolicy (QSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored));
}
}
void TabProxyWidget :: showEvent(QShowEvent * e)
{
AdoptTargetWidget();
QWidget::showEvent(e);
if (_targetWidget) _targetWidget->show();
}
void TabProxyWidget :: AdoptTargetWidget()
{
if ((_targetWidget)&&(_targetWidget->parentWidget() != this))
{
QLayout * layout = _targetWidget->layout();
if (layout) layout->removeWidget(_targetWidget);
_targetWidget->setParent(this);
_layout->addWidget(_targetWidget);
}
}
static void SetWidgetBackgroundColor(QWidget * w, const QColor bc)
{
QPalette p = w->palette();
p.setColor(QPalette::Window, bc);
w->setAutoFillBackground(true);
w->setPalette(p);
}
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
QTabWidget * tabWidget = new QTabWidget;
tabWidget->setWindowTitle("Proxy Widget test");
QWidget * proxyMe = new QLabel("Shared Widget!");
SetWidgetBackgroundColor(proxyMe, Qt::green);
int counter = 0;
for (int i=0; i<5; i++)
{
QWidget * nextTab = new QWidget;
QBoxLayout * tabLayout = new QBoxLayout(QBoxLayout::TopToBottom, nextTab);
const int numAbove = rand()%3;
for (int i=0; i<numAbove; i++) tabLayout->addWidget(new QLabel(QString("Unshared label #%1 above").arg(++counter)));
tabLayout->addWidget(new TabProxyWidget(proxyMe));
const int numBelow = rand()%3;
for (int i=0; i<numBelow; i++) tabLayout->addWidget(new QLabel(QString("Unshared label #%1 below").arg(++counter)));
tabWidget->addTab(nextTab, QString("Tab %1").arg(i+1));
}
tabWidget->show();
return app.exec();
}
I'm trying to make a custom Gtk::Entry widget (gtkmm4) that accepts only numbers and shows text as currency. Decimal and thousand separators are automatically added to the text. So I derived from Gtk::Entry and connected the signal_changed() with a member function that formats the input:
class CurrencyEntry : public Gtk::Entry{
public:
CurrencyEntry() {
set_placeholder_text("0.00");
connectionChange = signal_changed().connect(
sigc::mem_fun(*this, &CurrencyEntry::filterInput)
);
}
protected:
sigc::connection connectionChange;
Glib::ustring oldText;
void filterInput(){
auto currentText = get_text();
/* format currentText */
connectionChange.block();
set_text(currentText);
connectionChange.unblock();
/* move the cursor */
}
};
The problem is: the user presses one key at time, but more than one symbol can be added to the text in specific cases. It seems that the default behavior of the cursor is to always move 1 place per key pressed, ignoring the extra symbols. This is the result (| is the cursor):
Current Text
Typed Key
Result
Desired Result
| (empty)
1
0|.01
0.01|
123.45|
6
1,234.5|6
1,234.56|
98|0,123.45|
7
9,8|70,123.45
9,87|0,123.45
I need to move the cursor to the correct position. At first seemed trivial, but so far I have tried:
Calling set_position(position) at the end of filterInput().
Calling gtk_editable_set_position( GTK_EDITABLE(this->gobj()), position) at the end of filterInput().
Overriding Entry::on_insert_text(const Glib::ustring& text, int* position) and change the value pointed by the position parameter.
Calling Editable::on_insert_text(const Glib::ustring& text, int* position) directly at the end of filterInput(), passing a new position value.
Nothing happens. All these commands seem to be ignored or ignore the position parameter. Am I doing something wrong or is this some kind of bug? How can I set the cursor position correctly in a Gtk::Entry widget?
The position seems not to be updated from the entry's handler. I tried other handlers (like insert_text) and the same issue arises. One way to solve this is to, from within you entry's handler, add a function to be executed in the idle loop. In that function, you can update the position. Here is the code:
#include <algorithm>
#include <iostream>
#include <string>
#include <gtkmm.h>
class CurrencyEntry : public Gtk::Entry
{
public:
CurrencyEntry()
{
m_connection = signal_changed().connect(
[this]()
{
// Get the current edit box content:
std::string str = get_text();
// Make it upper case:
std::transform(str.begin(), str.end(), str.begin(), ::toupper);
// Set the updated text. The connection is blocked to avoid
// recursion:
m_connection.block();
set_text(str);
m_connection.unblock();
// Update the position in the idle loop:
Glib::signal_idle().connect(
[this]()
{
set_position(2);
return false;
});
});
}
private:
sigc::connection m_connection;
};
class MainWindow : public Gtk::ApplicationWindow
{
public:
MainWindow();
private:
CurrencyEntry m_entry;
};
MainWindow::MainWindow()
{
add(m_entry);
}
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
MainWindow window;
window.show_all();
return app->run(window);
}
This is a simplified version of you case: all inserted text is transformed to upper case and, if possible, the cursor's position is set to 2. I think you can adapt to your use case from that.
I want to draw a simple text with Cairo in an app using Gtkmm. I want to give the font style (it can be Pango::FontDescription or Pango::Context and so on ...) directly to draw text with Cairo when the Gtk::FontButton is clicked (in other words when the signal_font_set signal is issued). In the example below, I have a Gtk::HeaderBar that contains a Gtk::FontButton that sends a Glib::RefPtr<<Pango::Context>> to the drawing class when clicked. I want something like this:
MyWindow.cpp:
#include <iostream>
#include "MyWindow.h"
MyWindow::MyWindow() {
set_default_size(1000, 1000);
set_position(Gtk::WIN_POS_CENTER);
header.set_show_close_button(true);
header.pack_start(fontButton);;
set_titlebar(header);
fontButton.signal_font_set().connect([&] {
drawingArea.select_font(fontButton.get_pango_context());
});
scrolledWindow.add(drawingArea);
add(scrolledWindow);
show_all();
}
MyDrawing.h:
#ifndef DRAWING_H
#define DRAWING_H
#include <gtkmm.h>
#include <cairo/cairo.h>
class MyDrawing : public Gtk::Layout
{
public:
MyDrawing();
~MyDrawing() override;
void select_font(Glib::RefPtr<Pango::Context> p_pangoContext);
private:
bool draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context);
Glib::RefPtr<Pango::Context> m_pangoContext;
};
#endif // DRAWING_H
and MyDrawing.cpp:
#include <iostream>
#include <utility>
#include "MyDrawing.h"
#include <cairomm/context.h>
#include <cairomm/surface.h>
MyDrawing::MyDrawing() {
this->signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_text));
}
MyDrawing::~MyDrawing() = default;
bool MyDrawing::draw_text(const Cairo::RefPtr<::Cairo::Context> &p_context) {
auto layout = create_pango_layout("hello ");
if(m_pangoContext) {
layout->set_font_description(m_pangoContext->get_font_description());
}
else {
Pango::FontDescription fontDescription;
layout->set_font_description(fontDescription);
}
p_context->save();
p_context->set_font_size(30);
p_context->set_source_rgb(0.1, 0.1, 0.1);
p_context->move_to(40, 40);
layout->show_in_cairo_context(p_context);
p_context->restore();
return true;
}
void MyDrawing::select_font(Glib::RefPtr<Pango::Context> p_pangoContext) {
this->m_pangoContext = std::move(p_pangoContext);
queue_draw();
}
when I set up Pango::FontDescription manually like :
Pango::FontDescription fontDescription;
fontDescription.set_weight(Pango::WEIGHT_BOLD);
fontDescription.set_style(Pango::STYLE_ITALIC);
layout->set_font_description(fontDescription);
it works:
Here is a way that works: instead of using a Pango::Context, you can get the font name from Gtk::FontButton::get_font_name and then create a Pango::FontDescription from it. Here is a minimal example to show this:
#include <iostream>
#include <gtkmm.h>
class MyDrawing : public Gtk::Layout
{
public:
MyDrawing();
void set_font(const std::string& p_selectedFont);
private:
bool draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context);
std::string m_selectedFont;
Glib::RefPtr<Pango::Layout> m_pangoLayout;
};
MyDrawing::MyDrawing()
{
signal_draw().connect(sigc::mem_fun(*this, &MyDrawing::draw_text));
m_pangoLayout = Pango::Layout::create(get_pango_context());
m_pangoLayout->set_text(
"This text's appearance should change\n"
"when font description changes."
);
}
void MyDrawing::set_font(const std::string& p_selectedFont)
{
// Record the font selected by the user (as a string... Ugh!):
m_selectedFont = p_selectedFont;
std::cout << "Selected font: " << m_selectedFont << std::endl;
// Request a redraw:
queue_draw();
}
bool MyDrawing::draw_text(const Cairo::RefPtr<::Cairo::Context>& p_context)
{
// Create a font description from what was selected by the user earlier,
// and set it:
const Pango::FontDescription description{m_selectedFont};
m_pangoLayout->set_font_description(description);
p_context->save();
p_context->set_font_size(30);
p_context->set_source_rgb(0.1, 0.1, 0.1);
p_context->move_to(40, 40);
m_pangoLayout->show_in_cairo_context(p_context);
p_context->restore();
return true;
}
class MyWindow : public Gtk::ApplicationWindow
{
public:
MyWindow();
private:
Gtk::HeaderBar m_headerBar;
Gtk::FontButton m_fontButton;
MyDrawing m_drawingArea;
};
MyWindow::MyWindow()
{
m_headerBar.set_show_close_button(true);
m_headerBar.pack_start(m_fontButton);;
set_titlebar(m_headerBar);
m_fontButton.signal_font_set().connect(
[this]()
{
m_drawingArea.set_font(m_fontButton.get_font_name());
}
);
add(m_drawingArea);
show_all();
}
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
MyWindow window;
window.show_all();
return app->run(window);
}
I am not a Pango expert, so I don't know if there is a better way using your initial strategy. Furthermore, the string representing the font names seem to need to follow some convention. From the documentation of Pango::FontDescription::FontDescription(const Glib::ustring& font_name) (Gtkmm 3.24):
font_name must have the form "[FAMILY-LIST] [STYLE-OPTIONS] [SIZE]",
where FAMILY-LIST is a comma separated list of families optionally
terminated by a comma, STYLE_OPTIONS is a whitespace separated list of
words where each WORD describes one of style, variant, weight, or
stretch, and SIZE is an decimal number (size in points). Any one of
the options may be absent. If FAMILY-LIST is absent, then the
family_name field of the resulting font description will be
initialized to 0. If STYLE-OPTIONS is missing, then all style options
will be set to the default values. If SIZE is missing, the size in the
resulting font description will be set to 0.
so you may have to do some testing of your own to make sure this works for all fonts. In my case, all 10-15 fonts I have tested seemed to work fine.
On the code bellow I am setting ScrolledWindows on a grid container and then setting their properties in a loop, because I want to put TextViews on the grid. I do not want to use TreeView because it will be ackward to put long or moderate texts there, which is what I am intending. Unfortunately the function get_child_at(int col,int row), for getting the attached widgets returns Gtk::Widget and for the properties I need I must do a cast for Gtk::ScrolledWindow; which I am not being able to do.
(some code)
descriptors->attach(* (new Gtk::ScrolledWindow ()), 0,0);
descriptors->attach(* (new Gtk::ScrolledWindow ()), 1,0);
descriptors->attach(* (new Gtk::ScrolledWindow ()), 2,0);
(some other code)
for(int i = 0; i < 3; i++)
{
//Glib::RefPtr<Gtk::ScrolledWindow> * disposable_pointer = new Glib::RefPtr<Gtk::ScrolledWindow>();
//disposable_pointer = descriptors->get_child_at (i,0);
//Glib::RefPtr<Gtk::ScrolledWindow> disposable_pointer = Glib::RefPtr<Gtk::Widget>::cast_dynamic(*descriptors->get_child_at (i,0));
//Glib::RefPtr<Gtk::ScrolledWindow>Glib::RefPtr<Gtk::ScrolledWindow>::cast_dynamic
//current_grid_child_scrolledWin = Glib::RefPtr<Gtk::ScrolledWindow>cast_dynamic(descriptors->get_child_at (i,0));
//current_grid_child_scrolledWin = disposable_pointer->get();
Glib::RefPtr<Gtk::Widget> * something = new Glib::RefPtr<Gtk::Widget>(descriptors->get_child_at (i,0));
Glib::RefPtr<Gtk::ScrolledWindow> disposable_pointer = *something;
current_grid_child_scrolledWin->set_vadjustment (Glib::RefPtr<Gtk::Adjustment>());
current_grid_child_scrolledWin->set_hadjustment (Glib::RefPtr<Gtk::Adjustment>());
current_grid_child_scrolledWin->set_policy (Gtk::PolicyType::POLICY_AUTOMATIC, Gtk::PolicyType::POLICY_AUTOMATIC);
current_grid_child_scrolledWin->set_vexpand (true);
current_grid_child_scrolledWin->set_hexpand (true);
current_grid_child_scrolledWin->set_margin_end (10);
current_grid_child_scrolledWin->set_margin_bottom (10);
current_grid_child_scrolledWin->set_visible (true);
}
the last error I got was
/usr/include/glibmm-2.4/glibmm/refptr.h:309:31: error: invalid conversion from 'Gtk::Widget*' to 'Gtk::ScrolledWindow*' [-fpermissive]
309 | pCppObject_(src.operator->())
| ^
| |
| Gtk::Widget*
I considered the gtkmm documentation on using casting with Glib::RefPtr
As we can see from the Gtk::Grid documentation, the return type of get_child_at is a raw pointer to a widget:
Widget* Gtk::Grid::get_child_at(int column,
int row
)
and not a smart pointer (in your case a Glib::Refptr). This is because the caller of get_child_at is not responsible to manage the lifetime of the object pointed to by the returned pointer. You simply get a handle, work on it and then leave it alone. Someone else is responsible to call delete on it. Here is how I would do it in your case:
#include <gtkmm.h>
#include <iostream>
int main(int argc, char *argv[])
{
// Initialize Gtkmm:
auto app = Gtk::Application::create(argc, argv, "so.question.q66162108");
// Create a grid and attach a scrolled window:
Gtk::Grid container;
Gtk::ScrolledWindow scrolledWindow;
// Add the scrolled window to the grid. Make sure the container is responsible
// of deleting it! Otherwise, remeber to call delete on it:
container.attach(*Gtk::manage(new Gtk::ScrolledWindow()), 0, 0, 1, 1);
// Here, you get a raw pointer to a widget:
Gtk::Widget* pWidget = container.get_child_at(0, 0);
// We cast it to its most derived type:
Gtk::ScrolledWindow* pScrolledWindow = dynamic_cast<Gtk::ScrolledWindow*>(pWidget);
// If the cast fail (i.e. not a ScrolledWindow at (0, 0), then the
// casted pointer will be set to nullptr:
if(pScrolledWindow)
{
std::cout << "Casting worked" << std::endl;
// Handle properties here...
pScrolledWindow->set_visible (true);
// Set other properties here...
}
return 0;
} // At this point the container is destroyed and since Gtk::manage
// was used, the scrolled window will be automatically deleted.
Notice that nowhere is this code I am using a smart pointer. This is because when I new my Gtk::ScrolledWindow, I use Gtk::manage*. This function marks the object (here the Gtk::ScrolledWindow) as owned by its parent container widget (Here the Gtk::Grid), so you don't need to delete it manually. I becomes the container's responsibility to call delete on its children when it goes away.
*If you use a newer Gtkmm version, you will have to use make_managed<T> instead. See here.
I'm trying to create custom overlays in Marble while following this tutorial. My code is identical to the one in the example.
Everything seems ok, but somehow the generated layer is editable and I can click it and change its size.
I'd like it to be just static on the background with no way of interacting with it.
There doesn't seem to be any obvious flag to set or function to override (so that I could just ignore all user events).
Any ideas?
Code as requested:
#include <QDebug>
#include <QFileInfo>
#include <QApplication>
#include <QImage>
#include <marble/MarbleWidget.h>
#include <marble/GeoDataDocument.h>
#include <marble/GeoDataGroundOverlay.h>
#include <marble/GeoDataTreeModel.h>
#include <marble/MarbleModel.h>
using namespace Marble;
int main(int argc, char** argv) {
QApplication app(argc,argv);
QFileInfo inputFile( app.arguments().last() );
if ( app.arguments().size() < 2 || !inputFile.exists() ) {
qWarning() << "Usage: " << app.arguments().first() << "file.png";
return 1;
}
// Create a Marble QWidget without a parent
MarbleWidget *mapWidget = new MarbleWidget();
// Load the Satellite map
mapWidget->setMapThemeId( "earth/bluemarble/bluemarble.dgml" );
// Create a bounding box from the given corner points
GeoDataLatLonBox box( 55, 48, 14.5, 6, GeoDataCoordinates::Degree );
box.setRotation( 0, GeoDataCoordinates::Degree );
// Create an overlay and assign the image to render and its bounding box to it
GeoDataGroundOverlay *overlay = new GeoDataGroundOverlay;
overlay->setLatLonBox( box );
overlay->setIcon( QImage( inputFile.absoluteFilePath() ) );
// Create a document as a container for the overlay
GeoDataDocument *document = new GeoDataDocument();
document->append( overlay );
// Add the document to MarbleWidget's tree model
mapWidget->model()->treeModel()->addDocument( document );
mapWidget->show();
return app.exec();
}
Update:
You can programatically enable/disable plugins using RenderPlugin and setVisible:
QList<RenderPlugin *> renderPluginList = marbleWidget->renderPlugins();
for (RenderPlugin *renderPlugin : renderPluginList) {
if (std::find(plugin_list.begin(), plugin_list.end(), renderPlugin->nameId()) != plugin_list.end())
{
renderPlugin->setVisible(true);
}
else
{
renderPlugin->setVisible(false);
}
}
Where plugin_list is a std::vector<QString> of plugin nameId()s.
To disable just the Annotation plugin, you could use:
QList<RenderPlugin *> renderPluginList = mapWidget->renderPlugins();
for (RenderPlugin *renderPlugin : renderPluginList) {
if (renderPlugin->nameId() == "annotation")
{
renderPlugin->setVisible(false);
}
}
In case you are still experiencing this issue, one thing to check is whether you have the AnnotationPlugin (.dll if on Windows) in the plugins/ directory. This plugin allows for moving and resizing various features on the MarbleWidget map.