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:
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"),
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);
48 * if (stat (filename, &buf) == -1)
49 * g_object_set (change_notification,
51 * "has-known-mtime", FALSE,
54 * g_object_set (change_notification,
56 * "has-known-mtime", TRUE,
57 * "known-mtime", &buf.st_mtim,
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.
68 #include <sys/types.h>
76 #include "../include/gschem_change_notification.h"
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 * */
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
);
132 timespec_free (GschemTimespec
*timespec
)
134 g_return_if_fail (timespec
!= NULL
);
135 g_slice_free (GschemTimespec
, timespec
);
139 gschem_timespec_get_type (void)
141 static GType type
= 0;
144 type
= g_boxed_type_register_static (
145 g_intern_static_string ("GschemTimespec"),
146 (GBoxedCopyFunc
) timespec_copy
,
147 (GBoxedFreeFunc
) timespec_free
);
153 /******************************************************************************/
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
);
165 update_current_mtime (GschemChangeNotification
*chnot
)
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
);
173 chnot
->has_current_mtime
= TRUE
;
174 chnot
->current_mtime
= buf
.st_mtim
;
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
)));
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 /******************************************************************************/
204 gschem_change_notification_get_type ()
206 static GType 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
),
218 (GInstanceInitFunc
) instance_init
,
219 NULL
/* value_table */
222 type
= g_type_register_static (G_TYPE_OBJECT
,
223 "GschemChangeNotification",
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",
255 properties
[PROP_PATH
] =
256 g_param_spec_string ("path",
260 G_PARAM_READWRITE
| G_PARAM_EXPLICIT_NOTIFY
);
261 properties
[PROP_HAS_KNOWN_MTIME
] =
262 g_param_spec_boolean ("has-known-mtime",
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
,
281 properties
[PROP_MARKUP
] =
282 g_param_spec_string ("markup",
287 properties
[PROP_BUTTON_STOCK_ID
] =
288 g_param_spec_string ("button-stock-id",
293 properties
[PROP_BUTTON_LABEL
] =
294 g_param_spec_string ("button-label",
300 g_object_class_install_properties (G_OBJECT_CLASS (class),
301 N_PROPERTIES
, properties
);
304 g_signal_new ("response",
305 G_OBJECT_CLASS_TYPE (class),
308 g_cclosure_marshal_VOID__INT
,
315 instance_init (GschemChangeNotification
*chnot
)
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
),
351 GTK_RESPONSE_ACCEPT
);
353 gtk_info_bar_add_button (GTK_INFO_BAR (chnot
->info_bar
),
354 GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
);
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
);
371 set_property (GObject
*object
,
376 GschemChangeNotification
*chnot
= GSCHEM_CHANGE_NOTIFICATION (object
);
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
));
385 case PROP_GSCHEM_PAGE
:
386 chnot
->page
= (PAGE
*) g_value_get_pointer (value
);
390 path
= g_value_get_string (value
);
391 if (path
!= NULL
&& *path
== '\0')
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
);
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
);
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
);
416 case PROP_KNOWN_MTIME
:
417 chnot
->known_mtime
= *((struct timespec
*) g_value_get_boxed (value
));
418 g_object_notify_by_pspec (object
, pspec
);
421 case PROP_MESSAGE_TYPE
:
422 gtk_info_bar_set_message_type (GTK_INFO_BAR (chnot
->info_bar
),
423 g_value_get_enum (value
));
427 gtk_label_set_markup (GTK_LABEL (chnot
->label
),
428 g_value_get_string (value
));
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
));
438 case PROP_BUTTON_LABEL
:
439 gtk_button_set_label (GTK_BUTTON (chnot
->button
),
440 g_value_get_string (value
));
444 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
450 get_property (GObject
*object
,
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
);
462 case PROP_GSCHEM_PAGE
:
463 g_value_set_pointer (value
, chnot
->page
);
467 g_value_set_string (value
, chnot
->path
);
470 case PROP_HAS_KNOWN_MTIME
:
471 g_value_set_boolean (value
, chnot
->has_known_mtime
);
474 case PROP_KNOWN_MTIME
:
475 g_value_set_boxed (value
, &chnot
->known_mtime
);
479 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, property_id
, pspec
);
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
);