2 * ROX-Filer, filer for the ROX desktop project
3 * Copyright (C) 2006, Thomas Leonard and others (see changelog for details).
5 * This program is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License as published by the Free
7 * Software Foundation; either version 2 of the License, or (at your option)
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17 * Place, Suite 330, Boston, MA 02111-1307 USA
20 /* gtksavebox.c - ROX-style savebox widget */
23 * Note: This file is formatted like the Gtk+ sources, as it is/was hoped
24 * to include it in Gtk+ at some point.
33 #include "gdk/gdkkeysyms.h"
35 #include "gtksavebox.h"
36 #include "gtk/gtkwidget.h"
37 #include "gtk/gtkalignment.h"
38 #include "gtk/gtkdnd.h"
39 #include "gtk/gtkbutton.h"
40 #include "gtk/gtksignal.h"
41 #include "gtk/gtkhbox.h"
42 #include "gtk/gtkeventbox.h"
43 #include "gtk/gtkentry.h"
44 #include "gtk/gtkmessagedialog.h"
45 #include "gtk/gtkhseparator.h"
46 #include "gtk/gtkvbox.h"
47 #include "gtk/gtkdialog.h"
48 #include "gtk/gtklabel.h"
49 #include "gtk/gtkstock.h"
53 #include "gui_support.h"
58 * - Clicking Save or pressing Return:
59 * - Emits 'save_to_file',
60 * - Emits 'saved_to_uri' (with the same pathname),
61 * - Destroys the widget.
63 * - Clicking Cancel or pressing Escape:
64 * - Destroys the widget.
66 * - Dragging the data somewhere:
67 * - Will either emit 'save_to_file' or get the selection,
68 * - Emits 'saved_to_uri' (possibly with a NULL URI),
69 * - Destroys the widget.
72 * - Emits 'saved_to_uri' with a NULL URI,
73 * - Destroys the widget.
75 * To clarify: 'saved_to_uri' indicates that the save was successful. A
76 * NULL URI just means that the data was saved to another application rather
77 * than a fixed address. Data should only be marked unmodified when
78 * saved_to_uri is called with a non-NULL URI.
80 * Discard is a bit like a successful save to a null device. The data should
81 * be discarded when saved_to_uri is called, whatever URI is set to.
86 * gint save_to_file (GtkSavebox *savebox, const gchar *pathname)
87 * Save the data to disk using this pathname. Return GTK_XDS_SAVED
88 * on success, or GTK_XDS_SAVE_ERROR on failure (and report the error
89 * to the user somehow). DO NOT mark the data unmodified or change
90 * the pathname for the file - this might be a scrap file transfer.
92 * void saved_to_uri (GtkSavebox *savebox, const gchar *uri)
93 * The data is saved. If 'uri' is non-NULL, mark the file as unmodified
94 * and update the pathname/uri for the file to the one given.
95 * The URI is UTF-8 (not escaped).
111 static gpointer parent_class
;
112 static guint savebox_signals
[LAST_SIGNAL
];
114 /* Longest possible XdndDirectSave0 property value */
115 #define XDS_MAXURILEN 4096
117 static GdkAtom XdndDirectSave
;
118 static GdkAtom text_plain
;
119 static GdkAtom xa_string
;
121 static void gtk_savebox_class_init (GtkSaveboxClass
*klass
);
122 static void gtk_savebox_init (GtkSavebox
*savebox
);
123 static void button_press_over_icon (GtkWidget
*drag_box
,
124 GdkEventButton
*event
,
125 GtkSavebox
*savebox
);
126 static void drag_data_get (GtkWidget
*widget
,
127 GdkDragContext
*context
,
128 GtkSelectionData
*selection_data
,
131 static guchar
*read_xds_property (GdkDragContext
*context
,
133 static void write_xds_property (GdkDragContext
*context
,
134 const guchar
*value
);
135 static void drag_end (GtkWidget
*widget
,
136 GdkDragContext
*context
);
137 static void gtk_savebox_response (GtkDialog
*savebox
,
139 static void discard_clicked (GtkWidget
*button
,
141 static void do_save (GtkSavebox
*savebox
);
142 static void gtk_savebox_set_property (GObject
*object
,
146 static void gtk_savebox_get_property (GObject
*object
,
152 marshal_INT__STRING (GClosure
*closure
,
153 GValue
*return_value
,
154 guint n_param_values
,
155 const GValue
*param_values
,
156 gpointer invocation_hint
,
157 gpointer marshal_data
);
160 gtk_savebox_get_type (void)
162 static GType my_type
= 0;
166 static const GTypeInfo info
=
168 sizeof (GtkSaveboxClass
),
169 NULL
, /* base_init */
170 NULL
, /* base_finalise */
171 (GClassInitFunc
) gtk_savebox_class_init
,
172 NULL
, /* class_finalise */
173 NULL
, /* class_data */
176 (GInstanceInitFunc
) gtk_savebox_init
179 my_type
= g_type_register_static(GTK_TYPE_DIALOG
, "GtkSavebox", &info
, 0);
186 gtk_savebox_class_init (GtkSaveboxClass
*class)
188 GObjectClass
*object_class
;
189 GtkDialogClass
*dialog
= (GtkDialogClass
*) class;
191 XdndDirectSave
= gdk_atom_intern ("XdndDirectSave0", FALSE
);
192 text_plain
= gdk_atom_intern ("text/plain", FALSE
);
193 xa_string
= gdk_atom_intern ("STRING", FALSE
);
195 parent_class
= g_type_class_peek_parent (class);
197 class->saved_to_uri
= NULL
;
198 class->save_to_file
= NULL
;
199 dialog
->response
= gtk_savebox_response
;
201 object_class
= G_OBJECT_CLASS(class);
203 savebox_signals
[SAVE_TO_FILE
] = g_signal_new(
205 G_TYPE_FROM_CLASS(object_class
),
207 G_STRUCT_OFFSET(GtkSaveboxClass
,
214 savebox_signals
[SAVED_TO_URI
] = g_signal_new(
216 G_TYPE_FROM_CLASS(object_class
),
218 G_STRUCT_OFFSET(GtkSaveboxClass
,
221 g_cclosure_marshal_VOID__STRING
,
225 object_class
->set_property
= gtk_savebox_set_property
;
226 object_class
->get_property
= gtk_savebox_get_property
;
228 g_object_class_install_property(object_class
, PROP_HAS_DISCARD
,
229 g_param_spec_boolean("has_discard",
231 "The dialog has a Discard button",
237 gtk_savebox_init (GtkSavebox
*savebox
)
239 GtkWidget
*alignment
, *button
;
240 GtkDialog
*dialog
= (GtkDialog
*) savebox
;
241 GtkTargetEntry targets
[] = { {"XdndDirectSave0", 0, GTK_TARGET_XDS
} };
243 gtk_dialog_set_has_separator (dialog
, FALSE
);
245 savebox
->targets
= gtk_target_list_new (targets
,
246 sizeof (targets
) / sizeof (*targets
));
247 savebox
->icon
= NULL
;
249 gtk_window_set_title (GTK_WINDOW (savebox
), _("Save As:"));
250 gtk_window_set_position (GTK_WINDOW (savebox
), GTK_WIN_POS_MOUSE
);
251 gtk_window_set_wmclass (GTK_WINDOW (savebox
), "savebox", "Savebox");
253 alignment
= gtk_alignment_new (0.5, 0.5, 0, 0);
254 gtk_box_pack_start (GTK_BOX (dialog
->vbox
), alignment
, TRUE
, TRUE
, 0);
256 savebox
->drag_box
= gtk_event_box_new ();
257 gtk_container_set_border_width (GTK_CONTAINER (savebox
->drag_box
), 4);
258 gtk_widget_add_events (savebox
->drag_box
, GDK_BUTTON_PRESS_MASK
);
259 g_signal_connect (savebox
->drag_box
, "button_press_event",
260 G_CALLBACK (button_press_over_icon
), savebox
);
261 g_signal_connect (savebox
, "drag_end",
262 G_CALLBACK (drag_end
), savebox
);
263 g_signal_connect (savebox
, "drag_data_get",
264 G_CALLBACK (drag_data_get
), savebox
);
265 gtk_container_add (GTK_CONTAINER (alignment
), savebox
->drag_box
);
267 savebox
->entry
= gtk_entry_new ();
268 g_signal_connect_swapped (savebox
->entry
, "activate",
269 G_CALLBACK (do_save
), savebox
);
270 gtk_box_pack_start (GTK_BOX (dialog
->vbox
), savebox
->entry
, FALSE
, TRUE
, 4);
272 gtk_widget_show_all (dialog
->vbox
);
273 gtk_widget_grab_focus (savebox
->entry
);
275 savebox
->discard_area
= gtk_hbutton_box_new();
277 button
= button_new_mixed (GTK_STOCK_DELETE
, "_Discard");
278 gtk_box_pack_start (GTK_BOX (savebox
->discard_area
), button
, FALSE
, TRUE
, 2);
279 g_signal_connect (button
, "clicked", G_CALLBACK (discard_clicked
), savebox
);
280 GTK_WIDGET_UNSET_FLAGS (button
, GTK_CAN_FOCUS
);
281 GTK_WIDGET_SET_FLAGS (button
, GTK_CAN_DEFAULT
);
283 gtk_box_pack_end (GTK_BOX (dialog
->vbox
), savebox
->discard_area
,
285 gtk_box_reorder_child (GTK_BOX (dialog
->vbox
), savebox
->discard_area
, 0);
287 savebox
->dnd_action
= GDK_ACTION_COPY
;
291 gtk_savebox_set_action (GtkSavebox
*savebox
, GdkDragAction action
)
293 g_return_if_fail (savebox
!= NULL
);
294 g_return_if_fail (GTK_IS_SAVEBOX (savebox
));
296 savebox
->dnd_action
= action
;
300 gtk_savebox_new (const gchar
*action
)
306 dialog
= GTK_DIALOG (gtk_widget_new (gtk_savebox_get_type(), NULL
));
308 gtk_dialog_add_button (dialog
, GTK_STOCK_CANCEL
, GTK_RESPONSE_CANCEL
);
310 button
= button_new_mixed (GTK_STOCK_SAVE
, action
);
311 GTK_WIDGET_SET_FLAGS (button
, GTK_CAN_DEFAULT
);
312 gtk_widget_show (button
);
313 gtk_dialog_add_action_widget (dialog
, button
, GTK_RESPONSE_OK
);
315 gtk_dialog_set_default_response (dialog
, GTK_RESPONSE_OK
);
317 list
= gtk_container_get_children (GTK_CONTAINER (dialog
->action_area
));
318 for (next
= list
; next
; next
= next
->next
)
319 GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET(next
->data
), GTK_CAN_FOCUS
);
322 return GTK_WIDGET(dialog
);
326 gtk_savebox_set_icon (GtkSavebox
*savebox
, GdkPixbuf
*pixbuf
)
328 g_return_if_fail (savebox
!= NULL
);
329 g_return_if_fail (GTK_IS_SAVEBOX (savebox
));
330 g_return_if_fail (pixbuf
!= NULL
);
333 gtk_image_set_from_pixbuf (GTK_IMAGE (savebox
->icon
), pixbuf
);
336 savebox
->icon
= gtk_image_new_from_pixbuf (pixbuf
);
337 gtk_container_add (GTK_CONTAINER (savebox
->drag_box
), savebox
->icon
);
338 gtk_widget_show(savebox
->icon
);
343 gtk_savebox_set_pathname (GtkSavebox
*savebox
, const gchar
*pathname
)
345 const gchar
*slash
, *dot
;
348 g_return_if_fail (savebox
!= NULL
);
349 g_return_if_fail (GTK_IS_SAVEBOX (savebox
));
350 g_return_if_fail (pathname
!= NULL
);
352 gtk_entry_set_text (GTK_ENTRY (savebox
->entry
), pathname
);
354 slash
= strrchr (pathname
, '/');
356 leaf
= slash
? g_utf8_pointer_to_offset(pathname
, slash
) + 1 : 0;
357 dot
= strchr(pathname
+ leaf
, '.');
359 gtk_editable_select_region (GTK_EDITABLE (savebox
->entry
), leaf
,
360 dot
? g_utf8_pointer_to_offset (pathname
, dot
)
365 gtk_savebox_set_has_discard (GtkSavebox
*savebox
, gboolean setting
)
368 gtk_widget_show_all (savebox
->discard_area
);
370 gtk_widget_hide (savebox
->discard_area
);
374 button_press_over_icon (GtkWidget
*drag_box
, GdkEventButton
*event
,
377 GdkDragContext
*context
;
378 const gchar
*uri
= NULL
, *leafname
;
380 g_return_if_fail (savebox
!= NULL
);
381 g_return_if_fail (GTK_IS_SAVEBOX (savebox
));
382 g_return_if_fail (event
!= NULL
);
383 g_return_if_fail (savebox
->icon
!= NULL
);
385 savebox
->using_xds
= FALSE
;
386 savebox
->data_sent
= FALSE
;
387 context
= gtk_drag_begin (GTK_WIDGET (savebox
),
388 savebox
->targets
, savebox
->dnd_action
,
389 event
->button
, (GdkEvent
*) event
);
391 uri
= gtk_entry_get_text (GTK_ENTRY (savebox
->entry
));
393 leafname
= g_basename (uri
);
395 leafname
= _("Unnamed");
397 write_xds_property (context
, leafname
);
399 gtk_drag_set_icon_pixbuf (context
,
400 gtk_image_get_pixbuf (GTK_IMAGE (savebox
->icon
)),
406 drag_data_get (GtkWidget
*widget
,
407 GdkDragContext
*context
,
408 GtkSelectionData
*selection_data
,
413 guchar to_send
= 'E';
417 g_return_if_fail (widget
!= NULL
);
418 g_return_if_fail (GTK_IS_SAVEBOX (widget
));
419 g_return_if_fail (context
!= NULL
);
420 g_return_if_fail (selection_data
!= NULL
);
422 savebox
= GTK_SAVEBOX (widget
);
424 /* We're only concerned with the XDS protocol. Responding to other requests
425 * (including application/octet-stream) is the job of the application.
427 if (info
!= GTK_TARGET_XDS
)
429 /* Assume that the data will be/has been sent */
430 savebox
->data_sent
= TRUE
;
434 uri
= read_xds_property (context
, FALSE
);
438 gint result
= GTK_XDS_NO_HANDLER
;
439 EscapedPath
*escaped_uri
;
441 /* Escape and then unescape. A little inefficient. */
442 escaped_uri
= escape_uri_path (uri
);
443 pathname
= get_local_path (escaped_uri
);
445 g_print("[ asked to save as '%s' (%s escaped) ]\n",
446 pathname
, (char *) escaped_uri
);
448 g_free (escaped_uri
);
451 to_send
= 'F'; /* Not on the local machine */
454 g_signal_emit (widget
, savebox_signals
[SAVE_TO_FILE
], 0,
458 if (result
== GTK_XDS_SAVED
)
460 savebox
->data_sent
= TRUE
;
463 else if (result
!= GTK_XDS_SAVE_ERROR
)
464 g_warning ("No handler for saving to a file.\n");
471 g_warning (_("Remote application wants to use Direct Save, but I can't "
472 "read the XdndDirectSave0 (type text/plain) property.\n"));
476 savebox
->using_xds
= TRUE
;
477 gtk_selection_data_set (selection_data
, xa_string
, 8, &to_send
, 1);
480 /* Result is a UTF-8 encoded path. Not escaped. g_free() the result. */
482 read_xds_property (GdkDragContext
*context
, gboolean
delete)
486 guchar
*retval
= NULL
;
488 g_return_val_if_fail (context
!= NULL
, NULL
);
490 if (gdk_property_get (context
->source_window
, XdndDirectSave
, text_plain
,
491 0, XDS_MAXURILEN
, delete,
492 NULL
, NULL
, &length
, &prop_text
)
495 /* Terminate the string */
496 retval
= g_realloc (prop_text
, length
+ 1);
497 retval
[length
] = '\0';
500 /* Should really do a character set conversation here, but assume UTF-8 */
506 write_xds_property (GdkDragContext
*context
, const guchar
*value
)
508 /* XXX: Should set character set to UTF-8 here. Spec says default is
514 gdk_property_change (context
->source_window
, XdndDirectSave
,
515 text_plain
, 8, GDK_PROP_MODE_REPLACE
,
516 value
, strlen (value
));
519 gdk_property_delete (context
->source_window
, XdndDirectSave
);
522 static void drag_end (GtkWidget
*widget
, GdkDragContext
*context
)
524 g_return_if_fail (widget
!= NULL
);
525 g_return_if_fail (GTK_IS_SAVEBOX (widget
));
526 g_return_if_fail (context
!= NULL
);
528 if (GTK_SAVEBOX (widget
)->using_xds
)
531 uri
= read_xds_property (context
, TRUE
);
536 EscapedPath
*escaped_uri
;
538 escaped_uri
= escape_uri_path (uri
);
539 path
= get_local_path (escaped_uri
);
542 g_signal_emit (widget
, savebox_signals
[SAVED_TO_URI
], 0,
543 path
? path
: (const gchar
*) uri
);
548 gtk_widget_destroy (widget
);
554 write_xds_property (context
, NULL
);
556 if (GTK_SAVEBOX (widget
)->data_sent
)
558 g_signal_emit (widget
, savebox_signals
[SAVED_TO_URI
], 0, NULL
);
559 gtk_widget_destroy (widget
);
563 static void discard_clicked (GtkWidget
*button
, GtkWidget
*savebox
)
565 g_signal_emit (savebox
, savebox_signals
[SAVED_TO_URI
], 0, NULL
);
566 gtk_widget_destroy (savebox
);
569 /* User has clicked Save or pressed Return... */
570 static void do_save (GtkSavebox
*savebox
)
572 gint result
= GTK_XDS_NO_HANDLER
;
576 g_return_if_fail (savebox
!= NULL
);
577 g_return_if_fail (GTK_IS_SAVEBOX (savebox
));
579 uri
= gtk_entry_get_text (GTK_ENTRY (savebox
->entry
));
581 /* This is a bit inefficient... */ {
582 EscapedPath
*escaped_uri
;
584 escaped_uri
= escape_uri_path (uri
);
585 pathname
= get_local_path (escaped_uri
);
593 dialog
= gtk_message_dialog_new (GTK_WINDOW (savebox
),
594 GTK_DIALOG_MODAL
| GTK_DIALOG_DESTROY_WITH_PARENT
,
595 GTK_MESSAGE_INFO
, GTK_BUTTONS_OK
,
596 _("Drag the icon to a directory viewer\n"
597 "(or enter a full pathname)"));
599 gtk_window_set_position (GTK_WINDOW (dialog
), GTK_WIN_POS_CENTER
);
601 gtk_dialog_run (GTK_DIALOG (dialog
));
602 gtk_widget_destroy (dialog
);
607 g_signal_emit (savebox
, savebox_signals
[SAVE_TO_FILE
], 0, pathname
, &result
);
609 if (result
== GTK_XDS_SAVED
)
611 g_signal_emit (savebox
, savebox_signals
[SAVED_TO_URI
], 0, pathname
);
613 gtk_widget_destroy (GTK_WIDGET (savebox
));
615 else if (result
== GTK_XDS_NO_HANDLER
)
616 g_warning ("No handler for saving to a file.\n");
622 gtk_savebox_response (GtkDialog
*savebox
, gint response
)
624 if (response
== GTK_RESPONSE_OK
)
626 do_save(GTK_SAVEBOX(savebox
));
629 else if (response
== GTK_RESPONSE_CANCEL
)
630 gtk_widget_destroy (GTK_WIDGET (savebox
));
634 gtk_savebox_set_property (GObject
*object
,
641 savebox
= GTK_SAVEBOX (object
);
645 case PROP_HAS_DISCARD
:
646 gtk_savebox_set_has_discard (GTK_SAVEBOX(object
),
647 g_value_get_boolean (value
));
651 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
657 gtk_savebox_get_property (GObject
*object
,
664 savebox
= GTK_SAVEBOX (object
);
668 case PROP_HAS_DISCARD
:
669 g_value_set_boolean (value
, GTK_WIDGET_VISIBLE(savebox
->discard_area
));
673 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
679 marshal_INT__STRING (GClosure
*closure
,
680 GValue
*return_value
,
681 guint n_param_values
,
682 const GValue
*param_values
,
683 gpointer invocation_hint
,
684 gpointer marshal_data
)
686 typedef gint (*GMarshalFunc_INT__STRING
) (gpointer data1
,
689 register GMarshalFunc_INT__STRING callback
;
690 register GCClosure
*cc
= (GCClosure
*) closure
;
691 register gpointer data1
, data2
;
694 g_return_if_fail (return_value
!= NULL
);
695 g_return_if_fail (n_param_values
== 2);
697 if (G_CCLOSURE_SWAP_DATA (closure
))
699 data1
= closure
->data
;
700 data2
= g_value_peek_pointer (param_values
+ 0);
704 data1
= g_value_peek_pointer (param_values
+ 0);
705 data2
= closure
->data
;
707 callback
= (GMarshalFunc_INT__STRING
)
708 (marshal_data
? marshal_data
: cc
->callback
);
710 v_return
= callback (data1
, param_values
[1].data
[0].v_pointer
, data2
);
712 g_value_set_int (return_value
, v_return
);