novelWriter 2.3.1__py3-none-any.whl → 2.4__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 (119) hide show
  1. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/METADATA +5 -6
  2. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/RECORD +114 -107
  3. novelwriter/__init__.py +17 -10
  4. novelwriter/assets/i18n/nw_de_DE.qm +0 -0
  5. novelwriter/assets/i18n/nw_en_US.qm +0 -0
  6. novelwriter/assets/i18n/nw_es_419.qm +0 -0
  7. novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
  8. novelwriter/assets/i18n/nw_it_IT.qm +0 -0
  9. novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
  10. novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
  11. novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
  12. novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
  13. novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
  14. novelwriter/assets/icons/none.svg +4 -0
  15. novelwriter/assets/icons/typicons_dark/icons.conf +4 -0
  16. novelwriter/assets/icons/typicons_dark/nw_tb-mark.svg +7 -0
  17. novelwriter/assets/icons/typicons_dark/typ_refresh-flipped.svg +1 -1
  18. novelwriter/assets/icons/typicons_dark/typ_refresh.svg +1 -1
  19. novelwriter/assets/icons/typicons_dark/typ_search-grey.svg +4 -0
  20. novelwriter/assets/icons/typicons_dark/typ_times.svg +1 -1
  21. novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
  22. novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
  23. novelwriter/assets/icons/typicons_light/icons.conf +4 -0
  24. novelwriter/assets/icons/typicons_light/nw_tb-mark.svg +7 -0
  25. novelwriter/assets/icons/typicons_light/typ_refresh-flipped.svg +1 -1
  26. novelwriter/assets/icons/typicons_light/typ_refresh.svg +1 -1
  27. novelwriter/assets/icons/typicons_light/typ_search-grey.svg +4 -0
  28. novelwriter/assets/icons/typicons_light/typ_times.svg +1 -1
  29. novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
  30. novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
  31. novelwriter/assets/manual.pdf +0 -0
  32. novelwriter/assets/sample.zip +0 -0
  33. novelwriter/assets/syntax/default_dark.conf +1 -0
  34. novelwriter/assets/syntax/default_light.conf +1 -0
  35. novelwriter/assets/syntax/grey_dark.conf +1 -0
  36. novelwriter/assets/syntax/grey_light.conf +1 -0
  37. novelwriter/assets/syntax/light_owl.conf +1 -0
  38. novelwriter/assets/syntax/night_owl.conf +1 -0
  39. novelwriter/assets/syntax/solarized_dark.conf +1 -0
  40. novelwriter/assets/syntax/solarized_light.conf +1 -0
  41. novelwriter/assets/syntax/tomorrow.conf +1 -0
  42. novelwriter/assets/syntax/tomorrow_night.conf +1 -0
  43. novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
  44. novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
  45. novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
  46. novelwriter/assets/text/credits_en.htm +25 -23
  47. novelwriter/common.py +12 -4
  48. novelwriter/config.py +47 -16
  49. novelwriter/constants.py +5 -6
  50. novelwriter/core/buildsettings.py +64 -44
  51. novelwriter/core/coretools.py +97 -13
  52. novelwriter/core/docbuild.py +74 -7
  53. novelwriter/core/document.py +24 -3
  54. novelwriter/core/index.py +31 -112
  55. novelwriter/core/project.py +10 -15
  56. novelwriter/core/projectxml.py +1 -1
  57. novelwriter/core/sessions.py +2 -2
  58. novelwriter/core/spellcheck.py +3 -3
  59. novelwriter/core/status.py +6 -5
  60. novelwriter/core/storage.py +8 -2
  61. novelwriter/core/tohtml.py +22 -25
  62. novelwriter/core/tokenizer.py +417 -233
  63. novelwriter/core/tomd.py +17 -8
  64. novelwriter/core/toodt.py +386 -351
  65. novelwriter/core/tree.py +8 -8
  66. novelwriter/dialogs/about.py +9 -11
  67. novelwriter/dialogs/docmerge.py +17 -14
  68. novelwriter/dialogs/docsplit.py +20 -19
  69. novelwriter/dialogs/editlabel.py +5 -4
  70. novelwriter/dialogs/preferences.py +32 -40
  71. novelwriter/dialogs/projectsettings.py +31 -28
  72. novelwriter/dialogs/quotes.py +10 -9
  73. novelwriter/dialogs/wordlist.py +17 -14
  74. novelwriter/enum.py +17 -14
  75. novelwriter/error.py +14 -12
  76. novelwriter/extensions/circularprogress.py +12 -8
  77. novelwriter/extensions/configlayout.py +1 -3
  78. novelwriter/extensions/modified.py +51 -2
  79. novelwriter/extensions/pagedsidebar.py +16 -14
  80. novelwriter/extensions/simpleprogress.py +3 -1
  81. novelwriter/extensions/statusled.py +3 -1
  82. novelwriter/extensions/switch.py +10 -9
  83. novelwriter/extensions/switchbox.py +14 -13
  84. novelwriter/extensions/versioninfo.py +1 -1
  85. novelwriter/gui/doceditor.py +433 -496
  86. novelwriter/gui/dochighlight.py +54 -33
  87. novelwriter/gui/docviewer.py +162 -175
  88. novelwriter/gui/docviewerpanel.py +20 -37
  89. novelwriter/gui/editordocument.py +15 -4
  90. novelwriter/gui/itemdetails.py +51 -54
  91. novelwriter/gui/mainmenu.py +37 -16
  92. novelwriter/gui/noveltree.py +31 -37
  93. novelwriter/gui/outline.py +120 -98
  94. novelwriter/gui/projtree.py +61 -67
  95. novelwriter/gui/search.py +362 -0
  96. novelwriter/gui/sidebar.py +36 -45
  97. novelwriter/gui/statusbar.py +14 -14
  98. novelwriter/gui/theme.py +107 -32
  99. novelwriter/guimain.py +209 -202
  100. novelwriter/shared.py +31 -6
  101. novelwriter/text/counting.py +138 -0
  102. novelwriter/tools/dictionaries.py +15 -14
  103. novelwriter/tools/lipsum.py +20 -17
  104. novelwriter/tools/manusbuild.py +43 -35
  105. novelwriter/tools/manuscript.py +381 -104
  106. novelwriter/tools/manussettings.py +262 -125
  107. novelwriter/tools/noveldetails.py +20 -18
  108. novelwriter/tools/welcome.py +52 -49
  109. novelwriter/tools/writingstats.py +61 -55
  110. novelwriter/types.py +90 -0
  111. novelwriter/core/__init__.py +0 -3
  112. novelwriter/dialogs/__init__.py +0 -3
  113. novelwriter/extensions/__init__.py +0 -3
  114. novelwriter/gui/__init__.py +0 -3
  115. novelwriter/tools/__init__.py +0 -3
  116. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/LICENSE.md +0 -0
  117. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/WHEEL +0 -0
  118. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/entry_points.txt +0 -0
  119. {novelWriter-2.3.1.dist-info → novelWriter-2.4.dist-info}/top_level.txt +0 -0
@@ -26,8 +26,8 @@ from __future__ import annotations
26
26
  import math
27
27
  import logging
28
28
 
29
+ from PyQt5.QtCore import pyqtSlot
29
30
  from PyQt5.QtGui import QCloseEvent
30
- from PyQt5.QtCore import QSize, Qt, pyqtSlot
31
31
  from PyQt5.QtWidgets import (
32
32
  QAbstractItemView, QDialog, QDialogButtonBox, QFormLayout, QGridLayout,
33
33
  QHBoxLayout, QLabel, QSpinBox, QStackedWidget, QTreeWidget,
@@ -37,10 +37,11 @@ from PyQt5.QtWidgets import (
37
37
  from novelwriter import CONFIG, SHARED
38
38
  from novelwriter.common import formatTime, numberToRoman
39
39
  from novelwriter.constants import nwUnicode
40
- from novelwriter.extensions.switch import NSwitch
41
40
  from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollablePage
42
- from novelwriter.extensions.pagedsidebar import NPagedSideBar
43
41
  from novelwriter.extensions.novelselector import NovelSelector
42
+ from novelwriter.extensions.pagedsidebar import NPagedSideBar
43
+ from novelwriter.extensions.switch import NSwitch
44
+ from novelwriter.types import QtAlignRight, QtDecoration, QtDialogClose
44
45
 
45
46
  logger = logging.getLogger(__name__)
46
47
 
@@ -94,7 +95,7 @@ class GuiNovelDetails(QDialog):
94
95
  self.mainStack.addWidget(self.contentsPage)
95
96
 
96
97
  # Buttons
97
- self.buttonBox = QDialogButtonBox(QDialogButtonBox.StandardButton.Close)
98
+ self.buttonBox = QDialogButtonBox(QtDialogClose, self)
98
99
  self.buttonBox.rejected.connect(self.close)
99
100
 
100
101
  # Assemble
@@ -309,7 +310,8 @@ class _ContentsPage(NFixedPage):
309
310
  self._data = []
310
311
  self._currentRoot = None
311
312
 
312
- iPx = SHARED.theme.baseIconSize
313
+ iPx = SHARED.theme.baseIconHeight
314
+ iSz = SHARED.theme.baseIconSize
313
315
  hPx = CONFIG.pxInt(12)
314
316
  vPx = CONFIG.pxInt(4)
315
317
  options = SHARED.project.options
@@ -322,7 +324,7 @@ class _ContentsPage(NFixedPage):
322
324
 
323
325
  # Contents Tree
324
326
  self.tocTree = QTreeWidget(self)
325
- self.tocTree.setIconSize(QSize(iPx, iPx))
327
+ self.tocTree.setIconSize(iSz)
326
328
  self.tocTree.setIndentation(0)
327
329
  self.tocTree.setColumnCount(6)
328
330
  self.tocTree.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
@@ -337,10 +339,10 @@ class _ContentsPage(NFixedPage):
337
339
 
338
340
  treeHeadItem = self.tocTree.headerItem()
339
341
  if treeHeadItem:
340
- treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignmentFlag.AlignRight)
341
- treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignmentFlag.AlignRight)
342
- treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignmentFlag.AlignRight)
343
- treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignmentFlag.AlignRight)
342
+ treeHeadItem.setTextAlignment(self.C_WORDS, QtAlignRight)
343
+ treeHeadItem.setTextAlignment(self.C_PAGES, QtAlignRight)
344
+ treeHeadItem.setTextAlignment(self.C_PAGE, QtAlignRight)
345
+ treeHeadItem.setTextAlignment(self.C_PROG, QtAlignRight)
344
346
 
345
347
  treeHeader = self.tocTree.header()
346
348
  treeHeader.setStretchLastSection(True)
@@ -364,7 +366,7 @@ class _ContentsPage(NFixedPage):
364
366
  countFrom = options.getInt("GuiNovelDetails", "countFrom", 1)
365
367
  clearDouble = options.getBool("GuiNovelDetails", "clearDouble", True)
366
368
 
367
- self.wpLabel = QLabel(self.tr("Words per page"))
369
+ self.wpLabel = QLabel(self.tr("Words per page"), self)
368
370
 
369
371
  self.wpValue = QSpinBox(self)
370
372
  self.wpValue.setMinimum(10)
@@ -373,7 +375,7 @@ class _ContentsPage(NFixedPage):
373
375
  self.wpValue.setValue(wordsPerPage)
374
376
  self.wpValue.valueChanged.connect(self._populateTree)
375
377
 
376
- self.poLabel = QLabel(self.tr("First page offset"))
378
+ self.poLabel = QLabel(self.tr("First page offset"), self)
377
379
 
378
380
  self.poValue = QSpinBox(self)
379
381
  self.poValue.setMinimum(1)
@@ -382,7 +384,7 @@ class _ContentsPage(NFixedPage):
382
384
  self.poValue.setValue(countFrom)
383
385
  self.poValue.valueChanged.connect(self._populateTree)
384
386
 
385
- self.dblLabel = QLabel(self.tr("Chapters on odd pages"))
387
+ self.dblLabel = QLabel(self.tr("Chapters on odd pages"), self)
386
388
 
387
389
  self.dblValue = NSwitch(self, height=iPx)
388
390
  self.dblValue.setChecked(clearDouble)
@@ -483,17 +485,17 @@ class _ContentsPage(NFixedPage):
483
485
  if tTitle.strip() == "":
484
486
  tTitle = self.tr("Untitled")
485
487
 
486
- newItem.setData(self.C_TITLE, Qt.DecorationRole, hDec)
488
+ newItem.setData(self.C_TITLE, QtDecoration, hDec)
487
489
  newItem.setText(self.C_TITLE, tTitle)
488
490
  newItem.setText(self.C_WORDS, f"{wCount:n}")
489
491
  newItem.setText(self.C_PAGES, f"{pCount:n}")
490
492
  newItem.setText(self.C_PAGE, progPage)
491
493
  newItem.setText(self.C_PROG, progText)
492
494
 
493
- newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
494
- newItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
495
- newItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
496
- newItem.setTextAlignment(self.C_PROG, Qt.AlignRight)
495
+ newItem.setTextAlignment(self.C_WORDS, QtAlignRight)
496
+ newItem.setTextAlignment(self.C_PAGES, QtAlignRight)
497
+ newItem.setTextAlignment(self.C_PAGE, QtAlignRight)
498
+ newItem.setTextAlignment(self.C_PROG, QtAlignRight)
497
499
 
498
500
  # Make pages and titles/partitions stand out
499
501
  if tLevel < 2:
@@ -25,34 +25,35 @@ from __future__ import annotations
25
25
 
26
26
  import logging
27
27
 
28
- from pathlib import Path
29
28
  from datetime import datetime
29
+ from pathlib import Path
30
30
 
31
- from PyQt5.QtGui import QCloseEvent, QColor, QFont, QPaintEvent, QPainter, QPen
32
31
  from PyQt5.QtCore import (
33
32
  QAbstractListModel, QEvent, QModelIndex, QObject, QPoint, QSize, Qt,
34
33
  pyqtSignal, pyqtSlot
35
34
  )
35
+ from PyQt5.QtGui import QCloseEvent, QColor, QFont, QPaintEvent, QPainter, QPen
36
36
  from PyQt5.QtWidgets import (
37
- QAction, QDialog, QFileDialog, QFormLayout, QHBoxLayout, QLabel, QLineEdit,
38
- QListView, QMenu, QPushButton, QScrollArea, QShortcut, QStackedWidget,
39
- QStyle, QStyleOptionViewItem, QStyledItemDelegate, QToolButton,
40
- QVBoxLayout, QWidget, qApp
37
+ QAction, QApplication, QDialog, QFileDialog, QFormLayout, QHBoxLayout,
38
+ QLabel, QLineEdit, QListView, QMenu, QPushButton, QScrollArea, QShortcut,
39
+ QStackedWidget, QStyleOptionViewItem, QStyledItemDelegate, QVBoxLayout,
40
+ QWidget
41
41
  )
42
42
 
43
43
  from novelwriter import CONFIG, SHARED
44
- from novelwriter.enum import nwItemClass
45
- from novelwriter.common import formatInt, makeFileNameSafe
44
+ from novelwriter.common import cssCol, formatInt, makeFileNameSafe
46
45
  from novelwriter.constants import nwFiles
47
46
  from novelwriter.core.coretools import ProjectBuilder
47
+ from novelwriter.enum import nwItemClass
48
48
  from novelwriter.extensions.configlayout import NWrappedWidgetBox
49
+ from novelwriter.extensions.modified import NIconToolButton, NSpinBox
49
50
  from novelwriter.extensions.switch import NSwitch
50
- from novelwriter.extensions.modified import NSpinBox
51
51
  from novelwriter.extensions.versioninfo import VersionInfoWidget
52
+ from novelwriter.types import QtAlignLeft, QtAlignRightTop, QtSelected
52
53
 
53
54
  logger = logging.getLogger(__name__)
54
55
 
55
- PANEL_ALPHA = 0.7
56
+ PANEL_ALPHA = 178
56
57
 
57
58
 
58
59
  class GuiWelcome(QDialog):
@@ -75,8 +76,9 @@ class GuiWelcome(QDialog):
75
76
  hD = CONFIG.pxInt(36)
76
77
  hE = CONFIG.pxInt(48)
77
78
  hF = CONFIG.pxInt(128)
78
- self._hPx = CONFIG.pxInt(600)
79
+ btnIconSize = SHARED.theme.buttonIconSize
79
80
 
81
+ self._hPx = CONFIG.pxInt(600)
80
82
  self.resize(*CONFIG.welcomeWinSize)
81
83
 
82
84
  # Elements
@@ -109,26 +111,32 @@ class GuiWelcome(QDialog):
109
111
 
110
112
  self.btnList = QPushButton(self.tr("List"), self)
111
113
  self.btnList.setIcon(SHARED.theme.getIcon("list"))
114
+ self.btnList.setIconSize(btnIconSize)
112
115
  self.btnList.clicked.connect(self._showOpenProjectPage)
113
116
 
114
117
  self.btnNew = QPushButton(self.tr("New"), self)
115
118
  self.btnNew.setIcon(SHARED.theme.getIcon("add"))
119
+ self.btnNew.setIconSize(btnIconSize)
116
120
  self.btnNew.clicked.connect(self._showNewProjectPage)
117
121
 
118
122
  self.btnBrowse = QPushButton(self.tr("Browse"), self)
119
123
  self.btnBrowse.setIcon(SHARED.theme.getIcon("browse"))
124
+ self.btnBrowse.setIconSize(btnIconSize)
120
125
  self.btnBrowse.clicked.connect(self._browseForProject)
121
126
 
122
127
  self.btnCancel = QPushButton(self.tr("Cancel"), self)
123
128
  self.btnCancel.setIcon(SHARED.theme.getIcon("cross"))
129
+ self.btnCancel.setIconSize(btnIconSize)
124
130
  self.btnCancel.clicked.connect(self.close)
125
131
 
126
132
  self.btnCreate = QPushButton(self.tr("Create"), self)
127
133
  self.btnCreate.setIcon(SHARED.theme.getIcon("star"))
134
+ self.btnCreate.setIconSize(btnIconSize)
128
135
  self.btnCreate.clicked.connect(self.tabNew.createNewProject)
129
136
 
130
137
  self.btnOpen = QPushButton(self.tr("Open"), self)
131
138
  self.btnOpen.setIcon(SHARED.theme.getIcon("open"))
139
+ self.btnOpen.setIconSize(btnIconSize)
132
140
  self.btnOpen.clicked.connect(self._openSelectedItem)
133
141
 
134
142
  self.btnBox = QHBoxLayout()
@@ -153,10 +161,8 @@ class GuiWelcome(QDialog):
153
161
  self.innerBox.addSpacing(hB)
154
162
  self.innerBox.addLayout(self.btnBox)
155
163
 
156
- topRight = Qt.AlignmentFlag.AlignTop | Qt.AlignmentFlag.AlignRight
157
-
158
164
  self.outerBox = QHBoxLayout()
159
- self.outerBox.addWidget(self.nwLogo, 3, topRight)
165
+ self.outerBox.addWidget(self.nwLogo, 3, QtAlignRightTop)
160
166
  self.outerBox.addLayout(self.innerBox, 9)
161
167
  self.outerBox.setContentsMargins(hF, hE, hC, hE)
162
168
 
@@ -306,12 +312,12 @@ class _OpenProjectPage(QWidget):
306
312
 
307
313
  self._selectFirstItem()
308
314
 
309
- baseCol = self.palette().base().color()
310
- self.setStyleSheet((
311
- "QListView {{border: none; background: rgba({r},{g},{b},{a});}} "
312
- "QLineEdit {{border: none; background: rgba({r},{g},{b},{a}); padding: {m}px;}} "
313
- ).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue(),
314
- a=PANEL_ALPHA, m=CONFIG.pxInt(4)))
315
+ mPx = CONFIG.pxInt(4)
316
+ baseCol = cssCol(self.palette().base().color(), PANEL_ALPHA)
317
+ self.setStyleSheet(
318
+ f"QListView {{border: none; background: {baseCol};}} "
319
+ f"QLineEdit {{border: none; background: {baseCol}; padding: {mPx}px;}} "
320
+ )
315
321
 
316
322
  return
317
323
 
@@ -371,7 +377,7 @@ class _OpenProjectPage(QWidget):
371
377
  action.triggered.connect(self.openSelectedItem)
372
378
  action = ctxMenu.addAction(self.tr("Remove Project"))
373
379
  action.triggered.connect(self._deleteSelectedItem)
374
- ctxMenu.exec_(self.mapToGlobal(pos))
380
+ ctxMenu.exec(self.mapToGlobal(pos))
375
381
  ctxMenu.deleteLater()
376
382
  return
377
383
 
@@ -405,11 +411,11 @@ class _ProjectListItem(QStyledItemDelegate):
405
411
  self._pPx = (mPx//2, 3*mPx//2, iPx + mPx, mPx, mPx + tPx) # Painter coordinates
406
412
  self._hPx = 2*mPx + tPx + fPx # Fixed height
407
413
 
408
- self._tFont = qApp.font()
414
+ self._tFont = QApplication.font()
409
415
  self._tFont.setPointSizeF(1.2*fPt)
410
416
  self._tFont.setWeight(QFont.Weight.Bold)
411
417
 
412
- self._dFont = qApp.font()
418
+ self._dFont = QApplication.font()
413
419
  self._dFont.setPointSizeF(fPt)
414
420
  self._dPen = QPen(SHARED.theme.helpText)
415
421
 
@@ -425,9 +431,9 @@ class _ProjectListItem(QStyledItemDelegate):
425
431
  ix, iy, x, y1, y2 = self._pPx
426
432
 
427
433
  painter.save()
428
- if opt.state & QStyle.StateFlag.State_Selected == QStyle.StateFlag.State_Selected:
434
+ if opt.state & QtSelected == QtSelected:
429
435
  painter.setOpacity(0.25)
430
- painter.fillRect(rect, qApp.palette().highlight())
436
+ painter.fillRect(rect, QApplication.palette().highlight())
431
437
  painter.setOpacity(1.0)
432
438
 
433
439
  painter.drawPixmap(ix, rect.top() + iy, self._icon)
@@ -456,7 +462,7 @@ class _ProjectListModel(QAbstractListModel):
456
462
  opened = self.tr("Last Opened")
457
463
  records = sorted(CONFIG.recentProjects.listEntries(), key=lambda x: x[3], reverse=True)
458
464
  for path, title, count, time in records:
459
- when = datetime.fromtimestamp(time).strftime("%x")
465
+ when = CONFIG.localDate(datetime.fromtimestamp(time))
460
466
  data.append((title, path, f"{opened}: {when}, {words}: {formatInt(count)}"))
461
467
  self._data = data
462
468
  return
@@ -518,11 +524,11 @@ class _NewProjectPage(QWidget):
518
524
  # Styles
519
525
  # ======
520
526
 
521
- baseCol = self.palette().base().color()
522
- self.setStyleSheet((
523
- "QScrollArea {{border: none; background: rgba({r},{g},{b},{a});}} "
524
- "_NewProjectForm {{border: none; background: rgba({r},{g},{b},{a});}} "
525
- ).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue(), a=PANEL_ALPHA))
527
+ baseCol = cssCol(self.palette().base().color(), PANEL_ALPHA)
528
+ self.setStyleSheet(
529
+ f"QScrollArea {{border: none; background: {baseCol};}} "
530
+ f"_NewProjectForm {{border: none; background: {baseCol};}} "
531
+ )
526
532
 
527
533
  return
528
534
 
@@ -554,11 +560,12 @@ class _NewProjectForm(QWidget):
554
560
  def __init__(self, parent: QWidget) -> None:
555
561
  super().__init__(parent=parent)
556
562
 
557
- self._basePath = CONFIG.lastPath()
563
+ self._basePath = CONFIG.homePath()
558
564
  self._fillMode = self.FILL_BLANK
559
565
  self._copyPath = None
560
566
 
561
- iPx = SHARED.theme.baseIconSize
567
+ iPx = SHARED.theme.baseIconHeight
568
+ iSz = SHARED.theme.baseIconSize
562
569
  sPx = CONFIG.pxInt(16)
563
570
 
564
571
  # Project Settings
@@ -579,8 +586,7 @@ class _NewProjectForm(QWidget):
579
586
  self.projPath = QLineEdit(self)
580
587
  self.projPath.setReadOnly(True)
581
588
 
582
- self.browsePath = QToolButton(self)
583
- self.browsePath.setIcon(SHARED.theme.getIcon("browse"))
589
+ self.browsePath = NIconToolButton(self, iSz, "browse")
584
590
  self.browsePath.clicked.connect(self._doBrowse)
585
591
 
586
592
  self.pathBox = QHBoxLayout()
@@ -591,8 +597,7 @@ class _NewProjectForm(QWidget):
591
597
  self.projFill = QLineEdit(self)
592
598
  self.projFill.setReadOnly(True)
593
599
 
594
- self.browseFill = QToolButton(self)
595
- self.browseFill.setIcon(SHARED.theme.getIcon("add_document"))
600
+ self.browseFill = NIconToolButton(self, iSz, "add_document")
596
601
 
597
602
  self.fillMenu = _PopLeftDirectionMenu(self.browseFill)
598
603
 
@@ -609,7 +614,6 @@ class _NewProjectForm(QWidget):
609
614
  self.fillCopy.triggered.connect(self._setFillCopy)
610
615
 
611
616
  self.browseFill.setMenu(self.fillMenu)
612
- self.browseFill.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
613
617
 
614
618
  self.fillBox = QHBoxLayout()
615
619
  self.fillBox.addWidget(self.projFill)
@@ -617,7 +621,7 @@ class _NewProjectForm(QWidget):
617
621
 
618
622
  # Project Form
619
623
  self.projectForm = QFormLayout()
620
- self.projectForm.setAlignment(Qt.AlignmentFlag.AlignLeft)
624
+ self.projectForm.setAlignment(QtAlignLeft)
621
625
  self.projectForm.addRow(self.tr("Project Name"), self.projName)
622
626
  self.projectForm.addRow(self.tr("Author"), self.projAuthor)
623
627
  self.projectForm.addRow(self.tr("Project Path"), self.pathBox)
@@ -628,7 +632,7 @@ class _NewProjectForm(QWidget):
628
632
 
629
633
  self.numChapters = NSpinBox(self)
630
634
  self.numChapters.setRange(0, 200)
631
- self.numChapters.setValue(5)
635
+ self.numChapters.setValue(0)
632
636
  self.numChapters.setToolTip(self.tr("Set to 0 to only add scenes"))
633
637
 
634
638
  self.chapterBox = NWrappedWidgetBox(
@@ -638,7 +642,7 @@ class _NewProjectForm(QWidget):
638
642
 
639
643
  self.numScenes = NSpinBox(self)
640
644
  self.numScenes.setRange(0, 200)
641
- self.numScenes.setValue(5)
645
+ self.numScenes.setValue(0)
642
646
 
643
647
  self.sceneBox = NWrappedWidgetBox(
644
648
  self.tr("Add {0} scene documents (to each chapter)"), self.numScenes
@@ -668,7 +672,7 @@ class _NewProjectForm(QWidget):
668
672
  self.addNotes.setChecked(False)
669
673
 
670
674
  self.notesForm = QFormLayout()
671
- self.notesForm.setAlignment(Qt.AlignmentFlag.AlignLeft)
675
+ self.notesForm.setAlignment(QtAlignLeft)
672
676
  self.notesForm.addRow(self.tr("Add a folder for plot notes"), self.addPlot)
673
677
  self.notesForm.addRow(self.tr("Add a folder for character notes"), self.addChar)
674
678
  self.notesForm.addRow(self.tr("Add a folder for location notes"), self.addWorld)
@@ -678,10 +682,10 @@ class _NewProjectForm(QWidget):
678
682
  # ========
679
683
 
680
684
  self.extraBox = QVBoxLayout()
681
- self.extraBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Chapters and Scenes"))))
685
+ self.extraBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Chapters and Scenes")), self))
682
686
  self.extraBox.addLayout(self.novelForm)
683
687
  self.extraBox.addSpacing(sPx)
684
- self.extraBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Project Notes"))))
688
+ self.extraBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Project Notes")), self))
685
689
  self.extraBox.addLayout(self.notesForm)
686
690
  self.extraBox.setContentsMargins(0, 0, 0, 0)
687
691
 
@@ -690,7 +694,7 @@ class _NewProjectForm(QWidget):
690
694
  self.extraWidget.setContentsMargins(0, 0, 0, 0)
691
695
 
692
696
  self.formBox = QVBoxLayout()
693
- self.formBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Create New Project"))))
697
+ self.formBox.addWidget(QLabel("<b>{0}</b>".format(self.tr("Create New Project")), self))
694
698
  self.formBox.addLayout(self.projectForm)
695
699
  self.formBox.addSpacing(sPx)
696
700
  self.formBox.addWidget(self.extraWidget)
@@ -734,11 +738,10 @@ class _NewProjectForm(QWidget):
734
738
  """Select a project folder."""
735
739
  if projDir := QFileDialog.getExistingDirectory(
736
740
  self, self.tr("Select Project Folder"),
737
- str(self._basePath), options=QFileDialog.ShowDirsOnly
741
+ str(self._basePath), options=QFileDialog.Option.ShowDirsOnly
738
742
  ):
739
743
  self._basePath = Path(projDir)
740
744
  self._updateProjPath()
741
- CONFIG.setLastPath(self._basePath)
742
745
  return
743
746
 
744
747
  @pyqtSlot()
@@ -749,7 +752,7 @@ class _NewProjectForm(QWidget):
749
752
  return
750
753
 
751
754
  @pyqtSlot()
752
- def _syncSwitches(self):
755
+ def _syncSwitches(self) -> None:
753
756
  """Check if the add notes option should also be switched off."""
754
757
  addPlot = self.addPlot.isChecked()
755
758
  addChar = self.addChar.isChecked()
@@ -809,7 +812,7 @@ class _PopLeftDirectionMenu(QMenu):
809
812
 
810
813
  def event(self, event: QEvent) -> bool:
811
814
  """Overload the show event and move the menu popup location."""
812
- if event.type() == QEvent.Show:
815
+ if event.type() == QEvent.Type.Show:
813
816
  if isinstance(parent := self.parent(), QWidget):
814
817
  offset = QPoint(parent.width() - self.width(), parent.height())
815
818
  self.move(parent.mapToGlobal(offset))
@@ -29,18 +29,23 @@ import logging
29
29
  from datetime import datetime
30
30
  from typing import TYPE_CHECKING
31
31
 
32
- from PyQt5.QtGui import QCloseEvent, QPixmap, QCursor
33
32
  from PyQt5.QtCore import Qt, pyqtSlot
33
+ from PyQt5.QtGui import QCloseEvent, QCursor, QPixmap
34
34
  from PyQt5.QtWidgets import (
35
- qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout,
36
- QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout
35
+ QAction, QApplication, QDialog, QDialogButtonBox, QFileDialog, QGridLayout,
36
+ QGroupBox, QHBoxLayout, QLabel, QMenu, QSpinBox, QTreeWidget,
37
+ QTreeWidgetItem
37
38
  )
38
39
 
39
40
  from novelwriter import CONFIG, SHARED
40
- from novelwriter.error import formatException
41
41
  from novelwriter.common import formatTime, checkInt, checkIntTuple, minmax
42
42
  from novelwriter.constants import nwConst
43
+ from novelwriter.error import formatException
43
44
  from novelwriter.extensions.switch import NSwitch
45
+ from novelwriter.types import (
46
+ QtAlignLeftMiddle, QtAlignRight, QtAlignRightMiddle, QtDecoration,
47
+ QtDialogClose, QtRoleAction
48
+ )
44
49
 
45
50
  if TYPE_CHECKING: # pragma: no cover
46
51
  from novelwriter.guimain import GuiMain
@@ -100,7 +105,7 @@ class GuiWritingStats(QDialog):
100
105
  pOptions.getInt("GuiWritingStats", "widthCol3", 80)
101
106
  )
102
107
 
103
- self.listBox = QTreeWidget()
108
+ self.listBox = QTreeWidget(self)
104
109
  self.listBox.setHeaderLabels([
105
110
  self.tr("Session Start"),
106
111
  self.tr("Length"),
@@ -116,14 +121,15 @@ class GuiWritingStats(QDialog):
116
121
 
117
122
  hHeader = self.listBox.headerItem()
118
123
  if hHeader is not None:
119
- hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
120
- hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
121
- hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
124
+ hHeader.setTextAlignment(self.C_LENGTH, QtAlignRight)
125
+ hHeader.setTextAlignment(self.C_IDLE, QtAlignRight)
126
+ hHeader.setTextAlignment(self.C_COUNT, QtAlignRight)
122
127
 
128
+ sDec = Qt.SortOrder.DescendingOrder
129
+ sAsc = Qt.SortOrder.AscendingOrder
123
130
  sortCol = minmax(pOptions.getInt("GuiWritingStats", "sortCol", 0), 0, 2)
124
131
  sortOrder = checkIntTuple(
125
- pOptions.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder),
126
- (Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder
132
+ pOptions.getInt("GuiWritingStats", "sortOrder", sDec), (sAsc, sDec), sDec
127
133
  )
128
134
  self.listBox.sortByColumn(sortCol, sortOrder) # type: ignore
129
135
  self.listBox.setSortingEnabled(True)
@@ -139,36 +145,36 @@ class GuiWritingStats(QDialog):
139
145
  self.infoForm = QGridLayout(self)
140
146
  self.infoBox.setLayout(self.infoForm)
141
147
 
142
- self.labelTotal = QLabel(formatTime(0))
148
+ self.labelTotal = QLabel(formatTime(0), self)
143
149
  self.labelTotal.setFont(SHARED.theme.guiFontFixed)
144
- self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
150
+ self.labelTotal.setAlignment(QtAlignRightMiddle)
145
151
 
146
- self.labelIdleT = QLabel(formatTime(0))
152
+ self.labelIdleT = QLabel(formatTime(0), self)
147
153
  self.labelIdleT.setFont(SHARED.theme.guiFontFixed)
148
- self.labelIdleT.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
154
+ self.labelIdleT.setAlignment(QtAlignRightMiddle)
149
155
 
150
- self.labelFilter = QLabel(formatTime(0))
156
+ self.labelFilter = QLabel(formatTime(0), self)
151
157
  self.labelFilter.setFont(SHARED.theme.guiFontFixed)
152
- self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
158
+ self.labelFilter.setAlignment(QtAlignRightMiddle)
153
159
 
154
- self.novelWords = QLabel("0")
160
+ self.novelWords = QLabel("0", self)
155
161
  self.novelWords.setFont(SHARED.theme.guiFontFixed)
156
- self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
162
+ self.novelWords.setAlignment(QtAlignRightMiddle)
157
163
 
158
- self.notesWords = QLabel("0")
164
+ self.notesWords = QLabel("0", self)
159
165
  self.notesWords.setFont(SHARED.theme.guiFontFixed)
160
- self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
166
+ self.notesWords.setAlignment(QtAlignRightMiddle)
161
167
 
162
- self.totalWords = QLabel("0")
168
+ self.totalWords = QLabel("0", self)
163
169
  self.totalWords.setFont(SHARED.theme.guiFontFixed)
164
- self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
170
+ self.totalWords.setAlignment(QtAlignRightMiddle)
165
171
 
166
- lblTTime = QLabel(self.tr("Total Time:"))
167
- lblITime = QLabel(self.tr("Idle Time:"))
168
- lblFTime = QLabel(self.tr("Filtered Time:"))
169
- lblNvCount = QLabel(self.tr("Novel Word Count:"))
170
- lblNtCount = QLabel(self.tr("Notes Word Count:"))
171
- lblTtCount = QLabel(self.tr("Total Word Count:"))
172
+ lblTTime = QLabel(self.tr("Total Time:"), self)
173
+ lblITime = QLabel(self.tr("Idle Time:"), self)
174
+ lblFTime = QLabel(self.tr("Filtered Time:"), self)
175
+ lblNvCount = QLabel(self.tr("Novel Word Count:"), self)
176
+ lblNtCount = QLabel(self.tr("Notes Word Count:"), self)
177
+ lblTtCount = QLabel(self.tr("Total Word Count:"), self)
172
178
 
173
179
  self.infoForm.addWidget(lblTTime, 0, 0)
174
180
  self.infoForm.addWidget(lblITime, 1, 0)
@@ -187,54 +193,54 @@ class GuiWritingStats(QDialog):
187
193
  self.infoForm.setRowStretch(6, 1)
188
194
 
189
195
  # Filter Options
190
- sPx = SHARED.theme.baseIconSize
196
+ iPx = SHARED.theme.baseIconHeight
191
197
 
192
198
  self.filterBox = QGroupBox(self.tr("Filters"), self)
193
199
  self.filterForm = QGridLayout(self)
194
200
  self.filterBox.setLayout(self.filterForm)
195
201
 
196
- self.incNovel = NSwitch(self, height=sPx)
202
+ self.incNovel = NSwitch(self, height=iPx)
197
203
  self.incNovel.setChecked(
198
204
  pOptions.getBool("GuiWritingStats", "incNovel", True)
199
205
  )
200
206
  self.incNovel.clicked.connect(self._updateListBox)
201
207
 
202
- self.incNotes = NSwitch(self, height=sPx)
208
+ self.incNotes = NSwitch(self, height=iPx)
203
209
  self.incNotes.setChecked(
204
210
  pOptions.getBool("GuiWritingStats", "incNotes", True)
205
211
  )
206
212
  self.incNotes.clicked.connect(self._updateListBox)
207
213
 
208
- self.hideZeros = NSwitch(self, height=sPx)
214
+ self.hideZeros = NSwitch(self, height=iPx)
209
215
  self.hideZeros.setChecked(
210
216
  pOptions.getBool("GuiWritingStats", "hideZeros", True)
211
217
  )
212
218
  self.hideZeros.clicked.connect(self._updateListBox)
213
219
 
214
- self.hideNegative = NSwitch(self, height=sPx)
220
+ self.hideNegative = NSwitch(self, height=iPx)
215
221
  self.hideNegative.setChecked(
216
222
  pOptions.getBool("GuiWritingStats", "hideNegative", False)
217
223
  )
218
224
  self.hideNegative.clicked.connect(self._updateListBox)
219
225
 
220
- self.groupByDay = NSwitch(self, height=sPx)
226
+ self.groupByDay = NSwitch(self, height=iPx)
221
227
  self.groupByDay.setChecked(
222
228
  pOptions.getBool("GuiWritingStats", "groupByDay", False)
223
229
  )
224
230
  self.groupByDay.clicked.connect(self._updateListBox)
225
231
 
226
- self.showIdleTime = NSwitch(self, height=sPx)
232
+ self.showIdleTime = NSwitch(self, height=iPx)
227
233
  self.showIdleTime.setChecked(
228
234
  pOptions.getBool("GuiWritingStats", "showIdleTime", False)
229
235
  )
230
236
  self.showIdleTime.clicked.connect(self._updateListBox)
231
237
 
232
- self.filterForm.addWidget(QLabel(self.tr("Count novel files")), 0, 0)
233
- self.filterForm.addWidget(QLabel(self.tr("Count note files")), 1, 0)
234
- self.filterForm.addWidget(QLabel(self.tr("Hide zero word count")), 2, 0)
235
- self.filterForm.addWidget(QLabel(self.tr("Hide negative word count")), 3, 0)
236
- self.filterForm.addWidget(QLabel(self.tr("Group entries by day")), 4, 0)
237
- self.filterForm.addWidget(QLabel(self.tr("Show idle time")), 5, 0)
238
+ self.filterForm.addWidget(QLabel(self.tr("Count novel files"), self), 0, 0)
239
+ self.filterForm.addWidget(QLabel(self.tr("Count note files"), self), 1, 0)
240
+ self.filterForm.addWidget(QLabel(self.tr("Hide zero word count"), self), 2, 0)
241
+ self.filterForm.addWidget(QLabel(self.tr("Hide negative word count"), self), 3, 0)
242
+ self.filterForm.addWidget(QLabel(self.tr("Group entries by day"), self), 4, 0)
243
+ self.filterForm.addWidget(QLabel(self.tr("Show idle time"), self), 5, 0)
238
244
  self.filterForm.addWidget(self.incNovel, 0, 1)
239
245
  self.filterForm.addWidget(self.incNotes, 1, 1)
240
246
  self.filterForm.addWidget(self.hideZeros, 2, 1)
@@ -255,17 +261,17 @@ class GuiWritingStats(QDialog):
255
261
 
256
262
  self.optsBox = QHBoxLayout()
257
263
  self.optsBox.addStretch(1)
258
- self.optsBox.addWidget(QLabel(self.tr("Word count cap for the histogram")), 0)
264
+ self.optsBox.addWidget(QLabel(self.tr("Word count cap for the histogram"), self), 0)
259
265
  self.optsBox.addWidget(self.histMax, 0)
260
266
 
261
267
  # Buttons
262
- self.buttonBox = QDialogButtonBox()
268
+ self.buttonBox = QDialogButtonBox(self)
263
269
  self.buttonBox.rejected.connect(self._doClose)
264
270
 
265
- self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
271
+ self.btnClose = self.buttonBox.addButton(QtDialogClose)
266
272
  self.btnClose.setAutoDefault(False)
267
273
 
268
- self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QDialogButtonBox.ActionRole)
274
+ self.btnSave = self.buttonBox.addButton(self.tr("Save As"), QtRoleAction)
269
275
  self.btnSave.setAutoDefault(False)
270
276
 
271
277
  self.saveMenu = QMenu(self)
@@ -300,10 +306,10 @@ class GuiWritingStats(QDialog):
300
306
 
301
307
  def populateGUI(self) -> None:
302
308
  """Populate list box with data from the log file."""
303
- qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
309
+ QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
304
310
  self._loadLogFile()
305
311
  self._updateListBox()
306
- qApp.restoreOverrideCursor()
312
+ QApplication.restoreOverrideCursor()
307
313
  return
308
314
 
309
315
  ##
@@ -569,6 +575,8 @@ class GuiWritingStats(QDialog):
569
575
  pcTotal = wcTotal
570
576
 
571
577
  # Populate the list
578
+ mTrans = Qt.TransformationMode.FastTransformation
579
+ mAspect = Qt.AspectRatioMode.IgnoreAspectRatio
572
580
  showIdleTime = self.showIdleTime.isChecked()
573
581
  for _, sStart, sDiff, nWords, _, _, sIdle in self.filterData:
574
582
 
@@ -587,16 +595,14 @@ class GuiWritingStats(QDialog):
587
595
  if nWords > 0 and listMax > 0:
588
596
  wBar = self.barImage.scaled(
589
597
  int(200*min(nWords, histMax)/listMax),
590
- self.barHeight,
591
- Qt.IgnoreAspectRatio,
592
- Qt.FastTransformation
598
+ self.barHeight, mAspect, mTrans
593
599
  )
594
- newItem.setData(self.C_BAR, Qt.DecorationRole, wBar)
600
+ newItem.setData(self.C_BAR, QtDecoration, wBar)
595
601
 
596
- newItem.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
597
- newItem.setTextAlignment(self.C_IDLE, Qt.AlignRight)
598
- newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
599
- newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter)
602
+ newItem.setTextAlignment(self.C_LENGTH, QtAlignRight)
603
+ newItem.setTextAlignment(self.C_IDLE, QtAlignRight)
604
+ newItem.setTextAlignment(self.C_COUNT, QtAlignRight)
605
+ newItem.setTextAlignment(self.C_BAR, QtAlignLeftMiddle)
600
606
 
601
607
  newItem.setFont(self.C_TIME, SHARED.theme.guiFontFixed)
602
608
  newItem.setFont(self.C_LENGTH, SHARED.theme.guiFontFixed)