r3768: Updated years.
[rox-filer.git] / ROX-Filer / src / gtksavebox.c
blob1400ffb01c4e53b99f1a742f3458f996aed89c97
1 /*
2 * $Id$
4 * SaveBox widget for the ROX desktop project
5 * Copyright (C) 2005 Thomas Leonard.
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
17 * You should have received a copy of the GNU General Public License along with
18 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 * Place, Suite 330, Boston, MA 02111-1307 USA
22 /* gtksavebox.c - ROX-style savebox widget */
25 * Note: This file is formatted like the Gtk+ sources, as it is/was hoped
26 * to include it in Gtk+ at some point.
29 #include "config.h"
31 #include <unistd.h>
32 #include <string.h>
33 #include <errno.h>
35 #include "gdk/gdkkeysyms.h"
37 #include "gtksavebox.h"
38 #include "gtk/gtkwidget.h"
39 #include "gtk/gtkalignment.h"
40 #include "gtk/gtkdnd.h"
41 #include "gtk/gtkbutton.h"
42 #include "gtk/gtksignal.h"
43 #include "gtk/gtkhbox.h"
44 #include "gtk/gtkeventbox.h"
45 #include "gtk/gtkentry.h"
46 #include "gtk/gtkmessagedialog.h"
47 #include "gtk/gtkhseparator.h"
48 #include "gtk/gtkvbox.h"
49 #include "gtk/gtkdialog.h"
50 #include "gtk/gtklabel.h"
51 #include "gtk/gtkstock.h"
53 #include "global.h"
54 #include "support.h"
55 #include "gui_support.h"
57 /*
58 * Behaviour:
60 * - Clicking Save or pressing Return:
61 * - Emits 'save_to_file',
62 * - Emits 'saved_to_uri' (with the same pathname),
63 * - Destroys the widget.
65 * - Clicking Cancel or pressing Escape:
66 * - Destroys the widget.
68 * - Dragging the data somewhere:
69 * - Will either emit 'save_to_file' or get the selection,
70 * - Emits 'saved_to_uri' (possibly with a NULL URI),
71 * - Destroys the widget.
73 * - Clicking Discard:
74 * - Emits 'saved_to_uri' with a NULL URI,
75 * - Destroys the widget.
77 * To clarify: 'saved_to_uri' indicates that the save was successful. A
78 * NULL URI just means that the data was saved to another application rather
79 * than a fixed address. Data should only be marked unmodified when
80 * saved_to_uri is called with a non-NULL URI.
82 * Discard is a bit like a successful save to a null device. The data should
83 * be discarded when saved_to_uri is called, whatever URI is set to.
86 * Signals:
88 * gint save_to_file (GtkSavebox *savebox, const gchar *pathname)
89 * Save the data to disk using this pathname. Return GTK_XDS_SAVED
90 * on success, or GTK_XDS_SAVE_ERROR on failure (and report the error
91 * to the user somehow). DO NOT mark the data unmodified or change
92 * the pathname for the file - this might be a scrap file transfer.
94 * void saved_to_uri (GtkSavebox *savebox, const gchar *uri)
95 * The data is saved. If 'uri' is non-NULL, mark the file as unmodified
96 * and update the pathname/uri for the file to the one given.
97 * The URI is UTF-8 (not escaped).
100 enum {
101 PROP_0,
102 PROP_HAS_DISCARD
105 enum
107 SAVE_TO_FILE,
108 SAVED_TO_URI,
110 LAST_SIGNAL
113 static gpointer parent_class;
114 static guint savebox_signals[LAST_SIGNAL];
116 /* Longest possible XdndDirectSave0 property value */
117 #define XDS_MAXURILEN 4096
119 static GdkAtom XdndDirectSave;
120 static GdkAtom text_plain;
121 static GdkAtom xa_string;
123 static void gtk_savebox_class_init (GtkSaveboxClass *klass);
124 static void gtk_savebox_init (GtkSavebox *savebox);
125 static void button_press_over_icon (GtkWidget *drag_box,
126 GdkEventButton *event,
127 GtkSavebox *savebox);
128 static void drag_data_get (GtkWidget *widget,
129 GdkDragContext *context,
130 GtkSelectionData *selection_data,
131 guint info,
132 guint32 time);
133 static guchar *read_xds_property (GdkDragContext *context,
134 gboolean delete);
135 static void write_xds_property (GdkDragContext *context,
136 const guchar *value);
137 static void drag_end (GtkWidget *widget,
138 GdkDragContext *context);
139 static void gtk_savebox_response (GtkDialog *savebox,
140 gint response);
141 static void discard_clicked (GtkWidget *button,
142 GtkWidget *savebox);
143 static void do_save (GtkSavebox *savebox);
144 static void gtk_savebox_set_property (GObject *object,
145 guint prop_id,
146 const GValue *value,
147 GParamSpec *pspec);
148 static void gtk_savebox_get_property (GObject *object,
149 guint prop_id,
150 GValue *value,
151 GParamSpec *pspec);
153 void
154 marshal_INT__STRING (GClosure *closure,
155 GValue *return_value,
156 guint n_param_values,
157 const GValue *param_values,
158 gpointer invocation_hint,
159 gpointer marshal_data);
161 GType
162 gtk_savebox_get_type (void)
164 static GType my_type = 0;
166 if (!my_type)
168 static const GTypeInfo info =
170 sizeof (GtkSaveboxClass),
171 NULL, /* base_init */
172 NULL, /* base_finalise */
173 (GClassInitFunc) gtk_savebox_class_init,
174 NULL, /* class_finalise */
175 NULL, /* class_data */
176 sizeof(GtkSavebox),
177 0, /* n_preallocs */
178 (GInstanceInitFunc) gtk_savebox_init
181 my_type = g_type_register_static(GTK_TYPE_DIALOG, "GtkSavebox", &info, 0);
184 return my_type;
187 static void
188 gtk_savebox_class_init (GtkSaveboxClass *class)
190 GObjectClass *object_class;
191 GtkDialogClass *dialog = (GtkDialogClass *) class;
193 XdndDirectSave = gdk_atom_intern ("XdndDirectSave0", FALSE);
194 text_plain = gdk_atom_intern ("text/plain", FALSE);
195 xa_string = gdk_atom_intern ("STRING", FALSE);
197 parent_class = g_type_class_peek_parent (class);
199 class->saved_to_uri = NULL;
200 class->save_to_file = NULL;
201 dialog->response = gtk_savebox_response;
203 object_class = G_OBJECT_CLASS(class);
205 savebox_signals[SAVE_TO_FILE] = g_signal_new(
206 "save_to_file",
207 G_TYPE_FROM_CLASS(object_class),
208 G_SIGNAL_RUN_LAST,
209 G_STRUCT_OFFSET(GtkSaveboxClass,
210 save_to_file),
211 NULL, NULL,
212 marshal_INT__STRING,
213 G_TYPE_INT, 1,
214 G_TYPE_STRING);
216 savebox_signals[SAVED_TO_URI] = g_signal_new(
217 "saved_to_uri",
218 G_TYPE_FROM_CLASS(object_class),
219 G_SIGNAL_RUN_LAST,
220 G_STRUCT_OFFSET(GtkSaveboxClass,
221 saved_to_uri),
222 NULL, NULL,
223 g_cclosure_marshal_VOID__STRING,
224 G_TYPE_NONE, 1,
225 G_TYPE_STRING);
227 object_class->set_property = gtk_savebox_set_property;
228 object_class->get_property = gtk_savebox_get_property;
230 g_object_class_install_property(object_class, PROP_HAS_DISCARD,
231 g_param_spec_boolean("has_discard",
232 "Has Discard",
233 "The dialog has a Discard button",
234 TRUE,
235 G_PARAM_READWRITE));
238 static void
239 gtk_savebox_init (GtkSavebox *savebox)
241 GtkWidget *alignment, *button;
242 GtkDialog *dialog = (GtkDialog *) savebox;
243 GtkTargetEntry targets[] = { {"XdndDirectSave0", 0, GTK_TARGET_XDS} };
245 gtk_dialog_set_has_separator (dialog, FALSE);
247 savebox->targets = gtk_target_list_new (targets,
248 sizeof (targets) / sizeof (*targets));
249 savebox->icon = NULL;
251 gtk_window_set_title (GTK_WINDOW (savebox), _("Save As:"));
252 gtk_window_set_position (GTK_WINDOW (savebox), GTK_WIN_POS_MOUSE);
253 gtk_window_set_wmclass (GTK_WINDOW (savebox), "savebox", "Savebox");
255 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
256 gtk_box_pack_start (GTK_BOX (dialog->vbox), alignment, TRUE, TRUE, 0);
258 savebox->drag_box = gtk_event_box_new ();
259 gtk_container_set_border_width (GTK_CONTAINER (savebox->drag_box), 4);
260 gtk_widget_add_events (savebox->drag_box, GDK_BUTTON_PRESS_MASK);
261 g_signal_connect (savebox->drag_box, "button_press_event",
262 G_CALLBACK (button_press_over_icon), savebox);
263 g_signal_connect (savebox, "drag_end",
264 G_CALLBACK (drag_end), savebox);
265 g_signal_connect (savebox, "drag_data_get",
266 G_CALLBACK (drag_data_get), savebox);
267 gtk_container_add (GTK_CONTAINER (alignment), savebox->drag_box);
269 savebox->entry = gtk_entry_new ();
270 g_signal_connect_swapped (savebox->entry, "activate",
271 G_CALLBACK (do_save), savebox);
272 gtk_box_pack_start (GTK_BOX (dialog->vbox), savebox->entry, FALSE, TRUE, 4);
274 gtk_widget_show_all (dialog->vbox);
275 gtk_widget_grab_focus (savebox->entry);
277 savebox->discard_area = gtk_hbutton_box_new();
279 button = button_new_mixed (GTK_STOCK_DELETE, "_Discard");
280 gtk_box_pack_start (GTK_BOX (savebox->discard_area), button, FALSE, TRUE, 2);
281 g_signal_connect (button, "clicked", G_CALLBACK (discard_clicked), savebox);
282 GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS);
283 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
285 gtk_box_pack_end (GTK_BOX (dialog->vbox), savebox->discard_area,
286 FALSE, TRUE, 0);
287 gtk_box_reorder_child (GTK_BOX (dialog->vbox), savebox->discard_area, 0);
289 savebox->dnd_action = GDK_ACTION_COPY;
292 void
293 gtk_savebox_set_action (GtkSavebox *savebox, GdkDragAction action)
295 g_return_if_fail (savebox != NULL);
296 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
298 savebox->dnd_action = action;
301 GtkWidget*
302 gtk_savebox_new (const gchar *action)
304 GtkWidget *button;
305 GtkDialog *dialog;
306 GList *list, *next;
308 dialog = GTK_DIALOG (gtk_widget_new (gtk_savebox_get_type(), NULL));
310 gtk_dialog_add_button (dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
312 button = button_new_mixed (GTK_STOCK_SAVE, action);
313 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
314 gtk_widget_show (button);
315 gtk_dialog_add_action_widget (dialog, button, GTK_RESPONSE_OK);
317 gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
319 list = gtk_container_get_children (GTK_CONTAINER (dialog->action_area));
320 for (next = list; next; next = next->next)
321 GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET(next->data), GTK_CAN_FOCUS);
322 g_list_free(list);
324 return GTK_WIDGET(dialog);
327 void
328 gtk_savebox_set_icon (GtkSavebox *savebox, GdkPixbuf *pixbuf)
330 g_return_if_fail (savebox != NULL);
331 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
332 g_return_if_fail (pixbuf != NULL);
334 if (savebox->icon)
335 gtk_image_set_from_pixbuf (GTK_IMAGE (savebox->icon), pixbuf);
336 else
338 savebox->icon = gtk_image_new_from_pixbuf (pixbuf);
339 gtk_container_add (GTK_CONTAINER (savebox->drag_box), savebox->icon);
340 gtk_widget_show(savebox->icon);
344 void
345 gtk_savebox_set_pathname (GtkSavebox *savebox, const gchar *pathname)
347 const gchar *slash, *dot;
348 gint leaf;
350 g_return_if_fail (savebox != NULL);
351 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
352 g_return_if_fail (pathname != NULL);
354 gtk_entry_set_text (GTK_ENTRY (savebox->entry), pathname);
356 slash = strrchr (pathname, '/');
358 leaf = slash ? g_utf8_pointer_to_offset(pathname, slash) + 1 : 0;
359 dot = strchr(pathname + leaf, '.');
361 gtk_editable_select_region (GTK_EDITABLE (savebox->entry), leaf,
362 dot ? g_utf8_pointer_to_offset (pathname, dot)
363 : -1);
366 void
367 gtk_savebox_set_has_discard (GtkSavebox *savebox, gboolean setting)
369 if (setting)
370 gtk_widget_show_all (savebox->discard_area);
371 else
372 gtk_widget_hide (savebox->discard_area);
375 static void
376 button_press_over_icon (GtkWidget *drag_box, GdkEventButton *event,
377 GtkSavebox *savebox)
379 GdkDragContext *context;
380 const gchar *uri = NULL, *leafname;
382 g_return_if_fail (savebox != NULL);
383 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
384 g_return_if_fail (event != NULL);
385 g_return_if_fail (savebox->icon != NULL);
387 savebox->using_xds = FALSE;
388 savebox->data_sent = FALSE;
389 context = gtk_drag_begin (GTK_WIDGET (savebox),
390 savebox->targets, savebox->dnd_action,
391 event->button, (GdkEvent *) event);
393 uri = gtk_entry_get_text (GTK_ENTRY (savebox->entry));
394 if (uri && *uri)
395 leafname = g_basename (uri);
396 else
397 leafname = _("Unnamed");
399 write_xds_property (context, leafname);
401 gtk_drag_set_icon_pixbuf (context,
402 gtk_image_get_pixbuf (GTK_IMAGE (savebox->icon)),
403 event->x, event->y);
407 static void
408 drag_data_get (GtkWidget *widget,
409 GdkDragContext *context,
410 GtkSelectionData *selection_data,
411 guint info,
412 guint32 time)
414 GtkSavebox *savebox;
415 guchar to_send = 'E';
416 gchar *uri;
417 gchar *pathname;
419 g_return_if_fail (widget != NULL);
420 g_return_if_fail (GTK_IS_SAVEBOX (widget));
421 g_return_if_fail (context != NULL);
422 g_return_if_fail (selection_data != NULL);
424 savebox = GTK_SAVEBOX (widget);
426 /* We're only concerned with the XDS protocol. Responding to other requests
427 * (including application/octet-stream) is the job of the application.
429 if (info != GTK_TARGET_XDS)
431 /* Assume that the data will be/has been sent */
432 savebox->data_sent = TRUE;
433 return;
436 uri = read_xds_property (context, FALSE);
438 if (uri)
440 gint result = GTK_XDS_NO_HANDLER;
441 EscapedPath *escaped_uri;
443 /* Escape and then unescape. A little inefficient. */
444 escaped_uri = escape_uri_path (uri);
445 pathname = get_local_path (escaped_uri);
446 #if 0
447 g_print("[ asked to save as '%s' (%s escaped) ]\n",
448 pathname, (char *) escaped_uri);
449 #endif
450 g_free (escaped_uri);
452 if (!pathname)
453 to_send = 'F'; /* Not on the local machine */
454 else
456 g_signal_emit (widget, savebox_signals[SAVE_TO_FILE], 0,
457 pathname, &result);
458 g_free (pathname);
460 if (result == GTK_XDS_SAVED)
462 savebox->data_sent = TRUE;
463 to_send = 'S';
465 else if (result != GTK_XDS_SAVE_ERROR)
466 g_warning ("No handler for saving to a file.\n");
468 g_free (uri);
471 else
473 g_warning (_("Remote application wants to use Direct Save, but I can't "
474 "read the XdndDirectSave0 (type text/plain) property.\n"));
477 if (to_send != 'E')
478 savebox->using_xds = TRUE;
479 gtk_selection_data_set (selection_data, xa_string, 8, &to_send, 1);
482 /* Result is a UTF-8 encoded path. Not escaped. g_free() the result. */
483 static guchar *
484 read_xds_property (GdkDragContext *context, gboolean delete)
486 guchar *prop_text;
487 guint length;
488 guchar *retval = NULL;
490 g_return_val_if_fail (context != NULL, NULL);
492 if (gdk_property_get (context->source_window, XdndDirectSave, text_plain,
493 0, XDS_MAXURILEN, delete,
494 NULL, NULL, &length, &prop_text)
495 && prop_text)
497 /* Terminate the string */
498 retval = g_realloc (prop_text, length + 1);
499 retval[length] = '\0';
502 /* Should really do a character set conversation here, but assume UTF-8 */
504 return retval;
507 static void
508 write_xds_property (GdkDragContext *context, const guchar *value)
510 /* XXX: Should set character set to UTF-8 here. Spec says default is
511 * ISO-8859-1!
514 if (value)
516 gdk_property_change (context->source_window, XdndDirectSave,
517 text_plain, 8, GDK_PROP_MODE_REPLACE,
518 value, strlen (value));
520 else
521 gdk_property_delete (context->source_window, XdndDirectSave);
524 static void drag_end (GtkWidget *widget, GdkDragContext *context)
526 g_return_if_fail (widget != NULL);
527 g_return_if_fail (GTK_IS_SAVEBOX (widget));
528 g_return_if_fail (context != NULL);
530 if (GTK_SAVEBOX (widget)->using_xds)
532 guchar *uri;
533 uri = read_xds_property (context, TRUE);
535 if (uri)
537 gchar *path;
538 EscapedPath *escaped_uri;
540 escaped_uri = escape_uri_path (uri);
541 path = get_local_path (escaped_uri);
542 g_free(escaped_uri);
544 g_signal_emit (widget, savebox_signals[SAVED_TO_URI], 0,
545 path ? path : (const gchar *) uri);
546 if (path)
547 g_free (path);
548 g_free(uri);
550 gtk_widget_destroy (widget);
552 return;
555 else
556 write_xds_property (context, NULL);
558 if (GTK_SAVEBOX (widget)->data_sent)
560 g_signal_emit (widget, savebox_signals[SAVED_TO_URI], 0, NULL);
561 gtk_widget_destroy (widget);
565 static void discard_clicked (GtkWidget *button, GtkWidget *savebox)
567 g_signal_emit (savebox, savebox_signals[SAVED_TO_URI], 0, NULL);
568 gtk_widget_destroy (savebox);
571 /* User has clicked Save or pressed Return... */
572 static void do_save (GtkSavebox *savebox)
574 gint result = GTK_XDS_NO_HANDLER;
575 const gchar *uri;
576 gchar *pathname;
578 g_return_if_fail (savebox != NULL);
579 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
581 uri = gtk_entry_get_text (GTK_ENTRY (savebox->entry));
583 /* This is a bit inefficient... */ {
584 EscapedPath *escaped_uri;
586 escaped_uri = escape_uri_path (uri);
587 pathname = get_local_path (escaped_uri);
588 g_free(escaped_uri);
591 if (!pathname)
593 GtkWidget *dialog;
595 dialog = gtk_message_dialog_new (GTK_WINDOW (savebox),
596 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
597 GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
598 _("Drag the icon to a directory viewer\n"
599 "(or enter a full pathname)"));
601 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
603 gtk_dialog_run (GTK_DIALOG (dialog));
604 gtk_widget_destroy (dialog);
606 return;
609 g_signal_emit (savebox, savebox_signals[SAVE_TO_FILE], 0, pathname, &result);
611 if (result == GTK_XDS_SAVED)
613 g_signal_emit (savebox, savebox_signals[SAVED_TO_URI], 0, pathname);
615 gtk_widget_destroy (GTK_WIDGET (savebox));
617 else if (result == GTK_XDS_NO_HANDLER)
618 g_warning ("No handler for saving to a file.\n");
620 g_free(pathname);
623 static void
624 gtk_savebox_response (GtkDialog *savebox, gint response)
626 if (response == GTK_RESPONSE_OK)
628 do_save(GTK_SAVEBOX(savebox));
629 return;
631 else if (response == GTK_RESPONSE_CANCEL)
632 gtk_widget_destroy (GTK_WIDGET (savebox));
635 static void
636 gtk_savebox_set_property (GObject *object,
637 guint prop_id,
638 const GValue *value,
639 GParamSpec *pspec)
641 GtkSavebox *savebox;
643 savebox = GTK_SAVEBOX (object);
645 switch (prop_id)
647 case PROP_HAS_DISCARD:
648 gtk_savebox_set_has_discard (GTK_SAVEBOX(object),
649 g_value_get_boolean (value));
650 break;
652 default:
653 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
654 break;
658 static void
659 gtk_savebox_get_property (GObject *object,
660 guint prop_id,
661 GValue *value,
662 GParamSpec *pspec)
664 GtkSavebox *savebox;
666 savebox = GTK_SAVEBOX (object);
668 switch (prop_id)
670 case PROP_HAS_DISCARD:
671 g_value_set_boolean (value, GTK_WIDGET_VISIBLE(savebox->discard_area));
672 break;
674 default:
675 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
676 break;
680 void
681 marshal_INT__STRING (GClosure *closure,
682 GValue *return_value,
683 guint n_param_values,
684 const GValue *param_values,
685 gpointer invocation_hint,
686 gpointer marshal_data)
688 typedef gint (*GMarshalFunc_INT__STRING) (gpointer data1,
689 gpointer arg_1,
690 gpointer data2);
691 register GMarshalFunc_INT__STRING callback;
692 register GCClosure *cc = (GCClosure*) closure;
693 register gpointer data1, data2;
694 gint v_return;
696 g_return_if_fail (return_value != NULL);
697 g_return_if_fail (n_param_values == 2);
699 if (G_CCLOSURE_SWAP_DATA (closure))
701 data1 = closure->data;
702 data2 = g_value_peek_pointer (param_values + 0);
704 else
706 data1 = g_value_peek_pointer (param_values + 0);
707 data2 = closure->data;
709 callback = (GMarshalFunc_INT__STRING)
710 (marshal_data ? marshal_data : cc->callback);
712 v_return = callback (data1, param_values[1].data[0].v_pointer, data2);
714 g_value_set_int (return_value, v_return);