root/tags/gajim-0.11.4/src/dataforms_widget.py

Revision 8990, 17.8 kB (checked in by asterix, 10 months ago)

fix jid-multi problems (only one row with a given jid is allowed)

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