root/trunk/src/dataforms_widget.py

Revision 10549, 18.4 kB (checked in by asterix, 6 weeks ago)

revert thorstenp patches for now. They introduce bugs.

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