Drop Novel Details tool (#3044)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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",
|
||||
},
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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))
|
||||
@@ -58,7 +58,6 @@
|
||||
"unfold-show",
|
||||
|
||||
"sb_build",
|
||||
"sb_details",
|
||||
"sb_novel",
|
||||
"sb_project",
|
||||
"sb_search",
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
@@ -100,7 +100,6 @@ ICONS = [
|
||||
"unfold-hide",
|
||||
"unfold-show",
|
||||
"sb_build",
|
||||
"sb_details",
|
||||
"sb_novel",
|
||||
"sb_project",
|
||||
"sb_search",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in New Issue
Block a user