/*
* 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
));
}