Commit 0721ec90 authored by Pierre Wieser's avatar Pierre Wieser

Install action changed notification system

parent 96ecfbfe
2009-06-07 Pierre Wieser <pwieser@trychlos.org>
Install the notification system.
* src/common/nautilus-actions.c:
Define new message "notify_nautilus_of_action_changed".
* src/common/nact-pivot.c:
Define new message "notify_pivot_of_action_changed".
* src/common/nact-pivot.h:
Rename and update nactPivotValue structure to NactPivotNotify.
* src/common/gconf.c:
Send message to NactPivot on action changed.
2009-06-06 Pierre Wieser <pwieser@trychlos.org>
Remove NactIIOClient interface.
......
......@@ -40,6 +40,13 @@
#include "nact-action-profile.h"
#include "nact-uti-lists.h"
/* private class data
*/
struct NactActionProfileClassPrivate {
};
/* private instance data
*/
struct NactActionProfilePrivate {
gboolean dispose_has_run;
......@@ -62,9 +69,10 @@ struct NactActionProfilePrivate {
GSList *schemes;
};
struct NactActionProfileClassPrivate {
};
/* private instance properties
* please note that property names must have the same spelling as the
* NactIIOProvider parameters
*/
enum {
PROP_ACTION = 1,
PROP_PROFILE_NAME,
......@@ -80,9 +88,6 @@ enum {
PROP_SCHEMES
};
/* please note that property names must have the same spelling as the
* NactIIOProvider parameters
*/
#define PROP_ACTION_STR "action"
#define PROP_PROFILE_NAME_STR "name"
#define PROP_LABEL_STR "desc-name"
......@@ -489,6 +494,39 @@ do_dump_list( const gchar *thisfn, const gchar *label, GSList *list )
g_string_free( str, TRUE );
}
/*
* Check if the given profile is empty, i.e. all its attributes are
* empty.
*/
gboolean
nact_action_profile_is_empty( const NactActionProfile *profile )
{
g_assert( NACT_IS_ACTION_PROFILE( profile ));
if( profile->private->name && strlen( profile->private->name )){
return( FALSE );
}
if( profile->private->label && strlen( profile->private->label )){
return( FALSE );
}
if( profile->private->path && strlen( profile->private->path )){
return( FALSE );
}
if( profile->private->parameters && strlen( profile->private->parameters )){
return( FALSE );
}
if( !nactuti_is_empty_string_list( profile->private->basenames )){
return( FALSE );
}
if( !nactuti_is_empty_string_list( profile->private->mimetypes )){
return( FALSE );
}
if( !nactuti_is_empty_string_list( profile->private->schemes )){
return( FALSE );
}
return( TRUE );
}
/**
* Returns a pointer to the action for this profile.
*/
......
......@@ -74,6 +74,8 @@ NactActionProfile *nact_action_profile_new( const NactObject *action, const gcha
void nact_action_profile_load( NactObject *profile );
gboolean nact_action_profile_is_empty( const NactActionProfile *profile );
NactObject *nact_action_profile_get_action( const NactActionProfile *profile );
gchar *nact_action_profile_get_name( const NactActionProfile *profile );
gchar *nact_action_profile_get_path( const NactActionProfile *profile );
......
......@@ -38,6 +38,13 @@
#include "nact-action-profile.h"
#include "nact-uti-lists.h"
/* private class data
*/
struct NactActionClassPrivate {
};
/* private instance data
*/
struct NactActionPrivate {
gboolean dispose_has_run;
......@@ -55,9 +62,10 @@ struct NactActionPrivate {
GSList *profiles;
};
struct NactActionClassPrivate {
};
/* private instance properties
* please note that property names must have the same spelling as the
* NactIIOProvider parameters
*/
enum {
PROP_UUID = 1,
PROP_VERSION,
......@@ -66,9 +74,6 @@ enum {
PROP_ICON
};
/* please note that property names must have the same spelling as the
* NactIIOProvider parameters
*/
#define PROP_UUID_STR "uuid"
#define PROP_VERSION_STR "version"
#define PROP_LABEL_STR "label"
......@@ -338,7 +343,7 @@ free_profiles( NactAction *action )
*
* Note that the parm may actually be a profile's parm.
*/
NactAction *
/*NactAction *
nact_action_create( const gchar *key, const gchar *parm, const NactPivotValue *value )
{
static const gchar *thisfn = "nact_action_create";
......@@ -347,17 +352,17 @@ nact_action_create( const gchar *key, const gchar *parm, const NactPivotValue *v
NactAction *action = g_object_new( NACT_ACTION_TYPE, NULL );
nact_action_update( action, parm, value );
return( action );
}
}*/
/**
* Update the given parameter of an action.
*/
void
/*void
nact_action_update( NactAction *action, const gchar *parm, const NactPivotValue *value )
{
static const gchar *thisfn = "nact_action_update";
g_debug( "%s: action=%p, parm='%s', value=%p", thisfn, action, parm, value );
}
}*/
static void
do_dump( const NactObject *action )
......@@ -385,6 +390,38 @@ do_dump( const NactObject *action )
}
}
/**
* Check if the given action is empty, i.e. all its attributes are empty.
*/
gboolean
nact_action_is_empty( const NactAction *action )
{
g_assert( NACT_IS_ACTION( action ));
if( action->private->uuid && strlen( action->private->uuid )){
return( FALSE );
}
if( action->private->version && strlen( action->private->version )){
return( FALSE );
}
if( action->private->label && strlen( action->private->label )){
return( FALSE );
}
if( action->private->tooltip && strlen( action->private->tooltip )){
return( FALSE );
}
if( action->private->icon && strlen( action->private->icon )){
return( FALSE );
}
GSList *ip;
for( ip = action->private->profiles ; ip ; ip = ip->next ){
if( !nact_action_profile_is_empty( NACT_ACTION_PROFILE( ip->data ))){
return( FALSE );
}
}
return( TRUE );
}
/**
* Return the globally unique identifier (UUID) of the action.
*
......
......@@ -71,8 +71,8 @@ GType nact_action_get_type( void );
NactAction *nact_action_new( const gchar *uuid );
NactAction *nact_action_create( const gchar *key, const gchar *parm, const NactPivotValue *value );
void nact_action_update( NactAction *action, const gchar *parm, const NactPivotValue *value );
/*NactAction *nact_action_create( const gchar *key, const gchar *parm, const NactPivotValue *value );
void nact_action_update( NactAction *action, const gchar *parm, const NactPivotValue *value );*/
gboolean nact_action_is_empty( const NactAction *action );
......
This diff is collapsed.
......@@ -66,7 +66,7 @@ typedef struct {
GType nact_gconf_get_type( void );
NactGConf *nact_gconf_new( const GObject *notification_handler );
NactGConf *nact_gconf_new( const GObject *notified );
G_END_DECLS
......
......@@ -35,6 +35,8 @@
#include "nact-iio-provider.h"
#include "nact-pivot.h"
/* private interface data
*/
struct NactIIOProviderInterfacePrivate {
};
......
......@@ -35,11 +35,15 @@
#include "nact-object.h"
#include "nact-uti-lists.h"
struct NactObjectPrivate {
gboolean dispose_has_run;
/* private class data
*/
struct NactObjectClassPrivate {
};
struct NactObjectClassPrivate {
/* private instance data
*/
struct NactObjectPrivate {
gboolean dispose_has_run;
};
static GObjectClass *st_parent_class = NULL;
......
......@@ -32,25 +32,28 @@
#include <config.h>
#endif
#include <string.h>
#include "nact-action.h"
#include "nact-gconf.h"
#include "nact-pivot.h"
#include "nact-iio-provider.h"
#include "nact-uti-lists.h"
/* action_changed_cb send events which are stacked in a static GSList
* we so hope to optimize updating the global list of actions
/* private class data
*/
typedef struct {
gchar *uuid;
gchar *parm;
NactPivotValue *value;
}
stackItem;
struct NactPivotClassPrivate {
};
/* private instance data
*/
struct NactPivotPrivate {
gboolean dispose_has_run;
/* instance to be notified of an action modification
*/
gpointer notified;
/* list of interface providers
* needs to be in the instance rather than in the class to be able
* to pass NactPivot object to the IO provider, so that the later
......@@ -63,35 +66,48 @@ struct NactPivotPrivate {
GSList *actions;
};
struct NactPivotClassPrivate {
/* private instance properties
*/
enum {
PROP_NOTIFIED = 1
};
#define PROP_NOTIFIED_STR "to-be-notified"
/* signal definition
*/
enum {
ACTION_CHANGED,
LAST_SIGNAL
};
#define SIGNAL_ACTION_CHANGED_NAME "notify_pivot_of_action_changed"
static GObjectClass *st_parent_class = NULL;
static GSList *st_stack_events = NULL;
static gint st_signals[ LAST_SIGNAL ] = { 0 };
static GTimeVal st_last_event;
static guint st_event_source_id = 0;
static gint st_timeout_usec = 500000;
static GType register_type( void );
static void class_init( NactPivotClass *klass );
static void instance_init( GTypeInstance *instance, gpointer klass );
static GSList *register_interface_providers( const NactPivot *pivot );
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 );
static void check_for_remove_action( NactPivot *pivot, NactAction *action );
static gint cmp_events( gconstpointer a, gconstpointer b );
static void free_stack_events( GSList *stack );
static void action_changed_handler( NactPivot *pivot, gpointer user_data );
static void update_action( GSList *actions, NactPivotNotify *notify );
static NactAction *get_action( GSList *list, const gchar *uuid );
static gboolean on_action_changed_timeout( gpointer user_data );
static stackItem *stack_item_new( const gchar *uuid, const gchar *parm, const NactPivotValue *value );
static void stack_item_free( stackItem *item );
static gulong time_val_diff( const GTimeVal *recent, const GTimeVal *old );
static void update_actions( NactPivot *pivot, GSList *stack );
static NactAction *get_action( GSList *list, const gchar *uuid );
NactPivot *
nact_pivot_new( void )
nact_pivot_new( const GObject *target )
{
return( g_object_new( NACT_PIVOT_TYPE, NULL ));
return( g_object_new( NACT_PIVOT_TYPE, PROP_NOTIFIED_STR, target, NULL ));
}
GType
......@@ -135,8 +151,33 @@ class_init( NactPivotClass *klass )
GObjectClass *object_class = G_OBJECT_CLASS( klass );
object_class->dispose = instance_dispose;
object_class->finalize = instance_finalize;
object_class->set_property = instance_set_property;
object_class->get_property = instance_get_property;
GParamSpec *spec;
spec = g_param_spec_pointer(
PROP_NOTIFIED_STR,
PROP_NOTIFIED_STR,
"A pointer to a GObject which will receive action_changed notifications",
G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS | G_PARAM_READWRITE );
g_object_class_install_property( object_class, PROP_NOTIFIED, spec );
klass->private = g_new0( NactPivotClassPrivate, 1 );
/* see nautilus_actions_class_init for why we use this function
*/
st_signals[ ACTION_CHANGED ] = g_signal_new_class_handler(
SIGNAL_ACTION_CHANGED_NAME,
G_TYPE_FROM_CLASS( klass ),
G_SIGNAL_RUN_LAST | G_SIGNAL_NO_RECURSE | G_SIGNAL_NO_HOOKS,
( GCallback ) action_changed_handler,
NULL,
NULL,
g_cclosure_marshal_VOID__POINTER,
G_TYPE_NONE,
1,
G_TYPE_POINTER
);
}
static void
......@@ -154,6 +195,40 @@ instance_init( GTypeInstance *instance, gpointer klass )
self->private->actions = nact_iio_provider_load_actions( G_OBJECT( self ));
}
static void
instance_get_property( GObject *object, guint property_id, GValue *value, GParamSpec *spec )
{
g_assert( NACT_IS_PIVOT( object ));
NactPivot *self = NACT_PIVOT( object );
switch( property_id ){
case PROP_NOTIFIED:
g_value_set_pointer( value, self->private->notified );
break;
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 )
{
g_assert( NACT_IS_PIVOT( object ));
NactPivot *self = NACT_PIVOT( object );
switch( property_id ){
case PROP_NOTIFIED:
self->private->notified = g_value_get_pointer( value );
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID( object, property_id, spec );
break;
}
}
static GSList *
register_interface_providers( const NactPivot *pivot )
{
......@@ -218,6 +293,13 @@ instance_finalize( GObject *object )
/**
* Returns the list of providers of the required interface.
*
* This function is called by interfaces API in order to find the
* list of providers registered for this given interface.
*
* @pivot: this instance.
*
* @type: the type of searched interface.
*/
GSList *
nact_pivot_get_providers( const NactPivot *pivot, GType type )
......@@ -238,136 +320,71 @@ nact_pivot_get_providers( const NactPivot *pivot, GType type )
return( list );
}
/**
* This function should be called when a storage subsystem detects that
* a stored action has changed.
* As a Nautilus extension, NactPivot will take care of updating menu
* characteristics accordingly.
*
* @pivot: the NactPivot object.
*
* @uuid: identifiant of the action.
*
* @parm: the parameter path (e.g. "profile-main/path")
*
* @value: the new value as a NactPivotValue structure ; do not free it
* here as it is the responsability of the allocater subsystem.
*
* Depending of the sort of update which occurs, we may receive many
* notifications for the same action. We so stack the notifications and
* start a one sec. timeout before updating the whole stack.
*/
void
nact_pivot_on_action_changed( NactPivot *pivot, const gchar *uuid, const gchar *parm, NactPivotValue *value )
static void
action_changed_handler( NactPivot *self, gpointer user_data )
{
static const gchar *thisfn = "nact_pivot_on_action_changed";
g_debug( "%s: pivot=%p, uuid='%s', parm='%s', value=%p", thisfn, pivot, uuid, parm, value );
/*static const gchar *thisfn = "nact_pivot_action_changed_handler";
g_debug( "%s: self=%p, data=%p", thisfn, self, user_data );*/
g_assert( NACT_IS_PIVOT( self ));
g_assert( user_data );
if( self->private->dispose_has_run ){
return;
}
stackItem *item = ( stackItem * ) stack_item_new( uuid, parm, value );
st_stack_events = g_slist_prepend( st_stack_events, item );
/* apply the change to the list of actions */
update_action( self->private->actions, ( NactPivotNotify * ) user_data );
/* set a timeout to notify nautilus at the end of the serie */
g_get_current_time( &st_last_event );
if( !st_event_source_id ){
st_event_source_id = g_timeout_add_seconds( 1, ( GSourceFunc ) on_action_changed_timeout, pivot );
st_event_source_id = g_timeout_add_seconds( 1, ( GSourceFunc ) on_action_changed_timeout, self );
}
}
/**
* Duplicate a NactPivotValue structure and its content.
*/
NactPivotValue *
nact_pivot_duplicate_pivot_value( const NactPivotValue *value )
static void
update_action( GSList *actions, NactPivotNotify *notify )
{
if( !value ){
return(( NactPivotValue * ) NULL );
}
NactPivotValue *newvalue = g_new0( NactPivotValue, 1 );
switch( value->type ){
case NACT_PIVOT_STR:
newvalue->data = g_strdup(( gchar * ) value->data );
break;
g_assert( notify );
if( notify->uuid && strlen( notify->uuid )){
case NACT_PIVOT_BOOL:
newvalue->data = value->data;
break;
case NACT_PIVOT_STRLIST:
newvalue->data = nactuti_duplicate_string_list(( GSList * ) value->data );
break;
NactAction *action = get_action( actions, notify->uuid );
g_debug( "nact_pivot_update_action: uuid='%s', parm='%s', action=%p", notify->uuid, notify->parm, action );
if( !action ){
/* this is a creation */
default:
g_assert_not_reached();
break;
} else {
/* this is an update or a deletion */
}
}
return( newvalue );
nact_pivot_free_notify( notify );
}
/**
* Free a NactPivotValue structure and its content.
*/
void
nact_pivot_free_pivot_value( NactPivotValue *value )
static NactAction *
get_action( GSList *list, const gchar *uuid )
{
if( value ){
switch( value->type ){
case NACT_PIVOT_STR:
g_free(( gchar * ) value->data );
break;
case NACT_PIVOT_BOOL:
break;
case NACT_PIVOT_STRLIST:
nactuti_free_string_list(( GSList * ) value->data );
break;
default:
g_assert_not_reached();
break;
NactAction *found = NULL;
GSList *ia;
for( ia = list ; ia && !found ; ia = ia->next ){
NactAction *action = ( NactAction * ) ia->data;
gchar *id = nact_action_get_uuid( action );
if( !g_strcmp0( id, uuid )){
found = action;
}
g_free( value );
}
}
static void
check_for_remove_action( NactPivot *pivot, NactAction *action )
{
static const gchar *thisfn ="check_for_remove_action";
g_assert( NACT_IS_PIVOT( pivot ));
g_assert( NACT_IS_ACTION( action ));
if( nact_action_is_empty( action )){
g_debug( "%s: removing action %p", thisfn, action );
pivot->private->actions = g_slist_remove( pivot->private->actions, action );
g_free( id );
}
return( found );
}
/*
* comparaison function between two stack items
/**
* Returns the searched NactAction, or NULL.
*/
static gint
cmp_events( gconstpointer a, gconstpointer b )
{
stackItem *sa = ( stackItem * ) a;
stackItem *sb = ( stackItem * ) b;
return( g_strcmp0( sa->uuid, sb->uuid ));
}
static void
free_stack_events( GSList *stack )
GObject *
nact_pivot_get_action( NactPivot *pivot, const gchar *uuid )
{
GSList *is;
for( is = stack ; is ; is = is->next ){
stack_item_free(( stackItem * ) is->data );
}
g_slist_free( stack );
g_assert( NACT_IS_PIVOT( pivot ));
return( G_OBJECT( get_action( pivot->private->actions, uuid )));
}
/*
......@@ -376,47 +393,29 @@ free_stack_events( GSList *stack )
* second old
*
* there is no race condition here as we are not multithreaded
* or .. is there ?
*/
static gboolean
on_action_changed_timeout( gpointer user_data )
{
static const gchar *thisfn = "on_action_changed_timeout";
/*static const gchar *thisfn = "nact_pivot_on_action_changed_timeout";
g_debug( "%s: pivot=%p", thisfn, user_data );*/
GTimeVal now;
g_assert( NACT_IS_PIVOT( user_data ));
NactPivot *pivot = NACT_PIVOT( user_data );
g_get_current_time( &now );
gulong diff = time_val_diff( &now, &st_last_event );
if( diff < 500000 ){
if( diff < st_timeout_usec ){
return( TRUE );
}
g_debug( "%s: treating stack with %d events", thisfn, g_slist_length( st_stack_events ));
update_actions( NACT_PIVOT( user_data ), st_stack_events );
g_signal_emit_by_name( G_OBJECT( pivot->private->notified ), "notify_nautilus_of_action_changed" );
st_event_source_id = 0;
free_stack_events( st_stack_events );
st_stack_events = NULL;
return( FALSE );
}
static stackItem *
stack_item_new( const gchar *uuid, const gchar *parm, const NactPivotValue *value )
{
stackItem *item = g_new0( stackItem, 1 );
item->uuid = g_strdup( uuid );
item->parm = g_strdup( parm );
item->value = nact_pivot_duplicate_pivot_value( value );
return( item );
}
static void
stack_item_free( stackItem *item )
{
g_free( item->uuid );
g_free( item->parm );
nact_pivot_free_pivot_value( item->value );
g_free( item );
return( FALSE );
}
/*
......@@ -430,70 +429,35 @@ time_val_diff( const GTimeVal *recent, const GTimeVal *old )
return( microsec );
}
/*
* iterate through the list of events, sorted by action id
* on new action, add it to the list, creating the object
* when all events have been treated, check to see if the action was
* actually removed (all fields, including key, are blank or null)
*
* remove = key + parm=null and value=null
/**
* Free a NactPivotValue structure and its content.
*/
static void
update_actions( NactPivot *pivot, GSList *stack )
{
GSList *it;
NactAction *action = NULL;
gchar *previd = NULL;
GSList *sorted = g_slist_sort( stack, cmp_events );
for( it = sorted ; it ; it = it->next ){
stackItem *item = ( stackItem * ) it->data;
if( action && g_strcmp0( previd, item->uuid )){
g_assert( action && NACT_IS_ACTION( action ));
check_for_remove_action( pivot, action );
g_free( previd );
}
previd = g_strdup( item->uuid );
action = get_action( pivot->private->actions, item->uuid );
if( action ){
nact_action_update( action, item->parm, item->value );
} else {
action = nact_action_create( item->uuid, item->parm, item->value );
pivot->private->actions = g_slist_prepend( pivot->private->actions, action );
}
}
if( action ){
g_assert( action && NACT_IS_ACTION( action ));
check_for_remove_action( pivot, action );
g_free( previd );
}
}
static NactAction *
get_action( GSList *list, const gchar *uuid )
void
nact_pivot_free_notify( NactPivotNotify *npn )
{
NactAction *found = NULL;
GSList *ia;
for( ia = list ; ia && !found ; ia = ia->next ){
NactAction *action = ( NactAction * ) ia->data;
gchar *id = nact_action_get_uuid( action );
if( !g_strcmp0( id, uuid )){
found = action;
if( npn ){