fma-pivot.c 17.4 KB
Newer Older
1
/*
2 3
 * FileManager-Actions
 * A file-manager 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)
Pierre Wieser's avatar
Pierre Wieser committed
7
 * Copyright (C) 2009-2015 Pierre Wieser and others (see AUTHORS)
8
 *
9
 * FileManager-Actions is free software; you can redistribute it and/or
10 11
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
12 13
 * the License, or (at your option) any later version.
 *
14
 * FileManager-Actions is distributed in the hope that it will be useful,
15 16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * 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 FileManager-Actions; see the file COPYING. If not, see
21
 * <http://www.gnu.org/licenses/>.
22 23 24 25 26 27 28 29 30 31 32 33
 *
 * Authors:
 *   Frederic Ruaudel <grumz@grumz.net>
 *   Rodrigo Moya <rodrigo@gnome-db.org>
 *   Pierre Wieser <pwieser@trychlos.org>
 *   ... and many others (see AUTHORS)
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

34 35
#include <string.h>

36
#include <api/fma-core-utils.h>
37
#include <api/fma-timeout.h>
38

39
#include "fma-io-provider.h"
40
#include "fma-module.h"
41
#include "fma-pivot.h"
42

43
/* private class data
44
 */
45
struct _FMAPivotClassPrivate {
46
	void *empty;						/* so that gcc -pedantic is happy */
47
};
48

49 50
/* private instance data
 */
51
struct _FMAPivotPrivate {
52
	gboolean    dispose_has_run;
53

54
	guint       loadable_set;
55

56
	/* dynamically loaded modules (extension plugins)
57
	 */
58 59
	GList      *modules;

60
	/* configuration tree of actions and menus
61
	 */
62
	GList      *tree;
63

64 65
	/* timeout to manage i/o providers 'item-changed' burst
	 */
Pierre Wieser's avatar
Pierre Wieser committed
66
	FMATimeout  change_timeout;
67 68
};

69
/* FMAPivot properties
70 71
 */
enum {
72 73 74 75 76 77 78
	PRIVOT_PROP_0,

	PIVOT_PROP_LOADABLE_ID,
	PIVOT_PROP_TREE_ID,

	/* count of properties */
	PIVOT_PROP_N
79 80
};

81 82 83 84 85 86
/* signals
 */
enum {
	ITEMS_CHANGED,
	LAST_SIGNAL
};
87

Pierre Wieser's avatar
Pierre Wieser committed
88 89 90
static GObjectClass  *st_parent_class           = NULL;
static gint           st_burst_timeout          = 100;		/* burst timeout in msec */
static gint           st_signals[ LAST_SIGNAL ] = { 0 };
91

Pierre Wieser's avatar
Pierre Wieser committed
92 93 94 95 96 97 98 99
static GType          register_type( void );
static void           class_init( FMAPivotClass *klass );
static void           instance_init( GTypeInstance *instance, gpointer klass );
static void           instance_constructed( GObject *object );
static void           instance_get_property( GObject *object, guint property_id, GValue *value, GParamSpec *spec );
static void           instance_set_property( GObject *object, guint property_id, const GValue *value, GParamSpec *spec );
static void           instance_dispose( GObject *object );
static void           instance_finalize( GObject *object );
100

101
static FMAObjectItem *get_item_from_tree( const FMAPivot *pivot, GList *tree, const gchar *id );
102

103
/* FMAIIOProvider management */
Pierre Wieser's avatar
Pierre Wieser committed
104
static void           on_items_changed_timeout( FMAPivot *pivot );
105

106
GType
107
fma_pivot_get_type( void )
108 109 110 111 112 113 114 115 116 117 118 119 120
{
	static GType object_type = 0;

	if( !object_type ){
		object_type = register_type();
	}

	return( object_type );
}

static GType
register_type( void )
{
121
	static const gchar *thisfn = "fma_pivot_register_type";
122 123
	GType type;

124
	static GTypeInfo info = {
125
		sizeof( FMAPivotClass ),
126 127 128 129 130
		( GBaseInitFunc ) NULL,
		( GBaseFinalizeFunc ) NULL,
		( GClassInitFunc ) class_init,
		NULL,
		NULL,
131
		sizeof( FMAPivot ),
132 133 134 135
		0,
		( GInstanceInitFunc ) instance_init
	};

136 137
	g_debug( "%s", thisfn );

138
	type = g_type_register_static( G_TYPE_OBJECT, "FMAPivot", &info, 0 );
139 140

	return( type );
141 142 143
}

static void
144
class_init( FMAPivotClass *klass )
145
{
146
	static const gchar *thisfn = "fma_pivot_class_init";
147 148 149
	GObjectClass *object_class;

	g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
150 151 152

	st_parent_class = g_type_class_peek_parent( klass );

153
	object_class = G_OBJECT_CLASS( klass );
154
	object_class->constructed = instance_constructed;
155 156
	object_class->set_property = instance_set_property;
	object_class->get_property = instance_get_property;
157 158 159
	object_class->dispose = instance_dispose;
	object_class->finalize = instance_finalize;

160 161 162 163 164 165 166 167 168 169 170 171 172 173
	g_object_class_install_property( object_class, PIVOT_PROP_LOADABLE_ID,
			g_param_spec_uint(
					PIVOT_PROP_LOADABLE,
					"Loadable set",
					"The set of loadble items",
					0, 255, 0,
					G_PARAM_CONSTRUCT | G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE ));

	g_object_class_install_property( object_class, PIVOT_PROP_TREE_ID,
			g_param_spec_pointer(
					PIVOT_PROP_TREE,
					"Items tree",
					"Hierarchical tree of items",
					G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE ));
174

175
	klass->private = g_new0( FMAPivotClassPrivate, 1 );
176 177

	/*
178
	 * FMAPivot::pivot-items-changed:
179
	 *
180
	 * This signal is sent by FMAPivot at the end of a burst of modifications
181 182 183 184 185 186
	 * as signaled by i/o providers.
	 *
	 * The signal is registered without any default handler.
	 */
	st_signals[ ITEMS_CHANGED ] = g_signal_new(
				PIVOT_SIGNAL_ITEMS_CHANGED,
187
				FMA_TYPE_PIVOT,
188 189 190 191 192 193 194
				G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
				0,									/* class offset */
				NULL,								/* accumulator */
				NULL,								/* accumulator data */
				g_cclosure_marshal_VOID__VOID,
				G_TYPE_NONE,
				0 );
195 196
}

197 198 199
static void
instance_init( GTypeInstance *instance, gpointer klass )
{
200 201
	static const gchar *thisfn = "fma_pivot_instance_init";
	FMAPivot *self;
202

203
	g_return_if_fail( FMA_IS_PIVOT( instance ));
204

205 206 207
	g_debug( "%s: instance=%p (%s), klass=%p",
			thisfn, ( void * ) instance, G_OBJECT_TYPE_NAME( instance ), ( void * ) klass );

208
	self = FMA_PIVOT( instance );
209

210
	self->private = g_new0( FMAPivotPrivate, 1 );
211

212
	self->private->dispose_has_run = FALSE;
213
	self->private->loadable_set = PIVOT_LOAD_NONE;
214
	self->private->modules = NULL;
215
	self->private->tree = NULL;
216 217 218 219

	/* initialize timeout parameters for 'item-changed' handler
	 */
	self->private->change_timeout.timeout = st_burst_timeout;
220
	self->private->change_timeout.handler = ( FMATimeoutFunc ) on_items_changed_timeout;
221 222
	self->private->change_timeout.user_data = self;
	self->private->change_timeout.source_id = 0;
223 224
}

225 226 227
static void
instance_constructed( GObject *object )
{
228 229
	static const gchar *thisfn = "fma_pivot_instance_constructed";
	FMAPivotPrivate *priv;
230

231
	g_return_if_fail( FMA_IS_PIVOT( object ));
232

233
	priv = FMA_PIVOT( object )->private;
234 235 236 237 238 239 240

	if( !priv->dispose_has_run ){

		/* chain up to the parent class */
		if( G_OBJECT_CLASS( st_parent_class )->constructed ){
			G_OBJECT_CLASS( st_parent_class )->constructed( object );
		}
241

242
		g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));
243

244
		priv->modules = fma_module_load_modules();
245

Pierre Wieser's avatar
Pierre Wieser committed
246 247
		/* force class initialization and io-factory registration
		 */
248 249
		g_object_unref( fma_object_action_new_with_profile());
		g_object_unref( fma_object_menu_new());
250 251 252
	}
}

253 254 255
static void
instance_get_property( GObject *object, guint property_id, GValue *value, GParamSpec *spec )
{
256
	FMAPivot *self;
257

258 259
	g_return_if_fail( FMA_IS_PIVOT( object ));
	self = FMA_PIVOT( object );
260 261 262 263

	if( !self->private->dispose_has_run ){

		switch( property_id ){
264 265 266 267
			case PIVOT_PROP_LOADABLE_ID:
				g_value_set_uint( value, self->private->loadable_set );
				break;

268
			case PIVOT_PROP_TREE_ID:
269 270 271
				g_value_set_pointer( value, self->private->tree );
				break;

272 273 274 275 276 277 278 279 280 281
			default:
				G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, spec );
				break;
		}
	}
}

static void
instance_set_property( GObject *object, guint property_id, const GValue *value, GParamSpec *spec )
{
282
	FMAPivot *self;
283

284 285
	g_return_if_fail( FMA_IS_PIVOT( object ));
	self = FMA_PIVOT( object );
286 287 288 289

	if( !self->private->dispose_has_run ){

		switch( property_id ){
290 291 292 293
			case PIVOT_PROP_LOADABLE_ID:
				self->private->loadable_set = g_value_get_uint( value );
				break;

294
			case PIVOT_PROP_TREE_ID:
295 296 297
				self->private->tree = g_value_get_pointer( value );
				break;

298 299 300 301 302
			default:
				G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, spec );
				break;
		}
	}
Pierre Wieser's avatar
Pierre Wieser committed
303 304
}

305 306 307
static void
instance_dispose( GObject *object )
{
308 309
	static const gchar *thisfn = "fma_pivot_instance_dispose";
	FMAPivot *self;
310

311
	g_return_if_fail( FMA_IS_PIVOT( object ));
312

313
	self = FMA_PIVOT( object );
314 315 316

	if( !self->private->dispose_has_run ){

317 318
		g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));

319 320
		self->private->dispose_has_run = TRUE;

321
		/* release modules */
322
		fma_module_release_modules( self->private->modules );
323 324
		self->private->modules = NULL;

325
		/* release item tree */
326 327
		g_debug( "%s: tree=%p (count=%u)", thisfn,
				( void * ) self->private->tree, g_list_length( self->private->tree ));
328 329
		fma_object_dump_tree( self->private->tree );
		self->private->tree = fma_object_free_items( self->private->tree );
330

331
		/* release the settings */
332
		fma_settings_free();
333

334
		/* release the I/O Provider object list */
335
		fma_io_provider_unref_io_providers_list();
336

337
		/* chain up to the parent class */
338 339 340
		if( G_OBJECT_CLASS( st_parent_class )->dispose ){
			G_OBJECT_CLASS( st_parent_class )->dispose( object );
		}
341 342 343 344 345 346
	}
}

static void
instance_finalize( GObject *object )
{
347 348
	static const gchar *thisfn = "fma_pivot_instance_finalize";
	FMAPivot *self;
349

350
	g_return_if_fail( FMA_IS_PIVOT( object ));
351

352 353
	g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));

354
	self = FMA_PIVOT( object );
355

356 357
	g_free( self->private );

358
	/* chain call to parent class */
359
	if( G_OBJECT_CLASS( st_parent_class )->finalize ){
360 361 362 363
		G_OBJECT_CLASS( st_parent_class )->finalize( object );
	}
}

364
/*
365
 * fma_pivot_new:
Pierre Wieser's avatar
Pierre Wieser committed
366
 *
367 368 369 370 371
 * This object takes care of all items/actions/menus/providers/settings
 * management which is required to correctly handle file manager context
 * menus.
 *
 * When this object is instantiated, it automatically takes care of:
372
 * - loading FileManager-Actions dynamic modules;
373 374 375
 * - initializing the preferences monitoring.
 *
 * Actual loading of items from i/o providers is delayed until a call
376 377
 * to call to fma_pivot_load_items() function, so that the caller is able
 * to set its own needed #FMAPivot properties (e.g. the loadable set of
378 379
 * items).
 *
380
 * Only one #FMAPivot object should be instantiated for a running application.
381
 *
382
 * Returns: a newly allocated #FMAPivot object which should be g_object_unref()
383
 * by the caller at the end of the application.
Pierre Wieser's avatar
Pierre Wieser committed
384
 */
385 386
FMAPivot *
fma_pivot_new( void )
387
{
388 389
	static const gchar *thisfn = "fma_pivot_new";
	FMAPivot *pivot;
390

391
	g_debug( "%s", thisfn );
392

393
	pivot = g_object_new( FMA_TYPE_PIVOT, NULL );
394

395
	return( pivot );
396 397
}

398
/*
399 400
 * fma_pivot_dump:
 * @pivot: the #FMAPivot object do be dumped.
401
 *
402
 * Dumps the content of a #FMAPivot object.
403 404
 */
void
405
fma_pivot_dump( const FMAPivot *pivot )
406
{
407
	static const gchar *thisfn = "fma_pivot_dump";
408
	GList *it;
409
	int i;
410

411
	if( !pivot->private->dispose_has_run ){
412

413 414 415 416
		g_debug( "%s: loadable_set=%d", thisfn, pivot->private->loadable_set );
		g_debug( "%s:      modules=%p (%d elts)", thisfn, ( void * ) pivot->private->modules, g_list_length( pivot->private->modules ));
		g_debug( "%s:         tree=%p (%d elts)", thisfn, ( void * ) pivot->private->tree, g_list_length( pivot->private->tree ));
		/*g_debug( "%s:     monitors=%p (%d elts)", thisfn, ( void * ) pivot->private->monitors, g_list_length( pivot->private->monitors ));*/
417 418 419 420

		for( it = pivot->private->tree, i = 0 ; it ; it = it->next ){
			g_debug( "%s:     [%d]: %p", thisfn, i++, it->data );
		}
421 422 423
	}
}

424
/*
425 426
 * fma_pivot_get_providers:
 * @pivot: this #FMAPivot instance.
427
 * @type: the type of searched interface.
428
 * For now, we only have FMA_TYPE_IIO_PROVIDER interfaces.
429
 *
430
 * Returns: a newly allocated list of providers of the required interface.
431 432
 *
 * This function is called by interfaces API in order to find the
433
 * list of providers registered for their own given interface.
434
 *
435
 * The returned list should be release by calling fma_pivot_free_providers().
436
 */
437
GList *
438
fma_pivot_get_providers( const FMAPivot *pivot, GType type )
439
{
440
	static const gchar *thisfn = "fma_pivot_get_providers";
441
	GList *list = NULL;
442

443
	g_return_val_if_fail( FMA_IS_PIVOT( pivot ), NULL );
444

445 446
	if( !pivot->private->dispose_has_run ){

447 448
		g_debug( "%s: pivot=%p, type=%lu (%s)", thisfn, ( void * ) pivot, ( unsigned long ) type, g_type_name( type ));

449
		list = fma_module_get_extensions_for_type( pivot->private->modules, type );
450
		g_debug( "%s: list=%p, count=%d", thisfn, ( void * ) list, list ? g_list_length( list ) : 0 );
451 452 453 454
	}

	return( list );
}
455

456
/*
457
 * fma_pivot_free_providers:
458 459
 * @providers: a list of providers.
 *
460
 * Frees a list of providers as returned from fma_pivot_get_providers().
461 462
 */
void
463
fma_pivot_free_providers( GList *providers )
464
{
465
	static const gchar *thisfn = "fma_pivot_free_providers";
466 467 468

	g_debug( "%s: providers=%p", thisfn, ( void * ) providers );

469
	fma_module_free_extensions_list( providers );
470 471
}

472
/*
473 474
 * fma_pivot_get_item:
 * @pivot: this #FMAPivot instance.
475 476 477 478
 * @id: the required item identifier.
 *
 * Returns the specified item, action or menu.
 *
479
 * Returns: the required #FMAObjectItem-derived object, or %NULL if not
480 481
 * found.
 *
482
 * The returned pointer is owned by #FMAPivot, and should not be
483
 * g_free() nor g_object_unref() by the caller.
484
 */
485
FMAObjectItem *
486
fma_pivot_get_item( const FMAPivot *pivot, const gchar *id )
487
{
488
	FMAObjectItem *object = NULL;
489

490
	g_return_val_if_fail( FMA_IS_PIVOT( pivot ), NULL );
491 492 493

	if( !pivot->private->dispose_has_run ){

494 495
		if( !id || !strlen( id )){
			return( NULL );
496
		}
497 498

		object = get_item_from_tree( pivot, pivot->private->tree, id );
499
	}
500 501

	return( object );
502 503
}

504
static FMAObjectItem *
505
get_item_from_tree( const FMAPivot *pivot, GList *tree, const gchar *id )
506
{
507
	GList *subitems, *ia;
508
	FMAObjectItem *found = NULL;
509

510
	for( ia = tree ; ia && !found ; ia = ia->next ){
511

512
		gchar *i_id = fma_object_get_id( FMA_OBJECT( ia->data ));
513

514
		if( !g_ascii_strcasecmp( id, i_id )){
515
			found = FMA_OBJECT_ITEM( ia->data );
516
		}
517

518 519
		if( !found && FMA_IS_OBJECT_ITEM( ia->data )){
			subitems = fma_object_get_items( ia->data );
520 521
			found = get_item_from_tree( pivot, subitems, id );
		}
522 523
	}

524
	return( found );
525 526
}

527
/*
528 529
 * fma_pivot_get_items:
 * @pivot: this #FMAPivot instance.
530
 *
531
 * Returns: the current configuration tree.
532
 *
533
 * The returned list is owned by this #FMAPivot object, and should not
534
 * be g_free(), nor g_object_unref() by the caller.
535
 */
536
GList *
537
fma_pivot_get_items( const FMAPivot *pivot )
538
{
539
	GList *tree;
540

541
	g_return_val_if_fail( FMA_IS_PIVOT( pivot ), NULL );
542

543 544
	tree = NULL;

545
	if( !pivot->private->dispose_has_run ){
546

547 548 549 550
		tree = pivot->private->tree;
	}

	return( tree );
551 552
}

553
/*
554 555
 * fma_pivot_load_items:
 * @pivot: this #FMAPivot instance.
556
 *
557
 * Loads the hierarchical list of items from I/O providers.
558 559
 */
void
560
fma_pivot_load_items( FMAPivot *pivot )
561
{
562
	static const gchar *thisfn = "fma_pivot_load_items";
563 564
	GSList *messages, *im;

565
	g_return_if_fail( FMA_IS_PIVOT( pivot ));
566

567
	if( !pivot->private->dispose_has_run ){
568

569 570
		g_debug( "%s: pivot=%p", thisfn, ( void * ) pivot );

571
		messages = NULL;
572
		fma_object_free_items( pivot->private->tree );
573
		pivot->private->tree = fma_io_provider_load_items( pivot, pivot->private->loadable_set, &messages );
574

575 576 577
		for( im = messages ; im ; im = im->next ){
			g_warning( "%s: %s", thisfn, ( const gchar * ) im->data );
		}
578

579
		fma_core_utils_slist_free( messages );
580
	}
581 582
}

583
/*
584 585
 * fma_pivot_set_new_items:
 * @pivot: this #FMAPivot instance.
586 587
 * @tree: the new tree of items.
 *
588 589
 * Replace the current list with this one, acquiring the full ownership
 * of the provided @tree.
590 591
 */
void
592
fma_pivot_set_new_items( FMAPivot *pivot, GList *items )
593
{
594
	static const gchar *thisfn = "fma_pivot_set_new_items";
595

596
	g_return_if_fail( FMA_IS_PIVOT( pivot ));
597 598 599 600 601 602

	if( !pivot->private->dispose_has_run ){

		g_debug( "%s: pivot=%p, items=%p (count=%d)",
				thisfn, ( void * ) pivot, ( void * ) items, items ? g_list_length( items ) : 0 );

603
		fma_object_free_items( pivot->private->tree );
604 605 606 607
		pivot->private->tree = items;
	}
}

608
/*
609
 * fma_pivot_on_item_changed_handler:
610
 * @provider: the #FMAIIOProvider which has emitted the signal.
611
 * @pivot: this #FMAPivot instance.
612
 *
613
 * This handler is trigerred by #FMAIIOProvider providers when an action
614
 * is changed in their underlying storage subsystems.
615
 *
616
 * The FMAIIOProvider is supposed to have itself already summarized
617 618
 * a minima its own burst of notifications.
 *
619 620
 * We don't care of updating our internal list with each and every
 * atomic modification; instead we wait for the end of notifications
621
 * serie, and then signal our consumers.
622 623
 */
void
624
fma_pivot_on_item_changed_handler( FMAIIOProvider *provider, FMAPivot *pivot  )
625
{
626
	static const gchar *thisfn = "fma_pivot_on_item_changed_handler";
627

628
	g_return_if_fail( FMA_IS_IIO_PROVIDER( provider ));
629
	g_return_if_fail( FMA_IS_PIVOT( pivot ));
630

631
	if( !pivot->private->dispose_has_run ){
632
		g_debug( "%s: provider=%p, pivot=%p", thisfn, ( void * ) provider, ( void * ) pivot );
633

634
		na_timeout_event( &pivot->private->change_timeout );
Pierre Wieser's avatar
Pierre Wieser committed
635
	}
636 637
}

638
/*
639 640 641
 * this callback is triggered after having received a first 'item-changed' event,
 * and having received no more event during a 'st_burst_timeout' period; we can
 * so suppose that the burst if modification events is terminated
642
 * this is up to FMAPivot to send now its summarized signal
643
 */
644
static void
645
on_items_changed_timeout( FMAPivot *pivot )
646
{
647
	static const gchar *thisfn = "fma_pivot_on_items_changed_timeout";
648

649
	g_return_if_fail( FMA_IS_PIVOT( pivot ));
650

651 652
	g_debug( "%s: emitting %s signal", thisfn, PIVOT_SIGNAL_ITEMS_CHANGED );
	g_signal_emit_by_name(( gpointer ) pivot, PIVOT_SIGNAL_ITEMS_CHANGED );
653 654
}

655
/*
656 657
 * fma_pivot_set_loadable:
 * @pivot: this #FMAPivot instance.
658 659 660 661 662
 * @loadable: the population of items to be loaded.
 *
 * Sets the loadable set.
 */
void
663
fma_pivot_set_loadable( FMAPivot *pivot, guint loadable )
664
{
665
	g_return_if_fail( FMA_IS_PIVOT( pivot ));
666 667 668 669 670 671

	if( !pivot->private->dispose_has_run ){

		pivot->private->loadable_set = loadable;
	}
}