Files
novelWriter/nw/gui/doceditor.py
T
2019-04-21 01:04:13 +02:00

277 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- coding: utf-8 -*-
"""novelWriter GUI Document Editor
novelWriter GUI Document Editor
===================================
Class holding the document editor
File History:
Created: 2018-09-29 [0.0.1]
"""
import logging
import nw
from time import time
from PyQt5.QtWidgets import QWidget, QTextEdit, QHBoxLayout, QVBoxLayout, QFrame, QSplitter, QToolBar, QAction, QScrollArea
from PyQt5.QtCore import Qt, QSize, QSizeF
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QTextFormat, QTextBlockFormat
from nw.gui.dochighlight import GuiDocHighlighter
logger = logging.getLogger(__name__)
class GuiDocEditor(QWidget):
def __init__(self, theParent):
QWidget.__init__(self)
logger.debug("Initialising DocEditor ...")
self.mainConf = nw.CONFIG
self.theParent = theParent
self.charCount = 0
self.wordCount = 0
self.lineCount = 0
# Internal Temps
self.blockWords = {}
self.currEditBlock = -1
self.currEditStart = 0
self.currEditFinal = 0
self.outerBox = QVBoxLayout()
self.guiEditor = QTextEdit()
if self.mainConf.textFixedW:
self.guiEditor.setLineWrapMode(QTextEdit.FixedPixelWidth)
self.guiEditor.setLineWrapColumnOrWidth(self.mainConf.textWidth)
else:
mTB = self.mainConf.textMargin[0]
mLR = self.mainConf.textMargin[1]
self.guiEditor.setViewportMargins(mLR,mTB,mLR,mTB)
self.hLight = GuiDocHighlighter(self.guiEditor.document())
self.setLayout(self.outerBox)
self.editToolBar = QToolBar()
self._buildTabToolBar()
self.outerBox.addWidget(self.editToolBar)
self.outerBox.addWidget(self.guiEditor)
self.guiEditor.setMinimumWidth(400)
self.guiEditor.setAcceptRichText(False)
self.theDoc = self.guiEditor.document()
self.theDoc.setDocumentMargin(0)
self.theDoc.contentsChange.connect(self._docChange)
logger.debug("DocEditor initialisation complete")
return
def setText(self, theText):
self.guiEditor.setPlainText(theText)
self.charCount = self.theDoc.characterCount()
self.wordCount = 0
self.currEditBlock = -1
self.currEditWords = 0
tStart = time()
self.blockWords = {}
for n in range(self.theDoc.blockCount()):
self._countWords(self.theDoc.findBlockByNumber(n))
self.wordCount = sum(self.blockWords.values())
logger.verbose("Doc word count took %.3f µs" % ((time()-tStart)*1e6))
self.theParent.statusBar.setCharCount(self.charCount)
self.theParent.statusBar.setWordCount(self.wordCount)
return True
def getText(self):
theText = self.guiEditor.toPlainText()
return theText
def changeWidth(self):
"""Automatically adjust the margins so the text is centred, but only if Config.textFixedW is
set to True.
"""
if self.mainConf.textFixedW:
tW = self.guiEditor.width()
sW = self.guiEditor.verticalScrollBar().width()
tM = int((tW - sW - self.mainConf.textWidth)/2)
mTB = self.mainConf.textMargin[0]
self.guiEditor.setViewportMargins(tM,mTB,0,mTB)
return
def _docChange(self, thePos, charsRemoved, charsAdded):
tStart = time()
self.charCount = self.theDoc.characterCount()
self.lineCount = self.theDoc.lineCount()
self.theParent.statusBar.setCharCount(self.charCount)
currBlock = self.theDoc.findBlock(thePos)
self._countWords(currBlock)
self.wordCount = sum(self.blockWords.values())
self.theParent.statusBar.setWordCount(self.wordCount)
if self.mainConf.doReplace:
self._docAutoReplace(currBlock)
logger.verbose("Doc change signal took %.3f µs" % ((time()-tStart)*1e6))
return
def _docAutoReplace(self, theBlock):
if not theBlock.isValid():
return
theText = theBlock.text()
theCursor = self.guiEditor.textCursor()
thePos = theCursor.positionInBlock()
theLen = len(theText)
if theLen < 1 or thePos-1 > theLen:
return
theOne = theText[thePos-1:thePos]
theTwo = theText[thePos-2:thePos]
theThree = theText[thePos-3:thePos]
if self.mainConf.doReplaceQuote and theOne == "\"":
qOpen = self.mainConf.replaceQuotes[0]
qClose = self.mainConf.replaceQuotes[1]
nOpen = theText.count(qOpen)
nClose = theText.count(qClose)
if nOpen > nClose:
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().insertText(qClose)
else:
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().insertText(qOpen)
elif self.mainConf.doReplaceDash and theTwo == "--":
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().insertText("")
elif self.mainConf.doReplaceDash and theTwo == "-":
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().insertText("")
elif self.mainConf.doReplaceDots and theThree == "...":
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().deletePreviousChar()
self.guiEditor.textCursor().insertText("")
return
def _countWords(self, theBlock):
"""Count the number of words in a given block, but only if it's a text block. I.e. we skip
blocks starting with @ and %, and we also skip the ### in titles. Dashes are replaced with
spaces before the count to ensure, for instance, 'word1—word2' is counted as two words.
"""
if not theBlock.isValid():
return
theText = theBlock.text()
theLen = len(theText)
self.blockWords[theBlock.blockNumber()] = 0
if theLen == 0:
return 0
if theText[0] == "@" or theText[0] == "%":
return 0
if theText[0] == "#":
nWords = -1
else:
nWords = 0
theBuff = theText.replace(""," ").replace(""," ")
nWords += len(theBuff.split())
self.blockWords[theBlock.blockNumber()] = nWords
return nWords
def _buildTabToolBar(self):
toolBar = self.editToolBar
toolBar.setToolButtonStyle(Qt.ToolButtonIconOnly)
toolBar.setIconSize(QSize(16,16))
# Text > Bold
tbTextBold = QAction(QIcon.fromTheme("format-text-bold"), "Bold (Ctrl+B)", toolBar)
tbTextBold.setShortcut("Ctrl+B")
tbTextBold.setStatusTip("Toggle Selected Text Bold")
toolBar.addAction(tbTextBold)
# Text > Italics
tbTextItalic = QAction(QIcon.fromTheme("format-text-italic"), "Italic (Ctrl+I)", toolBar)
tbTextItalic.setShortcut("Ctrl+I")
tbTextItalic.setStatusTip("Toggle Selected Text Italic")
toolBar.addAction(tbTextItalic)
# Text > Underline
tbTextUnderline = QAction(QIcon.fromTheme("format-text-underline"), "Underline (Ctrl+U)", toolBar)
tbTextUnderline.setShortcut("Ctrl+U")
tbTextUnderline.setStatusTip("Toggle Selected Text Underline")
toolBar.addAction(tbTextUnderline)
# Text > Strikethrough
tbTextStrikethrough = QAction(QIcon.fromTheme("format-text-strikethrough"), "Strikethrough (Ctrl+D)", toolBar)
tbTextStrikethrough.setShortcut("Ctrl+D")
tbTextStrikethrough.setStatusTip("Toggle Selected Text Strikethrough")
toolBar.addAction(tbTextStrikethrough)
# --------------------
toolBar.addSeparator()
# Edit > Cut
tbEditCut = QAction(QIcon.fromTheme("edit-cut"), "Cut (Ctrl+X)", toolBar)
tbEditCut.setShortcut("Ctrl+X")
tbEditCut.setStatusTip("Cut Selected Text")
toolBar.addAction(tbEditCut)
# Edit > Copy
tbEditCopy = QAction(QIcon.fromTheme("edit-copy"), "Copy (Ctrl+C)", toolBar)
tbEditCopy.setShortcut("Ctrl+C")
tbEditCopy.setStatusTip("Copy Selected Text")
toolBar.addAction(tbEditCopy)
# Edit > Paste
tbEditPaste = QAction(QIcon.fromTheme("edit-paste"), "Paste (Ctrl+V)", toolBar)
tbEditPaste.setShortcut("Ctrl+V")
tbEditPaste.setStatusTip("Paste Text")
toolBar.addAction(tbEditPaste)
# --------------------
toolBar.addSeparator()
# Edit > Undo
tbEditUndo = QAction(QIcon.fromTheme("edit-undo"), "Undo (Ctrl+Z)", toolBar)
tbEditUndo.setShortcut("Ctrl+Z")
tbEditUndo.setStatusTip("Undo Last Change")
toolBar.addAction(tbEditUndo)
# Edit > Redo
tbEditRedo = QAction(QIcon.fromTheme("edit-redo"), "Redo (Ctrl+Y)", toolBar)
tbEditRedo.setShortcut("Ctrl+Y")
tbEditRedo.setStatusTip("Revert Last Undo")
toolBar.addAction(tbEditRedo)
return
# END Class GuiDocEditor