1 ##############################################################################
3 # Copyright (c) 2004-2008 TINY SPRL. (http://tiny.be) All Rights Reserved.
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
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 ##############################################################################
40 from options
import options
49 # Upgrade this number to force the client to ask the survey
53 def _search_file(file, dir='path.share'):
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
),
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:
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')
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')
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
)
101 list.set_model(model
)
102 list.connect('row-activated', lambda x
,y
,z
: win
.response(gtk
.RESPONSE_OK
) or True)
109 if response
== gtk
.RESPONSE_OK
:
110 sel
= list.get_selection().get_selected()
114 res
= model
.get_value(iter, 0)
115 res
= (res
, values
[res
])
126 def tipoftheday(parent
=None):
128 def __init__(self
, parent
=None):
130 self
.number
= int(options
.options
['tip.position'])
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')
138 self
.win
.set_transient_for(parent
)
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'))
146 'on_but_next_activate': self
.tip_next
,
147 'on_but_previous_activate': self
.tip_previous
,
148 'on_but_close_activate': self
.tip_close
,
151 winglade
.signal_connect(signal
, dict[signal
])
156 lang
= options
['client.lang']
159 f
= terp_path('tipoftheday.'+lang
+'.txt')
161 f
= terp_path('tipoftheday.txt')
162 tips
= file(f
).read().split('---')
163 tip
= tips
[self
.number
% len(tips
)]
165 self
.label
.set_text(tip
)
166 self
.label
.set_use_markup( True )
168 def tip_next(self
, *args
):
172 def tip_previous(self
, *args
):
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
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
__()
194 args
= urllib
.urlencode(self
.args
)
195 fp
= urllib
.urlopen('http://www.tinyerp.com/scripts/survey.php', args
)
201 def upload_data(email
, data
, type='SURVEY', supportid
=''):
202 a
= upload_data_thread(email
, data
, type, supportid
)
207 if options
.options
['survey.position']==SURVEY_VERSION
:
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)
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)
237 upload_data(email
, result
, type='SURVEY '+str(SURVEY_VERSION
))
238 options
.options
['survey.position']=SURVEY_VERSION
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.'))
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.'))
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
)
254 buttons
=(gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
,
255 gtk
.STOCK_SAVE
, gtk
.RESPONSE_OK
)
256 win
= gtk
.FileChooserDialog(title
, None, action
, buttons
)
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)
268 win
.set_current_name(filename
)
270 def update_preview_cb(win
, img
):
271 filename
= win
.get_preview_filename()
273 pixbuf
= gtk
.gdk
.pixbuf_new_from_file_at_size(filename
, 128, 128)
274 img
.set_from_pixbuf(pixbuf
)
278 win
.set_preview_widget_active(have_preview
)
282 img_preview
= gtk
.Image()
283 win
.set_preview_widget(img_preview
)
284 win
.connect('update-preview', update_preview_cb
, img_preview
)
287 if button
!=gtk
.RESPONSE_OK
:
291 filepath
= win
.get_filename()
293 filepath
= filepath
.decode('utf-8')
295 options
.options
['client.default_path'] = os
.path
.dirname(filepath
)
302 filenames
= win
.get_filenames()
304 filenames
= [x
.decode('utf-8') for x
in filenames
]
306 options
.options
['client.default_path'] = os
.path
.dirname(filenames
[0])
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
)
326 sur
.get_widget('id_entry').set_text(support_id
)
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 !'))
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')
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
)
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 !'))
404 sur
.signal_connect('on_button_send_clicked', send
)
405 sur
.signal_connect('on_closebutton_clicked', lambda x
: win
.destroy())
412 def message(msg
, type=gtk
.MESSAGE_INFO
, parent
=None):
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
,
419 dialog
.set_icon(TINYERP_ICON
)
426 return s
.replace('&','&').replace('<','<').replace('>','>')
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')
434 buffer = dia
.get_widget('msg_tv').get_buffer()
435 iter_start
= buffer.get_start_iter()
436 buffer.insert(iter_start
, msg
)
439 parent
=service
.LocalService('gui.main').window
440 win
.set_transient_for(parent
)
441 win
.set_icon(TINYERP_ICON
)
449 def warning(msg
, title
='', parent
=None):
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
)))
462 def sur(msg
, parent
=None):
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
)
469 l
= sur
.get_widget('lab_question')
473 parent
=service
.LocalService('gui.main').window
474 win
.set_transient_for(parent
)
475 win
.set_icon(TINYERP_ICON
)
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')
489 parent
=service
.LocalService('gui.main').window
490 win
.set_transient_for(parent
)
491 win
.set_icon(TINYERP_ICON
)
496 if response
== gtk
.RESPONSE_YES
:
498 elif response
== gtk
.RESPONSE_NO
:
500 elif response
== gtk
.RESPONSE_CANCEL
:
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')
512 gtk
.rc_parse("themes/"+theme
+"/gtkrc")
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')
523 parent
=service
.LocalService('gui.main').window
524 win
.set_transient_for(parent
)
525 win
.set_icon(TINYERP_ICON
)
530 if response
== gtk
.RESPONSE_CANCEL
:
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')
540 parent
=service
.LocalService('gui.main').window
541 win
.set_transient_for(parent
)
542 win
.set_icon(TINYERP_ICON
)
548 if res
== gtk
.RESPONSE_OK
:
550 if res
== gtk
.RESPONSE_APPLY
:
551 obj
= service
.LocalService('gui.window')
552 obj
.create(False, resource
, id, [], 'form', None, context
,'form,tree')
555 def open_file(value
, parent
):
557 if options
['client.filetype']:
558 if isinstance(options
['client.filetype'], str):
559 filetype
= eval(options
['client.filetype'])
561 filetype
= options
['client.filetype']
562 root
, ext
= os
.path
.splitext(value
)
564 if ext
[1:] in filetype
:
565 cmd
= filetype
[ext
[1:]] % (value
)
567 cmd
= file_selection(_('Open with...'),
571 filetype
[ext
[1:]] = cmd
572 options
['client.filetype'] = filetype
580 prog
, args
= cmd
.split(' ', 1)
581 args
= [os
.path
.basename(prog
)] + args
.split(' ')
583 os
.execvp(prog
, args
)
595 'readonly':'#eeebe7',
596 'required':'#d2d2ff',