add target new functionality with view_id
[openerp-client.git] / bin / common / common.py
blob5014d4395c0369df701dd42d1155cface476308d
1 ##############################################################################
3 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
5 # $Id$
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
12 # Service Company
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 ##############################################################################
30 import gtk
31 from gtk import glade
32 import gobject
34 import gettext
36 import os
37 import sys
38 import common
39 import logging
40 from options import options
41 import service
43 import ConfigParser
45 import threading
46 import time
49 # Upgrade this number to force the client to ask the survey
51 SURVEY_VERSION = '3'
53 def _search_file(file, dir='path.share'):
54 tests = [
55 lambda x: os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), x),
56 lambda x: os.path.join(os.getcwd(), os.path.dirname(sys.argv[0]), 'pixmaps', x),
57 lambda x: os.path.join(options.options[dir],x),
59 for func in tests:
60 x = func(file)
61 if os.path.exists(x):
62 return x
63 return False
65 terp_path = _search_file
66 terp_path_pixmaps = lambda x: _search_file(x, 'path.pixmaps')
68 TINYERP_ICON = gtk.gdk.pixbuf_new_from_file(
69 terp_path_pixmaps('tinyerp-icon-32x32.png'))
71 def selection(title, values, alwaysask=False, parent=None):
72 if not values or len(values)==0:
73 return None
74 elif len(values)==1 and (not alwaysask):
75 key = values.keys()[0]
76 return (key, values[key])
78 xml = glade.XML(terp_path("terp.glade"), "win_selection", gettext.textdomain())
79 win = xml.get_widget('win_selection')
80 if not parent:
81 parent = service.LocalService('gui.main').window
82 win.set_icon(TINYERP_ICON)
83 win.set_transient_for(parent)
85 label = xml.get_widget('win_sel_title')
86 if title:
87 label.set_text(title)
89 list = xml.get_widget('win_sel_tree')
90 list.get_selection().set_mode('single')
91 cell = gtk.CellRendererText()
92 column = gtk.TreeViewColumn("Widget", cell, text=0)
93 list.append_column(column)
94 list.set_search_column(0)
95 model = gtk.ListStore(gobject.TYPE_STRING)
96 keys = values.keys()
97 keys.sort()
98 for val in keys:
99 model.append([val])
101 list.set_model(model)
102 list.connect('row-activated', lambda x,y,z: win.response(gtk.RESPONSE_OK) or True)
104 ok = False
105 while not ok:
106 response = win.run()
107 ok = True
108 res = None
109 if response == gtk.RESPONSE_OK:
110 sel = list.get_selection().get_selected()
111 if sel:
112 (model, iter) = sel
113 if iter:
114 res = model.get_value(iter, 0)
115 res = (res, values[res])
116 else:
117 ok = False
118 else:
119 ok = False
120 else:
121 res = None
122 parent.present()
123 win.destroy()
124 return res
126 def tipoftheday(parent=None):
127 class tip(object):
128 def __init__(self, parent=None):
129 try:
130 self.number = int(options.options['tip.position'])
131 except:
132 self.number = 0
133 log = logging.getLogger('common.message')
134 log.error('Invalid value for option tip.position ! See ~/.terprc !')
135 winglade=glade.XML(common.terp_path("terp.glade"), "win_tips", gettext.textdomain())
136 self.win = winglade.get_widget('win_tips')
137 if parent:
138 self.win.set_transient_for(parent)
139 self.parent = parent
140 self.win.show_all()
141 self.label = winglade.get_widget('tip_label')
142 self.check = winglade.get_widget('tip_checkbutton')
143 img = winglade.get_widget('tip_image_tinyerp')
144 img.set_from_file(common.terp_path_pixmaps('tinyerp.png'))
145 dict = {
146 'on_but_next_activate': self.tip_next,
147 'on_but_previous_activate': self.tip_previous,
148 'on_but_close_activate': self.tip_close,
150 for signal in dict:
151 winglade.signal_connect(signal, dict[signal])
152 self.tip_set()
153 self.win.show_all()
155 def tip_set(self):
156 lang = options['client.lang']
157 f = False
158 if lang:
159 f = terp_path('tipoftheday.'+lang+'.txt')
160 if not f:
161 f = terp_path('tipoftheday.txt')
162 tips = file(f).read().split('---')
163 tip = tips[self.number % len(tips)]
164 del tips
165 self.label.set_text(tip)
166 self.label.set_use_markup( True )
168 def tip_next(self, *args):
169 self.number+=1
170 self.tip_set()
172 def tip_previous(self, *args):
173 if self.number>0:
174 self.number -= 1
175 self.tip_set()
177 def tip_close(self, *args):
178 check = self.check.get_active()
179 options.options['tip.autostart'] = check
180 options.options['tip.position'] = self.number+1
181 options.save()
182 parent.present()
183 self.win.destroy()
184 tip2 = tip(parent)
185 return True
187 class upload_data_thread(threading.Thread):
188 def __init__(self, email, data, type, supportid):
189 self.args = [('email',email),('type',type),('supportid',supportid),('data',data)]
190 super(upload_data_thread,self).__init__()
191 def run(self):
192 try:
193 import urllib
194 args = urllib.urlencode(self.args)
195 fp = urllib.urlopen('http://www.tinyerp.com/scripts/survey.php', args)
196 fp.read()
197 fp.close()
198 except:
199 pass
201 def upload_data(email, data, type='SURVEY', supportid=''):
202 a = upload_data_thread(email, data, type, supportid)
203 a.start()
204 return True
206 def terp_survey():
207 if options.options['survey.position']==SURVEY_VERSION:
208 return False
209 import pickle
210 widnames = ('country','role','industry','employee','hear','system','opensource')
211 winglade = glade.XML(common.terp_path("terp.glade"), "dia_survey", gettext.textdomain())
212 win = winglade.get_widget('dia_survey')
213 parent = service.LocalService('gui.main').window
214 win.set_transient_for(parent)
215 win.set_icon(TINYERP_ICON)
216 for widname in widnames:
217 wid = winglade.get_widget('combo_'+widname)
218 wid.child.set_text('(choose one)')
219 wid.child.set_editable(False)
220 res = win.run()
221 if res==gtk.RESPONSE_OK:
222 email = winglade.get_widget('entry_email').get_text()
223 company = winglade.get_widget('entry_company').get_text()
224 result = "\ncompany: "+str(company)
225 for widname in widnames:
226 wid = winglade.get_widget('combo_'+widname)
227 result += "\n"+widname+": "+wid.child.get_text()
228 result += "\nplan_use: "+str(winglade.get_widget('check_use').get_active())
229 result += "\nplan_sell: "+str(winglade.get_widget('check_sell').get_active())
231 buffer = winglade.get_widget('textview_comment').get_buffer()
232 iter_start = buffer.get_start_iter()
233 iter_end = buffer.get_end_iter()
234 result += "\nnote: "+buffer.get_text(iter_start,iter_end,False)
235 parent.present()
236 win.destroy()
237 upload_data(email, result, type='SURVEY '+str(SURVEY_VERSION))
238 options.options['survey.position']=SURVEY_VERSION
239 options.save()
240 common.message(_('Thank you for the feedback !\nYour comments have been sent to Tiny ERP.\nYou should now start by creating a new database or\nconnecting to an existing server through the "File" menu.'))
241 else:
242 parent.present()
243 win.destroy()
244 common.message(_('Thank you for testing Tiny ERP !\nYou should now start by creating a new database or\nconnecting to an existing server through the "File" menu.'))
245 return True
248 def file_selection(title, filename='', parent=None,
249 action=gtk.FILE_CHOOSER_ACTION_OPEN, preview=True, multi=False, filters=None):
250 if action == gtk.FILE_CHOOSER_ACTION_OPEN:
251 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
252 gtk.STOCK_OPEN,gtk.RESPONSE_OK)
253 else:
254 buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
255 gtk.STOCK_SAVE, gtk.RESPONSE_OK)
256 win = gtk.FileChooserDialog(title, None, action, buttons)
257 if not parent:
258 parent = service.LocalService('gui.main').window
259 win.set_transient_for(parent)
260 win.set_icon(TINYERP_ICON)
261 win.set_current_folder(options.options['client.default_path'])
262 win.set_select_multiple(multi)
263 win.set_default_response(gtk.RESPONSE_OK)
264 if filters is not None:
265 for filter in filters:
266 win.add_filter(filter)
267 if filename:
268 win.set_current_name(filename)
270 def update_preview_cb(win, img):
271 filename = win.get_preview_filename()
272 try:
273 pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(filename, 128, 128)
274 img.set_from_pixbuf(pixbuf)
275 have_preview = True
276 except:
277 have_preview = False
278 win.set_preview_widget_active(have_preview)
279 return
281 if preview:
282 img_preview = gtk.Image()
283 win.set_preview_widget(img_preview)
284 win.connect('update-preview', update_preview_cb, img_preview)
286 button = win.run()
287 if button!=gtk.RESPONSE_OK:
288 win.destroy()
289 return False
290 if not multi:
291 filepath = win.get_filename()
292 if filepath:
293 filepath = filepath.decode('utf-8')
294 try:
295 options.options['client.default_path'] = os.path.dirname(filepath)
296 except:
297 pass
298 parent.present()
299 win.destroy()
300 return filepath
301 else:
302 filenames = win.get_filenames()
303 if filenames:
304 filenames = [x.decode('utf-8') for x in filenames]
305 try:
306 options.options['client.default_path'] = os.path.dirname(filenames[0])
307 except:
308 pass
309 parent.present()
310 win.destroy()
311 return filenames
313 def support(*args):
314 import pickle
315 wid_list = ['email_entry','id_entry','name_entry','phone_entry','company_entry','error_details','explanation_textview','remark_textview']
316 required_wid = ['email_entry', 'name_entry', 'company_name', 'id_entry']
317 support_id = options['support.support_id']
318 recipient = options['support.recipient']
320 sur = glade.XML(terp_path("terp.glade"), "dia_support",gettext.textdomain())
321 win = sur.get_widget('dia_support')
322 parent = service.LocalService('gui.main').window
323 win.set_transient_for(parent)
324 win.set_icon(TINYERP_ICON)
325 win.show_all()
326 sur.get_widget('id_entry').set_text(support_id)
328 response = win.run()
329 if response == gtk.RESPONSE_OK:
330 fromaddr = sur.get_widget('email_entry').get_text()
331 id_contract = sur.get_widget('id_entry').get_text()
332 name = sur.get_widget('name_entry').get_text()
333 phone = sur.get_widget('phone_entry').get_text()
334 company = sur.get_widget('company_entry').get_text()
336 urgency = sur.get_widget('urgency_combo').get_active_text()
338 buffer = sur.get_widget('explanation_textview').get_buffer()
339 explanation = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
341 buffer = sur.get_widget('remark_textview').get_buffer()
342 remarks = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
344 content = name +"(%s, %s, %s)"%(id_contract, company, phone) +" has reported the following bug:\n"+ explanation + "\nremarks:\n" + remarks
346 if upload_data(fromaddr, content, 'support', id_contract):
347 common.message(_('Support request sent !'))
349 parent.present()
350 win.destroy()
351 return True
353 def error(title, message, details='', parent=None):
354 log = logging.getLogger('common.message')
355 log.error('MSG %s: %s' % (str(message),details))
357 wid_list = ['email_entry','id_entry','name_entry','phone_entry','company_entry','error_details','explanation_textview','remarks_textview']
358 required_wid = ['email_entry', 'name_entry', 'company_name', 'id_entry']
359 colors = {'invalid':'#ffdddd', 'readonly':'grey', 'required':'#ddddff', 'normal':'white'}
361 support_id = options['support.support_id']
362 recipient = options['support.recipient']
364 sur = glade.XML(terp_path("terp.glade"), "win_error",gettext.textdomain())
365 win = sur.get_widget('win_error')
366 if not parent:
367 parent=service.LocalService('gui.main').window
368 win.set_transient_for(parent)
369 win.set_icon(TINYERP_ICON)
370 sur.get_widget('error_title').set_text(str(title))
371 sur.get_widget('error_info').set_text(str(message))
372 buf = gtk.TextBuffer()
373 buf.set_text(unicode(details,'latin1').encode('utf-8'))
374 sur.get_widget('error_details').set_buffer(buf)
376 sur.get_widget('id_entry').set_text(support_id)
378 def send(widget):
379 import pickle
381 fromaddr = sur.get_widget('email_entry').get_text()
382 id_contract = sur.get_widget('id_entry').get_text()
383 name = sur.get_widget('name_entry').get_text()
384 phone = sur.get_widget('phone_entry').get_text()
385 company = sur.get_widget('company_entry').get_text()
387 urgency = sur.get_widget('urgency_combo').get_active_text()
389 buffer = sur.get_widget('error_details').get_buffer()
390 traceback = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
392 buffer = sur.get_widget('explanation_textview').get_buffer()
393 explanation = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
395 buffer = sur.get_widget('remarks_textview').get_buffer()
396 remarks = buffer.get_text(buffer.get_start_iter(), buffer.get_end_iter())
398 content = "(%s, %s, %s)"%(id_contract, company, phone) +" has reported the following bug:\n"+ explanation + "\nremarks: " + remarks + "\nThe traceback is:\n" + traceback
400 if upload_data(fromaddr, content, 'error', id_contract):
401 common.message(_('Support request sent !'))
402 return
404 sur.signal_connect('on_button_send_clicked', send)
405 sur.signal_connect('on_closebutton_clicked', lambda x : win.destroy())
407 response = win.run()
408 parent.present()
409 win.destroy()
410 return True
412 def message(msg, type=gtk.MESSAGE_INFO, parent=None):
413 if not parent:
414 parent=service.LocalService('gui.main').window
415 dialog = gtk.MessageDialog(parent,
416 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
417 type, gtk.BUTTONS_OK,
418 msg)
419 dialog.set_icon(TINYERP_ICON)
420 dialog.run()
421 parent.present()
422 dialog.destroy()
423 return True
425 def to_xml(s):
426 return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;')
428 def message_box(title, msg, parent=None):
429 dia = glade.XML(terp_path("terp.glade"), "dia_message_box",gettext.textdomain())
430 win = dia.get_widget('dia_message_box')
431 l = dia.get_widget('msg_title')
432 l.set_text(title)
434 buffer = dia.get_widget('msg_tv').get_buffer()
435 iter_start = buffer.get_start_iter()
436 buffer.insert(iter_start, msg)
438 if not parent:
439 parent=service.LocalService('gui.main').window
440 win.set_transient_for(parent)
441 win.set_icon(TINYERP_ICON)
443 response = win.run()
444 parent.present()
445 win.destroy()
446 return True
449 def warning(msg, title='', parent=None):
450 if not parent:
451 parent=service.LocalService('gui.main').window
452 dialog = gtk.MessageDialog(parent, gtk.DIALOG_DESTROY_WITH_PARENT,
453 gtk.MESSAGE_WARNING, gtk.BUTTONS_OK)
454 dialog.set_icon(TINYERP_ICON)
455 dialog.set_markup('<b>%s</b>\n\n%s' % (to_xml(title),to_xml(msg)))
456 dialog.show_all()
457 dialog.run()
458 parent.present()
459 dialog.destroy()
460 return True
462 def sur(msg, parent=None):
463 if not parent:
464 parent=service.LocalService('gui.main').window
465 sur = glade.XML(terp_path("terp.glade"), "win_sur",gettext.textdomain())
466 win = sur.get_widget('win_sur')
467 win.set_transient_for(parent)
468 win.show_all()
469 l = sur.get_widget('lab_question')
470 l.set_text(msg)
472 if not parent:
473 parent=service.LocalService('gui.main').window
474 win.set_transient_for(parent)
475 win.set_icon(TINYERP_ICON)
477 response = win.run()
478 parent.present()
479 win.destroy()
480 return response == gtk.RESPONSE_OK
482 def sur_3b(msg, parent=None):
483 sur = glade.XML(terp_path("terp.glade"), "win_quest_3b",gettext.textdomain())
484 win = sur.get_widget('win_quest_3b')
485 l = sur.get_widget('label')
486 l.set_text(msg)
488 if not parent:
489 parent=service.LocalService('gui.main').window
490 win.set_transient_for(parent)
491 win.set_icon(TINYERP_ICON)
493 response = win.run()
494 parent.present()
495 win.destroy()
496 if response == gtk.RESPONSE_YES:
497 return 'ok'
498 elif response == gtk.RESPONSE_NO:
499 return 'ko'
500 elif response == gtk.RESPONSE_CANCEL:
501 return 'cancel'
502 else:
503 return 'cancel'
505 def theme_set():
506 theme = options['client.theme']
507 if theme and (theme <> 'none'):
508 fname = os.path.join("themes", theme, "gtkrc")
509 if not os.path.isfile(fname):
510 common.warning('File not found: '+fname+'\nSet theme to none in ~/.terprc', 'Error setting theme')
511 return False
512 gtk.rc_parse("themes/"+theme+"/gtkrc")
513 return True
515 def ask(question, parent=None):
516 dia = glade.XML(terp_path('terp.glade'), 'win_quest', gettext.textdomain())
517 win = dia.get_widget('win_quest')
518 label = dia.get_widget('label')
519 label.set_text(question)
520 entry = dia.get_widget('entry')
522 if not parent:
523 parent=service.LocalService('gui.main').window
524 win.set_transient_for(parent)
525 win.set_icon(TINYERP_ICON)
527 response = win.run()
528 parent.present()
529 win.destroy()
530 if response == gtk.RESPONSE_CANCEL:
531 return None
532 else:
533 return entry.get_text()
535 def concurrency(resource, id, context, parent=None):
536 dia = glade.XML(common.terp_path("terp.glade"),'dialog_concurrency_exception',gettext.textdomain())
537 win = dia.get_widget('dialog_concurrency_exception')
539 if not parent:
540 parent=service.LocalService('gui.main').window
541 win.set_transient_for(parent)
542 win.set_icon(TINYERP_ICON)
544 res= win.run()
545 parent.present()
546 win.destroy()
548 if res == gtk.RESPONSE_OK:
549 return True
550 if res == gtk.RESPONSE_APPLY:
551 obj = service.LocalService('gui.window')
552 obj.create(False, resource, id, [], 'form', None, context,'form,tree')
553 return False
555 def open_file(value, parent):
556 filetype = {}
557 if options['client.filetype']:
558 if isinstance(options['client.filetype'], str):
559 filetype = eval(options['client.filetype'])
560 else:
561 filetype = options['client.filetype']
562 root, ext = os.path.splitext(value)
563 cmd = False
564 if ext[1:] in filetype:
565 cmd = filetype[ext[1:]] % (value)
566 if not cmd:
567 cmd = file_selection(_('Open with...'),
568 parent=parent)
569 if cmd:
570 cmd = cmd + ' %s'
571 filetype[ext[1:]] = cmd
572 options['client.filetype'] = filetype
573 options.save()
574 cmd = cmd % (value)
575 if cmd:
576 pid = os.fork()
577 if not pid:
578 pid = os.fork()
579 if not pid:
580 prog, args = cmd.split(' ', 1)
581 args = [os.path.basename(prog)] + args.split(' ')
582 try:
583 os.execvp(prog, args)
584 except:
585 pass
586 time.sleep(0.1)
587 sys.exit(0)
588 os.waitpid(pid, 0)
591 # Color set
593 colors = {
594 'invalid':'#ff6969',
595 'readonly':'#eeebe7',
596 'required':'#d2d2ff',
597 'normal':'white'