Add a novel selector class (#1252)
This commit is contained in:
@@ -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 <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
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
|
||||
@@ -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("<b>%s</b>" % 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):
|
||||
|
||||
+15
-23
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user