diff --git a/nw/config.py b/nw/config.py index 985eca43..66fdb649 100644 --- a/nw/config.py +++ b/nw/config.py @@ -95,6 +95,7 @@ class Config: self.projColWidth = [140, 55, 140] self.mainPanePos = [300, 800] self.docPanePos = [400, 400] + self.outlnPanePos = [500, 150] self.isFullScreen = False ## Project @@ -342,6 +343,9 @@ class Config: self.docPanePos = self._parseLine( cnfParse, cnfSec, "docpane", self.CNF_LIST, self.docPanePos ) + self.outlnPanePos = self._parseLine( + cnfParse, cnfSec, "outlinepane", self.CNF_LIST, self.outlnPanePos + ) self.isFullScreen = self._parseLine( cnfParse, cnfSec, "fullscreen", self.CNF_BOOL, self.isFullScreen ) @@ -481,12 +485,13 @@ class Config: ## Sizes cnfSec = "Sizes" cnfParse.add_section(cnfSec) - cnfParse.set(cnfSec,"geometry", self._packList(self.winGeometry)) - cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth)) - cnfParse.set(cnfSec,"projcols", self._packList(self.projColWidth)) - cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos)) - cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos)) - cnfParse.set(cnfSec,"fullscreen", str(self.isFullScreen)) + cnfParse.set(cnfSec,"geometry", self._packList(self.winGeometry)) + cnfParse.set(cnfSec,"treecols", self._packList(self.treeColWidth)) + cnfParse.set(cnfSec,"projcols", self._packList(self.projColWidth)) + cnfParse.set(cnfSec,"mainpane", self._packList(self.mainPanePos)) + cnfParse.set(cnfSec,"docpane", self._packList(self.docPanePos)) + cnfParse.set(cnfSec,"outlinepane", self._packList(self.outlnPanePos)) + cnfParse.set(cnfSec,"fullscreen", str(self.isFullScreen)) ## Project cnfSec = "Project" @@ -700,6 +705,11 @@ class Config: self.confChanged = True return True + def setOutlinePanePos(self, panePos): + self.outlnPanePos = panePos + self.confChanged = True + return True + def setShowRefPanel(self, checkState): self.showRefPanel = checkState self.confChanged = True diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 4df869b5..981173f2 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -13,7 +13,8 @@ from nw.gui.docviewer import GuiDocViewer from nw.gui.itemdetails import GuiItemDetails from nw.gui.itemeditor import GuiItemEditor from nw.gui.mainmenu import GuiMainMenu -from nw.gui.outline import GuiProjectOutline +from nw.gui.outline import GuiOutline +from nw.gui.outlinedetails import GuiOutlineDetails from nw.gui.preferences import GuiPreferences from nw.gui.projload import GuiProjectLoad from nw.gui.projsettings import GuiProjectSettings @@ -37,7 +38,8 @@ __all__ = [ "GuiItemDetails", "GuiItemEditor", "GuiMainMenu", - "GuiProjectOutline", + "GuiOutline", + "GuiOutlineDetails", "GuiPreferences", "GuiProjectLoad", "GuiProjectSettings", diff --git a/nw/gui/docdetails.py b/nw/gui/docdetails.py index d4b2621c..09b43426 100644 --- a/nw/gui/docdetails.py +++ b/nw/gui/docdetails.py @@ -37,13 +37,13 @@ logger = logging.getLogger(__name__) class GuiDocViewDetails(QWidget): - def __init__(self, theParent, theProject): + def __init__(self, theParent): QWidget.__init__(self, theParent) logger.debug("Initialising DocViewDetails ...") self.mainConf = nw.CONFIG self.theParent = theParent - self.theProject = theProject + self.theProject = theParent.theProject self.currHandle = None self.outerBox = QGridLayout(self) diff --git a/nw/gui/doceditor.py b/nw/gui/doceditor.py index bf511e07..1b3f81bb 100644 --- a/nw/gui/doceditor.py +++ b/nw/gui/doceditor.py @@ -50,16 +50,16 @@ logger = logging.getLogger(__name__) class GuiDocEditor(QTextEdit): - def __init__(self, theParent, theProject): - QTextEdit.__init__(self) + def __init__(self, theParent): + QTextEdit.__init__(self, theParent) logger.debug("Initialising GuiDocEditor ...") # Class Variables self.mainConf = nw.CONFIG - self.theProject = theProject self.theParent = theParent self.theTheme = theParent.theTheme + self.theProject = theParent.theProject self.docChanged = False self.spellCheck = False self.nwDocument = NWDoc(self.theProject, self.theParent) diff --git a/nw/gui/docviewer.py b/nw/gui/docviewer.py index 1be66bba..fb1a9c99 100644 --- a/nw/gui/docviewer.py +++ b/nw/gui/docviewer.py @@ -40,16 +40,16 @@ logger = logging.getLogger(__name__) class GuiDocViewer(QTextBrowser): - def __init__(self, theParent, theProject): - QTextBrowser.__init__(self) + def __init__(self, theParent): + QTextBrowser.__init__(self, theParent) - logger.debug("Initialising DocViewer ...") + logger.debug("Initialising GuiDocViewer ...") # Class Variables self.mainConf = nw.CONFIG - self.theProject = theProject self.theParent = theParent self.theTheme = theParent.theTheme + self.theProject = theParent.theProject self.theHandle = None self.qDocument = self.document() @@ -70,7 +70,7 @@ class GuiDocViewer(QTextBrowser): self.anchorClicked.connect(self._linkClicked) self.setFocusPolicy(Qt.StrongFocus) - logger.debug("DocViewer initialisation complete") + logger.debug("GuiDocViewer initialisation complete") # Connect Functions self.setSelectedHandle = self.theParent.treeView.setSelectedHandle @@ -268,6 +268,21 @@ class GuiDocViewer(QTextBrowser): logger.verbose("Cursor moved to line %d" % theLine) return True + ## + # Slots + ## + + def _linkClicked(self, theURL): + """Slot for a link in the document being clicked. + """ + theLink = theURL.url() + logger.verbose("Clicked link: '%s'" % theLink) + if len(theLink) > 0: + theBits = theLink.split("=") + if len(theBits) == 2: + self.loadFromTag(theBits[1]) + return + ## # Events ## @@ -293,17 +308,6 @@ class GuiDocViewer(QTextBrowser): self.setTextCursor(theCursor) return - def _linkClicked(self, theURL): - """Slot for a link in the document being clicked. - """ - theLink = theURL.url() - logger.verbose("Clicked link: '%s'" % theLink) - if len(theLink) > 0: - theBits = theLink.split("=") - if len(theBits) == 2: - self.loadFromTag(theBits[1]) - return - def _makeStyleSheet(self): """Generate an appropriate style sheet for the document viewer, based on the current syntax highlighter theme, diff --git a/nw/gui/itemdetails.py b/nw/gui/itemdetails.py index 93ffb788..7335a596 100644 --- a/nw/gui/itemdetails.py +++ b/nw/gui/itemdetails.py @@ -30,7 +30,7 @@ import nw from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QIcon, QPixmap -from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel +from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel from nw.constants import ( nwLabels, nwItemClass, nwItemType, nwItemLayout, nwUnicode @@ -38,15 +38,15 @@ from nw.constants import ( logger = logging.getLogger(__name__) -class GuiItemDetails(QFrame): +class GuiItemDetails(QWidget): - def __init__(self, theParent, theProject): - QFrame.__init__(self, theParent) + def __init__(self, theParent): + QWidget.__init__(self, theParent) logger.debug("Initialising GuiItemDetails ...") self.mainConf = nw.CONFIG self.theParent = theParent - self.theProject = theProject + self.theProject = theParent.theProject self.theTheme = theParent.theTheme self.theHandle = None diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 5ff4d918..fc170377 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -39,13 +39,13 @@ logger = logging.getLogger(__name__) class GuiMainMenu(QMenuBar): - def __init__(self, theParent, theProject): + def __init__(self, theParent): QMenuBar.__init__(self, theParent) - logger.debug("Initialising Main Menu ...") + logger.debug("Initialising GuiMainMenu ...") self.mainConf = nw.CONFIG self.theParent = theParent - self.theProject = theProject + self.theProject = theParent.theProject self._buildProjectMenu() self._buildDocumentMenu() @@ -60,7 +60,7 @@ class GuiMainMenu(QMenuBar): self._moveTreeItem = self.theParent.treeView.moveTreeItem self._newTreeItem = self.theParent.treeView.newTreeItem - logger.debug("Main Menu initialisation complete") + logger.debug("GuiMainMenu initialisation complete") return diff --git a/nw/gui/outline.py b/nw/gui/outline.py index 12c26383..368b0403 100644 --- a/nw/gui/outline.py +++ b/nw/gui/outline.py @@ -39,7 +39,7 @@ from nw.constants import nwKeyWords, nwLabels, nwOutline logger = logging.getLogger(__name__) -class GuiProjectOutline(QTreeWidget): +class GuiOutline(QTreeWidget): DEF_WIDTH = { nwOutline.TITLE : 200, @@ -79,17 +79,17 @@ class GuiProjectOutline(QTreeWidget): nwOutline.SYNOP : False, } - def __init__(self, theParent, theProject): + def __init__(self, theParent): QTreeWidget.__init__(self, theParent) - logger.debug("Initialising ProjectOutline ...") + logger.debug("Initialising GuiOutline ...") self.mainConf = nw.CONFIG self.theParent = theParent - self.theProject = theProject + self.theProject = theParent.theProject self.theTheme = theParent.theTheme self.theIndex = theParent.theIndex - self.optState = theProject.optState + self.optState = theParent.theProject.optState self.headerMenu = GuiOutlineHeaderMenu(self) self.firstView = True @@ -100,6 +100,7 @@ class GuiProjectOutline(QTreeWidget): self.setExpandsOnDoubleClick(False) self.setDragEnabled(False) self.itemDoubleClicked.connect(self._treeDoubleClick) + self.itemSelectionChanged.connect(self._itemSelected) iPx = self.theTheme.textIconSize self.setIconSize(QSize(iPx, iPx)) @@ -120,7 +121,7 @@ class GuiProjectOutline(QTreeWidget): self.clearOutline() self.headerMenu.setHiddenState(self.colHidden) - logger.debug("ProjectOutline initialisation complete") + logger.debug("GuiOutline initialisation complete") return @@ -198,6 +199,18 @@ class GuiProjectOutline(QTreeWidget): self.theParent.openDocument(tHandle, tLine - 1) return + def _itemSelected(self): + """Extract the handle and line number of the currently selected + title, and send it to the details panel. + """ + selItems = self.selectedItems() + if selItems: + tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole) + sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole) + logger.verbose("User selected entry %s:%s" % (tHandle, sTitle)) + self.theParent.projMeta.showItem(tHandle, sTitle) + return + def _headerRightClick(self, clickPos): """Show the header column menu. """ @@ -233,7 +246,7 @@ class GuiProjectOutline(QTreeWidget): # Load whatever we saved last time, regardless of wether it # contains the correct names or number of columns. The names # must be valid though. - tempOrder = self.optState.getValue("GuiProjectOutline", "headerOrder", []) + tempOrder = self.optState.getValue("GuiOutline", "headerOrder", []) treeOrder = [] for hName in tempOrder: try: @@ -256,14 +269,14 @@ class GuiProjectOutline(QTreeWidget): # We load whatever column widths and hidden states we find in # the file, and leave the rest in their default state. - tmpWidth = self.optState.getValue("GuiProjectOutline", "columnWidth", {}) + tmpWidth = self.optState.getValue("GuiOutline", "columnWidth", {}) for hName in tmpWidth: try: self.colWidth[nwOutline[hName]] = tmpWidth[hName] except: logger.warning("Ignored unknown outline column '%s'" % str(hName)) - tmpHidden = self.optState.getValue("GuiProjectOutline", "columnHidden", {}) + tmpHidden = self.optState.getValue("GuiOutline", "columnHidden", {}) for hName in tmpHidden: try: self.colHidden[nwOutline[hName]] = tmpHidden[hName] @@ -304,9 +317,9 @@ class GuiProjectOutline(QTreeWidget): if not logHidden and logWidth > 0: colWidth[hName] = logWidth - self.optState.setValue("GuiProjectOutline", "headerOrder", treeOrder) - self.optState.setValue("GuiProjectOutline", "columnWidth", colWidth) - self.optState.setValue("GuiProjectOutline", "columnHidden", colHidden) + self.optState.setValue("GuiOutline", "headerOrder", treeOrder) + self.optState.setValue("GuiOutline", "columnWidth", colWidth) + self.optState.setValue("GuiOutline", "columnHidden", colHidden) self.optState.saveSettings() return @@ -415,6 +428,7 @@ class GuiProjectOutline(QTreeWidget): newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName) newItem.setIcon(self.colIndex[nwOutline.LABEL], self.theTheme.getIcon("proj_document")) newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:].lstrip("0")) + newItem.setData(self.colIndex[nwOutline.LINE], Qt.UserRole, sTitle) newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"]) newItem.setText(self.colIndex[nwOutline.CCOUNT], str(novIdx["cCount"])) newItem.setText(self.colIndex[nwOutline.WCOUNT], str(novIdx["wCount"])) @@ -438,7 +452,7 @@ class GuiProjectOutline(QTreeWidget): return newItem -# END Class GuiProjectOutline +# END Class GuiOutline class GuiOutlineHeaderMenu(QMenu): diff --git a/nw/gui/outlinedetails.py b/nw/gui/outlinedetails.py new file mode 100644 index 00000000..33048837 --- /dev/null +++ b/nw/gui/outlinedetails.py @@ -0,0 +1,301 @@ +# -*- coding: utf-8 -*- +"""novelWriter GUI Project Outline + + novelWriter – GUI Project Outline +=================================== + Class holding the project outline view + + File History: + Created: 2020-06-02 [0.7.0] + + This file is a part of novelWriter + Copyright 2020, Veronica Berglyd Olsen + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +""" + +import logging +import nw + +from PyQt5.QtCore import Qt +from PyQt5.QtWidgets import ( + QScrollArea, QWidget, QGridLayout, QHBoxLayout, QGroupBox, QLabel, QSizePolicy +) + +from nw.constants import nwLabels, nwKeyWords +from nw.common import checkInt + +logger = logging.getLogger(__name__) + +class GuiOutlineDetails(QScrollArea): + + LVL_MAP = { + "H1" : "Title", + "H2" : "Chapter", + "H3" : "Scene", + "H4" : "Section" + } + + def __init__(self, theParent): + QScrollArea.__init__(self, theParent) + + logger.debug("Initialising GuiOutlineDetails ...") + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theProject = theParent.theProject + self.theTheme = theParent.theTheme + self.theIndex = theParent.theIndex + self.optState = theParent.theProject.optState + + # Sizes + minTitle = 30*self.theTheme.textNWidth + maxTitle = 40*self.theTheme.textNWidth + wCount = self.theTheme.getTextWidth("999,999") + hSpace = int(0.8*self.theTheme.textNWidth) + vSpace = int(0.2*self.theTheme.textNHeight) + + # Details Area + self.titleLabel = QLabel("Title") + self.fileLabel = QLabel("Document") + self.itemLabel = QLabel("Status") + self.titleValue = QLabel("") + self.fileValue = QLabel("") + self.itemValue = QLabel("") + + self.titleValue.setMinimumWidth(minTitle) + self.titleValue.setMaximumWidth(maxTitle) + self.fileValue.setMinimumWidth(minTitle) + self.fileValue.setMaximumWidth(maxTitle) + self.itemValue.setMinimumWidth(minTitle) + self.itemValue.setMaximumWidth(maxTitle) + + # Stats Area + self.cCLabel = QLabel("Characters") + self.wCLabel = QLabel("Words") + self.pCLabel = QLabel("Paragraphs") + self.cCValue = QLabel("") + self.wCValue = QLabel("") + self.pCValue = QLabel("") + + self.cCValue.setMinimumWidth(wCount) + self.wCValue.setMinimumWidth(wCount) + self.pCValue.setMinimumWidth(wCount) + self.cCValue.setAlignment(Qt.AlignRight) + self.wCValue.setAlignment(Qt.AlignRight) + self.pCValue.setAlignment(Qt.AlignRight) + + # Synopsis + self.synopLabel = QLabel("Synopsis") + self.synopValue = QLabel("") + self.synopLWrap = QHBoxLayout() + self.synopValue.setWordWrap(True) + self.synopValue.setAlignment(Qt.AlignTop | Qt.AlignLeft) + self.synopLWrap.addWidget(self.synopValue, 1) + + # Tags + self.povKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.POV_KEY]) + self.chrKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]) + self.pltKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]) + self.timKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]) + self.wldKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]) + self.objKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]) + self.entKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]) + self.cstKeyLabel = QLabel("%s" % nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]) + + self.povKeyLWrap = QHBoxLayout() + self.chrKeyLWrap = QHBoxLayout() + self.pltKeyLWrap = QHBoxLayout() + self.timKeyLWrap = QHBoxLayout() + self.wldKeyLWrap = QHBoxLayout() + self.objKeyLWrap = QHBoxLayout() + self.entKeyLWrap = QHBoxLayout() + self.cstKeyLWrap = QHBoxLayout() + + self.povKeyValue = QLabel("") + self.chrKeyValue = QLabel("") + self.pltKeyValue = QLabel("") + self.timKeyValue = QLabel("") + self.wldKeyValue = QLabel("") + self.objKeyValue = QLabel("") + self.entKeyValue = QLabel("") + self.cstKeyValue = QLabel("") + + self.povKeyValue.setWordWrap(True) + self.chrKeyValue.setWordWrap(True) + self.pltKeyValue.setWordWrap(True) + self.timKeyValue.setWordWrap(True) + self.wldKeyValue.setWordWrap(True) + self.objKeyValue.setWordWrap(True) + self.entKeyValue.setWordWrap(True) + self.cstKeyValue.setWordWrap(True) + + self.povKeyValue.linkActivated.connect(self._tagClicked) + self.chrKeyValue.linkActivated.connect(self._tagClicked) + self.pltKeyValue.linkActivated.connect(self._tagClicked) + self.timKeyValue.linkActivated.connect(self._tagClicked) + self.wldKeyValue.linkActivated.connect(self._tagClicked) + self.objKeyValue.linkActivated.connect(self._tagClicked) + self.entKeyValue.linkActivated.connect(self._tagClicked) + self.cstKeyValue.linkActivated.connect(self._tagClicked) + + self.povKeyLWrap.addWidget(self.povKeyValue, 1) + self.chrKeyLWrap.addWidget(self.chrKeyValue, 1) + self.pltKeyLWrap.addWidget(self.pltKeyValue, 1) + self.timKeyLWrap.addWidget(self.timKeyValue, 1) + self.wldKeyLWrap.addWidget(self.wldKeyValue, 1) + self.objKeyLWrap.addWidget(self.objKeyValue, 1) + self.entKeyLWrap.addWidget(self.entKeyValue, 1) + self.cstKeyLWrap.addWidget(self.cstKeyValue, 1) + + # Selected Item Details + self.mainGroup = QGroupBox("Title Details", self) + self.mainForm = QGridLayout() + self.mainGroup.setLayout(self.mainForm) + + self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, Qt.AlignTop | Qt.AlignRight) + self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, Qt.AlignTop | Qt.AlignRight) + self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, Qt.AlignTop | Qt.AlignRight) + self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft) + self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft) + + self.mainForm.setColumnStretch(1, 1) + self.mainForm.setRowStretch(4, 1) + self.mainForm.setHorizontalSpacing(hSpace) + self.mainForm.setVerticalSpacing(vSpace) + + # Selected Item Tags + self.tagsGroup = QGroupBox("Reference Tags", self) + self.tagsForm = QGridLayout() + self.tagsGroup.setLayout(self.tagsForm) + + self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.chrKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.chrKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.pltKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.pltKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.timKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.timKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.wldKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.wldKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.objKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.objKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.entKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.entKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addWidget(self.cstKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft) + self.tagsForm.addLayout(self.cstKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft) + + self.tagsForm.setColumnStretch(1, 1) + self.tagsForm.setRowStretch(7, 1) + self.tagsForm.setHorizontalSpacing(hSpace) + self.tagsForm.setVerticalSpacing(vSpace) + + # Assemble + self.outerWidget = QWidget() + self.outerBox = QHBoxLayout() + self.outerBox.addWidget(self.mainGroup, 0) + self.outerBox.addWidget(self.tagsGroup, 1) + # self.outerBox.addStretch(1) + + self.outerWidget.setLayout(self.outerBox) + self.setWidget(self.outerWidget) + self.show() + + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + self.setWidgetResizable(True) + + logger.debug("GuiOutlineDetails initialisation complete") + + return + + def showItem(self, tHandle, sTitle): + """Update the content of the tree with the given handle and line + number pointing to a header. + """ + try: + nwItem = self.theProject.projTree[tHandle] + novIdx = self.theIndex.novelIndex[tHandle][sTitle] + theRefs = self.theIndex.getReferences(tHandle, sTitle) + except: + return False + + if novIdx["level"] in self.LVL_MAP: + self.titleLabel.setText("%s" % self.LVL_MAP[novIdx["level"]]) + else: + self.titleLabel.setText("Title") + self.titleValue.setText(novIdx["title"]) + + self.fileValue.setText(nwItem.itemName) + self.itemValue.setText(nwItem.itemStatus) + + self.cCValue.setText("{:n}".format(checkInt(novIdx["cCount"], 0))) + self.wCValue.setText("{:n}".format(checkInt(novIdx["pCount"], 0))) + self.pCValue.setText("{:n}".format(checkInt(novIdx["wCount"], 0))) + + self.synopValue.setText(novIdx["synopsis"]) + + self.povKeyValue.setText(self._formatTags(theRefs, nwKeyWords.POV_KEY)) + self.chrKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CHAR_KEY)) + self.pltKeyValue.setText(self._formatTags(theRefs, nwKeyWords.PLOT_KEY)) + self.timKeyValue.setText(self._formatTags(theRefs, nwKeyWords.TIME_KEY)) + self.wldKeyValue.setText(self._formatTags(theRefs, nwKeyWords.WORLD_KEY)) + self.objKeyValue.setText(self._formatTags(theRefs, nwKeyWords.OBJECT_KEY)) + self.entKeyValue.setText(self._formatTags(theRefs, nwKeyWords.ENTITY_KEY)) + self.cstKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CUSTOM_KEY)) + + return True + + ## + # Slots + ## + + def _tagClicked(self, theLink): + """Capture the click of a tag in the right-most column. + """ + logger.verbose("Clicked link: '%s'" % theLink) + if len(theLink) > 0: + theBits = theLink.split("=") + if len(theBits) == 2: + self.theParent.docViewer.loadFromTag(theBits[1]) + self.theParent.tabWidget.setCurrentWidget(self.theParent.splitView) + return + + ## + # Internal Functions + ## + + def _formatTags(self, theRefs, theKey): + """Format the tags as clickable links. + """ + if theKey not in theKey: + return "" + refTags = [] + for tTag in theRefs[theKey]: + refTags.append("%s" % ( + theKey[1:], tTag, tTag + )) + return ", ".join(refTags) + +# END Class GuiOutlineDetails diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index 72246c3a..1dd065cc 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -49,14 +49,14 @@ class GuiProjectTree(QTreeWidget): C_EXPORT = 2 C_FLAGS = 3 - def __init__(self, theParent, theProject): + def __init__(self, theParent): QTreeWidget.__init__(self, theParent) logger.debug("Initialising GuiProjectTree ...") self.mainConf = nw.CONFIG self.theParent = theParent self.theTheme = theParent.theTheme - self.theProject = theProject + self.theProject = theParent.theProject # Tree Settings self.theMap = None diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 49ea367c..8c736719 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -140,6 +140,8 @@ class GuiTheme: self.fontPixelSize = int(round(qMetric.height())) self.baseIconSize = int(round(qMetric.ascent())) self.textIconSize = int(round(qMetric.ascent() + qMetric.leading())) + self.textNHeight = qMetric.boundingRect("N").height() + self.textNWidth = qMetric.boundingRect("N").width() logger.verbose("GUI Font Family: %s" % self.guiFont.family()) logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize) diff --git a/nw/guimain.py b/nw/guimain.py index ea6c050e..e9d9b81d 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -35,15 +35,15 @@ from time import time from PyQt5.QtCore import Qt, QTimer from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor from PyQt5.QtWidgets import ( - qApp, QMainWindow, QVBoxLayout, QFrame, QSplitter, QFileDialog, QShortcut, + qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut, QMessageBox, QDialog, QTabWidget ) from nw.gui import ( - GuiMainMenu, GuiMainStatus, GuiTheme, GuiProjectTree, GuiDocEditor, - GuiDocViewer, GuiItemDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails, - GuiPreferences, GuiProjectSettings, GuiItemEditor, GuiProjectOutline, - GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad, GuiBuildNovel + GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails, + GuiDocViewer, GuiItemDetails, GuiItemEditor, GuiMainMenu, GuiMainStatus, + GuiNoticeBar, GuiOutline, GuiOutlineDetails, GuiPreferences, GuiProjectLoad, + GuiProjectSettings, GuiProjectTree, GuiSearchBar, GuiSessionLogView, GuiTheme ) from nw.core import NWProject, NWDoc, NWIndex from nw.constants import nwFiles, nwItemType, nwAlert @@ -91,28 +91,29 @@ class GuiMain(QMainWindow): # Main GUI Elements self.statusBar = GuiMainStatus(self) self.noticeBar = GuiNoticeBar(self) - self.treeView = GuiProjectTree(self, self.theProject) - self.docEditor = GuiDocEditor(self, self.theProject) - self.docViewer = GuiDocViewer(self, self.theProject) - self.viewMeta = GuiDocViewDetails(self, self.theProject) + self.treeView = GuiProjectTree(self) + self.docEditor = GuiDocEditor(self) + self.docViewer = GuiDocViewer(self) + self.viewMeta = GuiDocViewDetails(self) self.searchBar = GuiSearchBar(self) - self.treeMeta = GuiItemDetails(self, self.theProject) - self.projView = GuiProjectOutline(self, self.theProject) - self.mainMenu = GuiMainMenu(self, self.theProject) + self.treeMeta = GuiItemDetails(self) + self.projView = GuiOutline(self) + self.projMeta = GuiOutlineDetails(self) + self.mainMenu = GuiMainMenu(self) # Minor Gui Elements self.statusIcons = [] self.importIcons = [] # Assemble Main Window - self.treePane = QFrame() + self.treePane = QWidget() self.treeBox = QVBoxLayout() self.treeBox.setContentsMargins(0,0,0,0) self.treeBox.addWidget(self.treeView) self.treeBox.addWidget(self.treeMeta) self.treePane.setLayout(self.treeBox) - self.editPane = QFrame() + self.editPane = QWidget() self.docEdit = QVBoxLayout() self.docEdit.setContentsMargins(0,0,0,0) self.docEdit.setSpacing(2) @@ -121,7 +122,7 @@ class GuiMain(QMainWindow): self.docEdit.addWidget(self.docEditor) self.editPane.setLayout(self.docEdit) - self.viewPane = QFrame() + self.viewPane = QWidget() self.docView = QVBoxLayout() self.docView.setContentsMargins(0,0,0,0) self.docView.setSpacing(2) @@ -137,6 +138,8 @@ class GuiMain(QMainWindow): self.splitOutline = QSplitter(Qt.Vertical) self.splitOutline.addWidget(self.projView) + self.splitOutline.addWidget(self.projMeta) + self.splitOutline.setSizes(self.mainConf.outlnPanePos) self.tabWidget = QTabWidget() self.tabWidget.setTabPosition(QTabWidget.East) @@ -847,6 +850,7 @@ class GuiMain(QMainWindow): if not self.isZenMode: self.mainConf.setMainPanePos(self.splitMain.sizes()) self.mainConf.setDocPanePos(self.splitView.sizes()) + self.mainConf.setOutlinePanePos(self.splitOutline.sizes()) self.mainConf.saveConfig() self.reportConfErr() diff --git a/tests/reference/novelwriter.conf b/tests/reference/novelwriter.conf index f86bddc0..2c17e47f 100644 --- a/tests/reference/novelwriter.conf +++ b/tests/reference/novelwriter.conf @@ -1,5 +1,5 @@ [Main] -timestamp = 2020-05-21 14:52:36 +timestamp = 2020-06-03 20:42:03 theme = default syntax = default_light icons = typicons_grey_light @@ -13,6 +13,7 @@ treecols = 120, 30, 50 projcols = 140, 55, 140 mainpane = 300, 800 docpane = 400, 400 +outlinepane = 500, 150 fullscreen = False [Project]