1 /*
2  * Nautilus-Actions
3  * A Nautilus extension which offers configurable context menu actions.
4  *
5  * Copyright (C) 2005 The GNOME Foundation
6  * Copyright (C) 2006-2008 Frederic Ruaudel and others (see AUTHORS)
7  * Copyright (C) 2009-2014 Pierre Wieser and others (see AUTHORS)
8  *
9  * Nautilus-Actions is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of
12  * the License, or (at your option) any later version.
13  *
14  * Nautilus-Actions is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Nautilus-Actions; see the file COPYING. If not, see
21  * <http://www.gnu.org/licenses/>.
22  *
23  * Authors:
24  *   Frederic Ruaudel <grumz@grumz.net>
25  *   Rodrigo Moya <rodrigo@gnome-db.org>
26  *   Pierre Wieser <pwieser@trychlos.org>
27  *   ... and many others (see AUTHORS)
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #include <api/na-iduplicable.h>
35 
36 #include "na-marshal.h"
37 
38 /* private interface data
39  */
40 struct _NAIDuplicableInterfacePrivate {
41 	GList *consumers;
42 };
43 
44 /* the data sructure set on each NAIDuplicable object
45  */
46 typedef struct {
47 	NAIDuplicable *origin;
48 	gboolean       modified;
49 	gboolean       valid;
50 }
51 	DuplicableStr;
52 
53 #define NA_IDUPLICABLE_DATA_DUPLICABLE			"na-iduplicable-data-duplicable"
54 
55 /* signals emitted on NAIDuplicable when a status changes
56  */
57 enum {
58 	MODIFIED_CHANGED,
59 	VALID_CHANGED,
60 	LAST_SIGNAL
61 };
62 
63 static NAIDuplicableInterface *st_interface = NULL;
64 static guint                   st_initializations = 0;
65 static gint                    st_signals[ LAST_SIGNAL ] = { 0 };
66 
67 static GType          register_type( void );
68 static void           interface_base_init( NAIDuplicableInterface *klass );
69 static void           interface_base_finalize( NAIDuplicableInterface *klass );
70 
71 static void           v_copy( NAIDuplicable *target, const NAIDuplicable *source, guint mode );
72 static gboolean       v_are_equal( const NAIDuplicable *a, const NAIDuplicable *b );
73 static gboolean       v_is_valid( const NAIDuplicable *object );
74 
75 static DuplicableStr *get_duplicable_str( const NAIDuplicable *object );
76 
77 static void           on_modified_changed_class_handler( NAIDuplicable *instance, GObject *object, gboolean is_modified );
78 static void           on_valid_changed_class_handler( NAIDuplicable *instance, GObject *object, gboolean is_valid );
79 static void           propagate_signal_to_consumers( NAIDuplicable *instance, const gchar *signal, GObject *object, gboolean new_status );
80 static void           release_signal_consumers( GList *consumers );
81 
82 GType
na_iduplicable_get_type(void)83 na_iduplicable_get_type( void )
84 {
85 	static GType iface_type = 0;
86 
87 	if( !iface_type ){
88 		iface_type = register_type();
89 	}
90 
91 	return( iface_type );
92 }
93 
94 static GType
register_type(void)95 register_type( void )
96 {
97 	static const gchar *thisfn = "na_iduplicable_register_type";
98 	GType type;
99 
100 	static const GTypeInfo info = {
101 		sizeof( NAIDuplicableInterface ),
102 		( GBaseInitFunc ) interface_base_init,
103 		( GBaseFinalizeFunc ) interface_base_finalize,
104 		NULL,
105 		NULL,
106 		NULL,
107 		0,
108 		0,
109 		NULL
110 	};
111 
112 	g_debug( "%s", thisfn );
113 
114 	type = g_type_register_static( G_TYPE_INTERFACE, "NAIDuplicable", &info, 0 );
115 
116 	g_type_interface_add_prerequisite( type, G_TYPE_OBJECT );
117 
118 	return( type );
119 }
120 
121 static void
interface_base_init(NAIDuplicableInterface * klass)122 interface_base_init( NAIDuplicableInterface *klass )
123 {
124 	static const gchar *thisfn = "na_iduplicable_interface_base_init";
125 
126 	if( !st_initializations ){
127 
128 		g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
129 
130 		klass->private = g_new0( NAIDuplicableInterfacePrivate, 1 );
131 
132 		klass->private->consumers = NULL;
133 
134 		klass->copy = NULL;
135 		klass->are_equal = NULL;
136 		klass->is_valid = NULL;
137 
138 		/**
139 		 * NAIDuplicable::modified-changed:
140 		 *
141 		 * This signal is emitted by #NAIDuplicable when the modification
142 		 * status of an object has been modified.
143 		 *
144 		 * The default class handler propagates the signal to registered
145 		 * consumers.
146 		 *
147 		 * Signal args: New modification status
148 		 *
149 		 * Handler prototype:
150 		 * void ( *handler )( NAIDuplicable *duplicable, NAObject *object, gboolean is_modified, gpointer user_data );
151 		 *
152 		 * When the signal is first emitted, thus on NAIDuplicable, @duplicable
153 		 * and @object are pointers to the same address. This duplication is
154 		 * relevant when propagating the signal to customer, as the signal is
155 		 * emitted on the customer itself, while we still need the @object
156 		 * pointer.
157 		 */
158 		st_signals[ MODIFIED_CHANGED ] = g_signal_new_class_handler(
159 				IDUPLICABLE_SIGNAL_MODIFIED_CHANGED,
160 				G_TYPE_OBJECT,
161 				G_SIGNAL_RUN_CLEANUP,
162 				G_CALLBACK( on_modified_changed_class_handler ),
163 				NULL,
164 				NULL,
165 				na_cclosure_marshal_VOID__POINTER_BOOLEAN,
166 				G_TYPE_NONE,
167 				2,
168 				G_TYPE_POINTER, G_TYPE_BOOLEAN );
169 
170 		/**
171 		 * NAIDuplicable::valid-changed:
172 		 *
173 		 * This signal is emitted by #NAIDuplicable when the validity
174 		 * status of an object has been modified.
175 		 *
176 		 * The default class handler propagates the signal to registered
177 		 * consumers.
178 		 *
179 		 * Signal args: New validity status
180 		 *
181 		 * Handler prototype:
182 		 * void ( *handler )( NAIDuplicable *duplicable, NAObject *object, gboolean is_valid, gpointer user_data );
183 		 *
184 		 * When the signal is first emitted, thus on NAIDuplicable, @duplicable
185 		 * and @object are pointers to the same address. This duplication is
186 		 * relevant when propagating the signal to customer, as the signal is
187 		 * emitted on the customer itself, while we still need the @object
188 		 * pointer.
189 		 */
190 		st_signals[ VALID_CHANGED ] = g_signal_new_class_handler(
191 				IDUPLICABLE_SIGNAL_VALID_CHANGED,
192 				G_TYPE_OBJECT,
193 				G_SIGNAL_RUN_CLEANUP,
194 				G_CALLBACK( on_valid_changed_class_handler ),
195 				NULL,
196 				NULL,
197 				na_cclosure_marshal_VOID__POINTER_BOOLEAN,
198 				G_TYPE_NONE,
199 				2,
200 				G_TYPE_POINTER, G_TYPE_BOOLEAN );
201 
202 		st_interface = klass;
203 	}
204 
205 	st_initializations += 1;
206 }
207 
208 static void
interface_base_finalize(NAIDuplicableInterface * klass)209 interface_base_finalize( NAIDuplicableInterface *klass )
210 {
211 	static const gchar *thisfn = "na_iduplicable_interface_base_finalize";
212 
213 	st_initializations -= 1;
214 
215 	if( !st_initializations ){
216 
217 		g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
218 
219 		release_signal_consumers( klass->private->consumers );
220 
221 		g_free( klass->private );
222 	}
223 }
224 
225 /**
226  * na_iduplicable_dispose:
227  * @object: the #NAIDuplicable object to be initialized.
228  *
229  * Releases resources.
230  *
231  * Since: 2.30
232  */
233 void
na_iduplicable_dispose(const NAIDuplicable * object)234 na_iduplicable_dispose( const NAIDuplicable *object )
235 {
236 	DuplicableStr *str;
237 
238 	g_return_if_fail( NA_IS_IDUPLICABLE( object ));
239 
240 	str = get_duplicable_str( object );
241 	g_free( str );
242 	g_object_set_data( G_OBJECT( object ), NA_IDUPLICABLE_DATA_DUPLICABLE, NULL );
243 }
244 
245 /**
246  * na_iduplicable_dump:
247  * @object: the #NAIDuplicable object to be dumped.
248  *
249  * Dumps via g_debug the properties of the object.
250  *
251  * We ouput here only the data we set ourselves againt the
252  * #NAIDuplicable -implemented object.
253  *
254  * This function should be called by the implementation when it dumps
255  * itself its own content.
256  *
257  * Since: 2.30
258  */
259 void
na_iduplicable_dump(const NAIDuplicable * object)260 na_iduplicable_dump( const NAIDuplicable *object )
261 {
262 	static const gchar *thisfn = "na_iduplicable_dump";
263 	DuplicableStr *str;
264 
265 	g_return_if_fail( NA_IS_IDUPLICABLE( object ));
266 
267 	str = get_duplicable_str( object );
268 
269 	g_debug( "| %s:   origin=%p", thisfn, ( void * ) str->origin );
270 	g_debug( "| %s: modified=%s", thisfn, str->modified ? "True" : "False" );
271 	g_debug( "| %s:    valid=%s", thisfn, str->valid ? "True" : "False" );
272 }
273 
274 /**
275  * na_iduplicable_duplicate:
276  * @object: the #NAIDuplicable object to be duplicated.
277  * @mode: the %DuplicableMode duplication mode.
278  *
279  * Exactly duplicates a #NAIDuplicable -implemented object, including
280  * modification and validity status which are copied from @object to
281  * the duplicated one.
282  *
283  * Returns: a new #NAIDuplicable.
284  *
285  * Since: 2.30
286  */
287 NAIDuplicable *
na_iduplicable_duplicate(const NAIDuplicable * object,guint mode)288 na_iduplicable_duplicate( const NAIDuplicable *object, guint mode )
289 {
290 	static const gchar *thisfn = "na_iduplicable_duplicate";
291 	NAIDuplicable *dup;
292 	DuplicableStr *dup_str, *obj_str;
293 
294 	g_return_val_if_fail( NA_IS_IDUPLICABLE( object ), NULL );
295 
296 	g_debug( "%s: object=%p (%s)",
297 			thisfn,
298 			( void * ) object, G_OBJECT_TYPE_NAME( object ));
299 
300 	dup = g_object_new( G_OBJECT_TYPE( object ), NULL );
301 
302 	v_copy( dup, object, mode );
303 
304 	dup_str = get_duplicable_str( dup );
305 	obj_str = get_duplicable_str( object );
306 
307 	dup_str->origin = ( NAIDuplicable * ) object;
308 	dup_str->modified = obj_str->modified;
309 	dup_str->valid = obj_str->valid;
310 
311 	return( dup );
312 }
313 
314 /**
315  * na_iduplicable_check_status:
316  * @object: the #NAIDuplicable object to be checked.
317  *
318  * Checks the edition status of the #NAIDuplicable object, and set up
319  * the corresponding properties.
320  *
321  * This function is supposed to be called each time the object may have
322  * been modified in order to set the corresponding properties. Helper
323  * functions na_iduplicable_is_modified() and na_iduplicable_is_valid()
324  * will then only return the current value of the properties.
325  *
326  * na_iduplicable_check_status() is not, as itself, recursive.
327  * That is, the modification and validity status are only set on the
328  * specified object.
329  * #NAObject implementation has chosen to handle itself the recursivity:
330  * na_object_check_status() so first check status for children, before
331  * calling this function.
332  *
333  * Since: 2.30
334  */
335 void
na_iduplicable_check_status(const NAIDuplicable * object)336 na_iduplicable_check_status( const NAIDuplicable *object )
337 {
338 	static const gchar *thisfn = "na_iduplicable_check_status";
339 	DuplicableStr *str;
340 	gboolean was_modified, was_valid;
341 
342 	g_return_if_fail( NA_IS_IDUPLICABLE( object ));
343 
344 	g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));
345 
346 	str = get_duplicable_str( object );
347 
348 	was_modified = str->modified;
349 	was_valid = str->valid;
350 
351 	if( str->origin ){
352 		g_debug( "%s: vs. origin=%p (%s)", thisfn, ( void * ) str->origin, G_OBJECT_TYPE_NAME( str->origin ));
353 		g_return_if_fail( NA_IS_IDUPLICABLE( str->origin ));
354 		str->modified = !v_are_equal( str->origin, object );
355 
356 	} else {
357 		str->modified = TRUE;
358 	}
359 
360 	if( was_modified != str->modified ){
361 		g_debug( "%s: %p (%s) status changed to modified=%s",
362 				thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ), str->modified ? "True":"False" );
363 		g_signal_emit_by_name( G_OBJECT( object ), IDUPLICABLE_SIGNAL_MODIFIED_CHANGED, object, str->modified );
364 	}
365 
366 	str->valid = v_is_valid( object );
367 
368 	if( was_valid != str->valid ){
369 		g_debug( "%s: %p (%s) status changed to valid=%s",
370 				thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ), str->valid ? "True":"False" );
371 		g_signal_emit_by_name( G_OBJECT( object ), IDUPLICABLE_SIGNAL_VALID_CHANGED, object, str->valid );
372 	}
373 }
374 
375 /**
376  * na_iduplicable_get_origin:
377  * @object: the #NAIDuplicable object whose origin is to be returned.
378  *
379  * Returns the origin of a duplicated #NAIDuplicable.
380  *
381  * Returns: the original #NAIDuplicable, or NULL.
382  *
383  * Since: 2.30
384  */
385 NAIDuplicable *
na_iduplicable_get_origin(const NAIDuplicable * object)386 na_iduplicable_get_origin( const NAIDuplicable *object )
387 {
388 	NAIDuplicable *origin;
389 	DuplicableStr *str;
390 
391 	g_return_val_if_fail( NA_IS_IDUPLICABLE( object ), NULL );
392 
393 	str = get_duplicable_str( object );
394 	origin = str->origin;
395 
396 	return( origin );
397 }
398 
399 /**
400  * na_iduplicable_is_valid:
401  * @object: the #NAIDuplicable object whose status is to be returned.
402  *
403  * Returns the current value of the relevant property
404  * without rechecking the edition status itself.
405  *
406  * Returns: %TRUE is the provided object is valid.
407  *
408  * Since: 2.30
409  */
410 gboolean
na_iduplicable_is_valid(const NAIDuplicable * object)411 na_iduplicable_is_valid( const NAIDuplicable *object )
412 {
413 	gboolean is_valid;
414 	DuplicableStr *str;
415 
416 	g_return_val_if_fail( NA_IS_IDUPLICABLE( object ), FALSE );
417 
418 	str = get_duplicable_str( object );
419 	is_valid = str->valid;
420 
421 	return( is_valid );
422 }
423 
424 /**
425  * na_iduplicable_is_modified:
426  * @object: the #NAIDuplicable object whose status is to be returned.
427  *
428  * Returns the current value of the 'is_modified'
429  * property without rechecking the edition status itself.
430  *
431  * Returns: %TRUE is the provided object has been modified regarding of
432  * the original one.
433  *
434  * Since: 2.30
435  */
436 gboolean
na_iduplicable_is_modified(const NAIDuplicable * object)437 na_iduplicable_is_modified( const NAIDuplicable *object )
438 {
439 	gboolean is_modified;
440 	DuplicableStr *str;
441 
442 	g_return_val_if_fail( NA_IS_IDUPLICABLE( object ), FALSE );
443 
444 	str = get_duplicable_str( object );
445 	is_modified = str->modified;
446 
447 	return( is_modified );
448 }
449 
450 /**
451  * na_iduplicable_set_origin:
452  * @object: the #NAIDuplicable object whose origin is to be set.
453  * @origin: the new original #NAIDuplicable.
454  *
455  * Sets the new origin of a duplicated #NAIDuplicable.
456  *
457  * Since: 2.30
458  */
459 void
na_iduplicable_set_origin(NAIDuplicable * object,const NAIDuplicable * origin)460 na_iduplicable_set_origin( NAIDuplicable *object, const NAIDuplicable *origin )
461 {
462 	DuplicableStr *str;
463 
464 	g_return_if_fail( NA_IS_IDUPLICABLE( object ));
465 	g_return_if_fail( NA_IS_IDUPLICABLE( origin ) || !origin );
466 
467 	str = get_duplicable_str( object );
468 	str->origin = ( NAIDuplicable * ) origin;
469 }
470 
471 #ifdef NA_ENABLE_DEPRECATED
472 /**
473  * na_iduplicable_set_modified:
474  * @object: the #NAIDuplicable object whose modification status is to be set.
475  * @modified: the new modification status #NAIDuplicable.
476  *
477  * Sets the new modification status of a duplicated #NAIDuplicable.
478  *
479  * Since: 2.30
480  * Deprecated: 3.1
481  */
482 void
na_iduplicable_set_modified(NAIDuplicable * object,gboolean modified)483 na_iduplicable_set_modified( NAIDuplicable *object, gboolean modified )
484 {
485 	DuplicableStr *str;
486 
487 	g_return_if_fail( NA_IS_IDUPLICABLE( object ));
488 
489 	str = get_duplicable_str( object );
490 	str->modified = modified;
491 }
492 #endif /* NA_ENABLE_DEPRECATED */
493 
494 static void
v_copy(NAIDuplicable * target,const NAIDuplicable * source,guint mode)495 v_copy( NAIDuplicable *target, const NAIDuplicable *source, guint mode )
496 {
497 	if( NA_IDUPLICABLE_GET_INTERFACE( target )->copy ){
498 		NA_IDUPLICABLE_GET_INTERFACE( target )->copy( target, source, mode );
499 	}
500 }
501 
502 static gboolean
v_are_equal(const NAIDuplicable * a,const NAIDuplicable * b)503 v_are_equal( const NAIDuplicable *a, const NAIDuplicable *b )
504 {
505 	if( NA_IDUPLICABLE_GET_INTERFACE( a )->are_equal ){
506 		return( NA_IDUPLICABLE_GET_INTERFACE( a )->are_equal( a, b ));
507 	}
508 
509 	return( TRUE );
510 }
511 
512 static gboolean
v_is_valid(const NAIDuplicable * object)513 v_is_valid( const NAIDuplicable *object )
514 {
515 	if( NA_IDUPLICABLE_GET_INTERFACE( object )->is_valid ){
516 		return( NA_IDUPLICABLE_GET_INTERFACE( object )->is_valid( object ));
517 	}
518 
519 	return( TRUE );
520 }
521 
522 /**
523  * na_iduplicable_register_consumer:
524  * @consumer: the target instance.
525  *
526  * This function registers a consumer, i.e. an instance to which edition
527  * status signals will be propagated.
528  *
529  * Since: 2.30
530  */
531 void
na_iduplicable_register_consumer(GObject * consumer)532 na_iduplicable_register_consumer( GObject *consumer )
533 {
534 	g_return_if_fail( st_interface );
535 
536 	g_debug( "na_iduplicable_register_consumer: consumer=%p", ( void * ) consumer );
537 
538 	st_interface->private->consumers = g_list_prepend( st_interface->private->consumers, consumer );
539 }
540 
541 static void
on_modified_changed_class_handler(NAIDuplicable * instance,GObject * object,gboolean is_modified)542 on_modified_changed_class_handler( NAIDuplicable *instance, GObject *object, gboolean is_modified )
543 {
544 	if( NA_IS_IDUPLICABLE( instance )){
545 		propagate_signal_to_consumers( instance, IDUPLICABLE_SIGNAL_MODIFIED_CHANGED, object, is_modified );
546 	}
547 }
548 
549 static void
on_valid_changed_class_handler(NAIDuplicable * instance,GObject * object,gboolean is_valid)550 on_valid_changed_class_handler( NAIDuplicable *instance, GObject *object, gboolean is_valid )
551 {
552 	if( NA_IS_IDUPLICABLE( instance )){
553 		propagate_signal_to_consumers( instance, IDUPLICABLE_SIGNAL_VALID_CHANGED, object, is_valid );
554 	}
555 }
556 
557 static void
propagate_signal_to_consumers(NAIDuplicable * instance,const gchar * signal,GObject * object,gboolean new_status)558 propagate_signal_to_consumers( NAIDuplicable *instance, const gchar *signal, GObject *object, gboolean new_status )
559 {
560 	static const gchar *thisfn = "na_iduplicable_propagate_signals_to_consumers";
561 	GList *ic;
562 
563 	g_return_if_fail( NA_IS_IDUPLICABLE( instance ));
564 
565 	g_debug( "%s: instance=%p, signal=%s", thisfn, ( void * ) instance, signal );
566 
567 	for( ic = st_interface->private->consumers ; ic ; ic = ic->next ){
568 		g_signal_emit_by_name( ic->data, signal, object, new_status );
569 	}
570 }
571 
572 static void
release_signal_consumers(GList * consumers)573 release_signal_consumers( GList *consumers )
574 {
575 	g_list_free( consumers );
576 }
577 
578 static DuplicableStr *
get_duplicable_str(const NAIDuplicable * object)579 get_duplicable_str( const NAIDuplicable *object )
580 {
581 	DuplicableStr *str;
582 
583 	str = ( DuplicableStr * ) g_object_get_data( G_OBJECT( object ), NA_IDUPLICABLE_DATA_DUPLICABLE );
584 
585 	if( !str ){
586 		str = g_new0( DuplicableStr, 1 );
587 
588 		str->origin = NULL;
589 		str->modified = FALSE;
590 		str->valid = TRUE;
591 
592 		g_object_set_data( G_OBJECT( object ), NA_IDUPLICABLE_DATA_DUPLICABLE, str );
593 	}
594 
595 	return( str );
596 }
597