Redesign the Document Merge dialog box

This commit is contained in:
Veronica Berglyd Olsen
2022-10-09 15:30:02 +02:00
parent 43bb5aab5c
commit e1c3a12d07
5 changed files with 238 additions and 131 deletions
+2
View File
@@ -19,6 +19,7 @@ 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 novelwriter.core.doctools import DocMerger
from novelwriter.core.document import NWDoc
from novelwriter.core.index import countWords
from novelwriter.core.project import NWProject
@@ -28,6 +29,7 @@ from novelwriter.core.toodt import ToOdt
from novelwriter.core.tomd import ToMarkdown
__all__ = [
"DocMerger",
"countWords",
"NWDoc",
"NWProject",
+102
View File
@@ -0,0 +1,102 @@
"""
novelWriter Project Document Tools
====================================
A collection of tools to create and manipulate documents
File History:
Created: 2022-10-02 [2.0b1]
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import logging
logger = logging.getLogger(__name__)
# logger.verbose("GuiDocMerge merge button clicked")
# finalOrder = []
# for i in range(self.listBox.count()):
# finalOrder.append(self.listBox.item(i).data(Qt.UserRole))
# if len(finalOrder) == 0:
# self.mainGui.makeAlert(self.tr(
# "No source documents found. Nothing to do."
# ), nwAlert.ERROR)
# return False
# theText = ""
# for tHandle in finalOrder:
# inDoc = NWDoc(self.theProject, tHandle)
# docText = inDoc.readDocument()
# docErr = inDoc.getError()
# if docText is None and docErr:
# self.mainGui.makeAlert([
# self.tr("Failed to open document file."), docErr
# ], nwAlert.ERROR)
# if docText:
# theText += docText.rstrip("\n")+"\n\n"
# if self.sourceItem is None:
# self.mainGui.makeAlert(self.tr(
# "No source folder selected. Nothing to do."
# ), nwAlert.ERROR)
# return False
# srcItem = self.theProject.tree[self.sourceItem]
# if srcItem is None:
# self.mainGui.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
# return False
# nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemParent)
# newItem = self.theProject.tree[nHandle]
# newItem.setStatus(srcItem.itemStatus)
# newItem.setImport(srcItem.itemImport)
# outDoc = NWDoc(self.theProject, nHandle)
# if not outDoc.writeDocument(theText):
# self.mainGui.makeAlert([
# self.tr("Could not save document."), outDoc.getError()
# ], nwAlert.ERROR)
# return False
# self.mainGui.projView.revealNewTreeItem(nHandle)
# self.mainGui.openDocument(nHandle, doScroll=True)
# self._doClose()
class DocMerger:
def __init__(self, theProject):
self.theProject = theProject
self._targetDoc = None
return
def setTargetDoc(self, tHandle):
return
def createNewDoc(self, docLabel, pHandle, itemLayout):
return
def appendDoc(self, tHandle):
return
# END Class DocMerger
+76 -113
View File
@@ -1,10 +1,11 @@
"""
novelWriter GUI Doc Merge Tool
================================
GUI class for merging multiple documents to one document
novelWriter GUI Doc Merge Dialog
==================================
Custom dialog class for merging documents.
File History:
Created: 2020-01-23 [0.4.3]
Created: 2020-01-23 [0.4.3]
Rewritten: 2022-10-06 [2.0b1]
This file is a part of novelWriter
Copyright 20182022, Veronica Berglyd Olsen
@@ -26,169 +27,131 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
import novelwriter
from PyQt5.QtCore import Qt
from PyQt5.QtCore import Qt, QSize
from PyQt5.QtWidgets import (
QDialog, QVBoxLayout, QLabel, QListWidget, QAbstractItemView,
QListWidgetItem, QDialogButtonBox
QListWidgetItem, QDialogButtonBox, QGridLayout
)
from novelwriter.core import NWDoc
from novelwriter.enum import nwAlert, nwItemType
from novelwriter.gui.custom import QHelpLabel
from novelwriter.gui.custom import QHelpLabel, QSwitch
logger = logging.getLogger(__name__)
class GuiDocMerge(QDialog):
def __init__(self, mainGui):
QDialog.__init__(self, mainGui)
def __init__(self, mainGui, sHandle, itemList):
super().__init__(parent=mainGui)
logger.debug("Initialising GuiDocMerge ...")
self.setObjectName("GuiDocMerge")
self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
self.theProject = mainGui.theProject
self.sourceItem = None
self.outerBox = QVBoxLayout()
self._data = {}
self.setWindowTitle(self.tr("Merge Documents"))
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Documents to Merge")))
self.helpLabel = QHelpLabel(
self.tr("Drag and drop items to change the order."), self.mainGui.mainTheme.helpText
)
self.helpLabel = QHelpLabel(self.tr(
"Drag and drop items to change the order, or uncheck to exclude."
), self.mainTheme.helpText)
iPx = self.mainTheme.baseIconSize
hSp = self.mainConf.pxInt(12)
vSp = self.mainConf.pxInt(8)
bSp = self.mainConf.pxInt(12)
self.listBox = QListWidget()
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
self.listBox.setIconSize(QSize(iPx, iPx))
self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
self.listBox.setSelectionBehavior(QAbstractItemView.SelectRows)
self.listBox.setSelectionMode(QAbstractItemView.SingleSelection)
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
# Merge Options
self.trashLabel = QLabel(self.tr("Move merged items to Trash"))
self.trashSwitch = QSwitch()
self.optBox = QGridLayout()
self.optBox.addWidget(self.trashLabel, 0, 0)
self.optBox.addWidget(self.trashSwitch, 0, 1)
self.optBox.setHorizontalSpacing(hSp)
self.optBox.setColumnStretch(2, 1)
# Buttons
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
self.buttonBox.accepted.connect(self._doMerge)
self.buttonBox.rejected.connect(self._doClose)
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.setSpacing(0)
self.outerBox.addWidget(self.headLabel)
self.outerBox.addWidget(self.helpLabel)
self.outerBox.addSpacing(self.mainConf.pxInt(8))
self.outerBox.addSpacing(vSp)
self.outerBox.addWidget(self.listBox)
self.outerBox.addSpacing(self.mainConf.pxInt(12))
self.outerBox.addSpacing(vSp)
self.outerBox.addLayout(self.optBox)
self.outerBox.addSpacing(bSp)
self.outerBox.addWidget(self.buttonBox)
self.setLayout(self.outerBox)
self.rejected.connect(self._doClose)
self._populateList()
# Load Content
self._loadContent(sHandle, itemList)
logger.debug("GuiDocMerge initialisation complete")
return
##
# Buttons
##
def _doMerge(self):
"""Perform the merge of the files in the selected folder, and
create a new file in the same parent folder. The old files are
not removed in the merge process, and must be deleted manually.
def getData(self):
"""Return the user's choices.
"""
logger.verbose("GuiDocMerge merge button clicked")
finalOrder = []
finalItems = []
for i in range(self.listBox.count()):
finalOrder.append(self.listBox.item(i).data(Qt.UserRole))
item = self.listBox.item(i)
if item.checkState() == Qt.Checked:
finalItems.append(item.data(Qt.UserRole))
if len(finalOrder) == 0:
self.mainGui.makeAlert(self.tr(
"No source documents found. Nothing to do."
), nwAlert.ERROR)
return False
self._data["moveToTrash"] = self.trashSwitch.isChecked()
self._data["finalItems"] = finalItems
theText = ""
for tHandle in finalOrder:
inDoc = NWDoc(self.theProject, tHandle)
docText = inDoc.readDocument()
docErr = inDoc.getError()
if docText is None and docErr:
self.mainGui.makeAlert([
self.tr("Failed to open document file."), docErr
], nwAlert.ERROR)
if docText:
theText += docText.rstrip("\n")+"\n\n"
if self.sourceItem is None:
self.mainGui.makeAlert(self.tr(
"No source folder selected. Nothing to do."
), nwAlert.ERROR)
return False
srcItem = self.theProject.tree[self.sourceItem]
if srcItem is None:
self.mainGui.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
return False
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemParent)
newItem = self.theProject.tree[nHandle]
newItem.setStatus(srcItem.itemStatus)
newItem.setImport(srcItem.itemImport)
outDoc = NWDoc(self.theProject, nHandle)
if not outDoc.writeDocument(theText):
self.mainGui.makeAlert([
self.tr("Could not save document."), outDoc.getError()
], nwAlert.ERROR)
return False
self.mainGui.projView.revealNewTreeItem(nHandle)
self.mainGui.openDocument(nHandle, doScroll=True)
self._doClose()
return True
def _doClose(self):
"""Close the dialog window without doing anything.
"""
self.close()
return
return self._data
##
# Internal Functions
##
def _populateList(self):
"""Get the item selected in the tree, check that it is a folder,
and try to find all files associated with it. The valid files
are then added to the list view in order. The list itself can be
reordered by the user.
def _loadContent(self, sHandle, itemList):
"""Load content from a given list of items.
"""
tHandle = self.mainGui.projView.getSelectedHandle()
self.sourceItem = tHandle
if tHandle is None:
return False
self._data = {}
self._data["sHandle"] = sHandle
self._data["origItems"] = itemList
nwItem = self.theProject.tree[tHandle]
if nwItem is None:
return False
if nwItem.itemType is not nwItemType.FOLDER:
self.mainGui.makeAlert(self.tr(
"Element selected in the project tree must be a folder."
), nwAlert.ERROR)
return False
for sHandle in self.mainGui.projView.getTreeFromHandle(tHandle):
newItem = QListWidgetItem()
nwItem = self.theProject.tree[sHandle]
if not nwItem.isFileType():
self.listBox.clear()
for tHandle in itemList:
nwItem = self.theProject.tree[tHandle]
if nwItem is None or not nwItem.isFileType():
continue
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
itemIcon = self.mainTheme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
)
newItem = QListWidgetItem()
newItem.setIcon(itemIcon)
newItem.setText(nwItem.itemName)
newItem.setData(Qt.UserRole, sHandle)
newItem.setData(Qt.UserRole, tHandle)
newItem.setCheckState(Qt.Checked)
self.listBox.addItem(newItem)
return True
return
# END Class GuiDocMerge
+7 -7
View File
@@ -202,7 +202,7 @@ class QConfigLayout(QGridLayout):
class QHelpLabel(QLabel):
def __init__(self, theText, textCol, fontSize=0.9):
QLabel.__init__(self, theText)
super().__init__(theText)
if isinstance(textCol, QColor):
qCol = textCol
@@ -377,7 +377,7 @@ class QSwitch(QAbstractButton):
class PagedDialog(QDialog):
def __init__(self, parent=None):
QDialog.__init__(self, parent=parent)
super().__init__(parent=parent)
self._tabBar = VerticalTabBar(self)
self._tabBar.setExpanding(False)
@@ -410,13 +410,13 @@ class PagedDialog(QDialog):
return
def addTab(self, widget, label):
"""Forwards the adding of tabs to the QTabWidget.
"""Forward the adding of tabs to the QTabWidget.
"""
self._tabBox.addTab(widget, label)
return
def addControls(self, buttonBar):
"""Adds a button bar to the dialog.
"""Add a button bar to the dialog.
"""
self._buttonBox.addWidget(buttonBar)
return
@@ -427,14 +427,14 @@ class PagedDialog(QDialog):
class VerticalTabBar(QTabBar):
def __init__(self, parent=None):
QTabBar.__init__(self, parent=parent)
super().__init__(parent=parent)
self._mW = novelwriter.CONFIG.pxInt(150)
return
def tabSizeHint(self, index):
"""Returns a transposed size hint for the rotated bar.
"""Return a transposed size hint for the rotated bar.
"""
tSize = QTabBar.tabSizeHint(self, index)
tSize = super().tabSizeHint(index)
tSize.transpose()
tSize.setWidth(min(tSize.width(), self._mW))
return tSize
+51 -11
View File
@@ -34,15 +34,15 @@ from time import time
from PyQt5.QtGui import QPalette
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QAbstractItemView, QFrame, QHBoxLayout, QHeaderView, QLabel,
QAbstractItemView, QFrame, QHBoxLayout, QHeaderView, QLabel, QDialog,
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem,
QVBoxLayout, QWidget
)
from novelwriter.core import NWDoc
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.dialogs import GuiDocMerge, GuiEditLabel
from novelwriter.constants import nwHeaders, trConst, nwLabels
from novelwriter.dialogs.editlabel import GuiEditLabel
logger = logging.getLogger(__name__)
@@ -334,7 +334,7 @@ class GuiProjectTree(QTreeWidget):
self.mainConf = novelwriter.CONFIG
self.projView = projView
self.mainGui = projView.mainGui
self.mainTheme = projView.mainGui.mainTheme
self.mainTheme = projView.mainGui.mainTheme
self.theProject = projView.mainGui.theProject
# Internal Variables
@@ -382,9 +382,8 @@ class GuiProjectTree(QTreeWidget):
trRoot = self.invisibleRootItem()
trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled)
# Set Multiple Selection by CTRL
# Disabled for now, until the merge files option has been added
# self.setSelectionMode(QAbstractItemView.ExtendedSelection)
# Set selection options
self.setSelectionMode(QAbstractItemView.SingleSelection)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
# Connect signals
@@ -1077,16 +1076,21 @@ class GuiProjectTree(QTreeWidget):
lambda n, key=key: self._changeItemImport(tHandle, key)
)
# Transform Item
# ==============
mTrans = ctxMenu.addMenu(self.tr("Transform"))
if isFile and tItem.documentAllowed():
if tItem.isNoteLayout():
ctxMenu.addAction(
mTrans.addAction(
self.tr("Convert to {0}").format(
trConst(nwLabels.LAYOUT_NAME[nwItemLayout.DOCUMENT])
),
lambda: self._changeItemLayout(tHandle, nwItemLayout.DOCUMENT)
)
else:
ctxMenu.addAction(
mTrans.addAction(
self.tr("Convert to {0}").format(
trConst(nwLabels.LAYOUT_NAME[nwItemLayout.NOTE])
),
@@ -1094,19 +1098,37 @@ class GuiProjectTree(QTreeWidget):
)
elif isFolder:
if tItem.documentAllowed():
ctxMenu.addAction(
mTrans.addAction(
self.tr("Convert to {0}").format(
trConst(nwLabels.LAYOUT_NAME[nwItemLayout.DOCUMENT])
),
lambda: self._covertFolderToFile(tHandle, nwItemLayout.DOCUMENT)
)
ctxMenu.addAction(
mTrans.addAction(
self.tr("Convert to {0}").format(
trConst(nwLabels.LAYOUT_NAME[nwItemLayout.NOTE])
),
lambda: self._covertFolderToFile(tHandle, nwItemLayout.NOTE)
)
if hasChild:
if isFile:
mTrans.addAction(
self.tr("Merge Child Documents"),
lambda: self._mergeDocuments(tHandle, isFile)
)
else:
mTrans.addAction(
self.tr("Combine Documents in Folder"),
lambda: self._mergeDocuments(tHandle, isFile)
)
if isFile:
mTrans.addAction(
self.tr("Split Document by Header"),
lambda: self._splitDocument(tHandle)
)
ctxMenu.addSeparator()
# Expand/Collapse
@@ -1355,6 +1377,24 @@ class GuiProjectTree(QTreeWidget):
logger.info("Folder conversion cancelled")
return
def _mergeDocuments(self, tHandle, isFile):
"""Merge an item's child documents into a single document.
"""
logger.info("Request to merge items under handle '%s'", tHandle)
itemList = self.getTreeFromHandle(tHandle)
itemList.remove(tHandle)
dlgMerge = GuiDocMerge(self.mainGui, tHandle, itemList)
dlgMerge.exec_()
if dlgMerge.result() == QDialog.Accepted:
print(dlgMerge.getData())
return
def _splitDocument(self, tHandle):
return
def _scanChildren(self, theList, tItem, tIndex):
"""This is a recursive function returning all items in a tree
starting at a given QTreeWidgetItem.
@@ -1380,7 +1420,7 @@ class GuiProjectTree(QTreeWidget):
"""
tHandle = nwItem.itemHandle
pHandle = nwItem.itemParent
newItem = QTreeWidgetItem([""]*4)
newItem = QTreeWidgetItem()
newItem.setText(self.C_NAME, "")
newItem.setText(self.C_COUNT, "0")