manuskript/manuskript/ui/search.py
2021-04-02 14:15:17 +01:00

175 lines
6.4 KiB
Python

#!/usr/bin/env python
# --!-- coding: utf8 --!--
from PyQt5.QtCore import Qt, QRect, QEvent, QCoreApplication
from PyQt5.QtGui import QPalette, QFontMetrics, QKeySequence
from PyQt5.QtWidgets import QWidget, qApp, QListWidgetItem, QStyledItemDelegate, QStyle, QLabel, QToolTip, QShortcut
from manuskript.functions import mainWindow
from manuskript.ui import style
from manuskript.ui.search_ui import Ui_search
from manuskript.enums import Model
from manuskript.models.flatDataModelWrapper import flatDataModelWrapper
from manuskript.ui.searchMenu import searchMenu
from manuskript.ui.highlighters.searchResultHighlighters.searchResultHighlighter import searchResultHighlighter
class search(QWidget, Ui_search):
def __init__(self, parent=None):
_translate = QCoreApplication.translate
QWidget.__init__(self, parent)
self.setupUi(self)
self.searchTextInput.returnPressed.connect(self.search)
self.searchMenu = searchMenu()
self.btnOptions.setMenu(self.searchMenu)
self.delegate = listResultDelegate(self)
self.result.setItemDelegate(self.delegate)
self.result.setMouseTracking(True)
self.result.itemClicked.connect(self.openItem)
self.result.setStyleSheet(style.searchResultSS())
self.searchTextInput.setStyleSheet(style.lineEditSS())
self.searchResultHighlighter = searchResultHighlighter()
self.noResultsLabel = QLabel(_translate("Search", "No results found"), self.result)
self.noResultsLabel.setVisible(False)
self.noResultsLabel.setStyleSheet("QLabel {color: gray;}")
# Add shortcuts for navigating through search results
QShortcut(QKeySequence(_translate("MainWindow", "F3")), self.searchTextInput, self.nextSearchResult)
QShortcut(QKeySequence(_translate("MainWindow", "Shift+F3")), self.searchTextInput, self.previousSearchResult)
# These texts are already included in translation files but including ":" at the end. We force here the
# translation for them without ":"
_translate("MainWindow", "Situation")
_translate("MainWindow", "Status")
def nextSearchResult(self):
if self.result.currentRow() < self.result.count() - 1:
self.result.setCurrentRow(self.result.currentRow() + 1)
else:
self.result.setCurrentRow(0)
if 0 < self.result.currentRow() < self.result.count():
self.openItem(self.result.currentItem())
def previousSearchResult(self):
if self.result.currentRow() > 0:
self.result.setCurrentRow(self.result.currentRow() - 1)
else:
self.result.setCurrentRow(self.result.count() - 1)
if 0 < self.result.currentRow() < self.result.count():
self.openItem(self.result.currentItem())
def prepareRegex(self, searchText):
import re
flags = re.UNICODE
if self.searchMenu.caseSensitive() is False:
flags |= re.IGNORECASE
if self.searchMenu.regex() is False:
searchText = re.escape(searchText)
if self.searchMenu.matchWords() is True:
# Source: https://stackoverflow.com/a/15863102
searchText = r'\b' + searchText + r'\b'
return re.compile(searchText, flags)
def search(self):
self.result.clear()
self.result.setCurrentRow(0)
searchText = self.searchTextInput.text()
if len(searchText) > 0:
searchRegex = self.prepareRegex(searchText)
results = []
# Set override cursor
qApp.setOverrideCursor(Qt.WaitCursor)
for model, modelName in [
(mainWindow().mdlOutline, Model.Outline),
(mainWindow().mdlCharacter, Model.Character),
(flatDataModelWrapper(mainWindow().mdlFlatData), Model.FlatData),
(mainWindow().mdlWorld, Model.World),
(mainWindow().mdlPlots, Model.Plot)
]:
filteredColumns = self.searchMenu.columns(modelName)
# Searching
if len(filteredColumns):
results += model.searchOccurrences(searchRegex, filteredColumns)
# Showing results
self.generateResultsLists(results)
# Remove override cursor
qApp.restoreOverrideCursor()
def generateResultsLists(self, results):
self.noResultsLabel.setVisible(len(results) == 0)
for result in results:
item = QListWidgetItem(result.title(), self.result)
item.setData(Qt.UserRole, result)
item.setData(Qt.UserRole + 1, ' > '.join(result.path()))
item.setData(Qt.UserRole + 2, result.context())
self.result.addItem(item)
def openItem(self, item):
self.searchResultHighlighter.highlightSearchResult(item.data(Qt.UserRole))
def leaveEvent(self, event):
self.delegate.mouseLeave()
class listResultDelegate(QStyledItemDelegate):
def __init__(self, parent=None):
QStyledItemDelegate.__init__(self, parent)
self._tooltipRowIndex = -1
def paint(self, painter, option, index):
extra = index.data(Qt.UserRole + 1)
if not extra:
return QStyledItemDelegate.paint(self, painter, option, index)
else:
if option.state & QStyle.State_Selected:
painter.fillRect(option.rect, option.palette.color(QPalette.Highlight))
title = index.data()
painter.drawText(option.rect.adjusted(2, 1, 0, 0), Qt.AlignLeft, title)
fm = QFontMetrics(option.font)
w = fm.width(title)
r = QRect(option.rect)
r.setLeft(r.left() + w)
painter.save()
if option.state & QStyle.State_Selected:
painter.setPen(Qt.white)
else:
painter.setPen(Qt.gray)
painter.drawText(r.adjusted(2, 1, 0, 0), Qt.AlignLeft, " - {}".format(extra))
painter.restore()
def editorEvent(self, event, model, option, index):
if event.type() == QEvent.MouseMove and self._tooltipRowIndex != index.row():
self._tooltipRowIndex = index.row()
context = index.data(Qt.UserRole + 2)
extra = index.data(Qt.UserRole + 1)
QToolTip.showText(event.globalPos(),
"<p>#" + str(index.row()) + " - " + extra + "</p><p>" + context + "</p>")
return True
return False
def mouseLeave(self):
self._tooltipRowIndex = -1