novelWriter 2.3.1__py3-none-any.whl → 2.4b1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/METADATA +1 -1
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/RECORD +81 -70
  3. novelwriter/__init__.py +5 -5
  4. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  5. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  6. novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +4 -0
  7. novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +4 -0
  8. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  9. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  10. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  11. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  12. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  13. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  14. novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +4 -0
  15. novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +4 -0
  16. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  17. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  18. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  19. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  20. novelwriter/assets/manual.pdf +0 -0
  21. novelwriter/assets/sample.zip +0 -0
  22. novelwriter/assets/syntax/default_dark.conf +1 -0
  23. novelwriter/assets/syntax/default_light.conf +1 -0
  24. novelwriter/assets/syntax/grey_dark.conf +1 -0
  25. novelwriter/assets/syntax/grey_light.conf +1 -0
  26. novelwriter/assets/syntax/light_owl.conf +1 -0
  27. novelwriter/assets/syntax/night_owl.conf +1 -0
  28. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  29. novelwriter/assets/syntax/solarized_light.conf +1 -0
  30. novelwriter/assets/syntax/tomorrow.conf +1 -0
  31. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  32. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  33. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  34. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  35. novelwriter/assets/text/credits_en.htm +25 -23
  36. novelwriter/common.py +1 -1
  37. novelwriter/config.py +35 -12
  38. novelwriter/constants.py +5 -6
  39. novelwriter/core/buildsettings.py +60 -40
  40. novelwriter/core/coretools.py +98 -13
  41. novelwriter/core/docbuild.py +74 -7
  42. novelwriter/core/document.py +24 -3
  43. novelwriter/core/index.py +31 -112
  44. novelwriter/core/project.py +10 -15
  45. novelwriter/core/sessions.py +2 -2
  46. novelwriter/core/status.py +4 -4
  47. novelwriter/core/storage.py +8 -2
  48. novelwriter/core/tohtml.py +22 -25
  49. novelwriter/core/tokenizer.py +416 -232
  50. novelwriter/core/tomd.py +17 -8
  51. novelwriter/core/toodt.py +65 -7
  52. novelwriter/core/tree.py +8 -8
  53. novelwriter/dialogs/docsplit.py +7 -8
  54. novelwriter/dialogs/preferences.py +3 -6
  55. novelwriter/enum.py +17 -14
  56. novelwriter/extensions/modified.py +20 -2
  57. novelwriter/extensions/versioninfo.py +1 -1
  58. novelwriter/gui/doceditor.py +257 -279
  59. novelwriter/gui/dochighlight.py +29 -25
  60. novelwriter/gui/docviewer.py +139 -148
  61. novelwriter/gui/docviewerpanel.py +4 -24
  62. novelwriter/gui/editordocument.py +12 -1
  63. novelwriter/gui/itemdetails.py +6 -6
  64. novelwriter/gui/mainmenu.py +37 -16
  65. novelwriter/gui/noveltree.py +11 -19
  66. novelwriter/gui/outline.py +43 -20
  67. novelwriter/gui/projtree.py +35 -43
  68. novelwriter/gui/search.py +316 -0
  69. novelwriter/gui/sidebar.py +25 -30
  70. novelwriter/gui/theme.py +59 -6
  71. novelwriter/guimain.py +176 -173
  72. novelwriter/shared.py +26 -1
  73. novelwriter/text/__init__.py +3 -0
  74. novelwriter/text/counting.py +137 -0
  75. novelwriter/tools/manuscript.py +344 -55
  76. novelwriter/tools/manussettings.py +213 -71
  77. novelwriter/tools/welcome.py +1 -1
  78. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/LICENSE.md +0 -0
  79. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/WHEEL +0 -0
  80. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/entry_points.txt +0 -0
  81. {novelWriter-2.3.1.dist-info → novelWriter-2.4b1.dist-info}/top_level.txt +0 -0
@@ -26,8 +26,9 @@ from __future__ import annotations
26
26
  import logging
27
27
 
28
28
  from time import time
29
+ from collections.abc import Iterable
29
30
 
30
- from PyQt5.QtGui import QTextCursor, QTextDocument
31
+ from PyQt5.QtGui import QTextBlock, QTextCursor, QTextDocument
31
32
  from PyQt5.QtCore import QObject, pyqtSlot
32
33
  from PyQt5.QtWidgets import QPlainTextDocumentLayout, qApp
33
34
  from novelwriter import SHARED
@@ -113,6 +114,16 @@ class GuiTextDocument(QTextDocument):
113
114
  return word, cPos, cLen, SHARED.spelling.suggestWords(word)
114
115
  return "", -1, -1, []
115
116
 
117
+ def iterBlockByType(self, cType: int, maxCount: int = 1000) -> Iterable[QTextBlock]:
118
+ """Iterate over all text blocks of a given type."""
119
+ count = 0
120
+ for i in range(self.blockCount()):
121
+ block = self.findBlockByNumber(i)
122
+ if count < maxCount and block.isValid() and block.userState() & cType > 0:
123
+ count += 1
124
+ yield block
125
+ return None
126
+
116
127
  ##
117
128
  # Public Slots
118
129
  ##
@@ -48,7 +48,7 @@ class GuiItemDetails(QWidget):
48
48
  logger.debug("Create: GuiItemDetails")
49
49
 
50
50
  # Internal Variables
51
- self._itemHandle = None
51
+ self._handle = None
52
52
 
53
53
  # Sizes
54
54
  hSp = CONFIG.pxInt(6)
@@ -194,7 +194,7 @@ class GuiItemDetails(QWidget):
194
194
 
195
195
  def clearDetails(self) -> None:
196
196
  """Clear all the data values."""
197
- self._itemHandle = None
197
+ self._handle = None
198
198
  self.labelIcon.clear()
199
199
  self.labelData.clear()
200
200
  self.statusIcon.clear()
@@ -210,11 +210,11 @@ class GuiItemDetails(QWidget):
210
210
 
211
211
  def refreshDetails(self) -> None:
212
212
  """Reload the content of the details panel."""
213
- self.updateViewBox(self._itemHandle)
213
+ self.updateViewBox(self._handle)
214
214
 
215
215
  def updateTheme(self) -> None:
216
216
  """Update theme elements."""
217
- self.updateViewBox(self._itemHandle)
217
+ self.updateViewBox(self._handle)
218
218
  return
219
219
 
220
220
  ##
@@ -233,7 +233,7 @@ class GuiItemDetails(QWidget):
233
233
  self.clearDetails()
234
234
  return
235
235
 
236
- self._itemHandle = tHandle
236
+ self._handle = tHandle
237
237
  iPx = int(round(0.8*SHARED.theme.baseIconSize))
238
238
 
239
239
  # Label
@@ -295,7 +295,7 @@ class GuiItemDetails(QWidget):
295
295
  """Update the counts if the handle is the same as the one we're
296
296
  already showing. Otherwise, do nothing.
297
297
  """
298
- if tHandle == self._itemHandle:
298
+ if tHandle == self._handle:
299
299
  self.cCountData.setText(f"{cC:n}")
300
300
  self.wCountData.setText(f"{wC:n}")
301
301
  self.pCountData.setText(f"{pC:n}")
@@ -33,9 +33,9 @@ from PyQt5.QtCore import QUrl, pyqtSignal, pyqtSlot
33
33
  from PyQt5.QtWidgets import QMenuBar, QAction
34
34
 
35
35
  from novelwriter import CONFIG, SHARED
36
- from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
37
36
  from novelwriter.common import openExternalPath
38
37
  from novelwriter.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode
38
+ from novelwriter.enum import nwDocAction, nwDocInsert, nwView, nwWidget
39
39
  from novelwriter.extensions.eventfilters import StatusTipFilter
40
40
 
41
41
  if TYPE_CHECKING: # pragma: no cover
@@ -55,6 +55,7 @@ class GuiMainMenu(QMenuBar):
55
55
  requestDocInsertText = pyqtSignal(str)
56
56
  requestDocKeyWordInsert = pyqtSignal(str)
57
57
  requestFocusChange = pyqtSignal(nwWidget)
58
+ requestViewChange = pyqtSignal(nwView)
58
59
 
59
60
  def __init__(self, mainGui: GuiMain) -> None:
60
61
  super().__init__(parent=mainGui)
@@ -196,12 +197,12 @@ class GuiMainMenu(QMenuBar):
196
197
  # Document > Open
197
198
  self.aOpenDoc = self.docuMenu.addAction(self.tr("Open Document"))
198
199
  self.aOpenDoc.setShortcut("Ctrl+O")
199
- self.aOpenDoc.triggered.connect(lambda: self.mainGui.openSelectedItem())
200
+ self.aOpenDoc.triggered.connect(self.mainGui.openSelectedItem)
200
201
 
201
202
  # Document > Save
202
203
  self.aSaveDoc = self.docuMenu.addAction(self.tr("Save Document"))
203
204
  self.aSaveDoc.setShortcut("Ctrl+S")
204
- self.aSaveDoc.triggered.connect(lambda: self.mainGui.saveDocument())
205
+ self.aSaveDoc.triggered.connect(self.mainGui.saveDocument)
205
206
 
206
207
  # Document > Close
207
208
  self.aCloseDoc = self.docuMenu.addAction(self.tr("Close Document"))
@@ -219,7 +220,7 @@ class GuiMainMenu(QMenuBar):
219
220
  # Document > Close Preview
220
221
  self.aCloseView = self.docuMenu.addAction(self.tr("Close Document View"))
221
222
  self.aCloseView.setShortcut("Ctrl+Shift+R")
222
- self.aCloseView.triggered.connect(lambda: self.mainGui.closeDocViewer())
223
+ self.aCloseView.triggered.connect(self.mainGui.closeDocViewer)
223
224
 
224
225
  # Document > Separator
225
226
  self.docuMenu.addSeparator()
@@ -671,6 +672,12 @@ class GuiMainMenu(QMenuBar):
671
672
  lambda: self.requestDocAction.emit(nwDocAction.SC_ULINE)
672
673
  )
673
674
 
675
+ # Shortcode Mark
676
+ self.aScMark = self.mShortcodes.addAction(self.tr("Highlight"))
677
+ self.aScMark.triggered.connect(
678
+ lambda: self.requestDocAction.emit(nwDocAction.SC_MARK)
679
+ )
680
+
674
681
  # Shortcode Superscript
675
682
  self.aScSuper = self.mShortcodes.addAction(self.tr("Superscript"))
676
683
  self.aScSuper.triggered.connect(
@@ -686,29 +693,29 @@ class GuiMainMenu(QMenuBar):
686
693
  # Format > Separator
687
694
  self.fmtMenu.addSeparator()
688
695
 
689
- # Format > Header 1 (Partition)
690
- self.aFmtHead1 = self.fmtMenu.addAction(self.tr("Header 1 (Partition)"))
696
+ # Format > Heading 1 (Partition)
697
+ self.aFmtHead1 = self.fmtMenu.addAction(self.tr("Heading 1 (Partition)"))
691
698
  self.aFmtHead1.setShortcut("Ctrl+1")
692
699
  self.aFmtHead1.triggered.connect(
693
700
  lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H1)
694
701
  )
695
702
 
696
- # Format > Header 2 (Chapter)
697
- self.aFmtHead2 = self.fmtMenu.addAction(self.tr("Header 2 (Chapter)"))
703
+ # Format > Heading 2 (Chapter)
704
+ self.aFmtHead2 = self.fmtMenu.addAction(self.tr("Heading 2 (Chapter)"))
698
705
  self.aFmtHead2.setShortcut("Ctrl+2")
699
706
  self.aFmtHead2.triggered.connect(
700
707
  lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H2)
701
708
  )
702
709
 
703
- # Format > Header 3 (Scene)
704
- self.aFmtHead3 = self.fmtMenu.addAction(self.tr("Header 3 (Scene)"))
710
+ # Format > Heading 3 (Scene)
711
+ self.aFmtHead3 = self.fmtMenu.addAction(self.tr("Heading 3 (Scene)"))
705
712
  self.aFmtHead3.setShortcut("Ctrl+3")
706
713
  self.aFmtHead3.triggered.connect(
707
714
  lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H3)
708
715
  )
709
716
 
710
- # Format > Header 4 (Section)
711
- self.aFmtHead4 = self.fmtMenu.addAction(self.tr("Header 4 (Section)"))
717
+ # Format > Heading 4 (Section)
718
+ self.aFmtHead4 = self.fmtMenu.addAction(self.tr("Heading 4 (Section)"))
712
719
  self.aFmtHead4.setShortcut("Ctrl+4")
713
720
  self.aFmtHead4.triggered.connect(
714
721
  lambda: self.requestDocAction.emit(nwDocAction.BLOCK_H4)
@@ -729,6 +736,12 @@ class GuiMainMenu(QMenuBar):
729
736
  lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN)
730
737
  )
731
738
 
739
+ # Format > Hard Scene
740
+ self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Hard Scene"))
741
+ self.aFmtHardSc.triggered.connect(
742
+ lambda: self.requestDocAction.emit(nwDocAction.BLOCK_HSC)
743
+ )
744
+
732
745
  # Format > Separator
733
746
  self.fmtMenu.addSeparator()
734
747
 
@@ -797,14 +810,14 @@ class GuiMainMenu(QMenuBar):
797
810
  # Format > Separator
798
811
  self.fmtMenu.addSeparator()
799
812
 
800
- # Format > Replace Single Quotes
801
- self.aFmtReplSng = self.fmtMenu.addAction(self.tr("Convert Single Quotes"))
813
+ # Format > Replace Straight Single Quotes
814
+ self.aFmtReplSng = self.fmtMenu.addAction(self.tr("Replace Straight Single Quotes"))
802
815
  self.aFmtReplSng.triggered.connect(
803
816
  lambda: self.requestDocAction.emit(nwDocAction.REPL_SNG)
804
817
  )
805
818
 
806
- # Format > Replace Double Quotes
807
- self.aFmtReplDbl = self.fmtMenu.addAction(self.tr("Convert Double Quotes"))
819
+ # Format > Replace Straight Double Quotes
820
+ self.aFmtReplDbl = self.fmtMenu.addAction(self.tr("Replace Straight Double Quotes"))
808
821
  self.aFmtReplDbl.triggered.connect(
809
822
  lambda: self.requestDocAction.emit(nwDocAction.REPL_DBL)
810
823
  )
@@ -849,6 +862,14 @@ class GuiMainMenu(QMenuBar):
849
862
  self.aReplaceNext.setShortcut("Ctrl+Shift+1")
850
863
  self.aReplaceNext.triggered.connect(lambda: self.mainGui.docEditor.replaceNext())
851
864
 
865
+ # Search > Separator
866
+ self.srcMenu.addSeparator()
867
+
868
+ # Search > Find in Project
869
+ self.aFindProj = self.srcMenu.addAction(self.tr("Find in Project"))
870
+ self.aFindProj.setShortcut("Ctrl+Shift+F")
871
+ self.aFindProj.triggered.connect(lambda: self.requestViewChange.emit(nwView.SEARCH))
872
+
852
873
  return
853
874
 
854
875
  def _buildToolsMenu(self) -> None:
@@ -35,16 +35,18 @@ from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
35
35
  from PyQt5.QtCore import QModelIndex, QPoint, Qt, QSize, pyqtSlot, pyqtSignal
36
36
  from PyQt5.QtWidgets import (
37
37
  QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
38
- QInputDialog, QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget,
39
- QTreeWidgetItem, QVBoxLayout, QWidget
38
+ QInputDialog, QMenu, QSizePolicy, QToolTip, QTreeWidget, QTreeWidgetItem,
39
+ QVBoxLayout, QWidget
40
40
  )
41
41
 
42
42
  from novelwriter import CONFIG, SHARED
43
- from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
44
43
  from novelwriter.common import minmax
45
44
  from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
46
45
  from novelwriter.core.index import IndexHeading
46
+ from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
47
+ from novelwriter.extensions.modified import NIconToolButton
47
48
  from novelwriter.extensions.novelselector import NovelSelector
49
+ from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
48
50
 
49
51
  if TYPE_CHECKING: # pragma: no cover
50
52
  from novelwriter.guimain import GuiMain
@@ -215,15 +217,13 @@ class GuiNovelToolBar(QWidget):
215
217
  self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
216
218
  self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
217
219
 
218
- self.tbNovel = QToolButton(self)
220
+ self.tbNovel = NIconToolButton(self, iPx)
219
221
  self.tbNovel.setToolTip(self.tr("Novel Root"))
220
- self.tbNovel.setIconSize(QSize(iPx, iPx))
221
222
  self.tbNovel.clicked.connect(self.novelValue.showPopup)
222
223
 
223
224
  # Refresh Button
224
- self.tbRefresh = QToolButton(self)
225
+ self.tbRefresh = NIconToolButton(self, iPx)
225
226
  self.tbRefresh.setToolTip(self.tr("Refresh"))
226
- self.tbRefresh.setIconSize(QSize(iPx, iPx))
227
227
  self.tbRefresh.clicked.connect(self._refreshNovelTree)
228
228
 
229
229
  # More Options Menu
@@ -241,11 +241,9 @@ class GuiNovelToolBar(QWidget):
241
241
  self.aLastColSize = self.mLastCol.addAction(self.tr("Column Size"))
242
242
  self.aLastColSize.triggered.connect(self._selectLastColumnSize)
243
243
 
244
- self.tbMore = QToolButton(self)
244
+ self.tbMore = NIconToolButton(self, iPx)
245
245
  self.tbMore.setToolTip(self.tr("More Options"))
246
- self.tbMore.setIconSize(QSize(iPx, iPx))
247
246
  self.tbMore.setMenu(self.mMore)
248
- self.tbMore.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
249
247
 
250
248
  # Assemble
251
249
  self.outerBox = QHBoxLayout()
@@ -280,16 +278,10 @@ class GuiNovelToolBar(QWidget):
280
278
  self.setPalette(qPalette)
281
279
 
282
280
  # StyleSheets
283
- fadeCol = qPalette.text().color()
284
- buttonStyle = (
285
- "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
286
- "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
287
- ).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
288
- buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
289
-
281
+ buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
290
282
  self.tbNovel.setStyleSheet(buttonStyle)
291
283
  self.tbRefresh.setStyleSheet(buttonStyle)
292
- self.tbMore.setStyleSheet(buttonStyleMenu)
284
+ self.tbMore.setStyleSheet(buttonStyle)
293
285
 
294
286
  self.novelValue.setStyleSheet(
295
287
  "QComboBox {border-style: none; padding-left: 0;} "
@@ -750,7 +742,7 @@ class GuiNovelTree(QTreeWidget):
750
742
  logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
751
743
 
752
744
  pIndex = SHARED.project.index
753
- novIdx = pIndex.getItemHeader(tHandle, sTitle)
745
+ novIdx = pIndex.getItemHeading(tHandle, sTitle)
754
746
  refTags = pIndex.getReferences(tHandle, sTitle)
755
747
  if not novIdx:
756
748
  return
@@ -798,14 +798,20 @@ class GuiOutlineDetails(QScrollArea):
798
798
  hSpace = int(CONFIG.pxInt(10))
799
799
  vSpace = int(CONFIG.pxInt(4))
800
800
 
801
+ bFont = SHARED.theme.guiFontB
802
+
801
803
  # Details Area
802
- self.titleLabel = QLabel("<b>%s</b>" % self.tr("Title"))
803
- self.fileLabel = QLabel("<b>%s</b>" % self.tr("Document"))
804
- self.itemLabel = QLabel("<b>%s</b>" % self.tr("Status"))
804
+ self.titleLabel = QLabel(self.tr("Title"))
805
+ self.fileLabel = QLabel(self.tr("Document"))
806
+ self.itemLabel = QLabel(self.tr("Status"))
805
807
  self.titleValue = QLabel("")
806
808
  self.fileValue = QLabel("")
807
809
  self.itemValue = QLabel("")
808
810
 
811
+ self.titleLabel.setFont(bFont)
812
+ self.fileLabel.setFont(bFont)
813
+ self.itemLabel.setFont(bFont)
814
+
809
815
  self.titleValue.setMinimumWidth(minTitle)
810
816
  self.titleValue.setMaximumWidth(maxTitle)
811
817
  self.fileValue.setMinimumWidth(minTitle)
@@ -814,13 +820,17 @@ class GuiOutlineDetails(QScrollArea):
814
820
  self.itemValue.setMaximumWidth(maxTitle)
815
821
 
816
822
  # Stats Area
817
- self.cCLabel = QLabel("<b>%s</b>" % self.tr("Characters"))
818
- self.wCLabel = QLabel("<b>%s</b>" % self.tr("Words"))
819
- self.pCLabel = QLabel("<b>%s</b>" % self.tr("Paragraphs"))
823
+ self.cCLabel = QLabel(self.tr("Characters"))
824
+ self.wCLabel = QLabel(self.tr("Words"))
825
+ self.pCLabel = QLabel(self.tr("Paragraphs"))
820
826
  self.cCValue = QLabel("")
821
827
  self.wCValue = QLabel("")
822
828
  self.pCValue = QLabel("")
823
829
 
830
+ self.cCLabel.setFont(bFont)
831
+ self.wCLabel.setFont(bFont)
832
+ self.pCLabel.setFont(bFont)
833
+
824
834
  self.cCValue.setMinimumWidth(wCount)
825
835
  self.wCValue.setMinimumWidth(wCount)
826
836
  self.pCValue.setMinimumWidth(wCount)
@@ -829,23 +839,36 @@ class GuiOutlineDetails(QScrollArea):
829
839
  self.pCValue.setAlignment(Qt.AlignRight)
830
840
 
831
841
  # Synopsis
832
- self.synopLabel = QLabel("<b>%s</b>" % self.tr("Synopsis"))
842
+ self.synopLabel = QLabel(self.tr("Synopsis"))
843
+ self.synopLabel.setFont(bFont)
844
+
833
845
  self.synopValue = QLabel("")
834
- self.synopLWrap = QHBoxLayout()
835
846
  self.synopValue.setWordWrap(True)
836
847
  self.synopValue.setAlignment(Qt.AlignTop | Qt.AlignLeft)
848
+
849
+ self.synopLWrap = QHBoxLayout()
837
850
  self.synopLWrap.addWidget(self.synopValue, 1)
838
851
 
839
852
  # Tags
840
- self.povKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]))
841
- self.focKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]))
842
- self.chrKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]))
843
- self.pltKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]))
844
- self.timKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]))
845
- self.wldKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]))
846
- self.objKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]))
847
- self.entKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]))
848
- self.cstKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]))
853
+ self.povKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]))
854
+ self.focKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]))
855
+ self.chrKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]))
856
+ self.pltKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]))
857
+ self.timKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]))
858
+ self.wldKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]))
859
+ self.objKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]))
860
+ self.entKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]))
861
+ self.cstKeyLabel = QLabel(trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]))
862
+
863
+ self.povKeyLabel.setFont(bFont)
864
+ self.focKeyLabel.setFont(bFont)
865
+ self.chrKeyLabel.setFont(bFont)
866
+ self.pltKeyLabel.setFont(bFont)
867
+ self.timKeyLabel.setFont(bFont)
868
+ self.wldKeyLabel.setFont(bFont)
869
+ self.objKeyLabel.setFont(bFont)
870
+ self.entKeyLabel.setFont(bFont)
871
+ self.cstKeyLabel.setFont(bFont)
849
872
 
850
873
  self.povKeyLWrap = QHBoxLayout()
851
874
  self.focKeyLWrap = QHBoxLayout()
@@ -989,7 +1012,7 @@ class GuiOutlineDetails(QScrollArea):
989
1012
 
990
1013
  def clearDetails(self) -> None:
991
1014
  """Clear all the data labels."""
992
- self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
1015
+ self.titleLabel.setText(self.tr("Title"))
993
1016
  self.titleValue.setText("")
994
1017
  self.fileValue.setText("")
995
1018
  self.itemValue.setText("")
@@ -1020,10 +1043,10 @@ class GuiOutlineDetails(QScrollArea):
1020
1043
  """
1021
1044
  pIndex = SHARED.project.index
1022
1045
  nwItem = SHARED.project.tree[tHandle]
1023
- novIdx = pIndex.getItemHeader(tHandle, sTitle)
1046
+ novIdx = pIndex.getItemHeading(tHandle, sTitle)
1024
1047
  novRefs = pIndex.getReferences(tHandle, sTitle)
1025
1048
  if nwItem and novIdx:
1026
- self.titleLabel.setText("<b>%s</b>" % self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
1049
+ self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1")))
1027
1050
  self.titleValue.setText(novIdx.title)
1028
1051
 
1029
1052
  itemStatus, _ = nwItem.getImportStatus(incIcon=False)
@@ -38,20 +38,22 @@ from PyQt5.QtGui import (
38
38
  from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
39
39
  from PyQt5.QtWidgets import (
40
40
  QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
41
- QLabel, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget,
42
- QTreeWidgetItem, QVBoxLayout, QWidget
41
+ QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
42
+ QVBoxLayout, QWidget
43
43
  )
44
44
 
45
45
  from novelwriter import CONFIG, SHARED
46
- from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
47
46
  from novelwriter.common import minmax
48
47
  from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
49
- from novelwriter.core.item import NWItem
50
48
  from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
49
+ from novelwriter.core.item import NWItem
51
50
  from novelwriter.dialogs.docmerge import GuiDocMerge
52
51
  from novelwriter.dialogs.docsplit import GuiDocSplit
53
52
  from novelwriter.dialogs.editlabel import GuiEditLabel
54
53
  from novelwriter.dialogs.projectsettings import GuiProjectSettings
54
+ from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
55
+ from novelwriter.extensions.modified import NIconToolButton
56
+ from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
55
57
 
56
58
  if TYPE_CHECKING: # pragma: no cover
57
59
  from novelwriter.guimain import GuiMain
@@ -142,7 +144,6 @@ class GuiProjectView(QWidget):
142
144
  self.requestDeleteItem = self.projTree.requestDeleteItem
143
145
  self.getSelectedHandle = self.projTree.getSelectedHandle
144
146
  self.changedSince = self.projTree.changedSince
145
- self.createNewNote = self.projTree.createNewNote
146
147
 
147
148
  return
148
149
 
@@ -240,6 +241,12 @@ class GuiProjectView(QWidget):
240
241
  self.projBar.buildQuickLinksMenu()
241
242
  return
242
243
 
244
+ @pyqtSlot(str, nwItemClass)
245
+ def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
246
+ """Process new not request."""
247
+ self.projTree.createNewNote(tag, itemClass)
248
+ return
249
+
243
250
  # END Class GuiProjectView
244
251
 
245
252
 
@@ -263,29 +270,26 @@ class GuiProjectToolBar(QWidget):
263
270
  self.setAutoFillBackground(True)
264
271
 
265
272
  # Widget Label
266
- self.viewLabel = QLabel("<b>%s</b>" % self.tr("Project Content"))
273
+ self.viewLabel = QLabel(self.tr("Project Content"))
274
+ self.viewLabel.setFont(SHARED.theme.guiFontB)
267
275
  self.viewLabel.setContentsMargins(0, 0, 0, 0)
268
276
  self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
269
277
 
270
278
  # Quick Links
271
279
  self.mQuick = QMenu(self)
272
280
 
273
- self.tbQuick = QToolButton(self)
281
+ self.tbQuick = NIconToolButton(self, iPx)
274
282
  self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
275
283
  self.tbQuick.setShortcut("Ctrl+L")
276
- self.tbQuick.setIconSize(QSize(iPx, iPx))
277
284
  self.tbQuick.setMenu(self.mQuick)
278
- self.tbQuick.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
279
285
 
280
286
  # Move Buttons
281
- self.tbMoveU = QToolButton(self)
287
+ self.tbMoveU = NIconToolButton(self, iPx)
282
288
  self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
283
- self.tbMoveU.setIconSize(QSize(iPx, iPx))
284
289
  self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1))
285
290
 
286
- self.tbMoveD = QToolButton(self)
291
+ self.tbMoveD = NIconToolButton(self, iPx)
287
292
  self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
288
- self.tbMoveD.setIconSize(QSize(iPx, iPx))
289
293
  self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
290
294
 
291
295
  # Add Item Menu
@@ -324,12 +328,10 @@ class GuiProjectToolBar(QWidget):
324
328
  self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
325
329
  self._buildRootMenu()
326
330
 
327
- self.tbAdd = QToolButton(self)
331
+ self.tbAdd = NIconToolButton(self, iPx)
328
332
  self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item"))
329
333
  self.tbAdd.setShortcut("Ctrl+N")
330
- self.tbAdd.setIconSize(QSize(iPx, iPx))
331
334
  self.tbAdd.setMenu(self.mAdd)
332
- self.tbAdd.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
333
335
 
334
336
  # More Options Menu
335
337
  self.mMore = QMenu(self)
@@ -343,11 +345,9 @@ class GuiProjectToolBar(QWidget):
343
345
  self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
344
346
  self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash())
345
347
 
346
- self.tbMore = QToolButton(self)
348
+ self.tbMore = NIconToolButton(self, iPx)
347
349
  self.tbMore.setToolTip(self.tr("More Options"))
348
- self.tbMore.setIconSize(QSize(iPx, iPx))
349
350
  self.tbMore.setMenu(self.mMore)
350
- self.tbMore.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
351
351
 
352
352
  # Assemble
353
353
  self.outerBox = QHBoxLayout()
@@ -377,18 +377,12 @@ class GuiProjectToolBar(QWidget):
377
377
  qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
378
378
  self.setPalette(qPalette)
379
379
 
380
- fadeCol = qPalette.text().color()
381
- buttonStyle = (
382
- "QToolButton {{padding: {0}px; border: none; background: transparent;}} "
383
- "QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
384
- ).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
385
- buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
386
-
387
- self.tbQuick.setStyleSheet(buttonStyleMenu)
380
+ buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
381
+ self.tbQuick.setStyleSheet(buttonStyle)
388
382
  self.tbMoveU.setStyleSheet(buttonStyle)
389
383
  self.tbMoveD.setStyleSheet(buttonStyle)
390
- self.tbAdd.setStyleSheet(buttonStyleMenu)
391
- self.tbMore.setStyleSheet(buttonStyleMenu)
384
+ self.tbAdd.setStyleSheet(buttonStyle)
385
+ self.tbMore.setStyleSheet(buttonStyle)
392
386
 
393
387
  self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark"))
394
388
  self.tbMoveU.setIcon(SHARED.theme.getIcon("up"))
@@ -606,18 +600,18 @@ class GuiProjectTree(QTreeWidget):
606
600
  self._timeChanged = 0.0
607
601
  return
608
602
 
609
- def createNewNote(self, tag: str, itemClass: nwItemClass | None) -> bool:
603
+ def createNewNote(self, tag: str, itemClass: nwItemClass) -> None:
610
604
  """Create a new note. This function is used by the document
611
605
  editor to create note files for unknown tags.
612
606
  """
613
- rHandle = SHARED.project.tree.findRoot(itemClass)
614
- if rHandle:
615
- tHandle = SHARED.project.newFile(tag, rHandle)
616
- if tHandle:
607
+ if itemClass != nwItemClass.NO_CLASS:
608
+ if not (rHandle := SHARED.project.tree.findRoot(itemClass)):
609
+ self.newTreeItem(nwItemType.ROOT, itemClass)
610
+ rHandle = SHARED.project.tree.findRoot(itemClass)
611
+ if rHandle and (tHandle := SHARED.project.newFile(tag, rHandle)):
617
612
  SHARED.project.writeNewFile(tHandle, 1, False, f"@tag: {tag}\n\n")
618
613
  self.revealNewTreeItem(tHandle, wordCount=True)
619
- return True
620
- return False
614
+ return
621
615
 
622
616
  def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None,
623
617
  hLevel: int = 1, isNote: bool = False, copyDoc: str | None = None) -> bool:
@@ -1834,7 +1828,7 @@ class _TreeContextMenu(QMenu):
1834
1828
 
1835
1829
  def _itemHeader(self) -> None:
1836
1830
  """Check if there is a header that can be used for rename."""
1837
- if hItem := SHARED.project.index.getItemHeader(self._handle, "T0001"):
1831
+ if hItem := SHARED.project.index.getItemHeading(self._handle, "T0001"):
1838
1832
  action = self.addAction(self.tr("Rename to Heading"))
1839
1833
  action.triggered.connect(
1840
1834
  lambda: self.projTree.renameTreeItem(self._handle, hItem.title)
@@ -1933,7 +1927,7 @@ class _TreeContextMenu(QMenu):
1933
1927
  action.triggered.connect(lambda: tree._mergeDocuments(tHandle, True))
1934
1928
 
1935
1929
  if isFile:
1936
- action = menu.addAction(self.tr("Split Document by Headers"))
1930
+ action = menu.addAction(self.tr("Split Document by Headings"))
1937
1931
  action.triggered.connect(lambda: tree._splitDocument(tHandle))
1938
1932
 
1939
1933
  return
@@ -1947,11 +1941,9 @@ class _TreeContextMenu(QMenu):
1947
1941
  action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, True))
1948
1942
  action = self.addAction(self.tr("Collapse All"))
1949
1943
  action.triggered.connect(lambda: tree.setExpandedFromHandle(tHandle, False))
1950
- action = self.addAction(self.tr("Duplicate from Here"))
1951
- action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
1952
- elif isFile:
1953
- action = self.addAction(self.tr("Duplicate Document"))
1954
- action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
1944
+
1945
+ action = self.addAction(self.tr("Duplicate"))
1946
+ action.triggered.connect(lambda: tree._duplicateFromHandle(tHandle))
1955
1947
 
1956
1948
  if self._item.itemClass == nwItemClass.TRASH or isRoot or (isFolder and not hasChild):
1957
1949
  action = self.addAction(self.tr("Delete Permanently"))