Skip to content

Commit b4a321d

Browse files
authored
Merge pull request scp-fs2open#7591 from Goober5000/fix/icon_propagation
fix several bugs related to icon propagation, mostly in FRED
2 parents 56dddef + 978e3be commit b4a321d

5 files changed

Lines changed: 143 additions & 30 deletions

File tree

fred2/briefingeditordlg.cpp

Lines changed: 68 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,18 @@ void briefing_editor_dlg::update_data(int update)
432432

433433
if (m_last_icon >= 0) {
434434
valid = (m_id != ptr->icons[m_last_icon].id);
435-
if (m_id >= 0) {
435+
if (m_id < 0) {
436+
// briefing icon ids must never be negative; revert to the previous id
437+
if (valid) {
438+
char msg[1024];
439+
440+
sprintf(msg, "Icon ID #%d is invalid. Briefing icon IDs cannot be negative.\n"
441+
"Icon ID has been reset back to %d", m_id, ptr->icons[m_last_icon].id);
442+
443+
m_id = ptr->icons[m_last_icon].id;
444+
MessageBox(msg);
445+
}
446+
} else {
436447
if (valid && !m_change_local) {
437448
for (i=m_last_stage+1; i<Briefing->num_stages; i++) {
438449
if (find_icon(m_id, i) >= 0) {
@@ -473,7 +484,7 @@ void briefing_editor_dlg::update_data(int update)
473484

474485
string_copy(buf, m_icon_label, MAX_LABEL_LEN - 1);
475486
lcl_fred_replace_stuff(buf, MAX_LABEL_LEN - 1);
476-
if (stricmp(ptr->icons[m_last_icon].label, buf) && !m_change_local) {
487+
if (strcmp(ptr->icons[m_last_icon].label, buf) && !m_change_local) {
477488
set_modified();
478489
reset_icon_loop(m_last_stage);
479490
while (get_next_icon(m_id))
@@ -483,36 +494,41 @@ void briefing_editor_dlg::update_data(int update)
483494

484495
string_copy(buf, m_icon_closeup_label, MAX_LABEL_LEN - 1);
485496
lcl_fred_replace_stuff(buf, MAX_LABEL_LEN - 1);
486-
if (stricmp(ptr->icons[m_last_icon].closeup_label, buf) && !m_change_local) {
497+
if (strcmp(ptr->icons[m_last_icon].closeup_label, buf) && !m_change_local) {
487498
set_modified();
488499
reset_icon_loop(m_last_stage);
489500
while (get_next_icon(m_id))
490501
strcpy_s(iconp->closeup_label, buf);
491502
}
492503
strcpy_s(ptr->icons[m_last_icon].closeup_label, buf);
493504

494-
if (m_icon_scale > 0)
495-
ptr->icons[m_last_icon].scale_factor = m_icon_scale / 100.0f;
496-
497-
if ( m_hilight )
498-
ptr->icons[m_last_icon].flags |= BI_HIGHLIGHT;
499-
else
500-
ptr->icons[m_last_icon].flags &= ~BI_HIGHLIGHT;
501-
502-
if (m_flipicon)
503-
ptr->icons[m_last_icon].flags |= BI_MIRROR_ICON;
504-
else
505-
ptr->icons[m_last_icon].flags &= ~BI_MIRROR_ICON;
505+
if (m_icon_scale > 0) {
506+
float new_scale = m_icon_scale / 100.0f;
507+
if ((ptr->icons[m_last_icon].scale_factor != new_scale) && !m_change_local) {
508+
set_modified();
509+
reset_icon_loop(m_last_stage);
510+
while (get_next_icon(m_id))
511+
iconp->scale_factor = new_scale;
512+
}
513+
ptr->icons[m_last_icon].scale_factor = new_scale;
514+
}
506515

507-
if (m_use_wing)
508-
ptr->icons[m_last_icon].flags |= BI_USE_WING_ICON;
509-
else
510-
ptr->icons[m_last_icon].flags &= ~BI_USE_WING_ICON;
516+
// build the new flags for this icon from the checkboxes, preserving any non-editable bits
517+
const int editable_flags = BI_HIGHLIGHT | BI_MIRROR_ICON | BI_USE_WING_ICON | BI_USE_CARGO_ICON;
518+
int new_flags = ptr->icons[m_last_icon].flags;
519+
if (m_hilight) new_flags |= BI_HIGHLIGHT; else new_flags &= ~BI_HIGHLIGHT;
520+
if (m_flipicon) new_flags |= BI_MIRROR_ICON; else new_flags &= ~BI_MIRROR_ICON;
521+
if (m_use_wing) new_flags |= BI_USE_WING_ICON; else new_flags &= ~BI_USE_WING_ICON;
522+
if (m_use_cargo) new_flags |= BI_USE_CARGO_ICON; else new_flags &= ~BI_USE_CARGO_ICON;
511523

512-
if (m_use_cargo)
513-
ptr->icons[m_last_icon].flags |= BI_USE_CARGO_ICON;
514-
else
515-
ptr->icons[m_last_icon].flags &= ~BI_USE_CARGO_ICON;
524+
if ((ptr->icons[m_last_icon].flags != new_flags) && !m_change_local) {
525+
set_modified();
526+
reset_icon_loop(m_last_stage);
527+
// apply only the editable bits to later icons, preserving their other flags
528+
while (get_next_icon(m_id))
529+
iconp->flags = (iconp->flags & ~editable_flags) | (new_flags & editable_flags);
530+
}
531+
ptr->icons[m_last_icon].flags = new_flags;
516532

517533
if ((ptr->icons[m_last_icon].type != m_icon_image) && !m_change_local) {
518534
set_modified();
@@ -942,6 +958,7 @@ void briefing_editor_dlg::copy_stage(int from, int to)
942958
Briefing->stages[to].camera_time = 500;
943959
Briefing->stages[to].num_icons = 0;
944960
Briefing->stages[to].formula = Locked_sexp_true;
961+
Briefing->stages[to].draw_grid = true;
945962
Briefing->stages[to].grid_color = Color_briefing_grid;
946963
return;
947964
}
@@ -956,6 +973,7 @@ void briefing_editor_dlg::copy_stage(int from, int to)
956973
Briefing->stages[to].num_icons = Briefing->stages[from].num_icons;
957974
Briefing->stages[to].num_lines = Briefing->stages[from].num_lines;
958975
Briefing->stages[to].formula = Briefing->stages[from].formula;
976+
Briefing->stages[to].draw_grid = Briefing->stages[from].draw_grid;
959977
// For now let's just always set this back to default. Eventually when we have a UI color picker in qtFRED, we can copy from stage to stage
960978
Briefing->stages[to].grid_color = Color_briefing_grid;
961979

@@ -1089,12 +1107,13 @@ void briefing_editor_dlg::OnMakeIcon()
10891107

10901108
strncpy(biconp->label, name, len);
10911109
biconp->label[len] = 0;
1110+
biconp->closeup_label[0] = 0;
10921111
// iconp->text[0] = 0;
10931112
biconp->type = 0;
10941113
biconp->team = team;
10951114
biconp->pos = pos;
10961115
biconp->flags = 0;
1097-
biconp->id = Cur_brief_id++;
1116+
biconp->id = get_unused_briefing_icon_id();
10981117
biconp->scale_factor = 1.0f;
10991118

11001119
biconp->modelnum = -1;
@@ -1300,10 +1319,13 @@ int briefing_editor_dlg::check_mouse_hit(int x, int y)
13001319
return -1;
13011320
}
13021321

1303-
void briefing_editor_dlg::OnPropagateIcons()
1322+
void briefing_editor_dlg::OnPropagateIcons()
13041323
{
13051324
object *ptr;
13061325

1326+
// commit any pending edits in the icon fields before propagating the icons forward
1327+
update_data(1);
1328+
13071329
ptr = GET_FIRST(&obj_used_list);
13081330
while (ptr != END_OF_LIST(&obj_used_list)) {
13091331
if ((ptr->type == OBJ_POINT) && (ptr->flags[Object::Object_Flags::Marked])) {
@@ -1343,6 +1365,26 @@ int briefing_editor_dlg::find_icon(int id, int stage)
13431365
return -1;
13441366
}
13451367

1368+
// is the given id already used by any icon in any stage of the current briefing?
1369+
bool briefing_editor_dlg::briefing_icon_id_used(int id)
1370+
{
1371+
for (int s=0; s<Briefing->num_stages; s++)
1372+
if (find_icon(id, s) >= 0)
1373+
return true;
1374+
1375+
return false;
1376+
}
1377+
1378+
// find an id that isn't already used by an icon in the current briefing, advancing
1379+
// Cur_brief_id past any values that are already taken (e.g. from manual id edits)
1380+
int briefing_editor_dlg::get_unused_briefing_icon_id()
1381+
{
1382+
while (briefing_icon_id_used(Cur_brief_id))
1383+
Cur_brief_id++;
1384+
1385+
return Cur_brief_id++;
1386+
}
1387+
13461388
void briefing_editor_dlg::reset_icon_loop(int stage)
13471389
{
13481390
stage_loop = stage + 1;
@@ -1355,7 +1397,7 @@ int briefing_editor_dlg::get_next_icon(int id)
13551397
icon_loop++;
13561398
if (icon_loop >= Briefing->stages[stage_loop].num_icons) {
13571399
stage_loop++;
1358-
if (stage_loop > Briefing->num_stages)
1400+
if (stage_loop >= Briefing->num_stages)
13591401
return 0;
13601402

13611403
icon_loop = -1;

fred2/briefingeditordlg.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ class briefing_editor_dlg : public CDialog
3434
void OnOK();
3535
void OnCancel();
3636
int find_icon(int id, int stage);
37+
bool briefing_icon_id_used(int id);
38+
int get_unused_briefing_icon_id();
3739
void propagate_icon(int num);
3840
void reset_editor();
3941
int check_mouse_hit(int x, int y);

qtfred/src/mission/dialogs/BriefingEditorDialogModel.cpp

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -785,9 +785,68 @@ int BriefingEditorDialogModel::getIconId() const
785785
return s.icons[_currentIcon].id;
786786
}
787787

788-
void BriefingEditorDialogModel::setIconId(int id)
788+
bool BriefingEditorDialogModel::setIconId(int id)
789789
{
790-
applyToSelectedIconsCurrentAndForward([&](brief_icon& ic) { modify(ic.id, id); });
790+
auto& briefing = _wipBriefings[_currentTeam];
791+
if (briefing.num_stages <= 0 || _currentStage < 0 || _currentStage >= briefing.num_stages)
792+
return true;
793+
794+
auto& stage = briefing.stages[_currentStage];
795+
if (_currentIcon < 0 || _currentIcon >= stage.num_icons)
796+
return true;
797+
798+
// an id applies to a single icon, so operate on the current icon rather than the
799+
// whole selection; applying one id to several icons would itself create a collision
800+
const int oldId = stage.icons[_currentIcon].id;
801+
if (id == oldId)
802+
return true; // no change
803+
804+
// briefing icon ids must never be negative (the spin box also enforces this)
805+
if (id < 0)
806+
return false;
807+
808+
// an icon id must be unique within its stage
809+
for (int i = 0; i < stage.num_icons; ++i) {
810+
if (i != _currentIcon && stage.icons[i].id == id) {
811+
QMessageBox::warning(nullptr,
812+
tr("Icon ID"),
813+
tr("Icon ID %1 is already used by another icon in this stage. The ID has not been changed.").arg(id));
814+
return false;
815+
}
816+
}
817+
818+
// when propagating forward, the new id must not already be used by a different icon in a
819+
// later stage, or the global rename would merge two distinct icon chains into one
820+
if (!_changeLocally) {
821+
for (int st = _currentStage + 1; st < briefing.num_stages; ++st) {
822+
const auto& s = briefing.stages[st];
823+
for (int i = 0; i < s.num_icons; ++i) {
824+
if (s.icons[i].id == id) {
825+
QMessageBox::warning(nullptr,
826+
tr("Icon ID"),
827+
tr("Icon ID %1 is already used in a later stage. You can only change to that ID "
828+
"locally. The ID has not been changed.").arg(id));
829+
return false;
830+
}
831+
}
832+
}
833+
}
834+
835+
// apply the change to the current icon, and (unless local-only) to the same icon in later stages
836+
stage.icons[_currentIcon].id = id;
837+
if (!_changeLocally) {
838+
for (int st = _currentStage + 1; st < briefing.num_stages; ++st) {
839+
auto& s = briefing.stages[st];
840+
for (int i = 0; i < s.num_icons; ++i) {
841+
if (s.icons[i].id == oldId)
842+
s.icons[i].id = id;
843+
}
844+
}
845+
}
846+
847+
set_modified();
848+
modelChanged();
849+
return true;
791850
}
792851

793852
SCP_string BriefingEditorDialogModel::getIconLabel() const

qtfred/src/mission/dialogs/BriefingEditorDialogModel.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ class BriefingEditorDialogModel : public AbstractDialogModel {
7474
vec3d getIconPosition() const;
7575
void setIconPosition(const vec3d& pos);
7676
int getIconId() const;
77-
void setIconId(int id);
77+
// returns false if the requested id was rejected (e.g. it collides with another icon)
78+
bool setIconId(int id);
7879
SCP_string getIconLabel() const;
7980
void setIconLabel(const SCP_string& text);
8081
SCP_string getIconCloseupLabel() const;

qtfred/src/ui/dialogs/BriefingEditorDialog.cpp

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ BriefingEditorDialog::BriefingEditorDialog(FredView* parent, EditorViewport* vie
8989
ui->iconCloseupLabelLineEdit->setMaxLength(MAX_LABEL_LEN - 1);
9090
ui->voiceFileLineEdit->setMaxLength(MAX_FILENAME_LEN - 1);
9191

92+
// validate the icon id when the edit is committed (focus-out/Enter) rather than on every
93+
// keystroke, so a transient value while typing can't pop a collision warning
94+
ui->iconIdSpinBox->setKeyboardTracking(false);
95+
9296
setupMapWidget();
9397
initializeUi();
9498
updateUi();
@@ -562,7 +566,12 @@ void BriefingEditorDialog::on_disableGridCheckBox_toggled(bool checked)
562566

563567
void BriefingEditorDialog::on_iconIdSpinBox_valueChanged(int arg1)
564568
{
565-
_model->setIconId(arg1);
569+
if (!_model->setIconId(arg1)) {
570+
// the id was rejected; reset the spin box to the model's current value
571+
ui->iconIdSpinBox->blockSignals(true);
572+
ui->iconIdSpinBox->setValue(_model->getIconId());
573+
ui->iconIdSpinBox->blockSignals(false);
574+
}
566575
}
567576

568577
void BriefingEditorDialog::on_iconLabelLineEdit_textChanged(const QString& string)

0 commit comments

Comments
 (0)