tryton 7.0.21__py3-none-any.whl → 7.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of tryton might be problematic. Click here for more details.

Files changed (94) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +125 -69
  4. tryton/common/completion.py +2 -2
  5. tryton/common/domain_inversion.py +1 -2
  6. tryton/common/domain_parser.py +7 -17
  7. tryton/common/selection.py +6 -3
  8. tryton/common/tempfile.py +34 -0
  9. tryton/config.py +3 -2
  10. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  11. tryton/data/locale/bg/LC_MESSAGES/tryton.po +28 -3
  12. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  13. tryton/data/locale/ca/LC_MESSAGES/tryton.po +33 -5
  14. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  15. tryton/data/locale/cs/LC_MESSAGES/tryton.po +28 -3
  16. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  17. tryton/data/locale/de/LC_MESSAGES/tryton.po +32 -4
  18. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  19. tryton/data/locale/es/LC_MESSAGES/tryton.po +32 -4
  20. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  21. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +29 -4
  22. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  23. tryton/data/locale/et/LC_MESSAGES/tryton.po +32 -5
  24. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  25. tryton/data/locale/fa/LC_MESSAGES/tryton.po +32 -6
  26. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  27. tryton/data/locale/fi/LC_MESSAGES/tryton.po +28 -3
  28. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  29. tryton/data/locale/fr/LC_MESSAGES/tryton.po +32 -4
  30. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  31. tryton/data/locale/hu/LC_MESSAGES/tryton.po +32 -5
  32. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/id/LC_MESSAGES/tryton.po +30 -3
  34. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/it/LC_MESSAGES/tryton.po +31 -5
  36. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  38. tryton/data/locale/lo/LC_MESSAGES/tryton.po +31 -5
  39. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lt/LC_MESSAGES/tryton.po +32 -5
  41. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  42. tryton/data/locale/nl/LC_MESSAGES/tryton.po +32 -4
  43. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  44. tryton/data/locale/pl/LC_MESSAGES/tryton.po +32 -5
  45. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  46. tryton/data/locale/pt/LC_MESSAGES/tryton.po +31 -5
  47. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  48. tryton/data/locale/ro/LC_MESSAGES/tryton.po +43 -16
  49. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  50. tryton/data/locale/ru/LC_MESSAGES/tryton.po +29 -5
  51. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  52. tryton/data/locale/sl/LC_MESSAGES/tryton.po +32 -4
  53. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  54. tryton/data/locale/tr/LC_MESSAGES/tryton.po +28 -3
  55. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  56. tryton/data/locale/uk/LC_MESSAGES/tryton.po +32 -5
  57. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  58. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +32 -4
  59. tryton/device_cookie.py +1 -1
  60. tryton/gui/main.py +3 -2
  61. tryton/gui/window/about.py +1 -1
  62. tryton/gui/window/dblogin.py +2 -2
  63. tryton/gui/window/email_.py +1 -1
  64. tryton/gui/window/form.py +4 -3
  65. tryton/gui/window/log.py +24 -2
  66. tryton/gui/window/view_form/model/field.py +56 -62
  67. tryton/gui/window/view_form/model/group.py +3 -1
  68. tryton/gui/window/view_form/model/record.py +55 -16
  69. tryton/gui/window/view_form/screen/screen.py +22 -22
  70. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +7 -12
  71. tryton/gui/window/view_form/view/form.py +4 -14
  72. tryton/gui/window/view_form/view/form_gtk/binary.py +3 -3
  73. tryton/gui/window/view_form/view/form_gtk/dictionary.py +33 -27
  74. tryton/gui/window/view_form/view/form_gtk/document.py +10 -9
  75. tryton/gui/window/view_form/view/form_gtk/many2many.py +17 -7
  76. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  77. tryton/gui/window/view_form/view/form_gtk/one2many.py +25 -6
  78. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  79. tryton/gui/window/view_form/view/list.py +47 -56
  80. tryton/gui/window/view_form/view/list_gtk/widget.py +36 -23
  81. tryton/gui/window/view_form/view/screen_container.py +5 -3
  82. tryton/gui/window/win_export.py +1 -2
  83. tryton/gui/window/win_form.py +6 -8
  84. tryton/gui/window/wizard.py +11 -10
  85. tryton/jsonrpc.py +41 -27
  86. tryton/pyson.py +54 -4
  87. tryton/rpc.py +18 -0
  88. tryton/tests/test_common_domain_parser.py +0 -8
  89. {tryton-7.0.21.data → tryton-7.2.0.data}/scripts/tryton +1 -2
  90. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/METADATA +6 -20
  91. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/RECORD +94 -92
  92. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/WHEEL +1 -1
  93. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/LICENSE +0 -0
  94. {tryton-7.0.21.dist-info → tryton-7.2.0.dist-info}/top_level.txt +0 -0
@@ -37,7 +37,7 @@ class Many2One(Widget):
37
37
  lambda x, y: self._focus_out())
38
38
  self.wid_text.connect('changed', self.sig_changed)
39
39
  self.changed = True
40
- self.focus_out = True
40
+ self._popup = False
41
41
 
42
42
  if int(self.attrs.get('completion', 1)):
43
43
  self.wid_text.connect('changed', self._update_completion)
@@ -103,13 +103,16 @@ class Many2One(Widget):
103
103
  model = self.get_model()
104
104
  if not model or not common.MODELACCESS[model]['read']:
105
105
  return
106
- if not self.focus_out or not self.field:
106
+ if not self.field:
107
107
  return
108
108
  self.changed = False
109
109
  value = self.field.get(self.record)
110
110
  model = self.get_model()
111
111
 
112
- self.focus_out = False
112
+ if self._popup:
113
+ return
114
+ else:
115
+ self._popup = True
113
116
  if model and not self.has_target(value):
114
117
  if (not self._readonly
115
118
  and (self.wid_text.get_text()
@@ -126,7 +129,7 @@ class Many2One(Widget):
126
129
  self.value_from_id(*result[0]), force_change=True)
127
130
  else:
128
131
  self.wid_text.set_text('')
129
- self.focus_out = True
132
+ self._popup = False
130
133
  self.changed = True
131
134
 
132
135
  win = WinSearch(model, callback, sel_multi=False,
@@ -139,7 +142,7 @@ class Many2One(Widget):
139
142
  win.screen.search_filter(quote(text))
140
143
  win.show()
141
144
  return
142
- self.focus_out = True
145
+ self._popup = False
143
146
  self.changed = True
144
147
  return
145
148
 
@@ -164,7 +167,10 @@ class Many2One(Widget):
164
167
  def sig_new(self, defaults=None):
165
168
  if not self.create_access:
166
169
  return
167
- self.focus_out = False
170
+ if self._popup:
171
+ return
172
+ else:
173
+ self._popup = True
168
174
  screen = self.get_screen(search=True)
169
175
  defaults = defaults.copy() if defaults is not None else {}
170
176
  defaults['rec_name'] = self.wid_text.get_text()
@@ -174,7 +180,7 @@ class Many2One(Widget):
174
180
  self.field.set_client(self.record,
175
181
  self.value_from_id(screen.current_record.id,
176
182
  screen.current_record.rec_name()))
177
- self.focus_out = True
183
+ self._popup = False
178
184
  WinForm(
179
185
  screen, callback, new=True, save_current=True, defaults=defaults)
180
186
 
@@ -184,10 +190,9 @@ class Many2One(Widget):
184
190
  model = self.get_model()
185
191
  if not model or not common.MODELACCESS[model]['read']:
186
192
  return
187
- if not self.focus_out or not self.field:
193
+ if not self.field:
188
194
  return
189
195
  self.changed = False
190
- self.focus_out = False
191
196
  value = self.field.get(self.record)
192
197
 
193
198
  if (icon_pos == Gtk.EntryIconPosition.SECONDARY
@@ -196,9 +201,12 @@ class Many2One(Widget):
196
201
  self.field.set_client(self.record, self.value_from_id(None, ''))
197
202
  self.wid_text.set_text('')
198
203
  self.changed = True
199
- self.focus_out = True
200
204
  return
201
205
 
206
+ if self._popup:
207
+ return
208
+ else:
209
+ self._popup = True
202
210
  if self.has_target(value):
203
211
  m2o_id = self.id_from_value(self.field.get(self.record))
204
212
  screen = self.get_screen()
@@ -211,7 +219,7 @@ class Many2One(Widget):
211
219
  self.value_from_id(screen.current_record.id,
212
220
  screen.current_record.rec_name()),
213
221
  force_change=True)
214
- self.focus_out = True
222
+ self._popup = False
215
223
  self.changed = True
216
224
  WinForm(screen, callback, save_current=True)
217
225
  return
@@ -225,7 +233,7 @@ class Many2One(Widget):
225
233
  if result:
226
234
  self.field.set_client(self.record,
227
235
  self.value_from_id(*result[0]), force_change=True)
228
- self.focus_out = True
236
+ self._popup = False
229
237
  self.changed = True
230
238
  win = WinSearch(model, callback, sel_multi=False,
231
239
  context=context, domain=domain, order=order,
@@ -236,7 +244,7 @@ class Many2One(Widget):
236
244
  win.screen.search_filter(quote(text))
237
245
  win.show()
238
246
  return
239
- self.focus_out = True
247
+ self._popup = False
240
248
  self.changed = True
241
249
 
242
250
  def sig_key_press(self, widget, event, *args):
@@ -75,7 +75,6 @@ class One2Many(Widget):
75
75
 
76
76
  hbox.pack_start(Gtk.VSeparator(), expand=False, fill=True, padding=0)
77
77
 
78
- self.focus_out = True
79
78
  self.wid_completion = None
80
79
  if attrs.get('add_remove'):
81
80
 
@@ -182,6 +181,8 @@ class One2Many(Widget):
182
181
  if self.attrs.get('add_remove'):
183
182
  self.wid_text.connect('key_press_event', self.on_keypress)
184
183
 
184
+ self._popup = False
185
+
185
186
  def get_access(self, type_):
186
187
  model = self.attrs['relation']
187
188
  if model:
@@ -370,12 +371,17 @@ class One2Many(Widget):
370
371
  self._new_single(defaults)
371
372
 
372
373
  def _new_single(self, defaults=None):
374
+ if self._popup:
375
+ return
376
+ else:
377
+ self._popup = True
373
378
  sequence = self._sequence()
374
379
 
375
380
  def update_sequence():
376
381
  if sequence:
377
382
  self.screen.group.set_sequence(
378
383
  field=sequence, position=self.screen.new_position)
384
+ self._popup = False
379
385
 
380
386
  if self.screen.current_view.creatable:
381
387
  self.screen.new()
@@ -392,6 +398,10 @@ class One2Many(Widget):
392
398
  fields = self.attrs['product'].split(',')
393
399
  product = {}
394
400
 
401
+ if self._popup:
402
+ return
403
+ else:
404
+ self._popup = True
395
405
  first = self.screen.new(default=False)
396
406
  default = first.default_get(defaults=defaults)
397
407
  first.set_default(default)
@@ -420,6 +430,7 @@ class One2Many(Widget):
420
430
  win_search.show()
421
431
 
422
432
  def make_product():
433
+ self._popup = False
423
434
  self.screen.group.remove(first, remove=True)
424
435
  if not product:
425
436
  return
@@ -448,7 +459,14 @@ class One2Many(Widget):
448
459
  return
449
460
  record = self.screen.current_record
450
461
  if record:
451
- WinForm(self.screen, lambda a: None)
462
+ if self._popup:
463
+ return
464
+ else:
465
+ self._popup = True
466
+
467
+ def callback(result):
468
+ self._popup = False
469
+ WinForm(self.screen, callback)
452
470
 
453
471
  def _sig_next(self, widget):
454
472
  if not self._validate():
@@ -475,8 +493,6 @@ class One2Many(Widget):
475
493
  self.screen.unremove()
476
494
 
477
495
  def _sig_add(self, *args):
478
- if not self.focus_out:
479
- return
480
496
  if not self.write_access or not self.read_access:
481
497
  return
482
498
  self.view.set_value()
@@ -487,12 +503,14 @@ class One2Many(Widget):
487
503
  domain = ['OR', domain, ('id', 'in', removed_ids)]
488
504
  text = self.wid_text.get_text()
489
505
 
490
- self.focus_out = False
506
+ if self._popup:
507
+ return
508
+ else:
509
+ self._popup = True
491
510
 
492
511
  sequence = self._sequence()
493
512
 
494
513
  def callback(result):
495
- self.focus_out = True
496
514
  if result:
497
515
  ids = [x[0] for x in result]
498
516
  self.screen.load(ids, modified=True)
@@ -501,6 +519,7 @@ class One2Many(Widget):
501
519
  field=sequence, position=self.screen.new_position)
502
520
  self.screen.set_cursor()
503
521
  self.wid_text.set_text('')
522
+ self._popup = False
504
523
 
505
524
  order = self.field.get_search_order(self.record)
506
525
  win = WinSearch(self.attrs['relation'], callback, sel_multi=True,
@@ -50,6 +50,8 @@ class Label(StateMixin, Gtk.Label):
50
50
  readonly = ((field and field.attrs.get('readonly'))
51
51
  or state_changes.get('readonly', not bool(field)))
52
52
  common.apply_label_attributes(self, readonly, required)
53
+ self.set_max_width_chars(80)
54
+ self.set_line_wrap(True)
53
55
 
54
56
 
55
57
  class VBox(StateMixin, Gtk.VBox):
@@ -187,14 +189,16 @@ class Link(StateMixin, Gtk.Button):
187
189
  ['AND', domain, tab_domain], 0, 100, context=context,
188
190
  callback=functools.partial(
189
191
  self._set_count, idx=i, current=self._current,
190
- counter=counter, label=label))
192
+ counter=counter, label=label),
193
+ process_exception=False)
191
194
  else:
192
195
  common.RPCExecute(
193
196
  'model', action['res_model'], 'search_count',
194
197
  domain, 0, 100, context=context,
195
198
  callback=functools.partial(
196
199
  self._set_count, current=self._current,
197
- counter=counter, label=label))
200
+ counter=counter, label=label),
201
+ process_exception=False)
198
202
 
199
203
  def _set_count(self, value, idx=0, current=None, counter=None, label=''):
200
204
  if current != self._current or not self.get_parent():
@@ -1,10 +1,12 @@
1
1
  # This file is part of Tryton. The COPYRIGHT file at the top level of
2
2
  # this repository contains the full copyright notices and license terms.
3
+ import csv
3
4
  import gettext
4
5
  import json
5
6
  import locale
6
7
  import sys
7
8
  from functools import wraps
9
+ from io import StringIO
8
10
 
9
11
  from gi.repository import Gdk, GLib, GObject, Gtk
10
12
  from pygtkcompat.generictreemodel import GenericTreeModel
@@ -333,7 +335,7 @@ class TreeXMLViewParser(XMLViewParser):
333
335
 
334
336
  self.view.treeview.append_column(column)
335
337
 
336
- if 'optional' in attributes and name != self.exclude_field:
338
+ if 'optional' in attributes:
337
339
  self.view.optionals.append(column)
338
340
 
339
341
  def _parse_button(self, node, attributes):
@@ -720,48 +722,42 @@ class ViewTree(View):
720
722
  record.cancel()
721
723
  iter_ = model.iter_next(iter_)
722
724
 
723
- def on_copy(self):
725
+ def on_copy(self, columns=None):
726
+ if columns is None:
727
+ columns = self.treeview.get_columns()
728
+
729
+ def copy_foreach(treemodel, path, iter, add):
730
+ record = treemodel.get_value(iter, 0)
731
+ values = []
732
+ for col in columns:
733
+ if not col.get_visible() or not col.name:
734
+ continue
735
+ widget = self.get_column_widget(col)
736
+ values.append(widget.get_textual_value(record))
737
+ add(values)
738
+
724
739
  for clipboard_type in [
725
740
  Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
726
741
  clipboard = self.treeview.get_clipboard(clipboard_type)
727
742
  selection = self.treeview.get_selection()
728
- data = []
729
- selection.selected_foreach(self.copy_foreach, data)
730
- clipboard.set_text('\n'.join(data), -1)
731
-
732
- def copy_foreach(self, treemodel, path, iter, data):
733
- record = treemodel.get_value(iter, 0)
734
- values = []
735
- for col in self.treeview.get_columns():
736
- if not col.get_visible() or not col.name:
737
- continue
738
- widget = self.get_column_widget(col)
739
- values.append('"'
740
- + str(widget.get_textual_value(record)).replace('"', '""')
741
- + '"')
742
- data.append('\t'.join(values))
743
- return
743
+ data = StringIO()
744
+ writer = csv.writer(data, delimiter='\t', lineterminator='\n')
745
+ selection.selected_foreach(copy_foreach, writer.writerow)
746
+ clipboard.set_text(data.getvalue(), -1)
744
747
 
745
748
  def on_paste(self):
746
749
  if not self.editable:
747
750
  return
748
751
 
749
- def unquote(value):
750
- if value[:1] == '"' and value[-1:] == '"':
751
- return value[1:-1]
752
- return value
753
- data = []
752
+ reader = None
754
753
  for clipboard_type in [
755
754
  Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
756
755
  clipboard = self.treeview.get_clipboard(clipboard_type)
757
756
  text = clipboard.wait_for_text()
758
757
  if not text:
759
758
  continue
760
- data = [[unquote(v) for v in l.split('\t')]
761
- for l in text.splitlines()]
759
+ reader = csv.reader(StringIO(text), delimiter='\t')
762
760
  break
763
- else:
764
- return
765
761
  col = self.treeview.get_cursor()[1]
766
762
  columns = [c for c in self.treeview.get_columns()
767
763
  if c.get_visible() and c.name]
@@ -776,7 +772,7 @@ class ViewTree(View):
776
772
  group = self.group
777
773
  idx = len(group)
778
774
  default = None
779
- for line in data:
775
+ for line in reader:
780
776
  if idx >= len(group):
781
777
  record = group.new(default=False)
782
778
  if default is None:
@@ -787,25 +783,13 @@ class ViewTree(View):
787
783
  for col, value in zip(columns, line):
788
784
  widget = self.get_column_widget(col)
789
785
  if widget.get_textual_value(record) != value:
790
- if hasattr(widget, 'search_remote'):
791
- field = record.group.fields[widget.attrs['name']]
792
- win = widget.search_remote(record, field, value)
793
- if len(win.screen.group) == 1:
794
- target, = win.screen.group
795
- field.set_client(
796
- record, (target.id, target.rec_name()))
797
- else:
798
- field.set_client(record, (None, ''))
799
- win.response(win, Gtk.ResponseType.CANCEL)
800
- else:
801
- widget.value_from_text(record, value)
786
+ widget.value_from_text(record, value)
802
787
  if value and not widget.get_textual_value(record):
803
788
  # Stop setting value if a value is correctly set
804
789
  idx = len(group)
805
790
  break
806
791
  if not record.validate():
807
792
  break
808
- record.save()
809
793
  idx += 1
810
794
  self.record = record
811
795
  self.screen.display(set_cursor=True)
@@ -914,12 +898,27 @@ class ViewTree(View):
914
898
  except TypeError:
915
899
  # Outside row
916
900
  return False
901
+ selection = treeview.get_selection()
917
902
  menu = Gtk.Menu()
918
- copy_item = Gtk.MenuItem(label=_('Copy'))
919
- copy_item.connect('activate', lambda x: self.on_copy())
920
- menu.append(copy_item)
903
+ if selection.count_selected_rows():
904
+ if selection.count_selected_rows() == 1:
905
+ copy_item = Gtk.MenuItem(label=_("Copy"))
906
+ copy_item.connect(
907
+ 'activate', lambda x: self.on_copy([col]))
908
+ menu.append(copy_item)
909
+ copy_row_label = _("Copy Row")
910
+ else:
911
+ copy_row_label = _("Copy Rows")
912
+ if col:
913
+ copy_column_item = Gtk.MenuItem(label=_("Copy Column"))
914
+ copy_column_item.connect(
915
+ 'activate', lambda x: self.on_copy(columns=[col]))
916
+ menu.append(copy_column_item)
917
+ copy_row_item = Gtk.MenuItem(label=copy_row_label)
918
+ copy_row_item.connect('activate', lambda x: self.on_copy())
919
+ menu.append(copy_row_item)
921
920
  if self.editable:
922
- paste_item = Gtk.MenuItem(label=_('Paste'))
921
+ paste_item = Gtk.MenuItem(label=_("Paste Rows"))
923
922
  paste_item.connect('activate', lambda x: self.on_paste())
924
923
  menu.append(paste_item)
925
924
 
@@ -953,7 +952,6 @@ class ViewTree(View):
953
952
  menu, model, record_id, title=label, field=field,
954
953
  context=context)
955
954
 
956
- selection = treeview.get_selection()
957
955
  if selection.count_selected_rows() == 1:
958
956
  group = self.group
959
957
  if selection.get_mode() == Gtk.SelectionMode.SINGLE:
@@ -1055,12 +1053,9 @@ class ViewTree(View):
1055
1053
  elif tree_sel.get_mode() == Gtk.SelectionMode.MULTIPLE:
1056
1054
  model, paths = tree_sel.get_selected_rows()
1057
1055
  if model and paths:
1058
- records = []
1059
- for path in paths:
1060
- iter_ = model.get_iter(path)
1061
- records.append(model.get_value(iter_, 0))
1062
- if self.record not in records:
1063
- self.record = records[0]
1056
+ iter_ = model.get_iter(paths[0])
1057
+ record = model.get_value(iter_, 0)
1058
+ self.record = record
1064
1059
  else:
1065
1060
  self.record = None
1066
1061
 
@@ -1100,10 +1095,6 @@ class ViewTree(View):
1100
1095
  def display(self, force=False):
1101
1096
  self.treeview.display_counter += 1
1102
1097
  current_record = self.record
1103
- if current_record and current_record not in current_record.group:
1104
- # current record may have been removed by on_change calls without
1105
- # changing the current record of screen before the display
1106
- current_record = None
1107
1098
  if (force
1108
1099
  or not self.treeview.get_model()
1109
1100
  or self.group != self.treeview.get_model().group):
@@ -215,8 +215,12 @@ class Affix(Cell):
215
215
  else:
216
216
  value = self.icon
217
217
  if self.attrs.get('icon_type') == 'url':
218
+ def callback(pixbuf):
219
+ self.display_counters.pop(record.id, None)
220
+ self.view.treeview.queue_resize() # trigger a redraw
218
221
  pixbuf = common.IconFactory.get_pixbuf_url(
219
- value, size_param=self.attrs.get('url_size'))
222
+ value, size_param=self.attrs.get('url_size'),
223
+ callback=callback)
220
224
  else:
221
225
  pixbuf = common.IconFactory.get_pixbuf(
222
226
  value, Gtk.IconSize.BUTTON)
@@ -410,19 +414,19 @@ class Int(GenericText):
410
414
  self.symbol = attrs.get('symbol')
411
415
  self.grouping = bool(int(attrs.get('grouping', 1)))
412
416
  if self.symbol:
413
- self.renderer_prefix = Symbol(view, attrs, 0)
414
- self.renderer_suffix = Symbol(view, attrs, 1)
417
+ self._cell_prefix = Symbol(view, attrs, 0)
418
+ self._cell_suffix = Symbol(view, attrs, 1)
415
419
 
416
420
  @property
417
421
  def prefixes(self):
418
422
  if self.symbol:
419
- return [self.renderer_prefix]
423
+ return [self._cell_prefix]
420
424
  return []
421
425
 
422
426
  @property
423
427
  def suffixes(self):
424
428
  if self.symbol:
425
- return [self.renderer_suffix]
429
+ return [self._cell_suffix]
426
430
  return []
427
431
 
428
432
  @catch_errors()
@@ -574,20 +578,20 @@ class Binary(GenericText):
574
578
  super(Binary, self).__init__(view, attrs, renderer=renderer)
575
579
  self.renderer.set_property('editable', False)
576
580
  self.renderer.set_property('xalign', self.align)
577
- self.renderer_save = _BinarySave(self)
578
- self.renderer_select = _BinarySelect(self)
581
+ self._cell_save = _BinarySave(self)
582
+ self._cell_select = _BinarySelect(self)
579
583
  if self.attrs.get('filename'):
580
- self.renderer_open = _BinaryOpen(self)
584
+ self._cell_open = _BinaryOpen(self)
581
585
  else:
582
- self.renderer_open = None
586
+ self._cell_open = None
583
587
 
584
588
  @property
585
589
  def prefixes(self):
586
- return filter(None, [self.renderer_open])
590
+ return filter(None, [self._cell_open])
587
591
 
588
592
  @property
589
593
  def suffixes(self):
590
- return [self.renderer_save, self.renderer_select]
594
+ return [self._cell_save, self._cell_select]
591
595
 
592
596
  @catch_errors()
593
597
  def get_textual_value(self, record):
@@ -738,7 +742,7 @@ class _BinarySelect(_BinaryIcon):
738
742
  invisible = field.get_state_attrs(record).get('invisible', False)
739
743
  readonly = self.attrs.get('readonly',
740
744
  field.get_state_attrs(record).get('readonly', False))
741
- if readonly or size:
745
+ if readonly and size:
742
746
  cell.set_property('visible', False)
743
747
  else:
744
748
  cell.set_property('visible', not invisible)
@@ -822,6 +826,7 @@ class M2O(GenericText):
822
826
  if renderer is None and int(attrs.get('completion', 1)):
823
827
  renderer = partial(CellRendererTextCompletion, self.set_completion)
824
828
  super(M2O, self).__init__(view, attrs, renderer=renderer)
829
+ self._popup = False
825
830
 
826
831
  def get_model(self, record, field):
827
832
  return self.attrs['relation']
@@ -850,7 +855,8 @@ class M2O(GenericText):
850
855
  if model and common.get_toplevel_window().get_focus():
851
856
  field = record[self.attrs['name']]
852
857
  win = self.search_remote(record, field, text, callback=callback)
853
- win.show()
858
+ if win:
859
+ win.show()
854
860
 
855
861
  def editing_started(self, cell, editable, path):
856
862
  super(M2O, self).editing_started(cell, editable, path)
@@ -919,7 +925,9 @@ class M2O(GenericText):
919
925
  domain = field.domain_get(record)
920
926
  context = field.get_context(record)
921
927
  if not create and changed:
922
- self.search_remote(record, field, text, callback=callback).show()
928
+ win = self.search_remote(record, field, text, callback=callback)
929
+ if win:
930
+ win.show()
923
931
  return
924
932
  target_id = self.id_from_value(field.get(record))
925
933
 
@@ -959,6 +967,11 @@ class M2O(GenericText):
959
967
  access = common.MODELACCESS[model]
960
968
  create_access = int(self.attrs.get('create', 1)) and access['create']
961
969
 
970
+ if self._popup:
971
+ return
972
+ else:
973
+ self._popup = True
974
+
962
975
  def search_callback(found):
963
976
  value = None
964
977
  if found:
@@ -966,6 +979,7 @@ class M2O(GenericText):
966
979
  field.set_client(record, value)
967
980
  if callback:
968
981
  callback()
982
+ self._popup = False
969
983
  win = WinSearch(model, search_callback, sel_multi=False,
970
984
  context=context, domain=domain,
971
985
  order=order, view_ids=self.attrs.get('view_ids', '').split(','),
@@ -1253,11 +1267,11 @@ class Reference(M2O):
1253
1267
 
1254
1268
  def __init__(self, view, attrs, renderer=None):
1255
1269
  super(Reference, self).__init__(view, attrs, renderer=renderer)
1256
- self.renderer_selection = _ReferenceSelection(view, attrs)
1270
+ self._cell_selection = _ReferenceSelection(view, attrs)
1257
1271
 
1258
1272
  @property
1259
1273
  def prefixes(self):
1260
- return [self.renderer_selection]
1274
+ return [self._cell_selection]
1261
1275
 
1262
1276
  def get_model(self, record, field):
1263
1277
  value = field.get_client(record)
@@ -1405,13 +1419,12 @@ class Button(Cell):
1405
1419
  cell.set_property('visible', not invisible)
1406
1420
  readonly = states.get('readonly', False)
1407
1421
  cell.set_property('sensitive', not readonly)
1408
- if self.attrs.get('type', 'class') == 'class':
1409
- parent = record.parent if record else None
1410
- while parent:
1411
- if parent.modified:
1412
- cell.set_property('sensitive', False)
1413
- break
1414
- parent = parent.parent
1422
+ parent = record.parent if record else None
1423
+ while parent:
1424
+ if parent.modified:
1425
+ cell.set_property('sensitive', False)
1426
+ break
1427
+ parent = parent.parent
1415
1428
  # TODO icon
1416
1429
  self._set_visual(cell, record)
1417
1430
 
@@ -185,8 +185,7 @@ class Selection(Gtk.ScrolledWindow):
185
185
 
186
186
  class ScreenContainer(object):
187
187
 
188
- def __init__(self, screen, tab_domain):
189
- self.screen = screen
188
+ def __init__(self, tab_domain):
190
189
  self.viewport = Gtk.Viewport()
191
190
  self.viewport.set_shadow_type(Gtk.ShadowType.NONE)
192
191
  self.vbox = Gtk.VBox(spacing=3)
@@ -352,9 +351,12 @@ class ScreenContainer(object):
352
351
  def widget_get(self):
353
352
  return self.vbox
354
353
 
355
- def show_filter(self):
354
+ def set_screen(self, screen):
355
+ self.screen = screen
356
356
  self.but_bookmark.set_sensitive(bool(list(self.bookmarks())))
357
357
  self.bookmark_match()
358
+
359
+ def show_filter(self):
358
360
  if self.filter_vbox:
359
361
  self.filter_vbox.show()
360
362
  if self.notebook:
@@ -7,7 +7,6 @@ import gettext
7
7
  import json
8
8
  import locale
9
9
  import os
10
- import tempfile
11
10
  import urllib.parse
12
11
  from itertools import zip_longest
13
12
  from numbers import Number
@@ -15,7 +14,7 @@ from numbers import Number
15
14
  from gi.repository import Gdk, GObject, Gtk
16
15
 
17
16
  import tryton.common as common
18
- from tryton.common import RPCException, RPCExecute
17
+ from tryton.common import RPCException, RPCExecute, tempfile
19
18
  from tryton.config import CONFIG
20
19
  from tryton.gui.window.win_csv import WinCSV
21
20
  from tryton.jsonrpc import JSONEncoder
@@ -69,7 +69,7 @@ class WinForm(NoModal, InfoBar):
69
69
  self.accel_group = Gtk.AccelGroup()
70
70
  self.win.add_accel_group(self.accel_group)
71
71
 
72
- readonly = self.screen.group.readonly
72
+ readonly = self.screen.readonly or self.screen.group.readonly
73
73
 
74
74
  self.but_ok = None
75
75
  self.but_new = None
@@ -360,7 +360,7 @@ class WinForm(NoModal, InfoBar):
360
360
  deletable = True
361
361
  if self.screen.current_record:
362
362
  deletable = self.screen.current_record.deletable
363
- readonly = self.screen.group.readonly
363
+ readonly = self.screen.readonly or self.screen.group.readonly
364
364
  if position >= 1:
365
365
  name = str(position)
366
366
  if self.domain is not None:
@@ -373,11 +373,9 @@ class WinForm(NoModal, InfoBar):
373
373
  self.but_pre.set_sensitive(True)
374
374
  else:
375
375
  self.but_pre.set_sensitive(False)
376
- self.but_del.set_sensitive(bool(
377
- not readonly
378
- and access['delete']
379
- and deletable))
380
- self.but_undel.set_sensitive(bool(not readonly))
376
+ if access['delete'] and not readonly and deletable:
377
+ self.but_del.set_sensitive(True)
378
+ self.but_undel.set_sensitive(True)
381
379
  else:
382
380
  self.but_del.set_sensitive(False)
383
381
  self.but_undel.set_sensitive(False)
@@ -401,7 +399,7 @@ class WinForm(NoModal, InfoBar):
401
399
  cancel_responses = [
402
400
  Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]
403
401
  self.screen.current_view.set_value()
404
- readonly = self.screen.group.readonly
402
+ readonly = self.screen.readonly or self.screen.group.readonly
405
403
  if (response_id not in cancel_responses
406
404
  and not readonly
407
405
  and self.screen.current_record is not None):