From a88a25ee63169af9b68dadd279f2f6da4c32f068 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Fri, 18 Mar 2011 15:07:21 +0100 Subject: [PATCH 01/15] Fix unpassing test --- tryton/common/domain_inversion.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tryton/common/domain_inversion.py b/tryton/common/domain_inversion.py index 85078ac536..5004ead4ba 100644 --- a/tryton/common/domain_inversion.py +++ b/tryton/common/domain_inversion.py @@ -349,7 +349,7 @@ def test_simplify(): domain = ['OR', [['x', '=', 3]], [['y', '=', 5]]] assert simplify(domain) == ['OR', [['x', '=', 3]], [['y', '=', 5]]] domain = ['OR', ['x', '=', 3], ['AND', ['y', '=', 5]]] - assert simplify(domain) == ['OR', ['x', '=', 3], ['y', '=', 5]] + assert simplify(domain) == ['OR', ['x', '=', 3], [['y', '=', 5]]] def test_merge(): domain = [['x', '=', 6], ['y', '=', 7]] From 4bba8812ad1318e83d8c8d759022d195556fe57c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 21 Mar 2011 17:51:38 +0100 Subject: [PATCH 02/15] Allow to use profile combobox with keyboard --- tryton/gui/window/dblogin.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tryton/gui/window/dblogin.py b/tryton/gui/window/dblogin.py index 65abe999f4..bc39d07bae 100644 --- a/tryton/gui/window/dblogin.py +++ b/tryton/gui/window/dblogin.py @@ -389,6 +389,8 @@ class DBLogin(object): self.combo_profile.add_attribute(cell, 'sensitive', 1) self.combo_profile.set_model(self.profile_store) self.combo_profile.connect('changed', self.profile_changed) + self.combo_profile.connect('move-active', self.profile_move_active) + self.move_active = False self.profile_label = gtk.Label(_(u'Profile:')) self.profile_label.set_justify(gtk.JUSTIFY_RIGHT) self.profile_label.set_alignment(1, 0.5) @@ -456,10 +458,16 @@ class DBLogin(object): username = '' if username: self.entry_login.set_text(username) - self.entry_password.grab_focus() + focus_widget = self.entry_password else: self.entry_login.set_text('') - self.entry_login.grab_focus() + focus_widget = self.entry_login + if not self.move_active: + focus_widget.grab_focus() + self.move_active = False + + def profile_move_active(self, combobox, scrolltype): + self.move_active = True def run(self, profile_name, parent): if not profile_name: From 142bc8fdf8c1734995ac2b028d9d451048d4ea9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 21 Mar 2011 17:57:09 +0100 Subject: [PATCH 03/15] Fix cleaning of removed/deleted records introduced with changeset 9ece47c60d69 It is not possible to use set with Record as we can not provide a constant hash because the id can change --- tryton/gui/window/view_form/model/group.py | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tryton/gui/window/view_form/model/group.py b/tryton/gui/window/view_form/model/group.py index 5454e608cf..79ace09a2d 100644 --- a/tryton/gui/window/view_form/model/group.py +++ b/tryton/gui/window/view_form/model/group.py @@ -155,25 +155,16 @@ class Group(SignalEvent, list): return list({}.fromkeys(res)) def load(self, ids, display=True, modified=False): - if not ids: - return True - # Work with sets to compute intersection and set difference - set_records = set(self.get(x) for x in ids) - for record in set_records & set(self.record_removed): - self.record_removed.remove(record) - for record in set_records & set(self.record_deleted): - self.record_deleted.remove(record) - - set_records -= set(x for x in self) - # return on the list form in order to preserver ordering - ids = [x for x in ids if self.get(x) in set_records] if not ids: return True if len(ids) > 1: self.lock_signal = True + new_records = [] for id in ids: + if self.get(id): + continue new_record = Record(self.model_name, id, self.window, parent=self.parent, parent_name=self.parent_name, group=self) self.append(new_record) @@ -182,6 +173,15 @@ class Group(SignalEvent, list): self._record_changed) new_record.signal_connect(self, 'record-modified', self._record_modified) + + # Remove previously removed or deleted records + for record in self.record_removed[:]: + if record.id in ids: + self.record_removed.remove(record) + for record in self.record_deleted[:]: + if record.id in ids: + self.record_deleted.remove(record) + if self.lock_signal: self.lock_signal = False self.signal('group-cleared') From 693ddd4859248bbc748492d8dd1d18d0b3976d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Mon, 21 Mar 2011 18:14:58 +0100 Subject: [PATCH 04/15] Add open button on binary and image widgets Hide buttons on image widget if readonly Use the same behavior for binary and image widgets --- CHANGELOG | 2 + .../window/view_form/view/form_gtk/binary.py | 101 +++++++++----- .../window/view_form/view/form_gtk/image.py | 124 ++++++++++++------ 3 files changed, 154 insertions(+), 73 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 65fb7251b3..0539bd9a2a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,5 @@ +* Add open button on binary and image widgets +* Hide buttons on image widget if readonly * Added a connection manager à la gajim * Fix warning in wizards * Added possibility to use server-side icons 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 560e9ae312..d9d4c19a78 100644 --- a/tryton/gui/window/view_form/view/form_gtk/binary.py +++ b/tryton/gui/window/view_form/view/form_gtk/binary.py @@ -1,10 +1,12 @@ #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 __future__ import with_statement import base64 import gtk import gettext import os -from tryton.common import file_selection, message, warning, Tooltips +import tempfile +from tryton.common import file_selection, message, warning, Tooltips, file_open from interface import WidgetInterface _ = gettext.gettext @@ -45,13 +47,25 @@ class Binary(WidgetInterface): self.but_new = gtk.Button() img_new = gtk.Image() - img_new.set_from_stock('tryton-open', gtk.ICON_SIZE_SMALL_TOOLBAR) + img_new.set_from_stock('tryton-find', gtk.ICON_SIZE_SMALL_TOOLBAR) self.but_new.set_image(img_new) self.but_new.set_relief(gtk.RELIEF_NONE) self.but_new.connect('clicked', self.sig_new) - self.tooltips.set_tip(self.but_new, _('Select a File')) + self.tooltips.set_tip(self.but_new, _('Select a File...')) self.widget.pack_start(self.but_new, expand=False, fill=False) + if self.filename: + self.but_open = gtk.Button() + img_open = gtk.Image() + img_open.set_from_stock('tryton-open', gtk.ICON_SIZE_SMALL_TOOLBAR) + self.but_open.set_image(img_open) + self.but_open.set_relief(gtk.RELIEF_NONE) + self.but_open.connect('clicked', self.sig_open) + self.tooltips.set_tip(self.but_open, _('Open...')) + self.widget.pack_start(self.but_open, expand=False, fill=False) + else: + self.but_open = None + self.but_save_as = gtk.Button() img_save_as = gtk.Image() img_save_as.set_from_stock('tryton-save-as', @@ -73,6 +87,10 @@ class Binary(WidgetInterface): self.tooltips.enable() + @property + def filename_field(self): + return self.record.group.fields.get(self.filename) + def _readonly_set(self, value): if value: self.but_new.hide() @@ -83,6 +101,9 @@ class Binary(WidgetInterface): self.but_remove.show() if self.wid_text: focus_chain = [self.wid_text] + elif self.filename: + focus_chain = [self.but_new, self.but_open, self.but_save_as, + self.but_remove] else: focus_chain = [self.but_new, self.but_save_as, self.but_remove] self.widget.set_focus_chain(focus_chain) @@ -94,36 +115,42 @@ class Binary(WidgetInterface): return self.wid_size.grab_focus() def sig_new(self, widget=None): - try: - filename = file_selection(_('Open...'), - parent=self.window) - if filename and self.field: - self.field.set_client(self.record, - base64.encodestring(open(filename, 'rb').read())) - if self.filename and self.record: - name_wid = self.record.group.fields[self.filename] - name_wid.set_client(self.record, os.path.basename(filename)) - self.display(self.record, self.field) - except Exception, exception: - warning(_('Error reading the file.\nError message:\n%s') \ - % str(exception), self.window, _('Error')) + filename = file_selection(_('Open...'), + parent=self.window) + if filename and self.field: + self.field.set_client(self.record, + base64.encodestring(open(filename, 'rb').read())) + if self.filename_field: + self.filename_field.set_client(self.record, + os.path.basename(filename)) + self.display(self.record, self.field) + + def sig_open(self, widget=None): + if not self.filename_field: + return + dtemp = tempfile.mkdtemp(prefix='tryton_') + filename = self.filename_field.get(self.record).replace( + os.sep, '_').replace(os.altsep or os.sep, '_') + file_path = os.path.join(dtemp, filename) + with open(file_path, 'wb') as fp: + fp.write(base64.decodestring(self.field.get(self.record))) + root, type_ = os.path.splitext(filename) + if type_: + type_ = type_[1:] + file_open(file_path, type_, self.window) def sig_save_as(self, widget=None): - try: - filename = file_selection(_('Save As...'), - parent=self.window, action=gtk.FILE_CHOOSER_ACTION_SAVE) - if filename and self.field: - file_p = open(filename,'wb+') - file_p.write(base64.decodestring( - self.field.get(self.record))) - file_p.close() - except Exception, exception: - warning(_('Error writing the file.\nError message:\n%s') \ - % str(exception), self.window, _('Error')) + filename = '' + if self.filename_field: + filename = self.filename_field.get(self.record) + filename = file_selection(_('Save As...'), filename=filename, + parent=self.window, action=gtk.FILE_CHOOSER_ACTION_SAVE) + if filename: + with open(filename,'wb') as fp: + fp.write(base64.decodestring(self.field.get(self.record))) def sig_remove(self, widget=None): - if self.field: - self.field.set_client(self.record, False) + self.field.set_client(self.record, False) self.display(self.record, self.field) def sig_key_press(self, widget, event, *args): @@ -132,7 +159,10 @@ class Binary(WidgetInterface): self.sig_new(widget) return True elif event.keyval == gtk.keysyms.F2: - self.sig_save_as(widget) + if self.filename: + self.sig_open(widget) + else: + self.sig_save_as(widget) return True return False @@ -142,12 +172,15 @@ class Binary(WidgetInterface): if self.wid_text: self.wid_text.set_text('') self.wid_size.set_text('') + if self.but_open: + self.but_open.set_sensitive(False) self.but_save_as.set_sensitive(False) return False if self.wid_text: - name_wid = record.group.fields[self.filename] - self.wid_text.set_text(name_wid.get(record) or '') + self.wid_text.set_text(self.filename_field.get(record) or '') self.wid_size.set_text(humanize(len(field.get(record) or []))) + if self.but_open: + self.but_open.set_sensitive(bool(field.get(record))) self.but_save_as.set_sensitive(bool(field.get(record))) return True @@ -159,8 +192,8 @@ class Binary(WidgetInterface): def set_value(self, record, field): if self.wid_text: - name_wid = self.record.group.fields[self.filename] - name_wid.set_client(self.record, self.wid_text.get_text() or False) + self.filename_field.set_client(self.record, + self.wid_text.get_text() or False) return def _color_widget(self): 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 36c722b8f0..124149bf99 100644 --- a/tryton/gui/window/view_form/view/form_gtk/image.py +++ b/tryton/gui/window/view_form/view/form_gtk/image.py @@ -1,10 +1,12 @@ #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 __future__ import with_statement import gtk import gettext import os +import tempfile from base64 import encodestring, decodestring -from tryton.common import file_selection, Tooltips +from tryton.common import file_selection, Tooltips, file_open from tryton.config import PIXMAPS_DIR from interface import WidgetInterface import urllib @@ -39,37 +41,55 @@ class Image(WidgetInterface): self.event.add(self.image) self.widget.pack_start(self.event, expand=True, fill=True) - self.alignment = gtk.Alignment(xalign=0.5, yalign=0.5) - self.hbox = gtk.HBox(spacing=0) - self.but_add = gtk.Button() - img_add = gtk.Image() - img_add.set_from_stock('tryton-open', gtk.ICON_SIZE_SMALL_TOOLBAR) - self.but_add.set_image(img_add) - self.but_add.set_relief(gtk.RELIEF_NONE) - self.but_add.connect('clicked', self.sig_add) - self.tooltips.set_tip(self.but_add, _('Set Image')) - self.hbox.pack_start(self.but_add, expand=False, fill=False) + if not attrs.get('readonly'): + alignment = gtk.Alignment(xalign=0.5, yalign=0.5) + hbox = gtk.HBox(spacing=0) + self.but_add = gtk.Button() + img_add = gtk.Image() + img_add.set_from_stock('tryton-find', gtk.ICON_SIZE_SMALL_TOOLBAR) + self.but_add.set_image(img_add) + self.but_add.set_relief(gtk.RELIEF_NONE) + self.but_add.connect('clicked', self.sig_add) + self.tooltips.set_tip(self.but_add, _('Select an Image...')) + hbox.pack_start(self.but_add, expand=False, fill=False) - self.but_save_as = gtk.Button() - img_save_as = gtk.Image() - img_save_as.set_from_stock('tryton-save', gtk.ICON_SIZE_SMALL_TOOLBAR) - self.but_save_as.set_image(img_save_as) - self.but_save_as.set_relief(gtk.RELIEF_NONE) - self.but_save_as.connect('clicked', self.sig_save_as) - self.tooltips.set_tip(self.but_save_as, _('Save As')) - self.hbox.pack_start(self.but_save_as, expand=False, fill=False) + if self.filename: + self.but_open = gtk.Button() + img_open = gtk.Image() + img_open.set_from_stock('tryton-open', gtk.ICON_SIZE_SMALL_TOOLBAR) + self.but_open.set_image(img_open) + self.but_open.set_relief(gtk.RELIEF_NONE) + self.but_open.connect('clicked', self.sig_open) + self.tooltips.set_tip(self.but_open, _('Open...')) + hbox.pack_start(self.but_open, expand=False, fill=False) + else: + self.but_open = None - self.but_remove = gtk.Button() - img_remove = gtk.Image() - img_remove.set_from_stock('tryton-clear', gtk.ICON_SIZE_SMALL_TOOLBAR) - self.but_remove.set_image(img_remove) - self.but_remove.set_relief(gtk.RELIEF_NONE) - self.but_remove.connect('clicked', self.sig_remove) - self.tooltips.set_tip(self.but_remove, _('Clear')) - self.hbox.pack_start(self.but_remove, expand=False, fill=False) + self.but_save_as = gtk.Button() + img_save_as = gtk.Image() + img_save_as.set_from_stock('tryton-save', gtk.ICON_SIZE_SMALL_TOOLBAR) + self.but_save_as.set_image(img_save_as) + self.but_save_as.set_relief(gtk.RELIEF_NONE) + self.but_save_as.connect('clicked', self.sig_save_as) + self.tooltips.set_tip(self.but_save_as, _('Save As...')) + hbox.pack_start(self.but_save_as, expand=False, fill=False) - self.alignment.add(self.hbox) - self.widget.pack_start(self.alignment, expand=False, fill=False) + self.but_remove = gtk.Button() + img_remove = gtk.Image() + img_remove.set_from_stock('tryton-clear', gtk.ICON_SIZE_SMALL_TOOLBAR) + self.but_remove.set_image(img_remove) + self.but_remove.set_relief(gtk.RELIEF_NONE) + self.but_remove.connect('clicked', self.sig_remove) + self.tooltips.set_tip(self.but_remove, _('Clear')) + hbox.pack_start(self.but_remove, expand=False, fill=False) + + alignment.add(hbox) + self.widget.pack_start(alignment, expand=False, fill=False) + else: + self.but_add = None + self.but_open = None + self.but_save_as = None + self.but_remove = None self.tooltips.enable() @@ -77,14 +97,23 @@ class Image(WidgetInterface): self.update_img() + @property + def filename_field(self): + return self.record.group.fields.get(self.filename) + def grab_focus(self): return self.image.grab_focus() def _readonly_set(self, value): self._readonly = value - self.but_add.set_sensitive(not value) - self.but_save_as.set_sensitive(not value) - self.but_remove.set_sensitive(not value) + if self.but_add: + self.but_add.set_sensitive(not value) + if self.but_open: + self.but_open.set_sensitive(not value) + if self.but_save_as: + self.but_save_as.set_sensitive(not value) + if self.but_remove: + self.but_remove.set_sensitive(not value) def sig_add(self, widget): filter_all = gtk.FileFilter() @@ -103,17 +132,34 @@ class Image(WidgetInterface): if filename: self.field.set_client(self.record, encodestring(open(filename, 'rb').read())) - if self.filename and self.record: - name_wid = self.record.group.fields[self.filename] - name_wid.set_client(self.record, os.path.basename(filename)) + if self.filename_field: + self.filename_field.set_client(self.record, + os.path.basename(filename)) self.update_img() + def sig_open(self, widget=None): + if not self.filename_field: + return + dtemp = tempfile.mkdtemp(prefix='tryton_') + filename = self.filename_field.get(self.record).replace( + os.sep, '_').replace(os.altsep or os.sep, '_') + file_path = os.path.join(dtemp, filename) + with open(file_path, 'wb') as fp: + fp.write(decodestring(self.field.get(self.record))) + root, type_ = os.path.splitext(filename) + if type_: + type_ = type_[1:] + file_open(file_path, type_, self.window) + def sig_save_as(self, widget): - filename = file_selection(_('Save As...'), parent=self.window, - action=gtk.FILE_CHOOSER_ACTION_SAVE) + filename = '' + if self.filename_field: + filename = self.filename_field.get(self.record) + filename = file_selection(_('Save As...'), filename=filename, + parent=self.window, action=gtk.FILE_CHOOSER_ACTION_SAVE) if filename: - open(filename, 'wb').write(decodestring( - self.field.get_client(self.record))) + with open(filename, 'wb') as fp: + fp.write(decodestring(self.field.get(self.record))) def sig_remove(self, widget): self.field.set_client(self.record, False) From 1de4f131f08c425b5867896ba0a9c779c8b830da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Tue, 22 Mar 2011 12:07:16 +0100 Subject: [PATCH 05/15] Use new pyson_email on report --- tryton/gui/window/view_form/view/form.py | 5 +++-- tryton/gui/window/view_form/view/list.py | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tryton/gui/window/view_form/view/form.py b/tryton/gui/window/view_form/view/form.py index 2c9e351b54..3a4599d9ca 100644 --- a/tryton/gui/window/view_form/view/form.py +++ b/tryton/gui/window/view_form/view/form.py @@ -121,8 +121,9 @@ class ViewForm(ParserView): 'to be able to use the action button!'), self.window) return False email = {} - if 'email' in action: - email = self.screen.current_record.expr_eval(action['email']) + if 'pyson_email' in action: + email = self.screen.current_record.expr_eval( + action['pyson_email']) if not email: email = {} email['subject'] = action['name'].replace('_', '') diff --git a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py index b2aeac8917..f0e04915b2 100644 --- a/tryton/gui/window/view_form/view/list.py +++ b/tryton/gui/window/view_form/view/list.py @@ -285,8 +285,8 @@ class ViewList(ParserView): message(_('No record selected!'), self.window) return False email = {} - if action.get('email'): - email = self.screen.current_record.expr_eval(action['email']) + if action.get('pyson_email'): + email = self.screen.current_record.expr_eval(action['pyson_email']) if not email: email = {} email['subject'] = action['name'].replace('_', '') From b27404e8a8e835495c3dc6f18e8b8b43a23af419 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 23 Mar 2011 14:56:54 +0100 Subject: [PATCH 06/15] gdk.pixbuf_new_from_file requires an unicode on windows --- tryton/common/common.py | 7 ++++--- tryton/config.py | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/tryton/common/common.py b/tryton/common/common.py index 9190b73c02..c31d490c84 100644 --- a/tryton/common/common.py +++ b/tryton/common/common.py @@ -58,7 +58,7 @@ class TrytonIconFactory(gtk.IconFactory): continue try: pixbuf = gtk.gdk.pixbuf_new_from_file( - os.path.join(PIXMAPS_DIR, fname)) + os.path.join(PIXMAPS_DIR, fname).encode('utf-8')) except Exception: continue icon_set = gtk.IconSet(pixbuf) @@ -103,7 +103,7 @@ class TrytonIconFactory(gtk.IconFactory): fileno, path = tempfile.mkstemp() with os.fdopen(fileno, 'w') as svgfile: svgfile.write(icon['icon']) - pixbuf = gtk.gdk.pixbuf_new_from_file(path) + pixbuf = gtk.gdk.pixbuf_new_from_file(path.encode('utf-8')) os.remove(path) iconset = gtk.IconSet(pixbuf) self.add(icon['name'], iconset) @@ -309,7 +309,8 @@ def file_selection(title, filename='', parent=None, def update_preview_cb(win, img): filename = win.get_preview_filename() try: - pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128) + pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename.encode('utf-8'), + 128, 128) img.set_from_pixbuf(pixbuf) have_preview = True except Exception: diff --git a/tryton/config.py b/tryton/config.py index e0944aa718..d41373afed 100644 --- a/tryton/config.py +++ b/tryton/config.py @@ -187,7 +187,7 @@ if not os.path.isdir(PIXMAPS_DIR): PIXMAPS_DIR = os.path.join(PREFIX, 'share', 'pixmaps', 'tryton') TRYTON_ICON = gtk.gdk.pixbuf_new_from_file( - os.path.join(PIXMAPS_DIR, 'tryton-icon.png')) + os.path.join(PIXMAPS_DIR, 'tryton-icon.png').encode('utf-8')) def _data_dir(): data_dir = os.path.join(CURRENT_DIR, 'share', 'tryton') From 9a853aea8dff7ec96af00a351ae78bda1cc44a03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 23 Mar 2011 15:03:09 +0100 Subject: [PATCH 07/15] Disallow to close wizard with Esc and remove close button also on windows We must force the user to follow the flow of the wizard --- tryton/wizard/main.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tryton/wizard/main.py b/tryton/wizard/main.py index 2a51421478..450ad15cfd 100644 --- a/tryton/wizard/main.py +++ b/tryton/wizard/main.py @@ -25,8 +25,9 @@ class Dialog(object): self.dia = gtk.Dialog(_('Wizard'), parent, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT) self.dia.set_position(gtk.WIN_POS_CENTER_ON_PARENT) - if hasattr(self.dia, 'set_deletable') and os.name != 'nt': + if hasattr(self.dia, 'set_deletable'): self.dia.set_deletable(False) + self.dia.connect('close', self.close) self.accel_group = gtk.AccelGroup() self.dia.add_accel_group(self.accel_group) @@ -145,6 +146,10 @@ class Dialog(object): self.parent.present() return False + def close(self, widget, event=None): + widget.emit_stop_by_name('close') + return True + def destroy(self): self.dia.destroy() From fec8134c4058ee7f290ded1325d17a06bc7bf378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 23 Mar 2011 16:17:04 +0100 Subject: [PATCH 08/15] Improve speed of dashboard by preventing the load of other views --- tryton/gui/window/view_board/action.py | 9 ++++----- tryton/gui/window/view_form/screen/screen.py | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tryton/gui/window/view_board/action.py b/tryton/gui/window/view_board/action.py index cfff94e34c..d41d408252 100644 --- a/tryton/gui/window/view_board/action.py +++ b/tryton/gui/window/view_board/action.py @@ -143,11 +143,10 @@ class Action(SignalEvent): self.widget.show_all() if self.action['view_type'] == 'form': - self.screen = Screen(self.action['res_model'], self._window, - view_type=self.action['view_mode'], context=self.context, - view_ids=view_ids, domain=self.domain, readonly=True) - self.screen.screen_container.alternate_view = True - self.screen.switch_view(view_type=self.action['view_mode']) + self.screen = Screen(self.action['res_model'], + self._window, view_type=self.action['view_mode'], + context=self.context, view_ids=view_ids, + domain=self.domain, readonly=True, alternate_view=True) alignment.add(self.screen.screen_container.alternate_viewport) name = self.screen.current_view.title self.screen.signal_connect(self, 'record-message', self._sig_label) diff --git a/tryton/gui/window/view_form/screen/screen.py b/tryton/gui/window/view_form/screen/screen.py index a9889be6f8..d3a0ea98e1 100644 --- a/tryton/gui/window/view_form/screen/screen.py +++ b/tryton/gui/window/view_form/screen/screen.py @@ -20,7 +20,7 @@ class Screen(SignalEvent): def __init__(self, model_name, window, view_ids=None, view_type=None, context=None, views_preload=None, domain=None, row_activate=None, limit=None, readonly=False, exclude_field=None, sort=None, - search_value=None): + search_value=None, alternate_view=False): if view_ids is None: view_ids = [] if view_type is None: @@ -54,6 +54,7 @@ class Screen(SignalEvent): self.__current_record = None self.current_record = None self.screen_container = ScreenContainer() + self.screen_container.alternate_view = alternate_view self.filter_widget = None self.widget = self.screen_container.widget_get() self.__current_view = 0 From c27af4f1f1a15a85e66fc57da5abee4c7f62e02a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Wed, 23 Mar 2011 16:23:42 +0100 Subject: [PATCH 09/15] Set watch cursor on RPCProgress --- tryton/common/common.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tryton/common/common.py b/tryton/common/common.py index c31d490c84..4081c89435 100644 --- a/tryton/common/common.py +++ b/tryton/common/common.py @@ -1105,11 +1105,12 @@ class RPCProgress(object): def run(self): thread.start_new_thread(self.start, ()) + watch = gtk.gdk.Cursor(gtk.gdk.WATCH) + self.parent.window.set_cursor(watch) i = 0 win = None progressbar = None while (not self.res) and (not self.error): - time.sleep(0.1) i += 1 if i > 10: if not win or not progressbar: @@ -1146,11 +1147,14 @@ class RPCProgress(object): win.set_transient_for(self.parent) win.set_modal(True) win.show_all() + win.window.set_cursor(watch) with gtk.gdk.lock: progressbar.pulse() with gtk.gdk.lock: while gtk.events_pending(): gtk.main_iteration() + time.sleep(0.1) + self.parent.window.set_cursor(None) if win: win.destroy() while gtk.events_pending(): From 9441deda6a76bd063e3050e2ade8a220494157ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 27 Mar 2011 16:32:51 +0200 Subject: [PATCH 10/15] Setting sequence must be done as client and not internal --- tryton/gui/window/view_form/model/group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tryton/gui/window/view_form/model/group.py b/tryton/gui/window/view_form/model/group.py index 79ace09a2d..b6cee4c6fa 100644 --- a/tryton/gui/window/view_form/model/group.py +++ b/tryton/gui/window/view_form/model/group.py @@ -234,7 +234,7 @@ class Group(SignalEvent, list): if record[field]: if index >= record[field].get(record): index += 1 - record[field].set(record, index, modified=True) + record[field].set_client(record, index) else: index = record[field].get(record) From 6961fe94d2524cf2cc41609c63bf7e478a2fc929 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 27 Mar 2011 16:43:03 +0200 Subject: [PATCH 11/15] Ensure to have only one instance of IPCServer running --- tryton/ipc.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tryton/ipc.py b/tryton/ipc.py index 3f28f198a2..62ce5e83aa 100644 --- a/tryton/ipc.py +++ b/tryton/ipc.py @@ -19,6 +19,8 @@ class IPCServer(object): instance = None def __init__(self, hostname, port, database): + if Server.instance: + Server.instance.stop() self.hostname = hostname self.port = port self.database = database From dc38c4d8289f7952c1e2832539b915279c9c9b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 27 Mar 2011 17:03:12 +0200 Subject: [PATCH 12/15] Add missing call to to_xml for error label --- tryton/common/common.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tryton/common/common.py b/tryton/common/common.py index 4081c89435..5601976b24 100644 --- a/tryton/common/common.py +++ b/tryton/common/common.py @@ -520,7 +520,7 @@ def error(title, parent, details): box = gtk.VBox() label_error = gtk.Label() - label_error.set_markup('' + _('Error: ') + '' + title) + label_error.set_markup('' + _('Error: ') + '' + to_xml(title)) label_error.set_alignment(0, 0.5) label_error.set_padding(-1, 14) label_error.modify_font(pango.FontDescription("monospace")) From 0998086668bbedfa2d918788e4ac639b201dcfb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Krier?= Date: Sun, 27 Mar 2011 23:24:58 +0200 Subject: [PATCH 13/15] Add missing copyright owner --- COPYRIGHT | 1 + 1 file changed, 1 insertion(+) diff --git a/COPYRIGHT b/COPYRIGHT index 42539e2dbc..3a41a1760a 100644 --- a/COPYRIGHT +++ b/COPYRIGHT @@ -4,6 +4,7 @@ Copyright (C) 2007-2010 Bertrand Chenal. Copyright (C) 2008-2010 B2CK SPRL. Copyright (C) 2008-2010 Udo Spallek. Copyright (C) 2008-2010 virtual things - Preisler & Spallek GbR. +Copyright (C) 2007-2009 Lorenzo Gil Sanchez. Copyright (C) 2004-2008 Tiny SPRL. This program is free software: you can redistribute it and/or modify From f58c282c4888a05819f866d21900b1f934004043 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Tue, 29 Mar 2011 11:19:17 +0200 Subject: [PATCH 14/15] Add checkbutton to show content of sha field --- .../window/view_form/view/form_gtk/char.py | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) 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 a99550a0e0..28fad0dde1 100644 --- a/tryton/gui/window/view_form/view/form_gtk/char.py +++ b/tryton/gui/window/view_form/view/form_gtk/char.py @@ -1,7 +1,12 @@ #This file is part of Tryton. The COPYRIGHT file at the top level of #this repository contains the full copyright notices and license terms. +import gettext + import gtk from interface import WidgetInterface +from tryton.common import Tooltips + +_ = gettext.gettext class Char(WidgetInterface): "Char" @@ -50,4 +55,21 @@ class Sha(Char): def __init__(self, field_name, model_name, window, attrs=None): super(Sha, self).__init__(field_name, model_name, window, attrs=attrs) - self.entry.set_visibility(False) + self.entry.props.visibility = False + + self.visibility_checkbox = gtk.CheckButton() + self.visibility_checkbox.connect('toggled', self.toggle_visibility) + Tooltips().set_tip(self.visibility_checkbox, _('Show plain text')) + self.widget.pack_start(self.visibility_checkbox, expand=False) + + def _readonly_set(self, value): + super(Char, self)._readonly_set(value) + self.entry.set_editable(not value) + self.visibility_checkbox.props.visible = not value + if value: + self.widget.set_focus_chain([]) + else: + self.widget.set_focus_chain([self.entry, self.visibility_checkbox]) + + def toggle_visibility(self, button): + self.entry.props.visibility = not self.entry.props.visibility From 978890855b898573b9742d6ea045c92db9e9cca0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolas=20=C3=89vrard?= Date: Tue, 29 Mar 2011 15:20:39 +0200 Subject: [PATCH 15/15] Fix domain inversion localization --- tryton/common/domain_inversion.py | 22 ++++++++++++++++++---- tryton/gui/window/view_form/model/field.py | 2 +- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/tryton/common/domain_inversion.py b/tryton/common/domain_inversion.py index 5004ead4ba..b0222ce68e 100644 --- a/tryton/common/domain_inversion.py +++ b/tryton/common/domain_inversion.py @@ -22,7 +22,9 @@ OPERATORS = { 'not child_of': lambda a, b: True, } -def locale_part(expression): +def locale_part(expression, field_name): + if expression == field_name: + return 'id' if '.' in expression: fieldname, local = expression.split('.', 1) return local @@ -75,14 +77,14 @@ def eval_domain(domain, context, boolop=operator.and_): return boolop(eval_domain(domain[0], context), eval_domain(domain[1:], context, boolop)) -def localize_domain(domain): +def localize_domain(domain, field_name=None): "returns only locale part of domain. eg: langage.code -> code" if domain in ('AND', 'OR', True, False): return domain elif is_leaf(domain): - return [locale_part(domain[0])] + domain[1:] + return [locale_part(domain[0], field_name)] + domain[1:] else: - return [localize_domain(part) for part in domain] + return [localize_domain(part, field_name) for part in domain] def unlocalize_domain(domain, fieldname): if domain in ('AND', 'OR', True, False): @@ -403,6 +405,17 @@ def test_evaldomain(): assert not eval_domain(domain, {'x': 5}) assert not eval_domain(domain, {'x': 11}) +def test_localize(): + domain = [['x', '=', 5]] + assert localize_domain(domain) == [['x', '=', 5]] + + domain = [['x', '=', 5], ['x.code', '=', 7]] + assert localize_domain(domain, 'x') == [['id', '=', 5], ['code', '=', 7]] + + domain = ['OR', ['AND', ['x', '>', 7], ['x', '<', 15]], ['x.code', '=', 8]] + assert localize_domain(domain, 'x') == \ + ['OR', ['AND', ['id', '>', 7], ['id', '<', 15]], ['code', '=', 8]] + if __name__ == '__main__': test_simple_inversion() test_and_inversion() @@ -414,3 +427,4 @@ if __name__ == '__main__': test_parse() test_simplify() test_evaldomain() + test_localize() diff --git a/tryton/gui/window/view_form/model/field.py b/tryton/gui/window/view_form/model/field.py index 9d24c5f782..3275a97843 100644 --- a/tryton/gui/window/view_form/model/field.py +++ b/tryton/gui/window/view_form/model/field.py @@ -394,7 +394,7 @@ class M2OField(CharField): def domain_get(self, record): screen_domain, attr_domain = self.domains_get(record) - return localize_domain(inverse_leaf(screen_domain)) + attr_domain + return localize_domain(inverse_leaf(screen_domain), self.name) + attr_domain class O2OField(M2OField):