diff --git a/data/glade/data_form_window.glade b/data/glade/data_form_window.glade index 1c296f137..06dc44d35 100644 --- a/data/glade/data_form_window.glade +++ b/data/glade/data_form_window.glade @@ -183,7 +183,7 @@ - + True window1 GTK_WINDOW_TOPLEVEL @@ -412,4 +412,398 @@ + + True + window1 + GTK_WINDOW_TOPLEVEL + GTK_WIN_POS_NONE + False + True + False + True + False + False + GDK_WINDOW_TYPE_HINT_NORMAL + GDK_GRAVITY_NORTH_WEST + True + + + + 3 + True + False + 3 + + + + True + Fill in the form. + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + 0 + False + False + + + + + + True + + + 0 + False + True + + + + + + True + True + True + True + GTK_POS_TOP + False + False + + + + True + True + GTK_POLICY_AUTOMATIC + GTK_POLICY_AUTOMATIC + GTK_SHADOW_NONE + GTK_CORNER_TOP_LEFT + + + + True + GTK_SHADOW_IN + + + + + + + + + False + True + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + True + False + 0 + + + + True + True + GTK_POLICY_ALWAYS + GTK_POLICY_ALWAYS + GTK_SHADOW_IN + GTK_CORNER_TOP_LEFT + + + + True + True + True + False + False + True + False + False + False + + + + + 0 + True + True + + + + + + True + False + 0 + + + + True + True + gtk-add + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + True + True + gtk-remove + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + True + + + 3 + False + False + + + + + + True + True + gtk-edit + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + True + + + 3 + False + True + + + + + + True + True + gtk-go-up + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + True + True + gtk-go-down + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + + True + + + 3 + False + True + + + + + + True + True + gtk-clear + True + GTK_RELIEF_NORMAL + True + + + + 0 + False + False + + + + + 0 + False + True + + + + + False + True + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + + + + + + True + + False + False + GTK_JUSTIFY_LEFT + False + False + 0.5 + 0.5 + 0 + 0 + PANGO_ELLIPSIZE_NONE + -1 + False + 0 + + + tab + + + + + 0 + True + True + + + + + + diff --git a/src/adhoc_commands.py b/src/adhoc_commands.py index 5d5fd936f..c682b3317 100644 --- a/src/adhoc_commands.py +++ b/src/adhoc_commands.py @@ -30,7 +30,7 @@ from common import xmpp, gajim, dataforms import gtkgui_helpers import dialogs -import dataforms as dataformwidget +import dataforms_widget class CommandWindow: '''Class for a window for single ad-hoc commands session. Note, that @@ -70,7 +70,7 @@ class CommandWindow: self.__dict__[name] = self.xml.get_widget(name) # creating data forms widget - self.data_form_widget = dataformwidget.DataFormWidget() + self.data_form_widget = dataforms_widget.DataFormWidget() self.data_form_widget.show() self.sending_form_stage_vbox.pack_start(self.data_form_widget) @@ -497,14 +497,18 @@ class CommandWindow: def send_cancel(self): '''Send the command with action='cancel'. ''' assert self.commandnode is not None - assert self.sessionid is not None - - stanza = xmpp.Iq(typ='set', to=self.jid) - stanza.addChild('command', attrs={ - 'xmlns':xmpp.NS_COMMANDS, - 'node':self.commandnode, - 'sessionid':self.sessionid, - 'action':'cancel' - }) - - self.account.connection.send(stanza) + if self.sessionid is not None: + # we already have sessionid, so the service sent at least one reply. + stanza = xmpp.Iq(typ='set', to=self.jid) + stanza.addChild('command', attrs={ + 'xmlns':xmpp.NS_COMMANDS, + 'node':self.commandnode, + 'sessionid':self.sessionid, + 'action':'cancel' + }) + + self.account.connection.send(stanza) + else: + # we did not received any reply from service; TODO: we should wait and + # then send cancel; for now we do nothing + pass diff --git a/src/common/dataforms.py b/src/common/dataforms.py index c9ae561b4..476b855c1 100644 --- a/src/common/dataforms.py +++ b/src/common/dataforms.py @@ -64,13 +64,14 @@ class DataForm(xmpp.Node, object): You can also set 'tofill' to DataForm to get a form of type 'submit' with the same fields as in the form given, but without unnecessary data like instructions or title. Also the type will be set to 'submit' by default.""" - assert (isinstance(node, xmpp.Node) or node is None) - assert (isinstance(tofill, DataForm) or tofill is None) - assert not (node is not None and tofill is not None) + # assert that exactly one of fields 'node' and 'tofill' is filled + assert node is None or isinstance(node, xmpp.Node) + assert tofill is None or isinstance(tofill, xmpp.Node) + assert node is None or tofill is None assert typ in (None, 'form', 'submit', 'cancel', 'result') - assert isinstance(title, basestring) or title is None - assert isinstance(instructions, basestring) or instructions is None + assert title is None or isinstance(title, basestring) + assert instructions is None or isinstance(instructions, basestring) assert (fields is None or fields.__iter__) xmpp.Node.__init__(self, 'x', node=node) @@ -186,21 +187,14 @@ class DataForm(xmpp.Node, object): if self.mode is DATAFORM_SINGLE: yield DataRecord(node=self) else: - for item in self.getChildren(): - if not isinstance(item, xmpp.Node): continue - if not item.getName()=='item': continue - yield DataRecord(node=item) + for i in iter_elements(self, 'item'): + yield i def get_records(self): if self.mode is DATAFORM_SINGLE: return [DataRecord(node=self),] else: - items = [] - for node in self.getChildren(): - if not isinstance(node, xmpp.Node): continue - if not node.getName()=='item': continue - items.append(DataRecord(node=node)) - return items + return list(iter_elements(self, 'item')) def set_records(self, records): if self.mode is DATAFORM_SINGLE: @@ -211,10 +205,15 @@ class DataForm(xmpp.Node, object): for field in record.iter_fields(): self[field.var]=field.value else: - self.del_records(self) + self.del_records() for record in records: assert isinstance(record, dict) - newitem = self.addChild('item', node=record) + newitem = self.addChild('item') + for key, value in record.iteritems(): + newitem.addChild(node=DataField( + var=key, + value=value, + typ=self.get_field(key).type)) def del_records(self): if self.mode is DATAFORM_SINGLE: @@ -229,6 +228,22 @@ class DataForm(xmpp.Node, object): """Records kept in this form; if in DATAFORM_SINGLE mode, there will be exactly one record, otherwise there might be more or less records.""") + def iter_fields(self): + if self.mode is DATAFORM_SINGLE: + container = self + else: + container = self.getTag("recorded") + + for child in container.getChildren(): + if isinstance(child, DataField): + yield child + + def get_field(self, fieldvar): + ''' Find DataField that has fieldvar as var. ''' + for field in self.iter_fields(): + if field.var == fieldvar: return field + raise KeyError, "This form does not contain %r field." % fieldvar + def get_fields(self): if self.mode is DATAFORM_SINGLE: container = self @@ -260,16 +275,6 @@ class DataForm(xmpp.Node, object): except ValueError: pass - def iter_fields(self): - if self.mode is DATAFORM_SINGLE: - container = self - else: - container = self.getTag("recorded") - - for child in container.getChildren(): - if isinstance(child, DataField): - yield child - fields = property(get_fields, set_fields, del_fields, """Fields in this form; a list; if in DATAFORM_SINGLE mode, you should not set their values directly.""") diff --git a/src/dataforms.py b/src/dataforms.py deleted file mode 100644 index 0047a3b93..000000000 --- a/src/dataforms.py +++ /dev/null @@ -1,347 +0,0 @@ -## dataforms.py -## -## Copyright (C) 2003-2006 Yann Le Boulanger -## Copyright (C) 2005-2006 Nikos Kouremenos -## Copyright (C) 2005 Dimitur Kirov -## Copyright (C) 2003-2005 Vincent Hanquez -## -## This program is free software; you can redistribute it and/or modify -## it under the terms of the GNU General Public License as published -## by the Free Software Foundation; version 2 only. -## -## This program is distributed in the hope that it will be useful, -## but WITHOUT ANY WARRANTY; without even the implied warranty of -## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -## GNU General Public License for more details. -## -""" This module contains widget that can display data form (JEP-0004). """ - -# TODO: forms of type='result' should be read-only - -import gtk -import pango - -import gtkgui_helpers - -import common.xmpp as xmpp -import common.dataforms as dataforms - -class DataFormWidget(gtk.Alignment, object): -# "public" interface - """ Data Form widget. Use like any other widget. """ - def __init__(self, dataformnode=None): - """ Create a widget. """ - gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) - - self._data_form = None - - self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_scrolledwindow') - self.instructions = self.xml.get_widget('form_instructions_label') - self.separator = self.xml.get_widget('form_instructions_hseparator') - self.container = self.xml.get_widget('container_vbox') - - self.add(self.xml.get_widget('data_form_scrolledwindow')) - - if dataformnode is not None: - self.set_data_form(dataformnode) - - def set_data_form(self, dataform=None): - """ Set the data form (xmpp.DataForm) displayed in widget. - Set to None to erase the form. """ - assert isinstance(dataform, dataforms.DataForm) - - self.del_data_form() - self._data_form = dataform - if dataform.mode==dataforms.DATAFORM_SINGLE: - self.form = self.__class__.SingleForm(dataform) - else: - self.form = self.__class__.MultipleForm(dataform) - self.form.show() - self.container.pack_end(self.form, expand=True, fill=True) - - if dataform.instructions is None: - self.instructions.set_no_show_all(True) - self.instructions.hide() - self.separator.set_no_show_all(True) - self.separator.hide() - else: - self.instructions.set_text(dataform.instructions) - self.instructions.set_no_show_all(False) - self.instructions.show() - self.separator.set_no_show_all(False) - self.separator.show() - - def get_data_form(self): - """ Data form displayed in the widget or None if no form. """ - return self._data_form - - def del_data_form(self): - if self._data_form is not None: - self.container.remove(self.form) - self.form = None - self._data_form = None - - data_form = property(get_data_form, set_data_form, del_data_form, - "Data form presented in a widget") - - def get_title(self): - """ Get the title of data form, as a unicode object. If no - title or no form, returns u''. Useful for setting window title. """ - if self._data_form is not None: - if self._data_form.has_key('title'): - return self._data_form['title'].encode('utf-8') - return u'' - - title = property(get_title, None, None, "Data form title") - - def show(self): - """ Treat 'us' as one widget. """ - self.show_all() - -# "private" methods - -# we have actually two different kinds of data forms: one is a simple form to fill, -# second is a table with several records; we will treat second as read-only, but still -# we should have a way to show it - -# we will place both types in two different private classes, so the code will be clean; -# both will have the same interface - - class SingleForm(gtk.Table, object): - """ Widget that represent DATAFORM_SINGLE mode form. """ - def __init__(self, dataform): - assert dataform.mode==dataforms.DATAFORM_SINGLE - - gtk.Table.__init__(self) - self.set_col_spacings(6) - self.set_row_spacings(6) - - self._data_form = dataform - - # building widget - linecounter = 0 - - # for each field... - for field in self._data_form.iter_fields(): - if field.type=='hidden': continue - - commonlabel = True - commondesc = True - commonwidget = True - widget = None - - if field.type=='boolean': - widget = gtk.CheckButton() - widget.connect('toggled', self.on_boolean_checkbutton_toggled, field) - widget.set_active(field.value) - - elif field.type=='fixed': - leftattach = 1 - rightattach = 2 - if field.label is None: - commonlabel = False - leftattach = 0 - if field.description is None: - commondesc = False - rightattach = 3 - - commonwidget=False - widget = gtk.Label(field.value) - widget.set_line_wrap(True) - self.attach(widget, leftattach, rightattach, linecounter, linecounter+1, - xoptions=gtk.FILL, yoptions=gtk.FILL) - - elif field.type == 'list-single': - # TODO: When more than few choices, make a list - # TODO: Think of moving that to another function (it could be used - # TODO: in stage2 of adhoc commands too). - # TODO: What if we have radio buttons and non-required field? - # TODO: We cannot deactivate them all... - widget = gtk.VBox() - first_radio = None - for value, label in field.iter_options(): - radio = gtk.RadioButton(first_radio, label=label) - radio.connect('toggled', self.on_list_single_radiobutton_toggled, - field, value) - if first_radio is None: - first_radio = radio - if field.value is None: - field.value = value - if value == field.value: - radio.set_active(True) - widget.pack_start(radio, expand=False) - - elif field.type == 'list-multi': - # TODO: When more than few choices, make a list - widget = gtk.VBox() - for value, label in field.iter_options(): - check = gtk.CheckButton(label, use_underline=False) - check.set_active(value in field.value) - check.connect('toggled', self.on_list_multi_checkbutton_toggled, - field, value) - widget.pack_start(check, expand=False) - - elif field.type == 'jid-single': - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, field) - if field.value is None: - field.value = u'' - widget.set_text(field.value) - - elif field.type == 'jid-multi': - commonwidget = False - - xml = gtkgui_helpers.get_glade('data_form_window.glade', 'item_list_table') - widget = xml.get_widget('item_list_table') - treeview = xml.get_widget('item_treeview') - - listmodel = gtk.ListStore(str) - for value in field.iter_values(): - # nobody will create several megabytes long stanza - listmodel.insert(999999, (value,)) - - treeview.set_model(listmodel) - - renderer = gtk.CellRendererText() - renderer.set_property('editable', True) - renderer.connect('edited', - self.on_jid_multi_cellrenderertext_edited, listmodel, field) - - treeview.append_column(gtk.TreeViewColumn(None, renderer, - text=0)) - - xml.get_widget('add_button').connect('clicked', - self.on_jid_multi_add_button_clicked, treeview, listmodel, field) - xml.get_widget('edit_button').connect('clicked', - self.on_jid_multi_edit_button_clicked, treeview) - xml.get_widget('remove_button').connect('clicked', - self.on_jid_multi_remove_button_clicked, treeview, field) - xml.get_widget('clear_button').connect('clicked', - self.on_jid_multi_clean_button_clicked, listmodel, field) - - self.attach(widget, 1, 2, linecounter, linecounter+1) - - del xml - - elif field.type == 'text-private': - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, field) - widget.set_visibility(False) - if field.value is None: - field.value = u'' - widget.set_text(field.value) - - elif field.type == 'text-multi': - # TODO: bigger text view - commonwidget = False - - textwidget = gtk.TextView() - textwidget.set_wrap_mode(gtk.WRAP_WORD) - textwidget.get_buffer().connect('changed', self.on_text_multi_textbuffer_changed, - field) - if field.value is None: - field.value = u'' - textwidget.get_buffer().set_text(field.value) - - widget = gtk.ScrolledWindow() - widget.add(textwidget) - - self.attach(widget, 1, 2, linecounter, linecounter+1) - - else:# field.type == 'text-single' or field.type is nonstandard: - # JEP says that if we don't understand some type, we - # should handle it as text-single - widget = gtk.Entry() - widget.connect('changed', self.on_text_single_entry_changed, field) - if field.value is None: - field.value = u'' - widget.set_text(field.value) - - if commonlabel and field.label is not None: - label = gtk.Label(field.label) - label.set_alignment(1.0, 0.5) - self.attach(label, 0, 1, linecounter, linecounter+1, - xoptions=gtk.FILL, yoptions=gtk.FILL) - - if commonwidget: - assert widget is not None - self.attach(widget, 1, 2, linecounter, linecounter+1, - yoptions=gtk.FILL) - widget.show_all() - - if commondesc and field.description is not None: - label = gtk.Label() - label.set_markup(''+\ - gtkgui_helpers.escape_for_pango_markup(field.description)+\ - '') - label.set_line_wrap(True) - self.attach(label, 2, 3, linecounter, linecounter+1, - xoptions=gtk.FILL|gtk.SHRINK, yoptions=gtk.FILL|gtk.SHRINK) - - linecounter+=1 - if self.get_property('visible'): - self.show_all() - - def show(self): - # simulate that we are one widget - self.show_all() - - def on_boolean_checkbutton_toggled(self, widget, field): - field.value = widget.get_active() - - def on_list_single_radiobutton_toggled(self, widget, field, value): - field.value = value - - def on_list_multi_checkbutton_toggled(self, widget, field, value): - # TODO: make some methods like add_value and remove_value - if widget.get_active() and value not in field.value: - field.value += [value] - elif not widget.get_active() and value in field.value: - field.value = [v for v in field.value if v!=value] - - def on_text_single_entry_changed(self, widget, field): - field.value = widget.get_text() - - def on_text_multi_textbuffer_changed(self, widget, field): - field.value = widget.get_text( - widget.get_start_iter(), - widget.get_end_iter()) - - def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, model, field): - old=model[path][0] - model[path][0]=newtext - - values = field.value - values[values.index(old)]=newtext - field.value = values - - def on_jid_multi_add_button_clicked(self, widget, treeview, model, field): - iter = model.insert(999999, ("new@jabber.id",)) - treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True) - field.value = field.value + ["new@jabber.id"] - - def on_jid_multi_edit_button_clicked(self, widget, treeview): - model, iter = treeview.get_selection().get_selected() - assert iter is not None - - treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True) - - def on_jid_multi_remove_button_clicked(self, widget, treeview, field): - selection = treeview.get_selection() - model = treeview.get_model() - deleted = [] - - def remove(model, path, iter, deleted): - deleted+=model[iter] - model.remove(iter) - - selection.selected_foreach(remove, deleted) - field.value = (v for v in field.value if v not in deleted) - - def on_jid_multi_clean_button_clicked(self, widget, model, field): - model.clear() - del field.value - - class MultipleForm(gtk.Alignment, object): - def __init__(self, dataform): - assert dataform.mode==dataforms.DATAFORM_MULTIPLE diff --git a/src/dataforms_widget.py b/src/dataforms_widget.py new file mode 100644 index 000000000..d0fae0c6c --- /dev/null +++ b/src/dataforms_widget.py @@ -0,0 +1,417 @@ +## dataforms.py +## +## Copyright (C) 2003-2006 Yann Le Boulanger +## Copyright (C) 2005-2006 Nikos Kouremenos +## Copyright (C) 2005 Dimitur Kirov +## Copyright (C) 2003-2005 Vincent Hanquez +## +## This program is free software; you can redistribute it and/or modify +## it under the terms of the GNU General Public License as published +## by the Free Software Foundation; version 2 only. +## +## This program is distributed in the hope that it will be useful, +## but WITHOUT ANY WARRANTY; without even the implied warranty of +## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +## GNU General Public License for more details. +## +""" This module contains widget that can display data form (JEP-0004). +Words single and multiple refers here to types of data forms: +single means these with one record of data (without element), +multiple - these that may contain more data (with element).""" + +# TODO: forms of type='result' should be read-only +# TODO: remove tabs from dialog + +import gtk +import pango + +import gtkgui_helpers + +import common.xmpp as xmpp +import common.dataforms as dataforms + +class DataFormWidget(gtk.Alignment, object): +# "public" interface + """ Data Form widget. Use like any other widget. """ + def __init__(self, dataformnode=None): + """ Create a widget. """ + gtk.Alignment.__init__(self, xscale=1.0, yscale=1.0) + + self._data_form = None + + self.xml=gtkgui_helpers.get_glade('data_form_window.glade', 'data_form_vbox') + for name in ('instructions_label', 'instructions_hseparator', + 'single_form_viewport', 'data_form_types_notebook', + 'single_form_scrolledwindow', 'multiple_form_hbox', + 'records_treeview', 'up_button', 'down_button', 'clear_button'): + self.__dict__[name] = self.xml.get_widget(name) + + self.add(self.xml.get_widget('data_form_vbox')) + + if dataformnode is not None: + self.set_data_form(dataformnode) + + def set_data_form(self, dataform=None): + """ Set the data form (xmpp.DataForm) displayed in widget. + Set to None to erase the form. """ + assert isinstance(dataform, dataforms.DataForm) + + self.del_data_form() + self._data_form = dataform + if dataform.mode==dataforms.DATAFORM_SINGLE: + self.build_single_data_form() + else: + self.build_multiple_data_form() + + # create appropriate description for instructions field if there isn't any + if dataform.instructions is None: + if dataform.type=='result': + # form is single + instructions = _('This is result of query.') + else: + # form is writable + if dataform.mode==dataforms.DATAFORM_SINGLE: + instructions = _('Fill in the form.') + else: + instructions = _('Edit items on the list') + else: + instructions = dataform.instructions + + self.instructions_label.set_text(dataform.instructions) + + def get_data_form(self): + """ Data form displayed in the widget or None if no form. """ + return self._data_form + + def del_data_form(self): + self.clean_data_form() + self._data_form = None + + data_form = property(get_data_form, set_data_form, del_data_form, + "Data form presented in a widget") + + def get_title(self): + """ Get the title of data form, as a unicode object. If no + title or no form, returns u''. Useful for setting window title. """ + if self._data_form is not None: + if self._data_form.has_key('title'): + # TODO: encode really needed? check this + return self._data_form['title'].encode('utf-8') + return u'' + + title = property(get_title, None, None, "Data form title") + + def show(self): + """ Treat 'us' as one widget. """ + self.show_all() + +# "private" methods + +# we have actually two different kinds of data forms: one is a simple form to fill, +# second is a table with several records; + + def clean_data_form(self): + '''Remove data about existing form. This metod is empty, because + it is rewritten by build_*_data_form, according to type of form + which is actually displayed.''' + pass + + def build_single_data_form(self): + '''Invoked when new single form is to be created.''' + assert self._data_form.mode==dataforms.DATAFORM_SINGLE + + self.clean_data_form() + + self.singleform = SingleForm(self._data_form) + self.singleform.show() + self.single_form_viewport.add(self.singleform) + self.data_form_types_notebook.set_current_page( + self.data_form_types_notebook.page_num( + self.single_form_scrolledwindow)) + + self.clean_data_form = self.clean_single_data_form + + def clean_single_data_form(self): + '''(Called as clean_data_form, read the docs of clean_data_form()). + Remove form from widget.''' + self.singleform.destroy() + del self.singleform + + def build_multiple_data_form(self): + '''Invoked when new multiple form is to be created.''' + assert self._data_form.mode==dataforms.DATAFORM_MULTIPLE + + self.clean_data_form() + + # creating model for form... + fieldtypes = [] + for field in self._data_form.iter_fields(): + # note: we store also text-private and hidden fields, + # we just do not display them. + # TODO: boolean fields + #elif field.type=='boolean': fieldtypes.append(bool) + fieldtypes.append(unicode) + + self.multiplemodel = gtk.ListModel(*fieldtypes) + + # moving all data to model + for item in self._data_form.iter_records(): + self.multiplemodel.append(field.value for field in item.iter_fields()) + + # contructing columns... + for field in self._data_form.iter_fields(): + self.records_treeview.append( + gtk.TreeViewColumn( + title=field.label, + cell_renderer=gtk.CellRendererText(), + text=field.value)) + + self.records.show_all() + + self.data_form_types_notebook.set_current_page( + self.data_form_types_notebook.page_num( + self.multiple_form_hbox)) + + self.clean_data_form = self.clean_multiple_data_form + + def clean_multiple_data_form(self): + '''(Called as clean_data_form, read the docs of clean_data_form()). + Remove form from widget.''' + pass + +class SingleForm(gtk.Table, object): + """ Widget that represent DATAFORM_SINGLE mode form. Because this is used + not only to display single forms, but to form input windows of multiple-type + forms, it is in another class.""" + def __init__(self, dataform): + assert dataform.mode==dataforms.DATAFORM_SINGLE + + gtk.Table.__init__(self) + self.set_col_spacings(6) + self.set_row_spacings(6) + + self._data_form = dataform + + # building widget + linecounter = 0 + + # for each field... + for field in self._data_form.iter_fields(): + if field.type=='hidden': continue + + commonlabel = True + commondesc = True + commonwidget = True + widget = None + + if field.type=='boolean': + widget = gtk.CheckButton() + widget.connect('toggled', self.on_boolean_checkbutton_toggled, field) + widget.set_active(field.value) + + elif field.type=='fixed': + leftattach = 1 + rightattach = 2 + if field.label is None: + commonlabel = False + leftattach = 0 + if field.description is None: + commondesc = False + rightattach = 3 + + commonwidget=False + widget = gtk.Label(field.value) + widget.set_line_wrap(True) + self.attach(widget, leftattach, rightattach, linecounter, linecounter+1, + xoptions=gtk.FILL, yoptions=gtk.FILL) + + elif field.type == 'list-single': + # TODO: When more than few choices, make a list + # TODO: Think of moving that to another function (it could be used + # TODO: in stage2 of adhoc commands too). + # TODO: What if we have radio buttons and non-required field? + # TODO: We cannot deactivate them all... + widget = gtk.VBox() + first_radio = None + for value, label in field.iter_options(): + radio = gtk.RadioButton(first_radio, label=label) + radio.connect('toggled', self.on_list_single_radiobutton_toggled, + field, value) + if first_radio is None: + first_radio = radio + if field.value is None: + field.value = value + if value == field.value: + radio.set_active(True) + widget.pack_start(radio, expand=False) + + elif field.type == 'list-multi': + # TODO: When more than few choices, make a list + widget = gtk.VBox() + for value, label in field.iter_options(): + check = gtk.CheckButton(label, use_underline=False) + check.set_active(value in field.value) + check.connect('toggled', self.on_list_multi_checkbutton_toggled, + field, value) + widget.pack_start(check, expand=False) + + elif field.type == 'jid-single': + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + if field.value is None: + field.value = u'' + widget.set_text(field.value) + + elif field.type == 'jid-multi': + commonwidget = False + + xml = gtkgui_helpers.get_glade('data_form_window.glade', 'item_list_table') + widget = xml.get_widget('item_list_table') + treeview = xml.get_widget('item_treeview') + + listmodel = gtk.ListStore(str) + for value in field.iter_values(): + # nobody will create several megabytes long stanza + listmodel.insert(999999, (value,)) + + treeview.set_model(listmodel) + + renderer = gtk.CellRendererText() + renderer.set_property('editable', True) + renderer.connect('edited', + self.on_jid_multi_cellrenderertext_edited, listmodel, field) + + treeview.append_column(gtk.TreeViewColumn(None, renderer, + text=0)) + + xml.get_widget('add_button').connect('clicked', + self.on_jid_multi_add_button_clicked, treeview, listmodel, field) + xml.get_widget('edit_button').connect('clicked', + self.on_jid_multi_edit_button_clicked, treeview) + xml.get_widget('remove_button').connect('clicked', + self.on_jid_multi_remove_button_clicked, treeview, field) + xml.get_widget('clear_button').connect('clicked', + self.on_jid_multi_clean_button_clicked, listmodel, field) + + self.attach(widget, 1, 2, linecounter, linecounter+1) + + del xml + + elif field.type == 'text-private': + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + widget.set_visibility(False) + if field.value is None: + field.value = u'' + widget.set_text(field.value) + + elif field.type == 'text-multi': + # TODO: bigger text view + commonwidget = False + + textwidget = gtk.TextView() + textwidget.set_wrap_mode(gtk.WRAP_WORD) + textwidget.get_buffer().connect('changed', self.on_text_multi_textbuffer_changed, + field) + if field.value is None: + field.value = u'' + textwidget.get_buffer().set_text(field.value) + + widget = gtk.ScrolledWindow() + widget.add(textwidget) + + self.attach(widget, 1, 2, linecounter, linecounter+1) + + else:# field.type == 'text-single' or field.type is nonstandard: + # JEP says that if we don't understand some type, we + # should handle it as text-single + widget = gtk.Entry() + widget.connect('changed', self.on_text_single_entry_changed, field) + if field.value is None: + field.value = u'' + widget.set_text(field.value) + + if commonlabel and field.label is not None: + label = gtk.Label(field.label) + label.set_alignment(1.0, 0.5) + self.attach(label, 0, 1, linecounter, linecounter+1, + xoptions=gtk.FILL, yoptions=gtk.FILL) + + if commonwidget: + assert widget is not None + self.attach(widget, 1, 2, linecounter, linecounter+1, + yoptions=gtk.FILL) + widget.show_all() + + if commondesc and field.description is not None: + label = gtk.Label() + label.set_markup(''+\ + gtkgui_helpers.escape_for_pango_markup(field.description)+\ + '') + label.set_line_wrap(True) + self.attach(label, 2, 3, linecounter, linecounter+1, + xoptions=gtk.FILL|gtk.SHRINK, yoptions=gtk.FILL|gtk.SHRINK) + + linecounter+=1 + if self.get_property('visible'): + self.show_all() + + def show(self): + # simulate that we are one widget + self.show_all() + + def on_boolean_checkbutton_toggled(self, widget, field): + field.value = widget.get_active() + + def on_list_single_radiobutton_toggled(self, widget, field, value): + field.value = value + + def on_list_multi_checkbutton_toggled(self, widget, field, value): + # TODO: make some methods like add_value and remove_value + if widget.get_active() and value not in field.value: + field.value += [value] + elif not widget.get_active() and value in field.value: + field.value = [v for v in field.value if v!=value] + + def on_text_single_entry_changed(self, widget, field): + field.value = widget.get_text() + + def on_text_multi_textbuffer_changed(self, widget, field): + field.value = widget.get_text( + widget.get_start_iter(), + widget.get_end_iter()) + + def on_jid_multi_cellrenderertext_edited(self, cell, path, newtext, model, field): + old=model[path][0] + model[path][0]=newtext + + values = field.value + values[values.index(old)]=newtext + field.value = values + + def on_jid_multi_add_button_clicked(self, widget, treeview, model, field): + iter = model.insert(999999, ("new@jabber.id",)) + treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True) + field.value = field.value + ["new@jabber.id"] + + def on_jid_multi_edit_button_clicked(self, widget, treeview): + model, iter = treeview.get_selection().get_selected() + assert iter is not None + + treeview.set_cursor(model.get_path(iter), treeview.get_column(0), True) + + def on_jid_multi_remove_button_clicked(self, widget, treeview, field): + selection = treeview.get_selection() + model = treeview.get_model() + deleted = [] + + def remove(model, path, iter, deleted): + deleted+=model[iter] + model.remove(iter) + + selection.selected_foreach(remove, deleted) + field.value = (v for v in field.value if v not in deleted) + + def on_jid_multi_clean_button_clicked(self, widget, model, field): + model.clear() + del field.value