r3305: Misc changes for next release.
[rox-filer.git] / ROX-Filer / src / gtksavebox.c
blob5654aefaa015efa9a21840f1d4622ceb9cb33de6
1 /*
2 * $Id$
4 * SaveBox widget for the ROX desktop project
5 * Copyright (C) 2003 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.
99 enum {
100 PROP_0,
101 PROP_HAS_DISCARD
104 enum
106 SAVE_TO_FILE,
107 SAVED_TO_URI,
109 LAST_SIGNAL
112 static gpointer parent_class;
113 static guint savebox_signals[LAST_SIGNAL];
115 /* Longest possible XdndDirectSave0 property value */
116 #define XDS_MAXURILEN 4096
118 static GdkAtom XdndDirectSave;
119 static GdkAtom text_plain;
120 static GdkAtom xa_string;
122 static void gtk_savebox_class_init (GtkSaveboxClass *klass);
123 static void gtk_savebox_init (GtkSavebox *savebox);
124 static void button_press_over_icon (GtkWidget *drag_box,
125 GdkEventButton *event,
126 GtkSavebox *savebox);
127 static void drag_data_get (GtkWidget *widget,
128 GdkDragContext *context,
129 GtkSelectionData *selection_data,
130 guint info,
131 guint32 time);
132 static guchar *read_xds_property (GdkDragContext *context,
133 gboolean delete);
134 static void write_xds_property (GdkDragContext *context,
135 const guchar *value);
136 static void drag_end (GtkWidget *widget,
137 GdkDragContext *context);
138 static void gtk_savebox_response (GtkDialog *savebox,
139 gint response);
140 static void discard_clicked (GtkWidget *button,
141 GtkWidget *savebox);
142 static void do_save (GtkSavebox *savebox);
143 static void gtk_savebox_set_property (GObject *object,
144 guint prop_id,
145 const GValue *value,
146 GParamSpec *pspec);
147 static void gtk_savebox_get_property (GObject *object,
148 guint prop_id,
149 GValue *value,
150 GParamSpec *pspec);
152 void
153 marshal_INT__STRING (GClosure *closure,
154 GValue *return_value,
155 guint n_param_values,
156 const GValue *param_values,
157 gpointer invocation_hint,
158 gpointer marshal_data);
160 GType
161 gtk_savebox_get_type (void)
163 static GType my_type = 0;
165 if (!my_type)
167 static const GTypeInfo info =
169 sizeof (GtkSaveboxClass),
170 NULL, /* base_init */
171 NULL, /* base_finalise */
172 (GClassInitFunc) gtk_savebox_class_init,
173 NULL, /* class_finalise */
174 NULL, /* class_data */
175 sizeof(GtkSavebox),
176 0, /* n_preallocs */
177 (GInstanceInitFunc) gtk_savebox_init
180 my_type = g_type_register_static(GTK_TYPE_DIALOG, "GtkSavebox", &info, 0);
183 return my_type;
186 static void
187 gtk_savebox_class_init (GtkSaveboxClass *class)
189 GObjectClass *object_class;
190 GtkDialogClass *dialog = (GtkDialogClass *) class;
192 XdndDirectSave = gdk_atom_intern ("XdndDirectSave0", FALSE);
193 text_plain = gdk_atom_intern ("text/plain", FALSE);
194 xa_string = gdk_atom_intern ("STRING", FALSE);
196 parent_class = g_type_class_peek_parent (class);
198 class->saved_to_uri = NULL;
199 class->save_to_file = NULL;
200 dialog->response = gtk_savebox_response;
202 object_class = G_OBJECT_CLASS(class);
204 savebox_signals[SAVE_TO_FILE] = g_signal_new(
205 "save_to_file",
206 G_TYPE_FROM_CLASS(object_class),
207 G_SIGNAL_RUN_LAST,
208 G_STRUCT_OFFSET(GtkSaveboxClass,
209 save_to_file),
210 NULL, NULL,
211 marshal_INT__STRING,
212 G_TYPE_INT, 1,
213 G_TYPE_STRING);
215 savebox_signals[SAVED_TO_URI] = g_signal_new(
216 "saved_to_uri",
217 G_TYPE_FROM_CLASS(object_class),
218 G_SIGNAL_RUN_LAST,
219 G_STRUCT_OFFSET(GtkSaveboxClass,
220 saved_to_uri),
221 NULL, NULL,
222 g_cclosure_marshal_VOID__STRING,
223 G_TYPE_NONE, 1,
224 G_TYPE_STRING);
226 object_class->set_property = gtk_savebox_set_property;
227 object_class->get_property = gtk_savebox_get_property;
229 g_object_class_install_property(object_class, PROP_HAS_DISCARD,
230 g_param_spec_boolean("has_discard",
231 "Has Discard",
232 "The dialog has a Discard button",
233 TRUE,
234 G_PARAM_READWRITE));
237 static void
238 gtk_savebox_init (GtkSavebox *savebox)
240 GtkWidget *alignment, *button;
241 GtkDialog *dialog = (GtkDialog *) savebox;
242 GtkTargetEntry targets[] = { {"XdndDirectSave0", 0, GTK_TARGET_XDS} };
244 gtk_dialog_set_has_separator (dialog, FALSE);
246 savebox->targets = gtk_target_list_new (targets,
247 sizeof (targets) / sizeof (*targets));
248 savebox->icon = NULL;
250 gtk_window_set_title (GTK_WINDOW (savebox), _("Save As:"));
251 gtk_window_set_position (GTK_WINDOW (savebox), GTK_WIN_POS_MOUSE);
252 gtk_window_set_wmclass (GTK_WINDOW (savebox), "savebox", "Savebox");
254 alignment = gtk_alignment_new (0.5, 0.5, 0, 0);
255 gtk_box_pack_start (GTK_BOX (dialog->vbox), alignment, TRUE, TRUE, 0);
257 savebox->drag_box = gtk_event_box_new ();
258 gtk_container_set_border_width (GTK_CONTAINER (savebox->drag_box), 4);
259 gtk_widget_add_events (savebox->drag_box, GDK_BUTTON_PRESS_MASK);
260 g_signal_connect (savebox->drag_box, "button_press_event",
261 G_CALLBACK (button_press_over_icon), savebox);
262 g_signal_connect (savebox, "drag_end",
263 G_CALLBACK (drag_end), savebox);
264 g_signal_connect (savebox, "drag_data_get",
265 G_CALLBACK (drag_data_get), savebox);
266 gtk_container_add (GTK_CONTAINER (alignment), savebox->drag_box);
268 savebox->entry = gtk_entry_new ();
269 g_signal_connect_swapped (savebox->entry, "activate",
270 G_CALLBACK (do_save), savebox);
271 gtk_box_pack_start (GTK_BOX (dialog->vbox), savebox->entry, FALSE, TRUE, 4);
273 gtk_widget_show_all (dialog->vbox);
274 gtk_widget_grab_focus (savebox->entry);
276 savebox->discard_area = gtk_hbutton_box_new();
278 button = button_new_mixed (GTK_STOCK_DELETE, "_Discard");
279 gtk_box_pack_start (GTK_BOX (savebox->discard_area), button, FALSE, TRUE, 2);
280 g_signal_connect (button, "clicked", G_CALLBACK (discard_clicked), savebox);
281 GTK_WIDGET_UNSET_FLAGS (button, GTK_CAN_FOCUS);
282 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
284 gtk_box_pack_end (GTK_BOX (dialog->vbox), savebox->discard_area,
285 FALSE, TRUE, 0);
286 gtk_box_reorder_child (GTK_BOX (dialog->vbox), savebox->discard_area, 0);
288 savebox->dnd_action = GDK_ACTION_COPY;
291 void
292 gtk_savebox_set_action (GtkSavebox *savebox, GdkDragAction action)
294 g_return_if_fail (savebox != NULL);
295 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
297 savebox->dnd_action = action;
300 GtkWidget*
301 gtk_savebox_new (const gchar *action)
303 GtkWidget *button;
304 GtkDialog *dialog;
305 GList *list, *next;
307 dialog = GTK_DIALOG (gtk_widget_new (gtk_savebox_get_type(), NULL));
309 gtk_dialog_add_button (dialog, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
311 button = button_new_mixed (GTK_STOCK_SAVE, action);
312 GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT);
313 gtk_widget_show (button);
314 gtk_dialog_add_action_widget (dialog, button, GTK_RESPONSE_OK);
316 gtk_dialog_set_default_response (dialog, GTK_RESPONSE_OK);
318 list = gtk_container_get_children (GTK_CONTAINER (dialog->action_area));
319 for (next = list; next; next = next->next)
320 GTK_WIDGET_UNSET_FLAGS (GTK_WIDGET(next->data), GTK_CAN_FOCUS);
321 g_list_free(list);
323 return GTK_WIDGET(dialog);
326 void
327 gtk_savebox_set_icon (GtkSavebox *savebox, GdkPixbuf *pixbuf)
329 g_return_if_fail (savebox != NULL);
330 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
331 g_return_if_fail (pixbuf != NULL);
333 if (savebox->icon)
334 gtk_image_set_from_pixbuf (GTK_IMAGE (savebox->icon), pixbuf);
335 else
337 savebox->icon = gtk_image_new_from_pixbuf (pixbuf);
338 gtk_container_add (GTK_CONTAINER (savebox->drag_box), savebox->icon);
339 gtk_widget_show(savebox->icon);
343 void
344 gtk_savebox_set_pathname (GtkSavebox *savebox, const gchar *pathname)
346 const gchar *slash, *dot;
347 gint leaf;
349 g_return_if_fail (savebox != NULL);
350 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
351 g_return_if_fail (pathname != NULL);
353 gtk_entry_set_text (GTK_ENTRY (savebox->entry), pathname);
355 slash = strrchr (pathname, '/');
357 leaf = slash ? g_utf8_pointer_to_offset(pathname, slash) + 1 : 0;
358 dot = strchr(pathname + leaf, '.');
360 gtk_editable_select_region (GTK_EDITABLE (savebox->entry), leaf,
361 dot ? g_utf8_pointer_to_offset (pathname, dot)
362 : -1);
365 void
366 gtk_savebox_set_has_discard (GtkSavebox *savebox, gboolean setting)
368 if (setting)
369 gtk_widget_show_all (savebox->discard_area);
370 else
371 gtk_widget_hide (savebox->discard_area);
374 static void
375 button_press_over_icon (GtkWidget *drag_box, GdkEventButton *event,
376 GtkSavebox *savebox)
378 GdkDragContext *context;
379 const gchar *uri = NULL, *leafname;
381 g_return_if_fail (savebox != NULL);
382 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
383 g_return_if_fail (event != NULL);
384 g_return_if_fail (savebox->icon != NULL);
386 savebox->using_xds = FALSE;
387 savebox->data_sent = FALSE;
388 context = gtk_drag_begin (GTK_WIDGET (savebox),
389 savebox->targets, savebox->dnd_action,
390 event->button, (GdkEvent *) event);
392 uri = gtk_entry_get_text (GTK_ENTRY (savebox->entry));
393 if (uri && *uri)
394 leafname = g_basename (uri);
395 else
396 leafname = _("Unnamed");
398 write_xds_property (context, leafname);
400 gtk_drag_set_icon_pixbuf (context,
401 gtk_image_get_pixbuf (GTK_IMAGE (savebox->icon)),
402 event->x, event->y);
406 static void
407 drag_data_get (GtkWidget *widget,
408 GdkDragContext *context,
409 GtkSelectionData *selection_data,
410 guint info,
411 guint32 time)
413 GtkSavebox *savebox;
414 guchar to_send = 'E';
415 gchar *uri;
416 gchar *pathname;
418 g_return_if_fail (widget != NULL);
419 g_return_if_fail (GTK_IS_SAVEBOX (widget));
420 g_return_if_fail (context != NULL);
421 g_return_if_fail (selection_data != NULL);
423 savebox = GTK_SAVEBOX (widget);
425 /* We're only concerned with the XDS protocol. Responding to other requests
426 * (including application/octet-stream) is the job of the application.
428 if (info != GTK_TARGET_XDS)
430 /* Assume that the data will be/has been sent */
431 savebox->data_sent = TRUE;
432 return;
435 uri = read_xds_property (context, FALSE);
437 if (uri)
439 gint result = GTK_XDS_NO_HANDLER;
441 pathname = get_local_path (uri);
442 if (!pathname)
443 to_send = 'F'; /* Not on the local machine */
444 else
446 g_signal_emit (widget, savebox_signals[SAVE_TO_FILE], 0,
447 pathname, &result);
448 g_free (pathname);
450 if (result == GTK_XDS_SAVED)
452 savebox->data_sent = TRUE;
453 to_send = 'S';
455 else if (result != GTK_XDS_SAVE_ERROR)
456 g_warning ("No handler for saving to a file.\n");
458 g_free (uri);
461 else
463 g_warning (_("Remote application wants to use Direct Save, but I can't "
464 "read the XdndDirectSave0 (type text/plain) property.\n"));
467 if (to_send != 'E')
468 savebox->using_xds = TRUE;
469 gtk_selection_data_set (selection_data, xa_string, 8, &to_send, 1);
472 static guchar *
473 read_xds_property (GdkDragContext *context, gboolean delete)
475 guchar *prop_text;
476 guint length;
477 guchar *retval = NULL;
479 g_return_val_if_fail (context != NULL, NULL);
481 if (gdk_property_get (context->source_window, XdndDirectSave, text_plain,
482 0, XDS_MAXURILEN, delete,
483 NULL, NULL, &length, &prop_text)
484 && prop_text)
486 /* Terminate the string */
487 retval = g_realloc (prop_text, length + 1);
488 retval[length] = '\0';
491 return retval;
494 static void
495 write_xds_property (GdkDragContext *context, const guchar *value)
497 if (value)
499 gdk_property_change (context->source_window, XdndDirectSave,
500 text_plain, 8, GDK_PROP_MODE_REPLACE,
501 value, strlen (value));
503 else
504 gdk_property_delete (context->source_window, XdndDirectSave);
507 static void drag_end (GtkWidget *widget, GdkDragContext *context)
509 g_return_if_fail (widget != NULL);
510 g_return_if_fail (GTK_IS_SAVEBOX (widget));
511 g_return_if_fail (context != NULL);
513 if (GTK_SAVEBOX (widget)->using_xds)
515 guchar *uri;
516 uri = read_xds_property (context, TRUE);
518 if (uri)
520 gchar *path;
522 path = get_local_path (uri);
524 g_signal_emit (widget, savebox_signals[SAVED_TO_URI], 0,
525 path ? path : (const gchar *) uri);
526 if (path)
527 g_free (path);
528 g_free(uri);
530 gtk_widget_destroy (widget);
532 return;
535 else
536 write_xds_property (context, NULL);
538 if (GTK_SAVEBOX (widget)->data_sent)
540 g_signal_emit (widget, savebox_signals[SAVED_TO_URI], 0, NULL);
541 gtk_widget_destroy (widget);
545 static void discard_clicked (GtkWidget *button, GtkWidget *savebox)
547 g_signal_emit (savebox, savebox_signals[SAVED_TO_URI], 0, NULL);
548 gtk_widget_destroy (savebox);
551 /* User has clicked Save or pressed Return... */
552 static void do_save (GtkSavebox *savebox)
554 gint result = GTK_XDS_NO_HANDLER;
555 const gchar *uri;
556 gchar *pathname;
558 g_return_if_fail (savebox != NULL);
559 g_return_if_fail (GTK_IS_SAVEBOX (savebox));
561 uri = gtk_entry_get_text (GTK_ENTRY (savebox->entry));
562 pathname = get_local_path (uri);
564 if (!pathname)
566 GtkWidget *dialog;
568 dialog = gtk_message_dialog_new (GTK_WINDOW (savebox),
569 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
570 GTK_MESSAGE_INFO, GTK_BUTTONS_OK,
571 _("Drag the icon to a directory viewer\n"
572 "(or enter a full pathname)"));
574 gtk_window_set_position (GTK_WINDOW (dialog), GTK_WIN_POS_CENTER);
576 gtk_dialog_run (GTK_DIALOG (dialog));
577 gtk_widget_destroy (dialog);
579 return;
582 g_signal_emit (savebox, savebox_signals[SAVE_TO_FILE], 0, pathname, &result);
584 if (result == GTK_XDS_SAVED)
586 g_signal_emit (savebox, savebox_signals[SAVED_TO_URI], 0, pathname);
588 gtk_widget_destroy (GTK_WIDGET (savebox));
590 else if (result == GTK_XDS_NO_HANDLER)
591 g_warning ("No handler for saving to a file.\n");
593 g_free(pathname);
596 static void
597 gtk_savebox_response (GtkDialog *savebox, gint response)
599 if (response == GTK_RESPONSE_OK)
601 do_save(GTK_SAVEBOX(savebox));
602 return;
604 else if (response == GTK_RESPONSE_CANCEL)
605 gtk_widget_destroy (GTK_WIDGET (savebox));
608 static void
609 gtk_savebox_set_property (GObject *object,
610 guint prop_id,
611 const GValue *value,
612 GParamSpec *pspec)
614 GtkSavebox *savebox;
616 savebox = GTK_SAVEBOX (object);
618 switch (prop_id)
620 case PROP_HAS_DISCARD:
621 gtk_savebox_set_has_discard (GTK_SAVEBOX(object),
622 g_value_get_boolean (value));
623 break;
625 default:
626 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
627 break;
631 static void
632 gtk_savebox_get_property (GObject *object,
633 guint prop_id,
634 GValue *value,
635 GParamSpec *pspec)
637 GtkSavebox *savebox;
639 savebox = GTK_SAVEBOX (object);
641 switch (prop_id)
643 case PROP_HAS_DISCARD:
644 g_value_set_boolean (value, GTK_WIDGET_VISIBLE(savebox->discard_area));
645 break;
647 default:
648 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
649 break;
653 void
654 marshal_INT__STRING (GClosure *closure,
655 GValue *return_value,
656 guint n_param_values,
657 const GValue *param_values,
658 gpointer invocation_hint,
659 gpointer marshal_data)
661 typedef gint (*GMarshalFunc_INT__STRING) (gpointer data1,
662 gpointer arg_1,
663 gpointer data2);
664 register GMarshalFunc_INT__STRING callback;
665 register GCClosure *cc = (GCClosure*) closure;
666 register gpointer data1, data2;
667 gint v_return;
669 g_return_if_fail (return_value != NULL);
670 g_return_if_fail (n_param_values == 2);
672 if (G_CCLOSURE_SWAP_DATA (closure))
674 data1 = closure->data;
675 data2 = g_value_peek_pointer (param_values + 0);
677 else
679 data1 = g_value_peek_pointer (param_values + 0);
680 data2 = closure->data;
682 callback = (GMarshalFunc_INT__STRING)
683 (marshal_data ? marshal_data : cc->callback);
685 v_return = callback (data1, param_values[1].data[0].v_pointer, data2);
687 g_value_set_int (return_value, v_return);