32#include <CustomFont.h>
34#include <imgui_stdlib.h>
41using namespace std::literals;
50 if (item.
name.empty())
return;
51 buf.appendf(
"%s.name=%s\n", name.c_str(), item.
name.c_str());
53 buf.appendf(
"%s.patch=%s\n", name.c_str(), patch.c_str());
56 buf.appendf(
"%s.romType=%s\n", name.c_str(),
61 saveItem(group.
edit, name);
62 auto recentName =
tmpStrCat(name,
".recent");
63 for (
const auto& item : group.
recent) {
64 saveItem(item, recentName);
71 for (
const auto& info : diskMediaInfo) {
72 saveGroup(info.groups[0],
tmpStrCat(name,
".image"));
73 saveGroup(info.groups[1],
tmpStrCat(name,
".dirAsDsk"));
76 if (info.show) buf.appendf(
"%s.show=1\n", name.c_str());
81 for (
const auto& info : cartridgeMediaInfo) {
82 saveGroup(info.groups[0],
tmpStrCat(name,
".rom"));
83 saveGroup(info.groups[1],
tmpStrCat(name,
".extension"));
85 if (info.show) buf.appendf(
"%s.show=1\n", name.c_str());
90 for (
const auto& info : hdMediaInfo) {
91 saveGroup(info, name);
96 for (
const auto& info : cdMediaInfo) {
97 saveGroup(info, name);
101 if (cassetteMediaInfo.
show) buf.append(
"cassette.show=1\n");
102 saveGroup(cassetteMediaInfo.
group,
"cassette");
104 saveGroup(extensionMediaInfo,
"extension");
105 saveGroup(laserdiscMediaInfo,
"laserdisc");
110 auto get = [&](std::string_view prefix,
auto& array) -> std::remove_reference_t<
decltype(array[0])>* {
111 if ((name.size() >= (prefix.size() + 2)) && name.starts_with(prefix) && (name[prefix.size() + 1] ==
'.')) {
112 char c = name[prefix.size()];
113 if ((
'a' <= c) && (c < char(
'a' + array.size()))) {
114 return &array[c -
'a'];
119 auto loadItem = [&](
MediaItem& item, std::string_view suffix) {
120 if (suffix ==
"name") {
122 }
else if (suffix ==
"patch") {
124 }
else if (suffix ==
"romType") {
130 auto loadGroup = [&](
ItemGroup& group, std::string_view suffix) {
131 if (suffix.starts_with(
"recent.")) {
132 if (suffix ==
"recent.name" && !group.
recent.full()) {
135 if (!group.
recent.empty()) {
136 loadItem(group.
recent.back(), suffix.substr(7));
139 loadItem(group.
edit, suffix);
145 }
else if (
auto* disk = get(
"disk", diskMediaInfo)) {
146 auto suffix = name.substr(6);
147 if (suffix.starts_with(
"image.")) {
148 loadGroup(disk->groups[0], suffix.substr(6));
149 }
else if (suffix.starts_with(
"dirAsDsk.")) {
150 loadGroup(disk->groups[1], suffix.substr(9));
151 }
else if (suffix ==
"select") {
152 if (
auto i = StringOp::stringTo<int>(value)) {
157 }
else if (suffix ==
"show") {
160 }
else if (
auto* cart = get(
"cart", cartridgeMediaInfo)) {
161 auto suffix = name.substr(6);
162 if (suffix.starts_with(
"rom.")) {
163 loadGroup(cart->groups[0], suffix.substr(4));
164 }
else if (suffix.starts_with(
"extension.")) {
165 loadGroup(cart->groups[1], suffix.substr(10));
166 }
else if (suffix ==
"select") {
167 if (
auto i = StringOp::stringTo<int>(value)) {
172 }
else if (suffix ==
"show") {
175 }
else if (
auto* hd = get(
"hd", hdMediaInfo)) {
176 loadGroup(*hd, name.substr(4));
177 }
else if (
auto* cd = get(
"cd", cdMediaInfo)) {
178 loadGroup(*cd, name.substr(4));
179 }
else if (name.starts_with(
"cassette.")) {
180 auto suffix = name.substr(9);
181 if (suffix ==
"show") {
184 loadGroup(cassetteMediaInfo.
group, suffix);
186 }
else if (name.starts_with(
"extension.")) {
187 loadGroup(extensionMediaInfo, name.substr(10));
188 }
else if (name.starts_with(
"laserdisc.")) {
189 loadGroup(laserdiscMediaInfo, name.substr(10));
193static std::string buildFilter(std::string_view description, std::span<const std::string_view> extensions)
195 auto formatExtensions = [&]() -> std::string {
196 if (extensions.size() <= 3) {
198 [](
const auto& ext) {
return strCat(
"*.", ext); }),
201 return join(extensions,
',');
205 description,
" (", formatExtensions(),
"){",
207 [](
const auto& ext) {
return strCat(
'.', ext); }),
217static std::string romFilter()
222static std::string cassetteFilter()
227static std::string hdFilter()
229 return buildFilter(
"Hard disk images", std::array{
"dsk"sv});
232static std::string cdFilter()
234 return buildFilter(
"CDROM images", std::array{
"iso"sv});
237template<std::invocable<const std::
string&> DisplayFunc = std::
identity>
238static std::string display(
const ImGuiMedia::MediaItem& item, DisplayFunc displayFunc = {})
240 std::string result = displayFunc(item.name);
244 if (
auto n = item.ipsPatches.size()) {
245 strAppend(result,
" (+", n,
" patch", (n == 1 ?
"" :
"es"),
')');
252 if (extensionInfo.empty()) {
253 extensionInfo = parseAllConfigFiles<ExtensionInfo>(
manager,
"extensions", {
"Manufacturer"sv,
"Product code"sv,
"Name"sv});
255 return extensionInfo;
260 extensionInfo.clear();
277 std::optional<MSXMotherBoard> mb;
281 mb->getMSXCliComm().setSuppressMessages(
true);
283 mb->loadMachine(
"C-BIOS_MSX1");
288 if (
const auto* current = reactor.getMotherBoard()) {
290 mb->getMSXCliComm().setSuppressMessages(true);
291 mb->loadMachine(std::string(current->getMachineName()));
300 auto ext = mb->loadExtension(info.
configName,
"any");
301 mb->insertExtension(info.
configName, std::move(ext));
316 return (it != allExtensions.end()) ? std::to_address(it) :
nullptr;
322 return info ? info->displayName
323 : std::string(config);
329 if (
auto sha1 = reactor.getFilePool().getSha1Sum(filename)) {
331 if (
const auto* romInfo = database.fetchRomInfo(*sha1)) {
332 if (
auto title = romInfo->getTitle(database.getBufferStart());
334 return std::string(title);
362 std::string result = slot
372 std::string_view display;
374 display = result->getListIndexUnchecked(1).getString();
376 return display.empty() ?
"Empty"
381void ImGuiMedia::printExtensionInfo(ExtensionInfo& info)
384 bool ok =
test.empty();
387 ImGui::TableSetupColumn(
"description", ImGuiTableColumnFlags_WidthFixed);
388 ImGui::TableSetupColumn(
"value", ImGuiTableColumnFlags_WidthStretch);
390 for (
const auto& [desc, value_] : info.configInfo) {
391 const auto& value = value_;
392 if (ImGui::TableNextColumn()) {
395 if (ImGui::TableNextColumn()) {
411void ImGuiMedia::extensionTooltip(ExtensionInfo& info)
414 printExtensionInfo(info);
418bool ImGuiMedia::drawExtensionFilter()
420 std::string filterDisplay =
"filter";
421 if (!filterType.empty() || !filterString.empty())
strAppend(filterDisplay,
':');
422 if (!filterType.empty())
strAppend(filterDisplay,
' ', filterType);
423 if (!filterString.empty())
strAppend(filterDisplay,
' ', filterString);
425 bool newFilterOpen = filterOpen;
426 im::TreeNode(filterDisplay.c_str(), &newFilterOpen, [&]{
427 displayFilterCombo(filterType,
"Type", getAllExtensions());
428 ImGui::InputText(ICON_IGFD_SEARCH, &filterString);
429 simpleToolTip(
"A list of substrings that must be part of the extension.\n"
431 "For example: enter 'ko' to search for 'Konami' extensions. "
432 "Then refine the search by appending '<space>sc' to find the 'Konami SCC' extension.");
434 bool changed = filterOpen != newFilterOpen;
435 filterOpen = newFilterOpen;
441 im::Menu(
"Media", motherBoard !=
nullptr, [&]{
444 enum class Status {
NONE, ITEM, SEPARATOR };
446 Status status =
NONE;
448 auto endGroup = [&] {
449 if (status == ITEM) status = SEPARATOR;
451 auto elementInGroup = [&] {
452 if (status == SEPARATOR) {
458 auto showCurrent = [&](
TclObject current, std::string_view type) {
459 if (current.
empty()) {
467 auto showRecent = [&](std::string_view mediaName,
ItemGroup& group,
468 function_ref<std::string(
const std::string&)> displayFunc = std::identity{},
469 const std::function<void(
const std::string&)>& toolTip = {}) {
470 if (!group.recent.empty()) {
474 for (
const auto& item : group.recent) {
475 auto d =
strCat(display(item, displayFunc),
"##", count++);
476 if (ImGui::MenuItem(d.c_str())) {
478 insertMedia(mediaName, group);
480 if (toolTip) toolTip(item.name);
490 bool anySlot =
false;
492 if (!slotManager.slotExists(i))
continue;
494 auto displayName =
strCat(
"Cartridge Slot ",
char(
'A' + i));
495 ImGui::MenuItem(displayName.c_str(),
nullptr, &cartridgeMediaInfo[i].show);
499 ImGui::TextDisabled(
"No cartridge slots present");
506 auto mediaName =
"ext"sv;
507 auto& group = extensionMediaInfo;
510 HelpMarker(
"Note that some extensions are I/O only and will not occupy any cartridge slot when inserted. "
511 "These can only be removed via the 'Media > Extensions > Remove' menu. "
512 "To insert (non I/O-only) extensions in a specific slot, use the 'Media > Cartridge Slot' menu.");
513 drawExtensionFilter();
520 float width = 40.0f * ImGui::GetFontSize();
521 float height = 10.25f * ImGui::GetTextLineHeightWithSpacing();
524 auto& ext = allExtensions[filteredExtensions[i]];
525 bool ok = getTestResult(ext).empty();
526 im::StyleColor(!ok, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
527 if (ImGui::Selectable(ext.displayName.c_str())) {
528 group.edit.name = ext.configName;
529 insertMedia(mediaName, group);
530 ImGui::CloseCurrentPopup();
532 extensionTooltip(ext);
538 showRecent(mediaName, group,
539 [
this](
const std::string& config) {
542 [
this](
const std::string& e) {
544 extensionTooltip(*info);
552 im::Menu(
"Remove", [&]{
554 for (const auto& ext : extensions) {
555 auto name = strCat(slotAndNameForHardwareConfig(slotManager, *ext),
"##", count++);
556 if (ImGui::Selectable(name.c_str())) {
557 manager.executeDelayed(makeTclList(
"remove_extension", ext->getName()));
559 if (auto* info = findExtensionInfo(ext->getConfigName())) {
560 extensionTooltip(*info);
571 bool anyDrive =
false;
573 if (!(*drivesInUse)[i])
continue;
575 auto displayName =
strCat(
"Disk Drive ",
char(
'A' + i));
576 ImGui::MenuItem(displayName.c_str(),
nullptr, &diskMediaInfo[i].show);
577 simpleToolTip([&] {
return displayNameForDriveContent(i); });
580 ImGui::TextDisabled(
"No disk drives present");
586 if (
auto cmdResult = manager.execute(TclObject(
"cassetteplayer"))) {
587 ImGui::MenuItem(
"Tape Deck",
nullptr, &cassetteMediaInfo.show);
589 auto tip = cmdResult->getListIndexUnchecked(1).getString();
590 return !tip.empty() ? std::string(tip) :
"Empty";
593 ImGui::TextDisabled(
"No cassette port present");
599 std::string hdName =
"hdX";
601 if (!(*hdInUse)[i])
continue;
602 hdName.back() = char(
'a' + i);
603 auto displayName =
strCat(
"Hard Disk ",
char(
'A' + i));
604 if (
auto cmdResult = manager.execute(TclObject(hdName))) {
606 auto& group = hdMediaInfo[i];
608 auto currentImage = cmdResult->getListIndex(interp, 1);
609 showCurrent(currentImage,
"hard disk");
610 bool powered = motherBoard->isPowered();
611 im::Disabled(powered, [&]{
612 if (ImGui::MenuItem(
"Select hard disk image...")) {
613 manager.openFile->selectFile(
614 "Select image for " + displayName,
616 [this, &group, hdName](const auto& fn) {
617 group.edit.name = fn;
618 this->insertMedia(hdName, group);
620 currentImage.getString());
624 HelpMarker(
"Hard disk image cannot be switched while the MSX is powered on.");
627 showRecent(hdName, group);
635 auto cdInUse = IDECDROM::getDrivesInUse(*motherBoard);
636 std::string cdName =
"cdX";
638 if (!(*cdInUse)[i])
continue;
639 cdName.back() = char(
'a' + i);
640 auto displayName =
strCat(
"CDROM Drive ",
char(
'A' + i));
641 if (
auto cmdResult = manager.execute(TclObject(cdName))) {
643 auto& group = cdMediaInfo[i];
645 auto currentImage = cmdResult->getListIndex(interp, 1);
646 showCurrent(currentImage,
"CDROM");
647 if (ImGui::MenuItem(
"Eject", nullptr, false, !currentImage.empty())) {
648 manager.executeDelayed(makeTclList(cdName,
"eject"));
650 if (ImGui::MenuItem(
"Insert CDROM image...")) {
651 manager.openFile->selectFile(
652 "Select CDROM image for " + displayName,
654 [
this, &group, cdName](
const auto& fn) {
655 group.edit.name = fn;
656 this->insertMedia(cdName, group);
658 currentImage.getString());
660 showRecent(cdName, group);
667 if (
auto cmdResult = manager.execute(TclObject(
"laserdiscplayer"))) {
670 auto currentImage = cmdResult->getListIndex(interp, 1);
671 showCurrent(currentImage,
"laserdisc");
672 if (ImGui::MenuItem(
"eject",
nullptr,
false, !currentImage.empty())) {
673 manager.executeDelayed(
makeTclList(
"laserdiscplayer",
"eject"));
675 if (ImGui::MenuItem(
"Insert LaserDisc image...")) {
676 manager.openFile->selectFile(
677 "Select LaserDisc image",
678 buildFilter(
"LaserDisc images", std::array<std::string_view, 1>{
"ogv"}),
679 [
this](
const auto& fn) {
680 laserdiscMediaInfo.edit.name = fn;
681 this->insertMedia(
"laserdiscplayer", laserdiscMediaInfo);
683 currentImage.getString());
685 showRecent(
"laserdiscplayer", laserdiscMediaInfo);
694 if (!motherBoard)
return;
696 auto drivesInUse = RealDrive::getDrivesInUse(*motherBoard);
697 for (
auto i :
xrange(RealDrive::MAX_DRIVES)) {
698 if (!(*drivesInUse)[i])
continue;
699 if (diskMediaInfo[i].show) {
705 for (
auto i :
xrange(CartridgeSlotManager::MAX_SLOTS)) {
706 if (!slotManager.slotExists(i))
continue;
707 if (cartridgeMediaInfo[i].show) {
712 if (cassetteMediaInfo.show) {
713 if (
auto cmdResult = manager.execute(
TclObject(
"cassetteplayer"))) {
714 cassetteMenu(*cmdResult);
724static void printPatches(
const TclObject& patches)
726 if (!patches.empty()) {
729 for (
const auto& patch : patches) {
736static std::string leftClip(std::string_view s,
float maxWidth)
739 if (fullWidth <= maxWidth)
return std::string(s);
742 if (maxWidth <= 0.0f)
return "...";
747 return strCat(
"...", s.substr(len - num));
750bool ImGuiMedia::selectRecent(ItemGroup& group,
function_ref<std::string(
const std::string&)> displayFunc,
float width)
const
752 bool interacted =
false;
753 ImGui::SetNextItemWidth(-width);
754 const auto& style = ImGui::GetStyle();
755 auto textWidth = ImGui::GetContentRegionAvail().x - (3.0f * style.FramePadding.x + ImGui::GetFrameHeight() + width);
756 auto preview = leftClip(displayFunc(group.edit.name), textWidth);
757 im::Combo(
"##recent", preview.c_str(), [&]{
759 for (const auto& item : group.recent) {
760 auto d = strCat(display(item, displayFunc),
"##", count++);
761 if (ImGui::Selectable(d.c_str())) {
767 interacted |= ImGui::IsItemActive();
771static float calcButtonWidth(std::string_view text1,
const char* text2)
773 const auto& style = ImGui::GetStyle();
774 float width = style.ItemSpacing.x + 2.0f * style.FramePadding.x +
ImGui::CalcTextSize(text1).x;
781bool ImGuiMedia::selectImage(ItemGroup& group,
const std::string& title,
783 function_ref<std::string(
const std::string&)> displayFunc,
784 const std::function<
void()>& createNewCallback)
786 bool interacted =
false;
788 auto width = calcButtonWidth(ICON_IGFD_FOLDER_OPEN, createNewCallback ? ICON_IGFD_ADD : nullptr);
789 interacted |= selectRecent(group, displayFunc, width);
790 if (createNewCallback) {
792 if (ImGui::Button(ICON_IGFD_ADD)) {
799 if (ImGui::Button(ICON_IGFD_FOLDER_OPEN)) {
801 manager.openFile->selectFile(
804 [&](
const auto& fn) { group.edit.name = fn; },
812bool ImGuiMedia::selectDirectory(ItemGroup& group,
const std::string& title,
zstring_view current,
813 const std::function<
void()>& createNewCallback)
815 bool interacted =
false;
817 auto width = calcButtonWidth(ICON_IGFD_FOLDER_OPEN, createNewCallback ? ICON_IGFD_ADD : nullptr);
818 interacted |= selectRecent(group, std::identity{}, width);
819 if (createNewCallback) {
821 if (ImGui::Button(ICON_IGFD_ADD)) {
828 if (ImGui::Button(ICON_IGFD_FOLDER_OPEN)) {
830 manager.openFile->selectDirectory(
832 [&](
const auto& fn) { group.edit.name = fn; },
840bool ImGuiMedia::selectMapperType(
const char* label,
RomType& romType)
842 bool interacted =
false;
844 constexpr const char* autoStr =
"auto detect";
845 std::string current = isAutoDetect ? autoStr : std::string(RomInfo::romTypeToName(romType));
847 if (ImGui::Selectable(autoStr, isAutoDetect)) {
849 romType = ROM_UNKNOWN;
852 for (
const auto& romInfo : RomInfo::getRomTypeInfo()) {
853 bool selected = romType ==
static_cast<RomType>(count);
854 if (ImGui::Selectable(std::string(romInfo.name).c_str(), selected)) {
856 romType =
static_cast<RomType>(count);
862 interacted |= ImGui::IsItemActive();
866bool ImGuiMedia::selectPatches(MediaItem& item,
int& patchIndex)
868 bool interacted =
false;
869 std::string patchesTitle =
"IPS patches";
870 if (!item.ipsPatches.empty()) {
871 strAppend(patchesTitle,
" (", item.ipsPatches.size(),
')');
875 const auto& style = ImGui::GetStyle();
876 auto width = style.ItemSpacing.x + 2.0f * style.FramePadding.x + ImGui::CalcTextSize(
"Remove"sv).x;
877 ImGui::SetNextItemWidth(-width);
879 im::ListBox(
"##", [&]{
881 for (const auto& patch : item.ipsPatches) {
882 auto preview = leftClip(patch, ImGui::GetContentRegionAvail().x);
883 if (ImGui::Selectable(strCat(preview,
"##", count).c_str(), count == patchIndex)) {
893 if (ImGui::Button(
"Add")) {
895 manager.openFile->selectFile(
896 "Select disk IPS patch",
897 buildFilter(
"IPS patches", std::array<std::string_view, 1>{
"ips"}),
898 [&](
const std::string& ips) {
899 patchIndex = narrow<int>(item.ipsPatches.size());
900 item.ipsPatches.push_back(ips);
903 auto size = narrow<int>(item.ipsPatches.size());
904 im::Disabled(patchIndex < 0 || patchIndex >= size, [&] {
905 if (ImGui::Button(
"Remove")) {
907 item.ipsPatches.erase(item.ipsPatches.begin() + patchIndex);
910 if (ImGui::ArrowButton(
"up", ImGuiDir_Up)) {
911 std::swap(item.ipsPatches[patchIndex], item.ipsPatches[patchIndex - 1]);
916 if (ImGui::ArrowButton(
"down", ImGuiDir_Down)) {
917 std::swap(item.ipsPatches[patchIndex], item.ipsPatches[patchIndex + 1]);
927bool ImGuiMedia::insertMediaButton(std::string_view mediaName, ItemGroup& group,
bool* showWindow)
929 bool clicked =
false;
931 const auto& style = ImGui::GetStyle();
932 auto width = 4.0f * style.FramePadding.x + style.ItemSpacing.x +
933 ImGui::CalcTextSize(
"Apply"sv).x + ImGui::CalcTextSize(
"Ok"sv).x;
934 ImGui::SetCursorPosX(ImGui::GetContentRegionAvail().x - width + style.WindowPadding.x);
935 clicked |= ImGui::Button(
"Apply");
937 if (ImGui::Button(
"Ok")) {
942 insertMedia(mediaName, group);
948TclObject ImGuiMedia::showDiskInfo(std::string_view mediaName, DiskMediaInfo& info)
950 TclObject currentTarget;
951 auto cmdResult = manager.execute(
makeTclList(
"machine_info",
"media", mediaName));
952 if (!cmdResult)
return currentTarget;
954 int selectType = [&]{
957 auto s = type->getString();
959 return SELECT_EMPTY_DISK;
960 }
else if (s ==
"ramdsk") {
961 return SELECT_RAMDISK;
962 }
else if (s ==
"dirasdisk") {
963 return SELECT_DIR_AS_DISK;
966 return SELECT_DISK_IMAGE;
969 std::string_view typeStr = [&]{
970 switch (selectType) {
971 case SELECT_EMPTY_DISK:
return "No disk inserted";
972 case SELECT_RAMDISK:
return "RAM disk";
973 case SELECT_DIR_AS_DISK:
return "Dir as disk:";
974 case SELECT_DISK_IMAGE:
return "Disk image:";
978 bool disableEject = selectType == SELECT_EMPTY_DISK;
979 bool detailedInfo = selectType ==
one_of(SELECT_DIR_AS_DISK, SELECT_DISK_IMAGE);
980 auto currentPatches = getPatches(*cmdResult);
982 bool copyCurrent =
false;
984 copyCurrent = ImGui::SmallButton(
"Current disk");
985 HelpMarker(
"Press to copy current disk to 'Select new disk' section.");
992 currentTarget = *target;
995 ImGui::GetContentRegionAvail().x));
997 std::string statusLine;
998 auto add = [&](std::string_view s) {
999 if (statusLine.empty()) {
1006 if (ro->getOptionalBool().value_or(
false)) {
1011 add(doubleSided->getOptionalBool().value_or(
true) ?
"double-sided" :
"single-sided");
1014 add(
tmpStrCat(
size->getOptionalInt().value_or(0) / 1024,
"kB"));
1016 if (!statusLine.empty()) {
1019 printPatches(currentPatches);
1022 if (copyCurrent && selectType != SELECT_EMPTY_DISK) {
1023 info.select = selectType;
1024 auto& edit = info.groups[selectType].edit;
1025 edit.name = currentTarget.getString();
1026 edit.ipsPatches = to_vector<std::string>(currentPatches);
1029 if (ImGui::Button(
"Eject")) {
1030 manager.executeDelayed(
makeTclList(mediaName,
"eject"));
1034 return currentTarget;
1037void ImGuiMedia::printDatabase(
const RomInfo& romInfo,
const char* buf)
1039 auto printRow = [](std::string_view description, std::string_view value) {
1040 if (value.empty())
return;
1041 if (ImGui::TableNextColumn()) {
1044 if (ImGui::TableNextColumn()) {
1049 printRow(
"Title", romInfo.
getTitle(buf));
1050 printRow(
"Year", romInfo.
getYear(buf));
1051 printRow(
"Company", romInfo.
getCompany(buf));
1052 printRow(
"Country", romInfo.
getCountry(buf));
1056 std::string result =
"Unmodified dump";
1058 strAppend(result,
" (confirmed by ", str,
')');
1062 return std::string(str);
1065 printRow(
"Status", status);
1066 printRow(
"Remark", romInfo.
getRemark(buf));
1072 ImGui::TableSetupColumn(
"description", ImGuiTableColumnFlags_WidthFixed);
1073 ImGui::TableSetupColumn(
"value", ImGuiTableColumnFlags_WidthStretch);
1075 if (ImGui::TableNextColumn()) {
1078 if (ImGui::TableNextColumn()) {
1083 const auto* romInfo = [&]() ->
const RomInfo* {
1085 if (
const auto* info = database.fetchRomInfo(Sha1Sum(actual->getString()))) {
1090 if (
const auto* info = database.fetchRomInfo(Sha1Sum(original->getString()))) {
1097 ImGuiMedia::printDatabase(*romInfo, database.getBufferStart());
1100 std::string mapperStr{RomInfo::romTypeToName(romType)};
1102 if (
auto dbType = romInfo->getRomType();
1104 strAppend(mapperStr,
" (database: ", RomInfo::romTypeToName(dbType),
')');
1107 if (ImGui::TableNextColumn()) {
1110 if (ImGui::TableNextColumn()) {
1116TclObject ImGuiMedia::showCartridgeInfo(std::string_view mediaName, CartridgeMediaInfo& info,
int slot)
1118 TclObject currentTarget;
1120 if (!cmdResult)
return currentTarget;
1122 int selectType = [&]{
1124 auto s = type->getString();
1125 if (s ==
"extension") {
1126 return SELECT_EXTENSION;
1129 return SELECT_ROM_IMAGE;
1132 return SELECT_EMPTY_SLOT;
1135 bool disableEject = selectType == SELECT_EMPTY_SLOT;
1136 auto currentPatches = getPatches(*cmdResult);
1138 bool copyCurrent =
false;
1140 copyCurrent = ImGui::SmallButton(
"Current cartridge");
1148 if (selectType == SELECT_EMPTY_SLOT) {
1151 currentTarget = *target;
1152 if (selectType == SELECT_EXTENSION) {
1153 if (
auto* i = findExtensionInfo(target->getString())) {
1154 printExtensionInfo(*i);
1156 }
else if (selectType == SELECT_ROM_IMAGE) {
1158 currentRomType = RomInfo::nameToRomType(mapper->getString());
1160 printRomInfo(manager, *cmdResult, target->getString(), currentRomType);
1161 printPatches(currentPatches);
1165 if (copyCurrent && selectType != SELECT_EMPTY_SLOT) {
1166 info.select = selectType;
1167 auto& edit = info.groups[selectType].edit;
1168 edit.name = currentTarget.getString();
1169 edit.ipsPatches = to_vector<std::string>(currentPatches);
1170 edit.romType = currentRomType;
1173 if (ImGui::Button(
"Eject")) {
1178 return currentTarget;
1181void ImGuiMedia::diskMenu(
int i)
1183 auto& info = diskMediaInfo[i];
1184 auto mediaName =
strCat(
"disk",
char(
'a' + i));
1185 auto displayName =
strCat(
"Disk Drive ",
char(
'A' + i));
1186 ImGui::SetNextWindowSize(
gl::vec2{29, 22} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
1187 im::Window(displayName.c_str(), &info.show, [&]{
1188 auto current = showDiskInfo(mediaName, info);
1189 im::Child(
"select", {0, -ImGui::GetFrameHeightWithSpacing()}, [&]{
1190 ImGui::TextUnformatted(
"Select new disk"sv);
1192 ImGui::RadioButton(
"disk image", &info.select, SELECT_DISK_IMAGE);
1193 im::VisuallyDisabled(info.select != SELECT_DISK_IMAGE, [&]{
1195 auto& group = info.groups[SELECT_DISK_IMAGE];
1196 auto createNew = [&]{
1197 manager.openFile->selectNewFile(
1198 "Select name for new blank disk image",
1199 "Disk images (*.dsk){.dsk}",
1200 [&](const auto& fn) {
1201 group.edit.name = fn;
1202 auto& diskManipulator = manager.getReactor().getDiskManipulator();
1204 diskManipulator.create(fn, MSXBootSectorType::DOS2, {1440});
1205 } catch (MSXException& e) {
1206 manager.printError(
"Couldn't create new disk image: ", e.getMessage());
1209 current.getString());
1211 bool interacted = selectImage(
1212 group, strCat(
"Select disk image for ", displayName), &diskFilter,
1213 current.getString(), std::identity{}, createNew);
1214 interacted |= selectPatches(group.edit, group.patchIndex);
1215 if (interacted) info.select = SELECT_DISK_IMAGE;
1218 ImGui::RadioButton(
"dir as disk", &info.select, SELECT_DIR_AS_DISK);
1221 auto& group = info.groups[SELECT_DIR_AS_DISK];
1222 auto createNew = [&]{
1223 manager.openFile->selectNewFile(
1224 "Select name for new empty directory",
1226 [&](const auto& fn) {
1227 group.edit.name = fn;
1229 FileOperations::mkdirp(fn);
1230 } catch (MSXException& e) {
1231 manager.printError(
"Couldn't create directory: ", e.getMessage());
1234 current.getString());
1236 bool interacted = selectDirectory(
1237 group, strCat(
"Select directory for ", displayName),
1238 current.getString(), createNew);
1239 if (interacted) info.select = SELECT_DIR_AS_DISK;
1242 ImGui::RadioButton(
"RAM disk", &info.select, SELECT_RAMDISK);
1244 insertMediaButton(mediaName, info.groups[info.select], &info.show);
1248void ImGuiMedia::cartridgeMenu(
int cartNum)
1250 auto& info = cartridgeMediaInfo[cartNum];
1251 auto displayName =
strCat(
"Cartridge Slot ",
char(
'A' + cartNum));
1252 ImGui::SetNextWindowSize(
gl::vec2{37, 30} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
1253 im::Window(displayName.c_str(), &info.show, [&]{
1254 auto cartName = strCat(
"cart", char(
'a' + cartNum));
1255 auto extName = strCat(
"ext", char(
'a' + cartNum));
1257 auto current = showCartridgeInfo(cartName, info, cartNum);
1259 im::Child(
"select", {0, -ImGui::GetFrameHeightWithSpacing()}, [&]{
1260 ImGui::TextUnformatted(
"Select new cartridge:"sv);
1262 ImGui::RadioButton(
"ROM image", &info.select, SELECT_ROM_IMAGE);
1263 im::VisuallyDisabled(info.select != SELECT_ROM_IMAGE, [&]{
1265 auto& group = info.groups[SELECT_ROM_IMAGE];
1266 auto& item = group.edit;
1267 bool interacted = selectImage(
1268 group, strCat(
"Select ROM image for ", displayName), &romFilter, current.getString());
1270 const auto& style = ImGui::GetStyle();
1271 ImGui::SetNextItemWidth(-(ImGui::CalcTextSize(
"mapper-type").x + style.ItemInnerSpacing.x));
1272 interacted |= selectMapperType(
"mapper-type", item.romType);
1273 interacted |= selectPatches(item, group.patchIndex);
1274 interacted |= ImGui::Checkbox(
"Reset MSX on inserting ROM", &resetOnInsertRom);
1275 if (interacted) info.select = SELECT_ROM_IMAGE;
1278 ImGui::RadioButton(
"extension", &info.select, SELECT_EXTENSION);
1281 auto& allExtensions = getAllExtensions();
1282 auto& group = info.groups[SELECT_EXTENSION];
1283 auto& item = group.edit;
1285 bool interacted = drawExtensionFilter();
1287 auto drawExtensions = [&]{
1288 auto filteredExtensions = to_vector(xrange(allExtensions.size()));
1289 applyComboFilter(
"Type", filterType, allExtensions, filteredExtensions);
1290 applyDisplayNameFilter(filterString, allExtensions, filteredExtensions);
1292 im::ListClipper(filteredExtensions.size(), [&](int i) {
1293 auto& ext = allExtensions[filteredExtensions[i]];
1294 bool ok = getTestResult(ext).empty();
1295 im::StyleColor(!ok, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
1296 if (ImGui::Selectable(ext.displayName.c_str(), item.name == ext.configName)) {
1298 item.name = ext.configName;
1300 if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left)) {
1301 insertMedia(extName, group);
1303 extensionTooltip(ext);
1308 im::ListBox(
"##list", [&]{
1312 im::Combo(
"##extension", displayNameForExtension(item.name).c_str(), [&]{
1317 interacted |= ImGui::IsItemActive();
1318 if (interacted) info.select = SELECT_EXTENSION;
1322 if (insertMediaButton(info.select == SELECT_ROM_IMAGE ? cartName : extName,
1323 info.groups[info.select], &info.show)) {
1324 if (resetOnInsertRom && info.select == SELECT_ROM_IMAGE) {
1331static void addRecent(ImGuiMedia::ItemGroup& group)
1333 auto& recent = group.recent;
1334 if (
auto it2 =
ranges::find(recent, group.edit); it2 != recent.end()) {
1336 std::rotate(recent.begin(), it2, it2 + 1);
1339 if (recent.full()) recent.pop_back();
1340 recent.push_front(group.edit);
1344static bool ButtonWithCustomRendering(
1345 const char* label,
gl::vec2 size,
bool pressed,
1346 std::invocable<gl::vec2 /*center*/, ImDrawList*>
auto render)
1348 bool result =
false;
1349 im::StyleColor(pressed, ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_ButtonActive), [&]{
1350 gl::vec2 topLeft = ImGui::GetCursorScreenPos();
1352 result = ImGui::Button(label, size);
1353 render(center, ImGui::GetWindowDrawList());
1358static void RenderPlay(
gl::vec2 center, ImDrawList* drawList)
1360 float half = 0.4f * ImGui::GetTextLineHeight();
1361 auto p1 = center +
gl::vec2(half, 0.0f);
1362 auto p2 = center +
gl::vec2(-half, half);
1364 drawList->AddTriangleFilled(p1, p2,
p3,
getColor(imColor::TEXT));
1366static void RenderRewind(
gl::vec2 center, ImDrawList* drawList)
1368 float size = 0.8f * ImGui::GetTextLineHeight();
1369 float half =
size * 0.5f;
1370 auto color =
getColor(imColor::TEXT);
1371 auto p1 = center +
gl::vec2(-size, 0.0f);
1372 auto p2 = center +
gl::vec2(0.0f, -half);
1374 drawList->AddTriangleFilled(p1, p2,
p3, color);
1379 drawList->AddTriangleFilled(p1, p2,
p3, color);
1381static void RenderStop(
gl::vec2 center, ImDrawList* drawList)
1383 gl::vec2 half{0.4f * ImGui::GetTextLineHeight()};
1384 drawList->AddRectFilled(center - half, center + half,
getColor(imColor::TEXT));
1386static void RenderRecord(
gl::vec2 center, ImDrawList* drawList)
1388 float radius = 0.4f * ImGui::GetTextLineHeight();
1389 drawList->AddCircleFilled(center, radius,
getColor(imColor::TEXT));
1393void ImGuiMedia::cassetteMenu(
const TclObject& cmdResult)
1395 ImGui::SetNextWindowSize(
gl::vec2{29, 20} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
1396 auto& info = cassetteMediaInfo;
1397 auto& group = info.group;
1399 ImGui::TextUnformatted(
"Current tape"sv);
1400 auto current = cmdResult.getListIndexUnchecked(1).getString();
1402 if (current.empty()) {
1403 ImGui::TextUnformatted(
"No tape inserted"sv);
1405 ImGui::TextUnformatted(
"Tape image:"sv);
1407 ImGui::TextUnformatted(leftClip(current, ImGui::GetContentRegionAvail().x));
1411 if (ImGui::Button(
"Eject")) {
1412 manager.executeDelayed(makeTclList(
"cassetteplayer",
"eject"));
1419 auto status = cmdResult.getListIndexUnchecked(2).getString();
1420 auto size = ImGui::GetFrameHeightWithSpacing();
1421 if (ButtonWithCustomRendering(
"##Play", {2.0f *
size,
size}, status ==
"play", RenderPlay)) {
1425 if (ButtonWithCustomRendering(
"##Rewind", {2.0f *
size,
size},
false, RenderRewind)) {
1429 if (ButtonWithCustomRendering(
"##Stop", {2.0f *
size,
size}, status ==
"stop", RenderStop)) {
1433 if (ButtonWithCustomRendering(
"##Record", {2.0f *
size,
size}, status ==
"record", RenderRecord)) {
1435 "Select new wav file for record",
1436 "Tape images (*.wav){.wav}",
1437 [&](
const auto& fn) {
1438 group.edit.name = fn;
1440 [&group](
const TclObject&) {
1449 auto getFloat = [&](std::string_view subCmd) {
1450 auto r = manager.
execute(
makeTclList(
"cassetteplayer", subCmd)).value_or(TclObject(0.0));
1451 return r.getOptionalFloat().value_or(0.0f);
1453 auto length = getFloat(
"getlength");
1454 auto pos = getFloat(
"getpos");
1455 auto format = [](
float time) {
1456 int t = narrow_cast<int>(time);
1457 int s =
t % 60;
t /= 60;
1458 int m =
t % 60;
t /= 60;
1459 std::ostringstream os;
1460 os << std::setfill(
'0');
1461 if (
t) os << std::setw(2) <<
t <<
':';
1462 os << std::setw(2) << m <<
':';
1463 os << std::setw(2) << s;
1466 ImGui::Text(
"%s / %s",
format(pos).c_str(),
format(length).c_str());
1470 const auto& hotKey = reactor.getHotKey();
1471 if (
auto* autoRun =
dynamic_cast<BooleanSetting*
>(controller.findSetting(
"autoruncassettes"))) {
1472 Checkbox(hotKey,
"(try to) Auto Run", *autoRun);
1474 if (
auto* mute =
dynamic_cast<BooleanSetting*
>(controller.findSetting(
"cassetteplayer_ch1_mute"))) {
1475 Checkbox(hotKey,
"Mute tape audio", *mute, [](
const Setting&) {
return std::string{}; });
1480 im::Child(
"select", {0, -ImGui::GetFrameHeightWithSpacing()}, [&]{
1483 selectImage(group,
"Select tape image", &cassetteFilter, current);
1486 insertMediaButton(
"cassetteplayer", group, &info.show);
1490void ImGuiMedia::insertMedia(std::string_view mediaName, ItemGroup& group)
1492 auto& item = group.edit;
1493 if (item.name.empty())
return;
1495 auto cmd =
makeTclList(mediaName,
"insert", item.name);
1496 for (
const auto& patch : item.ipsPatches) {
1497 cmd.addListElement(
"-ips", patch);
1499 if (item.romType != ROM_UNKNOWN) {
1500 cmd.addListElement(
"-romtype", RomInfo::romTypeToName(item.romType));
1503 [&group](
const TclObject&) {
void test(const IterableBitSet< N > &s, std::initializer_list< size_t > list)
static constexpr unsigned MAX_SLOTS
std::optional< unsigned > findSlotWith(const HardwareConfig &config) const
std::string getPsSsString(unsigned slot) const
const HardwareConfig * getConfigForSlot(unsigned slot) const
static std::span< const std::string_view > getExtensions()
static std::span< const std::string_view > getExtensions()
static std::shared_ptr< HDInUse > getDrivesInUse(MSXMotherBoard &motherBoard)
const std::string & getConfigName() const
std::string_view getRomFilename() const
std::optional< TclObject > execute(TclObject command)
Interpreter & getInterpreter()
std::unique_ptr< ImGuiOpenFile > openFile
void executeDelayed(std::function< void()> action)
CartridgeSlotManager & getSlotManager()
MSXCommandController & getMSXCommandController()
const Extensions & getExtensions() const
static std::span< const std::string_view > getExtensions()
MSXMotherBoard * getMotherBoard() const
RomDatabase & getSoftwareDatabase()
This class implements a real drive, single or double sided.
static std::shared_ptr< DrivesInUse > getDrivesInUse(MSXMotherBoard &motherBoard)
std::string_view getYear(const char *buf) const
std::string_view getTitle(const char *buf) const
std::string_view getCompany(const char *buf) const
std::string_view getRemark(const char *buf) const
static std::string_view romTypeToName(RomType type)
std::string_view getCountry(const char *buf) const
static RomType nameToRomType(std::string_view name)
std::string_view getOrigType(const char *buf) const
std::optional< TclObject > getOptionalDictValue(const TclObject &key) const
zstring_view getString() const
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr auto empty() const
mat3 p3(vec3(1, 2, 3), vec3(4, 5, 6), vec3(7, 0, 9))
detail::Joiner< Collection, Separator > join(Collection &&col, Separator &&sep)
auto CalcTextSize(std::string_view str)
void TextUnformatted(const std::string &str)
bool stringToBool(string_view str)
T length(const vecN< N, T > &x)
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)
void ID(const char *str_id, 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 Child(const char *str_id, const ImVec2 &size, ImGuiChildFlags child_flags, ImGuiWindowFlags window_flags, std::invocable<> auto next)
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 Group(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)
void format(SectorAccessibleDisk &disk, MSXBootSectorType bootType)
Format the given disk (= a single partition).
string_view getFilename(string_view path)
Returns the file portion of a path name.
This file implemented 3 utility functions:
bool Checkbox(const HotKey &hotKey, BooleanSetting &setting)
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
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)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
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 lower_bound(ForwardRange &&range, const T &value, Compare comp={}, Proj proj={})
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
TemporaryString tmpStrCat(Ts &&... ts)
void strAppend(std::string &result, Ts &&...ts)
constexpr auto xrange(T e)