Bugfix
[openerp-client.git] / bin / common / common.py
blobee2309ba3e39ca9f1e35c656d17695c2568032fc
1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
6 # $Id$
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
13 # Service Company
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
29 ##############################################################################
31 import gtk
32 from gtk import glade
33 import gobject
35 import gettext
37 import os
38 import sys
39 import common
40 import logging
41 from options import options
42 import service
44 import ConfigParser
46 import threading
47 import time
50 # Upgrade this number to force the client to ask the survey
52 SURVEY_VERSION = '3'
54 def _search_file(file, dir='path.share'):
55 tests = [
56 lambda x: os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), x),
57 lambda x: os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'pixmaps', x),
58 lambda x: os.path.join(options.options[dir],x),
60 for func in tests:
61 x = func(file)
62 if os.path.exists(x):
63 return x
64 return False
66 terp_path = _search_file
67 terp_path_pixmaps = lambda x: _search_file(x, 'path.pixmaps')
69 TINYERP_ICON = gtk.gdk.pixbuf_new_from_file(
70 terp_path_pixmaps('tinyerp-icon-32x32.png'))
72 def selection(title, values, alwaysask=False, parent=None):
73 if not values or len(values)==0:
74 return None
75 elif len(values)==1 and (not alwaysask):
76 key = values.keys()[0]
77 return (key, values[key])
79 xml = glade.XML(terp_path("terp.glade"), "win_selection", gettext.textdomain())
80 win = xml.get_widget('win_selection')
81 if not parent:
82 parent = service.LocalService('gui.main').window
83 win.set_icon(TINYERP_ICON)
84 win.set_transient_for(parent)
86 label = xml.get_widget('win_sel_title')
87 if title:
88 label.set_text(title)
90 list = xml.get_widget('win_sel_tree')
91 list.get_selection().set_mode('single')
92 cell = gtk.CellRendererText()
93 column = gtk.TreeViewColumn("Widget", cell, text=0)
94 list.append_column(column)
95 list.set_search_column(0)
96 model = gtk.ListStore(gobject.TYPE_STRING)
97 keys = values.keys()
98 keys.sort()
99 for val in keys:
100 model.append([val])
102 list.set_model(model)
103 list.connect('row-activated', lambda x,y,z: win.response(gtk.RESPONSE_OK) or True)
105 ok = False
106 while not ok:
107 response = win.run()
108 ok = True
109 res = None
110 if response == gtk.RESPONSE_OK:
111 sel = list.get_selection().get_selected()
112 if sel:
113 (model, iter) = sel
114 if iter:
115 res = model.get_value(iter, 0)
116 res = (res, values[res])
117 else:
118 ok = False
119 else:
120 ok = False
121 else:
122 res = None
123 parent.present()
124 win.destroy()
125 return res
127 class upload_data_thread(threading.Thread):
128 def __init__(self, email, data, type, supportid):
129 self.args = [('email',email),('type',type),('supportid',supportid),('data',data)]
130 super(upload_data_thread,self).__init__()
131 def run(self):
132 try:
133 import urllib
134 args = urllib.urlencode(self.args)
135 fp = urllib.urlopen('http://www.tinyerp.com/scripts/survey.php', args)
136 fp.read()
137 fp.close()
138 except:
139 pass
141 def upload_data(email, data, type='SURVEY', supportid=''):
142 a = upload_data_thread(email, data, type, supportid)
143 a.start()
144 return True
146 def terp_survey():
147 if options.options['survey.position']==SURVEY_VERSION:
148 return False
149 import pickle
150 widnames = ('country','role','industry','employee','hear','system','opensource')
151 winglade = glade.XML(common.terp_path("terp.glade"), "dia_survey", gettext.textdomain())
152 win = winglade.get_widget('dia_survey')
153 parent = service.LocalService('gui.main').window
154 win.set_transient_for(parent)
155 win.set_icon(TINYERP_ICON)
156 for widname in widnames:
157 wid = winglade.get_widget('combo_'+widname)
158 wid.child.set_text('(choose one)')
159 wid.child.set_editable(False)
160 res = win.run()
161 if res==gtk.RESPONSE_OK:
162 email = winglade.get_widget('entry_email').get_text()
163 company = winglade.get_widget('entry_company').get_text()
164 result = "\ncompany: "+str(company)
165 for widname in widnames:
166 wid = winglade.get_widget('combo_'+widname)
167 result += "\n"+widname+": "+wid.child.get_text()
168 result += "\nplan_use: "+str(winglade.get_widget('check_use').get_active())
169 result += "\nplan_sell: "+str(winglade.get_widget('check_sell').get_active())
171 buffer = winglade.get_widget('textview_comment').get_buffer()
172 iter_start = buffer.get_start_iter()
173 iter_end = buffer.get_end_iter()
174 result += "\nnote: "+buffer.get_text(iter_start,iter_end,False)
175 parent.present()
176 win.destroy()
177 upload_data(email, result, type='SURVEY '+str(SURVEY_VERSION))
178 options.options['survey.position']=SURVEY_VERSION
179 options.save()
180 common.message(_('Thank you for the feedback !\nYour comments have been sent to OpenERP.\nYou should now start by creating a new database or\nconnecting to an existing server through the "File" menu.'))
181 else:
182 parent.present()
183 win.destroy()
184 common.message(_('Thank you for testing OpenERP !\nYou should now start by creating a new database or\nconnecting to an existing server through the "File" menu.'))
185 return True
188 def file_selection(title, filename='', parent=None,
189 action=gtk.FILE_CHOOSER_ACTION_OPEN, preview=True, multi=False, filters=None):
190 if action == gtk.FILE_CHOOSER_ACTION_OPEN:
191 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
192 gtk.STOCK_OPEN,gtk.RESPONSE_OK)
193 else:
194 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
195 gtk.STOCK_SAVE, gtk.RESPONSE_OK)
196 win = gtk.FileChooserDialog(title, None, action, buttons)
197 if not parent:
198 parent = service.LocalService('gui.main').window
199 win.set_transient_for(parent)
200 win.set_icon(TINYERP_ICON)
201 win.set_current_folder(options.options['client.default_path'])
202 win.set_select_multiple(multi)
203 win.set_default_response(gtk.RESPONSE_OK)
204 if filters is not None:
205 for filter in filters:
206 win.add_filter(filter)
207 if filename:
208 win.set_current_name(filename)
210 def update_preview_cb(win, img):
211 filename = win.get_preview_filename()
212 try:
213 pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128)
214 img.set_from_pixbuf(pixbuf)
215 have_preview = True
216 except:
217 have_preview = False
218 win.set_preview_widget_active(have_preview)
219 return
221 if preview:
222 img_preview = gtk.Image()
223 win.set_preview_widget(img_preview)
224 win.connect('update-preview', update_preview_cb, img_preview)
226 button = win.run()
227 if button!=gtk.RESPONSE_OK:
228 win.destroy()
229 return False
230 if not multi:
231 filepath = win.get_filename()
232 if filepath:
233 filepath = filepath.decode('utf-8')
234 try:
235 options.options['client.default_path'] = os.path.dirname(filepath)
236 except:
237 pass
238 parent.present()
239 win.destroy()
240 return filepath
241 else:
242 filenames = win.get_filenames()
243 if filenames:
244 filenames = [x.decode('utf-8') for x in filenames]
245 try:
246 options.options['client.default_path'] = os.path.dirname(filenames[0])
247 except:
248 pass
249 parent.present()
250 win.destroy()
251 return filenames
253 def support(*args):
254 import pickle
255 wid_list = ['email_entry','id_entry','name_entry','phone_entry','company_entry','error_details','explanation_textview','remark_textview']
256 required_wid = ['email_entry', 'name_entry', 'company_name', 'id_entry']
257 support_id = options['support.support_id']
258 recipient = options['support.recipient']
260 sur = glade.XML(terp_path("terp.glade"), "dia_support",gettext.textdomain())
261 win = sur.get_widget('dia_support')
262 parent = service.LocalService('gui.main').window
263 win.set_transient_for(parent)
264 win.set_icon(TINYERP_ICON)
265 win.show_all()
266 sur.get_widget('id_entry').set_text(support_id)
268 response = win.run()
269 if response == gtk.RESPONSE_OK:
270 fromaddr = sur.get_widget('email_entry').get_text()
271 id_contract = sur.get_widget('id_entry').get_text()
272 name = sur.get_widget('name_entry').get_text()
273 phone = sur.get_widget('phone_entry').get_text()
274 company = sur.get_widget('company_entry').get_text()
276 urgency = sur.get_widget('urgency_combo').get_active_text()
278 buffer = sur.get_widget('explanation_textview').get_buffer()
279 explanation = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
281 buffer = sur.get_widget('remark_textview').get_buffer()
282 remarks = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
284 content = name +"(%s, %s, %s)"%(id_contract, company, phone) +" has reported the following bug:\n"+ explanation + "\nremarks:\n" + remarks
286 if upload_data(fromaddr, content, 'support', id_contract):
287 common.message(_('Support request sent !'))
289 parent.present()
290 win.destroy()
291 return True
293 def error(title, message, details='', parent=None):
294 log = logging.getLogger('common.message')
295 log.error('MSG %s: %s' % (str(message),details))
297 wid_list = ['email_entry','id_entry','name_entry','phone_entry','company_entry','error_details','explanation_textview','remarks_textview']
298 required_wid = ['email_entry', 'name_entry', 'company_name', 'id_entry']
299 colors = {'invalid':'#ffdddd', 'readonly':'grey', 'required':'#ddddff', 'normal':'white'}
301 support_id = options['support.support_id']
302 recipient = options['support.recipient']
304 sur = glade.XML(terp_path("terp.glade"), "win_error",gettext.textdomain())
305 win = sur.get_widget('win_error')
306 if not parent:
307 parent=service.LocalService('gui.main').window
308 win.set_transient_for(parent)
309 win.set_icon(TINYERP_ICON)
310 sur.get_widget('error_title').set_text(str(title))
311 sur.get_widget('error_info').set_text(str(message))
312 buf = gtk.TextBuffer()
313 buf.set_text(unicode(details,'latin1').encode('utf-8'))
314 sur.get_widget('error_details').set_buffer(buf)
316 sur.get_widget('id_entry').set_text(support_id)
318 def send(widget):
319 import pickle
321 fromaddr = sur.get_widget('email_entry').get_text()
322 id_contract = sur.get_widget('id_entry').get_text()
323 name = sur.get_widget('name_entry').get_text()
324 phone = sur.get_widget('phone_entry').get_text()
325 company = sur.get_widget('company_entry').get_text()
327 urgency = sur.get_widget('urgency_combo').get_active_text()
329 buffer = sur.get_widget('error_details').get_buffer()
330 traceback = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
332 buffer = sur.get_widget('explanation_textview').get_buffer()
333 explanation = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
335 buffer = sur.get_widget('remarks_textview').get_buffer()
336 remarks = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
338 content = "(%s, %s, %s)"%(id_contract, company, phone) +" has reported the following bug:\n"+ explanation + "\nremarks: " + remarks + "\nThe traceback is:\n" + traceback
340 if upload_data(fromaddr, content, 'error', id_contract):
341 common.message(_('Support request sent !'))
342 return
344 sur.signal_connect('on_button_send_clicked', send)
345 sur.signal_connect('on_closebutton_clicked', lambda x : win.destroy())
347 response = win.run()
348 parent.present()
349 win.destroy()
350 return True
352 def message(msg, type=gtk.MESSAGE_INFO, parent=None):
353 if not parent:
354 parent=service.LocalService('gui.main').window
355 dialog = gtk.MessageDialog(parent,
356 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
357 type, gtk.BUTTONS_OK,
358 msg)
359 dialog.set_icon(TINYERP_ICON)
360 dialog.run()
361 parent.present()
362 dialog.destroy()
363 return True
365 def to_xml(s):
366 return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
368 def message_box(title, msg, parent=None):
369 dia = glade.XML(terp_path("terp.glade"), "dia_message_box",gettext.textdomain())
370 win = dia.get_widget('dia_message_box')
371 l = dia.get_widget('msg_title')
372 l.set_text(title)
374 buffer = dia.get_widget('msg_tv').get_buffer()
375 iter_start = buffer.get_start_iter()
376 buffer.insert(iter_start, msg)
378 if not parent:
379 parent=service.LocalService('gui.main').window
380 win.set_transient_for(parent)
381 win.set_icon(TINYERP_ICON)
383 response = win.run()
384 parent.present()
385 win.destroy()
386 return True
389 def warning(msg, title='', parent=None):
390 if not parent:
391 parent=service.LocalService('gui.main').window
392 dialog = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT,
393 gtk.MESSAGE_WARNING, gtk.BUTTONS_OK)
394 dialog.set_icon(TINYERP_ICON)
395 dialog.set_markup('<b>%s</b>\n\n%s' % (to_xml(title),to_xml(msg)))
396 dialog.show_all()
397 dialog.run()
398 parent.present()
399 dialog.destroy()
400 return True
402 def sur(msg, parent=None):
403 if not parent:
404 parent=service.LocalService('gui.main').window
405 sur = glade.XML(terp_path("terp.glade"), "win_sur",gettext.textdomain())
406 win = sur.get_widget('win_sur')
407 win.set_transient_for(parent)
408 win.show_all()
409 l = sur.get_widget('lab_question')
410 l.set_text(msg)
412 if not parent:
413 parent=service.LocalService('gui.main').window
414 win.set_transient_for(parent)
415 win.set_icon(TINYERP_ICON)
417 response = win.run()
418 parent.present()
419 win.destroy()
420 return response == gtk.RESPONSE_OK
422 def sur_3b(msg, parent=None):
423 sur = glade.XML(terp_path("terp.glade"), "win_quest_3b",gettext.textdomain())
424 win = sur.get_widget('win_quest_3b')
425 l = sur.get_widget('label')
426 l.set_text(msg)
428 if not parent:
429 parent=service.LocalService('gui.main').window
430 win.set_transient_for(parent)
431 win.set_icon(TINYERP_ICON)
433 response = win.run()
434 parent.present()
435 win.destroy()
436 if response == gtk.RESPONSE_YES:
437 return 'ok'
438 elif response == gtk.RESPONSE_NO:
439 return 'ko'
440 elif response == gtk.RESPONSE_CANCEL:
441 return 'cancel'
442 else:
443 return 'cancel'
445 def theme_set():
446 theme = options['client.theme']
447 if theme and (theme <> 'none'):
448 fname = os.path.join("themes", theme, "gtkrc")
449 if not os.path.isfile(fname):
450 common.warning('File not found: '+fname+'\nSet theme to none in ~/.terprc', 'Error setting theme')
451 return False
452 gtk.rc_parse("themes/"+theme+"/gtkrc")
453 return True
455 def ask(question, parent=None):
456 dia = glade.XML(terp_path('terp.glade'), 'win_quest', gettext.textdomain())
457 win = dia.get_widget('win_quest')
458 label = dia.get_widget('label1')
459 label.set_text(question)
460 entry = dia.get_widget('entry')
462 if not parent:
463 parent=service.LocalService('gui.main').window
464 win.set_transient_for(parent)
465 win.set_icon(TINYERP_ICON)
467 response = win.run()
468 parent.present()
469 win.destroy()
470 if response == gtk.RESPONSE_CANCEL:
471 return None
472 else:
473 return entry.get_text()
475 def concurrency(resource, id, context, parent=None):
476 dia = glade.XML(common.terp_path("terp.glade"),'dialog_concurrency_exception',gettext.textdomain())
477 win = dia.get_widget('dialog_concurrency_exception')
479 if not parent:
480 parent=service.LocalService('gui.main').window
481 win.set_transient_for(parent)
482 win.set_icon(TINYERP_ICON)
484 res= win.run()
485 parent.present()
486 win.destroy()
488 if res == gtk.RESPONSE_OK:
489 return True
490 if res == gtk.RESPONSE_APPLY:
491 obj = service.LocalService('gui.window')
492 obj.create(False, resource, id, [], 'form', None, context,'form,tree')
493 return False
495 def open_file(value, parent):
496 filetype = {}
497 if options['client.filetype']:
498 if isinstance(options['client.filetype'], str):
499 filetype = eval(options['client.filetype'])
500 else:
501 filetype = options['client.filetype']
502 root, ext = os.path.splitext(value)
503 cmd = False
504 if ext[1:] in filetype:
505 cmd = filetype[ext[1:]] % (value)
506 if not cmd:
507 cmd = file_selection(_('Open with...'),
508 parent=parent)
509 if cmd:
510 cmd = cmd + ' %s'
511 filetype[ext[1:]] = cmd
512 options['client.filetype'] = filetype
513 options.save()
514 cmd = cmd % (value)
515 if cmd:
516 pid = os.fork()
517 if not pid:
518 pid = os.fork()
519 if not pid:
520 prog, args = cmd.split(' ', 1)
521 args = [os.path.basename(prog)] + args.split(' ')
522 try:
523 os.execvp(prog, args)
524 except:
525 pass
526 time.sleep(0.1)
527 sys.exit(0)
528 os.waitpid(pid, 0)
531 # Color set
533 colors = {
534 'invalid':'#ff6969',
535 'readonly':'#eeebe7',
536 'required':'#d2d2ff',
537 'normal':'white'
542 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: