diff --git a/i18n/nw_base.ts b/i18n/nw_base.ts index 310b02dc..ddb5b171 100644 --- a/i18n/nw_base.ts +++ b/i18n/nw_base.ts @@ -2063,38 +2063,38 @@ GuiMainStatus - - + + None - + Editor - + Project - + Session Time - + Words: {0} ({1}) - + Project word count (session change) - + Novel word count (session change) @@ -2102,47 +2102,47 @@ GuiNovelToolBar - - Novel Outline + + Outline of {0} - - Refresh - - - - + Novel Root - + + Refresh + + + + Last Column - + Hidden - + Point of View Character - + Focus Character - + Novel Plot - + More Options @@ -2150,7 +2150,7 @@ GuiNovelTree - + No meta data @@ -2158,65 +2158,65 @@ GuiOutlineDetails - - - - + + + + Title - + Chapter - + Scene - + Section - + Document - + Status - + Characters - + Words - + Paragraphs - + Synopsis - + Title Details - + Reference Tags @@ -2224,7 +2224,7 @@ GuiOutlineHeaderMenu - + Select Columns @@ -2232,20 +2232,15 @@ GuiOutlineToolBar - + Outline of - + Refresh - - - All Novel Folders - - GuiPreferences @@ -2923,17 +2918,17 @@ GuiProjectDetails - + Project Details - + Overview - + Contents @@ -2941,72 +2936,72 @@ GuiProjectDetailsContents - + Table of Contents - + Title - + Words - + Pages - + Page - + Progress - + Typical word count for a 5 by 8 inch book page with 11 pt font is 350. - + Start counting page numbers from this page. - + Assume a new chapter or partition always start on an odd numbered page. - + Words per page - + Count pages from - + Clear double pages - + END - + Untitled @@ -3014,42 +3009,42 @@ GuiProjectDetailsMain - + Working Title: {0} - + By {0} - + Words - + Chapters - + Scenes - + Revisions - + Editing Time - + Path @@ -4004,6 +3999,14 @@ + + NovelSelector + + + All Novel Folders + + + ProjWizardCustomPage diff --git a/novelwriter/dialogs/projdetails.py b/novelwriter/dialogs/projdetails.py index 9f3999a4..00d3d4df 100644 --- a/novelwriter/dialogs/projdetails.py +++ b/novelwriter/dialogs/projdetails.py @@ -30,15 +30,14 @@ import novelwriter from PyQt5.QtCore import Qt, QSize, pyqtSlot from PyQt5.QtGui import QFont from PyQt5.QtWidgets import ( - QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QHBoxLayout, - QLabel, QLineEdit, QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, - QWidget + QAbstractItemView, QDialogButtonBox, QGridLayout, QHBoxLayout, QLabel, + QLineEdit, QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) -from novelwriter.enum import nwItemClass from novelwriter.common import numberToRoman from novelwriter.custom import PagedDialog, QSwitch -from novelwriter.constants import nwLabels, nwUnicode +from novelwriter.constants import nwUnicode +from novelwriter.gui.components import NovelSelector logger = logging.getLogger(__name__) @@ -297,9 +296,9 @@ class GuiProjectDetailsContents(QWidget): self.tocLabel = QLabel("%s" % self.tr("Table of Contents")) - self.novelValue = QComboBox(self) + self.novelValue = NovelSelector(self, self.theProject, self.mainGui) self.novelValue.setMinimumWidth(self.mainConf.pxInt(200)) - self.novelValue.currentIndexChanged.connect(self._novelValueChanged) + self.novelValue.novelSelectionChanged.connect(self._novelValueChanged) self.headBox = QHBoxLayout() self.headBox.addWidget(self.tocLabel) @@ -431,10 +430,9 @@ class GuiProjectDetailsContents(QWidget): """Populate the tree. """ self._currentRoot = None - self._populateNovelList() - - rootHandle = self.novelValue.currentData() - self._prepareData(rootHandle) + self.novelValue.updateList() + self.novelValue.setHandle(self.novelValue.firstHandle) + self._prepareData(self.novelValue.firstHandle) self._populateTree() return @@ -443,17 +441,6 @@ class GuiProjectDetailsContents(QWidget): # Internal Functions ## - def _populateNovelList(self): - """Fill the novel combo box with a list of all novel folders. - """ - self.novelValue.clear() - - tIcon = self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]) - for tHandle, nwItem in self.theProject.tree.iterRoots(nwItemClass.NOVEL): - self.novelValue.addItem(tIcon, nwItem.itemName, tHandle) - - return - def _prepareData(self, rootHandle): """Extract the information from the project index. """ @@ -466,15 +453,14 @@ class GuiProjectDetailsContents(QWidget): # Slots ## - @pyqtSlot() - def _novelValueChanged(self): + @pyqtSlot(str) + def _novelValueChanged(self, tHandle): """Refresh the tree with another root item. """ - rootHandle = self.novelValue.currentData() - if rootHandle != self._currentRoot: - self._prepareData(rootHandle) + if tHandle != self._currentRoot: + self._prepareData(tHandle) self._populateTree() - self._currentRoot = rootHandle + self._currentRoot = self.novelValue.handle return @pyqtSlot() diff --git a/novelwriter/gui/components.py b/novelwriter/gui/components.py index 35234d40..9b1cf2d4 100644 --- a/novelwriter/gui/components.py +++ b/novelwriter/gui/components.py @@ -40,20 +40,35 @@ class NovelSelector(QComboBox): novelSelectionChanged = pyqtSignal(str) - def __init__(self, parent, project, theme): + def __init__(self, parent, project, mainGui): super().__init__(parent=parent) + self._mainGui = mainGui self._project = project - self._theme = theme + self._theme = mainGui.mainTheme self._blockSignal = False + self._firstHandle = None + self.currentIndexChanged.connect(self._indexChanged) return + ## + # Properties + ## + @property def handle(self): return self.currentData() + @property + def firstHandle(self): + return self._firstHandle + + ## + # Methods + ## + def setHandle(self, tHandle, blockSignal=True): """Set the currently selected handle. """ @@ -71,6 +86,7 @@ class NovelSelector(QComboBox): """Rebuild the list of novel items. """ self._blockSignal = True + self._firstHandle = None self.clear() icon = self._theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]) @@ -82,13 +98,12 @@ class NovelSelector(QComboBox): else: name = nwItem.itemName self.addItem(icon, nwItem.itemName, tHandle) + if self._firstHandle is None: + self._firstHandle = tHandle if includeAll: self.insertSeparator(self.count()) - if prefix: - self.addItem(prefix.format(self.tr("All Novel Folders")), "") - else: - self.addItem(icon, self.tr("All Novel Folders"), "") + self.addItem(icon, self.tr("All Novel Folders"), "") self.setHandle(handle) self.setEnabled(self.count() > 1) @@ -102,6 +117,8 @@ class NovelSelector(QComboBox): @pyqtSlot(int) def _indexChanged(self, index): + """Re-emit the change of selected novel signal, unless blocked. + """ if not self._blockSignal: self.novelSelectionChanged.emit(self.currentData()) return diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index d7b38a21..25cdc5f9 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -191,6 +191,7 @@ class GuiNovelToolBar(QWidget): self.mainConf = novelwriter.CONFIG self.novelView = novelView + self.mainGui = novelView.mainGui self.theProject = novelView.mainGui.theProject self.mainTheme = novelView.mainGui.mainTheme @@ -204,12 +205,17 @@ class GuiNovelToolBar(QWidget): selFont = self.font() selFont.setWeight(QFont.Bold) self.novelPrefix = self.tr("Outline of {0}") - self.novelValue = NovelSelector(self, self.theProject, self.mainTheme) + self.novelValue = NovelSelector(self, self.theProject, self.mainGui) self.novelValue.setFont(selFont) self.novelValue.setMinimumWidth(self.mainConf.pxInt(150)) self.novelValue.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot) + self.tbNovel = QToolButton(self) + self.tbNovel.setToolTip(self.tr("Novel Root")) + self.tbNovel.setIconSize(QSize(iPx, iPx)) + self.tbNovel.clicked.connect(self._openNovelSelector) + # Refresh Button self.tbRefresh = QToolButton(self) self.tbRefresh.setToolTip(self.tr("Refresh")) @@ -236,6 +242,7 @@ class GuiNovelToolBar(QWidget): # Assemble self.outerBox = QHBoxLayout() self.outerBox.addWidget(self.novelValue) + self.outerBox.addWidget(self.tbNovel) self.outerBox.addWidget(self.tbRefresh) self.outerBox.addWidget(self.tbMore) self.outerBox.setContentsMargins(mPx, mPx, 0, mPx) @@ -257,6 +264,7 @@ class GuiNovelToolBar(QWidget): """Update theme elements. """ # Icons + self.tbNovel.setIcon(self.mainTheme.getIcon("cls_novel")) self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh")) self.tbMore.setIcon(self.mainTheme.getIcon("menu")) @@ -271,6 +279,7 @@ class GuiNovelToolBar(QWidget): "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}" ).format(self.mainConf.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue()) + self.tbNovel.setStyleSheet(buttonStyle) self.tbRefresh.setStyleSheet(buttonStyle) self.tbMore.setStyleSheet(buttonStyle) @@ -279,10 +288,7 @@ class GuiNovelToolBar(QWidget): "QComboBox::drop-down {border-style: none}" ) self.novelValue.updateList(prefix=self.novelPrefix) - if self.novelValue.count() > 1: - self.novelValue.setToolTip(self.tr("Click to change root folder")) - else: - self.novelValue.setToolTip("") + self.tbNovel.setVisible(self.novelValue.count() > 1) return @@ -297,10 +303,7 @@ class GuiNovelToolBar(QWidget): """Build the novel root menu. """ self.novelValue.updateList(prefix=self.novelPrefix) - if self.novelValue.count() > 1: - self.novelValue.setToolTip(self.tr("Click to change root folder")) - else: - self.novelValue.setToolTip("") + self.tbNovel.setVisible(self.novelValue.count() > 1) return def setCurrentRoot(self, rootHandle): @@ -321,6 +324,13 @@ class GuiNovelToolBar(QWidget): # Private Slots ## + @pyqtSlot() + def _openNovelSelector(self): + """Trigger the dropdown list of the novel selector. + """ + self.novelValue.showPopup() + return + @pyqtSlot() def _refreshNovelTree(self): """Rebuild the current tree. @@ -519,13 +529,14 @@ class GuiNovelTree(QTreeWidget): return def getSelectedHandle(self): - """Get the currently selected handle. If multiple items are - selected, return the first. + """Get the currently selected or active handle. If multiple + items are selected, return the first. """ - selItem = self.selectedItems() - if selItem: - tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE) - sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE) + selList = self.selectedItems() + trItem = selList[0] if selList else self.currentItem() + if isinstance(trItem, QTreeWidgetItem): + tHandle = trItem.data(self.C_TITLE, self.D_HANDLE) + sTitle = trItem.data(self.C_TITLE, self.D_TITLE) return tHandle, sTitle return None, None diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 9cfd3d86..16e8c0fe 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -234,7 +234,7 @@ class GuiOutlineToolBar(QToolBar): self.novelLabel = QLabel(self.tr("Outline of")) self.novelLabel.setContentsMargins(0, 0, mPx, 0) - self.novelValue = NovelSelector(self, self.theProject, self.mainTheme) + self.novelValue = NovelSelector(self, self.theProject, self.mainGui) self.novelValue.setMinimumWidth(self.mainConf.pxInt(200)) self.novelValue.novelSelectionChanged.connect(self._novelValueChanged) diff --git a/tests/test_dialogs/test_dlg_projdetails.py b/tests/test_dialogs/test_dlg_projdetails.py index e8d6053c..92df7f39 100644 --- a/tests/test_dialogs/test_dlg_projdetails.py +++ b/tests/test_dialogs/test_dlg_projdetails.py @@ -93,6 +93,11 @@ def testDlgProjDetails_Dialog(qtbot, nwGUI, nwLipsum): assert tocTree.topLevelItem(i).text(tocTab.C_PAGES) == thePages[i] assert tocTree.topLevelItem(i).text(tocTab.C_PAGE) == thePage[i] + # Re-populate + assert tocTab._currentRoot is None + tocTab._novelValueChanged("7a992350f3eb6") # Not a root + assert tocTab._currentRoot == "b3643d0f92e32" # The actual novel root + # qtbot.stop() # Clean Up