Philipp Hörist pushed to branch master at gajim / gajim
Commits:
3f38ccc3 by Philipp Hörist at 2017-03-11T22:11:00+01:00
Simplify import and move to the top
- - - - -
1e06db9f by Philipp Hörist at 2017-03-11T22:11:01+01:00
Remove smooth scrolling
GTK3+ does smooth scrolling in ScrolledWindows on its own
- - - - -
8547d2b4 by Philipp Hörist at 2017-03-11T22:11:02+01:00
Simplify scrolling in conversation textview
- - - - -
55561b3d by Philipp Hörist at 2017-03-11T22:18:37+01:00
Merge branch 'scrolling' into 'master'
Improve Scrolling
See merge request !65
- - - - -
5 changed files:
- src/chat_control.py
- src/chat_control_base.py
- src/common/config.py
- src/conversation_textview.py
- src/gui_interface.py
Changes:
=====================================
src/chat_control.py
=====================================
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -39,6 +39,7 @@ import gui_menu_builder
import message_control
import dialogs
+from common import logger
from common import gajim
from common import helpers
from common import exceptions
@@ -1626,10 +1627,9 @@ class ChatControl(ChatControlBase):
rows = gajim.logger.get_last_conversation_lines(jid, restore_how_many,
pending_how_many, timeout, self.account)
except exceptions.DatabaseMalformed:
- import common.logger
dialogs.ErrorDialog(_('Database Error'),
_('The database file (%s) cannot be read. Try to repair it or '
- 'remove it (all history will be lost).') % common.logger.LOG_DB_PATH)
+ 'remove it (all history will be lost).') % logger.LOG_DB_PATH)
rows = []
local_old_kind = None
self.conv_textview.just_cleared = True
=====================================
src/chat_control_base.py
=====================================
--- a/src/chat_control_base.py
+++ b/src/chat_control_base.py
@@ -323,7 +323,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
id_ = widget.connect('changed',
self.on_conversation_vadjustment_changed)
self.handlers[id_] = widget
- self.scroll_to_end_id = None
self.was_at_the_end = True
self.correcting = False
self.last_sent_msg = None
@@ -984,27 +983,8 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# There were events to remove
self.redraw_after_event_removed(jid)
- def bring_scroll_to_end(self, textview, diff_y=0):
- """
- Scroll to the end of textview if end is not visible
- """
- if self.scroll_to_end_id:
- # a scroll is already planned
- return
- buffer_ = textview.get_buffer()
- end_iter = buffer_.get_end_iter()
- end_rect = textview.get_iter_location(end_iter)
- visible_rect = textview.get_visible_rect()
- # scroll only if expected end is not visible
- if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
- self.scroll_to_end_id = GLib.idle_add(self.scroll_to_end_iter,
- textview)
-
- def scroll_to_end_iter(self, textview):
- buffer_ = textview.get_buffer()
- end_iter = buffer_.get_end_iter()
- textview.scroll_to_iter(end_iter, 0, False, 1, 1)
- self.scroll_to_end_id = None
+ def scroll_to_end_iter(self):
+ self.conv_textview.scroll_to_end_iter()
return False
def on_configure_event(self, msg_textview, event):
@@ -1065,18 +1045,11 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
# used to stay at the end of the textview when we shrink conversation
# textview.
if self.was_at_the_end:
- if self.conv_textview.at_the_end():
- # we are at the end
- self.conv_textview.bring_scroll_to_end(-18)
- else:
- self.conv_textview.bring_scroll_to_end(-18, use_smooth=False)
+ 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):
- # stop automatic scroll when we manually scroll
- if not self.conv_textview.auto_scrolling:
- self.conv_textview.stop_scrolling()
self.was_at_the_end = (adjustment.get_upper() - adjustment.get_value() \
- adjustment.get_page_size()) < 18
if self.resource:
=====================================
src/common/config.py
=====================================
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -279,7 +279,6 @@ class Config:
'hide_groupchat_occupants_list': [opt_bool, False, _('Hides the group chat occupants list in group chat window.')],
'chat_merge_consecutive_nickname': [opt_bool, False, _('In a chat, show the nickname at the beginning of a line only when it\'s not the same person talking than in previous message.')],
'chat_merge_consecutive_nickname_indent': [opt_str, ' ', _('Indentation when using merge consecutive nickname.')],
- 'use_smooth_scrolling': [opt_bool, True, _('Smooth scroll message in conversation window')],
'gc_nicknames_colors': [ opt_str, '#4e9a06:#f57900:#ce5c00:#3465a4:#204a87:#75507b:#5c3566:#c17d11:#8f5902:#ef2929:#cc0000:#a40000', _('List of colors, separated by ":", that will be used to color nicknames in group chats.'), True ],
'ctrl_tab_go_to_next_composing': [opt_bool, True, _('Ctrl-Tab go to next composing tab when none is unread.')],
'confirm_metacontacts': [ opt_str, '', _('Should we show the confirm metacontacts creation dialog or not? Empty string means we never show the dialog.')],
=====================================
src/conversation_textview.py
=====================================
--- a/src/conversation_textview.py
+++ b/src/conversation_textview.py
@@ -216,9 +216,6 @@ class ConversationTextview(GObject.GObject):
# last_received_message_id[name] = (msg_stanza_id, line_start_mark)
self.last_received_message_id = {}
- # It's True when we scroll in the code, so we can detect scroll from user
- self.auto_scrolling = False
-
# connect signals
id_ = self.tv.connect('populate_popup', self.on_textview_populate_popup)
self.handlers[id_] = self.tv
@@ -325,7 +322,6 @@ class ConversationTextview(GObject.GObject):
# holds a mark at the end of --- line
self.focus_out_end_mark = None
- self.smooth_id = None
self.just_cleared = False
def query_tooltip(self, widget, x_pos, y_pos, keyboard_mode, tooltip):
@@ -398,87 +394,7 @@ class ConversationTextview(GObject.GObject):
self.tv.tagSthAtSth.set_property('foreground', color)
def at_the_end(self):
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- end_rect = self.tv.get_iter_location(end_iter)
- visible_rect = self.tv.get_visible_rect()
- if end_rect.y <= (visible_rect.y + visible_rect.height):
- return True
- return False
-
- # Smooth scrolling inspired by Pidgin code
- def smooth_scroll(self):
- parent = self.tv.get_parent()
- if not parent:
- return False
- vadj = parent.get_vadjustment()
- max_val = vadj.get_upper() - vadj.get_page_size() + 1
- cur_val = vadj.get_value()
- # scroll by 1/3rd of remaining distance
- onethird = cur_val + ((max_val - cur_val) / 3.0)
- self.auto_scrolling = True
- vadj.set_value(onethird)
- self.auto_scrolling = False
- if max_val - onethird < 0.01:
- self.smooth_id = None
- self.smooth_scroll_timer.cancel()
- return False
- return True
-
- def smooth_scroll_timeout(self):
- GLib.idle_add(self.do_smooth_scroll_timeout)
- return
-
- def do_smooth_scroll_timeout(self):
- if not self.smooth_id:
- # we finished scrolling
- return
- GLib.source_remove(self.smooth_id)
- self.smooth_id = None
- parent = self.tv.get_parent()
- if parent:
- vadj = parent.get_vadjustment()
- self.auto_scrolling = True
- vadj.set_value(vadj.get_upper() - vadj.get_page_size() + 1)
- self.auto_scrolling = False
-
- def smooth_scroll_to_end(self):
- if self.smooth_id is not None: # already scrolling
- return False
- self.smooth_id = GLib.timeout_add(self.SCROLL_DELAY,
- self.smooth_scroll)
- self.smooth_scroll_timer = Timer(self.MAX_SCROLL_TIME,
- self.smooth_scroll_timeout)
- self.smooth_scroll_timer.start()
- return False
-
- def scroll_to_end(self):
- parent = self.tv.get_parent()
- buffer_ = self.tv.get_buffer()
- end_mark = buffer_.get_mark('end')
- if not end_mark:
- return False
- self.auto_scrolling = True
- self.tv.scroll_to_mark(end_mark, 0, True, 0, 1)
- adjustment = parent.get_hadjustment()
- adjustment.set_value(0)
- self.auto_scrolling = False
- return False # when called in an idle_add, just do it once
-
- def bring_scroll_to_end(self, diff_y=0, use_smooth=None):
- ''' scrolls to the end of textview if end is not visible '''
- if use_smooth is None:
- use_smooth = gajim.config.get('use_smooth_scrolling')
- buffer_ = self.tv.get_buffer()
- end_iter = buffer_.get_end_iter()
- end_rect = self.tv.get_iter_location(end_iter)
- visible_rect = self.tv.get_visible_rect()
- # scroll only if expected end is not visible
- if end_rect.y >= (visible_rect.y + visible_rect.height + diff_y):
- if use_smooth:
- GLib.idle_add(self.smooth_scroll_to_end)
- else:
- GLib.idle_add(self.scroll_to_end_iter)
+ return gtkgui_helpers.at_the_end(self.tv.get_parent())
def scroll_to_end_iter(self):
buffer_ = self.tv.get_buffer()
@@ -488,12 +404,6 @@ class ConversationTextview(GObject.GObject):
self.tv.scroll_to_iter(end_iter, 0, False, 1, 1)
return False # when called in an idle_add, just do it once
- def stop_scrolling(self):
- if self.smooth_id:
- GLib.source_remove(self.smooth_id)
- self.smooth_id = None
- self.smooth_scroll_timer.cancel()
-
def correct_message(self, correct_id, kind, name):
allowed = True
if kind == 'incoming':
@@ -649,7 +559,7 @@ class ConversationTextview(GObject.GObject):
if scroll:
# scroll to the end (via idle in case the scrollbar has
# appeared)
- GLib.idle_add(self.scroll_to_end)
+ GLib.idle_add(self.scroll_to_end_iter)
def on_textview_draw(self, widget, ctx):
return
@@ -1283,8 +1193,6 @@ class ConversationTextview(GObject.GObject):
# even if we insert directly at the mark iter
temp_mark = buffer_.create_mark('temp', iter_, left_gravity=True)
- at_the_end = self.at_the_end()
-
if text.startswith('/me '):
direction_mark = i18n.paragraph_direction_mark(str(text[3:]))
else:
@@ -1369,13 +1277,9 @@ class ConversationTextview(GObject.GObject):
self.last_sent_message_id = (msg_stanza_id, new_mark)
if not insert_mark:
- if at_the_end or kind == 'outgoing':
+ if self.at_the_end() or kind == 'outgoing':
# we are at the end or we are sending something
- # scroll to the end (via idle in case the scrollbar has appeared)
- if gajim.config.get('use_smooth_scrolling'):
- GLib.idle_add(self.smooth_scroll_to_end)
- else:
- GLib.idle_add(self.scroll_to_end)
+ GLib.idle_add(self.scroll_to_end_iter)
self.just_cleared = False
buffer_.end_user_action()
=====================================
src/gui_interface.py
=====================================
--- a/src/gui_interface.py
+++ b/src/gui_interface.py
@@ -1831,7 +1831,7 @@ class Interface:
# Using isinstance here because we want to catch all derived types
if isinstance(ctrl, ChatControlBase):
tv = ctrl.conv_textview
- tv.scroll_to_end()
+ tv.scroll_to_end_iter()
################################################################################
### Methods dealing with emoticons
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/b7cce4e7282a3da7016d52baeef2b11de…
Philipp Hörist pushed to branch master at gajim / gajim
Commits:
432ec7ff by Markus Böhme at 2017-03-07T16:46:05+01:00
Fix usage of logger constants in command system
The definition of various constants was refactored to use enumerations
in commit 2fbadc91e9dc. One reference in the command system was not
changed to use the new enum, however.
- - - - -
f1ee561b by Markus Böhme at 2017-03-07T16:46:12+01:00
Fix broken command system by importing CommandContainer modules
ChatControlBase was split from ChatControl into its own module in commit
80221afc2ccb. With this split the import of the CommandContainer modules
was removed as well, likely because their members are not referenced
anywhere. However, importing these modules even when they are not used
directly is crucial because the contained CommandContainers are only
registered with the command system when their definitions are evaluated.
Import the modules with CommandContainers in chat_control_base.py to fix
the command system. Also add a comment stating more clearly why the
imports need to be kept around.
- - - - -
ceb7772b by Markus Böhme at 2017-03-07T16:46:12+01:00
Correct some typos in command system comments
- - - - -
01893054 by Markus Böhme at 2017-03-07T16:48:32+01:00
Fix grep command in chat window
Commit d75ebd95e5e3 modified the return value of logger.py's method
get_search_results_for_query by appending another component to the
tuple. Because of this unpacking the tuple in the command system's grep
command failed.
- - - - -
205f5f30 by Philipp Hörist at 2017-03-07T17:19:11+01:00
Merge branch 'command-system-regressions' into 'master'
Fix various regressions in the command system
See merge request !63
- - - - -
4 changed files:
- src/chat_control_base.py
- src/command_system/framework.py
- src/command_system/implementation/middleware.py
- src/command_system/implementation/standard.py
Changes:
=====================================
src/chat_control_base.py
=====================================
--- a/src/chat_control_base.py
+++ b/src/chat_control_base.py
@@ -54,6 +54,13 @@ from common.connection_handlers_events import MessageOutgoingEvent
from command_system.implementation.middleware import ChatCommandProcessor
from command_system.implementation.middleware import CommandTools
+# The members of these modules are not referenced directly anywhere in this
+# module, but still they need to be kept around. Importing them automatically
+# registers the contained CommandContainers with the command system, thereby
+# populating the list of available commands.
+import command_system.implementation.standard
+import command_system.implementation.execute
+
try:
import gtkspell
HAS_GTK_SPELL = True
=====================================
src/command_system/framework.py
=====================================
--- a/src/command_system/framework.py
+++ b/src/command_system/framework.py
@@ -15,7 +15,7 @@
"""
Provides a tiny framework with simple, yet powerful and extensible
-architecture to implement commands in a streight and flexible,
+architecture to implement commands in a straight and flexible,
declarative way.
"""
@@ -67,7 +67,7 @@ class CommandProcessor(object):
"""
# This defines a command prefix (or an initializer), which should
- # preceede a a text in order it to be processed as a command.
+ # precede a text in order for it to be processed as a command.
COMMAND_PREFIX = '/'
def process_as_command(self, text):
=====================================
src/command_system/implementation/middleware.py
=====================================
--- a/src/command_system/implementation/middleware.py
+++ b/src/command_system/implementation/middleware.py
@@ -75,7 +75,7 @@ class ChatCommandProcessor(CommandProcessor):
self.command_succeeded = True
def looks_like_command(self, text, body, name, arguments):
- # Command escape stuff ggoes here. If text was prepended by the
+ # Command escape stuff goes here. If text was prepended by the
# command prefix twice, like //not_a_command (if prefix is set
# to /) then it will be escaped, that is sent just as a regular
# message with one (only one) prefix removed, so message will be
=====================================
src/command_system/implementation/standard.py
=====================================
--- a/src/command_system/implementation/standard.py
+++ b/src/command_system/implementation/standard.py
@@ -24,7 +24,7 @@ import dialogs
from common import gajim
from common import helpers
from common.exceptions import GajimGeneralException
-from common.logger import Constants
+from common.logger import KindConstant
from ..errors import CommandError
from ..framework import CommandContainer, command, doc
@@ -32,10 +32,6 @@ from ..mapping import generate_usage
from .hosts import ChatCommands, PrivateChatCommands, GroupChatCommands
-# This holds constants fron the logger, which we'll be using in some of our
-# commands.
-lc = Constants()
-
class StandardCommonCommands(CommandContainer):
"""
This command container contains standard commands which are common
@@ -104,10 +100,10 @@ class StandardCommonCommands(CommandContainer):
raise CommandError(_("Limit must be an integer"))
for row in results:
- contact, time, kind, show, message, subject = row
+ contact, time, kind, show, message, subject, log_line_id = row
if not contact:
- if kind == lc.KIND_CHAT_MSG_SENT:
+ if kind == KindConstant.CHAT_MSG_SENT:
contact = gajim.nicks[self.account]
else:
contact = self.contact.name
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/c02260c4d18fece5541878e6635d7e4e0…
Philipp Hörist pushed to branch master at gajim / gajim
Commits:
2e026335 by Markus Böhme at 2017-02-22T12:56:31+01:00
Simplify appending new line in history window
ConversationTextview provides an abstraction for adding text with tags.
Use it instead of directly accessing its backing text buffer.
- - - - -
d9a22515 by Markus Böhme at 2017-02-22T12:56:31+01:00
Highlight all paragraphs of a search result in the history window
A single message may span multiple paragraphs. Highlight all paragraphs
of a search result because a keyword could be in any of them.
Every message already has the 'eol' tag on its newline character.
Highlight from the start of a search match up to the next 'eol' tag.
- - - - -
65c6a1e7 by Markus Böhme at 2017-02-22T12:56:31+01:00
Remove unnecessary code in message highlighting in the history window
The message to be highlighted is searched via an already formatted
timestamp, and the match contains the whole timestamp with any
preceding characters (like "["). Therefore, calling backward_char on
the start iterator of the match just goes back to the previous line
and is not needed.
- - - - -
66589886 by Markus Böhme at 2017-02-22T14:55:44+01:00
Remove dead code in logger.py method get_search_results_for_query
The method get_search_results_for_query provides a means to search via a
passed in SQL query. This feature has been unused and dead since its initial
commit back in 2005 (commit e542fff69f56), so just remove it.
- - - - -
d75ebd95 by Markus Böhme at 2017-03-02T23:55:18+01:00
Rework message highlighting in the history window to not use timestamps
Search results in the history window to are scrolled to and highlighted
once the user clicks on one of them. Messages are searched by text in
the log database, but then looked up in the history text buffer via a
visible or invisible timestamp obtained from the database record.
Timestamps in the history text buffer, depending on user configuration,
may only have a coarse granularity. Philipp Hörist discovered that this
may lead to the highlighting of the wrong line in the history text view
and proposed an alternative solution that is implemented in this patch.
Timestamps are abolished as a means of searching messages. Instead, add
a mark with the unique message id (log line id) at the start of every
message in the history text buffer when the conversation is loaded.
After fetching the search results from the database this id can be used
to unambiguously find the corresponding message in the history text
buffer.
- - - - -
deebe384 by Markus Böhme at 2017-03-03T17:29:05+01:00
Prevent unnecessary reloading of conversations in the history window
Do not reload the conversation in the history window when the user
selects another search result from the same day. Just clearing the
previous highlighting is enough. This prevents flickering and provides
a much more pleasant user experience.
- - - - -
1b975172 by Philipp Hörist at 2017-03-03T23:45:06+01:00
Merge branch 'history-highlighting' into 'master'
Highlight all paragraphs of a message in the history window
See merge request !46
- - - - -
3 changed files:
- src/common/logger.py
- src/history_window.py
- src/htmltextview.py
Changes:
=====================================
src/common/logger.py
=====================================
--- a/src/common/logger.py
+++ b/src/common/logger.py
@@ -628,7 +628,7 @@ class Logger:
def get_conversation_for_date(self, jid, year, month, day, account):
"""
- Return contact_name, time, kind, show, message, subject
+ Return contact_name, time, kind, show, message, subject, additional_data, log_line_id
For each row in a list of tupples, returns list with empty tupple if we
found nothing to meet our demands
@@ -645,7 +645,9 @@ class Logger:
last_second_of_day = start_of_day + seconds_in_a_day - 1
self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject, additional_data FROM logs
+ SELECT contact_name, time, kind, show, message, subject,
+ additional_data, log_line_id
+ FROM logs
WHERE (%s)
AND time BETWEEN %d AND %d
ORDER BY time
@@ -660,7 +662,7 @@ class Logger:
def get_search_results_for_query(self, jid, query, account, year=False,
month=False, day=False):
"""
- Returns contact_name, time, kind, show, message
+ Returns contact_name, time, kind, show, message, subject, log_line_id
For each row in a list of tupples, returns list with empty tupple if we
found nothing to meet our demands
@@ -671,33 +673,25 @@ class Logger:
# Error trying to create a new jid_id. This means there is no log
return []
- if False: # query.startswith('SELECT '): # it's SQL query (FIXME)
- try:
- self.cur.execute(query)
- except sqlite.OperationalError as e:
- results = [('', '', '', '', str(e))]
- return results
-
- else: # user just typed something, we search in message column
- where_sql, jid_tuple = self._build_contact_where(account, jid)
- like_sql = '%' + query.replace("'", "''") + '%'
- if year:
- start_of_day = self.get_unix_time_from_date(year, month, day)
- seconds_in_a_day = 86400 # 60 * 60 * 24
- last_second_of_day = start_of_day + seconds_in_a_day - 1
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject FROM logs
- WHERE (%s) AND message LIKE '%s'
- AND time BETWEEN %d AND %d
- ORDER BY time
- ''' % (where_sql, like_sql, start_of_day, last_second_of_day),
- jid_tuple)
- else:
- self.cur.execute('''
- SELECT contact_name, time, kind, show, message, subject FROM logs
- WHERE (%s) AND message LIKE '%s'
- ORDER BY time
- ''' % (where_sql, like_sql), jid_tuple)
+ where_sql, jid_tuple = self._build_contact_where(account, jid)
+ like_sql = '%' + query.replace("'", "''") + '%'
+ if year:
+ start_of_day = self.get_unix_time_from_date(year, month, day)
+ seconds_in_a_day = 86400 # 60 * 60 * 24
+ last_second_of_day = start_of_day + seconds_in_a_day - 1
+ self.cur.execute('''
+ SELECT contact_name, time, kind, show, message, subject, log_line_id FROM logs
+ WHERE (%s) AND message LIKE '%s'
+ AND time BETWEEN %d AND %d
+ ORDER BY time
+ ''' % (where_sql, like_sql, start_of_day, last_second_of_day),
+ jid_tuple)
+ else:
+ self.cur.execute('''
+ SELECT contact_name, time, kind, show, message, subject, log_line_id FROM logs
+ WHERE (%s) AND message LIKE '%s'
+ ORDER BY time
+ ''' % (where_sql, like_sql), jid_tuple)
results = self.cur.fetchall()
return results
=====================================
src/history_window.py
=====================================
--- a/src/history_window.py
+++ b/src/history_window.py
@@ -56,6 +56,7 @@ class Column(IntEnum):
UNIXTIME = 2
MESSAGE = 3
TIME = 4
+ LOG_LINE_ID = 5
class HistoryWindow:
"""
@@ -85,8 +86,8 @@ class HistoryWindow:
self.results_window = xml.get_object('results_scrolledwindow')
self.search_in_date = xml.get_object('search_in_date')
- # contact_name, date, message, time
- model = Gtk.ListStore(str, str, str, str, str)
+ # jid, contact_name, date, message, time, log_line_id
+ model = Gtk.ListStore(str, str, str, str, str, int)
self.results_treeview.set_model(model)
col = Gtk.TreeViewColumn(_('Name'))
self.results_treeview.append_column(col)
@@ -393,28 +394,31 @@ class HistoryWindow:
show_status = self.show_status_checkbutton.get_active()
lines = gajim.logger.get_conversation_for_date(self.jid, year, month, day, self.account)
- # lines holds list with tupples that have:
- # contact_name, time, kind, show, message
for line in lines:
# line[0] is contact_name, line[1] is time of message
# line[2] is kind, line[3] is show, line[4] is message, line[5] is subject
- # line[6] is additional_data
+ # line[6] is additional_data, line[7] is log_line_id
if not show_status and line[2] in (KindConstant.GCSTATUS,
KindConstant.STATUS):
continue
self._add_new_line(line[0], line[1], line[2], line[3], line[4],
- line[5], line[6])
+ line[5], line[6], line[7])
- def _add_new_line(self, contact_name, tim, kind, show, message, subject, additional_data):
+ def _add_new_line(self, contact_name, tim, kind, show, message, subject,
+ additional_data, log_line_id):
"""
Add a new line in textbuffer
"""
if not message and kind not in (KindConstant.STATUS,
KindConstant.GCSTATUS):
return
+
buf = self.history_buffer
end_iter = buf.get_end_iter()
+ # Make the beginning of every message searchable by its log_line_id
+ buf.create_mark(str(log_line_id), end_iter, left_gravity=True)
+
if gajim.config.get('print_time') == 'always':
timestamp_str = gajim.config.get('time_stamp')
timestamp_str = helpers.from_one_line(timestamp_str)
@@ -429,12 +433,6 @@ class HistoryWindow:
tim = time.strftime('%X ', time.localtime(float(tim)))
buf.insert_with_tags_by_name(end_iter, tim + '\n',
'time_sometimes')
- else: # don't print time. So we print it as invisible to be able to
- # search for it
- timestamp_str = gajim.config.get('time_stamp')
- timestamp_str = helpers.from_one_line(timestamp_str)
- tim = time.strftime(timestamp_str, time.localtime(float(tim)))
- buf.insert_with_tags_by_name(end_iter, tim, 'invisible')
tag_name = ''
tag_msg = ''
@@ -512,9 +510,7 @@ class HistoryWindow:
else:
self.history_textview.print_real_text(message, name=contact_name,
xhtml=xhtml, additional_data=additional_data)
- buffer_ = self.history_textview.tv.get_buffer()
- eob = buffer_.get_end_iter()
- buffer_.insert_with_tags_by_name(eob, '\n', 'eol')
+ self.history_textview.print_real_text('\n', text_tags=['eol'])
def on_search_entry_activate(self, widget):
text = self.search_entry.get_text()
@@ -563,12 +559,13 @@ class HistoryWindow:
contact_name = self.completion_dict[jid][InfoColumn.NAME]
tim = row[1]
message = row[4]
+ log_line_id = row[6]
local_time = time.localtime(tim)
date = time.strftime('%Y-%m-%d', local_time)
# jid (to which log is assigned to), name, date, message,
# time (full unix time)
- model.append((jid, contact_name, date, message, str(tim)))
+ model.append((jid, contact_name, date, message, str(tim), log_line_id))
def on_results_treeview_row_activated(self, widget, path, column):
"""
@@ -576,7 +573,7 @@ class HistoryWindow:
which results to showing conversation logs for that date
"""
# get currently selected date
- cur_year, cur_month = self.calendar.get_date()[0:2]
+ cur_year, cur_month, cur_day = self.calendar.get_date()
cur_month = gtkgui_helpers.make_gtk_month_python_month(cur_month)
model = widget.get_model()
# make it a tupple (Y, M, D, 0, 0, 0...)
@@ -595,33 +592,41 @@ class HistoryWindow:
if year != cur_year or gtk_month != cur_month:
self.calendar.select_month(month, year)
- self.calendar.select_day(day)
- unix_time = model[path][Column.TIME]
- self._scroll_to_result(unix_time)
- #FIXME: one day do not search just for unix_time but the whole and user
- # specific format of the textbuffer line [time] nick: message
- # and highlight all that
+ if year != cur_year or gtk_month != cur_month or day != cur_day:
+ self.calendar.select_day(day)
+
+ self._scroll_to_message_and_highlight(model[path][Column.LOG_LINE_ID])
- def _scroll_to_result(self, unix_time):
+ def _scroll_to_message_and_highlight(self, log_line_id):
"""
- Scroll to the result using unix_time and highlight line
+ Scroll to a message and highlight it
"""
- start_iter = self.history_buffer.get_start_iter()
- local_time = time.localtime(float(unix_time))
- timestamp_str = gajim.config.get('time_stamp')
- timestamp_str = helpers.from_one_line(timestamp_str)
- tim = time.strftime(timestamp_str, local_time)
- result = start_iter.forward_search(tim, Gtk.TextSearchFlags.TEXT_ONLY,
- None)
- if result is not None:
- match_start_iter, match_end_iter = result
- match_start_iter.backward_char() # include '[' or other character before time
- match_end_iter.forward_line() # highlight all message not just time
- self.history_buffer.apply_tag_by_name('highlight', match_start_iter,
- match_end_iter)
- mark = self.history_buffer.create_mark('match', match_start_iter, True)
- GLib.idle_add(self.history_textview.tv.scroll_to_mark, mark,
- 0, True, 0.0, 0.5)
+
+ def iterator_has_mark(iterator, mark_name):
+ for mark in iterator.get_marks():
+ if mark.get_name() == mark_name:
+ return True
+ return False
+
+ # Clear previous search result by removing the highlighting. The scroll
+ # mark is automatically removed when the new one is set.
+ start = self.history_buffer.get_start_iter()
+ end = self.history_buffer.get_end_iter()
+ self.history_buffer.remove_tag_by_name('highlight', start, end)
+
+ log_line_id = str(log_line_id)
+ line = start
+ while not iterator_has_mark(line, log_line_id):
+ if not line.forward_line():
+ return
+
+ match_start = line
+ match_end = match_start.copy()
+ match_end.forward_to_tag_toggle(self.history_buffer.eol_tag)
+
+ self.history_buffer.apply_tag_by_name('highlight', match_start, match_end)
+ mark = self.history_buffer.create_mark('match', match_start, True)
+ GLib.idle_add(self.history_textview.tv.scroll_to_mark, mark, 0, True, 0.0, 0.5)
def on_log_history_checkbutton_toggled(self, widget):
# log conversation history?
=====================================
src/htmltextview.py
=====================================
--- a/src/htmltextview.py
+++ b/src/htmltextview.py
@@ -850,7 +850,7 @@ class HtmlTextView(Gtk.TextView):
self.connect('copy-clipboard', self.on_html_text_view_copy_clipboard)
self.id_ = self.connect('button-release-event',
self.on_left_mouse_button_release)
- self.get_buffer().create_tag('eol')
+ self.get_buffer().eol_tag = self.get_buffer().create_tag('eol')
self.tooltip = tooltips.BaseTooltip()
self.config = gajim.config
self.interface = gajim.interface
View it on GitLab: https://dev.gajim.org/gajim/gajim/compare/8157cb38eca26ed48aa1a157c148eb115…