diff --git a/nw/convert/text/tohtml.py b/nw/convert/text/tohtml.py index 64ddbbb7..3a67df48 100644 --- a/nw/convert/text/tohtml.py +++ b/nw/convert/text/tohtml.py @@ -23,6 +23,18 @@ class ToHtml(Tokenizer): def __init__(self, theProject, theParent): Tokenizer.__init__(self, theProject, theParent) + self.forPreview = False + return + + def setPreview(self, forPreview): + """If we're using this class to generate markdown preview, we need to make a few changes to + formatting, which is selected by this flag. + """ + + self.forPreview = forPreview + if forPreview: + self.doKeywords = True + return def doAutoReplace(self): @@ -103,10 +115,45 @@ class ToHtml(Tokenizer): self.theResult += "
%s
\n" % tText elif tType == self.T_KEYWORD and self.doKeywords: - self.theResult += "
@%s
\n" % tText - - # print(self.theResult) + self.theResult += self._formatTags(tText) return + ## + # Internal Functions + ## + + def _formatTags(self, tText): + + if not self.forPreview: + return "
@%s
\n" % tText + + theLabel = { + "@tag" : "Tag", + "@pov" : "Point of View", + "@char" : "Character(s)", + "@plot" : "Plot", + "@time" : "Time", + "@location" : "Location(s)", + "@object" : "Object(s)", + "@custom" : "Custom", + } + + tText = "@"+tText + isValid, theBits, thePos = self.theParent.theIndex.scanThis(tText) + if not isValid or not theBits: + return "" + + retText = "" + refTags = [] + if theBits[0] in theLabel: + retText += "%s: " % theLabel[theBits[0]] + for tTag in theBits[1:]: + refTags.append("%s" % ( + theBits[0][1:], tTag, tTag + )) + retText += ", ".join(refTags) + + return "
%s
" % retText + # END Class ToHtml diff --git a/nw/gui/elements/docviewer.py b/nw/gui/elements/docviewer.py index 39342036..b669b2d2 100644 --- a/nw/gui/elements/docviewer.py +++ b/nw/gui/elements/docviewer.py @@ -19,7 +19,7 @@ from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor from nw.convert.tokenizer import Tokenizer from nw.convert.text.tohtml import ToHtml -from nw.enum import nwItemType +from nw.enum import nwAlert, nwItemType logger = logging.getLogger(__name__) @@ -46,6 +46,8 @@ class GuiDocViewer(QTextBrowser): theOpt.setAlignment(Qt.AlignJustify) self.qDocument.setDefaultTextOption(theOpt) + self.anchorClicked.connect(self._linkClicked) + logger.debug("DocViewer initialisation complete") return @@ -102,6 +104,7 @@ class GuiDocViewer(QTextBrowser): logger.debug("Generating preview for item %s" % tHandle) sPos = self.verticalScrollBar().value() aDoc = ToHtml(self.theProject, self.theParent) + aDoc.setPreview(True) aDoc.setText(tHandle) aDoc.doAutoReplace() aDoc.tokenizeText() @@ -113,6 +116,8 @@ class GuiDocViewer(QTextBrowser): self.theHandle = tHandle self.theProject.setLastViewed(tHandle) + self.theParent.docMeta.refreshReferences(tHandle) + return True def loadFromTag(self, theTag): @@ -137,22 +142,59 @@ class GuiDocViewer(QTextBrowser): # Internal Functions ## + def _linkClicked(self, theURL): + + theLink = theURL.url() + tHandle = None + onLine = 0 + theTag = "" + if len(theLink) > 0: + theBits = theLink.split("=") + if len(theBits) == 2: + theTag = theBits[1] + tHandle, onLine = self.theParent.theIndex.getTagSource(theBits[1]) + + if tHandle is None: + self.theParent.makeAlert(( + "Could not find the reference for tag '%s'. It either doesn't exist, or the index " + "is out of date. The index can be updated from the Tools menu.") % theTag, + nwAlert.ERROR + ) + return + else: + self.loadText(tHandle) + + return + def _makeStyleSheet(self): - self.qDocument.setDefaultStyleSheet(( + styleSheet = ( "body {{" - " font-size: {textSize}pt;" + " font-size: {textSize:.1f}pt;" " color: rgb({tColR},{tColG},{tColB});" "}}\n" - "h1, h2, h3, h4 {{" + "h1 {{" " color: rgb({hColR},{hColG},{hColB});" + " font-size: {h1Size:.1f}pt;" + "}}\n" + "h2 {{" + " color: rgb({hColR},{hColG},{hColB});" + " font-size: {h2Size:.1f}pt;" + "}}\n" + "h3 {{" + " color: rgb({hColR},{hColG},{hColB});" + " font-size: {h3Size:.1f}pt;" + "}}\n" + "h4 {{" + " color: rgb({hColR},{hColG},{hColB});" + " font-size: {h4Size:.1f}pt;" "}}\n" "a {{" " color: rgb({aColR},{aColG},{aColB});" "}}\n" "pre {{" " color: rgb({cColR},{cColG},{cColB});" - " font-size: {preSize}pt;" + " font-size: {preSize:.1f}pt;" "}}\n" "mark {{" " color: rgb({eColR},{eColG},{eColB});" @@ -163,9 +205,17 @@ class GuiDocViewer(QTextBrowser): "td {{" " padding: 0px 4px;" "}}\n" + ".tags {{" + " color: rgb({kColR},{kColG},{kColB});" + " font-wright: bold;" + "}}\n" ).format( textSize = self.mainConf.textSize, preSize = self.mainConf.textSize*0.9, + h1Size = self.mainConf.textSize*1.8, + h2Size = self.mainConf.textSize*1.6, + h3Size = self.mainConf.textSize*1.4, + h4Size = self.mainConf.textSize*1.2, tColR = self.theTheme.colText[0], tColG = self.theTheme.colText[1], tColB = self.theTheme.colText[2], @@ -181,7 +231,11 @@ class GuiDocViewer(QTextBrowser): aColR = self.theTheme.colLink[0], aColG = self.theTheme.colLink[1], aColB = self.theTheme.colLink[2], - )) + kColR = self.theTheme.colKey[0], + kColG = self.theTheme.colKey[1], + kColB = self.theTheme.colKey[2], + ) + self.qDocument.setDefaultStyleSheet(styleSheet) return True diff --git a/nw/gui/elements/viewdetails.py b/nw/gui/elements/viewdetails.py new file mode 100644 index 00000000..38f1260c --- /dev/null +++ b/nw/gui/elements/viewdetails.py @@ -0,0 +1,82 @@ +# -*- coding: utf-8 -*- +"""novelWriter GUI DocView Details + + novelWriter – GUI DocView Details +=================================== + Class holding the document view details panel + + File History: + Created: 2019-09-31 [0.3.2] + +""" + +import logging +import nw + +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import QWidget, QHBoxLayout, QLabel, QGroupBox, QComboBox + +from nw.constants import nwLabels + +logger = logging.getLogger(__name__) + +class GuiDocViewDetails(QWidget): + + def __init__(self, theParent, theProject): + QWidget.__init__(self, theParent) + + logger.debug("Initialising DocViewDetails ...") + self.mainConf = nw.CONFIG + self.debugGUI = self.mainConf.debugGUI + self.theParent = theParent + self.theProject = theProject + + self.outerBox = QHBoxLayout(self) + self.outerBox.setContentsMargins(4,4,4,4) + + self.refTags = QGroupBox("Referenced From", self) + self.refTagsForm = QHBoxLayout(self.refTags) + self.refTags.setLayout(self.refTagsForm) + + self.refList = QLabel("None") + self.refList.linkActivated.connect(self._linkClicked) + + self.refTagsForm.addWidget(self.refList) + self.outerBox.addWidget(self.refTags) + self.setLayout(self.outerBox) + self.setContentsMargins(0,0,0,0) + + logger.debug("DocViewDetails initialisation complete") + + return + + def refreshReferences(self, tHandle): + + theRefs = self.theParent.theIndex.buildReferenceList(tHandle) + if theRefs: + self.setVisible(True) + else: + self.setVisible(False) + return + + theList = [] + for tHandle in theRefs: + tItem = self.theProject.getItem(tHandle) + if tItem is not None: + theList.append("%s" % (tHandle,tItem.itemName)) + + self.refList.setText(", ".join(theList)) + + return + + ## + # Internal Functions + ## + + def _linkClicked(self, theLink): + if len(theLink) == 18: + tHandle = theLink[-13:] + self.theParent.viewDocument(tHandle) + return + +# END Class GuiDocViewDetails diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index d69f50ee..cbd02b70 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -25,7 +25,7 @@ class GuiMainStatus(QStatusBar): def __init__(self, theParent): QStatusBar.__init__(self, theParent) - logger.debug("Initialising GuiMainStatus ...") + logger.debug("Initialising MainStatus ...") self.mainConf = nw.CONFIG self.theParent = theParent @@ -80,7 +80,7 @@ class GuiMainStatus(QStatusBar): self.sessionTimer.timeout.connect(self._updateTime) self.sessionTimer.start() - logger.debug("GuiMainStatus initialisation complete") + logger.debug("MainStatus initialisation complete") self.clearStatus() diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index a45c9067..6c70e981 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -31,6 +31,7 @@ from nw.gui.elements.docviewer import GuiDocViewer from nw.gui.elements.docdetails import GuiDocDetails from nw.gui.elements.searchbar import GuiSearchBar from nw.gui.elements.noticebar import GuiNoticeBar +from nw.gui.elements.viewdetails import GuiDocViewDetails from nw.gui.dialogs.configeditor import GuiConfigEditor from nw.gui.dialogs.projecteditor import GuiProjectEditor from nw.gui.dialogs.export import GuiExport @@ -72,14 +73,15 @@ class GuiMain(QMainWindow): self.setWindowIcon(QIcon(path.join(self.mainConf.appIcon))) # Main GUI Elements - self.statusBar = GuiMainStatus(self) - self.noticeBar = GuiNoticeBar(self) - self.docEditor = GuiDocEditor(self, self.theProject) - self.docViewer = GuiDocViewer(self, self.theProject) - self.docDetails = GuiDocDetails(self, self.theProject) - self.searchBar = GuiSearchBar(self) - self.treeView = GuiDocTree(self, self.theProject) - self.mainMenu = GuiMainMenu(self, self.theProject) + self.statusBar = GuiMainStatus(self) + self.noticeBar = GuiNoticeBar(self) + self.docEditor = GuiDocEditor(self, self.theProject) + self.docViewer = GuiDocViewer(self, self.theProject) + self.docMeta = GuiDocViewDetails(self, self.theProject) + self.searchBar = GuiSearchBar(self) + self.treeMeta = GuiDocDetails(self, self.theProject) + self.treeView = GuiDocTree(self, self.theProject) + self.mainMenu = GuiMainMenu(self, self.theProject) # Minor Gui Elements self.statusIcons = [] @@ -90,20 +92,27 @@ class GuiMain(QMainWindow): self.treeBox = QVBoxLayout() self.treeBox.setContentsMargins(0,0,0,0) self.treeBox.addWidget(self.treeView) - self.treeBox.addWidget(self.docDetails) + self.treeBox.addWidget(self.treeMeta) self.treePane.setLayout(self.treeBox) - self.docPane = QFrame() + self.editPane = QFrame() + self.docEdit = QVBoxLayout() + self.docEdit.setContentsMargins(0,0,0,0) + self.docEdit.addWidget(self.searchBar) + self.docEdit.addWidget(self.noticeBar) + self.docEdit.addWidget(self.docEditor) + self.editPane.setLayout(self.docEdit) + + self.viewPane = QFrame() self.docView = QVBoxLayout() self.docView.setContentsMargins(0,0,0,0) - self.docView.addWidget(self.searchBar) - self.docView.addWidget(self.noticeBar) - self.docView.addWidget(self.docEditor) - self.docPane.setLayout(self.docView) + self.docView.addWidget(self.docViewer) + self.docView.addWidget(self.docMeta) + self.viewPane.setLayout(self.docView) self.splitView = QSplitter(Qt.Horizontal) - self.splitView.addWidget(self.docPane) - self.splitView.addWidget(self.docViewer) + self.splitView.addWidget(self.editPane) + self.splitView.addWidget(self.viewPane) self.splitView.splitterMoved.connect(self._splitViewMove) self.splitMain = QSplitter(Qt.Horizontal) @@ -117,15 +126,15 @@ class GuiMain(QMainWindow): self.idxTree = self.splitMain.indexOf(self.treePane) self.idxMain = self.splitMain.indexOf(self.splitView) - self.idxEditor = self.splitView.indexOf(self.docPane) - self.idxViewer = self.splitView.indexOf(self.docViewer) + self.idxEditor = self.splitView.indexOf(self.editPane) + self.idxViewer = self.splitView.indexOf(self.viewPane) self.splitMain.setCollapsible(self.idxTree, False) self.splitMain.setCollapsible(self.idxMain, False) self.splitView.setCollapsible(self.idxEditor, False) self.splitView.setCollapsible(self.idxViewer, True) - self.docViewer.setVisible(False) + self.viewPane.setVisible(False) self.searchBar.setVisible(False) # Build The Tree View @@ -365,9 +374,9 @@ class GuiMain(QMainWindow): logger.debug("No document selected, giving up") return False - if self.docViewer.loadText(tHandle) and not self.docViewer.isVisible(): + if self.docViewer.loadText(tHandle) and not self.viewPane.isVisible(): bPos = self.splitMain.sizes() - self.docViewer.setVisible(True) + self.viewPane.setVisible(True) vPos = [0,0] vPos[0] = int(bPos[1]/2) vPos[1] = bPos[1]-vPos[0] @@ -669,11 +678,11 @@ class GuiMain(QMainWindow): self.docViewer.clearViewer() self.theProject.setLastViewed(None) bPos = self.splitMain.sizes() - self.docViewer.setVisible(False) + self.viewPane.setVisible(False) vPos = [bPos[1],0] self.splitView.setSizes(vPos) self.docEditor.changeWidth() - return not self.docViewer.isVisible() + return not self.viewPane.isVisible() ## # Internal Functions @@ -739,7 +748,7 @@ class GuiMain(QMainWindow): def _treeSingleClick(self): sHandle = self.treeView.getSelectedHandle() if sHandle is not None: - self.docDetails.buildViewBox(sHandle) + self.treeMeta.buildViewBox(sHandle) return def _treeDoubleClick(self, tItem, colNo): diff --git a/nw/project/index.py b/nw/project/index.py index a55bf14e..86236a61 100644 --- a/nw/project/index.py +++ b/nw/project/index.py @@ -319,6 +319,35 @@ class NWIndex(): return True + def buildReferenceList(self, theHandle): + + theRefs = {} + + tItem = self.theProject.getItem(theHandle) + if theHandle is None: + return theRefs + + theTag = None + for tTag in self.tagIndex: + if theHandle == self.tagIndex[tTag][1]: + theTag = tTag + break + + if theTag is not None: + for tHandle in self.refIndex: + for nLine, tKey, tTag, nTitle in self.refIndex[tHandle]: + if tTag == theTag: + theRefs[tHandle] = nLine + + return theRefs + + def getTagSource(self, theTag): + if theTag in self.tagIndex: + theRef = self.tagIndex[theTag] + if len(theRef) == 3: + return theRef[1], theRef[0] + return None, 0 + def buildTagNovelMap(self, theTags, theFilters=None): tagMap = {}