novelWriter 2.4b1__py3-none-any.whl → 2.4.1__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.
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/METADATA +5 -6
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/RECORD +79 -83
- novelwriter/__init__.py +15 -8
- novelwriter/assets/i18n/nw_de_DE.qm +0 -0
- novelwriter/assets/i18n/nw_en_US.qm +0 -0
- novelwriter/assets/i18n/nw_es_419.qm +0 -0
- novelwriter/assets/i18n/nw_fr_FR.qm +0 -0
- novelwriter/assets/i18n/nw_it_IT.qm +0 -0
- novelwriter/assets/i18n/nw_ja_JP.qm +0 -0
- novelwriter/assets/i18n/nw_nb_NO.qm +0 -0
- novelwriter/assets/i18n/nw_nl_NL.qm +0 -0
- novelwriter/assets/i18n/nw_pt_BR.qm +0 -0
- novelwriter/assets/i18n/nw_zh_CN.qm +0 -0
- novelwriter/assets/icons/none.svg +4 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +2 -2
- novelwriter/assets/icons/typicons_dark/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_unfold-visible.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +2 -2
- novelwriter/assets/icons/typicons_light/typ_unfold-hidden.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_unfold-visible.svg +4 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/common.py +11 -3
- novelwriter/config.py +12 -4
- novelwriter/core/buildsettings.py +7 -7
- novelwriter/core/coretools.py +21 -22
- novelwriter/core/docbuild.py +2 -2
- novelwriter/core/projectxml.py +1 -1
- novelwriter/core/spellcheck.py +3 -3
- novelwriter/core/status.py +3 -2
- novelwriter/core/tokenizer.py +3 -3
- novelwriter/core/toodt.py +333 -356
- novelwriter/dialogs/about.py +9 -11
- novelwriter/dialogs/docmerge.py +17 -14
- novelwriter/dialogs/docsplit.py +14 -12
- novelwriter/dialogs/editlabel.py +5 -4
- novelwriter/dialogs/preferences.py +29 -34
- novelwriter/dialogs/projectsettings.py +31 -28
- novelwriter/dialogs/quotes.py +10 -9
- novelwriter/dialogs/wordlist.py +17 -14
- novelwriter/error.py +14 -12
- novelwriter/extensions/circularprogress.py +12 -8
- novelwriter/extensions/configlayout.py +1 -3
- novelwriter/extensions/modified.py +33 -2
- novelwriter/extensions/pagedsidebar.py +16 -14
- novelwriter/extensions/simpleprogress.py +3 -1
- novelwriter/extensions/statusled.py +3 -1
- novelwriter/extensions/switch.py +10 -9
- novelwriter/extensions/switchbox.py +14 -13
- novelwriter/gui/doceditor.py +205 -246
- novelwriter/gui/dochighlight.py +26 -9
- novelwriter/gui/docviewer.py +55 -59
- novelwriter/gui/docviewerpanel.py +16 -13
- novelwriter/gui/editordocument.py +4 -4
- novelwriter/gui/itemdetails.py +45 -48
- novelwriter/gui/mainmenu.py +2 -2
- novelwriter/gui/noveltree.py +23 -21
- novelwriter/gui/outline.py +93 -94
- novelwriter/gui/projtree.py +32 -30
- novelwriter/gui/search.py +75 -29
- novelwriter/gui/sidebar.py +24 -28
- novelwriter/gui/statusbar.py +14 -14
- novelwriter/gui/theme.py +61 -39
- novelwriter/guimain.py +37 -33
- novelwriter/shared.py +21 -9
- novelwriter/text/counting.py +1 -0
- novelwriter/tools/dictionaries.py +15 -14
- novelwriter/tools/lipsum.py +20 -17
- novelwriter/tools/manusbuild.py +44 -35
- novelwriter/tools/manuscript.py +112 -112
- novelwriter/tools/manussettings.py +91 -98
- novelwriter/tools/noveldetails.py +20 -18
- novelwriter/tools/welcome.py +51 -48
- novelwriter/tools/writingstats.py +61 -55
- novelwriter/types.py +90 -0
- novelwriter/assets/icons/typicons_dark/typ_arrow-down.svg +0 -4
- novelwriter/assets/icons/typicons_dark/typ_arrow-right.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-down.svg +0 -4
- novelwriter/assets/icons/typicons_light/typ_arrow-right.svg +0 -4
- novelwriter/core/__init__.py +0 -3
- novelwriter/dialogs/__init__.py +0 -3
- novelwriter/extensions/__init__.py +0 -3
- novelwriter/gui/__init__.py +0 -3
- novelwriter/text/__init__.py +0 -3
- novelwriter/tools/__init__.py +0 -3
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/WHEEL +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.4b1.dist-info → novelWriter-2.4.1.dist-info}/top_level.txt +0 -0
novelwriter/tools/welcome.py
CHANGED
@@ -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,
|
38
|
-
QListView, QMenu, QPushButton, QScrollArea, QShortcut,
|
39
|
-
|
40
|
-
|
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.
|
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 =
|
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
|
-
|
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,
|
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
|
-
|
310
|
-
self.
|
311
|
-
|
312
|
-
"
|
313
|
-
|
314
|
-
|
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.
|
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 =
|
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 =
|
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 &
|
434
|
+
if opt.state & QtSelected == QtSelected:
|
429
435
|
painter.setOpacity(0.25)
|
430
|
-
painter.fillRect(rect,
|
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)
|
@@ -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:
|
524
|
-
"_NewProjectForm {{border: none; background:
|
525
|
-
)
|
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.
|
563
|
+
self._basePath = CONFIG.homePath()
|
558
564
|
self._fillMode = self.FILL_BLANK
|
559
565
|
self._copyPath = None
|
560
566
|
|
561
|
-
iPx = SHARED.theme.
|
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 =
|
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 =
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
36
|
-
|
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,
|
120
|
-
hHeader.setTextAlignment(self.C_IDLE,
|
121
|
-
hHeader.setTextAlignment(self.C_COUNT,
|
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",
|
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(
|
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(
|
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(
|
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(
|
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(
|
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(
|
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
|
-
|
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=
|
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=
|
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=
|
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=
|
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=
|
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=
|
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(
|
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"),
|
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
|
-
|
309
|
+
QApplication.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
304
310
|
self._loadLogFile()
|
305
311
|
self._updateListBox()
|
306
|
-
|
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,
|
600
|
+
newItem.setData(self.C_BAR, QtDecoration, wBar)
|
595
601
|
|
596
|
-
newItem.setTextAlignment(self.C_LENGTH,
|
597
|
-
newItem.setTextAlignment(self.C_IDLE,
|
598
|
-
newItem.setTextAlignment(self.C_COUNT,
|
599
|
-
newItem.setTextAlignment(self.C_BAR,
|
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)
|
novelwriter/types.py
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
"""
|
2
|
+
novelWriter – Types and Flags
|
3
|
+
=============================
|
4
|
+
|
5
|
+
File History:
|
6
|
+
Created: 2024-04-01 [2.4rc1]
|
7
|
+
|
8
|
+
This file is a part of novelWriter
|
9
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
10
|
+
|
11
|
+
This program is free software: you can redistribute it and/or modify
|
12
|
+
it under the terms of the GNU General Public License as published by
|
13
|
+
the Free Software Foundation, either version 3 of the License, or
|
14
|
+
(at your option) any later version.
|
15
|
+
|
16
|
+
This program is distributed in the hope that it will be useful, but
|
17
|
+
WITHOUT ANY WARRANTY; without even the implied warranty of
|
18
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
19
|
+
General Public License for more details.
|
20
|
+
|
21
|
+
You should have received a copy of the GNU General Public License
|
22
|
+
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
23
|
+
"""
|
24
|
+
from __future__ import annotations
|
25
|
+
|
26
|
+
from PyQt5.QtCore import Qt
|
27
|
+
from PyQt5.QtGui import QColor, QPainter, QTextCursor
|
28
|
+
from PyQt5.QtWidgets import QDialogButtonBox, QStyle
|
29
|
+
|
30
|
+
# Qt Alignment Flags
|
31
|
+
|
32
|
+
QtAlignAbsolute = Qt.AlignmentFlag.AlignAbsolute
|
33
|
+
QtAlignCenter = Qt.AlignmentFlag.AlignCenter
|
34
|
+
QtAlignCenterTop = Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop
|
35
|
+
QtAlignJustify = Qt.AlignmentFlag.AlignJustify
|
36
|
+
QtAlignLeft = Qt.AlignmentFlag.AlignLeft
|
37
|
+
QtAlignLeftBase = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignBaseline
|
38
|
+
QtAlignLeftMiddle = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter
|
39
|
+
QtAlignLeftTop = Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop
|
40
|
+
QtAlignMiddle = Qt.AlignmentFlag.AlignVCenter
|
41
|
+
QtAlignRight = Qt.AlignmentFlag.AlignRight
|
42
|
+
QtAlignRightBase = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignBaseline
|
43
|
+
QtAlignRightMiddle = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter
|
44
|
+
QtAlignRightTop = Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop
|
45
|
+
QtAlignTop = Qt.AlignmentFlag.AlignTop
|
46
|
+
|
47
|
+
# Qt Painter Types
|
48
|
+
|
49
|
+
QtTransparent = QColor(0, 0, 0, 0)
|
50
|
+
QtNoBrush = Qt.BrushStyle.NoBrush
|
51
|
+
QtNoPen = Qt.PenStyle.NoPen
|
52
|
+
QtRoundCap = Qt.PenCapStyle.RoundCap
|
53
|
+
QtSolidLine = Qt.PenStyle.SolidLine
|
54
|
+
QtPaintAnitAlias = QPainter.RenderHint.Antialiasing
|
55
|
+
QtMouseOver = QStyle.StateFlag.State_MouseOver
|
56
|
+
QtSelected = QStyle.StateFlag.State_Selected
|
57
|
+
|
58
|
+
# Qt Tree and Table Types
|
59
|
+
|
60
|
+
QtDecoration = Qt.ItemDataRole.DecorationRole
|
61
|
+
QtUserRole = Qt.ItemDataRole.UserRole
|
62
|
+
|
63
|
+
# Keyboard and Mouse Buttons
|
64
|
+
|
65
|
+
QtModCtrl = Qt.KeyboardModifier.ControlModifier
|
66
|
+
QtModeNone = Qt.KeyboardModifier.NoModifier
|
67
|
+
QtModShift = Qt.KeyboardModifier.ShiftModifier
|
68
|
+
QtMouseLeft = Qt.MouseButton.LeftButton
|
69
|
+
QtMouseMiddle = Qt.MouseButton.MiddleButton
|
70
|
+
|
71
|
+
# Dialog Button Box Types
|
72
|
+
|
73
|
+
QtDialogApply = QDialogButtonBox.StandardButton.Apply
|
74
|
+
QtDialogCancel = QDialogButtonBox.StandardButton.Cancel
|
75
|
+
QtDialogClose = QDialogButtonBox.StandardButton.Close
|
76
|
+
QtDialogOk = QDialogButtonBox.StandardButton.Ok
|
77
|
+
QtDialogReset = QDialogButtonBox.StandardButton.Reset
|
78
|
+
QtDialogSave = QDialogButtonBox.StandardButton.Save
|
79
|
+
|
80
|
+
QtRoleAccept = QDialogButtonBox.ButtonRole.AcceptRole
|
81
|
+
QtRoleAction = QDialogButtonBox.ButtonRole.ActionRole
|
82
|
+
QtRoleApply = QDialogButtonBox.ButtonRole.ApplyRole
|
83
|
+
QtRoleReject = QDialogButtonBox.ButtonRole.RejectRole
|
84
|
+
|
85
|
+
# Cursor Types
|
86
|
+
|
87
|
+
QtKeepAnchor = QTextCursor.MoveMode.KeepAnchor
|
88
|
+
QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor
|
89
|
+
QtMoveLeft = QTextCursor.MoveOperation.Left
|
90
|
+
QtMoveRight = QTextCursor.MoveOperation.Right
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="m1.5077 8.1077 10.492 10.662 10.492-10.662c0.33846-0.33846 0.50769-0.84615 0.50769-1.1846 0-0.33846-0.16923-0.84615-0.50769-1.1846-0.33846-0.33846-0.67692-0.50769-1.1846-0.50769h-18.615c-0.50769 0-0.84615 0.16923-1.1846 0.50769-0.33846 0.33846-0.50769 0.84615-0.50769 1.1846 0 0.33846 0.16923 0.84615 0.50769 1.1846z" fill="#aeaeae" stroke-width="1.6923"/>
|
4
|
-
</svg>
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="m8.1077 22.492 10.662-10.492-10.662-10.492c-0.33846-0.33846-0.84615-0.50769-1.1846-0.50769-0.33846 0-0.84615 0.16923-1.1846 0.50769-0.33846 0.33846-0.50769 0.67692-0.50769 1.1846v18.615c0 0.50769 0.16923 0.84615 0.50769 1.1846 0.33846 0.33846 0.84615 0.50769 1.1846 0.50769 0.33846 0 0.84615-0.16923 1.1846-0.50769z" fill="#aeaeae" stroke-width="1.6923"/>
|
4
|
-
</svg>
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="m1.5077 8.1077 10.492 10.662 10.492-10.662c0.33846-0.33846 0.50769-0.84615 0.50769-1.1846 0-0.33846-0.16923-0.84615-0.50769-1.1846-0.33846-0.33846-0.67692-0.50769-1.1846-0.50769h-18.615c-0.50769 0-0.84615 0.16923-1.1846 0.50769-0.33846 0.33846-0.50769 0.84615-0.50769 1.1846 0 0.33846 0.16923 0.84615 0.50769 1.1846z" fill-opacity=".78039" stroke-width="1.6923"/>
|
4
|
-
</svg>
|
@@ -1,4 +0,0 @@
|
|
1
|
-
<?xml version="1.0" encoding="UTF-8"?>
|
2
|
-
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
3
|
-
<path d="m8.1077 22.492 10.662-10.492-10.662-10.492c-0.33846-0.33846-0.84615-0.50769-1.1846-0.50769-0.33846 0-0.84615 0.16923-1.1846 0.50769-0.33846 0.33846-0.50769 0.67692-0.50769 1.1846v18.615c0 0.50769 0.16923 0.84615 0.50769 1.1846 0.33846 0.33846 0.84615 0.50769 1.1846 0.50769 0.33846 0 0.84615-0.16923 1.1846-0.50769z" fill-opacity=".78039" stroke-width="1.6923"/>
|
4
|
-
</svg>
|
novelwriter/core/__init__.py
DELETED