Commit 1d231b1f authored by Daniel Brötzmann's avatar Daniel Brötzmann Committed by Daniel Brötzmann
Browse files

New ConversationView

parent 7ecda265
Pipeline #7189 failed with stages
in 7 minutes and 22 seconds
......@@ -211,8 +211,6 @@ def __init__(self, parent_win, contact, acct, session, resource=None):
gui_menu_builder.get_encryption_menu(
self.control_id, self._type, self.account == 'Local'))
self.set_encryption_menu_icon()
# restore previous conversation
self.restore_conversation()
self.msg_textview.grab_focus()
# pylint: disable=line-too-long
......@@ -627,14 +625,14 @@ def _on_decrypted_message_received(self, event):
additional_data=event.additional_data)
if event.msg_log_id:
pw = self.parent_win
end = self.conv_textview.autoscroll
end = self.conversation_view.autoscroll
if not pw or (pw.get_active_control() and self \
== pw.get_active_control() and pw.is_active() and end):
app.storage.archive.set_read_messages([event.msg_log_id])
@event_filter(['account', 'jid'])
def _on_message_error(self, event):
self.conv_textview.show_error(event.message_id, event.error)
self.conversation_view.show_error(event.message_id, event.error)
@event_filter(['account', 'jid'])
def _on_message_sent(self, event):
......@@ -663,11 +661,11 @@ def _on_message_sent(self, event):
@event_filter(['account', 'jid'])
def _receipt_received(self, event):
self.conv_textview.show_receipt(event.receipt_id)
self.conversation_view.show_receipt(event.receipt_id)
@event_filter(['account', 'jid'])
def _displayed_received(self, event):
self.conv_textview.show_displayed(event.marker_id)
self.conversation_view.set_read_marker(event.marker_id)
@event_filter(['account', 'jid'])
def _on_zeroconf_error(self, event):
......@@ -1320,66 +1318,6 @@ def _on_drag_data_received(self, widget, context, x, y, selection,
[self.contact.jid],
[dropped_jid])
def restore_conversation(self):
jid = self.contact.jid
# don't restore lines if it's a transport
if app.jid_is_transport(jid):
return
# number of messages that are in queue and are already logged, we want
# to avoid duplication
pending = len(app.events.get_events(self.account, jid, ['chat', 'pm']))
if self.resource:
pending += len(app.events.get_events(self.account,
self.contact.get_full_jid(),
['chat', 'pm']))
rows = app.storage.archive.get_last_conversation_lines(
self.account, jid, pending)
local_old_kind = None
self.conv_textview.just_cleared = True
for row in rows: # time, kind, message, subject, additional_data
msg = row.message
additional_data = row.additional_data
if not msg: # message is empty, we don't print it
continue
if row.kind in (KindConstant.CHAT_MSG_SENT,
KindConstant.SINGLE_MSG_SENT):
kind = 'outgoing'
name = self.get_our_nick()
elif row.kind in (KindConstant.SINGLE_MSG_RECV,
KindConstant.CHAT_MSG_RECV):
kind = 'incoming'
name = self.contact.get_shown_name()
elif row.kind == KindConstant.ERROR:
kind = 'status'
name = self.contact.get_shown_name()
tim = float(row.time)
if row.subject:
msg = _('Subject: %(subject)s\n%(message)s') % \
{'subject': row.subject, 'message': msg}
ChatControlBase.add_message(self,
msg,
kind,
name,
tim,
restored=True,
old_kind=local_old_kind,
additional_data=additional_data,
message_id=row.message_id,
marker=row.marker,
error=row.error)
if (row.message.startswith('/me ') or
row.message.startswith('/me\n')):
local_old_kind = None
else:
local_old_kind = kind
if rows:
self.conv_textview.print_empty_line()
def read_queue(self):
"""
Read queue and print messages contained in it
......
......@@ -25,6 +25,7 @@
import os
import sys
import datetime
import time
import uuid
import tempfile
......@@ -45,12 +46,12 @@
from gajim.common.helpers import event_filter
from gajim.common.contacts import GC_Contact
from gajim.common.const import Chatstate
from gajim.common.const import KindConstant
from gajim.common.structs import OutgoingMessage
from gajim import gtkgui_helpers
from gajim.conversation_textview import ConversationTextview
from gajim.gui.conversation_view import ConversationView
from gajim.gui.dialogs import DialogButton
from gajim.gui.dialogs import ConfirmationDialog
from gajim.gui.dialogs import PastePreviewDialog
......@@ -97,7 +98,7 @@
################################################################################
class ChatControlBase(ChatCommandProcessor, CommandTools, EventHelper):
"""
A base class containing a banner, ConversationTextview, MessageInputTextView
A base class containing a banner, ConversationView, MessageInputTextView
"""
_type = None # type: ControlType
......@@ -169,19 +170,29 @@ def __init__(self, parent_win, widget_name, contact, acct,
Gdk.DragAction.COPY | Gdk.DragAction.MOVE)
self.xml.overlay.drag_dest_set_target_list(dst_targets)
# Create textviews and connect signals
self.conv_textview = ConversationTextview(self.account)
id_ = self.conv_textview.connect('quote', self.on_quote)
self.handlers[id_] = self.conv_textview
self.conv_textview.tv.connect('key-press-event',
self._on_conv_textview_key_press_event)
self.xml.conversation_scrolledwindow.add(self.conv_textview.tv)
widget = self.xml.conversation_scrolledwindow.get_vadjustment()
widget.connect('changed', self.on_conversation_vadjustment_changed)
# Create ConversationView and connect signals
self.conversation_view = ConversationView(self.account, self.contact)
id_ = self.conversation_view.connect('quote', self.on_quote)
self.handlers[id_] = self.conversation_view
id_ = self.conversation_view.connect(
'load-history', self._on_load_history)
self.handlers[id_] = self.conversation_view
id_ = self.conversation_view.connect(
'key-press-event', self._on_conversation_view_key_press)
self.handlers[id_] = self.conversation_view
self.xml.conversation_scrolledwindow.add(self.conversation_view)
self._scrolled_old_upper = None
self._scrolled_old_value = None
self._scrolled_previous_value = 0
self._fetching_history = False
self._initial_fetch_done = False
vadjustment = self.xml.conversation_scrolledwindow.get_vadjustment()
vadjustment.connect(
'changed', self._on_conversation_vadjustment_changed)
vadjustment.connect('value-changed', self._on_scroll_value_changed)
vscrollbar = self.xml.conversation_scrolledwindow.get_vscrollbar()
vscrollbar.connect('button-release-event',
self._on_scrollbar_button_release)
......@@ -228,7 +239,6 @@ def __init__(self, parent_win, widget_name, contact, acct,
# Attach speller
self.set_speller()
self.conv_textview.tv.show()
# For XEP-0172
self.user_nick = None
......@@ -247,7 +257,7 @@ def __init__(self, parent_win, widget_name, contact, acct,
self.handlers[id_] = parent_win.window
self.encryption = self.get_encryption_state()
self.conv_textview.encryption_enabled = self.encryption is not None
self.conversation_view.encryption_enabled = self.encryption is not None
# PluginSystem: adding GUI extension point for ChatControlBase
# instance object (also subclasses, eg. ChatControl or GroupchatControl)
......@@ -268,7 +278,7 @@ def __init__(self, parent_win, widget_name, contact, acct,
# to properly use the super, because of the old code.
CommandTools.__init__(self)
def _on_conv_textview_key_press_event(self, textview, event):
def _on_conversation_view_key_press(self, _listbox, event):
if event.get_state() & Gdk.ModifierType.SHIFT_MASK:
if event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
return Gdk.EVENT_PROPAGATE
......@@ -280,12 +290,12 @@ def _on_conv_textview_key_press_event(self, textview, event):
# focused).
return Gdk.EVENT_PROPAGATE
if event.get_state() & COPY_MODIFIER:
# Don’t reroute the event if it is META + c and the
# textview has a selection
if event.hardware_keycode in KEYCODES_KEY_C:
if textview.get_buffer().props.has_selection:
return Gdk.EVENT_PROPAGATE
# if event.get_state() & COPY_MODIFIER:
# # Don’t reroute the event if it is META + c and the
# # textview has a selection
# if event.hardware_keycode in KEYCODES_KEY_C:
# if textview.get_buffer().props.has_selection:
# return Gdk.EVENT_PROPAGATE
if not self.msg_textview.get_sensitive():
# If the input textview is not sensitive it can’t get the focus.
......@@ -509,7 +519,7 @@ def delegate_action(self, action):
return Gdk.EVENT_STOP
if action == 'clear-chat':
self.conv_textview.clear()
self.conversation_view.clear()
return Gdk.EVENT_STOP
if action == 'delete-line':
......@@ -609,7 +619,7 @@ def _on_authentication_button_clicked(self, _button):
def set_encryption_state(self, encryption):
self.encryption = encryption
self.conv_textview.encryption_enabled = encryption is not None
self.conversation_view.encryption_enabled = encryption is not None
self.contact.settings.set('encryption', self.encryption or '')
def get_encryption_state(self):
......@@ -694,8 +704,7 @@ def shutdown(self):
self.handlers[i].disconnect(i)
self.handlers.clear()
self.conv_textview.del_handlers()
del self.conv_textview
del self.conversation_view
del self.msg_textview
del self.msg_scrolledwindow
......@@ -849,7 +858,7 @@ def _on_message_textview_key_press_event(self, textview, event):
return True
if event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
self.conv_textview.tv.event(event)
self.conversation_view.event(event)
self._on_scroll(None, event.keyval)
return True
......@@ -1095,11 +1104,11 @@ def save_message(self, message, msg_type):
self.received_history_pos = pos
def add_info_message(self, text, message_id=None):
self.conv_textview.print_conversation_line(
self.conversation_view.add_message(
text, 'info', '', None, message_id=message_id, graphics=False)
def add_status_message(self, text):
self.conv_textview.print_conversation_line(
self.conversation_view.add_message(
text, 'status', '', None)
def add_message(self,
......@@ -1127,9 +1136,8 @@ def add_message(self,
"""
jid = self.contact.jid
full_jid = self.get_full_jid()
textview = self.conv_textview
end = False
if self.conv_textview.autoscroll or kind == 'outgoing':
if self.conversation_view.autoscroll or kind == 'outgoing':
end = True
if other_tags_for_name is None:
......@@ -1141,21 +1149,18 @@ def add_message(self,
if additional_data is None:
additional_data = AdditionalDataDict()
textview.print_conversation_line(text,
kind,
name,
tim,
other_tags_for_name,
other_tags_for_time,
other_tags_for_text,
subject,
old_kind,
displaymarking=displaymarking,
message_id=message_id,
correct_id=correct_id,
additional_data=additional_data,
marker=marker,
error=error)
self.conversation_view.add_message(
text,
kind,
name,
tim,
other_text_tags=other_tags_for_text,
display_marking=displaymarking,
message_id=message_id,
correct_id=correct_id,
additional_data=additional_data,
marker=marker,
error=error)
if restored:
return
......@@ -1302,7 +1307,7 @@ def _style_changed(self, *args):
self.update_tags()
def update_tags(self):
self.conv_textview.update_tags()
self.conversation_view.update_text_tags()
@staticmethod
def clear(tv):
......@@ -1374,7 +1379,7 @@ def set_control_active(self, state):
if state:
self.set_emoticon_popover()
jid = self.contact.jid
if self.conv_textview.autoscroll:
if self.conversation_view.autoscroll:
# we are at the end
type_ = [f'printed_{self._type}']
if self._type.is_groupchat:
......@@ -1403,14 +1408,73 @@ def set_control_active(self, state):
Chatstate.INACTIVE)
def scroll_to_end(self, force=False):
self.conv_textview.scroll_to_end(force)
self.conversation_view.scroll_to_end(force)
def _on_load_history(self, _button, lines):
self.fetch_n_lines_history(lines)
def fetch_n_lines_history(self, n_lines):
if self._fetching_history:
return
timestamp_end = self.conversation_view.first_message_timestamp
if not timestamp_end:
timestamp_end = datetime.datetime.now()
self._fetching_history = True
adjustment = self.xml.conversation_scrolledwindow.get_vadjustment()
# Store these values so we can restore scroll position later
self._scrolled_old_upper = adjustment.get_upper()
self._scrolled_old_value = adjustment.get_value()
if self.is_groupchat:
messages = app.storage.archive.get_conversation_muc_before(
self.account,
self.contact.jid,
timestamp_end,
n_lines)
else:
messages = app.storage.archive.get_conversation_before(
self.account,
self.contact.jid,
timestamp_end,
n_lines)
for msg in messages:
if not msg:
continue
kind = 'status'
contact_name = msg.contact_name
if msg.kind in (
KindConstant.SINGLE_MSG_RECV, KindConstant.CHAT_MSG_RECV):
kind = 'incoming'
contact_name = self.contact.get_shown_name()
elif msg.kind == KindConstant.GC_MSG:
kind = 'incoming'
elif msg.kind in (
KindConstant.SINGLE_MSG_SENT, KindConstant.CHAT_MSG_SENT):
kind = 'outgoing'
contact_name = self.get_our_nick()
if not msg.message:
continue
self.conversation_view.add_message(
msg.message,
kind,
contact_name,
msg.time,
subject=msg.subject,
additional_data=msg.additional_data,
message_id=msg.message_id,
marker=msg.marker,
error=msg.error,
history=True)
def _on_edge_reached(self, _scrolledwindow, pos):
if pos != Gtk.PositionType.BOTTOM:
return
# Remove all events and set autoscroll True
app.log('autoscroll').info('Autoscroll enabled')
self.conv_textview.autoscroll = True
self.conversation_view.autoscroll = True
if self.resource:
jid = self.contact.get_full_jid()
else:
......@@ -1440,13 +1504,44 @@ def _on_edge_reached(self, _scrolledwindow, pos):
self._type)
self.last_msg_id = None
def _on_edge_overshot(self, _scrolledwindow, pos):
# Fetch messages if we scroll against the top
if pos != Gtk.PositionType.TOP:
return
self.fetch_n_lines_history(30)
def _on_scroll_value_changed(self, adjustment):
if self.conversation_view.clearing or self._fetching_history:
return
value = adjustment.get_value()
previous_value = self._scrolled_previous_value
self._scrolled_previous_value = value
if value >= previous_value:
return
# Fetch messages before we reach the top
if value <= int(0.1 * adjustment.get_upper()):
self.fetch_n_lines_history(10)
def _on_scroll_realize(self, _widget):
# Initial message fetching when ChatControl is first opened
if not self._initial_fetch_done:
self._initial_fetch_done = True
restore_lines = app.settings.get('restore_lines')
if restore_lines <= 0:
return
self.fetch_n_lines_history(restore_lines)
def _on_scrollbar_button_release(self, scrollbar, event):
if event.get_button()[1] != 1:
# We want only to catch the left mouse button
return
if not at_the_end(scrollbar.get_parent()):
app.log('autoscroll').info('Autoscroll disabled')
self.conv_textview.autoscroll = False
self.conversation_view.autoscroll = False
def has_focus(self):
if self.parent_win:
......@@ -1456,12 +1551,12 @@ def has_focus(self):
return False
def _on_scroll(self, widget, event):
if not self.conv_textview.autoscroll:
if not self.conversation_view.autoscroll:
# autoscroll is already disabled
return
if widget is None:
# call from _conv_textview_key_press_event()
# call from _on_conversation_view_key_press()
# SHIFT + Gdk.KEY_Page_Up
if event != Gdk.KEY_Page_Up:
return
......@@ -1493,10 +1588,18 @@ def _on_scroll(self, widget, event):
adjustment = self.xml.conversation_scrolledwindow.get_vadjustment()
if adjustment.get_upper() != adjustment.get_page_size():
app.log('autoscroll').info('Autoscroll disabled')
self.conv_textview.autoscroll = False
self.conversation_view.autoscroll = False
def on_conversation_vadjustment_changed(self, _adjustment):
def _on_conversation_vadjustment_changed(self, adjustment):
self.scroll_to_end()
if self._fetching_history:
# Make sure the scroll position is kept
new_upper = adjustment.get_upper()
diff = new_upper - self._scrolled_old_upper
new_value = diff + self._scrolled_old_value
adjustment.set_value(new_value)
self._fetching_history = False
return
def redraw_after_event_removed(self, jid):
"""
......@@ -1582,7 +1685,7 @@ def got_connected(self):
def got_disconnected(self):
self.msg_textview.set_sensitive(False)
self.msg_textview.set_editable(False)
self.conv_textview.tv.grab_focus()
self.conversation_view.grab_focus()
self.update_toolbar()
......
......@@ -33,7 +33,7 @@
from traceback import print_exc
from gi.repository import Pango
# from gi.repository import Pango
from gajim.common import app
from gajim.common.i18n import _
......@@ -117,29 +117,33 @@ def __init__(self):
self.install_tags()
def install_tags(self):
buffer_ = self.conv_textview.tv.get_buffer()
# TODO implement this in ConversationView
# buffer_ = self.conv_textview.tv.get_buffer()
name = "Monospace"
font = Pango.FontDescription(name)
# name = "Monospace"
# font = Pango.FontDescription(name)
command_ok_tag = buffer_.create_tag("command_ok")
command_ok_tag.set_property("font-desc", font)
command_ok_tag.set_property("foreground", "#3465A4")
# command_ok_tag = buffer_.create_tag("command_ok")
# command_ok_tag.set_property("font-desc", font)
# command_ok_tag.set_property("foreground", "#3465A4")
command_error_tag = buffer_.create_tag("command_error")
command_error_tag.set_property("font-desc", font)
command_error_tag.set_property("foreground", "#F57900")
# command_error_tag = buffer_.create_tag("command_error")
# command_error_tag.set_property("font-desc", font)
# command_error_tag.set_property("foreground", "#F57900")
return
def shift_line(self):
buffer_ = self.conv_textview.tv.get_buffer()
iter_ = buffer_.get_end_iter()
if iter_.ends_line() and not iter_.is_start():
buffer_.insert_with_tags_by_name(iter_, "\n", "eol")
# buffer_ = self.conv_textview.tv.get_buffer()
# iter_ = buffer_.get_end_iter()
# if iter_.ends_line() and not iter_.is_start():
# buffer_.insert_with_tags_by_name(iter_, "\n", "eol")
return
def append_with_tags(self, text, *tags):
buffer_ = self.conv_textview.tv.get_buffer()
iter_ = buffer_.get_end_iter()
buffer_.insert_with_tags_by_name(iter_, text, *tags)
# buffer_ = self.conv_textview.tv.get_buffer()
# iter_ = buffer_.get_end_iter()
# buffer_.insert_with_tags_by_name(iter_, text, *tags)
return
def echo(self, text, tag="command_ok"):
"""
......
......@@ -1047,6 +1047,7 @@ def is_active(self):
'iris.xpcs:',
'iris.lwz:',
'ldap://',
'mailto:',
'mid:',
'modem:',
'msrp://',
......@@ -1079,6 +1080,7 @@ def is_active(self):
'vemmi://',
'xmlrpc.beep://',
'xmlrpc.beeps://',
'xmpp:',
'z39.50r://',
'z39.50s://',
'about:',
......@@ -1105,6 +1107,22 @@ def is_active(self):
}
TRUST_SYMBOL_DATA = {
Trust.UNTRUSTED: ('dialog-error-symbolic',
_('Untrusted'),
'error-color'),
Trust.UNDECIDED: ('security-low-symbolic',
_('Trust Not Decided'),
'warning-color'),
Trust.BLIND: ('security-medium-symbolic',
_('Unverified'),
'encrypted-color'),
Trust.VERIFIED: ('security-high-symbolic',
_('Verified'),
'encrypted-color')
}
THRESHOLD_OPTIONS = {
-1: _('No Sync'),
1: _('1 Day'),
......
......@@ -399,72 +399,72 @@ def get_unread_msgs(self):
return all_messages
@timeit
def load_groupchat_messages(self, account, jid):
account_id = self.get_account_id(account, type_=JIDConstant.ROOM_TYPE)
def get_conversation_before(self, account, jid, end_timestamp, n_lines):
"""
Load n_lines lines of conversation with jid before end_timestamp
sql = '''
SELECT time, contact_name, message, additional_data, message_id
FROM logs NATURAL JOIN jids WHERE jid = ?
AND account_id = ? AND kind = ?
ORDER BY time DESC, log_line_id DESC LIMIT ?'''