From 093fb115ffb8e6fe4682f2e42c8d9eef97219cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 8 Jan 2025 16:53:34 +0100 Subject: [PATCH] Add color widget to form view Closes #13800 --- sao/CHANGELOG | 1 + sao/src/sao.less | 6 +- sao/src/view/form.js | 60 ++++++++++++++++++- tryton/CHANGELOG | 1 + .../tryton/gui/window/view_form/view/form.py | 3 +- .../window/view_form/view/form_gtk/char.py | 39 +++++++++++- .../view_form/view/form_gtk/dictionary.py | 39 +++++++++++- trytond/CHANGELOG | 1 + trytond/doc/ref/models.rst | 1 + trytond/trytond/ir/message.xml | 3 + trytond/trytond/ir/ui/form.rnc | 1 + trytond/trytond/ir/ui/form.rng | 1 + trytond/trytond/model/dictschema.py | 1 + 13 files changed, 150 insertions(+), 7 deletions(-) diff --git a/sao/CHANGELOG b/sao/CHANGELOG index cbee97a0e9..8bc6c09c36 100644 --- a/sao/CHANGELOG +++ b/sao/CHANGELOG @@ -1,3 +1,4 @@ +* Support color widget on form view Version 7.4.0 - 2024-11-04 -------------------------- diff --git a/sao/src/sao.less b/sao/src/sao.less index 44ce9cf31c..b368720c8a 100644 --- a/sao/src/sao.less +++ b/sao/src/sao.less @@ -884,9 +884,9 @@ img.icon { width: max-content; max-width: 80ch; } - .form-char, .form-password, .form-integer, .form-float, .form-timedelta, - .form-selection, .form-multiselection, .form-url, .form-email, - .form-callto, .form-sip, .form-pyson { + .form-char, .form-password, .form-color, .form-integer, .form-float, + .form-timedelta, .form-selection, .form-multiselection, .form-url, + .form-email, .form-callto, .form-sip, .form-pyson { input, select { min-width: 8ch; } diff --git a/sao/src/view/form.js b/sao/src/view/form.js index b5cfc6dd65..f7b6220d99 100644 --- a/sao/src/view/form.js +++ b/sao/src/view/form.js @@ -1774,6 +1774,35 @@ function eval_pyson(value){ } }); + Sao.View.Form.Color = Sao.class_(Sao.View.Form.Char, { + class_: 'form-color', + init: function(view, attributes) { + Sao.View.Form.Color._super.init.call(this, view, attributes); + + this.color_input = jQuery('', { + 'class': 'btn btn-default btn-sm', + 'type': 'color', + 'title': Sao.i18n.gettext( + 'Select a color for "%1"', attributes.string), + }).appendTo(jQuery('', { + 'class': 'input-group-btn', + }).appendTo(this.group)); + this.color_input.change(this.set_color.bind(this)); + }, + set_color: function() { + this.input.val(this.color_input.val()); + this.focus_out(); + }, + set_readonly: function(readonly) { + Sao.View.Form.Color._super.set_readonly.call(this, readonly); + this.color_input.prop('disabled', readonly); + }, + display: function() { + Sao.View.Form.Color._super.display.call(this); + this.color_input.val(this.get_client_value()); + }, + }); + Sao.View.Form.Date = Sao.class_(Sao.View.Form.Widget, { class_: 'form-date', _input: 'date', @@ -5354,7 +5383,9 @@ function eval_pyson(value){ get_entries: function(type) { switch (type) { case 'char': - return Sao.View.Form.Dict.Entry; + return Sao.View.Form.Dict.Char; + case 'color': + return Sao.View.Form.Dict.Color; case 'boolean': return Sao.View.Form.Dict.Boolean; case 'selection': @@ -5436,6 +5467,32 @@ function eval_pyson(value){ } }); + Sao.View.Form.Dict.Color = Sao.class_(Sao.View.Form.Dict.Char, { + class_: 'dict-color', + create_widget: function() { + Sao.View.Form.Dict.Color._super.create_widget.call(this); + this.color_input = jQuery('', { + 'class': 'btn btn-default btn-sm', + 'type': 'color', + 'title': Sao.i18n.gettext( + 'Select a color for "%1"', this.definition.string), + }).prependTo(this.el.find('.input-group-btn')); + this.color_input.change(this.set_color.bind(this)); + }, + set_color: function() { + this.input.val(this.color_input.val()); + this.parent_widget.focus_out(); + }, + set_value: function(value) { + Sao.View.Form.Dict.Color._super.set_value.call(this, value); + this.color_input.val(value || ''); + }, + set_readonly: function(readonly) { + Sao.View.Form.Dict.Color._super.set_readonly.call(this, readonly); + this.color_input.prop('disabled', readonly); + }, + }); + Sao.View.Form.Dict.Boolean = Sao.class_(Sao.View.Form.Dict.Entry, { class_: 'dict-boolean', create_widget: function() { @@ -5800,6 +5857,7 @@ function eval_pyson(value){ 'boolean': Sao.View.Form.Boolean, 'callto': Sao.View.Form.CallTo, 'char': Sao.View.Form.Char, + 'color': Sao.View.Form.Color, 'date': Sao.View.Form.Date, 'datetime': Sao.View.Form.DateTime, 'dict': Sao.View.Form.Dict, diff --git a/tryton/CHANGELOG b/tryton/CHANGELOG index ecc1c47f5c..22a66edd56 100644 --- a/tryton/CHANGELOG +++ b/tryton/CHANGELOG @@ -1,3 +1,4 @@ +* Support color widget on form view Version 7.4.0 - 2024-11-04 -------------------------- diff --git a/tryton/tryton/gui/window/view_form/view/form.py b/tryton/tryton/gui/window/view_form/view/form.py index 5939bcb7ee..f77133110a 100644 --- a/tryton/tryton/gui/window/view_form/view/form.py +++ b/tryton/tryton/gui/window/view_form/view/form.py @@ -17,7 +17,7 @@ from tryton.gui.window.code_scanner import CodeScanner from . import View, XMLViewParser from .form_gtk.binary import Binary from .form_gtk.calendar_ import Date, DateTime, Time -from .form_gtk.char import Char, Password +from .form_gtk.char import Char, Color, Password from .form_gtk.checkbox import CheckBox from .form_gtk.dictionary import DictWidget from .form_gtk.document import Document @@ -167,6 +167,7 @@ class FormXMLViewParser(XMLViewParser): 'boolean': CheckBox, 'callto': CallTo, 'char': Char, + 'color': Color, 'date': Date, 'datetime': DateTime, 'dict': DictWidget, diff --git a/tryton/tryton/gui/window/view_form/view/form_gtk/char.py b/tryton/tryton/gui/window/view_form/view/form_gtk/char.py index 51643c4b85..963b75d6c0 100644 --- a/tryton/tryton/gui/window/view_form/view/form_gtk/char.py +++ b/tryton/tryton/gui/window/view_form/view/form_gtk/char.py @@ -2,7 +2,7 @@ # this repository contains the full copyright notices and license terms. import gettext -from gi.repository import GLib, Gtk +from gi.repository import Gdk, GLib, Gtk from tryton.common import IconFactory, Tooltips from tryton.common.entry_position import reset_position @@ -177,3 +177,40 @@ class Password(Char): else: entry = self.entry entry.props.visibility = not self.entry.props.visibility + + +class Color(Char): + + def __init__(self, view, attrs): + super().__init__(view, attrs) + + self.color_button = Gtk.ColorButton() + self.color_button.connect('color-set', self.set_color) + self.color_button.set_title( + _('Select a color for "%s"') % attrs['string']) + Tooltips().set_tip(self.color_button, _("Select a color")) + self.widget.pack_start( + self.color_button, expand=False, fill=True, padding=0) + + def set_color(self, button): + if self.autocomplete: + entry = self.entry.get_child() + else: + entry = self.entry + rgba = self.color_button.get_rgba() + rgb = list(map(lambda x: int(x * 255), list(rgba)[:3])) + entry.set_text('#{:02X}{:02X}{:02X}'.format(*rgb)) + self._focus_out() + + def _readonly_set(self, value): + super()._readonly_set(value) + self.color_button.set_sensitive(not value) + + def display(self): + super().display() + if self.field and self.record: + value = self.field.get_client(self.record) + rgba = Gdk.RGBA() + if value: + rgba.parse(value) + self.color_button.set_rgba(rgba) diff --git a/tryton/tryton/gui/window/view_form/view/form_gtk/dictionary.py b/tryton/tryton/gui/window/view_form/view/form_gtk/dictionary.py index a2d56cb7a7..d2ac3b1611 100644 --- a/tryton/tryton/gui/window/view_form/view/form_gtk/dictionary.py +++ b/tryton/tryton/gui/window/view_form/view/form_gtk/dictionary.py @@ -9,7 +9,7 @@ import operator from collections import defaultdict from decimal import Decimal -from gi.repository import GLib, GObject, Gtk +from gi.repository import Gdk, GLib, GObject, Gtk from tryton.common import ( IconFactory, Tooltips, timezoned_date, untimezoned_date) @@ -83,6 +83,42 @@ class DictCharEntry(DictEntry): return self.get_value() != (value.get(self.name, '') or '') +class DictColorEntry(DictCharEntry): + + def create_widget(self): + self.entry = super().create_widget() + widget = Gtk.HBox() + widget.pack_start(self.entry, expand=True, fill=True, padding=0) + self.button = Gtk.ColorButton() + self.button.connect('color-set', self.set_color) + self.button.set_title( + _('Select a color for "%s"') % self.definition['string']) + Tooltips().set_tip(self.button, _("Select a color")) + widget.pack_start(self.button, expand=False, fill=True, padding=0) + return widget + + def set_color(self, button): + rgba = self.button.get_rgba() + rgb = map(lambda x: int(x * 255), list(rgba)[:3]) + self.entry.set_text('#{:02X}{:02X}{:02X}'.format(*rgb)) + self.parent_widget._focus_out() + + def get_value(self): + return self.entry.get_text() + + def set_value(self, value): + self.entry.set_text(str(value or '')) + reset_position(self.entry) + rgba = Gdk.RGBA() + if value: + rgba.parse(value) + self.button.set_rgba(rgba) + + def set_readonly(self, readonly): + self.entry.set_editable(not readonly) + self.button.set_sensitive(not readonly) + + class DictBooleanEntry(DictEntry): def create_widget(self): @@ -417,6 +453,7 @@ class DictDateEntry(DictEntry): DICT_ENTRIES = { 'char': DictCharEntry, + 'color': DictColorEntry, 'boolean': DictBooleanEntry, 'selection': DictSelectionEntry, 'multiselection': DictMultiSelectionEntry, diff --git a/trytond/CHANGELOG b/trytond/CHANGELOG index 698b89cde5..0d9de99c18 100644 --- a/trytond/CHANGELOG +++ b/trytond/CHANGELOG @@ -1,3 +1,4 @@ +* Add color widget to form view * Limit access to database from the same series * Fill wizard state view instance with default value * Replace passlib by pwdlib diff --git a/trytond/doc/ref/models.rst b/trytond/doc/ref/models.rst index 7263c80390..7dc33c8bd8 100644 --- a/trytond/doc/ref/models.rst +++ b/trytond/doc/ref/models.rst @@ -930,6 +930,7 @@ Class attributes are: * boolean * integer * char + * color * float * numeric * date diff --git a/trytond/trytond/ir/message.xml b/trytond/trytond/ir/message.xml index 4353dd0bdc..8c88fac4f2 100644 --- a/trytond/trytond/ir/message.xml +++ b/trytond/trytond/ir/message.xml @@ -98,6 +98,9 @@ this repository contains the full copyright notices and license terms. --> Char + + Color + Float diff --git a/trytond/trytond/ir/ui/form.rnc b/trytond/trytond/ir/ui/form.rnc index 767a2b1b96..18400f3a2f 100644 --- a/trytond/trytond/ir/ui/form.rnc +++ b/trytond/trytond/ir/ui/form.rnc @@ -48,6 +48,7 @@ attlist.field &= | "boolean" | "callto" | "char" + | "color" | "date" | "datetime" | "dict" diff --git a/trytond/trytond/ir/ui/form.rng b/trytond/trytond/ir/ui/form.rng index 41f85543a8..28bb7f713f 100644 --- a/trytond/trytond/ir/ui/form.rng +++ b/trytond/trytond/ir/ui/form.rng @@ -226,6 +226,7 @@ boolean callto char + color date datetime dict diff --git a/trytond/trytond/model/dictschema.py b/trytond/trytond/model/dictschema.py index f42426c408..d78b573f87 100644 --- a/trytond/trytond/model/dictschema.py +++ b/trytond/trytond/model/dictschema.py @@ -40,6 +40,7 @@ class DictSchemaMixin(object): ('boolean', lazy_gettext('ir.msg_dict_schema_boolean')), ('integer', lazy_gettext('ir.msg_dict_schema_integer')), ('char', lazy_gettext('ir.msg_dict_schema_char')), + ('color', lazy_gettext('ir.msg_dict_schema_color')), ('float', lazy_gettext('ir.msg_dict_schema_float')), ('numeric', lazy_gettext('ir.msg_dict_schema_numeric')), ('date', lazy_gettext('ir.msg_dict_schema_date')),