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 <gtk/gtk.h>
35 #include <string.h>
36 
37 #include <api/na-object-api.h>
38 
39 #include <core/na-exporter.h>
40 #include <core/na-export-format.h>
41 
42 #include "nact-application.h"
43 #include "nact-export-ask.h"
44 #include "nact-tree-model.h"
45 #include "nact-clipboard.h"
46 
47 /* private class data
48  */
49 struct _NactClipboardClassPrivate {
50 	void *empty;						/* so that gcc -pedantic is happy */
51 };
52 
53 /* private instance data
54  */
55 typedef struct {
56 	guint    target;
57 	gchar   *folder;
58 	GList   *rows;
59 	gboolean copy;
60 }
61 	NactClipboardDndData;
62 
63 typedef struct {
64 	GList *items;
65 	gint   mode;
66 	guint  nb_actions;
67 	guint  nb_profiles;
68 	guint  nb_menus;
69 }
70 	PrimaryData;
71 
72 struct _NactClipboardPrivate {
73 	gboolean      dispose_has_run;
74 	BaseWindow   *window;
75 	GtkClipboard *dnd;
76 	GtkClipboard *primary;
77 	PrimaryData  *primary_data;
78 	gboolean      primary_got;
79 };
80 
81 #define NACT_CLIPBOARD_ATOM				gdk_atom_intern( "_NACT_CLIPBOARD", FALSE )
82 #define NACT_CLIPBOARD_NACT_ATOM		gdk_atom_intern( "ClipboardNautilusActions", FALSE )
83 
84 enum {
85 	NACT_CLIPBOARD_FORMAT_NACT = 0,
86 	NACT_CLIPBOARD_FORMAT_APPLICATION_XML,
87 	NACT_CLIPBOARD_FORMAT_TEXT_PLAIN
88 };
89 
90 /* clipboard formats
91  * - a special ClipboardNautilusAction format for internal move/copy
92  *   and also used by drag and drop operations
93  * - a XdndDirectSave, suitable for exporting to a file manager
94  *   (note that Nautilus recognized the "XdndDirectSave0" format as XDS
95  *   protocol)
96  * - a text (xml) format, to go to clipboard or a text editor
97  */
98 static GtkTargetEntry clipboard_formats[] = {
99 	{ "ClipboardNautilusActions", 0, NACT_CLIPBOARD_FORMAT_NACT },
100 	{ "application/xml",          0, NACT_CLIPBOARD_FORMAT_APPLICATION_XML },
101 	{ "text/plain",               0, NACT_CLIPBOARD_FORMAT_TEXT_PLAIN },
102 };
103 
104 static GObjectClass *st_parent_class = NULL;
105 
106 static GType  register_type( void );
107 static void   class_init( NactClipboardClass *klass );
108 static void   instance_init( GTypeInstance *instance, gpointer klass );
109 static void   instance_dispose( GObject *application );
110 static void   instance_finalize( GObject *application );
111 
112 static void   get_from_dnd_clipboard_callback( GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, guchar *data );
113 static void   clear_dnd_clipboard_callback( GtkClipboard *clipboard, NactClipboardDndData *data );
114 static gchar *export_rows( NactClipboard *clipboard, GList *rows, const gchar *dest_folder );
115 static gchar *export_objects( NactClipboard *clipboard, GList *objects );
116 static gchar *export_row_object( NactClipboard *clipboard, NAObject *object, const gchar *dest_folder, GList **exported, gboolean first );
117 
118 static void   get_from_primary_clipboard_callback( GtkClipboard *gtk_clipboard, GtkSelectionData *selection_data, guint info, NactClipboard *clipboard );
119 static void   clear_primary_clipboard( NactClipboard *clipboard );
120 static void   clear_primary_clipboard_callback( GtkClipboard *gtk_clipboard, NactClipboard *clipboard );
121 static void   dump_primary_clipboard( NactClipboard *clipboard );
122 
123 static gchar *clipboard_mode_to_string( gint mode );
124 
125 GType
nact_clipboard_get_type(void)126 nact_clipboard_get_type( void )
127 {
128 	static GType type = 0;
129 
130 	if( !type ){
131 		type = register_type();
132 	}
133 
134 	return( type );
135 }
136 
137 static GType
register_type(void)138 register_type( void )
139 {
140 	static const gchar *thisfn = "nact_clipboard_register_type";
141 	GType type;
142 
143 	static GTypeInfo info = {
144 		sizeof( NactClipboardClass ),
145 		( GBaseInitFunc ) NULL,
146 		( GBaseFinalizeFunc ) NULL,
147 		( GClassInitFunc ) class_init,
148 		NULL,
149 		NULL,
150 		sizeof( NactClipboard ),
151 		0,
152 		( GInstanceInitFunc ) instance_init
153 	};
154 
155 	g_debug( "%s", thisfn );
156 
157 	type = g_type_register_static( G_TYPE_OBJECT, "NactClipboard", &info, 0 );
158 
159 	return( type );
160 }
161 
162 static void
class_init(NactClipboardClass * klass)163 class_init( NactClipboardClass *klass )
164 {
165 	static const gchar *thisfn = "nact_clipboard_class_init";
166 	GObjectClass *object_class;
167 
168 	g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
169 
170 	st_parent_class = g_type_class_peek_parent( klass );
171 
172 	object_class = G_OBJECT_CLASS( klass );
173 	object_class->dispose = instance_dispose;
174 	object_class->finalize = instance_finalize;
175 
176 	klass->private = g_new0( NactClipboardClassPrivate, 1 );
177 }
178 
179 static void
instance_init(GTypeInstance * instance,gpointer klass)180 instance_init( GTypeInstance *instance, gpointer klass )
181 {
182 	static const gchar *thisfn = "nact_clipboard_instance_init";
183 	NactClipboard *self;
184 	GdkDisplay *display;
185 
186 	g_return_if_fail( NACT_IS_CLIPBOARD( instance ));
187 
188 	g_debug( "%s: instance=%p (%s), klass=%p",
189 			thisfn, ( void * ) instance, G_OBJECT_TYPE_NAME( instance ), ( void * ) klass );
190 
191 	self = NACT_CLIPBOARD( instance );
192 
193 	self->private = g_new0( NactClipboardPrivate, 1 );
194 
195 	self->private->dispose_has_run = FALSE;
196 
197 	display = gdk_display_get_default();
198 	self->private->dnd = gtk_clipboard_get_for_display( display, NACT_CLIPBOARD_ATOM );
199 	self->private->primary = gtk_clipboard_get_for_display( display, GDK_SELECTION_CLIPBOARD );
200 	self->private->primary_data = NULL;
201 }
202 
203 static void
instance_dispose(GObject * object)204 instance_dispose( GObject *object )
205 {
206 	static const gchar *thisfn = "nact_clipboard_instance_dispose";
207 	NactClipboard *self;
208 
209 	g_return_if_fail( NACT_IS_CLIPBOARD( object ));
210 
211 	self = NACT_CLIPBOARD( object );
212 
213 	if( !self->private->dispose_has_run ){
214 
215 		g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));
216 
217 		self->private->dispose_has_run = TRUE;
218 
219 		gtk_clipboard_clear( self->private->dnd );
220 		gtk_clipboard_clear( self->private->primary );
221 
222 		/* chain up to the parent class */
223 		if( G_OBJECT_CLASS( st_parent_class )->dispose ){
224 			G_OBJECT_CLASS( st_parent_class )->dispose( object );
225 		}
226 	}
227 }
228 
229 static void
instance_finalize(GObject * instance)230 instance_finalize( GObject *instance )
231 {
232 	static const gchar *thisfn = "nact_clipboard_instance_finalize";
233 	NactClipboard *self;
234 
235 	g_return_if_fail( NACT_IS_CLIPBOARD( instance ));
236 
237 	g_debug( "%s: instance=%p (%s)", thisfn, ( void * ) instance, G_OBJECT_TYPE_NAME( instance ));
238 
239 	self = NACT_CLIPBOARD( instance );
240 
241 	if( self->private->primary_data ){
242 		clear_primary_clipboard( self );
243 		g_free( self->private->primary_data );
244 	}
245 
246 	g_free( self->private );
247 
248 	/* chain call to parent class */
249 	if( G_OBJECT_CLASS( st_parent_class )->finalize ){
250 		G_OBJECT_CLASS( st_parent_class )->finalize( instance );
251 	}
252 }
253 
254 /**
255  * nact_clipboard_new:
256  *
257  * Returns: a new #NactClipboard object.
258  */
259 NactClipboard *
nact_clipboard_new(BaseWindow * window)260 nact_clipboard_new( BaseWindow *window )
261 {
262 	NactClipboard *clipboard;
263 
264 	g_return_val_if_fail( BASE_IS_WINDOW( window ), NULL );
265 
266 	clipboard = g_object_new( NACT_TYPE_CLIPBOARD, NULL );
267 
268 	clipboard->private->window = window;
269 
270 	return( clipboard );
271 }
272 
273 /**
274  * nact_clipboard_dnd_set:
275  * @clipboard: this #NactClipboard instance.
276  * @rows: the list of row references of dragged items.
277  * @folder: the URI of the target folder if any (XDS protocol to outside).
278  * @copy_data: %TRUE if data is to be copied, %FALSE else
279  *  (only relevant when drag and drop occurs inside of the tree view).
280  *
281  * Set the selected items into our dnd clipboard.
282  */
283 void
nact_clipboard_dnd_set(NactClipboard * clipboard,guint target,GList * rows,const gchar * folder,gboolean copy_data)284 nact_clipboard_dnd_set( NactClipboard *clipboard, guint target, GList *rows, const gchar *folder, gboolean copy_data )
285 {
286 	static const gchar *thisfn = "nact_clipboard_dnd_set";
287 	NactClipboardDndData *data;
288 	GList *it;
289 
290 	g_return_if_fail( NACT_IS_CLIPBOARD( clipboard ));
291 	g_return_if_fail( rows && g_list_length( rows ));
292 
293 	if( !clipboard->private->dispose_has_run ){
294 
295 		data = g_new0( NactClipboardDndData, 1 );
296 
297 		data->target = target;
298 		data->folder = g_strdup( folder );
299 		data->rows = NULL;
300 		data->copy = copy_data;
301 
302 		for( it = rows ; it ; it = it->next ){
303 			data->rows = g_list_append(
304 					data->rows,
305 					gtk_tree_row_reference_copy(( GtkTreeRowReference * ) it->data ));
306 		}
307 
308 		gtk_clipboard_set_with_data( clipboard->private->dnd,
309 				clipboard_formats, G_N_ELEMENTS( clipboard_formats ),
310 				( GtkClipboardGetFunc ) get_from_dnd_clipboard_callback,
311 				( GtkClipboardClearFunc ) clear_dnd_clipboard_callback,
312 				data );
313 
314 		g_debug( "%s: clipboard=%p, data=%p", thisfn, ( void * ) clipboard, ( void * ) data );
315 	}
316 }
317 
318 /**
319  * nact_clipboard_dnd_get_data:
320  * @clipboard: this #NactClipboard instance.
321  * @copy_data: will be set to the original value of the drag and drop.
322  *
323  * Returns the list of rows references privously stored.
324  *
325  * The returned list should be gtk_tree_row_reference_free() by the
326  * caller.
327  */
328 GList *
nact_clipboard_dnd_get_data(NactClipboard * clipboard,gboolean * copy_data)329 nact_clipboard_dnd_get_data( NactClipboard *clipboard, gboolean *copy_data )
330 {
331 	static const gchar *thisfn = "nact_clipboard_dnd_get_data";
332 	GList *rows = NULL;
333 	GtkSelectionData *selection;
334 	NactClipboardDndData *data;
335 	GList *it;
336 
337 	g_debug( "%s: clipboard=%p", thisfn, ( void * ) clipboard );
338 	g_return_val_if_fail( NACT_IS_CLIPBOARD( clipboard ), NULL );
339 
340 	if( copy_data ){
341 		*copy_data = FALSE;
342 	}
343 
344 	if( !clipboard->private->dispose_has_run ){
345 
346 		selection = gtk_clipboard_wait_for_contents( clipboard->private->dnd, NACT_CLIPBOARD_NACT_ATOM );
347 		if( selection ){
348 			data = ( NactClipboardDndData * ) gtk_selection_data_get_data( selection );
349 
350 			if( data->target == NACT_XCHANGE_FORMAT_NACT ){
351 				for( it = data->rows ; it ; it = it->next ){
352 					rows = g_list_append( rows,
353 							gtk_tree_row_reference_copy(( GtkTreeRowReference * ) it->data ));
354 				}
355 				*copy_data = data->copy;
356 			}
357 		}
358 		gtk_selection_data_free( selection );
359 	}
360 
361 	return( rows );
362 }
363 
364 /*
365  * Get text/plain from selected actions.
366  *
367  * This is called when we drop or paste a selection onto an application
368  * willing to deal with Xdnd protocol, for text/plain or application/xml
369  * mime types.
370  *
371  * Selected items may include menus, actions and profiles.
372  * For now, we only exports actions (and profiles) as XML files.
373  *
374  * FIXME: na_xml_writer_get_xml_buffer() returns a valid XML document,
375  * which includes the <?xml ...?> header. Concatenating several valid
376  * XML documents doesn't provide a valid global XML doc, because of
377  * the presence of several <?xml ?> headers inside.
378  */
379 gchar *
nact_clipboard_dnd_get_text(NactClipboard * clipboard,GList * rows)380 nact_clipboard_dnd_get_text( NactClipboard *clipboard, GList *rows )
381 {
382 	static const gchar *thisfn = "nact_clipboard_dnd_get_text";
383 	gchar *buffer;
384 
385 	g_return_val_if_fail( NACT_IS_CLIPBOARD( clipboard ), NULL );
386 
387 	g_debug( "%s: clipboard=%p, rows=%p (count=%u)",
388 			thisfn, ( void * ) clipboard, ( void * ) rows, g_list_length( rows ));
389 
390 	buffer = NULL;
391 
392 	if( !clipboard->private->dispose_has_run ){
393 
394 		buffer = export_rows( clipboard, rows, NULL );
395 		g_debug( "%s: returning buffer=%p (length=%lu)", thisfn, ( void * ) buffer, g_utf8_strlen( buffer, -1 ));
396 	}
397 
398 	return( buffer );
399 }
400 
401 /**
402  * nact_clipboard_dnd_drag_end:
403  * @clipboard: this #NactClipboard instance.
404  *
405  * On drag-end, exports the objects if needed.
406  */
407 void
nact_clipboard_dnd_drag_end(NactClipboard * clipboard)408 nact_clipboard_dnd_drag_end( NactClipboard *clipboard )
409 {
410 	static const gchar *thisfn = "nact_clipboard_dnd_drag_end";
411 	GtkSelectionData *selection;
412 	NactClipboardDndData *data;
413 	gchar *buffer;
414 
415 	g_debug( "%s: clipboard=%p", thisfn, ( void * ) clipboard );
416 	g_return_if_fail( NACT_IS_CLIPBOARD( clipboard ));
417 
418 	if( !clipboard->private->dispose_has_run ){
419 
420 		selection = gtk_clipboard_wait_for_contents( clipboard->private->dnd, NACT_CLIPBOARD_NACT_ATOM );
421 		g_debug( "%s: selection=%p", thisfn, ( void * ) selection );
422 
423 		if( selection ){
424 			data = ( NactClipboardDndData * ) gtk_selection_data_get_data( selection );
425 			g_debug( "%s: data=%p (NactClipboardDndData)", thisfn, ( void * ) data );
426 
427 			if( data->target == NACT_XCHANGE_FORMAT_XDS ){
428 				g_debug( "%s: folder=%s", thisfn, data->folder );
429 				buffer = export_rows( clipboard, data->rows, data->folder );
430 				g_free( buffer );
431 			}
432 
433 			gtk_selection_data_free( selection );
434 		}
435 	}
436 }
437 
438 /**
439  * nact_clipboard_dnd_clear:
440  * @clipboard: this #NactClipboard instance.
441  *
442  * Clears the drag-and-drop clipboard.
443  *
444  * At least called on drag-begin.
445  */
446 void
nact_clipboard_dnd_clear(NactClipboard * clipboard)447 nact_clipboard_dnd_clear( NactClipboard *clipboard )
448 {
449 	g_debug( "nact_clipboard_dnd_clear: clipboard=%p", ( void * ) clipboard );
450 
451 	gtk_clipboard_clear( clipboard->private->dnd );
452 }
453 
454 static void
get_from_dnd_clipboard_callback(GtkClipboard * clipboard,GtkSelectionData * selection_data,guint info,guchar * data)455 get_from_dnd_clipboard_callback( GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, guchar *data )
456 {
457 	static const gchar *thisfn = "nact_clipboard_get_from_dnd_clipboard_callback";
458 	GdkAtom selection_data_target;
459 
460 	selection_data_target = gtk_selection_data_get_target( selection_data );
461 
462 	g_debug( "%s: clipboard=%p, selection_data=%p, target=%s, info=%d, data=%p",
463 			thisfn, ( void * ) clipboard,
464 			( void * ) selection_data, gdk_atom_name( selection_data_target ), info, ( void * ) data );
465 
466 	gtk_selection_data_set( selection_data,
467 			selection_data_target, 8, data, sizeof( NactClipboardDndData ));
468 }
469 
470 static void
clear_dnd_clipboard_callback(GtkClipboard * clipboard,NactClipboardDndData * data)471 clear_dnd_clipboard_callback( GtkClipboard *clipboard, NactClipboardDndData *data )
472 {
473 	static const gchar *thisfn = "nact_clipboard_clear_dnd_clipboard_callback";
474 
475 	g_debug( "%s: clipboard=%p, data=%p", thisfn, ( void * ) clipboard, ( void * ) data );
476 
477 	g_free( data->folder );
478 	g_list_foreach( data->rows, ( GFunc ) gtk_tree_row_reference_free, NULL );
479 	g_list_free( data->rows );
480 	g_free( data );
481 }
482 
483 /*
484  * returns a buffer which contains all exported items if dest_folder is null
485  * else export items as files to target directory, returning an empty string
486  */
487 static gchar *
export_rows(NactClipboard * clipboard,GList * rows,const gchar * dest_folder)488 export_rows( NactClipboard *clipboard, GList *rows, const gchar *dest_folder )
489 {
490 	static const gchar *thisfn = "nact_clipboard_export_rows";
491 	GString *data;
492 	GtkTreeModel *model;
493 	GList *exported, *irow;
494 	GtkTreePath *path;
495 	GtkTreeIter iter;
496 	NAObject *object;
497 	gchar *buffer;
498 	gboolean first;
499 
500 	g_debug( "%s: clipboard=%p, rows=%p (count=%d), dest_folder=%s",
501 			thisfn, ( void * ) clipboard, ( void * ) rows, g_list_length( rows ), dest_folder );
502 
503 	first = TRUE;
504 	buffer = NULL;
505 	exported = NULL;
506 	data = g_string_new( "" );
507 	model = gtk_tree_row_reference_get_model(( GtkTreeRowReference * ) rows->data );
508 
509 	for( irow = rows ; irow ; irow = irow->next ){
510 		path = gtk_tree_row_reference_get_path(( GtkTreeRowReference * ) irow->data );
511 		if( path ){
512 			gtk_tree_model_get_iter( model, &iter, path );
513 			gtk_tree_path_free( path );
514 			gtk_tree_model_get( model, &iter, TREE_COLUMN_NAOBJECT, &object, -1 );
515 			buffer = export_row_object( clipboard, object, dest_folder, &exported, first );
516 			if( buffer && strlen( buffer )){
517 				data = g_string_append( data, buffer );
518 				g_free( buffer );
519 			}
520 			g_object_unref( object );
521 		}
522 		first = FALSE;
523 	}
524 
525 	g_list_free( exported );
526 	return( g_string_free( data, FALSE ));
527 }
528 
529 static gchar *
export_objects(NactClipboard * clipboard,GList * objects)530 export_objects( NactClipboard *clipboard, GList *objects )
531 {
532 	gchar *buffer;
533 	GString *data;
534 	GList *exported;
535 	GList *iobj;
536 	NAObject *object;
537 	gboolean first;
538 
539 	first = TRUE;
540 	buffer = NULL;
541 	exported = NULL;
542 	data = g_string_new( "" );
543 
544 	for( iobj = objects ; iobj ; iobj = iobj->next ){
545 		object = NA_OBJECT( iobj->data );
546 		buffer = export_row_object( clipboard, object, NULL, &exported, first );
547 		if( buffer && strlen( buffer )){
548 			data = g_string_append( data, buffer );
549 			g_free( buffer );
550 		}
551 		g_object_unref( object );
552 		first = FALSE;
553 	}
554 
555 	g_list_free( exported );
556 	return( g_string_free( data, FALSE ));
557 }
558 
559 /*
560  * export to a buffer if dest_folder is null, and returns this buffer
561  * else export to a new file in the target directory (returning an empty string)
562  *
563  * exported maintains a list of exported items, so that the same item is not
564  * exported twice
565  */
566 static gchar *
export_row_object(NactClipboard * clipboard,NAObject * object,const gchar * dest_folder,GList ** exported,gboolean first)567 export_row_object( NactClipboard *clipboard, NAObject *object, const gchar *dest_folder, GList **exported, gboolean first )
568 {
569 	static const gchar *thisfn = "nact_clipboard_export_row_object";
570 	GList *subitems, *isub;
571 	NactApplication *application;
572 	NAUpdater *updater;
573 	NAObjectItem *item;
574 	gchar *item_label;
575 	gint index;
576 	GString *data;
577 	gchar *buffer;
578 	gchar *format;
579 	gchar *fname;
580 	GSList *msgs;
581 
582 	data = g_string_new( "" );
583 
584 	/* if we have a menu, first export the subitems
585 	 */
586 	if( NA_IS_OBJECT_MENU( object )){
587 		subitems = na_object_get_items( object );
588 
589 		for( isub = subitems ; isub ; isub = isub->next ){
590 			buffer = export_row_object( clipboard, isub->data, dest_folder, exported, first );
591 			if( buffer && strlen( buffer )){
592 				data = g_string_append( data, buffer );
593 				g_free( buffer );
594 			}
595 			first = FALSE;
596 		}
597 	}
598 
599 	/* only export NAObjectItem type
600 	 * here, object may be a menu, an action or a profile
601 	 */
602 	msgs = NULL;
603 	item = ( NAObjectItem * ) object;
604 	if( NA_IS_OBJECT_PROFILE( object )){
605 		item = NA_OBJECT_ITEM( na_object_get_parent( object ));
606 	}
607 
608 	application = NACT_APPLICATION( base_window_get_application( clipboard->private->window ));
609 	updater = nact_application_get_updater( application );
610 
611 	index = g_list_index( *exported, ( gconstpointer ) item );
612 	if( index == -1 ){
613 
614 		item_label = na_object_get_label( item );
615 		g_debug( "%s: exporting %s", thisfn, item_label );
616 		g_free( item_label );
617 
618 		*exported = g_list_prepend( *exported, ( gpointer ) item );
619 		format = na_settings_get_string( NA_IPREFS_EXPORT_PREFERRED_FORMAT, NULL, NULL );
620 		g_return_val_if_fail( format && strlen( format ), NULL );
621 
622 		if( !strcmp( format, EXPORTER_FORMAT_ASK )){
623 			g_free( format );
624 			format = nact_export_ask_user( clipboard->private->window, item, first );
625 			g_return_val_if_fail( format && strlen( format ), NULL );
626 		}
627 
628 		if( strcmp( format, EXPORTER_FORMAT_NOEXPORT ) != 0 ){
629 			if( dest_folder ){
630 				fname = na_exporter_to_file( NA_PIVOT( updater ), item, dest_folder, format, &msgs );
631 				g_free( fname );
632 
633 			} else {
634 				buffer = na_exporter_to_buffer( NA_PIVOT( updater ), item, format, &msgs );
635 				if( buffer && strlen( buffer )){
636 					data = g_string_append( data, buffer );
637 					g_free( buffer );
638 				}
639 			}
640 		}
641 
642 		g_free( format );
643 	}
644 
645 	return( g_string_free( data, FALSE ));
646 }
647 
648 /**
649  * nact_clipboard_primary_set:
650  * @clipboard: this #NactClipboard object.
651  * @items: a list of #NAObject items
652  * @mode: where do these items come from ?
653  *  Or what is the operation which has led the items to the clipboard?
654  *
655  * Installs a copy of provided items in the clipboard.
656  *
657  * Rationale: when cutting an item to the clipboard, the next paste
658  * will keep its same original id, and it is safe because this is
659  * actually what we want when we cut/paste.
660  *
661  * Contrarily, when we copy/paste, we are waiting for a new element
662  * which has the same characteristics that the previous one ; we so
663  * have to renumber actions/menus items when copying into the clipboard.
664  *
665  * Note that we use NAIDuplicable interface without actually taking care
666  * of what is origin or so, as origin will be reinitialized when getting
667  * data out of the clipboard.
668  */
669 void
nact_clipboard_primary_set(NactClipboard * clipboard,GList * items,gint mode)670 nact_clipboard_primary_set( NactClipboard *clipboard, GList *items, gint mode )
671 {
672 	static const gchar *thisfn = "nact_clipboard_primary_set";
673 	PrimaryData *user_data;
674 	GList *it;
675 
676 	g_debug( "%s: clipboard=%p, items=%p (count=%d), mode=%d",
677 			thisfn, ( void * ) clipboard, ( void * ) items, g_list_length( items ), mode );
678 	g_return_if_fail( NACT_IS_CLIPBOARD( clipboard ));
679 
680 	if( !clipboard->private->dispose_has_run ){
681 
682 		user_data = clipboard->private->primary_data;
683 
684 		if( user_data == NULL ){
685 			user_data = g_new0( PrimaryData, 1 );
686 			clipboard->private->primary_data = user_data;
687 			g_debug( "%s: allocating PrimaryData=%p", thisfn, ( void * ) user_data );
688 
689 		} else {
690 			clear_primary_clipboard( clipboard );
691 		}
692 
693 		na_object_item_count_items( items,
694 				( gint * ) &user_data->nb_menus,
695 				( gint * ) &user_data->nb_actions,
696 				( gint * ) &user_data->nb_profiles,
697 				FALSE );
698 
699 		for( it = items ; it ; it = it->next ){
700 			user_data->items =
701 					g_list_prepend( user_data->items, na_object_duplicate( it->data, DUPLICATE_REC ));
702 		}
703 		user_data->items = g_list_reverse( user_data->items );
704 
705 		user_data->mode = mode;
706 
707 		gtk_clipboard_set_with_data( clipboard->private->primary,
708 				clipboard_formats, G_N_ELEMENTS( clipboard_formats ),
709 				( GtkClipboardGetFunc ) get_from_primary_clipboard_callback,
710 				( GtkClipboardClearFunc ) clear_primary_clipboard_callback,
711 				clipboard );
712 
713 		clipboard->private->primary_got = FALSE;
714 	}
715 }
716 
717 /**
718  * nact_clipboard_primary_get:
719  * @clipboard: this #NactClipboard object.
720  *
721  * Returns: a copy of the list of items previously referenced in the
722  * internal clipboard.
723  *
724  * We allocate a new id for items in order to be ready to paste another
725  * time.
726  */
727 GList *
nact_clipboard_primary_get(NactClipboard * clipboard,gboolean * relabel)728 nact_clipboard_primary_get( NactClipboard *clipboard, gboolean *relabel )
729 {
730 	static const gchar *thisfn = "nact_clipboard_primary_get";
731 	GList *items = NULL;
732 	GtkSelectionData *selection;
733 	PrimaryData *user_data;
734 	GList *it;
735 	NAObject *obj;
736 
737 	g_debug( "%s: clipboard=%p", thisfn, ( void * ) clipboard );
738 	g_return_val_if_fail( NACT_IS_CLIPBOARD( clipboard ), NULL );
739 	g_return_val_if_fail( relabel, NULL );
740 
741 	if( !clipboard->private->dispose_has_run ){
742 
743 		selection = gtk_clipboard_wait_for_contents( clipboard->private->primary, NACT_CLIPBOARD_NACT_ATOM );
744 
745 		if( selection ){
746 			user_data = ( PrimaryData * ) gtk_selection_data_get_data( selection );
747 			g_debug( "%s: retrieving PrimaryData=%p", thisfn, ( void * ) user_data );
748 
749 			if( user_data ){
750 				for( it = user_data->items ; it ; it = it->next ){
751 					obj = NA_OBJECT( na_object_duplicate( it->data, DUPLICATE_REC ));
752 					na_object_set_origin( obj, NULL );
753 					items = g_list_prepend( items, obj );
754 				}
755 				items = g_list_reverse( items );
756 
757 				*relabel = (( user_data->mode == CLIPBOARD_MODE_CUT && clipboard->private->primary_got ) ||
758 								user_data->mode == CLIPBOARD_MODE_COPY );
759 
760 				clipboard->private->primary_got = TRUE;
761 			}
762 
763 			gtk_selection_data_free( selection );
764 		}
765 	}
766 
767 	return( items );
768 }
769 
770 /**
771  * nact_clipboard_primary_counts:
772  * @clipboard: this #NactClipboard object.
773  *
774  * Returns some counters on content of primary clipboard.
775  */
776 void
nact_clipboard_primary_counts(NactClipboard * clipboard,guint * actions,guint * profiles,guint * menus)777 nact_clipboard_primary_counts( NactClipboard *clipboard, guint *actions, guint *profiles, guint *menus )
778 {
779 	PrimaryData *user_data;
780 
781 	g_return_if_fail( NACT_IS_CLIPBOARD( clipboard ));
782 	g_return_if_fail( actions && profiles && menus );
783 
784 	if( !clipboard->private->dispose_has_run ){
785 
786 		*actions = 0;
787 		*profiles = 0;
788 		*menus = 0;
789 
790 		user_data = clipboard->private->primary_data;
791 		if( user_data ){
792 
793 			*actions = user_data->nb_actions;
794 			*profiles = user_data->nb_profiles;
795 			*menus = user_data->nb_menus;
796 		}
797 	}
798 }
799 
800 static void
get_from_primary_clipboard_callback(GtkClipboard * gtk_clipboard,GtkSelectionData * selection_data,guint info,NactClipboard * clipboard)801 get_from_primary_clipboard_callback( GtkClipboard *gtk_clipboard, GtkSelectionData *selection_data, guint info, NactClipboard *clipboard )
802 {
803 	static const gchar *thisfn = "nact_clipboard_get_from_primary_clipboard_callback";
804 	PrimaryData *user_data;
805 	gchar *buffer;
806 	GdkAtom selection_data_target;
807 
808 	selection_data_target = gtk_selection_data_get_target( selection_data );
809 
810 	g_debug( "%s: gtk_clipboard=%p, selection_data=%p, target=%s, info=%d, clipboard=%p",
811 			thisfn, ( void * ) gtk_clipboard,
812 			( void * ) selection_data, gdk_atom_name( selection_data_target ), info, ( void * ) clipboard );
813 
814 	user_data = clipboard->private->primary_data;
815 
816 	if( info == NACT_CLIPBOARD_FORMAT_TEXT_PLAIN ){
817 		buffer = export_objects( clipboard, user_data->items );
818 		gtk_selection_data_set( selection_data,
819 				selection_data_target, 8, ( const guchar * ) buffer, strlen( buffer ));
820 		g_free( buffer );
821 
822 	} else {
823 		gtk_selection_data_set( selection_data,
824 				selection_data_target, 8, ( const guchar * ) user_data, sizeof( PrimaryData ));
825 	}
826 }
827 
828 static void
clear_primary_clipboard(NactClipboard * clipboard)829 clear_primary_clipboard( NactClipboard *clipboard )
830 {
831 	static const gchar *thisfn = "nact_clipboard_clear_primary_clipboard";
832 	PrimaryData *user_data;
833 
834 	g_debug( "%s: clipboard=%p", thisfn, ( void * ) clipboard );
835 
836 	user_data = clipboard->private->primary_data;
837 	g_return_if_fail( user_data != NULL );
838 
839 	g_list_foreach( user_data->items, ( GFunc ) g_object_unref, NULL );
840 	g_list_free( user_data->items );
841 	user_data->items = NULL;
842 	user_data->nb_menus = 0;
843 	user_data->nb_actions = 0;
844 	user_data->nb_profiles = 0;
845 
846 	clipboard->private->primary_got = FALSE;
847 }
848 
849 static void
clear_primary_clipboard_callback(GtkClipboard * gtk_clipboard,NactClipboard * clipboard)850 clear_primary_clipboard_callback( GtkClipboard *gtk_clipboard, NactClipboard *clipboard )
851 {
852 }
853 
854 /**
855  * nact_clipboard_dump:
856  * @clipboard: this #NactClipboard instance.
857  *
858  * Dumps the content of the primary clipboard.
859  */
860 void
nact_clipboard_dump(NactClipboard * clipboard)861 nact_clipboard_dump( NactClipboard *clipboard )
862 {
863 	static const gchar *thisfn = "nact_clipboard_dump";
864 
865 	g_return_if_fail( NACT_IS_CLIPBOARD( clipboard ));
866 
867 	if( !clipboard->private->dispose_has_run ){
868 
869 		g_debug( "%s:       window=%p (%s)", thisfn, ( void * ) clipboard->private->window, G_OBJECT_TYPE_NAME( clipboard->private->window ));
870 		g_debug( "%s:          dnd=%p", thisfn, ( void * ) clipboard->private->dnd );
871 		g_debug( "%s:      primary=%p", thisfn, ( void * ) clipboard->private->primary );
872 		g_debug( "%s: primary_data=%p", thisfn, ( void * ) clipboard->private->primary_data );
873 
874 		if( clipboard->private->primary_data ){
875 			dump_primary_clipboard( clipboard );
876 		}
877 	}
878 }
879 
880 static void
dump_primary_clipboard(NactClipboard * clipboard)881 dump_primary_clipboard( NactClipboard *clipboard )
882 {
883 	static const gchar *thisfn = "nact_clipboard_dump_primary";
884 	PrimaryData *user_data;
885 	gchar *mode;
886 	GList *it;
887 
888 	g_return_if_fail( NACT_IS_CLIPBOARD( clipboard ));
889 
890 	if( !clipboard->private->dispose_has_run ){
891 
892 		user_data = clipboard->private->primary_data;
893 
894 		if( user_data ){
895 			g_debug( "%s:           user_data->nb_actions=%d", thisfn, user_data->nb_actions );
896 			g_debug( "%s:          user_data->nb_profiles=%d", thisfn, user_data->nb_profiles );
897 			g_debug( "%s:             user_data->nb_menus=%d", thisfn, user_data->nb_menus );
898 			g_debug( "%s:                user_data->items=%p (count=%d)",
899 					thisfn,
900 					( void * ) user_data->items,
901 					user_data->items ? g_list_length( user_data->items ) : 0 );
902 			mode = clipboard_mode_to_string( user_data->mode );
903 			g_debug( "%s:                 user_data->mode=%d (%s)", thisfn, user_data->mode, mode );
904 			g_free( mode );
905 			for( it = user_data->items ; it ; it = it->next ){
906 				na_object_object_dump( NA_OBJECT( it->data ));
907 			}
908 		}
909 
910 		g_debug( "%s: clipboard->private->primary_got=%s", thisfn, clipboard->private->primary_got ? "True":"False" );
911 	}
912 }
913 
914 static gchar *
clipboard_mode_to_string(gint mode)915 clipboard_mode_to_string( gint mode )
916 {
917 	gchar *mode_str;
918 
919 	switch( mode ){
920 		case CLIPBOARD_MODE_CUT:
921 			mode_str = g_strdup( "CutMode" );
922 			break;
923 
924 		case CLIPBOARD_MODE_COPY:
925 			mode_str = g_strdup( "CopyMode" );
926 			break;
927 
928 		default:
929 			mode_str = g_strdup( "unknown mode" );
930 			break;
931 	}
932 
933 	return( mode_str );
934 }
935