GTK+ Forums

Discussion forum for GTK+ and Programming. Ask questions, troubleshoot problems, view and post example code, or express your opinions.
It is currently Sat Oct 25, 2014 11:36 pm

All times are UTC




Post new topic Reply to topic  [ 3 posts ] 
Author Message
 Post subject: cross application drag n' drop (read file) for X windows (C)
PostPosted: Wed Mar 11, 2009 8:41 pm 
Offline
GTK+ Guru

Joined: Thu Aug 07, 2008 12:23 am
Posts: 103
Location: NYC
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:
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:
#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.


Top
 Profile  
 
 Post subject: Re: cross application drag n' drop (read file) for X windows
PostPosted: Thu Apr 30, 2009 3:12 pm 
Offline
GTK+ Guru

Joined: Thu Aug 07, 2008 12:23 am
Posts: 103
Location: NYC
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:
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!


Top
 Profile  
 
 Post subject:
PostPosted: Thu Oct 29, 2009 7:18 pm 
Offline
Familiar Face

Joined: Thu Oct 08, 2009 1:46 pm
Posts: 16
thanks a lot for this one! I think DnD in GTK+ is a subject still much too less documented!


Top
 Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 3 posts ] 

All times are UTC


Who is online

Users browsing this forum: No registered users and 2 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group