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