Commit 5f071032 authored by Daniel Brötzmann's avatar Daniel Brötzmann
Browse files

Add type annotations and refactor signal handling for ConversationView and its children

parent 69f71372
......@@ -110,7 +110,7 @@ def _raise_event(self, name: str, properties: Any) -> None:
marker_id=properties.marker.id))
def _send_marker(self,
contact: types.ChatContacts,
contact: types.ChatContactT,
marker: str,
id_: str,
type_: str) -> None:
......@@ -139,7 +139,7 @@ def _send_marker(self,
self._log.info('Send %s: %s', marker, contact.jid)
def send_displayed_marker(self,
contact: types.ChatContacts,
contact: types.ChatContactT,
id_: str,
type_: str) -> None:
self._send_marker(contact, 'displayed', id_, type_)
......@@ -42,8 +42,6 @@
from gajim.common.modules.contacts import GroupchatContact
from gajim.common.modules.contacts import GroupchatParticipant
ChatContacts = Union[BareContact, GroupchatContact, GroupchatParticipant]
from gajim.gui_interface import Interface
from gajim.common.settings import Settings
from gajim.gtk.css_config import CSSConfig
......@@ -70,3 +68,6 @@
AnyCallableT = Callable[..., Any]
ObservableCbDict = dict[str, list[weakref.WeakMethod[AnyCallableT]]]
ChatContactT = Union['BareContact', 'GroupchatContact', 'GroupchatParticipant']
GroupchatContactT = Union['GroupchatContact']
......@@ -13,6 +13,7 @@
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
from __future__ import annotations
from typing import Optional
from typing import Any
from typing import cast
......@@ -52,11 +53,9 @@
from gajim.common.helpers import get_uf_relative_time
from gajim.common.helpers import message_needs_highlight
from gajim.common.helpers import AdditionalDataDict
from gajim.common.modules.contacts import BareContact
from gajim.common.modules.contacts import GroupchatContact
from gajim.common.modules.contacts import GroupchatParticipant
from gajim.common.preview_helpers import filename_from_uri
from gajim.common.preview_helpers import guess_simple_file_type
from gajim.common.types import ChatContactT
from .menus import get_chat_list_row_menu
from .builder import get_builder
......@@ -65,7 +64,6 @@
log = logging.getLogger('gajim.gui.chatlist')
MessageEventT = Union[MessageReceived, GcMessageReceived, MamMessageReceived]
ContactT = Union[BareContact, GroupchatContact, GroupchatParticipant]
class ChatList(Gtk.ListBox, EventHelper):
......@@ -746,13 +744,13 @@ def toggle_pinned(self) -> None:
self._pinned = not self._pinned
def _on_presence_update(self,
_contact: ContactT,
_contact: ChatContactT,
_signal_name: str
) -> None:
self.update_avatar()
def _on_avatar_update(self,
_contact: ContactT,
_contact: ChatContactT,
_signal_name: str
) -> None:
self.update_avatar()
......@@ -781,7 +779,7 @@ def update_account_identifier(self) -> None:
self._ui.account_identifier.set_visible(show)
def _on_chatstate_update(self,
contact: ContactT,
contact: ChatContactT,
_signal_name: str
) -> None:
if contact.chatstate is None:
......@@ -790,7 +788,7 @@ def _on_chatstate_update(self,
self._ui.chatstate_image.set_visible(contact.chatstate.is_composing)
def _on_nickname_update(self,
_contact: ContactT,
_contact: ChatContactT,
_signal_name: str
) -> None:
self.update_name()
......
......@@ -79,8 +79,9 @@
from gajim.gui.util import get_hardware_key_codes
from gajim.gui.builder import get_builder
from gajim.gui.util import set_urgency_hint
from gajim.gui.util import scroll_to_end
from gajim.gui.util import AccountBadge
from gajim.gui.const import ControlType # pylint: disable=unused-import
from gajim.gui.const import ControlType
from gajim.gui.const import TARGET_TYPE_URI_LIST
from gajim.gui.emoji_chooser import emoji_chooser
......@@ -188,8 +189,9 @@ def __init__(self, widget_name: str, account: str, jid: JID) -> None:
# Create ConversationView and connect signals
self.conversation_view = ConversationView(self.account, self.contact)
self.conversation_view.connect('quote', self.on_quote)
self.conversation_view.connect('mention', self.on_mention)
self.conversation_view.connect('quote', self._on_quote)
self.conversation_view.connect('mention', self._on_mention)
self.conversation_view.connect('scroll-to-end', self._on_scroll_to_end)
id_ = self.conversation_view.connect(
'key-press-event', self._on_conversation_view_key_press)
......@@ -761,16 +763,19 @@ def paste_clipboard_as_quote(self, _item: Gtk.MenuItem) -> None:
return
self.insert_as_quote(text)
def on_quote(self, _widget: Gtk.Widget, text: str) -> None:
def _on_quote(self, _view: ConversationView, text: str) -> None:
self.insert_as_quote(text)
def on_mention(self, _widget: Gtk.Widget, name: str) -> None:
def _on_mention(self, _view: ConversationView, name: str) -> None:
gc_refer_to_nick_char = app.settings.get('gc_refer_to_nick_char')
text = f'{name}{gc_refer_to_nick_char} '
message_buffer = self.msg_textview.get_buffer()
message_buffer.insert_at_cursor(text)
GLib.idle_add(self.msg_textview.grab_focus)
def _on_scroll_to_end(self, _view: ConversationView) -> None:
scroll_to_end(self._scrolled_view)
def _on_message_textview_paste_event(self,
_texview: MessageInputTextView
) -> None:
......
......@@ -110,8 +110,8 @@ def __init__(self, account: str, jid: JID) -> None:
self._call_widget.connect('call-ended', self._on_call_ended)
self.xml.paned1.add2(self._call_widget)
self.conversation_view.connect('accept-call', self._on_accept_call)
self.conversation_view.connect('decline-call', self._on_decline_call)
self.conversation_view.connect('call-accepted', self._on_call_accepted)
self.conversation_view.connect('call-declined', self._on_call_declined)
# Menu for the HeaderBar
self.control_menu = get_singlechat_menu(
......@@ -480,16 +480,16 @@ def _on_start_call(self,
def _process_jingle_av_event(self, event):
self._call_widget.process_event(event)
def _on_accept_call(self,
_view: ConversationView,
session: JingleSession
) -> None:
def _on_call_accepted(self,
_view: ConversationView,
session: JingleSession
) -> None:
self._call_widget.accept_call(session)
def _on_decline_call(self,
_view: ConversationView,
session: JingleSession
) -> None:
def _on_call_declined(self,
_view: ConversationView,
session: JingleSession
) -> None:
self._call_widget.decline_call(session)
def _on_call_ended(self, _call_widget: CallWidget) -> None:
......
......@@ -33,10 +33,11 @@ def __init__(self, account: str, widget: Optional[str] = None) -> None:
self._client = app.get_client(account)
self.type: str = ''
self.timestamp: datetime = datetime.fromtimestamp(0)
self.kind: Optional[str] = None
self.name: Optional[str] = None
self.kind: str = ''
self.name: str = ''
self.message_id: Optional[str] = None
self.log_line_id: Optional[str] = None
self.stanza_id: Optional[str] = None
self.text: str = ''
self._merged: bool = False
......
......@@ -15,14 +15,13 @@
from __future__ import annotations
from typing import Optional
from typing import cast
from typing import TYPE_CHECKING
import time
from datetime import datetime
from gi.repository import GdkPixbuf
from gi.repository import Gtk
from gi.repository import GObject
from gajim.common import app
from gajim.common import types
......@@ -36,11 +35,22 @@
from .widgets import SimpleLabel
from .base import BaseRow
if TYPE_CHECKING:
from gajim.gui.conversation.view import ConversationView
class CallRow(BaseRow):
__gsignals__ = {
'call-accepted': (
GObject.SignalFlags.RUN_LAST,
None,
(object,)
),
'call-declined': (
GObject.SignalFlags.RUN_LAST,
None,
(object,)
),
}
def __init__(self,
account: str,
contact: types.BareContact,
......@@ -101,24 +111,22 @@ def update(self) -> None:
def _on_accept(self, button: Gtk.Button) -> None:
button.set_sensitive(False)
self._decline_button.set_sensitive(False)
view = cast('ConversationView', self.get_parent())
if self._event is not None:
session = self._client.get_module('Jingle').get_jingle_session(
self._event.fjid, self._event.sid)
view.accept_call(session)
self.emit('call-accepted', session)
else:
assert self._session is not None
view.accept_call(self._session)
self.emit('call-accepted', self._session)
def _on_decline(self, _button: Gtk.Button) -> None:
view = cast('ConversationView', self.get_parent())
if self._event is not None:
session = self._client.get_module('Jingle').get_jingle_session(
self._event.fjid, self._event.sid)
view.decline_call(session)
self.emit('call-declined', session)
else:
assert self._session is not None
view.decline_call(self._session)
self.emit('call-declined', self._session)
self._session = None
def _add_history_call_widget(self) -> None:
......
......@@ -15,14 +15,15 @@
from __future__ import annotations
from typing import Optional
from typing import Union
from datetime import datetime
from datetime import timedelta
from gi.repository import Gdk
from gi.repository import GdkPixbuf
from gi.repository import GLib
from gi.repository import Gtk
from gi.repository import GObject
import cairo
......@@ -30,7 +31,6 @@
from nbxmpp.modules.security_labels import Displaymarking
from gajim.common import app
from gajim.common import types
from gajim.common.const import AvatarSize
from gajim.common.const import Trust
from gajim.common.const import TRUST_SYMBOL_DATA
......@@ -42,6 +42,8 @@
from gajim.common.helpers import to_user_string
from gajim.common.i18n import _
from gajim.common.i18n import Q_
from gajim.common.modules.contacts import GroupchatContact
from gajim.common.types import ChatContactT
from .base import BaseRow
from .widgets import MoreMenuButton
......@@ -52,16 +54,27 @@
from ...util import format_fingerprint
from ...util import get_cursor
MERGE_TIMEFRAME = timedelta(seconds=120)
class MessageRow(BaseRow):
__gsignals__ = {
'mention': (
GObject.SignalFlags.RUN_LAST,
None,
(str,)
),
'quote': (
GObject.SignalFlags.RUN_LAST,
None,
(str,)
),
}
def __init__(self,
account: str,
contact: Union[types.BareContact,
types.GroupchatContact,
types.GroupchatParticipant],
contact: ChatContactT,
message_id: Optional[str],
stanza_id: Optional[str],
timestamp: float,
......@@ -83,7 +96,7 @@ def __init__(self,
self.stanza_id = stanza_id
self.log_line_id = log_line_id
self.kind = kind
self.name = name or ''
self.name = name
self.text = text
self.additional_data = additional_data
......@@ -216,6 +229,10 @@ def update_text_tags(self) -> None:
self._message_widget.update_text_tags()
def _check_for_highlight(self, text: str) -> None:
assert isinstance(self._contact, GroupchatContact)
if self._contact.nickname is None:
return
needs_highlight = message_needs_highlight(
text,
self._contact.nickname,
......@@ -224,12 +241,12 @@ def _check_for_highlight(self, text: str) -> None:
self.get_style_context().add_class(
'gajim-mention-highlight')
def _get_avatar(self, kind: str, name: str) -> Optional[cairo.Surface]:
def _get_avatar(self, kind: str, name: str) -> Optional[cairo.ImageSurface]:
if self._contact is None:
return None
scale = self.get_scale_factor()
if self._is_groupchat:
if isinstance(self._contact, GroupchatContact):
contact = self._contact.get_resource(name)
return contact.get_avatar(AvatarSize.ROSTER, scale, add_show=False)
......@@ -239,7 +256,9 @@ def _get_avatar(self, kind: str, name: str) -> Optional[cairo.Surface]:
else:
contact = self._contact
return contact.get_avatar(AvatarSize.ROSTER, scale, add_show=False)
avatar = contact.get_avatar(AvatarSize.ROSTER, scale, add_show=False)
assert not isinstance(avatar, GdkPixbuf.Pixbuf)
return avatar
def _on_avatar_clicked(self,
_widget: Gtk.Widget,
......@@ -247,21 +266,31 @@ def _on_avatar_clicked(self,
name: str
) -> int:
if event.type == Gdk.EventType.BUTTON_PRESS and event.button == 1:
self.get_parent().on_mention(name)
self.emit('mention', name)
return Gdk.EVENT_STOP
@staticmethod
def _on_realize(event_box: Gtk.EventBox) -> None:
event_box.get_window().set_cursor(get_cursor('pointer'))
window = event_box.get_window()
if window is not None:
window.set_cursor(get_cursor('pointer'))
def is_same_sender(self, message: MessageRow) -> bool:
return message.name == self.name
def is_same_encryption(self, message: MessageRow) -> bool:
message_details = self._get_encryption_details(message.additional_data)
own_details = self._get_encryption_details(self.additional_data)
m_add_data = message.additional_data
if m_add_data is None:
m_add_data = AdditionalDataDict()
s_add_data = self.additional_data
if s_add_data is None:
s_add_data = AdditionalDataDict()
message_details = self._get_encryption_details(m_add_data)
own_details = self._get_encryption_details(s_add_data)
if message_details is None and own_details is None:
return True
if message_details is not None and own_details is not None:
# *_details contains encryption method's name, fingerprint, trust
m_name, _, m_trust = message_details
......@@ -270,7 +299,7 @@ def is_same_encryption(self, message: MessageRow) -> bool:
return True
return False
def is_mergeable(self, message: BaseRow) -> bool:
def is_mergeable(self, message: MessageRow) -> bool:
if message.type != self.type:
return False
if not self.is_same_sender(message):
......@@ -286,7 +315,7 @@ def on_copy_message(self, _widget: Gtk.Widget) -> None:
clip.set_text(f'{timestamp} - {self.name}: {text}', -1)
def on_quote_message(self, _widget: Gtk.Widget) -> None:
self.get_parent().on_quote(self._message_widget.get_text())
self.emit('quote', self._message_widget.get_text())
def on_retract_message(self, _widget: Gtk.Widget) -> None:
def _on_retract(reason: str) -> None:
......@@ -324,7 +353,7 @@ def _get_encryption_image(self,
icon = 'channel-secure-symbolic'
color = 'encrypted-color'
else:
icon, trust_tooltip, color = TRUST_SYMBOL_DATA[trust]
icon, trust_tooltip, color = TRUST_SYMBOL_DATA[Trust(trust)]
tooltip = f'{tooltip}\n{trust_tooltip}'
if fingerprint is not None:
fingerprint = format_fingerprint(fingerprint)
......@@ -338,13 +367,15 @@ def _get_encryption_image(self,
@staticmethod
def _get_encryption_details(additional_data: AdditionalDataDict
) -> Optional[tuple[str, str, Trust]]:
) -> Optional[tuple[
str, Optional[str], Optional[Trust]]]:
name = additional_data.get_value('encrypted', 'name')
if name is None:
return None
fingerprint = additional_data.get_value('encrypted', 'fingerprint')
trust = additional_data.get_value('encrypted', 'trust')
trust_data = additional_data.get_value('encrypted', 'trust')
trust = Trust(trust_data)
return name, fingerprint, trust
@property
......
......@@ -19,14 +19,14 @@
from gi.repository import Gtk
from gajim.common import types
from gajim.common.i18n import _
from gajim.common.types import ChatContactT
from .base import BaseRow
class ReadMarkerRow(BaseRow):
def __init__(self, account: str, contact: types.BareContact) -> None:
def __init__(self, account: str, contact: ChatContactT) -> None:
BaseRow.__init__(self, account, widget='label')
self.set_activatable(False)
self.type = 'read_marker'
......@@ -46,7 +46,7 @@ def __init__(self, account: str, contact: types.BareContact) -> None:
self.set_no_show_all(True)
def _on_nickname_update(self,
contact: types.BareContact,
contact: ChatContactT,
_signal_name: str
) -> None:
text = _('%s has read up to this point') % contact.name
......
......@@ -14,8 +14,8 @@
from __future__ import annotations
import typing
from typing import Union
from typing import Any
from gi.repository import Gtk
from gi.repository import Pango
......@@ -30,10 +30,6 @@
from ...util import wrap_with_event_box
if typing.TYPE_CHECKING:
from .message import MessageRow
ContactT = Union[BareContact, GroupchatContact, GroupchatParticipant]
......@@ -49,7 +45,7 @@ def __init__(self) -> None:
@wrap_with_event_box
class MoreMenuButton(Gtk.Button):
def __init__(self,
row: MessageRow,
row: Any,
contact: ContactT,
name: str
) -> None:
......
......@@ -42,11 +42,12 @@
from gajim.common.helpers import to_user_string
from gajim.common.helpers import get_start_of_day
from gajim.common.jingle_session import JingleSession
from gajim.common.modules.contacts import GroupchatParticipant
from gajim.common.modules.contacts import GroupchatContact
from gajim.common.modules.contacts import BareContact
from gajim.common.modules.httpupload import HTTPFileTransfer
from gajim.common.storage.archive import ConversationRow
from gajim.common.modules.contacts import BareContact
from gajim.common.modules.contacts import GroupchatContact
from gajim.common.modules.contacts import GroupchatParticipant
from gajim.common.types import ChatContactT
from .rows.base import BaseRow
from .rows.read_marker import ReadMarkerRow
......@@ -62,12 +63,8 @@
from .rows.muc_join_left import MUCJoinLeft
from .rows.muc_user_status import MUCUserStatus
from ..util import scroll_to_end
log = logging.getLogger('gajim.gui.conversation_view')
ContactT = Union[BareContact, GroupchatContact, GroupchatParticipant]
class ConversationView(Gtk.ListBox):
......@@ -82,19 +79,24 @@ class ConversationView(Gtk.ListBox):
None,
(str, )
),
'accept-call': (
'scroll-to-end': (
GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
None,
()
),
'call-accepted': (
GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
None,
(object, )
),
'decline-call': (
'call-declined': (
GObject.SignalFlags.RUN_LAST | GObject.SignalFlags.ACTION,
None,
(object, )
),
}
def __init__(self, account: str, contact: ContactT) -> None:
def __init__(self, account: str, contact: ChatContactT) -> None:
Gtk.ListBox.__init__(self)
self.set_selection_mode(Gtk.SelectionMode.NONE)
self.set_sort_func(self._sort_func)
......@@ -152,7 +154,7 @@ def unlock(self) -> None:
def clear(self) -> None:
for row in self.get_children()[2:]:
if row.type == 'read_marker':
if isinstance(row, ReadMarkerRow):
continue
self.remove(row)
row.destroy()
......@@ -252,6 +254,8 @@ def add_call_message(self,
assert isinstance(self._contact, BareContact)
call_row = CallRow(
self._account, self._contact, event=event, db_message=db_message)
call_row.connect('call-accepted', self._on_call_accepted)
call_row.connect('call-declined', self._on_call_declined)
self._insert_message(call_row)
def add_command_output(self, text: str, is_error: bool) -> None:
......@@ -274,7 +278,7 @@ def add_message(self,
if not timestamp:
timestamp = time.time()
message = MessageRow(
message_row = MessageRow(
self._account,
self._contact,
message_id,
......@@ -289,19 +293,20 @@ def add_message(self,
error=error,
encryption_enabled=self.encryption_enabled,
log_line_id=log_line_id)
message_row.connect('mention', self._on_mention)
message_row.connect('quote', self._on_quote)
if message.type == 'chat':
if message_id is not None:
self._message_id_row_map[message_id] = message
if message_id is not None:
self._message_id_row_map[message_id] = message_row
if kind == 'incoming':
self._read_marker_row.set_last_incoming_timestamp(
message.timestamp)
if (marker is not None and marker == 'displayed'
and message_id is not None):
self.set_read_marker(message_id)
if kind == 'incoming':
self._read_marker_row.set_last_incoming_timestamp(
message_row.timestamp)
if (marker is not None and marker == 'displayed'