Philipp Hörist pushed to branch gajim_1.0 at gajim / gajim

Commits:

21 changed files:

Changes:

  • gajim/chat_control.py
    ... ... @@ -238,8 +238,11 @@ class ChatControl(ChatControlBase):
    238 238
                 self._nec_chatstate_received)
    
    239 239
             app.ged.register_event_handler('caps-received', ged.GUI1,
    
    240 240
                 self._nec_caps_received)
    
    241
    -        app.ged.register_event_handler('stanza-message-outgoing', ged.OUT_POSTCORE,
    
    241
    +        app.ged.register_event_handler('message-sent', ged.OUT_POSTCORE,
    
    242 242
                 self._message_sent)
    
    243
    +        app.ged.register_event_handler(
    
    244
    +            'mam-decrypted-message-received',
    
    245
    +            ged.GUI1, self._nec_mam_decrypted_message_received)
    
    243 246
     
    
    244 247
             # PluginSystem: adding GUI extension point for this ChatControl
    
    245 248
             # instance object
    
    ... ... @@ -817,6 +820,20 @@ class ChatControl(ChatControlBase):
    817 820
                 app.plugin_manager.extension_point(
    
    818 821
                     'encryption_dialog' + self.encryption, self)
    
    819 822
     
    
    823
    +    def _nec_mam_decrypted_message_received(self, obj):
    
    824
    +        if obj.conn.name != self.account:
    
    825
    +            return
    
    826
    +        if obj.with_ != self.contact.jid:
    
    827
    +            return
    
    828
    +
    
    829
    +        kind = '' # incoming
    
    830
    +        if obj.kind == KindConstant.CHAT_MSG_SENT:
    
    831
    +            kind = 'outgoing'
    
    832
    +
    
    833
    +        self.print_conversation(obj.msgtxt, kind, tim=obj.timestamp,
    
    834
    +            encrypted=obj.encrypted, correct_id=obj.correct_id, 
    
    835
    +            msg_stanza_id=obj.message_id, additional_data=obj.additional_data)
    
    836
    +
    
    820 837
         def _message_sent(self, obj):
    
    821 838
             if obj.conn.name != self.account:
    
    822 839
                 return
    
    ... ... @@ -1154,7 +1171,7 @@ class ChatControl(ChatControlBase):
    1154 1171
                 self._nec_chatstate_received)
    
    1155 1172
             app.ged.remove_event_handler('caps-received', ged.GUI1,
    
    1156 1173
                 self._nec_caps_received)
    
    1157
    -        app.ged.remove_event_handler('stanza-message-outgoing', ged.OUT_POSTCORE,
    
    1174
    +        app.ged.remove_event_handler('message-sent', ged.OUT_POSTCORE,
    
    1158 1175
                 self._message_sent)
    
    1159 1176
     
    
    1160 1177
             self.unsubscribe_events()
    

  • gajim/chat_control_base.py
    ... ... @@ -1380,13 +1380,12 @@ class ScrolledWindow(Gtk.ScrolledWindow):
    1380 1380
     
    
    1381 1381
         def do_get_preferred_height(self):
    
    1382 1382
             min_height, natural_height = Gtk.ScrolledWindow.do_get_preferred_height(self)
    
    1383
    -        child = self.get_child()
    
    1384 1383
             # Gtk Bug: If policy is set to Automatic, the ScrolledWindow
    
    1385
    -        # has a min size of around 46 depending on the System. Because
    
    1386
    -        # we want it smaller, we set policy NEVER if the height is < 50
    
    1384
    +        # has a min size of around 46-82 depending on the System. Because
    
    1385
    +        # we want it smaller, we set policy NEVER if the height is < 90
    
    1387 1386
             # so the ScrolledWindow will shrink to around 26 (1 line heigh).
    
    1388
    -        # Once it gets over 50 its no problem to restore the policy.
    
    1389
    -        if natural_height < 50:
    
    1387
    +        # Once it gets over 90 its no problem to restore the policy.
    
    1388
    +        if natural_height < 90:
    
    1390 1389
                 GLib.idle_add(self.set_policy,
    
    1391 1390
                               Gtk.PolicyType.AUTOMATIC,
    
    1392 1391
                               Gtk.PolicyType.NEVER)
    

  • gajim/common/connection.py
    ... ... @@ -64,6 +64,7 @@ from gajim.common import exceptions
    64 64
     from gajim.common import check_X509
    
    65 65
     from gajim.common.connection_handlers import *
    
    66 66
     from gajim.common.helpers import version_condition
    
    67
    +from gajim.common.contacts import GC_Contact
    
    67 68
     
    
    68 69
     from gajim.gtkgui_helpers import get_action
    
    69 70
     
    
    ... ... @@ -373,6 +374,10 @@ class CommonConnection:
    373 374
                     contact = app.contacts.get_contact_with_highest_priority(
    
    374 375
                         self.name, obj.jid)
    
    375 376
     
    
    377
    +            # Mark Message as MUC PM
    
    378
    +            if isinstance(contact, GC_Contact):
    
    379
    +                msg_iq.setTag('x', namespace=nbxmpp.NS_MUC_USER)
    
    380
    +
    
    376 381
                 # chatstates - if peer supports xep85, send chatstates
    
    377 382
                 # please note that the only valid tag inside a message containing a
    
    378 383
                 # <body> tag is the active event
    
    ... ... @@ -2070,10 +2075,7 @@ class Connection(CommonConnection, ConnectionHandlers):
    2070 2075
             obj.timestamp = time.time()
    
    2071 2076
             obj.stanza_id = self.connection.send(obj.msg_iq, now=obj.now)
    
    2072 2077
     
    
    2073
    -        app.nec.push_incoming_event(MessageSentEvent(
    
    2074
    -            None, conn=self, jid=obj.jid, message=obj.message, keyID=obj.keyID,
    
    2075
    -            chatstate=obj.chatstate, automatic_message=obj.automatic_message,
    
    2076
    -            stanza_id=obj.stanza_id, additional_data=obj.additional_data))
    
    2078
    +        app.nec.push_incoming_event(MessageSentEvent(None, **vars(obj)))
    
    2077 2079
     
    
    2078 2080
             if isinstance(obj.jid, list):
    
    2079 2081
                 for j in obj.jid:
    

  • gajim/common/connection_handlers_events.py
    ... ... @@ -163,6 +163,25 @@ class HelperEvent:
    163 163
             if forwarded is not None:
    
    164 164
                 return forwarded.getTag('message', protocol=True)
    
    165 165
     
    
    166
    +    def _is_self_message(self, message):
    
    167
    +        if self.self_message is not None:
    
    168
    +            return self.self_message
    
    169
    +        own_jid = self.conn.get_own_jid()
    
    170
    +        frm = message.getFrom()
    
    171
    +        to = message.getTo()
    
    172
    +        # If 'to' is not set we assume own jid
    
    173
    +        self.self_message = frm.bareMatch(to or own_jid)
    
    174
    +        return self.self_message
    
    175
    +
    
    176
    +    def _is_muc_pm(self, message):
    
    177
    +        if self.muc_pm is not None:
    
    178
    +            return self.muc_pm
    
    179
    +        self.muc_pm = False
    
    180
    +        muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER)
    
    181
    +        if muc_user is not None:
    
    182
    +            self.muc_pm = muc_user.getChildren() == []
    
    183
    +        return self.muc_pm
    
    184
    +
    
    166 185
     class HttpAuthReceivedEvent(nec.NetworkIncomingEvent):
    
    167 186
         name = 'http-auth-received'
    
    168 187
         base_network_events = []
    
    ... ... @@ -1015,6 +1034,8 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1015 1034
             self.encrypted = False
    
    1016 1035
             self.groupchat = False
    
    1017 1036
             self.nick = None
    
    1037
    +        self.self_message = None
    
    1038
    +        self.muc_pm = None
    
    1018 1039
     
    
    1019 1040
         def generate(self):
    
    1020 1041
             account = self.conn.name
    
    ... ... @@ -1031,6 +1052,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1031 1052
     
    
    1032 1053
             # use stanza-id as unique-id
    
    1033 1054
             self.unique_id, origin_id = self.get_unique_id()
    
    1055
    +        self.message_id = self.msg_.getID()
    
    1034 1056
     
    
    1035 1057
             # Check for duplicates
    
    1036 1058
             if app.logger.find_stanza_id(account,
    
    ... ... @@ -1081,6 +1103,11 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1081 1103
     
    
    1082 1104
         def get_unique_id(self):
    
    1083 1105
             stanza_id = self.get_stanza_id(self.result, query=True)
    
    1106
    +
    
    1107
    +        if self._is_self_message(self.msg_) or self._is_muc_pm(self.msg_):
    
    1108
    +            origin_id = self.msg_.getOriginID()
    
    1109
    +            return stanza_id, origin_id
    
    1110
    +
    
    1084 1111
             if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()):
    
    1085 1112
                 # message we sent
    
    1086 1113
                 origin_id = self.msg_.getOriginID()
    
    ... ... @@ -1101,6 +1128,8 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1101 1128
             :stanza:        Complete stanza Node
    
    1102 1129
             :forwarded:     Forwarded Node
    
    1103 1130
             :result:        Result Node
    
    1131
    +        :muc_pm:        True, if this is a MUC PM
    
    1132
    +                        propagated to MamDecryptedMessageReceivedEvent
    
    1104 1133
             '''
    
    1105 1134
             self._set_base_event_vars_as_attributes(base_event)
    
    1106 1135
             self.additional_data = {}
    
    ... ... @@ -1123,6 +1152,7 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1123 1152
                 return False
    
    1124 1153
     
    
    1125 1154
             self.unique_id = self.get_stanza_id(self.result, query=True)
    
    1155
    +        self.message_id = self.msg_.getID()
    
    1126 1156
     
    
    1127 1157
             # Check for duplicates
    
    1128 1158
             if app.logger.find_stanza_id(account,
    
    ... ... @@ -1171,33 +1201,43 @@ class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1171 1201
         base_network_events = []
    
    1172 1202
     
    
    1173 1203
         def generate(self):
    
    1204
    +        self.correct_id = None
    
    1205
    +
    
    1174 1206
             if not self.msgtxt:
    
    1175 1207
                 # For example Chatstates, Receipts, Chatmarkers
    
    1176 1208
                 log.debug('Received MAM message without text')
    
    1177 1209
                 return
    
    1178 1210
     
    
    1211
    +        replace = self.msg_.getTag('replace', namespace=nbxmpp.NS_CORRECT)
    
    1212
    +        if replace is not None:
    
    1213
    +            self.correct_id = replace.getAttr('id')
    
    1214
    +
    
    1179 1215
             self.get_oob_data(self.msg_)
    
    1180 1216
     
    
    1181 1217
             if self.groupchat:
    
    1182 1218
                 return True
    
    1183 1219
     
    
    1184
    -        self.is_pm = app.logger.jid_is_room_jid(self.with_.getStripped())
    
    1185
    -        if self.is_pm is None:
    
    1186
    -            # Check if this event is triggered after a disco, so we dont
    
    1187
    -            # run into an endless loop
    
    1188
    -            if hasattr(self, 'disco'):
    
    1189
    -                log.error('JID not known even after sucessful disco')
    
    1220
    +        if not self.muc_pm:
    
    1221
    +            # muc_pm = False, means only there was no muc#user namespace
    
    1222
    +            # This could still be a muc pm, we check the database if we
    
    1223
    +            # know this jid. If not we disco it.
    
    1224
    +            self.muc_pm = app.logger.jid_is_room_jid(self.with_.getStripped())
    
    1225
    +            if self.muc_pm is None:
    
    1226
    +                # Check if this event is triggered after a disco, so we dont
    
    1227
    +                # run into an endless loop
    
    1228
    +                if hasattr(self, 'disco'):
    
    1229
    +                    log.error('JID not known even after sucessful disco')
    
    1230
    +                    return
    
    1231
    +                # we don't know this JID, we need to disco it.
    
    1232
    +                server = self.with_.getDomain()
    
    1233
    +                if server not in self.conn.mam_awaiting_disco_result:
    
    1234
    +                    self.conn.mam_awaiting_disco_result[server] = [self]
    
    1235
    +                    self.conn.discoverInfo(server)
    
    1236
    +                else:
    
    1237
    +                    self.conn.mam_awaiting_disco_result[server].append(self)
    
    1190 1238
                     return
    
    1191
    -            # we don't know this JID, we need to disco it.
    
    1192
    -            server = self.with_.getDomain()
    
    1193
    -            if server not in self.conn.mam_awaiting_disco_result:
    
    1194
    -                self.conn.mam_awaiting_disco_result[server] = [self]
    
    1195
    -                self.conn.discoverInfo(server)
    
    1196
    -            else:
    
    1197
    -                self.conn.mam_awaiting_disco_result[server].append(self)
    
    1198
    -            return
    
    1199 1239
     
    
    1200
    -        if self.is_pm:
    
    1240
    +        if self.muc_pm:
    
    1201 1241
                 self.with_ = str(self.with_)
    
    1202 1242
             else:
    
    1203 1243
                 self.with_ = self.with_.getStripped()
    
    ... ... @@ -1217,6 +1257,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1217 1257
             self.forwarded = False
    
    1218 1258
             self.sent = False
    
    1219 1259
             self.encrypted = False
    
    1260
    +        self.self_message = None
    
    1261
    +        self.muc_pm = None
    
    1220 1262
             account = self.conn.name
    
    1221 1263
     
    
    1222 1264
             if self.stanza.getFrom() == self.conn.get_own_jid(warn=True):
    
    ... ... @@ -1250,11 +1292,16 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1250 1292
             self.unique_id = self.get_unique_id()
    
    1251 1293
             # Check groupchat messages for duplicates,
    
    1252 1294
             # We do this because of MUC History messages
    
    1253
    -        if self.stanza.getType() == 'groupchat':
    
    1295
    +        type_ = self.stanza.getType()
    
    1296
    +        if type_ == 'groupchat' or self.self_message or self.muc_pm:
    
    1297
    +            if type_ == 'groupchat':
    
    1298
    +                archive_jid = self.stanza.getFrom().getStripped()
    
    1299
    +            else:
    
    1300
    +                archive_jid = self.conn.get_own_jid().getStripped()
    
    1254 1301
                 if app.logger.find_stanza_id(account,
    
    1255
    -                                         self.stanza.getFrom().getStripped(),
    
    1302
    +                                         archive_jid,
    
    1256 1303
                                              self.unique_id,
    
    1257
    -                                         groupchat=True):
    
    1304
    +                                         groupchat=type_ == 'groupchat'):
    
    1258 1305
                     return
    
    1259 1306
     
    
    1260 1307
             address_tag = self.stanza.getTag('addresses',
    
    ... ... @@ -1406,6 +1453,21 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1406 1453
             return True
    
    1407 1454
     
    
    1408 1455
         def get_unique_id(self):
    
    1456
    +        '''
    
    1457
    +        Messages to self:
    
    1458
    +
    
    1459
    +        Messages to self are stored multiple times in MAM so we cant use
    
    1460
    +        stanza-id to deduplicate. We use origin-id instead. Its not perfect
    
    1461
    +        but there is no better way for now.
    
    1462
    +        We drop "received"-Carbons of Message to self, so we dont have to
    
    1463
    +        parse origin-id in that case.
    
    1464
    +
    
    1465
    +        MUC PMs:
    
    1466
    +
    
    1467
    +        MUC PMs are also stored multiple times, we also depend on origin-id
    
    1468
    +        for now.
    
    1469
    +        '''
    
    1470
    +
    
    1409 1471
             if self.stanza.getType() == 'groupchat':
    
    1410 1472
                 # TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced
    
    1411 1473
                 return self.get_stanza_id(self.stanza)
    
    ... ... @@ -1424,6 +1486,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1424 1486
                                              protocol=True)
    
    1425 1487
             if sent_carbon is not None:
    
    1426 1488
                 message = self.get_forwarded_message(sent_carbon)
    
    1489
    +            if self._is_self_message(message) or self._is_muc_pm(message):
    
    1490
    +                return message.getOriginID()
    
    1427 1491
                 return self.get_stanza_id(message)
    
    1428 1492
     
    
    1429 1493
             # Received Carbon
    
    ... ... @@ -1432,9 +1496,13 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1432 1496
                                                  protocol=True)
    
    1433 1497
             if received_carbon is not None:
    
    1434 1498
                 message = self.get_forwarded_message(received_carbon)
    
    1499
    +            if self._is_muc_pm(message):
    
    1500
    +                return message.getOriginID()
    
    1435 1501
                 return self.get_stanza_id(message)
    
    1436 1502
     
    
    1437 1503
             # Normal Message
    
    1504
    +        if self._is_self_message(self.stanza) or self._is_muc_pm(self.stanza):
    
    1505
    +            return self.stanza.getOriginID()
    
    1438 1506
             return self.get_stanza_id(self.stanza)
    
    1439 1507
     
    
    1440 1508
     class ZeroconfMessageReceivedEvent(MessageReceivedEvent):
    
    ... ... @@ -1547,6 +1615,7 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1547 1615
             self.forwarded = self.msg_obj.forwarded
    
    1548 1616
             self.sent = self.msg_obj.sent
    
    1549 1617
             self.conn = self.msg_obj.conn
    
    1618
    +        self.muc_pm = self.msg_obj.muc_pm
    
    1550 1619
             self.popup = False
    
    1551 1620
             self.msg_log_id = None # id in log database
    
    1552 1621
             self.attention = False # XEP-0224
    

  • gajim/common/contacts.py
    ... ... @@ -509,6 +509,15 @@ class Contacts():
    509 509
                         return c
    
    510 510
                 return self._contacts[jid][0]
    
    511 511
     
    
    512
    +    def get_contact_strict(self, jid, resource):
    
    513
    +        """
    
    514
    +        Return the contact instance for the given resource or None
    
    515
    +        """
    
    516
    +        if jid in self._contacts:
    
    517
    +            for c in self._contacts[jid]:
    
    518
    +                if c.resource == resource:
    
    519
    +                    return c
    
    520
    +
    
    512 521
         def get_avatar(self, jid, size=None, scale=None):
    
    513 522
             if jid not in self._contacts:
    
    514 523
                 return None
    
    ... ... @@ -554,7 +563,7 @@ class Contacts():
    554 563
             Get Contact object for specific resource of given jid
    
    555 564
             """
    
    556 565
             barejid, resource = common.app.get_room_and_nick_from_fjid(fjid)
    
    557
    -        return self.get_contact(barejid, resource)
    
    566
    +        return self.get_contact_strict(barejid, resource)
    
    558 567
     
    
    559 568
         def get_first_contact_from_jid(self, jid):
    
    560 569
             if jid in self._contacts:
    

  • gajim/common/helpers.py
    ... ... @@ -1592,3 +1592,35 @@ def version_condition(current_version, required_version):
    1592 1592
         if V(current_version) < V(required_version):
    
    1593 1593
             return False
    
    1594 1594
         return True
    
    1595
    +
    
    1596
    +def get_available_emoticon_themes():
    
    1597
    +    emoticons_themes = []
    
    1598
    +    emoticons_data_path = os.path.join(app.DATA_DIR, 'emoticons')
    
    1599
    +    font_theme_path = os.path.join(
    
    1600
    +        app.DATA_DIR, 'emoticons', 'font-emoticons', 'emoticons_theme.py')
    
    1601
    +
    
    1602
    +    folders = os.listdir(emoticons_data_path)
    
    1603
    +    if os.path.isdir(app.MY_EMOTS_PATH):
    
    1604
    +        folders += os.listdir(app.MY_EMOTS_PATH)
    
    1605
    +
    
    1606
    +    file = 'emoticons_theme.py'
    
    1607
    +    if os.name == 'nt' and not os.path.exists(font_theme_path):
    
    1608
    +        # When starting Gajim from source .py files are available
    
    1609
    +        # We test this with font-emoticons and fallback to .pyc files otherwise
    
    1610
    +        file = 'emoticons_theme.pyc'
    
    1611
    +
    
    1612
    +    for theme in folders:
    
    1613
    +        theme_path = os.path.join(emoticons_data_path, theme, file)
    
    1614
    +        if os.path.exists(theme_path):
    
    1615
    +            emoticons_themes.append(theme)
    
    1616
    +    emoticons_themes.sort()
    
    1617
    +    return emoticons_themes
    
    1618
    +
    
    1619
    +def get_emoticon_theme_path(theme):
    
    1620
    +    emoticons_data_path = os.path.join(app.DATA_DIR, 'emoticons', theme)
    
    1621
    +    if os.path.exists(emoticons_data_path):
    
    1622
    +        return emoticons_data_path
    
    1623
    +
    
    1624
    +    emoticons_user_path = os.path.join(app.MY_EMOTS_PATH, theme)
    
    1625
    +    if os.path.exists(emoticons_user_path):
    
    1626
    +        return emoticons_user_path

  • gajim/common/logger.py
    ... ... @@ -1158,6 +1158,7 @@ class Logger:
    1158 1158
             :param account:     The account
    
    1159 1159
     
    
    1160 1160
             :param archive_jid: The jid of the archive the stanza-id belongs to
    
    1161
    +                            only used if groupchat=True
    
    1161 1162
     
    
    1162 1163
             :param stanza_id:   The stanza-id
    
    1163 1164
     
    
    ... ... @@ -1182,7 +1183,7 @@ class Logger:
    1182 1183
             if groupchat:
    
    1183 1184
                 # Stanza ID is only unique within a specific archive.
    
    1184 1185
                 # So a Stanza ID could be repeated in different MUCs, so we
    
    1185
    -            # filter also for the archive JID
    
    1186
    +            # filter also for the archive JID which is the bare MUC jid.
    
    1186 1187
                 sql = '''
    
    1187 1188
                     SELECT stanza_id FROM logs
    
    1188 1189
                     WHERE stanza_id IN ({values})
    
    ... ... @@ -1193,14 +1194,14 @@ class Logger:
    1193 1194
             else:
    
    1194 1195
                 sql = '''
    
    1195 1196
                     SELECT stanza_id FROM logs
    
    1196
    -                WHERE stanza_id IN ({values}) AND account_id = ? LIMIT 1
    
    1197
    +                WHERE stanza_id IN ({values}) AND account_id = ? AND kind != ? LIMIT 1
    
    1197 1198
                     '''.format(values=', '.join('?' * len(ids)))
    
    1198 1199
                 result = self.con.execute(
    
    1199
    -                sql, tuple(ids) + (account_id,)).fetchone()
    
    1200
    +                sql, tuple(ids) + (account_id, KindConstant.GC_MSG)).fetchone()
    
    1200 1201
     
    
    1201 1202
             if result is not None:
    
    1202 1203
                 log.info('Found duplicated message, stanza-id: %s, origin-id: %s, '
    
    1203
    -                     'archive-jid: %s, account: %s', stanza_id, origin_id, archive_id, account_id)
    
    1204
    +                     'archive-jid: %s, account: %s', stanza_id, origin_id, archive_jid, account_id)
    
    1204 1205
                 return True
    
    1205 1206
             return False
    
    1206 1207
     
    

  • gajim/common/message_archiving.py
    ... ... @@ -200,13 +200,19 @@ class ConnectionArchive313:
    200 200
         def _nec_mam_decrypted_message_received(self, obj):
    
    201 201
             if obj.conn.name != self.name:
    
    202 202
                 return
    
    203
    -        # if self.archiving_namespace != nbxmpp.NS_MAM_2:
    
    204
    -        # Fallback duplicate search without stanza-id
    
    205
    -        duplicate = app.logger.search_for_duplicate(
    
    206
    -            self.name, obj.with_, obj.timestamp, obj.msgtxt)
    
    207
    -        if duplicate:
    
    208
    -            # dont propagate the event further
    
    209
    -            return True
    
    203
    +
    
    204
    +        namespace = self.archiving_namespace
    
    205
    +        if obj.groupchat:
    
    206
    +            namespace = muc_caps_cache.get_mam_namespace(obj.room_jid)
    
    207
    +
    
    208
    +        if namespace != nbxmpp.NS_MAM_2:
    
    209
    +            # Fallback duplicate search without stanza-id
    
    210
    +            duplicate = app.logger.search_for_duplicate(
    
    211
    +                self.name, obj.with_, obj.timestamp, obj.msgtxt)
    
    212
    +            if duplicate:
    
    213
    +                # dont propagate the event further
    
    214
    +                return True
    
    215
    +
    
    210 216
             app.logger.insert_into_logs(self.name,
    
    211 217
                                         obj.with_,
    
    212 218
                                         obj.timestamp,
    

  • gajim/config.py
    ... ... @@ -143,29 +143,17 @@ class PreferencesWindow:
    143 143
     
    
    144 144
             # emoticons
    
    145 145
             emoticons_combobox = self.xml.get_object('emoticons_combobox')
    
    146
    -        emoticons_list = os.listdir(os.path.join(app.DATA_DIR, 'emoticons'))
    
    147
    -        # user themes
    
    148
    -        if os.path.isdir(app.MY_EMOTS_PATH):
    
    149
    -            emoticons_list += os.listdir(app.MY_EMOTS_PATH)
    
    150
    -        emoticons_list.sort()
    
    151
    -        renderer_text = Gtk.CellRendererText()
    
    152
    -        emoticons_combobox.pack_start(renderer_text, True)
    
    153
    -        emoticons_combobox.add_attribute(renderer_text, 'text', 0)
    
    154
    -        model = Gtk.ListStore(str)
    
    155
    -        emoticons_combobox.set_model(model)
    
    156
    -        l = [_('Disabled')]
    
    157
    -        for dir_ in emoticons_list:
    
    158
    -            if not os.path.isdir(os.path.join(app.DATA_DIR, 'emoticons', dir_)) \
    
    159
    -            and not os.path.isdir(os.path.join(app.MY_EMOTS_PATH, dir_)) :
    
    160
    -                continue
    
    161
    -            if dir_ != '.svn':
    
    162
    -                l.append(dir_)
    
    163
    -        for i in range(len(l)):
    
    164
    -            model.append([l[i]])
    
    165
    -            if app.config.get('emoticons_theme') == l[i]:
    
    166
    -                emoticons_combobox.set_active(i)
    
    167
    -        if not app.config.get('emoticons_theme'):
    
    168
    -            emoticons_combobox.set_active(0)
    
    146
    +        emoticon_themes = helpers.get_available_emoticon_themes()
    
    147
    +
    
    148
    +        emoticons_combobox.append_text(_('Disabled'))
    
    149
    +        for theme in emoticon_themes:
    
    150
    +            emoticons_combobox.append_text(theme)
    
    151
    +
    
    152
    +        config_theme = app.config.get('emoticons_theme')
    
    153
    +        if config_theme not in emoticon_themes:
    
    154
    +            config_theme = _('Disabled')
    
    155
    +        emoticons_combobox.set_id_column(0)
    
    156
    +        emoticons_combobox.set_active_id(config_theme)
    
    169 157
     
    
    170 158
             # Set default for single window type
    
    171 159
             choices = c_config.opt_one_window_types
    

  • gajim/data/gui/chat_control.ui
    ... ... @@ -392,6 +392,8 @@
    392 392
                             <property name="visible_window">False</property>
    
    393 393
                             <child>
    
    394 394
                               <object class="GtkImage" id="avatar_image">
    
    395
    +                            <property name="width_request">48</property>
    
    396
    +                            <property name="height_request">48</property>
    
    395 397
                                 <property name="visible">True</property>
    
    396 398
                                 <property name="can_focus">False</property>
    
    397 399
                                 <property name="stock">gtk-missing-image</property>
    

  • gajim/data/gui/history_manager.ui
    1 1
     <?xml version="1.0" encoding="UTF-8"?>
    
    2
    -<!-- Generated with glade 3.18.3 -->
    
    2
    +<!-- Generated with glade 3.20.2 -->
    
    3 3
     <interface>
    
    4 4
       <requires lib="gtk+" version="3.12"/>
    
    5 5
       <object class="GtkMenu" id="context_menu">
    
    ... ... @@ -82,6 +82,9 @@
    82 82
           <action-widget response="-6">cancel_button</action-widget>
    
    83 83
           <action-widget response="-5">save_button</action-widget>
    
    84 84
         </action-widgets>
    
    85
    +    <child>
    
    86
    +      <placeholder/>
    
    87
    +    </child>
    
    85 88
       </object>
    
    86 89
       <object class="GtkImage" id="image1">
    
    87 90
         <property name="visible">True</property>
    
    ... ... @@ -136,111 +139,56 @@
    136 139
                     <property name="orientation">vertical</property>
    
    137 140
                     <child>
    
    138 141
                       <object class="GtkBox" id="welcome_vbox">
    
    142
    +                    <property name="width_request">300</property>
    
    139 143
                         <property name="visible">True</property>
    
    140 144
                         <property name="can_focus">False</property>
    
    141
    -                    <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    145
    +                    <property name="margin_left">20</property>
    
    146
    +                    <property name="margin_right">20</property>
    
    142 147
                         <property name="orientation">vertical</property>
    
    148
    +                    <property name="spacing">10</property>
    
    143 149
                         <child>
    
    144
    -                      <object class="GtkLabel" id="welcome_label">
    
    150
    +                      <object class="GtkLabel">
    
    145 151
                             <property name="visible">True</property>
    
    146 152
                             <property name="can_focus">False</property>
    
    153
    +                        <property name="halign">center</property>
    
    147 154
                             <property name="label" translatable="yes">&lt;big&gt;&lt;b&gt;Welcome to Gajim History Logs Manager&lt;/b&gt;&lt;/big&gt;</property>
    
    148 155
                             <property name="use_markup">True</property>
    
    149
    -                        <property name="justify">center</property>
    
    150
    -                        <property name="wrap">True</property>
    
    151 156
                           </object>
    
    152 157
                           <packing>
    
    153
    -                        <property name="expand">True</property>
    
    158
    +                        <property name="expand">False</property>
    
    154 159
                             <property name="fill">True</property>
    
    155 160
                             <property name="position">0</property>
    
    156 161
                           </packing>
    
    157 162
                         </child>
    
    158 163
                         <child>
    
    159
    -                      <object class="GtkBox" id="hbox3">
    
    164
    +                      <object class="GtkLabel">
    
    160 165
                             <property name="visible">True</property>
    
    161 166
                             <property name="can_focus">False</property>
    
    162
    -                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    163
    -                        <child>
    
    164
    -                          <object class="GtkImage" id="image3">
    
    165
    -                            <property name="visible">True</property>
    
    166
    -                            <property name="can_focus">False</property>
    
    167
    -                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    168
    -                            <property name="xalign">1</property>
    
    169
    -                            <property name="yalign">0</property>
    
    170
    -                            <property name="stock">gtk-info</property>
    
    171
    -                          </object>
    
    172
    -                          <packing>
    
    173
    -                            <property name="expand">True</property>
    
    174
    -                            <property name="fill">True</property>
    
    175
    -                            <property name="position">0</property>
    
    176
    -                          </packing>
    
    177
    -                        </child>
    
    178
    -                        <child>
    
    179
    -                          <object class="GtkLabel" id="info_label">
    
    180
    -                            <property name="visible">True</property>
    
    181
    -                            <property name="can_focus">False</property>
    
    182
    -                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    183
    -                            <property name="label" translatable="yes">This log manager is not intended for log viewing. If you are looking for such functionality, use the history window instead.
    
    167
    +                        <property name="halign">start</property>
    
    168
    +                        <property name="label" translatable="yes">This log manager is not intended for log viewing. If you are looking for such functionality, use the history window instead.
    
    184 169
     
    
    185 170
     Use this program to delete or export logs. You can select logs from the left and/or search database from below.</property>
    
    186
    -                            <property name="use_markup">True</property>
    
    187
    -                            <property name="wrap">True</property>
    
    188
    -                          </object>
    
    189
    -                          <packing>
    
    190
    -                            <property name="expand">True</property>
    
    191
    -                            <property name="fill">True</property>
    
    192
    -                            <property name="position">1</property>
    
    193
    -                          </packing>
    
    194
    -                        </child>
    
    171
    +                        <property name="wrap">True</property>
    
    195 172
                           </object>
    
    196 173
                           <packing>
    
    197 174
                             <property name="expand">False</property>
    
    198
    -                        <property name="fill">False</property>
    
    199
    -                        <property name="padding">20</property>
    
    175
    +                        <property name="fill">True</property>
    
    200 176
                             <property name="position">1</property>
    
    201 177
                           </packing>
    
    202 178
                         </child>
    
    203 179
                         <child>
    
    204
    -                      <object class="GtkBox" id="hbox2">
    
    180
    +                      <object class="GtkLabel">
    
    205 181
                             <property name="visible">True</property>
    
    206 182
                             <property name="can_focus">False</property>
    
    207
    -                        <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    208
    -                        <child>
    
    209
    -                          <object class="GtkImage" id="warning_image">
    
    210
    -                            <property name="visible">True</property>
    
    211
    -                            <property name="can_focus">False</property>
    
    212
    -                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    213
    -                            <property name="xalign">1</property>
    
    214
    -                            <property name="yalign">0</property>
    
    215
    -                            <property name="stock">gtk-dialog-warning</property>
    
    216
    -                          </object>
    
    217
    -                          <packing>
    
    218
    -                            <property name="expand">True</property>
    
    219
    -                            <property name="fill">True</property>
    
    220
    -                            <property name="position">0</property>
    
    221
    -                          </packing>
    
    222
    -                        </child>
    
    223
    -                        <child>
    
    224
    -                          <object class="GtkLabel" id="warning_label">
    
    225
    -                            <property name="visible">True</property>
    
    226
    -                            <property name="can_focus">False</property>
    
    227
    -                            <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
    
    228
    -                            <property name="label" translatable="yes">&lt;b&gt;WARNING:&lt;/b&gt;
    
    183
    +                        <property name="halign">start</property>
    
    184
    +                        <property name="label" translatable="yes">&lt;b&gt;WARNING:&lt;/b&gt;
    
    229 185
     If you plan to do massive deletions, please make sure Gajim is not running. Generally avoid deletions with contacts you currently chat with.</property>
    
    230
    -                            <property name="use_markup">True</property>
    
    231
    -                            <property name="wrap">True</property>
    
    232
    -                          </object>
    
    233
    -                          <packing>
    
    234
    -                            <property name="expand">True</property>
    
    235
    -                            <property name="fill">True</property>
    
    236
    -                            <property name="position">1</property>
    
    237
    -                          </packing>
    
    238
    -                        </child>
    
    186
    +                        <property name="use_markup">True</property>
    
    187
    +                        <property name="wrap">True</property>
    
    239 188
                           </object>
    
    240 189
                           <packing>
    
    241 190
                             <property name="expand">False</property>
    
    242
    -                        <property name="fill">False</property>
    
    243
    -                        <property name="padding">20</property>
    
    191
    +                        <property name="fill">True</property>
    
    244 192
                             <property name="position">2</property>
    
    245 193
                           </packing>
    
    246 194
                         </child>
    
    ... ... @@ -358,5 +306,8 @@ If you plan to do massive deletions, please make sure Gajim is not running. Gene
    358 306
             </child>
    
    359 307
           </object>
    
    360 308
         </child>
    
    309
    +    <child type="titlebar">
    
    310
    +      <placeholder/>
    
    311
    +    </child>
    
    361 312
       </object>
    
    362 313
     </interface>

  • gajim/data/gui/preferences_window.ui
    1 1
     <?xml version="1.0" encoding="UTF-8"?>
    
    2
    -<!-- Generated with glade 3.20.0 -->
    
    2
    +<!-- Generated with glade 3.20.2 -->
    
    3 3
     <interface>
    
    4 4
       <requires lib="gtk+" version="3.12"/>
    
    5 5
       <object class="GtkAdjustment" id="adjustment1">
    
    ... ... @@ -516,17 +516,6 @@
    516 516
                               </packing>
    
    517 517
                             </child>
    
    518 518
                             <child>
    
    519
    -                          <object class="GtkComboBox" id="emoticons_combobox">
    
    520
    -                            <property name="visible">True</property>
    
    521
    -                            <property name="can_focus">False</property>
    
    522
    -                            <signal name="changed" handler="on_emoticons_combobox_changed" swapped="no"/>
    
    523
    -                          </object>
    
    524
    -                          <packing>
    
    525
    -                            <property name="left_attach">1</property>
    
    526
    -                            <property name="top_attach">0</property>
    
    527
    -                          </packing>
    
    528
    -                        </child>
    
    529
    -                        <child>
    
    530 519
                               <object class="GtkCheckButton" id="show_avatar_in_tabs_checkbutton">
    
    531 520
                                 <property name="label" translatable="yes">Show avatar in chat tabs</property>
    
    532 521
                                 <property name="visible">True</property>
    
    ... ... @@ -542,6 +531,17 @@
    542 531
                                 <property name="width">2</property>
    
    543 532
                               </packing>
    
    544 533
                             </child>
    
    534
    +                        <child>
    
    535
    +                          <object class="GtkComboBoxText" id="emoticons_combobox">
    
    536
    +                            <property name="visible">True</property>
    
    537
    +                            <property name="can_focus">False</property>
    
    538
    +                            <signal name="changed" handler="on_emoticons_combobox_changed" swapped="no"/>
    
    539
    +                          </object>
    
    540
    +                          <packing>
    
    541
    +                            <property name="left_attach">1</property>
    
    542
    +                            <property name="top_attach">0</property>
    
    543
    +                          </packing>
    
    544
    +                        </child>
    
    545 545
                           </object>
    
    546 546
                         </child>
    
    547 547
                         <child type="label">
    
    ... ... @@ -2888,5 +2888,8 @@ to discover one from server.</property>
    2888 2888
             </child>
    
    2889 2889
           </object>
    
    2890 2890
         </child>
    
    2891
    +    <child type="titlebar">
    
    2892
    +      <placeholder/>
    
    2893
    +    </child>
    
    2891 2894
       </object>
    
    2892 2895
     </interface>

  • gajim/dialogs.py
    ... ... @@ -3006,18 +3006,20 @@ class ContactRow(Gtk.Grid):
    3006 3006
             self.new = jid == ''
    
    3007 3007
     
    
    3008 3008
             if self.groupchat:
    
    3009
    -            if self.new:
    
    3010
    -                muc_image = app.interface.jabber_state_images['32']['muc_inactive']
    
    3011
    -            else:
    
    3012
    -                muc_image = app.interface.jabber_state_images['32']['muc_active']
    
    3013
    -            image = Gtk.Image.new_from_pixbuf(muc_image.get_pixbuf())
    
    3009
    +            muc_icon = gtkgui_helpers.get_iconset_name_for(
    
    3010
    +                'muc-inactive' if self.new else 'muc-active')
    
    3011
    +            image = Gtk.Image.new_from_icon_name(muc_icon, Gtk.IconSize.DND)
    
    3014 3012
             else:
    
    3015
    -            avatar = app.contacts.get_avatar(account, jid, AvatarSize.ROSTER)
    
    3013
    +            scale = self.get_scale_factor()
    
    3014
    +            avatar = app.contacts.get_avatar(
    
    3015
    +                account, jid, AvatarSize.ROSTER, scale)
    
    3016 3016
                 if avatar is None:
    
    3017 3017
                     image = Gtk.Image.new_from_icon_name(
    
    3018 3018
                         'avatar-default', Gtk.IconSize.DND)
    
    3019 3019
                 else:
    
    3020
    -                image = Gtk.Image.new_from_pixbuf(avatar)
    
    3020
    +                image = Gtk.Image.new_from_surface(avatar)
    
    3021
    +
    
    3022
    +        image.set_size_request(AvatarSize.ROSTER, AvatarSize.ROSTER)
    
    3021 3023
             self.add(image)
    
    3022 3024
     
    
    3023 3025
             middle_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
    

  • gajim/groupchat_control.py
    ... ... @@ -82,60 +82,61 @@ empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1)
    82 82
     empty_pixbuf.fill(0xffffff00)
    
    83 83
     
    
    84 84
     
    
    85
    -def status_cell_data_func(column, renderer, model, iter_, user_data):
    
    85
    +def cell_data_func(column, renderer, model, iter_, user_data):
    
    86
    +    # Background color has to be rendered for all cells
    
    87
    +    theme = app.config.get('roster_theme')
    
    88
    +    has_parent = bool(model.iter_parent(iter_))
    
    89
    +    if has_parent:
    
    90
    +        bgcolor = app.config.get_per('themes', theme, 'contactbgcolor')
    
    91
    +        renderer.set_property('cell-background', bgcolor or None)
    
    92
    +    else:
    
    93
    +        bgcolor = app.config.get_per('themes', theme, 'groupbgcolor')
    
    94
    +        renderer.set_property('cell-background', bgcolor or None)
    
    95
    +
    
    96
    +    if user_data == 'status':
    
    97
    +        status_cell_data_func(column, renderer, model, iter_, has_parent)
    
    98
    +    elif user_data == 'name':
    
    99
    +        text_cell_data_func(column, renderer, model, iter_, has_parent, theme)
    
    100
    +    elif user_data == 'avatar':
    
    101
    +        avatar_cell_data_func(column, renderer, model, iter_, has_parent)
    
    102
    +
    
    103
    +def status_cell_data_func(column, renderer, model, iter_, has_parent):
    
    86 104
         renderer.set_property('width', 26)
    
    87 105
         image = model[iter_][Column.IMG]
    
    88 106
         surface = image.get_property('surface')
    
    89 107
         renderer.set_property('surface', surface)
    
    90 108
     
    
    91
    -
    
    92
    -def tree_cell_data_func(column, renderer, model, iter_, user_data):
    
    93
    -    # cell data func is global, because we don't want it to keep
    
    94
    -    # reference to GroupchatControl instance (self)
    
    95
    -    theme = app.config.get('roster_theme')
    
    96
    -    # allocate space for avatar only if needed
    
    97
    -    parent_iter = model.iter_parent(iter_)
    
    98
    -    if isinstance(renderer, Gtk.CellRendererPixbuf):
    
    99
    -        image = model[iter_][Column.AVATAR_IMG]
    
    100
    -        if image is None:
    
    101
    -            return
    
    109
    +def avatar_cell_data_func(column, renderer, model, iter_, has_parent):
    
    110
    +    image = model[iter_][Column.AVATAR_IMG]
    
    111
    +    if image is None:
    
    112
    +        renderer.set_property('surface', None)
    
    113
    +    else:
    
    102 114
             surface = image.get_property('surface')
    
    103 115
             renderer.set_property('surface', surface)
    
    104 116
     
    
    105
    -        avatar_position = app.config.get('avatar_position_in_roster')
    
    106
    -        if avatar_position == 'right':
    
    107
    -            renderer.set_property('xalign', 1) # align pixbuf to the right
    
    108
    -        else:
    
    109
    -            renderer.set_property('xalign', 0.5)
    
    110
    -        if parent_iter:
    
    111
    -            renderer.set_property('visible', True)
    
    112
    -            renderer.set_property('width', AvatarSize.ROSTER)
    
    113
    -        else:
    
    114
    -            renderer.set_property('visible', False)
    
    115
    -    if parent_iter:
    
    116
    -        bgcolor = app.config.get_per('themes', theme, 'contactbgcolor')
    
    117
    -        if bgcolor:
    
    118
    -            renderer.set_property('cell-background', bgcolor)
    
    117
    +    renderer.set_property('xalign', 0.5)
    
    118
    +    if has_parent:
    
    119
    +        renderer.set_property('visible', True)
    
    120
    +        renderer.set_property('width', AvatarSize.ROSTER)
    
    121
    +    else:
    
    122
    +        renderer.set_property('visible', False)
    
    123
    +
    
    124
    +def text_cell_data_func(column, renderer, model, iter_, has_parent, theme):
    
    125
    +    # cell data func is global, because we don't want it to keep
    
    126
    +    # reference to GroupchatControl instance (self)
    
    127
    +    if has_parent:
    
    128
    +        color = app.config.get_per('themes', theme, 'contacttextcolor')
    
    129
    +        if color:
    
    130
    +            renderer.set_property('foreground', color)
    
    119 131
             else:
    
    120
    -            renderer.set_property('cell-background', None)
    
    121
    -        if isinstance(renderer, Gtk.CellRendererText):
    
    122
    -            # foreground property is only with CellRendererText
    
    123
    -            color = app.config.get_per('themes', theme, 'contacttextcolor')
    
    124
    -            if color:
    
    125
    -                renderer.set_property('foreground', color)
    
    126
    -            else:
    
    127
    -                renderer.set_property('foreground', None)
    
    128
    -            renderer.set_property('font',
    
    129
    -                gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
    
    130
    -    else: # it is root (eg. group)
    
    131
    -        bgcolor = app.config.get_per('themes', theme, 'groupbgcolor')
    
    132
    -        renderer.set_property('cell-background', bgcolor or None)
    
    133
    -        if isinstance(renderer, Gtk.CellRendererText):
    
    134
    -            # foreground property is only with CellRendererText
    
    135
    -            color = app.config.get_per('themes', theme, 'grouptextcolor')
    
    136
    -            renderer.set_property('foreground', color or None)
    
    137
    -            renderer.set_property('font',
    
    138
    -                gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
    
    132
    +            renderer.set_property('foreground', None)
    
    133
    +        renderer.set_property('font',
    
    134
    +            gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont'))
    
    135
    +    else:
    
    136
    +        color = app.config.get_per('themes', theme, 'grouptextcolor')
    
    137
    +        renderer.set_property('foreground', color or None)
    
    138
    +        renderer.set_property('font',
    
    139
    +            gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
    
    139 140
     
    
    140 141
     
    
    141 142
     class PrivateChatControl(ChatControl):
    
    ... ... @@ -438,15 +439,15 @@ class GroupchatControl(ChatControlBase):
    438 439
             self.renderers_list += (
    
    439 440
                 # status img
    
    440 441
                 ('icon', Gtk.CellRendererPixbuf(), False,
    
    441
    -            None, Column.IMG, status_cell_data_func, None),
    
    442
    +            None, Column.IMG, cell_data_func, 'status'),
    
    442 443
                 # contact name
    
    443 444
                 ('name', renderer_text, True,
    
    444
    -            'markup', Column.TEXT, tree_cell_data_func, None))
    
    445
    +            'markup', Column.TEXT, cell_data_func, 'name'))
    
    445 446
     
    
    446 447
             # avatar img
    
    447 448
             avatar_renderer = ('avatar', Gtk.CellRendererPixbuf(),
    
    448 449
                 False, None, Column.AVATAR_IMG,
    
    449
    -            tree_cell_data_func, None)
    
    450
    +            cell_data_func, 'avatar')
    
    450 451
     
    
    451 452
             if app.config.get('avatar_position_in_roster') == 'right':
    
    452 453
                 self.renderers_list.append(avatar_renderer)
    
    ... ... @@ -1109,8 +1110,9 @@ class GroupchatControl(ChatControlBase):
    1109 1110
                 return
    
    1110 1111
             self.print_conversation(
    
    1111 1112
                 obj.msgtxt, contact=obj.nick,
    
    1112
    -            tim=obj.timestamp, encrypted=obj.encrypted,
    
    1113
    -            msg_stanza_id=obj.unique_id,
    
    1113
    +            tim=obj.timestamp, correct_id=obj.correct_id,
    
    1114
    +            encrypted=obj.encrypted,
    
    1115
    +            msg_stanza_id=obj.message_id,
    
    1114 1116
                 additional_data=obj.additional_data)
    
    1115 1117
     
    
    1116 1118
         def _nec_gc_message_received(self, obj):
    

  • gajim/gtkgui_helpers.py
    ... ... @@ -437,6 +437,16 @@ def get_possible_button_event(event):
    437 437
     def destroy_widget(widget):
    
    438 438
         widget.destroy()
    
    439 439
     
    
    440
    +def scale_with_ratio(size, width, height):
    
    441
    +    if height == width:
    
    442
    +        return size, size
    
    443
    +    if height > width:
    
    444
    +        ratio = height / float(width)
    
    445
    +        return int(size / ratio), size 
    
    446
    +    else:
    
    447
    +        ratio = width / float(height)
    
    448
    +        return size, int(size / ratio)
    
    449
    +
    
    440 450
     def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''):
    
    441 451
         def on_continue(response, file_path):
    
    442 452
             if response < 0:
    

  • gajim/gui_interface.py
    ... ... @@ -1996,18 +1996,16 @@ class Interface:
    1996 1996
             if 'preferences' in app.interface.instances:
    
    1997 1997
                 transient_for = app.interface.instances['preferences'].window
    
    1998 1998
     
    
    1999
    -        path = os.path.join(app.DATA_DIR, 'emoticons', emot_theme)
    
    2000
    -        if not os.path.exists(path):
    
    2001
    -            # It's maybe a user theme
    
    2002
    -            path = os.path.join(app.MY_EMOTS_PATH, emot_theme)
    
    2003
    -            if not os.path.exists(path):
    
    2004
    -                # theme doesn't exist, disable emoticons
    
    2005
    -                dialogs.WarningDialog(_('Emoticons disabled'),
    
    2006
    -                    _('Your configured emoticons theme has not been found, so '
    
    2007
    -                    'emoticons have been disabled.'),
    
    2008
    -                    transient_for=transient_for)
    
    1999
    +        themes = helpers.get_available_emoticon_themes()
    
    2000
    +        if emot_theme not in themes:
    
    2001
    +            if 'font-emoticons' in themes:
    
    2002
    +                emot_theme = 'font-emoticons'
    
    2003
    +                app.config.set('emoticons_theme', 'font-emoticons')
    
    2004
    +            else:
    
    2009 2005
                     app.config.set('emoticons_theme', '')
    
    2010 2006
                     return
    
    2007
    +
    
    2008
    +        path = helpers.get_emoticon_theme_path(emot_theme)
    
    2011 2009
             if not emoticons.load(path, ascii_emoticons):
    
    2012 2010
                 dialogs.WarningDialog(
    
    2013 2011
                         _('Emoticons disabled'),
    
    ... ... @@ -2482,7 +2480,7 @@ class Interface:
    2482 2480
             try:
    
    2483 2481
                 if size is not None:
    
    2484 2482
                     pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(
    
    2485
    -                    path, size, size, False)
    
    2483
    +                    path, size, size, True)
    
    2486 2484
                 else:
    
    2487 2485
                     pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
    
    2488 2486
             except GLib.GError as error:
    
    ... ... @@ -2503,8 +2501,10 @@ class Interface:
    2503 2501
                     array, GdkPixbuf.Colorspace.RGB, True,
    
    2504 2502
                     8, width, height, width * 4)
    
    2505 2503
                 if size:
    
    2504
    +                width, height = gtkgui_helpers.scale_with_ratio(
    
    2505
    +                    size, width, height)
    
    2506 2506
                     pixbuf = pixbuf.scale_simple(
    
    2507
    -                    size, size, GdkPixbuf.InterpType.BILINEAR)
    
    2507
    +                    width, height, GdkPixbuf.InterpType.BILINEAR)
    
    2508 2508
     
    
    2509 2509
             if filename not in app.avatar_cache:
    
    2510 2510
                 app.avatar_cache[filename] = {}
    

  • gajim/roster_window.py
    ... ... @@ -4864,11 +4864,8 @@ class RosterWindow:
    4864 4864
             app.config.get('avatar_position_in_roster') != 'left':
    
    4865 4865
                 renderer.set_property('visible', False)
    
    4866 4866
     
    
    4867
    -        if app.config.get('avatar_position_in_roster') == 'left':
    
    4868
    -            renderer.set_property('width', AvatarSize.ROSTER)
    
    4869
    -            renderer.set_property('xalign', 0.5)
    
    4870
    -        else:
    
    4871
    -            renderer.set_property('xalign', 1) # align pixbuf to the right
    
    4867
    +        renderer.set_property('width', AvatarSize.ROSTER)
    
    4868
    +        renderer.set_property('xalign', 0.5)
    
    4872 4869
     
    
    4873 4870
         def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer,
    
    4874 4871
         data=None):
    

  • gajim/session.py
    ... ... @@ -101,7 +101,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
    101 101
             if treat_as:
    
    102 102
                 obj.mtype = treat_as
    
    103 103
             pm = False
    
    104
    -        if obj.gc_control and obj.resource:
    
    104
    +        if obj.muc_pm or (obj.gc_control and obj.resource):
    
    105 105
                 # It's a Private message
    
    106 106
                 pm = True
    
    107 107
                 obj.mtype = 'pm'
    
    ... ... @@ -123,6 +123,11 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
    123 123
                     additional_data=obj.additional_data,
    
    124 124
                     stanza_id=obj.unique_id)
    
    125 125
     
    
    126
    +        if obj.muc_pm and not obj.gc_control:
    
    127
    +            # This is a carbon of a PM from a MUC we are not currently
    
    128
    +            # joined. We log it silently without notification.
    
    129
    +            return True
    
    130
    +
    
    126 131
             # Handle chat states
    
    127 132
             if contact and (not obj.forwarded or not obj.sent):
    
    128 133
                 if self.control and self.control.type_id == \
    

  • gajim/statusicon.py
    ... ... @@ -245,7 +245,7 @@ class StatusIcon:
    245 245
     
    
    246 246
                 for account in accounts_list:
    
    247 247
                     account_label = app.config.get_per(
    
    248
    -                    'accounts', account, 'account_label')
    
    248
    +                    'accounts', account, 'account_label') or account
    
    249 249
                     if app.account_is_connected(account):
    
    250 250
                         # for chat_with
    
    251 251
                         item = Gtk.MenuItem.new_with_label(
    
    ... ... @@ -291,7 +291,7 @@ class StatusIcon:
    291 291
     
    
    292 292
                 for account in accounts_list:
    
    293 293
                     account_label = app.config.get_per(
    
    294
    -                    'accounts', account, 'account_label')
    
    294
    +                    'accounts', account, 'account_label') or account
    
    295 295
                     if app.connections[account].is_zeroconf or \
    
    296 296
                     not app.account_is_connected(account):
    
    297 297
                         continue
    

  • gajim/tooltips.py
    ... ... @@ -252,6 +252,9 @@ class GCTooltip():
    252 252
                     self.avatar.show()
    
    253 253
                     self.fillelement.show()
    
    254 254
     
    
    255
    +        app.plugin_manager.gui_extension_point(
    
    256
    +            'gc_tooltip_populate', self, contact, self.tooltip_grid)
    
    257
    +
    
    255 258
         @staticmethod
    
    256 259
         def colorize_affiliation(affiliation):
    
    257 260
             """
    

  • setup.py
    ... ... @@ -6,8 +6,6 @@ import sys
    6 6
     if sys.version_info[0] < 3:
    
    7 7
         sys.exit('Tried to install with Python 2, gajim only supports Python 3.')
    
    8 8
     
    
    9
    -import codecs
    
    10
    -
    
    11 9
     from setuptools import setup, find_packages
    
    12 10
     from setuptools import Command
    
    13 11
     from setuptools.command.build_py import build_py as _build
    
    ... ... @@ -129,20 +127,6 @@ def build_intl(build_cmd):
    129 127
             data_files.append((target, [base + '/' + filename]))
    
    130 128
     
    
    131 129
     
    
    132
    -def substitute_variables(filename_in, filename_out, subst_vars):
    
    133
    -    '''
    
    134
    -    Substitute variables in a file.
    
    135
    -    '''
    
    136
    -    f_in = codecs.open(filename_in, encoding='utf-8')
    
    137
    -    f_out = codecs.open(filename_out, encoding='utf-8', mode='w')
    
    138
    -    for line in f_in:
    
    139
    -        for variable, substitution in subst_vars:
    
    140
    -            line = line.replace(variable, substitution)
    
    141
    -        f_out.write(line)
    
    142
    -    f_in.close()
    
    143
    -    f_out.close()
    
    144
    -
    
    145
    -
    
    146 130
     def merge(in_file, out_file, option, po_dir='po'):
    
    147 131
         '''
    
    148 132
         Run the msgfmt command.
    
    ... ... @@ -276,7 +260,7 @@ setup(
    276 260
         package_data={'gajim': package_data},
    
    277 261
         data_files=data_files,
    
    278 262
         install_requires=[
    
    279
    -        'nbxmpp>=0.6.1',
    
    263
    +        'nbxmpp>=0.6.3',
    
    280 264
             'pyOpenSSL>=0.12',
    
    281 265
             'pyasn1',
    
    282 266
         ],