I didn't get a response to this question regarding the right way to
block a signal. I have reworked my example to be much much smaller so
that hopefully it garners some eyeballs.
Again, if my approach is fundamentally wrong, please let me know.
To reiterate the problem: I basically want two widgets to affect each
other's values. But if I do this naievely, like so...
Spinbutton
on_value_changed:
Entry.set_text()
Entry
on_changed:
Spinbutton.set_value()
That causes an infinite loop. My idea is to block the signals before
calling set_text() or set_value(), but, as the attached example will
show, I get an error (min_clock.py:29: Warning: gsignal.c:1695: instance
`0xb25840' has no handler with id `46')
Any guidance would be appreciated.
Shandy
On Tue, 2007-11-20 at 11:03 -0800, Shandy Brown wrote:
> I'm trying to write a clock widget. I want it to look nice and to
> behave in a friendly manner.
>
> * It should show colons between the hours, minutes, and seconds
> * typing the time should "skip" over the colons
>
> I've tried to implement this by 5 sequential text entry widgets, each
> without a frame so they look like they're part of the same widget, laid
> out like this:
>
> +----+---+----+---+----+
> | hh | : | mm | : | ss |
> +----+---+----+---+----+
>
> if the time is 10:20, I want focus to default to the hours, and the user
> can type in "1020".
>
> The best way I can think of doing this is by detecting that on the
> keypress for "2", the number is out of bounds, then blocking the signals
> for the minute text entry, and setting it's value to 2.
>
> However, sometimes when I try to block the signals, I get the message
>
> Warning: gsignal.c:1695: instance `0xaf6800' has no handler with id `115'
>
> Which doesn't make sense to me, because I 115 definitely exists, as I
> stored it previously.
>
> I've attached the code which will exhibit the behaviour. Just type
> "python clock.py"
>
> Any suggestions? Anyone know why signal 115 disappears?
>
> -sjbrown
> _______________________________________________
> pygtk mailing list [email protected]
> http://www.daa.com.au/mailman/listinfo/pygtk
> Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/
#! /usr/bin/python
'''
module docstring goes here.
'''
import gtk
from gtk import gdk
import gtk.glade
import re
#-----------------------------------------------------------------------------
def SignalLoopProtected( widgetName, signalName ):
'''This decorator ensures that a loop won't be created from a callback
continually calling itself in a loop, or a loop between a pair of callbacks
that call each other. It takes the callback, "origFunc" and wraps it
in calls to handler_block and handler_unblock.
Requirements:
a handler ID has to be created and stored in the instance. It has to be
accessible by calling GetHandlerID(widgetName, signalName)
'''
def Decorated( origFunc ):
def NewFunc( self, widget, *args ):
assert hasattr(widget, 'handler_block') \
and hasattr(self,'GetHandlerID'), \
'SignalLoopProtected can only decorate GTK callback funcs that'+\
' can be retrieved from self.GetHandlerID(widgetName,signalName)'
handlerID = self.GetHandlerID(widgetName, signalName)
print "++ Blocking signal %s id: %d" % (signalName, handlerID)
widget.handler_block( handlerID )
rc = origFunc( self, widget, *args )
print '-- Unblocking signal %s id: %d' % (signalName, handlerID)
widget.handler_unblock( handlerID )
return rc
NewFunc.func_name = origFunc.func_name
return NewFunc
return Decorated
#-----------------------------------------------------------------------------
class GladeInspectingAutoconnecter(object):
'''Base class that inspects Glade XML and autoconnects
matching names to the instance
Provides Autoconnect(), which should be called at the end of __init__,
and GetHandlerID, which is useful for the above decorator,
SignalLoopProtected.
'''
# Regular expression to match handler method names patterns
# On_widget__signal and After_widget__signal. Note that we use two
# underscores between the Glade widget name and the signal name.
handler_re = re.compile(r'(On|After)_(.*)__(.*)', re.IGNORECASE)
def Autoconnect(self, xml):
"""Connects signal handling methods to Glade widgets.
Methods named like On_widget__signal or After_widget__signal
are connected to the appropriate widgets and signals.
"""
self._handlerIDs = {}
for attr in dir(self):
match = self.handler_re.match(attr)
if not match:
continue
when, widgetName, signalName = match.groups()
method = getattr(self, attr)
assert callable(method), 'Instance attr looks like method name'
widget = xml.get_widget(widgetName)
if not widget:
continue
handlerID = widget.connect(signalName.replace('_', '-'), method)
self._handlerIDs[(widgetName, signalName)] = handlerID
print 'All handlers found:'
for k,v in self._handlerIDs.items():
print "%40s : %5s" % (k,v)
def GetHandlerID( self, widgetName, signalName ):
return self._handlerIDs[(widgetName, signalName)]
class Clock(GladeInspectingAutoconnecter):
def __init__(self, gladeXML):
#self.xml = gtk.glade.XML("clock.glade")
self.xml = gladeXML
self.hours = self.xml.get_widget("clock_hours")
self.clockSpin = self.xml.get_widget("clockspin")
self.Autoconnect(self.xml)
self.lastFocusedClockEntry = self.hours
self.pendingChangeToClockSpin = None
def on_clockentry__focus(self, widget, *args):
print '\n on_clockentry_focus'
self.lastFocusedClockEntry = widget
# set up the clock spin widget...
digits = widget.get_text() #order is important. get text before set_range.
if digits:
print ' last focused text', digits
self.clockSpin.set_value(int(digits))
on_clock_hours__focus = on_clockentry__focus
def on_clockentry__insert_at_cursor(self, widget, *args):
print '\n on_clockentry_insert_at_cursor'
print widget, args
on_clock_hours__insert_at_cursor = on_clockentry__insert_at_cursor
@SignalLoopProtected('clockspin', 'change_value')
def on_clockentry__insert_text(self, widget, newText, *args):
#NOTE: changed happens BEFORE clockentry_changed
print '\n on_clockentry_insert_text. Widget:', widget, '!'
print '\n on_clockentry_insert_text. Widget:', widget.get_name(), '!'
text = widget.get_text()
print ' cur text', text, 'newText:', newText
fullText = text+newText
print ' fulltext is ', fullText
try:
self.pendingChangeToClockSpin = int(fullText)
except ValueError:
pass
print ' RETURN on_clockentry_insert_text. '
return False
on_clock_hours__insert_text = on_clockentry__insert_text
def on_clockentry__changed(self, widget, *args):
#NOTE: changed happens AFTER insert_text
print '\n on_clockentry_changed'
print ' widget:', widget, 'args:', args
text = widget.get_text()
print ' input text:', text
#allow only numeric characters
justDigits = filter(str.isdigit, text)
if not justDigits:
widget.set_text('')
return
widget.set_text(justDigits)
if self.pendingChangeToClockSpin:
self.clockSpin.set_value(self.pendingChangeToClockSpin)
self.pendingChangeToClockSpin = None
on_clock_hours__changed = on_clockentry__changed
@SignalLoopProtected('clock_hours', 'changed')
def on_clockspin__value_changed(self, *args):
print '\n on_clockspin__value_changed', args
strValue = str(int(self.clockSpin.get_value_as_int()))
self.lastFocusedClockEntry.set_text(strValue)
return False
def on_clockspin__change_value(self, *args):
print '\n on_clockspin_change_value (KEYBOARD input)', args
return False
#-------------------------------------------------------------------------------
example_xml = '''<?xml version="1.0" standalone="no"?> <!--*- mode: xml -*-->
<!DOCTYPE glade-interface SYSTEM "http://glade.gnome.org/glade-2.0.dtd">
<glade-interface>
<widget class="GtkWindow" id="window1">
<property name="width_request">200</property>
<property name="height_request">200</property>
<property name="visible">True</property>
<signal name="destroy_event" handler="on_window1_destroy_event" />
<signal name="delete_event" handler="on_window1_delete_event" />
<child>
<widget class="GtkHBox" id="clock_hbox">
<property name="visible">True</property>
<property name="homogeneous">False</property>
<property name="spacing">0</property>
<child>
<widget class="GtkEntry" id="clock_hours">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="events">GDK_FOCUS_CHANGE_MASK</property>
<property name="editable">True</property>
<property name="visibility">True</property>
<property name="max_length">2</property>
<property name="text">00</property>
<property name="activates_default">False</property>
<property name="width_chars">2</property>
<signal name="activate" handler="on_clockentry_activate" />
<signal name="changed" handler="on_clockentry_changed" />
<signal name="insert_text" handler="on_clockentry_insert_text" />
<signal name="focus_in_event" handler="on_clockentry_focus" />
</widget>
</child>
<child>
<widget class="GtkSpinButton" id="clockspin">
<property name="visible">True</property>
<property name="wrap">True</property>
<property name="adjustment">0 0 1 1 1 1</property>
<signal name="value_changed" handler="on_clockspin__value_changed" />
</widget>
</child>
</widget>
</child>
</widget>
</glade-interface>
'''
class TestingClock(Clock):
def __init__(self):
xml = gtk.glade.xml_new_from_buffer(example_xml, len(example_xml))
Clock.__init__(self, xml)
self.xml.signal_autoconnect(self)
def on_window1_delete_event(self, *args):
print 'delete!'
gtk.main_quit()
def on_window1_destroy_event(self, *args):
print 'destroy!'
gtk.main_quit()
if __name__ == '__main__':
c = TestingClock()
gtk.main()
_______________________________________________
pygtk mailing list [email protected]
http://www.daa.com.au/mailman/listinfo/pygtk
Read the PyGTK FAQ: http://www.async.com.br/faq/pygtk/