From cbec9a9959cdbdfba90a1817671de5d8519ca7fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Fri, 27 Jan 2012 21:17:35 +0100 Subject: [PATCH] Add support for fuzzy_translation and improve translate feature issue2224 review199001 --- CHANGELOG | 1 + .../window/view_form/view/form_gtk/char.py | 26 ++- .../view_form/view/form_gtk/interface.py | 152 +++++++++++++ .../window/view_form/view/form_gtk/parser.py | 199 +----------------- .../window/view_form/view/form_gtk/textbox.py | 38 +++- 5 files changed, 216 insertions(+), 200 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0d7d2eba21..9bea157698 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,4 @@ +* Add support for fuzzy_translation * Activate save button on editing * Refactor set/get and set/get_client of fields * Display binary size alongside the buttons in treeview 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 8baba21d8f..21c5d1f27c 100644 --- a/tryton/gui/window/view_form/view/form_gtk/char.py +++ b/tryton/gui/window/view_form/view/form_gtk/char.py @@ -4,13 +4,13 @@ import gettext import gobject import gtk -from interface import WidgetInterface +from interface import WidgetInterface, TranslateMixin from tryton.common import Tooltips _ = gettext.gettext -class Char(WidgetInterface): +class Char(WidgetInterface, TranslateMixin): "Char" def __init__(self, field_name, model_name, attrs=None): @@ -42,6 +42,26 @@ class Char(WidgetInterface): focus_entry.connect('key-press-event', self.send_modified) self.widget.pack_start(self.entry) + self.button = None + if attrs.get('translate'): + self.button = self.translate_button() + self.widget.pack_start(self.button, False, False) + + def translate_widget(self): + entry = gtk.Entry() + entry.set_property('activates_default', True) + entry.set_width_chars(10) + entry.set_max_length(int(self.attrs.get('size', 0))) + return entry + + @staticmethod + def translate_widget_set(widget, value): + widget.set_text(value) + + @staticmethod + def translate_widget_get(widget): + return widget.get_text() + def _color_widget(self): if self.autocomplete: return self.entry.get_child() @@ -97,6 +117,8 @@ class Char(WidgetInterface): self.entry.set_button_sensitivity(sensitivity[value]) else: self.entry.set_editable(not value) + if self.button: + self.button.set_sensitive(not value) if value: self.widget.set_focus_chain([]) else: diff --git a/tryton/gui/window/view_form/view/form_gtk/interface.py b/tryton/gui/window/view_form/view/form_gtk/interface.py index 2f89db9741..c469287fe2 100644 --- a/tryton/gui/window/view_form/view/form_gtk/interface.py +++ b/tryton/gui/window/view_form/view/form_gtk/interface.py @@ -2,7 +2,16 @@ #this repository contains the full copyright notices and license terms. import gtk import gobject +import gettext + from tryton.common import COLORS +import tryton.common as common +import tryton.rpc as rpc +from tryton.exceptions import TrytonServerError +from tryton.gui.window.nomodal import NoModal +from tryton.common import TRYTON_ICON + +_ = gettext.gettext class WidgetInterface(object): @@ -154,3 +163,146 @@ class WidgetInterface(object): def cancel(self): pass + + +class TranslateDialog(NoModal): + + def __init__(self, widget, languages): + NoModal.__init__(self) + self.widget = widget + self.win = gtk.Dialog(_('Translation'), self.parent, + gtk.DIALOG_DESTROY_WITH_PARENT) + self.win.set_position(gtk.WIN_POS_CENTER_ON_PARENT) + self.win.set_icon(TRYTON_ICON) + self.win.connect('response', self.response) + + self.accel_group = gtk.AccelGroup() + self.win.add_accel_group(self.accel_group) + + self.win.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) + self.win.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK).add_accelerator( + 'clicked', self.accel_group, gtk.keysyms.Return, + gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) + + self.widgets = {} + table = gtk.Table(len(languages), 2) + table.set_homogeneous(False) + table.set_col_spacings(3) + table.set_row_spacings(2) + table.set_border_width(1) + for i, language in enumerate(languages): + if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL: + label = _(':') + language['name'] + else: + label = language['name'] + _(':') + label = gtk.Label(label) + label.set_alignment(1.0, 0.5) + table.attach(label, 0, 1, i, i + 1, xoptions=gtk.FILL) + + context = rpc.CONTEXT.copy() + context['language'] = language['code'] + try: + value = rpc.execute('model', self.widget.record.model_name, + 'read', self.widget.record.id, [self.widget.field_name], + context)[self.widget.field_name] + except TrytonServerError, exception: + common.process_exception(exception) + return + widget = self.widget.translate_widget() + self.widgets[language['code']] = (widget, value) + self.widget.translate_widget_set(widget, value) + table.attach(widget, 1, 2, i, i + 1) + + vbox = gtk.VBox() + vbox.pack_start(table, False, True) + viewport = gtk.Viewport() + viewport.set_shadow_type(gtk.SHADOW_NONE) + viewport.add(vbox) + scrolledwindow = gtk.ScrolledWindow() + scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) + scrolledwindow.set_shadow_type(gtk.SHADOW_NONE) + scrolledwindow.add(viewport) + self.win.vbox.pack_start(scrolledwindow, True, True) + + sensible_allocation = self.sensible_widget.get_allocation() + self.win.set_default_size(int(sensible_allocation.width * 0.9), + int(sensible_allocation.height * 0.9)) + + self.register() + self.win.show_all() + common.center_window(self.win, self.parent, self.sensible_widget) + + def response(self, win, response): + if response == gtk.RESPONSE_OK: + for code, widget in self.widgets.iteritems(): + widget, old_value = widget + new_value = self.widget.translate_widget_get(widget) + if new_value == old_value: + continue + context = rpc.CONTEXT.copy() + context['language'] = code + context['fuzzy_translation'] = False + args = ('model', self.widget.record.model_name, 'write', + self.widget.record.id, { + self.widget.field_name: new_value, + }, context) + try: + rpc.execute(*args) + except TrytonServerError, exception: + common.process_exception(exception, *args) + self.widget.record.cancel() + self.widget.view.display() + self.destroy() + + def destroy(self): + self.win.destroy() + NoModal.destroy(self) + + +class TranslateMixin: + + def translate_button(self): + button = gtk.Button() + img = gtk.Image() + img.set_from_stock('tryton-locale', gtk.ICON_SIZE_SMALL_TOOLBAR) + button.set_image(img) + button.set_relief(gtk.RELIEF_NONE) + button.connect('clicked', self.translate) + return button + + def translate(self, widget): + if self.record.id < 0 or self.record.modified: + common.message( + _('You need to save the record before adding translations!')) + return + + try: + lang_ids = rpc.execute('model', 'ir.lang', 'search', [ + ('translatable', '=', True), + ], rpc.CONTEXT) + except TrytonServerError, exception: + common.process_exception(exception) + return + + if not lang_ids: + common.message(_('No other language available!')) + return + try: + languages = rpc.execute('model', 'ir.lang', 'read', lang_ids, + ['code', 'name'], rpc.CONTEXT) + except TrytonServerError, exception: + common.process_exception(exception) + return + + TranslateDialog(self, languages) + + def translate_widget(self): + raise NotImplemented + + @staticmethod + def translate_widget_set(widget, value): + raise NotImplemented + + @staticmethod + def translate_widget_get(widget): + raise NotImplemented diff --git a/tryton/gui/window/view_form/view/form_gtk/parser.py b/tryton/gui/window/view_form/view/form_gtk/parser.py index cd08b607fc..bf65e10069 100644 --- a/tryton/gui/window/view_form/view/form_gtk/parser.py +++ b/tryton/gui/window/view_form/view/form_gtk/parser.py @@ -2,13 +2,10 @@ #this repository contains the full copyright notices and license terms. import gtk import gettext -import copy import logging -import tryton.rpc as rpc from tryton.gui.window.view_form.view.interface import ParserInterface import tryton.common as common -from tryton.config import CONFIG, TRYTON_ICON -from tryton.exceptions import TrytonServerError +from tryton.config import CONFIG from tryton.common.button import Button _ = gettext.gettext @@ -129,7 +126,6 @@ class _container(object): self.cont = [] self.col = [] self.tooltips = tooltips - self.trans_box = [] def new(self, col=4): table = gtk.Table(1, col) @@ -155,7 +151,7 @@ class _container(object): table.resize(height + 1, self.col[-1]) def wid_add(self, widget, name='', yexpand=False, ypadding=2, rowspan=1, - colspan=1, translate=False, fname=None, help_tip=False, + 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]: @@ -176,15 +172,6 @@ class _container(object): if help_tip: self.tooltips.set_tip(widget, help_tip) self.tooltips.enable() - if translate: - button = gtk.Button() - img = gtk.Image() - img.set_from_stock('tryton-locale', gtk.ICON_SIZE_SMALL_TOOLBAR) - button.set_image(img) - button.set_relief(gtk.RELIEF_NONE) - self.trans_box.append((button, name, fname, - widget.get_children()[0])) - widget.get_child().pack_start(button, fill=False, expand=False) widget.show_all() table.attach(widget, width, width + colspan, height, height + rowspan, @@ -454,7 +441,7 @@ class ParserForm(ParserInterface): for attr_name in ('relation', 'domain', 'selection', 'relation_field', 'string', 'views', 'invisible', 'add_remove', 'sort', 'context', 'size', 'filename', - 'autocomplete'): + 'autocomplete', 'translate'): if attr_name in fields[name].attrs and \ not attr_name in attrs: attrs[attr_name] = fields[name].attrs[attr_name] @@ -471,13 +458,9 @@ class ParserForm(ParserInterface): widget_act.widget.set_size_request( int(attrs.get('width', -1)), int(attrs.get('height', -1))) - translate = False - if ftype in ('char', 'text'): - translate = fields[name].attrs.get('translate', False) container.wid_add(Alignment(widget_act.widget, attrs), fields[name].attrs['string'], fname=name, help_tip=hlp, - translate=translate, colspan=colspan, yexpand=yexpand, yfill=yfill, xexpand=xexpand, xfill=xfill) @@ -559,185 +542,9 @@ class ParserForm(ParserInterface): paned.pack1(widget, resize=True, shrink=True) elif not paned.get_child2(): paned.pack2(widget, resize=True, shrink=True) - for (button, src, name, widget) in container.trans_box: - button.connect('clicked', self.translate, model_name, name, - src, widget) return container.pop(), dict_widget, button_list, on_write, \ notebook_list, cursor_widget - def translate(self, widget, model_name, name, src, widget_entry): - obj_id = self.screen.current_record.id - if obj_id < 0: - common.message( - _('You need to save the record before adding translations!')) - return False - - obj_id = self.screen.current_record.save(force_reload=False) - try: - lang_ids = rpc.execute('model', 'ir.lang', - 'search', [('translatable', '=', '1')], - rpc.CONTEXT) - except TrytonServerError, exception: - common.process_exception(exception) - return False - - if not lang_ids: - common.message(_('No other language available!')) - return False - try: - lang_ids += rpc.execute('model', 'ir.lang', - 'search', [('code', '=', 'en_US')], - rpc.CONTEXT) - langs = rpc.execute('model', 'ir.lang', - 'read', lang_ids, ['code', 'name'], - rpc.CONTEXT) - except TrytonServerError, exception: - common.process_exception(exception) - return False - - code = rpc.CONTEXT.get('language', 'en_US') - - widget_entry = widget_entry.get_children()[0] - if isinstance(widget_entry, gtk.ScrolledWindow): - widget_entry = widget_entry.get_child() - - #widget accessor functions - def value_get(widget): - if isinstance(widget, gtk.Entry): - return widget.get_text() - elif isinstance(widget, gtk.TextView): - buf = widget.get_buffer() - iter_start = buf.get_start_iter() - iter_end = buf.get_end_iter() - return buf.get_text(iter_start, iter_end, False) - else: - return None - - def value_set(widget, value): - if not value: - value = '' - if isinstance(widget, gtk.Entry): - widget.set_text(value) - elif isinstance(widget, gtk.TextView): - buf = widget.get_buffer() - buf.delete(buf.get_start_iter(), buf.get_end_iter()) - iter_start = buf.get_start_iter() - buf.insert(iter_start, value) - - def widget_duplicate(widget): - if isinstance(widget, gtk.Entry): - entry = gtk.Entry() - entry.set_property('activates_default', True) - entry.set_max_length(widget.get_max_length()) - entry.set_width_chars(widget.get_width_chars()) - return entry, gtk.FILL - elif isinstance(widget, gtk.TextView): - textview = gtk.TextView() - textview.set_wrap_mode(gtk.WRAP_WORD) - scrolledwindow = gtk.ScrolledWindow() - scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, - gtk.POLICY_ALWAYS) - scrolledwindow.set_shadow_type(gtk.SHADOW_NONE) - scrolledwindow.set_size_request(-1, 80) - scrolledwindow.add(textview) - textview.set_accepts_tab(False) - return scrolledwindow, gtk.FILL | gtk.EXPAND - else: - return None, False - - parent = common.get_toplevel_window() - win = gtk.Dialog(_('Add Translation'), parent, - gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) - win.set_has_separator(True) - win.vbox.set_spacing(5) - win.set_property('default-width', 600) - win.set_property('default-height', 400) - win.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - win.set_icon(TRYTON_ICON) - - accel_group = gtk.AccelGroup() - win.add_accel_group(accel_group) - - win.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL) - - but_ok = win.add_button(gtk.STOCK_OK, gtk.RESPONSE_OK) - but_ok.add_accelerator('clicked', accel_group, gtk.keysyms.Return, - gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE) - - vbox = gtk.VBox(spacing=5) - - entries_list = [] - table = gtk.Table(len(langs), 2) - table.set_homogeneous(False) - table.set_col_spacings(3) - table.set_row_spacings(0) - table.set_border_width(1) - i = 0 - for lang in langs: - context = copy.copy(rpc.CONTEXT) - context['language'] = lang['code'] - try: - val = rpc.execute('model', model_name, - 'read', [obj_id], [name], context) - except TrytonServerError, exception: - common.process_exception(exception) - return False - val = val[0] - if gtk.widget_get_default_direction() == gtk.TEXT_DIR_RTL: - label = gtk.Label(_(':') + lang['name']) - else: - label = gtk.Label(lang['name'] + _(':')) - label.set_alignment(1.0, 0.5) - (entry, yoptions) = widget_duplicate(widget_entry) - - if code == lang['code']: - value_set(entry, value_get(widget_entry)) - else: - value_set(entry, val[name]) - - entries_list.append((val['id'], lang['code'], entry)) - table.attach(label, 0, 1, i, i + 1, yoptions=False, - xoptions=gtk.FILL, ypadding=2, xpadding=3) - table.attach(entry, 1, 2, i, i + 1, yoptions=yoptions, - ypadding=2, xpadding=3) - i += 1 - - vbox.pack_start(table) - viewport = gtk.Viewport() - viewport.set_shadow_type(gtk.SHADOW_NONE) - viewport.add(vbox) - scrolledwindow = gtk.ScrolledWindow() - scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, - gtk.POLICY_AUTOMATIC) - scrolledwindow.set_shadow_type(gtk.SHADOW_NONE) - scrolledwindow.add(viewport) - win.vbox.add(scrolledwindow) - win.show_all() - - response = win.run() - if response == gtk.RESPONSE_OK: - to_save = [(x[0], x[1], value_get(x[2])) for x in entries_list] - while to_save != []: - new_val = {} - new_val['id'], new_val['code'], new_val['value'] = \ - to_save.pop() - #update form field - if new_val['code'] == code: - value_set(widget_entry, new_val['value']) - context = copy.copy(rpc.CONTEXT) - context['language'] = new_val['code'] - args = ('model', model_name, 'write', [obj_id], - {str(name): new_val['value']}, context) - try: - rpc.execute(*args) - except TrytonServerError, exception: - common.process_exception(exception, *args) - if response != gtk.RESPONSE_CANCEL: - self.screen.current_record.reload() - win.destroy() - parent.present() - return response != gtk.RESPONSE_CANCEL - from calendar import Calendar, DateTime from float import Float from integer import Integer 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 e496b6976d..21616a7e01 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 +from interface import WidgetInterface, TranslateMixin from tryton.config import CONFIG try: @@ -10,7 +10,7 @@ except ImportError: gtkspell = None -class TextBox(WidgetInterface): +class TextBox(WidgetInterface, TranslateMixin): def __init__(self, field_name, model_name, attrs=None): super(TextBox, self).__init__(field_name, model_name, attrs=attrs) @@ -36,12 +36,46 @@ class TextBox(WidgetInterface): self.widget.pack_start(self.scrolledwindow) self.lang = None + self.button = None + if attrs.get('translate'): + self.button = self.translate_button() + self.widget.pack_start(self.button, False, False) + + def translate_widget(self): + scrolledwindow = gtk.ScrolledWindow() + scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, + gtk.POLICY_AUTOMATIC) + scrolledwindow.set_shadow_type(gtk.SHADOW_ETCHED_IN) + scrolledwindow.set_size_request(-1, 80) + + textview = gtk.TextView() + textview.set_wrap_mode(gtk.WRAP_WORD) + textview.set_accepts_tab(False) + + scrolledwindow.add(textview) + return scrolledwindow + + @staticmethod + def translate_widget_set(widget, value): + textview = widget.get_child() + buf = textview.get_buffer() + buf.delete(buf.get_start_iter(), buf.get_end_iter()) + buf.insert(buf.get_start_iter(), value or '') + + @staticmethod + def translate_widget_get(widget): + textview = widget.get_child() + buf = textview.get_buffer() + return buf.get_text(buf.get_start_iter(), buf.get_end_iter(), False) + def grab_focus(self): return self.textview.grab_focus() def _readonly_set(self, value): super(TextBox, self)._readonly_set(value) self.textview.set_editable(not value) + if self.button: + self.button.set_sensitive(not value) if value: self.widget.set_focus_chain([]) else: