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

Commits:

3 changed files:

Changes:

  • gajim/data/gui/start_chat_dialog.ui
    1 1
     <?xml version="1.0" encoding="UTF-8"?>
    
    2
    -<!-- Generated with glade 3.20.1 -->
    
    2
    +<!-- Generated with glade 3.22.1 -->
    
    3 3
     <interface>
    
    4 4
       <requires lib="gtk+" version="3.20"/>
    
    5 5
       <object class="GtkBox" id="box">
    
    ... ... @@ -25,7 +25,7 @@
    25 25
           </packing>
    
    26 26
         </child>
    
    27 27
         <child>
    
    28
    -      <object class="GtkScrolledWindow">
    
    28
    +      <object class="GtkScrolledWindow" id="scrolledwindow">
    
    29 29
             <property name="visible">True</property>
    
    30 30
             <property name="can_focus">True</property>
    
    31 31
             <property name="hscrollbar_policy">never</property>
    
    ... ... @@ -53,4 +53,218 @@
    53 53
           </packing>
    
    54 54
         </child>
    
    55 55
       </object>
    
    56
    +  <object class="GtkGrid" id="search_result_grid">
    
    57
    +    <property name="visible">True</property>
    
    58
    +    <property name="can_focus">False</property>
    
    59
    +    <child>
    
    60
    +      <object class="GtkLabel" id="name_label">
    
    61
    +        <property name="visible">True</property>
    
    62
    +        <property name="can_focus">False</property>
    
    63
    +        <property name="halign">start</property>
    
    64
    +        <property name="ellipsize">end</property>
    
    65
    +        <property name="max_width_chars">50</property>
    
    66
    +        <property name="xalign">0</property>
    
    67
    +        <style>
    
    68
    +          <class name="bold"/>
    
    69
    +        </style>
    
    70
    +      </object>
    
    71
    +      <packing>
    
    72
    +        <property name="left_attach">0</property>
    
    73
    +        <property name="top_attach">0</property>
    
    74
    +      </packing>
    
    75
    +    </child>
    
    76
    +    <child>
    
    77
    +      <object class="GtkLabel" id="jid_label">
    
    78
    +        <property name="visible">True</property>
    
    79
    +        <property name="can_focus">False</property>
    
    80
    +        <property name="halign">start</property>
    
    81
    +        <property name="hexpand">True</property>
    
    82
    +        <property name="ellipsize">end</property>
    
    83
    +        <property name="max_width_chars">50</property>
    
    84
    +        <property name="xalign">0</property>
    
    85
    +      </object>
    
    86
    +      <packing>
    
    87
    +        <property name="left_attach">0</property>
    
    88
    +        <property name="top_attach">1</property>
    
    89
    +        <property name="width">2</property>
    
    90
    +      </packing>
    
    91
    +    </child>
    
    92
    +    <child>
    
    93
    +      <object class="GtkBox">
    
    94
    +        <property name="visible">True</property>
    
    95
    +        <property name="can_focus">False</property>
    
    96
    +        <property name="spacing">6</property>
    
    97
    +        <child>
    
    98
    +          <object class="GtkImage">
    
    99
    +            <property name="visible">True</property>
    
    100
    +            <property name="can_focus">False</property>
    
    101
    +            <property name="icon_name">system-users-symbolic</property>
    
    102
    +          </object>
    
    103
    +          <packing>
    
    104
    +            <property name="expand">False</property>
    
    105
    +            <property name="fill">True</property>
    
    106
    +            <property name="pack_type">end</property>
    
    107
    +            <property name="position">0</property>
    
    108
    +          </packing>
    
    109
    +        </child>
    
    110
    +        <child>
    
    111
    +          <object class="GtkLabel" id="user_label">
    
    112
    +            <property name="visible">True</property>
    
    113
    +            <property name="can_focus">False</property>
    
    114
    +            <property name="tooltip_text" translatable="yes">Users</property>
    
    115
    +            <property name="xalign">1</property>
    
    116
    +          </object>
    
    117
    +          <packing>
    
    118
    +            <property name="expand">False</property>
    
    119
    +            <property name="fill">True</property>
    
    120
    +            <property name="pack_type">end</property>
    
    121
    +            <property name="position">1</property>
    
    122
    +          </packing>
    
    123
    +        </child>
    
    124
    +      </object>
    
    125
    +      <packing>
    
    126
    +        <property name="left_attach">1</property>
    
    127
    +        <property name="top_attach">0</property>
    
    128
    +      </packing>
    
    129
    +    </child>
    
    130
    +  </object>
    
    131
    +  <object class="GtkGrid" id="tooltip">
    
    132
    +    <property name="visible">True</property>
    
    133
    +    <property name="can_focus">False</property>
    
    134
    +    <property name="column_spacing">6</property>
    
    135
    +    <child>
    
    136
    +      <object class="GtkLabel" id="tooltip_name">
    
    137
    +        <property name="can_focus">False</property>
    
    138
    +        <property name="no_show_all">True</property>
    
    139
    +        <property name="halign">start</property>
    
    140
    +        <property name="ellipsize">end</property>
    
    141
    +        <property name="max_width_chars">50</property>
    
    142
    +        <property name="xalign">0</property>
    
    143
    +        <style>
    
    144
    +          <class name="bold"/>
    
    145
    +        </style>
    
    146
    +      </object>
    
    147
    +      <packing>
    
    148
    +        <property name="left_attach">1</property>
    
    149
    +        <property name="top_attach">0</property>
    
    150
    +      </packing>
    
    151
    +    </child>
    
    152
    +    <child>
    
    153
    +      <object class="GtkLabel" id="tooltip_jid">
    
    154
    +        <property name="visible">True</property>
    
    155
    +        <property name="can_focus">False</property>
    
    156
    +        <property name="halign">start</property>
    
    157
    +        <property name="hexpand">True</property>
    
    158
    +        <property name="ellipsize">end</property>
    
    159
    +        <property name="max_width_chars">50</property>
    
    160
    +        <property name="xalign">0</property>
    
    161
    +      </object>
    
    162
    +      <packing>
    
    163
    +        <property name="left_attach">1</property>
    
    164
    +        <property name="top_attach">2</property>
    
    165
    +      </packing>
    
    166
    +    </child>
    
    167
    +    <child>
    
    168
    +      <object class="GtkLabel" id="tooltip_description">
    
    169
    +        <property name="can_focus">False</property>
    
    170
    +        <property name="no_show_all">True</property>
    
    171
    +        <property name="halign">start</property>
    
    172
    +        <property name="wrap">True</property>
    
    173
    +        <property name="max_width_chars">65</property>
    
    174
    +        <property name="xalign">0</property>
    
    175
    +      </object>
    
    176
    +      <packing>
    
    177
    +        <property name="left_attach">1</property>
    
    178
    +        <property name="top_attach">1</property>
    
    179
    +      </packing>
    
    180
    +    </child>
    
    181
    +    <child>
    
    182
    +      <object class="GtkLabel" id="tooltip_name_label">
    
    183
    +        <property name="visible">True</property>
    
    184
    +        <property name="can_focus">False</property>
    
    185
    +        <property name="label" translatable="yes">Name:</property>
    
    186
    +        <property name="xalign">1</property>
    
    187
    +      </object>
    
    188
    +      <packing>
    
    189
    +        <property name="left_attach">0</property>
    
    190
    +        <property name="top_attach">0</property>
    
    191
    +      </packing>
    
    192
    +    </child>
    
    193
    +    <child>
    
    194
    +      <object class="GtkLabel" id="tooltip_description_label">
    
    195
    +        <property name="visible">True</property>
    
    196
    +        <property name="can_focus">False</property>
    
    197
    +        <property name="label" translatable="yes">Description:</property>
    
    198
    +        <property name="xalign">1</property>
    
    199
    +      </object>
    
    200
    +      <packing>
    
    201
    +        <property name="left_attach">0</property>
    
    202
    +        <property name="top_attach">1</property>
    
    203
    +      </packing>
    
    204
    +    </child>
    
    205
    +    <child>
    
    206
    +      <object class="GtkLabel">
    
    207
    +        <property name="visible">True</property>
    
    208
    +        <property name="can_focus">False</property>
    
    209
    +        <property name="label" translatable="yes">Address:</property>
    
    210
    +        <property name="xalign">1</property>
    
    211
    +      </object>
    
    212
    +      <packing>
    
    213
    +        <property name="left_attach">0</property>
    
    214
    +        <property name="top_attach">2</property>
    
    215
    +      </packing>
    
    216
    +    </child>
    
    217
    +    <child>
    
    218
    +      <object class="GtkLabel" id="tooltip_user">
    
    219
    +        <property name="visible">True</property>
    
    220
    +        <property name="can_focus">False</property>
    
    221
    +        <property name="halign">start</property>
    
    222
    +        <property name="xalign">0</property>
    
    223
    +      </object>
    
    224
    +      <packing>
    
    225
    +        <property name="left_attach">1</property>
    
    226
    +        <property name="top_attach">4</property>
    
    227
    +      </packing>
    
    228
    +    </child>
    
    229
    +    <child>
    
    230
    +      <object class="GtkImage">
    
    231
    +        <property name="visible">True</property>
    
    232
    +        <property name="can_focus">False</property>
    
    233
    +        <property name="halign">end</property>
    
    234
    +        <property name="valign">center</property>
    
    235
    +        <property name="icon_name">system-users-symbolic</property>
    
    236
    +      </object>
    
    237
    +      <packing>
    
    238
    +        <property name="left_attach">0</property>
    
    239
    +        <property name="top_attach">4</property>
    
    240
    +      </packing>
    
    241
    +    </child>
    
    242
    +    <child>
    
    243
    +      <object class="GtkLabel" id="anonymous_label">
    
    244
    +        <property name="visible">True</property>
    
    245
    +        <property name="can_focus">False</property>
    
    246
    +        <property name="label" translatable="yes">Semi-Anonymous
    
    247
    +&lt;span size="small"&gt;Only moderators can see your XMPP-Address)&lt;/span&gt;</property>
    
    248
    +        <property name="use_markup">True</property>
    
    249
    +        <property name="xalign">0</property>
    
    250
    +      </object>
    
    251
    +      <packing>
    
    252
    +        <property name="left_attach">1</property>
    
    253
    +        <property name="top_attach">3</property>
    
    254
    +      </packing>
    
    255
    +    </child>
    
    256
    +    <child>
    
    257
    +      <object class="GtkImage" id="anonymous_icon">
    
    258
    +        <property name="visible">True</property>
    
    259
    +        <property name="can_focus">False</property>
    
    260
    +        <property name="halign">end</property>
    
    261
    +        <property name="valign">center</property>
    
    262
    +        <property name="icon_name">security-medium-symbolic</property>
    
    263
    +      </object>
    
    264
    +      <packing>
    
    265
    +        <property name="left_attach">0</property>
    
    266
    +        <property name="top_attach">3</property>
    
    267
    +      </packing>
    
    268
    +    </child>
    
    269
    +  </object>
    
    56 270
     </interface>

  • gajim/data/style/gajim.css
    ... ... @@ -134,6 +134,15 @@ list.settings > row > box {
    134 134
     #StartChatListBox > row { padding: 10px 20px 10px 10px; }
    
    135 135
     #StartChatListBox > row:not(.activatable) label { color: @insensitive_fg_color }
    
    136 136
     
    
    137
    +/* StartChatListBox */
    
    138
    +.start-chat-row { border-bottom: 1px solid; border-color: @theme_unfocused_bg_color; }
    
    139
    +.start-chat-row:last-child { border-bottom: 0px}
    
    140
    +.start-chat-row.activatable:active { box-shadow: none; }
    
    141
    +.start-chat-row { padding: 10px 20px 10px 10px; }
    
    142
    +.start-chat-row:not(.activatable) label { color: @insensitive_fg_color }
    
    143
    +.start-chat-row:focus { outline: none; }
    
    144
    +
    
    145
    +
    
    137 146
     /* GroupchatConfig */
    
    138 147
     #GroupchatConfig > box > buttonbox { margin: 0px 12px 12px 12px; }
    
    139 148
     #GroupchatConfig stack { border-bottom: 1px solid; border-color: @borders;}
    

  • gajim/gtk/start_chat.py
    ... ... @@ -13,12 +13,16 @@
    13 13
     # along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    14 14
     
    
    15 15
     import locale
    
    16
    +from enum import IntEnum
    
    16 17
     
    
    17 18
     from gi.repository import Gdk
    
    18 19
     from gi.repository import Gtk
    
    19 20
     from gi.repository import GLib
    
    20 21
     from gi.repository import Pango
    
    21 22
     
    
    23
    +from nbxmpp.util import is_error_result
    
    24
    +from nbxmpp.const import AnonymityMode
    
    25
    +
    
    22 26
     from gajim.common import app
    
    23 27
     from gajim.common import helpers
    
    24 28
     from gajim.common.i18n import _
    
    ... ... @@ -26,6 +30,15 @@ from gajim.common.const import AvatarSize
    26 30
     
    
    27 31
     from gajim.gtk.util import get_icon_name
    
    28 32
     from gajim.gtk.util import get_builder
    
    33
    +from gajim.gtk.util import ensure_not_destroyed
    
    34
    +
    
    35
    +
    
    36
    +SERVICE_JID = 'rodrigo.de.mucobedo@dreckshal.de'
    
    37
    +
    
    38
    +
    
    39
    +class Search(IntEnum):
    
    40
    +    CONTACT = 0
    
    41
    +    MUC = 1
    
    29 42
     
    
    30 43
     
    
    31 44
     class StartChatDialog(Gtk.ApplicationWindow):
    
    ... ... @@ -38,6 +51,9 @@ class StartChatDialog(Gtk.ApplicationWindow):
    38 51
             self.set_title(_('Start new Conversation'))
    
    39 52
             self.set_default_size(-1, 400)
    
    40 53
             self.ready_to_destroy = False
    
    54
    +        self._parameter_form = None
    
    55
    +        self._destroyed = False
    
    56
    +        self._search_stopped = False
    
    41 57
     
    
    42 58
             self.builder = get_builder('start_chat_dialog.ui')
    
    43 59
             self.listbox = self.builder.get_object('listbox')
    
    ... ... @@ -66,6 +82,9 @@ class StartChatDialog(Gtk.ApplicationWindow):
    66 82
             self.listbox.set_sort_func(self._sort_func, None)
    
    67 83
             self.listbox.connect('row-activated', self._on_row_activated)
    
    68 84
     
    
    85
    +        self._muc_search_listbox = MUCSearch()
    
    86
    +        self._current_listbox = self.listbox
    
    87
    +
    
    69 88
             self.connect('key-press-event', self._on_key_press)
    
    70 89
             self.connect('destroy', self._destroy)
    
    71 90
     
    
    ... ... @@ -118,7 +137,7 @@ class StartChatDialog(Gtk.ApplicationWindow):
    118 137
                 return True
    
    119 138
     
    
    120 139
             if (event.state == Gdk.ModifierType.SHIFT_MASK and
    
    121
    -              event.keyval == Gdk.KEY_ISO_Left_Tab):
    
    140
    +                event.keyval == Gdk.KEY_ISO_Left_Tab):
    
    122 141
                 self.search_entry.emit('previous-match')
    
    123 142
                 return True
    
    124 143
     
    
    ... ... @@ -127,6 +146,8 @@ class StartChatDialog(Gtk.ApplicationWindow):
    127 146
                 return True
    
    128 147
     
    
    129 148
             if event.keyval == Gdk.KEY_Escape:
    
    149
    +            self._search_stopped = True
    
    150
    +            self._muc_search_listbox.remove_all()
    
    130 151
                 if self.search_entry.get_text() != '':
    
    131 152
                     self.search_entry.emit('stop-search')
    
    132 153
                 else:
    
    ... ... @@ -134,6 +155,11 @@ class StartChatDialog(Gtk.ApplicationWindow):
    134 155
                 return True
    
    135 156
     
    
    136 157
             if event.keyval == Gdk.KEY_Return:
    
    158
    +            if self._current_listbox_is(Search.MUC):
    
    159
    +                self._muc_search_listbox.remove_all()
    
    160
    +                self._start_search()
    
    161
    +                return True
    
    162
    +
    
    137 163
                 row = self.listbox.get_selected_row()
    
    138 164
                 if row is not None:
    
    139 165
                     row.emit('activate')
    
    ... ... @@ -159,8 +185,27 @@ class StartChatDialog(Gtk.ApplicationWindow):
    159 185
     
    
    160 186
             self.ready_to_destroy = True
    
    161 187
     
    
    188
    +    def _set_listbox(self, listbox):
    
    189
    +        if self._current_listbox == listbox:
    
    190
    +            return
    
    191
    +        viewport = self.builder.scrolledwindow.get_child()
    
    192
    +        viewport.remove(viewport.get_child())
    
    193
    +        self.builder.scrolledwindow.remove(viewport)
    
    194
    +        self.builder.scrolledwindow.add(listbox)
    
    195
    +        self._current_listbox = listbox
    
    196
    +
    
    197
    +    def _current_listbox_is(self, box):
    
    198
    +        if self._current_listbox == self.listbox:
    
    199
    +            return box == Search.CONTACT
    
    200
    +        return box == Search.MUC
    
    201
    +
    
    162 202
         def _on_search_changed(self, entry):
    
    163 203
             search_text = entry.get_text()
    
    204
    +        if search_text.startswith('g:'):
    
    205
    +            self._set_listbox(self._muc_search_listbox)
    
    206
    +            return
    
    207
    +
    
    208
    +        self._set_listbox(self.listbox)
    
    164 209
             if '@' in search_text:
    
    165 210
                 self._add_new_jid_row()
    
    166 211
                 self._update_new_jid_rows(search_text)
    
    ... ... @@ -256,7 +301,83 @@ class StartChatDialog(Gtk.ApplicationWindow):
    256 301
             return locale.strcoll(name1.lower(), name2.lower())
    
    257 302
     
    
    258 303
         @staticmethod
    
    259
    -    def _destroy(*args):
    
    304
    +    def _get_connection():
    
    305
    +        accounts = app.get_connected_accounts()
    
    306
    +        if not accounts:
    
    307
    +            raise NotConnected
    
    308
    +        return app.connections[accounts.pop()].connection
    
    309
    +
    
    310
    +    def _start_search(self):
    
    311
    +        self._search_stopped = False
    
    312
    +        try:
    
    313
    +            con = self._get_connection()
    
    314
    +        except NotConnected:
    
    315
    +            return
    
    316
    +
    
    317
    +        text = self.search_entry.get_text()
    
    318
    +        text = text[2:].strip()
    
    319
    +        self._muc_search_listbox.start_search()
    
    320
    +
    
    321
    +        if self._parameter_form is None:
    
    322
    +            con.get_module('Muclumbus').request_parameters(
    
    323
    +                SERVICE_JID,
    
    324
    +                callback=self._parameters_received,
    
    325
    +                user_data=(con, text))
    
    326
    +        else:
    
    327
    +            self._parameter_form.vars['q'].value = text
    
    328
    +
    
    329
    +            con.get_module('Muclumbus').set_search(
    
    330
    +                SERVICE_JID,
    
    331
    +                self._parameter_form,
    
    332
    +                callback=self._on_search_result,
    
    333
    +                user_data=con)
    
    334
    +
    
    335
    +    @ensure_not_destroyed
    
    336
    +    def _parameters_received(self, result, user_data):
    
    337
    +        if is_error_result(result):
    
    338
    +            self._on_error(result)
    
    339
    +            return
    
    340
    +
    
    341
    +        con, text = user_data
    
    342
    +        self._parameter_form = result
    
    343
    +        self._parameter_form.type_ = 'submit'
    
    344
    +        self._parameter_form.vars['q'].value = text
    
    345
    +
    
    346
    +        con.get_module('Muclumbus').set_search(
    
    347
    +            SERVICE_JID,
    
    348
    +            self._parameter_form,
    
    349
    +            callback=self._on_search_result,
    
    350
    +            user_data=con)
    
    351
    +
    
    352
    +    @ensure_not_destroyed
    
    353
    +    def _on_search_result(self, result, con):
    
    354
    +        if self._search_stopped:
    
    355
    +            return
    
    356
    +
    
    357
    +        if is_error_result(result):
    
    358
    +            self._on_error(result)
    
    359
    +            return
    
    360
    +
    
    361
    +        for item in result.items:
    
    362
    +            self._muc_search_listbox.add(ResultRow(item))
    
    363
    +
    
    364
    +        if result.end:
    
    365
    +            self._muc_search_listbox.end_search()
    
    366
    +            return
    
    367
    +
    
    368
    +        con.get_module('Muclumbus').set_search(
    
    369
    +            SERVICE_JID,
    
    370
    +            self._parameter_form,
    
    371
    +            items_per_page=result.max,
    
    372
    +            after=result.last,
    
    373
    +            callback=self._on_search_result,
    
    374
    +            user_data=con)
    
    375
    +
    
    376
    +    def _on_error(self, result):
    
    377
    +        self._muc_search_listbox.show_error(result)
    
    378
    +
    
    379
    +    def _destroy(self, *args):
    
    380
    +        self._destroyed = True
    
    260 381
             del app.interface.instances['start_chat']
    
    261 382
     
    
    262 383
     
    
    ... ... @@ -345,3 +466,149 @@ class ContactRow(Gtk.Grid):
    345 466
             if self.show_account:
    
    346 467
                 return '%s %s %s' % (self.name, self.jid, self.account_label)
    
    347 468
             return '%s %s' % (self.name, self.jid)
    
    469
    +
    
    470
    +
    
    471
    +class MUCSearch(Gtk.ListBox):
    
    472
    +    def __init__(self):
    
    473
    +        Gtk.ListBox.__init__(self)
    
    474
    +        self.set_has_tooltip(True)
    
    475
    +        self.set_activate_on_single_click(False)
    
    476
    +        self._progress = None
    
    477
    +
    
    478
    +        self._ui = get_builder('start_chat_dialog.ui', ['tooltip'])
    
    479
    +        self.connect('query-tooltip', self._query_tooltip)
    
    480
    +        self.connect('row-activated', self._on_row_activated)
    
    481
    +        self.show_all()
    
    482
    +
    
    483
    +    def remove_all(self):
    
    484
    +        def remove(row):
    
    485
    +            self.remove(row)
    
    486
    +            row.destroy()
    
    487
    +        self.foreach(remove)
    
    488
    +
    
    489
    +    def show_error(self, error):
    
    490
    +        self.insert(ErrorRow(error), 0)
    
    491
    +        self.remove(self._progress)
    
    492
    +        self._progress.destroy()
    
    493
    +
    
    494
    +    def start_search(self):
    
    495
    +        self._progress = ProgressRow()
    
    496
    +        super().add(self._progress)
    
    497
    +
    
    498
    +    def end_search(self):
    
    499
    +        self._progress.stop()
    
    500
    +
    
    501
    +    def add(self, row):
    
    502
    +        super().add(row)
    
    503
    +        self._progress.update()
    
    504
    +
    
    505
    +    def _query_tooltip(self, _widget, _x_pos, y_pos, _keyboard_mode, tooltip):
    
    506
    +        row = self.get_row_at_y(y_pos)
    
    507
    +        if row is None or not isinstance(row, ResultRow):
    
    508
    +            self._clear_tooltip()
    
    509
    +            return False
    
    510
    +
    
    511
    +        if row.item.anonymity_mode == AnonymityMode.SEMI:
    
    512
    +            self._ui.anonymous_icon.show()
    
    513
    +            self._ui.anonymous_label.show()
    
    514
    +
    
    515
    +        self._ui.tooltip_user.set_text(row.item.nusers)
    
    516
    +        self._ui.tooltip_jid.set_text(row.item.jid)
    
    517
    +        if row.item.description:
    
    518
    +            self._ui.tooltip_description.set_text(row.item.description)
    
    519
    +            self._ui.tooltip_description.show()
    
    520
    +            self._ui.tooltip_description_label.show()
    
    521
    +
    
    522
    +        if row.item.name:
    
    523
    +            self._ui.tooltip_name.set_text(row.item.name)
    
    524
    +            self._ui.tooltip_name.show()
    
    525
    +            self._ui.tooltip_name_label.show()
    
    526
    +
    
    527
    +        tooltip.set_custom(self._ui.tooltip)
    
    528
    +        return True
    
    529
    +
    
    530
    +    def _clear_tooltip(self):
    
    531
    +        self._ui.anonymous_icon.hide()
    
    532
    +        self._ui.anonymous_label.hide()
    
    533
    +        self._ui.tooltip_description.hide()
    
    534
    +        self._ui.tooltip_description_label.hide()
    
    535
    +        self._ui.tooltip_name.hide()
    
    536
    +        self._ui.tooltip_name_label.hide()
    
    537
    +
    
    538
    +    def _on_row_activated(self, _listbox, row):
    
    539
    +        app.interface.join_gc_minimal(None, row.item.jid)
    
    540
    +
    
    541
    +
    
    542
    +class ResultRow(Gtk.ListBoxRow):
    
    543
    +    def __init__(self, item):
    
    544
    +        Gtk.ListBoxRow.__init__(self)
    
    545
    +        self.set_activatable(True)
    
    546
    +        self.get_style_context().add_class('start-chat-row')
    
    547
    +        self.item = item
    
    548
    +        self._ui = get_builder('start_chat_dialog.ui', ['search_result_grid'])
    
    549
    +        self._ui.jid_label.set_text(item.jid)
    
    550
    +        self._ui.name_label.set_text(item.name)
    
    551
    +        self._ui.user_label.set_text(item.nusers)
    
    552
    +        self.add(self._ui.search_result_grid)
    
    553
    +        self.show_all()
    
    554
    +
    
    555
    +
    
    556
    +class ErrorRow(Gtk.ListBoxRow):
    
    557
    +    def __init__(self, error):
    
    558
    +        Gtk.ListBoxRow.__init__(self)
    
    559
    +        self.set_selectable(False)
    
    560
    +        self.set_activatable(False)
    
    561
    +        self.get_style_context().add_class('start-chat-row')
    
    562
    +        self._error = Gtk.Label(label=str(error))
    
    563
    +        self._error.set_line_wrap(True)
    
    564
    +        self._error.set_line_wrap_mode(Pango.WrapMode.WORD)
    
    565
    +        self._error.set_xalign(0)
    
    566
    +        self._error_image = Gtk.Image.new_from_icon_name(
    
    567
    +            'dialog-error', Gtk.IconSize.MENU)
    
    568
    +        self._error_image.get_style_context().add_class('error-color')
    
    569
    +        self._error_image.set_valign(Gtk.Align.CENTER)
    
    570
    +        box = Gtk.Box()
    
    571
    +        box.set_spacing(6)
    
    572
    +        box.add(self._error_image)
    
    573
    +        box.add(self._error)
    
    574
    +        self.add(box)
    
    575
    +        self.show_all()
    
    576
    +
    
    577
    +
    
    578
    +class ProgressRow(Gtk.ListBoxRow):
    
    579
    +    def __init__(self):
    
    580
    +        Gtk.ListBoxRow.__init__(self)
    
    581
    +        self.set_selectable(False)
    
    582
    +        self.set_activatable(False)
    
    583
    +        self.get_style_context().add_class('start-chat-row')
    
    584
    +        self._text = _('%s Group chats found')
    
    585
    +        self._count = 0
    
    586
    +        self._spinner = Gtk.Spinner()
    
    587
    +        self._spinner.start()
    
    588
    +        self._count_label = Gtk.Label(label=self._text % 0)
    
    589
    +        self._count_label.get_style_context().add_class('bold')
    
    590
    +        self._finished_image = Gtk.Image.new_from_icon_name(
    
    591
    +            'emblem-ok-symbolic', Gtk.IconSize.MENU)
    
    592
    +        self._finished_image.get_style_context().add_class('success-color')
    
    593
    +        self._finished_image.set_no_show_all(True)
    
    594
    +
    
    595
    +        box = Gtk.Box()
    
    596
    +        box.set_spacing(6)
    
    597
    +        box.add(self._finished_image)
    
    598
    +        box.add(self._spinner)
    
    599
    +        box.add(self._count_label)
    
    600
    +        self.add(box)
    
    601
    +        self.show_all()
    
    602
    +
    
    603
    +    def update(self):
    
    604
    +        self._count += 1
    
    605
    +        self._count_label.set_text(self._text % self._count)
    
    606
    +
    
    607
    +    def stop(self):
    
    608
    +        self._spinner.stop()
    
    609
    +        self._spinner.hide()
    
    610
    +        self._finished_image.show()
    
    611
    +
    
    612
    +
    
    613
    +class NotConnected(Exception):
    
    614
    +    pass