24#include <imgui_stdlib.h>
29using namespace std::literals;
36 for (
const auto& item : recentMachines) {
37 buf.appendf(
"machine.recent=%s\n", item.c_str());
43 if (name ==
"machine.recent") {
58 if (
auto* firmwareSwitch =
dynamic_cast<BooleanSetting*
>(controller.findSetting(
"firmwareswitch"))) {
59 Checkbox(hotKey,
"Firmware switch", *firmwareSwitch);
63 auto& pauseSetting = reactor.getGlobalSettings().getPauseSetting();
64 bool pause = pauseSetting.getBoolean();
66 ImGui::MenuItem(
"Pause", shortCut.c_str(), &pause)) {
67 pauseSetting.setBoolean(pause);
71 ImGui::MenuItem(
"Reset", shortCut.c_str(),
nullptr, motherBoard !=
nullptr)) {
75 auto& powerSetting = reactor.getGlobalSettings().getPowerSetting();
76 bool power = powerSetting.getBoolean();
78 ImGui::MenuItem(
"Power", shortCut.c_str(), &power)) {
79 powerSetting.setBoolean(power);
90 paintSelectMachine(motherBoard);
98void ImGuiMachine::paintSelectMachine(
const MSXMotherBoard* motherBoard)
100 ImGui::SetNextWindowSize(
gl::vec2{29, 26} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
104 auto currentInstance = reactor.getMachineID();
105 if (instances.size() > 1 || currentInstance.empty()) {
106 ImGui::TextUnformatted(
"Instances:"sv);
107 HelpMarker(
"Switch between different machine instances. Right-click to delete an instance.");
109 float height = (std::min(4.0f, float(instances.size())) + 0.25f) * ImGui::GetTextLineHeightWithSpacing();
110 im::ListBox(
"##empty", {-FLT_MIN, height}, [&]{
111 im::ID_for_range(instances.size(), [&](int i) {
112 const auto& name = instances[i];
113 bool isCurrent = name == currentInstance;
114 auto board = reactor.getMachine(name);
115 std::string display = [&]{
117 auto configName = board->getMachineName();
118 auto* info = findMachineInfo(configName);
120 auto time = (board->getCurrentTime() - EmuTime::zero()).toDouble();
121 return strCat(info->displayName,
" (", formatTime(time),
')');
123 return std::string(name);
126 if (ImGui::Selectable(display.c_str(), isCurrent)) {
127 manager.executeDelayed(makeTclList(
"activate_machine", name));
129 im::PopupContextItem(
"instance context menu", [&]{
130 if (ImGui::Selectable(
"Delete instance")) {
131 manager.executeDelayed(makeTclList(
"delete_machine", name));
142 auto* info = findMachineInfo(configName);
144 std::string display =
strCat(
"Current machine: ", info->displayName);
146 printConfigInfo(*info);
148 if (newMachineConfig.empty()) newMachineConfig = configName;
149 if (
auto& defaultMachine = reactor.getMachineSetting();
150 defaultMachine.getString() != configName) {
151 if (ImGui::Button(
"Make this the default machine")) {
152 defaultMachine.setValue(TclObject(configName));
154 simpleToolTip(
"Use this as the default MSX machine when openMSX starts.");
158 HelpMarker(
"If you select another machine than the default machine, a button will appear here to make that machine the default, i.e. the machine that is used when openMSX starts.");
165 auto showMachine = [&](MachineInfo& info,
bool doubleClickToSelect) {
166 bool ok = getTestResult(info).empty();
168 bool selected = info.configName == newMachineConfig;
169 if (ImGui::Selectable(info.displayName.c_str(), selected,
170 doubleClickToSelect ? ImGuiSelectableFlags_AllowDoubleClick: ImGuiSelectableFlags_None)) {
171 newMachineConfig = info.configName;
172 if (ok && (doubleClickToSelect ? ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) : true)) {
179 if (ImGui::IsWindowAppearing()) ImGui::SetScrollHereY();
181 if (ImGui::IsItemHovered(ImGuiHoveredFlags_DelayNormal | ImGuiHoveredFlags_NoSharedDelay | ImGuiHoveredFlags_Stationary)) {
183 printConfigInfo(info);
189 im::TreeNode(
"Recently used", recentMachines.
empty() ? ImGuiTreeNodeFlags_None : ImGuiTreeNodeFlags_DefaultOpen, [&]{
190 if (recentMachines.
empty()) {
193 im::Combo(
"##recent",
"Switch to recently used machine...", [&]{
194 for (
const auto& item : recentMachines) {
195 if (
auto* info = findMachineInfo(item)) {
196 showMachine(*info,
false);
200 simpleToolTip(
"Replace the current with the selected machine.");
206 auto& allMachines = getAllMachines();
207 std::string filterDisplay =
"filter";
208 if (!filterType.empty() || !filterRegion.empty() || !filterString.empty())
strAppend(filterDisplay,
':');
209 if (!filterType.empty())
strAppend(filterDisplay,
' ', filterType);
210 if (!filterRegion.empty())
strAppend(filterDisplay,
' ', filterRegion);
211 if (!filterString.empty())
strAppend(filterDisplay,
' ', filterString);
213 im::TreeNode(filterDisplay.c_str(), ImGuiTreeNodeFlags_DefaultOpen, [&]{
214 displayFilterCombo(filterType,
"Type", allMachines);
215 displayFilterCombo(filterRegion,
"Region", allMachines);
216 if (ImGui::IsWindowAppearing()) ImGui::SetKeyboardFocusHere();
217 ImGui::InputText(ICON_IGFD_FILTER, &filterString);
218 simpleToolTip(
"A list of substrings that must be part of the machine name.\n"
220 "For example: enter 'pa' to search for 'Panasonic' machines. "
221 "Then refine the search by appending '<space>st' to find the 'Panasonic FS-A1ST' machine.");
229 auto it =
ranges::find(filteredMachines, newMachineConfig,
230 [&](
auto idx) {
return allMachines[idx].configName; });
231 bool inFilteredList = it != filteredMachines.end();
232 int selectedIdx = inFilteredList ? narrow<int>(*it) : -1;
234 im::ListBox(
"##list", {-FLT_MIN, -ImGui::GetFrameHeightWithSpacing()}, [&]{
236 auto idx = filteredMachines[i];
237 auto& info = allMachines[idx];
238 showMachine(info, true);
243 if (!inFilteredList)
return false;
244 auto* info = findMachineInfo(newMachineConfig);
245 if (!info)
return false;
246 const auto&
test = getTestResult(*info);
250 if (ImGui::Button(
"Replace current machine")) {
253 simpleToolTip(
"Replace the current machine with the selected machine. "
254 "Alternatively you can also double click in the list above (in addition that also closes this window).");
255 ImGui::SameLine(0.0f, 10.0f);
256 if (ImGui::Button(
"New machine instance")) {
257 std::string script =
strCat(
258 "set id [create_machine]\n"
259 "set err [catch {${id}::load_machine ", newMachineConfig,
"} error_result]\n"
261 " delete_machine $id\n"
262 " error \"Error activating new machine: $error_result\"\n"
264 " activate_machine $id\n"
268 simpleToolTip(
"Create a new machine instance (next to the current machine). "
269 "Later you can switch between the different instances (like different tabs in a web browser).");
274void ImGuiMachine::paintTestHardware()
276 ImGui::SetNextWindowSize(
gl::vec2{41.0f, 32.5f} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
277 im::Window(
"Test MSX hardware", &showTestHardware, [&]{
278 auto formatNum = [](
size_t num, std::string_view text) {
280 return strCat(
"No ", text,
"###", text);
282 return strCat(num,
' ', text,
"###", text);
285 auto printList = [](
const auto& indices,
const auto& infos) {
286 auto n = narrow<int>(indices.size());
287 auto clamped = std::clamp(narrow_cast<float>(n), 2.5f, 7.5f);
288 gl::vec2 listSize{0.0f, clamped * ImGui::GetTextLineHeightWithSpacing()};
290 ImGui::SetNextItemWidth(-FLT_MIN);
293 auto& info = infos[indices[i]];
295 assert(info.testResult);
303 auto& allMachines = getAllMachines();
304 std::vector<size_t> workingMachines, nonWorkingMachines;
305 for (
auto [idx, info] :
enumerate(allMachines)) {
306 if (!info.testResult.has_value()) {
307 if (!doTest)
continue;
310 const auto& result = getTestResult(info);
311 (result.empty() ? workingMachines : nonWorkingMachines).push_back(idx);
313 bool allMachinesTested = allMachines.size() == (workingMachines.size() + nonWorkingMachines.size());
316 im::TreeNode(
"Machines", ImGuiTreeNodeFlags_DefaultOpen, [&]{
317 auto workingText = formatNum(workingMachines.size(),
"working machines");
319 printList(workingMachines, allMachines);
321 auto nonWorkingText = formatNum(nonWorkingMachines.size(),
"non-working machines");
322 int flags = nonWorkingMachines.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen;
324 printList(nonWorkingMachines, allMachines);
329 auto& allExtensions = manager.media->getAllExtensions();
330 std::vector<size_t> workingExtensions, nonWorkingExtensions;
331 for (
auto [idx, info] :
enumerate(allExtensions)) {
332 if (!info.testResult.has_value()) {
333 if (!doTest)
continue;
336 const auto& result = manager.media->getTestResult(info);
337 (result.empty() ? workingExtensions : nonWorkingExtensions).push_back(idx);
339 bool allExtensionsTested = allExtensions.size() == (workingExtensions.size() + nonWorkingExtensions.size());
342 im::TreeNode(
"Extensions", ImGuiTreeNodeFlags_DefaultOpen, [&]{
343 auto workingText = formatNum(workingExtensions.size(),
"working extensions");
345 printList(workingExtensions, allExtensions);
347 auto nonWorkingText = formatNum(nonWorkingExtensions.size(),
"non-working extensions");
348 int flags = nonWorkingExtensions.empty() ? 0 : ImGuiTreeNodeFlags_DefaultOpen;
350 printList(nonWorkingExtensions, allExtensions);
356 if (!nonWorkingMachines.empty() || !nonWorkingExtensions.empty()) {
357 im::Disabled(!allMachinesTested || !allExtensionsTested, [&]{
358 if (ImGui::Button(
"Copy list of non-working hardware to clipboard")) {
360 auto print = [&](std::string_view label,
const auto& indices,
const auto& infos) {
361 if (!indices.empty()) {
362 strAppend(result,
"Non-working ", label,
":\n");
363 for (
auto idx : indices) {
364 const auto& info = infos[idx];
365 strAppend(result,
'\t', info.displayName,
" (", info.configName,
")\n",
366 "\t\t", *info.testResult,
'\n');
371 print(
"machines", nonWorkingMachines, allMachines);
372 print(
"extensions", nonWorkingExtensions, allExtensions);
373 ImGui::SetClipboardText(result.c_str());
378 im::Disabled(!allMachinesTested || !allExtensionsTested, [&]{
379 if (ImGui::Button(
"Rerun test")) {
380 manager.media->resetExtensionInfo();
389 if (ImGui::Button(
"Open user system ROMs folder...")) {
390 SDL_OpenURL(
strCat(
"file://", FileOperations::getUserDataDir(),
"/systemroms").c_str());
392 if (ImGui::Button(
"Open system wide system ROMs folder...")) {
393 SDL_OpenURL(
strCat(
"file://", FileOperations::getSystemDataDir(),
"/systemroms").c_str());
398std::vector<ImGuiMachine::MachineInfo>& ImGuiMachine::getAllMachines()
400 if (machineInfo.empty()) {
401 machineInfo = parseAllConfigFiles<MachineInfo>(manager,
"machines", {
"Manufacturer"sv,
"Product code"sv});
406static void amendConfigInfo(MSXMotherBoard& mb, ImGuiMachine::MachineInfo& info)
408 auto& configInfo = info.configInfo;
410 const auto& debugger = mb.getDebugger();
411 unsigned ramSize = 0;
412 for (
const auto& [name, debuggable] : debugger.getDebuggables()) {
413 if (debuggable->getDescription() ==
one_of(
"memory mapper",
"ram")) {
414 ramSize += debuggable->getSize();
417 configInfo.emplace_back(
"RAM size",
strCat(ramSize / 1024,
"kB"));
419 if (
auto* vdp =
dynamic_cast<VDP*
>(mb.findDevice(
"VDP"))) {
420 configInfo.emplace_back(
"VRAM size",
strCat(vdp->getVRAM().getSize() / 1024,
"kB"));
421 configInfo.emplace_back(
"VDP version", vdp->getVersionString());
424 if (
auto drives = RealDrive::getDrivesInUse(mb)) {
425 configInfo.emplace_back(
"Disk drives",
strCat(narrow<int>(drives->count())));
428 const auto& carts = mb.getSlotManager();
429 configInfo.emplace_back(
"Cartridge slots",
strCat(carts.getNumberOfSlots()));
432const std::string& ImGuiMachine::getTestResult(MachineInfo& info)
434 if (!info.testResult) {
435 info.testResult.emplace();
437 auto& reactor = manager.getReactor();
438 manager.executeDelayed([&reactor, &info]()
mutable {
441 MSXMotherBoard mb(reactor);
442 mb.getMSXCliComm().setSuppressMessages(
true);
443 mb.loadMachine(info.configName);
444 assert(info.testResult->empty());
445 amendConfigInfo(mb, info);
446 }
catch (MSXException& e) {
447 info.testResult =
e.getMessage();
451 return info.testResult.value();
454bool ImGuiMachine::printConfigInfo(MachineInfo& info)
456 const auto&
test = getTestResult(info);
457 bool ok =
test.empty();
459 im::Table(
"##machine-info", 2, ImGuiTableFlags_SizingFixedFit, [&]{
460 for (
const auto& [desc, value_] : info.configInfo) {
461 const auto& value = value_;
462 if (ImGui::TableNextColumn()) {
465 if (ImGui::TableNextColumn()) {
482ImGuiMachine::MachineInfo* ImGuiMachine::findMachineInfo(std::string_view config)
484 auto& allMachines = getAllMachines();
485 auto it =
ranges::find(allMachines, config, &MachineInfo::configName);
486 return (it != allMachines.end()) ? std::to_address(it) : nullptr;
void test(const IterableBitSet< N > &s, std::initializer_list< size_t > list)
void loadLine(std::string_view name, zstring_view value) override
void showMenu(MSXMotherBoard *motherBoard) override
void save(ImGuiTextBuffer &buf) 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
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
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 Combo(const char *label, const char *preview_value, ImGuiComboFlags 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 Indent(float indent_w, std::invocable<> auto next)
void ListClipper(size_t count, int forceIndex, float lineHeight, std::invocable< int > auto next)
void ItemTooltip(std::invocable<> 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 addRecentItem(circular_buffer< T > &recentItems, const T &item)
void simpleToolTip(std::string_view desc)
std::string getShortCutForCommand(const HotKey &hotkey, std::string_view command)
void HelpMarker(std::string_view desc)
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))> >
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)