Skip to content

Commit ebe2d23

Browse files
Merge pull request #6931 from MjnMixael/qtfred_utils
Merge Recent QtFRED Generalized Dialogs
2 parents 966ac4d + 4d8dbcb commit ebe2d23

8 files changed

Lines changed: 529 additions & 0 deletions

File tree

qtfred/source_groups.cmake

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,16 @@ add_file_folder("Source/UI/Dialogs/ShipEditor"
161161
src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.h
162162
src/ui/dialogs/ShipEditor/ShipCustomWarpDialog.cpp
163163
)
164+
add_file_folder("Source/UI/General"
165+
src/ui/dialogs/General/CheckBoxListDialog.cpp
166+
src/ui/dialogs/General/CheckBoxListDialog.h
167+
src/ui/dialogs/General/ImagePickerDialog.cpp
168+
src/ui/dialogs/General/ImagePickerDialog.h
169+
)
164170

165171
add_file_folder("Source/UI/Util"
172+
src/ui/util/ImageRenderer.cpp
173+
src/ui/util/ImageRenderer.h
166174
src/ui/util/menu.cpp
167175
src/ui/util/menu.h
168176
src/ui/util/SignalBlockers.cpp
@@ -186,6 +194,7 @@ add_file_folder("UI"
186194
ui/BackgroundEditor.ui
187195
ui/BriefingEditorDialog.ui
188196
ui/CampaignEditorDialog.ui
197+
ui/CheckBoxListDialog.ui
189198
ui/CommandBriefingDialog.ui
190199
ui/CustomWingNamesDialog.ui
191200
ui/EventEditorDialog.ui
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#include "ui/dialogs/General/CheckBoxListDialog.h"
2+
3+
#include "ui_CheckBoxListDialog.h"
4+
5+
#include <QCheckBox>
6+
#include <QScrollArea>
7+
#include <QVBoxLayout>
8+
9+
namespace fso::fred::dialogs {
10+
11+
CheckBoxListDialog::CheckBoxListDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CheckBoxListDialog)
12+
{
13+
ui->setupUi(this);
14+
15+
// Allow resizing
16+
this->setSizeGripEnabled(true);
17+
18+
// clear placeholder layout contents if any
19+
if (ui->checkboxContainer->layout()) {
20+
QLayoutItem* item;
21+
while ((item = ui->checkboxContainer->layout()->takeAt(0)) != nullptr) {
22+
delete item->widget();
23+
delete item;
24+
}
25+
delete ui->checkboxContainer->layout();
26+
}
27+
28+
// Set a fresh layout
29+
auto* layout = new QVBoxLayout(ui->checkboxContainer);
30+
layout->setContentsMargins(0, 0, 0, 0);
31+
layout->setSpacing(4);
32+
}
33+
34+
void CheckBoxListDialog::setCaption(const QString& text)
35+
{
36+
this->setWindowTitle(text);
37+
}
38+
39+
void CheckBoxListDialog::setOptions(const QVector<std::pair<QString, bool>>& options)
40+
{
41+
// Clear previous checkboxes
42+
for (auto* cb : _checkboxes) {
43+
cb->deleteLater();
44+
}
45+
_checkboxes.clear();
46+
47+
auto* layout = qobject_cast<QVBoxLayout*>(ui->checkboxContainer->layout());
48+
if (!layout) {
49+
return;
50+
}
51+
52+
for (const auto& [label, checked] : options) {
53+
auto* cb = new QCheckBox(label, this);
54+
cb->setChecked(checked);
55+
layout->addWidget(cb);
56+
_checkboxes.append(cb);
57+
}
58+
// Add spacer to push items to top
59+
layout->addStretch();
60+
}
61+
62+
QVector<bool> CheckBoxListDialog::getCheckedStates() const
63+
{
64+
QVector<bool> states;
65+
for (auto* cb : _checkboxes) {
66+
states.append(cb->isChecked());
67+
}
68+
return states;
69+
}
70+
71+
} // namespace fso::fred::dialogs
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#pragma once
2+
3+
4+
#include <QDialog>
5+
#include <QCheckBox>
6+
7+
namespace fso::fred::dialogs {
8+
9+
namespace Ui {
10+
class CheckBoxListDialog;
11+
}
12+
13+
class CheckBoxListDialog : public QDialog {
14+
Q_OBJECT
15+
public:
16+
explicit CheckBoxListDialog(QWidget* parent = nullptr);
17+
18+
void setCaption(const QString& text);
19+
void setOptions(const QVector<std::pair<QString, bool>>& options);
20+
QVector<bool> getCheckedStates() const;
21+
22+
private:
23+
Ui::CheckBoxListDialog* ui;
24+
QVector<QCheckBox*> _checkboxes;
25+
};
26+
27+
} // namespace fso::fred::dialogs
Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
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+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#pragma once
2+
#include <QDialog>
3+
#include <QHash>
4+
#include <QLineEdit>
5+
#include <QListWidget>
6+
#include <QPixmap>
7+
#include <QPushButton>
8+
9+
class ImagePickerDialog : public QDialog {
10+
Q_OBJECT
11+
public:
12+
explicit ImagePickerDialog(QWidget* parent = nullptr);
13+
14+
void setImageFilenames(const QStringList& filenames);
15+
void setInitialSelection(const QString& filename);
16+
QString selectedFile() const
17+
{
18+
return _selected;
19+
}
20+
21+
void allowUnset(bool enable)
22+
{
23+
_allowUnset = enable;
24+
}
25+
26+
private slots:
27+
void onFilterTextChanged(const QString& text);
28+
void onItemActivated(QListWidgetItem* item);
29+
void onOk();
30+
31+
private: // NOLINT(readability-redundant-access-specifiers)
32+
void rebuildList();
33+
QIcon iconFor(const QString& name);
34+
35+
QLineEdit* _filterEdit{nullptr};
36+
QListWidget* _list{nullptr};
37+
QPushButton* _okBtn{nullptr};
38+
QPushButton* _cancelBtn{nullptr};
39+
40+
QStringList _allFiles;
41+
QString _filterText;
42+
QString _selected;
43+
44+
QHash<QString, QPixmap> _thumbCache;
45+
46+
bool _allowUnset{false};
47+
};

0 commit comments

Comments
 (0)