3

I am trying to separate single click events from double click events using glib.timeout_add() and glib.source_remove(). Here is how I am doing it :

class Exchange:

'''

some code

'''

    def __init__(self):

        self.timeoutID_1 = 0
        self.timeoutID_2 = 0

        self.startTime1 = 0.0
        self.stopTime1 = 0.0
        self.startTime2 = 0.0
        self.stopTime2 = 0.0

        '''

        some code

        '''

        ## THE DOUBLE CLICK SIGNAL - item-activated
        iconView1.connect("item-activated", self.on_item_activated_1,upButton1, store1)
        sw1.add(iconView1)

        iconView2.connect("item-activated", self.on_item_activated_2,upButton2, store2)
        sw2.add(iconView2)

        ## THE SINGLE CLICK SIGNAL - selection-changed

        iconView1.connect("selection-changed", self.on_selection_changed_1, copyButton1, cutButton1, pasteButton1, deleteButton1)
       iconView2.connect("selection-changed", self.on_selection_changed_2, copyButton2, cutButton2, pasteButton2, deleteButton2)

    def on_selection_changed_1(self, iconView1, copyButton1, cutButton1, pasteButton1, deleteButton1) :


        self.startTime1 = time.time()

        self.timeoutID_1 = glib.timeout_add(2000, self.selectIcon_1, iconView1, copyButton1, cutButton1, pasteButton1, deleteButton1)


    def on_selection_changed_2(self, iconView2, copyButton2, cutButton2, pasteButton2, deleteButton2) :

        self.startTime2 = time.time()

        self.timeoutID_2 = glib.timeout_add(2000, self.selectIcon_2, iconView2, copyButton2, cutButton2, pasteButton2, deleteButton2)


    def selectIcon_1(self, iconView1, copyButton1, cutButton1, pasteButton1, deleteButton1) :

        copyButton1.set_sensitive(True)
        cutButton1.set_sensitive(True)
        pasteButton1.set_sensitive(True)
        deleteButton1.set_sensitive(True)


    def selectIcon_2(self, iconView2, copyButton2, cutButton2, pasteButton2, deleteButton2) :


        copyButton2.set_sensitive(True)
        cutButton2.set_sensitive(True)
        pasteButton2.set_sensitive(True)
        deleteButton2.set_sensitive(True)

    def on_item_activated_1(self, iconView1, item, upButton1, store1) :

        self.stopTime1 = time.time()

        if self.stopTime1 - self.startTime1 < 1.50 :
            glib.source_remove(self.timeoutID_1)

        '''

        some code

        '''

    def on_item_activated_2(self, iconView2, item, upButton2, store2) :

        self.stopTime2 = time.time()

        if self.stopTime2 - self.startTime2 < 1.50 :
            glib.source_remove(self.timeoutID_2)

        '''

        some code

        '''

Despite self.stopTime - self.startTime <1.50 being True (signifying a valid double click) , the single click event is executed, however only once,after 2 secs, for each single-click. How can I completely cancel the execution of the selectIcon methods for a valid double click?

UPDATE

Following mtwebster's answer I tried to use button_press_event. Sadly though I am back to square one.

def on_button_press_event(self, widget, event) :
    if event.button == 1 :
        data = widget.get_path_at_pos(int(event.x), int(event.y))
        if data :
            if event.type == gtk.gdk._2BUTTON_PRESS :
                print " double click "

            elif event.type == gtk.gdk.BUTTON_PRESS :
                print " single click "

OUTPUT ::

vineet@vineet:~/Documents/Project$ python draft6.py
 single click 
 single click 
 double click 

Adding to my woes three click events are being executed for a double click, two single clicks and one double click!! Are there any other timing mechanisms I could use instead of glib.timeout_add() where I won't have to deal with uncertainty of the repeated call nonsense?

Flame of udun
  • 2,136
  • 7
  • 35
  • 79
  • Why would you want to handle all the `"button-press/release-event"` signals yourself? It is cumbersome to get the logic right. The documentation clearly says `"item-activated"` is DOUBLE-CLICK activated (if the `"activate-on-single-click"` property is set to `FALSE`, and some other foo) and `"selection-changes"` is usually single click activated (or arrow key fun). Can you elaborate what behaviour you want to achieve? – drahnr Apr 08 '14 at 11:10
  • @drahnr I did use those methods but for every single click in iconView `selection-changed` is triggered as well. I am building a filebrowser with cut/copy/paste/delete utility. So every single click on a file/folder selects for cut/copy etc. and every double click on a folder will help me view the contents of that folder – Flame of udun Apr 08 '14 at 12:05
  • 1
    I still do not see the issue. What exactly prevents you to use `"item-activated"` with callback for open/view folder fun and `"selection-changed"` for highlighting your selection accordingly? `button-press/release-events` are not required at all, your events are separated implicitly. Do I miss something? – drahnr Apr 08 '14 at 12:35
  • 1
    @drahnr I was too muddled up in my thoughts and hence missed the obvious. Did it using `item-activated` and `selection-changed` and it worked. Please add an answer and I'll give you the bounty rep. – Flame of udun Apr 08 '14 at 16:32

4 Answers4

0

Not really a valid answer I suppose... but have you considered just connecting to the 'on-button-press' signal of the IconView?

In the event callback, you can just let Gdk decide if it was a double-click event or single click by checking event->type (See https://developer.gnome.org/gdk3/stable/gdk3-Event-Structures.html#GdkEventButton).

mtwebster
  • 149
  • 1
  • 3
  • 1
    Then please try to upgrade it to a real answer. – Beryllium Apr 05 '14 at 19:03
  • @mtwebster can't find this signal in the [reference manual](http://www.pygtk.org/pygtk2reference/class-gtkiconview.html)...also `item-activated` provides me with a path to the icon activated and it's really helpful for the file browser project I am working on – Flame of udun Apr 06 '14 at 10:07
  • But I guess I could simply use the gdk part you suggested in my current event callbacks – Flame of udun Apr 06 '14 at 10:23
  • `on-button-press` is a GtkWidget signal (and since IconView is a widget, you can use it. In this event callback you can then call `gtk_icon_view_get_path_at_pos (iconView, event->x, event->y)` which will return a GtkTreePath for figuring out what item was actually clicked. A simplistic example in python: https://github.com/mtwebster/git-monkey/blob/master/usr/lib/git-monkey/git-monkey.py#L392 – mtwebster Apr 06 '14 at 11:39
  • A more complex example (in C): https://github.com/linuxmint/nemo/blob/master/src/nemo-list-view.c#L825 . Note, this callback is on a TreeView, not and IconView, so some of the methods and arguments are different, but it's the same principle. – mtwebster Apr 06 '14 at 11:54
  • `path, column, x, y = data` throws `ValueError: need more than 1 value to unpack` . The contents of `data` are `(7,)` – Flame of udun Apr 08 '14 at 04:24
0

I don't think removing the handler is a good strategy here.

I've done this sort of hack once -- to attach different logic to a single click (i.e. click-to-select) and double click (i.e. click-to-execute) to a row in a TreeView. In addition you probably want to support keyboard navigation and images.

I managed to get it working in the version of pygtk I worked on at the time. However a few minor gtk releases later and my code broke. It turned out to be impossible to support multiple versions of gtk with same code.

The most practical way was to track 2 items:

  • is the click coming onto an already-selected object (in my case row)
  • how and how long ago was this row selected
    • row was selected by default (first row when showing/switching view) -- ignore
    • row was selected by keyboard -- ignore
    • row was selected too long ago (say 300ms) -- ignore
    • else (selected by click, less than 300ms) -- execute
Dima Tisnek
  • 11,241
  • 4
  • 68
  • 120
0

You could schedule the single click event to happen after the double click duration, then if a double click happens, remove the scheduled single click events before the handler is run. For example:

import pygtk
pygtk.require('2.0')
import gtk
import glib
class Test:
    def __init__(self):
        # Create a new window
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title("Test")

        # It's a good idea to do this for all windows.
        self.window.connect("destroy", lambda wid: gtk.main_quit())
        self.window.connect("delete_event", lambda a1,a2:gtk.main_quit())

        # Sets the border width of the window.
        self.window.set_border_width(20)

        # Create a new button
        button = gtk.Button("Click")
        self.click_events = []

        # Connect the "clicked" signal of the button to our callback
        button.connect("button-press-event", self.on_button_press_event)
        button.show()

        self.window.add(button)
        self.window.show()

    def on_button_press_event(self, widget, event):
        if event.button == 1:
            if event.type == gtk.gdk._2BUTTON_PRESS:
                # Remove all single click events
                while self.click_events:
                    glib.source_remove(self.click_events.pop()) 
                glib.idle_add(self.handle_double_click,widget,event)
            elif event.type == gtk.gdk.BUTTON_PRESS:
                # Schedule after double click can occur
                self.click_events.append(glib.timeout_add(300,self.handle_single_click,widget,event))


    def handle_single_click(self,widget,event):
        # TODO: Handle single click
        print "TODO: Handle single click"


    def handle_double_click(self,widget,event):
        # TODO: Handle double click
        print "TODO: Handle double click"

def main():
    Test()
    gtk.main()
    return 0     

if __name__ == "__main__":
    main()
frmdstryr
  • 20,142
  • 3
  • 38
  • 32
0

Handling Double Click and Single Click Events in Python Gtk3

#!/usr/bin/python3
# -*- coding: utf-8 -*-

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

class Test:
    def __init__(self):
        # Create a new window
        self.window = Gtk.Window()
        self.window.set_title("Test")

        # It's a good idea to do this for all windows.
        self.window.connect("destroy", lambda wid: Gtk.main_quit())
        self.window.connect("delete_event", lambda a1,a2:Gtk.main_quit())

        # Sets the border width of the window.
        self.window.set_border_width(20)

        # Create a new button
        button = Gtk.Button(label = "Click")

        # Two lists for append the click events
        self.single_click_events = []
        self.double_click_events = []

        # Connect the "clicked" signal of the button to our callback
        button.connect("button-press-event", self.on_button_press_event)
        button.show()

        self.window.add(button)
        self.window.show()

    def on_button_press_event(self, widget, event):
        if event.button == 1:
            if event.type == Gdk.EventType._2BUTTON_PRESS:
                # Remove all single click events
                while self.single_click_events:
                    GLib.source_remove(self.single_click_events.pop()) 
                GLib.idle_add(self.handle_double_click,widget,event)
            elif event.type == Gdk.EventType.BUTTON_PRESS:
                # Schedule the callback 'self.handle_single_click' after double click can occur (after 300ms)
                self.single_click_events.append(GLib.timeout_add(300,self.handle_single_click,widget,event))


    def handle_single_click(self,widget,event):
        # TODO: Handle single click
        print("TODO: Handle single click")
        # Remove all single click events
        while self.single_click_events:
            GLib.source_remove(self.single_click_events.pop())


    def handle_double_click(self,widget,event):
        # TODO: Handle double click
        print("TODO: Handle double click")
        # Remove all double click events
        while self.double_click_events:
            GLib.source_remove(self.double_click_events.pop())

def main():
    Test()
    Gtk.main()
    return 0     

if __name__ == "__main__":
    main()