tryton 7.0.7__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.
- tryton/__init__.py +1 -1
- tryton/cache.py +34 -0
- tryton/common/common.py +149 -73
- tryton/common/completion.py +2 -2
- tryton/common/datetime_.py +3 -1
- tryton/common/domain_inversion.py +2 -1
- tryton/common/domain_parser.py +22 -11
- tryton/common/popup_menu.py +1 -1
- tryton/common/selection.py +6 -3
- tryton/common/tempfile.py +34 -0
- tryton/config.py +4 -5
- tryton/data/locale/bg/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/bg/LC_MESSAGES/tryton.po +69 -20
- tryton/data/locale/ca/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ca/LC_MESSAGES/tryton.po +70 -25
- tryton/data/locale/cs/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/cs/LC_MESSAGES/tryton.po +68 -21
- tryton/data/locale/de/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/de/LC_MESSAGES/tryton.po +71 -26
- tryton/data/locale/es/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es/LC_MESSAGES/tryton.po +68 -23
- tryton/data/locale/es_419/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/es_419/LC_MESSAGES/tryton.po +72 -22
- tryton/data/locale/et/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/et/LC_MESSAGES/tryton.po +73 -23
- tryton/data/locale/fa/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fa/LC_MESSAGES/tryton.po +74 -25
- tryton/data/locale/fi/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fi/LC_MESSAGES/tryton.po +63 -20
- tryton/data/locale/fr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/fr/LC_MESSAGES/tryton.po +73 -28
- tryton/data/locale/hu/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/hu/LC_MESSAGES/tryton.po +75 -23
- tryton/data/locale/id/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/id/LC_MESSAGES/tryton.po +72 -23
- tryton/data/locale/it/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/it/LC_MESSAGES/tryton.po +73 -24
- tryton/data/locale/ja_JP/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lo/LC_MESSAGES/tryton.po +74 -25
- tryton/data/locale/lt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/lt/LC_MESSAGES/tryton.po +73 -23
- tryton/data/locale/nl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/nl/LC_MESSAGES/tryton.po +72 -27
- tryton/data/locale/pl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pl/LC_MESSAGES/tryton.po +113 -78
- tryton/data/locale/pt/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/pt/LC_MESSAGES/tryton.po +73 -24
- tryton/data/locale/ro/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ro/LC_MESSAGES/tryton.po +87 -36
- tryton/data/locale/ru/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/ru/LC_MESSAGES/tryton.po +72 -25
- tryton/data/locale/sl/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/sl/LC_MESSAGES/tryton.po +77 -26
- tryton/data/locale/tr/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/tr/LC_MESSAGES/tryton.po +64 -20
- tryton/data/locale/uk/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/uk/LC_MESSAGES/tryton.po +75 -23
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.mo +0 -0
- tryton/data/locale/zh_CN/LC_MESSAGES/tryton.po +76 -24
- tryton/device_cookie.py +1 -1
- tryton/gui/main.py +14 -12
- tryton/gui/window/about.py +1 -1
- tryton/gui/window/dblogin.py +2 -2
- tryton/gui/window/email_.py +2 -2
- tryton/gui/window/form.py +10 -5
- tryton/gui/window/log.py +24 -2
- tryton/gui/window/tabcontent.py +2 -2
- tryton/gui/window/view_form/model/field.py +84 -34
- tryton/gui/window/view_form/model/group.py +7 -2
- tryton/gui/window/view_form/model/record.py +70 -31
- tryton/gui/window/view_form/screen/screen.py +98 -47
- tryton/gui/window/view_form/view/calendar_gtk/calendar_.py +15 -9
- tryton/gui/window/view_form/view/form.py +6 -12
- tryton/gui/window/view_form/view/form_gtk/char.py +5 -6
- tryton/gui/window/view_form/view/form_gtk/dictionary.py +49 -29
- tryton/gui/window/view_form/view/form_gtk/document.py +15 -10
- tryton/gui/window/view_form/view/form_gtk/many2many.py +49 -7
- tryton/gui/window/view_form/view/form_gtk/many2one.py +21 -13
- tryton/gui/window/view_form/view/form_gtk/multiselection.py +15 -5
- tryton/gui/window/view_form/view/form_gtk/one2many.py +42 -10
- tryton/gui/window/view_form/view/form_gtk/state_widget.py +6 -2
- tryton/gui/window/view_form/view/form_gtk/url.py +8 -4
- tryton/gui/window/view_form/view/graph_gtk/graph.py +3 -1
- tryton/gui/window/view_form/view/list.py +116 -48
- tryton/gui/window/view_form/view/list_gtk/editabletree.py +2 -1
- tryton/gui/window/view_form/view/list_gtk/widget.py +58 -23
- tryton/gui/window/view_form/view/screen_container.py +3 -5
- tryton/gui/window/win_csv.py +6 -12
- tryton/gui/window/win_export.py +49 -26
- tryton/gui/window/win_form.py +9 -7
- tryton/gui/window/win_import.py +45 -15
- tryton/gui/window/wizard.py +13 -10
- tryton/jsonrpc.py +75 -34
- tryton/plugins/__init__.py +5 -3
- tryton/pyson.py +57 -6
- tryton/rpc.py +18 -0
- tryton/tests/test_common_domain_parser.py +31 -2
- tryton/translate.py +5 -2
- {tryton-7.0.7.data → tryton-7.4.4.data}/scripts/tryton +8 -7
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/METADATA +6 -6
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/RECORD +105 -103
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/WHEEL +1 -1
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/LICENSE +0 -0
- {tryton-7.0.7.dist-info → tryton-7.4.4.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
|
@@ -142,7 +141,7 @@ class One2Many(Widget):
|
|
|
142
141
|
hbox.pack_start(self.but_del, expand=False, fill=False, padding=0)
|
|
143
142
|
|
|
144
143
|
self.but_undel = Gtk.Button(can_focus=False)
|
|
145
|
-
tooltips.set_tip(self.but_undel, _(
|
|
144
|
+
tooltips.set_tip(self.but_undel, _("Undelete selected record"))
|
|
146
145
|
self.but_undel.connect('clicked', self._sig_undelete)
|
|
147
146
|
self.but_undel.add(common.IconFactory.get_image(
|
|
148
147
|
'tryton-undo', Gtk.IconSize.SMALL_TOOLBAR))
|
|
@@ -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:
|
|
@@ -294,7 +295,13 @@ class One2Many(Widget):
|
|
|
294
295
|
if isinstance(self._position, int):
|
|
295
296
|
first = self._position <= 1
|
|
296
297
|
last = self._position >= self._length
|
|
297
|
-
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)
|
|
298
305
|
view_type = self.screen.current_view.view_type
|
|
299
306
|
has_views = self.screen.number_of_views > 1
|
|
300
307
|
|
|
@@ -312,15 +319,16 @@ class One2Many(Widget):
|
|
|
312
319
|
self.but_undel.set_sensitive(bool(
|
|
313
320
|
not self._readonly
|
|
314
321
|
and not size_limit
|
|
322
|
+
and undeletable
|
|
315
323
|
and self._position))
|
|
316
324
|
self.but_open.set_sensitive(bool(
|
|
317
325
|
self._position
|
|
318
326
|
and self.read_access))
|
|
319
327
|
self.but_next.set_sensitive(bool(
|
|
320
|
-
self.
|
|
328
|
+
self._length
|
|
321
329
|
and not last))
|
|
322
330
|
self.but_pre.set_sensitive(bool(
|
|
323
|
-
self.
|
|
331
|
+
self._length
|
|
324
332
|
and not first))
|
|
325
333
|
if self.attrs.get('add_remove'):
|
|
326
334
|
self.but_add.set_sensitive(bool(
|
|
@@ -370,12 +378,17 @@ class One2Many(Widget):
|
|
|
370
378
|
self._new_single(defaults)
|
|
371
379
|
|
|
372
380
|
def _new_single(self, defaults=None):
|
|
381
|
+
if self._popup:
|
|
382
|
+
return
|
|
383
|
+
else:
|
|
384
|
+
self._popup = True
|
|
373
385
|
sequence = self._sequence()
|
|
374
386
|
|
|
375
387
|
def update_sequence():
|
|
376
388
|
if sequence:
|
|
377
389
|
self.screen.group.set_sequence(
|
|
378
390
|
field=sequence, position=self.screen.new_position)
|
|
391
|
+
self._popup = False
|
|
379
392
|
|
|
380
393
|
if self.screen.current_view.creatable:
|
|
381
394
|
self.screen.new()
|
|
@@ -392,6 +405,10 @@ class One2Many(Widget):
|
|
|
392
405
|
fields = self.attrs['product'].split(',')
|
|
393
406
|
product = {}
|
|
394
407
|
|
|
408
|
+
if self._popup:
|
|
409
|
+
return
|
|
410
|
+
else:
|
|
411
|
+
self._popup = True
|
|
395
412
|
first = self.screen.new(default=False)
|
|
396
413
|
default = first.default_get(defaults=defaults)
|
|
397
414
|
first.set_default(default)
|
|
@@ -420,6 +437,7 @@ class One2Many(Widget):
|
|
|
420
437
|
win_search.show()
|
|
421
438
|
|
|
422
439
|
def make_product():
|
|
440
|
+
self._popup = False
|
|
423
441
|
self.screen.group.remove(first, remove=True)
|
|
424
442
|
if not product:
|
|
425
443
|
return
|
|
@@ -448,7 +466,14 @@ class One2Many(Widget):
|
|
|
448
466
|
return
|
|
449
467
|
record = self.screen.current_record
|
|
450
468
|
if record:
|
|
451
|
-
|
|
469
|
+
if self._popup:
|
|
470
|
+
return
|
|
471
|
+
else:
|
|
472
|
+
self._popup = True
|
|
473
|
+
|
|
474
|
+
def callback(result):
|
|
475
|
+
self._popup = False
|
|
476
|
+
WinForm(self.screen, callback)
|
|
452
477
|
|
|
453
478
|
def _sig_next(self, widget):
|
|
454
479
|
if not self._validate():
|
|
@@ -475,24 +500,27 @@ class One2Many(Widget):
|
|
|
475
500
|
self.screen.unremove()
|
|
476
501
|
|
|
477
502
|
def _sig_add(self, *args):
|
|
478
|
-
if not self.focus_out:
|
|
479
|
-
return
|
|
480
503
|
if not self.write_access or not self.read_access:
|
|
481
504
|
return
|
|
482
505
|
self.view.set_value()
|
|
483
506
|
domain = self.field.domain_get(self.record)
|
|
484
507
|
context = self.field.get_search_context(self.record)
|
|
485
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)]
|
|
486
512
|
removed_ids = self.field.get_removed_ids(self.record)
|
|
487
513
|
domain = ['OR', domain, ('id', 'in', removed_ids)]
|
|
488
514
|
text = self.wid_text.get_text()
|
|
489
515
|
|
|
490
|
-
self.
|
|
516
|
+
if self._popup:
|
|
517
|
+
return
|
|
518
|
+
else:
|
|
519
|
+
self._popup = True
|
|
491
520
|
|
|
492
521
|
sequence = self._sequence()
|
|
493
522
|
|
|
494
523
|
def callback(result):
|
|
495
|
-
self.focus_out = True
|
|
496
524
|
if result:
|
|
497
525
|
ids = [x[0] for x in result]
|
|
498
526
|
self.screen.load(ids, modified=True)
|
|
@@ -501,6 +529,7 @@ class One2Many(Widget):
|
|
|
501
529
|
field=sequence, position=self.screen.new_position)
|
|
502
530
|
self.screen.set_cursor()
|
|
503
531
|
self.wid_text.set_text('')
|
|
532
|
+
self._popup = False
|
|
504
533
|
|
|
505
534
|
order = self.field.get_search_order(self.record)
|
|
506
535
|
win = WinSearch(self.attrs['relation'], callback, sel_multi=True,
|
|
@@ -586,6 +615,9 @@ class One2Many(Widget):
|
|
|
586
615
|
model = self.attrs['relation']
|
|
587
616
|
domain = self.field.domain_get(self.record)
|
|
588
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)]
|
|
589
621
|
removed_ids = self.field.get_removed_ids(self.record)
|
|
590
622
|
domain = ['OR', domain, ('id', 'in', removed_ids)]
|
|
591
623
|
update_completion(self.wid_text, self.record, self.field, model,
|
|
@@ -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():
|
|
@@ -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.
|
|
134
|
-
|
|
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.
|
|
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(
|
|
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)
|
|
@@ -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 =
|
|
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,13 @@
|
|
|
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
|
|
8
|
+
from collections import defaultdict
|
|
7
9
|
from functools import wraps
|
|
10
|
+
from io import StringIO
|
|
8
11
|
|
|
9
12
|
from gi.repository import Gdk, GLib, GObject, Gtk
|
|
10
13
|
from pygtkcompat.generictreemodel import GenericTreeModel
|
|
@@ -333,8 +336,8 @@ class TreeXMLViewParser(XMLViewParser):
|
|
|
333
336
|
|
|
334
337
|
self.view.treeview.append_column(column)
|
|
335
338
|
|
|
336
|
-
if 'optional' in attributes:
|
|
337
|
-
self.view.optionals.append(column)
|
|
339
|
+
if 'optional' in attributes and name != self.exclude_field:
|
|
340
|
+
self.view.optionals[column.name].append(column)
|
|
338
341
|
|
|
339
342
|
def _parse_button(self, node, attributes):
|
|
340
343
|
button = Button(self.view, attributes)
|
|
@@ -435,7 +438,7 @@ class ViewTree(View):
|
|
|
435
438
|
|
|
436
439
|
def __init__(self, view_id, screen, xml, children_field):
|
|
437
440
|
self.children_field = children_field
|
|
438
|
-
self.optionals =
|
|
441
|
+
self.optionals = defaultdict(list)
|
|
439
442
|
self.sum_widgets = []
|
|
440
443
|
self.sum_box = Gtk.HBox()
|
|
441
444
|
self.treeview = None
|
|
@@ -449,6 +452,8 @@ class ViewTree(View):
|
|
|
449
452
|
grid_lines = Gtk.TreeViewGridLines.VERTICAL
|
|
450
453
|
|
|
451
454
|
super().__init__(view_id, screen, xml)
|
|
455
|
+
|
|
456
|
+
self.set_selection_column()
|
|
452
457
|
self.set_drag_and_drop()
|
|
453
458
|
|
|
454
459
|
self.mnemonic_widget = self.treeview
|
|
@@ -517,21 +522,27 @@ class ViewTree(View):
|
|
|
517
522
|
self.treeview.append_column(column)
|
|
518
523
|
|
|
519
524
|
def optional_menu(self, column):
|
|
520
|
-
def toggle(menuitem,
|
|
521
|
-
|
|
525
|
+
def toggle(menuitem, columns):
|
|
526
|
+
visible = menuitem.get_active()
|
|
527
|
+
for column in columns:
|
|
528
|
+
column.set_visible(visible)
|
|
522
529
|
self.save_optional()
|
|
523
530
|
|
|
524
531
|
widget = column.get_widget()
|
|
525
532
|
menu = Gtk.Menu()
|
|
526
|
-
for
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
menuitem.
|
|
533
|
+
for name, columns in self.optionals.items():
|
|
534
|
+
visible = any(c.get_visible() for c in columns)
|
|
535
|
+
title = ' / '.join({c.get_title() for c in columns})
|
|
536
|
+
menuitem = Gtk.CheckMenuItem(label=title)
|
|
537
|
+
menuitem.set_active(visible)
|
|
538
|
+
menuitem.connect('toggled', toggle, columns)
|
|
530
539
|
menu.add(menuitem)
|
|
531
540
|
popup(menu, widget)
|
|
532
541
|
|
|
533
542
|
def save_optional(self):
|
|
534
|
-
fields = {
|
|
543
|
+
fields = {}
|
|
544
|
+
for name, columns in self.optionals.items():
|
|
545
|
+
fields[name] = all(not c.get_visible() for c in columns)
|
|
535
546
|
try:
|
|
536
547
|
RPCExecute(
|
|
537
548
|
'model', 'ir.ui.view_tree_optional', 'set_optional',
|
|
@@ -614,6 +625,21 @@ class ViewTree(View):
|
|
|
614
625
|
else:
|
|
615
626
|
arrow.clear()
|
|
616
627
|
|
|
628
|
+
def set_selection_column(self):
|
|
629
|
+
def select_data_func(column, cell, model, iter_, selection):
|
|
630
|
+
cell.set_property('active', selection.iter_is_selected(iter_))
|
|
631
|
+
|
|
632
|
+
selection = self.treeview.get_selection()
|
|
633
|
+
column = Gtk.TreeViewColumn()
|
|
634
|
+
column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
|
|
635
|
+
column.name = None
|
|
636
|
+
column._type = 'selection'
|
|
637
|
+
cell = Gtk.CellRendererToggle()
|
|
638
|
+
cell.set_sensitive(False)
|
|
639
|
+
column.pack_start(cell, expand=False)
|
|
640
|
+
column.set_cell_data_func(cell, select_data_func, selection)
|
|
641
|
+
self.treeview.insert_column(column, 0)
|
|
642
|
+
|
|
617
643
|
def set_drag_and_drop(self):
|
|
618
644
|
dnd = False
|
|
619
645
|
if self.children_field:
|
|
@@ -720,46 +746,47 @@ class ViewTree(View):
|
|
|
720
746
|
record.cancel()
|
|
721
747
|
iter_ = model.iter_next(iter_)
|
|
722
748
|
|
|
723
|
-
def on_copy(self):
|
|
749
|
+
def on_copy(self, columns=None):
|
|
750
|
+
if columns is None:
|
|
751
|
+
columns = self.treeview.get_columns()
|
|
752
|
+
|
|
753
|
+
def copy_foreach(treemodel, path, iter, add):
|
|
754
|
+
record = treemodel.get_value(iter, 0)
|
|
755
|
+
values = []
|
|
756
|
+
for col in columns:
|
|
757
|
+
if not col.get_visible() or not col.name:
|
|
758
|
+
continue
|
|
759
|
+
widget = self.get_column_widget(col)
|
|
760
|
+
values.append(widget.get_textual_value(record))
|
|
761
|
+
add(values)
|
|
762
|
+
|
|
724
763
|
for clipboard_type in [
|
|
725
764
|
Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
|
|
726
765
|
clipboard = self.treeview.get_clipboard(clipboard_type)
|
|
727
766
|
selection = self.treeview.get_selection()
|
|
728
|
-
data =
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
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
|
|
767
|
+
data = StringIO()
|
|
768
|
+
lineterminator = (
|
|
769
|
+
'\n' if selection.count_selected_rows() > 1 else '')
|
|
770
|
+
writer = csv.writer(
|
|
771
|
+
data, delimiter='\t', lineterminator=lineterminator)
|
|
772
|
+
selection.selected_foreach(copy_foreach, writer.writerow)
|
|
773
|
+
clipboard.set_text(data.getvalue(), -1)
|
|
744
774
|
|
|
745
775
|
def on_paste(self):
|
|
746
776
|
if not self.editable:
|
|
747
777
|
return
|
|
748
778
|
|
|
749
|
-
|
|
750
|
-
if value[:1] == '"' and value[-1:] == '"':
|
|
751
|
-
return value[1:-1]
|
|
752
|
-
return value
|
|
753
|
-
data = []
|
|
779
|
+
reader = None
|
|
754
780
|
for clipboard_type in [
|
|
755
781
|
Gdk.SELECTION_CLIPBOARD, Gdk.SELECTION_PRIMARY]:
|
|
756
782
|
clipboard = self.treeview.get_clipboard(clipboard_type)
|
|
757
783
|
text = clipboard.wait_for_text()
|
|
758
784
|
if not text:
|
|
759
785
|
continue
|
|
760
|
-
|
|
761
|
-
for l in text.splitlines()]
|
|
786
|
+
reader = csv.reader(StringIO(text), delimiter='\t')
|
|
762
787
|
break
|
|
788
|
+
else:
|
|
789
|
+
return
|
|
763
790
|
col = self.treeview.get_cursor()[1]
|
|
764
791
|
columns = [c for c in self.treeview.get_columns()
|
|
765
792
|
if c.get_visible() and c.name]
|
|
@@ -774,7 +801,7 @@ class ViewTree(View):
|
|
|
774
801
|
group = self.group
|
|
775
802
|
idx = len(group)
|
|
776
803
|
default = None
|
|
777
|
-
for line in
|
|
804
|
+
for line in reader:
|
|
778
805
|
if idx >= len(group):
|
|
779
806
|
record = group.new(default=False)
|
|
780
807
|
if default is None:
|
|
@@ -785,13 +812,25 @@ class ViewTree(View):
|
|
|
785
812
|
for col, value in zip(columns, line):
|
|
786
813
|
widget = self.get_column_widget(col)
|
|
787
814
|
if widget.get_textual_value(record) != value:
|
|
788
|
-
widget
|
|
815
|
+
if hasattr(widget, 'search_remote'):
|
|
816
|
+
field = record.group.fields[widget.attrs['name']]
|
|
817
|
+
win = widget.search_remote(record, field, value)
|
|
818
|
+
if len(win.screen.group) == 1:
|
|
819
|
+
target, = win.screen.group
|
|
820
|
+
field.set_client(
|
|
821
|
+
record, (target.id, target.rec_name()))
|
|
822
|
+
else:
|
|
823
|
+
field.set_client(record, (None, ''))
|
|
824
|
+
win.response(win, Gtk.ResponseType.CANCEL)
|
|
825
|
+
else:
|
|
826
|
+
widget.value_from_text(record, value)
|
|
789
827
|
if value and not widget.get_textual_value(record):
|
|
790
828
|
# Stop setting value if a value is correctly set
|
|
791
829
|
idx = len(group)
|
|
792
830
|
break
|
|
793
831
|
if not record.validate():
|
|
794
832
|
break
|
|
833
|
+
record.save()
|
|
795
834
|
idx += 1
|
|
796
835
|
self.record = record
|
|
797
836
|
self.screen.display(set_cursor=True)
|
|
@@ -900,12 +939,27 @@ class ViewTree(View):
|
|
|
900
939
|
except TypeError:
|
|
901
940
|
# Outside row
|
|
902
941
|
return False
|
|
942
|
+
selection = treeview.get_selection()
|
|
903
943
|
menu = Gtk.Menu()
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
944
|
+
if selection.count_selected_rows():
|
|
945
|
+
if selection.count_selected_rows() == 1:
|
|
946
|
+
copy_item = Gtk.MenuItem(label=_("Copy"))
|
|
947
|
+
copy_item.connect(
|
|
948
|
+
'activate', lambda x: self.on_copy([col]))
|
|
949
|
+
menu.append(copy_item)
|
|
950
|
+
copy_row_label = _("Copy Row")
|
|
951
|
+
else:
|
|
952
|
+
copy_row_label = _("Copy Rows")
|
|
953
|
+
if col:
|
|
954
|
+
copy_column_item = Gtk.MenuItem(label=_("Copy Column"))
|
|
955
|
+
copy_column_item.connect(
|
|
956
|
+
'activate', lambda x: self.on_copy(columns=[col]))
|
|
957
|
+
menu.append(copy_column_item)
|
|
958
|
+
copy_row_item = Gtk.MenuItem(label=copy_row_label)
|
|
959
|
+
copy_row_item.connect('activate', lambda x: self.on_copy())
|
|
960
|
+
menu.append(copy_row_item)
|
|
907
961
|
if self.editable:
|
|
908
|
-
paste_item = Gtk.MenuItem(label=_(
|
|
962
|
+
paste_item = Gtk.MenuItem(label=_("Paste Rows"))
|
|
909
963
|
paste_item.connect('activate', lambda x: self.on_paste())
|
|
910
964
|
menu.append(paste_item)
|
|
911
965
|
|
|
@@ -939,7 +993,6 @@ class ViewTree(View):
|
|
|
939
993
|
menu, model, record_id, title=label, field=field,
|
|
940
994
|
context=context)
|
|
941
995
|
|
|
942
|
-
selection = treeview.get_selection()
|
|
943
996
|
if selection.count_selected_rows() == 1:
|
|
944
997
|
group = self.group
|
|
945
998
|
if selection.get_mode() == Gtk.SelectionMode.SINGLE:
|
|
@@ -1041,9 +1094,12 @@ class ViewTree(View):
|
|
|
1041
1094
|
elif tree_sel.get_mode() == Gtk.SelectionMode.MULTIPLE:
|
|
1042
1095
|
model, paths = tree_sel.get_selected_rows()
|
|
1043
1096
|
if model and paths:
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1097
|
+
records = []
|
|
1098
|
+
for path in paths:
|
|
1099
|
+
iter_ = model.get_iter(path)
|
|
1100
|
+
records.append(model.get_value(iter_, 0))
|
|
1101
|
+
if self.record not in records:
|
|
1102
|
+
self.record = records[0]
|
|
1047
1103
|
else:
|
|
1048
1104
|
self.record = None
|
|
1049
1105
|
|
|
@@ -1083,6 +1139,10 @@ class ViewTree(View):
|
|
|
1083
1139
|
def display(self, force=False):
|
|
1084
1140
|
self.treeview.display_counter += 1
|
|
1085
1141
|
current_record = self.record
|
|
1142
|
+
if current_record and current_record not in current_record.group:
|
|
1143
|
+
# current record may have been removed by on_change calls without
|
|
1144
|
+
# changing the current record of screen before the display
|
|
1145
|
+
current_record = None
|
|
1086
1146
|
if (force
|
|
1087
1147
|
or not self.treeview.get_model()
|
|
1088
1148
|
or self.group != self.treeview.get_model().group):
|
|
@@ -1096,9 +1156,14 @@ class ViewTree(View):
|
|
|
1096
1156
|
selection.select_path(path)
|
|
1097
1157
|
# The search column must be set each time the model is changed
|
|
1098
1158
|
self.treeview.set_search_column(0)
|
|
1159
|
+
selection = self.treeview.get_selection()
|
|
1099
1160
|
if not current_record:
|
|
1100
|
-
selection = self.treeview.get_selection()
|
|
1101
1161
|
selection.unselect_all()
|
|
1162
|
+
else:
|
|
1163
|
+
model = self.treeview.get_model()
|
|
1164
|
+
path = Gtk.TreePath(current_record.get_index_path(model.group))
|
|
1165
|
+
if not selection.path_is_selected(path):
|
|
1166
|
+
selection.select_path(path)
|
|
1102
1167
|
self.treeview.queue_draw()
|
|
1103
1168
|
if self.editable:
|
|
1104
1169
|
self.set_state()
|
|
@@ -1122,7 +1187,8 @@ class ViewTree(View):
|
|
|
1122
1187
|
continue
|
|
1123
1188
|
widget = self.get_column_widget(column)
|
|
1124
1189
|
widget.set_editable()
|
|
1125
|
-
if
|
|
1190
|
+
if ('optional' in widget.attrs
|
|
1191
|
+
and column.name in tree_column_optional):
|
|
1126
1192
|
optional = tree_column_optional[column.name]
|
|
1127
1193
|
else:
|
|
1128
1194
|
optional = bool(int(widget.attrs.get('optional', '0')))
|
|
@@ -1141,7 +1207,7 @@ class ViewTree(View):
|
|
|
1141
1207
|
column.set_visible(not unique or bool(self.children_field))
|
|
1142
1208
|
if self.children_field:
|
|
1143
1209
|
for i, column in enumerate(self.treeview.get_columns()):
|
|
1144
|
-
if
|
|
1210
|
+
if column._type in {'selection', 'optional', 'drag'}:
|
|
1145
1211
|
continue
|
|
1146
1212
|
if column.get_visible():
|
|
1147
1213
|
self.treeview.set_expander_column(column)
|
|
@@ -1208,9 +1274,9 @@ class ViewTree(View):
|
|
|
1208
1274
|
label.set_text(text)
|
|
1209
1275
|
|
|
1210
1276
|
def set_cursor(self, new=False, reset_view=True):
|
|
1211
|
-
self.treeview.grab_focus()
|
|
1212
1277
|
model = self.treeview.get_model()
|
|
1213
1278
|
if self.record and model and self.treeview.get_realized():
|
|
1279
|
+
self.treeview.grab_focus()
|
|
1214
1280
|
path = self.record.get_index_path(model.group)
|
|
1215
1281
|
if model.get_flags() & Gtk.TreeModelFlags.LIST_ONLY:
|
|
1216
1282
|
path = (path[0],)
|
|
@@ -1234,6 +1300,8 @@ class ViewTree(View):
|
|
|
1234
1300
|
records = []
|
|
1235
1301
|
sel = self.treeview.get_selection()
|
|
1236
1302
|
sel.selected_foreach(_func_sel_get, records)
|
|
1303
|
+
if not records and self.record:
|
|
1304
|
+
records.append(self.record)
|
|
1237
1305
|
return records
|
|
1238
1306
|
|
|
1239
1307
|
def get_selected_paths(self):
|
|
@@ -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
|
-
|
|
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):
|