Merge lp:~openerp-commiter/openobject-client/client-image-widget into lp:openobject-client

Proposed by Nicolas DS
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
Reviewer Review Type Date Requested Status
Christophe Simonis (OpenERP) Needs Fixing
Stephane Wirtel (OpenERP) Pending
Review via email: mp+23325@code.launchpad.net

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://zyphos.be/openobject/img_widget_tree.jpg (Tree.image)
* http://zyphos.be/openobject/M2O_img_widget_tree.jpg (Tree.many2one_image)
* http://zyphos.be/openobject/M2O_img_widget_form.jpg (Form.many2one_image)

See branch description for more details.

To post a comment you must log in.
Revision history for this message
Christophe Simonis (OpenERP) (kangol) wrote :

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"

review: Needs Fixing
Revision history for this message
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/view/common_gtk.py @line 137).

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/bin/widget/view/form_gtk/parser.py @line 416
widget_act = widgets_type[type][0](self.window, self.parent, model, fields[name])

and "type" is not alway the original field type, indeed:
@line 401
type = attrs.get('widget', fields[name]['type'])

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://en.wikipedia.org/wiki/Push_technology

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://code.launchpad.net/~openerp-commiter/openobject-client/client-image-widget

Thank you for your review.

Revision history for this message
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://sprunge.us/MhFQ?python
http://sprunge.us/EfKA?xml

review: Needs Fixing
Revision history for this message
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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bin/widget/model/field.py'
--- bin/widget/model/field.py 2010-10-05 07:05:05 +0000
+++ bin/widget/model/field.py 2010-10-06 13:46:15 +0000
@@ -496,6 +496,7 @@
496 'integer' : IntegerField,496 'integer' : IntegerField,
497 'float' : FloatField,497 'float' : FloatField,
498 'many2one' : M2OField,498 'many2one' : M2OField,
499 'many2one_image' : M2OField,
499 'many2many' : M2MField,500 'many2many' : M2MField,
500 'one2many' : O2MField,501 'one2many' : O2MField,
501 'reference' : ReferenceField,502 'reference' : ReferenceField,
502503
=== added file 'bin/widget/view/common_gtk.py'
--- bin/widget/view/common_gtk.py 1970-01-01 00:00:00 +0000
+++ bin/widget/view/common_gtk.py 2010-10-06 13:46:15 +0000
@@ -0,0 +1,193 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import collections
23import base64
24import time
25import gtk
26import rpc
27import common
28
29NOIMAGE = file(common.terp_path_pixmaps("noimage.png"), 'rb').read()
30
31def make_pixbuf(data):
32 ''' Generate a pixbuf (gnome image) from a image file data
33 Warning this processing is slow and should be cached'''
34 if not data:
35 data = NOIMAGE
36 else:
37 data = base64.decodestring(data)
38 pixbuf = None
39 loader = gtk.gdk.PixbufLoader()
40 try:
41 loader.write(data, len(data))
42 pixbuf = loader.get_pixbuf()
43 except:
44 pass
45 if not pixbuf:
46 loader.close()
47 loader = gtk.gdk.PixbufLoader('png')
48 loader.write(NOIMAGE, len(NOIMAGE))
49 pixbuf = loader.get_pixbuf()
50
51 loader.close()
52 return pixbuf
53
54def thumbnail_pixbuf(pixbuf, to_width, to_height):
55 ''' Make a thumbnail of the image according to dimensions provided
56 Warning this processing is slow and should be cached'''
57 img_height = pixbuf.get_height()
58 if img_height > to_height:
59 height = to_height
60 else:
61 height = img_height
62
63 img_width = pixbuf.get_width()
64 if img_width > to_width:
65 width = to_width
66 else:
67 width = img_width
68
69 if (img_width / width) < (img_height / height):
70 width = float(img_width) / float(img_height) * float(height)
71 else:
72 height = float(img_height) / float(img_width) * float(width)
73
74 return pixbuf.scale_simple(int(width), int(height), gtk.gdk.INTERP_BILINEAR)
75
76class CacheDict(collections.MutableMapping):
77 ''' Act like a dict but with timeout
78 @param timeout: timeout in seconds
79 '''
80 def __init__(self, timeout=30):
81 self.cache = {}
82 self.timeout = int(timeout)
83 self.last_check = time.time()
84
85 def __setitem__(self, key, value):
86 if not self.timeout:
87 return
88 self.check_timeout()
89 self.cache[key] = (value, time.time())
90
91 def __getitem__(self, key):
92 if key not in self.cache:
93 raise KeyError
94 return self.cache[key][0]
95
96 def __delitem__(self, key):
97 if key not in self.cache:
98 raise KeyError
99 del self.cache[key]
100
101 def keys(self):
102 return self.cache.keys()
103
104 def __iter__(self):
105 for key in self.cache:
106 yield key
107
108 def __len__(self):
109 return len(self.cache)
110
111 def check_timeout(self):
112 ''' Remove expired cache '''
113 now = time.time()
114 if now < self.last_check + self.timeout / 10:
115 return
116 ct = now - self.timeout # Critical time
117 key2del = [k for k in self.cache if self.cache[k][1] < ct]
118 for key in key2del:
119 self.cache.pop(key)
120
121CACHE_TIME_OUT = 120 # In second
122pixbuf_cache = CacheDict(CACHE_TIME_OUT)
123
124
125def get_pixbuf(model, field, ids):
126 ''' Retrieve the data and convert to pixbuf if possible
127 @param model: The model that the image come from. (Ie: res.images)
128 @param field: The column of the model. (Ie: img)
129 @param ids: Id of records row in database (int or list of int)
130 @return pixbuf or dict of pixbuf
131 '''
132 result = {}
133 is_int = isinstance(ids, int)
134 if is_int:
135 ids = [ids]
136 cache_ids = dict([(id, "%s[%s][%d]" % (model, field, id)) for id in ids])
137 ids2query = []
138 for id in ids:
139 if cache_ids[id] in pixbuf_cache:
140 result[id] = pixbuf_cache[cache_ids[id]]
141 else:
142 ids2query.append(id)
143
144 if ids2query:
145 datas = rpc.session.rpc_exec_auth('/object', 'execute', model, 'read',
146 ids2query, ['id', field])
147 for res in datas:
148 id = res['id']
149 pixbuf = make_pixbuf(res[field])
150 pixbuf_cache[cache_ids[id]] = pixbuf
151 result[id] = pixbuf
152
153 # Complete result table
154 for id in ids:
155 if id not in result:
156 result[id] = None
157
158 if is_int:
159 return result[0]
160 return result
161
162def get_thumbnail(model, field, ids, to_width, to_height):
163 ''' Resize Image to fit width, height cache result to be used later.
164 @param model: The model that the image come from. (Ie: res.images)
165 @param field: The column of the resource. (Ie: img)
166 @param ids: Id of records row in database. (int or list of int)
167 @param to_width: Maximum width in pixel.
168 @param to_height: Maximum height in pixel.
169 @return pixbuf or dict of pixbuf
170 '''
171 if not ids:
172 return None
173 result = {}
174 is_int = isinstance(ids, int)
175 if is_int:
176 ids = [ids]
177
178 ids2query = []
179 cache_ids = dict([(id, "%s[%s][%d]-%dx%d" % (model, field, id, to_width, to_height)) for id in ids])
180 for id in ids:
181 if cache_ids[id] in pixbuf_cache:
182 result[id] = pixbuf_cache[cache_ids[id]]
183 else:
184 ids2query.append(id)
185 if ids2query:
186 pixbufs = get_pixbuf(model, field, ids2query)
187 for id, pixbuf in pixbufs.iteritems():
188 thumbnail = thumbnail_pixbuf(pixbuf, to_width, to_height)
189 pixbuf_cache[cache_ids[id]] = thumbnail
190 result[id] = thumbnail
191 if is_int:
192 return result[ids[0]]
193 return result
0194
=== modified file 'bin/widget/view/form_gtk/image.py'
--- bin/widget/view/form_gtk/image.py 2010-09-08 06:30:55 +0000
+++ bin/widget/view/form_gtk/image.py 2010-10-06 13:46:15 +0000
@@ -31,9 +31,13 @@
31import interface31import interface
32import tempfile32import tempfile
33import urllib33import urllib
34<<<<<<< TREE
3435
35NOIMAGE = file(common.terp_path_pixmaps("noimage.png"), 'rb').read()36NOIMAGE = file(common.terp_path_pixmaps("noimage.png"), 'rb').read()
36REQUIRED_IMG = file(common.terp_path_pixmaps("image_required.png"), 'rb').read()37REQUIRED_IMG = file(common.terp_path_pixmaps("image_required.png"), 'rb').read()
38=======
39from widget.view import common_gtk
40>>>>>>> MERGE-SOURCE
3741
3842
39class image_wid(interface.widget_interface):43class image_wid(interface.widget_interface):
@@ -106,9 +110,16 @@
106110
107 filter_image = gtk.FileFilter()111 filter_image = gtk.FileFilter()
108 filter_image.set_name(_('Images'))112 filter_image.set_name(_('Images'))
109 for mime in ("image/png", "image/jpeg", "image/gif"):113 supported_mime = ('image/bmp', 'image/gif', 'image/jpeg', 'image/tiff',
114 'image/x-cmu-raster', 'image/x-icon',
115 'image/x-portable-anymap', 'image/x-xbitmap',
116 'image/x-xpixmap', 'image/png')
117 supported_pat = ('*.ani', '*.bmp', '*.gif', '*.ico', '*.jpe', '*.jpeg',
118 '*.jpg', '*.pcx', '*.png', '*.pnm', '*.ras', '*.tga',
119 '*.tif', '*.tiff', '*.xbm', '*.xpm')
120 for mime in supported_mime:
110 filter_image.add_mime_type(mime)121 filter_image.add_mime_type(mime)
111 for pat in ("*.png", "*.jpg", "*.gif", "*.tif", "*.xpm"):122 for pat in supported_pat:
112 filter_image.add_pattern(pat)123 filter_image.add_pattern(pat)
113124
114 filename = common.file_selection(_('Open...'), parent=self._window, preview=True,125 filename = common.file_selection(_('Open...'), parent=self._window, preview=True,
@@ -164,6 +175,7 @@
164 self.update_img()175 self.update_img()
165176
166 def update_img(self):177 def update_img(self):
178<<<<<<< TREE
167 if not self._value:179 if not self._value:
168 if self._set_required_img:180 if self._set_required_img:
169 data = REQUIRED_IMG181 data = REQUIRED_IMG
@@ -207,6 +219,10 @@
207 height = float(img_height) / float(img_width) * float(width)219 height = float(img_height) / float(img_width) * float(width)
208220
209 scaled = pixbuf.scale_simple(int(width), int(height), gtk.gdk.INTERP_BILINEAR)221 scaled = pixbuf.scale_simple(int(width), int(height), gtk.gdk.INTERP_BILINEAR)
222=======
223 pixbuf = common_gtk.make_pixbuf(self._value)
224 scaled = common_gtk.thumbnail_pixbuf(pixbuf, self.width, self.height)
225>>>>>>> MERGE-SOURCE
210 self.image.set_from_pixbuf(scaled)226 self.image.set_from_pixbuf(scaled)
211227
212 def display(self, model, model_field):228 def display(self, model, model_field):
213229
=== added file 'bin/widget/view/form_gtk/many2one_image.py'
--- bin/widget/view/form_gtk/many2one_image.py 1970-01-01 00:00:00 +0000
+++ bin/widget/view/form_gtk/many2one_image.py 2010-10-06 13:46:15 +0000
@@ -0,0 +1,202 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22import gobject
23import gtk
24
25import interface
26import common
27
28import widget
29from widget.screen import Screen
30
31from modules.gui.window.win_search import win_search
32import rpc
33
34import service
35
36import many2one
37from widget.view import common_gtk
38
39class many2one_image(many2one.many2one):
40 ''' Binary many2one image form widget
41 Ie:
42 <field name="img_id" widget="many2one_image" fieldname="img"/>
43
44 img_field (optional): field of the many2one relation to show in widget
45 Otherwise it will take the first binary field.
46 '''
47 def __init__(self, window, parent, model, attrs={}):
48 interface.widget_interface.__init__(self, window, parent, model, attrs)
49
50 self.widget = gtk.VBox(spacing=3)
51
52 self.image = gtk.Image()
53 self.height = int(attrs.get('img_height', 100))
54 self.width = int(attrs.get('img_width', 300))
55 self.model_field = False
56
57 self.widget.pack_start(self.image, expand=True, fill=True)
58
59 alignment = gtk.Alignment(xalign=0.5, yalign=0.5)
60 hbox = gtk.HBox()
61
62 self.but_open = many2one.Button('gtk-open', self.sig_edit,
63 _('Open this resource'))
64 hbox.pack_start(self.but_open, padding=2, expand=False, fill=False)
65
66 self.but_find = many2one.Button('gtk-find', self.sig_find,
67 _('Search a resource'))
68 hbox.pack_start(self.but_find, padding=2, expand=False, fill=False)
69
70 self.but_remove = many2one.Button('gtk-clear', self.sig_remove,
71 _('Clear'))
72 hbox.pack_start(self.but_remove, padding=2, expand=False, fill=False)
73
74 alignment.add(hbox)
75 self.widget.pack_start(alignment, expand=False, fill=False)
76
77 self.value_on_field = ''
78 self.ok = True
79 self._readonly = False
80 self.model_type = attrs['relation']
81 self._menu_loaded = False
82 self._menu_entries.append((None, None, None))
83 self._menu_entries.append((_('Action'), lambda x: self.click_and_action('client_action_multi'), 0))
84 self._menu_entries.append((_('Report'), lambda x: self.click_and_action('client_print_multi'), 0))
85
86 def _readonly_set(self, value):
87 self._readonly = value
88 #self.but_new.set_sensitive(not value)
89
90 def _color_widget(self):
91 return self.image
92
93 def sig_find(self, widget, event=None, leave=True):
94 self.ok = False
95 if not self._readonly:
96 domain = self.model_field.domain_get(self._view.model)
97 context = self.model_field.context_get(self._view.model)
98 #self.wid_text.grab_focus()
99 name_search = ''
100 relation = self.attrs['relation']
101 ids = rpc.session.rpc_exec_auth('/object', 'execute', relation,
102 'name_search', name_search, domain,
103 'ilike', context)
104 if (len(ids) == 1) and leave:
105 self.model_field.set_client(self._view.model, ids[0],
106 force_change=True)
107 self.display(self._view.model, self.model_field)
108 self.ok = True
109 return True
110
111 win = win_search(self.attrs['relation'], sel_multi=False,
112 ids=map(lambda x: x[0], ids), context=context,
113 domain=domain, parent=self._window)
114 ids = win.go()
115 if ids:
116 name = rpc.session.rpc_exec_auth('/object', 'execute', relation,
117 'name_get', [ids[0]],
118 rpc.session.context)[0]
119 self.model_field.set_client(self._view.model, name,
120 force_change=True)
121 self.display(self._view.model, self.model_field)
122 self.ok = True
123
124 def sig_edit(self, widget, event=None, leave=False):
125 self.ok = False
126 if not leave:
127 domain = self.model_field.domain_get(self._view.model)
128 context = self.model_field.context_get(self._view.model)
129 m2o_value = self.model_field.get(self._view.model, False)
130 relation_record_id = self.model_field.get(self._view.model)
131 dia = many2one.dialog(self.attrs['relation'],
132 relation_record_id,
133 attrs=self.attrs, window=self._window,
134 domain=domain, context=context)
135 ok, value = dia.run()
136 if ok:
137 self.model_field.set_client(self._view.model, value,
138 force_change=True)
139 dia.destroy()
140 self.display(self._view.model, self.model_field)
141 self.ok = True
142
143 def sig_new(self, *args):
144 domain = self.model_field.domain_get(self._view.model)
145 context = self.model_field.context_get(self._view.model)
146 dia = many2one.dialog(self.attrs['relation'], attrs=self.attrs,
147 window=self._window, domain=domain,
148 context=context)
149 ok, value = dia.run()
150 if ok:
151 self.model_field.set_client(self._view.model, value)
152 self.display(self._view.model, self.model_field)
153 dia.destroy()
154
155 def sig_remove(self, widget=None):
156 self.model_field.set_client(self._view.model, False)
157 self.display(self._view.model, False)
158
159 def sig_key_press(self, widget, event, *args):
160 if event.keyval == gtk.keysyms.F1:
161 self.sig_new(widget, event)
162 elif event.keyval == gtk.keysyms.F2:
163 self.sig_activate(widget, event)
164 elif event.keyval == gtk.keysyms.F3:
165 self.sig_edit(widget, event)
166 elif event.keyval == gtk.keysyms.Tab:
167 self.sig_activate(widget, event, leave=True)
168 return True
169 return False
170
171 def display(self, model, model_field):
172 if not model_field:
173 self.ok = False
174 return False
175 self.model_field = model_field
176 interface.widget_interface.display(self, model, model_field)
177 self.ok = False
178 # retrieve image data
179 m2o_value = model.value.get(model_field.name, False)
180 relation_record_id = m2o_value and m2o_value[0]
181 relation = model_field.attrs['relation']
182 img_field_name = model_field.attrs.get('fieldname', False)
183 if not img_field_name:
184 # if not img_field is set get the first binary field
185 fields = rpc.session.rpc_exec_auth('/object', 'execute', relation,
186 'fields_get')
187 for field_name in fields:
188 if fields[field_name]['type'] == 'binary':
189 img_field_name = field_name
190 break
191
192 if not img_field_name:
193 return
194
195 thumbnail = common_gtk.get_thumbnail(relation, img_field_name,
196 relation_record_id, self.width,
197 self.height)
198 self.image.set_from_pixbuf(thumbnail)
199 self.ok = True
200
201# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
202
0203
=== modified file 'bin/widget/view/form_gtk/parser.py'
--- bin/widget/view/form_gtk/parser.py 2010-09-23 09:18:56 +0000
+++ bin/widget/view/form_gtk/parser.py 2010-10-06 13:46:15 +0000
@@ -462,11 +462,9 @@
462 continue462 continue
463463
464 fields[name]['name'] = name464 fields[name]['name'] = name
465 if 'saves' in attrs:465 for attr in ['saves', 'filename', 'fieldname']:
466 fields[name]['saves'] = attrs['saves']466 if attr in attrs:
467467 fields[name][attr] = attrs[attr]
468 if 'filename' in attrs:
469 fields[name]['filename'] = attrs['filename']
470468
471 if fields[name]['type'] == 'many2one' and 'search_mode' in attrs:469 if fields[name]['type'] == 'many2one' and 'search_mode' in attrs:
472 fields[name]['search_mode'] = attrs['search_mode']470 fields[name]['search_mode'] = attrs['search_mode']
@@ -907,6 +905,7 @@
907#import one2many905#import one2many
908import many2many906import many2many
909import many2one907import many2one
908import many2one_image
910import selection909import selection
911import one2many_list910import one2many_list
912import picture911import picture
@@ -937,6 +936,7 @@
937 'one2many_list': (one2many_list.one2many_list, 1, True, True),936 'one2many_list': (one2many_list.one2many_list, 1, True, True),
938 'many2many': (many2many.many2many, 1, True, True),937 'many2many': (many2many.many2many, 1, True, True),
939 'many2one': (many2one.many2one, 1, False, False),938 'many2one': (many2one.many2one, 1, False, False),
939 'many2one_image': (many2one_image.many2one_image, 1, False, False),
940 'email' : (url.email, 1, False, False),940 'email' : (url.email, 1, False, False),
941 'url' : (url.url, 1, False, False),941 'url' : (url.url, 1, False, False),
942 'callto' : (url.callto, 1, False, False),942 'callto' : (url.callto, 1, False, False),
943943
=== added file 'bin/widget/view/tree_gtk/image.py'
--- bin/widget/view/tree_gtk/image.py 1970-01-01 00:00:00 +0000
+++ bin/widget/view/tree_gtk/image.py 2010-10-06 13:46:15 +0000
@@ -0,0 +1,64 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22
23import gtk
24
25import service
26import common
27from widget.view import common_gtk
28
29class Image(object):
30 def __init__(self, field_name, treeview=None, attrs=None, window=None):
31 self.field_name = field_name
32 self.attrs = attrs or {}
33 self.renderer = gtk.CellRendererPixbuf()
34 self.editable = attrs.get('editable',False)
35 self.height = int(attrs.get('img_height', 33))
36 self.width = int(attrs.get('img_width', 100))
37 self.treeview = treeview
38 if not window:
39 window = service.LocalService('gui.main').window
40 self.window = window
41 self.pixbuf_cache = {} # warning caching consequence is memory eated.
42
43 def setter(self, column, cell, store, iter):
44 model_record = store.get_value(iter, 0)
45 resource = model_record.resource
46 resource_id = model_record.id
47 field_name = column.name
48 thumbnail = common_gtk.get_thumbnail(resource, field_name,
49 resource_id, self.width,
50 self.height)
51
52 cell.set_property('pixbuf', thumbnail)
53
54 def open_remote(self, model, create, changed=False, text=None):
55 raise NotImplementedError
56
57 def _get_data(self, model_record):
58 return model_record[self.field_name].get_client(model_record)
59
60 def get_textual_value(self, model):
61 return ''
62
63 def value_from_text(self, model, text):
64 return text
0\ No newline at end of file65\ No newline at end of file
166
=== added file 'bin/widget/view/tree_gtk/many2one_image.py'
--- bin/widget/view/tree_gtk/many2one_image.py 1970-01-01 00:00:00 +0000
+++ bin/widget/view/tree_gtk/many2one_image.py 2010-10-06 13:46:15 +0000
@@ -0,0 +1,81 @@
1# -*- coding: utf-8 -*-
2##############################################################################
3#
4# OpenERP, Open Source Management Solution
5# Copyright (C) 2004-2010 Tiny SPRL (<http://tiny.be>).
6#
7# This program is free software: you can redistribute it and/or modify
8# it under the terms of the GNU Affero General Public License as
9# published by the Free Software Foundation, either version 3 of the
10# License, or (at your option) any later version.
11#
12# This program is distributed in the hope that it will be useful,
13# but WITHOUT ANY WARRANTY; without even the implied warranty of
14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15# GNU Affero General Public License for more details.
16#
17# You should have received a copy of the GNU Affero General Public License
18# along with this program. If not, see <http://www.gnu.org/licenses/>.
19#
20##############################################################################
21
22
23import gtk
24
25import service
26import common
27from widget.view import common_gtk
28import rpc
29
30class many2one_image(object):
31 ''' Binary many2one image tree widget
32 Ie:
33 <field name="img_id" widget="many2one_image" fieldname="img"/>
34
35 fieldname (optional): field of the many2one relation to show in widget
36 Otherwise it will take the first binary field.
37 '''
38 def __init__(self, field_name, treeview=None, attrs=None, window=None):
39 self.field_name = field_name
40 self.attrs = attrs or {}
41 self.renderer = gtk.CellRendererPixbuf()
42 self.editable = attrs.get('editable',False)
43 self.height = int(attrs.get('img_height', 33))
44 self.width = int(attrs.get('img_width', 100))
45 self.treeview = treeview
46 if not window:
47 window = service.LocalService('gui.main').window
48 self.window = window
49
50 self.img_field_name = self.attrs.get('fieldname', False)
51 if not self.img_field_name:
52 # if not img_field is set get the first binary field
53 fields = rpc.session.rpc_exec_auth('/object', 'execute', self.attrs['relation'], 'fields_get')
54 for field_name in fields:
55 if fields[field_name]['type'] == 'binary':
56 self.img_field_name = field_name
57 break
58
59 def setter(self, column, cell, store, iter):
60 if not self.img_field_name:
61 return
62
63 model_record = store.get_value(iter, 0)
64 m2o_value = model_record.value[self.field_name]
65 relation_record_id = m2o_value and m2o_value[0]
66 relation = self.attrs['relation']
67
68 thumbnail = common_gtk.get_thumbnail(relation, self.img_field_name,
69 relation_record_id, self.width,
70 self.height)
71
72 cell.set_property('pixbuf', thumbnail)
73
74 def open_remote(self, model, create, changed=False, text=None):
75 raise NotImplementedError
76
77 def get_textual_value(self, model):
78 return ''
79
80 def value_from_text(self, model, text):
81 return text
0\ No newline at end of file82\ No newline at end of file
183
=== modified file 'bin/widget/view/tree_gtk/parser.py'
--- bin/widget/view/tree_gtk/parser.py 2010-09-30 06:52:47 +0000
+++ bin/widget/view/tree_gtk/parser.py 2010-10-06 13:46:15 +0000
@@ -131,7 +131,7 @@
131 if node.tag == 'field':131 if node.tag == 'field':
132 handler_id = False132 handler_id = False
133 fname = str(node_attrs['name'])133 fname = str(node_attrs['name'])
134 if fields[fname]['type'] in ('image', 'binary'):134 if fields[fname]['type'] in ('binary', ):
135 continue # not showed types135 continue # not showed types
136 if fname == 'sequence':136 if fname == 'sequence':
137 treeview.sequence = True137 treeview.sequence = True
@@ -178,6 +178,7 @@
178 'integer': (60, 170),178 'integer': (60, 170),
179 'float': (80, 300),179 'float': (80, 300),
180 'float_time': (80,150),180 'float_time': (80,150),
181<<<<<<< TREE
181 'date': (70, False),182 'date': (70, False),
182 'datetime': (145, 145),183 'datetime': (145, 145),
183 'selection': (90, 250),184 'selection': (90, 250),
@@ -186,6 +187,17 @@
186 'many2many': (50, False),187 'many2many': (50, False),
187 'boolean': (20, 80),188 'boolean': (20, 80),
188 'progressbar':(150, 200)189 'progressbar':(150, 200)
190=======
191 'date': (70,100),
192 'datetime': (145,145),
193 'selection': (90,250),
194 'char': (100,False),
195 'one2many': (50,False),
196 'many2many': (50,False),
197 'boolean': (20,80),
198 'progressbar':(150,200),
199 'image': (20, 100)
200>>>>>>> MERGE-SOURCE
189 }201 }
190202
191 if col._type not in twidth:203 if col._type not in twidth:
@@ -697,6 +709,10 @@
697 return 0709 return 0
698#gobject.type_register(CellRendererButton)710#gobject.type_register(CellRendererButton)
699711
712
713import image
714import many2one_image
715
700CELLTYPES = {716CELLTYPES = {
701 'char': Char,717 'char': Char,
702 'many2one': M2O,718 'many2one': M2O,
@@ -711,6 +727,8 @@
711 'boolean': Boolean,727 'boolean': Boolean,
712 'progressbar': ProgressBar,728 'progressbar': ProgressBar,
713 'button': CellRendererButton,729 'button': CellRendererButton,
730 'image': image.Image,
731 'many2one_image': many2one_image.many2one_image,
714}732}
715733
716# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:734# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: