root/branches/gajim_0.11.1/src/dataforms_widget.py

Revision 8643, 17.2 kB (checked in by roidelapluie, 12 months ago)

0.11.2: add multiple data form support (XEP-0004)

Line 
1##      dataforms.py
2##
3## Copyright (C) 2003-2006 Yann Le Boulanger <asterix@lagaule.org>
4## Copyright (C) 2005-2006 Nikos Kouremenos <nkour@jabber.org>
5## Copyright (C) 2005 Dimitur Kirov <dkirov@gmail.com>
6## Copyright (C) 2003-2005 Vincent Hanquez <tab@snarc.org>
7##
8## This program is free software; you can redistribute it and/or modify
9## it under the terms of the GNU General Public License as published
10## by the Free Software Foundation; version 2 only.
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 General Public License for more details.
16##
17""" This module contains widget that can display data form (JEP-0004).
18Words single and multiple refers here to types of data forms:
19single means these with one record of data (without <reported/> element),
20multiple - these which may contain more data (with <reported/> element)."""
21
22import gtk
23
24import gtkgui_helpers
25
26import common.dataforms as dataforms
27
28import itertools
29
30class DataFormWidget(gtk.Alignment, object):
31# "public" interface
32        """ Data Form widget. Use like any other widget. """
33        def __init__(self, dataformnode=None):
34                """ Create a widget. """
35                gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0)
36
37                self._data_form = None
38
39                self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_vbox')
40                self.xml.signal_autoconnect(self)
41                for name in ('instructions_label', 'instructions_hseparator',
42                                'single_form_viewport', 'data_form_types_notebook',
43                                'single_form_scrolledwindow', 'multiple_form_hbox',
44                                'records_treeview', 'buttons_vbox', 'add_button', 'remove_button',
45                                'edit_button', 'up_button', 'down_button', 'clear_button'):
46                        self.__dict__[name] = self.xml.get_widget(name)
47
48                self.add(self.xml.get_widget('data_form_vbox'))
49
50                if dataformnode is not None:
51                        self.set_data_form(dataformnode)
52
53                selection = self.records_treeview.get_selection()
54                selection.connect('changed', self.on_records_selection_changed)
55                selection.set_mode(gtk.SELECTION_MULTIPLE)
56
57        def set_data_form(self, dataform):
58                """ Set the data form (xmpp.DataForm) displayed in widget. """
59                assert isinstance(dataform, dataforms.DataForm)
60
61                self.del_data_form()
62                self._data_form = dataform
63                if isinstance(dataform, dataforms.SimpleDataForm):
64                        self.build_single_data_form()
65                else:
66                        self.build_multiple_data_form()
67
68                # create appropriate description for instructions field if there isn't any
69                if dataform.instructions=='':
70                        self.instructions_label.set_no_show_all(True)
71                        self.instructions_label.hide()
72                else:
73                        self.instructions_label.set_text(dataform.instructions)
74
75        def get_data_form(self):
76                """ Data form displayed in the widget or None if no form. """
77                return self._data_form
78
79        def del_data_form(self):
80                self.clean_data_form()
81                self._data_form = None
82
83        data_form = property(get_data_form, set_data_form, del_data_form,
84                "Data form presented in a widget")
85
86        def get_title(self):
87                """ Get the title of data form, as a unicode object. If no
88                title or no form, returns u''. Useful for setting window title. """
89                if self._data_form is not None:
90                        if self._data_form.title is not None:
91                                return self._data_form.title
92                return u''
93
94        title = property(get_title, None, None, "Data form title")
95
96        def show(self):
97                """ Treat 'us' as one widget. """
98                self.show_all()
99
100# "private" methods
101
102# we have actually two different kinds of data forms: one is a simple form to fill,
103# second is a table with several records;
104
105        def empty_method(self):
106                pass
107
108        def clean_data_form(self):
109                '''Remove data about existing form. This metod is empty, because
110                it is rewritten by build_*_data_form, according to type of form
111                which is actually displayed.'''
112                pass
113
114        def build_single_data_form(self):
115                '''Invoked when new single form is to be created.'''
116                assert isinstance(self._data_form, dataforms.SimpleDataForm)
117
118                self.clean_data_form()
119
120                self.singleform = SingleForm(self._data_form)
121                self.singleform.show()
122                self.single_form_viewport.add(self.singleform)
123                self.data_form_types_notebook.set_current_page(
124                        self.data_form_types_notebook.page_num(
125                                self.single_form_scrolledwindow))
126
127                self.clean_data_form = self.clean_single_data_form
128
129        def clean_single_data_form(self):
130                '''(Called as clean_data_form, read the docs of clean_data_form()).
131                Remove form from widget.'''
132                self.singleform.destroy()
133                self.clean_data_form = self.empty_method        # we won't call it twice
134                del self.singleform
135
136        def build_multiple_data_form(self):
137                '''Invoked when new multiple form is to be created.'''
138                assert isinstance(self._data_form, dataforms.MultipleDataForm)
139
140                self.clean_data_form()
141
142                # creating model for form...
143                fieldtypes = []
144                for field in self._data_form.reported.iter_fields():
145                        # note: we store also text-private and hidden fields,
146                        # we just do not display them.
147                        # TODO: boolean fields
148                        #elif field.type=='boolean': fieldtypes.append(bool)
149                        fieldtypes.append(str)
150
151                self.multiplemodel = gtk.ListStore(*fieldtypes)
152
153                # moving all data to model
154                for item in self._data_form.iter_records():
155                        # TODO: probably wrong... (.value[s]?, fields not in the same order?)
156                        # not checking multiple-item forms...
157                        self.multiplemodel.append([field.value for field in item.iter_fields()])
158
159                # constructing columns...
160                for field, counter in zip(self._data_form.reported.iter_fields(), itertools.count()):
161                        self.records_treeview.append_column(
162                                gtk.TreeViewColumn(field.label, gtk.CellRendererText(),
163                                        text=counter))
164
165                self.records_treeview.set_model(self.multiplemodel)
166                self.records_treeview.show_all()
167
168                self.data_form_types_notebook.set_current_page(
169                        self.data_form_types_notebook.page_num(
170                                self.multiple_form_hbox))
171
172                self.clean_data_form = self.clean_multiple_data_form
173
174                readwrite = self._data_form.type != 'result'
175                if not readwrite:
176                        self.buttons_vbox.set_no_show_all(True)
177                        self.buttons_vbox.hide()
178                else:
179                        self.buttons_vbox.set_no_show_all(False)
180                        # refresh list look
181                        self.refresh_multiple_buttons()
182
183        def clean_multiple_data_form(self):
184                '''(Called as clean_data_form, read the docs of clean_data_form()).
185                Remove form from widget.'''
186                self.clean_data_form = self.empty_method        # we won't call it twice
187                del self.multiplemodel
188
189        def refresh_multiple_buttons(self):
190                ''' Checks for treeview state and makes control buttons sensitive.'''
191                selection = self.records_treeview.get_selection()
192                model = self.records_treeview.get_model()
193                count = selection.count_selected_rows()
194                if count == 0:
195                        self.remove_button.set_sensitive(False)
196                        self.edit_button.set_sensitive(False)
197                        self.up_button.set_sensitive(False)
198                        self.down_button.set_sensitive(False)
199                elif count == 1:
200                        self.remove_button.set_sensitive(True)
201                        self.edit_button.set_sensitive(True)
202                        _, (path,) = selection.get_selected_rows()
203                        iter = model.get_iter(path)
204                        if model.iter_next(iter) is None:
205                                self.up_button.set_sensitive(True)
206                                self.down_button.set_sensitive(False)
207                        elif path == (0, ):
208                                self.up_button.set_sensitive(False)
209                                self.down_button.set_sensitive(True)
210                        else:
211                                self.up_button.set_sensitive(True)
212                                self.down_button.set_sensitive(True)
213                else:
214                        self.remove_button.set_sensitive(True)
215                        self.edit_button.set_sensitive(True)
216                        self.up_button.set_sensitive(False)
217                        self.down_button.set_sensitive(False)
218
219                if len(model) == 0:
220                        self.clear_button.set_sensitive(False)
221                else:
222                        self.clear_button.set_sensitive(True)
223
224        def on_clear_button_clicked(self, widget):
225                self.records_treeview.get_model().clear()
226
227        def on_remove_button_clicked(self, widget):
228                selection = self.records_treeview.get_selection()
229                model, rowrefs = selection.get_selected_rows()  # rowref is a list of paths
230                for i in xrange(len(rowrefs)):
231                        rowrefs[i] = gtk.TreeRowReference(model, rowrefs[i])
232                # rowref is a list of row references; need to convert because we will modify the model,
233                # paths would change
234                for rowref in rowrefs:
235                        del model[rowref.get_path()]
236       
237        def on_up_button_clicked(self, widget):
238                selection = self.records_treeview.get_selection()
239                model, (path,) = selection.get_selected_rows()
240                iter = model.get_iter(path)
241                previter = model.get_iter((path[0]-1,)) # constructing path for previous iter
242                model.swap(iter, previter)
243
244                self.refresh_multiple_buttons()
245
246        def on_down_button_clicked(self, widget):
247                selection = self.records_treeview.get_selection()
248                model, (path,) = selection.get_selected_rows()
249                iter = model.get_iter(path)
250                nextiter = model.iter_next(iter)
251                model.swap(iter, nextiter)
252
253                self.refresh_multiple_buttons()
254
255        def on_records_selection_changed(self, widget):
256                self.refresh_multiple_buttons()
257
258class SingleForm(gtk.Table, object):
259        """ Widget that represent DATAFORM_SINGLE mode form. Because this is used
260        not only to display single forms, but to form input windows of multiple-type
261        forms, it is in another class."""
262        def __init__(self, dataform):
263                assert isinstance(dataform, dataforms.SimpleDataForm)
264
265                gtk.Table.__init__(self)
266                self.set_col_spacings(12)
267                self.set_row_spacings(6)
268
269                self.tooltips = gtk.Tooltips()
270
271                def decorate_with_tooltip(widget, field):
272                        """ Adds a tooltip containing field's description to a widget.
273                        Creates EventBox if widget doesn't have its own gdk window.
274                        Returns decorated widget. """
275                        if field.description!='':
276                                if widget.flags()&gtk.NO_WINDOW:
277                                        evbox = gtk.EventBox()
278                                        evbox.add(widget)
279                                        widget = evbox
280                                self.tooltips.set_tip(widget, field.description)
281                        return widget
282
283                self._data_form = dataform
284
285                # building widget
286                linecounter = 0
287
288                # is the form changeable?
289                readwrite = dataform.type != 'result'
290
291                # for each field...
292                for field in self._data_form.iter_fields():
293                        if field.type=='hidden': continue
294
295                        commonlabel = True
296                        commonlabelcenter = False
297                        commonwidget = True
298                        widget = None
299
300                        if field.type=='boolean':
301                                commonlabelcenter = True
302                                widget = gtk.CheckButton()
303                                widget.connect('toggled', self.on_boolean_checkbutton_toggled, field)
304                                widget.set_active(field.value)
305
306                        elif field.type=='fixed':
307                                leftattach = 1
308                                rightattach = 2
309                                if field.label is None:
310                                        commonlabel = False
311                                        leftattach = 0
312                               
313                                commonwidget=False
314                                widget = gtk.Label(field.value)
315                                widget.set_line_wrap(True)
316                                self.attach(widget, leftattach, rightattach, linecounter, linecounter+1,
317                                        xoptions=gtk.FILL, yoptions=gtk.FILL)
318
319                        elif field.type == 'list-single':
320                                # TODO: When more than few choices, make a list
321                                # TODO: Think of moving that to another function (it could be used
322                                # TODO: in stage2 of adhoc commands too).
323                                # TODO: What if we have radio buttons and non-required field?
324                                # TODO: We cannot deactivate them all...
325                                widget = gtk.VBox()
326                                first_radio = None
327                                for value, label in field.iter_options():
328                                        radio = gtk.RadioButton(first_radio, label=label)
329                                        radio.connect('toggled', self.on_list_single_radiobutton_toggled,
330                                                field, value)
331                                        if first_radio is None:
332                                                first_radio = radio
333                                                if field.value=='':     # TODO: is None when done
334                                                        field.value = value
335                                        if value == field.value:
336                                                radio.set_active(True)
337                                        widget.set_sensitive(readwrite)
338                                        widget.pack_start(radio, expand=False)
339
340                        elif field.type == 'list-multi':
341                                # TODO: When more than few choices, make a list
342                                widget = gtk.VBox()
343                                for value, label in field.iter_options():
344                                        check = gtk.CheckButton(label, use_underline=False)
345                                        check.set_active(value in field.values)
346                                        check.connect('toggled', self.on_list_multi_checkbutton_toggled,
347                                                field, value)
348                                        widget.set_sensitive(readwrite)
349                                        widget.pack_start(check, expand=False)
350
351                        elif field.type == 'jid-single':
352                                widget = gtk.Entry()
353                                widget.connect('changed', self.on_text_single_entry_changed, field)
354                                widget.set_text(field.value)
355
356                        elif field.type == 'jid-multi':
357                                commonwidget = False
358
359                                xml = gtkgui_helpers.get_glade('data_form_window.glade', 'item_list_table')
360                                widget = xml.get_widget('item_list_table')
361                                treeview = xml.get_widget('item_treeview')
362
363                                listmodel = gtk.ListStore(str)
364                                for value in field.iter_values():
365                                        # nobody will create several megabytes long stanza
366                                        listmodel.insert(999999, (value,))
367
368                                treeview.set_model(listmodel)
369
370                                renderer = gtk.CellRendererText()
371                                renderer.set_property('editable', True)
372                                renderer.connect('edited',
373                                        self.on_jid_multi_cellrenderertext_edited, listmodel, field)
374
375                                treeview.append_column(gtk.TreeViewColumn(None, renderer,
376                                        text=0))
377
378                                decorate_with_tooltip(treeview, field)
379
380                                add_button=xml.get_widget('add_button')
381                                add_button.connect('clicked',
382                                        self.on_jid_multi_add_button_clicked, treeview, listmodel, field)
383                                edit_button=xml.get_widget('edit_button')
384                                edit_button.connect('clicked',
385                                        self.on_jid_multi_edit_button_clicked, treeview)
386                                remove_button=xml.get_widget('remove_button')
387                                remove_button.connect('clicked',
388                                        self.on_jid_multi_remove_button_clicked, treeview, field)
389                                clear_button=xml.get_widget('clear_button')
390                                clear_button.connect('clicked',
391                                        self.on_jid_multi_clean_button_clicked, listmodel, field)
392                                if not readwrite:
393                                        add_button.set_no_show_all(True)
394                                        edit_button.set_no_show_all(True)
395                                        remove_button.set_no_show_all(True)
396                                        clear_button.set_no_show_all(True)
397
398                                widget.set_sensitive(readwrite)
399                                self.attach(widget, 1, 2, linecounter, linecounter+1)
400
401                                del xml
402
403                        elif field.type == 'text-private':
404                                commonlabelcenter = True
405                                widget = gtk.Entry()
406                                widget.connect('changed', self.on_text_single_entry_changed, field)
407                                widget.set_visibility(False)
408                                widget.set_text(field.value)
409
410                        elif field.type == 'text-multi':
411                                # TODO: bigger text view
412                                commonwidget = False
413
414                                textwidget = gtk.TextView()
415                                textwidget.set_wrap_mode(gtk.WRAP_WORD)
416                                textwidget.get_buffer().connect('changed', self.on_text_multi_textbuffer_changed,
417                                        field)
418                                textwidget.get_buffer().set_text(field.value)
419                               
420                                widget = gtk.ScrolledWindow()
421                                widget.add(textwidget)
422
423                                widget.set_sensitive(readwrite)
424                                widget=decorate_with_tooltip(widget, field)
425                                self.attach(widget, 1, 2, linecounter, linecounter+1)
426
427                        else:# field.type == 'text-single' or field.type is nonstandard:
428                                # JEP says that if we don't understand some type, we
429                                # should handle it as text-single
430                                commonlabelcenter = True
431                                if readwrite:
432                                        widget = gtk.Entry()
433                                        widget.connect('changed', self.on_text_single_entry_changed, field)
434                                        widget.set_sensitive(readwrite)
435                                        if field.value is None:
436                                                field.value = u''
437                                        widget.set_text(field.value)
438                                else:
439                                        commonwidget=False
440                                        widget = gtk.Label(field.value)
441                                        widget.set_sensitive(True)
442                                        widget.set_alignment(0.0, 0.5)
443                                        widget=decorate_with_tooltip(widget, field)
444                                        self.attach(widget, 1, 2, linecounter, linecounter+1,
445                                                yoptions=gtk.FILL)
446
447                        if commonlabel and field.label is not None:
448                                label = gtk.Label(field.label)
449                                if commonlabelcenter:
450                                        label.set_alignment(0.0, 0.5)
451                                else:
452                                        label.set_alignment(0.0, 0.0)
453                                label = decorate_with_tooltip(label, field)
454                                self.attach(label, 0, 1, linecounter, linecounter+1,
455                                        xoptions=gtk.FILL, yoptions=gtk.FILL)
456
457                        if commonwidget:
458                                assert widget is not None
459                                widget.set_sensitive(readwrite)
460                                widget = decorate_with_tooltip(widget, field)
461                                self.attach(widget, 1, 2, linecounter, linecounter+1,
462                                        yoptions=gtk.FILL)
463                        widget.show_all()
464
465                        linecounter+=1
466                if self.get_property('visible'):
467                        self.show_all()
468
469        def show(self):
470                # simulate that we are one widget
471                self.show_all()
472
473        def on_boolean_checkbutton_toggled(self, widget, field):
474                field.value = widget.get_active()
475
476        def on_list_single_radiobutton_toggled(self, widget, field, value):
477                field.value = value
478
479        def on_list_multi_checkbutton_toggled(self, widget, field, value):
480                # TODO: make some methods like add_value and remove_value
481                if widget.get_active() and value not in field.values:
482                        field.values += [value]
483                elif not widget.get_active() and value in field.values:
484                        field.values = [v for v in field.values if v!=value]
485
486        def on_text_single_entry_changed(self, widget, field):
487                field.value = widget.get_text()
488
489        def on_text_multi_textbuffer_changed(self, widget, field):
490                field.value = widget.get_text(
491                        widget.get_start_iter(),
492                        widget.get_end_iter())
493
494        def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, model, field):
495                old=model[path][0]
496                model[path][0]=newtext
497
498                values = field.values
499                values[values.index(old)]=newtext
500                field.values = values
501
502        def on_jid_multi_add_button_clicked(self, widget, treeview, model, field):
503                iter = model.insert(999999, ("new@jabber.id",))
504                treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True)
505                field.values = field.values + ["new@jabber.id"]
506
507        def on_jid_multi_edit_button_clicked(self, widget, treeview):
508                model, iter = treeview.get_selection().get_selected()
509                assert iter is not None
510
511                treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True)
512
513        def on_jid_multi_remove_button_clicked(self, widget, treeview, field):
514                selection = treeview.get_selection()
515                model = treeview.get_model()
516                deleted = []
517
518                def remove(model, path, iter, deleted):
519                        deleted+=model[iter]
520                        model.remove(iter)
521
522                selection.selected_foreach(remove, deleted)
523                field.values = (v for v in field.values if v not in deleted)
524
525        def on_jid_multi_clean_button_clicked(self, widget, model, field):
526                model.clear()
527                del field.values
Note: See TracBrowser for help on using the browser.