"""
novelWriter – GUI Main Menu
===========================

File History:
Created: 2019-04-27 [0.0.1]

This file is a part of novelWriter
Copyright 2018–2023, Veronica Berglyd Olsen

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
from __future__ import annotations

import logging

from typing import TYPE_CHECKING
from pathlib import Path
from urllib.parse import urljoin
from urllib.request import pathname2url

from PyQt5.QtCore import QUrl, pyqtSlot
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtWidgets import QMenuBar, QAction

from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
from novelwriter.constants import nwConst, trConst, nwKeyWords, nwLabels, nwUnicode

if TYPE_CHECKING:  # pragma: no cover
    from novelwriter.guimain import GuiMain

logger = logging.getLogger(__name__)


class GuiMainMenu(QMenuBar):
    """The GUI main menu. All menu actions are defined here with the
    main menu as the owner. Each widget that need them elsewhere need to
    add them from this class.
    """

    def __init__(self, mainGui: GuiMain) -> None:
        super().__init__(parent=mainGui)

        logger.debug("Create: GuiMainMenu")

        self.mainGui = mainGui

        # Build Menu
        self._buildProjectMenu()
        self._buildDocumentMenu()
        self._buildEditMenu()
        self._buildViewMenu()
        self._buildInsertMenu()
        self._buildFormatMenu()
        self._buildSearchMenu()
        self._buildToolsMenu()
        self._buildHelpMenu()

        # Function Pointers
        self._docAction     = self.mainGui.passDocumentAction
        self._docInsert     = self.mainGui.docEditor.insertText
        self._insertKeyWord = self.mainGui.docEditor.insertKeyWord

        logger.debug("Ready: GuiMainMenu")

        return

    ##
    #  Update Menu on Settings Changed
    ##

    def setSpellCheck(self, state: bool) -> None:
        """Forward spell check check state to its action."""
        self.aSpellCheck.setChecked(state)
        return

    ##
    #  Private Slots
    ##

    @pyqtSlot()
    def _toggleSpellCheck(self) -> None:
        """Toggle spell checking. The active status of the spell check
        flag is handled by the document editor class, so we make no
        decision, just pass a None to the function and let it decide.
        """
        self.mainGui.docEditor.toggleSpellCheck(None)
        return

    @pyqtSlot(str)
    def _openWebsite(self, url: str) -> None:
        """Open a URL in the system's default browser."""
        QDesktopServices.openUrl(QUrl(url))
        return

    @pyqtSlot()
    def _openUserManualFile(self) -> None:
        """Open the documentation in PDF format."""
        if isinstance(CONFIG.pdfDocs, Path):
            QDesktopServices.openUrl(
                QUrl(urljoin("file:", pathname2url(str(CONFIG.pdfDocs))))
            )
        return

    @pyqtSlot(str)
    def _changeSpelling(self, language: str) -> None:
        """Change the spell check language."""
        SHARED.project.data.setSpellLang(language)
        SHARED.updateSpellCheckLanguage()
        return

    ##
    #  Internal Functions
    ##

    def _buildProjectMenu(self) -> None:
        """Assemble the Project menu."""
        # Project
        self.projMenu = self.addMenu(self.tr("&Project"))

        # Project > New Project
        self.aNewProject = QAction(self.tr("New Project"), self)
        self.aNewProject.triggered.connect(lambda: self.mainGui.newProject(None))
        self.projMenu.addAction(self.aNewProject)

        # Project > Open Project
        self.aOpenProject = QAction(self.tr("Open Project"), self)
        self.aOpenProject.setShortcut("Ctrl+Shift+O")
        self.aOpenProject.triggered.connect(lambda: self.mainGui.showProjectLoadDialog())
        self.projMenu.addAction(self.aOpenProject)

        # Project > Save Project
        self.aSaveProject = QAction(self.tr("Save Project"), self)
        self.aSaveProject.setShortcut("Ctrl+Shift+S")
        self.aSaveProject.triggered.connect(lambda: self.mainGui.saveProject())
        self.projMenu.addAction(self.aSaveProject)

        # Project > Close Project
        self.aCloseProject = QAction(self.tr("Close Project"), self)
        self.aCloseProject.setShortcut("Ctrl+Shift+W")
        self.aCloseProject.triggered.connect(lambda: self.mainGui.closeProject(False))
        self.projMenu.addAction(self.aCloseProject)

        # Project > Separator
        self.projMenu.addSeparator()

        # Project > Project Settings
        self.aProjectSettings = QAction(self.tr("Project Settings"), self)
        self.aProjectSettings.setShortcut("Ctrl+Shift+,")
        self.aProjectSettings.triggered.connect(lambda: self.mainGui.showProjectSettingsDialog())
        self.projMenu.addAction(self.aProjectSettings)

        # Project > Project Details
        self.aProjectDetails = QAction(self.tr("Project Details"), self)
        self.aProjectDetails.setShortcut("Shift+F6")
        self.aProjectDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog())
        self.projMenu.addAction(self.aProjectDetails)

        # Project > Separator
        self.projMenu.addSeparator()

        # Project > Edit
        self.aEditItem = QAction(self.tr("Rename Item"), self)
        self.aEditItem.setShortcut("F2")
        self.aEditItem.triggered.connect(lambda: self.mainGui.editItemLabel(None))
        self.projMenu.addAction(self.aEditItem)

        # Project > Delete
        self.aDeleteItem = QAction(self.tr("Delete Item"), self)
        self.aDeleteItem.setShortcut("Ctrl+Shift+Del")
        self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None))
        self.projMenu.addAction(self.aDeleteItem)

        # Project > Empty Trash
        self.aEmptyTrash = QAction(self.tr("Empty Trash"), self)
        self.aEmptyTrash.triggered.connect(lambda: self.mainGui.projView.emptyTrash())
        self.projMenu.addAction(self.aEmptyTrash)

        # Project > Separator
        self.projMenu.addSeparator()

        # Project > Exit
        self.aExitNW = QAction(self.tr("Exit"), self)
        self.aExitNW.setShortcut("Ctrl+Q")
        self.aExitNW.setMenuRole(QAction.QuitRole)
        self.aExitNW.triggered.connect(lambda: self.mainGui.closeMain())
        self.projMenu.addAction(self.aExitNW)

        return

    def _buildDocumentMenu(self) -> None:
        """Assemble the Document menu."""
        # Document
        self.docuMenu = self.addMenu(self.tr("&Document"))

        # Document > Open
        self.aOpenDoc = QAction(self.tr("Open Document"), self)
        self.aOpenDoc.setShortcut("Ctrl+O")
        self.aOpenDoc.triggered.connect(lambda: self.mainGui.openSelectedItem())
        self.docuMenu.addAction(self.aOpenDoc)

        # Document > Save
        self.aSaveDoc = QAction(self.tr("Save Document"), self)
        self.aSaveDoc.setShortcut("Ctrl+S")
        self.aSaveDoc.triggered.connect(lambda: self.mainGui.saveDocument())
        self.docuMenu.addAction(self.aSaveDoc)

        # Document > Close
        self.aCloseDoc = QAction(self.tr("Close Document"), self)
        self.aCloseDoc.setShortcut("Ctrl+W")
        self.aCloseDoc.triggered.connect(lambda: self.mainGui.closeDocEditor())
        self.docuMenu.addAction(self.aCloseDoc)

        # Document > Separator
        self.docuMenu.addSeparator()

        # Document > Preview
        self.aViewDoc = QAction(self.tr("View Document"), self)
        self.aViewDoc.setShortcut("Ctrl+R")
        self.aViewDoc.triggered.connect(lambda: self.mainGui.viewDocument(None))
        self.docuMenu.addAction(self.aViewDoc)

        # Document > Close Preview
        self.aCloseView = QAction(self.tr("Close Document View"), self)
        self.aCloseView.setShortcut("Ctrl+Shift+R")
        self.aCloseView.triggered.connect(lambda: self.mainGui.closeDocViewer())
        self.docuMenu.addAction(self.aCloseView)

        # Document > Separator
        self.docuMenu.addSeparator()

        # Document > Show File Details
        self.aFileDetails = QAction(self.tr("Show File Details"), self)
        self.aFileDetails.triggered.connect(lambda: self.mainGui.docEditor.revealLocation())
        self.docuMenu.addAction(self.aFileDetails)

        # Document > Import From File
        self.aImportFile = QAction(self.tr("Import Text from File"), self)
        self.aImportFile.setShortcut("Ctrl+Shift+I")
        self.aImportFile.triggered.connect(lambda: self.mainGui.importDocument())
        self.docuMenu.addAction(self.aImportFile)

        return

    def _buildEditMenu(self) -> None:
        """Assemble the Edit menu."""
        # Edit
        self.editMenu = self.addMenu(self.tr("&Edit"))

        # Edit > Undo
        self.aEditUndo = QAction(self.tr("Undo"), self)
        self.aEditUndo.setShortcut("Ctrl+Z")
        self.aEditUndo.triggered.connect(lambda: self._docAction(nwDocAction.UNDO))
        self.editMenu.addAction(self.aEditUndo)

        # Edit > Redo
        self.aEditRedo = QAction(self.tr("Redo"), self)
        self.aEditRedo.setShortcut("Ctrl+Y")
        self.aEditRedo.triggered.connect(lambda: self._docAction(nwDocAction.REDO))
        self.editMenu.addAction(self.aEditRedo)

        # Edit > Separator
        self.editMenu.addSeparator()

        # Edit > Cut
        self.aEditCut = QAction(self.tr("Cut"), self)
        self.aEditCut.setShortcut("Ctrl+X")
        self.aEditCut.triggered.connect(lambda: self._docAction(nwDocAction.CUT))
        self.editMenu.addAction(self.aEditCut)

        # Edit > Copy
        self.aEditCopy = QAction(self.tr("Copy"), self)
        self.aEditCopy.setShortcut("Ctrl+C")
        self.aEditCopy.triggered.connect(lambda: self._docAction(nwDocAction.COPY))
        self.editMenu.addAction(self.aEditCopy)

        # Edit > Paste
        self.aEditPaste = QAction(self.tr("Paste"), self)
        self.aEditPaste.setShortcut("Ctrl+V")
        self.aEditPaste.triggered.connect(lambda: self._docAction(nwDocAction.PASTE))
        self.editMenu.addAction(self.aEditPaste)

        # Edit > Separator
        self.editMenu.addSeparator()

        # Edit > Select All
        self.aSelectAll = QAction(self.tr("Select All"), self)
        self.aSelectAll.setShortcut("Ctrl+A")
        self.aSelectAll.triggered.connect(lambda: self._docAction(nwDocAction.SEL_ALL))
        self.editMenu.addAction(self.aSelectAll)

        # Edit > Select Paragraph
        self.aSelectPar = QAction(self.tr("Select Paragraph"), self)
        self.aSelectPar.setShortcut("Ctrl+Shift+A")
        self.aSelectPar.triggered.connect(lambda: self._docAction(nwDocAction.SEL_PARA))
        self.editMenu.addAction(self.aSelectPar)

        return

    def _buildViewMenu(self) -> None:
        """Assemble the View menu."""
        # View
        self.viewMenu = self.addMenu(self.tr("&View"))

        # View > TreeView
        self.aFocusTree = QAction(self.tr("Go to Project Tree"), self)
        if CONFIG.osWindows:
            self.aFocusTree.setShortcut("Ctrl+Alt+1")
        else:
            self.aFocusTree.setShortcut("Alt+1")
        self.aFocusTree.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.TREE))
        self.viewMenu.addAction(self.aFocusTree)

        # View > Document Pane 1
        self.aFocusEditor = QAction(self.tr("Go to Document Editor"), self)
        if CONFIG.osWindows:
            self.aFocusEditor.setShortcut("Ctrl+Alt+2")
        else:
            self.aFocusEditor.setShortcut("Alt+2")
        self.aFocusEditor.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.EDITOR))
        self.viewMenu.addAction(self.aFocusEditor)

        # View > Document Pane 2
        self.aFocusView = QAction(self.tr("Go to Document Viewer"), self)
        if CONFIG.osWindows:
            self.aFocusView.setShortcut("Ctrl+Alt+3")
        else:
            self.aFocusView.setShortcut("Alt+3")
        self.aFocusView.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.VIEWER))
        self.viewMenu.addAction(self.aFocusView)

        # View > Outline
        self.aFocusOutline = QAction(self.tr("Go to Outline"), self)
        if CONFIG.osWindows:
            self.aFocusOutline.setShortcut("Ctrl+Alt+4")
        else:
            self.aFocusOutline.setShortcut("Alt+4")
        self.aFocusOutline.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.OUTLINE))
        self.viewMenu.addAction(self.aFocusOutline)

        # View > Separator
        self.viewMenu.addSeparator()

        # View > Go Backward
        self.aViewPrev = QAction(self.tr("Navigate Backward"), self)
        self.aViewPrev.setShortcut("Alt+Left")
        self.aViewPrev.triggered.connect(self.mainGui.docViewer.navBackward)
        self.viewMenu.addAction(self.aViewPrev)

        # View > Go Forward
        self.aViewNext = QAction(self.tr("Navigate Forward"), self)
        self.aViewNext.setShortcut("Alt+Right")
        self.aViewNext.triggered.connect(self.mainGui.docViewer.navForward)
        self.viewMenu.addAction(self.aViewNext)

        # View > Separator
        self.viewMenu.addSeparator()

        # View > Focus Mode
        self.aFocusMode = QAction(self.tr("Focus Mode"), self)
        self.aFocusMode.setShortcut("F8")
        self.aFocusMode.triggered.connect(lambda: self.mainGui.toggleFocusMode())
        self.viewMenu.addAction(self.aFocusMode)

        # View > Toggle Full Screen
        self.aFullScreen = QAction(self.tr("Full Screen Mode"), self)
        self.aFullScreen.setShortcut("F11")
        self.aFullScreen.triggered.connect(lambda: self.mainGui.toggleFullScreenMode())
        self.viewMenu.addAction(self.aFullScreen)

        return

    def _buildInsertMenu(self) -> None:
        """Assemble the Insert menu."""
        # Insert
        self.insMenu = self.addMenu(self.tr("&Insert"))

        # Insert > Dashes and Dots
        self.mInsDashes = self.insMenu.addMenu(self.tr("Dashes"))

        # Insert > Short Dash
        self.aInsENDash = QAction(self.tr("Short Dash"), self)
        self.aInsENDash.setShortcut("Ctrl+K, -")
        self.aInsENDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_ENDASH))
        self.mInsDashes.addAction(self.aInsENDash)

        # Insert > Long Dash
        self.aInsEMDash = QAction(self.tr("Long Dash"), self)
        self.aInsEMDash.setShortcut("Ctrl+K, _")
        self.aInsEMDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_EMDASH))
        self.mInsDashes.addAction(self.aInsEMDash)

        # Insert > Long Dash
        self.aInsHorBar = QAction(self.tr("Horizontal Bar"), self)
        self.aInsHorBar.setShortcut("Ctrl+K, Ctrl+_")
        self.aInsHorBar.triggered.connect(lambda: self._docInsert(nwUnicode.U_HBAR))
        self.mInsDashes.addAction(self.aInsHorBar)

        # Insert > Figure Dash
        self.aInsFigDash = QAction(self.tr("Figure Dash"), self)
        self.aInsFigDash.setShortcut("Ctrl+K, ~")
        self.aInsFigDash.triggered.connect(lambda: self._docInsert(nwUnicode.U_FGDASH))
        self.mInsDashes.addAction(self.aInsFigDash)

        # Insert > Quote Marks
        self.mInsQuotes = self.insMenu.addMenu(self.tr("Quote Marks"))

        # Insert > Left Single Quote
        self.aInsQuoteLS = QAction(self.tr("Left Single Quote"), self)
        self.aInsQuoteLS.setShortcut("Ctrl+K, 1")
        self.aInsQuoteLS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LS))
        self.mInsQuotes.addAction(self.aInsQuoteLS)

        # Insert > Right Single Quote
        self.aInsQuoteRS = QAction(self.tr("Right Single Quote"), self)
        self.aInsQuoteRS.setShortcut("Ctrl+K, 2")
        self.aInsQuoteRS.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RS))
        self.mInsQuotes.addAction(self.aInsQuoteRS)

        # Insert > Left Double Quote
        self.aInsQuoteLD = QAction(self.tr("Left Double Quote"), self)
        self.aInsQuoteLD.setShortcut("Ctrl+K, 3")
        self.aInsQuoteLD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_LD))
        self.mInsQuotes.addAction(self.aInsQuoteLD)

        # Insert > Right Double Quote
        self.aInsQuoteRD = QAction(self.tr("Right Double Quote"), self)
        self.aInsQuoteRD.setShortcut("Ctrl+K, 4")
        self.aInsQuoteRD.triggered.connect(lambda: self._docInsert(nwDocInsert.QUOTE_RD))
        self.mInsQuotes.addAction(self.aInsQuoteRD)

        # Insert > Alternative Apostrophe
        self.aInsMSApos = QAction(self.tr("Alternative Apostrophe"), self)
        self.aInsMSApos.setShortcut("Ctrl+K, '")
        self.aInsMSApos.triggered.connect(lambda: self._docInsert(nwUnicode.U_MAPOSS))
        self.mInsQuotes.addAction(self.aInsMSApos)

        # Insert > Symbols
        self.mInsPunct = self.insMenu.addMenu(self.tr("General Punctuation"))

        # Insert > Ellipsis
        self.aInsEllipsis = QAction(self.tr("Ellipsis"), self)
        self.aInsEllipsis.setShortcut("Ctrl+K, .")
        self.aInsEllipsis.triggered.connect(lambda: self._docInsert(nwUnicode.U_HELLIP))
        self.mInsPunct.addAction(self.aInsEllipsis)

        # Insert > Prime
        self.aInsPrime = QAction(self.tr("Prime"), self)
        self.aInsPrime.setShortcut("Ctrl+K, Ctrl+'")
        self.aInsPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_PRIME))
        self.mInsPunct.addAction(self.aInsPrime)

        # Insert > Double Prime
        self.aInsDPrime = QAction(self.tr("Double Prime"), self)
        self.aInsDPrime.setShortcut("Ctrl+K, Ctrl+\"")
        self.aInsDPrime.triggered.connect(lambda: self._docInsert(nwUnicode.U_DPRIME))
        self.mInsPunct.addAction(self.aInsDPrime)

        # Insert > White Spaces
        self.mInsSpace = self.insMenu.addMenu(self.tr("White Spaces"))

        # Insert > Non-Breaking Space
        self.aInsNBSpace = QAction(self.tr("Non-Breaking Space"), self)
        self.aInsNBSpace.setShortcut("Ctrl+K, Space")
        self.aInsNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_NBSP))
        self.mInsSpace.addAction(self.aInsNBSpace)

        # Insert > Thin Space
        self.aInsThinSpace = QAction(self.tr("Thin Space"), self)
        self.aInsThinSpace.setShortcut("Ctrl+K, Shift+Space")
        self.aInsThinSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THSP))
        self.mInsSpace.addAction(self.aInsThinSpace)

        # Insert > Thin Non-Breaking Space
        self.aInsThinNBSpace = QAction(self.tr("Thin Non-Breaking Space"), self)
        self.aInsThinNBSpace.setShortcut("Ctrl+K, Ctrl+Space")
        self.aInsThinNBSpace.triggered.connect(lambda: self._docInsert(nwUnicode.U_THNBSP))
        self.mInsSpace.addAction(self.aInsThinNBSpace)

        # Insert > Symbols
        self.mInsSymbol = self.insMenu.addMenu(self.tr("Other Symbols"))

        # Insert > List Bullet
        self.aInsBullet = QAction(self.tr("List Bullet"), self)
        self.aInsBullet.setShortcut("Ctrl+K, *")
        self.aInsBullet.triggered.connect(lambda: self._docInsert(nwUnicode.U_BULL))
        self.mInsSymbol.addAction(self.aInsBullet)

        # Insert > Hyphen Bullet
        self.aInsHyBull = QAction(self.tr("Hyphen Bullet"), self)
        self.aInsHyBull.setShortcut("Ctrl+K, Ctrl+-")
        self.aInsHyBull.triggered.connect(lambda: self._docInsert(nwUnicode.U_HYBULL))
        self.mInsSymbol.addAction(self.aInsHyBull)

        # Insert > Flower Mark
        self.aInsFlower = QAction(self.tr("Flower Mark"), self)
        self.aInsFlower.setShortcut("Ctrl+K, Ctrl+*")
        self.aInsFlower.triggered.connect(lambda: self._docInsert(nwUnicode.U_FLOWER))
        self.mInsSymbol.addAction(self.aInsFlower)

        # Insert > Per Mille
        self.aInsPerMille = QAction(self.tr("Per Mille"), self)
        self.aInsPerMille.setShortcut("Ctrl+K, %")
        self.aInsPerMille.triggered.connect(lambda: self._docInsert(nwUnicode.U_PERMIL))
        self.mInsSymbol.addAction(self.aInsPerMille)

        # Insert > Degree Symbol
        self.aInsDegree = QAction(self.tr("Degree Symbol"), self)
        self.aInsDegree.setShortcut("Ctrl+K, Ctrl+O")
        self.aInsDegree.triggered.connect(lambda: self._docInsert(nwUnicode.U_DEGREE))
        self.mInsSymbol.addAction(self.aInsDegree)

        # Insert > Minus Sign
        self.aInsMinus = QAction(self.tr("Minus Sign"), self)
        self.aInsMinus.setShortcut("Ctrl+K, Ctrl+M")
        self.aInsMinus.triggered.connect(lambda: self._docInsert(nwUnicode.U_MINUS))
        self.mInsSymbol.addAction(self.aInsMinus)

        # Insert > Times Sign
        self.aInsTimes = QAction(self.tr("Times Sign"), self)
        self.aInsTimes.setShortcut("Ctrl+K, Ctrl+X")
        self.aInsTimes.triggered.connect(lambda: self._docInsert(nwUnicode.U_TIMES))
        self.mInsSymbol.addAction(self.aInsTimes)

        # Insert > Division
        self.aInsDivide = QAction(self.tr("Division Sign"), self)
        self.aInsDivide.setShortcut("Ctrl+K, Ctrl+D")
        self.aInsDivide.triggered.connect(lambda: self._docInsert(nwUnicode.U_DIVIDE))
        self.mInsSymbol.addAction(self.aInsDivide)

        # Insert > Tags and References
        self.mInsKeywords = self.insMenu.addMenu(self.tr("Tags and References"))
        self.mInsKWItems = {}
        self.mInsKWItems[nwKeyWords.TAG_KEY]    = (QAction(self.mInsKeywords), "Ctrl+K, G")
        self.mInsKWItems[nwKeyWords.POV_KEY]    = (QAction(self.mInsKeywords), "Ctrl+K, V")
        self.mInsKWItems[nwKeyWords.FOCUS_KEY]  = (QAction(self.mInsKeywords), "Ctrl+K, F")
        self.mInsKWItems[nwKeyWords.CHAR_KEY]   = (QAction(self.mInsKeywords), "Ctrl+K, C")
        self.mInsKWItems[nwKeyWords.PLOT_KEY]   = (QAction(self.mInsKeywords), "Ctrl+K, P")
        self.mInsKWItems[nwKeyWords.TIME_KEY]   = (QAction(self.mInsKeywords), "Ctrl+K, T")
        self.mInsKWItems[nwKeyWords.WORLD_KEY]  = (QAction(self.mInsKeywords), "Ctrl+K, L")
        self.mInsKWItems[nwKeyWords.OBJECT_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, O")
        self.mInsKWItems[nwKeyWords.ENTITY_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, E")
        self.mInsKWItems[nwKeyWords.CUSTOM_KEY] = (QAction(self.mInsKeywords), "Ctrl+K, X")
        for n, keyWord in enumerate(self.mInsKWItems):
            self.mInsKWItems[keyWord][0].setText(trConst(nwLabels.KEY_NAME[keyWord]))
            self.mInsKWItems[keyWord][0].setShortcut(self.mInsKWItems[keyWord][1])
            self.mInsKWItems[keyWord][0].triggered.connect(
                lambda n, keyWord=keyWord: self._insertKeyWord(keyWord)
            )
            self.mInsKeywords.addAction(self.mInsKWItems[keyWord][0])

        # Insert > Special Comments
        self.mInsComments = self.insMenu.addMenu(self.tr("Special Comments"))

        # Insert > Synopsis Comment
        self.aInsSynopsis = QAction(self.tr("Synopsis Comment"), self)
        self.aInsSynopsis.setShortcut("Ctrl+K, S")
        self.aInsSynopsis.triggered.connect(lambda: self._docInsert(nwDocInsert.SYNOPSIS))
        self.mInsComments.addAction(self.aInsSynopsis)

        # Insert > Symbols
        self.mInsBreaks = self.insMenu.addMenu(self.tr("Page Break and Space"))

        # Insert > New Page
        self.aInsNewPage = QAction(self.tr("Page Break"), self)
        self.aInsNewPage.triggered.connect(lambda: self._docInsert(nwDocInsert.NEW_PAGE))
        self.mInsBreaks.addAction(self.aInsNewPage)

        # Insert > Vertical Space (Single)
        self.aInsVSpaceS = QAction(self.tr("Vertical Space (Single)"), self)
        self.aInsVSpaceS.triggered.connect(lambda: self._docInsert(nwDocInsert.VSPACE_S))
        self.mInsBreaks.addAction(self.aInsVSpaceS)

        # Insert > Vertical Space (Multi)
        self.aInsVSpaceM = QAction(self.tr("Vertical Space (Multi)"), self)
        self.aInsVSpaceM.triggered.connect(lambda: self._docInsert(nwDocInsert.VSPACE_M))
        self.mInsBreaks.addAction(self.aInsVSpaceM)

        # Insert > Placeholder Text
        self.aLipsumText = QAction(self.tr("Placeholder Text"), self)
        self.aLipsumText.triggered.connect(lambda: self.mainGui.showLoremIpsumDialog())
        self.insMenu.addAction(self.aLipsumText)

        return

    def _buildFormatMenu(self) -> None:
        """Assemble the Format menu."""
        # Format
        self.fmtMenu = self.addMenu(self.tr("&Format"))

        # Format > Emphasis
        self.aFmtEmph = QAction(self.tr("Emphasis"), self)
        self.aFmtEmph.setShortcut("Ctrl+I")
        self.aFmtEmph.triggered.connect(lambda: self._docAction(nwDocAction.EMPH))
        self.fmtMenu.addAction(self.aFmtEmph)

        # Format > Strong Emphasis
        self.aFmtStrong = QAction(self.tr("Strong Emphasis"), self)
        self.aFmtStrong.setShortcut("Ctrl+B")
        self.aFmtStrong.triggered.connect(lambda: self._docAction(nwDocAction.STRONG))
        self.fmtMenu.addAction(self.aFmtStrong)

        # Format > Strikethrough
        self.aFmtStrike = QAction(self.tr("Strikethrough"), self)
        self.aFmtStrike.setShortcut("Ctrl+D")
        self.aFmtStrike.triggered.connect(lambda: self._docAction(nwDocAction.STRIKE))
        self.fmtMenu.addAction(self.aFmtStrike)

        # Edit > Separator
        self.fmtMenu.addSeparator()

        # Format > Double Quotes
        self.aFmtDQuote = QAction(self.tr("Wrap Double Quotes"), self)
        self.aFmtDQuote.setShortcut("Ctrl+\"")
        self.aFmtDQuote.triggered.connect(lambda: self._docAction(nwDocAction.D_QUOTE))
        self.fmtMenu.addAction(self.aFmtDQuote)

        # Format > Single Quotes
        self.aFmtSQuote = QAction(self.tr("Wrap Single Quotes"), self)
        self.aFmtSQuote.setShortcut("Ctrl+'")
        self.aFmtSQuote.triggered.connect(lambda: self._docAction(nwDocAction.S_QUOTE))
        self.fmtMenu.addAction(self.aFmtSQuote)

        # Format > Separator
        self.fmtMenu.addSeparator()

        # Format > Header 1 (Partition)
        self.aFmtHead1 = QAction(self.tr("Header 1 (Partition)"), self)
        self.aFmtHead1.setShortcut("Ctrl+1")
        self.aFmtHead1.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H1))
        self.fmtMenu.addAction(self.aFmtHead1)

        # Format > Header 2 (Chapter)
        self.aFmtHead2 = QAction(self.tr("Header 2 (Chapter)"), self)
        self.aFmtHead2.setShortcut("Ctrl+2")
        self.aFmtHead2.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H2))
        self.fmtMenu.addAction(self.aFmtHead2)

        # Format > Header 3 (Scene)
        self.aFmtHead3 = QAction(self.tr("Header 3 (Scene)"), self)
        self.aFmtHead3.setShortcut("Ctrl+3")
        self.aFmtHead3.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H3))
        self.fmtMenu.addAction(self.aFmtHead3)

        # Format > Header 4 (Section)
        self.aFmtHead4 = QAction(self.tr("Header 4 (Section)"), self)
        self.aFmtHead4.setShortcut("Ctrl+4")
        self.aFmtHead4.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_H4))
        self.fmtMenu.addAction(self.aFmtHead4)

        # Format > Separator
        self.fmtMenu.addSeparator()

        # Format > Novel Title
        self.aFmtTitle = QAction(self.tr("Novel Title"), self)
        self.aFmtTitle.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TTL))
        self.fmtMenu.addAction(self.aFmtTitle)

        # Format > Unnumbered Chapter
        self.aFmtUnNum = QAction(self.tr("Unnumbered Chapter"), self)
        self.aFmtUnNum.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_UNN))
        self.fmtMenu.addAction(self.aFmtUnNum)

        # Format > Separator
        self.fmtMenu.addSeparator()

        # Format > Align Left
        self.aFmtAlignLeft = QAction(self.tr("Align Left"), self)
        self.aFmtAlignLeft.setShortcut("Ctrl+5")
        self.aFmtAlignLeft.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_L))
        self.fmtMenu.addAction(self.aFmtAlignLeft)

        # Format > Align Centre
        self.aFmtAlignCentre = QAction(self.tr("Align Centre"), self)
        self.aFmtAlignCentre.setShortcut("Ctrl+6")
        self.aFmtAlignCentre.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_C))
        self.fmtMenu.addAction(self.aFmtAlignCentre)

        # Format > Align Right
        self.aFmtAlignRight = QAction(self.tr("Align Right"), self)
        self.aFmtAlignRight.setShortcut("Ctrl+7")
        self.aFmtAlignRight.triggered.connect(lambda: self._docAction(nwDocAction.ALIGN_R))
        self.fmtMenu.addAction(self.aFmtAlignRight)

        # Format > Separator
        self.fmtMenu.addSeparator()

        # Format > Indent Left
        self.aFmtIndentLeft = QAction(self.tr("Indent Left"), self)
        self.aFmtIndentLeft.setShortcut("Ctrl+8")
        self.aFmtIndentLeft.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_L))
        self.fmtMenu.addAction(self.aFmtIndentLeft)

        # Format > Indent Right
        self.aFmtIndentRight = QAction(self.tr("Indent Right"), self)
        self.aFmtIndentRight.setShortcut("Ctrl+9")
        self.aFmtIndentRight.triggered.connect(lambda: self._docAction(nwDocAction.INDENT_R))
        self.fmtMenu.addAction(self.aFmtIndentRight)

        # Format > Separator
        self.fmtMenu.addSeparator()

        # Format > Comment
        self.aFmtComment = QAction(self.tr("Toggle Comment"), self)
        self.aFmtComment.setShortcut("Ctrl+/")
        self.aFmtComment.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_COM))
        self.fmtMenu.addAction(self.aFmtComment)

        # Format > Remove Block Format
        self.aFmtNoFormat = QAction(self.tr("Remove Block Format"), self)
        self.aFmtNoFormat.setShortcuts(["Ctrl+0", "Ctrl+Shift+/"])
        self.aFmtNoFormat.triggered.connect(lambda: self._docAction(nwDocAction.BLOCK_TXT))
        self.fmtMenu.addAction(self.aFmtNoFormat)

        # Format > Separator
        self.fmtMenu.addSeparator()

        # Format > Replace Single Quotes
        self.aFmtReplSng = QAction(self.tr("Convert Single Quotes"), self)
        self.aFmtReplSng.triggered.connect(lambda: self._docAction(nwDocAction.REPL_SNG))
        self.fmtMenu.addAction(self.aFmtReplSng)

        # Format > Replace Double Quotes
        self.aFmtReplDbl = QAction(self.tr("Convert Double Quotes"), self)
        self.aFmtReplDbl.triggered.connect(lambda: self._docAction(nwDocAction.REPL_DBL))
        self.fmtMenu.addAction(self.aFmtReplDbl)

        # Format > Remove In-Paragraph Breaks
        self.aFmtRmBreaks = QAction(self.tr("Remove In-Paragraph Breaks"), self)
        self.aFmtRmBreaks.triggered.connect(lambda: self._docAction(nwDocAction.RM_BREAKS))
        self.fmtMenu.addAction(self.aFmtRmBreaks)

        return

    def _buildSearchMenu(self) -> None:
        """Assemble the Search menu."""
        # Search
        self.srcMenu = self.addMenu(self.tr("&Search"))

        # Search > Find
        self.aFind = QAction(self.tr("Find"), self)
        self.aFind.setShortcut("Ctrl+F")
        self.aFind.triggered.connect(lambda: self.mainGui.docEditor.beginSearch())
        self.srcMenu.addAction(self.aFind)

        # Search > Replace
        self.aReplace = QAction(self.tr("Replace"), self)
        self.aReplace.setShortcut("Ctrl+=" if CONFIG.osDarwin else "Ctrl+H")
        self.aReplace.triggered.connect(lambda: self.mainGui.docEditor.beginReplace())
        self.srcMenu.addAction(self.aReplace)

        # Search > Find Next
        self.aFindNext = QAction(self.tr("Find Next"), self)
        self.aFindNext.setShortcuts(["Ctrl+G", "F3"] if CONFIG.osDarwin else ["F3", "Ctrl+G"])
        self.aFindNext.triggered.connect(lambda: self.mainGui.docEditor.findNext())
        self.srcMenu.addAction(self.aFindNext)

        # Search > Find Prev
        self.aFindPrev = QAction(self.tr("Find Previous"), self)
        self.aFindPrev.setShortcuts(
            ["Ctrl+Shift+G", "Shift+F3"] if CONFIG.osDarwin else ["Shift+F3", "Ctrl+Shift+G"]
        )
        self.aFindPrev.triggered.connect(lambda: self.mainGui.docEditor.findNext(goBack=True))
        self.srcMenu.addAction(self.aFindPrev)

        # Search > Replace Next
        self.aReplaceNext = QAction(self.tr("Replace Next"), self)
        self.aReplaceNext.setShortcut("Ctrl+Shift+1")
        self.aReplaceNext.triggered.connect(lambda: self.mainGui.docEditor.replaceNext())
        self.srcMenu.addAction(self.aReplaceNext)

        return

    def _buildToolsMenu(self) -> None:
        """Assemble the Tools menu."""
        # Tools
        self.toolsMenu = self.addMenu(self.tr("&Tools"))

        # Tools > Check Spelling
        self.aSpellCheck = QAction(self.tr("Check Spelling"), self)
        self.aSpellCheck.setCheckable(True)
        self.aSpellCheck.setChecked(SHARED.project.data.spellCheck)
        self.aSpellCheck.triggered.connect(self._toggleSpellCheck)  # triggered, not toggled!
        self.aSpellCheck.setShortcut("Ctrl+F7")
        self.toolsMenu.addAction(self.aSpellCheck)

        self.mSelectLanguage = self.toolsMenu.addMenu(self.tr("Spell Check Language"))
        languages = SHARED.spelling.listDictionaries()
        languages.insert(0, ("None", self.tr("Default")))
        for n, (tag, language) in enumerate(languages):
            aSpell = QAction(self.mSelectLanguage)
            aSpell.setText(language)
            aSpell.triggered.connect(lambda n, tag=tag: self._changeSpelling(tag))
            self.mSelectLanguage.addAction(aSpell)

        # Tools > Re-Run Spell Check
        self.aReRunSpell = QAction(self.tr("Re-Run Spell Check"), self)
        self.aReRunSpell.setShortcut("F7")
        self.aReRunSpell.triggered.connect(lambda: self.mainGui.docEditor.spellCheckDocument())
        self.toolsMenu.addAction(self.aReRunSpell)

        # Tools > Project Word List
        self.aEditWordList = QAction(self.tr("Project Word List"), self)
        self.aEditWordList.triggered.connect(lambda: self.mainGui.showProjectWordListDialog())
        self.toolsMenu.addAction(self.aEditWordList)

        # Tools > Separator
        self.toolsMenu.addSeparator()

        # Tools > Rebuild Index
        self.aRebuildIndex = QAction(self.tr("Rebuild Index"), self)
        self.aRebuildIndex.setShortcut("F9")
        self.aRebuildIndex.triggered.connect(lambda: self.mainGui.rebuildIndex())
        self.toolsMenu.addAction(self.aRebuildIndex)

        # Tools > Separator
        self.toolsMenu.addSeparator()

        # Tools > Backup Project
        self.aBackupProject = QAction(self.tr("Backup Project"), self)
        self.aBackupProject.triggered.connect(lambda: SHARED.project.backupProject(True))
        self.toolsMenu.addAction(self.aBackupProject)

        # Tools > Build Manuscript
        self.aBuildManuscript = QAction(self.tr("Build Manuscript"), self)
        self.aBuildManuscript.setShortcut("F5")
        self.aBuildManuscript.triggered.connect(self.mainGui.showBuildManuscriptDialog)
        self.toolsMenu.addAction(self.aBuildManuscript)

        # Tools > Writing Statistics
        self.aWritingStats = QAction(self.tr("Writing Statistics"), self)
        self.aWritingStats.setShortcut("F6")
        self.aWritingStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog())
        self.toolsMenu.addAction(self.aWritingStats)

        # Tools > Preferences
        self.aPreferences = QAction(self.tr("Preferences"), self)
        self.aPreferences.setShortcut("Ctrl+,")
        self.aPreferences.setMenuRole(QAction.PreferencesRole)
        self.aPreferences.triggered.connect(lambda: self.mainGui.showPreferencesDialog())
        self.toolsMenu.addAction(self.aPreferences)

        return

    def _buildHelpMenu(self) -> None:
        """Assemble the Help menu."""
        # Help
        self.helpMenu = self.addMenu(self.tr("&Help"))

        # Help > About
        self.aAboutNW = QAction(self.tr("About novelWriter"), self)
        self.aAboutNW.setMenuRole(QAction.AboutRole)
        self.aAboutNW.triggered.connect(lambda: self.mainGui.showAboutNWDialog())
        self.helpMenu.addAction(self.aAboutNW)

        # Help > About Qt5
        self.aAboutQt = QAction(self.tr("About Qt5"), self)
        self.aAboutQt.setMenuRole(QAction.AboutQtRole)
        self.aAboutQt.triggered.connect(lambda: self.mainGui.showAboutQtDialog())
        self.helpMenu.addAction(self.aAboutQt)

        # Help > Separator
        self.helpMenu.addSeparator()

        # Help > User Manual (Online)
        self.aHelpDocs = QAction(self.tr("User Manual (Online)"), self)
        self.aHelpDocs.setShortcut("F1")
        self.aHelpDocs.triggered.connect(lambda: self._openWebsite(nwConst.URL_DOCS))
        self.helpMenu.addAction(self.aHelpDocs)

        # Help > User Manual (PDF)
        if isinstance(CONFIG.pdfDocs, Path):
            self.aPdfDocs = QAction(self.tr("User Manual (PDF)"), self)
            self.aPdfDocs.setShortcut("Shift+F1")
            self.aPdfDocs.triggered.connect(self._openUserManualFile)
            self.helpMenu.addAction(self.aPdfDocs)

        # Help > Separator
        self.helpMenu.addSeparator()

        # Document > Report an Issue
        self.aIssue = QAction(self.tr("Report an Issue (GitHub)"), self)
        self.aIssue.triggered.connect(lambda: self._openWebsite(nwConst.URL_REPORT))
        self.helpMenu.addAction(self.aIssue)

        # Document > Ask a Question
        self.aQuestion = QAction(self.tr("Ask a Question (GitHub)"), self)
        self.aQuestion.triggered.connect(lambda: self._openWebsite(nwConst.URL_HELP))
        self.helpMenu.addAction(self.aQuestion)

        # Document > Main Website
        self.aWebsite = QAction(self.tr("The novelWriter Website"), self)
        self.aWebsite.triggered.connect(lambda: self._openWebsite(nwConst.URL_WEB))
        self.helpMenu.addAction(self.aWebsite)

        # Help > Separator
        self.helpMenu.addSeparator()

        # Document > Check for Updates
        self.aUpdates = QAction(self.tr("Check for New Release"), self)
        self.aUpdates.triggered.connect(lambda: self.mainGui.showUpdatesDialog())
        self.helpMenu.addAction(self.aUpdates)

        return

# END Class GuiMainMenu
