diff --git a/src/frontends/wx/wx_frame.cpp b/src/frontends/wx/wx_frame.cpp index c455602..c82040f 100644 --- a/src/frontends/wx/wx_frame.cpp +++ b/src/frontends/wx/wx_frame.cpp @@ -74,6 +74,39 @@ wxString type_suffix(SignalType t) { return t == SignalType::Other ? wxString() : " (" + wxString(signal_type_name(t)) + ")"; } + +// What a tree node stands for, attached to the item so a selection or a +// right-click can drive the edit operations on the right domain object. +struct NodeData : public wxTreeItemData { + enum class Kind { Other, Module, Part, Pin, Signal }; + Kind kind; + Module *module = nullptr; + Part *part = nullptr; + Signal *signal = nullptr; + explicit NodeData(Kind k) : kind(k) {} +}; + +NodeData *node_of(wxTreeCtrl *tree, const wxTreeItemId &id) { + return id.IsOk() ? static_cast(tree->GetItemData(id)) : nullptr; +} + +// The part of the current selection — a Part node, or the Pin's owning part. +Part *selected_part(wxTreeCtrl *tree) { + NodeData *d = node_of(tree, tree->GetSelection()); + if (d && (d->kind == NodeData::Kind::Part || d->kind == NodeData::Kind::Pin)) + return d->part; + return nullptr; +} + +// The signal of the current selection (and, via `mod`, its module). +Signal *selected_signal(wxTreeCtrl *tree, Module **mod) { + NodeData *d = node_of(tree, tree->GetSelection()); + if (d && d->kind == NodeData::Kind::Signal) { + if (mod) *mod = d->module; + return d->signal; + } + return nullptr; +} } // namespace EssimFrame::EssimFrame(WxFrontend &fe) @@ -145,6 +178,8 @@ EssimFrame::EssimFrame(WxFrontend &fe) Bind(wxEVT_MENU, &EssimFrame::OnQuit, this, ID_QUIT); Bind(wxEVT_MENU, &EssimFrame::OnAbout, this, ID_ABOUT); + tree_->Bind(wxEVT_TREE_ITEM_MENU, &EssimFrame::OnTreeContextMenu, this); + RebuildModelView(); } @@ -173,6 +208,11 @@ void EssimFrame::RebuildModelView() { wxTreeItemId mid = tree_->AppendItem( root, wx(mname) + wxString::Format(" — %d part(s), %d signal(s)", mp, ms)); + { + auto *d = new NodeData(NodeData::Kind::Module); + d->module = m; + tree_->SetItemData(mid, d); + } // Parts → pins (each pin shows the signal it is wired to, or NC). std::vector parts; @@ -185,6 +225,12 @@ void EssimFrame::RebuildModelView() { if (!p->connector_type.empty()) label += " [" + wx(p->connector_type) + "]"; wxTreeItemId pid = tree_->AppendItem(mid, label); + { + auto *d = new NodeData(NodeData::Kind::Part); + d->module = m; + d->part = p; + tree_->SetItemData(pid, d); + } std::vector pins; for (auto &nkv : *p) pins.push_back(nkv.first); @@ -202,7 +248,11 @@ void EssimFrame::RebuildModelView() { pl += ", dropped"; pl += ")"; } - tree_->AppendItem(pid, pl); + wxTreeItemId nid = tree_->AppendItem(pid, pl); + auto *d = new NodeData(NodeData::Kind::Pin); + d->module = m; + d->part = p; + tree_->SetItemData(nid, d); } } @@ -215,8 +265,13 @@ void EssimFrame::RebuildModelView() { std::sort(sigs.begin(), sigs.end(), natural_less); for (const auto &sname : sigs) { Signal *s = m->signals->get(sname); - tree_->AppendItem(sid, wx(sname) + type_suffix(s->type) - + wxString::Format(" — %d pin(s)", (int)s->size())); + wxTreeItemId nid = tree_->AppendItem( + sid, wx(sname) + type_suffix(s->type) + + wxString::Format(" — %d pin(s)", (int)s->size())); + auto *d = new NodeData(NodeData::Kind::Signal); + d->module = m; + d->signal = s; + tree_->SetItemData(nid, d); } } @@ -368,7 +423,8 @@ Part *EssimFrame::PickPart(const wxString &caption) { void EssimFrame::OnSetConnectorType(wxCommandEvent &) { - Part *p = PickPart(); + Part *p = selected_part(tree_); + if (!p) p = PickPart(); if (!p) return; wxTextEntryDialog dlg(this, "Connector type (empty = none):", @@ -392,7 +448,8 @@ void EssimFrame::OnSetConnectorType(wxCommandEvent &) { } void EssimFrame::OnAttachBsdl(wxCommandEvent &) { - Part *p = PickPart(); + Part *p = selected_part(tree_); + if (!p) p = PickPart(); if (!p) return; wxFileDialog dlg(this, "Attach a BSDL model", "", "", @@ -416,7 +473,8 @@ void EssimFrame::OnAttachBsdl(wxCommandEvent &) { } void EssimFrame::OnConnect(wxCommandEvent &) { - Part *p1 = PickPart("Connect — first part"); + Part *p1 = selected_part(tree_); + if (!p1) p1 = PickPart("Connect — first part"); if (!p1) return; Part *p2 = PickPart("Connect — second part"); if (!p2) return; @@ -453,21 +511,25 @@ void EssimFrame::OnConnect(wxCommandEvent &) { } void EssimFrame::OnSetSignalType(wxCommandEvent &) { - Module *m = PickModule("Set signal type"); - if (!m) return; - if (m->signals->size() == 0) { - wxMessageBox("That module has no signals.", "Set signal type", - wxOK | wxICON_INFORMATION, this); - return; + Module *m = nullptr; + Signal *sig = selected_signal(tree_, &m); + if (!sig) { + m = PickModule("Set signal type"); + if (!m) return; + if (m->signals->size() == 0) { + wxMessageBox("That module has no signals.", "Set signal type", + wxOK | wxICON_INFORMATION, this); + return; + } + std::vector sigs; + for (auto &skv : *m->signals) sigs.push_back(skv.first); + std::sort(sigs.begin(), sigs.end(), natural_less); + wxArrayString schoices; + for (const auto &s : sigs) schoices.Add(wx(s)); + int si = wxGetSingleChoiceIndex("Signal:", "Set signal type", schoices, this); + if (si < 0) return; + sig = m->signals->get(sigs[si]); } - std::vector sigs; - for (auto &skv : *m->signals) sigs.push_back(skv.first); - std::sort(sigs.begin(), sigs.end()); - wxArrayString schoices; - for (const auto &s : sigs) schoices.Add(wx(s)); - int si = wxGetSingleChoiceIndex("Signal:", "Set signal type", schoices, this); - if (si < 0) return; - Signal *sig = m->signals->get(sigs[si]); static const wxString types[] = {"power", "gnd", "other"}; int ti = wxGetSingleChoiceIndex("Type:", "Set signal type", @@ -522,3 +584,22 @@ void EssimFrame::OnAbout(wxCommandEvent &) { "wxWidgets frontend over essim_core.", "About essim", wxOK | wxICON_INFORMATION, this); } + +void EssimFrame::OnTreeContextMenu(wxTreeEvent &ev) { + wxTreeItemId id = ev.GetItem(); + if (id.IsOk()) tree_->SelectItem(id); // the edit handlers read the selection + NodeData *d = node_of(tree_, id); + if (!d) return; + + // Reuse the menu IDs so these route to the same handlers, which now act on + // the (just-selected) tree item. + wxMenu menu; + if (d->kind == NodeData::Kind::Part || d->kind == NodeData::Kind::Pin) { + menu.Append(ID_SET_CONNECTOR_TYPE, "Set connector type…"); + menu.Append(ID_ATTACH_BSDL, "Attach BSDL…"); + menu.Append(ID_CONNECT, "Connect to…"); + } else if (d->kind == NodeData::Kind::Signal) { + menu.Append(ID_SET_SIGNAL_TYPE, "Set signal type…"); + } + if (menu.GetMenuItemCount() > 0) PopupMenu(&menu); +} diff --git a/src/frontends/wx/wx_frame.hpp b/src/frontends/wx/wx_frame.hpp index 571ea13..255078f 100644 --- a/src/frontends/wx/wx_frame.hpp +++ b/src/frontends/wx/wx_frame.hpp @@ -7,6 +7,7 @@ class WxFrontend; class wxTreeCtrl; class wxTextCtrl; class wxCommandEvent; +class wxTreeEvent; // The essim main window. Holds no domain state of its own: it reads and mutates // the System owned by the WxFrontend, calling the core/app operations directly @@ -30,6 +31,11 @@ private: void OnQuit(wxCommandEvent &); void OnAbout(wxCommandEvent &); + // Right-click on a tree item → context menu of the edit actions valid for + // that node (part / signal). The actions reuse the menu IDs, so they run + // the same handlers — which read the tree selection. + void OnTreeContextMenu(wxTreeEvent &); + // Modal pickers over the current System. `caption` titles the dialogs (e.g. // to distinguish two picks). Each returns nullptr if there is nothing to // pick or the user cancels.