Philipp Hörist pushed to branch master at gajim / gajim

Commits:

6 changed files:

Changes:

  • gajim/common/modules/dataforms.py
    ... ... @@ -665,6 +665,10 @@ class DataForm(ExtendedNode):
    665 665
             for value in self.getTags('instructions'):
    
    666 666
                 self.delChild(value)
    
    667 667
     
    
    668
    +    @property
    
    669
    +    def is_reported(self):
    
    670
    +        return self.getTag('reported') is not None
    
    671
    +
    
    668 672
     
    
    669 673
     class SimpleDataForm(DataForm, DataRecord):
    
    670 674
         def __init__(self, type_=None, title=None, instructions=None, fields=None,
    
    ... ... @@ -744,12 +748,6 @@ class MultipleDataForm(DataForm):
    744 748
             for record in self.getTags('item'):
    
    745 749
                 yield record
    
    746 750
     
    
    747
    -#    @property
    
    748
    -#    def reported(self):
    
    749
    -#        """
    
    750
    -#        DataRecord that contains descriptions of fields in records
    
    751
    -#        """
    
    752
    -#        return self.getTag('reported')
    
    753 751
     #
    
    754 752
     #    @reported.setter
    
    755 753
     #    def reported(self, record):
    

  • gajim/data/gui/search_window.ui deleted
    1
    -<?xml version="1.0" encoding="UTF-8"?>
    
    2
    -<!-- Generated with glade 3.18.3 -->
    
    3
    -<interface>
    
    4
    -  <requires lib="gtk+" version="3.12"/>
    
    5
    -  <object class="GtkImage" id="image1">
    
    6
    -    <property name="visible">True</property>
    
    7
    -    <property name="can_focus">False</property>
    
    8
    -    <property name="stock">gtk-add</property>
    
    9
    -  </object>
    
    10
    -  <object class="GtkImage" id="image2">
    
    11
    -    <property name="visible">True</property>
    
    12
    -    <property name="can_focus">False</property>
    
    13
    -    <property name="stock">gtk-find</property>
    
    14
    -  </object>
    
    15
    -  <object class="GtkImage" id="image3">
    
    16
    -    <property name="visible">True</property>
    
    17
    -    <property name="can_focus">False</property>
    
    18
    -    <property name="stock">gtk-dialog-info</property>
    
    19
    -  </object>
    
    20
    -  <object class="GtkWindow" id="search_window">
    
    21
    -    <property name="can_focus">False</property>
    
    22
    -    <property name="border_width">12</property>
    
    23
    -    <property name="title" translatable="yes">Search</property>
    
    24
    -    <property name="type_hint">dialog</property>
    
    25
    -    <signal name="destroy" handler="on_search_window_destroy" swapped="no"/>
    
    26
    -    <signal name="key-press-event" handler="on_search_window_key_press_event" swapped="no"/>
    
    27
    -    <child>
    
    28
    -      <object class="GtkBox" id="vbox1">
    
    29
    -        <property name="visible">True</property>
    
    30
    -        <property name="can_focus">False</property>
    
    31
    -        <property name="orientation">vertical</property>
    
    32
    -        <property name="spacing">6</property>
    
    33
    -        <child>
    
    34
    -          <object class="GtkBox" id="search_vbox">
    
    35
    -            <property name="visible">True</property>
    
    36
    -            <property name="can_focus">False</property>
    
    37
    -            <property name="orientation">vertical</property>
    
    38
    -            <child>
    
    39
    -              <object class="GtkLabel" id="label">
    
    40
    -                <property name="visible">True</property>
    
    41
    -                <property name="can_focus">False</property>
    
    42
    -                <property name="label" translatable="yes">Please wait while retrieving search form...</property>
    
    43
    -              </object>
    
    44
    -              <packing>
    
    45
    -                <property name="expand">False</property>
    
    46
    -                <property name="fill">False</property>
    
    47
    -                <property name="position">0</property>
    
    48
    -              </packing>
    
    49
    -            </child>
    
    50
    -            <child>
    
    51
    -              <object class="GtkProgressBar" id="progressbar">
    
    52
    -                <property name="visible">True</property>
    
    53
    -                <property name="can_focus">False</property>
    
    54
    -                <property name="pulse_step">0.10000000149</property>
    
    55
    -              </object>
    
    56
    -              <packing>
    
    57
    -                <property name="expand">False</property>
    
    58
    -                <property name="fill">False</property>
    
    59
    -                <property name="position">1</property>
    
    60
    -              </packing>
    
    61
    -            </child>
    
    62
    -            <child>
    
    63
    -              <placeholder/>
    
    64
    -            </child>
    
    65
    -          </object>
    
    66
    -          <packing>
    
    67
    -            <property name="expand">False</property>
    
    68
    -            <property name="fill">True</property>
    
    69
    -            <property name="position">0</property>
    
    70
    -          </packing>
    
    71
    -        </child>
    
    72
    -        <child>
    
    73
    -          <object class="GtkButtonBox" id="hbuttonbox1">
    
    74
    -            <property name="visible">True</property>
    
    75
    -            <property name="can_focus">False</property>
    
    76
    -            <property name="spacing">6</property>
    
    77
    -            <property name="layout_style">end</property>
    
    78
    -            <child>
    
    79
    -              <object class="GtkButton" id="add_contact_button">
    
    80
    -                <property name="label" translatable="yes">_Add contact</property>
    
    81
    -                <property name="sensitive">False</property>
    
    82
    -                <property name="can_focus">True</property>
    
    83
    -                <property name="receives_default">True</property>
    
    84
    -                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    85
    -                <property name="no_show_all">True</property>
    
    86
    -                <property name="image">image1</property>
    
    87
    -                <property name="use_underline">True</property>
    
    88
    -                <signal name="clicked" handler="on_add_contact_button_clicked" swapped="no"/>
    
    89
    -              </object>
    
    90
    -              <packing>
    
    91
    -                <property name="expand">False</property>
    
    92
    -                <property name="fill">False</property>
    
    93
    -                <property name="position">0</property>
    
    94
    -              </packing>
    
    95
    -            </child>
    
    96
    -            <child>
    
    97
    -              <object class="GtkButton" id="information_button">
    
    98
    -                <property name="label" translatable="yes">_Information</property>
    
    99
    -                <property name="sensitive">False</property>
    
    100
    -                <property name="can_focus">True</property>
    
    101
    -                <property name="receives_default">True</property>
    
    102
    -                <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    103
    -                <property name="no_show_all">True</property>
    
    104
    -                <property name="image">image3</property>
    
    105
    -                <property name="use_underline">True</property>
    
    106
    -                <signal name="clicked" handler="on_information_button_clicked" swapped="no"/>
    
    107
    -              </object>
    
    108
    -              <packing>
    
    109
    -                <property name="expand">False</property>
    
    110
    -                <property name="fill">False</property>
    
    111
    -                <property name="position">1</property>
    
    112
    -              </packing>
    
    113
    -            </child>
    
    114
    -            <child>
    
    115
    -              <object class="GtkButton" id="search_button">
    
    116
    -                <property name="label" translatable="yes">_Search</property>
    
    117
    -                <property name="visible">True</property>
    
    118
    -                <property name="can_focus">True</property>
    
    119
    -                <property name="can_default">True</property>
    
    120
    -                <property name="receives_default">False</property>
    
    121
    -                <property name="image">image2</property>
    
    122
    -                <property name="use_underline">True</property>
    
    123
    -                <signal name="clicked" handler="on_search_button_clicked" swapped="no"/>
    
    124
    -              </object>
    
    125
    -              <packing>
    
    126
    -                <property name="expand">False</property>
    
    127
    -                <property name="fill">False</property>
    
    128
    -                <property name="position">2</property>
    
    129
    -              </packing>
    
    130
    -            </child>
    
    131
    -            <child>
    
    132
    -              <object class="GtkButton" id="close_button">
    
    133
    -                <property name="label">gtk-close</property>
    
    134
    -                <property name="visible">True</property>
    
    135
    -                <property name="can_focus">True</property>
    
    136
    -                <property name="can_default">True</property>
    
    137
    -                <property name="receives_default">False</property>
    
    138
    -                <property name="use_stock">True</property>
    
    139
    -                <signal name="clicked" handler="on_close_button_clicked" swapped="no"/>
    
    140
    -              </object>
    
    141
    -              <packing>
    
    142
    -                <property name="expand">False</property>
    
    143
    -                <property name="fill">False</property>
    
    144
    -                <property name="position">3</property>
    
    145
    -              </packing>
    
    146
    -            </child>
    
    147
    -          </object>
    
    148
    -          <packing>
    
    149
    -            <property name="expand">False</property>
    
    150
    -            <property name="fill">True</property>
    
    151
    -            <property name="position">1</property>
    
    152
    -          </packing>
    
    153
    -        </child>
    
    154
    -      </object>
    
    155
    -    </child>
    
    156
    -  </object>
    
    157
    -</interface>

  • gajim/data/style/gajim.css
    ... ... @@ -256,3 +256,7 @@ button.flat.link { padding: 0; border: 0; }
    256 256
     /* Treeview */
    
    257 257
     .adhoc-treeview { padding: 5px; }
    
    258 258
     .adhoc-scrolled { border: 1px solid; border-color:@unfocused_borders; }
    
    259
    +
    
    260
    +/* Search Dialog */
    
    261
    +.search-treeview { padding: 5px; }
    
    262
    +.search-scrolled { border: 1px solid; border-color:@unfocused_borders; }

  • gajim/gtk/discovery.py
    ... ... @@ -58,7 +58,7 @@ from gajim.common.const import StyleAttr
    58 58
     from gajim.gtk.dialogs import ErrorDialog
    
    59 59
     from gajim.gtk.dialogs import InformationDialog
    
    60 60
     from gajim.gtk.service_registration import ServiceRegistration
    
    61
    -from gajim.gtk.discovery_search import SearchWindow
    
    61
    +from gajim.gtk.search import Search
    
    62 62
     from gajim.gtk.adhoc import AdHocCommand
    
    63 63
     from gajim.gtk.util import icon_exists
    
    64 64
     from gajim.gtk.util import get_builder
    
    ... ... @@ -1284,12 +1284,7 @@ class ToplevelAgentBrowser(AgentBrowser):
    1284 1284
             if not iter_:
    
    1285 1285
                 return
    
    1286 1286
             service = model[iter_][0]
    
    1287
    -        if service in app.interface.instances[self.account]['search']:
    
    1288
    -            app.interface.instances[self.account]['search'][service].window.\
    
    1289
    -                    present()
    
    1290
    -        else:
    
    1291
    -            app.interface.instances[self.account]['search'][service] = \
    
    1292
    -                    SearchWindow(self.account, service)
    
    1287
    +        Search(self.account, service, self.window.window)
    
    1293 1288
     
    
    1294 1289
         def cleanup(self):
    
    1295 1290
             AgentBrowser.cleanup(self)
    

  • gajim/gtk/discovery_search.py deleted
    1
    -# Copyright (C) 2007 Stephan Erb <steve-e AT h3c.de>
    
    2
    -# Copyright (C) 2007-2014 Yann Leboulanger <asterix AT lagaule.org>
    
    3
    -#
    
    4
    -# This file is part of Gajim.
    
    5
    -#
    
    6
    -# Gajim is free software; you can redistribute it and/or modify
    
    7
    -# it under the terms of the GNU General Public License as published
    
    8
    -# by the Free Software Foundation; version 3 only.
    
    9
    -#
    
    10
    -# Gajim is distributed in the hope that it will be useful,
    
    11
    -# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    12
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    13
    -# GNU General Public License for more details.
    
    14
    -#
    
    15
    -# You should have received a copy of the GNU General Public License
    
    16
    -# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    17
    -
    
    18
    -from gi.repository import GLib
    
    19
    -from gi.repository import Gtk
    
    20
    -from gi.repository import Gdk
    
    21
    -
    
    22
    -from gajim.common import app
    
    23
    -from gajim.common import ged
    
    24
    -from gajim.common.modules import dataforms
    
    25
    -from gajim.common.i18n import _
    
    26
    -
    
    27
    -from gajim import vcard
    
    28
    -from gajim import dataforms_widget
    
    29
    -
    
    30
    -from gajim.gtk.util import get_builder
    
    31
    -from gajim.gtk.add_contact import AddNewContactWindow
    
    32
    -from gajim.gtk.dataform import FakeDataFormWidget
    
    33
    -
    
    34
    -
    
    35
    -class SearchWindow:
    
    36
    -    def __init__(self, account, jid):
    
    37
    -        self.account = account
    
    38
    -        self.jid = jid
    
    39
    -
    
    40
    -        self._ui = get_builder('search_window.ui')
    
    41
    -        self.window = self._ui.search_window
    
    42
    -
    
    43
    -        self._ui.search_button.set_sensitive(False)
    
    44
    -
    
    45
    -        self._ui.connect_signals(self)
    
    46
    -        self.window.show_all()
    
    47
    -        self.request_form()
    
    48
    -        self.pulse_id = GLib.timeout_add(80, self.pulse_callback)
    
    49
    -
    
    50
    -        self.is_form = None
    
    51
    -
    
    52
    -        # Is there a jid column in results ? if -1: no, else column number
    
    53
    -        self.jid_column = -1
    
    54
    -
    
    55
    -        app.ged.register_event_handler('search-form-received',
    
    56
    -                                       ged.GUI1,
    
    57
    -                                       self._nec_search_form_received)
    
    58
    -        app.ged.register_event_handler('search-result-received',
    
    59
    -                                       ged.GUI1,
    
    60
    -                                       self._nec_search_result_received)
    
    61
    -
    
    62
    -    def request_form(self):
    
    63
    -        con = app.connections[self.account]
    
    64
    -        con.get_module('Search').request_search_fields(self.jid)
    
    65
    -
    
    66
    -    def pulse_callback(self):
    
    67
    -        self._ui.progressbar.pulse()
    
    68
    -        return True
    
    69
    -
    
    70
    -    def on_search_window_key_press_event(self, _widget, event):
    
    71
    -        if event.keyval == Gdk.KEY_Escape:
    
    72
    -            self.window.destroy()
    
    73
    -
    
    74
    -    def on_search_window_destroy(self, _widget):
    
    75
    -        if self.pulse_id:
    
    76
    -            GLib.source_remove(self.pulse_id)
    
    77
    -        del app.interface.instances[self.account]['search'][self.jid]
    
    78
    -        app.ged.remove_event_handler('search-form-received',
    
    79
    -                                     ged.GUI1,
    
    80
    -                                     self._nec_search_form_received)
    
    81
    -        app.ged.remove_event_handler('search-result-received',
    
    82
    -                                     ged.GUI1,
    
    83
    -                                     self._nec_search_result_received)
    
    84
    -
    
    85
    -    def on_close_button_clicked(self, _button):
    
    86
    -        self.window.destroy()
    
    87
    -
    
    88
    -    def on_search_button_clicked(self, _button):
    
    89
    -        con = app.connections[self.account]
    
    90
    -        if self.is_form:
    
    91
    -            self.data_form_widget.data_form.type_ = 'submit'
    
    92
    -            con.get_module('Search').send_search_form(
    
    93
    -                self.jid, self.data_form_widget.data_form.get_purged(), True)
    
    94
    -        else:
    
    95
    -            infos = self.data_form_widget.get_submit_form()
    
    96
    -            if 'instructions' in infos:
    
    97
    -                del infos['instructions']
    
    98
    -            con.get_module('Search').send_search_form(self.jid, infos, False)
    
    99
    -
    
    100
    -        self._ui.search_vbox.remove(self.data_form_widget)
    
    101
    -
    
    102
    -        self._ui.progressbar.show()
    
    103
    -        self._ui.label.set_text(_('Waiting for results'))
    
    104
    -        self._ui.label.show()
    
    105
    -        self.pulse_id = GLib.timeout_add(80, self.pulse_callback)
    
    106
    -        self._ui.search_button.hide()
    
    107
    -
    
    108
    -    def on_add_contact_button_clicked(self, _widget):
    
    109
    -        (model, iter_) = self.result_treeview.get_selection().get_selected()
    
    110
    -        if not iter_:
    
    111
    -            return
    
    112
    -        jid = model[iter_][self.jid_column]
    
    113
    -        AddNewContactWindow(self.account, jid)
    
    114
    -
    
    115
    -    def on_information_button_clicked(self, _widget):
    
    116
    -        (model, iter_) = self.result_treeview.get_selection().get_selected()
    
    117
    -        if not iter_:
    
    118
    -            return
    
    119
    -        jid = model[iter_][self.jid_column]
    
    120
    -        if jid in app.interface.instances[self.account]['infos']:
    
    121
    -            app.interface.instances[self.account]['infos'][jid].window.present()
    
    122
    -        else:
    
    123
    -            contact = app.contacts.create_contact(jid=jid, account=self.account)
    
    124
    -            app.interface.instances[self.account]['infos'][jid] = \
    
    125
    -                    vcard.VcardWindow(contact, self.account)
    
    126
    -
    
    127
    -    def _nec_search_form_received(self, obj):
    
    128
    -        if self.pulse_id:
    
    129
    -            GLib.source_remove(self.pulse_id)
    
    130
    -        self._ui.progressbar.hide()
    
    131
    -        self._ui.label.hide()
    
    132
    -
    
    133
    -        if obj.is_dataform:
    
    134
    -            self.is_form = True
    
    135
    -            self.data_form_widget = dataforms_widget.DataFormWidget()
    
    136
    -            self.dataform = dataforms.extend_form(node=obj.data)
    
    137
    -            self.data_form_widget.set_sensitive(True)
    
    138
    -            try:
    
    139
    -                self.data_form_widget.data_form = self.dataform
    
    140
    -            except dataforms.Error:
    
    141
    -                self._ui.label.set_text(_('Error in received dataform'))
    
    142
    -                self._ui.label.show()
    
    143
    -                return
    
    144
    -            if self.data_form_widget.title:
    
    145
    -                self.window.set_title(
    
    146
    -                    '%s - Search - Gajim' % self.data_form_widget.title)
    
    147
    -        else:
    
    148
    -            self.is_form = False
    
    149
    -            self.data_form_widget = FakeDataFormWidget(obj.data)
    
    150
    -
    
    151
    -        self.data_form_widget.show_all()
    
    152
    -        self._ui.search_vbox.pack_start(self.data_form_widget, True, True, 0)
    
    153
    -        self._ui.search_button.set_sensitive(True)
    
    154
    -
    
    155
    -    def on_result_treeview_cursor_changed(self, treeview):
    
    156
    -        if self.jid_column == -1:
    
    157
    -            return
    
    158
    -        (model, iter_) = treeview.get_selection().get_selected()
    
    159
    -        if not iter_:
    
    160
    -            return
    
    161
    -        if model[iter_][self.jid_column]:
    
    162
    -            self._ui.add_contact_button.set_sensitive(True)
    
    163
    -            self._ui.information_button.set_sensitive(True)
    
    164
    -        else:
    
    165
    -            self._ui.add_contact_button.set_sensitive(False)
    
    166
    -            self._ui.information_button.set_sensitive(False)
    
    167
    -
    
    168
    -    def _nec_search_result_received(self, obj):
    
    169
    -        if self.pulse_id:
    
    170
    -            GLib.source_remove(self.pulse_id)
    
    171
    -        self._ui.progressbar.hide()
    
    172
    -        self._ui.label.hide()
    
    173
    -
    
    174
    -        if not obj.is_dataform:
    
    175
    -            if not obj.data:
    
    176
    -                self._ui.label.set_text(_('No result'))
    
    177
    -                self._ui.label.show()
    
    178
    -                return
    
    179
    -            # We suppose all items have the same fields
    
    180
    -            sw = Gtk.ScrolledWindow()
    
    181
    -            sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
    
    182
    -            self.result_treeview = Gtk.TreeView()
    
    183
    -            self.result_treeview.connect(
    
    184
    -                'cursor-changed', self.on_result_treeview_cursor_changed)
    
    185
    -            sw.add(self.result_treeview)
    
    186
    -            # Create model
    
    187
    -            fieldtypes = [str]*len(obj.data[0])
    
    188
    -            model = Gtk.ListStore(*fieldtypes)
    
    189
    -            # Copy data to model
    
    190
    -            for item in obj.data:
    
    191
    -                model.append(item.values())
    
    192
    -            # Create columns
    
    193
    -            counter = 0
    
    194
    -            for field in obj.data[0].keys():
    
    195
    -                self.result_treeview.append_column(
    
    196
    -                    Gtk.TreeViewColumn(field,
    
    197
    -                                       Gtk.CellRendererText(),
    
    198
    -                                       text=counter))
    
    199
    -                if field == 'jid':
    
    200
    -                    self.jid_column = counter
    
    201
    -                counter += 1
    
    202
    -            self.result_treeview.set_model(model)
    
    203
    -            sw.show_all()
    
    204
    -            self._ui.search_vbox.pack_start(sw, True, True, 0)
    
    205
    -            if self.jid_column > -1:
    
    206
    -                self._ui.add_contact_button.show()
    
    207
    -                self._ui.information_button.show()
    
    208
    -            return
    
    209
    -
    
    210
    -        self.dataform = dataforms.extend_form(node=obj.data)
    
    211
    -        if not self.dataform.items:
    
    212
    -            # No result
    
    213
    -            self._ui.label.set_text(_('No result'))
    
    214
    -            self._ui.label.show()
    
    215
    -            return
    
    216
    -
    
    217
    -        self.data_form_widget.set_sensitive(True)
    
    218
    -        try:
    
    219
    -            self.data_form_widget.data_form = self.dataform
    
    220
    -        except dataforms.Error:
    
    221
    -            self._ui.label.set_text(_('Error in received dataform'))
    
    222
    -            self._ui.label.show()
    
    223
    -            return
    
    224
    -
    
    225
    -        self.result_treeview = self.data_form_widget.records_treeview
    
    226
    -        selection = self.result_treeview.get_selection()
    
    227
    -        selection.set_mode(Gtk.SelectionMode.SINGLE)
    
    228
    -        self.result_treeview.connect(
    
    229
    -            'cursor-changed', self.on_result_treeview_cursor_changed)
    
    230
    -
    
    231
    -        counter = 0
    
    232
    -        for field in self.dataform.reported.iter_fields():
    
    233
    -            if field.var == 'jid':
    
    234
    -                self.jid_column = counter
    
    235
    -                break
    
    236
    -            counter += 1
    
    237
    -        self._ui.search_vbox.pack_start(self.data_form_widget, True, True, 0)
    
    238
    -        self.data_form_widget.show()
    
    239
    -        if self.jid_column > -1:
    
    240
    -            self._ui.add_contact_button.show()
    
    241
    -            self._ui.information_button.show()
    
    242
    -        if self.data_form_widget.title:
    
    243
    -            self.window.set_title(
    
    244
    -                '%s - Search - Gajim' % self.data_form_widget.title)

  • gajim/gtk/search.py
    1
    +# Copyright (C) 2019 Philipp Hörist <philipp AT hoerist.com>
    
    2
    +#
    
    3
    +# This file is part of Gajim.
    
    4
    +#
    
    5
    +# Gajim is free software; you can redistribute it and/or modify
    
    6
    +# it under the terms of the GNU General Public License as published
    
    7
    +# by the Free Software Foundation; version 3 only.
    
    8
    +#
    
    9
    +# Gajim is distributed in the hope that it will be useful,
    
    10
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    11
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    12
    +# GNU General Public License for more details.
    
    13
    +#
    
    14
    +# You should have received a copy of the GNU General Public License
    
    15
    +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    16
    +
    
    17
    +import logging
    
    18
    +import itertools
    
    19
    +from enum import IntEnum
    
    20
    +
    
    21
    +from gi.repository import Gtk
    
    22
    +
    
    23
    +from gajim.common import app
    
    24
    +from gajim.common import ged
    
    25
    +from gajim.common.i18n import _
    
    26
    +
    
    27
    +from gajim.common.modules import dataforms
    
    28
    +
    
    29
    +from gajim.gtk.dataform import DataFormWidget
    
    30
    +from gajim.gtk.util import ensure_not_destroyed
    
    31
    +from gajim.gtk.util import find_widget
    
    32
    +
    
    33
    +log = logging.getLogger('gajim.gtk.search')
    
    34
    +
    
    35
    +
    
    36
    +class Page(IntEnum):
    
    37
    +    REQUEST_FORM = 0
    
    38
    +    FORM = 1
    
    39
    +    REQUEST_RESULT = 2
    
    40
    +    COMPLETED = 3
    
    41
    +    ERROR = 4
    
    42
    +
    
    43
    +
    
    44
    +class Search(Gtk.Assistant):
    
    45
    +    def __init__(self, account, jid, transient_for=None):
    
    46
    +        Gtk.Assistant.__init__(self)
    
    47
    +
    
    48
    +        self._con = app.connections[account]
    
    49
    +        self._account = account
    
    50
    +        self._jid = jid
    
    51
    +        self._destroyed = False
    
    52
    +
    
    53
    +        self.set_application(app.app)
    
    54
    +        self.set_resizable(True)
    
    55
    +        self.set_position(Gtk.WindowPosition.CENTER)
    
    56
    +        if transient_for is not None:
    
    57
    +            self.set_transient_for(transient_for)
    
    58
    +
    
    59
    +        self.set_size_request(500, 400)
    
    60
    +        self.get_style_context().add_class('dialog-margin')
    
    61
    +
    
    62
    +        self._add_page(RequestForm())
    
    63
    +        self._add_page(Form())
    
    64
    +        self._add_page(RequestResult())
    
    65
    +        self._add_page(Completed())
    
    66
    +        self._add_page(Error())
    
    67
    +
    
    68
    +        self.connect('prepare', self._on_page_change)
    
    69
    +        self.connect('cancel', self._on_cancel)
    
    70
    +        self.connect('close', self._on_cancel)
    
    71
    +        self.connect('destroy', self._on_destroy)
    
    72
    +
    
    73
    +        self._remove_sidebar()
    
    74
    +
    
    75
    +        self._buttons = {}
    
    76
    +        self._add_custom_buttons()
    
    77
    +
    
    78
    +        self.show()
    
    79
    +        app.ged.register_event_handler('search-form-received',
    
    80
    +                                       ged.GUI1,
    
    81
    +                                       self._search_form_received)
    
    82
    +        app.ged.register_event_handler('search-result-received',
    
    83
    +                                       ged.GUI1,
    
    84
    +                                       self._search_result_received)
    
    85
    +
    
    86
    +        self._request_search_fields()
    
    87
    +
    
    88
    +    def _add_custom_buttons(self):
    
    89
    +        action_area = find_widget('action_area', self)
    
    90
    +        for button in list(action_area.get_children()):
    
    91
    +            self.remove_action_widget(button)
    
    92
    +
    
    93
    +        search = Gtk.Button(label=_('Search'))
    
    94
    +        search.connect('clicked', self._execute_search)
    
    95
    +        search.get_style_context().add_class('suggested-action')
    
    96
    +        self._buttons['search'] = search
    
    97
    +        self.add_action_widget(search)
    
    98
    +
    
    99
    +        new_search = Gtk.Button(label=_('New Search'))
    
    100
    +        new_search.get_style_context().add_class('suggested-action')
    
    101
    +        new_search.connect('clicked',
    
    102
    +                           lambda *args: self.set_current_page(Page.FORM))
    
    103
    +        self._buttons['new-search'] = new_search
    
    104
    +        self.add_action_widget(new_search)
    
    105
    +
    
    106
    +    def _set_button_visibility(self, page):
    
    107
    +        for button in self._buttons.values():
    
    108
    +            button.hide()
    
    109
    +
    
    110
    +        if page == Page.FORM:
    
    111
    +            self._buttons['search'].show()
    
    112
    +
    
    113
    +        elif page in (Page.ERROR, Page.COMPLETED):
    
    114
    +            self._buttons['new-search'].show()
    
    115
    +
    
    116
    +    def _add_page(self, page):
    
    117
    +        self.append_page(page)
    
    118
    +        self.set_page_type(page, page.type_)
    
    119
    +        self.set_page_title(page, page.title)
    
    120
    +        self.set_page_complete(page, page.complete)
    
    121
    +
    
    122
    +    def set_stage_complete(self, is_valid):
    
    123
    +        self._buttons['search'].set_sensitive(is_valid)
    
    124
    +
    
    125
    +    def _request_search_fields(self):
    
    126
    +        self._con.get_module('Search').request_search_fields(self._jid)
    
    127
    +
    
    128
    +    def _execute_search(self, *args):
    
    129
    +        self.set_current_page(Page.REQUEST_RESULT)
    
    130
    +        form = self.get_nth_page(Page.FORM).get_submit_form()
    
    131
    +        self._con.get_module('Search').send_search_form(self._jid, form, True)
    
    132
    +
    
    133
    +    @ensure_not_destroyed
    
    134
    +    def _search_form_received(self, event):
    
    135
    +        if not event.is_dataform:
    
    136
    +            self.set_current_page(Page.ERROR)
    
    137
    +            return
    
    138
    +
    
    139
    +        self.get_nth_page(Page.FORM).process_search_form(event.data)
    
    140
    +        self.set_current_page(Page.FORM)
    
    141
    +
    
    142
    +    @ensure_not_destroyed
    
    143
    +    def _search_result_received(self, event):
    
    144
    +        if event.data is None:
    
    145
    +            self._on_error('')
    
    146
    +            return
    
    147
    +        self.get_nth_page(Page.COMPLETED).process_result(event.data)
    
    148
    +        self.set_current_page(Page.COMPLETED)
    
    149
    +
    
    150
    +    def _remove_sidebar(self):
    
    151
    +        main_box = self.get_children()[0]
    
    152
    +        sidebar = main_box.get_children()[0]
    
    153
    +        main_box.remove(sidebar)
    
    154
    +
    
    155
    +    def _on_page_change(self, _assistant, _page):
    
    156
    +        self._set_button_visibility(self.get_current_page())
    
    157
    +
    
    158
    +    def _on_error(self, error_text):
    
    159
    +        log.info('Show Error page')
    
    160
    +        page = self.get_nth_page(Page.ERROR)
    
    161
    +        page.set_text(error_text)
    
    162
    +        self.set_current_page(Page.ERROR)
    
    163
    +
    
    164
    +    def _on_cancel(self, _widget):
    
    165
    +        app.ged.remove_event_handler('search-form-received',
    
    166
    +                                     ged.GUI1,
    
    167
    +                                     self._search_form_received)
    
    168
    +        app.ged.remove_event_handler('search-result-received',
    
    169
    +                                     ged.GUI1,
    
    170
    +                                     self._search_result_received)
    
    171
    +        self.destroy()
    
    172
    +
    
    173
    +    def _on_destroy(self, *args):
    
    174
    +        self._destroyed = True
    
    175
    +
    
    176
    +
    
    177
    +class RequestForm(Gtk.Box):
    
    178
    +
    
    179
    +    type_ = Gtk.AssistantPageType.CUSTOM
    
    180
    +    title = _('Request Search Form')
    
    181
    +    complete = False
    
    182
    +
    
    183
    +    def __init__(self):
    
    184
    +        super().__init__(orientation=Gtk.Orientation.VERTICAL)
    
    185
    +        self.set_spacing(18)
    
    186
    +        spinner = Gtk.Spinner()
    
    187
    +        self.pack_start(spinner, True, True, 0)
    
    188
    +        spinner.start()
    
    189
    +        self.show_all()
    
    190
    +
    
    191
    +
    
    192
    +class Form(Gtk.Box):
    
    193
    +
    
    194
    +    type_ = Gtk.AssistantPageType.CUSTOM
    
    195
    +    title = _('Search')
    
    196
    +    complete = True
    
    197
    +
    
    198
    +    def __init__(self):
    
    199
    +        super().__init__(orientation=Gtk.Orientation.VERTICAL)
    
    200
    +        self.set_spacing(18)
    
    201
    +        self._dataform_widget = None
    
    202
    +        self.show_all()
    
    203
    +
    
    204
    +    @property
    
    205
    +    def search_form(self):
    
    206
    +        return self._dataform_widget.get_submit_form()
    
    207
    +
    
    208
    +    def clear(self):
    
    209
    +        self._show_form(None)
    
    210
    +
    
    211
    +    def process_search_form(self, form):
    
    212
    +        self._show_form(form)
    
    213
    +
    
    214
    +    def _show_form(self, form):
    
    215
    +        if self._dataform_widget is not None:
    
    216
    +            self.remove(self._dataform_widget)
    
    217
    +            self._dataform_widget.destroy()
    
    218
    +        if form is None:
    
    219
    +            return
    
    220
    +
    
    221
    +        options = {'form-width': 350}
    
    222
    +
    
    223
    +        form = dataforms.extend_form(node=form)
    
    224
    +        self._dataform_widget = DataFormWidget(form, options=options)
    
    225
    +        self._dataform_widget.connect('is-valid', self._on_is_valid)
    
    226
    +        self._dataform_widget.validate()
    
    227
    +        self._dataform_widget.show_all()
    
    228
    +        self.add(self._dataform_widget)
    
    229
    +
    
    230
    +    def _on_is_valid(self, _widget, is_valid):
    
    231
    +        self.get_toplevel().set_stage_complete(is_valid)
    
    232
    +
    
    233
    +    def get_submit_form(self):
    
    234
    +        return self._dataform_widget.get_submit_form()
    
    235
    +
    
    236
    +
    
    237
    +class RequestResult(RequestForm):
    
    238
    +
    
    239
    +    type_ = Gtk.AssistantPageType.CUSTOM
    
    240
    +    title = _('Search…')
    
    241
    +    complete = False
    
    242
    +
    
    243
    +
    
    244
    +class Completed(Gtk.Box):
    
    245
    +
    
    246
    +    type_ = Gtk.AssistantPageType.CUSTOM
    
    247
    +    title = _('Search Result')
    
    248
    +    complete = True
    
    249
    +
    
    250
    +    def __init__(self):
    
    251
    +        super().__init__(orientation=Gtk.Orientation.VERTICAL)
    
    252
    +        self.set_spacing(12)
    
    253
    +        self.show_all()
    
    254
    +        self._label = Gtk.Label(label=_('No results found'))
    
    255
    +        self._label.get_style_context().add_class('bold16')
    
    256
    +        self._label.set_no_show_all(True)
    
    257
    +        self._label.set_halign(Gtk.Align.CENTER)
    
    258
    +        self._scrolled = Gtk.ScrolledWindow()
    
    259
    +        self._scrolled.get_style_context().add_class('search-scrolled')
    
    260
    +        self._scrolled.set_no_show_all(True)
    
    261
    +        self._treeview = None
    
    262
    +        self.add(self._label)
    
    263
    +        self.add(self._scrolled)
    
    264
    +        self.show_all()
    
    265
    +
    
    266
    +    def process_result(self, form):
    
    267
    +        if self._treeview is not None:
    
    268
    +            self._scrolled.remove(self._treeview)
    
    269
    +            self._treeview.destroy()
    
    270
    +            self._treeview = None
    
    271
    +            self._label.hide()
    
    272
    +            self._scrolled.hide()
    
    273
    +
    
    274
    +        if not form:
    
    275
    +            self._label.show()
    
    276
    +            return
    
    277
    +
    
    278
    +        form = dataforms.extend_form(node=form)
    
    279
    +
    
    280
    +        fieldtypes = []
    
    281
    +        fieldvars = []
    
    282
    +        for field in form.reported.iter_fields():
    
    283
    +            if field.type_ == 'boolean':
    
    284
    +                fieldtypes.append(bool)
    
    285
    +            elif field.type_ in ('jid-single', 'text-single'):
    
    286
    +                fieldtypes.append(str)
    
    287
    +            else:
    
    288
    +                log.warning('Not supported field received: %s', field.type_)
    
    289
    +                continue
    
    290
    +            fieldvars.append(field.var)
    
    291
    +
    
    292
    +        liststore = Gtk.ListStore(*fieldtypes)
    
    293
    +
    
    294
    +        for item in form.iter_records():
    
    295
    +            iter_ = liststore.append()
    
    296
    +            for field in item.iter_fields():
    
    297
    +                if field.var in fieldvars:
    
    298
    +                    liststore.set_value(iter_,
    
    299
    +                                        fieldvars.index(field.var),
    
    300
    +                                        field.value)
    
    301
    +
    
    302
    +        self._treeview = Gtk.TreeView()
    
    303
    +        self._treeview.set_hexpand(True)
    
    304
    +        self._treeview.set_vexpand(True)
    
    305
    +        self._treeview.get_style_context().add_class('search-treeview')
    
    306
    +
    
    307
    +        for field, counter in zip(form.reported.iter_fields(),
    
    308
    +                                  itertools.count()):
    
    309
    +            self._treeview.append_column(
    
    310
    +                Gtk.TreeViewColumn(field.label,
    
    311
    +                                   Gtk.CellRendererText(),
    
    312
    +                                   text=counter))
    
    313
    +
    
    314
    +        self._treeview.set_model(liststore)
    
    315
    +        self._treeview.show()
    
    316
    +        self._scrolled.add(self._treeview)
    
    317
    +        self._scrolled.show()
    
    318
    +
    
    319
    +
    
    320
    +class Error(Gtk.Box):
    
    321
    +
    
    322
    +    type_ = Gtk.AssistantPageType.CUSTOM
    
    323
    +    title = _('Error')
    
    324
    +    complete = True
    
    325
    +
    
    326
    +    def __init__(self):
    
    327
    +        super().__init__(orientation=Gtk.Orientation.VERTICAL)
    
    328
    +        self.set_spacing(12)
    
    329
    +        self.set_homogeneous(True)
    
    330
    +
    
    331
    +        icon = Gtk.Image.new_from_icon_name('dialog-error-symbolic',
    
    332
    +                                            Gtk.IconSize.DIALOG)
    
    333
    +        icon.get_style_context().add_class('error-color')
    
    334
    +        icon.set_valign(Gtk.Align.END)
    
    335
    +        self._label = Gtk.Label()
    
    336
    +        self._label.get_style_context().add_class('bold16')
    
    337
    +        self._label.set_valign(Gtk.Align.START)
    
    338
    +
    
    339
    +        self.add(icon)
    
    340
    +        self.add(self._label)
    
    341
    +        self.show_all()
    
    342
    +
    
    343
    +    def set_text(self, text):
    
    344
    +        self._label.set_text(text)