Merge lp:~openerp-commiter/openobject-client/client-image-widget into lp:openobject-client
- client-image-widget
- Merge into trunk
Status: | Needs review |
---|---|
Proposed branch: | lp:~openerp-commiter/openobject-client/client-image-widget |
Merge into: | lp:openobject-client |
Diff against target: |
723 lines (+583/-8) (has conflicts) 8 files modified
bin/widget/model/field.py (+1/-0) bin/widget/view/common_gtk.py (+193/-0) bin/widget/view/form_gtk/image.py (+18/-2) bin/widget/view/form_gtk/many2one_image.py (+202/-0) bin/widget/view/form_gtk/parser.py (+5/-5) bin/widget/view/tree_gtk/image.py (+64/-0) bin/widget/view/tree_gtk/many2one_image.py (+81/-0) bin/widget/view/tree_gtk/parser.py (+19/-1) Text conflict in bin/widget/view/form_gtk/image.py Text conflict in bin/widget/view/tree_gtk/parser.py |
To merge this branch: | bzr merge lp:~openerp-commiter/openobject-client/client-image-widget |
Related bugs: | |
Related blueprints: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Christophe Simonis (OpenERP) | Needs Fixing | ||
Stephane Wirtel (OpenERP) | Pending | ||
Review via email: mp+23325@code.launchpad.net |
Commit message
Description of the change
* Add image widget for Tree.image (and so for Form.M2M too), Tree.M2O_image, Form.M2O_image
* Add more image format support.
Pictures of those new widgets in action:
* http://
* http://
* http://
See branch description for more details.
Christophe Simonis (OpenERP) (kangol) wrote : | # |
Nicolas DS (zyphos) wrote : | # |
In GTK client all widget are separated by view. And the "Tree.image" does NOT exist for now in GTK client.
This branch is keeping widget="image" for image in Tree view, Form view and many2many in Form view (of course it is a tree view embedded in a screen view that acts as a form widget.).
In GTK client, "Tree view" widget are a lot different from "Form" one indeed:
- You need to define a new kind of cell renderer for special content. (It's not as easy as Form view)
- GTK treeview widget is asking a LOT of time to refresh row and column. (2 times to show the widget and then each time the mouse pointer get over, get out of any cell.)
In any refresh with the standard widget you need to: get the image data, compute the pixbuf (GTK image), then rescale the pixbuf to fit into screen. Those operations are very CPU/bandwidth intensive, that's why those widget need to be cached (for 120sec defined in bin/widget/
The problem with caching is the fact that if the image change in DB, the client will see the change maximum 120 sec further, and not in real time. Real time is still working for the basic "Form.image" widget.
At first I tried to use the widget="image" for many2one, but it's impossible in widget definition in openobject to know where type of field data come from.
Indeed widget python code is called from:
client/
widget_act = widgets_
and "type" is not alway the original field type, indeed:
@line 401
type = attrs.get('widget', fields[
and the many2one image widget needs to search for a binary field to get the data from.
I don't understand your one2many thing, I didn't made any one2many widget, if you look into source you will see that many2one_image is inheriting from the many2one class. So even if the many2one original class is a one2many I am lost.
The only fixing I see could be a different caching time or avoiding this caching time. But the best solution would be to create some kind of "push on field content change" from server to the client. But this feature needs a persistent connection to the server.
http://
Or another one is to store hash of binary fields to quickly see if the image content has changed without eating lot of resource (CPU/bandwidth).
Don't forget to have a look at branch description for more information how this branch is working:
https:/
Thank you for your review.
Christophe Simonis (OpenERP) (kangol) wrote : | # |
After seeing you example module, I see what you try to do with the many2one_image.
You are doing it wrong. You have to create a related field on your object and use it with widget="image"
I modify your code (untested):
http://
http://
Nicolas DS (zyphos) wrote : | # |
You are right for the Tree.many2one_image widget !
I must admit that I never used field.related before ;)
It acts like the one I created.
But for the Form.many2one_image the purpose isn't the same.
The Form.many2one_image represent a M2O object, so you can change the M2O relation directly, with your solution you will directly change the showed image itself and not the M2O relation.
- 1214. By Nicolas DS
-
[MERGE] from official trunk GTK client
- 1215. By Nicolas DS
-
[IMP] get_pixbuf and get_thumbnail support multi id query
- 1216. By Nicolas DS
-
[FIX] image-widget: Typos + Making code more robust
- 1217. By Nicolas DS
-
[REF] Form.M2O_image widget
- 1218. By Nicolas DS
-
[IMP] Added clear button for the form.M2O_image widget
- 1219. By Nicolas DS
-
[IMP] form.M2O_image code clean up
Unmerged revisions
- 1219. By Nicolas DS
-
[IMP] form.M2O_image code clean up
- 1218. By Nicolas DS
-
[IMP] Added clear button for the form.M2O_image widget
- 1217. By Nicolas DS
-
[REF] Form.M2O_image widget
- 1216. By Nicolas DS
-
[FIX] image-widget: Typos + Making code more robust
- 1215. By Nicolas DS
-
[IMP] get_pixbuf and get_thumbnail support multi id query
- 1214. By Nicolas DS
-
[MERGE] from official trunk GTK client
- 1213. By Nicolas DS
-
[FIX] Bug due to model field change
- 1212. By Nicolas DS
-
[FIX] M2O image find and get
- 1211. By Nicolas DS
-
[FIX] Case when M2O relation is empty
- 1210. By Nicolas DS
-
[MERGE] From trunk
Preview Diff
1 | === modified file 'bin/widget/model/field.py' |
2 | --- bin/widget/model/field.py 2010-10-05 07:05:05 +0000 |
3 | +++ bin/widget/model/field.py 2010-10-06 13:46:15 +0000 |
4 | @@ -496,6 +496,7 @@ |
5 | 'integer' : IntegerField, |
6 | 'float' : FloatField, |
7 | 'many2one' : M2OField, |
8 | + 'many2one_image' : M2OField, |
9 | 'many2many' : M2MField, |
10 | 'one2many' : O2MField, |
11 | 'reference' : ReferenceField, |
12 | |
13 | === added file 'bin/widget/view/common_gtk.py' |
14 | --- bin/widget/view/common_gtk.py 1970-01-01 00:00:00 +0000 |
15 | +++ bin/widget/view/common_gtk.py 2010-10-06 13:46:15 +0000 |
16 | @@ -0,0 +1,193 @@ |
17 | +# -*- coding: utf-8 -*- |
18 | +############################################################################## |
19 | +# |
20 | +# OpenERP, Open Source Management Solution |
21 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
22 | +# |
23 | +# This program is free software: you can redistribute it and/or modify |
24 | +# it under the terms of the GNU Affero General Public License as |
25 | +# published by the Free Software Foundation, either version 3 of the |
26 | +# License, or (at your option) any later version. |
27 | +# |
28 | +# This program is distributed in the hope that it will be useful, |
29 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
30 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
31 | +# GNU Affero General Public License for more details. |
32 | +# |
33 | +# You should have received a copy of the GNU Affero General Public License |
34 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
35 | +# |
36 | +############################################################################## |
37 | + |
38 | +import collections |
39 | +import base64 |
40 | +import time |
41 | +import gtk |
42 | +import rpc |
43 | +import common |
44 | + |
45 | +NOIMAGE = file(common.terp_path_pixmaps("noimage.png"), 'rb').read() |
46 | + |
47 | +def make_pixbuf(data): |
48 | + ''' Generate a pixbuf (gnome image) from a image file data |
49 | + Warning this processing is slow and should be cached''' |
50 | + if not data: |
51 | + data = NOIMAGE |
52 | + else: |
53 | + data = base64.decodestring(data) |
54 | + pixbuf = None |
55 | + loader = gtk.gdk.PixbufLoader() |
56 | + try: |
57 | + loader.write(data, len(data)) |
58 | + pixbuf = loader.get_pixbuf() |
59 | + except: |
60 | + pass |
61 | + if not pixbuf: |
62 | + loader.close() |
63 | + loader = gtk.gdk.PixbufLoader('png') |
64 | + loader.write(NOIMAGE, len(NOIMAGE)) |
65 | + pixbuf = loader.get_pixbuf() |
66 | + |
67 | + loader.close() |
68 | + return pixbuf |
69 | + |
70 | +def thumbnail_pixbuf(pixbuf, to_width, to_height): |
71 | + ''' Make a thumbnail of the image according to dimensions provided |
72 | + Warning this processing is slow and should be cached''' |
73 | + img_height = pixbuf.get_height() |
74 | + if img_height > to_height: |
75 | + height = to_height |
76 | + else: |
77 | + height = img_height |
78 | + |
79 | + img_width = pixbuf.get_width() |
80 | + if img_width > to_width: |
81 | + width = to_width |
82 | + else: |
83 | + width = img_width |
84 | + |
85 | + if (img_width / width) < (img_height / height): |
86 | + width = float(img_width) / float(img_height) * float(height) |
87 | + else: |
88 | + height = float(img_height) / float(img_width) * float(width) |
89 | + |
90 | + return pixbuf.scale_simple(int(width), int(height), gtk.gdk.INTERP_BILINEAR) |
91 | + |
92 | +class CacheDict(collections.MutableMapping): |
93 | + ''' Act like a dict but with timeout |
94 | + @param timeout: timeout in seconds |
95 | + ''' |
96 | + def __init__(self, timeout=30): |
97 | + self.cache = {} |
98 | + self.timeout = int(timeout) |
99 | + self.last_check = time.time() |
100 | + |
101 | + def __setitem__(self, key, value): |
102 | + if not self.timeout: |
103 | + return |
104 | + self.check_timeout() |
105 | + self.cache[key] = (value, time.time()) |
106 | + |
107 | + def __getitem__(self, key): |
108 | + if key not in self.cache: |
109 | + raise KeyError |
110 | + return self.cache[key][0] |
111 | + |
112 | + def __delitem__(self, key): |
113 | + if key not in self.cache: |
114 | + raise KeyError |
115 | + del self.cache[key] |
116 | + |
117 | + def keys(self): |
118 | + return self.cache.keys() |
119 | + |
120 | + def __iter__(self): |
121 | + for key in self.cache: |
122 | + yield key |
123 | + |
124 | + def __len__(self): |
125 | + return len(self.cache) |
126 | + |
127 | + def check_timeout(self): |
128 | + ''' Remove expired cache ''' |
129 | + now = time.time() |
130 | + if now < self.last_check + self.timeout / 10: |
131 | + return |
132 | + ct = now - self.timeout # Critical time |
133 | + key2del = [k for k in self.cache if self.cache[k][1] < ct] |
134 | + for key in key2del: |
135 | + self.cache.pop(key) |
136 | + |
137 | +CACHE_TIME_OUT = 120 # In second |
138 | +pixbuf_cache = CacheDict(CACHE_TIME_OUT) |
139 | + |
140 | + |
141 | +def get_pixbuf(model, field, ids): |
142 | + ''' Retrieve the data and convert to pixbuf if possible |
143 | + @param model: The model that the image come from. (Ie: res.images) |
144 | + @param field: The column of the model. (Ie: img) |
145 | + @param ids: Id of records row in database (int or list of int) |
146 | + @return pixbuf or dict of pixbuf |
147 | + ''' |
148 | + result = {} |
149 | + is_int = isinstance(ids, int) |
150 | + if is_int: |
151 | + ids = [ids] |
152 | + cache_ids = dict([(id, "%s[%s][%d]" % (model, field, id)) for id in ids]) |
153 | + ids2query = [] |
154 | + for id in ids: |
155 | + if cache_ids[id] in pixbuf_cache: |
156 | + result[id] = pixbuf_cache[cache_ids[id]] |
157 | + else: |
158 | + ids2query.append(id) |
159 | + |
160 | + if ids2query: |
161 | + datas = rpc.session.rpc_exec_auth('/object', 'execute', model, 'read', |
162 | + ids2query, ['id', field]) |
163 | + for res in datas: |
164 | + id = res['id'] |
165 | + pixbuf = make_pixbuf(res[field]) |
166 | + pixbuf_cache[cache_ids[id]] = pixbuf |
167 | + result[id] = pixbuf |
168 | + |
169 | + # Complete result table |
170 | + for id in ids: |
171 | + if id not in result: |
172 | + result[id] = None |
173 | + |
174 | + if is_int: |
175 | + return result[0] |
176 | + return result |
177 | + |
178 | +def get_thumbnail(model, field, ids, to_width, to_height): |
179 | + ''' Resize Image to fit width, height cache result to be used later. |
180 | + @param model: The model that the image come from. (Ie: res.images) |
181 | + @param field: The column of the resource. (Ie: img) |
182 | + @param ids: Id of records row in database. (int or list of int) |
183 | + @param to_width: Maximum width in pixel. |
184 | + @param to_height: Maximum height in pixel. |
185 | + @return pixbuf or dict of pixbuf |
186 | + ''' |
187 | + if not ids: |
188 | + return None |
189 | + result = {} |
190 | + is_int = isinstance(ids, int) |
191 | + if is_int: |
192 | + ids = [ids] |
193 | + |
194 | + ids2query = [] |
195 | + cache_ids = dict([(id, "%s[%s][%d]-%dx%d" % (model, field, id, to_width, to_height)) for id in ids]) |
196 | + for id in ids: |
197 | + if cache_ids[id] in pixbuf_cache: |
198 | + result[id] = pixbuf_cache[cache_ids[id]] |
199 | + else: |
200 | + ids2query.append(id) |
201 | + if ids2query: |
202 | + pixbufs = get_pixbuf(model, field, ids2query) |
203 | + for id, pixbuf in pixbufs.iteritems(): |
204 | + thumbnail = thumbnail_pixbuf(pixbuf, to_width, to_height) |
205 | + pixbuf_cache[cache_ids[id]] = thumbnail |
206 | + result[id] = thumbnail |
207 | + if is_int: |
208 | + return result[ids[0]] |
209 | + return result |
210 | |
211 | === modified file 'bin/widget/view/form_gtk/image.py' |
212 | --- bin/widget/view/form_gtk/image.py 2010-09-08 06:30:55 +0000 |
213 | +++ bin/widget/view/form_gtk/image.py 2010-10-06 13:46:15 +0000 |
214 | @@ -31,9 +31,13 @@ |
215 | import interface |
216 | import tempfile |
217 | import urllib |
218 | +<<<<<<< TREE |
219 | |
220 | NOIMAGE = file(common.terp_path_pixmaps("noimage.png"), 'rb').read() |
221 | REQUIRED_IMG = file(common.terp_path_pixmaps("image_required.png"), 'rb').read() |
222 | +======= |
223 | +from widget.view import common_gtk |
224 | +>>>>>>> MERGE-SOURCE |
225 | |
226 | |
227 | class image_wid(interface.widget_interface): |
228 | @@ -106,9 +110,16 @@ |
229 | |
230 | filter_image = gtk.FileFilter() |
231 | filter_image.set_name(_('Images')) |
232 | - for mime in ("image/png", "image/jpeg", "image/gif"): |
233 | + supported_mime = ('image/bmp', 'image/gif', 'image/jpeg', 'image/tiff', |
234 | + 'image/x-cmu-raster', 'image/x-icon', |
235 | + 'image/x-portable-anymap', 'image/x-xbitmap', |
236 | + 'image/x-xpixmap', 'image/png') |
237 | + supported_pat = ('*.ani', '*.bmp', '*.gif', '*.ico', '*.jpe', '*.jpeg', |
238 | + '*.jpg', '*.pcx', '*.png', '*.pnm', '*.ras', '*.tga', |
239 | + '*.tif', '*.tiff', '*.xbm', '*.xpm') |
240 | + for mime in supported_mime: |
241 | filter_image.add_mime_type(mime) |
242 | - for pat in ("*.png", "*.jpg", "*.gif", "*.tif", "*.xpm"): |
243 | + for pat in supported_pat: |
244 | filter_image.add_pattern(pat) |
245 | |
246 | filename = common.file_selection(_('Open...'), parent=self._window, preview=True, |
247 | @@ -164,6 +175,7 @@ |
248 | self.update_img() |
249 | |
250 | def update_img(self): |
251 | +<<<<<<< TREE |
252 | if not self._value: |
253 | if self._set_required_img: |
254 | data = REQUIRED_IMG |
255 | @@ -207,6 +219,10 @@ |
256 | height = float(img_height) / float(img_width) * float(width) |
257 | |
258 | scaled = pixbuf.scale_simple(int(width), int(height), gtk.gdk.INTERP_BILINEAR) |
259 | +======= |
260 | + pixbuf = common_gtk.make_pixbuf(self._value) |
261 | + scaled = common_gtk.thumbnail_pixbuf(pixbuf, self.width, self.height) |
262 | +>>>>>>> MERGE-SOURCE |
263 | self.image.set_from_pixbuf(scaled) |
264 | |
265 | def display(self, model, model_field): |
266 | |
267 | === added file 'bin/widget/view/form_gtk/many2one_image.py' |
268 | --- bin/widget/view/form_gtk/many2one_image.py 1970-01-01 00:00:00 +0000 |
269 | +++ bin/widget/view/form_gtk/many2one_image.py 2010-10-06 13:46:15 +0000 |
270 | @@ -0,0 +1,202 @@ |
271 | +# -*- coding: utf-8 -*- |
272 | +############################################################################## |
273 | +# |
274 | +# OpenERP, Open Source Management Solution |
275 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
276 | +# |
277 | +# This program is free software: you can redistribute it and/or modify |
278 | +# it under the terms of the GNU Affero General Public License as |
279 | +# published by the Free Software Foundation, either version 3 of the |
280 | +# License, or (at your option) any later version. |
281 | +# |
282 | +# This program is distributed in the hope that it will be useful, |
283 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
284 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
285 | +# GNU Affero General Public License for more details. |
286 | +# |
287 | +# You should have received a copy of the GNU Affero General Public License |
288 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
289 | +# |
290 | +############################################################################## |
291 | + |
292 | +import gobject |
293 | +import gtk |
294 | + |
295 | +import interface |
296 | +import common |
297 | + |
298 | +import widget |
299 | +from widget.screen import Screen |
300 | + |
301 | +from modules.gui.window.win_search import win_search |
302 | +import rpc |
303 | + |
304 | +import service |
305 | + |
306 | +import many2one |
307 | +from widget.view import common_gtk |
308 | + |
309 | +class many2one_image(many2one.many2one): |
310 | + ''' Binary many2one image form widget |
311 | + Ie: |
312 | + <field name="img_id" widget="many2one_image" fieldname="img"/> |
313 | + |
314 | + img_field (optional): field of the many2one relation to show in widget |
315 | + Otherwise it will take the first binary field. |
316 | + ''' |
317 | + def __init__(self, window, parent, model, attrs={}): |
318 | + interface.widget_interface.__init__(self, window, parent, model, attrs) |
319 | + |
320 | + self.widget = gtk.VBox(spacing=3) |
321 | + |
322 | + self.image = gtk.Image() |
323 | + self.height = int(attrs.get('img_height', 100)) |
324 | + self.width = int(attrs.get('img_width', 300)) |
325 | + self.model_field = False |
326 | + |
327 | + self.widget.pack_start(self.image, expand=True, fill=True) |
328 | + |
329 | + alignment = gtk.Alignment(xalign=0.5, yalign=0.5) |
330 | + hbox = gtk.HBox() |
331 | + |
332 | + self.but_open = many2one.Button('gtk-open', self.sig_edit, |
333 | + _('Open this resource')) |
334 | + hbox.pack_start(self.but_open, padding=2, expand=False, fill=False) |
335 | + |
336 | + self.but_find = many2one.Button('gtk-find', self.sig_find, |
337 | + _('Search a resource')) |
338 | + hbox.pack_start(self.but_find, padding=2, expand=False, fill=False) |
339 | + |
340 | + self.but_remove = many2one.Button('gtk-clear', self.sig_remove, |
341 | + _('Clear')) |
342 | + hbox.pack_start(self.but_remove, padding=2, expand=False, fill=False) |
343 | + |
344 | + alignment.add(hbox) |
345 | + self.widget.pack_start(alignment, expand=False, fill=False) |
346 | + |
347 | + self.value_on_field = '' |
348 | + self.ok = True |
349 | + self._readonly = False |
350 | + self.model_type = attrs['relation'] |
351 | + self._menu_loaded = False |
352 | + self._menu_entries.append((None, None, None)) |
353 | + self._menu_entries.append((_('Action'), lambda x: self.click_and_action('client_action_multi'), 0)) |
354 | + self._menu_entries.append((_('Report'), lambda x: self.click_and_action('client_print_multi'), 0)) |
355 | + |
356 | + def _readonly_set(self, value): |
357 | + self._readonly = value |
358 | + #self.but_new.set_sensitive(not value) |
359 | + |
360 | + def _color_widget(self): |
361 | + return self.image |
362 | + |
363 | + def sig_find(self, widget, event=None, leave=True): |
364 | + self.ok = False |
365 | + if not self._readonly: |
366 | + domain = self.model_field.domain_get(self._view.model) |
367 | + context = self.model_field.context_get(self._view.model) |
368 | + #self.wid_text.grab_focus() |
369 | + name_search = '' |
370 | + relation = self.attrs['relation'] |
371 | + ids = rpc.session.rpc_exec_auth('/object', 'execute', relation, |
372 | + 'name_search', name_search, domain, |
373 | + 'ilike', context) |
374 | + if (len(ids) == 1) and leave: |
375 | + self.model_field.set_client(self._view.model, ids[0], |
376 | + force_change=True) |
377 | + self.display(self._view.model, self.model_field) |
378 | + self.ok = True |
379 | + return True |
380 | + |
381 | + win = win_search(self.attrs['relation'], sel_multi=False, |
382 | + ids=map(lambda x: x[0], ids), context=context, |
383 | + domain=domain, parent=self._window) |
384 | + ids = win.go() |
385 | + if ids: |
386 | + name = rpc.session.rpc_exec_auth('/object', 'execute', relation, |
387 | + 'name_get', [ids[0]], |
388 | + rpc.session.context)[0] |
389 | + self.model_field.set_client(self._view.model, name, |
390 | + force_change=True) |
391 | + self.display(self._view.model, self.model_field) |
392 | + self.ok = True |
393 | + |
394 | + def sig_edit(self, widget, event=None, leave=False): |
395 | + self.ok = False |
396 | + if not leave: |
397 | + domain = self.model_field.domain_get(self._view.model) |
398 | + context = self.model_field.context_get(self._view.model) |
399 | + m2o_value = self.model_field.get(self._view.model, False) |
400 | + relation_record_id = self.model_field.get(self._view.model) |
401 | + dia = many2one.dialog(self.attrs['relation'], |
402 | + relation_record_id, |
403 | + attrs=self.attrs, window=self._window, |
404 | + domain=domain, context=context) |
405 | + ok, value = dia.run() |
406 | + if ok: |
407 | + self.model_field.set_client(self._view.model, value, |
408 | + force_change=True) |
409 | + dia.destroy() |
410 | + self.display(self._view.model, self.model_field) |
411 | + self.ok = True |
412 | + |
413 | + def sig_new(self, *args): |
414 | + domain = self.model_field.domain_get(self._view.model) |
415 | + context = self.model_field.context_get(self._view.model) |
416 | + dia = many2one.dialog(self.attrs['relation'], attrs=self.attrs, |
417 | + window=self._window, domain=domain, |
418 | + context=context) |
419 | + ok, value = dia.run() |
420 | + if ok: |
421 | + self.model_field.set_client(self._view.model, value) |
422 | + self.display(self._view.model, self.model_field) |
423 | + dia.destroy() |
424 | + |
425 | + def sig_remove(self, widget=None): |
426 | + self.model_field.set_client(self._view.model, False) |
427 | + self.display(self._view.model, False) |
428 | + |
429 | + def sig_key_press(self, widget, event, *args): |
430 | + if event.keyval == gtk.keysyms.F1: |
431 | + self.sig_new(widget, event) |
432 | + elif event.keyval == gtk.keysyms.F2: |
433 | + self.sig_activate(widget, event) |
434 | + elif event.keyval == gtk.keysyms.F3: |
435 | + self.sig_edit(widget, event) |
436 | + elif event.keyval == gtk.keysyms.Tab: |
437 | + self.sig_activate(widget, event, leave=True) |
438 | + return True |
439 | + return False |
440 | + |
441 | + def display(self, model, model_field): |
442 | + if not model_field: |
443 | + self.ok = False |
444 | + return False |
445 | + self.model_field = model_field |
446 | + interface.widget_interface.display(self, model, model_field) |
447 | + self.ok = False |
448 | + # retrieve image data |
449 | + m2o_value = model.value.get(model_field.name, False) |
450 | + relation_record_id = m2o_value and m2o_value[0] |
451 | + relation = model_field.attrs['relation'] |
452 | + img_field_name = model_field.attrs.get('fieldname', False) |
453 | + if not img_field_name: |
454 | + # if not img_field is set get the first binary field |
455 | + fields = rpc.session.rpc_exec_auth('/object', 'execute', relation, |
456 | + 'fields_get') |
457 | + for field_name in fields: |
458 | + if fields[field_name]['type'] == 'binary': |
459 | + img_field_name = field_name |
460 | + break |
461 | + |
462 | + if not img_field_name: |
463 | + return |
464 | + |
465 | + thumbnail = common_gtk.get_thumbnail(relation, img_field_name, |
466 | + relation_record_id, self.width, |
467 | + self.height) |
468 | + self.image.set_from_pixbuf(thumbnail) |
469 | + self.ok = True |
470 | + |
471 | +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
472 | + |
473 | |
474 | === modified file 'bin/widget/view/form_gtk/parser.py' |
475 | --- bin/widget/view/form_gtk/parser.py 2010-09-23 09:18:56 +0000 |
476 | +++ bin/widget/view/form_gtk/parser.py 2010-10-06 13:46:15 +0000 |
477 | @@ -462,11 +462,9 @@ |
478 | continue |
479 | |
480 | fields[name]['name'] = name |
481 | - if 'saves' in attrs: |
482 | - fields[name]['saves'] = attrs['saves'] |
483 | - |
484 | - if 'filename' in attrs: |
485 | - fields[name]['filename'] = attrs['filename'] |
486 | + for attr in ['saves', 'filename', 'fieldname']: |
487 | + if attr in attrs: |
488 | + fields[name][attr] = attrs[attr] |
489 | |
490 | if fields[name]['type'] == 'many2one' and 'search_mode' in attrs: |
491 | fields[name]['search_mode'] = attrs['search_mode'] |
492 | @@ -907,6 +905,7 @@ |
493 | #import one2many |
494 | import many2many |
495 | import many2one |
496 | +import many2one_image |
497 | import selection |
498 | import one2many_list |
499 | import picture |
500 | @@ -937,6 +936,7 @@ |
501 | 'one2many_list': (one2many_list.one2many_list, 1, True, True), |
502 | 'many2many': (many2many.many2many, 1, True, True), |
503 | 'many2one': (many2one.many2one, 1, False, False), |
504 | + 'many2one_image': (many2one_image.many2one_image, 1, False, False), |
505 | 'email' : (url.email, 1, False, False), |
506 | 'url' : (url.url, 1, False, False), |
507 | 'callto' : (url.callto, 1, False, False), |
508 | |
509 | === added file 'bin/widget/view/tree_gtk/image.py' |
510 | --- bin/widget/view/tree_gtk/image.py 1970-01-01 00:00:00 +0000 |
511 | +++ bin/widget/view/tree_gtk/image.py 2010-10-06 13:46:15 +0000 |
512 | @@ -0,0 +1,64 @@ |
513 | +# -*- coding: utf-8 -*- |
514 | +############################################################################## |
515 | +# |
516 | +# OpenERP, Open Source Management Solution |
517 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
518 | +# |
519 | +# This program is free software: you can redistribute it and/or modify |
520 | +# it under the terms of the GNU Affero General Public License as |
521 | +# published by the Free Software Foundation, either version 3 of the |
522 | +# License, or (at your option) any later version. |
523 | +# |
524 | +# This program is distributed in the hope that it will be useful, |
525 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
526 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
527 | +# GNU Affero General Public License for more details. |
528 | +# |
529 | +# You should have received a copy of the GNU Affero General Public License |
530 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
531 | +# |
532 | +############################################################################## |
533 | + |
534 | + |
535 | +import gtk |
536 | + |
537 | +import service |
538 | +import common |
539 | +from widget.view import common_gtk |
540 | + |
541 | +class Image(object): |
542 | + def __init__(self, field_name, treeview=None, attrs=None, window=None): |
543 | + self.field_name = field_name |
544 | + self.attrs = attrs or {} |
545 | + self.renderer = gtk.CellRendererPixbuf() |
546 | + self.editable = attrs.get('editable',False) |
547 | + self.height = int(attrs.get('img_height', 33)) |
548 | + self.width = int(attrs.get('img_width', 100)) |
549 | + self.treeview = treeview |
550 | + if not window: |
551 | + window = service.LocalService('gui.main').window |
552 | + self.window = window |
553 | + self.pixbuf_cache = {} # warning caching consequence is memory eated. |
554 | + |
555 | + def setter(self, column, cell, store, iter): |
556 | + model_record = store.get_value(iter, 0) |
557 | + resource = model_record.resource |
558 | + resource_id = model_record.id |
559 | + field_name = column.name |
560 | + thumbnail = common_gtk.get_thumbnail(resource, field_name, |
561 | + resource_id, self.width, |
562 | + self.height) |
563 | + |
564 | + cell.set_property('pixbuf', thumbnail) |
565 | + |
566 | + def open_remote(self, model, create, changed=False, text=None): |
567 | + raise NotImplementedError |
568 | + |
569 | + def _get_data(self, model_record): |
570 | + return model_record[self.field_name].get_client(model_record) |
571 | + |
572 | + def get_textual_value(self, model): |
573 | + return '' |
574 | + |
575 | + def value_from_text(self, model, text): |
576 | + return text |
577 | \ No newline at end of file |
578 | |
579 | === added file 'bin/widget/view/tree_gtk/many2one_image.py' |
580 | --- bin/widget/view/tree_gtk/many2one_image.py 1970-01-01 00:00:00 +0000 |
581 | +++ bin/widget/view/tree_gtk/many2one_image.py 2010-10-06 13:46:15 +0000 |
582 | @@ -0,0 +1,81 @@ |
583 | +# -*- coding: utf-8 -*- |
584 | +############################################################################## |
585 | +# |
586 | +# OpenERP, Open Source Management Solution |
587 | +# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>). |
588 | +# |
589 | +# This program is free software: you can redistribute it and/or modify |
590 | +# it under the terms of the GNU Affero General Public License as |
591 | +# published by the Free Software Foundation, either version 3 of the |
592 | +# License, or (at your option) any later version. |
593 | +# |
594 | +# This program is distributed in the hope that it will be useful, |
595 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
596 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
597 | +# GNU Affero General Public License for more details. |
598 | +# |
599 | +# You should have received a copy of the GNU Affero General Public License |
600 | +# along with this program. If not, see <http://www.gnu.org/licenses/>. |
601 | +# |
602 | +############################################################################## |
603 | + |
604 | + |
605 | +import gtk |
606 | + |
607 | +import service |
608 | +import common |
609 | +from widget.view import common_gtk |
610 | +import rpc |
611 | + |
612 | +class many2one_image(object): |
613 | + ''' Binary many2one image tree widget |
614 | + Ie: |
615 | + <field name="img_id" widget="many2one_image" fieldname="img"/> |
616 | + |
617 | + fieldname (optional): field of the many2one relation to show in widget |
618 | + Otherwise it will take the first binary field. |
619 | + ''' |
620 | + def __init__(self, field_name, treeview=None, attrs=None, window=None): |
621 | + self.field_name = field_name |
622 | + self.attrs = attrs or {} |
623 | + self.renderer = gtk.CellRendererPixbuf() |
624 | + self.editable = attrs.get('editable',False) |
625 | + self.height = int(attrs.get('img_height', 33)) |
626 | + self.width = int(attrs.get('img_width', 100)) |
627 | + self.treeview = treeview |
628 | + if not window: |
629 | + window = service.LocalService('gui.main').window |
630 | + self.window = window |
631 | + |
632 | + self.img_field_name = self.attrs.get('fieldname', False) |
633 | + if not self.img_field_name: |
634 | + # if not img_field is set get the first binary field |
635 | + fields = rpc.session.rpc_exec_auth('/object', 'execute', self.attrs['relation'], 'fields_get') |
636 | + for field_name in fields: |
637 | + if fields[field_name]['type'] == 'binary': |
638 | + self.img_field_name = field_name |
639 | + break |
640 | + |
641 | + def setter(self, column, cell, store, iter): |
642 | + if not self.img_field_name: |
643 | + return |
644 | + |
645 | + model_record = store.get_value(iter, 0) |
646 | + m2o_value = model_record.value[self.field_name] |
647 | + relation_record_id = m2o_value and m2o_value[0] |
648 | + relation = self.attrs['relation'] |
649 | + |
650 | + thumbnail = common_gtk.get_thumbnail(relation, self.img_field_name, |
651 | + relation_record_id, self.width, |
652 | + self.height) |
653 | + |
654 | + cell.set_property('pixbuf', thumbnail) |
655 | + |
656 | + def open_remote(self, model, create, changed=False, text=None): |
657 | + raise NotImplementedError |
658 | + |
659 | + def get_textual_value(self, model): |
660 | + return '' |
661 | + |
662 | + def value_from_text(self, model, text): |
663 | + return text |
664 | \ No newline at end of file |
665 | |
666 | === modified file 'bin/widget/view/tree_gtk/parser.py' |
667 | --- bin/widget/view/tree_gtk/parser.py 2010-09-30 06:52:47 +0000 |
668 | +++ bin/widget/view/tree_gtk/parser.py 2010-10-06 13:46:15 +0000 |
669 | @@ -131,7 +131,7 @@ |
670 | if node.tag == 'field': |
671 | handler_id = False |
672 | fname = str(node_attrs['name']) |
673 | - if fields[fname]['type'] in ('image', 'binary'): |
674 | + if fields[fname]['type'] in ('binary', ): |
675 | continue # not showed types |
676 | if fname == 'sequence': |
677 | treeview.sequence = True |
678 | @@ -178,6 +178,7 @@ |
679 | 'integer': (60, 170), |
680 | 'float': (80, 300), |
681 | 'float_time': (80,150), |
682 | +<<<<<<< TREE |
683 | 'date': (70, False), |
684 | 'datetime': (145, 145), |
685 | 'selection': (90, 250), |
686 | @@ -186,6 +187,17 @@ |
687 | 'many2many': (50, False), |
688 | 'boolean': (20, 80), |
689 | 'progressbar':(150, 200) |
690 | +======= |
691 | + 'date': (70,100), |
692 | + 'datetime': (145,145), |
693 | + 'selection': (90,250), |
694 | + 'char': (100,False), |
695 | + 'one2many': (50,False), |
696 | + 'many2many': (50,False), |
697 | + 'boolean': (20,80), |
698 | + 'progressbar':(150,200), |
699 | + 'image': (20, 100) |
700 | +>>>>>>> MERGE-SOURCE |
701 | } |
702 | |
703 | if col._type not in twidth: |
704 | @@ -697,6 +709,10 @@ |
705 | return 0 |
706 | #gobject.type_register(CellRendererButton) |
707 | |
708 | + |
709 | +import image |
710 | +import many2one_image |
711 | + |
712 | CELLTYPES = { |
713 | 'char': Char, |
714 | 'many2one': M2O, |
715 | @@ -711,6 +727,8 @@ |
716 | 'boolean': Boolean, |
717 | 'progressbar': ProgressBar, |
718 | 'button': CellRendererButton, |
719 | + 'image': image.Image, |
720 | + 'many2one_image': many2one_image.many2one_image, |
721 | } |
722 | |
723 | # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: |
I don't get it. I don't see why you need to define a new widget ? Is keeping widget="image" not enough, and just handle the case where this is display in tree view? Beeing displayed into a x2many [1] is not a special case.
[1] this is a one2many, not a many2one. And only for this error, the merge is in "Needs Fixing"