From d08843afc75dfb3a847c073659f5c2a8b55f0731 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 22:59:44 +0200 Subject: [PATCH 1/8] Merged icons and themes files --- nw/gui/__init__.py | 2 +- nw/gui/icons.py | 327 --------------------------------------------- nw/gui/theme.py | 307 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 304 insertions(+), 332 deletions(-) delete mode 100644 nw/gui/icons.py diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 8360a460..b1912bbb 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -2,7 +2,7 @@ # Main Window Elements from nw.gui.build import GuiBuildNovel -from nw.gui.icons import GuiIcons +from nw.gui.theme import GuiIcons from nw.gui.mainmenu import GuiMainMenu from nw.gui.statusbar import GuiMainStatus from nw.gui.theme import GuiTheme diff --git a/nw/gui/icons.py b/nw/gui/icons.py deleted file mode 100644 index f1a33145..00000000 --- a/nw/gui/icons.py +++ /dev/null @@ -1,327 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Icons Class - - novelWriter – Icons Class -=========================== - This class manages the GUI icons - - File History: - Created: 2019-11-08 [0.4.0] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import configparser -import nw - -from os import path, listdir - -from PyQt5.QtCore import QSize -from PyQt5.QtSvg import QSvgWidget -from PyQt5.QtGui import QIcon, QPixmap -from PyQt5.QtWidgets import QStyle, qApp - -logger = logging.getLogger(__name__) - -class GuiIcons: - """The icon class manages the content of the assets/icons folder, - and provides a simple interface for requesting icons. Only icons - listed in the ICON_MAP are handled. - - Icons are loaded on first request, and then cached for further - requests. Each icon key in the ICON_MAP has a series of fallbacks: - * The first lookup is in the key-to-file map for the selected icon - theme. The map is specified in the icons.conf file in the theme - folder. The map makes it possible to preserve the original file - name from the icon theme were the icons were extracted. - * Second, if the icon does not exist in the theme map, the - GuiIcons class will check if there is a QStyle icon specified in - the ICON_MAP data tuple[0]. This will let Qt pull the closest - system icon. - * Third action is to look up the freedesktop icon theme name using - the fromTheme Qt call. This generally produces the same results - as the step above, but has more icons available in other cases. - * Fourth, and finally, the icon is looked up in the fallback - folder. Files in this folder must have the same file name as the - novelWriter internal icon key, with '-dark' appended to it for - the dark background version of the icon. - """ - - ICON_MAP = { - # Project and GUI icons - "cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), - "proj_document" : (QStyle.SP_FileIcon, "x-office-document"), - "proj_folder" : (QStyle.SP_DirIcon, "folder"), - "proj_nwx" : (None, None), - "status_lang" : (None, None), - "status_time" : (None, None), - "status_stats" : (None, None), - - ## General Button Icons - "folder-open" : (QStyle.SP_DirOpenIcon, "folder-open"), - "delete" : (QStyle.SP_DialogDiscardButton, "edit-delete"), - "add" : (None, "list-add"), - "remove" : (None, "list-remove"), - "close" : (QStyle.SP_DialogCloseButton, "window-close"), - "done" : (QStyle.SP_DialogApplyButton, None), - "search" : (None, "edit-find"), - "search-replace" : (None, "edit-find-replace"), - "clear" : (QStyle.SP_LineEditClearButton, "clear_left"), - "save" : (QStyle.SP_DialogSaveButton, "document-save"), - "edit" : (None, None), - "check" : (None, None), - "cross" : (None, None), - - ## Other Icons - "warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), - } - - DECO_MAP = { - "nwicon" : ["icons", "novelwriter.svg"], - } - - def __init__(self, theParent): - - self.mainConf = nw.CONFIG - self.theParent = theParent - - # Storage - self.qIcons = {} - self.themeMap = {} - self.themeList = [] - self.fbackName = "fallback" - self.confName = "icons.conf" - - # Icon Theme Path - self.iconPath = None - self.confFile = None - - # Icon Theme Meta - self.themeName = "" - self.themeDescription = "" - self.themeAuthor = "" - self.themeCredit = "" - self.themeUrl = "" - self.themeLicense = "" - self.themeLicenseUrl = "" - - return - - ## - # Actions - ## - - def updateTheme(self): - """Update the theme map. This is more of an init, since many of - the GUI icons cannot really be replaced without writing specific - update functions for the classes where they're used. - """ - - logger.debug("Loading icon theme files") - - self.themeMap = {} - checkPath = path.join(self.mainConf.iconPath, self.mainConf.guiIcons) - if path.isdir(checkPath): - logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons) - self.iconPath = checkPath - self.confFile = path.join(checkPath, self.confName) - else: - return False - - # Config File - confParser = configparser.ConfigParser() - try: - confParser.read_file(open(self.confFile, mode="r", encoding="utf8")) - except Exception as e: - logger.error("Could not load icon theme settings from: %s" % self.confFile) - return False - - ## Main - cnfSec = "Main" - if confParser.has_section(cnfSec): - self.themeName = self._parseLine( confParser, cnfSec, "name", "") - self.themeDescription = self._parseLine( confParser, cnfSec, "description", "") - self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "") - self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "") - self.themeUrl = self._parseLine( confParser, cnfSec, "url", "") - self.themeLicense = self._parseLine( confParser, cnfSec, "license", "") - self.themeLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "") - - ## Palette - cnfSec = "Map" - if confParser.has_section(cnfSec): - for iconName, iconFile in confParser.items(cnfSec): - if iconName not in self.ICON_MAP: - logger.error("Unknown icon name '%s' in config file" % iconName) - else: - iconPath = path.join(self.iconPath, iconFile) - if path.isfile(iconPath): - self.themeMap[iconName] = iconPath - logger.verbose("Icon slot '%s' using file '%s'" % (iconName, iconFile)) - else: - logger.error("Icon file '%s' not in theme folder" % iconFile) - - logger.info("Loaded icon theme '%s'" % self.mainConf.guiIcons) - - return True - - ## - # Access Functions - ## - - def loadDecoration(self, decoKey, decoSize=None): - """Load graphical decoration element based on the decoration - map. This function always returns a QSwgWidget. - """ - if decoKey not in self.DECO_MAP: - logger.error("Decoration with name '%s' does not exist" % decoKey) - return QSvgWidget() - - svgPath = path.join( - self.mainConf.assetPath, - self.DECO_MAP[decoKey][0], - self.DECO_MAP[decoKey][1] - ) - if not path.isfile(svgPath): - logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey]) - return QSvgWidget() - - svgDeco = QSvgWidget(svgPath) - if decoSize is not None: - svgDeco.setFixedSize(QSize(decoSize[0],decoSize[1])) - - return svgDeco - - def getIcon(self, iconKey, iconSize=None): - """Return an icon from the icon buffer. If it doesn't exist, - return, load it, and if it still doesn't exist, return an empty - icon. - """ - if iconKey in self.qIcons: - return self.qIcons[iconKey] - else: - qIcon = self._loadIcon(iconKey) - self.qIcons[iconKey] = qIcon - return qIcon - - def getPixmap(self, iconKey, iconSize): - """Return an icon from the icon buffer as a QPixmap. If it - doesn't exist, return an empty QPixmap. - """ - qIcon = self.getIcon(iconKey) - return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal) - - def listThemes(self): - """Scan the icons themes folder and list all themes. - """ - if self.themeList: - return self.themeList - - confParser = configparser.ConfigParser() - for themeDir in listdir(self.mainConf.iconPath): - themePath = path.join(self.mainConf.iconPath, themeDir) - if not path.isdir(themePath) or themeDir == self.fbackName: - continue - themeConf = path.join(themePath, self.confName) - logger.verbose("Checking icon theme config for '%s'" % themeDir) - try: - confParser.read_file(open(themeConf, mode="r", encoding="utf8")) - except Exception as e: - self.theParent.makeAlert( - ["Could not load theme config file.",str(e)], nwAlert.ERROR - ) - continue - themeName = "" - if confParser.has_section("Main"): - if confParser.has_option("Main", "name"): - themeName = confParser.get("Main", "name") - logger.verbose("Theme name is '%s'" % themeName) - if themeName != "": - self.themeList.append((themeDir, themeName)) - - self.themeList = sorted(self.themeList, key=lambda x: x[1]) - - return self.themeList - - ## - # Internal Functions - ## - - def _loadIcon(self, iconKey): - """Load an icon from the assets or theme folder, with a - preference for dark/light icons depending on theme type, if such - an icon exists. Prefer svg files over png files. Always returns - a QIcon. - """ - - if iconKey not in self.ICON_MAP: - logger.error("Requested unknown icon name '%s'" % iconKey) - return QIcon() - - if iconKey in self.themeMap: - logger.verbose("Loading: %s" % path.relpath(self.themeMap[iconKey])) - return QIcon(self.themeMap[iconKey]) - - # Next, we try to load the Qt style icons - if self.ICON_MAP[iconKey][0] is not None: - logger.verbose("Loading icon '%s' from Qt QStyle.standardIcon" % iconKey) - return qApp.style().standardIcon(self.ICON_MAP[iconKey][0]) - - # If we're still here, try to set from system theme - if self.ICON_MAP[iconKey][1] is not None: - logger.verbose("Loading icon '%s' from system theme" % iconKey) - if QIcon().hasThemeIcon(self.ICON_MAP[iconKey][1]): - return QIcon().fromTheme(self.ICON_MAP[iconKey][1]) - - # Finally. we check if we have a fallback icon - if self.mainConf.guiDark: - fbackIcon = path.join( - self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey - ) - if path.isfile(fbackIcon): - logger.verbose("Loading icon '%s' from fallback theme" % iconKey) - return QIcon(fbackIcon) - fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey) - if path.isfile(fbackIcon): - logger.verbose("Loading icon '%s' from fallback theme" % iconKey) - return QIcon(fbackIcon) - - # Give up and return an empty icon - logger.warning("Did not load an icon for '%s'" % iconKey) - - return QIcon() - - def _parseLine(self, confParser, cnfSec, cnfName, cnfDefault): - """Simple wrapper for the config parser check for entry existing - before arrempting to load. - """ - if confParser.has_section(cnfSec): - if confParser.has_option(cnfSec, cnfName): - return confParser.get(cnfSec, cnfName) - return cnfDefault - -# END Class GuiIcons diff --git a/nw/gui/theme.py b/nw/gui/theme.py index a376e1b1..8613c683 100644 --- a/nw/gui/theme.py +++ b/nw/gui/theme.py @@ -6,7 +6,8 @@ This class reads and store the main theme File History: - Created: 2019-05-18 [0.1.3] + Created: 2019-05-18 [0.1.3] GuiTheme + Created: 2019-11-08 [0.4.0] GuiIcons This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen @@ -32,16 +33,22 @@ import nw from os import path, listdir from math import ceil -from PyQt5.QtWidgets import qApp +from PyQt5.QtCore import QSize +from PyQt5.QtSvg import QSvgWidget +from PyQt5.QtWidgets import QStyle, qApp from PyQt5.QtGui import ( - QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase + QPalette, QColor, QIcon, QFont, QFontMetrics, QFontDatabase, QPixmap ) from nw.constants import nwAlert -from nw.gui.icons import GuiIcons logger = logging.getLogger(__name__) +# =============================================================================================== # +# Gui Theme Class +# Handles the look and feel of novelWriter +# =============================================================================================== # + class GuiTheme: def __init__(self, theParent): @@ -447,3 +454,295 @@ class GuiTheme: return cnfDefault # End Class GuiTheme + +# =============================================================================================== # +# Icons Class +# =============================================================================================== # + +class GuiIcons: + """The icon class manages the content of the assets/icons folder, + and provides a simple interface for requesting icons. Only icons + listed in the ICON_MAP are handled. + + Icons are loaded on first request, and then cached for further + requests. Each icon key in the ICON_MAP has a series of fallbacks: + * The first lookup is in the key-to-file map for the selected icon + theme. The map is specified in the icons.conf file in the theme + folder. The map makes it possible to preserve the original file + name from the icon theme were the icons were extracted. + * Second, if the icon does not exist in the theme map, the + GuiIcons class will check if there is a QStyle icon specified in + the ICON_MAP data tuple[0]. This will let Qt pull the closest + system icon. + * Third action is to look up the freedesktop icon theme name using + the fromTheme Qt call. This generally produces the same results + as the step above, but has more icons available in other cases. + * Fourth, and finally, the icon is looked up in the fallback + folder. Files in this folder must have the same file name as the + novelWriter internal icon key, with '-dark' appended to it for + the dark background version of the icon. + """ + + ICON_MAP = { + # Project and GUI icons + "cls_none" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_novel" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_plot" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_character" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_world" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_timeline" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_object" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_entity" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_custom" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "cls_trash" : (QStyle.SP_DriveHDIcon, "drive-harddisk"), + "proj_document" : (QStyle.SP_FileIcon, "x-office-document"), + "proj_folder" : (QStyle.SP_DirIcon, "folder"), + "proj_nwx" : (None, None), + "status_lang" : (None, None), + "status_time" : (None, None), + "status_stats" : (None, None), + + ## General Button Icons + "folder-open" : (QStyle.SP_DirOpenIcon, "folder-open"), + "delete" : (QStyle.SP_DialogDiscardButton, "edit-delete"), + "add" : (None, "list-add"), + "remove" : (None, "list-remove"), + "close" : (QStyle.SP_DialogCloseButton, "window-close"), + "done" : (QStyle.SP_DialogApplyButton, None), + "search" : (None, "edit-find"), + "search-replace" : (None, "edit-find-replace"), + "clear" : (QStyle.SP_LineEditClearButton, "clear_left"), + "save" : (QStyle.SP_DialogSaveButton, "document-save"), + "edit" : (None, None), + "check" : (None, None), + "cross" : (None, None), + + ## Other Icons + "warning" : (QStyle.SP_MessageBoxWarning, "dialog-warning"), + } + + DECO_MAP = { + "nwicon" : ["icons", "novelwriter.svg"], + } + + def __init__(self, theParent): + + self.mainConf = nw.CONFIG + self.theParent = theParent + + # Storage + self.qIcons = {} + self.themeMap = {} + self.themeList = [] + self.fbackName = "fallback" + self.confName = "icons.conf" + + # Icon Theme Path + self.iconPath = None + self.confFile = None + + # Icon Theme Meta + self.themeName = "" + self.themeDescription = "" + self.themeAuthor = "" + self.themeCredit = "" + self.themeUrl = "" + self.themeLicense = "" + self.themeLicenseUrl = "" + + return + + ## + # Actions + ## + + def updateTheme(self): + """Update the theme map. This is more of an init, since many of + the GUI icons cannot really be replaced without writing specific + update functions for the classes where they're used. + """ + + logger.debug("Loading icon theme files") + + self.themeMap = {} + checkPath = path.join(self.mainConf.iconPath, self.mainConf.guiIcons) + if path.isdir(checkPath): + logger.debug("Loading icon theme '%s'" % self.mainConf.guiIcons) + self.iconPath = checkPath + self.confFile = path.join(checkPath, self.confName) + else: + return False + + # Config File + confParser = configparser.ConfigParser() + try: + confParser.read_file(open(self.confFile, mode="r", encoding="utf8")) + except Exception as e: + logger.error("Could not load icon theme settings from: %s" % self.confFile) + return False + + ## Main + cnfSec = "Main" + if confParser.has_section(cnfSec): + self.themeName = self._parseLine( confParser, cnfSec, "name", "") + self.themeDescription = self._parseLine( confParser, cnfSec, "description", "") + self.themeAuthor = self._parseLine( confParser, cnfSec, "author", "") + self.themeCredit = self._parseLine( confParser, cnfSec, "credit", "") + self.themeUrl = self._parseLine( confParser, cnfSec, "url", "") + self.themeLicense = self._parseLine( confParser, cnfSec, "license", "") + self.themeLicenseUrl = self._parseLine( confParser, cnfSec, "licenseurl", "") + + ## Palette + cnfSec = "Map" + if confParser.has_section(cnfSec): + for iconName, iconFile in confParser.items(cnfSec): + if iconName not in self.ICON_MAP: + logger.error("Unknown icon name '%s' in config file" % iconName) + else: + iconPath = path.join(self.iconPath, iconFile) + if path.isfile(iconPath): + self.themeMap[iconName] = iconPath + logger.verbose("Icon slot '%s' using file '%s'" % (iconName, iconFile)) + else: + logger.error("Icon file '%s' not in theme folder" % iconFile) + + logger.info("Loaded icon theme '%s'" % self.mainConf.guiIcons) + + return True + + ## + # Access Functions + ## + + def loadDecoration(self, decoKey, decoSize=None): + """Load graphical decoration element based on the decoration + map. This function always returns a QSwgWidget. + """ + if decoKey not in self.DECO_MAP: + logger.error("Decoration with name '%s' does not exist" % decoKey) + return QSvgWidget() + + svgPath = path.join( + self.mainConf.assetPath, + self.DECO_MAP[decoKey][0], + self.DECO_MAP[decoKey][1] + ) + if not path.isfile(svgPath): + logger.error("Decoration file '%s' not in assets folder" % self.DECO_MAP[decoKey]) + return QSvgWidget() + + svgDeco = QSvgWidget(svgPath) + if decoSize is not None: + svgDeco.setFixedSize(QSize(decoSize[0],decoSize[1])) + + return svgDeco + + def getIcon(self, iconKey, iconSize=None): + """Return an icon from the icon buffer. If it doesn't exist, + return, load it, and if it still doesn't exist, return an empty + icon. + """ + if iconKey in self.qIcons: + return self.qIcons[iconKey] + else: + qIcon = self._loadIcon(iconKey) + self.qIcons[iconKey] = qIcon + return qIcon + + def getPixmap(self, iconKey, iconSize): + """Return an icon from the icon buffer as a QPixmap. If it + doesn't exist, return an empty QPixmap. + """ + qIcon = self.getIcon(iconKey) + return qIcon.pixmap(iconSize[0], iconSize[1], QIcon.Normal) + + def listThemes(self): + """Scan the icons themes folder and list all themes. + """ + if self.themeList: + return self.themeList + + confParser = configparser.ConfigParser() + for themeDir in listdir(self.mainConf.iconPath): + themePath = path.join(self.mainConf.iconPath, themeDir) + if not path.isdir(themePath) or themeDir == self.fbackName: + continue + themeConf = path.join(themePath, self.confName) + logger.verbose("Checking icon theme config for '%s'" % themeDir) + try: + confParser.read_file(open(themeConf, mode="r", encoding="utf8")) + except Exception as e: + self.theParent.makeAlert( + ["Could not load theme config file.",str(e)], nwAlert.ERROR + ) + continue + themeName = "" + if confParser.has_section("Main"): + if confParser.has_option("Main", "name"): + themeName = confParser.get("Main", "name") + logger.verbose("Theme name is '%s'" % themeName) + if themeName != "": + self.themeList.append((themeDir, themeName)) + + self.themeList = sorted(self.themeList, key=lambda x: x[1]) + + return self.themeList + + ## + # Internal Functions + ## + + def _loadIcon(self, iconKey): + """Load an icon from the assets or theme folder, with a + preference for dark/light icons depending on theme type, if such + an icon exists. Prefer svg files over png files. Always returns + a QIcon. + """ + + if iconKey not in self.ICON_MAP: + logger.error("Requested unknown icon name '%s'" % iconKey) + return QIcon() + + if iconKey in self.themeMap: + logger.verbose("Loading: %s" % path.relpath(self.themeMap[iconKey])) + return QIcon(self.themeMap[iconKey]) + + # Next, we try to load the Qt style icons + if self.ICON_MAP[iconKey][0] is not None: + logger.verbose("Loading icon '%s' from Qt QStyle.standardIcon" % iconKey) + return qApp.style().standardIcon(self.ICON_MAP[iconKey][0]) + + # If we're still here, try to set from system theme + if self.ICON_MAP[iconKey][1] is not None: + logger.verbose("Loading icon '%s' from system theme" % iconKey) + if QIcon().hasThemeIcon(self.ICON_MAP[iconKey][1]): + return QIcon().fromTheme(self.ICON_MAP[iconKey][1]) + + # Finally. we check if we have a fallback icon + if self.mainConf.guiDark: + fbackIcon = path.join( + self.mainConf.iconPath, self.fbackName, "%s-dark.svg" % iconKey + ) + if path.isfile(fbackIcon): + logger.verbose("Loading icon '%s' from fallback theme" % iconKey) + return QIcon(fbackIcon) + fbackIcon = path.join(self.mainConf.iconPath, self.fbackName, "%s.svg" % iconKey) + if path.isfile(fbackIcon): + logger.verbose("Loading icon '%s' from fallback theme" % iconKey) + return QIcon(fbackIcon) + + # Give up and return an empty icon + logger.warning("Did not load an icon for '%s'" % iconKey) + + return QIcon() + + def _parseLine(self, confParser, cnfSec, cnfName, cnfDefault): + """Simple wrapper for the config parser check for entry existing + before arrempting to load. + """ + if confParser.has_section(cnfSec): + if confParser.has_option(cnfSec, cnfName): + return confParser.get(cnfSec, cnfName) + return cnfDefault + +# END Class GuiIcons From 6f7d66cb3110d8709555c26ea2efe510ea822da5 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 23:08:40 +0200 Subject: [PATCH 2/8] Merged the custom widgets and layouts into one file --- nw/gui/additions/__init__.py | 4 +- nw/gui/additions/pageddialog.py | 125 ---------------- nw/gui/additions/qconfiglayout.py | 238 +++++++++++++++++++++++++++++- nw/gui/additions/qswitch.py | 164 -------------------- 4 files changed, 236 insertions(+), 295 deletions(-) delete mode 100644 nw/gui/additions/pageddialog.py delete mode 100644 nw/gui/additions/qswitch.py diff --git a/nw/gui/additions/__init__.py b/nw/gui/additions/__init__.py index dbacd16e..83c73cdc 100644 --- a/nw/gui/additions/__init__.py +++ b/nw/gui/additions/__init__.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- -from nw.gui.additions.pageddialog import PagedDialog +from nw.gui.additions.qconfiglayout import PagedDialog from nw.gui.additions.qconfiglayout import QConfigLayout from nw.gui.additions.qconfiglayout import QHelpLabel -from nw.gui.additions.qswitch import QSwitch +from nw.gui.additions.qconfiglayout import QSwitch __all__ = [ "PagedDialog", diff --git a/nw/gui/additions/pageddialog.py b/nw/gui/additions/pageddialog.py deleted file mode 100644 index 2b65cbb9..00000000 --- a/nw/gui/additions/pageddialog.py +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Paged Dialog - - novelWriter – Paged Dialog -============================ - A custom QDialog with a built-in QTabWidget and vertical tabs. - - File History: - Created: 2020-05-17 [0.5.1] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from PyQt5.QtCore import Qt, QRect, QPoint -from PyQt5.QtWidgets import ( - QDialog, QHBoxLayout, QVBoxLayout, QTabWidget, QTabBar, QStyle, - QStylePainter, QStyleOptionTab -) - -logger = logging.getLogger(__name__) - -class PagedDialog(QDialog): - - def __init__(self, theParent=None): - QDialog.__init__(self, parent=theParent) - - self._outerBox = QVBoxLayout() - self._buttonBox = QHBoxLayout() - self._tabBox = QTabWidget() - - self._tabBar = VerticalTabBar(self) - self._tabBox.setTabBar(self._tabBar) - self._tabBox.setTabPosition(QTabWidget.West) - self._tabBar.setExpanding(False) - - self._outerBox.addWidget(self._tabBox) - self._outerBox.addLayout(self._buttonBox) - self.setLayout(self._outerBox) - - # Default Margins - qM = self._outerBox.contentsMargins() - mL = qM.left() - mR = qM.right() - mT = qM.top() - mB = qM.bottom() - - self.setContentsMargins(0, 0, 0, 0) - self._outerBox.setContentsMargins(0, 0, 0, mB) - self._buttonBox.setContentsMargins(mL, 0, mR, 0) - self._outerBox.setSpacing(mT) - - return - - def addTab(self, tabWidget, tabLabel): - """Forwards the adding of tabs to the QTabWidget. - """ - self._tabBox.addTab(tabWidget, tabLabel) - return - - def addControls(self, buttonBar): - """Adds a button bar to the dialog. - """ - self._buttonBox.addWidget(buttonBar) - return - -# END Class PagedDialog - -class VerticalTabBar(QTabBar): - - def __init__(self, theParent=None): - QTabBar.__init__(self, parent=theParent) - return - - def tabSizeHint(self, theIndex): - """Returns a transposed size hint for the rotated bar. - """ - tSize = QTabBar.tabSizeHint(self, theIndex) - tSize.transpose() - return tSize - - def paintEvent(self, theEvent): - """Custom implementation of the label painter that rotates the - label 90 degrees. - """ - pObj = QStylePainter(self) - oObj = QStyleOptionTab() - - for i in range(self.count()): - self.initStyleOption(oObj, i) - pObj.drawControl(QStyle.CE_TabBarTabShape, oObj) - pObj.save() - - oSize = oObj.rect.size() - oSize.transpose() - oRect = QRect(QPoint(), oSize) - oRect.moveCenter(oObj.rect.center()) - oObj.rect = oRect - - oCenter = self.tabRect(i).center() - pObj.translate(oCenter) - pObj.rotate(90) - pObj.translate(-oCenter) - pObj.drawControl(QStyle.CE_TabBarTabLabel, oObj) - pObj.restore() - - return - -# END Class VerticalTabBar diff --git a/nw/gui/additions/qconfiglayout.py b/nw/gui/additions/qconfiglayout.py index 70b3042d..96e0c482 100644 --- a/nw/gui/additions/qconfiglayout.py +++ b/nw/gui/additions/qconfiglayout.py @@ -6,7 +6,9 @@ A custom Qt grid layout for config forms similar to QFormLayout File History: - Created: 2020-05-03 [0.4.5] + Created: 2020-05-03 [0.4.5] QConfigLayout + Created: 2020-05-03 [0.4.5] QSwitch + Created: 2020-05-17 [0.5.1] PagedDialog This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen @@ -28,16 +30,24 @@ import logging import nw -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QColor, QPalette +from PyQt5.QtGui import QColor, QPalette, QPainter +from PyQt5.QtCore import ( + Qt, QRect, QPoint, QSize, QRectF, QPropertyAnimation, pyqtProperty +) from PyQt5.QtWidgets import ( - QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout + QGridLayout, QLabel, QWidget, QVBoxLayout, QHBoxLayout, QSizePolicy, + QAbstractButton, QDialog, QTabWidget, QTabBar, QStyle, + QStylePainter, QStyleOptionTab ) from nw.constants import nwUnicode logger = logging.getLogger(__name__) +# =============================================================================================== # +# Config FOrm Layout +# =============================================================================================== # + class QConfigLayout(QGridLayout): def __init__(self): @@ -198,3 +208,223 @@ class QHelpLabel(QLabel): return # END Class QHelpLabel + +# =============================================================================================== # +# Switch Widget +# =============================================================================================== # + +class QSwitch(QAbstractButton): + + def __init__(self, parent=None): + super().__init__(parent=parent) + + self.setCheckable(True) + self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) + self.setFixedWidth(40) + self.setFixedHeight(20) + self._offset = 10 + + return + + ## + # Properties + ## + + @pyqtProperty(int) + def offset(self): + return self._offset + + @offset.setter + def offset(self, theOffset): + self._offset = theOffset + self.update() + return + + ## + # Getters and Setters + ## + + def setChecked(self, isChecked): + """Overload setChecked to also alter the offset. + """ + super().setChecked(isChecked) + if isChecked: + self.offset = 30 + else: + self.offset = 10 + return + + ## + # Events + ## + + def resizeEvent(self, theEvent): + """Overload resize to ensure correct offset. + """ + super().resizeEvent(theEvent) + if self.isChecked(): + self.offset = 30 + else: + self.offset = 10 + return + + def paintEvent(self, event): + """Drawing the switch itself. + """ + qPaint = QPainter(self) + qPaint.setRenderHint(QPainter.Antialiasing, True) + qPaint.setPen(Qt.NoPen) + + qPalette = self.palette() + if self.isChecked(): + trackBrush = qPalette.highlight() + thumbBrush = qPalette.highlightedText() + textColor = qPalette.highlight().color() + thumbText = nwUnicode.U_CHECK + else: + trackBrush = qPalette.dark() + thumbBrush = qPalette.light() + textColor = qPalette.dark().color() + thumbText = nwUnicode.U_MULT + + if self.isEnabled(): + trackOpacity = 1.0 + else: + trackOpacity = 0.6 + trackBrush = qPalette.shadow() + thumbBrush = qPalette.mid() + textColor = qPalette.shadow().color() + + qPaint.setBrush(trackBrush) + qPaint.setOpacity(trackOpacity) + qPaint.drawRoundedRect(0, 0, 40, 20, 10, 10) + + qPaint.setBrush(thumbBrush) + qPaint.drawEllipse(self.offset - 8, 2, 16, 16) + + theFont = qPaint.font() + theFont.setPixelSize(12) + qPaint.setPen(textColor) + qPaint.setFont(theFont) + qPaint.drawText( + QRectF(self.offset - 8, 2, 16, 16), + Qt.AlignCenter, thumbText + ) + + return + + def mouseReleaseEvent(self, event): + """Animate the switch on mouse release. + """ + super().mouseReleaseEvent(event) + if event.button() == Qt.LeftButton: + doAnim = QPropertyAnimation(self, b'offset', self) + doAnim.setDuration(120) + doAnim.setStartValue(self.offset) + if self.isChecked(): + doAnim.setEndValue(30) + else: + doAnim.setEndValue(10) + doAnim.start() + return + + def enterEvent(self, event): + """Change the cursor when hovering the button. + """ + self.setCursor(Qt.PointingHandCursor) + super().enterEvent(event) + return + +# END Class QSwitch + +# =============================================================================================== # +# Paged Dialog w/Custom TabWidget +# =============================================================================================== # + +class PagedDialog(QDialog): + + def __init__(self, theParent=None): + QDialog.__init__(self, parent=theParent) + + self._outerBox = QVBoxLayout() + self._buttonBox = QHBoxLayout() + self._tabBox = QTabWidget() + + self._tabBar = VerticalTabBar(self) + self._tabBox.setTabBar(self._tabBar) + self._tabBox.setTabPosition(QTabWidget.West) + self._tabBar.setExpanding(False) + + self._outerBox.addWidget(self._tabBox) + self._outerBox.addLayout(self._buttonBox) + self.setLayout(self._outerBox) + + # Default Margins + qM = self._outerBox.contentsMargins() + mL = qM.left() + mR = qM.right() + mT = qM.top() + mB = qM.bottom() + + self.setContentsMargins(0, 0, 0, 0) + self._outerBox.setContentsMargins(0, 0, 0, mB) + self._buttonBox.setContentsMargins(mL, 0, mR, 0) + self._outerBox.setSpacing(mT) + + return + + def addTab(self, tabWidget, tabLabel): + """Forwards the adding of tabs to the QTabWidget. + """ + self._tabBox.addTab(tabWidget, tabLabel) + return + + def addControls(self, buttonBar): + """Adds a button bar to the dialog. + """ + self._buttonBox.addWidget(buttonBar) + return + +# END Class PagedDialog + +class VerticalTabBar(QTabBar): + + def __init__(self, theParent=None): + QTabBar.__init__(self, parent=theParent) + return + + def tabSizeHint(self, theIndex): + """Returns a transposed size hint for the rotated bar. + """ + tSize = QTabBar.tabSizeHint(self, theIndex) + tSize.transpose() + return tSize + + def paintEvent(self, theEvent): + """Custom implementation of the label painter that rotates the + label 90 degrees. + """ + pObj = QStylePainter(self) + oObj = QStyleOptionTab() + + for i in range(self.count()): + self.initStyleOption(oObj, i) + pObj.drawControl(QStyle.CE_TabBarTabShape, oObj) + pObj.save() + + oSize = oObj.rect.size() + oSize.transpose() + oRect = QRect(QPoint(), oSize) + oRect.moveCenter(oObj.rect.center()) + oObj.rect = oRect + + oCenter = self.tabRect(i).center() + pObj.translate(oCenter) + pObj.rotate(90) + pObj.translate(-oCenter) + pObj.drawControl(QStyle.CE_TabBarTabLabel, oObj) + pObj.restore() + + return + +# END Class VerticalTabBar diff --git a/nw/gui/additions/qswitch.py b/nw/gui/additions/qswitch.py deleted file mode 100644 index 7eb32830..00000000 --- a/nw/gui/additions/qswitch.py +++ /dev/null @@ -1,164 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Addition QSwitch - - novelWriter – Addition QSwitch -================================ - A custom Qt switch button - - File History: - Created: 2020-05-03 [0.4.5] - - The core code of this class is based on Stack Overflow example by - Stefan Scherfke: https://stackoverflow.com/a/51825815/5825851 - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from PyQt5.QtCore import Qt, QSize, QRectF, QPropertyAnimation, pyqtProperty -from PyQt5.QtWidgets import QAbstractButton, QSizePolicy -from PyQt5.QtGui import QPainter - -from nw.constants import nwUnicode - -logger = logging.getLogger(__name__) - -class QSwitch(QAbstractButton): - - def __init__(self, parent=None): - super().__init__(parent=parent) - - self.setCheckable(True) - self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed) - self.setFixedWidth(40) - self.setFixedHeight(20) - self._offset = 10 - - return - - ## - # Properties - ## - - @pyqtProperty(int) - def offset(self): - return self._offset - - @offset.setter - def offset(self, theOffset): - self._offset = theOffset - self.update() - return - - ## - # Getters and Setters - ## - - def setChecked(self, isChecked): - """Overload setChecked to also alter the offset. - """ - super().setChecked(isChecked) - if isChecked: - self.offset = 30 - else: - self.offset = 10 - return - - ## - # Events - ## - - def resizeEvent(self, theEvent): - """Overload resize to ensure correct offset. - """ - super().resizeEvent(theEvent) - if self.isChecked(): - self.offset = 30 - else: - self.offset = 10 - return - - def paintEvent(self, event): - """Drawing the switch itself. - """ - qPaint = QPainter(self) - qPaint.setRenderHint(QPainter.Antialiasing, True) - qPaint.setPen(Qt.NoPen) - - qPalette = self.palette() - if self.isChecked(): - trackBrush = qPalette.highlight() - thumbBrush = qPalette.highlightedText() - textColor = qPalette.highlight().color() - thumbText = nwUnicode.U_CHECK - else: - trackBrush = qPalette.dark() - thumbBrush = qPalette.light() - textColor = qPalette.dark().color() - thumbText = nwUnicode.U_MULT - - if self.isEnabled(): - trackOpacity = 1.0 - else: - trackOpacity = 0.6 - trackBrush = qPalette.shadow() - thumbBrush = qPalette.mid() - textColor = qPalette.shadow().color() - - qPaint.setBrush(trackBrush) - qPaint.setOpacity(trackOpacity) - qPaint.drawRoundedRect(0, 0, 40, 20, 10, 10) - - qPaint.setBrush(thumbBrush) - qPaint.drawEllipse(self.offset - 8, 2, 16, 16) - - theFont = qPaint.font() - theFont.setPixelSize(12) - qPaint.setPen(textColor) - qPaint.setFont(theFont) - qPaint.drawText( - QRectF(self.offset - 8, 2, 16, 16), - Qt.AlignCenter, thumbText - ) - - return - - def mouseReleaseEvent(self, event): - """Animate the switch on mouse release. - """ - super().mouseReleaseEvent(event) - if event.button() == Qt.LeftButton: - doAnim = QPropertyAnimation(self, b'offset', self) - doAnim.setDuration(120) - doAnim.setStartValue(self.offset) - if self.isChecked(): - doAnim.setEndValue(30) - else: - doAnim.setEndValue(10) - doAnim.start() - return - - def enterEvent(self, event): - """Change the cursor when hovering the button. - """ - self.setCursor(Qt.PointingHandCursor) - super().enterEvent(event) - return - -# END Class QSwitch From 61831d9c0e3ff80adbedef4f02e9b597a05a1347 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 23:13:08 +0200 Subject: [PATCH 3/8] Moved the custom widgets classes to the root gui folder --- nw/gui/additions/__init__.py | 12 ------------ nw/gui/build.py | 2 +- nw/gui/{additions/qconfiglayout.py => custom.py} | 0 nw/gui/dialogs/docmerge.py | 2 +- nw/gui/dialogs/docsplit.py | 2 +- nw/gui/dialogs/itemeditor.py | 2 +- nw/gui/dialogs/preferences.py | 2 +- nw/gui/dialogs/projectsettings.py | 2 +- 8 files changed, 6 insertions(+), 18 deletions(-) delete mode 100644 nw/gui/additions/__init__.py rename nw/gui/{additions/qconfiglayout.py => custom.py} (100%) diff --git a/nw/gui/additions/__init__.py b/nw/gui/additions/__init__.py deleted file mode 100644 index 83c73cdc..00000000 --- a/nw/gui/additions/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 -*- -from nw.gui.additions.qconfiglayout import PagedDialog -from nw.gui.additions.qconfiglayout import QConfigLayout -from nw.gui.additions.qconfiglayout import QHelpLabel -from nw.gui.additions.qconfiglayout import QSwitch - -__all__ = [ - "PagedDialog", - "QConfigLayout", - "QHelpLabel", - "QSwitch", -] diff --git a/nw/gui/build.py b/nw/gui/build.py index 639f504d..791dff67 100644 --- a/nw/gui/build.py +++ b/nw/gui/build.py @@ -43,7 +43,7 @@ from PyQt5.QtWidgets import ( QFileDialog, QFontDialog, QSpinBox ) -from nw.gui.additions import QSwitch +from nw.gui.custom import QSwitch from nw.core import ToHtml from nw.constants import ( nwAlert, nwFiles, nwItemType, nwItemLayout, nwItemClass diff --git a/nw/gui/additions/qconfiglayout.py b/nw/gui/custom.py similarity index 100% rename from nw/gui/additions/qconfiglayout.py rename to nw/gui/custom.py diff --git a/nw/gui/dialogs/docmerge.py b/nw/gui/dialogs/docmerge.py index 1fc225a9..73b15a6a 100644 --- a/nw/gui/dialogs/docmerge.py +++ b/nw/gui/dialogs/docmerge.py @@ -35,7 +35,7 @@ from PyQt5.QtWidgets import ( ) from nw.constants import nwAlert, nwItemType -from nw.gui.additions import QHelpLabel +from nw.gui.custom import QHelpLabel from nw.core import NWDoc logger = logging.getLogger(__name__) diff --git a/nw/gui/dialogs/docsplit.py b/nw/gui/dialogs/docsplit.py index e029a49b..702e8f17 100644 --- a/nw/gui/dialogs/docsplit.py +++ b/nw/gui/dialogs/docsplit.py @@ -34,7 +34,7 @@ from PyQt5.QtWidgets import ( QListWidgetItem, QDialogButtonBox, QLabel ) from nw.constants import nwAlert, nwItemType, nwItemClass, nwItemLayout -from nw.gui.additions import QHelpLabel +from nw.gui.custom import QHelpLabel from nw.core import NWDoc logger = logging.getLogger(__name__) diff --git a/nw/gui/dialogs/itemeditor.py b/nw/gui/dialogs/itemeditor.py index ce1fac67..7eb54b14 100644 --- a/nw/gui/dialogs/itemeditor.py +++ b/nw/gui/dialogs/itemeditor.py @@ -34,7 +34,7 @@ from PyQt5.QtWidgets import ( QDialogButtonBox ) -from nw.gui.additions import QSwitch +from nw.gui.custom import QSwitch from nw.constants import nwLabels, nwItemLayout, nwItemClass, nwItemType logger = logging.getLogger(__name__) diff --git a/nw/gui/dialogs/preferences.py b/nw/gui/dialogs/preferences.py index 280993f5..a05e5eb0 100644 --- a/nw/gui/dialogs/preferences.py +++ b/nw/gui/dialogs/preferences.py @@ -37,7 +37,7 @@ from PyQt5.QtWidgets import ( QDialogButtonBox, QFileDialog, QFontDialog ) -from nw.gui.additions import QSwitch, QConfigLayout, PagedDialog +from nw.gui.custom import QSwitch, QConfigLayout, PagedDialog from nw.core import NWSpellCheck, NWSpellSimple, NWSpellEnchant from nw.constants import nwAlert, nwQuotes diff --git a/nw/gui/dialogs/projectsettings.py b/nw/gui/dialogs/projectsettings.py index 66af994a..a1c28ca4 100644 --- a/nw/gui/dialogs/projectsettings.py +++ b/nw/gui/dialogs/projectsettings.py @@ -38,7 +38,7 @@ from PyQt5.QtWidgets import ( ) from nw.constants import nwAlert -from nw.gui.additions import QSwitch, PagedDialog, QConfigLayout +from nw.gui.custom import QSwitch, PagedDialog, QConfigLayout logger = logging.getLogger(__name__) From a76562e025ef0a07981e6b3f14a17086bac65afc Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 23:23:11 +0200 Subject: [PATCH 4/8] Moved WordCounter class into doceditor file --- nw/gui/__init__.py | 2 -- nw/gui/elements/doceditor.py | 38 +++++++++++++++++++---- nw/gui/tools/__init__.py | 2 -- nw/gui/tools/wordcounter.py | 60 ------------------------------------ 4 files changed, 32 insertions(+), 70 deletions(-) delete mode 100644 nw/gui/tools/wordcounter.py diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index b1912bbb..0ce25673 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -31,7 +31,6 @@ from nw.gui.elements.viewdetails import GuiDocViewDetails # Tools from nw.gui.tools.dochighlight import GuiDocHighlighter from nw.gui.tools.optionstate import OptionState -from nw.gui.tools.wordcounter import WordCounter __all__ = [ "GuiBuildNovel", @@ -58,5 +57,4 @@ __all__ = [ "GuiDocViewDetails", "GuiDocHighlighter", "OptionState", - "WordCounter", ] diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index 8ceb297e..9d9f7fa2 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -6,7 +6,8 @@ Class holding the document editor File History: - Created: 2018-09-29 [0.0.1] + Created: 2018-09-29 [0.0.1] GuiDocEditor + Created: 2019-04-22 [0.0.1] WordCounter This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen @@ -30,7 +31,7 @@ import nw from time import time -from PyQt5.QtCore import Qt, QTimer, pyqtSlot +from PyQt5.QtCore import Qt, QThread, QTimer, pyqtSlot from PyQt5.QtWidgets import ( qApp, QTextEdit, QAction, QMenu, QShortcut, QMessageBox ) @@ -40,9 +41,9 @@ from PyQt5.QtGui import ( ) from nw.core import NWDoc -from nw.gui.tools import GuiDocHighlighter, WordCounter +from nw.gui.tools import GuiDocHighlighter from nw.gui.elements.doctitlebar import GuiDocTitleBar -from nw.core import NWSpellSimple +from nw.core import NWSpellSimple, countWords from nw.constants import nwUnicode, nwDocAction logger = logging.getLogger(__name__) @@ -52,7 +53,7 @@ class GuiDocEditor(QTextEdit): def __init__(self, theParent, theProject): QTextEdit.__init__(self) - logger.debug("Initialising DocEditor ...") + logger.debug("Initialising GuiDocEditor ...") # Class Variables self.mainConf = nw.CONFIG @@ -131,7 +132,7 @@ class GuiDocEditor(QTextEdit): self.initEditor() - logger.debug("DocEditor initialisation complete") + logger.debug("GuiDocEditor initialisation complete") # Connect Functions self.setSelectedHandle = self.theParent.treeView.setSelectedHandle @@ -1082,3 +1083,28 @@ class GuiDocEditor(QTextEdit): return # END Class GuiDocEditor + +class WordCounter(QThread): + + def __init__(self, theParent): + QThread.__init__(self, theParent) + self.theParent = theParent + self.charCount = 0 + self.wordCount = 0 + self.paraCount = 0 + return + + def run(self): + """Overloaded run function for the word counter, forwarding the + call to the function that does the actual counting. + """ + theText = self.theParent.getText() + cC, wC, pC = countWords(theText) + + self.charCount = cC + self.wordCount = wC + self.paraCount = pC + + return + +## END Class WordCounter diff --git a/nw/gui/tools/__init__.py b/nw/gui/tools/__init__.py index cc6cfdfa..e69db837 100644 --- a/nw/gui/tools/__init__.py +++ b/nw/gui/tools/__init__.py @@ -2,10 +2,8 @@ from nw.gui.tools.dochighlight import GuiDocHighlighter from nw.gui.tools.optionstate import OptionState -from nw.gui.tools.wordcounter import WordCounter __all__ = [ "GuiDocHighlighter", "OptionState", - "WordCounter", ] diff --git a/nw/gui/tools/wordcounter.py b/nw/gui/tools/wordcounter.py deleted file mode 100644 index 49255040..00000000 --- a/nw/gui/tools/wordcounter.py +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter GUI Document Word Counter - - novelWriter – GUI Document Word Counter -=================================== - A thread for counting words and characters in a document - - File History: - Created: 2019-04-22 [0.0.1] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from PyQt5.QtCore import QThread - -from nw.core.tools import countWords - -logger = logging.getLogger(__name__) - -class WordCounter(QThread): - - def __init__(self, theParent): - QThread.__init__(self, theParent) - self.theParent = theParent - self.charCount = 0 - self.wordCount = 0 - self.paraCount = 0 - return - - def run(self): - """Overloaded run function for the word counter, forwarding the - call to the function that does the actual counting. - """ - theText = self.theParent.getText() - cC, wC, pC = countWords(theText) - - self.charCount = cC - self.wordCount = wC - self.paraCount = pC - - return - -## END Class WordCounter From 98360b2edd0d9b34f6cdc3d98ef0ffbba37f30ce Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 23:28:38 +0200 Subject: [PATCH 5/8] Moved optionstate class into project file --- nw/core/project.py | 163 ++++++++++++++++++++++++++++++-- nw/gui/__init__.py | 2 - nw/gui/tools/__init__.py | 2 - nw/gui/tools/optionstate.py | 180 ------------------------------------ 4 files changed, 156 insertions(+), 191 deletions(-) delete mode 100644 nw/gui/tools/optionstate.py diff --git a/nw/core/project.py b/nw/core/project.py index be62ffae..43efa057 100644 --- a/nw/core/project.py +++ b/nw/core/project.py @@ -6,12 +6,14 @@ Class wrapping the data if a novelWriter project File History: - Created: 2018-09-29 [0.0.1] NWProject - Created: 2018-10-27 [0.0.1] NWItem - Created: 2019-05-19 [0.1.3] NWStatus - Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file - Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file - Added: 2020-05-07 [0.4.5] NWTree + Created: 2018-09-29 [0.0.1] NWProject + Created: 2018-10-27 [0.0.1] NWItem + Created: 2019-05-19 [0.1.3] NWStatus + Created: 2019-10-21 [0.3.1] OptionState + Merged: 2020-05-07 [0.4.5] Moved NWItem class to this file + Merged: 2020-05-07 [0.4.5] Moved NWStatus class to this file + Added: 2020-05-07 [0.4.5] NWTree + Rewritten: 2020-02-19 [0.4.5] OptionState This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen @@ -42,7 +44,6 @@ from shutil import make_archive from PyQt5.QtWidgets import QMessageBox -from nw.gui.tools import OptionState from nw.core.document import NWDoc from nw.common import checkString, checkBool, checkInt, formatTimeStamp from nw.constants import ( @@ -2007,3 +2008,151 @@ class NWStatus(): raise StopIteration # END Class NWStatus + +# =============================================================================================== # +# OptionState +# Save the project-wise state of options that don't go into project XML or main config +# =============================================================================================== # + +class OptionState(): + + def __init__(self, theProject): + + self.theProject = theProject + self.theState = {} + self.stringOpt = () + self.boolOpt = () + self.intOpt = () + + return + + def loadSettings(self): + """Load the options dictionary from the project settings file. + """ + if self.theProject.projMeta is None: + return False + + stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) + theState = {} + + if path.isfile(stateFile): + logger.debug("Loading GUI options file") + try: + with open(stateFile,mode="r",encoding="utf8") as inFile: + theJson = inFile.read() + theState = json.loads(theJson) + except Exception as e: + logger.error("Failed to load GUI options file") + logger.error(str(e)) + return False + for anOpt in theState: + self.theState[anOpt] = theState[anOpt] + + return True + + def saveSettings(self): + """Save the options dictionary to the project settings file. + """ + if self.theProject.projMeta is None: + return False + + stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) + logger.debug("Saving GUI options file") + + try: + with open(stateFile,mode="w+",encoding="utf8") as outFile: + outFile.write(json.dumps(self.theState, indent=2)) + except Exception as e: + logger.error("Failed to save GUI options file") + logger.error(str(e)) + return False + + return True + + def setValue(self, setGroup, setName, setValue): + """Saves a value, with a given group and name. + """ + if not setGroup in self.theState: + self.theState[setGroup] = {} + self.theState[setGroup][setName] = setValue + return True + + def getValue(self, getGroup, getName, defaultValue): + """Return an arbitrary type value, if it exists. Otherwise, + return the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return self.theState[getGroup][getName] + except: + return defaultValue + return defaultValue + + def getString(self, getGroup, getName, defaultValue): + """Return the value as a string, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return str(self.theState[getGroup][getName]) + except: + return defaultValue + return defaultValue + + def getInt(self, getGroup, getName, defaultValue): + """Return the value as an int, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return int(self.theState[getGroup][getName]) + except: + return defaultValue + return defaultValue + + def getFloat(self, getGroup, getName, defaultValue): + """Return the value as a float, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return float(self.theState[getGroup][getName]) + except: + return defaultValue + return defaultValue + + def getBool(self, getGroup, getName, defaultValue): + """Return the value as a bool, if it exists. Otherwise, return + the default value. + """ + if getGroup in self.theState: + if getName in self.theState[getGroup]: + try: + return bool(self.theState[getGroup][getName]) + except: + return defaultValue + return defaultValue + + def validIntRange(self, theValue, intA, intB, intDefault): + """Check that an int is in a given range. If it isn't, return + the default value. + """ + if isinstance(theValue, int): + if theValue >= intA and theValue <= intB: + return theValue + return intDefault + + def validIntTuple(self, theValue, theTuple, intDefault): + """Check that an int is an element of a tuple. If it isn't, + return the default value. + """ + if isinstance(theValue, int): + if theValue in theTuple: + return theValue + return intDefault + +# END Class OptionState diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 0ce25673..06c862eb 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -30,7 +30,6 @@ from nw.gui.elements.viewdetails import GuiDocViewDetails # Tools from nw.gui.tools.dochighlight import GuiDocHighlighter -from nw.gui.tools.optionstate import OptionState __all__ = [ "GuiBuildNovel", @@ -56,5 +55,4 @@ __all__ = [ "GuiSearchBar", "GuiDocViewDetails", "GuiDocHighlighter", - "OptionState", ] diff --git a/nw/gui/tools/__init__.py b/nw/gui/tools/__init__.py index e69db837..e064e251 100644 --- a/nw/gui/tools/__init__.py +++ b/nw/gui/tools/__init__.py @@ -1,9 +1,7 @@ # -*- coding: utf-8 -*- from nw.gui.tools.dochighlight import GuiDocHighlighter -from nw.gui.tools.optionstate import OptionState __all__ = [ "GuiDocHighlighter", - "OptionState", ] diff --git a/nw/gui/tools/optionstate.py b/nw/gui/tools/optionstate.py deleted file mode 100644 index 5f265622..00000000 --- a/nw/gui/tools/optionstate.py +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter Options State - - novelWriter – Options State -============================= - Class holding the last state of GUI options - - File History: - Created: 2019-10-21 [0.3.1] - Original version meant to be sub classed - Created: 2020-02-19 [0.4.5] - Rewritten from superclass to single file tool - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import json -import nw - -from os import path - -from nw.constants import nwFiles - -logger = logging.getLogger(__name__) - -class OptionState(): - - def __init__(self, theProject): - - self.theProject = theProject - self.theState = {} - self.stringOpt = () - self.boolOpt = () - self.intOpt = () - - return - - def loadSettings(self): - """Load the options dictionary from the project settings file. - """ - if self.theProject.projMeta is None: - return False - - stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) - theState = {} - - if path.isfile(stateFile): - logger.debug("Loading GUI options file") - try: - with open(stateFile,mode="r",encoding="utf8") as inFile: - theJson = inFile.read() - theState = json.loads(theJson) - except Exception as e: - logger.error("Failed to load GUI options file") - logger.error(str(e)) - return False - for anOpt in theState: - self.theState[anOpt] = theState[anOpt] - - return True - - def saveSettings(self): - """Save the options dictionary to the project settings file. - """ - if self.theProject.projMeta is None: - return False - - stateFile = path.join(self.theProject.projMeta, nwFiles.OPTS_FILE) - logger.debug("Saving GUI options file") - - try: - with open(stateFile,mode="w+",encoding="utf8") as outFile: - outFile.write(json.dumps(self.theState, indent=2)) - except Exception as e: - logger.error("Failed to save GUI options file") - logger.error(str(e)) - return False - - return True - - def setValue(self, setGroup, setName, setValue): - """Saves a value, with a given group and name. - """ - if not setGroup in self.theState: - self.theState[setGroup] = {} - self.theState[setGroup][setName] = setValue - return True - - def getValue(self, getGroup, getName, defaultValue): - """Return an arbitrary type value, if it exists. Otherwise, - return the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return self.theState[getGroup][getName] - except: - return defaultValue - return defaultValue - - def getString(self, getGroup, getName, defaultValue): - """Return the value as a string, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return str(self.theState[getGroup][getName]) - except: - return defaultValue - return defaultValue - - def getInt(self, getGroup, getName, defaultValue): - """Return the value as an int, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return int(self.theState[getGroup][getName]) - except: - return defaultValue - return defaultValue - - def getFloat(self, getGroup, getName, defaultValue): - """Return the value as a float, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return float(self.theState[getGroup][getName]) - except: - return defaultValue - return defaultValue - - def getBool(self, getGroup, getName, defaultValue): - """Return the value as a bool, if it exists. Otherwise, return - the default value. - """ - if getGroup in self.theState: - if getName in self.theState[getGroup]: - try: - return bool(self.theState[getGroup][getName]) - except: - return defaultValue - return defaultValue - - def validIntRange(self, theValue, intA, intB, intDefault): - """Check that an int is in a given range. If it isn't, return - the default value. - """ - if isinstance(theValue, int): - if theValue >= intA and theValue <= intB: - return theValue - return intDefault - - def validIntTuple(self, theValue, theTuple, intDefault): - """Check that an int is an element of a tuple. If it isn't, - return the default value. - """ - if isinstance(theValue, int): - if theValue in theTuple: - return theValue - return intDefault - -# END Class OptionState From bd3f6d39e79740cbda87763da96da4b9e3a7b8c1 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 23:30:58 +0200 Subject: [PATCH 6/8] Moved dochighlight up one level --- nw/gui/__init__.py | 2 -- nw/gui/{tools => }/dochighlight.py | 0 nw/gui/elements/doceditor.py | 2 +- nw/gui/tools/__init__.py | 7 ------- 4 files changed, 1 insertion(+), 10 deletions(-) rename nw/gui/{tools => }/dochighlight.py (100%) delete mode 100644 nw/gui/tools/__init__.py diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 06c862eb..b417ac6b 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -29,7 +29,6 @@ from nw.gui.elements.searchbar import GuiSearchBar from nw.gui.elements.viewdetails import GuiDocViewDetails # Tools -from nw.gui.tools.dochighlight import GuiDocHighlighter __all__ = [ "GuiBuildNovel", @@ -54,5 +53,4 @@ __all__ = [ "GuiProjectOutline", "GuiSearchBar", "GuiDocViewDetails", - "GuiDocHighlighter", ] diff --git a/nw/gui/tools/dochighlight.py b/nw/gui/dochighlight.py similarity index 100% rename from nw/gui/tools/dochighlight.py rename to nw/gui/dochighlight.py diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index 9d9f7fa2..3a03369d 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -41,7 +41,7 @@ from PyQt5.QtGui import ( ) from nw.core import NWDoc -from nw.gui.tools import GuiDocHighlighter +from nw.gui.dochighlight import GuiDocHighlighter from nw.gui.elements.doctitlebar import GuiDocTitleBar from nw.core import NWSpellSimple, countWords from nw.constants import nwUnicode, nwDocAction diff --git a/nw/gui/tools/__init__.py b/nw/gui/tools/__init__.py deleted file mode 100644 index e064e251..00000000 --- a/nw/gui/tools/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- - -from nw.gui.tools.dochighlight import GuiDocHighlighter - -__all__ = [ - "GuiDocHighlighter", -] From 3f069ac794eaf6cdb9b03ef25b88d7808f417969 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 23:39:04 +0200 Subject: [PATCH 7/8] Merged all document bars into one file --- nw/gui/__init__.py | 6 +- nw/gui/elements/__init__.py | 6 +- nw/gui/elements/{searchbar.py => docbars.py} | 144 ++++++++++++++++++- nw/gui/elements/doceditor.py | 2 +- nw/gui/elements/doctitlebar.py | 117 --------------- nw/gui/elements/docviewer.py | 2 +- nw/gui/elements/noticebar.py | 83 ----------- 7 files changed, 147 insertions(+), 213 deletions(-) rename nw/gui/elements/{searchbar.py => docbars.py} (55%) delete mode 100644 nw/gui/elements/doctitlebar.py delete mode 100644 nw/gui/elements/noticebar.py diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index b417ac6b..35cbac0a 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -20,12 +20,12 @@ from nw.gui.dialogs.sessionlog import GuiSessionLogView # GUI Elements from nw.gui.elements.docdetails import GuiDocDetails from nw.gui.elements.doceditor import GuiDocEditor -from nw.gui.elements.doctitlebar import GuiDocTitleBar +from nw.gui.elements.docbars import GuiDocTitleBar from nw.gui.elements.doctree import GuiDocTree from nw.gui.elements.docviewer import GuiDocViewer -from nw.gui.elements.noticebar import GuiNoticeBar +from nw.gui.elements.docbars import GuiNoticeBar from nw.gui.elements.outline import GuiProjectOutline -from nw.gui.elements.searchbar import GuiSearchBar +from nw.gui.elements.docbars import GuiSearchBar from nw.gui.elements.viewdetails import GuiDocViewDetails # Tools diff --git a/nw/gui/elements/__init__.py b/nw/gui/elements/__init__.py index b0938816..29a334a4 100644 --- a/nw/gui/elements/__init__.py +++ b/nw/gui/elements/__init__.py @@ -2,12 +2,12 @@ from nw.gui.elements.docdetails import GuiDocDetails from nw.gui.elements.doceditor import GuiDocEditor -from nw.gui.elements.doctitlebar import GuiDocTitleBar +from nw.gui.elements.docbars import GuiDocTitleBar from nw.gui.elements.doctree import GuiDocTree from nw.gui.elements.docviewer import GuiDocViewer -from nw.gui.elements.noticebar import GuiNoticeBar +from nw.gui.elements.docbars import GuiNoticeBar from nw.gui.elements.outline import GuiProjectOutline -from nw.gui.elements.searchbar import GuiSearchBar +from nw.gui.elements.docbars import GuiSearchBar from nw.gui.elements.viewdetails import GuiDocViewDetails __all__ = [ diff --git a/nw/gui/elements/searchbar.py b/nw/gui/elements/docbars.py similarity index 55% rename from nw/gui/elements/searchbar.py rename to nw/gui/elements/docbars.py index 590d8d0a..0923cb91 100644 --- a/nw/gui/elements/searchbar.py +++ b/nw/gui/elements/docbars.py @@ -6,7 +6,9 @@ Class holding the main window search bar File History: - Created: 2019-09-29 [0.2.1] + Created: 2019-09-29 [0.2.1] GuiSearchBar + Created: 2019-10-31 [0.3.2] GuiNoticeBar + Created: 2020-04-25 [0.4.5] GuiDocTitleBar This file is a part of novelWriter Copyright 2020, Veronica Berglyd Olsen @@ -29,11 +31,13 @@ import logging import nw from PyQt5.QtCore import Qt +from PyQt5.QtGui import QPalette, QColor from PyQt5.QtWidgets import ( - qApp, QFrame, QGridLayout, QLabel, QLineEdit, QPushButton + qApp, QFrame, QGridLayout, QLabel, QLineEdit, QPushButton, + QHBoxLayout ) -from nw.constants import nwDocAction +from nw.constants import nwDocAction, nwUnicode logger = logging.getLogger(__name__) @@ -42,7 +46,7 @@ class GuiSearchBar(QFrame): def __init__(self, theParent): QFrame.__init__(self, theParent) - logger.debug("Initialising SearchBar ...") + logger.debug("Initialising GuiSearchBar ...") self.mainConf = nw.CONFIG self.theParent = theParent @@ -89,7 +93,7 @@ class GuiSearchBar(QFrame): self._replaceVisible(False) - logger.debug("SearchBar initialisation complete") + logger.debug("GuiSearchBar initialisation complete") return @@ -163,3 +167,133 @@ class GuiSearchBar(QFrame): return True # END Class GuiSearchBar + +class GuiNoticeBar(QFrame): + + def __init__(self, theParent): + QFrame.__init__(self, theParent) + + logger.debug("Initialising GuiNoticeBar ...") + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theTheme = theParent.theTheme + + self.setContentsMargins(0,0,0,0) + self.setFrameShape(QFrame.Box) + + self.mainBox = QHBoxLayout(self) + self.mainBox.setContentsMargins(8,2,2,2) + + self.noteLabel = QLabel("") + + self.closeButton = QPushButton(self.theTheme.getIcon("close"),"") + self.closeButton.clicked.connect(self.hideNote) + + self.mainBox.addWidget(self.noteLabel) + self.mainBox.addWidget(self.closeButton) + self.mainBox.setStretch(0, 1) + + self.setLayout(self.mainBox) + + self.hideNote() + + logger.debug("GuiNoticeBar initialisation complete") + + return + + def showNote(self, theNote): + """Show the note on the noticebar. + """ + self.noteLabel.setText("Note: %s" % theNote) + self.setVisible(True) + return + + def hideNote(self): + """Clear the noticebar and hide it. + """ + self.noteLabel.setText("") + self.setVisible(False) + return + +# END Class GuiNoticeBar + +class GuiDocTitleBar(QLabel): + + def __init__(self, theParent, theProject): + QLabel.__init__(self, theParent) + + logger.debug("Initialising GuiDocTitleBar ...") + + self.mainConf = nw.CONFIG + self.theParent = theParent + self.theProject = theProject + self.theTheme = theParent.theTheme + self.theHandle = None + + self.setText("") + self.setIndent(0) + self.setMargin(0) + self.setContentsMargins(0, 0, 0, 0) + self.setAutoFillBackground(True) + self.setAlignment(Qt.AlignCenter) + self.setWordWrap(True) + self.setFrameShape(QFrame.NoFrame) + self.setLineWidth(0) + + lblPalette = self.palette() + lblPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack)) + lblPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) + self.setPalette(lblPalette) + + lblFont = self.font() + lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize) + self.setFont(lblFont) + + logger.debug("GuiDocTitleBar initialisation complete") + + return + + ## + # Setters + ## + + def setTitleFromHandle(self, tHandle): + """Sets the document title from the handle, or alternatively, + set the whole document path. + """ + self.setText("") + self.theHandle = tHandle + if tHandle is None: + return False + + if self.mainConf.showFullPath: + tTitle = [] + tTree = self.theProject.projTree.getItemPath(tHandle) + for aHandle in reversed(tTree): + nwItem = self.theProject.projTree[aHandle] + if nwItem is not None: + tTitle.append(nwItem.itemName) + sSep = " %s " % nwUnicode.U_RSAQUO + self.setText(sSep.join(tTitle)) + else: + nwItem = self.theProject.projTree[tHandle] + if nwItem is None: + return False + + self.setText(nwItem.itemName) + + return True + + ## + # Events + ## + + def mousePressEvent(self, theEvent): + """Capture a click on the title and ensure that the item is + selected in the project tree. + """ + self.theParent.setSelectedHandle(self.theHandle) + return + +# END Class GuiDocTitleBar diff --git a/nw/gui/elements/doceditor.py b/nw/gui/elements/doceditor.py index 3a03369d..0493f167 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/elements/doceditor.py @@ -42,7 +42,7 @@ from PyQt5.QtGui import ( from nw.core import NWDoc from nw.gui.dochighlight import GuiDocHighlighter -from nw.gui.elements.doctitlebar import GuiDocTitleBar +from nw.gui.elements.docbars import GuiDocTitleBar from nw.core import NWSpellSimple, countWords from nw.constants import nwUnicode, nwDocAction diff --git a/nw/gui/elements/doctitlebar.py b/nw/gui/elements/doctitlebar.py deleted file mode 100644 index 8c5208e4..00000000 --- a/nw/gui/elements/doctitlebar.py +++ /dev/null @@ -1,117 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter GUI Document Title Bar - - novelWriter – GUI Document Title Bar -====================================== - Class holding the document title bar class - - File History: - Created: 2020-04-25 [0.4.5] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from PyQt5.QtCore import Qt -from PyQt5.QtGui import QPalette, QColor -from PyQt5.QtWidgets import QLabel, QFrame - -from nw.constants import nwUnicode - -logger = logging.getLogger(__name__) - -class GuiDocTitleBar(QLabel): - - def __init__(self, theParent, theProject): - QLabel.__init__(self, theParent) - - logger.debug("Initialising DocTitleBar ...") - - self.mainConf = nw.CONFIG - self.theParent = theParent - self.theProject = theProject - self.theTheme = theParent.theTheme - self.theHandle = None - - self.setText("") - self.setIndent(0) - self.setMargin(0) - self.setContentsMargins(0, 0, 0, 0) - self.setAutoFillBackground(True) - self.setAlignment(Qt.AlignCenter) - self.setWordWrap(True) - self.setFrameShape(QFrame.NoFrame) - self.setLineWidth(0) - - lblPalette = self.palette() - lblPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack)) - lblPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText)) - self.setPalette(lblPalette) - - lblFont = self.font() - lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize) - self.setFont(lblFont) - - logger.debug("DocTitleBar initialisation complete") - - return - - ## - # Setters - ## - - def setTitleFromHandle(self, tHandle): - """Sets the document title from the handle, or alternatively, - set the whole document path. - """ - self.setText("") - self.theHandle = tHandle - if tHandle is None: - return False - - if self.mainConf.showFullPath: - tTitle = [] - tTree = self.theProject.projTree.getItemPath(tHandle) - for aHandle in reversed(tTree): - nwItem = self.theProject.projTree[aHandle] - if nwItem is not None: - tTitle.append(nwItem.itemName) - sSep = " %s " % nwUnicode.U_RSAQUO - self.setText(sSep.join(tTitle)) - else: - nwItem = self.theProject.projTree[tHandle] - if nwItem is None: - return False - - self.setText(nwItem.itemName) - - return True - - ## - # Events - ## - - def mousePressEvent(self, theEvent): - """Capture a click on the title and ensure that the item is - selected in the project tree. - """ - self.theParent.setSelectedHandle(self.theHandle) - return - -# END Class GuiDocTitleBar diff --git a/nw/gui/elements/docviewer.py b/nw/gui/elements/docviewer.py index 66f2a5d3..820c6e85 100644 --- a/nw/gui/elements/docviewer.py +++ b/nw/gui/elements/docviewer.py @@ -34,7 +34,7 @@ from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor from nw.core import ToHtml from nw.constants import nwAlert, nwItemType, nwDocAction -from nw.gui.elements.doctitlebar import GuiDocTitleBar +from nw.gui.elements.docbars import GuiDocTitleBar logger = logging.getLogger(__name__) diff --git a/nw/gui/elements/noticebar.py b/nw/gui/elements/noticebar.py deleted file mode 100644 index 4b6d57aa..00000000 --- a/nw/gui/elements/noticebar.py +++ /dev/null @@ -1,83 +0,0 @@ -# -*- coding: utf-8 -*- -"""novelWriter GUI Main Window Notice Bar - - novelWriter – GUI Main Window Notice Bar -========================================== - Class holding the main window notice bar for the doc editor - - File History: - Created: 2019-10-31 [0.3.2] - - This file is a part of novelWriter - Copyright 2020, Veronica Berglyd Olsen - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, but - WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . -""" - -import logging -import nw - -from PyQt5.QtWidgets import QFrame, QHBoxLayout, QLabel, QPushButton - -logger = logging.getLogger(__name__) - -class GuiNoticeBar(QFrame): - - def __init__(self, theParent): - QFrame.__init__(self, theParent) - - logger.debug("Initialising NoticeBar ...") - - self.mainConf = nw.CONFIG - self.theParent = theParent - self.theTheme = theParent.theTheme - - self.setContentsMargins(0,0,0,0) - self.setFrameShape(QFrame.Box) - - self.mainBox = QHBoxLayout(self) - self.mainBox.setContentsMargins(8,2,2,2) - - self.noteLabel = QLabel("") - - self.closeButton = QPushButton(self.theTheme.getIcon("close"),"") - self.closeButton.clicked.connect(self.hideNote) - - self.mainBox.addWidget(self.noteLabel) - self.mainBox.addWidget(self.closeButton) - self.mainBox.setStretch(0, 1) - - self.setLayout(self.mainBox) - - self.hideNote() - - logger.debug("NoticeBar initialisation complete") - - return - - def showNote(self, theNote): - """Show the note on the noticebar. - """ - self.noteLabel.setText("Note: %s" % theNote) - self.setVisible(True) - return - - def hideNote(self): - """Clear the noticebar and hide it. - """ - self.noteLabel.setText("") - self.setVisible(False) - return - -# END Class GuiNoticeBar From 49141ac8de90b20bda3e0fe74b66fbd50c000b42 Mon Sep 17 00:00:00 2001 From: "Veronica K. B. Olsen" <1619840+vkbo@users.noreply.github.com> Date: Mon, 1 Jun 2020 23:47:37 +0200 Subject: [PATCH 8/8] Moved all gui files to the root gui folder --- nw/gui/__init__.py | 73 +++++++++++-------------- nw/gui/{dialogs => }/about.py | 0 nw/gui/dialogs/__init__.py | 21 ------- nw/gui/{elements => }/docbars.py | 0 nw/gui/{elements => }/docdetails.py | 0 nw/gui/{elements => }/doceditor.py | 2 +- nw/gui/{dialogs => }/docmerge.py | 0 nw/gui/{dialogs => }/docsplit.py | 0 nw/gui/{elements => }/doctree.py | 0 nw/gui/{elements => }/docviewer.py | 2 +- nw/gui/elements/__init__.py | 23 -------- nw/gui/{dialogs => }/itemeditor.py | 0 nw/gui/mainmenu.py | 2 +- nw/gui/{elements => }/outline.py | 0 nw/gui/{dialogs => }/preferences.py | 0 nw/gui/{dialogs => }/projectload.py | 0 nw/gui/{dialogs => }/projectsettings.py | 0 nw/gui/{dialogs => }/sessionlog.py | 0 nw/gui/{elements => }/viewdetails.py | 0 19 files changed, 36 insertions(+), 87 deletions(-) rename nw/gui/{dialogs => }/about.py (100%) delete mode 100644 nw/gui/dialogs/__init__.py rename nw/gui/{elements => }/docbars.py (100%) rename nw/gui/{elements => }/docdetails.py (100%) rename nw/gui/{elements => }/doceditor.py (99%) rename nw/gui/{dialogs => }/docmerge.py (100%) rename nw/gui/{dialogs => }/docsplit.py (100%) rename nw/gui/{elements => }/doctree.py (100%) rename nw/gui/{elements => }/docviewer.py (99%) delete mode 100644 nw/gui/elements/__init__.py rename nw/gui/{dialogs => }/itemeditor.py (100%) rename nw/gui/{elements => }/outline.py (100%) rename nw/gui/{dialogs => }/preferences.py (100%) rename nw/gui/{dialogs => }/projectload.py (100%) rename nw/gui/{dialogs => }/projectsettings.py (100%) rename nw/gui/{dialogs => }/sessionlog.py (100%) rename nw/gui/{elements => }/viewdetails.py (100%) diff --git a/nw/gui/__init__.py b/nw/gui/__init__.py index 35cbac0a..28a7a654 100644 --- a/nw/gui/__init__.py +++ b/nw/gui/__init__.py @@ -1,56 +1,49 @@ # -*- coding: utf-8 -*- -# Main Window Elements +from nw.gui.about import GuiAbout from nw.gui.build import GuiBuildNovel -from nw.gui.theme import GuiIcons +from nw.gui.docbars import GuiDocTitleBar +from nw.gui.docbars import GuiNoticeBar +from nw.gui.docbars import GuiSearchBar +from nw.gui.docdetails import GuiDocDetails +from nw.gui.doceditor import GuiDocEditor +from nw.gui.docmerge import GuiDocMerge +from nw.gui.docsplit import GuiDocSplit +from nw.gui.doctree import GuiDocTree +from nw.gui.docviewer import GuiDocViewer +from nw.gui.itemeditor import GuiItemEditor from nw.gui.mainmenu import GuiMainMenu +from nw.gui.outline import GuiProjectOutline +from nw.gui.preferences import GuiPreferences +from nw.gui.projectload import GuiProjectLoad +from nw.gui.projectsettings import GuiProjectSettings +from nw.gui.sessionlog import GuiSessionLogView from nw.gui.statusbar import GuiMainStatus +from nw.gui.theme import GuiIcons from nw.gui.theme import GuiTheme - -# Dialogs -from nw.gui.dialogs.about import GuiAbout -from nw.gui.dialogs.preferences import GuiPreferences -from nw.gui.dialogs.docmerge import GuiDocMerge -from nw.gui.dialogs.docsplit import GuiDocSplit -from nw.gui.dialogs.itemeditor import GuiItemEditor -from nw.gui.dialogs.projectsettings import GuiProjectSettings -from nw.gui.dialogs.projectload import GuiProjectLoad -from nw.gui.dialogs.sessionlog import GuiSessionLogView - -# GUI Elements -from nw.gui.elements.docdetails import GuiDocDetails -from nw.gui.elements.doceditor import GuiDocEditor -from nw.gui.elements.docbars import GuiDocTitleBar -from nw.gui.elements.doctree import GuiDocTree -from nw.gui.elements.docviewer import GuiDocViewer -from nw.gui.elements.docbars import GuiNoticeBar -from nw.gui.elements.outline import GuiProjectOutline -from nw.gui.elements.docbars import GuiSearchBar -from nw.gui.elements.viewdetails import GuiDocViewDetails - -# Tools +from nw.gui.viewdetails import GuiDocViewDetails __all__ = [ - "GuiBuildNovel", - "GuiIcons", - "GuiMainMenu", - "GuiMainStatus", - "GuiTheme", "GuiAbout", - "GuiPreferences", - "GuiDocMerge", - "GuiDocSplit", - "GuiItemEditor", - "GuiProjectSettings", - "GuiProjectLoad", - "GuiSessionLogView", + "GuiBuildNovel", + "GuiDocTitleBar", + "GuiNoticeBar", + "GuiSearchBar", "GuiDocDetails", "GuiDocEditor", - "GuiDocTitleBar", + "GuiDocMerge", + "GuiDocSplit", "GuiDocTree", "GuiDocViewer", - "GuiNoticeBar", + "GuiItemEditor", + "GuiMainMenu", "GuiProjectOutline", - "GuiSearchBar", + "GuiPreferences", + "GuiProjectLoad", + "GuiProjectSettings", + "GuiSessionLogView", + "GuiMainStatus", + "GuiIcons", + "GuiTheme", "GuiDocViewDetails", ] diff --git a/nw/gui/dialogs/about.py b/nw/gui/about.py similarity index 100% rename from nw/gui/dialogs/about.py rename to nw/gui/about.py diff --git a/nw/gui/dialogs/__init__.py b/nw/gui/dialogs/__init__.py deleted file mode 100644 index 5f4e4a03..00000000 --- a/nw/gui/dialogs/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 -*- - -from nw.gui.dialogs.about import GuiAbout -from nw.gui.dialogs.preferences import GuiPreferences -from nw.gui.dialogs.docmerge import GuiDocMerge -from nw.gui.dialogs.docsplit import GuiDocSplit -from nw.gui.dialogs.itemeditor import GuiItemEditor -from nw.gui.dialogs.projectsettings import GuiProjectSettings -from nw.gui.dialogs.projectload import GuiProjectLoad -from nw.gui.dialogs.sessionlog import GuiSessionLogView - -__all__ = [ - "GuiAbout", - "GuiPreferences", - "GuiDocMerge", - "GuiDocSplit", - "GuiItemEditor", - "GuiProjectSettings", - "GuiProjectLoad", - "GuiSessionLogView", -] diff --git a/nw/gui/elements/docbars.py b/nw/gui/docbars.py similarity index 100% rename from nw/gui/elements/docbars.py rename to nw/gui/docbars.py diff --git a/nw/gui/elements/docdetails.py b/nw/gui/docdetails.py similarity index 100% rename from nw/gui/elements/docdetails.py rename to nw/gui/docdetails.py diff --git a/nw/gui/elements/doceditor.py b/nw/gui/doceditor.py similarity index 99% rename from nw/gui/elements/doceditor.py rename to nw/gui/doceditor.py index 0493f167..bf511e07 100644 --- a/nw/gui/elements/doceditor.py +++ b/nw/gui/doceditor.py @@ -42,7 +42,7 @@ from PyQt5.QtGui import ( from nw.core import NWDoc from nw.gui.dochighlight import GuiDocHighlighter -from nw.gui.elements.docbars import GuiDocTitleBar +from nw.gui.docbars import GuiDocTitleBar from nw.core import NWSpellSimple, countWords from nw.constants import nwUnicode, nwDocAction diff --git a/nw/gui/dialogs/docmerge.py b/nw/gui/docmerge.py similarity index 100% rename from nw/gui/dialogs/docmerge.py rename to nw/gui/docmerge.py diff --git a/nw/gui/dialogs/docsplit.py b/nw/gui/docsplit.py similarity index 100% rename from nw/gui/dialogs/docsplit.py rename to nw/gui/docsplit.py diff --git a/nw/gui/elements/doctree.py b/nw/gui/doctree.py similarity index 100% rename from nw/gui/elements/doctree.py rename to nw/gui/doctree.py diff --git a/nw/gui/elements/docviewer.py b/nw/gui/docviewer.py similarity index 99% rename from nw/gui/elements/docviewer.py rename to nw/gui/docviewer.py index 820c6e85..1be66bba 100644 --- a/nw/gui/elements/docviewer.py +++ b/nw/gui/docviewer.py @@ -34,7 +34,7 @@ from PyQt5.QtGui import QTextOption, QFont, QPalette, QColor, QTextCursor from nw.core import ToHtml from nw.constants import nwAlert, nwItemType, nwDocAction -from nw.gui.elements.docbars import GuiDocTitleBar +from nw.gui.docbars import GuiDocTitleBar logger = logging.getLogger(__name__) diff --git a/nw/gui/elements/__init__.py b/nw/gui/elements/__init__.py deleted file mode 100644 index 29a334a4..00000000 --- a/nw/gui/elements/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 -*- - -from nw.gui.elements.docdetails import GuiDocDetails -from nw.gui.elements.doceditor import GuiDocEditor -from nw.gui.elements.docbars import GuiDocTitleBar -from nw.gui.elements.doctree import GuiDocTree -from nw.gui.elements.docviewer import GuiDocViewer -from nw.gui.elements.docbars import GuiNoticeBar -from nw.gui.elements.outline import GuiProjectOutline -from nw.gui.elements.docbars import GuiSearchBar -from nw.gui.elements.viewdetails import GuiDocViewDetails - -__all__ = [ - "GuiDocDetails", - "GuiDocEditor", - "GuiDocTitleBar", - "GuiDocTree", - "GuiDocViewer", - "GuiNoticeBar", - "GuiProjectOutline", - "GuiSearchBar", - "GuiDocViewDetails", -] diff --git a/nw/gui/dialogs/itemeditor.py b/nw/gui/itemeditor.py similarity index 100% rename from nw/gui/dialogs/itemeditor.py rename to nw/gui/itemeditor.py diff --git a/nw/gui/mainmenu.py b/nw/gui/mainmenu.py index 31206d82..5ff4d918 100644 --- a/nw/gui/mainmenu.py +++ b/nw/gui/mainmenu.py @@ -32,7 +32,7 @@ from PyQt5.QtCore import QUrl from PyQt5.QtGui import QDesktopServices from PyQt5.QtWidgets import QMenuBar, QAction, QMessageBox -from nw.gui.dialogs import GuiAbout +from nw.gui.about import GuiAbout from nw.constants import nwItemType, nwItemClass, nwDocAction logger = logging.getLogger(__name__) diff --git a/nw/gui/elements/outline.py b/nw/gui/outline.py similarity index 100% rename from nw/gui/elements/outline.py rename to nw/gui/outline.py diff --git a/nw/gui/dialogs/preferences.py b/nw/gui/preferences.py similarity index 100% rename from nw/gui/dialogs/preferences.py rename to nw/gui/preferences.py diff --git a/nw/gui/dialogs/projectload.py b/nw/gui/projectload.py similarity index 100% rename from nw/gui/dialogs/projectload.py rename to nw/gui/projectload.py diff --git a/nw/gui/dialogs/projectsettings.py b/nw/gui/projectsettings.py similarity index 100% rename from nw/gui/dialogs/projectsettings.py rename to nw/gui/projectsettings.py diff --git a/nw/gui/dialogs/sessionlog.py b/nw/gui/sessionlog.py similarity index 100% rename from nw/gui/dialogs/sessionlog.py rename to nw/gui/sessionlog.py diff --git a/nw/gui/elements/viewdetails.py b/nw/gui/viewdetails.py similarity index 100% rename from nw/gui/elements/viewdetails.py rename to nw/gui/viewdetails.py