openMSX
ImGuiSymbols.cc
Go to the documentation of this file.
1#include "ImGuiSymbols.hh"
2
3#include "ImGuiBreakPoints.hh"
4#include "ImGuiCpp.hh"
5#include "ImGuiManager.hh"
6#include "ImGuiDebugger.hh"
7#include "ImGuiOpenFile.hh"
8#include "ImGuiUtils.hh"
9#include "ImGuiWatchExpr.hh"
10
11#include "CliComm.hh"
12#include "File.hh"
13#include "Interpreter.hh"
14#include "MSXCliComm.hh"
15#include "MSXException.hh"
16#include "MSXMotherBoard.hh"
17#include "MSXCPUInterface.hh"
18#include "Reactor.hh"
19
20#include "enumerate.hh"
21#include "ranges.hh"
22#include "StringOp.hh"
23#include "stl.hh"
24#include "strCat.hh"
25#include "unreachable.hh"
26#include "xrange.hh"
27
28#include <imgui.h>
29
30#include <cassert>
31
32namespace openmsx {
33
35 : ImGuiPart(manager_)
36 , symbolManager(manager.getReactor().getSymbolManager())
37{
38 symbolManager.setObserver(this);
39}
40
42{
43 symbolManager.setObserver(nullptr);
44}
45
46void ImGuiSymbols::save(ImGuiTextBuffer& buf)
47{
48 savePersistent(buf, *this, persistentElements);
49 for (const auto& file : symbolManager.getFiles()) {
50 buf.appendf("symbolfile=%s\n", file.filename.c_str());
51 buf.appendf("symbolfiletype=%s\n", SymbolFile::toString(file.type).c_str());
52 if (file.slot) {
53 buf.appendf("slotsubslot=%d\n", *file.slot);
54 }
55 }
56 for (const auto& [file, error, type, slot] : fileError) {
57 buf.appendf("symbolfile=%s\n", file.c_str());
58 buf.appendf("symbolfiletype=%s\n", SymbolFile::toString(type).c_str());
59 if (slot) {
60 buf.appendf("slotsubslot=%d\n", *slot);
61 }
62 }
63}
64
66{
67 symbolManager.removeAllFiles();
68 fileError.clear();
69}
70
71void ImGuiSymbols::loadLine(std::string_view name, zstring_view value)
72{
73 if (loadOnePersistent(name, value, *this, persistentElements)) {
74 // already handled
75 } else if (name == "symbolfile") {
76 fileError.emplace_back(std::string{value}, // filename
77 std::string{}, // error
79 std::nullopt); // slot-subslot
80 } else if (name == "symbolfiletype") {
81 if (!fileError.empty()) {
82 fileError.back().type =
84 }
85 } else if (name == "slotsubslot") {
86 if (!fileError.empty()) {
87 if (auto slot = StringOp::stringTo<int>(value)) {
88 fileError.back().slot = slot;
89 }
90 }
91 }
92}
93
95{
96 std::vector<FileInfo> tmp;
97 std::swap(tmp, fileError);
98 for (const auto& info : tmp) {
99 loadFile(info.filename, SymbolManager::LoadEmpty::ALLOWED, info.type, info.slot);
100 }
101}
102
103void ImGuiSymbols::loadFile(const std::string& filename, SymbolManager::LoadEmpty loadEmpty, SymbolFile::Type type, std::optional<uint8_t> slot)
104{
105 auto& cliComm = manager.getCliComm();
106 auto it = ranges::find(fileError, filename, &FileInfo::filename);
107 try {
108 if (!symbolManager.reloadFile(filename, loadEmpty, type, slot)) {
109 cliComm.printWarning("Symbol file \"", filename,
110 "\" doesn't contain any symbols");
111 }
112 if (it != fileError.end()) fileError.erase(it); // clear previous error
113 } catch (MSXException& e) {
114 cliComm.printWarning(
115 "Couldn't load symbol file \"", filename, "\": ", e.getMessage());
116 if (it != fileError.end()) {
117 it->error = e.getMessage(); // overwrite previous error
118 it->type = type;
119 } else {
120 fileError.emplace_back(filename, e.getMessage(), type, slot); // set error
121 }
122 }
123}
124
125static bool isSlotExpanded(MSXMotherBoard* motherBoard, int ps)
126{
127 return motherBoard != nullptr ? motherBoard->getCPUInterface().isExpanded(ps) : true;
128}
129
130static std::string formatSlot(std::optional<uint8_t> slot, MSXMotherBoard* motherBoard = nullptr)
131{
132 if (!slot) return "-";
133 int ps = (*slot >> 0) & 3;
134 int ss = (*slot >> 2) & 3;
135 return isSlotExpanded(motherBoard, ps) ? strCat(ps, '-', ss)
136 : strCat(ps);
137}
138
139static void checkSort(const SymbolManager& manager, std::vector<SymbolRef>& symbols)
140{
141 auto* sortSpecs = ImGui::TableGetSortSpecs();
142 if (!sortSpecs->SpecsDirty) return;
143
144 sortSpecs->SpecsDirty = false;
145 assert(sortSpecs->SpecsCount == 1);
146 assert(sortSpecs->Specs);
147 assert(sortSpecs->Specs->SortOrder == 0);
148
149 switch (sortSpecs->Specs->ColumnIndex) {
150 case 0: // name
151 sortUpDown_String(symbols, sortSpecs, [&](const auto& sym) { return sym.name(manager); });
152 break;
153 case 1: // value
154 sortUpDown_T(symbols, sortSpecs, [&](const auto& sym) { return sym.value(manager); });
155 break;
156 case 2: // slot
157 sortUpDown_String(symbols, sortSpecs, [&](const auto& sym) { return formatSlot(sym.slot(manager)); });
158 break;
159 case 3: // segment
160 sortUpDown_T(symbols, sortSpecs, [&](const auto& sym) { return sym.segment(manager); });
161 break;
162 case 4: // file (all symbols)
163 sortUpDown_String(symbols, sortSpecs, [&](const auto& sym) { return sym.file(manager); });
164 break;
165 default:
167 }
168}
169
170void ImGuiSymbols::drawContext(MSXMotherBoard* motherBoard, const SymbolRef& sym)
171{
172 if (ImGui::MenuItem("Show in Dissassembly", nullptr, nullptr, motherBoard != nullptr)) {
173 manager.debugger->setGotoTarget(sym.value(symbolManager));
174 }
175 if (ImGui::MenuItem("Set breakpoint", nullptr, nullptr, motherBoard != nullptr)) {
176 std::string cond;
177 if (auto slot = sym.slot(symbolManager)) {
178 strAppend(cond, "[pc_in_slot ", *slot & 3, ' ', (*slot >> 2) & 3);
179 if (auto segment = sym.segment(symbolManager)) {
180 strAppend(cond, ' ', *segment);
181 }
182 strAppend(cond, ']');
183 }
184 BreakPoint newBp(sym.value(symbolManager), TclObject("debug break"), TclObject(cond), false);
185 auto& cpuInterface = motherBoard->getCPUInterface();
186 cpuInterface.insertBreakPoint(std::move(newBp));
187 }
188}
189
190template<bool FILTER_FILE>
191void ImGuiSymbols::drawTable(MSXMotherBoard* motherBoard, const std::string& file)
192{
193 assert(FILTER_FILE == !file.empty());
194
195 int flags = ImGuiTableFlags_RowBg |
196 ImGuiTableFlags_BordersV |
197 ImGuiTableFlags_BordersOuter |
198 ImGuiTableFlags_ContextMenuInBody |
199 ImGuiTableFlags_Resizable |
200 ImGuiTableFlags_Reorderable |
201 ImGuiTableFlags_Sortable |
202 ImGuiTableFlags_Hideable |
203 ImGuiTableFlags_SizingStretchProp |
204 (FILTER_FILE ? ImGuiTableFlags_ScrollY : 0);
205 im::Table(file.c_str(), (FILTER_FILE ? 4 : 5), flags, {0, 100}, [&]{
206 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
207 ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_NoHide);
208 ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed);
209 ImGui::TableSetupColumn("slot", ImGuiTableColumnFlags_DefaultHide);
210 ImGui::TableSetupColumn("segment", ImGuiTableColumnFlags_DefaultHide);
211 if (!FILTER_FILE) {
212 ImGui::TableSetupColumn("file");
213 }
214 ImGui::TableHeadersRow();
215 checkSort(symbolManager, symbols);
216
217 for (const auto& sym : symbols) {
218 if (FILTER_FILE && (sym.file(symbolManager) != file)) continue;
219
220 if (ImGui::TableNextColumn()) { // name
222 auto symName = sym.name(symbolManager);
223 ImGui::Selectable(symName.data(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
224 auto symNameMenu = strCat("symbol-manager##", symName);
225 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
226 ImGui::OpenPopup(symNameMenu.c_str());
227 }
228 im::Popup(symNameMenu.c_str(), [&]{ drawContext(motherBoard, sym); });
229 }
230 if (ImGui::TableNextColumn()) { // value
232 ImGui::StrCat(hex_string<4>(sym.value(symbolManager)));
233 }
234 if (ImGui::TableNextColumn()) { // slot
236 ImGui::TextUnformatted(formatSlot(sym.slot(symbolManager), motherBoard));
237 }
238 if (ImGui::TableNextColumn()) { // segment
240 ImGui::TextUnformatted(sym.segment(symbolManager) ? tmpStrCat(*sym.segment(symbolManager)).c_str() : "-");
241 }
242 if (!FILTER_FILE && ImGui::TableNextColumn()) { // file
243 ImGui::TextUnformatted(sym.file(symbolManager));
244 }
245 }
246 });
247}
248
250{
251 if (!show) return;
252
253 const auto& style = ImGui::GetStyle();
254 ImGui::SetNextWindowSize(gl::vec2{24, 18} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
255 im::Window("Symbol Manager", &show, [&]{
256 if (ImGui::Button("Load symbol file...")) {
257 manager.openFile->selectFile(
258 "Select symbol file",
260 [this](const std::string& filename) {
262 loadFile(filename, SymbolManager::LoadEmpty::NOT_ALLOWED, type);
263 });
264 }
265
266 im::TreeNode("Symbols per file", ImGuiTreeNodeFlags_DefaultOpen, [&]{
267 std::optional<FileInfo> reloadAction;
268 std::string removeAction;
269 auto drawFile = [&](const std::string& filename, std::string_view error, SymbolFile::Type type, std::optional<int> slot) {
270 bool hasError = !error.empty();
271 auto* file = symbolManager.findFile(filename);
272 assert((file != nullptr) ^ hasError); // not both
273 im::StyleColor(hasError, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
274 auto title = tmpStrCat("File: ", filename);
275 im::TreeNode(title.c_str(), [&]{
276 if (hasError) {
277 ImGui::TextUnformatted(error);
278 }
279 im::StyleColor(ImGuiCol_Text, getColor(imColor::TEXT), [&]{
280 if (!hasError) {
281 auto arrowSize = ImGui::GetFrameHeight();
282 auto extra = arrowSize + 2.0f * style.FramePadding.x;
283 ImGui::SetNextItemWidth(ImGui::CalcTextSize("3-3").x + extra);
284 ImGui::AlignTextToFramePadding();
285
286 auto preview = formatSlot(file->slot, motherBoard);
287 im::Combo(tmpStrCat("Slot##", filename).data(), preview.c_str(), [&]{
288 // Set slot and all the symbols in it
289 auto setSlot = [&](std::optional<uint8_t> newSlot) {
290 file->slot = newSlot;
291 for (auto& symbol : file->getSymbols()) {
292 symbol.slot = newSlot;
293 }
294 };
295 // initial state
296 if (ImGui::Selectable("-", !file->slot)) {
297 setSlot({});
298 }
299
300 for (uint8_t ps = 0; ps < 4; ++ps) {
301 if (isSlotExpanded(motherBoard, ps)) {
302 for (uint8_t ss = 0; ss < 4; ++ss) {
303 auto slotInfo = tmpStrCat(ps, "-", ss);
304 int psSs = (ss << 2) + ps;
305 if (ImGui::Selectable(slotInfo.c_str(), psSs == file->slot)) {
306 setSlot(psSs);
307 }
308 }
309 } else {
310 if (ImGui::Selectable(tmpStrCat(ps).c_str(), file->slot && ps == file->slot)) {
311 setSlot(ps);
312 }
313 }
314 }
315 });
316 ImGui::SameLine();
317 }
318
319 if (ImGui::Button("Reload")) {
320 reloadAction.emplace(filename, std::string{}, type, slot);
321 }
322 ImGui::SameLine();
323 if (ImGui::Button("Remove")) {
324 removeAction = filename;
325 }
326 if (!hasError) {
327 drawTable<true>(motherBoard, filename);
328 }
329 });
330 });
331 });
332 };
333
334 for (const auto& file : symbolManager.getFiles()) {
335 drawFile(file.filename, {}, file.type, file.slot);
336 }
337 for (const auto& info : fileError) {
338 drawFile(info.filename, info.error, info.type, info.slot);
339 }
340
341 // only make changes after the above loops (don't loop over changing collection)
342 if (reloadAction) {
343 loadFile(reloadAction->filename, SymbolManager::LoadEmpty::NOT_ALLOWED,
344 reloadAction->type, reloadAction->slot);
345 }
346 if (!removeAction.empty()) {
347 symbolManager.removeFile(removeAction);
348 if (auto it = ranges::find(fileError, removeAction, &FileInfo::filename);
349 it != fileError.end()) {
350 fileError.erase(it);
351 }
352 }
353 });
354 im::TreeNode("All symbols", [&]{
355 if (ImGui::Button("Reload all")) {
356 auto tmp = to_vector(view::transform(symbolManager.getFiles(), [&](const auto& file) {
357 return FileInfo{file.filename, std::string{}, file.type, file.slot};
358 }));
359 append(tmp, std::move(fileError));
360 fileError.clear();
361 for (const auto& info : tmp) {
362 loadFile(info.filename, SymbolManager::LoadEmpty::NOT_ALLOWED, info.type, info.slot);
363 }
364 }
365 ImGui::SameLine();
366 if (ImGui::Button("Remove all")) {
367 symbolManager.removeAllFiles();
368 fileError.clear();
369 }
370 drawTable<false>(motherBoard);
371 });
372 });
373}
374
375void ImGuiSymbols::notifySymbolsChanged()
376{
377 symbols.clear();
378 for (const auto& [fileIdx, file] : enumerate(symbolManager.getFiles())) {
379 for (auto symbolIdx : xrange(file.symbols.size())) {
380 //symbols.emplace_back(narrow<unsigned>(fileIdx),
381 // narrow<unsigned>(symbolIdx));
382 // clang workaround
383 symbols.push_back(SymbolRef{narrow<unsigned>(fileIdx),
384 narrow<unsigned>(symbolIdx)});
385 }
386 }
387
388 manager.breakPoints->refreshSymbols();
389 manager.watchExpr->refreshSymbols();
390}
391
392} // namespace openmsx
const char * c_str() const
std::unique_ptr< ImGuiOpenFile > openFile
std::unique_ptr< ImGuiDebugger > debugger
static std::string getLastFilter()
ImGuiManager & manager
Definition ImGuiPart.hh:30
void loadLine(std::string_view name, zstring_view value) override
void save(ImGuiTextBuffer &buf) override
void loadStart() override
void loadEnd() override
void paint(MSXMotherBoard *motherBoard) override
ImGuiSymbols(ImGuiManager &manager)
const auto & getFiles() const
static std::string getFileFilters()
void removeFile(std::string_view filename)
bool reloadFile(const std::string &filename, LoadEmpty loadEmpty, SymbolFile::Type type, std::optional< uint8_t > slot={})
static SymbolFile::Type getTypeForFilter(std::string_view filter)
SymbolFile * findFile(std::string_view filename)
void setObserver(SymbolObserver *observer_)
Like std::string_view, but with the extra guarantee that it refers to a zero-terminated string.
constexpr const char * c_str() const
constexpr auto enumerate(Iterable &&iterable)
Heavily inspired by Nathan Reed's blog post: Python-Like enumerate() In C++17 http://reedbeta....
Definition enumerate.hh:28
void StrCat(Ts &&...ts)
Definition ImGuiUtils.hh:43
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:37
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:24
constexpr double e
Definition Math.hh:21
void Table(const char *str_id, int column, ImGuiTableFlags flags, const ImVec2 &outer_size, float inner_width, std::invocable<> auto next)
Definition ImGuiCpp.hh:455
void Window(const char *name, bool *p_open, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:63
bool TreeNode(const char *label, ImGuiTreeNodeFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:302
void Combo(const char *label, const char *preview_value, ImGuiComboFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:289
void StyleColor(bool active, Args &&...args)
Definition ImGuiCpp.hh:175
void Popup(const char *str_id, ImGuiWindowFlags flags, std::invocable<> auto next)
Definition ImGuiCpp.hh:391
This file implemented 3 utility functions:
Definition Autofire.cc:11
bool loadOnePersistent(std::string_view name, zstring_view value, C &c, const std::tuple< Elements... > &tup)
void sortUpDown_String(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
void savePersistent(ImGuiTextBuffer &buf, C &c, const std::tuple< Elements... > &tup)
void sortUpDown_T(Range &range, const ImGuiTableSortSpecs *sortSpecs, Projection proj)
ImU32 getColor(imColor col)
auto find(InputRange &&range, const T &value)
Definition ranges.hh:162
size_t size(std::string_view utf8)
constexpr auto transform(Range &&range, UnaryOp op)
Definition view.hh:520
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
Definition stl.hh:275
std::string strCat()
Definition strCat.hh:703
TemporaryString tmpStrCat(Ts &&... ts)
Definition strCat.hh:742
void strAppend(std::string &result, Ts &&...ts)
Definition strCat.hh:752
static std::optional< Type > parseType(std::string_view str)
static zstring_view toString(Type type)
#define UNREACHABLE
constexpr auto xrange(T e)
Definition xrange.hh:132