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 Simplify cellrenderers
- Makes it better understandable - Fixes a bug where an avatar was drawn in a group row - Fixes not all cells getting the same background color while using a theme
- - - - - cca8c0c3 by Philipp Hörist at 2018-03-05T23:01:29+01:00 Fix avatar ratio
Fixes #8907
- - - - - fa15baf1 by Philipp Hörist at 2018-03-05T23:02:01+01:00 StartChatDialog: Use HighDPI icons/avatars
- - - - - 9cc7df89 by Philipp Hörist at 2018-03-05T23:03:23+01:00 StatusIcon: Add fallback for account label
Fixes #8901
- - - - - e5dbcc7c by Philipp Hörist at 2018-03-05T23:03:38+01:00 Fix MessagTextview height
On some Desktops eg. Mate, the MessageTextview was to big, because the default height seems to be 82.
What influences the default height (compare Windows where it is 46), is unclear.
- - - - - f7e01e43 by Philipp Hörist at 2018-03-05T23:03:58+01:00 Add extension point for GC tooltip
- - - - - bbb75570 by Philipp Hörist at 2018-03-05T23:04:09+01:00 Limit deduplication to correct archive
We limit the search to non-groupchat messages because a stanza id is only unique within an archive
- - - - - c1e482a1 by Philipp Hörist at 2018-03-05T23:08:11+01:00 Fix duplicates for self messages and MUC PMs
Fixes #8812
- - - - - 2fdaf391 by Philipp Hörist at 2018-03-05T23:08:26+01:00 Print correct archive jid in log message
- - - - - 931d138b by Philipp Hörist at 2018-03-05T23:10:35+01:00 Pass jid as string to find_stanza_id()
- - - - - a0c8424f by Philipp Hörist at 2018-03-05T23:10:36+01:00 Tag MUC PMs
This gives the server the chance to react accordingly without applying much logic. Also it makes it easier for us to recognize MUC PMs in MAM querys
- - - - - 843e56a7 by Philipp Hörist at 2018-03-05T23:10:36+01:00 Refactor emoticon theme combobox
- Use ComboBoxText, its much simpler - Add a dedicated method that returns all available themes - If the configured Theme is not available fallback to font-emoticons
- - - - - 0ffb4537 by Philipp Hörist at 2018-03-05T23:10:36+01:00 Better emoticon theme fallback strategy
Fallback must be happening in init_emoticon() instead of PreferencesWindow
- - - - - 6e6d9497 by Philipp Hörist at 2018-03-05T23:10:36+01:00 Dont notify about PMs from MUCs we are not joined
The server sends us carbons if one of our other resources has a conversation in a muc we are not joined. Instead we log the messages silently.
- - - - - fb1945e6 by Philipp Hörist at 2018-03-05T23:10:37+01:00 MAM: Check muc#user namespace before we disco
1. Check if the message has the muc#user namespace 2. Check if we have this jid already in the DB 3. Last resort disco the jid
- - - - - 41828335 by André Apitzsch at 2018-03-05T23:12:26+01:00 setup.py: remove unused function
- - - - - 062e69f0 by Philipp Hörist at 2018-03-05T23:12:26+01:00 HistoryManger: Fix window dimensions
- - - - - 000ac263 by Philipp Hörist at 2018-03-05T23:12:26+01:00 MAM: Display received mam message in open chats
- - - - - 6793aa65 by Philipp Hörist at 2018-03-05T23:12:27+01:00 MAM: Display corrected messages on MAM catchup
This shows corrected messages correctly in groupchat when we receive the messages via MAM
- - - - - 06759167 by Philipp Hörist at 2018-03-05T23:13:17+01:00 Only use fallback duplicate search on MAM < 2
- - - - - 4a233d87 by Philipp Hörist at 2018-03-05T23:13:17+01:00 Use MessageSentEvent to print to ChatControl
Its later than StanzaMessageOutgoingEvent, this allows encryption Plugins to not return the encrypted payload immediatly. For example encrypting may take some time because a password is needed or the load has to be encrypted in another Thread.
- - - - - c3ddf476 by Philipp Hörist at 2018-03-05T23:13:18+01:00 Add get_contact_strict()
This adds a method that returns only a contact if there is an exact match, instead of a contact where only the barejid matches.
We need this when we update the Caps of a contact.
- - - - - 3808df05 by Philipp Hörist at 2018-03-05T23:13:18+01:00 Set correct kind on incoming MAM messages
- - - - -
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:
===================================== gajim/chat_control.py ===================================== --- a/gajim/chat_control.py +++ b/gajim/chat_control.py @@ -238,8 +238,11 @@ class ChatControl(ChatControlBase): self._nec_chatstate_received) app.ged.register_event_handler('caps-received', ged.GUI1, self._nec_caps_received) - app.ged.register_event_handler('stanza-message-outgoing', ged.OUT_POSTCORE, + app.ged.register_event_handler('message-sent', ged.OUT_POSTCORE, self._message_sent) + app.ged.register_event_handler( + 'mam-decrypted-message-received', + ged.GUI1, self._nec_mam_decrypted_message_received)
# PluginSystem: adding GUI extension point for this ChatControl # instance object @@ -817,6 +820,20 @@ class ChatControl(ChatControlBase): app.plugin_manager.extension_point( 'encryption_dialog' + self.encryption, self)
+ def _nec_mam_decrypted_message_received(self, obj): + if obj.conn.name != self.account: + return + if obj.with_ != self.contact.jid: + return + + kind = '' # incoming + if obj.kind == KindConstant.CHAT_MSG_SENT: + kind = 'outgoing' + + self.print_conversation(obj.msgtxt, kind, tim=obj.timestamp, + encrypted=obj.encrypted, correct_id=obj.correct_id, + msg_stanza_id=obj.message_id, additional_data=obj.additional_data) + def _message_sent(self, obj): if obj.conn.name != self.account: return @@ -1154,7 +1171,7 @@ class ChatControl(ChatControlBase): self._nec_chatstate_received) app.ged.remove_event_handler('caps-received', ged.GUI1, self._nec_caps_received) - app.ged.remove_event_handler('stanza-message-outgoing', ged.OUT_POSTCORE, + app.ged.remove_event_handler('message-sent', ged.OUT_POSTCORE, self._message_sent)
self.unsubscribe_events()
===================================== gajim/chat_control_base.py ===================================== --- a/gajim/chat_control_base.py +++ b/gajim/chat_control_base.py @@ -1380,13 +1380,12 @@ class ScrolledWindow(Gtk.ScrolledWindow):
def do_get_preferred_height(self): min_height, natural_height = Gtk.ScrolledWindow.do_get_preferred_height(self) - child = self.get_child() # Gtk Bug: If policy is set to Automatic, the ScrolledWindow - # has a min size of around 46 depending on the System. Because - # we want it smaller, we set policy NEVER if the height is < 50 + # has a min size of around 46-82 depending on the System. Because + # we want it smaller, we set policy NEVER if the height is < 90 # so the ScrolledWindow will shrink to around 26 (1 line heigh). - # Once it gets over 50 its no problem to restore the policy. - if natural_height < 50: + # Once it gets over 90 its no problem to restore the policy. + if natural_height < 90: GLib.idle_add(self.set_policy, Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.NEVER)
===================================== gajim/common/connection.py ===================================== --- a/gajim/common/connection.py +++ b/gajim/common/connection.py @@ -64,6 +64,7 @@ from gajim.common import exceptions from gajim.common import check_X509 from gajim.common.connection_handlers import * from gajim.common.helpers import version_condition +from gajim.common.contacts import GC_Contact
from gajim.gtkgui_helpers import get_action
@@ -373,6 +374,10 @@ class CommonConnection: contact = app.contacts.get_contact_with_highest_priority( self.name, obj.jid)
+ # Mark Message as MUC PM + if isinstance(contact, GC_Contact): + msg_iq.setTag('x', namespace=nbxmpp.NS_MUC_USER) + # chatstates - if peer supports xep85, send chatstates # please note that the only valid tag inside a message containing a # <body> tag is the active event @@ -2070,10 +2075,7 @@ class Connection(CommonConnection, ConnectionHandlers): obj.timestamp = time.time() obj.stanza_id = self.connection.send(obj.msg_iq, now=obj.now)
- app.nec.push_incoming_event(MessageSentEvent( - None, conn=self, jid=obj.jid, message=obj.message, keyID=obj.keyID, - chatstate=obj.chatstate, automatic_message=obj.automatic_message, - stanza_id=obj.stanza_id, additional_data=obj.additional_data)) + app.nec.push_incoming_event(MessageSentEvent(None, **vars(obj)))
if isinstance(obj.jid, list): for j in obj.jid:
===================================== gajim/common/connection_handlers_events.py ===================================== --- a/gajim/common/connection_handlers_events.py +++ b/gajim/common/connection_handlers_events.py @@ -163,6 +163,25 @@ class HelperEvent: if forwarded is not None: return forwarded.getTag('message', protocol=True)
+ def _is_self_message(self, message): + if self.self_message is not None: + return self.self_message + own_jid = self.conn.get_own_jid() + frm = message.getFrom() + to = message.getTo() + # If 'to' is not set we assume own jid + self.self_message = frm.bareMatch(to or own_jid) + return self.self_message + + def _is_muc_pm(self, message): + if self.muc_pm is not None: + return self.muc_pm + self.muc_pm = False + muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER) + if muc_user is not None: + self.muc_pm = muc_user.getChildren() == [] + return self.muc_pm + class HttpAuthReceivedEvent(nec.NetworkIncomingEvent): name = 'http-auth-received' base_network_events = [] @@ -1015,6 +1034,8 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.encrypted = False self.groupchat = False self.nick = None + self.self_message = None + self.muc_pm = None
def generate(self): account = self.conn.name @@ -1031,6 +1052,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
# use stanza-id as unique-id self.unique_id, origin_id = self.get_unique_id() + self.message_id = self.msg_.getID()
# Check for duplicates if app.logger.find_stanza_id(account, @@ -1081,6 +1103,11 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
def get_unique_id(self): stanza_id = self.get_stanza_id(self.result, query=True) + + if self._is_self_message(self.msg_) or self._is_muc_pm(self.msg_): + origin_id = self.msg_.getOriginID() + return stanza_id, origin_id + if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()): # message we sent origin_id = self.msg_.getOriginID() @@ -1101,6 +1128,8 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): :stanza: Complete stanza Node :forwarded: Forwarded Node :result: Result Node + :muc_pm: True, if this is a MUC PM + propagated to MamDecryptedMessageReceivedEvent ''' self._set_base_event_vars_as_attributes(base_event) self.additional_data = {} @@ -1123,6 +1152,7 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): return False
self.unique_id = self.get_stanza_id(self.result, query=True) + self.message_id = self.msg_.getID()
# Check for duplicates if app.logger.find_stanza_id(account, @@ -1171,33 +1201,43 @@ class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): base_network_events = []
def generate(self): + self.correct_id = None + if not self.msgtxt: # For example Chatstates, Receipts, Chatmarkers log.debug('Received MAM message without text') return
+ replace = self.msg_.getTag('replace', namespace=nbxmpp.NS_CORRECT) + if replace is not None: + self.correct_id = replace.getAttr('id') + self.get_oob_data(self.msg_)
if self.groupchat: return True
- self.is_pm = app.logger.jid_is_room_jid(self.with_.getStripped()) - if self.is_pm is None: - # Check if this event is triggered after a disco, so we dont - # run into an endless loop - if hasattr(self, 'disco'): - log.error('JID not known even after sucessful disco') + if not self.muc_pm: + # muc_pm = False, means only there was no muc#user namespace + # This could still be a muc pm, we check the database if we + # know this jid. If not we disco it. + self.muc_pm = app.logger.jid_is_room_jid(self.with_.getStripped()) + if self.muc_pm is None: + # Check if this event is triggered after a disco, so we dont + # run into an endless loop + if hasattr(self, 'disco'): + log.error('JID not known even after sucessful disco') + return + # we don't know this JID, we need to disco it. + server = self.with_.getDomain() + if server not in self.conn.mam_awaiting_disco_result: + self.conn.mam_awaiting_disco_result[server] = [self] + self.conn.discoverInfo(server) + else: + self.conn.mam_awaiting_disco_result[server].append(self) return - # we don't know this JID, we need to disco it. - server = self.with_.getDomain() - if server not in self.conn.mam_awaiting_disco_result: - self.conn.mam_awaiting_disco_result[server] = [self] - self.conn.discoverInfo(server) - else: - self.conn.mam_awaiting_disco_result[server].append(self) - return
- if self.is_pm: + if self.muc_pm: self.with_ = str(self.with_) else: self.with_ = self.with_.getStripped() @@ -1217,6 +1257,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.forwarded = False self.sent = False self.encrypted = False + self.self_message = None + self.muc_pm = None account = self.conn.name
if self.stanza.getFrom() == self.conn.get_own_jid(warn=True): @@ -1250,11 +1292,16 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.unique_id = self.get_unique_id() # Check groupchat messages for duplicates, # We do this because of MUC History messages - if self.stanza.getType() == 'groupchat': + type_ = self.stanza.getType() + if type_ == 'groupchat' or self.self_message or self.muc_pm: + if type_ == 'groupchat': + archive_jid = self.stanza.getFrom().getStripped() + else: + archive_jid = self.conn.get_own_jid().getStripped() if app.logger.find_stanza_id(account, - self.stanza.getFrom().getStripped(), + archive_jid, self.unique_id, - groupchat=True): + groupchat=type_ == 'groupchat'): return
address_tag = self.stanza.getTag('addresses', @@ -1406,6 +1453,21 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): return True
def get_unique_id(self): + ''' + Messages to self: + + Messages to self are stored multiple times in MAM so we cant use + stanza-id to deduplicate. We use origin-id instead. Its not perfect + but there is no better way for now. + We drop "received"-Carbons of Message to self, so we dont have to + parse origin-id in that case. + + MUC PMs: + + MUC PMs are also stored multiple times, we also depend on origin-id + for now. + ''' + if self.stanza.getType() == 'groupchat': # TODO: Disco the MUC check if 'urn:xmpp:mam:2' is announced return self.get_stanza_id(self.stanza) @@ -1424,6 +1486,8 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): protocol=True) if sent_carbon is not None: message = self.get_forwarded_message(sent_carbon) + if self._is_self_message(message) or self._is_muc_pm(message): + return message.getOriginID() return self.get_stanza_id(message)
# Received Carbon @@ -1432,9 +1496,13 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): protocol=True) if received_carbon is not None: message = self.get_forwarded_message(received_carbon) + if self._is_muc_pm(message): + return message.getOriginID() return self.get_stanza_id(message)
# Normal Message + if self._is_self_message(self.stanza) or self._is_muc_pm(self.stanza): + return self.stanza.getOriginID() return self.get_stanza_id(self.stanza)
class ZeroconfMessageReceivedEvent(MessageReceivedEvent): @@ -1547,6 +1615,7 @@ class DecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent): self.forwarded = self.msg_obj.forwarded self.sent = self.msg_obj.sent self.conn = self.msg_obj.conn + self.muc_pm = self.msg_obj.muc_pm self.popup = False self.msg_log_id = None # id in log database self.attention = False # XEP-0224
===================================== gajim/common/contacts.py ===================================== --- a/gajim/common/contacts.py +++ b/gajim/common/contacts.py @@ -509,6 +509,15 @@ class Contacts(): return c return self._contacts[jid][0]
+ def get_contact_strict(self, jid, resource): + """ + Return the contact instance for the given resource or None + """ + if jid in self._contacts: + for c in self._contacts[jid]: + if c.resource == resource: + return c + def get_avatar(self, jid, size=None, scale=None): if jid not in self._contacts: return None @@ -554,7 +563,7 @@ class Contacts(): Get Contact object for specific resource of given jid """ barejid, resource = common.app.get_room_and_nick_from_fjid(fjid) - return self.get_contact(barejid, resource) + return self.get_contact_strict(barejid, resource)
def get_first_contact_from_jid(self, jid): if jid in self._contacts:
===================================== gajim/common/helpers.py ===================================== --- a/gajim/common/helpers.py +++ b/gajim/common/helpers.py @@ -1592,3 +1592,35 @@ def version_condition(current_version, required_version): if V(current_version) < V(required_version): return False return True + +def get_available_emoticon_themes(): + emoticons_themes = [] + emoticons_data_path = os.path.join(app.DATA_DIR, 'emoticons') + font_theme_path = os.path.join( + app.DATA_DIR, 'emoticons', 'font-emoticons', 'emoticons_theme.py') + + folders = os.listdir(emoticons_data_path) + if os.path.isdir(app.MY_EMOTS_PATH): + folders += os.listdir(app.MY_EMOTS_PATH) + + file = 'emoticons_theme.py' + if os.name == 'nt' and not os.path.exists(font_theme_path): + # When starting Gajim from source .py files are available + # We test this with font-emoticons and fallback to .pyc files otherwise + file = 'emoticons_theme.pyc' + + for theme in folders: + theme_path = os.path.join(emoticons_data_path, theme, file) + if os.path.exists(theme_path): + emoticons_themes.append(theme) + emoticons_themes.sort() + return emoticons_themes + +def get_emoticon_theme_path(theme): + emoticons_data_path = os.path.join(app.DATA_DIR, 'emoticons', theme) + if os.path.exists(emoticons_data_path): + return emoticons_data_path + + emoticons_user_path = os.path.join(app.MY_EMOTS_PATH, theme) + if os.path.exists(emoticons_user_path): + return emoticons_user_path
===================================== gajim/common/logger.py ===================================== --- a/gajim/common/logger.py +++ b/gajim/common/logger.py @@ -1158,6 +1158,7 @@ class Logger: :param account: The account
:param archive_jid: The jid of the archive the stanza-id belongs to + only used if groupchat=True
:param stanza_id: The stanza-id
@@ -1182,7 +1183,7 @@ class Logger: if groupchat: # Stanza ID is only unique within a specific archive. # So a Stanza ID could be repeated in different MUCs, so we - # filter also for the archive JID + # filter also for the archive JID which is the bare MUC jid. sql = ''' SELECT stanza_id FROM logs WHERE stanza_id IN ({values}) @@ -1193,14 +1194,14 @@ class Logger: else: sql = ''' SELECT stanza_id FROM logs - WHERE stanza_id IN ({values}) AND account_id = ? LIMIT 1 + WHERE stanza_id IN ({values}) AND account_id = ? AND kind != ? LIMIT 1 '''.format(values=', '.join('?' * len(ids))) result = self.con.execute( - sql, tuple(ids) + (account_id,)).fetchone() + sql, tuple(ids) + (account_id, KindConstant.GC_MSG)).fetchone()
if result is not None: log.info('Found duplicated message, stanza-id: %s, origin-id: %s, ' - 'archive-jid: %s, account: %s', stanza_id, origin_id, archive_id, account_id) + 'archive-jid: %s, account: %s', stanza_id, origin_id, archive_jid, account_id) return True return False
===================================== gajim/common/message_archiving.py ===================================== --- a/gajim/common/message_archiving.py +++ b/gajim/common/message_archiving.py @@ -200,13 +200,19 @@ class ConnectionArchive313: def _nec_mam_decrypted_message_received(self, obj): if obj.conn.name != self.name: return - # if self.archiving_namespace != nbxmpp.NS_MAM_2: - # Fallback duplicate search without stanza-id - duplicate = app.logger.search_for_duplicate( - self.name, obj.with_, obj.timestamp, obj.msgtxt) - if duplicate: - # dont propagate the event further - return True + + namespace = self.archiving_namespace + if obj.groupchat: + namespace = muc_caps_cache.get_mam_namespace(obj.room_jid) + + if namespace != nbxmpp.NS_MAM_2: + # Fallback duplicate search without stanza-id + duplicate = app.logger.search_for_duplicate( + self.name, obj.with_, obj.timestamp, obj.msgtxt) + if duplicate: + # dont propagate the event further + return True + app.logger.insert_into_logs(self.name, obj.with_, obj.timestamp,
===================================== gajim/config.py ===================================== --- a/gajim/config.py +++ b/gajim/config.py @@ -143,29 +143,17 @@ class PreferencesWindow:
# emoticons emoticons_combobox = self.xml.get_object('emoticons_combobox') - emoticons_list = os.listdir(os.path.join(app.DATA_DIR, 'emoticons')) - # user themes - if os.path.isdir(app.MY_EMOTS_PATH): - emoticons_list += os.listdir(app.MY_EMOTS_PATH) - emoticons_list.sort() - renderer_text = Gtk.CellRendererText() - emoticons_combobox.pack_start(renderer_text, True) - emoticons_combobox.add_attribute(renderer_text, 'text', 0) - model = Gtk.ListStore(str) - emoticons_combobox.set_model(model) - l = [_('Disabled')] - for dir_ in emoticons_list: - if not os.path.isdir(os.path.join(app.DATA_DIR, 'emoticons', dir_)) \ - and not os.path.isdir(os.path.join(app.MY_EMOTS_PATH, dir_)) : - continue - if dir_ != '.svn': - l.append(dir_) - for i in range(len(l)): - model.append([l[i]]) - if app.config.get('emoticons_theme') == l[i]: - emoticons_combobox.set_active(i) - if not app.config.get('emoticons_theme'): - emoticons_combobox.set_active(0) + emoticon_themes = helpers.get_available_emoticon_themes() + + emoticons_combobox.append_text(_('Disabled')) + for theme in emoticon_themes: + emoticons_combobox.append_text(theme) + + config_theme = app.config.get('emoticons_theme') + if config_theme not in emoticon_themes: + config_theme = _('Disabled') + emoticons_combobox.set_id_column(0) + emoticons_combobox.set_active_id(config_theme)
# Set default for single window type choices = c_config.opt_one_window_types
===================================== gajim/data/gui/chat_control.ui ===================================== --- a/gajim/data/gui/chat_control.ui +++ b/gajim/data/gui/chat_control.ui @@ -392,6 +392,8 @@ <property name="visible_window">False</property> <child> <object class="GtkImage" id="avatar_image"> + <property name="width_request">48</property> + <property name="height_request">48</property> <property name="visible">True</property> <property name="can_focus">False</property> <property name="stock">gtk-missing-image</property>
===================================== gajim/data/gui/history_manager.ui ===================================== --- a/gajim/data/gui/history_manager.ui +++ b/gajim/data/gui/history_manager.ui @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.18.3 --> +<!-- Generated with glade 3.20.2 --> <interface> <requires lib="gtk+" version="3.12"/> <object class="GtkMenu" id="context_menu"> @@ -82,6 +82,9 @@ <action-widget response="-6">cancel_button</action-widget> <action-widget response="-5">save_button</action-widget> </action-widgets> + <child> + <placeholder/> + </child> </object> <object class="GtkImage" id="image1"> <property name="visible">True</property> @@ -136,111 +139,56 @@ <property name="orientation">vertical</property> <child> <object class="GtkBox" id="welcome_vbox"> + <property name="width_request">300</property> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> + <property name="margin_left">20</property> + <property name="margin_right">20</property> <property name="orientation">vertical</property> + <property name="spacing">10</property> <child> - <object class="GtkLabel" id="welcome_label"> + <object class="GtkLabel"> <property name="visible">True</property> <property name="can_focus">False</property> + <property name="halign">center</property> <property name="label" translatable="yes"><big><b>Welcome to Gajim History Logs Manager</b></big></property> <property name="use_markup">True</property> - <property name="justify">center</property> - <property name="wrap">True</property> </object> <packing> - <property name="expand">True</property> + <property name="expand">False</property> <property name="fill">True</property> <property name="position">0</property> </packing> </child> <child> - <object class="GtkBox" id="hbox3"> + <object class="GtkLabel"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <child> - <object class="GtkImage" id="image3"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">1</property> - <property name="yalign">0</property> - <property name="stock">gtk-info</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="info_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <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. + <property name="halign">start</property> + <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.
Use this program to delete or export logs. You can select logs from the left and/or search database from below.</property> - <property name="use_markup">True</property> - <property name="wrap">True</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> + <property name="wrap">True</property> </object> <packing> <property name="expand">False</property> - <property name="fill">False</property> - <property name="padding">20</property> + <property name="fill">True</property> <property name="position">1</property> </packing> </child> <child> - <object class="GtkBox" id="hbox2"> + <object class="GtkLabel"> <property name="visible">True</property> <property name="can_focus">False</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <child> - <object class="GtkImage" id="warning_image"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="xalign">1</property> - <property name="yalign">0</property> - <property name="stock">gtk-dialog-warning</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - <child> - <object class="GtkLabel" id="warning_label"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property> - <property name="label" translatable="yes"><b>WARNING:</b> + <property name="halign">start</property> + <property name="label" translatable="yes"><b>WARNING:</b> If you plan to do massive deletions, please make sure Gajim is not running. Generally avoid deletions with contacts you currently chat with.</property> - <property name="use_markup">True</property> - <property name="wrap">True</property> - </object> - <packing> - <property name="expand">True</property> - <property name="fill">True</property> - <property name="position">1</property> - </packing> - </child> + <property name="use_markup">True</property> + <property name="wrap">True</property> </object> <packing> <property name="expand">False</property> - <property name="fill">False</property> - <property name="padding">20</property> + <property name="fill">True</property> <property name="position">2</property> </packing> </child> @@ -358,5 +306,8 @@ If you plan to do massive deletions, please make sure Gajim is not running. Gene </child> </object> </child> + <child type="titlebar"> + <placeholder/> + </child> </object> </interface>
===================================== gajim/data/gui/preferences_window.ui ===================================== --- a/gajim/data/gui/preferences_window.ui +++ b/gajim/data/gui/preferences_window.ui @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8"?> -<!-- Generated with glade 3.20.0 --> +<!-- Generated with glade 3.20.2 --> <interface> <requires lib="gtk+" version="3.12"/> <object class="GtkAdjustment" id="adjustment1"> @@ -516,17 +516,6 @@ </packing> </child> <child> - <object class="GtkComboBox" id="emoticons_combobox"> - <property name="visible">True</property> - <property name="can_focus">False</property> - <signal name="changed" handler="on_emoticons_combobox_changed" swapped="no"/> - </object> - <packing> - <property name="left_attach">1</property> - <property name="top_attach">0</property> - </packing> - </child> - <child> <object class="GtkCheckButton" id="show_avatar_in_tabs_checkbutton"> <property name="label" translatable="yes">Show avatar in chat tabs</property> <property name="visible">True</property> @@ -542,6 +531,17 @@ <property name="width">2</property> </packing> </child> + <child> + <object class="GtkComboBoxText" id="emoticons_combobox"> + <property name="visible">True</property> + <property name="can_focus">False</property> + <signal name="changed" handler="on_emoticons_combobox_changed" swapped="no"/> + </object> + <packing> + <property name="left_attach">1</property> + <property name="top_attach">0</property> + </packing> + </child> </object> </child> <child type="label"> @@ -2888,5 +2888,8 @@ to discover one from server.</property> </child> </object> </child> + <child type="titlebar"> + <placeholder/> + </child> </object> </interface>
===================================== gajim/dialogs.py ===================================== --- a/gajim/dialogs.py +++ b/gajim/dialogs.py @@ -3006,18 +3006,20 @@ class ContactRow(Gtk.Grid): self.new = jid == ''
if self.groupchat: - if self.new: - muc_image = app.interface.jabber_state_images['32']['muc_inactive'] - else: - muc_image = app.interface.jabber_state_images['32']['muc_active'] - image = Gtk.Image.new_from_pixbuf(muc_image.get_pixbuf()) + muc_icon = gtkgui_helpers.get_iconset_name_for( + 'muc-inactive' if self.new else 'muc-active') + image = Gtk.Image.new_from_icon_name(muc_icon, Gtk.IconSize.DND) else: - avatar = app.contacts.get_avatar(account, jid, AvatarSize.ROSTER) + scale = self.get_scale_factor() + avatar = app.contacts.get_avatar( + account, jid, AvatarSize.ROSTER, scale) if avatar is None: image = Gtk.Image.new_from_icon_name( 'avatar-default', Gtk.IconSize.DND) else: - image = Gtk.Image.new_from_pixbuf(avatar) + image = Gtk.Image.new_from_surface(avatar) + + image.set_size_request(AvatarSize.ROSTER, AvatarSize.ROSTER) self.add(image)
middle_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=0)
===================================== gajim/groupchat_control.py ===================================== --- a/gajim/groupchat_control.py +++ b/gajim/groupchat_control.py @@ -82,60 +82,61 @@ empty_pixbuf = GdkPixbuf.Pixbuf.new(GdkPixbuf.Colorspace.RGB, True, 8, 1, 1) empty_pixbuf.fill(0xffffff00)
-def status_cell_data_func(column, renderer, model, iter_, user_data): +def cell_data_func(column, renderer, model, iter_, user_data): + # Background color has to be rendered for all cells + theme = app.config.get('roster_theme') + has_parent = bool(model.iter_parent(iter_)) + if has_parent: + bgcolor = app.config.get_per('themes', theme, 'contactbgcolor') + renderer.set_property('cell-background', bgcolor or None) + else: + bgcolor = app.config.get_per('themes', theme, 'groupbgcolor') + renderer.set_property('cell-background', bgcolor or None) + + if user_data == 'status': + status_cell_data_func(column, renderer, model, iter_, has_parent) + elif user_data == 'name': + text_cell_data_func(column, renderer, model, iter_, has_parent, theme) + elif user_data == 'avatar': + avatar_cell_data_func(column, renderer, model, iter_, has_parent) + +def status_cell_data_func(column, renderer, model, iter_, has_parent): renderer.set_property('width', 26) image = model[iter_][Column.IMG] surface = image.get_property('surface') renderer.set_property('surface', surface)
- -def tree_cell_data_func(column, renderer, model, iter_, user_data): - # cell data func is global, because we don't want it to keep - # reference to GroupchatControl instance (self) - theme = app.config.get('roster_theme') - # allocate space for avatar only if needed - parent_iter = model.iter_parent(iter_) - if isinstance(renderer, Gtk.CellRendererPixbuf): - image = model[iter_][Column.AVATAR_IMG] - if image is None: - return +def avatar_cell_data_func(column, renderer, model, iter_, has_parent): + image = model[iter_][Column.AVATAR_IMG] + if image is None: + renderer.set_property('surface', None) + else: surface = image.get_property('surface') renderer.set_property('surface', surface)
- avatar_position = app.config.get('avatar_position_in_roster') - if avatar_position == 'right': - renderer.set_property('xalign', 1) # align pixbuf to the right - else: - renderer.set_property('xalign', 0.5) - if parent_iter: - renderer.set_property('visible', True) - renderer.set_property('width', AvatarSize.ROSTER) - else: - renderer.set_property('visible', False) - if parent_iter: - bgcolor = app.config.get_per('themes', theme, 'contactbgcolor') - if bgcolor: - renderer.set_property('cell-background', bgcolor) + renderer.set_property('xalign', 0.5) + if has_parent: + renderer.set_property('visible', True) + renderer.set_property('width', AvatarSize.ROSTER) + else: + renderer.set_property('visible', False) + +def text_cell_data_func(column, renderer, model, iter_, has_parent, theme): + # cell data func is global, because we don't want it to keep + # reference to GroupchatControl instance (self) + if has_parent: + color = app.config.get_per('themes', theme, 'contacttextcolor') + if color: + renderer.set_property('foreground', color) else: - renderer.set_property('cell-background', None) - if isinstance(renderer, Gtk.CellRendererText): - # foreground property is only with CellRendererText - color = app.config.get_per('themes', theme, 'contacttextcolor') - if color: - renderer.set_property('foreground', color) - else: - renderer.set_property('foreground', None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) - else: # it is root (eg. group) - bgcolor = app.config.get_per('themes', theme, 'groupbgcolor') - renderer.set_property('cell-background', bgcolor or None) - if isinstance(renderer, Gtk.CellRendererText): - # foreground property is only with CellRendererText - color = app.config.get_per('themes', theme, 'grouptextcolor') - renderer.set_property('foreground', color or None) - renderer.set_property('font', - gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont')) + renderer.set_property('foreground', None) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'contactfont')) + else: + color = app.config.get_per('themes', theme, 'grouptextcolor') + renderer.set_property('foreground', color or None) + renderer.set_property('font', + gtkgui_helpers.get_theme_font_for_option(theme, 'groupfont'))
class PrivateChatControl(ChatControl): @@ -438,15 +439,15 @@ class GroupchatControl(ChatControlBase): self.renderers_list += ( # status img ('icon', Gtk.CellRendererPixbuf(), False, - None, Column.IMG, status_cell_data_func, None), + None, Column.IMG, cell_data_func, 'status'), # contact name ('name', renderer_text, True, - 'markup', Column.TEXT, tree_cell_data_func, None)) + 'markup', Column.TEXT, cell_data_func, 'name'))
# avatar img avatar_renderer = ('avatar', Gtk.CellRendererPixbuf(), False, None, Column.AVATAR_IMG, - tree_cell_data_func, None) + cell_data_func, 'avatar')
if app.config.get('avatar_position_in_roster') == 'right': self.renderers_list.append(avatar_renderer) @@ -1109,8 +1110,9 @@ class GroupchatControl(ChatControlBase): return self.print_conversation( obj.msgtxt, contact=obj.nick, - tim=obj.timestamp, encrypted=obj.encrypted, - msg_stanza_id=obj.unique_id, + tim=obj.timestamp, correct_id=obj.correct_id, + encrypted=obj.encrypted, + msg_stanza_id=obj.message_id, additional_data=obj.additional_data)
def _nec_gc_message_received(self, obj):
===================================== gajim/gtkgui_helpers.py ===================================== --- a/gajim/gtkgui_helpers.py +++ b/gajim/gtkgui_helpers.py @@ -437,6 +437,16 @@ def get_possible_button_event(event): def destroy_widget(widget): widget.destroy()
+def scale_with_ratio(size, width, height): + if height == width: + return size, size + if height > width: + ratio = height / float(width) + return int(size / ratio), size + else: + ratio = width / float(height) + return size, int(size / ratio) + def on_avatar_save_as_menuitem_activate(widget, avatar, default_name=''): def on_continue(response, file_path): if response < 0:
===================================== gajim/gui_interface.py ===================================== --- a/gajim/gui_interface.py +++ b/gajim/gui_interface.py @@ -1996,18 +1996,16 @@ class Interface: if 'preferences' in app.interface.instances: transient_for = app.interface.instances['preferences'].window
- path = os.path.join(app.DATA_DIR, 'emoticons', emot_theme) - if not os.path.exists(path): - # It's maybe a user theme - path = os.path.join(app.MY_EMOTS_PATH, emot_theme) - if not os.path.exists(path): - # theme doesn't exist, disable emoticons - dialogs.WarningDialog(_('Emoticons disabled'), - _('Your configured emoticons theme has not been found, so ' - 'emoticons have been disabled.'), - transient_for=transient_for) + themes = helpers.get_available_emoticon_themes() + if emot_theme not in themes: + if 'font-emoticons' in themes: + emot_theme = 'font-emoticons' + app.config.set('emoticons_theme', 'font-emoticons') + else: app.config.set('emoticons_theme', '') return + + path = helpers.get_emoticon_theme_path(emot_theme) if not emoticons.load(path, ascii_emoticons): dialogs.WarningDialog( _('Emoticons disabled'), @@ -2482,7 +2480,7 @@ class Interface: try: if size is not None: pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale( - path, size, size, False) + path, size, size, True) else: pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) except GLib.GError as error: @@ -2503,8 +2501,10 @@ class Interface: array, GdkPixbuf.Colorspace.RGB, True, 8, width, height, width * 4) if size: + width, height = gtkgui_helpers.scale_with_ratio( + size, width, height) pixbuf = pixbuf.scale_simple( - size, size, GdkPixbuf.InterpType.BILINEAR) + width, height, GdkPixbuf.InterpType.BILINEAR)
if filename not in app.avatar_cache: app.avatar_cache[filename] = {}
===================================== gajim/roster_window.py ===================================== --- a/gajim/roster_window.py +++ b/gajim/roster_window.py @@ -4864,11 +4864,8 @@ class RosterWindow: app.config.get('avatar_position_in_roster') != 'left': renderer.set_property('visible', False)
- if app.config.get('avatar_position_in_roster') == 'left': - renderer.set_property('width', AvatarSize.ROSTER) - renderer.set_property('xalign', 0.5) - else: - renderer.set_property('xalign', 1) # align pixbuf to the right + renderer.set_property('width', AvatarSize.ROSTER) + renderer.set_property('xalign', 0.5)
def _fill_padlock_pixbuf_renderer(self, column, renderer, model, titer, data=None):
===================================== gajim/session.py ===================================== --- a/gajim/session.py +++ b/gajim/session.py @@ -101,7 +101,7 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): if treat_as: obj.mtype = treat_as pm = False - if obj.gc_control and obj.resource: + if obj.muc_pm or (obj.gc_control and obj.resource): # It's a Private message pm = True obj.mtype = 'pm' @@ -123,6 +123,11 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession): additional_data=obj.additional_data, stanza_id=obj.unique_id)
+ if obj.muc_pm and not obj.gc_control: + # This is a carbon of a PM from a MUC we are not currently + # joined. We log it silently without notification. + return True + # Handle chat states if contact and (not obj.forwarded or not obj.sent): if self.control and self.control.type_id == \
===================================== gajim/statusicon.py ===================================== --- a/gajim/statusicon.py +++ b/gajim/statusicon.py @@ -245,7 +245,7 @@ class StatusIcon:
for account in accounts_list: account_label = app.config.get_per( - 'accounts', account, 'account_label') + 'accounts', account, 'account_label') or account if app.account_is_connected(account): # for chat_with item = Gtk.MenuItem.new_with_label( @@ -291,7 +291,7 @@ class StatusIcon:
for account in accounts_list: account_label = app.config.get_per( - 'accounts', account, 'account_label') + 'accounts', account, 'account_label') or account if app.connections[account].is_zeroconf or \ not app.account_is_connected(account): continue
===================================== gajim/tooltips.py ===================================== --- a/gajim/tooltips.py +++ b/gajim/tooltips.py @@ -252,6 +252,9 @@ class GCTooltip(): self.avatar.show() self.fillelement.show()
+ app.plugin_manager.gui_extension_point( + 'gc_tooltip_populate', self, contact, self.tooltip_grid) + @staticmethod def colorize_affiliation(affiliation): """
===================================== setup.py ===================================== --- a/setup.py +++ b/setup.py @@ -6,8 +6,6 @@ import sys if sys.version_info[0] < 3: sys.exit('Tried to install with Python 2, gajim only supports Python 3.')
-import codecs - from setuptools import setup, find_packages from setuptools import Command from setuptools.command.build_py import build_py as _build @@ -129,20 +127,6 @@ def build_intl(build_cmd): data_files.append((target, [base + '/' + filename]))
-def substitute_variables(filename_in, filename_out, subst_vars): - ''' - Substitute variables in a file. - ''' - f_in = codecs.open(filename_in, encoding='utf-8') - f_out = codecs.open(filename_out, encoding='utf-8', mode='w') - for line in f_in: - for variable, substitution in subst_vars: - line = line.replace(variable, substitution) - f_out.write(line) - f_in.close() - f_out.close() - - def merge(in_file, out_file, option, po_dir='po'): ''' Run the msgfmt command. @@ -276,7 +260,7 @@ setup( package_data={'gajim': package_data}, data_files=data_files, install_requires=[ - 'nbxmpp>=0.6.1', + 'nbxmpp>=0.6.3', 'pyOpenSSL>=0.12', 'pyasn1', ],
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/84748928d41e11bc57d5b7de7d5f0fb59f...
--- View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/84748928d41e11bc57d5b7de7d5f0fb59f... You're receiving this email because of your account on dev.gajim.org.