tryton 7.2.13__py3-none-any.whl → 7.4.4__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 (79) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/common/common.py +12 -4
  3. tryton/common/domain_parser.py +5 -4
  4. tryton/common/popup_menu.py +1 -1
  5. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  6. tryton/data/locale/bg/LC_MESSAGES/tryton.po +27 -16
  7. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  8. tryton/data/locale/ca/LC_MESSAGES/tryton.po +23 -17
  9. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  10. tryton/data/locale/cs/LC_MESSAGES/tryton.po +26 -17
  11. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  12. tryton/data/locale/de/LC_MESSAGES/tryton.po +27 -21
  13. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  14. tryton/data/locale/es/LC_MESSAGES/tryton.po +22 -16
  15. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  16. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +31 -19
  17. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  18. tryton/data/locale/et/LC_MESSAGES/tryton.po +27 -17
  19. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  20. tryton/data/locale/fa/LC_MESSAGES/tryton.po +28 -18
  21. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  22. tryton/data/locale/fi/LC_MESSAGES/tryton.po +22 -16
  23. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  24. tryton/data/locale/fr/LC_MESSAGES/tryton.po +28 -22
  25. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  26. tryton/data/locale/hu/LC_MESSAGES/tryton.po +29 -17
  27. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  28. tryton/data/locale/id/LC_MESSAGES/tryton.po +31 -21
  29. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  30. tryton/data/locale/it/LC_MESSAGES/tryton.po +30 -20
  31. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  32. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  33. tryton/data/locale/lo/LC_MESSAGES/tryton.po +29 -19
  34. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  35. tryton/data/locale/lt/LC_MESSAGES/tryton.po +27 -17
  36. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  37. tryton/data/locale/nl/LC_MESSAGES/tryton.po +26 -20
  38. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/pl/LC_MESSAGES/tryton.po +29 -18
  40. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/pt/LC_MESSAGES/tryton.po +28 -18
  42. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  43. tryton/data/locale/ro/LC_MESSAGES/tryton.po +37 -26
  44. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  45. tryton/data/locale/ru/LC_MESSAGES/tryton.po +29 -19
  46. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  47. tryton/data/locale/sl/LC_MESSAGES/tryton.po +45 -35
  48. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  49. tryton/data/locale/tr/LC_MESSAGES/tryton.po +23 -16
  50. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  51. tryton/data/locale/uk/LC_MESSAGES/tryton.po +31 -19
  52. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  53. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +32 -21
  54. tryton/gui/main.py +11 -10
  55. tryton/gui/window/email_.py +1 -1
  56. tryton/gui/window/form.py +4 -1
  57. tryton/gui/window/tabcontent.py +2 -2
  58. tryton/gui/window/view_form/model/group.py +4 -1
  59. tryton/gui/window/view_form/model/record.py +9 -19
  60. tryton/gui/window/view_form/screen/screen.py +15 -1
  61. tryton/gui/window/view_form/view/form_gtk/dictionary.py +12 -5
  62. tryton/gui/window/view_form/view/form_gtk/document.py +6 -0
  63. tryton/gui/window/view_form/view/form_gtk/many2many.py +32 -0
  64. tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
  65. tryton/gui/window/view_form/view/form_gtk/one2many.py +17 -4
  66. tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
  67. tryton/gui/window/view_form/view/list.py +49 -14
  68. tryton/gui/window/win_csv.py +6 -12
  69. tryton/gui/window/win_export.py +49 -25
  70. tryton/gui/window/win_import.py +36 -11
  71. tryton/jsonrpc.py +29 -6
  72. tryton/pyson.py +2 -1
  73. tryton/tests/test_common_domain_parser.py +23 -2
  74. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/METADATA +2 -2
  75. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/RECORD +79 -79
  76. {tryton-7.2.13.data → tryton-7.4.4.data}/scripts/tryton +0 -0
  77. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
  78. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/WHEEL +0 -0
  79. {tryton-7.2.13.dist-info → tryton-7.4.4.dist-info}/top_level.txt +0 -0
@@ -140,9 +140,9 @@ msgstr "并行操作异常"
140
140
  msgid "Could not get a session."
141
141
  msgstr "无法获取会话。"
142
142
 
143
- #, python-format
143
+ #, fuzzy, python-format
144
144
  msgid "Error: \"%s\". Try again later."
145
- msgstr ""
145
+ msgstr "错误:“%s”。请稍后重试。"
146
146
 
147
147
  msgid "Too many requests. Try again later."
148
148
  msgstr "请求太多,请稍后重试."
@@ -240,8 +240,8 @@ msgstr "报告..."
240
240
  msgid "Print..."
241
241
  msgstr "打印..."
242
242
 
243
- msgid "E-Mail..."
244
- msgstr "用电子邮件发送..."
243
+ msgid "Email..."
244
+ msgstr "电子邮件(_E)..."
245
245
 
246
246
  msgid "Bold"
247
247
  msgstr "粗体"
@@ -351,12 +351,6 @@ msgstr "帮助"
351
351
  msgid "No result found."
352
352
  msgstr "无符合条件的数据."
353
353
 
354
- msgid "Favorites"
355
- msgstr "收藏(_V)"
356
-
357
- msgid "Manage..."
358
- msgstr "管理..."
359
-
360
354
  msgid "Action"
361
355
  msgstr "操作"
362
356
 
@@ -587,6 +581,9 @@ msgstr "连接 Tryton 服务器"
587
581
  msgid "Profile:"
588
582
  msgstr "配置:"
589
583
 
584
+ msgid "Manage..."
585
+ msgstr "管理..."
586
+
590
587
  msgid "Host / Database information"
591
588
  msgstr "主机/数据库信息"
592
589
 
@@ -596,8 +593,8 @@ msgstr "用户名:"
596
593
  msgid "Unable to complete email entry"
597
594
  msgstr "无法完成电子邮件输入"
598
595
 
599
- #, python-format
600
- msgid "E-mail %s"
596
+ #, fuzzy, python-format
597
+ msgid "Email %s"
601
598
  msgstr "电子邮件 %s"
602
599
 
603
600
  msgid "To:"
@@ -845,10 +842,12 @@ msgstr "报告(_R)..."
845
842
  msgid "_Print..."
846
843
  msgstr "打印(_P)..."
847
844
 
848
- msgid "_E-Mail..."
849
- msgstr "电子邮件发送(_E)..."
845
+ #, fuzzy
846
+ msgid "_Email..."
847
+ msgstr "电子邮件(_E)..."
850
848
 
851
- msgid "Send an e-mail using the record"
849
+ #, fuzzy
850
+ msgid "Send an email using the record"
852
851
  msgstr "使用当前记录发送电子邮件"
853
852
 
854
853
  msgid "_Export Data..."
@@ -900,6 +899,9 @@ msgstr "字段名"
900
899
  msgid "CSV Export: %s"
901
900
  msgstr "CSV导出:%s"
902
901
 
902
+ msgid "Open"
903
+ msgstr "打开"
904
+
903
905
  msgid "_Save Export"
904
906
  msgstr "保存导出设置(_S)"
905
907
 
@@ -915,12 +917,6 @@ msgstr "<b>预定义导出设置</b>"
915
917
  msgid "Name"
916
918
  msgstr "名称"
917
919
 
918
- msgid "Open"
919
- msgstr "打开"
920
-
921
- msgid "Save"
922
- msgstr "保存"
923
-
924
920
  msgid "Listed Records"
925
921
  msgstr "已列出记录"
926
922
 
@@ -978,6 +974,9 @@ msgstr "保存和新建"
978
974
  msgid "Add and New"
979
975
  msgstr "添加和新建"
980
976
 
977
+ msgid "Save"
978
+ msgstr "保存"
979
+
981
980
  msgid "Add"
982
981
  msgstr "添加"
983
982
 
@@ -1003,6 +1002,10 @@ msgstr "已选记录取消删除 <Ins>"
1003
1002
  msgid "CSV Import: %s"
1004
1003
  msgstr "CSV导入: %s"
1005
1004
 
1005
+ #, fuzzy
1006
+ msgid "Import"
1007
+ msgstr "报告(_R)"
1008
+
1006
1009
  msgid "_Auto-Detect"
1007
1010
  msgstr "自动设置(_A)"
1008
1011
 
@@ -1206,6 +1209,10 @@ msgstr "添加已存在记录"
1206
1209
  msgid "Remove selected record"
1207
1210
  msgstr "删除所选记录"
1208
1211
 
1212
+ #, fuzzy
1213
+ msgid "Restore selected record"
1214
+ msgstr "删除所选记录"
1215
+
1209
1216
  msgid "Open the record <F2>"
1210
1217
  msgstr "打开记录 <F2>"
1211
1218
 
@@ -1221,6 +1228,10 @@ msgstr "编辑所选记录<F2>"
1221
1228
  msgid "Delete selected record"
1222
1229
  msgstr "删除所选记录"
1223
1230
 
1231
+ #, fuzzy
1232
+ msgid "Undelete selected record"
1233
+ msgstr "删除所选记录"
1234
+
1224
1235
  #, python-format
1225
1236
  msgid "%s%%"
1226
1237
  msgstr "%s%%"
tryton/gui/main.py CHANGED
@@ -463,10 +463,6 @@ class Main(Gtk.Application):
463
463
  'id': id_,
464
464
  })
465
465
 
466
- def _manage_favorites(widget):
467
- Window.create(self.menu_screen.model_name + '.favorite',
468
- mode=['tree', 'form'],
469
- name=_("Favorites"))
470
466
  try:
471
467
  favorites = RPCExecute('model',
472
468
  self.menu_screen.model_name + '.favorite', 'get',
@@ -477,11 +473,6 @@ class Main(Gtk.Application):
477
473
  menuitem = Gtk.MenuItem(label=name)
478
474
  menuitem.connect('activate', _action_favorite, id_)
479
475
  self.menu_favorite.add(menuitem)
480
- self.menu_favorite.add(Gtk.SeparatorMenuItem())
481
- manage_favorites = Gtk.MenuItem(label=_("Manage..."),
482
- use_underline=True)
483
- manage_favorites.connect('activate', _manage_favorites)
484
- self.menu_favorite.add(manage_favorites)
485
476
  self.menu_favorite.show_all()
486
477
 
487
478
  def favorite_unset(self):
@@ -772,6 +763,9 @@ class Main(Gtk.Application):
772
763
  expand=True, fill=True, padding=0)
773
764
  treeview = screen.current_view.treeview
774
765
  treeview.set_headers_visible(False)
766
+ for col in treeview.get_columns():
767
+ if col._type in {'selection', 'optional', 'drag'}:
768
+ col.set_visible(False)
775
769
 
776
770
  # Favorite column
777
771
  column = Gtk.TreeViewColumn()
@@ -864,7 +858,14 @@ class Main(Gtk.Application):
864
858
  halign=Gtk.Align.START)
865
859
  self.tooltips.set_tip(label, page.name)
866
860
  self.tooltips.enable()
867
- hbox.pack_start(label, expand=True, fill=True, padding=0)
861
+
862
+ def remove_book_middle_click(widget, event):
863
+ if event.button == 2:
864
+ return self._sig_remove_book(widget, page.widget)
865
+ eventbox = Gtk.EventBox()
866
+ eventbox.connect('button-press-event', remove_book_middle_click)
867
+ eventbox.add(label)
868
+ hbox.pack_start(eventbox, expand=True, fill=True, padding=0)
868
869
 
869
870
  button = Gtk.Button()
870
871
  button.set_relief(Gtk.ReliefStyle.NONE)
@@ -106,7 +106,7 @@ class Email(NoModal):
106
106
  self.dialog.set_default_size(*self.default_size())
107
107
  self.dialog.connect('response', self.response)
108
108
 
109
- self.dialog.set_title(_('E-mail %s') % name)
109
+ self.dialog.set_title(_('Email %s') % name)
110
110
 
111
111
  grid = Gtk.Grid(
112
112
  column_spacing=3, row_spacing=3,
tryton/gui/window/form.py CHANGED
@@ -579,6 +579,7 @@ class Form(TabContent):
579
579
  name = str(position) if position else '_'
580
580
  selected = len(self.screen.selected_records)
581
581
  view_type = self.screen.current_view.view_type
582
+ next_view_type = self.screen.next_view_type
582
583
  has_views = self.screen.number_of_views > 1
583
584
  if selected > 1:
584
585
  name += '#%i' % selected
@@ -594,7 +595,9 @@ class Form(TabContent):
594
595
  for b in self.screen.get_buttons())
595
596
  set_sensitive(button_id, bool(position) and can_be_sensitive)
596
597
  set_sensitive(
597
- 'switch', (position or view_type == 'form') and has_views)
598
+ 'switch',
599
+ (position or view_type == 'form' or next_view_type != 'form')
600
+ and has_views)
598
601
  set_sensitive('remove', self.screen.deletable)
599
602
  set_sensitive('previous', self.screen.has_prev())
600
603
  set_sensitive('next', self.screen.has_next())
@@ -141,8 +141,8 @@ class TabContent(InfoBar):
141
141
  accel_path='<tryton>/Form/Print'),
142
142
  ToolbarItem(
143
143
  id='email',
144
- label=_("_E-Mail..."),
145
- tooltip=_("Send an e-mail using the record"),
144
+ label=_("_Email..."),
145
+ tooltip=_("Send an email using the record"),
146
146
  icon_name='tryton-email',
147
147
  accel_path='<tryton>/Form/Email'),
148
148
  None,
@@ -128,6 +128,7 @@ class Group(list):
128
128
  self._group_list_changed('record-removed', record, idx)
129
129
  super(Group, self).remove(record)
130
130
  del self.__id2record[record.id]
131
+ record.destroy()
131
132
 
132
133
  def clear(self):
133
134
  # Use reversed order to minimize the cursor reposition as the cursor
@@ -408,7 +409,9 @@ class Group(list):
408
409
  if record not in self.record_deleted:
409
410
  self.record_deleted.append(record)
410
411
  record.modified_fields.setdefault('id')
411
- if record.id < 0 or force_remove:
412
+ if (record.id < 0
413
+ or (self.parent and self.parent.id < 0)
414
+ or force_remove):
412
415
  self._remove(record)
413
416
 
414
417
  if len(self):
@@ -177,10 +177,10 @@ class Record:
177
177
  for record in id2record.values():
178
178
  record.exception = True
179
179
  if process_exception:
180
- values = [{'id': x} for x in id2record]
180
+ values = []
181
181
  default_values = {f: None for f in fnames if f != 'id'}
182
- for value in values:
183
- value.update(default_values)
182
+ for id in id2record:
183
+ values.append({'id': id, **default_values})
184
184
  else:
185
185
  raise
186
186
  id2value = dict((value['id'], value) for value in values)
@@ -299,20 +299,13 @@ class Record:
299
299
  return self.group.fields
300
300
 
301
301
  def _check_load(self, fields=None):
302
- if fields is not None:
303
- if not self.get_loaded(fields):
304
- self.reload(fields)
305
- return True
306
- return False
307
- if not self.loaded:
308
- self.reload()
309
- return True
310
- return False
302
+ if not self.get_loaded(fields):
303
+ self.reload(fields)
311
304
 
312
305
  def get_loaded(self, fields=None):
313
- if fields:
314
- return set(fields) <= (self._loaded | set(self.modified_fields))
315
- return set(self.group.fields.keys()) == self._loaded
306
+ if fields is None:
307
+ fields = self.group.fields.keys()
308
+ return set(fields) <= (self._loaded | set(self.modified_fields))
316
309
 
317
310
  loaded = property(get_loaded)
318
311
 
@@ -444,10 +437,7 @@ class Record:
444
437
  return ''
445
438
 
446
439
  def validate(self, fields=None, softvalidation=False, pre_validate=None):
447
- if isinstance(fields, list) and fields:
448
- self._check_load(fields)
449
- elif fields is None:
450
- self._check_load()
440
+ self._check_load(fields)
451
441
  res = True
452
442
  for field_name, field in list(self.group.fields.items()):
453
443
  if fields is not None and field_name not in fields:
@@ -323,6 +323,7 @@ class Screen:
323
323
  if only_ids:
324
324
  return ids
325
325
  self.clear()
326
+ GLib.idle_add(self.screen_container.search_entry.grab_focus)
326
327
  self.load(ids)
327
328
  self.count_tab_domain()
328
329
  return bool(ids)
@@ -541,6 +542,15 @@ class Screen:
541
542
  def view_index(self):
542
543
  return self.__current_view
543
544
 
545
+ @property
546
+ def next_view_type(self):
547
+ views = self.views + self.view_to_load
548
+ next_view_index = (self.view_index + 1) % len(views)
549
+ next_view = views[next_view_index]
550
+ if next_view and not isinstance(next_view, str):
551
+ next_view = next_view.view_type
552
+ return next_view
553
+
544
554
  def switch_view(
545
555
  self, view_type=None, view_id=None, creatable=None, display=True):
546
556
  if view_id is not None:
@@ -792,6 +802,7 @@ class Screen:
792
802
  records = records or self.selected_records
793
803
  if not records:
794
804
  return
805
+ current_record = self.current_record
795
806
  if delete:
796
807
  # Must delete children records before parent
797
808
  records.sort(key=lambda r: r.depth, reverse=True)
@@ -822,7 +833,10 @@ class Screen:
822
833
  record.parent.save(force_reload=False)
823
834
  record.destroy()
824
835
 
825
- self.current_record = None
836
+ if current_record and not current_record.destroyed:
837
+ self.current_record = current_record
838
+ else:
839
+ self.current_record = None
826
840
  self.set_cursor()
827
841
  self.display()
828
842
  return True
@@ -224,17 +224,24 @@ class DictMultiSelectionEntry(DictEntry):
224
224
  name = str(name)
225
225
  model.append((value, name))
226
226
 
227
- name_column = Gtk.TreeViewColumn()
227
+ column = Gtk.TreeViewColumn()
228
+ select_cell = Gtk.CellRendererToggle()
229
+ select_cell.set_sensitive(False)
230
+ column.pack_start(select_cell, expand=False)
231
+ column.set_cell_data_func(select_cell, self._select_data_func)
228
232
  name_cell = Gtk.CellRendererText()
229
- name_column.pack_start(name_cell, expand=True)
230
- name_column.add_attribute(name_cell, 'text', 1)
231
- self.tree.append_column(name_column)
233
+ column.pack_start(name_cell, expand=True)
234
+ column.add_attribute(name_cell, 'text', 1)
235
+ self.tree.append_column(column)
232
236
 
233
237
  return widget
234
238
 
239
+ def _select_data_func(self, column, cell, model, iter_, selection):
240
+ cell.set_property('active', selection.iter_is_selected(iter_))
241
+
235
242
  def get_value(self):
236
243
  model, paths = self.tree.get_selection().get_selected_rows()
237
- return [model[path][0] for path in paths]
244
+ return [model[path][0] for path in paths] or None
238
245
 
239
246
  def set_value(self, value):
240
247
  value2path = {v: idx for idx, (v, _) in enumerate(self.selection)}
@@ -1,5 +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
+ import logging
3
4
  from pathlib import Path
4
5
 
5
6
  from gi.repository import Gdk, GLib, Gtk
@@ -15,6 +16,8 @@ from tryton.common import data2pixbuf, resize_pixbuf
15
16
  from .binary import BinaryMixin
16
17
  from .widget import Widget
17
18
 
19
+ logger = logging.getLogger(__name__)
20
+
18
21
 
19
22
  class Document(BinaryMixin, Widget):
20
23
  expand = True
@@ -90,5 +93,8 @@ class Document(BinaryMixin, Widget):
90
93
  model.set_document(document)
91
94
  self.evince_view.set_model(model)
92
95
  except GLib.GError:
96
+ logger.warning(
97
+ f"Could not open document {filename}",
98
+ exc_info=True)
93
99
  self.evince_view.set_model(EvinceView.DocumentModel())
94
100
  self.evince_scroll.hide()
@@ -82,6 +82,14 @@ class Many2Many(Widget):
82
82
  self.but_remove.set_relief(Gtk.ReliefStyle.NONE)
83
83
  hbox.pack_start(self.but_remove, expand=False, fill=False, padding=0)
84
84
 
85
+ self.but_unremove = Gtk.Button(can_focus=False)
86
+ tooltips.set_tip(self.but_unremove, _("Restore selected record"))
87
+ self.but_unremove.connect('clicked', self._sig_unremove)
88
+ self.but_unremove.add(common.IconFactory.get_image(
89
+ 'tryton-undo', Gtk.IconSize.SMALL_TOOLBAR))
90
+ self.but_unremove.set_relief(Gtk.ReliefStyle.NONE)
91
+ hbox.pack_start(self.but_unremove, expand=False, fill=False, padding=0)
92
+
85
93
  tooltips.enable()
86
94
 
87
95
  frame = Gtk.Frame()
@@ -130,6 +138,9 @@ class Many2Many(Widget):
130
138
  elif event.keyval in remove_keys and editable:
131
139
  self._sig_remove()
132
140
  return True
141
+ elif event.keyval == Gdk.KEY_Insert:
142
+ self._sig_unremove()
143
+ return True
133
144
  elif widget == self.wid_text:
134
145
  if event.keyval == Gdk.KEY_F3:
135
146
  self._sig_new()
@@ -166,6 +177,9 @@ class Many2Many(Widget):
166
177
  add_remove = self.record.expr_eval(self.attrs.get('add_remove'))
167
178
  if add_remove:
168
179
  domain = [domain, add_remove]
180
+ existing_ids = self.field.get_eval(self.record)
181
+ if existing_ids:
182
+ domain = [domain, ('id', 'not in', existing_ids)]
169
183
  context = self.field.get_search_context(self.record)
170
184
  order = self.field.get_search_order(self.record)
171
185
  value = self.wid_text.get_text()
@@ -194,6 +208,9 @@ class Many2Many(Widget):
194
208
  def _sig_remove(self, *args):
195
209
  self.screen.remove(remove=True)
196
210
 
211
+ def _sig_unremove(self, *args):
212
+ self.screen.unremove()
213
+
197
214
  def _on_activate(self):
198
215
  self._sig_edit()
199
216
 
@@ -284,11 +301,23 @@ class Many2Many(Widget):
284
301
  else:
285
302
  size_limit = False
286
303
 
304
+ removable = any(
305
+ not r.deleted and not r.removed
306
+ for r in self.screen.selected_records)
307
+ unremovable = any(
308
+ r.deleted or r.removed for r in self.screen.selected_records)
309
+
287
310
  self.but_add.set_sensitive(bool(
288
311
  not self._readonly
289
312
  and not size_limit))
290
313
  self.but_remove.set_sensitive(bool(
291
314
  not self._readonly
315
+ and removable
316
+ and self._position))
317
+ self.but_unremove.set_sensitive(bool(
318
+ not self._readonly
319
+ and not size_limit
320
+ and unremovable
292
321
  and self._position))
293
322
 
294
323
  def record_message(self, position, size, *args):
@@ -343,6 +372,9 @@ class Many2Many(Widget):
343
372
  add_remove = self.record.expr_eval(self.attrs.get('add_remove'))
344
373
  if add_remove:
345
374
  domain = [domain, add_remove]
375
+ existing_ids = self.field.get_eval(self.record)
376
+ if existing_ids:
377
+ domain = [domain, ('id', 'not in', existing_ids)]
346
378
  update_completion(
347
379
  self.wid_text, self.record, self.field, model, domain)
348
380
 
@@ -33,15 +33,23 @@ class MultiSelection(Widget, SelectionMixin):
33
33
  selection.set_mode(Gtk.SelectionMode.MULTIPLE)
34
34
  selection.connect('changed', self.changed)
35
35
  self.widget.add(self.tree)
36
- name_column = Gtk.TreeViewColumn()
36
+ column = Gtk.TreeViewColumn()
37
+ select_cell = Gtk.CellRendererToggle()
38
+ select_cell.set_sensitive(False)
39
+ column.pack_start(select_cell, expand=False)
40
+ column.set_cell_data_func(
41
+ select_cell, self._select_data_func, selection)
37
42
  name_cell = Gtk.CellRendererText()
38
- name_column.pack_start(name_cell, expand=True)
39
- name_column.add_attribute(name_cell, 'text', 1)
40
- self.tree.append_column(name_column)
43
+ column.pack_start(name_cell, expand=True)
44
+ column.add_attribute(name_cell, 'text', 1)
45
+ self.tree.append_column(column)
41
46
 
42
47
  self.nullable_widget = False
43
48
  self.init_selection()
44
49
 
50
+ def _select_data_func(self, column, cell, model, iter_, selection):
51
+ cell.set_property('active', selection.iter_is_selected(iter_))
52
+
45
53
  def _readonly_set(self, readonly):
46
54
  super(MultiSelection, self)._readonly_set(readonly)
47
55
  selection = self.tree.get_selection()
@@ -70,6 +78,8 @@ class MultiSelection(Widget, SelectionMixin):
70
78
  self.field.set_client(self.record, self.get_value())
71
79
 
72
80
  def display(self):
81
+ def freeze(iter):
82
+ return list(map(tuple, iter))
73
83
  selection = self.tree.get_selection()
74
84
  selection.handler_block_by_func(self.changed)
75
85
  try:
@@ -77,7 +87,7 @@ class MultiSelection(Widget, SelectionMixin):
77
87
  # it will be set back in the super call
78
88
  selection.set_select_function(lambda *a: True)
79
89
  self.update_selection(self.record, self.field)
80
- new_model = self.selection != [list(row) for row in self.model]
90
+ new_model = freeze(self.selection) != freeze(self.model)
81
91
  if new_model:
82
92
  self.model.clear()
83
93
  if not self.field:
@@ -141,7 +141,7 @@ class One2Many(Widget):
141
141
  hbox.pack_start(self.but_del, expand=False, fill=False, padding=0)
142
142
 
143
143
  self.but_undel = Gtk.Button(can_focus=False)
144
- tooltips.set_tip(self.but_undel, _('Undelete selected record <Ins>'))
144
+ tooltips.set_tip(self.but_undel, _("Undelete selected record"))
145
145
  self.but_undel.connect('clicked', self._sig_undelete)
146
146
  self.but_undel.add(common.IconFactory.get_image(
147
147
  'tryton-undo', Gtk.IconSize.SMALL_TOOLBAR))
@@ -295,7 +295,13 @@ class One2Many(Widget):
295
295
  if isinstance(self._position, int):
296
296
  first = self._position <= 1
297
297
  last = self._position >= self._length
298
- deletable = self.screen.deletable
298
+ deletable = (
299
+ self.screen.deletable
300
+ and any(
301
+ not r.deleted and not r.removed
302
+ for r in self.screen.selected_records))
303
+ undeletable = any(
304
+ r.deleted or r.removed for r in self.screen.selected_records)
299
305
  view_type = self.screen.current_view.view_type
300
306
  has_views = self.screen.number_of_views > 1
301
307
 
@@ -313,15 +319,16 @@ class One2Many(Widget):
313
319
  self.but_undel.set_sensitive(bool(
314
320
  not self._readonly
315
321
  and not size_limit
322
+ and undeletable
316
323
  and self._position))
317
324
  self.but_open.set_sensitive(bool(
318
325
  self._position
319
326
  and self.read_access))
320
327
  self.but_next.set_sensitive(bool(
321
- self._position
328
+ self._length
322
329
  and not last))
323
330
  self.but_pre.set_sensitive(bool(
324
- self._position
331
+ self._length
325
332
  and not first))
326
333
  if self.attrs.get('add_remove'):
327
334
  self.but_add.set_sensitive(bool(
@@ -499,6 +506,9 @@ class One2Many(Widget):
499
506
  domain = self.field.domain_get(self.record)
500
507
  context = self.field.get_search_context(self.record)
501
508
  domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'))]
509
+ existing_ids = self.field.get_eval(self.record)
510
+ if existing_ids:
511
+ domain = [domain, ('id', 'not in', existing_ids)]
502
512
  removed_ids = self.field.get_removed_ids(self.record)
503
513
  domain = ['OR', domain, ('id', 'in', removed_ids)]
504
514
  text = self.wid_text.get_text()
@@ -605,6 +615,9 @@ class One2Many(Widget):
605
615
  model = self.attrs['relation']
606
616
  domain = self.field.domain_get(self.record)
607
617
  domain = [domain, self.record.expr_eval(self.attrs.get('add_remove'))]
618
+ existing_ids = self.field.get_eval(self.record)
619
+ if existing_ids:
620
+ domain = [domain, ('id', 'not in', existing_ids)]
608
621
  removed_ids = self.field.get_removed_ids(self.record)
609
622
  domain = ['OR', domain, ('id', 'in', removed_ids)]
610
623
  update_completion(self.wid_text, self.record, self.field, model,
@@ -130,8 +130,10 @@ class HTML(Widget, TranslateMixin):
130
130
  self.button, expand=False, fill=False, padding=0)
131
131
 
132
132
  if attrs.get('translate'):
133
- self.widget.pack_start(
134
- self.translate_button(), expand=False, fill=False, padding=0)
133
+ self.button.set_image(common.IconFactory.get_image(
134
+ 'tryton-translate', Gtk.IconSize.SMALL_TOOLBAR))
135
+ self.button.set_always_show_image(True)
136
+ self.button.connect('clicked', self.translate)
135
137
 
136
138
  def uri(self, language=None):
137
139
  if not self.record or self.record.id < 0 or CONNECTION.url is None:
@@ -149,7 +151,8 @@ class HTML(Widget, TranslateMixin):
149
151
 
150
152
  def display(self):
151
153
  super().display()
152
- self.button.set_uri(self.uri())
154
+ if self.attrs.get('translate'):
155
+ self.button.set_uri(self.uri())
153
156
 
154
157
  def _readonly_set(self, value):
155
158
  super()._readonly_set(value)
@@ -158,6 +161,7 @@ class HTML(Widget, TranslateMixin):
158
161
 
159
162
  def translate_dialog(self, languages):
160
163
  languages = {l['name']: l['code'] for l in languages}
161
- result = selection(_('Choose a language'), languages)
164
+ result = selection(
165
+ _('Choose a language'), languages, default=CONFIG['client.lang'])
162
166
  if result:
163
167
  webbrowser.open(self.uri(language=result[1]), new=2)