diff --git a/novelwriter/gui/components.py b/novelwriter/gui/components.py
new file mode 100644
index 00000000..35234d40
--- /dev/null
+++ b/novelwriter/gui/components.py
@@ -0,0 +1,165 @@
+"""
+novelWriter – GUI Components Module
+===================================
+A module of various small GUI components
+
+File History:
+Created: 2020-05-17 [0.5.1] StatusLED
+Created: 2022-11-17 [2.0rc2] NovelSelector
+
+This file is a part of novelWriter
+Copyright 2018–2022, 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
+
+from PyQt5.QtGui import QPainter
+from PyQt5.QtCore import pyqtSignal, pyqtSlot
+from PyQt5.QtWidgets import QAbstractButton, QComboBox
+
+from novelwriter.enum import nwItemClass
+from novelwriter.constants import nwLabels
+
+logger = logging.getLogger(__name__)
+
+
+class NovelSelector(QComboBox):
+
+ novelSelectionChanged = pyqtSignal(str)
+
+ def __init__(self, parent, project, theme):
+ super().__init__(parent=parent)
+
+ self._project = project
+ self._theme = theme
+ self._blockSignal = False
+ self.currentIndexChanged.connect(self._indexChanged)
+
+ return
+
+ @property
+ def handle(self):
+ return self.currentData()
+
+ def setHandle(self, tHandle, blockSignal=True):
+ """Set the currently selected handle.
+ """
+ self._blockSignal = blockSignal
+ if tHandle is None:
+ index = self.count() - 1
+ else:
+ index = self.findData(tHandle)
+ if index >= 0:
+ self.setCurrentIndex(index)
+ self._blockSignal = False
+ return
+
+ def updateList(self, includeAll=False, prefix=None):
+ """Rebuild the list of novel items.
+ """
+ self._blockSignal = True
+ self.clear()
+
+ icon = self._theme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
+ handle = self.currentData()
+ for tHandle, nwItem in self._project.tree.iterRoots(nwItemClass.NOVEL):
+ if prefix:
+ name = prefix.format(nwItem.itemName)
+ self.addItem(name, tHandle)
+ else:
+ name = nwItem.itemName
+ self.addItem(icon, nwItem.itemName, 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.setHandle(handle)
+ self.setEnabled(self.count() > 1)
+ self._blockSignal = False
+
+ return
+
+ ##
+ # Private Slots
+ ##
+
+ @pyqtSlot(int)
+ def _indexChanged(self, index):
+ if not self._blockSignal:
+ self.novelSelectionChanged.emit(self.currentData())
+ return
+
+# END Class NovelSelector
+
+
+class StatusLED(QAbstractButton):
+
+ S_NONE = 0
+ S_BAD = 1
+ S_GOOD = 2
+
+ def __init__(self, colNone, colGood, colBad, sW, sH, parent=None):
+ super().__init__(parent=parent)
+
+ self._colNone = colNone
+ self._colGood = colGood
+ self._colBad = colBad
+ self._theCol = colNone
+
+ self.setFixedWidth(sW)
+ self.setFixedHeight(sH)
+
+ return
+
+ ##
+ # Setters
+ ##
+
+ def setState(self, theState):
+ """Set the colour state.
+ """
+ if theState == self.S_GOOD:
+ self._theCol = self._colGood
+ elif theState == self.S_BAD:
+ self._theCol = self._colBad
+ else:
+ self._theCol = self._colNone
+
+ self.update()
+
+ return
+
+ ##
+ # Events
+ ##
+
+ def paintEvent(self, _):
+ """Drawing the LED.
+ """
+ qPalette = self.palette()
+ qPaint = QPainter(self)
+ qPaint.setRenderHint(QPainter.Antialiasing, True)
+ qPaint.setPen(qPalette.dark().color())
+ qPaint.setBrush(self._theCol)
+ qPaint.setOpacity(1.0)
+ qPaint.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
+ return
+
+# END Class StatusLED
diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py
index cc809e17..d7b38a21 100644
--- a/novelwriter/gui/noveltree.py
+++ b/novelwriter/gui/noveltree.py
@@ -31,16 +31,17 @@ import novelwriter
from enum import Enum
from time import time
-from PyQt5.QtGui import QPalette
+from PyQt5.QtGui import QFont, QPalette
from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import (
- QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel,
- QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget, QTreeWidgetItem,
+ QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QMenu,
+ QSizePolicy, QToolButton, QToolTip, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget
)
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
+from novelwriter.gui.components import NovelSelector
logger = logging.getLogger(__name__)
@@ -199,10 +200,15 @@ class GuiNovelToolBar(QWidget):
self.setContentsMargins(0, 0, 0, 0)
self.setAutoFillBackground(True)
- # Widget Label
- self.viewLabel = QLabel("%s" % self.tr("Novel Outline"))
- self.viewLabel.setContentsMargins(0, 0, 0, 0)
- self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ # Novel Selector
+ selFont = self.font()
+ selFont.setWeight(QFont.Bold)
+ self.novelPrefix = self.tr("Outline of {0}")
+ self.novelValue = NovelSelector(self, self.theProject, self.mainTheme)
+ self.novelValue.setFont(selFont)
+ self.novelValue.setMinimumWidth(self.mainConf.pxInt(150))
+ self.novelValue.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
+ self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
# Refresh Button
self.tbRefresh = QToolButton(self)
@@ -210,17 +216,6 @@ class GuiNovelToolBar(QWidget):
self.tbRefresh.setIconSize(QSize(iPx, iPx))
self.tbRefresh.clicked.connect(self._refreshNovelTree)
- # Novel Root Menu
- self.mRoot = QMenu()
- self.gRoot = QActionGroup(self.mRoot)
- self.aRoot = {}
-
- self.tbRoot = QToolButton(self)
- self.tbRoot.setToolTip(self.tr("Novel Root"))
- self.tbRoot.setIconSize(QSize(iPx, iPx))
- self.tbRoot.setMenu(self.mRoot)
- self.tbRoot.setPopupMode(QToolButton.InstantPopup)
-
# More Options Menu
self.mMore = QMenu()
@@ -240,9 +235,8 @@ class GuiNovelToolBar(QWidget):
# Assemble
self.outerBox = QHBoxLayout()
- self.outerBox.addWidget(self.viewLabel)
+ self.outerBox.addWidget(self.novelValue)
self.outerBox.addWidget(self.tbRefresh)
- self.outerBox.addWidget(self.tbRoot)
self.outerBox.addWidget(self.tbMore)
self.outerBox.setContentsMargins(mPx, mPx, 0, mPx)
self.outerBox.setSpacing(0)
@@ -264,7 +258,6 @@ class GuiNovelToolBar(QWidget):
"""
# Icons
self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh"))
- self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]))
self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
qPalette = self.palette()
@@ -279,39 +272,42 @@ class GuiNovelToolBar(QWidget):
).format(self.mainConf.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
self.tbRefresh.setStyleSheet(buttonStyle)
- self.tbRoot.setStyleSheet(buttonStyle)
self.tbMore.setStyleSheet(buttonStyle)
+ self.novelValue.setStyleSheet(
+ "QComboBox {border-style: none; padding-left: 0;} "
+ "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("")
+
return
def clearContent(self):
"""Run clearing project tasks.
"""
- self.mRoot.clear()
- self.aRoot = {}
+ self.novelValue.clear()
+ self.novelValue.setToolTip("")
return
def buildNovelRootMenu(self):
"""Build the novel root menu.
"""
- self.mRoot.clear()
- self.aRoot = {}
- for n, (tHandle, nwItem) in enumerate(self.theProject.tree.iterRoots(nwItemClass.NOVEL)):
- aRoot = self.mRoot.addAction(nwItem.itemName)
- aRoot.setData(tHandle)
- aRoot.setCheckable(True)
- aRoot.triggered.connect(lambda n, tHandle=tHandle: self.setCurrentRoot(tHandle))
- self.gRoot.addAction(aRoot)
- self.aRoot[tHandle] = aRoot
-
+ 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("")
return
def setCurrentRoot(self, rootHandle):
"""Set the current active root handle.
"""
- if rootHandle in self.aRoot:
- self.aRoot[rootHandle].setChecked(True)
- self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
+ self.novelValue.setHandle(rootHandle)
+ self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
return
def setLastColType(self, colType, doRefresh=True):
diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py
index ef898237..9cfd3d86 100644
--- a/novelwriter/gui/outline.py
+++ b/novelwriter/gui/outline.py
@@ -37,9 +37,9 @@ from PyQt5.QtCore import (
Qt, pyqtSignal, pyqtSlot, QSize, QT_TRANSLATE_NOOP
)
from PyQt5.QtWidgets import (
- QAbstractItemView, QAction, QComboBox, QFrame, QGridLayout, QGroupBox,
- QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar,
- QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
+ QAbstractItemView, QAction, QFrame, QGridLayout, QGroupBox, QHBoxLayout,
+ QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar, QToolButton,
+ QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
)
from novelwriter.enum import (
@@ -47,6 +47,7 @@ from novelwriter.enum import (
)
from novelwriter.common import checkInt
from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
+from novelwriter.gui.components import NovelSelector
logger = logging.getLogger(__name__)
@@ -233,9 +234,9 @@ class GuiOutlineToolBar(QToolBar):
self.novelLabel = QLabel(self.tr("Outline of"))
self.novelLabel.setContentsMargins(0, 0, mPx, 0)
- self.novelValue = QComboBox(self)
+ self.novelValue = NovelSelector(self, self.theProject, self.mainTheme)
self.novelValue.setMinimumWidth(self.mainConf.pxInt(200))
- self.novelValue.currentIndexChanged.connect(self._novelValueChanged)
+ self.novelValue.novelSelectionChanged.connect(self._novelValueChanged)
# Actions
self.aRefresh = QAction(self.tr("Refresh"), self)
@@ -274,31 +275,22 @@ class GuiOutlineToolBar(QToolBar):
"""
self.setStyleSheet("QToolBar {border: 0px;}")
+ self.novelValue.updateList(includeAll=True)
self.aRefresh.setIcon(self.mainTheme.getIcon("refresh"))
self.tbColumns.setIcon(self.mainTheme.getIcon("menu"))
return
def populateNovelList(self):
- """Fill the novel combo box with a list of all novel folders.
+ """Relaod the content of the novel list.
"""
- 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)
- self.novelValue.insertSeparator(self.novelValue.count())
- self.novelValue.addItem(tIcon, self.tr("All Novel Folders"), "")
+ self.novelValue.updateList(includeAll=True)
return
def setCurrentRoot(self, rootHandle):
"""Set the current active root handle.
"""
- if rootHandle is None:
- rootIdx = self.novelValue.count() - 1
- else:
- rootIdx = self.novelValue.findData(rootHandle)
- if rootIdx >= 0:
- self.novelValue.setCurrentIndex(rootIdx)
+ self.novelValue.setHandle(rootHandle)
return
def setColumnHiddenState(self, hiddenState):
@@ -311,19 +303,18 @@ class GuiOutlineToolBar(QToolBar):
# Private Slots
##
- @pyqtSlot(int)
- def _novelValueChanged(self, index):
+ @pyqtSlot(str)
+ def _novelValueChanged(self, tHandle):
"""Emit a signal containing the handle of the selected item.
"""
- if index >= 0:
- self.loadNovelRootRequest.emit(self.novelValue.currentData())
+ self.loadNovelRootRequest.emit(tHandle)
return
@pyqtSlot()
def _refreshRequested(self):
"""Emit a signal containing the handle of the selected item.
"""
- self.loadNovelRootRequest.emit(self.novelValue.currentData())
+ self.loadNovelRootRequest.emit(self.novelValue.handle)
return
# END Class GuiOutlineToolBar
@@ -679,6 +670,7 @@ class GuiOutlineTree(QTreeWidget):
if they are hidden. This ensures that showing and hiding columns
is fast and doesn't require a rebuild of the tree.
"""
+ logger.debug("Rebuilding Outline tree")
self.clear()
if self._firstView:
diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py
index 9dcb97af..a5824214 100644
--- a/novelwriter/gui/statusbar.py
+++ b/novelwriter/gui/statusbar.py
@@ -30,10 +30,11 @@ import novelwriter
from time import time
from PyQt5.QtCore import pyqtSlot, QLocale
-from PyQt5.QtGui import QColor, QPainter
-from PyQt5.QtWidgets import qApp, QStatusBar, QLabel, QAbstractButton
+from PyQt5.QtGui import QColor
+from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
from novelwriter.common import formatTime
+from novelwriter.gui.components import StatusLED
logger = logging.getLogger(__name__)
@@ -246,59 +247,3 @@ class GuiMainStatus(QStatusBar):
return
# END Class GuiMainStatus
-
-
-class StatusLED(QAbstractButton):
-
- S_NONE = 0
- S_BAD = 1
- S_GOOD = 2
-
- def __init__(self, colNone, colGood, colBad, sW, sH, parent=None):
- super().__init__(parent=parent)
-
- self._colNone = colNone
- self._colGood = colGood
- self._colBad = colBad
- self._theCol = colNone
-
- self.setFixedWidth(sW)
- self.setFixedHeight(sH)
-
- return
-
- ##
- # Setters
- ##
-
- def setState(self, theState):
- """Set the colour state.
- """
- if theState == self.S_GOOD:
- self._theCol = self._colGood
- elif theState == self.S_BAD:
- self._theCol = self._colBad
- else:
- self._theCol = self._colNone
-
- self.update()
-
- return
-
- ##
- # Events
- ##
-
- def paintEvent(self, _):
- """Drawing the LED.
- """
- qPalette = self.palette()
- qPaint = QPainter(self)
- qPaint.setRenderHint(QPainter.Antialiasing, True)
- qPaint.setPen(qPalette.dark().color())
- qPaint.setBrush(self._theCol)
- qPaint.setOpacity(1.0)
- qPaint.drawEllipse(1, 1, self.width() - 2, self.height() - 2)
- return
-
-# END Class StatusLED