Skip to content

Commit 5799857

Browse files
committed
Add Part Color Swatch Enhancement
1 parent 91aaba5 commit 5799857

6 files changed

Lines changed: 86 additions & 7 deletions

File tree

CMakeLists.txt

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.16)
22

33
project(BrickSuite
4-
VERSION 0.3.0
4+
VERSION 0.4.0
55
LANGUAGES CXX
66
)
77

@@ -731,6 +731,12 @@ if(BUILD_TESTING)
731731
)
732732
target_link_libraries(PartRelationshipImportTest PRIVATE Qt6::Core Qt6::Sql ZLIB::ZLIB)
733733
add_test(NAME PartRelationshipImport COMMAND PartRelationshipImportTest)
734+
qt_add_executable(ColorComboHelperTest EXCLUDE_FROM_ALL
735+
tests/ColorComboHelperTest.cpp
736+
src/ui/helpers/ColorComboHelper.h src/ui/helpers/ColorComboHelper.cpp
737+
)
738+
target_link_libraries(ColorComboHelperTest PRIVATE Qt6::Widgets)
739+
add_test(NAME ColorComboHelper COMMAND ColorComboHelperTest -platform offscreen)
734740
endif()
735741

736742
if(WIN32)

resources/help/inventory.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
<h1>My Inventory</h1><p>My Inventory tracks loose physical pieces. A row identifies an exact Part and Color together with quantity, Storage, Manufacturer, Condition, and Ownership.</p><img src="images/inventory_overview.png" alt="My Inventory">
33
<h2>Search and filter</h2><p>Enter a Part number or name in <b>Search</b>, optionally choose Category, Color, Storage, or Manufacturer, then choose <b>Search</b>. Filters combine. Choose the corresponding <b>All...</b> value to remove a filter.</p><p>If Add Part is opened while a specific Storage filter is active, that visible filter becomes the initial destination. Otherwise BrickSuite may reuse the most recent successful Add/import destination for this Workspace during the current application session. Closing BrickSuite clears this session memory.</p>
44
<h2>Add a loose part</h2><ol><li>Choose <b>Add Part...</b>. The dialog is non-modal, so it may remain open while you consult Parts Catalog or Part Reference.</li><li>Enter a Part number, or send a selected part from Parts Catalog or <b>Tools → Part Reference...</b>.</li><li>Review the resolved Part and Category. For a BrickLink identifier, enable <b>Try BrickLink ID</b> to resolve through known cross-references.</li><li>Select Color, an active Inventory-capable leaf Storage location, Manufacturer, Condition, Ownership, and Quantity.</li><li>Add the entry and verify the resulting row.</li></ol><img src="images/inventory_add.png" alt="Add Part dialog"><p><b>Show all colors</b> includes colors beyond those currently associated with the resolved Part.</p><p><b>Remember Part</b> retains the resolved Part for the next entry. <b>Keep Open</b> controls whether the dialog stays open after a successful addition. The options are independent: one reuses identity and the other reuses the window.</p><div class="note"><b>If resolution fails:</b> verify Parts Catalog was imported, try the exact Rebrickable number, or use Try BrickLink ID for a BrickLink number. Part Reference cannot add a part absent from the local catalog.</div>
5+
<p>Add Part's Color list shows a square swatch of the stored catalog color beside its name. Swatches stay the same in Light and Dark themes. Colors with missing or invalid hex data remain selectable without a swatch.</p>
56
<h2>Parts Catalog or Part Reference?</h2><p>Use <a href="parts_catalog.html"><b>Parts Catalog</b></a> for complete database search, categories, colors, external identities, relationships, and Details. Use <a href="part_reference.html"><b>Part Reference</b></a> for visual family browsing and supported dimension grids. Both can send an exact local identity to Add Part.</p>
67
<h2>Actions for an existing row</h2><img src="images/inventory_actions.png" alt="Inventory Actions menu"><ul><li><b>Details</b> shows current inventory and Part information.</li><li><b>Edit</b> changes supported descriptive attributes.</li><li><b>Move</b> transfers a chosen quantity to another active Inventory-capable leaf and records both locations.</li><li><b>Correct Entry...</b> adjusts a wrong stored count. Review the intended correction; do not use Move for a count correction.</li><li><b>Remove Entry...</b> deliberately removes quantity and records the operation. It is not the same as Mark Lost.</li><li><b>Mark Lost...</b> records physically missing pieces that may later be returned through Lost / Found.</li><li><b>View History</b> shows additions, corrections, removals, moves, imports, Build activity, and Lost/Found events.</li></ul><img src="images/inventory_move.png" alt="Move Inventory"><img src="images/inventory_history.png" alt="Inventory History">
78
<h2>Import and receive inventory</h2><p><b>Import CSV</b> supports previewed Rebrickable Append, Replace, Subtract, and Compare Only. Select the operation and physical attributes, review projected quantities and errors, and only then apply. Compare Only never changes BrickSuite.</p><p>Supported BrickOwl order receiving also produces a preview with identity and color status. Committing valid rows is transactional and records history; cancelling makes no changes.</p>

src/ui/helpers/ColorComboHelper.cpp

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
#include <QColor>
2424
#include <QComboBox>
2525
#include <QPalette>
26+
#include <QIcon>
27+
#include <QPixmap>
2628

2729
#include <algorithm>
2830
#include <cmath>
@@ -31,7 +33,8 @@ int ColorComboHelper::addColorItem(
3133
QComboBox* comboBox,
3234
const QString& name,
3335
int colorId,
34-
const QString& rgbHex)
36+
const QString& rgbHex,
37+
bool showCatalogSwatch)
3538
{
3639
if (!comboBox)
3740
return -1;
@@ -54,6 +57,24 @@ int ColorComboHelper::addColorItem(
5457
if (!sourceColor.isValid())
5558
return index;
5659

60+
if (showCatalogSwatch) {
61+
QPixmap swatch(16, 16);
62+
swatch.fill(sourceColor);
63+
QIcon icon;
64+
// Catalog color is data: never let Qt synthesize a tinted selected
65+
// or disabled variant from the current UI palette.
66+
for (const auto mode : {QIcon::Normal, QIcon::Disabled, QIcon::Active, QIcon::Selected}) {
67+
icon.addPixmap(swatch, mode, QIcon::Off);
68+
icon.addPixmap(swatch, mode, QIcon::On);
69+
}
70+
comboBox->setItemIcon(index, icon);
71+
72+
// The swatch now carries the catalog color. Leave the name on the
73+
// combo's palette-driven foreground so normal and selected text use
74+
// BrickSuite's theme-aware Text and HighlightedText colors.
75+
return index;
76+
}
77+
5778
const QColor backgroundColor =
5879
comboBox->palette().color(
5980
QPalette::Base);
@@ -186,4 +207,4 @@ double ColorComboHelper::contrastRatio(
186207
return
187208
(lighter + 0.05) /
188209
(darker + 0.05);
189-
}
210+
}

src/ui/helpers/ColorComboHelper.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class ColorComboHelper
3232
QComboBox* comboBox,
3333
const QString& name,
3434
int colorId,
35-
const QString& rgbHex);
35+
const QString& rgbHex,
36+
bool showCatalogSwatch = false);
3637

3738
static QColor readableColor(
3839
const QColor& sourceColor,
@@ -45,4 +46,4 @@ class ColorComboHelper
4546
static double contrastRatio(
4647
const QColor& foreground,
4748
const QColor& background);
48-
};
49+
};

src/ui/inventory/AddInventoryDialog.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -687,7 +687,7 @@ void AddInventoryDialog::loadAllColors()
687687
const QList<Color> colors = repository.getAll();
688688

689689
for (const Color& color : colors) {
690-
ColorComboHelper::addColorItem(m_colorCombo, color.name(), color.id(), color.rgb());
690+
ColorComboHelper::addColorItem(m_colorCombo, color.name(), color.id(), color.rgb(), true);
691691
}
692692

693693
m_colorCombo->setEnabled(true);
@@ -713,7 +713,7 @@ void AddInventoryDialog::applyKnownColors(int preferredColorId)
713713
// The internal BrickSuite Color ID remains the
714714
// combo item's user data.
715715
//
716-
ColorComboHelper::addColorItem(m_colorCombo, color.name(), color.id(), color.rgb());
716+
ColorComboHelper::addColorItem(m_colorCombo, color.name(), color.id(), color.rgb(), true);
717717
}
718718

719719
m_colorCombo->setEnabled(true);

tests/ColorComboHelperTest.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#include "../src/ui/helpers/ColorComboHelper.h"
2+
3+
#include <QApplication>
4+
#include <QComboBox>
5+
#include <QDebug>
6+
#include <QImage>
7+
#include <QPalette>
8+
9+
int main(int argc, char** argv)
10+
{
11+
QApplication app(argc, argv);
12+
QComboBox combo;
13+
for (const QColor background : {QColor(Qt::white), QColor(Qt::black)}) {
14+
QPalette palette = combo.palette();
15+
palette.setColor(QPalette::Base, background);
16+
combo.setPalette(palette);
17+
combo.clear();
18+
for (const QString hex : {QString("FFFFFF"), QString("#000000"), QString(" 12aBcD ")}) {
19+
const int index = ColorComboHelper::addColorItem(&combo, hex, 42, hex, true);
20+
QString normalized = hex.trimmed();
21+
if (!normalized.startsWith('#')) normalized.prepend('#');
22+
const QColor expected(normalized);
23+
if (combo.itemData(index).toInt() != 42 || combo.itemText(index) != hex) return 1;
24+
if (combo.itemData(index, Qt::ForegroundRole).isValid()) return 6;
25+
for (const auto mode : {QIcon::Normal, QIcon::Disabled, QIcon::Active, QIcon::Selected}) {
26+
const QImage image = combo.itemIcon(index).pixmap(16, 16, mode).toImage();
27+
if (image.isNull() || image.pixelColor(8, 8) != expected) return 2;
28+
}
29+
}
30+
for (const QString hex : {QString(), QString("not-hex"), QString("#GGGGGG")}) {
31+
const int index = ColorComboHelper::addColorItem(&combo, "Unknown", 99, hex, true);
32+
combo.setCurrentIndex(index);
33+
if (!combo.itemIcon(index).isNull() || combo.currentData().toInt() != 99
34+
|| combo.currentText() != "Unknown") return 3;
35+
}
36+
const int plain = ColorComboHelper::addColorItem(&combo, "Other dialog", 7, "FF0000");
37+
if (!combo.itemIcon(plain).isNull() || combo.itemData(plain).toInt() != 7) return 4;
38+
// A palette change must not recolor an already populated swatch.
39+
palette.setColor(QPalette::Base, background == QColor(Qt::white) ? Qt::black : Qt::white);
40+
palette.setColor(QPalette::Text, background == QColor(Qt::white) ? Qt::white : Qt::black);
41+
palette.setColor(QPalette::HighlightedText, background == QColor(Qt::white) ? Qt::black : Qt::white);
42+
combo.setPalette(palette);
43+
if (combo.itemIcon(0).pixmap(16, 16).toImage().pixelColor(8, 8) != QColor(Qt::white)) return 5;
44+
if (combo.itemData(0, Qt::ForegroundRole).isValid()
45+
|| combo.palette().color(QPalette::Text) != palette.color(QPalette::Text)
46+
|| combo.palette().color(QPalette::HighlightedText) != palette.color(QPalette::HighlightedText)) return 7;
47+
}
48+
qInfo() << "Color combo swatch validation passed.";
49+
return 0;
50+
}

0 commit comments

Comments
 (0)