#!/usr/bin/env python # --!-- coding: utf8 --!-- import re ############################################################################### # SHORT REFERENCES ############################################################################### # A regex used to match references from PyQt5.QtWidgets import qApp from manuskript.enums import Outline from manuskript.enums import Character from manuskript.enums import Plot from manuskript.enums import PlotStep from manuskript.functions import mainWindow RegEx = r"{(\w):(\d+):?.*?}" # A non-capturing regex used to identify references RegExNonCapturing = r"{\w:\d+:?.*?}" # The basic format of the references EmptyRef = "{{{}:{}:{}}}" EmptyRefSearchable = "{{{}:{}:" CharacterLetter = "C" TextLetter = "T" PlotLetter = "P" WorldLetter = "W" def plotReference(ID, searchable=False): """Takes the ID of a plot and returns a reference for that plot. @searchable: returns a stripped version that allows simple text search.""" if not searchable: return EmptyRef.format(PlotLetter, ID, "") else: return EmptyRefSearchable.format(PlotLetter, ID, "") def characterReference(ID, searchable=False): """Takes the ID of a character and returns a reference for that character. @searchable: returns a stripped version that allows simple text search.""" if not searchable: return EmptyRef.format(CharacterLetter, ID, "") else: return EmptyRefSearchable.format(CharacterLetter, ID, "") def textReference(ID, searchable=False): """Takes the ID of an outline item and returns a reference for that item. @searchable: returns a stripped version that allows simple text search.""" if not searchable: return EmptyRef.format(TextLetter, ID, "") else: return EmptyRefSearchable.format(TextLetter, ID, "") def worldReference(ID, searchable=False): """Takes the ID of a world item and returns a reference for that item. @searchable: returns a stripped version that allows simple text search.""" if not searchable: return EmptyRef.format(WorldLetter, ID, "") else: return EmptyRefSearchable.format(WorldLetter, ID, "") ############################################################################### # READABLE INFOS ############################################################################### def infos(ref): """Returns a full paragraph in HTML format containing detailed infos about the reference ``ref``. """ match = re.fullmatch(RegEx, ref) if not match: return qApp.translate("references", "Not a reference: {}.").format(ref) _type = match.group(1) _ref = match.group(2) # A text or outine item if _type == TextLetter: m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if not idx.isValid(): return qApp.translate("references", "Unknown reference: {}.").format(ref) item = idx.internalPointer() # Titles pathTitle = qApp.translate("references", "Path:") statsTitle = qApp.translate("references", "Stats:") POVTitle = qApp.translate("references", "POV:") statusTitle = qApp.translate("references", "Status:") labelTitle = qApp.translate("references", "Label:") ssTitle = qApp.translate("references", "Short summary:") lsTitle = qApp.translate("references", "Long summary:") notesTitle = qApp.translate("references", "Notes:") # The POV of the scene POV = "" if item.POV(): POV = "{text}".format( ref=characterReference(item.POV()), text=mainWindow().mdlCharacter.getCharacterByID(item.POV()).name()) # The status of the scene status = item.status() if status: status = mainWindow().mdlStatus.item(int(status), 0).text() else: status = "" # The label of the scene label = item.label() if label: label = mainWindow().mdlLabels.item(int(label), 0).text() else: label = "" # The path of the scene path = item.pathID() pathStr = [] for _id, title in path: pathStr.append("{text}".format( ref=textReference(_id), text=title)) path = " > ".join(pathStr) # Summaries and notes ss = item.data(Outline.summarySentence.value) ls = item.data(Outline.summaryFull.value) notes = item.data(Outline.notes.value) text = """

{title}

{pathTitle} {path}

{statsTitle} {stats}
{POV} {status} {label}

{ss} {ls} {notes} {references} """.format( title=item.title(), pathTitle=pathTitle, path=path, statsTitle=statsTitle, stats=item.stats(), POV="{POVTitle} {POV}
".format( POVTitle=POVTitle, POV=POV) if POV else "", status="{statusTitle} {status}
".format( statusTitle=statusTitle, status=status) if status else "", label="{labelTitle} {label}

".format( labelTitle=labelTitle, label=label) if label else "", ss="

{ssTitle} {ss}

".format( ssTitle=ssTitle, ss=ss.replace("\n", "
")) if ss.strip() else "", ls="

{lsTitle}
{ls}

".format( lsTitle=lsTitle, ls=ls.replace("\n", "
")) if ls.strip() else "", notes="

{notesTitle}
{notes}

".format( notesTitle=notesTitle, notes=linkifyAllRefs(notes)) if notes.strip() else "", references=listReferences(ref) ) return text # A character elif _type == CharacterLetter: m = mainWindow().mdlCharacter c = m.getCharacterByID(int(_ref)) index = c.index() name = c.name() # Titles basicTitle = qApp.translate("references", "Basic info") detailedTitle = qApp.translate("references", "Detailed info") POVof = qApp.translate("references", "POV of:") # Goto (link) goto = qApp.translate("references", "Go to {}.") goto = goto.format(refToLink(ref)) # basic infos basic = [] for i in [ (Character.motivation, qApp.translate("references", "Motivation"), False), (Character.goal, qApp.translate("references", "Goal"), False), (Character.conflict, qApp.translate("references", "Conflict"), False), (Character.epiphany, qApp.translate("references", "Epiphany"), False), (Character.summarySentence, qApp.translate("references", "Short summary"), True), (Character.summaryPara, qApp.translate("references", "Longer summary"), True), ]: val = m.data(index.sibling(index.row(), i[0].value)) if val: basic.append("{title}:{n}{val}".format( title=i[1], n="\n" if i[2] else " ", val=val)) basic = "
".join(basic) # detailed infos detailed = [] for _name, _val in c.listInfos(): detailed.append("{}: {}".format( _name, _val)) detailed = "
".join(detailed) # list scenes of which it is POV oM = mainWindow().mdlOutline lst = oM.findItemsByPOV(_ref) listPOV = "" for t in lst: idx = oM.getIndexByID(t) listPOV += "
  • {text}
  • ".format( link=textReference(t), text=oM.data(idx, Outline.title.value)) text = """

    {name}

    {goto} {basicInfos} {detailedInfos} {POV} {references} """.format( name=name, goto=goto, basicInfos="

    {basicTitle}

    {basic}".format( basicTitle=basicTitle, basic=basic) if basic else "", detailedInfos="

    {detailedTitle}

    {detailed}".format( detailedTitle=detailedTitle, detailed=detailed) if detailed else "", POV="

    {POVof}

    ".format( POVof=POVof, listPOV=listPOV) if listPOV else "", references=listReferences(ref) ) return text # A plot elif _type == PlotLetter: m = mainWindow().mdlPlots index = m.getIndexFromID(_ref) name = m.getPlotNameByID(_ref) # Titles descriptionTitle = qApp.translate("references", "Description") resultTitle = qApp.translate("references", "Result") charactersTitle = qApp.translate("references", "Characters") stepsTitle = qApp.translate("references", "Resolution steps") # Goto (link) goto = qApp.translate("references", "Go to {}.") goto = goto.format(refToLink(ref)) # Description description = m.data(index.sibling(index.row(), Plot.description.value)) # Result result = m.data(index.sibling(index.row(), Plot.result.value)) # Characters pM = mainWindow().mdlCharacter item = m.item(index.row(), Plot.characters.value) characters = "" if item: for r in range(item.rowCount()): ID = item.child(r, 0).text() characters += "
  • {text}".format( link=characterReference(ID), text=pM.getCharacterByID(ID).name()) # Resolution steps steps = "" item = m.item(index.row(), Plot.steps.value) if item: for r in range(item.rowCount()): title = item.child(r, PlotStep.name.value).text() summary = item.child(r, PlotStep.summary.value).text() meta = item.child(r, PlotStep.meta.value).text() if meta: meta = " ({})".format(meta) steps += "
  • {title}{summary}{meta}
  • ".format( title=title, summary=": {}".format(summary) if summary else "", meta=meta if meta else "") text = """

    {name}

    {goto} {characters} {description} {result} {steps} {references} """.format( name=name, goto=goto, description="

    {title}

    {text}".format( title=descriptionTitle, text=description) if description else "", result="

    {title}

    {text}".format( title=resultTitle, text=result) if result else "", characters="

    {title}

    ".format( title=charactersTitle, lst=characters) if characters else "", steps="

    {title}

    ".format( title=stepsTitle, steps=steps) if steps else "", references=listReferences(ref) ) return text # A World item elif _type == WorldLetter: m = mainWindow().mdlWorld index = m.indexByID(_ref) name = m.name(index) # Titles descriptionTitle = qApp.translate("references", "Description") passionTitle = qApp.translate("references", "Passion") conflictTitle = qApp.translate("references", "Conflict") # Goto (link) goto = qApp.translate("references", "Go to {}.") goto = goto.format(refToLink(ref)) # Description description = basicFormat(m.description(index)) # Passion passion = basicFormat(m.passion(index)) # Conflict conflict = basicFormat(m.conflict(index)) text = """

    {name}

    {goto} {description} {passion} {conflict} {references} """.format( name=name, goto=goto, description="

    {title}

    {text}".format( title=descriptionTitle, text=description) if description else "", passion="

    {title}

    {text}".format( title=passionTitle, text=passion) if passion else "", conflict="

    {title}

    ".format( title=conflictTitle, lst=conflict) if conflict else "", references=listReferences(ref) ) return text else: return qApp.translate("references", "Unknown reference: {}.").format(ref) def shortInfos(ref): """Returns infos about reference ``ref``. Returns -1 if ``ref`` is not a valid reference, and None if it is valid but unknown.""" match = re.fullmatch(RegEx, ref) if not match: return -1 _type = match.group(1) _ref = match.group(2) infos = {} infos["ID"] = _ref if _type == TextLetter: infos["type"] = TextLetter m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if not idx.isValid(): return None item = idx.internalPointer() if item.isFolder(): infos["text_type"] = "folder" else: infos["text_type"] = "text" infos["title"] = item.title() infos["path"] = item.path() return infos elif _type == CharacterLetter: infos["type"] = CharacterLetter m = mainWindow().mdlCharacter c = m.getCharacterByID(_ref) if c: infos["title"] = c.name() infos["name"] = c.name() return infos elif _type == PlotLetter: infos["type"] = PlotLetter m = mainWindow().mdlPlots name = m.getPlotNameByID(_ref) if name: infos["title"] = name return infos elif _type == WorldLetter: infos["type"] = WorldLetter m = mainWindow().mdlWorld item = m.itemByID(_ref) if item: name = item.text() path = m.path(item) infos["title"] = name infos["path"] = path return infos return None def title(ref): """Returns a the title (or name) for the reference ``ref``.""" infos = shortInfos(ref) if infos and infos != -1 and "title" in infos: return infos["title"] else: return None def type(ref): infos = shortInfos(ref) if infos and infos != -1: return infos["type"] def ID(ref): infos = shortInfos(ref) if infos and infos != -1: return infos["ID"] def tooltip(ref): """Returns a tooltip in HTML for the reference ``ref``.""" infos = shortInfos(ref) if not infos: return qApp.translate("references", "Unknown reference: {}.").format(ref) if infos == -1: return qApp.translate("references", "Not a reference: {}.").format(ref) if infos["type"] == TextLetter: if infos["text_type"] == "folder": tt = qApp.translate("references", "Folder: {}").format(infos["title"]) else: tt = qApp.translate("references", "Text: {}").format(infos["title"]) tt += "
    {}".format(infos["path"]) return tt elif infos["type"] == CharacterLetter: return qApp.translate("references", "Character: {}").format(infos["title"]) elif infos["type"] == PlotLetter: return qApp.translate("references", "Plot: {}").format(infos["title"]) elif infos["type"] == WorldLetter: return qApp.translate("references", "World: {name}{path}").format( name=infos["title"], path=" ({})".format(infos["path"]) if infos["path"] else "") ############################################################################### # FUNCTIONS ############################################################################### def refToLink(ref): """Transforms the reference ``ref`` in a link displaying useful infos about that reference. For character, character's name. For text item, item's name, etc. """ match = re.fullmatch(RegEx, ref) if match: _type = match.group(1) _ref = match.group(2) text = "" if _type == TextLetter: m = mainWindow().mdlOutline idx = m.getIndexByID(_ref) if idx.isValid(): item = idx.internalPointer() text = item.title() elif _type == CharacterLetter: m = mainWindow().mdlCharacter text = m.getCharacterByID(int(_ref)).name() elif _type == PlotLetter: m = mainWindow().mdlPlots text = m.getPlotNameByID(_ref) elif _type == WorldLetter: m = mainWindow().mdlWorld text = m.itemByID(_ref).text() if text: return "{text}".format( ref=ref, text=text) else: return ref def linkifyAllRefs(text): """Takes all the references in ``text`` and transform them into HMTL links.""" return re.sub(RegEx, lambda m: refToLink(m.group(0)), text) def findReferencesTo(ref, parent=None, recursive=True): """List of text items containing references ref, and returns IDs. Starts from item parent. If None, starts from root.""" oM = mainWindow().mdlOutline if parent == None: parent = oM.rootItem # Removes everything after the second ':': '{L:ID:random text}' → '{L:ID:' ref = ref[:ref.index(":", ref.index(":") + 1)+1] # Bare form '{L:ID}' ref2 = ref[:-1] + "}" # Since it's a simple search (no regex), we search for both. lst = parent.findItemsContaining(ref, [Outline.notes.value], recursive=recursive) lst += parent.findItemsContaining(ref2, [Outline.notes.value], recursive=recursive) return lst def listReferences(ref, title=qApp.translate("references", "Referenced in:")): oM = mainWindow().mdlOutline listRefs = "" lst = findReferencesTo(ref) for t in lst: idx = oM.getIndexByID(t) listRefs += "
  • {text}
  • ".format( link=textReference(t), text=oM.data(idx, Outline.title.value)) return "

    {title}

    ".format( title=title, ref=listRefs) if listRefs else "" def basicFormat(text): if not text: return "" text = text.replace("\n", "
    ") text = linkifyAllRefs(text) return text def open(ref): """Identify ``ref`` and open it.""" match = re.fullmatch(RegEx, ref) if not match: return _type = match.group(1) _ref = match.group(2) if _type == CharacterLetter: mw = mainWindow() item = mw.lstCharacters.getItemByID(_ref) if item: mw.tabMain.setCurrentIndex(mw.TabPersos) mw.lstCharacters.setCurrentItem(item) return True print("Error: Ref {} not found".format(ref)) return False elif _type == TextLetter: mw = mainWindow() index = mw.mdlOutline.getIndexByID(_ref) if index.isValid(): mw.tabMain.setCurrentIndex(mw.TabRedac) mw.mainEditor.setCurrentModelIndex(index, newTab=True) return True else: print("Ref not found") return False elif _type == PlotLetter: mw = mainWindow() item = mw.lstPlots.getItemByID(_ref) if item: mw.tabMain.setCurrentIndex(mw.TabPlots) mw.lstPlots.setCurrentItem(item) return True print("Ref not found") return False elif _type == WorldLetter: mw = mainWindow() item = mw.mdlWorld.itemByID(_ref) if item: mw.tabMain.setCurrentIndex(mw.TabWorld) mw.treeWorld.setCurrentIndex( mw.mdlWorld.indexFromItem(item)) return True print("Ref not found") return False print("Ref not implemented") return False