diff --git a/novelwriter/core/options.py b/novelwriter/core/options.py index de8c5759..9f78ed7a 100644 --- a/novelwriter/core/options.py +++ b/novelwriter/core/options.py @@ -56,7 +56,7 @@ VALID_MAP: dict[str, set[str]] = { "GuiWordList": {"winWidth", "winHeight"}, "GuiNovelView": {"lastCol", "lastColSize"}, "GuiBuildSettings": { - "winWidth", "winHeight", "treeWidth", "filterWidth", + "winWidth", "winHeight", "treeWidth", "filterWidth", "autoPreview", }, "GuiManuscript": { "winWidth", "winHeight", "optsWidth", "viewWidth", "listHeight", diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index df976d21..fc53b1e7 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -158,7 +158,7 @@ class GuiPreferences(NDialog): self.mainForm.addGroupLabel(title, section) # Display Language - self.guiLocale = NComboBox(self, scrollable=True) + self.guiLocale = NComboBox(self) self.guiLocale.setMinimumWidth(200) for lang, name in CONFIG.listLanguages(CONFIG.LANG_NW): self.guiLocale.addItem(name, lang) @@ -170,9 +170,9 @@ class GuiPreferences(NDialog): ) # Colour Theme - self.lightTheme = NComboBox(self, scrollable=True) + self.lightTheme = NComboBox(self) self.lightTheme.setMinimumWidth(200) - self.darkTheme = NComboBox(self, scrollable=True) + self.darkTheme = NComboBox(self) self.darkTheme.setMinimumWidth(200) for key, theme in SHARED.theme.colourThemes.items(): if theme.dark: @@ -193,7 +193,7 @@ class GuiPreferences(NDialog): ) # Icon Theme - self.iconTheme = NComboBox(self, scrollable=True) + self.iconTheme = NComboBox(self) self.iconTheme.setMinimumWidth(200) for key, theme in SHARED.theme.iconCache.iconThemes.items(): self.iconTheme.addItem(theme.name, key) @@ -513,7 +513,7 @@ class GuiPreferences(NDialog): self.mainForm.addGroupLabel(title, section) # Spell Checking - self.spellLanguage = NComboBox(self, scrollable=True) + self.spellLanguage = NComboBox(self) self.spellLanguage.setMinimumWidth(200) if CONFIG.hasEnchant: diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 16a4a865..bd701efc 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -257,7 +257,7 @@ class _SettingsPage(NScrollableForm): # Project Language projLang = data.language or CONFIG.guiLocale - self.projLang = NComboBox(self, scrollable=True) + self.projLang = NComboBox(self) self.projLang.setMinimumWidth(200) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) @@ -269,7 +269,7 @@ class _SettingsPage(NScrollableForm): ) # Spell Check Language - self.spellLang = NComboBox(self, scrollable=True) + self.spellLang = NComboBox(self) self.spellLang.setMinimumWidth(200) self.spellLang.addItem(self.tr("Default"), "None") if CONFIG.hasEnchant: diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index d6a01b44..0915fd1b 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -125,16 +125,11 @@ class NComboBox(QComboBox): window of many widgets. """ - def __init__( - self, parent: QWidget | None = None, maxItems: int = 15, scrollable: bool = False - ) -> None: + def __init__(self, parent: QWidget | None = None, maxItems: int = 15) -> None: super().__init__(parent=parent) self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.setMaxVisibleItems(maxItems) - if scrollable: - # The style sheet disables Fusion style pop-up mode on some - # platforms and allows for scrolling of long lists of items - self.setStyleSheet("QComboBox {combobox-popup: 0;}") + self.updateStyle() def wheelEvent(self, event: QWheelEvent) -> None: """Only capture the mouse wheel if the widget has focus.""" @@ -148,6 +143,12 @@ class NComboBox(QComboBox): idx = self.findData(data) self.setCurrentIndex(self.findData(default) if idx < 0 else idx) + def updateStyle(self) -> None: + """Update the style sheet.""" + # The style sheet disables Fusion style pop-up mode on some + # platforms and allows for scrolling of long lists of items + self.setStyleSheet("QComboBox {combobox-popup: 0;}") + class NSpinBox(QSpinBox): """Custom: Modified QSpinBox. diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 5bf30983..34e0056c 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -92,10 +92,10 @@ class GuiManuscript(NToolDialog): iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - pOptions = SHARED.project.options + options = SHARED.project.options self.resize( - pOptions.getInt("GuiManuscript", "winWidth", 900), - pOptions.getInt("GuiManuscript", "winHeight", 600), + options.getInt("GuiManuscript", "winWidth", 900), + options.getInt("GuiManuscript", "winHeight", 600), ) # Build Controls @@ -142,7 +142,7 @@ class GuiManuscript(NToolDialog): # ============ self.buildDetails = _DetailsWidget(self) - self.buildDetails.setColumnWidth(pOptions.getInt("GuiManuscript", "detailsWidth", 100)) + self.buildDetails.setColumnWidth(options.getInt("GuiManuscript", "detailsWidth", 100)) self.buildOutline = _OutlineWidget(self) @@ -154,8 +154,8 @@ class GuiManuscript(NToolDialog): self.buildSplit.addWidget(self.buildList) self.buildSplit.addWidget(self.detailsTabs) self.buildSplit.setSizes([ - pOptions.getInt("GuiManuscript", "listHeight", 50), - pOptions.getInt("GuiManuscript", "detailsHeight", 50), + options.getInt("GuiManuscript", "listHeight", 50), + options.getInt("GuiManuscript", "detailsHeight", 50), ]) # Process Controls @@ -183,7 +183,7 @@ class GuiManuscript(NToolDialog): # =============== self.swtNewPage = NSwitch(self, height=iPx) - self.swtNewPage.setChecked(pOptions.getBool("GuiManuscript", "showNewPage", True)) + self.swtNewPage.setChecked(options.getBool("GuiManuscript", "showNewPage", True)) self.swtNewPage.clicked.connect(self._generatePreview) self.lblNewPage = QLabel(self.tr("Show Page Breaks"), self) @@ -226,8 +226,8 @@ class GuiManuscript(NToolDialog): self.mainSplit.setStretchFactor(0, 0) self.mainSplit.setStretchFactor(1, 1) self.mainSplit.setSizes([ - pOptions.getInt("GuiManuscript", "optsWidth", 225), - pOptions.getInt("GuiManuscript", "viewWidth", 675), + options.getInt("GuiManuscript", "optsWidth", 225), + options.getInt("GuiManuscript", "viewWidth", 675), ]) self.outerBox = QVBoxLayout() @@ -273,7 +273,7 @@ class GuiManuscript(NToolDialog): self.tbAdd.setThemeIcon("add", "add") self.tbDel.setThemeIcon("remove", "remove") - self.tbCopy.setThemeIcon("copy", "accept") + self.tbCopy.setThemeIcon("copy", "action") self.tbEdit.setThemeIcon("edit", "change") buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) @@ -352,13 +352,16 @@ class GuiManuscript(NToolDialog): self._builds.removeBuild(build.buildID) self._updateBuildsList() - @pyqtSlot(BuildSettings) - def _processNewSettings(self, build: BuildSettings) -> None: + @pyqtSlot(BuildSettings, bool) + def _processNewSettings(self, build: BuildSettings, refreshPreview: bool) -> None: """Process new build settings from the settings dialog.""" self._builds.setBuild(build) self._updateBuildItem(build) - if (current := self.buildList.currentItem()) and current.data(self.D_KEY) == build.buildID: - self._updateBuildDetails(current, current) + if refreshPreview: + self.buildList.setCurrentItem(self._buildMap[build.buildID]) + self._generatePreview() + elif (item := self.buildList.currentItem()) and item.data(self.D_KEY) == build.buildID: + self._updateBuildDetails(item, item) @pyqtSlot() def _generatePreview(self) -> None: diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index 35fe49f6..5808f954 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -72,7 +72,7 @@ class GuiBuildSettings(NToolDialog): OPT_HEADINGS = 2 OPT_FORMATTING = 10 - newSettingsReady = pyqtSignal(BuildSettings) + newSettingsReady = pyqtSignal(BuildSettings, bool) def __init__(self, parent: GuiMain, build: BuildSettings) -> None: super().__init__(parent=parent) @@ -86,6 +86,7 @@ class GuiBuildSettings(NToolDialog): self.setWindowTitle(self.tr("Manuscript Build Settings")) self.setMinimumSize(700, 400) + iPx = SHARED.theme.baseIconHeight options = SHARED.project.options self.resize( options.getInt("GuiBuildSettings", "winWidth", 750), @@ -124,6 +125,13 @@ class GuiBuildSettings(NToolDialog): self.toolStack.addWidget(self.optTabHeadings) self.toolStack.addWidget(self.optTabFormatting) + # Preview + self.swtAutoPreview = NSwitch(self, height=iPx) + self.swtAutoPreview.setChecked(options.getBool("GuiBuildSettings", "autoPreview", True)) + + self.lblAutoPreview = QLabel(self.tr("Auro-Update Preview"), self) + self.lblAutoPreview.setBuddy(self.swtAutoPreview) + # Buttons self.btnApply = SHARED.theme.getStandardButton(nwStandardButton.APPLY, self) self.btnSave = SHARED.theme.getStandardButton(nwStandardButton.SAVE, self) @@ -147,10 +155,17 @@ class GuiBuildSettings(NToolDialog): self.mainBox.addWidget(self.toolStack) self.mainBox.setContentsMargins(0, 0, 0, 0) + self.bottomBox = QHBoxLayout() + self.bottomBox.addWidget(self.lblAutoPreview, 0) + self.bottomBox.addWidget(self.swtAutoPreview, 0) + self.bottomBox.addSpacing(8) + self.bottomBox.addWidget(self.btnBox, 1) + self.bottomBox.setContentsMargins(0, 0, 0, 0) + self.outerBox = QVBoxLayout() self.outerBox.addLayout(self.topBox) self.outerBox.addLayout(self.mainBox) - self.outerBox.addWidget(self.btnBox) + self.outerBox.addLayout(self.bottomBox) self.outerBox.setSpacing(12) self.setLayout(self.outerBox) @@ -259,12 +274,13 @@ class GuiBuildSettings(NToolDialog): """Save the various user settings.""" treeWidth, filterWidth = self.optTabSelect.mainSplitSizes() logger.debug("Saving State: GuiBuildSettings") - pOptions = SHARED.project.options - pOptions.setValue("GuiBuildSettings", "winWidth", self.width()) - pOptions.setValue("GuiBuildSettings", "winHeight", self.height()) - pOptions.setValue("GuiBuildSettings", "treeWidth", treeWidth) - pOptions.setValue("GuiBuildSettings", "filterWidth", filterWidth) - pOptions.saveSettings() + options = SHARED.project.options + options.setValue("GuiBuildSettings", "winWidth", self.width()) + options.setValue("GuiBuildSettings", "winHeight", self.height()) + options.setValue("GuiBuildSettings", "treeWidth", treeWidth) + options.setValue("GuiBuildSettings", "filterWidth", filterWidth) + options.setValue("GuiBuildSettings", "autoPreview", self.swtAutoPreview.isChecked()) + options.saveSettings() def _applyChanges(self) -> None: """Apply all settings changes to the build object.""" @@ -274,7 +290,7 @@ class GuiBuildSettings(NToolDialog): def _emitBuildData(self) -> None: """Assemble the build data and emit the signal.""" - self.newSettingsReady.emit(self._build) + self.newSettingsReady.emit(self._build, self.swtAutoPreview.isChecked()) self._build.resetChangedState() @@ -1329,6 +1345,9 @@ class _FormattingTab(NScrollableForm): self.pixH.setPixmap(SHARED.theme.getPixmap("fit_height", (iPx, iPx))) self.pixW.setPixmap(SHARED.theme.getPixmap("fit_width", (iPx, iPx))) + self.pageSize.updateStyle() + self.pageUnit.updateStyle() + def loadContent(self) -> None: """Populate the widgets.""" # Text Content diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index 3d8b86cb..f8348593 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -108,10 +108,11 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): bSettings = SHARED.findTopLevelWidget(GuiBuildSettings) assert isinstance(bSettings, GuiBuildSettings) bSettings.editBuildName.setText("Test Build") + bSettings.swtAutoPreview.setChecked(False) build = None @pyqtSlot(BuildSettings) - def _testNewSettingsReady(new: BuildSettings): + def _testNewSettingsReady(new: BuildSettings, refresh: bool): nonlocal build build = new @@ -151,6 +152,22 @@ def testToolManuscript_Builds(qtbot, nwGUI, projPath): assert new is not None assert new.name == "Test Build 2" + # Processing first build with refresh should change selection + manus.docPreview._docTime = 0 + manus._processNewSettings(build, True) + assert manus.docPreview._docTime > 0 # Refreshed + current = manus._getSelectedBuild() + assert current is not None + assert current.name == "Test Build" + + # Processing new build without refresh should keep selection + manus.docPreview._docTime = 0 + manus._processNewSettings(new, False) + assert manus.docPreview._docTime == 0 # No refresh + current = manus._getSelectedBuild() + assert current is not None + assert current.name == "Test Build" + # Trigger a theme update, which should propagate to settings nwGUI.refreshThemeColors() diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py index 853fe6cb..3d8804cb 100644 --- a/tests/test_tools/test_tools_manussettings.py +++ b/tests/test_tools/test_tools_manussettings.py @@ -69,7 +69,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd): triggered = False @pyqtSlot(BuildSettings) - def _testNewSettingsReady(new: BuildSettings): + def _testNewSettingsReady(new: BuildSettings, refresh: bool): nonlocal triggered assert new.buildID == build.buildID triggered = True