search in history by ctrl + F - initial commit

This commit is contained in:
ingvar1995 2017-02-12 17:58:23 +03:00
parent 1d33d298c3
commit 8b56184510
5 changed files with 185 additions and 23 deletions

View file

@ -30,7 +30,8 @@ class Contact(basecontact.BaseContact):
self._unsaved_messages = 0
self._history_loaded = self._new_actions = False
self._receipts = 0
self._curr_text = ''
self._curr_text = self._search_string = ''
self._search_index = 0
def __del__(self):
self.set_visibility(False)
@ -94,6 +95,10 @@ class Contact(basecontact.BaseContact):
else:
return ''
# -----------------------------------------------------------------------------------------------------------------
# Unsent messages
# -----------------------------------------------------------------------------------------------------------------
def get_unsent_messages(self):
"""
:return list of unsent messages
@ -108,6 +113,17 @@ class Contact(basecontact.BaseContact):
messages = filter(lambda x: x.get_type() <= 1 and x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr)
return list(map(lambda x: x.get_data(), messages))
def mark_as_sent(self):
try:
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
message.mark_as_sent()
except Exception as ex:
util.log('Mark as sent ex: ' + str(ex))
# -----------------------------------------------------------------------------------------------------------------
# Message deletion
# -----------------------------------------------------------------------------------------------------------------
def delete_message(self, time):
elem = list(filter(lambda x: type(x) is TextMessage and x.get_data()[2] == time, self._corr))[0]
tmp = list(filter(lambda x: x.get_type() <= 1, self._corr))
@ -118,7 +134,7 @@ class Contact(basecontact.BaseContact):
def delete_old_messages(self):
"""
Delete old messages (reduces RAM if messages saving is not enabled)
Delete old messages (reduces RAM usage if messages saving is not enabled)
"""
old = filter(lambda x: x.get_type() == 2 and (x.get_status() >= 2 or x.get_status() is None),
self._corr[:-SAVE_MESSAGES])
@ -126,13 +142,7 @@ class Contact(basecontact.BaseContact):
l = max(len(self._corr) - SAVE_MESSAGES, 0) - len(old)
self._unsaved_messages -= l
self._corr = old + self._corr[-SAVE_MESSAGES:]
def mark_as_sent(self):
try:
message = list(filter(lambda x: x.get_owner() == MESSAGE_OWNER['NOT_SENT'], self._corr))[0]
message.mark_as_sent()
except Exception as ex:
util.log('Mark as sent ex: ' + str(ex))
self._search_index = 0
def clear_corr(self, save_unsent=False):
"""
@ -140,6 +150,7 @@ class Contact(basecontact.BaseContact):
"""
if hasattr(self, '_message_getter'):
del self._message_getter
self._search_index = 0
# don't delete data about active file transfer
if not save_unsent:
self._corr = list(filter(lambda x: x.get_type() == 2 and
@ -151,6 +162,43 @@ class Contact(basecontact.BaseContact):
self._corr))
self._unsaved_messages = len(self.get_unsent_messages())
# -----------------------------------------------------------------------------------------------------------------
# Chat history search
# -----------------------------------------------------------------------------------------------------------------
def search_string(self, search_string):
self._search_string, self._search_index = search_string, 0
return self.search_prev()
def search_prev(self):
while True:
l = len(self._corr)
for i in range(self._search_index - 1, -l - 1, -1):
if type(self._corr[i]) is not TextMessage:
continue
if self._search_string.lower() in self._corr[i].get_data()[0].lower():
self._search_index = i
return i
self._search_index = -l
self.load_corr(False)
if len(self._corr) == l:
return None # not found
def search_next(self):
if not self._search_index:
return None
for i in range(self._search_index + 1, 0):
if type(self._corr[i]) is not TextMessage:
continue
if self._search_string.lower() in self._corr[i].get_data()[0].lower():
self._search_index = i
return i
return None # not found
# -----------------------------------------------------------------------------------------------------------------
# Current text - text from message area
# -----------------------------------------------------------------------------------------------------------------
def get_curr_text(self):
return self._curr_text

View file

@ -11,6 +11,7 @@ from widgets import DataLabel, create_menu
import html as h
import smileys
import settings
import re
class MessageEdit(QtGui.QTextBrowser):
@ -189,6 +190,12 @@ class MessageItem(QtGui.QWidget):
self.message.setFixedHeight(self.height())
self.name.setPixmap(pixmap.scaled(30, 30, QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
def select_text(self, text=''):
tmp = self.message.toHtml()
pattern = re.compile(re.escape(text), re.IGNORECASE) # TODO: save case
tmp = pattern.sub('<font color="red">{}</font>'.format(text), tmp)
self.message.setHtml(tmp)
class ContactItem(QtGui.QWidget):
"""

View file

@ -407,6 +407,8 @@ class MainWindow(QtGui.QMainWindow, Singleton):
clipboard.setText(s)
elif event.key() == QtCore.Qt.Key_Z and event.modifiers() & QtCore.Qt.ControlModifier and self.messages.selectedIndexes():
self.messages.clearSelection()
elif event.key() == QtCore.Qt.Key_F and event.modifiers() & QtCore.Qt.ControlModifier:
self.show_search_field()
else:
super(MainWindow, self).keyPressEvent(event)
@ -699,3 +701,12 @@ class MainWindow(QtGui.QMainWindow, Singleton):
ind = self.online_contacts.currentIndex()
d = {0: 0, 1: 1, 2: 2, 3: 4, 4: 1 | 4, 5: 2 | 4}
self.profile.filtration_and_sorting(d[ind], self.contact_name.text())
def show_search_field(self):
if hasattr(self, 'search_field') and self.search_field.isVisible():
return
self.search_field = SearchScreen(self.messages, self.messages.width(), self.messages.parent())
x, y = self.messages.x(), self.messages.y() + self.messages.height() - 40
self.search_field.setGeometry(x, y, self.messages.width(), 40)
self.messages.setGeometry(x, self.messages.y(), self.messages.width(), self.messages.height() - 40)
self.search_field.show()

View file

@ -2,7 +2,7 @@ try:
from PySide import QtCore, QtGui
except ImportError:
from PyQt4 import QtCore, QtGui
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget
from widgets import RubberBand, create_menu, QRightClickButton, CenteredWidget, LineEdit
from profile import Profile
import smileys
import util
@ -404,3 +404,96 @@ class MainMenuButton(QtGui.QPushButton):
metrics = QtGui.QFontMetrics(self.font())
self.setFixedWidth(metrics.size(QtCore.Qt.TextSingleLine, text).width() + 20)
super().setText(text)
class ClickableLabel(QtGui.QLabel):
def __init__(self, *args):
super().__init__(*args)
def mouseReleaseEvent(self, ev):
self.emit(QtCore.SIGNAL('clicked()'))
class SearchScreen(QtGui.QWidget):
def __init__(self, messages, width, *args):
super().__init__(*args)
self.setMaximumSize(width, 40)
self.setMinimumSize(width, 40)
self._messages = messages
self.search_text = LineEdit(self)
self.search_text.setGeometry(0, 0, width - 100, 40)
self.search_button = ClickableLabel(self)
self.search_button.setGeometry(width - 100, 0, 40, 40)
pixmap = QtGui.QPixmap()
pixmap.load(util.curr_directory() + '/images/search.png')
self.search_button.setScaledContents(False)
self.search_button.setAlignment(QtCore.Qt.AlignCenter)
self.search_button.setPixmap(pixmap)
self.connect(self.search_button, QtCore.SIGNAL('clicked()'), self.search)
self.prev_button = QtGui.QPushButton(self)
self.prev_button.setGeometry(width - 60, 0, 20, 20)
self.prev_button.clicked.connect(self.prev)
self.prev_button.setText('\u25B2')
self.next_button = QtGui.QPushButton(self)
self.next_button.setGeometry(width - 60, 20, 20, 20)
self.next_button.clicked.connect(self.next)
self.next_button.setText('\u25BC')
self.close_button = QtGui.QPushButton(self)
self.close_button.setGeometry(width - 40, 0, 40, 40)
self.close_button.clicked.connect(self.close)
self.close_button.setText('×')
self.close_button.setAlignment(QtCore.Qt.AlignCenter)
font = QtGui.QFont()
font.setPointSize(32)
font.setBold(True)
self.close_button.setFont(font)
def search(self):
text = self.search_text.text() # TODO: clean selection
friend = Profile.get_instance().get_curr_friend()
if text and friend:
index = friend.search_string(text)
self.load_messages(index)
def prev(self):
friend = Profile.get_instance().get_curr_friend()
if friend is not None:
index = friend.search_prev()
self.load_messages(index)
def next(self):
friend = Profile.get_instance().get_curr_friend()
if friend is not None:
index = friend.search_next()
if index is not None:
text = self.search_text.text()
count = self._messages.count()
index += count
item = self._messages.item(index)
self._messages.scrollToItem(item)
self._messages.itemWidget(item).select_text(text)
def load_messages(self, index):
if index is not None:
profile = Profile.get_instance()
count = self._messages.count()
while count + index < 0:
profile.load_history()
count = self._messages.count()
index += count
item = self._messages.item(index)
self._messages.scrollToItem(item)
text = self.search_text.text()
self._messages.itemWidget(item).select_text(text)
def closeEvent(self, *args):
Profile.get_instance().update() # TODO: clean selection?
self._messages.setGeometry(0, 0, self._messages.width(), self._messages.height() + 40)
super().closeEvent(*args)

View file

@ -183,6 +183,9 @@ class Profile(basecontact.BaseContact, Singleton):
return None
return self._contacts[num]
def get_curr_friend(self):
return self._contacts[self._active_friend] if self._active_friend + 1 else None
# -----------------------------------------------------------------------------------------------------------------
# Work with active friend
# -----------------------------------------------------------------------------------------------------------------
@ -211,7 +214,7 @@ class Profile(basecontact.BaseContact, Singleton):
if value is not None:
if self._active_friend + 1 and self._active_friend != value:
try:
self._contacts[self._active_friend].curr_text = self._screen.messageEdit.toPlainText()
self.get_curr_friend().curr_text = self._screen.messageEdit.toPlainText()
except:
pass
friend = self._contacts[value]
@ -261,7 +264,7 @@ class Profile(basecontact.BaseContact, Singleton):
else:
self._screen.call_finished()
else:
friend = self._contacts[self._active_friend]
friend = self.get_curr_friend()
self._screen.account_name.setText(friend.name)
self._screen.account_status.setText(friend.status_message)
@ -287,18 +290,18 @@ class Profile(basecontact.BaseContact, Singleton):
def get_last_message(self):
if self._active_friend + 1:
return self._contacts[self._active_friend].get_last_message_text()
return self.get_curr_friend().get_last_message_text()
else:
return ''
def get_active_number(self):
return self._contacts[self._active_friend].number if self._active_friend + 1 else -1
return self.get_curr_friend().number if self._active_friend + 1 else -1
def get_active_name(self):
return self._contacts[self._active_friend].name if self._active_friend + 1 else ''
return self.get_curr_friend().name if self._active_friend + 1 else ''
def is_active_online(self):
return self._active_friend + 1 and self._contacts[self._active_friend].status is not None
return self._active_friend + 1 and self.get_curr_friend().status is not None
def new_name(self, number, name):
friend = self.get_friend_by_number(number)
@ -373,7 +376,7 @@ class Profile(basecontact.BaseContact, Singleton):
"""
if Settings.get_instance()['typing_notifications'] and self._active_friend + 1:
try:
friend = self._contacts[self._active_friend]
friend = self.get_curr_friend()
if friend.status is not None:
self._tox.self_set_typing(friend.number, typing)
except:
@ -443,7 +446,7 @@ class Profile(basecontact.BaseContact, Singleton):
t = time.time()
self.create_message_item(message, t, MESSAGE_OWNER['FRIEND'], message_type)
self._messages.scrollToBottom()
self._contacts[self._active_friend].append_message(
self.get_curr_friend().append_message(
TextMessage(message, MESSAGE_OWNER['FRIEND'], t, message_type))
else:
friend = self.get_friend_by_number(friend_num)
@ -482,7 +485,7 @@ class Profile(basecontact.BaseContact, Singleton):
friend.append_message(TextMessage(text, MESSAGE_OWNER['NOT_SENT'], t, message_type))
def delete_message(self, time):
friend = self._contacts[self._active_friend]
friend = self.get_curr_friend()
friend.delete_message(time)
self._history.delete_message(friend.tox_id, time)
self.update()
@ -536,7 +539,7 @@ class Profile(basecontact.BaseContact, Singleton):
if not self._load_history:
return
self._load_history = False
friend = self._contacts[self._active_friend]
friend = self.get_curr_friend()
friend.load_corr(False)
data = friend.get_corr()
if not data:
@ -624,7 +627,7 @@ class Profile(basecontact.BaseContact, Singleton):
pixmap = None
if self._show_avatars:
if owner == MESSAGE_OWNER['FRIEND']:
pixmap = self._contacts[self._active_friend].get_pixmap()
pixmap = self.get_curr_friend().get_pixmap()
else:
pixmap = self.get_pixmap()
return self._factory.message_item(text, time, name, owner != MESSAGE_OWNER['NOT_SENT'],
@ -968,7 +971,7 @@ class Profile(basecontact.BaseContact, Singleton):
0, -1)
def cancel_not_started_transfer(self, time):
self._contacts[self._active_friend].delete_one_unsent_file(time)
self.get_curr_friend().delete_one_unsent_file(time)
self.update()
def pause_transfer(self, friend_number, file_number, by_friend=False):
@ -1202,7 +1205,7 @@ class Profile(basecontact.BaseContact, Singleton):
else:
text = QtGui.QApplication.translate("incoming_call", "Outgoing audio call", None,
QtGui.QApplication.UnicodeUTF8)
self._contacts[self._active_friend].append_message(InfoMessage(text, time.time()))
self.get_curr_friend().append_message(InfoMessage(text, time.time()))
self.create_message_item(text, time.time(), '', MESSAGE_TYPE['INFO_MESSAGE'])
self._messages.scrollToBottom()
elif num in self._call: # finish or cancel call if you call with active friend