~ubuntu-branches/ubuntu/quantal/openerp-client/quantal

« back to all changes in this revision

Viewing changes to bin/widget/view/tree_gtk/editabletree.py

  • Committer: Bazaar Package Importer
  • Author(s): Daniel Baumann
  • Date: 2008-12-07 20:17:00 UTC
  • Revision ID: james.westby@ubuntu.com-20081207201700-a875pic3sd7xkoru
Tags: upstream-5.0.0~alpha
ImportĀ upstreamĀ versionĀ 5.0.0~alpha

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# -*- encoding: utf-8 -*-
 
2
##############################################################################
 
3
#
 
4
#    OpenERP, Open Source Management Solution   
 
5
#    Copyright (C) 2004-2008 Tiny SPRL (<http://tiny.be>). All Rights Reserved
 
6
#    $Id$
 
7
#
 
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.
 
12
#
 
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.
 
17
#
 
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/>.
 
20
#
 
21
##############################################################################
 
22
 
 
23
import gtk
 
24
import parser
 
25
import observator
 
26
 
 
27
 
 
28
class EditableTreeView(gtk.TreeView, observator.Observable):
 
29
 
 
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) 
 
34
 
 
35
    def __init__(self, position):
 
36
        super(EditableTreeView, self).__init__()
 
37
        self.editable = position
 
38
        self.cells = {}
 
39
 
 
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]
 
45
 
 
46
        # The value has not changed ... do nothing.
 
47
        if value == cell.get_textual_value(current_model):
 
48
            return
 
49
 
 
50
        try:
 
51
            real_value = cell.value_from_text(current_model, value)
 
52
            modelfield.set_client(current_model, real_value)
 
53
        except parser.UnsettableColumn:
 
54
            return
 
55
 
 
56
        # And now the on_change stuff ... only 3 lines are enough.
 
57
        #callback = modelfield.attrs.get('on_change', '')
 
58
        #if callback:
 
59
        #   current_model.on_change(callback)
 
60
 
 
61
        # And finally the conditional default
 
62
        #if modelfield.attrs.get('change_default', False):
 
63
        #   current_model.cond_default(fieldname, real_value)
 
64
 
 
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:
 
69
            changed = True
 
70
        else:
 
71
            changed=False
 
72
        try:
 
73
            valid, value = cell.open_remote(current_model, create, changed, value)
 
74
            if valid:
 
75
                modelfield.set_client(current_model, value)
 
76
        except NotImplementedError:
 
77
            pass
 
78
        return cell.get_textual_value(current_model)
 
79
 
 
80
    def on_create_line(self):
 
81
        model = self.get_model()
 
82
        if self.editable == 'top':
 
83
            method = model.prepend
 
84
        else:
 
85
            method = model.append
 
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)
 
91
        return res
 
92
 
 
93
 
 
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')
 
100
            else:
 
101
                return renderer.get_property('editable')
 
102
        
 
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)
 
108
        return columns
 
109
 
 
110
 
 
111
    def __next_column(self, col):
 
112
        cols = self.get_columns(False, False)
 
113
        current = cols.index(col)
 
114
        idx = (current + 1) % len(cols)
 
115
        return cols[idx]
 
116
 
 
117
    def __prev_column(self, col):
 
118
        cols = self.get_columns(False, False)
 
119
        current = cols.index(col)
 
120
        idx = (current - 1) % len(cols)
 
121
        return cols[idx]
 
122
 
 
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')):
 
128
            start_editing=False
 
129
        else:
 
130
            self.warn('misc-message', '')
 
131
        return super(EditableTreeView, self).set_cursor(path, focus_column,
 
132
                start_editing)
 
133
 
 
134
    def set_value(self):
 
135
        path, column = self.get_cursor()
 
136
        store = self.get_model()
 
137
        if not path or not column:
 
138
            return True
 
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()
 
145
            else:
 
146
                txt = entry.get_active_text()
 
147
            self.on_quit_cell(model, column.name, txt)
 
148
        return True
 
149
 
 
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):
 
162
                    if line[1] == txt:
 
163
                        break
 
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))
 
168
                return True
 
169
            else:
 
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:
 
176
                id = model.save()
 
177
                if not id:
 
178
                    return True
 
179
                else:
 
180
                    res = store.saved(id)
 
181
                    if res:
 
182
                        self.screen.display(res.id)
 
183
 
 
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:
 
188
                        break
 
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")
 
192
                return True
 
193
 
 
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)
 
207
            else:
 
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:
 
212
            if model.id is None:
 
213
                store.remove(store.get_iter(path))
 
214
                self.screen.current_model = False
 
215
            if not path[0]:
 
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')):
 
221
                return True
 
222
            if isinstance(entry, gtk.Entry):
 
223
                value=entry.get_text()
 
224
            else:
 
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)
 
231
            else:
 
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)
 
235
            return False
 
236
        else:
 
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)
 
244
            return False
 
245
 
 
246
        return True
 
247
 
 
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)
 
253
        return new_path
 
254
 
 
255
    def _key_up(self, path, store, column):
 
256
        if path[0] == 0 and self.editable == 'top':
 
257
            self.on_create_line()
 
258
            new_path = 0
 
259
        else:
 
260
            new_path = (path[0] - 1) % len(store)
 
261
        self.set_cursor(new_path, column, True)
 
262
        return new_path
 
263
 
 
264
    def on_editing_done(self, entry):
 
265
        path, column = self.get_cursor()
 
266
        if not path:
 
267
            return True
 
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())
 
274
 
 
275
 
 
276
# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
 
277