1 #include "part_editor.hpp"
2 #include "dialogs/pool_browser_dialog.hpp"
3 #include "widgets/pool_browser_package.hpp"
4 #include "widgets/pool_browser_part.hpp"
5 #include "widgets/tag_entry.hpp"
6 #include <iostream>
7 #include "pool/part.hpp"
8 #include "pool/pool_parametric.hpp"
9 #include "util/util.hpp"
10 #include "util/gtk_util.hpp"
11 #include <glibmm.h>
12 #include "util/pool_completion.hpp"
13 #include "parametric.hpp"
14 #include "pool/ipool.hpp"
15 
16 namespace horizon {
17 class EntryWithInheritance : public Gtk::Box {
18 public:
EntryWithInheritance()19     EntryWithInheritance()
20         : Glib::ObjectBase(typeid(EntryWithInheritance)), Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0),
21           p_property_inherit(*this, "inherit"), p_property_can_inherit(*this, "can-inherit")
22     {
23         entry = Gtk::manage(new Gtk::Entry);
24         entry_add_sanitizer(entry);
25         button = Gtk::manage(new Gtk::ToggleButton("Inherit"));
26 
27         pack_start(*entry, true, true, 0);
28         pack_start(*button, false, false, 0);
29         get_style_context()->add_class("linked");
30         entry->show();
31         button->show();
32 
33         inh_binding = Glib::Binding::bind_property(button->property_active(), property_inherit(),
34                                                    Glib::BINDING_BIDIRECTIONAL);
35         property_inherit().signal_changed().connect([this] {
36             entry->set_sensitive(!property_inherit());
37             if (property_inherit()) {
38                 text_this = entry->get_text();
39                 entry->set_text(text_inherit);
40             }
41             else {
42                 entry->set_text(text_this);
43             }
44         });
45 
46         property_can_inherit().signal_changed().connect([this] {
47             button->set_sensitive(property_can_inherit());
48             if (!property_can_inherit()) {
49                 property_inherit() = false;
50             }
51         });
52     }
set_text_inherit(const std::string & s)53     void set_text_inherit(const std::string &s)
54     {
55         text_inherit = s;
56         if (property_inherit()) {
57             entry->set_text(text_inherit);
58         }
59     }
60 
set_text_this(const std::string & s)61     void set_text_this(const std::string &s)
62     {
63         text_this = s;
64         if (!property_inherit()) {
65             entry->set_text(text_this);
66         }
67     }
68 
get_as_pair()69     std::pair<bool, std::string> get_as_pair()
70     {
71         if (property_inherit()) {
72             return {true, text_this};
73         }
74         else {
75             return {false, entry->get_text()};
76         }
77     }
78 
79     Gtk::Entry *entry = nullptr;
80     Gtk::ToggleButton *button = nullptr;
81 
82 
property_inherit()83     Glib::PropertyProxy<bool> property_inherit()
84     {
85         return p_property_inherit.get_proxy();
86     }
property_can_inherit()87     Glib::PropertyProxy<bool> property_can_inherit()
88     {
89         return p_property_can_inherit.get_proxy();
90     }
91 
92 private:
93     Glib::Property<bool> p_property_inherit;
94     Glib::Property<bool> p_property_can_inherit;
95     Glib::RefPtr<Glib::Binding> inh_binding;
96     std::string text_inherit;
97     std::string text_this;
98 };
99 
100 class OrderableMPNEditor : public Gtk::Box, public Changeable {
101 public:
OrderableMPNEditor(Part & p,const UUID & uu)102     OrderableMPNEditor(Part &p, const UUID &uu) : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0), part(p), uuid(uu)
103     {
104         get_style_context()->add_class("linked");
105         entry = Gtk::manage(new Gtk::Entry);
106         entry->show();
107         entry->set_text(part.orderable_MPNs.at(uuid));
108         entry->signal_changed().connect([this] { s_signal_changed.emit(); });
109         entry_add_sanitizer(entry);
110         pack_start(*entry, true, true, 0);
111 
112         auto bu = Gtk::manage(new Gtk::Button());
113         bu->set_image_from_icon_name("list-remove-symbolic");
114         bu->show();
115         pack_start(*bu, false, false, 0);
116         bu->signal_clicked().connect([this] {
117             part.orderable_MPNs.erase(uuid);
118             s_signal_changed.emit();
119             delete this;
120         });
121     }
get_MPN()122     std::string get_MPN()
123     {
124         return entry->get_text();
125     }
get_uuid() const126     const UUID &get_uuid() const
127     {
128         return uuid;
129     }
130 
focus()131     void focus()
132     {
133         entry->grab_focus();
134     }
135 
136 private:
137     Part &part;
138     UUID uuid;
139     Gtk::Entry *entry = nullptr;
140 };
141 
142 class FlagEditor : public Gtk::Box, public Changeable {
143 public:
FlagEditor(Part::FlagState & a_state,bool has_inherit)144     FlagEditor(Part::FlagState &a_state, bool has_inherit) : Gtk::Box(Gtk::ORIENTATION_HORIZONTAL, 0), state(a_state)
145     {
146         get_style_context()->add_class("linked");
147         static const std::vector<std::pair<Part::FlagState, std::string>> states = {
148                 {Part::FlagState::SET, "Yes"},
149                 {Part::FlagState::CLEAR, "No"},
150                 {Part::FlagState::INHERIT, "Inherit"},
151         };
152         Gtk::RadioButton *group = nullptr;
153         for (const auto &[st, name] : states) {
154             auto bu = Gtk::manage(new Gtk::RadioButton(name));
155             bu->set_mode(false);
156             if (group)
157                 bu->join_group(*group);
158             else
159                 group = bu;
160             if (st == Part::FlagState::INHERIT && !has_inherit)
161                 bu->set_sensitive(false);
162             if (state == st)
163                 bu->set_active(true);
164             bu->show();
165             pack_start(*bu, false, false, 0);
166             Part::FlagState st2 = st;
167             bu->signal_toggled().connect([this, bu, st2] {
168                 if (bu->get_active()) {
169                     state = st2;
170                     s_signal_changed.emit();
171                 }
172             });
173         }
174     }
175 
176 private:
177     Part::FlagState &state;
178 };
179 
PartEditor(BaseObjectType * cobject,const Glib::RefPtr<Gtk::Builder> & x,Part & p,IPool & po,PoolParametric & pp)180 PartEditor::PartEditor(BaseObjectType *cobject, const Glib::RefPtr<Gtk::Builder> &x, Part &p, IPool &po,
181                        PoolParametric &pp)
182     : Gtk::Box(cobject), part(p), pool(po), pool_parametric(pp)
183 {
184 
185     auto add_entry = [x](const char *name) {
186         Gtk::Box *t;
187         x->get_widget(name, t);
188         auto e = Gtk::manage(new EntryWithInheritance);
189         e->show();
190         t->pack_start(*e, true, true, 0);
191         return e;
192     };
193 
194     x->get_widget("entity_label", w_entity_label);
195     x->get_widget("package_label", w_package_label);
196     x->get_widget("change_package_button", w_change_package_button);
197     x->get_widget("model_combo", w_model_combo);
198     x->get_widget("model_inherit", w_model_inherit);
199     x->get_widget("base_label", w_base_label);
200     {
201         Gtk::Box *tag_box;
202         x->get_widget("tags", tag_box);
203         w_tags = Gtk::manage(new TagEntry(pool, ObjectType::PART, true));
204         w_tags->show();
205         tag_box->pack_start(*w_tags, true, true, 0);
206     }
207     x->get_widget("tags_inherit", w_tags_inherit);
208     x->get_widget("tags_inherited", w_tags_inherited);
209     x->get_widget("tv_pins", w_tv_pins);
210     x->get_widget("tv_pads", w_tv_pads);
211     x->get_widget("button_map", w_button_map);
212     x->get_widget("button_unmap", w_button_unmap);
213     x->get_widget("button_automap", w_button_automap);
214     x->get_widget("button_select_pin", w_button_select_pin);
215     x->get_widget("button_select_pads", w_button_select_pads);
216     x->get_widget("button_copy_from_other", w_button_copy_from_other);
217     x->get_widget("pin_stat", w_pin_stat);
218     x->get_widget("pad_stat", w_pad_stat);
219     x->get_widget("parametric_box", w_parametric_box);
220     x->get_widget("parametric_table_combo", w_parametric_table_combo);
221     x->get_widget("copy_parametric_from_base", w_parametric_from_base);
222     x->get_widget("orderable_MPNs_label", w_orderable_MPNs_label);
223     x->get_widget("orderable_MPNs_box", w_orderable_MPNs_box);
224     x->get_widget("orderable_MPNs_add_button", w_orderable_MPNs_add_button);
225     x->get_widget("flags_grid", w_flags_grid);
226     x->get_widget("flags_label", w_flags_label);
227     w_parametric_from_base->hide();
228 
229     w_entity_label->set_track_visited_links(false);
230     w_entity_label->signal_activate_link().connect(
231             [this](const std::string &url) {
232                 s_signal_goto.emit(ObjectType::ENTITY, UUID(url));
233                 return true;
234             },
235             false);
236     w_base_label->set_track_visited_links(false);
237     w_base_label->signal_activate_link().connect(
238             [this](const std::string &url) {
239                 s_signal_goto.emit(ObjectType::PART, UUID(url));
240                 return true;
241             },
242             false);
243     w_package_label->set_track_visited_links(false);
244     w_package_label->signal_activate_link().connect(
245             [this](const std::string &url) {
246                 s_signal_goto.emit(ObjectType::PACKAGE, UUID(url));
247                 return true;
248             },
249             false);
250 
251     w_mpn = add_entry("part_mpn_box");
252     w_value = add_entry("part_value_box");
253     w_manufacturer = add_entry("part_manufacturer_box");
254     w_manufacturer->entry->set_completion(create_pool_manufacturer_completion(pool));
255     w_datasheet = add_entry("part_datasheet_box");
256     w_description = add_entry("part_description_box");
257 
258     attr_editors.emplace(horizon::Part::Attribute::MPN, w_mpn);
259     attr_editors.emplace(horizon::Part::Attribute::VALUE, w_value);
260     attr_editors.emplace(horizon::Part::Attribute::MANUFACTURER, w_manufacturer);
261     attr_editors.emplace(horizon::Part::Attribute::DESCRIPTION, w_description);
262     attr_editors.emplace(horizon::Part::Attribute::DATASHEET, w_datasheet);
263 
264 
265     for (auto &it : attr_editors) {
266         it.second->property_can_inherit() = part.base;
267         it.second->set_text_this(part.attributes.at(it.first).second);
268         if (part.base) {
269             it.second->set_text_inherit(part.base->attributes.at(it.first).second);
270             it.second->property_inherit() = part.attributes.at(it.first).first;
271         }
272         it.second->entry->signal_changed().connect([this, it] {
273             part.attributes[it.first] = it.second->get_as_pair();
274             set_needs_save();
275         });
276         it.second->button->signal_toggled().connect([this, it] {
277             part.attributes[it.first] = it.second->get_as_pair();
278             set_needs_save();
279         });
280     }
281 
282     update_entries();
283 
284     w_change_package_button->signal_clicked().connect(sigc::mem_fun(*this, &PartEditor::change_package));
285 
286     w_tags_inherit->set_active(part.inherit_tags);
287     w_tags_inherit->signal_toggled().connect([this] { set_needs_save(); });
288 
289     w_tags->set_tags(part.tags);
290     w_tags->signal_changed().connect([this] {
291         part.tags = w_tags->get_tags();
292         set_needs_save();
293     });
294 
295     pin_store = Gtk::ListStore::create(pin_list_columns);
296     pin_store->set_sort_func(pin_list_columns.pin_name,
297                              [this](const Gtk::TreeModel::iterator &ia, const Gtk::TreeModel::iterator &ib) {
298                                  Gtk::TreeModel::Row ra = *ia;
299                                  Gtk::TreeModel::Row rb = *ib;
300                                  Glib::ustring a = ra[pin_list_columns.pin_name];
301                                  Glib::ustring b = rb[pin_list_columns.pin_name];
302                                  return strcmp_natural(a, b);
303                              });
304     w_tv_pins->set_model(pin_store);
305 
306     w_tv_pins->append_column("Gate", pin_list_columns.gate_name);
307     w_tv_pins->append_column("Pin", pin_list_columns.pin_name);
308     {
309         auto cr = Gtk::manage(new Gtk::CellRendererPixbuf());
310         cr->property_icon_name() = "object-select-symbolic";
311         cr->property_xalign() = 0;
312         auto tvc = Gtk::manage(new Gtk::TreeViewColumn("Mapped", *cr));
313         tvc->add_attribute(cr->property_visible(), pin_list_columns.mapped);
314         w_tv_pins->append_column(*tvc);
315     }
316 
317     w_tv_pins->get_column(0)->set_sort_column(pin_list_columns.gate_name);
318     w_tv_pins->get_column(1)->set_sort_column(pin_list_columns.pin_name);
319 
320     pin_store->set_sort_column(pin_list_columns.pin_name, Gtk::SORT_ASCENDING);
321     Glib::signal_timeout().connect_once(
322             sigc::track_obj([this] { pin_store->set_sort_column(pin_list_columns.gate_name, Gtk::SORT_ASCENDING); },
323                             *this),
324             10);
325 
326     pad_store = Gtk::ListStore::create(pad_list_columns);
327     pad_store->set_sort_func(pad_list_columns.pad_name,
328                              [this](const Gtk::TreeModel::iterator &ia, const Gtk::TreeModel::iterator &ib) {
329                                  Gtk::TreeModel::Row ra = *ia;
330                                  Gtk::TreeModel::Row rb = *ib;
331                                  Glib::ustring a = ra[pad_list_columns.pad_name];
332                                  Glib::ustring b = rb[pad_list_columns.pad_name];
333                                  return strcmp_natural(a, b);
334                              });
335     w_tv_pads->set_model(pad_store);
336     w_tv_pads->append_column("Pad", pad_list_columns.pad_name);
337 
338     w_tv_pads->append_column("Gate", pad_list_columns.gate_name);
339     w_tv_pads->append_column("Pin", pad_list_columns.pin_name);
340     w_tv_pads->get_column(0)->set_sort_column(pad_list_columns.pad_name);
341 
342     pad_store->set_sort_column(pad_list_columns.pad_name, Gtk::SORT_ASCENDING);
343 
344     update_treeview();
345 
346     update_map_buttons();
347     w_tv_pads->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PartEditor::update_map_buttons));
348     w_tv_pins->get_selection()->signal_changed().connect(sigc::mem_fun(*this, &PartEditor::update_map_buttons));
349     w_change_package_button->set_sensitive(!part.base);
350 
351     w_button_unmap->signal_clicked().connect([this] {
352         auto sel = w_tv_pads->get_selection();
353         for (auto &path : sel->get_selected_rows()) {
354             auto it = pad_store->get_iter(path);
355             Gtk::TreeModel::Row row = *it;
356             row[pad_list_columns.gate_name] = "";
357             row[pad_list_columns.pin_name] = "";
358             row[pad_list_columns.pin_uuid] = UUID();
359             row[pad_list_columns.gate_uuid] = UUID();
360         }
361         update_mapped();
362         set_needs_save();
363     });
364 
365     w_tv_pins->signal_row_activated().connect([this](const Gtk::TreeModel::Path &path, Gtk::TreeViewColumn *col) {
366         auto it_pin = pin_store->get_iter(path);
367         map_pin(it_pin);
368     });
369 
370     w_button_map->signal_clicked().connect([this] {
371         auto pin_sel = w_tv_pins->get_selection();
372         auto it_pin = pin_sel->get_selected();
373         map_pin(it_pin);
374     });
375 
376     w_button_automap->signal_clicked().connect([this] {
377         auto sel = w_tv_pads->get_selection();
378         for (auto &path : sel->get_selected_rows()) {
379             auto it = pad_store->get_iter(path);
380             Gtk::TreeModel::Row row = *it;
381             Glib::ustring pad_name = row[pad_list_columns.pad_name];
382             for (const auto &it_pin : pin_store->children()) {
383                 Gtk::TreeModel::Row row_pin = *it_pin;
384                 Glib::ustring pin_name = row_pin[pin_list_columns.pin_name];
385                 if (pin_name == pad_name) {
386                     row[pad_list_columns.gate_name] = row_pin.get_value(pin_list_columns.gate_name);
387                     row[pad_list_columns.pin_name] = row_pin.get_value(pin_list_columns.pin_name);
388                     row[pad_list_columns.pin_uuid] = row_pin.get_value(pin_list_columns.pin_uuid);
389                     row[pad_list_columns.gate_uuid] = row_pin.get_value(pin_list_columns.gate_uuid);
390                     break;
391                 }
392             }
393         }
394         update_mapped();
395         set_needs_save();
396     });
397 
398     w_button_select_pin->signal_clicked().connect([this] {
399         auto sel = w_tv_pads->get_selection();
400         auto rows = sel->get_selected_rows();
401         if (rows.size() != 1)
402             return;
403         auto it_pad = pad_store->get_iter(rows.front());
404         Gtk::TreeModel::Row row_pad = *it_pad;
405         auto gate_uuid = row_pad[pad_list_columns.gate_uuid];
406         auto pin_uuid = row_pad[pad_list_columns.pin_uuid];
407         for (auto &it : pin_store->children()) {
408             Gtk::TreeModel::Row row = *it;
409             if (row[pin_list_columns.gate_uuid] == gate_uuid && row[pin_list_columns.pin_uuid] == pin_uuid) {
410                 w_tv_pins->get_selection()->select(it);
411                 tree_view_scroll_to_selection(w_tv_pins);
412                 break;
413             }
414         }
415     });
416 
417     w_button_select_pads->signal_clicked().connect([this] {
418         auto sel = w_tv_pins->get_selection();
419         auto rows = sel->get_selected_rows();
420         if (rows.size() != 1)
421             return;
422         auto it_pin = pin_store->get_iter(rows.front());
423         Gtk::TreeModel::Row row_pin = *it_pin;
424         auto gate_uuid = row_pin[pin_list_columns.gate_uuid];
425         auto pin_uuid = row_pin[pin_list_columns.pin_uuid];
426 
427         w_tv_pads->get_selection()->unselect_all();
428         for (auto &it : pad_store->children()) {
429             Gtk::TreeModel::Row row = *it;
430             if (row[pad_list_columns.gate_uuid] == gate_uuid && row[pad_list_columns.pin_uuid] == pin_uuid) {
431                 w_tv_pads->get_selection()->select(it);
432             }
433         }
434         tree_view_scroll_to_selection(w_tv_pads);
435     });
436     w_button_copy_from_other->signal_clicked().connect(sigc::mem_fun(*this, &PartEditor::copy_from_other_part));
437 
438     update_mapped();
439     populate_models();
440     w_model_combo->set_active_id((std::string)part.model);
441 
442     if (part.base) {
443         w_model_inherit->set_active(part.inherit_model);
444         w_model_inherit->signal_toggled().connect([this] {
445             set_needs_save();
446             update_model_inherit();
447         });
448         update_model_inherit();
449     }
450     else {
451         w_model_inherit->set_sensitive(false);
452     }
453 
454     w_model_combo->signal_changed().connect([this] { set_needs_save(); });
455 
456     w_parametric_table_combo->append("", "None");
457     for (const auto &it : pool_parametric.get_tables()) {
458         w_parametric_table_combo->append(it.first, it.second.display_name);
459     }
460     w_parametric_table_combo->set_active_id("");
461     if (part.parametric.count("table")) {
462         std::string tab = part.parametric.at("table");
463         if (pool_parametric.get_tables().count(tab)) {
464             w_parametric_table_combo->set_active_id(tab);
465         }
466     }
467     update_parametric_editor();
468     w_parametric_table_combo->signal_changed().connect([this] {
469         set_needs_save();
470         update_parametric_editor();
471     });
472     w_parametric_from_base->set_sensitive(part.base);
473     w_parametric_from_base->signal_clicked().connect([this] {
474         if (part.base) {
475             set_needs_save();
476         }
477     });
478 
479     w_orderable_MPNs_add_button->signal_clicked().connect([this] {
480         auto uu = UUID::random();
481         part.orderable_MPNs.emplace(uu, "");
482         auto ed = create_orderable_MPN_editor(uu);
483         ed->focus();
484         set_needs_save();
485         update_orderable_MPNs_label();
486     });
487 
488     for (const auto &it : part.orderable_MPNs) {
489         create_orderable_MPN_editor(it.first);
490     }
491     update_orderable_MPNs_label();
492 
493     {
494         static const std::map<Part::Flag, std::string> flag_names = {
495                 {Part::Flag::BASE_PART, "Base part"},
496                 {Part::Flag::EXCLUDE_BOM, "Exclude from BOM"},
497                 {Part::Flag::EXCLUDE_PNP, "Exclude from Pick&Place"},
498         };
499         int top = 0;
500         for (const auto &[fl, name] : flag_names) {
501             auto ed = Gtk::manage(new FlagEditor(part.flags.at(fl), part.base));
502             ed->signal_changed().connect([this] {
503                 set_needs_save();
504                 update_flags_label();
505             });
506             grid_attach_label_and_widget(w_flags_grid, name, ed, top);
507         }
508     }
509     update_flags_label();
510 }
create_orderable_MPN_editor(const UUID & uu)511 class OrderableMPNEditor *PartEditor::create_orderable_MPN_editor(const UUID &uu)
512 {
513     auto ed = Gtk::manage(new OrderableMPNEditor(part, uu));
514     w_orderable_MPNs_box->pack_start(*ed, false, false, 0);
515     ed->signal_changed().connect([this, ed] {
516         if (part.orderable_MPNs.count(ed->get_uuid()))
517             part.orderable_MPNs.at(ed->get_uuid()) = ed->get_MPN();
518         set_needs_save();
519         update_orderable_MPNs_label();
520     });
521     ed->show();
522     return ed;
523 }
524 
update_map_buttons()525 void PartEditor::update_map_buttons()
526 {
527     if (part.base) {
528         w_button_map->set_sensitive(false);
529         w_button_unmap->set_sensitive(false);
530         w_button_automap->set_sensitive(false);
531         w_button_copy_from_other->set_sensitive(false);
532     }
533     else {
534         bool can_map =
535                 w_tv_pads->get_selection()->count_selected_rows() && w_tv_pins->get_selection()->count_selected_rows();
536         w_button_map->set_sensitive(can_map);
537         w_button_unmap->set_sensitive(w_tv_pads->get_selection()->count_selected_rows());
538         w_button_automap->set_sensitive(w_tv_pads->get_selection()->count_selected_rows());
539         w_button_copy_from_other->set_sensitive(true);
540     }
541     w_button_select_pads->set_sensitive(w_tv_pins->get_selection()->count_selected_rows());
542     w_button_select_pin->set_sensitive(w_tv_pads->get_selection()->count_selected_rows() == 1);
543 }
544 
update_orderable_MPNs_label()545 void PartEditor::update_orderable_MPNs_label()
546 {
547     std::string s;
548     for (const auto &[uu, mpn] : part.orderable_MPNs) {
549         s += Glib::Markup::escape_text(mpn) + ", ";
550     }
551     if (s.size()) {
552         s.pop_back();
553         s.pop_back();
554     }
555     else {
556         s = "<i>no orderable MPNs defined</i>";
557     }
558     w_orderable_MPNs_label->set_markup(s);
559 }
560 
update_flags_label()561 void PartEditor::update_flags_label()
562 {
563     std::string s;
564     static const std::map<Part::Flag, std::string> flag_names = {
565             {Part::Flag::BASE_PART, "Base part"},
566             {Part::Flag::EXCLUDE_BOM, "No BOM"},
567             {Part::Flag::EXCLUDE_PNP, "No Pick&Place"},
568     };
569     for (const auto &[fl, name] : flag_names) {
570         if (part.get_flag(fl)) {
571             s += Glib::Markup::escape_text(name);
572             s += ", ";
573         }
574     }
575     if (s.size()) {
576         s.pop_back();
577         s.pop_back();
578     }
579     else {
580         s = "<i>none set</i>";
581     }
582     w_flags_label->set_markup(s);
583 }
584 
map_pin(Gtk::TreeModel::iterator it_pin)585 void PartEditor::map_pin(Gtk::TreeModel::iterator it_pin)
586 {
587     auto pin_sel = w_tv_pins->get_selection();
588     if (it_pin) {
589         Gtk::TreeModel::Row row_pin = *it_pin;
590         auto sel = w_tv_pads->get_selection();
591         for (auto &path : sel->get_selected_rows()) {
592             auto it = pad_store->get_iter(path);
593             Gtk::TreeModel::Row row = *it;
594             row[pad_list_columns.gate_name] = row_pin.get_value(pin_list_columns.gate_name);
595             row[pad_list_columns.pin_name] = row_pin.get_value(pin_list_columns.pin_name);
596             row[pad_list_columns.pin_uuid] = row_pin.get_value(pin_list_columns.pin_uuid);
597             row[pad_list_columns.gate_uuid] = row_pin.get_value(pin_list_columns.gate_uuid);
598         }
599         if (++it_pin) {
600             pin_sel->select(it_pin);
601         }
602         if (sel->count_selected_rows() == 1) {
603             auto it_pad = pad_store->get_iter(sel->get_selected_rows().at(0));
604             sel->unselect(it_pad);
605             if (++it_pad) {
606                 sel->select(it_pad);
607             }
608         }
609         update_mapped();
610         set_needs_save();
611     }
612 }
613 
update_model_inherit()614 void PartEditor::update_model_inherit()
615 {
616     auto active = w_model_inherit->get_active();
617     if (active) {
618         w_model_combo->set_active_id((std::string)part.base->model);
619     }
620     w_model_combo->set_sensitive(!active);
621 }
622 
append_with_slash(const std::string & s)623 static std::string append_with_slash(const std::string &s)
624 {
625     if (s.size())
626         return " / " + s;
627     else
628         return "";
629 }
630 
update_entries()631 void PartEditor::update_entries()
632 {
633 
634 
635     if (part.base) {
636         w_base_label->set_markup(
637                 "<a href=\"" + (std::string)part.base->uuid + "\">"
638                 + Glib::Markup::escape_text(part.base->get_MPN() + append_with_slash(part.base->get_manufacturer()))
639                 + "</a>");
640         w_entity_label->set_markup("<a href=\"" + (std::string)part.base->entity->uuid + "\">"
641                                    + Glib::Markup::escape_text(part.base->entity->name
642                                                                + append_with_slash(part.base->entity->manufacturer))
643                                    + "</a>");
644         w_package_label->set_markup("<a href=\"" + (std::string)part.base->package->uuid + "\">"
645                                     + Glib::Markup::escape_text(part.base->package->name
646                                                                 + append_with_slash(part.base->package->manufacturer))
647                                     + "</a>");
648     }
649     else {
650         w_base_label->set_text("none");
651         w_entity_label->set_markup(
652                 "<a href=\"" + (std::string)part.entity->uuid + "\">"
653                 + Glib::Markup::escape_text(part.entity->name + append_with_slash(part.entity->manufacturer)) + "</a>");
654         w_package_label->set_markup(
655                 "<a href=\"" + (std::string)part.package->uuid + "\">"
656                 + Glib::Markup::escape_text(part.package->name + append_with_slash(part.package->manufacturer))
657                 + "</a>");
658     }
659 
660     if (part.base) {
661         std::stringstream s;
662         auto tags_from_base = part.base->get_tags();
663         std::copy(tags_from_base.begin(), tags_from_base.end(), std::ostream_iterator<std::string>(s, " "));
664         w_tags_inherited->set_text(s.str());
665     }
666     else {
667         w_tags_inherited->set_text("");
668     }
669 
670     w_tags_inherit->set_sensitive(part.base);
671 }
672 
change_package()673 void PartEditor::change_package()
674 {
675     auto top = dynamic_cast<Gtk::Window *>(get_ancestor(GTK_TYPE_WINDOW));
676     PoolBrowserDialog dia(top, ObjectType::PACKAGE, pool);
677     if (dia.run() == Gtk::RESPONSE_OK) {
678         set_needs_save();
679         part.package = pool.get_package(dia.get_browser().get_selected());
680         auto ch = pad_store->children();
681         std::set<UUID> pads_exisiting;
682         for (auto it = ch.begin(); it != ch.end();) {
683             Gtk::TreeModel::Row row = *it;
684             auto pad_name = row[pad_list_columns.pad_name];
685             UUID pad_uuid;
686             for (const auto &it_pad : part.package->pads) {
687                 if (it_pad.second.name == pad_name) {
688                     pad_uuid = it_pad.second.uuid;
689                     break;
690                 }
691             }
692             if (pad_uuid) {
693                 row[pad_list_columns.pad_uuid] = pad_uuid;
694                 pads_exisiting.insert(pad_uuid);
695                 it++;
696             }
697             else {
698                 pad_store->erase(it++);
699             }
700         }
701         for (const auto &it : part.package->pads) {
702             if (pads_exisiting.count(it.first) == 0) {
703                 Gtk::TreeModel::Row row = *(pad_store->append());
704                 row[pad_list_columns.pad_uuid] = it.first;
705                 row[pad_list_columns.pad_name] = it.second.name;
706             }
707         }
708 
709         update_entries();
710         update_mapped();
711         populate_models();
712         w_model_combo->set_active_id((std::string)part.package->default_model);
713         set_needs_save();
714     }
715 }
716 
reload()717 void PartEditor::reload()
718 {
719     part.update_refs(pool);
720     update_entries();
721 }
722 
save()723 void PartEditor::save()
724 {
725     part.inherit_model = w_model_inherit->get_active();
726 
727     if (w_model_combo->get_active_row_number() != -1)
728         part.model = UUID(w_model_combo->get_active_id());
729     else
730         part.model = UUID();
731 
732     part.inherit_tags = w_tags_inherit->get_active();
733 
734     if (parametric_editor) {
735         part.parametric = parametric_editor->get_values();
736     }
737     else {
738         part.parametric.clear();
739     }
740 
741     PoolEditorInterface::save();
742 }
743 
744 
update_treeview()745 void PartEditor::update_treeview()
746 {
747     pin_store->freeze_notify();
748     pad_store->freeze_notify();
749 
750     pin_store->clear();
751     pad_store->clear();
752 
753     for (const auto &it_gate : part.entity->gates) {
754         for (const auto &it_pin : it_gate.second.unit->pins) {
755             Gtk::TreeModel::Row row = *(pin_store->append());
756             row[pin_list_columns.gate_uuid] = it_gate.first;
757             row[pin_list_columns.gate_name] = it_gate.second.name;
758             row[pin_list_columns.pin_uuid] = it_pin.first;
759             row[pin_list_columns.pin_name] = it_pin.second.primary_name;
760         }
761     }
762 
763     for (const auto &it : part.package->pads) {
764         if (it.second.pool_padstack->type != Padstack::Type::MECHANICAL) {
765             Gtk::TreeModel::Row row = *(pad_store->append());
766             row[pad_list_columns.pad_uuid] = it.first;
767             row[pad_list_columns.pad_name] = it.second.name;
768             if (part.pad_map.count(it.first)) {
769                 const auto &x = part.pad_map.at(it.first);
770                 row[pad_list_columns.gate_uuid] = x.gate->uuid;
771                 row[pad_list_columns.gate_name] = x.gate->name;
772                 row[pad_list_columns.pin_uuid] = x.pin->uuid;
773                 row[pad_list_columns.pin_name] = x.pin->primary_name;
774             }
775         }
776     }
777 
778     pad_store->thaw_notify();
779     pin_store->thaw_notify();
780 }
781 
update_mapped()782 void PartEditor::update_mapped()
783 {
784     std::set<std::pair<UUID, UUID>> pins_mapped;
785     int n_pads_not_mapped = 0;
786     for (const auto &it : pad_store->children()) {
787         if (it[pad_list_columns.gate_uuid] != UUID()) {
788             pins_mapped.emplace(it[pad_list_columns.gate_uuid], it[pad_list_columns.pin_uuid]);
789         }
790         else {
791             n_pads_not_mapped++;
792         }
793     }
794     for (auto &it : pin_store->children()) {
795         if (pins_mapped.count({it[pin_list_columns.gate_uuid], it[pin_list_columns.pin_uuid]})) {
796             it[pin_list_columns.mapped] = true;
797         }
798         else {
799             it[pin_list_columns.mapped] = false;
800         }
801     }
802     w_pin_stat->set_text(std::to_string(pin_store->children().size() - pins_mapped.size()) + " pins not mapped");
803     w_pad_stat->set_text(std::to_string(n_pads_not_mapped) + " pads not mapped");
804 
805     part.pad_map.clear();
806     for (const auto &it : pad_store->children()) {
807         if (it[pad_list_columns.gate_uuid] != UUID() && part.package->pads.count(it[pad_list_columns.pad_uuid])) {
808             const horizon::Gate *gate = &part.entity->gates.at(it[pad_list_columns.gate_uuid]);
809             const horizon::Pin *pin = &gate->unit->pins.at(it[pad_list_columns.pin_uuid]);
810             part.pad_map.emplace(it[pad_list_columns.pad_uuid], Part::PadMapItem(gate, pin));
811         }
812     }
813 }
814 
populate_models()815 void PartEditor::populate_models()
816 {
817     w_model_combo->remove_all();
818     for (const auto &it : part.package->models) {
819         w_model_combo->append((std::string)it.first, Glib::path_get_basename(it.second.filename));
820     }
821 }
822 
update_parametric_editor()823 void PartEditor::update_parametric_editor()
824 {
825     auto chs = w_parametric_box->get_children();
826     if (chs.size()) {
827         delete chs.back();
828     }
829     parametric_editor = nullptr;
830     auto tab = w_parametric_table_combo->get_active_id();
831     if (pool_parametric.get_tables().count(tab)) {
832         auto ed = Gtk::manage(new ParametricEditor(pool_parametric, tab));
833         ed->show();
834         w_parametric_box->pack_start(*ed, true, true, 0);
835         if (part.parametric.count("table") && part.parametric.at("table") == tab) {
836             ed->update(part.parametric);
837         }
838         parametric_editor = ed;
839         parametric_editor->signal_changed().connect([this] { set_needs_save(); });
840     }
841 }
842 
copy_from_other_part()843 void PartEditor::copy_from_other_part()
844 {
845     auto top = dynamic_cast<Gtk::Window *>(get_ancestor(GTK_TYPE_WINDOW));
846     PoolBrowserDialog dia(top, ObjectType::PART, pool);
847     auto &br = dynamic_cast<PoolBrowserPart &>(dia.get_browser());
848     br.set_entity_uuid(part.entity->uuid);
849     if (dia.run() == Gtk::RESPONSE_OK) {
850         auto uu = br.get_selected();
851         auto other_part = pool.get_part(uu);
852         for (auto &it_pad : pad_store->children()) {
853             Gtk::TreeModel::Row row_pad = *it_pad;
854             if (row_pad.get_value(pad_list_columns.gate_uuid) == UUID()) { // only update unmapped pads
855                 std::string pad_name = row_pad.get_value(pad_list_columns.pad_name);
856                 // find other part mapping
857                 for (const auto &it_map_other : other_part->pad_map) {
858                     auto pad_uu_other = it_map_other.first;
859                     const auto &pad_name_other = other_part->package->pads.at(pad_uu_other).name;
860                     if (pad_name_other == pad_name) { // found it
861                         row_pad[pad_list_columns.gate_name] = it_map_other.second.gate->name;
862                         row_pad[pad_list_columns.pin_name] = it_map_other.second.pin->primary_name;
863                         row_pad[pad_list_columns.pin_uuid] = it_map_other.second.pin->uuid;
864                         row_pad[pad_list_columns.gate_uuid] = it_map_other.second.gate->uuid;
865                         break;
866                     }
867                 }
868             }
869         }
870         update_mapped();
871         set_needs_save();
872     }
873 }
874 
create(Part & p,IPool & po,PoolParametric & pp)875 PartEditor *PartEditor::create(Part &p, IPool &po, PoolParametric &pp)
876 {
877     PartEditor *w;
878     Glib::RefPtr<Gtk::Builder> x = Gtk::Builder::create();
879     x->add_from_resource("/org/horizon-eda/horizon/pool-prj-mgr/pool-mgr/editors/part_editor.ui");
880     x->get_widget_derived("part_editor", w, p, po, pp);
881     w->reference();
882     return w;
883 }
884 } // namespace horizon
885