Use CSS to fade background colour in ValidatableMaskedEntry

This commit is contained in:
Nick Hall 2016-05-26 18:18:33 +01:00
parent 5ef38e2a57
commit e2a5fe8e12

View File

@ -43,7 +43,6 @@ from gi.repository import GObject
from gi.repository import GLib
from gi.repository import Gdk
from gi.repository import Gtk
from gi.repository import GdkPixbuf
from gi.repository import Pango
#-------------------------------------------------------------------------
@ -63,111 +62,6 @@ from .undoableentry import UndoableEntry
#
#============================================================================
class FadeOut(GObject.GObject):
"""
I am a helper class to draw the fading effect of the background
Call my methods :meth:`start` and :meth:`stop` to control the fading.
"""
__gsignals__ = {
'done': (GObject.SignalFlags.RUN_FIRST,
None,
()),
'color-changed': (GObject.SignalFlags.RUN_FIRST,
None,
(Gdk.Color, )),
}
# How long time it'll take before we start (in ms)
COMPLAIN_DELAY = 500
MERGE_COLORS_DELAY = 100
def __init__(self, widget, err_color = "#ffd5d5"):
GObject.GObject.__init__(self)
self.ERROR_COLOR = err_color
self._widget = widget
self._start_color = None
self._background_timeout_id = -1
self._countdown_timeout_id = -1
self._done = False
def _merge_colors(self, src_color, dst_color, steps=10):
"""
Change the background of widget from src_color to dst_color
in the number of steps specified
"""
##_LOG.debug('_merge_colors: %s -> %s' % (src_color, dst_color))
rs, gs, bs = src_color.red, src_color.green, src_color.blue
rd, gd, bd = dst_color.red, dst_color.green, dst_color.blue
rinc = (rd - rs) / float(steps)
ginc = (gd - gs) / float(steps)
binc = (bd - bs) / float(steps)
for dummy in range(steps):
rs += rinc
gs += ginc
bs += binc
col = Gdk.color_parse("#%02X%02X%02X" % (int(rs) >> 8,
int(gs) >> 8,
int(bs) >> 8))
self.emit('color-changed', col)
yield True
self.emit('done')
self._background_timeout_id = -1
self._done = True
yield False
def _start_merging(self):
# If we changed during the delay
if self._background_timeout_id != -1:
##_LOG.debug('_start_merging: Already running')
return
##_LOG.debug('_start_merging: Starting')
generator = self._merge_colors(self._start_color,
Gdk.color_parse(self.ERROR_COLOR))
self._background_timeout_id = (
GLib.timeout_add(FadeOut.MERGE_COLORS_DELAY, generator.__next__))
self._countdown_timeout_id = -1
def start(self, color):
"""
Schedules a start of the countdown.
:param color: initial background color
:returns: True if we could start, False if was already in progress
"""
if self._background_timeout_id != -1:
##_LOG.debug('start: Background change already running')
return False
if self._countdown_timeout_id != -1:
##_LOG.debug('start: Countdown already running')
return False
if self._done:
##_LOG.debug('start: Not running, already set')
return False
self._start_color = color
##_LOG.debug('start: Scheduling')
self._countdown_timeout_id = GLib.timeout_add(
FadeOut.COMPLAIN_DELAY, self._start_merging)
return True
def stop(self):
"""Stops the fadeout and restores the background color"""
##_LOG.debug('Stopping')
if self._background_timeout_id != -1:
GLib.source_remove(self._background_timeout_id)
self._background_timeout_id = -1
if self._countdown_timeout_id != -1:
GLib.source_remove(self._countdown_timeout_id)
self._countdown_timeout_id = -1
self._widget.update_background(self._start_color, unset=True)
self._done = False
(DIRECTION_LEFT, DIRECTION_RIGHT) = (1, -1)
(INPUT_ASCII_LETTER,
@ -1013,52 +907,6 @@ class MaskedEntry(UndoableEntry):
def set_stock(self, icon_name):
self.set_icon_from_icon_name(Gtk.EntryIconPosition.SECONDARY, icon_name)
def update_background(self, color, unset=False):
maxvalcol = 65535.
if color:
red = int(color.red/ maxvalcol*255)
green = int(color.green/ maxvalcol*255)
blue = int(color.blue/ maxvalcol*255)
rgba = Gdk.RGBA()
Gdk.RGBA.parse(rgba, 'rgb(%f,%f,%f)'%(red, green, blue))
self.override_background_color(Gtk.StateFlags.NORMAL |
Gtk.StateFlags.ACTIVE | Gtk.StateFlags.SELECTED |
Gtk.StateFlags.FOCUSED, rgba)
#GTK 3: workaround, background not changing in themes, use symbolic
self.override_symbolic_color('bg_color', rgba)
self.override_symbolic_color('base_color', rgba)
self.override_symbolic_color('theme_bg_color', rgba)
self.override_symbolic_color('theme_base_color', rgba)
##self.get_window().set_background_rgba(rgba)
pango_context = self.get_layout().get_context()
font_description = pango_context.get_font_description()
if unset:
font_description.set_weight(Pango.Weight.NORMAL)
else:
font_description.set_weight(Pango.Weight.BOLD)
self.override_font(font_description)
else:
self.override_background_color(Gtk.StateFlags.NORMAL |
Gtk.StateFlags.ACTIVE | Gtk.StateFlags.SELECTED |
Gtk.StateFlags.FOCUSED, None)
# Setting the following to None causes an error (bug #6353).
#self.override_symbolic_color('bg_color', None)
#self.override_symbolic_color('base_color', None)
#self.override_symbolic_color('theme_bg_color', None)
#self.override_symbolic_color('theme_base_color', None)
pango_context = self.get_layout().get_context()
font_description = pango_context.get_font_description()
font_description.set_weight(Pango.Weight.NORMAL)
self.override_font(font_description)
def get_background(self):
backcol = self.get_style_context().get_background_color(Gtk.StateType.NORMAL)
bcol= Gdk.Color.parse('#fff')[1]
bcol.red = int(backcol.red * 65535)
bcol.green = int(backcol.green * 65535)
bcol.blue = int(backcol.blue * 65535)
return bcol
# Gtk.EntryCompletion convenience function
def prefill(self, itemdata, sort=False):
@ -1091,6 +939,7 @@ class MaskedEntry(UndoableEntry):
VALIDATION_ICON_WIDTH = 16
MANDATORY_ICON = 'dialog-information'
ERROR_ICON = 'process-stop'
FADE_TIME = 2500
class ValidatableMaskedEntry(MaskedEntry):
"""
@ -1132,7 +981,7 @@ class ValidatableMaskedEntry(MaskedEntry):
#allowed_data_types = (basestring, datetime.date, datetime.time,
#datetime.datetime, object) + number
def __init__(self, data_type=None, err_color = "#ffd5d5", error_icon=ERROR_ICON):
def __init__(self, data_type=None, err_color="pink", error_icon=ERROR_ICON):
self.data_type = None
self.mandatory = False
self.error_icon = error_icon
@ -1142,8 +991,19 @@ class ValidatableMaskedEntry(MaskedEntry):
self._valid = True
self._def_error_msg = None
self._fade = FadeOut(self, err_color)
self._fade.connect('color-changed', self._on_fadeout__color_changed)
self.__fade_tag = None
provider = Gtk.CssProvider()
css = '.fade {\n'
css += ' background: {};\n'.format(err_color)
css += ' transition: background {:d}ms linear;\n'.format(FADE_TIME)
css += '}'
css += '.bg {\n'
css += ' background: {};\n'.format(err_color)
css += '}'
provider.load_from_data(css.encode('utf8'))
context = self.get_style_context()
context.add_provider(provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
# FIXME put data type support back
#self.set_property('data-type', data_type)
@ -1265,10 +1125,18 @@ class ValidatableMaskedEntry(MaskedEntry):
reset the background color
"""
##_LOG.debug('Setting state for %s to VALID' % self.model_attribute)
if self.is_valid():
return
self._set_valid_state(True)
self._fade.stop()
self.set_pixbuf(None)
if self.__fade_tag is not None:
GLib.source_remove(self.__fade_tag)
self.__fade_tag = None
self.set_stock(None)
context = self.get_style_context()
context.remove_class('fade')
context.remove_class('bg')
def set_invalid(self, text=None, fade=True):
"""
@ -1278,6 +1146,8 @@ class ValidatableMaskedEntry(MaskedEntry):
:param fade: if we should fade the background
"""
##_LOG.debug('Setting state for %s to INVALID' % self.model_attribute)
if not self.is_valid():
return
self._set_valid_state(False)
@ -1306,29 +1176,13 @@ class ValidatableMaskedEntry(MaskedEntry):
self.set_tooltip(text)
if not fade:
if self.error_icon:
context = self.get_style_context()
if fade:
self.__fade_tag = GLib.timeout_add(FADE_TIME, self.__fade_finished)
context.add_class('fade')
else:
self.set_stock(self.error_icon)
self.update_background(Gdk.color_parse(self._fade.ERROR_COLOR))
return
# When the fading animation is finished, set the error icon
# We don't need to check if the state is valid, since stop()
# (which removes this timeout) is called as soon as the user
# types valid data.
def done(fadeout, c):
if self.error_icon:
self.set_stock(self.error_icon)
self.queue_draw()
fadeout.disconnect(c.signal_id)
class SignalContainer:
pass
c = SignalContainer()
c.signal_id = self._fade.connect('done', done, c)
if self._fade.start(self.get_background()):
self.set_pixbuf(None)
context.add_class('bg')
def set_blank(self):
"""
@ -1340,9 +1194,7 @@ class ValidatableMaskedEntry(MaskedEntry):
if self.mandatory:
self.set_stock(MANDATORY_ICON)
self.queue_draw()
self.set_tooltip(_('This field is mandatory'))
self._fade.stop()
valid = False
else:
valid = True
@ -1377,10 +1229,11 @@ class ValidatableMaskedEntry(MaskedEntry):
self.emit('validation-changed', state)
self._valid = state
# Callbacks
def _on_fadeout__color_changed(self, fadeout, color):
self.update_background(color)
def __fade_finished(self):
"""Set error icon after fade has finished."""
self.__fade_tag = None
self.set_stock(self.error_icon)
return False
def main(args):