Use CSS to fade background colour in ValidatableMaskedEntry
This commit is contained in:
parent
5ef38e2a57
commit
e2a5fe8e12
@ -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):
|
||||
|
Loading…
Reference in New Issue
Block a user