예제 #1
0
    def unpackXML(self, xParent):
        """Unpack an XML tree and set the class values.
        """
        theLabels = []
        theColours = []

        for xChild in xParent:
            theLabels.append(xChild.text)
            cR = checkInt(xChild.attrib.get("red", 0), 0, False)
            cG = checkInt(xChild.attrib.get("green", 0), 0, False)
            cB = checkInt(xChild.attrib.get("blue", 0), 0, False)
            theColours.append((cR, cG, cB))

        if len(theLabels) > 0:
            self._theLabels = []
            self._theColours = []
            self._theCounts = []
            self._theIcons = []
            self._theMap = {}
            self._theLength = 0
            self._theIndex = 0

            for n in range(len(theLabels)):
                self.addEntry(theLabels[n], theColours[n])

        return True
예제 #2
0
 def setCounts(self, charCount, wordCount, paraCount):
     """Set the character, word and paragraph count. Make sure the
     value is an integer and is not smaller than 0.
     """
     self._charCount = max(0, checkInt(charCount, 0))
     self._wordCount = max(0, checkInt(wordCount, 0))
     self._paraCount = max(0, checkInt(paraCount, 0))
     return
예제 #3
0
    def showItem(self, tHandle, sTitle):
        """Update the content of the tree with the given handle and line
        number pointing to a header.
        """
        pIndex = self.theProject.index
        nwItem = self.theProject.tree[tHandle]
        novIdx = pIndex.getNovelData(tHandle, sTitle)
        theRefs = pIndex.getReferences(tHandle, sTitle)
        if nwItem is None or novIdx is None:
            return False

        if novIdx.level in self.LVL_MAP:
            self.titleLabel.setText("<b>%s</b>" %
                                    self.tr(self.LVL_MAP[novIdx.level]))
        else:
            self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
        self.titleValue.setText(novIdx.title)

        itemStatus, _ = nwItem.getImportStatus()

        self.fileValue.setText(nwItem.itemName)
        self.itemValue.setText(itemStatus)

        cC = checkInt(novIdx.charCount, 0)
        wC = checkInt(novIdx.wordCount, 0)
        pC = checkInt(novIdx.paraCount, 0)

        self.cCValue.setText(f"{cC:n}")
        self.wCValue.setText(f"{wC:n}")
        self.pCValue.setText(f"{pC:n}")

        self.synopValue.setText(novIdx.synopsis)

        self.povKeyValue.setText(self._formatTags(theRefs, nwKeyWords.POV_KEY))
        self.focKeyValue.setText(
            self._formatTags(theRefs, nwKeyWords.FOCUS_KEY))
        self.chrKeyValue.setText(self._formatTags(theRefs,
                                                  nwKeyWords.CHAR_KEY))
        self.pltKeyValue.setText(self._formatTags(theRefs,
                                                  nwKeyWords.PLOT_KEY))
        self.timKeyValue.setText(self._formatTags(theRefs,
                                                  nwKeyWords.TIME_KEY))
        self.wldKeyValue.setText(
            self._formatTags(theRefs, nwKeyWords.WORLD_KEY))
        self.objKeyValue.setText(
            self._formatTags(theRefs, nwKeyWords.OBJECT_KEY))
        self.entKeyValue.setText(
            self._formatTags(theRefs, nwKeyWords.ENTITY_KEY))
        self.cstKeyValue.setText(
            self._formatTags(theRefs, nwKeyWords.CUSTOM_KEY))

        return True
예제 #4
0
 def setOrder(self, order):
     """Set the item order, and ensure that it is valid. This value
     is purely a meta value, and not actually used by novelWriter at
     the moment.
     """
     self._order = checkInt(order, 0)
     return
예제 #5
0
 def getInt(self, group, name, default):
     """Return the value as an int, if it exists. Otherwise, return
     the default value.
     """
     if group in self._theState:
         return checkInt(self._theState[group].get(name, default), default)
     return default
예제 #6
0
    def unpackXML(self, xParent):
        """Unpack an XML tree and set the class values.
        """
        self._store = {}
        self._reverse = {}
        self._default = None

        for xChild in xParent:
            key = xChild.attrib.get("key", None)
            name = xChild.text.strip()
            count = max(checkInt(xChild.attrib.get("count", 0), 0), 0)
            red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
            green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0,
                           255)
            blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0,
                          255)
            self.write(key, name, (red, green, blue), count)

        return True
예제 #7
0
    def getSelectedHandle(self):
        """Get the currently selected handle. If multiple items are
        selected, return the first.
        """
        selItem = self.selectedItems()
        tHandle = None
        tLine = 0
        if selItem:
            tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE)
            sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE)
            tLine = checkInt(sTitle[1:], 1) - 1

        return tHandle, tLine
예제 #8
0
    def getSelectedHandle(self):
        """Get the currently selected handle. If multiple items are
        selected, return the first.
        """
        selItem = self.selectedItems()
        tHandle = None
        tLine = 0
        if selItem:
            tHandle = selItem[0].data(self.C_TITLE, Qt.UserRole)[0]
            tLine = checkInt(selItem[0].data(self.C_TITLE, Qt.UserRole)[1],
                             1) - 1

        return tHandle, tLine
예제 #9
0
    def getSelectedHandle(self):
        """Get the currently selected handle. If multiple items are
        selected, return the first.
        """
        selItem = self.selectedItems()
        tHandle = None
        tLine = 0
        if selItem:
            tHandle = selItem[0].data(self._colIdx[nwOutline.TITLE],
                                      Qt.UserRole)
            tLine = checkInt(selItem[0].text(self._colIdx[nwOutline.LINE]),
                             1) - 1

        return tHandle, tLine
예제 #10
0
def testBaseCommon_CheckInt():
    """Test the checkInt function.
    """
    assert checkInt(None, 3, True) is None
    assert checkInt("None", 3, True) is None
    assert checkInt(None, 3, False) == 3
    assert checkInt(1, 3, False) == 1
    assert checkInt(1.0, 3, False) == 1
    assert checkInt(True, 3, False) == 1
예제 #11
0
 def setParaCount(self, count):
     """Set the paragraph count, and ensure that it is an integer.
     """
     self._paraCount = max(0, checkInt(count, 0))
     return
예제 #12
0
 def setCursorPos(self, position):
     """Set the cursor position, and ensure that it is an integer.
     """
     self._cursorPos = max(0, checkInt(position, 0))
     return
예제 #13
0
 def setWordCount(self, count):
     """Set the word count, and ensure that it is an integer.
     """
     self._wordCount = max(0, checkInt(count, 0))
     return
예제 #14
0
 def setCharCount(self, count):
     """Set the character count, and ensure that it is an integer.
     """
     self._charCount = max(0, checkInt(count, 0))
     return
예제 #15
0
    def tokenizeText(self):
        """Scan the text for either lines starting with specific
        characters that indicate headers, comments, commands etc, or
        just contain plain text. In the case of plain text, apply the
        same RegExes that the syntax highlighter uses and save the
        locations of these formatting tags into the token array.

        The format of the token list is an entry with a five-tuple for
        each line in the file. The tuple is as follows:
          1: The type of the block, self.T_*
          2: The line in the file where this block occurred
          3: The text content of the block, without leading tags
          4: The internal formatting map of the text, self.FMT_*
          5: The style of the block, self.A_*
        """
        # RegExes for adding formatting tags within text lines
        rxFormats = [
            (QRegularExpression(nwRegEx.FMT_EI), [None, self.FMT_I_B, None, self.FMT_I_E]),
            (QRegularExpression(nwRegEx.FMT_EB), [None, self.FMT_B_B, None, self.FMT_B_E]),
            (QRegularExpression(nwRegEx.FMT_ST), [None, self.FMT_D_B, None, self.FMT_D_E]),
        ]

        self._theTokens = []
        tmpMarkdown = []
        nLine = 0
        breakNext = False
        for aLine in self._theText.splitlines():
            nLine += 1
            sLine = aLine.strip()

            # Check for blank lines
            if len(sLine) == 0:
                self._theTokens.append((
                    self.T_EMPTY, nLine, "", None, self.A_NONE
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("\n")

                continue

            if breakNext:
                sAlign = self.A_PBB
                breakNext = False
            else:
                sAlign = self.A_NONE

            # Check Line Format
            # =================

            if aLine[0] == "[":
                # Parse special formatting line

                if sLine in ("[NEWPAGE]", "[NEW PAGE]"):
                    breakNext = True
                    continue

                elif sLine == "[VSPACE]":
                    self._theTokens.append(
                        (self.T_SKIP, nLine, "", None, sAlign)
                    )
                    continue

                elif sLine.startswith("[VSPACE:") and sLine.endswith("]"):
                    nSkip = checkInt(sLine[8:-1], 0)
                    if nSkip >= 1:
                        self._theTokens.append(
                            (self.T_SKIP, nLine, "", None, sAlign)
                        )
                    if nSkip > 1:
                        self._theTokens += (nSkip - 1) * [
                            (self.T_SKIP, nLine, "", None, self.A_NONE)
                        ]
                    continue

            elif aLine[0] == "%":
                cLine = aLine[1:].lstrip()
                synTag = cLine[:9].lower()
                if synTag == "synopsis:":
                    self._theTokens.append((
                        self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, sAlign
                    ))
                    if self._doSynopsis and self._keepMarkdown:
                        tmpMarkdown.append("%s\n" % aLine)
                else:
                    self._theTokens.append((
                        self.T_COMMENT, nLine, aLine[1:].strip(), None, sAlign
                    ))
                    if self._doComments and self._keepMarkdown:
                        tmpMarkdown.append("%s\n" % aLine)

            elif aLine[0] == "@":
                self._theTokens.append((
                    self.T_KEYWORD, nLine, aLine[1:].strip(), None, sAlign
                ))
                if self._doKeywords and self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

            elif aLine[:2] == "# ":
                if self._isNovel:
                    sAlign |= self.A_CENTRE
                    sAlign |= self.A_PBB

                self._theTokens.append((
                    self.T_HEAD1, nLine, aLine[2:].strip(), None, sAlign
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

            elif aLine[:3] == "## ":
                if self._isNovel:
                    sAlign |= self.A_PBB

                self._theTokens.append((
                    self.T_HEAD2, nLine, aLine[3:].strip(), None, sAlign
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

            elif aLine[:4] == "### ":
                self._theTokens.append((
                    self.T_HEAD3, nLine, aLine[4:].strip(), None, sAlign
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

            elif aLine[:5] == "#### ":
                self._theTokens.append((
                    self.T_HEAD4, nLine, aLine[5:].strip(), None, sAlign
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

            elif aLine[:3] == "#! ":
                if self._isNovel:
                    tStyle = self.T_TITLE
                else:
                    tStyle = self.T_HEAD1

                self._theTokens.append((
                    tStyle, nLine, aLine[3:].strip(), None, sAlign | self.A_CENTRE
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

            elif aLine[:4] == "##! ":
                if self._isNovel:
                    tStyle = self.T_UNNUM
                    sAlign |= self.A_PBB
                else:
                    tStyle = self.T_HEAD2

                self._theTokens.append((
                    tStyle, nLine, aLine[4:].strip(), None, sAlign
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

            else:
                if not self._doBodyText:
                    # Skip all body text
                    continue

                # Check Alignment and Indentation
                alnLeft = False
                alnRight = False
                indLeft = False
                indRight = False
                if aLine.startswith(">>"):
                    alnRight = True
                    aLine = aLine[2:].lstrip(" ")
                elif aLine.startswith(">"):
                    indLeft = True
                    aLine = aLine[1:].lstrip(" ")

                if aLine.endswith("<<"):
                    alnLeft = True
                    aLine = aLine[:-2].rstrip(" ")
                elif aLine.endswith("<"):
                    indRight = True
                    aLine = aLine[:-1].rstrip(" ")

                if alnLeft and alnRight:
                    sAlign |= self.A_CENTRE
                elif alnLeft:
                    sAlign |= self.A_LEFT
                elif alnRight:
                    sAlign |= self.A_RIGHT

                if indLeft:
                    sAlign |= self.A_IND_L
                if indRight:
                    sAlign |= self.A_IND_R

                # Otherwise we use RegEx to find formatting tags within a line of text
                fmtPos = []
                for theRX, theKeys in rxFormats:
                    rxThis = theRX.globalMatch(aLine, 0)
                    while rxThis.hasNext():
                        rxMatch = rxThis.next()
                        for n in range(1, len(theKeys)):
                            if theKeys[n] is not None:
                                xPos = rxMatch.capturedStart(n)
                                xLen = rxMatch.capturedLength(n)
                                fmtPos.append([xPos, xLen, theKeys[n]])

                # Save the line as is, but append the array of formatting locations
                # sorted by position
                fmtPos = sorted(fmtPos, key=itemgetter(0))
                self._theTokens.append((
                    self.T_TEXT, nLine, aLine, fmtPos, sAlign
                ))
                if self._keepMarkdown:
                    tmpMarkdown.append("%s\n" % aLine)

        # If we have content, turn off the first page flag
        if self._isFirst and self._theTokens:
            self._isFirst = False

            # Make sure the token array doesn't start with a page break
            # on the very first page, adding a blank first page.
            if self._theTokens[0][4] & self.A_PBB:
                tToken = self._theTokens[0]
                self._theTokens[0] = (
                    tToken[0], tToken[1], tToken[2], tToken[3], tToken[4] & ~self.A_PBB
                )

        # Always add an empty line at the end of the file
        self._theTokens.append((
            self.T_EMPTY, nLine, "", None, self.A_NONE
        ))
        if self._keepMarkdown:
            tmpMarkdown.append("\n")

        if self._keepMarkdown:
            self._theMarkdown.append("".join(tmpMarkdown))

        # Second Pass
        # ===========
        # Some items need a second pass

        pToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
        nToken = (self.T_EMPTY, 0, "", None, self.A_NONE)
        tCount = len(self._theTokens)
        for n, tToken in enumerate(self._theTokens):

            if n > 0:
                pToken = self._theTokens[n-1]
            if n < tCount - 1:
                nToken = self._theTokens[n+1]

            if tToken[0] == self.T_KEYWORD:
                aStyle = tToken[4]
                if pToken[0] == self.T_KEYWORD:
                    aStyle |= self.A_Z_TOPMRG
                if nToken[0] == self.T_KEYWORD:
                    aStyle |= self.A_Z_BTMMRG
                self._theTokens[n] = (
                    tToken[0], tToken[1], tToken[2], tToken[3], aStyle
                )

        return
예제 #16
0
    def highlightBlock(self, theText):
        """Highlight a single block. Prefer to check first character for
        all formats that are defined by their initial characters. This
        is significantly faster than running the regex checks used for
        text paragraphs.
        """
        self.setCurrentBlockState(self.BLOCK_NONE)
        if self.theHandle is None or not theText:
            return

        if theText.startswith("@"):  # Keywords and commands
            self.setCurrentBlockState(self.BLOCK_META)
            pIndex = self.theProject.index
            tItem = self.mainGui.theProject.tree[self.theHandle]
            isValid, theBits, thePos = pIndex.scanThis(theText)
            isGood = pIndex.checkThese(theBits, tItem)
            if isValid:
                for n, theBit in enumerate(theBits):
                    xPos = thePos[n]
                    xLen = len(theBit)
                    if isGood[n]:
                        if n == 0:
                            self.setFormat(xPos, xLen, self.hStyles["keyword"])
                        else:
                            self.setFormat(xPos, xLen, self.hStyles["value"])
                    else:
                        kwFmt = self.format(xPos)
                        kwFmt.setUnderlineColor(self.colError)
                        kwFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
                        self.setFormat(xPos, xLen, kwFmt)

            # We never want to run the spell checker on keyword/values,
            # so we force a return here
            return

        elif theText.startswith(("# ", "#! ", "## ", "##! ", "### ", "#### ")):
            self.setCurrentBlockState(self.BLOCK_TITLE)

            if theText.startswith("# "):  # Header 1
                self.setFormat(0, 1, self.hStyles["header1h"])
                self.setFormat(1, len(theText), self.hStyles["header1"])

            elif theText.startswith("## "):  # Header 2
                self.setFormat(0, 2, self.hStyles["header2h"])
                self.setFormat(2, len(theText), self.hStyles["header2"])

            elif theText.startswith("### "):  # Header 3
                self.setFormat(0, 3, self.hStyles["header3h"])
                self.setFormat(3, len(theText), self.hStyles["header3"])

            elif theText.startswith("#### "):  # Header 4
                self.setFormat(0, 4, self.hStyles["header4h"])
                self.setFormat(4, len(theText), self.hStyles["header4"])

            if theText.startswith("#! "):  # Title
                self.setFormat(0, 2, self.hStyles["header1h"])
                self.setFormat(2, len(theText), self.hStyles["header1"])

            elif theText.startswith("##! "):  # Unnumbered
                self.setFormat(0, 3, self.hStyles["header2h"])
                self.setFormat(3, len(theText), self.hStyles["header2"])

        elif theText.startswith("%"):  # Comments
            self.setCurrentBlockState(self.BLOCK_TEXT)
            toCheck = theText[1:].lstrip()
            synTag  = toCheck[:9].lower()
            tLen = len(theText)
            cLen = len(toCheck)
            cOff = tLen - cLen
            if synTag == "synopsis:":
                self.setFormat(0, cOff+9, self.hStyles["modifier"])
                self.setFormat(cOff+9, tLen, self.hStyles["hidden"])
            else:
                self.setFormat(0, tLen, self.hStyles["hidden"])

        else:  # Text Paragraph

            if theText.startswith("["):  # Special Command
                sText = theText.rstrip()
                if sText in ("[NEWPAGE]", "[NEW PAGE]", "[VSPACE]"):
                    self.setFormat(0, len(theText), self.hStyles["keyword"])
                    return

                elif sText.startswith("[VSPACE:") and sText.endswith("]"):
                    tLen = len(sText)
                    tVal = checkInt(sText[8:-1], 0)
                    self.setFormat(0, 8, self.hStyles["keyword"])
                    if tVal > 0:
                        self.setFormat(8, tLen-9, self.hStyles["codevalue"])
                    else:
                        self.setFormat(8, tLen-9, self.hStyles["codeinval"])
                    self.setFormat(tLen-1, tLen, self.hStyles["keyword"])
                    return

            # Regular text
            self.setCurrentBlockState(self.BLOCK_TEXT)
            for rX, xFmt in self.rxRules:
                rxItt = rX.globalMatch(theText, 0)
                while rxItt.hasNext():
                    rxMatch = rxItt.next()
                    for xM in xFmt:
                        xPos = rxMatch.capturedStart(xM)
                        xLen = rxMatch.capturedLength(xM)
                        for x in range(xPos, xPos+xLen):
                            spFmt = self.format(x)
                            if spFmt != self.hStyles["hidden"]:
                                spFmt.merge(xFmt[xM])
                                self.setFormat(x, 1, spFmt)

        if not self.spellCheck:
            return

        rxSpell = self.spellRx.globalMatch(theText, 0)
        while rxSpell.hasNext():
            rxMatch = rxSpell.next()
            if not self.spEnchant.checkWord(rxMatch.captured(0)):
                if rxMatch.captured(0).isupper() or rxMatch.captured(0).isnumeric():
                    continue
                xPos = rxMatch.capturedStart(0)
                xLen = rxMatch.capturedLength(0)
                for x in range(xPos, xPos+xLen):
                    spFmt = self.format(x)
                    spFmt.setUnderlineColor(self.colSpell)
                    spFmt.setUnderlineStyle(QTextCharFormat.SpellCheckUnderline)
                    self.setFormat(x, 1, spFmt)

        return
예제 #17
0
    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