diff --git a/tryton/gui/main.py b/tryton/gui/main.py index 83a7ab9100..56d11bc52f 100644 --- a/tryton/gui/main.py +++ b/tryton/gui/main.py @@ -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 diff --git a/tryton/gui/window/view_board/action.py b/tryton/gui/window/view_board/action.py index 77b17c06e8..747815dbf3 100644 --- a/tryton/gui/window/view_board/action.py +++ b/tryton/gui/window/view_board/action.py @@ -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(), diff --git a/tryton/gui/window/view_board/parser.py b/tryton/gui/window/view_board/parser.py deleted file mode 100644 index e22021cf80..0000000000 --- a/tryton/gui/window/view_board/parser.py +++ /dev/null @@ -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 diff --git a/tryton/gui/window/view_board/view_board.py b/tryton/gui/window/view_board/view_board.py index 688e0cff2b..10d3ddb32e 100644 --- a/tryton/gui/window/view_board/view_board.py +++ b/tryton/gui/window/view_board/view_board.py @@ -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 diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py index 95c08fc710..96b75ec911 100644 --- a/tryton/gui/window/view_form/screen/screen.py +++ b/tryton/gui/window/view_form/screen/screen.py @@ -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) diff --git a/tryton/gui/window/view_form/view/__init__.py b/tryton/gui/window/view_form/view/__init__.py index c86640b67f..ef1268c957 100644 --- a/tryton/gui/window/view_form/view/__init__.py +++ b/tryton/gui/window/view_form/view/__init__.py @@ -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) diff --git a/tryton/gui/window/view_form/view/calendar_.py b/tryton/gui/window/view_form/view/calendar_.py index 93d4c746a0..a3a8a2466d 100644 --- a/tryton/gui/window/view_form/view/calendar_.py +++ b/tryton/gui/window/view_form/view/calendar_.py @@ -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 diff --git a/tryton/gui/window/view_form/view/calendar_gtk/__init__.py b/tryton/gui/window/view_form/view/calendar_gtk/__init__.py index 5218f22bd6..c86640b67f 100644 --- a/tryton/gui/window/view_form/view/calendar_gtk/__init__.py +++ b/tryton/gui/window/view_form/view/calendar_gtk/__init__.py @@ -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 * diff --git a/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py b/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py index bc799ab46a..a30e26259f 100644 --- a/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +++ b/tryton/gui/window/view_form/view/calendar_gtk/calendar_.py @@ -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 diff --git a/tryton/gui/window/view_form/view/calendar_gtk/parser.py b/tryton/gui/window/view_form/view/calendar_gtk/parser.py deleted file mode 100644 index 579f05972e..0000000000 --- a/tryton/gui/window/view_form/view/calendar_gtk/parser.py +++ /dev/null @@ -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 diff --git a/tryton/gui/window/view_form/view/form.py b/tryton/gui/window/view_form/view/form.py index 62a18f0b19..ab0ad3b9b4 100644 --- a/tryton/gui/window/view_form/view/form.py +++ b/tryton/gui/window/view_form/view/form.py @@ -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: diff --git a/tryton/gui/window/view_form/view/form_gtk/__init__.py b/tryton/gui/window/view_form/view/form_gtk/__init__.py index 5218f22bd6..c86640b67f 100644 --- a/tryton/gui/window/view_form/view/form_gtk/__init__.py +++ b/tryton/gui/window/view_form/view/form_gtk/__init__.py @@ -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 * diff --git a/tryton/gui/window/view_form/view/form_gtk/binary.py b/tryton/gui/window/view_form/view/form_gtk/binary.py index 26f704a722..c7ecca48bf 100644 --- a/tryton/gui/window/view_form/view/form_gtk/binary.py +++ b/tryton/gui/window/view_form/view/form_gtk/binary.py @@ -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') diff --git a/tryton/gui/window/view_form/view/form_gtk/calendar.py b/tryton/gui/window/view_form/view/form_gtk/calendar.py index bfab5539db..64682c3ce6 100644 --- a/tryton/gui/window/view_form/view/form_gtk/calendar.py +++ b/tryton/gui/window/view_form/view/form_gtk/calendar.py @@ -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): diff --git a/tryton/gui/window/view_form/view/form_gtk/char.py b/tryton/gui/window/view_form/view/form_gtk/char.py index 3cf5a2a73c..28e7be5149 100644 --- a/tryton/gui/window/view_form/view/form_gtk/char.py +++ b/tryton/gui/window/view_form/view/form_gtk/char.py @@ -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() diff --git a/tryton/gui/window/view_form/view/form_gtk/checkbox.py b/tryton/gui/window/view_form/view/form_gtk/checkbox.py index e6f94e3b63..25a6b88009 100644 --- a/tryton/gui/window/view_form/view/form_gtk/checkbox.py +++ b/tryton/gui/window/view_form/view/form_gtk/checkbox.py @@ -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) diff --git a/tryton/gui/window/view_form/view/form_gtk/dictionary.py b/tryton/gui/window/view_form/view/form_gtk/dictionary.py index 87d1cce075..3801312b41 100644 --- a/tryton/gui/window/view_form/view/form_gtk/dictionary.py +++ b/tryton/gui/window/view_form/view/form_gtk/dictionary.py @@ -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 = {} diff --git a/tryton/gui/window/view_form/view/form_gtk/float.py b/tryton/gui/window/view_form/view/form_gtk/float.py index 2ae2f77545..c0555f370b 100644 --- a/tryton/gui/window/view_form/view/form_gtk/float.py +++ b/tryton/gui/window/view_form/view/form_gtk/float.py @@ -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): diff --git a/tryton/gui/window/view_form/view/form_gtk/float_time.py b/tryton/gui/window/view_form/view/form_gtk/float_time.py index 93d759236e..573530740b 100644 --- a/tryton/gui/window/view_form/view/form_gtk/float_time.py +++ b/tryton/gui/window/view_form/view/form_gtk/float_time.py @@ -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() diff --git a/tryton/gui/window/view_form/view/form_gtk/image.py b/tryton/gui/window/view_form/view/form_gtk/image.py index ba5bdd0df4..4139e45709 100644 --- a/tryton/gui/window/view_form/view/form_gtk/image.py +++ b/tryton/gui/window/view_form/view/form_gtk/image.py @@ -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)) diff --git a/tryton/gui/window/view_form/view/form_gtk/integer.py b/tryton/gui/window/view_form/view/form_gtk/integer.py index cc8ee4db22..256c3449f5 100644 --- a/tryton/gui/window/view_form/view/form_gtk/integer.py +++ b/tryton/gui/window/view_form/view/form_gtk/integer.py @@ -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) diff --git a/tryton/gui/window/view_form/view/form_gtk/many2many.py b/tryton/gui/window/view_form/view/form_gtk/many2many.py index fbc0dd6f5e..d2229a9b0d 100644 --- a/tryton/gui/window/view_form/view/form_gtk/many2many.py +++ b/tryton/gui/window/view_form/view/form_gtk/many2many.py @@ -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): diff --git a/tryton/gui/window/view_form/view/form_gtk/many2one.py b/tryton/gui/window/view_form/view/form_gtk/many2one.py index 04f2d2313f..a4692ba19b 100644 --- a/tryton/gui/window/view_form/view/form_gtk/many2one.py +++ b/tryton/gui/window/view_form/view/form_gtk/many2one.py @@ -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) diff --git a/tryton/gui/window/view_form/view/form_gtk/multiselection.py b/tryton/gui/window/view_form/view/form_gtk/multiselection.py index 99b2abf782..6e60ed82eb 100644 --- a/tryton/gui/window/view_form/view/form_gtk/multiselection.py +++ b/tryton/gui/window/view_form/view/form_gtk/multiselection.py @@ -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) diff --git a/tryton/gui/window/view_form/view/form_gtk/one2many.py b/tryton/gui/window/view_form/view/form_gtk/one2many.py index ca9f02cbce..604a03a886 100644 --- a/tryton/gui/window/view_form/view/form_gtk/one2many.py +++ b/tryton/gui/window/view_form/view/form_gtk/one2many.py @@ -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 diff --git a/tryton/gui/window/view_form/view/form_gtk/one2one.py b/tryton/gui/window/view_form/view/form_gtk/one2one.py index 3af021f7f5..334c8af02f 100644 --- a/tryton/gui/window/view_form/view/form_gtk/one2one.py +++ b/tryton/gui/window/view_form/view/form_gtk/one2one.py @@ -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): diff --git a/tryton/gui/window/view_form/view/form_gtk/parser.py b/tryton/gui/window/view_form/view/form_gtk/parser.py deleted file mode 100644 index e7c71f2cee..0000000000 --- a/tryton/gui/window/view_form/view/form_gtk/parser.py +++ /dev/null @@ -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), -} diff --git a/tryton/gui/window/view_form/view/form_gtk/progressbar.py b/tryton/gui/window/view_form/view/form_gtk/progressbar.py index 23e9b6ea29..40511492e8 100644 --- a/tryton/gui/window/view_form/view/form_gtk/progressbar.py +++ b/tryton/gui/window/view_form/view/form_gtk/progressbar.py @@ -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) diff --git a/tryton/gui/window/view_form/view/form_gtk/reference.py b/tryton/gui/window/view_form/view/form_gtk/reference.py index 0455e23e55..4faa4be4cc 100644 --- a/tryton/gui/window/view_form/view/form_gtk/reference.py +++ b/tryton/gui/window/view_form/view/form_gtk/reference.py @@ -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() diff --git a/tryton/gui/window/view_form/view/form_gtk/richtextbox.py b/tryton/gui/window/view_form/view/form_gtk/richtextbox.py index 69b85847df..d804f6379a 100644 --- a/tryton/gui/window/view_form/view/form_gtk/richtextbox.py +++ b/tryton/gui/window/view_form/view/form_gtk/richtextbox.py @@ -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) diff --git a/tryton/gui/window/view_form/view/form_gtk/selection.py b/tryton/gui/window/view_form/view/form_gtk/selection.py index 09a977b7ae..622944b57c 100644 --- a/tryton/gui/window/view_form/view/form_gtk/selection.py +++ b/tryton/gui/window/view_form/view/form_gtk/selection.py @@ -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() diff --git a/tryton/gui/window/view_form/view/form_gtk/state_widget.py b/tryton/gui/window/view_form/view/form_gtk/state_widget.py new file mode 100644 index 0000000000..a88369b969 --- /dev/null +++ b/tryton/gui/window/view_form/view/form_gtk/state_widget.py @@ -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()) diff --git a/tryton/gui/window/view_form/view/form_gtk/textbox.py b/tryton/gui/window/view_form/view/form_gtk/textbox.py index 176189bf19..19306da54b 100644 --- a/tryton/gui/window/view_form/view/form_gtk/textbox.py +++ b/tryton/gui/window/view_form/view/form_gtk/textbox.py @@ -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() diff --git a/tryton/gui/window/view_form/view/form_gtk/url.py b/tryton/gui/window/view_form/view/form_gtk/url.py index 870e0ff925..9edb0729b5 100644 --- a/tryton/gui/window/view_form/view/form_gtk/url.py +++ b/tryton/gui/window/view_form/view/form_gtk/url.py @@ -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() diff --git a/tryton/gui/window/view_form/view/form_gtk/interface.py b/tryton/gui/window/view_form/view/form_gtk/widget.py similarity index 94% rename from tryton/gui/window/view_form/view/form_gtk/interface.py rename to tryton/gui/window/view_form/view/form_gtk/widget.py index 22ef029413..98f720f2eb 100644 --- a/tryton/gui/window/view_form/view/form_gtk/interface.py +++ b/tryton/gui/window/view_form/view/form_gtk/widget.py @@ -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 diff --git a/tryton/gui/window/view_form/view/graph.py b/tryton/gui/window/view_form/view/graph.py index 850d608ff6..21051305e7 100644 --- a/tryton/gui/window/view_form/view/graph.py +++ b/tryton/gui/window/view_form/view/graph.py @@ -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('' + _('Image Size') + '') + 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() diff --git a/tryton/gui/window/view_form/view/graph_gtk/graph.py b/tryton/gui/window/view_form/view/graph_gtk/graph.py index 8038ff52bb..76a4a05963 100644 --- a/tryton/gui/window/view_form/view/graph_gtk/graph.py +++ b/tryton/gui/window/view_form/view/graph_gtk/graph.py @@ -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() diff --git a/tryton/gui/window/view_form/view/graph_gtk/parser.py b/tryton/gui/window/view_form/view/graph_gtk/parser.py deleted file mode 100644 index 396fb1dd82..0000000000 --- a/tryton/gui/window/view_form/view/graph_gtk/parser.py +++ /dev/null @@ -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('' + _('Image Size') + '') - 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 diff --git a/tryton/gui/window/view_form/view/interface.py b/tryton/gui/window/view_form/view/interface.py deleted file mode 100644 index 051beb56c5..0000000000 --- a/tryton/gui/window/view_form/view/interface.py +++ /dev/null @@ -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 diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index 6a8978d7bd..ef0cf1041b 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -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) diff --git a/tryton/gui/window/view_form/view/list_gtk/__init__.py b/tryton/gui/window/view_form/view/list_gtk/__init__.py index 5218f22bd6..c86640b67f 100644 --- a/tryton/gui/window/view_form/view/list_gtk/__init__.py +++ b/tryton/gui/window/view_form/view/list_gtk/__init__.py @@ -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 * diff --git a/tryton/gui/window/view_form/view/list_gtk/editabletree.py b/tryton/gui/window/view_form/view/list_gtk/editabletree.py index 2f6670172f..eb24be4e85 100644 --- a/tryton/gui/window/view_form/view/list_gtk/editabletree.py +++ b/tryton/gui/window/view_form/view/list_gtk/editabletree.py @@ -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) diff --git a/tryton/gui/window/view_form/view/list_gtk/parser.py b/tryton/gui/window/view_form/view/list_gtk/widget.py similarity index 62% rename from tryton/gui/window/view_form/view/list_gtk/parser.py rename to tryton/gui/window/view_form/view/list_gtk/widget.py index 33426653cf..820d43b6a5 100644 --- a/tryton/gui/window/view_form/view/list_gtk/parser.py +++ b/tryton/gui/window/view_form/view/list_gtk/widget.py @@ -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) diff --git a/tryton/gui/window/view_form/view/widget_parse.py b/tryton/gui/window/view_form/view/widget_parse.py deleted file mode 100644 index 45a33112a3..0000000000 --- a/tryton/gui/window/view_form/view/widget_parse.py +++ /dev/null @@ -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) diff --git a/tryton/gui/window/win_search.py b/tryton/gui/window/win_search.py index c8d20265fc..5ee48c72ca 100644 --- a/tryton/gui/window/win_search.py +++ b/tryton/gui/window/win_search.py @@ -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