Beispiel #1
0
class GuiPreferencesAutomation(QWidget):

    def __init__(self, mainGui):
        QWidget.__init__(self, mainGui)

        self.mainConf  = novelwriter.CONFIG
        self.mainGui   = mainGui
        self.mainTheme = mainGui.mainTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
        self.setLayout(self.mainForm)

        # Automatic Features
        # ==================
        self.mainForm.addGroupLabel(self.tr("Automatic Features"))

        # Auto-Select Word Under Cursor
        self.autoSelect = QSwitch()
        self.autoSelect.setChecked(self.mainConf.autoSelect)
        self.mainForm.addRow(
            self.tr("Auto-select word under cursor"),
            self.autoSelect,
            self.tr("Apply formatting to word under cursor if no selection is made.")
        )

        # Auto-Replace as You Type Main Switch
        self.doReplace = QSwitch()
        self.doReplace.setChecked(self.mainConf.doReplace)
        self.doReplace.toggled.connect(self._toggleAutoReplaceMain)
        self.mainForm.addRow(
            self.tr("Auto-replace text as you type"),
            self.doReplace,
            self.tr("Allow the editor to replace symbols as you type.")
        )

        # Replace as You Type
        # ===================
        self.mainForm.addGroupLabel(self.tr("Replace as You Type"))

        # Auto-Replace Single Quotes
        self.doReplaceSQuote = QSwitch()
        self.doReplaceSQuote.setChecked(self.mainConf.doReplaceSQuote)
        self.doReplaceSQuote.setEnabled(self.mainConf.doReplace)
        self.mainForm.addRow(
            self.tr("Auto-replace single quotes"),
            self.doReplaceSQuote,
            self.tr("Try to guess which is an opening or a closing quote.")
        )

        # Auto-Replace Double Quotes
        self.doReplaceDQuote = QSwitch()
        self.doReplaceDQuote.setChecked(self.mainConf.doReplaceDQuote)
        self.doReplaceDQuote.setEnabled(self.mainConf.doReplace)
        self.mainForm.addRow(
            self.tr("Auto-replace double quotes"),
            self.doReplaceDQuote,
            self.tr("Try to guess which is an opening or a closing quote.")
        )

        # Auto-Replace Hyphens
        self.doReplaceDash = QSwitch()
        self.doReplaceDash.setChecked(self.mainConf.doReplaceDash)
        self.doReplaceDash.setEnabled(self.mainConf.doReplace)
        self.mainForm.addRow(
            self.tr("Auto-replace dashes"),
            self.doReplaceDash,
            self.tr("Double and triple hyphens become short and long dashes.")
        )

        # Auto-Replace Dots
        self.doReplaceDots = QSwitch()
        self.doReplaceDots.setChecked(self.mainConf.doReplaceDots)
        self.doReplaceDots.setEnabled(self.mainConf.doReplace)
        self.mainForm.addRow(
            self.tr("Auto-replace dots"),
            self.doReplaceDots,
            self.tr("Three consecutive dots become ellipsis.")
        )

        # Automatic Padding
        # =================
        self.mainForm.addGroupLabel(self.tr("Automatic Padding"))

        # Pad Before
        self.fmtPadBefore = QLineEdit()
        self.fmtPadBefore.setMaxLength(32)
        self.fmtPadBefore.setText(self.mainConf.fmtPadBefore)
        self.mainForm.addRow(
            self.tr("Insert non-breaking space before"),
            self.fmtPadBefore,
            self.tr("Automatically add space before any of these symbols."),
        )

        # Pad After
        self.fmtPadAfter = QLineEdit()
        self.fmtPadAfter.setMaxLength(32)
        self.fmtPadAfter.setText(self.mainConf.fmtPadAfter)
        self.mainForm.addRow(
            self.tr("Insert non-breaking space after"),
            self.fmtPadAfter,
            self.tr("Automatically add space after any of these symbols."),
        )

        # Use Thin Space
        self.fmtPadThin = QSwitch()
        self.fmtPadThin.setChecked(self.mainConf.fmtPadThin)
        self.fmtPadThin.setEnabled(self.mainConf.doReplace)
        self.mainForm.addRow(
            self.tr("Use thin space instead"),
            self.fmtPadThin,
            self.tr("Inserts a thin space instead of a regular space.")
        )

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Automatic Features
        self.mainConf.autoSelect = self.autoSelect.isChecked()
        self.mainConf.doReplace  = self.doReplace.isChecked()

        # Replace as You Type
        self.mainConf.doReplaceSQuote = self.doReplaceSQuote.isChecked()
        self.mainConf.doReplaceDQuote = self.doReplaceDQuote.isChecked()
        self.mainConf.doReplaceDash   = self.doReplaceDash.isChecked()
        self.mainConf.doReplaceDots   = self.doReplaceDots.isChecked()

        # Automatic Padding
        self.mainConf.fmtPadBefore = self.fmtPadBefore.text().strip()
        self.mainConf.fmtPadAfter  = self.fmtPadAfter.text().strip()
        self.mainConf.fmtPadThin   = self.fmtPadThin.isChecked()

        self.mainConf.confChanged = True

        return

    ##
    #  Slots
    ##

    def _toggleAutoReplaceMain(self, theState):
        """Enables or disables switches controlled by the main auto
        replace switch.
        """
        self.doReplaceSQuote.setEnabled(theState)
        self.doReplaceDQuote.setEnabled(theState)
        self.doReplaceDash.setEnabled(theState)
        self.doReplaceDots.setEnabled(theState)
        self.fmtPadThin.setEnabled(theState)
        return
Beispiel #2
0
class GuiPreferencesEditor(QWidget):

    def __init__(self, mainGui):
        QWidget.__init__(self, mainGui)

        self.mainConf  = novelwriter.CONFIG
        self.mainGui   = mainGui
        self.mainTheme = mainGui.mainTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
        self.setLayout(self.mainForm)

        mW = self.mainConf.pxInt(250)

        # Spell Checking
        # ==============
        self.mainForm.addGroupLabel(self.tr("Spell Checking"))

        # Spell Check Provider and Language
        self.spellLanguage = QComboBox(self)
        self.spellLanguage.setMaximumWidth(mW)

        langAvail = self.mainGui.docEditor.spEnchant.listDictionaries()
        if self.mainConf.hasEnchant:
            if langAvail:
                for spTag, spProv in langAvail:
                    qLocal = QLocale(spTag)
                    spLang = qLocal.nativeLanguageName().title()
                    self.spellLanguage.addItem("%s [%s]" % (spLang, spProv), spTag)
            else:
                self.spellLanguage.addItem(self.tr("None"), "")
                self.spellLanguage.setEnabled(False)
        else:
            self.spellLanguage.addItem(self.tr("Not installed"), "")
            self.spellLanguage.setEnabled(False)

        spellIdx = self.spellLanguage.findData(self.mainConf.spellLanguage)
        if spellIdx != -1:
            self.spellLanguage.setCurrentIndex(spellIdx)

        self.mainForm.addRow(
            self.tr("Spell check language"),
            self.spellLanguage,
            self.tr("Available languages are determined by your system.")
        )

        # Big Document Size Limit
        self.bigDocLimit = QSpinBox(self)
        self.bigDocLimit.setMinimum(10)
        self.bigDocLimit.setMaximum(10000)
        self.bigDocLimit.setSingleStep(10)
        self.bigDocLimit.setValue(self.mainConf.bigDocLimit)
        self.mainForm.addRow(
            self.tr("Big document limit"),
            self.bigDocLimit,
            self.tr("Full spell checking is disabled above this limit."),
            theUnit=self.tr("kB")
        )

        # Word Count
        # ==========
        self.mainForm.addGroupLabel(self.tr("Word Count"))

        # Word Count Timer
        self.wordCountTimer = QDoubleSpinBox(self)
        self.wordCountTimer.setDecimals(1)
        self.wordCountTimer.setMinimum(2.0)
        self.wordCountTimer.setMaximum(600.0)
        self.wordCountTimer.setSingleStep(0.1)
        self.wordCountTimer.setValue(self.mainConf.wordCountTimer)
        self.mainForm.addRow(
            self.tr("Word count interval"),
            self.wordCountTimer,
            theUnit=self.tr("seconds")
        )

        # Include Notes in Word Count
        self.incNotesWCount = QSwitch()
        self.incNotesWCount.setChecked(self.mainConf.incNotesWCount)
        self.mainForm.addRow(
            self.tr("Include project notes in status bar word count"),
            self.incNotesWCount
        )

        # Writing Guides
        # ==============
        self.mainForm.addGroupLabel(self.tr("Writing Guides"))

        # Show Tabs and Spaces
        self.showTabsNSpaces = QSwitch()
        self.showTabsNSpaces.setChecked(self.mainConf.showTabsNSpaces)
        self.mainForm.addRow(
            self.tr("Show tabs and spaces"),
            self.showTabsNSpaces
        )

        # Show Line Endings
        self.showLineEndings = QSwitch()
        self.showLineEndings.setChecked(self.mainConf.showLineEndings)
        self.mainForm.addRow(
            self.tr("Show line endings"),
            self.showLineEndings
        )

        # Scroll Behaviour
        # ================
        self.mainForm.addGroupLabel(self.tr("Scroll Behaviour"))

        # Scroll Past End
        self.scrollPastEnd = QSpinBox(self)
        self.scrollPastEnd.setMinimum(0)
        self.scrollPastEnd.setMaximum(100)
        self.scrollPastEnd.setSingleStep(1)
        self.scrollPastEnd.setValue(int(self.mainConf.scrollPastEnd))
        self.mainForm.addRow(
            self.tr("Scroll past end of the document"),
            self.scrollPastEnd,
            self.tr("Set to 0 to disable this feature."),
            theUnit=self.tr("lines")
        )

        # Typewriter Scrolling
        self.autoScroll = QSwitch()
        self.autoScroll.setChecked(self.mainConf.autoScroll)
        self.mainForm.addRow(
            self.tr("Typewriter style scrolling when you type"),
            self.autoScroll,
            self.tr("Keeps the cursor at a fixed vertical position.")
        )

        # Typewriter Position
        self.autoScrollPos = QSpinBox(self)
        self.autoScrollPos.setMinimum(10)
        self.autoScrollPos.setMaximum(90)
        self.autoScrollPos.setSingleStep(1)
        self.autoScrollPos.setValue(int(self.mainConf.autoScrollPos))
        self.mainForm.addRow(
            self.tr("Minimum position for Typewriter scrolling"),
            self.autoScrollPos,
            self.tr("Percentage of the editor height from the top."),
            theUnit="%"
        )

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Spell Checking
        self.mainConf.spellLanguage = self.spellLanguage.currentData()
        self.mainConf.bigDocLimit   = self.bigDocLimit.value()

        # Word Count
        self.mainConf.wordCountTimer = self.wordCountTimer.value()
        self.mainConf.incNotesWCount = self.incNotesWCount.isChecked()

        # Writing Guides
        self.mainConf.showTabsNSpaces = self.showTabsNSpaces.isChecked()
        self.mainConf.showLineEndings = self.showLineEndings.isChecked()

        # Scroll Behaviour
        self.mainConf.scrollPastEnd = self.scrollPastEnd.value()
        self.mainConf.autoScroll    = self.autoScroll.isChecked()
        self.mainConf.autoScrollPos = self.autoScrollPos.value()

        self.mainConf.confChanged = True

        return
Beispiel #3
0
class GuiPreferencesSyntax(QWidget):

    def __init__(self, mainGui):
        QWidget.__init__(self, mainGui)

        self.mainConf  = novelwriter.CONFIG
        self.mainGui   = mainGui
        self.mainTheme = mainGui.mainTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
        self.setLayout(self.mainForm)

        # Quotes & Dialogue
        # =================
        self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))

        self.highlightQuotes = QSwitch()
        self.highlightQuotes.setChecked(self.mainConf.highlightQuotes)
        self.highlightQuotes.toggled.connect(self._toggleHighlightQuotes)
        self.mainForm.addRow(
            self.tr("Highlight text wrapped in quotes"),
            self.highlightQuotes,
            self.tr("Applies to the document editor only.")
        )

        self.allowOpenSQuote = QSwitch()
        self.allowOpenSQuote.setChecked(self.mainConf.allowOpenSQuote)
        self.mainForm.addRow(
            self.tr("Allow open-ended single quotes"),
            self.allowOpenSQuote,
            self.tr("Highlight single-quoted line with no closing quote.")
        )

        self.allowOpenDQuote = QSwitch()
        self.allowOpenDQuote.setChecked(self.mainConf.allowOpenDQuote)
        self.mainForm.addRow(
            self.tr("Allow open-ended double quotes"),
            self.allowOpenDQuote,
            self.tr("Highlight double-quoted line with no closing quote.")
        )

        # Text Emphasis
        # =============
        self.mainForm.addGroupLabel(self.tr("Text Emphasis"))

        self.highlightEmph = QSwitch()
        self.highlightEmph.setChecked(self.mainConf.highlightEmph)
        self.mainForm.addRow(
            self.tr("Add highlight colour to emphasised text"),
            self.highlightEmph,
            self.tr("Applies to the document editor only.")
        )

        # Text Errors
        # ===========

        self.mainForm.addGroupLabel(self.tr("Text Errors"))

        self.showMultiSpaces = QSwitch()
        self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
        self.mainForm.addRow(
            self.tr("Highlight multiple or trailing spaces"),
            self.showMultiSpaces,
            self.tr("Applies to the document editor only.")
        )

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Quotes & Dialogue
        self.mainConf.highlightQuotes = self.highlightQuotes.isChecked()
        self.mainConf.allowOpenSQuote = self.allowOpenSQuote.isChecked()
        self.mainConf.allowOpenDQuote = self.allowOpenDQuote.isChecked()

        # Text Emphasis
        self.mainConf.highlightEmph = self.highlightEmph.isChecked()

        # Text Errors
        self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked()

        self.mainConf.confChanged = True

        return

    ##
    #  Slots
    ##

    def _toggleHighlightQuotes(self, theState):
        """Enables or disables switches controlled by the highlight
        quotes switch.
        """
        self.allowOpenSQuote.setEnabled(theState)
        self.allowOpenDQuote.setEnabled(theState)
        return
Beispiel #4
0
class GuiPreferencesProjects(QWidget):

    def __init__(self, mainGui):
        QWidget.__init__(self, mainGui)

        self.mainConf  = novelwriter.CONFIG
        self.mainGui   = mainGui
        self.mainTheme = mainGui.mainTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
        self.setLayout(self.mainForm)

        # Automatic Save
        # ==============
        self.mainForm.addGroupLabel(self.tr("Automatic Save"))

        # Document Save Timer
        self.autoSaveDoc = QSpinBox(self)
        self.autoSaveDoc.setMinimum(5)
        self.autoSaveDoc.setMaximum(600)
        self.autoSaveDoc.setSingleStep(1)
        self.autoSaveDoc.setValue(self.mainConf.autoSaveDoc)
        self.mainForm.addRow(
            self.tr("Save document interval"),
            self.autoSaveDoc,
            self.tr("How often the document is automatically saved."),
            theUnit=self.tr("seconds")
        )

        # Project Save Timer
        self.autoSaveProj = QSpinBox(self)
        self.autoSaveProj.setMinimum(5)
        self.autoSaveProj.setMaximum(600)
        self.autoSaveProj.setSingleStep(1)
        self.autoSaveProj.setValue(self.mainConf.autoSaveProj)
        self.mainForm.addRow(
            self.tr("Save project interval"),
            self.autoSaveProj,
            self.tr("How often the project is automatically saved."),
            theUnit=self.tr("seconds")
        )

        # Project Backup
        # ==============
        self.mainForm.addGroupLabel(self.tr("Project Backup"))

        # Backup Path
        self.backupPath = self.mainConf.backupPath
        self.backupGetPath = QPushButton(self.tr("Browse"))
        self.backupGetPath.clicked.connect(self._backupFolder)
        self.backupPathRow = self.mainForm.addRow(
            self.tr("Backup storage location"),
            self.backupGetPath,
            self.tr("Path: {0}").format(self.backupPath)
        )

        # Run when closing
        self.backupOnClose = QSwitch()
        self.backupOnClose.setChecked(self.mainConf.backupOnClose)
        self.backupOnClose.toggled.connect(self._toggledBackupOnClose)
        self.mainForm.addRow(
            self.tr("Run backup when the project is closed"),
            self.backupOnClose,
            self.tr("Can be overridden for individual projects in Project Settings.")
        )

        # Ask before backup
        # Only enabled when "Run when closing" is checked
        self.askBeforeBackup = QSwitch()
        self.askBeforeBackup.setChecked(self.mainConf.askBeforeBackup)
        self.askBeforeBackup.setEnabled(self.mainConf.backupOnClose)
        self.mainForm.addRow(
            self.tr("Ask before running backup"),
            self.askBeforeBackup,
            self.tr("If off, backups will run in the background.")
        )

        # Session Timer
        # =============
        self.mainForm.addGroupLabel(self.tr("Session Timer"))

        # Pause when idle
        self.stopWhenIdle = QSwitch()
        self.stopWhenIdle.setChecked(self.mainConf.stopWhenIdle)
        self.mainForm.addRow(
            self.tr("Pause the session timer when not writing"),
            self.stopWhenIdle,
            self.tr("Also pauses when the application window does not have focus.")
        )

        # Inactive time for idle
        self.userIdleTime = QDoubleSpinBox()
        self.userIdleTime.setMinimum(0.5)
        self.userIdleTime.setMaximum(600.0)
        self.userIdleTime.setSingleStep(0.5)
        self.userIdleTime.setDecimals(1)
        self.userIdleTime.setValue(self.mainConf.userIdleTime/60.0)
        self.mainForm.addRow(
            self.tr("Editor inactive time before pausing timer"),
            self.userIdleTime,
            self.tr("User activity includes typing and changing the content."),
            theUnit=self.tr("minutes")
        )

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Automatic Save
        self.mainConf.autoSaveDoc  = self.autoSaveDoc.value()
        self.mainConf.autoSaveProj = self.autoSaveProj.value()

        # Project Backup
        self.mainConf.backupPath      = self.backupPath
        self.mainConf.backupOnClose   = self.backupOnClose.isChecked()
        self.mainConf.askBeforeBackup = self.askBeforeBackup.isChecked()

        # Session Timer
        self.mainConf.stopWhenIdle = self.stopWhenIdle.isChecked()
        self.mainConf.userIdleTime = round(self.userIdleTime.value() * 60)

        self.mainConf.confChanged = True

        return

    ##
    #  Slots
    ##

    def _backupFolder(self):
        """Open a dialog to select the backup folder.
        """
        currDir = self.backupPath
        if not os.path.isdir(currDir):
            currDir = ""

        newDir = QFileDialog.getExistingDirectory(
            self, self.tr("Backup Directory"), currDir, options=QFileDialog.ShowDirsOnly
        )
        if newDir:
            self.backupPath = newDir
            self.mainForm.setHelpText(
                self.backupPathRow, self.tr("Path: {0}").format(self.backupPath)
            )
            return True

        return False

    def _toggledBackupOnClose(self, theState):
        """Enable or disable switch that depends on the backup on close
        switch.
        """
        self.askBeforeBackup.setEnabled(theState)
        return
Beispiel #5
0
class GuiPreferencesDocuments(QWidget):

    def __init__(self, mainGui):
        QWidget.__init__(self, mainGui)

        self.mainConf  = novelwriter.CONFIG
        self.mainGui   = mainGui
        self.mainTheme = mainGui.mainTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
        self.setLayout(self.mainForm)

        # Text Style
        # ==========
        self.mainForm.addGroupLabel(self.tr("Text Style"))

        # Font Family
        self.textFont = QLineEdit()
        self.textFont.setReadOnly(True)
        self.textFont.setFixedWidth(self.mainConf.pxInt(162))
        self.textFont.setText(self.mainConf.textFont)
        self.fontButton = QPushButton("...")
        self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
        self.fontButton.clicked.connect(self._selectFont)
        self.mainForm.addRow(
            self.tr("Font family"),
            self.textFont,
            self.tr("Applies to both document editor and viewer."),
            theButton=self.fontButton
        )

        # Font Size
        self.textSize = QSpinBox(self)
        self.textSize.setMinimum(8)
        self.textSize.setMaximum(60)
        self.textSize.setSingleStep(1)
        self.textSize.setValue(self.mainConf.textSize)
        self.mainForm.addRow(
            self.tr("Font size"),
            self.textSize,
            self.tr("Applies to both document editor and viewer."),
            theUnit=self.tr("pt")
        )

        # Text Flow
        # =========
        self.mainForm.addGroupLabel(self.tr("Text Flow"))

        # Max Text Width in Normal Mode
        self.textWidth = QSpinBox(self)
        self.textWidth.setMinimum(0)
        self.textWidth.setMaximum(10000)
        self.textWidth.setSingleStep(10)
        self.textWidth.setValue(self.mainConf.textWidth)
        self.mainForm.addRow(
            self.tr("Maximum text width in \"Normal Mode\""),
            self.textWidth,
            self.tr("Set to 0 to disable this feature."),
            theUnit=self.tr("px")
        )

        # Max Text Width in Focus Mode
        self.focusWidth = QSpinBox(self)
        self.focusWidth.setMinimum(200)
        self.focusWidth.setMaximum(10000)
        self.focusWidth.setSingleStep(10)
        self.focusWidth.setValue(self.mainConf.focusWidth)
        self.mainForm.addRow(
            self.tr("Maximum text width in \"Focus Mode\""),
            self.focusWidth,
            self.tr("The maximum width cannot be disabled."),
            theUnit=self.tr("px")
        )

        # Focus Mode Footer
        self.hideFocusFooter = QSwitch()
        self.hideFocusFooter.setChecked(self.mainConf.hideFocusFooter)
        self.mainForm.addRow(
            self.tr("Hide document footer in \"Focus Mode\""),
            self.hideFocusFooter,
            self.tr("Hide the information bar in the document editor.")
        )

        # Justify Text
        self.doJustify = QSwitch()
        self.doJustify.setChecked(self.mainConf.doJustify)
        self.mainForm.addRow(
            self.tr("Justify the text margins"),
            self.doJustify,
            self.tr("Applies to both document editor and viewer."),
        )

        # Document Margins
        self.textMargin = QSpinBox(self)
        self.textMargin.setMinimum(0)
        self.textMargin.setMaximum(900)
        self.textMargin.setSingleStep(1)
        self.textMargin.setValue(self.mainConf.textMargin)
        self.mainForm.addRow(
            self.tr("Minimum text margin"),
            self.textMargin,
            self.tr("Applies to both document editor and viewer."),
            theUnit=self.tr("px")
        )

        # Tab Width
        self.tabWidth = QSpinBox(self)
        self.tabWidth.setMinimum(0)
        self.tabWidth.setMaximum(200)
        self.tabWidth.setSingleStep(1)
        self.tabWidth.setValue(self.mainConf.tabWidth)
        self.mainForm.addRow(
            self.tr("Tab width"),
            self.tabWidth,
            self.tr("The width of a tab key press in the editor and viewer."),
            theUnit=self.tr("px")
        )

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Text Style
        self.mainConf.textFont = self.textFont.text()
        self.mainConf.textSize = self.textSize.value()

        # Text Flow
        self.mainConf.textWidth       = self.textWidth.value()
        self.mainConf.focusWidth      = self.focusWidth.value()
        self.mainConf.hideFocusFooter = self.hideFocusFooter.isChecked()
        self.mainConf.doJustify       = self.doJustify.isChecked()
        self.mainConf.textMargin      = self.textMargin.value()
        self.mainConf.tabWidth        = self.tabWidth.value()

        self.mainConf.confChanged = True

        return

    ##
    #  Slots
    ##

    def _selectFont(self):
        """Open the QFontDialog and set a font for the font style.
        """
        currFont = QFont()
        currFont.setFamily(self.mainConf.textFont)
        currFont.setPointSize(self.mainConf.textSize)
        theFont, theStatus = QFontDialog.getFont(currFont, self)
        if theStatus:
            self.textFont.setText(theFont.family())
            self.textSize.setValue(theFont.pointSize())

        return
Beispiel #6
0
class GuiPreferencesSyntax(QWidget):
    def __init__(self, theParent):
        QWidget.__init__(self, theParent)

        self.mainConf = novelwriter.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.theTheme.helpText)
        self.setLayout(self.mainForm)

        # Highlighting Theme
        # ==================
        self.mainForm.addGroupLabel(self.tr("Highlighting Theme"))

        self.guiSyntax = QComboBox()
        self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
        self.theSyntaxes = self.theTheme.listSyntax()
        for syntaxFile, syntaxName in self.theSyntaxes:
            self.guiSyntax.addItem(syntaxName, syntaxFile)
        syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
        if syntaxIdx != -1:
            self.guiSyntax.setCurrentIndex(syntaxIdx)

        self.mainForm.addRow(
            self.tr("Highlighting theme"), self.guiSyntax,
            self.tr("Colour theme for the editor and viewer."))

        # Quotes & Dialogue
        # =================
        self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))

        self.highlightQuotes = QSwitch()
        self.highlightQuotes.setChecked(self.mainConf.highlightQuotes)
        self.highlightQuotes.toggled.connect(self._toggleHighlightQuotes)
        self.mainForm.addRow(self.tr("Highlight text wrapped in quotes"),
                             self.highlightQuotes,
                             self.tr("Applies to the document editor only."))

        self.allowOpenSQuote = QSwitch()
        self.allowOpenSQuote.setChecked(self.mainConf.allowOpenSQuote)
        self.mainForm.addRow(
            self.tr("Allow open-ended single quotes"), self.allowOpenSQuote,
            self.tr("Highlight single-quoted line with no closing quote."))

        self.allowOpenDQuote = QSwitch()
        self.allowOpenDQuote.setChecked(self.mainConf.allowOpenDQuote)
        self.mainForm.addRow(
            self.tr("Allow open-ended double quotes"), self.allowOpenDQuote,
            self.tr("Highlight double-quoted line with no closing quote."))

        # Text Emphasis
        # =============
        self.mainForm.addGroupLabel(self.tr("Text Emphasis"))

        self.highlightEmph = QSwitch()
        self.highlightEmph.setChecked(self.mainConf.highlightEmph)
        self.mainForm.addRow(
            self.tr("Add highlight colour to emphasised text"),
            self.highlightEmph,
            self.tr("Applies to the document editor only."))

        # Text Errors
        # ===========

        self.mainForm.addGroupLabel(self.tr("Text Errors"))

        self.showMultiSpaces = QSwitch()
        self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
        self.mainForm.addRow(self.tr("Highlight multiple spaces"),
                             self.showMultiSpaces,
                             self.tr("Applies to the document editor only."))

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        # Highlighting Theme
        self.mainConf.guiSyntax = self.guiSyntax.currentData()

        # Quotes & Dialogue
        self.mainConf.highlightQuotes = self.highlightQuotes.isChecked()
        self.mainConf.allowOpenSQuote = self.allowOpenSQuote.isChecked()
        self.mainConf.allowOpenDQuote = self.allowOpenDQuote.isChecked()

        # Text Emphasis
        self.mainConf.highlightEmph = self.highlightEmph.isChecked()

        # Text Errors
        self.mainConf.showMultiSpaces = self.showMultiSpaces.isChecked()

        self.mainConf.confChanged = True

        return

    ##
    #  Slots
    ##

    def _toggleHighlightQuotes(self, theState):
        """Enables or disables switches controlled by the highlight
        quotes switch.
        """
        self.allowOpenSQuote.setEnabled(theState)
        self.allowOpenDQuote.setEnabled(theState)
        return
Beispiel #7
0
class GuiPreferencesGeneral(QWidget):

    def __init__(self, mainGui):
        QWidget.__init__(self, mainGui)

        self.mainConf  = novelwriter.CONFIG
        self.mainGui   = mainGui
        self.mainTheme = mainGui.mainTheme

        # The Form
        self.mainForm = QConfigLayout()
        self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
        self.setLayout(self.mainForm)

        # Look and Feel
        # =============
        self.mainForm.addGroupLabel(self.tr("Look and Feel"))
        minWidth = self.mainConf.pxInt(200)

        # Select Locale
        self.guiLang = QComboBox()
        self.guiLang.setMinimumWidth(minWidth)
        theLangs = self.mainConf.listLanguages(self.mainConf.LANG_NW)
        for lang, langName in theLangs:
            self.guiLang.addItem(langName, lang)
        langIdx = self.guiLang.findData(self.mainConf.guiLang)
        if langIdx != -1:
            self.guiLang.setCurrentIndex(langIdx)

        self.mainForm.addRow(
            self.tr("Main GUI language"),
            self.guiLang,
            self.tr("Requires restart.")
        )

        # Select Theme
        self.guiTheme = QComboBox()
        self.guiTheme.setMinimumWidth(minWidth)
        self.theThemes = self.mainTheme.listThemes()
        for themeDir, themeName in self.theThemes:
            self.guiTheme.addItem(themeName, themeDir)
        themeIdx = self.guiTheme.findData(self.mainConf.guiTheme)
        if themeIdx != -1:
            self.guiTheme.setCurrentIndex(themeIdx)

        self.mainForm.addRow(
            self.tr("Main GUI theme"),
            self.guiTheme,
            self.tr("Requires restart.")
        )

        # Select Icon Theme
        self.guiIcons = QComboBox()
        self.guiIcons.setMinimumWidth(minWidth)
        self.iconCache = self.mainTheme.iconCache.listThemes()
        for iconDir, iconName in self.iconCache:
            self.guiIcons.addItem(iconName, iconDir)
        iconIdx = self.guiIcons.findData(self.mainConf.guiIcons)
        if iconIdx != -1:
            self.guiIcons.setCurrentIndex(iconIdx)

        self.mainForm.addRow(
            self.tr("Main icon theme"),
            self.guiIcons,
            self.tr("Requires restart.")
        )

        # Editor Theme
        self.guiSyntax = QComboBox()
        self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
        self.theSyntaxes = self.mainTheme.listSyntax()
        for syntaxFile, syntaxName in self.theSyntaxes:
            self.guiSyntax.addItem(syntaxName, syntaxFile)
        syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
        if syntaxIdx != -1:
            self.guiSyntax.setCurrentIndex(syntaxIdx)

        self.mainForm.addRow(
            self.tr("Editor theme"),
            self.guiSyntax,
            self.tr("Colour theme for the editor and viewer.")
        )

        # Font Family
        self.guiFont = QLineEdit()
        self.guiFont.setReadOnly(True)
        self.guiFont.setFixedWidth(self.mainConf.pxInt(162))
        self.guiFont.setText(self.mainConf.guiFont)
        self.fontButton = QPushButton("...")
        self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
        self.fontButton.clicked.connect(self._selectFont)
        self.mainForm.addRow(
            self.tr("Font family"),
            self.guiFont,
            self.tr("Requires restart."),
            theButton=self.fontButton
        )

        # Font Size
        self.guiFontSize = QSpinBox(self)
        self.guiFontSize.setMinimum(8)
        self.guiFontSize.setMaximum(60)
        self.guiFontSize.setSingleStep(1)
        self.guiFontSize.setValue(self.mainConf.guiFontSize)
        self.mainForm.addRow(
            self.tr("Font size"),
            self.guiFontSize,
            self.tr("Requires restart."),
            theUnit=self.tr("pt")
        )

        # GUI Settings
        # ============
        self.mainForm.addGroupLabel(self.tr("GUI Settings"))

        self.emphLabels = QSwitch()
        self.emphLabels.setChecked(self.mainConf.emphLabels)
        self.mainForm.addRow(
            self.tr("Emphasise partition and chapter labels"),
            self.emphLabels,
            self.tr("Makes them stand out in the project tree."),
        )

        self.showFullPath = QSwitch()
        self.showFullPath.setChecked(self.mainConf.showFullPath)
        self.mainForm.addRow(
            self.tr("Show full path in document header"),
            self.showFullPath,
            self.tr("Add the parent folder names to the header.")
        )

        self.hideVScroll = QSwitch()
        self.hideVScroll.setChecked(self.mainConf.hideVScroll)
        self.mainForm.addRow(
            self.tr("Hide vertical scroll bars in main windows"),
            self.hideVScroll,
            self.tr("Scrolling available with mouse wheel and keys only.")
        )

        self.hideHScroll = QSwitch()
        self.hideHScroll.setChecked(self.mainConf.hideHScroll)
        self.mainForm.addRow(
            self.tr("Hide horizontal scroll bars in main windows"),
            self.hideHScroll,
            self.tr("Scrolling available with mouse wheel and keys only.")
        )

        return

    def saveValues(self):
        """Save the values set for this tab.
        """
        guiLang     = self.guiLang.currentData()
        guiTheme    = self.guiTheme.currentData()
        guiIcons    = self.guiIcons.currentData()
        guiSyntax   = self.guiSyntax.currentData()
        guiFont     = self.guiFont.text()
        guiFontSize = self.guiFontSize.value()
        emphLabels  = self.emphLabels.isChecked()

        # Check if restart is needed
        needsRestart = False
        needsRestart |= self.mainConf.guiLang != guiLang
        needsRestart |= self.mainConf.guiTheme != guiTheme
        needsRestart |= self.mainConf.guiIcons != guiIcons
        needsRestart |= self.mainConf.guiFont != guiFont
        needsRestart |= self.mainConf.guiFontSize != guiFontSize

        # Check if refreshing project tree is needed
        refreshTree = False
        refreshTree |= self.mainConf.emphLabels != emphLabels

        self.mainConf.guiLang      = guiLang
        self.mainConf.guiTheme     = guiTheme
        self.mainConf.guiIcons     = guiIcons
        self.mainConf.guiSyntax    = guiSyntax
        self.mainConf.guiFont      = guiFont
        self.mainConf.guiFontSize  = guiFontSize
        self.mainConf.emphLabels   = emphLabels
        self.mainConf.showFullPath = self.showFullPath.isChecked()
        self.mainConf.hideVScroll  = self.hideVScroll.isChecked()
        self.mainConf.hideHScroll  = self.hideHScroll.isChecked()

        self.mainConf.confChanged = True

        return needsRestart, refreshTree

    ##
    #  Slots
    ##

    def _selectFont(self):
        """Open the QFontDialog and set a font for the font style.
        """
        currFont = QFont()
        currFont.setFamily(self.mainConf.guiFont)
        currFont.setPointSize(self.mainConf.guiFontSize)
        theFont, theStatus = QFontDialog.getFont(currFont, self)
        if theStatus:
            self.guiFont.setText(theFont.family())
            self.guiFontSize.setValue(theFont.pointSize())
        return
Beispiel #8
0
class GuiBuildNovel(QDialog):

    FMT_PDF    = 1  # Print to PDF
    FMT_ODT    = 2  # Open Document file
    FMT_FODT   = 3  # Flat Open Document file
    FMT_HTM    = 4  # HTML5
    FMT_NWD    = 5  # nW Markdown
    FMT_MD     = 6  # Standard Markdown
    FMT_GH     = 7  # GitHub Markdown
    FMT_JSON_H = 8  # HTML5 wrapped in JSON
    FMT_JSON_M = 9  # nW Markdown wrapped in JSON

    def __init__(self, mainGui):
        QDialog.__init__(self, mainGui)

        logger.debug("Initialising GuiBuildNovel ...")
        self.setObjectName("GuiBuildNovel")

        self.mainConf   = novelwriter.CONFIG
        self.mainGui    = mainGui
        self.mainTheme  = mainGui.mainTheme
        self.theProject = mainGui.theProject

        self.htmlText  = []  # List of html documents
        self.htmlStyle = []  # List of html styles
        self.htmlSize  = 0   # Size of the html document
        self.buildTime = 0   # The timestamp of the last build

        self.setWindowTitle(self.tr("Build Novel Project"))
        self.setMinimumWidth(self.mainConf.pxInt(700))
        self.setMinimumHeight(self.mainConf.pxInt(600))

        pOptions = self.theProject.options
        self.resize(
            self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winWidth",  900)),
            self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winHeight", 800))
        )

        self.docView = GuiBuildNovelDocView(self, self.theProject)

        hS = self.mainTheme.fontPixelSize
        wS = 2*hS

        # Title Formats
        # =============

        self.titleGroup = QGroupBox(self.tr("Title Formats for Novel Files"), self)
        self.titleForm  = QGridLayout(self)
        self.titleGroup.setLayout(self.titleForm)

        fmtHelp = "<br>".join([
            "<b>%s</b>" % self.tr("Formatting Codes:"),
            self.tr("{0} for the title as set in the document").format(r"%title%"),
            self.tr("{0} for chapter number (1, 2, 3)").format(r"%ch%"),
            self.tr("{0} for chapter number as a word (one, two)").format(r"%chw%"),
            self.tr("{0} for chapter number in upper case Roman").format(r"%chI%"),
            self.tr("{0} for chapter number in lower case Roman").format(r"%chi%"),
            self.tr("{0} for scene number within chapter").format(r"%sc%"),
            self.tr("{0} for scene number within novel").format(r"%sca%"),
        ])
        fmtScHelp = "<br><br>%s" % self.tr(
            "Leave blank to skip this heading, or set to a static text, like "
            "for instance '{0}', to make a separator. The separator will "
            "be centred automatically and only appear between sections of "
            "the same type."
        ).format("* * *")
        xFmt = self.mainConf.pxInt(100)

        self.fmtTitle = QLineEdit()
        self.fmtTitle.setMaxLength(200)
        self.fmtTitle.setMinimumWidth(xFmt)
        self.fmtTitle.setToolTip(fmtHelp)
        self.fmtTitle.setText(
            self._reFmtCodes(self.theProject.titleFormat["title"])
        )

        self.fmtChapter = QLineEdit()
        self.fmtChapter.setMaxLength(200)
        self.fmtChapter.setMinimumWidth(xFmt)
        self.fmtChapter.setToolTip(fmtHelp)
        self.fmtChapter.setText(
            self._reFmtCodes(self.theProject.titleFormat["chapter"])
        )

        self.fmtUnnumbered = QLineEdit()
        self.fmtUnnumbered.setMaxLength(200)
        self.fmtUnnumbered.setMinimumWidth(xFmt)
        self.fmtUnnumbered.setToolTip(fmtHelp)
        self.fmtUnnumbered.setText(
            self._reFmtCodes(self.theProject.titleFormat["unnumbered"])
        )

        self.fmtScene = QLineEdit()
        self.fmtScene.setMaxLength(200)
        self.fmtScene.setMinimumWidth(xFmt)
        self.fmtScene.setToolTip(fmtHelp + fmtScHelp)
        self.fmtScene.setText(
            self._reFmtCodes(self.theProject.titleFormat["scene"])
        )

        self.fmtSection = QLineEdit()
        self.fmtSection.setMaxLength(200)
        self.fmtSection.setMinimumWidth(xFmt)
        self.fmtSection.setToolTip(fmtHelp + fmtScHelp)
        self.fmtSection.setText(
            self._reFmtCodes(self.theProject.titleFormat["section"])
        )

        self.buildLang = QComboBox()
        self.buildLang.setMinimumWidth(xFmt)
        theLangs = self.mainConf.listLanguages(self.mainConf.LANG_PROJ)
        self.buildLang.addItem("[%s]" % self.tr("Not Set"), "None")
        for langID, langName in theLangs:
            self.buildLang.addItem(langName, langID)

        langIdx = self.buildLang.findData(self.theProject.projLang)
        if langIdx != -1:
            self.buildLang.setCurrentIndex(langIdx)

        self.hideScene = QSwitch(width=wS, height=hS)
        self.hideScene.setChecked(
            pOptions.getBool("GuiBuildNovel", "hideScene", False)
        )

        self.hideSection = QSwitch(width=wS, height=hS)
        self.hideSection.setChecked(
            pOptions.getBool("GuiBuildNovel", "hideSection", True)
        )

        # Wrapper boxes due to QGridView and QLineEdit expand bug
        self.boxTitle = QHBoxLayout()
        self.boxTitle.addWidget(self.fmtTitle)
        self.boxChapter = QHBoxLayout()
        self.boxChapter.addWidget(self.fmtChapter)
        self.boxUnnumb = QHBoxLayout()
        self.boxUnnumb.addWidget(self.fmtUnnumbered)
        self.boxScene = QHBoxLayout()
        self.boxScene.addWidget(self.fmtScene)
        self.boxSection = QHBoxLayout()
        self.boxSection.addWidget(self.fmtSection)

        titleLabel    = QLabel(self.tr("Title"))
        chapterLabel  = QLabel(self.tr("Chapter"))
        unnumbLabel   = QLabel(self.tr("Unnumbered"))
        sceneLabel    = QLabel(self.tr("Scene"))
        sectionLabel  = QLabel(self.tr("Section"))
        langLabel     = QLabel(self.tr("Language"))
        hSceneLabel   = QLabel(self.tr("Hide scene"))
        hSectionLabel = QLabel(self.tr("Hide section"))

        self.titleForm.addWidget(titleLabel,       0, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addLayout(self.boxTitle,    0, 1, 1, 1, Qt.AlignRight)
        self.titleForm.addWidget(chapterLabel,     1, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addLayout(self.boxChapter,  1, 1, 1, 1, Qt.AlignRight)
        self.titleForm.addWidget(unnumbLabel,      2, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addLayout(self.boxUnnumb,   2, 1, 1, 1, Qt.AlignRight)
        self.titleForm.addWidget(sceneLabel,       3, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addLayout(self.boxScene,    3, 1, 1, 1, Qt.AlignRight)
        self.titleForm.addWidget(sectionLabel,     4, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addLayout(self.boxSection,  4, 1, 1, 1, Qt.AlignRight)
        self.titleForm.addWidget(langLabel,        5, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addWidget(self.buildLang,   5, 1, 1, 1, Qt.AlignRight)
        self.titleForm.addWidget(hSceneLabel,      6, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addWidget(self.hideScene,   6, 1, 1, 1, Qt.AlignRight)
        self.titleForm.addWidget(hSectionLabel,    7, 0, 1, 1, Qt.AlignLeft)
        self.titleForm.addWidget(self.hideSection, 7, 1, 1, 1, Qt.AlignRight)

        self.titleForm.setColumnStretch(0, 0)
        self.titleForm.setColumnStretch(1, 1)

        # Font Options
        # ============

        self.fontGroup = QGroupBox(self.tr("Font Options"), self)
        self.fontForm  = QGridLayout(self)
        self.fontGroup.setLayout(self.fontForm)

        # Font Family
        self.textFont = QLineEdit()
        self.textFont.setReadOnly(True)
        self.textFont.setMinimumWidth(xFmt)
        self.textFont.setText(
            pOptions.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
        )
        self.fontButton = QPushButton("...")
        self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
        self.fontButton.clicked.connect(self._selectFont)

        self.textSize = QSpinBox(self)
        self.textSize.setFixedWidth(6*self.mainTheme.textNWidth)
        self.textSize.setMinimum(6)
        self.textSize.setMaximum(72)
        self.textSize.setSingleStep(1)
        self.textSize.setValue(
            pOptions.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
        )

        self.lineHeight = QDoubleSpinBox(self)
        self.lineHeight.setFixedWidth(6*self.mainTheme.textNWidth)
        self.lineHeight.setMinimum(0.8)
        self.lineHeight.setMaximum(3.0)
        self.lineHeight.setSingleStep(0.05)
        self.lineHeight.setDecimals(2)
        self.lineHeight.setValue(
            pOptions.getFloat("GuiBuildNovel", "lineHeight", 1.15)
        )

        # Wrapper box due to QGridView and QLineEdit expand bug
        self.boxFont = QHBoxLayout()
        self.boxFont.addWidget(self.textFont)

        fontFamilyLabel = QLabel(self.tr("Font family"))
        fontSizeLabel   = QLabel(self.tr("Font size"))
        lineHeightLabel = QLabel(self.tr("Line height"))
        justifyLabel    = QLabel(self.tr("Justify text"))
        stylingLabel    = QLabel(self.tr("Disable styling"))

        self.fontForm.addWidget(fontFamilyLabel,  0, 0, 1, 1, Qt.AlignLeft)
        self.fontForm.addLayout(self.boxFont,     0, 1, 1, 1, Qt.AlignRight)
        self.fontForm.addWidget(self.fontButton,  0, 2, 1, 1, Qt.AlignRight)
        self.fontForm.addWidget(fontSizeLabel,    1, 0, 1, 1, Qt.AlignLeft)
        self.fontForm.addWidget(self.textSize,    1, 1, 1, 2, Qt.AlignRight)
        self.fontForm.addWidget(lineHeightLabel,  2, 0, 1, 1, Qt.AlignLeft)
        self.fontForm.addWidget(self.lineHeight,  2, 1, 1, 2, Qt.AlignRight)

        self.fontForm.setColumnStretch(0, 0)
        self.fontForm.setColumnStretch(1, 1)
        self.fontForm.setColumnStretch(2, 0)

        # Styling Options
        # ===============

        self.styleGroup = QGroupBox(self.tr("Styling Options"), self)
        self.styleForm  = QGridLayout(self)
        self.styleGroup.setLayout(self.styleForm)

        self.justifyText = QSwitch(width=wS, height=hS)
        self.justifyText.setChecked(
            pOptions.getBool("GuiBuildNovel", "justifyText", False)
        )

        self.noStyling = QSwitch(width=wS, height=hS)
        self.noStyling.setChecked(
            pOptions.getBool("GuiBuildNovel", "noStyling", False)
        )

        self.styleForm.addWidget(justifyLabel,     1, 0, 1, 1, Qt.AlignLeft)
        self.styleForm.addWidget(self.justifyText, 1, 1, 1, 2, Qt.AlignRight)
        self.styleForm.addWidget(stylingLabel,     2, 0, 1, 1, Qt.AlignLeft)
        self.styleForm.addWidget(self.noStyling,   2, 1, 1, 2, Qt.AlignRight)

        self.styleForm.setColumnStretch(0, 0)
        self.styleForm.setColumnStretch(1, 1)

        # Include Options
        # ===============

        self.textGroup = QGroupBox(self.tr("Include Options"), self)
        self.textForm  = QGridLayout(self)
        self.textGroup.setLayout(self.textForm)

        self.includeSynopsis = QSwitch(width=wS, height=hS)
        self.includeSynopsis.setChecked(
            pOptions.getBool("GuiBuildNovel", "incSynopsis", False)
        )

        self.includeComments = QSwitch(width=wS, height=hS)
        self.includeComments.setChecked(
            pOptions.getBool("GuiBuildNovel", "incComments", False)
        )

        self.includeKeywords = QSwitch(width=wS, height=hS)
        self.includeKeywords.setChecked(
            pOptions.getBool("GuiBuildNovel", "incKeywords", False)
        )

        self.includeBody = QSwitch(width=wS, height=hS)
        self.includeBody.setChecked(
            pOptions.getBool("GuiBuildNovel", "incBodyText", True)
        )

        synopsisLabel = QLabel(self.tr("Include synopsis"))
        commentsLabel = QLabel(self.tr("Include comments"))
        keywordsLabel = QLabel(self.tr("Include keywords"))
        bodyLabel     = QLabel(self.tr("Include body text"))

        self.textForm.addWidget(synopsisLabel,        0, 0, 1, 1, Qt.AlignLeft)
        self.textForm.addWidget(self.includeSynopsis, 0, 1, 1, 1, Qt.AlignRight)
        self.textForm.addWidget(commentsLabel,        1, 0, 1, 1, Qt.AlignLeft)
        self.textForm.addWidget(self.includeComments, 1, 1, 1, 1, Qt.AlignRight)
        self.textForm.addWidget(keywordsLabel,        2, 0, 1, 1, Qt.AlignLeft)
        self.textForm.addWidget(self.includeKeywords, 2, 1, 1, 1, Qt.AlignRight)
        self.textForm.addWidget(bodyLabel,            3, 0, 1, 1, Qt.AlignLeft)
        self.textForm.addWidget(self.includeBody,     3, 1, 1, 1, Qt.AlignRight)

        self.textForm.setColumnStretch(0, 1)
        self.textForm.setColumnStretch(1, 0)

        # File Filter Options
        # ===================

        self.fileGroup = QGroupBox(self.tr("File Filter Options"), self)
        self.fileForm  = QGridLayout(self)
        self.fileGroup.setLayout(self.fileForm)

        self.novelFiles = QSwitch(width=wS, height=hS)
        self.novelFiles.setChecked(
            pOptions.getBool("GuiBuildNovel", "addNovel", True)
        )

        self.noteFiles = QSwitch(width=wS, height=hS)
        self.noteFiles.setChecked(
            pOptions.getBool("GuiBuildNovel", "addNotes", False)
        )

        self.ignoreFlag = QSwitch(width=wS, height=hS)
        self.ignoreFlag.setChecked(
            pOptions.getBool("GuiBuildNovel", "ignoreFlag", False)
        )

        novelLabel  = QLabel(self.tr("Include novel files"))
        notesLabel  = QLabel(self.tr("Include note files"))
        exportLabel = QLabel(self.tr("Ignore export flag"))

        self.fileForm.addWidget(novelLabel,      0, 0, 1, 1, Qt.AlignLeft)
        self.fileForm.addWidget(self.novelFiles, 0, 1, 1, 1, Qt.AlignRight)
        self.fileForm.addWidget(notesLabel,      1, 0, 1, 1, Qt.AlignLeft)
        self.fileForm.addWidget(self.noteFiles,  1, 1, 1, 1, Qt.AlignRight)
        self.fileForm.addWidget(exportLabel,     2, 0, 1, 1, Qt.AlignLeft)
        self.fileForm.addWidget(self.ignoreFlag, 2, 1, 1, 1, Qt.AlignRight)

        self.fileForm.setColumnStretch(0, 1)
        self.fileForm.setColumnStretch(1, 0)

        # Export Options
        # ==============

        self.exportGroup = QGroupBox(self.tr("Export Options"), self)
        self.exportForm  = QGridLayout(self)
        self.exportGroup.setLayout(self.exportForm)

        self.replaceTabs = QSwitch(width=wS, height=hS)
        self.replaceTabs.setChecked(
            pOptions.getBool("GuiBuildNovel", "replaceTabs", False)
        )

        self.replaceUCode = QSwitch(width=wS, height=hS)
        self.replaceUCode.setChecked(
            pOptions.getBool("GuiBuildNovel", "replaceUCode", False)
        )

        tabsLabel  = QLabel(self.tr("Replace tabs with spaces"))
        uCodeLabel = QLabel(self.tr("Replace Unicode in HTML"))

        self.exportForm.addWidget(tabsLabel,         0, 0, 1, 1, Qt.AlignLeft)
        self.exportForm.addWidget(self.replaceTabs,  0, 1, 1, 1, Qt.AlignRight)
        self.exportForm.addWidget(uCodeLabel,        1, 0, 1, 1, Qt.AlignLeft)
        self.exportForm.addWidget(self.replaceUCode, 1, 1, 1, 1, Qt.AlignRight)

        self.exportForm.setColumnStretch(0, 1)
        self.exportForm.setColumnStretch(1, 0)

        # Build Button
        # ============

        self.buildProgress = QProgressBar()

        self.buildNovel = QPushButton(self.tr("Build Preview"))
        self.buildNovel.clicked.connect(self._buildPreview)

        # Action Buttons
        # ==============

        self.buttonBox = QHBoxLayout()

        # Printing

        self.printMenu = QMenu(self)
        self.btnPrint = QPushButton(self.tr("Print"))
        self.btnPrint.setMenu(self.printMenu)

        self.printSend = QAction(self.tr("Print Preview"), self)
        self.printSend.triggered.connect(self._printDocument)
        self.printMenu.addAction(self.printSend)

        self.printFile = QAction(self.tr("Print to PDF"), self)
        self.printFile.triggered.connect(lambda: self._saveDocument(self.FMT_PDF))
        self.printMenu.addAction(self.printFile)

        # Saving to File

        self.saveMenu = QMenu(self)
        self.btnSave = QPushButton(self.tr("Save As"))
        self.btnSave.setMenu(self.saveMenu)

        self.saveODT = QAction(self.tr("Open Document (.odt)"), self)
        self.saveODT.triggered.connect(lambda: self._saveDocument(self.FMT_ODT))
        self.saveMenu.addAction(self.saveODT)

        self.saveFODT = QAction(self.tr("Flat Open Document (.fodt)"), self)
        self.saveFODT.triggered.connect(lambda: self._saveDocument(self.FMT_FODT))
        self.saveMenu.addAction(self.saveFODT)

        self.saveHTM = QAction(self.tr("novelWriter HTML (.htm)"), self)
        self.saveHTM.triggered.connect(lambda: self._saveDocument(self.FMT_HTM))
        self.saveMenu.addAction(self.saveHTM)

        self.saveNWD = QAction(self.tr("novelWriter Markdown (.nwd)"), self)
        self.saveNWD.triggered.connect(lambda: self._saveDocument(self.FMT_NWD))
        self.saveMenu.addAction(self.saveNWD)

        self.saveMD = QAction(self.tr("Standard Markdown (.md)"), self)
        self.saveMD.triggered.connect(lambda: self._saveDocument(self.FMT_MD))
        self.saveMenu.addAction(self.saveMD)

        self.saveGH = QAction(self.tr("GitHub Markdown (.md)"), self)
        self.saveGH.triggered.connect(lambda: self._saveDocument(self.FMT_GH))
        self.saveMenu.addAction(self.saveGH)

        self.saveJsonH = QAction(self.tr("JSON + novelWriter HTML (.json)"), self)
        self.saveJsonH.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_H))
        self.saveMenu.addAction(self.saveJsonH)

        self.saveJsonM = QAction(self.tr("JSON + novelWriter Markdown (.json)"), self)
        self.saveJsonM.triggered.connect(lambda: self._saveDocument(self.FMT_JSON_M))
        self.saveMenu.addAction(self.saveJsonM)

        self.btnClose = QPushButton(self.tr("Close"))
        self.btnClose.clicked.connect(self._doClose)

        self.buttonBox.addWidget(self.btnSave)
        self.buttonBox.addWidget(self.btnPrint)
        self.buttonBox.addWidget(self.btnClose)
        self.buttonBox.setSpacing(self.mainConf.pxInt(4))

        # Assemble GUI
        # ============

        # Splitter Position
        boxWidth = self.mainConf.pxInt(350)
        boxWidth = pOptions.getInt("GuiBuildNovel", "boxWidth", boxWidth)
        docWidth = max(self.width() - boxWidth, 100)
        docWidth = pOptions.getInt("GuiBuildNovel", "docWidth", docWidth)

        # The Tool Box
        self.toolsBox = QVBoxLayout()
        self.toolsBox.addWidget(self.titleGroup)
        self.toolsBox.addWidget(self.fontGroup)
        self.toolsBox.addWidget(self.styleGroup)
        self.toolsBox.addWidget(self.textGroup)
        self.toolsBox.addWidget(self.fileGroup)
        self.toolsBox.addWidget(self.exportGroup)
        self.toolsBox.addStretch(1)

        # Tool Box Wrapper Widget
        self.toolsWidget = QWidget()
        self.toolsWidget.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Minimum)
        self.toolsWidget.setLayout(self.toolsBox)

        # Tool Box Scroll Area
        self.toolsArea = QScrollArea()
        self.toolsArea.setMinimumWidth(self.mainConf.pxInt(250))
        self.toolsArea.setWidgetResizable(True)
        self.toolsArea.setWidget(self.toolsWidget)

        if self.mainConf.hideVScroll:
            self.toolsArea.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        else:
            self.toolsArea.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)

        if self.mainConf.hideHScroll:
            self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
        else:
            self.toolsArea.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)

        # Tools and Buttons Layout
        tSp = self.mainConf.pxInt(8)
        self.innerBox = QVBoxLayout()
        self.innerBox.addWidget(self.toolsArea)
        self.innerBox.addSpacing(tSp)
        self.innerBox.addWidget(self.buildProgress)
        self.innerBox.addWidget(self.buildNovel)
        self.innerBox.addSpacing(tSp)
        self.innerBox.addLayout(self.buttonBox)

        # Tools and Buttons Wrapper Widget
        self.innerWidget = QWidget()
        self.innerWidget.setLayout(self.innerBox)

        # Main Dialog Splitter
        self.mainSplit = QSplitter(Qt.Horizontal)
        self.mainSplit.addWidget(self.innerWidget)
        self.mainSplit.addWidget(self.docView)
        self.mainSplit.setSizes([boxWidth, docWidth])

        self.idxSettings = self.mainSplit.indexOf(self.innerWidget)
        self.idxDocument = self.mainSplit.indexOf(self.docView)

        self.mainSplit.setCollapsible(self.idxSettings, False)
        self.mainSplit.setCollapsible(self.idxDocument, False)

        # Outer Layout
        self.outerBox = QHBoxLayout()
        self.outerBox.addWidget(self.mainSplit)

        self.setLayout(self.outerBox)
        self.buildNovel.setFocus()

        logger.debug("GuiBuildNovel initialisation complete")

        return

    def viewCachedDoc(self):
        """Load the previously generated document from cache.
        """
        if self._loadCache():
            textFont = self.textFont.text()
            textSize = self.textSize.value()
            justifyText = self.justifyText.isChecked()
            self.docView.setTextFont(textFont, textSize)
            self.docView.setJustify(justifyText)
            if self.noStyling.isChecked():
                self.docView.clearStyleSheet()
            else:
                self.docView.setStyleSheet(self.htmlStyle)

            htmlSize = sum([len(x) for x in self.htmlText])
            if htmlSize < nwConst.MAX_BUILDSIZE:
                qApp.processEvents()
                self.docView.setContent(self.htmlText, self.buildTime)
            else:
                self.docView.setText(
                    self.tr("Failed to generate preview. The result is too big.")
                )

        else:
            self.htmlText = []
            self.htmlStyle = []
            self.buildTime = 0
            return False

        return True

    ##
    #  Slots and Related
    ##

    def _buildPreview(self):
        """Build a preview of the project in the document viewer.
        """
        # Get Settings
        justifyText = self.justifyText.isChecked()
        noStyling = self.noStyling.isChecked()
        textFont = self.textFont.text()
        textSize = self.textSize.value()
        replaceTabs = self.replaceTabs.isChecked()

        self.htmlText = []
        self.htmlStyle = []
        self.htmlSize = 0

        # Build Preview
        # =============

        makeHtml = ToHtml(self.theProject)
        self._doBuild(makeHtml, isPreview=True)
        if replaceTabs:
            makeHtml.replaceTabs()

        self.htmlText  = makeHtml.fullHTML
        self.htmlStyle = makeHtml.getStyleSheet()
        self.htmlSize  = makeHtml.getFullResultSize()
        self.buildTime = int(time())

        # Load Preview
        # ============

        self.docView.setTextFont(textFont, textSize)
        self.docView.setJustify(justifyText)
        if noStyling:
            self.docView.clearStyleSheet()
        else:
            self.docView.setStyleSheet(self.htmlStyle)

        if self.htmlSize < nwConst.MAX_BUILDSIZE:
            self.docView.setContent(self.htmlText, self.buildTime)
        else:
            self.docView.setText(
                "Failed to generate preview. The result is too big."
            )

        self._saveCache()

        return

    def _doBuild(self, bldObj, isPreview=False, doConvert=True):
        """Rund the build with a specific build object.
        """
        tStart = int(time())

        # Get Settings
        fmtTitle      = self.fmtTitle.text()
        fmtChapter    = self.fmtChapter.text()
        fmtUnnumbered = self.fmtUnnumbered.text()
        fmtScene      = self.fmtScene.text()
        fmtSection    = self.fmtSection.text()
        buildLang     = self.buildLang.currentData()
        hideScene     = self.hideScene.isChecked()
        hideSection   = self.hideSection.isChecked()
        textFont      = self.textFont.text()
        textSize      = self.textSize.value()
        lineHeight    = self.lineHeight.value()
        justifyText   = self.justifyText.isChecked()
        noStyling     = self.noStyling.isChecked()
        incSynopsis   = self.includeSynopsis.isChecked()
        incComments   = self.includeComments.isChecked()
        incKeywords   = self.includeKeywords.isChecked()
        novelFiles    = self.novelFiles.isChecked()
        noteFiles     = self.noteFiles.isChecked()
        ignoreFlag    = self.ignoreFlag.isChecked()
        includeBody   = self.includeBody.isChecked()
        replaceUCode  = self.replaceUCode.isChecked()

        # The language lookup dict is reloaded if needed
        self.theProject.setProjectLang(buildLang)

        # Get font information
        fontInfo = QFontInfo(QFont(textFont, textSize))
        textFixed = fontInfo.fixedPitch()

        isHtml = isinstance(bldObj, ToHtml)
        isOdt = isinstance(bldObj, ToOdt)

        bldObj.setTitleFormat(fmtTitle)
        bldObj.setChapterFormat(fmtChapter)
        bldObj.setUnNumberedFormat(fmtUnnumbered)
        bldObj.setSceneFormat(fmtScene, hideScene)
        bldObj.setSectionFormat(fmtSection, hideSection)

        bldObj.setFont(textFont, textSize, textFixed)
        bldObj.setJustify(justifyText)
        bldObj.setLineHeight(lineHeight)

        bldObj.setSynopsis(incSynopsis)
        bldObj.setComments(incComments)
        bldObj.setKeywords(incKeywords)
        bldObj.setBodyText(includeBody)

        if isHtml:
            bldObj.setStyles(not noStyling)
            bldObj.setReplaceUnicode(replaceUCode)

        if isOdt:
            bldObj.setColourHeaders(not noStyling)
            bldObj.setLanguage(buildLang)
            bldObj.initDocument()

        # Make sure the project and document is up to date
        self.mainGui.saveDocument()

        self.buildProgress.setMaximum(len(self.theProject.tree))
        self.buildProgress.setValue(0)

        for nItt, tItem in enumerate(self.theProject.tree):

            noteRoot = noteFiles
            noteRoot &= tItem.itemType == nwItemType.ROOT
            noteRoot &= tItem.itemClass != nwItemClass.NOVEL
            noteRoot &= tItem.itemClass != nwItemClass.ARCHIVE

            try:
                if noteRoot:
                    # Add headers for root folders of notes
                    bldObj.addRootHeading(tItem.itemHandle)
                    if doConvert:
                        bldObj.doConvert()

                elif self._checkInclude(tItem, noteFiles, novelFiles, ignoreFlag):
                    bldObj.setText(tItem.itemHandle)
                    bldObj.doPreProcessing()
                    bldObj.tokenizeText()
                    bldObj.doHeaders()
                    if doConvert:
                        bldObj.doConvert()
                    bldObj.doPostProcessing()

            except Exception:
                logger.error("Failed to build document '%s'", tItem.itemHandle)
                logException()
                if isPreview:
                    self.docView.setText((
                        "Failed to generate preview. "
                        "Document with title '%s' could not be parsed."
                    ) % tItem.itemName)

                return False

            # Update progress bar, also for skipped items
            self.buildProgress.setValue(nItt+1)

        if isOdt:
            bldObj.closeDocument()

        tEnd = int(time())
        logger.debug("Built project in %.3f ms", 1000*(tEnd - tStart))

        if bldObj.errData:
            self.mainGui.makeAlert([
                self.tr("There were problems when building the project:")
            ] + bldObj.errData, nwAlert.ERROR)

        return

    def _checkInclude(self, theItem, noteFiles, novelFiles, ignoreFlag):
        """This function checks whether a file should be included in the
        export or not. For standard note and novel files, this is
        controlled by the options selected by the user. For other files
        classified as non-exportable, a few checks must be made, and the
        following are not:
        * Items that are not actual files.
        * Items that have been orphaned which are tagged as NO_LAYOUT
          and NO_CLASS.
        * Items that appear in the TRASH folder or have parent set to
          None (orphaned files).
        """
        if theItem is None:
            return False

        if not (theItem.isExported or ignoreFlag):
            return False

        isNone  = theItem.itemType != nwItemType.FILE
        isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
        isNone |= theItem.isInactive()
        isNone |= theItem.itemParent is None
        isNote  = theItem.itemLayout == nwItemLayout.NOTE
        isNovel = not isNone and not isNote

        if isNone:
            return False
        if isNote and not noteFiles:
            return False
        if isNovel and not novelFiles:
            return False

        return True

    def _saveDocument(self, theFmt):
        """Save the document to various formats.
        """
        replaceTabs = self.replaceTabs.isChecked()

        fileExt = ""
        textFmt = ""

        # Settings
        # ========

        if theFmt == self.FMT_ODT:
            fileExt = "odt"
            textFmt = self.tr("Open Document")

        elif theFmt == self.FMT_FODT:
            fileExt = "fodt"
            textFmt = self.tr("Flat Open Document")

        elif theFmt == self.FMT_HTM:
            fileExt = "htm"
            textFmt = self.tr("Plain HTML")

        elif theFmt == self.FMT_NWD:
            fileExt = "nwd"
            textFmt = self.tr("novelWriter Markdown")

        elif theFmt == self.FMT_MD:
            fileExt = "md"
            textFmt = self.tr("Standard Markdown")

        elif theFmt == self.FMT_GH:
            fileExt = "md"
            textFmt = self.tr("GitHub Markdown")

        elif theFmt == self.FMT_JSON_H:
            fileExt = "json"
            textFmt = self.tr("JSON + novelWriter HTML")

        elif theFmt == self.FMT_JSON_M:
            fileExt = "json"
            textFmt = self.tr("JSON + novelWriter Markdown")

        elif theFmt == self.FMT_PDF:
            fileExt = "pdf"
            textFmt = self.tr("PDF")

        else:
            return False

        # Generate File Name
        # ==================

        cleanName = makeFileNameSafe(self.theProject.projName)
        fileName = "%s.%s" % (cleanName, fileExt)
        saveDir = self.mainConf.lastPath
        if not os.path.isdir(saveDir):
            saveDir = os.path.expanduser("~")

        savePath = os.path.join(saveDir, fileName)
        savePath, _ = QFileDialog.getSaveFileName(
            self, self.tr("Save Document As"), savePath
        )
        if not savePath:
            return False

        self.mainConf.setLastPath(savePath)

        # Build and Write
        # ===============

        errMsg = ""
        wSuccess = False

        if theFmt == self.FMT_ODT:
            makeOdt = ToOdt(self.theProject, isFlat=False)
            self._doBuild(makeOdt)
            try:
                makeOdt.saveOpenDocText(savePath)
                wSuccess = True
            except Exception as exc:
                errMsg = formatException(exc)

        elif theFmt == self.FMT_FODT:
            makeOdt = ToOdt(self.theProject, isFlat=True)
            self._doBuild(makeOdt)
            try:
                makeOdt.saveFlatXML(savePath)
                wSuccess = True
            except Exception as exc:
                errMsg = formatException(exc)

        elif theFmt == self.FMT_HTM:
            makeHtml = ToHtml(self.theProject)
            self._doBuild(makeHtml)
            if replaceTabs:
                makeHtml.replaceTabs()

            try:
                makeHtml.saveHTML5(savePath)
                wSuccess = True
            except Exception as exc:
                errMsg = formatException(exc)

        elif theFmt == self.FMT_NWD:
            makeNwd = ToMarkdown(self.theProject)
            makeNwd.setKeepMarkdown(True)
            self._doBuild(makeNwd, doConvert=False)
            if replaceTabs:
                makeNwd.replaceTabs(spaceChar=" ")

            try:
                makeNwd.saveRawMarkdown(savePath)
                wSuccess = True
            except Exception as exc:
                errMsg = formatException(exc)

        elif theFmt in (self.FMT_MD, self.FMT_GH):
            makeMd = ToMarkdown(self.theProject)
            if theFmt == self.FMT_GH:
                makeMd.setGitHubMarkdown()
            else:
                makeMd.setStandardMarkdown()

            self._doBuild(makeMd)
            if replaceTabs:
                makeMd.replaceTabs(nSpaces=4, spaceChar=" ")

            try:
                makeMd.saveMarkdown(savePath)
                wSuccess = True
            except Exception as exc:
                errMsg = formatException(exc)

        elif theFmt == self.FMT_JSON_H or theFmt == self.FMT_JSON_M:
            jsonData = {
                "meta": {
                    "workingTitle": self.theProject.projName,
                    "novelTitle": self.theProject.bookTitle,
                    "authors": self.theProject.bookAuthors,
                    "buildTime": self.buildTime,
                }
            }

            if theFmt == self.FMT_JSON_H:
                makeHtml = ToHtml(self.theProject)
                self._doBuild(makeHtml)
                if replaceTabs:
                    makeHtml.replaceTabs()

                theBody = []
                for htmlPage in makeHtml.fullHTML:
                    theBody.append(htmlPage.rstrip("\n").split("\n"))
                jsonData["text"] = {
                    "css": self.htmlStyle,
                    "html": theBody,
                }

            elif theFmt == self.FMT_JSON_M:
                makeMd = ToHtml(self.theProject)
                makeMd.setKeepMarkdown(True)
                self._doBuild(makeMd, doConvert=False)
                if replaceTabs:
                    makeMd.replaceTabs(spaceChar=" ")

                theBody = []
                for nwdPage in makeMd.theMarkdown:
                    theBody.append(nwdPage.split("\n"))
                jsonData["text"] = {
                    "nwd": theBody,
                }

            try:
                with open(savePath, mode="w", encoding="utf-8") as outFile:
                    outFile.write(json.dumps(jsonData, indent=2))
                    wSuccess = True
            except Exception as exc:
                errMsg = formatException(exc)

        elif theFmt == self.FMT_PDF:
            try:
                thePrinter = QPrinter()
                thePrinter.setOutputFormat(QPrinter.PdfFormat)
                thePrinter.setOrientation(QPrinter.Portrait)
                thePrinter.setDuplex(QPrinter.DuplexLongSide)
                thePrinter.setFontEmbeddingEnabled(True)
                thePrinter.setColorMode(QPrinter.Color)
                thePrinter.setOutputFileName(savePath)
                self.docView.document().print(thePrinter)
                wSuccess = True

            except Exception as exc:
                errMsg = formatException(exc)

        else:
            # If the if statements above and here match, it should not
            # be possible to reach this else statement.
            return False  # pragma: no cover

        # Report to User
        # ==============

        if wSuccess:
            self.mainGui.makeAlert([
                self.tr("{0} file successfully written to:").format(textFmt), savePath
            ], nwAlert.INFO)
        else:
            self.mainGui.makeAlert(self.tr(
                "Failed to write {0} file. {1}"
            ).format(textFmt, errMsg), nwAlert.ERROR)

        return wSuccess

    def _printDocument(self):
        """Open the print preview dialog.
        """
        thePreview = QPrintPreviewDialog(self)
        thePreview.paintRequested.connect(self._doPrintPreview)
        thePreview.exec_()
        return

    def _doPrintPreview(self, thePrinter):
        """Connect the print preview painter to the document viewer.
        """
        qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
        thePrinter.setOrientation(QPrinter.Portrait)
        self.docView.document().print(thePrinter)
        qApp.restoreOverrideCursor()
        return

    def _selectFont(self):
        """Open the QFontDialog and set a font for the font style.
        """
        currFont = QFont()
        currFont.setFamily(self.textFont.text())
        currFont.setPointSize(self.textSize.value())
        theFont, theStatus = QFontDialog.getFont(currFont, self)
        if theStatus:
            self.textFont.setText(theFont.family())
            self.textSize.setValue(theFont.pointSize())

        self.raise_()  # Move the dialog to front (fixes a bug on macOS)

        return

    def _loadCache(self):
        """Save the current data to cache.
        """
        buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
        dataCount = 0
        if os.path.isfile(buildCache):
            logger.debug("Loading build cache")
            try:
                with open(buildCache, mode="r", encoding="utf-8") as inFile:
                    theJson = inFile.read()
                theData = json.loads(theJson)
            except Exception:
                logger.error("Failed to load build cache")
                logException()
                return False

            if "buildTime" in theData.keys():
                self.buildTime = theData["buildTime"]
            if "htmlStyle" in theData.keys():
                self.htmlStyle = theData["htmlStyle"]
                dataCount += 1
            if "htmlText" in theData.keys():
                self.htmlText = theData["htmlText"]
                dataCount += 1

        return dataCount == 2

    def _saveCache(self):
        """Save the current data to cache.
        """
        buildCache = os.path.join(self.theProject.projCache, nwFiles.BUILD_CACHE)
        logger.debug("Saving build cache")
        try:
            with open(buildCache, mode="w+", encoding="utf-8") as outFile:
                outFile.write(json.dumps({
                    "buildTime": self.buildTime,
                    "htmlStyle": self.htmlStyle,
                    "htmlText": self.htmlText,
                }, indent=2))
        except Exception:
            logger.error("Failed to save build cache")
            logException()
            return False

        return True

    def _doClose(self):
        """Close button was clicked.
        """
        self.close()
        return

    ##
    #  Events
    ##

    def closeEvent(self, theEvent):
        """Capture the user closing the window so we can save settings.
        """
        self._saveSettings()
        self.docView.clear()
        theEvent.accept()
        return

    ##
    #  Internal Functions
    ##

    def _saveSettings(self):
        """Save the various user settings.
        """
        logger.debug("Saving GuiBuildNovel settings")

        # Formatting
        self.theProject.setTitleFormat({
            "title":      self.fmtTitle.text().strip(),
            "chapter":    self.fmtChapter.text().strip(),
            "unnumbered": self.fmtUnnumbered.text().strip(),
            "scene":      self.fmtScene.text().strip(),
            "section":    self.fmtSection.text().strip(),
        })

        buildLang    = self.buildLang.currentData()
        hideScene    = self.hideScene.isChecked()
        hideSection  = self.hideSection.isChecked()
        winWidth     = self.mainConf.rpxInt(self.width())
        winHeight    = self.mainConf.rpxInt(self.height())
        justifyText  = self.justifyText.isChecked()
        noStyling    = self.noStyling.isChecked()
        textFont     = self.textFont.text()
        textSize     = self.textSize.value()
        lineHeight   = self.lineHeight.value()
        novelFiles   = self.novelFiles.isChecked()
        noteFiles    = self.noteFiles.isChecked()
        ignoreFlag   = self.ignoreFlag.isChecked()
        incSynopsis  = self.includeSynopsis.isChecked()
        incComments  = self.includeComments.isChecked()
        incKeywords  = self.includeKeywords.isChecked()
        incBodyText  = self.includeBody.isChecked()
        replaceTabs  = self.replaceTabs.isChecked()
        replaceUCode = self.replaceUCode.isChecked()

        mainSplit = self.mainSplit.sizes()
        boxWidth  = self.mainConf.rpxInt(mainSplit[0])
        docWidth  = self.mainConf.rpxInt(mainSplit[1])

        self.theProject.setProjectLang(buildLang)

        # GUI Settings
        pOptions = self.theProject.options
        pOptions.setValue("GuiBuildNovel", "hideScene",    hideScene)
        pOptions.setValue("GuiBuildNovel", "hideSection",  hideSection)
        pOptions.setValue("GuiBuildNovel", "winWidth",     winWidth)
        pOptions.setValue("GuiBuildNovel", "winHeight",    winHeight)
        pOptions.setValue("GuiBuildNovel", "boxWidth",     boxWidth)
        pOptions.setValue("GuiBuildNovel", "docWidth",     docWidth)
        pOptions.setValue("GuiBuildNovel", "justifyText",  justifyText)
        pOptions.setValue("GuiBuildNovel", "noStyling",    noStyling)
        pOptions.setValue("GuiBuildNovel", "textFont",     textFont)
        pOptions.setValue("GuiBuildNovel", "textSize",     textSize)
        pOptions.setValue("GuiBuildNovel", "lineHeight",   lineHeight)
        pOptions.setValue("GuiBuildNovel", "addNovel",     novelFiles)
        pOptions.setValue("GuiBuildNovel", "addNotes",     noteFiles)
        pOptions.setValue("GuiBuildNovel", "ignoreFlag",   ignoreFlag)
        pOptions.setValue("GuiBuildNovel", "incSynopsis",  incSynopsis)
        pOptions.setValue("GuiBuildNovel", "incComments",  incComments)
        pOptions.setValue("GuiBuildNovel", "incKeywords",  incKeywords)
        pOptions.setValue("GuiBuildNovel", "incBodyText",  incBodyText)
        pOptions.setValue("GuiBuildNovel", "replaceTabs",  replaceTabs)
        pOptions.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
        pOptions.saveSettings()

        return

    def _reFmtCodes(self, theFormat):
        """Translates old formatting codes to new ones.
        """
        theFormat = theFormat.replace(r"%chnum%",     r"%ch%")
        theFormat = theFormat.replace(r"%scnum%",     r"%sc%")
        theFormat = theFormat.replace(r"%scabsnum%",  r"%sca%")
        theFormat = theFormat.replace(r"%chnumword%", r"%chw%")
        return theFormat
Beispiel #9
0
class GuiWritingStats(QDialog):

    C_TIME = 0
    C_LENGTH = 1
    C_IDLE = 2
    C_COUNT = 3
    C_BAR = 4

    FMT_JSON = 0
    FMT_CSV = 1

    def __init__(self, theParent):
        QDialog.__init__(self, theParent)

        logger.debug("Initialising GuiWritingStats ...")
        self.setObjectName("GuiWritingStats")

        self.mainConf = novelwriter.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme
        self.theProject = theParent.theProject
        self.optState = theParent.theProject.optState

        self.logData = []
        self.filterData = []
        self.timeFilter = 0.0
        self.wordOffset = 0

        self.setWindowTitle(self.tr("Writing Statistics"))
        self.setMinimumWidth(self.mainConf.pxInt(420))
        self.setMinimumHeight(self.mainConf.pxInt(400))
        self.resize(
            self.mainConf.pxInt(
                self.optState.getInt("GuiWritingStats", "winWidth", 550)),
            self.mainConf.pxInt(
                self.optState.getInt("GuiWritingStats", "winHeight", 500)))

        # List Box
        wCol0 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol0", 180))
        wCol1 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol1", 80))
        wCol2 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol2", 80))
        wCol3 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol3", 80))

        self.listBox = QTreeWidget()
        self.listBox.setHeaderLabels([
            self.tr("Session Start"),
            self.tr("Length"),
            self.tr("Idle"),
            self.tr("Words"),
            self.tr("Histogram"),
        ])
        self.listBox.setIndentation(0)
        self.listBox.setColumnWidth(self.C_TIME, wCol0)
        self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
        self.listBox.setColumnWidth(self.C_IDLE, wCol2)
        self.listBox.setColumnWidth(self.C_COUNT, wCol3)

        hHeader = self.listBox.headerItem()
        hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
        hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
        hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)

        sortCol = checkIntRange(
            self.optState.getInt("GuiWritingStats", "sortCol", 0), 0, 2, 0)
        sortOrder = checkIntTuple(
            self.optState.getInt("GuiWritingStats", "sortOrder",
                                 Qt.DescendingOrder),
            (Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder)
        self.listBox.sortByColumn(sortCol, sortOrder)
        self.listBox.setSortingEnabled(True)

        # Word Bar
        self.barHeight = int(round(0.5 * self.theTheme.fontPixelSize))
        self.barWidth = self.mainConf.pxInt(200)
        self.barImage = QPixmap(self.barHeight, self.barHeight)
        self.barImage.fill(self.palette().highlight().color())

        # Session Info
        self.infoBox = QGroupBox(self.tr("Sum Totals"), self)
        self.infoForm = QGridLayout(self)
        self.infoBox.setLayout(self.infoForm)

        self.labelTotal = QLabel(formatTime(0))
        self.labelTotal.setFont(self.theTheme.guiFontFixed)
        self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.labelIdleT = QLabel(formatTime(0))
        self.labelIdleT.setFont(self.theTheme.guiFontFixed)
        self.labelIdleT.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.labelFilter = QLabel(formatTime(0))
        self.labelFilter.setFont(self.theTheme.guiFontFixed)
        self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.novelWords = QLabel("0")
        self.novelWords.setFont(self.theTheme.guiFontFixed)
        self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.notesWords = QLabel("0")
        self.notesWords.setFont(self.theTheme.guiFontFixed)
        self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.totalWords = QLabel("0")
        self.totalWords.setFont(self.theTheme.guiFontFixed)
        self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        lblTTime = QLabel(self.tr("Total Time:"))
        lblITime = QLabel(self.tr("Idle Time:"))
        lblFTime = QLabel(self.tr("Filtered Time:"))
        lblNvCount = QLabel(self.tr("Novel Word Count:"))
        lblNtCount = QLabel(self.tr("Notes Word Count:"))
        lblTtCount = QLabel(self.tr("Total Word Count:"))

        self.infoForm.addWidget(lblTTime, 0, 0)
        self.infoForm.addWidget(lblITime, 1, 0)
        self.infoForm.addWidget(lblFTime, 2, 0)
        self.infoForm.addWidget(lblNvCount, 3, 0)
        self.infoForm.addWidget(lblNtCount, 4, 0)
        self.infoForm.addWidget(lblTtCount, 5, 0)

        self.infoForm.addWidget(self.labelTotal, 0, 1)
        self.infoForm.addWidget(self.labelIdleT, 1, 1)
        self.infoForm.addWidget(self.labelFilter, 2, 1)
        self.infoForm.addWidget(self.novelWords, 3, 1)
        self.infoForm.addWidget(self.notesWords, 4, 1)
        self.infoForm.addWidget(self.totalWords, 5, 1)

        self.infoForm.setRowStretch(6, 1)

        # Filter Options
        sPx = self.theTheme.baseIconSize

        self.filterBox = QGroupBox(self.tr("Filters"), self)
        self.filterForm = QGridLayout(self)
        self.filterBox.setLayout(self.filterForm)

        self.incNovel = QSwitch(width=2 * sPx, height=sPx)
        self.incNovel.setChecked(
            self.optState.getBool("GuiWritingStats", "incNovel", True))
        self.incNovel.clicked.connect(self._updateListBox)

        self.incNotes = QSwitch(width=2 * sPx, height=sPx)
        self.incNotes.setChecked(
            self.optState.getBool("GuiWritingStats", "incNotes", True))
        self.incNotes.clicked.connect(self._updateListBox)

        self.hideZeros = QSwitch(width=2 * sPx, height=sPx)
        self.hideZeros.setChecked(
            self.optState.getBool("GuiWritingStats", "hideZeros", True))
        self.hideZeros.clicked.connect(self._updateListBox)

        self.hideNegative = QSwitch(width=2 * sPx, height=sPx)
        self.hideNegative.setChecked(
            self.optState.getBool("GuiWritingStats", "hideNegative", False))
        self.hideNegative.clicked.connect(self._updateListBox)

        self.groupByDay = QSwitch(width=2 * sPx, height=sPx)
        self.groupByDay.setChecked(
            self.optState.getBool("GuiWritingStats", "groupByDay", False))
        self.groupByDay.clicked.connect(self._updateListBox)

        self.showIdleTime = QSwitch(width=2 * sPx, height=sPx)
        self.showIdleTime.setChecked(
            self.optState.getBool("GuiWritingStats", "showIdleTime", False))
        self.showIdleTime.clicked.connect(self._updateListBox)

        self.filterForm.addWidget(QLabel(self.tr("Count novel files")), 0, 0)
        self.filterForm.addWidget(QLabel(self.tr("Count note files")), 1, 0)
        self.filterForm.addWidget(QLabel(self.tr("Hide zero word count")), 2,
                                  0)
        self.filterForm.addWidget(QLabel(self.tr("Hide negative word count")),
                                  3, 0)
        self.filterForm.addWidget(QLabel(self.tr("Group entries by day")), 4,
                                  0)
        self.filterForm.addWidget(QLabel(self.tr("Show idle time")), 5, 0)
        self.filterForm.addWidget(self.incNovel, 0, 1)
        self.filterForm.addWidget(self.incNotes, 1, 1)
        self.filterForm.addWidget(self.hideZeros, 2, 1)
        self.filterForm.addWidget(self.hideNegative, 3, 1)
        self.filterForm.addWidget(self.groupByDay, 4, 1)
        self.filterForm.addWidget(self.showIdleTime, 5, 1)
        self.filterForm.setRowStretch(6, 1)

        # Settings
        self.histMax = QSpinBox(self)
        self.histMax.setMinimum(100)
        self.histMax.setMaximum(100000)
        self.histMax.setSingleStep(100)
        self.histMax.setValue(
            self.optState.getInt("GuiWritingStats", "histMax", 2000))
        self.histMax.valueChanged.connect(self._updateListBox)

        self.optsBox = QHBoxLayout()
        self.optsBox.addStretch(1)
        self.optsBox.addWidget(
            QLabel(self.tr("Word count cap for the histogram")), 0)
        self.optsBox.addWidget(self.histMax, 0)

        # Buttons
        self.buttonBox = QDialogButtonBox()
        self.buttonBox.rejected.connect(self._doClose)

        self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
        self.btnClose.setAutoDefault(False)

        self.btnSave = self.buttonBox.addButton(self.tr("Save As"),
                                                QDialogButtonBox.ActionRole)
        self.btnSave.setAutoDefault(False)

        self.saveMenu = QMenu(self)
        self.btnSave.setMenu(self.saveMenu)

        self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self)
        self.saveJSON.triggered.connect(lambda: self._saveData(self.FMT_JSON))
        self.saveMenu.addAction(self.saveJSON)

        self.saveCSV = QAction(self.tr("CSV Data File (.csv)"), self)
        self.saveCSV.triggered.connect(lambda: self._saveData(self.FMT_CSV))
        self.saveMenu.addAction(self.saveCSV)

        # Assemble
        self.outerBox = QGridLayout()
        self.outerBox.addWidget(self.listBox, 0, 0, 1, 2)
        self.outerBox.addLayout(self.optsBox, 1, 0, 1, 2)
        self.outerBox.addWidget(self.infoBox, 2, 0)
        self.outerBox.addWidget(self.filterBox, 2, 1)
        self.outerBox.addWidget(self.buttonBox, 3, 0, 1, 2)
        self.outerBox.setRowStretch(0, 1)

        self.setLayout(self.outerBox)

        logger.debug("GuiWritingStats initialisation complete")

        return

    def populateGUI(self):
        """Populate list box with data from the log file.
        """
        qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
        self._loadLogFile()
        self._updateListBox()
        qApp.restoreOverrideCursor()
        return

    ##
    #  Slots
    ##

    def _doClose(self):
        """Save the state of the window, clear cache, end close.
        """
        self.logData = []

        winWidth = self.mainConf.rpxInt(self.width())
        winHeight = self.mainConf.rpxInt(self.height())
        widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0))
        widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1))
        widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
        widthCol3 = self.mainConf.rpxInt(self.listBox.columnWidth(3))
        sortCol = self.listBox.sortColumn()
        sortOrder = self.listBox.header().sortIndicatorOrder()
        incNovel = self.incNovel.isChecked()
        incNotes = self.incNotes.isChecked()
        hideZeros = self.hideZeros.isChecked()
        hideNegative = self.hideNegative.isChecked()
        groupByDay = self.groupByDay.isChecked()
        showIdleTime = self.showIdleTime.isChecked()
        histMax = self.histMax.value()

        self.optState.setValue("GuiWritingStats", "winWidth", winWidth)
        self.optState.setValue("GuiWritingStats", "winHeight", winHeight)
        self.optState.setValue("GuiWritingStats", "widthCol0", widthCol0)
        self.optState.setValue("GuiWritingStats", "widthCol1", widthCol1)
        self.optState.setValue("GuiWritingStats", "widthCol2", widthCol2)
        self.optState.setValue("GuiWritingStats", "widthCol3", widthCol3)
        self.optState.setValue("GuiWritingStats", "sortCol", sortCol)
        self.optState.setValue("GuiWritingStats", "sortOrder", sortOrder)
        self.optState.setValue("GuiWritingStats", "incNovel", incNovel)
        self.optState.setValue("GuiWritingStats", "incNotes", incNotes)
        self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros)
        self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative)
        self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay)
        self.optState.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
        self.optState.setValue("GuiWritingStats", "histMax", histMax)

        self.optState.saveSettings()
        self.close()

        return

    def _saveData(self, dataFmt):
        """Save the content of the list box to a file.
        """
        fileExt = ""
        textFmt = ""

        if dataFmt == self.FMT_JSON:
            fileExt = "json"
            textFmt = self.tr("JSON Data File")
        elif dataFmt == self.FMT_CSV:
            fileExt = "csv"
            textFmt = self.tr("CSV Data File")
        else:
            return False

        # Generate the file name
        saveDir = self.mainConf.lastPath
        if not os.path.isdir(saveDir):
            saveDir = os.path.expanduser("~")

        fileName = "sessionStats.%s" % fileExt
        savePath = os.path.join(saveDir, fileName)

        savePath, _ = QFileDialog.getSaveFileName(
            self, self.tr("Save Data As"), savePath,
            "%s (*.%s)" % (textFmt, fileExt))
        if not savePath:
            return False

        self.mainConf.setLastPath(savePath)

        # Do the actual writing
        wSuccess = False
        errMsg = ""

        try:
            with open(savePath, mode="w", encoding="utf-8") as outFile:
                if dataFmt == self.FMT_JSON:
                    jsonData = []
                    for _, sD, tT, wD, wA, wB, tI in self.filterData:
                        jsonData.append({
                            "date": sD,
                            "length": tT,
                            "newWords": wD,
                            "novelWords": wA,
                            "noteWords": wB,
                            "idleTime": tI,
                        })
                    json.dump(jsonData, outFile, indent=2)
                    wSuccess = True

                if dataFmt == self.FMT_CSV:
                    outFile.write(
                        '"Date","Length (sec)","Words Changed",'
                        '"Novel Words","Note Words","Idle Time (sec)"\n')
                    for _, sD, tT, wD, wA, wB, tI in self.filterData:
                        outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB},{tI}\n')
                    wSuccess = True

        except Exception as exc:
            errMsg = formatException(exc)
            wSuccess = False

        # Report to user
        if wSuccess:
            self.theParent.makeAlert([
                self.tr("{0} file successfully written to:").format(textFmt),
                savePath
            ], nwAlert.INFO)
        else:
            self.theParent.makeAlert(
                [self.tr("Failed to write {0} file.").format(textFmt), errMsg],
                nwAlert.ERROR)

        return wSuccess

    ##
    #  Internal Functions
    ##

    def _loadLogFile(self):
        """Load the content of the log file into a buffer.
        """
        logger.debug("Loading session log file")

        self.logData = []
        self.wordOffset = 0

        ttNovel = 0
        ttNotes = 0
        ttTime = 0
        ttIdle = 0

        logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
        if not os.path.isfile(logFile):
            logger.info("This project has no writing stats logfile")
            return False

        try:
            with open(logFile, mode="r", encoding="utf-8") as inFile:
                for inLine in inFile:
                    if inLine.startswith("#"):
                        if inLine.startswith("# Offset"):
                            self.wordOffset = checkInt(inLine[9:].strip(), 0)
                            logger.verbose(
                                "Initial word count when log was started is %d"
                                % self.wordOffset)
                        continue

                    inData = inLine.split()
                    if len(inData) < 6:
                        continue

                    dStart = datetime.fromisoformat(" ".join(inData[0:2]))
                    dEnd = datetime.fromisoformat(" ".join(inData[2:4]))

                    sIdle = 0
                    if len(inData) > 6:
                        sIdle = checkInt(inData[6], 0)

                    tDiff = dEnd - dStart
                    sDiff = tDiff.total_seconds()
                    ttTime += sDiff
                    ttIdle += sIdle

                    wcNovel = int(inData[4])
                    wcNotes = int(inData[5])
                    ttNovel = wcNovel
                    ttNotes = wcNotes

                    self.logData.append(
                        (dStart, sDiff, wcNovel, wcNotes, sIdle))

        except Exception as exc:
            self.theParent.makeAlert(
                self.tr("Failed to read session log file."),
                nwAlert.ERROR,
                exception=exc)
            return False

        ttWords = ttNovel + ttNotes
        self.labelTotal.setText(formatTime(round(ttTime)))
        self.labelIdleT.setText(formatTime(round(ttIdle)))
        self.novelWords.setText(f"{ttNovel:n}")
        self.notesWords.setText(f"{ttNotes:n}")
        self.totalWords.setText(f"{ttWords:n}")

        return True

    ##
    #  Slots
    ##

    def _updateListBox(self):
        """Load/reload the content of the list box.
        """
        self.listBox.clear()
        self.timeFilter = 0.0

        incNovel = self.incNovel.isChecked()
        incNotes = self.incNotes.isChecked()
        hideZeros = self.hideZeros.isChecked()
        hideNegative = self.hideNegative.isChecked()
        groupByDay = self.groupByDay.isChecked()
        histMax = self.histMax.value()

        # Group the data
        if groupByDay:
            tempData = []
            sessDate = None
            sessTime = 0
            sessIdle = 0
            lstNovel = 0
            lstNotes = 0

            for n, (dStart, sDiff, wcNovel, wcNotes,
                    sIdle) in enumerate(self.logData):
                if n == 0:
                    sessDate = dStart.date()
                if sessDate != dStart.date():
                    tempData.append(
                        (sessDate, sessTime, lstNovel, lstNotes, sessIdle))
                    sessDate = dStart.date()
                    sessTime = sDiff
                    sessIdle = sIdle
                    lstNovel = wcNovel
                    lstNotes = wcNotes
                else:
                    sessTime += sDiff
                    sessIdle += sIdle
                    lstNovel = wcNovel
                    lstNotes = wcNotes

            if sessDate is not None:
                tempData.append(
                    (sessDate, sessTime, lstNovel, lstNotes, sessIdle))

        else:
            tempData = self.logData

        # Calculate Word Diff
        self.filterData = []
        pcTotal = 0
        listMax = 0
        isFirst = True
        for dStart, sDiff, wcNovel, wcNotes, sIdle in tempData:

            wcTotal = 0
            if incNovel:
                wcTotal += wcNovel
            if incNotes:
                wcTotal += wcNotes

            dwTotal = wcTotal - pcTotal
            if hideZeros and dwTotal == 0:
                continue
            if hideNegative and dwTotal < 0:
                pcTotal = wcTotal
                continue

            if isFirst:
                # Subtract the offset from the first list entry
                dwTotal -= self.wordOffset
                dwTotal = max(dwTotal, 1)  # Don't go zero or negative
                isFirst = False

            if groupByDay:
                sStart = dStart.strftime(nwConst.FMT_DSTAMP)
            else:
                sStart = dStart.strftime(nwConst.FMT_TSTAMP)

            self.filterData.append(
                (dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes, sIdle))
            listMax = min(max(listMax, dwTotal), histMax)
            pcTotal = wcTotal

        # Populate the list
        showIdleTime = self.showIdleTime.isChecked()
        for _, sStart, sDiff, nWords, _, _, sIdle in self.filterData:

            if showIdleTime:
                idleEntry = formatTime(sIdle)
            else:
                sRatio = sIdle / sDiff if sDiff > 0.0 else 0.0
                idleEntry = "%d %%" % round(100.0 * sRatio)

            newItem = QTreeWidgetItem()
            newItem.setText(self.C_TIME, sStart)
            newItem.setText(self.C_LENGTH, formatTime(round(sDiff)))
            newItem.setText(self.C_IDLE, idleEntry)
            newItem.setText(self.C_COUNT, f"{nWords:n}")

            if nWords > 0 and listMax > 0:
                theBar = self.barImage.scaled(
                    int(200 * min(nWords, histMax) / listMax), self.barHeight,
                    Qt.IgnoreAspectRatio, Qt.FastTransformation)
                newItem.setData(self.C_BAR, Qt.DecorationRole, theBar)

            newItem.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
            newItem.setTextAlignment(self.C_IDLE, Qt.AlignRight)
            newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
            newItem.setTextAlignment(self.C_BAR,
                                     Qt.AlignLeft | Qt.AlignVCenter)

            newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed)
            newItem.setFont(self.C_LENGTH, self.theTheme.guiFontFixed)
            newItem.setFont(self.C_COUNT, self.theTheme.guiFontFixed)
            if showIdleTime:
                newItem.setFont(self.C_IDLE, self.theTheme.guiFontFixed)
            else:
                newItem.setFont(self.C_IDLE, self.theTheme.guiFont)

            self.listBox.addTopLevelItem(newItem)
            self.timeFilter += sDiff

        self.labelFilter.setText(formatTime(round(self.timeFilter)))

        return True
Beispiel #10
0
class GuiLipsum(QDialog):
    def __init__(self, mainGui):
        QDialog.__init__(self, mainGui)

        logger.debug("Initialising GuiLipsum ...")
        self.setObjectName("GuiLipsum")

        self.mainConf = novelwriter.CONFIG
        self.mainGui = mainGui
        self.mainTheme = mainGui.mainTheme

        self.setWindowTitle(self.tr("Insert Placeholder Text"))

        self.innerBox = QHBoxLayout()
        self.innerBox.setSpacing(self.mainConf.pxInt(16))

        # Icon
        nPx = self.mainConf.pxInt(64)
        vSp = self.mainConf.pxInt(4)
        self.docIcon = QLabel()
        self.docIcon.setPixmap(
            self.mainTheme.getPixmap("proj_document", (nPx, nPx)))

        self.leftBox = QVBoxLayout()
        self.leftBox.setSpacing(vSp)
        self.leftBox.addWidget(self.docIcon)
        self.leftBox.addStretch(1)
        self.innerBox.addLayout(self.leftBox)

        # Form
        self.headLabel = QLabel("<b>{0}</b>".format(
            self.tr("Insert Lorem Ipsum Text")))

        self.paraLabel = QLabel(self.tr("Number of paragraphs"))
        self.paraCount = QSpinBox()
        self.paraCount.setMinimum(1)
        self.paraCount.setMaximum(100)
        self.paraCount.setValue(5)

        self.randLabel = QLabel(self.tr("Randomise order"))
        self.randSwitch = QSwitch()

        self.formBox = QGridLayout()
        self.formBox.addWidget(self.headLabel, 0, 0, 1, 2, Qt.AlignLeft)
        self.formBox.addWidget(self.paraLabel, 1, 0, 1, 1, Qt.AlignLeft)
        self.formBox.addWidget(self.paraCount, 1, 1, 1, 1, Qt.AlignRight)
        self.formBox.addWidget(self.randLabel, 2, 0, 1, 1, Qt.AlignLeft)
        self.formBox.addWidget(self.randSwitch, 2, 1, 1, 1, Qt.AlignRight)
        self.formBox.setVerticalSpacing(vSp)
        self.formBox.setRowStretch(3, 1)
        self.innerBox.addLayout(self.formBox)

        # Buttons
        self.buttonBox = QDialogButtonBox()
        self.buttonBox.rejected.connect(self._doClose)

        self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close)
        self.btnClose.setAutoDefault(False)

        self.btnSave = self.buttonBox.addButton(self.tr("Insert"),
                                                QDialogButtonBox.ActionRole)
        self.btnSave.clicked.connect(self._doInsert)
        self.btnSave.setAutoDefault(False)

        # Assemble
        self.outerBox = QVBoxLayout()
        self.outerBox.addLayout(self.innerBox)
        self.outerBox.addWidget(self.buttonBox)
        self.outerBox.setSpacing(self.mainConf.pxInt(16))
        self.setLayout(self.outerBox)

        logger.debug("GuiLipsum initialisation complete")

        return

    ##
    #  Slots
    ##

    def _doInsert(self):
        """Load the text and insert it in the open document.
        """
        lipsumFile = os.path.join(self.mainConf.assetPath, "text",
                                  "lipsum.txt")
        lipsumText = readTextFile(lipsumFile).splitlines()

        if self.randSwitch.isChecked():
            random.shuffle(lipsumText)

        pCount = self.paraCount.value()
        inText = "\n\n".join(lipsumText[0:pCount]) + "\n\n"

        self.mainGui.docEditor.insertText(inText)

        return

    def _doClose(self):
        """Close the dialog window without doing anything.
        """
        self.close()
        return
Beispiel #11
0
class GuiItemEditor(QDialog):
    def __init__(self, theParent, tHandle):
        QDialog.__init__(self, theParent)

        logger.debug("Initialising GuiItemEditor ...")
        self.setObjectName("GuiItemEditor")

        self.mainConf = novelwriter.CONFIG
        self.theParent = theParent
        self.theProject = theParent.theProject

        ##
        #  Build GUI
        ##

        self.theItem = self.theProject.projTree[tHandle]
        if self.theItem is None:
            self.close()
            return

        self.setWindowTitle(self.tr("Item Settings"))

        mVd = self.mainConf.pxInt(220)
        mSp = self.mainConf.pxInt(16)
        vSp = self.mainConf.pxInt(4)

        # Item Label
        self.editName = QLineEdit()
        self.editName.setMinimumWidth(mVd)
        self.editName.setMaxLength(200)

        # Item Status
        self.editStatus = QComboBox()
        self.editStatus.setMinimumWidth(mVd)
        if self.theItem.itemClass in nwLists.CLS_NOVEL:
            for sLabel, _, _, sIcon in self.theProject.statusItems:
                self.editStatus.addItem(sIcon, sLabel, sLabel)
        else:
            for sLabel, _, _, sIcon in self.theProject.importItems:
                self.editStatus.addItem(sIcon, sLabel, sLabel)

        # Item Layout
        self.editLayout = QComboBox()
        self.editLayout.setMinimumWidth(mVd)
        validLayouts = []
        if self.theItem.itemType == nwItemType.FILE:
            if self.theItem.itemClass in nwLists.CLS_NOVEL:
                validLayouts.append(nwItemLayout.DOCUMENT)
            validLayouts.append(nwItemLayout.NOTE)
        else:
            validLayouts.append(nwItemLayout.NO_LAYOUT)
            self.editLayout.setEnabled(False)

        for itemLayout in nwItemLayout:
            if itemLayout in validLayouts:
                self.editLayout.addItem(
                    trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)

        # Export Switch
        self.textExport = QLabel(self.tr("Include when building project"))
        self.editExport = QSwitch()
        if self.theItem.itemType == nwItemType.FILE:
            self.editExport.setEnabled(True)
            self.editExport.setChecked(self.theItem.isExported)
        else:
            self.editExport.setEnabled(False)
            self.editExport.setChecked(False)

        # Buttons
        self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok
                                          | QDialogButtonBox.Cancel)
        self.buttonBox.accepted.connect(self._doSave)
        self.buttonBox.rejected.connect(self._doClose)

        # Set Current Values
        self.editName.setText(self.theItem.itemName)
        self.editName.selectAll()

        currStatus, _ = self.theItem.getImportStatus()
        statusIdx = self.editStatus.findData(currStatus)
        if statusIdx != -1:
            self.editStatus.setCurrentIndex(statusIdx)

        layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
        if layoutIdx != -1:
            self.editLayout.setCurrentIndex(layoutIdx)

        ##
        #  Assemble
        ##

        nameLabel = QLabel(self.tr("Label"))
        statusLabel = QLabel(self.tr("Status"))
        layoutLabel = QLabel(self.tr("Layout"))

        self.mainForm = QGridLayout()
        self.mainForm.setVerticalSpacing(vSp)
        self.mainForm.setHorizontalSpacing(mSp)
        self.mainForm.addWidget(nameLabel, 0, 0, 1, 1)
        self.mainForm.addWidget(self.editName, 0, 1, 1, 2)
        self.mainForm.addWidget(statusLabel, 1, 0, 1, 1)
        self.mainForm.addWidget(self.editStatus, 1, 1, 1, 2)
        self.mainForm.addWidget(layoutLabel, 2, 0, 1, 1)
        self.mainForm.addWidget(self.editLayout, 2, 1, 1, 2)
        self.mainForm.addWidget(self.textExport, 3, 0, 1, 2)
        self.mainForm.addWidget(self.editExport, 3, 2, 1, 1)
        self.mainForm.setColumnStretch(0, 0)
        self.mainForm.setColumnStretch(1, 1)
        self.mainForm.setColumnStretch(2, 0)

        self.outerBox = QVBoxLayout()
        self.outerBox.setSpacing(mSp)
        self.outerBox.addLayout(self.mainForm)
        self.outerBox.addStretch(1)
        self.outerBox.addWidget(self.buttonBox)
        self.setLayout(self.outerBox)

        self.rejected.connect(self._doClose)

        logger.debug("GuiItemEditor initialisation complete")

        return

    ##
    #  Slots
    ##

    @pyqtSlot()
    def _doSave(self):
        """Save the setting to the item.
        """
        logger.verbose("ItemEditor save button clicked")

        itemName = self.editName.text()
        itemStatus = self.editStatus.currentData()
        itemLayout = self.editLayout.currentData()
        isExported = self.editExport.isChecked()

        self.theItem.setName(itemName)
        self.theItem.setImportStatus(itemStatus)
        self.theItem.setLayout(itemLayout)
        self.theItem.setExported(isExported)

        self.theProject.setProjectChanged(True)

        self.accept()
        self.close()

        return

    @pyqtSlot()
    def _doClose(self):
        """Close the dialog without saving the settings.
        """
        logger.verbose("ItemEditor cancel button clicked")
        self.close()
        return
Beispiel #12
0
class GuiProjectDetailsContents(QWidget):

    C_TITLE = 0
    C_WORDS = 1
    C_PAGES = 2
    C_PAGE = 3
    C_PROG = 4

    def __init__(self, theParent, theProject):
        QWidget.__init__(self, theParent)

        self.mainConf = novelwriter.CONFIG
        self.theParent = theParent
        self.theProject = theProject
        self.theTheme = theParent.theTheme
        self.theIndex = theParent.theIndex
        self.optState = theProject.optState

        # Internal
        self._theToC = []

        iPx = self.theTheme.baseIconSize
        hPx = self.mainConf.pxInt(12)
        vPx = self.mainConf.pxInt(4)

        # Contents Tree
        # =============

        self.tocTree = QTreeWidget()
        self.tocTree.setIconSize(QSize(iPx, iPx))
        self.tocTree.setIndentation(0)
        self.tocTree.setColumnCount(6)
        self.tocTree.setSelectionMode(QAbstractItemView.NoSelection)
        self.tocTree.setHeaderLabels([
            self.tr("Title"),
            self.tr("Words"),
            self.tr("Pages"),
            self.tr("Page"),
            self.tr("Progress"), ""
        ])

        treeHeadItem = self.tocTree.headerItem()
        treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
        treeHeadItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
        treeHeadItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
        treeHeadItem.setTextAlignment(self.C_PROG, Qt.AlignRight)

        treeHeader = self.tocTree.header()
        treeHeader.setStretchLastSection(True)
        treeHeader.setMinimumSectionSize(hPx)

        wCol0 = self.mainConf.pxInt(
            self.optState.getInt("GuiProjectDetails", "widthCol0", 200))
        wCol1 = self.mainConf.pxInt(
            self.optState.getInt("GuiProjectDetails", "widthCol1", 60))
        wCol2 = self.mainConf.pxInt(
            self.optState.getInt("GuiProjectDetails", "widthCol2", 60))
        wCol3 = self.mainConf.pxInt(
            self.optState.getInt("GuiProjectDetails", "widthCol3", 60))
        wCol4 = self.mainConf.pxInt(
            self.optState.getInt("GuiProjectDetails", "widthCol4", 90))

        self.tocTree.setColumnWidth(0, wCol0)
        self.tocTree.setColumnWidth(1, wCol1)
        self.tocTree.setColumnWidth(2, wCol2)
        self.tocTree.setColumnWidth(3, wCol3)
        self.tocTree.setColumnWidth(4, wCol4)
        self.tocTree.setColumnWidth(5, hPx)

        # Options
        # =======

        wordsPerPage = self.optState.getInt("GuiProjectDetails",
                                            "wordsPerPage", 350)
        countFrom = self.optState.getInt("GuiProjectDetails", "countFrom", 1)
        clearDouble = self.optState.getInt("GuiProjectDetails", "clearDouble",
                                           True)

        wordsHelp = (self.tr(
            "Typical word count for a 5 by 8 inch book page with 11 pt font is 350."
        ))
        offsetHelp = (self.tr("Start counting page numbers from this page."))
        dblHelp = (self.tr(
            "Assume a new chapter or partition always start on an odd numbered page."
        ))

        self.wpLabel = QLabel(self.tr("Words per page"))
        self.wpLabel.setToolTip(wordsHelp)

        self.wpValue = QSpinBox()
        self.wpValue.setMinimum(10)
        self.wpValue.setMaximum(1000)
        self.wpValue.setSingleStep(10)
        self.wpValue.setValue(wordsPerPage)
        self.wpValue.setToolTip(wordsHelp)
        self.wpValue.valueChanged.connect(self._populateTree)

        self.poLabel = QLabel(self.tr("Count pages from"))
        self.poLabel.setToolTip(offsetHelp)

        self.poValue = QSpinBox()
        self.poValue.setMinimum(1)
        self.poValue.setMaximum(9999)
        self.poValue.setSingleStep(1)
        self.poValue.setValue(countFrom)
        self.poValue.setToolTip(offsetHelp)
        self.poValue.valueChanged.connect(self._populateTree)

        self.dblLabel = QLabel(self.tr("Clear double pages"))
        self.dblLabel.setToolTip(dblHelp)

        self.dblValue = QSwitch(self, 2 * iPx, iPx)
        self.dblValue.setChecked(clearDouble)
        self.dblValue.setToolTip(dblHelp)
        self.dblValue.clicked.connect(self._populateTree)

        self.optionsBox = QGridLayout()
        self.optionsBox.addWidget(self.wpLabel, 0, 0)
        self.optionsBox.addWidget(self.wpValue, 0, 1)
        self.optionsBox.addWidget(self.dblLabel, 0, 3)
        self.optionsBox.addWidget(self.dblValue, 0, 4)
        self.optionsBox.addWidget(self.poLabel, 1, 0)
        self.optionsBox.addWidget(self.poValue, 1, 1)
        self.optionsBox.setHorizontalSpacing(hPx)
        self.optionsBox.setVerticalSpacing(vPx)
        self.optionsBox.setColumnStretch(2, 1)

        # Assemble
        # ========

        self.outerBox = QVBoxLayout()
        self.outerBox.addWidget(
            QLabel("<b>%s</b>" % self.tr("Table of Contents")))
        self.outerBox.addWidget(self.tocTree)
        self.outerBox.addLayout(self.optionsBox)

        self.setLayout(self.outerBox)

        return

    def getColumnSizes(self):
        """Return the column widths for the tree columns.
        """
        retVals = [
            self.tocTree.columnWidth(0),
            self.tocTree.columnWidth(1),
            self.tocTree.columnWidth(2),
            self.tocTree.columnWidth(3),
            self.tocTree.columnWidth(4),
        ]
        return retVals

    def updateValues(self):
        """Populate the tree.
        """
        self._prepareData()
        self._populateTree()
        return

    ##
    #  Internal Functions
    ##

    def _prepareData(self):
        """Extract the data for the tree.
        """
        self._theToC = []
        self._theToC = self.theIndex.getTableOfContents(2)
        self._theToC.append(("", 0, self.tr("END"), 0))
        return

    ##
    #  Slots
    ##

    def _populateTree(self):
        """Set the content of the chapter/page tree.
        """
        dblPages = self.dblValue.isChecked()
        wpPage = self.wpValue.value()
        fstPage = self.poValue.value() - 1

        pTotal = 0
        tPages = 1

        theList = []
        for _, tLevel, tTitle, wCount in self._theToC:
            pCount = math.ceil(wCount / wpPage)
            if dblPages:
                pCount += pCount % 2

            pTotal += pCount
            theList.append((tLevel, tTitle, wCount, pCount))

        pMax = pTotal - fstPage

        self.tocTree.clear()
        for tLevel, tTitle, wCount, pCount in theList:
            newItem = QTreeWidgetItem()

            if tPages <= fstPage:
                progPage = numberToRoman(tPages, True)
                progText = ""
            else:
                cPage = tPages - fstPage
                pgProg = 100.0 * (cPage - 1) / pMax if pMax > 0 else 0.0
                progPage = f"{cPage:n}"
                progText = f"{pgProg:.1f}{nwUnicode.U_THSP}%"

            if tTitle.strip() == "":
                tTitle = self.tr("Untitled")

            newItem.setIcon(self.C_TITLE,
                            self.theTheme.getIcon("doc_h%d" % tLevel))
            newItem.setText(self.C_TITLE, tTitle)
            newItem.setText(self.C_WORDS, f"{wCount:n}")
            newItem.setText(self.C_PAGES, f"{pCount:n}")
            newItem.setText(self.C_PAGE, progPage)
            newItem.setText(self.C_PROG, progText)

            newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
            newItem.setTextAlignment(self.C_PAGES, Qt.AlignRight)
            newItem.setTextAlignment(self.C_PAGE, Qt.AlignRight)
            newItem.setTextAlignment(self.C_PROG, Qt.AlignRight)

            # Make pages and titles/partitions stand out
            if tLevel < 2:
                bFont = newItem.font(self.C_TITLE)
                if tLevel == 0:
                    bFont.setItalic(True)
                else:
                    bFont.setBold(True)
                    bFont.setUnderline(True)
                newItem.setFont(self.C_TITLE, bFont)

            tPages += pCount

            self.tocTree.addTopLevelItem(newItem)

        return