Daniel Brötzmann pushed to branch chat_markers at gajim / gajim
Commits:
-
39805924
by lovetox at 2020-10-11T07:46:16+02:00
-
362c3d01
by lovetox at 2020-10-11T10:26:41+02:00
-
bb740ca4
by lovetox at 2020-10-11T10:27:59+02:00
-
159be3fa
by André Apitzsch at 2020-10-11T12:28:05+02:00
-
fc8b5f28
by Yann Leboulanger at 2020-10-11T15:09:41+02:00
-
01e60650
by wurstsalat at 2020-10-11T15:09:41+02:00
27 changed files:
- gajim/chat_control.py
- gajim/chat_control_base.py
- gajim/common/const.py
- gajim/common/events.py
- gajim/common/jingle_rtp.py
- gajim/common/modules/__init__.py
- + gajim/common/modules/chat_markers.py
- gajim/common/modules/message.py
- gajim/common/modules/metacontacts.py
- gajim/common/setting_values.py
- gajim/common/zeroconf/connection_zeroconf.py
- gajim/conversation_textview.py
- gajim/data/style/gajim.css
- gajim/groupchat_control.py
- gajim/gtk/about.py
- gajim/gtk/add_contact.py
- gajim/gtk/bookmarks.py
- gajim/gtk/filetransfer.py
- gajim/gtk/groupchat_settings.py
- gajim/gtk/history_sync.py
- gajim/gtk/pep_config.py
- gajim/gtk/preferences.py
- gajim/gtk/roster_item_exchange.py
- gajim/gtk/start_chat.py
- gajim/gui_interface.py
- gajim/gui_menu_builder.py
- gajim/roster_window.py
Changes:
| ... | ... | @@ -123,7 +123,7 @@ class ChatControl(ChatControlBase): |
| 123 | 123 |
|
| 124 | 124 |
# Menu for the HeaderBar
|
| 125 | 125 |
self.control_menu = gui_menu_builder.get_singlechat_menu(
|
| 126 |
- self.control_id, self.account, self.contact.jid)
|
|
| 126 |
+ self.control_id, self.account, self.contact.jid, self._type)
|
|
| 127 | 127 |
|
| 128 | 128 |
# Settings menu
|
| 129 | 129 |
self.xml.settings_menu.set_menu_model(self.control_menu)
|
| ... | ... | @@ -228,6 +228,7 @@ class ChatControl(ChatControlBase): |
| 228 | 228 |
('mam-decrypted-message-received', ged.GUI1, self._on_mam_decrypted_message_received),
|
| 229 | 229 |
('decrypted-message-received', ged.GUI1, self._on_decrypted_message_received),
|
| 230 | 230 |
('receipt-received', ged.GUI1, self._receipt_received),
|
| 231 |
+ ('displayed-received', ged.GUI1, self._displayed_received),
|
|
| 231 | 232 |
('message-error', ged.GUI1, self._on_message_error),
|
| 232 | 233 |
('zeroconf-error', ged.GUI1, self._on_zeroconf_error),
|
| 233 | 234 |
])
|
| ... | ... | @@ -271,6 +272,15 @@ class ChatControl(ChatControlBase): |
| 271 | 272 |
act.connect('change-state', self._on_send_chatstate)
|
| 272 | 273 |
self.parent_win.window.add_action(act)
|
| 273 | 274 |
|
| 275 |
+ marker = self.contact.settings.get('send_marker')
|
|
| 276 |
+ |
|
| 277 |
+ act = Gio.SimpleAction.new_stateful(
|
|
| 278 |
+ f'send-marker-{self.control_id}',
|
|
| 279 |
+ None,
|
|
| 280 |
+ GLib.Variant.new_boolean(marker))
|
|
| 281 |
+ act.connect('change-state', self._on_send_marker)
|
|
| 282 |
+ self.parent_win.window.add_action(act)
|
|
| 283 |
+ |
|
| 274 | 284 |
def update_actions(self):
|
| 275 | 285 |
win = self.parent_win.window
|
| 276 | 286 |
online = app.account_is_connected(self.account)
|
| ... | ... | @@ -363,6 +373,7 @@ class ChatControl(ChatControlBase): |
| 363 | 373 |
'information-',
|
| 364 | 374 |
'start-call-',
|
| 365 | 375 |
'send-chatstate-',
|
| 376 |
+ 'send-marker',
|
|
| 366 | 377 |
]
|
| 367 | 378 |
for action in actions:
|
| 368 | 379 |
self.parent_win.window.remove_action(f'{action}{self.control_id}')
|
| ... | ... | @@ -435,6 +446,10 @@ class ChatControl(ChatControlBase): |
| 435 | 446 |
action.set_state(param)
|
| 436 | 447 |
self.contact.settings.set('send_chatstate', param.get_string())
|
| 437 | 448 |
|
| 449 |
+ def _on_send_marker(self, action, param):
|
|
| 450 |
+ action.set_state(param)
|
|
| 451 |
+ self.contact.settings.set('send_marker', param.get_boolean())
|
|
| 452 |
+ |
|
| 438 | 453 |
def subscribe_events(self):
|
| 439 | 454 |
"""
|
| 440 | 455 |
Register listeners to the events class
|
| ... | ... | @@ -643,6 +658,10 @@ class ChatControl(ChatControlBase): |
| 643 | 658 |
def _receipt_received(self, event):
|
| 644 | 659 |
self.conv_textview.show_receipt(event.receipt_id)
|
| 645 | 660 |
|
| 661 |
+ @event_filter(['account', 'jid'])
|
|
| 662 |
+ def _displayed_received(self, event):
|
|
| 663 |
+ self.conv_textview.show_displayed(event.marker_id)
|
|
| 664 |
+ |
|
| 646 | 665 |
@event_filter(['account', 'jid'])
|
| 647 | 666 |
def _on_zeroconf_error(self, event):
|
| 648 | 667 |
self.add_status_message(event.message)
|
| ... | ... | @@ -1384,6 +1403,14 @@ class ChatControl(ChatControlBase): |
| 1384 | 1403 |
self.set_session(event.session)
|
| 1385 | 1404 |
if message_ids:
|
| 1386 | 1405 |
app.storage.archive.set_read_messages(message_ids)
|
| 1406 |
+ |
|
| 1407 |
+ # XEP-0333 Send <displayed> marker
|
|
| 1408 |
+ con = app.connections[self.account]
|
|
| 1409 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1410 |
+ self.contact,
|
|
| 1411 |
+ self.last_msg_id,
|
|
| 1412 |
+ self._type)
|
|
| 1413 |
+ self.last_msg_id = None
|
|
| 1387 | 1414 |
app.events.remove_events(self.account,
|
| 1388 | 1415 |
jid_with_resource,
|
| 1389 | 1416 |
types=[str(self._type)])
|
| ... | ... | @@ -214,6 +214,9 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 214 | 214 |
self.received_history_pos = 0
|
| 215 | 215 |
self.orig_msg = None
|
| 216 | 216 |
|
| 217 |
+ # For XEP-0333
|
|
| 218 |
+ self.last_msg_id = None
|
|
| 219 |
+ |
|
| 217 | 220 |
self.correcting = False
|
| 218 | 221 |
self.last_sent_msg = None
|
| 219 | 222 |
|
| ... | ... | @@ -1154,6 +1157,9 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 1154 | 1157 |
if restored:
|
| 1155 | 1158 |
return
|
| 1156 | 1159 |
|
| 1160 |
+ if message_id:
|
|
| 1161 |
+ self.last_msg_id = message_id
|
|
| 1162 |
+ |
|
| 1157 | 1163 |
if kind == 'incoming':
|
| 1158 | 1164 |
if (not self._type.is_groupchat or
|
| 1159 | 1165 |
self.contact.can_notify() or
|
| ... | ... | @@ -1198,7 +1204,9 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 1198 | 1204 |
|
| 1199 | 1205 |
event = event_type(text,
|
| 1200 | 1206 |
subject,
|
| 1201 |
- self, msg_log_id,
|
|
| 1207 |
+ self,
|
|
| 1208 |
+ msg_log_id,
|
|
| 1209 |
+ message_id=message_id,
|
|
| 1202 | 1210 |
show_in_roster=show_in_roster,
|
| 1203 | 1211 |
show_in_systray=show_in_systray)
|
| 1204 | 1212 |
app.events.add_event(self.account, full_jid, event)
|
| ... | ... | @@ -1334,7 +1342,7 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 1334 | 1342 |
_('Privacy'),
|
| 1335 | 1343 |
_('Warning'),
|
| 1336 | 1344 |
_('If you send a file to <b>%s</b>, your real XMPP '
|
| 1337 |
- 'address will be revealed.' % gc_contact.name),
|
|
| 1345 |
+ 'address will be revealed.') % gc_contact.name,
|
|
| 1338 | 1346 |
[DialogButton.make('Cancel'),
|
| 1339 | 1347 |
DialogButton.make(
|
| 1340 | 1348 |
'OK',
|
| ... | ... | @@ -1350,7 +1358,7 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 1350 | 1358 |
jid = self.contact.jid
|
| 1351 | 1359 |
if self.conv_textview.autoscroll:
|
| 1352 | 1360 |
# we are at the end
|
| 1353 |
- type_ = ['printed_%s' % self._type]
|
|
| 1361 |
+ type_ = [f'printed_{self._type}']
|
|
| 1354 | 1362 |
if self._type.is_groupchat:
|
| 1355 | 1363 |
type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
|
| 1356 | 1364 |
if not app.events.remove_events(self.account,
|
| ... | ... | @@ -1358,6 +1366,12 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 1358 | 1366 |
types=type_):
|
| 1359 | 1367 |
# There were events to remove
|
| 1360 | 1368 |
self.redraw_after_event_removed(jid)
|
| 1369 |
+ # XEP-0333 Send <displayed> marker
|
|
| 1370 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1371 |
+ self.contact,
|
|
| 1372 |
+ self.last_msg_id,
|
|
| 1373 |
+ self._type)
|
|
| 1374 |
+ self.last_msg_id = None
|
|
| 1361 | 1375 |
# send chatstate inactive to the one we're leaving
|
| 1362 | 1376 |
# and active to the one we visit
|
| 1363 | 1377 |
if self.msg_textview.has_text():
|
| ... | ... | @@ -1386,20 +1400,27 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 1386 | 1400 |
types_list = []
|
| 1387 | 1401 |
if self._type.is_groupchat:
|
| 1388 | 1402 |
types_list = ['printed_gc_msg', 'gc_msg', 'printed_marked_gc_msg']
|
| 1389 |
- else: # Not a GC
|
|
| 1390 |
- types_list = ['printed_%s' % self._type, str(self._type)]
|
|
| 1403 |
+ else:
|
|
| 1404 |
+ types_list = [f'printed_{self._type}', str(self._type)]
|
|
| 1391 | 1405 |
|
| 1392 | 1406 |
if not app.events.get_events(self.account, jid, types_list):
|
| 1393 | 1407 |
return
|
| 1394 | 1408 |
if not self.parent_win:
|
| 1395 | 1409 |
return
|
| 1396 |
- if self.parent_win.get_active_control() == self and \
|
|
| 1397 |
- self.parent_win.window.is_active():
|
|
| 1410 |
+ if (self.parent_win.get_active_control() == self and
|
|
| 1411 |
+ self.parent_win.window.is_active()):
|
|
| 1398 | 1412 |
# we are at the end
|
| 1399 | 1413 |
if not app.events.remove_events(
|
| 1400 | 1414 |
self.account, jid, types=types_list):
|
| 1401 | 1415 |
# There were events to remove
|
| 1402 | 1416 |
self.redraw_after_event_removed(jid)
|
| 1417 |
+ # XEP-0333 Send <displayed> tag
|
|
| 1418 |
+ con = app.connections[self.account]
|
|
| 1419 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 1420 |
+ self.contact,
|
|
| 1421 |
+ self.last_msg_id,
|
|
| 1422 |
+ self._type)
|
|
| 1423 |
+ self.last_msg_id = None
|
|
| 1403 | 1424 |
|
| 1404 | 1425 |
def _on_scrollbar_button_release(self, scrollbar, event):
|
| 1405 | 1426 |
if event.get_button()[1] != 1:
|
| ... | ... | @@ -1464,6 +1485,8 @@ class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper): |
| 1464 | 1485 |
We just removed a 'printed_*' event, redraw contact in roster or
|
| 1465 | 1486 |
gc_roster and titles in roster and msg_win
|
| 1466 | 1487 |
"""
|
| 1488 |
+ if not self.parent_win: # minimized groupchat
|
|
| 1489 |
+ return
|
|
| 1467 | 1490 |
self.parent_win.redraw_tab(self)
|
| 1468 | 1491 |
self.parent_win.show_title()
|
| 1469 | 1492 |
# TODO : get the contact and check get_show_in_roster()
|
| ... | ... | @@ -970,6 +970,7 @@ COMMON_FEATURES = [ |
| 970 | 970 |
Namespace.SECLABEL,
|
| 971 | 971 |
Namespace.CONFERENCE,
|
| 972 | 972 |
Namespace.CORRECT,
|
| 973 |
+ Namespace.CHATMARKERS,
|
|
| 973 | 974 |
Namespace.EME,
|
| 974 | 975 |
Namespace.XHTML_IM,
|
| 975 | 976 |
Namespace.HASHES_2,
|
| ... | ... | @@ -91,13 +91,14 @@ class PmEvent(ChatEvent): |
| 91 | 91 |
class PrintedChatEvent(Event):
|
| 92 | 92 |
type_ = 'printed_chat'
|
| 93 | 93 |
def __init__(self, message, subject, control, msg_log_id, time_=None,
|
| 94 |
- show_in_roster=False, show_in_systray=True):
|
|
| 94 |
+ message_id=None, show_in_roster=False, show_in_systray=True):
|
|
| 95 | 95 |
Event.__init__(self, time_, show_in_roster=show_in_roster,
|
| 96 | 96 |
show_in_systray=show_in_systray)
|
| 97 | 97 |
self.message = message
|
| 98 | 98 |
self.subject = subject
|
| 99 | 99 |
self.control = control
|
| 100 | 100 |
self.msg_log_id = msg_log_id
|
| 101 |
+ self.message_id = message_id
|
|
| 101 | 102 |
|
| 102 | 103 |
class PrintedGcMsgEvent(PrintedChatEvent):
|
| 103 | 104 |
type_ = 'printed_gc_msg'
|
| ... | ... | @@ -130,7 +130,7 @@ class JingleRTPContent(JingleContent): |
| 130 | 130 |
None,
|
| 131 | 131 |
conn=self.session.connection,
|
| 132 | 132 |
level='error',
|
| 133 |
- pri_txt=_('%s configuration error' % text),
|
|
| 133 |
+ pri_txt=_('%s configuration error') % text,
|
|
| 134 | 134 |
sec_txt=_('Couldn’t set up %(text)s. Check your '
|
| 135 | 135 |
'configuration.\nPipeline:\n%(pipeline)s\n'
|
| 136 | 136 |
'Error:\n%(error)s') % {
|
| ... | ... | @@ -40,6 +40,7 @@ MODULES = [ |
| 40 | 40 |
'bookmarks',
|
| 41 | 41 |
'caps',
|
| 42 | 42 |
'carbons',
|
| 43 |
+ 'chat_markers',
|
|
| 43 | 44 |
'chatstates',
|
| 44 | 45 |
'delimiter',
|
| 45 | 46 |
'discovery',
|
| 1 |
+# This file is part of Gajim.
|
|
| 2 |
+#
|
|
| 3 |
+# Gajim is free software; you can redistribute it and/or modify
|
|
| 4 |
+# it under the terms of the GNU General Public License as published
|
|
| 5 |
+# by the Free Software Foundation; version 3 only.
|
|
| 6 |
+#
|
|
| 7 |
+# Gajim is distributed in the hope that it will be useful,
|
|
| 8 |
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
| 9 |
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
| 10 |
+# GNU General Public License for more details.
|
|
| 11 |
+#
|
|
| 12 |
+# You should have received a copy of the GNU General Public License
|
|
| 13 |
+# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
| 14 |
+ |
|
| 15 |
+# Chat Markers (XEP-0333)
|
|
| 16 |
+ |
|
| 17 |
+import nbxmpp
|
|
| 18 |
+from nbxmpp.namespaces import Namespace
|
|
| 19 |
+from nbxmpp.structs import StanzaHandler
|
|
| 20 |
+ |
|
| 21 |
+from gajim.common import app
|
|
| 22 |
+from gajim.common.nec import NetworkEvent
|
|
| 23 |
+from gajim.common.modules.base import BaseModule
|
|
| 24 |
+ |
|
| 25 |
+ |
|
| 26 |
+class ChatMarkers(BaseModule):
|
|
| 27 |
+ |
|
| 28 |
+ _nbxmpp_extends = 'ChatMarkers'
|
|
| 29 |
+ |
|
| 30 |
+ def __init__(self, con):
|
|
| 31 |
+ BaseModule.__init__(self, con)
|
|
| 32 |
+ |
|
| 33 |
+ self.handlers = [
|
|
| 34 |
+ StanzaHandler(name='message',
|
|
| 35 |
+ callback=self._process_chat_marker,
|
|
| 36 |
+ ns=Namespace.CHATMARKERS,
|
|
| 37 |
+ priority=47),
|
|
| 38 |
+ ]
|
|
| 39 |
+ |
|
| 40 |
+ def _process_chat_marker(self, _con, _stanza, properties):
|
|
| 41 |
+ if not properties.is_marker or not properties.marker.is_displayed:
|
|
| 42 |
+ return
|
|
| 43 |
+ |
|
| 44 |
+ if properties.type.is_error:
|
|
| 45 |
+ return
|
|
| 46 |
+ |
|
| 47 |
+ if properties.type.is_groupchat:
|
|
| 48 |
+ manager = self._con.get_module('MUC').get_manager()
|
|
| 49 |
+ muc_data = manager.get(properties.muc_jid)
|
|
| 50 |
+ if muc_data is None:
|
|
| 51 |
+ return
|
|
| 52 |
+ |
|
| 53 |
+ if properties.muc_nickname != muc_data.nick:
|
|
| 54 |
+ return
|
|
| 55 |
+ |
|
| 56 |
+ self._raise_event('read-state-sync', properties)
|
|
| 57 |
+ return
|
|
| 58 |
+ |
|
| 59 |
+ if properties.is_carbon_message and properties.carbon.is_sent:
|
|
| 60 |
+ self._raise_event('read-state-sync', properties)
|
|
| 61 |
+ return
|
|
| 62 |
+ |
|
| 63 |
+ if properties.is_mam_message:
|
|
| 64 |
+ if properties.from_.bareMatch(self._con.get_own_jid()):
|
|
| 65 |
+ return
|
|
| 66 |
+ |
|
| 67 |
+ self._raise_event('displayed-received', properties)
|
|
| 68 |
+ |
|
| 69 |
+ def _raise_event(self, name, properties):
|
|
| 70 |
+ self._log.info('%s: %s %s',
|
|
| 71 |
+ name,
|
|
| 72 |
+ properties.jid,
|
|
| 73 |
+ properties.marker.id)
|
|
| 74 |
+ |
|
| 75 |
+ jid = properties.jid
|
|
| 76 |
+ if not properties.is_muc_pm and not properties.type.is_groupchat:
|
|
| 77 |
+ jid = properties.jid.bare
|
|
| 78 |
+ |
|
| 79 |
+ app.nec.push_outgoing_event(
|
|
| 80 |
+ NetworkEvent(name,
|
|
| 81 |
+ account=self._account,
|
|
| 82 |
+ jid=jid,
|
|
| 83 |
+ properties=properties,
|
|
| 84 |
+ type=properties.type,
|
|
| 85 |
+ is_muc_pm=properties.is_muc_pm,
|
|
| 86 |
+ marker_id=properties.marker.id))
|
|
| 87 |
+ |
|
| 88 |
+ def _send_marker(self, contact, marker, id_, type_):
|
|
| 89 |
+ jid = contact.jid
|
|
| 90 |
+ if contact.is_pm_contact:
|
|
| 91 |
+ jid = app.get_jid_without_resource(contact.jid)
|
|
| 92 |
+ |
|
| 93 |
+ if type_ in ('gc', 'pm'):
|
|
| 94 |
+ if not app.settings.get_group_chat_setting(
|
|
| 95 |
+ self._account, jid, 'send_marker'):
|
|
| 96 |
+ return
|
|
| 97 |
+ else:
|
|
| 98 |
+ if not app.settings.get_contact_setting(
|
|
| 99 |
+ self._account, jid, 'send_marker'):
|
|
| 100 |
+ return
|
|
| 101 |
+ |
|
| 102 |
+ typ = 'groupchat' if type_ == 'gc' else 'chat'
|
|
| 103 |
+ message = nbxmpp.Message(to=contact.jid, typ=typ)
|
|
| 104 |
+ message.setMarker(marker, id_)
|
|
| 105 |
+ self._log.info('Send %s: %s', marker, contact.jid)
|
|
| 106 |
+ self._nbxmpp().send(message)
|
|
| 107 |
+ |
|
| 108 |
+ def send_displayed_marker(self, contact, id_, type_):
|
|
| 109 |
+ self._send_marker(contact, 'displayed', id_, str(type_))
|
|
| 110 |
+ |
|
| 111 |
+ |
|
| 112 |
+def get_instance(*args, **kwargs):
|
|
| 113 |
+ return ChatMarkers(*args, **kwargs), 'ChatMarkers'
|
| ... | ... | @@ -305,6 +305,8 @@ class Message(BaseModule): |
| 305 | 305 |
stanza.setID(message.message_id)
|
| 306 | 306 |
stanza.setOriginID(message.message_id)
|
| 307 | 307 |
|
| 308 |
+ stanza.setMarkable()
|
|
| 309 |
+ |
|
| 308 | 310 |
if message.label:
|
| 309 | 311 |
stanza.addChild(node=message.label)
|
| 310 | 312 |
|
| ... | ... | @@ -347,6 +349,10 @@ class Message(BaseModule): |
| 347 | 349 |
stanza.setTag('no-store',
|
| 348 | 350 |
namespace=Namespace.MSG_HINTS)
|
| 349 | 351 |
|
| 352 |
+ # XEP-0333
|
|
| 353 |
+ if message.message:
|
|
| 354 |
+ stanza.setMarkable()
|
|
| 355 |
+ |
|
| 350 | 356 |
# Add other nodes
|
| 351 | 357 |
if message.nodes is not None:
|
| 352 | 358 |
for node in message.nodes:
|
| ... | ... | @@ -30,6 +30,10 @@ class MetaContacts(BaseModule): |
| 30 | 30 |
self.available = False
|
| 31 | 31 |
|
| 32 | 32 |
def get_metacontacts(self):
|
| 33 |
+ if not app.settings.get('metacontacts_enabled'):
|
|
| 34 |
+ self._con.connect_machine()
|
|
| 35 |
+ return
|
|
| 36 |
+ |
|
| 33 | 37 |
self._log.info('Request')
|
| 34 | 38 |
node = nbxmpp.Node('storage', attrs={'xmlns': 'storage:metacontacts'})
|
| 35 | 39 |
iq = nbxmpp.Iq('get', Namespace.PRIVATE, payload=node)
|
| ... | ... | @@ -139,6 +139,7 @@ APP_SETTINGS = { |
| 139 | 139 |
'chat_merge_consecutive_nickname': False,
|
| 140 | 140 |
'chat_merge_consecutive_nickname_indent': ' ',
|
| 141 | 141 |
'ctrl_tab_go_to_next_composing': True,
|
| 142 |
+ 'metacontacts_enabled': True,
|
|
| 142 | 143 |
'confirm_metacontacts': '',
|
| 143 | 144 |
'confirm_block': '',
|
| 144 | 145 |
'enable_negative_priority': False,
|
| ... | ... | @@ -254,12 +255,15 @@ ACCOUNT_SETTINGS = { |
| 254 | 255 |
'filetransfer_preference': 'httpupload',
|
| 255 | 256 |
'send_chatstate_default': 'composing_only',
|
| 256 | 257 |
'gc_send_chatstate_default': 'composing_only',
|
| 258 |
+ 'send_marker_default': True,
|
|
| 259 |
+ 'gc_send_marker_default': True,
|
|
| 257 | 260 |
'chat_history_max_age': -1,
|
| 258 | 261 |
},
|
| 259 | 262 |
|
| 260 | 263 |
'contact': {
|
| 261 | 264 |
'speller_language': '',
|
| 262 | 265 |
'send_chatstate': HAS_ACCOUNT_DEFAULT,
|
| 266 |
+ 'send_marker': HAS_ACCOUNT_DEFAULT,
|
|
| 263 | 267 |
'encryption': '',
|
| 264 | 268 |
},
|
| 265 | 269 |
|
| ... | ... | @@ -271,6 +275,7 @@ ACCOUNT_SETTINGS = { |
| 271 | 275 |
'minimize_on_autojoin': True,
|
| 272 | 276 |
'minimize_on_close': True,
|
| 273 | 277 |
'send_chatstate': HAS_ACCOUNT_DEFAULT,
|
| 278 |
+ 'send_marker': HAS_ACCOUNT_DEFAULT,
|
|
| 274 | 279 |
'encryption': '',
|
| 275 | 280 |
'sync_threshold': HAS_APP_DEFAULT,
|
| 276 | 281 |
},
|
| ... | ... | @@ -261,7 +261,7 @@ class ConnectionZeroconf(CommonConnection, ConnectionHandlersZeroconf): |
| 261 | 261 |
if result is False:
|
| 262 | 262 |
app.nec.push_incoming_event(ConnectionLostEvent(None,
|
| 263 | 263 |
conn=self, title=_('Could not start local service'),
|
| 264 |
- msg=_('Unable to bind to port %d.' % self.port)))
|
|
| 264 |
+ msg=_('Unable to bind to port %d.') % self.port))
|
|
| 265 | 265 |
else: # result is None
|
| 266 | 266 |
app.nec.push_incoming_event(ConnectionLostEvent(None,
|
| 267 | 267 |
conn=self, title=_('Could not start local service'),
|
| ... | ... | @@ -38,6 +38,7 @@ from gajim.common import app |
| 38 | 38 |
from gajim.common import helpers
|
| 39 | 39 |
from gajim.common import i18n
|
| 40 | 40 |
from gajim.common.i18n import _
|
| 41 |
+from gajim.common.i18n import Q_
|
|
| 41 | 42 |
from gajim.common.helpers import AdditionalDataDict
|
| 42 | 43 |
from gajim.common.const import StyleAttr
|
| 43 | 44 |
from gajim.common.const import Trust
|
| ... | ... | @@ -311,6 +312,12 @@ class ConversationTextview(GObject.GObject): |
| 311 | 312 |
return
|
| 312 | 313 |
line.set_receipt()
|
| 313 | 314 |
|
| 315 |
+ def show_displayed(self, id_):
|
|
| 316 |
+ line = self._get_message_line(id_)
|
|
| 317 |
+ if line is None:
|
|
| 318 |
+ return
|
|
| 319 |
+ line.set_displayed()
|
|
| 320 |
+ |
|
| 314 | 321 |
def show_error(self, id_, error):
|
| 315 | 322 |
line = self._get_message_line(id_)
|
| 316 | 323 |
if line is None:
|
| ... | ... | @@ -1194,16 +1201,27 @@ class MessageLine: |
| 1194 | 1201 |
self.timestamp = timestamp
|
| 1195 | 1202 |
self.start_mark = start_mark
|
| 1196 | 1203 |
self._has_receipt = False
|
| 1204 |
+ self._has_displayed = False
|
|
| 1197 | 1205 |
self._message_icons = message_icons
|
| 1198 | 1206 |
|
| 1199 | 1207 |
@property
|
| 1200 | 1208 |
def has_receipt(self):
|
| 1201 | 1209 |
return self._has_receipt
|
| 1202 | 1210 |
|
| 1211 |
+ @property
|
|
| 1212 |
+ def has_displayed(self):
|
|
| 1213 |
+ return self._has_displayed
|
|
| 1214 |
+ |
|
| 1203 | 1215 |
def set_receipt(self):
|
| 1204 | 1216 |
self._has_receipt = True
|
| 1217 |
+ if self._has_displayed:
|
|
| 1218 |
+ return
|
|
| 1205 | 1219 |
self._message_icons.set_receipt_icon_visible(True)
|
| 1206 | 1220 |
|
| 1221 |
+ def set_displayed(self):
|
|
| 1222 |
+ self._has_displayed = True
|
|
| 1223 |
+ self._message_icons.set_displayed_icon_visible(True)
|
|
| 1224 |
+ |
|
| 1207 | 1225 |
def set_correction(self, tooltip):
|
| 1208 | 1226 |
self._message_icons.set_correction_icon_visible(True)
|
| 1209 | 1227 |
self._message_icons.set_correction_tooltip(tooltip)
|
| ... | ... | @@ -1222,12 +1240,12 @@ class MessageIcons(Gtk.Box): |
| 1222 | 1240 |
'document-edit-symbolic', Gtk.IconSize.MENU)
|
| 1223 | 1241 |
self._correction_image.set_no_show_all(True)
|
| 1224 | 1242 |
|
| 1225 |
- self._receipt_image = Gtk.Image.new_from_icon_name(
|
|
| 1243 |
+ self._marker_image = Gtk.Image.new_from_icon_name(
|
|
| 1226 | 1244 |
'emblem-ok-symbolic', Gtk.IconSize.MENU)
|
| 1227 |
- self._receipt_image.get_style_context().add_class(
|
|
| 1245 |
+ self._marker_image.get_style_context().add_class(
|
|
| 1228 | 1246 |
'receipt-received-color')
|
| 1229 |
- self._receipt_image.set_tooltip_text(_('Received'))
|
|
| 1230 |
- self._receipt_image.set_no_show_all(True)
|
|
| 1247 |
+ self._marker_image.set_tooltip_text(_('Received'))
|
|
| 1248 |
+ self._marker_image.set_no_show_all(True)
|
|
| 1231 | 1249 |
|
| 1232 | 1250 |
self._error_image = Gtk.Image.new_from_icon_name(
|
| 1233 | 1251 |
'dialog-warning-symbolic', Gtk.IconSize.MENU)
|
| ... | ... | @@ -1235,14 +1253,22 @@ class MessageIcons(Gtk.Box): |
| 1235 | 1253 |
self._error_image.set_no_show_all(True)
|
| 1236 | 1254 |
|
| 1237 | 1255 |
self.add(self._correction_image)
|
| 1238 |
- self.add(self._receipt_image)
|
|
| 1256 |
+ self.add(self._marker_image)
|
|
| 1239 | 1257 |
self.add(self._error_image)
|
| 1240 | 1258 |
self.show_all()
|
| 1241 | 1259 |
|
| 1242 | 1260 |
def set_receipt_icon_visible(self, visible):
|
| 1243 | 1261 |
if not app.settings.get('positive_184_ack'):
|
| 1244 | 1262 |
return
|
| 1245 |
- self._receipt_image.set_visible(visible)
|
|
| 1263 |
+ self._marker_image.set_visible(visible)
|
|
| 1264 |
+ |
|
| 1265 |
+ def set_displayed_icon_visible(self, visible):
|
|
| 1266 |
+ self._marker_image.set_visible(visible)
|
|
| 1267 |
+ self._marker_image.get_style_context().remove_class(
|
|
| 1268 |
+ 'receipt-received-color')
|
|
| 1269 |
+ self._marker_image.get_style_context().add_class(
|
|
| 1270 |
+ 'displayed-received-color')
|
|
| 1271 |
+ self._marker_image.set_tooltip_text(Q_('?Message state:Read'))
|
|
| 1246 | 1272 |
|
| 1247 | 1273 |
def set_correction_icon_visible(self, visible):
|
| 1248 | 1274 |
self._correction_image.set_visible(visible)
|
| ... | ... | @@ -295,6 +295,7 @@ button.flat.link { padding: 0; border: 0; } |
| 295 | 295 |
|
| 296 | 296 |
/*Receipts*/
|
| 297 | 297 |
.receipt-received-color { color: rgb(75, 181, 67); }
|
| 298 |
+.displayed-received-color { color: rgb(0, 141, 242); }
|
|
| 298 | 299 |
|
| 299 | 300 |
/*Dataforms*/
|
| 300 | 301 |
.field-fixed { font-size: 16px; font-weight: bold; padding-top:5px;}
|
| ... | ... | @@ -495,7 +495,7 @@ class GroupchatControl(ChatControlBase): |
| 495 | 495 |
con = app.connections[self.account]
|
| 496 | 496 |
message_id = con.get_module('MUC').invite(self.room_jid, contact_jid)
|
| 497 | 497 |
self.add_info_message(
|
| 498 |
- _('%s has been invited to this group chat' % contact_jid),
|
|
| 498 |
+ _('%s has been invited to this group chat') % contact_jid,
|
|
| 499 | 499 |
message_id=message_id)
|
| 500 | 500 |
|
| 501 | 501 |
def _on_destroy_room(self, _action, _param):
|
| ... | ... | @@ -591,7 +591,7 @@ class GroupchatControl(ChatControlBase): |
| 591 | 591 |
try:
|
| 592 | 592 |
task.finish()
|
| 593 | 593 |
except Exception as error:
|
| 594 |
- self.add_info_message(_('Avatar upload failed: %s' % error))
|
|
| 594 |
+ self.add_info_message(_('Avatar upload failed: %s') % error)
|
|
| 595 | 595 |
|
| 596 | 596 |
else:
|
| 597 | 597 |
self.add_info_message(_('Avatar upload successful'))
|
| ... | ... | @@ -612,7 +612,7 @@ class GroupchatControl(ChatControlBase): |
| 612 | 612 |
def _on_kick(self, _action, param):
|
| 613 | 613 |
nick = param.get_string()
|
| 614 | 614 |
self._kick_nick = nick
|
| 615 |
- self.xml.kick_label.set_text(_('Kick %s' % nick))
|
|
| 615 |
+ self.xml.kick_label.set_text(_('Kick %s') % nick)
|
|
| 616 | 616 |
self.xml.kick_reason_entry.grab_focus()
|
| 617 | 617 |
self.xml.kick_participant_button.grab_default()
|
| 618 | 618 |
self._show_page('kick')
|
| ... | ... | @@ -621,7 +621,7 @@ class GroupchatControl(ChatControlBase): |
| 621 | 621 |
jid = param.get_string()
|
| 622 | 622 |
self._ban_jid = jid
|
| 623 | 623 |
nick = app.get_nick_from_jid(jid)
|
| 624 |
- self.xml.ban_label.set_text(_('Ban %s' % nick))
|
|
| 624 |
+ self.xml.ban_label.set_text(_('Ban %s') % nick)
|
|
| 625 | 625 |
self.xml.ban_reason_entry.grab_focus()
|
| 626 | 626 |
self.xml.ban_participant_button.grab_default()
|
| 627 | 627 |
self._show_page('ban')
|
| ... | ... | @@ -656,6 +656,17 @@ class GroupchatControl(ChatControlBase): |
| 656 | 656 |
self.update_actions()
|
| 657 | 657 |
self.set_lock_image()
|
| 658 | 658 |
self.draw_banner_text()
|
| 659 |
+ type_ = ['printed_gc_msg', 'printed_marked_gc_msg']
|
|
| 660 |
+ if not app.events.remove_events(self.account,
|
|
| 661 |
+ self.get_full_jid(),
|
|
| 662 |
+ types=type_):
|
|
| 663 |
+ # XEP-0333 Send <displayed> marker
|
|
| 664 |
+ con = app.connections[self.account]
|
|
| 665 |
+ con.get_module('ChatMarkers').send_displayed_marker(
|
|
| 666 |
+ self.contact,
|
|
| 667 |
+ self.last_msg_id,
|
|
| 668 |
+ self._type)
|
|
| 669 |
+ self.last_msg_id = None
|
|
| 659 | 670 |
|
| 660 | 671 |
def _on_roster_row_activated(self, _roster, nick):
|
| 661 | 672 |
self._start_private_message(nick)
|
| ... | ... | @@ -799,12 +810,12 @@ class GroupchatControl(ChatControlBase): |
| 799 | 810 |
tim=event.properties.timestamp,
|
| 800 | 811 |
displaymarking=event.displaymarking,
|
| 801 | 812 |
correct_id=event.correct_id,
|
| 802 |
- message_id=event.properties.id,
|
|
| 813 |
+ message_id=event.properties.stanza_id.id,
|
|
| 803 | 814 |
additional_data=event.additional_data)
|
| 804 | 815 |
event.needs_highlight = self.needs_visual_notification(event.msgtxt)
|
| 805 | 816 |
|
| 806 | 817 |
def on_private_message(self, nick, sent, msg, tim, session, additional_data,
|
| 807 |
- msg_log_id=None, displaymarking=None):
|
|
| 818 |
+ message_id, msg_log_id=None, displaymarking=None):
|
|
| 808 | 819 |
# Do we have a queue?
|
| 809 | 820 |
fjid = self.room_jid + '/' + nick
|
| 810 | 821 |
|
| ... | ... | @@ -817,7 +828,9 @@ class GroupchatControl(ChatControlBase): |
| 817 | 828 |
session=session,
|
| 818 | 829 |
displaymarking=displaymarking,
|
| 819 | 830 |
sent_forwarded=sent,
|
| 820 |
- additional_data=additional_data)
|
|
| 831 |
+ additional_data=additional_data,
|
|
| 832 |
+ message_id=message_id)
|
|
| 833 |
+ |
|
| 821 | 834 |
app.events.add_event(self.account, fjid, event)
|
| 822 | 835 |
|
| 823 | 836 |
if allow_popup_window(self.account):
|
| ... | ... | @@ -1055,6 +1068,7 @@ class GroupchatControl(ChatControlBase): |
| 1055 | 1068 |
event.properties.timestamp,
|
| 1056 | 1069 |
self.session,
|
| 1057 | 1070 |
event.additional_data,
|
| 1071 |
+ event.properties.id,
|
|
| 1058 | 1072 |
msg_log_id=event.msg_log_id,
|
| 1059 | 1073 |
displaymarking=event.displaymarking)
|
| 1060 | 1074 |
|
| ... | ... | @@ -49,8 +49,8 @@ class AboutDialog(Gtk.AboutDialog): |
| 49 | 49 |
|
| 50 | 50 |
comments = []
|
| 51 | 51 |
comments.append(_('A GTK XMPP client'))
|
| 52 |
- comments.append(_('GTK Version: %s' % gtk_ver))
|
|
| 53 |
- comments.append(_('GLib Version: %s' % glib_ver))
|
|
| 52 |
+ comments.append(_('GTK Version: %s') % gtk_ver)
|
|
| 53 |
+ comments.append(_('GLib Version: %s') % glib_ver)
|
|
| 54 | 54 |
comments.append(_('PyGObject Version: %s') % gobject_ver)
|
| 55 | 55 |
comments.append(_('python-nbxmpp Version: %s') % nbxmpp.__version__)
|
| 56 | 56 |
|
| ... | ... | @@ -258,7 +258,7 @@ class AddNewContactWindow(Gtk.ApplicationWindow, EventHelper): |
| 258 | 258 |
if not jid:
|
| 259 | 259 |
ErrorDialog(
|
| 260 | 260 |
_('%s Missing') % self.uid_label.get_text(),
|
| 261 |
- _('You must supply the %s of the new contact.' %
|
|
| 261 |
+ (_('You must supply the %s of the new contact.') %
|
|
| 262 | 262 |
self.uid_label.get_text())
|
| 263 | 263 |
)
|
| 264 | 264 |
return
|
| ... | ... | @@ -45,7 +45,7 @@ class Bookmarks(Gtk.ApplicationWindow): |
| 45 | 45 |
self.set_application(app.app)
|
| 46 | 46 |
self.set_position(Gtk.WindowPosition.CENTER)
|
| 47 | 47 |
self.set_show_menubar(False)
|
| 48 |
- self.set_title(_('Bookmarks for %s' % app.get_account_label(account)))
|
|
| 48 |
+ self.set_title(_('Bookmarks for %s') % app.get_account_label(account))
|
|
| 49 | 49 |
self.set_default_size(700, 500)
|
| 50 | 50 |
|
| 51 | 51 |
self.account = account
|
| ... | ... | @@ -396,7 +396,7 @@ class FileTransfersWindow: |
| 396 | 396 |
file_name = GLib.markup_escape_text(
|
| 397 | 397 |
os.path.basename(file_path))
|
| 398 | 398 |
ErrorDialog(
|
| 399 |
- _('Cannot overwrite existing file \'%s\'' % file_name),
|
|
| 399 |
+ _('Cannot overwrite existing file \'%s\'') % file_name,
|
|
| 400 | 400 |
_('A file with this name already exists and you do '
|
| 401 | 401 |
'not have permission to overwrite it.'))
|
| 402 | 402 |
return
|
| ... | ... | @@ -71,6 +71,12 @@ class GroupChatSettings(SettingsBox): |
| 71 | 71 |
'send_chatstate',
|
| 72 | 72 |
props={'entries': chat_state}),
|
| 73 | 73 |
|
| 74 |
+ Setting(SettingKind.SWITCH,
|
|
| 75 |
+ _('Send Chat Markers'),
|
|
| 76 |
+ SettingType.GROUP_CHAT,
|
|
| 77 |
+ 'send_marker',
|
|
| 78 |
+ desc=_('Let others know if you read up to this point')),
|
|
| 79 |
+ |
|
| 74 | 80 |
Setting(SettingKind.POPOVER,
|
| 75 | 81 |
_('Sync Threshold'),
|
| 76 | 82 |
SettingType.GROUP_CHAT,
|
| ... | ... | @@ -248,11 +248,11 @@ class DownloadHistoryPage(Gtk.Box): |
| 248 | 248 |
self.received += 1
|
| 249 | 249 |
if self.count:
|
| 250 | 250 |
self.progress.set_fraction(self.received / self.count)
|
| 251 |
- self.progress.set_text(_('%(received)s of %(max)s' % {
|
|
| 252 |
- 'received': self.received, 'max': self.count}))
|
|
| 251 |
+ self.progress.set_text(_('%(received)s of %(max)s') % {
|
|
| 252 |
+ 'received': self.received, 'max': self.count})
|
|
| 253 | 253 |
else:
|
| 254 | 254 |
self.progress.pulse()
|
| 255 |
- self.progress.set_text(_('Downloaded %s messages' % self.received))
|
|
| 255 |
+ self.progress.set_text(_('Downloaded %s messages') % self.received)
|
|
| 256 | 256 |
|
| 257 | 257 |
def finished(self):
|
| 258 | 258 |
self.progress.set_fraction(1)
|
| ... | ... | @@ -273,7 +273,7 @@ class SummaryPage(Gtk.Box): |
| 273 | 273 |
def finished(self):
|
| 274 | 274 |
received = self.assistant.download_history.received
|
| 275 | 275 |
self.label.set_text(_('Finished synchronising chat history:\n'
|
| 276 |
- '%s messages downloaded' % received))
|
|
| 276 |
+ '%s messages downloaded') % received)
|
|
| 277 | 277 |
|
| 278 | 278 |
def nothing_to_do(self):
|
| 279 | 279 |
self.label.set_text(_('Gajim is fully synchronised with the archive.'))
|
| ... | ... | @@ -14,7 +14,7 @@ |
| 14 | 14 |
|
| 15 | 15 |
from gi.repository import Gdk
|
| 16 | 16 |
from gi.repository import Gtk
|
| 17 |
-from nbxmpp.util import is_error_result
|
|
| 17 |
+from nbxmpp.errors import StanzaError
|
|
| 18 | 18 |
|
| 19 | 19 |
from gajim.common import app
|
| 20 | 20 |
from gajim.common import ged
|
| ... | ... | @@ -85,9 +85,11 @@ class PEPConfig(Gtk.ApplicationWindow, EventHelper): |
| 85 | 85 |
self._con.get_module('Discovery').disco_items(
|
| 86 | 86 |
jid, callback=self._items_received)
|
| 87 | 87 |
|
| 88 |
- def _items_received(self, result):
|
|
| 89 |
- if is_error_result(result):
|
|
| 90 |
- ErrorDialog('Error', to_user_string(result))
|
|
| 88 |
+ def _items_received(self, task):
|
|
| 89 |
+ try:
|
|
| 90 |
+ result = task.finish()
|
|
| 91 |
+ except StanzaError as error:
|
|
| 92 |
+ ErrorDialog('Error', to_user_string(error))
|
|
| 91 | 93 |
return
|
| 92 | 94 |
|
| 93 | 95 |
jid = result.jid.bare
|
| ... | ... | @@ -249,6 +249,11 @@ class ContactList(PreferenceBox): |
| 249 | 249 |
'mergeaccounts',
|
| 250 | 250 |
callback=self._on_merge_accounts),
|
| 251 | 251 |
|
| 252 |
+ Setting(SettingKind.SWITCH,
|
|
| 253 |
+ _('Enable Metacontacts'),
|
|
| 254 |
+ SettingType.CONFIG,
|
|
| 255 |
+ 'metacontacts_enabled'),
|
|
| 256 |
+ |
|
| 252 | 257 |
Setting(SettingKind.SWITCH,
|
| 253 | 258 |
_('Show Avatars'),
|
| 254 | 259 |
SettingType.CONFIG,
|
| ... | ... | @@ -194,7 +194,7 @@ class RosterItemExchangeWindow(Gtk.ApplicationWindow): |
| 194 | 194 |
count += 1
|
| 195 | 195 |
# It is selected
|
| 196 | 196 |
message = _('%s suggested me to add you to my '
|
| 197 |
- 'contact list.' % self.jid_from)
|
|
| 197 |
+ 'contact list.') % self.jid_from
|
|
| 198 | 198 |
# Keep same groups and same nickname
|
| 199 | 199 |
groups = model[iter_][3].split(', ')
|
| 200 | 200 |
if groups == ['']:
|
| ... | ... | @@ -652,7 +652,7 @@ class ContactRow(Gtk.ListBoxRow): |
| 652 | 652 |
if show_account:
|
| 653 | 653 |
account_badge = generate_account_badge(account)
|
| 654 | 654 |
account_badge.set_tooltip_text(
|
| 655 |
- _('Account: %s' % self.account_label))
|
|
| 655 |
+ _('Account: %s') % self.account_label)
|
|
| 656 | 656 |
account_badge.set_halign(Gtk.Align.END)
|
| 657 | 657 |
account_badge.set_valign(Gtk.Align.START)
|
| 658 | 658 |
account_badge.set_hexpand(True)
|
| ... | ... | @@ -242,6 +242,37 @@ class Interface: |
| 242 | 242 |
if ctrl and ctrl.session and len(obj.contact_list) > 1:
|
| 243 | 243 |
ctrl.remove_session(ctrl.session)
|
| 244 | 244 |
|
| 245 |
+ def handle_event_read_state_sync(self, event):
|
|
| 246 |
+ if event.type.is_groupchat:
|
|
| 247 |
+ control = app.get_groupchat_control(
|
|
| 248 |
+ event.account, event.jid.bare)
|
|
| 249 |
+ if control is None:
|
|
| 250 |
+ log.warning('Groupchat control not found')
|
|
| 251 |
+ return
|
|
| 252 |
+ |
|
| 253 |
+ jid = event.jid.bare
|
|
| 254 |
+ types = ['printed_gc_msg', 'printed_marked_gc_msg']
|
|
| 255 |
+ |
|
| 256 |
+ else:
|
|
| 257 |
+ types = ['chat', 'pm', 'printed_chat', 'printed_pm']
|
|
| 258 |
+ jid = event.jid
|
|
| 259 |
+ |
|
| 260 |
+ control = app.interface.msg_win_mgr.get_control(jid, event.account)
|
|
| 261 |
+ |
|
| 262 |
+ # Compare with control.last_msg_id.
|
|
| 263 |
+ events_ = app.events.get_events(event.account, jid, types)
|
|
| 264 |
+ if not events_:
|
|
| 265 |
+ log.warning('No Events')
|
|
| 266 |
+ return
|
|
| 267 |
+ |
|
| 268 |
+ if events_[-1].message_id != event.marker_id:
|
|
| 269 |
+ return
|
|
| 270 |
+ |
|
| 271 |
+ if not app.events.remove_events(event.account, jid, types=types):
|
|
| 272 |
+ # There were events to remove
|
|
| 273 |
+ if control is not None:
|
|
| 274 |
+ control.redraw_after_event_removed(event.jid)
|
|
| 275 |
+ |
|
| 245 | 276 |
@staticmethod
|
| 246 | 277 |
def handle_event_msgsent(obj):
|
| 247 | 278 |
if not obj.play_sound:
|
| ... | ... | @@ -926,7 +957,7 @@ class Interface: |
| 926 | 957 |
if helpers.allow_showing_notification(account):
|
| 927 | 958 |
heading = _('Incoming Call')
|
| 928 | 959 |
contact = app.get_name_from_jid(account, event.jid)
|
| 929 |
- text = _('%s is calling' % contact)
|
|
| 960 |
+ text = _('%s is calling') % contact
|
|
| 930 | 961 |
app.notification.popup(
|
| 931 | 962 |
heading,
|
| 932 | 963 |
event.fjid,
|
| ... | ... | @@ -1060,6 +1091,7 @@ class Interface: |
| 1060 | 1091 |
'unsubscribed-presence-received': [
|
| 1061 | 1092 |
self.handle_event_unsubscribed_presence],
|
| 1062 | 1093 |
'zeroconf-name-conflict': [self.handle_event_zc_name_conflict],
|
| 1094 |
+ 'read-state-sync': [self.handle_event_read_state_sync],
|
|
| 1063 | 1095 |
}
|
| 1064 | 1096 |
|
| 1065 | 1097 |
def register_core_handlers(self):
|
| ... | ... | @@ -1983,7 +2015,7 @@ class Interface: |
| 1983 | 2015 |
_('Update Available'),
|
| 1984 | 2016 |
_('Gajim Update Available'),
|
| 1985 | 2017 |
_('There is an update available for Gajim '
|
| 1986 |
- '(latest version: %s)' % str(latest_version)),
|
|
| 2018 |
+ '(latest version: %s)') % str(latest_version),
|
|
| 1987 | 2019 |
_('_Do not show again'),
|
| 1988 | 2020 |
[DialogButton.make('Cancel',
|
| 1989 | 2021 |
text=_('_Later'),
|
| ... | ... | @@ -511,12 +511,13 @@ def get_transport_menu(contact, account): |
| 511 | 511 |
return menu
|
| 512 | 512 |
|
| 513 | 513 |
|
| 514 |
-def get_singlechat_menu(control_id, account, jid):
|
|
| 514 |
+def get_singlechat_menu(control_id, account, jid, type_):
|
|
| 515 | 515 |
singlechat_menu = [
|
| 516 | 516 |
(_('Send File'), [
|
| 517 | 517 |
('win.send-file-httpupload-', _('Upload File…')),
|
| 518 | 518 |
('win.send-file-jingle-', _('Send File Directly…')),
|
| 519 | 519 |
]),
|
| 520 |
+ ('win.send-marker-', _('Send Read Markers')),
|
|
| 520 | 521 |
(_('Send Chatstate'), ['chatstate']),
|
| 521 | 522 |
('win.invite-contacts-', _('Invite Contacts…')),
|
| 522 | 523 |
('win.add-to-roster-', _('Add to Contact List…')),
|
| ... | ... | @@ -545,6 +546,9 @@ def get_singlechat_menu(control_id, account, jid): |
| 545 | 546 |
for item in preset:
|
| 546 | 547 |
if isinstance(item[1], str):
|
| 547 | 548 |
action_name, label = item
|
| 549 |
+ if action_name == 'win.send-marker-' and type_ == 'pm':
|
|
| 550 |
+ continue
|
|
| 551 |
+ |
|
| 548 | 552 |
if action_name == 'app.browse-history':
|
| 549 | 553 |
menuitem = Gio.MenuItem.new(label, action_name)
|
| 550 | 554 |
dict_ = {'account': GLib.Variant('s', account),
|
| ... | ... | @@ -66,7 +66,6 @@ from gajim.gtk.dialogs import ConfirmationDialog |
| 66 | 66 |
from gajim.gtk.dialogs import ConfirmationCheckDialog
|
| 67 | 67 |
from gajim.gtk.dialogs import ErrorDialog
|
| 68 | 68 |
from gajim.gtk.dialogs import InputDialog
|
| 69 |
-from gajim.gtk.dialogs import WarningDialog
|
|
| 70 | 69 |
from gajim.gtk.dialogs import InformationDialog
|
| 71 | 70 |
from gajim.gtk.single_message import SingleMessageWindow
|
| 72 | 71 |
from gajim.gtk.add_contact import AddNewContactWindow
|
| ... | ... | @@ -3664,11 +3663,7 @@ class RosterWindow: |
| 3664 | 3663 |
con_dest = app.connections[account_dest]
|
| 3665 | 3664 |
if (not con_source.get_module('MetaContacts').available or
|
| 3666 | 3665 |
not con_dest.get_module('MetaContacts').available):
|
| 3667 |
- WarningDialog(_('Metacontacts storage not supported by '
|
|
| 3668 |
- 'your server'),
|
|
| 3669 |
- _('Your server does not support storing metacontacts '
|
|
| 3670 |
- 'information. So this information will not be saved on next '
|
|
| 3671 |
- 'reconnection.'))
|
|
| 3666 |
+ return
|
|
| 3672 | 3667 |
|
| 3673 | 3668 |
def merge_contacts(is_checked=None):
|
| 3674 | 3669 |
contacts = 0
|