/* * 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 */ #ifndef EZGL_APPLICATION_HPP #define EZGL_APPLICATION_HPP #include "ezgl/canvas.hpp" #include "ezgl/control.hpp" #include "ezgl/callback.hpp" #include "ezgl/graphics.hpp" #include "ezgl/color.hpp" #include #include #include #include /** * A library for creating a graphical user interface. */ namespace ezgl { class application; /** * The signature of a function that connects GObject to functions via signals. * * @see application::get_object. */ using connect_g_objects_fn = void (*)(application *app); /** * The signature of a setup callback function */ using setup_callback_fn = void (*)(application *app, bool new_window); /** * The signature of a button callback function */ using button_callback_fn = void (*)(GtkWidget *widget, application *app); /** * The signature of a user-defined callback function for mouse events */ using mouse_callback_fn = void (*)(application *app, GdkEventButton *event, double x, double y); /** * The signature of a user-defined callback function for keyboard events */ using key_callback_fn = void (*)(application *app, GdkEventKey *event, char *key_name); /** * The core application. * * The GUI of an application is created from an XML file. Widgets created in the XML file can be retrieved from an * application object, but only after the object has been initialized by GTK via application::run. */ class application { public: /** * Configuration settings for the application. * * The GUI will be built from the XML description given by main_ui_resource. * The XML file must contain a GtkWindow with the name in window_identifier. */ struct settings { /** * The resource path that contains the XML file, which describes the GUI. */ std::string main_ui_resource; /** * The name of the main window in the XML file. */ std::string window_identifier; /** * The name of the main canvas in the XML file. */ std::string canvas_identifier; /** * Specify the function that will connect GUI objects to user-defined callbacks. * * GUI objects (i.e., a GObject) can be retrieved from this application object. These objects can then be connected * to specific events using g_signal_connect. A list of signals that can be used to make these connections can be * found here. * * If not provided, application::register_default_buttons_callbacks function will be used, which assumes that the * UI has GtkButton widgets named "ZoomFitButton", "ZoomInButton", "ZoomOutButton", "UpButton", "DownButton", * "LeftButton", "RightButton", "ProceedButton" */ connect_g_objects_fn setup_callbacks; /** * Create the settings structure with default values */ settings() : main_ui_resource("/ezgl/main.ui"), window_identifier("MainWindow"), canvas_identifier("MainCanvas"), setup_callbacks(nullptr) { } /** * Create the settings structure with user-defined values */ settings(std::string m_resource, std::string w_identifier, std::string c_identifier, connect_g_objects_fn s_callbacks = nullptr) : main_ui_resource(m_resource), window_identifier(w_identifier), canvas_identifier(c_identifier), setup_callbacks(s_callbacks) { } }; public: /** * Create an application. * * @param s The preconfigured settings. */ explicit application(application::settings s); /** * Add a canvas to the application. * * If the canvas has already been added, it will not be overwritten and a warning will be displayed. * * @param canvas_id The id of the GtkDrawingArea in the XML file. * @param draw_callback The function to call that draws to this canvas. * @param coordinate_system The initial coordinate system of this canvas. * @param background_color (OPTIONAL) The color of the canvas background. Default is WHITE. * * @return A pointer to the newly created canvas. */ canvas *add_canvas(std::string const &canvas_id, draw_canvas_fn draw_callback, rectangle coordinate_system, color background_color = WHITE); /** * Add a button * * @param button_text the new button text * @param left the column number to attach the left side of the new button to * @param top the row number to attach the top side of the new button to * @param width the number of columns that the button will span * @param height the number of rows that the button will span * @param button_func callback function for the button * * The function assumes that the UI has a GtkGrid named "InnerGrid" */ void create_button(const char *button_text, int left, int top, int width, int height, button_callback_fn button_func); /** * Add a button convenience * Adds a button at a given row index (assuming buttons in the right bar use 1 row each) * by inserting a row in the grid and adding the button. Uses the default width of 3 and height of 1 * * @param button_text the new button text * @param insert_row the row in the right bar to insert the button * @param button_func callback function for the button * * The function assumes that the UI has a GtkGrid named "InnerGrid" */ void create_button(const char *button_text, int insert_row, button_callback_fn button_func); /** * Deletes a button by its label (displayed text) * * @param the text of the button to delete * @return whether the button was found and deleted * * The function assumes that the UI has a GtkGrid named "InnerGrid" */ bool destroy_button(const char *button_text_to_destroy); /** * Change the label of the button (displayed text) * * @param button_text the old text of the button * @param new_button_text the new button text * * The function assumes that the UI has a GtkGrid named "InnerGrid" */ void change_button_text(const char *button_text, const char *new_button_text); /** * Update the message in the status bar * * @param message The message that will be displayed on the status bar * * The function assumes that the UI has a GtkStatusbar named "StatusBar" */ void update_message(std::string const &message); /** * Change the coordinate system of a created canvas * * @param canvas_id The id of the GtkDrawingArea in the XML file. * @param coordinate_system The new coordinate system of this canvas. */ void change_canvas_world_coordinates(std::string const &canvas_id, rectangle coordinate_system); /** * redraw the main canvas */ void refresh_drawing(); /** * Get a renderer that can be used to draw on top of the main canvas */ renderer *get_renderer(); /** * Flush the drawings done by the renderer, returned from get_renderer(), to the on-screen buffer * * The flushing is done immediately */ void flush_drawing(); /** * Run the application. * * Once this is called, the application will be initialized first. Initialization will build the GUI based on the XML * resource given in the constructor. Once the GUI has been created, the function initial_setup_user_callback will be * called. * * After initialization, control of the program will be given to GTK. You will only regain control for the signals * that you have registered callbacks for. * * @param initial_setup_user_callback A user-defined function that is called before application activation * @param mouse_press_user_callback The user-defined callback function for mouse press * @param mouse_move_user_callback The user-defined callback function for mouse move * @param key_press_user_callback The user-defined callback function for keyboard press * * @return The exit status. */ int 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); /** * Destructor. */ ~application(); /** * Copies are disabled. */ application(application const &) = delete; /** * Copies are disabled. */ application &operator=(application const &) = delete; /** * Ownership of an application is transferrable. */ application(application &&) = default; /** * Ownership of an application is transferrable. */ application &operator=(application &&) = default; /** * Retrieve a pointer to a canvas that was previously added to the application. * * Calling this function before application::run results in undefined behaviour. * * @param canvas_id The key used when the canvas was added. * * @return A non-owning pointer, or nullptr if not found. * * @see application::get_object */ canvas *get_canvas(std::string const &canvas_id) const; /** * Retrieve a GLib Object (i.e., a GObject). * * This is useful for retrieving GUI elements specified in your XML file(s). You should only call this function after * the application has been run, otherwise the GUI elements will have not been created yet. * * @param name The ID of the object. * @return The object with the ID, or NULL if it could not be found. * * @see application::run */ GObject *get_object(gchar const *name) const; /** * Get the ID of the main window */ std::string get_main_window_id() const { return m_window_id; } /** * Get the ID of the main canvas */ std::string get_main_canvas_id() const { return m_canvas_id; } /** * Quit the application */ void quit(); private: // The package path to the XML file that describes the UI. std::string m_main_ui; // The ID of the main window to add to our GTK application. std::string m_window_id; // The ID of the main canvas std::string m_canvas_id; // The GTK application. GtkApplication *m_application; // The GUI builder that parses an XML user interface. GtkBuilder *m_builder; // The function to call when the application is starting up. connect_g_objects_fn m_register_callbacks; // The collection of canvases added to the application. std::map> m_canvases; // A flag that indicates if the run() was called before or not to allow multiple reruns bool first_run; // A flag that indicates if we are resuming an older run to allow proper quitting bool resume_run; private: // Called when our GtkApplication is initialized for the first time. static void startup(GtkApplication *gtk_app, gpointer user_data); // Called when GTK activates our application for the first time. static void activate(GtkApplication *gtk_app, gpointer user_data); // Called during application activation to setup the default callbacks for the prebuilt buttons static void register_default_buttons_callbacks(application *application); // Called during application activation to setup the default callbacks for the mouse and key events static void register_default_events_callbacks(application *application); public: // The user-defined initial setup callback function setup_callback_fn initial_setup_callback; // The user-defined callback function for handling mouse press mouse_callback_fn mouse_press_callback; // The user-defined callback function for handling mouse move mouse_callback_fn mouse_move_callback; // The user-defined callback function for handling keyboard press key_callback_fn key_press_callback; }; /** * Set the disable_event_loop flag to new_setting * Call with new_setting == true to make the event_loop immediately return. * Needed only for auto-marking * * @param new_setting The new state of disable_event_loop flag */ void set_disable_event_loop(bool new_setting); } #endif //EZGL_APPLICATION_HPP