Philipp Hörist pushed to branch gajim_1.0 at gajim / gajim
Commits:
-
a967cf1b
by Philipp Hörist at 2018-03-05T23:00:47+01:00
-
cca8c0c3
by Philipp Hörist at 2018-03-05T23:01:29+01:00
-
fa15baf1
by Philipp Hörist at 2018-03-05T23:02:01+01:00
-
9cc7df89
by Philipp Hörist at 2018-03-05T23:03:23+01:00
-
e5dbcc7c
by Philipp Hörist at 2018-03-05T23:03:38+01:00
-
f7e01e43
by Philipp Hörist at 2018-03-05T23:03:58+01:00
-
bbb75570
by Philipp Hörist at 2018-03-05T23:04:09+01:00
-
c1e482a1
by Philipp Hörist at 2018-03-05T23:08:11+01:00
-
2fdaf391
by Philipp Hörist at 2018-03-05T23:08:26+01:00
-
931d138b
by Philipp Hörist at 2018-03-05T23:10:35+01:00
-
a0c8424f
by Philipp Hörist at 2018-03-05T23:10:36+01:00
-
843e56a7
by Philipp Hörist at 2018-03-05T23:10:36+01:00
-
0ffb4537
by Philipp Hörist at 2018-03-05T23:10:36+01:00
-
6e6d9497
by Philipp Hörist at 2018-03-05T23:10:36+01:00
-
fb1945e6
by Philipp Hörist at 2018-03-05T23:10:37+01:00
-
41828335
by André Apitzsch at 2018-03-05T23:12:26+01:00
-
062e69f0
by Philipp Hörist at 2018-03-05T23:12:26+01:00
-
000ac263
by Philipp Hörist at 2018-03-05T23:12:26+01:00
-
6793aa65
by Philipp Hörist at 2018-03-05T23:12:27+01:00
-
06759167
by Philipp Hörist at 2018-03-05T23:13:17+01:00
-
4a233d87
by Philipp Hörist at 2018-03-05T23:13:17+01:00
-
c3ddf476
by Philipp Hörist at 2018-03-05T23:13:18+01:00
-
3808df05
by Philipp Hörist at 2018-03-05T23:13:18+01:00
21 changed files:
- gajim/chat_control.py
- gajim/chat_control_base.py
- gajim/common/connection.py
- gajim/common/connection_handlers_events.py
- gajim/common/contacts.py
- gajim/common/helpers.py
- gajim/common/logger.py
- gajim/common/message_archiving.py
- gajim/config.py
- gajim/data/gui/chat_control.ui
- gajim/data/gui/history_manager.ui
- gajim/data/gui/preferences_window.ui
- gajim/dialogs.py
- gajim/groupchat_control.py
- gajim/gtkgui_helpers.py
- gajim/gui_interface.py
- gajim/roster_window.py
- gajim/session.py
- gajim/statusicon.py
- gajim/tooltips.py
- setup.py
Changes:
| ... | ... | @@ -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()
|
| ... | ... | @@ -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)
|
| ... | ... | @@ -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:
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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:
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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 |
|
| ... | ... | @@ -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,
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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>
|
| 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"><big><b>Welcome to Gajim History Logs Manager</b></big></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"><b>WARNING:</b>
|
|
| 183 |
+ <property name="halign">start</property>
|
|
| 184 |
+ <property name="label" translatable="yes"><b>WARNING:</b>
|
|
| 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>
|
| 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>
|
| ... | ... | @@ -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)
|
| ... | ... | @@ -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):
|
| ... | ... | @@ -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:
|
| ... | ... | @@ -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] = {}
|
| ... | ... | @@ -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):
|
| ... | ... | @@ -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 == \
|
| ... | ... | @@ -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
|
| ... | ... | @@ -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 |
"""
|
| ... | ... | @@ -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 |
],
|