-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2006-2007 Imendio AB
* Copyright (C) 2007-2008 Collabora Ltd.
*/
#include "config.h"
+#include "empathy-geometry.h"
#include <sys/stat.h>
-#include <glib.h>
-#include <gdk/gdk.h>
-
-#include "empathy-geometry.h"
+#include "empathy-ui-utils.h"
+#include "empathy-utils.h"
#define DEBUG_FLAG EMPATHY_DEBUG_OTHER
-#include <libempathy/empathy-debug.h>
+#include "empathy-debug.h"
#define GEOMETRY_DIR_CREATE_MODE (S_IRUSR | S_IWUSR | S_IXUSR)
#define GEOMETRY_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
-#define GEOMETRY_KEY_FILENAME "geometry.ini"
-#define GEOMETRY_FORMAT "%d,%d,%d,%d"
-#define GEOMETRY_GROUP_NAME "geometry"
+/* geometry.ini file contains 2 groups:
+ * - one with position and size of each window
+ * - one with the maximized state of each window
+ * Windows are identified by a name. (e.g. "main-window") */
+#define GEOMETRY_FILENAME "geometry.ini"
+#define GEOMETRY_POSITION_FORMAT "%d,%d,%d,%d" /* "x,y,w,h" */
+#define GEOMETRY_POSITION_GROUP "geometry"
+#define GEOMETRY_MAXIMIZED_GROUP "maximized"
+
+/* Key used to keep window's name inside the object's qdata */
+#define GEOMETRY_NAME_KEY "geometry-name-key"
+
+static guint store_id = 0;
-static gchar *geometry_get_filename (void);
+static void
+geometry_real_store (GKeyFile *key_file)
+{
+ gchar *filename;
+ gchar *content;
+ gsize length;
+ GError *error = NULL;
+
+ content = g_key_file_to_data (key_file, &length, &error);
+ if (error != NULL)
+ {
+ DEBUG ("Error: %s", error->message);
+ g_error_free (error);
+ return;
+ }
+
+ filename = g_build_filename (g_get_user_config_dir (),
+ PACKAGE_NAME, GEOMETRY_FILENAME, NULL);
+
+ if (!g_file_set_contents (filename, content, length, &error))
+ {
+ DEBUG ("Error: %s", error->message);
+ g_error_free (error);
+ }
+
+ g_free (content);
+ g_free (filename);
+}
-static gchar *
-geometry_get_filename (void)
+static gboolean
+geometry_store_cb (gpointer key_file)
{
- gchar *dir;
- gchar *filename;
+ geometry_real_store (key_file);
+ store_id = 0;
- dir = g_build_filename (g_get_home_dir (), ".gnome2", PACKAGE_NAME, NULL);
- if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR)) {
- DEBUG ("Creating directory:'%s'", dir);
- g_mkdir_with_parents (dir, GEOMETRY_DIR_CREATE_MODE);
- }
+ return FALSE;
+}
- filename = g_build_filename (dir, GEOMETRY_KEY_FILENAME, NULL);
- g_free (dir);
+static void
+geometry_schedule_store (GKeyFile *key_file)
+{
+ if (store_id != 0)
+ g_source_remove (store_id);
- return filename;
+ store_id = g_timeout_add_seconds (1, geometry_store_cb, key_file);
+}
+
+static GKeyFile *
+geometry_get_key_file (void)
+{
+ static GKeyFile *key_file = NULL;
+ gchar *dir;
+ gchar *filename;
+
+ if (key_file != NULL)
+ return key_file;
+
+ dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
+ if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
+ {
+ DEBUG ("Creating directory:'%s'", dir);
+ g_mkdir_with_parents (dir, GEOMETRY_DIR_CREATE_MODE);
+ }
+
+ filename = g_build_filename (dir, GEOMETRY_FILENAME, NULL);
+ g_free (dir);
+
+ key_file = g_key_file_new ();
+ g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
+ g_free (filename);
+
+ return key_file;
}
void
-empathy_geometry_save (const gchar *name,
- gint x,
- gint y,
- gint w,
- gint h)
+empathy_geometry_save_values (GtkWindow *window,
+ gint x,
+ gint y,
+ gint w,
+ gint h,
+ gboolean maximized)
{
- GError *error = NULL;
- GKeyFile *key_file;
- gchar *filename;
- GdkScreen *screen;
- gint max_width;
- gint max_height;
- gchar *content;
- gsize length;
- gchar *str;
+ GKeyFile *key_file;
+ gchar *position_str = NULL;
+ GHashTable *names;
+ GHashTableIter iter;
+ const gchar *name;
- DEBUG ("Saving window geometry: x:%d, y:%d, w:%d, h:%d\n",
- x, y, w, h);
+ names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
- screen = gdk_screen_get_default ();
- max_width = gdk_screen_get_width (screen);
- max_height = gdk_screen_get_height (screen);
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (names != NULL);
- w = CLAMP (w, 100, max_width);
- h = CLAMP (h, 100, max_height);
+ /* Don't save off-screen positioning */
+ if (!EMPATHY_RECT_IS_ON_SCREEN (x, y, w, h))
+ return;
- x = CLAMP (x, 0, max_width - w);
- y = CLAMP (y, 0, max_height - h);
+ key_file = geometry_get_key_file ();
- str = g_strdup_printf (GEOMETRY_FORMAT, x, y, w, h);
+ /* Save window size only if not maximized */
+ if (!maximized)
+ {
+ position_str = g_strdup_printf (GEOMETRY_POSITION_FORMAT, x, y, w, h);
+ }
- key_file = g_key_file_new ();
+ g_hash_table_iter_init (&iter, names);
+ while (g_hash_table_iter_next (&iter, (gpointer) &name, NULL))
+ {
+ gchar *escaped_name;
- filename = geometry_get_filename ();
+ /* escape the name so that unwanted characters such as # are removed */
+ escaped_name = g_uri_escape_string (name, NULL, TRUE);
- g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
- g_key_file_set_string (key_file, GEOMETRY_GROUP_NAME, name, str);
+ g_key_file_set_boolean (key_file, GEOMETRY_MAXIMIZED_GROUP,
+ escaped_name, maximized);
- g_free (str);
+ if (position_str != NULL)
+ g_key_file_set_string (key_file, GEOMETRY_POSITION_GROUP,
+ escaped_name, position_str);
- content = g_key_file_to_data (key_file, &length, NULL);
- if (!g_file_set_contents (filename, content, length, &error)) {
- g_warning ("Couldn't save window geometry, error:%d->'%s'",
- error->code, error->message);
- g_error_free (error);
- }
+ g_free (escaped_name);
+ }
- g_free (content);
- g_free (filename);
- g_key_file_free (key_file);
+
+ geometry_schedule_store (key_file);
+ g_free (position_str);
}
-void
-empathy_geometry_load (const gchar *name,
- gint *x,
- gint *y,
- gint *w,
- gint *h)
+static void
+empathy_geometry_save (GtkWindow *window)
{
- GKeyFile *key_file;
- gchar *filename;
- gchar *str = NULL;
+ GdkWindow *gdk_window;
+ GdkWindowState window_state;
+ gboolean maximized;
+ gint x, y, w, h;
- if (x) {
- *x = -1;
- }
+ g_return_if_fail (GTK_IS_WINDOW (window));
- if (y) {
- *y = -1;
- }
+ if (!gtk_widget_get_visible (GTK_WIDGET (window)))
+ return;
- if (w) {
- *w = -1;
- }
+ /* Get window geometry */
+ gtk_window_get_position (window, &x, &y);
+ gtk_window_get_size (window, &w, &h);
- if (h) {
- *h = -1;
- }
+ gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
+ window_state = gdk_window_get_state (gdk_window);
+ maximized = (window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0;
- key_file = g_key_file_new ();
+ empathy_geometry_save_values (window, x, y, w, h, maximized);
+}
- filename = geometry_get_filename ();
+static void
+empathy_geometry_load (GtkWindow *window,
+ const gchar *name)
+{
+ GKeyFile *key_file;
+ gchar *escaped_name;
+ gchar *str;
+ gboolean maximized;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (!EMP_STR_EMPTY (name));
+
+ /* escape the name so that unwanted characters such as # are removed */
+ escaped_name = g_uri_escape_string (name, NULL, TRUE);
+
+ key_file = geometry_get_key_file ();
+
+ /* restore window size and position */
+ str = g_key_file_get_string (key_file, GEOMETRY_POSITION_GROUP,
+ escaped_name, NULL);
+ if (str)
+ {
+ gint x, y, w, h;
+
+ sscanf (str, GEOMETRY_POSITION_FORMAT, &x, &y, &w, &h);
+ gtk_window_move (window, x, y);
+ gtk_window_resize (window, w, h);
+ }
+
+ /* restore window maximized state */
+ maximized = g_key_file_get_boolean (key_file, GEOMETRY_MAXIMIZED_GROUP,
+ escaped_name, NULL);
+
+ if (maximized)
+ gtk_window_maximize (window);
+ else
+ gtk_window_unmaximize (window);
+
+ g_free (str);
+ g_free (escaped_name);
+}
- if (g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL)) {
- str = g_key_file_get_string (key_file, GEOMETRY_GROUP_NAME, name, NULL);
- }
+static gboolean
+geometry_configure_event_cb (GtkWindow *window,
+ GdkEventConfigure *event,
+ gpointer user_data)
+{
+ empathy_geometry_save (window);
- if (str) {
- gint tmp_x, tmp_y, tmp_w, tmp_h;
+ return FALSE;
+}
- sscanf (str, GEOMETRY_FORMAT, &tmp_x, &tmp_y, &tmp_w, &tmp_h);
+static gboolean
+geometry_window_state_event_cb (GtkWindow *window,
+ GdkEventWindowState *event,
+ gpointer user_data)
+{
+ if ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0)
+ {
+ empathy_geometry_save (window);
+ }
- if (x) {
- *x = tmp_x;
- }
+ return FALSE;
+}
- if (y) {
- *y = tmp_y;
- }
+static void
+geometry_map_cb (GtkWindow *window,
+ gpointer user_data)
+{
+ GHashTable *names;
+ GHashTableIter iter;
+ const gchar *name;
- if (w) {
- *w = tmp_w;
- }
+ /* The WM will replace this window, restore its last position */
+ names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
- if (h) {
- *h = tmp_h;
- }
+ g_assert (names != NULL);
- g_free (str);
- }
+ /* Use the first name we get in the hash table */
+ g_hash_table_iter_init (&iter, names);
+ g_assert (g_hash_table_iter_next (&iter, (gpointer) &name, NULL));
- DEBUG ("Loading window geometry: x:%d, y:%d, w:%d, h:%d\n",
- x ? *x : -1, y ? *y : -1, w ? *w : -1, h ? *h : -1);
+ empathy_geometry_load (window, name);
+}
- g_free (filename);
- g_key_file_free (key_file);
+void
+empathy_geometry_bind (GtkWindow *window,
+ const gchar *name)
+{
+ GHashTable *names;
+ gboolean connect_sigs = FALSE;
+
+ g_return_if_fail (GTK_IS_WINDOW (window));
+ g_return_if_fail (!EMP_STR_EMPTY (name));
+
+ /* Check if this window is already bound */
+ names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
+ if (names == NULL)
+ {
+ connect_sigs = TRUE;
+ names = g_hash_table_new_full (g_str_hash, g_str_equal,
+ g_free, NULL);
+
+ g_object_set_data_full (G_OBJECT (window), GEOMETRY_NAME_KEY, names,
+ (GDestroyNotify) g_hash_table_unref);
+ }
+ else if (g_hash_table_lookup (names, name) != NULL)
+ {
+ return;
+ }
+
+ /* Store the geometry name in the window's data */
+ g_hash_table_insert (names, g_strdup (name), GUINT_TO_POINTER (TRUE));
+
+ /* Load initial geometry */
+ empathy_geometry_load (window, name);
+
+ if (!connect_sigs)
+ return;
+
+ /* Track geometry changes */
+ g_signal_connect (window, "configure-event",
+ G_CALLBACK (geometry_configure_event_cb), NULL);
+ g_signal_connect (window, "window-state-event",
+ G_CALLBACK (geometry_window_state_event_cb), NULL);
+ g_signal_connect (window, "map",
+ G_CALLBACK (geometry_map_cb), NULL);
}
+void
+empathy_geometry_unbind (GtkWindow *window,
+ const gchar *name)
+{
+ GHashTable *names;
+
+ names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
+ if (names == NULL)
+ return;
+
+ g_hash_table_remove (names, name);
+
+ if (g_hash_table_size (names) > 0)
+ return;
+
+ g_signal_handlers_disconnect_by_func (window,
+ geometry_configure_event_cb, NULL);
+ g_signal_handlers_disconnect_by_func (window,
+ geometry_window_state_event_cb, NULL);
+ g_signal_handlers_disconnect_by_func (window,
+ geometry_map_cb, NULL);
+
+ g_object_set_data (G_OBJECT (window), GEOMETRY_NAME_KEY, NULL);
+}