Refactorize XML parsing
issue3938 review11281004
This commit is contained in:
1 parent
a320fbc2b2
commit
a2ac629320
45 files changed
+1596
-1852
No files matched your search
+2
-2
@@ -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
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
@@ -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
|
||||
@@ -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()
|
||||
|
||||
+18
-17
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
||||
+93
-379
@@ -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)
|
||||
@@ -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
|
||||
|
||||
|
||||
Reference in new issue
Block a user