Skip to content

Commit

Permalink
Allow full font style settings for GUI and editor (#1881)
Browse files Browse the repository at this point in the history
  • Loading branch information
vkbo authored May 20, 2024
2 parents 0bc8a63 + d6b4019 commit d2fca3b
Show file tree
Hide file tree
Showing 20 changed files with 197 additions and 236 deletions.
39 changes: 19 additions & 20 deletions novelwriter/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,29 +216,28 @@ def main(sysArgs: list | None = None) -> GuiMain | None:

# Import GUI (after dependency checks), and launch
from novelwriter.guimain import GuiMain
if testMode:
nwGUI = GuiMain()
return nwGUI

else:
nwApp = QApplication([CONFIG.appName, (f"-style={qtStyle}")])
nwApp.setApplicationName(CONFIG.appName)
nwApp.setApplicationVersion(__version__)
nwApp.setOrganizationDomain(__domain__)
nwApp.setOrganizationName(__domain__)
nwApp.setDesktopFileName(CONFIG.appName)
if testMode:
# Only used for testing where the test framework creates the app
CONFIG.loadConfig()
return GuiMain()

# Connect the exception handler before making the main GUI
sys.excepthook = exceptionHandler
app = QApplication([CONFIG.appName, (f"-style={qtStyle}")])
app.setApplicationName(CONFIG.appName)
app.setApplicationVersion(__version__)
app.setOrganizationDomain(__domain__)
app.setOrganizationName(__domain__)
app.setDesktopFileName(CONFIG.appName)

# Run Config steps that require the QApplication
CONFIG.initLocalisation(nwApp)
CONFIG.setTextFont(CONFIG.textFont, CONFIG.textSize) # Makes sure these are valid
# Connect the exception handler before making the main GUI
sys.excepthook = exceptionHandler

# Launch main GUI
nwGUI = GuiMain()
nwGUI.postLaunchTasks(cmdOpen)
# Run Config steps that require the QApplication
CONFIG.loadConfig()
CONFIG.initLocalisation(app)

sys.exit(nwApp.exec())
# Launch main GUI
nwGUI = GuiMain()
nwGUI.postLaunchTasks(cmdOpen)

return None
sys.exit(app.exec())
10 changes: 9 additions & 1 deletion novelwriter/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
from urllib.request import pathname2url

from PyQt5.QtCore import QCoreApplication, QUrl
from PyQt5.QtGui import QColor, QDesktopServices
from PyQt5.QtGui import QColor, QDesktopServices, QFont, QFontInfo

from novelwriter.constants import nwConst, nwLabels, nwUnicode, trConst
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
Expand Down Expand Up @@ -401,6 +401,14 @@ def cssCol(col: QColor, alpha: int | None = None) -> str:
return f"rgba({col.red()}, {col.green()}, {col.blue()}, {alpha or col.alpha()})"


def describeFont(font: QFont) -> str:
"""Describe a font in a way that can be displayed on the GUI."""
if isinstance(font, QFont):
info = QFontInfo(font)
return f"{font.family()} {info.styleName()} @ {font.pointSize()} pt"
return "Error"


##
# Encoder Functions
##
Expand Down
172 changes: 99 additions & 73 deletions novelwriter/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,10 @@
PYQT_VERSION, PYQT_VERSION_STR, QT_VERSION, QT_VERSION_STR, QLibraryInfo,
QLocale, QStandardPaths, QSysInfo, QTranslator
)
from PyQt5.QtGui import QFontDatabase
from PyQt5.QtGui import QFont, QFontDatabase
from PyQt5.QtWidgets import QApplication

from novelwriter.common import NWConfigParser, checkInt, checkPath, formatTimeStamp
from novelwriter.common import NWConfigParser, checkInt, checkPath, describeFont, formatTimeStamp
from novelwriter.constants import nwFiles, nwUnicode
from novelwriter.error import formatException, logException

Expand Down Expand Up @@ -106,15 +106,14 @@ def __init__(self) -> None:
self._recentObj = RecentProjects(self)

# General GUI Settings
self.guiLocale = self._qLocale.name()
self.guiTheme = "default" # GUI theme
self.guiSyntax = "default_light" # Syntax theme
self.guiFont = "" # Defaults to system default font in theme class
self.guiFontSize = 11 # Is overridden if system default is loaded
self.guiScale = 1.0 # Set automatically by Theme class
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown
self.guiLocale = self._qLocale.name()
self.guiTheme = "default" # GUI theme
self.guiSyntax = "default_light" # Syntax theme
self.guiFont = QFont() # Main GUI font
self.guiScale = 1.0 # Set automatically by Theme class
self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.lastNotes = "0x0" # The latest release notes that have been shown

# Size Settings
self._mainWinSize = [1200, 650] # Last size of the main GUI window
Expand All @@ -132,43 +131,42 @@ def __init__(self) -> None:
self.askBeforeBackup = True # Flag for asking before running automatic backup

# Text Editor Settings
self.textFont = "" # Editor font
self.textSize = 12 # Editor font size
self.textWidth = 700 # Editor text width
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width

self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
self.showFullPath = True # Show full document path in editor header
self.autoSelect = True # Auto-select word when applying format with no selection

self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in editor
self.showLineEndings = False # Show line endings in editor
self.showMultiSpaces = True # Highlight multiple spaces in the text

self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
self.doReplaceDQuote = True # Smart double quotes
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis

self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor

self.dialogStyle = 2 # Quote type to use for dialogue
self.allowOpenDial = True # Allow open-ended dialogue quotes
self.narratorBreak = "" # Symbol to use for narrator break
self.dialogLine = "" # Symbol to use for dialogue line
self.altDialogOpen = "" # Alternative dialog symbol, open
self.altDialogClose = "" # Alternative dialog symbol, close
self.highlightEmph = True # Add colour to text emphasis

self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
self.incNotesWCount = True # The status bar word count includes notes
self.textFont = QFont() # Editor font
self.textWidth = 700 # Editor text width
self.textMargin = 40 # Editor/viewer text margin
self.tabWidth = 40 # Editor tabulator width

self.focusWidth = 800 # Focus Mode text width
self.hideFocusFooter = False # Hide document footer in Focus Mode
self.showFullPath = True # Show full document path in editor header
self.autoSelect = True # Auto-select word when applying format with no selection

self.doJustify = False # Justify text
self.showTabsNSpaces = False # Show tabs and spaces in editor
self.showLineEndings = False # Show line endings in editor
self.showMultiSpaces = True # Highlight multiple spaces in the text

self.doReplace = True # Enable auto-replace as you type
self.doReplaceSQuote = True # Smart single quotes
self.doReplaceDQuote = True # Smart double quotes
self.doReplaceDash = True # Replace multiple hyphens with dashes
self.doReplaceDots = True # Replace three dots with ellipsis

self.autoScroll = False # Typewriter-like scrolling
self.autoScrollPos = 30 # Start point for typewriter-like scrolling
self.scrollPastEnd = True # Scroll past end of document, and centre cursor

self.dialogStyle = 2 # Quote type to use for dialogue
self.allowOpenDial = True # Allow open-ended dialogue quotes
self.narratorBreak = "" # Symbol to use for narrator break
self.dialogLine = "" # Symbol to use for dialogue line
self.altDialogOpen = "" # Alternative dialog symbol, open
self.altDialogClose = "" # Alternative dialog symbol, close
self.highlightEmph = True # Add colour to text emphasis

self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
self.incNotesWCount = True # The status bar word count includes notes

# User-Selected Symbol Settings
self.fmtApostrophe = nwUnicode.U_RSQUO
Expand Down Expand Up @@ -362,23 +360,53 @@ def setBackupPath(self, path: Path | str) -> None:
self._backupPath = checkPath(path, self._backPath)
return

def setTextFont(self, family: str | None, pointSize: int = 12) -> None:
def setGuiFont(self, value: QFont | str | None) -> None:
"""Update the GUI's font style from settings."""
if isinstance(value, QFont):
self.guiFont = value
elif value and isinstance(value, str):
self.guiFont = QFont()
self.guiFont.fromString(value)
else:
font = QFont()
fontDB = QFontDatabase()
if self.osWindows and "Arial" in fontDB.families():
# On Windows we default to Arial if possible
font.setFamily("Arial")
font.setPointSize(10)
else:
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.guiFont = font
logger.debug("GUI font set to: %s", describeFont(font))

QApplication.setFont(self.guiFont)

return

def setTextFont(self, value: QFont | str | None) -> None:
"""Set the text font if it exists. If it doesn't, or is None,
set to default font.
"""
fontDB = QFontDatabase()
fontFam = fontDB.families()
self.textSize = pointSize
if family is None or family not in fontFam:
logger.warning("Unknown font '%s'", family)
if isinstance(value, QFont):
self.textFont = value
elif value and isinstance(value, str):
self.textFont = QFont()
self.textFont.fromString(value)
else:
fontDB = QFontDatabase()
fontFam = fontDB.families()
if self.osWindows and "Arial" in fontFam:
self.textFont = "Arial"
font = QFont()
font.setFamily("Arial")
font.setPointSize(12)
elif self.osDarwin and "Helvetica" in fontFam:
self.textFont = "Helvetica"
font = QFont()
font.setFamily("Helvetica")
font.setPointSize(12)
else:
self.textFont = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont).family()
else:
self.textFont = family
font = fontDB.systemFont(QFontDatabase.SystemFont.GeneralFont)
self.textFont = font
logger.debug("Text font set to: %s", describeFont(font))
return

##
Expand Down Expand Up @@ -502,12 +530,6 @@ def initConfig(self, confPath: str | Path | None = None,
(self._dataPath / "syntax").mkdir(exist_ok=True)
(self._dataPath / "themes").mkdir(exist_ok=True)

# Check if config file exists, and load it. If not, we save defaults
if (self._confPath / nwFiles.CONF_FILE).is_file():
self.loadConfig()
else:
self.saveConfig()

self._recentObj.loadCache()
self._checkOptionalPackages()

Expand Down Expand Up @@ -548,6 +570,14 @@ def loadConfig(self) -> bool:

conf = NWConfigParser()
cnfPath = self._confPath / nwFiles.CONF_FILE

if not cnfPath.exists():
# Initial file, so we just create one from defaults
self.setGuiFont(None)
self.setTextFont(None)
self.saveConfig()
return True

try:
with open(cnfPath, mode="r", encoding="utf-8") as inFile:
conf.read_file(inFile)
Expand All @@ -561,10 +591,9 @@ def loadConfig(self) -> bool:

# Main
sec = "Main"
self.setGuiFont(conf.rdStr(sec, "font", ""))
self.guiTheme = conf.rdStr(sec, "theme", self.guiTheme)
self.guiSyntax = conf.rdStr(sec, "syntax", self.guiSyntax)
self.guiFont = conf.rdStr(sec, "font", self.guiFont)
self.guiFontSize = conf.rdInt(sec, "fontsize", self.guiFontSize)
self.guiLocale = conf.rdStr(sec, "localisation", self.guiLocale)
self.hideVScroll = conf.rdBool(sec, "hidevscroll", self.hideVScroll)
self.hideHScroll = conf.rdBool(sec, "hidehscroll", self.hideHScroll)
Expand All @@ -591,8 +620,7 @@ def loadConfig(self) -> bool:

# Editor
sec = "Editor"
self.textFont = conf.rdStr(sec, "textfont", self.textFont)
self.textSize = conf.rdInt(sec, "textsize", self.textSize)
self.setTextFont(conf.rdStr(sec, "textfont", ""))
self.textWidth = conf.rdInt(sec, "width", self.textWidth)
self.textMargin = conf.rdInt(sec, "margin", self.textMargin)
self.tabWidth = conf.rdInt(sec, "tabwidth", self.tabWidth)
Expand Down Expand Up @@ -672,10 +700,9 @@ def saveConfig(self) -> bool:
}

conf["Main"] = {
"font": self.guiFont.toString(),
"theme": str(self.guiTheme),
"syntax": str(self.guiSyntax),
"font": str(self.guiFont),
"fontsize": str(self.guiFontSize),
"localisation": str(self.guiLocale),
"hidevscroll": str(self.hideVScroll),
"hidehscroll": str(self.hideHScroll),
Expand All @@ -702,8 +729,7 @@ def saveConfig(self) -> bool:
}

conf["Editor"] = {
"textfont": str(self.textFont),
"textsize": str(self.textSize),
"textfont": self.textFont.toString(),
"width": str(self.textWidth),
"margin": str(self.textMargin),
"tabwidth": str(self.tabWidth),
Expand Down
2 changes: 1 addition & 1 deletion novelwriter/core/buildsettings.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@
"text.includeBodyText": (bool, True),
"text.ignoredKeywords": (str, ""),
"text.addNoteHeadings": (bool, True),
"format.textFont": (str, CONFIG.textFont),
"format.textFont": (str, CONFIG.textFont.family()),
"format.textSize": (int, 12),
"format.lineHeight": (float, 1.15, 0.75, 3.0),
"format.justifyText": (bool, False),
Expand Down
2 changes: 1 addition & 1 deletion novelwriter/core/docbuild.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def _setupBuild(self, bldObj: Tokenizer) -> dict:
textFont = self._build.getStr("format.textFont")
textSize = self._build.getInt("format.textSize")

fontFamily = textFont or CONFIG.textFont
fontFamily = textFont or CONFIG.textFont.family()
bldFont = QFont(fontFamily, textSize)
fontInfo = QFontInfo(bldFont)
textFixed = fontInfo.fixedPitch()
Expand Down
Loading

0 comments on commit d2fca3b

Please sign in to comment.