Add a button to open manuscript build folder (#1613)

This commit is contained in:
Veronica Berglyd Olsen
2023-11-23 16:55:22 +01:00
committed by GitHub
7 changed files with 89 additions and 28 deletions
+14 -1
View File
@@ -33,8 +33,11 @@ from typing import Any, Literal
from pathlib import Path
from datetime import datetime
from configparser import ConfigParser
from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtCore import QCoreApplication
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtWidgets import QWidget, qApp
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@@ -495,6 +498,16 @@ def getFileSize(path: Path) -> int:
return -1
def openExternalPath(path: Path) -> bool:
"""Open a path by passing it to the desktop environment."""
if Path(path).exists():
QDesktopServices.openUrl(
QUrl(urljoin("file:", pathname2url(str(path))))
)
return True
return False
# =============================================================================================== #
# Other Functions
# =============================================================================================== #
+2 -2
View File
@@ -45,7 +45,7 @@ from novelwriter.core.sessions import NWSessionLog
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
from novelwriter.core.projectdata import NWProjectData
from novelwriter.common import (
checkStringNone, formatInt, formatTimeStamp, hexToInt, makeFileNameSafe, minmax
checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt, makeFileNameSafe, minmax
)
if TYPE_CHECKING: # pragma: no cover
@@ -420,7 +420,7 @@ class NWProject:
timeStamp = formatTimeStamp(time(), fileSafe=True)
archName = baseDir / f"{cleanName} {timeStamp}.zip"
if self._storage.zipIt(archName, compression=2):
size = formatInt(archName.stat().st_size)
size = formatInt(getFileSize(archName))
if doNotify:
SHARED.info(
self.tr("Created a backup of your project of size {0}B.").format(size),
+3 -6
View File
@@ -27,15 +27,14 @@ import logging
from typing import TYPE_CHECKING
from pathlib import Path
from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import QMenuBar, QAction
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
from novelwriter.common import openExternalPath
from novelwriter.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode
if TYPE_CHECKING: # pragma: no cover
@@ -111,9 +110,7 @@ class GuiMainMenu(QMenuBar):
def _openUserManualFile(self) -> None:
"""Open the documentation in PDF format."""
if isinstance(CONFIG.pdfDocs, Path):
QDesktopServices.openUrl(
QUrl(urljoin("file:", pathname2url(str(CONFIG.pdfDocs))))
)
openExternalPath(CONFIG.pdfDocs)
return
@pyqtSlot(str)
+4 -11
View File
@@ -27,19 +27,17 @@ import logging
from pathlib import Path
from zipfile import ZipFile
from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtGui import QCloseEvent, QDesktopServices, QTextCursor
from PyQt5.QtCore import QUrl, pyqtSlot
from PyQt5.QtGui import QCloseEvent, QTextCursor
from PyQt5.QtCore import pyqtSlot
from PyQt5.QtWidgets import (
QDialog, QDialogButtonBox, QFileDialog, QFrame, QHBoxLayout, QLabel,
QLineEdit, QPlainTextEdit, QPushButton, QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import formatInt, getFileSize
from novelwriter.error import formatException
from novelwriter.common import openExternalPath, formatInt, getFileSize
logger = logging.getLogger(__name__)
@@ -217,12 +215,7 @@ class GuiDictionaries(QDialog):
@pyqtSlot()
def _doOpenInstallLocation(self) -> None:
"""Open the dictionary folder."""
path = self.inPath.text()
if Path(path).is_dir():
QDesktopServices.openUrl(
QUrl(urljoin("file:", pathname2url(path)))
)
else:
if not openExternalPath(Path(self.inPath.text())):
SHARED.error("Path not found.")
return
+14 -4
View File
@@ -37,7 +37,7 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwBuildFmt
from novelwriter.common import makeFileNameSafe
from novelwriter.common import makeFileNameSafe, openExternalPath
from novelwriter.constants import nwLabels
from novelwriter.core.item import NWItem
from novelwriter.core.docbuild import NWBuildDocument
@@ -176,9 +176,11 @@ class GuiManuscriptBuild(QDialog):
self.buildBox.setVerticalSpacing(sp4)
# Dialog Buttons
self.btnOpen = QPushButton(SHARED.theme.getIcon("browse"), self.tr("Open Folder"))
self.btnBuild = QPushButton(SHARED.theme.getIcon("export"), self.tr("&Build"))
self.dlgButtons = QDialogButtonBox(QDialogButtonBox.Close)
self.dlgButtons.addButton(self.btnBuild, QDialogButtonBox.ActionRole)
self.dlgButtons = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
self.dlgButtons.addButton(self.btnOpen, QDialogButtonBox.ButtonRole.ActionRole)
self.dlgButtons.addButton(self.btnBuild, QDialogButtonBox.ButtonRole.ActionRole)
# Assemble GUI
# ============
@@ -254,7 +256,10 @@ class GuiManuscriptBuild(QDialog):
"""Handle button clicks from the dialog button box."""
role = self.dlgButtons.buttonRole(button)
if role == QDialogButtonBox.ActionRole:
self._runBuild()
if button == self.btnBuild:
self._runBuild()
elif button == self.btnOpen:
self._openOutputFolder()
elif role == QDialogButtonBox.RejectRole:
self.close()
return
@@ -386,4 +391,9 @@ class GuiManuscriptBuild(QDialog):
return
def _openOutputFolder(self):
"""Open the build folder in the system's file explorer."""
openExternalPath(Path(self.buildPath.text()))
return
# END Class GuiManuscriptBuild
+37 -4
View File
@@ -20,6 +20,8 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import time
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
import pytest
from pathlib import Path
@@ -32,10 +34,10 @@ from novelwriter.guimain import GuiMain
from novelwriter.common import (
checkBool, checkFloat, checkHandle, checkInt, checkIntTuple, checkPath,
checkString, checkStringNone, checkUuid, formatInt, formatTime,
formatTimeStamp, fuzzyTime, getGuiItem, hexToInt, isHandle, isItemClass,
isItemLayout, isItemType, isTitleTag, jsonEncode, makeFileNameSafe, minmax,
numberToRoman, NWConfigParser, readTextFile, simplified, transferCase,
xmlIndent, yesNo
formatTimeStamp, fuzzyTime, getFileSize, getGuiItem, hexToInt, isHandle,
isItemClass, isItemLayout, isItemType, isTitleTag, jsonEncode,
makeFileNameSafe, minmax, numberToRoman, NWConfigParser, openExternalPath,
readTextFile, simplified, transferCase, xmlIndent, yesNo
)
@@ -621,6 +623,37 @@ def testBaseCommon_makeFileNameSafe():
# END Test testBaseCommon_makeFileNameSafe
@pytest.mark.base
def testBaseCommon_getFileSize(fncPath):
"""Test the getFileSize function."""
(fncPath / "one.txt").write_bytes(b"foobar")
(fncPath / "two.txt").touch()
assert getFileSize(fncPath / "nope.txt") == -1
assert getFileSize(fncPath / "one.txt") == 6
assert getFileSize(fncPath / "two.txt") == 0
# END Test testBaseCommon_getFileSize
@pytest.mark.base
def testBaseCommon_openExternalPath(monkeypatch, tstPaths):
"""Test the openExternalPath function."""
lastUrl = ""
def mockOpenUrl(url: QUrl) -> None:
nonlocal lastUrl
lastUrl = url.toString()
return
monkeypatch.setattr(QDesktopServices, "openUrl", mockOpenUrl)
assert openExternalPath(Path("/foo/bar")) is False
assert openExternalPath(tstPaths.tmpDir) is True
assert lastUrl.startswith("file://")
# END Test testBaseCommon_openExternalPath
@pytest.mark.base
def testBaseCommon_getGuiItem(nwGUI):
"""Check the GUI item function."""
+15
View File
@@ -19,6 +19,8 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from PyQt5.QtCore import QUrl
from PyQt5.QtGui import QDesktopServices
import pytest
from pathlib import Path
@@ -132,6 +134,19 @@ def testManuscriptBuild_Main(
mp.setattr(QMessageBox, "result", lambda *a: QMessageBox.No)
assert manus._runBuild() is False
# Test that the open button works
lastUrl = ""
def mockOpenUrl(url: QUrl) -> None:
nonlocal lastUrl
lastUrl = url.toString()
return
with monkeypatch.context() as mp:
mp.setattr(QDesktopServices, "openUrl", mockOpenUrl)
manus.btnOpen.click()
assert lastUrl.startswith("file://")
# Finish
manus._dialogButtonClicked(manus.dlgButtons.button(QDialogButtonBox.Close))
# qtbot.stop()