Skip to content

Commit

Permalink
Merge pull request #717 from moisesjbc/feature/global-search
Browse files Browse the repository at this point in the history
Add global search
  • Loading branch information
TheJackiMonster committed Apr 2, 2021
2 parents e528180 + 1e52af5 commit 4eadf53
Show file tree
Hide file tree
Showing 59 changed files with 14,018 additions and 11,808 deletions.
948 changes: 496 additions & 452 deletions i18n/manuskript_ar_SA.ts

Large diffs are not rendered by default.

769 changes: 378 additions & 391 deletions i18n/manuskript_de.ts

Large diffs are not rendered by default.

950 changes: 497 additions & 453 deletions i18n/manuskript_en_GB.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_es.ts

Large diffs are not rendered by default.

948 changes: 496 additions & 452 deletions i18n/manuskript_fa.ts

Large diffs are not rendered by default.

3,542 changes: 1,787 additions & 1,755 deletions i18n/manuskript_fr.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_hu.ts

Large diffs are not rendered by default.

948 changes: 496 additions & 452 deletions i18n/manuskript_id.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_it.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_ja.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_ko.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_nb_NO.ts

Large diffs are not rendered by default.

950 changes: 497 additions & 453 deletions i18n/manuskript_nl.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_pl.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_pt_BR.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_pt_PT.ts

Large diffs are not rendered by default.

948 changes: 496 additions & 452 deletions i18n/manuskript_ro.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_ru.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_sv.ts

Large diffs are not rendered by default.

948 changes: 496 additions & 452 deletions i18n/manuskript_tr.ts

Large diffs are not rendered by default.

950 changes: 497 additions & 453 deletions i18n/manuskript_uk.ts

Large diffs are not rendered by default.

952 changes: 498 additions & 454 deletions i18n/manuskript_zh_CN.ts

Large diffs are not rendered by default.

948 changes: 496 additions & 452 deletions i18n/manuskript_zh_HANT.ts

Large diffs are not rendered by default.

18 changes: 17 additions & 1 deletion manuskript/enums.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#!/usr/bin/env python
#--!-- coding: utf8 --!--
# --!-- coding: utf8 --!--


from enum import IntEnum
Expand All @@ -17,6 +17,7 @@ class Character(IntEnum):
summaryFull = 9
notes = 10
pov = 11
infos = 12

class Plot(IntEnum):
name = 0
Expand Down Expand Up @@ -67,3 +68,18 @@ class Abstract(IntEnum):
title = 0
ID = 1
type = 2

class FlatData(IntEnum):
summarySituation = 0,
summarySentence = 1,
summaryPara = 2,
summaryPage = 3,
summaryFull = 4

class Model(IntEnum):
Character = 0
Plot = 1
PlotStep = 2
World = 3
Outline = 4
FlatData = 5
45 changes: 44 additions & 1 deletion manuskript/functions/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from PyQt5.QtCore import QUrl, QTimer
from PyQt5.QtGui import QBrush, QIcon, QPainter, QColor, QImage, QPixmap
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import qApp, QFileDialog, QTextEdit
from PyQt5.QtWidgets import qApp, QFileDialog

from manuskript.enums import Outline

Expand Down Expand Up @@ -450,5 +450,48 @@ def inspect():
s.function))
print(" " + "".join(s.code_context))


def search(searchRegex, text):
"""
Search all occurrences of a regex in a text.
:param searchRegex: a regex object with the search to perform
:param text: text to search on
:return: list of tuples (startPos, endPos)
"""
if text is not None:
return [(m.start(), m.end(), getSearchResultContext(text, m.start(), m.end())) for m in searchRegex.finditer(text)]
else:
return []

def getSearchResultContext(text, startPos, endPos):
matchSize = endPos - startPos
maxContextSize = max(matchSize, 600)
extraContextSize = int((maxContextSize - matchSize) / 2)
separator = "[...]"

context = ""

i = startPos - 1
while i > 0 and (startPos - i) < extraContextSize and text[i] != '\n':
i -= 1
contextStartPos = i
if i > 0:
context += separator + " "
context += text[contextStartPos:startPos].replace('\n', '')

context += '<b>' + text[startPos:endPos].replace('\n', '') + '</b>'

i = endPos
while i < len(text) and (i - endPos) < extraContextSize and text[i] != '\n':
i += 1
contextEndPos = i

context += text[endPos:contextEndPos].replace('\n', '')
if i < len(text):
context += " " + separator

return context

# Spellchecker loads writablePath from this file, so we need to load it after they get defined
from manuskript.functions.spellchecker import Spellchecker
10 changes: 9 additions & 1 deletion manuskript/mainWindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
QRegExp, QUrl, QSize, QModelIndex)
from PyQt5.QtGui import QStandardItemModel, QIcon, QColor
from PyQt5.QtWidgets import QMainWindow, QHeaderView, qApp, QMenu, QActionGroup, QAction, QStyle, QListWidgetItem, \
QLabel, QDockWidget, QWidget, QMessageBox
QLabel, QDockWidget, QWidget, QMessageBox, QLineEdit

from manuskript import settings
from manuskript.enums import Character, PlotStep, Plot, World, Outline
Expand Down Expand Up @@ -129,6 +129,7 @@ def __init__(self):
self.actCopy.triggered.connect(self.documentsCopy)
self.actCut.triggered.connect(self.documentsCut)
self.actPaste.triggered.connect(self.documentsPaste)
self.actSearch.triggered.connect(self.doSearch)
self.actRename.triggered.connect(self.documentsRename)
self.actDuplicate.triggered.connect(self.documentsDuplicate)
self.actDelete.triggered.connect(self.documentsDelete)
Expand Down Expand Up @@ -499,6 +500,13 @@ def documentsCut(self):
def documentsPaste(self):
"Paste clipboard item(s) into selected item."
if self._lastFocus: self._lastFocus.paste()
def doSearch(self):
"Do a global search."
self.dckSearch.show()
self.dckSearch.activateWindow()
searchTextInput = self.dckSearch.findChild(QLineEdit, 'searchTextInput')
searchTextInput.setFocus()
searchTextInput.selectAll()
def documentsRename(self):
"Rename selected item."
if self._lastFocus: self._lastFocus.rename()
Expand Down
58 changes: 53 additions & 5 deletions manuskript/models/characterModel.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
from PyQt5.QtCore import QModelIndex, Qt, QAbstractItemModel, QVariant
from PyQt5.QtGui import QIcon, QPixmap, QColor

from manuskript.functions import randomColor, iconColor, mainWindow
from manuskript.enums import Character as C
from manuskript.functions import randomColor, iconColor, mainWindow, search
from manuskript.enums import Character as C, Model
from manuskript.searchLabels import CharacterSearchLabels

from manuskript.models.searchableModel import searchableModel
from manuskript.models.searchableItem import searchableItem

class characterModel(QAbstractItemModel):
class characterModel(QAbstractItemModel, searchableModel):

def __init__(self, parent):
QAbstractItemModel.__init__(self, parent)
Expand Down Expand Up @@ -229,12 +232,14 @@ def removeCharacterInfo(self, ID):
c.infos.pop(r)
self.endRemoveRows()

def searchableItems(self):
return self.characters

###############################################################################
# CHARACTER
###############################################################################


class Character():
class Character(searchableItem):
def __init__(self, model, name="No name", importance = 0):
self._model = model
self.lastPath = ""
Expand All @@ -248,6 +253,8 @@ def __init__(self, model, name="No name", importance = 0):

self.infos = []

super().__init__(CharacterSearchLabels)

def name(self):
return self._data[C.name.value]

Expand All @@ -263,6 +270,12 @@ def ID(self):
def index(self, column=0):
return self._model.indexFromItem(self, column)

def data(self, column):
if column == "Info":
return self.infos
else:
return self._data.get(column, None)

def assignRandomColor(self):
"""
Assigns a random color the the character.
Expand Down Expand Up @@ -325,6 +338,41 @@ def listInfos(self):
r.append((i.description, i.value))
return r

def searchTitle(self, column):
return self.name()

def searchOccurrences(self, searchRegex, column):
results = []

data = self.searchData(column)
if isinstance(data, list):
for i in range(0, len(data)):
# For detailed info we will highlight the full row, so we pass the row index
# to the highlighter instead of the (startPos, endPos) of the match itself.
results += [self.wrapSearchOccurrence(column, i, 0, context) for
(startPos, endPos, context) in search(searchRegex, data[i].description)]
results += [self.wrapSearchOccurrence(column, i, 0, context) for
(startPos, endPos, context) in search(searchRegex, data[i].value)]
else:
results += super().searchOccurrences(searchRegex, column)

return results

def searchID(self):
return self.ID()

def searchPath(self, column):
return [self.translate("Characters"), self.name(), self.translate(self.searchColumnLabel(column))]

def searchData(self, column):
if column == C.infos:
return self.infos
else:
return self.data(column)

def searchModel(self):
return Model.Character


class CharacterInfo():
def __init__(self, character, description="", value=""):
Expand Down
53 changes: 53 additions & 0 deletions manuskript/models/flatDataModelWrapper.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
#!/usr/bin/env python
# --!-- coding: utf8 --!--

from manuskript.enums import FlatData, Model
from manuskript.searchLabels import FlatDataSearchLabels

from manuskript.models.searchableModel import searchableModel
from manuskript.models.searchableItem import searchableItem

"""
All searches are performed on models inheriting from searchableModel, but special metadata such as book summaries
are stored directly on a GUI element (QStandardItemModel). We wrap this GUI element inside this wrapper class
so it exposes the same interface for searches.
"""
class flatDataModelWrapper(searchableModel, searchableItem):
def __init__(self, qstandardItemModel):
self.qstandardItemModel = qstandardItemModel

def searchableItems(self):
return [flatDataItemWrapper(self.qstandardItemModel)]


class flatDataItemWrapper(searchableItem):
def __init__(self, qstandardItemModel):
super().__init__(FlatDataSearchLabels)
self.qstandardItemModel = qstandardItemModel

def searchModel(self):
return Model.FlatData

def searchID(self):
return None

def searchTitle(self, column):
return self.translate(self.searchColumnLabel(column))

def searchPath(self, column):
return [self.translate("Summary"), self.translate(self.searchColumnLabel(column))]

def searchData(self, column):
return self.qstandardItemModel.item(1, self.searchDataIndex(column)).text()

@staticmethod
def searchDataIndex(column):
columnIndices = {
FlatData.summarySituation: 0,
FlatData.summarySentence: 1,
FlatData.summaryPara: 2,
FlatData.summaryPage: 3,
FlatData.summaryFull: 4
}

return columnIndices[column]
56 changes: 46 additions & 10 deletions manuskript/models/outlineItem.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
from PyQt5.QtWidgets import qApp
from lxml import etree as ET
from manuskript.models.abstractItem import abstractItem
from manuskript.models.searchableItem import searchableItem
from manuskript import enums
from manuskript import functions as F
from manuskript import settings
from manuskript.converters import HTML2PlainText
from manuskript.searchLabels import OutlineSearchLabels
from manuskript.enums import Outline, Model

try:
locale.setlocale(locale.LC_ALL, '')
Expand All @@ -21,7 +24,7 @@
pass


class outlineItem(abstractItem):
class outlineItem(abstractItem, searchableItem):

enum = enums.Outline

Expand All @@ -30,6 +33,7 @@ class outlineItem(abstractItem):

def __init__(self, model=None, title="", _type="folder", xml=None, parent=None, ID=None):
abstractItem.__init__(self, model, title, _type, xml, parent, ID)
searchableItem.__init__(self, OutlineSearchLabels)

self.defaultTextType = None
if not self._data.get(self.enum.compile):
Expand Down Expand Up @@ -355,8 +359,7 @@ def findItemsByPOV(self, POV):

return lst

def findItemsContaining(self, text, columns, mainWindow=F.mainWindow(),
caseSensitive=False, recursive=True):
def findItemsContaining(self, text, columns, mainWindow=F.mainWindow(), caseSensitive=False, recursive=True):
"""Returns a list if IDs of all subitems
containing ``text`` in columns ``columns``
(being a list of int).
Expand All @@ -369,16 +372,14 @@ def findItemsContaining(self, text, columns, mainWindow=F.mainWindow(),

return lst

def itemContains(self, text, columns, mainWindow=F.mainWindow(),
caseSensitive=False):
def itemContains(self, text, columns, mainWindow=F.mainWindow(), caseSensitive=False):
lst = []
text = text.lower() if not caseSensitive else text
for c in columns:

if c == self.enum.POV and self.POV():
c = mainWindow.mdlCharacter.getCharacterByID(self.POV())
if c:
searchIn = c.name()
character = mainWindow.mdlCharacter.getCharacterByID(self.POV())
if character:
searchIn = character.name()
else:
searchIn = ""
print("Character POV not found:", self.POV())
Expand All @@ -393,7 +394,6 @@ def itemContains(self, text, columns, mainWindow=F.mainWindow(),
searchIn = self.data(c)

searchIn = searchIn.lower() if not caseSensitive else searchIn

if text in searchIn:
if not self.ID() in lst:
lst.append(self.ID())
Expand Down Expand Up @@ -515,3 +515,39 @@ def setFromXMLProcessMore(self, root):
for child in root:
if child.tag == "revision":
self.appendRevision(child.attrib["timestamp"], child.attrib["text"])

#######################################################################
# Search
#######################################################################
def searchModel(self):
return Model.Outline

def searchID(self):
return self.data(Outline.ID)

def searchTitle(self, column):
return self.title()

def searchPath(self, column):
return [self.translate("Outline")] + self.path().split(' > ') + [self.translate(self.searchColumnLabel(column))]

def searchData(self, column):
mainWindow = F.mainWindow()

searchData = None

if column == self.enum.POV and self.POV():
character = mainWindow.mdlCharacter.getCharacterByID(self.POV())
if character:
searchData = character.name()

elif column == self.enum.status:
searchData = mainWindow.mdlStatus.item(F.toInt(self.status()), 0).text()

elif column == self.enum.label:
searchData = mainWindow.mdlLabels.item(F.toInt(self.label()), 0).text()

else:
searchData = self.data(column)

return searchData

0 comments on commit 4eadf53

Please sign in to comment.