Add a button to open manuscript build folder (#1613)
This commit is contained in:
+14
-1
@@ -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
|
||||
# =============================================================================================== #
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user