Refactorize XML parsing

issue3938
review11281004
This commit is contained in:
Cédric Krier committed 2014-06-09 18:09:45 +02:00
1 parent a320fbc2b2
commit a2ac629320
45 files changed
+1596 -1852

No files matched your search

+2 -2
View File
@@ -1054,13 +1054,13 @@ class Main(object):
screen.switch_view(view_type=screen.current_view.view_type)
vbox.pack_start(screen.screen_container.alternate_viewport, True, True)
screen.current_view.widget_tree.set_headers_visible(False)
treeview = screen.current_view.treeview
treeview.set_headers_visible(False)
self.menu_expander.add(vbox)
self.menu_expander.show()
# Favorite column
treeview = screen.current_view.widget_tree
column = gtk.TreeViewColumn()
column.name = None
column._type = None
+1 -1
View File
@@ -94,7 +94,7 @@ class Action(SignalEvent):
return
if (self.screen.current_view.view_type == 'tree' and
self.screen.current_view.widget_tree.keyword_open):
self.screen.current_view.treeview.keyword_open):
GenericAction.exec_keyword('tree_open', {
'model': self.screen.model_name,
'id': self.screen.id_get(),
-161
View File
@@ -1,161 +0,0 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
'Parser'
import gtk
import gettext
from tryton.gui.window.view_form.view.form_gtk.parser import _container, VBox
import tryton.common as common
from action import Action
from tryton.config import CONFIG
_ = gettext.gettext
class ParserBoard(object):
def __init__(self, context=None):
self.title = None
self.context = context
def parse(self, root_node, notebook=None, paned=None, tooltips=None):
widgets = []
attrs = common.node_attributes(root_node)
if not tooltips:
tooltips = common.Tooltips()
container = _container(tooltips)
container.new(col=int(attrs.get('col', 4)))
if not self.title:
self.title = attrs.get('string', 'Unknown')
for node in root_node.childNodes:
if not node.nodeType == node.ELEMENT_NODE:
continue
attrs = common.node_attributes(node)
yexpand = int(attrs.get('yexpand', 0))
yfill = int(attrs.get('yfill', 0))
xexpand = int(attrs.get('xexpand', 1))
xfill = int(attrs.get('xfill', 1))
colspan = int(attrs.get('colspan', 1))
if node.localName == 'image':
common.ICONFACTORY.register_icon(attrs['name'])
icon = gtk.Image()
icon.set_from_stock(attrs['name'], gtk.ICON_SIZE_DIALOG)
container.wid_add(icon,
help_tip=attrs.get('help', False),
colspan=colspan,
yexpand=yexpand, yfill=yfill, ypadding=10,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'separator':
text = attrs.get('string', '')
vbox = VBox(attrs=attrs)
if text:
label = gtk.Label(text)
label.set_use_markup(True)
label.set_alignment(float(attrs.get('align', 0.0)), 0.5)
vbox.pack_start(label)
vbox.pack_start(gtk.HSeparator())
container.wid_add(vbox,
help_tip=attrs.get('help', False),
colspan=colspan,
yexpand=yexpand, yfill=yfill, ypadding=10,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'label':
text = attrs.get('string', '')
if not text:
container.empty_add(int(attrs.get('colspan', 1)))
continue
label = gtk.Label(text)
label.set_use_markup(True)
label.set_alignment(float(attrs.get('xalign', 1.0)),
float(attrs.get('yalign', 0.0)))
label.set_angle(int(attrs.get('angle', 0)))
xexpand = bool(attrs.get('xexpand', 0))
container.wid_add(label,
help_tip=attrs.get('help', False),
colspan=colspan,
yexpand=yexpand, yfill=yfill,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'newline':
container.newline()
elif node.localName == 'notebook':
notebook = gtk.Notebook()
if CONFIG['client.form_tab'] == 'top':
pos = gtk.POS_TOP
elif CONFIG['client.form_tab'] == 'left':
pos = gtk.POS_LEFT
elif CONFIG['client.form_tab'] == 'right':
pos = gtk.POS_RIGHT
elif CONFIG['client.form_tab'] == 'bottom':
pos = gtk.POS_BOTTOM
notebook.set_tab_pos(pos)
notebook.set_border_width(3)
container.wid_add(notebook,
colspan=int(attrs.get('colspan', 4)),
yexpand=True, yfill=True)
widget, new_widgets = self.parse(node, notebook,
tooltips=tooltips)
widgets += new_widgets
elif node.localName == 'page':
if CONFIG['client.form_tab'] == 'left':
angle = 90
elif CONFIG['client.form_tab'] == 'right':
angle = -90
else:
angle = 0
label = gtk.Label(attrs.get('string', _('No String Attr.')))
label.set_angle(angle)
widget, new_widgets = self.parse(node, notebook,
tooltips=tooltips)
widgets += new_widgets
notebook.append_page(widget, label)
elif node.localName == 'group':
widget, new_widgets = self.parse(node, tooltips=tooltips)
widgets += new_widgets
if attrs.get('string', None):
frame = gtk.Frame(attrs['string'])
frame.add(widget)
else:
frame = widget
container.wid_add(frame,
colspan=colspan,
yexpand=yexpand, yfill=yfill, ypadding=0,
xexpand=xexpand, xfill=xfill, xpadding=0)
elif node.localName == 'hpaned':
hpaned = gtk.HPaned()
container.wid_add(hpaned, colspan=int(attrs.get('colspan', 4)),
yexpand=True, yfill=True)
widget, new_widgets = self.parse(node, paned=hpaned,
tooltips=tooltips)
widgets += new_widgets
if 'position' in attrs:
hpaned.set_position(int(attrs['position']))
elif node.localName == 'vpaned':
vpaned = gtk.VPaned()
container.wid_add(vpaned, colspan=int(attrs.get('colspan', 4)),
yexpand=True, yfill=True)
widget, new_widgets = self.parse(node, paned=vpaned,
tooltips=tooltips)
widgets += new_widgets
if 'position' in attrs:
vpaned.set_position(int(attrs['position']))
elif node.localName == 'child':
widget, new_widgets = self.parse(node, paned=paned,
tooltips=tooltips)
widgets += new_widgets
if not paned.get_child1():
paned.pack1(widget, resize=True, shrink=True)
elif not paned.get_child2():
paned.pack2(widget, resize=True, shrink=True)
elif node.localName == 'action':
widget_act = Action(attrs, self.context)
widgets.append(widget_act)
yexpand = bool(attrs.get('yexpand', 1))
yfill = bool(attrs.get('yfill', 1))
container.wid_add(widget_act.widget,
colspan=colspan,
yexpand=yexpand, yfill=yfill,
xexpand=xexpand, xfill=xfill)
return container.pop(), widgets
+170 -12
View File
@@ -1,10 +1,15 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
'View board'
import gtk
import gettext
import xml.dom.minidom
from parser import ParserBoard
from tryton.gui.window.view_board.action import Action
from tryton.gui.window.view_form.view.form import Container
from tryton.common import node_attributes, ICONFACTORY
from tryton.config import CONFIG
from .action import Action
_ = gettext.gettext
class ViewBoard(object):
@@ -12,21 +17,174 @@ class ViewBoard(object):
def __init__(self, arch, context=None):
self.context = context
self.widgets = []
self.actions = []
xml_dom = xml.dom.minidom.parseString(arch)
parser = ParserBoard(context)
for node in xml_dom.childNodes:
if not node.nodeType == node.ELEMENT_NODE:
continue
self.widget, self.widgets = parser.parse(node)
break
self.actions = [x for x in self.widgets if isinstance(x, Action)]
for action in self.actions:
action.signal_connect(self, 'active-changed',
self._active_changed)
if node.nodeType == node.ELEMENT_NODE:
break
self.attributes = node_attributes(node)
self.widget = self.parse(node).table
self.widget.show_all()
self._active_changed(None)
def parse(self, node, container=None):
if not container:
node_attrs = node_attributes(node)
container = Container(int(node_attrs.get('col', 4)))
for node in node.childNodes:
if node.nodeType != node.ELEMENT_NODE:
continue
node_attrs = node_attributes(node)
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])
parser = getattr(self, '_parse_%s' % node.tagName)
parser(node, container, node_attrs)
return container
def _parse_image(self, node, container, attributes):
ICONFACTORY.register_icon(attributes['name'])
image = gtk.Image()
image.set_from_stock(attributes['name'], gtk.ICON_SIZE_DIALOG)
container.add(image, attributes)
def _parse_separator(self, node, container, attributes):
vbox = gtk.VBox()
if attributes.get('string'):
label = gtk.Label(attributes['string'])
label.set_alignment(float(attributes.get('xalign', 0.0)),
float(attributes.get('yalign', 0.5)))
vbox.pack_start(label)
vbox.pack_start(gtk.HSeparator())
container.add(vbox, attributes)
def _parse_label(self, node, container, attributes):
if not attributes.get('string'):
container.add(None, attributes)
return
label = gtk.Label(attributes['string'])
label.set_alignment(float(attributes.get('xalign', 0.0)),
float(attributes.get('yalign', 0.5)))
label.set_angle(int(attributes.get('angle', 0)))
attributes.setdefault('xexpand', 0)
container.add(label, attributes)
def _parse_newline(self, node, container, attributes):
container.add_row()
def _parse_notebook(self, node, container, attributes):
attributes.setdefault('yexpand', True)
attributes.setdefault('yfill', True)
notebook = gtk.Notebook()
notebook.set_scrollable(True)
positions = {
'top': gtk.POS_TOP,
'left': gtk.POS_LEFT,
'right': gtk.POS_RIGHT,
'bottom': gtk.POS_BOTTOM,
}
notebook.set_tab_pos(positions[CONFIG['client.form_tab']])
container.add(notebook, attributes)
self.parse(node, notebook)
def _parse_page(self, node, notebook, attributes):
if CONFIG['client.form_tab'] == 'left':
angle = 90
tab_box = gtk.VBox(spacing=3)
image_pos, image_rotate = ('end',
gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE)
elif CONFIG['client.form_tab'] == 'right':
angle = -90
tab_box = gtk.VBox(spacing=3)
image_pos, image_rotate = ('start',
gtk.gdk.PIXBUF_ROTATE_CLOCKWISE)
else:
angle = 0
tab_box = gtk.HBox(spacing=3)
image_pos, image_rotate = ('start',
gtk.gdk.PIXBUF_ROTATE_NONE)
if '_' not in attributes['string']:
attributes['string'] = '_' + attributes['string']
label = gtk.Label(attributes['string'])
label.set_angle(angle)
label.set_use_underline(True)
tab_box.pack_start(label)
if 'icon' in attributes:
ICONFACTORY.register_icon(attributes['icon'])
pixbuf = tab_box.render_icon(attributes['icon'],
gtk.ICON_SIZE_SMALL_TOOLBAR)
pixbuf = pixbuf.rotate_simple(image_rotate)
icon = gtk.Image()
icon.set_from_pixbuf(pixbuf)
if image_pos == 'end':
tab_box.pack_end(icon)
else:
tab_box.pack_start(icon)
tab_box.show_all()
viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_NONE)
scrolledwindow = gtk.ScrolledWindow()
scrolledwindow.set_shadow_type(gtk.SHADOW_NONE)
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolledwindow.add(viewport)
scrolledwindow.show_all()
notebook.append_page(scrolledwindow, tab_box)
container = self.parse(node)
viewport.add(container.table)
def _parse_group(self, node, container, attributes):
group = self.parse(node)
group.table.set_homogeneous(attributes.get('homogeneous', False))
frame = gtk.Frame(attributes.get('string'))
if not attributes.get('string'):
frame.set_shadow_type(gtk.SHADOW_NONE)
frame.set_border_width(0)
frame.add(group.table)
container.add(frame, attributes)
def _parse_paned(self, node, container, 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)
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)
if not paned.get_child1():
pack = paned.pack1
else:
pack = paned.pack2
pack(container.table, resize=True, shrink=True)
def _parse_action(self, node, container, attributes):
attributes.setdefault('yexpand', True)
attributes.setdefault('yfill', True)
action = Action(attributes, self.context)
action.signal_connect(self, 'active-changed', self._active_changed)
self.actions.append(action)
container.add(action.widget, attributes)
@property
def title(self):
self.attributes.get('string', '')
def widget_get(self):
return self.widget
+13 -21
View File
@@ -18,6 +18,7 @@ import logging
from tryton.gui.window.view_form.model.group import Group
from tryton.gui.window.view_form.view.screen_container import ScreenContainer
from tryton.gui.window.view_form.view import View
from tryton.signal_event import SignalEvent
from tryton.config import CONFIG
from tryton.exceptions import TrytonServerError, TrytonServerUnavailable
@@ -329,7 +330,7 @@ class Screen(SignalEvent):
def default_row_activate(self):
if (self.current_view.view_type == 'tree' and
self.current_view.widget_tree.keyword_open):
self.current_view.attributes.get('keyword_open')):
return Action.exec_keyword('tree_open', {
'model': self.model_name,
'id': self.id_get(),
@@ -402,14 +403,13 @@ class Screen(SignalEvent):
view_id = view['view_id']
xml_dom = xml.dom.minidom.parseString(arch)
for node in xml_dom.childNodes:
if node.localName == 'tree':
self.fields_view_tree = view
break
root, = xml_dom.childNodes
if root.tagName == 'tree':
self.fields_view_tree = view
# Ensure that loading is always lazy for fields on form view
# and always eager for fields on tree or graph view
if node.localName == 'form':
if root.tagName == 'form':
loading = 'lazy'
else:
loading = 'eager'
@@ -419,18 +419,11 @@ class Screen(SignalEvent):
else:
fields[field]['loading'] = \
self.group.fields[field].attrs['loading']
children_field = view.get('field_childs')
from tryton.gui.window.view_form.view.widget_parse import WidgetParse
self.group.add_fields(fields)
parser = WidgetParse(parent=self.parent)
view = parser.parse(self, xml_dom, self.group.fields,
children_field=children_field)
view = View.parse(self, xml_dom, view.get('field_childs'))
view.view_id = view_id
self.views.append(view)
return view
def new(self, default=True):
@@ -457,8 +450,7 @@ class Screen(SignalEvent):
def new_model_position(self):
position = -1
if (self.current_view and self.current_view.view_type == 'tree'
and hasattr(self.current_view.widget_tree, 'editable')
and self.current_view.widget_tree.editable == 'top'):
and self.current_view.attributes.get('editable') == 'top'):
position = 0
return position
@@ -723,8 +715,8 @@ class Screen(SignalEvent):
view.set_value()
self.set_cursor(reset_view=False)
if view.view_type == 'tree' and len(self.group):
start, end = view.widget_tree.get_visible_range()
vadjustment = view.widget_tree.get_vadjustment()
start, end = view.treeview.get_visible_range()
vadjustment = view.treeview.get_vadjustment()
vadjustment.value = vadjustment.value + vadjustment.page_increment
store = view.store
iter_ = store.get_iter(end)
@@ -788,8 +780,8 @@ class Screen(SignalEvent):
view.set_value()
self.set_cursor(reset_view=False)
if view.view_type == 'tree' and len(self.group):
start, end = view.widget_tree.get_visible_range()
vadjustment = view.widget_tree.get_vadjustment()
start, end = view.treeview.get_visible_range()
vadjustment = view.treeview.get_vadjustment()
vadjustment.value = vadjustment.value - vadjustment.page_increment
store = view.store
iter_ = store.get_iter(start)
@@ -1,2 +1,49 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from tryton.common import node_attributes
class View(object):
view_type = None
widget = None
view_id = None
modified = None
editable = None
children_field = None
def __init__(self, screen, xml):
self.screen = screen
self.fields = {}
self.attributes = node_attributes(xml)
screen.set_on_write(self.attributes.get('on_write'))
def set_value(self):
raise NotImplementedError
def get_fields(self):
raise NotImplementedError
def get_buttons(self):
raise NotImplementedError
@property
def title(self):
return self.attributes.get('string', '')
@staticmethod
def parse(screen, xml, children_field):
from .list import ViewTree
from .form import ViewForm
from .graph import ViewGraph
from .calendar_ import ViewCalendar
root, = xml.childNodes
tagname = root.tagName
if tagname == 'tree':
return ViewTree(screen, root, children_field)
elif tagname == 'form':
return ViewForm(screen, root)
elif tagname == 'graph':
return ViewGraph(screen, root)
elif tagname == 'calendar':
return ViewCalendar(screen, root)
+101 -16
View File
@@ -2,27 +2,112 @@
#this repository contains the full copyright notices and license terms.
from functools import wraps
from interface import ParserView
import gtk
import datetime
import gettext
from tryton.common import node_attributes
from . import View
try:
from .calendar_gtk.calendar_ import Calendar_
from .calendar_gtk.toolbar import Toolbar
except ImportError:
Calendar_ = None
Toolbar = None
_ = gettext.gettext
def goocalendar_required(func):
"Decorator for goocalendar required"
@wraps(func)
def wrapper(self, *args, **kwargs):
if 'goocalendar' not in self.children:
if 'goocalendar' not in self.widgets:
return
return func(self, *args, **kwargs)
return wrapper
class ViewCalendar(ParserView):
class ViewCalendar(View):
def __init__(self, screen, widget, children=None, buttons=None,
notebooks=None, cursor_widget=None, children_field=None):
super(ViewCalendar, self).__init__(screen, widget, children, buttons,
notebooks, cursor_widget, children_field)
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)
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
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)
goocalendar.connect('event-activated', self.on_event_activated)
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
scrolledWindow = gtk.ScrolledWindow()
scrolledWindow.add_with_viewport(goocalendar)
vbox.pack_start(toolbar, False, False)
vbox.pack_start(scrolledWindow, True, True)
return vbox
def on_page_changed(self, goocalendar, day, toolbar):
toolbar.update_displayed_date()
if goocalendar.update_domain():
self.screen.search_filter()
def on_view_changed(self, goocalendar, view, toolbar):
toolbar.update_displayed_date()
if goocalendar.update_domain():
self.screen.search_filter()
def on_event_pressed(self, goocalendar, event):
self.screen.current_record = event.record
def on_event_activated(self, goocalendar, event):
self.screen.switch_view('form')
def on_event_released(self, goocalendar, event):
dtstart = self.attributes.get('dtstart')
dtend = self.attributes.get('dtend') or dtstart
record = event.record
group = record.group
previous_start = record[dtstart].get(record)
new_start = event.start
new_end = event.end
if not isinstance(previous_start, datetime.datetime):
new_start = event.start.date()
new_end = event.end.date() if event.end else None
if previous_start <= new_start:
if new_end:
group.fields[dtend].set_client(record, new_end)
group.fields[dtstart].set_client(record, new_start)
else:
group.fields[dtstart].set_client(record, new_start)
if new_end:
group.fields[dtend].set_client(record, new_end)
goocalendar.select(new_start)
record.save()
def on_day_pressed(self, goocalendar, day):
self.screen.current_record = None
def on_day_activated(self, goocalendar, day):
self.screen.new()
def __getitem__(self, name):
return None
@@ -30,19 +115,19 @@ class ViewCalendar(ParserView):
@goocalendar_required
def destroy(self):
self.widget.destroy()
self.children['goocalendar'].destroy()
self.widgets['goocalendar'].destroy()
@goocalendar_required
def get_selected_date(self):
return self.children['goocalendar'].selected_date
return self.widgets['goocalendar'].selected_date
@goocalendar_required
def set_default_date(self, record, selected_date):
self.children['goocalendar'].set_default_date(record, selected_date)
self.widgets['goocalendar'].set_default_date(record, selected_date)
def current_domain(self):
if 'goocalendar' in self.children:
return self.children['goocalendar'].current_domain()
if 'goocalendar' in self.widgets:
return self.widgets['goocalendar'].current_domain()
else:
# No need to load any record as nothing will be shown
return [('id', '=', -1)]
@@ -55,14 +140,14 @@ class ViewCalendar(ParserView):
@goocalendar_required
def display(self):
self.children['goocalendar'].display(self.screen.group)
gtkcal = self.children['toolbar'].gtkcal
self.widgets['goocalendar'].display(self.screen.group)
gtkcal = self.widgets['toolbar'].gtkcal
if gtkcal and not gtkcal.is_drawable():
import goocanvas
# disable gtk.Calendar if it is not drawable anymore
self.children['toolbar'].gtkcal_item.set_property('visibility',
self.widgets['toolbar'].gtkcal_item.set_property('visibility',
goocanvas.ITEM_INVISIBLE)
self.children['toolbar'].current_page.set_active(False)
self.widgets['toolbar'].current_page.set_active(False)
def set_cursor(self, new=False, reset_view=True):
pass
@@ -1,3 +1,2 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from parser import *
@@ -3,27 +3,22 @@
import calendar
import datetime
import goocalendar
import tryton.common as common
from dates_period import DatesPeriod
class Calendar_(goocalendar.Calendar):
'Calendar'
def __init__(self, attrs, model, root_node, fields, screen,
event_store=None):
def __init__(self, attrs, screen, fields, event_store=None):
super(Calendar_, self).__init__(event_store)
self.attrs = attrs
self.model = model
self.root_node = root_node
self.fields = fields
self.screen = screen
self.fields = fields
self.event_store = event_store
self.current_domain_period = self.get_displayed_period()
def set_default_date(self, record, selected_date):
attrs = common.node_attributes(self.root_node)
dtstart = attrs.get('dtstart')
dtstart = self.attrs.get('dtstart')
record[dtstart].set(record, datetime.datetime.combine(selected_date,
datetime.time(0)))
@@ -54,9 +49,8 @@ class Calendar_(goocalendar.Calendar):
def current_domain(self):
first_datetime, last_datetime = \
self.current_domain_period.get_dates(True)
attrs = common.node_attributes(self.root_node)
dtstart = attrs.get('dtstart')
dtend = attrs.get('dtend') or dtstart
dtstart = self.attrs.get('dtstart')
dtend = self.attrs.get('dtend') or dtstart
domain = ['OR',
['AND', (dtstart, '>=', first_datetime),
(dtstart, '<', last_datetime)],
@@ -67,9 +61,8 @@ class Calendar_(goocalendar.Calendar):
return domain
def display(self, group):
attrs = common.node_attributes(self.root_node)
dtstart = attrs.get('dtstart')
dtend = attrs.get('dtend') or dtstart
dtstart = self.attrs.get('dtstart')
dtend = self.attrs.get('dtend') or dtstart
if self.screen.current_record:
record = self.screen.current_record
date = record[dtstart].get(record)
@@ -82,14 +75,6 @@ class Calendar_(goocalendar.Calendar):
event_store = goocalendar.EventStore()
self.event_store = event_store
fields = []
for node in self.root_node.childNodes:
if not node.nodeType == node.ELEMENT_NODE:
continue
if node.localName == 'field':
attrs = common.node_attributes(node)
fields.append(attrs.get('name'))
for record in group:
if not record[dtstart].get(record):
continue
@@ -107,8 +92,8 @@ class Calendar_(goocalendar.Calendar):
all_day = True
# TODO define color code
label = '\n'.join(record[name].get_client(record)
for name in fields).rstrip()
label = '\n'.join(record[attrs['name']].get_client(record)
for attrs in self.fields).rstrip()
event = goocalendar.Event(label, start, end,
bg_color='lightblue', all_day=all_day)
event.record = record
@@ -1,85 +0,0 @@
#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 datetime
import gettext
import gtk
from tryton.gui.window.view_form.view.interface import ParserInterface
import tryton.common as common
try:
from calendar_ import Calendar_
from toolbar import Toolbar
except ImportError:
Calendar_ = None
Toolbar = None
_ = gettext.gettext
class ParserCalendar(ParserInterface):
def on_page_changed(self, goocalendar, day, toolbar):
toolbar.update_displayed_date()
if goocalendar.update_domain():
self.screen.search_filter()
def on_view_changed(self, goocalendar, view, toolbar):
toolbar.update_displayed_date()
if goocalendar.update_domain():
self.screen.search_filter()
def on_event_pressed(self, goocalendar, event):
self.screen.current_record = event.record
def on_event_activated(self, goocalendar, event):
self.screen.switch_view('form')
def on_event_released(self, goocalendar, event, attrs):
dtstart = attrs.get('dtstart')
dtend = attrs.get('dtend') or dtstart
record = event.record
group = record.group
previous_start = record[dtstart].get(record)
new_start = event.start
new_end = event.end
if not isinstance(previous_start, datetime.datetime):
new_start = event.start.date()
new_end = event.end.date() if event.end else None
if previous_start <= new_start:
if new_end:
group.fields[dtend].set_client(record, new_end)
group.fields[dtstart].set_client(record, new_start)
else:
group.fields[dtstart].set_client(record, new_start)
if new_end:
group.fields[dtend].set_client(record, new_end)
goocalendar.select(new_start)
record.save()
def on_day_pressed(self, goocalendar, day):
self.screen.current_record = None
def on_day_activated(self, goocalendar, day):
self.screen.new()
def parse(self, model, root_node, fields):
attrs = common.node_attributes(root_node)
self.title = attrs.get('string', 'Unknown')
vbox = gtk.VBox()
if not Calendar_:
return vbox, {}, [], '', [], None
goocalendar = Calendar_(attrs=attrs, model=model, root_node=root_node,
fields=fields, screen=self.screen)
toolbar = Toolbar(goocalendar)
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)
goocalendar.connect('event-activated', self.on_event_activated)
goocalendar.connect('event-released', self.on_event_released, attrs)
goocalendar.connect('day-pressed', self.on_day_pressed)
goocalendar.connect('day-activated', self.on_day_activated)
scrolledWindow = gtk.ScrolledWindow()
scrolledWindow.add_with_viewport(goocalendar)
vbox.pack_start(toolbar, False, False)
vbox.pack_start(scrolledWindow, True, True)
return vbox, {'root': scrolledWindow, 'toolbar': toolbar,
'goocalendar': goocalendar}, [], '', [], None
+372 -28
View File
@@ -4,45 +4,114 @@ import operator
from functools import reduce
import gtk
import gettext
from interface import ParserView
from collections import defaultdict
from . import View
from tryton.common.focus import (get_invisible_ancestor, find_focused_child,
next_focus_widget)
from tryton.common import Tooltips, node_attributes, ICONFACTORY
from tryton.common.button import Button
from tryton.config import CONFIG
from .form_gtk.calendar import Calendar, DateTime, Time
from .form_gtk.float import Float
from .form_gtk.integer import Integer
from .form_gtk.selection import Selection
from .form_gtk.char import Char, Password
from .form_gtk.float_time import FloatTime
from .form_gtk.checkbox import CheckBox
from .form_gtk.reference import Reference
from .form_gtk.binary import Binary
from .form_gtk.textbox import TextBox
from .form_gtk.one2many import One2Many
from .form_gtk.many2many import Many2Many
from .form_gtk.many2one import Many2One
from .form_gtk.url import Email, URL, CallTo, SIP
from .form_gtk.image import Image as Image2
from .form_gtk.progressbar import ProgressBar
from .form_gtk.one2one import One2One
from .form_gtk.richtextbox import RichTextBox
from .form_gtk.dictionary import DictWidget
from .form_gtk.multiselection import MultiSelection
from .form_gtk.state_widget import (Label, VBox, Image, Frame, ScrolledWindow,
Notebook, Alignment)
_ = gettext.gettext
class ViewForm(ParserView):
class Container(object):
def __init__(self, col=4):
if CONFIG['client.modepda']:
col = 1
self.col = col
self.table = gtk.Table(1, col)
self.table.set_homogeneous(False)
self.table.set_col_spacings(0)
self.table.set_row_spacings(0)
self.table.set_border_width(0)
self.last = (0, 0)
self.tooltips = Tooltips()
self.tooltips.enable()
def __init__(self, screen, widget, children=None, state_widgets=None,
notebooks=None, cursor_widget='', children_field=None):
super(ViewForm, self).__init__(screen, widget, children, state_widgets,
notebooks, cursor_widget, children_field)
def add_row(self):
height, width = self.last
self.table.resize(height + 1, self.col)
self.last = (height + 1, 0)
def add(self, widget, attributes):
height, width = self.last
colspan = attributes.get('colspan', 1)
if colspan > self.col:
colspan = self.col
if width + colspan > self.col:
self.add_row()
height, width = self.last
self.last = height, width + colspan
if not widget:
return
yopt = 0
if attributes.get('yexpand'):
yopt |= gtk.EXPAND
if attributes.get('yfill'):
yopt |= gtk.FILL
xopt = 0
if attributes.get('xexpand', True):
xopt |= gtk.EXPAND
if attributes.get('xfill', True):
xopt |= gtk.FILL
if attributes.get('help'):
self.tooltips.set_tip(widget, attributes['help'])
widget.show_all()
self.table.attach(widget,
width, width + colspan,
height, height + 1,
yoptions=yopt, xoptions=xopt,
ypadding=1, xpadding=2)
class ViewForm(View):
editable = True
def __init__(self, screen, xml):
super(ViewForm, self).__init__(screen, xml)
self.view_type = 'form'
self.editable = True
self.widget_id = 0
self.widgets = defaultdict(list)
self.state_widgets = []
self.notebooks = []
for button in self.get_buttons():
button.connect('clicked', self.button_clicked)
# Force to display the first time it switches on a page
# This avoids glitch in position of widgets
display_done = {}
for notebook in notebooks:
def switch(notebook, page, page_num):
if page_num not in display_done.setdefault(notebook, []):
notebook.grab_focus()
display_done[notebook].append(page_num)
self.display()
notebook.connect('switch-page', switch)
self.widgets = children
for widgets in self.widgets.itervalues():
for widget in widgets:
widget.view = self
container = self.parse(xml)
vbox = gtk.VBox()
vp = gtk.Viewport()
vp.set_shadow_type(gtk.SHADOW_NONE)
vp.add(self.widget)
vp.add(container.table)
scroll = gtk.ScrolledWindow()
scroll.add(vp)
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
@@ -54,6 +123,281 @@ class ViewForm(ParserView):
self.widget = vbox
def parse(self, node, container=None):
if not container:
node_attrs = node_attributes(node)
container = Container(int(node_attrs.get('col', 4)))
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])
parser = getattr(self, '_parse_%s' % node.tagName)
parser(node, container, node_attrs)
return container
def _parse_image(self, node, container, attributes):
ICONFACTORY.register_icon(attributes['name'])
image = Image(attrs=attributes)
image.set_from_stock(attributes['name'], gtk.ICON_SIZE_DIALOG)
self.state_widgets.append(image)
container.add(image, attributes)
def _parse_separator(self, node, container, attributes):
if 'name' in attributes:
field = self.screen.group.fields[attributes['name']]
for attr in ('states', 'string'):
if attr not in attributes and attr in field.attrs:
attributes[attr] = field.attrs[attr]
vbox = VBox(attrs=attributes)
if attributes.get('string'):
label = gtk.Label(attributes['string'])
label.set_alignment(float(attributes.get('xalign', 0.0)),
float(attributes.get('yalign', 0.5)))
vbox.pack_start(label)
vbox.pack_start(gtk.HSeparator())
self.state_widgets.append(vbox)
container.add(vbox, attributes)
def _parse_label(self, node, container, attributes):
if 'name' in attributes:
field = self.screen.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:
if gtk.widget_get_default_direction() == \
gtk.TEXT_DIR_RTL:
attributes['string'] = _(':') + field.attrs['string']
else:
attributes['string'] = field.attrs['string'] + _(':')
if CONFIG['client.modepda']:
attributes['xalign'] = 0.0
label = Label(attributes.get('string', ''), attrs=attributes)
label.set_alignment(float(attributes.get('xalign', 1.0)),
float(attributes.get('yalign', 0.0)))
label.set_angle(int(attributes.get('angle', 0)))
attributes.setdefault('xexpand', 0)
self.state_widgets.append(label)
container.add(label, attributes)
def _parse_newline(self, node, container, attributes):
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):
attributes.setdefault('yexpand', True)
attributes.setdefault('yfill', True)
attributes.setdefault('colspan', 4)
notebook = Notebook(attrs=attributes)
notebook.set_scrollable(True)
positions = {
'top': gtk.POS_TOP,
'left': gtk.POS_LEFT,
'right': gtk.POS_RIGHT,
'bottom': gtk.POS_BOTTOM,
}
notebook.set_tab_pos(positions[CONFIG['client.form_tab']])
notebook.set_border_width(3)
# 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:
# Not yet finish to parse
return
notebook.grab_focus()
self.display()
notebook.disconnect(handler_id)
handler_id = notebook.connect('switch-page', switch)
self.state_widgets.append(notebook)
self.notebooks.append(notebook)
container.add(notebook, attributes)
self.parse(node, notebook)
def _parse_page(self, node, notebook, attributes):
if CONFIG['client.form_tab'] == 'left':
angle = 90
tab_box = gtk.VBox(spacing=3)
image_pos, image_rotate = ('end',
gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE)
elif CONFIG['client.form_tab'] == 'right':
angle = -90
tab_box = gtk.VBox(spacing=3)
image_pos, image_rotate = ('start',
gtk.gdk.PIXBUF_ROTATE_CLOCKWISE)
else:
angle = 0
tab_box = gtk.HBox(spacing=3)
image_pos, image_rotate = ('start',
gtk.gdk.PIXBUF_ROTATE_NONE)
if 'name' in attributes:
field = self.screen.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 '_' not in attributes['string']:
attributes['string'] = '_' + attributes['string']
label = gtk.Label(attributes['string'])
label.set_angle(angle)
label.set_use_underline(True)
tab_box.pack_start(label)
if 'icon' in attributes:
ICONFACTORY.register_icon(attributes['icon'])
pixbuf = tab_box.render_icon(attributes['icon'],
gtk.ICON_SIZE_SMALL_TOOLBAR)
pixbuf = pixbuf.rotate_simple(image_rotate)
icon = gtk.Image()
icon.set_from_pixbuf(pixbuf)
if image_pos == 'end':
tab_box.pack_end(icon)
else:
tab_box.pack_start(icon)
tab_box.show_all()
viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_NONE)
scrolledwindow = ScrolledWindow(attrs=attributes)
scrolledwindow.set_shadow_type(gtk.SHADOW_NONE)
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolledwindow.add(viewport)
scrolledwindow.show_all()
self.state_widgets.append(scrolledwindow)
notebook.append_page(scrolledwindow, tab_box)
container = self.parse(node)
viewport.add(container.table)
def _parse_field(self, node, container, attributes):
name = attributes['name']
field = self.screen.group.fields[name]
if (name not in self.screen.group.fields
or name == self.screen.exclude_field):
container.add(None, attributes)
return
if 'widget' not in attributes:
attributes['widget'] = field.attrs['type']
for attr in ('relation', 'domain', 'selection',
'relation_field', 'string', '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.widget_id += 1
widget.position = self.widget_id
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)
def _parse_group(self, node, container, attributes):
group = self.parse(node)
group.table.set_homogeneous(attributes.get('homogeneous', False))
if 'name' in attributes:
field = self.screen.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]
frame = Frame(attributes.get('string'), attrs=attributes)
frame.add(group.table)
self.state_widgets.append(frame)
container.add(frame, attributes)
def _parse_paned(self, node, container, attributes, Paned):
paned = Paned()
if 'position' in attributes:
paned.set_position(attributes['position'])
container.add(paned, attributes)
self.parse(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)
if not paned.get_child1():
pack = paned.pack1
else:
pack = paned.pack2
pack(container.table, resize=True, shrink=True)
WIDGETS = {
'date': Calendar,
'datetime': DateTime,
'time': Time,
'float': Float,
'numeric': Float,
'integer': Integer,
'biginteger': Integer,
'selection': Selection,
'char': Char,
'password': Password,
'float_time': FloatTime,
'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,
}
@classmethod
def get_widget(cls, name):
return cls.WIDGETS[name]
def get_fields(self):
return self.widgets.keys()
def __getitem__(self, name):
return self.widgets[name][0]
@@ -135,8 +479,8 @@ class ViewForm(ParserView):
if reset_view:
for notebook in self.notebooks:
notebook.set_current_page(0)
if self.cursor_widget in self.widgets:
focus_widget = self.widgets[self.cursor_widget][0]
if self.attributes.get('cursor') in self.widgets:
focus_widget = self.widgets[self.attributes['cursor']][0]
record = self.screen.current_record
position = reduce(lambda x, y: x + len(y), self.widgets, 0)
if record:
@@ -1,3 +1,2 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from parser import *
@@ -6,16 +6,16 @@ import os
import tempfile
from tryton.common import common
from tryton.common import file_selection, Tooltips, file_open, slugify
from interface import WidgetInterface
from .widget import Widget
_ = gettext.gettext
class Binary(WidgetInterface):
class Binary(Widget):
"Binary"
def __init__(self, field_name, model_name, attrs=None):
super(Binary, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Binary, self).__init__(view, attrs)
self.filename = attrs.get('filename')
@@ -3,18 +3,18 @@
import gtk
import gettext
from interface import WidgetInterface
from .widget import Widget
from tryton.common.date_widget import DateEntry
from tryton.translate import date_format
_ = gettext.gettext
class Calendar(WidgetInterface):
class Calendar(Widget):
"Calendar"
def __init__(self, field_name, model_name, attrs=None):
super(Calendar, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Calendar, self).__init__(view, attrs)
self.widget = gtk.HBox()
self.entry = DateEntry('')
@@ -76,9 +76,6 @@ class Calendar(WidgetInterface):
class DateTime(Calendar):
"DateTime"
def __init__(self, field_name, model_name, attrs=None):
super(DateTime, self).__init__(field_name, model_name, attrs=attrs)
def get_format(self, record, field):
return date_format() + ' ' + field.time_format(record)
@@ -86,8 +83,8 @@ class DateTime(Calendar):
class Time(Calendar):
"Time"
def __init__(self, field_name, model_name, attrs=None):
super(Time, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Time, self).__init__(view, attrs)
self.entry.set_icon_from_stock(gtk.ENTRY_ICON_SECONDARY, None)
def get_format(self, record, field):
@@ -4,18 +4,18 @@ import gettext
import gobject
import gtk
from interface import WidgetInterface, TranslateMixin
from .widget import Widget, TranslateMixin
from tryton.common import Tooltips
from tryton.common.entry_position import manage_entry_position
_ = gettext.gettext
class Char(WidgetInterface, TranslateMixin):
class Char(Widget, TranslateMixin):
"Char"
def __init__(self, field_name, model_name, attrs=None):
super(Char, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Char, self).__init__(view, attrs)
self.widget = gtk.HBox()
self.autocomplete = bool(attrs.get('autocomplete'))
@@ -153,8 +153,8 @@ class Char(WidgetInterface, TranslateMixin):
class Password(Char):
def __init__(self, field_name, model_name, attrs=None):
super(Password, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Password, self).__init__(view, attrs)
self.entry.props.visibility = False
self.visibility_checkbox = gtk.CheckButton()
@@ -1,16 +1,16 @@
#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 gtk
from interface import WidgetInterface
from .widget import Widget
import gettext
_ = gettext.gettext
class CheckBox(WidgetInterface):
class CheckBox(Widget):
def __init__(self, field_name, model_name, attrs=None):
super(CheckBox, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(CheckBox, self).__init__(view, attrs)
self.widget = gtk.CheckButton()
self.widget.connect('focus-out-event', lambda x, y: self._focus_out())
self.widget.connect_after('toggled', self.sig_activate)
@@ -9,7 +9,7 @@ import decimal
import gettext
from decimal import Decimal
from interface import WidgetInterface
from .widget import Widget
from tryton.config import CONFIG
from tryton.gui.window.win_search import WinSearch
from tryton.common import RPCExecute, RPCException, timezoned_date, \
@@ -306,10 +306,10 @@ DICT_ENTRIES = {
}
class DictWidget(WidgetInterface):
class DictWidget(Widget):
def __init__(self, field_name, model_name, attrs=None):
super(DictWidget, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(DictWidget, self).__init__(view, attrs)
self.schema_model = attrs['schema_model']
self.keys = {}
self.fields = {}
@@ -2,14 +2,14 @@
#this repository contains the full copyright notices and license terms.
import gtk
import locale
from integer import Integer
from .integer import Integer
class Float(Integer):
"Float"
def __init__(self, field_name, model_name, attrs=None):
super(Float, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Float, self).__init__(view, attrs)
self.entry.connect('key-press-event', self.key_press_event)
def display(self, record, field):
@@ -1,15 +1,15 @@
#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 gtk
from interface import WidgetInterface
from .widget import Widget
import tryton.common as common
import tryton.rpc as rpc
class FloatTime(WidgetInterface):
class FloatTime(Widget):
def __init__(self, field_name, model_name, attrs=None):
super(FloatTime, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(FloatTime, self).__init__(view, attrs)
self.widget = gtk.HBox()
self.entry = gtk.Entry()
@@ -7,7 +7,7 @@ import os
import tempfile
from tryton.common import file_selection, Tooltips, file_open, slugify
from tryton.config import PIXMAPS_DIR
from interface import WidgetInterface
from .widget import Widget
import urllib
_ = gettext.gettext
@@ -15,10 +15,10 @@ _ = gettext.gettext
NOIMAGE = open(os.path.join(PIXMAPS_DIR, 'tryton-noimage.png'), 'rb').read()
class Image(WidgetInterface):
class Image(Widget):
def __init__(self, field_name, model_name, attrs=None):
super(Image, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Image, self).__init__(view, attrs)
self.filename = attrs.get('filename')
self.height = int(attrs.get('img_height', 100))
@@ -1,14 +1,14 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from char import Char
from .char import Char
import locale
class Integer(Char):
"Integer"
def __init__(self, field_name, model_name, attrs=None):
super(Integer, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Integer, self).__init__(view, attrs)
self.entry.set_width_chars(8)
_, _, padding, pack_type = self.widget.query_child_packing(
self.entry)
@@ -3,7 +3,7 @@
import gtk
from tryton.gui.window.view_form.screen import Screen
from interface import WidgetInterface
from .widget import Widget
from tryton.gui.window.win_search import WinSearch
from tryton.gui.window.win_form import WinForm
from tryton.config import CONFIG
@@ -16,10 +16,11 @@ from tryton.common.completion import get_completion, update_completion
_ = gettext.gettext
class Many2Many(WidgetInterface):
class Many2Many(Widget):
expand = True
def __init__(self, field_name, model_name, attrs=None):
super(Many2Many, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Many2Many, self).__init__(view, attrs)
self.widget = gtk.VBox(homogeneous=False, spacing=5)
self._readonly = True
@@ -97,8 +98,8 @@ class Many2Many(WidgetInterface):
self.wid_text.connect('key_press_event', self.on_keypress)
def _color_widget(self):
if hasattr(self.screen.current_view, 'widget_tree'):
return self.screen.current_view.widget_tree
if hasattr(self.screen.current_view, 'treeview'):
return self.screen.current_view.treeview
return super(Many2Many, self)._color_widget()
def grab_focus(self):
@@ -3,7 +3,8 @@
import gtk
import gobject
import gettext
from interface import WidgetInterface
from .widget import Widget
import tryton.common as common
from tryton.gui.window.view_form.screen import Screen
from tryton.gui.window.win_search import WinSearch
@@ -17,10 +18,10 @@ from tryton.common.entry_position import manage_entry_position
_ = gettext.gettext
class Many2One(WidgetInterface):
class Many2One(Widget):
def __init__(self, field_name, model_name, attrs=None):
super(Many2One, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Many2One, self).__init__(view, attrs)
self.widget = gtk.HBox(spacing=0)
self.widget.set_property('sensitive', True)
@@ -3,16 +3,16 @@
import gtk
import gobject
from .interface import WidgetInterface
from .widget import Widget
from tryton.common.selection import SelectionMixin
from tryton.common.treeviewcontrol import TreeViewControl
class MultiSelection(WidgetInterface, SelectionMixin):
class MultiSelection(Widget, SelectionMixin):
expand = True
def __init__(self, field_name, model_name, attrs=None):
super(MultiSelection, self).__init__(field_name, model_name,
attrs=attrs)
def __init__(self, view, attrs):
super(MultiSelection, self).__init__(view, attrs)
self.widget = gtk.ScrolledWindow()
self.widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
@@ -3,7 +3,7 @@
import gtk
import gettext
from interface import WidgetInterface
from .widget import Widget
from tryton.gui.window.view_form.screen import Screen
from tryton.gui.window.win_search import WinSearch
from tryton.gui.window.win_form import WinForm
@@ -16,10 +16,11 @@ from tryton.common.completion import get_completion, update_completion
_ = gettext.gettext
class One2Many(WidgetInterface):
class One2Many(Widget):
expand = True
def __init__(self, field_name, model_name, attrs=None):
super(One2Many, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(One2Many, self).__init__(view, attrs)
self.widget = gtk.VBox(homogeneous=False, spacing=2)
self._readonly = True
@@ -192,8 +193,8 @@ class One2Many(WidgetInterface):
but_switch.props.sensitive = self.screen.number_of_views > 1
def _color_widget(self):
if hasattr(self.screen.current_view, 'widget_tree'):
return self.screen.current_view.widget_tree
if hasattr(self.screen.current_view, 'treeview'):
return self.screen.current_view.treeview
return super(One2Many, self)._color_widget()
def grab_focus(self):
@@ -324,7 +325,7 @@ class One2Many(WidgetInterface):
sequence = None
for view in self.screen.views:
if view.view_type == 'tree':
sequence = view.widget_tree.sequence
sequence = view.attributes.get('sequence')
if sequence:
break
@@ -402,7 +403,7 @@ class One2Many(WidgetInterface):
sequence = None
if self.screen.current_view.view_type == 'tree':
sequence = self.screen.current_view.widget_tree.sequence
sequence = self.screen.current_view.attributes.get('sequence')
def callback(result):
self.focus_out = True
@@ -1,6 +1,6 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from many2one import Many2One
from .many2one import Many2One
class One2One(Many2One):
@@ -1,572 +0,0 @@
#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 gtk
import gettext
import logging
from tryton.gui.window.view_form.view.interface import ParserInterface
import tryton.common as common
from tryton.config import CONFIG
from tryton.common.button import Button
_ = gettext.gettext
class StateMixin(object):
def __init__(self, *args, **kwargs):
self.attrs = kwargs.pop('attrs')
super(StateMixin, self).__init__(*args, **kwargs)
def state_set(self, record):
if record:
state_changes = record.expr_eval(self.attrs.get('states', {}))
else:
state_changes = {}
if state_changes.get('invisible', self.attrs.get('invisible')):
self.hide()
else:
self.show()
class Label(StateMixin, gtk.Label):
def state_set(self, record):
super(Label, self).state_set(record)
if not self.attrs.get('string', True) and 'name' in self.attrs:
if record:
field = record.group.fields[self.attrs['name']]
text = field.get_client(record) or ''
else:
text = ''
self.set_text(text)
class VBox(StateMixin, gtk.VBox):
pass
class Image(StateMixin, gtk.Image):
pass
class Frame(StateMixin, gtk.Frame):
def __init__(self, label=None, attrs=None, widgets=None):
if not label: # label must be None to have no label widget
label = None
super(Frame, self).__init__(label=label, attrs=attrs)
self.widgets = widgets or {}
if not label:
self.set_shadow_type(gtk.SHADOW_NONE)
self.set_border_width(0)
class ScrolledWindow(StateMixin, gtk.ScrolledWindow):
pass
class Notebook(StateMixin, gtk.Notebook):
def state_set(self, record):
super(Notebook, self).state_set(record)
if record:
state_changes = record.expr_eval(self.attrs.get('states', {}))
else:
state_changes = {}
if state_changes.get('readonly', self.attrs.get('readonly')):
for widgets in self.widgets.itervalues():
for widget in widgets:
widget._readonly_set(True)
widget.color_set('readonly')
class Alignment(gtk.Alignment):
def __init__(self, widget, attrs):
super(Alignment, self).__init__(
float(attrs.get('xalign', 0.0)),
float(attrs.get('yalign', 0.5)),
float(attrs.get('xexpand', 1.0)),
float(attrs.get('yexpand', 1.0)))
self.add(widget)
widget.connect('show', lambda *a: self.show())
widget.connect('hide', lambda *a: self.hide())
class _container(object):
def __init__(self, tooltips):
self.cont = []
self.col = []
self.tooltips = tooltips
def new(self, col=4):
table = gtk.Table(1, col)
table.set_homogeneous(False)
table.set_col_spacings(0)
table.set_row_spacings(0)
table.set_border_width(0)
self.cont.append((table, 0, 0))
self.col.append(col)
def get(self):
return self.cont[-1][0]
def pop(self):
table = self.cont.pop()[0]
self.col.pop()
return table
def newline(self):
(table, width, height) = self.cont[-1]
if width > 0:
self.cont[-1] = (table, 0, height + 1)
table.resize(height + 1, self.col[-1])
def wid_add(self, widget, name='', yexpand=False, ypadding=2, rowspan=1,
colspan=1, fname=None, help_tip=False,
yfill=False, xexpand=True, xfill=True, xpadding=3):
(table, width, height) = self.cont[-1]
if colspan > self.col[-1]:
colspan = self.col[-1]
if colspan + width > self.col[-1]:
self.newline()
(table, width, height) = self.cont[-1]
yopt = False
if yexpand:
yopt = yopt | gtk.EXPAND
if yfill:
yopt = yopt | gtk.FILL
xopt = False
if xexpand:
xopt = xopt | gtk.EXPAND
if xfill:
xopt = xopt | gtk.FILL
if help_tip:
self.tooltips.set_tip(widget, help_tip)
self.tooltips.enable()
widget.show_all()
table.attach(widget, width, width + colspan,
height, height + rowspan,
yoptions=yopt, ypadding=ypadding,
xoptions=xopt, xpadding=xpadding)
self.cont[-1] = (table, width + colspan, height)
wid_list = table.get_children()
wid_list.reverse()
table.set_focus_chain(wid_list)
def empty_add(self, colspan=1):
(table, width, height) = self.cont[-1]
if colspan > self.col[-1]:
colspan = self.col[-1]
if colspan + width > self.col[-1]:
self.newline()
(table, width, height) = self.cont[-1]
self.cont[-1] = (table, width + colspan, height)
class ParserForm(ParserInterface):
def __init__(self, parent=None, attrs=None, screen=None,
children_field=None):
super(ParserForm, self).__init__(parent=parent, attrs=attrs,
screen=screen, children_field=children_field)
self.widget_id = 0
def parse(self, model_name, root_node, fields, notebook=None, paned=None,
tooltips=None):
dict_widget = {}
state_widgets = []
notebook_list = []
attrs = common.node_attributes(root_node)
on_write = attrs.get('on_write', '')
if not tooltips:
tooltips = common.Tooltips()
container = _container(tooltips)
if CONFIG['client.modepda']:
container.new(col=1)
else:
container.new(col=int(attrs.get('col', 4)))
cursor_widget = attrs.get('cursor')
if not self.title:
self.title = attrs.get('string', 'Unknown')
for node in root_node.childNodes:
if not node.nodeType == node.ELEMENT_NODE:
continue
attrs = common.node_attributes(node)
if not cursor_widget:
if (attrs.get('name') and fields.get(attrs['name'])
and not int(attrs.get('invisible', 0))
and not int(attrs.get('readonly', 0))
and attrs['name'] != self.screen.exclude_field):
cursor_widget = attrs.get('name')
yexpand = int(attrs.get('yexpand', 0))
yfill = int(attrs.get('yfill', 0))
xexpand = int(attrs.get('xexpand', 1))
xfill = int(attrs.get('xfill', 1))
colspan = int(attrs.get('colspan', 1))
if node.localName == 'image':
common.ICONFACTORY.register_icon(attrs['name'])
icon = Image(attrs=attrs)
state_widgets.append(icon)
icon.set_from_stock(attrs['name'], gtk.ICON_SIZE_DIALOG)
container.wid_add(icon,
help_tip=attrs.get('help', False),
colspan=colspan,
yexpand=yexpand, yfill=yfill, ypadding=10,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'separator':
text = attrs.get('string', '')
if 'name' in attrs:
for attr_name in ('states', 'invisible'):
if attr_name not in attrs and attrs['name'] in fields:
if attr_name in fields[attrs['name']].attrs:
attrs[attr_name] = fields[attrs['name']
].attrs[attr_name]
if not text:
text = fields[attrs['name']].attrs['string']
vbox = VBox(attrs=attrs)
state_widgets.append(vbox)
if text:
label = gtk.Label(text)
label.set_alignment(float(attrs.get('xalign', 0.0)), 0.5)
vbox.pack_start(label)
vbox.pack_start(gtk.HSeparator())
container.wid_add(vbox,
help_tip=attrs.get('help', False),
colspan=colspan,
yexpand=yexpand, yfill=yfill, ypadding=10,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'label':
text = attrs.get('string', '')
if 'name' in attrs and attrs['name'] in fields:
if attrs['name'] == self.screen.exclude_field:
container.empty_add(int(attrs.get('colspan', 1)))
continue
for attr_name in ('states', 'invisible'):
if attr_name not in attrs \
and attr_name in fields[attrs['name']].attrs:
attrs[attr_name] = fields[attrs['name']
].attrs[attr_name]
if 'string' not in attrs:
if gtk.widget_get_default_direction() == \
gtk.TEXT_DIR_RTL:
text = _(':') + \
fields[attrs['name']].attrs['string']
else:
text = fields[attrs['name']].attrs['string'] + \
_(':')
if 'xalign' not in attrs:
attrs['xalign'] = 1.0
elif not text:
for node in node.childNodes:
if node.nodeType == node.TEXT_NODE:
text += node.data
else:
text += node.toxml()
label = Label(text, attrs=attrs)
state_widgets.append(label)
if CONFIG['client.modepda']:
attrs['xalign'] = 0.0
label.set_alignment(float(attrs.get('xalign', 1.0)),
float(attrs.get('yalign', 0.0)))
label.set_angle(int(attrs.get('angle', 0)))
xexpand = bool(attrs.get('xexpand', 0))
container.wid_add(label,
help_tip=attrs.get('help', False),
colspan=colspan,
yexpand=yexpand, yfill=yfill,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'newline':
container.newline()
elif node.localName == 'button':
button = Button(attrs)
state_widgets.append(button)
container.wid_add(button,
help_tip=attrs.get('help', False),
colspan=colspan,
yexpand=yexpand, yfill=yfill,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'notebook':
notebook = Notebook(attrs=attrs)
state_widgets.append(notebook)
notebook.set_scrollable(True)
notebook_list.append(notebook)
if CONFIG['client.form_tab'] == 'top':
pos = gtk.POS_TOP
elif CONFIG['client.form_tab'] == 'left':
pos = gtk.POS_LEFT
elif CONFIG['client.form_tab'] == 'right':
pos = gtk.POS_RIGHT
elif CONFIG['client.form_tab'] == 'bottom':
pos = gtk.POS_BOTTOM
notebook.set_tab_pos(pos)
notebook.set_border_width(3)
colspan = int(attrs.get('colspan', 4))
yexpand = bool(attrs.get('yexpand', 1))
yfill = bool(attrs.get('yfill', 1))
container.wid_add(notebook,
colspan=colspan,
yexpand=yexpand, yfill=yfill,
xexpand=xexpand, xfill=xfill)
(widget, widgets, state_widgets2, spam, notebook_list2,
cursor_widget2) = self.parse(model_name, node, fields,
notebook, tooltips=tooltips)
if not cursor_widget:
cursor_widget = cursor_widget2
notebook_list.extend(notebook_list2)
state_widgets += state_widgets2
for widget_name, widgets in widgets.items():
dict_widget.setdefault(widget_name, [])
dict_widget[widget_name].extend(widgets)
elif node.localName == 'page':
if CONFIG['client.form_tab'] == 'left':
angle = 90
tab_box = gtk.VBox(spacing=3)
image_pos, image_rotate = ('end',
gtk.gdk.PIXBUF_ROTATE_COUNTERCLOCKWISE)
elif CONFIG['client.form_tab'] == 'right':
angle = -90
tab_box = gtk.VBox(spacing=3)
image_pos, image_rotate = ('start',
gtk.gdk.PIXBUF_ROTATE_CLOCKWISE)
else:
angle = 0
tab_box = gtk.HBox(spacing=3)
image_pos, image_rotate = ('start',
gtk.gdk.PIXBUF_ROTATE_NONE)
text = attrs.get('string', '')
if 'name' in attrs and attrs['name'] in fields:
if attrs['name'] == self.screen.exclude_field:
continue
for attr_name in ('states', 'invisible'):
if attr_name in fields[attrs['name']].attrs:
attrs[attr_name] = \
fields[attrs['name']].attrs[attr_name]
if not text:
text = fields[attrs['name']].attrs['string']
if not text:
text = _('No String Attr.')
if '_' not in text:
text = '_' + text
tab_label = gtk.Label(text)
tab_label.set_angle(angle)
tab_label.set_use_underline(True)
tab_box.pack_start(tab_label)
if 'icon' in attrs:
common.ICONFACTORY.register_icon(attrs['icon'])
pixbuf = tab_box.render_icon(attrs['icon'],
gtk.ICON_SIZE_SMALL_TOOLBAR)
pixbuf = pixbuf.rotate_simple(image_rotate)
icon = gtk.Image()
icon.set_from_pixbuf(pixbuf)
if image_pos == 'end':
tab_box.pack_end(icon)
else:
tab_box.pack_start(icon)
tab_box.show_all()
(widget, widgets, state_widgets2, spam, notebook_list2,
cursor_widget2) = self.parse(model_name, node, fields,
notebook, tooltips=tooltips)
if not cursor_widget:
cursor_widget = cursor_widget2
notebook_list.extend(notebook_list2)
state_widgets += state_widgets2
for widget_name, widgets in widgets.iteritems():
dict_widget.setdefault(widget_name, [])
dict_widget[widget_name].extend(widgets)
viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_NONE)
viewport.add(widget)
viewport.show()
scrolledwindow = ScrolledWindow(attrs=attrs)
scrolledwindow.set_shadow_type(gtk.SHADOW_NONE)
scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC,
gtk.POLICY_AUTOMATIC)
scrolledwindow.add(viewport)
state_widgets.append(scrolledwindow)
notebook.append_page(scrolledwindow, tab_box)
elif node.localName == 'field':
name = str(attrs['name'])
if name == self.screen.exclude_field:
container.empty_add(int(attrs.get('colspan', 1)))
continue
if name not in fields:
container.empty_add(int(attrs.get('colspan', 1)))
log = logging.getLogger(__name__)
log.error('Unknown field "%s"' % str(name))
continue
ftype = attrs.get('widget', fields[name].attrs['type'])
if not ftype in WIDGETS_TYPE:
container.empty_add(int(attrs.get('colspan', 1)))
continue
for attr_name 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_name in fields[name].attrs and \
not attr_name in attrs:
attrs[attr_name] = fields[name].attrs[attr_name]
widget_act = WIDGETS_TYPE[ftype][0](name, model_name, attrs)
self.widget_id += 1
widget_act.position = self.widget_id
dict_widget.setdefault(name, [])
dict_widget[name].append(widget_act)
yexpand = bool(attrs.get('yexpand', WIDGETS_TYPE[ftype][2]))
yfill = bool(attrs.get('yfill', WIDGETS_TYPE[ftype][3]))
hlp = fields[name].attrs.get('help', attrs.get('help', False))
if attrs.get('height', False) or attrs.get('width', False):
widget_act.widget.set_size_request(
int(attrs.get('width', -1)),
int(attrs.get('height', -1)))
container.wid_add(Alignment(widget_act.widget, attrs),
fields[name].attrs['string'], fname=name,
help_tip=hlp,
colspan=colspan,
yexpand=yexpand, yfill=yfill,
xexpand=xexpand, xfill=xfill)
elif node.localName == 'group':
(widget, widgets, state_widgets2, spam, notebook_list2,
cursor_widget2) = self.parse(model_name, node, fields,
tooltips=tooltips)
if not cursor_widget:
cursor_widget = cursor_widget2
notebook_list.extend(notebook_list2)
for widget_name, lwidgets in widgets.iteritems():
dict_widget.setdefault(widget_name, [])
dict_widget[widget_name].extend(lwidgets)
state_widgets += state_widgets2
text = ''
if 'name' in attrs and attrs['name'] in fields:
if attrs['name'] == self.screen.exclude_field:
container.empty_add(int(attrs.get('colspan', 1)))
continue
for attr_name in ('states', 'invisible'):
if attr_name in fields[attrs['name']].attrs:
attrs[attr_name] = fields[attrs['name']
].attrs[attr_name]
text = fields[attrs['name']].attrs['string']
if attrs.get('string'):
text = attrs['string']
widget.set_homogeneous(bool(int(attrs.get('homogeneous', 0))))
frame = Frame(text, attrs=attrs, widgets=widgets)
frame.add(widget)
state_widgets.append(frame)
container.wid_add(frame,
colspan=colspan,
yexpand=yexpand, yfill=yfill, ypadding=0,
xexpand=xexpand, xfill=xfill, xpadding=0)
elif node.localName == 'hpaned':
hpaned = gtk.HPaned()
container.wid_add(hpaned, colspan=int(attrs.get('colspan', 4)),
yexpand=True, yfill=True)
(widget, widgets, state_widgets2, spam, notebook_list2,
cursor_widget2) = self.parse(model_name, node, fields,
paned=hpaned, tooltips=tooltips)
if not cursor_widget:
cursor_widget = cursor_widget2
notebook_list.extend(notebook_list2)
state_widgets += state_widgets2
for widget_name, widgets in widgets.iteritems():
dict_widget.setdefault(widget_name, [])
dict_widget[widget_name].extend(widgets)
if 'position' in attrs:
hpaned.set_position(int(attrs['position']))
elif node.localName == 'vpaned':
vpaned = gtk.VPaned()
container.wid_add(vpaned, colspan=int(attrs.get('colspan', 4)),
yexpand=True, yfill=True)
(widget, widgets, state_widgets2, spam, notebook_list2,
cursor_widget2) = self.parse(model_name, node, fields,
paned=vpaned, tooltips=tooltips)
if not cursor_widget:
cursor_widget = cursor_widget2
notebook_list.extend(notebook_list2)
state_widgets += state_widgets2
for widget_name, widgets in widgets.iteritems():
dict_widget.setdefault(widget_name, [])
dict_widget[widget_name].extend(widgets)
if 'position' in attrs:
vpaned.set_position(int(attrs['position']))
elif node.localName == 'child':
(widget, widgets, state_widgets2, spam, notebook_list2,
cursor_widget2) = self.parse(model_name, node, fields,
paned=paned, tooltips=tooltips)
if not cursor_widget:
cursor_widget = cursor_widget2
notebook_list.extend(notebook_list2)
state_widgets += state_widgets2
for widget_name, widgets in widgets.iteritems():
dict_widget.setdefault(widget_name, [])
dict_widget[widget_name].extend(widgets)
if not paned.get_child1():
paned.pack1(widget, resize=True, shrink=True)
elif not paned.get_child2():
paned.pack2(widget, resize=True, shrink=True)
return container.pop(), dict_widget, state_widgets, on_write, \
notebook_list, cursor_widget
from calendar import Calendar, DateTime, Time
from float import Float
from integer import Integer
from selection import Selection
from char import Char, Password
from float_time import FloatTime
from checkbox import CheckBox
from reference import Reference
from binary import Binary
from textbox import TextBox
from one2many import One2Many
from many2many import Many2Many
from many2one import Many2One
from url import Email, URL, CallTo, SIP
from image import Image as Image2
from progressbar import ProgressBar
from one2one import One2One
from richtextbox import RichTextBox
from dictionary import DictWidget
from multiselection import MultiSelection
WIDGETS_TYPE = {
'date': (Calendar, 1, False, False),
'datetime': (DateTime, 1, False, False),
'time': (Time, 1, False, False),
'float': (Float, 1, False, False),
'numeric': (Float, 1, False, False),
'integer': (Integer, 1, False, False),
'biginteger': (Integer, 1, False, False),
'selection': (Selection, 1, False, False),
'char': (Char, 1, False, False),
'password': (Password, 1, False, False),
'float_time': (FloatTime, 1, False, False),
'boolean': (CheckBox, 1, False, False),
'reference': (Reference, 1, False, False),
'binary': (Binary, 1, False, False),
'text': (TextBox, 1, True, True),
'one2many': (One2Many, 1, True, True),
'many2many': (Many2Many, 1, True, True),
'many2one': (Many2One, 1, False, False),
'email': (Email, 1, False, False),
'url': (URL, 1, False, False),
'callto': (CallTo, 1, False, False),
'sip': (SIP, 1, False, False),
'image': (Image2, 1, False, False),
'progressbar': (ProgressBar, 1, False, False),
'one2one': (One2One, 1, False, False),
'richtext': (RichTextBox, 1, True, True),
'dict': (DictWidget, 1, False, False),
'multiselection': (MultiSelection, 1, True, True),
}
@@ -1,11 +1,11 @@
#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 gtk
from interface import WidgetInterface
from .widget import Widget
import locale
class ProgressBar(WidgetInterface):
class ProgressBar(Widget):
'Progress Bar'
orientations = {
'left_to_right': gtk.PROGRESS_LEFT_TO_RIGHT,
@@ -14,8 +14,8 @@ class ProgressBar(WidgetInterface):
'top_to_bottom': gtk.PROGRESS_TOP_TO_BOTTOM,
}
def __init__(self, field_name, model_name, attrs=None):
super(ProgressBar, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(ProgressBar, self).__init__(view, attrs)
self.widget = gtk.ProgressBar()
orientation = self.orientations.get(attrs.get('orientation',
'left_to_right'), gtk.PROGRESS_LEFT_TO_RIGHT)
@@ -3,7 +3,7 @@
import gtk
import gettext
from many2one import Many2One
from .many2one import Many2One
from tryton.common.selection import SelectionMixin, PopdownMixin
_ = gettext.gettext
@@ -11,8 +11,8 @@ _ = gettext.gettext
class Reference(Many2One, SelectionMixin, PopdownMixin):
def __init__(self, field_name, model_name, attrs=None):
super(Reference, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Reference, self).__init__(view, attrs)
self.widget_combo = gtk.ComboBoxEntry()
child = self.widget_combo.get_child()
@@ -5,7 +5,7 @@ import gtk
import pango
import re
from gettext import gettext as _
from textbox import TextBox
from .textbox import TextBox
from tryton.common import get_toplevel_window
@@ -64,8 +64,8 @@ def formalize_text_markup(text):
class RichTextBox(TextBox):
'''Implements a rich text editor as widget to Tryton Client'''
def __init__(self, field_name, model_name, attrs=None):
super(RichTextBox, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(RichTextBox, self).__init__(view, attrs)
self.table_tag = gtk.TextTagTable()
self.text_buffer = gtk.TextBuffer(self.table_tag)
self.textview.set_buffer(self.text_buffer)
@@ -3,15 +3,15 @@
import gtk
import gobject
from interface import WidgetInterface
from .widget import Widget
from tryton.common.selection import SelectionMixin, selection_shortcuts, \
PopdownMixin
class Selection(WidgetInterface, SelectionMixin, PopdownMixin):
class Selection(Widget, SelectionMixin, PopdownMixin):
def __init__(self, field_name, model_name, attrs=None):
super(Selection, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(Selection, self).__init__(view, attrs)
self.widget = gtk.HBox(spacing=3)
self.entry = gtk.ComboBoxEntry()
@@ -0,0 +1,84 @@
#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 gtk
class StateMixin(object):
def __init__(self, *args, **kwargs):
self.attrs = kwargs.pop('attrs')
super(StateMixin, self).__init__(*args, **kwargs)
def state_set(self, record):
if record:
state_changes = record.expr_eval(self.attrs.get('states', {}))
else:
state_changes = {}
if state_changes.get('invisible', self.attrs.get('invisible')):
self.hide()
else:
self.show()
class Label(StateMixin, gtk.Label):
def state_set(self, record):
super(Label, self).state_set(record)
if not self.attrs.get('string', True) and 'name' in self.attrs:
if record:
field = record.group.fields[self.attrs['name']]
text = field.get_client(record) or ''
else:
text = ''
self.set_text(text)
class VBox(StateMixin, gtk.VBox):
pass
class Image(StateMixin, gtk.Image):
pass
class Frame(StateMixin, gtk.Frame):
def __init__(self, label=None, attrs=None):
if not label: # label must be None to have no label widget
label = None
super(Frame, self).__init__(label=label, attrs=attrs)
if not label:
self.set_shadow_type(gtk.SHADOW_NONE)
self.set_border_width(0)
class ScrolledWindow(StateMixin, gtk.ScrolledWindow):
pass
class Notebook(StateMixin, gtk.Notebook):
def state_set(self, record):
super(Notebook, self).state_set(record)
if record:
state_changes = record.expr_eval(self.attrs.get('states', {}))
else:
state_changes = {}
if state_changes.get('readonly', self.attrs.get('readonly')):
for widgets in self.widgets.itervalues():
for widget in widgets:
widget._readonly_set(True)
widget.color_set('readonly')
class Alignment(gtk.Alignment):
def __init__(self, widget, attrs):
super(Alignment, self).__init__(
float(attrs.get('xalign', 0.0)),
float(attrs.get('yalign', 0.5)),
float(attrs.get('xexpand', 1.0)),
float(attrs.get('yexpand', 1.0)))
self.add(widget)
widget.connect('show', lambda *a: self.show())
widget.connect('hide', lambda *a: self.hide())
@@ -1,7 +1,7 @@
#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 gtk
from interface import WidgetInterface, TranslateMixin
from .widget import Widget, TranslateMixin
from tryton.config import CONFIG
try:
@@ -10,10 +10,11 @@ except ImportError:
gtkspell = None
class TextBox(WidgetInterface, TranslateMixin):
class TextBox(Widget, TranslateMixin):
expand = True
def __init__(self, field_name, model_name, attrs=None):
super(TextBox, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(TextBox, self).__init__(view, attrs)
self.widget = gtk.VBox()
self.scrolledwindow = gtk.ScrolledWindow()
@@ -1,7 +1,7 @@
#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 gtk
from char import Char
from .char import Char
import webbrowser
import tryton.common as common
@@ -9,8 +9,8 @@ import tryton.common as common
class URL(Char):
"url"
def __init__(self, field_name, model_name, attrs=None):
super(URL, self).__init__(field_name, model_name, attrs=attrs)
def __init__(self, view, attrs):
super(URL, self).__init__(view, attrs)
self.tooltips = common.Tooltips()
self.button = gtk.Button()
@@ -13,35 +13,36 @@ from tryton.common import RPCExecute, RPCException
_ = gettext.gettext
class WidgetInterface(object):
class Widget(object):
expand = False
def __init__(self, field_name, model_name, attrs=None):
super(WidgetInterface, self).__init__()
self.field_name = field_name
self.model_name = model_name
self.view = None # Filled by ViewForm
self.attrs = attrs or {}
for attr_name in ('readonly', 'invisible'):
if attr_name in self.attrs:
self.attrs[attr_name] = bool(int(self.attrs[attr_name]))
def __init__(self, view, attrs):
super(Widget, self).__init__()
self.view = view
self.attrs = attrs
self.widget = None
self.position = 0
self.colors = {}
self.visible = True
self.color_name = None
def __get_record(self):
if self.view and self.view.screen:
return self.view.screen.current_record
@property
def field_name(self):
return self.attrs['name']
record = property(__get_record)
@property
def model_name(self):
return self.view.screen.model_name
def __get_field(self):
@property
def record(self):
return self.view.screen.current_record
@property
def field(self):
if self.record:
return self.record.group.fields[self.field_name]
field = property(__get_field)
def destroy(self):
pass
+161 -13
View File
@@ -1,26 +1,80 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from interface import ParserView
import sys
import os
import gtk
import gettext
from . import View
from tryton.common import node_attributes, get_toplevel_window, message
from tryton.config import TRYTON_ICON, CONFIG
from .graph_gtk.bar import VerticalBar, HorizontalBar
from .graph_gtk.line import Line
from .graph_gtk.pie import Pie
_ = gettext.gettext
class ViewGraph(ParserView):
class ViewGraph(View):
def __init__(self, screen, widget, children=None, state_widgets=None,
notebooks=None, cursor_widget=None, children_field=None):
super(ViewGraph, self).__init__(screen, widget, children,
state_widgets, notebooks, cursor_widget, children_field)
def __init__(self, screen, xml):
super(ViewGraph, self).__init__(screen, xml)
self.view_type = 'graph'
self.widgets = children
self.editable = False
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
WIDGETS = {
'vbar': VerticalBar,
'hbar': HorizontalBar,
'line': Line,
'pie': Pie,
}
@classmethod
def get_widget(cls, name):
return cls.WIDGETS[name]
def __getitem__(self, name):
return None
def destroy(self):
self.widget.destroy()
for widget in self.widgets.keys():
self.widgets[widget].destroy()
del self.widgets[widget]
self.widgets['root'].destroy()
self.widgets.clear()
def set_value(self):
pass
@@ -29,8 +83,7 @@ class ViewGraph(ParserView):
pass
def display(self):
for widget in self.widgets.itervalues():
widget.display(self.screen.group)
self.widgets['root'].display(self.screen.group)
return True
def set_cursor(self, new=False, reset_view=True):
@@ -38,3 +91,98 @@ class ViewGraph(ParserView):
def get_fields(self):
return []
def get_buttons(self):
return []
def save(self, widget, graph):
parent = get_toplevel_window()
dia = gtk.Dialog(_('Save As'), parent,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
gtk.STOCK_OK, gtk.RESPONSE_OK))
dia.set_icon(TRYTON_ICON)
dia.set_has_separator(True)
dia.set_default_response(gtk.RESPONSE_OK)
dia.vbox.set_spacing(5)
dia.vbox.set_homogeneous(False)
title = gtk.Label('<b>' + _('Image Size') + '</b>')
title.set_alignment(0.0, 0.5)
title.set_use_markup(True)
dia.vbox.pack_start(title)
table = gtk.Table(2, 2)
table.set_col_spacings(3)
table.set_row_spacings(3)
table.set_border_width(1)
table.attach(gtk.Label(_('Width:')), 0, 1, 0, 1, yoptions=False,
xoptions=gtk.FILL)
spinwidth = gtk.SpinButton(gtk.Adjustment(400.0,
0.0, sys.maxint, 1.0, 10.0))
spinwidth.set_numeric(True)
spinwidth.set_activates_default(True)
table.attach(spinwidth, 1, 2, 0, 1, yoptions=False, xoptions=gtk.FILL)
table.attach(gtk.Label(_('Height:')), 0, 1, 1, 2, yoptions=False,
xoptions=gtk.FILL)
spinheight = gtk.SpinButton(gtk.Adjustment(200.0,
0.0, sys.maxint, 1.0, 10.0))
spinheight.set_numeric(True)
spinheight.set_activates_default(True)
table.attach(spinheight, 1, 2, 1, 2, yoptions=False, xoptions=gtk.FILL)
dia.vbox.pack_start(table)
filechooser = gtk.FileChooserWidget(gtk.FILE_CHOOSER_ACTION_SAVE, None)
filechooser.set_current_folder(CONFIG['client.default_path'])
filter = gtk.FileFilter()
filter.set_name(_('PNG image (*.png)'))
filter.add_mime_type('image/png')
filter.add_pattern('*.png')
filechooser.add_filter(filter)
dia.vbox.pack_start(filechooser)
dia.show_all()
while True:
response = dia.run()
width = spinwidth.get_value_as_int()
height = spinheight.get_value_as_int()
filename = filechooser.get_filename()
if filename:
filename = filename.decode('utf-8')
try:
CONFIG['client.default_path'] = \
os.path.dirname(filename)
CONFIG.save()
except IOError:
pass
if response == gtk.RESPONSE_OK:
if width and height and filename:
if not filename.endswith('.png'):
filename = filename + '.png'
try:
graph.export_png(filename, width, height)
break
except MemoryError:
message(_('Image size too large!'), dia,
gtk.MESSAGE_ERROR)
else:
break
parent.present()
dia.destroy()
def button_press(self, widget, event):
if event.button == 3:
menu = gtk.Menu()
item = gtk.ImageMenuItem(_('Save As...'))
img = gtk.Image()
img.set_from_stock('tryton-save-as', gtk.ICON_SIZE_MENU)
item.set_image(img)
item.connect('activate', self.save)
item.show()
menu.append(item)
menu.popup(None, None, None, event.button, event.time)
return True
elif event.button == 1:
self.widgets['root'].action()
@@ -71,8 +71,9 @@ class Graph(gtk.DrawingArea):
__gsignals__ = {"expose-event": "override"}
def __init__(self, xfield, yfields, attrs, model):
def __init__(self, view, xfield, yfields):
super(Graph, self).__init__()
self.view = view
self.xfield = xfield
self.yfields = yfields
self.datas = {}
@@ -84,8 +85,14 @@ class Graph(gtk.DrawingArea):
self.connect('motion-notify-event', self.motion)
self.connect('leave-notify-event', self.leave)
self.popup = Popup(self)
self.attrs = attrs
self.model = model
@property
def attrs(self):
return self.view.attributes
@property
def model(self):
return self.view.screen.model_name
def destroy(self):
self.popup.destroy()
@@ -1,155 +0,0 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from tryton.gui.window.view_form.view.interface import ParserInterface
import tryton.common as common
import gtk
from bar import VerticalBar, HorizontalBar
from line import Line
from pie import Pie
from tryton.config import TRYTON_ICON, CONFIG
import sys
import os
import gettext
_ = gettext.gettext
GRAPH_TYPE = {
'vbar': VerticalBar,
'hbar': HorizontalBar,
'line': Line,
'pie': Pie,
}
def save(widget, graph):
parent = common.get_toplevel_window()
dia = gtk.Dialog(_('Save As'), parent,
gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OK, gtk.RESPONSE_OK))
dia.set_icon(TRYTON_ICON)
dia.set_has_separator(True)
dia.set_default_response(gtk.RESPONSE_OK)
dia.vbox.set_spacing(5)
dia.vbox.set_homogeneous(False)
title = gtk.Label('<b>' + _('Image Size') + '</b>')
title.set_alignment(0.0, 0.5)
title.set_use_markup(True)
dia.vbox.pack_start(title)
table = gtk.Table(2, 2)
table.set_col_spacings(3)
table.set_row_spacings(3)
table.set_border_width(1)
table.attach(gtk.Label(_('Width:')), 0, 1, 0, 1, yoptions=False,
xoptions=gtk.FILL)
spinwidth = gtk.SpinButton(gtk.Adjustment(400.0,
0.0, sys.maxint, 1.0, 10.0))
spinwidth.set_numeric(True)
spinwidth.set_activates_default(True)
table.attach(spinwidth, 1, 2, 0, 1, yoptions=False, xoptions=gtk.FILL)
table.attach(gtk.Label(_('Height:')), 0, 1, 1, 2, yoptions=False,
xoptions=gtk.FILL)
spinheight = gtk.SpinButton(gtk.Adjustment(200.0,
0.0, sys.maxint, 1.0, 10.0))
spinheight.set_numeric(True)
spinheight.set_activates_default(True)
table.attach(spinheight, 1, 2, 1, 2, yoptions=False, xoptions=gtk.FILL)
dia.vbox.pack_start(table)
filechooser = gtk.FileChooserWidget(gtk.FILE_CHOOSER_ACTION_SAVE, None)
filechooser.set_current_folder(CONFIG['client.default_path'])
filter = gtk.FileFilter()
filter.set_name(_('PNG image (*.png)'))
filter.add_mime_type('image/png')
filter.add_pattern('*.png')
filechooser.add_filter(filter)
dia.vbox.pack_start(filechooser)
dia.show_all()
while True:
response = dia.run()
width = spinwidth.get_value_as_int()
height = spinheight.get_value_as_int()
filename = filechooser.get_filename()
if filename:
filename = filename.decode('utf-8')
try:
CONFIG['client.default_path'] = \
os.path.dirname(filename)
CONFIG.save()
except IOError:
pass
if response == gtk.RESPONSE_OK:
if width and height and filename:
if not filename.endswith('.png'):
filename = filename + '.png'
try:
graph.export_png(filename, width, height)
break
except MemoryError:
common.message(_('Image size too large!'), dia,
gtk.MESSAGE_ERROR)
else:
break
parent.present()
dia.destroy()
return
def button_press(widget, event, graph):
if event.button == 3:
menu = gtk.Menu()
item = gtk.ImageMenuItem(_('Save As...'))
img = gtk.Image()
img.set_from_stock('tryton-save-as', gtk.ICON_SIZE_MENU)
item.set_image(img)
item.connect('activate', save, graph)
item.show()
menu.append(item)
menu.popup(None, None, None, event.button, event.time)
return True
elif event.button == 1:
graph.action()
class ParserGraph(ParserInterface):
def parse(self, model, root_node, fields):
attrs = common.node_attributes(root_node)
self.title = attrs.get('string', 'Unknown')
xfield = None
yfields = []
for node in root_node.childNodes:
if not node.nodeType == node.ELEMENT_NODE:
continue
if node.localName == 'x':
for child in node.childNodes:
if not child.nodeType == child.ELEMENT_NODE:
continue
xfield = common.node_attributes(child)
if not xfield.get('string'):
xfield['string'] = fields[xfield['name']
].attrs['string']
break
elif node.localName == 'y':
for child in node.childNodes:
if not child.nodeType == child.ELEMENT_NODE:
continue
yattrs = common.node_attributes(child)
if not yattrs.get('string') and yattrs['name'] != '#':
yattrs['string'] = fields[yattrs['name']
].attrs['string']
yfields.append(yattrs)
widget = GRAPH_TYPE[attrs.get('type', 'vbar')
](xfield, yfields, attrs, model)
event = gtk.EventBox()
event.add(widget)
event.connect('button-press-event', button_press, widget)
return event, {'root': widget}, [], '', [], None
@@ -1,47 +0,0 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
"Interfaces"
class ParserInterface(object):
def __init__(self, parent=None, attrs=None, screen=None,
children_field=None):
self.parent = parent
self.attrs = attrs
self.title = None
self.state_widgets = {}
self.screen = screen
self.children_field = children_field
class ParserView(object):
def __init__(self, screen, widget, children=None, state_widgets=None,
notebooks=None, cursor_widget=None, children_field=None):
self.screen = screen
self.widget = widget
self.children = children
if state_widgets is None:
state_widgets = []
self.state_widgets = state_widgets
if notebooks is None:
notebooks = []
self.notebooks = notebooks
self.cursor_widget = cursor_widget
self.children_field = children_field
self.view_id = None
@property
def selected_records(self):
return []
def get_fields(self):
return self.children.keys()
def get_buttons(self):
return []
@property
def modified(self):
return False
+408 -153
View File
@@ -8,14 +8,19 @@ try:
except ImportError:
import json
import locale
from interface import ParserView
import gettext
from tryton.config import CONFIG
from tryton.common.cellrendererbutton import CellRendererButton
from tryton.common.cellrenderertoggle import CellRendererToggle
from tryton.gui.window import Window
from tryton.common.popup_menu import populate
from tryton.common import RPCExecute, RPCException
from tryton.common import RPCExecute, RPCException, node_attributes, Tooltips
from . import View
from .list_gtk.editabletree import EditableTreeView, TreeView
from .list_gtk.widget import (Affix, Char, Int, Boolean, URL, Date, Datetime,
Time, Float, FloatTime, Binary, M2O, O2O, O2M, M2M, Selection, Reference,
ProgressBar, Button)
_ = gettext.gettext
@@ -238,90 +243,338 @@ class AdaptModelGroup(gtk.GenericTreeModel):
return record.parent
class ViewList(ParserView):
class ViewTree(View):
def __init__(self, screen, widget, children=None, state_widgets=None,
notebooks=None, cursor_widget=None, children_field=None):
super(ViewList, self).__init__(screen, widget, children, state_widgets,
notebooks, cursor_widget, children_field)
self.store = None
def __init__(self, screen, xml, children_field):
super(ViewTree, self).__init__(screen, xml)
self.view_type = 'tree'
self.widgets = {}
self.state_widgets = []
self.children_field = children_field
self.sum_widgets = []
self.sum_box = gtk.HBox()
self.reload = False
if self.attributes.get('editable'):
self.treeview = EditableTreeView(self.attributes['editable'], self)
else:
self.treeview = TreeView()
vbox = gtk.VBox()
self.parse(xml)
self.treeview.set_property('rules-hint', True)
self.treeview.set_fixed_height_mode(True)
self.treeview.connect('button-press-event', self.__button_press)
self.treeview.connect('key-press-event', self.on_keypress)
self.treeview.connect_after('row-activated', self.__sig_switch)
if self.children_field:
self.treeview.connect('test-expand-row', self.test_expand_row)
self.treeview.set_expander_column(self.treeview.get_column(0))
self.treeview.set_rubber_banding(True)
selection = self.treeview.get_selection()
selection.set_mode(gtk.SELECTION_MULTIPLE)
selection.connect('changed', self.__select_changed)
self.set_drag_and_drop()
self.widget = gtk.VBox()
scroll = gtk.ScrolledWindow()
scroll.add(self.widget)
scroll.add(self.treeview)
scroll.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scroll.set_placement(gtk.CORNER_TOP_LEFT)
viewport = gtk.Viewport()
viewport.set_shadow_type(gtk.SHADOW_ETCHED_IN)
viewport.add(scroll)
self.widget_tree = self.widget
self.widget.pack_start(viewport, expand=True, fill=True)
vbox.pack_start(viewport, expand=True, fill=True)
self.widget_tree.screen = screen
self.widget = vbox
self.reload = False
self.children = children
if children:
hbox = gtk.HBox()
self.widget.pack_start(hbox, expand=False, fill=False, padding=2)
keys = children.keys()
keys.sort()
for i in keys:
hbox2 = gtk.HBox()
hbox2.pack_start(children[i][1], expand=True, fill=False)
hbox2.pack_start(children[i][2], expand=True, fill=False)
hbox.pack_start(hbox2, expand=False, fill=False, padding=12)
hbox.show_all()
self.sum_box.show()
self.widget.pack_start(self.sum_box, expand=False, fill=False)
self.display()
self.widget_tree.connect('button-press-event', self.__button_press)
self.widget_tree.connect_after('row-activated', self.__sig_switch)
if hasattr(self.widget_tree, 'set_rubber_banding'):
self.widget_tree.set_rubber_banding(True)
selection = self.widget_tree.get_selection()
selection.set_mode(gtk.SELECTION_MULTIPLE)
selection.connect('changed', self.__select_changed)
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.screen.group
node_attrs = node_attributes(node)
name = node_attrs['name']
field = group.fields[name]
for b_field in ('readonly', 'expand', 'tree_invisible'):
if b_field in node_attrs:
node_attrs[b_field] = bool(int(node_attrs[b_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)
assert name not in self.widgets
self.widgets[name] = widget
column = gtk.TreeViewColumn(field.attrs['string'])
column._type = 'field'
column.name = name
prefixes = []
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 affix.tagName == 'prefix':
list_ = prefixes
else:
list_ = suffixes
list_.append(Affix(self, affix_attrs))
for prefix in prefixes:
column.pack_start(prefix.renderer, expand=False)
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=False)
column.set_cell_data_func(suffix.renderer,
suffix.setter)
self.set_column_widget(column, field, node_attrs)
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)
column.set_visible(not node_attrs.get('tree_invisible', False))
if name == self.screen.exclude_field:
column.set_visible(False)
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)
column.set_visible(not node_attrs.get('tree_invisible', False))
self.treeview.append_column(column)
WIDGETS = {
'char': Char,
'many2one': M2O,
'date': Date,
'one2many': O2M,
'many2many': M2M,
'selection': Selection,
'float': Float,
'numeric': Float,
'float_time': FloatTime,
'integer': Int,
'biginteger': Int,
'datetime': Datetime,
'time': Time,
'boolean': Boolean,
'text': Char,
'url': URL,
'email': URL,
'callto': URL,
'sip': URL,
'progressbar': ProgressBar,
'reference': Reference,
'one2one': O2O,
'binary': Binary,
}
@classmethod
def get_widget(cls, name):
return cls.WIDGETS[name]
@staticmethod
def set_column_widget(column, field, attributes, arrow=True):
tooltips = Tooltips()
hbox = gtk.HBox(False, 2)
label = gtk.Label(attributes['string'])
label.show()
help = attributes['string']
if field and field.attrs.get('help'):
help += '\n' + field.attrs['help']
elif attributes.get('help'):
help += '\n' + attributes['help']
tooltips.set_tip(label, help)
tooltips.enable()
arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_IN)
column.arrow = arrow
column.arrow_show = False
hbox.pack_start(label, True, True, 0)
if arrow:
hbox.pack_start(arrow, False, False, 0)
column.set_clickable(True)
hbox.show()
column.set_widget(hbox)
def set_column_width(self, column, field, attributes):
default_width = {
'integer': 60,
'biginteger': 60,
'float': 80,
'numeric': 80,
'float_time': 100,
'date': 110,
'datetime': 160,
'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)
column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
def add_sum(self, attributes):
if 'sum' not in attributes:
return
if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL:
text = _(':') + attributes['sum']
else:
text = attributes['sum'] + _(':')
label, sum_ = gtk.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)
self.sum_widgets.append((attributes['name'], sum_))
def sort_model(self, column):
for col in self.treeview.get_columns():
if col != column:
col.arrow_show = False
col.arrow.hide()
self.screen.order = None
if not column.arrow_show:
column.arrow_show = True
column.arrow.set(gtk.ARROW_DOWN, gtk.SHADOW_IN)
column.arrow.show()
self.screen.order = [(column.name, 'ASC')]
else:
if column.arrow.get_property('arrow-type') == gtk.ARROW_DOWN:
column.arrow.set(gtk.ARROW_UP, gtk.SHADOW_IN)
self.screen.order = [(column.name, 'DESC')]
else:
column.arrow_show = False
column.arrow.hide()
model = self.treeview.get_model()
unsaved_records = [x for x in model.group if x.id < 0]
search_string = self.screen.screen_container.get_text() or None
if (self.screen.search_count == len(model)
or unsaved_records
or self.screen.parent):
ids = self.screen.search_filter(
search_string=search_string, only_ids=True)
model.sort(ids)
else:
self.screen.search_filter(search_string=search_string)
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.arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_IN)
column.arrow_show = False
column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
self.treeview.append_column(column)
def set_drag_and_drop(self):
dnd = False
if self.children_field:
children_field = self.widget_tree.cells.get(self.children_field)
if children_field:
parent_name = children_field.attrs.get('relation_field')
dnd = parent_name in self.widget_tree.cells
elif self.widget_tree.sequence:
children = self.screen.group.fields.get(self.children_field)
if children:
parent_name = children.attrs.get('relation_field')
dnd = parent_name in self.widgets
elif self.attributes.get('sequence'):
dnd = True
# Disable DnD on mac until it is fully supported
if sys.platform == 'darwin':
dnd = False
if screen.readonly:
if self.screen.readonly:
dnd = False
if dnd:
self.widget_tree.drag_source_set(
gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK,
[('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0)],
gtk.gdk.ACTION_MOVE)
self.widget_tree.drag_dest_set(gtk.DEST_DEFAULT_ALL,
[('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0)],
gtk.gdk.ACTION_MOVE)
self.widget_tree.connect('drag-begin', self.drag_begin)
self.widget_tree.connect('drag-motion', self.drag_motion)
self.widget_tree.connect('drag-drop', self.drag_drop)
self.widget_tree.connect("drag-data-get", self.drag_data_get)
self.widget_tree.connect('drag-data-received',
self.drag_data_received)
self.widget_tree.connect('drag-data-delete', self.drag_data_delete)
if not dnd:
return
self.widget_tree.connect('key_press_event', self.on_keypress)
if self.children_field:
self.widget_tree.connect('test-expand-row', self.test_expand_row)
self.widget_tree.set_expander_column(
self.widget_tree.get_column(0))
self.treeview.drag_source_set(
gtk.gdk.BUTTON1_MASK | gtk.gdk.BUTTON3_MASK,
[('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0)],
gtk.gdk.ACTION_MOVE)
self.treeview.drag_dest_set(gtk.DEST_DEFAULT_ALL,
[('MY_TREE_MODEL_ROW', gtk.TARGET_SAME_WIDGET, 0)],
gtk.gdk.ACTION_MOVE)
self.treeview.connect('drag-begin', self.drag_begin)
self.treeview.connect('drag-motion', self.drag_motion)
self.treeview.connect('drag-drop', self.drag_drop)
self.treeview.connect("drag-data-get", self.drag_data_get)
self.treeview.connect('drag-data-received',
self.drag_data_received)
self.treeview.connect('drag-data-delete', self.drag_data_delete)
@property
def modified(self):
@@ -329,10 +582,10 @@ class ViewList(ParserView):
@property
def editable(self):
return bool(getattr(self.widget_tree, 'editable', False))
return bool(getattr(self.treeview, 'editable', False))
def get_fields(self):
return [col.name for col in self.widget_tree.get_columns() if col.name]
return [col.name for col in self.treeview.get_columns() if col.name]
def get_buttons(self):
return [b for b in self.state_widgets
@@ -390,7 +643,7 @@ class ViewList(ParserView):
iter_ = model.iter_children(iter_)
if not iter_:
return False
fields = [col.name for col in self.widget_tree.get_columns()
fields = [col.name for col in self.treeview.get_columns()
if col.name]
while iter_:
record = model.get_value(iter_, 0)
@@ -405,14 +658,14 @@ class ViewList(ParserView):
def on_copy(self):
for clipboard_type in (gtk.gdk.SELECTION_CLIPBOARD,
gtk.gdk.SELECTION_PRIMARY):
clipboard = self.widget_tree.get_clipboard(clipboard_type)
clipboard = self.treeview.get_clipboard(clipboard_type)
targets = [
('STRING', 0, 0),
('TEXT', 0, 1),
('COMPOUND_TEXT', 0, 2),
('UTF8_STRING', 0, 3)
]
selection = self.widget_tree.get_selection()
selection = self.treeview.get_selection()
# Set to clipboard directly if not too much selected rows
# to speed up paste
# Don't use set_with_data on mac see:
@@ -429,11 +682,11 @@ class ViewList(ParserView):
def copy_foreach(self, treemodel, path, iter, data):
record = treemodel.get_value(iter, 0)
values = []
for col in self.widget_tree.get_columns():
for col in self.treeview.get_columns():
if not col.get_visible() or not col.name:
continue
cell = self.widget_tree.cells[col.name]
values.append('"' + str(cell.get_textual_value(record)) + '"')
widget = self.treeview.widgets[col.name]
values.append('"' + str(widget.get_textual_value(record)) + '"')
data.append('\t'.join(values))
return
@@ -459,15 +712,15 @@ class ViewList(ParserView):
data = []
for clipboard_type in (gtk.gdk.SELECTION_CLIPBOARD,
gtk.gdk.SELECTION_PRIMARY):
clipboard = self.widget_tree.get_clipboard(clipboard_type)
clipboard = self.treeview.get_clipboard(clipboard_type)
text = clipboard.wait_for_text()
if not text:
continue
data = [[unquote(v) for v in l.split('\t')]
for l in text.splitlines()]
break
col = self.widget_tree.get_cursor()[1]
columns = [c for c in self.widget_tree.get_columns()
col = self.treeview.get_cursor()[1]
columns = [c for c in self.treeview.get_columns()
if c.get_visible() and c.name]
if col in columns:
idx = columns.index(col)
@@ -489,10 +742,10 @@ class ViewList(ParserView):
group.add(record)
record = group[idx]
for col, value in zip(columns, line):
cell = self.widget_tree.cells[col.name]
if cell.get_textual_value(record) != value:
cell.value_from_text(record, value)
if value and not cell.get_textual_value(record):
widget = self.treeview.widgets[col.name]
if widget.get_textual_value(record) != value:
widget.value_from_text(record, value)
if value and not widget.get_textual_value(record):
# Stop setting value if a value is correctly set
idx = len(group)
break
@@ -527,9 +780,9 @@ class ViewList(ParserView):
etime):
treeview.emit_stop_by_name('drag-data-get')
def _func_sel_get(store, path, iter_, data):
value = store.get_value(iter_, 0)
data.append(json.dumps(value.get_path(store.group)))
def _func_sel_get(model, path, iter_, data):
value = model.get_value(iter_, 0)
data.append(json.dumps(value.get_path(model.group)))
data = []
treeselection = treeview.get_selection()
treeselection.selected_foreach(_func_sel_get, data)
@@ -542,8 +795,8 @@ class ViewList(ParserView):
def drag_data_received(self, treeview, context, x, y, selection,
info, etime):
treeview.emit_stop_by_name('drag-data-received')
if treeview.sequence:
field = self.screen.group.fields[treeview.sequence]
if self.attributes.get('sequence'):
field = self.screen.group.fields[self.attributes['sequence']]
for record in self.screen.group:
if field.get_state_attrs(
record).get('readonly', False):
@@ -559,13 +812,13 @@ class ViewList(ParserView):
if renderer.props.editing:
return
store = treeview.get_model()
model = treeview.get_model()
try:
data = json.loads(selection.data)
except ValueError:
return
record = store.group.get_by_path(data)
record_path = store.on_get_path(record)
record = model.group.get_by_path(data)
record_path = model.on_get_path(record)
drop_info = treeview.get_dest_row_at_pos(x, y)
def check_recursion(from_, to):
@@ -586,16 +839,16 @@ class ViewList(ParserView):
if not check_recursion(record_path, check_path):
return
if position == gtk.TREE_VIEW_DROP_BEFORE:
store.move_before(record, path)
model.move_before(record, path)
elif position == gtk.TREE_VIEW_DROP_AFTER:
store.move_after(record, path)
model.move_after(record, path)
elif self.children_field:
store.move_into(record, path)
model.move_into(record, path)
else:
store.move_after(record, (len(store) - 1,))
model.move_after(record, (len(model) - 1,))
context.drop_finish(False, etime)
if treeview.sequence:
record.group.set_sequence(field=treeview.sequence)
if self.attributes.get('sequence'):
record.group.set_sequence(field=self.attributes['sequence'])
return True
def drag_data_delete(self, treeview, context):
@@ -634,7 +887,7 @@ class ViewList(ParserView):
parent = parent.parent
else:
populate(menu, group.model_name, record)
for col in self.widget_tree.get_columns():
for col in self.treeview.get_columns():
if not col.get_visible() or not col.name:
continue
field = group.fields[col.name]
@@ -662,11 +915,12 @@ class ViewList(ParserView):
return False
def group_list_changed(self, group, signal):
if self.store is not None:
model = self.treeview.get_model()
if model is not None:
if signal[0] == 'record-added':
self.store.added(group, signal[1])
model.added(group, signal[1])
elif signal[0] == 'record-removed':
self.store.removed(group, signal[1])
model.removed(group, signal[1])
self.display()
def __str__(self):
@@ -680,7 +934,7 @@ class ViewList(ParserView):
return
fields = {}
last_col = None
for col in self.widget_tree.get_columns():
for col in self.treeview.get_columns():
if col.get_visible():
last_col = col
if not hasattr(col, 'name') or not hasattr(col, 'width'):
@@ -704,7 +958,7 @@ class ViewList(ParserView):
def destroy(self):
self.save_width_height()
self.widget_tree.destroy()
self.treeview.destroy()
def __sig_switch(self, treeview, path, column):
if column._type == 'button':
@@ -768,41 +1022,39 @@ class ViewList(ParserView):
go_previous()
# Delay the pre_validate to let GTK process the current event
gobject.idle_add(pre_validate)
self.update_children()
self.update_sum()
def set_value(self):
if self.editable:
self.widget_tree.set_value()
self.treeview.set_value()
def reset(self):
pass
# self.widget.set_model(self.store) could be removed if the store
# has not changed -> better ergonomy. To test
def display(self):
self.widget_tree.display_counter += 1
self.treeview.display_counter += 1
current_record = self.screen.current_record
if (self.reload
or not self.widget_tree.get_model()
or not self.treeview.get_model()
or (self.screen.group !=
self.widget_tree.get_model().group)):
self.store = AdaptModelGroup(self.screen.group,
self.treeview.get_model().group)):
model = AdaptModelGroup(self.screen.group,
self.children_field)
self.widget_tree.set_model(self.store)
self.treeview.set_model(model)
# __select_changed resets current_record to None
self.screen.current_record = current_record
if current_record:
selection = self.widget_tree.get_selection()
path = self.store.on_get_path(current_record)
selection = self.treeview.get_selection()
path = model.on_get_path(current_record)
selection.select_path(path)
self.reload = False
if not current_record:
selection = self.widget_tree.get_selection()
selection = self.treeview.get_selection()
selection.unselect_all()
self.widget_tree.queue_draw()
self.treeview.queue_draw()
if self.editable:
self.set_state()
self.update_children()
self.update_sum()
def set_state(self):
record = self.screen.current_record
@@ -812,62 +1064,63 @@ class ViewList(ParserView):
if field:
field.state_set(record)
def update_children(self):
def update_sum(self):
selected_records = self.selected_records
for child in self.children:
value = 0
value_selected = 0
for name, label in self.sum_widgets:
sum_ = 0
selected_sum = 0
loaded = True
child_fieldname = self.children[child][0]
digit = 0
for record in self.screen.group:
if not record.get_loaded([child_fieldname]):
if not record.get_loaded([name]):
loaded = False
break
field_value = record.fields_get()[child_fieldname].get(record)
if field_value is not None:
value += field_value
field = record[name]
value = field.get(record)
if value is not None:
sum_ += value
if record in selected_records or not selected_records:
value_selected += field_value
selected_sum += value
digit = max(field.digits(record)[1], digit)
if loaded:
label_str = locale.format('%.*f',
(self.children[child][3], value_selected), True)
label_str += ' / '
label_str += locale.format('%.*f',
(self.children[child][3], value), True)
text = '%s / %s' % (
locale.format('%.*f', (digit, selected_sum), True),
locale.format('%.*f', (digit, sum_), True))
else:
label_str = '-'
self.children[child][2].set_text(label_str)
text = '-'
label.set_text(text)
def set_cursor(self, new=False, reset_view=True):
self.widget_tree.grab_focus()
self.treeview.grab_focus()
if self.screen.current_record:
path = self.store.on_get_path(self.screen.current_record)
if self.store.get_flags() & gtk.TREE_MODEL_LIST_ONLY:
model = self.treeview.get_model()
path = model.on_get_path(self.screen.current_record)
if model.get_flags() & gtk.TREE_MODEL_LIST_ONLY:
path = (path[0],)
focus_column = self.widget_tree.next_column(path, editable=new)
focus_column = self.treeview.next_column(path, editable=new)
if path[:-1]:
self.widget_tree.expand_to_path(path[:-1])
self.widget_tree.scroll_to_cell(path, focus_column,
self.treeview.expand_to_path(path[:-1])
self.treeview.scroll_to_cell(path, focus_column,
use_align=False)
current_path = self.widget_tree.get_cursor()[0]
current_path = self.treeview.get_cursor()[0]
selected_path = \
self.widget_tree.get_selection().get_selected_rows()[1]
self.treeview.get_selection().get_selected_rows()[1]
if (current_path != path and path not in selected_path) or new:
self.widget_tree.set_cursor(path, focus_column, new)
self.treeview.set_cursor(path, focus_column, new)
@property
def selected_records(self):
def _func_sel_get(store, path, iter, records):
records.append(store.on_get_iter(path))
def _func_sel_get(model, path, iter, records):
records.append(model.on_get_iter(path))
records = []
sel = self.widget_tree.get_selection()
sel = self.treeview.get_selection()
sel.selected_foreach(_func_sel_get, records)
return records
def unset_editable(self):
self.widget_tree.editable = False
for col in self.widget_tree.get_columns():
self.treeview.editable = False
for col in self.treeview.get_columns():
for renderer in col.get_cell_renderers():
if isinstance(renderer, CellRendererToggle):
renderer.set_property('activatable', False)
@@ -879,7 +1132,7 @@ class ViewList(ParserView):
renderer.set_property('editable', False)
def get_selected_paths(self):
selection = self.widget_tree.get_selection()
selection = self.treeview.get_selection()
model, rows = selection.get_selected_rows()
id_paths = []
for row in rows:
@@ -892,17 +1145,17 @@ class ViewList(ParserView):
return id_paths
def select_nodes(self, nodes):
selection = self.widget_tree.get_selection()
selection = self.treeview.get_selection()
if not nodes:
return
selection.unselect_all()
scroll = False
for node in nodes:
path = path_convert_id2pos(self.store, node)
path = path_convert_id2pos(self.treeview.get_model(), node)
if path:
selection.select_path(path)
if not scroll:
self.widget_tree.scroll_to_cell(path)
self.treeview.scroll_to_cell(path)
scroll = True
def get_expanded_paths(self, starting_path=None, starting_id_path=None):
@@ -913,12 +1166,13 @@ class ViewList(ParserView):
if not starting_id_path:
starting_id_path = []
id_paths = []
record = self.store.on_get_iter(starting_path)
for path_idx in range(self.store.on_iter_n_children(record)):
model = self.treeview.get_model()
record = model.on_get_iter(starting_path)
for path_idx in range(model.on_iter_n_children(record)):
path = starting_path + [path_idx]
expanded = self.widget_tree.row_expanded(tuple(path))
expanded = self.treeview.row_expanded(tuple(path))
if expanded:
expanded_record = self.store.on_get_iter(path)
expanded_record = model.on_get_iter(path)
id_path = starting_id_path + [expanded_record.id]
id_paths.append(id_path)
child_id_paths = self.get_expanded_paths(path, id_path)
@@ -926,7 +1180,8 @@ class ViewList(ParserView):
return id_paths
def expand_nodes(self, nodes):
model = self.treeview.get_model()
for node in nodes:
expand_path = path_convert_id2pos(self.store, node)
expand_path = path_convert_id2pos(model, node)
if expand_path:
self.widget_tree.expand_to_path(expand_path)
self.treeview.expand_to_path(expand_path)
@@ -1,3 +1,2 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from parser import *
@@ -1,7 +1,6 @@
#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 gtk
import parser
import gettext
import gobject
from itertools import islice, cycle
@@ -13,10 +12,7 @@ _ = gettext.gettext
class TreeView(gtk.TreeView):
def __init__(self):
super(TreeView, self).__init__()
self.cells = {}
display_counter = 0
def next_column(self, path, column=None, editable=True, _sign=1):
columns = self.get_columns()
@@ -53,44 +49,41 @@ class EditableTreeView(TreeView):
leaving_events = leaving_record_events + (gtk.keysyms.Tab,
gtk.keysyms.ISO_Left_Tab, gtk.keysyms.KP_Enter)
def __init__(self, position):
def __init__(self, position, view):
super(EditableTreeView, self).__init__()
self.editable = position
self.view = view
def on_quit_cell(self, current_record, fieldname, value, callback=None):
field = current_record[fieldname]
cell = self.cells[fieldname]
widget = self.view.widgets[fieldname]
# The value has not changed and is valid ... do nothing.
if value == cell.get_textual_value(current_record) \
if value == widget.get_textual_value(current_record) \
and field.validate(current_record):
if callback:
callback()
return
try:
cell.value_from_text(current_record, value, callback=callback)
except parser.UnsettableColumn:
return
widget.value_from_text(current_record, value, callback=callback)
def on_open_remote(self, current_record, fieldname, create, value,
entry=None, callback=None):
cell = self.cells[fieldname]
if value != cell.get_textual_value(current_record) or not value:
widget = self.view.widgets[fieldname]
if value != widget.get_textual_value(current_record) or not value:
changed = True
else:
changed = False
try:
cell.open_remote(current_record, create, changed, value,
widget.open_remote(current_record, create, changed, value,
callback=callback)
except NotImplementedError:
pass
def on_create_line(self):
access = MODELACCESS[self.screen.model_name]
access = MODELACCESS[self.view.screen.model_name]
model = self.get_model()
if not access['create'] or (self.screen.size_limit is not None
and (len(model) >= self.screen.size_limit >= 0)):
if not access['create'] or (self.view.screen.size_limit is not None
and (len(model) >= self.view.screen.size_limit >= 0)):
return
if self.editable == 'top':
method = model.prepend
@@ -171,7 +164,7 @@ class EditableTreeView(TreeView):
gobject.idle_add(self.set_cursor, path,
self.prev_column(path, column), True)
elif keyval in self.leaving_record_events:
fields = self.cells.keys()
fields = self.view.widgets.keys()
if not record.validate(fields):
invalid_fields = record.invalid_fields
col = None
@@ -180,9 +173,9 @@ class EditableTreeView(TreeView):
break
gobject.idle_add(self.set_cursor, path, col, True)
return
if ((self.screen.pre_validate
if ((self.view.screen.pre_validate
and not record.pre_validate())
or (not self.screen.parent
or (not self.view.screen.parent
and not record.save())):
gobject.idle_add(self.set_cursor, path, column,
True)
@@ -212,8 +205,8 @@ class EditableTreeView(TreeView):
entry.handler_block(entry.editing_done_id)
def callback():
cell = self.cells[column.name]
value = cell.get_textual_value(record)
widget = self.view.widgets[column.name]
value = widget.get_textual_value(record)
if isinstance(entry, gtk.Entry):
entry.set_text(value)
else:
@@ -230,7 +223,7 @@ class EditableTreeView(TreeView):
# set_value
field.editabletree_entry = entry
def remove_widget(cell):
def remove_widget(widget):
if hasattr(field, 'editabletree_entry'):
del field.editabletree_entry
entry.connect('remove-widget', remove_widget)
@@ -10,14 +10,11 @@ import webbrowser
from functools import wraps, partial
from editabletree import EditableTreeView, TreeView
from tryton.gui.window.view_form.view.interface import ParserInterface
from tryton.gui.window.win_search import WinSearch
from tryton.gui.window.win_form import WinForm
from tryton.gui.window.view_form.screen import Screen
import tryton.rpc as rpc
from tryton.common import COLORS, node_attributes, \
file_selection, file_open, slugify
from tryton.common import COLORS, file_selection, file_open, slugify
import tryton.common as common
from tryton.common.cellrendererbutton import CellRendererButton
from tryton.common.cellrendererdate import CellRendererDate
@@ -46,40 +43,12 @@ def send_keys(renderer, editable, position, treeview):
editable.connect('changed', treeview.on_editing_done)
def sort_model(column, treeview, screen):
for col in treeview.get_columns():
if col != column:
col.arrow_show = False
col.arrow.hide()
screen.order = None
if not column.arrow_show:
column.arrow_show = True
column.arrow.set(gtk.ARROW_DOWN, gtk.SHADOW_IN)
column.arrow.show()
screen.order = [(column.name, 'ASC')]
else:
if column.arrow.get_property('arrow-type') == gtk.ARROW_DOWN:
column.arrow.set(gtk.ARROW_UP, gtk.SHADOW_IN)
screen.order = [(column.name, 'DESC')]
else:
column.arrow_show = False
column.arrow.hide()
store = treeview.get_model()
unsaved_records = [x for x in store.group if x.id < 0]
search_string = screen.screen_container.get_text() or None
if screen.search_count == len(store) or unsaved_records or screen.parent:
ids = screen.search_filter(search_string=search_string, only_ids=True)
store.sort(ids)
else:
screen.search_filter(search_string=search_string)
def realized(func):
"Decorator for treeview realized"
@wraps(func)
def wrapper(self, *args, **kwargs):
if (hasattr(self.treeview, 'get_realized')
and not self.treeview.get_realized()):
if (hasattr(self.view.treeview, 'get_realized')
and not self.view.treeview.get_realized()):
return
return func(self, *args, **kwargs)
return wrapper
@@ -123,7 +92,7 @@ class CellCache(list):
if not hasattr(self, 'cell_caches'):
self.cell_caches = {}
record = store.get_value(iter, 0)
counter = self.treeview.display_counter
counter = self.view.treeview.display_counter
if (self.display_counters.get(record.id) != counter):
if getattr(cell, 'decorated', None):
func(self, column, cell, store, iter)
@@ -139,223 +108,10 @@ class CellCache(list):
return wrapper
class ParserTree(ParserInterface):
def __init__(self, parent=None, attrs=None, screen=None,
children_field=None):
super(ParserTree, self).__init__(parent, attrs, screen,
children_field=children_field)
self.treeview = None
def parse(self, model_name, root_node, fields):
dict_widget = {}
state_widgets = []
attrs = node_attributes(root_node)
on_write = attrs.get('on_write', '')
editable = attrs.get('editable', False)
if editable:
treeview = EditableTreeView(editable)
else:
treeview = TreeView()
treeview.cells = {}
treeview.display_counter = 0
treeview.sequence = attrs.get('sequence', False)
treeview.colors = attrs.get('colors', '"black"')
treeview.keyword_open = attrs.get('keyword_open', False)
self.treeview = treeview
treeview.set_property('rules-hint', True)
if not self.title:
self.title = attrs.get('string', 'Unknown')
tooltips = common.Tooltips()
expandable = False
for node in root_node.childNodes:
node_attrs = node_attributes(node)
if node.localName == 'field':
fname = str(node_attrs['name'])
for boolean_fields in ('readonly', 'required', 'expand'):
if boolean_fields in node_attrs:
node_attrs[boolean_fields] = \
bool(int(node_attrs[boolean_fields]))
if fname not in fields:
continue
for attr_name in ('relation', 'domain', 'selection',
'relation_field', 'string', 'views', 'invisible',
'add_remove', 'sort', 'context', 'filename',
'selection_change_with'):
if attr_name in fields[fname].attrs and \
not attr_name in node_attrs:
node_attrs[attr_name] = fields[fname].attrs[attr_name]
cell_type = node_attrs.get('widget',
fields[fname].attrs['type'])
cell = CELLTYPES.get(cell_type)(fname, model_name,
treeview, node_attrs)
treeview.cells[fname] = cell
renderer = cell.renderer
readonly = not (editable and not node_attrs.get('readonly',
fields[fname].attrs.get('readonly', False)))
if isinstance(renderer, CellRendererToggle):
renderer.set_property('activatable', not readonly)
elif isinstance(renderer,
(gtk.CellRendererProgress, CellRendererButton)):
pass
else:
renderer.set_property('editable', not readonly)
if (not readonly
and not isinstance(renderer, CellRendererBinary)):
renderer.connect_after('editing-started', send_keys,
treeview)
col = gtk.TreeViewColumn(fields[fname].attrs['string'])
prefixes = []
suffixes = []
if cell_type in ('url', 'email', 'callto', 'sip'):
prefixes.append(Affix(fname, self.treeview, node_attrs,
protocol=cell_type))
if 'icon' in node_attrs:
prefixes.append(Affix(fname, self.treeview, node_attrs))
for affix in node.childNodes:
affix_attrs = node_attributes(affix)
if affix.localName == 'prefix':
list_ = prefixes
else:
list_ = suffixes
list_.append(Affix(fname, self.treeview, affix_attrs))
for prefix in prefixes:
col.pack_start(prefix.renderer, expand=False)
col.set_cell_data_func(prefix.renderer, prefix.setter)
col.pack_start(renderer, expand=True)
col.set_cell_data_func(renderer, cell.setter)
col.name = fname
for suffix in suffixes:
col.pack_start(suffix.renderer, expand=False)
col.set_cell_data_func(suffix.renderer, suffix.setter)
hbox = gtk.HBox(False, 2)
label = gtk.Label(fields[fname].attrs['string'])
label.show()
help = fields[fname].attrs['string']
if fields[fname].attrs.get('help'):
help += '\n' + fields[fname].attrs['help']
tooltips.set_tip(label, help)
tooltips.enable()
arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_IN)
col.arrow = arrow
col.arrow_show = False
hbox.pack_start(label, True, True, 0)
hbox.pack_start(arrow, False, False, 0)
hbox.show()
col.set_widget(hbox)
col._type = fields[fname].attrs['type']
col.set_clickable(True)
twidth = {
'integer': 60,
'biginteger': 60,
'float': 80,
'numeric': 80,
'float_time': 100,
'date': 110,
'datetime': 160,
'selection': 90,
'char': 100,
'one2many': 50,
'many2many': 50,
'boolean': 20,
'binary': 200,
}
width = self.screen.tree_column_width[model_name].get(fname)
if not width:
if 'width' in node_attrs:
width = int(node_attrs['width'])
else:
width = twidth.get(fields[fname].attrs['type'], 100)
col.width = width
if width > 0:
col.set_fixed_width(width)
col.set_min_width(1)
expand = node_attrs.get('expand', False)
col.set_expand(expand)
expandable |= expand
if (not treeview.sequence
and not self.children_field
and fields[fname].attrs.get('sortable', True)):
col.connect('clicked', sort_model, treeview, self.screen)
col.set_resizable(True)
col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
col.set_visible(not node_attrs.get('tree_invisible',
fields[fname].attrs.get('tree_invisible', False)))
if fname == self.screen.exclude_field:
col.set_visible(False)
i = treeview.append_column(col)
if 'sum' in node_attrs and fields[fname].attrs['type'] \
in ('integer', 'biginteger', 'float', 'numeric',
'float_time'):
label = gtk.Label(node_attrs['sum'] + _(': '))
label_sum = gtk.Label()
if isinstance(fields[fname].attrs.get('digits'),
basestring):
digits = 2
else:
digits = fields[fname].attrs.get('digits', (16, 2))[1]
dict_widget[i] = (fname, label, label_sum, digits)
elif node.localName == 'button':
#TODO add shortcut
cell = Button(treeview, self.screen, node_attrs)
state_widgets.append(cell)
renderer = cell.renderer
string = node_attrs.get('string', _('Unknown'))
col = gtk.TreeViewColumn(string, renderer)
col.name = None
col.set_visible(not node_attrs.get('tree_invisible', False))
label = gtk.Label(string)
label.show()
help = string
if node_attrs.get('help'):
help += '\n' + node_attrs['help']
tooltips.set_tip(label, help)
tooltips.enable()
arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_IN)
col.arrow = arrow
col.arrow_show = False
col.set_widget(label)
col._type = 'button'
col.set_cell_data_func(renderer, cell.setter)
if 'width' in node_attrs:
width = int(node_attrs['width'])
else:
width = 80
col.width = width
if width > 0:
col.set_fixed_width(width)
col.set_resizable(True)
col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
i = treeview.append_column(col)
if not expandable:
col = gtk.TreeViewColumn()
col.name = None
arrow = gtk.Arrow(gtk.ARROW_DOWN, gtk.SHADOW_IN)
col.arrow = arrow
col.arrow_show = False
col._type = 'fill'
col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
treeview.append_column(col)
treeview.set_fixed_height_mode(True)
return treeview, dict_widget, state_widgets, on_write, [], None
class Affix(object):
def __init__(self, field_name, treeview, attrs, protocol=None):
def __init__(self, view, attrs, protocol=None):
super(Affix, self).__init__()
self.field_name = attrs.get('name', field_name)
self.attrs = attrs
self.protocol = protocol
self.icon = attrs.get('icon')
@@ -368,13 +124,13 @@ class Affix(object):
self.renderer = gtk.CellRendererPixbuf()
else:
self.renderer = gtk.CellRendererText()
self.treeview = treeview
self.view = view
@realized
@CellCache.cache
def setter(self, column, cell, store, iter_):
record = store.get_value(iter_, 0)
field = record[self.field_name]
field = record[self.attrs['name']]
field.state_set(record, states=('invisible',))
invisible = field.get_state_attrs(record).get('invisible', False)
cell.set_property('visible', not invisible)
@@ -384,7 +140,7 @@ class Affix(object):
else:
value = self.icon
common.ICONFACTORY.register_icon(value)
pixbuf = self.treeview.render_icon(stock_id=value,
pixbuf = self.view.treeview.render_icon(stock_id=value,
size=gtk.ICON_SIZE_BUTTON, detail=None)
cell.set_property('pixbuf', pixbuf)
else:
@@ -394,9 +150,9 @@ class Affix(object):
cell.set_property('text', text)
def clicked(self, renderer, path):
store = self.treeview.get_model()
store = self.view.treeview.get_model()
record = store.get_value(store.get_iter(path), 0)
value = record[self.field_name].get(record)
value = record[self.attrs['name']].get(record)
if value:
if self.protocol == 'email':
value = 'mailto:%s' % value
@@ -409,17 +165,25 @@ class Affix(object):
class Char(object):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
def __init__(self, view, attrs, renderer=None):
super(Char, self).__init__()
self.field_name = field_name
self.model_name = model_name
self.attrs = attrs or {}
self.attrs = attrs
if renderer is None:
renderer = CellRendererText
self.renderer = renderer()
self.renderer.connect('editing-started', self.editing_started)
self.treeview = treeview
if not isinstance(self.renderer, CellRendererBinary):
self.renderer.connect_after('editing-started', send_keys,
view.treeview)
self.view = view
@property
def field_name(self):
return self.attrs['name']
@property
def model_name(self):
return self.view.screen.model_name
@realized
@CellCache.cache
@@ -442,7 +206,7 @@ class Char(object):
else:
cell.set_property('foreground-set', True)
field = record[self.field_name]
field = record[self.attrs['name']]
if self.attrs.get('type', field.attrs.get('type')) in \
('float', 'integer', 'biginteger', 'boolean',
@@ -451,17 +215,16 @@ class Char(object):
else:
align = 0
editable = getattr(self.view.treeview, 'editable', False)
states = ('invisible',)
if hasattr(self.treeview, 'editable') \
and self.treeview.editable:
if editable:
states = ('readonly', 'required', 'invisible')
field.state_set(record, states=states)
invisible = field.get_state_attrs(record).get('invisible', False)
cell.set_property('visible', not invisible)
if hasattr(self.treeview, 'editable') \
and self.treeview.editable:
if editable:
readonly = self.attrs.get('readonly',
field.get_state_attrs(record).get('readonly', False))
if invisible:
@@ -493,7 +256,7 @@ class Char(object):
cell.set_property('xalign', align)
def get_color(self, record):
return record.expr_eval(self.treeview.colors)
return record.expr_eval(self.view.attributes.get('colors', '"black"'))
def open_remote(self, record, create, changed=False, text=None,
callback=None):
@@ -502,10 +265,10 @@ class Char(object):
def get_textual_value(self, record):
if not record:
return ''
return record[self.field_name].get_client(record)
return record[self.attrs['name']].get_client(record)
def value_from_text(self, record, text, callback=None):
field = record[self.field_name]
field = record[self.attrs['name']]
field.set_client(record, text)
if callback:
callback()
@@ -514,29 +277,28 @@ class Char(object):
return False
def _get_record_field(self, path):
store = self.treeview.get_model()
store = self.view.treeview.get_model()
record = store.get_value(store.get_iter(path), 0)
field = record.group.fields[self.field_name]
field = record.group.fields[self.attrs['name']]
return record, field
class Int(Char):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
def __init__(self, view, attrs, renderer=None):
if renderer is None:
renderer = CellRendererInteger
super(Int, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
super(Int, self).__init__(view, attrs, renderer=renderer)
self.factor = float(attrs.get('factor', 1))
def get_textual_value(self, record):
if not record:
return ''
return record[self.field_name].get_client(record, factor=self.factor)
return record[self.attrs['name']].get_client(
record, factor=self.factor)
def value_from_text(self, record, text, callback=None):
field = record[self.field_name]
field = record[self.attrs['name']]
field.set_client(record, text, factor=self.factor)
if callback:
callback()
@@ -544,23 +306,22 @@ class Int(Char):
class Boolean(Char):
def __init__(self, field_name, model_name, treeview, attrs=None,
def __init__(self, view, attrs=None,
renderer=None):
if renderer is None:
renderer = CellRendererToggle
super(Boolean, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
super(Boolean, self).__init__(view, attrs, renderer=renderer)
self.renderer.connect('toggled', self._sig_toggled)
def _sig_toggled(self, renderer, path):
store = self.treeview.get_model()
store = self.view.treeview.get_model()
record = store.get_value(store.get_iter(path), 0)
field = record[self.field_name]
field = record[self.attrs['name']]
if not self.attrs.get('readonly',
field.get_state_attrs(record).get('readonly', False)):
value = record[self.field_name].get_client(record)
record[self.field_name].set_client(record, int(not value))
self.treeview.set_cursor(path)
value = record[self.attrs['name']].get_client(record)
record[self.attrs['name']].set_client(record, int(not value))
self.view.treeview.set_cursor(path)
return True
@@ -571,7 +332,7 @@ class URL(Char):
def setter(self, column, cell, store, iter):
super(URL, self).setter(column, cell, store, iter)
record = store.get_value(iter, 0)
field = record[self.field_name]
field = record[self.attrs['name']]
field.state_set(record, states=('readonly',))
readonly = field.get_state_attrs(record).get('readonly', False)
cell.set_property('visible', not readonly)
@@ -579,12 +340,10 @@ class URL(Char):
class Date(Char):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
def __init__(self, view, attrs, renderer=None):
if renderer is None:
renderer = partial(CellRendererDate, date_format())
super(Date, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
super(Date, self).__init__(view, attrs, renderer=renderer)
self.renderer.connect('editing-started', self.editing_started)
@@ -594,7 +353,7 @@ class Datetime(Date):
def setter(self, column, cell, store, iter):
super(Datetime, self).setter(column, cell, store, iter)
record = store.get_value(iter, 0)
field = record[self.field_name]
field = record[self.attrs['name']]
time_format = field.time_format(record)
self.renderer.format = date_format() + ' ' + time_format
@@ -605,45 +364,41 @@ class Time(Date):
def setter(self, column, cell, store, iter):
super(Time, self).setter(column, cell, store, iter)
record = store.get_value(iter, 0)
field = record[self.field_name]
field = record[self.attrs['name']]
time_format = field.time_format(record)
self.renderer.format = time_format
class Float(Int):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
def __init__(self, view, attrs, renderer=None):
if renderer is None:
renderer = CellRendererFloat
super(Float, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
super(Float, self).__init__(view, attrs, renderer=renderer)
@realized
def setter(self, column, cell, store, iter):
super(Float, self).setter(column, cell, store, iter)
record = store.get_value(iter, 0)
field = record[self.field_name]
field = record[self.attrs['name']]
digits = field.digits(record, factor=self.factor)
cell.digits = digits
class FloatTime(Char):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
super(FloatTime, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
def __init__(self, view, attrs, renderer=None):
super(FloatTime, self).__init__(view, attrs, renderer=renderer)
self.conv = None
if attrs and attrs.get('float_time'):
self.conv = rpc.CONTEXT.get(attrs['float_time'])
def get_textual_value(self, record):
val = record[self.field_name].get(record)
val = record[self.attrs['name']].get(record)
return common.float_time_to_text(val, self.conv)
def value_from_text(self, record, text, callback=None):
field = record[self.field_name]
field = record[self.attrs['name']]
digits = field.digits(record)
field.set_client(record,
common.text_to_float_time(text, self.conv, digits[1]))
@@ -653,13 +408,11 @@ class FloatTime(Char):
class Binary(Char):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
def __init__(self, view, attrs, renderer=None):
self.filename = attrs.get('filename')
if renderer is None:
renderer = partial(CellRendererBinary, bool(self.filename))
super(Binary, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
super(Binary, self).__init__(view, attrs, renderer=renderer)
self.renderer.connect('new', self.new_binary)
self.renderer.connect('open', self.open_binary)
self.renderer.connect('save', self.save_binary)
@@ -677,7 +430,7 @@ class Binary(Char):
@CellCache.cache
def setter(self, column, cell, store, iter):
record = store.get_value(iter, 0)
field = record[self.field_name]
field = record[self.attrs['name']]
if hasattr(field, 'get_size'):
size = field.get_size(record)
else:
@@ -685,14 +438,14 @@ class Binary(Char):
cell.set_property('size', common.humanize(size) if size else '')
states = ('invisible',)
if getattr(self.treeview, 'editable', False):
if getattr(self.view.treeview, 'editable', False):
states = ('readonly', 'required', 'invisible')
field.state_set(record, states=states)
invisible = field.get_state_attrs(record).get('invisible', False)
cell.set_property('visible', not invisible)
if getattr(self.treeview, 'editable', False):
if getattr(self.view.treeview, 'editable', False):
readonly = self.attrs.get('readonly',
field.get_state_attrs(record).get('readonly', False))
if invisible:
@@ -758,24 +511,22 @@ class Binary(Char):
class M2O(Char):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
def __init__(self, view, attrs, renderer=None):
if renderer is None and int(attrs.get('completion', 1)):
renderer = partial(CellRendererTextCompletion, self.set_completion)
super(M2O, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
super(M2O, self).__init__(view, attrs, renderer=renderer)
def value_from_text(self, record, text, callback=None):
field = record.group.fields[self.field_name]
field = record.group.fields[self.attrs['name']]
if not text:
field.set_client(record, (None, ''))
if callback:
callback()
return
relation = record[self.field_name].attrs['relation']
domain = record[self.field_name].domain_get(record)
context = record[self.field_name].context_get(record)
relation = record[self.attrs['name']].attrs['relation']
domain = record[self.attrs['name']].domain_get(record)
context = record[self.attrs['name']].context_get(record)
dom = [('rec_name', 'ilike', '%' + text + '%'), domain]
try:
ids = RPCExecute('model', relation, 'search', dom, 0, None, None,
@@ -795,7 +546,7 @@ class M2O(Char):
def open_remote(self, record, create=True, changed=False, text=None,
callback=None):
field = record.group.fields[self.field_name]
field = record.group.fields[self.attrs['name']]
relation = field.attrs['relation']
access = common.MODELACCESS[relation]
@@ -849,7 +600,7 @@ class M2O(Char):
def search_remote(self, record, relation, ids=None, domain=None,
context=None, callback=None):
field = record.group.fields[self.field_name]
field = record.group.fields[self.attrs['name']]
def search_callback(found):
value = None
@@ -917,12 +668,6 @@ class O2O(M2O):
pass
class UnsettableColumn(Exception):
def __init__(self):
Exception.__init__()
class O2M(Char):
@realized
@@ -931,7 +676,7 @@ class O2M(Char):
cell.set_property('xalign', 0.5)
def get_textual_value(self, record):
return '( ' + str(len(record[self.field_name]
return '( ' + str(len(record[self.attrs['name']]
.get_eval(record))) + ' )'
def value_from_text(self, record, text, callback=None):
@@ -940,8 +685,8 @@ class O2M(Char):
def open_remote(self, record, create=True, changed=False, text=None,
callback=None):
group = record.value[self.field_name]
field = record.group.fields[self.field_name]
group = record.value[self.attrs['name']]
field = record.group.fields[self.attrs['name']]
relation = field.attrs['relation']
context = field.context_get(record)
@@ -964,8 +709,8 @@ class M2M(O2M):
def open_remote(self, record, create=True, changed=False, text=None,
callback=None):
group = record.value[self.field_name]
field = record.group.fields[self.field_name]
group = record.value[self.attrs['name']]
field = record.group.fields[self.attrs['name']]
relation = field.attrs['relation']
context = field.context_get(record)
domain = field.domain_get(record)
@@ -988,11 +733,12 @@ class Selection(Char, SelectionMixin, PopdownMixin):
self.renderer = CellRendererCombo()
self.renderer.connect('editing-started', self.editing_started)
self.init_selection()
self.renderer.set_property('model', self.get_popdown_model(self.selection)[0])
self.renderer.set_property('model',
self.get_popdown_model(self.selection)[0])
self.renderer.set_property('text-column', 0)
def get_textual_value(self, record):
field = record[self.field_name]
field = record[self.attrs['name']]
self.update_selection(record, field)
value = field.get(record)
text = dict(self.selection).get(value, '')
@@ -1006,9 +752,9 @@ class Selection(Char, SelectionMixin, PopdownMixin):
def editing_started(self, cell, editable, path):
super(Selection, self).editing_started(cell, editable, path)
store = self.treeview.get_model()
store = self.view.treeview.get_model()
record = store.get_value(store.get_iter(path), 0)
field = record[self.field_name]
field = record[self.attrs['name']]
set_value = lambda *a: self.set_value(editable, record, field)
editable.child.connect('activate', set_value)
@@ -1034,14 +780,12 @@ class Selection(Char, SelectionMixin, PopdownMixin):
class Reference(Char, SelectionMixin):
def __init__(self, field_name, model_name, treeview, attrs=None,
renderer=None):
super(Reference, self).__init__(field_name, model_name, treeview,
attrs=attrs, renderer=renderer)
def __init__(self, view, attrs, renderer=None):
super(Reference, self).__init__(view, attrs, renderer=renderer)
self.init_selection()
def get_textual_value(self, record):
field = record[self.field_name]
field = record[self.attrs['name']]
self.update_selection(record, field)
value = field.get_client(record)
if not value:
@@ -1066,22 +810,19 @@ class ProgressBar(object):
'top_to_bottom': gtk.PROGRESS_TOP_TO_BOTTOM,
}
def __init__(self, field_name, model_name, treeview, attrs=None):
def __init__(self, view, attrs):
super(ProgressBar, self).__init__()
self.field_name = field_name
self.model_name = model_name
self.attrs = attrs or {}
self.attrs = attrs
self.renderer = gtk.CellRendererProgress()
orientation = self.orientations.get(self.attrs.get('orientation',
'left_to_right'), gtk.PROGRESS_LEFT_TO_RIGHT)
self.renderer.set_property('orientation', orientation)
self.treeview = treeview
@realized
@CellCache.cache
def setter(self, column, cell, store, iter):
record = store.get_value(iter, 0)
field = record[self.field_name]
field = record[self.attrs['name']]
value = float(self.get_textual_value(record) or 0.0)
cell.set_property('value', value)
digit = field.digits(record)[1]
@@ -1093,10 +834,10 @@ class ProgressBar(object):
raise NotImplementedError
def get_textual_value(self, record):
return record[self.field_name].get_client(record) or ''
return record[self.attrs['name']].get_client(record) or ''
def value_from_text(self, record, text, callback=None):
field = record[self.field_name]
field = record[self.attrs['name']]
field.set_client(record, float(text))
if callback:
callback()
@@ -1104,12 +845,11 @@ class ProgressBar(object):
class Button(object):
def __init__(self, treeview, screen, attrs=None):
def __init__(self, view, attrs):
super(Button, self).__init__()
self.attrs = attrs or {}
self.attrs = attrs
self.renderer = CellRendererButton(attrs.get('string', _('Unknown')))
self.treeview = treeview
self.screen = screen
self.view = view
self.renderer.connect('clicked', self.button_clicked)
@@ -1135,7 +875,7 @@ class Button(object):
def button_clicked(self, widget, path):
if not path:
return True
store = self.treeview.get_model()
store = self.view.treeview.get_model()
record = store.get_value(store.get_iter(path), 0)
state_changes = record.expr_eval(
@@ -1143,30 +883,4 @@ class Button(object):
if state_changes.get('invisible') \
or state_changes.get('readonly'):
return True
self.screen.button(self.attrs)
CELLTYPES = {
'char': Char,
'many2one': M2O,
'date': Date,
'one2many': O2M,
'many2many': M2M,
'selection': Selection,
'float': Float,
'numeric': Float,
'float_time': FloatTime,
'integer': Int,
'biginteger': Int,
'datetime': Datetime,
'time': Time,
'boolean': Boolean,
'text': Char,
'url': URL,
'email': URL,
'callto': URL,
'sip': URL,
'progressbar': ProgressBar,
'reference': Reference,
'one2one': O2O,
'binary': Binary,
}
self.view.screen.button(self.attrs)
@@ -1,47 +0,0 @@
#This file is part of Tryton. The COPYRIGHT file at the top level of
#this repository contains the full copyright notices and license terms.
from interface import ParserInterface
import form_gtk
import list_gtk
import graph_gtk
import calendar_gtk
from form import ViewForm
from list import ViewList
from graph import ViewGraph
from calendar_ import ViewCalendar
from tryton.exceptions import TrytonError
PARSERS = {
'form': form_gtk.ParserForm,
'tree': list_gtk.ParserTree,
'graph': graph_gtk.ParserGraph,
'calendar': calendar_gtk.ParserCalendar,
}
PARSERS2 = {
'form': ViewForm,
'tree': ViewList,
'graph': ViewGraph,
'calendar': ViewCalendar,
}
class WidgetParse(ParserInterface):
def parse(self, screen, root_node, fields, children_field=None):
for node in root_node.childNodes:
if not node.nodeType == node.ELEMENT_NODE:
continue
if node.localName in PARSERS:
widget = PARSERS[node.localName](self.parent, self.attrs,
screen, children_field)
(wid, child, state_widgets, on_write, notebooks,
cursor_widget) = widget.parse(screen.model_name, node,
fields)
screen.set_on_write(on_write)
res = PARSERS2[node.localName](screen, wid, child,
state_widgets, notebooks, cursor_widget, children_field)
res.title = widget.title
return res
else:
raise TrytonError('Unknow view mode: %s' % node.localName)
+2 -2
View File
@@ -60,7 +60,7 @@ class WinSearch(NoModal):
self.view.unset_editable()
# Prevent to set tree_state
self.screen.tree_states_done.add(id(self.view))
sel = self.view.widget_tree.get_selection()
sel = self.view.treeview.get_selection()
if not sel_multi:
sel.set_mode(gtk.SELECTION_SINGLE)
@@ -89,7 +89,7 @@ class WinSearch(NoModal):
common.center_window(self.win, self.parent, self.sensible_widget)
def sig_activate(self, *args):
self.view.widget_tree.emit_stop_by_name('row_activated')
self.view.treeview.emit_stop_by_name('row_activated')
self.win.response(gtk.RESPONSE_OK)
return True