24#include <imgui_stdlib.h>
29using namespace std::literals;
44 if (
auto* firmwareSwitch =
dynamic_cast<BooleanSetting*
>(controller.findSetting(
"firmwareswitch"))) {
45 Checkbox(hotKey,
"Firmware switch", *firmwareSwitch);
49 auto& pauseSetting = reactor.getGlobalSettings().getPauseSetting();
50 bool pause = pauseSetting.getBoolean();
52 ImGui::MenuItem(
"Pause", shortCut.c_str(), &pause)) {
53 pauseSetting.setBoolean(pause);
57 ImGui::MenuItem(
"Reset", shortCut.c_str(),
nullptr, motherBoard !=
nullptr)) {
61 auto& powerSetting = reactor.getGlobalSettings().getPowerSetting();
62 bool power = powerSetting.getBoolean();
64 ImGui::MenuItem(
"Power", shortCut.c_str(), &power)) {
65 powerSetting.setBoolean(power);
76 paintSelectMachine(motherBoard);
84void ImGuiMachine::paintSelectMachine(
const MSXMotherBoard* motherBoard)
86 ImGui::SetNextWindowSize(
gl::vec2{29, 26} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
90 auto currentInstance = reactor.getMachineID();
91 if (instances.size() > 1 || currentInstance.empty()) {
92 ImGui::TextUnformatted(
"Instances:"sv);
93 HelpMarker(
"Switch between different machine instances. Right-click to delete an instance.");
95 float height = (std::min(4.0f, float(instances.size())) + 0.25f) * ImGui::GetTextLineHeightWithSpacing();
96 im::ListBox(
"##empty", {-FLT_MIN, height}, [&]{
97 im::ID_for_range(instances.size(), [&](int i) {
98 const auto& name = instances[i];
99 bool isCurrent = name == currentInstance;
100 auto board = reactor.getMachine(name);
101 std::string display = [&]{
103 auto configName = board->getMachineName();
104 auto* info = findMachineInfo(configName);
106 auto time = (board->getCurrentTime() - EmuTime::zero()).toDouble();
107 return strCat(info->displayName,
" (", formatTime(time),
')');
109 return std::string(name);
112 if (ImGui::Selectable(display.c_str(), isCurrent)) {
113 manager.executeDelayed(makeTclList(
"activate_machine", name));
115 im::PopupContextItem(
"instance context menu", [&]{
116 if (ImGui::Selectable(
"Delete instance")) {
117 manager.executeDelayed(makeTclList(
"delete_machine", name));
128 auto* info = findMachineInfo(configName);
130 std::string display =
strCat(
"Current machine: ", info->displayName);
132 printConfigInfo(*info);
134 if (newMachineConfig.empty()) newMachineConfig = configName;
135 if (
auto& defaultMachine = reactor.getMachineSetting();
136 defaultMachine.getString() != configName) {
137 if (ImGui::Button(
"Make this the default machine")) {
138 defaultMachine.setValue(TclObject(configName));
140 simpleToolTip(
"Use this as the default MSX machine when openMSX starts.");
146 auto& allMachines = getAllMachines();
147 std::string filterDisplay =
"filter";
148 if (!filterType.empty() || !filterRegion.empty() || !filterString.empty())
strAppend(filterDisplay,
':');
149 if (!filterType.empty())
strAppend(filterDisplay,
' ', filterType);
150 if (!filterRegion.empty())
strAppend(filterDisplay,
' ', filterRegion);
151 if (!filterString.empty())
strAppend(filterDisplay,
' ', filterString);
153 im::TreeNode(filterDisplay.c_str(), ImGuiTreeNodeFlags_DefaultOpen, [&]{
154 displayFilterCombo(filterType,
"Type", allMachines);
155 displayFilterCombo(filterRegion,
"Region", allMachines);
156 if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
157 ImGui::InputText(ICON_IGFD_SEARCH, &filterString);
158 simpleToolTip(
"A list of substrings that must be part of the machine name.\n"
160 "For example: enter 'pa' to search for 'Panasonic' machines. "
161 "Then refine the search by appending '<space>st' to find the 'Panasonic FS-A1ST' machine.");
169 bool inFilteredList =
contains(filteredMachines, newMachineConfig,
170 [&](
auto idx) {
return allMachines[idx].configName; });
172 im::ListBox(
"##list", {-FLT_MIN, -ImGui::GetFrameHeightWithSpacing()}, [&]{
174 auto idx = filteredMachines[i];
175 auto& info = allMachines[idx];
176 bool ok = getTestResult(info).empty();
177 im::StyleColor(!ok, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
178 if (ImGui::Selectable(info.displayName.c_str(), info.configName == newMachineConfig, ImGuiSelectableFlags_AllowDoubleClick)) {
179 newMachineConfig = info.configName;
180 if (ok && ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
181 showSelectMachine = false;
182 manager.executeDelayed(makeTclList(
"machine", newMachineConfig));
186 printConfigInfo(info);
193 if (!inFilteredList)
return false;
194 auto* info = findMachineInfo(newMachineConfig);
195 if (!info)
return false;
196 const auto&
test = getTestResult(*info);
200 if (ImGui::Button(
"Replace current machine")) {
201 manager.executeDelayed(
makeTclList(
"machine", newMachineConfig));
203 simpleToolTip(
"Replace the current machine with the selected machine. "
204 "Alternatively you can also double click in the list above (in addition that also closes this window).");
205 ImGui::SameLine(0.0f, 10.0f);
206 if (ImGui::Button(
"New machine instance")) {
207 std::string script =
strCat(
208 "set id [create_machine]\n"
209 "set err [catch {${id}::load_machine ", newMachineConfig,
"} error_result]\n"
211 " delete_machine $id\n"
212 " error \"Error activating new machine: $error_result\"\n"
214 " activate_machine $id\n"
216 manager.executeDelayed(TclObject(script));
218 simpleToolTip(
"Create a new machine instance (next to the current machine). "
219 "Later you can switch between the different instances (like different tabs in a web browser).");
224void ImGuiMachine::paintTestHardware()
226 ImGui::SetNextWindowSize(
gl::vec2{41.0f, 32.5f} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
227 im::Window(
"Test MSX hardware", &showTestHardware, [&]{
228 auto formatNum = [](
size_t num, std::string_view text) {
230 return strCat(
"No ", text,
"###", text);
232 return strCat(num,
' ', text,
"###", text);
235 auto printList = [](
const auto& indices,
const auto& infos) {
236 auto n = narrow<int>(indices.size());
237 auto clamped = std::clamp(narrow_cast<float>(n), 2.5f, 7.5f);
238 gl::vec2 listSize{0.0f, clamped * ImGui::GetTextLineHeightWithSpacing()};
240 ImGui::SetNextItemWidth(-FLT_MIN);
243 auto& info = infos[indices[i]];
245 assert(info.testResult);
253 auto& allMachines = getAllMachines();
254 std::vector<size_t> workingMachines, nonWorkingMachines;
255 for (
auto [idx, info] :
enumerate(allMachines)) {
256 if (!info.testResult.has_value()) {
257 if (!doTest)
continue;
260 const auto& result = getTestResult(info);
261 (result.empty() ? workingMachines : nonWorkingMachines).push_back(idx);
263 bool allMachinesTested = allMachines.size() == (workingMachines.size() + nonWorkingMachines.size());
266 im::TreeNode(
"Machines", ImGuiTreeNodeFlags_DefaultOpen, [&]{
267 auto workingText = formatNum(workingMachines.size(),
"working machines");
269 printList(workingMachines, allMachines);
271 auto nonWorkingText = formatNum(nonWorkingMachines.size(),
"non-working machines");
272 int flags = nonWorkingMachines.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen;
274 printList(nonWorkingMachines, allMachines);
279 auto& allExtensions = manager.media->getAllExtensions();
280 std::vector<size_t> workingExtensions, nonWorkingExtensions;
281 for (
auto [idx, info] :
enumerate(allExtensions)) {
282 if (!info.testResult.has_value()) {
283 if (!doTest)
continue;
286 const auto& result = manager.media->getTestResult(info);
287 (result.empty() ? workingExtensions : nonWorkingExtensions).push_back(idx);
289 bool allExtensionsTested = allExtensions.size() == (workingExtensions.size() + nonWorkingExtensions.size());
292 im::TreeNode(
"Extensions", ImGuiTreeNodeFlags_DefaultOpen, [&]{
293 auto workingText = formatNum(workingExtensions.size(),
"working extensions");
295 printList(workingExtensions, allExtensions);
297 auto nonWorkingText = formatNum(nonWorkingExtensions.size(),
"non-working extensions");
298 int flags = nonWorkingExtensions.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen;
300 printList(nonWorkingExtensions, allExtensions);
306 if (!nonWorkingMachines.empty() || !nonWorkingExtensions.empty()) {
307 im::Disabled(!allMachinesTested || !allExtensionsTested, [&]{
308 if (ImGui::Button(
"Copy list of non-working hardware to clipboard")) {
310 auto print = [&](std::string_view label,
const auto& indices,
const auto& infos) {
311 if (!indices.empty()) {
312 strAppend(result,
"Non-working ", label,
":\n");
313 for (
auto idx : indices) {
314 const auto& info = infos[idx];
315 strAppend(result,
'\t', info.displayName,
" (", info.configName,
")\n",
316 "\t\t", *info.testResult,
'\n');
321 print(
"machines", nonWorkingMachines, allMachines);
322 print(
"extensions", nonWorkingExtensions, allExtensions);
323 ImGui::SetClipboardText(result.c_str());
328 im::Disabled(!allMachinesTested || !allExtensionsTested, [&]{
329 if (ImGui::Button(
"Rerun test")) {
330 manager.media->resetExtensionInfo();
339 if (ImGui::Button(
"Open user system ROMs folder...")) {
340 SDL_OpenURL(
strCat(
"file://", FileOperations::getUserDataDir(),
"/systemroms").c_str());
342 if (ImGui::Button(
"Open system wide system ROMs folder...")) {
343 SDL_OpenURL(
strCat(
"file://", FileOperations::getSystemDataDir(),
"/systemroms").c_str());
348std::vector<ImGuiMachine::MachineInfo>& ImGuiMachine::getAllMachines()
350 if (machineInfo.empty()) {
351 machineInfo = parseAllConfigFiles<MachineInfo>(manager,
"machines", {
"Manufacturer"sv,
"Product code"sv});
356static void amendConfigInfo(MSXMotherBoard& mb, ImGuiMachine::MachineInfo& info)
358 auto& configInfo = info.configInfo;
360 const auto& debugger = mb.getDebugger();
361 unsigned ramSize = 0;
362 for (
const auto& [name, debuggable] : debugger.getDebuggables()) {
363 if (debuggable->getDescription() ==
one_of(
"memory mapper",
"ram")) {
364 ramSize += debuggable->getSize();
367 configInfo.emplace_back(
"RAM size",
strCat(ramSize / 1024,
"kB"));
369 if (
auto* vdp =
dynamic_cast<VDP*
>(mb.findDevice(
"VDP"))) {
370 configInfo.emplace_back(
"VRAM size",
strCat(vdp->getVRAM().getSize() / 1024,
"kB"));
371 configInfo.emplace_back(
"VDP version", vdp->getVersionString());
374 if (
auto drives = RealDrive::getDrivesInUse(mb)) {
375 configInfo.emplace_back(
"Disk drives",
strCat(narrow<int>(drives->count())));
378 const auto& carts = mb.getSlotManager();
379 configInfo.emplace_back(
"Cartridge slots",
strCat(carts.getNumberOfSlots()));
382const std::string& ImGuiMachine::getTestResult(MachineInfo& info)
384 if (!info.testResult) {
385 info.testResult.emplace();
387 auto& reactor = manager.getReactor();
388 manager.executeDelayed([&reactor, &info]()
mutable {
391 MSXMotherBoard mb(reactor);
392 mb.getMSXCliComm().setSuppressMessages(
true);
393 mb.loadMachine(info.configName);
394 assert(info.testResult->empty());
395 amendConfigInfo(mb, info);
396 }
catch (MSXException& e) {
397 info.testResult =
e.getMessage();
401 return info.testResult.value();
404bool ImGuiMachine::printConfigInfo(MachineInfo& info)
406 const auto&
test = getTestResult(info);
407 bool ok =
test.empty();
409 im::Table(
"##machine-info", 2, ImGuiTableFlags_SizingFixedFit, [&]{
410 for (
const auto& [desc, value_] : info.configInfo) {
411 const auto& value = value_;
412 if (ImGui::TableNextColumn()) {
415 if (ImGui::TableNextColumn()) {
432ImGuiMachine::MachineInfo* ImGuiMachine::findMachineInfo(std::string_view config)
434 auto& allMachines = getAllMachines();
435 auto it =
ranges::find(allMachines, config, &MachineInfo::configName);
436 return (it != allMachines.end()) ? std::to_address(it) : nullptr;
void test(const IterableBitSet< N > &s, std::initializer_list< size_t > list)
void showMenu(MSXMotherBoard *motherBoard) override
void paint(MSXMotherBoard *motherBoard) override
void executeDelayed(std::function< void()> action)
std::string_view getMachineName() const
MSXCommandController & getMSXCommandController()
const HotKey & getHotKey() const
auto getMachineIDs() const
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
void TextUnformatted(const std::string &str)
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
void VisuallyDisabled(bool b, std::invocable<> auto next)
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
void ListBox(const char *label, const ImVec2 &size, std::invocable<> auto next)
void StyleColor(bool active, Args &&...args)
void TextWrapPos(float wrap_local_pos_x, std::invocable<> auto next)
bool Menu(const char *label, bool enabled, std::invocable<> auto next)
void Disabled(bool b, std::invocable<> auto next)
void ListClipper(size_t count, int forceIndex, float lineHeight, std::invocable< int > auto next)
This file implemented 3 utility functions:
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
void applyComboFilter(std::string_view key, std::string_view value, const std::vector< T > &items, std::vector< size_t > &indices)
void simpleToolTip(std::string_view desc)
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
ImU32 getColor(imColor col)
void applyDisplayNameFilter(std::string_view filterString, const std::vector< T > &items, std::vector< size_t > &indices)
TclObject makeTclList(Args &&... args)
auto find(InputRange &&range, const T &value)
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
constexpr bool contains(ITER first, ITER last, const VAL &val)
Check if a range contains a given value, using linear search.
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)