Sunday, 4 August 2013

Vala #9: Drag & Drop

It is often useful to add drag & drop functionality in GUI applications. It allows the user to transfer data from other applications and to manipulate UI objects within the same application.
It is pretty simple to add drag and drop functionality in a Vala program. The following code creates a GTK3 window. Dragging files from a file manager (like Nautilus) and dropping the files on the window will display the file names in a TreeView widget. We'll look at the code first and then go through the details.

Save the following code as dragdrop.vala:
using Gtk;

public class MainWindow : Gtk.Window
{
    private Box vboxMain;
    private ScrolledWindow swFiles;
    private TreeView tvFiles;
    private TreeViewColumn colName;

    
    private const Gtk.TargetEntry[] targets = {
        {"text/uri-list",0,0}
    };
    

    public static int main (string[] args) 
    {
        Gtk.init(ref args); 

        var window = new MainWindow (); 
        window.show_all (); 

        Gtk.main();

        return 0;
    }

    public MainWindow () 
    {
        this.title = "Drag files on this window";
        this.window_position = WindowPosition.CENTER;
        this.destroy.connect (Gtk.main_quit);
        set_default_size (550, 400);    

        //vboxMain
        vboxMain = new Box (Orientation.VERTICAL, 6);
        vboxMain.margin = 6;
        add (vboxMain);

        //tvFiles
        tvFiles = new TreeView();

        //swFiles
        swFiles = new ScrolledWindow(tvFiles.get_hadjustment (), tvFiles.get_vadjustment ());
        swFiles.set_shadow_type (ShadowType.ETCHED_IN);
        swFiles.set_size_request (550, 400);
        swFiles.add(tvFiles);
        vboxMain.add(swFiles);

        //colName
        colName = new TreeViewColumn();
        colName.title ="File";
        colName.expand = true;
        CellRendererText cellName = new CellRendererText ();
        colName.pack_start (cellName, false);
        colName.set_attributes(cellName, "text", 0);
        tvFiles.append_column(colName);

        //inputStore
        ListStore store = new ListStore (1, typeof (string));
        tvFiles.model = store;

        
        //connect drag drop handlers
        Gtk.drag_dest_set (this,Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY);
        this.drag_data_received.connect(this.on_drag_data_received);
        
    }

    
    private void on_drag_data_received (Gdk.DragContext drag_context, int x, int y, 
                                        Gtk.SelectionData data, uint info, uint time) 
    {
        //loop through list of URIs
        foreach(string uri in data.get_uris ()){
            string file = uri.replace("file://","").replace("file:/","");
            file = Uri.unescape_string (file);

            //add file to tree view
            add_file (file);
        }

        Gtk.drag_finish (drag_context, true, false, time);
    }
    

    private void add_file(string file)
    {
        TreeIter iter;
        ListStore store = (ListStore) tvFiles.model;
        store.append (out iter);
        store.set (iter, 0, file);
    }
}
Compile it:
valac --pkg gtk+-3.0 "dragdrop.vala"
Run the executable file:
./dragdrop
Drag files and directories from Nautilus (or any other file manager) and drop it on the window.

Code Walkthrough

The following code is added to the window constructor. It sets the current window as the drop target:
Gtk.drag_dest_set (this, Gtk.DestDefaults.ALL, targets, Gdk.DragAction.COPY);
this refers to the current window object which is the drop target.
targets is a struct of type Gtk.TargetEntry[]. It specifies the type of objects that can be dropped on the window.
private const Gtk.TargetEntry[] targets = {
    {"text/uri-list",0,0}
};
The mime-type text/uri-list specifies that the drop-target will accept only files and directories.
After setting the drop-target, we need to create a function which will handle the drag-drop event. The following code connects the function on_drag_data_received to the drag_data_received window event.
this.drag_data_received.connect(this.on_drag_data_received);
The function for handling the event is given below:
private void on_drag_data_received (Gdk.DragContext drag_context, int x, int y, 
                                                             Gtk.SelectionData data, uint info, uint time) 
{
    //loop through list of URIs
    foreach(string uri in data.get_uris ()){
        string file = uri.replace("file://","").replace("file:/","");
        file = Uri.unescape_string (file);

        //add file to tree view
        add_file (file);
    }

    Gtk.drag_finish (drag_context, true, false, time);
}
data.get_Uris() retrieves the list of URIs for the the files and directories that were dropped on the window. URIs are strings which contain the file path prefixed with file://. For example, the directory /usr/share/themes will have the URI file:///usr/share/themes. We can remove the prefix file:// to get the file path.
Once we get the file path we are adding it to the tree view by calling the function add_file().
private void add_file(string file)
{
    TreeIter iter;
    ListStore store = (ListStore) tvFiles.model;
    store.append (out iter);
    store.set (iter, 0, file);
}

1 comment:

If you are reporting an issue and commenting as an anonymous user, please leave your email address so that I can get in touch with you.