From d4b26025edddd9bb667a5cbf95f7682a6e0deebd Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Sun, 20 Dec 2020 15:50:01 +0100 Subject: [PATCH] Add the basic structure for a novel tree --- nw/config.py | 29 +++-- nw/gui/__init__.py | 2 + nw/gui/noveltree.py | 119 ++++++++++++++++++ nw/gui/projtree.py | 6 +- nw/guimain.py | 58 +++++---- sample/nwProject.nwx | 8 +- tests/reference/baseConfig_novelwriter.conf | 1 + .../reference/guiPreferences_novelwriter.conf | 1 + tests/test_base_config.py | 13 ++ tests/test_gui_outline.py | 2 +- tests/test_gui_preferences.py | 9 +- 11 files changed, 206 insertions(+), 42 deletions(-) create mode 100644 nw/gui/noveltree.py diff --git a/nw/config.py b/nw/config.py index 9c232109..b5fb1ff8 100644 --- a/nw/config.py +++ b/nw/config.py @@ -95,14 +95,15 @@ class Config: self.lastNotes = "" # The latest release notes that have been shown ## Sizes - self.winGeometry = [1200, 650] - self.treeColWidth = [200, 50, 30] - self.projColWidth = [200, 60, 140] - self.mainPanePos = [300, 800] - self.docPanePos = [400, 400] - self.viewPanePos = [500, 150] - self.outlnPanePos = [500, 150] - self.isFullScreen = False + self.winGeometry = [1200, 650] + self.treeColWidth = [200, 50, 30] + self.novelColWidth = [200, 50] + self.projColWidth = [200, 60, 140] + self.mainPanePos = [300, 800] + self.docPanePos = [400, 400] + self.viewPanePos = [500, 150] + self.outlnPanePos = [500, 150] + self.isFullScreen = False ## Features self.hideVScroll = False # Hide vertical scroll bars on main widgets @@ -395,6 +396,9 @@ class Config: self.treeColWidth = self._parseLine( cnfParse, cnfSec, "treecols", self.CNF_I_LST, self.treeColWidth ) + self.novelColWidth = self._parseLine( + cnfParse, cnfSec, "novelcols", self.CNF_I_LST, self.novelColWidth + ) self.projColWidth = self._parseLine( cnfParse, cnfSec, "projcols", self.CNF_I_LST, self.projColWidth ) @@ -597,6 +601,7 @@ class Config: cnfParse.add_section(cnfSec) cnfParse.set(cnfSec, "geometry", self._packList(self.winGeometry)) cnfParse.set(cnfSec, "treecols", self._packList(self.treeColWidth)) + cnfParse.set(cnfSec, "novelcols", self._packList(self.novelColWidth)) cnfParse.set(cnfSec, "projcols", self._packList(self.projColWidth)) cnfParse.set(cnfSec, "mainpane", self._packList(self.mainPanePos)) cnfParse.set(cnfSec, "docpane", self._packList(self.docPanePos)) @@ -816,6 +821,11 @@ class Config: self.confChanged = True return True + def setNovelColWidths(self, colWidths): + self.novelColWidth = [int(x/self.guiScale) for x in colWidths] + self.confChanged = True + return True + def setProjColWidths(self, colWidths): self.projColWidth = [int(x/self.guiScale) for x in colWidths] self.confChanged = True @@ -872,6 +882,9 @@ class Config: def getTreeColWidths(self): return [int(x*self.guiScale) for x in self.treeColWidth] + def getNovelColWidths(self): + return [int(x*self.guiScale) for x in self.novelColWidth] + def getProjColWidths(self): return [int(x*self.guiScale) for x in self.projColWidth] diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index d40d84fc..6b71867d 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -9,6 +9,7 @@ from nw.gui.docviewer import GuiDocViewer, GuiDocViewDetails from nw.gui.itemdetails import GuiItemDetails from nw.gui.itemeditor import GuiItemEditor from nw.gui.mainmenu import GuiMainMenu +from nw.gui.noveltree import GuiNovelTree from nw.gui.outline import GuiOutline from nw.gui.outlinedetails import GuiOutlineDetails from nw.gui.preferences import GuiPreferences @@ -31,6 +32,7 @@ __all__ = [ "GuiItemDetails", "GuiItemEditor", "GuiMainMenu", + "GuiNovelTree", "GuiMainStatus", "GuiOutline", "GuiOutlineDetails", diff --git a/nw/gui/noveltree.py b/nw/gui/noveltree.py new file mode 100644 index 00000000..35dd6632 --- /dev/null +++ b/nw/gui/noveltree.py @@ -0,0 +1,119 @@ +# -*- coding: utf-8 -*- +"""novelWriter GUI Novel Tree + + novelWriter – GUI Novel Tree +============================== + Class holding the project's novel files tree view + + File History: + Created: 2020-12-20 [1.1a0] + + This file is a part of novelWriter + Copyright 2018–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 nw +import logging + +from PyQt5.QtCore import Qt, QSize +from PyQt5.QtWidgets import QTreeWidget + +logger = logging.getLogger(__name__) + +class GuiNovelTree(QTreeWidget): + + C_TITLE = 0 + C_WORDS = 1 + C_PAGES = 2 + + def __init__(self, theParent): + QTreeWidget.__init__(self, theParent) + + logger.debug("Initialising GuiNovelTree ...") + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme + self.theProject = theParent.theProject + self.theIndex = theParent.theIndex + + # Build GUI + iPx = self.theTheme.baseIconSize + self.setIconSize(QSize(iPx, iPx)) + self.setExpandsOnDoubleClick(True) + self.setIndentation(iPx) + self.setColumnCount(3) + self.setHeaderLabels(["Title", "Words", "Pages"]) + + # Get user's column width preferences for NAME and COUNT + treeColWidth = self.mainConf.getNovelColWidths() + if len(treeColWidth) <= 3: + for colN, colW in enumerate(treeColWidth): + self.setColumnWidth(colN, colW) + + # The last column should just auto-scale + self.resizeColumnToContents(self.C_PAGES) + + # Set custom settings + self.initTree() + + logger.debug("GuiNovelTree initialisation complete") + + # Internal Mapping + self.makeAlert = self.theParent.makeAlert + + return + + def initTree(self): + """Set or update tree widget settings. + """ + # Scroll bars + if self.mainConf.hideVScroll: + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + else: + self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) + + if self.mainConf.hideHScroll: + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + else: + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + + return + + ## + # Class Methods + ## + + def clearTree(self): + """Clear the GUI content and the related maps. + """ + self.clear() + return + + def getColumnSizes(self): + """Return the column widths for the tree columns. + """ + retVals = [ + self.columnWidth(self.C_TITLE), + self.columnWidth(self.C_WORDS), + ] + return retVals + + ## + # Slots + ## + +# END Class GuiNovelTree diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index 2c52c4cb..d9a03c04 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -357,9 +357,9 @@ class GuiProjectTree(QTreeWidget): """Return the column widths for the tree columns. """ retVals = [ - self.columnWidth(0), - self.columnWidth(1), - self.columnWidth(2), + self.columnWidth(self.C_NAME), + self.columnWidth(self.C_COUNT), + self.columnWidth(self.C_EXPORT), ] return retVals diff --git a/nw/guimain.py b/nw/guimain.py index 4361ae8f..82a7310a 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -42,9 +42,9 @@ from PyQt5.QtWidgets import ( from nw.gui import ( GuiAbout, GuiBuildNovel, GuiDocEditor, GuiDocMerge, GuiDocSplit, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiItemEditor, - GuiMainMenu, GuiMainStatus, GuiOutline, GuiOutlineDetails, GuiPreferences, - GuiProjectLoad, GuiProjectSettings, GuiProjectTree, GuiProjectWizard, - GuiTheme, GuiWritingStats + GuiMainMenu, GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails, + GuiPreferences, GuiProjectLoad, GuiProjectSettings, GuiProjectTree, + GuiProjectWizard, GuiTheme, GuiWritingStats ) from nw.core import NWProject, NWDoc, NWIndex from nw.constants import nwItemType, nwItemClass, nwAlert @@ -99,6 +99,7 @@ class GuiMain(QMainWindow): # Main GUI Elements self.statusBar = GuiMainStatus(self) self.treeView = GuiProjectTree(self) + self.novelView = GuiNovelTree(self) self.docEditor = GuiDocEditor(self) self.viewMeta = GuiDocViewDetails(self) self.docViewer = GuiDocViewer(self) @@ -111,11 +112,23 @@ class GuiMain(QMainWindow): self.statusIcons = [] self.importIcons = [] + # Project Tabs : Project / Novel + self.projTabs = QTabWidget() + self.projTabs.setTabPosition(QTabWidget.South) + self.projTabs.setStyleSheet("QTabWidget::pane {border: 0;};") + self.projTabs.addTab(self.treeView, "Project") + self.projTabs.addTab(self.novelView, "Novel") + + tabFont = self.projTabs.tabBar().font() + tabFont.setPointSize(round(0.9*self.theTheme.fontPointSize)) + self.projTabs.tabBar().setFont(tabFont) + # Project Tree View self.treePane = QWidget() self.treeBox = QVBoxLayout() self.treeBox.setContentsMargins(0, 0, 0, 0) - self.treeBox.addWidget(self.treeView) + self.treeBox.setSpacing(0) + self.treeBox.addWidget(self.projTabs) self.treeBox.addWidget(self.treeMeta) self.treePane.setLayout(self.treeBox) @@ -136,31 +149,31 @@ class GuiMain(QMainWindow): self.splitOutline.addWidget(self.projMeta) self.splitOutline.setSizes(self.mainConf.getOutlinePanePos()) - # Main Tabs : Edirot / Outline - self.tabWidget = QTabWidget() - self.tabWidget.setTabPosition(QTabWidget.East) - self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}") - self.tabWidget.addTab(self.splitDocs, "Editor") - self.tabWidget.addTab(self.splitOutline, "Outline") - self.tabWidget.currentChanged.connect(self._mainTabChanged) + # Main Tabs : Editor / Outline + self.mainTabs = QTabWidget() + self.mainTabs.setTabPosition(QTabWidget.East) + self.mainTabs.setStyleSheet("QTabWidget::pane {border: 0;}") + self.mainTabs.addTab(self.splitDocs, "Editor") + self.mainTabs.addTab(self.splitOutline, "Outline") + self.mainTabs.currentChanged.connect(self._mainTabChanged) # Splitter : Project Tree / Main Tabs xCM = self.mainConf.pxInt(4) self.splitMain = QSplitter(Qt.Horizontal) self.splitMain.setContentsMargins(xCM, xCM, xCM, xCM) self.splitMain.addWidget(self.treePane) - self.splitMain.addWidget(self.tabWidget) + self.splitMain.addWidget(self.mainTabs) self.splitMain.setSizes(self.mainConf.getMainPanePos()) # Indices of All Splitter Widgets self.idxTree = self.splitMain.indexOf(self.treePane) - self.idxMain = self.splitMain.indexOf(self.tabWidget) + self.idxMain = self.splitMain.indexOf(self.mainTabs) self.idxEditor = self.splitDocs.indexOf(self.docEditor) self.idxViewer = self.splitDocs.indexOf(self.splitView) self.idxViewDoc = self.splitView.indexOf(self.docViewer) self.idxViewMeta = self.splitView.indexOf(self.viewMeta) - self.idxTabEdit = self.tabWidget.indexOf(self.splitDocs) - self.idxTabProj = self.tabWidget.indexOf(self.splitOutline) + self.idxTabEdit = self.mainTabs.indexOf(self.splitDocs) + self.idxTabProj = self.mainTabs.indexOf(self.splitOutline) # Splitter Behaviour self.splitMain.setCollapsible(self.idxTree, False) @@ -355,7 +368,7 @@ class GuiMain(QMainWindow): self.theIndex.clearIndex() self.clearGUI() self.hasProject = False - self.tabWidget.setCurrentWidget(self.splitDocs) + self.mainTabs.setCurrentWidget(self.splitDocs) return saveOK @@ -371,7 +384,7 @@ class GuiMain(QMainWindow): return False # Switch main tab to editor view - self.tabWidget.setCurrentWidget(self.splitDocs) + self.mainTabs.setCurrentWidget(self.splitDocs) # Try to open the project if not self.theProject.openProject(projFile): @@ -490,7 +503,7 @@ class GuiMain(QMainWindow): return False self.closeDocument() - self.tabWidget.setCurrentWidget(self.splitDocs) + self.mainTabs.setCurrentWidget(self.splitDocs) if self.docEditor.loadText(tHandle, tLine): if changeFocus: self.docEditor.setFocus() @@ -575,7 +588,7 @@ class GuiMain(QMainWindow): return False # Make sure main tab is in Editor view - self.tabWidget.setCurrentWidget(self.splitDocs) + self.mainTabs.setCurrentWidget(self.splitDocs) logger.debug("Viewing document with handle %s" % tHandle) if self.docViewer.loadText(tHandle): @@ -797,7 +810,7 @@ class GuiMain(QMainWindow): return False logger.verbose("Forcing a rebuild of the Project Outline") - self.tabWidget.setCurrentWidget(self.splitOutline) + self.mainTabs.setCurrentWidget(self.splitOutline) self.projView.refreshTree(overRide=True) return True @@ -1022,6 +1035,7 @@ class GuiMain(QMainWindow): self.mainConf.setShowRefPanel(self.viewMeta.isVisible()) self.mainConf.setTreeColWidths(self.treeView.getColumnSizes()) + self.mainConf.setNovelColWidths(self.novelView.getColumnSizes()) if not self.mainConf.isFullScreen: self.mainConf.setWinSize(self.width(), self.height()) @@ -1078,7 +1092,7 @@ class GuiMain(QMainWindow): self.mainMenu.aFocusMode.setChecked(self.isFocusMode) if self.isFocusMode: logger.debug("Activating Focus Mode") - self.tabWidget.setCurrentWidget(self.splitDocs) + self.mainTabs.setCurrentWidget(self.splitDocs) else: logger.debug("Deactivating Focus Mode") @@ -1086,7 +1100,7 @@ class GuiMain(QMainWindow): self.treePane.setVisible(isVisible) self.statusBar.setVisible(isVisible) self.mainMenu.setVisible(isVisible) - self.tabWidget.tabBar().setVisible(isVisible) + self.mainTabs.tabBar().setVisible(isVisible) hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter self.docEditor.docFooter.setVisible(not hideDocFooter) diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 4a7f45d3..c954192c 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,13 +1,13 @@ - + Sample Project Sample Project Jane Smith Jay Doh - 810 - 153 - 39261 + 836 + 155 + 39747 False diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 5ff6f852..27f2062d 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -11,6 +11,7 @@ lastnotes = 1.0 [Sizes] geometry = 1200, 650 treecols = 200, 50, 30 +novelcols = 200, 50 projcols = 200, 60, 140 mainpane = 300, 800 docpane = 400, 400 diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf index cdbc5337..5b10156f 100644 --- a/tests/reference/guiPreferences_novelwriter.conf +++ b/tests/reference/guiPreferences_novelwriter.conf @@ -11,6 +11,7 @@ lastnotes = 1.0 [Sizes] geometry = 1100, 650 treecols = 120, 30, 50 +novelcols = 200, 50 projcols = 140, 55, 140 mainpane = 300, 800 docpane = 400, 400 diff --git a/tests/test_base_config.py b/tests/test_base_config.py index fe755646..bc9dd161 100644 --- a/tests/test_base_config.py +++ b/tests/test_base_config.py @@ -304,6 +304,19 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir): assert tmpConf.setTreeColWidths([200, 50, 30]) + # Novel Tree Columns + tmpConf.guiScale = 2.0 + assert tmpConf.setNovelColWidths([10, 20]) + assert tmpConf.getNovelColWidths() == [10, 20] + assert tmpConf.novelColWidth == [5, 10] + + tmpConf.guiScale = 1.0 + assert tmpConf.setNovelColWidths([10, 20]) + assert tmpConf.getNovelColWidths() == [10, 20] + assert tmpConf.novelColWidth == [10, 20] + + assert tmpConf.setNovelColWidths([200, 50]) + # Project Settings Tree Columns tmpConf.guiScale = 2.0 assert tmpConf.setProjColWidths([10, 20, 30]) diff --git a/tests/test_gui_outline.py b/tests/test_gui_outline.py index cf93c111..906481c5 100644 --- a/tests/test_gui_outline.py +++ b/tests/test_gui_outline.py @@ -24,7 +24,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, nwLipsum): nwGUI.mainConf.lastPath = nwLipsum nwGUI.rebuildIndex() - nwGUI.tabWidget.setCurrentIndex(nwGUI.idxTabProj) + nwGUI.mainTabs.setCurrentIndex(nwGUI.idxTabProj) assert nwGUI.projView.topLevelItemCount() > 0 diff --git a/tests/test_gui_preferences.py b/tests/test_gui_preferences.py index 4c8b17ea..9c76d830 100644 --- a/tests/test_gui_preferences.py +++ b/tests/test_gui_preferences.py @@ -220,10 +220,11 @@ def testGuiPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf") copyfile(projFile, testFile) ignoreLines = [ - 2, # Timestamp - 9, # Release Notes - 12, 13, 14, 15, 16, 17, 18, # Window sizes - 7, 28, # Fonts (depends on system default) + 2, # Timestamp + 9, # Release Notes + 12, 13, 14, 15, # Window sizes + 16, 17, 18, 19, # Window sizes + 7, 29, # Fonts (depends on system default) ] assert cmpFiles(testFile, compFile, ignoreLines)