GTK+ Forums Forum Index GTK+ Forums
Discussion forum for GTK+ and Programming. Ask questions, troubleshoot problems, view and post example code, or express your opinions.
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

cross application drag n' drop (read file) for X windows (C)

 
Post new topic   Reply to topic    GTK+ Forums Forum Index -> GTK+ Example Code
Author Message
Mk27
GTK+ Guru


Joined: 07 Aug 2008
Posts: 100
Location: NYC

PostPosted: Wed Mar 11, 2009 8:41 pm    Post subject: cross application drag n' drop (read file) for X windows (C) Reply with quote

You may have noticed that you can drag and drop a file from another application (eg, the file browser) into a gtk textview without adding any code, but what you get is a network filepath:
Code: (Plaintext)
1
2
3

file:///home/alone/again/forexample.txt

If you want to actually display the file, you need to write drag n' drop signal handlers. I just went thru this yesturday and thought a basic task deserves some basic documentation, so here it is. I'm incorporating this into a project I'm doing for debian, and have tested it on a few linux flavors, with and without GNOME. Most of the necessary information was acquired from from the gtk+ API and from http://live.gnome.org/GnomeLove/DragNDropTutorial, which you will probably want to read first or look at the same time. I am not covering anything to do with how to create a pickup-able item -- just how to get one via the gtk/X window interface.

So I won't provide my own sketch of the concept either (just kidding). It's pretty simple -- the mouse pointer enters the geometry of the widget you want as a drop site iconically carrying a dragged file from another window. In this example, we use a textview into which you can drop a text file and read it. If you use a normal, editable textview, you only need two signal handlers, one for the drop event (to send a request to the source app for data), and one to receive the response to the request. If you use a read-only or "non-editable" textview, gtk unfortunately overrides the drop operation, so you cannot catch a signal. In that case, which is our example, you need four signal handlers, one to detect when the mouse enters the widget so we can turn "editable" on, and one to detect when the drop potential is over (that could be because data gets dropped, or it could be because the mouse pointer left the area) so that we can turn "editable" off again.

The data we actually receive is the network pathname that would have been printed in the textview, if it were not for our custom handler.

This is small text area into which you can drag and drop local files from nautilus, firefox, gedit, etc. The file is not moved anywhere; it just gets displayed.
Code: (C)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117

#include <gtk/gtk.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>

/* four signal handler callbacks */
void DnDreceive (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *data, guint ttype, guint time, gpointer *NA);
gboolean DnDdrop (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer *NA);
void DnDleave (GtkWidget *widget, GdkDragContext *context, guint time, gpointer *NA);
gboolean DnDmotion (GtkWidget *widget, GdkDragContext *context, gint x, gint y, GtkSelectionData *seld, guint ttype, guint time, gpointer *NA);

gboolean DND=FALSE; /* "in play" flag */

int main (int argc, char *argv[]) {
    GtkWidget *window, *textarea;
    /* this may not matter too much, they are MIME types; I found these in the gedit source (C) Maggi, Borelli, et. al. */
    /* (amongst other places) */
   
const GtkTargetEntry targets[2] = { {"text/plain",0,0}, { "application/x-rootwindow-drop",0,0 } };
   
        gtk_init (&argc, &argv);
   
        window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
        gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_window_set_title(GTK_WINDOW (window), "Drag N' Drop");
        g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
        g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
   
    textarea = gtk_text_view_new();       /* read-only text view */
   
gtk_widget_set_size_request(GTK_WIDGET (textarea), 300, 500);
    gtk_text_view_set_editable(GTK_TEXT_VIEW(textarea),FALSE);
    gtk_container_add (GTK_CONTAINER (window), textarea);
    gtk_drag_dest_set(textarea,GTK_DEST_DEFAULT_ALL,targets,2,GDK_ACTION_COPY);
    g_signal_connect(textarea,"drag-drop",G_CALLBACK(DnDdrop),NULL);
    g_signal_connect(textarea,"drag-motion",G_CALLBACK(DnDmotion),NULL);
    g_signal_connect(textarea,"drag-data-received",G_CALLBACK(DnDreceive),NULL);
        g_signal_connect (textarea, "drag-leave",G_CALLBACK(DnDleave),NULL);
 
        gtk_widget_show_all(window);
        gtk_main ();
        return 0;
}


void DnDreceive (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
GtkSelectionData *data, guint ttype, guint time, gpointer *NA) {
    GtkTextBuffer *text = gtk_text_view_get_buffer(GTK_TEXT_VIEW (widget));
    struct stat info;
    FILE *fh;    /* we have to read the file ourself */
   
int len;
    gboolean got=TRUE;    /* to bounce inappropriate data */
   
gchar *ptr=(char*)data->data, *buffer;
    /* ^ treat everything as char, then test... */
   
if ((strncmp(ptr,"file:///",8)!=0) || (strlen(ptr)>4096)) got=FALSE;
        gtk_drag_finish (context, got, FALSE, time);    /* tell source app not to erase the file */
   
if (!(got)) return;

    ptr+=(7*sizeof(char));    /* making this an absolute path... */
   
len=strlen(ptr)-1;
    /* some systems may terminate with network proper \r\n, etc, so defluff */
   
while ((ptr[len]==' ') || (ptr[len]=='\n') || (ptr[len]=='\r'))
        { ptr[len]='\0'; len--; }
            /* print the file contents into the text area */
   
if (stat(ptr,&info)<0) { perror("stat"); return; }
    if (!(fh=fopen(ptr,"r"))) { perror("fopen"); return; }   
    len=(int)info.st_size;
    buffer=malloc(len);
    if (fread(buffer,1,len,fh)!=len) perror("fread");           
    fclose(fh);
    gtk_text_buffer_set_text(text,buffer,len);     
        free(buffer);   
}


gboolean DnDdrop (GtkWidget *widget, GdkDragContext *context, gint x, gint y, guint time, gpointer *NA) {
        GdkAtom target_type;
/* adapted from "TestDnD - main.c : Simple tutorial for GTK+ Drag-N-Drop" Copyright (C) 2005 Ryan McDougall */
/* http://live.gnome.org/GnomeLove/DragNDropTutorial */
/* In this version, we will accept anything the source wants to give us */
        /* context->targets recieved from source app */
       
if (context-> targets) {
                target_type = GDK_POINTER_TO_ATOM (g_list_nth_data (context-> targets, 0)); /* Choose the best target type */
       
gtk_drag_get_data (
            widget,         /* "widget" should now receive 'drag-data-received' signal */
           
context,        /* represents the current state of the DnD */
           
target_type,    /* the target type we want */
           
time            /* our time stamp */
       
);
    }
    else return FALSE;      /* cancel */
   
return TRUE;
}


void DnDleave (GtkWidget *widget, GdkDragContext *context, guint time, gpointer *NA) {
    GtkWidget *window=gtk_widget_get_toplevel(widget);
    gtk_window_set_title(GTK_WINDOW (window), "Drop is Gone...");
    gtk_text_view_set_editable(GTK_TEXT_VIEW(widget),TRUE);   
    DND=FALSE;
}


gboolean DnDmotion (GtkWidget *widget, GdkDragContext *context, gint x, gint y,
GtkSelectionData *seld, guint ttype, guint time, gpointer *NA) {
    GtkWidget *window;
    if (DND) return TRUE;    /* if we are already there, that is good enough
You can comment out the last line and uncomment the next one to watch
the drag coordinates in the console while moving thru the text area */
    // g_print("DnDmotion() %d %d\n",x,y,); fflush(stdout); }
   
gtk_text_view_set_editable(GTK_TEXT_VIEW(widget),FALSE);   
    DND=TRUE;   
    window=gtk_widget_get_toplevel(widget);
    gtk_window_set_title(GTK_WINDOW (window), "Entered Drop Zone!");
    return TRUE;
}

A little more than the "few dozen lines" I was hoping for yesturday -- but the essence of the drop onto most widgets would be less than half of this, so close.
Back to top
Mk27
GTK+ Guru


Joined: 07 Aug 2008
Posts: 100
Location: NYC

PostPosted: Thu Apr 30, 2009 3:12 pm    Post subject: Re: cross application drag n' drop (read file) for X windows Reply with quote

Mk27 wrote:

This is small text area into which you can drag and drop local files from nautilus, firefox, gedit, etc.


In fact, after I did this I noticed that while it works with most apps, it rejects files from nautilus if nautilus is in list (not icon) view. So I finally got around to correcting this...

The reason, it turns out, is that nautilus is passing the files as an url mime type instead of text/plain. In this course of my investigation I found an easier way to set the target types in the gtk API as well, so that you do not have to list a bunch of mime types.

Here's the changes to the above code (they all take place in main):
Code: (C)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

int main (int argc, char *argv[]) {
    GtkWidget *window, *textarea;
    /*a GtkTargetEntry array is no longer needed */
               
   
gtk_init (&argc, &argv);
   
    window = gtk_window_new (GTK_WINDOW_TOPLEVEL);
    gtk_container_set_border_width (GTK_CONTAINER (window), 10);
    gtk_window_set_title(GTK_WINDOW (window), "Drag N' Drop");
    g_signal_connect (G_OBJECT (window), "delete_event", G_CALLBACK (gtk_main_quit), NULL);
    g_signal_connect (G_OBJECT (window), "destroy", G_CALLBACK (gtk_main_quit), NULL);
   
    textarea = gtk_text_view_new();       /* read-only text view */
   
gtk_widget_set_size_request(GTK_WIDGET (textarea), 300, 500);
    gtk_text_view_set_editable(GTK_TEXT_VIEW(textarea),FALSE);
    gtk_container_add (GTK_CONTAINER (window), textarea);
   
           /* set targets to NULL */
   
gtk_drag_dest_set(textarea,GTK_DEST_DEFAULT_ALL,NULL,0,GDK_ACTION_COPY);
           /* now add text targets */
   
gtk_drag_dest_add_text_targets(textarea);
           /* and to include nautilus list view drops: */
   
gtk_drag_dest_add_uri_targets(textarea);

    g_signal_connect(textarea,"drag-drop",G_CALLBACK(DnDdrop),NULL);
    g_signal_connect(textarea,"drag-motion",G_CALLBACK(DnDmotion),NULL);
    g_signal_connect(textarea,"drag-data-received",G_CALLBACK(DnDreceive),NULL);
    g_signal_connect (textarea, "drag-leave",G_CALLBACK(DnDleave),NULL);

That's it!
Back to top
hannenz
Familiar Face


Joined: 08 Oct 2009
Posts: 16

PostPosted: Thu Oct 29, 2009 7:18 pm    Post subject: Reply with quote

thanks a lot for this one! I think DnD in GTK+ is a subject still much too less documented!
Back to top
Display posts from previous:   
Post new topic   Reply to topic    GTK+ Forums Forum Index -> GTK+ Example Code All times are GMT
Page 1 of 1

 


Powered by phpBB © 2001, 2005 phpBB Group
CodeBB 1.0 Beta 2
Protected by Anti-Spam ACP