Add a new placeholder icon and clean up project search styling (#1781)

This commit is contained in:
Veronica Berglyd Olsen
2024-03-27 13:46:31 +01:00
committed by GitHub
9 changed files with 114 additions and 81 deletions
+4
View File
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="24" height="24" version="1.2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path d="m0 0v4l8 8-8 8v4h4l8-8 8 8h4v-4l-8-8 8-8v-4h-4l-8 8-8-8h-4z" fill="#f00" stroke-width=".70711"/>
</svg>

After

Width:  |  Height:  |  Size: 251 B

+6 -1
View File
@@ -36,7 +36,7 @@ from configparser import ConfigParser
from urllib.parse import urljoin
from urllib.request import pathname2url
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtGui import QColor, QDesktopServices
from PyQt5.QtCore import QCoreApplication, QUrl
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
@@ -382,6 +382,11 @@ def numberToRoman(value: int, toLower: bool = False) -> str:
return roman.lower() if toLower else roman
def cssCol(col: QColor, alpha: int | None = None) -> str:
"""Convert a QColor object to an rgba entry to use in CSS."""
return f"rgba({col.red()}, {col.green()}, {col.blue()}, {alpha or col.alpha()})"
##
# Encoder Functions
##
+19 -28
View File
@@ -41,6 +41,7 @@ from PyQt5.QtWidgets import (
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import cssCol
from novelwriter.constants import nwHeaders, nwUnicode
from novelwriter.core.tohtml import ToHtml
from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
@@ -467,35 +468,25 @@ class GuiDocViewer(QTextBrowser):
"""Generate an appropriate style sheet for the document viewer,
based on the current syntax highlighter theme.
"""
colText = SHARED.theme.colText
colHead = SHARED.theme.colHead
colVals = SHARED.theme.colVal
colMark = SHARED.theme.colMark
colKeys = SHARED.theme.colKey
colHide = SHARED.theme.colHidden
colMods = SHARED.theme.colMod
colOpts = SHARED.theme.colOpt
styleSheet = (
"body {{color: rgb({rT}, {gT}, {bT});}}\n"
"h1, h2, h3, h4 {{color: rgb({rH}, {gH}, {bH});}}\n"
"a {{color: rgb({rA}, {gA}, {bA});}}\n"
"mark {{background-color: rgba({rE}, {gE}, {bE}, {aE});}}\n"
".tags {{color: rgb({rK}, {gK}, {bK});}}\n"
".optional {{color: rgb({rO}, {gO}, {bO});}}\n"
".comment {{color: rgb({rC}, {gC}, {bC});}}\n"
".synopsis {{color: rgb({rM}, {gM}, {bM});}}\n"
".title {{text-align: center;}}\n"
).format(
rT=colText.red(), gT=colText.green(), bT=colText.blue(),
rH=colHead.red(), gH=colHead.green(), bH=colHead.blue(),
rA=colVals.red(), gA=colVals.green(), bA=colVals.blue(),
rE=colMark.red(), gE=colMark.green(), bE=colMark.blue(), aE=colMark.alpha(),
rK=colKeys.red(), gK=colKeys.green(), bK=colKeys.blue(),
rC=colHide.red(), gC=colHide.green(), bC=colHide.blue(),
rM=colMods.red(), gM=colMods.green(), bM=colMods.blue(),
rO=colOpts.red(), gO=colOpts.green(), bO=colOpts.blue(),
colText = cssCol(SHARED.theme.colText)
colHead = cssCol(SHARED.theme.colHead)
colVals = cssCol(SHARED.theme.colVal)
colMark = cssCol(SHARED.theme.colMark)
colKeys = cssCol(SHARED.theme.colKey)
colOpts = cssCol(SHARED.theme.colOpt)
colHide = cssCol(SHARED.theme.colHidden)
colMods = cssCol(SHARED.theme.colMod)
self.document().setDefaultStyleSheet(
f"body {{color: {colText};}}\n"
f"h1, h2, h3, h4 {{color: {colHead};}}\n"
f"a {{color: {colVals};}}\n"
f"mark {{background-color: {colMark};}}\n"
f".tags {{color: {colKeys};}}\n"
f".optional {{color: {colOpts};}}\n"
f".comment {{color: {colHide});}}\n"
f".synopsis {{color: {colMods};}}\n"
".title {text-align: center;}\n"
)
self.document().setDefaultStyleSheet(styleSheet)
return
+1 -1
View File
@@ -565,7 +565,7 @@ class GuiProjectTree(QTreeWidget):
# Auto Scroll
self._scrollMargin = SHARED.theme.baseIconSize
self._scrollDirection = 0
self._scrollTimer = QTimer()
self._scrollTimer = QTimer(self)
self._scrollTimer.timeout.connect(self._doAutoScroll)
self._scrollTimer.setInterval(250)
+30 -9
View File
@@ -28,14 +28,14 @@ import logging
from time import time
from PyQt5.QtCore import QSize, Qt, pyqtSignal, pyqtSlot
from PyQt5.QtGui import QCursor, QKeyEvent, QPalette
from PyQt5.QtGui import QCursor, QKeyEvent
from PyQt5.QtWidgets import (
QHBoxLayout, QHeaderView, QLabel, QLineEdit, QToolBar, QTreeWidget,
QFrame, QHBoxLayout, QHeaderView, QLabel, QLineEdit, QToolBar, QTreeWidget,
QTreeWidgetItem, QVBoxLayout, QWidget, qApp
)
from novelwriter import CONFIG, SHARED
from novelwriter.common import checkInt
from novelwriter.common import checkInt, cssCol
from novelwriter.core.coretools import DocSearch
from novelwriter.core.item import NWItem
@@ -63,6 +63,7 @@ class GuiProjectSearch(QWidget):
iPx = SHARED.theme.baseIconSize
mPx = CONFIG.pxInt(2)
tPx = CONFIG.pxInt(4)
self._time = time()
self._search = DocSearch()
@@ -71,7 +72,7 @@ class GuiProjectSearch(QWidget):
# Header
self.viewLabel = QLabel(self.tr("Project Search"))
self.viewLabel.setFont(SHARED.theme.guiFontB)
self.viewLabel.setContentsMargins(mPx, mPx, 0, mPx)
self.viewLabel.setContentsMargins(mPx, tPx, 0, mPx)
# Options
self.searchOpt = QToolBar(self)
@@ -96,7 +97,7 @@ class GuiProjectSearch(QWidget):
# Search Box
self.searchText = QLineEdit(self)
self.searchText.setPlaceholderText(self.tr("Search text ..."))
self.searchText.setPlaceholderText(self.tr("Search"))
self.searchText.setClearButtonEnabled(True)
self.searchAction = self.searchText.addAction(
@@ -110,6 +111,9 @@ class GuiProjectSearch(QWidget):
self.searchResult.setColumnCount(2)
self.searchResult.setIconSize(QSize(iPx, iPx))
self.searchResult.setIndentation(iPx)
self.searchResult.setFrameStyle(QFrame.Shape.NoFrame)
self.searchResult.setUniformRowHeights(True)
self.searchResult.setAllColumnsShowFocus(True)
self.searchResult.itemDoubleClicked.connect(self._searchResultDoubleClicked)
self.searchResult.itemSelectionChanged.connect(self._searchResultSelected)
@@ -121,11 +125,16 @@ class GuiProjectSearch(QWidget):
# Assemble
self.headerBox = QHBoxLayout()
self.headerBox.addWidget(self.viewLabel, 1)
self.headerBox.addWidget(self.searchOpt, 0)
self.headerBox.addWidget(self.searchOpt, 0, Qt.AlignmentFlag.AlignVCenter)
self.headerBox.setContentsMargins(0, 0, 0, 0)
self.headerBox.setSpacing(0)
self.headerWidget = QWidget(self)
self.headerWidget.setLayout(self.headerBox)
self.headerWidget.setContentsMargins(0, 0, 0, 0)
self.outerBox = QVBoxLayout()
self.outerBox.addLayout(self.headerBox, 0)
self.outerBox.addWidget(self.headerWidget, 0)
self.outerBox.addWidget(self.searchText, 0)
self.outerBox.addWidget(self.searchResult, 1)
self.outerBox.setContentsMargins(0, 0, 0, 0)
@@ -144,9 +153,21 @@ class GuiProjectSearch(QWidget):
def updateTheme(self) -> None:
"""Update theme elements."""
bPx = CONFIG.pxInt(1)
mPx = CONFIG.pxInt(2)
qPalette = self.palette()
qPalette.setBrush(QPalette.ColorRole.Window, qPalette.base())
self.setPalette(qPalette)
colBase = cssCol(qPalette.base().color())
colFocus = cssCol(qPalette.highlight().color())
self.headerWidget.setStyleSheet(f"background: {colBase};")
self.headerWidget.setAutoFillBackground(True)
self.setStyleSheet(
"QToolBar {padding: 0; background: none;} "
f"QLineEdit {{border: {bPx}px solid {colBase}; padding: {mPx}px;}} "
f"QLineEdit:focus {{border: {bPx}px solid {colFocus};}} "
)
self.searchAction.setIcon(SHARED.theme.getIcon("search"))
self.toggleCase.setIcon(SHARED.theme.getIcon("search_case"))
+20 -17
View File
@@ -38,7 +38,7 @@ from PyQt5.QtGui import (
from novelwriter import CONFIG
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
from novelwriter.error import logException
from novelwriter.common import NWConfigParser, minmax
from novelwriter.common import NWConfigParser, cssCol, minmax
from novelwriter.constants import nwLabels
logger = logging.getLogger(__name__)
@@ -440,24 +440,24 @@ class GuiTheme:
# Flat Tab Widget and Tab Bar:
self._styleSheets[STYLES_FLAT_TABS] = (
"QTabWidget::pane {{border: 0;}} "
"QTabWidget QTabBar::tab {{border: 0; padding: {0}px {1}px;}} "
"QTabWidget QTabBar::tab:selected {{color: rgb({2}, {3}, {4});}} "
).format(bPx, dPx, hCol.red(), hCol.green(), hCol.blue())
"QTabWidget::pane {border: 0;} "
f"QTabWidget QTabBar::tab {{border: 0; padding: {bPx}px {dPx}px;}} "
f"QTabWidget QTabBar::tab:selected {{color: {cssCol(hCol)};}} "
)
# Minimal Tool Button
self._styleSheets[STYLES_MIN_TOOLBUTTON] = (
"QToolButton {{padding: {0}px; margin: 0; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1}, {2}, {3}, 0.2);}} "
"QToolButton::menu-indicator {{image: none;}} "
).format(aPx, tCol.red(), tCol.green(), tCol.blue())
f"QToolButton {{padding: {aPx}px; margin: 0; border: none; background: transparent;}} "
f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} "
"QToolButton::menu-indicator {image: none;} "
)
# Big Tool Button
self._styleSheets[STYLES_BIG_TOOLBUTTON] = (
"QToolButton {{padding: {0}px; margin: 0; border: none; background: transparent;}} "
"QToolButton:hover {{border: none; background: rgba({1}, {2}, {3}, 0.2);}} "
"QToolButton::menu-indicator {{image: none;}} "
).format(cPx, tCol.red(), tCol.green(), tCol.blue())
f"QToolButton {{padding: {cPx}px; margin: 0; border: none; background: transparent;}} "
f"QToolButton:hover {{border: none; background: {cssCol(tCol, 48)};}} "
"QToolButton::menu-indicator {image: none;} "
)
return
@@ -544,6 +544,9 @@ class GuiIcons:
self._confName = "icons.conf"
self._iconPath = CONFIG.assetPath("icons")
# None Icon
self._noIcon = QIcon(str(self._iconPath / "none.svg"))
# Icon Theme Meta
self.themeName = ""
self.themeDescription = ""
@@ -680,7 +683,7 @@ class GuiIcons:
icon.addPixmap(pOne, QIcon.Mode.Normal, QIcon.State.On)
icon.addPixmap(pTwo, QIcon.Mode.Normal, QIcon.State.Off)
return icon
return QIcon()
return self._noIcon
def getPixmap(self, name: str, size: tuple[int, int]) -> QPixmap:
"""Return an icon from the icon buffer as a QPixmap. If it
@@ -712,7 +715,7 @@ class GuiIcons:
elif tLayout == nwItemLayout.NOTE:
iconName = "proj_note"
if iconName is None:
return QIcon()
return self._noIcon
return self.getIcon(iconName)
@@ -753,7 +756,7 @@ class GuiIcons:
"""
if name not in self.ICON_KEYS:
logger.error("Requested unknown icon name '%s'", name)
return QIcon()
return self._noIcon
# If we just want the app icons, return right away
if name == "novelwriter":
@@ -769,7 +772,7 @@ class GuiIcons:
# If we didn't find one, give up and return an empty icon
logger.warning("Did not load an icon for '%s'", name)
return QIcon()
return self._noIcon
# END Class GuiIcons
+13 -13
View File
@@ -42,7 +42,7 @@ from PyQt5.QtWidgets import (
from novelwriter import CONFIG, SHARED
from novelwriter.enum import nwItemClass
from novelwriter.common import formatInt, makeFileNameSafe
from novelwriter.common import cssCol, formatInt, makeFileNameSafe
from novelwriter.constants import nwFiles
from novelwriter.core.coretools import ProjectBuilder
from novelwriter.extensions.configlayout import NWrappedWidgetBox
@@ -52,7 +52,7 @@ from novelwriter.extensions.versioninfo import VersionInfoWidget
logger = logging.getLogger(__name__)
PANEL_ALPHA = 0.7
PANEL_ALPHA = 178
class GuiWelcome(QDialog):
@@ -306,12 +306,12 @@ class _OpenProjectPage(QWidget):
self._selectFirstItem()
baseCol = self.palette().base().color()
self.setStyleSheet((
"QListView {{border: none; background: rgba({r},{g},{b},{a});}} "
"QLineEdit {{border: none; background: rgba({r},{g},{b},{a}); padding: {m}px;}} "
).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue(),
a=PANEL_ALPHA, m=CONFIG.pxInt(4)))
mPx = CONFIG.pxInt(4)
baseCol = cssCol(self.palette().base().color(), PANEL_ALPHA)
self.setStyleSheet(
f"QListView {{border: none; background: {baseCol};}} "
f"QLineEdit {{border: none; background: {baseCol}; padding: {mPx}px;}} "
)
return
@@ -518,11 +518,11 @@ class _NewProjectPage(QWidget):
# Styles
# ======
baseCol = self.palette().base().color()
self.setStyleSheet((
"QScrollArea {{border: none; background: rgba({r},{g},{b},{a});}} "
"_NewProjectForm {{border: none; background: rgba({r},{g},{b},{a});}} "
).format(r=baseCol.red(), g=baseCol.green(), b=baseCol.blue(), a=PANEL_ALPHA))
baseCol = cssCol(self.palette().base().color(), PANEL_ALPHA)
self.setStyleSheet(
f"QScrollArea {{border: none; background: {baseCol};}} "
f"_NewProjectForm {{border: none; background: {baseCol};}} "
)
return
+15 -6
View File
@@ -29,16 +29,16 @@ from xml.etree import ElementTree as ET
from tools import writeFile
from mocked import causeOSError
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtGui import QColor, QDesktopServices
from PyQt5.QtCore import QUrl
from novelwriter.common import (
checkBool, checkFloat, checkInt, checkIntTuple, checkPath, checkString,
checkStringNone, checkUuid, formatFileFilter, formatInt, formatTime,
formatTimeStamp, formatVersion, fuzzyTime, getFileSize, hexToInt, isHandle,
isItemClass, isItemLayout, isItemType, isTitleTag, jsonEncode,
makeFileNameSafe, minmax, numberToRoman, NWConfigParser, openExternalPath,
readTextFile, simplified, transferCase, xmlIndent, yesNo
checkStringNone, checkUuid, cssCol, formatFileFilter, formatInt,
formatTime, formatTimeStamp, formatVersion, fuzzyTime, getFileSize,
hexToInt, isHandle, isItemClass, isItemLayout, isItemType, isTitleTag,
jsonEncode, makeFileNameSafe, minmax, numberToRoman, NWConfigParser,
openExternalPath, readTextFile, simplified, transferCase, xmlIndent, yesNo
)
@@ -494,6 +494,15 @@ def testBaseCommon_numberToRoman():
# END Test testBaseCommon_numberToRoman
@pytest.mark.base
def testBaseCommon_cssCol():
"""Test the cssCol function."""
assert cssCol(QColor(0, 0, 0, 0)) == "rgba(0, 0, 0, 0)"
assert cssCol(QColor(10, 20, 30, 40)) == "rgba(10, 20, 30, 40)"
# END Test testBaseCommon_cssCol
@pytest.mark.base
def testBaseCommon_jsonEncode():
"""Test the jsonEncode function."""
+6 -6
View File
@@ -332,7 +332,7 @@ def testGuiTheme_LoadIcons(qtbot):
# Load an unknown icon
qIcon = iconCache.getIcon("stuff")
assert isinstance(qIcon, QIcon)
assert qIcon.isNull() is True
assert qIcon == iconCache._noIcon
# Load an icon, it is likely already cached
qIcon = iconCache.getIcon("add")
@@ -349,17 +349,17 @@ def testGuiTheme_LoadIcons(qtbot):
# Load app icon
qIcon = iconCache.getIcon("novelwriter")
assert isinstance(qIcon, QIcon)
assert qIcon.isNull() is False
assert qIcon != iconCache._noIcon
# Load mime icon
qIcon = iconCache.getIcon("proj_nwx")
assert isinstance(qIcon, QIcon)
assert qIcon.isNull() is False
assert qIcon != iconCache._noIcon
# Toggle icon
qIcon = iconCache.getToggleIcon("bullet", (24, 24))
assert isinstance(qIcon, QIcon)
assert qIcon.isNull() is False
assert qIcon != iconCache._noIcon
pOn = qIcon.pixmap(24, 24, QIcon.Mode.Normal, QIcon.State.On)
pOff = qIcon.pixmap(24, 24, QIcon.Mode.Normal, QIcon.State.Off)
assert pOn != pOff
@@ -367,7 +367,7 @@ def testGuiTheme_LoadIcons(qtbot):
# Unknown toggle icon
qIcon = iconCache.getToggleIcon("stuff", (24, 24))
assert isinstance(qIcon, QIcon)
assert qIcon.isNull() is True
assert qIcon == iconCache._noIcon
# Load Item Icons
# ===============
@@ -420,7 +420,7 @@ def testGuiTheme_LoadIcons(qtbot):
# No Type -> Null
assert iconCache.getItemIcon(
nwItemType.NO_TYPE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H0"
).isNull() is True
) == iconCache._noIcon
# qtbot.stop()