diff --git a/nw/gui/docdetails.py b/nw/gui/docdetails.py index 1552640a..b1f91345 100644 --- a/nw/gui/docdetails.py +++ b/nw/gui/docdetails.py @@ -70,20 +70,9 @@ class GuiDocDetails(QFrame): if nwItem is None: colTwo = [""]*4 else: - itemStatus = nwItem.itemStatus - if nwItem.itemClass == nwItemClass.NOVEL: - if itemStatus < 0 or itemStatus >= len(self.theParent.statusLabels): - statusLabel = self.theParent.statusLabels[0] - else: - statusLabel = self.theParent.statusLabels[itemStatus] - else: - if itemStatus < 0 or itemStatus >= len(self.theParent.importLabels): - statusLabel = self.theParent.importLabels[0] - else: - statusLabel = self.theParent.importLabels[itemStatus] colTwo = [ nwItem.itemName, - statusLabel, + nwItem.itemStatus, nwLabels.CLASS_NAME[nwItem.itemClass], nwLabels.LAYOUT_NAME[nwItem.itemLayout], ] diff --git a/nw/gui/doctabs.py b/nw/gui/doctabs.py deleted file mode 100644 index b8e5c46c..00000000 --- a/nw/gui/doctabs.py +++ /dev/null @@ -1,111 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter GUI Document Tabs - - novelWriter – GUI Document Tabs -================================= - Class holding the document tab view - - File History: - Created: 2018-09-29 [0.0.1] - -""" - -import logging -import nw - -from PyQt5.QtCore import Qt -from PyQt5.QtWidgets import QTabWidget, QWidget, QVBoxLayout - -from nw.gui.doceditor import GuiDocEditor -from nw.gui.projecteditor import GuiProjectEditor -from nw.gui.aboutview import GuiAboutView - -logger = logging.getLogger(__name__) - -class GuiDocTabs(QTabWidget): - - def __init__(self): - QTabWidget.__init__(self) - - logger.debug("Initialising DocTabs ...") - self.mainConf = nw.CONFIG - self.tabList = [] - - self.resize(900,500) - self.setTabsClosable(True) - - self.tabBar().tabCloseRequested.connect(self._doCloseTab) - - self.createTab(None,nw.DOCTYPE_ABOUT) - - logger.debug("DocTabs initialisation complete") - - return - - def createTab(self, theName=None, tabType=None): - if tabType == nw.DOCTYPE_ABOUT: - self._createTabAbout() - return True - elif tabType == nw.DOCTYPE_PROJECT: - self._createTabProject(theName) - return True - elif tabType == nw.DOCTYPE_DOC: - self._createTabDoc(theName) - return True - return False - - # - # Internal Functions - # - - def _createTabDoc(self, docName=None): - if docName is None: - tabName = "New Document" - else: - tabName = docName - thisTab = GuiDocEditor() - self.addTab(thisTab,tabName) - self.setCurrentWidget(thisTab) - return True - - def _createTabProject(self, projectName=None): - """Display or create the project settings page. - """ - for i in range(self.count()): - thisTab = self.widget(i) - if isinstance(thisTab, GuiProjectEditor): - self.setCurrentWidget(thisTab) - return True - if projectName is None: - tabName = "New Project" - else: - tabName = projectName - thisTab = GuiProjectEditor() - self.addTab(thisTab,tabName) - self.setCurrentWidget(thisTab) - return True - - def _createTabAbout(self): - """Display or create the About tab. - """ - for i in range(self.count()): - thisTab = self.widget(i) - if isinstance(thisTab, GuiAboutView): - self.setCurrentWidget(thisTab) - return True - thisTab = GuiAboutView() - self.addTab(thisTab,"About") - self.setCurrentWidget(thisTab) - return True - - # - # Signals - # - - def _doCloseTab(self, tabIdx): - logger.verbose("User requested tab %d to be closed." % tabIdx) - self.removeTab(tabIdx) - logger.verbose("Tab %d closed." % tabIdx) - return - -# END Class GuiDocTabs diff --git a/nw/gui/doctree.py b/nw/gui/doctree.py index 8844866c..5ff18107 100644 --- a/nw/gui/doctree.py +++ b/nw/gui/doctree.py @@ -229,7 +229,7 @@ class GuiDocTree(QTreeWidget): trItemP.takeChild(tIndex) self.clearSelection() trItemP.setSelected(True) - self.theProject.setProjectChanged(True) + self.theProject.deleteItem(tHandle) else: self.makeAlert(["Cannot delete folder.","It is not empty."], nwAlert.ERROR) return @@ -259,19 +259,15 @@ class GuiDocTree(QTreeWidget): tStatus = nwLabels.CLASS_FLAG[nwItem.itemClass] if nwItem.itemType == nwItemType.FILE: tStatus += "."+nwLabels.LAYOUT_FLAG[nwItem.itemLayout] - nStatus = nwItem.itemStatus + iStatus = nwItem.itemStatus if tClass == nwItemClass.NOVEL: - if nStatus < 0 or nStatus >= len(self.theParent.statusIcons): - flagIcon = self.theParent.statusIcons[0] - else: - flagIcon = self.theParent.statusIcons[nStatus] + iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's a valid index + flagIcon = self.theParent.statusIcons[iStatus] else: - if nStatus < 0 or nStatus >= len(self.theParent.importIcons): - flagIcon = self.theParent.importIcons[0] - else: - flagIcon = self.theParent.importIcons[nStatus] + iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's a valid index + flagIcon = self.theParent.importIcons[iStatus] - trItem.setText(self.C_NAME,tName) + trItem.setText(self.C_NAME, tName) trItem.setText(self.C_FLAGS,tStatus) trItem.setIcon(self.C_FLAGS,flagIcon) diff --git a/nw/gui/itemeditor.py b/nw/gui/itemeditor.py index 3cc48140..06edf0fd 100644 --- a/nw/gui/itemeditor.py +++ b/nw/gui/itemeditor.py @@ -55,14 +55,14 @@ class GuiItemEditor(QDialog): self.editLayout = QComboBox() if self.theItem.itemClass == nwItemClass.NOVEL: - for n in range(len(self.theParent.statusLabels)): + for sLabel, _, _ in self.theProject.statusItems: self.editStatus.addItem( - self.theParent.statusIcons[n], self.theParent.statusLabels[n], n + self.theParent.statusIcons[sLabel], sLabel, sLabel ) else: - for n in range(len(self.theParent.statusLabels)): + for sLabel, _, _ in self.theProject.importItems: self.editStatus.addItem( - self.theParent.importIcons[n], self.theParent.importLabels[n], n + self.theParent.importIcons[sLabel], sLabel, sLabel ) self.validLayouts = [] diff --git a/nw/gui/projecteditor.py b/nw/gui/projecteditor.py index c7638b87..0fdb1fb8 100644 --- a/nw/gui/projecteditor.py +++ b/nw/gui/projecteditor.py @@ -13,21 +13,29 @@ import logging import nw -from os import path +from os import path -from PyQt5.QtWidgets import QDialog, QHBoxLayout, QVBoxLayout, QGroupBox, QFormLayout, QLineEdit, QPlainTextEdit, QPushButton +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QIcon, QPixmap, QColor, QBrush from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import ( + QDialog, QHBoxLayout, QVBoxLayout, QFormLayout, QLineEdit, QPlainTextEdit, QLabel, + QWidget, QTabWidget, QDialogButtonBox, QListWidget, QListWidgetItem, QPushButton, + QColorDialog, QAbstractItemView +) +from nw.enum import nwAlert logger = logging.getLogger(__name__) class GuiProjectEditor(QDialog): - def __init__(self, guiParent, theProject): - QDialog.__init__(self, guiParent) + def __init__(self, theParent, theProject): + QDialog.__init__(self, theParent) logger.debug("Initialising ProjectEditor ...") self.mainConf = nw.CONFIG + self.theParent = theParent self.theProject = theProject self.outerBox = QHBoxLayout() self.innerBox = QVBoxLayout() @@ -37,13 +45,72 @@ class GuiProjectEditor(QDialog): self.gradPath = path.abspath(path.join(self.mainConf.appPath,"graphics","block.svg")) self.svgGradient = QSvgWidget(self.gradPath) self.svgGradient.setFixedWidth(80) + + self.theProject.countStatus() + self.tabMain = GuiProjectEditMain(self.theParent, self.theProject) + self.tabStatus = GuiProjectEditStatus(self.theParent, self.theProject.statusItems) + self.tabImport = GuiProjectEditStatus(self.theParent, self.theProject.importItems) + + self.tabWidget = QTabWidget() + self.tabWidget.addTab(self.tabMain, "Settings") + self.tabWidget.addTab(self.tabStatus,"Status") + self.tabWidget.addTab(self.tabImport,"Importance") + self.setLayout(self.outerBox) self.outerBox.addWidget(self.svgGradient) self.outerBox.addLayout(self.innerBox) - self.mainGroup = QGroupBox("Project Settings") - self.mainForm = QFormLayout() + self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) + self.buttonBox.accepted.connect(self._doSave) + self.buttonBox.rejected.connect(self._doClose) + self.innerBox.addWidget(self.tabWidget) + self.innerBox.addWidget(self.buttonBox) + + self.show() + + logger.debug("ProjectEditor initialisation complete") + + return + + def _doSave(self): + logger.verbose("ProjectEditor save button clicked") + + projName = self.tabMain.editName.text() + bookTitle = self.tabMain.editTitle.text() + bookAuthors = self.tabMain.editAuthors.toPlainText() + self.theProject.setProjectName(projName) + self.theProject.setBookTitle(bookTitle) + self.theProject.setBookAuthors(bookAuthors) + + if self.tabStatus.colChanged: + statusCol = self.tabStatus.getNewList() + self.theProject.setStatusColours(statusCol) + if self.tabImport.colChanged: + importCol = self.tabImport.getNewList() + self.theProject.setImportColours(importCol) + if self.tabStatus.colChanged or self.tabImport.colChanged: + self.theParent.rebuildTree() + + self.close() + + return + + def _doClose(self): + logger.verbose("ProjectEditor close button clicked") + self.close() + return + +# END Class GuiProjectEditor + +class GuiProjectEditMain(QWidget): + + def __init__(self, theParent, theProject): + QWidget.__init__(self, theParent) + + self.theParent = theParent + self.theProject = theProject + self.mainForm = QFormLayout() self.editName = QLineEdit() self.editTitle = QLineEdit() self.editAuthors = QPlainTextEdit() @@ -59,40 +126,169 @@ class GuiProjectEditor(QDialog): bookAuthors += bookAuthor+"\n" self.editAuthors.setPlainText(bookAuthors) - self.buttonBox = QHBoxLayout() - self.closeButton = QPushButton("Close") - self.closeButton.clicked.connect(self._doClose) + self.setLayout(self.mainForm) + + return + +# END Class GuiProjectEditMain + +class GuiProjectEditStatus(QWidget): + + def __init__(self, theParent, theStatus): + QWidget.__init__(self, theParent) + + self.theParent = theParent + self.theStatus = theStatus + self.colData = [] + self.colCounts = [] + self.colChanged = False + self.selColour = None + + self.mainBox = QHBoxLayout() + self.mainForm = QVBoxLayout() + + self.listBox = QListWidget() + self.listBox.setDragDropMode(QAbstractItemView.InternalMove) + self.listBox.itemSelectionChanged.connect(self._selectedItem) + self.listBox.model().rowsMoved.connect(self._rowsMoved) + + for iName, iCol, nUse in self.theStatus: + self._addItem(iName, iCol, iName, nUse) + + self.editName = QLineEdit() + self.newButton = QPushButton("New") + self.delButton = QPushButton("Delete") self.saveButton = QPushButton("Save") - self.saveButton.clicked.connect(self._doSave) - self.buttonBox.addStretch(1) - self.buttonBox.addWidget(self.closeButton) - self.buttonBox.addWidget(self.saveButton) + self.colPixmap = QPixmap(16,16) + self.colPixmap.fill(QColor(120,120,120)) + self.colButton = QPushButton(QIcon(self.colPixmap),"Colour") + self.colButton.setIconSize(self.colPixmap.rect().size()) - self.mainGroup.setLayout(self.mainForm) - self.innerBox.addWidget(self.mainGroup) - self.innerBox.addLayout(self.buttonBox) + self.newButton.clicked.connect(self._newItem) + self.delButton.clicked.connect(self._delItem) + self.saveButton.clicked.connect(self._saveItem) + self.colButton.clicked.connect(self._selectColour) - self.show() + self.mainForm.addWidget(self.newButton) + self.mainForm.addWidget(self.delButton) + self.mainForm.addStretch(1) + self.mainForm.addWidget(QLabel("Name")) + self.mainForm.addWidget(self.editName) + self.mainForm.addWidget(self.colButton) + self.mainForm.addStretch(1) + self.mainForm.addWidget(self.saveButton) - logger.debug("ProjectEditor initialisation complete") + self.mainBox.addWidget(self.listBox) + self.mainBox.addLayout(self.mainForm) + + self.setLayout(self.mainBox) return - def _doSave(self): - logger.verbose("ProjectEditor save button clicked") - projName = self.editName.text() - bookTitle = self.editTitle.text() - bookAuthors = self.editAuthors.toPlainText() - self.theProject.setProjectName(projName) - self.theProject.setBookTitle(bookTitle) - self.theProject.setBookAuthors(bookAuthors) - self.theProject.setProjectChanged(True) - self.close() + def getNewList(self): + if self.colChanged: + newList = [] + for n in range(self.listBox.count()): + nItem = self.listBox.item(n) + nIdx = nItem.data(Qt.UserRole) + newList.append(self.colData[nIdx]) + return newList + return None + + ## + # User Actions + ## + + def _selectColour(self): + logger.verbose("Item colour button clicked") + if self.selColour is not None: + newCol = QColorDialog.getColor(self.selColour, self, "Select Colour", QColorDialog.DontUseNativeDialog) + if newCol: + self.selColour = newCol + colPixmap = QPixmap(16,16) + colPixmap.fill(newCol) + self.colButton.setIcon(QIcon(colPixmap)) + self.colButton.setIconSize(colPixmap.rect().size()) return - def _doClose(self): - logger.verbose("ProjectEditor close button clicked") - self.close() + def _newItem(self): + logger.verbose("New item button clicked") + newItem = self._addItem("New Item", (0, 0, 0), None, 0) + newItem.setBackground(QBrush(QColor(0,255,0,80))) + self.colChanged = True return -# END Class GuiProjectEditor + def _delItem(self): + logger.verbose("Delete item button clicked") + selItem = self._getSelectedItem() + if selItem is not None: + iRow = self.listBox.row(selItem) + selIdx = selItem.data(Qt.UserRole) + if self.colCounts[selIdx] == 0: + self.listBox.takeItem(iRow) + self.colChanged = True + else: + self.theParent.makeAlert("Cannot delete status item that is in use.",nwAlert.ERROR) + return + + def _saveItem(self): + logger.verbose("Save item button clicked") + selItem = self._getSelectedItem() + iRow = self.listBox.row(selItem) + if selItem is not None: + selIdx = selItem.data(Qt.UserRole) + self.colData[selIdx] = ( + self.editName.text().strip(), + self.selColour.red(), + self.selColour.green(), + self.selColour.blue(), + self.colData[selIdx][4] + ) + selItem.setText("%s [%d]" % (self.colData[selIdx][0], self.colCounts[selIdx])) + selItem.setIcon(self.colButton.icon()) + self.colChanged = True + return + + def _addItem(self, iName, iCol, oName, nUse): + newIcon = QPixmap(16,16) + newIcon.fill(QColor(*iCol)) + newItem = QListWidgetItem() + newItem.setText("%s [%d]" % (iName, nUse)) + newItem.setIcon(QIcon(newIcon)) + newItem.setData(Qt.UserRole, len(self.colData)) + self.listBox.addItem(newItem) + self.colData.append((iName,*iCol,oName)) + self.colCounts.append(nUse) + return newItem + + def _selectedItem(self): + logger.verbose("Item selected") + selItem = self._getSelectedItem() + if selItem is not None: + selIdx = selItem.data(Qt.UserRole) + selVal = self.colData[selIdx] + self.selColour = QColor(selVal[1],selVal[2],selVal[3]) + newIcon = QPixmap(16,16) + newIcon.fill(self.selColour) + self.editName.setText(selVal[0]) + self.colButton.setIcon(QIcon(newIcon)) + return + + ## + # Internal Functions + ## + + def _getSelectedItem(self): + selItem = self.listBox.selectedItems() + if len(selItem) == 0: + return None + if isinstance(selItem[0], QListWidgetItem): + return selItem[0] + return None + + def _rowsMoved(self): + logger.verbose("A drag move event occurred") + self.colChanged = True + return + +# END Class GuiProjectEditStatus diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index 6bcea030..20a0aa50 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -61,10 +61,8 @@ class GuiMain(QMainWindow): self.statusBar = GuiMainStatus(self) # Minor Gui Elements - self.statusIcons = [] - self.statusLabels = [] - self.importIcons = [] - self.importLabels = [] + self.statusIcons = [] + self.importIcons = [] # Assemble Main Window self.treePane = QFrame() @@ -186,12 +184,9 @@ class GuiMain(QMainWindow): projFile = self.openProjectDialog() if projFile is None: return False - self.treeView.clearTree() self.theProject.openProject(projFile) - self.treeView.buildTree() self._setWindowTitle(self.theProject.projName) - self._makeStatusIcons() - self._makeImportIcons() + self.rebuildTree() self.docEditor.setPwl(path.join(self.theProject.projMeta,"wordlist.txt")) self.docEditor.setSpellCheck(self.theProject.spellCheck) self.mainMenu.updateMenu() @@ -302,6 +297,13 @@ class GuiMain(QMainWindow): return + def rebuildTree(self): + self._makeStatusIcons() + self._makeImportIcons() + self.treeView.clearTree() + self.treeView.buildTree() + return + ## # Main Dialogs ## @@ -397,23 +399,19 @@ class GuiMain(QMainWindow): return True def _makeStatusIcons(self): - self.statusIcons = [] - self.statusLabels = [] - for sLabel, sR, sG, sB in self.theProject.statusCols: + self.statusIcons = {} + for sLabel, sCol, _ in self.theProject.statusItems: theIcon = QPixmap(32,32) - theIcon.fill(QColor(sR,sG,sB)) - self.statusIcons.append(QIcon(theIcon)) - self.statusLabels.append(sLabel) + theIcon.fill(QColor(*sCol)) + self.statusIcons[sLabel] = QIcon(theIcon) return def _makeImportIcons(self): - self.importIcons = [] - self.importLabels = [] - for sLabel, sR, sG, sB in self.theProject.importCols: + self.importIcons = {} + for sLabel, sCol, _ in self.theProject.importItems: theIcon = QPixmap(32,32) - theIcon.fill(QColor(sR,sG,sB)) - self.importIcons.append(QIcon(theIcon)) - self.importLabels.append(sLabel) + theIcon.fill(QColor(*sCol)) + self.importIcons[sLabel] = QIcon(theIcon) return ## diff --git a/nw/project/document.py b/nw/project/document.py index 1d0a8af3..bb8ad4bd 100644 --- a/nw/project/document.py +++ b/nw/project/document.py @@ -15,8 +15,7 @@ import nw from os import path, mkdir, rename, unlink -from nw.tools.analyse import TextAnalysis -from nw.enum import nwAlert +from nw.enum import nwAlert logger = logging.getLogger(__name__) diff --git a/nw/project/item.py b/nw/project/item.py index 7ea631ac..367167c8 100644 --- a/nw/project/item.py +++ b/nw/project/item.py @@ -26,7 +26,9 @@ class NWItem(): MAX_DEPTH = 8 - def __init__(self): + def __init__(self, theProject): + + self.theProject = theProject self.itemName = "" self.itemHandle = None @@ -35,7 +37,7 @@ class NWItem(): self.itemType = nwItemType.NO_TYPE self.itemClass = nwItemClass.NO_CLASS self.itemLayout = nwItemLayout.NO_LAYOUT - self.itemStatus = 0 + self.itemStatus = None self.isExpanded = False # Document Meta Data @@ -158,8 +160,10 @@ class NWItem(): return def setStatus(self, theStatus): - theStatus = checkInt(theStatus,0) - self.itemStatus = theStatus + if self.itemClass == nwItemClass.NOVEL: + self.itemStatus = self.theProject.statusItems.checkEntry(theStatus) + else: + self.itemStatus = self.theProject.importItems.checkEntry(theStatus) return def setExpanded(self, expState): diff --git a/nw/project/project.py b/nw/project/project.py index a2462d4d..c8d04db6 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -13,15 +13,16 @@ import logging import nw -from os import path, mkdir, listdir -from lxml import etree -from hashlib import sha256 -from datetime import datetime -from time import time +from os import path, mkdir, listdir +from lxml import etree +from hashlib import sha256 +from datetime import datetime +from time import time -from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert -from nw.common import checkString, checkBool -from nw.project.item import NWItem +from nw.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert +from nw.common import checkString, checkBool +from nw.project.item import NWItem +from nw.project.status import NWStatus logger = logging.getLogger(__name__) @@ -54,8 +55,8 @@ class NWProject(): # Project Settings self.spellCheck = False - self.statusCols = None - self.importCols = None + self.statusItems = None + self.importItems = None self.lastEdited = None self.lastViewed = None @@ -75,23 +76,25 @@ class NWProject(): if not self.checkRootUnique(rootClass): self.makeAlert("Duplicate root item detected!", nwAlert.ERROR) return None - newItem = NWItem() + newItem = NWItem(self) newItem.setName(rootName) newItem.setType(nwItemType.ROOT) newItem.setClass(rootClass) + newItem.setStatus(0) self._appendItem(None,None,newItem) return newItem.itemHandle def newFolder(self, folderName, folderClass, pHandle): - newItem = NWItem() + newItem = NWItem(self) newItem.setName(folderName) newItem.setType(nwItemType.FOLDER) newItem.setClass(folderClass) + newItem.setStatus(0) self._appendItem(None,pHandle,newItem) return newItem.itemHandle def newFile(self, fileName, fileClass, pHandle): - newItem = NWItem() + newItem = NWItem(self) newItem.setName(fileName) newItem.setType(nwItemType.FILE) if fileClass == nwItemClass.NOVEL: @@ -99,11 +102,12 @@ class NWProject(): else: newItem.setLayout(nwItemLayout.NOTE) newItem.setClass(fileClass) + newItem.setStatus(0) self._appendItem(None,pHandle,newItem) return newItem.itemHandle def addTrash(self): - newItem = NWItem() + newItem = NWItem(self) newItem.setName("Trash") newItem.setType(nwItemType.TRASH) newItem.setClass(nwItemClass.TRASH) @@ -144,18 +148,16 @@ class NWProject(): self.bookTitle = "" self.bookAuthors = [] self.spellCheck = False - self.statusCols = [ - ("New", 100,100,100), - ("Note", 200, 50, 0), - ("Draft", 200,150, 0), - ("Finished", 50,200, 0), - ] - self.importCols = [ - ("None", 100,100,100), - ("Minor", 200, 50, 0), - ("Major", 200,150, 0), - ("Main", 50,200, 0), - ] + self.statusItems = NWStatus() + self.statusItems.addEntry("New", (100,100,100)) + self.statusItems.addEntry("Note", (200, 50, 0)) + self.statusItems.addEntry("Draft", (200,150, 0)) + self.statusItems.addEntry("Finished",( 50,200, 0)) + self.importItems = NWStatus() + self.importItems.addEntry("New", (100,100,100)) + self.importItems.addEntry("Minor", (200, 50, 0)) + self.importItems.addEntry("Major", (200,150, 0)) + self.importItems.addEntry("Main", ( 50,200, 0)) return @@ -215,6 +217,10 @@ class NWProject(): self.lastEdited = checkString(xItem.text,None,True) if xItem.tag == "lastViewed": self.lastViewed = checkString(xItem.text,None,True) + if xItem.tag == "status": + self.statusItems.unpackEntries(xItem) + if xItem.tag == "importance": + self.importItems.unpackEntries(xItem) elif xChild.tag == "content": logger.debug("Found project content") for xItem in xChild: @@ -228,7 +234,7 @@ class NWProject(): pHandle = itemAttrib["parent"] else: pHandle = None - nwItem = NWItem() + nwItem = NWItem(self) for xValue in xItem: nwItem.setFromTag(xValue.tag,xValue.text) self._appendItem(tHandle,pHandle,nwItem) @@ -276,6 +282,11 @@ class NWProject(): self._saveProjectValue(xSettings,"lastEdited",self.lastEdited) self._saveProjectValue(xSettings,"lastViewed",self.lastViewed) + xStatus = etree.SubElement(xSettings,"status") + self.statusItems.packEntries(xStatus) + xStatus = etree.SubElement(xSettings,"importance") + self.importItems.packEntries(xStatus) + # Save Tree Content logger.debug("Writing project content") xContent = etree.SubElement(nwXML,"content",attrib={"count":str(len(self.treeOrder))}) @@ -353,6 +364,26 @@ class NWProject(): self.setProjectChanged(True) return True + def setStatusColours(self, newCols): + replaceMap = self.statusItems.setNewEntries(newCols) + if self.projTree is not None: + for nwItem in self.projTree.values(): + if nwItem.itemClass == nwItemClass.NOVEL: + if nwItem.itemStatus in replaceMap.keys(): + nwItem.setStatus(replaceMap[nwItem.itemStatus]) + self.setProjectChanged(True) + return + + def setImportColours(self, newCols,): + replaceMap = self.importItems.setNewEntries(newCols) + if self.projTree is not None: + for nwItem in self.projTree.values(): + if nwItem.itemClass != nwItemClass.NOVEL: + if nwItem.itemStatus in replaceMap.keys(): + nwItem.setStatus(replaceMap[nwItem.itemStatus]) + self.setProjectChanged(True) + return + def setProjectChanged(self, bValue): self.projChanged = bValue self.theParent.setProjectStatus(self.projChanged) @@ -409,6 +440,13 @@ class NWProject(): # Class Methods ## + def deleteItem(self, tHandle): + """This only removes the item from the order list, but not from the project tree. + """ + self.treeOrder.remove(tHandle) + self.setProjectChanged(True) + return True + def findRootItem(self, theClass): for aRoot in self.treeRoots: if theClass == self.projTree[aRoot].itemClass: @@ -426,6 +464,16 @@ class NWProject(): return False return True + def countStatus(self): + self.statusItems.resetCounts() + self.importItems.resetCounts() + for nwItem in self.projTree.values(): + if nwItem.itemClass == nwItemClass.NOVEL: + self.statusItems.countEntry(nwItem.itemStatus) + else: + self.importItems.countEntry(nwItem.itemStatus) + return + ## # Internal Functions ## @@ -492,7 +540,7 @@ class NWProject(): nOrph = 0 for oHandle in orphanFiles: nOrph += 1 - orItem = NWItem() + orItem = NWItem(self) orItem.setName("Orphaned File %d" % nOrph) orItem.setType(nwItemType.FILE) orItem.setClass(nwItemClass.NO_CLASS) diff --git a/nw/project/status.py b/nw/project/status.py new file mode 100644 index 00000000..1d19bf34 --- /dev/null +++ b/nw/project/status.py @@ -0,0 +1,154 @@ +# -*- coding: utf-8 -*- +"""novelWriter Item Status + + novelWriter – Item Status +=========================== + Class holding the project's item statuses + + File History: + Created: 2019-05-19 [0.1.3] + +""" + +import logging +import nw + +from lxml import etree + +from nw.enum import nwItemClass +from nw.common import checkInt + +logger = logging.getLogger(__name__) + +class NWStatus(): + + def __init__(self): + self.theLabels = [] + self.theColours = [] + self.theCounts = [] + self.theMap = {} + self.theLength = 0 + self.theIndex = 0 + return + + def addEntry(self, theLabel, theColours): + theLabel = theLabel.strip() + if self.lookupEntry(theLabel) is None: + self.theLabels.append(theLabel) + self.theColours.append(theColours) + self.theCounts.append(0) + self.theMap[theLabel] = self.theLength + self.theLength += 1 + return True + + def lookupEntry(self, theLabel): + theLabel = theLabel.strip() + if theLabel in self.theMap.keys(): + return self.theMap[theLabel] + return None + + def checkEntry(self, theStatus): + if isinstance(theStatus, str): + theStatus = theStatus.strip() + if self.lookupEntry(theStatus) is not None: + return theStatus + theStatus = checkInt(theStatus, 0, False) + if theStatus >= 0 and theStatus < self.theLength: + return self.theLabels[theStatus] + + def setNewEntries(self, newList): + + replaceMap = {} + + if newList is not None: + + self.theLabels = [] + self.theColours = [] + self.theCounts = [] + self.theMap = {} + self.theLength = 0 + self.theIndex = 0 + + for nName, nR, nG, nB, oName in newList: + self.addEntry(nName, (nR, nG, nB)) + if nName != oName and oName is not None: + replaceMap[oName] = nName + + return replaceMap + + def resetCounts(self): + self.theCounts = [0]*self.theLength + return + + def countEntry(self, theLabel): + theIndex = self.lookupEntry(theLabel) + if theIndex is not None: + self.theCounts[theIndex] += 1 + return + + def packEntries(self, xParent): + for n in range(self.theLength): + xSub = etree.SubElement(xParent,"entry",attrib={ + "red" : str(self.theColours[n][0]), + "green" : str(self.theColours[n][1]), + "blue" : str(self.theColours[n][2]), + }) + xSub.text = self.theLabels[n] + return True + + def unpackEntries(self, xParent): + + theLabels = [] + theColours = [] + + for xChild in xParent: + theLabels.append(xChild.text) + if "red" in xChild.attrib: + cR = checkInt(xChild.attrib["red"],0,False) + else: + cR = 0 + if "green" in xChild.attrib: + cG = checkInt(xChild.attrib["green"],0,False) + else: + cG = 0 + if "blue" in xChild.attrib: + cB = checkInt(xChild.attrib["blue"],0,False) + else: + cB = 0 + theColours.append((cR,cG,cB)) + + if len(theLabels) > 0: + self.theLabels = [] + self.theColours = [] + self.theCounts = [] + self.theMap = {} + self.theLength = 0 + self.theIndex = 0 + + for n in range(len(theLabels)): + self.addEntry(theLabels[n], theColours[n]) + + return True + + ## + # Iterator Bits + ## + + def __getitem__(self, n): + if n >= 0 and n < self.theLength: + return self.theLabels[n], self.theColours[n], self.theCounts[n] + return None, None, None + + def __iter__(self): + self.theIndex = 0 + return self + + def __next__(self): + if self.theIndex < self.theLength: + theLabel, theColour, theCount = self.__getitem__(self.theIndex) + self.theIndex += 1 + return theLabel, theColour, theCount + else: + raise StopIteration + +# END Class NWStatus diff --git a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd index c74aae3f..72d112c6 100644 --- a/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd +++ b/sample/sampleNovel/data_6/36b6aa9b697b_main.nwd @@ -15,6 +15,6 @@ So, this is some __text__ that we’ve been adding to this document. It is utter This paragraph is also meaningless. At least a bit. It’s also very short. -This one is a bit longer. “It also has some dialogue in it” she said, before she moved on to check if the spelllchecker worked. It did. “Cool,” she concluded. +This one is a bit longer. “It also has some dialogue in it” she said, before she moved on to check if the spellchecker worked. It did. “Cool,” she concluded. diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index 0d421f0e..769c4a83 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -8,22 +8,37 @@ True - 4cd0bd12b087d + 636b6aa9b697b 636b6aa9b697b + + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished + + + None + Minor + Major + Main + Novel ROOT NOVEL - 0 + New True Title Page FILE NOVEL - 2 + Started False TITLE 23 @@ -35,14 +50,14 @@ Some Chapter FOLDER NOVEL - 1 + Notes True New Scene FILE NOVEL - 2 + Started False SCENE 2571 @@ -54,19 +69,19 @@ File With Stuff FILE NOVEL - 1 + Notes False SCENE - 578 + 577 104 5 - 658 + 603 New File FILE NOVEL - 0 + Notes False SCENE 69 @@ -78,21 +93,21 @@ Characters ROOT CHARACTER - 0 + None True Main Characters FOLDER CHARACTER - 0 + None True John Smith FILE CHARACTER - 0 + Minor False NOTE 42 @@ -104,7 +119,7 @@ Jane Smith FILE CHARACTER - 0 + Major False NOTE 51 @@ -116,14 +131,14 @@ Locations ROOT WORLD - 0 + None True Earth FILE WORLD - 0 + None False NOTE 0 @@ -135,14 +150,14 @@ Trash TRASH TRASH - 0 + None True Orphaned File 2 FILE NO_CLASS - 0 + None False NO_LAYOUT 14 diff --git a/tests/reference/gui/1_nwProject.nwx b/tests/reference/gui/1_nwProject.nwx index cf0b0f54..971954a6 100644 --- a/tests/reference/gui/1_nwProject.nwx +++ b/tests/reference/gui/1_nwProject.nwx @@ -1,60 +1,72 @@ - + - False - None - None + True + 31489056e0916 + 31489056e0916 + + New + Note + Draft + Finished + + + New + Minor + Major + Main + Novel ROOT NOVEL - 0 - False + New + True New Chapter FOLDER NOVEL - 0 - False + New + True New Scene FILE NOVEL - 0 + New False SCENE - 0 - 0 - 0 - 0 + 377 + 69 + 2 + 443 Characters ROOT CHARACTER - 0 + New False Plot ROOT PLOT - 0 + New False World ROOT WORLD - 0 + New False diff --git a/tests/reference/gui/2_nwProject.nwx b/tests/reference/gui/2_nwProject.nwx index 33070870..02f3c384 100644 --- a/tests/reference/gui/2_nwProject.nwx +++ b/tests/reference/gui/2_nwProject.nwx @@ -1,5 +1,5 @@ - + Project Name Project Title @@ -10,27 +10,39 @@ False None None + + New + Note + Finished + Final + + + New + Minor + Major + Main + Novel ROOT NOVEL - 0 + New False New Chapter FOLDER NOVEL - 0 + New False New Scene FILE NOVEL - 0 + New False SCENE 0 @@ -42,21 +54,21 @@ Characters ROOT CHARACTER - 0 + New False Plot ROOT PLOT - 0 + New False World ROOT WORLD - 0 + New False diff --git a/tests/reference/gui/3_nwProject.nwx b/tests/reference/gui/3_nwProject.nwx index ecc54350..510192ff 100644 --- a/tests/reference/gui/3_nwProject.nwx +++ b/tests/reference/gui/3_nwProject.nwx @@ -1,5 +1,5 @@ - + @@ -8,27 +8,39 @@ False None None + + New + Note + Draft + Finished + + + New + Minor + Major + Main + Novel ROOT NOVEL - 0 + New False New Chapter FOLDER NOVEL - 0 + New False Just a Page FILE NOVEL - 1 + Note False PAGE 0 @@ -40,21 +52,21 @@ Characters ROOT CHARACTER - 0 + New False Plot ROOT PLOT - 0 + New False World ROOT WORLD - 0 + New False diff --git a/tests/reference/proj/1_nwProject.nwx b/tests/reference/proj/1_nwProject.nwx index 181e1166..e9ff11f5 100644 --- a/tests/reference/proj/1_nwProject.nwx +++ b/tests/reference/proj/1_nwProject.nwx @@ -1,5 +1,5 @@ - + @@ -8,48 +8,60 @@ False None None + + New + Note + Draft + Finished + + + New + Minor + Major + Main + Novel ROOT NOVEL - 0 + New False Characters ROOT CHARACTER - 0 + New False Plot ROOT PLOT - 0 + New False World ROOT WORLD - 0 + New False New Chapter FOLDER NOVEL - 0 + New False New Scene FILE NOVEL - 0 + New False SCENE 0 diff --git a/tests/reference/proj/2_nwProject.nwx b/tests/reference/proj/2_nwProject.nwx index f9dbdef1..8c68bc42 100644 --- a/tests/reference/proj/2_nwProject.nwx +++ b/tests/reference/proj/2_nwProject.nwx @@ -1,5 +1,5 @@ - + @@ -8,48 +8,60 @@ False None None + + New + Note + Draft + Finished + + + New + Minor + Major + Main + Novel ROOT NOVEL - 0 + New False Characters ROOT CHARACTER - 0 + New False Plot ROOT PLOT - 0 + New False World ROOT WORLD - 0 + New False New Chapter FOLDER NOVEL - 0 + New False New Scene FILE NOVEL - 0 + New False SCENE 0 @@ -61,28 +73,28 @@ Timeline ROOT TIMELINE - 0 + New False Object ROOT OBJECT - 0 + New False Custom1 ROOT CUSTOM - 0 + New False Custom2 ROOT CUSTOM - 0 + New False diff --git a/tests/test_gui.py b/tests/test_gui.py index 681c2116..ac47fdd2 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -99,15 +99,19 @@ def testMainWindows(qtbot, nwTempGUI, nwRef): qtbot.wait(stepDelay) nwGUI.docEditor.wCounter.run() qtbot.wait(stepDelay) + nwGUI.docEditor._updateCounts() # Save the document assert nwGUI.docEditor.docChanged assert nwGUI.saveDocument() + assert not nwGUI.docEditor.docChanged qtbot.wait(stepDelay) # Open and view the edited document assert nwGUI.openDocument("31489056e0916") assert nwGUI.viewDocument("31489056e0916") + qtbot.wait(stepDelay) + assert nwGUI.saveProject() # Check the files projFile = path.join(nwTempGUI,"nwProject.nwx") @@ -135,27 +139,40 @@ def testProjectEditor(qtbot, nwTempGUI, nwRef): qtbot.addWidget(projEdit) for c in "Project Name": - qtbot.keyClick(projEdit.editName, c, delay=keyDelay) + qtbot.keyClick(projEdit.tabMain.editName, c, delay=keyDelay) for c in "Project Title": - qtbot.keyClick(projEdit.editTitle, c, delay=keyDelay) + qtbot.keyClick(projEdit.tabMain.editTitle, c, delay=keyDelay) for c in "Jane Doe": - qtbot.keyClick(projEdit.editAuthors, c, delay=keyDelay) - qtbot.keyClick(projEdit.editAuthors, Qt.Key_Return, delay=keyDelay) + qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay) + qtbot.keyClick(projEdit.tabMain.editAuthors, Qt.Key_Return, delay=keyDelay) for c in "John Doh": - qtbot.keyClick(projEdit.editAuthors, c, delay=keyDelay) + qtbot.keyClick(projEdit.tabMain.editAuthors, c, delay=keyDelay) - qtbot.mouseClick(projEdit.saveButton, Qt.LeftButton) + #Test Status Tab + projEdit.tabWidget.setCurrentWidget(projEdit.tabStatus) + projEdit.tabStatus.listBox.item(2).setSelected(True) + qtbot.mouseClick(projEdit.tabStatus.delButton, Qt.LeftButton) + qtbot.mouseClick(projEdit.tabStatus.newButton, Qt.LeftButton) + projEdit.tabStatus.listBox.item(3).setSelected(True) + for n in range(8): + qtbot.keyClick(projEdit.tabStatus.editName, Qt.Key_Backspace, delay=keyDelay) + for c in "Final": + qtbot.keyClick(projEdit.tabStatus.editName, c, delay=keyDelay) + qtbot.mouseClick(projEdit.tabStatus.saveButton, Qt.LeftButton) + projEdit._doSave() + + # Open again, and check project settings projEdit = GuiProjectEditor(nwGUI, nwGUI.theProject) qtbot.addWidget(projEdit) - assert projEdit.editName.text() == "Project Name" - assert projEdit.editTitle.text() == "Project Title" - theAuth = projEdit.editAuthors.toPlainText().strip().splitlines() + assert projEdit.tabMain.editName.text() == "Project Name" + assert projEdit.tabMain.editTitle.text() == "Project Title" + theAuth = projEdit.tabMain.editAuthors.toPlainText().strip().splitlines() assert len(theAuth) == 2 assert theAuth[0] == "Jane Doe" assert theAuth[1] == "John Doh" - qtbot.mouseClick(projEdit.closeButton, Qt.LeftButton) + projEdit._doClose() qtbot.wait(stepDelay) assert nwGUI.saveProject() @@ -185,7 +202,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef): qtbot.addWidget(itemEdit) assert itemEdit.editName.text() == "New Scene" - assert itemEdit.editStatus.currentData() == 0 + assert itemEdit.editStatus.currentData() == "New" assert itemEdit.editLayout.currentData() == nwItemLayout.SCENE for c in "Just a Page": @@ -199,7 +216,7 @@ def testItemEditor(qtbot, nwTempGUI, nwRef): itemEdit = GuiItemEditor(nwGUI, nwGUI.theProject, "31489056e0916") qtbot.addWidget(itemEdit) assert itemEdit.editName.text() == "Just a Page" - assert itemEdit.editStatus.currentData() == 1 + assert itemEdit.editStatus.currentData() == "Note" assert itemEdit.editLayout.currentData() == nwItemLayout.PAGE qtbot.mouseClick(itemEdit.closeButton, Qt.LeftButton)