Compare commits
12 Commits
8bbc2da5c7
...
ef1fbd79b4
| Author | SHA1 | Date | |
|---|---|---|---|
| ef1fbd79b4 | |||
| eb7abdfbc6 | |||
| f574d28340 | |||
| 31f90e2c46 | |||
| 45bd8256b3 | |||
| 4e43cde063 | |||
| aef56fe41d | |||
| 0395f90f75 | |||
| a4d5b7a12a | |||
| ba29dc45a0 | |||
| d06c7be606 | |||
| 56cbd6e23d |
@@ -8,3 +8,11 @@ doc/*
|
||||
QElectroTech.tag
|
||||
!doc/doc-utils
|
||||
lang/*.qm
|
||||
|
||||
# -- IDE settings/intermediate files --
|
||||
# zed
|
||||
.zed
|
||||
.cache
|
||||
|
||||
# VS Code
|
||||
.vscode
|
||||
|
||||
@@ -250,6 +250,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/qeticons.h
|
||||
${QET_DIR}/sources/qetpalette.cpp
|
||||
${QET_DIR}/sources/qetpalette.h
|
||||
${QET_DIR}/sources/qetstyle.cpp
|
||||
${QET_DIR}/sources/qetstyle.h
|
||||
${QET_DIR}/sources/qetinformation.cpp
|
||||
${QET_DIR}/sources/qetinformation.h
|
||||
${QET_DIR}/sources/qetmainwindow.cpp
|
||||
@@ -434,6 +436,8 @@ set(QET_SRC_FILES
|
||||
${QET_DIR}/sources/ElementsCollection/elementcollectionhandler.h
|
||||
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.cpp
|
||||
${QET_DIR}/sources/ElementsCollection/elementcollectionitem.h
|
||||
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.cpp
|
||||
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h
|
||||
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.cpp
|
||||
${QET_DIR}/sources/ElementsCollection/elementscollectionmodel.h
|
||||
${QET_DIR}/sources/ElementsCollection/elementscollectionwidget.cpp
|
||||
|
||||
|
Before Width: | Height: | Size: 216 B |
|
Before Width: | Height: | Size: 262 B |
|
Before Width: | Height: | Size: 247 B |
|
Before Width: | Height: | Size: 271 B |
|
Before Width: | Height: | Size: 233 B |
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -216,6 +216,8 @@ Clarification:
|
||||
ico/128x128/diagram.png by the QElectroTech team (License CC BY-ND 3.0)
|
||||
ico/128x128/document-export.png by the QElectroTech team (License CC BY-ND 3.0)
|
||||
ico/128x128/project.png by the QElectroTech team (License CC BY-ND 3.0)
|
||||
ico/scalable/pdf-import.svg by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), laid out like ico/22x22/insert-image.png
|
||||
ico/scalable/diagram.svg, folio-new.svg, folio-delete.svg, folio-properties.svg, label.svg by Jeff Patterson from the QElectroTech team (License CC BY-ND 3.0), the folio icons redrawn in the same style
|
||||
ico/256x256/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
||||
ico/breeze-icons/* by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
||||
ico/diagram.png by Nuri from the QElectroTech team (License CC BY-ND 3.0)
|
||||
|
||||
@@ -148,15 +148,8 @@
|
||||
<file alias="themes/qet/22x22/conductor-reset.png">22x22/conductor2.png</file>
|
||||
<file alias="themes/qet/22x22/configure-toolbars.png">22x22/configure-toolbars.png</file>
|
||||
<file alias="themes/qet/22x22/configure.png">22x22/configure.png</file>
|
||||
<file alias="themes/qet/22x22/diagram.png">22x22/diagram.png</file>
|
||||
<file alias="themes/qet/22x22/diagram_add.png">22x22/diagram_add.png</file>
|
||||
<file alias="themes/qet/22x22/folio-new.png">22x22/diagram_add.png</file>
|
||||
<file alias="themes/qet/22x22/diagram_bg.png">22x22/diagram_bg.png</file>
|
||||
<file alias="themes/qet/22x22/diagram_del.png">22x22/diagram_del.png</file>
|
||||
<file alias="themes/qet/22x22/folio-delete.png">22x22/diagram_del.png</file>
|
||||
<file alias="themes/qet/22x22/dialog-cancel.png">22x22/dialog-cancel.png</file>
|
||||
<file alias="themes/qet/22x22/dialog-information.png">22x22/dialog-information.png</file>
|
||||
<file alias="themes/qet/22x22/folio-properties.png">22x22/dialog-information.png</file>
|
||||
<file alias="themes/qet/22x22/dialog-ok.png">22x22/dialog-ok.png</file>
|
||||
<file alias="themes/qet/22x22/document-close.png">22x22/document-close.png</file>
|
||||
<file alias="themes/qet/22x22/document-export.png">22x22/document-export.png</file>
|
||||
@@ -207,7 +200,6 @@
|
||||
<file alias="themes/qet/22x22/guides.png">22x22/guides.png</file>
|
||||
<file alias="themes/qet/22x22/hotspot.png">22x22/hotspot.png</file>
|
||||
<file alias="themes/qet/22x22/insert-image.png">22x22/insert-image.png</file>
|
||||
<file alias="themes/qet/22x22/label.png">22x22/label.png</file>
|
||||
<file alias="themes/qet/22x22/landscape.png">22x22/landscape.png</file>
|
||||
<file alias="themes/qet/22x22/line.png">22x22/line.png</file>
|
||||
<file alias="themes/qet/22x22/list-add.png">22x22/list-add.png</file>
|
||||
@@ -218,7 +210,6 @@
|
||||
<file alias="themes/qet/22x22/object-locked.png">22x22/object-locked.png</file>
|
||||
<file alias="themes/qet/22x22/object-rotate-right.png">22x22/object-rotate-right.png</file>
|
||||
<file alias="themes/qet/22x22/object-unlocked.png">22x22/object-unlocked.png</file>
|
||||
<file alias="themes/qet/22x22/pdf-import.png">22x22/pdf-import.png</file>
|
||||
<file alias="themes/qet/22x22/polygon.png">22x22/polygon.png</file>
|
||||
<file alias="themes/qet/22x22/portrait.png">22x22/portrait.png</file>
|
||||
<file alias="themes/qet/22x22/preferences-desktop-user.png">22x22/preferences-desktop-user.png</file>
|
||||
@@ -274,6 +265,12 @@
|
||||
<file alias="themes/qet/128x128/printer.png">128x128/printer.png</file>
|
||||
<file alias="themes/qet/128x128/project.png">128x128/project.png</file>
|
||||
<file alias="themes/qet/128x128/settings.png">128x128/settings.png</file>
|
||||
<file alias="themes/qet/scalable/diagram.svg">scalable/diagram.svg</file>
|
||||
<file alias="themes/qet/scalable/folio-delete.svg">scalable/folio-delete.svg</file>
|
||||
<file alias="themes/qet/scalable/folio-new.svg">scalable/folio-new.svg</file>
|
||||
<file alias="themes/qet/scalable/folio-properties.svg">scalable/folio-properties.svg</file>
|
||||
<file alias="themes/qet/scalable/label.svg">scalable/label.svg</file>
|
||||
<file alias="themes/qet/scalable/pdf-import.svg">scalable/pdf-import.svg</file>
|
||||
<file alias="themes/qet/scalable/edit-opacity.svg">breeze-icons/scalable/apps/hidef/edit-opacity.svg</file>
|
||||
<file alias="themes/qet/scalable/image-flip-horizontal.svg">breeze-icons/scalable/apps/hidef/image-flip-horizontal-symbolic.svg</file>
|
||||
<file alias="themes/qet/scalable/image-flip-vertical.svg">breeze-icons/scalable/apps/hidef/image-flip-vertical-symbolic.svg</file>
|
||||
@@ -290,7 +287,6 @@
|
||||
<file>themes/qet-dark/16x16/conductor-edit.png</file>
|
||||
<file>themes/qet-dark/16x16/configure-toolbars.png</file>
|
||||
<file>themes/qet-dark/16x16/configure.png</file>
|
||||
<file>themes/qet-dark/16x16/diagram.png</file>
|
||||
<file>themes/qet-dark/16x16/dialog-cancel.png</file>
|
||||
<file>themes/qet-dark/16x16/dialog-ok.png</file>
|
||||
<file>themes/qet-dark/16x16/document-export.png</file>
|
||||
@@ -331,9 +327,6 @@
|
||||
<file>themes/qet-dark/16x16/folder-open.png</file>
|
||||
<file>themes/qet-dark/16x16/folder-show-all.png</file>
|
||||
<file>themes/qet-dark/16x16/folder.png</file>
|
||||
<file>themes/qet-dark/16x16/folio-delete.png</file>
|
||||
<file>themes/qet-dark/16x16/folio-new.png</file>
|
||||
<file>themes/qet-dark/16x16/folio-properties.png</file>
|
||||
<file>themes/qet-dark/16x16/folio-ref-coming.png</file>
|
||||
<file>themes/qet-dark/16x16/go-company.png</file>
|
||||
<file>themes/qet-dark/16x16/go-down-double.png</file>
|
||||
@@ -348,7 +341,6 @@
|
||||
<file>themes/qet-dark/16x16/item-copy.png</file>
|
||||
<file>themes/qet-dark/16x16/item-move.png</file>
|
||||
<file>themes/qet-dark/16x16/kdenlive-show-video.png</file>
|
||||
<file>themes/qet-dark/16x16/label.png</file>
|
||||
<file>themes/qet-dark/16x16/list-add.png</file>
|
||||
<file>themes/qet-dark/16x16/list-remove.png</file>
|
||||
<file>themes/qet-dark/16x16/masquer.png</file>
|
||||
@@ -381,15 +373,7 @@
|
||||
<file>themes/qet-dark/22x22/arrow-right.png</file>
|
||||
<file>themes/qet-dark/22x22/configure-toolbars.png</file>
|
||||
<file>themes/qet-dark/22x22/configure.png</file>
|
||||
<file>themes/qet-dark/22x22/diagram.png</file>
|
||||
<file>themes/qet-dark/22x22/diagram_add.png</file>
|
||||
<file>themes/qet-dark/22x22/folio-new.png</file>
|
||||
<file>themes/qet-dark/22x22/diagram_bg.png</file>
|
||||
<file>themes/qet-dark/22x22/diagram_del.png</file>
|
||||
<file>themes/qet-dark/22x22/folio-delete.png</file>
|
||||
<file>themes/qet-dark/22x22/dialog-cancel.png</file>
|
||||
<file>themes/qet-dark/22x22/dialog-information.png</file>
|
||||
<file>themes/qet-dark/22x22/folio-properties.png</file>
|
||||
<file>themes/qet-dark/22x22/dialog-ok.png</file>
|
||||
<file>themes/qet-dark/22x22/document-export.png</file>
|
||||
<file>themes/qet-dark/22x22/document-import.png</file>
|
||||
@@ -425,7 +409,6 @@
|
||||
<file>themes/qet-dark/22x22/go-up.png</file>
|
||||
<file>themes/qet-dark/22x22/grid.png</file>
|
||||
<file>themes/qet-dark/22x22/insert-image.png</file>
|
||||
<file>themes/qet-dark/22x22/label.png</file>
|
||||
<file>themes/qet-dark/22x22/landscape.png</file>
|
||||
<file>themes/qet-dark/22x22/line.png</file>
|
||||
<file>themes/qet-dark/22x22/list-add.png</file>
|
||||
@@ -436,7 +419,6 @@
|
||||
<file>themes/qet-dark/22x22/object-locked.png</file>
|
||||
<file>themes/qet-dark/22x22/object-rotate-right.png</file>
|
||||
<file>themes/qet-dark/22x22/object-unlocked.png</file>
|
||||
<file>themes/qet-dark/22x22/pdf-import.png</file>
|
||||
<file>themes/qet-dark/22x22/polygon.png</file>
|
||||
<file>themes/qet-dark/22x22/portrait.png</file>
|
||||
<file>themes/qet-dark/22x22/preferences-desktop-user.png</file>
|
||||
@@ -472,8 +454,13 @@
|
||||
<file>themes/qet-dark/48x48/user-away.png</file>
|
||||
<file>themes/qet-dark/48x48/view-pim-journal.png</file>
|
||||
<file>themes/qet-dark/128x128/plasmagik.png</file>
|
||||
<file>themes/qet-dark/128x128/printer.png</file>
|
||||
<file>themes/qet-dark/128x128/settings.png</file>
|
||||
<file>themes/qet-dark/scalable/diagram.svg</file>
|
||||
<file>themes/qet-dark/scalable/folio-delete.svg</file>
|
||||
<file>themes/qet-dark/scalable/folio-new.svg</file>
|
||||
<file>themes/qet-dark/scalable/folio-properties.svg</file>
|
||||
<file>themes/qet-dark/scalable/label.svg</file>
|
||||
<file>themes/qet-dark/scalable/pdf-import.svg</file>
|
||||
<file>themes/qet-dark/scalable/edit-opacity.svg</file>
|
||||
<file>themes/qet-dark/scalable/image-flip-horizontal.svg</file>
|
||||
<file>themes/qet-dark/scalable/image-flip-vertical.svg</file>
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 17 18 h 1 v 1 h -1 Z M 17 19 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 8 h 1 v 1 h -1 Z M 6 10 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 7 10 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 8 h 1 v 1 h -1 Z M 8 10 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 8 h 1 v 1 h -1 Z M 10 10 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 8 h 1 v 1 h -1 Z M 12 10 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 8 h 1 v 1 h -1 Z M 14 10 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 8 h 1 v 1 h -1 Z M 15 10 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 14 h 1 v 1 h -1 Z M 4 15 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 14 h 1 v 1 h -1 Z M 5 15 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 14 h 1 v 1 h -1 Z M 6 15 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 14 h 1 v 1 h -1 Z M 7 15 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 14 h 1 v 1 h -1 Z M 8 15 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 14 h 1 v 1 h -1 Z M 9 15 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 14 h 1 v 1 h -1 Z M 10 15 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 14 h 1 v 1 h -1 Z M 12 15 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 14 h 1 v 1 h -1 Z M 13 15 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 14 h 1 v 1 h -1 Z M 14 15 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 14 h 1 v 1 h -1 Z M 15 15 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 14 h 1 v 1 h -1 Z M 16 15 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#4d4d4d;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 3 3 h 16 v 1 h -16 Z M 3 4 h 1 v 15 h -1 Z M 4 18 h 10 v 1 h -10 Z M 18 4 h 1 v 8 h -1 Z M 16 14 h 1 v 5 h -1 Z M 14 16 h 5 v 1 h -5 Z" class="ColorScheme-Text"/>
|
||||
<path style="fill:currentColor;fill-opacity:1;stroke:none" d="M 5 7 h 1 v 1 h -1 Z M 6 7 h 1 v 1 h -1 Z M 7 7 h 1 v 1 h -1 Z M 5 8 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 5 9 h 1 v 1 h -1 Z M 6 9 h 1 v 1 h -1 Z M 7 9 h 1 v 1 h -1 Z M 5 10 h 1 v 1 h -1 Z M 5 11 h 1 v 1 h -1 Z M 9 7 h 1 v 1 h -1 Z M 10 7 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 9 9 h 1 v 1 h -1 Z M 11 9 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 9 11 h 1 v 1 h -1 Z M 10 11 h 1 v 1 h -1 Z M 13 7 h 1 v 1 h -1 Z M 14 7 h 1 v 1 h -1 Z M 15 7 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 9 h 1 v 1 h -1 Z M 14 9 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 11 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 132 B |
|
Before Width: | Height: | Size: 154 B |
|
Before Width: | Height: | Size: 176 B |
|
Before Width: | Height: | Size: 163 B |
|
Before Width: | Height: | Size: 141 B |
|
Before Width: | Height: | Size: 146 B |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 702 B |
|
Before Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 181 B |
|
Before Width: | Height: | Size: 196 B |
|
Before Width: | Height: | Size: 189 B |
|
Before Width: | Height: | Size: 165 B |
|
Before Width: | Height: | Size: 667 B |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dcdcdc;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dcdcdc;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.7 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dcdcdc;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 17 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 17 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 17 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 17 17 h 1 v 1 h -1 Z M 17 18 h 1 v 1 h -1 Z M 17 19 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 17 h 1 v 1 h -1 Z M 19 17 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dcdcdc;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 8 h 1 v 1 h -1 Z M 6 10 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 7 10 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 8 h 1 v 1 h -1 Z M 8 10 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 8 h 1 v 1 h -1 Z M 10 10 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 8 h 1 v 1 h -1 Z M 12 10 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 8 h 1 v 1 h -1 Z M 14 10 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 8 h 1 v 1 h -1 Z M 15 10 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dcdcdc;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 5 h 1 v 1 h -1 Z M 3 6 h 1 v 1 h -1 Z M 3 7 h 1 v 1 h -1 Z M 3 8 h 1 v 1 h -1 Z M 3 9 h 1 v 1 h -1 Z M 3 10 h 1 v 1 h -1 Z M 3 11 h 1 v 1 h -1 Z M 3 12 h 1 v 1 h -1 Z M 3 13 h 1 v 1 h -1 Z M 3 14 h 1 v 1 h -1 Z M 3 15 h 1 v 1 h -1 Z M 3 16 h 1 v 1 h -1 Z M 4 5 h 1 v 1 h -1 Z M 4 13 h 1 v 1 h -1 Z M 4 14 h 1 v 1 h -1 Z M 4 15 h 1 v 1 h -1 Z M 4 16 h 1 v 1 h -1 Z M 5 5 h 1 v 1 h -1 Z M 5 13 h 1 v 1 h -1 Z M 5 14 h 1 v 1 h -1 Z M 5 15 h 1 v 1 h -1 Z M 5 16 h 1 v 1 h -1 Z M 6 5 h 1 v 1 h -1 Z M 6 13 h 1 v 1 h -1 Z M 6 14 h 1 v 1 h -1 Z M 6 15 h 1 v 1 h -1 Z M 6 16 h 1 v 1 h -1 Z M 7 5 h 1 v 1 h -1 Z M 7 13 h 1 v 1 h -1 Z M 7 14 h 1 v 1 h -1 Z M 7 15 h 1 v 1 h -1 Z M 7 16 h 1 v 1 h -1 Z M 8 5 h 1 v 1 h -1 Z M 8 13 h 1 v 1 h -1 Z M 8 14 h 1 v 1 h -1 Z M 8 15 h 1 v 1 h -1 Z M 8 16 h 1 v 1 h -1 Z M 9 5 h 1 v 1 h -1 Z M 9 13 h 1 v 1 h -1 Z M 9 14 h 1 v 1 h -1 Z M 9 15 h 1 v 1 h -1 Z M 9 16 h 1 v 1 h -1 Z M 10 5 h 1 v 1 h -1 Z M 10 13 h 1 v 1 h -1 Z M 10 14 h 1 v 1 h -1 Z M 10 15 h 1 v 1 h -1 Z M 10 16 h 1 v 1 h -1 Z M 11 5 h 1 v 1 h -1 Z M 11 13 h 1 v 1 h -1 Z M 11 14 h 1 v 1 h -1 Z M 11 15 h 1 v 1 h -1 Z M 11 16 h 1 v 1 h -1 Z M 12 5 h 1 v 1 h -1 Z M 12 13 h 1 v 1 h -1 Z M 12 14 h 1 v 1 h -1 Z M 12 15 h 1 v 1 h -1 Z M 12 16 h 1 v 1 h -1 Z M 13 5 h 1 v 1 h -1 Z M 13 13 h 1 v 1 h -1 Z M 13 14 h 1 v 1 h -1 Z M 13 15 h 1 v 1 h -1 Z M 13 16 h 1 v 1 h -1 Z M 14 5 h 1 v 1 h -1 Z M 14 13 h 1 v 1 h -1 Z M 14 14 h 1 v 1 h -1 Z M 14 15 h 1 v 1 h -1 Z M 14 16 h 1 v 1 h -1 Z M 15 5 h 1 v 1 h -1 Z M 15 13 h 1 v 1 h -1 Z M 15 14 h 1 v 1 h -1 Z M 15 15 h 1 v 1 h -1 Z M 15 16 h 1 v 1 h -1 Z M 16 5 h 1 v 1 h -1 Z M 16 13 h 1 v 1 h -1 Z M 16 14 h 1 v 1 h -1 Z M 16 15 h 1 v 1 h -1 Z M 16 16 h 1 v 1 h -1 Z M 17 5 h 1 v 1 h -1 Z M 17 13 h 1 v 1 h -1 Z M 17 14 h 1 v 1 h -1 Z M 17 15 h 1 v 1 h -1 Z M 17 16 h 1 v 1 h -1 Z M 18 5 h 1 v 1 h -1 Z M 18 6 h 1 v 1 h -1 Z M 18 7 h 1 v 1 h -1 Z M 18 8 h 1 v 1 h -1 Z M 18 9 h 1 v 1 h -1 Z M 18 10 h 1 v 1 h -1 Z M 18 11 h 1 v 1 h -1 Z M 18 12 h 1 v 1 h -1 Z M 18 13 h 1 v 1 h -1 Z M 18 14 h 1 v 1 h -1 Z M 18 15 h 1 v 1 h -1 Z M 18 16 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.4 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="24" height="24">
|
||||
<defs id="defs3051">
|
||||
<style type="text/css" id="current-color-scheme">
|
||||
.ColorScheme-Text {
|
||||
color:#dcdcdc;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g transform="translate(1,1)">
|
||||
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 3 3 h 16 v 1 h -16 Z M 3 4 h 1 v 15 h -1 Z M 4 18 h 10 v 1 h -10 Z M 18 4 h 1 v 8 h -1 Z M 16 14 h 1 v 5 h -1 Z M 14 16 h 5 v 1 h -5 Z" class="ColorScheme-Text"/>
|
||||
<path style="fill:#dcdcdc;fill-opacity:1;stroke:none" d="M 5 7 h 1 v 1 h -1 Z M 6 7 h 1 v 1 h -1 Z M 7 7 h 1 v 1 h -1 Z M 5 8 h 1 v 1 h -1 Z M 7 8 h 1 v 1 h -1 Z M 5 9 h 1 v 1 h -1 Z M 6 9 h 1 v 1 h -1 Z M 7 9 h 1 v 1 h -1 Z M 5 10 h 1 v 1 h -1 Z M 5 11 h 1 v 1 h -1 Z M 9 7 h 1 v 1 h -1 Z M 10 7 h 1 v 1 h -1 Z M 9 8 h 1 v 1 h -1 Z M 11 8 h 1 v 1 h -1 Z M 9 9 h 1 v 1 h -1 Z M 11 9 h 1 v 1 h -1 Z M 9 10 h 1 v 1 h -1 Z M 11 10 h 1 v 1 h -1 Z M 9 11 h 1 v 1 h -1 Z M 10 11 h 1 v 1 h -1 Z M 13 7 h 1 v 1 h -1 Z M 14 7 h 1 v 1 h -1 Z M 15 7 h 1 v 1 h -1 Z M 13 8 h 1 v 1 h -1 Z M 13 9 h 1 v 1 h -1 Z M 14 9 h 1 v 1 h -1 Z M 13 10 h 1 v 1 h -1 Z M 13 11 h 1 v 1 h -1 Z" class="ColorScheme-Text"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -38,6 +38,13 @@ An icon counts as line art when fewer than 20% of its visible pixels are
|
||||
saturated. Its dark copy keeps hue and alpha and inverts lightness, scaled
|
||||
so pure black becomes (220, 220, 220), the dark palette's text color.
|
||||
|
||||
An icon drawn as a light object is left alone even when it is not
|
||||
saturated: a page sheet, a printer body, the white half of the background
|
||||
swatch. Such art already reads on a dark toolbar, and inverting it would
|
||||
turn the white body black (the PDF import icon, GitHub #919). The test is
|
||||
the share of visible pixels that are near white: 30% or more and the icon
|
||||
inherits from the light theme untouched.
|
||||
|
||||
Requires Pillow. Idempotent: running it twice changes nothing.
|
||||
"""
|
||||
|
||||
@@ -64,16 +71,21 @@ 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.
|
||||
# the 22 pixel file is exposed under the 16 pixel name as well. The folio
|
||||
# icons that used to be listed here are SVGs now (ico/scalable/).
|
||||
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.
|
||||
# SVG icons referenced from sources/qeticons.cpp. ico/scalable/ holds the
|
||||
# ones drawn for QET as vectors; one file serves every size.
|
||||
SVGS = [
|
||||
"scalable/diagram.svg",
|
||||
"scalable/folio-delete.svg",
|
||||
"scalable/folio-new.svg",
|
||||
"scalable/folio-properties.svg",
|
||||
"scalable/label.svg",
|
||||
"scalable/pdf-import.svg",
|
||||
"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",
|
||||
@@ -85,6 +97,8 @@ SVGS = [
|
||||
]
|
||||
|
||||
SATURATED_FRACTION = 0.20 # at or above this an icon is "colored"
|
||||
WHITE_LIGHTNESS = 0.85 # a visible pixel this light counts as white
|
||||
WHITE_FRACTION = 0.30 # at or above this an icon is "light art"
|
||||
INK = 220 / 255.0 # lightness of pure black after inversion
|
||||
SVG_INK = "#dcdcdc"
|
||||
|
||||
@@ -107,6 +121,16 @@ def is_line_art(image):
|
||||
return saturated / len(pixels) < SATURATED_FRACTION
|
||||
|
||||
|
||||
def has_light_fill(image):
|
||||
"""True when the icon is mostly white: a page, a sheet, a light body."""
|
||||
pixels = visible_pixels(image)
|
||||
if not pixels:
|
||||
return False
|
||||
white = sum(1 for r, g, b, _ in pixels
|
||||
if colorsys.rgb_to_hls(r / 255, g / 255, b / 255)[1] > WHITE_LIGHTNESS)
|
||||
return white / len(pixels) >= WHITE_FRACTION
|
||||
|
||||
|
||||
def invert_lightness(image):
|
||||
"""Invert lightness, keeping hue, saturation and alpha.
|
||||
|
||||
@@ -177,7 +201,7 @@ def main():
|
||||
light = [] # (alias, source) pairs, paths relative to ico/
|
||||
dark = [] # paths relative to ico/, files exist on disk
|
||||
changed = 0
|
||||
line_art = colored = 0
|
||||
line_art = colored = light_art = 0
|
||||
|
||||
for size in SIZES:
|
||||
folder = ICO / size
|
||||
@@ -188,7 +212,10 @@ def main():
|
||||
names.append(ALIASES[rel])
|
||||
image = Image.open(png).convert("RGBA")
|
||||
art = is_line_art(image)
|
||||
if art:
|
||||
if art and has_light_fill(image):
|
||||
art = False
|
||||
light_art += 1
|
||||
elif art:
|
||||
line_art += 1
|
||||
dark_image = None
|
||||
else:
|
||||
@@ -213,6 +240,15 @@ def main():
|
||||
changed += write_if_changed(target, text)
|
||||
dark.append(f"themes/qet-dark/scalable/{name}")
|
||||
|
||||
# Drop dark files from an earlier run that are no longer generated, so
|
||||
# a reclassified icon falls back to the light theme instead of keeping
|
||||
# a stale copy.
|
||||
for stale in sorted((THEMES / "qet-dark").glob("*/*")):
|
||||
if stale.is_file() and stale.name != "index.theme" \
|
||||
and str(stale.relative_to(ICO)) not in dark:
|
||||
stale.unlink()
|
||||
changed += 1
|
||||
|
||||
dirs = SIZES + ["scalable"]
|
||||
changed += write_if_changed(THEMES / "qet" / "index.theme",
|
||||
index_theme("qet", "QElectroTech icons", dirs))
|
||||
@@ -231,8 +267,8 @@ def main():
|
||||
qrc += [" </qresource>", "</RCC>", ""]
|
||||
changed += write_if_changed(QRC, "\n".join(qrc))
|
||||
|
||||
print(f"{line_art} line-art icons, {colored} colored icons, {len(SVGS)} SVGs; "
|
||||
f"{changed} files written")
|
||||
print(f"{line_art} line-art icons, {light_art} light icons, {colored} colored icons, "
|
||||
f"{len(SVGS)} SVGs; {changed} files written or removed")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -93,10 +93,7 @@
|
||||
<file>ico/22x22/conductor2.png</file>
|
||||
<file>ico/22x22/configure.png</file>
|
||||
<file>ico/22x22/configure-toolbars.png</file>
|
||||
<file>ico/22x22/diagram_add.png</file>
|
||||
<file>ico/22x22/diagram_del.png</file>
|
||||
<file>ico/22x22/dialog-cancel.png</file>
|
||||
<file>ico/22x22/dialog-information.png</file>
|
||||
<file>ico/22x22/dialog-ok.png</file>
|
||||
<file>ico/22x22/document-close.png</file>
|
||||
<file>ico/22x22/document-export.png</file>
|
||||
@@ -140,8 +137,6 @@
|
||||
<file>ico/22x22/go-up.png</file>
|
||||
<file>ico/22x22/hotspot.png</file>
|
||||
<file>ico/22x22/insert-image.png</file>
|
||||
<file>ico/22x22/pdf-import.png</file>
|
||||
<file>ico/22x22/label.png</file>
|
||||
<file>ico/22x22/landscape.png</file>
|
||||
<file>ico/22x22/line.png</file>
|
||||
<file>ico/22x22/list-add.png</file>
|
||||
@@ -242,7 +237,6 @@
|
||||
<file>ico/16x16/project.png</file>
|
||||
<file>ico/16x16/qt.png</file>
|
||||
<file>ico/16x16/terminalstrip.png</file>
|
||||
<file>ico/22x22/diagram.png</file>
|
||||
<file>ico/22x22/edit-clear-locationbar-rtl.png</file>
|
||||
<file>ico/22x22/export-csv.png</file>
|
||||
<file>ico/22x22/format-text-subscript.png</file>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#include "elementpreviewdelegate.h"
|
||||
|
||||
#include <QWidget>
|
||||
|
||||
#include "../qetpalette.h"
|
||||
|
||||
/**
|
||||
@brief ElementPreviewDelegate::initStyleOption
|
||||
After the base class has filled the option from the model, replace
|
||||
the icon with one that reads on a dark palette. The adapted icon is
|
||||
built from the pixmap the view is about to draw, at the view's
|
||||
decoration size and device pixel ratio.
|
||||
*/
|
||||
void ElementPreviewDelegate::initStyleOption(QStyleOptionViewItem *option,
|
||||
const QModelIndex &index) const
|
||||
{
|
||||
QStyledItemDelegate::initStyleOption(option, index);
|
||||
if (option->icon.isNull() || !QET::Palette::isDark(option->palette))
|
||||
return;
|
||||
|
||||
const qint64 key = option->icon.cacheKey();
|
||||
const auto it = m_dark_icons.constFind(key);
|
||||
if (it != m_dark_icons.constEnd())
|
||||
{
|
||||
option->icon = *it;
|
||||
return;
|
||||
}
|
||||
|
||||
const qreal dpr = option->widget ? option->widget->devicePixelRatio() : 1.0;
|
||||
const QPixmap source = option->icon.pixmap(option->decorationSize, dpr);
|
||||
const QPixmap adapted = QET::Palette::forPalette(source, option->palette);
|
||||
QIcon icon = option->icon;
|
||||
if (adapted.cacheKey() != source.cacheKey())
|
||||
icon = QIcon(adapted);
|
||||
m_dark_icons.insert(key, icon);
|
||||
option->icon = icon;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#ifndef ELEMENTPREVIEWDELEGATE_H
|
||||
#define ELEMENTPREVIEWDELEGATE_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QStyledItemDelegate>
|
||||
|
||||
/**
|
||||
@brief The ElementPreviewDelegate class
|
||||
Draws the items of an element collection tree with icons that read on
|
||||
the current palette. Element previews are black line art drawn for a
|
||||
white sheet; on a dark palette this delegate hands the view the same
|
||||
picture with its lightness inverted (QET::Palette::forPalette), so the
|
||||
ink is light on the dark row. Colored icons, such as folders, are left
|
||||
alone, and nothing changes on a light palette. Adapted icons are kept
|
||||
per source icon, so a repaint costs a hash lookup.
|
||||
*/
|
||||
class ElementPreviewDelegate : public QStyledItemDelegate
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
using QStyledItemDelegate::QStyledItemDelegate;
|
||||
|
||||
protected:
|
||||
void initStyleOption(QStyleOptionViewItem *option,
|
||||
const QModelIndex &index) const override;
|
||||
|
||||
private:
|
||||
mutable QHash<qint64, QIcon> m_dark_icons;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -725,7 +725,10 @@ void ElementsCollectionWidget::showThisDir()
|
||||
ElementCollectionItem *eci =
|
||||
elementCollectionItemForIndex(m_showed_index);
|
||||
if (eci)
|
||||
{
|
||||
eci->setBackground(QBrush());
|
||||
eci->setForeground(QBrush());
|
||||
}
|
||||
}
|
||||
|
||||
m_showed_index = m_index_at_context_menu;
|
||||
@@ -736,7 +739,11 @@ void ElementsCollectionWidget::showThisDir()
|
||||
ElementCollectionItem *eci =
|
||||
elementCollectionItemForIndex(m_showed_index);
|
||||
if (eci)
|
||||
{
|
||||
// Amber under black, whatever the palette's text color.
|
||||
eci->setBackground(QBrush(QColor(255, 204, 0, 255)));
|
||||
eci->setForeground(QBrush(Qt::black));
|
||||
}
|
||||
search();
|
||||
}
|
||||
else
|
||||
@@ -755,7 +762,10 @@ void ElementsCollectionWidget::resetShowThisDir()
|
||||
ElementCollectionItem *eci = elementCollectionItemForIndex(
|
||||
m_showed_index);
|
||||
if (eci)
|
||||
{
|
||||
eci->setBackground(QBrush());
|
||||
eci->setForeground(QBrush());
|
||||
}
|
||||
}
|
||||
|
||||
m_showed_index = QModelIndex();
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
along with QElectroTech. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#include "elementstreeview.h"
|
||||
#include "elementpreviewdelegate.h"
|
||||
#include "../qetpalette.h"
|
||||
|
||||
#include "../factory/elementfactory.h"
|
||||
#include "../qetgraphicsitem/element.h"
|
||||
@@ -41,24 +43,11 @@ static int MAX_DND_PIXMAP_HEIGHT = 375;
|
||||
ElementsTreeView::ElementsTreeView(QWidget *parent) :
|
||||
QTreeView(parent)
|
||||
{
|
||||
// force du noir sur une alternance de blanc (comme le schema) et de gris
|
||||
// clair, avec du blanc sur bleu pas trop fonce pour la selection
|
||||
//
|
||||
// Element icons are rendered with colors read directly from each .elmt
|
||||
// file (almost always black linework, matching printed-schematic
|
||||
// convention) onto a transparent background -- so this view must keep
|
||||
// a light background regardless of the OS/desktop theme, or the icons
|
||||
// become invisible on dark themes. QAbstractItemView paints its rows
|
||||
// using the viewport's palette, not the view's own, so the palette
|
||||
// must be applied to both to actually take effect under every style.
|
||||
QPalette qp = palette();
|
||||
qp.setColor(QPalette::Text, Qt::black);
|
||||
qp.setColor(QPalette::Base, Qt::white);
|
||||
qp.setColor(QPalette::AlternateBase, QColor("#e8e8e8"));
|
||||
qp.setColor(QPalette::Highlight, QColor("#678db2"));
|
||||
qp.setColor(QPalette::HighlightedText, Qt::black);
|
||||
setPalette(qp);
|
||||
viewport()->setPalette(qp);
|
||||
// Rows follow the application palette. Element previews are black
|
||||
// line art drawn for a white sheet; ElementPreviewDelegate adapts them
|
||||
// to a dark palette, so this view no longer has to force a light one
|
||||
// (bugtracker #335).
|
||||
setItemDelegate(new ElementPreviewDelegate(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -220,7 +209,7 @@ void ElementsTreeView::startElementDrag(const ElementsLocation &location)
|
||||
&elmt_creation_state));
|
||||
if (elmt_creation_state) { return; }
|
||||
|
||||
QPixmap elmt_pixmap(temp_elmt->pixmap());
|
||||
QPixmap elmt_pixmap(QET::Palette::forPalette(temp_elmt->pixmap(), palette()));
|
||||
QPoint elmt_hotspot(temp_elmt->hotspot());
|
||||
|
||||
//Adjust the size of the pixmap if he is too big
|
||||
|
||||
@@ -52,6 +52,18 @@ ElementsCollectionCache::ElementsCollectionCache(const QString &database_path, Q
|
||||
QSqlQuery(cache_db_).exec("PRAGMA locking_mode = EXCLUSIVE");
|
||||
QSqlQuery(cache_db_).exec("PRAGMA synchronous = OFF");
|
||||
|
||||
// Previews used to be stored on an opaque white sheet; they are
|
||||
// transparent now, so the collection tree can adapt them to a
|
||||
// dark palette. A cache written before that is dropped once.
|
||||
QSqlQuery(cache_db_).exec("CREATE TABLE IF NOT EXISTS meta"
|
||||
"(key VARCHAR(32) NOT NULL PRIMARY KEY, value VARCHAR(64));");
|
||||
QSqlQuery meta(cache_db_);
|
||||
meta.exec("SELECT value FROM meta WHERE key = 'pixmaps'");
|
||||
if (!meta.next() || meta.value(0).toString() != QLatin1String("transparent")) {
|
||||
QSqlQuery(cache_db_).exec("DROP TABLE IF EXISTS pixmaps");
|
||||
QSqlQuery(cache_db_).exec("DROP TABLE IF EXISTS names");
|
||||
QSqlQuery(cache_db_).exec("REPLACE INTO meta (key, value) VALUES ('pixmaps', 'transparent')");
|
||||
}
|
||||
#if TODO_LIST
|
||||
#pragma message("@TODO the tables could already exist, handle that case.")
|
||||
#endif
|
||||
|
||||
@@ -54,24 +54,8 @@ ElementsPanel::ElementsPanel(QWidget *parent) :
|
||||
setDropIndicatorShown(true);
|
||||
setAutoExpandDelay(1000);
|
||||
|
||||
// force du noir sur une alternance de blanc (comme le schema) et de gris
|
||||
// clair, avec du blanc sur bleu pas trop fonce pour la selection
|
||||
//
|
||||
// Element icons are rendered with colors read directly from each .elmt
|
||||
// file (almost always black linework, matching printed-schematic
|
||||
// convention) onto a transparent background -- so this view must keep
|
||||
// a light background regardless of the OS/desktop theme, or the icons
|
||||
// become invisible on dark themes. QAbstractItemView paints its rows
|
||||
// using the viewport's palette, not the view's own, so the palette
|
||||
// must be applied to both to actually take effect under every style.
|
||||
QPalette qp = palette();
|
||||
qp.setColor(QPalette::Text, Qt::black);
|
||||
qp.setColor(QPalette::Base, Qt::white);
|
||||
qp.setColor(QPalette::AlternateBase, QColor("#e8e8e8"));
|
||||
qp.setColor(QPalette::Highlight, QColor("#678db2"));
|
||||
qp.setColor(QPalette::HighlightedText, Qt::black);
|
||||
setPalette(qp);
|
||||
viewport()->setPalette(qp);
|
||||
// Rows follow the application palette; the icons shown here come from
|
||||
// the icon theme, which has a dark variant.
|
||||
|
||||
// we handle double click on items ourselves
|
||||
connect(this, &ElementsPanel::itemDoubleClicked, this, &ElementsPanel::slot_doubleClick);
|
||||
|
||||
@@ -146,12 +146,13 @@ QPixmap ElementPictureFactory::pixmap(const ElementsLocation &location)
|
||||
QPixmap pix(w, h);
|
||||
//Element definitions almost always draw with a hardcoded black
|
||||
//stroke color, on the assumption of the white diagram sheet they
|
||||
//are normally placed on. A transparent background here makes
|
||||
//that stroke disappear against a dark widget/tree-view background
|
||||
//(bugtracker #335). Give it an opaque white background instead -
|
||||
//exactly what the element already assumes visually, in every
|
||||
//context this pixmap is used (tree icons, drag icon, previews).
|
||||
pix.fill(Qt::white);
|
||||
//are normally placed on. The pixmap is kept as drawn, on a
|
||||
//transparent background: the places that show it (the
|
||||
//collection tree through ElementPreviewDelegate, the drag icon)
|
||||
//adapt it to the palette with QET::Palette::forPalette(), so a
|
||||
//dark palette gets light ink instead of black on black
|
||||
//(bugtracker #335).
|
||||
pix.fill(Qt::transparent);
|
||||
|
||||
QPainter painter(&pix);
|
||||
painter.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "qetdiagrameditor.h"
|
||||
#include "qeticons.h"
|
||||
#include "qetpalette.h"
|
||||
#include "qetstyle.h"
|
||||
#include "utils/qetutils.h"
|
||||
#include "qetmessagebox.h"
|
||||
#include "qetproject.h"
|
||||
@@ -52,6 +53,7 @@
|
||||
#include <QFontDatabase>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QRegularExpression>
|
||||
#include <QStyleFactory>
|
||||
#include <QStyleHints>
|
||||
#ifdef BUILD_WITHOUT_KF
|
||||
# include "ui/nokde/kautosavefile.h"
|
||||
@@ -2368,6 +2370,12 @@ void QETApp::applyIconTheme(const QPalette &palette)
|
||||
*/
|
||||
void QETApp::initStyle()
|
||||
{
|
||||
// Wrap the running style so icons get a hover state (see qetstyle.h).
|
||||
// The proxy keeps the base style's object name, so the Fusion checks
|
||||
// below still see "fusion".
|
||||
if (!qobject_cast<QETStyle *>(qApp->style()))
|
||||
qApp->setStyle(new QETStyle(QStyleFactory::create(qApp->style()->objectName())));
|
||||
|
||||
initial_palette_ = qApp->palette();
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
#include "qetpalette.h"
|
||||
|
||||
#include <QColor>
|
||||
#include <QStyle>
|
||||
#include <cmath>
|
||||
|
||||
@@ -177,3 +178,68 @@ QPalette QET::Palette::forFusion(const QPalette &platform)
|
||||
return withPlatformAccent(isDark(platform) ? fusionDark() : fusionLight(),
|
||||
platform);
|
||||
}
|
||||
|
||||
bool QET::Palette::isLineArt(const QImage &image)
|
||||
{
|
||||
const QImage source = image.convertToFormat(QImage::Format_ARGB32);
|
||||
int visible = 0;
|
||||
int saturated = 0;
|
||||
for (int y = 0; y < source.height(); ++y)
|
||||
{
|
||||
const QRgb *line = reinterpret_cast<const QRgb *>(source.constScanLine(y));
|
||||
for (int x = 0; x < source.width(); ++x)
|
||||
{
|
||||
if (qAlpha(line[x]) <= 64)
|
||||
continue;
|
||||
++visible;
|
||||
const QColor color(line[x]);
|
||||
if (color.hslSaturationF() > 0.25 && color.value() > 60)
|
||||
++saturated;
|
||||
}
|
||||
}
|
||||
return visible > 0 && saturated < visible * 0.20;
|
||||
}
|
||||
|
||||
QImage QET::Palette::invertedLightness(const QImage &image)
|
||||
{
|
||||
// Lightness of pure black after inversion: the dark palette's text.
|
||||
const qreal ink = 220.0 / 255.0;
|
||||
QImage result = image.convertToFormat(QImage::Format_ARGB32);
|
||||
qreal darkest = 1.0;
|
||||
for (int y = 0; y < result.height(); ++y)
|
||||
{
|
||||
const QRgb *line = reinterpret_cast<const QRgb *>(result.constScanLine(y));
|
||||
for (int x = 0; x < result.width(); ++x)
|
||||
if (qAlpha(line[x]) > 64)
|
||||
darkest = qMin(darkest, QColor(line[x]).lightnessF());
|
||||
}
|
||||
const qreal span = qMax(1.0 - darkest, 1e-6);
|
||||
for (int y = 0; y < result.height(); ++y)
|
||||
{
|
||||
QRgb *line = reinterpret_cast<QRgb *>(result.scanLine(y));
|
||||
for (int x = 0; x < result.width(); ++x)
|
||||
{
|
||||
const int alpha = qAlpha(line[x]);
|
||||
if (alpha == 0)
|
||||
continue;
|
||||
const QColor color(line[x]);
|
||||
const qreal lightness = qBound(0.0, ink * (1.0 - (color.lightnessF() - darkest) / span), 1.0);
|
||||
QColor out = QColor::fromHslF(qMax(color.hslHueF(), 0.0), color.hslSaturationF(), lightness);
|
||||
out.setAlpha(alpha);
|
||||
line[x] = out.rgba();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QPixmap QET::Palette::forPalette(const QPixmap &pixmap, const QPalette &palette)
|
||||
{
|
||||
if (pixmap.isNull() || !isDark(palette))
|
||||
return pixmap;
|
||||
const QImage image = pixmap.toImage();
|
||||
if (!isLineArt(image))
|
||||
return pixmap;
|
||||
QPixmap result = QPixmap::fromImage(invertedLightness(image));
|
||||
result.setDevicePixelRatio(pixmap.devicePixelRatio());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -18,7 +18,9 @@
|
||||
#ifndef QET_PALETTE_H
|
||||
#define QET_PALETTE_H
|
||||
|
||||
#include <QImage>
|
||||
#include <QPalette>
|
||||
#include <QPixmap>
|
||||
|
||||
class QStyle;
|
||||
|
||||
@@ -87,6 +89,29 @@ namespace QET {
|
||||
platform's accent color kept when it is readable.
|
||||
*/
|
||||
QPalette forFusion(const QPalette &platform);
|
||||
|
||||
/**
|
||||
True when fewer than a fifth of the visible pixels are
|
||||
saturated: black or gray line art, which is what element
|
||||
previews and most of QET's own icons are.
|
||||
*/
|
||||
bool isLineArt(const QImage &image);
|
||||
|
||||
/**
|
||||
The image with its lightness inverted and hue, saturation and
|
||||
alpha kept: the darkest ink becomes light gray (220), white
|
||||
becomes black. The rule misc/make_icon_themes.py applies when
|
||||
it builds the dark icon theme.
|
||||
*/
|
||||
QImage invertedLightness(const QImage &image);
|
||||
|
||||
/**
|
||||
A picture drawn for a white sheet, made to read on palette:
|
||||
returned as is on a light palette, and with its lightness
|
||||
inverted on a dark one when it is line art. Colored art is
|
||||
left alone either way.
|
||||
*/
|
||||
QPixmap forPalette(const QPixmap &pixmap, const QPalette &palette);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#include "qetstyle.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QImage>
|
||||
#include <QPainter>
|
||||
#include <QPixmap>
|
||||
#include <QStyleOption>
|
||||
#include <QWidget>
|
||||
|
||||
#include "qetpalette.h"
|
||||
|
||||
/**
|
||||
@brief QETStyle::QETStyle
|
||||
@param base the style to wrap; this object takes ownership of it.
|
||||
*/
|
||||
QETStyle::QETStyle(QStyle *base) :
|
||||
QProxyStyle(base)
|
||||
{
|
||||
setObjectName(base ? base->objectName() : QString());
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETStyle::isLineArt
|
||||
The same rule misc/make_icon_themes.py uses to pick the icons that
|
||||
get a dark variant: an icon is line art when fewer than 20% of its
|
||||
visible pixels are saturated.
|
||||
*/
|
||||
bool QETStyle::isLineArt(const QImage &image)
|
||||
{
|
||||
const QImage source = image.convertToFormat(QImage::Format_ARGB32);
|
||||
int visible = 0;
|
||||
int saturated = 0;
|
||||
for (int y = 0; y < source.height(); ++y)
|
||||
{
|
||||
const QRgb *line = reinterpret_cast<const QRgb *>(source.constScanLine(y));
|
||||
for (int x = 0; x < source.width(); ++x)
|
||||
{
|
||||
const QRgb pixel = line[x];
|
||||
if (qAlpha(pixel) <= 64)
|
||||
continue;
|
||||
++visible;
|
||||
const QColor color(pixel);
|
||||
if (color.hslSaturationF() > 0.25 && color.value() > 60)
|
||||
++saturated;
|
||||
}
|
||||
}
|
||||
return visible > 0 && saturated < visible * 0.20;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETStyle::hoverColor
|
||||
The palette's highlight color is the accent users already know from
|
||||
selections. On a dark palette it is too dark to read on a hovered
|
||||
button face, so it is lightened, a step at a time, until it reaches
|
||||
3:1 (WCAG 1.4.11) against the Light role: Fusion paints a hovered
|
||||
auto-raise button with a gradient that runs from Button up to about
|
||||
that color, and the icon has to read on the lightest part of it.
|
||||
*/
|
||||
QColor QETStyle::hoverColor(const QPalette &palette)
|
||||
{
|
||||
const QColor face = palette.color(QPalette::Active, QPalette::Light);
|
||||
QColor ink = palette.color(QPalette::Active, QPalette::Highlight);
|
||||
// 3.5 rather than 3.0: the top of Fusion's hover gradient is a shade
|
||||
// lighter than the Light role, so the icon needs some headroom there.
|
||||
for (int step = 0; step < 20 && QET::Palette::contrastRatio(ink, face) < 3.5; ++step)
|
||||
ink = ink.lighter(110);
|
||||
return ink;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETStyle::tinted
|
||||
@return pixmap with every pixel set to color, alpha kept.
|
||||
*/
|
||||
QPixmap QETStyle::tinted(const QPixmap &pixmap, const QColor &color)
|
||||
{
|
||||
QImage image = pixmap.toImage().convertToFormat(QImage::Format_ARGB32_Premultiplied);
|
||||
QPainter painter(&image);
|
||||
painter.setCompositionMode(QPainter::CompositionMode_SourceIn);
|
||||
painter.fillRect(image.rect(), color);
|
||||
painter.end();
|
||||
|
||||
QPixmap result = QPixmap::fromImage(image);
|
||||
result.setDevicePixelRatio(pixmap.devicePixelRatio());
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETStyle::menuIcon
|
||||
An icon for a highlighted menu item: Normal as the original, Active
|
||||
as the original in the HighlightedText color when it is line art.
|
||||
Cached per source icon, size and device pixel ratio, since menus
|
||||
repaint on every pointer move.
|
||||
*/
|
||||
QIcon QETStyle::menuIcon(const QIcon &icon, const QPalette &palette, int size, qreal dpr) const
|
||||
{
|
||||
const QString key = QString("%1/%2/%3/%4")
|
||||
.arg(icon.cacheKey()).arg(size).arg(dpr)
|
||||
.arg(palette.color(QPalette::Active, QPalette::HighlightedText).name());
|
||||
auto it = menu_icons_.constFind(key);
|
||||
if (it != menu_icons_.constEnd())
|
||||
return *it;
|
||||
|
||||
const QPixmap normal = icon.pixmap(QSize(size, size), dpr, QIcon::Normal);
|
||||
QIcon result;
|
||||
result.addPixmap(normal, QIcon::Normal);
|
||||
result.addPixmap(isLineArt(normal.toImage())
|
||||
? tinted(normal, palette.color(QPalette::Active, QPalette::HighlightedText))
|
||||
: normal,
|
||||
QIcon::Active);
|
||||
result.addPixmap(icon.pixmap(QSize(size, size), dpr, QIcon::Disabled), QIcon::Disabled);
|
||||
menu_icons_.insert(key, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETStyle::drawControl
|
||||
CE_MenuItem with an icon and a highlight: swap the icon for menuIcon()
|
||||
so the base style's QIcon::Active request gets an icon that reads on
|
||||
the highlight bar. Everything else goes to the base style.
|
||||
*/
|
||||
void QETStyle::drawControl(ControlElement element,
|
||||
const QStyleOption *option,
|
||||
QPainter *painter,
|
||||
const QWidget *widget) const
|
||||
{
|
||||
if (element == CE_MenuItem)
|
||||
{
|
||||
const auto *item = qstyleoption_cast<const QStyleOptionMenuItem *>(option);
|
||||
if (item && !item->icon.isNull()
|
||||
&& (item->state & State_Selected) && (item->state & State_Enabled))
|
||||
{
|
||||
QStyleOptionMenuItem copy = *item;
|
||||
const int size = pixelMetric(PM_SmallIconSize, option, widget);
|
||||
const qreal dpr = widget ? widget->devicePixelRatio()
|
||||
: QApplication::instance() ? qApp->devicePixelRatio() : 1.0;
|
||||
copy.icon = menuIcon(item->icon, item->palette, size, dpr);
|
||||
QProxyStyle::drawControl(element, ©, painter, widget);
|
||||
return;
|
||||
}
|
||||
}
|
||||
QProxyStyle::drawControl(element, option, painter, widget);
|
||||
}
|
||||
|
||||
/**
|
||||
@brief QETStyle::generatedIconPixmap
|
||||
QIcon::Active: a line-art icon comes back tinted in hoverColor(),
|
||||
alpha kept, so the hovered icon differs from the normal one on both
|
||||
light and dark palettes. Colored icons and every
|
||||
other mode go to the base style.
|
||||
*/
|
||||
QPixmap QETStyle::generatedIconPixmap(QIcon::Mode mode,
|
||||
const QPixmap &pixmap,
|
||||
const QStyleOption *option) const
|
||||
{
|
||||
if (mode != QIcon::Active || pixmap.isNull())
|
||||
return QProxyStyle::generatedIconPixmap(mode, pixmap, option);
|
||||
|
||||
if (!isLineArt(pixmap.toImage()))
|
||||
return QProxyStyle::generatedIconPixmap(mode, pixmap, option);
|
||||
|
||||
const QPalette palette = option ? option->palette : QApplication::palette();
|
||||
return tinted(pixmap, hoverColor(palette));
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
#ifndef QET_STYLE_H
|
||||
#define QET_STYLE_H
|
||||
|
||||
#include <QHash>
|
||||
#include <QIcon>
|
||||
#include <QProxyStyle>
|
||||
|
||||
/**
|
||||
@brief The QETStyle class
|
||||
A proxy over the running widget style that gives icons a hover state.
|
||||
|
||||
QToolButton, QTabBar and item views ask the application style for the
|
||||
QIcon::Active pixmap of an icon while the mouse is over it. Every
|
||||
built-in style returns the icon unchanged for that mode, so only the
|
||||
button frame changes on hover, and on a dark palette Fusion's frame is
|
||||
too faint to see (GitHub #870). This proxy answers QIcon::Active for
|
||||
line-art icons with the icon tinted in the palette's highlight color,
|
||||
lightened when needed to reach 3:1 on a button face, so the icon
|
||||
itself changes on both light and dark palettes. Colored icons keep
|
||||
their colors. Every other mode is left to the base style.
|
||||
|
||||
Fusion also asks for QIcon::Active for the icon of a highlighted menu
|
||||
item and paints it on the highlight bar, where the hover tint would
|
||||
vanish. The generated pixmap is cached per icon and cannot tell a
|
||||
menu from a toolbar, so drawControl(CE_MenuItem) hands the base style
|
||||
an icon whose Active pixmap is the icon in the highlighted-text color.
|
||||
*/
|
||||
class QETStyle : public QProxyStyle
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit QETStyle(QStyle *base);
|
||||
~QETStyle() override = default;
|
||||
|
||||
QPixmap generatedIconPixmap(QIcon::Mode mode,
|
||||
const QPixmap &pixmap,
|
||||
const QStyleOption *option) const override;
|
||||
void drawControl(ControlElement element,
|
||||
const QStyleOption *option,
|
||||
QPainter *painter,
|
||||
const QWidget *widget = nullptr) const override;
|
||||
|
||||
/// True when fewer than a fifth of the visible pixels are saturated.
|
||||
static bool isLineArt(const QImage &image);
|
||||
/// The highlight color, lightened until it reads at 3:1 on a button.
|
||||
static QColor hoverColor(const QPalette &palette);
|
||||
/// pixmap with every pixel set to color, alpha kept.
|
||||
static QPixmap tinted(const QPixmap &pixmap, const QColor &color);
|
||||
|
||||
private:
|
||||
QIcon menuIcon(const QIcon &icon, const QPalette &palette, int size, qreal dpr) const;
|
||||
mutable QHash<QString, QIcon> menu_icons_;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -96,7 +96,9 @@ add_executable(
|
||||
tst_qetpalette.cpp
|
||||
inkcontrast.h
|
||||
${QET_DIR}/sources/qetpalette.cpp
|
||||
${QET_DIR}/sources/qetpalette.h)
|
||||
${QET_DIR}/sources/qetpalette.h
|
||||
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.cpp
|
||||
${QET_DIR}/sources/ElementsCollection/elementpreviewdelegate.h)
|
||||
add_test(NAME tst_qetpalette COMMAND tst_qetpalette)
|
||||
target_include_directories(tst_qetpalette PRIVATE ${QET_DIR}/sources)
|
||||
target_link_libraries(tst_qetpalette PRIVATE Qt::Test Qt::Widgets)
|
||||
@@ -110,6 +112,7 @@ add_executable(
|
||||
${QET_DIR}/sources/qeticons.cpp
|
||||
${QET_DIR}/sources/qeticons.h
|
||||
${QET_DIR}/sources/qetpalette.cpp
|
||||
${QET_DIR}/sources/qetstyle.cpp
|
||||
${QET_DIR}/sources/qetpalette.h
|
||||
${QET_DIR}/ico/icon-themes.qrc)
|
||||
add_test(NAME tst_qeticons COMMAND tst_qeticons)
|
||||
|
||||
@@ -38,9 +38,8 @@ namespace QET {
|
||||
image is also written there as <row>-<name>.png so a failure
|
||||
can be looked at.
|
||||
*/
|
||||
inline QImage grab(QWidget *widget, const char *name)
|
||||
inline QImage dump(const QImage &image, const char *name)
|
||||
{
|
||||
const QImage image = widget->grab().toImage();
|
||||
const QByteArray dir = qgetenv("QET_TEST_DUMP_DIR");
|
||||
if (!dir.isEmpty())
|
||||
image.save(QString("%1/%2-%3.png").arg(QString::fromLocal8Bit(dir),
|
||||
@@ -48,28 +47,55 @@ namespace QET {
|
||||
return image;
|
||||
}
|
||||
|
||||
inline QImage grab(QWidget *widget, const char *name)
|
||||
{
|
||||
return dump(widget->grab().toImage(), name);
|
||||
}
|
||||
|
||||
/**
|
||||
Contrast between the background of a rendered widget (its most
|
||||
frequent color) and the ink drawn on it (the pixel whose
|
||||
luminance differs most from the background), inside rect.
|
||||
*/
|
||||
inline double inkContrast(const QImage &image, const QRect &rect)
|
||||
inline QHash<QRgb, int> histogram(const QImage &image, const QRect &rect)
|
||||
{
|
||||
QHash<QRgb, int> histogram;
|
||||
QHash<QRgb, int> counts;
|
||||
for (int y = rect.top(); y <= rect.bottom(); ++y)
|
||||
for (int x = rect.left(); x <= rect.right(); ++x)
|
||||
++histogram[image.pixel(x, y)];
|
||||
++counts[image.pixel(x, y)];
|
||||
return counts;
|
||||
}
|
||||
|
||||
QRgb background = 0;
|
||||
/// The most frequent color inside rect.
|
||||
inline QRgb background(const QImage &image, const QRect &rect)
|
||||
{
|
||||
const QHash<QRgb, int> counts = histogram(image, rect);
|
||||
QRgb color = 0;
|
||||
int best = -1;
|
||||
for (auto it = histogram.cbegin(); it != histogram.cend(); ++it)
|
||||
if (it.value() > best) { best = it.value(); background = it.key(); }
|
||||
for (auto it = counts.cbegin(); it != counts.cend(); ++it)
|
||||
if (it.value() > best) { best = it.value(); color = it.key(); }
|
||||
return color;
|
||||
}
|
||||
|
||||
/// The color inside rect that contrasts most with the background: the ink.
|
||||
inline QRgb ink(const QImage &image, const QRect &rect)
|
||||
{
|
||||
const QHash<QRgb, int> counts = histogram(image, rect);
|
||||
const QColor bg(background(image, rect));
|
||||
QRgb color = bg.rgb();
|
||||
double contrast = 1.0;
|
||||
for (auto it = histogram.cbegin(); it != histogram.cend(); ++it)
|
||||
contrast = qMax(contrast, QET::Palette::contrastRatio(QColor(background),
|
||||
QColor(it.key())));
|
||||
return contrast;
|
||||
for (auto it = counts.cbegin(); it != counts.cend(); ++it)
|
||||
{
|
||||
const double c = QET::Palette::contrastRatio(bg, QColor(it.key()));
|
||||
if (c > contrast) { contrast = c; color = it.key(); }
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
inline double inkContrast(const QImage &image, const QRect &rect)
|
||||
{
|
||||
return QET::Palette::contrastRatio(QColor(background(image, rect)),
|
||||
QColor(ink(image, rect)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,12 @@
|
||||
#include <QMenuBar>
|
||||
#include <QShortcut>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
// Exported from QtGui but declared in qkeysequence.h only for the
|
||||
// documentation build; Qt's own docs say to declare it like this.
|
||||
Q_GUI_EXPORT void qt_set_sequence_auto_mnemonic(bool b);
|
||||
#endif
|
||||
|
||||
/*
|
||||
F10 should open the menu bar, as it does in most applications and as
|
||||
someone working without a mouse will expect. Qt provides this on Windows
|
||||
@@ -38,6 +44,7 @@ class TstMenuBarKeyboard : public QObject
|
||||
Q_OBJECT
|
||||
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void altLetterOpensMenu(); // control -- must pass, or nothing below means anything
|
||||
void plainF10DoesNothingInQt(); // the gap being filled
|
||||
void shortcutOpensMenuBar(); // the mechanism QETMainWindow uses
|
||||
@@ -48,6 +55,13 @@ namespace {
|
||||
QMainWindow *makeWindow(QMenu **file_menu)
|
||||
{
|
||||
auto *w = new QMainWindow;
|
||||
#ifdef Q_OS_MACOS
|
||||
// A QMenuBar is native on macOS: its menus live in the system menu
|
||||
// bar, outside the Qt widget tree, where QTest key events never reach
|
||||
// them and QMenu::isVisible() stays false. The in-window bar runs the
|
||||
// same QMenuBar code the other platforms use.
|
||||
w->menuBar()->setNativeMenuBar(false);
|
||||
#endif
|
||||
*file_menu = w->menuBar()->addMenu(QStringLiteral("&File"));
|
||||
(*file_menu)->addAction(QStringLiteral("Quit"));
|
||||
w->menuBar()->addMenu(QStringLiteral("&Edit"))->addAction(QStringLiteral("Copy"));
|
||||
@@ -60,6 +74,18 @@ QMainWindow *makeWindow(QMenu **file_menu)
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
macOS has no Alt+letter menu mnemonics, so Qt does not turn "&File"
|
||||
into a shortcut there (qt_set_sequence_auto_mnemonic is off). The
|
||||
control below needs one; switch mnemonics on for this process.
|
||||
*/
|
||||
void TstMenuBarKeyboard::initTestCase()
|
||||
{
|
||||
#ifdef Q_OS_MACOS
|
||||
qt_set_sequence_auto_mnemonic(true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
The control. Alt and a menu's letter is known to work, so if this fails
|
||||
the environment cannot open menus at all and the other two tests are
|
||||
|
||||
@@ -18,15 +18,20 @@
|
||||
#include <QtTest>
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QIcon>
|
||||
#include <QMainWindow>
|
||||
#include <QMenu>
|
||||
#include <QPainter>
|
||||
#include <QStyleFactory>
|
||||
#include <QStyleOption>
|
||||
#include <QToolBar>
|
||||
#include <QToolButton>
|
||||
|
||||
#include "inkcontrast.h"
|
||||
#include "qeticons.h"
|
||||
#include "qetpalette.h"
|
||||
#include "qetstyle.h"
|
||||
|
||||
/**
|
||||
Checks on QET's two icon themes, "qet" and "qet-dark", generated by
|
||||
@@ -34,11 +39,15 @@
|
||||
table that uses them.
|
||||
|
||||
Every icon name must resolve in both themes. The dark theme's files
|
||||
must be light ink. A toolbar button painted with Fusion must show its
|
||||
icon at 3:1 (WCAG 1.4.11) in both themes, with the disabled state
|
||||
reading weaker than the enabled one, which is what was reversed on
|
||||
macOS before (GitHub #466, #870). And the icons the elements panel
|
||||
draws in its 50 px slots must stay small.
|
||||
must read on the dark palette, and an icon drawn as a light object
|
||||
(a page sheet, the PDF import icon of GitHub #919) must come through
|
||||
the dark theme untouched rather than inverted into a black page.
|
||||
QET's own vector icons must serve every size in both themes. A
|
||||
toolbar button painted with Fusion must show its icon at 3:1 (WCAG
|
||||
1.4.11) in both themes, with the disabled state reading weaker than
|
||||
the enabled one, which is what was reversed on macOS before (GitHub
|
||||
#466, #870). And the icons the elements panel draws in its 50 px
|
||||
slots must stay small.
|
||||
*/
|
||||
class tst_qeticons : public QObject
|
||||
{
|
||||
@@ -47,9 +56,16 @@ class tst_qeticons : public QObject
|
||||
private slots:
|
||||
void initTestCase();
|
||||
void everyIconResolvesInBothThemes();
|
||||
void darkThemeFilesAreLightInk();
|
||||
void darkThemeFilesReadOnDarkPalette();
|
||||
void lightIconsStayLightInDarkTheme();
|
||||
void scalableIconsServeEverySize();
|
||||
void toolbarIconIsReadable_data();
|
||||
void toolbarIconIsReadable();
|
||||
void hoverChangesTheIcon_data();
|
||||
void hoverChangesTheIcon();
|
||||
void coloredIconKeepsItsColorsOnHover();
|
||||
void menuIconReadsOnHighlight_data();
|
||||
void menuIconReadsOnHighlight();
|
||||
void panelProjectIconStaysSmall();
|
||||
};
|
||||
|
||||
@@ -70,18 +86,26 @@ namespace {
|
||||
return names;
|
||||
}
|
||||
|
||||
/// Lightness of the lightest visible pixel: the ink, in a dark-theme file.
|
||||
double maxLightness(const QImage &image)
|
||||
/**
|
||||
Mean color of the visible pixels: what the icon reads as, taken
|
||||
as a whole, on the background behind it. A page-shaped icon whose
|
||||
white body was inverted keeps a light border, so the lightest
|
||||
pixel would pass it; the mean does not.
|
||||
*/
|
||||
QColor meanVisibleColor(const QImage &image)
|
||||
{
|
||||
double max = 0.0;
|
||||
qint64 r = 0, g = 0, b = 0, n = 0;
|
||||
for (int y = 0; y < image.height(); ++y)
|
||||
for (int x = 0; x < image.width(); ++x)
|
||||
{
|
||||
const QColor c = image.pixelColor(x, y);
|
||||
if (c.alpha() > 64) max = qMax(max, c.lightnessF());
|
||||
if (c.alpha() <= 64) continue;
|
||||
r += c.red(); g += c.green(); b += c.blue(); ++n;
|
||||
}
|
||||
return max;
|
||||
return n ? QColor(r / n, g / n, b / n) : QColor();
|
||||
}
|
||||
|
||||
const QStringList kSizes = {"16x16", "22x22", "32x32", "48x48", "128x128"};
|
||||
}
|
||||
|
||||
void tst_qeticons::initTestCase()
|
||||
@@ -111,27 +135,109 @@ void tst_qeticons::everyIconResolvesInBothThemes()
|
||||
QVERIFY2(!QIcon::fromTheme(name).pixmap(22).isNull(), qPrintable(name));
|
||||
}
|
||||
|
||||
void tst_qeticons::darkThemeFilesAreLightInk()
|
||||
/**
|
||||
Every file the generator put in the dark theme, taken as a whole,
|
||||
must reach 3:1 against the dark palette's window color. Inverted
|
||||
line art passes by a wide margin; an inverted light object (a white
|
||||
page turned black) sits below 2:1 and fails, which is the defect
|
||||
misc/make_icon_themes.py now avoids by leaving such icons to the
|
||||
light theme.
|
||||
*/
|
||||
void tst_qeticons::darkThemeFilesReadOnDarkPalette()
|
||||
{
|
||||
const QColor window = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Window);
|
||||
int checked = 0;
|
||||
for (const QString &size : {"16x16", "22x22", "32x32"})
|
||||
for (const QString &size : kSizes)
|
||||
{
|
||||
QDir dir(QString(":/ico/themes/qet-dark/%1").arg(size));
|
||||
for (const QString &file : dir.entryList({"*.png"}, QDir::Files))
|
||||
{
|
||||
const QImage dark(dir.filePath(file));
|
||||
const QImage light(QString(":/ico/themes/qet/%1/%2").arg(size, file));
|
||||
QVERIFY2(!dark.isNull() && !light.isNull(), qPrintable(file));
|
||||
// A page-shaped icon keeps its white fill dark after inversion,
|
||||
// so the mean would mislead; what matters is that the ink is light.
|
||||
QVERIFY2(maxLightness(dark) >= 0.8,
|
||||
qPrintable(QString("%1/%2 has no light ink").arg(size, file)));
|
||||
QVERIFY2(!dark.isNull(), qPrintable(file));
|
||||
const QColor mean = meanVisibleColor(dark);
|
||||
QVERIFY2(mean.isValid(), qPrintable(file));
|
||||
const double contrast = QET::Palette::contrastRatio(mean, window);
|
||||
QVERIFY2(contrast >= kIconRatio,
|
||||
qPrintable(QString("%1/%2 reads %3:1 on the dark window").arg(size, file).arg(contrast)));
|
||||
++checked;
|
||||
}
|
||||
}
|
||||
QVERIFY(checked > 100);
|
||||
}
|
||||
|
||||
/**
|
||||
Icons drawn as a light object are not in the dark theme; the theme
|
||||
inherits them from "qet". Asking the dark theme for them must return
|
||||
the light art, not a dark page (GitHub #919, the PDF import icon).
|
||||
*/
|
||||
void tst_qeticons::lightIconsStayLightInDarkTheme()
|
||||
{
|
||||
const QColor window = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Window);
|
||||
QIcon::setThemeName(QStringLiteral("qet-dark"));
|
||||
// The folio family is an SVG from 22 px up; their 16 px files are
|
||||
// still the light page art, as is the background swatch at 22 px.
|
||||
const QList<QPair<QString, int>> icons = {
|
||||
{"diagram", 16}, {"label", 16}, {"folio-new", 16}, {"folio-delete", 16},
|
||||
{"folio-properties", 16}, {"diagram_bg", 22}};
|
||||
for (const auto &[name, size] : icons)
|
||||
{
|
||||
QVERIFY2(!QFile::exists(QString(":/ico/themes/qet-dark/%1x%1/%2.png").arg(size).arg(name)),
|
||||
qPrintable(QString("%1 has a dark copy; the generator inverted a light icon").arg(name)));
|
||||
const QImage image = QIcon::fromTheme(name).pixmap(size).toImage();
|
||||
QVERIFY2(!image.isNull(), qPrintable(name));
|
||||
const QColor mean = meanVisibleColor(image);
|
||||
QVERIFY2(mean.lightnessF() > 0.6,
|
||||
qPrintable(QString("%1 comes out dark in the dark theme (lightness %2)")
|
||||
.arg(name).arg(mean.lightnessF())));
|
||||
QVERIFY2(QET::Palette::contrastRatio(mean, window) >= kIconRatio, qPrintable(name));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
QET's own vector icons live in ico/scalable/: one file for every size
|
||||
from the toolbar up, with a recolored copy in the dark theme. Each
|
||||
must resolve in both themes at 22, 24, 32 and 64 px, dark ink on the
|
||||
light theme and light ink on the dark one, with no 22 px PNG left
|
||||
beside it. Fusion's toolbar slot is 24 px, so the files are drawn on
|
||||
a 24 px canvas and their one pixel lines land on whole pixels there.
|
||||
*/
|
||||
void tst_qeticons::scalableIconsServeEverySize()
|
||||
{
|
||||
const QDir scalable(":/ico/themes/qet/scalable");
|
||||
QStringList names;
|
||||
for (const QString &file : scalable.entryList({"*.svg"}, QDir::Files))
|
||||
names << file.section('.', 0, -2);
|
||||
for (const QString &name : {"pdf-import", "folio-new", "folio-delete", "folio-properties", "diagram", "label"})
|
||||
QVERIFY2(names.contains(name), qPrintable(name + " is not in the scalable folder"));
|
||||
|
||||
const QByteArray dump = qgetenv("QET_TEST_DUMP_DIR");
|
||||
for (const QString &name : names)
|
||||
{
|
||||
QVERIFY2(QFile::exists(QString(":/ico/themes/qet-dark/scalable/%1.svg").arg(name)), qPrintable(name));
|
||||
QVERIFY2(!QFile::exists(QString(":/ico/themes/qet/22x22/%1.png").arg(name)),
|
||||
qPrintable(QString("%1 still has a 22 px PNG that hides the SVG").arg(name)));
|
||||
for (const QString &theme : {"qet", "qet-dark"})
|
||||
{
|
||||
QIcon::setThemeName(theme);
|
||||
const QIcon icon = QIcon::fromTheme(name);
|
||||
QVERIFY2(!icon.isNull(), qPrintable(name));
|
||||
for (int size : {22, 24, 32, 64})
|
||||
{
|
||||
const QPixmap pixmap = icon.pixmap(size);
|
||||
QCOMPARE(pixmap.width(), size);
|
||||
if (!dump.isEmpty())
|
||||
pixmap.save(QString("%1/%2-%3-%4.png").arg(QString::fromLocal8Bit(dump), name, theme).arg(size));
|
||||
const QColor mean = meanVisibleColor(pixmap.toImage());
|
||||
QVERIFY2(mean.isValid(), qPrintable(QString("%1 in %2 at %3 px is empty").arg(name, theme).arg(size)));
|
||||
if (theme == "qet")
|
||||
QVERIFY2(mean.lightnessF() < 0.5, qPrintable(QString("%1 light theme at %2 px: lightness %3").arg(name).arg(size).arg(mean.lightnessF())));
|
||||
else
|
||||
QVERIFY2(mean.lightnessF() > 0.6, qPrintable(QString("%1 dark theme at %2 px: lightness %3").arg(name).arg(size).arg(mean.lightnessF())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void tst_qeticons::toolbarIconIsReadable_data()
|
||||
{
|
||||
QTest::addColumn<QString>("theme");
|
||||
@@ -178,6 +284,169 @@ void tst_qeticons::toolbarIconIsReadable()
|
||||
.arg(disabled_contrast).arg(enabled_contrast)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
/**
|
||||
Paint a tool button through the style with the given extra states,
|
||||
the way QToolButton::paintEvent does, so hover can be rendered
|
||||
without a real pointer.
|
||||
*/
|
||||
QImage renderToolButton(QToolButton *button, QStyle::State extra, const char *name)
|
||||
{
|
||||
QStyleOptionToolButton option;
|
||||
option.initFrom(button);
|
||||
option.state |= QStyle::State_Enabled | QStyle::State_AutoRaise | QStyle::State_Raised | extra;
|
||||
option.icon = button->icon();
|
||||
option.iconSize = button->iconSize();
|
||||
option.subControls = QStyle::SC_ToolButton;
|
||||
option.features = QStyleOptionToolButton::None;
|
||||
option.toolButtonStyle = Qt::ToolButtonIconOnly;
|
||||
|
||||
QImage image(button->size(), QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(option.palette.color(QPalette::Window));
|
||||
QPainter painter(&image);
|
||||
button->style()->drawComplexControl(QStyle::CC_ToolButton, &option, &painter, button);
|
||||
painter.end();
|
||||
return QET::Test::dump(image, name);
|
||||
}
|
||||
|
||||
QToolButton *toolButton(QMainWindow &window, const QIcon &icon)
|
||||
{
|
||||
QToolBar *toolbar = window.addToolBar("view");
|
||||
auto *button = new QToolButton;
|
||||
button->setIcon(icon);
|
||||
button->setAutoRaise(true);
|
||||
toolbar->addWidget(button);
|
||||
window.resize(300, 100);
|
||||
window.show();
|
||||
return button;
|
||||
}
|
||||
}
|
||||
|
||||
void tst_qeticons::hoverChangesTheIcon_data()
|
||||
{
|
||||
QTest::addColumn<QString>("theme");
|
||||
QTest::addColumn<QPalette>("palette");
|
||||
QTest::addColumn<bool>("checked");
|
||||
QTest::newRow("light") << "qet" << QET::Palette::fusionLight() << false;
|
||||
QTest::newRow("dark") << "qet-dark" << QET::Palette::fusionDark() << false;
|
||||
// A toggle that is on: Fusion draws it sunken, and hovering it changes
|
||||
// nothing in the frame, so the icon is the only hover signal there.
|
||||
QTest::newRow("light-checked") << "qet" << QET::Palette::fusionLight() << true;
|
||||
QTest::newRow("dark-checked") << "qet-dark" << QET::Palette::fusionDark() << true;
|
||||
}
|
||||
|
||||
/**
|
||||
Hovering a line-art icon must change the icon itself, not only the
|
||||
button frame, and the hovered icon must still reach 3:1 (GitHub #870).
|
||||
QETStyle answers QIcon::Active with the icon tinted in the highlight
|
||||
color; the built-in styles return it unchanged.
|
||||
*/
|
||||
void tst_qeticons::hoverChangesTheIcon()
|
||||
{
|
||||
QFETCH(QString, theme);
|
||||
QFETCH(QPalette, palette);
|
||||
QFETCH(bool, checked);
|
||||
QIcon::setThemeName(theme);
|
||||
QApplication::setStyle(new QETStyle(QStyleFactory::create("Fusion")));
|
||||
QApplication::setPalette(palette);
|
||||
|
||||
QMainWindow window;
|
||||
QToolButton *button = toolButton(window, QIcon::fromTheme("zoom-fit-best"));
|
||||
button->setCheckable(checked);
|
||||
button->setChecked(checked);
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&window));
|
||||
|
||||
const QStyle::State on = checked ? (QStyle::State_On | QStyle::State_Sunken) : QStyle::State_None;
|
||||
const QImage normal = renderToolButton(button, on, "normal");
|
||||
const QImage hover = renderToolButton(button, on | QStyle::State_MouseOver, "hover");
|
||||
// Inside the frame Fusion draws around a hovered button, so the ink
|
||||
// measured is the icon's, not the frame's.
|
||||
const QRect inside = button->rect().adjusted(3, 3, -3, -3);
|
||||
const QColor normal_ink(QET::Test::ink(normal, inside));
|
||||
const QColor hover_ink(QET::Test::ink(hover, inside));
|
||||
QVERIFY2(hover_ink != normal_ink,
|
||||
qPrintable(QString("hover leaves the icon ink at %1").arg(normal_ink.name())));
|
||||
|
||||
// The tint, give or take the icon's own anti-aliasing.
|
||||
const QColor expected = QETStyle::hoverColor(palette);
|
||||
const int distance = qMax(qMax(qAbs(hover_ink.red() - expected.red()),
|
||||
qAbs(hover_ink.green() - expected.green())),
|
||||
qAbs(hover_ink.blue() - expected.blue()));
|
||||
QVERIFY2(distance <= 16,
|
||||
qPrintable(QString("hovered ink %1, expected %2").arg(hover_ink.name(), expected.name())));
|
||||
|
||||
const double hover_contrast = QET::Test::inkContrast(hover, inside);
|
||||
QVERIFY2(hover_contrast >= kIconRatio,
|
||||
qPrintable(QString("hovered icon: %1").arg(hover_contrast)));
|
||||
}
|
||||
|
||||
/**
|
||||
A colored icon (old Oxygen art) is not line art, so hover leaves its
|
||||
pixels alone.
|
||||
*/
|
||||
void tst_qeticons::coloredIconKeepsItsColorsOnHover()
|
||||
{
|
||||
QIcon::setThemeName(QStringLiteral("qet"));
|
||||
QApplication::setStyle(new QETStyle(QStyleFactory::create("Fusion")));
|
||||
QApplication::setPalette(QET::Palette::fusionLight());
|
||||
|
||||
const QIcon icon = QIcon::fromTheme("document-open"); // colored Oxygen art
|
||||
QVERIFY(!icon.isNull());
|
||||
const QPixmap normal = icon.pixmap(QSize(22, 22), 1.0, QIcon::Normal);
|
||||
const QPixmap active = icon.pixmap(QSize(22, 22), 1.0, QIcon::Active);
|
||||
QVERIFY(!QETStyle::isLineArt(normal.toImage()));
|
||||
QCOMPARE(active.toImage(), normal.toImage());
|
||||
}
|
||||
|
||||
void tst_qeticons::menuIconReadsOnHighlight_data()
|
||||
{
|
||||
toolbarIconIsReadable_data();
|
||||
}
|
||||
|
||||
/**
|
||||
Fusion asks for the QIcon::Active pixmap of a highlighted menu item's
|
||||
icon too, and paints it on the highlight bar. The hover tint would
|
||||
vanish there, so on a menu item the icon must read against the bar at
|
||||
3:1 instead: the same ink as the item's text.
|
||||
*/
|
||||
void tst_qeticons::menuIconReadsOnHighlight()
|
||||
{
|
||||
QFETCH(QString, theme);
|
||||
QFETCH(QPalette, palette);
|
||||
QIcon::setThemeName(theme);
|
||||
QApplication::setStyle(new QETStyle(QStyleFactory::create("Fusion")));
|
||||
QApplication::setPalette(palette);
|
||||
|
||||
QMenu menu;
|
||||
QAction *action = menu.addAction(QIcon::fromTheme("zoom-fit-best"), "Zoom to fit");
|
||||
menu.resize(160, 32);
|
||||
|
||||
QStyleOptionMenuItem option;
|
||||
option.initFrom(&menu);
|
||||
option.state |= QStyle::State_Enabled | QStyle::State_Selected;
|
||||
option.menuItemType = QStyleOptionMenuItem::Normal;
|
||||
option.checkType = QStyleOptionMenuItem::NotCheckable;
|
||||
option.icon = action->icon();
|
||||
option.text = action->text();
|
||||
option.maxIconWidth = 22;
|
||||
option.rect = QRect(0, 0, 160, 24);
|
||||
|
||||
QImage image(160, 24, QImage::Format_ARGB32_Premultiplied);
|
||||
image.fill(palette.color(QPalette::Window));
|
||||
QPainter painter(&image);
|
||||
menu.style()->drawControl(QStyle::CE_MenuItem, &option, &painter, &menu);
|
||||
painter.end();
|
||||
QET::Test::dump(image, "menu-highlight");
|
||||
|
||||
// The icon column: the first maxIconWidth pixels plus Fusion's margin.
|
||||
const QRect icon_column(0, 0, 30, 24);
|
||||
const QColor bar(QET::Test::background(image, icon_column));
|
||||
QCOMPARE(bar, palette.color(QPalette::Active, QPalette::Highlight));
|
||||
const double contrast = QET::Test::inkContrast(image, icon_column);
|
||||
QVERIFY2(contrast >= kIconRatio,
|
||||
qPrintable(QString("highlighted menu icon: %1").arg(contrast)));
|
||||
}
|
||||
|
||||
/**
|
||||
The elements panel sets a 50 px icon size so element previews are
|
||||
large. Its project root item uses ProjectFileGP, whose theme name also
|
||||
|
||||
@@ -24,11 +24,15 @@
|
||||
#include <QLineEdit>
|
||||
#include <QMainWindow>
|
||||
#include <QPushButton>
|
||||
#include <QPainter>
|
||||
#include <QRadioButton>
|
||||
#include <QStandardItemModel>
|
||||
#include <QTreeView>
|
||||
#include <QStyleFactory>
|
||||
#include <QToolBar>
|
||||
|
||||
#include "inkcontrast.h"
|
||||
#include "ElementsCollection/elementpreviewdelegate.h"
|
||||
#include "qetpalette.h"
|
||||
|
||||
using QET::Palette::contrastRatio;
|
||||
@@ -62,6 +66,10 @@ class tst_qetpalette : public QObject
|
||||
void styleIsFusionMatchesObjectName();
|
||||
void renderedWidgetsAreReadable_data();
|
||||
void renderedWidgetsAreReadable();
|
||||
void lineArtRuleSeparatesInkFromColor();
|
||||
void invertedLightnessKeepsHueAndAlpha();
|
||||
void elementPreviewReadsOnBothPalettes();
|
||||
void previewDelegateAdaptsLineArtOnly();
|
||||
|
||||
private:
|
||||
static void addPaletteRows();
|
||||
@@ -308,6 +316,127 @@ void tst_qetpalette::renderedWidgetsAreReadable()
|
||||
qPrintable(QString("line edit text: %1").arg(edit_contrast)));
|
||||
}
|
||||
|
||||
namespace {
|
||||
/// A 40 x 40 transparent picture with a 3 px stroke square in color, as
|
||||
/// an element preview is drawn for the white sheet.
|
||||
QImage strokeSquare(const QColor &color)
|
||||
{
|
||||
QImage image(40, 40, QImage::Format_ARGB32);
|
||||
image.fill(Qt::transparent);
|
||||
QPainter painter(&image);
|
||||
painter.setPen(QPen(color, 3));
|
||||
painter.drawRect(6, 6, 27, 27);
|
||||
return image;
|
||||
}
|
||||
|
||||
/// The most frequent color of a rendering: its background.
|
||||
QRgb dominant(const QImage &image)
|
||||
{
|
||||
QHash<QRgb, int> histogram;
|
||||
for (int y = 0; y < image.height(); ++y)
|
||||
for (int x = 0; x < image.width(); ++x)
|
||||
++histogram[image.pixel(x, y)];
|
||||
QRgb best = 0;
|
||||
int count = -1;
|
||||
for (auto it = histogram.cbegin(); it != histogram.cend(); ++it)
|
||||
if (it.value() > count) { count = it.value(); best = it.key(); }
|
||||
return best;
|
||||
}
|
||||
}
|
||||
|
||||
void tst_qetpalette::lineArtRuleSeparatesInkFromColor()
|
||||
{
|
||||
QVERIFY(QET::Palette::isLineArt(strokeSquare(Qt::black)));
|
||||
QVERIFY(QET::Palette::isLineArt(strokeSquare(QColor(80, 80, 80))));
|
||||
QVERIFY(!QET::Palette::isLineArt(strokeSquare(Qt::red)));
|
||||
QVERIFY(!QET::Palette::isLineArt(strokeSquare(QColor(30, 96, 176))));
|
||||
QVERIFY(!QET::Palette::isLineArt(QImage()));
|
||||
}
|
||||
|
||||
/**
|
||||
Black ink becomes the dark palette's light gray, a colored stroke keeps
|
||||
its hue, and transparency is untouched.
|
||||
*/
|
||||
void tst_qetpalette::invertedLightnessKeepsHueAndAlpha()
|
||||
{
|
||||
const QImage black = QET::Palette::invertedLightness(strokeSquare(Qt::black));
|
||||
QCOMPARE(black.pixelColor(6, 20).alpha(), 255);
|
||||
QVERIFY2(black.pixelColor(6, 20).lightnessF() > 0.8, "black ink did not become light");
|
||||
QCOMPARE(black.pixelColor(20, 20).alpha(), 0);
|
||||
|
||||
const QImage red = QET::Palette::invertedLightness(strokeSquare(Qt::red));
|
||||
const QColor stroke = red.pixelColor(6, 20);
|
||||
QVERIFY2(qAbs(stroke.hslHueF() - QColor(Qt::red).hslHueF()) < 0.02, "hue changed");
|
||||
QVERIFY(stroke.hslSaturationF() > 0.9);
|
||||
}
|
||||
|
||||
/**
|
||||
An element preview drawn for the white sheet must read at 3:1 on the
|
||||
Base color of both palettes: unchanged on the light one, inverted on
|
||||
the dark one.
|
||||
*/
|
||||
void tst_qetpalette::elementPreviewReadsOnBothPalettes()
|
||||
{
|
||||
const QPixmap preview = QPixmap::fromImage(strokeSquare(Qt::black));
|
||||
for (const QPalette &palette : {QET::Palette::fusionLight(), QET::Palette::fusionDark()})
|
||||
{
|
||||
const QColor base = palette.color(QPalette::Active, QPalette::Base);
|
||||
const QPixmap shown = QET::Palette::forPalette(preview, palette);
|
||||
QImage row(shown.size(), QImage::Format_ARGB32);
|
||||
row.fill(base);
|
||||
QPainter painter(&row);
|
||||
painter.drawPixmap(0, 0, shown);
|
||||
painter.end();
|
||||
const double contrast = QET::Test::inkContrast(row, row.rect());
|
||||
QVERIFY2(contrast >= 3.0, qPrintable(QString("preview reads %1:1 on Base %2").arg(contrast).arg(base.name())));
|
||||
}
|
||||
// A light palette hands the picture back untouched.
|
||||
QCOMPARE(QET::Palette::forPalette(preview, QET::Palette::fusionLight()).cacheKey(), preview.cacheKey());
|
||||
}
|
||||
|
||||
/**
|
||||
In a tree on the dark palette, the delegate inverts a line-art icon so
|
||||
it reads on the row, and leaves a colored icon (a folder) as it is.
|
||||
*/
|
||||
void tst_qetpalette::previewDelegateAdaptsLineArtOnly()
|
||||
{
|
||||
QApplication::setStyle(QStyleFactory::create("Fusion"));
|
||||
QApplication::setPalette(QET::Palette::fusionDark());
|
||||
|
||||
QStandardItemModel model;
|
||||
auto *element = new QStandardItem(QIcon(QPixmap::fromImage(strokeSquare(Qt::black))), "element");
|
||||
auto *folder = new QStandardItem(QIcon(QPixmap::fromImage(strokeSquare(QColor(30, 96, 176)))), "folder");
|
||||
model.appendRow(element);
|
||||
model.appendRow(folder);
|
||||
|
||||
QTreeView view;
|
||||
view.setModel(&model);
|
||||
view.setIconSize(QSize(40, 40));
|
||||
view.setItemDelegate(new ElementPreviewDelegate(&view));
|
||||
view.resize(300, 200);
|
||||
view.show();
|
||||
QVERIFY(QTest::qWaitForWindowExposed(&view));
|
||||
|
||||
const QImage image = view.viewport()->grab().toImage();
|
||||
const QRect element_icon(view.visualRect(element->index()).topLeft(), QSize(40, 40));
|
||||
const QRect folder_icon(view.visualRect(folder->index()).topLeft(), QSize(40, 40));
|
||||
const QColor base = QET::Palette::fusionDark().color(QPalette::Active, QPalette::Base);
|
||||
QCOMPARE(QColor(dominant(image)), base);
|
||||
|
||||
const double element_contrast = QET::Test::inkContrast(image, element_icon);
|
||||
QVERIFY2(element_contrast >= 3.0, qPrintable(QString("element preview reads %1:1 on the dark row").arg(element_contrast)));
|
||||
|
||||
// The folder icon keeps its blue: some pixel in its slot is still saturated blue.
|
||||
bool blue = false;
|
||||
for (int y = folder_icon.top(); y <= folder_icon.bottom() && !blue; ++y)
|
||||
for (int x = folder_icon.left(); x <= folder_icon.right() && !blue; ++x)
|
||||
{
|
||||
const QColor c = image.pixelColor(x, y);
|
||||
blue = c.hslSaturationF() > 0.5 && c.blue() > c.red() + 60;
|
||||
}
|
||||
QVERIFY2(blue, "the colored icon lost its color");
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
// Widgets are painted for real, on Qt's offscreen platform so the test
|
||||
|
||||