Sidebar cleanup and minor fixes (#1520)

This commit is contained in:
Veronica Berglyd Olsen
2023-09-05 21:26:20 +01:00
committed by GitHub
5 changed files with 149 additions and 107 deletions
+3 -4
View File
@@ -460,9 +460,8 @@ class GuiDocEditor(QTextEdit):
return True
def updateTagHighLighting(self):
"""Rerun the syntax highlighter on all meta data lines.
"""
def updateTagHighLighting(self) -> None:
"""Rerun the syntax highlighter on all meta data lines."""
self.highLight.rehighlightByType(GuiDocHighlighter.BLOCK_META)
return
@@ -672,7 +671,7 @@ class GuiDocEditor(QTextEdit):
self._nwItem.setCursorPos(cursPos)
return
def setCursorLine(self, line: int) -> bool:
def setCursorLine(self, line: int | None) -> bool:
"""Move the cursor to a given line in the document."""
if not isinstance(line, int):
return False
+3 -3
View File
@@ -329,7 +329,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setFormat(0, 4, self._hStyles["header4h"])
self.setFormat(4, len(text), self._hStyles["header4"])
if text.startswith("#! "): # Title
elif text.startswith("#! "): # Title
self.setFormat(0, 2, self._hStyles["header1h"])
self.setFormat(2, len(text), self._hStyles["header1"])
@@ -367,7 +367,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
self.setFormat(tLen-1, tLen, self._hStyles["keyword"])
return
# Regular text
# Regular Text
self.setCurrentBlockState(self.BLOCK_TEXT)
for rX, xFmt in self.rxRules:
rxItt = rX.globalMatch(text, 0)
@@ -389,7 +389,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
while rxSpell.hasNext():
rxMatch = rxSpell.next()
if not SHARED.spelling.checkWord(rxMatch.captured(0)):
if rxMatch.captured(0).isupper() or rxMatch.captured(0).isnumeric():
if not rxMatch.captured(0).isalpha() or rxMatch.captured(0).isupper():
continue
xPos = rxMatch.capturedStart(0)
xLen = rxMatch.capturedLength(0)
+100 -68
View File
@@ -25,99 +25,96 @@ from __future__ import annotations
import logging
from PyQt5.QtCore import Qt, QSize, pyqtSignal
from PyQt5.QtWidgets import (
QToolBar, QWidget, QSizePolicy, QAction, QMenu, QToolButton
)
from typing import TYPE_CHECKING
from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
from PyQt5.QtGui import QPalette
from PyQt5.QtWidgets import QMenu, QToolButton, QVBoxLayout, QWidget
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwView
if TYPE_CHECKING: # pragma: no cover
from novelwriter.guimain import GuiMain
logger = logging.getLogger(__name__)
class GuiSideBar(QToolBar):
class GuiSideBar(QWidget):
viewChangeRequested = pyqtSignal(nwView)
def __init__(self, mainGui):
def __init__(self, mainGui: GuiMain) -> None:
super().__init__(parent=mainGui)
logger.debug("Create: GuiSideBar")
self.mainGui = mainGui
# Style
iPx = CONFIG.pxInt(22)
mPx = CONFIG.pxInt(60)
lblFont = SHARED.theme.guiFont
lblFont.setPointSizeF(0.65*SHARED.theme.fontPointSize)
self.setMovable(False)
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.setIconSize(QSize(iPx, iPx))
self.setMaximumWidth(mPx)
iPx = CONFIG.pxInt(24)
iconSize = QSize(iPx, iPx)
self.setContentsMargins(0, 0, 0, 0)
stretch = QWidget(self)
stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
# Buttons
self.tbProject = QToolButton(self)
self.tbProject.setToolTip(self.tr("Project Tree View"))
self.tbProject.setIconSize(iconSize)
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
# Actions
self.aProject = QAction(self.tr("Project"), self)
self.aProject.setFont(lblFont)
self.aProject.setToolTip(self.tr("Project Tree View"))
self.aProject.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
self.tbNovel = QToolButton(self)
self.tbNovel.setToolTip(self.tr("Novel Tree View"))
self.tbNovel.setIconSize(iconSize)
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
self.aNovel = QAction(self.tr("Novel"), self)
self.aNovel.setFont(lblFont)
self.aNovel.setToolTip(self.tr("Novel Tree View"))
self.aNovel.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
self.tbOutline = QToolButton(self)
self.tbOutline.setToolTip(self.tr("Novel Outline View"))
self.tbOutline.setIconSize(iconSize)
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
self.aOutline = QAction(self.tr("Outline"), self)
self.aOutline.setFont(lblFont)
self.aOutline.setToolTip(self.tr("Novel Outline View"))
self.aOutline.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
self.tbBuild = QToolButton(self)
self.tbBuild.setToolTip(self.tr("Build Manuscript"))
self.tbBuild.setIconSize(iconSize)
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
self.aBuild = QAction(self.tr("Build"), self)
self.aBuild.setFont(lblFont)
self.aBuild.setToolTip(self.tr("Build Manuscript"))
self.aBuild.triggered.connect(lambda: self.mainGui.showBuildManuscriptDialog())
self.tbDetails = QToolButton(self)
self.tbDetails.setToolTip(self.tr("Project Details"))
self.tbDetails.setIconSize(iconSize)
self.tbDetails.clicked.connect(self.mainGui.showProjectDetailsDialog)
self.aDetails = QAction(self.tr("Details"), self)
self.aDetails.setFont(lblFont)
self.aDetails.setToolTip(self.tr("Project Details"))
self.aDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog())
self.aStats = QAction(self.tr("Stats"), self)
self.aStats.setFont(lblFont)
self.aStats.setToolTip(self.tr("Writing Statistics"))
self.aStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog())
self.tbStats = QToolButton(self)
self.tbStats.setToolTip(self.tr("Writing Statistics"))
self.tbStats.setIconSize(iconSize)
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
# Settings Menu
self.mSettings = QMenu()
self.tbSettings = QToolButton(self)
self.tbSettings.setToolTip(self.tr("Settings"))
self.tbSettings.setIconSize(iconSize)
self.tbSettings.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
self.mSettings = _PopRightMenu(self.tbSettings)
self.mSettings.addAction(self.mainGui.mainMenu.aEditWordList)
self.mSettings.addAction(self.mainGui.mainMenu.aProjectSettings)
self.mSettings.addSeparator()
self.mSettings.addAction(self.mainGui.mainMenu.aPreferences)
self.tbSettings = QToolButton(self)
self.tbSettings.setFont(lblFont)
self.tbSettings.setText(self.tr("Settings"))
self.tbSettings.setMenu(self.mSettings)
self.tbSettings.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.tbSettings.setPopupMode(QToolButton.InstantPopup)
# Assemble
self.addAction(self.aProject)
self.addAction(self.aNovel)
self.addAction(self.aOutline)
self.addAction(self.aBuild)
self.addWidget(stretch)
self.addAction(self.aDetails)
self.addAction(self.aStats)
self.addWidget(self.tbSettings)
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.tbProject)
self.outerBox.addWidget(self.tbNovel)
self.outerBox.addWidget(self.tbOutline)
self.outerBox.addWidget(self.tbBuild)
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbSettings)
self.outerBox.setContentsMargins(0, 0, CONFIG.pxInt(2), 0)
self.outerBox.setSpacing(CONFIG.pxInt(4))
self.setLayout(self.outerBox)
self.updateTheme()
@@ -125,19 +122,54 @@ class GuiSideBar(QToolBar):
return
def updateTheme(self):
"""Initialise GUI elements that depend on specific settings.
"""
self.setStyleSheet("QToolBar {border: 0px;}")
def updateTheme(self) -> None:
"""Initialise GUI elements that depend on specific settings."""
qPalette = self.palette()
qPalette.setBrush(QPalette.Window, qPalette.base())
self.setPalette(qPalette)
fadeCol = qPalette.text().color()
buttonStyle = (
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
).format(CONFIG.pxInt(4), fadeCol.red(), fadeCol.green(), fadeCol.blue())
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))
self.tbProject.setStyleSheet(buttonStyle)
self.tbNovel.setIcon(SHARED.theme.getIcon("view_novel"))
self.tbNovel.setStyleSheet(buttonStyle)
self.tbOutline.setIcon(SHARED.theme.getIcon("view_outline"))
self.tbOutline.setStyleSheet(buttonStyle)
self.tbBuild.setIcon(SHARED.theme.getIcon("view_build"))
self.tbBuild.setStyleSheet(buttonStyle)
self.tbDetails.setIcon(SHARED.theme.getIcon("proj_details"))
self.tbDetails.setStyleSheet(buttonStyle)
self.tbStats.setIcon(SHARED.theme.getIcon("proj_stats"))
self.tbStats.setStyleSheet(buttonStyle)
self.aProject.setIcon(SHARED.theme.getIcon("view_editor"))
self.aNovel.setIcon(SHARED.theme.getIcon("view_novel"))
self.aOutline.setIcon(SHARED.theme.getIcon("view_outline"))
self.aBuild.setIcon(SHARED.theme.getIcon("view_build"))
self.aDetails.setIcon(SHARED.theme.getIcon("proj_details"))
self.aStats.setIcon(SHARED.theme.getIcon("proj_stats"))
self.tbSettings.setIcon(SHARED.theme.getIcon("settings"))
self.tbSettings.setStyleSheet(buttonStyleMenu)
return
# END Class GuiSideBar
class _PopRightMenu(QMenu):
def event(self, event: QEvent):
"""Overload the show event and move the menu popup location."""
if event.type() == QEvent.Show:
parent = self.parent()
if isinstance(parent, QWidget):
offset = QPoint(parent.width(), parent.height() - self.height())
self.move(parent.mapToGlobal(offset))
return super(_PopRightMenu, self).event(event)
# END Class _PopRightMenu
+28 -20
View File
@@ -33,8 +33,8 @@ from datetime import datetime
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QCursor, QIcon, QKeySequence
from PyQt5.QtWidgets import (
qApp, QDialog, QFileDialog, QMainWindow, QMessageBox, QShortcut, QSplitter,
QStackedWidget, QVBoxLayout, QWidget
QDialog, QFileDialog, QHBoxLayout, QMainWindow, QMessageBox, QShortcut,
QSplitter, QStackedWidget, QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG, SHARED, __hexversion__
@@ -95,7 +95,7 @@ class GuiMain(QMainWindow):
logger.debug("Create: GUI")
self.setObjectName("GuiMain")
self.threadPool = QThreadPool()
self.threadPool = QThreadPool(self)
# System Info
# ===========
@@ -143,17 +143,17 @@ class GuiMain(QMainWindow):
self.itemDetails = GuiItemDetails(self)
self.outlineView = GuiOutlineView(self)
self.mainMenu = GuiMainMenu(self)
self.viewsBar = GuiSideBar(self)
self.sideBar = GuiSideBar(self)
# Project Tree Stack
self.projStack = QStackedWidget()
self.projStack = QStackedWidget(self)
self.projStack.addWidget(self.projView)
self.projStack.addWidget(self.novelView)
self.projStack.currentChanged.connect(self._projStackChanged)
# Project Tree View
self.treePane = QWidget()
self.treeBox = QVBoxLayout()
self.treePane = QWidget(self)
self.treeBox = QVBoxLayout(self)
self.treeBox.setContentsMargins(0, 0, 0, 0)
self.treeBox.setSpacing(mPx)
self.treeBox.addWidget(self.projStack)
@@ -161,7 +161,7 @@ class GuiMain(QMainWindow):
self.treePane.setLayout(self.treeBox)
# Splitter : Document Viewer / Document Meta
self.splitView = QSplitter(Qt.Vertical)
self.splitView = QSplitter(Qt.Vertical, self)
self.splitView.addWidget(self.docViewer)
self.splitView.addWidget(self.viewMeta)
self.splitView.setHandleWidth(hWd)
@@ -169,7 +169,7 @@ class GuiMain(QMainWindow):
self.splitView.setSizes(CONFIG.viewPanePos)
# Splitter : Document Editor / Document Viewer
self.splitDocs = QSplitter(Qt.Horizontal)
self.splitDocs = QSplitter(Qt.Horizontal, self)
self.splitDocs.addWidget(self.docEditor)
self.splitDocs.addWidget(self.splitView)
self.splitDocs.setOpaqueResize(False)
@@ -185,7 +185,7 @@ class GuiMain(QMainWindow):
self.splitMain.setSizes(CONFIG.mainPanePos)
# Main Stack : Editor / Outline
self.mainStack = QStackedWidget()
self.mainStack = QStackedWidget(self)
self.mainStack.addWidget(self.splitMain)
self.mainStack.addWidget(self.outlineView)
self.mainStack.currentChanged.connect(self._mainStackChanged)
@@ -222,11 +222,19 @@ class GuiMain(QMainWindow):
# Initialise the Project Tree
self.rebuildTrees()
# Set Main Window Elements
# Assemble Main Window Elements
self.mainBox = QHBoxLayout(self)
self.mainBox.addWidget(self.sideBar)
self.mainBox.addWidget(self.mainStack)
self.mainBox.setContentsMargins(0, 0, 0, 0)
self.mainBox.setSpacing(0)
self.mainWidget = QWidget(self)
self.mainWidget.setLayout(self.mainBox)
self.setMenuBar(self.mainMenu)
self.setCentralWidget(self.mainStack)
self.setCentralWidget(self.mainWidget)
self.setStatusBar(self.mainStatus)
self.addToolBar(Qt.LeftToolBarArea, self.viewsBar)
self.setContextMenuPolicy(Qt.NoContextMenu) # Issue #1147
# Connect Signals
@@ -236,7 +244,7 @@ class GuiMain(QMainWindow):
SHARED.projectStatusMessage.connect(self.mainStatus.setStatusMessage)
SHARED.spellLanguageChanged.connect(self.mainStatus.setLanguage)
self.viewsBar.viewChangeRequested.connect(self._changeView)
self.sideBar.viewChangeRequested.connect(self._changeView)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
self.projView.openDocumentRequest.connect(self._openDocument)
@@ -269,15 +277,15 @@ class GuiMain(QMainWindow):
# =======================
# Set Up Auto-Save Project Timer
self.asProjTimer = QTimer()
self.asProjTimer = QTimer(self)
self.asProjTimer.timeout.connect(self._autoSaveProject)
# Set Up Auto-Save Document Timer
self.asDocTimer = QTimer()
self.asDocTimer = QTimer(self)
self.asDocTimer.timeout.connect(self._autoSaveDocument)
# Main Clock
self.mainTimer = QTimer()
self.mainTimer = QTimer(self)
self.mainTimer.setInterval(1000)
self.mainTimer.timeout.connect(self._timeTick)
self.mainTimer.start()
@@ -872,7 +880,7 @@ class GuiMain(QMainWindow):
SHARED.theme.loadTheme()
self.docEditor.updateTheme()
self.docViewer.updateTheme()
self.viewsBar.updateTheme()
self.sideBar.updateTheme()
self.projView.updateTheme()
self.novelView.updateTheme()
self.outlineView.updateTheme()
@@ -1022,7 +1030,7 @@ class GuiMain(QMainWindow):
def showAboutQtDialog(self) -> None:
"""Show the about dialog for Qt."""
msgBox = QMessageBox()
msgBox = QMessageBox(self)
msgBox.aboutQt(self, "About Qt")
return
@@ -1146,7 +1154,7 @@ class GuiMain(QMainWindow):
self.treePane.setVisible(isVisible)
self.mainStatus.setVisible(isVisible)
self.mainMenu.setVisible(isVisible)
self.viewsBar.setVisible(isVisible)
self.sideBar.setVisible(isVisible)
hideDocFooter = self.isFocusMode and CONFIG.hideFocusFooter
self.docEditor.docFooter.setVisible(not hideDocFooter)
+15 -12
View File
@@ -72,8 +72,7 @@ def testGuiMain_ProjectBlocker(nwGUI):
@pytest.mark.gui
def testGuiMain_Launch(qtbot, monkeypatch, nwGUI, prjLipsum):
"""Test the handling of launch tasks.
"""
"""Test the handling of launch tasks."""
monkeypatch.setattr(GuiProjectLoad, "exec_", lambda *a: None)
monkeypatch.setattr(GuiProjectLoad, "result", lambda *a: QDialog.Accepted)
CONFIG.lastNotes = "0x0"
@@ -140,8 +139,7 @@ def testGuiMain_NewProject(monkeypatch, nwGUI, projPath):
@pytest.mark.gui
def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
"""Test handling of project tree items based on GUI focus states.
"""
"""Test handling of project tree items based on GUI focus states."""
buildTestProject(nwGUI, projPath)
sHandle = "000000000000f"
@@ -190,8 +188,7 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
@pytest.mark.gui
def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
"""Test the document editor.
"""
"""Test the document editor."""
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
@@ -555,9 +552,8 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
@pytest.mark.gui
def testGuiMain_FocusFullMode(qtbot, nwGUI, projPath, mockRnd):
"""Test toggling focus mode in main window.
"""
def testGuiMain_Features(qtbot, nwGUI, projPath, mockRnd):
"""Test various features of the main window."""
buildTestProject(nwGUI, projPath)
assert nwGUI.isFocusMode is False
@@ -576,7 +572,7 @@ def testGuiMain_FocusFullMode(qtbot, nwGUI, projPath, mockRnd):
assert nwGUI.treePane.isVisible() is False
assert nwGUI.mainStatus.isVisible() is False
assert nwGUI.mainMenu.isVisible() is False
assert nwGUI.viewsBar.isVisible() is False
assert nwGUI.sideBar.isVisible() is False
assert nwGUI.splitView.isVisible() is False
# Disable focus mode
@@ -584,7 +580,7 @@ def testGuiMain_FocusFullMode(qtbot, nwGUI, projPath, mockRnd):
assert nwGUI.treePane.isVisible() is True
assert nwGUI.mainStatus.isVisible() is True
assert nwGUI.mainMenu.isVisible() is True
assert nwGUI.viewsBar.isVisible() is True
assert nwGUI.sideBar.isVisible() is True
assert nwGUI.splitView.isVisible() is True
# Full Screen Mode
@@ -596,6 +592,13 @@ def testGuiMain_FocusFullMode(qtbot, nwGUI, projPath, mockRnd):
nwGUI.toggleFullScreenMode()
assert nwGUI.windowState() & Qt.WindowFullScreen != Qt.WindowFullScreen
# SideBar Menu
# ============
# Just make sure the custom event handler executes and doesn't fail
nwGUI.sideBar.mSettings.show()
nwGUI.sideBar.mSettings.hide()
# qtbot.stop()
# END Test testGuiMain_FocusFullMode
# END Test testGuiMain_Features