Skip to content

Commit 683b533

Browse files
authored
Qtfred Upgrade checkbox dialog (#7405)
1 parent 37ecbe4 commit 683b533

13 files changed

Lines changed: 145 additions & 287 deletions

qtfred/source_groups.cmake

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,9 +216,8 @@ add_file_folder("Source/UI/Dialogs"
216216
src/ui/dialogs/WaypointPathGeneratorDialog.h
217217
src/ui/dialogs/WingEditorDialog.cpp
218218
src/ui/dialogs/WingEditorDialog.h
219-
src/ui/dialogs/WingFlagsDialog.cpp
220-
src/ui/dialogs/WingFlagsDialog.h
221219
)
220+
222221
add_file_folder("Source/UI/Dialogs/BriefingEditor"
223222
src/ui/dialogs/BriefingEditor/CameraCoordinatesDialog.cpp
224223
src/ui/dialogs/BriefingEditor/CameraCoordinatesDialog.h
@@ -373,7 +372,6 @@ add_file_folder("UI"
373372
ui/ShipWeaponsDialog.ui
374373
ui/VariableDialog.ui
375374
ui/WingEditorDialog.ui
376-
ui/WingFlagsDialog.ui
377375
ui/SaveAsTemplateDialog.ui
378376
ui/SceneBrowserPanel.ui
379377
ui/TemplateBrowserDialog.ui

qtfred/src/mission/dialogs/ShipEditor/ShipEditorDialogModel.cpp

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -78,58 +78,62 @@ int ShipEditorDialogModel::getIfPlayerShip() const
7878
{
7979
return player_ship;
8080
}
81-
std::vector<std::pair<SCP_string, bool>> ShipEditorDialogModel::getAcceptedOrders() const
81+
82+
SCP_vector<std::pair<SCP_string, int>> ShipEditorDialogModel::getPlayerOrders()
8283
{
83-
std::vector<std::pair<SCP_string, bool>> acceptedOrders;
84-
object* objp;
85-
SCP_set<size_t> default_orders;
86-
if (!multi_edit) {
87-
default_orders = ship_get_default_orders_accepted(&Ship_info[Ships[_editor->cur_ship].ship_info_index]);
88-
} else {
89-
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
90-
if (((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) &&
91-
(objp->flags[Object::Object_Flags::Marked])) {
92-
const SCP_set<size_t>& these_orders =
93-
ship_get_default_orders_accepted(&Ship_info[Ships[objp->instance].ship_info_index]);
84+
SCP_vector<std::pair<SCP_string, int>> orders;
9485

95-
if (default_orders.empty()) {
96-
default_orders = these_orders;
97-
} else {
98-
Assert(default_orders == these_orders);
99-
}
86+
// Build the canonical default order set from marked ships (caller guarantees all marked ships share it)
87+
SCP_set<size_t> default_orders;
88+
object* objp;
89+
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
90+
if (((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) && (objp->flags[Object::Object_Flags::Marked])) {
91+
const SCP_set<size_t>& these_orders =
92+
ship_get_default_orders_accepted(&Ship_info[Ships[objp->instance].ship_info_index]);
93+
if (default_orders.empty()) {
94+
default_orders = these_orders;
95+
} else {
96+
Assert(default_orders == these_orders);
10097
}
10198
}
10299
}
103100

104101
for (size_t order_id : default_orders) {
105102
SCP_string name = Player_orders[order_id].localized_name;
106-
bool state = false;
107-
const SCP_set<size_t>& orders_accepted = Ships[_editor->cur_ship].orders_accepted;
108-
if (orders_accepted.contains(order_id))
109-
state = true;
110-
acceptedOrders.emplace_back(name, state);
103+
int state = -1;
104+
for (objp = GET_FIRST(&obj_used_list); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp)) {
105+
if (((objp->type == OBJ_SHIP) || (objp->type == OBJ_START)) &&
106+
(objp->flags[Object::Object_Flags::Marked])) {
107+
int ship_state = Ships[objp->instance].orders_accepted.contains(order_id) ? Qt::Checked : Qt::Unchecked;
108+
state = (state == -1) ? ship_state : tristate_set(ship_state, state);
109+
}
110+
}
111+
orders.emplace_back(name, (state == -1) ? Qt::Unchecked : state);
111112
}
112-
return acceptedOrders;
113+
114+
return orders;
113115
}
114-
void ShipEditorDialogModel::setAcceptedOrders(const std::vector<std::pair<SCP_string, bool>>& newOrders)
116+
117+
void ShipEditorDialogModel::applyPlayerOrders(const SCP_vector<std::pair<SCP_string, int>>& orders)
115118
{
116-
orders = newOrders;
117-
// Write directly to all marked ships
118-
for (auto* ptr = GET_FIRST(&obj_used_list); ptr != END_OF_LIST(&obj_used_list); ptr = GET_NEXT(ptr)) {
119+
object* ptr;
120+
for (ptr = GET_FIRST(&obj_used_list); ptr != END_OF_LIST(&obj_used_list); ptr = GET_NEXT(ptr)) {
119121
if (((ptr->type == OBJ_SHIP) || (ptr->type == OBJ_START)) && (ptr->flags[Object::Object_Flags::Marked])) {
120122
auto i = ptr->instance;
121123
SCP_set<size_t> default_orders = ship_get_default_orders_accepted(&Ship_info[Ships[i].ship_info_index]);
122-
SCP_set<size_t> new_orders_set;
123124
for (size_t order_id : default_orders) {
124-
for (const auto& order : orders) {
125-
if (order.first == Player_orders[order_id].localized_name) {
126-
if (order.second) {
127-
new_orders_set.insert(order_id);
125+
for (const auto& [name, state] : orders) {
126+
if (name == Player_orders[order_id].localized_name) {
127+
if (state == Qt::Checked) {
128+
Ships[i].orders_accepted.insert(order_id);
129+
} else if (state == Qt::Unchecked) {
130+
Ships[i].orders_accepted.erase(order_id);
128131
}
132+
// Qt::PartiallyChecked: leave each ship's existing state unchanged
133+
break;
129134
}
130135
}
131136
}
132-
Ships[i].orders_accepted = new_orders_set;
133137
}
134138
}
135139
set_modified();

qtfred/src/mission/dialogs/ShipEditor/ShipEditorDialogModel.h

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ class ShipEditorDialogModel : public AbstractDialogModel {
7070

7171
int respawn_priority;
7272

73-
std::vector<std::pair<SCP_string, bool>> orders;
7473
std::vector<std::pair<SCP_string, bool>> arrivalPaths;
7574
std::vector<std::pair<SCP_string, bool>> departurePaths;
7675

@@ -244,8 +243,8 @@ class ShipEditorDialogModel : public AbstractDialogModel {
244243
*/
245244
int getIfPlayerShip() const;
246245

247-
std::vector<std::pair<SCP_string, bool>> getAcceptedOrders() const;
248-
void setAcceptedOrders(const std::vector<std::pair<SCP_string, bool>>&);
246+
static SCP_vector<std::pair<SCP_string, int>> getPlayerOrders();
247+
void applyPlayerOrders(const SCP_vector<std::pair<SCP_string, int>>& orders);
249248

250249
std::vector<std::pair<SCP_string, bool>> getArrivalPaths() const;
251250
void setArrivalPaths(const std::vector<std::pair<SCP_string, bool>>&);

qtfred/src/ui/dialogs/General/CheckBoxListDialog.cpp

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -2,70 +2,54 @@
22

33
#include "ui_CheckBoxListDialog.h"
44

5-
#include <QCheckBox>
6-
#include <QScrollArea>
7-
#include <QVBoxLayout>
8-
95
namespace fso::fred::dialogs {
106

117
CheckBoxListDialog::CheckBoxListDialog(QWidget* parent) : QDialog(parent), ui(new Ui::CheckBoxListDialog)
128
{
139
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);
10+
setSizeGripEnabled(true);
3211
}
3312

3413
void CheckBoxListDialog::setCaption(const QString& text)
3514
{
36-
this->setWindowTitle(text);
15+
setWindowTitle(text);
3716
}
3817

3918
void CheckBoxListDialog::setOptions(const QVector<std::pair<QString, bool>>& options)
4019
{
41-
// Clear previous checkboxes
42-
for (auto* cb : _checkboxes) {
43-
cb->deleteLater();
44-
}
45-
_checkboxes.clear();
20+
QVector<std::pair<QString, int>> intOptions;
21+
intOptions.reserve(options.size());
22+
for (const auto& [name, checked] : options)
23+
intOptions.append({name, checked ? Qt::Checked : Qt::Unchecked});
24+
ui->flagList->setFlags(intOptions);
25+
}
26+
27+
void CheckBoxListDialog::setOptions(const QVector<std::pair<QString, int>>& options)
28+
{
29+
ui->flagList->setFlags(options);
30+
}
4631

47-
auto* layout = qobject_cast<QVBoxLayout*>(ui->checkboxContainer->layout());
48-
if (!layout) {
49-
return;
50-
}
32+
void CheckBoxListDialog::setOptionDescriptions(const QVector<std::pair<QString, QString>>& descriptions)
33+
{
34+
ui->flagList->setFlagDescriptions(descriptions);
35+
}
5136

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();
37+
void CheckBoxListDialog::setTristate(bool tristate)
38+
{
39+
ui->flagList->setTristate(tristate);
6040
}
6141

6242
QVector<bool> CheckBoxListDialog::getCheckedStates() const
6343
{
6444
QVector<bool> states;
65-
for (auto* cb : _checkboxes) {
66-
states.append(cb->isChecked());
67-
}
45+
for (const auto& [name, state] : ui->flagList->getFlags())
46+
states.append(state == Qt::Checked);
6847
return states;
6948
}
7049

50+
QVector<std::pair<QString, int>> CheckBoxListDialog::getFlags() const
51+
{
52+
return ui->flagList->getFlags();
53+
}
54+
7155
} // namespace fso::fred::dialogs

qtfred/src/ui/dialogs/General/CheckBoxListDialog.h

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
#pragma once
22

3+
#include <ui/widgets/FlagList.h>
34

45
#include <QDialog>
5-
#include <QCheckBox>
6+
#include <QString>
7+
#include <QVector>
68

79
namespace fso::fred::dialogs {
810

@@ -16,12 +18,20 @@ class CheckBoxListDialog : public QDialog {
1618
explicit CheckBoxListDialog(QWidget* parent = nullptr);
1719

1820
void setCaption(const QString& text);
21+
22+
// Binary mode (backwards-compatible)
1923
void setOptions(const QVector<std::pair<QString, bool>>& options);
2024
QVector<bool> getCheckedStates() const;
2125

26+
// Tristate / int-state mode
27+
void setOptions(const QVector<std::pair<QString, int>>& options);
28+
QVector<std::pair<QString, int>> getFlags() const;
29+
30+
void setOptionDescriptions(const QVector<std::pair<QString, QString>>& descriptions);
31+
void setTristate(bool tristate);
32+
2233
private:
2334
Ui::CheckBoxListDialog* ui;
24-
QVector<QCheckBox*> _checkboxes;
2535
};
2636

27-
} // namespace fso::fred::dialogs
37+
} // namespace fso::fred::dialogs

qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp

Lines changed: 11 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -655,29 +655,20 @@ void ShipEditorDialog::on_weaponsButton_clicked()
655655
}
656656
void ShipEditorDialog::on_playerOrdersButton_clicked()
657657
{
658-
CheckBoxListDialog dlg(this);
659-
dlg.setCaption("Player Orders Accepted");
660-
// Get our flag list and convert it to Qt's internal types
661-
auto playerOrders = _model->getAcceptedOrders();
658+
QVector<std::pair<QString, int>> toWidget;
659+
for (const auto& p : _model->getPlayerOrders())
660+
toWidget.append({QString::fromUtf8(p.first.c_str()), p.second});
662661

663-
QVector<std::pair<QString, bool>> checkbox_list;
662+
dialogs::CheckBoxListDialog dlg(this);
663+
dlg.setCaption(tr("Player Orders Accepted"));
664+
dlg.setTristate(true);
665+
dlg.setOptions(toWidget);
664666

665-
for (const auto& porder : playerOrders) {
666-
checkbox_list.append({porder.first.c_str(), porder.second});
667-
}
668-
dlg.setOptions(checkbox_list); // TODO upgrade checkbox to accept and display item descriptions
669667
if (dlg.exec() == QDialog::Accepted) {
670-
auto returned_values = dlg.getCheckedStates();
671-
672-
std::vector<std::pair<SCP_string, bool>> updatedOrders;
673-
674-
for (int i = 0; i < checkbox_list.size(); ++i) {
675-
// Convert back to std::string
676-
std::string name = checkbox_list[i].first.toUtf8().constData();
677-
updatedOrders.emplace_back(name, returned_values[i]);
678-
}
679-
680-
_model->setAcceptedOrders(updatedOrders);
668+
SCP_vector<std::pair<SCP_string, int>> orders;
669+
for (const auto& [name, state] : dlg.getFlags())
670+
orders.emplace_back(name.toUtf8().constData(), state);
671+
_model->applyPlayerOrders(orders);
681672
}
682673
}
683674
void ShipEditorDialog::on_specialStatsButton_clicked()

qtfred/src/ui/dialogs/WingEditorDialog.cpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
#include "General/ImagePickerDialog.h"
55
#include "ShipEditor/ShipGoalsDialog.h"
66
#include "ShipEditor/ShipCustomWarpDialog.h"
7-
#include "WingFlagsDialog.h"
87

98
#include "ui_WingEditorDialog.h"
109

@@ -477,9 +476,24 @@ void WingEditorDialog::on_initialOrdersButton_clicked()
477476

478477
void WingEditorDialog::on_wingFlagsButton_clicked()
479478
{
480-
WingFlagsDialog dlg(this, _model->getWingFlags(), _model->getWingFlagDescriptions());
479+
QVector<std::pair<QString, int>> qtFlags;
480+
for (const auto& f : _model->getWingFlags())
481+
qtFlags.append({QString::fromUtf8(f.first.c_str()), f.second ? Qt::Checked : Qt::Unchecked});
482+
483+
QVector<std::pair<QString, QString>> qtDescs;
484+
for (const auto& d : _model->getWingFlagDescriptions())
485+
qtDescs.append({QString::fromUtf8(d.first.c_str()), QString::fromUtf8(d.second.c_str())});
486+
487+
dialogs::CheckBoxListDialog dlg(this);
488+
dlg.setCaption(tr("Wing Flags"));
489+
dlg.setOptions(qtFlags);
490+
dlg.setOptionDescriptions(qtDescs);
491+
481492
if (dlg.exec() == QDialog::Accepted) {
482-
_model->setWingFlags(dlg.getFlags());
493+
std::vector<std::pair<SCP_string, bool>> result;
494+
for (const auto& f : dlg.getFlags())
495+
result.emplace_back(f.first.toUtf8().constData(), f.second == Qt::Checked);
496+
_model->setWingFlags(result);
483497
}
484498
}
485499

qtfred/src/ui/dialogs/WingFlagsDialog.cpp

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)