Add outline to manuscript tool (#1768)
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="m10.837 12.256h-6.8373v-6.8347c0.010667-1.5131 0.89467-1.877 1.964-0.80652l1.5387 1.5437c1.3533-1.0598 3.0093-1.6397 4.7547-1.6397 2.0667 0 4.012 0.80518 5.472 2.2676 1.4653 1.4557 2.2707 3.4034 2.2707 5.4696 0 2.0703-0.80533 4.0153-2.268 5.475-1.4627 1.4624-3.4067 2.2689-5.4747 2.2689-2.0667 0-4.012-0.80652-5.4747-2.2702-0.39467-0.39593-0.744-0.82784-1.04-1.2864-0.46267-0.7212-0.25333-1.6797 0.46667-2.1396 0.71867-0.46125 1.6773-0.25195 2.1387 0.46658 0.17733 0.27595 0.38933 0.53323 0.624 0.77319 0.87867 0.87717 2.0453 1.3597 3.2853 1.3597s2.4067-0.48124 3.2853-1.3597c0.876-0.87717 1.36-2.0436 1.36-3.2861 0-1.2398-0.48267-2.4062-1.36-3.2807-0.87867-0.87717-2.0453-1.3611-3.2853-1.3611-0.91733 0-1.7947 0.26795-2.5453 0.76252l1.9307 1.9343c1.0707 1.0691 0.70667 1.9436-0.80533 1.9436z" fill="#9c9" stroke-width="1.3332"/>
|
<path d="m10.692 12.288h-7.692v-7.689c0.012-1.7022 1.0065-2.1116 2.2095-0.90734l1.731 1.7367c1.5225-1.1923 3.3855-1.8447 5.349-1.8447 2.325 0 4.5135 0.90583 6.156 2.5511 1.6485 1.6377 2.5545 3.8288 2.5545 6.1533 0 2.3291-0.906 4.5172-2.5515 6.1594-1.6455 1.6452-3.8325 2.5525-6.159 2.5525-2.325 0-4.5135-0.90734-6.159-2.554-0.444-0.44542-0.837-0.93132-1.17-1.4472-0.5205-0.81135-0.285-1.8897 0.525-2.4071 0.8085-0.51891 1.887-0.28344 2.406 0.5249 0.1995 0.31044 0.438 0.59988 0.702 0.86984 0.9885 0.98682 2.301 1.5297 3.696 1.5297 1.395 0 2.7075-0.5414 3.696-1.5297 0.9855-0.98682 1.53-2.2991 1.53-3.6969 0-1.3948-0.543-2.707-1.53-3.6908-0.9885-0.98682-2.301-1.5312-3.696-1.5312-1.032 0-2.019 0.30144-2.8635 0.85784l2.172 2.1761c1.2045 1.2027 0.795 2.1866-0.906 2.1866z" fill="#9c9" stroke-width="1.3332"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 984 B After Width: | Height: | Size: 952 B |
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="m13.163 12.256h6.8373v-6.8347c-0.010667-1.5131-0.89467-1.877-1.964-0.80652l-1.5387 1.5437c-1.3533-1.0598-3.0093-1.6397-4.7547-1.6397-2.0667 0-4.012 0.80518-5.472 2.2676-1.4653 1.4557-2.2707 3.4034-2.2707 5.4696 0 2.0703 0.80533 4.0153 2.268 5.475 1.4627 1.4624 3.4067 2.2689 5.4747 2.2689 2.0667 0 4.012-0.80652 5.4747-2.2702 0.39467-0.39593 0.744-0.82784 1.04-1.2864 0.46267-0.7212 0.25333-1.6797-0.46667-2.1396-0.71867-0.46125-1.6773-0.25195-2.1387 0.46658-0.17733 0.27595-0.38933 0.53323-0.624 0.77319-0.87867 0.87717-2.0453 1.3597-3.2853 1.3597s-2.4067-0.48124-3.2853-1.3597c-0.876-0.87717-1.36-2.0436-1.36-3.2861 0-1.2398 0.48267-2.4062 1.36-3.2807 0.87867-0.87717 2.0453-1.3611 3.2853-1.3611 0.91733 0 1.7947 0.26795 2.5453 0.76252l-1.9307 1.9343c-1.0707 1.0691-0.70667 1.9436 0.80533 1.9436z" fill="#9c9" stroke-width="1.3332"/>
|
<path d="m13.308 12.288h7.692v-7.689c-0.012-1.7022-1.0065-2.1116-2.2095-0.90734l-1.731 1.7367c-1.5225-1.1923-3.3855-1.8447-5.349-1.8447-2.325 0-4.5135 0.90583-6.156 2.5511-1.6485 1.6377-2.5545 3.8288-2.5545 6.1533 0 2.3291 0.906 4.5172 2.5515 6.1594 1.6455 1.6452 3.8325 2.5525 6.159 2.5525 2.325 0 4.5135-0.90734 6.159-2.554 0.444-0.44542 0.837-0.93132 1.17-1.4472 0.5205-0.81135 0.285-1.8897-0.525-2.4071-0.8085-0.51891-1.887-0.28344-2.406 0.5249-0.1995 0.31044-0.438 0.59988-0.702 0.86984-0.9885 0.98682-2.301 1.5297-3.696 1.5297-1.395 0-2.7075-0.5414-3.696-1.5297-0.9855-0.98682-1.53-2.2991-1.53-3.6969 0-1.3948 0.543-2.707 1.53-3.6908 0.9885-0.98682 2.301-1.5312 3.696-1.5312 1.032 0 2.019 0.30144 2.8635 0.85784l-2.172 2.1761c-1.2045 1.2027-0.795 2.1866 0.906 2.1866z" fill="#9c9" stroke-width="1.3332"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 990 B After Width: | Height: | Size: 956 B |
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="m19.041 4.9586c-1.0144-1.0157-2.6635-1.0157-3.678 0l-3.3632 3.3632-3.3632-3.3632c-1.0144-1.0157-2.6635-1.0157-3.678 0-1.0157 1.0157-1.0157 2.6622 0 3.678l3.3619 3.3632-3.3619 3.3632c-1.0157 1.0157-1.0157 2.6622 0 3.678 0.50722 0.50852 1.1731 0.76212 1.839 0.76212 0.66588 0 1.3318-0.25361 1.839-0.76212l3.3632-3.3632 3.3632 3.3632c0.50722 0.50852 1.1731 0.76212 1.839 0.76212 0.66588 0 1.3318-0.25361 1.839-0.76212 1.0157-1.0157 1.0157-2.6622 0-3.678l-3.3619-3.3632 3.3619-3.3632c1.0157-1.0157 1.0157-2.6622 0-3.678z" fill="#d64848" stroke-width="1.3006"/>
|
<path d="m20.121 3.8786c-1.17-1.1715-3.072-1.1715-4.2421 0l-3.879 3.8791-3.879-3.8791c-1.17-1.1715-3.072-1.1715-4.2421 0-1.1715 1.1715-1.1715 3.0705 0 4.2421l3.8776 3.879-3.8776 3.879c-1.1715 1.1715-1.1715 3.0705 0 4.2421 0.58502 0.58652 1.353 0.87902 2.1211 0.87902 0.76801 0 1.5361-0.29251 2.1211-0.87902l3.879-3.879 3.879 3.879c0.58502 0.58652 1.353 0.87902 2.1211 0.87902 0.76801 0 1.5361-0.29251 2.1211-0.87902 1.1715-1.1715 1.1715-3.0705 0-4.2421l-3.8776-3.879 3.8776-3.879c1.1715-1.1715 1.1715-3.0705 0-4.2421z" fill="#d64848" stroke-width="1.5001"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 711 B After Width: | Height: | Size: 703 B |
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="m10.837 12.256h-6.8373v-6.8347c0.010667-1.5131 0.89467-1.877 1.964-0.80652l1.5387 1.5437c1.3533-1.0598 3.0093-1.6397 4.7547-1.6397 2.0667 0 4.012 0.80518 5.472 2.2676 1.4653 1.4557 2.2707 3.4034 2.2707 5.4696 0 2.0703-0.80533 4.0153-2.268 5.475-1.4627 1.4624-3.4067 2.2689-5.4747 2.2689-2.0667 0-4.012-0.80652-5.4747-2.2702-0.39467-0.39593-0.744-0.82784-1.04-1.2864-0.46267-0.7212-0.25333-1.6797 0.46667-2.1396 0.71867-0.46125 1.6773-0.25195 2.1387 0.46658 0.17733 0.27595 0.38933 0.53323 0.624 0.77319 0.87867 0.87717 2.0453 1.3597 3.2853 1.3597s2.4067-0.48124 3.2853-1.3597c0.876-0.87717 1.36-2.0436 1.36-3.2861 0-1.2398-0.48267-2.4062-1.36-3.2807-0.87867-0.87717-2.0453-1.3611-3.2853-1.3611-0.91733 0-1.7947 0.26795-2.5453 0.76252l1.9307 1.9343c1.0707 1.0691 0.70667 1.9436-0.80533 1.9436z" fill="#718c00" stroke-width="1.3332"/>
|
<path d="m10.692 12.288h-7.692v-7.689c0.012-1.7022 1.0065-2.1116 2.2095-0.90734l1.731 1.7367c1.5225-1.1923 3.3855-1.8447 5.349-1.8447 2.325 0 4.5135 0.90583 6.156 2.5511 1.6485 1.6377 2.5545 3.8288 2.5545 6.1533 0 2.3291-0.906 4.5172-2.5515 6.1594-1.6455 1.6452-3.8325 2.5525-6.159 2.5525-2.325 0-4.5135-0.90734-6.159-2.554-0.444-0.44542-0.837-0.93132-1.17-1.4472-0.5205-0.81135-0.285-1.8897 0.525-2.4071 0.8085-0.51891 1.887-0.28344 2.406 0.5249 0.1995 0.31044 0.438 0.59988 0.702 0.86984 0.9885 0.98682 2.301 1.5297 3.696 1.5297 1.395 0 2.7075-0.5414 3.696-1.5297 0.9855-0.98682 1.53-2.2991 1.53-3.6969 0-1.3948-0.543-2.707-1.53-3.6908-0.9885-0.98682-2.301-1.5312-3.696-1.5312-1.032 0-2.019 0.30144-2.8635 0.85784l2.172 2.1761c1.2045 1.2027 0.795 2.1866-0.906 2.1866z" fill="#718c00" stroke-width="1.4998"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 987 B After Width: | Height: | Size: 955 B |
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="m13.163 12.256h6.8373v-6.8347c-0.010667-1.5131-0.89467-1.877-1.964-0.80652l-1.5387 1.5437c-1.3533-1.0598-3.0093-1.6397-4.7547-1.6397-2.0667 0-4.012 0.80518-5.472 2.2676-1.4653 1.4557-2.2707 3.4034-2.2707 5.4696 0 2.0703 0.80533 4.0153 2.268 5.475 1.4627 1.4624 3.4067 2.2689 5.4747 2.2689 2.0667 0 4.012-0.80652 5.4747-2.2702 0.39467-0.39593 0.744-0.82784 1.04-1.2864 0.46267-0.7212 0.25333-1.6797-0.46667-2.1396-0.71867-0.46125-1.6773-0.25195-2.1387 0.46658-0.17733 0.27595-0.38933 0.53323-0.624 0.77319-0.87867 0.87717-2.0453 1.3597-3.2853 1.3597s-2.4067-0.48124-3.2853-1.3597c-0.876-0.87717-1.36-2.0436-1.36-3.2861 0-1.2398 0.48267-2.4062 1.36-3.2807 0.87867-0.87717 2.0453-1.3611 3.2853-1.3611 0.91733 0 1.7947 0.26795 2.5453 0.76252l-1.9307 1.9343c-1.0707 1.0691-0.70667 1.9436 0.80533 1.9436z" fill="#718c00" stroke-width="1.3332"/>
|
<path d="m13.308 12.288h7.692v-7.689c-0.012001-1.7022-1.0065-2.1116-2.2095-0.90734l-1.731 1.7367c-1.5225-1.1923-3.3855-1.8447-5.349-1.8447-2.325 0-4.5135 0.90583-6.156 2.5511-1.6485 1.6377-2.5545 3.8288-2.5545 6.1533 0 2.3291 0.906 4.5172 2.5515 6.1594 1.6455 1.6452 3.8325 2.5525 6.159 2.5525 2.325 0 4.5135-0.90734 6.159-2.554 0.444-0.44542 0.837-0.93132 1.17-1.4472 0.5205-0.81135 0.285-1.8897-0.525-2.4071-0.8085-0.51891-1.887-0.28344-2.406 0.5249-0.1995 0.31044-0.438 0.59988-0.702 0.86984-0.9885 0.98682-2.301 1.5297-3.696 1.5297-1.395 0-2.7075-0.5414-3.696-1.5297-0.9855-0.98682-1.53-2.2991-1.53-3.6969 0-1.3948 0.543-2.707 1.53-3.6908 0.9885-0.98682 2.301-1.5312 3.696-1.5312 1.032 0 2.019 0.30144 2.8635 0.85784l-2.172 2.1761c-1.2045 1.2027-0.795 2.1866 0.906 2.1866z" fill="#718c00" stroke-width="1.4998"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 993 B After Width: | Height: | Size: 962 B |
@@ -1,4 +1,4 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path d="m19.041 4.9586c-1.0144-1.0157-2.6635-1.0157-3.678 0l-3.3632 3.3632-3.3632-3.3632c-1.0144-1.0157-2.6635-1.0157-3.678 0-1.0157 1.0157-1.0157 2.6622 0 3.678l3.3619 3.3632-3.3619 3.3632c-1.0157 1.0157-1.0157 2.6622 0 3.678 0.50722 0.50852 1.1731 0.76212 1.839 0.76212 0.66588 0 1.3318-0.25361 1.839-0.76212l3.3632-3.3632 3.3632 3.3632c0.50722 0.50852 1.1731 0.76212 1.839 0.76212 0.66588 0 1.3318-0.25361 1.839-0.76212 1.0157-1.0157 1.0157-2.6622 0-3.678l-3.3619-3.3632 3.3619-3.3632c1.0157-1.0157 1.0157-2.6622 0-3.678z" fill="#c82829" stroke-width="1.3006"/>
|
<path d="m20.121 3.8786c-1.17-1.1715-3.072-1.1715-4.2421 0l-3.879 3.8791-3.879-3.8791c-1.17-1.1715-3.072-1.1715-4.2421 0-1.1715 1.1715-1.1715 3.0705 0 4.2421l3.8776 3.879-3.8776 3.879c-1.1715 1.1715-1.1715 3.0705 0 4.2421 0.58502 0.58652 1.353 0.87902 2.1211 0.87902 0.76801 0 1.5361-0.29251 2.1211-0.87902l3.879-3.879 3.879 3.879c0.58502 0.58652 1.353 0.87902 2.1211 0.87902 0.76801 0 1.5361-0.29251 2.1211-0.87902 1.1715-1.1715 1.1715-3.0705 0-4.2421l-3.8776-3.879 3.8776-3.879c1.1715-1.1715 1.1715-3.0705 0-4.2421z" fill="#c82829" stroke-width="1.5001"/>
|
||||||
</svg>
|
</svg>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 711 B After Width: | Height: | Size: 703 B |
@@ -52,7 +52,10 @@ class NWBuildDocument:
|
|||||||
manuscript, based on a build definition object (BuildSettings).
|
manuscript, based on a build definition object (BuildSettings).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("_project", "_build", "_queue", "_error", "_cache", "_count", "_preview")
|
__slots__ = (
|
||||||
|
"_project", "_build", "_queue", "_error", "_cache", "_count",
|
||||||
|
"_outline", "_preview"
|
||||||
|
)
|
||||||
|
|
||||||
def __init__(self, project: NWProject, build: BuildSettings) -> None:
|
def __init__(self, project: NWProject, build: BuildSettings) -> None:
|
||||||
self._project = project
|
self._project = project
|
||||||
@@ -61,6 +64,7 @@ class NWBuildDocument:
|
|||||||
self._error = None
|
self._error = None
|
||||||
self._cache = None
|
self._cache = None
|
||||||
self._count = False
|
self._count = False
|
||||||
|
self._outline = False
|
||||||
self._preview = False
|
self._preview = False
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -86,13 +90,21 @@ class NWBuildDocument:
|
|||||||
##
|
##
|
||||||
|
|
||||||
def setCountEnabled(self, state: bool) -> None:
|
def setCountEnabled(self, state: bool) -> None:
|
||||||
"""Turn on/off stats counting for builds."""
|
"""Turn on/off stats for builds."""
|
||||||
self._count = state
|
self._count = state
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setBuildOutline(self, state: bool) -> None:
|
||||||
|
"""Turn on/off outline for builds."""
|
||||||
|
self._outline = state
|
||||||
|
return
|
||||||
|
|
||||||
def setPreviewMode(self, state: bool) -> None:
|
def setPreviewMode(self, state: bool) -> None:
|
||||||
"""Set the preview mode of the build. Implies count mode."""
|
"""Set the preview mode of the build. This also enables stats
|
||||||
|
count and outline mode.
|
||||||
|
"""
|
||||||
self._preview = state
|
self._preview = state
|
||||||
|
self._outline = state
|
||||||
self._count = state
|
self._count = state
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -170,6 +182,8 @@ class NWBuildDocument:
|
|||||||
makeObj = ToHtml(self._project)
|
makeObj = ToHtml(self._project)
|
||||||
filtered = self._setupBuild(makeObj)
|
filtered = self._setupBuild(makeObj)
|
||||||
|
|
||||||
|
makeObj.setPreview(self._preview)
|
||||||
|
makeObj.setLinkHeadings(self._preview)
|
||||||
for i, tHandle in enumerate(self._queue):
|
for i, tHandle in enumerate(self._queue):
|
||||||
self._error = None
|
self._error = None
|
||||||
if filtered.get(tHandle, (False, 0))[0]:
|
if filtered.get(tHandle, (False, 0))[0]:
|
||||||
@@ -362,12 +376,16 @@ class NWBuildDocument:
|
|||||||
bldObj.doConvert()
|
bldObj.doConvert()
|
||||||
if self._count:
|
if self._count:
|
||||||
bldObj.countStats()
|
bldObj.countStats()
|
||||||
|
if self._outline:
|
||||||
|
bldObj.buildOutline()
|
||||||
elif tItem.isFileType():
|
elif tItem.isFileType():
|
||||||
bldObj.setText(tHandle)
|
bldObj.setText(tHandle)
|
||||||
bldObj.doPreProcessing()
|
bldObj.doPreProcessing()
|
||||||
bldObj.tokenizeText()
|
bldObj.tokenizeText()
|
||||||
if self._count:
|
if self._count:
|
||||||
bldObj.countStats()
|
bldObj.countStats()
|
||||||
|
if self._outline:
|
||||||
|
bldObj.buildOutline()
|
||||||
if convert:
|
if convert:
|
||||||
bldObj.doConvert()
|
bldObj.doConvert()
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ import logging
|
|||||||
from time import time
|
from time import time
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections.abc import Generator, ItemsView, Iterable
|
from collections.abc import ItemsView, Iterable
|
||||||
|
|
||||||
from novelwriter import SHARED
|
from novelwriter import SHARED
|
||||||
from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout
|
from novelwriter.enum import nwComment, nwItemClass, nwItemType, nwItemLayout
|
||||||
@@ -523,7 +523,7 @@ class NWIndex:
|
|||||||
return tItem[sTitle]
|
return tItem[sTitle]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def iterItemHeadings(self, tHandle: str) -> Generator[str, IndexHeading]:
|
def iterItemHeadings(self, tHandle: str) -> Iterable[tuple[str, IndexHeading]]:
|
||||||
"""Get all headings for a specific item."""
|
"""Get all headings for a specific item."""
|
||||||
if tItem := self._itemIndex[tHandle]:
|
if tItem := self._itemIndex[tHandle]:
|
||||||
yield from tItem.items()
|
yield from tItem.items()
|
||||||
@@ -531,7 +531,7 @@ class NWIndex:
|
|||||||
|
|
||||||
def novelStructure(
|
def novelStructure(
|
||||||
self, rootHandle: str | None = None, activeOnly: bool = True
|
self, rootHandle: str | None = None, activeOnly: bool = True
|
||||||
) -> Generator[tuple[str, str, str, IndexHeading]]:
|
) -> Iterable[tuple[str, str, str, IndexHeading]]:
|
||||||
"""Iterate over all titles in the novel, in the correct order as
|
"""Iterate over all titles in the novel, in the correct order as
|
||||||
they appear in the tree view and in the respective document
|
they appear in the tree view and in the respective document
|
||||||
files, but skipping all note files.
|
files, but skipping all note files.
|
||||||
@@ -673,7 +673,7 @@ class NWIndex:
|
|||||||
|
|
||||||
def getTagsData(
|
def getTagsData(
|
||||||
self, activeOnly: bool = True
|
self, activeOnly: bool = True
|
||||||
) -> Generator[tuple[str, str, str, IndexItem | None, IndexHeading | None]]:
|
) -> Iterable[tuple[str, str, str, IndexItem | None, IndexHeading | None]]:
|
||||||
"""Return all known tags."""
|
"""Return all known tags."""
|
||||||
for tag, data in self._tagsIndex.items():
|
for tag, data in self._tagsIndex.items():
|
||||||
iItem = self._itemIndex[data.get("handle")]
|
iItem = self._itemIndex[data.get("handle")]
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ from time import time
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from collections.abc import Generator
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from PyQt5.QtCore import QCoreApplication
|
from PyQt5.QtCore import QCoreApplication
|
||||||
|
|
||||||
@@ -517,7 +517,7 @@ class NWProject:
|
|||||||
# Class Methods
|
# Class Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def iterProjectItems(self) -> Generator[NWItem]:
|
def iterProjectItems(self) -> Iterable[NWItem]:
|
||||||
"""This function ensures that the item tree loaded is sent to
|
"""This function ensures that the item tree loaded is sent to
|
||||||
the GUI tree view in such a way that the tree can be built. That
|
the GUI tree view in such a way that the tree can be built. That
|
||||||
is, the parent item must be sent before its child. In principle,
|
is, the parent item must be sent before its child. In principle,
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import logging
|
|||||||
from time import time
|
from time import time
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections.abc import Generator
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from novelwriter.error import logException
|
from novelwriter.error import logException
|
||||||
from novelwriter.common import formatTimeStamp
|
from novelwriter.common import formatTimeStamp
|
||||||
@@ -110,7 +110,7 @@ class NWSessionLog:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def iterRecords(self) -> Generator[dict]:
|
def iterRecords(self) -> Iterable[dict]:
|
||||||
"""Iterate through all records in the log."""
|
"""Iterate through all records in the log."""
|
||||||
sessFile = self._project.storage.getMetaFile(nwFiles.SESS_FILE)
|
sessFile = self._project.storage.getMetaFile(nwFiles.SESS_FILE)
|
||||||
if isinstance(sessFile, Path) and sessFile.is_file():
|
if isinstance(sessFile, Path) and sessFile.is_file():
|
||||||
|
|||||||
@@ -28,10 +28,10 @@ import random
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal
|
from typing import TYPE_CHECKING, Literal
|
||||||
from collections.abc import Generator, ItemsView, Iterator, KeysView, ValuesView
|
from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView
|
||||||
|
|
||||||
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
|
from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor
|
||||||
from PyQt5.QtCore import QRectF, Qt
|
from PyQt5.QtCore import QRectF
|
||||||
|
|
||||||
from novelwriter import CONFIG
|
from novelwriter import CONFIG
|
||||||
from novelwriter.common import minmax, simplified
|
from novelwriter.common import minmax, simplified
|
||||||
@@ -193,7 +193,7 @@ class NWStatus:
|
|||||||
self._store[key]["count"] += 1
|
self._store[key]["count"] += 1
|
||||||
return
|
return
|
||||||
|
|
||||||
def pack(self) -> Generator[tuple[str, dict]]:
|
def pack(self) -> Iterable[tuple[str, dict]]:
|
||||||
"""Pack the status entries into a dictionary."""
|
"""Pack the status entries into a dictionary."""
|
||||||
for key, data in self._store.items():
|
for key, data in self._store.items():
|
||||||
yield (data["name"], {
|
yield (data["name"], {
|
||||||
@@ -248,7 +248,7 @@ class NWStatus:
|
|||||||
def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
|
def _createIcon(self, red: int, green: int, blue: int) -> QIcon:
|
||||||
"""Generate an icon for a status label."""
|
"""Generate an icon for a status label."""
|
||||||
pixmap = QPixmap(self._iPX, self._iPX)
|
pixmap = QPixmap(self._iPX, self._iPX)
|
||||||
pixmap.fill(Qt.transparent)
|
pixmap.fill(QColor(0, 0, 0, 0))
|
||||||
|
|
||||||
painter = QPainter(pixmap)
|
painter = QPainter(pixmap)
|
||||||
painter.setRenderHint(QPainter.Antialiasing)
|
painter.setRenderHint(QPainter.Antialiasing)
|
||||||
|
|||||||
@@ -74,15 +74,9 @@ class ToHtml(Tokenizer):
|
|||||||
# Setters
|
# Setters
|
||||||
##
|
##
|
||||||
|
|
||||||
def setPreview(self, doComments: bool, doSynopsis: bool) -> None:
|
def setPreview(self, state: bool) -> None:
|
||||||
"""If we're using this class to generate markdown preview, we
|
"""Set to preview generator mode."""
|
||||||
need to make a few changes to formatting, which is managed by
|
self._genMode = self.M_PREVIEW if state else self.M_EXPORT
|
||||||
these flags.
|
|
||||||
"""
|
|
||||||
self._genMode = self.M_PREVIEW
|
|
||||||
self._doKeywords = True
|
|
||||||
self._doComments = doComments
|
|
||||||
self._doSynopsis = doSynopsis
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def setStyles(self, cssStyles: bool) -> None:
|
def setStyles(self, cssStyles: bool) -> None:
|
||||||
@@ -175,6 +169,8 @@ class ToHtml(Tokenizer):
|
|||||||
pStyle = None
|
pStyle = None
|
||||||
lines = []
|
lines = []
|
||||||
|
|
||||||
|
tHandle = self._handle
|
||||||
|
|
||||||
for tType, nHead, tText, tFormat, tStyle in self._tokens:
|
for tType, nHead, tText, tFormat, tStyle in self._tokens:
|
||||||
|
|
||||||
# Replace < and > with HTML entities
|
# Replace < and > with HTML entities
|
||||||
@@ -233,8 +229,8 @@ class ToHtml(Tokenizer):
|
|||||||
else:
|
else:
|
||||||
hStyle = ""
|
hStyle = ""
|
||||||
|
|
||||||
if self._linkHeaders:
|
if self._linkHeadings and tHandle:
|
||||||
aNm = f"<a name='T{nHead:04d}'></a>"
|
aNm = f"<a name='{tHandle}:T{nHead:04d}'></a>"
|
||||||
else:
|
else:
|
||||||
aNm = ""
|
aNm = ""
|
||||||
|
|
||||||
@@ -302,8 +298,7 @@ class ToHtml(Tokenizer):
|
|||||||
lines.append(tTemp)
|
lines.append(tTemp)
|
||||||
|
|
||||||
self._result = "".join(lines)
|
self._result = "".join(lines)
|
||||||
if self._genMode != self.M_PREVIEW:
|
self._fullHTML.append(self._result)
|
||||||
self._fullHTML.append(self._result)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ from functools import partial
|
|||||||
|
|
||||||
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
from PyQt5.QtCore import QCoreApplication, QRegularExpression
|
||||||
|
|
||||||
from novelwriter.enum import nwComment, nwItemLayout
|
|
||||||
from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
|
from novelwriter.common import formatTimeStamp, numberToRoman, checkInt
|
||||||
from novelwriter.constants import (
|
from novelwriter.constants import (
|
||||||
nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst
|
nwHeadFmt, nwKeyWords, nwLabels, nwRegEx, nwShortcode, nwUnicode, trConst
|
||||||
)
|
)
|
||||||
from novelwriter.core.index import processComment
|
from novelwriter.core.index import processComment
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
|
from novelwriter.enum import nwComment, nwItemLayout
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -118,15 +118,16 @@ class Tokenizer(ABC):
|
|||||||
|
|
||||||
# Data Variables
|
# Data Variables
|
||||||
self._text = "" # The raw text to be tokenized
|
self._text = "" # The raw text to be tokenized
|
||||||
self._nwItem = None # The NWItem currently being processed
|
self._handle = None # The item handle currently being processed
|
||||||
self._result = "" # The result of the last document
|
self._result = "" # The result of the last document
|
||||||
self._counts = {} # Counter data
|
|
||||||
|
|
||||||
self._keepMarkdown = False # Whether to keep the markdown text
|
self._keepMarkdown = False # Whether to keep the markdown text
|
||||||
self._allMarkdown = [] # The result novelWriter markdown of all documents
|
self._allMarkdown = [] # The result novelWriter markdown of all documents
|
||||||
|
|
||||||
# Processed Tokens
|
# Processed Tokens and Meta Data
|
||||||
self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = []
|
self._tokens: list[tuple[int, int, str, list[tuple[int, int]], int]] = []
|
||||||
|
self._counts: dict[str, int] = {}
|
||||||
|
self._outline: dict[str, str] = {}
|
||||||
|
|
||||||
# User Settings
|
# User Settings
|
||||||
self._textFont = "Serif" # Output text font
|
self._textFont = "Serif" # Output text font
|
||||||
@@ -165,7 +166,7 @@ class Tokenizer(ABC):
|
|||||||
self._hideHScene = False # Do not include hard scene headings
|
self._hideHScene = False # Do not include hard scene headings
|
||||||
self._hideSection = False # Do not include section headings
|
self._hideSection = False # Do not include section headings
|
||||||
|
|
||||||
self._linkHeaders = False # Add an anchor before headings
|
self._linkHeadings = False # Add an anchor before headings
|
||||||
|
|
||||||
self._titleStyle = self.A_CENTRE | self.A_PBB
|
self._titleStyle = self.A_CENTRE | self.A_PBB
|
||||||
self._chapterStyle = self.A_PBB
|
self._chapterStyle = self.A_PBB
|
||||||
@@ -176,9 +177,7 @@ class Tokenizer(ABC):
|
|||||||
self._noSep = True # Flag to indicate that we don't want a scene separator
|
self._noSep = True # Flag to indicate that we don't want a scene separator
|
||||||
|
|
||||||
# This File
|
# This File
|
||||||
self._isNone = False # Document has unknown layout
|
|
||||||
self._isNovel = False # Document is a novel document
|
self._isNovel = False # Document is a novel document
|
||||||
self._isNote = False # Document is a project note
|
|
||||||
self._isFirst = True # Document is the first in a set
|
self._isFirst = True # Document is the first in a set
|
||||||
|
|
||||||
# Error Handling
|
# Error Handling
|
||||||
@@ -228,6 +227,11 @@ class Tokenizer(ABC):
|
|||||||
"""The collected stats about the text."""
|
"""The collected stats about the text."""
|
||||||
return self._counts
|
return self._counts
|
||||||
|
|
||||||
|
@property
|
||||||
|
def textOutline(self) -> dict[str, str]:
|
||||||
|
"""The generated outline of the text."""
|
||||||
|
return self._outline
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def errData(self) -> list[str]:
|
def errData(self) -> list[str]:
|
||||||
"""The error data."""
|
"""The error data."""
|
||||||
@@ -353,7 +357,7 @@ class Tokenizer(ABC):
|
|||||||
|
|
||||||
def setLinkHeadings(self, state: bool) -> None:
|
def setLinkHeadings(self, state: bool) -> None:
|
||||||
"""Enable or disable adding an anchor before headings."""
|
"""Enable or disable adding an anchor before headings."""
|
||||||
self._linkHeaders = state
|
self._linkHeadings = state
|
||||||
return
|
return
|
||||||
|
|
||||||
def setBodyText(self, state: bool) -> None:
|
def setBodyText(self, state: bool) -> None:
|
||||||
@@ -394,47 +398,43 @@ class Tokenizer(ABC):
|
|||||||
def doConvert(self) -> None:
|
def doConvert(self) -> None:
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def addRootHeading(self, tHandle: str) -> bool:
|
def addRootHeading(self, tHandle: str) -> None:
|
||||||
"""Add a heading at the start of a new root folder."""
|
"""Add a heading at the start of a new root folder."""
|
||||||
tItem = self._project.tree[tHandle]
|
self._text = ""
|
||||||
if not tItem or not tItem.isRootType():
|
self._handle = None
|
||||||
return False
|
|
||||||
|
|
||||||
if self._isFirst:
|
if (tItem := self._project.tree[tHandle]) and tItem.isRootType():
|
||||||
textAlign = self.A_CENTRE
|
self._handle = tHandle
|
||||||
self._isFirst = False
|
if self._isFirst:
|
||||||
else:
|
textAlign = self.A_CENTRE
|
||||||
textAlign = self.A_PBB | self.A_CENTRE
|
self._isFirst = False
|
||||||
|
else:
|
||||||
|
textAlign = self.A_PBB | self.A_CENTRE
|
||||||
|
|
||||||
trNotes = self._localLookup("Notes")
|
trNotes = self._localLookup("Notes")
|
||||||
title = f"{trNotes}: {tItem.itemName}"
|
title = f"{trNotes}: {tItem.itemName}"
|
||||||
self._tokens = []
|
self._tokens = []
|
||||||
self._tokens.append((
|
self._tokens.append((
|
||||||
self.T_TITLE, 0, title, [], textAlign
|
self.T_TITLE, 1, title, [], textAlign
|
||||||
))
|
))
|
||||||
if self._keepMarkdown:
|
if self._keepMarkdown:
|
||||||
self._allMarkdown.append(f"# {title}\n\n")
|
self._allMarkdown.append(f"#! {title}\n\n")
|
||||||
|
|
||||||
return True
|
return
|
||||||
|
|
||||||
def setText(self, tHandle: str, text: str | None = None) -> bool:
|
def setText(self, tHandle: str, text: str | None = None) -> None:
|
||||||
"""Set the text for the tokenizer from a handle. If text is not
|
"""Set the text for the tokenizer from a handle. If text is not
|
||||||
set, load it from the file.
|
set, it's is loaded from the file.
|
||||||
"""
|
"""
|
||||||
self._nwItem = self._project.tree[tHandle]
|
self._text = ""
|
||||||
if self._nwItem is None:
|
self._handle = None
|
||||||
return False
|
if nwItem := self._project.tree[tHandle]:
|
||||||
|
if text is None:
|
||||||
if text is None:
|
text = self._project.storage.getDocument(tHandle).readDocument() or ""
|
||||||
text = self._project.storage.getDocument(tHandle).readDocument() or ""
|
self._text = text
|
||||||
|
self._handle = tHandle
|
||||||
self._text = text
|
self._isNovel = nwItem.itemLayout == nwItemLayout.DOCUMENT
|
||||||
|
return
|
||||||
self._isNone = self._nwItem.itemLayout == nwItemLayout.NO_LAYOUT
|
|
||||||
self._isNovel = self._nwItem.itemLayout == nwItemLayout.DOCUMENT
|
|
||||||
self._isNote = self._nwItem.itemLayout == nwItemLayout.NOTE
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
def doPreProcessing(self) -> None:
|
def doPreProcessing(self) -> None:
|
||||||
"""Run trough the various replace dictionaries."""
|
"""Run trough the various replace dictionaries."""
|
||||||
@@ -470,7 +470,7 @@ class Tokenizer(ABC):
|
|||||||
"""
|
"""
|
||||||
self._tokens = []
|
self._tokens = []
|
||||||
if self._isNovel:
|
if self._isNovel:
|
||||||
self._hFormatter.setHandle(self._nwItem.itemHandle if self._nwItem else None)
|
self._hFormatter.setHandle(self._handle)
|
||||||
|
|
||||||
nHead = 0
|
nHead = 0
|
||||||
breakNext = False
|
breakNext = False
|
||||||
@@ -803,7 +803,29 @@ class Tokenizer(ABC):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def countStats(self) -> dict[str, int]:
|
def buildOutline(self) -> None:
|
||||||
|
"""Build an outline of the text up to level 3 headings."""
|
||||||
|
tHandle = self._handle or ""
|
||||||
|
isNovel = self._isNovel
|
||||||
|
for tType, nHead, tText, _, _ in self._tokens:
|
||||||
|
if tType == self.T_TITLE:
|
||||||
|
prefix = "TT"
|
||||||
|
elif tType == self.T_HEAD1:
|
||||||
|
prefix = "PT" if isNovel else "H1"
|
||||||
|
elif tType == self.T_HEAD2:
|
||||||
|
prefix = "CH" if isNovel else "H2"
|
||||||
|
elif tType == self.T_HEAD3:
|
||||||
|
prefix = "SC" if isNovel else "H3"
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = f"{tHandle}:T{nHead:04d}"
|
||||||
|
text = tText.replace(nwHeadFmt.BR, " ").replace("&", "&")
|
||||||
|
self._outline[key] = f"{prefix}|{text}"
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def countStats(self) -> None:
|
||||||
"""Count stats on the tokenized text."""
|
"""Count stats on the tokenized text."""
|
||||||
titleCount = self._counts.get("titleCount", 0)
|
titleCount = self._counts.get("titleCount", 0)
|
||||||
paragraphCount = self._counts.get("paragraphCount", 0)
|
paragraphCount = self._counts.get("paragraphCount", 0)
|
||||||
@@ -910,7 +932,7 @@ class Tokenizer(ABC):
|
|||||||
self._counts["textWordChars"] = textWordChars
|
self._counts["textWordChars"] = textWordChars
|
||||||
self._counts["titleWordChars"] = titleWordChars
|
self._counts["titleWordChars"] = titleWordChars
|
||||||
|
|
||||||
return {}
|
return
|
||||||
|
|
||||||
def saveRawMarkdown(self, path: str | Path) -> None:
|
def saveRawMarkdown(self, path: str | Path) -> None:
|
||||||
"""Save the raw text to a plain text file."""
|
"""Save the raw text to a plain text file."""
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import logging
|
|||||||
|
|
||||||
from typing import TYPE_CHECKING, Literal, overload
|
from typing import TYPE_CHECKING, Literal, overload
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections.abc import Generator, Iterator
|
from collections.abc import Iterable, Iterator
|
||||||
|
|
||||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||||
from novelwriter.error import logException
|
from novelwriter.error import logException
|
||||||
@@ -387,7 +387,7 @@ class NWTree:
|
|||||||
rootClasses.add(nwItem.itemClass)
|
rootClasses.add(nwItem.itemClass)
|
||||||
return rootClasses
|
return rootClasses
|
||||||
|
|
||||||
def iterRoots(self, itemClass: nwItemClass | None) -> Generator[tuple[str, NWItem]]:
|
def iterRoots(self, itemClass: nwItemClass | None) -> Iterable[tuple[str, NWItem]]:
|
||||||
"""Iterate over all root items of a given class in order."""
|
"""Iterate over all root items of a given class in order."""
|
||||||
for tHandle in self._order:
|
for tHandle in self._order:
|
||||||
nwItem = self.__getitem__(tHandle)
|
nwItem = self.__getitem__(tHandle)
|
||||||
|
|||||||
@@ -25,9 +25,9 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import QSize, Qt
|
||||||
from PyQt5.QtGui import QWheelEvent
|
from PyQt5.QtGui import QWheelEvent
|
||||||
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QWidget
|
from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget
|
||||||
|
|
||||||
|
|
||||||
class NComboBox(QComboBox):
|
class NComboBox(QComboBox):
|
||||||
@@ -85,3 +85,15 @@ class NDoubleSpinBox(QDoubleSpinBox):
|
|||||||
return
|
return
|
||||||
|
|
||||||
# END Class NDoubleSpinBox
|
# END Class NDoubleSpinBox
|
||||||
|
|
||||||
|
|
||||||
|
class NIconToolButton(QToolButton):
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget, iconSize: int) -> None:
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
self.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
||||||
|
self.setIconSize(QSize(iconSize, iconSize))
|
||||||
|
self.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class NIconToolButton
|
||||||
|
|||||||
@@ -53,15 +53,17 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
|
|
||||||
from novelwriter.common import minmax, transferCase
|
from novelwriter.common import minmax, transferCase
|
||||||
from novelwriter.constants import nwKeyWords, nwShortcode, nwUnicode
|
from novelwriter.constants import nwKeyWords, nwShortcode, nwUnicode
|
||||||
from novelwriter.tools.lipsum import GuiLipsum
|
|
||||||
from novelwriter.core.document import NWDocument
|
from novelwriter.core.document import NWDocument
|
||||||
from novelwriter.text.counting import standardCounter
|
from novelwriter.enum import nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwTrinary
|
||||||
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
||||||
|
from novelwriter.extensions.modified import NIconToolButton
|
||||||
from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
|
from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE
|
||||||
from novelwriter.gui.editordocument import GuiTextDocument
|
from novelwriter.gui.editordocument import GuiTextDocument
|
||||||
from novelwriter.extensions.eventfilters import WheelEventFilter
|
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
||||||
|
from novelwriter.text.counting import standardCounter
|
||||||
|
from novelwriter.tools.lipsum import GuiLipsum
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -2805,10 +2807,8 @@ class GuiDocEditHeader(QWidget):
|
|||||||
self._docHandle = None
|
self._docHandle = None
|
||||||
self._docOutline: dict[int, str] = {}
|
self._docOutline: dict[int, str] = {}
|
||||||
|
|
||||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
iPx = SHARED.theme.baseIconSize
|
||||||
mPx = CONFIG.pxInt(8)
|
mPx = CONFIG.pxInt(4)
|
||||||
hSp = CONFIG.pxInt(6)
|
|
||||||
iconSize = QSize(fPx, fPx)
|
|
||||||
|
|
||||||
# Main Widget Settings
|
# Main Widget Settings
|
||||||
self.setAutoFillBackground(True)
|
self.setAutoFillBackground(True)
|
||||||
@@ -2820,7 +2820,7 @@ class GuiDocEditHeader(QWidget):
|
|||||||
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
||||||
self.itemTitle.setAutoFillBackground(True)
|
self.itemTitle.setAutoFillBackground(True)
|
||||||
self.itemTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
|
self.itemTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
|
||||||
self.itemTitle.setFixedHeight(fPx)
|
self.itemTitle.setFixedHeight(iPx)
|
||||||
|
|
||||||
lblFont = self.itemTitle.font()
|
lblFont = self.itemTitle.font()
|
||||||
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||||
@@ -2830,63 +2830,44 @@ class GuiDocEditHeader(QWidget):
|
|||||||
self.outlineMenu = QMenu(self)
|
self.outlineMenu = QMenu(self)
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.tbButton = QToolButton(self)
|
self.tbButton = NIconToolButton(self, iPx)
|
||||||
self.tbButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.tbButton.setIconSize(iconSize)
|
|
||||||
self.tbButton.setFixedSize(fPx, fPx)
|
|
||||||
self.tbButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.tbButton.setVisible(False)
|
self.tbButton.setVisible(False)
|
||||||
self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
|
self.tbButton.setToolTip(self.tr("Toggle Tool Bar"))
|
||||||
self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
|
self.tbButton.clicked.connect(lambda: self.toggleToolBarRequest.emit())
|
||||||
|
|
||||||
self.outlineButton = QToolButton(self)
|
self.outlineButton = NIconToolButton(self, iPx)
|
||||||
self.outlineButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.outlineButton.setIconSize(iconSize)
|
|
||||||
self.outlineButton.setFixedSize(fPx, fPx)
|
|
||||||
self.outlineButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.outlineButton.setVisible(False)
|
self.outlineButton.setVisible(False)
|
||||||
self.outlineButton.setToolTip(self.tr("Outline"))
|
self.outlineButton.setToolTip(self.tr("Outline"))
|
||||||
self.outlineButton.setMenu(self.outlineMenu)
|
self.outlineButton.setMenu(self.outlineMenu)
|
||||||
self.outlineButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
|
||||||
|
|
||||||
self.searchButton = QToolButton(self)
|
self.searchButton = NIconToolButton(self, iPx)
|
||||||
self.searchButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.searchButton.setIconSize(iconSize)
|
|
||||||
self.searchButton.setFixedSize(fPx, fPx)
|
|
||||||
self.searchButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.searchButton.setVisible(False)
|
self.searchButton.setVisible(False)
|
||||||
self.searchButton.setToolTip(self.tr("Search"))
|
self.searchButton.setToolTip(self.tr("Search"))
|
||||||
self.searchButton.clicked.connect(self.docEditor.toggleSearch)
|
self.searchButton.clicked.connect(self.docEditor.toggleSearch)
|
||||||
|
|
||||||
self.minmaxButton = QToolButton(self)
|
self.minmaxButton = NIconToolButton(self, iPx)
|
||||||
self.minmaxButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.minmaxButton.setIconSize(iconSize)
|
|
||||||
self.minmaxButton.setFixedSize(fPx, fPx)
|
|
||||||
self.minmaxButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.minmaxButton.setVisible(False)
|
self.minmaxButton.setVisible(False)
|
||||||
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
|
self.minmaxButton.setToolTip(self.tr("Toggle Focus Mode"))
|
||||||
self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
|
self.minmaxButton.clicked.connect(lambda: self.docEditor.toggleFocusModeRequest.emit())
|
||||||
|
|
||||||
self.closeButton = QToolButton(self)
|
self.closeButton = NIconToolButton(self, iPx)
|
||||||
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.closeButton.setIconSize(iconSize)
|
|
||||||
self.closeButton.setFixedSize(fPx, fPx)
|
|
||||||
self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.closeButton.setVisible(False)
|
self.closeButton.setVisible(False)
|
||||||
self.closeButton.setToolTip(self.tr("Close"))
|
self.closeButton.setToolTip(self.tr("Close"))
|
||||||
self.closeButton.clicked.connect(self._closeDocument)
|
self.closeButton.clicked.connect(self._closeDocument)
|
||||||
|
|
||||||
# Assemble Layout
|
# Assemble Layout
|
||||||
self.outerBox = QHBoxLayout()
|
self.outerBox = QHBoxLayout()
|
||||||
self.outerBox.setSpacing(hSp)
|
|
||||||
self.outerBox.addWidget(self.tbButton, 0)
|
self.outerBox.addWidget(self.tbButton, 0)
|
||||||
self.outerBox.addWidget(self.outlineButton, 0)
|
self.outerBox.addWidget(self.outlineButton, 0)
|
||||||
self.outerBox.addWidget(self.searchButton, 0)
|
self.outerBox.addWidget(self.searchButton, 0)
|
||||||
|
self.outerBox.addSpacing(mPx)
|
||||||
self.outerBox.addWidget(self.itemTitle, 1)
|
self.outerBox.addWidget(self.itemTitle, 1)
|
||||||
self.outerBox.addSpacing(fPx + hSp)
|
self.outerBox.addSpacing(mPx)
|
||||||
|
self.outerBox.addSpacing(iPx)
|
||||||
self.outerBox.addWidget(self.minmaxButton, 0)
|
self.outerBox.addWidget(self.minmaxButton, 0)
|
||||||
self.outerBox.addWidget(self.closeButton, 0)
|
self.outerBox.addWidget(self.closeButton, 0)
|
||||||
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
||||||
|
self.outerBox.setSpacing(0)
|
||||||
|
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
@@ -2896,7 +2877,7 @@ class GuiDocEditHeader(QWidget):
|
|||||||
# Fix Margins and Size
|
# Fix Margins and Size
|
||||||
# This is needed for high DPI systems. See issue #499.
|
# This is needed for high DPI systems. See issue #499.
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
self.setMinimumHeight(fPx + 2*mPx)
|
self.setMinimumHeight(iPx + 2*mPx)
|
||||||
|
|
||||||
self.updateTheme()
|
self.updateTheme()
|
||||||
|
|
||||||
@@ -2944,15 +2925,9 @@ class GuiDocEditHeader(QWidget):
|
|||||||
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
|
self.minmaxButton.setIcon(SHARED.theme.getIcon("maximise"))
|
||||||
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
||||||
|
|
||||||
colText = SHARED.theme.colText
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
||||||
buttonStyle = (
|
|
||||||
"QToolButton {{border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
|
||||||
).format(colText.red(), colText.green(), colText.blue())
|
|
||||||
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
|
||||||
|
|
||||||
self.tbButton.setStyleSheet(buttonStyle)
|
self.tbButton.setStyleSheet(buttonStyle)
|
||||||
self.outlineButton.setStyleSheet(buttonStyleMenu)
|
self.outlineButton.setStyleSheet(buttonStyle)
|
||||||
self.searchButton.setStyleSheet(buttonStyle)
|
self.searchButton.setStyleSheet(buttonStyle)
|
||||||
self.minmaxButton.setStyleSheet(buttonStyle)
|
self.minmaxButton.setStyleSheet(buttonStyle)
|
||||||
self.closeButton.setStyleSheet(buttonStyle)
|
self.closeButton.setStyleSheet(buttonStyle)
|
||||||
|
|||||||
@@ -41,11 +41,13 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
|
|
||||||
from novelwriter.error import logException
|
|
||||||
from novelwriter.constants import nwHeaders, nwUnicode
|
from novelwriter.constants import nwHeaders, nwUnicode
|
||||||
from novelwriter.core.tohtml import ToHtml
|
from novelwriter.core.tohtml import ToHtml
|
||||||
|
from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
|
||||||
|
from novelwriter.error import logException
|
||||||
from novelwriter.extensions.eventfilters import WheelEventFilter
|
from novelwriter.extensions.eventfilters import WheelEventFilter
|
||||||
|
from novelwriter.extensions.modified import NIconToolButton
|
||||||
|
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -195,7 +197,10 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
|
|
||||||
sPos = self.verticalScrollBar().value()
|
sPos = self.verticalScrollBar().value()
|
||||||
aDoc = ToHtml(SHARED.project)
|
aDoc = ToHtml(SHARED.project)
|
||||||
aDoc.setPreview(CONFIG.viewComments, CONFIG.viewSynopsis)
|
aDoc.setPreview(True)
|
||||||
|
aDoc.setKeywords(True)
|
||||||
|
aDoc.setComments(CONFIG.viewComments)
|
||||||
|
aDoc.setSynopsis(CONFIG.viewSynopsis)
|
||||||
aDoc.setLinkHeadings(True)
|
aDoc.setLinkHeadings(True)
|
||||||
|
|
||||||
# Be extra careful here to prevent crashes when first opening a
|
# Be extra careful here to prevent crashes when first opening a
|
||||||
@@ -628,12 +633,10 @@ class GuiDocViewHeader(QWidget):
|
|||||||
|
|
||||||
# Internal Variables
|
# Internal Variables
|
||||||
self._docHandle = None
|
self._docHandle = None
|
||||||
self._docOutline: dict[int, tuple[str, int]] = {}
|
self._docOutline: dict[str, tuple[str, int]] = {}
|
||||||
|
|
||||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
iPx = SHARED.theme.baseIconSize
|
||||||
hSp = CONFIG.pxInt(6)
|
mPx = CONFIG.pxInt(4)
|
||||||
mPx = CONFIG.pxInt(8)
|
|
||||||
iconSize = QSize(fPx, fPx)
|
|
||||||
|
|
||||||
# Main Widget Settings
|
# Main Widget Settings
|
||||||
self.setAutoFillBackground(True)
|
self.setAutoFillBackground(True)
|
||||||
@@ -646,7 +649,7 @@ class GuiDocViewHeader(QWidget):
|
|||||||
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
self.itemTitle.setContentsMargins(0, 0, 0, 0)
|
||||||
self.itemTitle.setAutoFillBackground(True)
|
self.itemTitle.setAutoFillBackground(True)
|
||||||
self.itemTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
|
self.itemTitle.setAlignment(Qt.AlignmentFlag.AlignHCenter | Qt.AlignmentFlag.AlignTop)
|
||||||
self.itemTitle.setFixedHeight(fPx)
|
self.itemTitle.setFixedHeight(iPx)
|
||||||
|
|
||||||
lblFont = self.itemTitle.font()
|
lblFont = self.itemTitle.font()
|
||||||
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
lblFont.setPointSizeF(0.9*SHARED.theme.fontPointSize)
|
||||||
@@ -656,62 +659,43 @@ class GuiDocViewHeader(QWidget):
|
|||||||
self.outlineMenu = QMenu(self)
|
self.outlineMenu = QMenu(self)
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.outlineButton = QToolButton(self)
|
self.outlineButton = NIconToolButton(self, iPx)
|
||||||
self.outlineButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.outlineButton.setIconSize(iconSize)
|
|
||||||
self.outlineButton.setFixedSize(fPx, fPx)
|
|
||||||
self.outlineButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.outlineButton.setVisible(False)
|
self.outlineButton.setVisible(False)
|
||||||
self.outlineButton.setToolTip(self.tr("Outline"))
|
self.outlineButton.setToolTip(self.tr("Outline"))
|
||||||
self.outlineButton.setMenu(self.outlineMenu)
|
self.outlineButton.setMenu(self.outlineMenu)
|
||||||
self.outlineButton.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
|
||||||
|
|
||||||
self.backButton = QToolButton(self)
|
self.backButton = NIconToolButton(self, iPx)
|
||||||
self.backButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.backButton.setIconSize(iconSize)
|
|
||||||
self.backButton.setFixedSize(fPx, fPx)
|
|
||||||
self.backButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.backButton.setVisible(False)
|
self.backButton.setVisible(False)
|
||||||
self.backButton.setToolTip(self.tr("Go Backward"))
|
self.backButton.setToolTip(self.tr("Go Backward"))
|
||||||
self.backButton.clicked.connect(self.docViewer.navBackward)
|
self.backButton.clicked.connect(self.docViewer.navBackward)
|
||||||
|
|
||||||
self.forwardButton = QToolButton(self)
|
self.forwardButton = NIconToolButton(self, iPx)
|
||||||
self.forwardButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.forwardButton.setIconSize(iconSize)
|
|
||||||
self.forwardButton.setFixedSize(fPx, fPx)
|
|
||||||
self.forwardButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.forwardButton.setVisible(False)
|
self.forwardButton.setVisible(False)
|
||||||
self.forwardButton.setToolTip(self.tr("Go Forward"))
|
self.forwardButton.setToolTip(self.tr("Go Forward"))
|
||||||
self.forwardButton.clicked.connect(self.docViewer.navForward)
|
self.forwardButton.clicked.connect(self.docViewer.navForward)
|
||||||
|
|
||||||
self.refreshButton = QToolButton(self)
|
self.refreshButton = NIconToolButton(self, iPx)
|
||||||
self.refreshButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.refreshButton.setIconSize(iconSize)
|
|
||||||
self.refreshButton.setFixedSize(fPx, fPx)
|
|
||||||
self.refreshButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.refreshButton.setVisible(False)
|
self.refreshButton.setVisible(False)
|
||||||
self.refreshButton.setToolTip(self.tr("Reload"))
|
self.refreshButton.setToolTip(self.tr("Reload"))
|
||||||
self.refreshButton.clicked.connect(self._refreshDocument)
|
self.refreshButton.clicked.connect(self._refreshDocument)
|
||||||
|
|
||||||
self.closeButton = QToolButton(self)
|
self.closeButton = NIconToolButton(self, iPx)
|
||||||
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
|
||||||
self.closeButton.setIconSize(iconSize)
|
|
||||||
self.closeButton.setFixedSize(fPx, fPx)
|
|
||||||
self.closeButton.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.closeButton.setVisible(False)
|
self.closeButton.setVisible(False)
|
||||||
self.closeButton.setToolTip(self.tr("Close"))
|
self.closeButton.setToolTip(self.tr("Close"))
|
||||||
self.closeButton.clicked.connect(self._closeDocument)
|
self.closeButton.clicked.connect(self._closeDocument)
|
||||||
|
|
||||||
# Assemble Layout
|
# Assemble Layout
|
||||||
self.outerBox = QHBoxLayout()
|
self.outerBox = QHBoxLayout()
|
||||||
self.outerBox.setSpacing(hSp)
|
|
||||||
self.outerBox.addWidget(self.outlineButton, 0)
|
self.outerBox.addWidget(self.outlineButton, 0)
|
||||||
self.outerBox.addWidget(self.backButton, 0)
|
self.outerBox.addWidget(self.backButton, 0)
|
||||||
self.outerBox.addWidget(self.forwardButton, 0)
|
self.outerBox.addWidget(self.forwardButton, 0)
|
||||||
|
self.outerBox.addSpacing(mPx)
|
||||||
self.outerBox.addWidget(self.itemTitle, 1)
|
self.outerBox.addWidget(self.itemTitle, 1)
|
||||||
self.outerBox.addSpacing(fPx + hSp)
|
self.outerBox.addSpacing(mPx)
|
||||||
|
self.outerBox.addSpacing(iPx)
|
||||||
self.outerBox.addWidget(self.refreshButton, 0)
|
self.outerBox.addWidget(self.refreshButton, 0)
|
||||||
self.outerBox.addWidget(self.closeButton, 0)
|
self.outerBox.addWidget(self.closeButton, 0)
|
||||||
|
self.outerBox.setSpacing(0)
|
||||||
|
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
@@ -719,7 +703,7 @@ class GuiDocViewHeader(QWidget):
|
|||||||
# This is needed for high DPI systems. See issue #499.
|
# This is needed for high DPI systems. See issue #499.
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
||||||
self.setMinimumHeight(fPx + 2*mPx)
|
self.setMinimumHeight(iPx + 2*mPx)
|
||||||
|
|
||||||
# Fix the Colours
|
# Fix the Colours
|
||||||
self.updateTheme()
|
self.updateTheme()
|
||||||
@@ -746,9 +730,10 @@ class GuiDocViewHeader(QWidget):
|
|||||||
self.refreshButton.setVisible(False)
|
self.refreshButton.setVisible(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setOutline(self, data: dict[int, tuple[str, int]]) -> None:
|
def setOutline(self, data: dict[str, tuple[str, int]]) -> None:
|
||||||
"""Set the document outline dataset."""
|
"""Set the document outline dataset."""
|
||||||
if data != self._docOutline:
|
tHandle = self._docHandle
|
||||||
|
if data != self._docOutline and tHandle:
|
||||||
self.outlineMenu.clear()
|
self.outlineMenu.clear()
|
||||||
entries = []
|
entries = []
|
||||||
minLevel = 5
|
minLevel = 5
|
||||||
@@ -760,7 +745,7 @@ class GuiDocViewHeader(QWidget):
|
|||||||
indent = " "*(level - minLevel)
|
indent = " "*(level - minLevel)
|
||||||
action = self.outlineMenu.addAction(f"{indent}{text}")
|
action = self.outlineMenu.addAction(f"{indent}{text}")
|
||||||
action.triggered.connect(
|
action.triggered.connect(
|
||||||
lambda _, title=title: self.docViewer.navigateTo(f"#{title}")
|
lambda _, title=title: self.docViewer.navigateTo(f"#{tHandle}:{title}")
|
||||||
)
|
)
|
||||||
self._docOutline = data
|
self._docOutline = data
|
||||||
return
|
return
|
||||||
@@ -773,14 +758,8 @@ class GuiDocViewHeader(QWidget):
|
|||||||
self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
|
self.refreshButton.setIcon(SHARED.theme.getIcon("refresh"))
|
||||||
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
self.closeButton.setIcon(SHARED.theme.getIcon("close"))
|
||||||
|
|
||||||
colText = SHARED.theme.colText
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
||||||
buttonStyle = (
|
self.outlineButton.setStyleSheet(buttonStyle)
|
||||||
"QToolButton {{border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
|
||||||
).format(colText.red(), colText.green(), colText.blue())
|
|
||||||
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
|
||||||
|
|
||||||
self.outlineButton.setStyleSheet(buttonStyleMenu)
|
|
||||||
self.backButton.setStyleSheet(buttonStyle)
|
self.backButton.setStyleSheet(buttonStyle)
|
||||||
self.forwardButton.setStyleSheet(buttonStyle)
|
self.forwardButton.setStyleSheet(buttonStyle)
|
||||||
self.refreshButton.setStyleSheet(buttonStyle)
|
self.refreshButton.setStyleSheet(buttonStyle)
|
||||||
@@ -878,17 +857,16 @@ class GuiDocViewFooter(QWidget):
|
|||||||
# Internal Variables
|
# Internal Variables
|
||||||
self._docHandle = None
|
self._docHandle = None
|
||||||
|
|
||||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
iPx = SHARED.theme.baseIconSize
|
||||||
hSp = CONFIG.pxInt(4)
|
hSp = CONFIG.pxInt(4)
|
||||||
|
mPx = CONFIG.pxInt(4)
|
||||||
|
|
||||||
# Main Widget Settings
|
# Main Widget Settings
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
self.setAutoFillBackground(True)
|
self.setAutoFillBackground(True)
|
||||||
|
|
||||||
# Show/Hide Details
|
# Show/Hide Details
|
||||||
self.showHide = QToolButton(self)
|
self.showHide = NIconToolButton(self, iPx)
|
||||||
self.showHide.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
self.showHide.setIconSize(QSize(fPx, fPx))
|
|
||||||
self.showHide.clicked.connect(lambda: self.docViewer.togglePanelVisibility.emit())
|
self.showHide.clicked.connect(lambda: self.docViewer.togglePanelVisibility.emit())
|
||||||
self.showHide.setToolTip(self.tr("Show/Hide Viewer Panel"))
|
self.showHide.setToolTip(self.tr("Show/Hide Viewer Panel"))
|
||||||
|
|
||||||
@@ -898,7 +876,7 @@ class GuiDocViewFooter(QWidget):
|
|||||||
self.showComments.setCheckable(True)
|
self.showComments.setCheckable(True)
|
||||||
self.showComments.setChecked(CONFIG.viewComments)
|
self.showComments.setChecked(CONFIG.viewComments)
|
||||||
self.showComments.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
|
self.showComments.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
|
||||||
self.showComments.setIconSize(QSize(fPx, fPx))
|
self.showComments.setIconSize(QSize(iPx, iPx))
|
||||||
self.showComments.toggled.connect(self._doToggleComments)
|
self.showComments.toggled.connect(self._doToggleComments)
|
||||||
self.showComments.setToolTip(self.tr("Show Comments"))
|
self.showComments.setToolTip(self.tr("Show Comments"))
|
||||||
|
|
||||||
@@ -908,7 +886,7 @@ class GuiDocViewFooter(QWidget):
|
|||||||
self.showSynopsis.setCheckable(True)
|
self.showSynopsis.setCheckable(True)
|
||||||
self.showSynopsis.setChecked(CONFIG.viewSynopsis)
|
self.showSynopsis.setChecked(CONFIG.viewSynopsis)
|
||||||
self.showSynopsis.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
|
self.showSynopsis.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonTextBesideIcon)
|
||||||
self.showSynopsis.setIconSize(QSize(fPx, fPx))
|
self.showSynopsis.setIconSize(QSize(iPx, iPx))
|
||||||
self.showSynopsis.toggled.connect(self._doToggleSynopsis)
|
self.showSynopsis.toggled.connect(self._doToggleSynopsis)
|
||||||
self.showSynopsis.setToolTip(self.tr("Show Synopsis Comments"))
|
self.showSynopsis.setToolTip(self.tr("Show Synopsis Comments"))
|
||||||
|
|
||||||
@@ -928,10 +906,9 @@ class GuiDocViewFooter(QWidget):
|
|||||||
|
|
||||||
# Fix Margins and Size
|
# Fix Margins and Size
|
||||||
# This is needed for high DPI systems. See issue #499.
|
# This is needed for high DPI systems. See issue #499.
|
||||||
cM = CONFIG.pxInt(8)
|
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
self.outerBox.setContentsMargins(cM, cM, cM, cM)
|
self.outerBox.setContentsMargins(mPx, mPx, mPx, mPx)
|
||||||
self.setMinimumHeight(fPx + 2*cM)
|
self.setMinimumHeight(iPx + 2*mPx)
|
||||||
|
|
||||||
# Fix the Colours
|
# Fix the Colours
|
||||||
self.updateTheme()
|
self.updateTheme()
|
||||||
@@ -954,12 +931,7 @@ class GuiDocViewFooter(QWidget):
|
|||||||
self.showComments.setIcon(bulletIcon)
|
self.showComments.setIcon(bulletIcon)
|
||||||
self.showSynopsis.setIcon(bulletIcon)
|
self.showSynopsis.setIcon(bulletIcon)
|
||||||
|
|
||||||
colText = SHARED.theme.colText
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
||||||
buttonStyle = (
|
|
||||||
"QToolButton {{border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({0}, {1}, {2}, 0.2);}}"
|
|
||||||
).format(colText.red(), colText.green(), colText.blue())
|
|
||||||
|
|
||||||
self.showHide.setStyleSheet(buttonStyle)
|
self.showHide.setStyleSheet(buttonStyle)
|
||||||
self.showComments.setStyleSheet(buttonStyle)
|
self.showComments.setStyleSheet(buttonStyle)
|
||||||
self.showSynopsis.setStyleSheet(buttonStyle)
|
self.showSynopsis.setStyleSheet(buttonStyle)
|
||||||
|
|||||||
@@ -34,10 +34,11 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.enum import nwDocMode, nwItemClass
|
|
||||||
from novelwriter.common import checkInt
|
from novelwriter.common import checkInt
|
||||||
from novelwriter.constants import nwHeaders, nwLabels, nwLists, trConst
|
from novelwriter.constants import nwHeaders, nwLabels, nwLists, trConst
|
||||||
from novelwriter.core.index import IndexHeading, IndexItem
|
from novelwriter.core.index import IndexHeading, IndexItem
|
||||||
|
from novelwriter.enum import nwDocMode, nwItemClass
|
||||||
|
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -99,35 +100,14 @@ class GuiDocViewerPanel(QWidget):
|
|||||||
|
|
||||||
def updateTheme(self, updateTabs: bool = True) -> None:
|
def updateTheme(self, updateTabs: bool = True) -> None:
|
||||||
"""Update theme elements."""
|
"""Update theme elements."""
|
||||||
qPalette = self.palette()
|
|
||||||
mPx = CONFIG.pxInt(2)
|
|
||||||
vPx = CONFIG.pxInt(4)
|
|
||||||
hPx = CONFIG.pxInt(8)
|
|
||||||
hCol = qPalette.highlight().color()
|
|
||||||
fCol = qPalette.text().color()
|
|
||||||
|
|
||||||
buttonStyle = (
|
|
||||||
"QToolButton {{padding: {0}px; margin: 0 0 {1}px 0; border: none; "
|
|
||||||
"background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({2}, {3}, {4}, 0.2);}} "
|
|
||||||
"QToolButton::menu-indicator {{image: none;}} "
|
|
||||||
).format(mPx, mPx, fCol.red(), fCol.green(), fCol.blue())
|
|
||||||
self.optsButton.setIcon(SHARED.theme.getIcon("menu"))
|
self.optsButton.setIcon(SHARED.theme.getIcon("menu"))
|
||||||
self.optsButton.setStyleSheet(buttonStyle)
|
self.optsButton.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON))
|
||||||
|
self.mainTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS))
|
||||||
styleSheet = (
|
|
||||||
"QTabWidget::pane {{border: 0;}} "
|
|
||||||
"QTabWidget QTabBar::tab {{border: 0; padding: {0}px {1}px;}} "
|
|
||||||
"QTabWidget QTabBar::tab:selected {{color: rgb({2}, {3}, {4});}} "
|
|
||||||
).format(vPx, hPx, hCol.red(), hCol.green(), hCol.blue())
|
|
||||||
self.mainTabs.setStyleSheet(styleSheet)
|
|
||||||
self.updateHandle(self._lastHandle)
|
self.updateHandle(self._lastHandle)
|
||||||
|
|
||||||
if updateTabs:
|
if updateTabs:
|
||||||
self.tabBackRefs.updateTheme()
|
self.tabBackRefs.updateTheme()
|
||||||
for tab in self.kwTabs.values():
|
for tab in self.kwTabs.values():
|
||||||
tab.updateTheme()
|
tab.updateTheme()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def openProjectTasks(self) -> None:
|
def openProjectTasks(self) -> None:
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from collections.abc import Generator
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from time import time
|
from time import time
|
||||||
|
from collections.abc import Iterable
|
||||||
|
|
||||||
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextDocument
|
from PyQt5.QtGui import QTextBlock, QTextCursor, QTextDocument
|
||||||
from PyQt5.QtCore import QObject, pyqtSlot
|
from PyQt5.QtCore import QObject, pyqtSlot
|
||||||
@@ -114,7 +114,7 @@ class GuiTextDocument(QTextDocument):
|
|||||||
return word, cPos, cLen, SHARED.spelling.suggestWords(word)
|
return word, cPos, cLen, SHARED.spelling.suggestWords(word)
|
||||||
return "", -1, -1, []
|
return "", -1, -1, []
|
||||||
|
|
||||||
def iterBlockByType(self, cType: int, maxCount: int = 1000) -> Generator[QTextBlock]:
|
def iterBlockByType(self, cType: int, maxCount: int = 1000) -> Iterable[QTextBlock]:
|
||||||
"""Iterate over all text blocks of a given type."""
|
"""Iterate over all text blocks of a given type."""
|
||||||
count = 0
|
count = 0
|
||||||
for i in range(self.blockCount()):
|
for i in range(self.blockCount()):
|
||||||
|
|||||||
@@ -35,16 +35,18 @@ from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent
|
|||||||
from PyQt5.QtCore import QModelIndex, QPoint, Qt, QSize, pyqtSlot, pyqtSignal
|
from PyQt5.QtCore import QModelIndex, QPoint, Qt, QSize, pyqtSlot, pyqtSignal
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
|
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView,
|
||||||
QInputDialog, QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget,
|
QInputDialog, QMenu, QSizePolicy, QToolTip, QTreeWidget, QTreeWidgetItem,
|
||||||
QTreeWidgetItem, QVBoxLayout, QWidget
|
QVBoxLayout, QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
|
|
||||||
from novelwriter.common import minmax
|
from novelwriter.common import minmax
|
||||||
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
|
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
|
||||||
from novelwriter.core.index import IndexHeading
|
from novelwriter.core.index import IndexHeading
|
||||||
|
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
|
||||||
|
from novelwriter.extensions.modified import NIconToolButton
|
||||||
from novelwriter.extensions.novelselector import NovelSelector
|
from novelwriter.extensions.novelselector import NovelSelector
|
||||||
|
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -215,15 +217,13 @@ class GuiNovelToolBar(QWidget):
|
|||||||
self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||||
self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
|
self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot)
|
||||||
|
|
||||||
self.tbNovel = QToolButton(self)
|
self.tbNovel = NIconToolButton(self, iPx)
|
||||||
self.tbNovel.setToolTip(self.tr("Novel Root"))
|
self.tbNovel.setToolTip(self.tr("Novel Root"))
|
||||||
self.tbNovel.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbNovel.clicked.connect(self.novelValue.showPopup)
|
self.tbNovel.clicked.connect(self.novelValue.showPopup)
|
||||||
|
|
||||||
# Refresh Button
|
# Refresh Button
|
||||||
self.tbRefresh = QToolButton(self)
|
self.tbRefresh = NIconToolButton(self, iPx)
|
||||||
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
||||||
self.tbRefresh.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbRefresh.clicked.connect(self._refreshNovelTree)
|
self.tbRefresh.clicked.connect(self._refreshNovelTree)
|
||||||
|
|
||||||
# More Options Menu
|
# More Options Menu
|
||||||
@@ -241,11 +241,9 @@ class GuiNovelToolBar(QWidget):
|
|||||||
self.aLastColSize = self.mLastCol.addAction(self.tr("Column Size"))
|
self.aLastColSize = self.mLastCol.addAction(self.tr("Column Size"))
|
||||||
self.aLastColSize.triggered.connect(self._selectLastColumnSize)
|
self.aLastColSize.triggered.connect(self._selectLastColumnSize)
|
||||||
|
|
||||||
self.tbMore = QToolButton(self)
|
self.tbMore = NIconToolButton(self, iPx)
|
||||||
self.tbMore.setToolTip(self.tr("More Options"))
|
self.tbMore.setToolTip(self.tr("More Options"))
|
||||||
self.tbMore.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbMore.setMenu(self.mMore)
|
self.tbMore.setMenu(self.mMore)
|
||||||
self.tbMore.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
self.outerBox = QHBoxLayout()
|
self.outerBox = QHBoxLayout()
|
||||||
@@ -280,16 +278,10 @@ class GuiNovelToolBar(QWidget):
|
|||||||
self.setPalette(qPalette)
|
self.setPalette(qPalette)
|
||||||
|
|
||||||
# StyleSheets
|
# StyleSheets
|
||||||
fadeCol = qPalette.text().color()
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
||||||
buttonStyle = (
|
|
||||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
|
||||||
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
|
||||||
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
|
||||||
|
|
||||||
self.tbNovel.setStyleSheet(buttonStyle)
|
self.tbNovel.setStyleSheet(buttonStyle)
|
||||||
self.tbRefresh.setStyleSheet(buttonStyle)
|
self.tbRefresh.setStyleSheet(buttonStyle)
|
||||||
self.tbMore.setStyleSheet(buttonStyleMenu)
|
self.tbMore.setStyleSheet(buttonStyle)
|
||||||
|
|
||||||
self.novelValue.setStyleSheet(
|
self.novelValue.setStyleSheet(
|
||||||
"QComboBox {border-style: none; padding-left: 0;} "
|
"QComboBox {border-style: none; padding-left: 0;} "
|
||||||
|
|||||||
@@ -38,20 +38,22 @@ from PyQt5.QtGui import (
|
|||||||
from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
|
from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView,
|
||||||
QLabel, QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget,
|
QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem,
|
||||||
QTreeWidgetItem, QVBoxLayout, QWidget
|
QVBoxLayout, QWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
|
||||||
from novelwriter.common import minmax
|
from novelwriter.common import minmax
|
||||||
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
from novelwriter.constants import nwHeaders, nwUnicode, trConst, nwLabels
|
||||||
from novelwriter.core.item import NWItem
|
|
||||||
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
from novelwriter.core.coretools import DocDuplicator, DocMerger, DocSplitter
|
||||||
|
from novelwriter.core.item import NWItem
|
||||||
from novelwriter.dialogs.docmerge import GuiDocMerge
|
from novelwriter.dialogs.docmerge import GuiDocMerge
|
||||||
from novelwriter.dialogs.docsplit import GuiDocSplit
|
from novelwriter.dialogs.docsplit import GuiDocSplit
|
||||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||||
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
from novelwriter.dialogs.projectsettings import GuiProjectSettings
|
||||||
|
from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout
|
||||||
|
from novelwriter.extensions.modified import NIconToolButton
|
||||||
|
from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -275,22 +277,18 @@ class GuiProjectToolBar(QWidget):
|
|||||||
# Quick Links
|
# Quick Links
|
||||||
self.mQuick = QMenu(self)
|
self.mQuick = QMenu(self)
|
||||||
|
|
||||||
self.tbQuick = QToolButton(self)
|
self.tbQuick = NIconToolButton(self, iPx)
|
||||||
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
|
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
|
||||||
self.tbQuick.setShortcut("Ctrl+L")
|
self.tbQuick.setShortcut("Ctrl+L")
|
||||||
self.tbQuick.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbQuick.setMenu(self.mQuick)
|
self.tbQuick.setMenu(self.mQuick)
|
||||||
self.tbQuick.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
|
||||||
|
|
||||||
# Move Buttons
|
# Move Buttons
|
||||||
self.tbMoveU = QToolButton(self)
|
self.tbMoveU = NIconToolButton(self, iPx)
|
||||||
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
|
self.tbMoveU.setToolTip("%s [Ctrl+Up]" % self.tr("Move Up"))
|
||||||
self.tbMoveU.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1))
|
self.tbMoveU.clicked.connect(lambda: self.projTree.moveTreeItem(-1))
|
||||||
|
|
||||||
self.tbMoveD = QToolButton(self)
|
self.tbMoveD = NIconToolButton(self, iPx)
|
||||||
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
|
self.tbMoveD.setToolTip("%s [Ctrl+Down]" % self.tr("Move Down"))
|
||||||
self.tbMoveD.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
|
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
|
||||||
|
|
||||||
# Add Item Menu
|
# Add Item Menu
|
||||||
@@ -329,12 +327,10 @@ class GuiProjectToolBar(QWidget):
|
|||||||
self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
|
self.mAddRoot = self.mAdd.addMenu(trConst(nwLabels.ITEM_DESCRIPTION["root"]))
|
||||||
self._buildRootMenu()
|
self._buildRootMenu()
|
||||||
|
|
||||||
self.tbAdd = QToolButton(self)
|
self.tbAdd = NIconToolButton(self, iPx)
|
||||||
self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item"))
|
self.tbAdd.setToolTip("%s [Ctrl+N]" % self.tr("Add Item"))
|
||||||
self.tbAdd.setShortcut("Ctrl+N")
|
self.tbAdd.setShortcut("Ctrl+N")
|
||||||
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbAdd.setMenu(self.mAdd)
|
self.tbAdd.setMenu(self.mAdd)
|
||||||
self.tbAdd.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
|
||||||
|
|
||||||
# More Options Menu
|
# More Options Menu
|
||||||
self.mMore = QMenu(self)
|
self.mMore = QMenu(self)
|
||||||
@@ -348,11 +344,9 @@ class GuiProjectToolBar(QWidget):
|
|||||||
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
|
self.aEmptyTrash = self.mMore.addAction(self.tr("Empty Trash"))
|
||||||
self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash())
|
self.aEmptyTrash.triggered.connect(lambda: self.projTree.emptyTrash())
|
||||||
|
|
||||||
self.tbMore = QToolButton(self)
|
self.tbMore = NIconToolButton(self, iPx)
|
||||||
self.tbMore.setToolTip(self.tr("More Options"))
|
self.tbMore.setToolTip(self.tr("More Options"))
|
||||||
self.tbMore.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbMore.setMenu(self.mMore)
|
self.tbMore.setMenu(self.mMore)
|
||||||
self.tbMore.setPopupMode(QToolButton.ToolButtonPopupMode.InstantPopup)
|
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
self.outerBox = QHBoxLayout()
|
self.outerBox = QHBoxLayout()
|
||||||
@@ -382,18 +376,12 @@ class GuiProjectToolBar(QWidget):
|
|||||||
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
|
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
|
||||||
self.setPalette(qPalette)
|
self.setPalette(qPalette)
|
||||||
|
|
||||||
fadeCol = qPalette.text().color()
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
||||||
buttonStyle = (
|
self.tbQuick.setStyleSheet(buttonStyle)
|
||||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
|
||||||
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
|
||||||
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
|
||||||
|
|
||||||
self.tbQuick.setStyleSheet(buttonStyleMenu)
|
|
||||||
self.tbMoveU.setStyleSheet(buttonStyle)
|
self.tbMoveU.setStyleSheet(buttonStyle)
|
||||||
self.tbMoveD.setStyleSheet(buttonStyle)
|
self.tbMoveD.setStyleSheet(buttonStyle)
|
||||||
self.tbAdd.setStyleSheet(buttonStyleMenu)
|
self.tbAdd.setStyleSheet(buttonStyle)
|
||||||
self.tbMore.setStyleSheet(buttonStyleMenu)
|
self.tbMore.setStyleSheet(buttonStyle)
|
||||||
|
|
||||||
self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark"))
|
self.tbQuick.setIcon(SHARED.theme.getIcon("bookmark"))
|
||||||
self.tbMoveU.setIcon(SHARED.theme.getIcon("up"))
|
self.tbMoveU.setIcon(SHARED.theme.getIcon("up"))
|
||||||
|
|||||||
@@ -28,12 +28,14 @@ import logging
|
|||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
from PyQt5.QtGui import QPalette
|
from PyQt5.QtGui import QPalette
|
||||||
from PyQt5.QtCore import QEvent, QPoint, Qt, QSize, pyqtSignal
|
from PyQt5.QtCore import QEvent, QPoint, pyqtSignal
|
||||||
from PyQt5.QtWidgets import QMenu, QToolButton, QVBoxLayout, QWidget
|
from PyQt5.QtWidgets import QMenu, QVBoxLayout, QWidget
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.enum import nwView
|
from novelwriter.enum import nwView
|
||||||
from novelwriter.extensions.eventfilters import StatusTipFilter
|
from novelwriter.extensions.eventfilters import StatusTipFilter
|
||||||
|
from novelwriter.extensions.modified import NIconToolButton
|
||||||
|
from novelwriter.gui.theme import STYLES_BIG_TOOLBUTTON
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -53,46 +55,37 @@ class GuiSideBar(QWidget):
|
|||||||
self.mainGui = mainGui
|
self.mainGui = mainGui
|
||||||
|
|
||||||
iPx = CONFIG.pxInt(24)
|
iPx = CONFIG.pxInt(24)
|
||||||
iconSize = QSize(iPx, iPx)
|
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
self.installEventFilter(StatusTipFilter(mainGui))
|
self.installEventFilter(StatusTipFilter(mainGui))
|
||||||
|
|
||||||
# Buttons
|
# Buttons
|
||||||
self.tbProject = QToolButton(self)
|
self.tbProject = NIconToolButton(self, iPx)
|
||||||
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
|
self.tbProject.setToolTip("{0} [Ctrl+T]".format(self.tr("Project Tree View")))
|
||||||
self.tbProject.setIconSize(iconSize)
|
|
||||||
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
|
self.tbProject.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
|
||||||
|
|
||||||
self.tbNovel = QToolButton(self)
|
self.tbNovel = NIconToolButton(self, iPx)
|
||||||
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
|
self.tbNovel.setToolTip("{0} [Ctrl+T]".format(self.tr("Novel Tree View")))
|
||||||
self.tbNovel.setIconSize(iconSize)
|
|
||||||
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
|
self.tbNovel.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
|
||||||
|
|
||||||
self.tbOutline = QToolButton(self)
|
self.tbOutline = NIconToolButton(self, iPx)
|
||||||
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
|
self.tbOutline.setToolTip("{0} [Ctrl+Shift+T]".format(self.tr("Novel Outline View")))
|
||||||
self.tbOutline.setIconSize(iconSize)
|
|
||||||
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
|
self.tbOutline.clicked.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
|
||||||
|
|
||||||
self.tbBuild = QToolButton(self)
|
self.tbBuild = NIconToolButton(self, iPx)
|
||||||
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
|
self.tbBuild.setToolTip("{0} [F5]".format(self.tr("Build Manuscript")))
|
||||||
self.tbBuild.setIconSize(iconSize)
|
|
||||||
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
self.tbBuild.clicked.connect(self.mainGui.showBuildManuscriptDialog)
|
||||||
|
|
||||||
self.tbDetails = QToolButton(self)
|
self.tbDetails = NIconToolButton(self, iPx)
|
||||||
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
|
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
|
||||||
self.tbDetails.setIconSize(iconSize)
|
|
||||||
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
|
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
|
||||||
|
|
||||||
self.tbStats = QToolButton(self)
|
self.tbStats = NIconToolButton(self, iPx)
|
||||||
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
|
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
|
||||||
self.tbStats.setIconSize(iconSize)
|
|
||||||
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
|
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
|
||||||
|
|
||||||
# Settings Menu
|
# Settings Menu
|
||||||
self.tbSettings = QToolButton(self)
|
self.tbSettings = NIconToolButton(self, iPx)
|
||||||
self.tbSettings.setToolTip(self.tr("Settings"))
|
self.tbSettings.setToolTip(self.tr("Settings"))
|
||||||
self.tbSettings.setIconSize(iconSize)
|
|
||||||
self.tbSettings.setToolButtonStyle(Qt.ToolButtonStyle.ToolButtonIconOnly)
|
|
||||||
|
|
||||||
self.mSettings = _PopRightMenu(self.tbSettings)
|
self.mSettings = _PopRightMenu(self.tbSettings)
|
||||||
self.mSettings.addAction(self.mainGui.mainMenu.aEditWordList)
|
self.mSettings.addAction(self.mainGui.mainMenu.aEditWordList)
|
||||||
@@ -101,7 +94,6 @@ class GuiSideBar(QWidget):
|
|||||||
self.mSettings.addAction(self.mainGui.mainMenu.aPreferences)
|
self.mSettings.addAction(self.mainGui.mainMenu.aPreferences)
|
||||||
|
|
||||||
self.tbSettings.setMenu(self.mSettings)
|
self.tbSettings.setMenu(self.mSettings)
|
||||||
self.tbSettings.setPopupMode(QToolButton.InstantPopup)
|
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
self.outerBox = QVBoxLayout()
|
self.outerBox = QVBoxLayout()
|
||||||
@@ -129,12 +121,7 @@ class GuiSideBar(QWidget):
|
|||||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||||
self.setPalette(qPalette)
|
self.setPalette(qPalette)
|
||||||
|
|
||||||
fadeCol = qPalette.text().color()
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_BIG_TOOLBUTTON)
|
||||||
buttonStyle = (
|
|
||||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
|
||||||
).format(CONFIG.pxInt(6), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
|
||||||
buttonStyleMenu = f"{buttonStyle} QToolButton::menu-indicator {{image: none;}}"
|
|
||||||
|
|
||||||
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))
|
self.tbProject.setIcon(SHARED.theme.getIcon("view_editor"))
|
||||||
self.tbProject.setStyleSheet(buttonStyle)
|
self.tbProject.setStyleSheet(buttonStyle)
|
||||||
@@ -155,7 +142,7 @@ class GuiSideBar(QWidget):
|
|||||||
self.tbStats.setStyleSheet(buttonStyle)
|
self.tbStats.setStyleSheet(buttonStyle)
|
||||||
|
|
||||||
self.tbSettings.setIcon(SHARED.theme.getIcon("settings"))
|
self.tbSettings.setIcon(SHARED.theme.getIcon("settings"))
|
||||||
self.tbSettings.setStyleSheet(buttonStyleMenu)
|
self.tbSettings.setStyleSheet(buttonStyle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,10 @@ from novelwriter.constants import nwLabels
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
STYLES_FLAT_TABS = "flatTabWidget"
|
||||||
|
STYLES_MIN_TOOLBUTTON = "minimalToolButton"
|
||||||
|
STYLES_BIG_TOOLBUTTON = "bigToolButton"
|
||||||
|
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
# Gui Theme Class
|
# Gui Theme Class
|
||||||
@@ -120,6 +124,7 @@ class GuiTheme:
|
|||||||
self._syntaxList: list[tuple[str, str]] = []
|
self._syntaxList: list[tuple[str, str]] = []
|
||||||
self._availThemes: dict[str, Path] = {}
|
self._availThemes: dict[str, Path] = {}
|
||||||
self._availSyntax: dict[str, Path] = {}
|
self._availSyntax: dict[str, Path] = {}
|
||||||
|
self._styleSheets: dict[str, str] = {}
|
||||||
|
|
||||||
self._listConf(self._availSyntax, CONFIG.assetPath("syntax"))
|
self._listConf(self._availSyntax, CONFIG.assetPath("syntax"))
|
||||||
self._listConf(self._availThemes, CONFIG.assetPath("themes"))
|
self._listConf(self._availThemes, CONFIG.assetPath("themes"))
|
||||||
@@ -272,6 +277,9 @@ class GuiTheme:
|
|||||||
# Apply Styles
|
# Apply Styles
|
||||||
qApp.setPalette(self._guiPalette)
|
qApp.setPalette(self._guiPalette)
|
||||||
|
|
||||||
|
# Reset stylesheets so that they are regenerated
|
||||||
|
self._buildStyleSheets(self._guiPalette)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def loadSyntax(self) -> bool:
|
def loadSyntax(self) -> bool:
|
||||||
@@ -366,6 +374,10 @@ class GuiTheme:
|
|||||||
|
|
||||||
return self._syntaxList
|
return self._syntaxList
|
||||||
|
|
||||||
|
def getStyleSheet(self, name: str) -> str:
|
||||||
|
"""Load a standard style sheet."""
|
||||||
|
return self._styleSheets.get(name, "")
|
||||||
|
|
||||||
##
|
##
|
||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
@@ -412,6 +424,41 @@ class GuiTheme:
|
|||||||
self._guiPalette.setColor(value, self._parseColour(parser, section, name))
|
self._guiPalette.setColor(value, self._parseColour(parser, section, name))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def _buildStyleSheets(self, palette: QPalette) -> None:
|
||||||
|
"""Build default style sheets."""
|
||||||
|
self._styleSheets = {}
|
||||||
|
|
||||||
|
aPx = CONFIG.pxInt(2)
|
||||||
|
bPx = CONFIG.pxInt(4)
|
||||||
|
cPx = CONFIG.pxInt(6)
|
||||||
|
dPx = CONFIG.pxInt(8)
|
||||||
|
|
||||||
|
tCol = palette.text().color()
|
||||||
|
hCol = palette.highlight().color()
|
||||||
|
|
||||||
|
# Flat Tab Widget and Tab Bar:
|
||||||
|
self._styleSheets[STYLES_FLAT_TABS] = (
|
||||||
|
"QTabWidget::pane {{border: 0;}} "
|
||||||
|
"QTabWidget QTabBar::tab {{border: 0; padding: {0}px {1}px;}} "
|
||||||
|
"QTabWidget QTabBar::tab:selected {{color: rgb({2}, {3}, {4});}} "
|
||||||
|
).format(bPx, dPx, hCol.red(), hCol.green(), hCol.blue())
|
||||||
|
|
||||||
|
# Minimal Tool Button
|
||||||
|
self._styleSheets[STYLES_MIN_TOOLBUTTON] = (
|
||||||
|
"QToolButton {{padding: {0}px; margin: 0; border: none; background: transparent;}} "
|
||||||
|
"QToolButton:hover {{border: none; background: rgba({1}, {2}, {3}, 0.2);}} "
|
||||||
|
"QToolButton::menu-indicator {{image: none;}} "
|
||||||
|
).format(aPx, tCol.red(), tCol.green(), tCol.blue())
|
||||||
|
|
||||||
|
# Big Tool Button
|
||||||
|
self._styleSheets[STYLES_BIG_TOOLBUTTON] = (
|
||||||
|
"QToolButton {{padding: {0}px; margin: 0; border: none; background: transparent;}} "
|
||||||
|
"QToolButton:hover {{border: none; background: rgba({1}, {2}, {3}, 0.2);}} "
|
||||||
|
"QToolButton::menu-indicator {{image: none;}} "
|
||||||
|
).format(cPx, tCol.red(), tCol.green(), tCol.blue())
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
# End Class GuiTheme
|
# End Class GuiTheme
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -653,7 +653,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.ensureCursorVisibleNoCentre()
|
self.docEditor.ensureCursorVisibleNoCentre()
|
||||||
|
|
||||||
if sTitle:
|
if sTitle:
|
||||||
self.docViewer.navigateTo(f"#{sTitle}")
|
self.docViewer.navigateTo(f"#{tHandle}:{sTitle}")
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|||||||
@@ -31,25 +31,27 @@ from typing import TYPE_CHECKING
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent
|
from PyQt5.QtGui import QCloseEvent, QColor, QCursor, QFont, QPalette, QResizeEvent
|
||||||
from PyQt5.QtCore import QSize, QTimer, Qt, pyqtSlot
|
from PyQt5.QtCore import QSize, QTimer, QUrl, Qt, pyqtSignal, pyqtSlot
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QAbstractItemView, QDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
|
QAbstractItemView, QDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel,
|
||||||
QListWidget, QListWidgetItem, QPushButton, QSizePolicy, QSplitter,
|
QListWidget, QListWidgetItem, QPushButton, QSizePolicy, QSplitter,
|
||||||
QStackedWidget, QTextBrowser, QToolButton, QTreeWidget, QTreeWidgetItem,
|
QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, QTreeWidgetItem,
|
||||||
QVBoxLayout, QWidget, qApp
|
QVBoxLayout, QWidget, qApp
|
||||||
)
|
)
|
||||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
|
from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.error import logException
|
|
||||||
from novelwriter.common import checkInt, fuzzyTime
|
from novelwriter.common import checkInt, fuzzyTime
|
||||||
from novelwriter.core.tohtml import ToHtml
|
|
||||||
from novelwriter.core.docbuild import NWBuildDocument
|
|
||||||
from novelwriter.core.tokenizer import HeadingFormatter
|
|
||||||
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
|
from novelwriter.core.buildsettings import BuildCollection, BuildSettings
|
||||||
|
from novelwriter.core.docbuild import NWBuildDocument
|
||||||
|
from novelwriter.core.tohtml import ToHtml
|
||||||
|
from novelwriter.core.tokenizer import HeadingFormatter
|
||||||
|
from novelwriter.error import logException
|
||||||
|
from novelwriter.extensions.circularprogress import NProgressCircle
|
||||||
|
from novelwriter.extensions.modified import NIconToolButton
|
||||||
|
from novelwriter.gui.theme import STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON
|
||||||
from novelwriter.tools.manusbuild import GuiManuscriptBuild
|
from novelwriter.tools.manusbuild import GuiManuscriptBuild
|
||||||
from novelwriter.tools.manussettings import GuiBuildSettings
|
from novelwriter.tools.manussettings import GuiBuildSettings
|
||||||
from novelwriter.extensions.circularprogress import NProgressCircle
|
|
||||||
|
|
||||||
if TYPE_CHECKING: # pragma: no cover
|
if TYPE_CHECKING: # pragma: no cover
|
||||||
from novelwriter.guimain import GuiMain
|
from novelwriter.guimain import GuiMain
|
||||||
@@ -101,29 +103,22 @@ class GuiManuscript(QDialog):
|
|||||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||||
self.setPalette(qPalette)
|
self.setPalette(qPalette)
|
||||||
|
|
||||||
fadeCol = qPalette.text().color()
|
buttonStyle = SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON)
|
||||||
buttonStyle = (
|
|
||||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
|
||||||
).format(CONFIG.pxInt(2), fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
|
||||||
|
|
||||||
self.tbAdd = QToolButton(self)
|
self.tbAdd = NIconToolButton(self, iPx)
|
||||||
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
self.tbAdd.setIcon(SHARED.theme.getIcon("add"))
|
||||||
self.tbAdd.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbAdd.setToolTip(self.tr("Add New Build"))
|
self.tbAdd.setToolTip(self.tr("Add New Build"))
|
||||||
self.tbAdd.setStyleSheet(buttonStyle)
|
self.tbAdd.setStyleSheet(buttonStyle)
|
||||||
self.tbAdd.clicked.connect(self._createNewBuild)
|
self.tbAdd.clicked.connect(self._createNewBuild)
|
||||||
|
|
||||||
self.tbDel = QToolButton(self)
|
self.tbDel = NIconToolButton(self, iPx)
|
||||||
self.tbDel.setIcon(SHARED.theme.getIcon("remove"))
|
self.tbDel.setIcon(SHARED.theme.getIcon("remove"))
|
||||||
self.tbDel.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbDel.setToolTip(self.tr("Delete Selected Build"))
|
self.tbDel.setToolTip(self.tr("Delete Selected Build"))
|
||||||
self.tbDel.setStyleSheet(buttonStyle)
|
self.tbDel.setStyleSheet(buttonStyle)
|
||||||
self.tbDel.clicked.connect(self._deleteSelectedBuild)
|
self.tbDel.clicked.connect(self._deleteSelectedBuild)
|
||||||
|
|
||||||
self.tbEdit = QToolButton(self)
|
self.tbEdit = NIconToolButton(self, iPx)
|
||||||
self.tbEdit.setIcon(SHARED.theme.getIcon("edit"))
|
self.tbEdit.setIcon(SHARED.theme.getIcon("edit"))
|
||||||
self.tbEdit.setIconSize(QSize(iPx, iPx))
|
|
||||||
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
self.tbEdit.setToolTip(self.tr("Edit Selected Build"))
|
||||||
self.tbEdit.setStyleSheet(buttonStyle)
|
self.tbEdit.setStyleSheet(buttonStyle)
|
||||||
self.tbEdit.clicked.connect(self._editSelectedBuild)
|
self.tbEdit.clicked.connect(self._editSelectedBuild)
|
||||||
@@ -141,21 +136,31 @@ class GuiManuscript(QDialog):
|
|||||||
# Builds
|
# Builds
|
||||||
# ======
|
# ======
|
||||||
|
|
||||||
self.buildList = QListWidget()
|
self.buildList = QListWidget(self)
|
||||||
self.buildList.setIconSize(QSize(iPx, iPx))
|
self.buildList.setIconSize(QSize(iPx, iPx))
|
||||||
self.buildList.doubleClicked.connect(self._editSelectedBuild)
|
self.buildList.doubleClicked.connect(self._editSelectedBuild)
|
||||||
self.buildList.currentItemChanged.connect(self._updateBuildDetails)
|
self.buildList.currentItemChanged.connect(self._updateBuildDetails)
|
||||||
self.buildList.setSelectionMode(QAbstractItemView.SingleSelection)
|
self.buildList.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
self.buildList.setDragDropMode(QAbstractItemView.InternalMove)
|
self.buildList.setDragDropMode(QAbstractItemView.InternalMove)
|
||||||
|
|
||||||
|
# Details Tabs
|
||||||
|
# ============
|
||||||
|
|
||||||
self.buildDetails = _DetailsWidget(self)
|
self.buildDetails = _DetailsWidget(self)
|
||||||
self.buildDetails.setColumnWidth(
|
self.buildDetails.setColumnWidth(
|
||||||
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsWidth", 100)),
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsWidth", 100)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.buildOutline = _OutlineWidget(self)
|
||||||
|
|
||||||
|
self.detailsTabs = QTabWidget(self)
|
||||||
|
self.detailsTabs.addTab(self.buildDetails, self.tr("Build"))
|
||||||
|
self.detailsTabs.addTab(self.buildOutline, self.tr("Outline"))
|
||||||
|
self.detailsTabs.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_FLAT_TABS))
|
||||||
|
|
||||||
self.buildSplit = QSplitter(Qt.Orientation.Vertical, self)
|
self.buildSplit = QSplitter(Qt.Orientation.Vertical, self)
|
||||||
self.buildSplit.addWidget(self.buildList)
|
self.buildSplit.addWidget(self.buildList)
|
||||||
self.buildSplit.addWidget(self.buildDetails)
|
self.buildSplit.addWidget(self.detailsTabs)
|
||||||
self.buildSplit.setSizes([
|
self.buildSplit.setSizes([
|
||||||
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "listHeight", 50)),
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "listHeight", 50)),
|
||||||
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsHeight", 50)),
|
CONFIG.pxInt(pOptions.getInt("GuiManuscript", "detailsHeight", 50)),
|
||||||
@@ -223,6 +228,9 @@ class GuiManuscript(QDialog):
|
|||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
self.setSizeGripEnabled(True)
|
self.setSizeGripEnabled(True)
|
||||||
|
|
||||||
|
# Signals
|
||||||
|
self.buildOutline.outlineEntryClicked.connect(self.docPreview.navigateTo)
|
||||||
|
|
||||||
logger.debug("Ready: GuiManuscript")
|
logger.debug("Ready: GuiManuscript")
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -352,6 +360,7 @@ class GuiManuscript(QDialog):
|
|||||||
"uuid": build.buildID,
|
"uuid": build.buildID,
|
||||||
"time": int(time()),
|
"time": int(time()),
|
||||||
"stats": buildObj.textStats,
|
"stats": buildObj.textStats,
|
||||||
|
"outline": buildObj.textOutline,
|
||||||
"styles": buildObj.getStyleSheet(),
|
"styles": buildObj.getStyleSheet(),
|
||||||
"html": buildObj.fullHTML,
|
"html": buildObj.fullHTML,
|
||||||
}
|
}
|
||||||
@@ -408,6 +417,7 @@ class GuiManuscript(QDialog):
|
|||||||
build.getBool("format.justifyText")
|
build.getBool("format.justifyText")
|
||||||
)
|
)
|
||||||
self.docStats.updateStats(data.get("stats", {}))
|
self.docStats.updateStats(data.get("stats", {}))
|
||||||
|
self.buildOutline.updateOutline(data.get("outline", {}))
|
||||||
return
|
return
|
||||||
|
|
||||||
def _getSelectedBuild(self) -> BuildSettings | None:
|
def _getSelectedBuild(self) -> BuildSettings | None:
|
||||||
@@ -517,7 +527,7 @@ class _DetailsWidget(QWidget):
|
|||||||
self.listView = QTreeWidget(self)
|
self.listView = QTreeWidget(self)
|
||||||
self.listView.setHeaderLabels([self.tr("Setting"), self.tr("Value")])
|
self.listView.setHeaderLabels([self.tr("Setting"), self.tr("Value")])
|
||||||
self.listView.setIndentation(SHARED.theme.baseIconSize)
|
self.listView.setIndentation(SHARED.theme.baseIconSize)
|
||||||
self.listView.setSelectionMode(QAbstractItemView.NoSelection)
|
self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
self.outerBox = QVBoxLayout()
|
self.outerBox = QVBoxLayout()
|
||||||
@@ -649,6 +659,85 @@ class _DetailsWidget(QWidget):
|
|||||||
# END Class _DetailsWidget
|
# END Class _DetailsWidget
|
||||||
|
|
||||||
|
|
||||||
|
class _OutlineWidget(QWidget):
|
||||||
|
|
||||||
|
D_LINE = Qt.ItemDataRole.UserRole
|
||||||
|
|
||||||
|
outlineEntryClicked = pyqtSignal(str)
|
||||||
|
|
||||||
|
def __init__(self, parent: QWidget) -> None:
|
||||||
|
super().__init__(parent=parent)
|
||||||
|
|
||||||
|
self._outline = {}
|
||||||
|
|
||||||
|
# Tree Widget
|
||||||
|
self.listView = QTreeWidget(self)
|
||||||
|
self.listView.setHeaderHidden(True)
|
||||||
|
self.listView.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
|
||||||
|
self.listView.itemClicked.connect(self._onItemClick)
|
||||||
|
|
||||||
|
# Assemble
|
||||||
|
self.outerBox = QVBoxLayout()
|
||||||
|
self.outerBox.addWidget(self.listView)
|
||||||
|
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
def updateOutline(self, data: dict[str, str]) -> None:
|
||||||
|
"""Update the outline."""
|
||||||
|
if isinstance(data, dict) and data != self._outline:
|
||||||
|
self.listView.clear()
|
||||||
|
|
||||||
|
tFont = self.font()
|
||||||
|
tFont.setBold(True)
|
||||||
|
tBrush = self.palette().highlight()
|
||||||
|
|
||||||
|
hFont = self.font()
|
||||||
|
hFont.setBold(True)
|
||||||
|
hFont.setUnderline(True)
|
||||||
|
|
||||||
|
root = self.listView.invisibleRootItem()
|
||||||
|
parent = root
|
||||||
|
indent = False
|
||||||
|
for anchor, entry in data.items():
|
||||||
|
prefix, _, text = entry.partition("|")
|
||||||
|
if prefix in ("TT", "PT", "CH", "SC", "H1", "H2"):
|
||||||
|
item = QTreeWidgetItem([text])
|
||||||
|
item.setData(0, self.D_LINE, anchor)
|
||||||
|
if prefix == "TT":
|
||||||
|
item.setFont(0, tFont)
|
||||||
|
item.setForeground(0, tBrush)
|
||||||
|
root.addChild(item)
|
||||||
|
parent = root
|
||||||
|
elif prefix == "PT":
|
||||||
|
item.setFont(0, hFont)
|
||||||
|
root.addChild(item)
|
||||||
|
parent = root
|
||||||
|
elif prefix in ("CH", "H1"):
|
||||||
|
root.addChild(item)
|
||||||
|
parent = item
|
||||||
|
elif prefix in ("SC", "H2"):
|
||||||
|
parent.addChild(item)
|
||||||
|
indent = True
|
||||||
|
|
||||||
|
self.listView.setIndentation(SHARED.theme.baseIconSize if indent else CONFIG.pxInt(4))
|
||||||
|
self._outline = data
|
||||||
|
|
||||||
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Private Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
def _onItemClick(self, item: QTreeWidgetItem) -> None:
|
||||||
|
"""Process tree item click."""
|
||||||
|
self.outlineEntryClicked.emit(str(item.data(0, self.D_LINE)))
|
||||||
|
return
|
||||||
|
|
||||||
|
# END Class _OutlineWidget
|
||||||
|
|
||||||
|
|
||||||
class _PreviewWidget(QTextBrowser):
|
class _PreviewWidget(QTextBrowser):
|
||||||
|
|
||||||
def __init__(self, parent: QWidget) -> None:
|
def __init__(self, parent: QWidget) -> None:
|
||||||
@@ -688,7 +777,7 @@ class _PreviewWidget(QTextBrowser):
|
|||||||
self.ageLabel.setFont(aFont)
|
self.ageLabel.setFont(aFont)
|
||||||
self.ageLabel.setPalette(aPalette)
|
self.ageLabel.setPalette(aPalette)
|
||||||
self.ageLabel.setAutoFillBackground(True)
|
self.ageLabel.setAutoFillBackground(True)
|
||||||
self.ageLabel.setAlignment(Qt.AlignCenter)
|
self.ageLabel.setAlignment(Qt.AlignmentFlag.AlignCenter)
|
||||||
self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize))
|
self.ageLabel.setFixedHeight(int(2.1*SHARED.theme.fontPixelSize))
|
||||||
|
|
||||||
# Progress
|
# Progress
|
||||||
@@ -706,7 +795,7 @@ class _PreviewWidget(QTextBrowser):
|
|||||||
self._updateBuildAge()
|
self._updateBuildAge()
|
||||||
|
|
||||||
# Age Timer
|
# Age Timer
|
||||||
self.ageTimer = QTimer()
|
self.ageTimer = QTimer(self)
|
||||||
self.ageTimer.setInterval(10)
|
self.ageTimer.setInterval(10)
|
||||||
self.ageTimer.timeout.connect(self._updateBuildAge)
|
self.ageTimer.timeout.connect(self._updateBuildAge)
|
||||||
self.ageTimer.start()
|
self.ageTimer.start()
|
||||||
@@ -727,9 +816,9 @@ class _PreviewWidget(QTextBrowser):
|
|||||||
"""Enable/disable the justify text option."""
|
"""Enable/disable the justify text option."""
|
||||||
pOptions = self.document().defaultTextOption()
|
pOptions = self.document().defaultTextOption()
|
||||||
if state:
|
if state:
|
||||||
pOptions.setAlignment(Qt.AlignJustify)
|
pOptions.setAlignment(Qt.AlignmentFlag.AlignJustify)
|
||||||
else:
|
else:
|
||||||
pOptions.setAlignment(Qt.AlignAbsolute)
|
pOptions.setAlignment(Qt.AlignmentFlag.AlignAbsolute)
|
||||||
self.document().setDefaultTextOption(pOptions)
|
self.document().setDefaultTextOption(pOptions)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -765,7 +854,7 @@ class _PreviewWidget(QTextBrowser):
|
|||||||
def setContent(self, data: dict) -> None:
|
def setContent(self, data: dict) -> None:
|
||||||
"""Set the content of the preview widget."""
|
"""Set the content of the preview widget."""
|
||||||
sPos = self.verticalScrollBar().value()
|
sPos = self.verticalScrollBar().value()
|
||||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
qApp.setOverrideCursor(QCursor(Qt.CursorShape.WaitCursor))
|
||||||
|
|
||||||
self.buildProgress.setCentreText(self.tr("Processing ..."))
|
self.buildProgress.setCentreText(self.tr("Processing ..."))
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
@@ -775,8 +864,6 @@ class _PreviewWidget(QTextBrowser):
|
|||||||
|
|
||||||
html = "".join(data.get("html", []))
|
html = "".join(data.get("html", []))
|
||||||
html = html.replace("\t", "!!tab!!")
|
html = html.replace("\t", "!!tab!!")
|
||||||
html = html.replace("<del>", "<span style='text-decoration: line-through;'>")
|
|
||||||
html = html.replace("</del>", "</span>")
|
|
||||||
self.setHtml(html)
|
self.setHtml(html)
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
while self.find("!!tab!!"):
|
while self.find("!!tab!!"):
|
||||||
@@ -821,6 +908,13 @@ class _PreviewWidget(QTextBrowser):
|
|||||||
qApp.restoreOverrideCursor()
|
qApp.restoreOverrideCursor()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
|
def navigateTo(self, anchor: str) -> None:
|
||||||
|
"""Go to a specific #link in the document."""
|
||||||
|
logger.debug("Moving to anchor '#%s'", anchor)
|
||||||
|
self.setSource(QUrl(f"#{anchor}"))
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Private Slots
|
# Private Slots
|
||||||
##
|
##
|
||||||
@@ -882,19 +976,13 @@ class _StatsWidget(QWidget):
|
|||||||
self.minWidget = QWidget(self)
|
self.minWidget = QWidget(self)
|
||||||
self.maxWidget = QWidget(self)
|
self.maxWidget = QWidget(self)
|
||||||
|
|
||||||
fPx = int(0.6*SHARED.theme.fontPixelSize)
|
iPx = int(0.6*SHARED.theme.baseIconSize)
|
||||||
toggleIcon = SHARED.theme.getToggleIcon("unfold", (fPx, fPx))
|
toggleIcon = SHARED.theme.getToggleIcon("unfold", (iPx, iPx))
|
||||||
|
|
||||||
fadeCol = self.palette().text().color()
|
self.toggleButton = NIconToolButton(self, iPx)
|
||||||
buttonStyle = (
|
|
||||||
"QToolButton {{padding: 0; border: none; background: transparent;}} "
|
|
||||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
|
||||||
).format(fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
|
||||||
|
|
||||||
self.toggleButton = QToolButton(self)
|
|
||||||
self.toggleButton.setCheckable(True)
|
self.toggleButton.setCheckable(True)
|
||||||
self.toggleButton.setIcon(toggleIcon)
|
self.toggleButton.setIcon(toggleIcon)
|
||||||
self.toggleButton.setStyleSheet(buttonStyle)
|
self.toggleButton.setStyleSheet(SHARED.theme.getStyleSheet(STYLES_MIN_TOOLBUTTON))
|
||||||
self.toggleButton.toggled.connect(self._toggleView)
|
self.toggleButton.toggled.connect(self._toggleView)
|
||||||
|
|
||||||
self._buildMinimal()
|
self._buildMinimal()
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
"meta": {
|
"meta": {
|
||||||
"projectName": "Lorem Ipsum",
|
"projectName": "Lorem Ipsum",
|
||||||
"novelAuthor": "lipsum.com",
|
"novelAuthor": "lipsum.com",
|
||||||
"buildTime": 1706648873,
|
"buildTime": 1711014280,
|
||||||
"buildTimeStr": "2024-01-30 22:07:53"
|
"buildTimeStr": "2024-03-21 10:44:40"
|
||||||
},
|
},
|
||||||
"text": {
|
"text": {
|
||||||
"nwd": [
|
"nwd": [
|
||||||
@@ -169,7 +169,7 @@
|
|||||||
"Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo."
|
"Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo."
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"# Notes: Characters"
|
"#! Notes: Characters"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"# Nobody Owens",
|
"# Nobody Owens",
|
||||||
@@ -184,7 +184,7 @@
|
|||||||
"Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque."
|
"Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque."
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"# Notes: Plot"
|
"#! Notes: Plot"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"# Main Plot",
|
"# Main Plot",
|
||||||
@@ -196,7 +196,7 @@
|
|||||||
"Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur."
|
"Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur."
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"# Notes: World"
|
"#! Notes: World"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"# Ancient Europe",
|
"# Ancient Europe",
|
||||||
|
|||||||
@@ -147,7 +147,7 @@ Pellentesque rhoncus pharetra eros, non mollis nisi pretium non. Mauris accumsan
|
|||||||
|
|
||||||
Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.
|
Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacinia. Praesent lacinia urna porttitor aliquam condimentum. Nulla eu eros dictum, dictum nunc vitae, sagittis nibh. Integer ante neque, consequat nec sollicitudin id, consectetur vitae dolor. Nullam volutpat sem orci, quis viverra magna auctor a. Suspendisse potenti. Maecenas commodo sed neque pellentesque vehicula. Sed luctus nisl risus, elementum semper purus interdum vel. Ut pulvinar, massa sit amet venenatis placerat, nunc lacus hendrerit odio, non aliquet nunc risus eu lectus. Maecenas feugiat semper ligula, id lobortis sem porta eu. Integer posuere elit magna, at mollis eros bibendum et. Ut imperdiet purus vel nulla aliquam maximus. Morbi sodales purus tellus, a rhoncus sem rutrum sit amet. Quisque risus sem, laoreet nec convallis nec, rutrum vitae justo.
|
||||||
|
|
||||||
# Notes: Characters
|
#! Notes: Characters
|
||||||
|
|
||||||
# Nobody Owens
|
# Nobody Owens
|
||||||
|
|
||||||
@@ -160,7 +160,7 @@ Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque.
|
|||||||
|
|
||||||
Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.
|
Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.
|
||||||
|
|
||||||
# Notes: Plot
|
#! Notes: Plot
|
||||||
|
|
||||||
# Main Plot
|
# Main Plot
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vit
|
|||||||
|
|
||||||
Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.
|
Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.
|
||||||
|
|
||||||
# Notes: World
|
#! Notes: World
|
||||||
|
|
||||||
# Ancient Europe
|
# Ancient Europe
|
||||||
|
|
||||||
|
|||||||
@@ -86,8 +86,12 @@ def testCoreDocBuild_OpenDocument(monkeypatch, mockGUI, prjLipsum, fncPath, tstP
|
|||||||
|
|
||||||
docBuild = NWBuildDocument(project, build)
|
docBuild = NWBuildDocument(project, build)
|
||||||
docBuild.setCountEnabled(True)
|
docBuild.setCountEnabled(True)
|
||||||
|
docBuild.setBuildOutline(True)
|
||||||
docBuild.queueAll()
|
docBuild.queueAll()
|
||||||
|
|
||||||
|
assert docBuild._count is True
|
||||||
|
assert docBuild._outline is True
|
||||||
|
|
||||||
assert len(docBuild) == 21
|
assert len(docBuild) == 21
|
||||||
|
|
||||||
# Check FODT Build
|
# Check FODT Build
|
||||||
@@ -575,10 +579,10 @@ def testCoreDocBuild_IterBuild(mockGUI, fncPath: Path, mockRnd):
|
|||||||
"By Jane Doe\n\n"
|
"By Jane Doe\n\n"
|
||||||
"## New Chapter\n\n\n"
|
"## New Chapter\n\n\n"
|
||||||
"### New Scene\n\n\n"
|
"### New Scene\n\n\n"
|
||||||
"# Notes: Plot\n\n"
|
"#! Notes: Plot\n\n"
|
||||||
"# Main Plot\n"
|
"# Main Plot\n"
|
||||||
"**Text**\n\n"
|
"**Text**\n\n"
|
||||||
"# Notes: Characters\n\n"
|
"#! Notes: Characters\n\n"
|
||||||
"# Jane Doe\n"
|
"# Jane Doe\n"
|
||||||
"~~Text~~\n\n"
|
"~~Text~~\n\n"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1194,6 +1194,7 @@ def testCoreIndex_ItemIndex(mockGUI, fncPath, mockRnd):
|
|||||||
# Add a second novel
|
# Add a second novel
|
||||||
mHandle = project.newRoot(nwItemClass.NOVEL)
|
mHandle = project.newRoot(nwItemClass.NOVEL)
|
||||||
uHandle = project.newFile("Title Page", mHandle)
|
uHandle = project.newFile("Title Page", mHandle)
|
||||||
|
assert uHandle is not None
|
||||||
itemIndex.add(uHandle, project.tree[uHandle]) # type: ignore
|
itemIndex.add(uHandle, project.tree[uHandle]) # type: ignore
|
||||||
itemIndex.addItemHeading(uHandle, "T0001", "H1", "Novel 2") # type: ignore
|
itemIndex.addItemHeading(uHandle, "T0001", "H1", "Novel 2") # type: ignore
|
||||||
assert uHandle in itemIndex
|
assert uHandle in itemIndex
|
||||||
|
|||||||
@@ -38,7 +38,6 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
|
|||||||
# ===================
|
# ===================
|
||||||
|
|
||||||
html._isNovel = True
|
html._isNovel = True
|
||||||
html._isNote = False
|
|
||||||
html._isFirst = True
|
html._isFirst = True
|
||||||
|
|
||||||
# Header 1
|
# Header 1
|
||||||
@@ -87,33 +86,33 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
|
|||||||
# ==================
|
# ==================
|
||||||
|
|
||||||
html._isNovel = False
|
html._isNovel = False
|
||||||
html._isNote = True
|
|
||||||
html._isFirst = True
|
html._isFirst = True
|
||||||
|
html._handle = "0000000000000"
|
||||||
html.setLinkHeadings(True)
|
html.setLinkHeadings(True)
|
||||||
|
|
||||||
# Header 1
|
# Header 1
|
||||||
html._text = "# Heading One\n"
|
html._text = "# Heading One\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == "<h1><a name='T0001'></a>Heading One</h1>\n"
|
assert html.result == "<h1><a name='0000000000000:T0001'></a>Heading One</h1>\n"
|
||||||
|
|
||||||
# Header 2
|
# Header 2
|
||||||
html._text = "## Heading Two\n"
|
html._text = "## Heading Two\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == "<h2><a name='T0001'></a>Heading Two</h2>\n"
|
assert html.result == "<h2><a name='0000000000000:T0001'></a>Heading Two</h2>\n"
|
||||||
|
|
||||||
# Header 3
|
# Header 3
|
||||||
html._text = "### Heading Three\n"
|
html._text = "### Heading Three\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == "<h3><a name='T0001'></a>Heading Three</h3>\n"
|
assert html.result == "<h3><a name='0000000000000:T0001'></a>Heading Three</h3>\n"
|
||||||
|
|
||||||
# Header 4
|
# Header 4
|
||||||
html._text = "#### Heading Four\n"
|
html._text = "#### Heading Four\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == "<h4><a name='T0001'></a>Heading Four</h4>\n"
|
assert html.result == "<h4><a name='0000000000000:T0001'></a>Heading Four</h4>\n"
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
html._text = "#! Heading One\n"
|
html._text = "#! Heading One\n"
|
||||||
@@ -121,14 +120,14 @@ def testCoreToHtml_ConvertHeaders(mockGUI):
|
|||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
"<h1 class='title' style='text-align: center; page-break-before: always;'>"
|
"<h1 class='title' style='text-align: center; page-break-before: always;'>"
|
||||||
"<a name='T0001'></a>Heading One</h1>\n"
|
"<a name='0000000000000:T0001'></a>Heading One</h1>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Unnumbered
|
# Unnumbered
|
||||||
html._text = "##! Heading Two\n"
|
html._text = "##! Heading Two\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == "<h2><a name='T0001'></a>Heading Two</h2>\n"
|
assert html.result == "<h2><a name='0000000000000:T0001'></a>Heading Two</h2>\n"
|
||||||
|
|
||||||
# END Test testCoreToHtml_ConvertHeaders
|
# END Test testCoreToHtml_ConvertHeaders
|
||||||
|
|
||||||
@@ -140,7 +139,6 @@ def testCoreToHtml_ConvertParagraphs(mockGUI):
|
|||||||
html = ToHtml(project)
|
html = ToHtml(project)
|
||||||
|
|
||||||
html._isNovel = True
|
html._isNovel = True
|
||||||
html._isNote = False
|
|
||||||
html._isFirst = True
|
html._isFirst = True
|
||||||
|
|
||||||
# Paragraphs
|
# Paragraphs
|
||||||
@@ -229,7 +227,6 @@ def testCoreToHtml_ConvertParagraphs(mockGUI):
|
|||||||
|
|
||||||
# Multiple Keywords
|
# Multiple Keywords
|
||||||
html._isFirst = False
|
html._isFirst = False
|
||||||
html._noBreak = False
|
|
||||||
html.setKeywords(True)
|
html.setKeywords(True)
|
||||||
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
@@ -247,7 +244,7 @@ def testCoreToHtml_ConvertParagraphs(mockGUI):
|
|||||||
# Preview Mode
|
# Preview Mode
|
||||||
# ============
|
# ============
|
||||||
|
|
||||||
html.setPreview(True, True)
|
html.setPreview(True)
|
||||||
|
|
||||||
# Text (HTML4)
|
# Text (HTML4)
|
||||||
html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
html._text = "Some **nested bold and _italic_ and ~~strikethrough~~ text** here\n"
|
||||||
@@ -269,7 +266,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
html = ToHtml(project)
|
html = ToHtml(project)
|
||||||
|
|
||||||
html._isNovel = True
|
html._isNovel = True
|
||||||
html._isNote = False
|
html._handle = "0000000000000"
|
||||||
html.setLinkHeadings(True)
|
html.setLinkHeadings(True)
|
||||||
|
|
||||||
# Special Titles
|
# Special Titles
|
||||||
@@ -277,24 +274,24 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Title
|
# Title
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_TITLE, 1, "A Title", None, html.A_PBB | html.A_CENTRE),
|
(html.T_TITLE, 1, "A Title", [], html.A_PBB | html.A_CENTRE),
|
||||||
(html.T_EMPTY, 1, "", None, html.A_NONE),
|
(html.T_EMPTY, 1, "", [], html.A_NONE),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
"<h1 class='title' style='text-align: center; page-break-before: always;'>"
|
"<h1 class='title' style='text-align: center; page-break-before: always;'>"
|
||||||
"<a name='T0001'></a>A Title</h1>\n"
|
"<a name='0000000000000:T0001'></a>A Title</h1>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Unnumbered
|
# Unnumbered
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_HEAD2, 1, "Prologue", None, html.A_PBB),
|
(html.T_HEAD2, 1, "Prologue", [], html.A_PBB),
|
||||||
(html.T_EMPTY, 1, "", None, html.A_NONE),
|
(html.T_EMPTY, 1, "", [], html.A_NONE),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
"<h1 style='page-break-before: always;'>"
|
"<h1 style='page-break-before: always;'>"
|
||||||
"<a name='T0001'></a>Prologue</h1>\n"
|
"<a name='0000000000000:T0001'></a>Prologue</h1>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Separators
|
# Separators
|
||||||
@@ -302,16 +299,16 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Separator
|
# Separator
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_SEP, 1, "* * *", None, html.A_CENTRE),
|
(html.T_SEP, 1, "* * *", [], html.A_CENTRE),
|
||||||
(html.T_EMPTY, 1, "", None, html.A_NONE),
|
(html.T_EMPTY, 1, "", [], html.A_NONE),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == "<p class='sep' style='text-align: center;'>* * *</p>\n"
|
assert html.result == "<p class='sep' style='text-align: center;'>* * *</p>\n"
|
||||||
|
|
||||||
# Skip
|
# Skip
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_SKIP, 1, "", None, html.A_NONE),
|
(html.T_SKIP, 1, "", [], html.A_NONE),
|
||||||
(html.T_EMPTY, 1, "", None, html.A_NONE),
|
(html.T_EMPTY, 1, "", [], html.A_NONE),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == "<p class='skip'> </p>\n"
|
assert html.result == "<p class='skip'> </p>\n"
|
||||||
@@ -324,7 +321,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
# Align Left
|
# Align Left
|
||||||
html.setStyles(False)
|
html.setStyles(False)
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_HEAD1, 1, "A Title", None, html.A_LEFT),
|
(html.T_HEAD1, 1, "A Title", [], html.A_LEFT),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -335,7 +332,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Align Left
|
# Align Left
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_HEAD1, 1, "A Title", None, html.A_LEFT),
|
(html.T_HEAD1, 1, "A Title", [], html.A_LEFT),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -344,7 +341,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Align Right
|
# Align Right
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_HEAD1, 1, "A Title", None, html.A_RIGHT),
|
(html.T_HEAD1, 1, "A Title", [], html.A_RIGHT),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -353,7 +350,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Align Centre
|
# Align Centre
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_HEAD1, 1, "A Title", None, html.A_CENTRE),
|
(html.T_HEAD1, 1, "A Title", [], html.A_CENTRE),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -362,7 +359,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Align Justify
|
# Align Justify
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_HEAD1, 1, "A Title", None, html.A_JUSTIFY),
|
(html.T_HEAD1, 1, "A Title", [], html.A_JUSTIFY),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -374,7 +371,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Page Break Always
|
# Page Break Always
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_HEAD1, 1, "A Title", None, html.A_PBB | html.A_PBA),
|
(html.T_HEAD1, 1, "A Title", [], html.A_PBB | html.A_PBA),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -388,7 +385,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
# Indent Left
|
# Indent Left
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_TEXT, 1, "Some text ...", [], html.A_IND_L),
|
(html.T_TEXT, 1, "Some text ...", [], html.A_IND_L),
|
||||||
(html.T_EMPTY, 2, "", None, html.A_NONE),
|
(html.T_EMPTY, 2, "", [], html.A_NONE),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -398,7 +395,7 @@ def testCoreToHtml_ConvertDirect(mockGUI):
|
|||||||
# Indent Right
|
# Indent Right
|
||||||
html._tokens = [
|
html._tokens = [
|
||||||
(html.T_TEXT, 1, "Some text ...", [], html.A_IND_R),
|
(html.T_TEXT, 1, "Some text ...", [], html.A_IND_R),
|
||||||
(html.T_EMPTY, 2, "", None, html.A_NONE),
|
(html.T_EMPTY, 2, "", [], html.A_NONE),
|
||||||
]
|
]
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
assert html.result == (
|
assert html.result == (
|
||||||
@@ -461,7 +458,6 @@ def testCoreToHtml_SpecialCases(mockGUI):
|
|||||||
)
|
)
|
||||||
|
|
||||||
html._isFirst = False
|
html._isFirst = False
|
||||||
html._noBreak = False
|
|
||||||
html._text = "## Heading <1>\n"
|
html._text = "## Heading <1>\n"
|
||||||
html.tokenizeText()
|
html.tokenizeText()
|
||||||
html.doConvert()
|
html.doConvert()
|
||||||
@@ -606,16 +602,6 @@ def testCoreToHtml_Methods(mockGUI):
|
|||||||
"<p>Text with <brackets> & short–dash, long—dash …</p>\n"
|
"<p>Text with <brackets> & short–dash, long—dash …</p>\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# With Preview
|
|
||||||
html.setPreview(True, True)
|
|
||||||
html._text = docText
|
|
||||||
html.doPreProcessing()
|
|
||||||
html.tokenizeText()
|
|
||||||
html.doConvert()
|
|
||||||
assert html.allMarkdown[-1] == (
|
|
||||||
"Text with <brackets> & short–dash, long—dash …\n\n"
|
|
||||||
)
|
|
||||||
|
|
||||||
# Result Size
|
# Result Size
|
||||||
assert html.getFullResultSize() == 147
|
assert html.getFullResultSize() == 147
|
||||||
|
|
||||||
@@ -669,7 +655,7 @@ def testCoreToHtml_Format(mockGUI):
|
|||||||
# Preview Mode
|
# Preview Mode
|
||||||
# ============
|
# ============
|
||||||
|
|
||||||
html.setPreview(True, True)
|
html.setPreview(True)
|
||||||
|
|
||||||
assert html._formatSynopsis("synopsis text", True) == (
|
assert html._formatSynopsis("synopsis text", True) == (
|
||||||
"<p class='comment'><span class='synopsis'>Synopsis:</span> synopsis text</p>\n"
|
"<p class='comment'><span class='synopsis'>Synopsis:</span> synopsis text</p>\n"
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from novelwriter.core.tokenizer import HeadingFormatter, Tokenizer, stripEscape
|
|||||||
|
|
||||||
class BareTokenizer(Tokenizer):
|
class BareTokenizer(Tokenizer):
|
||||||
def doConvert(self):
|
def doConvert(self):
|
||||||
super().doConvert()
|
super().doConvert() # type: ignore (deliberate check)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
@@ -68,7 +68,7 @@ def testCoreToken_Setters(mockGUI):
|
|||||||
assert tokens._hideScene is False
|
assert tokens._hideScene is False
|
||||||
assert tokens._hideHScene is False
|
assert tokens._hideHScene is False
|
||||||
assert tokens._hideSection is False
|
assert tokens._hideSection is False
|
||||||
assert tokens._linkHeaders is False
|
assert tokens._linkHeadings is False
|
||||||
assert tokens._doBodyText is True
|
assert tokens._doBodyText is True
|
||||||
assert tokens._doSynopsis is False
|
assert tokens._doSynopsis is False
|
||||||
assert tokens._doComments is False
|
assert tokens._doComments is False
|
||||||
@@ -124,7 +124,7 @@ def testCoreToken_Setters(mockGUI):
|
|||||||
assert tokens._hideScene is True
|
assert tokens._hideScene is True
|
||||||
assert tokens._hideHScene is True
|
assert tokens._hideHScene is True
|
||||||
assert tokens._hideSection is True
|
assert tokens._hideSection is True
|
||||||
assert tokens._linkHeaders is True
|
assert tokens._linkHeadings is True
|
||||||
assert tokens._doBodyText is False
|
assert tokens._doBodyText is False
|
||||||
assert tokens._doSynopsis is True
|
assert tokens._doSynopsis is True
|
||||||
assert tokens._doComments is True
|
assert tokens._doComments is True
|
||||||
@@ -181,34 +181,36 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
|
|||||||
assert project.saveProject()
|
assert project.saveProject()
|
||||||
|
|
||||||
# Root Heading
|
# Root Heading
|
||||||
assert tokens.addRootHeading("stuff") is False
|
assert len(tokens._tokens) == 0
|
||||||
assert tokens.addRootHeading(C.hSceneDoc) is False
|
tokens.addRootHeading("stuff")
|
||||||
|
tokens.addRootHeading(C.hSceneDoc)
|
||||||
|
assert len(tokens._tokens) == 0
|
||||||
|
|
||||||
# First Page
|
# First Page
|
||||||
assert tokens.addRootHeading(C.hPlotRoot) is True
|
tokens.addRootHeading(C.hPlotRoot)
|
||||||
assert tokens.allMarkdown[-1] == "# Notes: Plot\n\n"
|
assert tokens.allMarkdown[-1] == "#! Notes: Plot\n\n"
|
||||||
assert tokens._tokens[-1] == (
|
assert tokens._tokens[-1] == (
|
||||||
Tokenizer.T_TITLE, 0, "Notes: Plot", [], Tokenizer.A_CENTRE
|
Tokenizer.T_TITLE, 1, "Notes: Plot", [], Tokenizer.A_CENTRE
|
||||||
)
|
)
|
||||||
|
|
||||||
# Not First Page
|
# Not First Page
|
||||||
assert tokens.addRootHeading(C.hPlotRoot) is True
|
tokens.addRootHeading(C.hPlotRoot)
|
||||||
assert tokens.allMarkdown[-1] == "# Notes: Plot\n\n"
|
assert tokens.allMarkdown[-1] == "#! Notes: Plot\n\n"
|
||||||
assert tokens._tokens[-1] == (
|
assert tokens._tokens[-1] == (
|
||||||
Tokenizer.T_TITLE, 0, "Notes: Plot", [], Tokenizer.A_CENTRE | Tokenizer.A_PBB
|
Tokenizer.T_TITLE, 1, "Notes: Plot", [], Tokenizer.A_CENTRE | Tokenizer.A_PBB
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set Text
|
# Set Text
|
||||||
assert tokens.setText("stuff") is False
|
tokens.setText("stuff")
|
||||||
assert tokens.setText(C.hSceneDoc) is True
|
assert tokens._text == ""
|
||||||
|
|
||||||
|
tokens.setText(C.hSceneDoc)
|
||||||
assert tokens._text == docText
|
assert tokens._text == docText
|
||||||
|
|
||||||
assert tokens.setText(C.hSceneDoc, docText) is True
|
tokens.setText(C.hSceneDoc, docText)
|
||||||
assert tokens._text == docText
|
assert tokens._text == docText
|
||||||
|
|
||||||
assert tokens._isNone is False
|
|
||||||
assert tokens._isNovel is True
|
assert tokens._isNovel is True
|
||||||
assert tokens._isNote is False
|
|
||||||
|
|
||||||
# Pre Processing
|
# Pre Processing
|
||||||
tokens.doPreProcessing()
|
tokens.doPreProcessing()
|
||||||
@@ -218,14 +220,14 @@ def testCoreToken_TextOps(monkeypatch, mockGUI, mockRnd, fncPath):
|
|||||||
savePath = fncPath / "dump.nwd"
|
savePath = fncPath / "dump.nwd"
|
||||||
tokens.saveRawMarkdown(savePath)
|
tokens.saveRawMarkdown(savePath)
|
||||||
assert readFile(savePath) == (
|
assert readFile(savePath) == (
|
||||||
"# Notes: Plot\n\n"
|
"#! Notes: Plot\n\n"
|
||||||
"# Notes: Plot\n\n"
|
"#! Notes: Plot\n\n"
|
||||||
)
|
)
|
||||||
tokens.saveRawMarkdownJSON(savePath)
|
tokens.saveRawMarkdownJSON(savePath)
|
||||||
assert json.loads(readFile(savePath))["text"] == {
|
assert json.loads(readFile(savePath))["text"] == {
|
||||||
"nwd": [
|
"nwd": [
|
||||||
["# Notes: Plot"],
|
["#! Notes: Plot"],
|
||||||
["# Notes: Plot"]
|
["#! Notes: Plot"]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -259,7 +261,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Story File
|
# Story File
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
tokens._isFirst = True
|
tokens._isFirst = True
|
||||||
tokens._text = "#! Novel Title\n"
|
tokens._text = "#! Novel Title\n"
|
||||||
|
|
||||||
@@ -272,7 +273,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
tokens._isFirst = True
|
tokens._isFirst = True
|
||||||
tokens._text = "#! Note Title\n"
|
tokens._text = "#! Note Title\n"
|
||||||
|
|
||||||
@@ -288,7 +288,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Story File
|
# Story File
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
tokens._isFirst = True
|
tokens._isFirst = True
|
||||||
tokens._text = "# Novel Title\n"
|
tokens._text = "# Novel Title\n"
|
||||||
|
|
||||||
@@ -301,7 +300,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
tokens._isFirst = True
|
tokens._isFirst = True
|
||||||
tokens._text = "# Note Title\n"
|
tokens._text = "# Note Title\n"
|
||||||
|
|
||||||
@@ -317,8 +315,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Story File
|
# Story File
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
tokens._noBreak = False
|
|
||||||
tokens._text = "## Chapter One\n"
|
tokens._text = "## Chapter One\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -330,7 +326,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
tokens._text = "## Heading 2\n"
|
tokens._text = "## Heading 2\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -345,7 +340,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Story File
|
# Story File
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
tokens._text = "### Scene One\n"
|
tokens._text = "### Scene One\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -357,7 +351,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
tokens._text = "### Heading 3\n"
|
tokens._text = "### Heading 3\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -372,7 +365,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Story File
|
# Story File
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
tokens._text = "#### A Section\n"
|
tokens._text = "#### A Section\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -384,7 +376,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
tokens._text = "#### Heading 4\n"
|
tokens._text = "#### Heading 4\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -399,7 +390,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Story File
|
# Story File
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
tokens._isFirst = False
|
tokens._isFirst = False
|
||||||
tokens._text = "#! Title\n"
|
tokens._text = "#! Title\n"
|
||||||
|
|
||||||
@@ -412,7 +402,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
tokens._isFirst = False
|
tokens._isFirst = False
|
||||||
tokens._text = "#! Title\n"
|
tokens._text = "#! Title\n"
|
||||||
|
|
||||||
@@ -428,7 +417,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Story File
|
# Story File
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
tokens._text = "##! Prologue\n"
|
tokens._text = "##! Prologue\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -440,7 +428,6 @@ def testCoreToken_HeaderFormat(mockGUI):
|
|||||||
|
|
||||||
# Note File
|
# Note File
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
tokens._text = "##! Prologue\n"
|
tokens._text = "##! Prologue\n"
|
||||||
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
@@ -462,7 +449,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
def processStyle(text: str, first: bool) -> int:
|
def processStyle(text: str, first: bool) -> int:
|
||||||
tokens._text = text
|
tokens._text = text
|
||||||
tokens._isFirst = first
|
tokens._isFirst = first
|
||||||
tokens._noBreak = first
|
|
||||||
tokens.tokenizeText()
|
tokens.tokenizeText()
|
||||||
return tokens._tokens[0][4]
|
return tokens._tokens[0][4]
|
||||||
|
|
||||||
@@ -479,7 +465,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Novel Docs
|
# Novel Docs
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
||||||
@@ -499,7 +484,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Note Docs
|
# Note Docs
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
||||||
@@ -530,7 +514,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Novel Docs
|
# Novel Docs
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_CENTRE
|
assert processStyle("# Title\n", False) == Tokenizer.A_CENTRE
|
||||||
@@ -550,7 +533,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Note Docs
|
# Note Docs
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
||||||
@@ -581,7 +563,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Novel Docs
|
# Novel Docs
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_PBB
|
assert processStyle("# Title\n", False) == Tokenizer.A_PBB
|
||||||
@@ -601,7 +582,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Note Docs
|
# Note Docs
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
||||||
@@ -632,7 +612,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Novel Docs
|
# Novel Docs
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
|
assert processStyle("# Title\n", False) == Tokenizer.A_CENTRE | Tokenizer.A_PBB
|
||||||
@@ -652,7 +631,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
|
|
||||||
# Note Docs
|
# Note Docs
|
||||||
tokens._isNovel = False
|
tokens._isNovel = False
|
||||||
tokens._isNote = True
|
|
||||||
|
|
||||||
# First Document is False
|
# First Document is False
|
||||||
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
assert processStyle("# Title\n", False) == Tokenizer.A_NONE
|
||||||
@@ -673,7 +651,6 @@ def testCoreToken_HeaderStyle(mockGUI):
|
|||||||
# Check Separation
|
# Check Separation
|
||||||
# ================
|
# ================
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
tokens._isNote = False
|
|
||||||
|
|
||||||
# Title Styles
|
# Title Styles
|
||||||
tokens.setTitleStyle(True, True)
|
tokens.setTitleStyle(True, True)
|
||||||
@@ -1310,8 +1287,6 @@ def testCoreToken_ProcessHeaders(mockGUI):
|
|||||||
# Story Files
|
# Story Files
|
||||||
##
|
##
|
||||||
|
|
||||||
tokens._isNone = False
|
|
||||||
tokens._isNote = False
|
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
|
|
||||||
# Titles
|
# Titles
|
||||||
@@ -1523,6 +1498,73 @@ def testCoreToken_ProcessHeaders(mockGUI):
|
|||||||
# END Test testCoreToken_ProcessHeaders
|
# END Test testCoreToken_ProcessHeaders
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.core
|
||||||
|
def testCoreToken_BuildOutline(mockGUI, ipsumText):
|
||||||
|
"""Test stats counter of the Tokenizer class."""
|
||||||
|
project = NWProject()
|
||||||
|
project.data.setLanguage("en")
|
||||||
|
project._loadProjectLocalisation()
|
||||||
|
tokens = BareTokenizer(project)
|
||||||
|
|
||||||
|
# Novel
|
||||||
|
tokens._isNovel = True
|
||||||
|
tokens._handle = "0000000000000"
|
||||||
|
tokens._text = (
|
||||||
|
"#! My Novel\n\n"
|
||||||
|
"# Part One\n\n"
|
||||||
|
"## Chapter One\n\n"
|
||||||
|
"### Scene One\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"### Scene Two\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"## Chapter Two\n\n"
|
||||||
|
"### Scene Three\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"###! Scene Four\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"#### Section\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
)
|
||||||
|
tokens.tokenizeText()
|
||||||
|
tokens.buildOutline()
|
||||||
|
|
||||||
|
# Note
|
||||||
|
tokens._isNovel = False
|
||||||
|
tokens._handle = "0000000000001"
|
||||||
|
tokens._text = (
|
||||||
|
"#! My Notes\n\n"
|
||||||
|
"# Header 1\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"## Header 2\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"### Header 3\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"#### Header 4\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
)
|
||||||
|
tokens.tokenizeText()
|
||||||
|
tokens.buildOutline()
|
||||||
|
|
||||||
|
# Check Outline
|
||||||
|
assert tokens.textOutline == {
|
||||||
|
"0000000000000:T0001": "TT|My Novel",
|
||||||
|
"0000000000000:T0002": "PT|Part One",
|
||||||
|
"0000000000000:T0003": "CH|Chapter One",
|
||||||
|
"0000000000000:T0004": "SC|Scene One",
|
||||||
|
"0000000000000:T0005": "SC|Scene Two",
|
||||||
|
"0000000000000:T0006": "CH|Chapter Two",
|
||||||
|
"0000000000000:T0007": "SC|Scene Three",
|
||||||
|
"0000000000000:T0008": "SC|Scene Four",
|
||||||
|
"0000000000001:T0001": "TT|My Notes",
|
||||||
|
"0000000000001:T0002": "H1|Header 1",
|
||||||
|
"0000000000001:T0003": "H2|Header 2",
|
||||||
|
"0000000000001:T0004": "H3|Header 3",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# END Test testCoreToken_BuildOutline
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.core
|
@pytest.mark.core
|
||||||
def testCoreToken_CountStats(mockGUI, ipsumText):
|
def testCoreToken_CountStats(mockGUI, ipsumText):
|
||||||
"""Test stats counter of the Tokenizer class."""
|
"""Test stats counter of the Tokenizer class."""
|
||||||
@@ -1530,8 +1572,6 @@ def testCoreToken_CountStats(mockGUI, ipsumText):
|
|||||||
project.data.setLanguage("en")
|
project.data.setLanguage("en")
|
||||||
project._loadProjectLocalisation()
|
project._loadProjectLocalisation()
|
||||||
tokens = BareTokenizer(project)
|
tokens = BareTokenizer(project)
|
||||||
tokens._isNone = False
|
|
||||||
tokens._isNote = False
|
|
||||||
tokens._isNovel = True
|
tokens._isNovel = True
|
||||||
|
|
||||||
# Short Text
|
# Short Text
|
||||||
@@ -1740,8 +1780,6 @@ def testCoreToken_SceneSeparators(mockGUI):
|
|||||||
project.data.setLanguage("en")
|
project.data.setLanguage("en")
|
||||||
project._loadProjectLocalisation()
|
project._loadProjectLocalisation()
|
||||||
md = ToMarkdown(project)
|
md = ToMarkdown(project)
|
||||||
md._isNone = False
|
|
||||||
md._isNote = False
|
|
||||||
md._isNovel = True
|
md._isNovel = True
|
||||||
|
|
||||||
# Separator Handling, Titles
|
# Separator Handling, Titles
|
||||||
@@ -1858,8 +1896,6 @@ def testCoreToken_SceneSeparators(mockGUI):
|
|||||||
# Requires a fresh builder class
|
# Requires a fresh builder class
|
||||||
md = ToMarkdown(project)
|
md = ToMarkdown(project)
|
||||||
md.setExtendedMarkdown()
|
md.setExtendedMarkdown()
|
||||||
md._isNone = False
|
|
||||||
md._isNote = False
|
|
||||||
md._isNovel = True
|
md._isNovel = True
|
||||||
|
|
||||||
md._text = (
|
md._text = (
|
||||||
@@ -1919,8 +1955,6 @@ def testCoreToken_HeaderVisibility(mockGUI):
|
|||||||
# Novel Files
|
# Novel Files
|
||||||
# ===========
|
# ===========
|
||||||
|
|
||||||
md._isNone = False
|
|
||||||
md._isNote = False
|
|
||||||
md._isNovel = True
|
md._isNovel = True
|
||||||
|
|
||||||
# Show All
|
# Show All
|
||||||
@@ -1973,8 +2007,6 @@ def testCoreToken_HeaderVisibility(mockGUI):
|
|||||||
# Note Files
|
# Note Files
|
||||||
# ==========
|
# ==========
|
||||||
|
|
||||||
md._isNone = False
|
|
||||||
md._isNote = True
|
|
||||||
md._isNovel = False
|
md._isNovel = False
|
||||||
|
|
||||||
# Hide All
|
# Hide All
|
||||||
@@ -2015,8 +2047,6 @@ def testCoreToken_CounterHandling(mockGUI):
|
|||||||
project.data.setLanguage("en")
|
project.data.setLanguage("en")
|
||||||
project._loadProjectLocalisation()
|
project._loadProjectLocalisation()
|
||||||
md = ToMarkdown(project)
|
md = ToMarkdown(project)
|
||||||
md._isNone = False
|
|
||||||
md._isNote = False
|
|
||||||
md._isNovel = True
|
md._isNovel = True
|
||||||
|
|
||||||
# Counter Handling, Novel Titles
|
# Counter Handling, Novel Titles
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ def testCoreToMarkdown_ConvertHeaders(mockGUI):
|
|||||||
toMD = ToMarkdown(project)
|
toMD = ToMarkdown(project)
|
||||||
|
|
||||||
toMD._isNovel = True
|
toMD._isNovel = True
|
||||||
toMD._isNote = False
|
|
||||||
toMD._isFirst = True
|
toMD._isFirst = True
|
||||||
|
|
||||||
# Header 1
|
# Header 1
|
||||||
@@ -84,7 +83,6 @@ def testCoreToMarkdown_ConvertParagraphs(mockGUI):
|
|||||||
toMD = ToMarkdown(project)
|
toMD = ToMarkdown(project)
|
||||||
|
|
||||||
toMD._isNovel = True
|
toMD._isNovel = True
|
||||||
toMD._isNote = False
|
|
||||||
toMD._isFirst = True
|
toMD._isFirst = True
|
||||||
|
|
||||||
# Text for Extended Markdown
|
# Text for Extended Markdown
|
||||||
@@ -200,15 +198,14 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
|
|||||||
toMD = ToMarkdown(project)
|
toMD = ToMarkdown(project)
|
||||||
|
|
||||||
toMD._isNovel = True
|
toMD._isNovel = True
|
||||||
toMD._isNote = False
|
|
||||||
|
|
||||||
# Special Titles
|
# Special Titles
|
||||||
# ==============
|
# ==============
|
||||||
|
|
||||||
# Title
|
# Title
|
||||||
toMD._tokens = [
|
toMD._tokens = [
|
||||||
(toMD.T_TITLE, 1, "A Title", None, toMD.A_PBB | toMD.A_CENTRE),
|
(toMD.T_TITLE, 1, "A Title", [], toMD.A_PBB | toMD.A_CENTRE),
|
||||||
(toMD.T_EMPTY, 1, "", None, toMD.A_NONE),
|
(toMD.T_EMPTY, 1, "", [], toMD.A_NONE),
|
||||||
]
|
]
|
||||||
toMD.doConvert()
|
toMD.doConvert()
|
||||||
assert toMD.result == "# A Title\n\n"
|
assert toMD.result == "# A Title\n\n"
|
||||||
@@ -218,16 +215,16 @@ def testCoreToMarkdown_ConvertDirect(mockGUI):
|
|||||||
|
|
||||||
# Separator
|
# Separator
|
||||||
toMD._tokens = [
|
toMD._tokens = [
|
||||||
(toMD.T_SEP, 1, "* * *", None, toMD.A_CENTRE),
|
(toMD.T_SEP, 1, "* * *", [], toMD.A_CENTRE),
|
||||||
(toMD.T_EMPTY, 1, "", None, toMD.A_NONE),
|
(toMD.T_EMPTY, 1, "", [], toMD.A_NONE),
|
||||||
]
|
]
|
||||||
toMD.doConvert()
|
toMD.doConvert()
|
||||||
assert toMD.result == "* * *\n\n"
|
assert toMD.result == "* * *\n\n"
|
||||||
|
|
||||||
# Skip
|
# Skip
|
||||||
toMD._tokens = [
|
toMD._tokens = [
|
||||||
(toMD.T_SKIP, 1, "", None, toMD.A_NONE),
|
(toMD.T_SKIP, 1, "", [], toMD.A_NONE),
|
||||||
(toMD.T_EMPTY, 1, "", None, toMD.A_NONE),
|
(toMD.T_EMPTY, 1, "", [], toMD.A_NONE),
|
||||||
]
|
]
|
||||||
toMD.doConvert()
|
toMD.doConvert()
|
||||||
assert toMD.result == "\n\n"
|
assert toMD.result == "\n\n"
|
||||||
|
|||||||
@@ -652,7 +652,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
|
|||||||
doc = ToOdt(project, isFlat=True)
|
doc = ToOdt(project, isFlat=True)
|
||||||
doc._tokens = [
|
doc._tokens = [
|
||||||
(doc.T_TEXT, 1, "This is a paragraph", [], doc.A_JUSTIFY),
|
(doc.T_TEXT, 1, "This is a paragraph", [], doc.A_JUSTIFY),
|
||||||
(doc.T_EMPTY, 1, "", None, doc.A_NONE),
|
(doc.T_EMPTY, 1, "", [], doc.A_NONE),
|
||||||
]
|
]
|
||||||
doc.initDocument()
|
doc.initDocument()
|
||||||
doc.doConvert()
|
doc.doConvert()
|
||||||
@@ -673,7 +673,7 @@ def testCoreToOdt_ConvertDirect(mockGUI):
|
|||||||
doc = ToOdt(project, isFlat=True)
|
doc = ToOdt(project, isFlat=True)
|
||||||
doc._tokens = [
|
doc._tokens = [
|
||||||
(doc.T_TEXT, 1, "This is a paragraph", [], doc.A_PBA),
|
(doc.T_TEXT, 1, "This is a paragraph", [], doc.A_PBA),
|
||||||
(doc.T_EMPTY, 1, "", None, doc.A_NONE),
|
(doc.T_EMPTY, 1, "", [], doc.A_NONE),
|
||||||
]
|
]
|
||||||
doc.initDocument()
|
doc.initDocument()
|
||||||
doc.doConvert()
|
doc.doConvert()
|
||||||
|
|||||||
@@ -26,11 +26,12 @@ import pytest
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from pytestqt.qtbot import QtBot
|
from pytestqt.qtbot import QtBot
|
||||||
|
|
||||||
|
from novelwriter.constants import nwHeadFmt
|
||||||
from tools import C, buildTestProject
|
from tools import C, buildTestProject
|
||||||
from mocked import causeOSError
|
from mocked import causeOSError
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, pyqtSlot
|
from PyQt5.QtCore import Qt, pyqtSlot
|
||||||
from PyQt5.QtWidgets import QAction, QDialogButtonBox
|
from PyQt5.QtWidgets import QAction, QDialogButtonBox, QListWidgetItem
|
||||||
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
from PyQt5.QtPrintSupport import QPrintPreviewDialog
|
||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
@@ -152,11 +153,31 @@ def testManuscript_Builds(qtbot: QtBot, nwGUI: GuiMain, projPath: Path):
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.gui
|
@pytest.mark.gui
|
||||||
def testManuscript_Features(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath: Path):
|
def testManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd):
|
||||||
"""Test other features of the GuiManuscript dialog."""
|
"""Test other features of the GuiManuscript dialog."""
|
||||||
buildTestProject(nwGUI, projPath)
|
buildTestProject(nwGUI, projPath)
|
||||||
nwGUI.openProject(projPath)
|
nwGUI.openProject(projPath)
|
||||||
|
|
||||||
|
nwGUI.openDocument(C.hTitlePage)
|
||||||
|
nwGUI.docEditor.setPlainText(
|
||||||
|
"#! My Novel\n\n"
|
||||||
|
"# Part One\n\n"
|
||||||
|
"## Chapter One\n\n"
|
||||||
|
"### Scene One\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"### Scene Two\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"## Chapter Two\n\n"
|
||||||
|
"### Scene Three\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"###! Scene Four\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
"#### Section\n\n"
|
||||||
|
"Text\n\n"
|
||||||
|
)
|
||||||
|
nwGUI.saveDocument()
|
||||||
|
# qtbot.stop()
|
||||||
|
|
||||||
manus = GuiManuscript(nwGUI)
|
manus = GuiManuscript(nwGUI)
|
||||||
manus.show()
|
manus.show()
|
||||||
manus.loadContent()
|
manus.loadContent()
|
||||||
@@ -185,6 +206,14 @@ def testManuscript_Features(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath:
|
|||||||
manus.btnPreview.click()
|
manus.btnPreview.click()
|
||||||
assert cacheFile.exists() is False
|
assert cacheFile.exists() is False
|
||||||
|
|
||||||
|
first = manus.buildList.item(0)
|
||||||
|
assert isinstance(first, QListWidgetItem)
|
||||||
|
build = manus._builds.getBuild(first.data(GuiManuscript.D_KEY))
|
||||||
|
assert isinstance(build, BuildSettings)
|
||||||
|
build.setValue("headings.fmtScene", nwHeadFmt.TITLE)
|
||||||
|
build.setValue("headings.fmtHardScene", nwHeadFmt.TITLE)
|
||||||
|
manus._builds.setBuild(build)
|
||||||
|
|
||||||
# Preview again, and allow cache file to be created
|
# Preview again, and allow cache file to be created
|
||||||
manus.buildList.setCurrentRow(0)
|
manus.buildList.setCurrentRow(0)
|
||||||
with qtbot.waitSignal(manus.docPreview.document().contentsChanged):
|
with qtbot.waitSignal(manus.docPreview.document().contentsChanged):
|
||||||
@@ -192,15 +221,45 @@ def testManuscript_Features(monkeypatch, qtbot: QtBot, nwGUI: GuiMain, projPath:
|
|||||||
assert manus.docPreview.toPlainText().strip() != ""
|
assert manus.docPreview.toPlainText().strip() != ""
|
||||||
assert cacheFile.exists() is True
|
assert cacheFile.exists() is True
|
||||||
|
|
||||||
|
# Check Outline
|
||||||
|
assert manus.buildOutline._outline == {
|
||||||
|
"000000000000c:T0001": "TT|My Novel",
|
||||||
|
"000000000000c:T0002": "PT|Part One",
|
||||||
|
"000000000000c:T0003": "CH|Chapter One",
|
||||||
|
"000000000000c:T0004": "SC|Scene One",
|
||||||
|
"000000000000c:T0005": "SC|Scene Two",
|
||||||
|
"000000000000c:T0006": "CH|Chapter Two",
|
||||||
|
"000000000000c:T0007": "SC|Scene Three",
|
||||||
|
"000000000000c:T0008": "SC|Scene Four",
|
||||||
|
"000000000000e:T0001": "CH|New Chapter",
|
||||||
|
"000000000000f:T0001": "SC|New Scene",
|
||||||
|
}
|
||||||
|
listView = manus.buildOutline.listView
|
||||||
|
assert listView.topLevelItemCount() == 5
|
||||||
|
keyRole = manus.buildOutline.D_LINE
|
||||||
|
|
||||||
|
assert (item := listView.topLevelItem(0)) and item.data(0, keyRole) == "000000000000c:T0001"
|
||||||
|
assert (item := listView.topLevelItem(1)) and item.data(0, keyRole) == "000000000000c:T0002"
|
||||||
|
assert (item := listView.topLevelItem(2)) and item.data(0, keyRole) == "000000000000c:T0003"
|
||||||
|
assert (item := listView.topLevelItem(3)) and item.data(0, keyRole) == "000000000000c:T0006"
|
||||||
|
assert (item := listView.topLevelItem(4)) and item.data(0, keyRole) == "000000000000e:T0001"
|
||||||
|
|
||||||
|
# Click Outline
|
||||||
|
item = listView.topLevelItem(4)
|
||||||
|
assert item is not None
|
||||||
|
with qtbot.waitSignal(manus.buildOutline.outlineEntryClicked) as signal:
|
||||||
|
manus.buildOutline._onItemClick(item)
|
||||||
|
assert signal.args == ["000000000000e:T0001"]
|
||||||
|
|
||||||
# Check Preview Stats
|
# Check Preview Stats
|
||||||
assert manus.docStats.mainStack.currentWidget() == manus.docStats.minWidget
|
assert manus.docStats.mainStack.currentWidget() == manus.docStats.minWidget
|
||||||
assert manus.docStats.minWordCount.text() == "7"
|
assert manus.docStats.minWordCount.text() == "25"
|
||||||
assert manus.docStats.minCharCount.text() == "31"
|
assert manus.docStats.minCharCount.text() == "117"
|
||||||
|
|
||||||
manus.docStats.toggleButton.toggle()
|
manus.docStats.toggleButton.toggle()
|
||||||
assert manus.docStats.mainStack.currentWidget() == manus.docStats.maxWidget
|
assert manus.docStats.mainStack.currentWidget() == manus.docStats.maxWidget
|
||||||
assert manus.docStats.maxTotalWords.text() == "7"
|
assert manus.docStats.maxTotalWords.text() == "25"
|
||||||
assert manus.docStats.maxTotalChars.text() == "31"
|
assert manus.docStats.maxTotalChars.text() == "117"
|
||||||
|
|
||||||
# Toggle justify
|
# Toggle justify
|
||||||
assert manus.docPreview.document().defaultTextOption().alignment() == Qt.AlignAbsolute
|
assert manus.docPreview.document().defaultTextOption().alignment() == Qt.AlignAbsolute
|
||||||
|
|||||||