PyGObject: Drag and Drop with ToolPalette

I have been able to get drag and drop working from a Gtk.ToolPalette, but only when setting Gtk.ToolButton.set_use_drag_window(True) . However when clicking on a ToolButton to drag and drop it, it does not result in the button actually being visually clicked. I understand this is because set_use_drag_window causes all events (even button clicks) to be intercepted as a drag event.

The docs say that the easiest way to use drag and drop with Gtk.ToolPalette is to call Gtk.ToolPalette.add_drag_dest() with the desired drag source palette and the desired drag target widget. This is kind of the opposite of what I need based on the complexity of the GUI app, since I need to setup the ToolPalette and then add a callback to the drag source after the DrawingArea is created.

I have inherited from the Gtk.TooPalette , created a Gtk.ToolItemGroup for each section of the palette, and then I am creating the buttons:

def toolbox_button(self, action_name, stock_id):
    button = Gtk.ToolButton.new_from_stock(stock_id)
    button.action_name = action_name
    button.set_use_drag_window(True)

    # Enable Drag and Drop
    button.drag_source_set(
        Gdk.ModifierType.BUTTON1_MASK,
        self.DND_TARGETS,
        Gdk.DragAction.COPY | Gdk.DragAction.LINK,
    )
    button.drag_source_set_icon_stock(stock_id)
    button.connect("drag-data-get", self._button_drag_data_get)

    return button

On the DrawingArea I am then making it a drag dest:

    view.drag_dest_set(
        Gtk.DestDefaults.MOTION,
        DiagramPage.VIEW_DND_TARGETS,
        Gdk.DragAction.MOVE | Gdk.DragAction.COPY | Gdk.DragAction.LINK,
    )

Is there a way to get drag and drop to work with the ToolPalette while still allowing the buttons to work normally?

My fellow contributor dug in to the GTK source code and figured out that the Gtk.ToggleToolButton actually has a child button which isn’t currently documented. Drag and drop works if you set the drag source to this “inner button”.

def toolbox_button(action_name, stock_id, label, shortcut):
    button = Gtk.ToggleToolButton.new()
    button.set_icon_name(stock_id)
    button.action_name = action_name
    if label:
        button.set_tooltip_text("%s (%s)" % (label, shortcut))

    # Enable Drag and Drop
    inner_button = button.get_children()[0]
    inner_button.drag_source_set(
        Gdk.ModifierType.BUTTON1_MASK | Gdk.ModifierType.BUTTON3_MASK,
        self.DND_TARGETS,
        Gdk.DragAction.COPY | Gdk.DragAction.LINK,
    )
    inner_button.drag_source_set_icon_stock(stock_id)
    inner_button.connect(
        "drag-data-get", self._button_drag_data_get, action_name
    )

    return button

What would be the best way to resolve this? I have never contributed to GTK before, would a PR be welcome to improve the C documentation?

So, does Gtk.ToolPalette.add_drag_dest do what you need, except that it requires you to specify the destination widget? If so, you should be able to use Gtk.ToolPalette.set_drag_source when you make the palette, and then call Gtk.Widget.drag_dest_set on the DrawingArea. That is what Gtk.ToolPalette.add_drag_dest does internally.

That works good. Tried it out. Not sure of a different way of going about it.

Eric

import gi
gi.require_version("Gtk", "3.0")
from gi.repository import Gtk, Gdk

class MainWindow(Gtk.Window):
    def __init__(self):
        Gtk.Window.__init__(self, title="Drag and Drop")
        self.set_position(Gtk.WindowPosition.CENTER)
        self.set_default_size(300, 200)

        #Just change a color with dnd.
        self.color = 0

        self.button = Gtk.ToolButton.new(label = "Click or Drag")
        self.button.set_hexpand(True)
        '''
        self.button.set_use_drag_window(True)
        self.button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.MOVE)
        self.button.drag_source_set_target_list(None)
        self.button.drag_source_add_text_targets()
        self.button.connect("drag-data-get", self.on_drag_data_get)
        '''
        self.button.connect("clicked", self.button_down)
        
        self.inner_button = self.button.get_children()[0]
        self.inner_button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.MOVE)
        self.inner_button.drag_source_set_target_list(None)
        self.inner_button.drag_source_add_text_targets()
        self.inner_button.connect("drag-data-get", self.on_drag_data_get)
        
        self.da = Gtk.DrawingArea()
        self.da.set_hexpand(True)
        self.da.set_vexpand(True)
        self.da.connect("draw", self.da_draw)      
        self.da.drag_dest_set(Gtk.DestDefaults.ALL, [], Gdk.DragAction.MOVE)
        self.da.drag_dest_set_target_list(None)
        self.da.drag_dest_add_text_targets()  
        self.da.connect("drag-data-received", self.on_drag_data_received)

        self.grid = Gtk.Grid()
        self.grid.attach(self.button, 0, 0, 1, 1)
        self.grid.attach(self.da, 0, 1, 1, 1)
        self.add(self.grid)

    def button_down(self, args):
        print("Button Clicked")
       
    def on_drag_data_get(self, widget, drag_context, data, info, time):
        print("Data Get")
        if(self.color == 0): 
            self.color = 1
        else:
            self.color = 0
        data.set_text("Button Dragged", -1)
        self.da.queue_draw()

    def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
        print("Data Received " + data.get_text())

    def da_draw(self, wid, cr):
        if(self.color == 0):
            cr.set_source_rgb(0.0 , 0.0 , 1.0)
        else:
            cr.set_source_rgb(0.0 , 1.0 , 0.0)
        cr.paint()
                     
win = MainWindow()
win.connect("delete-event", Gtk.main_quit)
win.show_all()
Gtk.main()

I will see if I can try to make that work, I definitely haven’t had success so far, but I will give it another shot.

@cecashon It looks like you made use of the inner button as well to get it to work. Thanks for providing an example!

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.