This repository has been archived on 2024-07-15. You can view files and clone it, but cannot push or open issues or pull requests.
hedera/vn/vn-gui.c

1544 lines
37 KiB
C

/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "vn-gui.h"
#include <gdome.h>
#include <stdlib.h>
#include <glib/gstdio.h>
#include <libxml/tree.h>
#include <libxml/parser.h>
#include <libxml/valid.h>
#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;
};
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);
}
/*
* 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;
}
/*
* 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 * path;
gchar * mod_title_strip, * mod_name;
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;
// Validating the module definition against the DTD (using libxml2 directly)
xmlValidCtxtPtr ctxt = xmlNewValidCtxt ();
path = g_strdup_printf ("%s/%s", dir, file);
if (ctxt)
{
gboolean valid = FALSE;
xmlDocPtr doc = xmlParseFile (path);
if (doc)
{
xmlDtdPtr dtd = xmlParseDTD (NULL, (const xmlChar *) MODULE_DTD);
if (dtd)
valid = xmlValidateDtd (ctxt, doc, dtd);
else
g_warning ("The DTD is not well formed");
xmlFreeDtd (dtd);
}
xmlFreeValidCtxt (ctxt);
xmlFreeDoc (doc);
if (!valid)
{
g_free (path);
g_warning ("The module definition at \"%s\" is not valid", file);
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);
if (doc)
{
nl = gdome_doc_getElementsByTagName (doc, S("library"), &e);
el = (GdomeElement *) gdome_nl_item (nl, 0, &e);
gdome_nl_unref (nl, &e);
library = gdome_el_getAttribute (el, S("name"), &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, S("form"), &e);
action_nl = gdome_doc_getElementsByTagName (doc, S("action"), &e);
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);
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;
const gchar * text_dom;
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);
// 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, S("icon"), &e);
action_name = gdome_el_getAttribute (el, S("action-name"), &e);
accel = gdome_el_getAttribute (el, S("accel"), &e);
name = gdome_el_getAttribute (el, S("name"), &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 (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 ,g_dgettext (text_dom, 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);
}
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, S("name"), &e);
node = gdome_el_firstChild (el, &e);
title = gdome_n_nodeValue (node, &e);
title_strip = g_strstrip (g_strdup (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 (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 tryes 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;
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);
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);
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;
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);
// 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", 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, 0600);
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
));
}