tryton 7.0.6__py3-none-any.whl → 7.2.13__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 (100) hide show
  1. tryton/__init__.py +1 -1
  2. tryton/cache.py +34 -0
  3. tryton/common/common.py +137 -69
  4. tryton/common/completion.py +2 -2
  5. tryton/common/datetime_.py +3 -1
  6. tryton/common/domain_inversion.py +10 -7
  7. tryton/common/domain_parser.py +17 -7
  8. tryton/common/selection.py +6 -3
  9. tryton/common/tempfile.py +34 -0
  10. tryton/config.py +4 -5
  11. tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
  12. tryton/data/locale/bg/LC_MESSAGES/tryton.po +42 -4
  13. tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
  14. tryton/data/locale/ca/LC_MESSAGES/tryton.po +47 -8
  15. tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
  16. tryton/data/locale/cs/LC_MESSAGES/tryton.po +42 -4
  17. tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
  18. tryton/data/locale/de/LC_MESSAGES/tryton.po +45 -6
  19. tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
  20. tryton/data/locale/es/LC_MESSAGES/tryton.po +46 -7
  21. tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
  22. tryton/data/locale/es_419/LC_MESSAGES/tryton.po +43 -5
  23. tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
  24. tryton/data/locale/et/LC_MESSAGES/tryton.po +46 -6
  25. tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
  26. tryton/data/locale/fa/LC_MESSAGES/tryton.po +46 -7
  27. tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
  28. tryton/data/locale/fi/LC_MESSAGES/tryton.po +41 -4
  29. tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
  30. tryton/data/locale/fr/LC_MESSAGES/tryton.po +46 -7
  31. tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
  32. tryton/data/locale/hu/LC_MESSAGES/tryton.po +46 -6
  33. tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
  34. tryton/data/locale/id/LC_MESSAGES/tryton.po +43 -4
  35. tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
  36. tryton/data/locale/it/LC_MESSAGES/tryton.po +45 -6
  37. tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
  38. tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
  39. tryton/data/locale/lo/LC_MESSAGES/tryton.po +45 -6
  40. tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
  41. tryton/data/locale/lt/LC_MESSAGES/tryton.po +46 -6
  42. tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
  43. tryton/data/locale/nl/LC_MESSAGES/tryton.po +46 -7
  44. tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
  45. tryton/data/locale/pl/LC_MESSAGES/tryton.po +84 -60
  46. tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
  47. tryton/data/locale/pt/LC_MESSAGES/tryton.po +45 -6
  48. tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
  49. tryton/data/locale/ro/LC_MESSAGES/tryton.po +57 -17
  50. tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
  51. tryton/data/locale/ru/LC_MESSAGES/tryton.po +43 -6
  52. tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
  53. tryton/data/locale/sl/LC_MESSAGES/tryton.po +46 -5
  54. tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
  55. tryton/data/locale/tr/LC_MESSAGES/tryton.po +41 -4
  56. tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
  57. tryton/data/locale/uk/LC_MESSAGES/tryton.po +46 -6
  58. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
  59. tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +46 -5
  60. tryton/device_cookie.py +1 -1
  61. tryton/gui/main.py +3 -2
  62. tryton/gui/window/about.py +1 -1
  63. tryton/gui/window/dblogin.py +2 -2
  64. tryton/gui/window/email_.py +1 -1
  65. tryton/gui/window/form.py +6 -4
  66. tryton/gui/window/log.py +24 -2
  67. tryton/gui/window/view_form/model/field.py +84 -34
  68. tryton/gui/window/view_form/model/group.py +3 -1
  69. tryton/gui/window/view_form/model/record.py +64 -15
  70. tryton/gui/window/view_form/screen/screen.py +83 -46
  71. tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
  72. tryton/gui/window/view_form/view/form.py +6 -12
  73. tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
  74. tryton/gui/window/view_form/view/form_gtk/dictionary.py +37 -24
  75. tryton/gui/window/view_form/view/form_gtk/document.py +9 -10
  76. tryton/gui/window/view_form/view/form_gtk/many2many.py +17 -7
  77. tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
  78. tryton/gui/window/view_form/view/form_gtk/one2many.py +25 -6
  79. tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
  80. tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
  81. tryton/gui/window/view_form/view/list.py +68 -35
  82. tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
  83. tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
  84. tryton/gui/window/view_form/view/screen_container.py +3 -5
  85. tryton/gui/window/win_export.py +1 -2
  86. tryton/gui/window/win_form.py +9 -7
  87. tryton/gui/window/win_import.py +9 -4
  88. tryton/gui/window/wizard.py +13 -10
  89. tryton/jsonrpc.py +46 -28
  90. tryton/plugins/__init__.py +5 -3
  91. tryton/pyson.py +55 -5
  92. tryton/rpc.py +18 -0
  93. tryton/tests/test_common_domain_parser.py +8 -0
  94. tryton/translate.py +5 -2
  95. {tryton-7.0.6.data → tryton-7.2.13.data}/scripts/tryton +8 -7
  96. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/METADATA +6 -6
  97. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/RECORD +100 -98
  98. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/WHEEL +1 -1
  99. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/LICENSE +0 -0
  100. {tryton-7.0.6.dist-info → tryton-7.2.13.dist-info}/top_level.txt +0 -0
@@ -48,7 +48,6 @@ class Many2Many(Widget):
48
48
  self.wid_text.set_placeholder_text(_('Search'))
49
49
  self.wid_text.set_property('width_chars', 13)
50
50
  self.wid_text.connect('focus-out-event', self._focus_out)
51
- self.focus_out = True
52
51
  hbox.pack_start(self.wid_text, expand=True, fill=True, padding=0)
53
52
 
54
53
  if int(self.attrs.get('completion', 1)):
@@ -113,6 +112,8 @@ class Many2Many(Widget):
113
112
  self.screen.widget.connect('key_press_event', self.on_keypress)
114
113
  self.wid_text.connect('key_press_event', self.on_keypress)
115
114
 
115
+ self._popup = False
116
+
116
117
  def on_keypress(self, widget, event):
117
118
  editable = self.wid_text.get_editable()
118
119
  activate_keys = [Gdk.KEY_Tab, Gdk.KEY_ISO_Left_Tab]
@@ -161,8 +162,6 @@ class Many2Many(Widget):
161
162
  return int(self.attrs.get('create', 1)) and self.get_access('create')
162
163
 
163
164
  def _sig_add(self, *args):
164
- if not self.focus_out:
165
- return
166
165
  domain = self.field.domain_get(self.record)
167
166
  add_remove = self.record.expr_eval(self.attrs.get('add_remove'))
168
167
  if add_remove:
@@ -171,15 +170,18 @@ class Many2Many(Widget):
171
170
  order = self.field.get_search_order(self.record)
172
171
  value = self.wid_text.get_text()
173
172
 
174
- self.focus_out = False
173
+ if self._popup:
174
+ return
175
+ else:
176
+ self._popup = True
175
177
 
176
178
  def callback(result):
177
- self.focus_out = True
178
179
  if result:
179
180
  ids = [x[0] for x in result]
180
181
  self.screen.load(ids, modified=True)
181
182
  self.screen.set_cursor()
182
183
  self.wid_text.set_text('')
184
+ self._popup = False
183
185
  win = WinSearch(self.attrs['relation'], callback, sel_multi=True,
184
186
  context=context, domain=domain, order=order,
185
187
  view_ids=self.attrs.get('view_ids', '').split(','),
@@ -215,6 +217,10 @@ class Many2Many(Widget):
215
217
  def _sig_edit(self):
216
218
  if not self.screen.current_record:
217
219
  return
220
+ if self._popup:
221
+ return
222
+ else:
223
+ self._popup = True
218
224
  # Create a new screen that is not linked to the parent otherwise on the
219
225
  # save of the record will trigger the save of the parent
220
226
  screen = self._get_screen_form()
@@ -231,22 +237,26 @@ class Many2Many(Widget):
231
237
  self.screen.current_record.modified_fields.setdefault('id')
232
238
  # Force a display to clear the CellCache
233
239
  self.screen.display()
240
+ self._popup = False
234
241
  WinForm(screen, callback)
235
242
 
236
243
  def _sig_new(self, defaults=None):
244
+ if self._popup:
245
+ return
246
+ else:
247
+ self._popup = True
237
248
  screen = self._get_screen_form()
238
249
  defaults = defaults.copy() if defaults is not None else {}
239
250
  defaults['rec_name'] = self.wid_text.get_text()
240
251
 
241
252
  def callback(result):
242
- self.focus_out = True
243
253
  if result:
244
254
  record = screen.current_record
245
255
  self.screen.load([record.id], modified=True)
246
256
  self.wid_text.set_text('')
247
257
  self.wid_text.grab_focus()
258
+ self._popup = False
248
259
 
249
- self.focus_out = False
250
260
  WinForm(
251
261
  screen, callback, new=True, save_current=True, defaults=defaults)
252
262
 
@@ -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():
@@ -12,6 +12,7 @@ from dateutil.relativedelta import relativedelta
12
12
  from gi.repository import Gdk, Gtk
13
13
 
14
14
  import tryton.rpc as rpc
15
+ from tryton import common
15
16
  from tryton.action import Action
16
17
  from tryton.common import COLOR_SCHEMES, generateColorscheme, hex2rgb
17
18
  from tryton.config import CONFIG
@@ -386,7 +387,8 @@ class Graph(Gtk.DrawingArea):
386
387
  if isinstance(value, datetime.timedelta):
387
388
  value = value.total_seconds()
388
389
  self.datas[x][key] += float(value or 0)
389
- date_format = self.view.screen.context.get('date_format', '%x')
390
+ date_format = common.date_format(
391
+ self.view.screen.context.get('date_format'))
390
392
  datetime_format = date_format + ' %X'
391
393
  if isinstance(minx, datetime.datetime):
392
394
  date = minx
@@ -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:
338
+ if 'optional' in attributes and name != self.exclude_field:
337
339
  self.view.optionals.append(column)
338
340
 
339
341
  def _parse_button(self, node, attributes):
@@ -720,46 +722,44 @@ 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
761
+ else:
762
+ return
763
763
  col = self.treeview.get_cursor()[1]
764
764
  columns = [c for c in self.treeview.get_columns()
765
765
  if c.get_visible() and c.name]
@@ -774,7 +774,7 @@ class ViewTree(View):
774
774
  group = self.group
775
775
  idx = len(group)
776
776
  default = None
777
- for line in data:
777
+ for line in reader:
778
778
  if idx >= len(group):
779
779
  record = group.new(default=False)
780
780
  if default is None:
@@ -785,13 +785,25 @@ class ViewTree(View):
785
785
  for col, value in zip(columns, line):
786
786
  widget = self.get_column_widget(col)
787
787
  if widget.get_textual_value(record) != value:
788
- widget.value_from_text(record, value)
788
+ if hasattr(widget, 'search_remote'):
789
+ field = record.group.fields[widget.attrs['name']]
790
+ win = widget.search_remote(record, field, value)
791
+ if len(win.screen.group) == 1:
792
+ target, = win.screen.group
793
+ field.set_client(
794
+ record, (target.id, target.rec_name()))
795
+ else:
796
+ field.set_client(record, (None, ''))
797
+ win.response(win, Gtk.ResponseType.CANCEL)
798
+ else:
799
+ widget.value_from_text(record, value)
789
800
  if value and not widget.get_textual_value(record):
790
801
  # Stop setting value if a value is correctly set
791
802
  idx = len(group)
792
803
  break
793
804
  if not record.validate():
794
805
  break
806
+ record.save()
795
807
  idx += 1
796
808
  self.record = record
797
809
  self.screen.display(set_cursor=True)
@@ -900,12 +912,27 @@ class ViewTree(View):
900
912
  except TypeError:
901
913
  # Outside row
902
914
  return False
915
+ selection = treeview.get_selection()
903
916
  menu = Gtk.Menu()
904
- copy_item = Gtk.MenuItem(label=_('Copy'))
905
- copy_item.connect('activate', lambda x: self.on_copy())
906
- menu.append(copy_item)
917
+ if selection.count_selected_rows():
918
+ if selection.count_selected_rows() == 1:
919
+ copy_item = Gtk.MenuItem(label=_("Copy"))
920
+ copy_item.connect(
921
+ 'activate', lambda x: self.on_copy([col]))
922
+ menu.append(copy_item)
923
+ copy_row_label = _("Copy Row")
924
+ else:
925
+ copy_row_label = _("Copy Rows")
926
+ if col:
927
+ copy_column_item = Gtk.MenuItem(label=_("Copy Column"))
928
+ copy_column_item.connect(
929
+ 'activate', lambda x: self.on_copy(columns=[col]))
930
+ menu.append(copy_column_item)
931
+ copy_row_item = Gtk.MenuItem(label=copy_row_label)
932
+ copy_row_item.connect('activate', lambda x: self.on_copy())
933
+ menu.append(copy_row_item)
907
934
  if self.editable:
908
- paste_item = Gtk.MenuItem(label=_('Paste'))
935
+ paste_item = Gtk.MenuItem(label=_("Paste Rows"))
909
936
  paste_item.connect('activate', lambda x: self.on_paste())
910
937
  menu.append(paste_item)
911
938
 
@@ -939,7 +966,6 @@ class ViewTree(View):
939
966
  menu, model, record_id, title=label, field=field,
940
967
  context=context)
941
968
 
942
- selection = treeview.get_selection()
943
969
  if selection.count_selected_rows() == 1:
944
970
  group = self.group
945
971
  if selection.get_mode() == Gtk.SelectionMode.SINGLE:
@@ -1041,9 +1067,12 @@ class ViewTree(View):
1041
1067
  elif tree_sel.get_mode() == Gtk.SelectionMode.MULTIPLE:
1042
1068
  model, paths = tree_sel.get_selected_rows()
1043
1069
  if model and paths:
1044
- iter_ = model.get_iter(paths[0])
1045
- record = model.get_value(iter_, 0)
1046
- self.record = record
1070
+ records = []
1071
+ for path in paths:
1072
+ iter_ = model.get_iter(path)
1073
+ records.append(model.get_value(iter_, 0))
1074
+ if self.record not in records:
1075
+ self.record = records[0]
1047
1076
  else:
1048
1077
  self.record = None
1049
1078
 
@@ -1083,6 +1112,10 @@ class ViewTree(View):
1083
1112
  def display(self, force=False):
1084
1113
  self.treeview.display_counter += 1
1085
1114
  current_record = self.record
1115
+ if current_record and current_record not in current_record.group:
1116
+ # current record may have been removed by on_change calls without
1117
+ # changing the current record of screen before the display
1118
+ current_record = None
1086
1119
  if (force
1087
1120
  or not self.treeview.get_model()
1088
1121
  or self.group != self.treeview.get_model().group):
@@ -163,7 +163,8 @@ class EditableTreeView(TreeView):
163
163
  for renderer in column.get_cells():
164
164
  if renderer.props.editing:
165
165
  widget = self.view.get_column_widget(column)
166
- self.on_editing_done(widget.editable, renderer)
166
+ editable = widget.get_editable(renderer)
167
+ self.on_editing_done(editable, renderer)
167
168
  return True
168
169
 
169
170
  def on_keypressed(self, entry, event, renderer):