diff --git a/nw/enum.py b/nw/enum.py index b6539496..a8406ee8 100644 --- a/nw/enum.py +++ b/nw/enum.py @@ -43,6 +43,7 @@ class nwItemLayout(Enum): PARTITION = 3 CHAPTER = 4 SCENE = 5 + NOTE = 6 # END Enum nwItemLayout diff --git a/nw/gui/docdetails.py b/nw/gui/docdetails.py new file mode 100644 index 00000000..180d16ca --- /dev/null +++ b/nw/gui/docdetails.py @@ -0,0 +1,86 @@ +# -*- coding: utf-8 -*- +"""novelWriter GUI Document Details + + novelWriter – GUI Document Details +==================================== + Class holding the left side document details panel + + File History: + Created: 2019-04-24 [0.0.1] + +""" + +import logging +import nw + +from os import path +from PyQt5.QtGui import QFont +from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel + +from nw.enum import nwItemType, nwItemClass, nwItemLayout +from nw.project.item import NWItem + +logger = logging.getLogger(__name__) + +class GuiDocDetails(QFrame): + + C_NAME = 0 + C_COUNT = 1 + C_FLAGS = 2 + C_HANDLE = 3 + + def __init__(self, theProject): + QFrame.__init__(self) + + logger.debug("Initialising DocDetails ...") + self.mainConf = nw.CONFIG + self.debugGUI = self.mainConf.debugGUI + self.theProject = theProject + + self.mainBox = QGridLayout(self) + self.mainBox.setVerticalSpacing(1) + self.mainBox.setHorizontalSpacing(15) + self.setLayout(self.mainBox) + + self.fntOne = QFont() + self.fntOne.setPointSize(10) + self.fntOne.setBold(True) + + self.fntTwo = QFont() + self.fntTwo.setPointSize(10) + + colOne = ["Name","Status","Class","Layout"] + for nRow in range(4): + lblOne = QLabel(colOne[nRow]) + lblOne.setFont(self.fntOne) + self.mainBox.addWidget(lblOne,nRow+1,0) + + self.mainBox.setColumnStretch(0,0) + self.mainBox.setColumnStretch(1,1) + + logger.debug("DocDetails initialisation complete") + + return + + def buildViewBox(self, tHandle): + + nwItem = self.theProject.getItem(tHandle) + itemStatus = nwItem.itemStatus + if itemStatus < 0 or itemStatus >= len(self.theProject.statusLabels): + itemStatus = 0 + + colTwo = [ + nwItem.itemName, + self.theProject.statusLabels[itemStatus], + NWItem.CLASS_NAME[nwItem.itemClass], + NWItem.LAYOUT_NAME[nwItem.itemLayout], + ] + + for nRow in range(4): + lblTwo = QLabel(colTwo[nRow]) + lblTwo.setFont(self.fntTwo) + self.mainBox.addWidget(lblTwo,nRow+1,1) + + return + +# END Class GuiDocDetails diff --git a/nw/gui/doctree.py b/nw/gui/doctree.py index ecba657e..cc26228e 100644 --- a/nw/gui/doctree.py +++ b/nw/gui/doctree.py @@ -14,8 +14,8 @@ import logging import nw from os import path -from PyQt5.QtGui import QIcon from PyQt5.QtCore import Qt, QSize +from PyQt5.QtGui import QIcon, QFont, QColor from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView, QInputDialog, QLineEdit from nw.enum import nwItemType, nwItemClass @@ -43,10 +43,13 @@ class GuiDocTree(QTreeWidget): self.setExpandsOnDoubleClick(True) self.setIndentation(13) self.setColumnCount(4) - self.setHeaderLabels(["Name","S","#","Handle"]) + self.setHeaderLabels(["Name","Count","Flags","Handle"]) if not self.debugGUI: self.hideColumn(self.C_HANDLE) + treeHead = self.headerItem() + treeHead.setTextAlignment(self.C_COUNT,Qt.AlignRight) + # Allow Move by Drag & Drop self.setDragEnabled(True) self.setDragDropMode(QAbstractItemView.InternalMove) @@ -54,6 +57,9 @@ class GuiDocTree(QTreeWidget): for colN in range(len(self.mainConf.treeColWidth)): self.setColumnWidth(colN,self.mainConf.treeColWidth[colN]) + self.fontFlags = QFont("Monospace",10) + self.fontCount = QFont("Monospace",10) + logger.debug("DocTree initialisation complete") return @@ -139,34 +145,53 @@ class GuiDocTree(QTreeWidget): return theItem, theCount+1 def _addTreeItem(self, nwItem): + tName = nwItem.itemName tHandle = nwItem.itemHandle pHandle = nwItem.parHandle - tStatus = 0 + tStatus = NWItem.CLASS_FLAG[nwItem.itemClass] + if nwItem.itemType == nwItemType.FILE: + tStatus += "."+NWItem.LAYOUT_FLAG[nwItem.itemLayout] + nStatus = nwItem.itemStatus + if nStatus < 0 or nStatus >= len(self.theProject.statusIcons): + nStatus = 0 + newItem = QTreeWidgetItem([""]*4) - newItem.setText(self.C_NAME, tName) - newItem.setText(self.C_FLAGS, str(tStatus)) - newItem.setText(self.C_COUNT, "0") + + newItem.setText(self.C_NAME,tName) + + newItem.setText(self.C_COUNT,"0") + # newItem.setFont(self.C_COUNT,self.fontCount) + newItem.setForeground(self.C_COUNT,QColor(0,105,135)) + newItem.setTextAlignment(self.C_COUNT,Qt.AlignRight) + + newItem.setText(self.C_FLAGS,tStatus) + newItem.setIcon(self.C_FLAGS,self.theProject.statusIcons[nStatus]) + newItem.setFont(self.C_FLAGS,self.fontFlags) + newItem.setText(self.C_HANDLE, tHandle) + self.theMap[tHandle] = newItem if pHandle is None: self.addTopLevelItem(newItem) else: self.theMap[pHandle].addChild(newItem) self.propagateCount(tHandle, nwItem.wordCount) - newItem.setTextAlignment(self.C_COUNT,Qt.AlignRight) + newItem.setExpanded(nwItem.isExpanded) + if nwItem.itemType == nwItemType.ROOT: newItem.setIcon(self.C_NAME, QIcon.fromTheme("drive-harddisk")) elif nwItem.itemType == nwItemType.FOLDER: newItem.setIcon(self.C_NAME, QIcon.fromTheme("folder")) elif nwItem.itemType == nwItemType.FILE: newItem.setIcon(self.C_NAME, QIcon.fromTheme("x-office-document")) + return True def propagateCount(self, tHandle, theCount, nDepth=0): tItem = self.theMap[tHandle] - tItem.setText(self.C_COUNT,str(theCount)) + tItem.setText(self.C_COUNT,"{:n}".format(theCount)) pItem = tItem.parent() if pItem is not None: pCount = 0 @@ -184,7 +209,7 @@ class GuiDocTree(QTreeWidget): self._addTreeItem(nwItem) return True - def _getSelectedHandle(self): + def getSelectedHandle(self): selItem = self.selectedItems() if len(selItem) == 0: return None @@ -210,7 +235,7 @@ class GuiDocTree(QTreeWidget): """Overload the drop of dragged item event to check whether the drop is allowed or not. Disallowed drops are cancelled. """ - sHandle = self._getSelectedHandle() + sHandle = self.getSelectedHandle() if sHandle is None: return diff --git a/nw/gui/doctreectx.py b/nw/gui/doctreectx.py index 859bcd9b..a1a3ce58 100644 --- a/nw/gui/doctreectx.py +++ b/nw/gui/doctreectx.py @@ -192,7 +192,7 @@ class GuiDocTreeCtx(QMenu): return def _buildSubMenuAddRoot(self, itemClass): - mnuSub = QAction("Add "+NWItem.CLASS_NAME[itemClass], self) + mnuSub = QAction("Add %s Root" % NWItem.CLASS_NAME[itemClass], self) mnuSub.triggered.connect(lambda: self._ctxSignal( nwItemAction.ADD_ROOT, itemClass, diff --git a/nw/gui/winmain.py b/nw/gui/winmain.py index 4e7f7fda..3bc6878d 100644 --- a/nw/gui/winmain.py +++ b/nw/gui/winmain.py @@ -21,6 +21,7 @@ from PyQt5.QtGui import QIcon from nw.gui.doctree import GuiDocTree from nw.gui.doctreectx import GuiDocTreeCtx from nw.gui.doceditor import GuiDocEditor +from nw.gui.docdetails import GuiDocDetails from nw.gui.projecteditor import GuiProjectEditor from nw.gui.statusbar import GuiMainStatus from nw.project.project import NWProject @@ -44,8 +45,9 @@ class GuiMain(QMainWindow): self.setWindowIcon(QIcon(path.join(self.mainConf.appPath,"..","novelWriter.svg"))) # Main GUI Elements - self.docEditor = GuiDocEditor(self) - self.treeView = GuiDocTree(self.theProject) + self.docEditor = GuiDocEditor(self) + self.docDetails = GuiDocDetails(self.theProject) + self.treeView = GuiDocTree(self.theProject) # Assemble Main Window self.stackPane = QStackedWidget() @@ -53,8 +55,14 @@ class GuiMain(QMainWindow): self.stackDoc = self.stackPane.addWidget(self.docEditor) self.stackPane.setCurrentIndex(self.stackNone) + self.treePane = QFrame() + self.treeBox = QVBoxLayout() + self.treeBox.addWidget(self.treeView) + self.treeBox.addWidget(self.docDetails) + self.treePane.setLayout(self.treeBox) + self.splitMain = QSplitter(Qt.Horizontal) - self.splitMain.addWidget(self.treeView) + self.splitMain.addWidget(self.treePane) self.splitMain.addWidget(self.stackPane) self.splitMain.setSizes(self.mainConf.mainPanePos) self.splitMain.splitterMoved.connect(self._splitMainMove) @@ -65,6 +73,7 @@ class GuiMain(QMainWindow): self._buildMenu() self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.customContextMenuRequested.connect(self._openDocTreeContextMenu) + self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) self.treeView.buildTree() @@ -159,6 +168,12 @@ class GuiMain(QMainWindow): # Internal Functions ## + def _treeSingleClick(self): + sHandle = self.treeView.getSelectedHandle() + if sHandle is not None: + self.docDetails.buildViewBox(sHandle) + return + def _treeDoubleClick(self, tItem, colNo): tHandle = tItem.text(3) logger.verbose("User double clicked tree item with handle %s" % tHandle) diff --git a/nw/project/item.py b/nw/project/item.py index fee4e182..4ec893a2 100644 --- a/nw/project/item.py +++ b/nw/project/item.py @@ -25,7 +25,7 @@ class NWItem(): MAX_DEPTH = 8 CLASS_NAME = { - nwItemClass.NO_CLASS : "Folder", + nwItemClass.NO_CLASS : "Custom", nwItemClass.NOVEL : "Novel", nwItemClass.PLOT : "Plot", nwItemClass.CHARACTER : "Characters", @@ -42,6 +42,15 @@ class NWItem(): nwItemClass.TIMELINE : "T", nwItemClass.OBJECT : "O", } + LAYOUT_NAME = { + nwItemLayout.NO_LAYOUT : "None", + nwItemLayout.TITLE_PAGE : "Title Page", + nwItemLayout.SIMPLE_PAGE : "Simple Page", + nwItemLayout.PARTITION : "Partition", + nwItemLayout.CHAPTER : "Chapter", + nwItemLayout.SCENE : "Scene", + nwItemLayout.NOTE : "Note", + } LAYOUT_FLAG = { nwItemLayout.NO_LAYOUT : "Xo", nwItemLayout.TITLE_PAGE : "Tt", @@ -49,6 +58,7 @@ class NWItem(): nwItemLayout.PARTITION : "Pt", nwItemLayout.CHAPTER : "Ch", nwItemLayout.SCENE : "Sc", + nwItemLayout.NOTE : "Nt", } def __init__(self): @@ -60,6 +70,7 @@ class NWItem(): self.itemType = nwItemType.NO_TYPE self.itemClass = nwItemClass.NO_CLASS self.itemLayout = nwItemLayout.NO_LAYOUT + self.itemStatus = 0 self.itemDepth = None self.hasChildren = False self.isExpanded = False @@ -83,12 +94,12 @@ class NWItem(): xSub = self._subPack(xPack,"name", text=str(self.itemName)) xSub = self._subPack(xPack,"type", text=str(self.itemType.name)) xSub = self._subPack(xPack,"class", text=str(self.itemClass.name)) + xSub = self._subPack(xPack,"status", text=str(self.itemStatus)) xSub = self._subPack(xPack,"depth", text=str(self.itemDepth)) xSub = self._subPack(xPack,"children", text=str(self.hasChildren)) xSub = self._subPack(xPack,"expanded", text=str(self.isExpanded)) - if self.itemClass == nwItemClass.NOVEL and self.itemLayout is not nwItemLayout.NO_LAYOUT: - xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name)) if self.itemType == nwItemType.FILE: + xSub = self._subPack(xPack,"layout", text=str(self.itemLayout.name)) xSub = self._subPack(xPack,"charCount", text=str(self.charCount), none=False) xSub = self._subPack(xPack,"wordCount", text=str(self.wordCount), none=False) xSub = self._subPack(xPack,"paraCount", text=str(self.paraCount), none=False) @@ -113,6 +124,7 @@ class NWItem(): elif tagName == "type": self.setType(tagValue) elif tagName == "class": self.setClass(tagValue) elif tagName == "layout": self.setLayout(tagValue) + elif tagName == "status": self.setStatus(tagValue) elif tagName == "depth": self.setDepth(tagValue) elif tagName == "children": self.setChildren(tagValue) elif tagName == "expanded": self.setExpanded(tagValue) @@ -182,6 +194,11 @@ class NWItem(): self.itemLayout = nwItemLayout.NO_LAYOUT return + def setStatus(self, theStatus): + theStatus = self._checkInt(theStatus,0) + self.itemStatus = theStatus + return + def setDepth(self, theDepth): theDepth = self._checkInt(theDepth,-1) if theDepth >= 0 and theDepth <= self.MAX_DEPTH: diff --git a/nw/project/project.py b/nw/project/project.py index 3abb0741..4c22b12c 100644 --- a/nw/project/project.py +++ b/nw/project/project.py @@ -19,6 +19,8 @@ from hashlib import sha256 from datetime import datetime from time import time +from PyQt5.QtGui import QIcon, QPixmap, QColor + from nw.enum import nwItemType, nwItemClass, nwItemAction from nw.project.item import NWItem @@ -41,6 +43,16 @@ class NWProject(): self.bookTitle = "" self.bookAuthors = [] + self.statusCols = [ + ("New", 100,100,100), + ("Note", 200, 50, 0), + ("Draft", 200,150, 0), + ("Finished", 50,200, 0), + ] + self.statusIcons = [] + self.statusLabels = [] + self._makeStatusIcons() + return ## @@ -79,6 +91,7 @@ class NWProject(): self.projName = "" self.bookTitle = "" self.bookAuthors = [] + self._makeStatusIcons() hNovel = self.newRoot("Novel", nwItemClass.NOVEL) hChars = self.newRoot("Characters", nwItemClass.CHARACTER) hWorld = self.newRoot("World", nwItemClass.WORLD) @@ -147,6 +160,7 @@ class NWProject(): nwItem.setFromTag(xValue.tag,xValue.text) self._appendItem(tHandle,pHandle,nwItem) + self._makeStatusIcons() self.mainConf.setRecent(self.projPath) return True @@ -365,6 +379,16 @@ class NWProject(): return + def _makeStatusIcons(self): + self.statusIcons = [] + self.statusLabels = [] + for sLabel, sR, sG, sB in self.statusCols: + theIcon = QPixmap(32,32) + theIcon.fill(QColor(sR,sG,sB)) + self.statusIcons.append(QIcon(theIcon)) + self.statusLabels.append(sLabel) + return + def _makeHandle(self, addSeed=""): newSeed = str(time()) + addSeed logger.verbose("Generating handle with seed '%s'" % newSeed) diff --git a/sample/sampleNovel/nwProject.nwx b/sample/sampleNovel/nwProject.nwx index 591883b8..773a6f65 100644 --- a/sample/sampleNovel/nwProject.nwx +++ b/sample/sampleNovel/nwProject.nwx @@ -1,5 +1,5 @@ - + Sample Project Sample Project @@ -11,6 +11,7 @@ Novel ROOT NOVEL + 0 0 True True @@ -19,6 +20,7 @@ New Chapter FOLDER NOVEL + 1 1 True True @@ -27,9 +29,11 @@ New Scene FILE NOVEL + 2 2 False False + SCENE 2575 377 5 @@ -38,9 +42,11 @@ Test File FILE NOVEL + 1 2 False False + SCENE 125 24 2 @@ -49,6 +55,7 @@ Characters ROOT CHARACTER + 0 0 True True @@ -57,9 +64,11 @@ Jon Smith FILE CHARACTER + 0 1 False False + NOTE 0 0 0 @@ -68,9 +77,11 @@ Jane Smith FILE CHARACTER + 0 1 False False + NOTE 0 0 0 @@ -79,6 +90,7 @@ World ROOT WORLD + 0 0 False False