Philipp Hörist pushed to branch master at gajim / gajim
Commits: 37646ac6 by lovetox at 2022-01-22T19:39:52+01:00 ConversationView: Destroy rows instead of removing them
- - - - - aed21314 by lovetox at 2022-01-22T19:39:52+01:00 Contacts: Add subject property
- - - - - 8599d5ad by lovetox at 2022-01-22T19:39:52+01:00 Contacts: Add type annotations
- - - - - 8d1c041f by lovetox at 2022-01-22T19:39:52+01:00 Add GroupchatDetails Dialog
- Move GroupchatInfo Page into Dialog - Refactor handling muc subject
- - - - -
11 changed files:
- gajim/common/modules/contacts.py - gajim/common/modules/muc.py - gajim/data/gui/groupchat_control.ui - gajim/gtk/const.py - gajim/gtk/controls/groupchat.py - gajim/gtk/conversation/rows/muc_subject.py - gajim/gtk/conversation/view.py - + gajim/gtk/groupchat_details.py - gajim/gtk/groupchat_info.py - gajim/gtk/menus.py - pyrightconfig.json
Changes:
===================================== gajim/common/modules/contacts.py ===================================== @@ -27,6 +27,8 @@ from nbxmpp.const import Affiliation from nbxmpp.const import Role from nbxmpp.protocol import JID +from nbxmpp.structs import DiscoInfo +from nbxmpp.structs import MucSubject
from gajim.common import app from gajim.common import types @@ -551,6 +553,13 @@ def occupant_jid(self): return None return muc_data.occupant_jid
+ @property + def subject(self) -> Optional[MucSubject]: + muc_data = self._module('MUC').get_muc_data(self._jid) + if muc_data is None: + return None + return muc_data.subject + @property def is_joined(self) -> bool: muc_data = self._module('MUC').get_muc_data(self._jid) @@ -576,11 +585,11 @@ def set_not_joined(self) -> None: for contact in self._resources.values(): contact.update_presence(UNKNOWN_MUC_PRESENCE, notify=False)
- def get_user_nicknames(self): + def get_user_nicknames(self) -> list[str]: client = app.get_client(self._account) return client.get_module('MUC').get_joined_users(self._jid)
- def get_disco(self, max_age=0): + def get_disco(self, max_age: int = 0) -> Optional[DiscoInfo]: return app.storage.cache.get_last_disco_info(self.jid, max_age=max_age)
def can_notify(self) -> bool:
===================================== gajim/common/modules/muc.py ===================================== @@ -17,6 +17,7 @@
import logging from collections import defaultdict +import time
import nbxmpp from nbxmpp.namespaces import Namespace @@ -655,9 +656,13 @@ def _on_subject_change(self, _con, _stanza, properties): self._log.warning('No MUCData found for %s', room_jid) return
- muc_data.subject = properties.subject + muc_subject = properties.muc_subject + if muc_subject is not None and muc_subject.timestamp is None: + muc_subject = muc_subject._replace(timestamp=time.time()) + + muc_data.subject = muc_subject room = self._get_contact(room_jid) - room.notify('room-subject', properties) + room.notify('room-subject', muc_subject)
if muc_data.state == MUCJoinedState.JOINING: self._room_join_complete(muc_data)
===================================== gajim/data/gui/groupchat_control.ui ===================================== @@ -1334,96 +1334,7 @@ </packing> </child> <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="orientation">vertical</property> - <child> - <!-- n-columns=1 n-rows=3 --> - <object class="GtkGrid" id="info_grid"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="orientation">vertical</property> - <property name="row-spacing">12</property> - <child> - <object class="GtkBox"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="homogeneous">True</property> - <child> - <object class="GtkButton" id="info_close_button"> - <property name="label" translatable="yes">_Close</property> - <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="can-default">True</property> - <property name="receives-default">True</property> - <property name="halign">center</property> - <property name="valign">start</property> - <property name="use-underline">True</property> - <signal name="clicked" handler="_on_page_cancel_clicked" swapped="no"/> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">2</property> - </packing> - </child> - <child> - <object class="GtkLabel"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="label" translatable="yes">Information</property> - <style> - <class name="bold16"/> - </style> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">0</property> - </packing> - </child> - <child> - <object class="GtkBox" id="info_box"> - <property name="visible">True</property> - <property name="can-focus">False</property> - <property name="hexpand">True</property> - <property name="vexpand">True</property> - <property name="orientation">vertical</property> - <child> - <placeholder/> - </child> - <style> - <class name="margin-18"/> - </style> - </object> - <packing> - <property name="left-attach">0</property> - <property name="top-attach">1</property> - </packing> - </child> - <style> - <class name="margin-18"/> - </style> - </object> - <packing> - <property name="expand">False</property> - <property name="fill">True</property> - <property name="position">0</property> - </packing> - </child> - </object> - <packing> - <property name="name">muc-info</property> - <property name="position">7</property> - </packing> + <placeholder/> </child> <child> <object class="GtkBox">
===================================== gajim/gtk/const.py ===================================== @@ -145,6 +145,7 @@ def __str__(self): 'CreateGroupchatWindow': 'gajim.gui.groupchat_creation', 'Features': 'gajim.gui.features', 'GroupchatConfig': 'gajim.gui.groupchat_config', + 'GroupchatDetails': 'gajim.gui.groupchat_details', 'GroupChatInvitation': 'gajim.gui.groupchat_invitation', 'GroupchatJoin': 'gajim.gui.groupchat_join', 'HistoryExport': 'gajim.gui.history_export',
===================================== gajim/gtk/controls/groupchat.py ===================================== @@ -27,7 +27,6 @@ from typing import Any from typing import Optional
-import time import logging
from nbxmpp import JID @@ -41,6 +40,7 @@ from nbxmpp.modules.vcard_temp import VCard from nbxmpp.structs import PresenceProperties from nbxmpp.structs import MessageProperties +from nbxmpp.structs import MucSubject from nbxmpp.task import Task
from gi.repository import Gtk @@ -77,7 +77,6 @@ from gajim.gui.groupchat_config import GroupchatConfig from gajim.gui.avatar_selector import AvatarSelector from gajim.gui.dataform import DataFormWidget -from gajim.gui.groupchat_info import GroupChatInfoScrolled from gajim.gui.groupchat_inviter import GroupChatInviter from gajim.gui.groupchat_settings import GroupChatSettings from gajim.gui.groupchat_roster import GroupchatRoster @@ -171,10 +170,7 @@ def __init__(self, account: str, jid: JID) -> None: # Holds CaptchaRequest widget self._captcha_request: Optional[DataFormWidget] = None
- # MUC Info - self._subject_data = None - self._muc_info_box = GroupChatInfoScrolled(self.account) - self.xml.info_box.add(self._muc_info_box) + self._subject_text = ''
# Groupchat settings self._groupchat_settings_box: Optional[GroupChatSettings] = None @@ -286,12 +282,6 @@ def _nick_completion(self) -> NickCompletionGenerator: self.contact.nickname) return self.__nick_completion
- @property - def subject(self) -> str: - if self._subject_data is None: - return '' - return self._subject_data.subject - @property def disco_info(self): return app.storage.cache.get_last_disco_info(self.contact.jid) @@ -306,7 +296,7 @@ def add_actions(self) -> None: ('destroy-', None, self._on_destroy_room), ('configure-', None, self._on_configure_room), ('request-voice-', None, self._on_request_voice), - ('information-', None, self._on_information), + ('groupchat-details-', None, self._on_details), ('invite-', None, self._on_invite), ('contact-information-', 's', self._on_contact_information), ('execute-command-', 's', self._on_execute_command), @@ -406,7 +396,7 @@ def remove_actions(self) -> None: 'destroy-', 'configure-', 'request-voice-', - 'information-', + 'groupchat-details-', 'invite-', 'contact-information-', 'execute-command-', @@ -441,11 +431,6 @@ def _show_page(self, name: str) -> None: if name == 'groupchat': transition = Gtk.StackTransitionType.SLIDE_UP self.msg_textview.grab_focus() - if name == 'muc-info': - # Set focus on the close button, otherwise one of - # the selectable labels of the GroupchatInfo box gets focus, - # which means it is fully selected - self.xml.info_close_button.grab_focus() self.xml.stack.set_visible_child_full(name, transition)
def _get_current_page(self) -> str: @@ -456,16 +441,11 @@ def _on_muc_disco_update(self, _event: events.MucDiscoUpdate) -> None: self.draw_banner_text()
# Actions - def _on_information(self, - _action: Gio.SimpleAction, - _param: Optional[GLib.Variant] - ) -> None: - self._muc_info_box.set_from_disco_info(self.disco_info) - if self._subject_data is not None: - self._muc_info_box.set_subject(self._subject_data.subject) - self._muc_info_box.set_author(self._subject_data.muc_nickname, - self._subject_data.user_timestamp) - self._show_page('muc-info') + def _on_details(self, + _action: Gio.SimpleAction, + _param: Optional[GLib.Variant] + ) -> None: + open_window('GroupchatDetails', contact=self.contact)
def _on_groupchat_settings(self, _action: Gio.SimpleAction, @@ -872,23 +852,21 @@ def add_message(self, def _on_room_subject(self, _contact: GroupchatContact, _signal_name: str, - properties: MessageProperties + subject: Optional[MucSubject] ) -> None: - if self.subject == properties.subject: - # Probably a rejoin, we already showed that subject + + if subject is None: return
- self._subject_data = properties + if self._subject_text == subject.text: + # Probably a rejoin, we already showed that subject + return
- date = '' - if properties.user_timestamp: - date = time.strftime('%c', - time.localtime(properties.user_timestamp)) + self._subject_text = subject.text
if (app.settings.get('show_subject_on_join') or not self.contact.is_joining): - self.conversation_view.add_muc_subject( - properties.subject, properties.muc_nickname, date) + self.conversation_view.add_muc_subject(subject)
def _on_room_config_changed(self, _contact: GroupchatContact, @@ -1594,8 +1572,6 @@ def delegate_action(self, action: str) -> int: self._on_password_cancel_clicked() elif self._get_current_page() == 'captcha': self._on_captcha_cancel_clicked() - elif self._get_current_page() == 'muc-info': - self._on_page_cancel_clicked() elif self._get_current_page() in ('error', 'captcha-error'): self._on_page_close_clicked() else: @@ -1609,7 +1585,7 @@ def delegate_action(self, action: str) -> int:
if action == 'show-contact-info': app.window.lookup_action( - f'information-{self.control_id}').activate() + f'groupchat-details-{self.control_id}').activate() return Gdk.EVENT_STOP
return Gdk.EVENT_PROPAGATE @@ -1650,8 +1626,6 @@ def _on_page_change(self, stack: Gtk.Stack, _param: Any) -> None: page_name = stack.get_visible_child_name() if page_name == 'groupchat': pass - elif page_name == 'muc-info': - self.xml.info_close_button.grab_default() elif page_name == 'password': self.xml.password_entry.set_text('') self.xml.password_entry.grab_focus() @@ -1730,7 +1704,7 @@ def _on_manage_save_clicked(self, _button: Gtk.Button) -> None: def _on_change_subject(self, _button: Gtk.Button) -> None: if self._get_current_page() not in ('groupchat', 'muc-manage'): return - self.xml.subject_textview.get_buffer().set_text(self.subject) + self.xml.subject_textview.get_buffer().set_text(self._subject_text) self.xml.subject_textview.grab_focus() self._show_page('subject')
===================================== gajim/gtk/conversation/rows/muc_subject.py ===================================== @@ -16,6 +16,7 @@ from datetime import datetime
from gi.repository import Gtk +from nbxmpp.structs import MucSubject
from gajim.common.const import AvatarSize from gajim.common.i18n import _ @@ -28,7 +29,7 @@ class MUCSubject(BaseRow):
type = 'muc-subject'
- def __init__(self, account: str, text: str, nick: str, date: str) -> None: + def __init__(self, account: str, subject: MucSubject) -> None: BaseRow.__init__(self, account)
timestamp = time.time() @@ -48,16 +49,21 @@ def __init__(self, account: str, text: str, nick: str, date: str) -> None: title.get_style_context().add_class('bold') subject_box.add(title)
- meta_str = _('Changed by %s') % nick - if date is not None: - meta_str = f'{meta_str} ({date})' + author = _('Changed by %s') % (subject.author or _('Unknown')) + + date = '' + if subject.timestamp is not None: + time_str = time.strftime("%c", time.localtime(subject.timestamp)) + date = f' ({time_str})' + + meta_str = f'{author}{date}' meta = Gtk.Label(label=meta_str) meta.set_halign(Gtk.Align.START) meta.get_style_context().add_class('small-label') subject_box.add(meta)
message_widget = MessageWidget(account) - message_widget.add_with_styling(text) + message_widget.add_with_styling(subject.text) subject_box.add(message_widget) self.grid.attach(subject_box, 1, 0, 1, 1)
===================================== gajim/gtk/conversation/view.py ===================================== @@ -32,6 +32,7 @@
from nbxmpp.errors import StanzaError from nbxmpp.modules.security_labels import Displaymarking +from nbxmpp.structs import MucSubject
from gajim.common import app from gajim.common.client import Client @@ -155,7 +156,6 @@ def clear(self) -> None: for row in self.get_children()[2:]: if isinstance(row, ReadMarkerRow): continue - self.remove(row) row.destroy()
self._reset_conversation_view() @@ -199,9 +199,9 @@ def _filter_func(self, row: BaseRow) -> bool:
return True
- def add_muc_subject(self, text: str, nick: str, date: str) -> None: - subject = MUCSubject(self._account, text, nick, date) - self._insert_message(subject) + def add_muc_subject(self, subject: MucSubject) -> None: + muc_subject = MUCSubject(self._account, subject) + self._insert_message(muc_subject)
def add_muc_user_left(self, nick: str, @@ -418,25 +418,25 @@ def _reduce_messages_before(self) -> bool: if row1.type == row2.type == 'date': # First two rows are date rows, # it’s safe to remove the fist row - self.remove(row1) + row1.destroy() success = True
if row1.type == 'date' and row2.type != 'date': # First one is a date row, keep it and # remove the second row instead - self.remove(row2) + row2.destroy() success = True
if row1.type != 'date': # Not a date row, safe to remove - self.remove(row1) + row1.destroy() success = True
return success
def _reduce_messages_after(self) -> None: row = self.get_row_at_index(len(self.get_children()) - 1) - self.remove(row) + row.destroy()
def scroll_to_message_and_highlight(self, log_line_id: str) -> None: highlight_row = None
===================================== gajim/gtk/groupchat_details.py ===================================== @@ -0,0 +1,77 @@ +# This file is part of Gajim. +# +# Gajim is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published +# by the Free Software Foundation; version 3 only. +# +# Gajim is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Gajim. If not, see http://www.gnu.org/licenses/. + +from __future__ import annotations + +from gi.repository import Gdk +from gi.repository import Gtk + +from gajim.common import app +from gajim.common.i18n import _ +from gajim.common.modules.contacts import GroupchatContact + +from .groupchat_info import GroupChatInfoScrolled +from .sidebar_switcher import SideBarSwitcher + + +class GroupchatDetails(Gtk.ApplicationWindow): + def __init__(self, contact: GroupchatContact) -> None: + Gtk.ApplicationWindow.__init__(self) + self.set_application(app.app) + self.set_position(Gtk.WindowPosition.CENTER) + self.set_show_menubar(False) + self.set_type_hint(Gdk.WindowTypeHint.DIALOG) + self.set_resizable(True) + self.set_default_size(-1, 600) + self.set_title(_('Groupchat Details')) + + self.account = contact.account + self._contact = contact + + main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self._stack = Gtk.Stack() + self._side_bar_switcher = SideBarSwitcher() + + main_box.add(self._side_bar_switcher) + main_box.add(self._stack) + + self._add_groupchat_info() + + self._side_bar_switcher.set_stack(self._stack) + + self.add(main_box) + + self.connect('key-press-event', self._on_key_press) + self.connect('destroy', self._on_destroy) + + self.show_all() + + def _add_groupchat_info(self) -> None: + groupchat_info = GroupChatInfoScrolled(self._contact.account, width=600) + groupchat_info.set_halign(Gtk.Align.FILL) + disco_info = self._contact.get_disco() + assert disco_info is not None + groupchat_info.set_from_disco_info(disco_info) + groupchat_info.set_subject(self._contact.subject) + + self._stack.add_titled(groupchat_info, 'info', _('Information')) + + def _on_key_press(self, + _widget: GroupchatDetails, + event: Gdk.EventKey) -> None: + if event.keyval == Gdk.KEY_Escape: + self.destroy() + + def _on_destroy(self, _widget: GroupchatDetails) -> None: + app.check_finalize(self)
===================================== gajim/gtk/groupchat_info.py ===================================== @@ -23,6 +23,7 @@ from nbxmpp import JID from nbxmpp.namespaces import Namespace from nbxmpp.structs import DiscoInfo +from nbxmpp.structs import MucSubject
from gajim.common import app from gajim.common.i18n import _ @@ -139,22 +140,22 @@ def set_account(self, account: str) -> None: def get_jid(self) -> Optional[JID]: return self._info.jid
- def set_author(self, - author: Optional[str], - epoch_timestamp: Optional[float] = None - ) -> None: + def set_subject(self, muc_subject: Optional[MucSubject]) -> None: + if muc_subject is None: + return + + author = muc_subject.author has_author = bool(author) - if has_author and epoch_timestamp is not None: - time_ = time.strftime('%c', time.localtime(epoch_timestamp)) - author = f'{author} - {time_}' + if has_author and muc_subject.timestamp is not None: + time_ = time.strftime('%c', time.localtime(muc_subject.timestamp)) + author = f'{muc_subject.author} - {time_}'
self._ui.author.set_text(author or '') self._ui.author.set_visible(has_author) self._ui.author_label.set_visible(has_author)
- def set_subject(self, subject: str) -> None: - has_subject = bool(subject) - subject = GLib.markup_escape_text(subject or '') + has_subject = bool(muc_subject.text) + subject = GLib.markup_escape_text(muc_subject.text) self._ui.subject.set_markup(make_href_markup(subject)) self._ui.subject.set_visible(has_subject) self._ui.subject_label.set_visible(has_subject)
===================================== gajim/gtk/menus.py ===================================== @@ -150,7 +150,7 @@ def get_groupchat_menu(control_id: str, account: str, jid: JID) -> Gio.Menu: params = RemoveHistoryActionParams(account=account, jid=jid)
menulist: MenuItemListT = [ - (_('Information'), f'win.information-{control_id}', None), + (_('Details'), f'win.groupchat-details-{control_id}', None), (_('Settings…'), f'win.groupchat-settings-{control_id}', None), (_('Manage…'), f'win.groupchat-manage-{control_id}', None), (_('Rename Chat…'), f'win.rename-groupchat-{control_id}', None),
===================================== pyrightconfig.json ===================================== @@ -54,6 +54,7 @@ "gajim/gtk/conversation/view.py", "gajim/gtk/features.py", "gajim/gtk/file_transfer_send.py", + "gajim/gtk/groupchat_details.py", "gajim/gtk/groupchat_invitation.py", "gajim/gtk/groupchat_inviter.py", "gajim/gtk/groupchat_nick.py",
View it on GitLab: https://dev.gajim.org/gajim/gajim/-/compare/a4880100643e69ae2b037014e2d68383...