1 #include "symbol_pin_names_window.hpp"
2 #include "pool/gate.hpp"
3 #include "pool/entity.hpp"
4 #include "schematic/schematic_symbol.hpp"
5 #include "util/util.hpp"
6 #include "util/str_util.hpp"
7 #include "util/gtk_util.hpp"
8 #include "common/object_descr.hpp"
9 #include "util/csv.hpp"
10 #include "core/tool_data_window.hpp"
11 #include "imp/imp_interface.hpp"
12 #include "util/changeable.hpp"
13 #include <iostream>
14 #include <deque>
15 #include <algorithm>
16 
17 namespace horizon {
18 
19 class PinNamesBox : public Gtk::FlowBox, public Changeable {
20 public:
PinNamesBox(Component * c,const UUIDPath<2> & p,Glib::RefPtr<Gtk::SizeGroup> sg_combo)21     PinNamesBox(Component *c, const UUIDPath<2> &p, Glib::RefPtr<Gtk::SizeGroup> sg_combo)
22         : Gtk::FlowBox(), comp(c), path(p), pin(comp->entity->gates.at(path.at(0)).unit->pins.at(path.at(1)))
23     {
24         set_homogeneous(true);
25         set_selection_mode(Gtk::SELECTION_NONE);
26         set_min_children_per_line(2);
27 
28         {
29             auto primary_button = Gtk::manage(new Gtk::CheckButton(pin.primary_name + " (primary)"));
30             if (comp->pin_names.count(path))
31                 primary_button->set_active(comp->pin_names.at(path).count(-1));
32             primary_button->signal_toggled().connect([this, primary_button] {
33                 add_remove_name(-1, primary_button->get_active());
34                 s_signal_changed.emit();
35             });
36             sg_combo->add_widget(*primary_button);
37             add(*primary_button);
38         }
39 
40         {
41             int i = 0;
42             for (const auto &pin_name : pin.names) {
43                 auto cb = Gtk::manage(new Gtk::CheckButton(pin_name));
44                 if (comp->pin_names.count(path))
45                     cb->set_active(comp->pin_names.at(path).count(i));
46                 cb->signal_toggled().connect([this, i, cb] {
47                     add_remove_name(i, cb->get_active());
48                     s_signal_changed.emit();
49                 });
50                 i++;
51                 sg_combo->add_widget(*cb);
52                 add(*cb);
53             }
54         }
55 
56         {
57             auto cbox = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL));
58             custom_cb = Gtk::manage(new Gtk::CheckButton(""));
59             cbox->pack_start(*custom_cb, false, false, 0);
60             custom_entry = Gtk::manage(new Gtk::Entry);
61             if (comp->custom_pin_names.count(path))
62                 custom_entry->set_text(comp->custom_pin_names.at(path));
63             cbox->pack_start(*custom_entry, true, true, 0);
64             custom_cb->signal_toggled().connect([this] {
65                 custom_entry->set_sensitive(custom_cb->get_active());
66                 add_remove_name(-2, custom_cb->get_active());
67                 s_signal_changed.emit();
68             });
69             custom_entry->set_sensitive(false);
70             custom_entry->set_placeholder_text("Custom");
71             custom_entry->signal_changed().connect([this] {
72                 comp->custom_pin_names[path] = custom_entry->get_text();
73                 s_signal_changed.emit();
74             });
75             if (comp->pin_names.count(path))
76                 custom_cb->set_active(comp->pin_names.at(path).count(-2));
77             sg_combo->add_widget(*cbox);
78             add(*cbox);
79         }
80     }
81 
get_pin() const82     const Pin &get_pin() const
83     {
84         return pin;
85     }
86 
set_custom_name(const std::string & name)87     void set_custom_name(const std::string &name)
88     {
89         if (!name.size())
90             return;
91         custom_entry->set_text(name);
92         custom_cb->set_active(true);
93     }
94 
95 private:
96     Component *comp;
97     UUIDPath<2> path;
98     const Pin &pin;
99     Gtk::Entry *custom_entry = nullptr;
100     Gtk::CheckButton *custom_cb = nullptr;
101 
add_remove_name(int name,bool add)102     void add_remove_name(int name, bool add)
103     {
104         if (add) {
105             comp->pin_names[path].insert(name);
106         }
107         else {
108             comp->pin_names[path].erase(name);
109         }
110     }
111 };
112 
113 class GatePinRow : public Gtk::Box {
114 public:
GatePinRow(const std::string & label,Glib::RefPtr<Gtk::SizeGroup> sg_label)115     GatePinRow(const std::string &label, Glib::RefPtr<Gtk::SizeGroup> sg_label)
116         : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 16)
117     {
118         auto la = Gtk::manage(new Gtk::Label(label));
119         la->set_xalign(0);
120         sg_label->add_widget(*la);
121         pack_start(*la, false, false, 0);
122     }
123 
add_box(PinNamesBox * b)124     void add_box(PinNamesBox *b)
125     {
126         box = b;
127         pack_start(*b, true, true, 0);
128     }
129 
get_box()130     PinNamesBox *get_box()
131     {
132         return box;
133     }
134 
135 private:
136     PinNamesBox *box = nullptr;
137 };
138 
139 class GatePinEditor : public Gtk::ListBox, public Changeable {
140 public:
GatePinEditor(Component * c,const Gate * g)141     GatePinEditor(Component *c, const Gate *g) : Gtk::ListBox(), comp(c), gate(g)
142     {
143         sg = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
144         sg_combo = Gtk::SizeGroup::create(Gtk::SIZE_GROUP_HORIZONTAL);
145         set_header_func(&header_func_separator);
146         set_selection_mode(Gtk::SELECTION_SINGLE);
147         std::deque<const Pin *> pins_sorted;
148         for (const auto &it : gate->unit->pins) {
149             pins_sorted.push_back(&it.second);
150         }
151         std::sort(pins_sorted.begin(), pins_sorted.end(),
152                   [](const auto &a, const auto &b) { return strcmp_natural(a->primary_name, b->primary_name) < 0; });
153         boxes.reserve(pins_sorted.size());
154         for (const auto it : pins_sorted) {
155             auto box = Gtk::manage(new GatePinRow(it->primary_name, sg));
156 
157             auto bu = Gtk::manage(new PinNamesBox(comp, UUIDPath<2>(gate->uuid, it->uuid), sg_combo));
158             bu->signal_changed().connect([this] { s_signal_changed.emit(); });
159             bu->show();
160             box->add_box(bu);
161 
162             box->set_margin_start(16);
163             box->set_margin_end(8);
164             box->set_margin_top(4);
165             box->set_margin_bottom(4);
166 
167             boxes.push_back(bu);
168             insert(*box, -1);
169         }
170     }
171     Component *comp;
172     const Gate *gate;
173 
get_boxes()174     const std::vector<PinNamesBox *> &get_boxes()
175     {
176         return boxes;
177     }
178 
179 private:
180     Glib::RefPtr<Gtk::SizeGroup> sg;
181     Glib::RefPtr<Gtk::SizeGroup> sg_combo;
182     std::vector<PinNamesBox *> boxes;
183 };
184 
handle_import()185 void SymbolPinNamesWindow::handle_import()
186 {
187     GtkFileChooserNative *native =
188             gtk_file_chooser_native_new("Open", GTK_WINDOW(gobj()), GTK_FILE_CHOOSER_ACTION_OPEN, "_Open", "_Cancel");
189     auto chooser = Glib::wrap(GTK_FILE_CHOOSER(native));
190     auto filter = Gtk::FileFilter::create();
191     filter->set_name("CSV documents");
192     filter->add_pattern("*.csv");
193     filter->add_pattern("*.CSV");
194     chooser->add_filter(filter);
195 
196     if (gtk_native_dialog_run(GTK_NATIVE_DIALOG(native)) == GTK_RESPONSE_ACCEPT) {
197         auto filename = chooser->get_filename();
198         try {
199             auto ifs = make_ifstream(filename);
200             if (!ifs.is_open()) {
201                 throw std::runtime_error("file " + filename + " not opened");
202             }
203             CSV::Csv csv;
204             ifs >> csv;
205             ifs.close();
206             csv.expand(2);
207             auto &boxes = editor->get_boxes();
208             for (const auto &it : csv) {
209                 std::string primary_name = it.at(0);
210                 std::string custom_name = it.at(1);
211                 trim(primary_name);
212                 trim(custom_name);
213                 auto box = std::find_if(boxes.begin(), boxes.end(),
214                                         [&primary_name](auto &x) { return x->get_pin().primary_name == primary_name; });
215                 if (box != boxes.end()) {
216                     (*box)->set_custom_name(custom_name);
217                 }
218             }
219         }
220         catch (const std::exception &e) {
221             Gtk::MessageDialog md(*this, "Error importing", false /* use_markup */, Gtk::MESSAGE_ERROR,
222                                   Gtk::BUTTONS_OK);
223             md.set_secondary_text(e.what());
224             md.run();
225         }
226         catch (...) {
227             Gtk::MessageDialog md(*this, "Error importing", false /* use_markup */, Gtk::MESSAGE_ERROR,
228                                   Gtk::BUTTONS_OK);
229             md.set_secondary_text("unknown error");
230             md.run();
231         }
232     }
233 }
234 
SymbolPinNamesWindow(Gtk::Window * parent,ImpInterface * intf,SchematicSymbol & s)235 SymbolPinNamesWindow::SymbolPinNamesWindow(Gtk::Window *parent, ImpInterface *intf, SchematicSymbol &s)
236     : ToolWindow(parent, intf), sym(s)
237 {
238     set_title("Symbol " + s.component->refdes + s.gate->suffix + " pin names");
239     set_default_size(300, 700);
240     auto box = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_VERTICAL, 0));
241 
242     auto mode_combo = Gtk::manage(new Gtk::ComboBoxText());
243 
244     for (const auto &it : object_descriptions.at(ObjectType::SCHEMATIC_SYMBOL)
245                                   .properties.at(ObjectProperty::ID::PIN_NAME_DISPLAY)
246                                   .enum_items) {
247         mode_combo->append(std::to_string(it.first), it.second);
248     }
249     mode_combo->set_active_id(std::to_string(static_cast<int>(sym.pin_display_mode)));
250     mode_combo->signal_changed().connect([this, mode_combo] {
251         sym.pin_display_mode = static_cast<SchematicSymbol::PinDisplayMode>(std::stoi(mode_combo->get_active_id()));
252         emit_event(ToolDataWindow::Event::UPDATE);
253     });
254 
255     auto box2 = Gtk::manage(new Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 4));
256     box2->property_margin() = 8;
257     box->pack_start(*box2, false, false, 0);
258     box2->pack_start(*mode_combo, true, true, 0);
259 
260     auto import_button = Gtk::manage(new Gtk::Button("Import custom pin names"));
261     import_button->signal_clicked().connect(sigc::mem_fun(*this, &SymbolPinNamesWindow::handle_import));
262     box2->pack_start(*import_button, false, false, 0);
263 
264     auto sep = Gtk::manage(new Gtk::Separator(Gtk::ORIENTATION_HORIZONTAL));
265     box->pack_start(*sep, false, false, 0);
266 
267     editor = Gtk::manage(new GatePinEditor(sym.component, sym.gate));
268     editor->signal_changed().connect([this] { emit_event(ToolDataWindow::Event::UPDATE); });
269     editor->signal_selected_rows_changed().connect([this] { emit_event(ToolDataWindow::Event::UPDATE); });
270     auto sc = Gtk::manage(new Gtk::ScrolledWindow());
271     sc->set_policy(Gtk::POLICY_NEVER, Gtk::POLICY_AUTOMATIC);
272     sc->add(*editor);
273     box->pack_start(*sc, true, true, 0);
274 
275 
276     add(*box);
277 
278     show_all();
279 }
280 
ensure_row_visible(GtkListBox * box,GtkListBoxRow * row)281 static void ensure_row_visible(GtkListBox *box, GtkListBoxRow *row)
282 {
283     GtkWidget *header;
284     gint y, height;
285     GtkAllocation allocation;
286 
287     GtkAdjustment *adjustment = gtk_list_box_get_adjustment(box);
288 
289     gtk_widget_get_allocation(GTK_WIDGET(row), &allocation);
290     y = allocation.y;
291     height = allocation.height;
292 
293     /* If the row has a header, we want to ensure that it is visible as well. */
294     header = gtk_list_box_row_get_header(row);
295     if (GTK_IS_WIDGET(header) && gtk_widget_is_drawable(header)) {
296         gtk_widget_get_allocation(header, &allocation);
297         y = allocation.y;
298         height += allocation.height;
299     }
300 
301     gtk_adjustment_clamp_page(adjustment, y, y + height);
302 }
303 
go_to_pin(const UUID & uu)304 void SymbolPinNamesWindow::go_to_pin(const UUID &uu)
305 {
306     for (auto ed : editor->get_boxes()) {
307         if (ed->get_pin().uuid == uu) {
308             editor->unselect_all();
309             auto row = dynamic_cast<Gtk::ListBoxRow *>(ed->get_ancestor(GTK_TYPE_LIST_BOX_ROW));
310             editor->select_row(*row);
311             ensure_row_visible(editor->gobj(), row->gobj());
312             break;
313         }
314     }
315 }
316 
get_selected_pin()317 UUID SymbolPinNamesWindow::get_selected_pin()
318 {
319     auto row = editor->get_selected_row();
320     if (row) {
321         auto row2 = dynamic_cast<GatePinRow *>(row->get_child());
322         return row2->get_box()->get_pin().uuid;
323     }
324     else {
325         return UUID();
326     }
327 }
328 
329 } // namespace horizon
330