novelWriter 2.2rc1__py3-none-any.whl → 2.3b1__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.2rc1.dist-info → novelWriter-2.3b1.dist-info}/METADATA +1 -1
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/RECORD +141 -129
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/WHEEL +1 -1
- novelwriter/__init__.py +11 -6
- 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_zh_CN.qm +0 -0
- novelwriter/assets/i18n/project_de_DE.json +1 -0
- novelwriter/assets/i18n/project_en_US.json +1 -0
- novelwriter/assets/i18n/project_es_419.json +11 -0
- novelwriter/assets/i18n/project_fr_FR.json +11 -0
- novelwriter/assets/i18n/project_it_IT.json +11 -0
- novelwriter/assets/i18n/project_ja_JP.json +2 -1
- novelwriter/assets/i18n/project_nb_NO.json +1 -0
- novelwriter/assets/i18n/project_zh_CN.json +11 -0
- novelwriter/assets/icons/typicons_dark/icons.conf +9 -2
- novelwriter/assets/icons/typicons_dark/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_dark/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_dark/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_dark/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_dark/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_dark/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_dark/typ_th-dot-more.svg +4 -0
- novelwriter/assets/icons/typicons_light/icons.conf +9 -2
- novelwriter/assets/icons/typicons_light/mixed_import.svg +5 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-bold.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-italic-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-italic.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-strike-md.svg +4 -0
- novelwriter/assets/icons/typicons_light/nw_tb-strike.svg +3 -1
- novelwriter/assets/icons/typicons_light/nw_tb-subscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-superscript.svg +4 -2
- novelwriter/assets/icons/typicons_light/nw_tb-underline.svg +4 -2
- novelwriter/assets/icons/typicons_light/typ_document-add-col.svg +8 -0
- novelwriter/assets/icons/typicons_light/typ_document-add.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_document.svg +4 -0
- novelwriter/assets/icons/typicons_light/typ_th-dot-more.svg +4 -0
- novelwriter/assets/images/novelwriter-text-dark.svg +4 -0
- novelwriter/assets/images/novelwriter-text-light.svg +4 -0
- novelwriter/assets/images/welcome-dark.jpg +0 -0
- novelwriter/assets/images/welcome-light.jpg +0 -0
- novelwriter/assets/manual.pdf +0 -0
- novelwriter/assets/sample.zip +0 -0
- novelwriter/assets/syntax/default_dark.conf +1 -0
- novelwriter/assets/syntax/default_light.conf +1 -0
- novelwriter/assets/syntax/grey_dark.conf +1 -0
- novelwriter/assets/syntax/grey_light.conf +1 -0
- novelwriter/assets/syntax/light_owl.conf +1 -0
- novelwriter/assets/syntax/night_owl.conf +1 -0
- novelwriter/assets/syntax/solarized_dark.conf +1 -0
- novelwriter/assets/syntax/solarized_light.conf +1 -0
- novelwriter/assets/syntax/tomorrow.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_blue.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_bright.conf +1 -0
- novelwriter/assets/syntax/tomorrow_night_eighties.conf +1 -0
- novelwriter/assets/text/credits_en.htm +4 -2
- novelwriter/assets/themes/default_dark.conf +2 -2
- novelwriter/assets/themes/default_light.conf +2 -2
- novelwriter/common.py +64 -66
- novelwriter/config.py +39 -44
- novelwriter/constants.py +39 -17
- novelwriter/core/buildsettings.py +8 -8
- novelwriter/core/coretools.py +194 -155
- novelwriter/core/docbuild.py +7 -4
- novelwriter/core/document.py +7 -7
- novelwriter/core/index.py +90 -57
- novelwriter/core/item.py +23 -5
- novelwriter/core/options.py +11 -10
- novelwriter/core/project.py +72 -47
- novelwriter/core/projectdata.py +3 -16
- novelwriter/core/projectxml.py +14 -42
- novelwriter/core/sessions.py +4 -3
- novelwriter/core/spellcheck.py +6 -4
- novelwriter/core/status.py +5 -4
- novelwriter/core/storage.py +179 -141
- novelwriter/core/tohtml.py +6 -4
- novelwriter/core/tokenizer.py +74 -46
- novelwriter/core/tomd.py +2 -2
- novelwriter/core/toodt.py +41 -31
- novelwriter/core/tree.py +5 -4
- novelwriter/dialogs/about.py +88 -179
- novelwriter/dialogs/docmerge.py +30 -20
- novelwriter/dialogs/docsplit.py +33 -22
- novelwriter/dialogs/editlabel.py +20 -8
- novelwriter/dialogs/preferences.py +562 -725
- novelwriter/dialogs/{projsettings.py → projectsettings.py} +301 -270
- novelwriter/dialogs/quotes.py +47 -36
- novelwriter/dialogs/wordlist.py +128 -59
- novelwriter/enum.py +25 -22
- novelwriter/error.py +2 -2
- novelwriter/extensions/circularprogress.py +12 -12
- novelwriter/extensions/configlayout.py +185 -146
- novelwriter/extensions/{wheeleventfilter.py → eventfilters.py} +15 -5
- novelwriter/extensions/modified.py +81 -0
- novelwriter/extensions/novelselector.py +27 -13
- novelwriter/extensions/pagedsidebar.py +15 -20
- novelwriter/extensions/simpleprogress.py +8 -9
- novelwriter/extensions/statusled.py +9 -9
- novelwriter/extensions/switch.py +32 -64
- novelwriter/extensions/switchbox.py +2 -7
- novelwriter/extensions/versioninfo.py +153 -0
- novelwriter/gui/doceditor.py +250 -214
- novelwriter/gui/dochighlight.py +66 -94
- novelwriter/gui/docviewer.py +71 -98
- novelwriter/gui/docviewerpanel.py +140 -47
- novelwriter/gui/editordocument.py +3 -3
- novelwriter/gui/itemdetails.py +9 -9
- novelwriter/gui/mainmenu.py +47 -46
- novelwriter/gui/noveltree.py +53 -61
- novelwriter/gui/outline.py +100 -76
- novelwriter/gui/projtree.py +193 -67
- novelwriter/gui/sidebar.py +9 -8
- novelwriter/gui/statusbar.py +49 -7
- novelwriter/gui/theme.py +65 -74
- novelwriter/guimain.py +173 -330
- novelwriter/shared.py +68 -30
- novelwriter/tools/dictionaries.py +7 -8
- novelwriter/tools/lipsum.py +34 -28
- novelwriter/tools/manusbuild.py +3 -4
- novelwriter/tools/manuscript.py +25 -32
- novelwriter/tools/manussettings.py +194 -225
- novelwriter/tools/noveldetails.py +525 -0
- novelwriter/tools/welcome.py +802 -0
- novelwriter/tools/writingstats.py +26 -13
- novelwriter/assets/icons/typicons_dark/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_dark/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-markdown.svg +0 -8
- novelwriter/assets/icons/typicons_light/nw_tb-shortcode.svg +0 -8
- novelwriter/assets/images/wizard-back.jpg +0 -0
- novelwriter/assets/text/gplv3_en.htm +0 -641
- novelwriter/assets/text/release_notes.htm +0 -17
- novelwriter/dialogs/projdetails.py +0 -525
- novelwriter/dialogs/projload.py +0 -298
- novelwriter/dialogs/updates.py +0 -182
- novelwriter/extensions/pageddialog.py +0 -130
- novelwriter/tools/projwizard.py +0 -478
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/LICENSE.md +0 -0
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/entry_points.txt +0 -0
- {novelWriter-2.2rc1.dist-info → novelWriter-2.3b1.dist-info}/top_level.txt +0 -0
novelwriter/gui/projtree.py
CHANGED
@@ -6,9 +6,10 @@ File History:
|
|
6
6
|
Created: 2018-09-29 [0.0.1] GuiProjectTree
|
7
7
|
Created: 2022-06-06 [2.0rc1] GuiProjectView
|
8
8
|
Created: 2022-06-06 [2.0rc1] GuiProjectToolBar
|
9
|
+
Created: 2023-11-22 [2.2rc1] _TreeContextMenu
|
9
10
|
|
10
11
|
This file is a part of novelWriter
|
11
|
-
Copyright 2018–
|
12
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
12
13
|
|
13
14
|
This program is free software: you can redistribute it and/or modify
|
14
15
|
it under the terms of the GNU General Public License as published by
|
@@ -31,15 +32,18 @@ from enum import Enum
|
|
31
32
|
from time import time
|
32
33
|
from typing import TYPE_CHECKING
|
33
34
|
|
34
|
-
from PyQt5.QtGui import
|
35
|
+
from PyQt5.QtGui import (
|
36
|
+
QDragEnterEvent, QDragMoveEvent, QDropEvent, QIcon, QMouseEvent, QPalette
|
37
|
+
)
|
35
38
|
from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
|
36
39
|
from PyQt5.QtWidgets import (
|
37
|
-
QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
38
|
-
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget,
|
39
|
-
QVBoxLayout, QWidget
|
40
|
+
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
41
|
+
QLabel, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget,
|
42
|
+
QTreeWidgetItem, QVBoxLayout, QWidget
|
40
43
|
)
|
41
44
|
|
42
45
|
from novelwriter import CONFIG, SHARED
|
46
|
+
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
43
47
|
from novelwriter.common import minmax
|
44
48
|
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
45
49
|
from novelwriter.core.item import NWItem
|
@@ -47,10 +51,7 @@ from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
|
47
51
|
from novelwriter.dialogs.docmerge import GuiDocMerge
|
48
52
|
from novelwriter.dialogs.docsplit import GuiDocSplit
|
49
53
|
from novelwriter.dialogs.editlabel import GuiEditLabel
|
50
|
-
from novelwriter.dialogs.
|
51
|
-
from novelwriter.enum import (
|
52
|
-
nwDocMode, nwItemType, nwItemClass, nwItemLayout, nwWidget
|
53
|
-
)
|
54
|
+
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
54
55
|
|
55
56
|
if TYPE_CHECKING: # pragma: no cover
|
56
57
|
from novelwriter.guimain import GuiMain
|
@@ -132,7 +133,9 @@ class GuiProjectView(QWidget):
|
|
132
133
|
self.keyContext.activated.connect(lambda: self.projTree.openContextOnSelected())
|
133
134
|
|
134
135
|
# Signals
|
135
|
-
self.selectedItemChanged.connect(self.projBar.
|
136
|
+
self.selectedItemChanged.connect(self.projBar.treeSelectionChanged)
|
137
|
+
self.projTree.itemRefreshed.connect(self.projBar.treeItemRefreshed)
|
138
|
+
self.projBar.newDocumentFromTemplate.connect(self.createFileFromTemplate)
|
136
139
|
|
137
140
|
# Function Mappings
|
138
141
|
self.emptyTrash = self.projTree.emptyTrash
|
@@ -158,7 +161,7 @@ class GuiProjectView(QWidget):
|
|
158
161
|
self.projTree.initSettings()
|
159
162
|
return
|
160
163
|
|
161
|
-
def
|
164
|
+
def closeProjectTasks(self) -> None:
|
162
165
|
"""Clear project-related GUI content."""
|
163
166
|
self.projBar.clearContent()
|
164
167
|
self.projBar.setEnabled(False)
|
@@ -167,7 +170,7 @@ class GuiProjectView(QWidget):
|
|
167
170
|
|
168
171
|
def openProjectTasks(self) -> None:
|
169
172
|
"""Run open project tasks."""
|
170
|
-
self.projBar.
|
173
|
+
self.projBar.buildQuickLinksMenu()
|
171
174
|
self.projBar.setEnabled(True)
|
172
175
|
return
|
173
176
|
|
@@ -213,10 +216,17 @@ class GuiProjectView(QWidget):
|
|
213
216
|
|
214
217
|
@pyqtSlot(str)
|
215
218
|
def updateItemValues(self, tHandle: str) -> None:
|
216
|
-
"""Update tree item"""
|
219
|
+
"""Update tree item."""
|
217
220
|
self.projTree.setTreeItemValues(tHandle)
|
218
221
|
return
|
219
222
|
|
223
|
+
@pyqtSlot(str)
|
224
|
+
def createFileFromTemplate(self, tHandle: str) -> None:
|
225
|
+
"""Create a new document from a template."""
|
226
|
+
logger.debug("Template selected: '%s'", tHandle)
|
227
|
+
self.projTree.newTreeItem(nwItemType.FILE, copyDoc=tHandle)
|
228
|
+
return
|
229
|
+
|
220
230
|
@pyqtSlot(str, int, int, int)
|
221
231
|
def updateCounts(self, tHandle: str, cCount: int, wCount: int, pCount: int) -> None:
|
222
232
|
"""Slot for updating the word count of a specific item."""
|
@@ -226,8 +236,8 @@ class GuiProjectView(QWidget):
|
|
226
236
|
|
227
237
|
@pyqtSlot(str)
|
228
238
|
def updateRootItem(self, tHandle: str) -> None:
|
229
|
-
"""
|
230
|
-
self.projBar.
|
239
|
+
"""Process root item changes."""
|
240
|
+
self.projBar.buildQuickLinksMenu()
|
231
241
|
return
|
232
242
|
|
233
243
|
# END Class GuiProjectView
|
@@ -235,6 +245,8 @@ class GuiProjectView(QWidget):
|
|
235
245
|
|
236
246
|
class GuiProjectToolBar(QWidget):
|
237
247
|
|
248
|
+
newDocumentFromTemplate = pyqtSignal(str)
|
249
|
+
|
238
250
|
def __init__(self, projView: GuiProjectView) -> None:
|
239
251
|
super().__init__(parent=projView)
|
240
252
|
|
@@ -304,6 +316,11 @@ class GuiProjectToolBar(QWidget):
|
|
304
316
|
lambda: self.projTree.newTreeItem(nwItemType.FOLDER)
|
305
317
|
)
|
306
318
|
|
319
|
+
self.mTemplates = _UpdatableMenu(self.mAdd)
|
320
|
+
self.mTemplates.setActionsVisible(False)
|
321
|
+
self.mTemplates.menuItemTriggered.connect(lambda h: self.newDocumentFromTemplate.emit(h))
|
322
|
+
self.mAdd.addMenu(self.mTemplates)
|
323
|
+
|
307
324
|
self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
|
308
325
|
self._buildRootMenu()
|
309
326
|
|
@@ -384,7 +401,7 @@ class GuiProjectToolBar(QWidget):
|
|
384
401
|
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
385
402
|
self.tbMore.setIcon(SHARED.theme.getIcon("menu"))
|
386
403
|
|
387
|
-
self.
|
404
|
+
self.buildQuickLinksMenu()
|
388
405
|
self._buildRootMenu()
|
389
406
|
|
390
407
|
return
|
@@ -392,21 +409,48 @@ class GuiProjectToolBar(QWidget):
|
|
392
409
|
def clearContent(self) -> None:
|
393
410
|
"""Clear dynamic content on the tool bar."""
|
394
411
|
self.mQuick.clear()
|
412
|
+
self.mTemplates.clearMenu()
|
395
413
|
return
|
396
414
|
|
397
|
-
def
|
415
|
+
def buildQuickLinksMenu(self) -> None:
|
398
416
|
"""Build the quick link menu."""
|
399
417
|
logger.debug("Rebuilding quick links menu")
|
400
418
|
self.mQuick.clear()
|
401
|
-
for
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
lambda
|
419
|
+
for tHandle, nwItem in SHARED.project.tree.iterRoots(None):
|
420
|
+
action = self.mQuick.addAction(nwItem.itemName)
|
421
|
+
action.setData(tHandle)
|
422
|
+
action.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass]))
|
423
|
+
action.triggered.connect(
|
424
|
+
lambda _, tHandle=tHandle: self.projView.setSelectedHandle(tHandle, doScroll=True)
|
407
425
|
)
|
408
426
|
return
|
409
427
|
|
428
|
+
##
|
429
|
+
# Public Slots
|
430
|
+
##
|
431
|
+
|
432
|
+
@pyqtSlot(str, NWItem, QIcon)
|
433
|
+
def treeItemRefreshed(self, tHandle: str, nwItem: NWItem, icon: QIcon) -> None:
|
434
|
+
"""Process change in tree items to update menu content."""
|
435
|
+
if nwItem.isTemplateFile() and nwItem.isActive:
|
436
|
+
self.mTemplates.addUpdate(tHandle, nwItem.itemName, icon)
|
437
|
+
elif tHandle in self.mTemplates:
|
438
|
+
self.mTemplates.remove(tHandle)
|
439
|
+
return
|
440
|
+
|
441
|
+
@pyqtSlot(str)
|
442
|
+
def treeSelectionChanged(self, tHandle: str) -> None:
|
443
|
+
"""Toggle the visibility of the new item entries for novel
|
444
|
+
documents. They should only be visible if novel documents can
|
445
|
+
actually be added.
|
446
|
+
"""
|
447
|
+
nwItem = SHARED.project.tree[tHandle]
|
448
|
+
allowDoc = isinstance(nwItem, NWItem) and nwItem.documentAllowed()
|
449
|
+
self.aAddEmpty.setVisible(allowDoc)
|
450
|
+
self.aAddChap.setVisible(allowDoc)
|
451
|
+
self.aAddScene.setVisible(allowDoc)
|
452
|
+
return
|
453
|
+
|
410
454
|
##
|
411
455
|
# Internal Functions
|
412
456
|
##
|
@@ -422,7 +466,6 @@ class GuiProjectToolBar(QWidget):
|
|
422
466
|
|
423
467
|
self.mAddRoot.clear()
|
424
468
|
addClass(nwItemClass.NOVEL)
|
425
|
-
addClass(nwItemClass.ARCHIVE)
|
426
469
|
self.mAddRoot.addSeparator()
|
427
470
|
addClass(nwItemClass.PLOT)
|
428
471
|
addClass(nwItemClass.CHARACTER)
|
@@ -431,26 +474,12 @@ class GuiProjectToolBar(QWidget):
|
|
431
474
|
addClass(nwItemClass.OBJECT)
|
432
475
|
addClass(nwItemClass.ENTITY)
|
433
476
|
addClass(nwItemClass.CUSTOM)
|
477
|
+
self.mAddRoot.addSeparator()
|
478
|
+
addClass(nwItemClass.ARCHIVE)
|
479
|
+
addClass(nwItemClass.TEMPLATE)
|
434
480
|
|
435
481
|
return
|
436
482
|
|
437
|
-
##
|
438
|
-
# Private Slots
|
439
|
-
##
|
440
|
-
|
441
|
-
@pyqtSlot(str)
|
442
|
-
def _treeSelectionChanged(self, tHandle: str) -> None:
|
443
|
-
"""Toggle the visibility of the new item entries for novel
|
444
|
-
documents. They should only be visible if novel documents can
|
445
|
-
actually be added.
|
446
|
-
"""
|
447
|
-
nwItem = SHARED.project.tree[tHandle]
|
448
|
-
allowDoc = isinstance(nwItem, NWItem) and nwItem.documentAllowed()
|
449
|
-
self.aAddEmpty.setVisible(allowDoc)
|
450
|
-
self.aAddChap.setVisible(allowDoc)
|
451
|
-
self.aAddScene.setVisible(allowDoc)
|
452
|
-
return
|
453
|
-
|
454
483
|
# END Class GuiProjectToolBar
|
455
484
|
|
456
485
|
|
@@ -465,6 +494,8 @@ class GuiProjectTree(QTreeWidget):
|
|
465
494
|
D_HANDLE = Qt.ItemDataRole.UserRole
|
466
495
|
D_WORDS = Qt.ItemDataRole.UserRole + 1
|
467
496
|
|
497
|
+
itemRefreshed = pyqtSignal(str, NWItem, QIcon)
|
498
|
+
|
468
499
|
def __init__(self, projView: GuiProjectView) -> None:
|
469
500
|
super().__init__(parent=projView)
|
470
501
|
|
@@ -588,7 +619,7 @@ class GuiProjectTree(QTreeWidget):
|
|
588
619
|
return False
|
589
620
|
|
590
621
|
def newTreeItem(self, itemType: nwItemType, itemClass: nwItemClass | None = None,
|
591
|
-
hLevel: int = 1, isNote: bool = False) -> bool:
|
622
|
+
hLevel: int = 1, isNote: bool = False, copyDoc: str | None = None) -> bool:
|
592
623
|
"""Add new item to the tree, with a given itemType (and
|
593
624
|
itemClass if Root), and attach it to the selected handle. Also
|
594
625
|
make sure the item is added in a place it can be added, and that
|
@@ -628,7 +659,10 @@ class GuiProjectTree(QTreeWidget):
|
|
628
659
|
# Set default label and determine if new item is to be added
|
629
660
|
# as child or sibling to the selected item
|
630
661
|
if itemType == nwItemType.FILE:
|
631
|
-
if
|
662
|
+
if copyDoc and (cItem := SHARED.project.tree[copyDoc]):
|
663
|
+
newLabel = cItem.itemName
|
664
|
+
asChild = sIsParent and pItem.isDocumentLayout()
|
665
|
+
elif isNote:
|
632
666
|
newLabel = self.tr("New Note")
|
633
667
|
asChild = sIsParent
|
634
668
|
elif hLevel == 2:
|
@@ -676,12 +710,14 @@ class GuiProjectTree(QTreeWidget):
|
|
676
710
|
return True
|
677
711
|
|
678
712
|
# Handle new file creation
|
679
|
-
if itemType == nwItemType.FILE and
|
713
|
+
if itemType == nwItemType.FILE and copyDoc:
|
714
|
+
SHARED.project.copyFileContent(tHandle, copyDoc)
|
715
|
+
elif itemType == nwItemType.FILE and hLevel > 0:
|
680
716
|
SHARED.project.writeNewFile(tHandle, hLevel, not isNote)
|
681
717
|
|
682
718
|
# Add the new item to the project tree
|
683
719
|
self.revealNewTreeItem(tHandle, nHandle=nHandle, wordCount=True)
|
684
|
-
self.
|
720
|
+
self.projView.setTreeFocus() # See issue #1376
|
685
721
|
|
686
722
|
return True
|
687
723
|
|
@@ -771,8 +807,7 @@ class GuiProjectTree(QTreeWidget):
|
|
771
807
|
|
772
808
|
def renameTreeItem(self, tHandle: str, name: str = "") -> None:
|
773
809
|
"""Open a dialog to edit the label of an item."""
|
774
|
-
tItem
|
775
|
-
if tItem:
|
810
|
+
if tItem := SHARED.project.tree[tHandle]:
|
776
811
|
newLabel, dlgOk = GuiEditLabel.getLabel(self, text=name or tItem.itemName)
|
777
812
|
if dlgOk:
|
778
813
|
tItem.setName(newLabel)
|
@@ -786,24 +821,24 @@ class GuiProjectTree(QTreeWidget):
|
|
786
821
|
project structure, and must be called before any code that
|
787
822
|
depends on this order to be up to date.
|
788
823
|
"""
|
789
|
-
|
824
|
+
items = []
|
790
825
|
for i in range(self.topLevelItemCount()):
|
791
826
|
item = self.topLevelItem(i)
|
792
827
|
if isinstance(item, QTreeWidgetItem):
|
793
|
-
|
828
|
+
items = self._scanChildren(items, item, i)
|
794
829
|
logger.debug("Saving project tree item order")
|
795
|
-
SHARED.project.setTreeOrder(
|
830
|
+
SHARED.project.setTreeOrder(items)
|
796
831
|
return
|
797
832
|
|
798
833
|
def getTreeFromHandle(self, tHandle: str) -> list[str]:
|
799
834
|
"""Recursively return all the child items starting from a given
|
800
835
|
item handle.
|
801
836
|
"""
|
802
|
-
|
803
|
-
|
804
|
-
if
|
805
|
-
|
806
|
-
return
|
837
|
+
result = []
|
838
|
+
tIten = self._getTreeItem(tHandle)
|
839
|
+
if tIten is not None:
|
840
|
+
result = self._scanChildren(result, tIten, 0)
|
841
|
+
return result
|
807
842
|
|
808
843
|
def requestDeleteItem(self, tHandle: str | None = None) -> bool:
|
809
844
|
"""Request an item deleted from the project tree. This function
|
@@ -858,11 +893,11 @@ class GuiProjectTree(QTreeWidget):
|
|
858
893
|
SHARED.info(self.tr("There is currently no Trash folder in this project."))
|
859
894
|
return False
|
860
895
|
|
861
|
-
|
862
|
-
if trashHandle in
|
863
|
-
|
896
|
+
trashItems = self.getTreeFromHandle(trashHandle)
|
897
|
+
if trashHandle in trashItems:
|
898
|
+
trashItems.remove(trashHandle)
|
864
899
|
|
865
|
-
nTrash = len(
|
900
|
+
nTrash = len(trashItems)
|
866
901
|
if nTrash == 0:
|
867
902
|
SHARED.info(self.tr("The Trash folder is already empty."))
|
868
903
|
return False
|
@@ -1030,6 +1065,9 @@ class GuiProjectTree(QTreeWidget):
|
|
1030
1065
|
trFont.setUnderline(hLevel == "H1")
|
1031
1066
|
trItem.setFont(self.C_NAME, trFont)
|
1032
1067
|
|
1068
|
+
# Emit Refresh Signal
|
1069
|
+
self.itemRefreshed.emit(tHandle, nwItem, itemIcon)
|
1070
|
+
|
1033
1071
|
return
|
1034
1072
|
|
1035
1073
|
def propagateCount(self, tHandle: str, newCount: int, countChildren: bool = False) -> None:
|
@@ -1359,8 +1397,7 @@ class GuiProjectTree(QTreeWidget):
|
|
1359
1397
|
not including) a given item.
|
1360
1398
|
"""
|
1361
1399
|
if isinstance(trItem, QTreeWidgetItem):
|
1362
|
-
|
1363
|
-
for i in range(chCount):
|
1400
|
+
for i in range(trItem.childCount()):
|
1364
1401
|
chItem = trItem.child(i)
|
1365
1402
|
chItem.setExpanded(isExpanded)
|
1366
1403
|
self._recursiveSetExpanded(chItem, isExpanded)
|
@@ -1634,6 +1671,71 @@ class GuiProjectTree(QTreeWidget):
|
|
1634
1671
|
# END Class GuiProjectTree
|
1635
1672
|
|
1636
1673
|
|
1674
|
+
class _UpdatableMenu(QMenu):
|
1675
|
+
|
1676
|
+
menuItemTriggered = pyqtSignal(str)
|
1677
|
+
|
1678
|
+
def __init__(self, parent: QWidget) -> None:
|
1679
|
+
super().__init__(parent=parent)
|
1680
|
+
self._map: dict[str, QAction] = {}
|
1681
|
+
self.setTitle(self.tr("From Template"))
|
1682
|
+
self.triggered.connect(self._actionTriggered)
|
1683
|
+
return
|
1684
|
+
|
1685
|
+
def __contains__(self, tHandle: str) -> bool:
|
1686
|
+
"""Look up a handle in the menu."""
|
1687
|
+
return tHandle in self._map
|
1688
|
+
|
1689
|
+
##
|
1690
|
+
# Methods
|
1691
|
+
##
|
1692
|
+
|
1693
|
+
def addUpdate(self, tHandle: str, name: str, icon: QIcon) -> None:
|
1694
|
+
"""Add or update a template item."""
|
1695
|
+
if tHandle in self._map:
|
1696
|
+
action = self._map[tHandle]
|
1697
|
+
action.setText(name)
|
1698
|
+
action.setIcon(icon)
|
1699
|
+
else:
|
1700
|
+
action = QAction(icon, name, self)
|
1701
|
+
action.setData(tHandle)
|
1702
|
+
self.addAction(action)
|
1703
|
+
self._map[tHandle] = action
|
1704
|
+
self.setActionsVisible(True)
|
1705
|
+
return
|
1706
|
+
|
1707
|
+
def remove(self, tHandle: str) -> None:
|
1708
|
+
"""Remove a template item."""
|
1709
|
+
if action := self._map.pop(tHandle, None):
|
1710
|
+
self.removeAction(action)
|
1711
|
+
if not self._map:
|
1712
|
+
self.setActionsVisible(False)
|
1713
|
+
return
|
1714
|
+
|
1715
|
+
def clearMenu(self) -> None:
|
1716
|
+
"""Clear all menu content."""
|
1717
|
+
self._map.clear()
|
1718
|
+
self.clear()
|
1719
|
+
return
|
1720
|
+
|
1721
|
+
def setActionsVisible(self, value: bool) -> None:
|
1722
|
+
"""Set the visibility of root action."""
|
1723
|
+
self.menuAction().setVisible(value)
|
1724
|
+
return
|
1725
|
+
|
1726
|
+
##
|
1727
|
+
# Private Slots
|
1728
|
+
##
|
1729
|
+
|
1730
|
+
@pyqtSlot(QAction)
|
1731
|
+
def _actionTriggered(self, action: QAction) -> None:
|
1732
|
+
"""Translate the menu trigger into an item trigger."""
|
1733
|
+
self.menuItemTriggered.emit(str(action.data()))
|
1734
|
+
return
|
1735
|
+
|
1736
|
+
# END Class _UpdatableMenu
|
1737
|
+
|
1738
|
+
|
1637
1739
|
class _TreeContextMenu(QMenu):
|
1638
1740
|
|
1639
1741
|
def __init__(self, projTree: GuiProjectTree, nwItem: NWItem) -> None:
|
@@ -1650,7 +1752,7 @@ class _TreeContextMenu(QMenu):
|
|
1650
1752
|
|
1651
1753
|
return
|
1652
1754
|
|
1653
|
-
def __del__(self): # pragma: no cover
|
1755
|
+
def __del__(self) -> None: # pragma: no cover
|
1654
1756
|
logger.debug("Delete: _TreeContextMenu")
|
1655
1757
|
return
|
1656
1758
|
|
@@ -1675,10 +1777,15 @@ class _TreeContextMenu(QMenu):
|
|
1675
1777
|
self._docActions()
|
1676
1778
|
self.addSeparator()
|
1677
1779
|
|
1780
|
+
# Create New Items
|
1781
|
+
self._itemCreation()
|
1782
|
+
self.addSeparator()
|
1783
|
+
|
1678
1784
|
# Edit Item Settings
|
1679
|
-
|
1680
|
-
|
1785
|
+
action = self.addAction(self.tr("Rename"))
|
1786
|
+
action.triggered.connect(lambda: self.projTree.renameTreeItem(self._handle))
|
1681
1787
|
if isFile:
|
1788
|
+
self._itemHeader()
|
1682
1789
|
self._itemActive(False)
|
1683
1790
|
self._itemStatusImport(False)
|
1684
1791
|
|
@@ -1717,6 +1824,25 @@ class _TreeContextMenu(QMenu):
|
|
1717
1824
|
)
|
1718
1825
|
return
|
1719
1826
|
|
1827
|
+
def _itemCreation(self) -> None:
|
1828
|
+
"""Add create item actions."""
|
1829
|
+
menu = self.addMenu(self.tr("Create New ..."))
|
1830
|
+
menu.addAction(self.projView.projBar.aAddEmpty)
|
1831
|
+
menu.addAction(self.projView.projBar.aAddChap)
|
1832
|
+
menu.addAction(self.projView.projBar.aAddScene)
|
1833
|
+
menu.addAction(self.projView.projBar.aAddNote)
|
1834
|
+
menu.addAction(self.projView.projBar.aAddFolder)
|
1835
|
+
return
|
1836
|
+
|
1837
|
+
def _itemHeader(self) -> None:
|
1838
|
+
"""Check if there is a header that can be used for rename."""
|
1839
|
+
if hItem := SHARED.project.index.getItemHeader(self._handle, "T0001"):
|
1840
|
+
action = self.addAction(self.tr("Rename to Heading"))
|
1841
|
+
action.triggered.connect(
|
1842
|
+
lambda: self.projTree.renameTreeItem(self._handle, hItem.title)
|
1843
|
+
)
|
1844
|
+
return
|
1845
|
+
|
1720
1846
|
def _itemActive(self, multi: bool) -> None:
|
1721
1847
|
"""Add Active/Inactive actions."""
|
1722
1848
|
if multi:
|
@@ -1747,7 +1873,7 @@ class _TreeContextMenu(QMenu):
|
|
1747
1873
|
menu.addSeparator()
|
1748
1874
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
1749
1875
|
action.triggered.connect(
|
1750
|
-
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.
|
1876
|
+
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_STATUS)
|
1751
1877
|
)
|
1752
1878
|
else:
|
1753
1879
|
menu = self.addMenu(self.tr("Set Importance to ..."))
|
@@ -1764,13 +1890,13 @@ class _TreeContextMenu(QMenu):
|
|
1764
1890
|
menu.addSeparator()
|
1765
1891
|
action = menu.addAction(self.tr("Manage Labels ..."))
|
1766
1892
|
action.triggered.connect(
|
1767
|
-
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.
|
1893
|
+
lambda: self.projView.projectSettingsRequest.emit(GuiProjectSettings.PAGE_IMPORT)
|
1768
1894
|
)
|
1769
1895
|
return
|
1770
1896
|
|
1771
1897
|
def _itemTransform(self, isFile: bool, isFolder: bool, hasChild: bool) -> None:
|
1772
1898
|
"""Add actions for the Transform menu."""
|
1773
|
-
menu = self.addMenu(self.tr("Transform"))
|
1899
|
+
menu = self.addMenu(self.tr("Transform ..."))
|
1774
1900
|
|
1775
1901
|
tree = self.projTree
|
1776
1902
|
tHandle = self._handle
|
novelwriter/gui/sidebar.py
CHANGED
@@ -3,10 +3,10 @@ novelWriter – GUI Main Window SideBar
|
|
3
3
|
=====================================
|
4
4
|
|
5
5
|
File History:
|
6
|
-
Created: 2022-05-10 [2.0rc1]
|
6
|
+
Created: 2022-05-10 [2.0rc1] GuiSideBar
|
7
7
|
|
8
8
|
This file is a part of novelWriter
|
9
|
-
Copyright 2018–
|
9
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
10
10
|
|
11
11
|
This program is free software: you can redistribute it and/or modify
|
12
12
|
it under the terms of the GNU General Public License as published by
|
@@ -27,12 +27,13 @@ import logging
|
|
27
27
|
|
28
28
|
from typing import TYPE_CHECKING
|
29
29
|
|
30
|
-
from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
|
31
30
|
from PyQt5.QtGui import QPalette
|
31
|
+
from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
|
32
32
|
from PyQt5.QtWidgets import QMenu, QToolButton, QVBoxLayout, QWidget
|
33
33
|
|
34
34
|
from novelwriter import CONFIG, SHARED
|
35
35
|
from novelwriter.enum import nwView
|
36
|
+
from novelwriter.extensions.eventfilters import StatusTipFilter
|
36
37
|
|
37
38
|
if TYPE_CHECKING: # pragma: no cover
|
38
39
|
from novelwriter.guimain import GuiMain
|
@@ -54,6 +55,7 @@ class GuiSideBar(QWidget):
|
|
54
55
|
iPx = CONFIG.pxInt(24)
|
55
56
|
iconSize = QSize(iPx, iPx)
|
56
57
|
self.setContentsMargins(0, 0, 0, 0)
|
58
|
+
self.installEventFilter(StatusTipFilter(mainGui))
|
57
59
|
|
58
60
|
# Buttons
|
59
61
|
self.tbProject = QToolButton(self)
|
@@ -77,9 +79,9 @@ class GuiSideBar(QWidget):
|
|
77
79
|
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
78
80
|
|
79
81
|
self.tbDetails = QToolButton(self)
|
80
|
-
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("
|
82
|
+
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
|
81
83
|
self.tbDetails.setIconSize(iconSize)
|
82
|
-
self.tbDetails.clicked.connect(self.mainGui.
|
84
|
+
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
|
83
85
|
|
84
86
|
self.tbStats = QToolButton(self)
|
85
87
|
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
|
@@ -162,11 +164,10 @@ class GuiSideBar(QWidget):
|
|
162
164
|
|
163
165
|
class _PopRightMenu(QMenu):
|
164
166
|
|
165
|
-
def event(self, event: QEvent):
|
167
|
+
def event(self, event: QEvent) -> bool:
|
166
168
|
"""Overload the show event and move the menu popup location."""
|
167
169
|
if event.type() == QEvent.Show:
|
168
|
-
parent
|
169
|
-
if isinstance(parent, QWidget):
|
170
|
+
if isinstance(parent := self.parent(), QWidget):
|
170
171
|
offset = QPoint(parent.width(), parent.height() - self.height())
|
171
172
|
self.move(parent.mapToGlobal(offset))
|
172
173
|
return super(_PopRightMenu, self).event(event)
|
novelwriter/gui/statusbar.py
CHANGED
@@ -3,10 +3,10 @@ novelWriter – GUI Main Window Status Bar
|
|
3
3
|
========================================
|
4
4
|
|
5
5
|
File History:
|
6
|
-
Created: 2019-04-20 [0.0.1]
|
6
|
+
Created: 2019-04-20 [0.0.1] GuiMainStatus
|
7
7
|
|
8
8
|
This file is a part of novelWriter
|
9
|
-
Copyright 2018–
|
9
|
+
Copyright 2018–2024, Veronica Berglyd Olsen
|
10
10
|
|
11
11
|
This program is free software: you can redistribute it and/or modify
|
12
12
|
it under the terms of the GNU General Public License as published by
|
@@ -27,9 +27,9 @@ import logging
|
|
27
27
|
|
28
28
|
from time import time
|
29
29
|
from typing import TYPE_CHECKING, Literal
|
30
|
+
from datetime import datetime
|
30
31
|
|
31
32
|
from PyQt5.QtCore import pyqtSlot, QLocale
|
32
|
-
from PyQt5.QtGui import QColor
|
33
33
|
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel
|
34
34
|
|
35
35
|
from novelwriter import CONFIG, SHARED
|
@@ -50,12 +50,13 @@ class GuiMainStatus(QStatusBar):
|
|
50
50
|
|
51
51
|
logger.debug("Create: GuiMainStatus")
|
52
52
|
|
53
|
-
self._refTime
|
53
|
+
self._refTime = -1.0
|
54
54
|
self._userIdle = False
|
55
|
+
self._debugInfo = False
|
55
56
|
|
56
|
-
colNone =
|
57
|
-
colSaved =
|
58
|
-
colUnsaved =
|
57
|
+
colNone = SHARED.theme.statNone
|
58
|
+
colSaved = SHARED.theme.statSaved
|
59
|
+
colUnsaved = SHARED.theme.statUnsaved
|
59
60
|
|
60
61
|
iPx = SHARED.theme.baseIconSize
|
61
62
|
|
@@ -223,4 +224,45 @@ class GuiMainStatus(QStatusBar):
|
|
223
224
|
self.setDocumentStatus(StatusLED.S_BAD if status else StatusLED.S_GOOD)
|
224
225
|
return
|
225
226
|
|
227
|
+
##
|
228
|
+
# Debug
|
229
|
+
##
|
230
|
+
|
231
|
+
def memInfo(self) -> None: # pragma: no cover
|
232
|
+
"""Display memory info on the status bar. This is used to
|
233
|
+
investigate memory usage and Qt widgets that get left in memory.
|
234
|
+
Enabled by the --meminfo command line flag.
|
235
|
+
|
236
|
+
By default, this tracks memory usage diff after launch. To track
|
237
|
+
full memory usage, set environment variable PYTHONTRACEMALLOC=1
|
238
|
+
before starting novelWriter.
|
239
|
+
"""
|
240
|
+
import tracemalloc
|
241
|
+
from collections import Counter
|
242
|
+
|
243
|
+
widgets = qApp.allWidgets()
|
244
|
+
if not self._debugInfo:
|
245
|
+
if tracemalloc.is_tracing():
|
246
|
+
self._traceMallocRef = "Total"
|
247
|
+
else:
|
248
|
+
self._traceMallocRef = "Relative"
|
249
|
+
tracemalloc.start()
|
250
|
+
self._debugInfo = True
|
251
|
+
self._wCounts = Counter([type(x).__name__ for x in widgets])
|
252
|
+
|
253
|
+
if hasattr(self, "_wCounts"):
|
254
|
+
diff = Counter([type(x).__name__ for x in widgets]) - self._wCounts
|
255
|
+
for name, count in diff.items():
|
256
|
+
logger.debug("Widget '%s': +%d", name, count)
|
257
|
+
|
258
|
+
mem = tracemalloc.get_traced_memory()
|
259
|
+
stamp = datetime.now().strftime("%H:%M:%S")
|
260
|
+
self.showMessage((
|
261
|
+
f"Debug [{stamp}]"
|
262
|
+
f" \u2013 Widgets: {len(widgets)}"
|
263
|
+
f" \u2013 {self._traceMallocRef} Memory: {mem[0]:n}"
|
264
|
+
f" \u2013 Peak: {mem[1]:n}"
|
265
|
+
), 6000)
|
266
|
+
return
|
267
|
+
|
226
268
|
# END Class GuiMainStatus
|