Signal_delete_event doesn't change page in notebook with set_current_page - c++

I'd like to change to a certain page in a notebook when deleting the window and do some work before effectively deleting the window.
The code below gives 1 for get_current_page, but the page isn't effectively changed to 1.
What should be the solution to this problem?
Form::Form()
{
add(box);
notebook.set_size_request(800, 600);
notebook.set_show_tabs(false);
box.pack_start(notebook, Gtk::PACK_SHRINK);
label_intro.set_text("Intro");
box_intro.pack_start(label_intro);
box_intro.show();
label_exit.set_text("Preparing clean exit ... Please wait!");
box_exit.pack_start(label_exit);
box_exit.show();
notebook.insert_page(box_intro, "Intro", 0);
notebook.insert_page(box_exit, "Exit", 1);
signal_delete_event().connect(sigc::mem_fun(*this, &Form::is_deleted));
set_title("title");
resize(800, 600);
show_all();
}
bool Form::is_deleted(GdkEventAny *any_event)
{
notebook.set_current_page(1);
std::cout << "current_page " << notebook.get_current_page() << std::endl; // gives 1
std::this_thread::sleep_for(2000ms);
return Window::on_delete_event(any_event);
}
class Form : public Window
{
public:
Form();
private:
bool is_deleted(GdkEventAny *any_event);
private:
// Form
Box box;
Notebook notebook;
Box box_intro, box_exit;
Label label_intro, label_exit;
};

Sorry for the late answer, your question made my work more than I thought!
Working with the Glib::TimeoutSource class (which is ridiculously under documented...), I was able to hack my way around this limitation.
Basically, my strategy was, on a single click, to run the delete-event signal handler two times:
once to update the notebook. In this case we return true to indicate the handler has "fully handled" the signal, and propagation should not happen, hence not closing the window. At this point, the user sees the change in the UI, but no work has been done. In this pass, we also set a Glib::Timeout to later call the close method on the window, calling once again the delete-event signal handler.
twice to do the work. This time, we do the real work (UI has already been updated). Once the work is done, we propagate the handler and close the window.
Here is this code*:
#include <chrono>
#include <iostream>
#include <thread>
#include <gtkmm.h>
using namespace std::chrono_literals;
class Form : public Gtk::Window
{
public:
Form();
private:
Gtk::Box box;
Gtk::Notebook notebook;
Gtk::Box box_intro;
Gtk::Box box_exit;
Gtk::Label label_intro;
Gtk::Label label_exit;
// This flag indicates if a first call to the "delete-event" signal
// has been done. On a second call to this event, this should be
// set to "true" to alter the handler's behaviour.
bool flag = false;
};
Form::Form()
{
add(box);
notebook.set_size_request(800, 600);
notebook.set_show_tabs(false);
box.pack_start(notebook, Gtk::PACK_SHRINK);
label_intro.set_text("Intro");
box_intro.pack_start(label_intro);
box_intro.show();
label_exit.set_text("Preparing clean exit ... Please wait!");
box_exit.pack_start(label_exit);
box_exit.show();
notebook.insert_page(box_intro, "Intro", 0);
notebook.insert_page(box_exit, "Exit", 1);
signal_delete_event().connect(
[this](GdkEventAny *any_event)
{
if(!flag)
{
// First time in. We change the notebook page:
notebook.set_current_page(1);
// If we block right away and don't return from
// this handler, the GUI will freeze. Hence, we set
// a timer to "close" the window in 10ms. Not that
// closing the window will call once more this handler...
Glib::signal_timeout().connect(
[this]()
{
close();
return false; // Disconnect after on call...
},
10
);
// So we change the flag value, to alter its behavior on the
// next pass.
flag = true;
return true; // Fully handled for now... leaving the handler.
// This will allow the main loop to be run and the
// window to uptate.
}
// On the second run, we do the work:
std::this_thread::sleep_for(1900ms);
// And we close the window (for real this time):
return Window::on_delete_event(any_event);
}
);
set_title("title");
resize(800, 600);
show_all();
}
int main(int argc, char *argv[])
{
auto app = Gtk::Application::create(argc, argv, "org.gtkmm.examples.base");
Form window;
window.show_all();
return app->run(window);
}
* I took the liberty to use lambda expressions, as I think they are more readable and encapsulated, and I wasn't sure if you knew about them. In any case, take what feels best.
I understand this is some sort of a hack, but after having tried a lot of things, I have come to believe this is the best we can achieve without dealing with more involved multi-threading strategies (which I am no expert of, sadly).
Hopes this, at least temporarily, solves your issue.

Related

SFML - Able to open a new window but can't close it

I'm stuck on how to manage multiple windows in SFML. Basically, I'm able to open a new window, but it won't let me close it. Here is the new window header and implementation.
Header:
class PopWindow {
private:
sf::RenderWindow popWin;
sf::Color popWinColor = sf::Color(50, 50, 50);
sf::Event popWinEvent{};
public:
// Default constructor
PopWindow();
// While pop is Open
int winOpen();
};
Implementation:
#include "PopWindow.h"
PopWindow::PopWindow() {
popWin.create(sf::VideoMode(500, 500), "Open Project");
}
int PopWindow::winOpen() {
while (popWin.isOpen()) {
while (popWin.pollEvent(popWinEvent)) {
if (popWinEvent.type == sf::Event::Closed || sf::Keyboard::isKeyPressed(sf::Keyboard::F2)) {
popWin.close();
}
popWin.clear(popWinColor);
popWin.display();
}
}
return 0;
}
Weird thing is if I declare a PopWindow object in the main class file and call object.winOpen(), it works totally fine. However, if the object is declared in one of other class files, I can only open it but not close it.
I appreciate your help.
I copied that code and checked it out and I see that it works fine if you close the windows in the opening order. For example if you declared window1 and window2 and call window1.winOpen() and then window2.winOpen(), then until you close the first window - window1 - the second not really started to handle closing event because window1.winOpen() did not finish yet. So you can drag the second window (which obviously opened on the first) aside and then close the first one and then close the second one.
But if you try to close the second before the first it will wait until you close the firs and then they both closed together.

gtk_widget_add_tick_callback() and gtk_main_iteration()

I have two GTK windows
Normal (main) window that runs animation, draws stuff in callback registered by gtk_widget_add_tick_callback().
At some point secondary window is created that runs modal loop:
void show_modal()
{
GtkWindow* gw = gtkwindow(this);
if( parent() )
gtk_window_set_transient_for(gw, gtkwindow( parent() ));
gtk_widget_show(GTK_WIDGET(gw));
gtk_window_set_modal(gw,TRUE);
gtk_window_set_keep_above(gw,TRUE);
this->update_window_state(gool::WINDOW_SHOWN);
while( this->is_valid_window() )
{
if(this->_window_state == WINDOW_HIDDEN) break;
if(this->_window_state == WINDOW_STATE_NA) break;
gtk_main_iteration(); // gtk_main_iteration_do(true);
}
}
Problem: Animation in main window works fine until show_modal() is invoked. It appears as gtk_main_iteration(); blocks ticks added by gtk_widget_add_tick_callback() function. As soon as I close secondary window and so while() {gtk_main_iteration();} loop exits then animations in main window start running again.
Any idea of how to make "animation friendly" modal loops in GTK?
UPDATE: it appears as gtk_main_iteration(); blocks not only ticks but any updates of any windows other than "current" - they are simply frozen. What is the reasoning of such GTK behavior?
UPDATE #2:
gtk_dialog_run(); behaves exactly as gtk_main_iteration(); - locks any updates on any window in process other than active window.
It seems to be by definition: link
gboolean gtk_main_iteration (void);
Runs a single iteration of the mainloop. If no events are waiting to be processed GTK+ will block until the next event is noticed. If you don’t want to block look at gtk_main_iteration_do() or check if any events are pending with gtk_events_pending() first.
The explanation suggests to use gtk_main_iteration_do(FALSE) if you don't want blocking:
gboolean gtk_main_iteration_do (gboolean blocking);
Runs a single iteration of the mainloop. If no events are available either return or block depending on the value of blocking:
TRUE if you want GTK+ to block if no events are pending
As for gtk_dialog_run: it also blocks by design link
gint gtk_dialog_run (GtkDialog *dialog);
Blocks in a recursive main loop until the dialog either emits the “response” signal, or is destroyed.[...]
I read about people solving this using multiple threads: handle the GUI in the main thread and do background work in another one. There's an article about it here that might be useful.
I assume that show_modal is called from a callback or other activity in main context. You could try adding your modal window into main context using invoke or signal_idle.
This way execution of show_modal will end.
#include <gtkmm.h>
#include <string>
int main()
{
auto Application = Gtk::Application::create();
Gtk::Window window;
Gtk::Window* window2;
Gtk::Button button;
window.add(button);
//I hope timeout behaves similar to ticks. I have no idea how animations in GTK work
int i=0;
Glib::MainContext::get_default()->signal_timeout().connect([&]()->bool{
button.set_label(std::to_string(i++));
return true;
}, 1000);
button.signal_clicked().connect([&]{
Glib::MainContext::get_default()->invoke([&]()->bool{
window2 = new Gtk::Window;
window2->set_modal(true);
window2->set_keep_above(true);
window2->signal_delete_event().connect([&](GdkEventAny* any_event)->bool{
delete window2;
return false;
});
window2->show_all();
return false;
});
});
window.show_all();
return Application->run(window);
}

Updating Qt Images does not work until exec is called

I need to create a simple GUI which displays images, the images in this example can change and the GUI will need to update it's contents.
I wrote the following update function in my widget class:
void myClass::updatePic() {
QPixmap pix("./pic.png");
int width = ui->picLabel->width();
int height = ui->picLabel->height();
ui->picLabel->setPixmap(pix.scaled(width,height,Qt::KeepAspectRatio));}
I try to use it in the following manner:
int main(int argc, char *argv[]) {
QApplication a(argc, argv);
myClass w;
w.show();
sleep(3);
w.updatePic();
sleep(3);
w.updatePic();
sleep(3);
return a.exec();}
But the window just opens and does not display the images until we get to the a.exec() line, and then it opens the last image. What am I doing wrong?
EDIT:
Clarification, the trigger for changing the images comes from an external program (specifically, the gui will be a node in ros, and will be triggered by another node). Is there a way to push a button not from the gui via an external program? the timer will work but I dislike this "busy wait" style solutions.
Thanks for the suggestions so far
exec runs the QT event loop, which includes rendering widgets.
So move your updatePic call into your widget and activate it by for example a button or in the show event
At first learn more about event loop. In particular, you must know that all events like paintEvent or resizeEvent are usually called on corresponding events handle. The events handle is usually called by the event loop, i.e. inside of exec function.
Let's unite answers of #MohaBou and #RvdK. You need to handle timer shots after the exec call. Use QObject::timerEvent for this.
myClass::myClass()
{
<...>
// This two variables are members of myClass.
_timerId = startTimer(3000);
_updatesCount = 0;
}
myClass::~myClass()
{
<...>
// For any case. As far as I remember, otherwise the late event
// may be handled after the destructor. Maybe it is false, do
// not remember...
if (_timerId >= 0) {
killTimer(_timerId);
_timerId = - 1;
}
}
myClass::timerEvent(QTimerEvent *event)
{
if (event->timerId() == _timerId) {
if (_updatesCount < 2) {
updatePic();
++_updatesCount;
} else {
killTimer(_timerId);
_timerId = - 1;
}
}
}
The startTimer method here adds especial timer event to the event query every 3 seconds. As all events, it may be handled only when the event loop will take control and all earlier events are handled. Because of it you can have a duration if many "heavy" events are handled.
EDIT: sorry, I didn't understand #MohaBou at first read. His answer with explicit QTimer is also good enough (but I still don't understand a part about modality).
The function exec also renders the child widgets. exec() blocks the application flow while show() doesn't. So, exec is mainly used for modal dialogs.
I recommend to update it in your custom witget by using a refresh timer. Use a QTimer to update the image every 3 secs:
QTimer* timer = new QTimer(this);
timer->setInterval(3000);
connect(timer, SINGAL(timeout()), this, SLOT(updatPicture()));
Update your picture in your custom slot:
MainWindow::updatePicture() {
updatePic()
}
If you want, you could use a lambda function:
connect(timer, &QTimer::timeout, this, [&w]() {
updatePic()
});

double Gtk::Messadialog before quitting

I'm trying to display a message dialog if the pid of running program is valid, This is my essencial code:
Gtk::Main kit (argc, argv);
Glib::RefPtr<Gtk::Builder> refBuilder = Gtk::Builder::create();
try { refBuilder->add_from_file (UI_PATH); }
catch (const Glib::FileError& ex) {
std::cout << "FileError: " << ex.what() << std::endl;
return 1;
}
catch (const Gtk::BuilderError& ex) {
std::cout << "BuilderError: " << ex.what() << std::endl;
return 1;
}
FormUI * ui = 0;
refBuilder->get_widget_derived ("window1", ui);
if (ui) {
kit.run (*ui);
}
delete ui;
Constructor:
signal_delete_event ().connect (sigc::mem_fun (*this, &FormUI::on_delete_event));
method:
bool FormUI::on_delete_event (GdkEventAny* event) {
if (_pid) {
bool retState;
Gtk::MessageDialog md(*this, Glib::ustring::compose ("<b>%1</b>", _("Warning: youtube-dl in process")), true, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_YES_NO, true);
md.set_title (PACKAGE_STRING);
md.set_secondary_text (_("Closing can generate a corrupted file, do you want to continue anyway?"));
if (md.run() == Gtk::RESPONSE_YES) {
kill (_pid, 0);
retState = false;
} else {
retState = true;
}
md.hide ();
return retState;
}
return false;
}
With the above, if pid is valid it displays the messagedialog as expected, but if I hit "yes" (to exit the application) it displays another messagedialog..why?
You said that FormUI is derived from Gtk::Window. Gtk::Window has a virtual method on_delete_event() that is automatically connected to the delete-event signal, no questions asked. Oops, you implemented a virtual method without knowing it! So what you did by calling
signal_delete_event ().connect (sigc::mem_fun (*this, &FormUI::on_delete_event));
was unknowingly connect that signal twice, and because Gtk::Window::on_delete_event() is virtual, both connections go to your own method.
Okay, so why do we still get two dialog boxes? Doesn't returning false mean to close the window? Not really.
delete-event is a GDK event. GDK events always return a boolean value: if the value is false (GDK_EVENT_PROPAGATE), the next signal handler in the signal connection chain is run, and if the return value is true (GDK_EVENT_STOP), no further signal in the signal connection chain is run.
It just so happens that if you don't stop a delete-event from propagating through the signal connection chain, the window is destroyed. So when there was only one handler connected, returning false from that handler would effectively destroy the window.
But now you have two handlers connected. The first one will return false, which causes the second one to run, and you get your second message dialog. When that one returns false, you get your window being destroyed.
Hopefully that should explain this problem. You can solve this by either not calling signal_delete_event().connect() or by changing the method name to something else. Be sure to watch the gtkmm documentation to make sure you aren't accidentally using other virtual methods that are automatically connected to signals (I'm not sure why gtkmm provides these virtual methods; convenience?). And be sure to understand how GDK events work; you'll need to know this if you ever play with GDK events for real (such as handling input in a Gtk::DrawingArea).

FLTK Closing window

I am using FLTK. I have a window with a variety of buttons the user can click to perform some action. In my int main() i have a switch statement to handle all of these. When the user clicks exit the switch statement is setup like so:
case Exit_program:
cout << "save files and exit\n";
do_save_exit(sw);
This goes to the do_save_exit function that creates a exit confirmation window with two buttons yes (exit) and no (don't exit). I got the yes button to work, exits the program, but the no button mean i should just hide the confirmation window. This is the follow functions:
void yes(Address addr, Address)
{
exit(0);
}
void no(Address addr, Address)
{
}
void do_save_exit(Window& w)
{
Window quit(Point(w.x()+100, w.y()+100), 250, 55, "Exit confirmation");
Text conf(Point(15,15),"Do you really want to save and exit?");
Button yes(Point(60, 20),35,30,"Yes",yes);
Button no(Point(140, 20),35,30,"No",no);
quit.attach(conf);
quit.attach(yes);
quit.attach(no);
wait_for_main_window_click();
}
The problem is, when i click the no button it goes to void no, but I can't go anywhere from there. I just want to do quit.hide() but the no function doesn't have sight of the quit window (out of scope). How should I proceed? Thank you
P.S: I have thought about using a pointer to the quit window and then using the pointer to quit the window in the no function but am not sure how to do that exactly.
The Fl_Window callback is called when an attempt is made to close the window. The default callback hides the window (and if all windows are hidden, your application ends). If you set your own window callback, you can override this behaviour, so as not to hide the window:
// This window callback allows the user to save & exit, don't save, or cancel.
static void window_cb (Fl_Widget *widget, void *)
{
Fl_Window *window = (Fl_Window *)widget;
// fl_choice presents a modal dialog window with up to three choices.
int result = fl_choice("Do you want to save before quitting?",
"Don't Save", // 0
"Save", // 1
"Cancel" // 2
);
if (result == 0) { // Close without saving
window->hide();
} else if (result == 1) { // Save and close
save();
window->hide();
} else if (result == 2) { // Cancel / don't close
// don't do anything
}
}
Set your window's callback wherever you set up your Fl_Window, e.g. in your main function:
window->callback( win_cb );
You probably need to look at using a modal (i.e., dialog) window. Look at <FL/fl_ask.h>
if (fl_ask("Do you really want to save and exit?"))
save_and_exit();
The header also has functions for the popup's font, title, etc.
When you build you don't get an error or warning? The problem is probably that you have both global functions names yes and no and also local variables called just the same. Rename either the functions of the variables.
No need to use hide().
You can simply use exit(0); in a callback.