tryton 6.6.8__py3-none-any.whl → 6.8.1__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 (92) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/action/main.py +32 -43
  3. tryton/bus.py +2 -0
  4. tryton/client.py +3 -0
  5. tryton/common/button.py +3 -1
  6. tryton/common/common.py +55 -43
  7. tryton/common/datetime_.py +14 -2
  8. tryton/common/domain_inversion.py +10 -10
  9. tryton/common/domain_parser.py +5 -2
  10. tryton/common/popup_menu.py +7 -0
  11. tryton/common/selection.py +3 -1
  12. tryton/config.py +22 -5
  13. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  14. tryton/data/locale/bg/LC_MESSAGES/tryton.po +45 -39
  15. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  16. tryton/data/locale/ca/LC_MESSAGES/tryton.po +41 -35
  17. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  18. tryton/data/locale/cs/LC_MESSAGES/tryton.po +46 -39
  19. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  20. tryton/data/locale/de/LC_MESSAGES/tryton.po +41 -35
  21. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  22. tryton/data/locale/es/LC_MESSAGES/tryton.po +41 -35
  23. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  24. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +171 -167
  25. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  26. tryton/data/locale/et/LC_MESSAGES/tryton.po +47 -39
  27. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  28. tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -38
  29. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  30. tryton/data/locale/fi/LC_MESSAGES/tryton.po +38 -32
  31. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  32. tryton/data/locale/fr/LC_MESSAGES/tryton.po +42 -36
  33. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  34. tryton/data/locale/hu/LC_MESSAGES/tryton.po +44 -34
  35. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  36. tryton/data/locale/id/LC_MESSAGES/tryton.po +40 -34
  37. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  38. tryton/data/locale/it/LC_MESSAGES/tryton.po +44 -34
  39. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  40. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/lo/LC_MESSAGES/tryton.po +46 -38
  42. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  43. tryton/data/locale/lt/LC_MESSAGES/tryton.po +47 -37
  44. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  45. tryton/data/locale/nl/LC_MESSAGES/tryton.po +41 -35
  46. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  47. tryton/data/locale/pl/LC_MESSAGES/tryton.po +45 -35
  48. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  49. tryton/data/locale/pt/LC_MESSAGES/tryton.po +46 -38
  50. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  51. tryton/data/locale/ro/LC_MESSAGES/tryton.po +50 -48
  52. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  53. tryton/data/locale/ru/LC_MESSAGES/tryton.po +45 -39
  54. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  55. tryton/data/locale/sl/LC_MESSAGES/tryton.po +47 -38
  56. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  57. tryton/data/locale/tr/LC_MESSAGES/tryton.po +39 -33
  58. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  59. tryton/data/locale/uk/LC_MESSAGES/tryton.po +43 -35
  60. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  61. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +45 -35
  62. tryton/data/pixmaps/tryton/tryton-icon.svg +1 -0
  63. tryton/gui/main.py +54 -61
  64. tryton/gui/window/dblogin.py +27 -10
  65. tryton/gui/window/form.py +21 -53
  66. tryton/gui/window/infobar.py +9 -4
  67. tryton/gui/window/log.py +95 -0
  68. tryton/gui/window/view_board/action.py +0 -4
  69. tryton/gui/window/view_form/model/field.py +36 -14
  70. tryton/gui/window/view_form/model/record.py +22 -9
  71. tryton/gui/window/view_form/screen/screen.py +45 -76
  72. tryton/gui/window/view_form/view/calendar_.py +24 -11
  73. tryton/gui/window/view_form/view/calendar_gtk/toolbar.py +6 -5
  74. tryton/gui/window/view_form/view/form.py +14 -5
  75. tryton/gui/window/view_form/view/form_gtk/many2many.py +10 -1
  76. tryton/gui/window/view_form/view/form_gtk/many2one.py +1 -0
  77. tryton/gui/window/view_form/view/form_gtk/one2many.py +7 -7
  78. tryton/gui/window/view_form/view/form_gtk/textbox.py +0 -2
  79. tryton/gui/window/view_form/view/form_gtk/widget.py +8 -10
  80. tryton/gui/window/view_form/view/list_form.py +61 -5
  81. tryton/gui/window/view_form/view/list_gtk/editabletree.py +13 -3
  82. tryton/gui/window/view_form/view/list_gtk/widget.py +97 -27
  83. tryton/gui/window/win_form.py +6 -5
  84. tryton/rpc.py +13 -15
  85. tryton/tests/test_common.py +46 -0
  86. tryton/tests/test_common_domain_parser.py +24 -24
  87. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/METADATA +6 -6
  88. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/RECORD +92 -89
  89. {tryton-6.6.8.data → tryton-6.8.1.data}/scripts/tryton +0 -0
  90. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/LICENSE +0 -0
  91. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/WHEEL +0 -0
  92. {tryton-6.6.8.dist-info → tryton-6.8.1.dist-info}/top_level.txt +0 -0
@@ -422,10 +422,7 @@ class Screen:
422
422
  self.filter_widget = None
423
423
  self.order = None
424
424
  self.__group.add_fields(fields)
425
- if len(group):
426
- self.current_record = group[0]
427
- else:
428
- self.current_record = None
425
+ self.current_record = None
429
426
  for name, views in fields_views.items():
430
427
  self.__group.fields[name].views.update(views)
431
428
  self.__group.exclude_field = self.exclude_field
@@ -452,14 +449,14 @@ class Screen:
452
449
  def record_notify(self, notifications):
453
450
  for window in self.windows:
454
451
  if isinstance(window, InfoBar):
455
- window.info_bar_refresh()
452
+ window.info_bar_refresh('notification')
456
453
  for type_, message in notifications:
457
454
  type_ = {
458
455
  'info': Gtk.MessageType.INFO,
459
456
  'warning': Gtk.MessageType.WARNING,
460
457
  'error': Gtk.MessageType.ERROR,
461
458
  }.get(type_, Gtk.MessageType.WARNING)
462
- window.info_bar_add(message, type_)
459
+ window.info_bar_add(message, type_, 'notification')
463
460
 
464
461
  def record_message(self, position, size, max_size, record_id):
465
462
  for window in self.windows:
@@ -486,7 +483,7 @@ class Screen:
486
483
  return self.__current_record
487
484
 
488
485
  def __set_current_record(self, record):
489
- if self.__current_record == record:
486
+ if self.__current_record == record and record:
490
487
  return
491
488
  self.__current_record = record
492
489
  if record:
@@ -789,11 +786,6 @@ class Screen:
789
786
  if not self.group.delete(records):
790
787
  return False
791
788
 
792
- top_record = records[0]
793
- top_group = top_record.group
794
- idx = top_group.index(top_record)
795
- path = top_record.get_path(self.group)
796
-
797
789
  for record in records:
798
790
  # set current model to None to prevent __select_changed
799
791
  # to save the previous_model as it can be already deleted.
@@ -815,15 +807,7 @@ class Screen:
815
807
  record.parent.save(force_reload=False)
816
808
  record.destroy()
817
809
 
818
- if idx > 0:
819
- record = top_group[idx - 1]
820
- path = path[:-1] + ((path[-1][0], record.id,),)
821
- else:
822
- path = path[:-1]
823
- if path:
824
- self.current_record = self.group.get_by_path(path)
825
- elif len(self.group):
826
- self.current_record = self.group[0]
810
+ self.current_record = None
827
811
  self.set_cursor()
828
812
  self.display()
829
813
  return True
@@ -840,40 +824,43 @@ class Screen:
840
824
 
841
825
  def set_tree_state(self):
842
826
  view = self.current_view
843
- if view.view_type not in ('tree', 'form'):
827
+ if view.view_type not in {'tree', 'form', 'list-form'}:
844
828
  return
845
829
  if id(view) in self.tree_states_done:
846
830
  return
847
831
  if view.view_type == 'form' and self.tree_states_done:
848
832
  return
849
- if (view.view_type == 'tree'
850
- and not view.attributes.get('tree_state', False)):
833
+ if (view.view_type in {'tree', 'list-form'}
834
+ and not int(view.attributes.get('tree_state', False))):
851
835
  # Mark as done to not set later when the view_type change
852
836
  self.tree_states_done.add(id(view))
853
837
  parent = self.parent.id if self.parent else None
854
838
  if parent is not None and parent < 0:
855
839
  return
856
840
  expanded_nodes, selected_nodes = [], []
857
- state = self.tree_states[parent][view.children_field]
858
- if state:
859
- expanded_nodes, selected_nodes = state
860
- if state is None and CONFIG['client.save_tree_state']:
861
- json_domain = self.get_tree_domain(parent)
862
- try:
863
- expanded_nodes, selected_nodes = RPCExecute('model',
864
- 'ir.ui.view_tree_state', 'get',
865
- self.model_name, json_domain,
866
- view.children_field)
867
- expanded_nodes = json.loads(expanded_nodes)
868
- selected_nodes = json.loads(selected_nodes)
869
- except RPCException:
870
- logger.warn(
871
- 'Unable to get view tree state for %s',
872
- self.model_name)
873
- self.tree_states[parent][view.children_field] = (
874
- expanded_nodes, selected_nodes)
875
- if view.view_type == 'tree':
876
- view.expand_nodes(expanded_nodes)
841
+ if view.view_type in {'tree', 'list-form'}:
842
+ state = self.tree_states[parent][view.children_field]
843
+ if state:
844
+ expanded_nodes, selected_nodes = state
845
+ if (state is None
846
+ and CONFIG['client.save_tree_state']
847
+ and int(view.attributes.get('tree_state', False))):
848
+ json_domain = self.get_tree_domain(parent)
849
+ try:
850
+ expanded_nodes, selected_nodes = RPCExecute('model',
851
+ 'ir.ui.view_tree_state', 'get',
852
+ self.model_name, json_domain,
853
+ view.children_field)
854
+ expanded_nodes = json.loads(expanded_nodes)
855
+ selected_nodes = json.loads(selected_nodes)
856
+ except RPCException:
857
+ logger.warn(
858
+ 'Unable to get view tree state for %s',
859
+ self.model_name)
860
+ self.tree_states[parent][view.children_field] = (
861
+ expanded_nodes, selected_nodes)
862
+ if view.view_type == 'tree':
863
+ view.expand_nodes(expanded_nodes)
877
864
  view.select_nodes(selected_nodes)
878
865
  else:
879
866
  if selected_nodes:
@@ -909,9 +896,12 @@ class Screen:
909
896
  self.current_record)
910
897
  self.tree_states[parent][view.children_field] = (
911
898
  [], [[path]])
912
- elif view.view_type == 'tree':
913
- view.save_width()
914
- paths = view.get_expanded_paths()
899
+ elif view.view_type in {'tree', 'list-form'}:
900
+ if view.view_type == 'tree':
901
+ view.save_width()
902
+ paths = view.get_expanded_paths()
903
+ else:
904
+ paths = []
915
905
  selected_paths = view.get_selected_paths()
916
906
  self.tree_states[parent][view.children_field] = (
917
907
  paths, selected_paths)
@@ -959,7 +949,7 @@ class Screen:
959
949
  if (self.current_record
960
950
  and self.current_record in self.current_record.group):
961
951
  pass
962
- elif self.group and self.current_view.view_type != 'calendar':
952
+ elif self.group and self.current_view.view_type == 'form':
963
953
  self.current_record = self.group[0]
964
954
  else:
965
955
  self.current_record = None
@@ -986,19 +976,7 @@ class Screen:
986
976
 
987
977
  def _get_next_record(self, test=False):
988
978
  view = self.current_view
989
- if view.view_type == 'tree' and len(self.group):
990
- range_ = view.treeview.get_visible_range()
991
- if range_ and not test:
992
- start, end = range_
993
- vadjustment = view.treeview.get_vadjustment()
994
- vadjustment.set_value(
995
- vadjustment.props.value + vadjustment.props.page_increment)
996
- model = view.treeview.get_model()
997
- iter_ = model.get_iter(end)
998
- return model.get_value(iter_, 0)
999
- else:
1000
- return self.group[-1]
1001
- elif (view.view_type == 'form'
979
+ if (view.view_type in {'tree', 'form'}
1002
980
  and self.current_record
1003
981
  and self.current_record.group):
1004
982
  group = self.current_record.group
@@ -1073,19 +1051,7 @@ class Screen:
1073
1051
 
1074
1052
  def _get_prev_record(self, test=False):
1075
1053
  view = self.current_view
1076
- if view.view_type == 'tree' and len(self.group):
1077
- range_ = view.treeview.get_visible_range()
1078
- if range_ and not test:
1079
- start, end = range_
1080
- vadjustment = view.treeview.get_vadjustment()
1081
- vadjustment.set_value(
1082
- vadjustment.props.value - vadjustment.props.page_increment)
1083
- model = view.treeview.get_model()
1084
- iter_ = model.get_iter(start)
1085
- return model.get_value(iter_, 0)
1086
- else:
1087
- return self.group[0]
1088
- elif (view.view_type == 'form'
1054
+ if (view.view_type in {'tree', 'form'}
1089
1055
  and self.current_record
1090
1056
  and self.current_record.group):
1091
1057
  group = self.current_record.group
@@ -1183,7 +1149,9 @@ class Screen:
1183
1149
 
1184
1150
  @property
1185
1151
  def listed_records(self):
1186
- if self.current_view and self.current_view.view_type == 'tree':
1152
+ if (self.current_view
1153
+ and self.current_view.view_type in {
1154
+ 'tree', 'calendar', 'list-form'}):
1187
1155
  return self.current_view.listed_records
1188
1156
  elif self.current_record:
1189
1157
  return [self.current_record]
@@ -1209,7 +1177,8 @@ class Screen:
1209
1177
  def get_buttons(self):
1210
1178
  'Return active buttons for the current view'
1211
1179
  def is_active(record, button):
1212
- if button.attrs.get('type', 'class') == 'instance':
1180
+ if (record.readonly
1181
+ or button.attrs.get('type', 'class') == 'instance'):
1213
1182
  return False
1214
1183
  states = record.expr_eval(button.attrs.get('states', {}))
1215
1184
  return not (states.get('invisible') or states.get('readonly'))
@@ -41,21 +41,10 @@ class CalendarXMLViewParser(XMLViewParser):
41
41
  self.parse(child)
42
42
  goocalendar = Calendar_(
43
43
  self.view.attributes, self.view, self.calendar_fields)
44
- scrolledwindow = Gtk.ScrolledWindow()
45
- scrolledwindow.add(goocalendar)
46
44
  toolbar = Toolbar(goocalendar)
47
- self.view.widget.pack_start(
48
- toolbar, expand=False, fill=False, padding=0)
49
- self.view.widget.pack_start(
50
- scrolledwindow, expand=True, fill=True, padding=0)
51
45
  self.view.widgets['goocalendar'] = goocalendar
52
46
  self.view.widgets['toolbar'] = toolbar
53
47
 
54
- if attributes.get('height') or attributes.get('width'):
55
- scrolledwindow.set_size_request(
56
- int(attributes.get('width', -1)),
57
- int(attributes.get('height', -1)))
58
-
59
48
  def _parse_field(self, node, attributes):
60
49
  self.calendar_fields.append(attributes)
61
50
 
@@ -85,6 +74,25 @@ class ViewCalendar(View):
85
74
  goocalendar.connect('day-pressed', self.on_day_pressed)
86
75
  goocalendar.connect('day-activated', self.on_day_activated)
87
76
 
77
+ self.widget.pack_start(toolbar, expand=False, fill=False, padding=0)
78
+ vp = Gtk.Viewport()
79
+ vp.set_shadow_type(Gtk.ShadowType.NONE)
80
+ vp.add(goocalendar)
81
+ self.scroll = scroll = Gtk.ScrolledWindow()
82
+ scroll.add(vp)
83
+ scroll.set_policy(
84
+ Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
85
+ scroll.set_placement(Gtk.CornerType.TOP_LEFT)
86
+ viewport = Gtk.Viewport()
87
+ viewport.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
88
+ viewport.add(scroll)
89
+ self.widget.pack_start(viewport, expand=True, fill=True, padding=0)
90
+
91
+ if self.attributes.get('height') or self.attributes.get('width'):
92
+ scroll.set_size_request(
93
+ int(self.attributes.get('width', -1)),
94
+ int(self.attributes.get('height', -1)))
95
+
88
96
  def on_page_changed(self, goocalendar, day, toolbar):
89
97
  toolbar.update_displayed_date()
90
98
  if goocalendar.update_domain():
@@ -170,3 +178,8 @@ class ViewCalendar(View):
170
178
 
171
179
  def get_fields(self):
172
180
  return []
181
+
182
+ @property
183
+ def listed_records(self):
184
+ event_store = self.widgets['goocalendar'].event_store
185
+ return [e.record for e in event_store.get_events()]
@@ -108,28 +108,29 @@ class Toolbar(Gtk.Toolbar):
108
108
  self.insert(blank_widget, -1)
109
109
 
110
110
  day_button = Gtk.RadioToolButton()
111
- day_button.set_label(_('Day View'))
111
+ day_button.set_label(_("Day"))
112
112
  day_button.connect("clicked", self.on_day_button_clicked)
113
113
  day_button.add_accelerator(
114
114
  "clicked", self.accel_group, Gdk.KEY_d,
115
115
  Gdk.ModifierType.MODIFIER_MASK, Gtk.AccelFlags.VISIBLE)
116
- self.insert(day_button, -1)
117
116
 
118
117
  week_button = Gtk.RadioToolButton.new_from_widget(day_button)
119
- week_button.set_label(_('Week View'))
118
+ week_button.set_label(_("Week"))
120
119
  week_button.connect("clicked", self.on_week_button_clicked)
121
120
  week_button.add_accelerator(
122
121
  "clicked", self.accel_group, Gdk.KEY_w,
123
122
  Gdk.ModifierType.MODIFIER_MASK, Gtk.AccelFlags.VISIBLE)
124
- self.insert(week_button, -1)
125
123
 
126
124
  month_button = Gtk.RadioToolButton.new_from_widget(week_button)
127
- month_button.set_label_widget(Gtk.Label(label=_('Month View')))
125
+ month_button.set_label_widget(Gtk.Label(label=_("Month")))
128
126
  month_button.connect("clicked", self.on_month_button_clicked)
129
127
  month_button.add_accelerator(
130
128
  "clicked", self.accel_group, Gdk.KEY_m,
131
129
  Gdk.ModifierType.MODIFIER_MASK, Gtk.AccelFlags.VISIBLE)
130
+
132
131
  self.insert(month_button, -1)
132
+ self.insert(week_button, -1)
133
+ self.insert(day_button, -1)
133
134
  buttons = {
134
135
  'month': month_button,
135
136
  'week': week_button,
@@ -279,13 +279,18 @@ class FormXMLViewParser(XMLViewParser):
279
279
  return
280
280
  vbox = VBox(attrs=attributes)
281
281
  if attributes.get('string'):
282
+ attributes.setdefault('xexpand', 0)
283
+ attributes.setdefault('xalign', 0)
284
+ attributes.setdefault('yalign', 0.5)
282
285
  label = Label(label=attributes['string'], attrs=attributes)
283
286
  label.set_halign(get_align(
284
- attributes.get('xalign', 0.0),
285
- bool(attributes.get('xexpand', True))))
287
+ attributes['xalign'],
288
+ bool(attributes.get('xexpand'))))
286
289
  label.set_valign(get_align(
287
- attributes.get('yalign', 0.5),
288
- bool(attributes.get('yexpand', False))))
290
+ attributes['yalign'],
291
+ bool(attributes.get('yexpand'))))
292
+ label.props.xalign = float(attributes['xalign'])
293
+ label.props.yalign = float(attributes['yalign'])
289
294
  vbox.pack_start(label, expand=True, fill=True, padding=0)
290
295
  self.view.state_widgets.append(label)
291
296
  if name:
@@ -303,13 +308,17 @@ class FormXMLViewParser(XMLViewParser):
303
308
  attributes['xalign'] = 0.0
304
309
 
305
310
  attributes.setdefault('xexpand', 0)
311
+ attributes.setdefault('xalign', 1)
312
+ attributes.setdefault('yalign', 0.5)
306
313
  label = Label(label=attributes.get('string', ''), attrs=attributes)
307
314
  label.set_halign(get_align(
308
- attributes.get('xalign', 1.0),
315
+ attributes['xalign'],
309
316
  bool(attributes.get('xexpand'))))
310
317
  label.set_valign(get_align(
311
318
  attributes.get('yalign', 0.5),
312
319
  bool(attributes.get('yexpand'))))
320
+ label.props.xalign = float(attributes['xalign'])
321
+ label.props.yalign = float(attributes['yalign'])
313
322
  label.set_angle(int(attributes.get('angle', 0)))
314
323
  self.view.state_widgets.append(label)
315
324
  self.container.add(label, attributes)
@@ -72,6 +72,9 @@ class Many2Many(Widget):
72
72
  self.but_add.set_relief(Gtk.ReliefStyle.NONE)
73
73
  hbox.pack_start(self.but_add, expand=False, fill=False, padding=0)
74
74
 
75
+ self.label = Gtk.Label(label='(_/0)')
76
+ hbox.pack_start(self.label, expand=False, fill=False, padding=0)
77
+
75
78
  self.but_remove = Gtk.Button(can_focus=False)
76
79
  tooltips.set_tip(self.but_remove, _('Remove selected record'))
77
80
  self.but_remove.connect('clicked', self._sig_remove)
@@ -279,8 +282,14 @@ class Many2Many(Widget):
279
282
  not self._readonly
280
283
  and self._position))
281
284
 
282
- def record_message(self, position, *args):
285
+ def record_message(self, position, size, *args):
283
286
  self._position = position
287
+ name = str(position) if position else '_'
288
+ selected = len(self.screen.selected_records)
289
+ if selected > 1:
290
+ name += '#%i' % selected
291
+ name = '(%s/%s)' % (name, common.humanize(size))
292
+ self.label.set_text(name)
284
293
  self._set_button_sensitive()
285
294
 
286
295
  def display(self):
@@ -19,6 +19,7 @@ _ = gettext.gettext
19
19
 
20
20
 
21
21
  class Many2One(Widget):
22
+ default_width_chars = 12
22
23
 
23
24
  def __init__(self, view, attrs):
24
25
  super(Many2One, self).__init__(view, attrs)
@@ -62,7 +62,7 @@ class One2Many(Widget):
62
62
  self.but_pre.set_relief(Gtk.ReliefStyle.NONE)
63
63
  hbox.pack_start(self.but_pre, expand=False, fill=False, padding=0)
64
64
 
65
- self.label = Gtk.Label(label='(0,0)')
65
+ self.label = Gtk.Label(label='(_/0)')
66
66
  hbox.pack_start(self.label, expand=False, fill=False, padding=0)
67
67
 
68
68
  self.but_next = Gtk.Button(can_focus=False)
@@ -511,12 +511,12 @@ class One2Many(Widget):
511
511
  def record_message(self, position, size, *args):
512
512
  self._position = position
513
513
  self._length = size
514
- if self._position:
515
- name = str(self._position)
516
- else:
517
- name = '_'
518
- line = '(%s/%s)' % (name, self._length)
519
- self.label.set_text(line)
514
+ name = str(position) if position else '_'
515
+ selected = len(self.screen.selected_records)
516
+ if selected > 1:
517
+ name += '#%i' % selected
518
+ name = '(%s/%s)' % (name, common.humanize(size))
519
+ self.label.set_text(name)
520
520
  self._set_button_sensitive()
521
521
 
522
522
  def display(self):
@@ -94,8 +94,6 @@ class TextBox(Widget, TranslateMixin):
94
94
  def _readonly_set(self, value):
95
95
  super(TextBox, self)._readonly_set(value)
96
96
  self.textview.set_editable(not value)
97
- if self.button:
98
- self.button.set_sensitive(not value)
99
97
 
100
98
  @property
101
99
  def modified(self):
@@ -16,7 +16,7 @@ _ = gettext.gettext
16
16
 
17
17
  class Widget(object):
18
18
  expand = False
19
- default_width_chars = 25
19
+ default_width_chars = 8
20
20
 
21
21
  def __init__(self, view, attrs):
22
22
  super(Widget, self).__init__()
@@ -204,17 +204,15 @@ class TranslateDialog(NoModal):
204
204
  widget.set_vexpand(self.widget.expand)
205
205
  widget.set_hexpand(True)
206
206
  grid.attach(widget, 1, i, 1, 1)
207
- editing = Gtk.CheckButton()
207
+ editing = Gtk.ToggleButton(label=_("Edit"))
208
208
  editing.connect('toggled', self.editing_toggled, widget)
209
209
  editing.props.sensitive = not readonly
210
- tooltips.set_tip(editing, _('Edit'))
211
210
  grid.attach(editing, 2, i, 1, 1)
212
- fuzzy = Gtk.CheckButton()
213
- fuzzy.set_active(value != fuzzy_value)
214
- fuzzy.props.sensitive = False
215
- tooltips.set_tip(fuzzy, _('Fuzzy'))
216
- grid.attach(fuzzy, 4, i, 1, 1)
217
- self.widgets[language['code']] = (widget, editing, fuzzy)
211
+ if value != fuzzy_value:
212
+ fuzzy = Gtk.Label(_("Fuzzy"))
213
+ widget_class(fuzzy, 'warning', True)
214
+ grid.attach(fuzzy, 4, i, 1, 1)
215
+ self.widgets[language['code']] = (widget, editing)
218
216
 
219
217
  tooltips.enable()
220
218
  vbox = Gtk.VBox()
@@ -241,7 +239,7 @@ class TranslateDialog(NoModal):
241
239
  def response(self, win, response):
242
240
  if response == Gtk.ResponseType.OK:
243
241
  for code, widget in self.widgets.items():
244
- widget, editing, fuzzy = widget
242
+ widget, editing = widget
245
243
  if not editing.get_active():
246
244
  continue
247
245
  value = self.widget.translate_widget_get(widget)
@@ -1,6 +1,6 @@
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
- from gi.repository import Gio, GObject, Gtk
3
+ from gi.repository import Gio, GLib, GObject, Gtk
4
4
 
5
5
  from tryton.common import common
6
6
 
@@ -119,6 +119,10 @@ class ViewListForm(View):
119
119
  return [
120
120
  self._model.get_item(r.get_index()).record for r in selected_rows]
121
121
 
122
+ @property
123
+ def listed_records(self):
124
+ return list(self._model.group)
125
+
122
126
  def group_list_changed(self, group, action, *args):
123
127
  if action == 'record-added':
124
128
  record, position = args
@@ -135,17 +139,69 @@ class ViewListForm(View):
135
139
  self._select_show_row(idx)
136
140
  break
137
141
 
138
- def _row_selected(self, listbox, row):
139
- if not row:
142
+ def get_selected_paths(self):
143
+ return [[r.id] for r in self.selected_records]
144
+
145
+ def select_nodes(self, nodes):
146
+ if not nodes:
140
147
  return
141
- self.record = self._model.get_item(row.get_index()).record
148
+ nodes = {n[0] for n in nodes}
149
+ self.listbox.handler_block_by_func(self.select_nodes)
150
+ self.listbox.unselect_all()
151
+ for idx, view_form in enumerate(self._view_forms):
152
+ if view_form.record.id in nodes:
153
+ row = self.listbox.get_row_at_index(idx)
154
+ if not row:
155
+ continue
156
+ self.listbox.select_row(row)
157
+ self.listbox.handler_unblock_by_func(self.select_nodes)
158
+
159
+ def _row_selected(self, listbox, row):
160
+ previous_record = self.record
161
+ if (previous_record
162
+ and previous_record not in previous_record.group):
163
+ previous_record = None
164
+
165
+ if row:
166
+ self.record = self._model.get_item(row.get_index()).record
167
+ else:
168
+ self.record = None
169
+
170
+ def go_previous():
171
+ self.record = previous_record
172
+ self.set_cursor()
173
+
174
+ def save():
175
+ if not previous_record.destroyed:
176
+ if not previous_record.save():
177
+ go_previous()
178
+
179
+ def pre_validate():
180
+ if not previous_record.destroyed:
181
+ if not previous_record.pre_validate():
182
+ go_previous()
183
+
184
+ if previous_record and previous_record != self.record:
185
+ if not self.screen.parent:
186
+ if not previous_record.validate(self.get_fields()):
187
+ go_previous()
188
+ return True
189
+ GLib.idle_add(save)
190
+ elif self.screen.pre_validate:
191
+ GLib.idle_add(pre_validate)
142
192
 
143
193
  @common.idle_add
144
194
  def _select_show_row(self, index):
145
195
  # translate_coordinates requires that both widgets are realized
146
196
  if not self.listbox.get_realized():
147
197
  return
148
- self.listbox.unselect_all()
198
+ # unselect_all triggers a loop in _row_selected if the record is not
199
+ # valid
200
+ self.listbox.handler_block_by_func(self._select_show_row)
201
+ try:
202
+ self.listbox.unselect_all()
203
+ finally:
204
+ self.listbox.handler_unblock_by_func(self._select_show_row)
149
205
  row = self.listbox.get_row_at_index(index)
150
206
  if not row or not row.get_realized():
151
207
  return
@@ -1,6 +1,7 @@
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
3
  import gettext
4
+ import logging
4
5
  from itertools import chain, cycle, islice
5
6
 
6
7
  from gi.repository import Gdk, GLib, Gtk
@@ -9,6 +10,7 @@ from tryton.common import MODELACCESS
9
10
  from tryton.common.datetime_ import Date, Time
10
11
 
11
12
  _ = gettext.gettext
13
+ logger = logging.getLogger(__name__)
12
14
 
13
15
 
14
16
  def focusable_cells(column, editable=True):
@@ -51,7 +53,14 @@ class TreeView(Gtk.TreeView):
51
53
  if not column.name:
52
54
  continue
53
55
  widget = self.view.get_column_widget(column)
54
- field = record[column.name]
56
+ try:
57
+ record.load(column.name, process_exception=False)
58
+ except Exception:
59
+ logger.error(
60
+ f"Error loading '{column.name}' for {record}",
61
+ exc_info=True)
62
+ return (None, None)
63
+ field = record.group.fields[column.name]
55
64
  field.state_set(record, states=('readonly', 'invisible'))
56
65
  invisible = field.get_state_attrs(record).get('invisible', False)
57
66
  if not column.get_visible():
@@ -83,7 +92,8 @@ class EditableTreeView(TreeView):
83
92
 
84
93
  def on_quit_cell(
85
94
  self, current_record, column, renderer, value, callback=None):
86
- field = current_record[column.name]
95
+ current_record.load(column.name, process_exception=False)
96
+ field = current_record.group.fields[column.name]
87
97
  widget = self.view.get_column_widget(column)
88
98
 
89
99
  # The value has not changed and is valid ... do nothing.
@@ -271,7 +281,7 @@ class EditableTreeView(TreeView):
271
281
  create=(event.keyval == Gdk.KEY_F3), value=value,
272
282
  callback=callback)
273
283
  else:
274
- field = record[column.name]
284
+ field = record.group.fields[column.name]
275
285
  if isinstance(entry, Gtk.Entry):
276
286
  entry.set_max_length(int(field.attrs.get('size', 0)))
277
287
  record.modified_fields.setdefault(column.name)