diff --git a/nw/common.py b/nw/common.py index 872365df..b15e148a 100644 --- a/nw/common.py +++ b/nw/common.py @@ -35,6 +35,8 @@ from nw.constants import nwConst logger = logging.getLogger(__name__) def checkString(checkValue, defaultValue, allowNone=False): + """Check if a variable is a string or a none. + """ if allowNone: if checkValue == None: return None @@ -45,6 +47,8 @@ def checkString(checkValue, defaultValue, allowNone=False): return defaultValue def checkInt(checkValue, defaultValue, allowNone=False): + """Check if a variable is an integer or a none. + """ if allowNone: if checkValue == None: return None @@ -56,6 +60,8 @@ def checkInt(checkValue, defaultValue, allowNone=False): return defaultValue def checkBool(checkValue, defaultValue, allowNone=False): + """Check if a variable is a boolean or a none. + """ if allowNone: if checkValue == None: return None @@ -92,7 +98,8 @@ def isHandle(theString): return not invalidChar def colRange(rgbStart, rgbEnd, nStep): - + """Generate a range of colours from one RGB value to another. + """ if len(rgbStart) != 3 and len(rgbEnd) != 3 and nStep < 1: logger.error("Cannot create colour range from given parameters") return None @@ -124,7 +131,7 @@ def colRange(rgbStart, rgbEnd, nStep): def formatInt(theInt): """Formats an integer with k, M, G etc. """ - postFix = ["k","M","G","T","P","E"] + postFix = ["k", "M", "G", "T", "P", "E"] theVal = float(theInt) if theVal > 1000.0: @@ -153,7 +160,6 @@ def splitVersionNumber(vString): """ Splits a version string on the form aa.bb.cc into major, minor and patch, and computes an integer value aabbcc. """ - vMajor = 0 vMinor = 0 vPatch = 0 diff --git a/nw/config.py b/nw/config.py index 619125fa..97e78c90 100644 --- a/nw/config.py +++ b/nw/config.py @@ -88,7 +88,7 @@ class Config: self.guiSyntax = "default_light" self.guiIcons = "typicons_colour_light" self.guiDark = False - self.guiLang = "en" # Hardcoded for now + self.guiLang = "en" # Hardcoded for now since the GUI is only in English self.guiFont = "" self.guiFontSize = 11 self.guiScale = 1.0 # Set automatically by Theme class @@ -286,7 +286,7 @@ class Config: # Check if config file exists if self.confPath is not None: - if path.isfile(path.join(self.confPath,self.confFile)): + if path.isfile(path.join(self.confPath, self.confFile)): # If it exists, load it self.loadConfig() else: @@ -855,7 +855,9 @@ class Config: ## def _unpackList(self, inStr, listLen, listDefault, castTo=int): - inData = inStr.split(",") + """Unpack a comma separated string of items into a list. + """ + inData = inStr.split(",") outData = [] for i in range(listLen): try: @@ -865,9 +867,13 @@ class Config: return outData def _packList(self, inData): + """Pack a list of items into a comma separated string. + """ return ", ".join(str(inVal) for inVal in inData) def _parseLine(self, cnfParse, cnfSec, cnfName, cnfType, cnfDefault): + """Parse a line and return the correct datatype. + """ if cnfParse.has_section(cnfSec): if cnfParse.has_option(cnfSec, cnfName): if cnfType == self.CNF_STR: @@ -883,6 +889,8 @@ class Config: return cnfDefault def _checkNone(self, checkVal): + """Convert a string to a none type. + """ if checkVal is None: return None if isinstance(checkVal, str): diff --git a/nw/core/document.py b/nw/core/document.py index 8c8b523f..0c29f995 100644 --- a/nw/core/document.py +++ b/nw/core/document.py @@ -44,10 +44,11 @@ class NWDoc(): self.mainConf = nw.CONFIG self.theProject = theProject self.theParent = theParent - self.theItem = None - self.docHandle = None - self.fileLoc = None - self.docMeta = "" + + self.theItem = None + self.docHandle = None + self.fileLoc = None + self.docMeta = "" # Internal Mapping self.makeAlert = self.theParent.makeAlert diff --git a/nw/core/spellcheck.py b/nw/core/spellcheck.py index 28e25e40..d4740b56 100644 --- a/nw/core/spellcheck.py +++ b/nw/core/spellcheck.py @@ -195,7 +195,6 @@ class NWSpellEnchant(NWSpellCheck): class NWSpellEnchantDummy: """Fallback for when Enchant is selected, but not installed. """ - def __init__(self): return diff --git a/nw/core/tokenizer.py b/nw/core/tokenizer.py index 3050cfc0..2ea8f330 100644 --- a/nw/core/tokenizer.py +++ b/nw/core/tokenizer.py @@ -227,7 +227,6 @@ class Tokenizer(): """Set the text for the tokenizer from a handle. If theText is not set, load it from the file. """ - self.theHandle = theHandle self.theItem = self.theProject.projTree[theHandle] if self.theItem is None: @@ -308,7 +307,6 @@ class Tokenizer(): 4: The internal formatting map of the text, self.FMT_* 5: The style of the block, self.A_* """ - # RegExes for adding formatting tags within text lines rxFormats = [ (QRegularExpression(nwRegEx.FMT_I), [None, self.FMT_I_B, None, self.FMT_I_E]), @@ -457,7 +455,6 @@ class Tokenizer(): """Apply formatting to the text headers according to document layout and user settings. """ - # No special header formatting for notes and no-layout files if self.isNone or self.isNote: return diff --git a/nw/error.py b/nw/error.py index e8466657..4c3fef17 100644 --- a/nw/error.py +++ b/nw/error.py @@ -46,7 +46,7 @@ def formatHtmlErrMsg(exType, exValue, exTrace): "

Please report this error by submitting an issue report on " "GitHub, providing a description and this error message. " "URL: <{issueUrl}>.

" - "

Environment
Version: {nwVersion}, OS: {osType} ({osKernel})," + "

Environment
Version: {nwVersion}, OS: {osType} ({osKernel}), " "Python: {pyVersion} ({pyHexVer:#x}), Qt: {qtVers}, PyQt: {pyqtVers}

" "

Error Type
{exType}: {exMessage}

" "

Traceback
{exTrace}

" diff --git a/nw/gui/docmerge.py b/nw/gui/docmerge.py index f6a565d1..547cbd44 100644 --- a/nw/gui/docmerge.py +++ b/nw/gui/docmerge.py @@ -95,7 +95,6 @@ class GuiDocMerge(QDialog): create a new file in the same parent folder. The old files are not removed in the merge process, and must be deleted manually. """ - logger.verbose("GuiDocMerge merge button clicked") finalOrder = [] @@ -142,7 +141,6 @@ class GuiDocMerge(QDialog): are then added to the list view in order. The list itself can be reordered by the user. """ - tHandle = self.theParent.treeView.getSelectedHandle() self.sourceItem = tHandle if tHandle is None: diff --git a/nw/gui/docsplit.py b/nw/gui/docsplit.py index 60b4069a..218b4855 100644 --- a/nw/gui/docsplit.py +++ b/nw/gui/docsplit.py @@ -110,7 +110,6 @@ class GuiDocSplit(QDialog): settings. The old file is not removed in the merge process, and must be deleted manually. """ - logger.verbose("GuiDocSplit split button clicked") if self.sourceItem is None: @@ -132,7 +131,7 @@ class GuiDocSplit(QDialog): nLines = len(theLines) theLines.insert(0, "%Split Doc") logger.debug( - "Splitting document %s with %d lines" % (self.sourceItem,nLines) + "Splitting document %s with %d lines" % (self.sourceItem, nLines) ) finalOrder = [] @@ -210,7 +209,6 @@ class GuiDocSplit(QDialog): are then added to the list view in order. The list itself can be reordered by the user. """ - if self.sourceItem is None: self.sourceItem = self.theParent.treeView.getSelectedHandle() diff --git a/nw/gui/itemeditor.py b/nw/gui/itemeditor.py index b1c2421a..1a149a09 100644 --- a/nw/gui/itemeditor.py +++ b/nw/gui/itemeditor.py @@ -150,7 +150,6 @@ class GuiItemEditor(QDialog): def _doSave(self): """Save the setting to the item. """ - logger.verbose("ItemEditor save button clicked") itemName = self.editName.text() @@ -171,6 +170,8 @@ class GuiItemEditor(QDialog): return def _doClose(self): + """Close the dialog without saving the settings. + """ logger.verbose("ItemEditor close button clicked") self.close() return diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 2ac66e33..b81bac82 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -192,7 +192,8 @@ class GuiMainMenu(QMenuBar): ## def _buildProjectMenu(self): - + """Assemble the Project menu. + """ # Project self.projMenu = self.addMenu("&Project") @@ -295,7 +296,8 @@ class GuiMainMenu(QMenuBar): return def _buildDocumentMenu(self): - + """Assemble the Document menu. + """ # Document self.docuMenu = self.addMenu("&Document") @@ -377,7 +379,8 @@ class GuiMainMenu(QMenuBar): return def _buildEditMenu(self): - + """Assemble the Edit menu. + """ # Edit self.editMenu = self.addMenu("&Edit") @@ -439,7 +442,8 @@ class GuiMainMenu(QMenuBar): return def _buildViewMenu(self): - + """Assemble the View menu. + """ # View self.viewMenu = self.addMenu("&View") @@ -486,7 +490,8 @@ class GuiMainMenu(QMenuBar): return def _buildInsertMenu(self): - + """Assemble the Insert menu. + """ # Insert self.insertMenu = self.addMenu("&Insert") @@ -576,7 +581,8 @@ class GuiMainMenu(QMenuBar): return def _buildSearchMenu(self): - + """Assemble the Search menu. + """ # Search self.srcMenu = self.addMenu("&Search") @@ -627,7 +633,8 @@ class GuiMainMenu(QMenuBar): return def _buildFormatMenu(self): - + """Assemble the Format menu. + """ # Format self.fmtMenu = self.addMenu("&Format") @@ -732,7 +739,8 @@ class GuiMainMenu(QMenuBar): return def _buildToolsMenu(self): - + """Assemble the Tools menu. + """ # Tools self.toolsMenu = self.addMenu("&Tools") @@ -828,7 +836,8 @@ class GuiMainMenu(QMenuBar): return def _buildHelpMenu(self): - + """Assemble the Help menu. + """ # Help self.helpMenu = self.addMenu("&Help") diff --git a/nw/gui/outline.py b/nw/gui/outline.py index cd4ff718..c1986b98 100644 --- a/nw/gui/outline.py +++ b/nw/gui/outline.py @@ -195,8 +195,10 @@ class GuiOutline(QTreeWidget): tLine = int(tItem.text(self.colIndex[nwOutline.LINE])) except: tLine = 1 + logger.verbose("User selected entry with handle %s on line %s" % (tHandle, tLine)) self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True) + return def _itemSelected(self): @@ -208,6 +210,7 @@ class GuiOutline(QTreeWidget): tHandle = selItems[0].data(self.colIndex[nwOutline.TITLE], Qt.UserRole) sTitle = selItems[0].data(self.colIndex[nwOutline.LINE], Qt.UserRole) self.theParent.projMeta.showItem(tHandle, sTitle) + return def _headerRightClick(self, clickPos): @@ -232,6 +235,7 @@ class GuiOutline(QTreeWidget): if theItem in self.colIndex: self.setColumnHidden(self.colIndex[theItem], not isChecked) self._saveHeaderState() + return ## diff --git a/nw/gui/preferences.py b/nw/gui/preferences.py index 31986546..8b103194 100644 --- a/nw/gui/preferences.py +++ b/nw/gui/preferences.py @@ -80,7 +80,9 @@ class GuiPreferences(PagedDialog): ## def _doSave(self): - + """Trigger all the save functions in the tabs, and collect the + status of the saves. + """ logger.verbose("ConfigEditor save button clicked") validEntries = True @@ -115,6 +117,8 @@ class GuiPreferences(PagedDialog): return def _doClose(self): + """Close the preferences without saving the changes. + """ logger.verbose("ConfigEditor close button clicked") self.close() return @@ -284,7 +288,8 @@ class GuiConfigEditGeneralTab(QWidget): return def saveValues(self): - + """Save the values set for this tab. + """ validEntries = True needsRestart = False @@ -329,7 +334,6 @@ class GuiConfigEditGeneralTab(QWidget): def _backupFolder(self): """Open a dialog to select the backup folder. """ - currDir = self.backupPath if not path.isdir(currDir): currDir = "" @@ -515,7 +519,8 @@ class GuiConfigEditLayoutTab(QWidget): return def saveValues(self): - + """Save the values set for this tab. + """ validEntries = True needsRestart = False @@ -681,7 +686,8 @@ class GuiConfigEditEditingTab(QWidget): return def saveValues(self): - + """Save the values set for this tab. + """ validEntries = True needsRestart = False @@ -712,6 +718,8 @@ class GuiConfigEditEditingTab(QWidget): ## def _disableComboItem(self, theList, theValue): + """Disable a list item in the combo box. + """ theIdx = theList.findData(theValue) theModel = theList.model() anItem = theModel.item(1) @@ -719,6 +727,8 @@ class GuiConfigEditEditingTab(QWidget): return theModel def _doUpdateSpellTool(self, currIdx): + """Update the list of dictionaries based on spell tool selected. + """ spellTool = self.spellToolList.currentData() self._updateLanguageList(spellTool) return @@ -903,7 +913,8 @@ class GuiConfigEditAutoReplaceTab(QWidget): return def saveValues(self): - + """Save the values set for this tab. + """ validEntries = True needsRestart = False diff --git a/nw/gui/projload.py b/nw/gui/projload.py index 738a493c..eddbbb33 100644 --- a/nw/gui/projload.py +++ b/nw/gui/projload.py @@ -152,6 +152,7 @@ class GuiProjectLoad(QDialog): """ logger.verbose("GuiProjectLoad open button clicked") self._saveDialogState() + selItems = self.listBox.selectedItems() if selItems: self.openPath = selItems[0].data(self.C_NAME, Qt.UserRole) @@ -160,6 +161,7 @@ class GuiProjectLoad(QDialog): else: self.openPath = None self.openState = self.NONE_STATE + return def _doSelectRecent(self): @@ -189,6 +191,7 @@ class GuiProjectLoad(QDialog): self.openPath = thePath self.openState = self.OPEN_STATE self.accept() + return def _doClose(self): diff --git a/nw/gui/projsettings.py b/nw/gui/projsettings.py index b8b47a90..86ea3ead 100644 --- a/nw/gui/projsettings.py +++ b/nw/gui/projsettings.py @@ -97,6 +97,7 @@ class GuiProjectSettings(PagedDialog): bookTitle = self.tabMain.editTitle.text() bookAuthors = self.tabMain.editAuthors.toPlainText() doBackup = not self.tabMain.doBackup.isChecked() + self.theProject.setProjectName(projName) self.theProject.setBookTitle(bookTitle) self.theProject.setBookAuthors(bookAuthors) @@ -105,11 +106,14 @@ class GuiProjectSettings(PagedDialog): 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() + if self.tabReplace.arChanged: newList = self.tabReplace.getNewList() self.theProject.setAutoReplace(newList) @@ -119,7 +123,7 @@ class GuiProjectSettings(PagedDialog): return def _doClose(self): - """Close the dialog. + """Save settings and close the dialog. """ winWidth = self.mainConf.rpxInt(self.width()) winHeight = self.mainConf.rpxInt(self.height()) @@ -372,6 +376,8 @@ class GuiProjectEditStatus(QWidget): ## def _selectColour(self): + """Open a dialog to select the status icon colour. + """ logger.verbose("Item colour button clicked") if self.selColour is not None: newCol = QColorDialog.getColor( @@ -386,6 +392,8 @@ class GuiProjectEditStatus(QWidget): return def _newItem(self): + """Create a new status item. + """ logger.verbose("New item button clicked") newItem = self._addItem("New Item", (0, 0, 0), None, 0) newItem.setBackground(QBrush(QColor(0, 255, 0, 80))) @@ -393,6 +401,8 @@ class GuiProjectEditStatus(QWidget): return def _delItem(self): + """Delete a status item. + """ logger.verbose("Delete item button clicked") selItem = self._getSelectedItem() if selItem is not None: @@ -408,6 +418,8 @@ class GuiProjectEditStatus(QWidget): return def _saveItem(self): + """Save changes made to a status item. + """ logger.verbose("Save item button clicked") selItem = self._getSelectedItem() iRow = self.listBox.row(selItem) @@ -427,6 +439,8 @@ class GuiProjectEditStatus(QWidget): return def _addItem(self, iName, iCol, oName, nUse): + """Add a status item to the list. + """ newIcon = QPixmap(self.iPx, self.iPx) newIcon.fill(QColor(*iCol)) newItem = QListWidgetItem() @@ -439,11 +453,14 @@ class GuiProjectEditStatus(QWidget): return newItem def _selectedItem(self): + """Extract the info of a selected item and populate the settings + boxes and button. + """ logger.verbose("Item selected") selItem = self._getSelectedItem() if selItem is not None: - selIdx = selItem.data(Qt.UserRole) - selVal = self.colData[selIdx] + selIdx = selItem.data(Qt.UserRole) + selVal = self.colData[selIdx] self.selColour = QColor(selVal[1], selVal[2], selVal[3]) newIcon = QPixmap(self.iPx, self.iPx) newIcon.fill(self.selColour) @@ -459,6 +476,8 @@ class GuiProjectEditStatus(QWidget): ## def _getSelectedItem(self): + """Get the currently selected item. + """ selItem = self.listBox.selectedItems() if len(selItem) == 0: return None @@ -467,6 +486,8 @@ class GuiProjectEditStatus(QWidget): return None def _rowsMoved(self): + """A row has been moved, so sett the changed flag. + """ logger.verbose("A drag move event occurred") self.colChanged = True return @@ -506,9 +527,9 @@ class GuiProjectEditReplace(QWidget): self.editKey = QLineEdit() self.editValue = QLineEdit() - self.saveButton = QPushButton(self.theTheme.getIcon("done"),"") - self.addButton = QPushButton(self.theTheme.getIcon("add"),"") - self.delButton = QPushButton(self.theTheme.getIcon("remove"),"") + self.saveButton = QPushButton(self.theTheme.getIcon("done"), "") + self.addButton = QPushButton(self.theTheme.getIcon("add"), "") + self.delButton = QPushButton(self.theTheme.getIcon("remove"), "") self.saveButton.setToolTip("Save entry") self.addButton.setToolTip("Add new entry") self.delButton.setToolTip("Delete selected entry") @@ -536,11 +557,13 @@ class GuiProjectEditReplace(QWidget): return def getNewList(self): + """Extract the list from the widget. + """ newList = {} for n in range(self.listBox.topLevelItemCount()): tItem = self.listBox.topLevelItem(n) - aKey = self._stripNotAllowed(tItem.text(0)) - aVal = tItem.text(1) + aKey = self._stripNotAllowed(tItem.text(0)) + aVal = tItem.text(1) if len(aKey) > 0: newList[aKey] = aVal return newList @@ -550,6 +573,9 @@ class GuiProjectEditReplace(QWidget): ## def _selectedItem(self): + """Extract the details from the selected item and populate the + edit form. + """ selItem = self._getSelectedItem() if selItem is None: return False @@ -564,7 +590,8 @@ class GuiProjectEditReplace(QWidget): return True def _saveEntry(self): - + """Save the form data into the list widget. + """ selItem = self._getSelectedItem() if selItem is None: return False @@ -586,6 +613,8 @@ class GuiProjectEditReplace(QWidget): return def _addEntry(self): + """Add a new list entry. + """ saveKey = "" % (self.listBox.topLevelItemCount() + 1) newVal = "" newItem = QTreeWidgetItem([saveKey, newVal]) @@ -593,6 +622,8 @@ class GuiProjectEditReplace(QWidget): return True def _delEntry(self): + """Delete the selected entry. + """ selItem = self._getSelectedItem() if selItem is None: return False @@ -601,12 +632,16 @@ class GuiProjectEditReplace(QWidget): return True def _getSelectedItem(self): + """Extract the currently selected item. + """ selItem = self.listBox.selectedItems() if len(selItem) == 0: return None return selItem[0] def _stripNotAllowed(self, theKey): + """Clean up the replace key string. + """ retKey = "" for c in theKey: if c.isalnum(): diff --git a/nw/gui/projtree.py b/nw/gui/projtree.py index 3baf5257..124e0a59 100644 --- a/nw/gui/projtree.py +++ b/nw/gui/projtree.py @@ -90,7 +90,7 @@ class GuiProjectTree(QTreeWidget): # for some fonts like the Ubuntu font. treeHeader = self.header() treeHeader.setStretchLastSection(True) - treeHeader.setMinimumSectionSize(iPx+6) + treeHeader.setMinimumSectionSize(iPx + 6) # Allow Move by Drag & Drop self.setDragEnabled(True) @@ -237,6 +237,7 @@ class GuiProjectTree(QTreeWidget): has focus. This also applies when the menu is used. """ if qApp.focusWidget() == self and self.theParent.hasProject: + tHandle = self.getSelectedHandle() tItem = self._getTreeItem(tHandle) pItem = tItem.parent() @@ -248,6 +249,7 @@ class GuiProjectTree(QTreeWidget): return False cItem = self.takeTopLevelItem(tIndex) self.insertTopLevelItem(nIndex, cItem) + else: tIndex = pItem.indexOfChild(tItem) nChild = pItem.childCount() @@ -256,11 +258,14 @@ class GuiProjectTree(QTreeWidget): return False cItem = pItem.takeChild(tIndex) pItem.insertChild(nIndex, cItem) + self.clearSelection() cItem.setSelected(True) self._setTreeChanged(True) + else: return False + return True def saveTreeOrder(self): @@ -516,8 +521,10 @@ class GuiProjectTree(QTreeWidget): for i in range(pItem.childCount()): pCount += int(pItem.child(i).text(self.C_COUNT)) pHandle = pItem.data(self.C_NAME, Qt.UserRole) + if not nDepth > 200 and pHandle != "": self.propagateCount(pHandle, pCount, nDepth+1) + return def projectWordCount(self): @@ -533,9 +540,11 @@ class GuiProjectTree(QTreeWidget): if tItem == self.orphRoot: continue nWords += int(tItem.text(self.C_COUNT)) + self.theProject.setProjectWordCount(nWords) sWords = self.theProject.getSessionWordCount() self.theParent.statusBar.setStats(nWords,sWords) + return def buildTree(self): @@ -547,9 +556,11 @@ class GuiProjectTree(QTreeWidget): logger.debug("Building project tree ...") self.clear() iCount = 0 + for nwItem in self.theProject.getProjectItems(): iCount += 1 self._addTreeItem(nwItem) + logger.debug("%d items added to project tree" % iCount) return True @@ -558,10 +569,13 @@ class GuiProjectTree(QTreeWidget): selected, return the first. """ selItem = self.selectedItems() + if len(selItem) == 0: return None + if isinstance(selItem[0], QTreeWidgetItem): return selItem[0].data(self.C_NAME, Qt.UserRole) + return None def getSelectedHandles(self): @@ -572,6 +586,7 @@ class GuiProjectTree(QTreeWidget): for n in range(len(selItems)): if isinstance(selItems[n], QTreeWidgetItem): selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole)) + return selHandles def setSelectedHandle(self, tHandle, doScroll=False): @@ -580,12 +595,14 @@ class GuiProjectTree(QTreeWidget): if tHandle in self.theMap: self.clearSelection() self.theMap[tHandle].setSelected(True) + selItems = self.selectedIndexes() if selItems and doScroll: self.scrollTo( selItems[0], QAbstractItemView.PositionAtCenter ) return True + return False ## @@ -601,9 +618,11 @@ class GuiProjectTree(QTreeWidget): tHandle = selItem.data(self.C_NAME, Qt.UserRole) tItem = self.theProject.projTree[tHandle] self.setSelectedHandle(tHandle) # Just to be safe + if self.ctxMenu.filterActions(tItem): # Only open menu if any actions remain after filter self.ctxMenu.exec_(self.viewport().mapToGlobal(clickPos)) + return ## @@ -615,9 +634,11 @@ class GuiProjectTree(QTreeWidget): mouse in a blank area of the tree view. """ QTreeWidget.mousePressEvent(self, theEvent) + selItem = self.indexAt(theEvent.pos()) if not selItem.isValid(): self.clearSelection() + return def dropEvent(self, theEvent): @@ -766,6 +787,7 @@ class GuiProjectTree(QTreeWidget): trashHandle = self.theProject.trashFolder() if trashHandle is None: return None + trItem = self._getTreeItem(trashHandle) if trItem is None: trItem = self._addTreeItem( @@ -773,6 +795,7 @@ class GuiProjectTree(QTreeWidget): ) trItem.setExpanded(True) self._setTreeChanged(True) + return trItem def _addOrphanedRoot(self): @@ -790,6 +813,7 @@ class GuiProjectTree(QTreeWidget): newItem.setExpanded(True) newItem.setData(self.C_NAME, Qt.UserRole, "") newItem.setIcon(self.C_NAME, self.theTheme.getIcon("proj_orphan")) + return def _cleanOrphanedRoot(self): @@ -834,10 +858,12 @@ class GuiProjectTree(QTreeWidget): if trItemP is None: logger.error("Failed to find new parent item of %s" % tHandle) return + pHandle = trItemP.data(self.C_NAME, Qt.UserRole) nwItemS.setParent(pHandle) self.setTreeItemValues(tHandle) self._setTreeChanged(True) + return def _setTreeChanged(self, theState): diff --git a/nw/gui/statusbar.py b/nw/gui/statusbar.py index 395fc5cc..330d7de9 100644 --- a/nw/gui/statusbar.py +++ b/nw/gui/statusbar.py @@ -264,7 +264,7 @@ class StatusLED(QAbstractButton): qPaint.setPen(qPalette.dark().color()) qPaint.setBrush(self._theCol) qPaint.setOpacity(1.0) - qPaint.drawEllipse(1, 1, self.width()-2, self.height()-2) + qPaint.drawEllipse(1, 1, self.width() - 2, self.height() - 2) return # END Class StatusLED diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 0265df62..134d91dd 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -77,11 +77,11 @@ class GuiTheme: self.themeLicenseUrl = "" ## GUI - self.treeWCount = [ 0, 0, 0] - self.statNone = [120,120,120] - self.statUnsaved = [120,120, 40] - self.statSaved = [ 40,120, 0] - self.helpText = [ 0, 0, 0] + self.treeWCount = [ 0, 0, 0] + self.statNone = [120, 120, 120] + self.statUnsaved = [120, 120, 40] + self.statSaved = [ 40, 120, 0] + self.helpText = [ 0, 0, 0] # Loaded Syntax Settings @@ -95,22 +95,22 @@ class GuiTheme: self.syntaxLicenseUrl = "" ## Colours - self.colBack = [255,255,255] - self.colText = [ 0, 0, 0] - self.colLink = [ 0, 0, 0] - self.colHead = [ 0, 0, 0] - self.colHeadH = [ 0, 0, 0] - self.colEmph = [ 0, 0, 0] - self.colDialN = [ 0, 0, 0] - self.colDialD = [ 0, 0, 0] - self.colDialS = [ 0, 0, 0] - self.colComm = [ 0, 0, 0] - self.colKey = [ 0, 0, 0] - self.colVal = [ 0, 0, 0] - self.colSpell = [ 0, 0, 0] - self.colTagErr = [ 0, 0, 0] - self.colRepTag = [ 0, 0, 0] - self.colMod = [ 0, 0, 0] + self.colBack = [255, 255, 255] + self.colText = [ 0, 0, 0] + self.colLink = [ 0, 0, 0] + self.colHead = [ 0, 0, 0] + self.colHeadH = [ 0, 0, 0] + self.colEmph = [ 0, 0, 0] + self.colDialN = [ 0, 0, 0] + self.colDialD = [ 0, 0, 0] + self.colDialS = [ 0, 0, 0] + self.colComm = [ 0, 0, 0] + self.colKey = [ 0, 0, 0] + self.colVal = [ 0, 0, 0] + self.colSpell = [ 0, 0, 0] + self.colTagErr = [ 0, 0, 0] + self.colRepTag = [ 0, 0, 0] + self.colMod = [ 0, 0, 0] # Changeable Settings self.guiTheme = None @@ -144,9 +144,9 @@ class GuiTheme: qMetric = QFontMetrics(self.guiFont) self.fontPointSize = self.guiFont.pointSizeF() self.fontPixelSize = int(round(qMetric.height())) - self.baseIconSize = int(round(qMetric.ascent())) - self.textNHeight = qMetric.boundingRect("N").height() - self.textNWidth = qMetric.boundingRect("N").width() + self.baseIconSize = int(round(qMetric.ascent())) + self.textNHeight = qMetric.boundingRect("N").height() + self.textNWidth= qMetric.boundingRect("N").width() logger.verbose("GUI Font Family: %s" % self.guiFont.family()) logger.verbose("GUI Font Point Size: %.2f" % self.fontPointSize) @@ -223,10 +223,10 @@ class GuiTheme: self.guiTheme = self.mainConf.guiTheme self.guiSyntax = self.mainConf.guiSyntax self.themeRoot = self.mainConf.themeRoot - self.themePath = path.join(self.mainConf.themeRoot,self.guiPath,self.guiTheme) - self.syntaxFile = path.join(self.themeRoot,self.syntaxPath,self.guiSyntax+".conf") - self.confFile = path.join(self.themePath,self.confName) - self.cssFile = path.join(self.themePath,self.cssName) + self.themePath = path.join(self.mainConf.themeRoot, self.guiPath, self.guiTheme) + self.syntaxFile = path.join(self.themeRoot, self.syntaxPath, self.guiSyntax+".conf") + self.confFile = path.join(self.themePath, self.confName) + self.cssFile = path.join(self.themePath, self.cssName) self.loadTheme() self.loadSyntax() @@ -256,7 +256,7 @@ class GuiTheme: cssData = "" try: if path.isfile(self.cssFile): - with open(self.cssFile,mode="r",encoding="utf8") as inFile: + with open(self.cssFile, mode="r", encoding="utf8") as inFile: cssData = inFile.read() except Exception as e: logger.error("Could not load theme css file") @@ -329,13 +329,13 @@ class GuiTheme: ## Main cnfSec = "Main" if confParser.has_section(cnfSec): - self.syntaxName = self._parseLine( confParser, cnfSec, "name", "") - self.syntaxDescription = self._parseLine( confParser, cnfSec, "description", "") - self.syntaxAuthor = self._parseLine( confParser, cnfSec, "author", "") - self.syntaxCredit = self._parseLine( confParser, cnfSec, "credit", "") - self.syntaxUrl = self._parseLine( confParser, cnfSec, "url", "") - self.syntaxLicense = self._parseLine( confParser, cnfSec, "license", "") - self.syntaxLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "") + self.syntaxName = self._parseLine(confParser, cnfSec, "name", "") + self.syntaxDescription = self._parseLine(confParser, cnfSec, "description", "") + self.syntaxAuthor = self._parseLine(confParser, cnfSec, "author", "") + self.syntaxCredit = self._parseLine(confParser, cnfSec, "credit", "") + self.syntaxUrl = self._parseLine(confParser, cnfSec, "url", "") + self.syntaxLicense = self._parseLine(confParser, cnfSec, "license", "") + self.syntaxLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") ## Syntax cnfSec = "Syntax" @@ -376,7 +376,7 @@ class GuiTheme: confParser.read_file(inFile) except Exception as e: self.theParent.makeAlert( - ["Could not load theme config file.",str(e)], nwAlert.ERROR + ["Could not load theme config file.", str(e)], nwAlert.ERROR ) continue themeName = "" @@ -409,7 +409,7 @@ class GuiTheme: confParser.read_file(inFile) except Exception as e: self.theParent.makeAlert( - ["Could not load syntax file.",str(e)], nwAlert.ERROR + ["Could not load syntax file.", str(e)], nwAlert.ERROR ) return [] syntaxName = "" @@ -429,8 +429,10 @@ class GuiTheme: ## def _loadColour(self, confParser, cnfSec, cnfName): + """Load a colour value from a config string. + """ if confParser.has_option(cnfSec,cnfName): - inData = confParser.get(cnfSec,cnfName).split(",") + inData = confParser.get(cnfSec,cnfName).split(",") outData = [] try: outData.append(int(inData[0])) @@ -438,16 +440,18 @@ class GuiTheme: outData.append(int(inData[2])) except: logger.error("Could not load theme colours for '%s' from config file" % cnfName) - outData = [0,0,0] + outData = [0, 0, 0] else: logger.warning("Could not find theme colours for '%s' in config file" % cnfName) - outData = [0,0,0] + outData = [0, 0, 0] return outData def _setPalette(self, confParser, cnfSec, cnfName, paletteVal): + """Set a palette colour value from a config string. + """ readCol = [] if confParser.has_option(cnfSec,cnfName): - inData = confParser.get(cnfSec,cnfName).split(",") + inData = confParser.get(cnfSec,cnfName).split(",") try: readCol.append(int(inData[0])) readCol.append(int(inData[1])) @@ -547,8 +551,8 @@ class GuiIcons: "reference" : (None, None), ## Switches - "sticky-on" : (None, None), - "sticky-off" : (None, None), + "sticky-on" : (None, None), + "sticky-off" : (None, None), } DECO_MAP = { @@ -615,13 +619,13 @@ class GuiIcons: ## Main cnfSec = "Main" if confParser.has_section(cnfSec): - self.themeName = self._parseLine( confParser, cnfSec, "name", "") - self.themeDescription = self._parseLine( confParser, cnfSec, "description", "") - self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "") - self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "") - self.themeUrl = self._parseLine( confParser, cnfSec, "url", "") - self.themeLicense = self._parseLine( confParser, cnfSec, "license", "") - self.themeLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "") + self.themeName = self._parseLine(confParser, cnfSec, "name", "") + self.themeDescription = self._parseLine(confParser, cnfSec, "description", "") + self.themeAuthor = self._parseLine(confParser, cnfSec, "author", "") + self.themeCredit = self._parseLine(confParser, cnfSec, "credit", "") + self.themeUrl = self._parseLine(confParser, cnfSec, "url", "") + self.themeLicense = self._parseLine(confParser, cnfSec, "license", "") + self.themeLicenseUrl = self._parseLine(confParser, cnfSec, "licenseurl", "") ## Palette cnfSec = "Map" @@ -705,7 +709,7 @@ class GuiIcons: confParser.read_file(inFile) except Exception as e: self.theParent.makeAlert( - ["Could not load theme config file.",str(e)], nwAlert.ERROR + ["Could not load theme config file.", str(e)], nwAlert.ERROR ) continue themeName = "" @@ -730,7 +734,6 @@ class GuiIcons: an icon exists. Prefer svg files over png files. Always returns a QIcon. """ - if iconKey not in self.ICON_MAP: logger.error("Requested unknown icon name '%s'" % iconKey) return QIcon() diff --git a/nw/gui/writingstats.py b/nw/gui/writingstats.py index 6e4a4df8..955376a7 100644 --- a/nw/gui/writingstats.py +++ b/nw/gui/writingstats.py @@ -90,7 +90,7 @@ class GuiWritingStats(QDialog): ) self.listBox = QTreeWidget() - self.listBox.setHeaderLabels(["Session Start","Length","Words","Histogram"]) + self.listBox.setHeaderLabels(["Session Start", "Length", "Words", "Histogram"]) self.listBox.setIndentation(0) self.listBox.setColumnWidth(self.C_TIME, wCol0) self.listBox.setColumnWidth(self.C_LENGTH, wCol1) diff --git a/nw/guimain.py b/nw/guimain.py index 46db9d1f..8c760531 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -128,7 +128,7 @@ class GuiMain(QMainWindow): self.tabWidget = QTabWidget() self.tabWidget.setTabPosition(QTabWidget.East) self.tabWidget.setStyleSheet("QTabWidget::pane {border: 0;}") - self.tabWidget.addTab(self.splitDocs, "Editor") + self.tabWidget.addTab(self.splitDocs, "Editor") self.tabWidget.addTab(self.splitOutline, "Outline") self.tabWidget.currentChanged.connect(self._mainTabChanged) @@ -139,8 +139,6 @@ class GuiMain(QMainWindow): self.splitMain.addWidget(self.tabWidget) self.splitMain.setSizes(self.mainConf.getMainPanePos()) - self.setCentralWidget(self.splitMain) - self.idxTree = self.splitMain.indexOf(self.treePane) self.idxMain = self.splitMain.indexOf(self.tabWidget) self.idxEditor = self.splitDocs.indexOf(self.docEditor) @@ -167,8 +165,8 @@ class GuiMain(QMainWindow): # Set Main Window Elements self.setMenuBar(self.mainMenu) + self.setCentralWidget(self.splitMain) self.setStatusBar(self.statusBar) - self.statusBar.setStatus("Ready") # Finalise Initialisation ########################## @@ -222,6 +220,7 @@ class GuiMain(QMainWindow): self.manageProjects() logger.debug("novelWriter is ready ...") + self.statusBar.setStatus("novelWriter is ready ...") return @@ -348,9 +347,7 @@ class GuiMain(QMainWindow): return saveOK def openProject(self, projFile): - """Open a project. The parameter projFile is passed from the - open recent projects menu, and must be set to be forwarded to - the project class. Otherwise, we just return. + """Open a project from a projFile path. """ if projFile is None: return False @@ -365,43 +362,47 @@ class GuiMain(QMainWindow): # Try to open the project if not self.theProject.openProject(projFile): - if self.theProject.lockedBy is not None: - if self.mainConf.showGUI: - try: - lockDetails = ( - "

The project was locked by the computer " - "'%s' (%s %s), last active on %s" - ) % ( - self.theProject.lockedBy[0], - self.theProject.lockedBy[1], - self.theProject.lockedBy[2], - datetime.fromtimestamp( - int(self.theProject.lockedBy[3]) - ).strftime("%x %X") - ) - except: - lockDetails = "" + # The project open failed. - msgBox = QMessageBox() - msgRes = msgBox.warning( - self, "Project Locked", ( - "The project is already open by another instance of novelWriter, and " - "is therefore locked. Override lock and continue anyway?

" - "Note: If the program or the computer previously crashed, the lock " - "can safely be overridden. If, however, another instance of " - "novelWriter has the project open, overriding the lock may corrupt " - "the project, and is not recommended.%s" - ) % lockDetails, - QMessageBox.Yes | QMessageBox.No, QMessageBox.No - ) - if msgRes == QMessageBox.Yes: - if not self.theProject.openProject(projFile, overrideLock=True): - return False - else: - return False - else: + if self.theProject.lockedBy is None: + # The project is not locked, so failed for some other + # reason handled by the project class. return False + if self.mainConf.showGUI: + try: + lockDetails = ( + "

The project was locked by the computer " + "'%s' (%s %s), last active on %s" + ) % ( + self.theProject.lockedBy[0], + self.theProject.lockedBy[1], + self.theProject.lockedBy[2], + datetime.fromtimestamp( + int(self.theProject.lockedBy[3]) + ).strftime("%x %X") + ) + except: + lockDetails = "" + + msgBox = QMessageBox() + msgRes = msgBox.warning( + self, "Project Locked", ( + "The project is already open by another instance of novelWriter, and " + "is therefore locked. Override lock and continue anyway?

" + "Note: If the program or the computer previously crashed, the lock " + "can safely be overridden. If, however, another instance of " + "novelWriter has the project open, overriding the lock may corrupt " + "the project, and is not recommended.%s" + ) % lockDetails, + QMessageBox.Yes | QMessageBox.No, QMessageBox.No + ) + if msgRes == QMessageBox.Yes: + if not self.theProject.openProject(projFile, overrideLock=True): + return False + else: + return False + # Project is loaded self.hasProject = True @@ -570,7 +571,7 @@ class GuiMain(QMainWindow): dlgOpt = QFileDialog.Options() dlgOpt |= QFileDialog.DontUseNativeDialog inPath = QFileDialog.getOpenFileName( - self,"Import File",lastPath,options=dlgOpt,filter=";;".join(extFilter) + self, "Import File", lastPath, options=dlgOpt, filter=";;".join(extFilter) ) if inPath: loadFile = inPath[0] @@ -582,7 +583,7 @@ class GuiMain(QMainWindow): theText = None try: - with open(loadFile,mode="rt",encoding="utf8") as inFile: + with open(loadFile, mode="rt", encoding="utf8") as inFile: theText = inFile.read() self.mainConf.setLastPath(loadFile) except Exception as e: @@ -602,7 +603,7 @@ class GuiMain(QMainWindow): if not self.docEditor.isEmpty(): if self.mainConf.showGUI: msgBox = QMessageBox() - msgRes = msgBox.question(self, "Import Document",( + msgRes = msgBox.question(self, "Import Document", ( "Importing the file will overwrite the current content of the document. " "Do you want to proceed?" )) @@ -917,6 +918,8 @@ class GuiMain(QMainWindow): return True def setFocus(self, paneNo): + """Switch focus to one of the three main gUi panes. + """ if paneNo == 1: self.treeView.setFocus() elif paneNo == 2: @@ -926,6 +929,8 @@ class GuiMain(QMainWindow): return def closeDocEditor(self): + """Close the document edit panel. This does not hide the editor. + """ self.closeDocument() self.theProject.setLastEdited(None) return @@ -1059,6 +1064,8 @@ class GuiMain(QMainWindow): return True def _setWindowTitle(self, projName=None): + """Set the window title and add the project's working title. + """ winTitle = self.mainConf.appName if projName is not None: winTitle += " - %s" % projName @@ -1066,19 +1073,30 @@ class GuiMain(QMainWindow): return True def _autoSaveProject(self): - if (self.hasProject and self.theProject.projChanged and - self.theProject.projPath is not None): + """Triggered by the auto-save project timer to save the project. + """ + doSave = self.hasProject + doSave &= self.theProject.projChanged + doSave &= self.theProject.projPath is not None + + if doSave: logger.debug("Autosaving project") self.saveProject(autoSave=True) + return def _autoSaveDocument(self): + """Triggered by the auto-save document timer to save the + document. + """ if self.hasProject and self.docEditor.docChanged: logger.debug("Autosaving document") self.saveDocument() return def _makeStatusIcons(self): + """Generate all the item status icons based on project settings. + """ self.statusIcons = {} iPx = self.mainConf.pxInt(32) for sLabel, sCol, _ in self.theProject.statusItems: @@ -1088,6 +1106,9 @@ class GuiMain(QMainWindow): return def _makeImportIcons(self): + """Generate all the item importance icons based on project + settings. + """ self.importIcons = {} iPx = self.mainConf.pxInt(32) for sLabel, sCol, _ in self.theProject.importItems: @@ -1101,6 +1122,9 @@ class GuiMain(QMainWindow): ## def closeEvent(self, theEvent): + """Capture the closing event of the GUI and call the close + function to handle all the close process steps. + """ if self.closeMain(): theEvent.accept() else: