Drop Novel Details tool (#3044)

This commit is contained in:
Veronica Berglyd Olsen
2026-10-01 00:39:44 +02:00
committed by GitHub
27 changed files with 18 additions and 825 deletions

View File

@@ -34,7 +34,6 @@ Main Window Shortcuts
":kbd:`Ctrl+Shift+T`", "Switch focus to the outline view"
":kbd:`Ctrl+Shift+W`", "Close the current project"
":kbd:`Shift+F1`", "Open the local user manual (PDF) if it is available"
":kbd:`Shift+F6`", "Open the **Project Details** dialog"
Project Tree Shortcuts

View File

@@ -34,9 +34,7 @@ right-clicking it and selecting the **Remove Project** option.
Project-specific settings are available in **Project Settings** in the **Project** menu. See
further details below in the :ref:`docs_ui_projects_settings` section.
Details about the project's novel text, including word counts, and a table of contents with word
and page counts, is available through the **Novel Details** dialog. Statistics about the project
is also available in the **Manuscript Build** tool.
Statistics about the project are available in the **Manuscript Build** tool.
Template Projects

View File

@@ -57,7 +57,6 @@ icon:bullet-on = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:unfold-hide = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M420-308q-8 0-14-5.5t-6-14.5v-304q0-9 6-14.5t14-5.5q2 0 14 6l145 145q5 5 7 10t2 11q0 6-2 11t-7 10L434-314q-3 3-6.5 4.5T420-308Z" /></svg>
icon:unfold-show = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M459-381 314-526q-3-3-4.5-6.5T308-540q0-8 5.5-14t14.5-6h304q9 0 14.5 6t5.5 14q0 2-6 14L501-381q-5 5-10 7t-11 2q-6 0-11-2t-10-7Z" /></svg>
icon:sb_build = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M760-252v72q0 8 6 14t14 6q8 0 14-6t6-14v-120q0-8-6-14t-14-6H660q-8 0-14 6t-6 14q0 8 6 14t14 6h72l-98 98q-6 6-6 14t6 14q6 6 14 6t14-6l98-98ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40ZM320-600h320q17 0 28.5-11.5T680-640q0-17-11.5-28.5T640-680H320q-17 0-28.5 11.5T280-640q0 17 11.5 28.5T320-600ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v218q0 18-15 28t-32 4q-17-5-35.5-7.5T720-520q-11 0-20.5.5T680-517q-9-2-20-2.5t-20-.5H320q-17 0-28.5 11.5T280-480q0 17 11.5 28.5T320-440h205q-18 17-32.5 37T467-360H320q-17 0-28.5 11.5T280-320q0 17 11.5 28.5T320-280h123q-2 10-2.5 19.5T440-240q0 20 2 38t7 35q5 17-5 32t-27 15H200Z" /></svg>
icon:sb_details = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M400-200q-17 0-28.5-11.5T360-240q0-17 11.5-28.5T400-280h400q17 0 28.5 11.5T840-240q0 17-11.5 28.5T800-200H400Zm0-240q-17 0-28.5-11.5T360-480q0-17 11.5-28.5T400-520h400q17 0 28.5 11.5T840-480q0 17-11.5 28.5T800-440H400Zm0-240q-17 0-28.5-11.5T360-720q0-17 11.5-28.5T400-760h400q17 0 28.5 11.5T840-720q0 17-11.5 28.5T800-680H400ZM200-160q-33 0-56.5-23.5T120-240q0-33 23.5-56.5T200-320q33 0 56.5 23.5T280-240q0 33-23.5 56.5T200-160Zm0-240q-33 0-56.5-23.5T120-480q0-33 23.5-56.5T200-560q33 0 56.5 23.5T280-480q0 33-23.5 56.5T200-400Zm0-240q-33 0-56.5-23.5T120-720q0-33 23.5-56.5T200-800q33 0 56.5 23.5T280-720q0 33-23.5 56.5T200-640Z" /></svg>
icon:sb_novel = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M320-320q17 0 28.5-11.5T360-360v-400q0-17-11.5-28.5T320-800q-17 0-28.5 11.5T280-760v400q0 17 11.5 28.5T320-320ZM760-80H240q-50 0-85-35t-35-85v-560q0-50 35-85t85-35h272q-34 35-53 81t-19 99q0 103 69.5 177T680-441v121q0 33-23.5 56.5T600-240H240q-17 0-28.5 11.5T200-200q0 17 11.5 28.5T240-160h520v-287q22-5 42-13.5t38-20.5v321q0 33-23.5 56.5T760-80Zm-60-400q0-92 64-156t156-64q-92 0-156-64t-64-156q0 92-64 156t-156 64q92 0 156 64t64 156Z" /></svg>
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M560-120v-66q0-8 3-15.5t9-13.5l209-208q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q8 9 12.5 20t4.5 22q0 11-4 22.5T903-300L695-92q-6 6-13.5 9T666-80h-66q-17 0-28.5-11.5T560-120Zm263-184 37-39-37-37-38 38 38 38ZM160-160q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h320q33 0 56.5 23.5T880-640v83q0 18-14.5 29t-33.5 8h-11q-28 0-52.5 10T724-480L503-259q-11 11-17 25.5t-6 30.5v3q0 17-11.5 28.5T440-160H160Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M380-320q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l224 224q11 11 11 28t-11 28q-11 11-28 11t-28-11L532-372q-30 24-69 38t-83 14Zm0-80q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" /></svg>

View File

@@ -57,7 +57,6 @@ icon:bullet-on = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:unfold-hide = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M436.4-350.31q-6.71 0-11.55-4.44-4.85-4.44-4.85-11.71v-227.08q0-7.27 4.95-11.71 4.95-4.44 11.55-4.44 1.65 0 11.35 5.23l107.07 107.08q3.7 3.69 5.7 7.97 2 4.28 2 9.41t-2 9.41q-2 4.28-5.7 7.98L447.83-355.52q-2.21 2.21-5.15 3.71-2.93 1.5-6.28 1.5Z" /></svg>
icon:unfold-show = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M462.61-405.08 355.52-512.17q-2.21-2.21-3.71-5.15-1.5-2.93-1.5-6.28 0-6.71 4.44-11.55 4.44-4.85 11.71-4.85h227.08q7.27 0 11.71 4.95 4.44 4.95 4.44 11.55 0 1.65-5.23 11.35L497.38-405.08q-3.69 3.7-7.97 5.7-4.28 2-9.41 2t-9.41-2q-4.28-2-7.98-5.7Z" /></svg>
icon:sb_build = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M763.85-257.38V-190q0 6.15 4.61 10.77 4.62 4.61 10.77 4.61 6.15 0 10.77-4.61 4.62-4.62 4.62-10.77v-103.85q0-6.46-4.85-11.3-4.85-4.85-11.31-4.85H674.62q-6.16 0-10.77 4.62-4.62 4.61-4.62 10.76 0 6.16 4.62 10.77 4.61 4.62 10.77 4.62H742l-90.31 90.31q-4.46 4.46-4.84 10.54-.39 6.07 4.84 11.3 4.74 4.46 11.07 4.46 6.32 0 10.78-4.46l90.31-90.3ZM720-75.38q-66.85 0-113.42-46.58Q560-168.54 560-235.38q0-66.85 46.58-113.43 46.57-46.57 113.42-46.57t113.42 46.57Q880-302.23 880-235.38q0 66.84-46.58 113.42Q786.85-75.38 720-75.38ZM320-610.77h320q8.5 0 14.25-5.76 5.75-5.75 5.75-14.27 0-8.51-5.75-14.24T640-650.77H320q-8.5 0-14.25 5.76T300-630.74q0 8.51 5.75 14.24t14.25 5.73ZM224.62-160q-26.66 0-45.64-18.98T160-224.62v-510.76q0-26.66 18.98-45.64T224.62-800h510.76q26.66 0 45.64 18.98T800-735.38v200.3q0 15.09-11.15 23.47-11.16 8.38-25.85 5.46-10.08-2.7-20.95-3.66-10.88-.96-22.05-.96-20.23 0-39.35 3.19-19.11 3.2-37.57 9.04-4.5-.97-10-1.22-5.5-.24-10-.24H320q-8.5 0-14.25 5.76T300-479.97q0 8.51 5.75 14.24T320-460h243.46q-30.31 20.85-53.27 48.92-22.96 28.08-37.81 61.85H320q-8.5 0-14.25 5.76-5.75 5.75-5.75 14.27 0 8.51 5.75 14.24t14.25 5.73h138.38q-4.3 16.92-6.73 34.11-2.42 17.2-2.42 35.12 0 10 .46 21.08.46 11.07 2.39 22.69 1.92 13.92-6.21 25.08Q437.74-160 423.92-160h-199.3Z" /></svg>
icon:sb_details = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M404.62-220q-8.5 0-14.25-5.76t-5.75-14.27q0-8.51 5.75-14.24t14.25-5.73H780q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T780-220H404.62Zm0-240q-8.5 0-14.25-5.76t-5.75-14.27q0-8.51 5.75-14.24t14.25-5.73H780q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T780-460H404.62Zm0-240q-8.5 0-14.25-5.76t-5.75-14.27q0-8.51 5.75-14.24t14.25-5.73H780q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T780-700H404.62ZM213.08-186.92q-21.9 0-37.49-15.59Q160-218.11 160-240t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Zm0-240q-21.9 0-37.49-15.59Q160-458.11 160-480t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Zm0-240q-21.9 0-37.49-15.59Q160-698.11 160-720t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Z" /></svg>
icon:sb_novel = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M315.41-295.38q8.51 0 14.24-5.75t5.73-14.25V-780q0-8.5-5.75-14.25-5.76-5.75-14.27-5.75t-14.24 5.75q-5.74 5.75-5.74 14.25v464.62q0 8.5 5.76 14.25t14.27 5.75ZM735.38-120H249.23q-37.18 0-63.2-26.03Q160-172.05 160-209.23v-541.54q0-37.18 26.03-63.2Q212.05-840 249.23-840h221.23q-19.38 30.38-30.31 65.62-10.92 35.23-10.92 74.38 0 103.77 67.58 179.69 67.57 75.93 167.81 88.54V-320q0 26.65-18.99 45.63-18.98 18.99-45.63 18.99H249.23q-20.08 0-34.65 13.67Q200-228.04 200-207.83t14.58 34.02Q229.15-160 249.23-160h486.15q10.77 0 17.7-6.92 6.92-6.93 6.92-17.7v-251.61q11.23-2.69 20.85-5.81 9.61-3.11 19.15-7.42v264.5q0 27.19-18.98 46.08Q762.04-120 735.38-120ZM700-500q0-83.64 58.18-141.82Q816.36-700 900-700q-83.64 0-141.82-58.18Q700-816.36 700-900q0 83.64-58.18 141.82Q583.64-700 500-700q83.64 0 141.82 58.18Q700-583.64 700-500Z" /></svg>
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M569.23-152.31v-42.92q0-6.46 2.23-12.04 2.23-5.58 7.46-10.81l203.62-202.61q5.92-5.16 12.31-7.62 6.38-2.46 12.77-2.46 6.61 0 13.38 2.58 6.77 2.57 11.92 7.73l37 37.77q4.93 5.92 7.5 12.31Q880-364 880-357.62q0 6.39-2.08 12.89-2.07 6.5-8 12.42L667.31-129.69q-5.23 5.23-10.81 7.46-5.58 2.23-12.04 2.23h-42.92q-13.92 0-23.12-9.19-9.19-9.19-9.19-23.12Zm238.39-167.07 37-38.24-37-37.76-38 38 38 38ZM184.62-200q-27.62 0-46.12-18.5Q120-237 120-264.62v-430.76q0-27.62 18.5-46.12Q157-760 184.62-760H357q12.92 0 25.12 5.23 12.19 5.23 20.88 13.92L463.85-680h311.53q27.62 0 46.12 18.5Q840-643 840-615.38v61.46q0 12.61-9.88 20.92-9.89 8.31-22.74 6.85H804.85q-22.62 0-44.43 8.46-21.8 8.46-40.27 24.61L493-266.69q-8.69 8.69-13.92 20.5t-5.23 24.73v.69q0 8.54-6.12 14.65-6.11 6.12-14.65 6.12H184.62Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M381.54-350.77q-95.92 0-162.58-66.65-66.65-66.66-66.65-162.58 0-95.92 66.65-162.58 66.66-66.65 162.58-66.65 95.92 0 162.58 66.65 66.65 66.66 66.65 162.58 0 41.69-14.77 80.69t-38.77 66.69l236.31 236.31q5.61 5.62 6 13.77.38 8.16-6 14.54-6.39 6.38-14.16 6.38-7.76 0-14.15-6.38L528.92-404.31q-30 25.54-69 39.54t-78.38 14Zm0-40q79.61 0 134.42-54.81 54.81-54.8 54.81-134.42 0-79.62-54.81-134.42-54.81-54.81-134.42-54.81-79.62 0-134.42 54.81-54.81 54.8-54.81 134.42 0 79.62 54.81 134.42 54.8 54.81 134.42 54.81Z" /></svg>

View File

@@ -57,7 +57,6 @@ icon:bullet-on = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:unfold-hide = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M420-308q-8 0-14-5.5t-6-14.5v-304q0-9 6-14.5t14-5.5q2 0 14 6l145 145q5 5 7 10t2 11q0 6-2 11t-7 10L434-314q-3 3-6.5 4.5T420-308Z" /></svg>
icon:unfold-show = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M459-381 314-526q-3-3-4.5-6.5T308-540q0-8 5.5-14t14.5-6h304q9 0 14.5 6t5.5 14q0 2-6 14L501-381q-5 5-10 7t-11 2q-6 0-11-2t-10-7Z" /></svg>
icon:sb_build = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M760-252v72q0 8 6 14t14 6q8 0 14-6t6-14v-120q0-8-6-14t-14-6H660q-8 0-14 6t-6 14q0 8 6 14t14 6h72l-98 98q-6 6-6 14t6 14q6 6 14 6t14-6l98-98ZM200-120q-33 0-56.5-23.5T120-200v-560q0-33 23.5-56.5T200-840h560q33 0 56.5 23.5T840-760v200q0 17-11.5 28.5T800-520q-17 0-28.5-11.5T760-560v-200H200v560h200q17 0 28.5 11.5T440-160q0 17-11.5 28.5T400-120H200Zm0-120v40-560 243-3 280Zm80-80q0 17 11.5 28.5T320-280h83q17 0 28.5-11.5T443-320q0-17-11.5-28.5T403-360h-83q-17 0-28.5 11.5T280-320Zm0-160q0 17 11.5 28.5T320-440h200q17 0 28.5-11.5T560-480q0-17-11.5-28.5T520-520H320q-17 0-28.5 11.5T280-480Zm0-160q0 17 11.5 28.5T320-600h320q17 0 28.5-11.5T680-640q0-17-11.5-28.5T640-680H320q-17 0-28.5 11.5T280-640ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40Z" /></svg>
icon:sb_details = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M400-200q-17 0-28.5-11.5T360-240q0-17 11.5-28.5T400-280h400q17 0 28.5 11.5T840-240q0 17-11.5 28.5T800-200H400Zm0-240q-17 0-28.5-11.5T360-480q0-17 11.5-28.5T400-520h400q17 0 28.5 11.5T840-480q0 17-11.5 28.5T800-440H400Zm0-240q-17 0-28.5-11.5T360-720q0-17 11.5-28.5T400-760h400q17 0 28.5 11.5T840-720q0 17-11.5 28.5T800-680H400ZM200-160q-33 0-56.5-23.5T120-240q0-33 23.5-56.5T200-320q33 0 56.5 23.5T280-240q0 33-23.5 56.5T200-160Zm0-240q-33 0-56.5-23.5T120-480q0-33 23.5-56.5T200-560q33 0 56.5 23.5T280-480q0 33-23.5 56.5T200-400Zm0-240q-33 0-56.5-23.5T120-720q0-33 23.5-56.5T200-800q33 0 56.5 23.5T280-720q0 33-23.5 56.5T200-640Z" /></svg>
icon:sb_novel = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M200-313q10-3 19.5-5t20.5-2h40v-480h-40q-17 0-28.5 11.5T200-760v447Zm40 233q-50 0-85-35t-35-85v-560q0-50 35-85t85-35h240q17 0 28.5 11.5T520-840q0 17-11.5 28.5T480-800H360v480h240v-80q0-17 11.5-28.5T640-440q17 0 28.5 11.5T680-400v160H240q-17 0-28.5 11.5T200-200q0 17 11.5 28.5T240-160h520v-280q0-17 11.5-28.5T800-480q17 0 28.5 11.5T840-440v280q0 33-23.5 56.5T760-80H240Zm-40-233v-487 487Zm500-167q0-92 64-156t156-64q-92 0-156-64t-64-156q0 92-64 156t-156 64q92 0 156 64t64 156Z" /></svg>
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-240v-480 480Zm0 80q-33 0-56.5-23.5T80-240v-480q0-33 23.5-56.5T160-800h207q16 0 30.5 6t25.5 17l57 57h320q33 0 56.5 23.5T880-640v80q0 17-11.5 28.5T840-520q-17 0-28.5-11.5T800-560v-80H447l-80-80H160v480h280q17 0 28.5 11.5T480-200q0 17-11.5 28.5T440-160H160Zm400 40v-66q0-8 3-15.5t9-13.5l209-208q9-9 20-13t22-4q12 0 23 4.5t20 13.5l37 37q8 9 12.5 20t4.5 22q0 11-4 22.5T903-300L695-92q-6 6-13.5 9T666-80h-66q-17 0-28.5-11.5T560-120Zm300-223-37-37 37 37ZM620-140h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M380-320q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l224 224q11 11 11 28t-11 28q-11 11-28 11t-28-11L532-372q-30 24-69 38t-83 14Zm0-80q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" /></svg>

View File

@@ -57,7 +57,6 @@ icon:bullet-on = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:unfold-hide = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M436.4-350.31q-6.71 0-11.55-4.44-4.85-4.44-4.85-11.71v-227.08q0-7.27 4.95-11.71 4.95-4.44 11.55-4.44 1.65 0 11.35 5.23l107.07 107.08q3.7 3.69 5.7 7.97 2 4.28 2 9.41t-2 9.41q-2 4.28-5.7 7.98L447.83-355.52q-2.21 2.21-5.15 3.71-2.93 1.5-6.28 1.5Z" /></svg>
icon:unfold-show = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M462.61-405.08 355.52-512.17q-2.21-2.21-3.71-5.15-1.5-2.93-1.5-6.28 0-6.71 4.44-11.55 4.44-4.85 11.71-4.85h227.08q7.27 0 11.71 4.95 4.44 4.95 4.44 11.55 0 1.65-5.23 11.35L497.38-405.08q-3.69 3.7-7.97 5.7-4.28 2-9.41 2t-9.41-2q-4.28-2-7.98-5.7Z" /></svg>
icon:sb_build = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M763.85-257.38V-190q0 6.15 4.61 10.77 4.62 4.61 10.77 4.61 6.15 0 10.77-4.61 4.62-4.62 4.62-10.77v-103.85q0-6.46-4.85-11.3-4.85-4.85-11.31-4.85H674.62q-6.16 0-10.77 4.62-4.62 4.61-4.62 10.76 0 6.16 4.62 10.77 4.61 4.62 10.77 4.62H742l-90.31 90.31q-4.46 4.46-4.84 10.54-.39 6.07 4.84 11.3 4.74 4.46 11.07 4.46 6.32 0 10.78-4.46l90.31-90.3ZM224.62-160q-26.66 0-45.64-18.98T160-224.62v-510.76q0-26.66 18.98-45.64T224.62-800h510.76q26.66 0 45.64 18.98T800-735.38v204.61q0 8.5-5.76 14.25t-14.27 5.75q-8.51 0-14.24-5.75T760-530.77v-204.61q0-9.24-7.69-16.93-7.69-7.69-16.93-7.69H224.62q-9.24 0-16.93 7.69-7.69 7.69-7.69 16.93v510.76q0 9.24 7.69 16.93 7.69 7.69 16.93 7.69h204.61q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T429.23-160H224.62ZM200-239.73V-200v-560V-507.77v-3 271.04Zm100-89.47q0 8.51 5.75 14.24t14.25 5.73h118.38q8.5 0 14.25-5.76t5.75-14.27q0-8.51-5.75-14.24t-14.25-5.73H320q-8.5 0-14.25 5.76-5.75 5.75-5.75 14.27Zm0-150.77q0 8.51 5.75 14.24T320-460h259.23q8.5 0 14.25-5.76t5.75-14.27q0-8.51-5.75-14.24T579.23-500H320q-8.5 0-14.25 5.76T300-479.97Zm0-150.77q0 8.51 5.75 14.24t14.25 5.73h320q8.5 0 14.25-5.76 5.75-5.75 5.75-14.27 0-8.51-5.75-14.24T640-650.77H320q-8.5 0-14.25 5.76T300-630.74ZM720-75.38q-66.85 0-113.42-46.58Q560-168.54 560-235.38q0-66.85 46.58-113.43 46.57-46.57 113.42-46.57t113.42 46.57Q880-302.23 880-235.38q0 66.84-46.58 113.42Q786.85-75.38 720-75.38Z" /></svg>
icon:sb_details = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M404.62-220q-8.5 0-14.25-5.76t-5.75-14.27q0-8.51 5.75-14.24t14.25-5.73H780q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T780-220H404.62Zm0-240q-8.5 0-14.25-5.76t-5.75-14.27q0-8.51 5.75-14.24t14.25-5.73H780q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T780-460H404.62Zm0-240q-8.5 0-14.25-5.76t-5.75-14.27q0-8.51 5.75-14.24t14.25-5.73H780q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T780-700H404.62ZM213.08-186.92q-21.9 0-37.49-15.59Q160-218.11 160-240t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Zm0-240q-21.9 0-37.49-15.59Q160-458.11 160-480t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Zm0-240q-21.9 0-37.49-15.59Q160-698.11 160-720t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Z" /></svg>
icon:sb_novel = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M200-279.15q10.77-6.85 22.95-11.54 12.18-4.69 26.28-4.69h46.15V-800h-46.15q-20.92 0-35.08 14.58Q200-770.85 200-750.77v471.62ZM249.23-120q-37.18 0-63.2-25.58Q160-171.15 160-207.69v-543.08q0-37.18 26.03-63.2Q212.05-840 249.23-840H520q8.5 0 14.25 5.76t5.75 14.27q0 8.51-5.75 14.24T520-800H335.38v504.62h289.24V-440q0-8.5 5.75-14.25 5.76-5.75 14.27-5.75t14.24 5.75q5.74 5.75 5.74 14.25v184.62H249.23q-20.08 0-34.65 13.67Q200-228.04 200-207.83t14.58 34.02Q229.15-160 249.23-160h486.15q10.77 0 17.7-6.92 6.92-6.93 6.92-17.7V-480q0-8.5 5.76-14.25t14.27-5.75q8.51 0 14.24 5.75T800-480v295.38q0 26.66-18.98 45.64T735.38-120H249.23ZM200-279.15V-800v520.85ZM700-500q0-83.64 58.18-141.82Q816.36-700 900-700q-83.64 0-141.82-58.18Q700-816.36 700-900q0 83.64-58.18 141.82Q583.64-700 500-700q83.64 0 141.82 58.18Q700-583.64 700-500Z" /></svg>
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-240v-480 480Zm24.62 40q-27.62 0-46.12-18.5Q120-237 120-264.62v-430.76q0-27.62 18.5-46.12Q157-760 184.62-760H357q12.92 0 25.12 5.23 12.19 5.23 20.88 13.92L463.85-680h311.53q27.62 0 46.12 18.5Q840-643 840-615.38v67.69q0 8.54-5.73 14.27T820-527.69q-8.54 0-14.27-5.73T800-547.69v-67.69q0-9.24-7.69-16.93-7.69-7.69-16.93-7.69H447.77l-80-80H184.62q-10.77 0-17.7 6.92-6.92 6.93-6.92 17.7v430.76q0 9.24 7.69 16.93 7.69 7.69 16.93 7.69h269.23q8.53 0 14.27 5.73 5.73 5.73 5.73 14.27t-5.73 14.27q-5.74 5.73-14.27 5.73H184.62Zm384.61 47.69v-42.92q0-6.46 2.23-12.04 2.23-5.58 7.46-10.81l203.62-202.61q5.92-5.93 12.31-8 6.38-2.08 12.77-2.08 6.61 0 13.38 2.58 6.77 2.57 11.92 7.73l37 37.77q4.93 5.92 7.5 12.31Q880-364 880-357.62q0 6.39-2.46 12.89-2.46 6.5-7.62 12.42L667.31-129.69q-5.23 5.23-10.81 7.46-5.58 2.23-12.04 2.23h-42.92q-13.92 0-23.12-9.19-9.19-9.19-9.19-23.12Zm275.39-205.31-37-37.76 37 37.76Zm-240 202.24h38l138.69-138.93-18.77-19-18.23-19.54-139.69 139.47v38Zm157.92-157.93-18.23-19.54 37 38.54-18.77-19Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M381.54-350.77q-95.92 0-162.58-66.65-66.65-66.66-66.65-162.58 0-95.92 66.65-162.58 66.66-66.65 162.58-66.65 95.92 0 162.58 66.65 66.65 66.66 66.65 162.58 0 41.69-14.77 80.69t-38.77 66.69l236.31 236.31q5.61 5.62 6 13.77.38 8.16-6 14.54-6.39 6.38-14.16 6.38-7.76 0-14.15-6.38L528.92-404.31q-30 25.54-69 39.54t-78.38 14Zm0-40q79.61 0 134.42-54.81 54.81-54.8 54.81-134.42 0-79.62-54.81-134.42-54.81-54.81-134.42-54.81-79.62 0-134.42 54.81-54.81 54.8-54.81 134.42 0 79.62 54.81 134.42 54.8 54.81 134.42 54.81Z" /></svg>

View File

@@ -57,7 +57,6 @@ icon:bullet-on = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:unfold-hide = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M400-280v-400l200 200-200 200Z" /></svg>
icon:unfold-show = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-360 280-560h400L480-360Z" /></svg>
icon:sb_build = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="m648-140 112-112v92h40v-160H640v40h92L620-168l28 28Zm-528 20v-720h720v348q-19-9-39-15.5t-41-9.5v-243H200v560h242q3 22 9.5 42t15.5 38H120Zm80-120v40-560 243-3 280Zm80-40h163q3-21 9.5-41t14.5-39H280v80Zm0-160h244q32-30 71.5-50t84.5-27v-3H280v80Zm0-160h400v-80H280v80ZM720-40q-83 0-141.5-58.5T520-240q0-83 58.5-141.5T720-440q83 0 141.5 58.5T920-240q0 83-58.5 141.5T720-40Z" /></svg>
icon:sb_details = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M360-200v-80h480v80H360Zm0-240v-80h480v80H360Zm0-240v-80h480v80H360ZM200-160q-33 0-56.5-23.5T120-240q0-33 23.5-56.5T200-320q33 0 56.5 23.5T280-240q0 33-23.5 56.5T200-160Zm0-240q-33 0-56.5-23.5T120-480q0-33 23.5-56.5T200-560q33 0 56.5 23.5T280-480q0 33-23.5 56.5T200-400Zm0-240q-33 0-56.5-23.5T120-720q0-33 23.5-56.5T200-800q33 0 56.5 23.5T280-720q0 33-23.5 56.5T200-640Z" /></svg>
icon:sb_novel = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M200-313q10-3 19.5-5t20.5-2h40v-480h-40q-17 0-28.5 11.5T200-760v447Zm40 233q-50 0-85-35t-35-85v-560q0-50 35-85t85-35h280v80H360v480h240v-120h80v200H240q-17 0-28.5 11.5T200-200q0 17 11.5 28.5T240-160h520v-320h80v400H240Zm-40-233v-487 487Zm500-167q0-92 64-156t156-64q-92 0-156-64t-64-156q0 92-64 156t-156 64q92 0 156 64t64 156Z" /></svg>
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-240v-480 480Zm-80 80v-640h320l80 80h400v86l-80 79v-85H447l-80-80H160v480h324l-4 4v76H80Zm480 80v-123l263-262 123 122L683-80H560Zm300-263-37-37 37 37ZM620-140h38l121-122-18-19-19-18-122 121v38Zm141-141-19-18 37 37-18-19Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M784-120 532-372q-30 24-69 38t-83 14q-109 0-184.5-75.5T120-580q0-109 75.5-184.5T380-840q109 0 184.5 75.5T640-580q0 44-14 83t-38 69l252 252-56 56ZM380-400q75 0 127.5-52.5T560-580q0-75-52.5-127.5T380-760q-75 0-127.5 52.5T200-580q0 75 52.5 127.5T380-400Z" /></svg>

View File

@@ -57,7 +57,6 @@ icon:bullet-on = <svg xmlns="http://www.w3.org/2000/svg" height="128" view
icon:unfold-hide = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M420-327.69v-304.62L572.31-480 420-327.69Z" /></svg>
icon:unfold-show = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M480-387.69 327.69-540h304.62L480-387.69Z" /></svg>
icon:sb_build = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="m662.62-156.15 101.23-101.23v82.76h30.77V-310H659.23v30.77H742L640.77-178l21.85 21.85ZM160-160v-640h640v302.62q-10.54-3.62-20.15-5.89-9.62-2.27-19.85-4.5V-760H200v560h251.23q1.46 11.23 3.73 20.85 2.27 9.61 5.89 19.15H160Zm40-80v40-560 252.23-3V-240Zm100-69.23h158.38q2.24-10.23 6.04-19.85 3.81-9.61 7.96-20.15H300v40ZM300-460h263.23q19.69-13.85 38.81-23.46 19.11-9.62 41.04-15.08V-500H300v40Zm0-150.77h360v-40H300v40ZM720-75.38q-66.85 0-113.42-46.58Q560-168.54 560-235.38q0-66.85 46.58-113.43 46.57-46.57 113.42-46.57t113.42 46.57Q880-302.23 880-235.38q0 66.84-46.58 113.42Q786.85-75.38 720-75.38Z" /></svg>
icon:sb_details = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M384.62-220v-40H800v40H384.62Zm0-240v-40H800v40H384.62Zm0-240v-40H800v40H384.62ZM213.08-186.92q-21.9 0-37.49-15.59Q160-218.11 160-240t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Zm0-240q-21.9 0-37.49-15.59Q160-458.11 160-480t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Zm0-240q-21.9 0-37.49-15.59Q160-698.11 160-720t15.59-37.49q15.59-15.59 37.49-15.59 21.89 0 37.48 15.59 15.59 15.6 15.59 37.49t-15.59 37.49q-15.59 15.59-37.48 15.59Z" /></svg>
icon:sb_novel = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M200-279.15q10.77-6.85 22.95-11.54 12.18-4.69 26.28-4.69h46.15V-800h-46.15q-20.92 0-35.08 14.58Q200-770.85 200-750.77v471.62ZM249.23-120q-37.18 0-63.2-25.58Q160-171.15 160-207.69v-543.08q0-37.18 26.03-63.2Q212.05-840 249.23-840H540v40H335.38v504.62h289.24V-460h40v204.62H249.23q-20.08 0-34.65 13.67Q200-228.04 200-207.83t14.58 34.02Q229.15-160 249.23-160H760v-340h40v380H249.23ZM200-279.15V-800v520.85ZM700-500q0-83.64 58.18-141.82Q816.36-700 900-700q-83.64 0-141.82-58.18Q700-816.36 700-900q0 83.64-58.18 141.82Q583.64-700 500-700q83.64 0 141.82 58.18Q700-583.64 700-500Z" /></svg>
icon:sb_project = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M160-240v-480 480Zm-40 40v-560h263.85l80 80h378.46v66L800-572.69V-640H447.77l-80-80H160v480h314.77l-.92.92V-200H120Zm449.23 80v-88.38l238.39-237.39 87.61 88.15L657.62-120h-88.39Zm275.39-237.62-37-37.76 37 37.76Zm-240 202.24h38l138.69-138.93-18.77-19-18.23-19.54-139.69 139.47v38Zm157.92-157.93-18.23-19.54 37 38.54-18.77-19Z" /></svg>
icon:sb_search = <svg xmlns="http://www.w3.org/2000/svg" height="128" viewBox="0 -960 960 960" width="128" fill="#000000"><path d="M779.38-153.85 528.92-404.31q-30 25.54-69 39.54t-78.38 14q-96.1 0-162.67-66.53-66.56-66.53-66.56-162.57 0-96.05 66.53-162.71 66.53-66.65 162.57-66.65 96.05 0 162.71 66.56Q610.77-676.1 610.77-580q0 41.69-14.77 80.69t-38.77 66.69l250.46 250.47-28.31 28.3ZM381.54-390.77q79.61 0 134.42-54.81 54.81-54.8 54.81-134.42 0-79.62-54.81-134.42-54.81-54.81-134.42-54.81-79.62 0-134.42 54.81-54.81 54.8-54.81 134.42 0 79.62 54.81 134.42 54.8 54.81 134.42 54.81Z" /></svg>

View File

@@ -40,7 +40,7 @@ from novelwriter.common import (
jsonEncode,
safeExists,
)
from novelwriter.constants import nwFiles, nwKeyWords, nwStyles
from novelwriter.constants import nwFiles, nwKeyWords
from novelwriter.core.indexdata import NOTE_TYPES, TT_NONE, IndexHeading, IndexNode, T_NoteTypes
from novelwriter.enum import nwComment, nwItemClass, nwItemLayout, nwItemType, nwNovelExtra
from novelwriter.error import logException
@@ -639,48 +639,6 @@ class Index:
"""Return all note comment keys."""
return self._itemIndex.allNoteKeys()
def getNovelWordCount(self, rootHandle: str | None = None, activeOnly: bool = True) -> int:
"""Count the number of words in one or all novel roots."""
return sum(
hItem.wordCount
for _, _, hItem in self._itemIndex.iterNovelStructure(rHandle=rootHandle, activeOnly=activeOnly)
)
def getNovelTitleCounts(self, rootHandle: str | None = None, activeOnly: bool = True) -> list[int]:
"""Count the number of titles in one or all novel roots."""
hCount = [0, 0, 0, 0, 0]
for _, _, hItem in self._itemIndex.iterNovelStructure(rHandle=rootHandle, activeOnly=activeOnly):
iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
hCount[iLevel] += 1
return hCount
def getTableOfContents(
self,
rHandle: str | None,
maxDepth: int,
activeOnly: bool = True,
) -> list[tuple[str, int, str, int]]:
"""Generate a table of contents up to a maximum depth."""
tOrder = []
tData = {}
pKey = None
for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(rHandle=rHandle, activeOnly=activeOnly):
tKey = f"{tHandle}:{sTitle}"
iLevel = nwStyles.H_LEVEL.get(hItem.level, 0)
if iLevel > maxDepth:
if pKey in tData:
tData[pKey]["words"] += hItem.wordCount
else:
pKey = tKey
tOrder.append(tKey)
tData[tKey] = {
"level": iLevel,
"title": hItem.title,
"words": hItem.wordCount,
}
return [(tKey, tData[tKey]["level"], tData[tKey]["title"], tData[tKey]["words"]) for tKey in tOrder]
def getCounts(self, tHandle: str, sTitle: str | None = None) -> tuple[int, int, int]:
"""Return the counts for a file, or a section of a file,
starting at title sTitle if it is provided.

View File

@@ -107,19 +107,6 @@ VALID_MAP: dict[str, set[str]] = {
"colWidths",
"hideInactive",
},
"GuiNovelDetails": {
"winWidth",
"winHeight",
"widthCol0",
"widthCol1",
"widthCol2",
"widthCol3",
"widthCol4",
"wordsPerPage",
"countFrom",
"clearDouble",
"novelRoot",
},
"GuiProjectSearch": {
"searchFilters",
},

View File

@@ -173,11 +173,6 @@ class NWProject:
"""Return the project lock information."""
return self._storage.lockStatus
@property
def currentEditTime(self) -> int:
"""Return total edit time, including the current session."""
return self._data.editTime + round(time() - self._session.start)
@property
def currentTotalCount(self) -> int:
"""Return the current total word count from the tree."""

View File

@@ -208,11 +208,6 @@ class GuiMainMenu(QMenuBar):
self.aProjectSettings.setShortcut("Ctrl+Shift+,")
self.aProjectSettings.triggered.connect(self.mainGui.showProjectSettingsDialog)
# Project > Novel Details
self.aNovelDetails = qtAddAction(self.projMenu, self.tr("Novel Details"))
self.aNovelDetails.setShortcut("Shift+F6")
self.aNovelDetails.triggered.connect(self.mainGui.showNovelDetailsDialog)
# Project > Separator
self.projMenu.addSeparator()

View File

@@ -79,10 +79,6 @@ class GuiSideBar(QWidget):
self.tbTheme.setToolTip(self.tr("Switch Colour Theme"))
self.tbTheme.clicked.connect(self._cycleColorTheme)
self.tbDetails = NFlatIconButton(self, iSz, "sb_details:sidebar", 0.25)
self.tbDetails.setToolTip("{0} [Shift+F6]".format(self.tr("Novel Details")))
self.tbDetails.clicked.connect(self.mainGui.showNovelDetailsDialog)
self.tbStats = NFlatIconButton(self, iSz, "sb_stats:sidebar", 0.25)
self.tbStats.setToolTip("{0} [F6]".format(self.tr("Writing Statistics")))
self.tbStats.clicked.connect(self.mainGui.showWritingStatsDialog)
@@ -111,7 +107,6 @@ class GuiSideBar(QWidget):
self.outerBox.addWidget(self.tbStory)
self.outerBox.addWidget(self.tbBuild)
self.outerBox.addStretch(1)
self.outerBox.addWidget(self.tbDetails)
self.outerBox.addWidget(self.tbStats)
self.outerBox.addWidget(self.tbTheme)
self.outerBox.addWidget(self.tbSettings)
@@ -133,7 +128,6 @@ class GuiSideBar(QWidget):
self.tbSearch.refreshTheme()
self.tbStory.refreshTheme()
self.tbBuild.refreshTheme()
self.tbDetails.refreshTheme()
self.tbStats.refreshTheme()
self.tbTheme.refreshTheme()
self.tbSettings.refreshTheme()

View File

@@ -66,7 +66,6 @@ from novelwriter.gui.statusbar import GuiMainStatus
from novelwriter.manuscript.manuscript import GuiManuscript
from novelwriter.story.storyview import GuiStoryView
from novelwriter.tools.dictionaries import GuiDictionaries
from novelwriter.tools.noveldetails import GuiNovelDetails
from novelwriter.tools.welcome import GuiWelcome
from novelwriter.tools.writingstats import GuiWritingStats
@@ -773,14 +772,6 @@ class GuiMain(QMainWindow):
dialog.newProjectSettingsReady.connect(self._processProjectSettingsChanges)
dialog.exec()
@pyqtSlot()
def showNovelDetailsDialog(self) -> None:
"""Open the novel details dialog."""
if SHARED.hasProject:
dialog = GuiNovelDetails(self)
dialog.activateDialog()
dialog.updateValues()
@pyqtSlot()
def showBuildManuscriptDialog(self) -> None:
"""Open the build manuscript dialog."""

View File

@@ -1,506 +0,0 @@
"""
novelWriter - GUI Novel Info
============================
This file is a part of novelWriter
Copyright (C) 2024 Veronica Berglyd Olsen and novelWriter contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
""" # noqa
from __future__ import annotations
import logging
import math
from typing import TYPE_CHECKING
from PyQt6.QtCore import pyqtSlot
from PyQt6.QtWidgets import (
QAbstractItemView,
QDialogButtonBox,
QFormLayout,
QGridLayout,
QHBoxLayout,
QLabel,
QSpinBox,
QStackedWidget,
QTreeWidget,
QTreeWidgetItem,
QVBoxLayout,
QWidget,
)
from novelwriter import SHARED
from novelwriter.common import formatPercent, formatTime, numberToRoman
from novelwriter.enum import nwStandardButton
from novelwriter.extensions.configlayout import NColorLabel, NFixedPage, NScrollablePage
from novelwriter.extensions.modified import NNonBlockingDialog
from novelwriter.extensions.novelselector import NovelSelector
from novelwriter.extensions.pagedsidebar import NPagedSideBar
from novelwriter.extensions.switch import NSwitch
from novelwriter.types import QtAlignRight, QtDecorationRole, QtFontSemiBold, QtRoleDestruct
if TYPE_CHECKING:
from PyQt6.QtGui import QCloseEvent
logger = logging.getLogger(__name__)
class GuiNovelDetails(NNonBlockingDialog):
"""GUI: Novel Details Tool."""
PAGE_OVERVIEW = 1
PAGE_CONTENTS = 2
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
logger.debug("Create: GuiNovelDetails")
self.setObjectName("GuiNovelDetails")
self.setWindowTitle(self.tr("Novel Details"))
options = SHARED.project.options
self.setMinimumSize(500, 400)
self.resize(
options.getInt("GuiNovelDetails", "winWidth", 650),
options.getInt("GuiNovelDetails", "winHeight", 500),
)
# Title
self.titleLabel = NColorLabel(
self.tr("Novel Details"),
self,
color=SHARED.theme.helpText,
scale=NColorLabel.HEADER_SCALE,
indent=4,
)
# Novel Selector
self.novelSelector = NovelSelector(self)
self.novelSelector.refreshNovelList()
self.novelSelector.setHandle(
options.getString("GuiNovelDetails", "novelRoot", self.novelSelector.firstHandle or "")
)
# SideBar
self.sidebar = NPagedSideBar(self)
self.sidebar.setLabelColor(SHARED.theme.helpText)
self.sidebar.setAccessibleName(self.titleLabel.text())
self.sidebar.addButton(self.tr("Overview"), self.PAGE_OVERVIEW)
self.sidebar.addButton(self.tr("Contents"), self.PAGE_CONTENTS)
self.sidebar.setSelected(self.PAGE_OVERVIEW)
self.sidebar.buttonClicked.connect(self._sidebarClicked)
# Content
self.overviewPage = _OverviewPage(self)
self.contentsPage = _ContentsPage(self)
self.mainStack = QStackedWidget(self)
self.mainStack.addWidget(self.overviewPage)
self.mainStack.addWidget(self.contentsPage)
# Buttons
self.btnClose = SHARED.theme.getStandardButton(nwStandardButton.CLOSE, self)
self.btnClose.clicked.connect(self.closeDialog)
self.btnBox = QDialogButtonBox(self)
self.btnBox.addButton(self.btnClose, QtRoleDestruct)
# Assemble
self.topBox = QHBoxLayout()
self.topBox.addWidget(self.titleLabel)
self.topBox.addStretch(1)
self.topBox.addWidget(self.novelSelector, 1)
self.mainBox = QHBoxLayout()
self.mainBox.addWidget(self.sidebar)
self.mainBox.addWidget(self.mainStack)
self.mainBox.setContentsMargins(0, 0, 0, 0)
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.topBox)
self.outerBox.addLayout(self.mainBox)
self.outerBox.addWidget(self.btnBox)
self.outerBox.setSpacing(8)
self.setLayout(self.outerBox)
self.setSizeGripEnabled(True)
# Connect Signals
self.novelSelector.novelSelectionChanged.connect(self.overviewPage.novelValueChanged)
self.novelSelector.novelSelectionChanged.connect(self.contentsPage.novelValueChanged)
logger.debug("Ready: GuiNovelDetails")
def __del__(self) -> None: # pragma: no cover
"""Class destructor."""
logger.debug("Delete: GuiNovelDetails")
##
# Methods
##
def updateValues(self) -> None:
"""Load the dialogs initial values."""
if handle := self.novelSelector.handle:
self.overviewPage.updateProjectData()
self.overviewPage.novelValueChanged(handle)
self.contentsPage.novelValueChanged(handle)
##
# Events
##
def closeEvent(self, event: QCloseEvent) -> None:
"""Capture the user closing the window and save settings."""
self._saveSettings()
event.accept()
self.softDelete()
##
# Private Slots
##
@pyqtSlot(int)
def _sidebarClicked(self, pageId: int) -> None:
"""Process a user request to switch page."""
if pageId == self.PAGE_OVERVIEW:
self.mainStack.setCurrentWidget(self.overviewPage)
elif pageId == self.PAGE_CONTENTS:
self.mainStack.setCurrentWidget(self.contentsPage)
##
# Internal Functions
##
def _saveSettings(self) -> None:
"""Save the user GUI settings."""
logger.debug("Saving State: GuiNovelDetails")
novelRoot = self.novelSelector.handle
options = SHARED.project.options
options.setValue("GuiNovelDetails", "winWidth", self.width())
options.setValue("GuiNovelDetails", "winHeight", self.height())
options.setValue("GuiNovelDetails", "novelRoot", novelRoot)
self.contentsPage.saveSettings()
class _OverviewPage(NScrollablePage):
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
# Project Info
self.projLabel = NColorLabel(
self.tr("Project"), self, color=SHARED.theme.helpText, scale=NColorLabel.HEADER_SCALE
)
self.projName = QLabel("", self)
self.projWords = QLabel("", self)
self.projNovels = QLabel("", self)
self.projNotes = QLabel("", self)
self.projRevisions = QLabel("", self)
self.projEditTime = QLabel("", self)
self.projForm = QFormLayout()
self.projForm.addRow(self.tr("Name"), self.projName)
self.projForm.addRow(self.tr("Revisions"), self.projRevisions)
self.projForm.addRow(self.tr("Editing Time"), self.projEditTime)
self.projForm.addRow(self.tr("Word Count"), self.projWords)
self.projForm.addRow(f"\u2026 {self.tr('In Novels')}", self.projNovels)
self.projForm.addRow(f"\u2026 {self.tr('In Notes')}", self.projNotes)
self.projForm.setContentsMargins(8, 0, 0, 0)
self.projForm.setHorizontalSpacing(24)
self.projForm.setVerticalSpacing(4)
# Novel Info
self.novelLabel = NColorLabel(
self.tr("Selected Novel"),
self,
color=SHARED.theme.helpText,
scale=NColorLabel.HEADER_SCALE,
)
self.novelName = QLabel("", self)
self.novelWords = QLabel("", self)
self.novelChapters = QLabel("", self)
self.novelScenes = QLabel("", self)
self.novelForm = QFormLayout()
self.novelForm.addRow(self.tr("Name"), self.novelName)
self.novelForm.addRow(self.tr("Word Count"), self.novelWords)
self.novelForm.addRow(self.tr("Chapters"), self.novelChapters)
self.novelForm.addRow(self.tr("Scenes"), self.novelScenes)
self.novelForm.setContentsMargins(8, 0, 0, 0)
self.novelForm.setHorizontalSpacing(24)
self.novelForm.setVerticalSpacing(4)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.projLabel)
self.outerBox.addLayout(self.projForm)
self.outerBox.addWidget(self.novelLabel)
self.outerBox.addLayout(self.novelForm)
self.outerBox.setSpacing(16)
self.outerBox.addStretch(1)
self.setCentralLayout(self.outerBox)
##
# Methods
##
def updateProjectData(self) -> None:
"""Load information about the project."""
project = SHARED.project
project.updateCounts()
wcNovel, wcNotes, _, _ = project.data.currCounts
self.projName.setText(project.data.name)
self.projRevisions.setText(f"{project.data.saveCount:n}")
self.projEditTime.setText(formatTime(project.currentEditTime))
self.projWords.setText(f"{wcNovel + wcNotes:n}")
self.projNovels.setText(f"{wcNovel:n}")
self.projNotes.setText(f"{wcNotes:n}")
##
# Public Slots
##
@pyqtSlot(str)
def novelValueChanged(self, tHandle: str) -> None:
"""Refresh the data for the selected novel."""
project = SHARED.project
if nwItem := project.tree[tHandle]:
self.novelName.setText(nwItem.itemName)
nwCount = project.index.getNovelWordCount(rootHandle=tHandle)
self.novelWords.setText(f"{nwCount:n}")
hCounts = project.index.getNovelTitleCounts(rootHandle=tHandle)
self.novelChapters.setText(f"{hCounts[2]:n}")
self.novelScenes.setText(f"{hCounts[3]:n}")
class _ContentsPage(NFixedPage):
C_TITLE = 0
C_WORDS = 1
C_PAGES = 2
C_PAGE = 3
C_PROG = 4
def __init__(self, parent: QWidget) -> None:
super().__init__(parent=parent)
self._data = []
self._currentRoot = None
iPx = SHARED.theme.baseIconHeight
iSz = SHARED.theme.baseIconSize
options = SHARED.project.options
# Title
self.contentLabel = NColorLabel(
self.tr("Table of Contents"),
self,
color=SHARED.theme.helpText,
scale=NColorLabel.HEADER_SCALE,
)
# Contents Tree
self.tocTree = QTreeWidget(self)
self.tocTree.setIconSize(iSz)
self.tocTree.setIndentation(0)
self.tocTree.setColumnCount(6)
self.tocTree.setSelectionMode(QAbstractItemView.SelectionMode.NoSelection)
self.tocTree.setHeaderLabels([
self.tr("Title"),
self.tr("Words"),
self.tr("Pages"),
self.tr("Page"),
self.tr("Progress"),
"",
])
treeHeadItem = self.tocTree.headerItem()
if treeHeadItem: # pragma: no branch
treeHeadItem.setTextAlignment(self.C_WORDS, QtAlignRight)
treeHeadItem.setTextAlignment(self.C_PAGES, QtAlignRight)
treeHeadItem.setTextAlignment(self.C_PAGE, QtAlignRight)
treeHeadItem.setTextAlignment(self.C_PROG, QtAlignRight)
if header := self.tocTree.header(): # pragma: no branch
header.setStretchLastSection(True)
header.setMinimumSectionSize(12)
wCol0 = options.getInt("GuiNovelDetails", "widthCol0", 200)
wCol1 = options.getInt("GuiNovelDetails", "widthCol1", 60)
wCol2 = options.getInt("GuiNovelDetails", "widthCol2", 60)
wCol3 = options.getInt("GuiNovelDetails", "widthCol3", 60)
wCol4 = options.getInt("GuiNovelDetails", "widthCol4", 90)
self.tocTree.setColumnWidth(0, wCol0)
self.tocTree.setColumnWidth(1, wCol1)
self.tocTree.setColumnWidth(2, wCol2)
self.tocTree.setColumnWidth(3, wCol3)
self.tocTree.setColumnWidth(4, wCol4)
self.tocTree.setColumnWidth(5, 12)
# Options
wordsPerPage = options.getInt("GuiNovelDetails", "wordsPerPage", 350)
countFrom = options.getInt("GuiNovelDetails", "countFrom", 1)
clearDouble = options.getBool("GuiNovelDetails", "clearDouble", True)
self.wpValue = QSpinBox(self)
self.wpValue.setMinimum(10)
self.wpValue.setMaximum(1000)
self.wpValue.setSingleStep(10)
self.wpValue.setValue(wordsPerPage)
self.wpValue.valueChanged.connect(self._populateTree)
self.wpLabel = QLabel(self.tr("Words per page"), self)
self.wpLabel.setBuddy(self.wpValue)
self.poValue = QSpinBox(self)
self.poValue.setMinimum(1)
self.poValue.setMaximum(9999)
self.poValue.setSingleStep(1)
self.poValue.setValue(countFrom)
self.poValue.valueChanged.connect(self._populateTree)
self.poLabel = QLabel(self.tr("First page offset"), self)
self.poLabel.setBuddy(self.poValue)
self.dblValue = NSwitch(self, height=iPx)
self.dblValue.setChecked(clearDouble)
self.dblValue.clicked.connect(self._populateTree)
self.dblLabel = QLabel(self.tr("Chapters on odd pages"), self)
self.dblLabel.setBuddy(self.dblValue)
self.optionsBox = QGridLayout()
self.optionsBox.addWidget(self.wpLabel, 0, 0)
self.optionsBox.addWidget(self.wpValue, 0, 1)
self.optionsBox.addWidget(self.dblLabel, 0, 3)
self.optionsBox.addWidget(self.dblValue, 0, 4)
self.optionsBox.addWidget(self.poLabel, 1, 0)
self.optionsBox.addWidget(self.poValue, 1, 1)
self.optionsBox.setHorizontalSpacing(12)
self.optionsBox.setVerticalSpacing(4)
self.optionsBox.setColumnStretch(2, 1)
# Assemble
self.outerBox = QVBoxLayout()
self.outerBox.addWidget(self.contentLabel)
self.outerBox.addWidget(self.tocTree)
self.outerBox.addLayout(self.optionsBox)
self.setCentralLayout(self.outerBox)
def saveSettings(self) -> None:
"""Save the user GUI settings."""
options = SHARED.project.options
options.setValue("GuiNovelDetails", "widthCol0", self.tocTree.columnWidth(0))
options.setValue("GuiNovelDetails", "widthCol1", self.tocTree.columnWidth(1))
options.setValue("GuiNovelDetails", "widthCol2", self.tocTree.columnWidth(2))
options.setValue("GuiNovelDetails", "widthCol3", self.tocTree.columnWidth(3))
options.setValue("GuiNovelDetails", "widthCol4", self.tocTree.columnWidth(4))
options.setValue("GuiNovelDetails", "wordsPerPage", self.wpValue.value())
options.setValue("GuiNovelDetails", "countFrom", self.poValue.value())
options.setValue("GuiNovelDetails", "clearDouble", self.dblValue.isChecked())
##
# Public Slots
##
@pyqtSlot(str)
def novelValueChanged(self, tHandle: str) -> None:
"""Refresh the tree with another root item."""
if tHandle != self._currentRoot:
self._prepareData(tHandle)
self._populateTree()
self._currentRoot = tHandle
##
# Private Slots
##
@pyqtSlot()
def _populateTree(self) -> None:
"""Set the content of the chapter/page tree."""
dblPages = self.dblValue.isChecked()
wpPage = self.wpValue.value()
fstPage = self.poValue.value() - 1
pTotal = 0
tPages = 1
entries = []
for _, tLevel, tTitle, wCount in self._data:
pCount = math.ceil(wCount / wpPage)
if dblPages:
pCount += pCount % 2
pTotal += pCount
entries.append((tLevel, tTitle, wCount, pCount))
pMax = pTotal - fstPage
self.tocTree.clear()
for tLevel, tTitle, wCount, pCount in entries:
newItem = QTreeWidgetItem()
if tPages <= fstPage:
progPage = numberToRoman(tPages, True)
progText = ""
else:
cPage = tPages - fstPage
progPage = f"{cPage:n}"
progText = formatPercent(cPage - 1, divisor=pMax, prec=1)
hDec = SHARED.theme.getHeaderDecoration(tLevel)
if tTitle.strip() == "":
tTitle = self.tr("Untitled")
newItem.setData(self.C_TITLE, QtDecorationRole, hDec)
newItem.setText(self.C_TITLE, tTitle)
newItem.setText(self.C_WORDS, f"{wCount:n}")
newItem.setText(self.C_PAGES, f"{pCount:n}")
newItem.setText(self.C_PAGE, progPage)
newItem.setText(self.C_PROG, progText)
newItem.setTextAlignment(self.C_WORDS, QtAlignRight)
newItem.setTextAlignment(self.C_PAGES, QtAlignRight)
newItem.setTextAlignment(self.C_PAGE, QtAlignRight)
newItem.setTextAlignment(self.C_PROG, QtAlignRight)
# Make pages and titles/partitions stand out
if tLevel < 2:
bFont = newItem.font(self.C_TITLE)
if tLevel == 0:
bFont.setItalic(True)
else:
bFont.setWeight(QtFontSemiBold)
bFont.setUnderline(True)
newItem.setFont(self.C_TITLE, bFont)
tPages += pCount
self.tocTree.addTopLevelItem(newItem)
##
# Internal Functions
##
def _prepareData(self, rootHandle: str | None) -> None:
"""Extract the information from the project index."""
logger.debug("Populating ToC from handle '%s'", rootHandle)
self._data = SHARED.project.index.getTableOfContents(rootHandle, 2)
self._data.append(("", 0, self.tr("END"), 0))

View File

@@ -58,7 +58,6 @@
"unfold-show",
"sb_build",
"sb_details",
"sb_novel",
"sb_project",
"sb_search",

View File

@@ -939,42 +939,6 @@ def testIndex_ExtractData(nwGUI, fncPath, mockRnd):
del project.tree._items["0000000000000"]
del project.tree._nodes["0000000000000"]
# Extract stats
assert index.getNovelWordCount(activeOnly=False) == 43
assert index.getNovelWordCount(activeOnly=True) == 15
assert index.getNovelWordCount(rootHandle=C.hNovelRoot, activeOnly=False) == 43
assert index.getNovelWordCount(rootHandle=C.hNovelRoot, activeOnly=True) == 15
assert index.getNovelWordCount(rootHandle=C.hWorldRoot, activeOnly=False) == 0
assert index.getNovelWordCount(rootHandle=C.hWorldRoot, activeOnly=True) == 0
assert index.getNovelTitleCounts(activeOnly=False) == [0, 3, 2, 3, 0]
assert index.getNovelTitleCounts(activeOnly=True) == [0, 1, 2, 3, 0]
# Table of Contents
assert index.getTableOfContents(C.hNovelRoot, 0, activeOnly=True) == []
assert index.getTableOfContents(C.hNovelRoot, 1, activeOnly=True) == [
(f"{C.hTitlePage}:T0001", 1, "New Novel", 15),
]
assert index.getTableOfContents(C.hNovelRoot, 2, activeOnly=True) == [
(f"{C.hTitlePage}:T0001", 1, "New Novel", 5),
(f"{C.hChapterDoc}:T0001", 2, "New Chapter", 4),
(f"{hHandle}:T0001", 2, "Chapter One", 6),
]
assert index.getTableOfContents(C.hNovelRoot, 3, activeOnly=True) == [
(f"{C.hTitlePage}:T0001", 1, "New Novel", 5),
(f"{C.hChapterDoc}:T0001", 2, "New Chapter", 2),
(f"{C.hSceneDoc}:T0001", 3, "New Scene", 2),
(f"{hHandle}:T0001", 2, "Chapter One", 2),
(f"{sHandle}:T0001", 3, "Scene One", 2),
(f"{tHandle}:T0001", 3, "Scene Two", 2),
]
assert index.getTableOfContents(C.hNovelRoot, 0, activeOnly=False) == []
assert index.getTableOfContents(C.hNovelRoot, 1, activeOnly=False) == [
(f"{C.hTitlePage}:T0001", 1, "New Novel", 9),
(f"{nHandle}:T0001", 1, "Hello World!", 12),
(f"{nHandle}:T0002", 1, "Hello World!", 22),
]
assert index.saveIndex() is True
assert project.saveProject() is True
project.closeProject()

View File

@@ -125,27 +125,27 @@ def testOptionState_SetGet(mockGUI):
assert options.setValue("GuiProjectSettings", "winWidth", 100) is True
# Set some values of different types
assert options.setValue("GuiNovelDetails", "winWidth", 100) is True
assert options.setValue("GuiNovelDetails", "winHeight", 12.34) is True
assert options.setValue("GuiNovelDetails", "clearDouble", True) is True
assert options.setValue("GuiManuscript", "winWidth", 100) is True
assert options.setValue("GuiManuscript", "winHeight", 12.34) is True
assert options.setValue("GuiManuscript", "detailsExpanded", True) is True
assert options.setValue("GuiNovelView", "lastCol", nwColHidden) is True
# Generic get, doesn't check type
assert options.getValue("GuiNovelDetails", "winWidth", None) == 100
assert options.getValue("GuiNovelDetails", "winHeight", None) == 12.34
assert options.getValue("GuiNovelDetails", "clearDouble", None) is True
assert options.getValue("GuiNovelDetails", "mockItem", None) is None
assert options.getValue("GuiManuscript", "winWidth", None) == 100
assert options.getValue("GuiManuscript", "winHeight", None) == 12.34
assert options.getValue("GuiManuscript", "detailsExpanded", None) is True
assert options.getValue("GuiManuscript", "mockItem", None) is None
# Get type-specific
assert options.getString("GuiNovelDetails", "winWidth", None) is None # type: ignore
assert options.getString("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getInt("GuiNovelDetails", "winWidth", None) == 100 # type: ignore
assert options.getInt("GuiNovelDetails", "textFont", None) is None # type: ignore
assert options.getInt("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getFloat("GuiNovelDetails", "winWidth", None) == 100.0 # type: ignore
assert options.getFloat("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getBool("GuiNovelDetails", "clearDouble", None) is True # type: ignore
assert options.getBool("GuiNovelDetails", "mockItem", None) is None # type: ignore
assert options.getString("GuiManuscript", "winWidth", None) is None # type: ignore
assert options.getString("GuiManuscript", "mockItem", None) is None # type: ignore
assert options.getInt("GuiManuscript", "winWidth", None) == 100 # type: ignore
assert options.getInt("GuiManuscript", "textFont", None) is None # type: ignore
assert options.getInt("GuiManuscript", "mockItem", None) is None # type: ignore
assert options.getFloat("GuiManuscript", "winWidth", None) == 100.0 # type: ignore
assert options.getFloat("GuiManuscript", "mockItem", None) is None # type: ignore
assert options.getBool("GuiManuscript", "detailsExpanded", None) is True # type: ignore
assert options.getBool("GuiManuscript", "mockItem", None) is None # type: ignore
assert options.getEnum("GuiNovelView", "lastCol", nwNovelExtra, nwColHidden) == nwColHidden
# Get list

View File

@@ -470,13 +470,6 @@ def testNWProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd):
project.data.setAuthor(" Jane\tDoe ")
assert project.data.author == "Jane Doe"
# Edit Time
project.data.setEditTime(1234)
project._session._start = 1600000000
with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
assert project.currentEditTime == 6834
# Spell check
project.setProjectChanged(False)
project.data.setSpellCheck(True)

View File

@@ -94,7 +94,6 @@ def testGuiMain_ProjectBlocker(nwGUI):
nwGUI.openNextDocument(C.hSceneDoc, False, True)
nwGUI._autoSaveProject()
nwGUI.showProjectSettingsDialog()
nwGUI.showNovelDetailsDialog()
nwGUI.showBuildManuscriptDialog()
nwGUI.showProjectWordListDialog()
nwGUI.showWritingStatsDialog()

View File

@@ -35,7 +35,6 @@ from novelwriter.dialogs.projectsettings import GuiProjectSettings
from novelwriter.dialogs.wordlist import GuiWordList
from novelwriter.manuscript.manuscript import GuiManuscript
from novelwriter.tools.dictionaries import GuiDictionaries
from novelwriter.tools.noveldetails import GuiNovelDetails
from novelwriter.tools.welcome import GuiWelcome
from novelwriter.tools.writingstats import GuiWritingStats
@@ -71,7 +70,6 @@ def testGuiI18n_Localisation(qtbot, monkeypatch, language, nwGUI, projPath):
showDialog(nwGUI.showWelcomeDialog, GuiWelcome)
showDialog(nwGUI.showPreferencesDialog, GuiPreferences)
showDialog(nwGUI.showProjectSettingsDialog, GuiProjectSettings)
showDialog(nwGUI.showNovelDetailsDialog, GuiNovelDetails)
showDialog(nwGUI.showBuildManuscriptDialog, GuiManuscript)
showDialog(nwGUI.showProjectWordListDialog, GuiWordList)
showDialog(nwGUI.showWritingStatsDialog, GuiWritingStats)

View File

@@ -1,160 +0,0 @@
"""
novelWriter - Novel Details Tool Tests
======================================
This file is a part of novelWriter
Copyright (C) 2024 Veronica Berglyd Olsen and novelWriter contributors
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
""" # noqa
from __future__ import annotations
import pytest
from PyQt6.QtGui import QAction
from novelwriter import SHARED
from novelwriter.enum import nwItemClass
from novelwriter.tools.noveldetails import GuiNovelDetails
from tests.helpers import checkDialogFreedOnClose
@pytest.mark.gui
def testGuiNovelDetails_Main(qtbot, nwGUI, prjLipsum, ipsumText):
"""Test the Novel Details main dialog."""
nwGUI.openProject(prjLipsum)
nHandle = "b3643d0f92e32"
# Add a second Novel folder
project = SHARED.project
secondText = "#! Second\n\n" + "\n\n".join(ipsumText)
sHandle = project.newRoot(nwItemClass.NOVEL)
dHandle = project.newFile("Document", sHandle)
if item := project.tree[sHandle]:
item.setName("Second")
project.storage.getDocument(dHandle).writeDocument(secondText)
project.index.reIndexHandle(dHandle)
# Create the dialog
nwGUI.mainMenu.aNovelDetails.activate(QAction.ActionEvent.Trigger)
qtbot.waitUntil(lambda: SHARED.findTopLevelWidget(GuiNovelDetails) is not None, timeout=1000)
details = SHARED.findTopLevelWidget(GuiNovelDetails)
assert isinstance(details, GuiNovelDetails)
# Overview Page
# =============
overview = details.overviewPage
# The selector should default to the first entry
assert details.novelSelector.handle == nHandle
# Check project data
assert overview.projName.text() == "Lorem Ipsum"
assert overview.projWords.text() == f"{4382:n}"
assert overview.projNovels.text() == f"{3644:n}"
assert overview.projNotes.text() == f"{738:n}"
assert overview.projRevisions.text() != ""
assert overview.projEditTime.text() != ""
# Check novel data for "Novel"
assert overview.novelName.text() == "Novel"
assert overview.novelWords.text() == f"{3006:n}"
assert overview.novelChapters.text() == f"{3:n}"
assert overview.novelScenes.text() == f"{5:n}"
# Check novel data for "Second"
details.novelSelector.setHandle(sHandle, blockSignal=False)
assert overview.novelName.text() == "Second"
assert overview.novelWords.text() == f"{529:n}"
assert overview.novelChapters.text() == f"{0:n}"
assert overview.novelScenes.text() == f"{0:n}"
# Contents Page
# =============
details.novelSelector.setHandle(nHandle, blockSignal=False)
details.sidebar.button(details.PAGE_CONTENTS).click()
assert details.mainStack.currentIndex() == 1
contents = details.contentsPage
# Check defaults
words = [f"{v:n}" for v in [44, 176, 94, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [2, 2, 2, 2, 4, 6, 0]]
page = [f"{v:n}" for v in [1, 3, 5, 7, 9, 13, 19]]
for i in range(6):
item = contents.tocTree.topLevelItem(i)
assert item is not None
assert item.text(contents.C_WORDS) == words[i]
assert item.text(contents.C_PAGES) == pages[i]
assert item.text(contents.C_PAGE) == page[i]
# Change Settings
contents.poValue.setValue(7)
contents.wpValue.setValue(50)
words = [f"{v:n}" for v in [44, 176, 94, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [2, 4, 2, 2, 22, 34, 0]]
page = ["i", "iii"] + [f"{v:n}" for v in [1, 3, 5, 27, 61]]
for i in range(6):
item = contents.tocTree.topLevelItem(i)
assert item is not None
assert item.text(contents.C_WORDS) == words[i]
assert item.text(contents.C_PAGES) == pages[i]
assert item.text(contents.C_PAGE) == page[i]
# Turn off use odd pages
contents.dblValue.setChecked(False)
contents.poValue.setValue(0)
contents.wpValue.setValue(100)
words = [f"{v:n}" for v in [44, 176, 94, 6, 1071, 1615, 0]]
pages = [f"{v:n}" for v in [1, 2, 1, 1, 11, 17, 0]]
page = [f"{v:n}" for v in [1, 2, 4, 5, 6, 17, 34]]
for i in range(6):
item = contents.tocTree.topLevelItem(i)
assert item is not None
assert item.text(contents.C_WORDS) == words[i]
assert item.text(contents.C_PAGES) == pages[i]
assert item.text(contents.C_PAGE) == page[i]
# Revert to Overview page
details.sidebar.button(details.PAGE_OVERVIEW).click()
assert details.mainStack.currentIndex() == 0
# Selecting the same root again does not rebuild the contents tree
contents.novelValueChanged(nHandle)
# A sidebar click with an unknown page ID does nothing
details._sidebarClicked(-1)
assert details.mainStack.currentIndex() == 0
# An unknown handle leaves the overview novel data unchanged
novelName = overview.novelName.text()
overview.novelValueChanged("0000000000000")
assert overview.novelName.text() == novelName
# With no novel selected, updating the dialog values does nothing
details.novelSelector.setCurrentIndex(-1)
assert details.novelSelector.handle is None
details.updateValues()
# qtbot.stop()
details.close()
@pytest.mark.gui
def testGuiNovelDetails_MemoryLeakRegression(qtbot, nwGUI, prjLipsum):
"""Test that the dialog is freed when it is closed."""
nwGUI.openProject(prjLipsum)
checkDialogFreedOnClose(qtbot, lambda: GuiNovelDetails(nwGUI))

View File

@@ -100,7 +100,6 @@ ICONS = [
"unfold-hide",
"unfold-show",
"sb_build",
"sb_details",
"sb_novel",
"sb_project",
"sb_search",

View File

@@ -58,7 +58,6 @@
"unfold-show": "caret-down",
"sb_build": "up-right-from-square",
"sb_details": "list",
"sb_novel": "book-open",
"sb_project": "pen-to-square",
"sb_search": "magnifying-glass",

View File

@@ -58,7 +58,6 @@
"unfold-show": "chevron-down",
"sb_build": "blocks",
"sb_details": "view",
"sb_novel": "book",
"sb_project": "folder-tree",
"sb_search": "book-search",

View File

@@ -58,7 +58,6 @@
"unfold-show": "arrow_drop_down",
"sb_build": "export_notes",
"sb_details": "format_list_bulleted",
"sb_novel": "book_4_spark",
"sb_project": "bookmark_manager",
"sb_search": "search",

View File

@@ -58,7 +58,6 @@
"unfold-show": "arrow-down-s-fill",
"sb_build": "stack",
"sb_details": "pages",
"sb_novel": "booklet",
"sb_project": "file-edit",
"sb_search": "file-search",