Use separate XML parser in Views

issue8131
review54561002
This commit is contained in:
Cédric Krier committed 2019-03-11 12:02:58 +01:00
1 parent c27b618f96
commit 237826bb43
7 files changed
+527 -523

No files matched your search

+1
View File
@@ -1,3 +1,4 @@
* Use separate XML parser in Views
* Use between numbers in filter box
* Rename email and calendar file
* Use markup for styling
+63 -1
View File
@@ -1,7 +1,13 @@
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import gettext
from collections import defaultdict
from tryton.common import node_attributes
_ = gettext.gettext
list_ = list # list builtins is overridden by import .list
class View(object):
view_type = None
@@ -12,13 +18,20 @@ class View(object):
editable = None
children_field = None
scroll = None
xml_parser = None
def __init__(self, screen, xml):
self.screen = screen
self.fields = {}
self.widgets = defaultdict(list_)
self.state_widgets = []
self.attributes = node_attributes(xml)
screen.set_on_write(self.attributes.get('on_write'))
self.xml_parser(
self, self.screen.exclude_field,
{k: f.attrs for k, f in self.screen.group.fields.items()}
).parse(xml)
def set_value(self):
raise NotImplementedError
@@ -61,3 +74,52 @@ class View(object):
return ViewGraph(screen, root)
elif tagname == 'calendar':
return ViewCalendar(screen, root)
class XMLViewParser:
def __init__(self, view, exclude_field, field_attrs):
self.view = view
self.exclude_field = exclude_field
self.field_attrs = field_attrs
def _node_attributes(self, node):
node_attrs = node_attributes(node)
if 'name' in node_attrs:
field = self.field_attrs.get(node_attrs['name'], {})
else:
field = {}
for name in ['readonly', 'homogeneous']:
if name in node_attrs:
node_attrs[name] = bool(int(node_attrs[name]))
for name in [
'yexpand', 'yfill', 'xexpand', 'xfill', 'colspan', 'position']:
if name in node_attrs:
node_attrs[name] = int(node_attrs[name])
for name in ['xalign', 'yalign']:
if name in node_attrs:
node_attrs[name] = float(node_attrs[name])
if field:
node_attrs.setdefault('widget', field['type'])
if node.tagName == 'label' and 'string' not in node_attrs:
node_attrs['string'] = field['string'] + _(':')
if node.tagName == 'field' and 'help' not in node_attrs:
node_attrs['help'] = field['help']
for name in [
'relation', 'domain', 'selection', 'string', 'states',
'relation_field', 'views', 'invisible', 'add_remove',
'sort', 'context', 'size', 'filename', 'autocomplete',
'translate', 'create', 'delete', 'selection_change_with',
'schema_model']:
if name in field:
node_attrs.setdefault(name, field[name])
return node_attrs
def parse(self, node):
node_attrs = self._node_attributes(node)
if node.nodeType != node.ELEMENT_NODE:
return
parser = getattr(self, '_parse_%s' % node.tagName)
parser(node, node_attrs)
+38 -26
View File
@@ -7,8 +7,7 @@ import gettext
from gi.repository import Gtk
from tryton.common import node_attributes
from . import View
from . import View, XMLViewParser
try:
from .calendar_gtk.calendar_ import Calendar_
from .calendar_gtk.toolbar import Toolbar
@@ -29,29 +28,48 @@ def goocalendar_required(func):
return wrapper
class CalendarXMLViewParser(XMLViewParser):
def __init__(self, view, exclude_field, field_attrs):
super().__init__(view, exclude_field, field_attrs)
self.calendar_fields = []
def _parse_calendar(self, node, attributes):
for child in node.childNodes:
self.parse(child)
goocalendar = Calendar_(
self.view.attributes, self.view, self.calendar_fields)
toolbar = Toolbar(goocalendar)
self.view.scroll.add(goocalendar)
self.view.widget.pack_start(
toolbar, expand=False, fill=False, padding=0)
self.view.widgets['goocalendar'] = goocalendar
self.view.widgets['toolbar'] = toolbar
def _parse_field(self, node, attributes):
self.calendar_fields.append(attributes)
class ViewCalendar(View):
editable = False
view_type = 'calendar'
xml_parser = CalendarXMLViewParser
def __init__(self, screen, xml):
super(ViewCalendar, self).__init__(screen, xml)
self.view_type = 'calendar'
self.editable = False
self.widgets = {}
self.widget = self.parse(xml)
self.widget = Gtk.VBox()
def parse(self, node):
vbox = Gtk.VBox()
if not Calendar_:
return vbox
fields = []
for node in node.childNodes:
if node.nodeType != node.ELEMENT_NODE:
continue
if node.tagName == 'field':
fields.append(node_attributes(node))
goocalendar = Calendar_(attrs=self.attributes, screen=self.screen,
fields=fields)
toolbar = Toolbar(goocalendar)
self.widgets['toolbar'] = toolbar
self.widgets = {}
return
self.scroll = scrolledWindow = Gtk.ScrolledWindow()
self.widget.pack_end(
scrolledWindow, expand=True, fill=True, padding=0)
super().__init__(screen, xml)
goocalendar = self.widgets['goocalendar']
toolbar = self.widgets['toolbar']
goocalendar.connect('view-changed', self.on_view_changed, toolbar)
goocalendar.connect('page-changed', self.on_page_changed, toolbar)
goocalendar.connect('event-pressed', self.on_event_pressed)
@@ -59,12 +77,6 @@ class ViewCalendar(View):
goocalendar.connect('event-released', self.on_event_released)
goocalendar.connect('day-pressed', self.on_day_pressed)
goocalendar.connect('day-activated', self.on_day_activated)
self.widgets['goocalendar'] = goocalendar
self.scroll = scrolledWindow = Gtk.ScrolledWindow()
scrolledWindow.add(goocalendar)
vbox.pack_start(toolbar, expand=False, fill=False, padding=0)
vbox.pack_start(scrolledWindow, expand=True, fill=True, padding=0)
return vbox
def on_page_changed(self, goocalendar, day, toolbar):
toolbar.update_displayed_date()
@@ -9,11 +9,11 @@ from .dates_period import DatesPeriod
class Calendar_(goocalendar.Calendar):
'Calendar'
def __init__(self, attrs, screen, fields, event_store=None):
def __init__(self, attrs, view, fields, event_store=None):
super(Calendar_, self).__init__(
event_store, attrs.get('mode', 'month'))
self.attrs = attrs
self.screen = screen
self.view_calendar = view
self.fields = fields
self.event_store = event_store
self.current_domain_period = self.get_displayed_period()
@@ -76,8 +76,8 @@ class Calendar_(goocalendar.Calendar):
def display(self, group):
dtstart = self.attrs['dtstart']
dtend = self.attrs.get('dtend')
if self.screen.current_record:
record = self.screen.current_record
if self.view_calendar.record:
record = self.view_calendar.record
date = record[dtstart].get(record)
if date: # select the day of the current record
self.select(date)
+176 -208
View File
@@ -2,11 +2,10 @@
# this repository contains the full copyright notices and license terms.
import operator
import gettext
from collections import defaultdict
from gi.repository import Gtk
from . import View
from . import View, XMLViewParser
from tryton.common.focus import (get_invisible_ancestor, find_focused_child,
next_focus_widget, find_focusable_child, find_first_focus_widget)
from tryton.common import Tooltips, node_attributes, IconFactory, get_align
@@ -163,102 +162,123 @@ class HContainer(Container):
self.table.pack_start(widget, expand=expand, fill=fill, padding=1)
class ViewForm(View):
editable = True
class FormXMLViewParser(XMLViewParser):
def __init__(self, screen, xml):
super(ViewForm, self).__init__(screen, xml)
self.view_type = 'form'
self.widgets = defaultdict(list)
self.state_widgets = []
self.notebooks = []
self.expandables = []
WIDGETS = {
'biginteger': Integer,
'binary': Binary,
'boolean': CheckBox,
'callto': CallTo,
'char': Char,
'date': Date,
'datetime': DateTime,
'dict': DictWidget,
'email': Email,
'float': Float,
'image': Image2,
'integer': Integer,
'many2many': Many2Many,
'many2one': Many2One,
'multiselection': MultiSelection,
'numeric': Float,
'one2many': One2Many,
'one2one': One2One,
'password': Password,
'progressbar': ProgressBar,
'pyson': PYSON,
'reference': Reference,
'richtext': RichTextBox,
'selection': Selection,
'sip': SIP,
'text': TextBox,
'time': Time,
'timedelta': TimeDelta,
'timestamp': DateTime,
'url': URL,
}
container = self.parse(xml)
def __init__(self, view, exclude_field, field_attrs):
super().__init__(view, exclude_field, field_attrs)
self._containers = []
self._mnemonics = {}
vbox = Gtk.VBox()
vp = Gtk.Viewport()
vp.set_shadow_type(Gtk.ShadowType.NONE)
vp.add(container.table)
self.scroll = scroll = Gtk.ScrolledWindow()
scroll.add(vp)
scroll.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll.set_placement(Gtk.CornerType.TOP_LEFT)
viewport = Gtk.Viewport()
viewport.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
viewport.add(scroll)
vbox.pack_start(viewport, expand=True, fill=True, padding=0)
@property
def container(self):
if self._containers:
return self._containers[-1]
return None
self.widget = vbox
self._viewport = vp
def _parse_form(self, node, attributes):
container_attributes = node_attributes(node)
container = Container.constructor(
int(container_attributes.get('col', 4)),
container_attributes.get('homogeneous', False))
self.view.viewport.add(container.table)
self.parse_child(node, container)
assert not self._containers
def parse(self, node, container=None):
if not container:
node_attrs = node_attributes(node)
container = Container.constructor(
int(node_attrs.get('col', 4)),
node_attrs.get('homogeneous', False))
mnemonics = {}
for node in node.childNodes:
if node.nodeType != node.ELEMENT_NODE:
continue
node_attrs = node_attributes(node)
for b_field in ('readonly', 'homogeneous'):
if b_field in node_attrs:
node_attrs[b_field] = bool(int(node_attrs[b_field]))
for i_field in ('yexpand', 'yfill', 'xexpand', 'xfill', 'colspan',
'position'):
if i_field in node_attrs:
node_attrs[i_field] = int(node_attrs[i_field])
def parse_child(self, node, container=None):
if container:
self._containers.append(container)
for child in node.childNodes:
self.parse(child)
if container:
self._containers.pop()
parser = getattr(self, '_parse_%s' % node.tagName)
widget = parser(node, container, node_attrs)
if not widget:
continue
name = node_attrs.get('name')
if node.tagName == 'label' and name:
mnemonics[name] = widget
if node.tagName == 'field':
if name in mnemonics and widget.mnemonic_widget:
label = mnemonics.pop(name)
label.set_label(set_underline(label.get_label()))
label.set_use_underline(True)
label.set_mnemonic_widget(widget.mnemonic_widget)
return container
def _parse_field(self, node, attributes):
name = attributes['name']
if name and name == self.exclude_field:
self.container.add(None, attributes)
return
def _parse_image(self, node, container, attributes):
widget = self.WIDGETS[attributes['widget']](self.view, attributes)
self.view.widgets[name].append(widget)
if widget.expand:
attributes.setdefault('yexpand', True)
attributes.setdefault('yfill', True)
if attributes.get('height') or attributes.get('width'):
widget.widget.set_size_request(
int(attributes.get('width', -1)),
int(attributes.get('height', -1)))
self.container.add(Alignment(widget.widget, attributes), attributes)
if name in self._mnemonics and widget.mnemonic_widget:
label = self._mnemonics.pop(name)
label.set_label(set_underline(label.get_label()))
label.set_use_underline(True)
label.set_mnemonic_widget(widget.mnemonic_widget)
def _parse_button(self, node, attributes):
button = Button(attributes)
button.connect('clicked', self.view.button_clicked)
self.view.state_widgets.append(button)
self.container.add(button, attributes)
def _parse_image(self, node, attributes):
image = Image(attrs=attributes)
self.state_widgets.append(image)
container.add(image, attributes)
self.view.state_widgets.append(image)
self.container.add(image, attributes)
def _parse_separator(self, node, container, attributes):
if 'name' in attributes:
field = self.group.fields[attributes['name']]
for attr in ('states', 'string'):
if attr not in attributes and attr in field.attrs:
attributes[attr] = field.attrs[attr]
def _parse_separator(self, node, attributes):
vbox = VBox(attrs=attributes)
if attributes.get('string'):
label = Label(label=attributes['string'], attrs=attributes)
label.set_halign(get_align(attributes.get('xalign', 0.0)))
label.set_valign(get_align(attributes.get('yalign', 0.5)))
vbox.pack_start(label, expand=True, fill=True, padding=0)
self.state_widgets.append(label)
self.view.state_widgets.append(label)
vbox.pack_start(Gtk.HSeparator(), expand=True, fill=True, padding=0)
self.state_widgets.append(vbox)
container.add(vbox, attributes)
self.view.state_widgets.append(vbox)
self.container.add(vbox, attributes)
def _parse_label(self, node, container, attributes):
if 'name' in attributes:
field = self.group.fields[attributes['name']]
if attributes['name'] == self.screen.exclude_field:
container.add(None, attributes)
return
if 'states' not in attributes and 'states' in field.attrs:
attributes['states'] = field.attrs['states']
if 'string' not in attributes:
attributes['string'] = field.attrs['string'] + _(':')
def _parse_label(self, node, attributes):
name = attributes.get('name')
if name and name == self.exclude_field:
self.container.add(None, attributes)
return
if CONFIG['client.modepda']:
attributes['xalign'] = 0.0
@@ -267,20 +287,15 @@ class ViewForm(View):
label.set_valign(get_align(attributes.get('yalign', 0.5)))
label.set_angle(int(attributes.get('angle', 0)))
attributes.setdefault('xexpand', 0)
self.state_widgets.append(label)
container.add(label, attributes)
return label
self.view.state_widgets.append(label)
self.container.add(label, attributes)
if name:
self._mnemonics[name] = label
def _parse_newline(self, node, container, attributes):
container.add_row()
def _parse_newline(self, node, attributes):
self.container.add_row()
def _parse_button(self, node, container, attributes):
button = Button(attributes)
button.connect('clicked', self.button_clicked)
self.state_widgets.append(button)
container.add(button, attributes)
def _parse_notebook(self, node, container, attributes):
def _parse_notebook(self, node, attributes):
attributes.setdefault('yexpand', True)
attributes.setdefault('yfill', True)
attributes.setdefault('colspan', 4)
@@ -295,28 +310,23 @@ class ViewForm(View):
# Force to display the first time it switches on a page
# This avoids glitch in position of widgets
def switch(notebook, page, page_num):
if not self.widget:
if not self.view.widget:
# Not yet finish to parse
return
notebook.grab_focus()
self.display()
self.view.display()
notebook.disconnect(handler_id)
handler_id = notebook.connect('switch-page', switch)
self.state_widgets.append(notebook)
self.view.state_widgets.append(notebook)
self.notebooks.append(notebook)
container.add(notebook, attributes)
self.parse(node, notebook)
self.view.notebooks.append(notebook)
self.container.add(notebook, attributes)
self.parse_child(node, notebook)
def _parse_page(self, node, notebook, attributes):
def _parse_page(self, node, attributes):
tab_box = Gtk.HBox(spacing=3)
if 'name' in attributes:
field = self.group.fields[attributes['name']]
if attributes['name'] == self.screen.exclude_field:
return
for attr in ('states', 'string'):
if attr not in attributes and attr in field.attrs:
attributes[attr] = field.attrs[attr]
if 'name' in attributes and attributes['name'] == self.exclude_field:
return
label = Gtk.Label(label=set_underline(attributes['string']))
label.set_use_underline(True)
@@ -335,133 +345,91 @@ class ViewForm(View):
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scrolledwindow.add(viewport)
scrolledwindow.show_all()
self.state_widgets.append(scrolledwindow)
notebook.append_page(scrolledwindow, tab_box)
container = self.parse(node)
self.view.state_widgets.append(scrolledwindow)
self.container.append_page(scrolledwindow, tab_box)
container = Container.constructor(
int(attributes.get('col', 4)),
attributes.get('homogeneous', False))
self.parse_child(node, container)
viewport.add(container.table)
def _parse_field(self, node, container, attributes):
name = attributes['name']
field = self.group.fields[name]
def _parse_group(self, node, attributes):
group = Container.constructor(
int(attributes.get('col', 4)),
attributes.get('homogeneous', False))
self.parse_child(node, group)
if (name not in self.group.fields
or name == self.screen.exclude_field):
container.add(None, attributes)
if 'name' in attributes and attributes['name'] == self.exclude_field:
self.container.add(None, attributes)
return
if 'widget' not in attributes:
attributes['widget'] = field.attrs['type']
for i_field in ('width', 'height'):
if i_field in attributes:
attributes[i_field] = int(attributes[i_field])
for attr in ('relation', 'domain', 'selection',
'relation_field', 'string', 'help', 'views',
'add_remove', 'sort', 'context', 'size', 'filename',
'autocomplete', 'translate', 'create', 'delete',
'selection_change_with', 'schema_model'):
if attr in field.attrs and attr not in attributes:
attributes[attr] = field.attrs[attr]
Widget = self.get_widget(attributes['widget'])
widget = Widget(self, attributes)
self.widgets[name].append(widget)
if Widget.expand:
attributes.setdefault('yexpand', True)
attributes.setdefault('yfill', True)
if attributes.get('height') or attributes.get('width'):
widget.widget.set_size_request(
int(attributes.get('width', -1)),
int(attributes.get('height', -1)))
container.add(Alignment(widget.widget, attributes), attributes)
return widget
def _parse_group(self, node, container, attributes):
group = self.parse(node)
if 'name' in attributes:
field = self.group.fields[attributes['name']]
if attributes['name'] == self.screen.exclude_field:
container.add(None, attributes)
return
for attr in ('states', 'string'):
if attr not in attributes and attr in field.attrs:
attributes[attr] = field.attrs[attr]
can_expand = attributes.get('expandable')
if can_expand:
widget = Expander(label=attributes.get('string'), attrs=attributes)
widget.add(group.table)
widget.set_expanded(can_expand == '1')
self.expandables.append(widget)
self.view.expandables.append(widget)
else:
widget = Frame(label=attributes.get('string'), attrs=attributes)
widget.add(group.table)
self.state_widgets.append(widget)
container.add(widget, attributes)
self.view.state_widgets.append(widget)
self.container.add(widget, attributes)
def _parse_paned(self, node, container, attributes, Paned):
def _parse_hpaned(self, node, attributes):
self._parse_paned(node, attributes, Gtk.HPaned)
def _parse_vpaned(self, node, attributes):
self._parse_paned(node, attributes, Gtk.VPaned)
def _parse_paned(self, node, attributes, Paned):
attributes.setdefault('yexpand', True)
attributes.setdefault('yfill', True)
paned = Paned()
if 'position' in attributes:
paned.set_position(attributes['position'])
container.add(paned, attributes)
self.parse(node, paned)
self.container.add(paned, attributes)
self.parse_child(node, paned)
def _parse_hpaned(self, node, container, attributes):
self._parse_paned(node, container, attributes, Gtk.HPaned)
def _parse_vpaned(self, node, container, attributes):
self._parse_paned(node, container, attributes, Gtk.VPaned)
def _parse_child(self, node, paned, attributes):
container = self.parse(node)
def _parse_child(self, node, attributes):
paned = self.container
container = Container.constructor(
int(attributes.get('col', 4)),
attributes.get('homogeneous', False))
self.parse_child(node, container)
if not paned.get_child1():
pack = paned.pack1
else:
pack = paned.pack2
pack(container.table, resize=True, shrink=True)
WIDGETS = {
'date': Date,
'datetime': DateTime,
'time': Time,
'timestamp': DateTime,
'float': Float,
'numeric': Float,
'integer': Integer,
'biginteger': Integer,
'selection': Selection,
'char': Char,
'password': Password,
'timedelta': TimeDelta,
'boolean': CheckBox,
'reference': Reference,
'binary': Binary,
'text': TextBox,
'one2many': One2Many,
'many2many': Many2Many,
'many2one': Many2One,
'email': Email,
'url': URL,
'callto': CallTo,
'sip': SIP,
'image': Image2,
'progressbar': ProgressBar,
'one2one': One2One,
'richtext': RichTextBox,
'dict': DictWidget,
'multiselection': MultiSelection,
'pyson': PYSON,
}
@classmethod
def get_widget(cls, name):
return cls.WIDGETS[name]
class ViewForm(View):
editable = True
view_type = 'form'
xml_parser = FormXMLViewParser
def __init__(self, screen, xml):
self.notebooks = []
self.expandables = []
vbox = Gtk.VBox()
vp = Gtk.Viewport()
vp.set_shadow_type(Gtk.ShadowType.NONE)
self.scroll = scroll = Gtk.ScrolledWindow()
scroll.add(vp)
scroll.set_policy(
Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
scroll.set_placement(Gtk.CornerType.TOP_LEFT)
viewport = Gtk.Viewport()
viewport.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
viewport.add(scroll)
vbox.pack_start(viewport, expand=True, fill=True, padding=0)
self.widget = vbox
self.viewport = vp
super().__init__(screen, xml)
def get_fields(self):
return list(self.widgets.keys())
@@ -554,7 +522,7 @@ class ViewForm(View):
focus_widget = find_focusable_child(self.widgets[
self.attributes['cursor']][0].widget)
else:
child = find_focusable_child(self._viewport)
child = find_focusable_child(self.viewport)
if child:
child.grab_focus()
record = self.record
@@ -568,7 +536,7 @@ class ViewForm(View):
invalid_widgets.append(invalid_widget)
if invalid_widgets:
focus_widget = find_first_focus_widget(
self._viewport, invalid_widgets)
self.viewport, invalid_widgets)
if focus_widget:
for notebook in self.notebooks:
for i in range(notebook.get_n_pages()):
+48 -45
View File
@@ -6,7 +6,7 @@ import gettext
from gi.repository import Gtk
from . import View
from . import View, XMLViewParser
from .graph_gtk.bar import VerticalBar, HorizontalBar
from .graph_gtk.line import Line
from .graph_gtk.pie import Pie
@@ -19,57 +19,60 @@ from tryton.gui import Main
_ = gettext.gettext
class ViewGraph(View):
def __init__(self, screen, xml):
super(ViewGraph, self).__init__(screen, xml)
self.view_type = 'graph'
self.widgets = {}
self.widget = self.parse(xml)
def parse(self, node):
xfield = None
yfields = []
for node in node.childNodes:
if node.nodeType != node.ELEMENT_NODE:
continue
if node.tagName == 'x':
for child in node.childNodes:
if not child.nodeType == child.ELEMENT_NODE:
continue
xfield = node_attributes(child)
field = self.screen.group.fields[xfield['name']]
if not xfield.get('string'):
xfield['string'] = field.attrs['string']
elif node.tagName == 'y':
for child in node.childNodes:
if not child.nodeType == child.ELEMENT_NODE:
continue
yattrs = node_attributes(child)
if not yattrs.get('string') and yattrs['name'] != '#':
field = self.screen.group.fields[yattrs['name']]
yattrs['string'] = field.attrs['string']
yfields.append(yattrs)
Widget = self.get_widget(self.attributes.get('type', 'vbar'))
widget = Widget(self, xfield, yfields)
self.widgets['root'] = widget
event = Gtk.EventBox()
event.add(widget)
event.connect('button-press-event', self.button_press)
return event
class GraphXMLViewParser(XMLViewParser):
WIDGETS = {
'vbar': VerticalBar,
'hbar': HorizontalBar,
'line': Line,
'pie': Pie,
'vbar': VerticalBar,
}
@classmethod
def get_widget(cls, name):
return cls.WIDGETS[name]
def __init__(self, view, exclude_field, field_attrs):
super().__init__(view, exclude_field, field_attrs)
self._xfield = None
self._yfields = []
def _node_attributes(self, node):
node_attrs = node_attributes(node)
if 'name' in node_attrs:
if not node_attrs.get('string') and node_attrs['name'] != '#':
field = self.field_attrs[node_attrs['name']]
node_attrs['string'] = field['string']
return node_attrs
def _parse_graph(self, node, attributes):
for child in node.childNodes:
self.parse(child)
Widget = self.WIDGETS.get(attributes.get('type', 'vbar'))
widget = Widget(
self.view, self._xfield, self._yfields)
self.view.widget.add(widget)
self.view.widgets['root'] = widget
def _parse_x(self, node, attributes):
for child in node.childNodes:
if not child.nodeType == child.ELEMENT_NODE:
continue
self._xfield = self._node_attributes(child)
def _parse_y(self, node, attributes):
for child in node.childNodes:
if not child.nodeType == child.ELEMENT_NODE:
continue
self._yfields.append(self._node_attributes(child))
class ViewGraph(View):
view_type = 'graph'
editable = False
xml_parser = GraphXMLViewParser
def __init__(self, screen, xml):
self.widget = event = Gtk.EventBox()
super().__init__(screen, xml)
event.connect('button-press-event', self.button_press)
def __getitem__(self, name):
return None
+197 -239
View File
@@ -5,7 +5,6 @@ import json
import locale
import gettext
from functools import wraps
from collections import defaultdict
from gi.repository import Gdk, GLib, GObject, Gtk
from pygtkcompat.generictreemodel import GenericTreeModel
@@ -18,7 +17,7 @@ from tryton.common import RPCExecute, RPCException, node_attributes, Tooltips
from tryton.common import domain_inversion, simplify, unique_value
from tryton.pyson import PYSONDecoder
import tryton.common as common
from . import View
from . import View, XMLViewParser
from .list_gtk.editabletree import EditableTreeView, TreeView
from .list_gtk.widget import (Affix, Char, Text, Int, Boolean, URL, Date,
Time, Float, TimeDelta, Binary, M2O, O2O, O2M, M2M, Selection, Reference,
@@ -250,26 +249,215 @@ class AdaptModelGroup(GenericTreeModel):
return record.parent
class TreeXMLViewParser(XMLViewParser):
WIDGETS = {
'biginteger': Int,
'binary': Binary,
'boolean': Boolean,
'callto': URL,
'char': Char,
'date': Date,
'email': URL,
'float': Float,
'image': Image,
'integer': Int,
'many2many': M2M,
'many2one': M2O,
'numeric': Float,
'one2many': O2M,
'one2one': O2O,
'progressbar': ProgressBar,
'reference': Reference,
'selection': Selection,
'sip': URL,
'text': Text,
'time': Time,
'timedelta': TimeDelta,
'url': URL,
}
def _parse_tree(self, node, attributes):
for child in node.childNodes:
self.parse(child)
def _parse_field(self, node, attributes):
name = attributes['name']
widget = self.WIDGETS[attributes['widget']](self.view, attributes)
self.view.widgets[name].append(widget)
column = Gtk.TreeViewColumn(attributes['string'])
column._type = 'field'
column.name = name
prefixes = []
suffixes = []
if attributes['widget'] in ['url', 'email', 'callto', 'sip']:
prefixes.append(
Affix(self.view, attributes, protocol=attributes['widget']))
if 'icon' in attributes:
prefixes.append(Affix(self.view, attributes))
for affix in node.childNodes:
affix_attrs = node_attributes(affix)
if 'name' not in affix_attrs:
affix_attrs['name'] = attributes['name']
if affix.tagName == 'prefix':
list_ = prefixes
else:
list_ = suffixes
list_.append(Affix(self, affix_attrs))
for prefix in prefixes:
column.pack_start(prefix.renderer, expand=prefix.expand)
column.set_cell_data_func(prefix.renderer, prefix.setter)
column.pack_start(widget.renderer, expand=True)
column.set_cell_data_func(widget.renderer, widget.setter)
for suffix in suffixes:
column.pack_start(suffix.renderer, expand=suffix.expand)
column.set_cell_data_func(suffix.renderer, suffix.setter)
self._set_column_widget(column, attributes, align=widget.align)
self._set_column_width(column, attributes)
if (not self.view.attributes.get('sequence')
and not self.view.children_field
and self.field_attrs[name].get('sortable', True)):
column.connect('clicked', self.view.sort_model)
self.view.treeview.append_column(column)
if 'sum' in attributes:
text = attributes['sum'] + _(':')
label, sum_ = Gtk.Label(label=text), Gtk.Label()
hbox = Gtk.HBox()
hbox.pack_start(label, expand=True, fill=False, padding=2)
hbox.pack_start(sum_, expand=True, fill=False, padding=2)
hbox.show_all()
self.view.sum_box.pack_start(
hbox, expand=False, fill=False, padding=0)
self.view.sum_widgets.append((attributes['name'], sum_))
def _parse_button(self, node, attributes):
button = Button(self.view, attributes)
self.view.state_widgets.append(button)
column = Gtk.TreeViewColumn(
attributes.get('string', ''), button.renderer)
column._type = 'button'
column.name = None
column.set_cell_data_func(button.renderer, button.setter)
self._set_column_widget(column, attributes, arrow=False)
self._set_column_width(column, attributes)
decoder = PYSONDecoder(self.view.screen.context)
column.set_visible(
not decoder.decode(attributes.get('tree_invisible', '0')))
self.view.treeview.append_column(column)
def _set_column_widget(self, column, attributes, arrow=True, align=0.5):
hbox = Gtk.HBox(homogeneous=False, spacing=2)
label = Gtk.Label(label=attributes['string'])
field = self.field_attrs.get(attributes['name'], {})
if field and self.view.editable:
required = field.get('required')
readonly = field.get('readonly')
common.apply_label_attributes(label, readonly, required)
label.show()
help = attributes.get('help')
if help:
tooltips = Tooltips()
tooltips.set_tip(label, help)
tooltips.enable()
if arrow:
arrow_widget = Gtk.Arrow(
arrow_type=Gtk.ArrowType.NONE, shadow_type=Gtk.ShadowType.NONE)
arrow_widget.show()
column.arrow = arrow_widget
hbox.pack_start(label, expand=True, fill=True, padding=0)
if arrow:
hbox.pack_start(arrow_widget, expand=False, fill=False, padding=0)
column.set_clickable(True)
hbox.show()
column.set_widget(hbox)
column.set_alignment(align)
def _set_column_width(self, column, attributes):
default_width = {
'integer': 60,
'biginteger': 60,
'float': 80,
'numeric': 80,
'timedelta': 100,
'date': 100,
'datetime': 100,
'time': 100,
'selection': 90,
'char': 100,
'one2many': 50,
'many2many': 50,
'boolean': 20,
'binary': 200,
}
screen = self.view.screen
width = screen.tree_column_width[screen.model_name].get(column.name)
field_attrs = self.field_attrs.get(attributes['name'], {})
if not width:
if 'width' in attributes:
width = int(attributes['width'])
elif field_attrs:
width = default_width.get(field_attrs['type'], 100)
else:
width = 80
column.width = width
if width > 0:
column.set_fixed_width(width)
column.set_min_width(1)
expand = attributes.get('expand', False)
column.set_expand(expand)
column.set_resizable(True)
if attributes.get('widget') != 'text':
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
class ViewTree(View):
view_type = 'tree'
xml_parser = TreeXMLViewParser
def __init__(self, screen, xml, children_field):
super(ViewTree, self).__init__(screen, xml)
self.view_type = 'tree'
self.widgets = defaultdict(list)
self.state_widgets = []
self.children_field = children_field
self.sum_widgets = []
self.sum_box = Gtk.HBox()
self.reload = False
if self.attributes.get('editable') and not screen.readonly:
self.treeview = EditableTreeView(self.attributes['editable'], self)
self.treeview = None
editable = xml.getAttribute('editable')
if editable and not screen.readonly:
self.treeview = EditableTreeView(editable, self)
grid_lines = Gtk.TreeViewGridLines.BOTH
else:
self.treeview = TreeView(self)
grid_lines = Gtk.TreeViewGridLines.VERTICAL
super().__init__(screen, xml)
self.mnemonic_widget = self.treeview
self.parse(xml)
# Add last column if necessary
for column in self.treeview.get_columns():
if column.get_expand():
break
else:
column = Gtk.TreeViewColumn()
column._type = 'fill'
column.name = None
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
self.treeview.append_column(column)
self.treeview.set_property('rules-hint', True)
self.treeview.set_property('enable-grid-lines', grid_lines)
@@ -307,231 +495,12 @@ class ViewTree(View):
self.display()
def parse(self, xml):
for node in xml.childNodes:
if node.nodeType != node.ELEMENT_NODE:
continue
if node.tagName == 'field':
self._parse_field(node)
elif node.tagName == 'button':
self._parse_button(node)
self.add_last_column()
def _parse_field(self, node):
group = self.group
node_attrs = node_attributes(node)
name = node_attrs['name']
field = group.fields[name]
for b_field in ('readonly', 'expand'):
if b_field in node_attrs:
node_attrs[b_field] = bool(int(node_attrs[b_field]))
for i_field in ('width', 'height'):
if i_field in node_attrs:
node_attrs[i_field] = int(node_attrs[i_field])
if 'widget' not in node_attrs:
node_attrs['widget'] = field.attrs['type']
for attr in ('relation', 'domain', 'selection',
'relation_field', 'string', 'views', 'invisible',
'add_remove', 'sort', 'context', 'size', 'filename',
'autocomplete', 'translate', 'create', 'delete',
'selection_change_with', 'schema_model'):
if (attr in field.attrs
and attr not in node_attrs):
node_attrs[attr] = field.attrs[attr]
Widget = self.get_widget(node_attrs['widget'])
widget = Widget(self, node_attrs)
self.widgets[name].append(widget)
column = Gtk.TreeViewColumn(node_attrs['string'])
column._type = 'field'
column.name = name
prefixes = []
suffixes = list(widget.suffixes)
if node_attrs['widget'] in ('url', 'email', 'callto', 'sip'):
prefixes.append(Affix(self, node_attrs,
protocol=node_attrs['widget']))
if 'icon' in node_attrs:
prefixes.append(Affix(self, node_attrs))
for affix in node.childNodes:
affix_attrs = node_attributes(affix)
if 'name' not in affix_attrs:
affix_attrs['name'] = name
if affix.tagName == 'prefix':
list_ = prefixes
else:
list_ = suffixes
list_.append(Affix(self, affix_attrs))
prefixes.extend(widget.prefixes)
for prefix in prefixes:
column.pack_start(prefix.renderer, expand=prefix.expand)
column.set_cell_data_func(prefix.renderer,
prefix.setter)
column.pack_start(widget.renderer, expand=True)
column.set_cell_data_func(widget.renderer, widget.setter)
for suffix in suffixes:
column.pack_start(suffix.renderer, expand=suffix.expand)
column.set_cell_data_func(suffix.renderer,
suffix.setter)
self.set_column_widget(column, field, node_attrs, align=widget.align)
self.set_column_width(column, field, node_attrs)
if (not self.attributes.get('sequence')
and not self.children_field
and field.attrs.get('sortable', True)):
column.connect('clicked', self.sort_model)
self.treeview.append_column(column)
self.add_sum(node_attrs)
def _parse_button(self, node):
node_attrs = node_attributes(node)
widget = Button(self, node_attrs)
self.state_widgets.append(widget)
column = Gtk.TreeViewColumn(node_attrs.get('string', ''),
widget.renderer)
column._type = 'button'
column.name = None
column.set_cell_data_func(widget.renderer, widget.setter)
self.set_column_widget(column, None, node_attrs, arrow=False)
self.set_column_width(column, None, node_attrs)
decoder = PYSONDecoder(self.screen.context)
column.set_visible(
not decoder.decode(node_attrs.get('tree_invisible', '0')))
self.treeview.append_column(column)
WIDGETS = {
'char': Char,
'many2one': M2O,
'date': Date,
'one2many': O2M,
'many2many': M2M,
'selection': Selection,
'float': Float,
'numeric': Float,
'timedelta': TimeDelta,
'integer': Int,
'biginteger': Int,
'time': Time,
'boolean': Boolean,
'text': Text,
'url': URL,
'email': URL,
'callto': URL,
'sip': URL,
'progressbar': ProgressBar,
'reference': Reference,
'one2one': O2O,
'binary': Binary,
'image': Image,
}
@classmethod
def get_widget(cls, name):
return cls.WIDGETS[name]
def set_column_widget(self, column, field, attributes,
arrow=True, align=0.5):
hbox = Gtk.HBox(homogeneous=False, spacing=2)
label = Gtk.Label(label=attributes['string'])
if field and self.editable:
required = field.attrs.get('required')
readonly = field.attrs.get('readonly')
common.apply_label_attributes(label, readonly, required)
label.show()
help = None
if field and field.attrs.get('help'):
help = field.attrs['help']
elif attributes.get('help'):
help = attributes['help']
if help:
tooltips = Tooltips()
tooltips.set_tip(label, help)
tooltips.enable()
if arrow:
arrow_widget = Gtk.Arrow(
arrow_type=Gtk.ArrowType.NONE, shadow_type=Gtk.ShadowType.NONE)
arrow_widget.show()
column.arrow = arrow_widget
hbox.pack_start(label, expand=True, fill=True, padding=0)
if arrow:
hbox.pack_start(
arrow_widget, expand=False, fill=False, padding=0)
column.set_clickable(True)
hbox.show()
column.set_widget(hbox)
column.set_alignment(align)
def set_column_width(self, column, field, attributes):
default_width = {
'integer': 60,
'biginteger': 60,
'float': 80,
'numeric': 80,
'timedelta': 100,
'date': 100,
'datetime': 100,
'time': 100,
'selection': 90,
'char': 100,
'one2many': 50,
'many2many': 50,
'boolean': 20,
'binary': 200,
}
width = self.screen.tree_column_width[self.screen.model_name].get(
column.name)
if not width:
if 'width' in attributes:
width = int(attributes['width'])
elif field:
width = default_width.get(field.attrs['type'], 100)
else:
width = 80
column.width = width
if width > 0:
column.set_fixed_width(width)
column.set_min_width(1)
expand = attributes.get('expand', False)
column.set_expand(expand)
column.set_resizable(True)
if attributes.get('widget') != 'text':
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
def get_column_widget(self, column):
'Return the widget of the column'
idx = [c for c in self.treeview.get_columns()
if c.name == column.name].index(column)
return self.widgets[column.name][idx]
def add_sum(self, attributes):
if 'sum' not in attributes:
return
text = attributes['sum'] + _(':')
label, sum_ = Gtk.Label(label=text), Gtk.Label()
hbox = Gtk.HBox()
hbox.pack_start(label, expand=True, fill=False, padding=2)
hbox.pack_start(sum_, expand=True, fill=False, padding=2)
hbox.show_all()
self.sum_box.pack_start(hbox, expand=False, fill=False, padding=0)
self.sum_widgets.append((attributes['name'], sum_))
def sort_model(self, column):
for col in self.treeview.get_columns():
if col != column and getattr(col, 'arrow', None):
@@ -577,17 +546,6 @@ class ViewTree(View):
else:
arrow.set(direction, Gtk.ShadowType.IN)
def add_last_column(self):
for column in self.treeview.get_columns():
if column.get_expand():
break
else:
column = Gtk.TreeViewColumn()
column._type = 'fill'
column.name = None
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
self.treeview.append_column(column)
def set_drag_and_drop(self):
dnd = False
if self.children_field: