OpenFPGA/libs/EXTERNAL/libezgl/src/application.cpp

463 lines
16 KiB
C++
Raw Normal View History

/*
* Copyright 2019 University of Toronto
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Authors: Mario Badr, Sameh Attia and Tanner Young-Schultz
*/
#include "ezgl/application.hpp"
namespace ezgl {
// A flag to disable event loop (default is false)
bool disable_event_loop = false;
void application::startup(GtkApplication *, gpointer user_data)
{
auto ezgl_app = static_cast<application *>(user_data);
g_return_if_fail(ezgl_app != nullptr);
char const *main_ui_resource = ezgl_app->m_main_ui.c_str();
// Build the main user interface from the XML resource.
GError *error = nullptr;
if(gtk_builder_add_from_resource(ezgl_app->m_builder, main_ui_resource, &error) == 0) {
g_error("%s.", error->message);
}
for(auto &c_pair : ezgl_app->m_canvases) {
GObject *drawing_area = ezgl_app->get_object(c_pair.second->id());
c_pair.second->initialize(GTK_WIDGET(drawing_area));
}
g_info("application::startup successful.");
}
void application::activate(GtkApplication *, gpointer user_data)
{
auto ezgl_app = static_cast<application *>(user_data);
g_return_if_fail(ezgl_app != nullptr);
// The main parent window needs to be explicitly added to our GTK application.
GObject *window = ezgl_app->get_object(ezgl_app->m_window_id.c_str());
gtk_application_add_window(ezgl_app->m_application, GTK_WINDOW(window));
// Setup the default callbacks for the mouse and key events
register_default_events_callbacks(ezgl_app);
if(ezgl_app->m_register_callbacks != nullptr) {
ezgl_app->m_register_callbacks(ezgl_app);
} else {
// Setup the default callbacks for the prebuilt buttons
register_default_buttons_callbacks(ezgl_app);
}
if(ezgl_app->initial_setup_callback != nullptr)
ezgl_app->initial_setup_callback(ezgl_app, true);
g_info("application::activate successful.");
}
application::application(application::settings s)
: m_main_ui(s.main_ui_resource)
, m_window_id(s.window_identifier)
, m_canvas_id(s.canvas_identifier)
, m_application(gtk_application_new("ezgl.app", G_APPLICATION_FLAGS_NONE))
, m_builder(gtk_builder_new())
, m_register_callbacks(s.setup_callbacks)
{
#ifdef EZGL_USE_X11
// Prefer x11 first, then other backends.
gdk_set_allowed_backends("x11,*");
#endif
// Connect our static functions application::{startup, activate} to their callbacks. We pass 'this' as the userdata
// so that we can use it in our static functions.
g_signal_connect(m_application, "startup", G_CALLBACK(startup), this);
g_signal_connect(m_application, "activate", G_CALLBACK(activate), this);
first_run = true;
resume_run = false;
}
application::~application()
{
// GTK uses reference counting to track object lifetime. Since we called *_new() for our application and builder, we
// need to unreference them. This should set their reference count to 0, letting GTK know that they should be cleaned
// up in memory.
g_object_unref(m_builder);
g_object_unref(m_application);
}
canvas *application::get_canvas(const std::string &canvas_id) const
{
auto it = m_canvases.find(canvas_id);
if(it != m_canvases.end()) {
return it->second.get();
}
g_warning("Could not find canvas with name %s.", canvas_id.c_str());
return nullptr;
}
canvas *application::add_canvas(std::string const &canvas_id,
draw_canvas_fn draw_callback,
rectangle coordinate_system,
color background_color)
{
if(draw_callback == nullptr) {
// A NULL draw callback means the canvas will never render anything to the screen.
g_warning("Canvas %s's draw callback is NULL.", canvas_id.c_str());
}
// Can't use make_unique with protected constructor without fancy code that will confuse students, so we use new
// instead.
std::unique_ptr<canvas> canvas_ptr(new canvas(canvas_id, draw_callback, coordinate_system, background_color));
auto it = m_canvases.emplace(canvas_id, std::move(canvas_ptr));
if(!it.second) {
// std::map's emplace does not insert the value when the key is already present.
g_warning("Duplicate key (%s) ignored in application::add_canvas.", canvas_id.c_str());
} else {
g_info("The %s canvas has been added to the application.", canvas_id.c_str());
}
return it.first->second.get();
}
GObject *application::get_object(gchar const *name) const
{
// Getting an object from the GTK builder does not increase its reference count.
GObject *object = gtk_builder_get_object(m_builder, name);
g_return_val_if_fail(object != nullptr, nullptr);
return object;
}
int application::run(setup_callback_fn initial_setup_user_callback,
mouse_callback_fn mouse_press_user_callback,
mouse_callback_fn mouse_move_user_callback,
key_callback_fn key_press_user_callback)
{
if(disable_event_loop)
return 0;
initial_setup_callback = initial_setup_user_callback;
mouse_press_callback = mouse_press_user_callback;
mouse_move_callback = mouse_move_user_callback;
key_press_callback = key_press_user_callback;
if(first_run) {
// set the first_run flag to false
first_run = false;
g_info("The event loop is now starting.");
// see: https://developer.gnome.org/gio/stable/GApplication.html#g-application-run
return g_application_run(G_APPLICATION(m_application), 0, 0);
}
// The result of calling g_application_run() again after it returns is unspecified.
// So in the subsequent runs instead of calling g_application_run(), we will go back to the event loop using gtk_main()
else if(!first_run && gtk_application_get_active_window(m_application) != nullptr) {
// Call user's initial setup call
if(initial_setup_callback != nullptr)
initial_setup_callback(this, false);
// set the resume_run flag to true
resume_run = true;
g_info("The event loop is now resuming.");
// see: https://developer.gnome.org/gtk3/stable/gtk3-General.html#gtk-main
gtk_main();
return 0;
}
// But if the GTK window is closed, we will have to destruct and reconstruct the GTKApplication
else {
// Destroy the GTK application
g_object_unref(m_application);
g_object_unref(m_builder);
// Reconstruct the GTK application
m_application = (gtk_application_new("edu.toronto.eecg.ezgl.app", G_APPLICATION_FLAGS_NONE));
m_builder = (gtk_builder_new());
g_signal_connect(m_application, "startup", G_CALLBACK(startup), this);
g_signal_connect(m_application, "activate", G_CALLBACK(activate), this);
// set the resume_run flag to false
resume_run = false;
g_info("The event loop is now restarting.");
// see: https://developer.gnome.org/gio/stable/GApplication.html#g-application-run
return g_application_run(G_APPLICATION(m_application), 0, 0);
}
}
void application::quit()
{
if(resume_run) {
// Quit the event loop (exit gtk_main())
gtk_main_quit();
} else {
// Quit the GTK application (exit g_application_run())
g_application_quit(G_APPLICATION(m_application));
}
}
void application::register_default_events_callbacks(ezgl::application *application)
{
// Get a pointer to the main window GUI object by using its name.
std::string main_window_id = application->get_main_window_id();
GObject *window = application->get_object(main_window_id.c_str());
// Get a pointer to the main canvas GUI object by using its name.
std::string main_canvas_id = application->get_main_canvas_id();
GObject *main_canvas = application->get_object(main_canvas_id.c_str());
// Connect press_key function to keyboard presses in the MainWindow.
g_signal_connect(window, "key_press_event", G_CALLBACK(press_key), application);
// Connect press_mouse function to mouse presses and releases in the MainWindow.
g_signal_connect(main_canvas, "button_press_event", G_CALLBACK(press_mouse), application);
// Connect release_mouse function to mouse presses and releases in the MainWindow.
g_signal_connect(main_canvas, "button_release_event", G_CALLBACK(release_mouse), application);
// Connect release_mouse function to mouse presses and releases in the MainWindow.
g_signal_connect(main_canvas, "motion_notify_event", G_CALLBACK(move_mouse), application);
// Connect scroll_mouse function to the mouse scroll event (up, down, left and right)
g_signal_connect(main_canvas, "scroll_event", G_CALLBACK(scroll_mouse), application);
// Connect press_proceed function to the close button of the MainWindow
g_signal_connect(window, "destroy", G_CALLBACK(press_proceed), application);
}
void application::register_default_buttons_callbacks(ezgl::application *application)
{
// Connect press_zoom_fit function to the Zoom-fit button
GObject *zoom_fit_button = application->get_object("ZoomFitButton");
g_signal_connect(zoom_fit_button, "clicked", G_CALLBACK(press_zoom_fit), application);
// Connect press_zoom_in function to the Zoom-in button
GObject *zoom_in_button = application->get_object("ZoomInButton");
g_signal_connect(zoom_in_button, "clicked", G_CALLBACK(press_zoom_in), application);
// Connect press_zoom_out function to the Zoom-out button
GObject *zoom_out_button = application->get_object("ZoomOutButton");
g_signal_connect(zoom_out_button, "clicked", G_CALLBACK(press_zoom_out), application);
// Connect press_up function to the Up button
GObject *shift_up_button = application->get_object("UpButton");
g_signal_connect(shift_up_button, "clicked", G_CALLBACK(press_up), application);
// Connect press_down function to the Down button
GObject *shift_down_button = application->get_object("DownButton");
g_signal_connect(shift_down_button, "clicked", G_CALLBACK(press_down), application);
// Connect press_left function to the Left button
GObject *shift_left_button = application->get_object("LeftButton");
g_signal_connect(shift_left_button, "clicked", G_CALLBACK(press_left), application);
// Connect press_right function to the Right button
GObject *shift_right_button = application->get_object("RightButton");
g_signal_connect(shift_right_button, "clicked", G_CALLBACK(press_right), application);
// Connect press_proceed function to the Proceed button
GObject *proceed_button = application->get_object("ProceedButton");
g_signal_connect(proceed_button, "clicked", G_CALLBACK(press_proceed), application);
}
void application::update_message(std::string const &message)
{
// Get the StatusBar Object
GtkStatusbar *status_bar = (GtkStatusbar *)get_object("StatusBar");
// Remove all previous messages from the message stack
gtk_statusbar_remove_all(status_bar, 0);
// Push user message to the message stack
gtk_statusbar_push(status_bar, 0, message.c_str());
}
void application::create_button(const char *button_text,
int left,
int top,
int width,
int height,
button_callback_fn button_func)
{
// get the internal Gtk grid
GtkGrid *in_grid = (GtkGrid *)get_object("InnerGrid");
// create the new button with the given label
GtkWidget *new_button = gtk_button_new_with_label(button_text);
// connect the buttons clicked event to the callback
if(button_func != NULL) {
g_signal_connect(G_OBJECT(new_button), "clicked", G_CALLBACK(button_func), this);
}
// add the new button
gtk_grid_attach(in_grid, new_button, left, top, width, height);
// show the button
gtk_widget_show(new_button);
}
void application::create_button(const char *button_text,
int insert_row,
button_callback_fn button_func)
{
// get the internal Gtk grid
GtkGrid *in_grid = (GtkGrid *)get_object("InnerGrid");
// add a row where we want to insert
gtk_grid_insert_row(in_grid, insert_row);
// create the button
create_button(button_text, 0, insert_row, 3, 1, button_func);
}
bool application::destroy_button(const char *button_text_to_destroy)
{
// get the inner grid
GtkGrid *in_grid = (GtkGrid *)get_object("InnerGrid");
// the text to delete, in c++ string form
std::string text_to_del = std::string(button_text_to_destroy);
// iterate over all of the children in the grid and find the button by it's text
GList *children, *iter;
children = gtk_container_get_children(GTK_CONTAINER(in_grid));
for(iter = children; iter != NULL; iter = g_list_next(iter)) {
// iterator to widget
GtkWidget *widget = GTK_WIDGET(iter->data);
// check if widget is a button
if(GTK_IS_BUTTON(widget)) {
// convert to button
GtkButton *button = GTK_BUTTON(widget);
// get the button label
const char *button_label = gtk_button_get_label(button);
if(button_label != nullptr) {
std::string button_text = std::string(button_label);
// does the label of the button match the one we want to delete?
if(button_text == text_to_del) {
// destroy the button (widget) and return true
gtk_widget_destroy(widget);
// free the children list
g_list_free (children);
return true;
}
}
}
}
// free the children list
g_list_free (children);
// couldn't find the button with the label 'button_text_to_destroy'
return false;
}
void application::change_button_text(const char *button_text, const char *new_button_text)
{
// get the inner grid
GtkGrid *in_grid = (GtkGrid *)get_object("InnerGrid");
// the text to change, in c++ string form
std::string text_to_change = std::string(button_text);
// iterate over all of the children in the grid and find the button by it's text
GList *children, *iter;
children = gtk_container_get_children(GTK_CONTAINER(in_grid));
for(iter = children; iter != NULL; iter = g_list_next(iter)) {
// iterator to widget
GtkWidget *widget = GTK_WIDGET(iter->data);
// check if widget is a button
if(GTK_IS_BUTTON(widget)) {
// convert to button
GtkButton *button = GTK_BUTTON(widget);
// get the button label
const char *button_label = gtk_button_get_label(button);
if(button_label != nullptr) {
std::string button_text_str = std::string(button_label);
// does the label of the button match the one we want to change?
if(button_text_str == text_to_change) {
// change button label
gtk_button_set_label(button, new_button_text);
}
}
}
}
// free the children list
g_list_free (children);
}
void application::change_canvas_world_coordinates(std::string const &canvas_id,
rectangle coordinate_system)
{
// get the canvas
canvas *cnv = get_canvas(canvas_id);
// reset the camera system with the new coordinates
if (cnv != nullptr) {
cnv->get_camera().reset_world(coordinate_system);
}
}
void application::refresh_drawing()
{
// get the main canvas
canvas *cnv = get_canvas(m_canvas_id);
// force redrawing
cnv->redraw();
}
void application::flush_drawing()
{
// get the main drawing area widget
GtkWidget *drawing_area = (GtkWidget *)get_object(m_canvas_id.c_str());
// queue a redraw of the GtkWidget
gtk_widget_queue_draw(drawing_area);
// run the main loop on pending events
while(gtk_events_pending())
gtk_main_iteration();
}
renderer *application::get_renderer()
{
// get the main canvas
canvas *cnv = get_canvas(m_canvas_id);
return cnv->create_animation_renderer();
}
void set_disable_event_loop(bool new_setting)
{
disable_event_loop = new_setting;
}
}