/* * Copyright (C) 2012 - Juan Ferrer Toribio * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "vn-gui.h" #include #include #include #include #include #include #define MAIN_UI _GUI_DIR"/main.glade" #define MENU_UI _GUI_DIR"/menu.glade" #define MODULE_DTD _DTD_DIR"/module.dtd" #define S(string) (gdome_str_mkref (string)) #define gtk_builder_get(builder, id) ((gpointer) gtk_builder_get_object (builder, id)) /** * SECTION: vn-gui * @Short_description: GUI manager * @Title: VnGui * * Manages most of the GUI operations. **/ G_DEFINE_TYPE (VnGui, vn_gui, G_TYPE_OBJECT); struct _VnWindow { VnGui * obj; GtkWindow * widget; GtkDialog * about; GtkHeaderBar * header; GtkWidget * menu_button; GtkWidget * spinner; GtkNotebook * notebook; VnForm * active_form; guint merge_id; gboolean maximized; }; enum { COL_ICON ,COL_NAME ,COL_TITLE ,COL_TYPE ,COL_MODULE ,COL_COUNT }; enum { LOGOUT ,EXIT ,LAST_SIGNAL }; typedef struct { VnGui * obj; gboolean aux; GError * error; GThread * thread; } GuiData; void vn_gui_on_new_window_activated (GSimpleAction * a, GVariant * v, gpointer obj); void vn_gui_on_logout_activated (GSimpleAction * a, GVariant * v, gpointer obj); void vn_gui_on_open_activated (GSimpleAction * a, GVariant * v, gpointer obj); void vn_gui_on_about_activated (GSimpleAction * a, GVariant * v, gpointer obj); void vn_gui_on_exit_activated (GSimpleAction * a, GVariant * v, gpointer obj); void vn_gui_on_close_tab_activated (GSimpleAction * a, GVariant * v, gpointer obj); void vn_gui_on_open_form_activated (GSimpleAction * action, GVariant * v, gpointer obj); static void vn_gui_reconnect (VnGui * obj); static void vn_gui_on_conn_error (DbConn * conn, const GError * error, VnGui * obj); static void vn_gui_on_conn_status_changed (DbConn * conn, DbConnStatus status, VnGui * obj); void vn_gui_on_window_destroyed (GtkWindow * widget, VnWindow * window); void vn_gui_on_page_removed (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window); static guint signals[LAST_SIGNAL] = {0}; static const GActionEntry app_entries[] = { {"new-window", vn_gui_on_new_window_activated} ,{"logout", vn_gui_on_logout_activated} ,{"connect", vn_gui_on_open_activated} ,{"about", vn_gui_on_about_activated} ,{"quit", vn_gui_on_exit_activated} }; static const GActionEntry win_entries[] = { {"close", vn_gui_on_close_tab_activated} }; static const GActionEntry open_action[] = { {"open-form", vn_gui_on_open_form_activated, "s"} }; /** * vn_gui_new: * @app: a #GtkApplication * @conn: the #VnLogin * * Creates a new Gui object. * * Return value: the created #VnGui **/ VnGui * vn_gui_new (GtkApplication * app, DbConn * conn) { return g_object_new (VN_TYPE_GUI, "app", app, "conn", conn, NULL); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Private /* * Frees the #GuiData struct. */ static void gui_data_free (GuiData * gui_data) { if (gui_data->error) g_error_free (gui_data->error); g_object_unref (gui_data->obj); g_thread_unref (gui_data->thread); g_free (gui_data); } static void vn_gui_free_window (VnGui * obj, VnWindow * window) { g_signal_handlers_disconnect_by_func (window->widget, vn_gui_on_window_destroyed, window); g_signal_handlers_disconnect_by_func (window->notebook, vn_gui_on_page_removed, window); gtk_widget_destroy (GTK_WIDGET (window->widget)); g_free (window); } /* * Validates a module definition file at @path against the DTD */ static gboolean vn_gui_validate_module_definition (VnGui * obj, const gchar * path) { gboolean valid = FALSE; xmlValidCtxtPtr ctxt = xmlNewValidCtxt (); if (ctxt) { xmlDocPtr doc = xmlParseFile (path); if (doc) { xmlDtdPtr dtd = xmlParseDTD (NULL, (const xmlChar *) MODULE_DTD); if (!dtd) g_error ("The DTD is not well formed!"); valid = xmlValidateDtd (ctxt, doc, dtd); xmlFreeDtd (dtd); } xmlFreeValidCtxt (ctxt); xmlFreeDoc (doc); } return valid; } /* * Loads a module and all of its forms. */ static void vn_gui_load_module (VnGui * obj, const gchar * dir, const gchar * file) { gint n; gchar * mod_title_strip, * mod_name; const gchar * text_dom = NULL; GModule * module = NULL; VnMod * mod = NULL; GdomeException e; GdomeDocument * doc; GdomeDOMImplementation * di; GdomeNodeList * nl; GdomeElement * el; GdomeNode * node; GdomeDOMString * mod_title; GdomeDOMString * library; GdomeDOMString * name_str; gchar * path = g_strdup_printf ("%s/%s", dir, file); // Getting the module info from XML file di = gdome_di_mkref (); doc = gdome_di_createDocFromURI (di, path, 0, &e); gdome_di_unref (di, &e); name_str = S("name"); if (doc) { gboolean is_module = FALSE; GdomeDocumentType * dt = gdome_doc_doctype (doc, &e); if (dt) { GdomeDOMString * dt_name = gdome_dt_name (dt, &e); is_module = dt_name && !g_strcmp0 (dt_name->str, "hedera-module"); gdome_str_unref (dt_name); gdome_dt_unref (dt, &e); } if (is_module) { if (vn_gui_validate_module_definition (obj, path)) { GdomeDOMString * library_str = S("library"), * form_str = S("form"); nl = gdome_doc_getElementsByTagName (doc, library_str, &e); el = (GdomeElement *) gdome_nl_item (nl, 0, &e); gdome_str_unref (library_str); gdome_nl_unref (nl, &e); library = gdome_el_getAttribute (el, name_str, &e); mod_name = g_strdup (library->str); node = gdome_el_firstChild (el, &e); mod_title = gdome_n_nodeValue (node, &e); mod_title_strip = g_strstrip (g_strdup (mod_title->str)); nl = gdome_doc_getElementsByTagName (doc, form_str, &e); gdome_str_unref (form_str); gdome_str_unref (library); gdome_doc_unref (doc, &e); gdome_n_unref (node, &e); gdome_el_unref (el, &e); } else { g_warning ("VnGui: The module definition at \"%s\" is not valid", path); gdome_doc_unref (doc, &e); gdome_str_unref (name_str); g_free (path); return; } } else { gdome_doc_unref (doc, &e); gdome_str_unref (name_str); g_free (path); return; } } else { g_warning ("VnGui: Error loading module info file: %s", (gchar *) file); gdome_str_unref (name_str); g_free (path); return; } g_free (path); // Loading the module dynamically for (n = 0; obj->lib_dirs[n] && !module; n++) { path = g_module_build_path (obj->lib_dirs[n], mod_name); module = g_module_open (path, 0); g_free (path); } if (module) { GType mod_type = VN_TYPE_MOD; VnModGetTypeFunc mod_get_type_func; gchar * c_name = g_strdelimit (g_strdup (mod_name), "-. ", '_'), * symbol_name = g_strdup_printf ("vn_%s_get_type", c_name); g_module_make_resident (module); if (g_module_symbol (module, symbol_name, (gpointer) &mod_get_type_func) && g_type_is_a (mod_get_type_func (), VN_TYPE_MOD)) mod_type = mod_get_type_func (); mod = g_object_new (mod_type ,"name" ,mod_name ,"data-dir" ,dir ,"module" ,module ,"title" ,mod_title_strip ,"gui" ,obj ,NULL ); g_free (c_name); g_free (symbol_name); } else g_warning ("VnGui: Can't load module %s: %s", mod_name, g_module_error ()); // If successful, load forms if (mod) { gulong len, n; GtkTreeIter parent_iter; GdomeDOMString * icon_str = S("icon"); // Creating folder to put forms inside text_dom = vn_mod_get_text_domain (mod); gtk_tree_store_append (obj->tree, &parent_iter, NULL); gtk_tree_store_set (obj->tree, &parent_iter ,COL_ICON ,"gtk-directory" ,COL_TITLE ,g_dgettext (text_dom, mod_title_strip) ,COL_TYPE ,G_TYPE_NONE ,COL_NAME ,NULL ,-1 ); len = gdome_nl_length (nl, &e) ; for (n = 0; n < len; n++) { gchar * c_name, * title_strip, * symbol_name; GdomeDOMString * icon, * name, * title; VnFormGetTypeFunc form_get_type_func; el = (GdomeElement *) gdome_nl_item (nl, n, &e); icon = gdome_el_getAttribute (el, icon_str, &e); name = gdome_el_getAttribute (el, name_str, &e); c_name = g_strdelimit (g_strdup (name->str), "-. ", '_'); node = gdome_el_firstChild (el, &e); title = gdome_n_nodeValue (node, &e); title_strip = g_strstrip (g_strdup (g_dgettext (text_dom, title->str))); gdome_n_unref (node, &e); gdome_el_unref (el, &e); symbol_name = g_strdup_printf ("vn_%s_get_type", c_name); if (g_module_symbol (module, symbol_name, (gpointer) &form_get_type_func)) { GType type = form_get_type_func (); if (g_type_is_a (type, VN_TYPE_FORM)) { GtkTreeIter * iter = g_new (GtkTreeIter, 1); gtk_tree_store_append (obj->tree, iter, &parent_iter); gtk_tree_store_set (obj->tree, iter ,COL_NAME ,name->str ,COL_ICON ,icon->str ,COL_TITLE ,title_strip ,COL_TYPE ,type ,COL_MODULE ,mod ,-1 ); g_hash_table_replace (obj->forms, g_strdup (name->str), iter); } else g_warning ("VnGui: %s isn't a VnForm", g_type_name (type)); } else g_warning ("VnGui: Error loading form: %s", g_module_error ()); g_free (c_name); g_free (title_strip); g_free (symbol_name); gdome_str_unref (name); gdome_str_unref (icon); gdome_str_unref (title); } obj->modules = g_slist_prepend (obj->modules, mod); gdome_str_unref (icon_str); } g_free (mod_title_strip); g_free (mod_name); gdome_str_unref (name_str); gdome_str_unref (mod_title); gdome_nl_unref (nl, &e); } static void set_accelerator (VnGui * obj, GMenuModel * model, gint item, gboolean enable) { GMenuAttributeIter * iter; GVariant * value; GVariant * target = NULL; const char * key; const char * accel = NULL; const char * action = NULL; iter = g_menu_model_iterate_item_attributes (model, item); while (g_menu_attribute_iter_get_next (iter, &key, &value)) { if (g_str_equal (key, "action") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) action = g_variant_get_string (value, NULL); else if (g_str_equal (key, "accel") && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) accel = g_variant_get_string (value, NULL); else if (g_str_equal (key, "target")) target = g_variant_ref (value); g_variant_unref (value); } g_object_unref (iter); if (accel && action) { if (enable) gtk_application_add_accelerator (obj->app, accel, action, target); else gtk_application_remove_accelerator (obj->app, action, target); } if (target) g_variant_unref (target); } static void vn_gui_set_menu_accels (VnGui * obj, GMenuModel * menu, gboolean enable) { gint i; for (i = 0; i < g_menu_model_get_n_items (menu); i++) { GMenuLinkIter * iter; GMenuModel * more; const gchar * key; set_accelerator (obj, menu, i, enable); iter = g_menu_model_iterate_item_links (menu, i); while (g_menu_link_iter_get_next (iter, &key, &more)) { vn_gui_set_menu_accels (obj, more, enable); g_object_unref (more); } g_object_unref (iter); } } static VnWindow * vn_gui_add_window (VnGui * obj, GtkWindow * widget, GtkNotebook * notebook) { GSList * n; GtkWidget * button; VnWindow * window = g_new (VnWindow, 1); window->obj = obj; window->widget = widget; window->notebook = notebook; window->active_form = NULL; window->merge_id = 0; obj->windows = g_slist_prepend (obj->windows, window); gtk_application_add_window (obj->app, widget); gtk_notebook_set_group_name (notebook, g_application_get_application_id (G_APPLICATION (obj->app))); // Setting header and window menu window->header = g_object_new (GTK_TYPE_HEADER_BAR ,"show-close-button", TRUE ,"title", obj->app_title ,"subtitle", db_conn_get_user (obj->conn) ,NULL); gtk_window_set_titlebar (widget, GTK_WIDGET (window->header)); button = gtk_menu_button_new (); g_action_map_add_action_entries (G_ACTION_MAP (window->widget), win_entries, G_N_ELEMENTS (win_entries), window); gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (button), obj->main_menu); gtk_button_set_image (GTK_BUTTON (button), gtk_image_new_from_icon_name ("emblem-system-symbolic", GTK_ICON_SIZE_BUTTON)); gtk_header_bar_pack_end (window->header, button); button = gtk_menu_button_new (); gtk_widget_set_no_show_all (button, TRUE); gtk_header_bar_pack_start (window->header, button); window->menu_button = button; window->spinner = gtk_spinner_new (); gtk_widget_set_opacity (window->spinner, 0); gtk_header_bar_pack_end (window->header, window->spinner); // Loading the modules actions g_action_map_add_action_entries (G_ACTION_MAP (window->widget), open_action, G_N_ELEMENTS (open_action), window); for (n = obj->modules; n; n = n->next) { gint size; VnMod * mod = VN_MOD (n->data); const GActionEntry * actions = vn_mod_get_actions (mod, &size); g_action_map_add_action_entries (G_ACTION_MAP (window->widget), actions, size, mod); } vn_gui_set_menu_accels (obj, obj->main_menu, TRUE); gtk_widget_show_all (GTK_WIDGET (widget)); return window; } /* * Shows an error dialog. */ static void vn_gui_show_error (VnGui * obj, const GError * error) { GtkWidget * dialog; GtkWindow * window = obj->active_window ? obj->active_window->widget : NULL; if (error && error->code == DB_CONN_ERROR_LOST) dialog = gtk_message_dialog_new (window ,GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT ,GTK_MESSAGE_QUESTION ,GTK_BUTTONS_YES_NO ,_("Connection has been lost. Do you want to reconnect?") ); else dialog = gtk_message_dialog_new (window ,GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT ,GTK_MESSAGE_WARNING ,GTK_BUTTONS_CLOSE ,_("An error occurred in the connection.") ); gtk_window_set_title (GTK_WINDOW (dialog), _("Database error")); if (error) gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), "%s", error->message); else gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), _("Unknown error")); if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_YES) vn_gui_reconnect (obj); gtk_widget_destroy (GTK_WIDGET (dialog)); } /* * Closes and saves the GUI interface. */ static void vn_gui_close (VnGui * obj) { GSList * n; g_return_if_fail (VN_IS_GUI (obj)); if (obj->windows) { vn_gui_save (obj); g_object_disconnect (obj->conn ,"any_signal", vn_gui_on_conn_error, obj ,"any_signal", vn_gui_on_conn_status_changed, obj ,NULL ); for (n = obj->windows; n; n = n->next) vn_gui_free_window (obj, n->data); g_slist_free (obj->windows); obj->windows = NULL; obj->active_window = NULL; } } /* * Idle function that completes the reopen thread. */ static gboolean vn_gui_reconnect_idle (GuiData * gui_data) { if (!gui_data->aux) vn_gui_show_error (gui_data->obj, gui_data->error); return G_SOURCE_REMOVE; } /* * Thread function that tries to reopen the connection asynchronously. */ static void vn_gui_reconnect_thread (GuiData * gui_data) { gui_data->aux = db_conn_reconnect (gui_data->obj->conn, &gui_data->error); g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) vn_gui_reconnect_idle, gui_data, (GDestroyNotify) gui_data_free); } /* * Reconnects to database. */ void vn_gui_reconnect (VnGui * obj) { GuiData * gui_data = g_new (GuiData, 1); gui_data->obj = g_object_ref (obj); gui_data->aux = FALSE; gui_data->error = NULL; gui_data->thread = g_thread_new ("vn-gui-reconnect", (GThreadFunc) vn_gui_reconnect_thread, gui_data); } /* * Saves the login information and closes the main GUI. */ static gboolean vn_gui_logout_idle (GuiData * gui_data) { vn_gui_close (gui_data->obj); if (gui_data->aux) g_signal_emit (gui_data->obj, signals[EXIT], 0); else g_signal_emit (gui_data->obj, signals[LOGOUT], 0); return G_SOURCE_REMOVE; } /* * Thread function that tryes to close the connection asynchronously. */ static void vn_gui_logout_thread (GuiData * gui_data) { db_conn_close (gui_data->obj->conn, TRUE); g_idle_add_full (G_PRIORITY_DEFAULT_IDLE, (GSourceFunc) vn_gui_logout_idle, gui_data, (GDestroyNotify) gui_data_free); } /* * Closes the connection and the user interface. */ void vn_gui_logout (VnGui * obj, gboolean exit) { GuiData * gui_data = g_new (GuiData, 1); gui_data->obj = g_object_ref (obj); gui_data->aux = exit; gui_data->error = NULL; gui_data->thread = g_thread_new ("vn-gui-close", (GThreadFunc) vn_gui_logout_thread, gui_data); } static void vn_gui_hide_form (VnWindow * window) { VnForm * form = window->active_form; if (form) { gint i, size; const GActionEntry * actions = vn_form_get_actions (form, &size); GMenuModel * menu = vn_form_get_menu_model (form); for (i = 0; i < size; i++) g_action_map_remove_action (G_ACTION_MAP (window->widget), actions[i].name); if (menu) vn_gui_set_menu_accels (window->obj, menu, FALSE); gtk_widget_hide (window->menu_button); window->active_form = NULL; } } static void vn_gui_set_show_tabs (VnWindow * window) { gtk_notebook_set_show_tabs (window->notebook, gtk_notebook_get_n_pages (window->notebook) > 1); } //--------------------------------------------------- Window handlers /* * Called when the last window is closed. */ gboolean vn_gui_on_last_window_deleted (GtkWidget * widget, GdkEvent * event, VnWindow * window) { VnGui * obj = window->obj; if (obj->windows->next) return FALSE; vn_gui_logout (obj, TRUE); return TRUE; } /* * Called when a window is closed. */ void vn_gui_on_window_destroyed (GtkWindow * widget, VnWindow * window) { VnGui * obj = window->obj; vn_gui_free_window (obj, window); obj->windows = g_slist_remove (obj->windows, window); } /* * Called when any window of the application gets the focus. */ gboolean vn_gui_on_window_focused (GtkWidget * widget, GdkEvent * event, VnWindow * window) { window->obj->active_window = window; return FALSE; } //--------------------------------------------------- Notebook handlers /* * Called when a page is detached from a notebook. This function creates a new * window with a notebook and puts the page in. * Connected to the "create-window" signal of GtkNotebook. */ GtkNotebook * vn_gui_on_page_detached (GtkNotebook * old_notebook, GtkWidget * page, gint x, gint y, VnWindow * window) { VnWindow * new_window = vn_gui_create_window (window->obj, x, y); return (window) ? new_window->notebook : NULL; } /* * Called when the focus changes from a page to another. It is also used in * newly opened pages. */ void vn_gui_on_switch_page (GtkNotebook * notebook, VnForm * form, guint num, VnWindow * window) { GtkTreeIter * iter; GMenuModel * menu; VnGui * obj = window->obj; vn_gui_hide_form (window); window->active_form = form; if ((iter = g_hash_table_lookup (obj->forms, vn_form_get_name (form)))) { gchar * form_title; gtk_tree_model_get (GTK_TREE_MODEL (obj->tree), iter, COL_TITLE, &form_title, -1); gtk_header_bar_set_title (window->header, form_title); g_free (form_title); } // Set active form Menu menu = vn_form_get_menu_model (form); if (menu) { gint size; const GActionEntry * actions = vn_form_get_actions (form, &size); g_action_map_add_action_entries (G_ACTION_MAP (window->widget), actions, size, form); vn_gui_set_menu_accels (obj, menu, TRUE); gtk_menu_button_set_menu_model (GTK_MENU_BUTTON (window->menu_button), menu); gtk_widget_show (window->menu_button); } } /* * Called when a page is removed from its notebook. If the window is not the * last one and its the last page destroys the window too. */ void vn_gui_on_page_removed (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window) { if (gtk_notebook_get_n_pages (notebook) < 1) { if (!window->obj->windows->next) { vn_gui_hide_form (window); gtk_header_bar_set_title (window->header, window->obj->app_title); } else gtk_widget_destroy (GTK_WIDGET (window->widget)); } vn_gui_set_show_tabs (window); } /* * Called each time a page is added to a notebook. */ void vn_gui_on_page_added (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window) { vn_gui_set_show_tabs (window); } //--------------------------------------------------- Action handlers /* * Opens a form when the action associated to it is activated. */ void vn_gui_on_open_form_activated (GSimpleAction * a, GVariant * p, gpointer obj) { VnWindow * window = obj; vn_gui_open_form_at_window (window->obj, g_variant_get_string (p, NULL), window); } /* * New empty window action handler. */ void vn_gui_on_new_window_activated (GSimpleAction * a, GVariant * v, gpointer obj) { gint x , y; VnWindow * w; VnGui * o = obj; gtk_window_get_position (o->active_window->widget, &x, &y); w = vn_gui_create_window (obj, x + 100, y + 100); gtk_window_resize (w->widget, 500, 500); } /* * Logout action handler. */ void vn_gui_on_logout_activated (GSimpleAction * a, GVariant * v, gpointer obj) { vn_gui_logout (obj, FALSE); } /* * Reconnects to database. */ void vn_gui_on_open_activated (GSimpleAction * a, GVariant * v, gpointer obj) { vn_gui_reconnect (obj); } /* * Shows a window with program information. */ void vn_gui_on_about_activated (GSimpleAction * a, GVariant * v, gpointer obj) { gtk_dialog_run (VN_GUI (obj)->active_window->about); } /* * Exit action handler. */ void vn_gui_on_exit_activated (GSimpleAction * a, GVariant * v, gpointer obj) { vn_gui_logout (obj, TRUE); } /* * Closes the current tab when the close-tab action is activated. */ void vn_gui_on_close_tab_activated (GSimpleAction * a, GVariant * v, gpointer w) { VnWindow * window = w; VnGui * obj = window->obj; if (window->active_form) vn_gui_close_form (window->obj, window->active_form); else if (!obj->windows->next) vn_gui_logout (obj, TRUE); else gtk_widget_destroy (GTK_WIDGET (window->widget)); } //--------------------------------------------------- Connection handlers /* * Called when there is a query error in the connection. */ static void vn_gui_on_conn_error (DbConn * conn, const GError * error, VnGui * obj) { vn_gui_show_error (obj, error); } /* * Enables/disables the #GtkSpinner when connection is loading. */ static void vn_gui_on_conn_status_changed (DbConn * conn, DbConnStatus status, VnGui * obj) { GSList * l; gchar * status_text; if (status & DB_CONN_CLOSING) status_text = _("Closing connection"); else if (status & DB_CONN_TRANSACTION) status_text = _("Transaction started"); else if (status & DB_CONN_OPENING) status_text = _("Connecting"); else if (status & DB_CONN_LOST) status_text = _("Connection lost"); else if (status == DB_CONN_CLOSED) status_text = _("Connection closed"); else if (status & DB_CONN_LOADING) status_text = _("Loading"); else status_text = _("Ready"); for (l = obj->windows; l; l = l->next) { VnWindow * win = l->data; gtk_widget_set_tooltip_text (win->spinner, status_text); if (status & DB_CONN_LOADING) { gtk_widget_set_opacity (win->spinner, 1); gtk_spinner_start (GTK_SPINNER (win->spinner)); } else { gtk_widget_set_opacity (win->spinner, 0); gtk_spinner_stop (GTK_SPINNER (win->spinner)); } } } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Public /** * vn_gui_open: * @obj: a #VnGui * * Shows the main GUI. **/ void vn_gui_open (VnGui * obj) { GtkBuilder * builder; GKeyFile * config; GError * err = NULL; g_return_if_fail (VN_IS_GUI (obj)); builder = gtk_builder_new (); // Loading Application Menu if (gtk_builder_add_from_file (builder, MENU_UI, &err)) { GSList * n; GMenu * section; GMenuModel * menu = G_MENU_MODEL (gtk_builder_get_object (builder, "app-menu")); g_action_map_add_action_entries (G_ACTION_MAP (obj->app), app_entries, G_N_ELEMENTS (app_entries), obj); gtk_application_set_app_menu (obj->app, menu); obj->main_menu = gtk_builder_get (builder, "win-menu"); section = gtk_builder_get (builder, "modules"); for (n = obj->modules; n; n = n->next) { VnMod * mod = VN_MOD (n->data); GMenuModel * mod_menu = vn_mod_get_menu_model (mod); if (mod_menu) { g_menu_prepend_submenu (section, g_strdup (vn_mod_get_title (mod)), mod_menu); g_object_unref (mod_menu); } } } else if (err) { g_warning ("VnGui: %s", err->message); g_error_free (err); } // Restoring interface config = g_key_file_new (); if (g_key_file_load_from_file (config, obj->config_file, 0, NULL)) { gsize m, n, len; gint x, y, width, height; gboolean maximized; gchar ** windows, ** forms; VnWindow * window; windows = g_key_file_get_groups (config, &len); for (m = 0; m < len; m++) { x = g_key_file_get_integer (config, windows[m], "x", NULL); y = g_key_file_get_integer (config, windows[m], "y", NULL); width = g_key_file_get_integer (config, windows[m], "width", NULL); height = g_key_file_get_integer (config, windows[m], "height", NULL); maximized = g_key_file_get_boolean (config, windows[m], "maximized", NULL); window = vn_gui_create_window (obj, x, y); gtk_window_resize (window->widget, width, height); if (maximized) gtk_window_maximize (window->widget); forms = g_key_file_get_string_list (config, windows[m], "forms", NULL, NULL); for (n = 0; forms[n]; n++) vn_gui_open_form_at_window (obj, forms[n], window); g_key_file_remove_group (config, windows[m], NULL); g_strfreev (forms); } g_strfreev (windows); } else { VnWindow * window = vn_gui_create_window (obj, 100, 100); gtk_window_resize (window->widget, 500, 400); } g_key_file_free (config); g_object_connect (obj->conn ,"signal::error", vn_gui_on_conn_error, obj ,"signal::status-changed", vn_gui_on_conn_status_changed, obj ,NULL ); g_object_unref (builder); } /** * vn_gui_save: * @obj: the #VnGui * * Saves the GUI configuration. **/ void vn_gui_save (VnGui * obj) { gint len; gint j, i = 0; gchar * group; const gchar ** forms; GSList * m; GList * nb_pages, * n; VnWindow * window; gint x, y, width, height; GdkWindow * win; gboolean maximized; GKeyFile * config; g_return_if_fail (VN_IS_GUI (obj)); // Saving the interface configuration config = g_key_file_new (); for (m = obj->windows; m; m = m->next) { window = (VnWindow *) m->data; group = g_strdup_printf ("window%d", i++); // Saving the window position and size gtk_window_get_position (window->widget, &x, &y); gtk_window_get_size (window->widget, &width, &height); g_key_file_set_integer (config, group, "x", x); g_key_file_set_integer (config, group, "y", y); g_key_file_set_integer (config, group, "width", width); g_key_file_set_integer (config, group, "height", height); g_object_get (window->widget, "window", &win, NULL); maximized = (gdk_window_get_state (win) & GDK_WINDOW_STATE_MAXIMIZED); g_object_unref (win); g_key_file_set_boolean (config, group, "maximized", maximized); // Saving the forms opened at window nb_pages = gtk_container_get_children (GTK_CONTAINER (window->notebook)); len = gtk_notebook_get_n_pages (window->notebook); forms = g_new (const gchar *, len); for (j = 0, n = nb_pages; n; n = n->next) forms[j++] = vn_form_get_name (n->data); g_key_file_set_string_list (config, group, "forms", forms, len); g_list_free (nb_pages); g_free (forms); g_free (group); } gvn_key_file_save (config, obj->config_file); g_key_file_free (config); } /** * vn_gui_create_window: * @obj: the #VnGui * @x: the x coordinate * @y: the y coordinate * * Creates a new window. * * Return value: VnWindow. **/ VnWindow * vn_gui_create_window (VnGui * obj, gint x, gint y) { GtkBuilder * builder; VnWindow * window = NULL; GError * err = NULL; g_return_val_if_fail (VN_IS_GUI (obj), NULL); builder = gtk_builder_new (); if (gtk_builder_add_from_file (builder, MAIN_UI, &err)) { GtkWindow * widget = gtk_builder_get (builder, "window"); GtkNotebook * notebook = gtk_builder_get (builder, "notebook"); gtk_window_move (widget, x, y); window = vn_gui_add_window (obj, widget, notebook); window->about = gtk_builder_get (builder, "about"); gtk_builder_connect_signals (builder, window); } else { g_warning ("VnGui: %s", err->message); g_error_free (err); } g_object_unref (builder); return window; } /** * vn_gui_open_form_at_window: * @obj: the #VnGui * @form_name: the name of the form that you want to open * @window: the #VnWindow where the form will be opened * * Opens a new form at the specified window. * * Return value: (transfer none): the created #VnForm **/ VnForm * vn_gui_open_form_at_window (VnGui * obj, const gchar * form_name, VnWindow * window) { gchar * icon; gchar * title; VnMod * module; GType form_type; GtkBox * hbox; GtkWidget * form; GtkWidget * widget; GtkWidget * button; GtkTreeIter * iter; GtkNotebook * notebook = NULL; g_return_val_if_fail (VN_IS_GUI (obj), NULL); iter = g_hash_table_lookup (obj->forms, form_name); if (!iter) { g_warning ("VnGui: Form %s doesn't exist", form_name); return NULL; } gtk_tree_model_get (GTK_TREE_MODEL (obj->tree), iter ,COL_ICON ,&icon ,COL_TITLE ,&title ,COL_MODULE ,&module ,COL_TYPE ,&form_type ,-1 ); form = g_object_new (form_type ,"name" ,form_name ,"gui" ,obj ,"module" ,module ,NULL ); vn_form_open (VN_FORM (form)); hbox = GTK_BOX (gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 5)); widget = gtk_image_new_from_icon_name (icon, GTK_ICON_SIZE_MENU); gtk_box_pack_start (hbox, widget, FALSE, FALSE, 0); widget = gtk_label_new (title); gtk_box_pack_start (hbox, widget, TRUE, TRUE, 0); button = gtk_button_new (); g_signal_connect (button, "clicked", G_CALLBACK (vn_gui_close_form), form); gtk_button_set_relief (GTK_BUTTON (button), GTK_RELIEF_NONE); gtk_box_pack_start (hbox, button, FALSE, FALSE, 0); widget = gtk_image_new_from_icon_name ("window-close-symbolic", GTK_ICON_SIZE_MENU); gtk_button_set_image (GTK_BUTTON (button), widget); if (!window) window = obj->active_window; notebook = window->notebook; gtk_notebook_set_current_page (notebook, gtk_notebook_append_page (notebook, form, GTK_WIDGET (hbox))); gtk_notebook_set_tab_detachable (notebook, form, TRUE); gtk_notebook_set_tab_reorderable (notebook, form, TRUE); gtk_container_child_set (GTK_CONTAINER (notebook), form, "tab-expand", TRUE, NULL); gtk_widget_show_all (GTK_WIDGET (hbox)); gtk_widget_show (form); g_free (icon); g_free (title); return VN_FORM (form); } /** * vn_gui_open_form: * @obj: the #VnGui * @form_name: the name of the form that you want to open * * Opens a new form, creating a new page for it at the main notebook. * * Return value: (transfer none): the created #VnForm **/ VnForm * vn_gui_open_form (VnGui * obj, const gchar * form_name) { g_return_val_if_fail (VN_IS_GUI (obj), NULL); g_return_val_if_fail (form_name, NULL); return vn_gui_open_form_at_window (obj, form_name, NULL); } /** * vn_gui_close_form: * @obj: the #VnGui * @form: the #VnForm that you want to close * * Closes a form. **/ void vn_gui_close_form (VnGui * obj, VnForm * form) { gint num; GtkNotebook * notebook; g_return_if_fail (VN_IS_FORM (form)); notebook = GTK_NOTEBOOK (gtk_widget_get_ancestor ( GTK_WIDGET (form), GTK_TYPE_NOTEBOOK)); num = gtk_notebook_page_num (notebook, GTK_WIDGET (form)); gtk_notebook_remove_page (notebook, num); } /** * vn_gui_get_conn: * @obj: a #VnGui * * Gets the Data Base connection used by @obj * * Return value: (transfer none): the #DbConn **/ DbConn * vn_gui_get_conn (VnGui * obj) { g_return_val_if_fail (VN_IS_GUI (obj), NULL); return obj->conn; } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Properties enum { PROP_CONN = 1 ,PROP_APP }; static void vn_gui_set_property (VnGui * obj, guint id, const GValue * value, GParamSpec * pspec) { switch (id) { case PROP_CONN: { gchar * query_path = g_build_path (G_SEARCHPATH_SEPARATOR_S ,_VN_MODULE_QUERY_DIR ,g_getenv ("VN_MODULE_QUERY_PATH") ,NULL ); obj->conn = g_value_dup_object (value); db_conn_set_query_path (obj->conn, query_path); g_free (query_path); break; } case PROP_APP: { gint n; const gchar * app_id; gchar * app_name; gchar * config_dir; gchar * lib_path, * data_path; obj->app = g_value_dup_object (value); app_id = g_application_get_application_id (G_APPLICATION (obj->app)); app_name = g_strrstr (app_id, ".") + 1; obj->app_title = g_strdup (app_name); obj->app_title[0] = g_ascii_toupper (obj->app_title[0]); config_dir = g_build_filename (g_get_user_config_dir (), app_name, NULL); g_mkdir_with_parents (config_dir, 0700); obj->config_file = g_build_filename (config_dir, "gui.ini", NULL); g_free (config_dir); // Setting module search paths lib_path = g_build_path (G_SEARCHPATH_SEPARATOR_S ,_VN_MODULE_LIB_DIR ,g_getenv ("VN_MODULE_LIB_PATH") ,NULL ); data_path = g_build_path (G_SEARCHPATH_SEPARATOR_S ,_VN_MODULE_DATA_DIR ,g_getenv ("VN_MODULE_DATA_PATH") ,NULL ); obj->lib_dirs = g_strsplit (lib_path, G_SEARCHPATH_SEPARATOR_S, 0); obj->data_dirs = g_strsplit (data_path, G_SEARCHPATH_SEPARATOR_S, 0); g_free (data_path); g_free (lib_path); // Initializing modules for (n = 0; obj->data_dirs[n]; n++) { const gchar * file; GError * err = NULL; GDir * dir = g_dir_open (obj->data_dirs[n], 0, &err); if (dir) { while ((file = g_dir_read_name (dir))) if (!g_strcmp0 (".xml", g_strrstr (file, "."))) vn_gui_load_module (obj, obj->data_dirs[n], file); g_dir_close (dir); } else { g_warning ("VnGui: Error opening directory at module data path: %s" ,err->message); g_error_free (err); } } break; } default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec); } } static void vn_gui_get_property (VnGui * obj, guint id, GValue * value, GParamSpec * pspec) { switch (id) { case PROP_CONN: g_value_set_object (value, obj->conn); break; case PROP_APP: g_value_set_object (value, obj->app); break; default: G_OBJECT_WARN_INVALID_PROPERTY_ID (obj, id, pspec); } } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Class static void vn_gui_init (VnGui * obj) { obj->active_window = NULL; obj->windows = NULL; obj->conn = NULL; obj->app = NULL; obj->app_title = NULL; obj->modules = NULL; obj->lib_dirs = NULL; obj->data_dirs = NULL; obj->config_file = NULL; obj->main_menu = NULL; obj->modules = NULL; obj->forms = g_hash_table_new_full ( (GHashFunc) g_str_hash ,(GEqualFunc) g_str_equal ,(GDestroyNotify) g_free ,(GDestroyNotify) g_free ); obj->tree = gtk_tree_store_new (COL_COUNT ,G_TYPE_STRING // COL_ICON ,G_TYPE_STRING // COL_NAME ,G_TYPE_STRING // COL_TITLE ,G_TYPE_GTYPE // COL_TYPE ,G_TYPE_OBJECT // COL_MODULE ); } static void vn_gui_finalize (VnGui * obj) { vn_gui_close (obj); db_conn_close (obj->conn, FALSE); g_hash_table_unref (obj->forms); g_slist_free_full (obj->modules, g_object_unref); g_clear_object (&obj->conn); g_clear_object (&obj->tree); g_clear_object (&obj->app); g_strfreev (obj->lib_dirs); g_strfreev (obj->data_dirs); g_free (obj->config_file); g_free (obj->app_title); G_OBJECT_CLASS (vn_gui_parent_class)->finalize (G_OBJECT (obj)); } static void vn_gui_class_init (VnGuiClass * k) { GObjectClass * klass = G_OBJECT_CLASS (k); klass->finalize = (GObjectFinalizeFunc) vn_gui_finalize; klass->set_property = (GObjectSetPropertyFunc) vn_gui_set_property; klass->get_property = (GObjectGetPropertyFunc) vn_gui_get_property; signals[LOGOUT] = g_signal_new ("logout", VN_TYPE_GUI, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); signals[EXIT] = g_signal_new ("exit", VN_TYPE_GUI, G_SIGNAL_RUN_FIRST, 0, NULL, NULL, g_cclosure_marshal_VOID__VOID, G_TYPE_NONE, 0 ); g_object_class_install_property (klass, PROP_CONN, g_param_spec_object ("conn" ,_("Connection") ,_("The connection used by Gui") ,DB_TYPE_CONN ,G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY )); g_object_class_install_property (klass, PROP_APP, g_param_spec_object ("app" ,_("Application") ,_("The application handler for the entire program") ,GTK_TYPE_APPLICATION ,G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY )); }