Make readonly, required and invalid widget themable

- add classes to the widget
- provide a minimal application style
- allow to load a user theme
- add the profile name to the main window

issue6643
review35141002
This commit is contained in:
Cédric Krier committed 2017-08-07 19:58:00 +02:00
1 parent 0f87253c1f
commit f40b0b2e1c
10 files changed
+98 -15

No files matched your search

+2
View File
@@ -1,3 +1,5 @@
* Make readonly, required and invalid widget themable
* Load user CSS theme
* Use profile name or login details in title
Version 4.4.0 - 2017-05-01
+21
View File
@@ -719,6 +719,26 @@ formatted text. The tags are explain follows:
* Foreground color: It is a attribute `foreground` for `span` tag, i.e.
<span foreground='#00f'>text</span>
CSS
***
The client can be styled using the file `theme.css`.
Here are the list of custom selectors:
* `.readonly`: readonly widget or label
* `.required`: widget or label of required field
* `.invalid`: widget for which the field value is not valid
* `window.profile-<name>`: the name of the connection profile is set on the
main window
For more information about style option see `GTK+ CSS`_
.. GTK+ CSS:: https://developer.gnome.org/gtk3/stable/chap-css-overview.html
Appendix
********
@@ -734,6 +754,7 @@ Configuration File
~/.config/tryton/x.y/ca_certs # Certification Authority (http://docs.python.org/library/ssl.html#ssl-certificates)
~/.config/tryton/x.y/profiles.cfg # Profile configuration
~/.config/tryton/x.y/plugins # Local user plugins directory
~/.config.tryton/x.y/theme.css # Custom CSS theme
.. note::
`~` means the home directory of the user.
+28
View File
@@ -14,6 +14,8 @@ except ImportError:
sys.modules['cdecimal'] = decimal
import os
Gdk = None
Gtk = None
if os.environ.get('GTK_VERSION', '2').startswith('3'):
import pygtkcompat
pygtkcompat.enable()
@@ -213,6 +215,32 @@ if sys.platform == 'win32':
return super(Dialog, self).run()
gtk.Dialog = Dialog
CSS = """
.readonly entry {
background-color: @insensitive_bg_color;
}
.required entry {
border-color: darker(@unfocused_borders);
}
.invalid entry {
border-color: @error_color;
}
"""
if Gdk and Gtk:
screen = Gdk.Screen.get_default()
style_context = Gtk.StyleContext()
provider = Gtk.CssProvider()
provider.load_from_data(CSS)
style_context.add_provider_for_screen(
screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION)
theme_path = os.path.join(get_config_dir(), 'theme.css')
if os.path.exists(theme_path):
provider = Gtk.CssProvider()
provider.load_from_path(theme_path)
style_context.add_provider_for_screen(
screen, provider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
class TrytonClient(object):
"Tryton client"
+10 -1
View File
@@ -16,7 +16,16 @@ if gtk_version == 2:
widget.modify_base(
gtk.STATE_NORMAL, style.base[gtk.STATE_INSENSITIVE])
widget.modify_bg(gtk.STATE_NORMAL, style.bg[gtk.STATE_INSENSITIVE])
def widget_class(widget, name, value):
pass
else:
def set_widget_style(widget, editable):
# TODO
pass
def widget_class(widget, name, value):
style_context = widget.get_style_context()
if value:
style_context.add_class(name)
else:
style_context.remove_class(name)
+11
View File
@@ -388,6 +388,17 @@ class Main(object):
if value:
titles.append(value)
self.window.set_title(' - '.join(titles))
try:
style_context = self.window.get_style_context()
except AttributeError:
pass
else:
for name in style_context.list_classes():
if name.startswith('profile-'):
style_context.remove_class(name)
if CONFIG['login.profile']:
style_context.add_class(
'profile-%s' % CONFIG['login.profile'])
def _set_menu_connection(self):
menu_connection = gtk.Menu()
@@ -11,6 +11,7 @@ import gettext
from tryton.common.placeholder_entry import PlaceholderEntry
from tryton.common.completion import get_completion, update_completion
from tryton.common.domain_parser import quote
from tryton.common.widget_style import widget_class
_ = gettext.gettext
@@ -228,6 +229,8 @@ class Many2Many(Widget):
def _set_label_state(self):
attrlist = common.get_label_attributes(self._readonly, self._required)
self.title.set_attributes(attrlist)
widget_class(self.title, 'readonly', self._readonly)
widget_class(self.title, 'required', self._required)
def _set_button_sensitive(self):
if self.record and self.field:
@@ -12,6 +12,7 @@ import tryton.common as common
from tryton.common.placeholder_entry import PlaceholderEntry
from tryton.common.completion import get_completion, update_completion
from tryton.common.domain_parser import quote
from tryton.common.widget_style import widget_class
_ = gettext.gettext
@@ -255,6 +256,8 @@ class One2Many(Widget):
def _set_label_state(self):
attrlist = common.get_label_attributes(self._readonly, self._required)
self.title.set_attributes(attrlist)
widget_class(self.title, 'readonly', self._readonly)
widget_class(self.title, 'required', self._required)
def _set_button_sensitive(self):
access = common.MODELACCESS[self.screen.model_name]
@@ -3,6 +3,7 @@
import gtk
import tryton.common as common
from tryton.common.widget_style import widget_class
class StateMixin(object):
@@ -46,6 +47,8 @@ class Label(StateMixin, gtk.Label):
or state_changes.get('readonly'))
attrlist = common.get_label_attributes(readonly, required)
self.set_attributes(attrlist)
widget_class(self, 'readonly', readonly)
widget_class(self, 'required', required)
class VBox(StateMixin, gtk.VBox):
@@ -8,6 +8,7 @@ import tryton.common as common
from tryton.gui.window.nomodal import NoModal
from tryton.common import TRYTON_ICON
from tryton.common import RPCExecute, RPCException
from tryton.common.widget_style import widget_class
_ = gettext.gettext
@@ -98,15 +99,20 @@ class Widget(object):
self.invisible_set(self.attrs.get('invisible', False))
self._required_set(False)
return
readonly = self.attrs.get('readonly',
field.get_state_attrs(record).get('readonly', False))
states = field.get_state_attrs(record)
readonly = self.attrs.get('readonly', states.get('readonly', False))
if self.view.screen.readonly:
readonly = True
self._readonly_set(readonly)
self.invisible_set(self.attrs.get('invisible',
field.get_state_attrs(record).get('invisible', False)))
self._required_set(
field.get_state_attrs(record).get('required', False))
widget_class(self.widget, 'readonly', readonly)
self._required_set(not readonly and states.get('required', False))
widget_class(
self.widget, 'required',
not readonly and states.get('required', False))
invalid = states.get('invalid', False)
widget_class(self.widget, 'invalid', not readonly and invalid)
self.invisible_set(self.attrs.get(
'invisible', states.get('invisible', False)))
def set_value(self, record, field):
pass
+5 -8
View File
@@ -2,7 +2,6 @@
# this repository contains the full copyright notices and license terms.
import gobject
import gtk
import pango
import sys
import json
import locale
@@ -17,6 +16,7 @@ from tryton.gui.window import Window
from tryton.common.popup_menu import populate
from tryton.common import RPCExecute, RPCException, node_attributes, Tooltips
from tryton.common import domain_inversion, simplify, unique_value
from tryton.common.widget_style import widget_class
from tryton.pyson import PYSONDecoder
import tryton.common as common
from . import View
@@ -454,13 +454,10 @@ class ViewTree(View):
if field and self.editable:
required = field.attrs.get('required')
readonly = field.attrs.get('readonly')
if (required or not readonly) and hasattr(pango, 'AttrWeight'):
attrlist = pango.AttrList()
if required:
attrlist.change(pango.AttrWeight(pango.WEIGHT_BOLD, 0, -1))
if not readonly:
attrlist.change(pango.AttrStyle(pango.STYLE_ITALIC, 0, -1))
label.set_attributes(attrlist)
attrlist = common.get_label_attributes(readonly, required)
label.set_attributes(attrlist)
widget_class(label, 'readonly', readonly)
widget_class(label, 'required', required)
label.show()
help = None
if field and field.attrs.get('help'):