/* * 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 CHILD_WINDOW_UI _GUI_DIR"/child-window.glade" #define ACTIONS_UI _GUI_DIR"/actions.glade" #define MENUBAR_XML _GUI_DIR"/menubar.ui" #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; GtkNotebook * notebook; GtkUIManager * manager; GtkWidget * toolbar; VnForm * active_form; GtkToggleAction * dynamic_tabs; GtkToggleAction * view_toolbar; guint merge_id; gboolean maximized; }; typedef struct { VnGui * gui; gchar * name; } FormData; typedef struct { GtkActionEntry * entry; FormData * form; } ActionData; typedef struct { GSList * action_data; VnMod * mod; } ModData; 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; 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_open_form_activated (GtkAction * action, FormData * form_data); void vn_gui_on_child_destroyed (GtkWindow * widget, VnWindow * window); void vn_gui_on_page_removed (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window); void vn_gui_on_main_page_removed (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window); static guint signals[LAST_SIGNAL] = {0}; /** * 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 static void vn_gui_free_action_data (ActionData * data) { if (data) { if (data->form) g_free (data->form->name); g_free (data->form); if (data->entry) { g_free ((gchar *) data->entry->name); g_free ((gchar *) data->entry->stock_id); g_free ((gchar *) data->entry->label); g_free ((gchar *) data->entry->accelerator); g_free ((gchar *) data->entry->tooltip); } g_free (data->entry); } g_free (data); } static void vn_gui_free_mod_data (ModData * data) { if (data->action_data) g_slist_free_full (data->action_data, (GDestroyNotify) vn_gui_free_action_data); g_free (data); } /* * 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_child_destroyed, window); g_signal_handlers_disconnect_by_func (window->notebook, vn_gui_on_page_removed, window); g_signal_handlers_disconnect_by_func (window->notebook, vn_gui_on_main_page_removed, window); gtk_widget_destroy (GTK_WIDGET (window->widget)); g_object_unref (window->manager); g_free (window); } static VnWindow * vn_gui_get_active_window (VnGui * obj) { GSList * n; for (n = obj->windows; n; n = n->next) if (gtk_window_is_active (((VnWindow *) n->data)->widget)) return n->data; return obj->main_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, * action_nl; GdomeElement * el; GdomeNode * node; GdomeDOMString * mod_title; GdomeDOMString * library; GdomeDOMString * name_str; gchar * path = g_strdup_printf ("%s/%s", dir, file); if (!vn_gui_validate_module_definition (obj, path)) { g_warning ("The module definition at \"%s\" is not valid", path); g_free (path); return; } // Getting the module info from XML file di = gdome_di_mkref (); doc = gdome_di_createDocFromURI (di, path, 0, &e); g_free (path); gdome_di_unref (di, &e); name_str = S("name"); if (doc) { GdomeDOMString * library_str = S("library"), * form_str = S("form"), * action_str = S("action"); 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); action_nl = gdome_doc_getElementsByTagName (doc, action_str, &e); gdome_str_unref (form_str); gdome_str_unref (action_str); gdome_str_unref (library); gdome_doc_unref (doc, &e); gdome_n_unref (node, &e); gdome_el_unref (el, &e); } else { g_warning ("VnGui: Error loading module info file: %s", (gchar *) file); gdome_str_unref (name_str); return; } // 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) { g_module_make_resident (module); mod = g_object_new (VN_TYPE_MOD ,"name" ,mod_name ,"data-dir" ,dir ,"module" ,module ,NULL ); } else g_warning ("VnGui: Can't load module %s: %s" ,mod_name ,g_module_error () ); // If successful, load forms, actions and UI if (mod) { GType type; gulong len, n; gchar * c_name; gchar * title_strip; gchar * ui_file; gchar * buffer; GdomeDOMString * name; GdomeDOMString * icon; GdomeDOMString * action_name; GdomeDOMString * accel; GdomeDOMString * title; gchar * symbol_name; GtkTreeIter parent_iter; GtkTreeIter * iter; VnFormGetTypeFunc mod_get_type_func; GError * err = NULL; GSList * mod_actions = NULL; ActionData * action_data; GtkActionGroup * actions = gtk_action_group_new (mod_name); GdomeDOMString * icon_str = S("icon"), * action_name_str = S("action-name"), * accel_str = S("accel"); // 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++) { // Getting form info el = (GdomeElement *) gdome_nl_item (nl, n, &e); icon = gdome_el_getAttribute (el, icon_str, &e); action_name = gdome_el_getAttribute (el, action_name_str, &e); accel = gdome_el_getAttribute (el, accel_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); // Loading form symbol_name = g_strdup_printf ("vn_%s_get_type", c_name); if (g_module_symbol (module, symbol_name, (gpointer) &mod_get_type_func)) { type = mod_get_type_func (); if (g_type_is_a (type, VN_TYPE_FORM)) { 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); if (g_strcmp0 ("", action_name->str)) { action_data = g_new (ActionData, 1); action_data->entry = g_new (GtkActionEntry, 1); action_data->entry->name = g_strdup (action_name->str); action_data->entry->stock_id = g_strdup (icon->str); action_data->entry->label = g_strdup (title_strip); action_data->entry->accelerator = g_strdup (accel->str); action_data->entry->tooltip = NULL; action_data->entry->callback = (GCallback) vn_gui_on_open_form_activated; action_data->form = g_new (FormData, 1); action_data->form->name = g_strdup (name->str); action_data->form->gui = obj; mod_actions = g_slist_prepend (mod_actions, action_data); gtk_action_group_add_actions (actions ,action_data->entry, 1 ,action_data->form ); } } 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); gdome_str_unref (name); gdome_str_unref (icon); gdome_str_unref (action_name); gdome_str_unref (accel); gdome_str_unref (title); g_free (title_strip); g_free (symbol_name); } gdome_str_unref (icon_str); gdome_str_unref (action_name_str); gdome_str_unref (accel_str); len = gdome_nl_length (action_nl, &e); for (n = 0; n < len; n++) { el = (GdomeElement *) gdome_nl_item (action_nl, n, &e); name = gdome_el_getAttribute (el, name_str, &e); 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_el_unref (el, &e); gdome_n_unref (node, &e); gdome_str_unref (title); action_data = g_new (ActionData, 1); action_data->entry = g_new0 (GtkActionEntry, 1); action_data->entry->name = g_strdup (name->str); action_data->entry->label = g_strdup (title_strip); action_data->form = NULL; mod_actions = g_slist_prepend (mod_actions, action_data); gtk_action_group_add_actions (actions, action_data->entry, 1, NULL); gdome_str_unref (name); g_free (title_strip); } ui_file = g_strdup_printf ("%s/%s.ui", dir, mod_name); if (g_file_get_contents (ui_file, &buffer, NULL, &err)) { ModData * mod_data = g_new (ModData, 1); mod_data->action_data = mod_actions; mod_data->mod = mod; vn_mod_set_ui (mod, buffer); obj->modules = g_slist_prepend (obj->modules, mod_data); } else { g_warning ("VnGui: %s", err->message); g_error_free (err); g_slist_free_full (mod_actions, (GDestroyNotify) vn_gui_free_action_data); } g_free (ui_file); // g_object_unref (mod); g_object_unref (actions); } g_free (mod_title_strip); g_free (mod_name); gdome_str_unref (name_str); gdome_str_unref (mod_title); gdome_nl_unref (nl, &e); gdome_nl_unref (action_nl, &e); } static VnWindow * vn_gui_add_window (VnGui * obj, GtkWindow * widget, GtkNotebook * notebook) { GdkRGBA color; GSList * n, * m; GError * err = NULL; GtkActionGroup * actions = NULL; GtkStyleContext * style; GtkBuilder * builder; VnWindow * window; window = g_new (VnWindow, 1); window->obj = obj; window->widget = widget; window->notebook = notebook; window->manager = gtk_ui_manager_new (); 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))); style = gtk_widget_get_style_context (GTK_WIDGET (widget)); gtk_style_context_get_background_color (style, GTK_STATE_FLAG_NORMAL, &color); gtk_widget_override_background_color (GTK_WIDGET (notebook), GTK_STATE_FLAG_NORMAL, &color); gtk_widget_show_all (GTK_WIDGET (widget)); // Loading the bars and associated actions builder = gtk_builder_new (); if (gtk_builder_add_from_file (builder, ACTIONS_UI, &err)) { actions = gtk_builder_get (builder, "main-actions"); gtk_builder_connect_signals (builder, window); gtk_ui_manager_insert_action_group (window->manager, actions, -1); } else { g_warning ("VnGui: %s", err->message); g_clear_error (&err); } if (gtk_ui_manager_add_ui_from_file (window->manager, MENUBAR_XML, &err)) { GtkBox * box = GTK_BOX (gtk_bin_get_child (GTK_BIN (widget))); GtkWidget * menubar = gtk_ui_manager_get_widget (window->manager, "/MenuBar"); GtkWidget * toolbar = gtk_ui_manager_get_widget (window->manager, "/Toolbar"); gtk_box_pack_start (box, menubar, FALSE, FALSE, 0); gtk_box_pack_start (box, toolbar, FALSE, FALSE, 0); gtk_style_context_add_class (gtk_widget_get_style_context (toolbar), GTK_STYLE_CLASS_PRIMARY_TOOLBAR); gtk_window_add_accel_group (widget, gtk_ui_manager_get_accel_group (window->manager)); window->toolbar = toolbar; } else { window->toolbar = NULL; g_warning ("VnGui: %s", err->message); g_error_free (err); } if (actions) { // TODO: Load from config file the default value for toggle actions. window->dynamic_tabs = gtk_builder_get (builder, "menu-view-tabs"); gtk_toggle_action_set_active (window->dynamic_tabs, TRUE); window->view_toolbar = gtk_builder_get (builder, "menu-view-toolbar"); gtk_toggle_action_set_active (window->view_toolbar, TRUE); } g_object_unref (builder); // Loading the modules actions for (n = obj->modules; n; n = n->next) { ModData * mod_data = n->data; GtkActionGroup * mod_actions = gtk_action_group_new (vn_mod_get_name (mod_data->mod)); for (m = mod_data->action_data; m; m = m->next) { ActionData * action_data = m->data; gtk_action_group_add_actions (mod_actions ,action_data->entry, 1 ,action_data->form ); } gtk_ui_manager_insert_action_group (window->manager, mod_actions, -1); g_object_unref (mod_actions); if (!gtk_ui_manager_add_ui_from_string (window->manager, vn_mod_get_ui (mod_data->mod), -1, &err)) { g_warning ("VnGui: %s", err->message); g_error_free (err); } } gtk_ui_manager_ensure_update (window->manager); return window; } /* * Shows an error dialog. */ static void vn_gui_show_error (VnGui * obj, const GError * error) { GtkWidget * dialog; GtkWindow * window = vn_gui_get_active_window (obj)->widget; 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->main_window) { 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->main_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 FALSE; } /* * 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 FALSE; } /* * 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) { if (window->active_form) { GtkActionGroup * actions = vn_form_get_action_group (window->active_form); if (actions) { gtk_ui_manager_remove_ui (window->manager, window->merge_id); gtk_ui_manager_remove_action_group (window->manager, actions); } window->active_form = NULL; } } static void vn_gui_set_show_tabs (VnWindow * window) { gboolean show_tabs = gtk_notebook_get_n_pages (window->notebook) > 1 || !gtk_toggle_action_get_active (window->dynamic_tabs); gtk_notebook_set_show_tabs (window->notebook, show_tabs); } //--------------------------------------------------- Window handlers /* * Called when the main window is closed. */ gboolean vn_gui_on_main_deleted (GtkWidget * widget, GdkEvent * event, VnWindow * window) { vn_gui_logout (window->obj, TRUE); return TRUE; } /* * Called when a child window is closed. */ void vn_gui_on_child_destroyed (GtkWindow * widget, VnWindow * window) { VnGui * obj = window->obj; vn_gui_free_window (obj, window); obj->windows = g_slist_remove (obj->windows, window); } //--------------------------------------------------- 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) { GError * err = NULL; GtkTreeIter * iter; GtkActionGroup * actions; VnGui * obj = window->obj; vn_gui_hide_form (window); // Merge form UI with the window UI window->active_form = form; if ((iter = g_hash_table_lookup (obj->forms, vn_form_get_name (form)))) { gchar * window_title, * form_title; gtk_tree_model_get (GTK_TREE_MODEL (obj->tree), iter, COL_TITLE, &form_title, -1); window_title = g_strdup_printf ("%s - %s", form_title, obj->app_title); gtk_window_set_title (window->widget, window_title); g_free (form_title); g_free (window_title); } if ((actions = vn_form_get_action_group (form))) { guint merge_id; const gchar * form_ui = vn_form_get_ui_manager (form); gtk_ui_manager_insert_action_group (window->manager, actions, -1); if ((merge_id = gtk_ui_manager_add_ui_from_string (window->manager, form_ui, -1, &err))) { window->merge_id = merge_id; } else { g_warning ("VnGui: %s", err->message); g_error_free (err); } } gtk_ui_manager_ensure_update (window->manager); } void vn_gui_on_page_added (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window) { vn_gui_set_show_tabs (window); } /* * Called when a page of the main window is removed from its notebook. */ void vn_gui_on_main_page_removed (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window) { vn_gui_set_show_tabs (window); if (gtk_notebook_get_n_pages (notebook) < 1) { vn_gui_hide_form (window); gtk_window_set_title (window->widget, window->obj->app_title); } } /* * Called when a page of a child window is removed from its notebook, if its * the last page destroys the window too. */ void vn_gui_on_page_removed (GtkNotebook * notebook, GtkWidget * page, guint num, VnWindow * window) { vn_gui_set_show_tabs (window); if (gtk_notebook_get_n_pages (notebook) < 1) gtk_widget_destroy (GTK_WIDGET (window->widget)); } //--------------------------------------------------- Action handlers /* * Opens a form when the action associated to it is activated. */ void vn_gui_on_open_form_activated (GtkAction * action, FormData * form_data) { vn_gui_open_form (form_data->gui, form_data->name); } /* * Reconnects to database. */ void vn_gui_on_open_activated (GtkAction * action, VnWindow * window) { vn_gui_reconnect (window->obj); } /* * Logout action handler. */ void vn_gui_on_logout_activated (GtkAction * action, VnWindow * window) { vn_gui_logout (window->obj, FALSE); } /* * Exit action handler. */ void vn_gui_on_exit_activated (GtkAction * action, VnWindow * window) { vn_gui_logout (window->obj, TRUE); } /* * Closes the current tab when the close-tab action is activated. */ void vn_gui_on_close_tab_activated (GtkAction * action, VnWindow * window) { if (window->active_form) vn_gui_close_form (window->obj, window->active_form); } /* * Shows/hides the tabs when the view-tabs action is activated */ void vn_gui_on_dynamic_tabs_activated (GtkToggleAction * action, VnWindow * window) { vn_gui_set_show_tabs (window); } /* * Shows/hides the toolbar when the view-toolbar action is activated */ void vn_gui_on_view_toolbar_activated (GtkToggleAction * action, VnWindow * window) { gtk_widget_set_visible (window->toolbar, gtk_toggle_action_get_active (action)); } /* * Shows a window with program information. */ void vn_gui_on_about_activated (GtkAction * action, VnWindow * window) { gtk_dialog_run (window->obj->about); } //--------------------------------------------------- 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) { 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"); gtk_label_set_text (obj->status_label, status_text); if (status & DB_CONN_LOADING) { // XXX: Used to fix a bug that causes the GtkSpinner not spin. gtk_widget_hide (GTK_WIDGET (obj->spinner)); gtk_widget_show (GTK_WIDGET (obj->spinner)); gtk_spinner_start (obj->spinner); } else gtk_spinner_stop (obj->spinner); } //+++++++++++++++++++++++++++++++++++++++++++++++++++ Public /** * vn_gui_open: * @obj: a #VnGui * * Shows the main GUI. **/ void vn_gui_open (VnGui * obj) { GtkBuilder * builder; GError * err = NULL; g_return_if_fail (VN_IS_GUI (obj)); g_return_if_fail (!obj->main_window); builder = gtk_builder_new (); if (gtk_builder_add_from_file (builder, MAIN_UI, &err)) { gchar * user; GtkLabel * label; GKeyFile * config; GtkWindow * widget; GtkNotebook * notebook; obj->spinner = gtk_builder_get (builder, "spinner"); obj->about = gtk_builder_get (builder, "about"); obj->menu = gtk_builder_get (builder, "menu"); obj->status_label = gtk_builder_get (builder, "status-label"); user = db_conn_get_user (obj->conn); label = gtk_builder_get (builder, "user-name"); gtk_label_set_text (label, user); g_free (user); widget = gtk_builder_get (builder, "window"); notebook = gtk_builder_get (builder, "notebook"); obj->main_window = vn_gui_add_window (obj, widget, notebook); gtk_builder_connect_signals (GTK_BUILDER (builder), obj->main_window); // Restoring interface config = g_key_file_new (); if (g_key_file_load_from_file (config, obj->config_file, 0, NULL)) { gsize m, n; gsize len; gint x, y; gint width, height; gboolean maximized; gchar ** windows; gchar ** 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); if (g_key_file_get_boolean (config, windows[m], "main", NULL)) { window = obj->main_window; gtk_window_move (window->widget, x, y); } else 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); } 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 ); } else if (err) { g_warning ("VnGui: %s", err->message); g_error_free (err); } 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)); g_return_if_fail (obj->main_window); // 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 if (window == obj->main_window) g_key_file_set_boolean (config, group, "main", TRUE); 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); g_return_val_if_fail (obj->main_window, NULL); builder = gtk_builder_new (); if (gtk_builder_add_from_file (builder, CHILD_WINDOW_UI, &err)) { GtkWindow * widget; GtkNotebook * notebook; widget = gtk_builder_get (builder, "child"); notebook = gtk_builder_get (builder, "notebook"); gtk_window_move (widget, x, y); window = vn_gui_add_window (obj, widget, notebook); 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); g_return_val_if_fail (obj->main_window, 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 = vn_gui_get_active_window (obj); 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_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->main_window = NULL; obj->about = 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->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, (GDestroyNotify) vn_gui_free_mod_data); 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 )); }