2 ## src/dataforms_widget.py
4 ## Copyright (C) 2003-2010 Yann Leboulanger <asterix AT lagaule.org>
5 ## Copyright (C) 2006 Tomasz Melcer <liori AT exroot.org>
6 ## Copyright (C) 2006-2007 Jean-Marie Traissard <jim AT lapin.org>
8 ## This file is part of Gajim.
10 ## Gajim is free software; you can redistribute it and/or modify
11 ## it under the terms of the GNU General Public License as published
12 ## by the Free Software Foundation; version 3 only.
14 ## Gajim is distributed in the hope that it will be useful,
15 ## but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 ## GNU General Public License for more details.
19 ## You should have received a copy of the GNU General Public License
20 ## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
23 ''' This module contains widget that can display data form (JEP-0004).
24 Words single and multiple refers here to types of data forms:
25 single means these with one record of data (without <reported/> element),
26 multiple - these which may contain more data (with <reported/> element).'''
35 import common
.dataforms
as dataforms
36 from common
import helpers
40 class DataFormWidget(gtk
.Alignment
, object):
43 Data Form widget. Use like any other widget
46 def __init__(self
, dataformnode
=None):
47 ''' Create a widget. '''
48 gtk
.Alignment
.__init
__(self
, xscale
=1.0, yscale
=1.0)
50 self
._data
_form
= None
52 self
.xml
= gtkgui_helpers
.get_gtk_builder('data_form_window.ui',
54 self
.xml
.connect_signals(self
)
55 for name
in ('instructions_label', 'instructions_hseparator',
56 'single_form_viewport', 'data_form_types_notebook',
57 'single_form_scrolledwindow', 'multiple_form_hbox',
58 'records_treeview', 'buttons_vbox', 'add_button', 'remove_button',
59 'edit_button', 'up_button', 'down_button', 'clear_button'):
60 self
.__dict
__[name
] = self
.xml
.get_object(name
)
62 self
.add(self
.xml
.get_object('data_form_vbox'))
64 if dataformnode
is not None:
65 self
.set_data_form(dataformnode
)
67 selection
= self
.records_treeview
.get_selection()
68 selection
.connect('changed', self
.on_records_selection_changed
)
69 selection
.set_mode(gtk
.SELECTION_MULTIPLE
)
71 def set_data_form(self
, dataform
):
73 Set the data form (xmpp.DataForm) displayed in widget
75 assert isinstance(dataform
, dataforms
.DataForm
)
78 self
._data
_form
= dataform
79 if isinstance(dataform
, dataforms
.SimpleDataForm
):
80 self
.build_single_data_form()
82 self
.build_multiple_data_form()
84 # create appropriate description for instructions field if there isn't any
85 if dataform
.instructions
== '':
86 self
.instructions_label
.set_no_show_all(True)
87 self
.instructions_label
.hide()
89 self
.instructions_label
.set_text(dataform
.instructions
)
90 gtkgui_helpers
.label_set_autowrap(self
.instructions_label
)
92 def get_data_form(self
):
94 Data form displayed in the widget or None if no form
96 return self
._data
_form
98 def del_data_form(self
):
99 self
.clean_data_form()
100 self
._data
_form
= None
102 data_form
= property(get_data_form
, set_data_form
, del_data_form
,
103 'Data form presented in a widget')
107 Get the title of data form, as a unicode object. If no title or no form,
108 returns u''. Useful for setting window title
110 if self
._data
_form
is not None:
111 if self
._data
_form
.title
is not None:
112 return self
._data
_form
.title
115 title
= property(get_title
, None, None, 'Data form title')
118 ''' Treat 'us' as one widget. '''
123 # we have actually two different kinds of data forms: one is a simple form to fill,
124 # second is a table with several records;
126 def empty_method(self
):
129 def clean_data_form(self
):
131 Remove data about existing form. This metod is empty, because it is
132 rewritten by build_*_data_form, according to type of form which is
137 def build_single_data_form(self
):
138 '''Invoked when new single form is to be created.'''
139 assert isinstance(self
._data
_form
, dataforms
.SimpleDataForm
)
141 self
.clean_data_form()
143 self
.singleform
= SingleForm(self
._data
_form
)
144 self
.singleform
.show()
145 self
.single_form_viewport
.add(self
.singleform
)
146 self
.data_form_types_notebook
.set_current_page(
147 self
.data_form_types_notebook
.page_num(
148 self
.single_form_scrolledwindow
))
150 self
.clean_data_form
= self
.clean_single_data_form
152 def clean_single_data_form(self
):
154 Called as clean_data_form, read the docs of clean_data_form(). Remove
157 self
.singleform
.destroy()
158 self
.clean_data_form
= self
.empty_method
# we won't call it twice
161 def build_multiple_data_form(self
):
163 Invoked when new multiple form is to be created
165 assert isinstance(self
._data
_form
, dataforms
.MultipleDataForm
)
167 self
.clean_data_form()
169 # creating model for form...
172 for field
in self
._data
_form
.reported
.iter_fields():
173 # note: we store also text-private and hidden fields,
174 # we just do not display them.
175 # TODO: boolean fields
176 #elif field.type=='boolean': fieldtypes.append(bool)
177 fieldtypes
.append(str)
178 fieldvars
.append(field
.var
)
180 self
.multiplemodel
= gtk
.ListStore(*fieldtypes
)
182 # moving all data to model
183 for item
in self
._data
_form
.iter_records():
184 iter_
= self
.multiplemodel
.append()
185 for field
in item
.iter_fields():
186 self
.multiplemodel
.set_value(iter_
, fieldvars
.index(field
.var
),
189 # constructing columns...
190 for field
, counter
in zip(self
._data
_form
.reported
.iter_fields(),
192 self
.records_treeview
.append_column(
193 gtk
.TreeViewColumn(field
.label
, gtk
.CellRendererText(),
196 self
.records_treeview
.set_model(self
.multiplemodel
)
197 self
.records_treeview
.show_all()
199 self
.data_form_types_notebook
.set_current_page(
200 self
.data_form_types_notebook
.page_num(
201 self
.multiple_form_hbox
))
203 self
.clean_data_form
= self
.clean_multiple_data_form
205 readwrite
= self
._data
_form
.type != 'result'
207 self
.buttons_vbox
.set_no_show_all(True)
208 self
.buttons_vbox
.hide()
210 self
.buttons_vbox
.set_no_show_all(False)
212 self
.refresh_multiple_buttons()
214 def clean_multiple_data_form(self
):
216 Called as clean_data_form, read the docs of clean_data_form(). Remove
219 self
.clean_data_form
= self
.empty_method
# we won't call it twice
220 del self
.multiplemodel
222 def refresh_multiple_buttons(self
):
224 Checks for treeview state and makes control buttons sensitive
226 selection
= self
.records_treeview
.get_selection()
227 model
= self
.records_treeview
.get_model()
228 count
= selection
.count_selected_rows()
230 self
.remove_button
.set_sensitive(False)
231 self
.edit_button
.set_sensitive(False)
232 self
.up_button
.set_sensitive(False)
233 self
.down_button
.set_sensitive(False)
235 self
.remove_button
.set_sensitive(True)
236 self
.edit_button
.set_sensitive(True)
237 _
, (path
,) = selection
.get_selected_rows()
238 iter_
= model
.get_iter(path
)
239 if model
.iter_next(iter_
) is None:
240 self
.up_button
.set_sensitive(True)
241 self
.down_button
.set_sensitive(False)
243 self
.up_button
.set_sensitive(False)
244 self
.down_button
.set_sensitive(True)
246 self
.up_button
.set_sensitive(True)
247 self
.down_button
.set_sensitive(True)
249 self
.remove_button
.set_sensitive(True)
250 self
.edit_button
.set_sensitive(True)
251 self
.up_button
.set_sensitive(False)
252 self
.down_button
.set_sensitive(False)
255 self
.clear_button
.set_sensitive(False)
257 self
.clear_button
.set_sensitive(True)
259 def on_clear_button_clicked(self
, widget
):
260 self
.records_treeview
.get_model().clear()
262 def on_remove_button_clicked(self
, widget
):
263 selection
= self
.records_treeview
.get_selection()
264 model
, rowrefs
= selection
.get_selected_rows()
265 # rowref is a list of paths
266 for i
in xrange(len(rowrefs
)):
267 rowrefs
[i
] = gtk
.TreeRowReference(model
, rowrefs
[i
])
268 # rowref is a list of row references; need to convert because we will
269 # modify the model, paths would change
270 for rowref
in rowrefs
:
271 del model
[rowref
.get_path()]
273 def on_up_button_clicked(self
, widget
):
274 selection
= self
.records_treeview
.get_selection()
275 model
, (path
,) = selection
.get_selected_rows()
276 iter_
= model
.get_iter(path
)
277 # constructing path for previous iter
278 previter
= model
.get_iter((path
[0]-1,))
279 model
.swap(iter_
, previter
)
281 self
.refresh_multiple_buttons()
283 def on_down_button_clicked(self
, widget
):
284 selection
= self
.records_treeview
.get_selection()
285 model
, (path
,) = selection
.get_selected_rows()
286 iter_
= model
.get_iter(path
)
287 nextiter
= model
.iter_next(iter_
)
288 model
.swap(iter_
, nextiter
)
290 self
.refresh_multiple_buttons()
292 def on_records_selection_changed(self
, widget
):
293 self
.refresh_multiple_buttons()
295 class SingleForm(gtk
.Table
, object):
297 Widget that represent DATAFORM_SINGLE mode form. Because this is used not
298 only to display single forms, but to form input windows of multiple-type
299 forms, it is in another class
302 def __init__(self
, dataform
):
303 assert isinstance(dataform
, dataforms
.SimpleDataForm
)
305 gtk
.Table
.__init
__(self
)
306 self
.set_col_spacings(12)
307 self
.set_row_spacings(6)
309 def decorate_with_tooltip(widget
, field
):
311 Adds a tooltip containing field's description to a widget. Creates
312 EventBox if widget doesn't have its own gdk window. Returns decorated
315 if field
.description
!= '':
316 if widget
.flags() & gtk
.NO_WINDOW
:
317 evbox
= gtk
.EventBox()
320 widget
.set_tooltip_text(field
.description
)
323 self
._data
_form
= dataform
328 # is the form changeable?
329 readwrite
= dataform
.type != 'result'
332 for field
in self
._data
_form
.iter_fields():
333 if field
.type == 'hidden': continue
336 commonlabelcenter
= False
340 if field
.type == 'boolean':
341 commonlabelcenter
= True
342 widget
= gtk
.CheckButton()
343 widget
.connect('toggled', self
.on_boolean_checkbutton_toggled
,
345 widget
.set_active(field
.value
)
347 elif field
.type == 'fixed':
350 if field
.label
is None:
355 widget
= gtk
.Label(field
.value
)
356 widget
.set_line_wrap(True)
357 self
.attach(widget
, leftattach
, rightattach
, linecounter
,
358 linecounter
+1, xoptions
=gtk
.FILL
, yoptions
=gtk
.FILL
)
360 elif field
.type == 'list-single':
361 # TODO: What if we have radio buttons and non-required field?
362 # TODO: We cannot deactivate them all...
363 if len(field
.options
) < 6:
364 # 5 option max: show radiobutton
367 for value
, label
in field
.iter_options():
370 radio
= gtk
.RadioButton(first_radio
, label
=label
)
371 radio
.connect('toggled',
372 self
.on_list_single_radiobutton_toggled
, field
, value
)
373 if first_radio
is None:
375 if field
.value
== '': # TODO: is None when done
377 if value
== field
.value
:
378 radio
.set_active(True)
379 widget
.pack_start(radio
, expand
=False)
381 # more than 5 options: show combobox
382 def on_list_single_combobox_changed(combobox
, f
):
383 iter_
= combobox
.get_active_iter()
385 model
= combobox
.get_model()
386 f
.value
= model
[iter_
][1]
389 widget
= gtkgui_helpers
.create_combobox(field
.options
,
391 widget
.connect('changed', on_list_single_combobox_changed
, field
)
392 widget
.set_sensitive(readwrite
)
394 elif field
.type == 'list-multi':
395 # TODO: When more than few choices, make a list
396 if len(field
.options
) < 6:
397 # 5 option max: show checkbutton
399 for value
, label
in field
.iter_options():
400 check
= gtk
.CheckButton(label
, use_underline
=False)
401 check
.set_active(value
in field
.values
)
402 check
.connect('toggled',
403 self
.on_list_multi_checkbutton_toggled
, field
, value
)
404 widget
.pack_start(check
, expand
=False)
405 widget
.set_sensitive(readwrite
)
407 # more than 5 options: show combobox
408 def on_list_multi_treeview_changed(selection
, f
):
409 def for_selected(treemodel
, path
, iter):
410 vals
.append(treemodel
[iter][1])
412 selection
.selected_foreach(for_selected
)
413 field
.values
= vals
[:]
414 widget
= gtk
.ScrolledWindow()
415 widget
.set_policy(gtk
.POLICY_AUTOMATIC
, gtk
.POLICY_AUTOMATIC
)
416 tv
= gtkgui_helpers
.create_list_multi(field
.options
,
419 widget
.set_size_request(-1, 120)
420 tv
.get_selection().connect('changed',
421 on_list_multi_treeview_changed
, field
)
422 tv
.set_sensitive(readwrite
)
424 elif field
.type == 'jid-single':
426 widget
.connect('changed', self
.on_text_single_entry_changed
, field
)
427 widget
.set_text(field
.value
)
429 elif field
.type == 'jid-multi':
432 xml
= gtkgui_helpers
.get_gtk_builder('data_form_window.ui',
433 'multiple_form_hbox')
434 widget
= xml
.get_object('multiple_form_hbox')
435 treeview
= xml
.get_object('records_treeview')
437 listmodel
= gtk
.ListStore(str)
438 for value
in field
.iter_values():
439 # nobody will create several megabytes long stanza
440 listmodel
.insert(999999, (value
,))
442 treeview
.set_model(listmodel
)
444 renderer
= gtk
.CellRendererText()
445 renderer
.set_property('editable', True)
446 renderer
.connect('edited',
447 self
.on_jid_multi_cellrenderertext_edited
, treeview
, listmodel
,
450 treeview
.append_column(gtk
.TreeViewColumn(None, renderer
,
453 decorate_with_tooltip(treeview
, field
)
455 add_button
=xml
.get_object('add_button')
456 add_button
.connect('clicked',
457 self
.on_jid_multi_add_button_clicked
, treeview
, listmodel
, field
)
458 edit_button
=xml
.get_object('edit_button')
459 edit_button
.connect('clicked',
460 self
.on_jid_multi_edit_button_clicked
, treeview
)
461 remove_button
=xml
.get_object('remove_button')
462 remove_button
.connect('clicked',
463 self
.on_jid_multi_remove_button_clicked
, treeview
, field
)
464 clear_button
=xml
.get_object('clear_button')
465 clear_button
.connect('clicked',
466 self
.on_jid_multi_clean_button_clicked
, listmodel
, field
)
468 add_button
.set_no_show_all(True)
469 edit_button
.set_no_show_all(True)
470 remove_button
.set_no_show_all(True)
471 clear_button
.set_no_show_all(True)
473 widget
.set_sensitive(readwrite
)
474 self
.attach(widget
, 1, 2, linecounter
, linecounter
+1)
478 elif field
.type == 'text-private':
479 commonlabelcenter
= True
481 widget
.connect('changed', self
.on_text_single_entry_changed
, field
)
482 widget
.set_visibility(False)
483 widget
.set_text(field
.value
)
485 elif field
.type == 'text-multi':
486 # TODO: bigger text view
489 textwidget
= gtk
.TextView()
490 textwidget
.set_wrap_mode(gtk
.WRAP_WORD
)
491 textwidget
.get_buffer().connect('changed',
492 self
.on_text_multi_textbuffer_changed
, field
)
493 textwidget
.get_buffer().set_text(field
.value
)
494 textwidget
.set_sensitive(readwrite
)
496 widget
= gtk
.ScrolledWindow()
497 widget
.add(textwidget
)
499 widget
=decorate_with_tooltip(widget
, field
)
500 self
.attach(widget
, 1, 2, linecounter
, linecounter
+1)
503 # field.type == 'text-single' or field.type is nonstandard:
504 # JEP says that if we don't understand some type, we
505 # should handle it as text-single
506 commonlabelcenter
= True
509 widget
.connect('changed', self
.on_text_single_entry_changed
,
511 widget
.set_sensitive(readwrite
)
512 if field
.value
is None:
514 widget
.set_text(field
.value
)
517 widget
= gtk
.Label(field
.value
)
518 widget
.set_sensitive(True)
519 widget
.set_alignment(0.0, 0.5)
520 widget
=decorate_with_tooltip(widget
, field
)
521 self
.attach(widget
, 1, 2, linecounter
, linecounter
+1,
524 if commonlabel
and field
.label
is not None:
525 label
= gtk
.Label(field
.label
)
526 if commonlabelcenter
:
527 label
.set_alignment(0.0, 0.5)
529 label
.set_alignment(0.0, 0.0)
530 label
= decorate_with_tooltip(label
, field
)
531 self
.attach(label
, 0, 1, linecounter
, linecounter
+1,
532 xoptions
=gtk
.FILL
, yoptions
=gtk
.FILL
)
534 if field
.media
is not None:
535 for uri
in field
.media
.uris
:
536 if uri
.type_
.startswith('image/'):
538 img_data
= base64
.decodestring(uri
.uri_data
)
539 pixbuf_l
= gtk
.gdk
.PixbufLoader()
540 pixbuf_l
.write(img_data
)
542 media
= gtk
.image_new_from_pixbuf(pixbuf_l
.\
545 media
= gtk
.Label(_('Unable to load image'))
547 media
= gtk
.Label(_('Media type not supported: %s') % \
550 self
.attach(media
, 0, 1, linecounter
, linecounter
+1,
551 xoptions
=gtk
.FILL
, yoptions
=gtk
.FILL
)
554 assert widget
is not None
555 widget
.set_sensitive(readwrite
)
556 widget
= decorate_with_tooltip(widget
, field
)
557 self
.attach(widget
, 1, 2, linecounter
, linecounter
+1,
561 label
= gtk
.Label('*')
562 label
.set_tooltip_text(_('This field is required'))
563 self
.attach(label
, 2, 3, linecounter
, linecounter
+1, xoptions
=0,
567 if self
.get_property('visible'):
571 # simulate that we are one widget
574 def on_boolean_checkbutton_toggled(self
, widget
, field
):
575 field
.value
= widget
.get_active()
577 def on_list_single_radiobutton_toggled(self
, widget
, field
, value
):
580 def on_list_multi_checkbutton_toggled(self
, widget
, field
, value
):
581 # TODO: make some methods like add_value and remove_value
582 if widget
.get_active() and value
not in field
.values
:
583 field
.values
+= [value
]
584 elif not widget
.get_active() and value
in field
.values
:
585 field
.values
= [v
for v
in field
.values
if v
!=value
]
587 def on_text_single_entry_changed(self
, widget
, field
):
588 field
.value
= widget
.get_text()
590 def on_text_multi_textbuffer_changed(self
, widget
, field
):
591 field
.value
= widget
.get_text(
592 widget
.get_start_iter(),
593 widget
.get_end_iter())
595 def on_jid_multi_cellrenderertext_edited(self
, cell
, path
, newtext
, treeview
,
601 newtext
= helpers
.parse_jid(newtext
)
602 except helpers
.InvalidFormat
, s
:
603 dialogs
.ErrorDialog(_('Invalid Jabber ID'), str(s
))
605 if newtext
in field
.values
:
607 _('Jabber ID already in list'),
608 _('The Jabber ID you entered is already in the list. Choose another one.'))
609 gobject
.idle_add(treeview
.set_cursor
, path
)
611 model
[path
][0]=newtext
613 values
= field
.values
614 values
[values
.index(old
)]=newtext
615 field
.values
= values
617 def on_jid_multi_add_button_clicked(self
, widget
, treeview
, model
, field
):
619 jid
= _('new@jabber.id')
620 if jid
in field
.values
:
622 while _('new%d@jabber.id') % i
in field
.values
:
624 jid
= _('new%d@jabber.id') % i
625 iter_
= model
.insert(999999, (jid
,))
626 treeview
.set_cursor(model
.get_path(iter_
), treeview
.get_column(0), True)
627 field
.values
= field
.values
+ [jid
]
629 def on_jid_multi_edit_button_clicked(self
, widget
, treeview
):
630 model
, iter_
= treeview
.get_selection().get_selected()
631 assert iter_
is not None
633 treeview
.set_cursor(model
.get_path(iter_
), treeview
.get_column(0), True)
635 def on_jid_multi_remove_button_clicked(self
, widget
, treeview
, field
):
636 selection
= treeview
.get_selection()
639 def remove(model
, path
, iter_
, deleted
):
640 deleted
+=model
[iter_
]
643 selection
.selected_foreach(remove
, deleted
)
644 field
.values
= (v
for v
in field
.values
if v
not in deleted
)
646 def on_jid_multi_clean_button_clicked(self
, widget
, model
, field
):