Commit ee5ba0df authored by Martin's avatar Martin

Merge tag 'upstream/1.0.1' into debian/master

parents 79b22257 f482b903
Gajim 1.0.1 (1 April 2018)
* Improve MAM support
* Image preview in file chooser dialog
* Groupchat: Set minimize on auto join default True
* Delete bookmark when we destroy a room
* Fix account deletion
* Fix custom font handling
* Fix OpenPGP message decryption
* Fix window position restore on multi-head setups
* Fix scrolling in message window
* Improve Windows build and build for 64 bits
Gajim 1.0.0 (17 March 2018)
* Ported to GTK3 / Python3
......@@ -33,6 +46,7 @@ Gajim 1.0.0 (17 March 2018)
- Meta Contacts: Filtering the roster could lead to a crash in some circumstances. Use CTRL + N for starting new chats as a workaround
- Audio/Video support is currently not maintained and most likely not working
- Windows: Translation is not working currently
Gajim 0.16.9 (30 November 2017)
......
......@@ -3,10 +3,10 @@
### Runtime Requirements
- python3.4 or higher
- python3.5 or higher
- python3-gi
- python3-gi-cairo
- gir1.2-gtk-3.0
- gir1.2-gtk-3.0 (>=3.22)
- python3-nbxmpp
- python3-openssl (>=0.14)
- python3-pyasn1
......
environment:
matrix:
- MSYSTEM: MINGW64
MSYS_ARCH: "x86_64"
ARCH: "64bit"
- MSYSTEM: MINGW32
MSYS_ARCH: "i686"
ARCH: "32bit"
branches:
only:
......@@ -22,10 +28,10 @@ build_script:
C:\msys64\usr\bin\sh.exe --login -c $command
}
bash 'git clone C:/projects/gajim C:/msys64/home/appveyor/gajim'
bash 'C:/msys64/home/appveyor/gajim/win/build.sh'
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim.exe" -FileName "Gajim-1.0.0-$($env:TIME_STRING).exe"
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim-Portable.exe" -FileName "Gajim-Portable-1.0.0-$($env:TIME_STRING).exe"
bash "git clone C:/projects/gajim C:/msys64/home/appveyor/gajim"
bash "C:/msys64/home/appveyor/gajim/win/build.sh $($env:MSYS_ARCH)"
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim.exe" -FileName "Gajim-1.0.1-$($env:ARCH)-$($env:TIME_STRING).exe"
Push-AppveyorArtifact "$($env:BUILDROOT)/Gajim-Portable.exe" -FileName "Gajim-Portable-1.0.1-$($env:ARCH)-$($env:TIME_STRING).exe"
# on_finish:
# - ps: $blockRdp = $true; iex ((new-object net.webclient).DownloadString('https://raw.githubusercontent.com/appveyor/ci/master/scripts/enable-rdp.ps1'))
......
import subprocess
__version__ = "1.0.0"
__version__ = "1.0.1"
try:
p = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
......
......@@ -1593,7 +1593,7 @@ class ChatControl(ChatControlBase):
markup += ' (%s)' % file_props.desc
markup += '\n%s: %s' % (_('Size'), helpers.convert_bytes(
file_props.size))
b1 = Gtk.Button(_('_Accept'))
b1 = Gtk.Button(_('Accept'))
b1.connect('clicked', self._on_accept_file_request, file_props)
b2 = Gtk.Button(stock=Gtk.STOCK_CANCEL)
b2.connect('clicked', self._on_cancel_file_request, file_props)
......
......@@ -305,13 +305,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
'conversation_scrolledwindow')
self.conv_scrolledwindow.add(self.conv_textview.tv)
widget = self.conv_scrolledwindow.get_vadjustment()
id_ = widget.connect('value-changed',
self.on_conversation_vadjustment_value_changed)
self.handlers[id_] = widget
id_ = widget.connect('changed',
self.on_conversation_vadjustment_changed)
self.handlers[id_] = widget
self.was_at_the_end = True
vscrollbar = self.conv_scrolledwindow.get_vscrollbar()
id_ = vscrollbar.connect('button-release-event',
self._on_scrollbar_button_release)
self.handlers[id_] = vscrollbar
self.correcting = False
self.last_sent_msg = None
......@@ -600,9 +602,12 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
valid, entries = self.keymap.get_entries_for_keyval(event.keyval)
keycode = entries[0].keycode
if (event.get_state() & Gdk.ModifierType.CONTROL_MASK and keycode in (
self.keycode_c, self.keycode_ins)) or (
event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up)):
self.keycode_c, self.keycode_ins)):
return False
if event.get_state() & Gdk.ModifierType.SHIFT_MASK and \
event.keyval in (Gdk.KEY_Page_Down, Gdk.KEY_Page_Up):
self._on_scroll(None, event.keyval)
return False
self.parent_win.notebook.event(event)
return True
......@@ -965,7 +970,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
full_jid = self.get_full_jid()
textview = self.conv_textview
end = False
if self.was_at_the_end or kind == 'outgoing':
if self.conv_textview.autoscroll or kind == 'outgoing':
end = True
if other_tags_for_name is None:
......@@ -1201,7 +1206,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if state:
self.set_emoticon_popover()
jid = self.contact.jid
if self.was_at_the_end:
if self.conv_textview.autoscroll:
# we are at the end
type_ = ['printed_' + self.type_id]
if self.type_id == message_control.TYPE_GC:
......@@ -1221,21 +1226,15 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
else:
self.send_chatstate('inactive', self.contact)
def scroll_to_end_iter(self):
self.conv_textview.scroll_to_end_iter()
return False
def scroll_to_end(self, force=False):
self.conv_textview.scroll_to_end(force)
def on_conversation_vadjustment_changed(self, adjustment):
# used to stay at the end of the textview when we shrink conversation
# textview.
if self.was_at_the_end:
self.scroll_to_end_iter()
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value()\
- adjustment.get_page_size()) < 18
def on_conversation_vadjustment_value_changed(self, adjustment):
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
- adjustment.get_page_size()) < 18
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
if self.resource:
jid = self.contact.get_full_jid()
else:
......@@ -1252,8 +1251,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
return
if not self.parent_win:
return
if self.conv_textview.at_the_end() and \
self.parent_win.get_active_control() == self and \
if self.parent_win.get_active_control() == self and \
self.parent_win.window.is_active():
# we are at the end
if self.type_id == message_control.TYPE_GC:
......@@ -1264,6 +1262,57 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# There were events to remove
self.redraw_after_event_removed(jid)
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 gtkgui_helpers.at_the_end(scrollbar.get_parent()):
app.log('autoscroll').info('Autoscroll disabled')
self.conv_textview.autoscroll = False
def _on_scroll(self, widget, event):
if not self.conv_textview.autoscroll:
# autoscroll is already disabled
return
if widget is None:
# call from _conv_textview_key_press_event()
# SHIFT + Gdk.KEY_Page_Up
if event != Gdk.KEY_Page_Up:
return
else:
# On scrolliung UP disable autoscroll
# get_scroll_direction() sets has_direction only TRUE
# if smooth scrolling is deactivated. If we have smooth
# smooth scrolling we have to use get_scroll_deltas()
has_direction, direction = event.get_scroll_direction()
if not has_direction:
direction = None
smooth, delta_x, delta_y = event.get_scroll_deltas()
if smooth:
if delta_y < 0:
direction = Gdk.ScrollDirection.UP
elif delta_y > 0:
direction = Gdk.ScrollDirection.DOWN
elif delta_x < 0:
direction = Gdk.ScrollDirection.LEFT
elif delta_x > 0:
direction = Gdk.ScrollDirection.RIGHT
else:
app.log('autoscroll').warning(
'Scroll directions cant be determined')
if direction != Gdk.ScrollDirection.UP:
return
# Check if we have a Scrollbar
adjustment = self.conv_scrolledwindow.get_vadjustment()
if adjustment.get_upper() != adjustment.get_page_size():
app.log('autoscroll').info('Autoscroll disabled')
self.conv_textview.autoscroll = False
def on_conversation_vadjustment_changed(self, adjustment):
self.scroll_to_end()
def redraw_after_event_removed(self, jid):
"""
We just removed a 'printed_*' event, redraw contact in roster or
......@@ -1344,7 +1393,7 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
"""
# make the last message visible, when changing to "full view"
if not state:
GLib.idle_add(self.conv_textview.scroll_to_end_iter)
self.scroll_to_end()
widget.set_no_show_all(state)
if state:
......
......@@ -142,7 +142,7 @@ class CommandProcessor(object):
def list_commands(self):
commands = list_commands(self.COMMAND_HOST)
commands = dict(commands)
return sorted(list(commands.values()), key=lambda k: k.__repr__())
return sorted(set(commands.values()), key=lambda k: k.__repr__())
class Command(object):
......
......@@ -305,6 +305,7 @@ class Config:
'use_keyring': [opt_bool, True, _('If True, Gajim will use the Systems Keyring to store account passwords.')],
'pgp_encoding': [ opt_str, '', _('Sets the encoding used by python-gnupg'), True],
'remote_commands': [opt_bool, False, _('If True, Gajim will execute XEP-0146 Commands.')],
'mam_blacklist': [opt_str, '', _('All non-compliant MAM Groupchats')],
}, {})
__options_per_key = {
......
......@@ -71,6 +71,9 @@ from gajim.gtkgui_helpers import get_action
if app.HAVE_PYOPENSSL:
import OpenSSL.crypto
if os.name == 'nt':
import certifi
from nbxmpp import Smacks
from string import Template
import logging
......@@ -692,6 +695,7 @@ class Connection(CommonConnection, ConnectionHandlers):
self.privacy_rules_requested = False
self.streamError = ''
self.secret_hmac = str(random.random())[2:].encode('utf-8')
self.removing_account = False
self.sm = Smacks(self) # Stream Management
......@@ -866,6 +870,8 @@ class Connection(CommonConnection, ConnectionHandlers):
def _connection_lost(self):
log.debug('_connection_lost')
self.disconnect(on_purpose = False)
if self.removing_account:
return
app.nec.push_incoming_event(ConnectionLostEvent(None, conn=self,
title=_('Connection with account "%s" has been lost') % self.name,
msg=_('Reconnect manually.')))
......@@ -892,8 +898,9 @@ class Connection(CommonConnection, ConnectionHandlers):
if self.new_account_form:
def _on_register_result(result):
if not nbxmpp.isResultNode(result):
reason = result.getErrorMsg() or result.getError()
app.nec.push_incoming_event(AccountNotCreatedEvent(
None, conn=self, reason=result.getError()))
None, conn=self, reason=reason))
return
if app.HAVE_GPG:
self.USE_GPG = True
......@@ -1210,21 +1217,16 @@ class Connection(CommonConnection, ConnectionHandlers):
self._current_type = self._current_host['type']
port = self._current_host['port']
cacerts = os.path.join(common.app.DATA_DIR, 'other', 'cacerts.pem')
if not os.path.exists(cacerts):
cacerts = ''
cacerts = ''
if os.name == 'nt':
cacerts = certifi.where()
mycerts = common.app.MY_CACERTS
tls_version = app.config.get_per('accounts', self.name,
'tls_version')
cipher_list = app.config.get_per('accounts', self.name,
'cipher_list')
if version_condition(nbxmpp.__version__, '0.6.3'):
secure_tuple = (self._current_type, cacerts, mycerts, tls_version,
cipher_list, self._current_host['alpn'])
else:
secure_tuple = (self._current_type, cacerts, mycerts, tls_version,
cipher_list)
tls_version = app.config.get_per('accounts', self.name, 'tls_version')
cipher_list = app.config.get_per('accounts', self.name, 'cipher_list')
secure_tuple = (self._current_type, cacerts, mycerts, tls_version,
cipher_list, self._current_host['alpn'])
con = nbxmpp.NonBlockingClient(
domain=self._hostname,
......@@ -2729,6 +2731,13 @@ class Connection(CommonConnection, ConnectionHandlers):
if jid:
destroy.setAttr('jid', jid)
self.connection.send(iq)
i = 0
for bm in self.bookmarks:
if bm['jid'] == room_jid:
del self.bookmarks[i]
break
i += 1
self.store_bookmarks()
def send_gc_status(self, nick, jid, show, status, auto=False):
if not app.account_is_connected(self.name):
......@@ -2891,6 +2900,7 @@ class Connection(CommonConnection, ConnectionHandlers):
# on_remove_success as a class property as pass it as an argument
def _on_unregister_account_connect(con):
self.on_connect_auth = None
self.removing_account = True
if app.account_is_connected(self.name):
hostname = app.config.get_per('accounts', self.name, 'hostname')
iq = nbxmpp.Iq(typ='set', to=hostname)
......@@ -2909,6 +2919,7 @@ class Connection(CommonConnection, ConnectionHandlers):
con.SendAndWaitForResponse(iq)
return
on_remove_success(False)
self.removing_account = False
if self.connected == 0:
self.on_connect_auth = _on_unregister_account_connect
self.connect_and_auth()
......
......@@ -511,6 +511,7 @@ class ConnectionVcard:
'not-allowed'):
app.log('avatar').info('vCard not available: %s %s',
frm_jid, stanza_error)
callback(jid, resource, room, {})
return
vcard_node = stanza.getTag('vCard', namespace=nbxmpp.NS_VCARD)
......@@ -850,10 +851,9 @@ class ConnectionHandlersBase:
obj.contact.contact_name = obj.contact_nickname
obj.need_redraw = True
if obj.old_show == obj.new_show and obj.contact.status == \
obj.status and obj.contact.priority == obj.prio and \
obj.contact.idle_time == obj.idle_time: # no change
return True
elif obj.old_show != obj.new_show or obj.contact.status != \
obj.status:
obj.need_redraw = True
else:
obj.contact = app.contacts.get_first_contact_from_jid(account,
jid)
......@@ -1066,9 +1066,17 @@ class ConnectionHandlersBase:
conn=self, msg_obj=obj, stanza_id=obj.unique_id))
return True
def _check_for_mam_compliance(self, room_jid, stanza_id):
namespace = muc_caps_cache.get_mam_namespace(room_jid)
if stanza_id is None and namespace == nbxmpp.NS_MAM_2:
helpers.add_to_mam_blacklist(room_jid)
def _nec_gc_message_received(self, obj):
if obj.conn.name != self.name:
return
self._check_for_mam_compliance(obj.jid, obj.unique_id)
if (app.config.should_log(obj.conn.name, obj.jid) and
obj.msgtxt and obj.nick):
# if not obj.nick, it means message comes from room itself
......
......@@ -1080,12 +1080,14 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
if delay is None:
log.error('Received MAM message without timestamp')
log.error(self.stanza)
return
self.timestamp = helpers.parse_datetime(
delay, check_utc=True, epoch=True)
if self.timestamp is None:
log.error('Received MAM message with invalid timestamp: %s', delay)
log.error(self.stanza)
return
# Save timestamp added by the user
......@@ -1097,6 +1099,7 @@ class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
if self.user_timestamp is None:
log.warning('Received MAM message with '
'invalid user timestamp: %s', user_delay)
log.warning(self.stanza)
log.debug('Received mam-message: unique id: %s', self.unique_id)
return True
......@@ -1175,12 +1178,14 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
if delay is None:
log.error('Received MAM message without timestamp')
log.error(self.stanza)
return
self.timestamp = helpers.parse_datetime(
delay, check_utc=True, epoch=True)
if self.timestamp is None:
log.error('Received MAM message with invalid timestamp: %s', delay)
log.error(self.stanza)
return
# Save timestamp added by the user
......@@ -1192,6 +1197,7 @@ class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
if self.user_timestamp is None:
log.warning('Received MAM message with '
'invalid user timestamp: %s', user_delay)
log.warning(self.stanza)
log.debug('Received mam-gc-message: unique id: %s', self.unique_id)
return True
......@@ -1227,6 +1233,7 @@ class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
# run into an endless loop
if hasattr(self, 'disco'):
log.error('JID not known even after sucessful disco')
log.error(self.with_.getStripped())
return
# we don't know this JID, we need to disco it.
server = self.with_.getDomain()
......
......@@ -73,7 +73,7 @@ if app.HAVE_GPG:
result = super(GnuPG, self).decrypt(data.encode('utf8'),
passphrase=self.passphrase)
return str(result)
return result.data.decode('utf8')
def sign(self, str_, keyID):
result = super(GnuPG, self).sign(str_.encode('utf8'), keyid=keyID, detach=True,
......
......@@ -431,10 +431,8 @@ def get_uf_sub(sub):
uf_sub = _('From')
elif sub == 'both':
uf_sub = _('Both')
elif sub is None:
uf_sub = _('Unknown')
else:
uf_sub = sub
uf_sub = _('Unknown')
return uf_sub
......@@ -1626,3 +1624,21 @@ def get_emoticon_theme_path(theme):
emoticons_user_path = os.path.join(app.MY_EMOTS_PATH, theme)
if os.path.exists(emoticons_user_path):
return emoticons_user_path
def add_to_mam_blacklist(jid):
config_value = app.config.get('mam_blacklist')
if not config_value:
config_value = [jid]
else:
if jid in config_value:
return
config_value = config_value.split(',')
config_value.append(jid)
log.warning('Found not-compliant MUC. %s added to MAM Blacklist', jid)
app.config.set('mam_blacklist', ','.join(config_value))
def get_mam_blacklist():
config_value = app.config.get('mam_blacklist')
if not config_value:
return []
return config_value.split(',')
......@@ -525,7 +525,7 @@ class Logger:
''' % msg_log_id
)
results = self.cur.fetchone()
if len(results) == 0:
if results is None:
# Log line is no more in logs table. remove it from unread_messages
self.set_read_messages([msg_log_id])
continue
......
......@@ -25,6 +25,7 @@ import nbxmpp
from gajim.common import app
from gajim.common import ged
from gajim.common import helpers
from gajim.common.logger import KindConstant, JIDConstant
from gajim.common.const import ArchiveState
from gajim.common.caps_cache import muc_caps_cache
......@@ -202,10 +203,12 @@ class ConnectionArchive313:
return
namespace = self.archiving_namespace
blacklisted = False
if obj.groupchat:
namespace = muc_caps_cache.get_mam_namespace(obj.room_jid)
blacklisted = obj.room_jid in helpers.get_mam_blacklist()
if namespace != nbxmpp.NS_MAM_2:
if namespace != nbxmpp.NS_MAM_2 or blacklisted:
# Fallback duplicate search without stanza-id
duplicate = app.logger.search_for_duplicate(
self.name, obj.with_, obj.timestamp, obj.msgtxt)
......
......@@ -14,9 +14,12 @@
## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
import gi
import logging
gi.require_version('Gst', '1.0')
from gi.repository import Gst
log = logging.getLogger('gajim.c.multimedia_helpers')
class DeviceManager(object):
def __init__(self):
......@@ -33,12 +36,16 @@ class DeviceManager(object):
def detect_element(self, name, text, pipe='%s'):
if Gst.ElementFactory.find(name):
element = Gst.ElementFactory.make(name, '%spresencetest' % name)
if element is None:
log.warning('could not create %spresencetest', name)
if hasattr(element.props, 'device'):
element.set_state(Gst.State.READY)
devices = element.get_properties('device')
if devices:
self.devices[text % _('Default device')] = pipe % name
for device in devices:
if device is None:
continue
element.set_state(Gst.State.NULL)
element.set_property('device', device)
element.set_state(Gst.State.READY)
......@@ -49,7 +56,7 @@ class DeviceManager(object):
else:
self.devices[text] = pipe % name
else:
print('element \'%s\' not found' % name)
log.info('element %s not found', name)
class AudioInputManager(DeviceManager):
......@@ -108,4 +115,3 @@ class VideoOutputManager(DeviceManager):
# ximagesink
self.detect_element('ximagesink', _('X Window System (without Xv)'))
self.detect_element('autovideosink', _('Autodetect'))
......@@ -48,6 +48,8 @@ from gajim import dialogs
from gajim import cell_renderer_image
from gajim import message_control
from gajim.chat_control_base import ChatControlBase
from gajim.gajim_themes_window import GajimThemesWindow
from gajim.advanced_configuration_window import AdvancedConfigurationWindow
from gajim import dataforms_widget
from gajim import gui_menu_builder
......@@ -696,7 +698,7 @@ class PreferencesWindow:
def on_manage_theme_button_clicked(self, widget):
if self.theme_preferences is None:
self.theme_preferences = dialogs.GajimThemesWindow()
self.theme_preferences = GajimThemesWindow()
else:
self.theme_preferences.window.present()
self.theme_preferences.select_active_theme()
......@@ -1188,7 +1190,7 @@ class PreferencesWindow:
app.interface.instances['advanced_config'].window.present()
else:
app.interface.instances['advanced_config'] = \
dialogs.AdvancedConfigurationWindow()
AdvancedConfigurationWindow()
#---------- ManageProxiesWindow class -------------#
class ManageProxiesWindow:
......
......@@ -210,7 +210,7 @@ class ConversationTextview(GObject.GObject):
self.last_sent_message_id = None
# last_received_message_id[name] = (msg_stanza_id, line_start_mark)
self.last_received_message_id = {}
self.autoscroll = True
# connect signals
id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup)
self.handlers[id_] = self.tv
......@@ -374,16 +374,9 @@ class ConversationTextview(GObject.GObject):
self.tv.tagXMPP.set_property('foreground', color)
self.tv.tagSthAtSth.set_property('foreground', color)
def at_the_end(self):
return gtkgui_helpers.at_the_end(self.tv.get_parent())
def scroll_to_end_iter(self):
buffer_ = self.tv.get_buffer()
end_iter = buffer_.get_end_iter()
if not end_iter:
return False
self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
return False # when called in an idle_add, just do it once
def scroll_to_end(self, force=False):
if self.autoscroll or force:
gtkgui_helpers.scroll_to_end(self.tv.get_parent())
def correct_message(self, correct_id, kind, name):
allowed = True
......@@ -453,7 +446,7 @@ class ConversationTextview(GObject.GObject):
buffer_.end_user_action()
del self.xep0184_marks[id_]
def show_focus_out_line(self, scroll=True):
def show_focus_out_line(self):
if not self.allow_focus_out_line:
# if room did not receive focus-in from the last time we added
# --- line then do not readd
......@@ -509,11 +502,7 @@ class ConversationTextview(GObject.GObject):
buffer_.get_end_iter(), left_gravity=True)
buffer_.end_user_action()
if scroll:
# scroll to the end (via idle in case the scrollbar has
# appeared)
GLib.idle_add(self.scroll_to_end_iter)
self.scroll_to_end()
def clear(self, tv = None):
"""
......@@ -1233,9 +1222,9 @@ class ConversationTextview(GObject.GObject):
self.last_sent_message_id = (msg_stanza_id, new_mark)
if not insert_mark:
if self.at_the_end() or kind == 'outgoing':
if self.autoscroll or kind == 'outgoing':
# we are at the end or we are sending something
GLib.idle_add(self.scroll_to_end_iter)
self.scroll_to_end(force=True)
self.just_cleared = False
buffer_.end_user_action()
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.2 -->
<!-- Generated with glade 3.21.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkAdjustment" id="adjustment1">
......@@ -594,6 +594,8 @@
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="overlay_scrolling">False</property>
<signal name="edge-reached" handler="_on_edge_reached" swapped="no"/>
<signal name="scroll-event" handler="_on_scroll" swapped="no"/>
<child>
<placeholder/>
</child>
......
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.20.2 -->
<!-- Generated with glade 3.21.0 -->
<interface>
<requires lib="gtk+" version="3.20"/>
<object class="GtkMenu" id="formattings_menu">
......@@ -95,7 +95,8 @@
<property name="can_focus">True</property>
<property name="position">495</property>
<property name="position_set">True</property>
<signal name="notify" handler="on_hpaned_notify" swapped="no"/>
<signal name="button-release-event" handler="_on_hpaned_release_button" swapped="no"/>
<signal name="notify::position" handler="_on_hpaned_handle_change" swapped="no"/>
<child>
<object class="GtkBox" id="gc_textviews_vbox">
<property name="width_request">0</property>
......@@ -187,6 +188,8 @@
<property name="can_focus">True</property>
<property name="shadow_type">in</property>
<property name="overlay_scrolling">False</property>
<signal name="edge-reached" handler="_on_edge_reached" swapped="no"/>