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 Disassembly", nullptr, nullptr, motherBoard != nullptr)) {
173 manager.debugger->setGotoTarget(sym.value(symbolManager));
174 }
175 if (ImGui::MenuItem("Set breakpoint", nullptr, nullptr, motherBoard != nullptr)) {
176 auto& interp = motherBoard->getReactor().getInterpreter();
177 BreakPoint newBp;
178 newBp.setAddress(interp, TclObject(strCat("$sym(", sym.name(symbolManager), ')')));
179
180 if (auto slot = sym.slot(symbolManager)) {
181 auto cond = strCat("[pc_in_slot ", *slot & 3, ' ', (*slot >> 2) & 3);
182 if (auto segment = sym.segment(symbolManager)) {
183 strAppend(cond, ' ', *segment);
184 }
185 strAppend(cond, ']');
186 newBp.setCondition(TclObject(cond));
187 }
188
189 auto& cpuInterface = motherBoard->getCPUInterface();
190 cpuInterface.insertBreakPoint(std::move(newBp));
191 }
192}
193
194template<bool FILTER_FILE>
195void ImGuiSymbols::drawTable(MSXMotherBoard* motherBoard, const std::string& file)
196{
197 assert(FILTER_FILE == !file.empty());
198
199 int flags = ImGuiTableFlags_RowBg |
200 ImGuiTableFlags_BordersV |
201 ImGuiTableFlags_BordersOuter |
202 ImGuiTableFlags_ContextMenuInBody |
203 ImGuiTableFlags_Resizable |
204 ImGuiTableFlags_Reorderable |
205 ImGuiTableFlags_Sortable |
206 ImGuiTableFlags_Hideable |
207 ImGuiTableFlags_SizingStretchProp |
208 (FILTER_FILE ? ImGuiTableFlags_ScrollY : 0);
209 im::Table(file.c_str(), (FILTER_FILE ? 4 : 5), flags, {0, 100}, [&]{
210 ImGui::TableSetupScrollFreeze(0, 1); // Make top row always visible
211 ImGui::TableSetupColumn("name", ImGuiTableColumnFlags_NoHide);
212 ImGui::TableSetupColumn("value", ImGuiTableColumnFlags_NoHide | ImGuiTableColumnFlags_DefaultSort | ImGuiTableColumnFlags_WidthFixed);
213 ImGui::TableSetupColumn("slot", ImGuiTableColumnFlags_DefaultHide);
214 ImGui::TableSetupColumn("segment", ImGuiTableColumnFlags_DefaultHide);
215 if (!FILTER_FILE) {
216 ImGui::TableSetupColumn("file");
217 }
218 ImGui::TableHeadersRow();
219 checkSort(symbolManager, symbols);
220
221 for (const auto& sym : symbols) {
222 if (FILTER_FILE && (sym.file(symbolManager) != file)) continue;
223
224 if (ImGui::TableNextColumn()) { // name
226 const auto& symName = sym.name(symbolManager);
227 ImGui::Selectable(symName.c_str(), false, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowOverlap);
228 auto symNameMenu = strCat("symbol-manager##", symName);
229 if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
230 ImGui::OpenPopup(symNameMenu.c_str());
231 }
232 im::Popup(symNameMenu.c_str(), [&]{ drawContext(motherBoard, sym); });
233 }
234 if (ImGui::TableNextColumn()) { // value
236 ImGui::StrCat(hex_string<4>(sym.value(symbolManager)));
237 }
238 if (ImGui::TableNextColumn()) { // slot
240 ImGui::TextUnformatted(formatSlot(sym.slot(symbolManager), motherBoard));
241 }
242 if (ImGui::TableNextColumn()) { // segment
244 ImGui::TextUnformatted(sym.segment(symbolManager) ? tmpStrCat(*sym.segment(symbolManager)).c_str() : "-");
245 }
246 if (!FILTER_FILE && ImGui::TableNextColumn()) { // file
247 ImGui::TextUnformatted(sym.file(symbolManager));
248 }
249 }
250 });
251}
252
254{
255 if (!show) return;
256
257 const auto& style = ImGui::GetStyle();
258 ImGui::SetNextWindowSize(gl::vec2{24, 18} * ImGui::GetFontSize(), ImGuiCond_FirstUseEver);
259 im::Window("Symbol Manager", &show, [&]{
260 if (ImGui::Button("Load symbol file...")) {
261 manager.openFile->selectFile(
262 "Select symbol file",
264 [this](const std::string& filename) {
266 loadFile(filename, SymbolManager::LoadEmpty::NOT_ALLOWED, type);
267 });
268 }
269
270 im::TreeNode("Symbols per file", ImGuiTreeNodeFlags_DefaultOpen, [&]{
271 std::optional<FileInfo> reloadAction;
272 std::string removeAction;
273 auto drawFile = [&](const std::string& filename, std::string_view error, SymbolFile::Type type, std::optional<int> slot) {
274 bool hasError = !error.empty();
275 auto* file = symbolManager.findFile(filename);
276 assert((file != nullptr) ^ hasError); // not both
277 im::StyleColor(hasError, ImGuiCol_Text, getColor(imColor::ERROR), [&]{
278 auto title = tmpStrCat("File: ", filename);
279 im::TreeNode(title.c_str(), [&]{
280 if (hasError) {
281 ImGui::TextUnformatted(error);
282 }
283 im::StyleColor(ImGuiCol_Text, getColor(imColor::TEXT), [&]{
284 if (!hasError) {
285 auto arrowSize = ImGui::GetFrameHeight();
286 auto extra = arrowSize + 2.0f * style.FramePadding.x;
287 ImGui::SetNextItemWidth(ImGui::CalcTextSize("3-3").x + extra);
288 ImGui::AlignTextToFramePadding();
289
290 auto preview = formatSlot(file->slot, motherBoard);
291 im::Combo(tmpStrCat("Slot##", filename).data(), preview.c_str(), [&]{
292 // Set slot and all the symbols in it
293 auto setSlot = [&](std::optional<uint8_t> newSlot) {
294 file->slot = newSlot;
295 for (auto& symbol : file->getSymbols()) {
296 symbol.slot = newSlot;
297 }
298 };
299 // initial state
300 if (ImGui::Selectable("-", !file->slot)) {
301 setSlot({});
302 }
303
304 for (uint8_t ps = 0; ps < 4; ++ps) {
305 if (isSlotExpanded(motherBoard, ps)) {
306 for (uint8_t ss = 0; ss < 4; ++ss) {
307 auto slotInfo = tmpStrCat(ps, "-", ss);
308 int psSs = (ss << 2) + ps;
309 if (ImGui::Selectable(slotInfo.c_str(), psSs == file->slot)) {
310 setSlot(psSs);
311 }
312 }
313 } else {
314 if (ImGui::Selectable(tmpStrCat(ps).c_str(), file->slot && ps == file->slot)) {
315 setSlot(ps);
316 }
317 }
318 }
319 });
320 ImGui::SameLine();
321 }
322
323 if (ImGui::Button("Reload")) {
324 reloadAction.emplace(filename, std::string{}, type, slot);
325 }
326 ImGui::SameLine();
327 if (ImGui::Button("Remove")) {
328 removeAction = filename;
329 }
330 if (!hasError) {
331 drawTable<true>(motherBoard, filename);
332 }
333 });
334 });
335 });
336 };
337
338 for (const auto& file : symbolManager.getFiles()) {
339 drawFile(file.filename, {}, file.type, file.slot);
340 }
341 for (const auto& info : fileError) {
342 drawFile(info.filename, info.error, info.type, info.slot);
343 }
344
345 // only make changes after the above loops (don't loop over changing collection)
346 if (reloadAction) {
347 loadFile(reloadAction->filename, SymbolManager::LoadEmpty::NOT_ALLOWED,
348 reloadAction->type, reloadAction->slot);
349 }
350 if (!removeAction.empty()) {
351 symbolManager.removeFile(removeAction);
352 if (auto it = ranges::find(fileError, removeAction, &FileInfo::filename);
353 it != fileError.end()) {
354 fileError.erase(it);
355 }
356 }
357 });
358 im::TreeNode("All symbols", [&]{
359 if (ImGui::Button("Reload all")) {
360 auto tmp = to_vector(view::transform(symbolManager.getFiles(), [&](const auto& file) {
361 return FileInfo{file.filename, std::string{}, file.type, file.slot};
362 }));
363 append(tmp, std::move(fileError));
364 fileError.clear();
365 for (const auto& info : tmp) {
366 loadFile(info.filename, SymbolManager::LoadEmpty::NOT_ALLOWED, info.type, info.slot);
367 }
368 }
369 ImGui::SameLine();
370 if (ImGui::Button("Remove all")) {
371 symbolManager.removeAllFiles();
372 fileError.clear();
373 }
374 drawTable<false>(motherBoard);
375 });
376 });
377}
378
379void ImGuiSymbols::notifySymbolsChanged()
380{
381 symbols.clear();
382 for (const auto& [fileIdx, file] : enumerate(symbolManager.getFiles())) {
383 for (auto symbolIdx : xrange(file.symbols.size())) {
384 //symbols.emplace_back(narrow<unsigned>(fileIdx),
385 // narrow<unsigned>(symbolIdx));
386 // clang workaround
387 symbols.push_back(SymbolRef{narrow<unsigned>(fileIdx),
388 narrow<unsigned>(symbolIdx)});
389 }
390 }
391
392 manager.breakPoints->refreshSymbols();
393 manager.watchExpr->refreshSymbols();
394}
395
396} // 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:45
auto CalcTextSize(std::string_view str)
Definition ImGuiUtils.hh:39
void TextUnformatted(const std::string &str)
Definition ImGuiUtils.hh:26
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:441
auto to_vector(Range &&range) -> std::vector< detail::ToVectorType< T, decltype(std::begin(range))> >
Definition stl.hh:278
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