From 7d395bc2dc731e64e0ec2de4ee4dad8d9fb1d27a Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 20 Jul 2026 10:44:11 -0500 Subject: [PATCH 1/2] add object select menu dropdown --- .../dialogs/WaypointEditorDialogModel.cpp | 4 ++ .../dialogs/WaypointEditorDialogModel.h | 3 +- .../src/ui/dialogs/JumpNodeEditorDialog.cpp | 20 +++++++ qtfred/src/ui/dialogs/PropEditorDialog.cpp | 24 +++++++++ .../dialogs/ShipEditor/ShipEditorDialog.cpp | 21 ++++++++ .../src/ui/dialogs/WaypointEditorDialog.cpp | 17 ++++++ qtfred/src/ui/dialogs/WingEditorDialog.cpp | 19 +++++++ qtfred/src/ui/util/menu.cpp | 53 +++++++++++++++++++ qtfred/src/ui/util/menu.h | 20 +++++++ 9 files changed, 180 insertions(+), 1 deletion(-) diff --git a/qtfred/src/mission/dialogs/WaypointEditorDialogModel.cpp b/qtfred/src/mission/dialogs/WaypointEditorDialogModel.cpp index ec053d77c4b..eddbf1dc955 100644 --- a/qtfred/src/mission/dialogs/WaypointEditorDialogModel.cpp +++ b/qtfred/src/mission/dialogs/WaypointEditorDialogModel.cpp @@ -398,6 +398,10 @@ void WaypointEditorDialogModel::selectWaypointPathByIndex(int idx) { initializeData(); } +int WaypointEditorDialogModel::getSelectedPathIndex() const { + return _selectedWaypointPaths.empty() ? -1 : _selectedWaypointPaths.front(); +} + void WaypointEditorDialogModel::selectNextPath() { if (Waypoint_lists.empty()) return; diff --git a/qtfred/src/mission/dialogs/WaypointEditorDialogModel.h b/qtfred/src/mission/dialogs/WaypointEditorDialogModel.h index 5bdb49ab1d7..52a861f72d2 100644 --- a/qtfred/src/mission/dialogs/WaypointEditorDialogModel.h +++ b/qtfred/src/mission/dialogs/WaypointEditorDialogModel.h @@ -45,6 +45,8 @@ class WaypointEditorDialogModel : public AbstractDialogModel { void selectNextPath(); void selectPreviousPath(); + void selectWaypointPathByIndex(int idx); + int getSelectedPathIndex() const; signals: void waypointPathMarkingChanged(); @@ -58,7 +60,6 @@ private slots: void initializeData(); void showErrorDialogNoCancel(const SCP_string& message); bool validateName(const SCP_string& name); - void selectWaypointPathByIndex(int idx); SCP_vector _selectedWaypointPaths; // indices into Waypoint_lists SCP_string _currentName; diff --git a/qtfred/src/ui/dialogs/JumpNodeEditorDialog.cpp b/qtfred/src/ui/dialogs/JumpNodeEditorDialog.cpp index 0b28b6479cb..f1b334fcf2a 100644 --- a/qtfred/src/ui/dialogs/JumpNodeEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/JumpNodeEditorDialog.cpp @@ -3,7 +3,9 @@ #include "ui_JumpNodeEditorDialog.h" #include +#include #include +#include namespace fso::fred::dialogs { @@ -32,6 +34,24 @@ JumpNodeEditorDialog::JumpNodeEditorDialog(FredView* parent, EditorViewport* vie updateUi(); }); + // "Select Jump Node" menu: jump the editor to any jump node in the mission. + Editor* editor = viewport->editor; + util::installSelectMenu( + this, + []() { + std::vector entries; + for (const auto& jn : Jump_nodes) { + entries.push_back({QString::fromUtf8(jn.GetName()), jn.GetSCPObjectNumber()}); + } + return entries; + }, + [this, editor]() { return _model->hasMultipleSelection() ? -1 : editor->currentObject; }, + [editor](int objnum) { + editor->unmark_all(); + editor->selectObject(objnum); + }, + tr("&Select Jump Node")); + // Resize the dialog to the minimum size resize(QDialog::sizeHint()); } diff --git a/qtfred/src/ui/dialogs/PropEditorDialog.cpp b/qtfred/src/ui/dialogs/PropEditorDialog.cpp index 2e5171f5c19..b71b04c2a76 100644 --- a/qtfred/src/ui/dialogs/PropEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/PropEditorDialog.cpp @@ -3,7 +3,11 @@ #include "ui_PropEditorDialog.h" #include +#include +#include +#include #include +#include #include #include @@ -40,6 +44,26 @@ PropEditorDialog::PropEditorDialog(FredView* parent, EditorViewport* viewport) QMetaObject::invokeMethod(this, [this]() { _viewport->editor->missionChanged(); }, Qt::QueuedConnection); }); + // "Select Prop" menu: jump the editor to any prop in the mission. + Editor* editor = viewport->editor; + util::installSelectMenu( + this, + []() { + std::vector entries; + for (auto* ptr = GET_FIRST(&obj_used_list); ptr != END_OF_LIST(&obj_used_list); ptr = GET_NEXT(ptr)) { + if (ptr->type == OBJ_PROP && Props[ptr->instance].has_value()) { + entries.push_back({QString::fromUtf8(Props[ptr->instance]->prop_name), OBJ_INDEX(ptr)}); + } + } + return entries; + }, + [this, editor]() { return _model->hasMultipleSelection() ? -1 : editor->currentObject; }, + [editor](int objnum) { + editor->unmark_all(); + editor->selectObject(objnum); + }, + tr("&Select Prop")); + resize(QDialog::sizeHint()); } diff --git a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp index 76b169192a4..d39bb93a5ce 100644 --- a/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/ShipEditor/ShipEditorDialog.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include @@ -53,6 +54,26 @@ ShipEditorDialog::ShipEditorDialog(FredView* parent, EditorViewport* viewport) // ui->cargoCombo->installEventFilter(this); + // "Select Ship" menu: jump the editor to any ship (or player) in the mission. + Editor* editor = viewport->editor; + util::installSelectMenu( + this, + []() { + std::vector entries; + for (auto* ptr = GET_FIRST(&obj_used_list); ptr != END_OF_LIST(&obj_used_list); ptr = GET_NEXT(ptr)) { + if (ptr->type == OBJ_SHIP || ptr->type == OBJ_START) { + entries.push_back({QString::fromUtf8(Ships[ptr->instance].ship_name), OBJ_INDEX(ptr)}); + } + } + return entries; + }, + [this, editor]() { return _model->getIfMultipleShips() ? -1 : editor->currentObject; }, + [editor](int objnum) { + editor->unmark_all(); + editor->selectObject(objnum); + }, + tr("&Select Ship")); + updateUi(true); // Resize the dialog to the minimum size diff --git a/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp b/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp index 441496dc8f4..1a6c5f0c04e 100644 --- a/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp @@ -5,6 +5,8 @@ #include #include +#include +#include namespace fso::fred::dialogs { @@ -34,6 +36,21 @@ WaypointEditorDialog::WaypointEditorDialog(FredView* parent, EditorViewport* vie updateUi(); }); + // "Select Waypoint Path" menu: jump the editor to any path in the mission. + auto* model = _model.get(); + util::installSelectMenu( + this, + []() { + std::vector entries; + for (int i = 0; i < static_cast(Waypoint_lists.size()); i++) { + entries.push_back({QString::fromUtf8(Waypoint_lists[i].get_name()), i}); + } + return entries; + }, + [model]() { return model->hasMultipleSelection() ? -1 : model->getSelectedPathIndex(); }, + [model](int idx) { model->selectWaypointPathByIndex(idx); }, + tr("&Select Waypoint Path")); + // Resize the dialog to the minimum size resize(QDialog::sizeHint()); } diff --git a/qtfred/src/ui/dialogs/WingEditorDialog.cpp b/qtfred/src/ui/dialogs/WingEditorDialog.cpp index dc49e80393b..dd40fdea28e 100644 --- a/qtfred/src/ui/dialogs/WingEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/WingEditorDialog.cpp @@ -8,8 +8,10 @@ #include "ui_WingEditorDialog.h" #include +#include #include #include +#include #include namespace fso::fred::dialogs { @@ -43,6 +45,23 @@ WingEditorDialog::WingEditorDialog(FredView* parent, EditorViewport* viewport) connect(ui->departureTree, &sexp_tree_view::helpChanged, this, [this](const QString& help) { ui->helpText->setPlainText(help); }); connect(ui->departureTree, &sexp_tree_view::miniHelpChanged, this, [this](const QString& help) { ui->HelpTitle->setText(help); }); + // "Select Wing" menu: jump the editor to any wing in the mission. + Editor* editor = viewport->editor; + util::installSelectMenu( + this, + []() { + std::vector entries; + for (int i = 0; i < MAX_WINGS; i++) { + if (Wings[i].wave_count) { + entries.push_back({QString::fromUtf8(Wings[i].name), i}); + } + } + return entries; + }, + [editor]() { return editor->cur_wing; }, + [editor](int wing) { editor->mark_wing(wing); }, + tr("&Select Wing")); + refreshAllDynamicCombos(); updateUi(); diff --git a/qtfred/src/ui/util/menu.cpp b/qtfred/src/ui/util/menu.cpp index 1cf538467ce..cb26c51f23f 100644 --- a/qtfred/src/ui/util/menu.cpp +++ b/qtfred/src/ui/util/menu.cpp @@ -3,6 +3,13 @@ #include "menu.h" +#include +#include +#include +#include +#include + +#include namespace fso { namespace fred { @@ -28,6 +35,52 @@ int propagate_disabled_status(QMenu* top) { return count; } +void installSelectMenu(QWidget* dialog, + std::function()> gather, + std::function currentId, + std::function onChosen, + const QString& menuTitle) +{ + auto* layout = dialog->layout(); + if (layout == nullptr) { + return; // needs a top-level layout to host a menu bar + } + + // Reuse an existing menu bar if one is already installed, otherwise add a + // slim one above the dialog's content. + auto* menuBar = qobject_cast(layout->menuBar()); + if (menuBar == nullptr) { + menuBar = new QMenuBar(dialog); + layout->setMenuBar(menuBar); + } + + QMenu* menu = menuBar->addMenu(menuTitle); + + // Rebuild the list from the live scene every time the menu opens. + QObject::connect(menu, &QMenu::aboutToShow, menu, + [menu, gather = std::move(gather), currentId = std::move(currentId), onChosen = std::move(onChosen)]() { + menu->clear(); + const int current = currentId ? currentId() : -1; + QAction* currentAct = nullptr; + for (const auto& entry : gather()) { + QAction* act = menu->addAction(entry.name); + if (entry.id == current) { + // Highlight the current object with a bold font + QFont font = act->font(); + font.setBold(true); + act->setFont(font); + currentAct = act; + } + const int id = entry.id; + QObject::connect(act, &QAction::triggered, menu, [onChosen, id]() { onChosen(id); }); + } + // Open with the current item pre-highlighted. + if (currentAct != nullptr) { + menu->setActiveAction(currentAct); + } + }); +} + } } } diff --git a/qtfred/src/ui/util/menu.h b/qtfred/src/ui/util/menu.h index 3623bdf948a..41b7e11a32b 100644 --- a/qtfred/src/ui/util/menu.h +++ b/qtfred/src/ui/util/menu.h @@ -1,6 +1,12 @@ #pragma once #include +#include + +#include +#include + +class QWidget; namespace fso { namespace fred { @@ -8,6 +14,20 @@ namespace util { int propagate_disabled_status(QMenu* top); +struct SelectMenuEntry { + QString name; + int id; +}; + +// Adds a "Select" menu to an object-editor dialog's menu bar, creating a slim +// menu bar via the dialog's top-level layout if one isn't already present and +// reusing an existing bar otherwise. +void installSelectMenu(QWidget* dialog, + std::function()> gather, + std::function currentId, + std::function onChosen, + const QString& menuTitle); + } } } From cac27dac2b5f4633f60e68db26aa55fe7ec880fa Mon Sep 17 00:00:00 2001 From: Mike Nelson Date: Mon, 20 Jul 2026 12:21:40 -0500 Subject: [PATCH 2/2] clang --- qtfred/src/ui/dialogs/WaypointEditorDialog.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp b/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp index 1a6c5f0c04e..0d85be900b6 100644 --- a/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp +++ b/qtfred/src/ui/dialogs/WaypointEditorDialog.cpp @@ -42,6 +42,7 @@ WaypointEditorDialog::WaypointEditorDialog(FredView* parent, EditorViewport* vie this, []() { std::vector entries; + entries.reserve(Waypoint_lists.size()); for (int i = 0; i < static_cast(Waypoint_lists.size()); i++) { entries.push_back({QString::fromUtf8(Waypoint_lists[i].get_name()), i}); }