r4895: New MIME type for Jar archives.
[rox-filer.git] / ROX-Filer / src / gtksavebox.c
blob63224dcfd87231dbd42a878e90db02e61bc4dfa4
1 /*
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)
8 * any later version.
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
13 * more details.
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.
27 #include "config.h"
29 #include <unistd.h>
30 #include <string.h>
31 #include <errno.h>
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"
51 #include "global.h"
52 #include "support.h"
53 #include "gui_support.h"
55 /*
56 * Behaviour:
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.
71 * - Clicking Discard:
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.
84 * Signals:
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).
98 enum {
99 PROP_0,
100 PROP_HAS_DISCARD
103 enum
105 SAVE_TO_FILE,
106 SAVED_TO_URI,
108 LAST_SIGNAL
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,
129 guint info,
130 guint32 time);
131 static guchar *read_xds_property (GdkDragContext *context,
132 gboolean delete);
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,
138 gint response);
139 static void discard_clicked (GtkWidget *button,
140 GtkWidget *savebox);
141 static void do_save (GtkSavebox *savebox);
142 static void gtk_savebox_set_property (GObject *object,
143 guint prop_id,
144 const GValue *value,
145 GParamSpec *pspec);
146 static void gtk_savebox_get_property (GObject *object,
147 guint prop_id,
148 GValue *value,
149 GParamSpec *pspec);
151 void
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);
159 GType
160 gtk_savebox_get_type (void)
162 static GType my_type = 0;
164 if (!my_type)
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 */
174 sizeof(GtkSavebox),
175 0, /* n_preallocs */
176 (GInstanceInitFunc) gtk_savebox_init
179 my_type = g_type_register_static(GTK_TYPE_DIALOG, "GtkSavebox", &info, 0);
182 return my_type;
185 static void
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(
204 "save_to_file",
205 G_TYPE_FROM_CLASS(object_class),
206 G_SIGNAL_RUN_LAST,
207 G_STRUCT_OFFSET(GtkSaveboxClass,
208 save_to_file),
209 NULL, NULL,
210 marshal_INT__STRING,
211 G_TYPE_INT, 1,
212 G_TYPE_STRING);
214 savebox_signals[SAVED_TO_URI] = g_signal_new(
215 "saved_to_uri",
216 G_TYPE_FROM_CLASS(object_class),
217 G_SIGNAL_RUN_LAST,
218 G_STRUCT_OFFSET(GtkSaveboxClass,
219 saved_to_uri),
220 NULL, NULL,
221 g_cclosure_marshal_VOID__STRING,
222 G_TYPE_NONE, 1,
223 G_TYPE_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",
230 "Has Discard",
231 "The dialog has a Discard button",
232 TRUE,
233 G_PARAM_READWRITE));
236 static void
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,
284 FALSE, TRUE, 0);
285 gtk_box_reorder_child (GTK_BOX (dialog->vbox), savebox->discard_area, 0);
287 savebox->dnd_action = GDK_ACTION_COPY;
290 void
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;
299 GtkWidget*
300 gtk_savebox_new (const gchar *action)
302 GtkWidget *button;
303 GtkDialog *dialog;
304 GList *list, *next;
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);
320 g_list_free(list);
322 return GTK_WIDGET(dialog);
325 void
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);
332 if (savebox->icon)
333 gtk_image_set_from_pixbuf (GTK_IMAGE (savebox->icon), pixbuf);
334 else
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);
342 void
343 gtk_savebox_set_pathname (GtkSavebox *savebox, const gchar *pathname)
345 const gchar *slash, *dot;
346 gint leaf;
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)
361 : -1);
364 void
365 gtk_savebox_set_has_discard (GtkSavebox *savebox, gboolean setting)
367 if (setting)
368 gtk_widget_show_all (savebox->discard_area);
369 else
370 gtk_widget_hide (savebox->discard_area);
373 static void
374 button_press_over_icon (GtkWidget *drag_box, GdkEventButton *event,
375 GtkSavebox *savebox)
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));
392 if (uri && *uri)
393 leafname = g_basename (uri);
394 else
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)),
401 event->x, event->y);
405 static void
406 drag_data_get (GtkWidget *widget,
407 GdkDragContext *context,
408 GtkSelectionData *selection_data,
409 guint info,
410 guint32 time)
412 GtkSavebox *savebox;
413 guchar to_send = 'E';
414 gchar *uri;
415 gchar *pathname;
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;
431 return;
434 uri = read_xds_property (context, FALSE);
436 if (uri)
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);
444 #if 0
445 g_print("[ asked to save as '%s' (%s escaped) ]\n",
446 pathname, (char *) escaped_uri);
447 #endif
448 g_free (escaped_uri);
450 if (!pathname)
451 to_send = 'F'; /* Not on the local machine */
452 else
454 g_signal_emit (widget, savebox_signals[SAVE_TO_FILE], 0,
455 pathname, &result);
456 g_free (pathname);
458 if (result == GTK_XDS_SAVED)
460 savebox->data_sent = TRUE;
461 to_send = 'S';
463 else if (result != GTK_XDS_SAVE_ERROR)
464 g_warning ("No handler for saving to a file.\n");
466 g_free (uri);
469 else
471 g_warning (_("Remote application wants to use Direct Save, but I can't "
472 "read the XdndDirectSave0 (type text/plain) property.\n"));
475 if (to_send != 'E')
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. */
481 static guchar *
482 read_xds_property (GdkDragContext *context, gboolean delete)
484 guchar *prop_text;
485 guint length;
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)
493 && 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 */
502 return retval;
505 static void
506 write_xds_property (GdkDragContext *context, const guchar *value)
508 /* XXX: Should set character set to UTF-8 here. Spec says default is
509 * ISO-8859-1!
512 if (value)
514 gdk_property_change (context->source_window, XdndDirectSave,
515 text_plain, 8, GDK_PROP_MODE_REPLACE,
516 value, strlen (value));
518 else
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)
530 guchar *uri;
531 uri = read_xds_property (context, TRUE);
533 if (uri)
535 gchar *path;
536 EscapedPath *escaped_uri;
538 escaped_uri = escape_uri_path (uri);
539 path = get_local_path (escaped_uri);
540 g_free(escaped_uri);
542 g_signal_emit (widget, savebox_signals[SAVED_TO_URI], 0,
543 path ? path : (const gchar *) uri);
544 if (path)
545 g_free (path);
546 g_free(uri);
548 gtk_widget_destroy (widget);
550 return;
553 else
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;
573 const gchar *uri;
574 gchar *pathname;
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);
586 g_free(escaped_uri);
589 if (!pathname)
591 GtkWidget *dialog;
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);
604 return;
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");
618 g_free(pathname);
621 static void
622 gtk_savebox_response (GtkDialog *savebox, gint response)
624 if (response == GTK_RESPONSE_OK)
626 do_save(GTK_SAVEBOX(savebox));
627 return;
629 else if (response == GTK_RESPONSE_CANCEL)
630 gtk_widget_destroy (GTK_WIDGET (savebox));
633 static void
634 gtk_savebox_set_property (GObject *object,
635 guint prop_id,
636 const GValue *value,
637 GParamSpec *pspec)
639 GtkSavebox *savebox;
641 savebox = GTK_SAVEBOX (object);
643 switch (prop_id)
645 case PROP_HAS_DISCARD:
646 gtk_savebox_set_has_discard (GTK_SAVEBOX(object),
647 g_value_get_boolean (value));
648 break;
650 default:
651 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
652 break;
656 static void
657 gtk_savebox_get_property (GObject *object,
658 guint prop_id,
659 GValue *value,
660 GParamSpec *pspec)
662 GtkSavebox *savebox;
664 savebox = GTK_SAVEBOX (object);
666 switch (prop_id)
668 case PROP_HAS_DISCARD:
669 g_value_set_boolean (value, GTK_WIDGET_VISIBLE(savebox->discard_area));
670 break;
672 default:
673 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
674 break;
678 void
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,
687 gpointer arg_1,
688 gpointer data2);
689 register GMarshalFunc_INT__STRING callback;
690 register GCClosure *cc = (GCClosure*) closure;
691 register gpointer data1, data2;
692 gint v_return;
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);
702 else
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);