1+ #include " ImagePickerDialog.h"
2+
3+ #include " ui/util/ImageRenderer.h"
4+
5+ #include < QBoxLayout>
6+ #include < QImage>
7+ #include < QStyle>
8+ #include < QPainter>
9+
10+ using fso::fred::util::loadImageToQImage;
11+
12+ ImagePickerDialog::ImagePickerDialog (QWidget* parent) : QDialog(parent)
13+ {
14+ setWindowTitle (" Choose Image" );
15+ resize (720 , 520 );
16+
17+ auto * vbox = new QVBoxLayout (this );
18+
19+ _filterEdit = new QLineEdit (this );
20+ _filterEdit->setPlaceholderText (" Filter by name..." );
21+ vbox->addWidget (_filterEdit);
22+
23+ _list = new QListWidget (this );
24+ _list->setViewMode (QListView::IconMode);
25+ _list->setIconSize (QSize (112 , 112 ));
26+ _list->setResizeMode (QListView::Adjust);
27+ _list->setUniformItemSizes (true );
28+ _list->setMovement (QListView::Static);
29+ _list->setSelectionMode (QAbstractItemView::SingleSelection);
30+ _list->setSpacing (8 );
31+ vbox->addWidget (_list, 1 );
32+
33+ auto * hbox = new QHBoxLayout ();
34+ hbox->addStretch (1 );
35+ _okBtn = new QPushButton (" OK" , this );
36+ _cancelBtn = new QPushButton (" Cancel" , this );
37+ hbox->addWidget (_okBtn);
38+ hbox->addWidget (_cancelBtn);
39+ vbox->addLayout (hbox);
40+
41+ connect (_filterEdit, &QLineEdit::textChanged, this , &ImagePickerDialog::onFilterTextChanged);
42+ connect (_list, &QListWidget::itemActivated, this , &ImagePickerDialog::onItemActivated);
43+ connect (_okBtn, &QPushButton::clicked, this , &ImagePickerDialog::onOk);
44+ connect (_cancelBtn, &QPushButton::clicked, this , &QDialog::reject);
45+ }
46+
47+ void ImagePickerDialog::setImageFilenames (const QStringList& filenames)
48+ {
49+ _allFiles = filenames;
50+ rebuildList ();
51+ }
52+
53+ void ImagePickerDialog::setInitialSelection (const QString& filename)
54+ {
55+ _selected = filename;
56+ // Apply after list is built
57+ for (int i = 0 ; i < _list->count (); ++i) {
58+ auto * it = _list->item (i);
59+ if (it->data (Qt::UserRole).toString () == filename) {
60+ _list->setCurrentItem (it);
61+ _list->scrollToItem (it, QAbstractItemView::PositionAtCenter);
62+ break ;
63+ }
64+ }
65+ }
66+
67+ void ImagePickerDialog::onFilterTextChanged (const QString& text)
68+ {
69+ _filterText = text;
70+ rebuildList ();
71+ }
72+
73+ void ImagePickerDialog::onItemActivated (QListWidgetItem* item)
74+ {
75+ if (!item)
76+ return ;
77+ _selected = item->data (Qt::UserRole).toString ();
78+ accept ();
79+ }
80+
81+ void ImagePickerDialog::onOk ()
82+ {
83+ auto * item = _list->currentItem ();
84+ _selected = item ? item->data (Qt::UserRole).toString () : QString ();
85+ accept ();
86+ }
87+
88+ QIcon ImagePickerDialog::iconFor (const QString& name)
89+ {
90+ if (_thumbCache.contains (name)) {
91+ return {_thumbCache.value (name)};
92+ }
93+
94+ // Decode via bmpman using ImageRenderer
95+ QImage img;
96+ QString err;
97+ if (loadImageToQImage (name.toStdString (), img, &err) && !img.isNull ()) {
98+ // Scale to icon size for snappy scrolling
99+ QPixmap pm = QPixmap::fromImage (img.scaled (_list->iconSize (), Qt::KeepAspectRatio, Qt::SmoothTransformation));
100+ _thumbCache.insert (name, pm);
101+ return {pm};
102+ }
103+
104+ // Fallback file icon
105+ return style ()->standardIcon (QStyle::SP_FileIcon);
106+ }
107+
108+ static QIcon makeEmptySlotIcon (const QSize& size)
109+ {
110+ QPixmap pm (size);
111+ pm.fill (Qt::transparent);
112+
113+ QPainter p (&pm);
114+ p.setRenderHint (QPainter::Antialiasing);
115+
116+ // Border
117+ QPen pen (QColor (180 , 180 , 180 ));
118+ pen.setWidth (2 );
119+ p.setPen (pen);
120+ p.setBrush (Qt::NoBrush);
121+ p.drawRect (pm.rect ().adjusted (1 , 1 , -2 , -2 ));
122+
123+ // Subtle X
124+ QPen xPen (QColor (180 , 180 , 180 , 180 )); // slightly transparent gray
125+ xPen.setWidth (2 );
126+ p.setPen (xPen);
127+
128+ const int pad = 6 ; // padding inside the square so X isn't edge-to-edge
129+ QPoint topLeft (pad, pad);
130+ QPoint bottomRight (size.width () - pad, size.height () - pad);
131+ QPoint topRight (bottomRight.x (), topLeft.y ());
132+ QPoint bottomLeft (topLeft.x (), bottomRight.y ());
133+
134+ p.drawLine (topLeft, bottomRight);
135+ p.drawLine (topRight, bottomLeft);
136+
137+ p.end ();
138+
139+ return {pm};
140+ }
141+
142+
143+ void ImagePickerDialog::rebuildList ()
144+ {
145+ _list->clear ();
146+
147+ // Add unset option first, if enabled
148+ if (_allowUnset) {
149+ auto unsetIcon = makeEmptySlotIcon (_list->iconSize ());
150+ auto * unsetItem = new QListWidgetItem (unsetIcon, " <None>" );
151+ unsetItem->setData (Qt::UserRole, QString ()); // empty filename
152+ unsetItem->setToolTip (" Remove image / no image selected" );
153+ _list->addItem (unsetItem);
154+
155+ if (_selected.isEmpty ()) {
156+ _list->setCurrentItem (unsetItem);
157+ }
158+ }
159+
160+ const auto fl = _filterText.trimmed ().toLower ();
161+ for (const auto & name : _allFiles) {
162+ if (!fl.isEmpty () && !name.toLower ().contains (fl))
163+ continue ;
164+
165+ auto icon = iconFor (name);
166+ auto * it = new QListWidgetItem (icon, name);
167+ it->setData (Qt::UserRole, name);
168+ it->setToolTip (name);
169+ _list->addItem (it);
170+
171+ if (!_selected.isEmpty () && name == _selected) {
172+ _list->setCurrentItem (it);
173+ }
174+ }
175+ }
0 commit comments