Files
qelectrotech-source-mirror/misc/make_icon_themes.py
T
Jeff Patterson 8ef130a59d Add a "qet" icon theme and load icons by name
QET had no icon theme: the 446 entries of the icon table and the 116
iconsets in .ui files each named a resource path, so an icon could only
ever be one file, and a variant for another palette or a vector source
had nowhere to go (GitHub #466, #690, #870). This adds the theme layout
without changing a single pixel; a dark variant comes in a follow-up.

The theme "qet" follows the freedesktop layout Qt's icon loader
understands. misc/make_icon_themes.py generates ico/icon-themes.qrc,
which aliases the existing ico/<size>/<name>.png files into
themes/qet/<size>/<name>.png, and ico/themes/qet/index.theme. No file
moves. The four table entries that paired a 16 pixel file with a 22
pixel file of another name (ConductorSettings, DiagramAdd,
DiagramDelete, DialogInformation) get the 22 pixel file aliased under
the 16 pixel name.

QETApp::initIconTheme() registers the theme before initIcons() and makes
it current on every platform, so a desktop icon theme cannot replace
QET's icons. Icons are then looked up by name: QIcon::fromTheme() in
qeticons.cpp and in the few places that built a QIcon from a resource
path directly, and theme="..." on the iconsets in .ui files, with the
resource path kept as fallback. Flags, color swatches, application and
MIME icons stay on their paths.

One entry does not go through the theme. The elements panel draws the
project root with ProjectFileGP in the 50 pixel slot it reserves for
element previews, and the name "project" also carries the 128 pixel
file the configuration dialog uses. On a 2x display Qt's loader picks
that file for a 50 pixel request and fills the slot. ProjectFileGP
loads the 16 and 22 pixel files directly, as before.

tests/qttest/tst_qeticons: every name in the theme resolves, the four
aliases resolve at 22 pixels, a Fusion tool button shows its icon at
3:1 with disabled weaker than enabled, and the project root icon stays
at 22 pixels or less when asked for 50 at 2x while the configuration
dialog still gets its 128 pixel file. The rendering helpers shared
with tst_qetpalette moved to tests/qttest/inkcontrast.h.
2026-09-17 05:41:50 -05:00

131 lines
4.6 KiB
Python

#!/usr/bin/env python3
# Copyright 2006-2026 The QElectroTech Team
# This file is part of QElectroTech.
#
# QElectroTech 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 2 of the License, or
# (at your option) any later version.
#
# QElectroTech 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 QElectroTech. If not, see <http://www.gnu.org/licenses/>.
"""Build QET's icon theme "qet" from the PNG and SVG sources in ico/.
Run from the repository root:
python3 misc/make_icon_themes.py
It writes:
ico/themes/qet/index.theme the theme description
ico/icon-themes.qrc resource file listing the theme
The theme does not copy any file. The .qrc aliases the existing
ico/<size>/<name>.png files into the theme layout Qt's icon loader
expects (themes/qet/<size>/<name>.png), so QIcon::fromTheme("name")
finds them. Idempotent: running it twice changes nothing.
"""
import os
import re
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
ICO = ROOT / "ico"
THEMES = ICO / "themes"
QRC = ICO / "icon-themes.qrc"
# Fixed-size folders that hold toolbar, menu and dialog icons. The 24x16
# flags, the 22x22/color swatches and the application icons are not theme
# icons and stay on their resource paths.
SIZES = ["16x16", "22x22", "32x32", "48x48", "128x128"]
# Table entries in sources/qeticons.cpp that pair a 16 pixel file with a
# 22 pixel file of another name. The theme needs one name per icon, so
# the 22 pixel file is exposed under the 16 pixel name as well.
ALIASES = {
"22x22/conductor2.png": "conductor-reset",
"22x22/diagram_add.png": "folio-new",
"22x22/diagram_del.png": "folio-delete",
"22x22/dialog-information.png": "folio-properties",
}
# SVG icons referenced from sources/qeticons.cpp.
SVGS = [
"breeze-icons/scalable/apps/hidef/edit-opacity.svg",
"breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg",
"breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg",
"breeze-icons/scalable/apps/hidef/draw-bezier-curves.svg",
"breeze-icons/scalable/apps/hidef/transform-crop.svg",
"generated/ellipse-to-bezier.svg",
"generated/rect-to-bezier.svg",
"generated/rect-to-polyline.svg",
]
def write_if_changed(path, data):
"""Write text only when the content differs, so git stays quiet."""
if path.exists() and path.read_text(encoding="utf-8") == data:
return False
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(data, encoding="utf-8")
return True
def index_theme(name, comment, dirs, inherits=None):
lines = ["[Icon Theme]", f"Name={name}", f"Comment={comment}"]
if inherits:
lines.append(f"Inherits={inherits}")
lines.append("Directories=" + ",".join(dirs))
lines.append("")
for d in dirs:
lines.append(f"[{d}]")
if d == "scalable":
lines += ["Size=22", "Type=Scalable", "MinSize=8", "MaxSize=256"]
else:
lines += [f"Size={d.split('x')[0]}", "Type=Fixed"]
lines.append("")
return "\n".join(lines)
def main():
entries = [] # (alias, source) pairs, paths relative to ico/
changed = 0
for size in SIZES:
for png in sorted((ICO / size).glob("*.png")):
rel = f"{size}/{png.name}"
names = [png.stem]
if rel in ALIASES:
names.append(ALIASES[rel])
for name in names:
entries.append((f"themes/qet/{size}/{name}.png", rel))
for rel in SVGS:
name = re.sub(r"-symbolic(?=\.svg$)", "", Path(rel).name)
entries.append((f"themes/qet/scalable/{name}", rel))
dirs = SIZES + ["scalable"]
changed += write_if_changed(THEMES / "qet" / "index.theme",
index_theme("qet", "QElectroTech icons", dirs))
qrc = ["<!DOCTYPE RCC>", "<!-- Generated by misc/make_icon_themes.py. Do not edit. -->",
'<RCC version="1.0">', ' <qresource prefix="/ico">',
" <file>themes/qet/index.theme</file>"]
for alias, source in entries:
qrc.append(f' <file alias="{alias}">{source}</file>')
qrc += [" </qresource>", "</RCC>", ""]
changed += write_if_changed(QRC, "\n".join(qrc))
print(f"{len(entries)} theme entries; {changed} files written")
if __name__ == "__main__":
os.chdir(ROOT)
main()