Remove extra debug message
[geda-gaf.git] / gschem / src / gschem_change_notification.c
blobd9c18a349bf6c9702510b5cc5d57e19ee9e42232
1 /* gEDA - GPL Electronic Design Automation
2 * gschem - gEDA Schematic Capture
3 * Copyright (C) 1998-2010 Ales Hvezda
4 * Copyright (C) 1998-2019 gEDA Contributors (see ChangeLog for details)
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
21 /*! \file gschem_change_notification.c
22 * \brief Manage a "file changed on disk" notification bar.
24 * The class \ref GschemChangeNotification contains the common logic
25 * for the info bars which are shown at the top of the main window if
26 * the current file or its back-annotation patch have been changed on
27 * disk. It creates the widget hierarchy for an info bar, shows/hides
28 * the bar on file system updates, and emits the "response" signal
29 * when the user either accepts or rejects the suggested action.
31 * Typically, it would be used as follows:
33 * \code
34 * GschemChangeNotification *change_notification =
35 * g_object_new (GSCHEM_TYPE_CHANGE_NOTIFICATION,
36 * "message-type", GTK_MESSAGE_QUESTION,
37 * "markup", _("The file has changed on disk.\n\n"
38 * "Do you want to perform some action?"),
39 * "button-label", _("_Perform action"),
40 * NULL);
41 * g_signal_connect (change_notification, "response",
42 * G_CALLBACK (handle_response), NULL);
43 * gtk_box_pack_start (..., change_notification->info_bar, FALSE, FALSE, 0);
45 * // ...
47 * struct stat buf;
48 * if (stat (filename, &buf) == -1)
49 * g_object_set (change_notification,
50 * "path", filename,
51 * "has-known-mtime", FALSE,
52 * NULL);
53 * else
54 * g_object_set (change_notification,
55 * "path", filename,
56 * "has-known-mtime", TRUE,
57 * "known-mtime", &buf.st_mtim,
58 * NULL);
59 * \endcode
61 * It is preferable to set the path and mtime information using
62 * \c g_object_set as this function queues the property notifications
63 * and only emits them after all properties have been set.
66 #include <config.h>
68 #include <sys/types.h>
69 #include <sys/stat.h>
71 #ifdef HAVE_LIBFAM
72 #include <fam.h>
73 #endif
75 #include "gschem.h"
76 #include "../include/gschem_change_notification.h"
79 enum {
80 PROP_GSCHEM_TOPLEVEL = 1, /* GschemToplevel * */
81 PROP_GSCHEM_PAGE, /* PAGE * */
82 PROP_PATH, /* gchar * */
83 PROP_HAS_KNOWN_MTIME, /* gboolean */
84 PROP_KNOWN_MTIME, /* struct timespec */
85 PROP_MESSAGE_TYPE, /* GtkMessageType */
86 PROP_MARKUP, /* gchar * */
87 PROP_BUTTON_STOCK_ID, /* gchar * */
88 PROP_BUTTON_LABEL, /* gchar * */
89 N_PROPERTIES
92 enum {
93 RESPONSE,
94 LAST_SIGNAL
97 static gpointer parent_class = NULL;
98 static GParamSpec *properties[N_PROPERTIES] = { NULL, };
99 static guint signals[LAST_SIGNAL] = { 0, };
101 static void class_init (GschemChangeNotificationClass *class);
102 static void instance_init (GschemChangeNotification *chnot);
104 static void constructed (GObject *object);
105 static void dispose (GObject *object);
106 static void set_property (GObject *object, guint property_id,
107 const GValue *value, GParamSpec *pspec);
108 static void get_property (GObject *object, guint property_id,
109 GValue *value, GParamSpec *pspec);
110 static void notify (GObject *object, GParamSpec *pspec);
114 static inline gboolean
115 timespec_ge (struct timespec *a, struct timespec *b)
117 return a->tv_sec >= b->tv_sec ||
118 (a->tv_sec == b->tv_sec && a->tv_nsec >= b->tv_nsec);
121 static GschemTimespec *
122 timespec_copy (const GschemTimespec *timespec)
124 g_return_val_if_fail (timespec != NULL, NULL);
126 GschemTimespec *copy = g_slice_new (GschemTimespec);
127 *copy = *timespec;
128 return copy;
131 static void
132 timespec_free (GschemTimespec *timespec)
134 g_return_if_fail (timespec != NULL);
135 g_slice_free (GschemTimespec, timespec);
138 GType
139 gschem_timespec_get_type (void)
141 static GType type = 0;
143 if (type == 0)
144 type = g_boxed_type_register_static (
145 g_intern_static_string ("GschemTimespec"),
146 (GBoxedCopyFunc) timespec_copy,
147 (GBoxedFreeFunc) timespec_free);
149 return type;
153 /******************************************************************************/
156 static void
157 response_callback (GtkWidget *info_bar, gint response_id, gpointer user_data)
159 g_signal_emit (GSCHEM_CHANGE_NOTIFICATION (user_data),
160 signals[RESPONSE], 0, response_id);
164 static void
165 update_current_mtime (GschemChangeNotification *chnot)
167 struct stat buf;
169 if (chnot->path == NULL || stat (chnot->path, &buf) == -1) {
170 chnot->has_current_mtime = FALSE;
171 memset (&chnot->current_mtime, 0, sizeof chnot->current_mtime);
172 } else {
173 chnot->has_current_mtime = TRUE;
174 chnot->current_mtime = buf.st_mtim;
179 static void
180 update_visibility (GschemChangeNotification *chnot)
182 gtk_widget_set_visible (chnot->info_bar,
183 chnot->has_current_mtime && (
184 !chnot->has_known_mtime ||
185 !timespec_ge (&chnot->known_mtime,
186 &chnot->current_mtime)));
190 static void
191 fam_event (const gchar *path, unsigned int code, gpointer user_data)
193 GschemChangeNotification *chnot = GSCHEM_CHANGE_NOTIFICATION (user_data);
195 update_current_mtime (chnot);
196 update_visibility (chnot);
200 /******************************************************************************/
203 GType
204 gschem_change_notification_get_type ()
206 static GType type = 0;
208 if (type == 0) {
209 static const GTypeInfo info = {
210 sizeof (GschemChangeNotificationClass),
211 NULL, /* base_init */
212 NULL, /* base_finalize */
213 (GClassInitFunc) class_init,
214 NULL, /* class_finalize */
215 NULL, /* class_data */
216 sizeof (GschemChangeNotification),
217 0, /* n_preallocs */
218 (GInstanceInitFunc) instance_init,
219 NULL /* value_table */
222 type = g_type_register_static (G_TYPE_OBJECT,
223 "GschemChangeNotification",
224 &info, 0);
227 return type;
231 static void
232 class_init (GschemChangeNotificationClass *class)
234 G_OBJECT_CLASS (class)->constructed = constructed;
235 G_OBJECT_CLASS (class)->dispose = dispose;
237 G_OBJECT_CLASS (class)->set_property = set_property;
238 G_OBJECT_CLASS (class)->get_property = get_property;
239 G_OBJECT_CLASS (class)->notify = notify;
241 parent_class = g_type_class_peek_parent (class);
244 properties[PROP_GSCHEM_TOPLEVEL] =
245 g_param_spec_pointer ("gschem-toplevel",
248 G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
249 properties[PROP_GSCHEM_PAGE] =
250 g_param_spec_pointer ("gschem-page",
253 G_PARAM_READWRITE);
255 properties[PROP_PATH] =
256 g_param_spec_string ("path",
259 NULL,
260 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
261 properties[PROP_HAS_KNOWN_MTIME] =
262 g_param_spec_boolean ("has-known-mtime",
265 FALSE,
266 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
267 properties[PROP_KNOWN_MTIME] =
268 g_param_spec_boxed ("known-mtime",
271 GSCHEM_TYPE_TIMESPEC,
272 G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY);
274 properties[PROP_MESSAGE_TYPE] =
275 g_param_spec_enum ("message-type",
278 GTK_TYPE_MESSAGE_TYPE,
279 GTK_MESSAGE_INFO,
280 G_PARAM_WRITABLE);
281 properties[PROP_MARKUP] =
282 g_param_spec_string ("markup",
285 NULL,
286 G_PARAM_WRITABLE);
287 properties[PROP_BUTTON_STOCK_ID] =
288 g_param_spec_string ("button-stock-id",
291 NULL,
292 G_PARAM_WRITABLE);
293 properties[PROP_BUTTON_LABEL] =
294 g_param_spec_string ("button-label",
297 NULL,
298 G_PARAM_WRITABLE);
300 g_object_class_install_properties (G_OBJECT_CLASS (class),
301 N_PROPERTIES, properties);
303 signals[RESPONSE] =
304 g_signal_new ("response",
305 G_OBJECT_CLASS_TYPE (class),
306 G_SIGNAL_RUN_LAST,
307 0, NULL, NULL,
308 g_cclosure_marshal_VOID__INT,
309 G_TYPE_NONE, 1,
310 G_TYPE_INT);
314 static void
315 instance_init (GschemChangeNotification *chnot)
320 static void
321 constructed (GObject *object)
323 GschemChangeNotification *chnot = GSCHEM_CHANGE_NOTIFICATION (object);
325 G_OBJECT_CLASS (parent_class)->constructed (object);
327 chnot->info_bar = gtk_info_bar_new ();
328 gtk_widget_set_no_show_all (chnot->info_bar, TRUE);
329 g_signal_connect (chnot->info_bar, "response",
330 G_CALLBACK (response_callback), chnot);
332 GtkWidget *image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_QUESTION,
333 GTK_ICON_SIZE_DIALOG);
334 gtk_widget_show (image);
336 chnot->label = gtk_label_new ("");
337 gtk_misc_set_alignment (GTK_MISC (chnot->label), 0., .5);
338 gtk_widget_show (chnot->label);
340 GtkWidget *content_area =
341 gtk_info_bar_get_content_area (GTK_INFO_BAR (chnot->info_bar));
342 gtk_box_pack_start (GTK_BOX (content_area), image, FALSE, FALSE, 0);
343 gtk_box_pack_start (GTK_BOX (content_area), chnot->label, TRUE, TRUE, 0);
345 chnot->button = gtk_button_new ();
346 gtk_button_set_use_underline (GTK_BUTTON (chnot->button), TRUE);
347 gtk_widget_set_can_default (chnot->button, TRUE);
348 gtk_widget_show (chnot->button);
349 gtk_info_bar_add_action_widget (GTK_INFO_BAR (chnot->info_bar),
350 chnot->button,
351 GTK_RESPONSE_ACCEPT);
353 gtk_info_bar_add_button (GTK_INFO_BAR (chnot->info_bar),
354 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
358 static void
359 dispose (GObject *object)
361 GschemChangeNotification *chnot = GSCHEM_CHANGE_NOTIFICATION (object);
363 g_clear_pointer (&chnot->path, g_free);
364 g_clear_pointer (&chnot->fam_handle, x_fam_unmonitor);
366 G_OBJECT_CLASS (parent_class)->dispose (object);
370 static void
371 set_property (GObject *object,
372 guint property_id,
373 const GValue *value,
374 GParamSpec *pspec)
376 GschemChangeNotification *chnot = GSCHEM_CHANGE_NOTIFICATION (object);
377 const gchar *path;
378 gboolean has_known_mtime;
380 switch (property_id) {
381 case PROP_GSCHEM_TOPLEVEL:
382 chnot->w_current = GSCHEM_TOPLEVEL (g_value_get_pointer (value));
383 break;
385 case PROP_GSCHEM_PAGE:
386 chnot->page = (PAGE *) g_value_get_pointer (value);
387 break;
389 case PROP_PATH:
390 path = g_value_get_string (value);
391 if (path != NULL && *path == '\0')
392 path = NULL;
394 if (g_strcmp0 (path, chnot->path) != 0) {
395 g_clear_pointer (&chnot->path, g_free);
396 g_clear_pointer (&chnot->fam_handle, x_fam_unmonitor);
398 if (path != NULL) {
399 chnot->path = g_strdup (path);
400 chnot->fam_handle = x_fam_monitor (path, NULL, fam_event, chnot);
403 update_current_mtime (chnot);
404 g_object_notify_by_pspec (object, pspec);
406 break;
408 case PROP_HAS_KNOWN_MTIME:
409 has_known_mtime = g_value_get_boolean (value);
410 if (has_known_mtime != chnot->has_known_mtime) {
411 chnot->has_known_mtime = has_known_mtime;
412 g_object_notify_by_pspec (object, pspec);
414 break;
416 case PROP_KNOWN_MTIME:
417 chnot->known_mtime = *((struct timespec *) g_value_get_boxed (value));
418 g_object_notify_by_pspec (object, pspec);
419 break;
421 case PROP_MESSAGE_TYPE:
422 gtk_info_bar_set_message_type (GTK_INFO_BAR (chnot->info_bar),
423 g_value_get_enum (value));
424 break;
426 case PROP_MARKUP:
427 gtk_label_set_markup (GTK_LABEL (chnot->label),
428 g_value_get_string (value));
429 break;
431 case PROP_BUTTON_STOCK_ID:
432 gtk_button_set_image (GTK_BUTTON (chnot->button),
433 gtk_image_new_from_stock (
434 g_value_get_string (value),
435 GTK_ICON_SIZE_BUTTON));
436 break;
438 case PROP_BUTTON_LABEL:
439 gtk_button_set_label (GTK_BUTTON (chnot->button),
440 g_value_get_string (value));
441 break;
443 default:
444 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
449 static void
450 get_property (GObject *object,
451 guint property_id,
452 GValue *value,
453 GParamSpec *pspec)
455 GschemChangeNotification *chnot = GSCHEM_CHANGE_NOTIFICATION (object);
457 switch (property_id) {
458 case PROP_GSCHEM_TOPLEVEL:
459 g_value_set_pointer (value, chnot->w_current);
460 break;
462 case PROP_GSCHEM_PAGE:
463 g_value_set_pointer (value, chnot->page);
464 break;
466 case PROP_PATH:
467 g_value_set_string (value, chnot->path);
468 break;
470 case PROP_HAS_KNOWN_MTIME:
471 g_value_set_boolean (value, chnot->has_known_mtime);
472 break;
474 case PROP_KNOWN_MTIME:
475 g_value_set_boxed (value, &chnot->known_mtime);
476 break;
478 default:
479 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
484 static void
485 notify (GObject *object, GParamSpec *pspec)
487 GschemChangeNotification *chnot = GSCHEM_CHANGE_NOTIFICATION (object);
489 if ((pspec == properties[PROP_PATH] ||
490 pspec == properties[PROP_HAS_KNOWN_MTIME] ||
491 pspec == properties[PROP_KNOWN_MTIME]))
492 update_visibility (chnot);
494 if (G_OBJECT_CLASS (parent_class)->notify != NULL)
495 G_OBJECT_CLASS (parent_class)->notify (object, pspec);