1
# -*- encoding: utf-8 -*-
2
##############################################################################
4
# OpenERP, Open Source Management Solution
5
# Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8
# This program is free software: you can redistribute it and/or modify
9
# it under the terms of the GNU General Public License as published by
10
# the Free Software Foundation, either version 3 of the License, or
11
# (at your option) any later version.
13
# This program is distributed in the hope that it will be useful,
14
# but WITHOUT ANY WARRANTY; without even the implied warranty of
15
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
# GNU General Public License for more details.
18
# You should have received a copy of the GNU General Public License
19
# along with this program. If not, see <http://www.gnu.org/licenses/>.
21
##############################################################################
28
class EditableTreeView(gtk.TreeView, observator.Observable):
30
leaving_model_events = (gtk.keysyms.Up, gtk.keysyms.Down,
31
gtk.keysyms.Return, gtk.keysyms.KP_Enter)
32
leaving_events = leaving_model_events + (gtk.keysyms.Tab,
33
gtk.keysyms.ISO_Left_Tab)
35
def __init__(self, position):
36
super(EditableTreeView, self).__init__()
37
self.editable = position
40
def on_quit_cell(self, current_model, fieldname, value):
41
modelfield = current_model[fieldname]
42
if hasattr(modelfield, 'editabletree_entry'):
43
del modelfield.editabletree_entry
44
cell = self.cells[fieldname]
46
# The value has not changed ... do nothing.
47
if value == cell.get_textual_value(current_model):
51
real_value = cell.value_from_text(current_model, value)
52
modelfield.set_client(current_model, real_value)
53
except parser.UnsettableColumn:
56
# And now the on_change stuff ... only 3 lines are enough.
57
#callback = modelfield.attrs.get('on_change', '')
59
# current_model.on_change(callback)
61
# And finally the conditional default
62
#if modelfield.attrs.get('change_default', False):
63
# current_model.cond_default(fieldname, real_value)
65
def on_open_remote(self, current_model, fieldname, create, value):
66
modelfield = current_model[fieldname]
67
cell = self.cells[fieldname]
68
if value != cell.get_textual_value(current_model) or not value:
73
valid, value = cell.open_remote(current_model, create, changed, value)
75
modelfield.set_client(current_model, value)
76
except NotImplementedError:
78
return cell.get_textual_value(current_model)
80
def on_create_line(self):
81
model = self.get_model()
82
if self.editable == 'top':
83
method = model.prepend
86
ctx=self.screen.context.copy()
87
if self.screen.current_model.parent:
88
ctx.update(self.screen.current_model.parent.expr_eval(self.screen.default_get))
89
new_model = model.model_group.model_new(domain=self.screen.domain, context=ctx)
90
res = method(new_model)
94
# if changed, please update the corresponded method in the DecoratedTreeView class
95
def get_columns(self, include_non_visible=True, include_non_editable=True):
96
def column_is_editable(column):
97
renderer = column.get_cell_renderers()[0]
98
if isinstance(renderer, gtk.CellRendererToggle):
99
return renderer.get_property('activatable')
101
return renderer.get_property('editable')
103
columns = super(EditableTreeView, self).get_columns()
104
if not include_non_visible:
105
columns = filter(lambda c: c.get_visible(), columns)
106
if not include_non_editable:
107
columns = filter(lambda c: column_is_editable(c), columns)
111
def __next_column(self, col):
112
cols = self.get_columns(False, False)
113
current = cols.index(col)
114
idx = (current + 1) % len(cols)
117
def __prev_column(self, col):
118
cols = self.get_columns(False, False)
119
current = cols.index(col)
120
idx = (current - 1) % len(cols)
123
def set_cursor(self, path, focus_column=None, start_editing=False):
124
if focus_column and (focus_column._type in ('many2one','many2many')):
125
msg = _('Shortcut: %s') % ('<i>%s</i>' % _('F1 New - F2 Open/Search'))
126
self.warn('misc-message', msg)
127
elif focus_column and (focus_column._type in ('boolean')):
130
self.warn('misc-message', '')
131
return super(EditableTreeView, self).set_cursor(path, focus_column,
135
path, column = self.get_cursor()
136
store = self.get_model()
137
if not path or not column:
139
model = store.get_value(store.get_iter(path), 0)
140
modelfield = model[column.name]
141
if hasattr(modelfield, 'editabletree_entry'):
142
entry = modelfield.editabletree_entry
143
if isinstance(entry, gtk.Entry):
144
txt = entry.get_text()
146
txt = entry.get_active_text()
147
self.on_quit_cell(model, column.name, txt)
150
def on_keypressed(self, entry, event):
151
path, column = self.get_cursor()
152
store = self.get_model()
153
model = store.get_value(store.get_iter(path), 0)
154
if event.keyval in self.leaving_events:
155
shift_pressed = bool(gtk.gdk.SHIFT_MASK & event.state)
156
if isinstance(entry, gtk.Entry):
157
txt = entry.get_text()
158
elif isinstance(entry, gtk.ComboBoxEntry) and shift_pressed and event.keyval != gtk.keysyms.ISO_Left_Tab:
159
model = entry.get_property('model')
160
txt = entry.get_active_text()
161
for idx, line in enumerate(model):
164
if event.keyval == gtk.keysyms.Up:
165
entry.set_active((idx - 1) % len(model))
166
elif event.keyval == gtk.keysyms.Down:
167
entry.set_active((idx + 1) % len(model))
170
txt = entry.get_active_text()
171
entry.disconnect(entry.editing_done_id)
172
self.on_quit_cell(model, column.name, txt)
173
entry.editing_done_id = entry.connect('editing_done', self.on_editing_done)
174
if event.keyval in self.leaving_model_events:
175
if model.validate() and self.screen.tree_saves:
180
res = store.saved(id)
182
self.screen.display(res.id)
184
elif self.screen.tree_saves:
185
invalid_fields = model.invalid_fields
186
for col in self.get_columns():
187
if col.name in invalid_fields:
189
self.set_cursor(path, col, True)
190
msg = _('Warning; field %s is required!') % ('<b>%s</b>' % invalid_fields[col.name])
191
self.warn('misc-message', msg, "red")
194
if event.keyval == gtk.keysyms.Tab:
195
new_col = self.__next_column(column)
196
self.set_cursor(path, new_col, True)
197
elif event.keyval == gtk.keysyms.ISO_Left_Tab:
198
new_col = self.__prev_column(column)
199
self.set_cursor(path, new_col, True)
200
elif event.keyval == gtk.keysyms.Up:
201
self._key_up(path, store, column)
202
elif event.keyval == gtk.keysyms.Down:
203
self._key_down(path,store,column)
204
elif event.keyval in (gtk.keysyms.Return, gtk.keysyms.KP_Enter):
205
if self.editable == 'top':
206
new_path = self._key_up(path, store, column)
208
new_path = self._key_down(path,store,column)
209
col = self.get_columns(False, False)[0]
210
self.set_cursor(new_path, col, True)
211
elif event.keyval == gtk.keysyms.Escape:
213
store.remove(store.get_iter(path))
214
self.screen.current_model = False
216
self.screen.current_model = False
217
self.screen.display()
218
self.set_cursor(path, column, False)
219
elif event.keyval in (gtk.keysyms.F1, gtk.keysyms.F2):
220
if (column._type not in ('many2one','many2many')):
222
if isinstance(entry, gtk.Entry):
223
value=entry.get_text()
225
value=entry.get_active_text()
226
entry.disconnect(entry.editing_done_id)
227
newval = self.on_open_remote(model, column.name,
228
create=(event.keyval==gtk.keysyms.F1), value=value)
229
if isinstance(entry, gtk.Entry):
230
entry.set_text(newval)
232
entry.set_active_text(newval)
233
entry.editing_done_id = entry.connect('editing_done', self.on_editing_done)
234
self.set_cursor(path, column, True)
237
modelfield = model[column.name]
238
if isinstance(entry, gtk.Entry):
239
entry.set_max_length(int(modelfield.attrs.get('size', 0)))
240
# store in the model the entry widget to get the value in set_value
241
modelfield.editabletree_entry = entry
242
model.modified = True
243
model.modified_fields.setdefault(column.name)
248
def _key_down(self, path, store, column):
249
if path[0] == len(store) - 1 and self.editable == 'bottom':
250
self.on_create_line()
251
new_path = (path[0] + 1) % len(store)
252
self.set_cursor(new_path, column, True)
255
def _key_up(self, path, store, column):
256
if path[0] == 0 and self.editable == 'top':
257
self.on_create_line()
260
new_path = (path[0] - 1) % len(store)
261
self.set_cursor(new_path, column, True)
264
def on_editing_done(self, entry):
265
path, column = self.get_cursor()
268
store = self.get_model()
269
model = store.get_value(store.get_iter(path), 0)
270
if isinstance(entry, gtk.Entry):
271
self.on_quit_cell(model, column.name, entry.get_text())
272
elif isinstance(entry, gtk.ComboBoxEntry):
273
self.on_quit_cell(model, column.name, entry.get_active_text())
276
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: