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')),