Add CSV export to outline
This commit is contained in:
@@ -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 = {
|
||||||
|
|||||||
+56
-21
@@ -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
|
||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
@@ -500,7 +512,41 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
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") 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 +556,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 +581,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
|
||||||
##
|
##
|
||||||
|
|||||||
Reference in New Issue
Block a user