diff --git a/nw/__init__.py b/nw/__init__.py index 61aa56bb..e0696373 100644 --- a/nw/__init__.py +++ b/nw/__init__.py @@ -172,7 +172,7 @@ def main(sysArgs=None): debugLevel = logging.INFO elif inOpt == "--debug": debugLevel = logging.DEBUG - logFormat = "[{asctime:}] {name:>30}:{lineno:<4d} {levelname:8} {message:}" + logFormat = "[{asctime:}] {name:>20}:{lineno:<4d} {levelname:8} {message:}" elif inOpt == "--logfile": logFile = inArg toFile = True @@ -180,7 +180,7 @@ def main(sysArgs=None): toStd = False elif inOpt == "--verbose": debugLevel = VERBOSE - logFormat = "[{asctime:}] {name:>30}:{lineno:<4d} {levelname:8} {message:}" + logFormat = "[{asctime:}] {name:>20}:{lineno:<4d} {levelname:8} {message:}" elif inOpt == "--style": qtStyle = inArg elif inOpt == "--config": @@ -196,7 +196,7 @@ def main(sysArgs=None): CONFIG.cmdOpen = cmdOpen # Set Logging - logFmt = logging.Formatter(fmt=logFormat,datefmt="%Y-%m-%d %H:%M:%S",style="{") + logFmt = logging.Formatter(fmt=logFormat, datefmt="%Y-%m-%d %H:%M:%S", style="{") if not logFile == "" and toFile: if path.isfile(logFile+".bak"): diff --git a/nw/assets/icons/fallback/hash-dark.svg b/nw/assets/icons/fallback/hash-dark.svg new file mode 100644 index 00000000..551d7993 --- /dev/null +++ b/nw/assets/icons/fallback/hash-dark.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/nw/assets/icons/fallback/hash.svg b/nw/assets/icons/fallback/hash.svg new file mode 100644 index 00000000..1553201b --- /dev/null +++ b/nw/assets/icons/fallback/hash.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/nw/assets/icons/typicons_colour_dark/hash.svg b/nw/assets/icons/typicons_colour_dark/hash.svg new file mode 100644 index 00000000..b1c34da8 --- /dev/null +++ b/nw/assets/icons/typicons_colour_dark/hash.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/nw/assets/icons/typicons_colour_dark/icons.conf b/nw/assets/icons/typicons_colour_dark/icons.conf index f3c96935..76290410 100644 --- a/nw/assets/icons/typicons_colour_dark/icons.conf +++ b/nw/assets/icons/typicons_colour_dark/icons.conf @@ -1,8 +1,9 @@ ## -# Icons: Typicons Colour Dark -# Source: https://github.com/stephenhutchings/typicons.font -# Credit: Stephen Hutchings -# Modified: Veronica Berglyd Olsen +# Icons: Typicons Colour Dark +# Source: https://github.com/stephenhutchings/typicons.font +# Credit: Stephen Hutchings +# Modified: Veronica Berglyd Olsen +# Additions: hash.svg ## [Main] @@ -39,3 +40,4 @@ save = download.svg edit = pencil.svg check = tick.svg cross = times.svg +hash = hash.svg diff --git a/nw/assets/icons/typicons_colour_light/hash.svg b/nw/assets/icons/typicons_colour_light/hash.svg new file mode 100644 index 00000000..e191e8b4 --- /dev/null +++ b/nw/assets/icons/typicons_colour_light/hash.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/nw/assets/icons/typicons_colour_light/icons.conf b/nw/assets/icons/typicons_colour_light/icons.conf index d3df7d01..08042fc9 100644 --- a/nw/assets/icons/typicons_colour_light/icons.conf +++ b/nw/assets/icons/typicons_colour_light/icons.conf @@ -1,8 +1,9 @@ ## -# Icons: Typicons Colour Light -# Source: https://github.com/stephenhutchings/typicons.font -# Credit: Stephen Hutchings -# Modified: Veronica Berglyd Olsen +# Icons: Typicons Colour Light +# Source: https://github.com/stephenhutchings/typicons.font +# Credit: Stephen Hutchings +# Modified: Veronica Berglyd Olsen +# Additions: hash.svg ## [Main] @@ -39,3 +40,4 @@ save = download.svg edit = pencil.svg check = tick.svg cross = times.svg +hash = hash.svg diff --git a/nw/assets/icons/typicons_grey_dark/hash.svg b/nw/assets/icons/typicons_grey_dark/hash.svg new file mode 100644 index 00000000..a6b9a654 --- /dev/null +++ b/nw/assets/icons/typicons_grey_dark/hash.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/nw/assets/icons/typicons_grey_dark/icons.conf b/nw/assets/icons/typicons_grey_dark/icons.conf index 75268e23..d81f4adf 100644 --- a/nw/assets/icons/typicons_grey_dark/icons.conf +++ b/nw/assets/icons/typicons_grey_dark/icons.conf @@ -1,7 +1,9 @@ ## -# Icons: Typicons Grey Dark -# Source: https://github.com/stephenhutchings/typicons.font -# Credit: Stephen Hutchings +# Icons: Typicons Grey Dark +# Source: https://github.com/stephenhutchings/typicons.font +# Credit: Stephen Hutchings +# Modified: Veronica Berglyd Olsen +# Additions: hash.svg ## [Main] @@ -38,3 +40,4 @@ save = download.svg edit = pencil.svg check = tick.svg cross = times.svg +hash = hash.svg diff --git a/nw/assets/icons/typicons_grey_light/hash.svg b/nw/assets/icons/typicons_grey_light/hash.svg new file mode 100644 index 00000000..1553201b --- /dev/null +++ b/nw/assets/icons/typicons_grey_light/hash.svg @@ -0,0 +1,55 @@ + + + + + + image/svg+xml + + + + + + + + + diff --git a/nw/assets/icons/typicons_grey_light/icons.conf b/nw/assets/icons/typicons_grey_light/icons.conf index 766aed98..2b26abc8 100644 --- a/nw/assets/icons/typicons_grey_light/icons.conf +++ b/nw/assets/icons/typicons_grey_light/icons.conf @@ -1,7 +1,9 @@ ## -# Icons: Typicons Grey Light -# Source: https://github.com/stephenhutchings/typicons.font -# Credit: Stephen Hutchings +# Icons: Typicons Grey Light +# Source: https://github.com/stephenhutchings/typicons.font +# Credit: Stephen Hutchings +# Modified: Veronica Berglyd Olsen +# Additions: hash.svg ## [Main] @@ -38,3 +40,4 @@ save = download.svg edit = pencil.svg check = tick.svg cross = times.svg +hash = hash.svg diff --git a/nw/constants/constants.py b/nw/constants/constants.py index 92378a73..66b026fc 100644 --- a/nw/constants/constants.py +++ b/nw/constants/constants.py @@ -136,7 +136,6 @@ class nwLabels(): } OUTLINE_COLS = { nwOutline.TITLE : "Title", - nwOutline.HANDLE : "Handle", nwOutline.LEVEL : "Level", nwOutline.LABEL : "Document", nwOutline.LINE : "Line", diff --git a/nw/constants/enum.py b/nw/constants/enum.py index c4a0f031..ee33c575 100644 --- a/nw/constants/enum.py +++ b/nw/constants/enum.py @@ -107,21 +107,20 @@ class nwAlert(Enum): class nwOutline(Enum): TITLE = 0 - HANDLE = 1 - LEVEL = 2 - LABEL = 3 - LINE = 4 - CCOUNT = 5 - WCOUNT = 6 - PCOUNT = 7 - POV = 8 - CHAR = 9 - PLOT = 10 - TIME = 11 - WORLD = 12 - OBJECT = 13 - ENTITY = 14 - CUSTOM = 15 - SYNOP = 16 + LEVEL = 1 + LABEL = 2 + LINE = 3 + CCOUNT = 4 + WCOUNT = 5 + PCOUNT = 6 + POV = 7 + CHAR = 8 + PLOT = 9 + TIME = 10 + WORLD = 11 + OBJECT = 12 + ENTITY = 13 + CUSTOM = 14 + SYNOP = 15 # END Enum nwOutline diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 28a7a654..4df869b5 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -5,23 +5,23 @@ from nw.gui.build import GuiBuildNovel from nw.gui.docbars import GuiDocTitleBar from nw.gui.docbars import GuiNoticeBar from nw.gui.docbars import GuiSearchBar -from nw.gui.docdetails import GuiDocDetails +from nw.gui.docdetails import GuiDocViewDetails from nw.gui.doceditor import GuiDocEditor from nw.gui.docmerge import GuiDocMerge from nw.gui.docsplit import GuiDocSplit -from nw.gui.doctree import GuiDocTree from nw.gui.docviewer import GuiDocViewer +from nw.gui.itemdetails import GuiItemDetails from nw.gui.itemeditor import GuiItemEditor from nw.gui.mainmenu import GuiMainMenu from nw.gui.outline import GuiProjectOutline from nw.gui.preferences import GuiPreferences -from nw.gui.projectload import GuiProjectLoad -from nw.gui.projectsettings import GuiProjectSettings +from nw.gui.projload import GuiProjectLoad +from nw.gui.projsettings import GuiProjectSettings +from nw.gui.projtree import GuiProjectTree from nw.gui.sessionlog import GuiSessionLogView from nw.gui.statusbar import GuiMainStatus from nw.gui.theme import GuiIcons from nw.gui.theme import GuiTheme -from nw.gui.viewdetails import GuiDocViewDetails __all__ = [ "GuiAbout", @@ -29,21 +29,21 @@ __all__ = [ "GuiDocTitleBar", "GuiNoticeBar", "GuiSearchBar", - "GuiDocDetails", + "GuiDocViewDetails", "GuiDocEditor", "GuiDocMerge", "GuiDocSplit", - "GuiDocTree", "GuiDocViewer", + "GuiItemDetails", "GuiItemEditor", "GuiMainMenu", "GuiProjectOutline", "GuiPreferences", "GuiProjectLoad", "GuiProjectSettings", + "GuiProjectTree", "GuiSessionLogView", "GuiMainStatus", "GuiIcons", "GuiTheme", - "GuiDocViewDetails", ] diff --git a/nw/gui/build.py b/nw/gui/build.py index 791dff67..f072b6ea 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -93,7 +93,7 @@ class GuiBuildNovel(QDialog): # Title Formats # ============= - self.titleGroup = QGroupBox("Novel Title Formats", self) + self.titleGroup = QGroupBox("Title Formats for Novel Files", self) self.titleForm = QGridLayout(self) self.titleGroup.setLayout(self.titleForm) @@ -169,9 +169,9 @@ class GuiBuildNovel(QDialog): # Text Options # ============= - self.textGroup = QGroupBox("Text Options", self) - self.textForm = QGridLayout(self) - self.textGroup.setLayout(self.textForm) + self.formatGroup = QGroupBox("Formatting Options", self) + self.formatForm = QGridLayout(self) + self.formatGroup.setLayout(self.formatForm) ## Font Family self.textFont = QLineEdit() @@ -212,29 +212,29 @@ class GuiBuildNovel(QDialog): self.optState.getBool("GuiBuildNovel", "noStyling", False) ) - self.textForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.textFont, 0, 1, 1, 1, Qt.AlignRight) - self.textForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight) - self.textForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight) - self.textForm.addWidget(QLabel("Justify text"), 2, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.justifyText, 2, 1, 1, 2, Qt.AlignRight) - self.textForm.addWidget(QLabel("Disable styling"), 3, 0, 1, 1, Qt.AlignLeft) - self.textForm.addWidget(self.noStyling, 3, 1, 1, 2, Qt.AlignRight) + self.formatForm.addWidget(QLabel("Font family"), 0, 0, 1, 1, Qt.AlignLeft) + self.formatForm.addWidget(self.textFont, 0, 1, 1, 1, Qt.AlignRight) + self.formatForm.addWidget(self.fontButton, 0, 2, 1, 1, Qt.AlignRight) + self.formatForm.addWidget(QLabel("Font size"), 1, 0, 1, 1, Qt.AlignLeft) + self.formatForm.addWidget(self.textSize, 1, 1, 1, 2, Qt.AlignRight) + self.formatForm.addWidget(QLabel("Justify text"), 2, 0, 1, 1, Qt.AlignLeft) + self.formatForm.addWidget(self.justifyText, 2, 1, 1, 2, Qt.AlignRight) + self.formatForm.addWidget(QLabel("Disable styling"), 3, 0, 1, 1, Qt.AlignLeft) + self.formatForm.addWidget(self.noStyling, 3, 1, 1, 2, Qt.AlignRight) - self.textForm.setColumnStretch(0, 1) - self.textForm.setColumnStretch(1, 0) - self.textForm.setColumnStretch(2, 0) + self.formatForm.setColumnStretch(0, 1) + self.formatForm.setColumnStretch(1, 0) + self.formatForm.setColumnStretch(2, 0) # Include Switches # ================ - self.includeGroup = QGroupBox("Include Non-Text Elements", self) - self.includeForm = QGridLayout(self) - self.includeGroup.setLayout(self.includeForm) + self.textGroup = QGroupBox("Text Options", self) + self.textForm = QGridLayout(self) + self.textGroup.setLayout(self.textForm) self.includeSynopsis = QSwitch() self.includeSynopsis.setToolTip( - "Include synopsis type comments in the output." + "Include synopsis comments in the output." ) self.includeSynopsis.setChecked(self.theProject.titleFormat["withSynopsis"]) @@ -250,21 +250,31 @@ class GuiBuildNovel(QDialog): ) self.includeKeywords.setChecked(self.theProject.titleFormat["withKeywords"]) - self.includeForm.addWidget(QLabel("Include synopsis"), 0, 0, 1, 1, Qt.AlignLeft) - self.includeForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight) - self.includeForm.addWidget(QLabel("Include comments"), 1, 0, 1, 1, Qt.AlignLeft) - self.includeForm.addWidget(self.includeComments, 1, 1, 1, 1, Qt.AlignRight) - self.includeForm.addWidget(QLabel("Include keywords"), 2, 0, 1, 1, Qt.AlignLeft) - self.includeForm.addWidget(self.includeKeywords, 2, 1, 1, 1, Qt.AlignRight) + self.includeBody = QSwitch() + self.includeBody.setToolTip( + "Include body text in the output." + ) + self.includeBody.setChecked( + self.optState.getBool("GuiBuildNovel", "includeBody", True) + ) - self.includeForm.setColumnStretch(0, 1) - self.includeForm.setColumnStretch(1, 0) + self.textForm.addWidget(QLabel("Include synopsis"), 0, 0, 1, 1, Qt.AlignLeft) + self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight) + self.textForm.addWidget(QLabel("Include comments"), 1, 0, 1, 1, Qt.AlignLeft) + self.textForm.addWidget(self.includeComments, 1, 1, 1, 1, Qt.AlignRight) + self.textForm.addWidget(QLabel("Include keywords"), 2, 0, 1, 1, Qt.AlignLeft) + self.textForm.addWidget(self.includeKeywords, 2, 1, 1, 1, Qt.AlignRight) + self.textForm.addWidget(QLabel("Include body text"), 3, 0, 1, 1, Qt.AlignLeft) + self.textForm.addWidget(self.includeBody, 3, 1, 1, 1, Qt.AlignRight) + + self.textForm.setColumnStretch(0, 1) + self.textForm.setColumnStretch(1, 0) # Additional Options # ================== - self.addsGroup = QGroupBox("Additional Options", self) - self.addsForm = QGridLayout(self) - self.addsGroup.setLayout(self.addsForm) + self.fileGroup = QGroupBox("File Options", self) + self.fileForm = QGridLayout(self) + self.fileGroup.setLayout(self.fileForm) self.novelFiles = QSwitch() self.novelFiles.setToolTip( @@ -290,26 +300,15 @@ class GuiBuildNovel(QDialog): self.optState.getBool("GuiBuildNovel", "ignoreFlag", False) ) - self.excludeBody = QSwitch() - self.excludeBody.setToolTip( - "Exclude body text in the output. Combine with 'Include synopsis' " - "for making outline." - ) - self.excludeBody.setChecked( - self.optState.getBool("GuiBuildNovel", "excludeBody", False) - ) + self.fileForm.addWidget(QLabel("Include novel files"), 0, 0, 1, 1, Qt.AlignLeft) + self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight) + self.fileForm.addWidget(QLabel("Include note files"), 1, 0, 1, 1, Qt.AlignLeft) + self.fileForm.addWidget(self.noteFiles, 1, 1, 1, 1, Qt.AlignRight) + self.fileForm.addWidget(QLabel("Ignore export flag"), 2, 0, 1, 1, Qt.AlignLeft) + self.fileForm.addWidget(self.ignoreFlag, 2, 1, 1, 1, Qt.AlignRight) - self.addsForm.addWidget(QLabel("Include novel files"), 0, 0, 1, 1, Qt.AlignLeft) - self.addsForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight) - self.addsForm.addWidget(QLabel("Include note files"), 1, 0, 1, 1, Qt.AlignLeft) - self.addsForm.addWidget(self.noteFiles, 1, 1, 1, 1, Qt.AlignRight) - self.addsForm.addWidget(QLabel("Ignore export flag"), 2, 0, 1, 1, Qt.AlignLeft) - self.addsForm.addWidget(self.ignoreFlag, 2, 1, 1, 1, Qt.AlignRight) - self.addsForm.addWidget(QLabel("Exclude body text"), 3, 0, 1, 1, Qt.AlignLeft) - self.addsForm.addWidget(self.excludeBody, 3, 1, 1, 1, Qt.AlignRight) - - self.addsForm.setColumnStretch(0, 1) - self.addsForm.setColumnStretch(1, 0) + self.fileForm.setColumnStretch(0, 1) + self.fileForm.setColumnStretch(1, 0) # Build Button # ============ @@ -373,9 +372,9 @@ class GuiBuildNovel(QDialog): # Assemble GUI # ============ self.toolsBox.addWidget(self.titleGroup) + self.toolsBox.addWidget(self.formatGroup) self.toolsBox.addWidget(self.textGroup) - self.toolsBox.addWidget(self.includeGroup) - self.toolsBox.addWidget(self.addsGroup) + self.toolsBox.addWidget(self.fileGroup) self.toolsBox.addStretch(1) self.toolsBox.addWidget(self.buildProgress) self.toolsBox.addWidget(self.buildNovel) @@ -388,6 +387,7 @@ class GuiBuildNovel(QDialog): self.outerBox.setStretch(1, 1) self.setLayout(self.outerBox) + self.buildNovel.setFocus() self.show() @@ -436,7 +436,7 @@ class GuiBuildNovel(QDialog): novelFiles = self.novelFiles.isChecked() noteFiles = self.noteFiles.isChecked() ignoreFlag = self.ignoreFlag.isChecked() - excludeBody = self.excludeBody.isChecked() + includeBody = self.includeBody.isChecked() makeHtml = ToHtml(self.theProject, self.theParent) makeHtml.setTitleFormat(fmtTitle) @@ -444,7 +444,7 @@ class GuiBuildNovel(QDialog): makeHtml.setUnNumberedFormat(fmtUnnumbered) makeHtml.setSceneFormat(fmtScene, fmtScene == "") makeHtml.setSectionFormat(fmtSection, fmtSection == "") - makeHtml.setBodyText(not excludeBody) + makeHtml.setBodyText(includeBody) makeHtml.setSynopsis(incSynopsis) makeHtml.setComments(incComments) makeHtml.setKeywords(incKeywords) @@ -867,7 +867,7 @@ class GuiBuildNovel(QDialog): self.optState.setValue("GuiBuildNovel", "addNovel", self.novelFiles.isChecked()) self.optState.setValue("GuiBuildNovel", "addNotes", self.noteFiles.isChecked()) self.optState.setValue("GuiBuildNovel", "ignoreFlag", self.ignoreFlag.isChecked()) - self.optState.setValue("GuiBuildNovel", "excludeBody", self.excludeBody.isChecked()) + self.optState.setValue("GuiBuildNovel", "includeBody", self.includeBody.isChecked()) self.optState.saveSettings() return diff --git a/nw/gui/docdetails.py b/nw/gui/docdetails.py index 39bc6fe6..d4b2621c 100644 --- a/nw/gui/docdetails.py +++ b/nw/gui/docdetails.py @@ -1,12 +1,12 @@ # -*- coding: utf-8 -*- -"""novelWriter GUI Document Details +"""novelWriter GUI DocView Details - novelWriter – GUI Document Details -==================================== - Class holding the left side document details panel + novelWriter – GUI DocView Details +=================================== + Class holding the document view details panel File History: - Created: 2019-04-24 [0.0.1] + Created: 2019-10-31 [0.3.2] This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen @@ -28,238 +28,124 @@ import logging import nw -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QFont, QIcon, QPixmap -from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel - -from nw.constants import ( - nwLabels, nwItemClass, nwItemType, nwItemLayout, nwUnicode +from PyQt5.QtCore import Qt, QSize +from PyQt5.QtWidgets import ( + QWidget, QLabel, QScrollArea, QFrame, QToolButton, QCheckBox, QGridLayout ) logger = logging.getLogger(__name__) -class GuiDocDetails(QFrame): +class GuiDocViewDetails(QWidget): def __init__(self, theParent, theProject): - QFrame.__init__(self, theParent) + QWidget.__init__(self, theParent) - logger.debug("Initialising DocDetails ...") + logger.debug("Initialising DocViewDetails ...") self.mainConf = nw.CONFIG self.theParent = theParent self.theProject = theProject - self.theTheme = theParent.theTheme - self.theHandle = None + self.currHandle = None - self.mainBox = QGridLayout(self) - self.mainBox.setVerticalSpacing(1) - self.mainBox.setHorizontalSpacing(6) - self.setLayout(self.mainBox) + self.outerBox = QGridLayout(self) + self.outerBox.setContentsMargins(0,0,0,0) + self.outerBox.setHorizontalSpacing(0) + self.outerBox.setVerticalSpacing(4) - self.pS = 0.9*self.theTheme.fontPointSize - self.iPx = self.theTheme.textIconSize - self.sPx = int(round(0.8*self.theTheme.textIconSize)) + self.refLabel = QLabel("Referenced By", self) - self.expCheck = self.theTheme.getPixmap("check", (self.iPx, self.iPx)) - self.expCross = self.theTheme.getPixmap("cross", (self.iPx, self.iPx)) + self.showHide = QToolButton() + self.showHide.setStyleSheet("QToolButton { border: none; }") + self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly) + self.showHide.setArrowType(Qt.DownArrow) + self.showHide.setCheckable(True) + self.showHide.setIconSize(QSize(16,16)) + self.showHide.toggled.connect(self._doShowHide) - self.fntLabel = QFont() - self.fntLabel.setBold(True) - self.fntLabel.setPointSizeF(self.pS) + self.isSticky = QCheckBox("Sticky") + self.isSticky.setChecked(False) + self.isSticky.toggled.connect(self._doSticky) - self.fntValue = QFont() - self.fntValue.setPointSizeF(self.pS) + self.refList = QLabel("None") + self.refList.setWordWrap(True) + self.refList.setAlignment(Qt.AlignTop) + self.refList.setScaledContents(True) + self.refList.linkActivated.connect(self._linkClicked) - # Label - self.labelName = QLabel("Label") - self.labelName.setFont(self.fntLabel) - self.labelName.setAlignment(Qt.AlignLeft | Qt.AlignBaseline) + self.scrollBox = QScrollArea() + self.scrollBox.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.scrollBox.setFrameStyle(QFrame.NoFrame) + self.scrollBox.setWidgetResizable(True) + self.scrollBox.setFixedHeight(80) + self.scrollBox.setWidget(self.refList) - self.labelFlag = QLabel("") - self.labelFlag.setAlignment(Qt.AlignRight | Qt.AlignBaseline) + self.outerBox.addWidget(self.showHide, 0, 0) + self.outerBox.addWidget(self.refLabel, 0, 1) + self.outerBox.addWidget(self.isSticky, 0, 2) + self.outerBox.addWidget(self.scrollBox, 1, 1, 1, 2) + self.outerBox.setColumnStretch(1, 1) - self.labelData = QLabel("") - self.labelData.setFont(self.fntValue) - self.labelData.setAlignment(Qt.AlignLeft | Qt.AlignBaseline) - self.labelData.setWordWrap(True) + self.setLayout(self.outerBox) + self.setContentsMargins(0,0,0,0) - # Status - self.statusName = QLabel("Status") - self.statusName.setFont(self.fntLabel) - self.statusName.setAlignment(Qt.AlignLeft) + self._doShowHide(self.mainConf.showRefPanel) - self.statusFlag = QLabel("") - self.statusFlag.setAlignment(Qt.AlignRight | Qt.AlignVCenter) - - self.statusData = QLabel("") - self.statusData.setFont(self.fntValue) - self.statusData.setAlignment(Qt.AlignLeft) - - # Class - self.className = QLabel("Class") - self.className.setFont(self.fntLabel) - self.className.setAlignment(Qt.AlignLeft) - - self.classFlag = QLabel("") - self.classFlag.setFont(self.fntValue) - self.classFlag.setAlignment(Qt.AlignRight) - - self.classData = QLabel("") - self.classData.setFont(self.fntValue) - self.classData.setAlignment(Qt.AlignLeft) - - # Layout - self.layoutName = QLabel("Layout") - self.layoutName.setFont(self.fntLabel) - self.layoutName.setAlignment(Qt.AlignLeft) - - self.layoutFlag = QLabel("") - self.layoutFlag.setFont(self.fntValue) - self.layoutFlag.setAlignment(Qt.AlignRight) - - self.layoutData = QLabel("") - self.layoutData.setFont(self.fntValue) - self.layoutData.setAlignment(Qt.AlignLeft) - - # Character Count - self.cCountName = QLabel(" Characters") - self.cCountName.setFont(self.fntLabel) - self.cCountName.setAlignment(Qt.AlignRight) - - self.cCountData = QLabel("") - self.cCountData.setFont(self.fntValue) - self.cCountData.setAlignment(Qt.AlignRight) - - # Word Count - self.wCountName = QLabel(" Words") - self.wCountName.setFont(self.fntLabel) - self.wCountName.setAlignment(Qt.AlignRight) - - self.wCountData = QLabel("") - self.wCountData.setFont(self.fntValue) - self.wCountData.setAlignment(Qt.AlignRight) - - # Paragraph Count - self.pCountName = QLabel(" Paragraphs") - self.pCountName.setFont(self.fntLabel) - self.pCountName.setAlignment(Qt.AlignRight) - - self.pCountData = QLabel("") - self.pCountData.setFont(self.fntValue) - self.pCountData.setAlignment(Qt.AlignRight) - - # Assemble - self.mainBox.addWidget(self.labelName, 0, 0, 1, 1) - self.mainBox.addWidget(self.labelFlag, 0, 1, 1, 1) - self.mainBox.addWidget(self.labelData, 0, 2, 1, 3) - - self.mainBox.addWidget(self.statusName, 1, 0, 1, 1) - self.mainBox.addWidget(self.statusFlag, 1, 1, 1, 1) - self.mainBox.addWidget(self.statusData, 1, 2, 1, 1) - self.mainBox.addWidget(self.cCountName, 1, 3, 1, 1) - self.mainBox.addWidget(self.cCountData, 1, 4, 1, 1) - - self.mainBox.addWidget(self.className, 2, 0, 1, 1) - self.mainBox.addWidget(self.classFlag, 2, 1, 1, 1) - self.mainBox.addWidget(self.classData, 2, 2, 1, 1) - self.mainBox.addWidget(self.wCountName, 2, 3, 1, 1) - self.mainBox.addWidget(self.wCountData, 2, 4, 1, 1) - - self.mainBox.addWidget(self.layoutName, 3, 0, 1, 1) - self.mainBox.addWidget(self.layoutFlag, 3, 1, 1, 1) - self.mainBox.addWidget(self.layoutData, 3, 2, 1, 1) - self.mainBox.addWidget(self.pCountName, 3, 3, 1, 1) - self.mainBox.addWidget(self.pCountData, 3, 4, 1, 1) - - self.mainBox.setColumnStretch(0,0) - self.mainBox.setColumnStretch(1,0) - self.mainBox.setColumnStretch(2,1) - self.mainBox.setColumnStretch(3,0) - self.mainBox.setColumnStretch(4,0) - - # Make sure the columns for flags and counts don't resize too often - flagWidth = self.theTheme.getTextWidth("Mm", self.fntValue) - countWidth = self.theTheme.getTextWidth("99,999", self.fntValue) - self.mainBox.setColumnMinimumWidth(1, flagWidth) - self.mainBox.setColumnMinimumWidth(4, countWidth) - - logger.debug("DocDetails initialisation complete") + logger.debug("DocViewDetails initialisation complete") return - ### - # Class Methods + def refreshReferences(self, tHandle): + """Update the current list of document references from the + project index. + """ + self.currHandle = tHandle + + if self.isSticky.isChecked(): + return + + theRefs = self.theParent.theIndex.getBackReferenceList(tHandle) + theList = [] + for tHandle in theRefs: + tItem = self.theProject.projTree[tHandle] + if tItem is not None: + theList.append("%s" % ( + tHandle, theRefs[tHandle], tItem.itemName + )) + + self.refList.setText(", ".join(theList)) + self.refList.adjustSize() + + return + + ## + # Internal Functions ## - def updateCounts(self, tHandle, cC, wC, pC): - """Just update the counts if the handle is the same as the one - we're already showing. + def _linkClicked(self, theLink): + """Capture the link-click and forward it to the document viewer + class for handling. """ - if tHandle == self.theHandle: - self.cCountData.setText("{:n}".format(cC)) - self.wCountData.setText("{:n}".format(wC)) - self.pCountData.setText("{:n}".format(pC)) + logger.verbose("Clicked link: '%s'" % theLink) + if len(theLink) == 27: + tHandle = theLink[6:19] + self.theParent.viewDocument(tHandle, theLink) return - def updateViewBox(self, tHandle): - """Populate the details box from a given handle. + def _doShowHide(self, chState): + """Toggle the expand/collapse of the panel. """ - self.theHandle = tHandle - nwItem = self.theProject.projTree[tHandle] - - if nwItem is None: - self.labelFlag.setText("") - self.labelData.setText("") - self.statusFlag.setText("") - self.statusData.setText("") - self.classFlag.setText("") - self.classData.setText("") - self.layoutFlag.setText("") - self.layoutData.setText("") - self.cCountData.setText("–") - self.wCountData.setText("–") - self.pCountData.setText("–") - + self.scrollBox.setVisible(chState) + self.mainConf.setShowRefPanel(chState) + if chState: + self.showHide.setArrowType(Qt.DownArrow) else: - theLabel = nwItem.itemName - if len(theLabel) > 100: - theLabel = theLabel[:96].rstrip()+" ..." - - iStatus = nwItem.itemStatus - if nwItem.itemClass == nwItemClass.NOVEL: - iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid - flagIcon = self.theParent.statusIcons[iStatus] - else: - iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid - flagIcon = self.theParent.importIcons[iStatus] - - if nwItem.itemType == nwItemType.FILE: - if nwItem.isExported: - self.labelFlag.setPixmap(self.expCheck) - else: - self.labelFlag.setPixmap(self.expCross) - else: - self.labelFlag.setPixmap(QPixmap(1,1)) - self.statusFlag.setPixmap(flagIcon.pixmap(self.sPx, self.sPx)) - self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) - if nwItem.itemLayout == nwItemLayout.NO_LAYOUT: - self.layoutFlag.setText("-") - else: - self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) - - self.labelData.setText(theLabel) - self.statusData.setText(nwItem.itemStatus) - self.classData.setText(nwLabels.CLASS_NAME[nwItem.itemClass]) - self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout]) - - if nwItem.itemType == nwItemType.FILE: - self.cCountData.setText("{:n}".format(nwItem.charCount)) - self.wCountData.setText("{:n}".format(nwItem.wordCount)) - self.pCountData.setText("{:n}".format(nwItem.paraCount)) - else: - self.cCountData.setText("–") - self.wCountData.setText("–") - self.pCountData.setText("–") - + self.showHide.setArrowType(Qt.RightArrow) return -# END Class GuiDocDetails + def _doSticky(self, chState): + """Toggle the sticky feature of the references. + """ + if not chState and self.currHandle is not None: + self.refreshReferences(self.currHandle) + return + +# END Class GuiDocViewDetails diff --git a/nw/gui/itemdetails.py b/nw/gui/itemdetails.py new file mode 100644 index 00000000..93ffb788 --- /dev/null +++ b/nw/gui/itemdetails.py @@ -0,0 +1,265 @@ +# -*- 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] + + This file is a part of novelWriter + Copyright 2020, 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 . +""" + +import logging +import nw + +from PyQt5.QtCore import Qt +from PyQt5.QtGui import QFont, QIcon, QPixmap +from PyQt5.QtWidgets import QFrame, QGridLayout, QLabel + +from nw.constants import ( + nwLabels, nwItemClass, nwItemType, nwItemLayout, nwUnicode +) + +logger = logging.getLogger(__name__) + +class GuiItemDetails(QFrame): + + def __init__(self, theParent, theProject): + QFrame.__init__(self, theParent) + + logger.debug("Initialising GuiItemDetails ...") + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theProject = theProject + self.theTheme = theParent.theTheme + self.theHandle = None + + self.mainBox = QGridLayout(self) + self.mainBox.setVerticalSpacing(1) + self.mainBox.setHorizontalSpacing(6) + self.setLayout(self.mainBox) + + self.pS = 0.9*self.theTheme.fontPointSize + self.iPx = self.theTheme.textIconSize + self.sPx = int(round(0.8*self.theTheme.textIconSize)) + + self.expCheck = self.theTheme.getPixmap("check", (self.iPx, self.iPx)) + self.expCross = self.theTheme.getPixmap("cross", (self.iPx, self.iPx)) + + self.fntLabel = QFont() + self.fntLabel.setBold(True) + self.fntLabel.setPointSizeF(self.pS) + + self.fntValue = QFont() + self.fntValue.setPointSizeF(self.pS) + + # Label + self.labelName = QLabel("Label") + self.labelName.setFont(self.fntLabel) + self.labelName.setAlignment(Qt.AlignLeft | Qt.AlignBaseline) + + self.labelFlag = QLabel("") + self.labelFlag.setAlignment(Qt.AlignRight | Qt.AlignBaseline) + + self.labelData = QLabel("") + self.labelData.setFont(self.fntValue) + self.labelData.setAlignment(Qt.AlignLeft | Qt.AlignBaseline) + self.labelData.setWordWrap(True) + + # Status + self.statusName = QLabel("Status") + self.statusName.setFont(self.fntLabel) + self.statusName.setAlignment(Qt.AlignLeft) + + self.statusFlag = QLabel("") + self.statusFlag.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + + self.statusData = QLabel("") + self.statusData.setFont(self.fntValue) + self.statusData.setAlignment(Qt.AlignLeft) + + # Class + self.className = QLabel("Class") + self.className.setFont(self.fntLabel) + self.className.setAlignment(Qt.AlignLeft) + + self.classFlag = QLabel("") + self.classFlag.setFont(self.fntValue) + self.classFlag.setAlignment(Qt.AlignRight) + + self.classData = QLabel("") + self.classData.setFont(self.fntValue) + self.classData.setAlignment(Qt.AlignLeft) + + # Layout + self.layoutName = QLabel("Layout") + self.layoutName.setFont(self.fntLabel) + self.layoutName.setAlignment(Qt.AlignLeft) + + self.layoutFlag = QLabel("") + self.layoutFlag.setFont(self.fntValue) + self.layoutFlag.setAlignment(Qt.AlignRight) + + self.layoutData = QLabel("") + self.layoutData.setFont(self.fntValue) + self.layoutData.setAlignment(Qt.AlignLeft) + + # Character Count + self.cCountName = QLabel(" Characters") + self.cCountName.setFont(self.fntLabel) + self.cCountName.setAlignment(Qt.AlignRight) + + self.cCountData = QLabel("") + self.cCountData.setFont(self.fntValue) + self.cCountData.setAlignment(Qt.AlignRight) + + # Word Count + self.wCountName = QLabel(" Words") + self.wCountName.setFont(self.fntLabel) + self.wCountName.setAlignment(Qt.AlignRight) + + self.wCountData = QLabel("") + self.wCountData.setFont(self.fntValue) + self.wCountData.setAlignment(Qt.AlignRight) + + # Paragraph Count + self.pCountName = QLabel(" Paragraphs") + self.pCountName.setFont(self.fntLabel) + self.pCountName.setAlignment(Qt.AlignRight) + + self.pCountData = QLabel("") + self.pCountData.setFont(self.fntValue) + self.pCountData.setAlignment(Qt.AlignRight) + + # Assemble + self.mainBox.addWidget(self.labelName, 0, 0, 1, 1) + self.mainBox.addWidget(self.labelFlag, 0, 1, 1, 1) + self.mainBox.addWidget(self.labelData, 0, 2, 1, 3) + + self.mainBox.addWidget(self.statusName, 1, 0, 1, 1) + self.mainBox.addWidget(self.statusFlag, 1, 1, 1, 1) + self.mainBox.addWidget(self.statusData, 1, 2, 1, 1) + self.mainBox.addWidget(self.cCountName, 1, 3, 1, 1) + self.mainBox.addWidget(self.cCountData, 1, 4, 1, 1) + + self.mainBox.addWidget(self.className, 2, 0, 1, 1) + self.mainBox.addWidget(self.classFlag, 2, 1, 1, 1) + self.mainBox.addWidget(self.classData, 2, 2, 1, 1) + self.mainBox.addWidget(self.wCountName, 2, 3, 1, 1) + self.mainBox.addWidget(self.wCountData, 2, 4, 1, 1) + + self.mainBox.addWidget(self.layoutName, 3, 0, 1, 1) + self.mainBox.addWidget(self.layoutFlag, 3, 1, 1, 1) + self.mainBox.addWidget(self.layoutData, 3, 2, 1, 1) + self.mainBox.addWidget(self.pCountName, 3, 3, 1, 1) + self.mainBox.addWidget(self.pCountData, 3, 4, 1, 1) + + self.mainBox.setColumnStretch(0,0) + self.mainBox.setColumnStretch(1,0) + self.mainBox.setColumnStretch(2,1) + self.mainBox.setColumnStretch(3,0) + self.mainBox.setColumnStretch(4,0) + + # Make sure the columns for flags and counts don't resize too often + flagWidth = self.theTheme.getTextWidth("Mm", self.fntValue) + countWidth = self.theTheme.getTextWidth("99,999", self.fntValue) + self.mainBox.setColumnMinimumWidth(1, flagWidth) + self.mainBox.setColumnMinimumWidth(4, countWidth) + + logger.debug("GuiItemDetails initialisation complete") + + return + + ### + # Class Methods + ## + + def updateCounts(self, tHandle, cC, wC, pC): + """Just update the counts if the handle is the same as the one + we're already showing. + """ + if tHandle == self.theHandle: + self.cCountData.setText("{:n}".format(cC)) + self.wCountData.setText("{:n}".format(wC)) + self.pCountData.setText("{:n}".format(pC)) + return + + def updateViewBox(self, tHandle): + """Populate the details box from a given handle. + """ + self.theHandle = tHandle + nwItem = self.theProject.projTree[tHandle] + + if nwItem is None: + self.labelFlag.setText("") + self.labelData.setText("") + self.statusFlag.setText("") + self.statusData.setText("") + self.classFlag.setText("") + self.classData.setText("") + self.layoutFlag.setText("") + self.layoutData.setText("") + self.cCountData.setText("–") + self.wCountData.setText("–") + self.pCountData.setText("–") + + else: + theLabel = nwItem.itemName + if len(theLabel) > 100: + theLabel = theLabel[:96].rstrip()+" ..." + + iStatus = nwItem.itemStatus + if nwItem.itemClass == nwItemClass.NOVEL: + iStatus = self.theProject.statusItems.checkEntry(iStatus) # Make sure it's valid + flagIcon = self.theParent.statusIcons[iStatus] + else: + iStatus = self.theProject.importItems.checkEntry(iStatus) # Make sure it's valid + flagIcon = self.theParent.importIcons[iStatus] + + if nwItem.itemType == nwItemType.FILE: + if nwItem.isExported: + self.labelFlag.setPixmap(self.expCheck) + else: + self.labelFlag.setPixmap(self.expCross) + else: + self.labelFlag.setPixmap(QPixmap(1,1)) + self.statusFlag.setPixmap(flagIcon.pixmap(self.sPx, self.sPx)) + self.classFlag.setText(nwLabels.CLASS_FLAG[nwItem.itemClass]) + if nwItem.itemLayout == nwItemLayout.NO_LAYOUT: + self.layoutFlag.setText("-") + else: + self.layoutFlag.setText(nwLabels.LAYOUT_FLAG[nwItem.itemLayout]) + + self.labelData.setText(theLabel) + self.statusData.setText(nwItem.itemStatus) + self.classData.setText(nwLabels.CLASS_NAME[nwItem.itemClass]) + self.layoutData.setText(nwLabels.LAYOUT_NAME[nwItem.itemLayout]) + + if nwItem.itemType == nwItemType.FILE: + self.cCountData.setText("{:n}".format(nwItem.charCount)) + self.wCountData.setText("{:n}".format(nwItem.wordCount)) + self.pCountData.setText("{:n}".format(nwItem.paraCount)) + else: + self.cCountData.setText("–") + self.wCountData.setText("–") + self.pCountData.setText("–") + + return + +# END Class GuiItemDetails diff --git a/nw/gui/outline.py b/nw/gui/outline.py index 982f21c9..12c26383 100644 --- a/nw/gui/outline.py +++ b/nw/gui/outline.py @@ -30,7 +30,7 @@ import nw from time import time -from PyQt5.QtCore import Qt +from PyQt5.QtCore import Qt, QSize from PyQt5.QtWidgets import ( QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView ) @@ -43,7 +43,6 @@ class GuiProjectOutline(QTreeWidget): DEF_WIDTH = { nwOutline.TITLE : 200, - nwOutline.HANDLE : 0, nwOutline.LEVEL : 40, nwOutline.LABEL : 150, nwOutline.LINE : 40, @@ -63,7 +62,6 @@ class GuiProjectOutline(QTreeWidget): DEF_HIDDEN = { nwOutline.TITLE : False, - nwOutline.HANDLE : True, nwOutline.LEVEL : True, nwOutline.LABEL : False, nwOutline.LINE : True, @@ -103,6 +101,10 @@ class GuiProjectOutline(QTreeWidget): self.setDragEnabled(False) self.itemDoubleClicked.connect(self._treeDoubleClick) + iPx = self.theTheme.textIconSize + self.setIconSize(QSize(iPx, iPx)) + self.setIndentation(iPx) + self.treeHead = self.header() self.treeHead.setContextMenuPolicy(Qt.CustomContextMenu) self.treeHead.customContextMenuRequested.connect(self._headerRightClick) @@ -187,7 +189,7 @@ class GuiProjectOutline(QTreeWidget): clicked, and send it to the main gui class for opening in the document editor. """ - tHandle = tItem.text(self.colIndex[nwOutline.HANDLE]) + tHandle = tItem.data(self.colIndex[nwOutline.TITLE], Qt.UserRole) try: tLine = int(tItem.text(self.colIndex[nwOutline.LINE])) except: @@ -332,7 +334,6 @@ class GuiProjectOutline(QTreeWidget): # Make sure title column is always visible, # and handle column always hidden self.setColumnHidden(self.colIndex[nwOutline.TITLE], False) - self.setColumnHidden(self.colIndex[nwOutline.HANDLE], True) headItem = self.headerItem() headItem.setTextAlignment(self.colIndex[nwOutline.CCOUNT], Qt.AlignRight) @@ -408,9 +409,11 @@ class GuiProjectOutline(QTreeWidget): newItem = QTreeWidgetItem() newItem.setText(self.colIndex[nwOutline.TITLE], novIdx["title"]) - newItem.setText(self.colIndex[nwOutline.HANDLE], tHandle) + newItem.setData(self.colIndex[nwOutline.TITLE], Qt.UserRole, tHandle) + newItem.setIcon(self.colIndex[nwOutline.TITLE], self.theTheme.getIcon("hash")) newItem.setText(self.colIndex[nwOutline.LEVEL], novIdx["level"]) newItem.setText(self.colIndex[nwOutline.LABEL], nwItem.itemName) + newItem.setIcon(self.colIndex[nwOutline.LABEL], self.theTheme.getIcon("proj_document")) newItem.setText(self.colIndex[nwOutline.LINE], sTitle[1:].lstrip("0")) newItem.setText(self.colIndex[nwOutline.SYNOP], novIdx["synopsis"]) newItem.setText(self.colIndex[nwOutline.CCOUNT], str(novIdx["cCount"])) @@ -451,7 +454,7 @@ class GuiOutlineHeaderMenu(QMenu): self.actionMap = {} for hItem in nwOutline: - if hItem == nwOutline.TITLE or hItem == nwOutline.HANDLE: + if hItem == nwOutline.TITLE: continue self.actionMap[hItem] = QAction(nwLabels.OUTLINE_COLS[hItem], self) self.actionMap[hItem].setCheckable(True) @@ -469,7 +472,7 @@ class GuiOutlineHeaderMenu(QMenu): self.acceptToggle = False for hItem in nwOutline: - if hItem == nwOutline.TITLE or hItem == nwOutline.HANDLE or hItem not in hiddenState: + if hItem == nwOutline.TITLE or hItem not in hiddenState: continue self.actionMap[hItem].setChecked(not hiddenState[hItem]) diff --git a/nw/gui/projectload.py b/nw/gui/projload.py similarity index 100% rename from nw/gui/projectload.py rename to nw/gui/projload.py diff --git a/nw/gui/projectsettings.py b/nw/gui/projsettings.py similarity index 100% rename from nw/gui/projectsettings.py rename to nw/gui/projsettings.py diff --git a/nw/gui/doctree.py b/nw/gui/projtree.py similarity index 92% rename from nw/gui/doctree.py rename to nw/gui/projtree.py index 2f4477af..72246c3a 100644 --- a/nw/gui/doctree.py +++ b/nw/gui/projtree.py @@ -31,7 +31,8 @@ import nw from PyQt5.QtCore import Qt, QSize from PyQt5.QtGui import QFont, QColor, QIcon from PyQt5.QtWidgets import ( - qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMessageBox + qApp, QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMessageBox, + QHeaderView ) from nw.core import NWDoc @@ -41,18 +42,17 @@ from nw.constants import ( logger = logging.getLogger(__name__) -class GuiDocTree(QTreeWidget): +class GuiProjectTree(QTreeWidget): C_NAME = 0 C_COUNT = 1 C_EXPORT = 2 C_FLAGS = 3 - C_HANDLE = 4 def __init__(self, theParent, theProject): QTreeWidget.__init__(self, theParent) - logger.debug("Initialising GuiDocTree ...") + logger.debug("Initialising GuiProjectTree ...") self.mainConf = nw.CONFIG self.theParent = theParent self.theTheme = theParent.theTheme @@ -69,24 +69,30 @@ class GuiDocTree(QTreeWidget): self.setIconSize(QSize(iPx, iPx)) self.setExpandsOnDoubleClick(True) self.setIndentation(iPx) - self.setColumnCount(5) - self.setHeaderLabels(["Label", "Words", "Inc", "Flags", "Handle"]) - self.hideColumn(self.C_HANDLE) + self.setColumnCount(4) + self.setHeaderLabels(["Label", "Words", "Inc", "Flags"]) - treeHead = self.headerItem() - treeHead.setTextAlignment(self.C_COUNT, Qt.AlignRight) - treeHead.setToolTip(self.C_NAME, "Item label") - treeHead.setToolTip(self.C_COUNT, "Word count") - treeHead.setToolTip(self.C_EXPORT, "Include in build") - treeHead.setToolTip(self.C_FLAGS, "Status, class, and layout flags") + treeHeadItem = self.headerItem() + treeHeadItem.setTextAlignment(self.C_COUNT, Qt.AlignRight) + treeHeadItem.setToolTip(self.C_NAME, "Item label") + treeHeadItem.setToolTip(self.C_COUNT, "Word count") + treeHeadItem.setToolTip(self.C_EXPORT, "Include in build") + treeHeadItem.setToolTip(self.C_FLAGS, "Status, class, and layout flags") # Force the font to fix font sizing issues on some platforms # like Ubuntu. This must also be set when the rows are added. self.setFont(self.theTheme.guiFont) - treeHead.setFont(self.C_NAME, self.theTheme.guiFont) - treeHead.setFont(self.C_COUNT,self.theTheme.guiFont) - treeHead.setFont(self.C_EXPORT, self.theTheme.guiFont) - treeHead.setFont(self.C_FLAGS, self.theTheme.guiFont) + treeHeadItem.setFont(self.C_NAME, self.theTheme.guiFont) + treeHeadItem.setFont(self.C_COUNT,self.theTheme.guiFont) + treeHeadItem.setFont(self.C_EXPORT, self.theTheme.guiFont) + treeHeadItem.setFont(self.C_FLAGS, self.theTheme.guiFont) + + # Let the last column stretch, and set the minimum size to the + # size of the icon as the default Qt font metrics approach fails + # for some fonts like the Ubuntu font. + treeHeader = self.header() + treeHeader.setStretchLastSection(True) + treeHeader.setMinimumSectionSize(iPx+6) # Allow Move by Drag & Drop self.setDragEnabled(True) @@ -102,13 +108,14 @@ class GuiDocTree(QTreeWidget): # self.setSelectionBehavior(QAbstractItemView.SelectRows) # Get user's column width preferences for NAME and COUNT - for colN, colW in enumerate(self.mainConf.treeColWidth): - self.setColumnWidth(colN, colW) + if len(self.mainConf.treeColWidth) <= 4: + for colN, colW in enumerate(self.mainConf.treeColWidth): + self.setColumnWidth(colN, colW) - self.resizeColumnToContents(self.C_EXPORT) + # The last column should just auto-scale self.resizeColumnToContents(self.C_FLAGS) - logger.debug("GuiDocTree initialisation complete") + logger.debug("GuiProjectTree initialisation complete") # Internal Mapping self.makeAlert = self.theParent.makeAlert @@ -286,6 +293,7 @@ class GuiDocTree(QTreeWidget): retVals = [ self.columnWidth(0), self.columnWidth(1), + self.columnWidth(2), ] return retVals @@ -479,7 +487,7 @@ class GuiDocTree(QTreeWidget): pCount = 0 for i in range(pItem.childCount()): pCount += int(pItem.child(i).text(self.C_COUNT)) - pHandle = pItem.text(self.C_HANDLE) + pHandle = pItem.data(self.C_NAME, Qt.UserRole) if not nDepth > 200 and pHandle != "": self.propagateCount(pHandle, pCount, nDepth+1) return @@ -521,17 +529,17 @@ class GuiDocTree(QTreeWidget): if len(selItem) == 0: return None if isinstance(selItem[0], QTreeWidgetItem): - return selItem[0].text(self.C_HANDLE) + return selItem[0].data(self.C_NAME, Qt.UserRole) return None def getSelectedHandles(self): """Return a list of all currently selected item handles. """ - selItems = self.selectedItems() + selItems = self.selectedItems() selHandles = [] for n in range(len(selItems)): if isinstance(selItems[n], QTreeWidgetItem): - selHandles.append(selItems[n].text(self.C_HANDLE)) + selHandles.append(selItems[n].data(self.C_NAME, Qt.UserRole)) return selHandles def setSelectedHandle(self, tHandle): @@ -563,8 +571,8 @@ class GuiDocTree(QTreeWidget): """This is a recursive function returning all items in a tree starting at a given QTreeWidgetItem. """ - tHandle = theItem.text(self.C_HANDLE) - nwItem = self.theProject.projTree[tHandle] + tHandle = theItem.data(self.C_NAME, Qt.UserRole) + nwItem = self.theProject.projTree[tHandle] nwItem.setExpanded(theItem.isExpanded()) nwItem.setOrder(theIndex) theList.append(tHandle) @@ -585,7 +593,6 @@ class GuiDocTree(QTreeWidget): newItem.setText(self.C_COUNT, "0") newItem.setText(self.C_EXPORT, "") newItem.setText(self.C_FLAGS, "") - newItem.setText(self.C_HANDLE, tHandle) newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter) newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter) @@ -596,6 +603,8 @@ class GuiDocTree(QTreeWidget): newItem.setFont(self.C_COUNT, self.theTheme.guiFont) newItem.setFont(self.C_FLAGS, self.theTheme.guiFont) + newItem.setData(self.C_NAME, Qt.UserRole, tHandle) + self.theMap[tHandle] = newItem if pHandle is None: if nwItem.itemType == nwItemType.ROOT: @@ -649,10 +658,10 @@ class GuiDocTree(QTreeWidget): newItem.setText(self.C_COUNT, "") newItem.setText(self.C_EXPORT, "") newItem.setText(self.C_FLAGS, "") - newItem.setText(self.C_HANDLE, "") self.addTopLevelItem(newItem) self.orphRoot = newItem newItem.setExpanded(True) + newItem.setData(self.C_NAME, "") newItem.setIcon(self.C_NAME, self.theTheme.getIcon("warning")) return @@ -677,7 +686,7 @@ class GuiDocTree(QTreeWidget): logger.error("Failed to find new parent item of %s" % tHandle) return False - pHandle = trItemP.text(self.C_HANDLE) + pHandle = trItemP.data(self.C_NAME, Qt.UserRole) wC = int(trItemS.text(self.C_COUNT)) self.propagateCount(tHandle, -wC) nwItemS.setParent(pHandle) @@ -702,7 +711,7 @@ class GuiDocTree(QTreeWidget): if trItemP is None: logger.error("Failed to find new parent item of %s" % tHandle) return - pHandle = trItemP.text(self.C_HANDLE) + pHandle = trItemP.data(self.C_NAME, Qt.UserRole) nwItemS.setParent(pHandle) self.setTreeItemValues(tHandle) self.theProject.setProjectChanged(True) @@ -737,7 +746,7 @@ class GuiDocTree(QTreeWidget): return dItem = self.itemFromIndex(dIndex) - dHandle = dItem.text(self.C_HANDLE) + dHandle = dItem.data(self.C_NAME, Qt.UserRole) snItem = self.theProject.projTree[sHandle] dnItem = self.theProject.projTree[dHandle] if dnItem is None: @@ -773,4 +782,4 @@ class GuiDocTree(QTreeWidget): return -# END Class GuiDocTree +# END Class GuiProjectTree diff --git a/nw/gui/theme.py b/nw/gui/theme.py index 8613c683..49ea367c 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -516,6 +516,7 @@ class GuiIcons: "edit" : (None, None), "check" : (None, None), "cross" : (None, None), + "hash" : (None, None), ## Other Icons "warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), diff --git a/nw/gui/viewdetails.py b/nw/gui/viewdetails.py deleted file mode 100644 index d4b2621c..00000000 --- a/nw/gui/viewdetails.py +++ /dev/null @@ -1,151 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter GUI DocView Details - - novelWriter – GUI DocView Details -=================================== - Class holding the document view details panel - - File History: - Created: 2019-10-31 [0.3.2] - - This file is a part of novelWriter - Copyright 2020, 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 . -""" - -import logging -import nw - -from PyQt5.QtCore import Qt, QSize -from PyQt5.QtWidgets import ( - QWidget, QLabel, QScrollArea, QFrame, QToolButton, QCheckBox, QGridLayout -) - -logger = logging.getLogger(__name__) - -class GuiDocViewDetails(QWidget): - - def __init__(self, theParent, theProject): - QWidget.__init__(self, theParent) - - logger.debug("Initialising DocViewDetails ...") - self.mainConf = nw.CONFIG - self.theParent = theParent - self.theProject = theProject - self.currHandle = None - - self.outerBox = QGridLayout(self) - self.outerBox.setContentsMargins(0,0,0,0) - self.outerBox.setHorizontalSpacing(0) - self.outerBox.setVerticalSpacing(4) - - self.refLabel = QLabel("Referenced By", self) - - self.showHide = QToolButton() - self.showHide.setStyleSheet("QToolButton { border: none; }") - self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly) - self.showHide.setArrowType(Qt.DownArrow) - self.showHide.setCheckable(True) - self.showHide.setIconSize(QSize(16,16)) - self.showHide.toggled.connect(self._doShowHide) - - self.isSticky = QCheckBox("Sticky") - self.isSticky.setChecked(False) - self.isSticky.toggled.connect(self._doSticky) - - self.refList = QLabel("None") - self.refList.setWordWrap(True) - self.refList.setAlignment(Qt.AlignTop) - self.refList.setScaledContents(True) - self.refList.linkActivated.connect(self._linkClicked) - - self.scrollBox = QScrollArea() - self.scrollBox.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) - self.scrollBox.setFrameStyle(QFrame.NoFrame) - self.scrollBox.setWidgetResizable(True) - self.scrollBox.setFixedHeight(80) - self.scrollBox.setWidget(self.refList) - - self.outerBox.addWidget(self.showHide, 0, 0) - self.outerBox.addWidget(self.refLabel, 0, 1) - self.outerBox.addWidget(self.isSticky, 0, 2) - self.outerBox.addWidget(self.scrollBox, 1, 1, 1, 2) - self.outerBox.setColumnStretch(1, 1) - - self.setLayout(self.outerBox) - self.setContentsMargins(0,0,0,0) - - self._doShowHide(self.mainConf.showRefPanel) - - logger.debug("DocViewDetails initialisation complete") - - return - - def refreshReferences(self, tHandle): - """Update the current list of document references from the - project index. - """ - self.currHandle = tHandle - - if self.isSticky.isChecked(): - return - - theRefs = self.theParent.theIndex.getBackReferenceList(tHandle) - theList = [] - for tHandle in theRefs: - tItem = self.theProject.projTree[tHandle] - if tItem is not None: - theList.append("%s" % ( - tHandle, theRefs[tHandle], tItem.itemName - )) - - self.refList.setText(", ".join(theList)) - self.refList.adjustSize() - - return - - ## - # Internal Functions - ## - - def _linkClicked(self, theLink): - """Capture the link-click and forward it to the document viewer - class for handling. - """ - logger.verbose("Clicked link: '%s'" % theLink) - if len(theLink) == 27: - tHandle = theLink[6:19] - self.theParent.viewDocument(tHandle, theLink) - return - - def _doShowHide(self, chState): - """Toggle the expand/collapse of the panel. - """ - self.scrollBox.setVisible(chState) - self.mainConf.setShowRefPanel(chState) - if chState: - self.showHide.setArrowType(Qt.DownArrow) - else: - self.showHide.setArrowType(Qt.RightArrow) - return - - def _doSticky(self, chState): - """Toggle the sticky feature of the references. - """ - if not chState and self.currHandle is not None: - self.refreshReferences(self.currHandle) - return - -# END Class GuiDocViewDetails diff --git a/nw/guimain.py b/nw/guimain.py index bfc98c71..ea6c050e 100644 --- a/nw/guimain.py +++ b/nw/guimain.py @@ -40,8 +40,8 @@ from PyQt5.QtWidgets import ( ) from nw.gui import ( - GuiMainMenu, GuiMainStatus, GuiTheme, GuiDocTree, GuiDocEditor, - GuiDocViewer, GuiDocDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails, + GuiMainMenu, GuiMainStatus, GuiTheme, GuiProjectTree, GuiDocEditor, + GuiDocViewer, GuiItemDetails, GuiSearchBar, GuiNoticeBar, GuiDocViewDetails, GuiPreferences, GuiProjectSettings, GuiItemEditor, GuiProjectOutline, GuiSessionLogView, GuiDocMerge, GuiDocSplit, GuiProjectLoad, GuiBuildNovel ) @@ -91,12 +91,12 @@ class GuiMain(QMainWindow): # Main GUI Elements self.statusBar = GuiMainStatus(self) self.noticeBar = GuiNoticeBar(self) - self.treeView = GuiDocTree(self, self.theProject) + self.treeView = GuiProjectTree(self, self.theProject) self.docEditor = GuiDocEditor(self, self.theProject) self.docViewer = GuiDocViewer(self, self.theProject) self.viewMeta = GuiDocViewDetails(self, self.theProject) self.searchBar = GuiSearchBar(self) - self.treeMeta = GuiDocDetails(self, self.theProject) + self.treeMeta = GuiItemDetails(self, self.theProject) self.projView = GuiProjectOutline(self, self.theProject) self.mainMenu = GuiMainMenu(self, self.theProject) @@ -466,14 +466,15 @@ class GuiMain(QMainWindow): self.docEditor.clearEditor() return True - def openDocument(self, tHandle, tLine=None): + def openDocument(self, tHandle, tLine=None, changeFocus=True): """Open a specific document, optionally at a given line. """ if self.hasProject: self.closeDocument() self.tabWidget.setCurrentWidget(self.splitView) if self.docEditor.loadText(tHandle, tLine): - self.docEditor.setFocus() + if changeFocus: + self.docEditor.setFocus() self.theProject.setLastEdited(tHandle) self.treeView.setSelectedHandle(tHandle) else: @@ -1022,7 +1023,7 @@ class GuiMain(QMainWindow): """The user double-clicked an item in the tree. If it is a file, we open it. Otherwise, we do nothing. """ - tHandle = tItem.text(self.treeView.C_HANDLE) + tHandle = tItem.data(self.treeView.C_NAME, Qt.UserRole) logger.verbose("User double clicked tree item with handle %s" % tHandle) nwItem = self.theProject.projTree[tHandle] if nwItem is not None: @@ -1035,7 +1036,8 @@ class GuiMain(QMainWindow): def _treeKeyPressReturn(self): """The user pressed return an item in the tree. If it is a file, - we open it. Otherwise, we do nothing. + we open it. Otherwise, we do nothing. Pressing return does not + change focus to the editor as double click does. """ tHandle = self.treeView.getSelectedHandle() logger.verbose("User pressed return on tree item with handle %s" % tHandle) @@ -1043,7 +1045,7 @@ class GuiMain(QMainWindow): if nwItem is not None: if nwItem.itemType == nwItemType.FILE: logger.verbose("Requested item %s is a file" % tHandle) - self.openDocument(tHandle) + self.openDocument(tHandle, changeFocus=False) else: logger.verbose("Requested item %s is a folder" % tHandle) return diff --git a/tests/test_gui.py b/tests/test_gui.py index e006cabb..fe52cabd 100644 --- a/tests/test_gui.py +++ b/tests/test_gui.py @@ -485,7 +485,7 @@ def testBuildTool(qtbot, nwTempBuild, nwLipsum, nwRef, nwTemp): qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.ignoreFlag, Qt.LeftButton) qtbot.wait(stepDelay) - qtbot.mouseClick(nwBuild.excludeBody, Qt.LeftButton) + qtbot.mouseClick(nwBuild.includeBody, Qt.LeftButton) qtbot.wait(stepDelay) qtbot.mouseClick(nwBuild.buildNovel, Qt.LeftButton)