Merge branch 'main' into dev
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
<h2>Release Notes for 2.1</h2>
|
||||
<p><i>Released on 17 October 2023</i></p>
|
||||
|
||||
<p>Scroll down for <a href="#patch">Patch Notes</a></p>
|
||||
|
||||
<p>The primary focus of this release has been a complete redesign of the Build Tool, that is, the
|
||||
tool that assembles your project into a manuscript document. The new tool, called the "Manuscript
|
||||
Build Tool" allows you to define multiple build definitions for your project. The build definitions
|
||||
@@ -30,5 +32,16 @@ a full list of changes, see the detailed changelogs.</p>
|
||||
|
||||
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
|
||||
|
||||
<a name="patch"></a><h2>Patch Notes</h2>
|
||||
|
||||
<h3>Patch 2.2.1 – 5 November 2023</h3>
|
||||
|
||||
<p>This is a patch release that fixes a layout issue and internationalisation issues with the new
|
||||
Manuscript Build tool. It also fixes a number of issues related to bugs in the underlying Qt
|
||||
framework that affects drag and drop functionality in the project tree. These issues were mostly
|
||||
only affecting Debian Linux package releases.</p>
|
||||
<p>Other, minor issues related to updating the editor on colour theme change and project word list
|
||||
changes have been fixed as well. See the full changelog for more details.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -81,10 +81,12 @@ class Config:
|
||||
|
||||
# Localisation
|
||||
# Note that these paths must be strings
|
||||
self._qLocale = QLocale.system()
|
||||
self._qtTrans = {}
|
||||
self._nwLangPath = self._appPath / "assets" / "i18n"
|
||||
self._qtLangPath = QLibraryInfo.location(QLibraryInfo.TranslationsPath)
|
||||
self._nwLangPath = str(self._appPath / "assets" / "i18n")
|
||||
|
||||
wantedLocale = self._nwLangPath / f"nw_{QLocale.system().name()}.qm"
|
||||
self._qLocale = QLocale.system() if wantedLocale.exists() else QLocale("en_GB")
|
||||
self._qtTrans = {}
|
||||
|
||||
# PDF Manual
|
||||
pdfDocs = self._appPath / "assets" / "manual.pdf"
|
||||
@@ -425,7 +427,7 @@ class Config:
|
||||
else:
|
||||
return []
|
||||
|
||||
for qmFile in Path(self._nwLangPath).iterdir():
|
||||
for qmFile in self._nwLangPath.iterdir():
|
||||
qmName = qmFile.name
|
||||
if not (qmFile.is_file() and qmName.startswith(fPre) and qmName.endswith(fExt)):
|
||||
continue
|
||||
@@ -493,8 +495,8 @@ class Config:
|
||||
self._qtTrans = {}
|
||||
|
||||
langList = [
|
||||
(self._qtLangPath, "qtbase"), # Qt 5.x
|
||||
(self._nwLangPath, "nw"), # novelWriter
|
||||
(self._qtLangPath, "qtbase"), # Qt 5.x
|
||||
(str(self._nwLangPath), "nw"), # novelWriter
|
||||
]
|
||||
for lngPath, lngBase in langList:
|
||||
for lngCode in self._qLocale.uiLanguages():
|
||||
|
||||
@@ -32,7 +32,7 @@ from enum import Enum
|
||||
from typing import Iterable
|
||||
from pathlib import Path
|
||||
|
||||
from PyQt5.QtCore import QT_TRANSLATE_NOOP
|
||||
from PyQt5.QtCore import QT_TRANSLATE_NOOP, QCoreApplication
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.enum import nwBuildFmt
|
||||
@@ -209,7 +209,7 @@ class BuildSettings:
|
||||
@staticmethod
|
||||
def getLabel(key: str) -> str:
|
||||
"""Extract the GUI label for a specific setting."""
|
||||
return SETTINGS_LABELS.get(key, "ERROR")
|
||||
return QCoreApplication.translate("Builds", SETTINGS_LABELS.get(key, "ERROR"))
|
||||
|
||||
def getStr(self, key: str) -> str:
|
||||
"""Type safe value access for strings."""
|
||||
|
||||
@@ -162,7 +162,7 @@ class ToHtml(Tokenizer):
|
||||
pStyle = None
|
||||
lines = []
|
||||
|
||||
for tType, tLine, tText, tFormat, tStyle in self._tokens:
|
||||
for tType, nHead, tText, tFormat, tStyle in self._tokens:
|
||||
|
||||
# Replace < and > with HTML entities
|
||||
if tFormat:
|
||||
@@ -223,7 +223,7 @@ class ToHtml(Tokenizer):
|
||||
hStyle = ""
|
||||
|
||||
if self._linkHeaders:
|
||||
aNm = f"<a name='T{tLine:06d}'></a>"
|
||||
aNm = f"<a name='T{nHead:04d}'></a>"
|
||||
else:
|
||||
aNm = ""
|
||||
|
||||
|
||||
@@ -382,7 +382,7 @@ class Tokenizer(ABC):
|
||||
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
|
||||
2: The header number under which the text is placed
|
||||
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_*
|
||||
@@ -396,16 +396,15 @@ class Tokenizer(ABC):
|
||||
|
||||
self._tokens = []
|
||||
tmpMarkdown = []
|
||||
nLine = 0
|
||||
nHead = 0
|
||||
breakNext = False
|
||||
for aLine in self._text.splitlines():
|
||||
nLine += 1
|
||||
sLine = aLine.strip()
|
||||
|
||||
# Check for blank lines
|
||||
if len(sLine) == 0:
|
||||
self._tokens.append((
|
||||
self.T_EMPTY, nLine, "", None, self.A_NONE
|
||||
self.T_EMPTY, nHead, "", None, self.A_NONE
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("\n")
|
||||
@@ -430,7 +429,7 @@ class Tokenizer(ABC):
|
||||
|
||||
elif sLine == "[VSPACE]":
|
||||
self._tokens.append(
|
||||
(self.T_SKIP, nLine, "", None, sAlign)
|
||||
(self.T_SKIP, nHead, "", None, sAlign)
|
||||
)
|
||||
continue
|
||||
|
||||
@@ -438,11 +437,11 @@ class Tokenizer(ABC):
|
||||
nSkip = checkInt(sLine[8:-1], 0)
|
||||
if nSkip >= 1:
|
||||
self._tokens.append(
|
||||
(self.T_SKIP, nLine, "", None, sAlign)
|
||||
(self.T_SKIP, nHead, "", None, sAlign)
|
||||
)
|
||||
if nSkip > 1:
|
||||
self._tokens += (nSkip - 1) * [
|
||||
(self.T_SKIP, nLine, "", None, self.A_NONE)
|
||||
(self.T_SKIP, nHead, "", None, self.A_NONE)
|
||||
]
|
||||
continue
|
||||
|
||||
@@ -451,20 +450,20 @@ class Tokenizer(ABC):
|
||||
synTag = cLine[:9].lower()
|
||||
if synTag == "synopsis:":
|
||||
self._tokens.append((
|
||||
self.T_SYNOPSIS, nLine, cLine[9:].strip(), None, sAlign
|
||||
self.T_SYNOPSIS, nHead, cLine[9:].strip(), None, sAlign
|
||||
))
|
||||
if self._doSynopsis and self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
else:
|
||||
self._tokens.append((
|
||||
self.T_COMMENT, nLine, aLine[1:].strip(), None, sAlign
|
||||
self.T_COMMENT, nHead, aLine[1:].strip(), None, sAlign
|
||||
))
|
||||
if self._doComments and self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[0] == "@":
|
||||
self._tokens.append((
|
||||
self.T_KEYWORD, nLine, aLine[1:].strip(), None, sAlign
|
||||
self.T_KEYWORD, nHead, aLine[1:].strip(), None, sAlign
|
||||
))
|
||||
if self._doKeywords and self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
@@ -474,8 +473,9 @@ class Tokenizer(ABC):
|
||||
sAlign |= self.A_CENTRE
|
||||
sAlign |= self.A_PBB
|
||||
|
||||
nHead += 1
|
||||
self._tokens.append((
|
||||
self.T_HEAD1, nLine, aLine[2:].strip(), None, sAlign
|
||||
self.T_HEAD1, nHead, aLine[2:].strip(), None, sAlign
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
@@ -484,39 +484,44 @@ class Tokenizer(ABC):
|
||||
if self._isNovel:
|
||||
sAlign |= self.A_PBB
|
||||
|
||||
nHead += 1
|
||||
self._tokens.append((
|
||||
self.T_HEAD2, nLine, aLine[3:].strip(), None, sAlign
|
||||
self.T_HEAD2, nHead, aLine[3:].strip(), None, sAlign
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:4] == "### ":
|
||||
nHead += 1
|
||||
self._tokens.append((
|
||||
self.T_HEAD3, nLine, aLine[4:].strip(), None, sAlign
|
||||
self.T_HEAD3, nHead, aLine[4:].strip(), None, sAlign
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:5] == "#### ":
|
||||
nHead += 1
|
||||
self._tokens.append((
|
||||
self.T_HEAD4, nLine, aLine[5:].strip(), None, sAlign
|
||||
self.T_HEAD4, nHead, aLine[5:].strip(), None, sAlign
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:3] == "#! ":
|
||||
nHead += 1
|
||||
if self._isNovel:
|
||||
tStyle = self.T_TITLE
|
||||
else:
|
||||
tStyle = self.T_HEAD1
|
||||
|
||||
self._tokens.append((
|
||||
tStyle, nLine, aLine[3:].strip(), None, sAlign | self.A_CENTRE
|
||||
tStyle, nHead, aLine[3:].strip(), None, sAlign | self.A_CENTRE
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
|
||||
elif aLine[:4] == "##! ":
|
||||
nHead += 1
|
||||
if self._isNovel:
|
||||
tStyle = self.T_UNNUM
|
||||
sAlign |= self.A_PBB
|
||||
@@ -524,7 +529,7 @@ class Tokenizer(ABC):
|
||||
tStyle = self.T_HEAD2
|
||||
|
||||
self._tokens.append((
|
||||
tStyle, nLine, aLine[4:].strip(), None, sAlign
|
||||
tStyle, nHead, aLine[4:].strip(), None, sAlign
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
@@ -581,7 +586,7 @@ class Tokenizer(ABC):
|
||||
# sorted by position
|
||||
fmtPos = sorted(fmtPos, key=itemgetter(0))
|
||||
self._tokens.append((
|
||||
self.T_TEXT, nLine, aLine, fmtPos, sAlign
|
||||
self.T_TEXT, nHead, aLine, fmtPos, sAlign
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("%s\n" % aLine)
|
||||
@@ -600,7 +605,7 @@ class Tokenizer(ABC):
|
||||
|
||||
# Always add an empty line at the end of the file
|
||||
self._tokens.append((
|
||||
self.T_EMPTY, nLine, "", None, self.A_NONE
|
||||
self.T_EMPTY, nHead, "", None, self.A_NONE
|
||||
))
|
||||
if self._keepMarkdown:
|
||||
tmpMarkdown.append("\n")
|
||||
|
||||
@@ -176,6 +176,8 @@ class GuiPreferencesGeneral(QWidget):
|
||||
for lang, langName in theLangs:
|
||||
self.guiLocale.addItem(langName, lang)
|
||||
langIdx = self.guiLocale.findData(CONFIG.guiLocale)
|
||||
if langIdx < 0:
|
||||
langIdx = self.guiLocale.findData("en_GB")
|
||||
if langIdx != -1:
|
||||
self.guiLocale.setCurrentIndex(langIdx)
|
||||
|
||||
|
||||
@@ -339,6 +339,8 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
self.clearEditor()
|
||||
else:
|
||||
self.redrawText()
|
||||
if not self._bigDoc:
|
||||
self.highLight.rehighlight()
|
||||
|
||||
return
|
||||
|
||||
@@ -980,7 +982,7 @@ class GuiDocEditor(QPlainTextEdit):
|
||||
uCursor = self.textCursor()
|
||||
pCursor = self.cursorForPosition(pos)
|
||||
|
||||
ctxMenu = QMenu()
|
||||
ctxMenu = QMenu(self)
|
||||
|
||||
# Follow
|
||||
if self._followTag(cursor=pCursor, loadTag=False):
|
||||
|
||||
@@ -381,7 +381,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
userCursor = self.textCursor()
|
||||
userSelection = userCursor.hasSelection()
|
||||
|
||||
mnuContext = QMenu()
|
||||
mnuContext = QMenu(self)
|
||||
|
||||
# Cut, Copy and Paste
|
||||
# ===================
|
||||
|
||||
@@ -228,7 +228,7 @@ class GuiNovelToolBar(QWidget):
|
||||
self.tbRefresh.clicked.connect(self._refreshNovelTree)
|
||||
|
||||
# More Options Menu
|
||||
self.mMore = QMenu()
|
||||
self.mMore = QMenu(self)
|
||||
|
||||
self.mLastCol = self.mMore.addMenu(self.tr("Last Column"))
|
||||
self.gLastCol = QActionGroup(self.mMore)
|
||||
|
||||
@@ -31,8 +31,8 @@ from enum import Enum
|
||||
from time import time
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from PyQt5.QtGui import QDropEvent, QMouseEvent, QPalette
|
||||
from PyQt5.QtCore import QPoint, Qt, QSize, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QDragMoveEvent, QDropEvent, QMouseEvent, QPalette
|
||||
from PyQt5.QtCore import QPoint, QTimer, Qt, QSize, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QDialog, QFrame, QHBoxLayout, QHeaderView, QLabel,
|
||||
QMenu, QShortcut, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem,
|
||||
@@ -248,7 +248,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self.viewLabel.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
|
||||
|
||||
# Quick Links
|
||||
self.mQuick = QMenu()
|
||||
self.mQuick = QMenu(self)
|
||||
|
||||
self.tbQuick = QToolButton(self)
|
||||
self.tbQuick.setToolTip("%s [Ctrl+L]" % self.tr("Quick Links"))
|
||||
@@ -269,7 +269,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self.tbMoveD.clicked.connect(lambda: self.projTree.moveTreeItem(1))
|
||||
|
||||
# Add Item Menu
|
||||
self.mAdd = QMenu()
|
||||
self.mAdd = QMenu(self)
|
||||
|
||||
self.aAddEmpty = self.mAdd.addAction(trConst(nwLabels.ITEM_DESCRIPTION["document"]))
|
||||
self.aAddEmpty.triggered.connect(
|
||||
@@ -307,7 +307,7 @@ class GuiProjectToolBar(QWidget):
|
||||
self.tbAdd.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
# More Options Menu
|
||||
self.mMore = QMenu()
|
||||
self.mMore = QMenu(self)
|
||||
|
||||
self.aExpand = self.mMore.addAction(self.tr("Expand All"))
|
||||
self.aExpand.triggered.connect(lambda: self.projTree.setExpandedFromHandle(None, True))
|
||||
@@ -507,8 +507,15 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Allow Move by Drag & Drop
|
||||
self.setDragEnabled(True)
|
||||
self.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.setDropIndicatorShown(True)
|
||||
|
||||
# Disable built-in autoscroll as it isn't working in some Qt
|
||||
# releases (see #1561) and instead use our own implementation
|
||||
self.setAutoScroll(False)
|
||||
|
||||
# But don't allow drop on root level
|
||||
# Due to a bug, this stops working somewhere between Qt 5.15.3
|
||||
# and 5.15.8, so this is also blocked in dropEvent (see #1569)
|
||||
trRoot = self.invisibleRootItem()
|
||||
trRoot.setFlags(trRoot.flags() ^ Qt.ItemIsDropEnabled)
|
||||
|
||||
@@ -524,6 +531,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||
self.itemSelectionChanged.connect(self._treeSelectionChange)
|
||||
|
||||
# Autoscroll
|
||||
self._scrollMargin = SHARED.theme.baseIconSize
|
||||
self._scrollDirection = 0
|
||||
self._scrollTimer = QTimer()
|
||||
self._scrollTimer.timeout.connect(self._doAutoScroll)
|
||||
self._scrollTimer.setInterval(250)
|
||||
|
||||
# Set custom settings
|
||||
self.initSettings()
|
||||
|
||||
@@ -1196,7 +1210,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.debug("No item found")
|
||||
return False
|
||||
|
||||
ctxMenu = QMenu()
|
||||
ctxMenu = QMenu(self)
|
||||
|
||||
# Trash Folder
|
||||
# ============
|
||||
@@ -1343,6 +1357,17 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
return True
|
||||
|
||||
@pyqtSlot()
|
||||
def _doAutoScroll(self) -> None:
|
||||
"""Scroll one item up or down based on direction value."""
|
||||
if self._scrollDirection == -1:
|
||||
self.scrollToItem(self.itemAbove(self.itemAt(1, 1)))
|
||||
elif self._scrollDirection == 1:
|
||||
self.scrollToItem(self.itemBelow(self.itemAt(1, self.height() - 1)))
|
||||
self._scrollDirection = 0
|
||||
self._scrollTimer.stop()
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -1374,14 +1399,36 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
def dragMoveEvent(self, event: QDragMoveEvent) -> None:
|
||||
"""Capture the drag move event to enable edge autoscroll."""
|
||||
y = event.pos().y()
|
||||
if y < self._scrollMargin:
|
||||
if not self._scrollTimer.isActive():
|
||||
self._scrollDirection = -1
|
||||
self._scrollTimer.start()
|
||||
elif y > self.height() - self._scrollMargin:
|
||||
if not self._scrollTimer.isActive():
|
||||
self._scrollDirection = 1
|
||||
self._scrollTimer.start()
|
||||
super().dragMoveEvent(event)
|
||||
return
|
||||
|
||||
def dropEvent(self, event: QDropEvent) -> None:
|
||||
"""Overload the drop item event to ensure relevant data has been
|
||||
updated.
|
||||
"""
|
||||
sHandle = self.getSelectedHandle()
|
||||
sItem = self._getTreeItem(sHandle) if sHandle else None
|
||||
if sHandle is None or sItem is None:
|
||||
if sHandle is None or sItem is None or sItem.parent() is None:
|
||||
logger.error("Invalid drag and drop event")
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
if not self.indexAt(event.pos()).isValid():
|
||||
# Needed due to a bug somewhere around Qt 5.15.8 that
|
||||
# ignores the invisible root item flags
|
||||
logger.error("Invalid drop location")
|
||||
event.ignore()
|
||||
return
|
||||
|
||||
logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
|
||||
|
||||
@@ -991,6 +991,7 @@ class GuiMain(QMainWindow):
|
||||
if dlgWords.result() == QDialog.Accepted:
|
||||
logger.debug("Reloading word list")
|
||||
SHARED.updateSpellCheckLanguage(reload=True)
|
||||
self.docEditor.spellCheckDocument()
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@@ -332,14 +332,16 @@ class _FilterTab(QWidget):
|
||||
|
||||
treeHeader = self.optTree.header()
|
||||
treeHeader.setStretchLastSection(False)
|
||||
treeHeader.setMinimumSectionSize(iPx + cMg) # See Issue #1551
|
||||
treeHeader.setSectionResizeMode(self.C_NAME, QHeaderView.Stretch)
|
||||
treeHeader.setSectionResizeMode(self.C_ACTIVE, QHeaderView.Fixed)
|
||||
treeHeader.setSectionResizeMode(self.C_STATUS, QHeaderView.Fixed)
|
||||
treeHeader.resizeSection(self.C_ACTIVE, iPx + cMg)
|
||||
treeHeader.resizeSection(self.C_STATUS, iPx + cMg)
|
||||
|
||||
self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
self.optTree.setDragDropMode(QAbstractItemView.NoDragDrop)
|
||||
self.optTree.setSelectionMode(QAbstractItemView.ExtendedSelection)
|
||||
self.optTree.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
|
||||
# Filters
|
||||
# =======
|
||||
@@ -390,11 +392,11 @@ class _FilterTab(QWidget):
|
||||
self.mainSplit.addWidget(self.filterOpt)
|
||||
self.mainSplit.setCollapsible(0, False)
|
||||
self.mainSplit.setCollapsible(1, False)
|
||||
self.mainSplit.setStretchFactor(0, 0)
|
||||
self.mainSplit.setStretchFactor(1, 1)
|
||||
self.mainSplit.setStretchFactor(0, 1)
|
||||
self.mainSplit.setStretchFactor(1, 0)
|
||||
self.mainSplit.setSizes([
|
||||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 1)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 1))
|
||||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "treeWidth", 300)),
|
||||
CONFIG.pxInt(pOptions.getInt("GuiBuildSettings", "filterWidth", 300))
|
||||
])
|
||||
|
||||
self.outerBox = QHBoxLayout()
|
||||
@@ -718,7 +720,7 @@ class _HeadingsTab(QWidget):
|
||||
|
||||
self.formSyntax = _HeadingSyntaxHighlighter(self.editTextBox.document())
|
||||
|
||||
self.menuInsert = QMenu()
|
||||
self.menuInsert = QMenu(self)
|
||||
self.aInsTitle = self.menuInsert.addAction(self.tr("Title"))
|
||||
self.aInsChNum = self.menuInsert.addAction(self.tr("Chapter Number"))
|
||||
self.aInsChWord = self.menuInsert.addAction(self.tr("Chapter Number (Word)"))
|
||||
|
||||
Reference in New Issue
Block a user