Skip to content

Commit

Permalink
add an 'auto_sync' submit trigger to Field
Browse files Browse the repository at this point in the history
  • Loading branch information
sccolbert committed Aug 12, 2013
1 parent 7fe1d99 commit 1926cde
Show file tree
Hide file tree
Showing 3 changed files with 95 additions and 53 deletions.
68 changes: 44 additions & 24 deletions enaml/qt/qt_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

from enaml.widgets.field import ProxyField

from .QtCore import Signal
from .QtCore import QTimer, Signal
from .QtGui import QLineEdit

from .qt_control import QtControl
Expand Down Expand Up @@ -45,6 +45,9 @@ class QtField(QtControl, ProxyField):
#: A reference to the widget created by the proxy.
widget = Typed(QFocusLineEdit)

#: A collapsing timer for auto sync text.
_text_timer = Typed(QTimer)

#: Cyclic notification guard. This a bitfield of multiple guards.
_guard = Int(0)

Expand Down Expand Up @@ -72,10 +75,8 @@ def init_widget(self):
self.set_echo_mode(d.echo_mode)
self.set_max_length(d.max_length)
self.set_read_only(d.read_only)
widget = self.widget
widget.lostFocus.connect(self.on_lost_focus)
widget.returnPressed.connect(self.on_return_pressed)
widget.textEdited.connect(self.on_text_edited)
self.set_submit_triggers(d.submit_triggers)
self.widget.textEdited.connect(self.on_text_edited)

#--------------------------------------------------------------------------
# Private API
Expand Down Expand Up @@ -115,27 +116,15 @@ def _clear_error_state(self):
#--------------------------------------------------------------------------
# Signal Handlers
#--------------------------------------------------------------------------
def on_lost_focus(self):
""" The signal handler for 'lostFocus' signal.
"""
if 'lost_focus' in self.declaration.submit_triggers:
self._guard |= TEXT_GUARD
try:
self._validate_and_apply()
finally:
self._guard &= ~TEXT_GUARD

def on_return_pressed(self):
""" The signal handler for 'returnPressed' signal.
def on_submit_text(self):
""" The signal handler for the text submit triggers.
"""
if 'return_pressed' in self.declaration.submit_triggers:
self._guard |= TEXT_GUARD
try:
self._validate_and_apply()
finally:
self._guard &= ~TEXT_GUARD
self._guard |= TEXT_GUARD
try:
self._validate_and_apply()
finally:
self._guard &= ~TEXT_GUARD

def on_text_edited(self):
""" The signal handler for the 'textEdited' signal.
Expand All @@ -149,6 +138,8 @@ def on_text_edited(self):
else:
self._clear_error_state()
self.widget.setToolTip(u'')
if self._text_timer is not None:
self._text_timer.start()

#--------------------------------------------------------------------------
# ProxyField API
Expand All @@ -167,6 +158,35 @@ def set_mask(self, mask):
"""
self.widget.setInputMask(mask)

def set_submit_triggers(self, triggers):
""" Set the submit triggers for the widget.
"""
widget = self.widget
handler = self.on_submit_text
try:
widget.lostFocus.disconnect(handler)
except TypeError: # was never connected
pass
try:
widget.returnPressed.disconnect()
except TypeError: # was never connected
pass
if 'lost_focus' in triggers:
widget.lostFocus.connect(handler)
if 'return_pressed' in triggers:
widget.returnPressed.connect(handler)
if 'auto_sync' in triggers:
if self._text_timer is None:
timer = self._text_timer = QTimer()
timer.setSingleShot(True)
timer.setInterval(300)
timer.timeout.connect(handler)
else:
if self._text_timer is not None:
self._text_timer.stop()
self._text_timer = None

def set_placeholder(self, text):
""" Set the placeholder text of the widget.
Expand Down
14 changes: 10 additions & 4 deletions enaml/widgets/field.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ def set_text(self, text):
def set_mask(self, mask):
raise NotImplementedError

def set_submit_triggers(self, triggers):
raise NotImplementedError

def set_placeholder(self, placeholder):
raise NotImplementedError

Expand Down Expand Up @@ -87,9 +90,12 @@ class Field(Control):

#: The list of actions which should cause the client to submit its
#: text to the server for validation and update. The currently
#: supported values are 'lost_focus' and 'return_pressed'.
#: supported values are 'lost_focus', 'return_pressed', and 'auto'.
#: The 'auto_sync' mode will attempt to validate and synchronize the
#: text when the user stops typing.
submit_triggers = d_(List(
Enum('lost_focus', 'return_pressed'), ['lost_focus', 'return_pressed']
Enum('lost_focus', 'return_pressed', 'auto_sync'),
['lost_focus', 'return_pressed']
))

#: The grayed-out text to display if the field is empty and the
Expand Down Expand Up @@ -119,8 +125,8 @@ class Field(Control):
#--------------------------------------------------------------------------
# Observers
#--------------------------------------------------------------------------
@observe(('text', 'placeholder', 'echo_mode', 'max_length', 'read_only',
'submit_triggers', 'validator'))
@observe(('text', 'mask', 'submit_triggers', 'placeholder', 'echo_mode',
'max_length', 'read_only'))
def _update_proxy(self, change):
""" An observer which sends state change to the proxy.
Expand Down
66 changes: 41 additions & 25 deletions enaml/wx/wx_field.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,9 @@ class WxField(WxControl, ProxyField):
#: A reference to the widget created by the proxy.
widget = Typed(wxLineEdit)

#: A collapsing timer for auto sync text.
_text_timer = Typed(wx.Timer)

#: Cyclic notification guard. This a bitfield of multiple guards.
_guard = Int(0)

Expand Down Expand Up @@ -186,10 +189,8 @@ def init_widget(self):
self.set_echo_mode(d.echo_mode)
self.set_max_length(d.max_length)
self.set_read_only(d.read_only)
widget = self.widget
widget.Bind(wx.EVT_KILL_FOCUS, self.on_lost_focus)
widget.Bind(wx.EVT_TEXT_ENTER, self.on_return_pressed)
widget.Bind(wx.EVT_TEXT, self.on_text_edited)
self.set_submit_triggers(d.submit_triggers)
self.widget.Bind(wx.EVT_TEXT, self.on_text_edited)

#--------------------------------------------------------------------------
# Private API
Expand Down Expand Up @@ -228,32 +229,24 @@ def _clear_error_state(self):
#--------------------------------------------------------------------------
# Event Handling
#--------------------------------------------------------------------------
def on_lost_focus(self, event):
""" The event handler for EVT_KILL_FOCUS event.
def on_submit_text(self, event):
""" The event handler for the text submit triggers.
"""
event.Skip()
if 'lost_focus' in self.declaration.submit_triggers:
self._guard |= TEXT_GUARD
try:
self._validate_and_apply()
finally:
self._guard &= ~TEXT_GUARD
# Only skip the focus event: wx triggers the system beep if the
# enter event is skipped.
if isinstance(event, wx.FocusEvent):
event.Skip()
self._guard |= TEXT_GUARD
try:
self._validate_and_apply()
finally:
self._guard &= ~TEXT_GUARD

def on_return_pressed(self, event):
""" The event handler for EVT_TEXT_ENTER event.
def on_text_edited(self, event):
""" The event handler for the text edited event.
"""
# don't skip or Wx triggers the system beep, grrrrrrr.....
#event.Skip()
if 'return_pressed' in self.declaration.submit_triggers:
self._guard |= TEXT_GUARD
try:
self._validate_and_apply()
finally:
self._guard &= ~TEXT_GUARD

def on_text_edited(self, event):
# Temporary kludge until error style is fully implemented
d = self.declaration
if d.validator and not d.validator.validate(self.widget.GetValue()):
Expand All @@ -262,6 +255,8 @@ def on_text_edited(self, event):
else:
self._clear_error_state()
self.widget.SetToolTip(wx.ToolTip(''))
if self._text_timer is not None:
self._text_timer.Start(300, oneShot=True)

#--------------------------------------------------------------------------
# ProxyField API
Expand All @@ -282,6 +277,27 @@ def set_mask(self, mask):
"""
pass

def set_submit_triggers(self, triggers):
""" Set the submit triggers for the widget.
"""
widget = self.widget
handler = self.on_submit_text
widget.Unbind(wx.EVT_KILL_FOCUS, handler=handler)
widget.Unbind(wx.EVT_TEXT_ENTER, handler=handler)
if 'lost_focus' in triggers:
widget.Bind(wx.EVT_KILL_FOCUS, handler)
if 'return_pressed' in triggers:
widget.Bind(wx.EVT_TEXT_ENTER, handler)
if 'auto_sync' in triggers:
if self._text_timer is None:
timer = self._text_timer = wx.Timer()
timer.Bind(wx.EVT_TIMER, handler)
else:
if self._text_timer is not None:
self._text_timer.Stop()
self._text_timer = None

def set_placeholder(self, placeholder):
""" Sets the placeholder text in the widget.
Expand Down

0 comments on commit 1926cde

Please sign in to comment.