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:
1 parent
0f87253c1f
commit
f40b0b2e1c
10 files changed
+98
-15
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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'):
|
||||
|
||||
Reference in new issue
Block a user