diff --git a/.flake8 b/.flake8 index adf39902..15525dce 100644 --- a/.flake8 +++ b/.flake8 @@ -1,4 +1,5 @@ [flake8] -ignore = E133,E221,E226,E228,E241,W503 +ignore = E133,E221,E226,E228,E241,W503,ANN101,ANN102,ANN401 +per-file-ignores=tests/*:ANN max-line-length = 99 exclude = docs/* diff --git a/.github/workflows/syntax.yml b/.github/workflows/syntax.yml index 1a8a3dd7..7a2f2844 100644 --- a/.github/workflows/syntax.yml +++ b/.github/workflows/syntax.yml @@ -4,11 +4,11 @@ on: push: branches: - main - - releases/** + - release pull_request: branches: - main - - releases/** + - release jobs: checkSyntax: @@ -22,9 +22,9 @@ jobs: - name: Checkout Source uses: actions/checkout@v4 - name: Install flake8 - run: pip install flake8 flake8-pep585 + run: pip install -r requirements-dev.txt - name: Syntax Check run: | flake8 --version flake8 novelwriter --count --show-source --statistics - flake8 tests --count --show-source --statistics + flake8 tests --count --show-source --statistics --extend-ignore ANN diff --git a/.github/workflows/test_linux.yml b/.github/workflows/test_linux.yml index f15cc424..2ca682ca 100644 --- a/.github/workflows/test_linux.yml +++ b/.github/workflows/test_linux.yml @@ -4,11 +4,11 @@ on: push: branches: - main - - releases/** + - release pull_request: branches: - main - - releases/** + - release jobs: testLinux: @@ -38,4 +38,6 @@ jobs: export QT_QPA_PLATFORM=offscreen pytest -v --cov=novelwriter --timeout=60 - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/test_mac.yml b/.github/workflows/test_mac.yml index eb176743..de4aebc5 100644 --- a/.github/workflows/test_mac.yml +++ b/.github/workflows/test_mac.yml @@ -4,11 +4,11 @@ on: push: branches: - main - - releases/** + - release pull_request: branches: - main - - releases/** + - release jobs: testMac: @@ -32,4 +32,6 @@ jobs: export QT_QPA_PLATFORM=offscreen pytest -v --cov=novelwriter --timeout=60 - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/.github/workflows/test_win.yml b/.github/workflows/test_win.yml index 09d935ea..9cb28ba0 100644 --- a/.github/workflows/test_win.yml +++ b/.github/workflows/test_win.yml @@ -4,11 +4,11 @@ on: push: branches: - main - - releases/** + - release pull_request: branches: - main - - releases/** + - release jobs: testWin: @@ -28,4 +28,6 @@ jobs: run: | pytest -v --cov=novelwriter --timeout=60 - name: Upload to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4737c3b8..50d50e09 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,14 +22,13 @@ just make a pull request directly. ## Picking the Correct Branch for a Pull Request -As of April 2024, pre-releases and point releases (like 2.x) are made from the `main` branch. The -`main` branch is the default branch. For general changes, please make a new branch in your own fork -from the current `main` branch. Do not make pull requests from your copy of the `main` branch. +The `main` branch is the default branch. For general changes, please make a new branch in your own +fork from the current `main` branch. Do not make pull requests from your copy of the `main` branch. -If you are submitting a fix to a current release, say 2.4, you must do so from the correct release -branch. For 2.4 this is `releases/v2.4`. If you make such a fix on the `main` branch, **it cannot -be included in a 2.4.x release**. This also applies to the current documentation published on the -main website. +As of April 2024, releases are made from the `release` branch. If you are submitting a fix to a +current release you must do so from the `release` branch. If you make such a fix on the `main` +branch, **it cannot be included in a patch release**. This also applies to the documentation for +the latest release published on the main website. **Also check the following:** diff --git a/README.md b/README.md index f7323f56..041126c5 100644 --- a/README.md +++ b/README.md @@ -49,9 +49,9 @@ code, please also read the full Project credits are available in [CREDITS.md](https://github.com/vkbo/novelWriter/blob/main/CREDITS.md). -**Note:** As of April 2024, pre-releases and point releases (2.x) are made from the `main` branch. -Patches are made from `releases/v2.x` branches. So if you're submitting a fix to a current release, -**including changes to documentation**, they must be made to the correct branch. +**Note:** As of April 2024 only pre-releases are made from the `main` branch. Full releases are +made from the `release` branch. So if you're submitting a fix to a current release, **including +changes to documentation**, they must be made to the `release` branch. ### Translations diff --git a/docs/FileFormatSpec-1.5.fodt b/docs/FileFormatSpec-1.5.fodt index 5721dff1..b3a392aa 100644 --- a/docs/FileFormatSpec-1.5.fodt +++ b/docs/FileFormatSpec-1.5.fodt @@ -1,24 +1,24 @@ - Veronica Olsen2022-11-05T18:25:44.8874731592024-03-11T11:06:21.968626872PT4H19M4S57LibreOffice/7.4.7.2$Linux_X86_64 LibreOffice_project/40$Build-2 + Veronica Olsen2022-11-05T18:25:44.8874731592024-04-13T01:32:31.183366872PT4H34M31S61LibreOffice/7.4.7.2$Linux_X86_64 LibreOffice_project/40$Build-2Veronica Olsen - 141 + 100612 0 - 27406 - 16198 + 26857 + 22264 true false view2 - 8186 - 12282 + 14092 + 111391 0 - 141 - 27404 - 16337 + 100612 + 26855 + 122874 0 1 false @@ -90,7 +90,7 @@ true true - 928650 + 1056439 true false @@ -158,7 +158,7 @@ - + @@ -682,6 +682,15 @@ + + + + + + + + + @@ -1130,147 +1139,163 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -1290,78 +1315,87 @@ - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + + + + + + + + + + @@ -1440,21 +1474,24 @@ novelWriter - Project File Format 1.5 Specification - This document covers the file format specification for the 1.5 file format for novelWriter project files. See the documentation1 - See: https://docs.novelwriter.io for a full description of the app’s functionality. The file format was intrioduced in Release 2.0. + Project File Format 1.5 Specification + This document covers the file format specification for the 1.5 file format for novelWriter project files. See the documentation1 + See: https://docs.novelwriter.io for a full description of the app’s functionality. The file format was intrioduced in Release 2.0. The target audience of this document is developers who intend to write a tool or script that generates valid novelWriter project files from data from other applications, or for templating purposes. - Note that flags are generally written out as yes/no values, but also true/false and on/off are understood by the application. The None value that occasionally occurs in actual project files is the text representation of the Python None value, and indicates that there is no value set. + Note that flags are generally written out as yes/no values, but also true/false and on/off are understood by the application. The None value that occasionally occurs in actual project files is the text representation of the Python None value, and indicates that there is no value set. Revisions - From Release 2.1 Beta 1: Removed the titleFormat node from settings. + From Release 2.1 Beta 1: Removed the titleFormat node from settings. - From Release 2.3 Beta 1: Removed the title node from project. + From Release 2.3 Beta 1: Removed the title node from project. - From Release 2.3: Added TEMPLATE item class. + From Release 2.3: Added TEMPLATE item class. + + + From Release 2.5: Added the shape node XML Root @@ -1478,7 +1515,7 @@ - appVersion + appVersion No @@ -1489,7 +1526,7 @@ - hexVersion + hexVersion No @@ -1500,18 +1537,18 @@ - fileVersion + fileVersion Yes - The file format version used when writing the project file. This determines how the file is parsed, so it is important that it is set correctly. Valid values are “1.0”, “1.1”, “1.2”, “1.3”, “1.4”, and “1.5”. + The file format version used when writing the project file. This determines how the file is parsed, so it is important that it is set correctly. Valid values are “1.0”, “1.1”, “1.2”, “1.3”, “1.4”, and “1.5”. - fileRevision + fileRevision No @@ -1522,7 +1559,7 @@ - timeStamp + timeStamp No @@ -1533,12 +1570,12 @@ Example - <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" - fileVersion="1.5" fileRevision="1" - timeStamp="2022-11-05 17:46:51"> + <novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" + fileVersion="1.5" fileRevision="1" + timeStamp="2022-11-05 17:46:51"> Project Node Tag: project - The project section is a level one node under the root. It stores the primary settings for the project. The only required values are the id attribute and the name value. All other entries are set to default values during parsing if ommitted from the XML. + The project section is a level one node under the root. It stores the primary settings for the project. The only required values are the id attribute and the name value. All other entries are set to default values during parsing if ommitted from the XML. Attributes @@ -1557,18 +1594,18 @@ - id + id - Yes + Yes - A UUID that is unique for the project and does not change during the lifetime of the project. It is used by novelWriter to keep track of temporary files and to associate cross-project settings with specific projects without relying on the project name. + A UUID that is unique for the project and does not change during the lifetime of the project. It is used by novelWriter to keep track of temporary files and to associate cross-project settings with specific projects without relying on the project name. - saveCount + saveCount No @@ -1579,7 +1616,7 @@ - autoCount + autoCount No @@ -1590,7 +1627,7 @@ - editTime + editTime No @@ -1618,48 +1655,48 @@ - name + name - Yes + Yes - The name the user has given to the project. + The name the user has given to the project. - title + title - No + No - Removed in Revision 2. + Removed in Revision 2. - author + author No - The name of the novel author or authors. The value is used in some places in the user interface, and can be added to manuscript builds. + The name of the novel author or authors. The value is used in some places in the user interface, and can be added to manuscript builds. Example - <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" - saveCount="5" autoCount="10" editTime="1000"> - <name>Sample Project</name> - <author>Jane Smith</author> + <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" + saveCount="5" autoCount="10" editTime="1000"> + <name>Sample Project</name> + <author>Jane Smith</author> </project> - - Settings Node + + Settings Node Tag: settings - The settings section is a level one node under the root. It stores the various runtime values for the project, and the settings available from the Project Settings panel. None of these settings are required, and if missing, will be set to default values. The entire settings section can thus be ommitted. + The settings section is a level one node under the root. It stores the various runtime values for the project, and the settings available from the Project Settings panel. None of these settings are required, and if missing, will be set to default values. The entire settings section can thus be ommitted. Attributes None Values @@ -1680,18 +1717,18 @@ - doBackup + doBackup - No + No - User-controlled setting for whether or not to run backup when the project is closed. + User-controlled setting for whether or not to run backup when the project is closed. - language + language No @@ -1702,7 +1739,7 @@ - spellChecking + spellChecking No @@ -1715,18 +1752,18 @@ - lastHandle + lastHandle No - An auto-generated list of the last documents open in the editor and viewer, and which novel root folder was last viewed in the Novel Tree and Outline View. These are saved as key/value pairs. See separate section for how they are stored as XML. + An auto-generated list of the last documents open in the editor and viewer, and which novel root folder was last viewed in the Novel Tree and Outline View. These are saved as key/value pairs. See separate section for how they are stored as XML. - autoReplace + autoReplace No @@ -1737,7 +1774,7 @@ - titleFormat + titleFormat No @@ -1748,39 +1785,39 @@ - status + status No - The status labels as defined by the user in Project Settings. These are saved as special key/value pars. See separate section for how they are stored as XML. + The status labels as defined by the user in Project Settings. These are saved as special key/value pars. See separate section for how they are stored as XML. - importance + importance No - The importance labels as defined by the user in Project Settings. These are saved as special key/value pars. See separate section for how they are stored as XML. + The importance labels as defined by the user in Project Settings. These are saved as special key/value pars. See separate section for how they are stored as XML. Example - <settings> - <doBackup>yes</doBackup> - <language>en_GB</language> - <spellChecking auto="yes">en_GB</spellChecking> - <lastHandle /> - <autoReplace /> - <status /> - <importance /> - </settings> - - Key/Value Nodes + <settings> + <doBackup>yes</doBackup> + <language>en_GB</language> + <spellChecking auto="yes">en_GB</spellChecking> + <lastHandle /> + <autoReplace /> + <status /> + <importance /> + </settings> + + Key/Value Nodes The key/value nodes are used for lastHandle, autoReplace and titleFormat settings. The lookup key is stored as an attribute, and the value is the text of the node. Attributes @@ -1800,25 +1837,25 @@ - key + key - Yes + Yes - The lookup key. + The lookup key. Example - <lastHandle> - <entry key="editor">636b6aa9b697b</entry> - <entry key="viewer">636b6aa9b697b</entry> - <entry key="novelTree">7031beac91f75</entry> - <entry key="outline">7031beac91f75</entry> - </lastHandle> + <lastHandle> + <entry key="editor">636b6aa9b697b</entry> + <entry key="viewer">636b6aa9b697b</entry> + <entry key="novelTree">7031beac91f75</entry> + <entry key="outline">7031beac91f75</entry> + </lastHandle> Status/Importance Key/Value Nodes - The status and importance settings are stored as key/value nodes with additional data attributes. The label of the status or importance setting is saved as the node text value. + The status and importance settings are stored as key/value nodes with additional data attributes. The label of the status or importance setting is saved as the node text value. Attributes @@ -1837,18 +1874,18 @@ - key + key - Yes + Yes - The lookup key. For status labels, it must consist of an “s” followed by six hexadecimal numbers. For importance labels it must consist of an “i” followed by six hexadecimal numbers. Each key string must be unique. Internally, they are random numbers, but also sequential values are valid. + The lookup key. For status labels, it must consist of an “s” followed by six hexadecimal numbers. For importance labels it must consist of an “i” followed by six hexadecimal numbers. Each key string must be unique. Internally, they are random numbers, but also sequential values are valid. - count + count No @@ -1859,7 +1896,7 @@ - red + red Yes @@ -1870,47 +1907,61 @@ - green + green Yes - A number between 0 and 255 representing the green component of the label colour. + A number between 0 and 255 representing the green component of the label colour. - blue + blue Yes - A number between 0 and 255 representing the blue component of the label colour. + A number between 0 and 255 representing the blue component of the label colour. + + + + + shape + + + No + + + An icon shape. Allowed vaues are SQUARE, TRIANGLE, NABLA, DIAMOND, PENTAGON, HEXAGON, STAR, PACMAN, CIRCLE_Q, CIRCLE_H, CIRCLE_T, CIRCLE, BARS_1, BARS_2, BARS_3, BARS_4, BLOCK_1, BLOCK_2, BLOCK_3, BLOCK_4. Defaults to SQUARE. + + Added in Revesion 4. - Example - <status> - <entry key="sf12341" count="4" - red="100" green="100" blue="100">New</entry> - <entry key="sd51c5b" count="0" - red="193" green="129" blue="0">Draft</entry> - <entry key="s78ea90" count="1" - red="58" green="180" blue="58">Finished</entry> - </status> - <importance> - <entry key="ia857f0" count="5" - red="100" green="100" blue="100">None</entry> - <entry key="icfb3a5" count="2" - red="0" green="122" blue="188">Minor</entry> - <entry key="i2d7a54" count="2" - red="21" green="0" blue="180">Major</entry> - </importance> - Content Node + + Example + <status> + <entry key="sf12341" count="4" red="100" green="100" blue="100" + shape="SQUARE">New</entry> + <entry key="sd51c5b" count="0" red="193" green="129" blue="0" + shape="SQUARE">Draft</entry> + <entry key="s78ea90" count="1" red="58" green="180" blue="58" + shape="SQUARE">Finished</entry> + </status> + <importance> + <entry key="ia857f0" count="5" red="100" green="100" blue="100" + shape="CIRCLE">None</entry> + <entry key="icfb3a5" count="2" red="0" green="122" blue="188" + shape="CIRCLE">Minor</entry> + <entry key="i2d7a54" count="2" red="21" green="0" blue="180" + shape="CIRCLE">Major</entry> + </importance> + Content Node Tag: content - The content section is a level one node under the root. It stores all the project items of the project. The items are stored in the order in which they appear in the project tree, so altering the order will affect the project structure. The attribute with the order number is not used when processing the data. It is written to the XML for debugging purposes. + The content section is a level one node under the root. It stores all the project items of the project. The items are stored in the order in which they appear in the project tree, so altering the order will affect the project structure. The attribute with the order number is not used when processing the data. It is written to the XML for debugging purposes. Attributes @@ -1929,35 +1980,35 @@ - items + items No - The number of project items in the content section. + The number of project items in the content section. - novelWords + novelWords No - The number of words in total for all document nodes with layout DOCUMENT. The value defaults to 0, but this number forms the basis of computing writing statistics during a session, so the number should be properly set to ensure correct statistics. + The number of words in total for all document nodes with layout DOCUMENT. The value defaults to 0, but this number forms the basis of computing writing statistics during a session, so the number should be properly set to ensure correct statistics. - notesWords + notesWords No - The number of words in total for all document nodes with layout NOTE. The value defaults to 0, but this number forms the basis of computing writing statistics during a session, so the number should be properly set to ensure correct statistics. + The number of words in total for all document nodes with layout NOTE. The value defaults to 0, but this number forms the basis of computing writing statistics during a session, so the number should be properly set to ensure correct statistics. @@ -1979,32 +2030,32 @@ - item + item - No + No - A node representing a project item. + A node representing a project item. Example - <content items="27" novelWords="954" notesWords="409"> - <item handle="7031beac91f75" parent="None" root="7031beac91f75" - order="0" type="ROOT" class="NOVEL"> - <meta expanded="yes"/> - <name status="sc24b8f" import="ia857f0">Novel</name> - </item> - ... - </content> - Item Nodes - The item nodes make up the actual project content of the project. Each node represents either a root folder, a regular folder, or a document. Each node has a type, class and layout setting that determine its category. Each item is given a handle that is a random hexadecimal string of length 13. - For the items that are document files, the handle corresponds to its expected filename. It is therefore important that they match in generated projects. Each document node is expected to correspond to a file in the contents folder of the project named “content/7031beac91f75.nwd” for the item handle “7031beac91f75”. If there is no such file, the document item is assumed to contain no text, and a file is created when the user tries to save text to it. - Each item node has a meta data node and a name node. The meta data node contains only collected information, and is thus not strictly required. However, an accurate word count and setting the correct icon in the project tree depend on these values. These values are set by the indexer class, so rebuilding the index should restore the data. - The name nod - e contains the primary user defined settings for an item, and is required for the entry to be valid. - Attributes + <content items="27" novelWords="954" notesWords="409"> + <item handle="7031beac91f75" parent="None" root="7031beac91f75" + order="0" type="ROOT" class="NOVEL"> + <meta expanded="yes"/> + <name status="sc24b8f" import="ia857f0">Novel</name> + </item> + ... + </content> + Item Nodes + The item nodes make up the actual project content of the project. Each node represents either a root folder, a regular folder, or a document. Each node has a type, class and layout setting that determine its category. Each item is given a handle that is a random hexadecimal string of length 13. + For the items that are document files, the handle corresponds to its expected filename. It is therefore important that they match in generated projects. Each document node is expected to correspond to a file in the contents folder of the project named “content/7031beac91f75.nwd” for the item handle “7031beac91f75”. If there is no such file, the document item is assumed to contain no text, and a file is created when the user tries to save text to it. + Each item node has a meta data node and a name node. The meta data node contains only collected information, and is thus not strictly required. However, an accurate word count and setting the correct icon in the project tree depend on these values. These values are set by the indexer class, so rebuilding the index should restore the data. + The name node contains the primary user defined settings for an item, and is required for the entry to be valid. + + Attributes @@ -2022,81 +2073,81 @@ - handle + handle - Yes + Yes - The 13 value hexadecimal string that represents the item in the project. This is the primary identifier of a project item, and is required. It must be unique withing a project. Internally, it is generated as a random value, but it can also be sequential. + The 13 value hexadecimal string that represents the item in the project. This is the primary identifier of a project item, and is required. It must be unique withing a project. Internally, it is generated as a random value, but it can also be sequential. - parent + parent - Yes + Yes - The handle of the parent item in the tree. This value should only be None for root folders. All other items must have a parent handle set. If the parent handle is None for an item that isn’t a root folder, the item will be treated as orphaned during project loading. + The handle of the parent item in the tree. This value should only be None for root folders. All other items must have a parent handle set. If the parent handle is None for an item that isn’t a root folder, the item will be treated as orphaned during project loading. - root + root - No + No - The handle of the top of its hierarchy of parent items. That is, the root folder which it ultimately sits under. If the attribute is not set, it will be computed during loading. It is saved to the XML file for efficiency reasons. + The handle of the top of its hierarchy of parent items. That is, the root folder which it ultimately sits under. If the attribute is not set, it will be computed during loading. It is saved to the XML file for efficiency reasons. - order + order - No + No - The numerical order of the item under its parent item. This value is not used during the loading process as the physical item order in the content node is used instead. It is primarily saved to the XML for debugging purposes. + The numerical order of the item under its parent item. This value is not used during the loading process as the physical item order in the content node is used instead. It is primarily saved to the XML for debugging purposes. - type + type - Yes + Yes - The item type of the item node. Allowed values are ROOT, FOLDER and FILE. + The item type of the item node. Allowed values are ROOT, FOLDER and FILE. - class + class - Yes + Yes - The item class of the item node. There are a number of item classes available in the app, all corresponding to a specific type of root folder. Each item in a root folder should have the same class set as the root folder itself. The attribute is not strictly required for items that aren’t root folders as it will be automatically set to match during loading. Allowed values are NOVEL, PLOT, CHARACTER, WORLD, TIMELINE, OBJECT, ENTITY, CUSTOM, ARCHIVE and TRASH. - - Added in Revision 3: TEMPLATE + The item class of the item node. There are a number of item classes available in the app, all corresponding to a specific type of root folder. Each item in a root folder should have the same class set as the root folder itself. The attribute is not strictly required for items that aren’t root folders as it will be automatically set to match during loading. Allowed values are NOVEL, PLOT, CHARACTER, WORLD, TIMELINE, OBJECT, ENTITY, CUSTOM, ARCHIVE and TRASH. + + Added in Revision 3: TEMPLATE - layout + layout - Yes + Yes - The item layout of the item node. Allowed values are DOCUMENT and NOTE. This is the attribute that determines if a FILE type item is a Novel Document or a Project Note. + The item layout of the item node. Allowed values are DOCUMENT and NOTE. This is the attribute that determines if a FILE type item is a Novel Document or a Project Note. @@ -2118,29 +2169,29 @@ - meta + meta - No + No - A node of meta data attributes for the item. + A node of meta data attributes for the item. - name + name - Yes + Yes - A node of user settings for the item. + A node of user settings for the item. - Meta Nodes - The meta data collected for the current item. This data either represents the item’s last state in the app, or consists of data collected by the indexer. It can be restored by rebuilding the index, so the data is not essential. + Meta Nodes + The meta data collected for the current item. This data either represents the item’s last state in the app, or consists of data collected by the indexer. It can be restored by rebuilding the index, so the data is not essential. Attributes @@ -2159,73 +2210,73 @@ - expanded + expanded - No + No - Whether the tree node in the project tree was expanded or collapsed during the last session. Applies to all item types, but is ignored for items without child items. + Whether the tree node in the project tree was expanded or collapsed during the last session. Applies to all item types, but is ignored for items without child items. - heading + heading - No + No - The heading level of the first heading of the text of the item. Only applies to FILE item types. Allowed values are H0, H1, H2, H3, and H4. Other values are reset to H0. + The heading level of the first heading of the text of the item. Only applies to FILE item types. Allowed values are H0, H1, H2, H3, and H4. Other values are reset to H0. - charCount + charCount - No + No - The number of characters in the text of the item. Only applies to FILE item types. + The number of characters in the text of the item. Only applies to FILE item types. - wordCount + wordCount - No + No - The number of words in the text of the item. Only applies to FILE item types. + The number of words in the text of the item. Only applies to FILE item types. - paraCount + paraCount - No + No - The number of paragraphs in the text of the item. Only applies to FILE item types. + The number of paragraphs in the text of the item. Only applies to FILE item types. - cursorPos + cursorPos - No + No - The last cursor position in the text of the item from the last editing session. Only applies to FILE item types. The value is used to restore the cursor position when the document is opened in the editor. + The last cursor position in the text of the item from the last editing session. Only applies to FILE item types. The value is used to restore the cursor position when the document is opened in the editor. - Name Nodes - The name node contains information about an item that is set by the user. Including its label, which is the text value of the node. This is the label that is displayed in the project tree. + Name Nodes + The name node contains information about an item that is set by the user. Including its label, which is the text value of the node. This is the label that is displayed in the project tree. Attributes @@ -2244,45 +2295,45 @@ - status + status - No + No - The ID of the status label that has been set for this item. Defaults to the first status item defined in the status section of the settings node. + The ID of the status label that has been set for this item. Defaults to the first status item defined in the status section of the settings node. - import + import - No + No - The ID of the importance label that has been set for this item. Defaults to the first importance item defined in the importance section of the settings node. + The ID of the importance label that has been set for this item. Defaults to the first importance item defined in the importance section of the settings node. - active + active - No + No - The active/inactive status of the document. Only applies to FILE item types. + The active/inactive status of the document. Only applies to FILE item types. Example - <item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" - order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> - <meta expanded="no" heading="H1" charCount="93" wordCount="19" - paraCount="2" cursorPos="119"/> - <name status="sc24b8f" import="ia857f0" active="yes">Title Page</name> - </item> + <item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" + order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> + <meta expanded="no" heading="H1" charCount="93" wordCount="19" + paraCount="2" cursorPos="119"/> + <name status="sc24b8f" import="ia857f0" active="yes">Title Page</name> + </item> \ No newline at end of file diff --git a/docs/source/fileformatspec15.pdf b/docs/source/fileformatspec15.pdf index 0edf908e..d23776a8 100644 Binary files a/docs/source/fileformatspec15.pdf and b/docs/source/fileformatspec15.pdf differ diff --git a/docs/source/images/fig_build_settings_headings.png b/docs/source/images/fig_build_settings_headings.png index a7262725..bba80c30 100644 Binary files a/docs/source/images/fig_build_settings_headings.png and b/docs/source/images/fig_build_settings_headings.png differ diff --git a/docs/source/images/fig_editor.png b/docs/source/images/fig_editor.png index f8aee5bb..b2f772bf 100644 Binary files a/docs/source/images/fig_editor.png and b/docs/source/images/fig_editor.png differ diff --git a/docs/source/images/fig_manuscript_build.png b/docs/source/images/fig_manuscript_build.png index 31605cd4..b6a92bfc 100644 Binary files a/docs/source/images/fig_manuscript_build.png and b/docs/source/images/fig_manuscript_build.png differ diff --git a/docs/source/images/fig_manuscript_build_outline.png b/docs/source/images/fig_manuscript_build_outline.png new file mode 100644 index 00000000..1ec9ffad Binary files /dev/null and b/docs/source/images/fig_manuscript_build_outline.png differ diff --git a/docs/source/images/fig_novel_tree_view.png b/docs/source/images/fig_novel_tree_view.png index b0f92b8f..41eaf1fa 100644 Binary files a/docs/source/images/fig_novel_tree_view.png and b/docs/source/images/fig_novel_tree_view.png differ diff --git a/docs/source/images/fig_outline_view.png b/docs/source/images/fig_outline_view.png index 90b4f3d1..3142af04 100644 Binary files a/docs/source/images/fig_outline_view.png and b/docs/source/images/fig_outline_view.png differ diff --git a/docs/source/images/fig_project_split_tool.png b/docs/source/images/fig_project_split_tool.png index a7809f82..cb3c498c 100644 Binary files a/docs/source/images/fig_project_split_tool.png and b/docs/source/images/fig_project_split_tool.png differ diff --git a/docs/source/images/fig_project_tree_view.png b/docs/source/images/fig_project_tree_view.png index f5058038..b891d3f1 100644 Binary files a/docs/source/images/fig_project_tree_view.png and b/docs/source/images/fig_project_tree_view.png differ diff --git a/docs/source/images/fig_viewer.png b/docs/source/images/fig_viewer.png index 7fbdc71d..d53beee1 100644 Binary files a/docs/source/images/fig_viewer.png and b/docs/source/images/fig_viewer.png differ diff --git a/docs/source/images/python_powered.png b/docs/source/images/python_powered.png deleted file mode 100644 index d51df248..00000000 Binary files a/docs/source/images/python_powered.png and /dev/null differ diff --git a/docs/source/images/screenshot_dark.png b/docs/source/images/screenshot_dark.png index 7f581b72..03433a38 100644 Binary files a/docs/source/images/screenshot_dark.png and b/docs/source/images/screenshot_dark.png differ diff --git a/docs/source/images/screenshot_default.png b/docs/source/images/screenshot_default.png deleted file mode 100644 index 93ba2ac8..00000000 Binary files a/docs/source/images/screenshot_default.png and /dev/null differ diff --git a/docs/source/images/screenshot_light.png b/docs/source/images/screenshot_light.png new file mode 100644 index 00000000..bb7e22c7 Binary files /dev/null and b/docs/source/images/screenshot_light.png differ diff --git a/docs/source/index.rst b/docs/source/index.rst index b0020fc6..7c8d75c3 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -17,14 +17,12 @@ storage for robustness. :width: 500 The project storage is suitable for version control software, and also well suited for file -synchronisation tools. All text is saved as plain text files with a meta data header. The core -project structure is stored in a single project XML file. Other meta data is saved as JSON files. -See the :ref:`a_breakdown_storage` section for more details. +synchronisation tools. All text is saved as plain text files, and your project data as standard +data formats in XML and JSON. See :ref:`a_storage` for more details. -Any operating system that can run Python 3 and has the Qt 5 libraries should be able to run -novelWriter. It runs fine on Linux, Windows and MacOS, and users have tested it on other platforms -as well. novelWriter can be run directly from the Python source, or installed from packages or with -pip. See :ref:`a_started` for more details. +Any operating system that has Python 3 and the Qt 5 libraries should be able to run novelWriter. +It runs fine on Linux, Windows and MacOS, and users have tested it on other platforms as well. +See :ref:`a_started` for more details. **Useful Links** @@ -53,7 +51,6 @@ pip. See :ref:`a_started` for more details. int_overview int_started int_howto - int_customise int_glossary .. toctree:: @@ -83,6 +80,7 @@ pip. See :ref:`a_started` for more details. :caption: Additional Details :hidden: + more_customise more_projectformat more_counting diff --git a/docs/source/int_howto.rst b/docs/source/int_howto.rst index fe5b22fa..bab60b39 100644 --- a/docs/source/int_howto.rst +++ b/docs/source/int_howto.rst @@ -19,9 +19,9 @@ Managing the Project .. dropdown:: Merge Multiple Documents Into One :animate: fade-in-slide-down - If you need to merge a set of documents in your project into a single document, you can achieve - this by first making a new folder for just that purpose, and drag all the files you want merged - into this folder. Then you can right click the folder, select :guilabel:`Transform` and + If you need to merge a selection of documents in your project into a single document, you can + achieve this by first making a new folder for just that purpose, and drag all the files you want + merged into this folder. Then you can right click the folder, select :guilabel:`Transform` and :guilabel:`Merge Documents in Folder`. In the dialog that pops up, the documents will be in the same order as in the folder, but you @@ -36,7 +36,7 @@ Layout Tricks The formatting tools available in novelWriter don't allow for complex structures like tables. However, the editor does render tabs in a similar way that regular word processors do. You can - set the width of a tab in :guilabel:`Preferences`. + set the width of a tab in **Preferences**. The tab key should have the same distance in the editor as in the viewer, so you can align text in columns using the tab key, and it should look the same when viewed next to the editor. @@ -66,18 +66,13 @@ Organising Your Text Depending on your writing style, you may need to separate between soft and hard scene breaks within chapters. Like for instance if you switch point-of-view character often. - In such cases you may want to use a scene heading for hard scene breaks and a section heading - for soft scene breaks. The :guilabel:`Build Manuscript` tool will let you add separate - formatting for the two when you generate your manuscript. You can for instance add the common - "``* * *``" for hard breaks and select to hide section breaks, which will just insert an empty - paragraph in their place. See :ref:`a_manuscript_settings` for more details. - - Keep in mind that this is not what the section heading is intended for, so the app will not - understand the section heading as a scene, but it will be formatted correctly in the manuscript. + In such cases you may want to use different scene headings for hard and soft scene breaks. The + **Build Manuscript** tool will let you define a different format for scenes using the ``###`` + and ``###!`` heading codes when you generate your manuscript. You can for instance add the + common "``* * *``" for hard breaks and select to soft scene breaks, which will just insert an + empty paragraph in their place. See :ref:`a_manuscript_settings` for more details. .. versionadded:: 2.4 - You can now distinguish between soft and hard scene breaks with a modified scene header. See - :ref:`a_manuscript_settings_head_hard` for more details. Other Tools diff --git a/docs/source/int_introduction.rst b/docs/source/int_introduction.rst index a23810bd..c67588e7 100644 --- a/docs/source/int_introduction.rst +++ b/docs/source/int_introduction.rst @@ -1,15 +1,21 @@ .. _a_intro: -************ -Key Features -************ +******** +Overview +******** .. _Snowflake: https://www.advancedfictionwriting.com/articles/snowflake-method/ .. _Markdown: https://en.wikipedia.org/wiki/Markdown -At its core, novelWriter is a multi-document plain text editor. It uses a markup syntax inspired by -Markdown_ to apply simple formatting to the text. It also allows for some extended formatting codes -using a shortcode format. See :ref:`a_fmt_shortcodes` for more details. +At its core, novelWriter is a multi-document plain text editor. The idea is to let you edit your +text without having to deal with formatting until you generate a draft document or manuscript. +Instead, you can focus on the writing right from the start. + +Of course, you probably need *some* formatting for your text. At the very least you need emphasis. +Most people are familiar with adding emphasis using underscores and asterisks. This formatting +standard comes from Markdown_ and is supported by novelWriter. It also uses Markdown formatting for +defining document headings. If you need more specialised formatting, additional formatting options +are available using a shortcode format. See :ref:`a_fmt_shortcodes` for more details. .. admonition:: Limitations @@ -17,22 +23,28 @@ using a shortcode format. See :ref:`a_fmt_shortcodes` for more details. those relevant for this purpose. It is **not** suitable for technical writing, and it is **not** a full-featured Markdown editor. - It is also not intended as a tool to organise research for writing, and therefore lacks + It is also not intended as a tool for organising research for writing, and therefore lacks formatting features you may need for this purpose. The notes feature in novelWriter is mainly intended for character profiles and plot outlines. -Your novel project is organised as a collection of separate plain text documents instead of a -single, large document. The idea is to make it easier to reorganise your project structure without -having to cut and paste text between chapters. +Your novel project in novelWriter is organised as a collection of separate plain text documents +instead of a single, large document. The idea is to make it easier to reorganise your project +structure without having to cut and paste text between chapters and scenes. There are two kinds of documents in your project: :term:`Novel Documents` are documents that are part of your story. The other kind of documents are :term:`Project Notes`. These are intended for your notes about your characters, your world building, and so on. You can at any point split the individual documents by their headings up into multiple documents, -or merge multiple documents into single documents. This makes it easier to use variations of the +or merge multiple documents into a single document. This makes it easier to use variations of the Snowflake_ method for writing. You can start by writing larger structure-focused documents, like -one per act for instance, and later effortlessly split these up into chapters or scenes. +for instance one document per act, and later effortlessly split these up into chapters or scenes. + + +.. _a_intro_features: + +Key Features +============ Below are some key features of novelWriter. @@ -40,12 +52,12 @@ Below are some key features of novelWriter. The aim of the user interface is to let you focus on writing instead of spending time formatting text. Formatting is therefore limited to a small set of formatting tags for simple things like text emphasis and paragraph alignment. When you really want to focus on just writing, you can - switch the editor into :guilabel:`Focus Mode` where only the text editor panel itself is - visible, and the project structure view is hidden away. + switch the editor into **Focus Mode** where only the text editor panel itself is visible, and + the project structure view is hidden away. **Keep an eye on your notes** - The main window can optionally show a document viewer to the right of the editor. This view - panel is intended for displaying another scene document, your character notes, plot notes, or any + The main window can optionally show a document viewer to the right of the editor. The viewer + is intended for displaying another scene document, your character notes, plot notes, or any other document you may need to reference while writing. It is not intended as a preview panel for the document you're editing, but if you wish, you can also use it for this purpose. @@ -58,32 +70,35 @@ Below are some key features of novelWriter. later split them into multiple documents based on chapter and scene headings. **Multi-novel project support** - You can have multiple Novel type root folders in a project. This allows you to keep a series of - individual novels with the same characters and world building in the same project, and create - manuscripts for them individually. + The main parts of your project is split up into top level special folders called "Root" folders. + Your main story text lives in the "Novel" root folder. You can have multiple such folders in a + project. This allows you to keep a series of individual novels with the same characters and + world building in the same project, and create manuscripts for them individually. **Keep track of your story elements** - All notes in your project can be assigned a :term:`tag` that you can :term:`reference` from any - other document or note. In fact, you can add a new tag under each heading of a note if you need - to be able to reference specific sections of it. + All notes in your project can be assigned a :term:`tag` that you can then :term:`reference` from + any other document or note. In fact, you can add a new tag under each heading of a note if you + need to be able to reference specific sections of it. **Get an overview of your story** - In the :guilabel:`Outline View` on the main window you can see an outline of all the chapters, - scenes, and sections of your project. If they have any references in them, these are listed in - additional columns. You can also add a synopsis to each chapter or scene, which can be listed - here as well. You have the option to add or remove columns of information from this outline. A - subset of the outline information is also available in the :guilabel:`Novel View` as an - alternative view to the project tree. + It is not the documents themselves that define the chapters and scenes of your story, but the + headings that separate them. In the **Outline View** on the main window you can see an outline + of all the chapter and scene headings of your project. If they have any references in them, like + which character is in what chapter and scene, these are listed in additional columns. + + You can also add a synopsis to each chapter or scene, which can be listed here as well. You have + the option to add or remove columns of information from this outline. A subset of the outline + information is also available in the **Novel View** as an alternative view to the project tree. **Get an overview of your story elements** - Under the document viewer panel you will find a series of tabs that shows the different story - elements you have created tags for. The tabs are sorted into Characters, Plots, etc, depending - on which categories you are using in your story. This panel can be hidden to free up space when - you don't need it. + Under the document viewer panel you will find a series of tabs that show the different story + elements you have created tags for. The tabs are sorted into **Characters**, **Plots**, etc, + depending on which categories you are using in your story. This panel can be hidden to free up + space when you don't need it. **Assembling your manuscript** Whether you want to assemble a manuscript, or export all your notes, or generate an outline of - your chapters and scenes with a synopsis, you can use the :guilabel:`Build Manuscript` tool to + your chapters and scenes with a synopsis included, you can use the **Build Manuscript** tool to do so. The tool lets you select what information you want to include in the generated document, and how it is formatted. You can send the result to a printer, a PDF, or to an Open Document file that can be opened by most office type word processors. You can also generate the result @@ -95,7 +110,7 @@ Below are some key features of novelWriter. Screenshots =========== -.. figure:: images/screenshot_default.png +.. figure:: images/screenshot_light.png :class: dark-light novelWriter with light colour theme diff --git a/docs/source/int_overview.rst b/docs/source/int_overview.rst index e83dcd58..96c938b7 100644 --- a/docs/source/int_overview.rst +++ b/docs/source/int_overview.rst @@ -1,28 +1,22 @@ -.. _a_overview: +.. _a_reading: -******** -Overview -******** +****************** +What to Read First +****************** -.. only:: html +The documentation of novelWriter is quite extensive. There are a lot of features to get used to, +but you don't need all of them to get started. - .. image:: images/python_powered.png - :align: right - :width: 220 +The chapters below labelled "Essential Information" are the ones you need to know to use the +application correctly. By "correctly", it is meant: in a way so novelWriter understands the basic +structure of your text. It collects a lot of information from your text and uses it to display the +structure of it in various ways to help you get an overview of your project. -novelWriter is built as a cross-platform application using `Python 3 `_ as -the programming language, and `Qt 5 `_ framework for the user interface. +The chapters labelled "Recommended Reading" includes additional information on how the different +parts if the application work and what the features do. -novelWriter is built for Linux first, so this is where it works best. However, it also runs fine -on Windows and MacOS due to the cross-platform framework it's built on. The author of the -application doesn't own a Mac, so on-going Mac support is dependent on user feedback and user -contributions. - -Spell checking in novelWriter is provided by a third party library called -`Enchant `_. Please see the section on :ref:`a_custom_dict` for -how to install spell checking languages. - -For install instructions for novelWriter, see :ref:`a_started`. +The "Optional" and "Lookup" chapters contain additional information or lookup tables that are not +essential for using the application. Using novelWriter @@ -82,8 +76,8 @@ meta data for it to extract. the application interface. :ref:`a_manuscript` - Recommended Reading - This chapter explains how the :guilabel:`Manuscript Build` tool works, how you can control the - way chapter titles are formatted, and how scene and section breaks are handled. + This chapter explains how the **Manuscript Build** tool works, how you can control the way + chapter titles are formatted, and how scene and section breaks are handled. Additional Details & Technical Topics diff --git a/docs/source/int_started.rst b/docs/source/int_started.rst index d53b68cb..d361de2f 100644 --- a/docs/source/int_started.rst +++ b/docs/source/int_started.rst @@ -1,8 +1,8 @@ .. _a_started: -*************** -Getting Started -*************** +********************** +Setup and Installation +********************** .. _Enchant: https://abiword.github.io/enchant/ .. _GitHub: https://github.com/vkbo/novelWriter @@ -15,8 +15,8 @@ Getting Started .. _AppImage: https://appimage.org/ Ready-made packages and installers for novelWriter are available for all major platforms, including -Linux, Windows and MacOS, from the `Downloads page`_. See below for install instructions for each -platform. +Linux, Windows and MacOS, from the `Downloads page`_. See below for install additional instructions +for each platform. You can also install novelWriter from the Python Package Index (PyPi_). See :ref:`a_started_pip`. Installing from PyPi does not set up icon launchers, so you will either have to do this yourself, @@ -44,7 +44,7 @@ multiple installations has been known to cause problems. The novelWriter installer is not signed because Microsoft doesn't currently provide a way for non-profit open source projects to properly sign their installers. The novelWriter project doesn't have the funding to pay for commercial software signing certificates. You will therefore - see an additional warning about this when you download the installer. + see an additional warning about this when you download and run the installer. .. _a_started_linux: @@ -56,6 +56,7 @@ A Debian package can be downloaded from the `Downloads page`_, or from the Relea GitHub_. This package should work on both Debian, Ubuntu and Linux Mint, at least. If you prefer, you can also add the novelWriter repository on Launchpad to your package manager. +The Launchpad packages `are signed by the author `__. Ubuntu diff --git a/docs/source/more_counting.rst b/docs/source/more_counting.rst index ac408937..7272c9fc 100644 --- a/docs/source/more_counting.rst +++ b/docs/source/more_counting.rst @@ -8,8 +8,8 @@ This is an overview of how words and other counts of your text are performed. Th should be relatively standard, and are compared to Libre Office Writer rules. The counts provided in the app on the raw text is meant to be approximate. For more accurate -counts, you need to build your manuscript in the :guilabel:`Manuscript Tool` and check the counts -on the generated preview. +counts, you need to build your manuscript in the **Manuscript Tool** and check the counts on the +generated preview. Text Word Counts and Stats @@ -31,11 +31,11 @@ After the above preparation of the text, the following counts are available. **Character Count** The character count is the sum of characters per line, including leading and in-text white space characters, but excluding trailing white space characters. Shortcodes in the text are not - included, but Markdown codes are. Only headers and text are counted. + included, but Markdown codes are. Only headings and text are counted. **Word Count** The words count is the sum of blocks of continuous character per line separated by any number of - white space characters or dashes. Only headers and text are counted. + white space characters or dashes. Only headings and text are counted. **Paragraph Count** The paragraph count is the number of text blocks separated by one or more empty line. A line @@ -45,10 +45,10 @@ After the above preparation of the text, the following counts are available. Manuscript Counts ================= -These are the rules for the counts available for a manuscript in the :guilabel:`Manuscript Tool`. -The rules have been tuned to agree with LibreOffice Writer, but will vary slightly depending on the -content of your text. LibreOffice Writer also counts the text in the page header, which the -Manuscript Tool does not. +These are the rules for the counts available for a manuscript in the **Manuscript Tool**. The rules +have been tuned to agree with LibreOffice Writer, but will vary slightly depending on the content +of your text. LibreOffice Writer also counts the text in the page header, which the **Manuscript +Tool** does not. The content of each line is counted after all formatting has been processed, so the result will be more accurate than the counts for text documents elsewhere in the app. The following rules apply: @@ -63,36 +63,36 @@ more accurate than the counts for text documents elsewhere in the app. The follo The following counts are available: -**Header Count** - The number of headers in the manuscript. +**Headings** + The number of headings in the manuscript. -**Paragraph Count** +**Paragraphs** The number of body text paragraphs in the manuscript. -**Total Word Count** +**Words** The number of words in the manuscript, including any comments and meta data text. -**Text Word Count** +**Words in Text** The number of words in body text paragraphs, excluding all other text. -**Header Word Count** - The number of words in headers, including inserted formatting like chapter numbers, etc. +**Words in Headings** + The number of words in headings, including inserted formatting like chapter numbers, etc. -**Total Character Count** - The number of characters on all lines, including any comments and meta data text. Paragraph +**Characters** + The number of characters in all lines, including any comments and meta data text. Paragraph breaks are not counted, but in-paragraph hard line breaks are. -**Text Character Count** +**Character in Text** The number of characters in body text paragraphs. Paragraph breaks are not counted, but in-paragraph hard line breaks are. -**Header Character Count** +**Characters in Headings** The number of characters in headings. -**Text Words Character Count** +**Character in Text, No Spaces** The number of characters in body text paragraphs considered part of a word or punctuation. That is, white space characters are not counted. -**Header Words Character Count** - The number of characters in headers considered part of a word or punctuation. That is, white +**Character in Headings, No Spaces** + The number of characters in headings considered part of a word or punctuation. That is, white space characters are not counted. diff --git a/docs/source/int_customise.rst b/docs/source/more_customise.rst similarity index 90% rename from docs/source/int_customise.rst rename to docs/source/more_customise.rst index f9a3b9fe..6e6c09da 100644 --- a/docs/source/int_customise.rst +++ b/docs/source/more_customise.rst @@ -34,10 +34,10 @@ and add dictionaries yourself. **Install Tool** -A small tool to assist with this can be found under :guilabel:`Tools > Add Dictionaries`. It will -import spell checking dictionaries from Free Office or Libre Office extensions. The dictionaries -are then installed in the install location for the Enchant library and should thus work for any -application that uses Enchant for spell checking. +A small tool to assist with this can be found under **Tools > Add Dictionaries**. It will import +spell checking dictionaries from Free Office or Libre Office extensions. The dictionaries are then +installed in the install location for the Enchant library and should thus work for any application +that uses Enchant for spell checking. **Manual Install** @@ -81,14 +81,14 @@ modify it as you like. `novelwriter/assets/icons `_. Remember to also change the name of your theme by modifying the ``name`` setting at the top of the -file, otherwise you may not be able to distinguish them in :guilabel:`Preferences`. +file, otherwise you may not be able to distinguish them in **Preferences**. For novelWriter to be able to locate the custom theme files, you must copy them to the :ref:`a_locations_data` location in your home or user area. There should be a folder there named ``syntax`` for syntax themes, just ``themes`` for GUI themes, and ``icons`` for icon themes. These folders are created the first time you start novelWriter. -Once the files are copied there, they should show up in :guilabel:`Preferences` with the label you +Once the files are copied there, they should show up in **Preferences** with the label you set as ``name`` inside the file. .. versionadded:: 2.0 @@ -190,10 +190,10 @@ Omitted syntax colours default to black, except ``background`` which defaults to ``texthighlight`` which defaults to white with half transparency. .. versionadded:: 2.2 - The `shortcode` syntax colour entry was added. + The ``shortcode`` syntax colour entry was added. .. versionadded:: 2.3 - The `optional` syntax colour entry was added. + The ``optional`` syntax colour entry was added. .. versionadded:: 2.4 - The `texthighlight` syntax colour entry was added. + The ``texthighlight`` syntax colour entry was added. diff --git a/docs/source/project_manuscript.rst b/docs/source/project_manuscript.rst index 9383079d..4f4657bc 100644 --- a/docs/source/project_manuscript.rst +++ b/docs/source/project_manuscript.rst @@ -4,9 +4,11 @@ Building the Manuscript *********************** +.. _Pandoc: https://pandoc.org/ + You can at any time build a manuscript, an outline of your notes, or any other type of document -from the text in your project. All of this is handled by the :guilabel:`Manuscript Build` tool. -You can activate it from the sidebar, the :guilabel:`Tools` menu, or by pressing :kbd:`F5`. +from the text in your project. All of this is handled by the **Manuscript Build** tool. You can +activate it from the sidebar, the **Tools** menu, or by pressing :kbd:`F5`. .. versionadded:: 2.1 This tool is new for version 2.1. A simpler tool was used for earlier versions. The simpler tool @@ -20,40 +22,60 @@ The Manuscript Build Tool ========================= .. figure:: images/fig_manuscript_build.png + :width: 80% - The :guilabel:`Manuscript Build` tool main window. + The **Manuscript Build** tool main window. -The main window of the :guilabel:`Manuscript Build` tool contains a list of all the builds you have +The main window of the **Manuscript Build** tool contains a list of all the builds you have defined, a selection of settings, and a few buttons to generate preview, open the print dialog, or run the build to create a manuscript document. +Outline and Word Counts +----------------------- + +.. figure:: images/fig_manuscript_build_outline.png + :width: 80% + + The **Manuscript Build** tool main window with the **Outline** visible. + +The **Outline** tab on the left lets you navigate the headings in the preview document. It will +show up to scene level headings for novel documents, and level 2 headings for notes. + +A collapsible panel of word and character counts are also available below the preview document. +These are calculated from the text you have included in the document, and are more accurate counts +than what's available in the project tree since they are counted *after formatting*. + +For a detailed description on how they are counted, see :ref:`a_counting`. + + .. _a_manuscript_settings: Build Settings ============== -Each build definition can be edited by opening it in the :guilabel:`Manuscript Build Settings` -dialog, either by double-clicking or by selecting it and pressing the edit button in the toolbar. +Each build definition can be edited by opening it in the **Manuscript Build Settings** dialog, +either by double-clicking or by selecting it and pressing the edit button in the toolbar. .. tip:: - You can keep the :guilabel:`Manuscript Build Settings` dialog open while testing the different - options, and just hit the :guilabel:`Apply` button. You can test the result of your settings - by pressing the :guilabel:`Preview` button in the main :guilabel:`Manuscript Build` window. - When you're happy with the result, you can close the settings. + You can keep the **Manuscript Build Settings** dialog open while testing the different options, + and just hit the :guilabel:`Apply` button. You can test the result of your settings by pressing + the :guilabel:`Preview` button in the main **Manuscript Build** window. When you're happy with + the result, you can close the settings. Document Selection ------------------ .. figure:: images/fig_build_settings_selections.png + :width: 80% - The :guilabel:`Selections` page of the :guilabel:`Manuscript Build Settings` dialog. + The **Selections** page of the **Manuscript Build Settings** dialog. -The :guilabel:`Selections` page of the :guilabel:`Manuscript Build Settings` dialog allows you to -fine tune which documents are included in the build. They are indicated by a green arrow icon in -the last column. On the right you have some filter options for selecting content of a specific -type, and a set of switches for which root folders to include. +The **Selections** page of the **Manuscript Build Settings** dialog allows you to fine tune which +documents are included in the build. They are indicated by a green arrow icon in the last column. +On the right you have some filter options for selecting content of a specific type, and a set of +switches for which root folders to include. You can override the result of these filters by marking one or more documents and selecting to explicitly include or exclude them by using the buttons below the tree view. The last button can be @@ -69,13 +91,14 @@ Formatting Headings ------------------- .. figure:: images/fig_build_settings_headings.png + :width: 80% - The :guilabel:`Headings` page of the :guilabel:`Manuscript Build Settings` dialog. + The **Headings** page of the **Manuscript Build Settings** dialog. -The :guilabel:`Headings` page of the :guilabel:`Manuscript Build Settings` dialog allows you to set -how the headings in your :term:`Novel Documents` are formatted. By default, the title is just -copied as-is, indicated by the ``{Title}`` format. You can change this to for instance add chapter -numbers and scene numbers, or insert character names, like shown in the figure above. +The **Headings** page of the **Manuscript Build Settings** dialog allows you to set how the +headings in your :term:`Novel Documents` are formatted. By default, the title is just copied as-is, +indicated by the ``{Title}`` format. You can change this to for instance add chapter numbers and +scene numbers, or insert character names, like shown in the figure above. Clicking the edit button next to a format will copy the formatting string into the edit box where it can be modified, and where a syntax highlighter will help indicate which parts are automatically @@ -86,7 +109,7 @@ Any text you add that isn't highlighted in colours will remain in your formatted ``{Title}`` will always be replaced by the text in the heading from your documents. You can preview the result of these format strings by clicking :guilabel:`Apply`, and then clicking -:guilabel:`Preview` in the :guilabel:`Manuscript Build` tool main window. +:guilabel:`Preview` in the **Manuscript Build** tool main window. Scene Separators @@ -107,10 +130,9 @@ be treated as a separator. Hard and Soft Scenes ^^^^^^^^^^^^^^^^^^^^ -If you wish to distinguish between so-called soft and hard scene breaks, where a hard scene break -is understood as a scene break that changes point-of-view character, you can use the modified scene -heading format in your text. You can then give these headings a different formatting in the -:guilabel:`Headings` settings. +If you wish to distinguish between so-called soft and hard scene breaks, you can use the +alternative scene heading format in your text. You can then give these headings a different +formatting in the **Headings** settings. See :ref:`a_fmt_head` for more info on how to format headings in your text. @@ -118,10 +140,9 @@ See :ref:`a_fmt_head` for more info on how to format headings in your text. Output Settings --------------- -The :guilabel:`Content`, :guilabel:`Format` and :guilabel:`Output` pages of the -:guilabel:`Manuscript Build Settings` dialog control a number of other settings for the output. -Some of these only apply to specific output formats, which is indicated by the section headings on -the settings pages. +The **Content**, **Format** and **Output** pages of the **Manuscript Build Settings** dialog +control a number of other settings for the output. Some of these only apply to specific output +formats, which is indicated by the section headings on the settings pages. .. _a_manuscript_build: @@ -130,12 +151,13 @@ Building Manuscript Documents ============================= .. figure:: images/fig_build_build.png + :width: 80% - The :guilabel:`Manuscript Build` dialog used for writing the actual manuscript documents. + The **Manuscript Build** dialog used for writing the actual manuscript documents. -When you press the :guilabel:`Build` button on the :guilabel:`Build Manuscript` tool main window, a -special file dialog opens up. This is where you pick your desired output format and where to write -the file. +When you press the :guilabel:`Build` button on the **Build Manuscript** tool main window, a special +file dialog opens up. This is where you pick your desired output format and where to write the +file. On the left side of the dialog is a list of all the available file formats, and on the right, a list of the documents which are included based on the build definition you selected. You can choose @@ -157,7 +179,7 @@ Open Document Format novelWriter HTML The HTML format writes a single ``.htm`` file with minimal style formatting. The HTML document - is suitable for further processing by document conversion tools like Pandoc, for importing in + is suitable for further processing by document conversion tools like Pandoc_, for importing in word processors, or for printing from browser. novelWriter Markup @@ -198,4 +220,4 @@ on the print dialog. .. note:: The paper format should in all cases default to whatever your system default is. If you want to - change it, you have to select it from the :guilabel:`Print Preview` dialog. + change it, you have to select it from the **Print Preview** dialog. diff --git a/docs/source/project_overview.rst b/docs/source/project_overview.rst index d546992f..9d89759f 100644 --- a/docs/source/project_overview.rst +++ b/docs/source/project_overview.rst @@ -4,25 +4,27 @@ Novel Projects ************** -New projects can be created from the :guilabel:`Project` menu by selecting -:guilabel:`Create or Open Project`. This will open the :guilabel:`Welcome` dialog, where you can -select the :guilabel:`New` button that will assist you in creating a new project. +New projects can be created from the **Project** menu by selecting **Create or Open Project**. This +will open the **Welcome** dialog, where you can select the :guilabel:`New` button that will assist +you in creating a new project. This dialog is also displayed when you start novelWriter. A novelWriter project requires a dedicated folder for storing its files on the local file system. If you're interested in the details, you can have a look at the chapter :ref:`a_storage`. -A list of recently opened projects is maintained, and displayed in the :guilabel:`Welcome` -dialog. A project can be removed from this list by selecting it and pressing the :kbd:`Del` key or -by right-clicking it and selecting the :guilabel:`Remove Project` option. +A list of recently opened projects is maintained, and displayed in the **Welcome** dialog. A +project can be removed from this list by selecting it and pressing the :kbd:`Del` key or by +right-clicking it and selecting the **Remove Project** option. .. figure:: images/fig_welcome.jpg The project list (left) and new project form (right) of the :guilabel:`Welcome` dialog. -Project-specific settings are available in :guilabel:`Project Settings` in the :guilabel:`Project` -menu. See further details below in the :ref:`a_proj_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 :guilabel:`Novel Details` dialog. +Project-specific settings are available in **Project Settings** in the **Project** menu. See +further details below in the :ref:`a_proj_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. .. _a_proj_roots: @@ -33,64 +35,70 @@ Project Roots Projects are structured into a set of top level folders called "Root Folders". They are visible in the project tree at the left side of the main window. -The :term:`novel documents` go into a root folder of type :guilabel:`Novel`. :term:`Project notes` -go into the other root folders. These other root folder types are intended for your notes on the -various elements of your story. Using them is of course entirely optional. +The :term:`novel documents` go into a root folder of type **Novel**. :term:`Project notes` go into +the other root folders. These other root folder types are intended for your notes on the various +elements of your story. Using them is of course entirely optional. A new project may not have all of the root folders present, but you can add the ones you want from the project tree tool bar. Each root folder has one or more :term:`reference` :term:`keyword` associated with it that is used to reference them from other documents and notes. The intended usage of each type of root folder is -listed below. However, aside from the :guilabel:`Novel` folder, no restrictions are applied by the +listed below. However, aside from the **Novel** folder, no restrictions are applied by the application on what you put in them. You can use them however you want. The root folder system is closely connected to how the Tags and References system works. For more details, see the :ref:`a_references` chapter. -:guilabel:`Novel` + +Root Folder Types +----------------- + +**Novel** This is the root folder type for text that goes into the final novel or novels. This class of documents have other rules and features than the project notes. See :ref:`a_struct` for more details. -:guilabel:`Plot` +**Plot** This is the root folder type where main plots can be outlined. It is optional, but adding at - least brief notes can be useful in order to tag plot elements for the :guilabel:`Outline View`. - Tags in this folder can be references using the ``@plot`` keyword. + least brief notes can be useful in order to tag plot elements for the **Outline View**. Tags in + this folder can be references using the ``@plot`` keyword. -:guilabel:`Characters` +**Characters** Character notes go in this root folder type. These are especially important if you want to use - the :guilabel:`Outline View` to see which character appears where, which part of the story is - told from a specific character's point-of-view, or focusing on a particular character's - storyline. The character names can also be inserted into for instance chapter titles when you - create your manuscript. Tags in this type of folder can be referenced using the ``@pov`` keyword - for point-of-view characters, ``@focus`` for a focus character, or the ``@char`` keyword for any + the **Outline View** to see which character appears where, which part of the story is told from + a specific character's point-of-view, or focusing on a particular character's storyline. + + The character names can also be inserted into for instance chapter titles when you create your + manuscript. Tags in this type of folder can be referenced using the ``@pov`` keyword for + point-of-view characters, ``@focus`` for a focus character, or the ``@char`` keyword for any other character present. -:guilabel:`Locations` +**Locations** The locations folder type is for various scene locations that you want to track. Tags in this folder can be references using the ``@location`` keyword. -:guilabel:`Timeline` +**Timeline** If the story has multiple plot timelines or jumps in time within the same plot, this folder type can be used to track this. Tags in this type of folder can be references using the ``@time`` keyword. -:guilabel:`Objects` - Important objects in the story, for instance objects that change hands often, can be tracked - here. Tags in this type of folder can be references using the ``@object`` keyword. +**Objects** + Important objects in the story, for instance physical objects that change hands often, can be + tracked here. Tags in this type of folder can be references using the ``@object`` keyword. -:guilabel:`Entities` +**Entities** Does your plot have many powerful organisations or companies? Or other entities that are part of the plot? They can be organised here. Tags in this type of folder can be references using the ``@entity`` keyword. -:guilabel:`Custom` +**Custom** The custom root folder type can be used for tracking anything else not covered by the above options. Tags in this folder type can be references using the ``@custom`` keyword. -The root folders correspond to the categories of tags that can be used to reference them. For more -information about the tags listed, see :ref:`a_references_references`. +The root folders are closely tied to the tags and reference system. Each folder type corresponds to +the categories of tags that can be used to reference them. For more information about the tags +listed, see :ref:`a_references_references`. .. note:: You can rename root folders to whatever you want. However, this doesn't change the reference @@ -105,12 +113,11 @@ information about the tags listed, see :ref:`a_references_references`. Deleted Documents ----------------- -Deleted documents will be moved into a special :guilabel:`Trash` root folder. Documents in the -trash folder can then be deleted permanently, either individually, or by emptying the trash from -the menu. Documents in the trash folder are removed from the :term:`project index` and cannot be -referenced. +Deleted documents are moved into a special **Trash** root folder. Documents in the trash folder can +then be deleted permanently, either individually, or by emptying the trash from the menu. Documents +in the trash folder are removed from the :term:`project index` and cannot be referenced. -A document or a folder can be moved to trash from the :guilabel:`Project` menu, or by pressing +A document or a folder can be moved to trash from the **Project** menu, or by pressing :kbd:`Ctrl+Shift+Del`. Root folders can only be removed when they are empty. @@ -119,13 +126,34 @@ A document or a folder can be moved to trash from the :guilabel:`Project` menu, Archived Documents ------------------ -If you don't want to delete a document, or put it in the :guilabel:`Trash` folder where it may be -deleted accidentally, but still want it out of your main project tree, you can create an -:guilabel:`Archive` root folder and move it there. +If you don't want to delete a document, or put it in the **Trash** folder where it may be deleted +accidentally, but still want it out of your main project tree, you can create an **Archive** root +folder instead and move it there. It has the same effect as moving it to **Trash**, but it is safe +from deletion. You can drag any document to this folder and preserve its settings. The document will always be -excluded from the :guilabel:`Build Manuscript` tool. It is also removed from the -:term:`project index`, so the tags and references defined in it will not show up anywhere else. +excluded from the **Build Manuscript** tool. It is also removed from the :term:`project index`, so +the tags and references defined in it will not show up anywhere else. + + +.. _a_proj_roots_dirs: + +Using Folders in the Project Tree +--------------------------------- + +Regular folders, those that are not root folders, have no structural significance to the project. +When novelWriter is processing the documents in a project, like for instance when you create a +manuscript from it, these folders are ignored. Only the order of the documents themselves matter. + +The folders are there purely as a way for you to organise the documents in meaningful sections and +to be able to collapse and hide them in the project tree when you're not working on those +documents. + +.. versionadded:: 2.0 + As of version 2.0 it is possible to add child documents to other documents. This is particularly + useful when you create chapters and scenes. If you add separate scene documents, you should also + add separate chapter documents, even if they only contain a chapter heading. You can then add + scene documents as child items to the chapters. .. _a_proj_roots_orphaned: @@ -143,8 +171,8 @@ previously have been located in the project. The project loading routine will tr as close as possible to this location, if it still exists. Generally, it will be appended to the end of the folder where it previously was located. If that folder doesn't exist, it will try to add it to the correct root folder type. If it cannot figure out which root folder is correct, the -document will be added to the :guilabel:`Novel` root folder. Finally, if the :guilabel:`Novel` -folder is missing, one will be created. +document will be added to the **Novel** root folder. Finally, if a **Novel** does not exist, one +will be created. If the title of the document can be recovered, the word "Recovered:" will be added as a prefix to indicate that it may need further attention. If the title cannot be determined, the document will @@ -157,15 +185,16 @@ Project Lockfile ---------------- To prevent lost documents caused by file conflicts when novelWriter projects are synchronised via -file synchronisation tools, a project lockfile is written to the project storage folder. If you try -to open a project which has such a file present, you will be presented with a warning, and some -information about where else novelWriter thinks the project is also open. You will be given the -option to ignore this warning, and continue opening the project at your own risk. +file synchronisation tools, a project lockfile is written to the project storage folder when a +project is open. If you try to open a project which already has such a file present, you will be +presented with a warning, and some information about where else novelWriter thinks the project is +also open. You will be given the option to ignore this warning, and continue opening the project at +your own risk. .. note:: - If, for some reason, novelWriter crashes, the lock file may remain even if there are no other - instances keeping the project open. In such a case it is safe to ignore the lock file warning - when re-opening the project. + If, for some reason, novelWriter or your computer crashes, the lock file may remain even if + there are no other instances keeping the project open. In such a case it is safe to ignore the + lock file warning when re-opening the project. .. warning:: If you choose to ignore the warning and continue opening the project, and multiple instances of @@ -175,52 +204,31 @@ option to ignore this warning, and continue opening the project at your own risk novelWriter will try to resolve project inconsistencies the next time you open the project. -.. _a_proj_roots_dirs: - -Using Folders in the Project Tree ---------------------------------- - -Folders, aside from root folders, have no structural significance to the project. When novelWriter -is processing the documents in a project, like for instance when you create a manuscript from it, -these folders are ignored. Only the order of the documents themselves matter. - -The folders are there purely as a way for you to organise the documents in meaningful sections and -to be able to collapse and hide them in the project tree when you're not working on those -documents. - -.. versionadded:: 2.0 - As of version 2.0 it is possible to add child documents to other documents. This is particularly - useful when you create chapters and scenes. If you add separate scene documents, you should also - add separate chapter documents, even if they only contain a chapter heading. You can then add - scene documents as child items to the chapters. - - .. _a_proj_files: Project Documents ================= -New documents can be created from the toolbar in the :guilabel:`Project Tree`, or by pressing -:kbd:`Ctrl+N`. This will open the create new item menu and let you choose between a number of -pre-defined documents and folders. You will be prompted for a label for the new item. +New documents can be created from the toolbar in the project tree, or by pressing :kbd:`Ctrl+N`. +This will open the create new item menu and let you choose between a number of pre-defined +documents and folders. You will be prompted for a label for the new item. -You can always rename an item by selecting :guilabel:`Rename Item` from the :guilabel:`Project` -menu, or by pressing :kbd:`F2`. +You can always rename an item by selecting **Rename Item** from the **Project** menu, or by +pressing :kbd:`F2` when a document or folder is selected. -Other settings for project items are available from the context menu that you can activate by -right-clicking on an item in the tree. The :guilabel:`Transform` submenu includes options for -converting, splitting, or merging items. See :ref:`a_ui_tree_split_merge` for more details on the -latter two. +Other settings for project documents and folders are available from the context menu that you can +activate by right-clicking on an it in the tree. The **Transform** submenu includes options for +converting, splitting, or merging documents. See :ref:`a_ui_tree_split_merge` for more details on +the latter two. Document Templates ------------------ If you wish to create template documents to be used when creating new project documents, like for -instance a character note template, you can add a :guilabel:`Templates` root folder to your -project. Any document added to this root folder will show up in the :guilabel:`Add Item` menu in -the project tree toolbar. When selected, a new document is created with its content copied from the -chosen template. +instance a character note template, you can add a **Templates** root folder to your project. Any +document added to this root folder will show up in the **Add Item** menu in the project tree +toolbar. When selected, a new document is created with its content copied from the chosen template. .. versionadded:: 2.3 @@ -233,8 +241,8 @@ Word Counts A character, word and paragraph count is maintained for each document, as well as for each section of a document following a :term:`heading`. The word count and change of words in the current session is displayed in the footer of any document open in the editor, and all stats are -shown in the details panel below the :guilabel:`Project Tree` for any document selected in the -project or novel trees. +shown in the details panel below the project tree for any document selected in the project or novel +trees. The word counts are not updated in real time, but run in the background every few seconds for as long as the document is being actively edited. @@ -242,7 +250,7 @@ long as the document is being actively edited. A total project word count is displayed in the status bar. The total count depends on the sum of the values in the project tree, which again depend on an up to date :term:`project index`. If the counts seem wrong, a full project word recount can be initiated by rebuilding the project's index. -Either from the :guilabel:`Tools` menu, or by pressing :kbd:`F9`. +Either from the **Tools** menu, or by pressing :kbd:`F9`. The rules for how the counts are made is covered in more detail in :ref:`a_counting`. @@ -252,33 +260,35 @@ The rules for how the counts are made is covered in more detail in :ref:`a_count Project Settings ================ -The :guilabel:`Project Settings` can be accessed from the :guilabel:`Project` menu, or by pressing +The **Project Settings** can be accessed from the **Project** menu, or by pressing :kbd:`Ctrl+Shift+,`. This will open a dialog box, with a set of tabs. Settings Tab ------------ -The :guilabel:`Settings` tab holds the project name, title, and author settings. +The **Settings** tab holds the project name, author, and language settings. -The :guilabel:`Project Name` can be edited here. It is used for the GUI (main window title) and for -generating backup files. So keep in mind that if you do change this setting, the backup file names -will change too. +The **Project Name** can be edited here. It is used for the main window title and for generating +backup files. So keep in mind that if you do change this setting, the backup file names will change +too. -You can also change the :guilabel:`Authors` and :guilabel:`Project Language` setting. These are -only used when building the manuscript, for some formats. +You can also change the **Authors** and **Project Language** setting. These are only used when +building the manuscript, for some formats. The language setting is also used when inserting text +into documents in the viewer, like for instance labels for keywords and special comments. If your project is in a different language than your main spell checking language is set to, you -can override the default setting here. The project language can also be changed from the -:guilabel:`Tools` menu. You can also override the automatic backup setting if you wish. +can override the default setting here. The project language can also be changed from the **Tools** +menu. + +You can also override the automatic backup setting for the project if you wish. Status and Importance Tabs -------------------------- -Each document or folder of type :guilabel:`Novel` can be given a "Status" label accompanied by a -coloured icon, and each document or folder of the remaining types can be given an "Importance" -label. +Each document or folder of type **Novel** can be given a "Status" label accompanied by a coloured +icon, and each document or folder of the remaining types can be given an "Importance" label. These labels are there purely for your convenience, and you are not required to use them for any other features to work. No other part of novelWriter accesses this information. The intention is to @@ -316,22 +326,22 @@ Backup ====== An automatic backup system is built into novelWriter. In order to use it, a backup path to where -the backup files are to be stored must be provided in :guilabel:`Preferences`. +the backup files are to be stored must be provided in **Preferences**. The path defaults to a +folder named "Backups" in your home directory. Backups can be run automatically when a project is closed, which also implies it is run when the application itself is closed. Backups are date stamped zip files of the project files in the project folder (files not strictly a part of the project are ignored). The zip archives are stored -in a subfolder of the backup path. The subfolder will have the same name as the -:guilabel:`Project Name` as defined in :ref:`a_proj_settings`. +in a subfolder of the backup path. The subfolder will have the same name as the **Project Name** as +defined in :ref:`a_proj_settings`. -The backup feature, when configured, can also be run manually from the :guilabel:`Tools` menu. -It is also possible to disable automated backups for a given project in -:guilabel:`Project Settings`. +The backup feature, when configured, can also be run manually from the **Tools** menu. It is also +possible to disable automated backups for a given project in **Project Settings**. .. note:: - For the backup to be able to run, the :guilabel:`Project Name` must be set in - :guilabel:`Project Settings`. This value is used to generate the name and path of the backups. - Without it, the backup will not run at all, but it will produce a warning message. + For the backup to be able to run, the **Project Name** must be set in **Project Settings**. This + value is used to generate the name and path of the backups. Without it, the backup will not run + at all, but it will produce a warning message. .. _a_proj_stats: @@ -344,9 +354,8 @@ total word counts of your novel documents and notes at the end of the session, p session lasted either more than 5 minutes, or that the total word count changed. For more details about the log file, see :ref:`a_storage`. -A tool to view the content of the log file is available in the :guilabel:`Tools` menu under -:guilabel:`Writing Statistics`. You can also launch it by pressing :kbd:`F6`, or find it on the -sidebar. +A tool to view the content of the log file is available in the **Tools** menu under **Writing +Statistics**. You can also launch it by pressing :kbd:`F6`, or find it on the sidebar. The tool will show a list of all your sessions, and a set of filters to apply to the data. You can also export the filtered data to a JSON file or to a CSV file that can be opened by a spreadsheet @@ -356,4 +365,4 @@ application like for instance Libre Office Calc or Excel. As of version 1.2, the log file also stores how much of the session time was spent idle. The definition of idle here is that the novelWriter main window loses focus, or the user hasn't made any changes to the currently open document in five minutes. The number of minutes can be altered - in :guilabel:`Preferences`. + in **Preferences**. diff --git a/docs/source/project_references.rst b/docs/source/project_references.rst index cdd29536..7b695801 100644 --- a/docs/source/project_references.rst +++ b/docs/source/project_references.rst @@ -16,9 +16,9 @@ this chapter hopes to explain in more detail how to use this tags and references .. tip:: If you find the Tags and Reference system difficult to follow just from reading this chapter, - you can create a new project in the :guilabel:`Welcome` dialog's New project form and select - "Create an example project" from the "Pre-fill project" option. The example project contains - several examples of tags and references. + you can create a new project in the **Welcome** dialog's New project form and select "Create an + example project" from the "Pre-fill project" option. The example project contains several + examples of tags and references. .. _a_references_metadata: @@ -68,7 +68,7 @@ You can only set **one** tag per heading, and the tag has to be unique across ** the project. After a tag has been defined, it can be referenced in novel documents, or cross-referenced in other -notes. Tags will also show up in the :guilabel:`Outline View` and in the references panel under the +notes. Tags will also show up in the **Outline View** and in the references panel under the document viewer when a document is open in the viewer. The syntax highlighter will indicate to you that the keyword is correctly used and that the tag is @@ -79,7 +79,7 @@ that valid tags do. The tag is the only part of these notes that novelWriter uses. The rest of the document content is there for you to use in whatever way you wish. Of course, the content of the documents can be added to the manuscript, or an outline document. If you want to compile a single document of all your -notes, you can do this from the :guilabel:`Manuscript Build` tool. +notes, you can do this from the **Manuscript Build** tool. .. versionadded:: 2.2 Tags are no longer case sensitive. The tags are by default displayed with the capitalisation you @@ -117,8 +117,8 @@ How to Use References ===================== Each heading of any level in your project can contain references to tags set in project notes. The -references are gathered by the indexer and used to generate the :guilabel:`Outline View`, among -other things. +references are gathered by the indexer and used to generate the **Outline View**, among other +things. References are set as a :term:`keyword` and a list of corresponding tags. The valid keywords are listed below. The format of a reference line is ``@keyword: value1, [value2] ... [valueN]``. All @@ -126,70 +126,69 @@ reference keywords allow multiple values. ``@pov`` The point-of-view character for the current section. The target must be a note tag in a - :guilabel:`Character` type root folder. + **Character** type root folder. ``@focus`` The character that has the focus for the current section. This can be used in cases where the - focus is not a point-of-view character. The target must be a note tag in a :guilabel:`Character` - type root folder. + focus is not a point-of-view character. The target must be a note tag in a **Character** type + root folder. ``@char`` - Other characters in the current section. The target must be a note tag in a - :guilabel:`Character` type root folder. This should not include the point-of-view or focus - character if those references are used. + Other characters in the current section. The target must be a note tag in a **Character** type + root folder. This should not include the point-of-view or focus character if those references + are used. ``@plot`` - The plot or subplot advanced in the current section. The target must be a note tag in a - :guilabel:`Plot` type root folder. + The plot or subplot advanced in the current section. The target must be a note tag in a **Plot** + type root folder. ``@time`` - The timelines touched by the current section. The target must be a note tag in a - :guilabel:`Timeline` type root folder. + The timelines touched by the current section. The target must be a note tag in a **Timeline** + type root folder. ``@location`` The location the current section takes place in. The target must be a note tag in a - :guilabel:`Locations` type root folder. + **Locations** type root folder. ``@object`` - Objects present in the current section. The target must be a note tag in a :guilabel:`Object` - type root folder. + Objects present in the current section. The target must be a note tag in a **Object** type root + folder. ``@entity`` - Entities present in the current section. The target must be a note tag in a - :guilabel:`Entities` type root folder. + Entities present in the current section. The target must be a note tag in an **Entities** type + root folder. ``@custom`` - Custom references in the current section. The target must be a note tag in a :guilabel:`Custom` - type root folder. The custom folder are for any other category of notes you may want to use. + Custom references in the current section. The target must be a note tag in a **Custom** type + root folder. The custom folder are for any other category of notes you may want to use. The syntax highlighter will alert the user that the tags and references are used correctly, and that the tags referenced exist. .. note:: The highlighter may be mistaken if the index of defined tags is out of date. If so, press - :kbd:`F9` to regenerate it, or select :guilabel:`Rebuild Index` from the :guilabel:`Tools` menu. - In general, the index for a document is regenerated when it is saved, so this shouldn't normally - be necessary. + :kbd:`F9` to regenerate it, or select **Rebuild Index** from the **Tools** menu. In general, the + index for a document is regenerated when it is saved, so this shouldn't normally be necessary. .. tip:: If you add a reference in the editor to a tag that doesn't yet exist, you can right-click it and - select :guilabel:`Create Note for Tag`. This will generate a new project note automatically with - the new tag defined. In order for this to be possible, a root folder for that category of - references must already exist. + select **Create Note for Tag**. This will generate a new project note automatically with the new + tag defined. In order for this to be possible, a root folder for that category of references + must already exist. One note can also reference another note in the same way novel documents do. When the note is opened in the document viewer, the references become clickable links, making it easier to follow connections in the plot. You can follow links in the document editor by clicking them with the mouse while holding down the :kbd:`Ctrl` key. Clicked links are always opened in the view panel. -Project notes don't show up in the :guilabel:`Outline View`, so referencing between notes is only +Project notes don't show up in the **Outline View**, so referencing between notes is only meaningful if you want to be able to click-navigate between them, or of course if you just want to highlight that two notes are related. .. tip:: If you cross-reference between notes and export your project as an HTML document using the - :guilabel:`Manuscript Build` tool, the cross-references become clickable links in the exported - HTML document as well. + **Manuscript Build** tool, the cross-references become clickable links in the exported HTML + document as well. Example of a novel document with references to characters and plots: @@ -217,7 +216,7 @@ character ``@`` on a new line. It will first suggest tag or reference keywords f after the ``:`` has been added, suggest references from the list of tags you have already defined. You can use the auto-completer to add multiple references with a ``,`` between them, and even type -new ones. New references can be created by right-clicking on them and selecting -:guilabel:`Create Note for Tag` from the menu. +new ones. New references can be created by right-clicking on them and selecting **Create Note for +Tag** from the menu. .. versionadded:: 2.2 diff --git a/docs/source/project_structure.rst b/docs/source/project_structure.rst index 0763fed7..e344ecd8 100644 --- a/docs/source/project_structure.rst +++ b/docs/source/project_structure.rst @@ -6,14 +6,13 @@ Novel Structure This chapter covers the structure of a novel project. -There are two different types of documents in a project, :guilabel:`Novel Documents` and -:guilabel:`Project Notes`. Active novel documents can only live in a :guilabel:`Novel` type root -folder. You can also move them to :guilabel:`Archive` and :guilabel:`Trash` of course, where they -become inactive. +There are two different types of documents in a project, **Novel Documents** and **Project Notes**. +Active novel documents can only live in a **Novel** type root folder. You can also move them to +**Archive** and **Trash** of course, where they become inactive. -The :guilabel:`Project Tree` can distinguish between the different heading levels of the novel -documents using coloured icons, and optionally add emphasis on the label, set in -:guilabel:`Preferences`. +The project tree can distinguish between the different heading levels of the novel documents using +coloured icons, and optionally add emphasis on the label, set in **Preferences** for easier +identification. .. _a_struct_heads: @@ -29,40 +28,41 @@ title. See also the :ref:`a_fmt` section for more details about the markup synta .. note:: The heading levels are not only important when generating the manuscript, they are also used by - the indexer when building the outline tree in the :guilabel:`Outline View` as well as in the - :guilabel:`Novel Tree`. Each heading also starts a new region where new Tags and References - can be defined. See :ref:`a_references` for more details. + the indexer when building the outline tree in the **Outline View** as well as in the **Novel + Tree**. Each heading also starts a new region where new Tags and References can be defined. See + :ref:`a_references` for more details. -The syntax for the four basic heading types, and the two special types, is listed in section +The syntax for the four basic heading types, and the three special types, is listed in section :ref:`a_fmt_head`. The meaning of the four levels for the structure of your novel is as follows: **Heading Level 1: Partition** - This heading level signifies that the text refers to a top level partition. This is useful when + This heading level signifies that the text refers to a top level heading. This is useful when you want to split the manuscript up into books, parts, or acts. These headings are not required. The novel title itself should use the special heading level ``#!`` covered in :ref:`a_fmt_head`. **Heading Level 2: Chapter** - This heading level signifies a chapter level partition. Each time you want to start a new - chapter, you must add such a heading. If you choose to split your manuscript up into one - document per scene, you need a single chapter document with just the heading. You can of course - also add a synopsis and reference keywords to the chapter document. If you want to open the - chapter with a quote or other introductory text that isn't part of a scene, this is also where - you'd put that text. + This heading level signifies a chapter. Each time you want to start a new chapter, you must add + such a heading. If you choose to split your manuscript up into one document per scene, you need + a single chapter document with just the heading. You can of course also add a synopsis and + reference keywords to the chapter document. If you want to open the chapter with a quote or + other introductory text that isn't part of a scene, this is also where you'd put that text. **Heading Level 3: Scene** - This heading level signifies a scene level partition. You must provide a title text, but the - title text can be replaced with a scene separator or just skipped entirely when you build your - manuscript. + This heading level signifies a scene. You must provide a title text, but the title text can be + replaced with a scene separator or just skipped entirely when you build your manuscript. If you + need to distinguish between hard and soft scene breaks, there is an alternative format for + scenes you can use for this distinction. The formatting is covered in :ref:`a_fmt_head`. See + also :ref:`a_struct_heads_scenes`. **Heading Level 4: Section** - This heading level signifies a sub-scene level partition, usually called a "section" in the + This heading level can be used to split up a scene, usually called a "section" in the documentation and the user interface. These can be useful if you want to change references mid-scene, like if you change the point-of-view character. You are free to use sections as you - wish, and you can filter them out of the final manuscript just like with scene titles. + wish, and you can filter them out of the final manuscript. -Page breaks are automatically added before partition and chapter headings when you build your -project to a format that supports page breaks. If you want page breaks in other places, you have to -specify them manually. See :ref:`a_fmt_break`. +Page breaks can be automatically added before partition, chapter and scene headings from the +**Manuscript Build** tool when you build your project to a format that supports page breaks. If you +want page breaks in other places, you have to specify them manually. See :ref:`a_fmt_break`. .. tip:: There are multiple options of how to process novel headings when building the manuscript. For @@ -76,9 +76,9 @@ specify them manually. See :ref:`a_fmt_break`. Novel Title and Front Matter ---------------------------- -It is recommended that you add a document at the very top of each Novel root folder with the novel -title as the first line. You should modify the level 1 heading format code with an ``!`` in order -to render it as a document title that is excluded from any automatic Table of Content in a +It is recommended that you add a document at the very top of each **Novel** root folder with the +novel title as the first line. You should modify the level 1 heading format code with an ``!`` in +order to render it as a document title that is excluded from any automatic Table of Content in a manuscript build document, like so: .. code-block:: md @@ -102,7 +102,7 @@ Unnumbered Chapter Headings If you use the automatic numbering feature for your chapters, but you want to keep some special chapters separate from this, you can add an ``!`` to the level 2 heading formatting code to tell -the build tool to skip these chapters. +the build tool to skip these chapters when adding numbers. .. code-block:: md @@ -110,13 +110,31 @@ the build tool to skip these chapters. Chapter Text -There is a separate formatting feature for such chapter titles in the :guilabel:`Manuscript Build` -tool as well. See the :ref:`a_manuscript` page for more details. When building a document of a -format that supports page breaks, also unnumbered chapters will have a page break added just like -for normal chapters. +There is a separate formatting feature for such chapter titles in the **Manuscript Build** tool as +well. See the :ref:`a_manuscript` page for more details. When building a document of a format that +supports page breaks, also unnumbered chapters can have a page break added just like for normal +chapters. -.. Note:: - Previously, you could also disable the automatic numbering of a chapter by adding an ``*`` as - the first character of the chapter title itself. This feature has been dropped in favour of the - current format in order to keep level 1 and 2 headers consistent. Please update your chapter - headings if you've used this syntax. + +.. _a_struct_heads_scenes: + +Hard and Soft Scene Breaks +-------------------------- + +If you need two different ways to style scenes in your manuscript, like if you want to insert +different scene separators for soft and hard scene breaks, there is an alternative scene format +available for scene headings with a ``!`` added to the formatting code. + +.. code-block:: md + + ### Soft Scene Transition + + A soft scene break. + + ###! Hard Scene Transition + + A hard scene break. + +There is a separate formatting feature for these titles in the **Manuscript Build** tool. + +.. versionadded:: 2.4 diff --git a/docs/source/tech_locations.rst b/docs/source/tech_locations.rst index da199292..3cd09e60 100644 --- a/docs/source/tech_locations.rst +++ b/docs/source/tech_locations.rst @@ -15,10 +15,9 @@ file locations are described in this chapter. Configuration ============= -The general configuration of novelWriter, including everything that is in :guilabel:`Preferences`, -is saved in one central configuration file. The location of this file depends on your operating -system. The system paths are provided by the Qt QStandardPaths_ class and its ``ConfigLocation`` -value. +The general configuration of novelWriter, including everything that is in **Preferences**, is saved +in one central configuration file. The location of this file depends on your operating system. The +system paths are provided by the Qt QStandardPaths_ class and its ``ConfigLocation`` value. The standard paths are: @@ -40,9 +39,8 @@ Application Data ================ novelWriter also stores a bit of data that is generated by the user's actions. This includes the -list of recent projects form the :guilabel:`Open Project` dialog. Custom themes should also be -saved here. The system paths are provided by the Qt QStandardPaths_ class and its -``AppDataLocation`` value. +list of recent projects form the **Welcome** dialog. Custom themes should also be saved here. The +system paths are provided by the Qt QStandardPaths_ class and its ``AppDataLocation`` value. The standard paths are: @@ -56,3 +54,12 @@ user's username on Windows. .. note:: These are the standard operating system defined locations. If your system has been set up in a different way, these locations may also be different. + +The Application Data location also holds several folders: + +``cache`` + This folder is used to save the preview data for the **Manuscript Build** tool. + +``icons``, ``syntax`` and ``themes`` + These folders are empty by default, but this is where the user can store custom theme files. + See :ref:`a_custom` for more details. diff --git a/docs/source/tech_source.rst b/docs/source/tech_source.rst index 8c23463e..87d616a3 100644 --- a/docs/source/tech_source.rst +++ b/docs/source/tech_source.rst @@ -159,6 +159,5 @@ You can also build a PDF manual from the documentation using the ``pkgutils.py`` python pkgutils.py manual This will build the documentation as a PDF using LaTeX. The file will then be copied into the -assets folder and made available in the :guilabel:`Help` menu in novelWriter. The Sphinx build -system has a few extra dependencies when building the PDF. Please check the `Sphinx Docs`_ for more -details. +assets folder and made available in the **Help** menu in novelWriter. The Sphinx build system has a +few extra dependencies when building the PDF. Please check the `Sphinx Docs`_ for more details. diff --git a/docs/source/tech_storage.rst b/docs/source/tech_storage.rst index 7fb9d39b..79d5890a 100644 --- a/docs/source/tech_storage.rst +++ b/docs/source/tech_storage.rst @@ -55,10 +55,10 @@ string. The documents are saved with a filename assembled from this handle and t ``.nwd``. If you wish to find the file system location of a document in the project, you can either look it -up in the project XML file, select :guilabel:`Show File Details` from the :guilabel:`Document` menu -when having the document open in the editor, or look in the ``ToC.txt`` file in the root of the -project folder. The ``ToC.txt`` file has a list of all documents in the project, referenced by -their label, and where they are saved. +up in the project XML file, select **Show File Details** from the **Document** menu when having the +document open in the editor, or look in the ``ToC.txt`` file in the root of the project folder. The +``ToC.txt`` file has a list of all documents in the project, referenced by their label, and where +they are saved. The reason for this cryptic file naming is to avoid issues with file naming conventions and restrictions on different operating systems, and also to have a file name that does not depend on @@ -109,7 +109,7 @@ The Project Index Between writing sessions, the project index is saved in a JSON file in ``meta/index.json``. This file is not critical. If it is lost, it can be completely rebuilt from within novelWriter from -the :guilabel:`Tools` menu. +the **Tools** menu. The index is maintained and updated whenever a document or note is saved in the editor. It contains all references and tags in documents and notes, as well as the location of all headers in the @@ -125,8 +125,8 @@ the index. If this too fails, you have likely encountered a bug. Build Definitions ----------------- -The build definitions from the :guilabel:`Manuscript Build` tool are kept in the -``meta/builds.json`` file. If this file is lost, all custom build definitions are lost too. +The build definitions from the **Manuscript Build** tool are kept in the ``meta/builds.json`` file. +If this file is lost, all custom build definitions are lost too. Cached GUI Options @@ -144,17 +144,17 @@ Custom Word List ---------------- A file named ``meta/userdict.json`` contains all the custom words you've added to the project for -spell checking purposes. The content of the file can be edited from the :guilabel:`Tools` menu. If -you lose this file, all your custom spell check words will be lost too. +spell checking purposes. The content of the file can be edited from the **Tools** menu. If you lose +this file, all your custom spell check words will be lost too. Session Stats ------------- The writing progress is saved in the ``meta/sessions.jsonl`` file. This file records the length -and word counts of each writing session on the given project. The file is used by the -:guilabel:`Writing Statistics` tool. If this file is lost, the history it contains is also lost, -but it has otherwise no impact on the project. +and word counts of each writing session on the given project. The file is used by the **Writing +Statistics** tool. If this file is lost, the history it contains is also lost, but it has otherwise +no impact on the project. Each session is recorded as a JSON object on a single line of the file. Each session record is appended tot he file. diff --git a/docs/source/usage_breakdown.rst b/docs/source/usage_breakdown.rst index c0f14968..f34f40ec 100644 --- a/docs/source/usage_breakdown.rst +++ b/docs/source/usage_breakdown.rst @@ -21,14 +21,15 @@ GUI Layout and Design The user interface of novelWriter is intended to be as minimalistic as practically possible, while at the same time provide useful features needed for writing a novel. -The main window does not have an editor toolbar like many other applications do. This reduces -clutter, and since the documents are formatted with style tags, it is more or less redundant. -Still, a small formatting toolbar can be popped out by clicking the three dots in the header of the -document editor to give quick access to standard formatting codes. +The main window does not by default have an editor toolbar like many other applications do. This +reduces clutter, and since the documents are formatted with style tags, it is not needed most of +the time. Still, a small formatting toolbar can be popped out by clicking the left-most button in +the header of the document editor. It gives quick access to standard formatting codes. Most formatting features supported are available through convenient keyboard shortcuts. They are -also available in the main menu, so you don't have to look up formatting codes every time you need -them. For reference, a list of all shortcuts can be found in the :ref:`a_kb` chapter. +also available in the main menu under **Format**, so you don't have to look up formatting codes +every time you need them. For reference, a list of all shortcuts can be found in the :ref:`a_kb` +chapter. .. note:: novelWriter is not intended to be a full office type word processor. It doesn't support images, @@ -37,13 +38,13 @@ them. For reference, a list of all shortcuts can be found in the :ref:`a_kb` cha simple features. On the left side of the main window, you will find a sidebar. This bar has buttons for the standard -views you can switch between, a quick link to the :guilabel:`Build Manuscript` tool, and a set of +views you can switch between, a quick link to the **Build Manuscript** tool, and a set of project-related tools and quick access to settings at the bottom. .. versionadded:: 2.2 A number of new formatting options were added in 2.2 to allow for some special formatting cases. At the same time, a small formatting toolbar was added in the editor. It is hidden by default, - but can be opened by pressing the three dots icon in the top right corner. + but can be opened by pressing the icon button in the top right corner. Project Tree and Editor View @@ -53,19 +54,19 @@ Project Tree and Editor View A screenshot of the Project Tree and Editor View. -When in :guilabel:`Project Tree View` mode, the main work area of the main window is split in two, -or optionally three, panels. The left-most panel contains the project tree and all the documents in -your project. The second panel is the document editor. +When the application is in **Project Tree View** mode, the main work area of the main window is +split in two, or optionally three, panels. The left-most panel contains the project tree and all +the documents in your project. The second panel is the document editor. An optional third panel on the right side contains a document viewer which can view any document in your project independently of what is open in the document editor. This panel is not intended as a -preview window, although you can use it for this purpose if you wish if you need to check that the -formatting tags behave as you expected. The main purpose of the viewer is for viewing your notes -next to your editor while you're writing. +preview window, although you can use it for this purpose if you wish. For instance if you need to +check that the formatting tags behave as you expect. However, the main purpose of the viewer is for +viewing your notes next to your editor while you're writing. -The editor also has a :guilabel:`Focus Mode` you can toggle either from the menu, from the icon in -the editor's header, or by pressing :kbd:`F8`. When :guilabel:`Focus Mode` is enabled, all the user -interface elements other than the document editor itself are hidden away. +The editor also has a **Focus Mode** you can toggle either from the menu, from the icon in the +editor's header, or by pressing :kbd:`F8`. When **Focus Mode** is enabled, all the user interface +elements other than the document editor itself are hidden away. Novel View and Editor View @@ -73,31 +74,32 @@ Novel View and Editor View .. figure:: images/fig_novel_tree_view.png - A screenshot of the Novel Tree View. + A screenshot of the Novel Tree and Editor View. -When in :guilabel:`Novel Tree View` mode, the project tree is replaced by an overview of your novel -structure for a specific Novel :term:`root folder`. Instead of showing individual documents, the -tree now shows all headings of your novel text. This includes multiple headings within the same -document. +When the application is in **Novel Tree View** mode, the project tree is replaced by an overview of +your novel structure for a specific Novel :term:`root folder`. Instead of showing individual +documents, the tree now shows all headings of your novel text. This includes multiple headings +within the same document. -Each heading is indented according to the heading level. You can open and edit your novel documents -from this view as well. All headings contained in the currently open document should be highlighted -in the view to indicate which ones belong together in the same document. +Each heading is indented according to the heading level, not its parent/child relationship to other +elements of your project. You can open and edit your novel documents from this view as well. All +headings contained in the currently open document should be highlighted in the view to indicate +which ones belong together in the same document. -If you have multiple "Novel" type root folders, the header of the novel view becomes a dropdown +If you have multiple **Novel** type root folders, the header of the novel view becomes a dropdown box. You can then switch between them by clicking the :guilabel:`Outline of ...` text. You can also click the novel icon button next to it. Generally, the novel view should update when you make changes to the novel structure, including edits of the current document in the editor. The information is only updated when the automatic save of the document is triggered, or you manually press :kbd:`Ctrl+S` to save changes. (You can -adjust the auto-save interval in :guilabel:`Preferences`.) You can also regenerate the whole novel -view by pressing the refresh button in the novel view header. +adjust the auto-save interval in **Preferences**.) You can also regenerate the whole novel view by +pressing the refresh button in the novel view header. It is possible to show an optional third column in the novel view. The settings are available from the menu button in the toolbar. -If you click the arrow icon to the right of each item, a tooltip will pop out showing you all the +If you click the triangular icon to the right of each item, a tooltip will pop out showing all the meta data collected for that heading. @@ -108,9 +110,9 @@ Novel Outline View A screenshot of the Novel Outline View. -When in :guilabel:`Novel Outline View` mode, the tree, editor and viewer will be replaced by a -large table that shows the entire novel structure with all the tags and references listed. Pretty -much all collected meta data is available here in different columns. +When the application is in **Novel Outline View** mode, the tree, editor and viewer will be +replaced by a large table that shows the entire novel structure with all the tags and references +listed. Pretty much all collected meta data is available here in different columns. You can select which novel root folder to display from the dropdown box, and you can select which columns to show or hide from the menu button. You can also rearrange the columns by drag and drop. @@ -122,18 +124,30 @@ Colour Themes By default, novelWriter will use the colour theme provided by the Qt library, which is determined by the Fusion_ style setting. You can also choose between a standard dark and light theme that have -neutral colours from :guilabel:`Preferences`. Other colour themes are also available. More themes -can be contributed to novelWriter on GitHub. +neutral colours from **Preferences**. + +If you wish, you *can* create your own colour themes, and even have them added to the application. +See :ref:`a_custom_theme` for more details. Switching the GUI colour theme does not affect the colours of the editor and viewer. They have -separate colour selectable from the "Document colour theme" setting in :guilabel:`Preferences`. -They are separated because there are a lot more options to choose from for the editor and viewer. +separate colour selectable from the "Document colour theme" setting in **Preferences**. They are +separated because there are a lot more options to choose from for the editor and viewer. .. note:: If you switch between light and dark mode on the GUI, you should also switch editor theme to match, otherwise icons may be hard to see in the editor and viewer. +Project Search +-------------- + +A global search tool is available from the side bar. It allows you to search through your entire +project. The tool does not provide a replace feature. There is a search and replace tool available +in the document editor that acts on the open document. + +.. versionadded:: 2.4 + + .. _a_breakdown_project: Project Layout @@ -152,12 +166,12 @@ within those documents. An illustration of how heading levels correspond to the novel structure. -The four heading levels (**H1** to **H4**) are treated as follows: +The four heading levels, **Level 1** to **Level 4**, are treated as follows: -* **H1** is used for the novel title, and for partitions. -* **H2** is used for chapter tiles. -* **H3** is used for scene titles -- optionally replaced by separators. -* **H4** is for section titles within scenes, if such granularity is needed. +* **Level 1** is used for the novel title, and for partitions. +* **Level 2** is used for chapter tiles. +* **Level 3** is used for scene titles -- optionally replaced by separators. +* **Level 4** is for section titles within scenes, if such granularity is needed. The project tree will select an icon for the document based on the first heading in it. @@ -178,25 +192,25 @@ Building a Manuscript ===================== The project can at any time be assembled into a range of different formats through the -:guilabel:`Build Manuscript` tool. Natively, novelWriter supports `Open Document`_, HTML5, and +**Build Manuscript** tool. Natively, novelWriter supports `Open Document`_, HTML5, and various flavours of Markdown. The HTML5 format is suitable for conversion by a number of other tools like Pandoc_, or for importing into word processors if the Open Document format isn't suitable. The Open Document format -is supported by most Office type applications. In addition, printing is also possible. Print to PDF +is supported by most office type applications. In addition, printing is also possible. Print to PDF is available from the print dialog. -In addition, you can export the content of the project to a JSON file. This is useful if you want -to write your own custom processing script in for instance Python, as the entire novel can be read -into a Python dictionary with a couple of lines of code. The JSON file can be populated with either -HTML formatted text, or with the raw text as typed into the novel documents. +For advanced processing, you can export the content of the project to a JSON file. This is useful +if you want to write your own custom processing script in for instance Python, as the entire novel +can be read into a Python dictionary with a couple of lines of code. The JSON file can be populated +with either HTML formatted text, or with the raw text as typed into the novel documents. See :ref:`a_manuscript` for more details. .. versionadded:: 2.1 - You can now define multiple build definitions in the :guilabel:`Build Manuscript` tool. This - allows you to define specific settings for various types of draft documents, outline documents, - and manuscript formats. See :ref:`a_manuscript` for more details. + You can now define multiple build definitions in the **Build Manuscript** tool. This allows you + to define specific settings for various types of draft documents, outline documents, and + manuscript formats. See :ref:`a_manuscript` for more details. .. _a_breakdown_storage: diff --git a/docs/source/usage_format.rst b/docs/source/usage_format.rst index f452c5fd..85b79c54 100644 --- a/docs/source/usage_format.rst +++ b/docs/source/usage_format.rst @@ -10,7 +10,7 @@ values and allowing for some text formatting. The syntax is based on Markdown, b (bold) and strike through text, as well as four levels of headings. For some further complex formatting needs, a set of shortcodes can be used. -In addition to formatting codes, novelWriter allows for comments, a synopsis tag, and a set of +In addition to formatting codes, novelWriter allows for comments, a synopsis tag, and a number of keyword and value sets used for :term:`tags` and :term:`references`. There are also some codes that apply to whole paragraphs. See :ref:`a_fmt_text` for more details. @@ -35,7 +35,7 @@ have a distinct colour, and the references themselves will get a colour if they references will get a squiggly error line underneath. The same applies to duplicate tags. There are a number of syntax highlighter colour themes available, both for light and dark GUIs. You -can select them from :guilabel:`Preferences`. +can select them from **Preferences**. .. _a_fmt_head: @@ -54,6 +54,9 @@ correctly to produce the intended result. See :ref:`a_struct_heads` for more det ``# Title Text`` Heading level one. For novel documents, the level indicates the start of a new partition. + Partitions are for when you want to split your story into "Part 1", "Part 2", etc. You can also + choose to use them for splitting the text up into acts, and then hide these headings in your + manuscript. ``## Title Text`` Heading level two. For novel documents, the level indicates the start of a new chapter. Chapter @@ -62,16 +65,17 @@ correctly to produce the intended result. See :ref:`a_struct_heads` for more det ``### Title Text`` Heading level three. For novel documents, the level indicates the start of a new scene. Scene numbers or scene separators can be inserted automatically when building the manuscript, so you - can use the title field as a working title for your scenes if you wish. + can use the title field as a working title for your scenes if you wish, but you must provide a + minimal title. ``#### Title Text`` Heading level four. For novel documents, the level indicates the start of a new section. Section titles can be replaced by separators or ignored completely when building the manuscript. -For headings level one through three, adding a ``!`` modifies its meaning: +For headings level one through three, adding a ``!`` modifies the meaning of the heading: ``#! Title Text`` - This tells the build tool that the level one heading is intended to be used for the novel's or + This tells the build tool that the level one heading is intended to be used for the novel or notes folder's main title, like for instance on the front page. When building the manuscript, this will use a different styling and will exclude the title from, for instance, a Table of Contents in Libre Office. @@ -82,9 +86,10 @@ For headings level one through three, adding a ``!`` modifies its meaning: :ref:`a_struct_heads_unnum` for more details. ``###! Title Text`` - This is an alternative scene heading that can be formatted differently in the - :guilabel:`Manuscript Build` tool. It is intended for separating "soft" and "hard" scene breaks. - Otherwise, it behaves identically to a regular scene heading. + This is an alternative scene heading that can be formatted differently in the **Manuscript + Build** tool. It is intended for separating "soft" and "hard" scene breaks. Aside from this, it + behaves identically to a regular scene heading. See :ref:`a_struct_heads_scenes` for more + details. .. note:: The space after the ``#`` or ``!`` character is mandatory. The syntax highlighter will change @@ -106,8 +111,8 @@ In addition, the editor supports a few additional types of white spaces: * Thin spaces are also supported, and can be inserted with :kbd:`Ctrl+K`, :kbd:`Shift+Space`. * Non-breaking thin space can be inserted with :kbd:`Ctrl+K`, :kbd:`Ctrl+Space`. -These are all insert features, and the :guilabel:`Insert` menu has more. The keyboard shortcuts for -them are also listed in :ref:`a_kb_ins`. +These are all insert features, and the **Insert** menu has more. The keyboard shortcuts for them +are also listed in :ref:`a_kb_ins`. Non-breaking spaces are highlighted by the syntax highlighter with an alternate coloured background, depending on the selected theme. @@ -129,12 +134,12 @@ A minimal set of Markdown text emphasis styles are supported for text paragraphs The text is rendered as emphasised text (italicised). ``**text**`` - The text is rendered as strongly important text (bold). + The text is rendered as strongly emphasised text (bold). ``~~text~~`` Strike through text. -In Markdown guides it is often recommended to differentiate between strong importance and emphasis +In Markdown guides it is often recommended to differentiate between strong emphasis and emphasis by using ``**`` for strong and ``_`` for emphasis, although Markdown generally also supports ``__`` for strong and ``*`` for emphasis. However, since the differentiation makes the highlighting and conversion significantly simpler and faster, in novelWriter this is a rule, not just a @@ -182,14 +187,15 @@ solved with simple Markdown-like formatting codes. Available shortcodes are list "``[i]text[/i]``", "Text is rendered as italicised text." "``[s]text[/s]``", "Text is rendered as strike through text." "``[u]text[/u]``", "Text is rendered as underlined text." + "``[m]text[/m]``", "Text is rendered as highlighted text." "``[sup]text[/sup]``", "Text is rendered as superscript text." "``[sub]text[/sub]``", "Text is rendered as subscript text." Unlike Markdown style codes, these can be used anywhere within a paragraph. Even in the middle of a word if you need to. You can also freely combine them to form more complex formatting. -The shortcodes are available from the :guilabel:`Format` menu and in the editor toolbar, which can -be activated by clicking the three dots in the editor header. +The shortcodes are available from the **Format** menu and in the editor toolbar, which can be +activated by clicking the left-most icon button in the editor header. .. versionadded:: 2.2 @@ -199,30 +205,39 @@ be activated by clicking the three dots in the editor header. Comments and Synopsis ===================== -In addition to these standard Markdown features, novelWriter also allows for comments in documents. -The text of a comment is ignored by the word counter. The text can also be filtered out when -building the manuscript or viewing the document. +In addition to the above formatting features, novelWriter also allows for comments in documents. +The text of a comment is always ignored by the word counter. The text can also be filtered out +when building the manuscript or viewing the document. -If the first word of a comment is ``Synopsis:`` (with the colon included), the comment is treated -in a special manner and will show up in the :ref:`a_ui_outline` in a dedicated column. The word -``synopsis`` is not case sensitive. If it is correctly formatted, the syntax highlighter will -indicate this by altering the colour of the word. +The first word of a comment, followed by a colon, can be one of a small set of modifiers that +indicates the comment is intended for a specific purpose. For instance, if the comment starts with +``Synopsis:``, the comment is treated in a special manner and will show up in the +:ref:`a_ui_outline` in a dedicated column. The word ``synopsis`` is not case sensitive. If it is +correctly formatted, the syntax highlighter will indicate this by altering the colour of the word. -``% text ...`` +The different styles of comments are as follows: + +``% Comment text ...`` This is a comment. The text is not rendered by default (this can be overridden), seen in the - document viewer, or counted towards word counts. + document viewer, or counted towards word counts. It is intended for you to make notes in your + text for your own sake, whatever that may be, that isn't part of the story text. This is the + general format of a comment. -``%Synopsis: text ...`` +``%Synopsis: Comment text ...`` This is a synopsis comment. It is generally treated in the same way as a regular comment, except that it is also captured by the indexing algorithm and displayed in the :ref:`a_ui_outline`. It can also be filtered separately when building the project to for instance generate an outline document of the whole project. -``%Short: text ...`` +``%Short: Comment text ...`` This is a short description comment. It is identical to the synopsis comment (they are interchangeable), but is intended to be used for project notes. The text shows up in the - Reference panel below the document viewer in the last column labelled - :guilabel:`Short Description`. + Reference panel below the document viewer in the last column labelled **Short Description**. + +``%~ Comment text ...`` + This can be used to exclude story text from your manuscript without having to delete it from + your text. Comments with the ``~`` will *never* be included in the manuscript, even if you have + chosen to include comments in it. That is the main difference between these two formats. .. note:: Only one comment can be flagged as a synopsis or short comment for each heading. If multiple @@ -245,14 +260,14 @@ heading. Setting it multiple times under the same heading will just override the A tag keyword followed by the tag value, like for instance the name of a character. References can be set anywhere within a section, and are collected according to their category. -References are in the form: +References are on the form: -``@keyword: value`` +``@keyword: value1, value2, ..., valueN`` A reference keyword followed by a value, or a comma separated list of values. Tags and references are covered in detail in the :ref:`a_references` chapter. The keywords can be -inserted at the cursor position in the editor via the :guilabel:`Insert` menu. If you start typing -an ``@`` on a new line, and auto-complete menu will also pop up suggesting keywords. +inserted at the cursor position in the editor via the **Insert** menu. If you start typing an ``@`` +on a new line, and auto-complete menu will also pop up suggesting keywords. .. _a_fmt_align: @@ -260,8 +275,8 @@ an ``@`` on a new line, and auto-complete menu will also pop up suggesting keywo Paragraph Alignment and Indentation =================================== -All documents have the text by default aligned to the left or justified, depending on your -settings in :guilabel:`Preferences`. +All documents have the text by default aligned to the left or justified, depending on your setting +in **Preferences**. You can override the default text alignment on individual paragraphs by specifying alignment tags. These tags are double angle brackets. Either ``>>`` or ``<<``. You put them either before or after @@ -296,6 +311,10 @@ Examples: Vertical Space and Page Breaks ============================== +You can apply page breaks to partition, chapter and scene headings for novel documents from the +**Manuscript Build** tool. If you need to add a page break or additional vertical spacing in other +places, there are special codes available for this purpose. + Adding more than one line break between paragraphs will **not** increase the space between those paragraphs when building the project. To add additional space between paragraphs, add the text ``[vspace]`` on a line of its own, and the build tool will insert a blank paragraph in its place. @@ -303,13 +322,8 @@ paragraphs when building the project. To add additional space between paragraphs If you need multiple blank paragraphs just add a colon and a number to the above code. For instance, writing ``[vspace:3]`` will insert three blank paragraphs. -Normally, the manuscript build tool will insert a page break before all headings of level one and -for all headings of level two for novel documents, i.e. chapters, but not for project notes. - -If you need to add a page break somewhere else, put the text ``[new page]`` on a line by itself -before the text you wish to start on a new page. - -If you want page breaks for scenes and sections, you must add them manually. +If you need to add a page break somewhere, put the text ``[new page]`` on a line by itself before +the text you wish to start on a new page. .. note:: The page break code is applied to the text that follows it. It adds a "page break before" mark @@ -325,8 +339,8 @@ If you want page breaks for scenes and sections, you must add them manually. [vspace:2] This is another text paragraph, but there will be two empty paragraphs - in-between them. + between them. [new page] - This text will always start on a new page if the build format has pages. + This text will start on a new page if the build format has pages. diff --git a/docs/source/usage_project.rst b/docs/source/usage_project.rst index 76bccb48..fceca8b5 100644 --- a/docs/source/usage_project.rst +++ b/docs/source/usage_project.rst @@ -8,7 +8,7 @@ This chapter covers in more detail the different project views available in nove .. figure:: images/fig_project_tree_detailed.png - The Project Tree as it appears when loading a sample project. + The **Project Content** tree as it appears when loading a sample project. .. _a_ui_tree: @@ -21,33 +21,32 @@ the project, and has four columns. **Column 1** The first column shows the icon and label of each folder, document, or note in your project. The - label is not the same as the title you set inside the document. However, the document's label - will appear in the header above the document text itself so you know where in the project an - open document belongs. The icon is selected based on the type of item, and for novel documents, - the level of the first header in the document text. + label is not the same as the heading title you set inside the document. However, the document's + label will appear in the header above the document text itself so you know where in the project + an open document belongs. The icon is selected based on the type of item, and for novel + documents, the level of the first heading in the document text. **Column 2** The second column shows the word count of the document, or the sum of words of the child items for folders and documents with sub-documents. If the counts seem incorrect, they can be updated - by rebuilding the :term:`project index` from the :guilabel:`Tools` menu, or by pressing - :kbd:`F9`. + by rebuilding the :term:`project index` from the **Tools** menu, or by pressing :kbd:`F9`. **Column 3** The third column indicates whether the document is considered active or inactive in the project. You can use this flag to indicate that a document is still in the project, but should not be - considered an active part of it. When you run the :guilabel:`Build Manuscript` tool, you can - include or exclude documents based on this flag. You can change this value from the + considered an active part of it. When you run the **Build Manuscript** tool, you can include or + exclude documents based on this flag. You can change this value from the right-click :term:`context menu`. **Column 4** The fourth column shows the user-defined status or importance labels you've assigned to each project item. See :ref:`a_ui_tree_status` for more details on how to uses these labels. You can - change these labels from the :term:`context menu`. + select these labels from the :term:`context menu`, and define them in **Project Settings**. Right-clicking an item in the project tree will open a context menu under the cursor, displaying a selection of actions that can be performed on the selected item. -At the top of the tree, you will find a set of buttons. +At the top of the project tree, you will find a set of buttons. * The first button is a quick links button that will show you a dropdown menu of all the :term:`root folders` in your project. Selecting one will move to that position in @@ -73,10 +72,10 @@ addition to the word count. Splitting and Merging Documents ------------------------------- -Under the :guilabel:`Transform` submenu in the context menu of an item in the project tree, you -will find several options on how to change a document or folder. This includes changing between -document and note, but also splitting them into multiple documents, or merging child items into a -single document. +Under the **Transform** submenu in the context menu of an item in the project tree, you will find +several options on how to change a document or folder. This includes changing between document and +note, but also splitting them into multiple documents, or merging child items into a single +document. Splitting Documents @@ -84,12 +83,12 @@ Splitting Documents .. figure:: images/fig_project_split_tool.png - The :guilabel:`Split Document` dialog. + The **Split Document** dialog. -The :guilabel:`Split Document by Heading` option will open a dialog that allows you to split the -selected document into multiple new documents based on the headings it contains. You can select at -which heading level the split is to be performed from the dropdown box. The list box will preview -which headings will be split into new documents. +The **Split Document by Headings** option will open a dialog that allows you to split the selected +document into multiple new documents based on the headings it contains. You can select at which +heading level the split is to be performed from the dropdown box. The list box will preview which +headings will be split into new documents. You are given the option to create a folder for these new documents, and whether or not to create a hierarchy of documents. That is, put sections under scenes, and scenes under chapters. @@ -103,18 +102,18 @@ Merging Documents .. figure:: images/fig_project_merge_tool.png - The :guilabel:`Merge Documents` dialog. + The **Merge Documents** dialog. You have two options for merging documents that are child elements of another document. You can -either :guilabel:`Merge Child Items into Self` and :guilabel:`Merge Child Items into New`. The -first option will pull all content of child items and merge them into the parent document, while -the second option will create a new document in the process. +either **Merge Child Items into Self** and **Merge Child Items into New**. The first option will +pull all content of child items and merge them into the parent document, while the second option +will create a new document in the process. When merging documents in a folder, you only have the latter process is possible, so only the -choice :guilabel:`Merge Documents in Folder` is available. +choice **Merge Documents in Folder** is available. -In either case, the :guilabel:`Merge Documents` dialog will let you exclude documents you don't -want to include, and it also lets you reorder them if you wish. +In either case, the **Merge Documents** dialog will let you exclude documents you don't want to +include, and it also lets you reorder them if you wish. .. _a_ui_tree_status: @@ -124,14 +123,14 @@ Document Importance and Status Each document or folder in your project can have either a "Status" or "Importance" flag set. These are flags that you control and define yourself, and novelWriter doesn't use them for anything. To -modify the labels, go to their respective tabs in :guilabel:`Project Settings`. +modify the labels, go to their respective tabs in **Project Settings**. The "Status" flag is intended to tag a :term:`novel document` as for instance a draft or as completed, and the "Importance" flag is intended to tag character notes, or other :term:`project notes`, as for instance a main, major, or minor character or story element. Whether a document uses a "Status" or "Importance" flag depends on which :term:`root folder` it -lives in. If it's in a Novel type folder, it uses the "Status" flag, otherwise it uses an +lives in. If it's in a **Novel** type folder, it uses the "Status" flag, otherwise it uses an "Importance" flag. @@ -142,7 +141,7 @@ Project Tree Drag & Drop The project tree allows drag & drop to a certain extent to allow you to reorder your documents and folders. Moving a document in the project tree will affect the text's position when you assemble -your manuscript in the :guilabel:`Manuscript Build` tool. +your manuscript in the **Manuscript Build** tool. .. versionadded:: 2.2 You can now select multiple items in the project tree by holding down the :kbd:`Ctrl` or @@ -150,14 +149,15 @@ your manuscript in the :guilabel:`Manuscript Build` tool. You can drag and drop documents and regular folders, but not root folders. If you select multiple items, they can only be dragged and dropped if they are siblings. That is, they have the same -parent item in the project. This is due to the way drag and drop is implemented in the user -interface framework novelWriter is built upon. +parent item in the project. This limitation is due to the way drag and drop is implemented in the +user interface framework novelWriter is built upon. -Documents and their folders can be rearranged freely within their root folders. If you move a Novel -document out of a Novel folder, it will be converted to a project note. Notes can be moved freely -between all root folders, but keep in mind that if you move a note into a Novel type root folder, -its "Importance" setting will be switched with a "Status" setting. See :ref:`a_ui_tree_status`. The -old value will not be overwritten though, and should be restored if you move it back at some point. +Documents and their folders can be rearranged freely within their root folders. If you move a +**Novel Document** out of a **Novel** folder, it will be converted to a **Project Note**. Notes can +be moved freely between all root folders, but keep in mind that if you move a note into a **Novel** +type root folder, its "Importance" setting will be switched with a "Status" setting. See +:ref:`a_ui_tree_status`. The old value will not be overwritten though, and should be restored if +you move it back at some point. Root folders in the project tree cannot be dragged and dropped at all. If you want to reorder them, you can move them up or down with respect to each other from the arrow buttons at the top of the @@ -175,8 +175,8 @@ The Novel Tree View An alternative way to view the project structure is the novel view. You can switch to this view by selecting the :guilabel:`Novel View` button in the sidebar. This view is a simplified version of -the view in the :guilabel:`Outline View`. It is convenient when you want to browse the structure -of the story itself rather than the document files. +the view in the **Outline View**. It is convenient when you want to browse the structure of the +story itself rather than the document files. .. note:: You cannot reorganise the entries in the novel view, or add any new documents, as that would @@ -194,14 +194,14 @@ The Novel Outline View A screenshot of the Novel Outline View. -The project's :guilabel:`Novel Outline View` is available as another view option from the sidebar. -The outline provides an overview of the novel structure, displaying a tree hierarchy of the -elements of the novel, that is, the level 1 to 4 headings representing partitions, chapters, scenes -and sections. +The project's **Novel Outline View** is available as another view option from the sidebar. The +outline provides an overview of the novel structure, displaying a tree hierarchy of the elements of +the novel, that is, the level 1 to 4 headings representing partitions, chapters, scenes and +sections. The document containing the heading can also be displayed as a separate column, as well as the line number where the heading is defined. Double-clicking an entry will open the corresponding document -in the editor and switch to :guilabel:`Project Tree View` mode. +in the editor and switch to **Project Tree View** mode. You can select which novel folder to display from the dropdown menu. You can optionally also choose to show a combination of all novel folders. @@ -217,7 +217,7 @@ the menu button in the toolbar. The order of the columns can also be rearranged a different position. You column settings are saved between sessions on a per-project basis. .. note:: - The :guilabel:`Title` column cannot be disabled or moved. + The **Title** column cannot be disabled or moved. The information viewed in the outline is based on the :term:`project index`. While novelWriter does its best to keep the index up to date when contents change, you can always rebuild it manually by @@ -226,5 +226,5 @@ pressing :kbd:`F9` if something isn't right. The outline view itself can be regenerated by pressing the refresh button. By default, the content is refreshed each time you switch to this view. -The :guilabel:`Synopsis` column of the outline view takes its information from a specially -formatted comment. See :ref:`a_fmt_comm`. +The **Synopsis** column of the outline view takes its information from a specially formatted +comment. See :ref:`a_fmt_comm`. diff --git a/docs/source/usage_shortcuts.rst b/docs/source/usage_shortcuts.rst index af54254b..a8f8163f 100644 --- a/docs/source/usage_shortcuts.rst +++ b/docs/source/usage_shortcuts.rst @@ -20,22 +20,22 @@ Main Window Shortcuts :header: "Shortcut", "Description" ":kbd:`F1`", "Open the online user manual" - ":kbd:`F5`", "Open the :guilabel:`Build Manuscript` tool" - ":kbd:`F6`", "Open the :guilabel:`Writing Statistics` tool" - ":kbd:`F8`", "Toggle :guilabel:`Focus Mode`" + ":kbd:`F5`", "Open the **Build Manuscript** tool" + ":kbd:`F6`", "Open the **Writing Statistics** tool" + ":kbd:`F8`", "Toggle **Focus Mode**" ":kbd:`F9`", "Re-build the project's index" ":kbd:`F11`", "Toggle full screen mode" - ":kbd:`Ctrl+,`", "Open the :guilabel:`Preferences` dialog" + ":kbd:`Ctrl+,`", "Open the **Preferences** dialog" ":kbd:`Ctrl+E`", "Switch focus to the document editor" ":kbd:`Ctrl+T`", "Switch or toggle focus for the project tree or novel view" ":kbd:`Ctrl+Q`", "Exit novelWriter" - ":kbd:`Ctrl+Shift+,`", "Open the :guilabel:`Project Settings` dialog" + ":kbd:`Ctrl+Shift+,`", "Open the **Project Settings** dialog" ":kbd:`Ctrl+Shift+O`", "Open the Welcome dialog to open or create a project" ":kbd:`Ctrl+Shift+S`", "Save the current project" ":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 :guilabel:`Project Details` dialog" + ":kbd:`Shift+F6`", "Open the **Project Details** dialog" .. _a_kb_tree: @@ -53,8 +53,8 @@ Project Tree Shortcuts ":kbd:`Alt+Left`", "Jump to the parent item in the tree" ":kbd:`Alt+Right`", "Jump to the first child item in the project tree" ":kbd:`Ctrl+.`", "Open the context menu on the selected item" - ":kbd:`Ctrl+L`", "Open the :guilabel:`Quick Links` menu" - ":kbd:`Ctrl+N`", "Open the :guilabel:`Create New Item` menu" + ":kbd:`Ctrl+L`", "Open the **Quick Links** menu" + ":kbd:`Ctrl+N`", "Open the **Create New Item** menu" ":kbd:`Ctrl+O`", "Open the selected document in the editor" ":kbd:`Ctrl+R`", "Open the selected document in the viewer" ":kbd:`Ctrl+Up`", "Move selected item one step up in the tree" @@ -75,11 +75,12 @@ Text Search Shortcuts :header: "Shortcut", "Description" ":kbd:`F3`", "Find the next occurrence of the search word" - ":kbd:`Ctrl+F`", "Open the search bar and search for the selected word, if any is selected" + ":kbd:`Ctrl+F`", "Open search and look for the selected word" ":kbd:`Ctrl+G`", "Find the next occurrence of the search word" - ":kbd:`Ctrl+H`", "Open the search tool and populate with the selected word (Mac :kbd:`Cmd+=`)" - ":kbd:`Ctrl+Shift+1`", "Replace selected occurrence of the search word, and move to the next" + ":kbd:`Ctrl+H`", "Open replace and look for the selected word (Mac :kbd:`Cmd+=`)" + ":kbd:`Ctrl+Shift+1`", "Replace selected occurrence, and move to the next" ":kbd:`Ctrl+Shift+G`", "Find the previous occurrence of the search word" + ":kbd:`Ctrl+Shift+F`", "Open project search and look for the selected word" ":kbd:`Shift+F3`", "Find the previous occurrence of the search word" @@ -91,8 +92,8 @@ Text Formatting Shortcuts ":kbd:`Ctrl+'`", "Wrap selected text, or word under cursor, in single quotes" ":kbd:`Ctrl+""`", "Wrap selected text, or word under cursor, in double quotes" - ":kbd:`Ctrl+/`", "Toggle block format as comment for block under cursor, or selected text" - ":kbd:`Ctrl+0`", "Remove block formatting for block under cursor, or selected text" + ":kbd:`Ctrl+/`", "Toggle comment format for block or selected text" + ":kbd:`Ctrl+0`", "Remove format for block or selected text" ":kbd:`Ctrl+1`", "Change block format to heading level 1" ":kbd:`Ctrl+2`", "Change block format to heading level 2" ":kbd:`Ctrl+3`", "Change block format to heading level 3" @@ -102,11 +103,11 @@ Text Formatting Shortcuts ":kbd:`Ctrl+7`", "Change block alignment to right-aligned" ":kbd:`Ctrl+8`", "Add a left margin to the block" ":kbd:`Ctrl+9`", "Add a right margin to the block" - ":kbd:`Ctrl+B`", "Format selected text, or word under cursor, with strong emphasis (bold)" + ":kbd:`Ctrl+B`", "Format selected text, or word under cursor, with bold" ":kbd:`Ctrl+D`", "Format selected text, or word under cursor, with strike through" - ":kbd:`Ctrl+I`", "Format selected text, or word under cursor, with emphasis (italic)" - ":kbd:`Ctrl+Shift+/`", "Remove block formatting for block under cursor, or selected text" - ":kbd:`Ctrl+Shift+D`", "Toggle block format as ignored text for block under cursor, or selected text" + ":kbd:`Ctrl+I`", "Format selected text, or word under cursor, with italic" + ":kbd:`Ctrl+Shift+/`", "Remove format for block or selected text" + ":kbd:`Ctrl+Shift+D`", "Toggle ignored text format for block or selected text" Other Editor Shortcuts diff --git a/docs/source/usage_typography.rst b/docs/source/usage_typography.rst index e96d6185..189e0dd1 100644 --- a/docs/source/usage_typography.rst +++ b/docs/source/usage_typography.rst @@ -11,7 +11,7 @@ Typographical Notes novelWriter has some support for typographical symbols that are not usually easily available in many text editors. This includes for instance the proper unicode quotation marks, dashes, ellipsis, -thin spaces, etc. All these symbols are available from the :guilabel:`Insert` menu, and via +thin spaces, etc. All these symbols are available from the **Insert** menu, and via keyboard shortcuts. See :ref:`a_kb_ins`. This chapter provides some additional information on how novelWriter handles these symbols. @@ -43,22 +43,22 @@ Single and Double Quotes All the different quotation marks listed on the `Quotation Mark`_ Wikipedia page are available, and can be selected as auto-replaced symbols for straight single and double quote key strokes. The -settings can be found in :guilabel:`Preferences`. +settings can be found in **Preferences**. Ordinarily, text wrapped in quotes are highlighted by the editor. This is meant as a convenience for highlighting dialogue between characters. This feature can be disabled in -:guilabel:`Preferences` if this feature isn't wanted. +**Preferences** if this feature isn't wanted. The editor distinguishes between text wrapped in regular straight double quotes and the user-selected double quote symbols. This is to help the writer recognise which parts of the text -are not using the chosen quote symbols. Two convenience functions in the :guilabel:`Format` menu +are not using the chosen quote symbols. Two convenience functions in the **Format** menu can be used to re-format a selected section of text with the correct quote symbols. Single and Double Prime ------------------------ -Both single and double prime symbols are available in the :guilabel:`Insert` menu. These symbols +Both single and double prime symbols are available in the **Insert** menu. These symbols are the correct symbols to use for unit symbols for feet, inches, minutes, and seconds. The usage of these is described in more detail on the Wikipedia Prime_ page. They look very similar to single and double straight quotes, and may be rendered similarly by the font, but they have different @@ -79,7 +79,7 @@ right single quotation marks, depending on the font. There is a Wikipedia articl `Modifier letter apostrophe`_ with more details. .. note:: - On export with the :guilabel:`Build Manuscript` tool, these apostrophes will be replaced + On export with the **Build Manuscript** tool, these apostrophes will be replaced automatically with the corresponding right hand single quote symbol as is generally recommended. Therefore it doesn't really matter if you only use them to correct syntax highlighting. diff --git a/docs/source/usage_writing.rst b/docs/source/usage_writing.rst index d9d6400f..a1b59c67 100644 --- a/docs/source/usage_writing.rst +++ b/docs/source/usage_writing.rst @@ -23,13 +23,14 @@ having it selected. This will open the document in the document editor. The edit Markdown-like syntax for some features, and a novelWriter-specific syntax for others. The syntax format is described in the :ref:`a_fmt` chapter. -The editor has a maximise button (toggles the :guilabel:`Focus Mode`) and a close button in the +The editor has a maximise button, which toggles the **Focus Mode**, and a close button in the top--right corner. On the top--left side you will find a tools button that opens a toolbar with a -few buttons for applying text formatting, and a search button to open the search dialog. +few buttons for applying text formatting, a drop down menu for navigating between headings, and a +search button to open the search dialog. Both the document editor and viewer will show the label of the currently open document in the header at the top of the edit or view panel. Optionally, the full project path to the document can -be shown. This can be set in :guilabel:`Preferences`. +be shown. This can be set in **Preferences**. .. tip:: Clicking on the document title bar will select the document in the project tree and thus reveal @@ -42,10 +43,10 @@ the label and pressing :kbd:`Ctrl+Return`. You can also control-click them with Editor Auto-Completer --------------------- -If you type the character ``@`` on a new line, a menu will appear showing the different available -keywords. The list will shorten as you type. Once a keyword command has been selected or typed, the -editor may suggest further content based on your project content. See :ref:`a_references_completer` -for more details. +If you type the character ``@`` on a new line, a pop-up menu will appear showing the different +available keywords. The list will shorten as you type. Once a keyword command has been selected or +typed, the editor may suggest further options based on your project content. See +:ref:`a_references_completer` for more details. .. versionadded:: 2.2 @@ -60,9 +61,9 @@ Viewing a Document A screenshot of the Document Viewer panel. Any document in the project tree can also be viewed in parallel in a right hand side document -viewer. To view a document, press :kbd:`Ctrl+R`, or select :guilabel:`View Document` in the menu or -context menu. If you have a middle mouse button, middle-clicking on the document will also open it -in the viewer. +viewer. To view a document, press :kbd:`Ctrl+R`, or select **View Document** in the menu or context +menu. If you have a middle mouse button, middle-clicking on the document will also open it in the +viewer. The document viewed does not have to be the same document as the one currently being edited. However, If you *are* viewing the same document, pressing :kbd:`Ctrl+R` again will update the @@ -75,19 +76,20 @@ content of the viewer with the content of the document the reference points to. The document viewer keeps a history of viewed documents, which you can navigate with the arrow buttons in the top--left corner of the viewer. If your mouse has backward and forward navigation buttons, these can be used as well. They work just like the backward and forward features in a -browser. +browser. The left-most button is a dropdown menu for quickly navigation between headings in the +document. -At the bottom of the view panel there is a :guilabel:`References` panel. (If it is hidden, click -the button on the left side of the footer area to reveal it.) This panel contains a References tab -with links to all documents referring back to the one you're currently viewing, if any has been -defined. If you have created root folders and tags for various story elements like characters and -plot points, these will appear as additional tabs in this panel. +At the bottom of the view panel there is a **References** panel. (If it is hidden, click the button +on the left side of the footer area to reveal it.) This panel contains a References tab with links +to all documents referring back to the one you're currently viewing, if any has been defined. If +you have created root folders and tags for various story elements like characters and plot points, +these will appear as additional tabs in this panel. .. note:: - The :guilabel:`References` panel relies on an up-to-date :term:`index` of the - project. The index is maintained automatically. However, if anything is missing, or seems wrong, - the index can always be rebuilt by selecting :guilabel:`Rebuild Index` from the - :guilabel:`Tools` menu, or by pressing :kbd:`F9`. + The **References** panel relies on an up-to-date :term:`index` of the project. + The index is maintained automatically. However, if anything is missing, or seems wrong, the + index can always be rebuilt by selecting **Rebuild Index** from the **Tools** menu, or by + pressing :kbd:`F9`. .. versionadded:: 2.2 The reference panel was redesigned and the additional tabs added. @@ -131,8 +133,7 @@ Auto-Replace as You Type ======================== A few auto-replace features are supported by the editor. You can control every aspect of the -auto-replace feature from :guilabel:`Preferences`. You can also disable this feature entirely if -you wish. +auto-replace feature from **Preferences**. You can also disable this feature entirely if you wish. .. tip:: If you don't like auto-replacement, all symbols inserted by this feature are also available in diff --git a/i18n/nw_base.ts b/i18n/nw_base.ts index e458fa8b..60b73d72 100644 --- a/i18n/nw_base.ts +++ b/i18n/nw_base.ts @@ -50,7 +50,7 @@ - Hard Scene Format + Alt. Scene Format @@ -791,22 +791,22 @@ GuiDocEditFooter - + Line: {0} ({1}) - + Words: {0} ({1}) - + Words: {0} selected - + Status @@ -814,27 +814,27 @@ GuiDocEditHeader - + Toggle Tool Bar - + Outline - + Search - + Toggle Focus Mode - + Close @@ -842,62 +842,62 @@ GuiDocEditSearch - + Search for - + Replace with - + Search - + Case Sensitive - + Whole Words Only - + RegEx Mode - + Loop Search - + Search Next File - + Preserve Case - + Close Search - + Find in current document - + Find and replace in current document @@ -920,107 +920,107 @@ - + Saved Document: {0} - + Spell checking requires the package PyEnchant. It does not appear to be installed. - + Spell check complete - + Document Details - + Created: {0} - + Updated: {0} - + File Location: {0} - + Set as Document Name - + Follow Tag - + Create Note for Tag - + Cut - + Copy - + Paste - + Select All - + Select Word - + Select Paragraph - + Spelling Suggestion(s) - + No Suggestions - + Add Word to Dictionary - + Please select some text before calling replace quotes. - + Do you want to create a new project note for the tag '{0}'? @@ -1104,52 +1104,52 @@ GuiDocToolBar - + Markdown Bold - + Markdown Italic - + Markdown Strikethrough - + Shortcode Bold - + Shortcode Italic - + Shortcode Strikethrough - + Shortcode Underline - + Shortcode Highlight - + Shortcode Superscript - + Shortcode Subscript @@ -1898,7 +1898,7 @@ - Hard Scene + Alternative Scene diff --git a/i18n/nw_en_US.ts b/i18n/nw_en_US.ts index a15a9cc7..b1adeacf 100644 --- a/i18n/nw_en_US.ts +++ b/i18n/nw_en_US.ts @@ -4,232 +4,232 @@ Builds - + Document Filters Document Filters - + Novel Documents Novel Documents - + Project Notes Project Notes - + Inactive Documents Inactive Documents - + Headings Headings - + Partition Format Partition Format - + Chapter Format Chapter Format - + Unnumbered Format Unnumbered Format - + Scene Format Scene Format - - Hard Scene Format - Hard Scene Format + + Alt. Scene Format + Alt. Scene Format - + Section Format Section Format - + Text Content Text Content - + Include Synopsis Include Synopsis - + Include Comments Include Comments - + Include Keywords Include Keywords - + Include Body Text Include Body Text - + Ignore These Keywords Ignore These Keywords - + Insert Content Insert Content - + Add Titles for Notes Add Titles for Notes - + Text Format Text Format - + Font Family Font Family - + Font Size Font Size - + Line Height Line Height - + Text Options Text Options - + Justify Text Margins Justify Text Margins - + Replace Unicode Characters Replace Unicode Characters - + Replace Tabs with Spaces Replace Tabs with Spaces - + Page Layout Page Layout - + Unit Unit - + Page Size Page Size - + Page Width Page Width - + Page Height Page Height - + Top Margin Top Margin - + Bottom Margin Bottom Margin - + Left Margin Left Margin - + Right Margin Right Margin - + Open Document (.odt) Open Document (.odt) - + Add Highlight Colours Add Highlight Colors - + Page Header Page Header - + Page Counter Offset Page Counter Offset - + First Line Indent First Line Indent - + Markdown (.md) Markdown (.md) - + Preserve Hard Line Breaks Preserve Hard Line Breaks - + HTML (.html) HTML (.html) - + Add CSS Styles Add CSS Styles - + Preserve Tab Characters Preserve Tab Characters @@ -237,72 +237,72 @@ Common - + in the future in the future - + just now just now - + a minute ago a minute ago - + {0} minutes ago {0} minutes ago - + an hour ago an hour ago - + {0} hours ago {0} hours ago - + a day ago a day ago - + {0} days ago {0} days ago - + a week ago a week ago - + {0} weeks ago {0} weeks ago - + a month ago a month ago - + {0} months ago {0} months ago - + a year ago a year ago - + {0} years ago {0} years ago @@ -310,375 +310,375 @@ Constant - - - + + + None None - + Novel Novel - - + + Plot Plot - - + + Characters Characters - - + + Locations Locations - - + + Timeline Timeline - - + + Objects Objects - - + + Entities Entities - - - + + + Custom Custom - + Archive Archive - + Templates Templates - + Trash Trash - - + + Novel Document Novel Document - - + + Project Note Project Note - + Root Folder Root Folder - + Folder Folder - + Novel Title Page Novel Title Page - + Novel Chapter Novel Chapter - + Novel Scene Novel Scene - + Novel Section Novel Section - + Tag Tag - + Point of View Point of View - - + + Focus Focus - + Title Title - + Level Level - + Document Document - + Line Line - + Chars Chars - + Words Words - + Pars Pars - + POV POV - + Synopsis Synopsis - + Open Document (.odt) Open Document (.odt) - + Flat Open Document (.fodt) Flat Open Document (.fodt) - + novelWriter HTML (.html) novelWriter HTML (.html) - + novelWriter Markup (.txt) novelWriter Markup (.txt) - + Standard Markdown (.md) Standard Markdown (.md) - + Extended Markdown (.md) Extended Markdown (.md) - + JSON + novelWriter HTML (.json) JSON + novelWriter HTML (.json) - + JSON + novelWriter Markup (.json) JSON + novelWriter Markup (.json) - + Text files Text files - + Markdown files Markdown files - + novelWriter files novelWriter files - + CSV files CSV files - + All files All files - + Millimetres Millimeters - + Centimetres Centimeters - + Inches Inches - + A4 A4 - + A5 A5 - + A6 A6 - + US Legal US Legal - + US Letter US Letter - + Straight single quotation mark Straight single quotation mark - + Straight double quotation mark Straight double quotation mark - + Left single quotation mark Left single quotation mark - + Right single quotation mark Right single quotation mark - + Single low-9 quotation mark Single low-9 quotation mark - + Single high-reversed-9 quotation mark Single high-reversed-9 quotation mark - + Left double quotation mark Left double quotation mark - + Right double quotation mark Right double quotation mark - + Double low-9 quotation mark Double low-9 quotation mark - + Double high-reversed-9 quotation mark Double high-reversed-9 quotation mark - + Double low-reversed-9 quotation mark Double low-reversed-9 quotation mark - + Single left-pointing angle quotation mark Single left-pointing angle quotation mark - + Single right-pointing angle quotation mark Single right-pointing angle quotation mark - + Double left-pointing angle quotation mark Double left-pointing angle quotation mark - + Double right-pointing angle quotation mark Double right-pointing angle quotation mark - + Left corner bracket Left corner bracket - + Right corner bracket Right corner bracket - + Left white corner bracket Left white corner bracket - + Right white corner bracket Right white corner bracket @@ -686,17 +686,17 @@ GuiAbout - + About novelWriter About novelWriter - + This application is licenced under {0} This application is licensed under {0} - + Credits Credits @@ -704,38 +704,38 @@ GuiBuildSettings - - + + Manuscript Build Settings Manuscript Build Settings - + Name Name - + Selection Selection - + Headings Headings - + Content Content - + Format Format - + Output Output @@ -743,47 +743,47 @@ GuiDictionaries - + Add Dictionaries Add Dictionaries - + Download a dictionary from one of the links, and add it below. Download a dictionary from one of the links, and add it below. - + Add Dictionary Add Dictionary - + Dictionary install location Dictionary install location - + Additional dictionaries found: {0} Additional dictionaries found: {0} - + Free or Libre Office extension Free or Libre Office extension - + Browse Files Browse Files - + Could not process dictionary file Could not process dictionary file - + Added: {0} [{1}B] Added: {0} [{1}B] @@ -791,22 +791,22 @@ GuiDocEditFooter - + Line: {0} ({1}) Line: {0} ({1}) - + Words: {0} ({1}) Words: {0} ({1}) - + Words: {0} selected Words: {0} selected - + Status Status @@ -814,27 +814,27 @@ GuiDocEditHeader - + Toggle Tool Bar Toggle Tool Bar - + Outline Outline - + Search Search - + Toggle Focus Mode Toggle Focus Mode - + Close Close @@ -842,62 +842,62 @@ GuiDocEditSearch - + Search for Search for - + Replace with Replace with - + Search Search - + Case Sensitive Case Sensitive - + Whole Words Only Whole Words Only - + RegEx Mode RegEx Mode - + Loop Search Loop Search - + Search Next File Search Next File - + Preserve Case Preserve Case - + Close Search Close Search - + Find in current document Find in current document - + Find and replace in current document Find and replace in current document @@ -905,122 +905,122 @@ GuiDocEditor - + Opened Document: {0} Opened Document: {0} - + This document has been changed outside of novelWriter while it was open. Overwrite the file on disk? This document has been changed outside of novelWriter while it was open. Overwrite the file on disk? - + Could not save document. Could not save document. - + Saved Document: {0} Saved Document: {0} - + Spell checking requires the package PyEnchant. It does not appear to be installed. Spell checking requires the package PyEnchant. It does not appear to be installed. - + Spell check complete Spell check complete - + Document Details Document Details - + Created: {0} Created: {0} - + Updated: {0} Updated: {0} - + File Location: {0} File Location: {0} - + Set as Document Name Set as Document Name - + Follow Tag Follow Tag - + Create Note for Tag Create Note for Tag - + Cut Cut - + Copy Copy - + Paste Paste - + Select All Select All - + Select Word Select Word - + Select Paragraph Select Paragraph - + Spelling Suggestion(s) Spelling Suggestion(s) - + No Suggestions No Suggestions - + Add Word to Dictionary Add Word to Dictionary - + Please select some text before calling replace quotes. Please select some text before calling replace quotes. - + Do you want to create a new project note for the tag '{0}'? Do you want to create a new project note for the tag '{0}'? @@ -1028,22 +1028,22 @@ GuiDocMerge - + Merge Documents Merge Documents - + Documents to Merge Documents to Merge - + Drag and drop items to change the order, or uncheck to exclude. Drag and drop items to change the order, or uncheck to exclude. - + Move merged items to Trash Move merged items to Trash @@ -1051,52 +1051,52 @@ GuiDocSplit - + Split Document Split Document - + Document Headings Document Headings - + Select the maximum level to split into files. Select the maximum level to split into files. - + Split on Heading Level 1 (Partition) Split on Heading Level 1 (Partition) - + Split up to Heading Level 2 (Chapter) Split up to Heading Level 2 (Chapter) - + Split up to Heading Level 3 (Scene) Split up to Heading Level 3 (Scene) - + Split up to Heading Level 4 (Section) Split up to Heading Level 4 (Section) - + Split into a new folder Split into a new folder - + Create document hierarchy Create document hierarchy - + Move split document to Trash Move split document to Trash @@ -1104,52 +1104,52 @@ GuiDocToolBar - + Markdown Bold Markdown Bold - + Markdown Italic Markdown Italic - + Markdown Strikethrough Markdown Strikethrough - + Shortcode Bold Shortcode Bold - + Shortcode Italic Shortcode Italic - + Shortcode Strikethrough Shortcode Strikethrough - + Shortcode Underline Shortcode Underline - + Shortcode Highlight Shortcode Highlight - + Shortcode Superscript Shortcode Superscript - + Shortcode Subscript Shortcode Subscript @@ -1157,27 +1157,27 @@ GuiDocViewFooter - + Show/Hide Viewer Panel Show/Hide Viewer Panel - + Comments Comments - + Show Comments Show Comments - + Synopsis Synopsis - + Show Synopsis Comments Show Synopsis Comments @@ -1185,27 +1185,27 @@ GuiDocViewHeader - + Outline Outline - + Go Backward Go Backward - + Go Forward Go Forward - + Reload Reload - + Close Close @@ -1213,27 +1213,27 @@ GuiDocViewer - + An error occurred while generating the preview. An error occurred while generating the preview. - + Copy Copy - + Select All Select All - + Select Word Select Word - + Select Paragraph Select Paragraph @@ -1241,12 +1241,12 @@ GuiDocViewerPanel - + Hide Inactive Tags Hide Inactive Tags - + References References @@ -1254,12 +1254,12 @@ GuiEditLabel - + Item Label Item Label - + Label Label @@ -1267,37 +1267,37 @@ GuiItemDetails - + Label Label - + Status Status - + Class Class - + Usage Usage - + Characters Characters - + Words Words - + Paragraphs Paragraphs @@ -1305,27 +1305,27 @@ GuiLipsum - + Insert Placeholder Text Insert Placeholder Text - + Insert Lorem Ipsum Text Insert Lorem Ipsum Text - + Number of paragraphs Number of paragraphs - + Randomise order Randomize order - + Insert Insert @@ -1333,103 +1333,103 @@ GuiMain - + novelWriter is ready ... novelWriter is ready ... - + You are now running novelWriter version {0}. You are now running novelWriter version {0}. - + Please check the {0}release notes{1} for further details. Please check the {0}release notes{1} for further details. - + Close the current project? Close the current project? - - + + Changes are saved automatically. Changes are saved automatically. - + Backup the current project? Backup the current project? - + The project is already open by another instance of novelWriter, and is therefore locked. Override lock and continue anyway? The project is already open by another instance of novelWriter, and is therefore locked. Override lock and continue anyway? - + Note: If the program or the computer previously crashed, the lock can safely be overridden. However, overriding it is not recommended if the project is open in another instance of novelWriter. Doing so may corrupt the project. Note: If the program or the computer previously crashed, the lock can safely be overridden. However, overriding it is not recommended if the project is open in another instance of novelWriter. Doing so may corrupt the project. - + The project was locked by the computer '{0}' ({1} {2}), last active on {3}. The project was locked by the computer '{0}' ({1} {2}), last active on {3}. - + The project index is outdated or broken. Rebuilding index. The project index is outdated or broken. Rebuilding index. - + Import File Import File - + Could not read file. The file must be an existing text file. Could not read file. The file must be an existing text file. - + Please open a document to import the text file into. Please open a document to import the text file into. - + Importing the file will overwrite the current content of the document. Do you want to proceed? Importing the file will overwrite the current content of the document. Do you want to proceed? - + Indexing completed in {0} ms Indexing completed in {0} ms - + The project index has been successfully rebuilt. The project index has been successfully rebuilt. - + Could not initialise the dialog. Could not initialize the dialog. - + Do you want to exit novelWriter? Do you want to exit novelWriter? - + Some changes will not be applied until novelWriter has been restarted. Some changes will not be applied until novelWriter has been restarted. - + Could not find the reference for tag '{0}'. It either doesn't exist, or the index is out of date. The index can be updated from the Tools menu, or by pressing {1}. Could not find the reference for tag '{0}'. It either doesn't exist, or the index is out of date. The index can be updated from the Tools menu, or by pressing {1}. @@ -1437,657 +1437,657 @@ GuiMainMenu - + &Project &Project - + Create or Open Project Create or Open Project - + Save Project Save Project - + Close Project Close Project - + Project Settings Project Settings - + Novel Details Novel Details - + Rename Item Rename Item - + Delete Item Delete Item - + Empty Trash Empty Trash - + Exit Exit - + &Document &Document - + Open Document Open Document - + Save Document Save Document - + Close Document Close Document - + View Document View Document - + Close Document View Close Document View - + Show File Details Show File Details - + Import Text from File Import Text from File - + &Edit &Edit - + Undo Undo - + Redo Redo - + Cut Cut - + Copy Copy - + Paste Paste - + Select All Select All - + Select Paragraph Select Paragraph - + &View &View - + Go to Project Tree Go to Project Tree - + Go to Document Editor Go to Document Editor - + Go to Outline Go to Outline - + Navigate Backward Navigate Backward - + Navigate Forward Navigate Forward - + Focus Mode Focus Mode - + Full Screen Mode Full Screen Mode - + &Insert &Insert - + Dashes Dashes - + Short Dash Short Dash - + Long Dash Long Dash - + Horizontal Bar Horizontal Bar - + Figure Dash Figure Dash - + Quote Marks Quote Marks - + Left Single Quote Left Single Quote - + Right Single Quote Right Single Quote - + Left Double Quote Left Double Quote - + Right Double Quote Right Double Quote - + Alternative Apostrophe Alternative Apostrophe - + General Punctuation General Punctuation - + Ellipsis Ellipsis - + Prime Prime - + Double Prime Double Prime - + White Spaces White Spaces - + Non-Breaking Space Non-Breaking Space - + Thin Space Thin Space - + Thin Non-Breaking Space Thin Non-Breaking Space - + Other Symbols Other Symbols - + List Bullet List Bullet - + Hyphen Bullet Hyphen Bullet - + Flower Mark Flower Mark - + Per Mille Per Mille - + Degree Symbol Degree Symbol - + Minus Sign Minus Sign - + Times Sign Times Sign - + Division Sign Division Sign - + Tags and References Tags and References - + Special Comments Special Comments - + Synopsis Comment Synopsis Comment - + Short Description Comment Short Description Comment - + Page Break and Space Page Break and Space - + Page Break Page Break - + Vertical Space (Single) Vertical Space (Single) - + Vertical Space (Multi) Vertical Space (Multi) - + Placeholder Text Placeholder Text - + &Format &Format - + Bold Bold - + Italic Italic - + Strikethrough Strikethrough - + Wrap Double Quotes Wrap Double Quotes - + Wrap Single Quotes Wrap Single Quotes - + More Formats ... More Formats ... - + Bold (Shortcode) Bold (Shortcode) - + Italics (Shortcode) Italics (Shortcode) - + Strikethrough (Shortcode) Strikethrough (Shortcode) - + Underline Underline - + Highlight Highlight - + Superscript Superscript - + Subscript Subscript - + Heading 1 (Partition) Heading 1 (Partition) - + Heading 2 (Chapter) Heading 2 (Chapter) - + Heading 3 (Scene) Heading 3 (Scene) - + Heading 4 (Section) Heading 4 (Section) - + Novel Title Novel Title - + Unnumbered Chapter Unnumbered Chapter - - Hard Scene - Hard Scene + + Alternative Scene + Alternative Scene - + Align Left Align Left - + Align Centre Align Center - + Align Right Align Right - + Indent Left Indent Left - + Indent Right Indent Right - + Toggle Comment Toggle Comment - + Toggle Ignore Text Toggle Ignore Text - + Remove Block Format Remove Block Format - + Replace Straight Single Quotes Replace Straight Single Quotes - + Replace Straight Double Quotes Replace Straight Double Quotes - + Remove In-Paragraph Breaks Remove In-Paragraph Breaks - + &Search &Search - + Find Find - + Replace Replace - + Find Next Find Next - + Find Previous Find Previous - + Replace Next Replace Next - + Find in Project Find in Project - + &Tools &Tools - + Check Spelling Check Spelling - + Spell Check Language Spell Check Language - + Default Default - + Re-Run Spell Check Re-Run Spell Check - + Project Word List Project Word List - + Add Dictionaries Add Dictionaries - + Rebuild Index Rebuild Index - + Backup Project Backup Project - + Build Manuscript Build Manuscript - + Writing Statistics Writing Statistics - + Preferences Preferences - + &Help &Help - + About novelWriter About novelWriter - + About Qt5 About Qt5 - + User Manual (Online) User Manual (Online) - + User Manual (PDF) User Manual (PDF) - + Report an Issue (GitHub) Report an Issue (GitHub) - + Ask a Question (GitHub) Ask a Question (GitHub) - + The novelWriter Website The novelWriter Website @@ -2095,38 +2095,38 @@ GuiMainStatus - - + + None None - + Editor Editor - + Project Project - + Session Time Session Time - + Words: {0} ({1}) Words: {0} ({1}) - + Project word count (session change) Project word count (session change) - + Novel word count (session change) Novel word count (session change) @@ -2134,63 +2134,63 @@ GuiManuscript - + Build Manuscript Build Manuscript - + Add New Build Add New Build - + Delete Selected Build Delete Selected Build - + Edit Selected Build Edit Selected Build - + Builds Builds - + Details Details - + Outline Outline - + Preview Preview - + Print Print - + Build Build - + Close Close - - + + My Manuscript My Manuscript @@ -2198,57 +2198,57 @@ GuiManuscriptBuild - + Build Manuscript Build Manuscript - + Output Format Output Format - + Table of Contents Table of Contents - + Path Path - + File Name File Name - + Reset file name to default Reset file name to default - + Open Folder Open Folder - + &Build &Build - + Select Folder Select Folder - + Output folder does not exist. Output folder does not exist. - + The file already exists. Do you want to overwrite it? The file already exists. Do you want to overwrite it? @@ -2256,18 +2256,18 @@ GuiNovelDetails - - + + Novel Details Novel Details - + Overview Overview - + Contents Contents @@ -2275,58 +2275,58 @@ GuiNovelToolBar - + Outline of {0} Outline of {0} - + Novel Root Novel Root - + Refresh Refresh - + Last Column Last Column - + Hidden Hidden - + Point of View Character Point of View Character - + Focus Character Focus Character - + Novel Plot Novel Plot - - + + Column Size Column Size - + More Options More Options - + Maximum column size in % Maximum column size in % @@ -2334,7 +2334,7 @@ GuiNovelTree - + No meta data No meta data @@ -2342,64 +2342,64 @@ GuiOutlineDetails - - - + + + Title Title - + Chapter Chapter - + Scene Scene - + Section Section - + Document Document - + Status Status - + Characters Characters - + Words Words - + Paragraphs Paragraphs - + Synopsis Synopsis - + Title Details Title Details - + Reference Tags Reference Tags @@ -2407,7 +2407,7 @@ GuiOutlineHeaderMenu - + Select Columns Select Columns @@ -2415,17 +2415,17 @@ GuiOutlineToolBar - + Outline of Outline of - + Refresh Refresh - + Export CSV Export CSV @@ -2433,7 +2433,7 @@ GuiOutlineTree - + Save Outline As Save Outline As @@ -2441,553 +2441,553 @@ GuiPreferences - - + + Preferences Preferences - + Search Search - + General General - + Appearance Appearance - + Display language Display language - - - + + + Requires restart to take effect. Requires restart to take effect. - + Colour theme Color theme - + General colour theme and icons. General color theme and icons. - + Application font family Application font family - + Application font size Application font size - - + + pt pt - + Hide vertical scroll bars in main windows Hide vertical scroll bars in main windows - - + + Scrolling available with mouse wheel and keys only. Scrolling available with mouse wheel and keys only. - + Hide horizontal scroll bars in main windows Hide horizontal scroll bars in main windows - + Document Style Document Style - + Document colour theme Document color theme - + Colour theme for the editor and viewer. Color theme for the editor and viewer. - + Document font family Document font family - - - - + + + + Applies to both document editor and viewer. Applies to both document editor and viewer. - + Document font size Document font size - + Emphasise partition and chapter labels Emphasize partition and chapter labels - + Makes them stand out in the project tree. Makes them stand out in the project tree. - + Show full path in document header Show full path in document header - + Add the parent folder names to the header. Add the parent folder names to the header. - + Include project notes in status bar word count Include project notes in status bar word count - + Auto Save Auto Save - + Save document interval Save document interval - + How often the document is automatically saved. How often the document is automatically saved. - - + + seconds seconds - + Save project interval Save project interval - + How often the project is automatically saved. How often the project is automatically saved. - + Project Backup Project Backup - + Browse Browse - + Backup storage location Backup storage location - - + + Path: {0} Path: {0} - + Run backup when the project is closed Run backup when the project is closed - + Can be overridden for individual projects in Project Settings. Can be overridden for individual projects in Project Settings. - + Ask before running backup Ask before running backup - + If off, backups will run in the background. If off, backups will run in the background. - + Session Timer Session Timer - + Pause the session timer when not writing Pause the session timer when not writing - + Also pauses when the application window does not have focus. Also pauses when the application window does not have focus. - + Editor inactive time before pausing timer Editor inactive time before pausing timer - + User activity includes typing and changing the content. User activity includes typing and changing the content. - + minutes minutes - + Writing Writing - + Text Flow Text Flow - + Maximum text width in "Normal Mode" Maximum text width in "Normal Mode" - + Set to 0 to disable this feature. Set to 0 to disable this feature. - - - - + + + + px px - + Maximum text width in "Focus Mode" Maximum text width in "Focus Mode" - + The maximum width cannot be disabled. The maximum width cannot be disabled. - + Hide document footer in "Focus Mode" Hide document footer in "Focus Mode" - + Hide the information bar in the document editor. Hide the information bar in the document editor. - + Justify the text margins Justify the text margins - + Minimum text margin Minimum text margin - + Tab width Tab width - + The width of a tab key press in the editor and viewer. The width of a tab key press in the editor and viewer. - + Text Editing Text Editing - + Spell check language Spell check language - + Available languages are determined by your system. Available languages are determined by your system. - + Auto-select word under cursor Auto-select word under cursor - + Apply formatting to word under cursor if no selection is made. Apply formatting to word under cursor if no selection is made. - + Show tabs and spaces Show tabs and spaces - + Show line endings Show line endings - + Editor Scrolling Editor Scrolling - + Scroll past end of the document Scroll past end of the document - + Also centres the cursor when scrolling. Also centers the cursor when scrolling. - + Typewriter style scrolling when you type Typewriter style scrolling when you type - + Keeps the cursor at a fixed vertical position. Keeps the cursor at a fixed vertical position. - + Minimum position for Typewriter scrolling Minimum position for Typewriter scrolling - + Percentage of the editor height from the top. Percentage of the editor height from the top. - + Text Highlighting Text Highlighting - + Highlight text wrapped in quotes Highlight text wrapped in quotes - - - + + + Applies to the document editor only. Applies to the document editor only. - + Allow open-ended single quotes Allow open-ended single quotes - + Highlight single-quoted line with no closing quote. Highlight single-quoted line with no closing quote. - + Allow open-ended double quotes Allow open-ended double quotes - + Highlight double-quoted line with no closing quote. Highlight double-quoted line with no closing quote. - + Add highlight colour to emphasised text Add highlight color to emphasised text - + Highlight multiple or trailing spaces Highlight multiple or trailing spaces - + Text Automation Text Automation - + Auto-replace text as you type Auto-replace text as you type - + Allow the editor to replace symbols as you type. Allow the editor to replace symbols as you type. - + Auto-replace single quotes Auto-replace single quotes - - + + Try to guess which is an opening or a closing quote. Try to guess which is an opening or a closing quote. - + Auto-replace double quotes Auto-replace double quotes - + Auto-replace dashes Auto-replace dashes - + Double and triple hyphens become short and long dashes. Double and triple hyphens become short and long dashes. - + Auto-replace dots Auto-replace dots - + Three consecutive dots become ellipsis. Three consecutive dots become ellipsis. - + Insert non-breaking space before Insert non-breaking space before - + Automatically add space before any of these symbols. Automatically add space before any of these symbols. - + Insert non-breaking space after Insert non-breaking space after - + Automatically add space after any of these symbols. Automatically add space after any of these symbols. - + Use thin space instead Use thin space instead - + Inserts a thin space instead of a regular space. Inserts a thin space instead of a regular space. - + Quotation Style Quotation Style - + Single quote open style Single quote open style - + The symbol to use for a leading single quote. The symbol to use for a leading single quote. - + Single quote close style Single quote close style - + The symbol to use for a trailing single quote. The symbol to use for a trailing single quote. - + Double quote open style Double quote open style - + The symbol to use for a leading double quote. The symbol to use for a leading double quote. - + Double quote close style Double quote close style - + The symbol to use for a trailing double quote. The symbol to use for a trailing double quote. - + Backup Directory Backup Directory @@ -2995,27 +2995,27 @@ GuiProjectSearch - + Project Search Project Search - + Case Sensitive Case Sensitive - + Whole Words Only Whole Words Only - + RegEx Mode RegEx Mode - + Search for Search for @@ -3023,28 +3023,28 @@ GuiProjectSettings - - + + Project Settings Project Settings - + Settings Settings - + Status Status - + Importance Importance - + Auto-Replace Auto-Replace @@ -3052,47 +3052,47 @@ GuiProjectToolBar - + Project Content Project Content - + Quick Links Quick Links - + Move Up Move Up - + Move Down Move Down - + Add Item Add Item - + Expand All Expand All - + Collapse All Collapse All - + Empty Trash Empty Trash - + More Options More Options @@ -3100,118 +3100,118 @@ GuiProjectTree - + Active Active - + Inactive Inactive - + Permanently delete {0} file(s) from Trash? Permanently delete {0} file(s) from Trash? - + Did not find anywhere to add the file or folder! Did not find anywhere to add the file or folder! - + Cannot add new files or folders to the Trash folder. Cannot add new files or folders to the Trash folder. - + New Note New Note - + New Chapter New Chapter - + New Scene New Scene - + New Document New Document - + New Folder New Folder - + There is currently no Trash folder in this project. There is currently no Trash folder in this project. - + The Trash folder is already empty. The Trash folder is already empty. - + Move '{0}' to Trash? Move '{0}' to Trash? - + Root folders can only be deleted when they are empty. Root folders can only be deleted when they are empty. - + Permanently delete '{0}'? Permanently delete '{0}'? - + Drag and drop is only allowed for single items, non-root items, or multiple items with the same parent. Drag and drop is only allowed for single items, non-root items, or multiple items with the same parent. - + No documents selected for merging. No documents selected for merging. - + Merged Merged - - + + Could not write document content. Could not write document content. - + Do you want to duplicate this document? Do you want to duplicate this document? - + Do you want to duplicate this item and all child items? Do you want to duplicate this item and all child items? - + Could not duplicate all items. Could not duplicate all items. - + There is nowhere to add item with name '{0}'. There is nowhere to add item with name '{0}'. @@ -3219,42 +3219,42 @@ GuiSideBar - + Project Tree View Project Tree View - + Novel Tree View Novel Tree View - + Project Search Project Search - + Novel Outline View Novel Outline View - + Build Manuscript Build Manuscript - + Novel Details Novel Details - + Writing Statistics Writing Statistics - + Settings Settings @@ -3262,37 +3262,37 @@ GuiWelcome - + Welcome Welcome - + List List - + New New - + Browse Browse - + Cancel Cancel - + Create Create - + Open Open @@ -3300,33 +3300,33 @@ GuiWordList - - + + Project Word List Project Word List - + Import words from text file Import words from text file - + Export words to text file Export words to text file - + Note: The import file must be a plain text file with UTF-8 or ASCII encoding. Note: The import file must be a plain text file with UTF-8 or ASCII encoding. - + Import File Import File - + Export File Export File @@ -3334,147 +3334,147 @@ GuiWritingStats - + Writing Statistics Writing Statistics - + Session Start Session Start - + Length Length - + Idle Idle - + Words Words - + Histogram Histogram - + Sum Totals Sum Totals - + Total Time: Total Time: - + Idle Time: Idle Time: - + Filtered Time: Filtered Time: - + Novel Word Count: Novel Word Count: - + Notes Word Count: Notes Word Count: - + Total Word Count: Total Word Count: - + Filters Filters - + Count novel files Count novel files - + Count note files Count note files - + Hide zero word count Hide zero word count - + Hide negative word count Hide negative word count - + Group entries by day Group entries by day - + Show idle time Show idle time - + Word count cap for the histogram Word count cap for the histogram - + Save As Save As - + JSON Data File (.json) JSON Data File (.json) - + CSV Data File (.csv) CSV Data File (.csv) - + JSON Data File JSON Data File - + CSV Data File CSV Data File - + Save Data As Save Data As - + {0} file successfully written to: {0} file successfully written to: - + Failed to write {0} file. Failed to write {0} file. @@ -3482,153 +3482,153 @@ NWProject - + Could not delete document file. Could not delete document file. - + Not a known project file format. Not a known project file format. - + Project file not found. Project file not found. - + Failed to open project. Failed to open project. - + Unknown Unknown - + Project file does not appear to be a novelWriterXML file. Project file does not appear to be a novelWriterXML file. - + Unknown or unsupported novelWriter project file format. The project cannot be opened by this version of novelWriter. The file was saved with novelWriter version {0}. Unknown or unsupported novelWriter project file format. The project cannot be opened by this version of novelWriter. The file was saved with novelWriter version {0}. - + Failed to parse project xml. Failed to parse project xml. - + The file format of your project is about to be updated. If you proceed, older versions of novelWriter will no longer be able to open this project. Continue? The file format of your project is about to be updated. If you proceed, older versions of novelWriter will no longer be able to open this project. Continue? - + This project was saved by a newer version of novelWriter, version {0}. This is version {1}. If you continue to open the project, some attributes and settings may not be preserved, but the overall project should be fine. Continue opening the project? This project was saved by a newer version of novelWriter, version {0}. This is version {1}. If you continue to open the project, some attributes and settings may not be preserved, but the overall project should be fine. Continue opening the project? - + Recovered Recovered - + Found {0} orphaned file(s) in the project. {1} file(s) were recovered. Found {0} orphaned file(s) in the project. {1} file(s) were recovered. - + Opened Project: {0} Opened Project: {0} - + There is no project open. There is no project open. - + Failed to save project. Failed to save project. - + Saved Project: {0} Saved Project: {0} - + Backing up project ... Backing up project ... - + Cannot backup project because no project name is set. Please set a Project Name in Project Settings. Cannot backup project because no project name is set. Please set a Project Name in Project Settings. - + Could not create backup folder. Could not create backup folder. - + Created a backup of your project of size {0}B. Created a backup of your project of size {0}B. - + Path: {0} Path: {0} - + Could not write backup archive. Could not write backup archive. - + Project backed up to '{0}' Project backed up to '{0}' - - + + New New - + Note Note - + Draft Draft - + Finished Finished - + Minor Minor - + Major Major - + Main Main @@ -3636,7 +3636,7 @@ NovelSelector - + All Novel Folders All Novel Folders @@ -3644,89 +3644,89 @@ ProjectBuilder - + The target folder is not empty. Please choose another folder. The target folder is not empty. Please choose another folder. - + An error occurred while trying to create the project. An error occurred while trying to create the project. - + New Project New Project - + Title Page Title Page - + By By - + Summary of the chapter. Summary of the chapter. - + Summary of the scene. Summary of the scene. - + A short description. A short description. - + Chapter {0} Chapter {0} - - + + Scene {0} Scene {0} - + Main Plot Main Plot - + Protagonist Protagonist - + Main Location Main Location - - + + The target folder already exists. Please choose another folder. The target folder already exists. Please choose another folder. - + Could not copy project files. Could not copy project files. - + Failed to create a new example project. Failed to create a new example project. - + Failed to create a new example project. Could not find the necessary files. They seem to be missing from this installation. Failed to create a new example project. Could not find the necessary files. They seem to be missing from this installation. @@ -3734,7 +3734,7 @@ QDialogButtonBox - + OK OK @@ -3742,27 +3742,27 @@ QGnomeTheme - + &OK &OK - + &Save &Save - + &Cancel &Cancel - + &Close &Close - + Close without Saving Close without Saving @@ -3770,92 +3770,92 @@ QPlatformTheme - + OK OK - + Save Save - + Save All Save All - + Open Open - + &Yes &Yes - + Yes to &All Yes to &All - + &No &No - + N&o to All N&o to All - + Abort Abort - + Retry Retry - + Ignore Ignore - + Close Close - + Cancel Cancel - + Discard Discard - + Help Help - + Apply Apply - + Reset Reset - + Restore Defaults Restore Defaults @@ -3863,58 +3863,58 @@ QWizard - + Go Back Go Back - + < &Back < &Back - + Continue Continue - + &Next &Next - + &Next > &Next > - + Commit Commit - + Done Done - + &Finish &Finish - - + + Cancel Cancel - + Help Help - + &Help &Help @@ -3922,17 +3922,17 @@ SharedData - + novelWriter Project File or Zip File novelWriter Project File or Zip File - + novelWriter Project File novelWriter Project File - + Open Project Open Project @@ -3940,42 +3940,42 @@ VersionInfoWidget - + Latest Version: {0} Latest Version: {0} - + Checking ... Checking ... - + Download from {0} Download from {0} - + Version Version - + Released on Released on - + Release Notes Release Notes - + Check Now Check Now - + Failed Failed @@ -3983,57 +3983,57 @@ _ContentsPage - + Table of Contents Table of Contents - + Title Title - + Words Words - + Pages Pages - + Page Page - + Progress Progress - + Words per page Words per page - + First page offset First page offset - + Chapters on odd pages Chapters on odd pages - + Untitled Untitled - + END END @@ -4041,32 +4041,32 @@ _DetailsWidget - + Setting Setting - + Value Value - + Name Name - + Selection Selection - + Title Title - + Hidden Hidden @@ -4074,37 +4074,37 @@ _FilterTab - + Included in manuscript Included in manuscript - + Excluded from manuscript Excluded from manuscript - + Always included Always included - + Always excluded Always excluded - + Reset to default Reset to default - + Mark selection as Mark selection as - + Select Root Folders Select Root Folders @@ -4112,22 +4112,22 @@ _GuiAlert - + Information Information - + Warning Warning - + Error Error - + Question Question @@ -4135,93 +4135,93 @@ _HeadingsTab - + Hide Hide - - + + Editing: {0} Editing: {0} - - + + None None - + Title Title - + Chapter Number Chapter Number - + Chapter Number (Word) Chapter Number (Word) - + Chapter Number (Upper Case Roman) Chapter Number (Upper Case Roman) - + Chapter Number (Lower Case Roman) Chapter Number (Lower Case Roman) - + Scene Number (In Chapter) Scene Number (In Chapter) - + Scene Number (Absolute) Scene Number (Absolute) - + Point of View Character Point of View Character - + Focus Character Focus Character - + Insert Insert - + Apply Apply - + Additional Styling Additional Styling - - - + + + Centre Center - - - + + + Page Break Page Break @@ -4229,117 +4229,117 @@ _NewProjectForm - + Required Required - + Optional Optional - + Create a fresh project Create a fresh project - + Create an example project Create an example project - + Copy an existing project Copy an existing project - + Project Name Project Name - + Author Author - + Project Path Project Path - + Prefill Project Prefill Project - + Set to 0 to only add scenes Set to 0 to only add scenes - + Add {0} chapter documents Add {0} chapter documents - + Add {0} scene documents (to each chapter) Add {0} scene documents (to each chapter) - + Add a folder for plot notes Add a folder for plot notes - + Add a folder for character notes Add a folder for character notes - + Add a folder for location notes Add a folder for location notes - + Add example notes to the above Add example notes to the above - + Chapters and Scenes Chapters and Scenes - + Project Notes Project Notes - + Create New Project Create New Project - + Select Project Folder Select Project Folder - + Fresh Project Fresh Project - + Example Project Example Project - + Template: {0} Template: {0} @@ -4347,7 +4347,7 @@ _NewProjectPage - + A project name is required. A project name is required. @@ -4355,27 +4355,27 @@ _OpenProjectPage - + The project path is not reachable. The project path is not reachable. - + Path Path - + Remove '{0}' from the recent projects list? The project files will not be deleted. Remove '{0}' from the recent projects list? The project files will not be deleted. - + Open Project Open Project - + Remove Project Remove Project @@ -4383,54 +4383,54 @@ _OverviewPage - + Project Project - - + + Name Name - + Revisions Revisions - + Editing Time Editing Time - - + + Word Count Word Count - + In Novels In Novels - + In Notes In Notes - + Selected Novel Selected Novel - + Chapters Chapters - + Scenes Scenes @@ -4438,27 +4438,27 @@ _PreviewWidget - + Press the "Preview" button to generate ... Press the "Preview" button to generate ... - + Processing ... Processing ... - + Done Done - + Unknown Unknown - + Built Built @@ -4466,12 +4466,12 @@ _ProjectListModel - + Word Count Word Count - + Last Opened Last Opened @@ -4479,27 +4479,27 @@ _ReplacePage - + Text Auto-Replace for Preview and Build Text Auto-Replace for Preview and Build - + Keyword Keyword - + Replace With Replace With - + Select item to edit Select item to edit - + Save Save @@ -4507,49 +4507,49 @@ _SettingsPage - + Project name Project name - + Changing this will affect the backup path. Changing this will affect the backup path. - + Author(s) Author(s) - - + + Only used when building the manuscript. Only used when building the manuscript. - + Project language Project language - + Default Default - + Spell check language Spell check language - - + + Overrides main preferences. Overrides main preferences. - + Disable backup on close Disable backup on close @@ -4557,59 +4557,59 @@ _StatsWidget - - + + Words Words - - + + Characters Characters - + Words in Headings Words in Headings - + Words in Text Words in Text - + Headings Headings - + Paragraphs Paragraphs - + Characters in Headings Characters in Headings - + Characters in Text Characters in Text - + Characters, No Spaces Characters, No Spaces - + Characters in Headings, No Spaces Characters in Headings, No Spaces - + Characters in Text, No Spaces Characters in Text, No Spaces @@ -4617,67 +4617,67 @@ _StatusPage - + Novel Document Status Levels Novel Document Status Levels - + Project Note Importance Levels Project Note Importance Levels - + Label Label - + Usage Usage - + Select item to edit Select item to edit - + Colour Color - + Save Save - + Select Colour Select Color - + New Item New Item - + Cannot delete a status item that is in use. Cannot delete a status item that is in use. - + Not in use Not in use - + Used once Used once - + Used by {0} items Used by {0} items @@ -4685,128 +4685,128 @@ _TreeContextMenu - + Empty Trash Empty Trash - + Rename Rename - + Open Document Open Document - + View Document View Document - + Create New ... Create New ... - + Rename to Heading Rename to Heading - + Set Active to ... Set Active to ... - + Toggle Active Toggle Active - + Set Status to ... Set Status to ... - - + + Manage Labels ... Manage Labels ... - + Set Importance to ... Set Importance to ... - + Transform ... Transform ... - - - - + + + + Convert to {0} Convert to {0} - + Merge Child Items into Self Merge Child Items into Self - + Merge Child Items into New Merge Child Items into New - + Merge Documents in Folder Merge Documents in Folder - + Split Document by Headings Split Document by Headings - + Expand All Expand All - + Collapse All Collapse All - + Duplicate Duplicate - - + + Delete Permanently Delete Permanently - - + + Move to Trash Move to Trash - + Move {0} items to Trash? Move {0} items to Trash? - + Do you want to convert the folder to a {0}? This action cannot be reversed. Do you want to convert the folder to a {0}? This action cannot be reversed. @@ -4814,7 +4814,7 @@ _UpdatableMenu - + From Template From Template @@ -4822,12 +4822,12 @@ _ViewPanelBackRefs - + Document Document - + First Heading First Heading @@ -4835,27 +4835,27 @@ _ViewPanelKeyWords - + Tag Tag - + Importance Importance - + Document Document - + Heading Heading - + Short Description Short Description diff --git a/i18n/nw_es_419.ts b/i18n/nw_es_419.ts index ba4c855b..58299e4e 100644 --- a/i18n/nw_es_419.ts +++ b/i18n/nw_es_419.ts @@ -31,32 +31,32 @@ Partition Format - + Formato para Particiones Chapter Format - + Formato para Capítulos Unnumbered Format - + Formato Sin Numeración Scene Format - + Formato para Escenas - Hard Scene Format - + Alt. Scene Format + Formato Alternativo para Escenas Section Format - + Formato para Secciones @@ -86,7 +86,7 @@ Ignore These Keywords - + Ignorar estas palabras clave @@ -206,17 +206,17 @@ First Line Indent - + Sangría en Línea Inicial Markdown (.md) - + Markdown (.md) Preserve Hard Line Breaks - + Conservar Saltos de Línea Forzados @@ -231,7 +231,7 @@ Preserve Tab Characters - + Preservar Caracteres de Tabulación @@ -791,22 +791,22 @@ GuiDocEditFooter - + Line: {0} ({1}) Línea: {0} ({1}) - + Words: {0} ({1}) Palabras: {0} ({1}) - + Words: {0} selected Palabras: {0} seleccionadas - + Status Estado @@ -814,27 +814,27 @@ GuiDocEditHeader - + Toggle Tool Bar Alternar Barra de Herramientas - + Outline - + Estructura - + Search Buscar - + Toggle Focus Mode Alternar el Modo Enfocado - + Close Cerrar @@ -842,62 +842,62 @@ GuiDocEditSearch - + Search for - + Buscar - + Replace with - + Reemplazar con - + Search Buscar - + Case Sensitive Sensibilidad a Mayúsculas y Minúsculas - + Whole Words Only Sólo Palabras Enteras - + RegEx Mode Modo ExReg - + Loop Search Reiniciar la Búsqueda - + Search Next File Buscar en el Siguiente Archivo - + Preserve Case Conservar Mayúsculas y Minúsculas - + Close Search Cerrar la Búsqueda - + Find in current document Buscar en el documento actual - + Find and replace in current document Buscar y reemplazar en el documento actual @@ -920,107 +920,107 @@ No se puedo guardar el documento. - + Saved Document: {0} Documento Guardado: {0} - + Spell checking requires the package PyEnchant. It does not appear to be installed. Para la corrección ortográfica se requiere del paquete PyEnchant. Parece que no se encuentra instalado. - + Spell check complete Se completó la comprobación ortográfica - + Document Details Detalles del Documento - + Created: {0} Creación: {0} - + Updated: {0} Actualizado en: {0} - + File Location: {0} Ubicación del Archivo: {0} - + Set as Document Name Elegir como Nombre del Documento - + Follow Tag Continuar a Etiqueta - + Create Note for Tag Crear Nota para la Etiqueta - + Cut Cortar - + Copy Copiar - + Paste Pegar - + Select All Seleccionar Todo - + Select Word Seleccionar Palabra - + Select Paragraph Seleccionar Párrafo - + Spelling Suggestion(s) Sugerencia(s) de Ortografía - + No Suggestions No Hay Sugerencias - + Add Word to Dictionary Añadir Palabra al Diccionario - + Please select some text before calling replace quotes. Por favor seleccione algo del texto antes de intentar reemplazar las comillas. - + Do you want to create a new project note for the tag '{0}'? ¿Desea crear una nueva nota del proyecto para la etiqueta '{0}'? @@ -1058,7 +1058,7 @@ Document Headings - + Títulación de Documentos @@ -1068,22 +1068,22 @@ Split on Heading Level 1 (Partition) - + Separar por el Nivel de Título 1 (Partición) Split up to Heading Level 2 (Chapter) - + Separar hasta el Nivel de Título 2 (Capítulo) Split up to Heading Level 3 (Scene) - + Separar hasta el Nivel de Título 3 (Escena) Split up to Heading Level 4 (Section) - + Separar hasta el Nivel de Título 4 (Sección) @@ -1104,52 +1104,52 @@ GuiDocToolBar - + Markdown Bold Negrita (de Markdown) - + Markdown Italic Cursiva (de Markdown) - + Markdown Strikethrough Tachado (de Markdown) - + Shortcode Bold Negrita (en código) - + Shortcode Italic Cursiva (en código) - + Shortcode Strikethrough Tachado (en código) - + Shortcode Underline Subrayado (en código) - + Shortcode Highlight - + Resaltado (en código) - + Shortcode Superscript Superíndice (en código) - + Shortcode Subscript Subíndice (en código) @@ -1187,7 +1187,7 @@ Outline - + Estructura @@ -1854,7 +1854,7 @@ Highlight - + Resaltar @@ -1869,22 +1869,22 @@ Heading 1 (Partition) - + Título 1 (Partición) Heading 2 (Chapter) - + Título 2 (Capítulo) Heading 3 (Scene) - + Título 3 (Escena) Heading 4 (Section) - + Título 4 (Sección) @@ -1898,8 +1898,8 @@ - Hard Scene - + Alternative Scene + Escena Alternativa @@ -1944,12 +1944,12 @@ Replace Straight Single Quotes - + Reemplazar Apóstrofos Replace Straight Double Quotes - + Reemplazar Comillas ASCII @@ -1989,7 +1989,7 @@ Find in Project - + Buscar en el Proyecto @@ -2161,12 +2161,12 @@ Details - + Detalles Outline - + Estructura @@ -2997,27 +2997,27 @@ Project Search - + Búsqueda en Proyecto Case Sensitive - Sensibilidad a Mayúsculas y Minúsculas + Sensibilidad a Mayúsculas y Minúsculas Whole Words Only - Sólo Palabras Enteras + Sólo Palabras Enteras RegEx Mode - Modo ExReg + Modo ExReg Search for - + Buscar @@ -3231,7 +3231,7 @@ Project Search - + Búsqueda en Proyecto @@ -4068,7 +4068,7 @@ Hidden - Ocultar + Oculto @@ -4209,21 +4209,21 @@ Additional Styling - + Estilos Adicionales Centre - + Centrado Page Break - Salto de Página + Salto de Página @@ -4560,58 +4560,58 @@ Words - Palabras + Palabras Characters - Personajes + Caracteres Words in Headings - + Palabras en Titulos Words in Text - + Palabras en el Texto Headings - Títulación + Títulos Paragraphs - Párrafos + Párrafos Characters in Headings - + Caracteres en los Títulos Characters in Text - + Caracteres en el Texto Characters, No Spaces - + Caracteres, Sin Espacios Characters in Headings, No Spaces - + Caracteres en los Títulos, Sin Espacios Characters in Text, No Spaces - + Caracteres en el Texto, Sin Espacios @@ -4771,7 +4771,7 @@ Split Document by Headings - + Separar el Documento según Titulación @@ -4786,7 +4786,7 @@ Duplicate - + Duplicar diff --git a/i18n/nw_it_IT.ts b/i18n/nw_it_IT.ts index 7b543baa..15e7c409 100644 --- a/i18n/nw_it_IT.ts +++ b/i18n/nw_it_IT.ts @@ -31,32 +31,32 @@ Partition Format - + Formato della Partizione Chapter Format - + Formato del Capitolo Unnumbered Format - + Formato non numerato Scene Format - + Formato della Scena - Hard Scene Format - + Alt. Scene Format + Formato scena alt. Section Format - + Formato della Sezione @@ -86,7 +86,7 @@ Ignore These Keywords - + Ignora queste parole chiave @@ -206,17 +206,17 @@ First Line Indent - + Rientro della prima linea Markdown (.md) - + Markdown (.md) Preserve Hard Line Breaks - + Conserva le interruzioni di linea @@ -231,7 +231,7 @@ Preserve Tab Characters - + Conserva gli spazi di tabulazione @@ -791,22 +791,22 @@ GuiDocEditFooter - + Line: {0} ({1}) Riga: {0} ({1}) - + Words: {0} ({1}) Parole: {0} ({1}) - + Words: {0} selected Parole: {0} selezionate - + Status Stato @@ -814,27 +814,27 @@ GuiDocEditHeader - + Toggle Tool Bar Attiva/disattiva la Barra degli strumenti - + Outline - + Struttura - + Search Cerca - + Toggle Focus Mode Attiva/Disattiva modalità Focus - + Close Chiudi @@ -842,62 +842,62 @@ GuiDocEditSearch - + Search for - + Ricerca - + Replace with - + Sostituisci con - + Search Cerca - + Case Sensitive Considera maiuscole/minuscole - + Whole Words Only Solo parole intere - + RegEx Mode Modalità RegEx - + Loop Search Ricerca a ciclo continuo - + Search Next File Cerca nel file successivo - + Preserve Case Non considerare maiuscole/minuscole - + Close Search Chiudi ricerca - + Find in current document Trova nel documento corrente - + Find and replace in current document Trova e sostituisci nel documento corrente @@ -920,107 +920,107 @@ Impossibile salvare il documento. - + Saved Document: {0} Documento salvato: {0} - + Spell checking requires the package PyEnchant. It does not appear to be installed. Il controllo ortografico richiede il pacchetto PyEnchant. Non sembra essere installato. - + Spell check complete Controllo ortografico completo - + Document Details Dettagli del documento - + Created: {0} Creato: {0} - + Updated: {0} Aggiornato: {0} - + File Location: {0} Posizione del file: {0} - + Set as Document Name Imposta come nome del documento - + Follow Tag Segui i Tag - + Create Note for Tag Crea una nota per il Tag - + Cut Taglia - + Copy Copia - + Paste Incolla - + Select All Seleziona tutto - + Select Word Seleziona parola - + Select Paragraph Seleziona paragrafo - + Spelling Suggestion(s) Suggerimento(i) ortografico(i) - + No Suggestions Nessun suggerimento - + Add Word to Dictionary Aggiungi parola al dizionario - + Please select some text before calling replace quotes. Per favore seleziona del testo prima di chiedere il cambio di virgolette. - + Do you want to create a new project note for the tag '{0}'? Vuoi creare una nuova nota di progetto per il tag '{0}'? @@ -1058,7 +1058,7 @@ Document Headings - + Intestazioni del documento @@ -1068,22 +1068,22 @@ Split on Heading Level 1 (Partition) - + Dividi sul livello d'intestazione 1 (Partizione) Split up to Heading Level 2 (Chapter) - + Dividi sul livello d'intestazione 2 (Capitolo) Split up to Heading Level 3 (Scene) - + Dividi sul livello d'intestazione 3 (Scena) Split up to Heading Level 4 (Section) - + Dividi sul livello d'intestazione 4 (Sezione) @@ -1104,52 +1104,52 @@ GuiDocToolBar - + Markdown Bold Markdown grassetto - + Markdown Italic Markdown corsivo - + Markdown Strikethrough Markdown barrato - + Shortcode Bold Shortcode grassetto - + Shortcode Italic Shortcode corsivo - + Shortcode Strikethrough Shortcode barrato - + Shortcode Underline Shortcode sottolineato - + Shortcode Highlight - + Evidenziazione - + Shortcode Superscript Shortcode apice - + Shortcode Subscript Shortcode pendice @@ -1187,7 +1187,7 @@ Outline - + Struttura @@ -1854,7 +1854,7 @@ Highlight - + Evidenzia @@ -1869,22 +1869,22 @@ Heading 1 (Partition) - + Titolo 1 (Partizione) Heading 2 (Chapter) - + Titolo 2 (Capitolo) Heading 3 (Scene) - + Titolo 3 (Scena) Heading 4 (Section) - + Titolo 4 (Sezione) @@ -1898,8 +1898,8 @@ - Hard Scene - + Alternative Scene + Scena alternativa @@ -1944,12 +1944,12 @@ Replace Straight Single Quotes - + Sostituisci le singole virgolette dritte Replace Straight Double Quotes - + Sostituisci le doppie virgolette dritte @@ -1989,7 +1989,7 @@ Find in Project - + Trova nel progetto @@ -2161,12 +2161,12 @@ Details - + Dettagli Outline - + Struttura @@ -2997,27 +2997,27 @@ Project Search - + Ricerca nel progetto Case Sensitive - Considera maiuscole/minuscole + Considera maiuscole/minuscole Whole Words Only - Solo parole intere + Solo parole intere RegEx Mode - Modalità RegEx + Modalità RegEx Search for - + Ricerca @@ -3231,7 +3231,7 @@ Project Search - + Ricerca nel progetto @@ -4068,7 +4068,7 @@ Hidden - Nascosto + Nascosto @@ -4209,21 +4209,21 @@ Additional Styling - + Elementi di stile supplementari Centre - + Centro Page Break - Interruzione di pagina + Interruzione di pagina @@ -4560,58 +4560,58 @@ Words - Parole + Parole Characters - Caratteri + Caratteri Words in Headings - + Parole nelle intestazioni Words in Text - + Parole nel testo Headings - Intestazioni + Intestazioni Paragraphs - Paragrafi + Paragrafi Characters in Headings - + Caratteri nelle intestazioni Characters in Text - + Caratteri nel testo Characters, No Spaces - + Caratteri, esclusi gli spazi Characters in Headings, No Spaces - + Caratteri nelle intestazioni, esclusi gli spazi Characters in Text, No Spaces - + Caratteri nel testo, esclusi gli spazi @@ -4771,7 +4771,7 @@ Split Document by Headings - + Dividi il documento alle intestazioni @@ -4786,7 +4786,7 @@ Duplicate - + Duplica diff --git a/i18n/nw_ja_JP.ts b/i18n/nw_ja_JP.ts index 1018ff39..0abdc800 100644 --- a/i18n/nw_ja_JP.ts +++ b/i18n/nw_ja_JP.ts @@ -31,32 +31,32 @@ Partition Format - + パーティション書式 Chapter Format - + チャプター書式 Unnumbered Format - + 番号なし書式 Scene Format - + シーン書式 - Hard Scene Format - + Alt. Scene Format + 代替シーン書式 Section Format - + セクション書式 @@ -86,7 +86,7 @@ Ignore These Keywords - + これらのキーワードを無視 @@ -196,7 +196,7 @@ Page Header - ページ見出し + ページヘッダー @@ -206,17 +206,17 @@ First Line Indent - + 最初の行のインデント Markdown (.md) - + マークダウン (.md) Preserve Hard Line Breaks - + ハードラインブレイクを保持 @@ -231,7 +231,7 @@ Preserve Tab Characters - + タブ文字を保持 @@ -409,17 +409,17 @@ Novel Chapter - 小説の章 + 小説のチャプター Novel Scene - 小説の場面 + 小説のシーン Novel Section - 小説の節 + 小説のセクション @@ -791,22 +791,22 @@ GuiDocEditFooter - + Line: {0} ({1}) 行: {0} ({1}) - + Words: {0} ({1}) 単語: {0} ({1}) - + Words: {0} selected 単語: {0} 選択済み - + Status ステータス @@ -814,27 +814,27 @@ GuiDocEditHeader - + Toggle Tool Bar ツールバーの切り替え - + Outline - + アウトライン - + Search 検索 - + Toggle Focus Mode フォーカスモードの切り替え - + Close 閉じる @@ -842,62 +842,62 @@ GuiDocEditSearch - + Search for - + 検索 - + Replace with - + 置換候補 - + Search 検索 - + Case Sensitive 大文字と小文字を区別 - + Whole Words Only 完全一致のみ - + RegEx Mode 正規表現モード - + Loop Search ループ検索 - + Search Next File 次のファイルを検索 - + Preserve Case 大文字と小文字を保持 - + Close Search 検索を閉じる - + Find in current document 現在のドキュメント内を検索 - + Find and replace in current document 現在のドキュメント内を検索して置き換え @@ -920,107 +920,107 @@ ドキュメントを保存できませんでした。 - + Saved Document: {0} 保存済みドキュメント: {0} - + Spell checking requires the package PyEnchant. It does not appear to be installed. スペルチェックにはPyEnchantパッケージが必要ですが、インストールされていないようです。 - + Spell check complete スペルチェック完了 - + Document Details ドキュメントの詳細 - + Created: {0} 作成済み: {0} - + Updated: {0} 更新: {0} - + File Location: {0} ファイル場所: {0} - + Set as Document Name ドキュメント名として設定 - + Follow Tag タグをフォロー - + Create Note for Tag タグのメモを作成 - + Cut 切り取り - + Copy コピー - + Paste 貼り付け - + Select All すべて選択 - + Select Word 単語を選択 - + Select Paragraph 段落を選択 - + Spelling Suggestion(s) スペルの提案 - + No Suggestions 候補なし - + Add Word to Dictionary 単語を辞書に追加 - + Please select some text before calling replace quotes. 置き換え引用符を呼び出す前にテキストを選択してください。 - + Do you want to create a new project note for the tag '{0}'? タグ '{0}' の新しいプロジェクトノートを作成しますか? @@ -1058,7 +1058,7 @@ Document Headings - + ドキュメントの見出し @@ -1068,22 +1068,22 @@ Split on Heading Level 1 (Partition) - + 見出し階層1 (パーティション) で分割 Split up to Heading Level 2 (Chapter) - + 見出し階層2 (チャプター) で分割 Split up to Heading Level 3 (Scene) - + 見出し階層3 (シーン) で分割 Split up to Heading Level 4 (Section) - + 見出し階層4 (セクション) で分割 @@ -1104,52 +1104,52 @@ GuiDocToolBar - + Markdown Bold マークダウン 太字 - + Markdown Italic マークダウン 斜体 - + Markdown Strikethrough マークダウン 取り消し線 - + Shortcode Bold ショートコード 太字 - + Shortcode Italic ショートコード 斜体 - + Shortcode Strikethrough ショートコード 取り消し線 - + Shortcode Underline ショートコード 下線 - + Shortcode Highlight - + ショートコードハイライト - + Shortcode Superscript ショートコード 上付き文字 - + Shortcode Subscript ショートコード 下付き文字 @@ -1187,7 +1187,7 @@ Outline - + アウトライン @@ -1644,22 +1644,22 @@ Left Single Quote - 左シングルクォーテーション + 左シングルクォート Right Single Quote - 右シングルクォーテーション + 右シングルクォート Left Double Quote - 左ダブルクォーテーション + 左ダブルクォート Right Double Quote - 右ダブルクォーテーション + 右ダブルクォート @@ -1819,12 +1819,12 @@ Wrap Double Quotes - ダブルクォーテーションで包む + ダブルクォートで包む Wrap Single Quotes - シングルクォーテーションで包む + シングルクォートで包む @@ -1854,7 +1854,7 @@ Highlight - + ハイライト @@ -1869,22 +1869,22 @@ Heading 1 (Partition) - + 見出し1 (パーティション) Heading 2 (Chapter) - + 見出し2 (チャプター) Heading 3 (Scene) - + 見出し3 (シーン) Heading 4 (Section) - + 見出し4 (セクション) @@ -1894,12 +1894,12 @@ Unnumbered Chapter - 番号のない章 + 番号なしチャプター - Hard Scene - + Alternative Scene + 代替シーン @@ -1944,12 +1944,12 @@ Replace Straight Single Quotes - + 直線シングルクォートを置き換え Replace Straight Double Quotes - + 直線ダブルクォートを置換 @@ -1989,7 +1989,7 @@ Find in Project - + プロジェクト内を検索 @@ -2161,12 +2161,12 @@ Details - + 詳細 Outline - + アウトライン @@ -2351,17 +2351,17 @@ Chapter - + チャプター Scene - 場面 + シーン Section - + セクション @@ -2551,7 +2551,7 @@ Emphasise partition and chapter labels - パーティションと章ラベルを強調 + パーティションとチャプターラベルを強調 @@ -2561,12 +2561,12 @@ Show full path in document header - ドキュメント見出しにフルパスを表示 + ドキュメントヘッダーにフルパスを表示 Add the parent folder names to the header. - 親フォルダ名を見出しに追加します。 + 親フォルダ名をヘッダーに追加します。 @@ -2821,7 +2821,7 @@ Highlight text wrapped in quotes - 引用符で囲まれたテキストを強調 + 引用符で囲まれたテキストをハイライト @@ -2833,22 +2833,22 @@ Allow open-ended single quotes - オープンエンドのシングルクォートを許可 + 閉じないシングルクォートを許可 Highlight single-quoted line with no closing quote. - 終了引用符がないシングルクォートの行を強調表示します。 + 終了引用符がないシングルクォートの行をハイライト表示します。 Allow open-ended double quotes - オープンエンドのダブルクォートを許可 + 閉じないダブルクォートを許可 Highlight double-quoted line with no closing quote. - 終了引用符がないダブルクォートの行を強調表示します。 + 終了引用符がないダブルクォートの行をハイライト表示します。 @@ -2997,27 +2997,27 @@ Project Search - + プロジェクト検索 Case Sensitive - 大文字と小文字を区別 + 大文字と小文字を区別 Whole Words Only - 完全一致のみ + 完全一致のみ RegEx Mode - 正規表現モード + 正規表現モード Search for - + 検索 @@ -3132,12 +3132,12 @@ New Chapter - 新規章 + 新規チャプター New Scene - 新規場面 + 新規シーン @@ -3231,7 +3231,7 @@ Project Search - + プロジェクト検索 @@ -3671,12 +3671,12 @@ Summary of the chapter. - 章の概要。 + チャプターの概要。 Summary of the scene. - 場面の概要。 + シーンの概要。 @@ -3686,13 +3686,13 @@ Chapter {0} - 章 {0} + チャプター {0} Scene {0} - 場面 {0} + シーン {0} @@ -4025,7 +4025,7 @@ Chapters on odd pages - 奇数ページ上の章 + 奇数ページ上のチャプター @@ -4068,7 +4068,7 @@ Hidden - 非表示 + 非表示 @@ -4159,32 +4159,32 @@ Chapter Number - 章番号 + チャプター番号 Chapter Number (Word) - 章番号 (文章) + チャプター番号 (文章) Chapter Number (Upper Case Roman) - 章番号 (大文字のローマ字) + チャプター番号 (大文字のローマ字) Chapter Number (Lower Case Roman) - 章番号 (小文字のローマ字) + チャプター番号 (小文字のローマ字) Scene Number (In Chapter) - 場面番号 (チャプター内) + シーン番号 (チャプター内) Scene Number (Absolute) - 場面番号 (絶対) + シーン番号 (絶対) @@ -4209,21 +4209,21 @@ Additional Styling - + 追加のスタイル Centre - + 中央 Page Break - 改ページ + 改ページ @@ -4281,12 +4281,12 @@ Add {0} chapter documents - {0} 章ドキュメントを追加 + {0} チャプタードキュメントを追加 Add {0} scene documents (to each chapter) - {0} 場面ドキュメントを追加(各章に) + {0} シーンドキュメントを追加(各チャプターに) @@ -4311,7 +4311,7 @@ Chapters and Scenes - 章と場面 + チャプターとシーン @@ -4427,12 +4427,12 @@ Chapters - + チャプター Scenes - 場面 + シーン @@ -4560,58 +4560,58 @@ Words - 単語 + 単語数 Characters - 文字 + 文字数 Words in Headings - + 見出し内の単語数 Words in Text - + テキスト内の単語数 Headings - 見出し + 見出し Paragraphs - 段落 + 段落 Characters in Headings - + 見出し内の文字数 Characters in Text - + テキスト内の文字数 Characters, No Spaces - + スペースなし文字数 Characters in Headings, No Spaces - + 見出し内のスペースなし文字数 Characters in Text, No Spaces - + テキスト内のスペースなし文字数 @@ -4771,7 +4771,7 @@ Split Document by Headings - + 見出しでドキュメントを分割 @@ -4786,7 +4786,7 @@ Duplicate - + 複製 diff --git a/i18n/nw_nb_NO.ts b/i18n/nw_nb_NO.ts index 20e3d251..aaab9314 100644 --- a/i18n/nw_nb_NO.ts +++ b/i18n/nw_nb_NO.ts @@ -4,232 +4,232 @@ Builds - + Document Filters Dokumentfiltre - + Novel Documents Romandokumenter - + Project Notes Prosjektnotater - + Inactive Documents Inaktive dokumenter - + Headings Overskrifter - + Partition Format Tittelformat - + Chapter Format Kapittelformat - + Unnumbered Format Unummerert format - + Scene Format Sceneformat - - Hard Scene Format + + Alt. Scene Format Alt. sceneformat - + Section Format Seksjonformat - + Text Content Tekstinnhold - + Include Synopsis Inkluder sammendrag - + Include Comments Inkluder kommentarer - + Include Keywords Inkluder kodeord - + Include Body Text Inkluder tekst - + Ignore These Keywords Ignorer disse nøkkelordene - + Insert Content Legg til innhold - + Add Titles for Notes Legg til titler for notater - + Text Format Tekstformat - + Font Family Skriftfamilie - + Font Size Skriftstørrelse - + Line Height Linjehøyde - + Text Options Skriftvalg - + Justify Text Margins Juster tekstmarginer - + Replace Unicode Characters Erstatt unicode-tegn - + Replace Tabs with Spaces Erstatt tabulator med mellomrom - + Page Layout Sideoppsett - + Unit Enhet - + Page Size Sidestørrelse - + Page Width Sidebredde - + Page Height Sidehøyde - + Top Margin Toppmarg - + Bottom Margin Bunnmarg - + Left Margin Venstremarg - + Right Margin Høyremarg - + Open Document (.odt) Open Document (.odt) - + Add Highlight Colours Bruk farger på spesielle elementer - + Page Header Topptekst - + Page Counter Offset Første side for sideteller - + First Line Indent Innrykk på første linje - + Markdown (.md) Markdown (.md) - + Preserve Hard Line Breaks Bevar harde linjeskift - + HTML (.html) HTML (.html) - + Add CSS Styles Legg til CSS style - + Preserve Tab Characters Bevar tabulatorer @@ -237,72 +237,72 @@ Common - + in the future i fremtiden - + just now nå nettopp - + a minute ago for et minutt siden - + {0} minutes ago for {0} minutter siden - + an hour ago for en time siden - + {0} hours ago for {0} timer siden - + a day ago for en dag siden - + {0} days ago for {0} dager siden - + a week ago for en uke siden - + {0} weeks ago for {0} uker siden - + a month ago for en måned siden - + {0} months ago for {0} måneder siden - + a year ago for et år siden - + {0} years ago for {0} år siden @@ -310,375 +310,375 @@ Constant - - - + + + None Ingen - + Novel Roman - - + + Plot Plott - - + + Characters Karakterer - - + + Locations Lokasjoner - - + + Timeline Tidslinje - - + + Objects Objekter - - + + Entities Enheter - - - + + + Custom Annet - + Archive Arkiv - + Templates Maler - + Trash Søppel - - + + Novel Document Romandokument - - + + Project Note Prosjektnotat - + Root Folder Hovedmappe - + Folder Mappe - + Novel Title Page Tittelside - + Novel Chapter Kapittel - + Novel Scene Scene - + Novel Section Seksjon - + Tag Knagg - + Point of View Perspektiv - - + + Focus Fokus - + Title Tittel - + Level Nivå - + Document Dokument - + Line Linje - + Chars Tegn - + Words Ord - + Pars Avsnitt - + POV Persp. - + Synopsis Sammendrag - + Open Document (.odt) Open Document (.odt) - + Flat Open Document (.fodt) Flat Open Document (.fodt) - + novelWriter HTML (.html) novelWriter HTML (.htm) - + novelWriter Markup (.txt) novelWriter Markup (.txt) - + Standard Markdown (.md) Standard Markdown (.md) - + Extended Markdown (.md) Utvidet Markdown (.md) - + JSON + novelWriter HTML (.json) JSON + novelWriter HTML (.json) - + JSON + novelWriter Markup (.json) JSON + novelWriter Markup (.json) - + Text files Tekstfiler - + Markdown files Markdown-filer - + novelWriter files novelWriter-filer - + CSV files CSV-filer - + All files Alle filer - + Millimetres Millimeter - + Centimetres Centimeter - + Inches Tommer - + A4 A4 - + A5 A5 - + A6 A6 - + US Legal US Legal - + US Letter US Letter - + Straight single quotation mark Rett, enkelt sitattegn - + Straight double quotation mark Rett, dobbelt sitattegn - + Left single quotation mark Venstre, enkelt sitattegn - + Right single quotation mark Høyre, enkelt sitattegn - + Single low-9 quotation mark Enkelt, lavt-9 sitattegn - + Single high-reversed-9 quotation mark Enkelt, høyt, reversert-9 sitattegn - + Left double quotation mark Venstre, dobbelt sitattegn - + Right double quotation mark Høyre, dobbelt sitattegn - + Double low-9 quotation mark Dobbelt, lavt-9 sitattegn - + Double high-reversed-9 quotation mark Dobbelt, høyt, reversert-9 sitattegn - + Double low-reversed-9 quotation mark Dobbelt, lavt, reversert-9 sitattegn - + Single left-pointing angle quotation mark Enkelt, venstre, angulært sitattegn - + Single right-pointing angle quotation mark Enkelt, høyre, angulært sitattegn - + Double left-pointing angle quotation mark Dobbelt, venstre, angulært sitattegn - + Double right-pointing angle quotation mark Dobbelt, høyre, angulært sitattegn - + Left corner bracket Venstre hjørnevinkel - + Right corner bracket Høyre hjørnevinkel - + Left white corner bracket Venstre, hvit hjørnevinkel - + Right white corner bracket Høyre, hvit hjørnevinkel @@ -686,17 +686,17 @@ GuiAbout - + About novelWriter Om novelWriter - + This application is licenced under {0} Denne applikasjonen er lisensiert under {0} - + Credits Krediteringer @@ -704,38 +704,38 @@ GuiBuildSettings - - + + Manuscript Build Settings Byggeinnstillinger for manuskript - + Name Navn - + Selection Utvalg - + Headings Overskrifter - + Content Innhold - + Format Format - + Output Utdata @@ -743,47 +743,47 @@ GuiDictionaries - + Add Dictionaries Legg til ordbøker - + Download a dictionary from one of the links, and add it below. Last ned en ordbok fra en av lenkene, og legg den til nedenfor. - + Add Dictionary Legg til ordbok - + Dictionary install location Mappe hvor ordbøkene er installert - + Additional dictionaries found: {0} Ordbøker funnet: {0} - + Free or Libre Office extension Free- eller Libre Office-utvidelse - + Browse Files Bla gjennom - + Could not process dictionary file Kunne ikke behandle ordbokfilen - + Added: {0} [{1}B] Lagt til: {0} [{1}B] @@ -791,22 +791,22 @@ GuiDocEditFooter - + Line: {0} ({1}) Linje: {0} ({1}) - + Words: {0} ({1}) Ord: {0} ({1}) - + Words: {0} selected Ord: {0} valgt - + Status Status @@ -814,27 +814,27 @@ GuiDocEditHeader - + Toggle Tool Bar Vis/skjul verktøylinje - + Outline Disposisjon - + Search Søk - + Toggle Focus Mode - Slå av/på "Fokus-modus" + Slå av/på "Fokus-modus" - + Close Lukk @@ -842,62 +842,62 @@ GuiDocEditSearch - + Search for Søketekst - + Replace with Erstatt med - + Search Søk - + Case Sensitive Skill store/små bokstaver - + Whole Words Only Kun hele ord - + RegEx Mode RegEx-modus - + Loop Search Søk rundt - + Search Next File Søk i neste file - + Preserve Case Behold store/små bokstaver - + Close Search Lukk søk - + Find in current document Søk i det åpne dokumentet - + Find and replace in current document Søk og erstatt i det åpne dokumentet @@ -905,122 +905,122 @@ GuiDocEditor - + Opened Document: {0} Åpnet dokument: {0} - + This document has been changed outside of novelWriter while it was open. Overwrite the file on disk? Dette dokumentet er endret utenfor novelWriter mens det var åpent. Overskrive filen på disken? - + Could not save document. Kunne ikke lagre dokumentet. - + Saved Document: {0} Lagret dokument: {0} - + Spell checking requires the package PyEnchant. It does not appear to be installed. Stavekontroll krever at pakken PyEnchant er installert. Det ser det ikke ut til at den er. - + Spell check complete Stavekontrollen er ferdig - + Document Details Dokumentdetaljer - + Created: {0} Opprettet: {0} - + Updated: {0} Oppdatert: {0} - + File Location: {0} Filplassering: {0} - + Set as Document Name Sett som dokumentnavn - + Follow Tag Følg knagg - + Create Note for Tag Opprett notat for knagg - + Cut Klipp - + Copy Kopier - + Paste Lim inn - + Select All Velg hele teksten - + Select Word Velg hele ordet - + Select Paragraph Velg hele avsnittet - + Spelling Suggestion(s) Forslag fra stavekontrollen - + No Suggestions Ingen forslag - + Add Word to Dictionary Legg til ord i ordbok - + Please select some text before calling replace quotes. Venligst velg en del av teksten før du velger å erstatte sitattegn. - + Do you want to create a new project note for the tag '{0}'? Vil du opprette et nytt prosjektnotat for knaggen '{0}'? @@ -1028,22 +1028,22 @@ GuiDocMerge - + Merge Documents Slå sammen dokumenter - + Documents to Merge Dokumenter som skal slås sammen - + Drag and drop items to change the order, or uncheck to exclude. Dra og slipp elementer for å endre rekkefølgen, eller fjern merking for å ekskludere. - + Move merged items to Trash Flytt sammenslåtte elementer til papirkurven @@ -1051,52 +1051,52 @@ GuiDocSplit - + Split Document Del opp dokument - + Document Headings Dokumentets overskrifter - + Select the maximum level to split into files. Velg hvilket nivå av overskrifter å dele opp til. - + Split on Heading Level 1 (Partition) Splitt på overskrifter på nivå 1 (titler) - + Split up to Heading Level 2 (Chapter) Splitt på overskrifter opp til nivå 2 (kapitler) - + Split up to Heading Level 3 (Scene) Splitt på overskrifter opp til nivå 3 (scener) - + Split up to Heading Level 4 (Section) Splitt på overskrifter opp til nivå 4 (seksjoner) - + Split into a new folder Del inn i en ny mappe - + Create document hierarchy Opprett dokumenthierarki - + Move split document to Trash Flytt splittet element til papirkurven @@ -1104,52 +1104,52 @@ GuiDocToolBar - + Markdown Bold Fet skrift med Markdown - + Markdown Italic Kursiv med Markdown - + Markdown Strikethrough Gjennomstrek med Markdown - + Shortcode Bold Fet skrift med kortkode - + Shortcode Italic Kursiv med kortkode - + Shortcode Strikethrough Gjennomstrek med kortkode - + Shortcode Underline Understrek med kortkode - + Shortcode Highlight Tekstutheving med kortkode - + Shortcode Superscript Hevet skrift med kortkode - + Shortcode Subscript Senket skrift med kortkode @@ -1157,27 +1157,27 @@ GuiDocViewFooter - + Show/Hide Viewer Panel Vis/skjul visningspanelet - + Comments Kommentarer - + Show Comments Vis kommentarer - + Synopsis Sammendrag - + Show Synopsis Comments Vis sammendrag @@ -1185,27 +1185,27 @@ GuiDocViewHeader - + Outline Disposisjon - + Go Backward Gå bakover - + Go Forward Gå fremover - + Reload Oppdater - + Close Lukk @@ -1213,27 +1213,27 @@ GuiDocViewer - + An error occurred while generating the preview. Det har oppstått en feil under genereringen av visningen. - + Copy Kopier - + Select All Velg hele teksten - + Select Word Velg hele ordet - + Select Paragraph Velg hele avsnittet @@ -1241,12 +1241,12 @@ GuiDocViewerPanel - + Hide Inactive Tags Skjul inaktive knagger - + References Referanser @@ -1254,12 +1254,12 @@ GuiEditLabel - + Item Label Enhetens navn - + Label Navn @@ -1267,37 +1267,37 @@ GuiItemDetails - + Label Navn - + Status Status - + Class Klasse - + Usage Formål - + Characters Tegn - + Words Ord - + Paragraphs Avsnitt @@ -1305,27 +1305,27 @@ GuiLipsum - + Insert Placeholder Text Sett inn midlertidig tekst - + Insert Lorem Ipsum Text Sett inn Lorem Ipsum-tekst - + Number of paragraphs Antall avsnitt - + Randomise order Tilfeldig rekkefølge - + Insert Sett inn @@ -1333,103 +1333,103 @@ GuiMain - + novelWriter is ready ... novelWriter er klar ... - + You are now running novelWriter version {0}. Du kjører nå novelWriter versjon {0}. - + Please check the {0}release notes{1} for further details. Sjekk {0}utgivelsesnotater{1} for mer informasjon. - + Close the current project? Ønsker du å lukke dette prosjektet? - - + + Changes are saved automatically. Endringer lagres automatisk. - + Backup the current project? Ønsker du å ta sikkerhetskopi av dette prosjektet? - + The project is already open by another instance of novelWriter, and is therefore locked. Override lock and continue anyway? Prosjektet er allerede åpent av en annen instans av novelWriter, og er derfor låst. Vil du overstyre denne låsen og fortsette likevel? - + Note: If the program or the computer previously crashed, the lock can safely be overridden. However, overriding it is not recommended if the project is open in another instance of novelWriter. Doing so may corrupt the project. Merk: Hvis programmet eller datamaskinen tidligere krasjet, kan fil-låsen trygt overstyres. Det anbefales imidlertid ikke å overstyre den hvis prosjektet er åpent i en annen instans av novelWriter. Å gjøre det kan skape konflikter i prosjektets filer. - + The project was locked by the computer '{0}' ({1} {2}), last active on {3}. Prosjektet er låst av datamaskinen {0} ({1} {2}), siste registrerte aktivitet var {3}. - + The project index is outdated or broken. Rebuilding index. Prosjektets indeks er utdatert eller skadet. Bygger indeksen på nytt. - + Import File Importer fil - + Could not read file. The file must be an existing text file. Kunne ikke lese filen. Filen må eksistere fra før av. - + Please open a document to import the text file into. Vennligst åpne et dokument hvor teksten i filen kan importeres. - + Importing the file will overwrite the current content of the document. Do you want to proceed? Å importere filen vil overskrive all eksisterende tekst i dokumentet. Ønsker du å fortsette? - + Indexing completed in {0} ms Indekseringen tok {0} ms - + The project index has been successfully rebuilt. Prosjektets indeks har blitt bygget på nytt. - + Could not initialise the dialog. Kunne ikke initialisere dialogen. - + Do you want to exit novelWriter? Ønsker du å avslutte novelWriter? - + Some changes will not be applied until novelWriter has been restarted. Noen endringer vil ikke tas i bruk før neste gang novelWriter startes. - + Could not find the reference for tag '{0}'. It either doesn't exist, or the index is out of date. The index can be updated from the Tools menu, or by pressing {1}. Kunne ikke finne referansen til knagg {0}. Enten finnes den ikke, eller så er prosjektets indeks ikke oppdatert. Indeksen kan oppdateres fra Verktøy-menyen eller ved å trykke på {1}. @@ -1437,657 +1437,657 @@ GuiMainMenu - + &Project &Prosjekt - + Create or Open Project Opprett eller åpne prosjekt - + Save Project Lagre prosjektet - + Close Project Lukk prosjektet - + Project Settings Prosjektinnstillinger - + Novel Details Roman-detaljer - + Rename Item Endre navn - + Delete Item Slett enhet - + Empty Trash Tøm søppel - + Exit Avslutt - + &Document &Dokument - + Open Document Åpne dokument - + Save Document Lagre dokumentet - + Close Document Lukk dokumentet - + View Document Vis dokument - + Close Document View Lukk dokumentvisning - + Show File Details Vis filinformasjon - + Import Text from File Importer tekst fra fil - + &Edit &Rediger - + Undo Angre - + Redo Gjenopprett - + Cut Klipp - + Copy Kopier - + Paste Lim inn - + Select All Velg hele teksten - + Select Paragraph Velg hele avsnittet - + &View &Vis - + Go to Project Tree Gå til prosjekt-tre - + Go to Document Editor Gå til dokument-editor - + Go to Outline Gå til disposisjon - + Navigate Backward Navigere bakover - + Navigate Forward Navigere fremover - + Focus Mode Focus-modus - + Full Screen Mode Fullskjerm-modus - + &Insert Sett &inn - + Dashes Bindestreker - + Short Dash Kort bindestrek - + Long Dash Lang bindestrek - + Horizontal Bar Horisontal strek - + Figure Dash Tallstrek - + Quote Marks Sitattegn - + Left Single Quote Venstre, enkelt sitattegn - + Right Single Quote Høyre, enkelt sitattegn - + Left Double Quote Venstre, dobbelt sitattegn - + Right Double Quote Høyre, dobbelt sitattegn - + Alternative Apostrophe Alternativ apostrof - + General Punctuation Generell tegnsetting - + Ellipsis Ellipsis - + Prime Primtegn - + Double Prime Dobbelt primtegn - + White Spaces Mellomrom - + Non-Breaking Space Hardt mellomrom - + Thin Space Kort mellomrom - + Thin Non-Breaking Space Hardt, kort mellomrom - + Other Symbols Andre symboler - + List Bullet Kulepunkt - + Hyphen Bullet Bindestrekpunkt - + Flower Mark Blomsterpunkt - + Per Mille Promille - + Degree Symbol Gradertegn - + Minus Sign Minustegn - + Times Sign Gangetegn - + Division Sign Deletegn - + Tags and References Knagger og referanser - + Special Comments Andre kommentartyper - + Synopsis Comment Kommentar med sammendrag - + Short Description Comment Kommentar for kort beskrivelse - + Page Break and Space Sideskift og avstand - + Page Break Sideskift - + Vertical Space (Single) Vertikal avstand (enkel) - + Vertical Space (Multi) Vertikal avstand (flere) - + Placeholder Text Midlertidig tekst - + &Format &Formattering - + Bold Fet - + Italic Kursiv - + Strikethrough Gjennomstrek - + Wrap Double Quotes Sett i doble sitattegn - + Wrap Single Quotes Sett i enkle sitattegn - + More Formats ... Flere formater ... - + Bold (Shortcode) Fet (kortkode) - + Italics (Shortcode) Kursiv (kortkode) - + Strikethrough (Shortcode) Gjennomstrek (Kortkode) - + Underline Understrek - + Highlight Tekstutheving - + Superscript Hevet skrift - + Subscript Senket skrift - + Heading 1 (Partition) Overskrift 1 (inndeling) - + Heading 2 (Chapter) Overskrift 2 (kapittel) - + Heading 3 (Scene) Overskrift 3 (scene) - + Heading 4 (Section) Overskrift 4 (seksjon) - + Novel Title Boktittel - + Unnumbered Chapter Unumrert kapittel - - Hard Scene - Alt. scene + + Alternative Scene + Alternativ scene - + Align Left Venstrejuster - + Align Centre Sentrer - + Align Right Høyrejuster - + Indent Left Innrykk fra venstre - + Indent Right Innrykk fra høyre - + Toggle Comment Veksle kommentar - + Toggle Ignore Text Aktiver/deaktiver ignorert tekst - + Remove Block Format Fjern formattering - + Replace Straight Single Quotes Erstatt enkle sitattegn - + Replace Straight Double Quotes Erstatt doble sitattegn - + Remove In-Paragraph Breaks Fjern linjeskift i avsnittet - + &Search &Søk - + Find Søk - + Replace Erstatt - + Find Next Finn neste - + Find Previous Finn forrige - + Replace Next Erstatt neste - + Find in Project Søk i prosjektet - + &Tools &Verktøy - + Check Spelling Stavekontroll - + Spell Check Language Språk for stavekontroll - + Default Ingen valg - + Re-Run Spell Check Kjør stavekontroll - + Project Word List Prosjektets ordliste - + Add Dictionaries Legg til ordbøker - + Rebuild Index Bygg indeks - + Backup Project Lag sikkerhetskopi av prosjektets mappe - + Build Manuscript Bygg manuskript - + Writing Statistics Statistikk - + Preferences Innstillinger - + &Help &Hjelp - + About novelWriter Om novelWriter - + About Qt5 Om Qt5 - + User Manual (Online) Brukermanual (på nett) - + User Manual (PDF) Brukermanual (PDF) - + Report an Issue (GitHub) Rapporter en feil (GitHub) - + Ask a Question (GitHub) Still et spørsmål (GitHub) - + The novelWriter Website novelWriters nettside @@ -2095,38 +2095,38 @@ GuiMainStatus - - + + None Ingen - + Editor Editor - + Project Prosjekt - + Session Time Tid brukt i gjeldende sesjon - + Words: {0} ({1}) Ord: {0} ({1}) - + Project word count (session change) Antall ord i prosjektet (endring i denne sesjonen) - + Novel word count (session change) Antall ord i roman-teksten (endring i denne sesjonen) @@ -2134,63 +2134,63 @@ GuiManuscript - + Build Manuscript Bygg manuskript - + Add New Build Legg til ny byggedefinisjon - + Delete Selected Build Slett valgte byggedefinisjon - + Edit Selected Build Rediger valgte byggedefinisjon - + Builds Byggedefinisjoner - + Details Detaljer - + Outline Disposisjon - + Preview Forhåndsvis - + Print Skriv ut - + Build Bygg - + Close Lukk - - + + My Manuscript Mitt manuskript @@ -2198,57 +2198,57 @@ GuiManuscriptBuild - + Build Manuscript Bygg manuskript - + Output Format Dokumentformat - + Table of Contents Innholdsfortegnelse - + Path Filbane - + File Name Filnavn - + Reset file name to default Tilbakestill filnavn til standard - + Open Folder Åpne mappe - + &Build &Bygg - + Select Folder Velg mappe - + Output folder does not exist. Mappen eksisterer ikke. - + The file already exists. Do you want to overwrite it? Filen eksisterer allerede. Vil du overskrive den? @@ -2256,18 +2256,18 @@ GuiNovelDetails - - + + Novel Details Roman-detaljer - + Overview Oversikt - + Contents Innhold @@ -2275,58 +2275,58 @@ GuiNovelToolBar - + Outline of {0} Innhold i {0} - + Novel Root Roman-mappe - + Refresh Oppdatér - + Last Column Siste kolonne - + Hidden Skjult - + Point of View Character Synsvinkel-karakter - + Focus Character Fokus-karakter - + Novel Plot Roman-plott - - + + Column Size Kolonnebredde - + More Options Flere valg - + Maximum column size in % Maksimal kolonnebredde i % @@ -2334,7 +2334,7 @@ GuiNovelTree - + No meta data Ingen meta-data @@ -2342,64 +2342,64 @@ GuiOutlineDetails - - - + + + Title Tittel - + Chapter Kapittel - + Scene Scene - + Section Seksjon - + Document Dokument - + Status Status - + Characters Tegn - + Words Ord - + Paragraphs Avsnitt - + Synopsis Sammendrag - + Title Details Oversikt - + Reference Tags Referanser @@ -2407,7 +2407,7 @@ GuiOutlineHeaderMenu - + Select Columns Velg kolonner @@ -2415,17 +2415,17 @@ GuiOutlineToolBar - + Outline of Disposisjon for - + Refresh Oppdatér - + Export CSV Eksporter CSV @@ -2433,7 +2433,7 @@ GuiOutlineTree - + Save Outline As Lagre disposisjon som @@ -2441,553 +2441,553 @@ GuiPreferences - - + + Preferences Innstillinger - + Search Søk - + General Generelt - + Appearance Utseende - + Display language Visningspråk - - - + + + Requires restart to take effect. Krever omstart for å tre i kraft. - + Colour theme Fargetema - + General colour theme and icons. Generelt fargetema og ikoner. - + Application font family Skriftfamilie for program - + Application font size Skriftstørrelse for program - - + + pt pt - + Hide vertical scroll bars in main windows Skjul vertikale rullefelt i hovedvinduer - - + + Scrolling available with mouse wheel and keys only. Rulling kan bare gjøres med mus og tastatur. - + Hide horizontal scroll bars in main windows Skjul horisontale rullefelt i hovedvinduer - + Document Style Dokumentets stil - + Document colour theme Dokumentets fargetema - + Colour theme for the editor and viewer. Fargetema for redigering og visning. - + Document font family Dokumentets skriftfamilie - - - - + + + + Applies to both document editor and viewer. Gjelder både redigerings- og visningsvindu. - + Document font size Dokumentets skriftstørrelse - + Emphasise partition and chapter labels Fremhev filnavn for inndeling og kapitler - + Makes them stand out in the project tree. Får dem til å skille seg ut i prosjekttreet. - + Show full path in document header Vis full prosjektbane i dokumenthoder - + Add the parent folder names to the header. Legger til mappene foran dokumentets navn. - + Include project notes in status bar word count Inkluder prosjektnotater i antallet ord i statuslinjen - + Auto Save Automatisk lagring - + Save document interval Intervall for lagring av dokument - + How often the document is automatically saved. Hvor ofte dokumentet lagres automatisk. - - + + seconds sekunder - + Save project interval Intervall for lagring av prosjekt - + How often the project is automatically saved. Hvor ofte prosjektet lagres automatisk. - + Project Backup Sikkerhetskopi - + Browse Bla - + Backup storage location Filbane for sikkerhetskopi - - + + Path: {0} Filbane: {0} - + Run backup when the project is closed Lag sikkerhetskopi når prosjektet lukkes - + Can be overridden for individual projects in Project Settings. Kan overstyres fra individuelle prosjektinnstillinger. - + Ask before running backup Spør før sikkerhetskopi tas - + If off, backups will run in the background. Hvis avslått, tas sikkerhetskopi automatisk. - + Session Timer Sesjons-klokke - + Pause the session timer when not writing Sett klokka på pause når du er inaktiv - + Also pauses when the application window does not have focus. Pauses også når du ikke jobber i applikasjonens vindu. - + Editor inactive time before pausing timer Tid uten skriving før klokka settes på pause - + User activity includes typing and changing the content. Dette måler kun endringer i teksteditoren. - + minutes minutter - + Writing Skriving - + Text Flow Tekstflyt - + Maximum text width in "Normal Mode" Maks tekstbredde i "Normal-modus" - + Set to 0 to disable this feature. Sett til 0 for å deaktivere denne funksjonen. - - - - + + + + px px - + Maximum text width in "Focus Mode" Maks tekstbredde i "Fokus-modus" - + The maximum width cannot be disabled. Denne maks-bredden kan ikke deaktiveres. - + Hide document footer in "Focus Mode" Gjem dokumentets bunnlinje i "Fokus-modus" - + Hide the information bar in the document editor. Skjul informasjonslinjen i dokumenteditoren. - + Justify the text margins Juster tekstmarginer - + Minimum text margin Minimum tekstmargin - + Tab width Tabulatorens bredde - + The width of a tab key press in the editor and viewer. Hvor langt tabulatoren hopper i editor og visning. - + Text Editing Redigering av tekst - + Spell check language Språk for stavekontroll - + Available languages are determined by your system. Tilgjengelige språk hentes fra operativystemet ditt. - + Auto-select word under cursor Auto-velg ord under markør - + Apply formatting to word under cursor if no selection is made. Hvis ingen tekst er valgt, formatter ordet hvor markøren står. - + Show tabs and spaces Synlige tabulatorer og mellomrom - + Show line endings Synlige linjeender - + Editor Scrolling Tekstbehandler rulling - + Scroll past end of the document Tillat å rulle forbi slutten av dokumentet - + Also centres the cursor when scrolling. Sentrerer også markøren når man ruller. - + Typewriter style scrolling when you type Skrivemaskin-liknende rulling mens du skriver - + Keeps the cursor at a fixed vertical position. Holder markøren på samme sted vertikalt. - + Minimum position for Typewriter scrolling Minste avstand for skrivemaskin-rulling - + Percentage of the editor height from the top. I prosent fra toppen av editor-vinduet. - + Text Highlighting Fremheving - + Highlight text wrapped in quotes Fremhev tekst mellom sitattegn - - - + + + Applies to the document editor only. Gjelder bare for redigeringsvindu. - + Allow open-ended single quotes Tillat enkle sitattegn som ikke lukkes - + Highlight single-quoted line with no closing quote. Fremhev sitater som ikke er lukket i samme avsnitt. - + Allow open-ended double quotes Tillat doble sitattegn som ikke lukkes - + Highlight double-quoted line with no closing quote. Fremhev sitater som ikke er lukket i samme avsnitt. - + Add highlight colour to emphasised text Fremhev formattert tekst - + Highlight multiple or trailing spaces Fremhev flere eller etterfølgende mellomrom - + Text Automation Tekstautomatisering - + Auto-replace text as you type Erstatt mens du skriver - + Allow the editor to replace symbols as you type. Erstatt symboler mens du skriver. - + Auto-replace single quotes Erstatt enkle sitattegn - - + + Try to guess which is an opening or a closing quote. Prøv å gjette om det er et åpne- eller lukketegn. - + Auto-replace double quotes Erstatt doble sitattegn - + Auto-replace dashes Erstatt bindestreker - + Double and triple hyphens become short and long dashes. To og tre bindestreker erstattes med kort og lang bindestrek. - + Auto-replace dots Erstatt tre punktum - + Three consecutive dots become ellipsis. Tre punktum på rad erstattes med ellipsis. - + Insert non-breaking space before Sett inn hardt mellomrom foran - + Automatically add space before any of these symbols. Legg til mellomrom automatisk foran disse tegnene. - + Insert non-breaking space after Sett inn hardt mellomrom etter - + Automatically add space after any of these symbols. Legg til mellomrom automatisk etter disse tegnene. - + Use thin space instead Bruk tynt mellomrom istedet - + Inserts a thin space instead of a regular space. Sett inn et tynt mellomrom istedenfor et vanlig et. - + Quotation Style Sitattegn - + Single quote open style Enkelt sitat, venstre side - + The symbol to use for a leading single quote. Symbol for enkelt sitattegn før et sitat. - + Single quote close style Enkelt sitat, høyre side - + The symbol to use for a trailing single quote. Symbol for enkelt sitattegn etter et sitat. - + Double quote open style Dobbelt sitat, venstre side - + The symbol to use for a leading double quote. Symbol for dobbelt sitattegn før et sitat. - + Double quote close style Dobbelt sitat, høyre side - + The symbol to use for a trailing double quote. Symbol for dobbelt sitattegn etter et sitat. - + Backup Directory Mappe for sikkerhetskopi @@ -2995,27 +2995,27 @@ GuiProjectSearch - + Project Search Søk i prosjektet - + Case Sensitive Skill store/små bokstaver - + Whole Words Only Kun hele ord - + RegEx Mode RegEx-modus - + Search for Søketekst @@ -3023,28 +3023,28 @@ GuiProjectSettings - - + + Project Settings Prosjektinnstillinger - + Settings Innstillinger - + Status Status - + Importance Viktighet - + Auto-Replace Autoerstatt @@ -3052,47 +3052,47 @@ GuiProjectToolBar - + Project Content Prosjektets innhold - + Quick Links Hurtiglenker - + Move Up Flytt opp - + Move Down Flytt ned - + Add Item Legg til element - + Expand All Utvid alle - + Collapse All Lukk alle - + Empty Trash Tøm papirkurven - + More Options Flere valg @@ -3100,118 +3100,118 @@ GuiProjectTree - + Active Aktiv - + Inactive Inaktiv - + Permanently delete {0} file(s) from Trash? Vil du slette {0} filer i papirkurven for godt? - + Did not find anywhere to add the file or folder! Fant ikke noe sted å legge til filen eller mappen! - + Cannot add new files or folders to the Trash folder. Kan ikke legge til nye filer eller mapper i papirkurvmappen. - + New Note Nytt notat - + New Chapter Nytt kapittel - + New Scene Ny scene - + New Document Nytt dokument - + New Folder Ny mappe - + There is currently no Trash folder in this project. Det er for øyeblikket ingen papirkurv i dette prosjektet. - + The Trash folder is already empty. Papirkurven er allerede tom. - + Move '{0}' to Trash? Vil du flytte filen "{0}" til søpla? - + Root folders can only be deleted when they are empty. Rotmapper kan bare slettes når de er tomme. - + Permanently delete '{0}'? Slette filen "{0}" for godt? - + Drag and drop is only allowed for single items, non-root items, or multiple items with the same parent. Dra og slipp er bare tillatt for enkeltelementer, ikke hovedmapper, eller elementer under samme mappe eller dokument. - + No documents selected for merging. Ingen dokumenter er valgt for sammenslåing. - + Merged Sammenslått - - + + Could not write document content. Kan ikke skrive til dokumentet. - + Do you want to duplicate this document? Vil du duplisere dette dokumentet? - + Do you want to duplicate this item and all child items? Vil du duplisere dette elementet og alle underelementer? - + Could not duplicate all items. Kunne ikke duplisere alle elementer. - + There is nowhere to add item with name '{0}'. Fant ikke noe sted å legge til enheten med navn {0}'. @@ -3219,42 +3219,42 @@ GuiSideBar - + Project Tree View Prosjektoversikt - + Novel Tree View Romanoversikt - + Project Search Søk i prosjektet - + Novel Outline View Disposisjon - + Build Manuscript Bygg manuskript - + Novel Details Roman-detaljer - + Writing Statistics Statistikk - + Settings Oppsett @@ -3262,37 +3262,37 @@ GuiWelcome - + Welcome Velkommen - + List Liste - + New Nytt - + Browse Bla gjennom - + Cancel Avbryt - + Create Opprett - + Open Åpne @@ -3300,33 +3300,33 @@ GuiWordList - - + + Project Word List Prosjektets ordliste - + Import words from text file Importer ord fra tekstfil - + Export words to text file Eksporter ord til tekstfil - + Note: The import file must be a plain text file with UTF-8 or ASCII encoding. Merk: Importfilen må være en standard tekstfil med UTF-8 eller ASCII-koding. - + Import File Importer fil - + Export File Eksporter fil @@ -3334,147 +3334,147 @@ GuiWritingStats - + Writing Statistics Statistikk - + Session Start Starttid - + Length Lengde - + Idle Inaktiv - + Words Ord - + Histogram Histogram - + Sum Totals Totalsummer - + Total Time: Totaltid: - + Idle Time: Inaktiv tid: - + Filtered Time: Filtrert tid: - + Novel Word Count: Ord i roman: - + Notes Word Count: Ord i notater: - + Total Word Count: Ord totalt: - + Filters Filtre - + Count novel files Tell i romanfiler - + Count note files Tell i notatfiler - + Hide zero word count Skjul null-verdier - + Hide negative word count Skjul negative verdier - + Group entries by day Samle rader per dag - + Show idle time Vis inaktiv som tid - + Word count cap for the histogram Maks antall ord for histogram - + Save As Lagre som - + JSON Data File (.json) JSON-format (.json) - + CSV Data File (.csv) CSV-format (.csv) - + JSON Data File JSON-format - + CSV Data File CSV-format - + Save Data As Lagre data som - + {0} file successfully written to: {0}-filen ble skrevet til: - + Failed to write {0} file. Kunne ikke skrive {0}-filen. @@ -3482,153 +3482,153 @@ NWProject - + Could not delete document file. Kunne ikke slette dokumentets fil. - + Not a known project file format. Ikke et kjent prosjektfilformat. - + Project file not found. Prosjektfilen finnes ikke. - + Failed to open project. Kunne ikke åpne prosjektet. - + Unknown Ukjent - + Project file does not appear to be a novelWriterXML file. Prosjektfilen later ikke til å være en novelWriterXML-fil. - + Unknown or unsupported novelWriter project file format. The project cannot be opened by this version of novelWriter. The file was saved with novelWriter version {0}. Prosjektfilen har et ukjent eller ikke støttet format, og kan ikke åpnes med denne versjonen av novelWriter. Prosjektet ble lagret av novelWriter versjon {0}. - + Failed to parse project xml. Kunne ikke lese prosjektets xml-data. - + The file format of your project is about to be updated. If you proceed, older versions of novelWriter will no longer be able to open this project. Continue? Filformatet til prosjektet ditt er i ferd med å bli oppdatert. Hvis du fortsetter, vil ikke eldre versjoner av novelWriter lenger kunne åpne dette prosjektet. Fortsette? - + This project was saved by a newer version of novelWriter, version {0}. This is version {1}. If you continue to open the project, some attributes and settings may not be preserved, but the overall project should be fine. Continue opening the project? Dette prosjektet ble lagret av en nyere versjon av novelWriter, versjon {0}. Dette er versjon {1}. Hvis du ønsker å fortsette med å åpne prosjektet, kan noen av innstillingene bli borte, men selve prosjektet vil være i orden. Vil du fortsatt åpne prosjektet? - + Recovered Gjennopprettet - + Found {0} orphaned file(s) in the project. {1} file(s) were recovered. {0} foreldreløse fil(er) ble funnet i prosjektet. {1} fil(er) ble gjenopprettet. - + Opened Project: {0} Åpnet prosjekt: {0} - + There is no project open. Det er ikke noe prosjekter åpent. - + Failed to save project. Kunne ikke lagre prosjektet. - + Saved Project: {0} Lagret prosjekt: {0} - + Backing up project ... Lager sikkerhetskopi ... - + Cannot backup project because no project name is set. Please set a Project Name in Project Settings. Kan ikke ta sikkerhetskopi av prosjektet da prosjektnavn ikke er satt. Du må først sette et prosjektnavn i Prosjektinnstillinger. - + Could not create backup folder. Kunne ikke lage mappe til sikkerhetskopi. - + Created a backup of your project of size {0}B. Opprettet en sikkerhetskopi av prosjektet med størrelse {0}B. - + Path: {0} Filbane: {0} - + Could not write backup archive. Kunne ikke lage sikkerhetskopi. - + Project backed up to '{0}' Sikkerhetskopi skrevet til '{0}' - - + + New Ny - + Note Notat - + Draft Utkast - + Finished Ferdig - + Minor Mindre - + Major Større - + Main Hoved @@ -3636,7 +3636,7 @@ NovelSelector - + All Novel Folders Alle roman-mapper @@ -3644,89 +3644,89 @@ ProjectBuilder - + The target folder is not empty. Please choose another folder. Den valgte mappen er ikke tom. Vennligst velg en annen mappe. - + An error occurred while trying to create the project. Det oppsto en feil under forsøk på å opprette prosjektet. - + New Project Nytt prosjekt - + Title Page Tittelside - + By Av - + Summary of the chapter. Sammendrag av kapittelet. - + Summary of the scene. Sammendrag av scenen. - + A short description. En kort beskrivelse. - + Chapter {0} Kapittel {0} - - + + Scene {0} Scene {0} - + Main Plot Hovedplott - + Protagonist Protagonist - + Main Location Hovedlokasjon - - + + The target folder already exists. Please choose another folder. Den valgte mappen finnes allerede. Vennligst velg en annen mappe. - + Could not copy project files. Kunne ikke kopiere prosjektfiler. - + Failed to create a new example project. Kunne ikke lage nytt eksempel-prosjekt. - + Failed to create a new example project. Could not find the necessary files. They seem to be missing from this installation. Kunne ikke lage nytt eksempel-prosjekt. Kunne ikke finne de nødvendige filene. De ser ut til å mangle i denne installasjonen. @@ -3734,7 +3734,7 @@ QDialogButtonBox - + OK OK @@ -3742,27 +3742,27 @@ QGnomeTheme - + &OK &OK - + &Save &Lagre - + &Cancel &Avbryt - + &Close &Lukk - + Close without Saving Lukk uten å lagre @@ -3770,92 +3770,92 @@ QPlatformTheme - + OK OK - + Save Lagre - + Save All Lagre alle - + Open Åpne - + &Yes &Ja - + Yes to &All Ja til &alle - + &No &Nei - + N&o to All N&ei til alle - + Abort Avbryt - + Retry Prøv igjen - + Ignore Ignorer - + Close Lukk - + Cancel Avbryt - + Discard Forkast - + Help Hjelp - + Apply Anvend - + Reset Nullstill - + Restore Defaults Gjennopprett standard @@ -3863,58 +3863,58 @@ QWizard - + Go Back Gå tilbake - + < &Back < &Tilbake - + Continue Fortsett - + &Next &Neste - + &Next > &Neste > - + Commit Gjennomfør - + Done Ferdig - + &Finish &Fullfør - - + + Cancel Avbryt - + Help Hjelp - + &Help &Hjelp @@ -3922,17 +3922,17 @@ SharedData - + novelWriter Project File or Zip File novelWriter prosjektfil eller Zip-fil - + novelWriter Project File novelWriter prosjektfil - + Open Project Åpne prosjekt @@ -3940,42 +3940,42 @@ VersionInfoWidget - + Latest Version: {0} Siste versjon: {0} - + Checking ... Sjekker ... - + Download from {0} Last ned fra {0} - + Version Versjon - + Released on Utgitt den - + Release Notes Lanseringsnotater - + Check Now Sjekk nå - + Failed Mislyktes @@ -3983,57 +3983,57 @@ _ContentsPage - + Table of Contents Innholdsfortegnelse - + Title Tittel - + Words Ord - + Pages Sider - + Page Side - + Progress Fremdrift - + Words per page Ord per side - + First page offset Første side forskjøvet - + Chapters on odd pages Kapittel på oddetall-sider - + Untitled Uten tittel - + END SLUTT @@ -4041,32 +4041,32 @@ _DetailsWidget - + Setting Innstilling - + Value Verdi - + Name Navn - + Selection Utvalg - + Title Tittel - + Hidden Skjult @@ -4074,37 +4074,37 @@ _FilterTab - + Included in manuscript Inkludert i manuskript - + Excluded from manuscript Ekskludert fra manuskript - + Always included Alltid inkludert - + Always excluded Alltid ekskludert - + Reset to default Tilbakestill til standard - + Mark selection as Merk utvalg som - + Select Root Folders Velg hovedmapper @@ -4112,22 +4112,22 @@ _GuiAlert - + Information Informasjon - + Warning Advarsel - + Error Feil - + Question Spørsmål @@ -4135,93 +4135,93 @@ _HeadingsTab - + Hide Skjul - - + + Editing: {0} Redigerer: {0} - - + + None Ingen - + Title Tittel - + Chapter Number Kapittelnummer - + Chapter Number (Word) Kapittelnummer (som ord) - + Chapter Number (Upper Case Roman) Kapittelnummer (store romertall) - + Chapter Number (Lower Case Roman) Kapittelnummer (små romertall) - + Scene Number (In Chapter) Scenenummer (i kapittel) - + Scene Number (Absolute) Scenenummer (absolutt) - + Point of View Character Synsvinkel-karakter - + Focus Character Fokus-karakter - + Insert Sett inn - + Apply Anvend - + Additional Styling Andre innstillinger - - - + + + Centre Sentrer - - - + + + Page Break Sideskift @@ -4229,117 +4229,117 @@ _NewProjectForm - + Required Påkrevd - + Optional Valgfritt - + Create a fresh project Opprett et nytt prosjekt - + Create an example project Opprett et eksempelprosjekt - + Copy an existing project Kopier et eksisterende prosjekt - + Project Name Prosjektnavn - + Author Forfatter - + Project Path Filbane - + Prefill Project Forhåndsfyll prosjektet - + Set to 0 to only add scenes Satt til 0 for bare å legge til scener - + Add {0} chapter documents Legg til {0} kapitteldokumenter - + Add {0} scene documents (to each chapter) Legg til {0} scenedokumenter (til hvert kapittel) - + Add a folder for plot notes Legg til en mappe for plott-notater - + Add a folder for character notes Legg til en mappe for karakterer - + Add a folder for location notes Legg til en mappe for lokasjoner - + Add example notes to the above Lag eksempelfiler til ovennevnte - + Chapters and Scenes Kapittel og scener - + Project Notes Prosjektnotater - + Create New Project Opprett nytt prosjekt - + Select Project Folder Velg prosjektmappe - + Fresh Project Nytt prosjekt - + Example Project Eksempelprosjekt - + Template: {0} Mal: {0} @@ -4347,7 +4347,7 @@ _NewProjectPage - + A project name is required. Vennligst oppgi et prosjektnavn. @@ -4355,27 +4355,27 @@ _OpenProjectPage - + The project path is not reachable. Prosjektets bane er ikke tilgjengelig. - + Path Filbane - + Remove '{0}' from the recent projects list? The project files will not be deleted. Vil du fjerne {0} fra listen over tidligere åpnede prosjekter? Selve prosjektfilene blir ikke slettet. - + Open Project Åpne prosjekt - + Remove Project Fjern prosjekt @@ -4383,54 +4383,54 @@ _OverviewPage - + Project Prosjekt - - + + Name Navn - + Revisions Revisjoner - + Editing Time Redigeringstid - - + + Word Count Antall ord - + In Novels I romaner - + In Notes I notater - + Selected Novel Velg roman - + Chapters Kapitler - + Scenes Scener @@ -4438,27 +4438,27 @@ _PreviewWidget - + Press the "Preview" button to generate ... Trykk på "Forhåndsvisning"-knappen for å generere ... - + Processing ... Behandler ... - + Done Ferdig - + Unknown Ukjent - + Built Bygget @@ -4466,12 +4466,12 @@ _ProjectListModel - + Word Count Antall ord - + Last Opened Sist åpnet @@ -4479,27 +4479,27 @@ _ReplacePage - + Text Auto-Replace for Preview and Build Erstatt automatisk for forhåndsvisning og manuskript - + Keyword Nøkkelord - + Replace With Erstatt med - + Select item to edit Velg enhet å redigere - + Save Lagre @@ -4507,49 +4507,49 @@ _SettingsPage - + Project name Prosjektnavn - + Changing this will affect the backup path. Å endring denne vil påvirke banen til sikkerhetskopier. - + Author(s) Forfatter(e) - - + + Only used when building the manuscript. Brukes kun ved bygging av manuskript. - + Project language Prosjektets språk - + Default Ingen valg - + Spell check language Språk for stavekontroll - - + + Overrides main preferences. Overstyrer valg i innstillinger. - + Disable backup on close Ikke lag sikkerhetskopi når prosjektet lukkes @@ -4557,59 +4557,59 @@ _StatsWidget - - + + Words Ord - - + + Characters Tegn - + Words in Headings Ord i overskrifter - + Words in Text Ord i tekst - + Headings Overskrifter - + Paragraphs Avsnitt - + Characters in Headings Tegn i overskrifter - + Characters in Text Tegn i tekst - + Characters, No Spaces Tegn, utenom mellomrom - + Characters in Headings, No Spaces Tegn i overskrifter, utenom mellomrom - + Characters in Text, No Spaces Tegn i tekst, utenom mellomrom @@ -4617,67 +4617,67 @@ _StatusPage - + Novel Document Status Levels Statusnivåer i roman-filer - + Project Note Importance Levels Viktighetsnivåer i notatfiler - + Label Navn - + Usage Bruk - + Select item to edit Velg enhet å redigere - + Colour Farge - + Save Lagre - + Select Colour Velg farge - + New Item Legg til - + Cannot delete a status item that is in use. Kan ikke slette status som er i bruk. - + Not in use Ikke i bruk - + Used once Brukt ett sted - + Used by {0} items Brukt {0} steder @@ -4685,128 +4685,128 @@ _TreeContextMenu - + Empty Trash Tøm papirkurven - + Rename Endre navn - + Open Document Åpne dokument - + View Document Vis dokument - + Create New ... Opprett ny ... - + Rename to Heading Endre navn til overskriften - + Set Active to ... Sett aktiv til ... - + Toggle Active Aktiver/deaktiver - + Set Status to ... Sett status til ... - - + + Manage Labels ... Administrer etiketter ... - + Set Importance to ... Sett viktighetsnivå til ... - + Transform ... Endre ... - - - - + + + + Convert to {0} Konverter til {0} - + Merge Child Items into Self Lim inn underelementer i dette dokumentet - + Merge Child Items into New Lim inn underelementer i nytt dokument - + Merge Documents in Folder Slå sammen dokumenter i mappen - + Split Document by Headings Splitt dokumentet etter overskrifter - + Expand All Utvid alle - + Collapse All Lukk alle - + Duplicate Dupliser - - + + Delete Permanently Slett permanent - - + + Move to Trash Flytt til papirkurv - + Move {0} items to Trash? Vil du flytte {0} filer til papirkurven? - + Do you want to convert the folder to a {0}? This action cannot be reversed. Vil du konvertere mappen til et {0}? Denne handlingen kan ikke angres. @@ -4814,7 +4814,7 @@ _UpdatableMenu - + From Template Fra mal @@ -4822,12 +4822,12 @@ _ViewPanelBackRefs - + Document Dokument - + First Heading Første overskrift @@ -4835,27 +4835,27 @@ _ViewPanelKeyWords - + Tag Knagg - + Importance Viktighet - + Document Dokument - + Heading Overskrift - + Short Description Kort beskrivelse diff --git a/novelwriter/__init__.py b/novelwriter/__init__.py index 5bd809a8..02d5a82e 100644 --- a/novelwriter/__init__.py +++ b/novelwriter/__init__.py @@ -23,16 +23,21 @@ along with this program. If not, see . """ from __future__ import annotations -import sys import getopt import logging +import sys + +from typing import TYPE_CHECKING from PyQt5.QtWidgets import QApplication, QErrorMessage -from novelwriter.error import exceptionHandler, logException from novelwriter.config import Config +from novelwriter.error import exceptionHandler, logException from novelwriter.shared import SharedData +if TYPE_CHECKING: # pragma: no cover + from novelwriter.guimain import GuiMain + # Package Meta # ============ @@ -42,9 +47,9 @@ __license__ = "GPLv3" __author__ = "Veronica Berglyd Olsen" __maintainer__ = "Veronica Berglyd Olsen" __email__ = "code@vkbo.net" -__version__ = "2.4rc1" -__hexversion__ = "0x020400c1" -__date__ = "2024-04-06" +__version__ = "2.5a1" +__hexversion__ = "0x020500a1" +__date__ = "2024-04-13" __status__ = "Stable" __domain__ = "novelwriter.io" @@ -60,7 +65,7 @@ CONFIG = Config() SHARED = SharedData() -def main(sysArgs: list | None = None): +def main(sysArgs: list | None = None) -> GuiMain | None: """Parse command line, set up logging, and launch main GUI.""" if sysArgs is None: sysArgs = sys.argv[1:] @@ -239,4 +244,6 @@ def main(sysArgs: list | None = None): sys.exit(nwApp.exec()) + return None + # END Function main diff --git a/novelwriter/common.py b/novelwriter/common.py index c148e75d..ff36bae2 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -449,7 +449,7 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None: indentations = ["\n"] - def indentChildren(elem, level): + def indentChildren(elem: ET.Element, level: int) -> None: chLevel = level + 1 try: chIndent = indentations[chLevel] @@ -472,6 +472,8 @@ def xmlIndent(tree: ET.Element | ET.ElementTree) -> None: if last is not None: last.tail = indentations[level] + return + if len(tree): indentChildren(tree, 0) tree.tail = "\n" @@ -534,8 +536,9 @@ class NWConfigParser(ConfigParser): helper functions, and support for lists. """ - def __init__(self): + def __init__(self) -> None: super().__init__() + return def rdStr(self, section: str, option: str, default: str) -> str: """Read string value.""" diff --git a/novelwriter/config.py b/novelwriter/config.py index 1f817e0f..e9fab918 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -390,6 +390,10 @@ class Config: """Un-scale fixed gui sizes by the screen scale factor.""" return int(value/self.guiScale) + def homePath(self) -> Path: + """The user's home folder.""" + return self._homePath + def dataPath(self, target: str | None = None) -> Path: """Return a path in the data folder.""" if isinstance(target, str): diff --git a/novelwriter/constants.py b/novelwriter/constants.py index a1e2b84a..48487d77 100644 --- a/novelwriter/constants.py +++ b/novelwriter/constants.py @@ -25,7 +25,7 @@ from __future__ import annotations from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP -from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline +from novelwriter.enum import nwBuildFmt, nwItemClass, nwItemLayout, nwOutline, nwStatusShape def trConst(text: str) -> str: @@ -268,6 +268,34 @@ class nwLabels: nwBuildFmt.J_HTML: ".json", nwBuildFmt.J_NWD: ".json", } + SHAPES_PLAIN = { + nwStatusShape.SQUARE: QT_TRANSLATE_NOOP("Constant", "Square"), + nwStatusShape.TRIANGLE: QT_TRANSLATE_NOOP("Constant", "Triangle"), + nwStatusShape.NABLA: QT_TRANSLATE_NOOP("Constant", "Nabla"), + nwStatusShape.DIAMOND: QT_TRANSLATE_NOOP("Constant", "Diamond"), + nwStatusShape.PENTAGON: QT_TRANSLATE_NOOP("Constant", "Pentagon"), + nwStatusShape.HEXAGON: QT_TRANSLATE_NOOP("Constant", "Hexagon"), + nwStatusShape.STAR: QT_TRANSLATE_NOOP("Constant", "Star"), + nwStatusShape.PACMAN: QT_TRANSLATE_NOOP("Constant", "Pacman"), + } + SHAPES_CIRCLE = { + nwStatusShape.CIRCLE_Q: QT_TRANSLATE_NOOP("Constant", "1/4 Circle"), + nwStatusShape.CIRCLE_H: QT_TRANSLATE_NOOP("Constant", "Half Circle"), + nwStatusShape.CIRCLE_T: QT_TRANSLATE_NOOP("Constant", "3/4 Circle"), + nwStatusShape.CIRCLE: QT_TRANSLATE_NOOP("Constant", "Full Circle"), + } + SHAPES_BARS = { + nwStatusShape.BARS_1: QT_TRANSLATE_NOOP("Constant", "1 Bar"), + nwStatusShape.BARS_2: QT_TRANSLATE_NOOP("Constant", "2 Bars"), + nwStatusShape.BARS_3: QT_TRANSLATE_NOOP("Constant", "3 Bars"), + nwStatusShape.BARS_4: QT_TRANSLATE_NOOP("Constant", "4 Bars"), + } + SHAPES_BLOCKS = { + nwStatusShape.BLOCK_1: QT_TRANSLATE_NOOP("Constant", "1 Block"), + nwStatusShape.BLOCK_2: QT_TRANSLATE_NOOP("Constant", "2 Blocks"), + nwStatusShape.BLOCK_3: QT_TRANSLATE_NOOP("Constant", "3 Blocks"), + nwStatusShape.BLOCK_4: QT_TRANSLATE_NOOP("Constant", "4 Blocks"), + } FILE_FILTERS = { "*.txt": QT_TRANSLATE_NOOP("Constant", "Text files"), "*.md": QT_TRANSLATE_NOOP("Constant", "Markdown files"), diff --git a/novelwriter/core/buildsettings.py b/novelwriter/core/buildsettings.py index 1bd03485..b68dd97d 100644 --- a/novelwriter/core/buildsettings.py +++ b/novelwriter/core/buildsettings.py @@ -56,13 +56,13 @@ SETTINGS_TEMPLATE = { "headings.fmtChapter": (str, nwHeadFmt.TITLE), "headings.fmtUnnumbered": (str, nwHeadFmt.TITLE), "headings.fmtScene": (str, "* * *"), - "headings.fmtHardScene": (str, ""), + "headings.fmtAltScene": (str, ""), "headings.fmtSection": (str, ""), "headings.hideTitle": (bool, False), "headings.hideChapter": (bool, False), "headings.hideUnnumbered": (bool, False), "headings.hideScene": (bool, False), - "headings.hideHardScene": (bool, False), + "headings.hideAltScene": (bool, False), "headings.hideSection": (bool, True), "headings.centerTitle": (bool, True), "headings.centerChapter": (bool, False), @@ -110,7 +110,7 @@ SETTINGS_LABELS = { "headings.fmtChapter": QT_TRANSLATE_NOOP("Builds", "Chapter Format"), "headings.fmtUnnumbered": QT_TRANSLATE_NOOP("Builds", "Unnumbered Format"), "headings.fmtScene": QT_TRANSLATE_NOOP("Builds", "Scene Format"), - "headings.fmtHardScene": QT_TRANSLATE_NOOP("Builds", "Hard Scene Format"), + "headings.fmtAltScene": QT_TRANSLATE_NOOP("Builds", "Alt. Scene Format"), "headings.fmtSection": QT_TRANSLATE_NOOP("Builds", "Section Format"), "text.grpContent": QT_TRANSLATE_NOOP("Builds", "Text Content"), @@ -178,7 +178,7 @@ class BuildSettings: def __init__(self) -> None: self._name = "" self._uuid = str(uuid.uuid4()) - self._path = Path.home() + self._path = CONFIG.homePath() self._build = "" self._order = 0 self._format = nwBuildFmt.ODT @@ -220,7 +220,7 @@ class BuildSettings: """The last used build path.""" if self._path.is_dir(): return self._path - return Path.home() + return CONFIG.homePath() @property def lastBuildName(self) -> str: @@ -297,7 +297,7 @@ class BuildSettings: if isinstance(path, Path) and path.is_dir(): self._path = path else: - self._path = Path.home() + self._path = CONFIG.homePath() self._changed = True return @@ -382,7 +382,7 @@ class BuildSettings: postponed = [] - def allowRoot(rHandle): + def allowRoot(rHandle: str | None) -> None: if rHandle in postponed and rHandle in result and rHandle is not None: result[rHandle] = (True, FilterMode.ROOT) postponed.remove(rHandle) diff --git a/novelwriter/core/coretools.py b/novelwriter/core/coretools.py index b8fc5ba1..925db901 100644 --- a/novelwriter/core/coretools.py +++ b/novelwriter/core/coretools.py @@ -104,7 +104,7 @@ class DocMerger: docText = self._project.storage.getDocumentText(srcHandle).rstrip("\n") if addComment: docInfo = srcItem.describeMe() - docSt, _ = srcItem.getImportStatus(incIcon=False) + docSt, _ = srcItem.getImportStatus() cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n" docText = cmtLine + docText diff --git a/novelwriter/core/docbuild.py b/novelwriter/core/docbuild.py index 3c1d6951..0c2ab461 100644 --- a/novelwriter/core/docbuild.py +++ b/novelwriter/core/docbuild.py @@ -306,8 +306,8 @@ class NWBuildDocument: self._build.getBool("headings.hideScene") ) bldObj.setHardSceneFormat( - self._build.getStr("headings.fmtHardScene"), - self._build.getBool("headings.hideHardScene") + self._build.getStr("headings.fmtAltScene"), + self._build.getBool("headings.hideAltScene") ) bldObj.setSectionFormat( self._build.getStr("headings.fmtSection"), diff --git a/novelwriter/core/item.py b/novelwriter/core/item.py index a52b8a36..35dba0fe 100644 --- a/novelwriter/core/item.py +++ b/novelwriter/core/item.py @@ -25,7 +25,7 @@ from __future__ import annotations import logging -from typing import TYPE_CHECKING, Any, Literal, overload +from typing import TYPE_CHECKING, Any from PyQt5.QtGui import QIcon @@ -308,25 +308,15 @@ class NWItem: return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, "")) - @overload # pragma: no cover - def getImportStatus(self, incIcon: Literal[True] = True) -> tuple[str, QIcon]: - pass - - @overload # pragma: no cover - def getImportStatus(self, incIcon: Literal[False]) -> tuple[str, None]: - pass - - def getImportStatus(self, incIcon=True): + def getImportStatus(self) -> tuple[str, QIcon]: """Return the relevant importance or status label and icon for the current item based on its class. """ if self.isNovelLike(): - stName = self._project.data.itemStatus.name(self._status) - stIcon = self._project.data.itemStatus.icon(self._status) if incIcon else None + entry = self._project.data.itemStatus[self._status] else: - stName = self._project.data.itemImport.name(self._import) - stIcon = self._project.data.itemImport.icon(self._import) if incIcon else None - return stName, stIcon + entry = self._project.data.itemImport[self._import] + return entry.name, entry.icon ## # Checker Methods diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 4a399c24..08a3dd11 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -26,33 +26,32 @@ from __future__ import annotations import json import logging +from collections.abc import Iterable from enum import Enum +from functools import partial +from pathlib import Path from time import time from typing import TYPE_CHECKING -from pathlib import Path -from functools import partial -from collections.abc import Iterable from PyQt5.QtCore import QCoreApplication from novelwriter import CONFIG, SHARED, __version__, __hexversion__ -from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout -from novelwriter.error import logException -from novelwriter.constants import trConst, nwLabels -from novelwriter.core.tree import NWTree -from novelwriter.core.index import NWIndex -from novelwriter.core.options import OptionState -from novelwriter.core.storage import NWStorage, NWStorageOpen -from novelwriter.core.sessions import NWSessionLog -from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState -from novelwriter.core.projectdata import NWProjectData from novelwriter.common import ( checkStringNone, formatInt, formatTimeStamp, getFileSize, hexToInt, makeFileNameSafe, minmax ) +from novelwriter.constants import trConst, nwLabels +from novelwriter.core.index import NWIndex +from novelwriter.core.options import OptionState +from novelwriter.core.projectdata import NWProjectData +from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState +from novelwriter.core.sessions import NWSessionLog +from novelwriter.core.storage import NWStorage, NWStorageOpen +from novelwriter.core.tree import NWTree +from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout +from novelwriter.error import logException if TYPE_CHECKING: # pragma: no cover from novelwriter.core.item import NWItem - from novelwriter.core.status import NWStatus logger = logging.getLogger(__name__) @@ -461,14 +460,14 @@ class NWProject: def setDefaultStatusImport(self) -> None: """Set the default status and importance values.""" - self._data.itemStatus.write(None, self.tr("New"), (100, 100, 100)) - self._data.itemStatus.write(None, self.tr("Note"), (200, 50, 0)) - self._data.itemStatus.write(None, self.tr("Draft"), (200, 150, 0)) - self._data.itemStatus.write(None, self.tr("Finished"), (50, 200, 0)) - self._data.itemImport.write(None, self.tr("New"), (100, 100, 100)) - self._data.itemImport.write(None, self.tr("Minor"), (200, 50, 0)) - self._data.itemImport.write(None, self.tr("Major"), (200, 150, 0)) - self._data.itemImport.write(None, self.tr("Main"), (50, 200, 0)) + self._data.itemStatus.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0) + self._data.itemStatus.add(None, self.tr("Note"), (200, 50, 0), "SQUARE", 0) + self._data.itemStatus.add(None, self.tr("Draft"), (200, 150, 0), "SQUARE", 0) + self._data.itemStatus.add(None, self.tr("Finished"), (50, 200, 0), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("New"), (100, 100, 100), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("Minor"), (200, 50, 0), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("Major"), (200, 150, 0), "SQUARE", 0) + self._data.itemImport.add(None, self.tr("Main"), (50, 200, 0), "SQUARE", 0) return def setProjectLang(self, language: str | None) -> None: @@ -491,14 +490,6 @@ class NWProject: self.setProjectChanged(True) return - def setStatusColours(self, new: list[dict], deleted: list[str]) -> bool: - """Update the list of novel file status flags.""" - return self._setStatusImport(new, deleted, self._data.itemStatus) - - def setImportColours(self, new: list[dict], deleted: list[str]) -> bool: - """Update the list of note file importance flags.""" - return self._setStatusImport(new, deleted, self._data.itemImport) - def setProjectChanged(self, status: bool) -> bool: """Toggle the project changed flag, and propagate the information to the GUI statusbar. @@ -584,28 +575,6 @@ class NWProject: # Internal Functions ## - def _setStatusImport(self, new: list[dict], delete: list[str], target: NWStatus) -> bool: - """Update the list of novel file status or importance flags, and - delete those that have been requested deleted. - """ - if not (new or delete): - return False - - order = [] - for entry in new: - key = entry.get("key", None) - name = entry.get("name", "") - cols = entry.get("cols", (100, 100, 100)) - if name: - order.append(target.write(key, name, cols)) - - for key in delete: - target.remove(key) - - target.reorder(order) - - return True - def _loadProjectLocalisation(self) -> bool: """Load the language data for the current project language.""" if self._data.language is None or CONFIG._nwLangPath is None: diff --git a/novelwriter/core/projectxml.py b/novelwriter/core/projectxml.py index c99c8abf..ac28ed4a 100644 --- a/novelwriter/core/projectxml.py +++ b/novelwriter/core/projectxml.py @@ -46,7 +46,7 @@ if TYPE_CHECKING: # pragma: no cover logger = logging.getLogger(__name__) FILE_VERSION = "1.5" # The current project file format version -FILE_REVISION = "3" # The current project file format revision +FILE_REVISION = "4" # The current project file format revision HEX_VERSION = 0x0105 NUM_VERSION = { @@ -109,6 +109,8 @@ class ProjectXMLReader: Rev 2: Drops the title node from project and adds the TEMPLATE class for items. 2.3 Beta 1. Rev 3: Added TEMPLATE class. 2.3. + Rev 4: Added shape attribute to status and importance entry + nodes. 2.5. """ def __init__(self, path: str | Path) -> None: @@ -356,8 +358,8 @@ class ProjectXMLReader: logger.debug("Parsing section (legacy format)") # Create maps to look up name -> key for status and importance - statusMap = {entry.get("name"): key for key, entry in data.itemStatus.items()} - importMap = {entry.get("name"): key for key, entry in data.itemImport.items()} + sMap: dict[str | None, str] = {e.name: k for k, e in data.itemStatus.iterItems()} + iMap: dict[str | None, str] = {e.name: k for k, e in data.itemImport.iterItems()} for xItem in xSection: if xItem.tag != "item": @@ -404,9 +406,9 @@ class ProjectXMLReader: # Status was split into separate status/import with a key in 1.4 if item.get("class", "") in ("NOVEL", "ARCHIVE"): - name["status"] = statusMap.get(tmpStatus, None) + name["status"] = sMap.get(tmpStatus, None) else: - name["import"] = importMap.get(tmpStatus, None) + name["import"] = iMap.get(tmpStatus, None) # A number of layouts were removed in 1.3 if item.get("layout", "") in ( @@ -436,7 +438,8 @@ class ProjectXMLReader: green = checkInt(xEntry.attrib.get("green", 0), 0) blue = checkInt(xEntry.attrib.get("blue", 0), 0) count = checkInt(xEntry.attrib.get("count", 0), 0) - sObject.write(key, xEntry.text or "", (red, green, blue), count) + shape = xEntry.attrib.get("shape", "") + sObject.add(key, xEntry.text or "", (red, green, blue), shape, count) return def _parseDictKeyText(self, xItem: ET.Element) -> dict: @@ -449,7 +452,7 @@ class ProjectXMLReader: result[xEntry.attrib["key"]] = checkString(xEntry.text, "") return result - def _parseDictTagText(self, xItem) -> dict: + def _parseDictTagText(self, xItem: ET.Element) -> dict: """Parse a dictionary stored with key as the tag and the value as the text property. """ diff --git a/novelwriter/core/spellcheck.py b/novelwriter/core/spellcheck.py index 51804d83..6dc47785 100644 --- a/novelwriter/core/spellcheck.py +++ b/novelwriter/core/spellcheck.py @@ -74,7 +74,7 @@ class NWSpellEnchant: # Setters ## - def setLanguage(self, language: str | None): + def setLanguage(self, language: str | None) -> None: """Load a dictionary for the language specified in the config. If that fails, we load a mock dictionary so that lookups don't crash. Note that enchant will allow loading an empty string as @@ -182,10 +182,10 @@ class FakeEnchant: def check(self, word: str) -> bool: return True - def suggest(self, word) -> list[str]: + def suggest(self, word: str) -> list[str]: return [] - def add_to_session(self, word: str): + def add_to_session(self, word: str) -> None: return # END Class FakeEnchant diff --git a/novelwriter/core/status.py b/novelwriter/core/status.py index 554bdeed..6b85ccec 100644 --- a/novelwriter/core/status.py +++ b/novelwriter/core/status.py @@ -24,17 +24,19 @@ along with this program. If not, see . """ from __future__ import annotations -import random +import dataclasses import logging +import random -from typing import TYPE_CHECKING, Literal -from collections.abc import ItemsView, Iterable, Iterator, KeysView, ValuesView +from collections.abc import Iterable +from typing import TYPE_CHECKING -from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor -from PyQt5.QtCore import QRectF +from PyQt5.QtCore import QPointF, Qt +from PyQt5.QtGui import QIcon, QPainter, QPainterPath, QPixmap, QColor, QPolygonF -from novelwriter import CONFIG -from novelwriter.common import minmax, simplified +from novelwriter import SHARED +from novelwriter.common import simplified +from novelwriter.enum import nwStatusShape from novelwriter.types import QtPaintAnitAlias, QtTransparent if TYPE_CHECKING: # pragma: no cover @@ -43,83 +45,94 @@ if TYPE_CHECKING: # pragma: no cover logger = logging.getLogger(__name__) +@dataclasses.dataclass +class StatusEntry: + + name: str + color: QColor + shape: nwStatusShape + icon: QIcon + count: int = 0 + + @classmethod + def duplicate(cls, source: StatusEntry) -> StatusEntry: + """Create a deep copy of the source object.""" + cls = dataclasses.replace(source) + cls.color = QColor(source.color) + cls.icon = QIcon(source.icon) + return cls + +# END Class StatusEntry + + +NO_ENTRY = StatusEntry("", QColor(0, 0, 0), nwStatusShape.SQUARE, QIcon(), 0) + + class NWStatus: - STATUS = 1 - IMPORT = 2 + STATUS = "s" + IMPORT = "i" - def __init__(self, kind: Literal[1, 2]) -> None: + __slots__ = ("_store", "_default", "_prefix", "_height") - self._type = kind - self._store = {} + def __init__(self, prefix: str) -> None: + self._store: dict[str, StatusEntry] = {} self._default = None - - self._iPX = CONFIG.pxInt(24) - - pA = CONFIG.pxInt(2) - pB = CONFIG.pxInt(20) - pR = float(CONFIG.pxInt(4)) - self._iconPath = QPainterPath() - self._iconPath.addRoundedRect(QRectF(pA, pA, pB, pB), pR, pR) - - self._defaultIcon = self._createIcon(100, 100, 100) - - if self._type == self.STATUS: - self._prefix = "s" - elif self._type == self.IMPORT: - self._prefix = "i" - else: - raise Exception("This is a bug!") - + self._prefix = prefix[:1] + self._height = SHARED.theme.baseIconHeight return - def write(self, key: str | None, name: str, col: tuple, count: int | None = None) -> str: + def __len__(self) -> int: + return len(self._store) + + def __getitem__(self, key: str | None) -> StatusEntry: + """Return the entry associated with a given key.""" + if key and key in self._store: + return self._store[key] + elif self._default is not None: + return self._store[self._default] + return NO_ENTRY + + ## + # Methods + ## + + def add(self, key: str | None, name: str, color: tuple[int, int, int], + shape: str, count: int) -> str: """Add or update a status entry. If the key is invalid, a new key is generated. """ - if not self._isKey(key): - key = self._newKey() - if not isinstance(col, tuple): - col = (100, 100, 100) - if len(col) != 3: - col = (100, 100, 100) + if isinstance(color, tuple) and len(color) == 3: + qColor = QColor(*color) + else: + qColor = QColor(100, 100, 100) - cR = minmax(col[0], 0, 255) - cG = minmax(col[1], 0, 255) - cB = minmax(col[2], 0, 255) + try: + iShape = nwStatusShape[shape] + except KeyError: + iShape = nwStatusShape.SQUARE + + key = self._checkKey(key) name = simplified(name) - if count is None: - count = self._store.get(key, {}).get("count", 0) - - self._store[key] = { - "name": name, - "icon": self._createIcon(cR, cG, cB), - "cols": (cR, cG, cB), - "count": count, - } + icon = self.createIcon(self._height, qColor, iShape) + self._store[key] = StatusEntry(name, qColor, iShape, icon, count) if self._default is None: self._default = key return key - def remove(self, key: str) -> bool: - """Remove an entry in the list, except if the count > 0.""" - if key not in self._store: - return False - if self._store[key]["count"] > 0: - return False + def update(self, update: list[tuple[str | None, StatusEntry]]) -> None: + """Update the list of statuses, and from removed list.""" + self._store.clear() + for key, entry in update: + self._store[self._checkKey(key)] = entry - del self._store[key] + # Check if we need a new default + if self._default not in self._store: + self._default = next(iter(self._store)) if self._store else None - keys = list(self._store.keys()) - if key == self._default: - if len(keys) > 0: - self._default = keys[0] - else: - self._default = None - - return True + return def check(self, value: str) -> str: """Check the key against the stored status names.""" @@ -129,93 +142,51 @@ class NWStatus: return self._default return "" - def name(self, key: str | None) -> str: - """Return the name associated with a given key.""" - if key and key in self._store: - return self._store[key]["name"] - elif self._default is not None: - return self._store[self._default]["name"] - return "" - - def cols(self, key: str | None) -> tuple[int, int, int]: - """Return the colours associated with a given key.""" - if key and key in self._store: - return self._store[key]["cols"] - elif self._default is not None: - return self._store[self._default]["cols"] - return 100, 100, 100 - - def count(self, key: str | None) -> int: - """Return the count associated with a given key.""" - if key and key in self._store: - return self._store[key]["count"] - elif self._default is not None: - return self._store[self._default]["count"] - return 0 - - def icon(self, key: str | None) -> QIcon: - """Return the icon associated with a given key.""" - if key and key in self._store: - return self._store[key]["icon"] - elif self._default is not None: - return self._store[self._default]["icon"] - return self._defaultIcon - - def reorder(self, order: list[str]) -> bool: - """Reorder the items according to list.""" - if len(order) != len(self._store): - logger.error("Length mismatch between new and old order") - return False - - if order == list(self._store.keys()): - return False - - store = {} - for key in order: - if key in self._store: - store[key] = self._store[key] - else: - logger.error("Unknown key '%s' in order", key) - return False - - self._store = store - - return True - def resetCounts(self) -> None: """Clear the counts of references to the status entries.""" - for key in self._store: - self._store[key]["count"] = 0 + for entry in self._store.values(): + entry.count = 0 return def increment(self, key: str | None) -> None: """Increment the counter for a given entry.""" if key and key in self._store: - self._store[key]["count"] += 1 + self._store[key].count += 1 return def pack(self) -> Iterable[tuple[str, dict]]: """Pack the status entries into a dictionary.""" - for key, data in self._store.items(): - yield (data["name"], { + for key, entry in self._store.items(): + yield (entry.name, { "key": key, - "count": str(data["count"]), - "red": str(data["cols"][0]), - "green": str(data["cols"][1]), - "blue": str(data["cols"][2]), + "count": str(entry.count), + "red": str(entry.color.red()), + "green": str(entry.color.green()), + "blue": str(entry.color.blue()), + "shape": entry.shape.name, }) return - def unpack(self, data: dict) -> None: - """Unpack a data dictionary and set the class values.""" - self._store = {} - self._default = None - for key, entry in data.items(): - label = entry.get("label", "") - colour = entry.get("colour", (100, 100, 100)) - count = entry.get("count", 0) - self.write(key, label, colour, count) - return + def iterItems(self) -> Iterable[tuple[str, StatusEntry]]: + """Yield entries from the status icons.""" + yield from self._store.items() + + @staticmethod + def createIcon(height: int, color: QColor, shape: nwStatusShape) -> QIcon: + """Generate an icon for a status label.""" + pixmap = QPixmap(48, 48) + pixmap.fill(QtTransparent) + + painter = QPainter(pixmap) + painter.setRenderHint(QtPaintAnitAlias) + painter.fillPath(_SHAPES.getShape(shape), color) + painter.end() + + return QIcon(pixmap.scaled( + height, height, + Qt.AspectRatioMode.IgnoreAspectRatio, + Qt.TransformationMode.SmoothTransformation + )) ## # Internal Functions @@ -246,38 +217,120 @@ class NWStatus: return False return True - def _createIcon(self, red: int, green: int, blue: int) -> QIcon: - """Generate an icon for a status label.""" - pixmap = QPixmap(self._iPX, self._iPX) - pixmap.fill(QtTransparent) - - painter = QPainter(pixmap) - painter.setRenderHint(QtPaintAnitAlias) - painter.fillPath(self._iconPath, QColor(red, green, blue)) - painter.end() - - return QIcon(pixmap) - - ## - # Iterator Bits - ## - - def __len__(self) -> int: - return len(self._store) - - def __getitem__(self, key: str) -> dict: - return self._store[key] - - def __iter__(self) -> Iterator[dict]: - return iter(self._store) - - def keys(self) -> KeysView[str]: - return self._store.keys() - - def items(self) -> ItemsView[str, dict]: - return self._store.items() - - def values(self) -> ValuesView[dict]: - return self._store.values() + def _checkKey(self, key: str | None) -> str: + """Check key is valid, and if not, generate one.""" + return key if self._isKey(key) else self._newKey() # END Class NWStatus + + +class _ShapeCache: + + def __init__(self) -> None: + self._cache: dict[nwStatusShape, QPainterPath] = {} + return + + def getShape(self, shape: nwStatusShape) -> QPainterPath: + """Return a painter shape for an icon.""" + if shape in self._cache: + return self._cache[shape] + + path = QPainterPath() + if shape == nwStatusShape.SQUARE: + path.addRoundedRect(2.0, 2.0, 44.0, 44.0, 4.0, 4.0) + elif shape == nwStatusShape.TRIANGLE: + path.addPolygon(QPolygonF([ + QPointF(24.00, 3.00), + QPointF(43.92, 37.50), + QPointF(4.08, 37.50), + ])) + elif shape == nwStatusShape.NABLA: + path.addPolygon(QPolygonF([ + QPointF(24.00, 48.00), + QPointF(4.08, 14.50), + QPointF(43.92, 14.50), + ])) + elif shape == nwStatusShape.DIAMOND: + path.addPolygon(QPolygonF([ + QPointF(24.00, 2.00), + QPointF(44.00, 24.00), + QPointF(24.00, 46.00), + QPointF(4.00, 24.00), + ])) + elif shape == nwStatusShape.PENTAGON: + path.addPolygon(QPolygonF([ + QPointF(24.00, 1.50), + QPointF(45.87, 17.39), + QPointF(37.52, 43.11), + QPointF(10.48, 43.11), + QPointF(2.13, 17.39), + ])) + elif shape == nwStatusShape.HEXAGON: + path.addPolygon(QPolygonF([ + QPointF(24.00, 1.50), + QPointF(43.92, 13.00), + QPointF(43.92, 36.00), + QPointF(24.00, 47.50), + QPointF(4.08, 36.00), + QPointF(4.08, 13.00), + ])) + elif shape == nwStatusShape.STAR: + path.addPolygon(QPolygonF([ + QPointF(24.00, 0.50), QPointF(31.05, 14.79), + QPointF(46.83, 17.08), QPointF(35.41, 28.21), + QPointF(38.11, 43.92), QPointF(24.00, 36.50), + QPointF(9.89, 43.92), QPointF(12.59, 28.21), + QPointF(1.17, 17.08), QPointF(15.37, 16.16), + ])) + elif shape == nwStatusShape.PACMAN: + path.moveTo(24.0, 24.0) + path.arcTo(2.0, 2.0, 44.0, 44.0, 40.0, 280.0) + elif shape == nwStatusShape.CIRCLE_Q: + path.moveTo(24.0, 24.0) + path.arcTo(2.0, 2.0, 44.0, 44.0, 0.0, 90.0) + elif shape == nwStatusShape.CIRCLE_H: + path.moveTo(24.0, 24.0) + path.arcTo(2.0, 2.0, 44.0, 44.0, -90.0, 180.0) + elif shape == nwStatusShape.CIRCLE_T: + path.moveTo(24.0, 24.0) + path.arcTo(2.0, 2.0, 44.0, 44.0, -180.0, 270.0) + elif shape == nwStatusShape.CIRCLE: + path.addEllipse(2.0, 2.0, 44.0, 44.0) + elif shape == nwStatusShape.BARS_1: + path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0) + elif shape == nwStatusShape.BARS_2: + path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0) + path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0) + elif shape == nwStatusShape.BARS_3: + path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0) + path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0) + path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0) + elif shape == nwStatusShape.BARS_4: + path.addRoundedRect(2.0, 2.0, 8.0, 44.0, 4.0, 4.0) + path.addRoundedRect(14.0, 2.0, 8.0, 44.0, 4.0, 4.0) + path.addRoundedRect(26.0, 2.0, 8.0, 44.0, 4.0, 4.0) + path.addRoundedRect(38.0, 2.0, 8.0, 44.0, 4.0, 4.0) + elif shape == nwStatusShape.BLOCK_1: + path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0) + elif shape == nwStatusShape.BLOCK_2: + path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0) + path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0) + elif shape == nwStatusShape.BLOCK_3: + path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0) + path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0) + path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0) + elif shape == nwStatusShape.BLOCK_4: + path.addRoundedRect(2.0, 2.0, 20.0, 20.0, 4.0, 4.0) + path.addRoundedRect(2.0, 24.0, 20.0, 20.0, 4.0, 4.0) + path.addRoundedRect(24.0, 2.0, 20.0, 20.0, 4.0, 4.0) + path.addRoundedRect(24.0, 24.0, 20.0, 20.0, 4.0, 4.0) + + self._cache[shape] = path + + return path + +# END Class _ShapeCache + + +# Create Singleton +_SHAPES = _ShapeCache() diff --git a/novelwriter/core/tokenizer.py b/novelwriter/core/tokenizer.py index abb6fc28..d8c9861e 100644 --- a/novelwriter/core/tokenizer.py +++ b/novelwriter/core/tokenizer.py @@ -49,7 +49,7 @@ ESCAPES = {r"\*": "*", r"\~": "~", r"\_": "_", r"\[": "[", r"\]": "]", r"\ ": "" RX_ESC = re.compile("|".join([re.escape(k) for k in ESCAPES.keys()]), flags=re.DOTALL) -def stripEscape(text) -> str: +def stripEscape(text: str) -> str: """Strip escaped Markdown characters from paragraph text.""" if "\\" in text: return RX_ESC.sub(lambda x: ESCAPES[x.group(0)], text) @@ -639,8 +639,8 @@ class Tokenizer(ABC): tmpMarkdown.append(f"{aLine}\n") elif aLine.startswith(("### ", "###! ")): - # (Hard) Scene Headings - # ===================== + # (Alternative) Scene Headings + # ============================ # Scene headings in novel documents are treated as centred # separators if the formatting does not change the text. If the # format is empty, the scene can be hidden or a blank paragraph diff --git a/novelwriter/core/toodt.py b/novelwriter/core/toodt.py index 2801fc5b..22418f32 100644 --- a/novelwriter/core/toodt.py +++ b/novelwriter/core/toodt.py @@ -548,7 +548,7 @@ class ToOdt(Tokenizer): oVers = _mkTag("office", "version") xSett = ET.Element(oRoot, attrib={oVers: X_VERS}) - def putInZip(name, xObj, zipObj): + def putInZip(name: str, xObj: ET.Element, zipObj: ZipFile) -> None: with zipObj.open(name, mode="w") as fObj: xml = ET.ElementTree(xObj) xml.write(fObj, encoding="utf-8", xml_declaration=True) diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 7d1e4671..a8900786 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -795,7 +795,7 @@ class GuiPreferences(QDialog): return @pyqtSlot() - def _selectTextFont(self): + def _selectTextFont(self) -> None: """Open the QFontDialog and set a font for the font style.""" current = QFont() current.setFamily(CONFIG.textFont) diff --git a/novelwriter/dialogs/projectsettings.py b/novelwriter/dialogs/projectsettings.py index 4e234f76..23bb43da 100644 --- a/novelwriter/dialogs/projectsettings.py +++ b/novelwriter/dialogs/projectsettings.py @@ -27,20 +27,26 @@ from __future__ import annotations import logging from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot -from PyQt5.QtGui import QCloseEvent, QColor, QIcon, QPixmap +from PyQt5.QtGui import QCloseEvent, QColor from PyQt5.QtWidgets import ( - QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout, - QLineEdit, QPushButton, QStackedWidget, QTreeWidget, QTreeWidgetItem, - QVBoxLayout, QWidget + QAbstractItemView, QApplication, QColorDialog, QDialog, QDialogButtonBox, QHBoxLayout, + QLineEdit, QMenu, QStackedWidget, QToolButton, QTreeWidget, + QTreeWidgetItem, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED from novelwriter.common import simplified +from novelwriter.constants import nwLabels, trConst +from novelwriter.core.status import NWStatus, StatusEntry +from novelwriter.enum import nwStatusShape from novelwriter.extensions.configlayout import NColourLabel, NFixedPage, NScrollableForm from novelwriter.extensions.modified import NComboBox, NIconToolButton from novelwriter.extensions.pagedsidebar import NPagedSideBar from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtDialogCancel, QtDialogSave, QtUserRole +from novelwriter.types import ( + QtDialogCancel, QtDialogSave, QtSizeMinimum, QtSizeMinimumExpanding, + QtUserRole +) logger = logging.getLogger(__name__) @@ -179,19 +185,19 @@ class GuiProjectSettings(QDialog): rebuildTrees = False - if self.statusPage.wasChanged: - newList, delList = self.statusPage.getNewList() - project.setStatusColours(newList, delList) + if self.statusPage.changed: + logger.debug("Updating status labels") + project.data.itemStatus.update(self.statusPage.getNewList()) rebuildTrees = True - if self.importPage.wasChanged: - newList, delList = self.importPage.getNewList() - project.setImportColours(newList, delList) + if self.importPage.changed: + logger.debug("Updating importance labels") + project.data.itemImport.update(self.importPage.getNewList()) rebuildTrees = True - if self.replacePage.wasChanged: - newList = self.replacePage.getNewList() - project.data.setAutoReplace(newList) + if self.replacePage.changed: + logger.debug("Updating auto-replace settings") + project.data.setAutoReplace(self.replacePage.getNewList()) self.newProjectSettingsReady.emit(rebuildTrees) QApplication.processEvents() @@ -257,17 +263,17 @@ class _SettingsPage(NScrollableForm): ) # Project Language + projLang = data.language or CONFIG.guiLocale self.projLang = NComboBox(self) self.projLang.setMinimumWidth(xW) for tag, language in CONFIG.listLanguages(CONFIG.LANG_PROJ): self.projLang.addItem(language, tag) + self.projLang.setCurrentData(projLang, projLang) self.addRow( self.tr("Project language"), self.projLang, self.tr("Only used when building the manuscript."), stretch=(3, 2) ) - if (idx := self.projLang.findData(data.language)) != -1: - self.projLang.setCurrentIndex(idx) # Spell Check Language self.spellLang = NComboBox(self) @@ -301,12 +307,12 @@ class _SettingsPage(NScrollableForm): class _StatusPage(NFixedPage): - COL_LABEL = 0 - COL_USAGE = 1 + C_DATA = 0 + C_LABEL = 0 + C_USAGE = 1 - KEY_ROLE = QtUserRole - COL_ROLE = QtUserRole + 1 - NUM_ROLE = QtUserRole + 2 + D_KEY = QtUserRole + D_ENTRY = QtUserRole + 1 def __init__(self, parent: QWidget, isStatus: bool) -> None: super().__init__(parent=parent) @@ -325,12 +331,21 @@ class _StatusPage(NFixedPage): ) self._changed = False - self._colDeleted = [] - self._selColour = QColor(100, 100, 100) + self._color = QColor(100, 100, 100) + self._shape = nwStatusShape.SQUARE + self._icons = {} - self.iPx = SHARED.theme.baseIconHeight + self._iPx = SHARED.theme.baseIconHeight iSz = SHARED.theme.baseIconSize - bSz = SHARED.theme.buttonIconSize + bPd = CONFIG.pxInt(4) + + iColor = self.palette().text().color() + + # Labels + self.trCountNone = self.tr("Not in use") + self.trCountOne = self.tr("Used once") + self.trCountMore = self.tr("Used by {0} items") + self.trSelColor = self.tr("Select Colour") # Title self.pageTitle = NColourLabel( @@ -341,12 +356,14 @@ class _StatusPage(NFixedPage): # List Box self.listBox = QTreeWidget(self) self.listBox.setHeaderLabels([self.tr("Label"), self.tr("Usage")]) - self.listBox.itemSelectionChanged.connect(self._selectedItem) - self.listBox.setColumnWidth(self.COL_LABEL, wCol0) + self.listBox.setColumnWidth(self.C_LABEL, wCol0) self.listBox.setIndentation(0) + self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.listBox.itemSelectionChanged.connect(self._selectionChanged) - for key, entry in status.items(): - self._addItem(key, entry["name"], entry["cols"], entry["count"]) + for key, entry in status.iterItems(): + self._addItem(key, StatusEntry.duplicate(entry)) # List Controls self.addButton = NIconToolButton(self, iSz, "add") @@ -367,16 +384,43 @@ class _StatusPage(NFixedPage): self.editName.setPlaceholderText(self.tr("Select item to edit")) self.editName.setEnabled(False) - self.colPixmap = QPixmap(self.iPx, self.iPx) - self.colPixmap.fill(QColor(100, 100, 100)) - self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"), self) - self.colButton.setIconSize(bSz) - self.colButton.setEnabled(False) - self.colButton.clicked.connect(self._selectColour) + buttonStyle = ( + f"QToolButton {{padding: 0 {bPd}px;}} " + "QToolButton::menu-indicator {image: none;}" + ) - self.saveButton = QPushButton(self.tr("Save"), self) - self.saveButton.setEnabled(False) - self.saveButton.clicked.connect(self._saveItem) + self.colorButton = NIconToolButton(self, iSz) + self.colorButton.setToolTip(self.tr("Colour")) + self.colorButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding) + self.colorButton.setStyleSheet(buttonStyle) + self.colorButton.setEnabled(False) + self.colorButton.clicked.connect(self._selectColour) + + def buildMenu(menu: QMenu, items: dict[nwStatusShape, str]) -> None: + for shape, label in items.items(): + icon = NWStatus.createIcon(self._iPx, iColor, shape) + action = menu.addAction(icon, trConst(label)) + action.triggered.connect(lambda _, shape=shape: self._selectShape(shape)) + self._icons[shape] = icon + + self.shapeMenu = QMenu(self) + buildMenu(self.shapeMenu, nwLabels.SHAPES_PLAIN) + buildMenu(self.shapeMenu.addMenu(self.tr("Circles ...")), nwLabels.SHAPES_CIRCLE) + buildMenu(self.shapeMenu.addMenu(self.tr("Bars ...")), nwLabels.SHAPES_BARS) + buildMenu(self.shapeMenu.addMenu(self.tr("Blocks ...")), nwLabels.SHAPES_BLOCKS) + + self.shapeButton = NIconToolButton(self, iSz) + self.shapeButton.setMenu(self.shapeMenu) + self.shapeButton.setToolTip(self.tr("Shape")) + self.shapeButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding) + self.shapeButton.setStyleSheet(buttonStyle) + self.shapeButton.setEnabled(False) + + self.applyButton = QToolButton(self) + self.applyButton.setText(self.tr("Apply")) + self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding) + self.applyButton.setEnabled(False) + self.applyButton.clicked.connect(self._applyChanges) # Assemble self.listControls = QVBoxLayout() @@ -387,28 +431,30 @@ class _StatusPage(NFixedPage): self.listControls.addStretch(1) self.editBox = QHBoxLayout() - self.editBox.addWidget(self.editName) - self.editBox.addWidget(self.colButton) - self.editBox.addWidget(self.saveButton) + self.editBox.addWidget(self.editName, 1) + self.editBox.addWidget(self.colorButton, 0) + self.editBox.addWidget(self.shapeButton, 0) + self.editBox.addWidget(self.applyButton, 0) self.mainBox = QVBoxLayout() - self.mainBox.addWidget(self.listBox) - self.mainBox.addLayout(self.editBox) + self.mainBox.addWidget(self.listBox, 1) + self.mainBox.addLayout(self.editBox, 0) self.innerBox = QHBoxLayout() - self.innerBox.addLayout(self.mainBox) - self.innerBox.addLayout(self.listControls) + self.innerBox.addLayout(self.mainBox, 1) + self.innerBox.addLayout(self.listControls, 0) self.outerBox = QVBoxLayout() - self.outerBox.addWidget(self.pageTitle) - self.outerBox.addLayout(self.innerBox) + self.outerBox.addWidget(self.pageTitle, 0) + self.outerBox.addLayout(self.innerBox, 1) self.setCentralLayout(self.outerBox) + self._setButtonIcons() return @property - def wasChanged(self) -> bool: + def changed(self) -> bool: """The user changed these settings.""" return self._changed @@ -416,20 +462,17 @@ class _StatusPage(NFixedPage): # Methods ## - def getNewList(self) -> tuple[list, list]: + def getNewList(self) -> list[tuple[str | None, StatusEntry]]: """Return list of entries.""" if self._changed: - newList = [] + update = [] for n in range(self.listBox.topLevelItemCount()): - item = self.listBox.topLevelItem(n) - if item is not None: - newList.append({ - "key": item.data(self.COL_LABEL, self.KEY_ROLE), - "name": item.text(self.COL_LABEL), - "cols": item.data(self.COL_LABEL, self.COL_ROLE), - }) - return newList, self._colDeleted - return [], [] + if item := self.listBox.topLevelItem(n): + key = item.data(self.C_DATA, self.D_KEY) + entry = item.data(self.C_DATA, self.D_ENTRY) + update.append((key, entry)) + return update + return [] def columnWidth(self) -> int: """Return the size of the header column.""" @@ -442,124 +485,119 @@ class _StatusPage(NFixedPage): @pyqtSlot() def _selectColour(self) -> None: """Open a dialog to select the status icon colour.""" - if self._selColour is not None: - newCol = QColorDialog.getColor( - self._selColour, self, self.tr("Select Colour") - ) - if newCol.isValid(): - self._selColour = newCol - pixmap = QPixmap(self.iPx, self.iPx) - pixmap.fill(newCol) - self.colButton.setIcon(QIcon(pixmap)) - self.colButton.setIconSize(pixmap.rect().size()) + if (color := QColorDialog.getColor(self._color, self, self.trSelColor)).isValid(): + self._color = color + self._setButtonIcons() return @pyqtSlot() def _newItem(self) -> None: """Create a new status item.""" - self._addItem(None, self.tr("New Item"), (100, 100, 100), 0) + color = QColor(100, 100, 100) + shape = nwStatusShape.SQUARE + icon = NWStatus.createIcon(self._iPx, color, shape) + self._addItem(None, StatusEntry(self.tr("New Item"), color, shape, icon, 0)) self._changed = True return @pyqtSlot() def _delItem(self) -> None: """Delete a status item.""" - selItem = self._getSelectedItem() - if isinstance(selItem, QTreeWidgetItem): - iRow = self.listBox.indexOfTopLevelItem(selItem) - if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0: + if item := self._getSelectedItem(): + iRow = self.listBox.indexOfTopLevelItem(item) + entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY) + if entry.count > 0: SHARED.error(self.tr("Cannot delete a status item that is in use.")) else: self.listBox.takeTopLevelItem(iRow) - self._colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE)) self._changed = True return @pyqtSlot() - def _saveItem(self) -> None: + def _applyChanges(self) -> None: """Save changes made to a status item.""" - selItem = self._getSelectedItem() - if isinstance(selItem, QTreeWidgetItem): - selItem.setText(self.COL_LABEL, simplified(self.editName.text())) - selItem.setIcon(self.COL_LABEL, self.colButton.icon()) - selItem.setData(self.COL_LABEL, self.COL_ROLE, ( - self._selColour.red(), self._selColour.green(), self._selColour.blue() - )) + if item := self._getSelectedItem(): + entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY) + + name = simplified(self.editName.text()) + icon = NWStatus.createIcon(self._iPx, self._color, self._shape) + + entry.name = name + entry.color = self._color + entry.shape = self._shape + entry.icon = icon + + item.setText(self.C_LABEL, name) + item.setIcon(self.C_LABEL, icon) + self._changed = True + return @pyqtSlot() - def _selectedItem(self) -> None: + def _selectionChanged(self) -> None: """Extract the info of a selected item and populate the settings boxes and button. If no item is selected, clear the form. """ - selItem = self._getSelectedItem() - if isinstance(selItem, QTreeWidgetItem): - cols = selItem.data(self.COL_LABEL, self.COL_ROLE) - name = selItem.text(self.COL_LABEL) - pixmap = QPixmap(self.iPx, self.iPx) - pixmap.fill(QColor(*cols)) - self._selColour = QColor(*cols) - self.editName.setText(name) - self.colButton.setIcon(QIcon(pixmap)) + if item := self._getSelectedItem(): + entry: StatusEntry = item.data(self.C_DATA, self.D_ENTRY) + self._color = entry.color + self._shape = entry.shape + self._setButtonIcons() + + self.editName.setText(entry.name) self.editName.selectAll() self.editName.setFocus() + self.editName.setEnabled(True) - self.colButton.setEnabled(True) - self.saveButton.setEnabled(True) + self.colorButton.setEnabled(True) + self.shapeButton.setEnabled(True) + self.applyButton.setEnabled(True) + else: - pixmap = QPixmap(self.iPx, self.iPx) - pixmap.fill(QColor(100, 100, 100)) - self._selColour = QColor(100, 100, 100) + self._color = QColor(100, 100, 100) + self._shape = nwStatusShape.SQUARE + self._setButtonIcons() self.editName.setText("") - self.colButton.setIcon(QIcon(pixmap)) + self.editName.setEnabled(False) - self.colButton.setEnabled(False) - self.saveButton.setEnabled(False) + self.colorButton.setEnabled(False) + self.shapeButton.setEnabled(False) + self.applyButton.setEnabled(False) return ## # Internal Functions ## - def _addItem(self, key: str | None, name: str, - colour: tuple[int, int, int], count: int) -> None: + def _selectShape(self, shape: nwStatusShape) -> None: + """Set the current shape.""" + self._shape = shape + self._setButtonIcons() + return + + def _addItem(self, key: str | None, entry: StatusEntry) -> None: """Add a status item to the list.""" - pixmap = QPixmap(self.iPx, self.iPx) - pixmap.fill(QColor(*colour)) - item = QTreeWidgetItem() - item.setText(self.COL_LABEL, name) - item.setIcon(self.COL_LABEL, QIcon(pixmap)) - item.setData(self.COL_LABEL, self.KEY_ROLE, key) - item.setData(self.COL_LABEL, self.COL_ROLE, colour) - item.setData(self.COL_LABEL, self.NUM_ROLE, count) - item.setText(self.COL_USAGE, self._usageString(count)) - + item.setText(self.C_LABEL, entry.name) + item.setIcon(self.C_LABEL, entry.icon) + item.setText(self.C_USAGE, self._usageString(entry.count)) + item.setData(self.C_DATA, self.D_KEY, key) + item.setData(self.C_DATA, self.D_ENTRY, entry) self.listBox.addTopLevelItem(item) - return def _moveItem(self, step: int) -> None: """Move and item up or down step.""" - selItem = self._getSelectedItem() - if selItem is None: - return - - tIndex = self.listBox.indexOfTopLevelItem(selItem) - nChild = self.listBox.topLevelItemCount() - nIndex = tIndex + step - if nIndex < 0 or nIndex >= nChild: - return - - cItem = self.listBox.takeTopLevelItem(tIndex) - self.listBox.insertTopLevelItem(nIndex, cItem) - self.listBox.clearSelection() - - if cItem is not None: - cItem.setSelected(True) - self._changed = True - + if item := self._getSelectedItem(): + tIdx = self.listBox.indexOfTopLevelItem(item) + nItm = self.listBox.topLevelItemCount() + nIdx = tIdx + step + if (0 <= nIdx < nItm) and (cItem := self.listBox.takeTopLevelItem(tIdx)): + self.listBox.insertTopLevelItem(nIdx, cItem) + self.listBox.clearSelection() + cItem.setSelected(True) + self._changed = True return def _getSelectedItem(self) -> QTreeWidgetItem | None: @@ -571,19 +609,26 @@ class _StatusPage(NFixedPage): def _usageString(self, count: int) -> str: """Generate usage string.""" if count == 0: - return self.tr("Not in use") + return self.trCountNone elif count == 1: - return self.tr("Used once") + return self.trCountOne else: - return self.tr("Used by {0} items").format(count) + return self.trCountMore.format(count) + + def _setButtonIcons(self) -> None: + """Set the colour of the colour button.""" + icon = NWStatus.createIcon(self._iPx, self._color, nwStatusShape.SQUARE) + self.colorButton.setIcon(icon) + self.shapeButton.setIcon(self._icons[self._shape]) + return # END Class _StatusPage class _ReplacePage(NFixedPage): - COL_KEY = 0 - COL_REPL = 1 + C_KEY = 0 + C_REPL = 1 def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) @@ -605,15 +650,17 @@ class _ReplacePage(NFixedPage): # List Box self.listBox = QTreeWidget(self) self.listBox.setHeaderLabels([self.tr("Keyword"), self.tr("Replace With")]) - self.listBox.setColumnWidth(self.COL_KEY, wCol0) + self.listBox.setColumnWidth(self.C_KEY, wCol0) self.listBox.setIndentation(0) - self.listBox.itemSelectionChanged.connect(self._selectedItem) + self.listBox.setSelectionBehavior(QAbstractItemView.SelectionBehavior.SelectRows) + self.listBox.setSelectionMode(QAbstractItemView.SelectionMode.SingleSelection) + self.listBox.itemSelectionChanged.connect(self._selectionChanged) for aKey, aVal in SHARED.project.data.autoReplace.items(): newItem = QTreeWidgetItem(["<%s>" % aKey, aVal]) self.listBox.addTopLevelItem(newItem) - self.listBox.sortByColumn(self.COL_KEY, Qt.SortOrder.AscendingOrder) + self.listBox.sortByColumn(self.C_KEY, Qt.SortOrder.AscendingOrder) self.listBox.setSortingEnabled(True) # List Controls @@ -633,8 +680,10 @@ class _ReplacePage(NFixedPage): self.editValue.setEnabled(False) self.editValue.setMaxLength(80) - self.saveButton = QPushButton(self.tr("Save"), self) - self.saveButton.clicked.connect(self._saveEntry) + self.applyButton = QToolButton(self) + self.applyButton.setText(self.tr("Apply")) + self.applyButton.setSizePolicy(QtSizeMinimum, QtSizeMinimumExpanding) + self.applyButton.clicked.connect(self._applyChanges) # Assemble self.listControls = QVBoxLayout() @@ -645,11 +694,11 @@ class _ReplacePage(NFixedPage): self.editBox = QHBoxLayout() self.editBox.addWidget(self.editKey, 4) self.editBox.addWidget(self.editValue, 5) - self.editBox.addWidget(self.saveButton, 0) + self.editBox.addWidget(self.applyButton, 0) self.mainBox = QVBoxLayout() - self.mainBox.addWidget(self.listBox) - self.mainBox.addLayout(self.editBox) + self.mainBox.addWidget(self.listBox, 1) + self.mainBox.addLayout(self.editBox, 0) self.innerBox = QHBoxLayout() self.innerBox.addLayout(self.mainBox) @@ -664,7 +713,7 @@ class _ReplacePage(NFixedPage): return @property - def wasChanged(self) -> bool: + def changed(self) -> bool: """The user changed these settings.""" return self._changed @@ -672,15 +721,13 @@ class _ReplacePage(NFixedPage): # Methods ## - def getNewList(self) -> dict: + def getNewList(self) -> dict[str, str]: """Extract the list from the widget.""" new = {} for n in range(self.listBox.topLevelItemCount()): - if tItem := self.listBox.topLevelItem(n): - aKey = self._stripNotAllowed(tItem.text(0)) - aVal = tItem.text(1) - if len(aKey) > 0: - new[aKey] = aVal + if item := self.listBox.topLevelItem(n): + if key := self._stripNotAllowed(item.text(self.C_KEY)): + new[key] = item.text(self.C_REPL) return new def columnWidth(self) -> int: @@ -692,51 +739,48 @@ class _ReplacePage(NFixedPage): ## @pyqtSlot() - def _selectedItem(self) -> None: + def _selectionChanged(self) -> None: """Extract the details from the selected item and populate the edit form. """ - if selItem := self._getSelectedItem(): - editKey = self._stripNotAllowed(selItem.text(0)) - editVal = selItem.text(1) - self.editKey.setText(editKey) - self.editValue.setText(editVal) + if item := self._getSelectedItem(): + self.editKey.setText(self._stripNotAllowed(item.text(self.C_KEY))) + self.editValue.setText(item.text(self.C_REPL)) self.editKey.setEnabled(True) self.editValue.setEnabled(True) self.editKey.selectAll() self.editKey.setFocus() + else: + self.editKey.setText("") + self.editValue.setText("") + self.editKey.setEnabled(False) + self.editValue.setEnabled(False) return @pyqtSlot() - def _saveEntry(self) -> None: + def _applyChanges(self) -> None: """Save the form data into the list widget.""" - if selItem := self._getSelectedItem(): - newKey = self.editKey.text() - newVal = self.editValue.text() - saveKey = self._stripNotAllowed(newKey) - if len(saveKey) > 0 and len(newVal) > 0: - selItem.setText(self.COL_KEY, "<%s>" % saveKey) - selItem.setText(self.COL_REPL, newVal) - self.editKey.clear() - self.editValue.clear() - self.editKey.setEnabled(False) - self.editValue.setEnabled(False) - self.listBox.clearSelection() + if item := self._getSelectedItem(): + key = self._stripNotAllowed(self.editKey.text()) + value = self.editValue.text() + if key and value: + item.setText(self.C_KEY, f"<{key}>") + item.setText(self.C_REPL, value) self._changed = True return @pyqtSlot() def _addEntry(self) -> None: """Add a new list entry.""" - saveKey = "" % (self.listBox.topLevelItemCount() + 1) - self.listBox.addTopLevelItem(QTreeWidgetItem([saveKey, ""])) + key = f"" + self.listBox.addTopLevelItem(QTreeWidgetItem([key, ""])) return @pyqtSlot() def _delEntry(self) -> None: """Delete the selected entry.""" - if selItem := self._getSelectedItem(): - self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(selItem)) + if item := self._getSelectedItem(): + self.listBox.takeTopLevelItem(self.listBox.indexOfTopLevelItem(item)) self._changed = True return diff --git a/novelwriter/dialogs/wordlist.py b/novelwriter/dialogs/wordlist.py index c82ca3d6..54ec2f2f 100644 --- a/novelwriter/dialogs/wordlist.py +++ b/novelwriter/dialogs/wordlist.py @@ -190,7 +190,7 @@ class GuiWordList(QDialog): )) ffilter = formatFileFilter(["*.txt", "*"]) path, _ = QFileDialog.getOpenFileName( - self, self.tr("Import File"), str(Path.home()), filter=ffilter + self, self.tr("Import File"), str(CONFIG.homePath()), filter=ffilter ) if path: try: @@ -207,7 +207,7 @@ class GuiWordList(QDialog): def _exportWords(self) -> None: """Export words to file.""" path, _ = QFileDialog.getSaveFileName( - self, self.tr("Export File"), str(Path.home()) + self, self.tr("Export File"), str(CONFIG.homePath()) ) if path: try: diff --git a/novelwriter/enum.py b/novelwriter/enum.py index 4750ef5d..1781eb2f 100644 --- a/novelwriter/enum.py +++ b/novelwriter/enum.py @@ -210,3 +210,29 @@ class nwBuildFmt(Enum): J_NWD = 7 # END Enum nwBuildFormat + + +class nwStatusShape(Enum): + + SQUARE = 0 + TRIANGLE = 1 + NABLA = 2 + DIAMOND = 3 + PENTAGON = 4 + HEXAGON = 5 + STAR = 6 + PACMAN = 7 + CIRCLE_Q = 8 + CIRCLE_H = 9 + CIRCLE_T = 10 + CIRCLE = 11 + BARS_1 = 12 + BARS_2 = 13 + BARS_3 = 14 + BARS_4 = 15 + BLOCK_1 = 16 + BLOCK_2 = 17 + BLOCK_3 = 18 + BLOCK_4 = 19 + +# END Enum nwStatusShape diff --git a/novelwriter/error.py b/novelwriter/error.py index 0801d55d..86641011 100644 --- a/novelwriter/error.py +++ b/novelwriter/error.py @@ -54,7 +54,7 @@ def logException() -> None: return -def formatException(exc) -> str: +def formatException(exc: BaseException) -> str: """Format an exception as a string the same way the default exception handler does. """ diff --git a/novelwriter/extensions/circularprogress.py b/novelwriter/extensions/circularprogress.py index bbc2f64a..23ca7d3d 100644 --- a/novelwriter/extensions/circularprogress.py +++ b/novelwriter/extensions/circularprogress.py @@ -27,10 +27,11 @@ from math import ceil from PyQt5.QtCore import QRect from PyQt5.QtGui import QBrush, QColor, QPaintEvent, QPainter, QPen -from PyQt5.QtWidgets import QProgressBar, QSizePolicy, QWidget +from PyQt5.QtWidgets import QProgressBar, QWidget from novelwriter.types import ( - QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSolidLine, QtTransparent + QtPaintAnitAlias, QtAlignCenter, QtRoundCap, QtSizeFixed, QtSolidLine, + QtTransparent ) @@ -59,7 +60,7 @@ class NProgressCircle(QProgressBar): bar=self.palette().highlight().color(), text=self.palette().text().color() ) - self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + self.setSizePolicy(QtSizeFixed, QtSizeFixed) self.setFixedWidth(size) self.setFixedHeight(size) return diff --git a/novelwriter/extensions/modified.py b/novelwriter/extensions/modified.py index 4eb50cff..b83a2662 100644 --- a/novelwriter/extensions/modified.py +++ b/novelwriter/extensions/modified.py @@ -25,6 +25,8 @@ along with this program. If not, see . """ from __future__ import annotations +from enum import Enum + from PyQt5.QtCore import QSize, Qt from PyQt5.QtGui import QWheelEvent from PyQt5.QtWidgets import QComboBox, QDoubleSpinBox, QSpinBox, QToolButton, QWidget @@ -46,7 +48,7 @@ class NComboBox(QComboBox): event.ignore() return - def setCurrentData(self, data: str, default: str) -> None: + def setCurrentData(self, data: str | Enum, default: str | Enum) -> None: """Set the current index from data, with a fallback.""" idx = self.findData(data) self.setCurrentIndex(self.findData(default) if idx < 0 else idx) diff --git a/novelwriter/extensions/pagedsidebar.py b/novelwriter/extensions/pagedsidebar.py index a3c53455..7709509f 100644 --- a/novelwriter/extensions/pagedsidebar.py +++ b/novelwriter/extensions/pagedsidebar.py @@ -28,11 +28,14 @@ from __future__ import annotations from PyQt5.QtGui import QColor, QPaintEvent, QPainter, QPolygon from PyQt5.QtCore import QPoint, QRectF, QSize, Qt, pyqtSignal, pyqtSlot from PyQt5.QtWidgets import ( - QAbstractButton, QAction, QButtonGroup, QLabel, QSizePolicy, QStyle, + QAbstractButton, QAction, QButtonGroup, QLabel, QStyle, QStyleOptionToolButton, QToolBar, QToolButton, QWidget ) -from novelwriter.types import QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen +from novelwriter.types import ( + QtPaintAnitAlias, QtAlignLeft, QtMouseOver, QtNoBrush, QtNoPen, + QtSizeExpanding, QtSizeFixed +) class NPagedSideBar(QToolBar): @@ -59,7 +62,7 @@ class NPagedSideBar(QToolBar): self.setOrientation(Qt.Orientation.Vertical) stretch = QWidget(self) - stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding) self._stretchAction = self.addWidget(stretch) return @@ -119,7 +122,7 @@ class _NPagedToolButton(QToolButton): def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) - self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + self.setSizePolicy(QtSizeExpanding, QtSizeFixed) self.setCheckable(True) fH = self.fontMetrics().height() @@ -197,7 +200,7 @@ class _NPagedToolLabel(QLabel): def __init__(self, parent: QWidget, textColor: QColor | None = None) -> None: super().__init__(parent=parent) - self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Fixed) + self.setSizePolicy(QtSizeExpanding, QtSizeFixed) fH = self.fontMetrics().height() self._bH = round(fH * 1.7) diff --git a/novelwriter/extensions/switch.py b/novelwriter/extensions/switch.py index e35bd79a..1c04a158 100644 --- a/novelwriter/extensions/switch.py +++ b/novelwriter/extensions/switch.py @@ -25,10 +25,10 @@ from __future__ import annotations from PyQt5.QtGui import QMouseEvent, QPainter, QPaintEvent, QResizeEvent from PyQt5.QtCore import QEvent, QPropertyAnimation, Qt, pyqtProperty -from PyQt5.QtWidgets import QAbstractButton, QSizePolicy, QWidget +from PyQt5.QtWidgets import QAbstractButton, QWidget from novelwriter import CONFIG, SHARED -from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen +from novelwriter.types import QtPaintAnitAlias, QtMouseLeft, QtNoPen, QtSizeFixed class NSwitch(QAbstractButton): @@ -46,7 +46,7 @@ class NSwitch(QAbstractButton): self._rR = self._xR - self._rB self.setCheckable(True) - self.setSizePolicy(QSizePolicy.Policy.Fixed, QSizePolicy.Policy.Fixed) + self.setSizePolicy(QtSizeFixed, QtSizeFixed) self.setFixedWidth(self._xW) self.setFixedHeight(self._xH) self._offset = self._xR diff --git a/novelwriter/extensions/switchbox.py b/novelwriter/extensions/switchbox.py index d41587ba..1cff6a9c 100644 --- a/novelwriter/extensions/switchbox.py +++ b/novelwriter/extensions/switchbox.py @@ -25,10 +25,13 @@ from __future__ import annotations from PyQt5.QtGui import QIcon from PyQt5.QtCore import pyqtSignal -from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QSizePolicy, QWidget +from PyQt5.QtWidgets import QGridLayout, QLabel, QScrollArea, QWidget from novelwriter.extensions.switch import NSwitch -from novelwriter.types import QtAlignLeft, QtAlignRight, QtAlignRightMiddle +from novelwriter.types import ( + QtAlignLeft, QtAlignRight, QtAlignRightMiddle, QtSizeMinimum, + QtSizeMinimumExpanding +) class NSwitchBox(QScrollArea): @@ -59,7 +62,7 @@ class NSwitchBox(QScrollArea): self._content.setColumnStretch(1, 1) self._widget = QWidget(self) - self._widget.setSizePolicy(QSizePolicy.Policy.MinimumExpanding, QSizePolicy.Policy.Minimum) + self._widget.setSizePolicy(QtSizeMinimumExpanding, QtSizeMinimum) self._widget.setLayout(self._content) self.setWidgetResizable(True) diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 13525ded..4d603289 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -381,7 +381,7 @@ class GuiDocEditor(QPlainTextEdit): return - def loadText(self, tHandle: str, tLine=None) -> bool: + def loadText(self, tHandle: str, tLine: int | None = None) -> bool: """Load text from a document into the editor. If we have an I/O error, we must handle this and clear the editor so that we don't risk overwriting the file if it exists. This can for instance @@ -497,6 +497,7 @@ class GuiDocEditor(QPlainTextEdit): return False self.setDocumentChanged(False) + self.docTextChanged.emit(self._docHandle, self._lastEdit) oldHeader = self._nwItem.mainHeading oldCount = SHARED.project.index.getHandleHeaderCount(tHandle) @@ -1080,7 +1081,7 @@ class GuiDocEditor(QPlainTextEdit): return @pyqtSlot() - def _cursorMoved(self): + def _cursorMoved(self) -> None: """Triggered when the cursor moved in the editor.""" self.docFooter.updateLineCount(self.textCursor()) return @@ -1470,7 +1471,7 @@ class GuiDocEditor(QPlainTextEdit): # Internal Functions : Text Manipulation ## - def _toggleFormat(self, fLen: int, fChar: str) -> bool: + def _toggleFormat(self, fLen: int, fChar: str) -> None: """Toggle the formatting of a specific type for a piece of text. If more than one block is selected, the formatting is applied to the first block. @@ -1488,12 +1489,12 @@ class GuiDocEditor(QPlainTextEdit): posS = cursor.selectionStart() posE = cursor.selectionEnd() - if self._qDocument.characterAt(posO - 1) == fChar: + if posS == posE and self._qDocument.characterAt(posO - 1) == fChar: logger.warning("Format repetition, cancelling action") cursor.clearSelection() cursor.setPosition(posO) self.setTextCursor(cursor) - return False + return blockS = self._qDocument.findBlock(posS) blockE = self._qDocument.findBlock(posE) @@ -1519,7 +1520,6 @@ class GuiDocEditor(QPlainTextEdit): break if fLen == min(numA, numB): - cursor.clearSelection() cursor.beginEditBlock() cursor.setPosition(posS) for i in range(fLen): @@ -1528,17 +1528,19 @@ class GuiDocEditor(QPlainTextEdit): for i in range(fLen): cursor.deletePreviousChar() cursor.endEditBlock() - cursor.clearSelection() - cursor.setPosition(posO - fLen) - self.setTextCursor(cursor) + + if select != _SelectAction.KEEP_SELECTION: + cursor.clearSelection() + cursor.setPosition(posO - fLen) + self.setTextCursor(cursor) else: self._wrapSelection(fChar*fLen, pos=posO, select=select) - return True + return def _wrapSelection(self, before: str, after: str | None = None, pos: int | None = None, - select: _SelectAction = _SelectAction.NO_DECISION) -> bool: + select: _SelectAction = _SelectAction.NO_DECISION) -> None: """Wrap the selected text in whatever is in tBefore and tAfter. If there is no selection, the autoSelect setting decides the action. AutoSelect will select the word under the cursor before @@ -1578,21 +1580,21 @@ class GuiDocEditor(QPlainTextEdit): if select == _SelectAction.MOVE_AFTER: cursor.setPosition(posE + len(before + after)) elif select == _SelectAction.KEEP_SELECTION: - cursor.setPosition(posE + len(before), QtMoveAnchor) - cursor.setPosition(posS + len(before), QtKeepAnchor) + cursor.setPosition(posS + len(before), QtMoveAnchor) + cursor.setPosition(posE + len(before), QtKeepAnchor) elif select == _SelectAction.KEEP_POSITION: cursor.setPosition(posO + len(before)) self.setTextCursor(cursor) - return True + return - def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> bool: + def _replaceQuotes(self, sQuote: str, oQuote: str, cQuote: str) -> None: """Replace all straight quotes in the selected text.""" cursor = self.textCursor() if not cursor.hasSelection(): SHARED.error(self.tr("Please select some text before calling replace quotes.")) - return False + return posS = cursor.selectionStart() posE = cursor.selectionEnd() @@ -1632,7 +1634,7 @@ class GuiDocEditor(QPlainTextEdit): self._allowAutoReplace(True) - return True + return def _processBlockFormat( self, action: nwDocAction, text: str, toggle: bool = True @@ -2185,7 +2187,7 @@ class MetaCompleter(QMenu): # Internal Functions ## - def _emitComplete(self, pos: int, length: int, value: str): + def _emitComplete(self, pos: int, length: int, value: str) -> None: """Emit the signal to indicate a selection has been made.""" self.complete.emit(pos, length, value) return @@ -2966,7 +2968,7 @@ class GuiDocEditHeader(QWidget): # Events ## - def mousePressEvent(self, event: QMouseEvent): + def mousePressEvent(self, event: QMouseEvent) -> None: """Capture a click on the title and ensure that the item is selected in the project tree. """ @@ -3134,7 +3136,7 @@ class GuiDocEditFooter(QWidget): sText = "" else: iPx = round(0.9*SHARED.theme.baseIconHeight) - status, icon = self._tItem.getImportStatus(incIcon=True) + status, icon = self._tItem.getImportStatus() sIcon = icon.pixmap(iPx, iPx) sText = f"{status} / {self._tItem.describeMe()}" diff --git a/novelwriter/gui/dochighlight.py b/novelwriter/gui/dochighlight.py index 62f88f61..24bacd58 100644 --- a/novelwriter/gui/dochighlight.py +++ b/novelwriter/gui/dochighlight.py @@ -328,7 +328,7 @@ class GuiDocHighlighter(QSyntaxHighlighter): self.setFormat(0, 3, self._hStyles["head2h"]) self.setFormat(3, len(text), self._hStyles["header2"]) - elif text.startswith("###! "): # Hard Scene + elif text.startswith("###! "): # Alternative Scene self.setFormat(0, 4, self._hStyles["head3h"]) self.setFormat(4, len(text), self._hStyles["header3"]) diff --git a/novelwriter/gui/docviewerpanel.py b/novelwriter/gui/docviewerpanel.py index f96e44e6..ed4d9ba6 100644 --- a/novelwriter/gui/docviewerpanel.py +++ b/novelwriter/gui/docviewerpanel.py @@ -450,7 +450,7 @@ class _ViewPanelKeyWords(QTreeWidget): nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, nwItem.mainHeading ) - impLabel, impIcon = nwItem.getImportStatus(incIcon=True) + impLabel, impIcon = nwItem.getImportStatus() iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0) if nwItem.isDocumentLayout() else 5 hDec = SHARED.theme.getHeaderDecorationNarrow(iLevel) diff --git a/novelwriter/gui/itemdetails.py b/novelwriter/gui/itemdetails.py index 5e9ed84d..1a90d2a3 100644 --- a/novelwriter/gui/itemdetails.py +++ b/novelwriter/gui/itemdetails.py @@ -253,7 +253,7 @@ class GuiItemDetails(QWidget): # Status # ====== - status, icon = nwItem.getImportStatus(incIcon=True) + status, icon = nwItem.getImportStatus() self.statusIcon.setPixmap(icon.pixmap(iPx, iPx)) self.statusData.setText(status) diff --git a/novelwriter/gui/mainmenu.py b/novelwriter/gui/mainmenu.py index 96fe2b2c..7d6b05cf 100644 --- a/novelwriter/gui/mainmenu.py +++ b/novelwriter/gui/mainmenu.py @@ -742,8 +742,8 @@ class GuiMainMenu(QMenuBar): lambda: self.requestDocAction.emit(nwDocAction.BLOCK_UNN) ) - # Format > Hard Scene - self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Hard Scene")) + # Format > Alternative Scene + self.aFmtHardSc = self.fmtMenu.addAction(self.tr("Alternative Scene")) self.aFmtHardSc.triggered.connect( lambda: self.requestDocAction.emit(nwDocAction.BLOCK_HSC) ) diff --git a/novelwriter/gui/noveltree.py b/novelwriter/gui/noveltree.py index ef52577d..6643df47 100644 --- a/novelwriter/gui/noveltree.py +++ b/novelwriter/gui/noveltree.py @@ -35,8 +35,8 @@ from PyQt5.QtCore import QModelIndex, QPoint, Qt, pyqtSlot, pyqtSignal from PyQt5.QtGui import QFocusEvent, QFont, QMouseEvent, QPalette, QResizeEvent from PyQt5.QtWidgets import ( QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, - QInputDialog, QMenu, QSizePolicy, QToolTip, QTreeWidget, QTreeWidgetItem, - QVBoxLayout, QWidget + QInputDialog, QMenu, QToolTip, QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget ) from novelwriter import CONFIG, SHARED @@ -47,7 +47,10 @@ from novelwriter.enum import nwDocMode, nwItemClass, nwOutline from novelwriter.extensions.modified import NIconToolButton from novelwriter.extensions.novelselector import NovelSelector from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON -from novelwriter.types import QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtUserRole +from novelwriter.types import ( + QtAlignRight, QtDecoration, QtMouseLeft, QtMouseMiddle, QtSizeExpanding, + QtUserRole +) if TYPE_CHECKING: # pragma: no cover from novelwriter.guimain import GuiMain @@ -215,7 +218,7 @@ class GuiNovelToolBar(QWidget): self.novelValue.setFont(selFont) self.novelValue.setListFormat(self.tr("Outline of {0}")) self.novelValue.setMinimumWidth(CONFIG.pxInt(150)) - self.novelValue.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.novelValue.setSizePolicy(QtSizeExpanding, QtSizeExpanding) self.novelValue.novelSelectionChanged.connect(self.setCurrentRoot) self.tbNovel = NIconToolButton(self, iSz) @@ -344,7 +347,7 @@ class GuiNovelToolBar(QWidget): # Internal Functions ## - def _addLastColAction(self, colType, actionLabel) -> None: + def _addLastColAction(self, colType: NovelTreeColumn, actionLabel: str) -> None: """Add a column selection entry to the last column menu.""" aLast = self.mLastCol.addAction(actionLabel) aLast.setCheckable(True) diff --git a/novelwriter/gui/outline.py b/novelwriter/gui/outline.py index 0d07e5ab..380455ce 100644 --- a/novelwriter/gui/outline.py +++ b/novelwriter/gui/outline.py @@ -36,20 +36,19 @@ from enum import Enum from PyQt5.QtCore import Qt, pyqtSignal, pyqtSlot, QT_TRANSLATE_NOOP from PyQt5.QtWidgets import ( QAbstractItemView, QAction, QFileDialog, QFrame, QGridLayout, QGroupBox, - QHBoxLayout, QLabel, QMenu, QScrollArea, QSizePolicy, QSplitter, QToolBar, - QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget + QHBoxLayout, QLabel, QMenu, QScrollArea, QSplitter, QToolBar, QToolButton, + QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED -from novelwriter.enum import ( - nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline -) +from novelwriter.enum import nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline from novelwriter.error import logException from novelwriter.common import checkInt, formatFileFilter, makeFileNameSafe from novelwriter.constants import nwHeaders, trConst, nwKeyWords, nwLabels from novelwriter.extensions.novelselector import NovelSelector from novelwriter.types import ( - QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration, QtUserRole + QtAlignLeftTop, QtAlignRight, QtAlignRightTop, QtDecoration, + QtSizeExpanding, QtUserRole ) @@ -190,7 +189,7 @@ class GuiOutlineView(QWidget): return @pyqtSlot(str) - def _rootItemChanged(self, tHandle) -> None: + def _rootItemChanged(self, tHandle: str) -> None: """Handle root novel changed or needs to be refreshed.""" self.outlineTree.refreshTree(rootHandle=(tHandle or None), overRide=True) return @@ -217,7 +216,7 @@ class GuiOutlineToolBar(QToolBar): self.setContentsMargins(0, 0, 0, 0) stretch = QWidget(self) - stretch.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + stretch.setSizePolicy(QtSizeExpanding, QtSizeExpanding) # Novel Selector self.novelLabel = QLabel(self.tr("Outline of"), self) @@ -428,7 +427,7 @@ class GuiOutlineTree(QTreeWidget): ## @property - def hiddenColumns(self): + def hiddenColumns(self) -> dict[nwOutline, bool]: return self._colHidden ## @@ -586,7 +585,7 @@ class GuiOutlineTree(QTreeWidget): # Internal Functions ## - def _loadHeaderState(self): + def _loadHeaderState(self) -> None: """Load the state of the main tree header, that is, column order and column width. """ @@ -1048,7 +1047,7 @@ class GuiOutlineDetails(QScrollArea): self.titleLabel.setText(self.tr(self.LVL_MAP.get(novIdx.level, "H1"))) self.titleValue.setText(novIdx.title) - itemStatus, _ = nwItem.getImportStatus(incIcon=False) + itemStatus, _ = nwItem.getImportStatus() self.fileValue.setText(nwItem.itemName) self.itemValue.setText(itemStatus) diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 2ade45d8..8074325b 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -38,8 +38,8 @@ from PyQt5.QtGui import ( ) from PyQt5.QtWidgets import ( QAbstractItemView, QAction, QDialog, QFrame, QHBoxLayout, QHeaderView, - QLabel, QMenu, QShortcut, QSizePolicy, QTreeWidget, QTreeWidgetItem, - QVBoxLayout, QWidget + QLabel, QMenu, QShortcut, QTreeWidget, QTreeWidgetItem, QVBoxLayout, + QWidget ) from novelwriter import CONFIG, SHARED @@ -54,7 +54,10 @@ from novelwriter.dialogs.projectsettings import GuiProjectSettings from novelwriter.enum import nwDocMode, nwItemType, nwItemClass, nwItemLayout from novelwriter.extensions.modified import NIconToolButton from novelwriter.gui.theme import STYLES_MIN_TOOLBUTTON -from novelwriter.types import QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtUserRole +from novelwriter.types import ( + QtAlignLeft, QtAlignRight, QtMouseLeft, QtMouseMiddle, QtSizeExpanding, + QtUserRole, +) if TYPE_CHECKING: # pragma: no cover from novelwriter.guimain import GuiMain @@ -274,7 +277,7 @@ class GuiProjectToolBar(QWidget): self.viewLabel = QLabel(self.tr("Project Content"), self) self.viewLabel.setFont(SHARED.theme.guiFontB) self.viewLabel.setContentsMargins(0, 0, 0, 0) - self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding) + self.viewLabel.setSizePolicy(QtSizeExpanding, QtSizeExpanding) # Quick Links self.mQuick = QMenu(self) @@ -453,7 +456,7 @@ class GuiProjectToolBar(QWidget): def _buildRootMenu(self) -> None: """Build the rood folder menu.""" - def addClass(itemClass): + def addClass(itemClass: nwItemClass) -> None: aNew = self.mAddRoot.addAction(trConst(nwLabels.CLASS_NAME[itemClass])) aNew.setIcon(SHARED.theme.getIcon(nwLabels.CLASS_ICON[itemClass])) aNew.triggered.connect(lambda: self.projTree.newTreeItem(nwItemType.ROOT, itemClass)) @@ -1033,7 +1036,7 @@ class GuiProjectTree(QTreeWidget): if trItem is None or nwItem is None: return - itemStatus, statusIcon = nwItem.getImportStatus(incIcon=True) + itemStatus, statusIcon = nwItem.getImportStatus() hLevel = nwItem.mainHeading itemIcon = SHARED.theme.getItemIcon( nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel @@ -1855,11 +1858,11 @@ class _TreeContextMenu(QMenu): if self._item.isNovelLike(): menu = self.addMenu(self.tr("Set Status to ...")) current = self._item.itemStatus - for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.items()): - name = entry["name"] + for n, (key, entry) in enumerate(SHARED.project.data.itemStatus.iterItems()): + name = entry.name if not multi and current == key: name += f" ({nwUnicode.U_CHECK})" - action = menu.addAction(entry["icon"], name) + action = menu.addAction(entry.icon, name) if multi: action.triggered.connect(lambda n, key=key: self._iterSetItemStatus(key)) else: @@ -1872,11 +1875,11 @@ class _TreeContextMenu(QMenu): else: menu = self.addMenu(self.tr("Set Importance to ...")) current = self._item.itemImport - for n, (key, entry) in enumerate(SHARED.project.data.itemImport.items()): - name = entry["name"] + for n, (key, entry) in enumerate(SHARED.project.data.itemImport.iterItems()): + name = entry.name if not multi and current == key: name += f" ({nwUnicode.U_CHECK})" - action = menu.addAction(entry["icon"], name) + action = menu.addAction(entry.icon, name) if multi: action.triggered.connect(lambda n, key=key: self._iterSetItemImport(key)) else: diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index c636f677..c13283f4 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -74,9 +74,9 @@ class GuiTheme: self.isLightTheme = True # GUI - self.statNone = QColor(120, 120, 120) - self.statUnsaved = QColor(200, 15, 39) - self.statSaved = QColor(2, 133, 37) + self.statNone = QColor(0, 0, 0) + self.statUnsaved = QColor(0, 0, 0) + self.statSaved = QColor(0, 0, 0) self.helpText = QColor(0, 0, 0) # Loaded Syntax Settings @@ -227,6 +227,10 @@ class GuiTheme: logException() return False + # Reset Palette + self._guiPalette = QApplication.style().standardPalette() + self._resetGuiColors() + # Main sec = "Main" if parser.has_section(sec): @@ -256,8 +260,6 @@ class GuiTheme: self._setPalette(parser, sec, "highlightedtext", QPalette.ColorRole.HighlightedText) self._setPalette(parser, sec, "link", QPalette.ColorRole.Link) self._setPalette(parser, sec, "linkvisited", QPalette.ColorRole.LinkVisited) - else: - self._guiPalette = QApplication.style().standardPalette() # GUI sec = "GUI" @@ -393,6 +395,14 @@ class GuiTheme: # Internal Functions ## + def _resetGuiColors(self) -> None: + """Reset GUI colours to default values.""" + self.statNone = QColor(120, 120, 120) + self.statUnsaved = QColor(200, 15, 39) + self.statSaved = QColor(2, 133, 37) + self.helpText = QColor(0, 0, 0) + return + def _setGuiFont(self) -> None: """Update the GUI's font style from settings.""" font = QFont() diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index 47b288d0..105d2652 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -944,7 +944,7 @@ class GuiMain(QMainWindow): # Events ## - def closeEvent(self, event: QCloseEvent): + def closeEvent(self, event: QCloseEvent) -> None: """Capture the closing event of the GUI and call the close function to handle all the close process steps. """ @@ -1201,7 +1201,7 @@ class GuiMain(QMainWindow): return @pyqtSlot() - def _toggleViewerPanelVisibility(self): + def _toggleViewerPanelVisibility(self) -> None: """Toggle the visibility of the document viewer panel.""" CONFIG.showViewerPanel = not CONFIG.showViewerPanel self.docViewerPanel.setVisible(CONFIG.showViewerPanel) diff --git a/novelwriter/text/counting.py b/novelwriter/text/counting.py index 59974ee5..61804edb 100644 --- a/novelwriter/text/counting.py +++ b/novelwriter/text/counting.py @@ -57,6 +57,7 @@ def preProcessText(text: str, keepHeaders: bool = True) -> list[str]: continue if line[0] == ">": line = line.lstrip(">").lstrip(" ") + if line: # Above block can return empty line (Issue #1816) if line[-1] == "<": line = line.rstrip("<").rstrip(" ") if "[" in line: diff --git a/novelwriter/tools/dictionaries.py b/novelwriter/tools/dictionaries.py index a944c745..a2607e63 100644 --- a/novelwriter/tools/dictionaries.py +++ b/novelwriter/tools/dictionaries.py @@ -179,7 +179,7 @@ class GuiDictionaries(QDialog): ## @pyqtSlot() - def _doBrowseHunspell(self): + def _doBrowseHunspell(self) -> None: """Browse for a Free/Libre Office dictionary.""" ffilter = formatFileFilter([ (self.tr("Free or Libre Office extension"), "*.sox *.oxt"), "*" @@ -193,7 +193,7 @@ class GuiDictionaries(QDialog): return @pyqtSlot() - def _doImportHunspell(self): + def _doImportHunspell(self) -> None: """Import a hunspell dictionary from .sox or .oxt file.""" procErr = self.tr("Could not process dictionary file") if self._installPath: diff --git a/novelwriter/tools/manusbuild.py b/novelwriter/tools/manusbuild.py index 301e4432..68b5e867 100644 --- a/novelwriter/tools/manusbuild.py +++ b/novelwriter/tools/manusbuild.py @@ -60,7 +60,7 @@ class GuiManuscriptBuild(QDialog): D_KEY = QtUserRole - def __init__(self, parent: QWidget, build: BuildSettings): + def __init__(self, parent: QWidget, build: BuildSettings) -> None: super().__init__(parent=parent) logger.debug("Create: GuiManuscriptBuild") @@ -260,7 +260,7 @@ class GuiManuscriptBuild(QDialog): ## @pyqtSlot("QAbstractButton*") - def _dialogButtonClicked(self, button: QAbstractButton): + def _dialogButtonClicked(self, button: QAbstractButton) -> None: """Handle button clicks from the dialog button box.""" role = self.dlgButtons.buttonRole(button) if role == QtRoleAction: @@ -273,7 +273,7 @@ class GuiManuscriptBuild(QDialog): return @pyqtSlot() - def _doSelectPath(self): + def _doSelectPath(self) -> None: """Select a folder for output.""" bPath = Path(self.buildPath.text()) bPath = bPath if bPath.is_dir() else self._build.lastPath @@ -285,7 +285,7 @@ class GuiManuscriptBuild(QDialog): return @pyqtSlot() - def _doResetBuildName(self): + def _doResetBuildName(self) -> None: """Generate a default build name.""" bName = f"{SHARED.project.data.name} - {self._build.name}" self.buildName.setText(bName) @@ -293,7 +293,7 @@ class GuiManuscriptBuild(QDialog): return @pyqtSlot() - def _resetProgress(self): + def _resetProgress(self) -> None: """Set the progress bar back to 0.""" self.buildProgress.setValue(0) return @@ -350,7 +350,7 @@ class GuiManuscriptBuild(QDialog): return items[0].data(self.D_KEY) return None - def _saveSettings(self): + def _saveSettings(self) -> None: """Save the user GUI settings.""" winWidth = CONFIG.rpxInt(self.width()) winHeight = CONFIG.rpxInt(self.height()) @@ -369,7 +369,7 @@ class GuiManuscriptBuild(QDialog): return - def _populateContentList(self): + def _populateContentList(self) -> None: """Build the content list.""" rootMap = {} filtered = self._build.buildItemFilter(SHARED.project) @@ -398,7 +398,7 @@ class GuiManuscriptBuild(QDialog): return - def _openOutputFolder(self): + def _openOutputFolder(self) -> None: """Open the build folder in the system's file explorer.""" openExternalPath(Path(self.buildPath.text())) return diff --git a/novelwriter/tools/manuscript.py b/novelwriter/tools/manuscript.py index 3b81429e..782a7109 100644 --- a/novelwriter/tools/manuscript.py +++ b/novelwriter/tools/manuscript.py @@ -36,8 +36,8 @@ from PyQt5.QtPrintSupport import QPrintPreviewDialog, QPrinter from PyQt5.QtWidgets import ( QAbstractItemView, QApplication, QDialog, QFormLayout, QGridLayout, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, QPushButton, - QSizePolicy, QSplitter, QStackedWidget, QTabWidget, QTextBrowser, - QTreeWidget, QTreeWidgetItem, QVBoxLayout, QWidget + QSplitter, QStackedWidget, QTabWidget, QTextBrowser, QTreeWidget, + QTreeWidgetItem, QVBoxLayout, QWidget ) from novelwriter import CONFIG, SHARED @@ -54,7 +54,7 @@ from novelwriter.tools.manusbuild import GuiManuscriptBuild from novelwriter.tools.manussettings import GuiBuildSettings from novelwriter.types import ( QtAlignAbsolute, QtAlignCenter, QtAlignJustify, QtAlignRight, QtAlignTop, - QtUserRole + QtSizeExpanding, QtSizeIgnored, QtUserRole ) if TYPE_CHECKING: # pragma: no cover @@ -303,25 +303,21 @@ class GuiManuscript(QDialog): @pyqtSlot() def _editSelectedBuild(self) -> None: """Edit the currently selected build settings entry.""" - build = self._getSelectedBuild() - if build is not None: + if build := self._getSelectedBuild(): self._openSettingsDialog(build) return @pyqtSlot("QListWidgetItem*", "QListWidgetItem*") def _updateBuildDetails(self, current: QListWidgetItem, previous: QListWidgetItem) -> None: """Process change of build selection to update the details.""" - if isinstance(current, QListWidgetItem): - build = self._builds.getBuild(current.data(self.D_KEY)) - if build is not None: - self.buildDetails.updateInfo(build) + if current and (build := self._builds.getBuild(current.data(self.D_KEY))): + self.buildDetails.updateInfo(build) return @pyqtSlot() def _deleteSelectedBuild(self) -> None: """Delete the currently selected build settings entry.""" - build = self._getSelectedBuild() - if build is not None: + if build := self._getSelectedBuild(): if SHARED.question(self.tr("Delete build '{0}'?".format(build.name))): self._builds.removeBuild(build.buildID) self._updateBuildsList() @@ -332,8 +328,7 @@ class GuiManuscript(QDialog): """Process new build settings from the settings dialog.""" self._builds.setBuild(build) self._updateBuildItem(build) - current = self.buildList.currentItem() - if isinstance(current, QListWidgetItem) and current.data(self.D_KEY) == build.buildID: + if (current := self.buildList.currentItem()) and current.data(self.D_KEY) == build.buildID: self._updateBuildDetails(current, current) return @@ -342,8 +337,7 @@ class GuiManuscript(QDialog): """Run the document builder on the current build settings for the preview widget. """ - build = self._getSelectedBuild() - if build is None: + if not (build := self._getSelectedBuild()): return docBuild = NWBuildDocument(SHARED.project, build) @@ -383,8 +377,7 @@ class GuiManuscript(QDialog): @pyqtSlot() def _buildManuscript(self) -> None: """Open the build dialog and build the manuscript.""" - build = self._getSelectedBuild() - if isinstance(build, BuildSettings): + if build := self._getSelectedBuild(): dlgBuild = GuiManuscriptBuild(self, build) dlgBuild.exec() @@ -627,7 +620,7 @@ class _DetailsWidget(QWidget): ("headings.fmtChapter", "headings.hideChapter"), ("headings.fmtUnnumbered", "headings.hideUnnumbered"), ("headings.fmtScene", "headings.hideScene"), - ("headings.fmtHardScene", "headings.hideHardScene"), + ("headings.fmtAltScene", "headings.hideAltScene"), ("headings.fmtSection", "headings.hideSection"), ]: sub = QTreeWidgetItem() @@ -1030,16 +1023,14 @@ class _StatsWidget(QWidget): @pyqtSlot(bool) def _toggleView(self, state: bool) -> None: """Toggle minimal or maximal view.""" - ignored = QSizePolicy.Policy.Ignored - expanded = QSizePolicy.Policy.Expanding if state: self.mainStack.setCurrentWidget(self.maxWidget) - self.maxWidget.setSizePolicy(expanded, expanded) - self.minWidget.setSizePolicy(ignored, ignored) + self.maxWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding) + self.minWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored) else: self.mainStack.setCurrentWidget(self.minWidget) - self.maxWidget.setSizePolicy(ignored, ignored) - self.minWidget.setSizePolicy(expanded, expanded) + self.maxWidget.setSizePolicy(QtSizeIgnored, QtSizeIgnored) + self.minWidget.setSizePolicy(QtSizeExpanding, QtSizeExpanding) self.maxWidget.adjustSize() self.minWidget.adjustSize() self.mainStack.adjustSize() diff --git a/novelwriter/tools/manussettings.py b/novelwriter/tools/manussettings.py index ebbcbb17..18786ffa 100644 --- a/novelwriter/tools/manussettings.py +++ b/novelwriter/tools/manussettings.py @@ -669,20 +669,20 @@ class _HeadingsTab(NScrollablePage): self.formatBox.addWidget(self.swtScene, 3, 4) # Alt Scene Heading - self.lblHScene = QLabel(self._build.getLabel("headings.fmtHardScene"), self) - self.fmtHScene = QLineEdit("", self) - self.fmtHScene.setReadOnly(True) - self.btnHScene = NIconToolButton(self, iSz, "edit") - self.btnHScene.clicked.connect(lambda: self._editHeading(self.EDIT_HSCENE)) - self.hdeHScene = QLabel(trHide, self) - self.hdeHScene.setIndent(bSp) - self.swtHScene = NSwitch(self, height=iPx) + self.lblAScene = QLabel(self._build.getLabel("headings.fmtAltScene"), self) + self.fmtAScene = QLineEdit("", self) + self.fmtAScene.setReadOnly(True) + self.btnAScene = NIconToolButton(self, iSz, "edit") + self.btnAScene.clicked.connect(lambda: self._editHeading(self.EDIT_HSCENE)) + self.hdeAScene = QLabel(trHide, self) + self.hdeAScene.setIndent(bSp) + self.swtAScene = NSwitch(self, height=iPx) - self.formatBox.addWidget(self.lblHScene, 4, 0) - self.formatBox.addWidget(self.fmtHScene, 4, 1) - self.formatBox.addWidget(self.btnHScene, 4, 2) - self.formatBox.addWidget(self.hdeHScene, 4, 3) - self.formatBox.addWidget(self.swtHScene, 4, 4) + self.formatBox.addWidget(self.lblAScene, 4, 0) + self.formatBox.addWidget(self.fmtAScene, 4, 1) + self.formatBox.addWidget(self.btnAScene, 4, 2) + self.formatBox.addWidget(self.hdeAScene, 4, 3) + self.formatBox.addWidget(self.swtAScene, 4, 4) # Section Heading self.lblSection = QLabel(self._build.getLabel("headings.fmtSection"), self) @@ -826,14 +826,14 @@ class _HeadingsTab(NScrollablePage): self.fmtChapter.setText(self._build.getStr("headings.fmtChapter")) self.fmtUnnumbered.setText(self._build.getStr("headings.fmtUnnumbered")) self.fmtScene.setText(self._build.getStr("headings.fmtScene")) - self.fmtHScene.setText(self._build.getStr("headings.fmtHardScene")) + self.fmtAScene.setText(self._build.getStr("headings.fmtAltScene")) self.fmtSection.setText(self._build.getStr("headings.fmtSection")) self.swtTitle.setChecked(self._build.getBool("headings.hideTitle")) self.swtChapter.setChecked(self._build.getBool("headings.hideChapter")) self.swtUnnumbered.setChecked(self._build.getBool("headings.hideUnnumbered")) self.swtScene.setChecked(self._build.getBool("headings.hideScene")) - self.swtHScene.setChecked(self._build.getBool("headings.hideHardScene")) + self.swtAScene.setChecked(self._build.getBool("headings.hideAltScene")) self.swtSection.setChecked(self._build.getBool("headings.hideSection")) self.centerTitle.setChecked(self._build.getBool("headings.centerTitle")) @@ -850,7 +850,7 @@ class _HeadingsTab(NScrollablePage): self._build.setValue("headings.hideChapter", self.swtChapter.isChecked()) self._build.setValue("headings.hideUnnumbered", self.swtUnnumbered.isChecked()) self._build.setValue("headings.hideScene", self.swtScene.isChecked()) - self._build.setValue("headings.hideHardScene", self.swtHScene.isChecked()) + self._build.setValue("headings.hideAltScene", self.swtAScene.isChecked()) self._build.setValue("headings.hideSection", self.swtSection.isChecked()) self._build.setValue("headings.centerTitle", self.centerTitle.isChecked()) @@ -890,8 +890,8 @@ class _HeadingsTab(NScrollablePage): text = self.fmtScene.text() label = self._build.getLabel("headings.fmtScene") elif heading == self.EDIT_HSCENE: - text = self.fmtHScene.text() - label = self._build.getLabel("headings.fmtHardScene") + text = self.fmtAScene.text() + label = self._build.getLabel("headings.fmtAltScene") elif heading == self.EDIT_SECTION: text = self.fmtSection.text() label = self._build.getLabel("headings.fmtSection") @@ -928,8 +928,8 @@ class _HeadingsTab(NScrollablePage): self.fmtScene.setText(text) self._build.setValue("headings.fmtScene", text) elif heading == self.EDIT_HSCENE: - self.fmtHScene.setText(text) - self._build.setValue("headings.fmtHardScene", text) + self.fmtAScene.setText(text) + self._build.setValue("headings.fmtAltScene", text) elif heading == self.EDIT_SECTION: self.fmtSection.setText(text) self._build.setValue("headings.fmtSection", text) @@ -1312,7 +1312,7 @@ class _FormatTab(NScrollableForm): return @pyqtSlot() - def _pageSizeValueChanged(self): + def _pageSizeValueChanged(self) -> None: """The user has changed the page size spin boxes, so we flip the page size box to Custom. """ diff --git a/novelwriter/tools/welcome.py b/novelwriter/tools/welcome.py index c8f26727..ce37539b 100644 --- a/novelwriter/tools/welcome.py +++ b/novelwriter/tools/welcome.py @@ -560,7 +560,7 @@ class _NewProjectForm(QWidget): def __init__(self, parent: QWidget) -> None: super().__init__(parent=parent) - self._basePath = CONFIG.lastPath() + self._basePath = CONFIG.homePath() self._fillMode = self.FILL_BLANK self._copyPath = None @@ -632,7 +632,7 @@ class _NewProjectForm(QWidget): self.numChapters = NSpinBox(self) self.numChapters.setRange(0, 200) - self.numChapters.setValue(5) + self.numChapters.setValue(0) self.numChapters.setToolTip(self.tr("Set to 0 to only add scenes")) self.chapterBox = NWrappedWidgetBox( @@ -642,7 +642,7 @@ class _NewProjectForm(QWidget): self.numScenes = NSpinBox(self) self.numScenes.setRange(0, 200) - self.numScenes.setValue(5) + self.numScenes.setValue(0) self.sceneBox = NWrappedWidgetBox( self.tr("Add {0} scene documents (to each chapter)"), self.numScenes @@ -742,7 +742,6 @@ class _NewProjectForm(QWidget): ): self._basePath = Path(projDir) self._updateProjPath() - CONFIG.setLastPath(self._basePath) return @pyqtSlot() @@ -753,7 +752,7 @@ class _NewProjectForm(QWidget): return @pyqtSlot() - def _syncSwitches(self): + def _syncSwitches(self) -> None: """Check if the add notes option should also be switched off.""" addPlot = self.addPlot.isChecked() addChar = self.addChar.isChecked() diff --git a/novelwriter/types.py b/novelwriter/types.py index 56e59604..49e670ce 100644 --- a/novelwriter/types.py +++ b/novelwriter/types.py @@ -25,7 +25,7 @@ from __future__ import annotations from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor, QPainter, QTextCursor -from PyQt5.QtWidgets import QDialogButtonBox, QStyle +from PyQt5.QtWidgets import QDialogButtonBox, QSizePolicy, QStyle # Qt Alignment Flags @@ -88,3 +88,11 @@ QtKeepAnchor = QTextCursor.MoveMode.KeepAnchor QtMoveAnchor = QTextCursor.MoveMode.MoveAnchor QtMoveLeft = QTextCursor.MoveOperation.Left QtMoveRight = QTextCursor.MoveOperation.Right + +# Size Policy + +QtSizeExpanding = QSizePolicy.Policy.Expanding +QtSizeFixed = QSizePolicy.Policy.Fixed +QtSizeIgnored = QSizePolicy.Policy.Ignored +QtSizeMinimum = QSizePolicy.Policy.Minimum +QtSizeMinimumExpanding = QSizePolicy.Policy.MinimumExpanding diff --git a/pkgutils.py b/pkgutils.py index 2144000a..9d51551d 100755 --- a/pkgutils.py +++ b/pkgutils.py @@ -47,7 +47,7 @@ def extractVersion(beQuiet: bool = False) -> tuple[str, str, str]: """Extract the novelWriter version number without having to import anything else from the main package. """ - def getValue(text): + def getValue(text: str) -> str: bits = text.partition("=") return bits[2].strip().strip('"') @@ -176,7 +176,7 @@ def cleanBuildDirs() -> None: print("Cleaning up build environment ...") print("") - def removeFolder(rmDir): + def removeFolder(rmDir: str) -> None: if os.path.isdir(rmDir): try: shutil.rmtree(rmDir) @@ -320,7 +320,7 @@ def buildQtI18nTS(sysArgs: list[str]) -> None: print("=============================") try: - from PyQt6.lupdate import lupdate + from PyQt6.lupdate.lupdate import lupdate except ImportError: print("ERROR: This command requires lupdate from PyQt6") print("On Debian/Ubuntu, install: pyqt6-dev-tools") @@ -911,7 +911,7 @@ def makeAppImage(sysArgs: list[str]) -> list[str]: import argparse try: - import python_appimage # noqa F401 + import python_appimage # noqa: F401 # type: ignore except ImportError: print( "ERROR: Package 'python-appimage' is missing on this system.\n" @@ -1252,12 +1252,12 @@ def makeWindowsEmbedded(sysArgs: list[str]) -> None: # Clean Up Files # ============== - def unlinkIfFound(delFile): + def unlinkIfFound(delFile: str) -> None: if os.path.isfile(delFile): os.unlink(delFile) print("Deleted: %s" % delFile) - def deleteFolder(delPath): + def deleteFolder(delPath: str) -> None: if os.path.isdir(delPath): shutil.rmtree(delPath) print("Deleted: %s" % delPath) @@ -1561,6 +1561,9 @@ def xdgUninstall() -> None: def winInstall() -> None: """Will attempt to install icons and make a launcher for Windows.""" + if sys.platform != "win32": + raise Exception("This method only runs on Windows") + import winreg try: import win32com.client @@ -1654,7 +1657,7 @@ def winInstall() -> None: print("") print("Creating registry keys ...") - def setKey(kPath, kName, kVal): + def setKey(kPath: str, kName: str, kVal: str) -> None: winreg.CreateKey(winreg.HKEY_CURRENT_USER, kPath) regKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, kPath, 0, winreg.KEY_WRITE) winreg.SetValueEx(regKey, kName, 0, winreg.REG_SZ, kVal) @@ -1688,6 +1691,9 @@ def winInstall() -> None: def winUninstall() -> None: """Will attempt to uninstall icons previously installed.""" + if sys.platform != "win32": + raise Exception("This method only runs on Windows") + import winreg try: import win32com.client diff --git a/requirements-dev.txt b/requirements-dev.txt index 65bec68e..baf54924 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,2 +1,3 @@ flake8 flake8-pep585 +flake8-annotations diff --git a/sample/nwProject.nwx b/sample/nwProject.nwx index 24170a34..716b21e7 100644 --- a/sample/nwProject.nwx +++ b/sample/nwProject.nwx @@ -1,6 +1,6 @@ - - + + Sample Project Jane Smith @@ -20,19 +20,20 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Background + Minor + Major + Main @@ -57,7 +58,7 @@ Chapter One - + Making a Scene @@ -78,7 +79,7 @@ - We Found John! + We Found John! @@ -90,7 +91,7 @@ - Chapter One + Chapter One @@ -102,11 +103,11 @@ - John Smith + John Smith - Jane Smith + Jane Smith @@ -114,15 +115,15 @@ - Earth + Earth - Space + Space - Mars + Mars diff --git a/tests/files/nwProject-1.5.nwx b/tests/files/nwProject-1.5.nwx index 7823c057..35eab583 100644 --- a/tests/files/nwProject-1.5.nwx +++ b/tests/files/nwProject-1.5.nwx @@ -1,5 +1,5 @@ - + Sample Project Jane Smith @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/mocked.py b/tests/mocked.py index 65b65d93..5f9fc6a3 100644 --- a/tests/mocked.py +++ b/tests/mocked.py @@ -75,7 +75,7 @@ class MockStatusBar: class MockTheme: def __init__(self): - self.baseIconHeight = 10 + self.baseIconHeight = 20 return def getPixmap(self, *a): diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index 4e218943..668c3fb9 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -1,5 +1,5 @@ [Meta] -timestamp = 2024-03-25 11:57:35 +timestamp = 2024-04-13 18:57:38 [Main] theme = default diff --git a/tests/reference/coreProject_NewFileFolder_nwProject.nwx b/tests/reference/coreProject_NewFileFolder_nwProject.nwx index 2422fc02..4aba7481 100644 --- a/tests/reference/coreProject_NewFileFolder_nwProject.nwx +++ b/tests/reference/coreProject_NewFileFolder_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreProject_NewRoot_nwProject.nwx b/tests/reference/coreProject_NewRoot_nwProject.nwx index 028da14c..4bfbb568 100644 --- a/tests/reference/coreProject_NewRoot_nwProject.nwx +++ b/tests/reference/coreProject_NewRoot_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_DocDuplicator_nwProject.nwx b/tests/reference/coreTools_DocDuplicator_nwProject.nwx index bab9a82d..5f6685a6 100644 --- a/tests/reference/coreTools_DocDuplicator_nwProject.nwx +++ b/tests/reference/coreTools_DocDuplicator_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx index 60c0fce3..01d3a0ef 100644 --- a/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx +++ b/tests/reference/coreTools_ProjectBuilderA_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Project A Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx index 86980406..ed43d5cd 100644 --- a/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx +++ b/tests/reference/coreTools_ProjectBuilderB_nwProject.nwx @@ -1,5 +1,5 @@ - + Test Project B Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/guiEditor_Main_Final_nwProject.nwx b/tests/reference/guiEditor_Main_Final_nwProject.nwx index c3ee941f..2c6603eb 100644 --- a/tests/reference/guiEditor_Main_Final_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Final_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/guiEditor_Main_Initial_nwProject.nwx b/tests/reference/guiEditor_Main_Initial_nwProject.nwx index aa9c3a74..f5119a55 100644 --- a/tests/reference/guiEditor_Main_Initial_nwProject.nwx +++ b/tests/reference/guiEditor_Main_Initial_nwProject.nwx @@ -1,5 +1,5 @@ - + New Project Jane Doe @@ -16,16 +16,16 @@ - New - Note - Draft - Finished + New + Note + Draft + Finished - New - Minor - Major - Main + New + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy10.nwx b/tests/reference/projectXML_ReadLegacy10.nwx index a8f9e945..70885cfd 100644 --- a/tests/reference/projectXML_ReadLegacy10.nwx +++ b/tests/reference/projectXML_ReadLegacy10.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy11.nwx b/tests/reference/projectXML_ReadLegacy11.nwx index c216f88a..1d93bd02 100644 --- a/tests/reference/projectXML_ReadLegacy11.nwx +++ b/tests/reference/projectXML_ReadLegacy11.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy12.nwx b/tests/reference/projectXML_ReadLegacy12.nwx index b3cad791..d344f378 100644 --- a/tests/reference/projectXML_ReadLegacy12.nwx +++ b/tests/reference/projectXML_ReadLegacy12.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy13.nwx b/tests/reference/projectXML_ReadLegacy13.nwx index 4bd25a9a..05a4dfa5 100644 --- a/tests/reference/projectXML_ReadLegacy13.nwx +++ b/tests/reference/projectXML_ReadLegacy13.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/reference/projectXML_ReadLegacy14.nwx b/tests/reference/projectXML_ReadLegacy14.nwx index 1ca92061..2a7e24d0 100644 --- a/tests/reference/projectXML_ReadLegacy14.nwx +++ b/tests/reference/projectXML_ReadLegacy14.nwx @@ -1,5 +1,5 @@ - + Sample Project Jay Doh @@ -20,19 +20,19 @@ D - New - Notes - Started - 1st Draft - 2nd Draft - 3rd Draft - Finished + New + Notes + Started + 1st Draft + 2nd Draft + 3rd Draft + Finished - None - Minor - Major - Main + None + Minor + Major + Main diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 5ec474fe..7c9cc88d 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -200,6 +200,9 @@ def testBaseConfig_Methods(fncPath): tstConf = Config() tstConf.initConfig(confPath=fncPath, dataPath=fncPath) + # Home Path + assert tstConf.homePath() == Path.home().absolute() + # Data Path assert tstConf.dataPath() == fncPath assert tstConf.dataPath("stuff") == fncPath / "stuff" diff --git a/tests/test_core/test_core_buildsettings.py b/tests/test_core/test_core_buildsettings.py index bccccae1..10c63253 100644 --- a/tests/test_core/test_core_buildsettings.py +++ b/tests/test_core/test_core_buildsettings.py @@ -27,6 +27,7 @@ import shutil from pathlib import Path +from novelwriter import CONFIG from tools import C, buildTestProject from mocked import causeOSError @@ -58,7 +59,7 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path): build.setName("Test Build") assert build.name == "Test Build" - # Only valid UUIDs are accpeted, anything else generates a new UUID + # Only valid UUIDs are accepted, anything else generates a new UUID build.setBuildID("5cf45d24-f496-42c9-8733-529a9e52a62b") assert build.buildID == "5cf45d24-f496-42c9-8733-529a9e52a62b" @@ -72,14 +73,14 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path): # Last path must be valid, if not it defaults to $HOME build.setLastPath("/path/to/nowhere") - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() build.setLastPath(None) - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() (fncPath / "test.txt").write_text("foobar") build.setLastPath(fncPath / "test.txt") # Can't be a file - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() build.setLastPath(fncPath) assert build.lastPath == fncPath @@ -93,7 +94,7 @@ def testCoreBuildSettings_ClassAttributes(fncPath: Path): build.setLastPath(testDir) assert build.lastPath == testDir testDir.rmdir() - assert build.lastPath == Path.home() + assert build.lastPath == CONFIG.homePath() # Last build name build.setLastBuildName(None) # type: ignore diff --git a/tests/test_core/test_core_item.py b/tests/test_core/test_core_item.py index 151d89da..65cf7b5f 100644 --- a/tests/test_core/test_core_item.py +++ b/tests/test_core/test_core_item.py @@ -553,8 +553,8 @@ def testCoreItem_ClassDefaults(mockGUI): def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd): """Test packing and unpacking entries for the NWItem class.""" project = NWProject() - project.data.itemStatus.write(None, "New", (100, 100, 100)) - project.data.itemImport.write(None, "New", (100, 100, 100)) + project.data.itemStatus.add(None, "New", (100, 100, 100), "SQUARE", 0) + project.data.itemImport.add(None, "New", (100, 100, 100), "SQUARE", 0) # Invalid item = NWItem(project, "0000000000000") diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index 8a4c0b8f..3304fc14 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -400,115 +400,6 @@ def testCoreProject_AccessItems(mockGUI, fncPath, mockRnd): # END Test testCoreProject_AccessItems -@pytest.mark.core -def testCoreProject_StatusImport(mockGUI, fncPath, mockRnd): - """Test the status and importance flag handling.""" - project = NWProject() - mockRnd.reset() - buildTestProject(project, fncPath) - - statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished] - importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain] - - # Change Status - # ============= - - project.tree[C.hNovelRoot].setStatus(statusKeys[3]) # type: ignore - project.tree[C.hPlotRoot].setStatus(statusKeys[2]) # type: ignore - project.tree[C.hCharRoot].setStatus(statusKeys[1]) # type: ignore - project.tree[C.hWorldRoot].setStatus(statusKeys[3]) # type: ignore - - assert project.tree[C.hNovelRoot].itemStatus == statusKeys[3] # type: ignore - assert project.tree[C.hPlotRoot].itemStatus == statusKeys[2] # type: ignore - assert project.tree[C.hCharRoot].itemStatus == statusKeys[1] # type: ignore - assert project.tree[C.hWorldRoot].itemStatus == statusKeys[3] # type: ignore - - newList = [ - {"key": statusKeys[0], "name": "New", "cols": (1, 1, 1)}, - {"key": statusKeys[1], "name": "Draft", "cols": (2, 2, 2)}, # These are swapped - {"key": statusKeys[2], "name": "Note", "cols": (3, 3, 3)}, # These are swapped - {"key": statusKeys[3], "name": "Edited", "cols": (4, 4, 4)}, # Renamed - {"key": None, "name": "Finished", "cols": (5, 5, 5)}, # New, reused name - ] - assert project.setStatusColours(None, None) is False # type: ignore - assert project.setStatusColours([], []) is False - assert project.setStatusColours(newList, []) is True - - assert project.data.itemStatus.name(statusKeys[0]) == "New" - assert project.data.itemStatus.name(statusKeys[1]) == "Draft" - assert project.data.itemStatus.name(statusKeys[2]) == "Note" - assert project.data.itemStatus.name(statusKeys[3]) == "Edited" - assert project.data.itemStatus.cols(statusKeys[0]) == (1, 1, 1) - assert project.data.itemStatus.cols(statusKeys[1]) == (2, 2, 2) - assert project.data.itemStatus.cols(statusKeys[2]) == (3, 3, 3) - assert project.data.itemStatus.cols(statusKeys[3]) == (4, 4, 4) - - # Check the new entry - lastKey = project.data.itemStatus.check("s000010") - assert lastKey == "s000010" - assert project.data.itemStatus.name(lastKey) == "Finished" - assert project.data.itemStatus.cols(lastKey) == (5, 5, 5) - - # Delete last entry - assert project.setStatusColours([], [lastKey]) is True - assert project.data.itemStatus.name(lastKey) == "New" - - # Change Importance - # ================= - - fHandle = project.newFile("Jane Doe", C.hCharRoot) - project.tree[fHandle].setImport(importKeys[3]) # type: ignore - - assert project.tree[fHandle].itemImport == importKeys[3] # type: ignore - newList = [ - {"key": importKeys[0], "name": "New", "cols": (1, 1, 1)}, - {"key": importKeys[1], "name": "Minor", "cols": (2, 2, 2)}, - {"key": importKeys[2], "name": "Major", "cols": (3, 3, 3)}, - {"key": importKeys[3], "name": "Min", "cols": (4, 4, 4)}, - {"key": None, "name": "Max", "cols": (5, 5, 5)}, - ] - assert project.setImportColours(None, None) is False # type: ignore - assert project.setImportColours([], []) is False - assert project.setImportColours(newList, []) is True - - assert project.data.itemImport.name(importKeys[0]) == "New" - assert project.data.itemImport.name(importKeys[1]) == "Minor" - assert project.data.itemImport.name(importKeys[2]) == "Major" - assert project.data.itemImport.name(importKeys[3]) == "Min" - assert project.data.itemImport.cols(importKeys[0]) == (1, 1, 1) - assert project.data.itemImport.cols(importKeys[1]) == (2, 2, 2) - assert project.data.itemImport.cols(importKeys[2]) == (3, 3, 3) - assert project.data.itemImport.cols(importKeys[3]) == (4, 4, 4) - - # Check the new entry - lastKey = project.data.itemImport.check("i000012") - assert lastKey == "i000012" - assert project.data.itemImport.name(lastKey) == "Max" - assert project.data.itemImport.cols(lastKey) == (5, 5, 5) - - # Delete last entry - assert project.setImportColours([], [lastKey]) is True - assert project.data.itemImport.name(lastKey) == "New" - - # Delete Status/Import - # ==================== - - project.data.itemStatus.resetCounts() - for key in list(project.data.itemStatus.keys()): - assert project.data.itemStatus.remove(key) is True - - project.data.itemImport.resetCounts() - for key in list(project.data.itemImport.keys()): - assert project.data.itemImport.remove(key) is True - - assert len(project.data.itemStatus) == 0 - assert len(project.data.itemImport) == 0 - assert project.saveProject() is True - project.closeProject() - -# END Test testCoreProject_StatusImport - - @pytest.mark.core def testCoreProject_Methods(monkeypatch, mockGUI, fncPath, mockRnd): """Test other project class methods and functions.""" diff --git a/tests/test_core/test_core_projectxml.py b/tests/test_core/test_core_projectxml.py index 534aaed2..394e29cf 100644 --- a/tests/test_core/test_core_projectxml.py +++ b/tests/test_core/test_core_projectxml.py @@ -27,9 +27,12 @@ from shutil import copyfile from datetime import datetime from novelwriter.constants import nwFiles +from novelwriter.enum import nwStatusShape from tools import cmpFiles, writeFile from mocked import causeOSError +from PyQt5.QtGui import QColor + from novelwriter.core.item import NWItem from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState from novelwriter.core.projectdata import NWProjectData @@ -131,7 +134,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath): assert xmlReader.state == XMLReadState.PARSED_OK assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlVersion == 0x0105 - assert xmlReader.xmlRevision == 3 + assert xmlReader.xmlRevision == 4 assert xmlReader.appVersion == "2.0-rc1" assert xmlReader.hexVersion == 0x020000c1 @@ -154,44 +157,57 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, tstPaths, fncPath): assert data.getLastHandle("novelTree") == "7031beac91f75" assert data.getLastHandle("outline") == "7031beac91f75" - assert data.itemStatus.name("sf12341") == "New" - assert data.itemStatus.name("sf24ce6") == "Notes" - assert data.itemStatus.name("sc24b8f") == "Started" - assert data.itemStatus.name("s90e6c9") == "1st Draft" - assert data.itemStatus.name("sd51c5b") == "2nd Draft" - assert data.itemStatus.name("s8ae72a") == "3rd Draft" - assert data.itemStatus.name("s78ea90") == "Finished" + assert data.itemStatus["sf12341"].name == "New" + assert data.itemStatus["sf24ce6"].name == "Notes" + assert data.itemStatus["sc24b8f"].name == "Started" + assert data.itemStatus["s90e6c9"].name == "1st Draft" + assert data.itemStatus["sd51c5b"].name == "2nd Draft" + assert data.itemStatus["s8ae72a"].name == "3rd Draft" + assert data.itemStatus["s78ea90"].name == "Finished" - assert data.itemImport.name("ia857f0") == "None" - assert data.itemImport.name("icfb3a5") == "Minor" - assert data.itemImport.name("i2d7a54") == "Major" - assert data.itemImport.name("i56be10") == "Main" + assert data.itemImport["ia857f0"].name == "None" + assert data.itemImport["icfb3a5"].name == "Minor" + assert data.itemImport["i2d7a54"].name == "Major" + assert data.itemImport["i56be10"].name == "Main" - assert data.itemStatus.cols("sf12341") == (100, 100, 100) - assert data.itemStatus.cols("sf24ce6") == (200, 50, 0) - assert data.itemStatus.cols("sc24b8f") == (182, 60, 0) - assert data.itemStatus.cols("s90e6c9") == (193, 129, 0) - assert data.itemStatus.cols("sd51c5b") == (193, 129, 0) - assert data.itemStatus.cols("s8ae72a") == (193, 129, 0) - assert data.itemStatus.cols("s78ea90") == (58, 180, 58) + assert data.itemStatus["sf12341"].color == QColor(100, 100, 100) + assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0) + assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0) + assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0) + assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0) + assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0) + assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58) - assert data.itemImport.cols("ia857f0") == (100, 100, 100) - assert data.itemImport.cols("icfb3a5") == (0, 122, 188) - assert data.itemImport.cols("i2d7a54") == (21, 0, 180) - assert data.itemImport.cols("i56be10") == (117, 0, 175) + assert data.itemImport["ia857f0"].color == QColor(100, 100, 100) + assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188) + assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180) + assert data.itemImport["i56be10"].color == QColor(117, 0, 175) - assert data.itemStatus.count("sf12341") == 4 - assert data.itemStatus.count("sf24ce6") == 2 - assert data.itemStatus.count("sc24b8f") == 3 - assert data.itemStatus.count("s90e6c9") == 7 - assert data.itemStatus.count("sd51c5b") == 0 - assert data.itemStatus.count("s8ae72a") == 0 - assert data.itemStatus.count("s78ea90") == 1 + assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE + assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE + assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE + assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE - assert data.itemImport.count("ia857f0") == 5 - assert data.itemImport.count("icfb3a5") == 2 - assert data.itemImport.count("i2d7a54") == 2 - assert data.itemImport.count("i56be10") == 1 + assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE + assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE + assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE + assert data.itemImport["i56be10"].shape == nwStatusShape.SQUARE + + assert data.itemStatus["sf12341"].count == 4 + assert data.itemStatus["sf24ce6"].count == 2 + assert data.itemStatus["sc24b8f"].count == 3 + assert data.itemStatus["s90e6c9"].count == 7 + assert data.itemStatus["sd51c5b"].count == 0 + assert data.itemStatus["s8ae72a"].count == 0 + assert data.itemStatus["s78ea90"].count == 1 + + assert data.itemImport["ia857f0"].count == 5 + assert data.itemImport["icfb3a5"].count == 2 + assert data.itemImport["i2d7a54"].count == 2 + assert data.itemImport["i56be10"].count == 1 # Compare content dumpFile = tstPaths.outDir / "projectXML_ReadCurrent.json" @@ -272,44 +288,57 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd): assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.0 assert data.getLastHandle("outline") is None # Doesn't exist in 1.0 - assert data.itemStatus.name("s000000") == "New" - assert data.itemStatus.name("s000001") == "Notes" - assert data.itemStatus.name("s000002") == "Started" - assert data.itemStatus.name("s000003") == "1st Draft" - assert data.itemStatus.name("s000004") == "2nd Draft" - assert data.itemStatus.name("s000005") == "3rd Draft" - assert data.itemStatus.name("s000006") == "Finished" + assert data.itemStatus["s000000"].name == "New" + assert data.itemStatus["s000001"].name == "Notes" + assert data.itemStatus["s000002"].name == "Started" + assert data.itemStatus["s000003"].name == "1st Draft" + assert data.itemStatus["s000004"].name == "2nd Draft" + assert data.itemStatus["s000005"].name == "3rd Draft" + assert data.itemStatus["s000006"].name == "Finished" - assert data.itemImport.name("i000007") == "None" - assert data.itemImport.name("i000008") == "Minor" - assert data.itemImport.name("i000009") == "Major" - assert data.itemImport.name("i00000a") == "Main" + assert data.itemImport["i000007"].name == "None" + assert data.itemImport["i000008"].name == "Minor" + assert data.itemImport["i000009"].name == "Major" + assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus.cols("s000000") == (100, 100, 100) - assert data.itemStatus.cols("s000001") == (200, 50, 0) - assert data.itemStatus.cols("s000002") == (182, 60, 0) - assert data.itemStatus.cols("s000003") == (193, 129, 0) - assert data.itemStatus.cols("s000004") == (193, 129, 0) - assert data.itemStatus.cols("s000005") == (193, 129, 0) - assert data.itemStatus.cols("s000006") == (58, 180, 58) + assert data.itemStatus["s000000"].color == QColor(100, 100, 100) + assert data.itemStatus["s000001"].color == QColor(200, 50, 0) + assert data.itemStatus["s000002"].color == QColor(182, 60, 0) + assert data.itemStatus["s000003"].color == QColor(193, 129, 0) + assert data.itemStatus["s000004"].color == QColor(193, 129, 0) + assert data.itemStatus["s000005"].color == QColor(193, 129, 0) + assert data.itemStatus["s000006"].color == QColor(58, 180, 58) - assert data.itemImport.cols("i000007") == (100, 100, 100) - assert data.itemImport.cols("i000008") == (0, 122, 188) - assert data.itemImport.cols("i000009") == (21, 0, 180) - assert data.itemImport.cols("i00000a") == (117, 0, 175) + assert data.itemImport["i000007"].color == QColor(100, 100, 100) + assert data.itemImport["i000008"].color == QColor(0, 122, 188) + assert data.itemImport["i000009"].color == QColor(21, 0, 180) + assert data.itemImport["i00000a"].color == QColor(117, 0, 175) - assert data.itemStatus.count("s000000") == 0 - assert data.itemStatus.count("s000001") == 0 - assert data.itemStatus.count("s000002") == 0 - assert data.itemStatus.count("s000003") == 0 - assert data.itemStatus.count("s000004") == 0 - assert data.itemStatus.count("s000005") == 0 - assert data.itemStatus.count("s000006") == 0 + assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE - assert data.itemImport.count("i000007") == 0 - assert data.itemImport.count("i000008") == 0 - assert data.itemImport.count("i000009") == 0 - assert data.itemImport.count("i00000a") == 0 + assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE + assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE + + assert data.itemStatus["s000000"].count == 0 + assert data.itemStatus["s000001"].count == 0 + assert data.itemStatus["s000002"].count == 0 + assert data.itemStatus["s000003"].count == 0 + assert data.itemStatus["s000004"].count == 0 + assert data.itemStatus["s000005"].count == 0 + assert data.itemStatus["s000006"].count == 0 + + assert data.itemImport["i000007"].count == 0 + assert data.itemImport["i000008"].count == 0 + assert data.itemImport["i000009"].count == 0 + assert data.itemImport["i00000a"].count == 0 # Compare content dumpFile = tstPaths.outDir / "projectXML_ReadLegacy10.json" @@ -325,7 +354,7 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockRnd): for entry in content: item = NWItem(mockProject, "0000000000000") # type: ignore item.unpack(entry) - status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + status[item.itemHandle] = item.getImportStatus()[0] packedContent.append(item.pack()) assert status == { @@ -406,44 +435,57 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd): assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.1 assert data.getLastHandle("outline") is None # Doesn't exist in 1.1 - assert data.itemStatus.name("s000000") == "New" - assert data.itemStatus.name("s000001") == "Notes" - assert data.itemStatus.name("s000002") == "Started" - assert data.itemStatus.name("s000003") == "1st Draft" - assert data.itemStatus.name("s000004") == "2nd Draft" - assert data.itemStatus.name("s000005") == "3rd Draft" - assert data.itemStatus.name("s000006") == "Finished" + assert data.itemStatus["s000000"].name == "New" + assert data.itemStatus["s000001"].name == "Notes" + assert data.itemStatus["s000002"].name == "Started" + assert data.itemStatus["s000003"].name == "1st Draft" + assert data.itemStatus["s000004"].name == "2nd Draft" + assert data.itemStatus["s000005"].name == "3rd Draft" + assert data.itemStatus["s000006"].name == "Finished" - assert data.itemImport.name("i000007") == "None" - assert data.itemImport.name("i000008") == "Minor" - assert data.itemImport.name("i000009") == "Major" - assert data.itemImport.name("i00000a") == "Main" + assert data.itemImport["i000007"].name == "None" + assert data.itemImport["i000008"].name == "Minor" + assert data.itemImport["i000009"].name == "Major" + assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus.cols("s000000") == (100, 100, 100) - assert data.itemStatus.cols("s000001") == (200, 50, 0) - assert data.itemStatus.cols("s000002") == (182, 60, 0) - assert data.itemStatus.cols("s000003") == (193, 129, 0) - assert data.itemStatus.cols("s000004") == (193, 129, 0) - assert data.itemStatus.cols("s000005") == (193, 129, 0) - assert data.itemStatus.cols("s000006") == (58, 180, 58) + assert data.itemStatus["s000000"].color == QColor(100, 100, 100) + assert data.itemStatus["s000001"].color == QColor(200, 50, 0) + assert data.itemStatus["s000002"].color == QColor(182, 60, 0) + assert data.itemStatus["s000003"].color == QColor(193, 129, 0) + assert data.itemStatus["s000004"].color == QColor(193, 129, 0) + assert data.itemStatus["s000005"].color == QColor(193, 129, 0) + assert data.itemStatus["s000006"].color == QColor(58, 180, 58) - assert data.itemImport.cols("i000007") == (100, 100, 100) - assert data.itemImport.cols("i000008") == (0, 122, 188) - assert data.itemImport.cols("i000009") == (21, 0, 180) - assert data.itemImport.cols("i00000a") == (117, 0, 175) + assert data.itemImport["i000007"].color == QColor(100, 100, 100) + assert data.itemImport["i000008"].color == QColor(0, 122, 188) + assert data.itemImport["i000009"].color == QColor(21, 0, 180) + assert data.itemImport["i00000a"].color == QColor(117, 0, 175) - assert data.itemStatus.count("s000000") == 0 - assert data.itemStatus.count("s000001") == 0 - assert data.itemStatus.count("s000002") == 0 - assert data.itemStatus.count("s000003") == 0 - assert data.itemStatus.count("s000004") == 0 - assert data.itemStatus.count("s000005") == 0 - assert data.itemStatus.count("s000006") == 0 + assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE - assert data.itemImport.count("i000007") == 0 - assert data.itemImport.count("i000008") == 0 - assert data.itemImport.count("i000009") == 0 - assert data.itemImport.count("i00000a") == 0 + assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE + assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE + + assert data.itemStatus["s000000"].count == 0 + assert data.itemStatus["s000001"].count == 0 + assert data.itemStatus["s000002"].count == 0 + assert data.itemStatus["s000003"].count == 0 + assert data.itemStatus["s000004"].count == 0 + assert data.itemStatus["s000005"].count == 0 + assert data.itemStatus["s000006"].count == 0 + + assert data.itemImport["i000007"].count == 0 + assert data.itemImport["i000008"].count == 0 + assert data.itemImport["i000009"].count == 0 + assert data.itemImport["i00000a"].count == 0 # Compare content dumpFile = tstPaths.outDir / "projectXML_ReadLegacy11.json" @@ -459,7 +501,7 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockRnd): for entry in content: item = NWItem(mockProject, "0000000000000") # type: ignore item.unpack(entry) - status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + status[item.itemHandle] = item.getImportStatus()[0] packedContent.append(item.pack()) assert status == { @@ -540,44 +582,57 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd): assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.2 assert data.getLastHandle("outline") is None # Doesn't exist in 1.2 - assert data.itemStatus.name("s000000") == "New" - assert data.itemStatus.name("s000001") == "Notes" - assert data.itemStatus.name("s000002") == "Started" - assert data.itemStatus.name("s000003") == "1st Draft" - assert data.itemStatus.name("s000004") == "2nd Draft" - assert data.itemStatus.name("s000005") == "3rd Draft" - assert data.itemStatus.name("s000006") == "Finished" + assert data.itemStatus["s000000"].name == "New" + assert data.itemStatus["s000001"].name == "Notes" + assert data.itemStatus["s000002"].name == "Started" + assert data.itemStatus["s000003"].name == "1st Draft" + assert data.itemStatus["s000004"].name == "2nd Draft" + assert data.itemStatus["s000005"].name == "3rd Draft" + assert data.itemStatus["s000006"].name == "Finished" - assert data.itemImport.name("i000007") == "None" - assert data.itemImport.name("i000008") == "Minor" - assert data.itemImport.name("i000009") == "Major" - assert data.itemImport.name("i00000a") == "Main" + assert data.itemImport["i000007"].name == "None" + assert data.itemImport["i000008"].name == "Minor" + assert data.itemImport["i000009"].name == "Major" + assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus.cols("s000000") == (100, 100, 100) - assert data.itemStatus.cols("s000001") == (200, 50, 0) - assert data.itemStatus.cols("s000002") == (182, 60, 0) - assert data.itemStatus.cols("s000003") == (193, 129, 0) - assert data.itemStatus.cols("s000004") == (193, 129, 0) - assert data.itemStatus.cols("s000005") == (193, 129, 0) - assert data.itemStatus.cols("s000006") == (58, 180, 58) + assert data.itemStatus["s000000"].color == QColor(100, 100, 100) + assert data.itemStatus["s000001"].color == QColor(200, 50, 0) + assert data.itemStatus["s000002"].color == QColor(182, 60, 0) + assert data.itemStatus["s000003"].color == QColor(193, 129, 0) + assert data.itemStatus["s000004"].color == QColor(193, 129, 0) + assert data.itemStatus["s000005"].color == QColor(193, 129, 0) + assert data.itemStatus["s000006"].color == QColor(58, 180, 58) - assert data.itemImport.cols("i000007") == (100, 100, 100) - assert data.itemImport.cols("i000008") == (0, 122, 188) - assert data.itemImport.cols("i000009") == (21, 0, 180) - assert data.itemImport.cols("i00000a") == (117, 0, 175) + assert data.itemImport["i000007"].color == QColor(100, 100, 100) + assert data.itemImport["i000008"].color == QColor(0, 122, 188) + assert data.itemImport["i000009"].color == QColor(21, 0, 180) + assert data.itemImport["i00000a"].color == QColor(117, 0, 175) - assert data.itemStatus.count("s000000") == 0 - assert data.itemStatus.count("s000001") == 0 - assert data.itemStatus.count("s000002") == 0 - assert data.itemStatus.count("s000003") == 0 - assert data.itemStatus.count("s000004") == 0 - assert data.itemStatus.count("s000005") == 0 - assert data.itemStatus.count("s000006") == 0 + assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE - assert data.itemImport.count("i000007") == 0 - assert data.itemImport.count("i000008") == 0 - assert data.itemImport.count("i000009") == 0 - assert data.itemImport.count("i00000a") == 0 + assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE + assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE + + assert data.itemStatus["s000000"].count == 0 + assert data.itemStatus["s000001"].count == 0 + assert data.itemStatus["s000002"].count == 0 + assert data.itemStatus["s000003"].count == 0 + assert data.itemStatus["s000004"].count == 0 + assert data.itemStatus["s000005"].count == 0 + assert data.itemStatus["s000006"].count == 0 + + assert data.itemImport["i000007"].count == 0 + assert data.itemImport["i000008"].count == 0 + assert data.itemImport["i000009"].count == 0 + assert data.itemImport["i00000a"].count == 0 # Compare content dumpFile = tstPaths.outDir / "projectXML_ReadLegacy12.json" @@ -593,7 +648,7 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockRnd): for entry in content: item = NWItem(mockProject, "0000000000000") # type: ignore item.unpack(entry) - status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + status[item.itemHandle] = item.getImportStatus()[0] packedContent.append(item.pack()) assert status == { @@ -677,44 +732,57 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd): assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3 assert data.getLastHandle("outline") is None # Doesn't exist in 1.3 - assert data.itemStatus.name("s000000") == "New" - assert data.itemStatus.name("s000001") == "Notes" - assert data.itemStatus.name("s000002") == "Started" - assert data.itemStatus.name("s000003") == "1st Draft" - assert data.itemStatus.name("s000004") == "2nd Draft" - assert data.itemStatus.name("s000005") == "3rd Draft" - assert data.itemStatus.name("s000006") == "Finished" + assert data.itemStatus["s000000"].name == "New" + assert data.itemStatus["s000001"].name == "Notes" + assert data.itemStatus["s000002"].name == "Started" + assert data.itemStatus["s000003"].name == "1st Draft" + assert data.itemStatus["s000004"].name == "2nd Draft" + assert data.itemStatus["s000005"].name == "3rd Draft" + assert data.itemStatus["s000006"].name == "Finished" - assert data.itemImport.name("i000007") == "None" - assert data.itemImport.name("i000008") == "Minor" - assert data.itemImport.name("i000009") == "Major" - assert data.itemImport.name("i00000a") == "Main" + assert data.itemImport["i000007"].name == "None" + assert data.itemImport["i000008"].name == "Minor" + assert data.itemImport["i000009"].name == "Major" + assert data.itemImport["i00000a"].name == "Main" - assert data.itemStatus.cols("s000000") == (100, 100, 100) - assert data.itemStatus.cols("s000001") == (200, 50, 0) - assert data.itemStatus.cols("s000002") == (182, 60, 0) - assert data.itemStatus.cols("s000003") == (193, 129, 0) - assert data.itemStatus.cols("s000004") == (193, 129, 0) - assert data.itemStatus.cols("s000005") == (193, 129, 0) - assert data.itemStatus.cols("s000006") == (58, 180, 58) + assert data.itemStatus["s000000"].color == QColor(100, 100, 100) + assert data.itemStatus["s000001"].color == QColor(200, 50, 0) + assert data.itemStatus["s000002"].color == QColor(182, 60, 0) + assert data.itemStatus["s000003"].color == QColor(193, 129, 0) + assert data.itemStatus["s000004"].color == QColor(193, 129, 0) + assert data.itemStatus["s000005"].color == QColor(193, 129, 0) + assert data.itemStatus["s000006"].color == QColor(58, 180, 58) - assert data.itemImport.cols("i000007") == (100, 100, 100) - assert data.itemImport.cols("i000008") == (0, 122, 188) - assert data.itemImport.cols("i000009") == (21, 0, 180) - assert data.itemImport.cols("i00000a") == (117, 0, 175) + assert data.itemImport["i000007"].color == QColor(100, 100, 100) + assert data.itemImport["i000008"].color == QColor(0, 122, 188) + assert data.itemImport["i000009"].color == QColor(21, 0, 180) + assert data.itemImport["i00000a"].color == QColor(117, 0, 175) - assert data.itemStatus.count("s000000") == 0 - assert data.itemStatus.count("s000001") == 0 - assert data.itemStatus.count("s000002") == 0 - assert data.itemStatus.count("s000003") == 0 - assert data.itemStatus.count("s000004") == 0 - assert data.itemStatus.count("s000005") == 0 - assert data.itemStatus.count("s000006") == 0 + assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000002"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000003"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000004"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000005"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s000006"].shape == nwStatusShape.SQUARE - assert data.itemImport.count("i000007") == 0 - assert data.itemImport.count("i000008") == 0 - assert data.itemImport.count("i000009") == 0 - assert data.itemImport.count("i00000a") == 0 + assert data.itemImport["i000007"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000008"].shape == nwStatusShape.SQUARE + assert data.itemImport["i000009"].shape == nwStatusShape.SQUARE + assert data.itemImport["i00000a"].shape == nwStatusShape.SQUARE + + assert data.itemStatus["s000000"].count == 0 + assert data.itemStatus["s000001"].count == 0 + assert data.itemStatus["s000002"].count == 0 + assert data.itemStatus["s000003"].count == 0 + assert data.itemStatus["s000004"].count == 0 + assert data.itemStatus["s000005"].count == 0 + assert data.itemStatus["s000006"].count == 0 + + assert data.itemImport["i000007"].count == 0 + assert data.itemImport["i000008"].count == 0 + assert data.itemImport["i000009"].count == 0 + assert data.itemImport["i00000a"].count == 0 # Compare content dumpFile = tstPaths.outDir / "projectXML_ReadLegacy13.json" @@ -730,7 +798,7 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockRnd): for entry in content: item = NWItem(mockProject, "0000000000000") # type: ignore item.unpack(entry) - status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + status[item.itemHandle] = item.getImportStatus()[0] packedContent.append(item.pack()) assert status == { @@ -814,44 +882,56 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd): assert data.getLastHandle("novelTree") is None # Doesn't exist in 1.3 assert data.getLastHandle("outline") is None # Doesn't exist in 1.3 - assert data.itemStatus.name("sf12341") == "New" - assert data.itemStatus.name("sf24ce6") == "Notes" - assert data.itemStatus.name("sc24b8f") == "Started" - assert data.itemStatus.name("s90e6c9") == "1st Draft" - assert data.itemStatus.name("sd51c5b") == "2nd Draft" - assert data.itemStatus.name("s8ae72a") == "3rd Draft" - assert data.itemStatus.name("s78ea90") == "Finished" + assert data.itemStatus["sf12341"].name == "New" + assert data.itemStatus["sf24ce6"].name == "Notes" + assert data.itemStatus["sc24b8f"].name == "Started" + assert data.itemStatus["s90e6c9"].name == "1st Draft" + assert data.itemStatus["sd51c5b"].name == "2nd Draft" + assert data.itemStatus["s8ae72a"].name == "3rd Draft" + assert data.itemStatus["s78ea90"].name == "Finished" - assert data.itemImport.name("ia857f0") == "None" - assert data.itemImport.name("icfb3a5") == "Minor" - assert data.itemImport.name("i2d7a54") == "Major" - assert data.itemImport.name("i56be10") == "Main" + assert data.itemImport["ia857f0"].name == "None" + assert data.itemImport["icfb3a5"].name == "Minor" + assert data.itemImport["i2d7a54"].name == "Major" + assert data.itemImport["i56be10"].name == "Main" - assert data.itemStatus.cols("sf12341") == (100, 100, 100) - assert data.itemStatus.cols("sf24ce6") == (200, 50, 0) - assert data.itemStatus.cols("sc24b8f") == (182, 60, 0) - assert data.itemStatus.cols("s90e6c9") == (193, 129, 0) - assert data.itemStatus.cols("sd51c5b") == (193, 129, 0) - assert data.itemStatus.cols("s8ae72a") == (193, 129, 0) - assert data.itemStatus.cols("s78ea90") == (58, 180, 58) + assert data.itemStatus["sf12341"].color == QColor(100, 100, 100) + assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0) + assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0) + assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0) + assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0) + assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0) + assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58) - assert data.itemImport.cols("ia857f0") == (100, 100, 100) - assert data.itemImport.cols("icfb3a5") == (0, 122, 188) - assert data.itemImport.cols("i2d7a54") == (21, 0, 180) - assert data.itemImport.cols("i56be10") == (117, 0, 175) + assert data.itemImport["ia857f0"].color == QColor(100, 100, 100) + assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188) + assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180) + assert data.itemImport["i56be10"].color == QColor(117, 0, 175) - assert data.itemStatus.count("sf12341") == 4 - assert data.itemStatus.count("sf24ce6") == 2 - assert data.itemStatus.count("sc24b8f") == 3 - assert data.itemStatus.count("s90e6c9") == 7 - assert data.itemStatus.count("sd51c5b") == 0 - assert data.itemStatus.count("s8ae72a") == 0 - assert data.itemStatus.count("s78ea90") == 1 + assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE + assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE + assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE + assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE + assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE - assert data.itemImport.count("ia857f0") == 5 - assert data.itemImport.count("icfb3a5") == 2 - assert data.itemImport.count("i2d7a54") == 2 - assert data.itemImport.count("i56be10") == 1 + assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE + assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE + assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE + assert data.itemImport["i56be10"].shape == nwStatusShape.SQUARE + + assert data.itemStatus["sf12341"].count == 4 + assert data.itemStatus["sf24ce6"].count == 2 + assert data.itemStatus["sc24b8f"].count == 3 + assert data.itemStatus["s90e6c9"].count == 7 + assert data.itemStatus["sd51c5b"].count == 0 + assert data.itemStatus["s8ae72a"].count == 0 + assert data.itemStatus["s78ea90"].count == 1 + assert data.itemImport["ia857f0"].count == 5 + assert data.itemImport["icfb3a5"].count == 2 + assert data.itemImport["i2d7a54"].count == 2 + assert data.itemImport["i56be10"].count == 1 # Compare content dumpFile = tstPaths.outDir / "projectXML_ReadLegacy14.json" @@ -867,7 +947,7 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockRnd): for entry in content: item = NWItem(mockProject, "0000000000000") # type: ignore item.unpack(entry) - status[item.itemHandle] = item.getImportStatus(incIcon=False)[0] + status[item.itemHandle] = item.getImportStatus()[0] packedContent.append(item.pack()) assert status == { diff --git a/tests/test_core/test_core_status.py b/tests/test_core/test_core_status.py index 9e30d9c4..7c6b2fc4 100644 --- a/tests/test_core/test_core_status.py +++ b/tests/test_core/test_core_status.py @@ -24,24 +24,50 @@ import pytest from tools import C -from PyQt5.QtGui import QIcon +from PyQt5.QtGui import QColor, QIcon -from novelwriter.core.status import NWStatus +from novelwriter.core.status import NWStatus, StatusEntry, _ShapeCache +from novelwriter.enum import nwStatusShape statusKeys = [C.sNew, C.sNote, C.sDraft, C.sFinished] importKeys = [C.iNew, C.iMinor, C.iMajor, C.iMain] @pytest.mark.core -def testCoreStatus_Internal(mockRnd): - """Test all the internal functions of the NWStatus class. - """ +def testCoreStatus_StatusEntry(): + """Test the StatusEntry class.""" + color = QColor(255, 0, 0) + icon = NWStatus.createIcon(24, color, nwStatusShape.CIRCLE) + entry = StatusEntry("Test", color, nwStatusShape.CIRCLE, icon, 42) + + # Check values + assert entry.name == "Test" + assert entry.color is color + assert entry.shape == nwStatusShape.CIRCLE + assert entry.icon is icon + assert entry.count == 42 + + # Make a copy + other = StatusEntry.duplicate(entry) + assert other is not entry + + # Check copy is not the same + assert other.name == "Test" + assert other.color is not color # Not the same object + assert other.color == color # But same colours + assert other.shape == nwStatusShape.CIRCLE + assert other.icon is not icon # Not the same icon, but a copy + assert other.count == 42 + +# END Test testCoreStatus_StatusEntry + + +@pytest.mark.core +def testCoreStatus_Internal(mockGUI, mockRnd): + """Test all the internal functions of the NWStatus class.""" nStatus = NWStatus(NWStatus.STATUS) nImport = NWStatus(NWStatus.IMPORT) - with pytest.raises(Exception): - NWStatus(999) - # Generate Key # ============ @@ -49,14 +75,14 @@ def testCoreStatus_Internal(mockRnd): assert nStatus._newKey() == statusKeys[1] # Key collision, should move to key 3 - nStatus.write(statusKeys[2], "Crash", (0, 0, 0)) + nStatus.add(statusKeys[2], "Crash", (0, 0, 0), "SQUARE", 0) assert nStatus._newKey() == statusKeys[3] assert nImport._newKey() == importKeys[0] assert nImport._newKey() == importKeys[1] # Key collision, should move to key 3 - nImport.write(importKeys[2], "Crash", (0, 0, 0)) + nImport.add(importKeys[2], "Crash", (0, 0, 0), "SQUARE", 0) assert nImport._newKey() == importKeys[3] # Check Key @@ -82,81 +108,94 @@ def testCoreStatus_Internal(mockRnd): assert nImport._isKey("i12345F") is False # Not a lower case hex value assert nImport._isKey("i12345f") is True # Valid hex value + assert nStatus._checkKey(None) == "s000008" # Creates next key + assert nStatus._checkKey("s654321") == "s654321" # Status key accepted + assert nStatus._checkKey("i123456") == "s000009" # Import key not accepted + + assert nImport._checkKey(None) == "i00000a" # Creates next key + assert nImport._checkKey("s654321") == "i00000b" # Status key not accepted + assert nImport._checkKey("i123456") == "i123456" # Import key accepted + # END Test testCoreStatus_Internal @pytest.mark.core -def testCoreStatus_Iterator(mockRnd): - """Test the iterator functions of the NWStatus class. - """ +def testCoreStatus_Iterator(mockGUI, mockRnd): + """Test the iterator functions of the NWStatus class.""" nStatus = NWStatus(NWStatus.STATUS) - - nStatus.write(None, "New", (100, 100, 100)) - nStatus.write(None, "Note", (200, 50, 0)) - nStatus.write(None, "Draft", (200, 150, 0)) - nStatus.write(None, "Finished", (50, 200, 0)) + nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0) + nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 1) + nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 2) + nStatus.add(None, "Finished", (50, 200, 0), "CIRCLE", 3) # Direct access entry = nStatus[statusKeys[0]] - assert entry["cols"] == (100, 100, 100) - assert entry["name"] == "New" - assert entry["count"] == 0 - assert isinstance(entry["icon"], QIcon) + assert entry.color == QColor(100, 100, 100) + assert entry.name == "New" + assert entry.count == 0 + assert isinstance(entry.icon, QIcon) - # Iterate - entries = list(nStatus) - assert len(entries) == 4 + # Length + assert len(nStatus._store) == 4 assert len(nStatus) == 4 - # Keys - assert list(nStatus.keys()) == statusKeys + # Content : Keys + assert [k for k, _ in nStatus.iterItems()] == [ + "s000000", "s000001", "s000002", "s000003" + ] - # Items - for index, (key, entry) in enumerate(nStatus.items()): - assert key == statusKeys[index] - assert "cols" in entry - assert "name" in entry - assert "count" in entry - assert "icon" in entry + # Content : Names + assert [e.name for _, e in nStatus.iterItems()] == [ + "New", "Note", "Draft", "Finished" + ] - # Valuse - for entry in nStatus.values(): - assert "cols" in entry - assert "name" in entry - assert "count" in entry - assert "icon" in entry + # Content : Colours + assert [e.color for _, e in nStatus.iterItems()] == [ + QColor(100, 100, 100), QColor(200, 50, 0), QColor(200, 150, 0), QColor(50, 200, 0) + ] + + # Content : Shape + assert [e.shape for _, e in nStatus.iterItems()] == [ + nwStatusShape.SQUARE, nwStatusShape.CIRCLE, nwStatusShape.SQUARE, nwStatusShape.CIRCLE + ] + + # Content : Count + assert [e.count for _, e in nStatus.iterItems()] == [0, 1, 2, 3] # END Test testCoreStatus_Iterator @pytest.mark.core -def testCoreStatus_Entries(mockRnd): - """Test all the simple setters for the NWStatus class. - """ +def testCoreStatus_Entries(mockGUI, mockRnd): + """Test all the simple setters for the NWStatus class.""" nStatus = NWStatus(NWStatus.STATUS) - # Write - # ===== + # Add + # === - # Have a key - nStatus.write(statusKeys[0], "Entry 1", (200, 100, 50)) - assert nStatus[statusKeys[0]]["name"] == "Entry 1" - assert nStatus[statusKeys[0]]["cols"] == (200, 100, 50) + # Has a key + nStatus.add(statusKeys[0], "Entry 1", (200, 100, 50), "SQUARE", 0) + assert nStatus[statusKeys[0]].name == "Entry 1" + assert nStatus[statusKeys[0]].color == QColor(200, 100, 50) + assert nStatus[statusKeys[0]].shape == nwStatusShape.SQUARE - # Don't have a key - nStatus.write(None, "Entry 2", (210, 110, 60)) - assert nStatus[statusKeys[1]]["name"] == "Entry 2" - assert nStatus[statusKeys[1]]["cols"] == (210, 110, 60) + # Doesn't have a key + nStatus.add(None, "Entry 2", (210, 110, 60), "SQUARE", 0) + assert nStatus[statusKeys[1]].name == "Entry 2" + assert nStatus[statusKeys[1]].color == QColor(210, 110, 60) + assert nStatus[statusKeys[1]].shape == nwStatusShape.SQUARE - # Wrong colour spec - nStatus.write(None, "Entry 3", "what?") - assert nStatus[statusKeys[2]]["name"] == "Entry 3" - assert nStatus[statusKeys[2]]["cols"] == (100, 100, 100) + # Wrong colour spec, unknown shape + nStatus.add(None, "Entry 3", "what?", "", 0) # type: ignore + assert nStatus[statusKeys[2]].name == "Entry 3" + assert nStatus[statusKeys[2]].color == QColor(100, 100, 100) + assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE # Wrong colour count - nStatus.write(None, "Entry 4", (10, 20)) - assert nStatus[statusKeys[3]]["name"] == "Entry 4" - assert nStatus[statusKeys[3]]["cols"] == (100, 100, 100) + nStatus.add(None, "Entry 4", (10, 20), "CIRCLE", 0) # type: ignore + assert nStatus[statusKeys[3]].name == "Entry 4" + assert nStatus[statusKeys[3]].color == QColor(100, 100, 100) + assert nStatus[statusKeys[3]].shape == nwStatusShape.CIRCLE # Check # ===== @@ -171,29 +210,38 @@ def testCoreStatus_Entries(mockRnd): # Name Access # =========== - assert nStatus.name(statusKeys[0]) == "Entry 1" - assert nStatus.name(statusKeys[1]) == "Entry 2" - assert nStatus.name(statusKeys[2]) == "Entry 3" - assert nStatus.name(statusKeys[3]) == "Entry 4" - assert nStatus.name("blablabla") == "Entry 1" + assert nStatus[statusKeys[0]].name == "Entry 1" + assert nStatus[statusKeys[1]].name == "Entry 2" + assert nStatus[statusKeys[2]].name == "Entry 3" + assert nStatus[statusKeys[3]].name == "Entry 4" + assert nStatus["blablabla"].name == "Entry 1" # Colour Access # ============= - assert nStatus.cols(statusKeys[0]) == (200, 100, 50) - assert nStatus.cols(statusKeys[1]) == (210, 110, 60) - assert nStatus.cols(statusKeys[2]) == (100, 100, 100) - assert nStatus.cols(statusKeys[3]) == (100, 100, 100) - assert nStatus.cols("blablabla") == (200, 100, 50) + assert nStatus[statusKeys[0]].color == QColor(200, 100, 50) + assert nStatus[statusKeys[1]].color == QColor(210, 110, 60) + assert nStatus[statusKeys[2]].color == QColor(100, 100, 100) + assert nStatus[statusKeys[3]].color == QColor(100, 100, 100) + assert nStatus["blablabla"].color == QColor(200, 100, 50) # Icon Access # =========== - assert isinstance(nStatus.icon(statusKeys[0]), QIcon) - assert isinstance(nStatus.icon(statusKeys[1]), QIcon) - assert isinstance(nStatus.icon(statusKeys[2]), QIcon) - assert isinstance(nStatus.icon(statusKeys[3]), QIcon) - assert isinstance(nStatus.icon("blablabla"), QIcon) + assert isinstance(nStatus[statusKeys[0]].icon, QIcon) + assert isinstance(nStatus[statusKeys[1]].icon, QIcon) + assert isinstance(nStatus[statusKeys[2]].icon, QIcon) + assert isinstance(nStatus[statusKeys[3]].icon, QIcon) + assert isinstance(nStatus["blablabla"].icon, QIcon) + + # Shape Access + # ============ + + assert nStatus[statusKeys[0]].shape == nwStatusShape.SQUARE + assert nStatus[statusKeys[1]].shape == nwStatusShape.SQUARE + assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE + assert nStatus[statusKeys[3]].shape == nwStatusShape.CIRCLE + assert nStatus["blablabla"].shape == nwStatusShape.SQUARE # Increment and Count Access # ========================== @@ -203,50 +251,32 @@ def testCoreStatus_Entries(mockRnd): for _ in range(n): nStatus.increment(statusKeys[i]) - assert nStatus.count(statusKeys[0]) == countTo[0] - assert nStatus.count(statusKeys[1]) == countTo[1] - assert nStatus.count(statusKeys[2]) == countTo[2] - assert nStatus.count(statusKeys[3]) == countTo[3] - assert nStatus.count("blablabla") == countTo[0] + assert nStatus[statusKeys[0]].count == countTo[0] + assert nStatus[statusKeys[1]].count == countTo[1] + assert nStatus[statusKeys[2]].count == countTo[2] + assert nStatus[statusKeys[3]].count == countTo[3] + assert nStatus["blablabla"].count == countTo[0] nStatus.resetCounts() - assert nStatus.count(statusKeys[0]) == 0 - assert nStatus.count(statusKeys[1]) == 0 - assert nStatus.count(statusKeys[2]) == 0 - assert nStatus.count(statusKeys[3]) == 0 + assert nStatus[statusKeys[0]].count == 0 + assert nStatus[statusKeys[1]].count == 0 + assert nStatus[statusKeys[2]].count == 0 + assert nStatus[statusKeys[3]].count == 0 - # Reorder - # ======= + # Update + # ====== - cOrder = list(nStatus.keys()) - assert cOrder == statusKeys + assert list(nStatus._store.keys()) == statusKeys - # Wrong length - assert nStatus.reorder([]) is False + # Reverse + order: list[tuple[str | None, StatusEntry]] = list(nStatus.iterItems()) + nStatus.update(list(reversed(order))) + assert list(nStatus._store.keys()) == list(reversed(statusKeys)) - # No change - assert nStatus.reorder(cOrder) is False - - # Actual reaorder - nOrder = [ - statusKeys[0], - statusKeys[2], - statusKeys[1], - statusKeys[3], - ] - assert nStatus.reorder(nOrder) is True - assert list(nStatus.keys()) == nOrder - - # Add an unknown key - wOrder = nOrder.copy() - wOrder[3] = nStatus._newKey() - assert nStatus.reorder(wOrder) is False - assert list(nStatus.keys()) == nOrder - - # Put it back - assert nStatus.reorder(cOrder) is True - assert list(nStatus.keys()) == cOrder + # Restore + nStatus.update(order) + assert list(nStatus._store.keys()) == statusKeys # Default # ======= @@ -255,56 +285,41 @@ def testCoreStatus_Entries(mockRnd): nStatus._default = None assert nStatus.check("Entry 5") == "" - assert nStatus.name("blablabla") == "" - assert nStatus.cols("blablabla") == (100, 100, 100) - assert nStatus.count("blablabla") == 0 - assert isinstance(nStatus.icon("blablabla"), QIcon) + assert nStatus["blablabla"].name == "" + assert nStatus["blablabla"].color == QColor(0, 0, 0) + assert nStatus["blablabla"].shape == nwStatusShape.SQUARE + assert nStatus["blablabla"].icon.isNull() + assert nStatus["blablabla"].count == 0 nStatus._default = default # Remove # ====== + # This uses update with deleted items - # Non-existing entry - assert nStatus.remove("blablabla") is False + order: list[tuple[str | None, StatusEntry]] = list(nStatus.iterItems()) - # Non-zero entry - nStatus.increment(statusKeys[3]) - assert nStatus.remove(statusKeys[3]) is False + # Remove Entry 0 + nStatus.update([order[1], order[3], order[2]]) + assert list(nStatus._store.keys()) == [statusKeys[1], statusKeys[3], statusKeys[2]] + assert nStatus._default == statusKeys[1] - # Delete last entry - nStatus.resetCounts() - lastName = nStatus.name(statusKeys[3]) - assert lastName == "Entry 4" - assert nStatus.remove(statusKeys[3]) is True - assert nStatus.check(statusKeys[3]) == nStatus._default - assert nStatus.check(lastName) == nStatus._default - - # Delete default entry, Entry 2 is new default - firstName = nStatus.name(nStatus._default) - assert firstName == "Entry 1" - assert nStatus.remove(nStatus._default) is True - assert nStatus.name(firstName) == "Entry 2" - - # Remove remaining entries - assert nStatus.remove(statusKeys[1]) is True - assert nStatus.remove(statusKeys[2]) is True - - assert len(nStatus) == 0 - assert nStatus._default is None + # Remove Entry 1 + nStatus.update([order[3], order[2]]) + assert list(nStatus._store.keys()) == [statusKeys[3], statusKeys[2]] + assert nStatus._default == statusKeys[3] # END Test testCoreStatus_Entries @pytest.mark.core -def testCoreStatus_PackUnpack(mockRnd): - """Test all the pack/unpack of the NWStatus class. - """ +def testCoreStatus_Pack(mockGUI, mockRnd): + """Test data packing of the NWStatus class.""" nStatus = NWStatus(NWStatus.STATUS) - nStatus.write(None, "New", (100, 100, 100)) - nStatus.write(None, "Note", (200, 50, 0)) - nStatus.write(None, "Draft", (200, 150, 0)) - nStatus.write(None, "Finished", (50, 200, 0)) + nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0) + nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 0) + nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 0) + nStatus.add(None, "Finished", (50, 200, 0), "SQUARE", 0) countTo = [3, 5, 7, 9] for i, n in enumerate(countTo): @@ -318,52 +333,85 @@ def testCoreStatus_PackUnpack(mockRnd): "count": "3", "red": "100", "green": "100", - "blue": "100" + "blue": "100", + "shape": "SQUARE", }), ("Note", { "key": statusKeys[1], "count": "5", "red": "200", "green": "50", - "blue": "0" + "blue": "0", + "shape": "CIRCLE", }), ("Draft", { "key": statusKeys[2], "count": "7", "red": "200", "green": "150", - "blue": "0" + "blue": "0", + "shape": "SQUARE", }), ("Finished", { "key": statusKeys[3], "count": "9", "red": "50", "green": "200", - "blue": "0" + "blue": "0", + "shape": "SQUARE", }), ] - # Unpack - nStatus = NWStatus(NWStatus.STATUS) - nStatus.unpack({ - statusKeys[0]: {"label": "New0", "colour": (100, 100, 100), "count": countTo[0]}, - statusKeys[1]: {"label": "New1", "colour": (150, 150, 150), "count": countTo[1]}, - statusKeys[2]: {"label": "New2", "colour": (200, 200, 200), "count": countTo[2]}, - statusKeys[3]: {"label": "New3", "colour": (250, 250, 250), "count": countTo[3]}, - }) - assert len(nStatus._store) == 4 - assert list(nStatus._store.keys()) == statusKeys - assert nStatus._store[statusKeys[0]]["name"] == "New0" - assert nStatus._store[statusKeys[1]]["name"] == "New1" - assert nStatus._store[statusKeys[2]]["name"] == "New2" - assert nStatus._store[statusKeys[3]]["name"] == "New3" - assert nStatus._store[statusKeys[0]]["cols"] == (100, 100, 100) - assert nStatus._store[statusKeys[1]]["cols"] == (150, 150, 150) - assert nStatus._store[statusKeys[2]]["cols"] == (200, 200, 200) - assert nStatus._store[statusKeys[3]]["cols"] == (250, 250, 250) - assert nStatus._store[statusKeys[0]]["count"] == countTo[0] - assert nStatus._store[statusKeys[1]]["count"] == countTo[1] - assert nStatus._store[statusKeys[2]]["count"] == countTo[2] - assert nStatus._store[statusKeys[3]]["count"] == countTo[3] +# END Test testCoreStatus_Pack -# END Test testCoreStatus_PackUnpack + +@pytest.mark.core +def testCoreStatus_ShapeCache(): + """Test the _ShapeCache class.""" + shapes = _ShapeCache() + + # Generate all shapes + square = shapes.getShape(nwStatusShape.SQUARE) + circleQ = shapes.getShape(nwStatusShape.CIRCLE_Q) + circleH = shapes.getShape(nwStatusShape.CIRCLE_H) + circleT = shapes.getShape(nwStatusShape.CIRCLE_T) + circle = shapes.getShape(nwStatusShape.CIRCLE) + triangle = shapes.getShape(nwStatusShape.TRIANGLE) + nabla = shapes.getShape(nwStatusShape.NABLA) + diamond = shapes.getShape(nwStatusShape.DIAMOND) + pentagon = shapes.getShape(nwStatusShape.PENTAGON) + hexagon = shapes.getShape(nwStatusShape.HEXAGON) + star = shapes.getShape(nwStatusShape.STAR) + pacman = shapes.getShape(nwStatusShape.PACMAN) + bars1 = shapes.getShape(nwStatusShape.BARS_1) + bars2 = shapes.getShape(nwStatusShape.BARS_2) + bars3 = shapes.getShape(nwStatusShape.BARS_3) + bars4 = shapes.getShape(nwStatusShape.BARS_4) + block1 = shapes.getShape(nwStatusShape.BLOCK_1) + block2 = shapes.getShape(nwStatusShape.BLOCK_2) + block3 = shapes.getShape(nwStatusShape.BLOCK_3) + block4 = shapes.getShape(nwStatusShape.BLOCK_4) + + # Request again should return from cache + assert shapes.getShape(nwStatusShape.SQUARE) is square + assert shapes.getShape(nwStatusShape.CIRCLE_Q) is circleQ + assert shapes.getShape(nwStatusShape.CIRCLE_H) is circleH + assert shapes.getShape(nwStatusShape.CIRCLE_T) is circleT + assert shapes.getShape(nwStatusShape.CIRCLE) is circle + assert shapes.getShape(nwStatusShape.TRIANGLE) is triangle + assert shapes.getShape(nwStatusShape.NABLA) is nabla + assert shapes.getShape(nwStatusShape.DIAMOND) is diamond + assert shapes.getShape(nwStatusShape.PENTAGON) is pentagon + assert shapes.getShape(nwStatusShape.HEXAGON) is hexagon + assert shapes.getShape(nwStatusShape.STAR) is star + assert shapes.getShape(nwStatusShape.PACMAN) is pacman + assert shapes.getShape(nwStatusShape.BARS_1) is bars1 + assert shapes.getShape(nwStatusShape.BARS_2) is bars2 + assert shapes.getShape(nwStatusShape.BARS_3) is bars3 + assert shapes.getShape(nwStatusShape.BARS_4) is bars4 + assert shapes.getShape(nwStatusShape.BLOCK_1) is block1 + assert shapes.getShape(nwStatusShape.BLOCK_2) is block2 + assert shapes.getShape(nwStatusShape.BLOCK_3) is block3 + assert shapes.getShape(nwStatusShape.BLOCK_4) is block4 + +# END Test testCoreStatus_ShapeCache diff --git a/tests/test_core/test_core_tokenizer.py b/tests/test_core/test_core_tokenizer.py index beba03f0..86cc0d50 100644 --- a/tests/test_core/test_core_tokenizer.py +++ b/tests/test_core/test_core_tokenizer.py @@ -949,6 +949,15 @@ def testCoreToken_ExtractFormats(mockGUI): (10, tokens.FMT_B_B), (31, tokens.FMT_I_B), (38, tokens.FMT_B_E), (38, tokens.FMT_I_E) ] + # So does this + text, fmt = tokens._extractFormats( + "Text with [b]bold and overlapping [i]italics[/b] in[/i] it." + ) + assert text == "Text with bold and overlapping italics in it." + assert fmt == [ + (10, tokens.FMT_B_B), (31, tokens.FMT_I_B), (38, tokens.FMT_B_E), (41, tokens.FMT_I_E) + ] + # END Test testCoreToken_ExtractFormats diff --git a/tests/test_dialogs/test_dlg_projectsettings.py b/tests/test_dialogs/test_dlg_projectsettings.py index 9831ea1f..498dfda5 100644 --- a/tests/test_dialogs/test_dlg_projectsettings.py +++ b/tests/test_dialogs/test_dlg_projectsettings.py @@ -24,14 +24,13 @@ import pytest from tools import C, buildTestProject -from PyQt5.QtCore import Qt from PyQt5.QtGui import QColor from PyQt5.QtWidgets import QDialog, QAction, QColorDialog from novelwriter import CONFIG, SHARED from novelwriter.dialogs.editlabel import GuiEditLabel from novelwriter.dialogs.projectsettings import GuiProjectSettings -from novelwriter.enum import nwItemType +from novelwriter.enum import nwItemType, nwStatusShape from novelwriter.types import QtMouseLeft KEY_DELAY = 1 @@ -169,8 +168,8 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn nwGUI.rebuildTrees() project.countStatus() - assert [e["count"] for _, e in project.data.itemStatus.items()] == [2, 0, 2, 1] - assert [e["count"] for _, e in project.data.itemImport.items()] == [3, 0, 2, 1] + assert [e.count for _, e in project.data.itemStatus.iterItems()] == [2, 0, 2, 1] + assert [e.count for _, e in project.data.itemImport.iterItems()] == [3, 0, 2, 1] # Create Dialog projSettings = GuiProjectSettings(nwGUI, GuiProjectSettings.PAGE_STATUS) @@ -182,20 +181,20 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn status = projSettings.statusPage - assert status.wasChanged is False - assert status.getNewList() == ([], []) + assert status.changed is False + assert status.getNewList() == [] assert status.listBox.topLevelItemCount() == 4 # Can't delete the first item (it's in use) status.listBox.clearSelection() status.listBox.setCurrentItem(status.listBox.topLevelItem(0)) - qtbot.mouseClick(status.delButton, QtMouseLeft) + status.delButton.click() assert status.listBox.topLevelItemCount() == 4 # Can delete the second item status.listBox.clearSelection() status.listBox.setCurrentItem(status.listBox.topLevelItem(1)) - qtbot.mouseClick(status.delButton, QtMouseLeft) + status.delButton.click() assert status.listBox.topLevelItemCount() == 3 # Add a new item @@ -204,39 +203,38 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn status.addButton.click() status.listBox.setCurrentItem(status.listBox.topLevelItem(3)) status.editName.setText("Final") - status.colButton.click() - status.saveButton.click() + status.colorButton.click() + status._selectShape(nwStatusShape.CIRCLE) + status.applyButton.click() assert status.listBox.topLevelItemCount() == 4 - assert status.wasChanged is True - assert status.getNewList() == ( - [ - { - "key": C.sNew, - "name": "New", - "cols": (100, 100, 100) - }, { - "key": C.sDraft, - "name": "Draft", - "cols": (200, 150, 0) - }, { - "key": C.sFinished, - "name": "Finished", - "cols": (50, 200, 0) - }, { - "key": None, - "name": "Final", - "cols": (20, 30, 40) - } - ], [ - C.sNote # Deleted item - ] - ) + assert status.changed is True + update = status.getNewList() + + assert update[0][0] == C.sNew + assert update[0][1].name == "New" + assert update[0][1].color == QColor(100, 100, 100) + assert update[0][1].shape == nwStatusShape.SQUARE + + assert update[1][0] == C.sDraft + assert update[1][1].name == "Draft" + assert update[1][1].color == QColor(200, 150, 0) + assert update[1][1].shape == nwStatusShape.SQUARE + + assert update[2][0] == C.sFinished + assert update[2][1].name == "Finished" + assert update[2][1].color == QColor(50, 200, 0) + assert update[2][1].shape == nwStatusShape.SQUARE + + assert update[3][0] is None + assert update[3][1].name == "Final" + assert update[3][1].color == QColor(20, 30, 40) + assert update[3][1].shape == nwStatusShape.CIRCLE # Move items, none selected -> no change status.listBox.clearSelection() status._moveItem(1) - assert [x["key"] for x in status.getNewList()[0]] == [ + assert [x[0] for x in status.getNewList()] == [ C.sNew, C.sDraft, C.sFinished, None ] @@ -244,7 +242,7 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn status.listBox.clearSelection() status.listBox.setCurrentItem(status.listBox.topLevelItem(0)) status._moveItem(-1) - assert [x["key"] for x in status.getNewList()[0]] == [ + assert [x[0] for x in status.getNewList()] == [ C.sNew, C.sDraft, C.sFinished, None ] @@ -252,13 +250,13 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn status.listBox.clearSelection() status.listBox.setCurrentItem(status.listBox.topLevelItem(3)) status._moveItem(-1) - assert [x["key"] for x in status.getNewList()[0]] == [ + assert [x[0] for x in status.getNewList()] == [ C.sNew, C.sDraft, None, C.sFinished ] # Move items, same selected, move down -> allowed status._moveItem(1) - assert [x["key"] for x in status.getNewList()[0]] == [ + assert [x[0] for x in status.getNewList()] == [ C.sNew, C.sDraft, C.sFinished, None ] @@ -271,62 +269,58 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn # Delete unused entry importance.listBox.clearSelection() importance.listBox.setCurrentItem(importance.listBox.topLevelItem(1)) - qtbot.mouseClick(importance.delButton, QtMouseLeft) + importance.delButton.click() assert importance.listBox.topLevelItemCount() == 3 # Add a new entry with monkeypatch.context() as mp: mp.setattr(QColorDialog, "getColor", lambda *a: QColor(20, 30, 40)) - qtbot.mouseClick(importance.addButton, QtMouseLeft) + importance.addButton.click() importance.listBox.clearSelection() importance.listBox.setCurrentItem(importance.listBox.topLevelItem(3)) - for _ in range(8): - qtbot.keyClick(importance.editName, Qt.Key.Key_Backspace, delay=KEY_DELAY) - for c in "Final": - qtbot.keyClick(importance.editName, c, delay=KEY_DELAY) - qtbot.mouseClick(importance.colButton, QtMouseLeft) - qtbot.mouseClick(importance.saveButton, QtMouseLeft) + importance.editName.setText("Final") + importance.colorButton.click() + importance._selectShape(nwStatusShape.TRIANGLE) + importance.applyButton.click() assert importance.listBox.topLevelItemCount() == 4 - assert importance.wasChanged is True - assert importance.getNewList() == ( - [ - { - "key": C.iNew, - "name": "New", - "cols": (100, 100, 100) - }, { - "key": C.iMajor, - "name": "Major", - "cols": (200, 150, 0) - }, { - "key": C.iMain, - "name": "Main", - "cols": (50, 200, 0) - }, { - "key": None, - "name": "Final", - "cols": (20, 30, 40) - } - ], [ - C.iMinor # Deleted item - ] - ) + assert importance.changed is True + update = importance.getNewList() + + assert update[0][0] == C.iNew + assert update[0][1].name == "New" + assert update[0][1].color == QColor(100, 100, 100) + assert update[0][1].shape == nwStatusShape.SQUARE + + assert update[1][0] == C.iMajor + assert update[1][1].name == "Major" + assert update[1][1].color == QColor(200, 150, 0) + assert update[1][1].shape == nwStatusShape.SQUARE + + assert update[2][0] == C.iMain + assert update[2][1].name == "Main" + assert update[2][1].color == QColor(50, 200, 0) + assert update[2][1].shape == nwStatusShape.SQUARE + + assert update[3][0] is None + assert update[3][1].name == "Final" + assert update[3][1].color == QColor(20, 30, 40) + assert update[3][1].shape == nwStatusShape.TRIANGLE # Check Project projSettings._doSave() - statusItems = dict(project.data.itemStatus.items()) - assert statusItems[C.sNew]["name"] == "New" - assert statusItems[C.sDraft]["name"] == "Draft" - assert statusItems[C.sFinished]["name"] == "Finished" - assert statusItems["s000013"]["name"] == "Final" + statusItems = dict(project.data.itemStatus.iterItems()) + assert statusItems[C.sNew].name == "New" + assert statusItems[C.sDraft].name == "Draft" + assert statusItems[C.sFinished].name == "Finished" + assert statusItems["s000013"].name == "Final" - importItems = dict(project.data.itemImport.items()) - assert importItems[C.iNew]["name"] == "New" - assert importItems[C.iMajor]["name"] == "Major" - assert importItems[C.iMain]["name"] == "Main" - assert importItems["i000014"]["name"] == "Final" + importItems = dict(project.data.itemImport.iterItems()) + assert importItems[C.iNew].name == "New" + assert importItems[C.iMajor].name == "Major" + assert importItems[C.iMain].name == "Main" + assert importItems["i000014"].name == "Final" # qtbot.stop() @@ -363,7 +357,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd): # Nothing to save or delete replace.listBox.clearSelection() - replace._saveEntry() + replace._applyChanges() replace._delEntry() assert replace.listBox.topLevelItemCount() == 2 @@ -381,7 +375,7 @@ def testDlgProjSettings_Replace(qtbot, monkeypatch, nwGUI, projPath, mockRnd): replace.editValue.setText("") for c in "With This Stuff ": qtbot.keyClick(replace.editValue, c, delay=KEY_DELAY) - qtbot.mouseClick(replace.saveButton, QtMouseLeft) + qtbot.mouseClick(replace.applyButton, QtMouseLeft) assert replace.listBox.topLevelItem(2).text(0) == "" # type: ignore assert replace.listBox.topLevelItem(2).text(1) == "With This Stuff " # type: ignore diff --git a/tests/test_gui/test_gui_doceditor.py b/tests/test_gui/test_gui_doceditor.py index cfcb8f68..be409df2 100644 --- a/tests/test_gui/test_gui_doceditor.py +++ b/tests/test_gui/test_gui_doceditor.py @@ -880,13 +880,13 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Wrap Equal nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._wrapSelection("=") is True + nwGUI.docEditor._wrapSelection("=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=") # Wrap Unequal nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._wrapSelection("=", "*") is True + nwGUI.docEditor._wrapSelection("=", "*") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur*") # Past Paragraph @@ -895,7 +895,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): cursor.setPosition(13, QTextCursor.MoveAnchor) cursor.setPosition(1000, QTextCursor.KeepAnchor) nwGUI.docEditor.setTextCursor(cursor) - assert nwGUI.docEditor._wrapSelection("=") is True + nwGUI.docEditor._wrapSelection("=") newText = nwGUI.docEditor.getText() newPara = list(filter(str.strip, newText.split("\n"))) @@ -910,14 +910,15 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Block format repetition nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(39) - assert nwGUI.docEditor._toggleFormat(1, "=") is True + nwGUI.docEditor._toggleFormat(1, "=") + assert nwGUI.docEditor.getText() == text.replace("amet", "=amet=", 1) + nwGUI.docEditor._toggleFormat(1, "=") assert nwGUI.docEditor.getText() == text.replace("amet", "=amet=", 1) - assert nwGUI.docEditor._toggleFormat(1, "=") is False # Wrap Single Equal nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._toggleFormat(1, "=") is True + nwGUI.docEditor._toggleFormat(1, "=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=") # Past Paragraph @@ -926,7 +927,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): cursor.setPosition(13, QTextCursor.MoveAnchor) cursor.setPosition(1000, QTextCursor.KeepAnchor) nwGUI.docEditor.setTextCursor(cursor) - assert nwGUI.docEditor._toggleFormat(1, "=") is True + nwGUI.docEditor._toggleFormat(1, "=") newText = nwGUI.docEditor.getText() newPara = list(filter(str.strip, newText.split("\n"))) @@ -936,30 +937,40 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): # Wrap Double Equal nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._toggleFormat(2, "=") is True + nwGUI.docEditor._toggleFormat(2, "=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "==consectetur==") + # Toggle Double Equal with Selection + nwGUI.docEditor.replaceText(text) + nwGUI.docEditor.setCursorSelection(41, 11) + nwGUI.docEditor._toggleFormat(2, "=") + assert nwGUI.docEditor.getText() == text.replace("consectetur", "==consectetur==") + assert nwGUI.docEditor.getSelectedText() == "consectetur" + nwGUI.docEditor._toggleFormat(2, "=") + assert nwGUI.docEditor.getText() == text + assert nwGUI.docEditor.getSelectedText() == "consectetur" + # Toggle Double Equal nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._toggleFormat(2, "=") is True - assert nwGUI.docEditor._toggleFormat(2, "=") is True + nwGUI.docEditor._toggleFormat(2, "=") + nwGUI.docEditor._toggleFormat(2, "=") assert nwGUI.docEditor.getText() == text # Toggle Triple+Double Equal nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._toggleFormat(3, "=") is True - assert nwGUI.docEditor._toggleFormat(2, "=") is True + nwGUI.docEditor._toggleFormat(3, "=") + nwGUI.docEditor._toggleFormat(2, "=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "=consectetur=") # Toggle Unequal repText = text.replace("consectetur", "=consectetur==") nwGUI.docEditor.replaceText(repText) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._toggleFormat(1, "=") is True + nwGUI.docEditor._toggleFormat(1, "=") assert nwGUI.docEditor.getText() == text.replace("consectetur", "consectetur=") - assert nwGUI.docEditor._toggleFormat(1, "=") is True + nwGUI.docEditor._toggleFormat(1, "=") assert nwGUI.docEditor.getText() == repText # Replace Quotes @@ -969,7 +980,8 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): text = "### A Scene\n\n%s" % ipsumText[0].replace("consectetur", "=consectetur=") nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) - assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is False + nwGUI.docEditor._replaceQuotes("=", "<", ">") + assert nwGUI.docEditor.getText() == text # First Paragraph Selected # This should not replace anything in second paragraph @@ -977,7 +989,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) assert nwGUI.docEditor.docAction(nwDocAction.SEL_PARA) - assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True + nwGUI.docEditor._replaceQuotes("=", "<", ">") newText = nwGUI.docEditor.getText() newPara = list(filter(str.strip, newText.split("\n"))) @@ -989,7 +1001,7 @@ def testGuiEditor_TextManipulation(qtbot, nwGUI, projPath, ipsumText, mockRnd): nwGUI.docEditor.replaceText(text) nwGUI.docEditor.setCursorPosition(45) assert nwGUI.docEditor.docAction(nwDocAction.SEL_ALL) - assert nwGUI.docEditor._replaceQuotes("=", "<", ">") is True + nwGUI.docEditor._replaceQuotes("=", "<", ">") assert nwGUI.docEditor.getText() == text.replace("=Lorem=", "") # Remove Line Breaks diff --git a/tests/test_gui/test_gui_theme.py b/tests/test_gui/test_gui_theme.py index ad2d7040..0490eabf 100644 --- a/tests/test_gui/test_gui_theme.py +++ b/tests/test_gui/test_gui_theme.py @@ -186,11 +186,11 @@ def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI): # Check a few values assert mainTheme._guiPalette.color( - QPalette.ColorRole.Window).getRgb() == (239, 239, 239, 255) + QPalette.ColorRole.Window).getRgb() == (239, 239, 239, 255) assert mainTheme._guiPalette.color( - QPalette.ColorRole.WindowText).getRgb() == (0, 0, 0, 255) + QPalette.ColorRole.WindowText).getRgb() == (0, 0, 0, 255) assert mainTheme._guiPalette.color( - QPalette.ColorRole.Base).getRgb() == (255, 255, 255, 255) + QPalette.ColorRole.Base).getRgb() == (255, 255, 255, 255) assert mainTheme._guiPalette.color( QPalette.ColorRole.AlternateBase).getRgb() == (239, 239, 239, 255) diff --git a/tests/test_text/test_core_counting.py b/tests/test_text/test_core_counting.py index bab0596e..05780c49 100644 --- a/tests/test_text/test_core_counting.py +++ b/tests/test_text/test_core_counting.py @@ -29,7 +29,7 @@ from novelwriter.text.counting import bodyTextCounter, preProcessText, standardC def testTextCounting_preProcessText(): """Test the text preprocessor for counters.""" # Not Text - assert preProcessText(None) == [] + assert preProcessText(None) == [] # type: ignore # No Text assert preProcessText("") == [] @@ -81,6 +81,10 @@ def testTextCounting_standardCounter(): assert standardCounter(None) == (0, 0, 0) # type: ignore assert standardCounter(1234) == (0, 0, 0) # type: ignore + # Test Corner Cases, Bug #1816 + assert standardCounter("> ") == (0, 0, 0) + assert standardCounter(" <") == (0, 0, 0) + # General Text cC, wC, pC = standardCounter(( "#! Title\n\n" @@ -88,7 +92,8 @@ def testTextCounting_standardCounter(): "# Heading One\n" "## Heading Two\n" "### Heading Three\n" - "#### Heading Four\n\n" + "###! Heading Four\n" + "#### Heading Five\n\n" "@tag: value\n\n" "% A comment that should not be counted.\n\n" "The first paragraph.\n\n" @@ -96,8 +101,8 @@ def testTextCounting_standardCounter(): "The third paragraph.\n\n" "Dashes\u2013and even longer\u2014dashes." )) - assert cC == 151 - assert wC == 24 + assert cC == 163 + assert wC == 26 assert pC == 4 # Text Alignment @@ -182,7 +187,7 @@ def testTextCounting_standardCounter(): def testTextCounting_bodyTextCounter(): """Test the body text counter.""" # Not Text - assert bodyTextCounter(None) == (0, 0, 0) + assert bodyTextCounter(None) == (0, 0, 0) # type: ignore # General Text wC, cC, sC = bodyTextCounter(( diff --git a/tests/test_tools/test_tools_manuscript.py b/tests/test_tools/test_tools_manuscript.py index 0d2e04ca..5e80767f 100644 --- a/tests/test_tools/test_tools_manuscript.py +++ b/tests/test_tools/test_tools_manuscript.py @@ -212,7 +212,7 @@ def testManuscript_Features(monkeypatch, qtbot, nwGUI, projPath, mockRnd): build = manus._builds.getBuild(first.data(GuiManuscript.D_KEY)) assert isinstance(build, BuildSettings) build.setValue("headings.fmtScene", nwHeadFmt.TITLE) - build.setValue("headings.fmtHardScene", nwHeadFmt.TITLE) + build.setValue("headings.fmtAltScene", nwHeadFmt.TITLE) manus._builds.setBuild(build) # Preview again, and allow cache file to be created diff --git a/tests/test_tools/test_tools_manussettings.py b/tests/test_tools/test_tools_manussettings.py index 179df303..3f51c9cd 100644 --- a/tests/test_tools/test_tools_manussettings.py +++ b/tests/test_tools/test_tools_manussettings.py @@ -335,7 +335,7 @@ def testBuildSettings_Headings(qtbot: QtBot, nwGUI: GuiMain): build.setValue("headings.fmtChapter", chTitle) build.setValue("headings.fmtUnnumbered", unTitle) build.setValue("headings.fmtScene", scTitle) - build.setValue("headings.fmtHardScene", shTitle) + build.setValue("headings.fmtAltScene", shTitle) build.setValue("headings.fmtSection", sxTitle) build.setValue("headings.hideScene", False) build.setValue("headings.hideSection", False) @@ -354,7 +354,7 @@ def testBuildSettings_Headings(qtbot: QtBot, nwGUI: GuiMain): assert headTab.fmtChapter.text() == chTitle assert headTab.fmtUnnumbered.text() == unTitle assert headTab.fmtScene.text() == scTitle - assert headTab.fmtHScene.text() == shTitle + assert headTab.fmtAScene.text() == shTitle assert headTab.fmtSection.text() == sxTitle assert headTab.swtScene.isChecked() is False assert headTab.swtSection.isChecked() is False @@ -401,7 +401,7 @@ def testBuildSettings_Headings(qtbot: QtBot, nwGUI: GuiMain): assert headTab.editTextBox.isEnabled() is False # Hard Scene - headTab.btnHScene.click() + headTab.btnAScene.click() assert headTab._editing == headTab.EDIT_HSCENE assert headTab.editTextBox.isEnabled() is True assert headTab.editTextBox.toPlainText() == shTitle @@ -467,10 +467,10 @@ def testBuildSettings_Headings(qtbot: QtBot, nwGUI: GuiMain): headTab.btnApply.click() assert build.getStr("headings.fmtScene") == nwHeadFmt.TITLE - headTab.btnHScene.click() + headTab.btnAScene.click() headTab.editTextBox.setPlainText(nwHeadFmt.TITLE) headTab.btnApply.click() - assert build.getStr("headings.fmtHardScene") == nwHeadFmt.TITLE + assert build.getStr("headings.fmtAltScene") == nwHeadFmt.TITLE headTab.btnSection.click() headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)