Add CSV export for Outline View (#1697)

This commit is contained in:
Veronica Berglyd Olsen
2024-02-15 22:07:32 +01:00
committed by GitHub
6 changed files with 142 additions and 79 deletions
+1
View File
@@ -274,6 +274,7 @@ class nwLabels:
"*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"), "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"),
"*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"), "*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"),
"*.nwd": QT_TRANSLATE_NOOP("Constant", "novelWriter files"), "*.nwd": QT_TRANSLATE_NOOP("Constant", "novelWriter files"),
"*.csv": QT_TRANSLATE_NOOP("Constant", "CSV files"),
"*": QT_TRANSLATE_NOOP("Constant", "All files"), "*": QT_TRANSLATE_NOOP("Constant", "All files"),
} }
UNIT_NAME = { UNIT_NAME = {
+90 -68
View File
@@ -27,18 +27,17 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
""" """
from __future__ import annotations from __future__ import annotations
import csv
import logging import logging
from time import time from time import time
from enum import Enum from enum import Enum
from PyQt5.QtCore import ( from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QSize, QT_TRANSLATE_NOOP
Qt, pyqtSignal, pyqtSlot, QSize, QT_TRANSLATE_NOOP
)
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QAction, QFrame, QGridLayout, QGroupBox, QHBoxLayout, QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox,
QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar, QToolButton, QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar,
QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget
) )
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
@@ -46,7 +45,7 @@ from novelwriter.enum import (
nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
) )
from novelwriter.error import logException from novelwriter.error import logException
from novelwriter.common import checkInt from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe
from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels
from novelwriter.extensions.novelselector import NovelSelector from novelwriter.extensions.novelselector import NovelSelector
@@ -88,6 +87,7 @@ class GuiOutlineView(QWidget):
self.outlineData.itemTagClicked.connect(self._tagClicked) self.outlineData.itemTagClicked.connect(self._tagClicked)
self.outlineBar.loadNovelRootRequest.connect(self._rootItemChanged) self.outlineBar.loadNovelRootRequest.connect(self._rootItemChanged)
self.outlineBar.viewColumnToggled.connect(self.outlineTree.menuColumnToggled) self.outlineBar.viewColumnToggled.connect(self.outlineTree.menuColumnToggled)
self.outlineBar.outlineExportRequest.connect(self.outlineTree.exportOutline)
# Function Mappings # Function Mappings
self.getSelectedHandle = self.outlineTree.getSelectedHandle self.getSelectedHandle = self.outlineTree.getSelectedHandle
@@ -124,7 +124,7 @@ class GuiOutlineView(QWidget):
def openProjectTasks(self) -> None: def openProjectTasks(self) -> None:
"""Run open project tasks.""" """Run open project tasks."""
lastOutline = SHARED.project.data.getLastHandle("outline") lastOutline = SHARED.project.data.getLastHandle("outline")
if not (lastOutline is None or lastOutline in SHARED.project.tree): if not lastOutline or lastOutline not in SHARED.project.tree:
lastOutline = SHARED.project.tree.findRoot(nwItemClass.NOVEL) lastOutline = SHARED.project.tree.findRoot(nwItemClass.NOVEL)
logger.debug("Setting outline tree to root item '%s'", lastOutline) logger.debug("Setting outline tree to root item '%s'", lastOutline)
@@ -198,6 +198,7 @@ class GuiOutlineView(QWidget):
class GuiOutlineToolBar(QToolBar): class GuiOutlineToolBar(QToolBar):
loadNovelRootRequest = pyqtSignal(str) loadNovelRootRequest = pyqtSignal(str)
outlineExportRequest = pyqtSignal()
viewColumnToggled = pyqtSignal(bool, Enum) viewColumnToggled = pyqtSignal(bool, Enum)
def __init__(self, outlineView: GuiOutlineView) -> None: def __init__(self, outlineView: GuiOutlineView) -> None:
@@ -228,6 +229,9 @@ class GuiOutlineToolBar(QToolBar):
self.aRefresh = QAction(self.tr("Refresh"), self) self.aRefresh = QAction(self.tr("Refresh"), self)
self.aRefresh.triggered.connect(self._refreshRequested) self.aRefresh.triggered.connect(self._refreshRequested)
self.aExport = QAction(self.tr("Export CSV"), self)
self.aExport.triggered.connect(self._exportRequested)
# Column Menu # Column Menu
self.mColumns = GuiOutlineHeaderMenu(self) self.mColumns = GuiOutlineHeaderMenu(self)
self.mColumns.columnToggled.connect( self.mColumns.columnToggled.connect(
@@ -243,6 +247,7 @@ class GuiOutlineToolBar(QToolBar):
self.addWidget(self.novelValue) self.addWidget(self.novelValue)
self.addSeparator() self.addSeparator()
self.addAction(self.aRefresh) self.addAction(self.aRefresh)
self.addAction(self.aExport)
self.addWidget(self.tbColumns) self.addWidget(self.tbColumns)
self.addWidget(stretch) self.addWidget(stretch)
@@ -261,6 +266,7 @@ class GuiOutlineToolBar(QToolBar):
self.setStyleSheet("QToolBar {border: 0px;}") self.setStyleSheet("QToolBar {border: 0px;}")
self.novelValue.refreshNovelList() self.novelValue.refreshNovelList()
self.aRefresh.setIcon(SHARED.theme.getIcon("refresh")) self.aRefresh.setIcon(SHARED.theme.getIcon("refresh"))
self.aExport.setIcon(SHARED.theme.getIcon("export"))
self.tbColumns.setIcon(SHARED.theme.getIcon("menu")) self.tbColumns.setIcon(SHARED.theme.getIcon("menu"))
self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}") self.tbColumns.setStyleSheet("QToolButton::menu-indicator {image: none;}")
return return
@@ -296,6 +302,12 @@ class GuiOutlineToolBar(QToolBar):
self.loadNovelRootRequest.emit(self.novelValue.handle) self.loadNovelRootRequest.emit(self.novelValue.handle)
return return
@pyqtSlot()
def _exportRequested(self) -> None:
"""Emit a signal that an export of the outline was requested."""
self.outlineExportRequest.emit()
return
# END Class GuiOutlineToolBar # END Class GuiOutlineToolBar
@@ -492,15 +504,48 @@ class GuiOutlineTree(QTreeWidget):
"""Get the currently selected handle. If multiple items are """Get the currently selected handle. If multiple items are
selected, return the first. selected, return the first.
""" """
selItem = self.selectedItems() if item := self.selectedItems():
if selItem: tHandle = item[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE)
tHandle = selItem[0].data(self._colIdx[nwOutline.TITLE], self.D_HANDLE) sTitle = item[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
sTitle = selItem[0].data(self._colIdx[nwOutline.TITLE], self.D_TITLE)
return tHandle, sTitle return tHandle, sTitle
return None, None return None, None
## ##
# Slots # Public Slots
##
@pyqtSlot(bool, Enum)
def menuColumnToggled(self, isChecked: bool, hItem: nwOutline) -> None:
"""Receive the changes to column visibility forwarded by the
column selection menu.
"""
if hItem in self._colIdx:
self.setColumnHidden(self._colIdx[hItem], not isChecked)
self._saveHeaderState()
return
@pyqtSlot()
def exportOutline(self) -> None:
"""Export the outline as a CSV file."""
path = CONFIG.lastPath() / f"{makeFileNameSafe(SHARED.project.data.name)}.csv"
path, _ = QFileDialog.getSaveFileName(
self, self.tr("Save Outline As"), str(path), formatFileFilter(["*.csv", "*"])
)
if path:
CONFIG.setLastPath(path)
logger.info("Writing CSV file: %s", path)
cols = [col for col in self._treeOrder if not self._colHidden[col]]
order = [self._colIdx[col] for col in cols]
with open(path, mode="w", newline="") as csvFile:
writer = csv.writer(csvFile, dialect="excel", quoting=csv.QUOTE_ALL)
writer.writerow([trConst(nwLabels.OUTLINE_COLS[col]) for col in cols])
for i in range(self.topLevelItemCount()):
if item := self.topLevelItem(i):
writer.writerow(item.text(i) for i in order)
return
##
# Private Slots
## ##
@pyqtSlot("QTreeWidgetItem*", int) @pyqtSlot("QTreeWidgetItem*", int)
@@ -510,9 +555,8 @@ class GuiOutlineTree(QTreeWidget):
document editor. document editor.
""" """
tHandle, sTitle = self.getSelectedHandle() tHandle, sTitle = self.getSelectedHandle()
if tHandle is None: if tHandle:
return self.outlineView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "", True)
self.outlineView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, sTitle or "", True)
return return
@pyqtSlot() @pyqtSlot()
@@ -536,16 +580,6 @@ class GuiOutlineTree(QTreeWidget):
self._saveHeaderState() self._saveHeaderState()
return return
@pyqtSlot(bool, Enum)
def menuColumnToggled(self, isChecked: bool, hItem: nwOutline) -> None:
"""Receive the changes to column visibility forwarded by the
column selection menu.
"""
if hItem in self._colIdx:
self.setColumnHidden(self._colIdx[hItem], not isChecked)
self._saveHeaderState()
return
## ##
# Internal Functions # Internal Functions
## ##
@@ -843,18 +877,15 @@ class GuiOutlineDetails(QScrollArea):
self.entKeyValue.setWordWrap(True) self.entKeyValue.setWordWrap(True)
self.cstKeyValue.setWordWrap(True) self.cstKeyValue.setWordWrap(True)
def tagClicked(link): self.povKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.itemTagClicked.emit(link) self.focKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.chrKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.povKeyValue.linkActivated.connect(tagClicked) self.pltKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.focKeyValue.linkActivated.connect(tagClicked) self.timKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.chrKeyValue.linkActivated.connect(tagClicked) self.wldKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.pltKeyValue.linkActivated.connect(tagClicked) self.objKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.timKeyValue.linkActivated.connect(tagClicked) self.entKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.wldKeyValue.linkActivated.connect(tagClicked) self.cstKeyValue.linkActivated.connect(lambda x: self.itemTagClicked.emit(x))
self.objKeyValue.linkActivated.connect(tagClicked)
self.entKeyValue.linkActivated.connect(tagClicked)
self.cstKeyValue.linkActivated.connect(tagClicked)
self.povKeyLWrap.addWidget(self.povKeyValue, 1) self.povKeyLWrap.addWidget(self.povKeyValue, 1)
self.focKeyLWrap.addWidget(self.focKeyValue, 1) self.focKeyLWrap.addWidget(self.focKeyValue, 1)
@@ -983,7 +1014,7 @@ class GuiOutlineDetails(QScrollArea):
## ##
@pyqtSlot(str, str) @pyqtSlot(str, str)
def showItem(self, tHandle: str, sTitle: str) -> bool: def showItem(self, tHandle: str, sTitle: str) -> None:
"""Update the content of the tree with the given handle and line """Update the content of the tree with the given handle and line
number pointing to a header. number pointing to a header.
""" """
@@ -991,41 +1022,32 @@ class GuiOutlineDetails(QScrollArea):
nwItem = SHARED.project.tree[tHandle] nwItem = SHARED.project.tree[tHandle]
novIdx = pIndex.getItemHeader(tHandle, sTitle) novIdx = pIndex.getItemHeader(tHandle, sTitle)
novRefs = pIndex.getReferences(tHandle, sTitle) novRefs = pIndex.getReferences(tHandle, sTitle)
if nwItem is None or novIdx is None: if nwItem and novIdx:
return False self.titleLabel.setText("<b>%s</b>" % self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
self.titleValue.setText(novIdx.title)
if novIdx.level in self.LVL_MAP: itemStatus, _ = nwItem.getImportStatus(incIcon=False)
self.titleLabel.setText("<b>%s</b>" % self.tr(self.LVL_MAP[novIdx.level]))
else:
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
self.titleValue.setText(novIdx.title)
itemStatus, _ = nwItem.getImportStatus(incIcon=False) self.fileValue.setText(nwItem.itemName)
self.itemValue.setText(itemStatus)
self.fileValue.setText(nwItem.itemName) self.cCValue.setText(f"{checkInt(novIdx.charCount, 0):n}")
self.itemValue.setText(itemStatus) self.wCValue.setText(f"{checkInt(novIdx.wordCount, 0):n}")
self.pCValue.setText(f"{checkInt(novIdx.paraCount, 0):n}")
cC = checkInt(novIdx.charCount, 0) self.synopValue.setText(novIdx.synopsis)
wC = checkInt(novIdx.wordCount, 0)
pC = checkInt(novIdx.paraCount, 0)
self.cCValue.setText(f"{cC:n}") self.povKeyValue.setText(self._formatTags(novRefs, nwKeyWords.POV_KEY))
self.wCValue.setText(f"{wC:n}") self.focKeyValue.setText(self._formatTags(novRefs, nwKeyWords.FOCUS_KEY))
self.pCValue.setText(f"{pC:n}") self.chrKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CHAR_KEY))
self.pltKeyValue.setText(self._formatTags(novRefs, nwKeyWords.PLOT_KEY))
self.timKeyValue.setText(self._formatTags(novRefs, nwKeyWords.TIME_KEY))
self.wldKeyValue.setText(self._formatTags(novRefs, nwKeyWords.WORLD_KEY))
self.objKeyValue.setText(self._formatTags(novRefs, nwKeyWords.OBJECT_KEY))
self.entKeyValue.setText(self._formatTags(novRefs, nwKeyWords.ENTITY_KEY))
self.cstKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CUSTOM_KEY))
self.synopValue.setText(novIdx.synopsis) return
self.povKeyValue.setText(self._formatTags(novRefs, nwKeyWords.POV_KEY))
self.focKeyValue.setText(self._formatTags(novRefs, nwKeyWords.FOCUS_KEY))
self.chrKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CHAR_KEY))
self.pltKeyValue.setText(self._formatTags(novRefs, nwKeyWords.PLOT_KEY))
self.timKeyValue.setText(self._formatTags(novRefs, nwKeyWords.TIME_KEY))
self.wldKeyValue.setText(self._formatTags(novRefs, nwKeyWords.WORLD_KEY))
self.objKeyValue.setText(self._formatTags(novRefs, nwKeyWords.OBJECT_KEY))
self.entKeyValue.setText(self._formatTags(novRefs, nwKeyWords.ENTITY_KEY))
self.cstKeyValue.setText(self._formatTags(novRefs, nwKeyWords.CUSTOM_KEY))
return True
@pyqtSlot() @pyqtSlot()
def updateClasses(self) -> None: def updateClasses(self) -> None:
@@ -0,0 +1,13 @@
"Title","Document","Words","Pars","POV","Characters","Plot","Locations","Synopsis"
"Lorem Ipsum","Lorem Ipsum","40","3","","","","",""
"Prologue","Prologue","92","1","","","","","Explanation from the lipsum.com website."
"Act One","Act One","6","1","","","","",""
"Chapter One","Chapter One","67","1","Bod","","Main","Europe","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."
"Scene One","Scene One","174","2","Bod","","Main","Europe","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."
"Scene One, Section Two","Scene One","230","2","","","","",""
"Scene Two","Scene Two","299","3","Bod","","Main","Europe","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."
"Scene Two, Section Two","Scene Two","301","3","","","","",""
"Chapter Two","Chapter Two","70","1","Bod","","Main","Europe","Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."
"Scene Three","Scene Three","439","4","Bod","","Main","Europe","Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."
"Scene Four","Scene Four","563","6","Bod","","Main","Europe","Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."
"Scene Five","Scene Five","543","5","Bod","","Main","Europe","Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."
1 Title Document Words Pars POV Characters Plot Locations Synopsis
2 Lorem Ipsum Lorem Ipsum 40 3
3 Prologue Prologue 92 1 Explanation from the lipsum.com website.
4 Act One Act One 6 1
5 Chapter One Chapter One 67 1 Bod Main Europe Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
6 Scene One Scene One 174 2 Bod Main Europe Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
7 Scene One, Section Two Scene One 230 2
8 Scene Two Scene Two 299 3 Bod Main Europe Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.
9 Scene Two, Section Two Scene Two 301 3
10 Chapter Two Chapter Two 70 1 Bod Main Europe Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.
11 Scene Three Scene Three 439 4 Bod Main Europe Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
12 Scene Four Scene Four 563 6 Bod Main Europe Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.
13 Scene Five Scene Five 543 5 Bod Main Europe Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
+1
View File
@@ -507,6 +507,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
errPos = currPos - 13 errPos = currPos - 13
if not sys.platform.startswith("win32"): if not sys.platform.startswith("win32"):
# Skip on Windows as spell checking is off there # Skip on Windows as spell checking is off there
# This check will fail without an 'en' dictionary, like aspell-en
word, cPos, cLen, suggest = docEditor._qDocument.spellErrorAtPos(errPos) word, cPos, cLen, suggest = docEditor._qDocument.spellErrorAtPos(errPos)
assert word == "tesst" assert word == "tesst"
assert cPos == 15 assert cPos == 15
+33 -3
View File
@@ -23,10 +23,12 @@ from __future__ import annotations
import time import time
import pytest import pytest
from tools import buildTestProject, writeFile from shutil import copyfile
from tools import buildTestProject, cmpFiles, writeFile
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QWidget, QAction from PyQt5.QtWidgets import QFileDialog, QWidget, QAction
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemClass, nwOutline, nwView from novelwriter.enum import nwItemClass, nwOutline, nwView
@@ -72,6 +74,7 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, projPath):
# Option State # Option State
# ============ # ============
pOptions = SHARED.project.options pOptions = SHARED.project.options
colNames = [h.name for h in nwOutline] colNames = [h.name for h in nwOutline]
colItems = [h for h in nwOutline] colItems = [h for h in nwOutline]
@@ -156,13 +159,25 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, projPath):
assert len(hiddenStates) == len(columnState) assert len(hiddenStates) == len(columnState)
assert not any(hiddenStates) assert not any(hiddenStates)
# Move Columns
# ============
# Current Order
order = [outlineTree._colIdx[col] for col in outlineTree._treeOrder]
assert order == [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
# Move 3 to 0
outlineTree._columnMoved(0, 3, 0)
order = [outlineTree._colIdx[col] for col in outlineTree._treeOrder]
assert order == [3, 0, 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]
# qtbot.stop() # qtbot.stop()
# END Test testGuiOutline_Main # END Test testGuiOutline_Main
@pytest.mark.gui @pytest.mark.gui
def testGuiOutline_Content(qtbot, nwGUI, prjLipsum): def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, prjLipsum, fncPath, tstPaths):
"""Test the outline view.""" """Test the outline view."""
assert nwGUI.openProject(prjLipsum) assert nwGUI.openProject(prjLipsum)
@@ -234,6 +249,8 @@ def testGuiOutline_Content(qtbot, nwGUI, prjLipsum):
# Scene One # Scene One
selItem = outlineTree.topLevelItem(4) selItem = outlineTree.topLevelItem(4)
outlineTree.clearSelection()
assert outlineTree.getSelectedHandle() == (None, None) # No selection
outlineTree.setCurrentItem(selItem) outlineTree.setCurrentItem(selItem)
tHandle, sTitle = outlineTree.getSelectedHandle() tHandle, sTitle = outlineTree.getSelectedHandle()
assert tHandle == "88243afbe5ed8" assert tHandle == "88243afbe5ed8"
@@ -265,6 +282,19 @@ def testGuiOutline_Content(qtbot, nwGUI, prjLipsum):
outlineTree._treeDoubleClick(selItem, 0) outlineTree._treeDoubleClick(selItem, 0)
assert nwGUI.docEditor.docHandle == "88243afbe5ed8" assert nwGUI.docEditor.docHandle == "88243afbe5ed8"
# Dump to CSV
# ===========
with monkeypatch.context() as mp:
csvFile = fncPath / "outline.csv"
mp.setattr(QFileDialog, "getSaveFileName", lambda *a, **k: (str(csvFile), ""))
outlineBar.aExport.trigger()
testFile = tstPaths.outDir / "guiOutline_Content_outline.csv"
compFile = tstPaths.refDir / "guiOutline_Content_outline.csv"
copyfile(csvFile, testFile)
assert cmpFiles(testFile, compFile)
# qtbot.stop() # qtbot.stop()
# END Test testGuiOutline_Content # END Test testGuiOutline_Content
+4 -8
View File
@@ -70,20 +70,19 @@ def cmpFiles(
ignoreLines = [] ignoreLines = []
try: try:
foOne = open(fileOne, mode="r", encoding="utf-8") with open(fileOne, mode="r", encoding="utf-8") as fo:
txtOne = fo.read().strip().splitlines()
except Exception as exc: except Exception as exc:
print(str(exc)) print(str(exc))
return False return False
try: try:
foTwo = open(fileTwo, mode="r", encoding="utf-8") with open(fileTwo, mode="r", encoding="utf-8") as fo:
txtTwo = fo.read().strip().splitlines()
except Exception as exc: except Exception as exc:
print(str(exc)) print(str(exc))
return False return False
txtOne = foOne.readlines()
txtTwo = foTwo.readlines()
if len(txtOne) != len(txtTwo): if len(txtOne) != len(txtTwo):
print("Files are not the same length") print("Files are not the same length")
return False return False
@@ -108,9 +107,6 @@ def cmpFiles(
print(" >> '%s'" % lnTwo) print(" >> '%s'" % lnTwo)
diffFound = True diffFound = True
foOne.close()
foTwo.close()
return not diffFound return not diffFound