diff --git a/sao/CHANGELOG b/sao/CHANGELOG index 8bc6c09c36..cdec25be88 100644 --- a/sao/CHANGELOG +++ b/sao/CHANGELOG @@ -1,3 +1,4 @@ +* Support color as type of icon and image * Support color widget on form view Version 7.4.0 - 2024-11-04 diff --git a/sao/src/view/form.js b/sao/src/view/form.js index f7b6220d99..9f10cf5b5b 100644 --- a/sao/src/view/form.js +++ b/sao/src/view/form.js @@ -1254,6 +1254,11 @@ function eval_pyson(value){ } else { this.img.removeAttr('src'); } + } else if (this.attributes.type == 'color') { + this.img.attr('src', Sao.common.image_url()); + // clean previous color if the new one is not valid + this.img.css('background-color', ''); + this.img.css('background-color', name); } else { Sao.common.ICONFACTORY.get_icon_url(name) .done(url => { diff --git a/sao/src/view/tree.js b/sao/src/view/tree.js index 6ae26abd02..b8ef876c0d 100644 --- a/sao/src/view/tree.js +++ b/sao/src/view/tree.js @@ -2312,6 +2312,12 @@ } else { img_tag.removeAttr('src'); } + } else if (this.attributes.icon_type == 'color') { + img_tag.attr('src', Sao.common.image_url()); + // clean previous color if the new one is not valid + img_tag.css('background-color', ''); + img_tag.css('background-color', value); + img_tag.toggle(Boolean(value)); } else { Sao.common.ICONFACTORY.get_icon_url(value) .done(url => { diff --git a/tryton/CHANGELOG b/tryton/CHANGELOG index 22a66edd56..3985894194 100644 --- a/tryton/CHANGELOG +++ b/tryton/CHANGELOG @@ -1,3 +1,4 @@ +* Support color as type of icon and image * Support color widget on form view Version 7.4.0 - 2024-11-04 diff --git a/tryton/tryton/common/__init__.py b/tryton/tryton/common/__init__.py index 2181e3b90d..d3697dabb4 100644 --- a/tryton/tryton/common/__init__.py +++ b/tryton/tryton/common/__init__.py @@ -4,15 +4,16 @@ from . import timedelta from .common import ( COLOR_SCHEMES, MODELACCESS, MODELHISTORY, MODELNAME, MODELNOTIFICATION, TRYTON_ICON, VIEW_SEARCH, IconFactory, Logout, RPCContextReload, - RPCException, RPCExecute, RPCProgress, Tooltips, apply_label_attributes, - ask, check_version, concurrency, data2pixbuf, date_format, ellipsize, - error, file_open, file_selection, file_write, filter_domain, - generateColorscheme, get_align, get_credentials, get_gdk_backend, - get_hostname, get_port, get_sensible_widget, get_toplevel_window, hex2rgb, - highlight_rgb, humanize, idle_add, mailto, message, node_attributes, - open_documentation, play_sound, process_exception, resize_pixbuf, - selection, setup_window, slugify, sur, sur_3b, timezoned_date, to_xml, - untimezoned_date, url_open, userwarning, warning) + RPCException, RPCExecute, RPCProgress, SolidColorFactory, Tooltips, + apply_label_attributes, ask, check_version, concurrency, data2pixbuf, + date_format, ellipsize, error, file_open, file_selection, file_write, + filter_domain, generateColorscheme, get_align, get_credentials, + get_gdk_backend, get_hostname, get_port, get_sensible_widget, + get_toplevel_window, hex2rgb, highlight_rgb, humanize, idle_add, mailto, + message, node_attributes, open_documentation, play_sound, + process_exception, resize_pixbuf, selection, setup_window, slugify, sur, + sur_3b, timezoned_date, to_xml, untimezoned_date, url_open, userwarning, + warning) from .domain_inversion import ( concat, domain_inversion, eval_domain, extract_reference_models, filter_leaf, inverse_leaf, localize_domain, merge, @@ -32,6 +33,7 @@ __all__ = [ RPCException, RPCExecute, RPCProgress, + SolidColorFactory, TRYTON_ICON, Tooltips, VIEW_SEARCH, diff --git a/tryton/tryton/common/common.py b/tryton/tryton/common/common.py index bf9612d260..5535dae878 100644 --- a/tryton/tryton/common/common.py +++ b/tryton/tryton/common/common.py @@ -233,6 +233,49 @@ class IconFactory: IconFactory.load_local_icons() +class SolidColorFactory: + _pixbufs = defaultdict(dict) + + @staticmethod + def _create_pixbuf(red, green, blue, width, height): + pixbuf = GdkPixbuf.Pixbuf.new( + GdkPixbuf.Colorspace.RGB, False, 8, width, height) + pixels = bytearray(pixbuf.get_pixels()) + rowstride = pixbuf.get_rowstride() + for y in range(height): + for x in range(width): + offset = y * rowstride + x * 3 + pixels[offset:offset + 3] = bytes([red, green, blue]) + return GdkPixbuf.Pixbuf.new_from_data( + pixels, GdkPixbuf.Colorspace.RGB, False, 8, width, height, + rowstride) + + @staticmethod + def hex_to_rgb(color): + color = color.lstrip('#') + if len(color) == 3: + color = ''.join([c * 2 for c in color]) + if len(color) != 6: + return + try: + red = int(color[0:2], 16) + green = int(color[2:4], 16) + blue = int(color[4:6], 16) + except ValueError: + return + return red, green, blue + + @classmethod + def get_pixbuf(cls, color, width=16, height=16): + rgb = cls.hex_to_rgb(color) + if rgb is None: + return + if rgb not in cls._pixbufs[width, height]: + cls._pixbufs[width, height][rgb] = cls._create_pixbuf( + *rgb, width, height) + return cls._pixbufs[width, height][rgb] + + class ModelAccess(object): batchnum = 100 diff --git a/tryton/tryton/gui/window/view_form/view/form_gtk/state_widget.py b/tryton/tryton/gui/window/view_form/view/form_gtk/state_widget.py index 3200e409c9..07f8d4078f 100644 --- a/tryton/tryton/gui/window/view_form/view/form_gtk/state_widget.py +++ b/tryton/tryton/gui/window/view_form/view/form_gtk/state_widget.py @@ -72,6 +72,8 @@ class Image(StateMixin, Gtk.Image): if self.attrs.get('type') == 'url': pixbuf = common.IconFactory.get_pixbuf_url( name, size=size, size_param=self.attrs.get('url_size')) + elif self.attrs.get('type') == 'color': + pixbuf = common.SolidColorFactory.get_pixbuf(name, size, size) else: pixbuf = common.IconFactory.get_pixbuf(name, size) self.set_from_pixbuf(pixbuf) diff --git a/tryton/tryton/gui/window/view_form/view/list_gtk/widget.py b/tryton/tryton/gui/window/view_form/view/list_gtk/widget.py index 8fbd9acf93..5bcd928eeb 100644 --- a/tryton/tryton/gui/window/view_form/view/list_gtk/widget.py +++ b/tryton/tryton/gui/window/view_form/view/list_gtk/widget.py @@ -221,6 +221,8 @@ class Affix(Cell): pixbuf = common.IconFactory.get_pixbuf_url( value, size_param=self.attrs.get('url_size'), callback=callback) + elif self.attrs.get('icon_type') == 'color': + pixbuf = common.SolidColorFactory.get_pixbuf(value) else: pixbuf = common.IconFactory.get_pixbuf( value, Gtk.IconSize.BUTTON) diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index 0d9de99c18..e35b520cef 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Add color as type of icon and image * Add color widget to form view * Limit access to database from the same series * Fill wizard state view instance with default value diff --git a/trytond/doc/topics/views/index.rst b/trytond/doc/topics/views/index.rst index c303190eca..afc109b36a 100644 --- a/trytond/doc/topics/views/index.rst +++ b/trytond/doc/topics/views/index.rst @@ -392,7 +392,7 @@ image Display an image with those attributes: ``type`` - The type of image source. Available values are ``icon`` or ``url``. + The type of image source. Available values are ``icon``, ``url`` or ``color``. The default value is ``icon``. ``name`` @@ -400,6 +400,7 @@ Display an image with those attributes: For the ``icon`` type it must be the name of an `Icon ` record. For the ``url`` type it must be the URL. It can be relative to the server. + For the ``color`` type it must be the color in ``#rrggbb`` hexadecimal. ``url_size`` The name of the size parameter to add to the URL. @@ -754,9 +755,10 @@ displayed in the same column with the attributes: For the ``icon`` type it must be the name of an `Icon ` record. For the ``url`` type it must be the URL and it can be relative to the server. + For the ``color`` type it must be the color in ``#rrggbb`` hexadecimal. ``icon_type`` - The type of icon source. Available values are ``icon`` or ``url``. + The type of icon source. Available values are ``icon``, ``url`` or ``color``. The default value is ``icon``. ``url_size`` diff --git a/trytond/trytond/ir/ui/form.rnc b/trytond/trytond/ir/ui/form.rnc index 18400f3a2f..330c2fea43 100644 --- a/trytond/trytond/ir/ui/form.rnc +++ b/trytond/trytond/ir/ui/form.rnc @@ -125,7 +125,7 @@ attlist.field &= [a:defaultValue = "square"] attribute border { "square" | "circ attlist.field &= [a:defaultValue = "0"] attribute visible { "0" | "1" }? image = element image { attlist.image, empty } attlist.image &= attribute name { text } -attlist.image &= [a:defaultValue = "icon"] attribute type { "icon" | "url" }? +attlist.image &= [a:defaultValue = "icon"] attribute type { "icon" | "url" | "color" }? attlist.image &= attribute url_size { text }? attlist.image &= [a:defaultValue = "square"] attribute border { "square" | "circle" | "rounded" }? attlist.image &= [ a:defaultValue = "1" ] attribute colspan { text }? diff --git a/trytond/trytond/ir/ui/form.rng b/trytond/trytond/ir/ui/form.rng index 28bb7f713f..a8035c2fae 100644 --- a/trytond/trytond/ir/ui/form.rng +++ b/trytond/trytond/ir/ui/form.rng @@ -609,6 +609,7 @@ icon url + color diff --git a/trytond/trytond/ir/ui/tree.rnc b/trytond/trytond/ir/ui/tree.rnc index 9024f9ba56..f5c7bc45cb 100644 --- a/trytond/trytond/ir/ui/tree.rnc +++ b/trytond/trytond/ir/ui/tree.rnc @@ -80,7 +80,7 @@ suffix = element suffix { attlist.affix, empty } attlist.affix &= attribute string { text }? attlist.affix &= ( attribute name { text } | attribute id { text } ) attlist.affix &= attribute icon { text }? -attlist.affix &= [a:defaultValue = "icon"] attribute icon_type { "icon" | "url" }? +attlist.affix &= [a:defaultValue = "icon"] attribute icon_type { "icon" | "url" | "color" }? attlist.affix &= attribute url_size { text }? attlist.affix &= [a:defaultValue = "square"] attribute border { "square" | "circle" | "rounded" }? button = element button { attlist.button, empty } diff --git a/trytond/trytond/ir/ui/tree.rng b/trytond/trytond/ir/ui/tree.rng index 96be5760a1..1816faaf06 100644 --- a/trytond/trytond/ir/ui/tree.rng +++ b/trytond/trytond/ir/ui/tree.rng @@ -379,6 +379,7 @@ icon url + color