Daniel Brötzmann pushed to branch mainwindow at gajim / gajim

Commits:

3 changed files:

Changes:

  • gajim/data/gui/roster.ui
    ... ... @@ -8,11 +8,9 @@
    8 8
           <column type="CairoGObjectSurface"/>
    
    9 9
           <!-- column-name Text -->
    
    10 10
           <column type="gchararray"/>
    
    11
    -      <!-- column-name Event -->
    
    12
    -      <column type="gboolean"/>
    
    13 11
           <!-- column-name IsContact -->
    
    14 12
           <column type="gboolean"/>
    
    15
    -      <!-- column-name GroupOrNickname -->
    
    13
    +      <!-- column-name JidOrGroup -->
    
    16 14
           <column type="gchararray"/>
    
    17 15
         </columns>
    
    18 16
       </object>
    
    ... ... @@ -42,7 +40,7 @@
    42 40
                 <property name="xalign">0</property>
    
    43 41
               </object>
    
    44 42
               <attributes>
    
    45
    -            <attribute name="visible">3</attribute>
    
    43
    +            <attribute name="visible">2</attribute>
    
    46 44
                 <attribute name="surface">0</attribute>
    
    47 45
               </attributes>
    
    48 46
             </child>
    
    ... ... @@ -56,22 +54,6 @@
    56 54
             </child>
    
    57 55
           </object>
    
    58 56
         </child>
    
    59
    -    <child>
    
    60
    -      <object class="GtkTreeViewColumn" id="event_column">
    
    61
    -        <property name="sizing">fixed</property>
    
    62
    -        <property name="fixed-width">35</property>
    
    63
    -        <property name="title">event</property>
    
    64
    -        <child>
    
    65
    -          <object class="GtkCellRendererPixbuf" id="icon">
    
    66
    -            <property name="xalign">0</property>
    
    67
    -            <property name="icon-name">gajim-event</property>
    
    68
    -          </object>
    
    69
    -          <attributes>
    
    70
    -            <attribute name="visible">2</attribute>
    
    71
    -          </attributes>
    
    72
    -        </child>
    
    73
    -      </object>
    
    74
    -    </child>
    
    75 57
         <child>
    
    76 58
           <object class="GtkTreeViewColumn" id="expander">
    
    77 59
             <property name="visible">False</property>
    

  • gajim/data/style/gajim.css
    ... ... @@ -416,6 +416,9 @@ infobar.error > revealer > box {
    416 416
     .start-chat-row:not(.activatable) label { color: @insensitive_fg_color; }
    
    417 417
     .start-chat-row:focus { outline: none; }
    
    418 418
     
    
    419
    +/* Roster */
    
    420
    +.roster treeview { padding-left: 4px; }
    
    421
    +
    
    419 422
     /* GroupChatRoster */
    
    420 423
     .groupchat-roster { border-left: 1px solid;
    
    421 424
                         border-color: @borders; }
    

  • gajim/gtk/roster.py
    1
    +import locale
    
    1 2
     import logging
    
    2 3
     from typing import Optional
    
    3 4
     from enum import IntEnum
    
    ... ... @@ -7,9 +8,12 @@
    7 8
     from gi.repository import GObject
    
    8 9
     
    
    9 10
     from gajim.common import app
    
    11
    +from gajim.common import ged
    
    10 12
     from gajim.common.const import AvatarSize
    
    13
    +from gajim.common.const import StyleAttr
    
    11 14
     from gajim.common.i18n import _
    
    12 15
     
    
    16
    +from .util import EventHelper
    
    13 17
     from .util import get_builder
    
    14 18
     
    
    15 19
     log = logging.getLogger('gajim.gui.roster')
    
    ... ... @@ -23,12 +27,11 @@
    23 27
     class Column(IntEnum):
    
    24 28
         AVATAR = 0
    
    25 29
         TEXT = 1
    
    26
    -    EVENT = 2
    
    27
    -    IS_CONTACT = 3
    
    28
    -    NICK_OR_GROUP = 4
    
    30
    +    IS_CONTACT = 2
    
    31
    +    JID_OR_GROUP = 3
    
    29 32
     
    
    30 33
     
    
    31
    -class Roster(Gtk.ScrolledWindow):
    
    34
    +class Roster(Gtk.ScrolledWindow, EventHelper):
    
    32 35
     
    
    33 36
         __gsignals__ = {
    
    34 37
             'row-activated': (
    
    ... ... @@ -39,11 +42,12 @@ class Roster(Gtk.ScrolledWindow):
    39 42
     
    
    40 43
         def __init__(self, account):
    
    41 44
             Gtk.ScrolledWindow.__init__(self)
    
    45
    +        EventHelper.__init__(self)
    
    42 46
             self.set_size_request(300, -1)
    
    47
    +        self.get_style_context().add_class('roster')
    
    43 48
     
    
    44 49
             self._account = account
    
    45
    -
    
    46
    -        self._handler_ids = {}
    
    50
    +        self._client = app.get_client(account)
    
    47 51
     
    
    48 52
             self._ui = get_builder('roster.ui')
    
    49 53
             self._ui.roster_treeview.set_model(None)
    
    ... ... @@ -55,12 +59,19 @@ def __init__(self, account):
    55 59
             self._group_refs = {}
    
    56 60
     
    
    57 61
             self._store = self._ui.contact_store
    
    62
    +        self._store.set_sort_func(Column.TEXT, self._tree_compare_iters)
    
    58 63
     
    
    59 64
             self._roster = self._ui.roster_treeview
    
    60 65
     
    
    66
    +        self._ui.contact_column.set_cell_data_func(self._ui.text_renderer,
    
    67
    +                                                   self._text_cell_data_func)
    
    61 68
             self.connect('destroy', self._on_destroy)
    
    62 69
             self._ui.connect_signals(self)
    
    63 70
     
    
    71
    +        self.register_events([
    
    72
    +            ('theme-update', ged.GUI2, self._on_theme_update),
    
    73
    +        ])
    
    74
    +
    
    64 75
             self._initial_draw()
    
    65 76
     
    
    66 77
         def _on_roster_row_activated(self, _treeview, path, _column):
    
    ... ... @@ -69,7 +80,7 @@ def _on_roster_row_activated(self, _treeview, path, _column):
    69 80
                 # This is a group row
    
    70 81
                 return
    
    71 82
     
    
    72
    -        nick = self._store[iter_][Column.NICK_OR_GROUP]
    
    83
    +        nick = self._store[iter_][Column.JID_OR_GROUP]
    
    73 84
             self.emit('row-activated', nick)
    
    74 85
     
    
    75 86
         def _on_roster_button_press_event(self, treeview, event):
    
    ... ... @@ -86,7 +97,7 @@ def _on_roster_button_press_event(self, treeview, event):
    86 97
                 # Group row
    
    87 98
                 return
    
    88 99
     
    
    89
    -        nick = self._store[iter_][Column.NICK_OR_GROUP]
    
    100
    +        nick = self._store[iter_][Column.JID_OR_GROUP]
    
    90 101
     
    
    91 102
             if event.button == 3:  # right click
    
    92 103
                 self._show_contact_menu(nick)
    
    ... ... @@ -98,27 +109,44 @@ def _on_roster_button_press_event(self, treeview, event):
    98 109
         def _on_focus_out(treeview, _param):
    
    99 110
             treeview.get_selection().unselect_all()
    
    100 111
     
    
    112
    +    def _on_theme_update(self, _event):
    
    113
    +        self.redraw()
    
    114
    +
    
    101 115
         def _show_contact_menu(self, nick):
    
    102 116
             pass
    
    103 117
     
    
    104 118
         def _on_destroy(self, _roster):
    
    105
    -        for id_ in list(self._handler_ids.keys()):
    
    106
    -            if self._handler_ids[id_].handler_is_connected(id_):
    
    107
    -                self._handler_ids[id_].disconnect(id_)
    
    108
    -            del self._handler_ids[id_]
    
    109
    -
    
    110 119
             self._contact_refs = {}
    
    111 120
             self._group_refs = {}
    
    112 121
             self._roster.set_model(None)
    
    113 122
             self._roster = None
    
    123
    +        # Store keeps a ref on the object if we dont unset the sort func
    
    124
    +        self._store.set_sort_func(Column.TEXT, print)
    
    114 125
             self._store.clear()
    
    115 126
             self._store = None
    
    116 127
             # self._tooltip.destroy()
    
    117 128
             # self._tooltip = None
    
    129
    +        app.check_finalize(self)
    
    118 130
     
    
    119 131
         def set_model(self):
    
    120 132
             self._roster.set_model(self._store)
    
    121 133
     
    
    134
    +    def redraw(self):
    
    135
    +        self._roster.set_model(None)
    
    136
    +        self._roster.set_model(self._store)
    
    137
    +        self._roster.expand_all()
    
    138
    +
    
    139
    +    def enable_sort(self, enable):
    
    140
    +        column = Gtk.TREE_SORTABLE_UNSORTED_SORT_COLUMN_ID
    
    141
    +        if enable:
    
    142
    +            column = Column.TEXT
    
    143
    +
    
    144
    +        self._store.set_sort_column_id(column, Gtk.SortType.ASCENDING)
    
    145
    +
    
    146
    +    def invalidate_sort(self):
    
    147
    +        self.enable_sort(False)
    
    148
    +        self.enable_sort(True)
    
    149
    +
    
    122 150
         def _initial_draw(self):
    
    123 151
             client = app.get_client(self._account)
    
    124 152
             for contact in client.get_module('Roster').iter_contacts():
    
    ... ... @@ -126,6 +154,7 @@ def _initial_draw(self):
    126 154
                 contact.connect('avatar-update', self._on_avatar_update)
    
    127 155
                 self._add_contact(contact)
    
    128 156
     
    
    157
    +        self.enable_sort(True)
    
    129 158
             self.set_model()
    
    130 159
             self._roster.expand_all()
    
    131 160
     
    
    ... ... @@ -143,7 +172,7 @@ def _add_contact(self, contact):
    143 172
                     group_iter = self._get_group_iter(group_name)
    
    144 173
                     if not group_iter:
    
    145 174
                         group_iter = self._store.append(
    
    146
    -                        None, (None, group_name, None, False, group_name))
    
    175
    +                        None, (None, group_name, False, group_name))
    
    147 176
                     group_path = self._store.get_path(group_iter)
    
    148 177
                     group_ref = Gtk.TreeRowReference(self._store, group_path)
    
    149 178
                     self._group_refs[group_name] = group_ref
    
    ... ... @@ -151,7 +180,7 @@ def _add_contact(self, contact):
    151 180
                 group_iter = self._get_group_iter(main_group)
    
    152 181
                 if not group_iter:
    
    153 182
                     group_iter = self._store.append(
    
    154
    -                    None, (None, main_group, None, False, main_group))
    
    183
    +                    None, (None, main_group, False, main_group))
    
    155 184
                 group_path = self._store.get_path(group_iter)
    
    156 185
                 group_ref = Gtk.TreeRowReference(self._store, group_path)
    
    157 186
                 self._group_refs[main_group] = group_ref
    
    ... ... @@ -160,7 +189,7 @@ def _add_contact(self, contact):
    160 189
             surface = contact.get_avatar(AvatarSize.CHAT, self.get_scale_factor())
    
    161 190
     
    
    162 191
             iter_ = self._store.append(
    
    163
    -            group_iter, (surface, contact.name, None, True, contact.name))
    
    192
    +            group_iter, (surface, contact.name, True, str(contact.jid)))
    
    164 193
             self._contact_refs[contact.jid] = Gtk.TreeRowReference(
    
    165 194
                 self._store, self._store.get_path(iter_))
    
    166 195
     
    
    ... ... @@ -175,12 +204,16 @@ def draw_groups(self):
    175 204
             for group in self._group_refs:
    
    176 205
                 self.draw_group(group)
    
    177 206
     
    
    178
    -    def draw_group(self, group):
    
    179
    -        group_iter = self._get_group_iter(group)
    
    207
    +    def draw_group(self, group_name):
    
    208
    +        group_iter = self._get_group_iter(group_name)
    
    180 209
             if not group_iter:
    
    181 210
                 return
    
    182 211
     
    
    183
    -        self._store[group_iter][Column.TEXT] = group
    
    212
    +        total_users = self._get_total_user_count()
    
    213
    +        group_users = self._store.iter_n_children(group_iter)
    
    214
    +        group_name += f' ({group_users}/{total_users})'
    
    215
    +
    
    216
    +        self._store[group_iter][Column.TEXT] = group_name
    
    184 217
     
    
    185 218
         def draw_contacts(self):
    
    186 219
             for jid in self._contact_refs:
    
    ... ... @@ -206,6 +239,12 @@ def draw_avatar(self, contact):
    206 239
                 AvatarSize.ROSTER, self.get_scale_factor())
    
    207 240
             self._store[iter_][Column.AVATAR] = surface
    
    208 241
     
    
    242
    +    def _get_total_user_count(self):
    
    243
    +        count = 0
    
    244
    +        for group_row in self._store:
    
    245
    +            count += self._store.iter_n_children(group_row.iter)
    
    246
    +        return count
    
    247
    +
    
    209 248
         def _get_group_iter(self, group_name: str) -> Optional[Gtk.TreeIter]:
    
    210 249
             try:
    
    211 250
                 ref = self._group_refs[group_name]
    
    ... ... @@ -229,6 +268,54 @@ def _get_contact_iter(self, jid: str) -> Optional[Gtk.TreeIter]:
    229 268
     
    
    230 269
             return self._store.get_iter(path)
    
    231 270
     
    
    271
    +    @staticmethod
    
    272
    +    def _text_cell_data_func(_column, renderer, model, iter_, _user_data):
    
    273
    +        has_parent = bool(model.iter_parent(iter_))
    
    274
    +        style = 'contact' if has_parent else 'group'
    
    275
    +
    
    276
    +        bgcolor = app.css_config.get_value('.gajim-%s-row' % style,
    
    277
    +                                           StyleAttr.BACKGROUND)
    
    278
    +        renderer.set_property('cell-background', bgcolor)
    
    279
    +
    
    280
    +        color = app.css_config.get_value('.gajim-%s-row' % style,
    
    281
    +                                         StyleAttr.COLOR)
    
    282
    +        renderer.set_property('foreground', color)
    
    283
    +
    
    284
    +        desc = app.css_config.get_font('.gajim-%s-row' % style)
    
    285
    +        renderer.set_property('font-desc', desc)
    
    286
    +
    
    287
    +        if not has_parent:
    
    288
    +            renderer.set_property('weight', 600)
    
    289
    +            renderer.set_property('ypad', 6)
    
    290
    +
    
    291
    +    def _tree_compare_iters(self, model, iter1, iter2, _user_data):
    
    292
    +        """
    
    293
    +        Compare two iterators to sort them
    
    294
    +        """
    
    295
    +
    
    296
    +        is_contact = model.iter_parent(iter1)
    
    297
    +        if is_contact:
    
    298
    +            name1 = model[iter1][Column.TEXT]
    
    299
    +            name2 = model[iter2][Column.TEXT]
    
    300
    +
    
    301
    +            if not app.settings.get('sort_by_show_in_roster'):
    
    302
    +                return locale.strcoll(name1.lower(), name2.lower())
    
    303
    +
    
    304
    +            contact1 = self._client.get_module('Contacts').get_contact(
    
    305
    +                model[iter1][Column.JID_OR_GROUP])
    
    306
    +            contact2 = self._client.get_module('Contacts').get_contact(
    
    307
    +                model[iter2][Column.JID_OR_GROUP])
    
    308
    +
    
    309
    +            if contact1.show != contact2.show:
    
    310
    +                return -1 if contact1.show > contact2.show else 1
    
    311
    +
    
    312
    +            return locale.strcoll(name1.lower(), name2.lower())
    
    313
    +
    
    314
    +        # Group
    
    315
    +        group1 = model[iter1][Column.JID_OR_GROUP]
    
    316
    +        group2 = model[iter2][Column.JID_OR_GROUP]
    
    317
    +        return -1 if group1 < group2 else 1
    
    318
    +
    
    232 319
         def clear(self):
    
    233 320
             self._contact_refs = {}
    
    234 321
             self._group_refs = {}