# (c) Copyright 2015. CodeWeavers, Inc.
import gobject
import gtk
def _blend_colors(a, b, a_weight, b_weight):
total_weight = a_weight + b_weight
return gtk.gdk.Color(
(a.red * a_weight + b.red * b_weight) // total_weight,
(a.green * a_weight + b.green * b_weight) // total_weight,
(a.blue * a_weight + b.blue * b_weight) // total_weight)
class PlaceholderEntry(gtk.Entry):
__gtype_name__ = 'PlaceholderEntry'
__gsignals__ = {
'real-text-changed': (gobject.SIGNAL_RUN_LAST, None, (str,)), # text
}
__gproperties__ = {
'placeholder-string' :
(gobject.TYPE_STRING,
"Placeholder String",
"Text displayed when the textbox is empty.",
'',
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE | gobject.PARAM_CONSTRUCT),
'placeholder-focused' :
(gobject.TYPE_BOOLEAN,
"Placeholder Focused",
"Show placeholder string even when the textbox is focused.",
True,
gobject.PARAM_READABLE | gobject.PARAM_WRITABLE | gobject.PARAM_CONSTRUCT),
}
def __init__(self):
self._showing_placeholder = False
self._setting_placeholder = False
self._setting_style = False
self._placeholder_string = ""
self._placeholder_focus = True
self._postev_source = None
self._preedit = ""
gtk.Entry.__init__(self)
for signal in ('grab-focus', 'button-press-event', 'button-release-event', 'motion-notify-event', 'key-press-event', 'key-release-event'):
self.connect(signal, self._pre_event)
self.connect_after(signal, self._post_event)
self.connect('changed', self._on_changed)
self.connect('insert-text', self._on_insert_text)
self.connect('insert-at-cursor', self._on_insert_at_cursor)
self.connect('move-cursor', self._on_move_cursor)
self.connect('preedit-changed', self._on_preedit_changed)
self.connect('style-set', self._on_style_set)
self.connect('focus-in-event', self._on_focus_in)
self.connect('focus-out-event', self._on_focus_out)
def do_get_property(self, prop):
if prop.name == 'placeholder-string':
return self._placeholder_string
elif prop.name == 'placeholder-focused':
return self._placeholder_focus
else:
raise AttributeError("unknown property %s" % prop.name)
def do_set_property(self, prop, value):
if prop.name == 'placeholder-string':
self._placeholder_string = value
self._adjust_text()
elif prop.name == 'placeholder-focused':
self._placeholder_focus = value
self._adjust_text()
else:
raise AttributeError("unknown property %s" % prop.name)
def _adjust_style(self):
if self._setting_style:
return
self._setting_style = True
self.modify_text(gtk.STATE_NORMAL, None)
self.modify_text(gtk.STATE_ACTIVE, None)
if self._showing_placeholder:
# pylint: disable=E1101
self.modify_text(gtk.STATE_NORMAL, _blend_colors(self.style.text[gtk.STATE_NORMAL], self.style.base[gtk.STATE_NORMAL], 1, 1))
self.modify_text(gtk.STATE_ACTIVE, _blend_colors(self.style.text[gtk.STATE_ACTIVE], self.style.base[gtk.STATE_ACTIVE], 1, 1))
self._setting_style = False
def _suppress_placeholder(self):
if self._preedit or (not self._placeholder_focus and self.is_focus()):
return True
return False
def _adjust_text(self):
if not self._showing_placeholder and self._placeholder_string and not self._suppress_placeholder() and not self.get_text():
self._showing_placeholder = True
self._adjust_style()
if self._showing_placeholder and self._suppress_placeholder():
self._kill_placeholder(True)
if self._showing_placeholder:
display_string = self._placeholder_string
if self.get_text() != display_string:
self._setting_placeholder = True
self.set_text(display_string)
self._setting_placeholder = False
if self.get_selection_bounds() or self.get_position():
self.set_position(0)
def _kill_placeholder(self, clear=False):
if self._showing_placeholder:
self._showing_placeholder = False
self._setting_placeholder = True
if clear:
self.set_text('')
self._setting_placeholder = False
self._adjust_style()
def get_real_text(self):
if self._showing_placeholder:
return ""
return self.get_text()
def _pre_event(self, *_args):
self._kill_placeholder(True)
if not self._postev_source:
# HIGH_IDLE is just before redraws, preventing flickering
self._postev_source = gobject.idle_add(self._post_event, priority=gobject.PRIORITY_HIGH_IDLE)
def _post_event(self, *_args):
self._adjust_text()
if self._postev_source:
gobject.source_remove(self._postev_source)
self._postev_source = None
def _on_changed(self, _editable):
if self._setting_placeholder:
return
if self._showing_placeholder and self.get_text() not in ('', self._placeholder_string):
self._kill_placeholder()
self._adjust_text()
self.emit('real-text-changed', self.get_real_text())
def _on_insert_text(self, _editable, _new_text, _new_text_length, _position):
if self._setting_placeholder:
return
if self._showing_placeholder:
self._kill_placeholder(True)
def _on_insert_at_cursor(self, _entry, _string):
if self._setting_placeholder:
return
if self._showing_placeholder:
self._kill_placeholder(True)
def _on_move_cursor(self, _entry, _step, _count, _extend_selection):
if self._showing_placeholder:
self.stop_emission('move-cursor')
def _on_preedit_changed(self, _entry, preedit):
self._preedit = preedit
self._adjust_text()
def _on_style_set(self, _widget, _previous_style):
if self._setting_style:
return
if self._showing_placeholder:
self._adjust_style()
def _on_focus_in(self, _widget, _event):
self._adjust_text()
def _on_focus_out(self, _widget, _event):
self._adjust_text()
if __name__ == '__main__':
entry1 = PlaceholderEntry()
entry2 = PlaceholderEntry()
entry1.set_property('placeholder-string', 'Entry One')
entry2.set_property('placeholder-string', 'Entry Two')
entry2.set_property('has-frame', False)
entry2.set_property('placeholder-focused', False)
def on_real_text_changed(_entry, text, name):
print "text changed", text, name
entry1.connect('real-text-changed', on_real_text_changed, "Entry One")
entry2.connect('real-text-changed', on_real_text_changed, "Entry Two")
vbox = gtk.VBox()
vbox.add(entry1)
vbox.add(entry2)
w = gtk.Window()
w.add(vbox)
w.show_all()
w.connect("destroy", lambda w: gtk.main_quit())
gtk.main()