]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-geometry.c
Merge branch 'gnome-3-8'
[empathy.git] / libempathy-gtk / empathy-geometry.c
1 /*
2  * Copyright (C) 2006-2007 Imendio AB
3  * Copyright (C) 2007-2008 Collabora Ltd.
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the
17  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
18  * Boston, MA  02110-1301  USA
19  *
20  * Authors: Martyn Russell <martyn@imendio.com>
21  *          Xavier Claessens <xclaesse@gmail.com>
22  */
23
24 #include "config.h"
25 #include "empathy-geometry.h"
26
27 #include <sys/stat.h>
28 #include <tp-account-widgets/tpaw-utils.h>
29
30 #include "empathy-ui-utils.h"
31 #include "empathy-utils.h"
32
33 #define DEBUG_FLAG EMPATHY_DEBUG_OTHER
34 #include "empathy-debug.h"
35
36 #define GEOMETRY_DIR_CREATE_MODE  (S_IRUSR | S_IWUSR | S_IXUSR)
37 #define GEOMETRY_FILE_CREATE_MODE (S_IRUSR | S_IWUSR)
38
39 /* geometry.ini file contains 2 groups:
40  *  - one with position and size of each window
41  *  - one with the maximized state of each window
42  * Windows are identified by a name. (e.g. "main-window") */
43 #define GEOMETRY_FILENAME             "geometry.ini"
44 #define GEOMETRY_POSITION_FORMAT      "%d,%d,%d,%d" /* "x,y,w,h" */
45 #define GEOMETRY_POSITION_GROUP       "geometry"
46 #define GEOMETRY_MAXIMIZED_GROUP      "maximized"
47
48 /* Key used to keep window's name inside the object's qdata */
49 #define GEOMETRY_NAME_KEY             "geometry-name-key"
50
51 static guint store_id = 0;
52
53 static void
54 geometry_real_store (GKeyFile *key_file)
55 {
56   gchar *filename;
57   gchar *content;
58   gsize length;
59   GError *error = NULL;
60
61   content = g_key_file_to_data (key_file, &length, &error);
62   if (error != NULL)
63     {
64       DEBUG ("Error: %s", error->message);
65       g_error_free (error);
66       return;
67     }
68
69   filename = g_build_filename (g_get_user_config_dir (),
70     PACKAGE_NAME, GEOMETRY_FILENAME, NULL);
71
72   if (!g_file_set_contents (filename, content, length, &error))
73     {
74       DEBUG ("Error: %s", error->message);
75       g_error_free (error);
76     }
77
78   g_free (content);
79   g_free (filename);
80 }
81
82 static gboolean
83 geometry_store_cb (gpointer key_file)
84 {
85   geometry_real_store (key_file);
86   store_id = 0;
87
88   return FALSE;
89 }
90
91 static void
92 geometry_schedule_store (GKeyFile *key_file)
93 {
94   if (store_id != 0)
95     g_source_remove (store_id);
96
97   store_id = g_timeout_add_seconds (1, geometry_store_cb, key_file);
98 }
99
100 static GKeyFile *
101 geometry_get_key_file (void)
102 {
103   static GKeyFile *key_file = NULL;
104   gchar *dir;
105   gchar *filename;
106
107   if (key_file != NULL)
108     return key_file;
109
110   dir = g_build_filename (g_get_user_config_dir (), PACKAGE_NAME, NULL);
111   if (!g_file_test (dir, G_FILE_TEST_EXISTS | G_FILE_TEST_IS_DIR))
112     {
113       DEBUG ("Creating directory:'%s'", dir);
114       g_mkdir_with_parents (dir, GEOMETRY_DIR_CREATE_MODE);
115     }
116
117   filename = g_build_filename (dir, GEOMETRY_FILENAME, NULL);
118   g_free (dir);
119
120   key_file = g_key_file_new ();
121   g_key_file_load_from_file (key_file, filename, G_KEY_FILE_NONE, NULL);
122   g_free (filename);
123
124   return key_file;
125 }
126
127 void
128 empathy_geometry_save_values (GtkWindow *window,
129     gint x,
130     gint y,
131     gint w,
132     gint h,
133     gboolean maximized)
134 {
135   GKeyFile *key_file;
136   gchar *position_str = NULL;
137   GHashTable *names;
138   GHashTableIter iter;
139   const gchar *name;
140
141   names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
142
143   g_return_if_fail (GTK_IS_WINDOW (window));
144   g_return_if_fail (names != NULL);
145
146   /* Don't save off-screen positioning */
147   if (!EMPATHY_RECT_IS_ON_SCREEN (x, y, w, h))
148     return;
149
150   key_file = geometry_get_key_file ();
151
152   /* Save window size only if not maximized */
153   if (!maximized)
154     {
155       position_str = g_strdup_printf (GEOMETRY_POSITION_FORMAT, x, y, w, h);
156     }
157
158   g_hash_table_iter_init (&iter, names);
159   while (g_hash_table_iter_next (&iter, (gpointer) &name, NULL))
160     {
161       gchar *escaped_name;
162
163       /* escape the name so that unwanted characters such as # are removed */
164       escaped_name = g_uri_escape_string (name, NULL, TRUE);
165
166       g_key_file_set_boolean (key_file, GEOMETRY_MAXIMIZED_GROUP,
167           escaped_name, maximized);
168
169       if (position_str != NULL)
170         g_key_file_set_string (key_file, GEOMETRY_POSITION_GROUP,
171             escaped_name, position_str);
172
173       g_free (escaped_name);
174     }
175
176
177   geometry_schedule_store (key_file);
178   g_free (position_str);
179 }
180
181 static void
182 empathy_geometry_save (GtkWindow *window)
183 {
184   GdkWindow *gdk_window;
185   GdkWindowState window_state;
186   gboolean maximized;
187   gint x, y, w, h;
188
189   g_return_if_fail (GTK_IS_WINDOW (window));
190
191   if (!gtk_widget_get_visible (GTK_WIDGET (window)))
192     return;
193
194   /* Get window geometry */
195   gtk_window_get_position (window, &x, &y);
196   gtk_window_get_size (window, &w, &h);
197
198   gdk_window = gtk_widget_get_window (GTK_WIDGET (window));
199   window_state = gdk_window_get_state (gdk_window);
200   maximized = (window_state & GDK_WINDOW_STATE_MAXIMIZED) != 0;
201
202   empathy_geometry_save_values (window, x, y, w, h, maximized);
203 }
204
205 static void
206 empathy_geometry_load (GtkWindow *window,
207     const gchar *name)
208 {
209   GKeyFile *key_file;
210   gchar    *escaped_name;
211   gchar    *str;
212   gboolean  maximized;
213
214   g_return_if_fail (GTK_IS_WINDOW (window));
215   g_return_if_fail (!TPAW_STR_EMPTY (name));
216
217   /* escape the name so that unwanted characters such as # are removed */
218   escaped_name = g_uri_escape_string (name, NULL, TRUE);
219
220   key_file = geometry_get_key_file ();
221
222   /* restore window size and position */
223   str = g_key_file_get_string (key_file, GEOMETRY_POSITION_GROUP,
224       escaped_name, NULL);
225   if (str)
226     {
227       gint x, y, w, h;
228
229       sscanf (str, GEOMETRY_POSITION_FORMAT, &x, &y, &w, &h);
230       gtk_window_move (window, x, y);
231       gtk_window_resize (window, w, h);
232     }
233
234   /* restore window maximized state */
235   maximized = g_key_file_get_boolean (key_file, GEOMETRY_MAXIMIZED_GROUP,
236       escaped_name, NULL);
237
238   if (maximized)
239     gtk_window_maximize (window);
240   else
241     gtk_window_unmaximize (window);
242
243   g_free (str);
244   g_free (escaped_name);
245 }
246
247 static gboolean
248 geometry_configure_event_cb (GtkWindow *window,
249     GdkEventConfigure *event,
250     gpointer user_data)
251 {
252   empathy_geometry_save (window);
253
254   return FALSE;
255 }
256
257 static gboolean
258 geometry_window_state_event_cb (GtkWindow *window,
259     GdkEventWindowState *event,
260     gpointer user_data)
261 {
262   if ((event->changed_mask & GDK_WINDOW_STATE_MAXIMIZED) != 0)
263     {
264       empathy_geometry_save (window);
265     }
266
267   return FALSE;
268 }
269
270 static void
271 geometry_map_cb (GtkWindow *window,
272     gpointer user_data)
273 {
274   GHashTable *names;
275   GHashTableIter iter;
276   const gchar *name;
277
278   /* The WM will replace this window, restore its last position */
279   names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
280
281   g_assert (names != NULL);
282
283   /* Use the first name we get in the hash table */
284   g_hash_table_iter_init (&iter, names);
285   g_assert (g_hash_table_iter_next (&iter, (gpointer) &name, NULL));
286
287   empathy_geometry_load (window, name);
288 }
289
290 void
291 empathy_geometry_bind (GtkWindow *window,
292     const gchar *name)
293 {
294   GHashTable *names;
295   gboolean connect_sigs = FALSE;
296
297   g_return_if_fail (GTK_IS_WINDOW (window));
298   g_return_if_fail (!TPAW_STR_EMPTY (name));
299
300   /* Check if this window is already bound */
301   names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
302   if (names == NULL)
303     {
304       connect_sigs = TRUE;
305       names = g_hash_table_new_full (g_str_hash, g_str_equal,
306           g_free, NULL);
307
308       g_object_set_data_full (G_OBJECT (window), GEOMETRY_NAME_KEY, names,
309           (GDestroyNotify) g_hash_table_unref);
310     }
311   else if (g_hash_table_lookup (names, name) != NULL)
312     {
313       return;
314     }
315
316   /* Store the geometry name in the window's data */
317   g_hash_table_insert (names, g_strdup (name), GUINT_TO_POINTER (TRUE));
318
319   /* Load initial geometry */
320   empathy_geometry_load (window, name);
321
322   if (!connect_sigs)
323     return;
324
325   /* Track geometry changes */
326   g_signal_connect (window, "configure-event",
327     G_CALLBACK (geometry_configure_event_cb), NULL);
328   g_signal_connect (window, "window-state-event",
329     G_CALLBACK (geometry_window_state_event_cb), NULL);
330   g_signal_connect (window, "map",
331     G_CALLBACK (geometry_map_cb), NULL);
332 }
333
334 void
335 empathy_geometry_unbind (GtkWindow *window,
336     const gchar *name)
337 {
338   GHashTable *names;
339
340   names = g_object_get_data (G_OBJECT (window), GEOMETRY_NAME_KEY);
341   if (names == NULL)
342     return;
343
344   g_hash_table_remove (names, name);
345
346   if (g_hash_table_size (names) > 0)
347     return;
348
349   g_signal_handlers_disconnect_by_func (window,
350     geometry_configure_event_cb, NULL);
351   g_signal_handlers_disconnect_by_func (window,
352     geometry_window_state_event_cb, NULL);
353   g_signal_handlers_disconnect_by_func (window,
354     geometry_map_cb, NULL);
355
356   g_object_set_data (G_OBJECT (window), GEOMETRY_NAME_KEY, NULL);
357 }