Skip to content

Commit efb1a62

Browse files
authored
Qtfred fixes and tweaks (#7379)
* fix mission stats message count * fix texture replace for retail textures * fix heap issue in ship alt class * fix props menu item * some ui clarity * unify up/down buttons to use our painted icons * fixup ship and wing cues toggles * rename and move Object Orientation menu item * menu seperator styling for dark mode
1 parent 86ef1ff commit efb1a62

18 files changed

Lines changed: 183 additions & 114 deletions

qtfred/src/mission/dialogs/MissionStatsDialogModel.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ int MissionStatsDialogModel::getJumpNodeCount()
7878

7979
int MissionStatsDialogModel::getMessageCount()
8080
{
81-
return Num_messages;
81+
return Num_messages - Num_builtin_messages;
8282
}
8383

8484
int MissionStatsDialogModel::getEventCount()

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

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,11 @@
22

33
#include "mission/object.h"
44

5+
// Sub-texture type suffixes, mirroring the strcat_s calls in modelread.cpp.
6+
// Used both when detecting sub-texture slots in initSubTypes and when parsing
7+
// new_texture strings on dialog reload.
8+
static const SCP_string SUBTEXTURE_SUFFIXES[] = { "misc", "shine", "glow", "normal", "height", "ao", "reflect" };
9+
510
namespace fso {
611
namespace fred {
712
namespace dialogs {
@@ -76,24 +81,55 @@ namespace fso {
7681
{
7782
if (!stricmp(Ships[_editor->cur_ship].ship_name, Fred_texture_replacement.ship_name) && !(Fred_texture_replacement.from_table))
7883
{
84+
// old_texture is stored as the bare base name by this dialog (no type suffix).
85+
// However, entries loaded from old mission files may have a type suffix
86+
// (e.g. "fenris-body-misc"), so fall back to stripping if no direct match.
7987
SCP_string pureName = Fred_texture_replacement.old_texture;
80-
auto npos = pureName.find_last_of('-');
81-
if (npos != SCP_string::npos) {
82-
pureName = pureName.substr(0, pureName.find_last_of('-'));
88+
89+
// Find the matching default texture slot.
90+
// Try direct match first; fall back to stripping the last '-' segment
91+
// for old mission-file entries that stored old_texture with a type suffix.
92+
size_t matchIdx = defaultTextures.size();
93+
for (size_t i = 0; i < defaultTextures.size(); i++) {
94+
if (lcase_equal(defaultTextures[i], pureName)) {
95+
matchIdx = i;
96+
break;
97+
}
98+
}
99+
if (matchIdx == defaultTextures.size()) {
100+
auto stripPos = pureName.find_last_of('-');
101+
if (stripPos != SCP_string::npos) {
102+
SCP_string stripped = pureName.substr(0, stripPos);
103+
for (size_t i = 0; i < defaultTextures.size(); i++) {
104+
if (lcase_equal(defaultTextures[i], stripped)) {
105+
matchIdx = i;
106+
break;
107+
}
108+
}
109+
}
83110
}
84111

85-
// look for corresponding old texture
86-
for (size_t i = 0; i < defaultTextures.size(); i++)
112+
if (matchIdx < defaultTextures.size())
87113
{
88-
// if match
89-
if (lcase_equal(defaultTextures[i], pureName))
114+
size_t i = matchIdx;
90115
{
91116
SCP_string newText = Fred_texture_replacement.new_texture;
92-
npos = newText.find_last_of('-');
93117
SCP_string type;
94-
if (npos != SCP_string::npos) {
95-
type = newText.substr(npos + 1);
96-
newText = newText.substr(0, newText.find_last_of('-'));
118+
{
119+
auto npos = newText.find_last_of('-');
120+
if (npos != SCP_string::npos) {
121+
SCP_string possibleType = newText.substr(npos + 1);
122+
// Only treat the suffix as a type if it's a known sub-texture type.
123+
// Texture names themselves can contain hyphens (e.g. "fighter01-01a"),
124+
// so we must not blindly strip the last segment.
125+
for (const auto& kt : SUBTEXTURE_SUFFIXES) {
126+
if (lcase_equal(possibleType, kt)) {
127+
type = possibleType;
128+
newText = newText.substr(0, npos);
129+
break;
130+
}
131+
}
132+
}
97133
}
98134
if (!type.empty()) {
99135
if (type == "misc") {
@@ -142,8 +178,6 @@ namespace fso {
142178
currentTextures[i]["main"] = newText;
143179
}
144180

145-
// we found one, so no more to check
146-
break;
147181
}
148182
}
149183
}
@@ -206,30 +240,19 @@ namespace fso {
206240
}
207241
if (!type.empty()) {
208242
if (type == "trans") {
209-
}
210-
else if (type == "misc") {
211-
subTypesAvailable[MapNum]["misc"] = true;
212-
}
213-
else if (type == "shine") {
214-
subTypesAvailable[MapNum]["shine"] = true;
215-
}
216-
else if (type == "glow") {
217-
subTypesAvailable[MapNum]["glow"] = true;
218-
}
219-
else if (type == "normal") {
220-
subTypesAvailable[MapNum]["normal"] = true;
221-
}
222-
else if (type == "height") {
223-
subTypesAvailable[MapNum]["height"] = true;
224-
}
225-
else if (type == "ao") {
226-
subTypesAvailable[MapNum]["ao"] = true;
227-
}
228-
else if (type == "reflect") {
229-
subTypesAvailable[MapNum]["reflect"] = true;
230-
}
231-
else {
232-
error_display(1, "Invalid Map type %s. Check your model's texture names or get a programmer", type.c_str());
243+
// transparency map, not a replaceable subtype
244+
} else {
245+
bool known = false;
246+
for (const auto& kt : SUBTEXTURE_SUFFIXES) {
247+
if (lcase_equal(type, kt)) {
248+
subTypesAvailable[MapNum][kt] = true;
249+
known = true;
250+
break;
251+
}
252+
}
253+
if (!known) {
254+
error_display(1, "Invalid Map type %s. Check your model's texture names or get a programmer", type.c_str());
255+
}
233256
}
234257
}
235258
}
@@ -564,7 +587,7 @@ namespace fso {
564587
{
565588
temp_bmp = bm_load_animation(fullName.c_str(), &temp_frames, &temp_fps, nullptr, nullptr, false, true);
566589
}
567-
return temp_bmp < 0;
590+
return temp_bmp >= 0;
568591
}
569592
}
570593

qtfred/src/ui/FredView.cpp

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -124,10 +124,6 @@ FredView::FredView(QWidget* parent) : QMainWindow(parent), ui(new Ui::FredView()
124124

125125
initializeGroupActions();
126126

127-
auto propsAction = new QAction(tr("Props"), this);
128-
connect(propsAction, &QAction::triggered, this, &FredView::on_actionProps_triggered);
129-
ui->menuObjects->insertAction(ui->actionWaypoint_Paths, propsAction);
130-
131127
connect(ui->actionPreferences, &QAction::triggered, this, [this]() {
132128
dialogs::PreferencesDialog preferencesDialog(this, _viewport);
133129
preferencesDialog.exec();
@@ -1638,7 +1634,7 @@ void FredView::on_actionCampaign_triggered(bool) {
16381634
editorCampaign->setAttribute(Qt::WA_DeleteOnClose);
16391635
editorCampaign->show();
16401636
}
1641-
void FredView::on_actionObjects_triggered(bool) {
1637+
void FredView::on_actionObject_Orientation_triggered(bool) {
16421638
orientEditorTriggered();
16431639
}
16441640
void FredView::on_actionCommand_Briefing_triggered(bool) {
@@ -1783,7 +1779,7 @@ void FredView::orientEditorTriggered() {
17831779
dialog->exec();
17841780
}
17851781
void FredView::onUpdateEditorActions() {
1786-
ui->actionObjects->setEnabled(query_valid_object(fred->currentObject));
1782+
ui->actionObject_Orientation->setEnabled(query_valid_object(fred->currentObject));
17871783

17881784
const bool validObject = query_valid_object(fred->currentObject);
17891785
const bool hasMarked = fred->getNumMarked() > 0;
@@ -1796,7 +1792,7 @@ void FredView::onUpdateEditorActions() {
17961792
ui->actionDelete_Wing->setEnabled(fred->cur_wing >= 0);
17971793

17981794
// Objects editor — requires a selected object
1799-
ui->actionObjects->setEnabled(validObject);
1795+
ui->actionObject_Orientation->setEnabled(validObject);
18001796

18011797
// Level/Align — require something to be selected
18021798
ui->actionLevel_Object->setEnabled(validObject);

qtfred/src/ui/FredView.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class FredView: public QMainWindow, public IDialogProvider {
116116
void on_actionMission_Specs_triggered(bool);
117117
void on_actionWaypoint_Paths_triggered(bool);
118118
void on_actionJump_Nodes_triggered(bool);
119-
void on_actionObjects_triggered(bool);
119+
void on_actionObject_Orientation_triggered(bool);
120120
void on_actionShips_triggered(bool);
121121
void on_actionWings_triggered(bool);
122122
void on_actionProps_triggered(bool);

qtfred/src/ui/Theme.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,11 @@ QToolButton:checked:hover {
102102
QToolButton::menu-indicator {
103103
image: none;
104104
}
105+
QMenu::separator {
106+
height: 1px;
107+
background: #8f8f8f;
108+
margin: 4px 8px;
109+
}
105110
)";
106111

107112
} // anonymous namespace

qtfred/src/ui/dialogs/ReinforcementsEditorDialog.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#include "ReinforcementsEditorDialog.h"
22
#include "ui_ReinforcementsDialog.h"
3+
#include "ui/Theme.h"
34
#include "mission/util.h"
45
#include <globalincs/linklist.h>
56
#include <ui/util/SignalBlockers.h>
@@ -16,6 +17,14 @@ ReinforcementsDialog::ReinforcementsDialog(FredView* parent, EditorViewport* vie
1617
this->setFocus();
1718
ui->setupUi(this);
1819

20+
fso::fred::bindStandardIcon(ui->moveSelectionUp, QStyle::SP_ArrowUp);
21+
ui->moveSelectionUp->setText(QString());
22+
ui->moveSelectionUp->setToolTip(tr("Move selected reinforcement up"));
23+
24+
fso::fred::bindStandardIcon(ui->moveSelectionDown, QStyle::SP_ArrowDown);
25+
ui->moveSelectionDown->setText(QString());
26+
ui->moveSelectionDown->setToolTip(tr("Move selected reinforcement down"));
27+
1928
updateUi();
2029
}
2130

@@ -262,4 +271,4 @@ void ReinforcementsDialog::on_poolMultiselectCheckbox_toggled(bool checked)
262271
}
263272
}
264273

265-
} // namespace fso::fred::dialogs
274+
} // namespace fso::fred::dialogs

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

Lines changed: 43 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#include "ui_ShipAltShipClass.h"
44

55
#include <mission/util.h>
6+
#include <ui/Theme.h>
67
#include <ui/util/SignalBlockers.h>
78

89
#include <QCloseEvent>
@@ -204,6 +205,15 @@ void ShipAltShipClass::initUI()
204205
variable_pool->appendRow(item);
205206
}
206207
ui->variableCombo->setModel(variable_pool);
208+
209+
fso::fred::bindStandardIcon(ui->upButton, QStyle::SP_ArrowUp);
210+
ui->upButton->setText(QString());
211+
ui->upButton->setToolTip(tr("Move selected class up"));
212+
213+
fso::fred::bindStandardIcon(ui->downButton, QStyle::SP_ArrowDown);
214+
ui->downButton->setText(QString());
215+
ui->downButton->setToolTip(tr("Move selected class down"));
216+
207217
updateUI();
208218
}
209219

@@ -222,36 +232,46 @@ void ShipAltShipClass::updateUI()
222232
if (ui->variableCombo->model()->rowCount() <= 1) {
223233
dynamic_cast<InverseSortFilterProxyModel*>(ui->shipCombo->model())->setFilterFixedString("Set From Variable");
224234
}
235+
// Workaround: avoid model()->match() which returns a QModelIndexList that triggers a
236+
// cross-heap free assert on Windows debug builds. Manually iterate instead.
225237
{
226-
QModelIndexList shipMatches =
227-
ui->shipCombo->model()->match(ui->shipCombo->model()->index(0, 0), Qt::UserRole, ship_class);
228-
if (!shipMatches.empty()) {
229-
ui->shipCombo->setCurrentIndex(shipMatches.first().row());
230-
} else {
231-
if (ui->classList->model()->rowCount() != 0 && ship_class != -1) {
232-
_viewport->dialogProvider->showButtonDialog(DialogType::Error,
233-
"Error",
234-
"Illegal ship class.\n Resetting to -1",
235-
{DialogButton::Ok});
238+
int shipRow = 0;
239+
bool found = false;
240+
auto* shipModel = ui->shipCombo->model();
241+
for (int i = 0; i < shipModel->rowCount(); ++i) {
242+
if (shipModel->data(shipModel->index(i, 0), Qt::UserRole).toInt() == ship_class) {
243+
shipRow = i;
244+
found = true;
245+
break;
236246
}
237-
ui->shipCombo->setCurrentIndex(0);
238247
}
248+
if (!found && ui->classList->model()->rowCount() != 0 && ship_class != -1) {
249+
_viewport->dialogProvider->showButtonDialog(DialogType::Error,
250+
"Error",
251+
"Illegal ship class.\n Resetting to -1",
252+
{DialogButton::Ok});
253+
}
254+
ui->shipCombo->setCurrentIndex(shipRow);
239255
}
240256

241257
{
242-
QModelIndexList varMatches =
243-
ui->variableCombo->model()->match(ui->variableCombo->model()->index(0, 0), Qt::UserRole, variable);
244-
if (!varMatches.empty()) {
245-
ui->variableCombo->setCurrentIndex(varMatches.first().row());
246-
} else {
247-
if (ui->classList->model()->rowCount() != 0) {
248-
_viewport->dialogProvider->showButtonDialog(DialogType::Error,
249-
"Error",
250-
"Illegal variable index.\n Resetting to -1",
251-
{DialogButton::Ok});
258+
int varRow = 0;
259+
bool found = false;
260+
auto* varModel = ui->variableCombo->model();
261+
for (int i = 0; i < varModel->rowCount(); ++i) {
262+
if (varModel->data(varModel->index(i, 0), Qt::UserRole).toInt() == variable) {
263+
varRow = i;
264+
found = true;
265+
break;
252266
}
253-
ui->variableCombo->setCurrentIndex(0);
254267
}
268+
if (!found && ui->classList->model()->rowCount() != 0) {
269+
_viewport->dialogProvider->showButtonDialog(DialogType::Error,
270+
"Error",
271+
"Illegal variable index.\n Resetting to -1",
272+
{DialogButton::Ok});
273+
}
274+
ui->variableCombo->setCurrentIndex(varRow);
255275
}
256276

257277
if (ui->variableCombo->model()->rowCount() <= 1) {
@@ -323,4 +343,4 @@ bool InverseSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelI
323343
bool accept = QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
324344
return !accept;
325345
}
326-
} // namespace fso::fred::dialogs
346+
} // namespace fso::fred::dialogs

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

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -667,21 +667,18 @@ void ShipEditorDialog::on_specialStatsButton_clicked()
667667
}
668668
void ShipEditorDialog::on_hideCuesButton_clicked()
669669
{
670-
if (ui->hideCuesButton->isChecked()) {
671-
ui->arrivalGroupBox->setVisible(false);
672-
ui->departureGroupBox->setVisible(false);
673-
ui->HelpTitle->setVisible(false);
674-
ui->helpText->setVisible(false);
675-
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
676-
resize(sizeHint());
677-
} else {
678-
ui->arrivalGroupBox->setVisible(true);
679-
ui->departureGroupBox->setVisible(true);
680-
ui->HelpTitle->setVisible(true);
681-
ui->helpText->setVisible(true);
682-
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
683-
resize(sizeHint());
684-
}
670+
const auto showHelp = _viewport->Show_sexp_help_ship_editor;
671+
672+
_cues_hidden = !_cues_hidden;
673+
674+
ui->arrivalGroupBox->setVisible(!_cues_hidden);
675+
ui->departureGroupBox->setVisible(!_cues_hidden);
676+
ui->HelpTitle->setVisible(!_cues_hidden && showHelp);
677+
ui->helpText->setVisible(!_cues_hidden && showHelp);
678+
ui->hideCuesButton->setText(_cues_hidden ? "Show Cues" : "Hide Cues");
679+
680+
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
681+
resize(sizeHint());
685682
}
686683
void ShipEditorDialog::on_restrictArrivalPathsButton_clicked()
687684
{

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,11 +116,13 @@ class ShipEditorDialog : public QDialog, public SexpTreeEditorInterface {
116116
void on_departureTree_helpChanged(const QString&);
117117
void on_departureTree_miniHelpChanged(const QString&);
118118
void on_noDepartureWarpCheckBox_toggled(bool);
119-
private:
119+
private: // NOLINT(readability-redundant-access-specifiers)
120120
std::unique_ptr<Ui::ShipEditorDialog> ui;
121121
std::unique_ptr<ShipEditorDialogModel> _model;
122122
EditorViewport* _viewport;
123123

124+
bool _cues_hidden = false;
125+
124126
void update();
125127

126128
void updateUI(bool overwrite = false);

0 commit comments

Comments
 (0)